diff --git a/czsc/__init__.py b/czsc/__init__.py index fbe340ad8..da84e6324 100644 --- a/czsc/__init__.py +++ b/czsc/__init__.py @@ -15,6 +15,7 @@ from czsc.strategies import CzscStrategyBase, CzscJsonStrategy from czsc.sensors import holds_concepts_effect, CTAResearch, EventMatchSensor from czsc.sensors.feature import FixedNumberSelector +from czsc.utils import ta from czsc.traders import ( CzscTrader, CzscSignals, diff --git a/czsc/utils/ta.py b/czsc/utils/ta.py index 4430eb1de..075eac933 100644 --- a/czsc/utils/ta.py +++ b/czsc/utils/ta.py @@ -50,12 +50,12 @@ def EMA(close: np.array, timeperiod=5): return np.array(res, dtype=np.double).round(4) -def MACD(close: np.array, fastperiod=12, slowperiod=26, signalperiod=9): +def MACD(real: np.array, fastperiod=12, slowperiod=26, signalperiod=9): """MACD 异同移动平均线 https://baike.baidu.com/item/MACD%E6%8C%87%E6%A0%87/6271283 - :param close: np.array - 收盘价序列 + :param real: np.array + 价格序列 :param fastperiod: int 快周期,默认值 12 :param slowperiod: int @@ -65,8 +65,8 @@ def MACD(close: np.array, fastperiod=12, slowperiod=26, signalperiod=9): :return: (np.array, np.array, np.array) diff, dea, macd """ - ema12 = EMA(close, timeperiod=fastperiod) - ema26 = EMA(close, timeperiod=slowperiod) + ema12 = EMA(real, timeperiod=fastperiod) + ema26 = EMA(real, timeperiod=slowperiod) diff = ema12 - ema26 dea = EMA(diff, timeperiod=signalperiod) macd = (diff - dea) * 2 @@ -146,7 +146,7 @@ def RSQ(close: [np.array, list]) -> float: return round(rsq, 4) -def plus_di(high, low, close, timeperiod=14): +def PLUS_DI(high, low, close, timeperiod=14): """ Calculate Plus Directional Indicator (PLUS_DI) manually. @@ -179,7 +179,7 @@ def plus_di(high, low, close, timeperiod=14): return plus_di_ -def minus_di(high, low, close, timeperiod=14): +def MINUS_DI(high, low, close, timeperiod=14): """ Calculate Minus Directional Indicator (MINUS_DI) manually. @@ -213,7 +213,7 @@ def minus_di(high, low, close, timeperiod=14): return minus_di_ -def atr(high, low, close, timeperiod=14): +def ATR(high, low, close, timeperiod=14): """ Calculate Average True Range (ATR). @@ -234,7 +234,6 @@ def atr(high, low, close, timeperiod=14): # Calculate ATR atr_ = tr.rolling(window=timeperiod).mean() - return atr_ @@ -338,3 +337,88 @@ def LINEARREG_ANGLE(real, timeperiod=14): angles[today] = np.arctan(m) * (180.0 / np.pi) return angles + + +def DOUBLE_SMA_LS(series: pd.Series, n=5, m=20, **kwargs): + """双均线多空 + + :param series: str, 数据源字段 + :param n: int, 短周期 + :param m: int, 长周期 + """ + assert n < m, "短周期必须小于长周期" + return np.sign(series.rolling(window=n).mean() - series.rolling(window=m).mean()).fillna(0) + + +def BOLL_LS(series: pd.Series, n=5, s=0.1, **kwargs): + """布林线多空 + + series 大于 n 周期均线 + s * n周期标准差,做多;小于 n 周期均线 - s * n周期标准差,做空 + + :param series: str, 数据源字段 + :param n: int, 短周期 + :param s: int, 波动率的倍数,默认为 0.1 + """ + sm = series.rolling(window=n).mean() + sd = series.rolling(window=n).std() + return np.where(series > sm + s * sd, 1, np.where(series < sm - s * sd, -1, 0)) + + +def SMA_MIN_MAX_SCALE(series: pd.Series, timeperiod=5, window=5, **kwargs): + """均线的最大最小值归一化 + + :param series: str, 数据源字段 + :param timeperiod: int, 均线周期 + :param window: int, 窗口大小 + """ + sm = series.rolling(window=timeperiod).mean() + sm_min = sm.rolling(window=window).min() + sm_max = sm.rolling(window=window).max() + res = (sm - sm_min) / (sm_max - sm_min) + res = res.fillna(0) * 2 - 1 + return res + + +def RS_VOLATILITY(df: pd.DataFrame, timeperiod=30, **kwargs): + """RS 波动率,值越大,波动越大 + + :param df: str, 标准K线数据 + :param timeperiod: int, 周期 + """ + log_h_c = np.log(df["high"] / df["close"]) + log_h_o = np.log(df["high"] / df["open"]) + log_l_c = np.log(df["low"] / df["close"]) + log_l_o = np.log(df["low"] / df["open"]) + + x = log_h_c * log_h_o + log_l_c * log_l_o + res = np.sqrt(x.rolling(window=timeperiod).mean()) + return res + + +def PK_VOLATILITY(df: pd.DataFrame, timeperiod=30, **kwargs): + """PK 波动率,值越大,波动越大 + + :param df: str, 标准K线数据 + :param timeperiod: int, 周期 + """ + log_h_l = np.log(df["high"] / df["low"]).pow(2) + log_hl_mean = log_h_l.rolling(window=timeperiod).sum() / (4 * timeperiod * np.log(2)) + res = np.sqrt(log_hl_mean) + return res + + +try: + import talib as ta + + SMA = ta.SMA + EMA = ta.EMA + MACD = ta.MACD + LINEARREG_ANGLE = ta.LINEARREG_ANGLE +except ImportError: + SMA = SMA + EMA = EMA + MACD = MACD + print( + f"ta-lib 没有正确安装,将使用自定义分析函数。建议安装 ta-lib,可以大幅提升计算速度。" + f"请参考安装教程 https://blog.csdn.net/qaz2134560/article/details/98484091" + ) diff --git a/czsc/utils/ta1.py b/czsc/utils/ta1.py deleted file mode 100644 index 63bd47542..000000000 --- a/czsc/utils/ta1.py +++ /dev/null @@ -1,86 +0,0 @@ -# coding: utf-8 -""" - -常用技术分析指标:MA, MACD, BOLL - -使用 ta-lib 可以大幅提升计算性能,10倍左右 -""" -import numpy as np -import talib as ta - -SMA = ta.SMA -EMA = ta.EMA -MACD = ta.MACD - - -def KDJ(close: np.array, high: np.array, low: np.array): - """ - - :param close: 收盘价序列 - :param high: 最高价序列 - :param low: 最低价序列 - :return: - """ - n = 9 - hv = [] - lv = [] - for i in range(len(close)): - if i < n: - h_ = high[0: i+1] - l_ = low[0: i+1] - else: - h_ = high[i - n + 1: i + 1] - l_ = low[i - n + 1: i + 1] - hv.append(max(h_)) - lv.append(min(l_)) - - hv = np.around(hv, decimals=2) - lv = np.around(lv, decimals=2) - rsv = np.where(hv == lv, 0, (close - lv) / (hv - lv) * 100) - - k = [] - d = [] - j = [] - for i in range(len(rsv)): - if i < n: - k_ = rsv[i] - d_ = k_ - else: - k_ = (2 / 3) * k[i-1] + (1 / 3) * rsv[i] - d_ = (2 / 3) * d[i-1] + (1 / 3) * k_ - - k.append(k_) - d.append(d_) - j.append(3 * k_ - 2 * d_) - - k = np.array(k, dtype=np.double) - d = np.array(d, dtype=np.double) - j = np.array(j, dtype=np.double) - return k, d, j - - -def RSQ(close: [np.array, list]) -> float: - """拟合优度 R SQuare - - :param close: 收盘价序列 - :return: - """ - x = list(range(len(close))) - y = np.array(close) - x_squred_sum = sum([x1 * x1 for x1 in x]) - xy_product_sum = sum([x[i] * y[i] for i in range(len(x))]) - num = len(x) - x_sum = sum(x) - y_sum = sum(y) - delta = float(num * x_squred_sum - x_sum * x_sum) - if delta == 0: - return 0 - y_intercept = (1 / delta) * (x_squred_sum * y_sum - x_sum * xy_product_sum) - slope = (1 / delta) * (num * xy_product_sum - x_sum * y_sum) - - y_mean = np.mean(y) - ss_tot = sum([(y1 - y_mean) * (y1 - y_mean) for y1 in y]) + 0.00001 - ss_err = sum([(y[i] - slope * x[i] - y_intercept) * (y[i] - slope * x[i] - y_intercept) for i in range(len(x))]) - rsq = 1 - ss_err / ss_tot - - return round(rsq, 4) diff --git "a/examples/develop/\345\257\271\346\257\224numpy\344\270\216talib\347\232\204\351\200\237\345\272\246.py" "b/examples/develop/\345\257\271\346\257\224numpy\344\270\216talib\347\232\204\351\200\237\345\272\246.py" index 75a47430b..1f2700062 100644 --- "a/examples/develop/\345\257\271\346\257\224numpy\344\270\216talib\347\232\204\351\200\237\345\272\246.py" +++ "b/examples/develop/\345\257\271\346\257\224numpy\344\270\216talib\347\232\204\351\200\237\345\272\246.py" @@ -1,4 +1,5 @@ import sys +import time sys.path.insert(0, r"A:\ZB\git_repo\waditu\czsc") import talib @@ -6,14 +7,83 @@ from czsc.connectors import cooperation as coo -df = coo.get_raw_bars(symbol="SFIC9001", freq="30分钟", fq="后复权", sdt="20100101", edt="20210301", raw_bars=False) +df = coo.get_raw_bars(symbol="SFIC9001", freq="日线", fq="后复权", sdt="20100101", edt="20210301", raw_bars=False) -def test_with_numpy(): +def test_vs_volatility(): df1 = df.copy() - df1["x"] = ta.LINEARREG_ANGLE(df["close"].values, 10) + df1["STD"] = df["close"].pct_change().rolling(30).std() + df1["RS"] = ta.RS_VOLATILITY(df1, timeperiod=30) + df1["PK"] = ta.PK_VOLATILITY(df1, timeperiod=30) + df1[["STD", "RS", "PK"]].corr() -def test_with_talib(): +def test_compare_LINEARREG_ANGLE(): df1 = df.copy() - df1["x"] = talib.LINEARREG_ANGLE(df["close"].values, 10) + s1 = time.time() + df1["x1"] = ta.LINEARREG_ANGLE(df["close"].values, 10) + e1 = time.time() - s1 + + s2 = time.time() + df1["x2"] = talib.LINEARREG_ANGLE(df["close"].values, 10) + e2 = time.time() - s2 + + print(f"计算时间差异,ta: {e1}, talib: {e2};相差:{e1 - e2}") + df1["diff"] = df1["x1"] - df1["x2"] + assert df1["diff"].abs().max() < 1e-6 + print(df1["diff"].abs().max()) + + +def test_compare_CCI(): + # 数据对不上,需要调整 + df1 = df.copy() + s1 = time.time() + df1["x1"] = ta.CCI(df1["high"].values, df1["low"].values, df1["close"].values, timeperiod=14) + e1 = time.time() - s1 + + s2 = time.time() + df1["x2"] = talib.CCI(df1["high"].values, df1["low"].values, df1["close"].values, timeperiod=14) + e2 = time.time() - s2 + + print(f"计算时间差异,ta: {e1}, talib: {e2};相差:{e1 - e2}") + df1["diff"] = df1["x1"] - df1["x2"] + print(df1["diff"].abs().describe()) + + assert df1["diff"].abs().max() < 1e-6 + + +def test_compare_MFI(): + # 数据对不上,需要调整 + df1 = df.copy() + s1 = time.time() + df1["x1"] = ta.MFI(df1["high"], df1["low"], df1["close"], df1["vol"], timeperiod=14) + e1 = time.time() - s1 + + s2 = time.time() + df1["x2"] = talib.MFI(df1["high"].values, df1["low"].values, df1["close"].values, df1["vol"].values, timeperiod=14) + e2 = time.time() - s2 + + print(f"计算时间差异,ta: {e1}, talib: {e2};相差:{e1 - e2}") + df1["diff"] = df1["x1"] - df1["x2"] + print(df1["diff"].abs().describe()) + + assert df1["diff"].abs().max() < 1e-6 + + +def test_compare_PLUS_DI(): + # 数据对不上,需要调整 + df1 = coo.get_raw_bars(symbol="SFIC9001", freq="日线", fq="后复权", sdt="20100101", edt="20210301", raw_bars=False) + + s1 = time.time() + df1["x1"] = ta.PLUS_DI(df1["high"], df1["low"], df1["close"], timeperiod=14) + e1 = time.time() - s1 + + s2 = time.time() + df1["x2"] = talib.PLUS_DI(df1["high"].values, df1["low"].values, df1["close"].values, timeperiod=14) + e2 = time.time() - s2 + + print(f"计算时间差异,ta: {e1}, talib: {e2};相差:{e1 - e2}") + df1["diff"] = df1["x1"] - df1["x2"] + print(df1["diff"].abs().describe()) + + assert df1["diff"].abs().max() < 1e-6