From 54b2e7323cbae4ffe761a7d23eecf0327a58da88 Mon Sep 17 00:00:00 2001 From: zengbin93 Date: Sun, 25 Jun 2023 23:17:02 +0800 Subject: [PATCH] =?UTF-8?q?V0.9.22=20=E6=9B=B4=E6=96=B0=E4=B8=80=E6=89=B9?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=20(#154)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 0.9.22 start coding * 0.9.22 更新信号函数 * 0.9.22 fix cci * 0.9.22 新增几个信号函数 * 0.9.22 update * 0.9.22 新增多维表格读取接口 * 0.9.22 新增信号函数 * 0.9.22 update * 0.9.22 update * 0.9.22 更新信号函数 * 0.9.22 更新信号函数 * 0.9.22 更新一批信号函数 * 0.9.22 update signals * 0.9.22 新增EMV基础策略 * 0.9.22 新增固定止盈止损策略 * 0.9.22 fix bug: 识别笔的过程有个细节导致程序出错 * 0.9.22 update * 0.9.22 update connectors * 0.9.22 更新一批信号函数 --- .github/workflows/pythonpackage.yml | 2 +- czsc/__init__.py | 4 +- czsc/analyze.py | 17 +- czsc/connectors/ts_connector.py | 34 + czsc/data/jq.py | 511 ------------ czsc/fsa/__init__.py | 1 + czsc/fsa/base.py | 2 + czsc/fsa/bi_table.py | 67 ++ czsc/objects.py | 33 +- czsc/sensors/__init__.py | 1 - czsc/sensors/utils.py | 120 +-- czsc/signals/__init__.py | 26 +- czsc/signals/ang.py | 335 ++++++++ czsc/signals/bar.py | 95 ++- czsc/signals/bxt.py | 747 ------------------ czsc/signals/coo.py | 161 +++- czsc/signals/cxt.py | 730 ++++++++++++++++- czsc/signals/pos.py | 117 +++ czsc/signals/tas.py | 517 +++++++++++- czsc/strategies.py | 125 ++- czsc/utils/oss.py | 12 +- czsc/utils/sig.py | 77 ++ czsc/utils/signal_analyzer.py | 4 + examples/dropit/create_trade_price.py | 32 + examples/signals_dev/bar_end_V221211.py | 53 +- .../signals_dev/bias_up_dw_line_V230604.py | 83 ++ examples/signals_dev/create_trade_price.py | 62 -- examples/signals_dev/cxt_eleven_bi_V230622.py | 130 +++ examples/signals_dev/cxt_five_bi_V230619.py | 107 +++ examples/signals_dev/cxt_nine_bi_V230621.py | 172 ++++ .../cxt_range_oscillation_V230620.py | 88 +++ examples/signals_dev/cxt_seven_bi_V230620.py | 131 +++ examples/signals_dev/cxt_three_bi_V230618.py | 92 +++ .../signals_dev/dema_up_dw_line_V230605.py | 63 ++ .../demakder_up_dw_line_V230605.py | 104 ++- .../{ => merged}/adtm_up_dw_line_V230603.py | 0 .../{ => merged}/amv_up_dw_line_V230603.py | 0 .../{ => merged}/asi_up_dw_line_V230603.py | 0 .../signals_dev/merged/bar_end_V221211.py | 45 ++ .../{ => merged}/check_atr_cache.py | 0 .../{ => merged}/check_boll_cache.py | 0 .../{ => merged}/check_cci_cache.py | 0 .../{ => merged}/check_macd_cache.py | 0 .../{ => merged}/check_sar_cache.py | 0 .../{ => merged}/clv_up_dw_line_V230605.py | 0 .../{ => merged}/cmo_up_dw_line_V230605.py | 0 .../signals_dev/merged/cxt_bi_end_V230618.py | 115 +++ .../merged/demakder_up_dw_line_V230605.py | 81 ++ .../{ => merged}/emv_up_dw_line_V230605.py | 0 .../{ => merged}/holds_concepts_effect.py | 0 .../{ => merged}/pos_fx_stop_V230414.py | 0 .../{ => merged}/pos_holds_V230414.py | 0 .../{ => merged}/tas_atr_stop_V230424.py | 0 .../{ => merged}/tas_double_ma_V230512.py | 0 .../{ => merged}/tas_sar_base_V230424.py | 0 examples/signals_dev/pos_fix_exit_V230624.py | 144 ++++ .../signals_dev/pos_profit_loss_V230624.py | 91 +++ examples/signals_dev/signal_match.py | 22 +- .../signals_dev/tas_cross_status_V230619.py | 103 +++ .../signals_dev/tas_cross_status_V230624.py | 101 +++ .../signals_dev/tas_cross_status_V230625.py | 98 +++ test/test_analyze.py | 37 +- test/test_trader_base.py | 1 - 63 files changed, 4067 insertions(+), 1626 deletions(-) create mode 100644 czsc/connectors/ts_connector.py delete mode 100644 czsc/data/jq.py create mode 100644 czsc/fsa/bi_table.py delete mode 100644 czsc/signals/bxt.py create mode 100644 examples/dropit/create_trade_price.py create mode 100644 examples/signals_dev/bias_up_dw_line_V230604.py delete mode 100644 examples/signals_dev/create_trade_price.py create mode 100644 examples/signals_dev/cxt_eleven_bi_V230622.py create mode 100644 examples/signals_dev/cxt_five_bi_V230619.py create mode 100644 examples/signals_dev/cxt_nine_bi_V230621.py create mode 100644 examples/signals_dev/cxt_range_oscillation_V230620.py create mode 100644 examples/signals_dev/cxt_seven_bi_V230620.py create mode 100644 examples/signals_dev/cxt_three_bi_V230618.py create mode 100644 examples/signals_dev/dema_up_dw_line_V230605.py rename examples/signals_dev/{ => merged}/adtm_up_dw_line_V230603.py (100%) rename examples/signals_dev/{ => merged}/amv_up_dw_line_V230603.py (100%) rename examples/signals_dev/{ => merged}/asi_up_dw_line_V230603.py (100%) create mode 100644 examples/signals_dev/merged/bar_end_V221211.py rename examples/signals_dev/{ => merged}/check_atr_cache.py (100%) rename examples/signals_dev/{ => merged}/check_boll_cache.py (100%) rename examples/signals_dev/{ => merged}/check_cci_cache.py (100%) rename examples/signals_dev/{ => merged}/check_macd_cache.py (100%) rename examples/signals_dev/{ => merged}/check_sar_cache.py (100%) rename examples/signals_dev/{ => merged}/clv_up_dw_line_V230605.py (100%) rename examples/signals_dev/{ => merged}/cmo_up_dw_line_V230605.py (100%) create mode 100644 examples/signals_dev/merged/cxt_bi_end_V230618.py create mode 100644 examples/signals_dev/merged/demakder_up_dw_line_V230605.py rename examples/signals_dev/{ => merged}/emv_up_dw_line_V230605.py (100%) rename examples/signals_dev/{ => merged}/holds_concepts_effect.py (100%) rename examples/signals_dev/{ => merged}/pos_fx_stop_V230414.py (100%) rename examples/signals_dev/{ => merged}/pos_holds_V230414.py (100%) rename examples/signals_dev/{ => merged}/tas_atr_stop_V230424.py (100%) rename examples/signals_dev/{ => merged}/tas_double_ma_V230512.py (100%) rename examples/signals_dev/{ => merged}/tas_sar_base_V230424.py (100%) create mode 100644 examples/signals_dev/pos_fix_exit_V230624.py create mode 100644 examples/signals_dev/pos_profit_loss_V230624.py create mode 100644 examples/signals_dev/tas_cross_status_V230619.py create mode 100644 examples/signals_dev/tas_cross_status_V230624.py create mode 100644 examples/signals_dev/tas_cross_status_V230625.py diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 087b6cca6..342a0b302 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -5,7 +5,7 @@ name: Python package on: push: - branches: [ master, V0.9.21 ] + branches: [ master, V0.9.22 ] pull_request: branches: [ master ] diff --git a/czsc/__init__.py b/czsc/__init__.py index bc219fd1b..808a685c0 100644 --- a/czsc/__init__.py +++ b/czsc/__init__.py @@ -25,10 +25,10 @@ from czsc.utils.signal_analyzer import SignalAnalyzer, SignalPerformance -__version__ = "0.9.21" +__version__ = "0.9.22" __author__ = "zengbin93" __email__ = "zeng_bin8888@163.com" -__date__ = "20230601" +__date__ = "20230616" def welcome(): diff --git a/czsc/analyze.py b/czsc/analyze.py index 54cf8d200..47bcad0ff 100644 --- a/czsc/analyze.py +++ b/czsc/analyze.py @@ -78,18 +78,9 @@ def check_fxs(bars: List[NewBar]) -> List[FX]: for i in range(1, len(bars)-1): fx = check_fx(bars[i-1], bars[i], bars[i+1]) if isinstance(fx, FX): - # 这里可能隐含Bug,默认情况下,fxs本身是顶底交替的,但是对于一些特殊情况下不是这样,这是不对的。 - # 临时处理方案,强制要求fxs序列顶底交替 + # 默认情况下,fxs本身是顶底交替的,但是对于一些特殊情况下不是这样; 临时强制要求fxs序列顶底交替 if len(fxs) >= 2 and fx.mark == fxs[-1].mark: - if envs.get_verbose(): - logger.info(f"\n\ncheck_fxs: 输入数据错误{'+' * 100}") - logger.info(f"当前:{fx.mark}, 上个:{fxs[-1].mark}") - for bar in fx.raw_bars: - logger.info(f"{bar}\n") - - logger.info('last fx raw bars: \n') - for bar in fxs[-1].raw_bars: - logger.info(f"{bar}\n") + logger.error(f"check_fxs错误: {bars[i].dt},{fx.mark},{fxs[-1].mark}") else: fxs.append(fx) return fxs @@ -232,7 +223,9 @@ def __update_bi(self): bars_ubi = self.bars_ubi if (last_bi.direction == Direction.Up and bars_ubi[-1].high > last_bi.high) \ or (last_bi.direction == Direction.Down and bars_ubi[-1].low < last_bi.low): - self.bars_ubi = last_bi.bars[:-1] + [x for x in bars_ubi if x.dt >= last_bi.bars[-1].dt] + # 当前笔被破坏,将当前笔的bars与bars_ubi进行合并,并丢弃,这里容易出错,多一根K线就可能导致错误 + # 必须是 -2,因为最后一根无包含K线有可能是未完成的 + self.bars_ubi = last_bi.bars[:-2] + [x for x in bars_ubi if x.dt >= last_bi.bars[-2].dt] self.bi_list.pop(-1) def update(self, bar: RawBar): diff --git a/czsc/connectors/ts_connector.py b/czsc/connectors/ts_connector.py new file mode 100644 index 000000000..1bc0ad5e3 --- /dev/null +++ b/czsc/connectors/ts_connector.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +""" +author: zengbin93 +email: zeng_bin8888@163.com +create_dt: 2023/6/24 18:49 +describe: Tushare数据源 +""" +import os +from czsc import data + +dc = data.TsDataCache(data_path=os.environ.get('ts_data_path', r'D:\ts_data')) + + +def get_symbols(step): + if step.upper() == 'ALL': + return data.get_symbols(dc, 'index') + data.get_symbols(dc, 'stock') + data.get_symbols(dc, 'etfs') + return data.get_symbols(dc, step) + + +def get_raw_bars(symbol, freq, sdt, edt, fq='后复权', raw_bar=True): + """读取本地数据""" + ts_code, asset = symbol.split("#") + freq = str(freq) + adj = "qfq" if fq == "前复权" else "hfq" + + if "分钟" in freq: + freq = freq.replace("分钟", "min") + bars = dc.pro_bar_minutes(ts_code, sdt=sdt, edt=edt, freq=freq, asset=asset, adj=adj, raw_bar=raw_bar) + + else: + _map = {"日线": "D", "周线": "W", "月线": "M"} + freq = _map[freq] + bars = dc.pro_bar(ts_code, start_date=sdt, end_date=edt, freq=freq, asset=asset, adj=adj, raw_bar=raw_bar) + return bars diff --git a/czsc/data/jq.py b/czsc/data/jq.py deleted file mode 100644 index c6be58219..000000000 --- a/czsc/data/jq.py +++ /dev/null @@ -1,511 +0,0 @@ -# coding: utf-8 -import os -import pickle -import json -import requests -import warnings -from collections import OrderedDict -import pandas as pd -from datetime import datetime, timedelta -from typing import List -from urllib.parse import quote - -from ..objects import RawBar, Freq -from ..utils.bar_generator import freq_end_time, BarGenerator -from .base import freq_cn2jq - -warnings.warn("请使用 czsc.connectors.jq_connector 替代,相关核心都已经完成迁移", DeprecationWarning) -url = "https://dataapi.joinquant.com/apis" -home_path = os.path.expanduser("~") -file_token = os.path.join(home_path, "jq.token") - -dt_fmt = "%Y-%m-%d %H:%M:%S" -date_fmt = "%Y-%m-%d" - -# 1m, 5m, 15m, 30m, 60m, 120m, 1d, 1w, 1M -freq_convert = {"1min": "1m", "5min": '5m', '15min': '15m', - "30min": "30m", "60min": '60m', "D": "1d", "W": '1w', "M": "1M"} - -freq_map = {'1min': Freq.F1, '5min': Freq.F5, '15min': Freq.F15, '30min': Freq.F30, - '60min': Freq.F60, 'D': Freq.D, 'W': Freq.W, 'M': Freq.M} - - -def set_token(jq_mob, jq_pwd): - """ - - :param jq_mob: str - mob是申请JQData时所填写的手机号 - :param jq_pwd: str - Password为聚宽官网登录密码,新申请用户默认为手机号后6位 - :return: None - """ - with open(file_token, 'wb') as f: - pickle.dump([jq_mob, jq_pwd], f) - - -def get_token(): - """获取调用凭证""" - if not os.path.exists(file_token): - raise ValueError(f"{file_token} 文件不存在,请先调用 set_token 进行设置") - - with open(file_token, 'rb') as f: - jq_mob, jq_pwd = pickle.load(f) - - body = { - "method": "get_current_token", - "mob": jq_mob, # mob是申请JQData时所填写的手机号 - "pwd": quote(jq_pwd), # Password为聚宽官网登录密码,新申请用户默认为手机号后6位 - } - response = requests.post(url, data=json.dumps(body)) - token = response.text - return token - - -to_jq_symbol = lambda x: x[:6] + ".XSHG" if x[0] == '6' else x[:6] + ".XSHE" - - -def text2df(text): - rows = [x.split(",") for x in text.strip().split('\n')] - df = pd.DataFrame(rows[1:], columns=rows[0]) - return df - - -def get_query_count() -> int: - """获取查询剩余条数 - https://dataapi.joinquant.com/docs#get_query_count---%E8%8E%B7%E5%8F%96%E6%9F%A5%E8%AF%A2%E5%89%A9%E4%BD%99%E6%9D%A1%E6%95%B0 - - :return: int - """ - data = { - "method": "get_query_count", - "token": get_token(), - } - r = requests.post(url, data=json.dumps(data)) - return int(r.text) - - -def get_concepts(): - """获取概念列表 - - https://dataapi.joinquant.com/docs#get_concepts---%E8%8E%B7%E5%8F%96%E6%A6%82%E5%BF%B5%E5%88%97%E8%A1%A8 - - :return: df - """ - data = { - "method": "get_concepts", - "token": get_token(), - } - r = requests.post(url, data=json.dumps(data)) - df = text2df(r.text) - return df - - -def get_concept_stocks(symbol, date=None): - """获取概念成份股 - - https://dataapi.joinquant.com/docs#get_concept_stocks---%E8%8E%B7%E5%8F%96%E6%A6%82%E5%BF%B5%E6%88%90%E4%BB%BD%E8%82%A1 - - :param symbol: str - 如 GN036 - :param date: str or datetime - 日期,如 2020-08-08 - :return: list - - examples: - ------- - >>> symbols1 = get_concept_stocks("GN036", date="2020-07-08") - >>> symbols2 = get_concept_stocks("GN036", date=datetime.now()) - """ - if not date: - date = str(datetime.now().date()) - else: - date = pd.to_datetime(date) - - if isinstance(date, datetime): - date = str(date.date()) - - data = { - "method": "get_concept_stocks", - "token": get_token(), - "code": symbol, - "date": date - } - r = requests.post(url, data=json.dumps(data)) - return r.text.split('\n') - - -def get_index_stocks(symbol, date=None): - """获取指数成份股 - - https://dataapi.joinquant.com/docs#get_index_stocks---%E8%8E%B7%E5%8F%96%E6%8C%87%E6%95%B0%E6%88%90%E4%BB%BD%E8%82%A1 - - :param symbol: str - 如 000300.XSHG - :param date: str or datetime - 日期,如 2020-08-08 - :return: list - - examples: - ------- - >>> symbols1 = get_index_stocks("000300.XSHG", date="2020-07-08") - >>> symbols2 = get_index_stocks("000300.XSHG", date=datetime.now()) - """ - if not date: - date = str(datetime.now().date()) - - if isinstance(date, datetime): - date = str(date.date()) - - data = { - "method": "get_index_stocks", - "token": get_token(), - "code": symbol, - "date": date - } - r = requests.post(url, data=json.dumps(data)) - return r.text.split('\n') - - -def get_industry(symbol): - """ - https://www.joinquant.com/help/api/help#JQDataHttp:get_industry-%E6%9F%A5%E8%AF%A2%E8%82%A1%E7%A5%A8%E6%89%80%E5%B1%9E%E8%A1%8C%E4%B8%9A - :param symbol: - :return: - """ - data = { - "method": "get_industry", - "token": get_token(), - "code": symbol, - "date": str(datetime.now().date()) - } - r = requests.post(url, data=json.dumps(data)) - df = text2df(r.text) - res = { - "股票代码": symbol, - "证监会行业代码": df[df['industry'] == 'zjw']['industry_code'].iloc[0], - "证监会行业名称": df[df['industry'] == 'zjw']['industry_name'].iloc[0], - "聚宽一级行业代码": df[df['industry'] == 'jq_l1']['industry_code'].iloc[0], - "聚宽一级行业名称": df[df['industry'] == 'jq_l1']['industry_name'].iloc[0], - "聚宽二级行业代码": df[df['industry'] == 'jq_l2']['industry_code'].iloc[0], - "聚宽二级行业名称": df[df['industry'] == 'jq_l2']['industry_name'].iloc[0], - "申万一级行业代码": df[df['industry'] == 'sw_l1']['industry_code'].iloc[0], - "申万一级行业名称": df[df['industry'] == 'sw_l1']['industry_name'].iloc[0], - "申万二级行业代码": df[df['industry'] == 'sw_l2']['industry_code'].iloc[0], - "申万二级行业名称": df[df['industry'] == 'sw_l2']['industry_name'].iloc[0], - "申万三级行业代码": df[df['industry'] == 'sw_l3']['industry_code'].iloc[0], - "申万三级行业名称": df[df['industry'] == 'sw_l3']['industry_name'].iloc[0], - } - return res - - -def get_all_securities(code, date=None) -> pd.DataFrame: - """ - https://dataapi.joinquant.com/docs#get_all_securities---%E8%8E%B7%E5%8F%96%E6%89%80%E6%9C%89%E6%A0%87%E7%9A%84%E4%BF%A1%E6%81%AF - 获取平台支持的所有股票、基金、指数、期货信息 - - 参数: - - code: 证券类型,可选: stock, fund, index, futures, etf, lof, fja, fjb, QDII_fund, - open_fund, bond_fund, stock_fund, money_market_fund, mixture_fund, options - date: 日期,用于获取某日期还在上市的证券信息,date为空时表示获取所有日期的标的信息 - - :return: - """ - if not date: - date = str(datetime.now().date()) - - if isinstance(date, datetime): - date = str(date.date()) - - data = { - "method": "get_all_securities", - "token": get_token(), - "code": code, - "date": date - } - r = requests.post(url, data=json.dumps(data)) - return text2df(r.text) - - -def get_kline(symbol: str, end_date: [datetime, str], freq: str, - start_date: [datetime, str] = None, count=None, fq: bool = True) -> List[RawBar]: - """获取K线数据 - - https://www.joinquant.com/help/api/help#JQDataHttp:get_priceget_bars-%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E6%97%B6%E9%97%B4%E5%91%A8%E6%9C%9F%E7%9A%84%E8%A1%8C%E6%83%85%E6%95%B0%E6%8D%AE - :param symbol: 聚宽标的代码 - :param start_date: 开始日期 - :param end_date: 截止日期 - :param freq: K线级别,可选值 ['1min', '5min', '30min', '60min', 'D', 'W', 'M'] - :param count: K线数量,最大值为 5000 - :param fq: 是否进行复权 - :return: pd.DataFrame - - >>> start_date = datetime.strptime("20200701", "%Y%m%d") - >>> end_date = datetime.strptime("20200719", "%Y%m%d") - >>> df1 = get_kline(symbol="000001.XSHG", start_date=start_date, end_date=end_date, freq="1min") - >>> df2 = get_kline(symbol="000001.XSHG", end_date=end_date, freq="1min", count=1000) - >>> df3 = get_kline(symbol="000001.XSHG", start_date='20200701', end_date='20200719', freq="1min", fq=True) - >>> df4 = get_kline(symbol="000001.XSHG", end_date='20200719', freq="1min", count=1000) - """ - if count and count > 5000: - warnings.warn(f"count={count}, 超过5000的最大值限制,仅返回最后5000条记录") - - end_date = pd.to_datetime(end_date) - - if start_date: - start_date = pd.to_datetime(start_date) - data = { - "method": "get_price_period", - "token": get_token(), - "code": symbol, - "unit": freq_convert[freq], - "date": start_date.strftime("%Y-%m-%d"), - "end_date": end_date.strftime("%Y-%m-%d"), - } - elif count: - data = { - "method": "get_price", - "token": get_token(), - "code": symbol, - "count": count, - "unit": freq_convert[freq], - "end_date": end_date.strftime("%Y-%m-%d"), - } - else: - raise ValueError("start_date 和 count 不能同时为空") - - if fq: - data.update({"fq_ref_date": end_date.strftime("%Y-%m-%d")}) - - r = requests.post(url, data=json.dumps(data)) - rows = [x.split(",") for x in r.text.strip().split('\n')][1:] - - bars = [] - i = -1 - for row in rows: - # row = ['date', 'open', 'close', 'high', 'low', 'volume', 'money'] - dt = pd.to_datetime(row[0]) - if freq == "D": - dt = dt.replace(hour=0, minute=0, second=0, microsecond=0) - - if int(row[5]) > 0: - i += 1 - bars.append(RawBar(symbol=symbol, dt=dt, id=i, freq=freq_map[freq], - open=round(float(row[1]), 2), - close=round(float(row[2]), 2), - high=round(float(row[3]), 2), - low=round(float(row[4]), 2), - vol=int(row[5]), amount=int(float(row[6])))) - # amount 单位:元 - if start_date: - bars = [x for x in bars if x.dt >= start_date] - if "min" in freq: - bars[-1].dt = freq_end_time(bars[-1].dt, freq=freq_map[freq]) - bars = [x for x in bars if x.dt <= end_date] - return bars - - -def get_kline_period(symbol: str, start_date: [datetime, str], - end_date: [datetime, str], freq: str, fq=True) -> List[RawBar]: - """获取指定时间段的行情数据 - - https://www.joinquant.com/help/api/help#JQDataHttp:get_price_periodget_bars_period-%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E6%97%B6%E9%97%B4%E6%AE%B5%E7%9A%84%E8%A1%8C%E6%83%85%E6%95%B0%E6%8D%AE - :param symbol: 聚宽标的代码 - :param start_date: 开始日期 - :param end_date: 截止日期 - :param freq: K线级别,可选值 ['1min', '5min', '30min', '60min', 'D', 'W', 'M'] - :param fq: 是否进行复权 - :return: - """ - start_date = pd.to_datetime(start_date) - end_date = pd.to_datetime(end_date) - - if (end_date - start_date).days * 5 / 7 > 1000: - warnings.warn(f"{end_date.date()} - {start_date.date()} 超过1000个交易日,K线获取可能失败,返回为0") - - data = { - "method": "get_price_period", - "token": get_token(), - "code": symbol, - "unit": freq_convert[freq], - "date": start_date.strftime("%Y-%m-%d"), - "end_date": end_date.strftime("%Y-%m-%d"), - } - if fq: - data.update({"fq_ref_date": end_date.strftime("%Y-%m-%d")}) - - r = requests.post(url, data=json.dumps(data)) - rows = [x.split(",") for x in r.text.strip().split('\n')][1:] - bars = [] - i = -1 - for row in rows: - # row = ['date', 'open', 'close', 'high', 'low', 'volume', 'money'] - dt = pd.to_datetime(row[0]) - if freq == "D": - dt = dt.replace(hour=0, minute=0, second=0, microsecond=0) - - if int(row[5]) > 0: - i += 1 - bars.append(RawBar(symbol=symbol, dt=dt, id=i, freq=freq_map[freq], - open=round(float(row[1]), 2), - close=round(float(row[2]), 2), - high=round(float(row[3]), 2), - low=round(float(row[4]), 2), - vol=int(row[5]), amount=int(float(row[6])))) - # amount 单位:元 - if start_date: - bars = [x for x in bars if x.dt >= start_date] - if "min" in freq and bars: - bars[-1].dt = freq_end_time(bars[-1].dt, freq=freq_map[freq]) - bars = [x for x in bars if x.dt <= end_date] - return bars - - -def get_init_bg(symbol: str, - end_dt: [str, datetime], - base_freq: str, - freqs: List[str], - max_count=1000, - fq=True): - """获取 symbol 的初始化 bar generator""" - if isinstance(end_dt, str): - end_dt = pd.to_datetime(end_dt, utc=False) - - delta_days = 180 - last_day = (end_dt - timedelta(days=delta_days)).replace(hour=16, minute=0) - - bg = BarGenerator(base_freq, freqs, max_count) - for freq in bg.bars.keys(): - bars_ = get_kline(symbol=symbol, end_date=last_day, freq=freq_cn2jq[freq], count=max_count, fq=fq) - bg.init_freq_bars(freq, bars_) - print(f"{symbol} - {freq} - {len(bg.bars[freq])} - last_dt: {bg.bars[freq][-1].dt} - last_day: {last_day}") - - bars2 = get_kline_period(symbol, last_day, end_dt, freq=freq_cn2jq[base_freq], fq=fq) - data = [x for x in bars2 if x.dt > last_day] - assert len(data) > 0 - print(f"{symbol}: bar generator 最新时间 {bg.bars[base_freq][-1].dt.strftime(dt_fmt)},还有{len(data)}行数据需要update") - return bg, data - - -def get_fundamental(table: str, symbol: str, date: str, columns: str = "") -> dict: - """ - https://dataapi.joinquant.com/docs#get_fundamentals---%E8%8E%B7%E5%8F%96%E5%9F%BA%E6%9C%AC%E8%B4%A2%E5%8A%A1%E6%95%B0%E6%8D%AE - - 财务数据列表: - https://www.joinquant.com/help/api/help?name=Stock#%E8%B4%A2%E5%8A%A1%E6%95%B0%E6%8D%AE%E5%88%97%E8%A1%A8 - - :param table: - :param symbol: - :param date: str - 查询日期2019-03-04或者年度2018或者季度2018q1 2018q2 2018q3 2018q4 - :param columns: - :return: df - - example: - ============ - >>>> x1 = get_fundamental(table="indicator", symbol="300803.XSHE", date="2020-11-12") - >>>> x2 = get_fundamental(table="indicator", symbol="300803.XSHE", date="2020") - >>>> x3 = get_fundamental(table="indicator", symbol="300803.XSHE", date="2020q3") - """ - data = { - "method": "get_fundamentals", - "token": get_token(), - "table": table, - "columns": columns, - "code": symbol, - "date": date, - "count": 1 - } - r = requests.post(url, data=json.dumps(data)) - df = text2df(r.text) - try: - return df.iloc[0].to_dict() - except: - return {} - - -def run_query(table: str, conditions: str, columns=None, count=1): - """模拟JQDataSDK的run_query方法 - - https://www.joinquant.com/help/api/help#JQDataHttp:run_query-%E6%A8%A1%E6%8B%9FJQDataSDK%E7%9A%84run_query%E6%96%B9%E6%B3%95 - - :param table: 要查询的数据库和表名,格式为 database + . + tablename 如finance.STK_XR_XD - :param conditions: 查询条件,可以为空 - 格式为report_date#>=#2006-12-01&report_date#<=#2006-12-31, - 条件内部#号分隔,格式: column # 判断符 # value, - 多个条件使用&号分隔,表示and,conditions不能有空格等特殊字符 - :param columns: 所查字段,为空时则查询所有字段,多个字段中间用,分隔。如id,company_id,columns不能有空格等特殊字符 - :param count: 数量 - :return: - """ - data = { - "method": "run_query", - "token": get_token(), - "table": table, - "conditions": conditions, - "count": count - } - if columns: - data['columns'] = columns - r = requests.post(url, data=json.dumps(data)) - df = text2df(r.text) - return df - - -def get_share_basic(symbol): - """获取单个标的的基本面基础数据 - - :param symbol: - :return: - """ - basic_info = run_query(table="finance.STK_COMPANY_INFO", conditions="code#=#{}".format(symbol), count=1) - basic_info = basic_info.iloc[0].to_dict() - - f10 = OrderedDict() - f10['股票代码'] = basic_info['code'] - f10['股票名称'] = basic_info['short_name'] - f10['行业'] = "{}-{}".format(basic_info['industry_1'], basic_info['industry_2']) - f10['地域'] = "{}{}".format(basic_info['province'], basic_info['city']) - f10['主营'] = basic_info['main_business'] - f10['同花顺F10'] = "http://basic.10jqka.com.cn/{}".format(basic_info['code'][:6]) - - # 市盈率,总市值,流通市值,流通比 - # ------------------------------------------------------------------------------------------------------------------ - last_date = datetime.now() - timedelta(days=1) - res = get_fundamental(table="valuation", symbol=symbol, date=last_date.strftime("%Y-%m-%d")) - f10['总市值(亿)'] = float(res['market_cap']) - f10['流通市值(亿)'] = float(res['circulating_market_cap']) - f10['流通比(%)'] = round(float(res['circulating_market_cap']) / float(res['market_cap']) * 100, 2) - f10['PE_TTM'] = float(res['pe_ratio']) - f10['PE'] = float(res['pe_ratio_lyr']) - f10['PB'] = float(res['pb_ratio']) - - # 净资产收益率 - # ------------------------------------------------------------------------------------------------------------------ - for year in ['2017', '2018', '2019', '2020']: - indicator = get_fundamental(table="indicator", symbol=symbol, date=year) - f10['{}EPS'.format(year)] = float(indicator.get('eps', 0)) if indicator.get('eps', 0) else 0 - f10['{}ROA'.format(year)] = float(indicator.get('roa', 0)) if indicator.get('roa', 0) else 0 - f10['{}ROE'.format(year)] = float(indicator.get('roe', 0)) if indicator.get('roe', 0) else 0 - f10['{}销售净利率(%)'.format(year)] = float(indicator.get('net_profit_margin', 0)) if indicator.get('net_profit_margin', 0) else 0 - f10['{}销售毛利率(%)'.format(year)] = float(indicator.get('gross_profit_margin', 0)) if indicator.get('gross_profit_margin', 0) else 0 - f10['{}营业收入同比增长率(%)'.format(year)] = float(indicator.get('inc_revenue_year_on_year', 0)) if indicator.get('inc_revenue_year_on_year', 0) else 0 - f10['{}营业收入环比增长率(%)'.format(year)] = float(indicator.get('inc_revenue_annual', 0)) if indicator.get('inc_revenue_annual', 0) else 0 - f10['{}营业利润同比增长率(%)'.format(year)] = float(indicator.get('inc_operation_profit_year_on_year', 0)) if indicator.get('inc_operation_profit_year_on_year', 0) else 0 - f10['{}经营活动产生的现金流量净额/营业收入(%)'.format(year)] = float(indicator.get('ocf_to_revenue', 0)) if indicator.get('ocf_to_revenue', 0) else 0 - - # 组合成可以用来推送的文本 - msg = "{}({})@{}\n".format(f10['股票代码'], f10['股票名称'], f10['地域']) - msg += "\n{}\n".format("*" * 30) - for k in ['行业', '主营', 'PE_TTM', 'PE', 'PB', '总市值(亿)', '流通市值(亿)', '流通比(%)', '同花顺F10']: - msg += "{}:{}\n".format(k, f10[k]) - - msg += "\n{}\n".format("*" * 30) - cols = ['EPS', 'ROA', 'ROE', '销售净利率(%)', '销售毛利率(%)', '营业收入同比增长率(%)', '营业利润同比增长率(%)', '经营活动产生的现金流量净额/营业收入(%)'] - msg += "2017~2020 财务变化\n\n" - for k in cols: - msg += k + ":{} | {} | {} | {}\n".format(*[f10['{}{}'.format(year, k)] for year in ['2017', '2018', '2019', '2020']]) - - f10['msg'] = msg - return f10 - diff --git a/czsc/fsa/__init__.py b/czsc/fsa/__init__.py index 493ddee13..800b08adb 100644 --- a/czsc/fsa/__init__.py +++ b/czsc/fsa/__init__.py @@ -10,6 +10,7 @@ from czsc.fsa.base import request, FeishuApiBase from czsc.fsa.spreed_sheets import SpreadSheets from czsc.fsa.im import IM +from czsc.fsa.bi_table import BiTable def push_text(text: str, key: str) -> None: diff --git a/czsc/fsa/base.py b/czsc/fsa/base.py index 4de273b88..2aede0705 100644 --- a/czsc/fsa/base.py +++ b/czsc/fsa/base.py @@ -19,6 +19,8 @@ from tenacity import retry, stop_after_attempt, wait_random from requests_toolbelt import MultipartEncoder +logger.disable(__name__) + @retry(stop=stop_after_attempt(3), wait=wait_random(min=1, max=5)) def request(method, url, headers, payload=None) -> dict: diff --git a/czsc/fsa/bi_table.py b/czsc/fsa/bi_table.py new file mode 100644 index 000000000..ff792f7dc --- /dev/null +++ b/czsc/fsa/bi_table.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +""" +author: zengbin93 +email: zeng_bin8888@163.com +create_dt: 2023/06/16 19:45 +describe: 飞书多维表格接口 +""" +import pandas as pd +from czsc.fsa.base import FeishuApiBase, request + + +class BiTable(FeishuApiBase): + """ + 多维表格概述: https://open.feishu.cn/document/server-docs/docs/bitable-v1/bitable-overview + """ + + def __init__(self, app_id, app_secret): + super().__init__(app_id, app_secret) + + def list_tables(self, app_token): + """列出数据表 + + https://open.feishu.cn/document/server-docs/docs/bitable-v1/app-table/list + + :param app_token: 应用token + :return: 返回数据 + """ + url = f"{self.host}/open-apis/bitable/v1/apps/{app_token}/tables" + return request("GET", url, self.get_headers()) + + def list_records(self, app_token, table_id, **kwargs): + """列出数据表中的记录 + + https://open.feishu.cn/document/server-docs/docs/bitable-v1/app-table-record/list + + :param app_token: 应用token + :param table_id: 数据表id + :return: 返回数据 + """ + url = f"{self.host}/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records" + if kwargs.get("page_size") is None: + kwargs["page_size"] = 500 + if kwargs.get("page_token") is None: + kwargs["page_token"] = "" + url = url + "?" + "&".join([f"{k}={v}" for k, v in kwargs.items()]) + return request("GET", url, self.get_headers()) + + # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # 以下是便捷使用的封装,非官方API接口 + # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + def read_table(self, app_token, table_id, **kwargs): + """读取多维表格中指定表格的数据 + + :param app_token: 多维表格应用token + :param table_id: 表格id + :return: + """ + rows = [] + res = self.list_records(app_token, table_id, **kwargs)['data'] + total = res['total'] + rows.extend(res['items']) + while res['has_more']: + res = self.list_records(app_token, table_id, page_token=res['page_token'], **kwargs)['data'] + rows.extend(res['items']) + + assert len(rows) == total, "数据读取异常" + return pd.DataFrame([x['fields'] for x in rows]) diff --git a/czsc/objects.py b/czsc/objects.py index 0079fe034..3c5cfb5d8 100644 --- a/czsc/objects.py +++ b/czsc/objects.py @@ -12,7 +12,7 @@ from dataclasses import dataclass, field from datetime import datetime from loguru import logger -from typing import List, Callable, AnyStr, Dict +from typing import List, Callable, AnyStr, Dict, Optional from czsc.enum import Mark, Direction, Freq, Operate from czsc.utils.corr import single_linear @@ -484,8 +484,6 @@ def is_match(self, s: dict) -> bool: @dataclass class Factor: - name: str - # signals_all 必须全部满足的信号,至少需要设定一个信号 signals_all: List[Signal] @@ -495,6 +493,15 @@ class Factor: # signals_not 不能满足其中任一信号,允许为空 signals_not: List[Signal] = field(default_factory=list) + name: str = "" + + def __post_init__(self): + if not self.signals_all: + raise ValueError("signals_all 不能为空") + str_signals = str(self.dump()) + sha256 = hashlib.sha256(str_signals.encode("utf-8")).hexdigest().upper()[:8] + self.name = f"{self.name}#{sha256}" + @property def unique_signals(self) -> List[str]: """获取 Factor 的唯一信号列表""" @@ -533,7 +540,6 @@ def dump(self) -> dict: signals_not = [x.signal for x in self.signals_not] if self.signals_not else [] raw = { - "name": self.name, "signals_all": signals_all, "signals_any": signals_any, "signals_not": signals_not, @@ -556,7 +562,7 @@ def load(cls, raw: dict): signals_not = [Signal(x) for x in raw.get("signals_not", [])] fa = Factor( - name=raw["name"], + name=raw.get("name", ""), signals_all=[Signal(x) for x in raw["signals_all"]], signals_any=signals_any, signals_not=signals_not, @@ -566,7 +572,6 @@ def load(cls, raw: dict): @dataclass class Event: - name: str operate: Operate # 多个信号组成一个因子,多个因子组成一个事件。 @@ -582,6 +587,15 @@ class Event: # signals_not 不能满足其中任一信号,允许为空 signals_not: List[Signal] = field(default_factory=list) + name: str = "" + + def __post_init__(self): + if not self.factors: + raise ValueError("factors 不能为空") + str_factors = str(self.dump()) + sha256 = hashlib.sha256(str_factors.encode("utf-8")).hexdigest().upper()[:8] + self.name = f"{self.operate.value}#{sha256}" + @property def unique_signals(self) -> List[str]: """获取 Event 的唯一信号列表""" @@ -646,7 +660,6 @@ def dump(self) -> dict: factors = [x.dump() for x in self.factors] raw = { - "name": self.name, "operate": self.operate.value, "signals_all": signals_all, "signals_any": signals_any, @@ -677,13 +690,7 @@ def load(cls, raw: dict): ), f"operate {raw['operate']} not in Operate" assert raw["factors"], "factors can not be empty" - # 生成 Event 的 name 和 sha256 - name = raw.get("name", raw["operate"]) - sha256 = hashlib.sha256(str(raw).encode("utf-8")).hexdigest().upper()[:8] - - # 生成 Event 对象 e = Event( - name=f"{name}_{sha256}", operate=Operate.__dict__["_value2member_map_"][raw["operate"]], factors=[Factor.load(x) for x in raw["factors"]], signals_all=[Signal(x) for x in raw.get("signals_all", [])], diff --git a/czsc/sensors/__init__.py b/czsc/sensors/__init__.py index d4a850828..75c030b3a 100644 --- a/czsc/sensors/__init__.py +++ b/czsc/sensors/__init__.py @@ -11,7 +11,6 @@ discretizer, get_index_beta, holds_concepts_effect, - SignalsPerformance, ) diff --git a/czsc/sensors/utils.py b/czsc/sensors/utils.py index ab59f55f5..b501a5f6f 100644 --- a/czsc/sensors/utils.py +++ b/czsc/sensors/utils.py @@ -135,115 +135,23 @@ def turn_over_rate(df_holds: pd.DataFrame) -> [pd.DataFrame, float]: return df_turns, round(df_turns.change.sum() / 2, 4) -@deprecated(version='1.0', reason="请使用 czsc.utils.signal_analyzer.SignalPerformance") -class SignalsPerformance: - """信号表现分析""" - - def __init__(self, dfs: pd.DataFrame, keys: List[AnyStr]): - """ - - :param dfs: 信号表 - :param keys: 信号列,支持一个或多个信号列组合分析 - """ - base_cols = [x for x in dfs.columns if len(x.split("_")) != 3] - dfs = dfs[base_cols + keys].copy() - - if 'year' not in dfs.columns: - y = dfs['dt'].apply(lambda x: x.year) - dfs['year'] = y.values - - self.dfs = dfs - self.keys = keys - self.b_cols = [x for x in dfs.columns if x[0] == 'b' and x[-1] == 'b'] - self.n_cols = [x for x in dfs.columns if x[0] == 'n' and x[-1] == 'b'] - - def __return_performance(self, dfs: pd.DataFrame, mode: str = '1b') -> pd.DataFrame: - """分析信号组合的分类能力,也就是信号出现前后的收益情况 - - :param dfs: 信号数据表, - :param mode: 分析模式, - 0b 截面向前看 - 0n 截面向后看 - 1b 时序向前看 - 1n 时序向后看 - :return: - """ - mode = mode.lower() - assert mode in ['0b', '0n', '1b', '1n'] - keys = self.keys - len_dfs = len(dfs) - cols = self.b_cols if mode.endswith('b') else self.n_cols - - sdt = dfs['dt'].min().strftime("%Y%m%d") - edt = dfs['dt'].max().strftime("%Y%m%d") - - def __static(_df, _name): - _res = {"name": _name, "date_span": f"{sdt} ~ {edt}", - "count": len(_df), "cover": round(len(_df) / len_dfs, 4)} - if mode.startswith('0'): - _r = _df.groupby('dt')[cols].mean().mean().to_dict() - else: - _r = _df[cols].mean().to_dict() - _res.update(_r) - return _res - - results = [__static(dfs, "基准")] - - for values, dfg in dfs.groupby(by=keys if len(keys) > 1 else keys[0]): - if isinstance(values, str): - values = [values] - assert len(keys) == len(values) - - name = "#".join([f"{key1}_{name1}" for key1, name1 in zip(keys, values)]) - results.append(__static(dfg, name)) - - dfr = pd.DataFrame(results) - dfr[cols] = dfr[cols].round(2) - return dfr - - def analyze(self, mode='0b') -> pd.DataFrame: - """分析信号出现前后的收益情况 - - :param mode: 分析模式, - 0b 截面向前看 - 0n 截面向后看 - 1b 时序向前看 - 1n 时序向后看 - :return: - """ - dfr = self.__return_performance(self.dfs, mode) - results = [dfr] - for year, df_ in self.dfs.groupby('year'): - dfr_ = self.__return_performance(df_, mode) - results.append(dfr_) - dfr = pd.concat(results, ignore_index=True) - return dfr - - def report(self, file_xlsx=None): - res = { - '向后看截面': self.analyze('0n'), - '向后看时序': self.analyze('1n'), - } - if file_xlsx: - writer = pd.ExcelWriter(file_xlsx) - for sn, df_ in res.items(): - df_.to_excel(writer, sheet_name=sn, index=False) - writer.close() - return res - - def holds_concepts_effect(holds: pd.DataFrame, concepts: dict, top_n=20, min_n=3, **kwargs): """股票持仓列表的板块效应 原理概述:在选股时,如果股票的概念板块与组合中的其他股票的概念板块有重合,那么这个股票的表现会更好。 :param holds: 组合股票池数据,样例: - 成分日期 证券代码 n1b 持仓权重 - 0 2020-01-02 000001.SZ 183.758194 0.001232 - 1 2020-01-02 000002.SZ -156.633896 0.001232 - 2 2020-01-02 000063.SZ 310.296204 0.001232 - 3 2020-01-02 000066.SZ -131.824997 0.001232 - 4 2020-01-02 000069.SZ -38.561699 0.001232 + + =================== ========= ========== + dt symbol weight + =================== ========= ========== + 2023-05-09 00:00:00 601858.SH 0.00333333 + 2023-05-09 00:00:00 300502.SZ 0.00333333 + 2023-05-09 00:00:00 603258.SH 0.00333333 + 2023-05-09 00:00:00 300499.SZ 0.00333333 + 2023-05-09 00:00:00 300624.SZ 0.00333333 + =================== ========= ========== + :param concepts: 股票的概念板块,样例: { '002507.SZ': ['电子商务', '超级品牌', '国企改革'], @@ -256,13 +164,13 @@ def holds_concepts_effect(holds: pd.DataFrame, concepts: dict, top_n=20, min_n=3 if kwargs.get('copy', True): holds = holds.copy() - holds['概念板块'] = holds['证券代码'].map(concepts).fillna('') + holds['概念板块'] = holds['symbol'].map(concepts).fillna('') holds['概念数量'] = holds['概念板块'].apply(len) holds = holds[holds['概念数量'] > 0] new_holds = [] dt_key_concepts = {} - for dt, dfg in tqdm(holds.groupby('成分日期'), desc='计算板块效应'): + for dt, dfg in tqdm(holds.groupby('dt'), desc='计算板块效应'): # 计算密集出现的概念 key_concepts = [k for k, v in Counter([x for y in dfg['概念板块'] for x in y]).most_common(top_n)] dt_key_concepts[dt] = key_concepts @@ -273,5 +181,5 @@ def holds_concepts_effect(holds: pd.DataFrame, concepts: dict, top_n=20, min_n=3 new_holds.append(sel) dfh = pd.concat(new_holds, ignore_index=True) - dfk = pd.DataFrame([{"成分日期": k, '强势概念': v} for k, v in dt_key_concepts.items()]) + dfk = pd.DataFrame([{"dt": k, '强势概念': v} for k, v in dt_key_concepts.items()]) return dfh, dfk diff --git a/czsc/signals/__init__.py b/czsc/signals/__init__.py index 3d4556709..d80feac1c 100644 --- a/czsc/signals/__init__.py +++ b/czsc/signals/__init__.py @@ -5,9 +5,6 @@ create_dt: 2021/11/21 17:48 describe: 信号系统,注意:这里仅仅只是提供一些写信号的例子,用来做策略是不太行的 """ - -from . import bxt - # ====================================================================================================================== # 以下是 0.9.1 开始的新标准下实现的信号函数,规范定义: # 1. 前缀3个字符区分信号类别 @@ -35,6 +32,14 @@ cxt_second_bs_V230320, cxt_bi_status_V230101, cxt_bi_status_V230102, + cxt_bi_zdf_V230601, + cxt_bi_end_V230618, + cxt_three_bi_V230618, + cxt_five_bi_V230619, + cxt_seven_bi_V230620, + cxt_nine_bi_V230621, + cxt_eleven_bi_V230622, + cxt_range_oscillation_V230620, ) @@ -48,6 +53,9 @@ from czsc.signals.coo import ( coo_td_V221110, coo_td_V221111, + coo_cci_V230323, + coo_kdj_V230322, + coo_sar_V230325, ) from czsc.signals.vol import ( @@ -162,6 +170,10 @@ tas_atr_break_V230424, tas_sar_base_V230425, + + tas_cross_status_V230619, + tas_cross_status_V230624, + tas_cross_status_V230625, ) from czsc.signals.pos import ( @@ -169,6 +181,8 @@ pos_ma_V230414, pos_holds_V230414, pos_bar_stop_V230524, + pos_fix_exit_V230624, + pos_profit_loss_V230624, ) @@ -178,4 +192,10 @@ asi_up_dw_line_V230603, clv_up_dw_line_V230605, cmo_up_dw_line_V230605, + skdj_up_dw_line_V230611, + bias_up_dw_line_V230618, + dema_up_dw_line_V230605, + demakder_up_dw_line_V230605, + emv_up_dw_line_V230605, + er_up_dw_line_V230604, ) \ No newline at end of file diff --git a/czsc/signals/ang.py b/czsc/signals/ang.py index b35f36599..983131a58 100644 --- a/czsc/signals/ang.py +++ b/czsc/signals/ang.py @@ -12,6 +12,7 @@ logger.warning("ta-lib 没有正确安装,相关信号函数无法正常执行。" "请参考安装教程 https://blog.csdn.net/qaz2134560/article/details/98484091") import numpy as np +import pandas as pd from typing import List from collections import OrderedDict from czsc.analyze import CZSC, RawBar @@ -268,3 +269,337 @@ def cmo_up_dw_line_V230605(c: CZSC, **kwargs) -> OrderedDict: if cmo < -m: v1 = "看空" return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def skdj_up_dw_line_V230611(c: CZSC, **kwargs) -> OrderedDict: + """SKDJ随机波动指标,贡献者:琅盎 + + 参数模板:"{freq}_D{di}N{n}M{m}UP{up}DW{dw}_SKDJ随机波动V230611" + + **信号逻辑:** + + SKDJ 为慢速随机波动(即慢速 KDJ)。SKDJ 中的 K 即 KDJ 中的 D, + SKJ 中的 D 即 KDJ 中的 D 取移动平均。其用法与 KDJ 相同。 + 当 D<40(处于超卖状态)且 K 上穿 D 时买入,当 D>60(处于超买状 + 态)K 下穿 D 时卖出。 + + **信号列表:** + + - Signal('日线_D1N233M145UP60DW40_SKDJ随机波动V230611_看多_任意_任意_0') + - Signal('日线_D1N233M145UP60DW40_SKDJ随机波动V230611_看空_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + - :param di: 信号计算截止倒数第i根K线 + - :param n: 取K线数量n必需大于m*2 + - :param m: 计算均值需要的参数 + - :param up: 信号预警值 + - :param dw: 信号预警值 + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + n = int(kwargs.get("n", 233)) + m = int(kwargs.get("m", 89)) + up = int(kwargs.get("up", 60)) + dw = int(kwargs.get("dw", 40)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}N{n}M{m}UP{up}DW{dw}_SKDJ随机波动V230611".split('_') + + # 计算RSV缓存 + rsv_cache = f'RSV{n}' + for i, bar in enumerate(c.bars_raw): + if bar.cache.get(rsv_cache) is not None: + continue + if i < n: + n_bars = c.bars_raw[:i+1] + else: + n_bars = get_sub_elements(c.bars_raw, di=i, n=n) + + min_low = min([x.low for x in n_bars]) + max_high = max([x.high for x in n_bars]) + bar.cache[rsv_cache] = (bar.close - min_low) / (max_high - min_low) * 100 + + v1 = "其他" + if len(c.bars_raw) < di + m*3 + 20 or n < m: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bars = get_sub_elements(c.bars_raw, di=di, n=m*3 + 20) + rsv = np.array([bar.cache[rsv_cache] for bar in bars]) + ma_rsv = np.convolve(rsv, np.ones(m)/m, mode='valid') + k = np.convolve(ma_rsv, np.ones(m)/m, mode='valid') + d = np.mean(k[-m:]) + + if dw < d < k[-1]: + v1 = "看多" + if k[-1] < d > up: + v1 = "看空" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def bias_up_dw_line_V230618(c: CZSC, **kwargs) -> OrderedDict: + """BIAS乖离率指标,贡献者:琅盎 + + 参数模板:"{freq}_D{di}N{n}M{m}P{p}TH1{th1}TH2{th2}TH3{th3}_BIAS乖离率V230618" + + **信号逻辑:** + + 乖离率 BIAS 用来衡量收盘价与移动平均线之间的差距。 + 当 BIAS6 大于 3 且 BIAS12 大于 5 且 BIAS24 大于 8, + 三个乖离率均进入股价强势上涨区间,产生买入信号; + 当 BIAS6 小于-3 且 BIAS12 小于-5 且BIAS24 小于-8 时, + 三种乖离率均进入股价强势下跌区间,产生卖出信号 + + **信号列表:** + + - Signal('日线_D1N6M12P24TH11TH23TH35_BIAS乖离率V230618_看空_任意_任意_0') + - Signal('日线_D1N6M12P24TH11TH23TH35_BIAS乖离率V230618_看多_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + + - :param di: 信号计算截止倒数第i根K线 + - :param n: 获取K线的根数,默认为30 + - :param m: 获取K线的根数,默认为20 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + n = int(kwargs.get("n", 6)) + m = int(kwargs.get("m", 12)) + p = int(kwargs.get("p", 24)) + th1 = int(kwargs.get("th1", 1)) + th2 = int(kwargs.get("th2", 3)) + th3 = int(kwargs.get("th3", 5)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}N{n}M{m}P{p}TH1{th1}TH2{th2}TH3{th3}_BIAS乖离率V230618".split('_') + v1 = "其他" + if len(c.bars_raw) < di + max(n, m, p): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bars1 = get_sub_elements(c.bars_raw, di=di, n=n) + bars2 = get_sub_elements(c.bars_raw, di=di, n=m) + bars3 = get_sub_elements(c.bars_raw, di=di, n=p) + + bias_ma1 = np.mean([bars1[i].close for i in range(len(bars1))]) + bias_ma2 = np.mean([bars2[i].close for i in range(len(bars2))]) + bias_ma3 = np.mean([bars3[i].close for i in range(len(bars3))]) + + bias1 = (bars1[-1].close - bias_ma1) / bias_ma1 * 100 + bias2 = (bars2[-1].close - bias_ma2) / bias_ma2 * 100 + bias3 = (bars3[-1].close - bias_ma3) / bias_ma3 * 100 + + if bias1 > th1 and bias2 > th2 and bias3 > th3: + v1 = "看多" + if bias1 < -th1 and bias2 < -th2 and bias3 < -th3: + v1 = "看空" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def dema_up_dw_line_V230605(c: CZSC, **kwargs) -> OrderedDict: + """DEMA短线趋势指标,贡献者:琅盎 + + 参数模板:"{freq}_D{di}N{n}_DEMA短线趋势V230605" + + **信号逻辑:** + + DEMA指标是一种趋势指标,用于衡量价格趋势的方向和强度。 + 与其他移动平均线指标相比,DEMA指标更加灵敏,能够更快地反应价格趋势的变化,因此在短期交易中具有一定的优势。 + 当收盘价大于DEMA看多, 当收盘价小于DEMA看空 + + **信号列表:** + + - Signal('日线_D1N5_DEMA短线趋势V230605_看多_任意_任意_0') + - Signal('日线_D1N5_DEMA短线趋势V230605_看空_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + - :param di: 信号计算截止倒数第i根K线 + - :param n: 获取K线的根数,默认为5 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + n = int(kwargs.get("n", 5)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}N{n}_DEMA短线趋势V230605".split('_') + v1 = "其他" + if len(c.bars_raw) < di + 2*n + 10: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + short_bars = get_sub_elements(c.bars_raw, di=di, n=n) + long_bars = get_sub_elements(c.bars_raw, di=di, n=n * 2) + dema = np.mean([x.close for x in short_bars]) * 2 - np.mean([x.close for x in long_bars]) + + v1 = "看多" if short_bars[-1].close > dema else "看空" + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def demakder_up_dw_line_V230605(c: CZSC, **kwargs) -> OrderedDict: + """DEMAKER价格趋势指标,贡献者:琅盎 + + 参数模板:"{freq}_D{di}N{n}TH{th}TL{tl}_DEMAKER价格趋势V230605" + + **信号逻辑:** + + DEMAKER指标的作用是用于判断价格的趋势和力度 + 当 demaker>0.6 时上升趋势强烈,当 demaker<0.4 时下跌趋势强烈。 + 当 demaker 上穿 0.6/下穿 0.4 时产生买入/卖出信号。 + + **信号列表:** + + - Signal('日线_D1N105TH5TL5_DEMAKER价格趋势V230605_看多_任意_任意_0') + - Signal('日线_D1N105TH5TL5_DEMAKER价格趋势V230605_看空_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + + - :param di: 信号计算截止倒数第i根K线 + - :param n: 获取K线的根数,默认为105 + - :param th: 开多阈值,默认为6 + - :param tl: 开空阈值,默认为4 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + n = int(kwargs.get("n", 105)) + th = int(kwargs.get("th", 5)) + tl = int(kwargs.get("tl", 5)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}N{n}TH{th}TL{tl}_DEMAKER价格趋势V230605".split('_') + + # 增加一个约束,如果K线数量不足时直接返回 + v1 = "其他" + if len(c.bars_raw) < di + n + 10: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bars = get_sub_elements(c.bars_raw, di=di, n=n) + demax = np.mean([bars[i].high - bars[i-1].high for i in + range(1, len(bars)) if bars[i].high - bars[i-1].high > 0]) + demin = np.mean([bars[i-1].low - bars[i].low for i in + range(1, len(bars)) if bars[i-1].low - bars[i].low > 0]) + demaker = demax / (demax + demin) + + if demaker > th / 10: + v1 = "看多" + if demaker < tl / 10: + v1 = "看空" + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def emv_up_dw_line_V230605(c: CZSC, **kwargs) -> OrderedDict: + """EMV简易波动指标,贡献者:琅盎 + + 参数模板:"{freq}_D{di}_EMV简易波动V230605" + + **信号逻辑:** + + emv 综合考虑了成交量和价格(中间价)的变化。 + emv>0 则多头处于优势,emv 上升说明买方力量在增大; + emv<0 则空头处于优势,emv 下降说明卖方力量在增大。 + 如果 emv 上穿 0,则产生买入信号; + 如果 emv 下穿 0,则产生卖出信号。 + + **信号列表:** + + - Signal('日线_D1_EMV简易波动V230605_看多_任意_任意_0') + - Signal('日线_D1_EMV简易波动V230605_看空_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + + - :param di: 信号计算截止倒数第i根K线 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}_EMV简易波动V230605".split('_') + + # 增加一个约束,如果K线数量不足时直接返回 + v1 = "其他" + if len(c.bars_raw) < di + 10: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + _bars = get_sub_elements(c.bars_raw, di=di, n=2) + mid_pt_move = (_bars[-1].high + _bars[-1].low) / 2 - (_bars[-2].high + _bars[-2].low) / 2 + box_ratio = _bars[-1].vol / (_bars[-1].high - _bars[-1].low + 1e-9) + emv = mid_pt_move / box_ratio + + v1 = "看多" if emv > 0 else "看空" + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def er_up_dw_line_V230604(c: CZSC, **kwargs) -> OrderedDict: + """ER价格动量指标,贡献者:琅盎 + + 参数模板:"{freq}_D{di}W{w}N{n}_ER价格动量V230604" + + **信号逻辑:** + + er 为动量指标。用来衡量市场的多空力量对比。在多头市场, + 人们会更贪婪地在接近高价的地方买入,BullPower 越高则当前 + 多头力量越强;而在空头市场,人们可能因为恐惧而在接近低价 + 的地方卖出。BearPower 越低则当前空头力量越强。当两者都大 + 于 0 时,反映当前多头力量占据主导地位;两者都小于 0 则反映 + 空头力量占据主导地位。 + 如果 BearPower 上穿 0,则产生买入信号; + 如果 BullPower 下穿 0,则产生卖出信号。 + + **信号列表:** + + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第10层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第9层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第8层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第5层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第1层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第10层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第2层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第6层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第7层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第8层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第9层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第4层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第5层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第7层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第3层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第2层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第6层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第1层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第4层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第3层_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + - :param di: 信号计算截止倒数第i根K线 + - :param n: 获取K线的根数,默认为105 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + w = int(kwargs.get("w", 60)) + n = int(kwargs.get("n", 10)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}W{w}N{n}_ER价格动量V230604".split('_') + + cache_key = f"ER{w}" + for i, bar in enumerate(c.bars_raw, 1): + if cache_key in bar.cache: + continue + _bars = c.bars_raw[i-w:i] + ma = np.mean([x.close for x in _bars]) + bull_power = bar.high - ma if bar.high > ma else bar.low - ma + bar.cache.update({cache_key: bull_power}) + + v1 = "其他" + if len(c.bars_raw) < di + w + 10: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + _bars = get_sub_elements(c.bars_raw, di=di, n=w*10) + factors = [x.cache[cache_key] for x in _bars] + factors = [x for x in factors if x * factors[-1] > 0] + + v1 = "均线上方" if factors[-1] > 0 else "均线下方" + q = pd.cut(factors, n, labels=list(range(1, n+1)), precision=5, duplicates='drop')[-1] + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=f"第{q}层") diff --git a/czsc/signals/bar.py b/czsc/signals/bar.py index 4eb5db0a6..06b96a40b 100644 --- a/czsc/signals/bar.py +++ b/czsc/signals/bar.py @@ -15,9 +15,10 @@ from czsc import envs, CZSC, Signal from czsc.traders.base import CzscSignals from czsc.objects import RawBar -from czsc.utils.sig import check_pressure_support, get_sub_elements, create_single_signal +from czsc.utils.sig import check_pressure_support from czsc.signals.tas import update_ma_cache from czsc.utils.bar_generator import freq_end_time +from czsc.utils import single_linear, freq_end_time, get_sub_elements, create_single_signal def bar_single_V230506(c: CZSC, **kwargs) -> OrderedDict: @@ -124,10 +125,17 @@ def bar_end_V221211(c: CZSC, freq1='60分钟', **kwargs) -> OrderedDict: 参数模板:"{freq}_{freq1}结束_BS辅助221211" + **信号逻辑:** + + 以 freq 为基础周期,freq1 为大周期,判断 freq1 K线是否结束。 + 如果结束,返回信号值为 "闭合",否则返回 "未闭x",x 为未闭合的次数。 + **信号列表:** - - Signal('60分钟_K线_结束_否_任意_任意_0') - - Signal('60分钟_K线_结束_是_任意_任意_0') + - Signal('15分钟_60分钟结束_BS辅助221211_未闭1_任意_任意_0') + - Signal('15分钟_60分钟结束_BS辅助221211_未闭2_任意_任意_0') + - Signal('15分钟_60分钟结束_BS辅助221211_未闭3_任意_任意_0') + - Signal('15分钟_60分钟结束_BS辅助221211_闭合_任意_任意_0') :param c: 基础周期的 CZSC 对象 :param freq1: 分钟周期名称 @@ -137,8 +145,18 @@ def bar_end_V221211(c: CZSC, freq1='60分钟', **kwargs) -> OrderedDict: k1, k2, k3 = f"{freq}_{freq1}结束_BS辅助221211".split('_') assert "分钟" in freq1 - dt: datetime = c.bars_raw[-1].dt - v = "是" if freq_end_time(dt, freq1) == dt else "否" + c1_dt = freq_end_time(c.bars_raw[-1].dt, freq1) + i = 0 + for bar in c.bars_raw[::-1]: + _edt = freq_end_time(bar.dt, freq1) + if _edt != c1_dt: + break + i += 1 + + if c1_dt == c.bars_raw[-1].dt: + v = "闭合" + else: + v = "未闭{}".format(i) return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v) @@ -1127,18 +1145,73 @@ def bar_zt_count_V230504(c: CZSC, **kwargs) -> OrderedDict: return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) bars = get_sub_elements(c.bars_raw, di=di, n=window) - c = [] + c1 = [] cc = 0 for b1, b2 in zip(bars[:-1], bars[1:]): if b2.close > b1.close * 1.07 and b2.close == b2.high: - c.append(1) + c1.append(1) else: - c.append(0) + c1.append(0) - if len(c) >= 2 and c[-1] == 1 and c[-2] == 1: + if len(c1) >= 2 and c1[-1] == 1 and c1[-2] == 1: cc += 1 - if sum(c) == 0: + if sum(c1) == 0: return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) else: - return create_single_signal(k1=k1, k2=k2, k3=k3, v1=f"{sum(c)}次", v2=f"连续{cc}次") + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=f"{sum(c1)}次", v2=f"连续{cc}次") + + +def bar_channel_V230508(c: CZSC, **kwargs) -> OrderedDict: + """N日内小阴小阳通道内运行 + + 参数模板:"{freq}_D{di}M{m}_通道V230507" + + **信号逻辑:** + + 1. 取N日内最高价和最低价,计算通道上下轨斜率; + 2. 看多:上轨斜率大于0,下轨斜率大于0,且内部K线的涨跌幅在M以内 + 3. 看空:上轨斜率小于0,下轨斜率小于0,且内部K线的涨跌幅在M以内 + + **信号列表:** + + - Signal('日线_D2M600_通道V230507_看空_任意_任意_0') + - Signal('日线_D2M600_通道V230507_看多_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + - :param di: 信号计算截止倒数第i根K线 + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + n = int(kwargs.get("n", 20)) + m = int(kwargs.get("m", 600)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}M{m}_通道V230507".split('_') + v1 = "其他" + + if len(c.bars_raw) < di + 10: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bars = get_sub_elements(c.bars_raw, di=di, n=n) + + if any(abs(x.close / x.open - 1) * 10000 > m for x in bars): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + res_high = single_linear([x.high for x in bars]) + res_low = single_linear([x.low for x in bars]) + high_right = max(x.high for x in bars[-3:]) + low_right = min(x.low for x in bars[-3:]) + max_high = max(x.high for x in bars) + min_low = min(x.low for x in bars) + + if not (res_high['r2'] > 0.8 and res_low['r2'] > 0.8): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + if res_high['slope'] > 0 and res_low['slope'] > 0 and high_right == max_high: + v1 = "看多" + + if res_high['slope'] < 0 and res_low['slope'] < 0 and low_right == min_low: + v1 = "看空" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) diff --git a/czsc/signals/bxt.py b/czsc/signals/bxt.py deleted file mode 100644 index 4c49633e9..000000000 --- a/czsc/signals/bxt.py +++ /dev/null @@ -1,747 +0,0 @@ -# -*- coding: utf-8 -*- -""" -author: zengbin93 -email: zeng_bin8888@163.com -create_dt: 2021/11/21 17:48 -describe: 笔相关信号的计算 -""" -from typing import List, Union -from collections import OrderedDict -from deprecated import deprecated -from czsc import analyze -from czsc.objects import Direction, BI, FakeBI, Signal -from czsc.enum import Freq -from czsc.utils.ta import RSQ - - -def check_three_bi(bis: List[Union[BI, FakeBI]], freq: Freq, di: int = 1) -> Signal: - """识别由远及近的三笔形态 - - :param freq: K线周期,也可以称为级别 - :param bis: 由远及近的三笔形态 - :param di: 最近一笔为倒数第i笔 - :return: - """ - v1 = '其他' - - if len(bis) == 3 and bis[0].direction == bis[2].direction: - bi1, bi2, bi3 = bis - assert bi3.direction in [Direction.Down, Direction.Up], "direction 的取值错误" - - # 识别向下形态 - if bi3.direction == Direction.Down: - if bi3.low > bi1.high: - v1 = '向下不重合' - elif bi2.low < bi3.low < bi1.high < bi2.high: - v1 = '向下奔走型' - elif bi1.high > bi3.high and bi1.low < bi3.low: - v1 = '向下收敛' - elif bi1.high < bi3.high and bi1.low > bi3.low: - v1 = '向下扩张' - elif bi3.low < bi1.low and bi3.high < bi1.high: - v1 = '向下盘背' if bi3.power < bi1.power else '向下无背' - - # 识别向上形态 - elif bi3.direction == Direction.Up: - if bi3.high < bi1.low: - v1 = '向上不重合' - elif bi2.low < bi1.low < bi3.high < bi2.high: - v1 = '向上奔走型' - elif bi1.high > bi3.high and bi1.low < bi3.low: - v1 = '向上收敛' - elif bi1.high < bi3.high and bi1.low > bi3.low: - v1 = '向上扩张' - elif bi3.low > bi1.low and bi3.high > bi1.high: - v1 = '向上盘背' if bi3.power < bi1.power else '向上无背' - - return Signal(k1=freq.value, k2=f"倒{di}笔", k3='三笔形态', v1=v1) - - -def check_five_bi(bis: List[Union[BI, FakeBI]], freq: Freq, di: int = 1) -> Signal: - """识别五笔形态 - - :param freq: K线周期,也可以称为级别 - :param bis: 由远及近的五笔 - :param di: 最近一笔为倒数第i笔 - :return: - """ - di_name = f"倒{di}笔" - v = Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='其他', v2='其他', v3='其他') - - if len(bis) != 5: - return v - - bi1, bi2, bi3, bi4, bi5 = bis - if not (bi1.direction == bi3.direction == bi5.direction): - print(f"1,3,5 的 direction 不一致,无法识别五段形态;{bi1}{bi3}{bi5}") - return v - - direction = bi1.direction - max_high = max([x.high for x in bis]) - min_low = min([x.low for x in bis]) - assert direction in [Direction.Down, Direction.Up], "direction 的取值错误" - - if direction == Direction.Down: - # aAb式底背驰 - if min(bi2.high, bi4.high) > max(bi2.low, bi4.low) and max_high == bi1.high and bi5.power < bi1.power: - if (min_low == bi3.low and bi5.low < bi1.low) or (min_low == bi5.low): - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='底背驰', v2='五笔aAb式') - - # 类趋势底背驰 - if max_high == bi1.high and min_low == bi5.low and bi4.high < bi2.low and bi5.power < max(bi3.power, bi1.power): - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='底背驰', v2='五笔类趋势') - - # 上颈线突破 - if (min_low == bi1.low and bi5.high > min(bi1.high, bi2.high) > bi5.low > bi1.low) \ - or (min_low == bi3.low and bi5.high > bi3.high > bi5.low > bi3.low): - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='上颈线突破', v2='五笔') - - # 五笔三买,要求bi5.high是最高点 - if max_high == bi5.high > bi5.low > max(bi1.high, bi3.high) \ - > min(bi1.high, bi3.high) > max(bi1.low, bi3.low) > min_low: - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='类三买', v2='五笔') - - if direction == Direction.Up: - # aAb式类一卖 - if min(bi2.high, bi4.high) > max(bi2.low, bi4.low) and min_low == bi1.low and bi5.power < bi1.power: - if (max_high == bi3.high and bi5.high > bi1.high) or (max_high == bi5.high): - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='顶背驰', v2='五笔aAb式') - - # 类趋势类一卖 - if min_low == bi1.low and max_high == bi5.high and bi5.power < max(bi1.power, bi3.power) and bi4.low > bi2.high: - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='顶背驰', v2='五笔类趋势') - - # 下颈线突破 - if (max_high == bi1.high and bi5.low < max(bi1.low, bi2.low) < bi5.high < max_high) \ - or (max_high == bi3.high and bi5.low < bi3.low < bi5.high < max_high): - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='下颈线突破', v2='五笔') - - # 五笔三卖,要求bi5.low是最低点 - if min_low == bi5.low < bi5.high < min(bi1.low, bi3.low) \ - < max(bi1.low, bi3.low) < min(bi1.high, bi3.high) < max_high: - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='类三卖', v2='五笔') - - return v - - -def check_seven_bi(bis: List[Union[BI, FakeBI]], freq: Freq, di: int = 1) -> Signal: - """识别七笔形态 - - :param freq: K线周期,也可以称为级别 - :param bis: 由远及近的七笔 - :param di: 最近一笔为倒数第i笔 - """ - di_name = f"倒{di}笔" - v = Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='其他', v2='其他', v3='其他') - - if len(bis) != 7: - return v - - bi1, bi2, bi3, bi4, bi5, bi6, bi7 = bis - max_high = max([x.high for x in bis]) - min_low = min([x.low for x in bis]) - direction = bi7.direction - - assert direction in [Direction.Down, Direction.Up], "direction 的取值错误" - - if direction == Direction.Down: - if bi1.high == max_high and bi7.low == min_low: - # aAbcd式底背驰 - if min(bi2.high, bi4.high) > max(bi2.low, bi4.low) > bi6.high and bi7.power < bi5.power: - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='底背驰', v2='七笔aAbcd式') - - # abcAd式底背驰 - if bi2.low > min(bi4.high, bi6.high) > max(bi4.low, bi6.low) and bi7.power < (bi1.high - bi3.low): - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='底背驰', v2='七笔abcAd式') - - # aAb式底背驰 - if min(bi2.high, bi4.high, bi6.high) > max(bi2.low, bi4.low, bi6.low) and bi7.power < bi1.power: - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='底背驰', v2='七笔aAb式') - - # 类趋势底背驰 - if bi2.low > bi4.high and bi4.low > bi6.high and bi7.power < max(bi5.power, bi3.power, bi1.power): - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='底背驰', v2='七笔类趋势') - - # 向上中枢完成 - if bi4.low == min_low and min(bi1.high, bi3.high) > max(bi1.low, bi3.low) \ - and min(bi5.high, bi7.high) > max(bi5.low, bi7.low) \ - and max(bi4.high, bi6.high) > min(bi3.high, bi4.high): - if max(bi1.low, bi3.low) < max(bi5.high, bi7.high): - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='向上中枢完成', v2='七笔') - - # 七笔三买:1~3构成中枢,最低点在1~3,最高点在5~7,5~7的最低点大于1~3的最高点 - if min(bi1.low, bi3.low) == min_low and max(bi5.high, bi7.high) == max_high \ - and min(bi5.low, bi7.low) > max(bi1.high, bi3.high) \ - and min(bi1.high, bi3.high) > max(bi1.low, bi3.low): - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='类三买', v2='七笔') - - if direction == Direction.Up: - # 顶背驰 - if bi1.low == min_low and bi7.high == max_high: - # aAbcd式顶背驰 - if bi6.low > min(bi2.high, bi4.high) > max(bi2.low, bi4.low) and bi7.power < bi5.power: - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='顶背驰', v2='七笔aAbcd式') - - # abcAd式顶背驰 - if min(bi4.high, bi6.high) > max(bi4.low, bi6.low) > bi2.high and bi7.power < (bi3.high - bi1.low): - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='顶背驰', v2='七笔abcAd式') - - # aAb式顶背驰 - if min(bi2.high, bi4.high, bi6.high) > max(bi2.low, bi4.low, bi6.low) and bi7.power < bi1.power: - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='顶背驰', v2='七笔aAb式') - - # 类趋势顶背驰 - if bi2.high < bi4.low and bi4.high < bi6.low and bi7.power < max(bi5.power, bi3.power, bi1.power): - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='顶背驰', v2='七笔类趋势') - - # 向下中枢完成 - if bi4.high == max_high and min(bi1.high, bi3.high) > max(bi1.low, bi3.low) \ - and min(bi5.high, bi7.high) > max(bi5.low, bi7.low) \ - and min(bi4.low, bi6.low) < max(bi3.low, bi4.low): - if min(bi1.high, bi3.high) > min(bi5.low, bi7.low): - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='向下中枢完成', v2='七笔') - - # 七笔三卖:1~3构成中枢,最高点在1~3,最低点在5~7,5~7的最高点小于1~3的最低点 - if min(bi5.low, bi7.low) == min_low and max(bi1.high, bi3.high) == max_high \ - and max(bi7.high, bi5.high) < min(bi1.low, bi3.low) \ - and min(bi1.high, bi3.high) > max(bi1.low, bi3.low): - return Signal(k1=freq.value, k2=di_name, k3='基础形态', v1='类三卖', v2='七笔') - return v - - -def check_nine_bi(bis: List[Union[BI, FakeBI]], freq: Freq, di: int = 1) -> Signal: - """识别九笔形态 - - :param freq: K线周期,也可以称为级别 - :param bis: 由远及近的九笔 - :param di: 最近一笔为倒数第i笔 - """ - di_name = f"倒{di}笔" - v = Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='其他', v2='其他', v3='其他') - if len(bis) != 9: - return v - - direction = bis[-1].direction - bi1, bi2, bi3, bi4, bi5, bi6, bi7, bi8, bi9 = bis - max_high = max([x.high for x in bis]) - min_low = min([x.low for x in bis]) - assert direction in [Direction.Down, Direction.Up], "direction 的取值错误" - - if direction == Direction.Down: - if min_low == bi9.low and max_high == bi1.high: - # aAb式类一买 - if min(bi2.high, bi4.high, bi6.high, bi8.high) > max(bi2.low, bi4.low, bi6.low, bi8.low) \ - and bi9.power < bi1.power and bi3.low >= bi1.low and bi7.high <= bi9.high: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一买', v2='九笔aAb式') - - # aAbcd式类一买 - if min(bi2.high, bi4.high, bi6.high) > max(bi2.low, bi4.low, bi6.low) > bi8.high \ - and bi9.power < bi7.power: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一买', v2='九笔aAbcd式') - - # ABC式类一买 - if bi3.low < bi1.low and bi7.high > bi9.high \ - and min(bi4.high, bi6.high) > max(bi4.low, bi6.low) \ - and (bi1.high - bi3.low) > (bi7.high - bi9.low): - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一买', v2='九笔ABC式') - - # 类趋势一买 - if bi8.high < bi6.low < bi6.high < bi4.low < bi4.high < bi2.low \ - and bi9.power < max([bi1.power, bi3.power, bi5.power, bi7.power]): - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一买', v2='九笔类趋势') - - # 九笔类一买(2~4构成中枢A,6~8构成中枢B,9背驰) - if max_high == max(bi1.high, bi3.high) and min_low == bi9.low \ - and min(bi2.high, bi4.high) > max(bi2.low, bi4.low) \ - and min(bi2.low, bi4.low) > max(bi6.high, bi8.high) \ - and min(bi6.high, bi8.high) > max(bi6.low, bi8.low) \ - and bi9.power < bi5.power: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一买', v2='九笔aAbBc式') - - # 类三买(1357构成中枢,最低点在3或5) - if max_high == bi9.high > bi9.low \ - > max([x.high for x in [bi1, bi3, bi5, bi7]]) \ - > min([x.high for x in [bi1, bi3, bi5, bi7]]) \ - > max([x.low for x in [bi1, bi3, bi5, bi7]]) \ - > min([x.low for x in [bi3, bi5]]) == min_low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类三买', v2='九笔GG三买') - - # 类三买(357构成中枢,8的力度小于2,9回调不跌破GG构成三买) - if bi8.power < bi2.power and max_high == bi9.high > bi9.low \ - > max([x.high for x in [bi3, bi5, bi7]]) \ - > min([x.high for x in [bi3, bi5, bi7]]) \ - > max([x.low for x in [bi3, bi5, bi7]]) > bi1.low == min_low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类三买', v2='九笔GG三买') - - if min_low == bi5.low and max_high == bi1.high and bi4.high < bi2.low: # 前五笔构成向下类趋势 - zd = max([x.low for x in [bi5, bi7]]) - zg = min([x.high for x in [bi5, bi7]]) - gg = max([x.high for x in [bi5, bi7]]) - if zg > zd and bi8.high > gg: # 567构成中枢,且8的高点大于gg - if bi9.low > zg: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类三买', v2='九笔ZG三买') - - # 类二买 - if bi9.high > gg > zg > bi9.low > zd: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类二买', v2='九笔') - - if direction == Direction.Up: - if max_high == bi9.high and min_low == bi1.low: - # aAbBc式类一卖 - if bi6.low > min(bi2.high, bi4.high) > max(bi2.low, bi4.low) \ - and min(bi6.high, bi8.high) > max(bi6.low, bi8.low) \ - and max(bi2.high, bi4.high) < min(bi6.low, bi8.low) \ - and bi9.power < bi5.power: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一卖', v2='九笔aAbBc式') - - # aAb式类一卖 - if min(bi2.high, bi4.high, bi6.high, bi8.high) > max(bi2.low, bi4.low, bi6.low, bi8.low) \ - and bi9.power < bi1.power and bi3.high <= bi1.high and bi7.low >= bi9.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一卖', v2='九笔aAb式') - - # aAbcd式类一卖 - if bi8.low > min(bi2.high, bi4.high, bi6.high) > max(bi2.low, bi4.low, bi6.low) \ - and bi9.power < bi7.power: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一卖', v2='九笔aAbcd式') - - # ABC式类一卖 - if bi3.high > bi1.high and bi7.low < bi9.low \ - and min(bi4.high, bi6.high) > max(bi4.low, bi6.low) \ - and (bi3.high - bi1.low) > (bi9.high - bi7.low): - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一卖', v2='九笔ABC式') - - # 类趋势一卖 - if bi8.low > bi6.high > bi6.low > bi4.high > bi4.low > bi2.high \ - and bi9.power < max([bi1.power, bi3.power, bi5.power, bi7.power]): - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一卖', v2='九笔类趋势') - - # 九笔三卖 - if max_high == bi1.high and min_low == bi9.low \ - and bi9.high < max([x.low for x in [bi3, bi5, bi7]]) < min([x.high for x in [bi3, bi5, bi7]]): - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类三卖', v2='九笔') - - if min_low == bi1.low and max_high == bi5.high and bi2.high < bi4.low: # 前五笔构成向上类趋势 - zd = max([x.low for x in [bi5, bi7]]) - zg = min([x.high for x in [bi5, bi7]]) - dd = min([x.low for x in [bi5, bi7]]) - if zg > zd and bi8.low < dd: # 567构成中枢,且8的低点小于dd - if bi9.high < zd: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类三卖', v2='九笔ZD三卖') - - # 类二卖 - if dd < zd <= bi9.high < zg: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类二卖', v2='九笔') - return v - - -def check_eleven_bi(bis: List[Union[BI, FakeBI]], freq: Freq, di: int = 1) -> Signal: - """识别十一笔形态 - - :param freq: K线周期,也可以称为级别 - :param bis: 由远及近的十一笔 - :param di: 最近一笔为倒数第i笔 - """ - di_name = f"倒{di}笔" - v = Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='其他', v2='其他', v3='其他') - if len(bis) != 11: - return v - - direction = bis[-1].direction - bi1, bi2, bi3, bi4, bi5, bi6, bi7, bi8, bi9, bi10, bi11 = bis - max_high = max([x.high for x in bis]) - min_low = min([x.low for x in bis]) - assert direction in [Direction.Down, Direction.Up], "direction 的取值错误" - - if direction == Direction.Down: - if min_low == bi11.low and max_high == bi1.high: - # ABC式类一买,A5B3C3 - if bi5.low == min([x.low for x in [bi1, bi3, bi5]]) \ - and bi9.low > bi11.low and bi9.high > bi11.high \ - and bi8.high > bi6.low and bi1.high - bi5.low > bi9.high - bi11.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一买', v2="11笔A5B3C3式") - - # ABC式类一买,A3B3C5 - if bi1.high > bi3.high and bi1.low > bi3.low \ - and bi7.high == max([x.high for x in [bi7, bi9, bi11]]) \ - and bi6.high > bi4.low and bi1.high - bi3.low > bi7.high - bi11.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一买', v2="11笔A3B3C5式") - - # ABC式类一买,A3B5C3 - if bi1.low > bi3.low and min(bi4.high, bi6.high, bi8.high) > max(bi4.low, bi6.low, bi8.low) \ - and bi9.high > bi11.high and bi1.high - bi3.low > bi9.high - bi11.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一买', v2="11笔A3B5C3式") - - # a1Ab式类一买,a1(1~7构成的类趋势) - if bi2.low > bi4.high > bi4.low > bi6.high > bi5.low > bi7.low and bi10.high > bi8.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一买', v2="11笔a1Ab式") - - # 类二买(1~7构成盘整背驰,246构成下跌中枢,9/11构成上涨中枢,且上涨中枢GG大于下跌中枢ZG) - if bi7.power < bi1.power and min_low == bi7.low < max([x.low for x in [bi2, bi4, bi6]]) \ - < min([x.high for x in [bi2, bi4, bi6]]) < max([x.high for x in [bi9, bi11]]) < bi1.high == max_high \ - and bi11.low > min([x.low for x in [bi2, bi4, bi6]]) \ - and min([x.high for x in [bi9, bi11]]) > max([x.low for x in [bi9, bi11]]): - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类二买', v2="11笔") - - # 类二买(1~7为区间极值,9~11构成上涨中枢,上涨中枢GG大于4~6的最大值,上涨中枢DD大于4~6的最小值) - if max_high == bi1.high and min_low == bi7.low \ - and min(bi9.high, bi11.high) > max(bi9.low, bi11.low) \ - and max(bi11.high, bi9.high) > max(bi4.high, bi6.high) \ - and min(bi9.low, bi11.low) > min(bi4.low, bi6.low): - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类二买', v2="11笔") - - # 类三买(1~9构成大级别中枢,10离开,11回调不跌破GG) - gg = max([x.high for x in [bi1, bi2, bi3]]) - zg = min([x.high for x in [bi1, bi2, bi3]]) - zd = max([x.low for x in [bi1, bi2, bi3]]) - dd = min([x.low for x in [bi1, bi2, bi3]]) - if max_high == bi11.high and bi11.low > zg > zd \ - and gg > bi5.low and gg > bi7.low and gg > bi9.low \ - and dd < bi5.high and dd < bi7.high and dd < bi9.high: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类三买', v2="11笔GG三买") - - if direction == Direction.Up: - if max_high == bi11.high and min_low == bi1.low: - # ABC式类一卖,A5B3C3 - if bi5.high == max([bi1.high, bi3.high, bi5.high]) and bi9.low < bi11.low and bi9.high < bi11.high \ - and bi8.low < bi6.high and bi11.high - bi9.low < bi5.high - bi1.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一卖', v2="11笔A5B3C3式") - - # ABC式类一卖,A3B3C5 - if bi7.low == min([bi11.low, bi9.low, bi7.low]) and bi1.high < bi3.high and bi1.low < bi3.low \ - and bi6.low < bi4.high and bi11.high - bi7.low < bi3.high - bi1.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一卖', v2="11笔A3B3C5式") - - # ABC式类一卖,A3B5C3 - if bi1.high < bi3.high and min(bi4.high, bi6.high, bi8.high) > max(bi4.low, bi6.low, bi8.low) \ - and bi9.low < bi11.low and bi3.high - bi1.low > bi11.high - bi9.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一卖', v2="11笔A3B5C3式") - - # 类二卖:1~9构成类趋势,11不创新高 - if max_high == bi9.high > bi8.low > bi6.high > bi6.low > bi4.high > bi4.low > bi2.high > bi1.low == min_low \ - and bi11.high < bi9.high: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类二卖', v2="11笔") - return v - - -def check_thirteen_bi(bis: List[Union[BI, FakeBI]], freq: Freq, di: int = 1) -> Signal: - """识别十三笔形态 - - :param freq: K线周期,也可以称为级别 - :param bis: 由远及近的十三笔 - :param di: 最近一笔为倒数第i笔 - """ - di_name = f"倒{di}笔" - v = Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='其他', v2='其他', v3='其他') - if len(bis) != 13: - return v - - direction = bis[-1].direction - bi1, bi2, bi3, bi4, bi5, bi6, bi7, bi8, bi9, bi10, bi11, bi12, bi13 = bis - max_high = max([x.high for x in bis]) - min_low = min([x.low for x in bis]) - - assert direction in [Direction.Down, Direction.Up], "direction 的取值错误" - - if direction == Direction.Down: - if min_low == bi13.low and max_high == bi1.high: - # ABC式类一买,A5B3C5 - if bi5.low < min(bi1.low, bi3.low) and bi9.high > max(bi11.high, bi13.high) \ - and bi8.high > bi6.low and bi1.high - bi5.low > bi9.high - bi13.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一买', v2="13笔A5B3C5式") - - # ABC式类一买,A3B5C5 - if bi3.low < min(bi1.low, bi5.low) and bi9.high > max(bi11.high, bi13.high) \ - and min(bi4.high, bi6.high, bi8.high) > max(bi4.low, bi6.low, bi8.low) \ - and bi1.high - bi3.low > bi9.high - bi13.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一买', v2="13笔A3B5C5式") - - # ABC式类一买,A5B5C3 - if bi5.low < min(bi1.low, bi3.low) and bi11.high > max(bi9.high, bi13.high) \ - and min(bi6.high, bi8.high, bi10.high) > max(bi6.low, bi8.low, bi10.low) \ - and bi1.high - bi5.low > bi11.high - bi13.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一买', v2="13笔A5B5C3式") - - if direction == Direction.Up: - if max_high == bi13.high and min_low == bi1.low: - # ABC式类一卖,A5B3C5 - if bi5.high > max(bi3.high, bi1.high) and bi9.low < min(bi11.low, bi13.low) \ - and bi8.low < bi6.high and bi5.high - bi1.low > bi13.high - bi9.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一卖', v2="13笔A5B3C5式") - - # ABC式类一卖,A3B5C5 - if bi3.high > max(bi5.high, bi1.high) and bi9.low < min(bi11.low, bi13.low) \ - and min(bi4.high, bi6.high, bi8.high) > max(bi4.low, bi6.low, bi8.low) \ - and bi3.high - bi1.low > bi13.high - bi9.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一卖', v2="13笔A3B5C5式") - - # ABC式类一卖,A5B5C3 - if bi5.high > max(bi3.high, bi1.high) and bi11.low < min(bi9.low, bi13.low) \ - and min(bi6.high, bi8.high, bi10.high) > max(bi6.low, bi8.low, bi10.low) \ - and bi5.high - bi1.low > bi13.high - bi11.low: - return Signal(k1=freq.value, k2=di_name, k3='类买卖点', v1='类一卖', v2="13笔A5B5C3式") - return v - - -# 以上是信号计算的辅助函数,主要是形态识别等。 -# ---------------------------------------------------------------------------------------------------------------------- -# 以下是信号计算函数(前缀固定为 get_s) - -@deprecated(version='1.0.0', reason="所有笔、分型相关信号迁移至 cxt 下") -def get_s_three_bi(c: analyze.CZSC, di: int = 1) -> OrderedDict: - """倒数第i笔的三笔形态信号 - - 信号完全分类: - Signal('日线_倒1笔_三笔形态_向上收敛_任意_任意_0') - Signal('日线_倒1笔_三笔形态_向下收敛_任意_任意_0') - Signal('日线_倒1笔_三笔形态_向下无背_任意_任意_0') - Signal('日线_倒1笔_三笔形态_向上不重合_任意_任意_0') - Signal('日线_倒1笔_三笔形态_向下盘背_任意_任意_0') - Signal('日线_倒1笔_三笔形态_向上扩张_任意_任意_0') - Signal('日线_倒1笔_三笔形态_向下不重合_任意_任意_0') - Signal('日线_倒1笔_三笔形态_向上盘背_任意_任意_0') - Signal('日线_倒1笔_三笔形态_向下扩张_任意_任意_0') - Signal('日线_倒1笔_三笔形态_向上奔走型_任意_任意_0') - Signal('日线_倒1笔_三笔形态_向上无背_任意_任意_0') - Signal('日线_倒1笔_三笔形态_向下奔走型_任意_任意_0') - - :param c: CZSC 对象 - :param di: 最近一笔为倒数第i笔 - :return: 信号字典 - """ - assert di >= 1 - bis = c.finished_bis - freq: Freq = c.freq - s = OrderedDict() - v = Signal(k1=str(freq.value), k2=f"倒{di}笔", k3="三笔形态", v1="其他", v2='其他', v3='其他') - s[v.key] = v.value - - if not bis: - return s - - if di == 1: - three_bi = bis[-3:] - else: - three_bi = bis[-3 - di + 1: -di + 1] - - v = check_three_bi(three_bi, freq, di) - s[v.key] = v.value - return s - - -@deprecated(version='1.0.0', reason="所有笔、分型相关信号迁移至 cxt 下") -def get_s_base_xt(c: analyze.CZSC, di: int = 1) -> OrderedDict: - """倒数第i笔的基础形态信号 - - 完全分类: - Signal('日线_倒1笔_基础形态_底背驰_五笔aAb式_任意_0'), - Signal('日线_倒1笔_基础形态_下颈线突破_五笔_任意_0'), - Signal('日线_倒1笔_基础形态_类三买_五笔_任意_0'), - Signal('日线_倒1笔_基础形态_向上中枢完成_七笔_任意_0'), - Signal('日线_倒1笔_基础形态_顶背驰_五笔aAb式_任意_0'), - Signal('日线_倒1笔_基础形态_顶背驰_七笔aAbcd式_任意_0'), - Signal('日线_倒1笔_基础形态_上颈线突破_五笔_任意_0'), - Signal('日线_倒1笔_基础形态_顶背驰_七笔aAb式_任意_0'), - Signal('日线_倒1笔_基础形态_类三买_七笔_任意_0'), - Signal('日线_倒1笔_基础形态_类三卖_五笔_任意_0'), - Signal('日线_倒1笔_基础形态_类三卖_七笔_任意_0'), - Signal('日线_倒1笔_基础形态_顶背驰_七笔abcAd式_任意_0') - - :param c: CZSC 对象 - :param di: 最近一笔为倒数第i笔 - :return: 信号字典 - """ - assert di >= 1 - - bis = c.finished_bis - freq: Freq = c.freq - s = OrderedDict() - v = Signal(k1=str(freq.value), k2=f"倒{di}笔", k3="基础形态", v1="其他", v2='其他', v3='其他') - s[v.key] = v.value - - if not bis: - return s - - if di == 1: - five_bi = bis[-5:] - seven_bi = bis[-7:] - else: - five_bi = bis[-5 - di + 1: -di + 1] - seven_bi = bis[-7 - di + 1: -di + 1] - - for v in [check_five_bi(five_bi, freq, di), check_seven_bi(seven_bi, freq, di)]: - if "其他" not in v.value: - s[v.key] = v.value - return s - - -@deprecated(version='1.0.0', reason="所有笔、分型相关信号迁移至 cxt 下") -def get_s_like_bs(c: analyze.CZSC, di: int = 1) -> OrderedDict: - """倒数第i笔的类买卖点信号 - - :param c: CZSC 对象 - :param di: 最近一笔为倒数第i笔 - :return: 信号字典 - """ - assert di >= 1 - bis = c.finished_bis - freq: Freq = c.freq - s = OrderedDict() - v = Signal(k1=str(freq.value), k2=f"倒{di}笔", k3="类买卖点", v1="其他", v2='其他', v3='其他') - s[v.key] = v.value - - if not bis: - return s - - if di == 1: - nine_bi = bis[-9:] - eleven_bi = bis[-11:] - thirteen_bi = bis[-13:] - else: - nine_bi = bis[-9 - di + 1: -di + 1] - eleven_bi = bis[-11 - di + 1: -di + 1] - thirteen_bi = bis[-13 - di + 1: -di + 1] - - for v in [check_nine_bi(nine_bi, freq, di), check_eleven_bi(eleven_bi, freq, di), - check_thirteen_bi(thirteen_bi, freq, di)]: - if "其他" not in v.value: - s[v.key] = v.value - return s - - -@deprecated(version='1.0.0', reason="所有笔、分型相关信号迁移至 cxt 下") -def get_s_bi_status(c: analyze.CZSC) -> OrderedDict: - """倒数第1笔的表里关系信号 - - :param c: CZSC 对象 - :return: 信号字典 - """ - freq: Freq = c.freq - s = OrderedDict() - v = Signal(k1=str(freq.value), k2="倒1笔", k3="表里关系", v1="其他", v2='其他', v3='其他') - s[v.key] = v.value - - if c.bi_list: - # 表里关系的定义参考:http://blog.sina.com.cn/s/blog_486e105c01007wc1.html - min_ubi = min([x.low for x in c.bars_ubi]) - max_ubi = max([x.high for x in c.bars_ubi]) - - last_bi = c.bi_list[-1] - v = None - if last_bi.direction == Direction.Down: - if min_ubi < last_bi.low: - v = Signal(k1=str(freq.value), k2="倒1笔", k3="表里关系", v1="向下延伸") - else: - v = Signal(k1=str(freq.value), k2="倒1笔", k3="表里关系", v1="底分完成") - if last_bi.direction == Direction.Up: - if max_ubi > last_bi.high: - v = Signal(k1=str(freq.value), k2="倒1笔", k3="表里关系", v1="向上延伸") - else: - v = Signal(k1=str(freq.value), k2="倒1笔", k3="表里关系", v1="顶分完成") - - if v and "其他" not in v.value: - s[v.key] = v.value - return s - - -@deprecated(version='1.0.0', reason="所有笔、分型相关信号迁移至 cxt 下") -def get_s_d0_bi(c: analyze.CZSC) -> OrderedDict: - """倒数第0笔信号 - - :param c: CZSC 对象 - :return: 信号字典 - """ - freq: Freq = c.freq - s = OrderedDict() - - default_signals = [ - Signal(k1=str(freq.value), k2="倒0笔", k3="方向", v1="其他", v2='其他', v3='其他'), - Signal(k1=str(freq.value), k2="倒0笔", k3="长度", v1="其他", v2='其他', v3='其他'), - ] - for signal in default_signals: - s[signal.key] = signal.value - - bis = c.finished_bis - - if bis: - # 倒0笔方向 - last_bi = bis[-1] - if last_bi.direction == Direction.Down: - v = Signal(k1=str(freq.value), k2="倒0笔", k3="方向", v1="向上") - elif last_bi.direction == Direction.Up: - v = Signal(k1=str(freq.value), k2="倒0笔", k3="方向", v1="向下") - else: - raise ValueError - - if v and "其他" not in v.value: - s[v.key] = v.value - - # 倒0笔长度 - bars_ubi = [x for x in c.bars_raw[-20:] if x.dt >= bis[-1].fx_b.elements[0].dt] - if len(bars_ubi) >= 9: - v = Signal(k1=str(freq.value), k2="倒0笔", k3="长度", v1="9根K线以上") - elif 9 > len(bars_ubi) > 5: - v = Signal(k1=str(freq.value), k2="倒0笔", k3="长度", v1="5到9根K线") - else: - v = Signal(k1=str(freq.value), k2="倒0笔", k3="长度", v1="5根K线以下") - - if "其他" not in v.value: - s[v.key] = v.value - return s - - -@deprecated(version='1.0.0', reason="所有笔、分型相关信号迁移至 cxt 下") -def get_s_di_bi(c: analyze.CZSC, di: int = 1) -> OrderedDict: - """倒数第i笔的表里关系信号 - - :param c: CZSC 对象 - :param di: 最近一笔为倒数第i笔 - :return: 信号字典 - """ - assert di >= 1 - freq: Freq = c.freq - s = OrderedDict() - k1 = str(freq.value) - k2 = f"倒{di}笔" - - default_signals = [ - Signal(k1=k1, k2=k2, k3="方向", v1="其他", v2='其他', v3='其他'), - Signal(k1=k1, k2=k2, k3="长度", v1="其他", v2='其他', v3='其他'), - Signal(k1=k1, k2=k2, k3="拟合优度", v1="其他", v2='其他', v3='其他'), - ] - for signal in default_signals: - s[signal.key] = signal.value - - bis = c.finished_bis - if not bis: - return s - - last_bi = bis[-di] - - # 方向 - v1 = Signal(k1=k1, k2=k2, k3="方向", v1=last_bi.direction.value) - s[v1.key] = v1.value - - # 长度 - if len(last_bi.bars) >= 15: - v = Signal(k1=k1, k2=k2, k3="长度", v1="15根K线以上") - elif 15 > len(c.bars_ubi) > 9: - v = Signal(k1=k1, k2=k2, k3="长度", v1="9到15根K线") - else: - v = Signal(k1=k1, k2=k2, k3="长度", v1="9根K线以下") - - if "其他" not in v.value: - s[v.key] = v.value - - # 拟合优度 - rsq = RSQ([x.close for x in last_bi.bars[1:-1]]) - if rsq > 0.8: - v = Signal(k1=k1, k2=k2, k3="拟合优度", v1="大于0.8") - elif rsq < 0.2: - v = Signal(k1=k1, k2=k2, k3="拟合优度", v1="小于0.2") - else: - v = Signal(k1=k1, k2=k2, k3="拟合优度", v1="0.2到0.8之间") - - if "其他" not in v.value: - s[v.key] = v.value - return s - diff --git a/czsc/signals/coo.py b/czsc/signals/coo.py index 7b7449d40..f9f0465b8 100644 --- a/czsc/signals/coo.py +++ b/czsc/signals/coo.py @@ -8,13 +8,11 @@ import numpy as np from deprecated import deprecated from collections import OrderedDict -from czsc import CZSC, Signal +from czsc import CZSC from czsc.utils import create_single_signal, get_sub_elements +from czsc.signals.tas import update_ma_cache, update_sar_cache, update_kdj_cache, update_cci_cache -# 在这里可以定义自己的信号函数 -# ---------------------------------------------------------------------------------------------------------------------- - def __cal_td_seq(close: np.ndarray): """TDSEQ计算辅助函数 @@ -62,7 +60,7 @@ def coo_td_V221110(c: CZSC, **kwargs) -> OrderedDict: if di == 1: close = np.array([x.close for x in c.bars_raw[-50:]]) else: - close = np.array([x.close for x in c.bars_raw[-50 - di + 1:-di + 1]]) + close = np.array([x.close for x in c.bars_raw[-50 - di + 1 : -di + 1]]) td = __cal_td_seq(close) if td[-1] > 0: @@ -78,7 +76,6 @@ def coo_td_V221110(c: CZSC, **kwargs) -> OrderedDict: return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) - def coo_td_V221111(c: CZSC, **kwargs) -> OrderedDict: """获取倒数第i根K线的TD信号 @@ -106,7 +103,7 @@ def coo_td_V221111(c: CZSC, **kwargs) -> OrderedDict: k1, k2, k3 = f"{freq}_D{di}TD_BS辅助V221111".split("_") if len(c.bars_raw) < 50 + di: return create_single_signal(k1=k1, k2=k2, k3=k3, v1="其他") - + bars = get_sub_elements(c.bars_raw, di=di, n=50) close = np.array([x.close for x in bars]) td = __cal_td_seq(close) @@ -122,3 +119,153 @@ def coo_td_V221111(c: CZSC, **kwargs) -> OrderedDict: v2 = '其他' return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) + + +def coo_cci_V230323(c: CZSC, **kwargs) -> OrderedDict: + """CCI结合均线的多空信号 + + 参数模板:"{freq}_D{di}CCI{n}#{ma_type}#{m}_BS辅助V230323" + + **信号逻辑:** + + 1. CCI大于100,且向上突破均线,看多; + 2. CCI小于-100,且向下突破均线,看空; + + **信号列表:** + + - Signal('15分钟_D1CCI20#SMA#5_BS辅助V230323_空头_向下_任意_0') + - Signal('15分钟_D1CCI20#SMA#5_BS辅助V230323_空头_向上_任意_0') + - Signal('15分钟_D1CCI20#SMA#5_BS辅助V230323_多头_向上_任意_0') + - Signal('15分钟_D1CCI20#SMA#5_BS辅助V230323_多头_向下_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + - :param di: 信号计算截止倒数第i根K线 + - :param n: CCI的计算周期 + - :param m: 乘以N表示均线的计算周期 + :return: 返回信号结果 + """ + di = int(kwargs.get("di", 1)) + n = int(kwargs.get("n", 20)) + m = int(kwargs.get("m", 5)) + ma_type = kwargs.get('ma_type', 'SMA').upper() + freq = c.freq.value + cache_key_cci = update_cci_cache(c, timeperiod=n) + cache_key_ma = update_ma_cache(c, ma_type=ma_type, timeperiod=n * m) + + k1, k2, k3 = f"{freq}_D{di}CCI{n}#{ma_type}#{m}_BS辅助V230323".split('_') + v1 = '其他' + if len(c.bars_raw) < n * m + di: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bars = get_sub_elements(c.bars_raw, di=di, n=2) + cci = bars[-1].cache[cache_key_cci] + MA_CC = bars[-1].cache[cache_key_ma] + + if cci > 100 and bars[-1].close > MA_CC: + v1 = '多头' + + if cci < -100 and bars[-1].close < MA_CC: + v1 = '空头' + + if v1 == '其他': + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + v2 = "向上" if cci >= bars[-2].cache[cache_key_cci] else "向下" + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) + + +def coo_kdj_V230322(c: CZSC, **kwargs) -> OrderedDict: + """均线判定方向,KD决定进场时机 + + 参数模板:"{freq}_D{di}KDJ{fastk_period}#{slowk_period}#{slowd_period}#{ma_type}#{n}_BS辅助V230322" + + **信号逻辑:** + + 1. K线向上突破均线,且 K < D,看多; + 2. K线向下突破均线,且 K > D,看空; + + **信号列表:** + + - Signal('15分钟_D1KDJ9#3#3#EMA#20_BS辅助V230322_空头_任意_任意_0') + - Signal('15分钟_D1KDJ9#3#3#EMA#20_BS辅助V230322_多头_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + - :param di: 信号计算截止倒数第i根K线 + - :param n: 均线计算周期 + - :param fastk_period: kdj参数 + - :param slowk_period: kdj参数 + - :param slowd_period: kdj参数 + :return: 返回信号结果 + """ + di = int(kwargs.get("di", 1)) + n = int(kwargs.get("n", 3)) + ma_type = kwargs.get('ma_type', 'EMA').upper() + fastk_period = int(kwargs.get('fastk_period', 9)) + slowk_period = int(kwargs.get('slowk_period', 3)) + slowd_period = int(kwargs.get('slowd_period', 3)) + + ma = update_ma_cache(c, ma_type=ma_type, timeperiod=n) + cache_key = update_kdj_cache(c, fastk_period=fastk_period, slowk_period=slowk_period, slowd_period=slowd_period) + + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}KDJ{fastk_period}#{slowk_period}#{slowd_period}#{ma_type}#{n}_BS辅助V230322".split('_') + if len(c.bars_raw) < fastk_period * slowk_period + di: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1="其他") + + _bars = get_sub_elements(c.bars_raw, di=di, n=n) + kdj, mac = _bars[-1].cache[cache_key], _bars[-1].cache[ma] + + if _bars[-1].close > mac and kdj['k'] < kdj['d']: + v1 = "多头" + elif _bars[-1].close < mac and kdj['k'] > kdj['d']: + v1 = "空头" + else: + v1 = "其他" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def coo_sar_V230325(c: CZSC, **kwargs) -> OrderedDict: + """SAR和高低点结合判断买卖时机 + + 参数模板:"{freq}_D{di}N{n}SAR_BS辅助V230325" + + **信号逻辑:** + + 1. 最高价大于N个周期收盘价的最高价,收盘价在SAR上方,看多; + 2. 最低价小于N个周期收盘价的最低价,收盘价在SAR下方,看空; + + **信号列表:** + + - Signal('15分钟_D1N20SAR_BS辅助V230325_空头_任意_任意_0') + - Signal('15分钟_D1N20SAR_BS辅助V230325_多头_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + - :param di: 信号计算截止倒数第i根K线 + - :param n: 信号计算的K线数量 + :return: 返回信号结果 + """ + di = int(kwargs.get("di", 1)) + n = int(kwargs.get("n", 60)) + cache_key = update_sar_cache(c) + + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}N{n}SAR_BS辅助V230325".split('_') + v1 = "其他" + if len(c.bars_raw) < n + di + 10: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + _bars = get_sub_elements(c.bars_raw, di=di, n=n) + hhv = max([x.close for x in _bars]) + llv = min([x.close for x in _bars]) + sar, close = _bars[-1].cache[cache_key], _bars[-1].close + + if close > sar and _bars[-1].high >= hhv: + v1 = "多头" + if close < sar and _bars[-1].low <= llv: + v1 = "空头" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) diff --git a/czsc/signals/cxt.py b/czsc/signals/cxt.py index a157452bf..8f223ea0f 100644 --- a/czsc/signals/cxt.py +++ b/czsc/signals/cxt.py @@ -6,15 +6,17 @@ describe: cxt 代表 CZSC 形态信号 """ import numpy as np -from loguru import logger +import pandas as pd from typing import List from czsc import CZSC from czsc.traders.base import CzscSignals from czsc.objects import FX, BI, Direction, ZS, Mark -from czsc.utils import get_sub_elements, create_single_signal, is_bis_up, is_bis_down +from czsc.utils import get_sub_elements, create_single_signal from czsc.utils.sig import get_zs_seq from czsc.signals.tas import update_ma_cache, update_macd_cache from collections import OrderedDict +from deprecated import deprecated + def cxt_bi_base_V230228(c: CZSC, **kwargs) -> OrderedDict: @@ -561,6 +563,7 @@ def cxt_second_bs_V230320(c: CZSC, **kwargs) -> OrderedDict: return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) +@deprecated(version='1.0.0', reason="即将删除,请使用 cxt_third_bs_V230319") def cxt_third_bs_V230318(c: CZSC, **kwargs) -> OrderedDict: """均线辅助识别第三类买卖点 @@ -1090,4 +1093,727 @@ def cxt_bi_status_V230102(c: CZSC, **kwargs) -> OrderedDict: return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) +def cxt_bi_zdf_V230601(c: CZSC, **kwargs) -> OrderedDict: + """BI涨跌幅的分层判断 + + 参数模板:"{freq}_D{di}N{n}_分层V230601" + + **信号逻辑:** + + 取最近50个缠论笔,计算涨跌幅,分N层判断。 + + **信号列表:** + + - Signal('60分钟_D1N5_分层V230601_向下_第5层_任意_0') + - Signal('60分钟_D1N5_分层V230601_向上_第5层_任意_0') + - Signal('60分钟_D1N5_分层V230601_向下_第3层_任意_0') + - Signal('60分钟_D1N5_分层V230601_向上_第2层_任意_0') + - Signal('60分钟_D1N5_分层V230601_向上_第4层_任意_0') + - Signal('60分钟_D1N5_分层V230601_向下_第2层_任意_0') + - Signal('60分钟_D1N5_分层V230601_向上_第1层_任意_0') + - Signal('60分钟_D1N5_分层V230601_向下_第1层_任意_0') + - Signal('60分钟_D1N5_分层V230601_向上_第3层_任意_0') + - Signal('60分钟_D1N5_分层V230601_向下_第4层_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + - di: 倒数第几根K线 + - n: 取截止dik的前n根K线 + :return: 返回信号结果 + """ + di = int(kwargs.get('di', 1)) + n = int(kwargs.get('n', 5)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}N{n}_分层V230601".split('_') + v1, v2 = '其他', '其他' + if len(c.bi_list) < 10 or len(c.bars_ubi) > 7: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bis = get_sub_elements(c.bi_list, di=di, n=50) + v1 = bis[-1].direction.value + powers = [x.power for x in bis] + v2 = pd.qcut(powers, n, labels=False, duplicates='drop')[-1] + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=f"第{v2 + 1}层") + + +def cxt_bi_end_V230618(c: CZSC, **kwargs) -> OrderedDict: + """笔结束辅助判断,贡献者:chenlei + + 参数模板:"{freq}_D{di}_BE辅助V230618" + + **信号逻辑:** + + 以向下笔为例,判断笔内是否有小级别中枢,如果有则看多,小中枢判断方法如下: + + 1. 笔内任意两根k线的重叠使该价格位的计数加1,计算从笔.high到笔.low之间各价格位的重叠次数 + 2. 通过各价格位的重叠可以得到横轴价格,纵轴重叠次数的图,通过计算途中波峰的个数来得到近似的小中枢个数 + + 例子:横轴从小到大对应的重叠次数为 1112233211112133334445553321,则可以通过计算从n变为1的次数来得到波峰个数 + 这里2-1,2-1,2-1,得到波峰数为3 + + **信号列表:** + + - Signal('日线_D1_BE辅助V230618_看多_1小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看空_3小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看空_2小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看空_1小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看多_2小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看空_5小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看空_4小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看多_3小中枢_任意_0') + + :param c: CZSC对象 + :param kwargs: + + - di: 倒数第几BI + - timeperiod: 均线周期 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + k1, k2, k3 = f"{c.freq.value}_D{di}_BE辅助V230618".split('_') + v1 = "其他" + if len(c.bi_list) < di + 6 or len(c.bars_ubi) > 7: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + def __cal_zs_number(raw_bars): + """计算笔内的小中枢数量""" + # 用笔内价格极值取得笔内价格范围 + max_price = max(bar.high for bar in raw_bars[:-1]) + min_price = min(bar.low for bar in raw_bars[:-1]) + price_range = max_price - min_price + + # 计算当前k线所覆盖的笔内价格范围,并用百分比表示 + for bar in raw_bars[:-1]: + bar_high_pct = int((100 * (bar.high - min_price) / price_range)) + bar_low_pct = int((100 * (bar.low - min_price) / price_range)) + bar.dt_high_pct = bar_high_pct + bar.dt_low_pct = bar_low_pct + + # 用这个list保存每个价格的重叠次数,把每个价格映射到100以内的区间内 + df_chengjiaoqu = [[i, 0] for i in range(101)] + + # 对每个k线进行映射,把该k线的价格范围映射到df_chengjiaoqu + for bar in raw_bars[:-1]: + range_max = bar.dt_high_pct + range_min = bar.dt_low_pct + + if range_max == range_min: + df_chengjiaoqu[range_max][1] += 1 + else: + for i in range(range_min, range_max + 1): + df_chengjiaoqu[i][1] += 1 + + # 计算波峰个数,相当于有多少个小中枢 + # 每个波峰结束后价格重叠区域必然会回到1 + peak_count = 0 + for i in range(1, len(df_chengjiaoqu) - 1): + if df_chengjiaoqu[i][1] == 1 and df_chengjiaoqu[i][1] < df_chengjiaoqu[i - 1][1]: + peak_count += 1 + return peak_count + + bi = c.bi_list[-di] + zs_count = __cal_zs_number(bi.raw_bars) + v1 = '看多' if bi.direction == Direction.Down else '看空' + # 为了增加稳定性,要确保笔内有小中枢,并且要确保笔内有至少2个分型存在,保证从上往下的分型12的长度比分型34的长度大,来确认背驰 + if len(bi.fxs) >= 4 and zs_count >= 1 and (bi.fxs[-4].fx - bi.fxs[-3].fx) - (bi.fxs[-2].fx - bi.fxs[-1].fx) > 0: + # 计算倒1笔内部的小中枢数量 + v2 = f"{zs_count}小中枢" + else: + v2 = "其他" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) + + +def cxt_three_bi_V230618(c: CZSC, **kwargs) -> OrderedDict: + """三笔形态分类 + + 参数模板:"{freq}_D{di}三笔_形态V230618" + + **信号逻辑:** + + 三笔的形态分类 + + **信号列表:** + + - Signal('日线_D1三笔_形态V230618_向下盘背_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向上奔走型_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向上扩张_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向下奔走型_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向上收敛_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向下无背_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向上不重合_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向下收敛_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向下扩张_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向下不重合_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向上盘背_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向上无背_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: + + - di: 倒数第几笔 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}三笔_形态V230618".split('_') + v1 = "其他" + if len(c.bi_list) < di + 6 or len(c.bars_ubi) > 7: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bis = get_sub_elements(c.bi_list, di=di, n=3) + assert len(bis) == 3 and bis[0].direction == bis[2].direction + bi1, bi2, bi3 = bis + + # 识别向下形态 + if bi3.direction == Direction.Down: + if bi3.low > bi1.high: + v1 = '向下不重合' + elif bi2.low < bi3.low < bi1.high < bi2.high: + v1 = '向下奔走型' + elif bi1.high > bi3.high and bi1.low < bi3.low: + v1 = '向下收敛' + elif bi1.high < bi3.high and bi1.low > bi3.low: + v1 = '向下扩张' + elif bi3.low < bi1.low and bi3.high < bi1.high: + v1 = '向下盘背' if bi3.power < bi1.power else '向下无背' + + # 识别向上形态 + elif bi3.direction == Direction.Up: + if bi3.high < bi1.low: + v1 = '向上不重合' + elif bi2.low < bi1.low < bi3.high < bi2.high: + v1 = '向上奔走型' + elif bi1.high > bi3.high and bi1.low < bi3.low: + v1 = '向上收敛' + elif bi1.high < bi3.high and bi1.low > bi3.low: + v1 = '向上扩张' + elif bi3.low > bi1.low and bi3.high > bi1.high: + v1 = '向上盘背' if bi3.power < bi1.power else '向上无背' + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def cxt_five_bi_V230619(c: CZSC, **kwargs) -> OrderedDict: + """五笔形态分类 + + 参数模板:"{freq}_D{di}五笔_形态V230619" + + **信号逻辑:** + + 五笔的形态分类 + + **信号列表:** + + - Signal('60分钟_D1五笔_形态V230619_上颈线突破_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_类三卖_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_类趋势底背驰_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_类趋势顶背驰_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_下颈线突破_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_类三买_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_aAb式顶背驰_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_aAb式底背驰_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: + + - di: 倒数第几笔 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}五笔_形态V230619".split('_') + v1 = "其他" + if len(c.bi_list) < di + 6 or len(c.bars_ubi) > 7: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bis = get_sub_elements(c.bi_list, di=di, n=5) + assert len(bis) == 5 and bis[0].direction == bis[2].direction == bis[4].direction, "笔的方向错误" + bi1, bi2, bi3, bi4, bi5 = bis + + direction = bi1.direction + max_high = max([x.high for x in bis]) + min_low = min([x.low for x in bis]) + assert direction in [Direction.Down, Direction.Up], "direction 的取值错误" + + if direction == Direction.Down: + # aAb式底背驰 + if min(bi2.high, bi4.high) > max(bi2.low, bi4.low) and max_high == bi1.high and bi5.power < bi1.power: + if (min_low == bi3.low and bi5.low < bi1.low) or (min_low == bi5.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAb式底背驰') + + # 类趋势底背驰 + if max_high == bi1.high and min_low == bi5.low and bi4.high < bi2.low and bi5.power < max(bi3.power, bi1.power): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类趋势底背驰') + + # 上颈线突破 + if (min_low == bi1.low and bi5.high > min(bi1.high, bi2.high) > bi5.low > bi1.low) \ + or (min_low == bi3.low and bi5.high > bi3.high > bi5.low > bi3.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='上颈线突破') + + # 五笔三买,要求bi5.high是最高点 + if max_high == bi5.high > bi5.low > max(bi1.high, bi3.high) \ + > min(bi1.high, bi3.high) > max(bi1.low, bi3.low) > min_low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三买') + + if direction == Direction.Up: + # aAb式顶背驰 + if min(bi2.high, bi4.high) > max(bi2.low, bi4.low) and min_low == bi1.low and bi5.power < bi1.power: + if (max_high == bi3.high and bi5.high > bi1.high) or (max_high == bi5.high): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAb式顶背驰') + + # 类趋势顶背驰 + if min_low == bi1.low and max_high == bi5.high and bi5.power < max(bi1.power, bi3.power) and bi4.low > bi2.high: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类趋势顶背驰') + + # 下颈线突破 + if (max_high == bi1.high and bi5.low < max(bi1.low, bi2.low) < bi5.high < max_high) \ + or (max_high == bi3.high and bi5.low < bi3.low < bi5.high < max_high): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='下颈线突破') + + # 五笔三卖,要求bi5.low是最低点 + if min_low == bi5.low < bi5.high < min(bi1.low, bi3.low) \ + < max(bi1.low, bi3.low) < min(bi1.high, bi3.high) < max_high: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三卖') + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def cxt_seven_bi_V230620(c: CZSC, **kwargs) -> OrderedDict: + """七笔形态分类 + + 参数模板:"{freq}_D{di}七笔_形态V230620" + + **信号逻辑:** + + 七笔的形态分类 + + **信号列表:** + + - Signal('60分钟_D1七笔_形态V230620_类三卖_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_向上中枢完成_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_aAbcd式顶背驰_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_类三买_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_向下中枢完成_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_aAb式底背驰_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_abcAd式顶背驰_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_abcAd式底背驰_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_aAb式顶背驰_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_类趋势顶背驰_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_aAbcd式底背驰_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: + + - di: 倒数第几笔 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}七笔_形态V230620".split('_') + v1 = "其他" + if len(c.bi_list) < di + 10 or len(c.bars_ubi) > 7: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bis = get_sub_elements(c.bi_list, di=di, n=7) + assert len(bis) == 7 and bis[0].direction == bis[2].direction == bis[4].direction, "笔的方向错误" + bi1, bi2, bi3, bi4, bi5, bi6, bi7 = bis + max_high = max([x.high for x in bis]) + min_low = min([x.low for x in bis]) + direction = bi7.direction + + if direction == Direction.Down: + if bi1.high == max_high and bi7.low == min_low: + # aAbcd式底背驰 + if min(bi2.high, bi4.high) > max(bi2.low, bi4.low) > bi6.high and bi7.power < bi5.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAbcd式底背驰') + + # abcAd式底背驰 + if bi2.low > min(bi4.high, bi6.high) > max(bi4.low, bi6.low) and bi7.power < (bi1.high - bi3.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='abcAd式底背驰') + + # aAb式底背驰 + if min(bi2.high, bi4.high, bi6.high) > max(bi2.low, bi4.low, bi6.low) and bi7.power < bi1.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAb式底背驰') + + # 类趋势底背驰 + if bi2.low > bi4.high and bi4.low > bi6.high and bi7.power < max(bi5.power, bi3.power, bi1.power): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类趋势底背驰') + + # 向上中枢完成 + if bi4.low == min_low and min(bi1.high, bi3.high) > max(bi1.low, bi3.low) \ + and min(bi5.high, bi7.high) > max(bi5.low, bi7.low) \ + and max(bi4.high, bi6.high) > min(bi3.high, bi4.high): + if max(bi1.low, bi3.low) < max(bi5.high, bi7.high): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='向上中枢完成') + + # 七笔三买:1~3构成中枢,最低点在1~3,最高点在5~7,5~7的最低点大于1~3的最高点 + if min(bi1.low, bi3.low) == min_low and max(bi5.high, bi7.high) == max_high \ + and min(bi5.low, bi7.low) > max(bi1.high, bi3.high) \ + and min(bi1.high, bi3.high) > max(bi1.low, bi3.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三买') + + if direction == Direction.Up: + # 顶背驰 + if bi1.low == min_low and bi7.high == max_high: + # aAbcd式顶背驰 + if bi6.low > min(bi2.high, bi4.high) > max(bi2.low, bi4.low) and bi7.power < bi5.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAbcd式顶背驰') + + # abcAd式顶背驰 + if min(bi4.high, bi6.high) > max(bi4.low, bi6.low) > bi2.high and bi7.power < (bi3.high - bi1.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='abcAd式顶背驰') + + # aAb式顶背驰 + if min(bi2.high, bi4.high, bi6.high) > max(bi2.low, bi4.low, bi6.low) and bi7.power < bi1.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAb式顶背驰') + + # 类趋势顶背驰 + if bi2.high < bi4.low and bi4.high < bi6.low and bi7.power < max(bi5.power, bi3.power, bi1.power): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类趋势顶背驰') + + # 向下中枢完成 + if bi4.high == max_high and min(bi1.high, bi3.high) > max(bi1.low, bi3.low) \ + and min(bi5.high, bi7.high) > max(bi5.low, bi7.low) \ + and min(bi4.low, bi6.low) < max(bi3.low, bi4.low): + if min(bi1.high, bi3.high) > min(bi5.low, bi7.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='向下中枢完成') + + # 七笔三卖:1~3构成中枢,最高点在1~3,最低点在5~7,5~7的最高点小于1~3的最低点 + if min(bi5.low, bi7.low) == min_low and max(bi1.high, bi3.high) == max_high \ + and max(bi7.high, bi5.high) < min(bi1.low, bi3.low) \ + and min(bi1.high, bi3.high) > max(bi1.low, bi3.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三卖') + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def cxt_nine_bi_V230621(c: CZSC, **kwargs) -> OrderedDict: + """九笔形态分类 + + 参数模板:"{freq}_D{di}九笔_形态V230621" + + **信号逻辑:** + + 九笔的形态分类 + + **信号列表:** + + - Signal('60分钟_D1九笔_形态V230621_类三买A_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_aAb式类一卖_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_类三卖A_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_aAbcd式类一买_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_ABC式类一卖_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_aAbBc式类一买_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_aAbcd式类一卖_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_ZD三卖_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_aAbBc式类一卖_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_ABC式类一买_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: + + - di: 倒数第几笔 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}九笔_形态V230621".split('_') + v1 = "其他" + if len(c.bi_list) < di + 13 or len(c.bars_ubi) > 7: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bis = get_sub_elements(c.bi_list, di=di, n=9) + assert len(bis) == 9 and bis[0].direction == bis[2].direction == bis[4].direction, "笔的方向错误" + bi1, bi2, bi3, bi4, bi5, bi6, bi7, bi8, bi9 = bis + max_high = max([x.high for x in bis]) + min_low = min([x.low for x in bis]) + direction = bi9.direction + + if direction == Direction.Down: + if min_low == bi9.low and max_high == bi1.high: + # aAb式类一买 + if min(bi2.high, bi4.high, bi6.high, bi8.high) > max(bi2.low, bi4.low, bi6.low, bi8.low) \ + and bi9.power < bi1.power and bi3.low >= bi1.low and bi7.high <= bi9.high: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAb式类一买') + + # aAbcd式类一买 + if min(bi2.high, bi4.high, bi6.high) > max(bi2.low, bi4.low, bi6.low) > bi8.high \ + and bi9.power < bi7.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAbcd式类一买') + + # ABC式类一买 + if bi3.low < bi1.low and bi7.high > bi9.high \ + and min(bi4.high, bi6.high) > max(bi4.low, bi6.low) \ + and (bi1.high - bi3.low) > (bi7.high - bi9.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='ABC式类一买') + + # 类趋势一买 + if bi8.high < bi6.low < bi6.high < bi4.low < bi4.high < bi2.low \ + and bi9.power < max([bi1.power, bi3.power, bi5.power, bi7.power]): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类趋势一买') + + # aAbBc式类一买(2~4构成中枢A,6~8构成中枢B,9背驰) + if max_high == max(bi1.high, bi3.high) and min_low == bi9.low \ + and min(bi2.high, bi4.high) > max(bi2.low, bi4.low) \ + and min(bi2.low, bi4.low) > max(bi6.high, bi8.high) \ + and min(bi6.high, bi8.high) > max(bi6.low, bi8.low) \ + and bi9.power < bi5.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAbBc式类一买') + + # 类三买(1357构成中枢,最低点在3或5) + if max_high == bi9.high > bi9.low \ + > max([x.high for x in [bi1, bi3, bi5, bi7]]) \ + > min([x.high for x in [bi1, bi3, bi5, bi7]]) \ + > max([x.low for x in [bi1, bi3, bi5, bi7]]) \ + > min([x.low for x in [bi3, bi5]]) == min_low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三买A') + + # 类三买(357构成中枢,8的力度小于2,9回调不跌破GG构成三买) + if bi8.power < bi2.power and max_high == bi9.high > bi9.low \ + > max([x.high for x in [bi3, bi5, bi7]]) \ + > min([x.high for x in [bi3, bi5, bi7]]) \ + > max([x.low for x in [bi3, bi5, bi7]]) > bi1.low == min_low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三买B') + + if min_low == bi5.low and max_high == bi1.high and bi4.high < bi2.low: # 前五笔构成向下类趋势 + zd = max([x.low for x in [bi5, bi7]]) + zg = min([x.high for x in [bi5, bi7]]) + gg = max([x.high for x in [bi5, bi7]]) + if zg > zd and bi8.high > gg: # 567构成中枢,且8的高点大于gg + if bi9.low > zg: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='ZG三买') + + # 类二买 + if bi9.high > gg > zg > bi9.low > zd: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类二买') + + if direction == Direction.Up: + if max_high == bi9.high and min_low == bi1.low: + # aAbBc式类一卖 + if bi6.low > min(bi2.high, bi4.high) > max(bi2.low, bi4.low) \ + and min(bi6.high, bi8.high) > max(bi6.low, bi8.low) \ + and max(bi2.high, bi4.high) < min(bi6.low, bi8.low) \ + and bi9.power < bi5.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAbBc式类一卖') + + # aAb式类一卖 + if min(bi2.high, bi4.high, bi6.high, bi8.high) > max(bi2.low, bi4.low, bi6.low, bi8.low) \ + and bi9.power < bi1.power and bi3.high <= bi1.high and bi7.low >= bi9.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAb式类一卖') + + # aAbcd式类一卖 + if bi8.low > min(bi2.high, bi4.high, bi6.high) > max(bi2.low, bi4.low, bi6.low) \ + and bi9.power < bi7.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAbcd式类一卖') + + # ABC式类一卖 + if bi3.high > bi1.high and bi7.low < bi9.low \ + and min(bi4.high, bi6.high) > max(bi4.low, bi6.low) \ + and (bi3.high - bi1.low) > (bi9.high - bi7.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='ABC式类一卖') + + # 类趋势一卖 + if bi8.low > bi6.high > bi6.low > bi4.high > bi4.low > bi2.high \ + and bi9.power < max([bi1.power, bi3.power, bi5.power, bi7.power]): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类趋势一卖') + + # 九笔三卖 + if max_high == bi1.high and min_low == bi9.low \ + and bi9.high < max([x.low for x in [bi3, bi5, bi7]]) < min([x.high for x in [bi3, bi5, bi7]]): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三卖A') + + if min_low == bi1.low and max_high == bi5.high and bi2.high < bi4.low: # 前五笔构成向上类趋势 + zd = max([x.low for x in [bi5, bi7]]) + zg = min([x.high for x in [bi5, bi7]]) + dd = min([x.low for x in [bi5, bi7]]) + if zg > zd and bi8.low < dd: # 567构成中枢,且8的低点小于dd + if bi9.high < zd: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='ZD三卖') + + # 类二卖 + if dd < zd <= bi9.high < zg: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类二卖') + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def cxt_eleven_bi_V230622(c: CZSC, **kwargs) -> OrderedDict: + """十一笔形态分类 + + 参数模板:"{freq}_D{di}十一笔_形态V230622" + + **信号逻辑:** + + 十一笔的形态分类 + **信号列表:** + + - Signal('60分钟_D1十一笔_形态V230622_类三买_任意_任意_0') + - Signal('60分钟_D1十一笔_形态V230622_A3B3C5式类一卖_任意_任意_0') + - Signal('60分钟_D1十一笔_形态V230622_类二买_任意_任意_0') + - Signal('60分钟_D1十一笔_形态V230622_A5B3C3式类一卖_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: + + - di: 倒数第几笔 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}十一笔_形态V230622".split('_') + v1 = "其他" + if len(c.bi_list) < di + 16 or len(c.bars_ubi) > 7: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bis = get_sub_elements(c.bi_list, di=di, n=11) + assert len(bis) == 11 and bis[0].direction == bis[2].direction == bis[4].direction, "笔的方向错误" + bi1, bi2, bi3, bi4, bi5, bi6, bi7, bi8, bi9, bi10, bi11 = bis + max_high = max([x.high for x in bis]) + min_low = min([x.low for x in bis]) + direction = bi11.direction + + if direction == Direction.Down: + if min_low == bi11.low and max_high == bi1.high: + # ABC式类一买,A5B3C3 + if bi5.low == min([x.low for x in [bi1, bi3, bi5]]) \ + and bi9.low > bi11.low and bi9.high > bi11.high \ + and bi8.high > bi6.low and bi1.high - bi5.low > bi9.high - bi11.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='A5B3C3式类一买') + + # ABC式类一买,A3B3C5 + if bi1.high > bi3.high and bi1.low > bi3.low \ + and bi7.high == max([x.high for x in [bi7, bi9, bi11]]) \ + and bi6.high > bi4.low and bi1.high - bi3.low > bi7.high - bi11.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='A3B3C5式类一买') + + # ABC式类一买,A3B5C3 + if bi1.low > bi3.low and min(bi4.high, bi6.high, bi8.high) > max(bi4.low, bi6.low, bi8.low) \ + and bi9.high > bi11.high and bi1.high - bi3.low > bi9.high - bi11.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='A3B5C3式类一买') + + # a1Ab式类一买,a1(1~7构成的类趋势) + if bi2.low > bi4.high > bi4.low > bi6.high > bi5.low > bi7.low and bi10.high > bi8.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='a1Ab式类一买') + + # 类二买(1~7构成盘整背驰,246构成下跌中枢,9/11构成上涨中枢,且上涨中枢GG大于下跌中枢ZG) + if bi7.power < bi1.power and min_low == bi7.low < max([x.low for x in [bi2, bi4, bi6]]) \ + < min([x.high for x in [bi2, bi4, bi6]]) < max([x.high for x in [bi9, bi11]]) < bi1.high == max_high \ + and bi11.low > min([x.low for x in [bi2, bi4, bi6]]) \ + and min([x.high for x in [bi9, bi11]]) > max([x.low for x in [bi9, bi11]]): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类二买') + + # 类二买(1~7为区间极值,9~11构成上涨中枢,上涨中枢GG大于4~6的最大值,上涨中枢DD大于4~6的最小值) + if max_high == bi1.high and min_low == bi7.low \ + and min(bi9.high, bi11.high) > max(bi9.low, bi11.low) \ + and max(bi11.high, bi9.high) > max(bi4.high, bi6.high) \ + and min(bi9.low, bi11.low) > min(bi4.low, bi6.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类二买') + + # 类三买(1~9构成大级别中枢,10离开,11回调不跌破GG) + gg = max([x.high for x in [bi1, bi2, bi3]]) + zg = min([x.high for x in [bi1, bi2, bi3]]) + zd = max([x.low for x in [bi1, bi2, bi3]]) + dd = min([x.low for x in [bi1, bi2, bi3]]) + if max_high == bi11.high and bi11.low > zg > zd \ + and gg > bi5.low and gg > bi7.low and gg > bi9.low \ + and dd < bi5.high and dd < bi7.high and dd < bi9.high: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三买') + + if direction == Direction.Up: + if max_high == bi11.high and min_low == bi1.low: + # ABC式类一卖,A5B3C3 + if bi5.high == max([bi1.high, bi3.high, bi5.high]) and bi9.low < bi11.low and bi9.high < bi11.high \ + and bi8.low < bi6.high and bi11.high - bi9.low < bi5.high - bi1.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='A5B3C3式类一卖') + + # ABC式类一卖,A3B3C5 + if bi7.low == min([bi11.low, bi9.low, bi7.low]) and bi1.high < bi3.high and bi1.low < bi3.low \ + and bi6.low < bi4.high and bi11.high - bi7.low < bi3.high - bi1.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='A3B3C5式类一卖') + + # ABC式类一卖,A3B5C3 + if bi1.high < bi3.high and min(bi4.high, bi6.high, bi8.high) > max(bi4.low, bi6.low, bi8.low) \ + and bi9.low < bi11.low and bi3.high - bi1.low > bi11.high - bi9.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='A3B5C3式类一卖') + + # 类二卖:1~9构成类趋势,11不创新高 + if max_high == bi9.high > bi8.low > bi6.high > bi6.low > bi4.high > bi4.low > bi2.high > bi1.low == min_low \ + and bi11.high < bi9.high: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类二卖') + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def cxt_range_oscillation_V230620(c: CZSC, **kwargs) -> OrderedDict: + """判断区间震荡 + + 参数模板:"{freq}_D{di}TH{th}_区间震荡V230620" + + **信号逻辑:** + + 1. 在区间震荡中,无论振幅大小,各笔的中心应改在相近的价格区间内平移,当各笔的中心的振幅大于一定数值时就认为这个窗口内没有固定区间的中枢震荡 + 2. 给定阈值 th,当各笔的中心的振幅大于 th 时,认为这个窗口内没有固定区间的中枢震荡 + + **信号列表:** + + - Signal('日线_D1TH5_区间震荡V230620_2笔震荡_向下_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_3笔震荡_向上_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_4笔震荡_向下_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_5笔震荡_向上_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_6笔震荡_向下_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_5笔震荡_向下_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_2笔震荡_向上_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_3笔震荡_向下_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_4笔震荡_向上_任意_0') + + :param c: CZSC对象 + :param kwargs: + + - di: 倒数第几笔 + - th: 振幅阈值,2 表示 2%,即 2% 以内的振幅都认为是震荡 + + :return: 信号识别结果 + """ + di = int(kwargs.get('di', 1)) + th = int(kwargs.get('th', 2)) # 振幅阈值,2 表示 2%,即 2% 以内的振幅都认为是震荡 + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}TH{th}_区间震荡V230620".split('_') + v1, v2 = '其他', '其他' + if len(c.bi_list) < di + 11: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) + + def __calculate_max_amplitude_percentage(prices): + """计算给定价位列表的最大振幅的百分比""" + if not prices: + return 100 + max_price, min_price = max(prices), min(prices) + return ((max_price - min_price) / min_price) * 100 if min_price != 0 else 100 + + _bis = get_sub_elements(c.bi_list, di=di, n=12) + + if len(_bis) != 12: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) + + price_list = [] + count = 1 + for bi in _bis[::-1]: + price_list.append((bi.high + bi.low) / 2) + if len(price_list) > 1: + if __calculate_max_amplitude_percentage(price_list) < th: + count += 1 + else: + break + + if count != 1: + v1 = f"{count}笔震荡" + v2 = "向上" if _bis[-1].direction == Direction.Up else "向下" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) diff --git a/czsc/signals/pos.py b/czsc/signals/pos.py index 381e467c8..4eeb7443f 100644 --- a/czsc/signals/pos.py +++ b/czsc/signals/pos.py @@ -238,3 +238,120 @@ def pos_holds_V230414(cat: CzscTrader, **kwargs) -> OrderedDict: v1 = '空头存疑' if zdf < m else '空头良好' return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def pos_fix_exit_V230624(cat: CzscTrader, **kwargs) -> OrderedDict: + """固定比例止损,止盈 + + 参数模板:"{pos_name}_固定{th}BP止盈止损_出场V230624" + + **信号逻辑:** + + 以多头为例,如果持有收益超过 th 个BP,则止盈;如果亏损超过 th 个BP,则止损。 + + **信号列表:** + + - Signal('日线三买多头_固定100BP止盈止损_出场V230624_多头止损_任意_任意_0') + - Signal('日线三买多头_固定100BP止盈止损_出场V230624_空头止损_任意_任意_0') + + :param cat: CzscTrader对象 + :param kwargs: 参数字典 + - pos_name: str,开仓信号的名称 + - freq1: str,给定的K线周期 + - n: int,向前找的K线个数,默认为 3 + :return: + """ + pos_name = kwargs["pos_name"] + th = int(kwargs.get('th', 300)) + k1, k2, k3 = f"{pos_name}_固定{th}BP止盈止损_出场V230624".split("_") + v1 = '其他' + if not hasattr(cat, "positions"): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + pos_ = [x for x in cat.positions if x.name == pos_name][0] + if len(pos_.operates) == 0 or pos_.operates[-1]['op'] in [Operate.SE, Operate.LE]: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + op = pos_.operates[-1] + op_price = op['price'] + + if op['op'] == Operate.LO: + if cat.latest_price < op_price * (1 - th / 10000): + v1 = '多头止损' + if cat.latest_price > op_price * (1 + th / 10000): + v1 = '多头止盈' + + if op['op'] == Operate.SO: + if cat.latest_price > op_price * (1 + th / 10000): + v1 = '空头止损' + if cat.latest_price < op_price * (1 - th / 10000): + v1 = '空头止盈' + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def pos_profit_loss_V230624(cat: CzscTrader, **kwargs) -> OrderedDict: + """开仓后盈亏比达到一定比值,才允许平仓 贡献者:谌意勇 + + 参数模板:"{pos_name}_{freq1}YKB{ykb}N{n}_盈亏比判断V230624" + + **信号逻辑:** + + 1. 通过公式 计算盈亏比=abs(现价-开仓价)/abs(开仓价-止损价)* 10,当比值大于一定阀值时才允许平仓 + + **信号列表:** + + - Signal('日线通道突破_60分钟YKB20N3_盈亏比判断V230624_空头止损_任意_任意_0') + - Signal('日线通道突破_60分钟YKB20N3_盈亏比判断V230624_多头止损_任意_任意_0') + - Signal('日线通道突破_60分钟YKB20N3_盈亏比判断V230624_多头达标_任意_任意_0') + - Signal('日线通道突破_60分钟YKB20N3_盈亏比判断V230624_空头达标_任意_任意_0') + + :param cat: CzscTrader对象 + :param kwargs: 参数字典 + + - pos_name: str,开仓信号的名称 + - freq1: str,给定的K线周期 + - ykb: int,默认为 20, 表示2倍盈亏比,计算盈亏比=abs(现价-开仓价)/abs(开仓价-止损价) + - n: int 默认为3 止损取最近n个分型的最低点或最高点 + + :return: + """ + pos_name = kwargs["pos_name"] + freq1 = kwargs["freq1"] + ykb = int(kwargs.get('ykb', 20)) + n = int(kwargs.get('n', 3)) + k1, k2, k3 = f"{pos_name}_{freq1}YKB{ykb}N{n}_盈亏比判断V230624".split("_") + v1 = '其他' + # 如果没有持仓策略,则不产生信号 + if not hasattr(cat, "positions"): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + pos = [x for x in cat.positions if x.name == pos_name][0] + if len(pos.operates) == 0 or pos.operates[-1]['op'] in [Operate.SE, Operate.LE]: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + c = cat.kas[freq1] + op = pos.operates[-1] + last_close = c.bars_raw[-1].close + + if op['op'] == Operate.LO: + fxs = [x for x in c.fx_list if x.mark == Mark.D and x.dt < op['dt']][-n:] + stop_price = min([x.low for x in fxs]) + ykb_ = ((last_close - op['price']) / (op['price'] - stop_price)) * 10 + if ykb_ > ykb: + v1 = '多头达标' + else: + if last_close < stop_price: + v1 = '多头止损' + + if op['op'] == Operate.SO: + fxs = [x for x in c.fx_list if x.mark == Mark.G and x.dt < op['dt']][-n:] + stop_price = max([x.high for x in fxs]) + ykb_ = ((last_close - op['price']) / (op['price'] - stop_price)) * 10 + if ykb_ > ykb: + v1 = '空头达标' + else: + if last_close > stop_price: + v1 = '空头止损' + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) diff --git a/czsc/signals/tas.py b/czsc/signals/tas.py index 0f4869016..3dab76730 100644 --- a/czsc/signals/tas.py +++ b/czsc/signals/tas.py @@ -15,11 +15,12 @@ logger.warning(f"ta-lib 没有正确安装,相关信号函数无法正常执行。" f"请参考安装教程 https://blog.csdn.net/qaz2134560/article/details/98484091") import numpy as np +from collections import OrderedDict from deprecated import deprecated from czsc.analyze import CZSC from czsc.objects import Signal, Direction, BI, RawBar, FX from czsc.utils import get_sub_elements, fast_slow_cross, count_last_same, create_single_signal -from collections import OrderedDict +from czsc.utils.sig import cross_zero_axis, cal_cross_num, down_cross_count def update_ma_cache(c: CZSC, **kwargs): @@ -1407,40 +1408,6 @@ def tas_rsi_base_V230227(c: CZSC, **kwargs) -> OrderedDict: return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) -# def tas_double_rsi_V221203(c: CZSC, **kwargs) -> OrderedDict: -# """两个周期的RSI多空信号 -# -# 参数模板:"{freq}_D{di}K#RSI{rsi_seq[0]}#{rsi_seq[1]}_RSI多空V221203" -# -# **信号逻辑:** -# -# 1. rsi1 > rsi2,多头;反之,空头 -# -# **信号列表:** -# -# - Signal('15分钟_D1K#RSI5#10_RSI多空V221203_空头_任意_任意_0') -# - Signal('15分钟_D1K#RSI5#10_RSI多空V221203_多头_任意_任意_0') -# -# :param c: CZSC对象 -# :param di: 信号计算截止倒数第i根K线 -# :param di: 信号计算截止倒数第i根K线 -# :param rsi_seq: 指定短期RSI, 长期RSI 参数 -# :return: 信号识别结果 -# """ -# di = int(kwargs.get('di', 1)) -# rsi_seq = kwargs.get('rsi_seq', (5, 10)) -# assert len(rsi_seq) == 2 and rsi_seq[1] > rsi_seq[0] -# rsi1 = update_rsi_cache(c, timeperiod=rsi_seq[0]) -# rsi2 = update_rsi_cache(c, timeperiod=rsi_seq[1]) -# -# k1, k2, k3 = f"{c.freq.value}_D{di}K#RSI{rsi_seq[0]}#{rsi_seq[1]}_RSI多空V221203".split('_') -# bars = get_sub_elements(c.bars_raw, di=di, n=3) -# rsi1v = bars[-1].cache[rsi1] -# rsi2v = bars[-1].cache[rsi2] -# v1 = "多头" if rsi1v >= rsi2v else "空头" -# return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) - - def tas_first_bs_V230217(c: CZSC, **kwargs) -> OrderedDict: """均线结合K线形态的一买一卖辅助判断 @@ -2057,7 +2024,7 @@ def update_atr_cache(c: CZSC, **kwargs): def tas_atr_break_V230424(c: CZSC, **kwargs): """ATR突破 - 参数模板:"{freq}_D{di}通道突破#{N}#{K1}#{K2}_BS辅助V230403" + 参数模板:"{freq}_D{di}ATR{timeperiod}T{th}突破_BS辅助V230424" **信号逻辑:** @@ -2226,3 +2193,481 @@ def tas_ma_system_V230513(c: CZSC, **kwargs) -> OrderedDict: v1 = '空头排列' return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def tas_macd_bs1_V230411(c: CZSC, **kwargs) -> OrderedDict: + """基于MACD DIF的笔背驰判断信号 + + 参数模板:"{freq}_D{di}T{tha}#{thb}#{thc}_BS1辅助V230411" + + **信号逻辑:** + + tha, thb, thc 分别为阈值,取值范围为 0 ~ 10000 + + 取5笔,从远到近分别记为 1、2、3、4、5,如果满足以下条件,则判断为顶背驰,反之为底背弛: + + 1. 5向上,1~3的累计涨幅超过阈值tha,且3顶部的dif值大于1顶部dif值; + 2. 5的顶部相比于3的顶部的涨幅超过阈值-thb,且相应dif值的变化率小于阈值-thc; + + **信号列表:** + + - Signal('15分钟_D1T100#10#30_BS1辅助V230411_底背驰_任意_任意_0') + - Signal('15分钟_D1T100#10#30_BS1辅助V230411_顶背驰_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + + - di: 信号计算截止倒数第i根K线 + - tha: 前三笔的累计涨跌超过阈值tha,单位:BP,表示万分之一 + - thb: 第3笔相比第1笔的顶的涨跌幅阈值thb,单位:BP,表示万分之一 + - thc: 第5笔相比第3笔的DIF值的变化率阈值thc,单位:BP,表示万分之一 + + :return: 返回信号结果 + """ + di = int(kwargs.get("di", 1)) + tha = int(kwargs.get("tha", 30)) + thb = int(kwargs.get("thb", 5)) + thc = int(kwargs.get("thc", 30)) + freq = c.freq.value + assert 0 < tha < 10000, "tha 必须在 0 到 10000 之间" + assert 0 < thb < 10000, "thb 必须在 0 到 10000 之间" + assert 0 < thc < 10000, "thc 必须在 0 到 10000 之间" + + cache_key = update_macd_cache(c, fastperiod=12, slowperiod=26, signalperiod=9) + k1, k2, k3 = f"{freq}_D{di}T{tha}#{thb}#{thc}_BS1辅助V230411".split('_') + v1 = "其他" + if len(c.bi_list) <= di + 7 or len(c.bars_ubi) > 9: + # 笔数不够,或者当下未完成笔已经延伸超过9根K线,不计算 + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bi1, bi2, bi3, bi4, bi5 = get_sub_elements(c.bi_list, di=di, n=5) + # 第一个bar要有macd结果 + if np.isnan(bi1.raw_bars[0].cache[cache_key]['dif']): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + if bi5.direction == Direction.Up: + bi1_dif = max([x.cache[cache_key]['dif'] for x in bi1.fx_b.raw_bars]) + bi3_dif = max([x.cache[cache_key]['dif'] for x in bi3.fx_b.raw_bars]) + bi5_dif = max([x.cache[cache_key]['dif'] for x in bi5.fx_b.raw_bars]) + + # 前三笔累计涨幅超过阈值tha + cond1 = ((bi3.high - bi1.low) / bi1.low) * 10000 > tha + + # 第二个向上笔的顶所对应的DIF值,要高于第一个向上笔的顶所对应的DIF值 + cond2 = bi3_dif > bi1_dif + + # 第三个向上笔的顶相比第二个向上笔的顶的涨幅超过阈值thb + cond3 = ((bi5.high - bi3.high) / bi3.high) * 10000 > -thb + + # 第三个向上笔的顶相比第二个向上笔的顶的DIF值的变化率小于阈值thc + cond4 = ((bi5_dif - bi3_dif) / bi3_dif) * 10000 < -thc + + if cond1 and cond2 and cond3 and cond4: + v1 = "顶背驰" + + elif bi5.direction == Direction.Down: + bi1_dif = min([x.cache[cache_key]['dif'] for x in bi1.fx_b.raw_bars]) + bi3_dif = min([x.cache[cache_key]['dif'] for x in bi3.fx_b.raw_bars]) + bi5_dif = min([x.cache[cache_key]['dif'] for x in bi5.fx_b.raw_bars]) + + # 前三笔累计跌幅超过阈值tha + cond1 = ((bi3.low - bi1.high) / bi1.high) * 10000 < -tha + + # 第二个向下笔的底所对应的DIF值,要小于第一个向下笔的底所对应的DIF值 + cond2 = bi3_dif < bi1_dif + + # 第三个向下笔的底相比第二个向下笔的底的跌幅小于thb + cond3 = ((bi5.low - bi3.low) / bi3.low) * 10000 < thb + + # 第三个向下笔的底相比第二个向下笔的底的DIF值的变化率大于thc + cond4 = ((bi5_dif - bi3_dif) / bi3_dif) * 10000 > thc + + if cond1 and cond2 and cond3 and cond4: + v1 = "底背驰" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def tas_macd_bs1_V230412(c: CZSC, **kwargs) -> OrderedDict: + """基于MACD DIF的笔背驰判断信号 + + 参数模板:"{freq}_D{di}T{tha}#{thb}_BS1辅助V230412" + + **信号逻辑:** + + 取5笔,从远到近分别记为 1、2、3、4、5,如果满足以下条件,则判断为顶背驰,反之为底背弛: + + 1. 5向上,1~3的累计涨幅超过阈值tha,且3顶部的dif值大于1顶部dif值; + 2. 5的顶部相比于3的顶部的涨幅超过阈值thb,且5顶部的dif值小于3顶部dif值; + + **信号列表:** + + - Signal('15分钟_D1T100#10_BS1辅助V230412_底背驰_任意_任意_0') + - Signal('15分钟_D1T100#10_BS1辅助V230412_顶背驰_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + + - di: 信号计算截止倒数第i根K线 + - tha: 前三笔的累计涨跌超过阈值tha + - thb: 第三笔相比第二个向上笔的顶的涨幅超过阈值thb + + :return: + """ + di = int(kwargs.get("di", 1)) + tha = int(kwargs.get("tha", 100)) + thb = int(kwargs.get("thb", 10)) + freq = c.freq.value + + cache_key = update_macd_cache(c, fastperiod=12, slowperiod=26, signalperiod=9) + k1, k2, k3 = f"{freq}_D{di}T{tha}#{thb}_BS1辅助V230412".split('_') + v1 = "其他" + if len(c.bi_list) <= di + 7 or len(c.bars_ubi) > 9: + # 笔数不够,或者当下未完成笔已经延伸超过9根K线,不计算 + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bi1, bi2, bi3, bi4, bi5 = get_sub_elements(c.bi_list, di=di, n=5) + # 第一个bar要有macd结果 + if np.isnan(bi1.raw_bars[0].cache[cache_key]['dif']): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + if bi5.direction == Direction.Up: + bi1_dif = max([x.cache[cache_key]['dif'] for x in bi1.fx_b.raw_bars]) + bi3_dif = max([x.cache[cache_key]['dif'] for x in bi3.fx_b.raw_bars]) + bi5_dif = max([x.cache[cache_key]['dif'] for x in bi5.fx_b.raw_bars]) + + # 前三笔累计涨幅超过阈值tha + cond1 = ((bi3.high - bi1.low) / bi1.low) * 10000 > tha + + # 5、3、1笔的dif值,3的最大 + cond2 = bi5_dif < bi3_dif > bi1_dif + + # 第三个向上笔的顶相比第二个向上笔的顶的涨幅超过阈值thb + cond3 = ((bi5.high - bi3.high) / bi3.high) * 10000 > -thb + + if cond1 and cond2 and cond3: + v1 = "顶背驰" + + elif bi5.direction == Direction.Down: + bi1_dif = min([x.cache[cache_key]['dif'] for x in bi1.fx_b.raw_bars]) + bi3_dif = min([x.cache[cache_key]['dif'] for x in bi3.fx_b.raw_bars]) + bi5_dif = min([x.cache[cache_key]['dif'] for x in bi5.fx_b.raw_bars]) + + # 前三笔累计跌幅超过阈值tha + cond1 = ((bi3.low - bi1.high) / bi1.high) * 10000 < -tha + + # 5、3、1笔的dif值,3的最小 + cond2 = bi5_dif > bi3_dif < bi1_dif + + # 第三个向下笔的底相比第二个向下笔的底的跌幅超过阈值-thb + cond3 = ((bi5.low - bi3.low) / bi3.low) * 10000 < thb + + if cond1 and cond2 and cond3: + v1 = "底背驰" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def tas_accelerate_V230531(c: CZSC, **kwargs) -> OrderedDict: + """BOLL辅助判断加速行情 + + 参数模板:"{freq}_D{di}N{n}_BOLL加速V230531" + + **信号逻辑:** + + 最近N根K线全部在中轨上方,上轨累计涨幅大于中轨2倍以上,多头加速,反之空头加速。 + + **信号列表:** + + - Signal('60分钟_D1N20T20_BOLL加速V230531_空头加速_未破下轨_任意_0') + - Signal('60分钟_D1N20T20_BOLL加速V230531_空头加速_跌破下轨_任意_0') + - Signal('60分钟_D1N20T20_BOLL加速V230531_多头加速_升破上轨_任意_0') + - Signal('60分钟_D1N20T20_BOLL加速V230531_多头加速_未破上轨_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + + - di: 倒数第几根K线 + - n: 取截止dik的前n根K线 + - t: 阈值,上下轨变化率超过中轨变化率的 t / 10 倍,默认20,即上下轨变化率超过中轨变化率的2倍 + + :return: 返回信号结果 + """ + di = int(kwargs.get('di', 1)) + n = int(kwargs.get('n', 20)) + t = int(kwargs.get('t', 20)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}N{n}T{t}_BOLL加速V230531".split('_') + v1, v2 = '其他', '其他' + cache_key = update_boll_cache_V230228(c, timeperiod=20, nbdev=20) + + if len(c.bars_raw) < 40: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bars = get_sub_elements(c.bars_raw, di=di, n=n) + mid_zdf = bars[-1].cache[cache_key]['中线'] / bars[0].cache[cache_key]['中线'] - 1 + up_zdf = bars[-1].cache[cache_key]['上轨'] / bars[0].cache[cache_key]['上轨'] - 1 + down_zdf = bars[-1].cache[cache_key]['下轨'] / bars[0].cache[cache_key]['下轨'] - 1 + last_bar = bars[-1] + + if all([x.close > x.cache[cache_key]['中线'] for x in bars]) and up_zdf > t / 10 * mid_zdf > 0: + v1 = '多头加速' + v2 = '升破上轨' if last_bar.cache[cache_key]['上轨'] < last_bar.high else '未破上轨' + + if all([x.close < x.cache[cache_key]['中线'] for x in bars]) and down_zdf < t / 10 * mid_zdf < 0: + v1 = '空头加速' + v2 = '跌破下轨' if last_bar.cache[cache_key]['下轨'] > last_bar.low else '未破下轨' + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) + + +def tas_ma_cohere_V230512(c: CZSC, **kwargs) -> OrderedDict: + """均线系统粘合/扩散状态 + + 参数模板:"{freq}_D{di}SMA{ma_seq}_均线系统V230512" + + **信号逻辑:** + + 1. 计算最近100根K线中,均线系统最大值与最小值的差异;取前80根K线,计算其标准差; + 1. 粘合:最近20根K线中,均线系统最大值与最小值的差异小于0.5倍标准差的数量超过16根 + 2. 扩散:最近20根K线中,均线系统最大值与最小值的差异大于1.0倍标准差的数量超过16根 + + **信号列表:** + + - Signal('60分钟_D1SMA5#13#21#34#55_均线系统V230512_扩散_任意_任意_0') + - Signal('60分钟_D1SMA5#13#21#34#55_均线系统V230512_粘合_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + :return: 返回信号结果 + """ + di = int(kwargs.get('di', 1)) + ma_seq = kwargs.get('ma_seq', "5#13#21#34#55") # 均线系统参数,以#分隔 + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}SMA{ma_seq}_均线系统V230512".split('_') + v1 = '其他' + ma_seq = [int(x) for x in ma_seq.split('#')] + for ma in ma_seq: + update_ma_cache(c, ma_type="SMA", timeperiod=ma) + + if len(c.bars_raw) < max(ma_seq) + di + 10: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + ret_seq = [] + for bar in get_sub_elements(c.bars_raw, di=di, n=100): + ma_val = [bar.cache[f'SMA#{x}'] for x in ma_seq] + ret_seq.append(max(ma_val) / min(ma_val) - 1) + + ret_std = np.std(ret_seq[:-20]) + if sum([1 if ret < 0.5 * ret_std else 0 for ret in ret_seq[-20:]]) >= 16: + v1 = "粘合" + + if sum([1 if ret > 1.0 * ret_std else 0 for ret in ret_seq[-20:]]) >= 16: + v1 = "扩散" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def tas_cross_status_V230619(c: CZSC, **kwargs) -> OrderedDict: + """0轴上下金死叉次数计算信号函数 贡献者:谌意勇 + + 参数模板:"{freq}_D{di}MACD{fastperiod}#{slowperiod}#{signalperiod}_金死叉V230619" + + **信号逻辑:** + + 精确确立MACD指标中0轴以上或以下位置第几次金叉和死叉,作为开仓的辅助买点: + + **信号列表:** + + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴上死叉第2次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下金叉第1次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下死叉第1次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下金叉第2次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下死叉第2次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下金叉第3次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴上死叉第1次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴上金叉第1次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下死叉第3次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下金叉第4次_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + + - :param di: 信号计算截止倒数第i根K线 + - :param fastperiod: MACD快线周期 + - :param slowperiod: MACD慢线周期 + - :param signalperiod: MACD信号线周期 + + :return: 信号识别结果 + """ + di = int(kwargs.get('di', 1)) + freq = c.freq.value + fastperiod = int(kwargs.get('fastperiod', 12)) + slowperiod = int(kwargs.get('slowperiod', 26)) + signalperiod = int(kwargs.get('signalperiod', 9)) + cache_key = update_macd_cache(c, **kwargs) + s = OrderedDict() + bars = get_sub_elements(c.bars_raw, di=di, n=100) + k1, k2, k3 = f"{freq}_D{di}MACD{fastperiod}#{slowperiod}#{signalperiod}_金死叉V230619".split('_') + v1 = "其他" + if len(bars)>=100: + dif = [x.cache[cache_key]['dif'] for x in bars] + dea = [x.cache[cache_key]['dea'] for x in bars] + + num_k = cross_zero_axis(dif, dea) # type: ignore + dif_temp = get_sub_elements(dif, di=di, n=num_k) + dea_temp = get_sub_elements(dea, di=di, n=num_k) + + if dif[-1] < 0 and dea[-1] < 0: + down_num_sc = down_cross_count(dif_temp, dea_temp) + down_num_jc = down_cross_count(dea_temp, dif_temp) + if dif[-1] > dea[-1] and dif[-2] < dea[-2]: + v1 = f'0轴下金叉第{down_num_jc}次' + elif dif[-1] < dea[-1] and dif[-2] > dea[-2]: + v1 = f'0轴下死叉第{down_num_sc}次' + + + elif dif[-1] > 0 and dea[-1] > 0: + up_num_sc = down_cross_count(dif_temp, dea_temp) + up_num_jc = down_cross_count(dea_temp, dif_temp) + if dif[-1] > dea[-1] and dif[-2] < dea[-2]: + v1 = f'0轴上金叉第{up_num_jc}次' + elif dif[-1] < dea[-1] and dif[-2] > dea[-2]: + v1 = f'0轴上死叉第{up_num_sc}次' + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def tas_cross_status_V230624(c: CZSC, **kwargs) -> OrderedDict: + """指定金死叉数值信号函数,以此来确定MACD交易区间 贡献者:谌意勇 + + 参数模板:"{freq}_D{di}N{n}MD{md}_MACD交叉数量V230624" + + **信号逻辑:** + + 1、通过指定0轴上下金死叉数量,来选择自己想要的指标形态,通过配合其他信号函数出信号 + 2、金叉数量和死叉数量要注意连续对应。0轴上一定是第一次先死叉,再金叉,死叉的数值同 + 金叉数值相比永远是相等或者大1,不能出现>=2的情况,0轴下则反之。 + + **信号列表:** + + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴上金叉第1次_0轴上死叉第1次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴上金叉第1次_0轴上死叉第2次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第0次_0轴下死叉第0次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第1次_0轴下死叉第0次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第1次_0轴下死叉第1次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第2次_0轴下死叉第1次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第2次_0轴下死叉第2次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第3次_0轴下死叉第2次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴上金叉第0次_0轴上死叉第0次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴上金叉第0次_0轴上死叉第1次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第3次_0轴下死叉第3次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第4次_0轴下死叉第3次_任意_0') + + :param c: czsc对象 + :param kwargs: + + - di: 倒数第i根K线 + - n: 从dik往前数n根k线(此数值不需要精确,函数会自动截取最后上下0轴以后的数据) + - md: 抖动过滤参数,金死叉之间格距离小于此数值,将被忽略(去除一些杂波扰动因素,最小值不小于1) + 0轴上下金死叉状态信息,与其他信号加以辅助操作。 + + :return: 信号字典 + """ + di = int(kwargs.get('di', 1)) + n = int(kwargs.get('n', 100)) + md = int(kwargs.get('md', 1)) # md 是 min distance 的缩写,表示金死叉之间格距离小于此数值,将被忽略(去除一些杂波扰动因素,最小值不小于1) + assert md >= 1, "md必须大于等于1" + freq = c.freq.value + cache_key = update_macd_cache(c, **kwargs) + + k1, k2, k3 = f"{freq}_D{di}N{n}MD{md}_MACD交叉数量V230624".split('_') + v1 = "其他" + v2 = "其他" + if len(c.bars_raw) < n + 1: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bars = get_sub_elements(c.bars_raw, di=di, n=n) + dif = [x.cache[cache_key]['dif'] for x in bars] + dea = [x.cache[cache_key]['dea'] for x in bars] + num_k = cross_zero_axis(dif, dea) + dif_temp = get_sub_elements(dif, di=1, n=num_k) + dea_temp = get_sub_elements(dea, di=1, n=num_k) + cross = fast_slow_cross(dif_temp, dea_temp) + + jc, sc = cal_cross_num(cross, md) + + if dif[-1] < 0 and dea[-1] < 0: + v1 = f'0轴下金叉第{jc}次' + v2 = f'0轴下死叉第{sc}次' + + elif dif[-1] > 0 and dea[-1] > 0: + v1 = f'0轴上金叉第{jc}次' + v2 = f'0轴上死叉第{sc}次' + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) + + +def tas_cross_status_V230625(c: CZSC, **kwargs) -> OrderedDict: + """指定金死叉数值信号函数, 以此来确定MACD交易区间 贡献者:谌意勇 + + 参数模板:"{freq}_D{di}N{n}MD{md}J{j}S{s}_MACD交叉数量V230625" + + **信号逻辑:** + + 1、通过指定jc或者sc数值来确定为哪第几次金叉或死叉之后的信号。两者最少要指定一个,并且指定其中一个时,另外一个需为0. + + **信号列表:** + + - Signal('15分钟_D1N100MD1J3S0_MACD交叉数量V230625_0轴下第3次金叉以后_任意_任意_0') + - Signal('15分钟_D1N100MD1J3S0_MACD交叉数量V230625_0轴上第3次金叉以后_任意_任意_0') + + :param c: czsc对象 + :param kwargs: + + - di: 倒数第i根K线 + - j: 金叉数值 + - s: 死叉数值 + - n: 从dik往前数n根k线(此数值不需要精确,函数会自动截取最后上下0轴以后的数据) + - md: 抖动过滤参数,金死叉之间格距离小于此数值,将被忽略(去除一些杂波扰动因素,最小值不小于1 + 0轴上下金死叉状态信息,与其他信号加以辅助操作。 + + :return: 信号字典 + """ + di = int(kwargs.get('di', 1)) + j = int(kwargs.get('j', 0)) + s = int(kwargs.get('s', 0)) + n = int(kwargs.get('n', 100)) + md = int(kwargs.get('md', 1)) + freq = c.freq.value + cache_key = update_macd_cache(c, **kwargs) + assert j * s == 0, "金叉死叉参数错误, j和s必须有一个为0" + + k1, k2, k3 = f"{freq}_D{di}N{n}MD{md}J{j}S{s}_MACD交叉数量V230625".split('_') + v1 = "其他" + if len(c.bars_raw) < di + n + 1: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bars = get_sub_elements(c.bars_raw, di=di, n=n) + dif = [x.cache[cache_key]['dif'] for x in bars] + dea = [x.cache[cache_key]['dea'] for x in bars] + num_k = cross_zero_axis(dif, dea) + dif_temp = get_sub_elements(dif, di=1, n=num_k) + dea_temp = get_sub_elements(dea, di=1, n=num_k) + cross = fast_slow_cross(dif_temp, dea_temp) + + jc, sc = cal_cross_num(cross, md) + + if dif[-1] < 0 and dea[-1] < 0: + if jc >= j and s == 0: + v1 = f'0轴下第{j}次金叉以后' + elif j == 0 and sc >= s: + v1 = f'0轴下第{s}次死叉以后' + + elif dif[-1] > 0 and dea[-1] > 0: + if jc >= j and s == 0: + v1 = f'0轴上第{j}次金叉以后' + elif j == 0 and sc >= s: + v1 = f'0轴上第{s}次死叉以后' + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) diff --git a/czsc/strategies.py b/czsc/strategies.py index 824b5236b..976c16ab5 100644 --- a/czsc/strategies.py +++ b/czsc/strategies.py @@ -823,11 +823,132 @@ def create_cci_short(symbol, is_stocks=False, **kwargs) -> Position: ] opens[0]["signals_not"].extend(zdt_sigs) exits[0]["signals_not"].extend(zdt_sigs) - pos_name = f"A股{freq}CCI多头" + pos_name = f"A股{freq}CCI空头" else: # 非A股:都行 opens[0]["signals_all"].append(f"{base_freq}_D1_涨跌停V230331_任意_任意_任意_0") - pos_name = f"{freq}CCI多头" + pos_name = f"{freq}CCI空头" + + pos = Position( + name=f"{pos_name}T0" if T0 else f"{pos_name}", + symbol=symbol, + opens=[Event.load(x) for x in opens], + exits=[Event.load(x) for x in exits], + interval=interval, + timeout=timeout, + stop_loss=stop_loss, + T0=T0, + ) + return pos + + +def create_emv_long(symbol, is_stocks=False, **kwargs) -> Position: + """EMV 多头策略 + + :param symbol: 标的代码 + :param is_stocks: 是否是 A 股 + :param kwargs: 其他参数 + + - base_freq: 基础级别 + - freq: 信号级别 + - T0: 是否是 T0 策略 + + :return: + """ + freq = kwargs.get("freq", "15分钟") + base_freq = kwargs.get("base_freq", freq) + di = int(kwargs.get("di", 1)) + T0 = kwargs.get("T0", False) + timeout = int(kwargs.get("timeout", 100)) + stop_loss = int(kwargs.get("stop_loss", 300)) + interval = int(kwargs.get("interval", 3600 * 2)) # 同向开仓时间间隔,单位:秒;默认 2 小时,一般不用修改 + + opens = [ + { + "operate": "开多", + "signals_not": [], + "factors": [{"name": "EMV多头", "signals_all": [f"{freq}_D{di}_EMV简易波动V230605_看多_任意_任意_0"]}], + } + ] + + exits = [ + { + "operate": "平多", + "signals_not": [], + "factors": [{"name": f"EMV空头", "signals_all": [f"{freq}_D{di}_EMV简易波动V230605_看空_任意_任意_0"]}], + } + ] + if is_stocks: + # A股空头:涨跌停不交易 + zdt_sigs = [f"{base_freq}_D1_涨跌停V230331_跌停_任意_任意_0", f"{base_freq}_D1_涨跌停V230331_涨停_任意_任意_0"] + opens[0]["signals_not"].extend(zdt_sigs) + exits[0]["signals_not"].extend(zdt_sigs) + pos_name = f"A股{freq}EMV多头" + else: + # 非A股多头:都行 + opens[0]["signals_all"].append(f"{base_freq}_D1_涨跌停V230331_任意_任意_任意_0") + pos_name = f"{freq}EMV多头" + + pos = Position( + name=f"{pos_name}T0" if T0 else f"{pos_name}", + symbol=symbol, + opens=[Event.load(x) for x in opens], + exits=[Event.load(x) for x in exits], + interval=interval, + timeout=timeout, + stop_loss=stop_loss, + T0=T0, + ) + return pos + + +def create_emv_short(symbol, is_stocks=False, **kwargs) -> Position: + """EMV 空头策略 + + :param symbol: 标的代码 + :param is_stocks: 是否是 A 股 + :param kwargs: 其他参数 + + - base_freq: 基础级别 + - freq: 信号级别 + - T0: 是否是 T0 策略 + + :return: + """ + freq = kwargs.get("freq", "15分钟") + base_freq = kwargs.get("base_freq", freq) + di = int(kwargs.get("di", 1)) + T0 = kwargs.get("T0", False) + timeout = int(kwargs.get("timeout", 100)) + stop_loss = int(kwargs.get("stop_loss", 300)) + interval = int(kwargs.get("interval", 3600 * 2)) # 同向开仓时间间隔,单位:秒;默认 2 小时,一般不用修改 + + opens = [ + { + "operate": "开空", + "signals_not": [], + "factors": [{"name": f"EMV空头", "signals_all": [f"{freq}_D{di}_EMV简易波动V230605_看空_任意_任意_0"]}], + } + + ] + + exits = [ + { + "operate": "平空", + "signals_not": [], + "factors": [{"name": "EMV多头", "signals_all": [f"{freq}_D{di}_EMV简易波动V230605_看多_任意_任意_0"]}], + } + ] + if is_stocks: + # A股:涨跌停不交易 + zdt_sigs = [f"{base_freq}_D1_涨跌停V230331_跌停_任意_任意_0", f"{base_freq}_D1_涨跌停V230331_涨停_任意_任意_0"] + opens[0]["signals_not"].extend(zdt_sigs) + exits[0]["signals_not"].extend(zdt_sigs) + pos_name = f"A股{freq}EMV空头" + else: + # 非A股:都行 + opens[0]["signals_all"].append(f"{base_freq}_D1_涨跌停V230331_任意_任意_任意_0") + pos_name = f"{freq}EMV空头" pos = Position( name=f"{pos_name}T0" if T0 else f"{pos_name}", diff --git a/czsc/utils/oss.py b/czsc/utils/oss.py index 301395a45..6dc4b2e73 100644 --- a/czsc/utils/oss.py +++ b/czsc/utils/oss.py @@ -7,7 +7,7 @@ """ import os from loguru import logger - +from tqdm import tqdm try: import oss2 from oss2.models import PartInfo @@ -52,8 +52,10 @@ def upload(self, filepath: str, oss_key: str, replace: bool = False) -> bool: with open(filepath, "rb") as file: result = self.bucket.put_object(oss_key, file) if result.status == 200: + logger.info(f"Upload {filepath} to {oss_key} successfully.") return True else: + logger.error(f"Upload {filepath} to {oss_key} failed: {result.status}, {result.request_id}") return False def download(self, oss_key: str, filepath: str) -> bool: @@ -85,8 +87,10 @@ def delete_file(self, oss_key: str) -> bool: """ result = self.bucket.delete_object(oss_key) if result.status == 204: + logger.info(f"Delete {oss_key} successfully.") return True else: + logger.error(f"Delete {oss_key} failed: {result.status}, {result.request_id}") return False def file_exists(self, oss_key: str) -> bool: @@ -125,7 +129,7 @@ def list_files(self, prefix="", extensions=None): :return: list, 列举的文件的名称列表。 """ oss_keys = [] - for obj in oss2.ObjectIterator(self.bucket, prefix=prefix): + for obj in tqdm(oss2.ObjectIterator(self.bucket, prefix=prefix), desc=f"List files of {prefix}"): if obj.key.endswith("/"): continue if extensions and not any(obj.key.endswith(ext) for ext in extensions): @@ -143,7 +147,7 @@ def batch_upload(self, filepaths: List[str], oss_keys: List[str], replace: bool :param threads: int, 并行上传的线程数。默认为5。 """ with ThreadPoolExecutor(max_workers=threads) as executor: - for filepath, oss_key in zip(filepaths, oss_keys): + for filepath, oss_key in tqdm(zip(filepaths, oss_keys), total=len(filepaths), desc="Uploading"): executor.submit(self.upload, filepath, oss_key, replace) def batch_download(self, oss_keys: List[str], local_paths: List[str], threads: int = 5): @@ -219,4 +223,6 @@ def upload_folder(self, local_folder: str, oss_folder: str, replace: bool = Fals oss_key = os.path.join(oss_folder, relative_path) oss_keys.append(oss_key) + logger.info(f"Uploading {local_folder} to {oss_folder}, {len(filepaths)} files in total.") self.batch_upload(filepaths, oss_keys, replace, threads) + logger.info(f"Upload {local_folder} finished.") diff --git a/czsc/utils/sig.py b/czsc/utils/sig.py index 2f640cf50..2bfab226f 100644 --- a/czsc/utils/sig.py +++ b/czsc/utils/sig.py @@ -322,3 +322,80 @@ def get_zs_seq(bis: List[BI]) -> List[ZS]: zs.bis.append(bi) zs_list[-1] = zs return zs_list + + +def cross_zero_axis(n1: Union[List, np.ndarray], n2: Union[List, np.ndarray]) -> int: + """判断两个数列的零轴交叉点 + + :param n1: 数列1 + :param n2: 数列2 + :return: 交叉点所在的索引位置 + """ + assert len(n1) == len(n2), '输入两个数列长度不等' + axis_0 = np.zeros(len(n1)) + + n1 = np.flip(n1) + n2 = np.flip(n2) + + x1 = np.where(n1[0] * n1 < axis_0, True, False) + x2 = np.where(n2[0] * n2 < axis_0, True, False) + + num1 = np.argmax(x1[:-1] != x1[1:]) + 2 if np.any(x1) else 0 + num2 = np.argmax(x2[:-1] != x2[1:]) + 2 if np.any(x2) else 0 + return max(num1, num2) + + +def cal_cross_num(cross: List, distance: int = 1) -> tuple: + """使用 distance 过滤掉fast_slow_cross函数返回值cross列表中 + 不符合要求的交叉点,返回处理后的金叉和死叉数值 + + :param cross: fast_slow_cross函数返回值 + :param distance: 金叉和死叉之间的最小距离 + :return: jc金叉值 ,SC死叉值 + """ + if len(cross) == 0: + return 0, 0 + elif len(cross) == 1: + cross_ = cross + elif len(cross) == 2: + if cross[-1]['距离'] < distance: + cross_ = [] + else: + cross_ = cross + else: + if cross[-1]['距离'] < distance: + last_cross = cross[-1] + del cross[-2] + re_cross = [i for i in cross if i['距离'] >= distance] + re_cross.append(last_cross) + else: + re_cross = [i for i in cross if i['距离'] >= distance] + cross_ = [] + for i in range(0, len(re_cross)): + if len(cross_) >= 1 and re_cross[i]['类型'] == re_cross[i - 1]['类型']: + # 不将上一个元素加入cross_ + del cross_[-1] + cross_.append(re_cross[i]) + else: + cross_.append(re_cross[i]) + + jc = len([x for x in cross_ if x['类型'] == '金叉']) + sc = len([x for x in cross_ if x['类型'] == '死叉']) + + return jc, sc + + +def down_cross_count(x1: Union[List, np.array], x2: Union[List, np.array]) -> int: + """输入两个序列,计算 x1 下穿 x2 的次数 + + :param x1: list + :param x2: list + :return: int + """ + x = np.array(x1) < np.array(x2) + num = 0 + for i in range(len(x) - 1): + b1, b2 = x[i], x[i + 1] + if b2 and b1 != b2: + num += 1 + return num diff --git a/czsc/utils/signal_analyzer.py b/czsc/utils/signal_analyzer.py index e79f7bf3e..b4e4fc44e 100644 --- a/czsc/utils/signal_analyzer.py +++ b/czsc/utils/signal_analyzer.py @@ -157,6 +157,10 @@ def generate_symbol_signals(self, symbol): return pd.DataFrame() sigs: pd.DataFrame = generate_czsc_signals(bars, deepcopy(self.signals_config), sdt=sdt, df=True) # type: ignore + if sigs.empty: + logger.error(f"{symbol} 信号生成失败:数据量不足") + return pd.DataFrame() + sigs.drop(['freq', 'cache'], axis=1, inplace=True) update_nbars(sigs, price_col='open', move=1, numbers=(1, 2, 3, 5, 8, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100)) diff --git a/examples/dropit/create_trade_price.py b/examples/dropit/create_trade_price.py new file mode 100644 index 000000000..0044fe42a --- /dev/null +++ b/examples/dropit/create_trade_price.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +""" +author: zengbin93 +email: zeng_bin8888@163.com +create_dt: 2023/3/21 14:37 +describe: 在1分钟周期上计算可交易价格 +""" +import os +import pandas as pd +from tqdm import tqdm +from typing import List, Union +from czsc.objects import RawBar +from czsc.connectors import qmt_connector as qmc +from czsc import cal_trade_price + + +def test_trade_price(): + # symbol = '000001.SZ' + # parquet 和 feather 性能测试对比 + # 存储空间,parquet 少 30%以上 + # 读取速度,parquet 慢 10%左右 + symbols = qmc.get_symbols('train') + results_path = r"D:\QMT投研\A股交易价_20170101_20230301" + os.makedirs(results_path, exist_ok=True) + for symbol in tqdm(symbols, desc='计算交易价'): + try: + bars = qmc.get_raw_bars(symbol, '1分钟', '20170101', '20230301') + df = cal_trade_price(bars) + df.to_parquet(os.path.join(results_path, f"{symbol}_price.parquet")) + # df.to_feather(os.path.join(results_path, f"{symbol}_price.feather")) + except: + print(f"fail on {symbol}") diff --git a/examples/signals_dev/bar_end_V221211.py b/examples/signals_dev/bar_end_V221211.py index 4012d7b83..615bf2b8f 100644 --- a/examples/signals_dev/bar_end_V221211.py +++ b/examples/signals_dev/bar_end_V221211.py @@ -1,10 +1,14 @@ -import talib as ta +# -*- coding: utf-8 -*- +# @Time : 2023/6/18 16:11 +# @Author : 琅盎 +# @FileName: BIAS_V1.py +# @Software: PyCharm +from collections import OrderedDict import numpy as np -from czsc import CZSC from datetime import datetime -from collections import OrderedDict -from czsc.utils.bar_generator import freq_end_time -from czsc.utils import create_single_signal, get_sub_elements +from czsc.connectors import research +from czsc import CZSC, check_signals_acc, get_sub_elements +from czsc.utils import create_single_signal, freq_end_time def bar_end_V221211(c: CZSC, freq1='60分钟', **kwargs) -> OrderedDict: @@ -12,10 +16,17 @@ def bar_end_V221211(c: CZSC, freq1='60分钟', **kwargs) -> OrderedDict: 参数模板:"{freq}_{freq1}结束_BS辅助221211" + **信号逻辑:** + + 以 freq 为基础周期,freq1 为大周期,判断 freq1 K线是否结束。 + 如果结束,返回信号值为 "闭合",否则返回 "未闭x",x 为未闭合的次数。 + **信号列表:** - - Signal('60分钟_K线_结束_否_任意_任意_0') - - Signal('60分钟_K线_结束_是_任意_任意_0') + - Signal('15分钟_60分钟结束_BS辅助221211_未闭1_任意_任意_0') + - Signal('15分钟_60分钟结束_BS辅助221211_未闭2_任意_任意_0') + - Signal('15分钟_60分钟结束_BS辅助221211_未闭3_任意_任意_0') + - Signal('15分钟_60分钟结束_BS辅助221211_闭合_任意_任意_0') :param c: 基础周期的 CZSC 对象 :param freq1: 分钟周期名称 @@ -25,21 +36,29 @@ def bar_end_V221211(c: CZSC, freq1='60分钟', **kwargs) -> OrderedDict: k1, k2, k3 = f"{freq}_{freq1}结束_BS辅助221211".split('_') assert "分钟" in freq1 - dt: datetime = c.bars_raw[-1].dt - v = "是" if freq_end_time(dt, freq1) == dt else "否" - return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v) + c1_dt = freq_end_time(c.bars_raw[-1].dt, freq1) + i = 0 + for bar in c.bars_raw[::-1]: + _edt = freq_end_time(bar.dt, freq1) + if _edt != c1_dt: + break + i += 1 + if c1_dt == c.bars_raw[-1].dt: + v = "闭合" + else: + v = "未闭{}".format(i) + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v) -def check(): - from czsc.connectors import research - from czsc.traders.base import check_signals_acc +def main(): symbols = research.get_symbols('A股主要指数') bars = research.get_raw_bars(symbols[0], '15分钟', '20181101', '20210101', fq='前复权') - signals_config = [{'name': bar_end_V221211, 'freq': '15分钟', 'freq1': '60分钟'}] - check_signals_acc(bars, signals_config=signals_config, height='780px', delta_days=0) - + signals_config = [ + {'name': bar_end_V221211, 'freq': '15分钟', 'freq1': '60分钟', 'di': 1}, + ] + check_signals_acc(bars, signals_config=signals_config, delta_days=0) if __name__ == '__main__': - check() + main() \ No newline at end of file diff --git a/examples/signals_dev/bias_up_dw_line_V230604.py b/examples/signals_dev/bias_up_dw_line_V230604.py new file mode 100644 index 000000000..c15d618a7 --- /dev/null +++ b/examples/signals_dev/bias_up_dw_line_V230604.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# @Time : 2023/6/18 16:11 +# @Author : 琅盎 +# @FileName: BIAS_V1.py +# @Software: PyCharm +from collections import OrderedDict +import numpy as np +from czsc.connectors import research +from czsc import CZSC, check_signals_acc, get_sub_elements +from czsc.utils import create_single_signal + + +def bias_up_dw_line_V230618(c: CZSC, **kwargs) -> OrderedDict: + """BIAS乖离率指标,贡献者:琅盎 + + 参数模板:"{freq}_D{di}N{n}M{m}P{p}TH1{th1}TH2{th2}TH3{th3}_BIAS乖离率V230618" + + **信号逻辑:** + + 乖离率 BIAS 用来衡量收盘价与移动平均线之间的差距。 + 当 BIAS6 大于 3 且 BIAS12 大于 5 且 BIAS24 大于 8, + 三个乖离率均进入股价强势上涨区间,产生买入信号; + 当 BIAS6 小于-3 且 BIAS12 小于-5 且BIAS24 小于-8 时, + 三种乖离率均进入股价强势下跌区间,产生卖出信号 + + **信号列表:** + + - Signal('日线_D1N6M12P24TH11TH23TH35_BIAS乖离率V230618_看空_任意_任意_0') + - Signal('日线_D1N6M12P24TH11TH23TH35_BIAS乖离率V230618_看多_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + + - :param di: 信号计算截止倒数第i根K线 + - :param n: 获取K线的根数,默认为30 + - :param m: 获取K线的根数,默认为20 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + n = int(kwargs.get("n", 6)) + m = int(kwargs.get("m", 12)) + p = int(kwargs.get("p", 24)) + th1 = int(kwargs.get("th1", 1)) + th2 = int(kwargs.get("th2", 3)) + th3 = int(kwargs.get("th3", 5)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}N{n}M{m}P{p}TH1{th1}TH2{th2}TH3{th3}_BIAS乖离率V230618".split('_') + v1 = "其他" + if len(c.bars_raw) < di + max(n, m, p): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bars1 = get_sub_elements(c.bars_raw, di=di, n=n) + bars2 = get_sub_elements(c.bars_raw, di=di, n=m) + bars3 = get_sub_elements(c.bars_raw, di=di, n=p) + + bias_ma1 = np.mean([bars1[i].close for i in range(len(bars1))]) + bias_ma2 = np.mean([bars2[i].close for i in range(len(bars2))]) + bias_ma3 = np.mean([bars3[i].close for i in range(len(bars3))]) + + bias1 = (bars1[-1].close - bias_ma1) / bias_ma1 * 100 + bias2 = (bars2[-1].close - bias_ma2) / bias_ma2 * 100 + bias3 = (bars3[-1].close - bias_ma3) / bias_ma3 * 100 + + if bias1 > th1 and bias2 > th2 and bias3 > th3: + v1 = "看多" + if bias1 < -th1 and bias2 < -th2 and bias3 < -th3: + v1 = "看空" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def main(): + symbols = research.get_symbols('A股主要指数') + bars = research.get_raw_bars(symbols[0], '15分钟', '20181101', '20210101', fq='前复权') + + signals_config = [ + {'name': bias_up_dw_line_V230618, 'freq': '日线', 'di': 1}, + ] + check_signals_acc(bars, signals_config=signals_config) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/examples/signals_dev/create_trade_price.py b/examples/signals_dev/create_trade_price.py deleted file mode 100644 index 7e28752d7..000000000 --- a/examples/signals_dev/create_trade_price.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -""" -author: zengbin93 -email: zeng_bin8888@163.com -create_dt: 2023/3/21 14:37 -describe: 在1分钟周期上计算可交易价格 -""" -import os -import pandas as pd -from tqdm import tqdm -from typing import List, Union -from czsc.objects import RawBar -from czsc.connectors import qmt_connector as qmc - - -def cal_trade_price(bars: Union[List[RawBar], pd.DataFrame], decimals=3, **kwargs): - """计算给定品种基础周期K线数据的交易价格 - - :param bars: 基础周期K线数据,一般是1分钟周期的K线 - :param decimals: 保留小数位数,默认值3 - :return: 交易价格表 - """ - df = pd.DataFrame(bars) if isinstance(bars, list) else bars - - # 下根K线开盘、收盘 - df['next_open'] = df['open'].shift(-1) - df['next_close'] = df['close'].shift(-1) - price_cols = ['next_open', 'next_close'] - - # TWAP / VWAP 价格 - df['vol_close_prod'] = df['vol'] * df['close'] - for t in kwargs.get('t_seq', (5, 10, 15, 20, 30, 60)): - df[f"TWAP{t}"] = df['close'].rolling(t).mean().shift(-t) - df[f"sum_vol_{t}"] = df['vol'].rolling(t).sum() - df[f"sum_vcp_{t}"] = df['vol_close_prod'].rolling(t).sum() - df[f"VWAP{t}"] = (df[f"sum_vcp_{t}"] / df[f"sum_vol_{t}"]).shift(-t) - price_cols.extend([f"TWAP{t}", f"VWAP{t}"]) - - # 用当前K线的收盘价填充交易价中的 nan 值 - for price_col in price_cols: - df.loc[df[price_col].isnull(), price_col] = df[df[price_col].isnull()]['close'] - - df = df[['symbol', 'dt', 'open', 'close', 'high', 'low', 'vol', 'amount'] + price_cols].round(decimals) - return df - - -def test_trade_price(): - # symbol = '000001.SZ' - # parquet 和 feather 性能测试对比 - # 存储空间,parquet 少 30%以上 - # 读取速度,parquet 慢 10%左右 - symbols = qmc.get_symbols('train') - results_path = r"D:\QMT投研\A股交易价_20170101_20230301" - os.makedirs(results_path, exist_ok=True) - for symbol in tqdm(symbols, desc='计算交易价'): - try: - bars = qmc.get_raw_bars(symbol, '1分钟', '20170101', '20230301') - df = cal_trade_price(bars) - df.to_parquet(os.path.join(results_path, f"{symbol}_price.parquet")) - # df.to_feather(os.path.join(results_path, f"{symbol}_price.feather")) - except: - print(f"fail on {symbol}") diff --git a/examples/signals_dev/cxt_eleven_bi_V230622.py b/examples/signals_dev/cxt_eleven_bi_V230622.py new file mode 100644 index 000000000..88c3e8f60 --- /dev/null +++ b/examples/signals_dev/cxt_eleven_bi_V230622.py @@ -0,0 +1,130 @@ +import talib as ta +import numpy as np +from czsc import CZSC, Direction +from collections import OrderedDict +from czsc.utils import create_single_signal, get_sub_elements + + +def cxt_eleven_bi_V230622(c: CZSC, **kwargs) -> OrderedDict: + """十一笔形态分类 + + 参数模板:"{freq}_D{di}十一笔_形态V230622" + + **信号逻辑:** + + 十一笔的形态分类 + + **信号列表:** + + - Signal('60分钟_D1九笔_形态V230621_类三买_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_A3B3C5式类一卖_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_类二买_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_A5B3C3式类一卖_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: + + - di: 倒数第几笔 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}九笔_形态V230621".split('_') + v1 = "其他" + if len(c.bi_list) < di + 16 or len(c.bars_ubi) > 7: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bis = get_sub_elements(c.bi_list, di=di, n=11) + assert len(bis) == 11 and bis[0].direction == bis[2].direction == bis[4].direction, "笔的方向错误" + bi1, bi2, bi3, bi4, bi5, bi6, bi7, bi8, bi9, bi10, bi11 = bis + max_high = max([x.high for x in bis]) + min_low = min([x.low for x in bis]) + direction = bi11.direction + + if direction == Direction.Down: + if min_low == bi11.low and max_high == bi1.high: + # ABC式类一买,A5B3C3 + if bi5.low == min([x.low for x in [bi1, bi3, bi5]]) \ + and bi9.low > bi11.low and bi9.high > bi11.high \ + and bi8.high > bi6.low and bi1.high - bi5.low > bi9.high - bi11.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='A5B3C3式类一买') + + # ABC式类一买,A3B3C5 + if bi1.high > bi3.high and bi1.low > bi3.low \ + and bi7.high == max([x.high for x in [bi7, bi9, bi11]]) \ + and bi6.high > bi4.low and bi1.high - bi3.low > bi7.high - bi11.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='A3B3C5式类一买') + + # ABC式类一买,A3B5C3 + if bi1.low > bi3.low and min(bi4.high, bi6.high, bi8.high) > max(bi4.low, bi6.low, bi8.low) \ + and bi9.high > bi11.high and bi1.high - bi3.low > bi9.high - bi11.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='A3B5C3式类一买') + + # a1Ab式类一买,a1(1~7构成的类趋势) + if bi2.low > bi4.high > bi4.low > bi6.high > bi5.low > bi7.low and bi10.high > bi8.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='a1Ab式类一买') + + # 类二买(1~7构成盘整背驰,246构成下跌中枢,9/11构成上涨中枢,且上涨中枢GG大于下跌中枢ZG) + if bi7.power < bi1.power and min_low == bi7.low < max([x.low for x in [bi2, bi4, bi6]]) \ + < min([x.high for x in [bi2, bi4, bi6]]) < max([x.high for x in [bi9, bi11]]) < bi1.high == max_high \ + and bi11.low > min([x.low for x in [bi2, bi4, bi6]]) \ + and min([x.high for x in [bi9, bi11]]) > max([x.low for x in [bi9, bi11]]): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类二买') + + # 类二买(1~7为区间极值,9~11构成上涨中枢,上涨中枢GG大于4~6的最大值,上涨中枢DD大于4~6的最小值) + if max_high == bi1.high and min_low == bi7.low \ + and min(bi9.high, bi11.high) > max(bi9.low, bi11.low) \ + and max(bi11.high, bi9.high) > max(bi4.high, bi6.high) \ + and min(bi9.low, bi11.low) > min(bi4.low, bi6.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类二买') + + # 类三买(1~9构成大级别中枢,10离开,11回调不跌破GG) + gg = max([x.high for x in [bi1, bi2, bi3]]) + zg = min([x.high for x in [bi1, bi2, bi3]]) + zd = max([x.low for x in [bi1, bi2, bi3]]) + dd = min([x.low for x in [bi1, bi2, bi3]]) + if max_high == bi11.high and bi11.low > zg > zd \ + and gg > bi5.low and gg > bi7.low and gg > bi9.low \ + and dd < bi5.high and dd < bi7.high and dd < bi9.high: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三买') + + if direction == Direction.Up: + if max_high == bi11.high and min_low == bi1.low: + # ABC式类一卖,A5B3C3 + if bi5.high == max([bi1.high, bi3.high, bi5.high]) and bi9.low < bi11.low and bi9.high < bi11.high \ + and bi8.low < bi6.high and bi11.high - bi9.low < bi5.high - bi1.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='A5B3C3式类一卖') + + # ABC式类一卖,A3B3C5 + if bi7.low == min([bi11.low, bi9.low, bi7.low]) and bi1.high < bi3.high and bi1.low < bi3.low \ + and bi6.low < bi4.high and bi11.high - bi7.low < bi3.high - bi1.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='A3B3C5式类一卖') + + # ABC式类一卖,A3B5C3 + if bi1.high < bi3.high and min(bi4.high, bi6.high, bi8.high) > max(bi4.low, bi6.low, bi8.low) \ + and bi9.low < bi11.low and bi3.high - bi1.low > bi11.high - bi9.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='A3B5C3式类一卖') + + # 类二卖:1~9构成类趋势,11不创新高 + if max_high == bi9.high > bi8.low > bi6.high > bi6.low > bi4.high > bi4.low > bi2.high > bi1.low == min_low \ + and bi11.high < bi9.high: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类二卖') + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + + +def check(): + from czsc.connectors import research + from czsc.traders.base import check_signals_acc + + symbols = research.get_symbols('A股主要指数') + bars = research.get_raw_bars(symbols[0], '15分钟', '20181101', '20210101', fq='前复权') + + signals_config = [{'name': cxt_eleven_bi_V230622, 'freq': '60分钟', 'di': 1}] + check_signals_acc(bars, signals_config=signals_config, height='780px') # type: ignore + + +if __name__ == '__main__': + check() diff --git a/examples/signals_dev/cxt_five_bi_V230619.py b/examples/signals_dev/cxt_five_bi_V230619.py new file mode 100644 index 000000000..fbca011fb --- /dev/null +++ b/examples/signals_dev/cxt_five_bi_V230619.py @@ -0,0 +1,107 @@ +import talib as ta +import numpy as np +from czsc import CZSC, Direction +from collections import OrderedDict +from czsc.utils import create_single_signal, get_sub_elements + + +def cxt_five_bi_V230619(c: CZSC, **kwargs) -> OrderedDict: + """五笔形态分类 + + 参数模板:"{freq}_D{di}五笔_形态V230619" + + **信号逻辑:** + + 五笔的形态分类 + + **信号列表:** + + - Signal('60分钟_D1五笔_形态V230619_上颈线突破_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_类三卖_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_类趋势底背驰_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_类趋势顶背驰_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_下颈线突破_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_类三买_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_aAb式顶背驰_任意_任意_0') + - Signal('60分钟_D1五笔_形态V230619_aAb式底背驰_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: + + - di: 倒数第几笔 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}五笔_形态V230619".split('_') + v1 = "其他" + if len(c.bi_list) < di + 6 or len(c.bars_ubi) > 7: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bis = get_sub_elements(c.bi_list, di=di, n=5) + assert len(bis) == 5 and bis[0].direction == bis[2].direction == bis[4].direction, "笔的方向错误" + bi1, bi2, bi3, bi4, bi5 = bis + + direction = bi1.direction + max_high = max([x.high for x in bis]) + min_low = min([x.low for x in bis]) + assert direction in [Direction.Down, Direction.Up], "direction 的取值错误" + + if direction == Direction.Down: + # aAb式底背驰 + if min(bi2.high, bi4.high) > max(bi2.low, bi4.low) and max_high == bi1.high and bi5.power < bi1.power: + if (min_low == bi3.low and bi5.low < bi1.low) or (min_low == bi5.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAb式底背驰') + + # 类趋势底背驰 + if max_high == bi1.high and min_low == bi5.low and bi4.high < bi2.low and bi5.power < max(bi3.power, bi1.power): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类趋势底背驰') + + # 上颈线突破 + if (min_low == bi1.low and bi5.high > min(bi1.high, bi2.high) > bi5.low > bi1.low) \ + or (min_low == bi3.low and bi5.high > bi3.high > bi5.low > bi3.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='上颈线突破') + + # 五笔三买,要求bi5.high是最高点 + if max_high == bi5.high > bi5.low > max(bi1.high, bi3.high) \ + > min(bi1.high, bi3.high) > max(bi1.low, bi3.low) > min_low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三买') + + if direction == Direction.Up: + # aAb式顶背驰 + if min(bi2.high, bi4.high) > max(bi2.low, bi4.low) and min_low == bi1.low and bi5.power < bi1.power: + if (max_high == bi3.high and bi5.high > bi1.high) or (max_high == bi5.high): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAb式顶背驰') + + # 类趋势顶背驰 + if min_low == bi1.low and max_high == bi5.high and bi5.power < max(bi1.power, bi3.power) and bi4.low > bi2.high: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类趋势顶背驰') + + # 下颈线突破 + if (max_high == bi1.high and bi5.low < max(bi1.low, bi2.low) < bi5.high < max_high) \ + or (max_high == bi3.high and bi5.low < bi3.low < bi5.high < max_high): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='下颈线突破') + + # 五笔三卖,要求bi5.low是最低点 + if min_low == bi5.low < bi5.high < min(bi1.low, bi3.low) \ + < max(bi1.low, bi3.low) < min(bi1.high, bi3.high) < max_high: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三卖') + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + + +def check(): + from czsc.connectors import research + from czsc.traders.base import check_signals_acc + + symbols = research.get_symbols('A股主要指数') + bars = research.get_raw_bars(symbols[0], '15分钟', '20181101', '20210101', fq='前复权') + + signals_config = [{'name': cxt_five_bi_V230619, 'freq': '60分钟', 'di': 1}] + check_signals_acc(bars, signals_config=signals_config, height='780px') # type: ignore + + +if __name__ == '__main__': + check() diff --git a/examples/signals_dev/cxt_nine_bi_V230621.py b/examples/signals_dev/cxt_nine_bi_V230621.py new file mode 100644 index 000000000..ae4845951 --- /dev/null +++ b/examples/signals_dev/cxt_nine_bi_V230621.py @@ -0,0 +1,172 @@ +import talib as ta +import numpy as np +from czsc import CZSC, Direction +from collections import OrderedDict +from czsc.utils import create_single_signal, get_sub_elements + + +def cxt_nine_bi_V230621(c: CZSC, **kwargs) -> OrderedDict: + """九笔形态分类 + + 参数模板:"{freq}_D{di}九笔_形态V230621" + + **信号逻辑:** + + 九笔的形态分类 + + **信号列表:** + + - Signal('60分钟_D1九笔_形态V230621_类三买A_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_aAb式类一卖_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_类三卖A_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_aAbcd式类一买_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_ABC式类一卖_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_aAbBc式类一买_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_aAbcd式类一卖_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_ZD三卖_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_aAbBc式类一卖_任意_任意_0') + - Signal('60分钟_D1九笔_形态V230621_ABC式类一买_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: + + - di: 倒数第几笔 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}九笔_形态V230621".split('_') + v1 = "其他" + if len(c.bi_list) < di + 13 or len(c.bars_ubi) > 7: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bis = get_sub_elements(c.bi_list, di=di, n=9) + assert len(bis) == 9 and bis[0].direction == bis[2].direction == bis[4].direction, "笔的方向错误" + bi1, bi2, bi3, bi4, bi5, bi6, bi7, bi8, bi9 = bis + max_high = max([x.high for x in bis]) + min_low = min([x.low for x in bis]) + direction = bi9.direction + + if direction == Direction.Down: + if min_low == bi9.low and max_high == bi1.high: + # aAb式类一买 + if min(bi2.high, bi4.high, bi6.high, bi8.high) > max(bi2.low, bi4.low, bi6.low, bi8.low) \ + and bi9.power < bi1.power and bi3.low >= bi1.low and bi7.high <= bi9.high: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAb式类一买') + + # aAbcd式类一买 + if min(bi2.high, bi4.high, bi6.high) > max(bi2.low, bi4.low, bi6.low) > bi8.high \ + and bi9.power < bi7.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAbcd式类一买') + + # ABC式类一买 + if bi3.low < bi1.low and bi7.high > bi9.high \ + and min(bi4.high, bi6.high) > max(bi4.low, bi6.low) \ + and (bi1.high - bi3.low) > (bi7.high - bi9.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='ABC式类一买') + + # 类趋势一买 + if bi8.high < bi6.low < bi6.high < bi4.low < bi4.high < bi2.low \ + and bi9.power < max([bi1.power, bi3.power, bi5.power, bi7.power]): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类趋势一买') + + # aAbBc式类一买(2~4构成中枢A,6~8构成中枢B,9背驰) + if max_high == max(bi1.high, bi3.high) and min_low == bi9.low \ + and min(bi2.high, bi4.high) > max(bi2.low, bi4.low) \ + and min(bi2.low, bi4.low) > max(bi6.high, bi8.high) \ + and min(bi6.high, bi8.high) > max(bi6.low, bi8.low) \ + and bi9.power < bi5.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAbBc式类一买') + + # 类三买(1357构成中枢,最低点在3或5) + if max_high == bi9.high > bi9.low \ + > max([x.high for x in [bi1, bi3, bi5, bi7]]) \ + > min([x.high for x in [bi1, bi3, bi5, bi7]]) \ + > max([x.low for x in [bi1, bi3, bi5, bi7]]) \ + > min([x.low for x in [bi3, bi5]]) == min_low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三买A') + + # 类三买(357构成中枢,8的力度小于2,9回调不跌破GG构成三买) + if bi8.power < bi2.power and max_high == bi9.high > bi9.low \ + > max([x.high for x in [bi3, bi5, bi7]]) \ + > min([x.high for x in [bi3, bi5, bi7]]) \ + > max([x.low for x in [bi3, bi5, bi7]]) > bi1.low == min_low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三买B') + + if min_low == bi5.low and max_high == bi1.high and bi4.high < bi2.low: # 前五笔构成向下类趋势 + zd = max([x.low for x in [bi5, bi7]]) + zg = min([x.high for x in [bi5, bi7]]) + gg = max([x.high for x in [bi5, bi7]]) + if zg > zd and bi8.high > gg: # 567构成中枢,且8的高点大于gg + if bi9.low > zg: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='ZG三买') + + # 类二买 + if bi9.high > gg > zg > bi9.low > zd: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类二买') + + if direction == Direction.Up: + if max_high == bi9.high and min_low == bi1.low: + # aAbBc式类一卖 + if bi6.low > min(bi2.high, bi4.high) > max(bi2.low, bi4.low) \ + and min(bi6.high, bi8.high) > max(bi6.low, bi8.low) \ + and max(bi2.high, bi4.high) < min(bi6.low, bi8.low) \ + and bi9.power < bi5.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAbBc式类一卖') + + # aAb式类一卖 + if min(bi2.high, bi4.high, bi6.high, bi8.high) > max(bi2.low, bi4.low, bi6.low, bi8.low) \ + and bi9.power < bi1.power and bi3.high <= bi1.high and bi7.low >= bi9.low: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAb式类一卖') + + # aAbcd式类一卖 + if bi8.low > min(bi2.high, bi4.high, bi6.high) > max(bi2.low, bi4.low, bi6.low) \ + and bi9.power < bi7.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAbcd式类一卖') + + # ABC式类一卖 + if bi3.high > bi1.high and bi7.low < bi9.low \ + and min(bi4.high, bi6.high) > max(bi4.low, bi6.low) \ + and (bi3.high - bi1.low) > (bi9.high - bi7.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='ABC式类一卖') + + # 类趋势一卖 + if bi8.low > bi6.high > bi6.low > bi4.high > bi4.low > bi2.high \ + and bi9.power < max([bi1.power, bi3.power, bi5.power, bi7.power]): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类趋势一卖') + + # 九笔三卖 + if max_high == bi1.high and min_low == bi9.low \ + and bi9.high < max([x.low for x in [bi3, bi5, bi7]]) < min([x.high for x in [bi3, bi5, bi7]]): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三卖A') + + if min_low == bi1.low and max_high == bi5.high and bi2.high < bi4.low: # 前五笔构成向上类趋势 + zd = max([x.low for x in [bi5, bi7]]) + zg = min([x.high for x in [bi5, bi7]]) + dd = min([x.low for x in [bi5, bi7]]) + if zg > zd and bi8.low < dd: # 567构成中枢,且8的低点小于dd + if bi9.high < zd: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='ZD三卖') + + # 类二卖 + if dd < zd <= bi9.high < zg: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类二卖') + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + + +def check(): + from czsc.connectors import research + from czsc.traders.base import check_signals_acc + + symbols = research.get_symbols('A股主要指数') + bars = research.get_raw_bars(symbols[0], '15分钟', '20181101', '20210101', fq='前复权') + + signals_config = [{'name': cxt_nine_bi_V230621, 'freq': '60分钟', 'di': 1}] + check_signals_acc(bars, signals_config=signals_config, height='780px') # type: ignore + + +if __name__ == '__main__': + check() diff --git a/examples/signals_dev/cxt_range_oscillation_V230620.py b/examples/signals_dev/cxt_range_oscillation_V230620.py new file mode 100644 index 000000000..3ac2e4ec0 --- /dev/null +++ b/examples/signals_dev/cxt_range_oscillation_V230620.py @@ -0,0 +1,88 @@ +import talib as ta +import numpy as np +from czsc import CZSC, Direction +from collections import OrderedDict +from czsc.utils import create_single_signal, get_sub_elements + + +def cxt_range_oscillation_V230620(c: CZSC, **kwargs) -> OrderedDict: + """判断区间震荡 + + 参数模板:"{freq}_D{di}TH{th}_区间震荡V230620" + + **信号逻辑:** + + 1. 在区间震荡中,无论振幅大小,各笔的中心应改在相近的价格区间内平移,当各笔的中心的振幅大于一定数值时就认为这个窗口内没有固定区间的中枢震荡 + 2. 给定阈值 th,当各笔的中心的振幅大于 th 时,认为这个窗口内没有固定区间的中枢震荡 + + **信号列表:** + + - Signal('日线_D1TH5_区间震荡V230620_2笔震荡_向下_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_3笔震荡_向上_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_4笔震荡_向下_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_5笔震荡_向上_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_6笔震荡_向下_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_5笔震荡_向下_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_2笔震荡_向上_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_3笔震荡_向下_任意_0') + - Signal('日线_D1TH5_区间震荡V230620_4笔震荡_向上_任意_0') + + :param c: CZSC对象 + :param kwargs: + + - di: 倒数第几笔 + - th: 振幅阈值,2 表示 2%,即 2% 以内的振幅都认为是震荡 + + :return: 信号识别结果 + """ + di = int(kwargs.get('di', 1)) + th = int(kwargs.get('th', 2)) # 振幅阈值,2 表示 2%,即 2% 以内的振幅都认为是震荡 + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}TH{th}_区间震荡V230620".split('_') + v1, v2 = '其他', '其他' + if len(c.bi_list) < di + 11: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) + + def __calculate_max_amplitude_percentage(prices): + """计算给定价位列表的最大振幅的百分比""" + if not prices: + return 100 + max_price, min_price = max(prices), min(prices) + return ((max_price - min_price) / min_price) * 100 if min_price != 0 else 100 + + _bis = get_sub_elements(c.bi_list, di=di, n=12) + + if len(_bis) != 12: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) + + price_list = [] + count = 1 + for bi in _bis[::-1]: + price_list.append((bi.high + bi.low) / 2) + if len(price_list) > 1: + if __calculate_max_amplitude_percentage(price_list) < th: + count += 1 + else: + break + + if count != 1: + v1 = f"{count}笔震荡" + v2 = "向上" if _bis[-1].direction == Direction.Up else "向下" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) + + +def check(): + from czsc.connectors import research + from czsc.traders.base import check_signals_acc + + symbols = research.get_symbols('中证500成分股') + symbol = symbols[0] + # for symbol in symbols[:10]: + bars = research.get_raw_bars(symbol, '15分钟', '20181101', '20210101', fq='前复权') + signals_config = [{'name': cxt_range_oscillation_V230620, 'freq': '日线', 'di': 1, 'th': 5}] + check_signals_acc(bars, signals_config=signals_config, height='780px') # type: ignore + + +if __name__ == '__main__': + check() diff --git a/examples/signals_dev/cxt_seven_bi_V230620.py b/examples/signals_dev/cxt_seven_bi_V230620.py new file mode 100644 index 000000000..c2175e948 --- /dev/null +++ b/examples/signals_dev/cxt_seven_bi_V230620.py @@ -0,0 +1,131 @@ +import talib as ta +import numpy as np +from czsc import CZSC, Direction +from collections import OrderedDict +from czsc.utils import create_single_signal, get_sub_elements + + +def cxt_seven_bi_V230620(c: CZSC, **kwargs) -> OrderedDict: + """七笔形态分类 + + 参数模板:"{freq}_D{di}七笔_形态V230620" + + **信号逻辑:** + + 七笔的形态分类 + + **信号列表:** + + - Signal('60分钟_D1七笔_形态V230620_类三卖_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_向上中枢完成_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_aAbcd式顶背驰_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_类三买_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_向下中枢完成_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_aAb式底背驰_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_abcAd式顶背驰_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_abcAd式底背驰_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_aAb式顶背驰_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_类趋势顶背驰_任意_任意_0') + - Signal('60分钟_D1七笔_形态V230620_aAbcd式底背驰_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: + + - di: 倒数第几笔 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}七笔_形态V230620".split('_') + v1 = "其他" + if len(c.bi_list) < di + 10 or len(c.bars_ubi) > 7: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bis = get_sub_elements(c.bi_list, di=di, n=7) + assert len(bis) == 7 and bis[0].direction == bis[2].direction == bis[4].direction, "笔的方向错误" + bi1, bi2, bi3, bi4, bi5, bi6, bi7 = bis + max_high = max([x.high for x in bis]) + min_low = min([x.low for x in bis]) + direction = bi7.direction + + if direction == Direction.Down: + if bi1.high == max_high and bi7.low == min_low: + # aAbcd式底背驰 + if min(bi2.high, bi4.high) > max(bi2.low, bi4.low) > bi6.high and bi7.power < bi5.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAbcd式底背驰') + + # abcAd式底背驰 + if bi2.low > min(bi4.high, bi6.high) > max(bi4.low, bi6.low) and bi7.power < (bi1.high - bi3.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='abcAd式底背驰') + + # aAb式底背驰 + if min(bi2.high, bi4.high, bi6.high) > max(bi2.low, bi4.low, bi6.low) and bi7.power < bi1.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAb式底背驰') + + # 类趋势底背驰 + if bi2.low > bi4.high and bi4.low > bi6.high and bi7.power < max(bi5.power, bi3.power, bi1.power): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类趋势底背驰') + + # 向上中枢完成 + if bi4.low == min_low and min(bi1.high, bi3.high) > max(bi1.low, bi3.low) \ + and min(bi5.high, bi7.high) > max(bi5.low, bi7.low) \ + and max(bi4.high, bi6.high) > min(bi3.high, bi4.high): + if max(bi1.low, bi3.low) < max(bi5.high, bi7.high): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='向上中枢完成') + + # 七笔三买:1~3构成中枢,最低点在1~3,最高点在5~7,5~7的最低点大于1~3的最高点 + if min(bi1.low, bi3.low) == min_low and max(bi5.high, bi7.high) == max_high \ + and min(bi5.low, bi7.low) > max(bi1.high, bi3.high) \ + and min(bi1.high, bi3.high) > max(bi1.low, bi3.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三买') + + if direction == Direction.Up: + # 顶背驰 + if bi1.low == min_low and bi7.high == max_high: + # aAbcd式顶背驰 + if bi6.low > min(bi2.high, bi4.high) > max(bi2.low, bi4.low) and bi7.power < bi5.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAbcd式顶背驰') + + # abcAd式顶背驰 + if min(bi4.high, bi6.high) > max(bi4.low, bi6.low) > bi2.high and bi7.power < (bi3.high - bi1.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='abcAd式顶背驰') + + # aAb式顶背驰 + if min(bi2.high, bi4.high, bi6.high) > max(bi2.low, bi4.low, bi6.low) and bi7.power < bi1.power: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='aAb式顶背驰') + + # 类趋势顶背驰 + if bi2.high < bi4.low and bi4.high < bi6.low and bi7.power < max(bi5.power, bi3.power, bi1.power): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类趋势顶背驰') + + # 向下中枢完成 + if bi4.high == max_high and min(bi1.high, bi3.high) > max(bi1.low, bi3.low) \ + and min(bi5.high, bi7.high) > max(bi5.low, bi7.low) \ + and min(bi4.low, bi6.low) < max(bi3.low, bi4.low): + if min(bi1.high, bi3.high) > min(bi5.low, bi7.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='向下中枢完成') + + # 七笔三卖:1~3构成中枢,最高点在1~3,最低点在5~7,5~7的最高点小于1~3的最低点 + if min(bi5.low, bi7.low) == min_low and max(bi1.high, bi3.high) == max_high \ + and max(bi7.high, bi5.high) < min(bi1.low, bi3.low) \ + and min(bi1.high, bi3.high) > max(bi1.low, bi3.low): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1='类三卖') + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + + +def check(): + from czsc.connectors import research + from czsc.traders.base import check_signals_acc + + symbols = research.get_symbols('A股主要指数') + bars = research.get_raw_bars(symbols[0], '15分钟', '20181101', '20210101', fq='前复权') + + signals_config = [{'name': cxt_seven_bi_V230620, 'freq': '60分钟', 'di': 1}] + check_signals_acc(bars, signals_config=signals_config, height='780px') # type: ignore + + +if __name__ == '__main__': + check() diff --git a/examples/signals_dev/cxt_three_bi_V230618.py b/examples/signals_dev/cxt_three_bi_V230618.py new file mode 100644 index 000000000..5d4bc49b6 --- /dev/null +++ b/examples/signals_dev/cxt_three_bi_V230618.py @@ -0,0 +1,92 @@ +import talib as ta +import numpy as np +from czsc import CZSC, Direction +from collections import OrderedDict +from czsc.utils import create_single_signal, get_sub_elements + + +def cxt_three_bi_V230618(c: CZSC, **kwargs) -> OrderedDict: + """三笔形态分类 + + 参数模板:"{freq}_D{di}三笔_形态V230618" + + **信号逻辑:** + + 三笔的形态分类 + + **信号列表:** + + - Signal('日线_D1三笔_形态V230618_向下盘背_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向上奔走型_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向上扩张_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向下奔走型_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向上收敛_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向下无背_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向上不重合_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向下收敛_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向下扩张_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向下不重合_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向上盘背_任意_任意_0') + - Signal('日线_D1三笔_形态V230618_向上无背_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: + + - di: 倒数第几笔 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}三笔_形态V230618".split('_') + v1 = "其他" + if len(c.bi_list) < di + 6 or len(c.bars_ubi) > 7: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bis = get_sub_elements(c.bi_list, di=di, n=3) + assert len(bis) == 3 and bis[0].direction == bis[2].direction + bi1, bi2, bi3 = bis + + # 识别向下形态 + if bi3.direction == Direction.Down: + if bi3.low > bi1.high: + v1 = '向下不重合' + elif bi2.low < bi3.low < bi1.high < bi2.high: + v1 = '向下奔走型' + elif bi1.high > bi3.high and bi1.low < bi3.low: + v1 = '向下收敛' + elif bi1.high < bi3.high and bi1.low > bi3.low: + v1 = '向下扩张' + elif bi3.low < bi1.low and bi3.high < bi1.high: + v1 = '向下盘背' if bi3.power < bi1.power else '向下无背' + + # 识别向上形态 + elif bi3.direction == Direction.Up: + if bi3.high < bi1.low: + v1 = '向上不重合' + elif bi2.low < bi1.low < bi3.high < bi2.high: + v1 = '向上奔走型' + elif bi1.high > bi3.high and bi1.low < bi3.low: + v1 = '向上收敛' + elif bi1.high < bi3.high and bi1.low > bi3.low: + v1 = '向上扩张' + elif bi3.low > bi1.low and bi3.high > bi1.high: + v1 = '向上盘背' if bi3.power < bi1.power else '向上无背' + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + + +def check(): + from czsc.connectors import research + from czsc.traders.base import check_signals_acc + + symbols = research.get_symbols('A股主要指数') + bars = research.get_raw_bars(symbols[0], '15分钟', '20181101', '20210101', fq='前复权') + + signals_config = [{'name': cxt_three_bi_V230618, 'freq': '日线', 'di': 1}] + check_signals_acc(bars, signals_config=signals_config, height='780px') # type: ignore + + +if __name__ == '__main__': + check() diff --git a/examples/signals_dev/dema_up_dw_line_V230605.py b/examples/signals_dev/dema_up_dw_line_V230605.py new file mode 100644 index 000000000..b1c926e6c --- /dev/null +++ b/examples/signals_dev/dema_up_dw_line_V230605.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# @Time : 2023/6/18 15:51 +# @Author : 琅盎 +# @FileName: DEMA.py +# @Software: PyCharm +from collections import OrderedDict +import numpy as np +from czsc.connectors import research +from czsc import CZSC, check_signals_acc, get_sub_elements +from czsc.utils import create_single_signal + + +def dema_up_dw_line_V230605(c: CZSC, **kwargs) -> OrderedDict: + """DEMA短线趋势指标,贡献者:琅盎 + + 参数模板:"{freq}_D{di}N{n}_DEMA短线趋势V230605" + + **信号逻辑:** + + DEMA指标是一种趋势指标,用于衡量价格趋势的方向和强度。 + 与其他移动平均线指标相比,DEMA指标更加灵敏,能够更快地反应价格趋势的变化,因此在短期交易中具有一定的优势。 + 当收盘价大于DEMA看多, 当收盘价小于DEMA看空 + + **信号列表:** + + - Signal('日线_D1N5_DEMA短线趋势V230605_看多_任意_任意_0') + - Signal('日线_D1N5_DEMA短线趋势V230605_看空_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + - :param di: 信号计算截止倒数第i根K线 + - :param n: 获取K线的根数,默认为5 + + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + n = int(kwargs.get("n", 5)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}N{n}_DEMA短线趋势V230605".split('_') + v1 = "其他" + if len(c.bars_raw) < di + 2*n + 10: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + short_bars = get_sub_elements(c.bars_raw, di=di, n=n) + long_bars = get_sub_elements(c.bars_raw, di=di, n=n * 2) + dema = np.mean([x.close for x in short_bars]) * 2 - np.mean([x.close for x in long_bars]) + + v1 = "看多" if short_bars[-1].close > dema else "看空" + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def main(): + symbols = research.get_symbols('A股主要指数') + bars = research.get_raw_bars(symbols[0], '15分钟', '20181101', '20210101', fq='前复权') + + signals_config = [ + {'name': dema_up_dw_line_V230605, 'freq': '日线', 'di': 1}, + ] + check_signals_acc(bars, signals_config=signals_config) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/examples/signals_dev/demakder_up_dw_line_V230605.py b/examples/signals_dev/demakder_up_dw_line_V230605.py index aea49422c..520d2bdc4 100644 --- a/examples/signals_dev/demakder_up_dw_line_V230605.py +++ b/examples/signals_dev/demakder_up_dw_line_V230605.py @@ -1,4 +1,10 @@ +# -*- coding: utf-8 -*- +# @Time : 2023/6/10 13:56 +# @Author : 琅盎 +# @FileName: ER.py +# @Software: PyCharm from collections import OrderedDict + import numpy as np import pandas as pd from loguru import logger @@ -7,75 +13,89 @@ from czsc.utils import create_single_signal -def demakder_up_dw_line_V230605(c: CZSC, **kwargs) -> OrderedDict: - """DEM多空,贡献者:琅盎 +def er_up_dw_line_V230604(c: CZSC, **kwargs) -> OrderedDict: + """ER价格动量指标,贡献者:琅盎 - 参数模板:"{freq}_D{di}N{n}H{th}L{tl}_DEM多空V230605" + 参数模板:"{freq}_D{di}W{w}N{n}_ER价格动量V230604" **信号逻辑:** - "Demark Indicator"(也称为 "DeMarker Indicator" 或 "DeM")是一种基于价格和时间的技术分析指标, - 用于衡量市场的过度买入或卖出。它是由 Tom Demark 开发的,旨在识别市场的顶部和底部。 - - 当 demaker>0.7 时上升趋势强烈,当 demaker<0.3 时下跌趋势强烈。 - 当 demaker 上穿 0.7/下穿 0.3 时产生买入/卖出信号。 + er 为动量指标。用来衡量市场的多空力量对比。在多头市场, + 人们会更贪婪地在接近高价的地方买入,BullPower 越高则当前 + 多头力量越强;而在空头市场,人们可能因为恐惧而在接近低价 + 的地方卖出。BearPower 越低则当前空头力量越强。当两者都大 + 于 0 时,反映当前多头力量占据主导地位;两者都小于 0 则反映 + 空头力量占据主导地位。 + 如果 BearPower 上穿 0,则产生买入信号; + 如果 BullPower 下穿 0,则产生卖出信号。 **信号列表:** - - Signal('日线_D1N105TH7TL7_V230605demakder_看空_任意_任意_0') - - Signal('日线_D1N105TH7TL7_V230605demakder_看多_任意_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第10层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第9层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第8层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第5层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第1层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第10层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第2层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第6层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第7层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第8层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第9层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第4层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第5层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第7层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第3层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第2层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第6层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第1层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线下方_第4层_任意_0') + - Signal('日线_D1W21N10_ER价格动量V230604_均线上方_第3层_任意_0') :param c: CZSC对象 :param kwargs: 参数字典 - :param di: 信号计算截止倒数第i根K线 - - :param n: 获取K线的根数,默认为100 - - :param th: 开多预值,默认为7,即demaker上穿0.7 - - :param tl: 开空预值,默认为3,即demaker下穿0.3 + - :param n: 获取K线的根数,默认为105 + :return: 信号识别结果 """ di = int(kwargs.get("di", 1)) - n = int(kwargs.get("n", 100)) - th = int(kwargs.get("th", 7)) - tl = int(kwargs.get("tl", 3)) + w = int(kwargs.get("w", 60)) + n = int(kwargs.get("n", 10)) freq = c.freq.value - k1, k2, k3 = f"{freq}_D{di}N{n}H{th}L{tl}_DEM多空V230605".split('_') + k1, k2, k3 = f"{freq}_D{di}W{w}N{n}_ER价格动量V230604".split('_') + + cache_key = f"ER{w}" + for i, bar in enumerate(c.bars_raw, 1): + if cache_key in bar.cache: + continue + _bars = c.bars_raw[i-w:i] + ma = np.mean([x.close for x in _bars]) + bull_power = bar.high - ma if bar.high > ma else bar.low - ma + bar.cache.update({cache_key: bull_power}) v1 = "其他" - if len(c.bars_raw) < di + n + 10: + if len(c.bars_raw) < di + w + 10: return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) - _bars = get_sub_elements(c.bars_raw, di=di, n=n) - # highs = np.array([bar.high for bar in _bars]) - # lows = np.array([bar.low for bar in _bars]) - - # diff_highs = np.diff(highs) - # diff_lows = np.diff(lows) - - # demax = np.mean(diff_highs[diff_highs > 0]) - # demin = np.mean(diff_lows[diff_lows < 0]) - - demax = np.mean([_bars[i].high - _bars[i-1].high for i in range(1, len(_bars)) if _bars[i].high - _bars[i-1].high > 0]) - demin = np.mean([_bars[i-1].low - _bars[i].low for i in range(1, len(_bars)) if _bars[i-1].low - _bars[i].low > 0]) - demaker = demax / (demax + demin) - - # logger.info(f"demaker:{demaker}, demax:{demax}, demin:{demin}") - if demaker > th / 10: - v1 = "看多" - if demaker < tl / 10: - v1 = "看空" + _bars = get_sub_elements(c.bars_raw, di=di, n=w*10) + factors = [x.cache[cache_key] for x in _bars] + factors = [x for x in factors if x * factors[-1] > 0] - return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + v1 = "均线上方" if factors[-1] > 0 else "均线下方" + q = pd.cut(factors, n, labels=list(range(1, n+1)), precision=5, duplicates='drop')[-1] + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=f"第{q}层") def main(): symbols = research.get_symbols('A股主要指数') - bars = research.get_raw_bars(symbols[0], '15分钟', '20181101', '20210101', fq='前复权') + bars = research.get_raw_bars(symbols[0], '15分钟', '20171101', '20210101', fq='前复权') signals_config = [ - {'name': demakder_up_dw_line_V230605, 'freq': '30分钟', 'di': 1}, + {'name': er_up_dw_line_V230604, 'freq': '日线', 'di': 1, 'w': 21, 'n': 10}, ] - check_signals_acc(bars, signals_config=signals_config) # type: ignore + check_signals_acc(bars, signals_config=signals_config) if __name__ == '__main__': - main() + main() \ No newline at end of file diff --git a/examples/signals_dev/adtm_up_dw_line_V230603.py b/examples/signals_dev/merged/adtm_up_dw_line_V230603.py similarity index 100% rename from examples/signals_dev/adtm_up_dw_line_V230603.py rename to examples/signals_dev/merged/adtm_up_dw_line_V230603.py diff --git a/examples/signals_dev/amv_up_dw_line_V230603.py b/examples/signals_dev/merged/amv_up_dw_line_V230603.py similarity index 100% rename from examples/signals_dev/amv_up_dw_line_V230603.py rename to examples/signals_dev/merged/amv_up_dw_line_V230603.py diff --git a/examples/signals_dev/asi_up_dw_line_V230603.py b/examples/signals_dev/merged/asi_up_dw_line_V230603.py similarity index 100% rename from examples/signals_dev/asi_up_dw_line_V230603.py rename to examples/signals_dev/merged/asi_up_dw_line_V230603.py diff --git a/examples/signals_dev/merged/bar_end_V221211.py b/examples/signals_dev/merged/bar_end_V221211.py new file mode 100644 index 000000000..4012d7b83 --- /dev/null +++ b/examples/signals_dev/merged/bar_end_V221211.py @@ -0,0 +1,45 @@ +import talib as ta +import numpy as np +from czsc import CZSC +from datetime import datetime +from collections import OrderedDict +from czsc.utils.bar_generator import freq_end_time +from czsc.utils import create_single_signal, get_sub_elements + + +def bar_end_V221211(c: CZSC, freq1='60分钟', **kwargs) -> OrderedDict: + """判断分钟 K 线是否结束 + + 参数模板:"{freq}_{freq1}结束_BS辅助221211" + + **信号列表:** + + - Signal('60分钟_K线_结束_否_任意_任意_0') + - Signal('60分钟_K线_结束_是_任意_任意_0') + + :param c: 基础周期的 CZSC 对象 + :param freq1: 分钟周期名称 + :return: s + """ + freq = c.freq.value + k1, k2, k3 = f"{freq}_{freq1}结束_BS辅助221211".split('_') + assert "分钟" in freq1 + + dt: datetime = c.bars_raw[-1].dt + v = "是" if freq_end_time(dt, freq1) == dt else "否" + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v) + + +def check(): + from czsc.connectors import research + from czsc.traders.base import check_signals_acc + + symbols = research.get_symbols('A股主要指数') + bars = research.get_raw_bars(symbols[0], '15分钟', '20181101', '20210101', fq='前复权') + + signals_config = [{'name': bar_end_V221211, 'freq': '15分钟', 'freq1': '60分钟'}] + check_signals_acc(bars, signals_config=signals_config, height='780px', delta_days=0) + + +if __name__ == '__main__': + check() diff --git a/examples/signals_dev/check_atr_cache.py b/examples/signals_dev/merged/check_atr_cache.py similarity index 100% rename from examples/signals_dev/check_atr_cache.py rename to examples/signals_dev/merged/check_atr_cache.py diff --git a/examples/signals_dev/check_boll_cache.py b/examples/signals_dev/merged/check_boll_cache.py similarity index 100% rename from examples/signals_dev/check_boll_cache.py rename to examples/signals_dev/merged/check_boll_cache.py diff --git a/examples/signals_dev/check_cci_cache.py b/examples/signals_dev/merged/check_cci_cache.py similarity index 100% rename from examples/signals_dev/check_cci_cache.py rename to examples/signals_dev/merged/check_cci_cache.py diff --git a/examples/signals_dev/check_macd_cache.py b/examples/signals_dev/merged/check_macd_cache.py similarity index 100% rename from examples/signals_dev/check_macd_cache.py rename to examples/signals_dev/merged/check_macd_cache.py diff --git a/examples/signals_dev/check_sar_cache.py b/examples/signals_dev/merged/check_sar_cache.py similarity index 100% rename from examples/signals_dev/check_sar_cache.py rename to examples/signals_dev/merged/check_sar_cache.py diff --git a/examples/signals_dev/clv_up_dw_line_V230605.py b/examples/signals_dev/merged/clv_up_dw_line_V230605.py similarity index 100% rename from examples/signals_dev/clv_up_dw_line_V230605.py rename to examples/signals_dev/merged/clv_up_dw_line_V230605.py diff --git a/examples/signals_dev/cmo_up_dw_line_V230605.py b/examples/signals_dev/merged/cmo_up_dw_line_V230605.py similarity index 100% rename from examples/signals_dev/cmo_up_dw_line_V230605.py rename to examples/signals_dev/merged/cmo_up_dw_line_V230605.py diff --git a/examples/signals_dev/merged/cxt_bi_end_V230618.py b/examples/signals_dev/merged/cxt_bi_end_V230618.py new file mode 100644 index 000000000..267b58dbb --- /dev/null +++ b/examples/signals_dev/merged/cxt_bi_end_V230618.py @@ -0,0 +1,115 @@ +import talib as ta +import numpy as np +from czsc import CZSC, Direction +from collections import OrderedDict +from czsc.utils import create_single_signal, get_sub_elements + + +def cxt_bi_end_V230618(c: CZSC, **kwargs) -> OrderedDict: + """笔结束辅助判断 + + 参数模板:"{freq}_D{di}_BE辅助V230618" + + **信号逻辑:** + + 以向下笔为例,判断笔内是否有小级别中枢,如果有则看多: + + **信号列表:** + + - Signal('日线_D1_BE辅助V230618_看多_1小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看空_3小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看空_2小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看空_1小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看多_2小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看空_5小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看空_4小中枢_任意_0') + - Signal('日线_D1_BE辅助V230618_看多_3小中枢_任意_0') + + **信号说明:** + + 类似 cxt_third_bs_V230318 信号,但增加了笔内有无小级别中枢的判断。用k线重叠来近似小级别中枢的判断 + + :param c: CZSC对象 + :param kwargs: timeperiod: 均线周期 + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + k1, k2, k3 = f"{c.freq.value}_D{di}_BE辅助V230618".split('_') + v1 = "其他" + if len(c.bi_list) < di + 6 or len(c.bars_ubi) > 7: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + def __cal_zs_number(raw_bars): + """计算笔内的小中枢数量 + + **信号逻辑:** + + 1. 笔内任意两根k线的重叠使该价格位的计数加1,计算从笔.high到笔.low之间各价格位的重叠次数 + 2. 通过各价格位的重叠可以得到横轴价格,纵轴重叠次数的图,通过计算途中波峰的个数来得到近似的小中枢个数 + 例子:横轴从小到大对应的重叠次数为 1112233211112133334445553321,则可以通过计算从n变为1的次数来得到波峰个数 + 这里2-1,2-1,2-1,得到波峰数为3 + + :param raw_bars: 构成笔的bar + :return: 小中枢数量 + """ + # 用笔内价格极值取得笔内价格范围 + max_price = max(bar.high for bar in raw_bars[:-1]) + min_price = min(bar.low for bar in raw_bars[:-1]) + price_range = max_price - min_price + + # 计算当前k线所覆盖的笔内价格范围,并用百分比表示 + for bar in raw_bars[:-1]: + bar_high_pct = int((100 * (bar.high - min_price) / price_range)) + bar_low_pct = int((100 * (bar.low - min_price) / price_range)) + bar.dt_high_pct = bar_high_pct + bar.dt_low_pct = bar_low_pct + + # 用这个list保存每个价格的重叠次数,把每个价格映射到100以内的区间内 + df_chengjiaoqu = [[i, 0] for i in range(101)] + + # 对每个k线进行映射,把该k线的价格范围映射到df_chengjiaoqu + for bar in raw_bars[:-1]: + range_max = bar.dt_high_pct + range_min = bar.dt_low_pct + + if range_max == range_min: + df_chengjiaoqu[range_max][1] += 1 + else: + for i in range(range_min, range_max + 1): + df_chengjiaoqu[i][1] += 1 + + # 计算波峰个数,相当于有多少个小中枢 + # 每个波峰结束后价格重叠区域必然会回到1 + peak_count = 0 + for i in range(1, len(df_chengjiaoqu) - 1): + if df_chengjiaoqu[i][1] == 1 and df_chengjiaoqu[i][1] < df_chengjiaoqu[i - 1][1]: + peak_count += 1 + return peak_count + + bi = c.bi_list[-di] + zs_count = __cal_zs_number(bi.raw_bars) + v1 = '看多' if bi.direction == Direction.Down else '看空' + # 为了增加稳定性,要确保笔内有小中枢,并且要确保笔内有至少2个分型存在,保证从上往下的分型12的长度比分型34的长度大,来确认背驰 + if len(bi.fxs) >= 4 and zs_count >= 1 and (bi.fxs[-4].fx - bi.fxs[-3].fx) - (bi.fxs[-2].fx - bi.fxs[-1].fx) > 0: + # 计算倒1笔内部的小中枢数量 + v2 = f"{zs_count}小中枢" + else: + v2 = "其他" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) + + + +def check(): + from czsc.connectors import research + from czsc.traders.base import check_signals_acc + + symbols = research.get_symbols('A股主要指数') + bars = research.get_raw_bars(symbols[0], '15分钟', '20181101', '20210101', fq='前复权') + + signals_config = [{'name': cxt_bi_end_V230618, 'freq': '日线', 'di': 1, 'timeperiod': 5, 'th': 30}] + check_signals_acc(bars, signals_config=signals_config, height='780px') # type: ignore + + +if __name__ == '__main__': + check() diff --git a/examples/signals_dev/merged/demakder_up_dw_line_V230605.py b/examples/signals_dev/merged/demakder_up_dw_line_V230605.py new file mode 100644 index 000000000..aea49422c --- /dev/null +++ b/examples/signals_dev/merged/demakder_up_dw_line_V230605.py @@ -0,0 +1,81 @@ +from collections import OrderedDict +import numpy as np +import pandas as pd +from loguru import logger +from czsc.connectors import research +from czsc import CZSC, check_signals_acc, get_sub_elements +from czsc.utils import create_single_signal + + +def demakder_up_dw_line_V230605(c: CZSC, **kwargs) -> OrderedDict: + """DEM多空,贡献者:琅盎 + + 参数模板:"{freq}_D{di}N{n}H{th}L{tl}_DEM多空V230605" + + **信号逻辑:** + + "Demark Indicator"(也称为 "DeMarker Indicator" 或 "DeM")是一种基于价格和时间的技术分析指标, + 用于衡量市场的过度买入或卖出。它是由 Tom Demark 开发的,旨在识别市场的顶部和底部。 + + 当 demaker>0.7 时上升趋势强烈,当 demaker<0.3 时下跌趋势强烈。 + 当 demaker 上穿 0.7/下穿 0.3 时产生买入/卖出信号。 + + **信号列表:** + + - Signal('日线_D1N105TH7TL7_V230605demakder_看空_任意_任意_0') + - Signal('日线_D1N105TH7TL7_V230605demakder_看多_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + - :param di: 信号计算截止倒数第i根K线 + - :param n: 获取K线的根数,默认为100 + - :param th: 开多预值,默认为7,即demaker上穿0.7 + - :param tl: 开空预值,默认为3,即demaker下穿0.3 + :return: 信号识别结果 + """ + di = int(kwargs.get("di", 1)) + n = int(kwargs.get("n", 100)) + th = int(kwargs.get("th", 7)) + tl = int(kwargs.get("tl", 3)) + freq = c.freq.value + k1, k2, k3 = f"{freq}_D{di}N{n}H{th}L{tl}_DEM多空V230605".split('_') + + v1 = "其他" + if len(c.bars_raw) < di + n + 10: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + _bars = get_sub_elements(c.bars_raw, di=di, n=n) + # highs = np.array([bar.high for bar in _bars]) + # lows = np.array([bar.low for bar in _bars]) + + # diff_highs = np.diff(highs) + # diff_lows = np.diff(lows) + + # demax = np.mean(diff_highs[diff_highs > 0]) + # demin = np.mean(diff_lows[diff_lows < 0]) + + demax = np.mean([_bars[i].high - _bars[i-1].high for i in range(1, len(_bars)) if _bars[i].high - _bars[i-1].high > 0]) + demin = np.mean([_bars[i-1].low - _bars[i].low for i in range(1, len(_bars)) if _bars[i-1].low - _bars[i].low > 0]) + demaker = demax / (demax + demin) + + # logger.info(f"demaker:{demaker}, demax:{demax}, demin:{demin}") + if demaker > th / 10: + v1 = "看多" + if demaker < tl / 10: + v1 = "看空" + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def main(): + symbols = research.get_symbols('A股主要指数') + bars = research.get_raw_bars(symbols[0], '15分钟', '20181101', '20210101', fq='前复权') + + signals_config = [ + {'name': demakder_up_dw_line_V230605, 'freq': '30分钟', 'di': 1}, + ] + check_signals_acc(bars, signals_config=signals_config) # type: ignore + + +if __name__ == '__main__': + main() diff --git a/examples/signals_dev/emv_up_dw_line_V230605.py b/examples/signals_dev/merged/emv_up_dw_line_V230605.py similarity index 100% rename from examples/signals_dev/emv_up_dw_line_V230605.py rename to examples/signals_dev/merged/emv_up_dw_line_V230605.py diff --git a/examples/signals_dev/holds_concepts_effect.py b/examples/signals_dev/merged/holds_concepts_effect.py similarity index 100% rename from examples/signals_dev/holds_concepts_effect.py rename to examples/signals_dev/merged/holds_concepts_effect.py diff --git a/examples/signals_dev/pos_fx_stop_V230414.py b/examples/signals_dev/merged/pos_fx_stop_V230414.py similarity index 100% rename from examples/signals_dev/pos_fx_stop_V230414.py rename to examples/signals_dev/merged/pos_fx_stop_V230414.py diff --git a/examples/signals_dev/pos_holds_V230414.py b/examples/signals_dev/merged/pos_holds_V230414.py similarity index 100% rename from examples/signals_dev/pos_holds_V230414.py rename to examples/signals_dev/merged/pos_holds_V230414.py diff --git a/examples/signals_dev/tas_atr_stop_V230424.py b/examples/signals_dev/merged/tas_atr_stop_V230424.py similarity index 100% rename from examples/signals_dev/tas_atr_stop_V230424.py rename to examples/signals_dev/merged/tas_atr_stop_V230424.py diff --git a/examples/signals_dev/tas_double_ma_V230512.py b/examples/signals_dev/merged/tas_double_ma_V230512.py similarity index 100% rename from examples/signals_dev/tas_double_ma_V230512.py rename to examples/signals_dev/merged/tas_double_ma_V230512.py diff --git a/examples/signals_dev/tas_sar_base_V230424.py b/examples/signals_dev/merged/tas_sar_base_V230424.py similarity index 100% rename from examples/signals_dev/tas_sar_base_V230424.py rename to examples/signals_dev/merged/tas_sar_base_V230424.py diff --git a/examples/signals_dev/pos_fix_exit_V230624.py b/examples/signals_dev/pos_fix_exit_V230624.py new file mode 100644 index 000000000..a3159560e --- /dev/null +++ b/examples/signals_dev/pos_fix_exit_V230624.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +""" +author: zengbin93 +email: zeng_bin8888@163.com +create_dt: 2023/4/14 17:48 +describe: +""" +import os +import sys + +sys.path.insert(0, r'D:\ZB\git_repo\waditu\czsc\examples\signals_dev') +os.environ['czsc_verbose'] = '1' +import pandas as pd +from typing import List +from loguru import logger +from czsc import CzscStrategyBase, Position +from czsc.connectors import research +from czsc.analyze import CZSC +from collections import OrderedDict +from czsc.traders.base import CzscTrader +from czsc.utils import create_single_signal +from czsc.objects import Operate, Direction, Mark +from czsc.signals.tas import update_ma_cache + +logger.enable('czsc.analyze') + +pd.set_option('expand_frame_repr', False) +pd.set_option('display.max_rows', 1000) +pd.set_option('display.max_columns', 1000) +pd.set_option('display.width', 1000) + + +def pos_fix_exit_V230624(cat: CzscTrader, **kwargs) -> OrderedDict: + """固定比例止损,止盈 + + 参数模板:"{pos_name}_固定{th}BP止盈止损_出场V230624" + + **信号逻辑:** + + 以多头为例,如果持有收益超过 th 个BP,则止盈;如果亏损超过 th 个BP,则止损。 + + **信号列表:** + + - Signal('日线三买多头_固定100BP止盈止损_出场V230624_多头止损_任意_任意_0') + - Signal('日线三买多头_固定100BP止盈止损_出场V230624_空头止损_任意_任意_0') + + :param cat: CzscTrader对象 + :param kwargs: 参数字典 + - pos_name: str,开仓信号的名称 + - freq1: str,给定的K线周期 + - n: int,向前找的K线个数,默认为 3 + :return: + """ + pos_name = kwargs["pos_name"] + th = int(kwargs.get('th', 300)) + k1, k2, k3 = f"{pos_name}_固定{th}BP止盈止损_出场V230624".split("_") + v1 = '其他' + if not hasattr(cat, "positions"): + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + pos_ = [x for x in cat.positions if x.name == pos_name][0] + if len(pos_.operates) == 0 or pos_.operates[-1]['op'] in [Operate.SE, Operate.LE]: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + op = pos_.operates[-1] + op_price = op['price'] + + if op['op'] == Operate.LO: + if cat.latest_price < op_price * (1 - th / 10000): + v1 = '多头止损' + if cat.latest_price > op_price * (1 + th / 10000): + v1 = '多头止盈' + + if op['op'] == Operate.SO: + if cat.latest_price > op_price * (1 + th / 10000): + v1 = '空头止损' + if cat.latest_price < op_price * (1 - th / 10000): + v1 = '空头止盈' + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +class MyStrategy(CzscStrategyBase): + def create_pos(self, freq='60分钟', freq1='15分钟'): + _pos_name = f'{freq}通道突破' + _pos = { + 'symbol': self.symbol, + 'name': _pos_name, + 'opens': [ + { + 'operate': '开多', + 'factors': [ + {'name': f'{freq}看多', 'signals_all': [f'{freq}_D1通道突破#5#30#30_BS辅助V230403_看多_任意_任意_0']} + ], + }, + { + 'operate': '开空', + 'factors': [ + {'name': f'{freq}看空', 'signals_all': [f'{freq}_D1通道突破#5#30#30_BS辅助V230403_看空_任意_任意_0']} + ], + }, + ], + 'exits': [ + { + 'operate': '平多', + 'factors': [ + {'name': '止损出场V230624', 'signals_all': [f'{_pos_name}_固定{200}BP止盈止损_出场V230624_空头止损_任意_任意_0']}, + {'name': '止损出场V230624', 'signals_all': [f'{_pos_name}_固定{200}BP止盈止损_出场V230624_空头止盈_任意_任意_0']}, + ], + }, + { + 'operate': '平空', + 'factors': [ + {'name': '止损出场V230624', 'signals_all': [f'{_pos_name}_固定{200}BP止盈止损_出场V230624_空头止损_任意_任意_0']}, + {'name': '止损出场V230624', 'signals_all': [f'{_pos_name}_固定{200}BP止盈止损_出场V230624_空头止盈_任意_任意_0']}, + ], + }, + ], + 'interval': 7200, + 'timeout': 100, + 'stop_loss': 500, + 'T0': True, + } + + return Position.load(_pos) + + @property + def positions(self) -> List[Position]: + _pos_list = [self.create_pos(freq='日线', freq1='60分钟')] + return _pos_list + + +def check(): + from czsc.connectors import research + + symbols = research.get_symbols('A股主要指数') + tactic = MyStrategy(symbol=symbols[0], signals_module_name='czsc.signals') + bars = research.get_raw_bars(symbols[0], tactic.base_freq, '20151101', '20210101', fq='前复权') + + tactic.check(bars, res_path=r'C:\Users\zengb\.czsc\策略信号验证', refresh=True) + + +if __name__ == '__main__': + check() diff --git a/examples/signals_dev/pos_profit_loss_V230624.py b/examples/signals_dev/pos_profit_loss_V230624.py new file mode 100644 index 000000000..fc02ca788 --- /dev/null +++ b/examples/signals_dev/pos_profit_loss_V230624.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +""" +author: zengbin93 +email: zeng_bin8888@163.com +create_dt: 2023/4/14 17:48 +describe: +""" +import os +import sys + +sys.path.insert(0, '.') +sys.path.insert(0, '..') +sys.path.insert(0, '../..') +sys.path.insert(0, '../../..') +os.environ['czsc_verbose'] = '1' +import pandas as pd +from typing import List +from loguru import logger +from czsc import CzscStrategyBase, Position +from czsc.connectors import research + +logger.enable('czsc.analyze') + +pd.set_option('expand_frame_repr', False) +pd.set_option('display.max_rows', 1000) +pd.set_option('display.max_columns', 1000) +pd.set_option('display.width', 1000) + + +class MyStrategy(CzscStrategyBase): + def create_pos(self, freq='60分钟', freq1='15分钟'): + _pos_name = f'{freq}通道突破' + _pos = { + 'symbol': self.symbol, + 'name': _pos_name, + 'opens': [ + { + 'operate': '开多', + 'factors': [ + {'name': f'{freq}看多', 'signals_all': [f'{freq}_D1通道突破#5#30#30_BS辅助V230403_看多_任意_任意_0']} + ], + }, + { + 'operate': '开空', + 'factors': [ + {'name': f'{freq}看空', 'signals_all': [f'{freq}_D1通道突破#5#30#30_BS辅助V230403_看空_任意_任意_0']} + ], + }, + ], + 'exits': [ + { + 'operate': '平多', + 'factors': [ + {'name': '止盈', 'signals_all': [f'{_pos_name}_{freq1}YKB20N3_盈亏比判断V230624_多头达标_任意_任意_0']}, + {'name': '止损', 'signals_all': [f'{_pos_name}_{freq1}YKB20N3_盈亏比判断V230624_多头止损_任意_任意_0']}, + ], + }, + { + 'operate': '平空', + 'factors': [ + {'name': '止盈', 'signals_all': [f'{_pos_name}_{freq1}YKB20N3_盈亏比判断V230624_空头达标_任意_任意_0']}, + {'name': '止损', 'signals_all': [f'{_pos_name}_{freq1}YKB20N3_盈亏比判断V230624_空头止损_任意_任意_0']}, + ], + }, + ], + 'interval': 7200, + 'timeout': 100, + 'stop_loss': 500, + 'T0': True, + } + + return Position.load(_pos) + + @property + def positions(self) -> List[Position]: + _pos_list = [self.create_pos(freq='日线', freq1='60分钟')] + return _pos_list + + +def check(): + from czsc.connectors import research + + symbols = research.get_symbols('A股主要指数') + tactic = MyStrategy(symbol=symbols[0], signals_module_name='czsc.signals') + bars = research.get_raw_bars(symbols[0], tactic.base_freq, '20151101', '20210101', fq='前复权') + + tactic.check(bars, res_path=r'C:\Users\zengb\.czsc\策略信号验证', refresh=True) + + +if __name__ == '__main__': + check() diff --git a/examples/signals_dev/signal_match.py b/examples/signals_dev/signal_match.py index da5d24915..b0172d56c 100644 --- a/examples/signals_dev/signal_match.py +++ b/examples/signals_dev/signal_match.py @@ -7,14 +7,14 @@ """ import os import sys -sys.path.insert(0, r'..\..') -# 插入用户自定义信号函数模块所在目录 -sys.path.insert(0, os.path.join(os.path.expanduser('~'), '.czsc/czsc_usr_signals')) +# sys.path.insert(0, r'..\..') +# # 插入用户自定义信号函数模块所在目录 +# sys.path.insert(0, os.path.join(os.path.expanduser('~'), '.czsc/czsc_usr_signals')) import re from loguru import logger from czsc.utils import import_by_name -from czsc.traders.sig_parse import SignalsParser +from czsc import SignalsParser signals_module_name = 'czsc.signals' @@ -40,13 +40,13 @@ parsed_name = {x['name'] for x in conf} print(f"total signal functions: {len(sp.sig_name_map)}; parsed: {len(parsed_name)}") - # # 测试信号配置生成信号 - from czsc import generate_czsc_signals, get_signals_freqs, get_signals_config - from test.test_analyze import read_1min - bars = read_1min() + # # # 测试信号配置生成信号 + # from czsc import generate_czsc_signals, get_signals_freqs, get_signals_config + # from test.test_analyze import read_1min + # bars = read_1min() - conf = get_signals_config(signals_seq) - freqs = get_signals_freqs(signals_seq) + # conf = get_signals_config(signals_seq) + # freqs = get_signals_freqs(signals_seq) - sigs = generate_czsc_signals(bars, signals_config=conf, sdt='20180101', df=True) + # sigs = generate_czsc_signals(bars, signals_config=conf, sdt='20180101', df=True) diff --git a/examples/signals_dev/tas_cross_status_V230619.py b/examples/signals_dev/tas_cross_status_V230619.py new file mode 100644 index 000000000..ce446fd26 --- /dev/null +++ b/examples/signals_dev/tas_cross_status_V230619.py @@ -0,0 +1,103 @@ +import sys +sys.path.insert(0, '.') +sys.path.insert(0, '..') +sys.path.insert(0, '../..') +sys.path.insert(0, '../../..') +import pandas as pd +import numpy as np +from collections import OrderedDict +from czsc import CZSC +from czsc.signals.tas import update_macd_cache, update_ma_cache +from czsc.utils import get_sub_elements, create_single_signal, fast_slow_cross +from czsc.utils.sig import cross_zero_axis, cal_cross_num, down_cross_count +from czsc.objects import Direction +from typing import List, Union + + +# 定义信号函数 +# ---------------------------------------------------------------------------------------------------------------------- + +def tas_cross_status_V230619(c: CZSC, **kwargs) -> OrderedDict: + """0轴上下金死叉次数计算信号函数 贡献者:谌意勇 + + 参数模板:"{freq}_D{di}MACD{fastperiod}#{slowperiod}#{signalperiod}_金死叉V230619" + + **信号逻辑:** + + 精确确立MACD指标中0轴以上或以下位置第几次金叉和死叉,作为开仓的辅助买点: + + **信号列表:** + + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴上死叉第2次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下金叉第1次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下死叉第1次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下金叉第2次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下死叉第2次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下金叉第3次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴上死叉第1次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴上金叉第1次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下死叉第3次_任意_任意_0') + - Signal('日线_D1MACD12#26#9_金死叉V230619_0轴下金叉第4次_任意_任意_0') + + :param c: CZSC对象 + :param kwargs: 参数字典 + + - :param di: 信号计算截止倒数第i根K线 + - :param fastperiod: MACD快线周期 + - :param slowperiod: MACD慢线周期 + - :param signalperiod: MACD信号线周期 + + :return: 信号识别结果 + """ + di = int(kwargs.get('di', 1)) + freq = c.freq.value + fastperiod = int(kwargs.get('fastperiod', 12)) + slowperiod = int(kwargs.get('slowperiod', 26)) + signalperiod = int(kwargs.get('signalperiod', 9)) + cache_key = update_macd_cache(c, **kwargs) + s = OrderedDict() + bars = get_sub_elements(c.bars_raw, di=di, n=100) + k1, k2, k3 = f"{freq}_D{di}MACD{fastperiod}#{slowperiod}#{signalperiod}_金死叉V230619".split('_') + v1 = "其他" + if len(bars)>=100: + dif = [x.cache[cache_key]['dif'] for x in bars] + dea = [x.cache[cache_key]['dea'] for x in bars] + + num_k = cross_zero_axis(dif, dea) # type: ignore + dif_temp = get_sub_elements(dif, di=di, n=num_k) + dea_temp = get_sub_elements(dea, di=di, n=num_k) + + if dif[-1] < 0 and dea[-1] < 0: + down_num_sc = down_cross_count(dif_temp, dea_temp) + down_num_jc = down_cross_count(dea_temp, dif_temp) + if dif[-1] > dea[-1] and dif[-2] < dea[-2]: + v1 = f'0轴下金叉第{down_num_jc}次' + elif dif[-1] < dea[-1] and dif[-2] > dea[-2]: + v1 = f'0轴下死叉第{down_num_sc}次' + + + elif dif[-1] > 0 and dea[-1] > 0: + up_num_sc = down_cross_count(dif_temp, dea_temp) + up_num_jc = down_cross_count(dea_temp, dif_temp) + if dif[-1] > dea[-1] and dif[-2] < dea[-2]: + v1 = f'0轴上金叉第{up_num_jc}次' + elif dif[-1] < dea[-1] and dif[-2] > dea[-2]: + v1 = f'0轴上死叉第{up_num_sc}次' + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def check(): + from czsc.connectors import research + from czsc.traders.base import check_signals_acc + + symbols = research.get_symbols('中证500成分股') + symbol = symbols[0] + # for symbol in symbols[:10]: + bars = research.get_raw_bars(symbol, '15分钟', '20181101', '20210101', fq='前复权') + signals_config = [{'name': tas_cross_status_V230619, 'freq': '日线', 'di': 1, 'th': 5}] + check_signals_acc(bars, signals_config=signals_config, height='780px') # type: ignore + + +if __name__ == '__main__': + check() diff --git a/examples/signals_dev/tas_cross_status_V230624.py b/examples/signals_dev/tas_cross_status_V230624.py new file mode 100644 index 000000000..73dcb4ca8 --- /dev/null +++ b/examples/signals_dev/tas_cross_status_V230624.py @@ -0,0 +1,101 @@ +import sys +sys.path.insert(0, '.') +sys.path.insert(0, '..') +sys.path.insert(0, '../..') +sys.path.insert(0, '../../..') +import pandas as pd +import numpy as np +from collections import OrderedDict +from czsc import CZSC +from czsc.signals.tas import update_macd_cache, update_ma_cache +from czsc.utils import get_sub_elements, create_single_signal, fast_slow_cross +from czsc.utils.sig import cross_zero_axis, cal_cross_num +from czsc.objects import Direction +from typing import List, Union + + + +def tas_cross_status_V230624(c: CZSC, **kwargs) -> OrderedDict: + """指定金死叉数值信号函数,以此来确定MACD交易区间 贡献者:谌意勇 + + 参数模板:"{freq}_D{di}N{n}MD{md}_MACD交叉数量V230624" + + **信号逻辑:** + + 1、通过指定0轴上下金死叉数量,来选择自己想要的指标形态,通过配合其他信号函数出信号 + 2、金叉数量和死叉数量要注意连续对应。0轴上一定是第一次先死叉,再金叉,死叉的数值同 + 金叉数值相比永远是相等或者大1,不能出现>=2的情况,0轴下则反之。 + + **信号列表:** + + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴上金叉第1次_0轴上死叉第1次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴上金叉第1次_0轴上死叉第2次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第0次_0轴下死叉第0次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第1次_0轴下死叉第0次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第1次_0轴下死叉第1次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第2次_0轴下死叉第1次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第2次_0轴下死叉第2次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第3次_0轴下死叉第2次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴上金叉第0次_0轴上死叉第0次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴上金叉第0次_0轴上死叉第1次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第3次_0轴下死叉第3次_任意_0') + - Signal('日线_D1N100MD1_MACD交叉数量V230624_0轴下金叉第4次_0轴下死叉第3次_任意_0') + + :param c: czsc对象 + :param kwargs: + + - di: 倒数第i根K线 + - n: 从dik往前数n根k线(此数值不需要精确,函数会自动截取最后上下0轴以后的数据) + - md: 抖动过滤参数,金死叉之间格距离小于此数值,将被忽略(去除一些杂波扰动因素,最小值不小于1) + 0轴上下金死叉状态信息,与其他信号加以辅助操作。 + + :return: 信号字典 + """ + di = int(kwargs.get('di', 1)) + n = int(kwargs.get('n', 100)) + md = int(kwargs.get('md', 1)) # md 是 min distance 的缩写,表示金死叉之间格距离小于此数值,将被忽略(去除一些杂波扰动因素,最小值不小于1) + assert md >= 1, "md必须大于等于1" + freq = c.freq.value + cache_key = update_macd_cache(c, **kwargs) + + k1, k2, k3 = f"{freq}_D{di}N{n}MD{md}_MACD交叉数量V230624".split('_') + v1 = "其他" + v2 = "其他" + if len(c.bars_raw) < n + 1: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bars = get_sub_elements(c.bars_raw, di=di, n=n) + dif = [x.cache[cache_key]['dif'] for x in bars] + dea = [x.cache[cache_key]['dea'] for x in bars] + num_k = cross_zero_axis(dif, dea) + dif_temp = get_sub_elements(dif, di=1, n=num_k) + dea_temp = get_sub_elements(dea, di=1, n=num_k) + cross = fast_slow_cross(dif_temp, dea_temp) + + jc, sc = cal_cross_num(cross, md) + + if dif[-1] < 0 and dea[-1] < 0: + v1 = f'0轴下金叉第{jc}次' + v2 = f'0轴下死叉第{sc}次' + + elif dif[-1] > 0 and dea[-1] > 0: + v1 = f'0轴上金叉第{jc}次' + v2 = f'0轴上死叉第{sc}次' + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1, v2=v2) + + +def check(): + from czsc.connectors import research + from czsc.traders.base import check_signals_acc + + symbols = research.get_symbols('中证500成分股') + symbol = symbols[0] + # for symbol in symbols[:10]: + bars = research.get_raw_bars(symbol, '15分钟', '20181101', '20210101', fq='前复权') + signals_config = [{'name': tas_cross_status_V230624, 'freq': '日线', 'di': 1, 'th': 5}] + check_signals_acc(bars, signals_config=signals_config, height='780px') # type: ignore + + +if __name__ == '__main__': + check() diff --git a/examples/signals_dev/tas_cross_status_V230625.py b/examples/signals_dev/tas_cross_status_V230625.py new file mode 100644 index 000000000..5111d40e6 --- /dev/null +++ b/examples/signals_dev/tas_cross_status_V230625.py @@ -0,0 +1,98 @@ +import sys + +sys.path.insert(0, '.') +sys.path.insert(0, '..') +sys.path.insert(0, '../..') +sys.path.insert(0, '../../..') +import pandas as pd +import numpy as np +from collections import OrderedDict +from czsc import CZSC +from czsc.signals.tas import update_macd_cache, update_ma_cache +from czsc.utils import get_sub_elements, create_single_signal, fast_slow_cross +from czsc.utils.sig import cross_zero_axis, cal_cross_num +from czsc.objects import Direction +from typing import List, Union + + +# 定义信号函数 +# ---------------------------------------------------------------------------------------------------------------------- +def tas_cross_status_V230625(c: CZSC, **kwargs) -> OrderedDict: + """指定金死叉数值信号函数, 以此来确定MACD交易区间 贡献者:谌意勇 + + 参数模板:"{freq}_D{di}N{n}MD{md}J{j}S{s}_MACD交叉数量V230625" + + **信号逻辑:** + + 1、通过指定jc或者sc数值来确定为哪第几次金叉或死叉之后的信号。两者最少要指定一个,并且指定其中一个时,另外一个需为0. + + **信号列表:** + + - Signal('15分钟_D1N100MD1J3S0_MACD交叉数量V230625_0轴下第3次金叉以后_任意_任意_0') + - Signal('15分钟_D1N100MD1J3S0_MACD交叉数量V230625_0轴上第3次金叉以后_任意_任意_0') + + :param c: czsc对象 + :param kwargs: + + - di: 倒数第i根K线 + - j: 金叉数值 + - s: 死叉数值 + - n: 从dik往前数n根k线(此数值不需要精确,函数会自动截取最后上下0轴以后的数据) + - md: 抖动过滤参数,金死叉之间格距离小于此数值,将被忽略(去除一些杂波扰动因素,最小值不小于1 + 0轴上下金死叉状态信息,与其他信号加以辅助操作。 + + :return: 信号字典 + """ + di = int(kwargs.get('di', 1)) + j = int(kwargs.get('j', 0)) + s = int(kwargs.get('s', 0)) + n = int(kwargs.get('n', 100)) + md = int(kwargs.get('md', 1)) + freq = c.freq.value + cache_key = update_macd_cache(c, **kwargs) + assert j * s == 0, "金叉死叉参数错误, j和s必须有一个为0" + + k1, k2, k3 = f"{freq}_D{di}N{n}MD{md}J{j}S{s}_MACD交叉数量V230625".split('_') + v1 = "其他" + if len(c.bars_raw) < di + n + 1: + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + bars = get_sub_elements(c.bars_raw, di=di, n=n) + dif = [x.cache[cache_key]['dif'] for x in bars] + dea = [x.cache[cache_key]['dea'] for x in bars] + num_k = cross_zero_axis(dif, dea) + dif_temp = get_sub_elements(dif, di=1, n=num_k) + dea_temp = get_sub_elements(dea, di=1, n=num_k) + cross = fast_slow_cross(dif_temp, dea_temp) + + jc, sc = cal_cross_num(cross, md) + + if dif[-1] < 0 and dea[-1] < 0: + if jc >= j and s == 0: + v1 = f'0轴下第{j}次金叉以后' + elif j == 0 and sc >= s: + v1 = f'0轴下第{s}次死叉以后' + + elif dif[-1] > 0 and dea[-1] > 0: + if jc >= j and s == 0: + v1 = f'0轴上第{j}次金叉以后' + elif j == 0 and sc >= s: + v1 = f'0轴上第{s}次死叉以后' + + return create_single_signal(k1=k1, k2=k2, k3=k3, v1=v1) + + +def check(): + from czsc.connectors import research + from czsc.traders.base import check_signals_acc + + symbols = research.get_symbols('中证500成分股') + symbol = symbols[0] + # for symbol in symbols[:10]: + bars = research.get_raw_bars(symbol, '15分钟', '20181101', '20210101', fq='前复权') + signals_config = [{'name': tas_cross_status_V230625, 'freq': '15分钟', 'di': 1, 'j': 3}] + check_signals_acc(bars, signals_config=signals_config, height='780px') # type: ignore + + +if __name__ == '__main__': + check() diff --git a/test/test_analyze.py b/test/test_analyze.py index 644b5433b..b135ccf64 100644 --- a/test/test_analyze.py +++ b/test/test_analyze.py @@ -6,7 +6,6 @@ from czsc.analyze import CZSC, RawBar, NewBar, remove_include, FX, check_fx, Direction, kline_pro from czsc.enum import Freq from collections import OrderedDict -from czsc.signals.bxt import get_s_d0_bi, get_s_three_bi cur_path = os.path.split(os.path.realpath(__file__))[0] @@ -66,25 +65,6 @@ def test_find_bi(): fxs.append(fx) -def get_user_signals(c: CZSC) -> OrderedDict: - """在 CZSC 对象上计算信号,这个是标准函数,主要用于研究。 - 实盘时可以按照自己的需要自定义计算哪些信号 - - :param c: CZSC 对象 - :return: 信号字典 - """ - s = OrderedDict({"symbol": c.symbol, "dt": c.bars_raw[-1].dt, "close": c.bars_raw[-1].close}) - # 倒0,特指未确认完成笔 - # 倒1,倒数第1笔的缩写,表示第N笔 - # 倒2,倒数第2笔的缩写,表示第N-1笔 - # 倒3,倒数第3笔的缩写,表示第N-2笔 - # 以此类推 - for i in range(1, 3): - s.update(get_s_three_bi(c, i)) - s.update(get_s_d0_bi(c)) - return s - - def test_czsc_update(): bars = read_daily() # 不计算任何信号 @@ -96,8 +76,7 @@ def test_czsc_update(): assert ubi['direction'] == Direction.Down assert ubi['high_bar'].dt < ubi['low_bar'].dt # 测试自定义信号 - c = CZSC(bars, get_signals=get_user_signals) - assert len(c.signals) == 7 + c = CZSC(bars, get_signals=None) kline = [x.__dict__ for x in c.bars_raw] bi = [{'dt': x.fx_a.dt, "bi": x.fx_a.fx} for x in c.bi_list] + \ @@ -106,17 +85,3 @@ def test_czsc_update(): file_html = "x.html" chart.render(file_html) os.remove(file_html) - - -def test_get_signals(): - - def get_test_signals(c: CZSC) -> OrderedDict: - s = OrderedDict({"symbol": c.symbol, "dt": c.bars_raw[-1].dt, "close": c.bars_raw[-1].close}) - s.update(get_s_d0_bi(c)) - return s - - bars = read_daily() - # 不计算任何信号 - c = CZSC(bars, get_signals=get_test_signals) - assert c.signals['日线_倒0笔_方向'] == '向下_任意_任意_0' - assert c.signals['日线_倒0笔_长度'] == '5到9根K线_任意_任意_0' diff --git a/test/test_trader_base.py b/test/test_trader_base.py index 0d65ddeec..a66b7fb88 100644 --- a/test/test_trader_base.py +++ b/test/test_trader_base.py @@ -83,7 +83,6 @@ def test_object_position(): cs.update_signals(bar) pos_y.update(cs.s) - assert pos_y.name == "测试C" df = pd.DataFrame(pos_y.pairs) assert df.shape == (17, 11) assert len(cs.s) == 13