refactor: 缠论引擎包化与 Web 分层(ECR-001)

将根目录引擎迁入 chanlun/ 并保留兼容 shim;拆分 TF_DF 与 web 服务;
前端模块化;strategies 改用 chanlun 导入;补充 ESS 文档与 golden 回归。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-05 18:48:20 +08:00
co-authored by Cursor
parent e2e45bc1bc
commit 74dec4e50b
160 changed files with 25576 additions and 23104 deletions
+317
View File
@@ -0,0 +1,317 @@
"""TF_DF builder mixin — 由 split_tfdf_builders 自动生成,逻辑与原 TF_DF 一致。"""
from __future__ import annotations
from datetime import timedelta
from decimal import Decimal
import numpy as np
import pandas as pd
import talib.abstract as ta
from pandas import DataFrame
from technical.util import resample_to_interval
from chanlun.core.ChanBI import ChanBI
from chanlun.core.ChanBIZS import ChanBIZS
from chanlun.core.ChanBSP import ChanBSP
from chanlun.core.ChanEnum import (
Chan_BI_DIR,
Chan_BSP_DIR,
Chan_BSP_TYPE,
Chan_FX_TYPE,
Chan_K_DIR,
Chan_KLC_FX,
Chan_KLC_STATE,
Chan_KLINE_DIR,
Chan_KLU_PATTERN,
Chan_PRICE_TREND,
Chan_SEG_DIR,
Chan_ZS_DIR,
)
from chanlun.core.ChanKLC import ChanKLC
from chanlun.core.ChanKLU import ChanKLU
from chanlun.core.ChanSBI import ChanSBI
from chanlun.core.ChanSEG import ChanSEG
from chanlun.core.ChanZS import ChanZS, ChanZS_Big
from chanlun.indicators.ChanMACD import ChanMACD
class SegBuilderMixin:
def get_seg_list(self, bi_list):
seg_list = []
up_bi_list = []
down_bi_list = []
last_up_bi = None
last_down_bi = None
last_up_sbi = None
last_down_sbi = None
last_seg = None
up_sbi_list = []
down_sbi_list = []
look_for_bottom = False
look_for_top = False
for bi in bi_list:
#print(len(up_sbi_list), len(down_sbi_list))
if len(seg_list) > 0:
# Last seg is up
if last_seg.dir == Chan_SEG_DIR.UP:
if bi.dir == Chan_BI_DIR.DOWN:
if len(down_sbi_list) > 1:
# Check down sbi inclusion
included = last_down_sbi.check_bi_included(bi)
if not included:
down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
last_down_sbi.set_next(down_sbi)
last_down_sbi.set_end_bi(last_down_bi)
down_sbi.set_pre(last_down_sbi)
down_sbi_list.append(down_sbi)
fx = last_down_sbi.check_fx()
# Found top
if fx == Chan_FX_TYPE.TOP:
if look_for_top:
seg_list[-2].set_sure(bi)
look_for_top = False
#print(bi.start_time, look_for_top, "UP 1")
# Has gap and search for bottom fx
if last_down_sbi.has_fx_gap:
look_for_bottom = True
last_seg.pre_set_end_bi(bi_list[last_down_sbi.start_bi.index - 1])
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
seg_list.append(seg)
last_seg.set_next(seg)
seg.set_pre(last_seg)
last_seg = seg
up_sbi_list = []
last_up_sbi = ChanSBI(last_up_bi, len(up_sbi_list), last_up_bi.dir)
up_sbi_list.append(last_up_sbi)
#up_sbi_list.append(last_up_sbi)
#print(last_up_bi.start_time, last_up_sbi.start_bi.start_time, "Reset up sbi list 1")
#print(bi.start_time, look_for_top, "UP 2")
# No gap end SEG
else:
if look_for_bottom:
look_for_bottom = False
last_seg.set_start_bi(last_down_sbi.start_bi)
seg_list[-2].set_end_bi(bi_list[last_down_sbi.start_bi.index - 1], bi)
up_sbi_list = []
last_up_sbi = ChanSBI(last_up_bi, len(up_sbi_list), last_up_bi.dir)
up_sbi_list.append(last_up_sbi)
last_seg.add_bi(bi)
#up_sbi_list.append(last_up_sbi)
#print(last_up_bi.start_time, last_up_sbi.start_bi.start_time, "Reset up sbi list 2")
#print(bi.start_time, look_for_top, "UP 3")
else:
last_seg.set_end_bi(bi_list[last_down_sbi.start_bi.index - 1], bi)
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
seg_list.append(seg)
last_seg.set_next(seg)
seg.set_pre(last_seg)
last_seg = seg
#print(last_down_sbi.end_bi.start_time, "Normal UP SEG", last_up_sbi.start_bi.start_time, bi.start_time)
#l_up_sbi = up_sbi_list[-1]
up_sbi_list = []
last_up_sbi = ChanSBI(last_up_bi, len(up_sbi_list), last_up_bi.dir)
up_sbi_list.append(last_up_sbi)
#up_sbi_list.append(last_up_sbi)
#print(last_up_bi.start_time, last_up_sbi.start_bi.start_time, "Reset up sbi list 3")
last_down_sbi = down_sbi
last_seg.add_bi(bi)
else:
if len(down_sbi_list) == 1:
included = last_down_sbi.check_bi_included(bi)
if not included:
down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
last_down_sbi.set_next(down_sbi)
last_down_sbi.set_end_bi(last_down_bi)
down_sbi.set_pre(last_down_sbi)
down_sbi_list.append(down_sbi)
last_down_sbi = down_sbi
#print(bi.start_time, look_for_top, "UP 4")
last_seg.add_bi(bi)
else:
last_down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
down_sbi_list.append(last_down_sbi)
last_seg.add_bi(bi)
#print(bi.start_time, look_for_top, "UP 5")
else:
if last_up_sbi:
included = last_up_sbi.check_bi_included(bi)
if not included:
up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
last_up_sbi.set_next(up_sbi)
last_up_sbi.set_end_bi(last_up_bi)
up_sbi.set_pre(last_up_sbi)
up_sbi_list.append(up_sbi)
last_up_sbi = up_sbi
#print(bi.start_time, look_for_top, "UP 6")
last_seg.add_bi(bi)
# Last seg is down
else:
if bi.dir == Chan_BI_DIR.UP:
if len(up_sbi_list) > 1:
# Check down sbi inclusion
included = last_up_sbi.check_bi_included(bi)
if not included:
up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
last_up_sbi.set_next(up_sbi)
last_up_sbi.set_end_bi(last_up_bi)
up_sbi.set_pre(last_up_sbi)
up_sbi_list.append(up_sbi)
fx = last_up_sbi.check_fx()
# Found bottom
if fx == Chan_FX_TYPE.BOTTOM:
if look_for_bottom:
seg_list[-2].set_sure(bi)
look_for_bottom = False
#print(bi.start_time, look_for_top, "DOWN 1")
# Has gap and search for bottom fx
if last_up_sbi.has_fx_gap:
look_for_top = True
last_seg.pre_set_end_bi(bi_list[last_up_sbi.start_bi.index - 1])
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP, bi)
seg_list.append(seg)
last_seg.set_next(seg)
seg.set_pre(last_seg)
last_seg = seg
down_sbi_list = []
last_down_sbi = ChanSBI(last_down_bi, len(down_sbi_list), last_down_bi.dir)
down_sbi_list.append(last_down_sbi)
#down_sbi_list.append(last_down_sbi)
#print(last_down_bi.start_time, last_down_sbi.start_bi.start_time, "Reset down sbi list 1")
#print(bi.start_time, look_for_top, "DOWN 2")
# No gap end SEG
else:
if look_for_top:
look_for_top = False
last_seg.set_start_bi(last_up_sbi.start_bi)
seg_list[-2].set_end_bi(bi_list[last_up_sbi.start_bi.index - 1], bi)
down_sbi_list = []
last_down_sbi = ChanSBI(last_down_bi, len(down_sbi_list), last_down_bi.dir)
down_sbi_list.append(last_down_sbi)
last_seg.add_bi(bi)
#down_sbi_list.append(last_down_sbi)
#print(last_down_bi.start_time, last_down_sbi.start_bi.start_time, "Reset down sbi list 2")
#print(bi.start_time, look_for_top, "DOWN 3")
else:
last_seg.set_end_bi(bi_list[last_up_sbi.start_bi.index - 1], bi)
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP, bi)
#print(last_up_sbi.start_bi.start_time)
last_seg.set_next(seg)
seg.set_pre(last_seg)
seg_list.append(seg)
last_seg = seg
#print(last_up_sbi.end_bi.start_time, "Normal DOWN SEG", last_down_sbi.start_bi.start_time, bi.start_time)
down_sbi_list = []
last_down_sbi = ChanSBI(last_down_bi, len(down_sbi_list), last_down_bi.dir)
down_sbi_list.append(last_down_sbi)
#down_sbi_list.append(last_down_sbi)
#print(last_down_bi.start_time, last_down_sbi.start_bi.start_time, "Reset down sbi list 3")
last_up_sbi = up_sbi
last_seg.add_bi(bi)
else:
if len(up_sbi_list) == 1:
#last_up_sbi = up_sbi_list[-1]
included = last_up_sbi.check_bi_included(bi)
if not included:
up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
last_up_sbi.set_next(up_sbi)
last_up_sbi.set_end_bi(last_up_bi)
up_sbi.set_pre(last_up_sbi)
up_sbi_list.append(up_sbi)
last_up_sbi = up_sbi
last_seg.add_bi(bi)
#print(bi.start_time, look_for_top, "DOWN 4")
else:
last_up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
up_sbi_list.append(last_up_sbi)
last_seg.add_bi(bi)
#print(bi.start_time, look_for_top, "DOWN 5")
else:
if last_down_sbi:
included = last_down_sbi.check_bi_included(bi)
if not included:
down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
last_down_sbi.set_next(down_sbi)
last_down_sbi.set_end_bi(last_down_bi)
down_sbi.set_pre(last_down_sbi)
down_sbi_list.append(down_sbi)
last_down_sbi = down_sbi
last_seg.add_bi(bi)
#print(bi.start_time, look_for_top, look_for_bottom, "DOWN 6")
# len(seg_list) = 0
else:
if bi.check_overlap():
if bi.dir == Chan_BI_DIR.UP:
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.UP, bi)
last_up_bi = bi
last_up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
seg_list.append(seg)
last_seg = seg
#print(bi.start_time, 'Create first UP SEG')
else:
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
last_down_bi = bi
last_down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
seg_list.append(seg)
last_seg = seg
#print(bi.start_time, 'Create first DOWN SEG')
if bi.dir == Chan_BI_DIR.UP:
last_up_bi = bi
up_bi_list.append(bi)
else:
last_down_bi = bi
down_bi_list.append(bi)
"""
if len(seg_list) > 1:
seg = seg_list[-1]
last_seg = seg_list[-2]
last_seg_bi = last_seg.bi_list[-3]
bi_index = seg.start_bi.index
for i in range(bi_index, len(bi_list) - 1):
# last seg is down
if seg.dir == Chan_SEG_DIR.UP:
if bi_list[i].dir == Chan_BI_DIR.UP:
last_seg_peak = last_seg_bi.high
if bi_list[i].high > last_seg_peak:
# The confirmed
print("Last UP seg is broken, create a new seg. 1")
seg.pre_set_end_bi(bi_list[i])
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.DOWN, bi)
seg_list.append(seg)
last_seg = seg_list[-2]
if len(last_seg.bi_list) > 3:
last_seg_bi = last_seg.bi_list[-3]
else:
if bi_list[i].dir == Chan_BI_DIR.DOWN:
last_seg_peak = last_seg_bi.low
if bi_list[i].low < last_seg_peak:
print("Last DOWN seg is broken, create a new seg. 1")
seg.pre_set_end_bi(bi_list[i])
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.UP, bi)
seg_list.append(seg)
last_seg = seg_list[-2]
if len(last_seg.bi_list) > 3:
last_seg_bi = last_seg.bi_list[-3]
else:
if len(seg_list) == 1:
last_seg = seg_list[-1]
bi_index = last_seg.bi_list[0].index
for i in range(bi_index, len(bi_list) - 1):
if i > bi_index + 2:
last_seg_peak = bi_list[i-2].high
# last seg is down
if last_seg.dir == Chan_SEG_DIR.DOWN:
if bi_list[i].dir == Chan_BI_DIR.UP:
if bi_list[i].high > last_seg_peak:
print("Last seg is broken, create a new seg. 2")
last_seg.pre_set_end_bi(bi_list[i-1])
seg = ChanSEG(bi_list[i], len(seg_list), Chan_SEG_DIR.UP, bi)
seg_list.append(seg)
last_seg = seg
last_seg_bi = bi_list[i]
break
"""
#self.cal_bi_zs(seg_list)
return seg_list