diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 17465edc7..0c49a7106 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -5,7 +5,7 @@ name: Python package on: push: - branches: [ master, V0.9.11 ] + branches: [ master, V0.9.12 ] pull_request: branches: [ master ] diff --git a/czsc/__init__.py b/czsc/__init__.py index 1c97a2ef0..87ff70099 100644 --- a/czsc/__init__.py +++ b/czsc/__init__.py @@ -21,7 +21,7 @@ from czsc.utils import get_sub_elements, get_py_namespace, freqs_sorted, x_round, import_by_name, create_grid_params -__version__ = "0.9.11" +__version__ = "0.9.12" __author__ = "zengbin93" __email__ = "zeng_bin8888@163.com" __date__ = "20230312" diff --git a/czsc/signals/__init__.py b/czsc/signals/__init__.py index ceecb7506..04ec3fd6d 100644 --- a/czsc/signals/__init__.py +++ b/czsc/signals/__init__.py @@ -47,6 +47,7 @@ bar_operate_span_V221111, bar_zdt_V221110, bar_zdt_V221111, + bar_zdt_V230313, bar_cross_ps_V221112, bar_section_momentum_V221112, bar_vol_grow_V221112, @@ -114,6 +115,7 @@ tas_boll_power_V221112, tas_boll_bc_V221118, + tas_boll_vt_V230312, tas_kdj_base_V221101, tas_kdj_evc_V221201, diff --git a/czsc/signals/bar.py b/czsc/signals/bar.py index 9a77cced4..d2e607c8f 100644 --- a/czsc/signals/bar.py +++ b/czsc/signals/bar.py @@ -35,10 +35,7 @@ def bar_end_V221111(c: CZSC, k1='60分钟') -> OrderedDict: dt: datetime = c.bars_raw[-1].dt v = "是" if dt.minute % m == 0 else "否" - s = OrderedDict() - signal = Signal(k1=k1, k2=k2, k3=k3, v1=v) - s[signal.key] = signal.value - return s + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v) def bar_operate_span_V221111(c: CZSC, k1: str = '开多', span=("1400", "1450")) -> OrderedDict: @@ -59,11 +56,7 @@ def bar_operate_span_V221111(c: CZSC, k1: str = '开多', span=("1400", "1450")) dt: datetime = c.bars_raw[-1].dt v = "是" if k2 <= dt.strftime("%H%M") <= k3 else "否" - - s = OrderedDict() - signal = Signal(k1=k1, k2=k2, k3=k3, v1=v) - s[signal.key] = signal.value - return s + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v) def bar_zdt_V221110(c: CZSC, di=1) -> OrderedDict: @@ -73,7 +66,7 @@ def bar_zdt_V221110(c: CZSC, di=1) -> OrderedDict: **信号逻辑:** - close等于high大于前close,近似认为是涨停;反之,跌停。 + close等于high,近似认为是涨停;反之,跌停。 **信号列表:** @@ -89,19 +82,45 @@ def bar_zdt_V221110(c: CZSC, di=1) -> OrderedDict: if len(c.bars_raw) < di + 2: v1 = "其他" else: - b1, b2 = c.bars_raw[-di], c.bars_raw[-di-1] + b1 = c.bars_raw[-di] - if b1.close == b1.high > b2.close: + if b1.close == b1.high: v1 = "涨停" - elif b1.close == b1.low < b2.close: + elif b1.close == b1.low: v1 = "跌停" else: v1 = "其他" - s = OrderedDict() - v = Signal(k1=k1, k2=k2, k3=k3, v1=v1) - s[v.key] = v.value - return s + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def bar_zdt_V230313(c: CZSC, di=1, **kwargs) -> OrderedDict: + """计算倒数第di根K线的涨跌停信息 + + **信号逻辑:** + + - close等于high大于等于前一根K线的close,近似认为是涨停;反之,跌停。 + + **信号列表:** + + - Signal('15分钟_D1涨跌停_V230313_跌停_任意_任意_0') + - Signal('15分钟_D1涨跌停_V230313_涨停_任意_任意_0') + + :param c: 基础周期的 CZSC 对象 + :param di: 倒数第 di 根 K 线 + :return: s + """ + k1, k2, k3, v1 = c.freq.value, f"D{di}涨跌停", "V230313", "其他" + if len(c.bars_raw) < di + 2: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + b1, b2 = c.bars_raw[-di], c.bars_raw[-di - 1] + if b1.close == b1.high >= b2.close: + v1 = "涨停" + elif b1.close == b1.low <= b2.close: + v1 = "跌停" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) def bar_zdt_V221111(cat: CzscSignals, freq: str, di: int = 1) -> OrderedDict: @@ -151,10 +170,7 @@ def bar_zdt_V221111(cat: CzscSignals, freq: str, di: int = 1) -> OrderedDict: else: v1 = "其他" - s = OrderedDict() - v = Signal(k1=k1, k2=k2, k3=k3, v1=v1) - s[v.key] = v.value - return s + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) def bar_vol_grow_V221112(c: CZSC, di: int = 2, n: int = 5) -> OrderedDict: @@ -185,10 +201,7 @@ def bar_vol_grow_V221112(c: CZSC, di: int = 2, n: int = 5) -> OrderedDict: mean_vol = sum([x.vol for x in bars[:-1]]) / n v1 = "是" if mean_vol * 4 >= bars[-1].vol >= mean_vol * 2 else "否" - s = OrderedDict() - signal = Signal(k1=k1, k2=k2, k3=k3, v1=v1) - s[signal.key] = signal.value - return s + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) def bar_fang_liang_break_V221216(c: CZSC, di: int = 1, th=300, ma1="SMA233") -> OrderedDict: @@ -236,10 +249,7 @@ def _vol_fang_liang_break(bars: List[RawBar]): break k1, k2, k3 = f"{c.freq.value}_D{di}TH{th}_突破{ma1.upper()}".split('_') - s = OrderedDict() - signal = Signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) - s[signal.key] = signal.value - return s + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) def bar_mean_amount_V221112(c: CZSC, di: int = 1, n: int = 10, th1: int = 1, th2: int = 4) -> OrderedDict: @@ -278,10 +288,7 @@ def bar_mean_amount_V221112(c: CZSC, di: int = 1, n: int = 10, th1: int = 1, th2 else: logger.warning(msg) - s = OrderedDict() - signal = Signal(k1=k1, k2=k2, k3=k3, v1=v1) - s[signal.key] = signal.value - return s + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) def bar_cross_ps_V221112(c: CZSC, di=1, num=3): @@ -369,10 +376,7 @@ def bar_section_momentum_V221112(c: CZSC, di: int = 1, n: int = 10, th: int = 10 v2 = "强势" if abs(bp) >= th else "弱势" v3 = "高波动" if rate >= 3 else "低波动" - s = OrderedDict() - signal = Signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2, v3=v3) - s[signal.key] = signal.value - return s + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2, v3=v3) def bar_accelerate_V221110(c: CZSC, di: int = 1, window: int = 10) -> OrderedDict: @@ -413,10 +417,7 @@ def bar_accelerate_V221110(c: CZSC, di: int = 1, window: int = 10) -> OrderedDic if c2 and green_pct: v1 = "下跌" - s = OrderedDict() - signal = Signal(k1=k1, k2=k2, k3=k3, v1=v1) - s[signal.key] = signal.value - return s + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) def bar_accelerate_V221118(c: CZSC, di: int = 1, window: int = 13, ma1='SMA10') -> OrderedDict: @@ -442,7 +443,6 @@ def bar_accelerate_V221118(c: CZSC, di: int = 1, window: int = 13, ma1='SMA10') :return: 信号识别结果 """ assert window > 3, "辨别加速,至少需要3根以上K线" - s = OrderedDict() k1, k2, k3 = c.freq.value, f"D{di}W{window}", f"{ma1}加速" bars = get_sub_elements(c.bars_raw, di=di, n=window) @@ -455,9 +455,7 @@ def bar_accelerate_V221118(c: CZSC, di: int = 1, window: int = 13, ma1='SMA10') else: v1 = "其他" - signal = Signal(k1=k1, k2=k2, k3=k3, v1=v1) - s[signal.key] = signal.value - return s + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) def bar_zdf_V221203(c: CZSC, di: int = 1, mode='ZF', span=(300, 600)) -> OrderedDict: @@ -486,11 +484,7 @@ def bar_zdf_V221203(c: CZSC, di: int = 1, mode='ZF', span=(300, 600)) -> Ordered edge = (1 - bars[-1].close / bars[-2].close) * 10000 v1 = "满足" if t2 >= edge >= t1 else "其他" - - s = OrderedDict() - signal = Signal(k1=k1, k2=k2, k3=k3, v1=v1) - s[signal.key] = signal.value - return s + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) def bar_fake_break_V230204(c: CZSC, di=1, **kwargs) -> OrderedDict: diff --git a/czsc/signals/tas.py b/czsc/signals/tas.py index 8d7a7149c..85e066199 100644 --- a/czsc/signals/tas.py +++ b/czsc/signals/tas.py @@ -145,7 +145,7 @@ def update_boll_cache_V230228(c: CZSC, **kwargs): for i in range(1, 6): _c = dict(c.bars_raw[-i].cache) if c.bars_raw[-i].cache else dict() - _c.update({cache_key: {"上轨": u1[i], "中线": m[i], "下轨": l1[i]}}) + _c.update({cache_key: {"上轨": u1[-i], "中线": m[-i], "下轨": l1[-i]}}) c.bars_raw[-i].cache = _c return cache_key @@ -203,9 +203,42 @@ def update_boll_cache(c: CZSC, **kwargs): return cache_key +def tas_boll_vt_V230312(c: CZSC, di: int = 1, **kwargs) -> OrderedDict: + """以BOLL通道为依据的多空进出场信号 + + **信号逻辑:** + + 1. 看多,当日收盘价在上轨上方,且最近max_overlap根K线中至少有一个收盘价都在上轨下方; + 2. 看空,当日收盘价在下轨下方,且最近max_overlap根K线中至少有一个收盘价都在下轨上方; + + **信号列表:** + + - Signal('15分钟_D1BOLL20S20MO5_BS辅助V230312_看空_任意_任意_0') + - Signal('15分钟_D1BOLL20S20MO5_BS辅助V230312_看多_任意_任意_0') + + :param c: CZSC对象 + :param di: 信号计算截止倒数第i根K线 + :return: + """ + key = update_boll_cache_V230228(c, **kwargs) + max_overlap = kwargs.get('max_overlap', 5) + k1, k2, k3 = f"{c.freq.value}_D{di}{key}MO{max_overlap}_BS辅助V230312".split('_') + v1 = "其他" + _bars = get_sub_elements(c.bars_raw, di=di, n=max_overlap + 1) + if len(_bars) < max_overlap + 1: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + if _bars[-1].close > _bars[-1].cache[key]['上轨'] and any([x.close < x.cache[key]['上轨'] for x in _bars]): + v1 = "看多" + + elif _bars[-1].close < _bars[-1].cache[key]['下轨'] and any([x.close > x.cache[key]['下轨'] for x in _bars]): + v1 = "看空" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + # MACD信号计算函数 # ====================================================================================================================== - def tas_macd_base_V221028(c: CZSC, di: int = 1, key="macd", **kwargs) -> OrderedDict: """MACD|DIF|DEA 多空和方向信号 diff --git a/czsc/traders/performance.py b/czsc/traders/performance.py index 8fc6e2355..616c24a41 100644 --- a/czsc/traders/performance.py +++ b/czsc/traders/performance.py @@ -158,6 +158,7 @@ def get_pairs_statistics(df_pairs: pd.DataFrame): "每自然日收益": 0, "每根K线收益": 0, "盈亏平衡点": 0, + "开仓日盈亏平衡点": 0, } return info @@ -189,6 +190,7 @@ def get_pairs_statistics(df_pairs: pd.DataFrame): "交易得分": round(total_gain_loss_rate * win_pct, 4), "赢面": round(single_gain_loss_rate * win_pct - (1 - win_pct), 4), "盈亏平衡点": round(cal_break_even_point(df_pairs['盈亏比例'].to_list()), 4), + "开仓日盈亏平衡点": round(df_pairs.groupby('开仓日')['盈亏比例'].apply(cal_break_even_point).mean(), 4), } info['每自然日收益'] = round(info['平均单笔收益'] / info['平均持仓天数'], 2) diff --git a/examples/ts_check_signal_acc.py b/examples/ts_check_signal_acc.py index 2ff977cd0..bf1ce71b0 100644 --- a/examples/ts_check_signal_acc.py +++ b/examples/ts_check_signal_acc.py @@ -9,14 +9,9 @@ import sys sys.path.insert(0, '..') import os -import numpy as np -from loguru import logger from collections import OrderedDict from czsc.data.ts_cache import TsDataCache -from czsc import CZSC, Signal from czsc.traders.base import CzscTrader, check_signals_acc -from czsc.signals.tas import update_ma_cache -from czsc.utils import get_sub_elements, create_single_signal from czsc import signals @@ -32,15 +27,16 @@ def get_signals(cat: CzscTrader) -> OrderedDict: s = OrderedDict({"symbol": cat.symbol, "dt": cat.end_dt, "close": cat.latest_price}) - # 使用缓存来更新信号的方法 + # 定义需要检查的信号 s.update(signals.tas_macd_first_bs_V221216(cat.kas['日线'], di=1)) return s if __name__ == '__main__': - # check_signals_acc(bars, get_signals, freqs=['日线', '60分钟']) check_signals_acc(bars, get_signals) + # 也可以指定信号的K线周期,比如只检查日线信号 + # check_signals_acc(bars, get_signals, freqs=['日线'])