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:
@@ -0,0 +1,402 @@
|
||||
import sys
|
||||
import os
|
||||
#sys.path.append(os.path.abspath("/Users/jack/Documents/GitHub/chan.py"))
|
||||
sys.path.append(os.path.abspath("/Users/jack/Project/chan.py"))
|
||||
from Chan import CChan
|
||||
from BuySellPoint.BS_Point import CBS_Point
|
||||
from ChanConfig import CChanConfig
|
||||
from Common.CEnum import AUTYPE, DATA_SRC, KL_TYPE, DATA_FIELD, BSP_TYPE, FX_TYPE, BI_DIR, KLINE_DIR, SEG_DIR
|
||||
from KLine.KLine_Unit import CKLine_Unit
|
||||
from Common.CTime import CTime
|
||||
from Common.func_util import kltype_lt_day, str2float
|
||||
from Bi.Bi import CBi
|
||||
from typing import Dict, List
|
||||
from functools import reduce
|
||||
from pandas import DataFrame
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
|
||||
def GetColumnNameFromFieldList(fileds: str):
|
||||
_dict = {
|
||||
"time": DATA_FIELD.FIELD_TIME,
|
||||
"open": DATA_FIELD.FIELD_OPEN,
|
||||
"high": DATA_FIELD.FIELD_HIGH,
|
||||
"low": DATA_FIELD.FIELD_LOW,
|
||||
"close": DATA_FIELD.FIELD_CLOSE,
|
||||
"volume": DATA_FIELD.FIELD_VOLUME
|
||||
}
|
||||
return [_dict[x] for x in fileds.split(",")]
|
||||
class ChanPY():
|
||||
k_type = KL_TYPE.K_5M
|
||||
config = CChanConfig({
|
||||
"bi_strict": True,
|
||||
"bi_algo": "normal",
|
||||
"trigger_step": True,
|
||||
"skip_step": 0,
|
||||
"divergence_rate": float("inf"),
|
||||
"bsp2_follow_1": False,
|
||||
"bsp3_follow_1": False,
|
||||
"min_zs_cnt": 1,
|
||||
"bs1_peak": False,
|
||||
"macd_algo": "peak",
|
||||
"bs_type": '1,2,3a,1p,2s,3b',
|
||||
"print_warning": True,
|
||||
"zs_algo": "normal",
|
||||
})
|
||||
chan = CChan(
|
||||
code="BTC/USDT:USDT",
|
||||
data_src=DATA_SRC.CCXT,
|
||||
lv_list=[k_type],
|
||||
config=config,
|
||||
autype=AUTYPE.QFQ,
|
||||
)
|
||||
klu_list = []
|
||||
bsps = []
|
||||
chanIn = True
|
||||
#def __init__(self, dataframe):
|
||||
#self.klu_list = self.get_kl_data(dataframe)
|
||||
#for klu in self.klu_list:
|
||||
#self.chan.trigger_load({self.k_type: [klu]})
|
||||
def add_klu(self, klu):
|
||||
if klu:
|
||||
self.chan.trigger_load({self.k_type: [klu]})
|
||||
self.klu_list.append(klu)
|
||||
def add_klu_from_dataframe(self, dataframe):
|
||||
if len(dataframe) > len(self.klu_list) and len(dataframe) - len(self.klu_list) == 1:
|
||||
klu = self.get_last_klu(dataframe)
|
||||
self.chan.trigger_load({self.k_type: [klu]})
|
||||
self.klu_list.append(klu)
|
||||
def parse_time_column(self, inp):
|
||||
if len(inp) == 10:
|
||||
year = int(inp[:4])
|
||||
month = int(inp[5:7])
|
||||
day = int(inp[8:10])
|
||||
hour = minute = 0
|
||||
elif len(inp) == 17:
|
||||
year = int(inp[:4])
|
||||
month = int(inp[4:6])
|
||||
day = int(inp[6:8])
|
||||
hour = int(inp[8:10])
|
||||
minute = int(inp[10:12])
|
||||
elif len(inp) == 19:
|
||||
year = int(inp[:4])
|
||||
month = int(inp[5:7])
|
||||
day = int(inp[8:10])
|
||||
hour = int(inp[11:13])
|
||||
minute = int(inp[14:16])
|
||||
else:
|
||||
raise Exception(f"unknown time column from TradingView:{inp}")
|
||||
return CTime(year, month, day, hour, minute, auto=not kltype_lt_day(self.k_type))
|
||||
|
||||
def create_item_dict(self, data, column_name):
|
||||
for i in range(len(data)):
|
||||
data[i] = self.parse_time_column(data[i]) if i == 0 else str2float(data[i])
|
||||
return dict(zip(column_name, data))
|
||||
def get_last_klu(self, dataframe:DataFrame):
|
||||
fields = "time,open,high,low,close,volume"
|
||||
item = dataframe.iloc[-1]
|
||||
date = item['date']
|
||||
o = item['open']
|
||||
h = item['high']
|
||||
l = item['low']
|
||||
c = item['close']
|
||||
v = item['volume']
|
||||
#time_obj = date.fromtimestamp(date)
|
||||
time_str = date.strftime('%Y-%m-%d %H:%M:%S')
|
||||
item_data = [
|
||||
time_str,
|
||||
o,
|
||||
h,
|
||||
l,
|
||||
c,
|
||||
v
|
||||
]
|
||||
klu = CKLine_Unit(self.create_item_dict(item_data, GetColumnNameFromFieldList(fields)), autofix=True)
|
||||
klu.set_idx(len(dataframe)-1)
|
||||
return klu
|
||||
def get_kl_data(self, dataframe:DataFrame):
|
||||
fields = "time,open,high,low,close,volume"
|
||||
klu_list = []
|
||||
for i in range(0, len(dataframe)):
|
||||
item = dataframe.iloc[i]
|
||||
date = item['date']
|
||||
o = item['open']
|
||||
h = item['high']
|
||||
l = item['low']
|
||||
c = item['close']
|
||||
v = item['volume']
|
||||
#time_obj = date.fromtimestamp(date)
|
||||
time_str = date.strftime('%Y-%m-%d %H:%M:%S')
|
||||
item_data = [
|
||||
time_str,
|
||||
o,
|
||||
h,
|
||||
l,
|
||||
c,
|
||||
v
|
||||
]
|
||||
klu = CKLine_Unit(self.create_item_dict(item_data, GetColumnNameFromFieldList(fields)), autofix=True)
|
||||
klu.set_idx(i)
|
||||
klu_list.append(klu)
|
||||
return klu_list
|
||||
def get_bsp_type(self, bsp_type, is_buy):
|
||||
if is_buy:
|
||||
if bsp_type == BSP_TYPE.T1:
|
||||
return 1
|
||||
if bsp_type == BSP_TYPE.T1P:
|
||||
return 2
|
||||
if bsp_type == BSP_TYPE.T2:
|
||||
return 3
|
||||
if bsp_type == BSP_TYPE.T2S:
|
||||
return 4
|
||||
if bsp_type == BSP_TYPE.T3A:
|
||||
return 5
|
||||
if bsp_type == BSP_TYPE.T3B:
|
||||
return 6
|
||||
else:
|
||||
if bsp_type == BSP_TYPE.T1:
|
||||
return -1
|
||||
if bsp_type == BSP_TYPE.T1P:
|
||||
return -2
|
||||
if bsp_type == BSP_TYPE.T2:
|
||||
return -3
|
||||
if bsp_type == BSP_TYPE.T2S:
|
||||
return -4
|
||||
if bsp_type == BSP_TYPE.T3A:
|
||||
return -5
|
||||
if bsp_type == BSP_TYPE.T3B:
|
||||
return -6
|
||||
def get_bsps(self, dataframe:DataFrame):
|
||||
fields = "time,open,high,low,close,volume"
|
||||
bsps = []
|
||||
updown = []
|
||||
bi_sure = []
|
||||
if self.chanIn:
|
||||
kl_data = self.get_kl_data(dataframe)
|
||||
bsp_list = []
|
||||
bsp_list_pre_len = 0
|
||||
last_bsp_value = 0
|
||||
last_updown = -1
|
||||
bi_list_pre_len = 0
|
||||
pre_bi = None
|
||||
zs_list_pre_len = 0
|
||||
pre_zs = None
|
||||
for klu in kl_data: # 获取单根K线
|
||||
self.chan.trigger_load({self.k_type: [klu]}) # 喂给CChan新增k线
|
||||
self.last_kline = klu
|
||||
bsp_list = self.chan.get_bsp()
|
||||
kl_datas = self.chan.kl_datas[self.k_type]
|
||||
bi_list = kl_datas.bi_list
|
||||
lst = kl_datas.lst
|
||||
if len(bsp_list) > 0:
|
||||
last_bsp = bsp_list[-1]
|
||||
#print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, lst[-2].fx, bi_list[-1].dir, bi_list[-1].is_sure,klu.close)
|
||||
if bsp_list_pre_len > len(bsp_list):
|
||||
if abs(last_bsp_value) == 1 or abs(last_bsp_value) == 2:
|
||||
bsps.append(1)
|
||||
#print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, 98)
|
||||
else:
|
||||
bsps.append(99)
|
||||
else:
|
||||
if bsp_list_pre_len == len(bsp_list):
|
||||
if klu.idx == last_bsp.klu.idx:
|
||||
last_bsp_value = self.get_bsp_type(last_bsp.type[0], last_bsp.is_buy)
|
||||
bsps.append(last_bsp_value)
|
||||
#print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value)
|
||||
else:
|
||||
bsps.append(0)
|
||||
else:
|
||||
last_bsp_value = self.get_bsp_type(last_bsp.type[0], last_bsp.is_buy)
|
||||
bsps.append(last_bsp_value)
|
||||
#print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value)
|
||||
else:
|
||||
bsps.append(0)
|
||||
bsp_list_pre_len = len(bsp_list)
|
||||
|
||||
#Check zs -----------------------------------
|
||||
zs_list = kl_datas.zs_list
|
||||
if len(zs_list) > 0:
|
||||
zs = zs_list[-1]
|
||||
#if zs_list_pre_len > len(zs_list):
|
||||
#print("No zs", zs.begin.time)
|
||||
#if len(zs_list) > zs_list_pre_len:
|
||||
#print(zs.begin.time, zs.end.time, zs.end.idx, zs.high, zs.low, zs.peak_high, zs.peak_low)
|
||||
zs_list_pre_len = len(zs_list)
|
||||
pre_zs = zs
|
||||
|
||||
#Check Bi -----------------------------------
|
||||
if len(bi_list) > 0:
|
||||
last_bi = bi_list[-1]
|
||||
if len(bi_list) == 1:
|
||||
if last_bi.dir == BI_DIR.UP:
|
||||
updown.append(1)
|
||||
last_updown = 1
|
||||
else:
|
||||
updown.append(-1)
|
||||
last_updown = -1
|
||||
else:
|
||||
if last_updown == 1:
|
||||
if last_bi.dir == BI_DIR.UP:
|
||||
updown.append(0)
|
||||
else:
|
||||
updown.append(-1)
|
||||
last_updown = -1
|
||||
else:
|
||||
if last_bi.dir == BI_DIR.DOWN:
|
||||
updown.append(0)
|
||||
else:
|
||||
updown.append(1)
|
||||
last_updown = 1
|
||||
else:
|
||||
updown.append(0)
|
||||
bi_list = kl_datas.bi_list
|
||||
if len(bi_list) > 0:
|
||||
last_bi = bi_list[-1]
|
||||
#if bi_list_pre_len > len(bi_list):
|
||||
#print("Bi ", klu.time, pre_bi.idx, pre_bi.is_sure, bi_list[-1].idx, bi_list[-1].is_sure)
|
||||
if last_bi.is_sure:
|
||||
bi_sure.append(1)
|
||||
#print(klu.time, last_bi.is_sure)
|
||||
else:
|
||||
bi_sure.append(0)
|
||||
pre_bi = bi_list[-1]
|
||||
bi_list_pre_len = len(bi_list)
|
||||
else:
|
||||
bi_sure.append(0)
|
||||
#if bsps[-1] != 0 or updown[-1] != 0:
|
||||
#print(klu.time, bsps[-1], updown[-1], bi_list[-1].is_sure)
|
||||
self.chanIn = False
|
||||
else:
|
||||
klu = self.get_last_klu(dataframe)
|
||||
if self.last_kline.time < klu.time:
|
||||
self.chan.trigger_load({self.k_type: [klu]}) # 喂给CChan新增k线
|
||||
self.last_kline = klu
|
||||
for index in range(0, len(bsps)):
|
||||
if not (abs(bsps[index]) == 1 or abs(bsps[index]) == 2):
|
||||
bsps[index] = 0
|
||||
else:
|
||||
if bsps[index] == 2:
|
||||
bsps[index] = 1
|
||||
else:
|
||||
if bsps[index] == -2:
|
||||
bsps[index] = -1
|
||||
else:
|
||||
bsps[index] = 0
|
||||
#print(bsps)
|
||||
#print(updown)
|
||||
kl_datas = self.chan.kl_datas[self.k_type]
|
||||
#for zs in kl_datas.zs_list:
|
||||
#print(zs.begin.time, zs.end.time)
|
||||
return bsps, updown, bi_sure
|
||||
|
||||
def get_bsp_state1(self, dataframe:DataFrame):
|
||||
fields = "time,open,high,low,close,volume"
|
||||
bsps = []
|
||||
if self.chanIn:
|
||||
kl_data = self.get_kl_data(dataframe)
|
||||
self.chan.trigger_load({self.k_type: kl_data})
|
||||
bsp_list = self.chan.get_bsp()
|
||||
bsp_index = 0
|
||||
for klu in kl_data:
|
||||
if bsp_index >= len(bsp_list):
|
||||
bsp_index = len(bsp_list) - 1
|
||||
bsp = bsp_list[bsp_index]
|
||||
if klu.idx == bsp.klu.idx:
|
||||
bsp_type = self.get_bsp_type(bsp.type[0], bsp.is_buy)
|
||||
if abs(bsp_type) == 1 or abs(bsp_type) == 10:
|
||||
bsps.append(1)
|
||||
else:
|
||||
bsps.append(0)
|
||||
bsp_index = bsp_index + 1
|
||||
else:
|
||||
bsps.append(0)
|
||||
self.chanIn = False
|
||||
else:
|
||||
klu = CKLine_Unit(self.create_item_dict(self.get_last_item_data(dataframe), GetColumnNameFromFieldList(fields)), autofix=True)
|
||||
if self.last_kline.time < klu.time:
|
||||
self.chan.trigger_load({self.k_type: [klu]}) # 喂给CChan新增k线
|
||||
self.last_kline = klu
|
||||
return bsps
|
||||
def get_bsp_state(self, dataframe:DataFrame):
|
||||
fields = "time,open,high,low,close,volume"
|
||||
if self.chanIn:
|
||||
kl_data = self.get_kl_data(dataframe)
|
||||
bsp_list = []
|
||||
bsp_list_pre_len = 0
|
||||
last_bsp_value = 0
|
||||
last_bsp_index = 0
|
||||
for klu in kl_data: # 获取单根K线
|
||||
self.chan.trigger_load({self.k_type: [klu]}) # 喂给CChan新增k线
|
||||
self.last_kline = klu
|
||||
bsp_list = self.chan.get_bsp()
|
||||
kl_datas = self.chan.kl_datas[self.k_type]
|
||||
bi_list = kl_datas.bi_list
|
||||
lst = kl_datas.lst
|
||||
if len(bsp_list) > 0:
|
||||
last_bsp = bsp_list[-1]
|
||||
#print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, lst[-2].fx, bi_list[-1].dir, bi_list[-1].is_sure,klu.close)
|
||||
if bsp_list_pre_len > len(bsp_list):
|
||||
if abs(last_bsp_value) == 1:
|
||||
self.bsps.append(1)
|
||||
#print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, 98)
|
||||
else:
|
||||
self.bsps.append(99)
|
||||
else:
|
||||
if bsp_list_pre_len == len(bsp_list):
|
||||
if klu.idx == last_bsp.klu.idx:
|
||||
if last_bsp.klu.idx - last_bsp_index > 3:
|
||||
last_bsp_value = self.get_bsp_type(last_bsp.type[0], last_bsp.is_buy)
|
||||
self.bsps.append(last_bsp_value)
|
||||
else:
|
||||
self.bsps.append(0)
|
||||
last_bsp_index = last_bsp.klu.idx
|
||||
#if abs(last_bsp_value) == 1 or abs(last_bsp_value) == 2:
|
||||
#print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, "Knonw")
|
||||
else:
|
||||
self.bsps.append(0)
|
||||
else:
|
||||
if klu.idx == last_bsp.klu.idx:
|
||||
if last_bsp.klu.idx - last_bsp_index > 3:
|
||||
last_bsp_value = self.get_bsp_type(last_bsp.type[0], last_bsp.is_buy)
|
||||
self.bsps.append(last_bsp_value)
|
||||
else:
|
||||
self.bsps.append(0)
|
||||
last_bsp_index = last_bsp.klu.idx
|
||||
#if abs(last_bsp_value) == 1 or abs(last_bsp_value) == 2:
|
||||
#print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, "Knonw")
|
||||
else:
|
||||
self.bsps.append(0)
|
||||
else:
|
||||
self.bsps.append(0)
|
||||
bsp_list_pre_len = len(bsp_list)
|
||||
self.chanIn = False
|
||||
else:
|
||||
klu = self.get_last_klu(dataframe)
|
||||
if self.last_kline.time < klu.time:
|
||||
self.chan.trigger_load({self.k_type: [klu]}) # 喂给CChan新增k线
|
||||
self.last_kline = klu
|
||||
bsp_list = self.chan.get_bsp()
|
||||
last_bsp = bsp_list[-1]
|
||||
if last_bsp.klu.idx == klu.idx:
|
||||
self.bsps.append(self.get_bsp_type(last_bsp.type[0], last_bsp.is_buy))
|
||||
else:
|
||||
self.bsps.append(0)
|
||||
for index in range(0, len(self.bsps)):
|
||||
if not (abs(self.bsps[index]) == 1 or abs(self.bsps[index]) == 2):
|
||||
self.bsps[index] = 0
|
||||
else:
|
||||
if self.bsps[index] == 2:
|
||||
self.bsps[index] = 10
|
||||
else:
|
||||
if self.bsps[index] == -2:
|
||||
self.bsps[index] = -10
|
||||
else:
|
||||
if self.bsps[index] == 1:
|
||||
self.bsps[index] = 1
|
||||
else:
|
||||
if self.bsps[index] == -1:
|
||||
self.bsps[index] = -1
|
||||
else:
|
||||
self.bsps[index] = 0
|
||||
return self.bsps
|
||||
|
||||
Reference in New Issue
Block a user