██████╗██╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ██╗ ██╗
██╔════╝██║ ██║██╔══██╗████╗ ██║ ██╔══██╗╚██╗ ██╔╝
██║ ███████║███████║██╔██╗ ██║ ██████╔╝ ╚████╔╝
██║ ██╔══██║██╔══██║██║╚██╗██║ ██╔═══╝ ╚██╔╝
╚██████╗██║ ██║██║ ██║██║ ╚████║██╗██║ ██║
╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝
- 为什么会有这个项目:我想自动化买卖股票 → 需要一套比较好的,容易程序化描述股票走势的理论 → 找到缠论 → 实现后顺便开源部分
- 为啥会开源:借助群众的力量,帮我优化找bug,顺便回馈群众~
- 开源部分包含什么:
- 缠论元素(笔,线段,中枢,买卖点)的计算
- 开源部分不包含什么:
- 策略:因为定制化很强
- 交易引擎:因为不通用
- 机器学习相关(特征/模型)
- 不包含策略,不包含策略,不包含策略(再次强调)
- 关于开发/新增特性:
- 不要找我开发,我不是外包
- 如果特性对我有用,我会立马开发
- 欢迎提交PR
- 什么东西不会发到开源版里
- 非通用的,定制化的东西
- 我解释/答疑起来会很麻烦的东西
- 策略
- 当然典型的策略的用法会放进demo里面
- 也欢迎PR demo策略
- 免责声明:
- 本人没读完原著
- 本人不完全理解缠论交易思想
- 实现细节可能不完全符合你所想
- 可能实现的是我理解的缠论
- README是完整版的描述,可能与开源版本不符
- 讨论组:Telegram
- 为什么用TG:因为后进来的朋友可以看到以前的讨论(啥时候墙内也有类似的,我再开个讨论的)
- 没法科学上网,没有tg账号:有问题可以邮件联系我
- 欢迎star,满足一下我的虚荣心
- 如有打赏,万分感谢!
- 视频介绍:20230909交流会录屏;
python3 main.py
即可体验
一言以蔽之:
- 根据给定范围的K线,算出当前静态范围下的缠论元素
- 框架对于各种元素都提供了不同的参数配置
- 基于上述能力,提供增加K线时的增量更新缠论元素
- 支持开发自己的数据源接入这个计算(参见数据接入速成班)
- 支持让你取出算好的元素组装你自己的策略(参见取出缠论元素组织策略)
框架支持计算指定时间范围内K线所组成的缠论元素,也支持逐根K线投喂更新已有的缠论元素;
任何时刻(比如每新增一根K线),都可以通过框架CChan这个类获得当下(到目前给定的K线为止)下所有缠论的元素(各种元素获取参见下文);
而对于最重要的买卖点,可以理解成每次计算出来的都是当下的“形态学”买卖点;只不过有些买卖点不是已经确定的,可能在将来消失;
所以随着K线的新增,你可能会发现某个位置的买点突然消失的,或者一个买卖点的位置一直在变动;
举个例子:
- 在某下降线段尾部单边下跌的一笔中(即每根新增K线的低点都比上一根更低),那么每新增一根K线,这根K线都可能在“当下”被标识为一类买卖点,而上一根K线的标识被取消;
- 如果某根K线之后开始反弹,那么在最低点这根K线被跌破之前,新投喂K线后这根最低点的K线会一直被持续标记为一买
- 直到形态上认为这个一买不再成立
框架会对所有元素都标明是否已经确定(或者说已经完成),可以参见各个类的is_sure属性;
is_sure=False在画图结果中表现为虚线(虚笔,虚段,虚线中枢);
对于is_sure=True的元素,在后续K线新增的过程中,这些元素不会再被变更,也不可能会被标记为is_sure=False;
而且is_sure=False的元素只可能出现在所有K线的头部或者尾部;
根据上面所言,框架会一直给你提示当下这一“帧”里面的缠论元素分别是什么样子;
随着K线的投喂,可能很多K线都曾经在某个时候被标记为“当下”的买卖点(而且这些买卖点大部分is_sure=False);
对于那些不确定是否将来最终会成立的买卖点,你需要自己开发策略判断这些买卖点是否最终会成立,或者当下是否需要交易(比如考虑买卖点的背驰度,中枢数之类的);
其中需要特别说明的是,由于走势的多义性,而框架在任何一个时刻只能给出一种缠论元素的计算结果,所以必然存在某些将来成立的买卖点,在K线新增的全过程中,一次都不曾被标记为买卖点过;(当然破解方法也比较简单,每根K线进来后跑两组不同的缠论计算参数)
而我比较偷懒,把这块策略判断的实现企图通过机器学习模型来解决;
依赖最低版本为python3.11;由于本项目是高度计算密集型,鉴于python3.11发布且运算速度大幅提升,实测相比于python 3.8.5计算时间缩短约16%,故后续开发均基于python3.11;
某些系统画图窗口可能会在程序运行完后自动关闭,常用解法:
- 在jupyter notebook之类应用上运行
- 在代码最后加上一句
input()
README.md文件主要为个人完整版撰写,对于开源版,主要查考此 quick_guide.md 即可;
另外,画图/CChanConfig等配置,框架里面一些定义解释可以参考README,其他的看 README 意义不大。
首先,完整版和开源版的区别参见写在前面章节,差异部分主要为个人策略和交易引擎定制开发的代码,不具备可泛化性,各位拿了也没啥用!
另外机器学习模型相关的代码和一些特征,也由于并非所有知识产权都归属于我,所以是没法对外提供的。 关于这部分的思路,B站发的交流会视频基本已经介绍过了,感兴趣的可以去看看。
其次,开源的部分提供了所有可以用到的基座和演示代码,已经足够大家自行用于组装自己的策略。
最后,不要再找我买完整版代码了,内部自用策略,而且此项目开发时间已超过一千小时,按照时薪计算也不是大家可以负担得起的。
大家所说的信号,应该就是框架计算出来的买卖点(bsp);
因为框架计算得永远都是“当前帧”下的缠论元素,随着K线的增加,原有的买卖点可能会被证明不成立(比如跌破一买),那么原本位置会被标记为买卖点的K线将不再被标记为bsp;
框架提供的默认画图能力其实是对接的matplotlib这个库,如果你希望在别的画图引擎上实现(比如某些web页面,或者ploty这种可交互的库),欢迎PR代码~
框架提供了一个画图元素类PlotMeta,里面提供了各种元素在绘图时的坐标信息(比如已经完成了K线时间到X轴坐标的转化了),方便自行对接到不同的画图引擎上;
问:使用 main.py 里面画动态图特别吃内存,32G都不够用,而且会越来越卡; 答:动图是一个纯实验性功能,内存泄露是已知问题,只是作为演示,多年没优化过了,欢迎各位感兴趣的提PR进行优化~
原因:框架里面很多缠论元素类都有一个next/pre成员指向了上一个/下一个元素,python导出时可能会遇到递归导致溢出问题; 解决方法:加一行配置,增加系统递归深度
sys.setrecursionlimit(0x100000)
常见报错类似:kline time err, cur=2024/01/01 00:05, last=2024/01/01
处理方法:如果数据级别为天级别以下(不包含天级别),且K线时间可能出现0点0分数据,那么在数据源类中返回的K线时间CTime中,把auto设置为False即可;
根本原因:一般日线K的时间都不包含小时信息,或者为0点0分,为了处理天级别+分钟级别K线时间对齐问题,auto 设置为 True(这是默认设置)则会自动将天的小时分钟信息设置为23:59,从而保证当天的分钟线为对应日期的次级别K线;但是对于数字货币类的分钟K线,如果也出现0点0分,则会有误判;
线段逻辑简要说明可以参考线段一章,如果不太能理解划段的结果,可以在画图开关里面打开plot_eigen
开关绘制出特征序列辅助分析。
如果还有问题,只能联系作者了。。
如需作者排查,尽量提供可以直接运行的主函数文件和数据文件;
很多用户使用,其实不需要细看整个代码是怎么实现了,假装相信框架没BUG,通过直接提取框架计算好的缠论元素来组装策略(具体内部元素结构的设计参见B站视频);
但是即便如此,以下三部分建议先读README文件了解下;
- 了解CChan这个类的输入参数及格式
- 了解CChanConfig接受的配置:默认配置基本可用,关注自己想了解的即可
- 如果涉及画图,了解画图的两个配置
- plot_config:需要画什么
- plot_para:画的每种元素单独配置
然后通过画图,可能找到一些不合理的地方(笔段中枢买卖点算错或漏算之类的),再具体去看实际实现细节或者反馈给作者。
这些README里面都有细讲。。
CChanConfig里面提供了很多的配置,其中很多人最容易被影响到自己计算结果的主要是这几个,它们的含义最好再仔细阅读一下readme相关解释:
- bi_strict:是否只用严格笔,默认为 Ture,其中这里的严格笔只考虑顶底分形之间相隔几个合并K线
- bi_fx_check:检查笔顶底分形是否成立的方法
- bi_end_is_peak: 笔的尾部是否是整笔中最低/最高, 默认为 True
- divergence_rate:1类买卖点背驰比例,即离开中枢的笔的 MACD 指标相对于进入中枢的笔,默认为 0.9
- min_zs_cnt:1类买卖点至少要经历几个中枢,默认为 1
- max_bs2_rate:2类买卖点那一笔回撤最大比例,默认为 0.618
- 注:如果是 1.0,那么相当于允许回测到1类买卖点的位置
- zs_algo: 中枢算法,涉及到中枢是否允许跨段
- CChan这个类里面有个变量
kl_datas
,这是一个字典,键是 KL_TYPE(即级别,具体取值参见Common/CEnum),值是 CKLine_List 类; - CKLine_List是所有元素取值的入口,关键成员是:
- lst: List[CKLine]:所有的合并K线
- bi_list:CBiList 类,管理所有的笔
- seg_list:CSegListComm 类,管理所有的线段
- zs_list:CZSList 类,管理所有的中枢
- bs_point_lst:CBSPointList 类,管理所有的买卖点
- 其余大部分人可能不关注的
- segseg_list:线段的线段
- segzs_list:线段中枢
- seg_bs_point_lst:线段买卖点
成员包括:
- idx:第几根
- CKLine.lst可以取到所有的单根K线变量 CKLine_Unit
- fx:FX_TYPE,分形类型
- dir:方向
- pre,next:前一/后一合并K线
- high:高点
- low:低点
成员包括:
- idx:第几根
- time
- low/close/open/high
- klc:获取所属的合并K线(即CKLine)变量
- sub_kl_list: List[CKLine_Unit] 获取次级别K线列表,范围在这根K线范围内的
- sup_kl: CKLine_Unit 父级别K线(CKLine_Unit)
成员包含:
- bi_list: List[CBi],每个元素是一笔
这个类的实现基本可以不用关注,除非你想实现自己的画笔算法
成员包含:
- idx:第几笔
- dir:方向,BI_DIR类
- is_sure:是否是确定的笔
- klc_lst:List[CKLine],该笔全部合并K线
- seg_idx:所属线段id
- parent_seg:CSeg 所属线段
- next/pre:前一/后一笔
可以关注一下这里面实现的一些关键函数:
- _high/_low
- get_first_klu/get_last_klu:获取笔第一根/最后一根K线
- get_begin_klu/get_end_klu:获取起止K线
- 注意一下:和get_first_klu不一样的地方在于,比如下降笔,这个获取的是第一个合并K线里面high最大的K线,而不是第一个合并K线里面的第一根K线;
- get_begin_val/get_end_val:获取笔起止K线的价格
- 比如下降笔get_begin_val就是get_begin_klu的高点
- lst: List[CSeg] 每一个成员是一根线段
这个类的实现基本可以不用关注,除非你想实现自己的画段算法,参照提供的几个demo,实现这个类的子类即可;
成员包括:
- idx
- start_bi:起始笔
- end_bi:终止笔
- is_sure:是否已确定
- dir:方向,BI_DIR类
- zs_lst: List[CZS] 线段内中枢列表
- pre/next:前一/后一线段
- bi_list: List[CBi] 线段内笔的列表
关注的一些关键函数和CBi里面一样,都已实现同名函数,如:
- _high/_low
- get_first_klu/get_last_klu
- get_begin_klu/get_end_klu
- get_begin_val/get_end_val
- zs_lst: List[CZS] 中枢列表
成员包括:
- begin/end:起止K线CKLine_Unit
- begin_bi/end_bi:中枢内部的第一笔/最后一笔
- bi_in:进中枢的那一笔(在中枢外面)
- bi_out:出中枢的那一笔(在中枢外面,不一定存在)
- low/high:中枢的高低点
- peak_low/peak/high:中枢内所有笔的最高/最低值
- bi_lst:中枢内笔列表
- sub_zs_lst:子中枢(如果出现过中枢合并的话)
- lst:List[CBS_Point] 所有的买卖点
成员包括:
- bi:所属的笔(买卖点一定在某一笔末尾)
- Klu:所在K线
- is_buy:True为买点,False为卖点
- type:List[BSP_TYPE] 买卖点类别,是个数组,比如2,3类买卖点是同一个
参考DataAPI/BaoStockAPI.py
,实现一个类继承自CCommonStockApi
,接受输入参数为:
- code
- k_type: KL_TYPE类型
- begin_date/end_date:能不能为None,应该是什么格式自行决定,会通过CChan类的初始化参数传进来;
并在该类里面实现一个关键方法:
get_kl_data(self)
:该方法为一个生成器,yield 返回每一根K线信息 CKLine_Unit
,K线类可以通过CKLine_Unit(item_dict)
来实例化;
| 当然你也可以直接返回一个有序的数组,每个元素为CKLine_Unit
,只不过性能可能会差一些;
class C_YOUR_OWN_DATA_CLS(CCommonStockApi):
def __init__(self, code, k_type=KL_TYPE.K_DAY, begin_date=None, end_date=None, autype=AUTYPE.QFQ):
super(C_YOUR_OWN_DATA_CLS, self).__init__(code, k_type, begin_date, end_date, autype)
def get_kl_data(self) -> Iterable[CKLine_Unit]:
...
yield CKLine_Unit(item_dict)
item_dict为一个字典:
- 包含时间:必须是框架实现的CTime类
- 开收高低:必须要有
- 换手率,成交额,成交量:这三个可以没有
{
DATA_FIELD.FIELD_TIME: time, # 必须是CTime
DATA_FIELD.FIELD_OPEN: float(_open), # 必填
DATA_FIELD.FIELD_CLOSE: float(_close), # 必填
DATA_FIELD.FIELD_LOW: float(_low), # 必填
DATA_FIELD.FIELD_HIGH: float(_high), # 必填
DATA_FIELD.FIELD_VOLUME: float(volume), # 可选
DATA_FIELD.FIELD_TURNOVER: float(amount), # 可选
DATA_FIELD.FIELD_TURNRATE: float(turn), # 可选
}
构造CTime(year, month, day, hour, minute)
实例即可;
如果数据来源于其他服务,需要有初始化和结束的操作,那么需要额外重载实现两个类函数:
@classmethod
def do_init(cls):
...
@classmethod
def do_close(cls):
...
比如baostock即需要实现login,logout操作,futu数据源需要初始化和关闭ctx操作;
最简单的方法就是把实现的类放在./DataAPI/
目录下,然后CChan
的data_src
参数配置为custom:文件名.类名
即可;
框架默认提供的线段画法是基于特征序列那一套的,如果不了解,请搜索引擎搜索:“缠论 线段 特征序列”;
首先需要说明的是,线段这部分为整个框架代码里面最复杂,最绕,最难以维护的部分(主要是本人的锅,开发能力不够),如果不是作为挑战,或者有重要特性需要实现,没啥事不要尝试去挑战阅读。。
关于划段算法,简单来说,就是上线线段里面的下降笔(或者下降线段里面的上升笔)当做K线(也叫特征序列),进行合并后找顶底分析,主要区分两种情况(以上升线段为例):
- 特征序列形成的顶分型第一二元素之间有跳空,那么需要在后面找到反向且合法的特征序列底分型,这个上升线段才会被判断为确认(即框架里面CSeg的is_sure=True,或者说画图为实线);
- 如果没有跳空,那么顶分型出现时即被判断为确认。
当然,里面还有更多的细节,这里不展开赘述。
当然,开源代码里面也提供了一些其他的线段的实现算法(参见 SegListDYH.py, SegListDef.py 等),大家也可以自行开发;
线段的确定时刻实际上是具有很强的滞后性的,所以框架在计算“当前帧”的缠论元素的时候,会产生一些推断的线段(即框架里面CSeg的is_sure=False,或者说画图为虚线),这些虚段在后面新增K线产生的过程中,可能随时有可能会变化。
虚段可能出现在线段的开头和结尾,主要是框架认为当下的K线信息不足以确定一些线段,或者说特征序列的顶底分型尚未完成;
开头的虚段参考意义不大,但是建议大家如果给出的K线算出来的所有线段都是虚的,那么不要进行任何回测或者在上面交易,至少在出现一条实段后在进行策略分析。
具体demo参见strategy_demo.py
原理就是:打开CChanConfig中的trigger_step
开关,那么CChan初始化的时候就不会做任何计算;
而手动调用CChan.step_load(),才会启动计算;
- 多少根K线就返回多少次
- 这个函数就是一个生成器:每喂一根K线后,就会计算当前K线位置的静态元素,返回当前的CChan类,可以用上文描述的方法来获取需要的元素;
- 每一帧的计算不是完全重算的,只重新计算不确定的部分,故计算性能还行
回测啥的就自行组装了~
实盘的时候需要在获取到K线之后触发缠论计算,可以使用CChan.trigger_load
来触发计算;
其中该函数的输入参数格式为:Dict[KL_TYPE, List[CKLine_Unit]]
具体使用case可以参考strategy_demo.py
这种场景一般是:
- 实盘中使用了1分钟,5分钟两个级别来构建策略
- 每过一分钟,当前5分钟的K线由于数据源并没提供,是由1分钟K线合成的
- 或者也有可能券商接口提供了当前5分钟的K线,但是由于没有完成,下一分钟这5分钟的K线可能会有变化
- 把这两个级别的K线用CChan.trigger_load()喂进去
问题是当你把当前的5分钟K线喂进去后,下一分钟框架无法回退掉/更新这个5分钟K线,而这个5分钟K线可能相较于前一分钟时所得是有变化的;
解决方法:参考strategy_demo.py 原理是:
- 当某一个时刻所有级别的K线都在将来不需要变化时(比如每5分钟结束时),把当前CChan保存下来(不管是用深度拷贝保存成一个临时的变量或者序列化到本地文件)成一个快照
- 之后每根最小级别K线产生时,均重新加载这个快照重新计算当前的所有级别
以实现RSI指标为例,只需要三步:
首先你需要实现一个计算类(见Math/RSI.py):
- 类可以接收指标计算的参数(如周期等)
- 类对外提供一个方法:
- 接收需要的K线信息(不要直接接收CKLine_Unit,不然后续可能会有循环引用问题)
- 返回指标值,或者包含所有指标结果的一个类(比如MACD类需要返回快线慢线,红绿柱等信息)
- tips:
- 出于性能考虑,指标的计算尽量优化成增量计算,而不是当前K线的指标需要用前面所有K线的数据来算
- 也就是新的一根K线+部分之前K线计算的中间结果,就可以算出当前K线的指标
- 本框架所有开源指标均遵循此原则
- 当然你自己开发的不在意计算效率的话,倒无所谓。。
然后在CChanConfig
的GetMetricModel方法修改res变量的注解(增加你的指标类),然后append一个上面实现的指标计算实例(可以参考rsi实现方法在)CChanConfig里面配置指标计算的参数;
def GetMetricModel(self):
res: List[CMACD | CTrendModel | BollModel | CDemarkEngine | RSI] = []
# 其他已有指标
# balabala....
res.append(RSI(self.rsi_cycle))
return res
最后在CKLine_Unit
的set_metric方法最后增加一行,调用你实现的指标类的计算方法:
def set_metric(self, metric_model_lst: list) -> None:
for metric_model in metric_model_lst:
# balabala...
elif isinstance(metric_model, RSI):
self.rsi = metric_model.add(self.close)
至此,你就可以通过CKLine_Unit.rsi取得你的指标,参与后续计算了;
如果你想你的指标支持绘制,PlotDriver.py的DrawElement方法增加一行:
def DrawElement(self, plot_config: Dict[str, bool], meta: CChanPlotMeta, ax: Axes, lv, plot_para, ax_macd: Optional[Axes], x_limits):
# balabala...
if plot_config.get("plot_rsi", False):
self.draw_rsi(meta, ax.twinx(), **plot_para.get('rsi', {}))
然后实现draw_rsi方法即可:
def draw_rsi(self, meta: CChanPlotMeta, ax, color='b'):
data = [klu.rsi for klu in meta.klu_iter()]
x_begin, x_end = int(ax.get_xlim()[0]), int(ax.get_xlim()[1])
ax.plot(range(x_begin, x_end), data[x_begin: x_end], c=color)
缠论有个好处在于,基础元素的定义大多非常精确,包括买卖点,很容易用程序化语言来实现;
带来的好处就是,策略可以接入机器学习,让机器学习来判断当下状态,框架也可以较方便的接入这些能力;
机器学习相关演示参见项目machinelearning
分支strategy_demo5.py,里面演示了包括:
- 如何在bsp各个地方增加特征计算
- 如何在bsp里面增加通用特征
- 策略如何与bsp特征联动
- 如何在策略生效时新增策略特征
- 如何绘图分析label是否正确
- 如何训练 & 预测
机器学习分支仅仅在主分支基础上,在 CChan 内部增加了一些特征计算演示的 demo,和机器学习特有的一些 demo策略。
主分支的所有特性都会合并进机器学习分支,只是额外的特征计算会消耗计算性能,考虑到不是所有人都需要机器学习特征计算,所以专门开了个分支;
不介意性能的话,无脑用机器学习分支亦可。
上面提到框架支持两种计算模式:
- 直接给定一批K线,计算出所有缠论元素
- 在已有(可以为空)K线基础上,逐根K线投喂更新得到“当下为止”的缠论元素
两种方法实现上略有区别,前者计算速度快于后者;
但是可以保证(或者说目标是要保证):如果给定同样范围的K线,那么两种模式最终计算状态应该是完全一致的!(如果不一致,说明有bug,联系作者排查)
如果你使用CChan.trigger_load
来触发计算,那么每次触发后框架只拿到当前位置的 K 线数据来进行计算缠论元素,你只要在下一次trigger_load之前完成策略的判断,那么你的策略模型在没有 Bug 的情况下,是拿不到未来信息的。
如果你觉得这个项目对你有帮助或有启发,可以请我喝一杯。。额。。咖啡和牛奶以外的东西,毕竟我喝这两种会拉肚子。。