Initial commit
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
from Combiner.KLine_Combiner import CKLine_Combiner
|
||||
from Common.CEnum import FX_CHECK_METHOD, FX_TYPE, KLINE_DIR
|
||||
from Common.ChanException import CChanException, ErrCode
|
||||
from Common.func_util import has_overlap
|
||||
from KLine.KLine_Unit import CKLine_Unit
|
||||
|
||||
|
||||
# 合并后的K线
|
||||
class CKLine(CKLine_Combiner[CKLine_Unit]):
|
||||
def __init__(self, kl_unit: CKLine_Unit, idx, _dir=KLINE_DIR.UP):
|
||||
super(CKLine, self).__init__(kl_unit, _dir)
|
||||
self.idx: int = idx
|
||||
self.kl_type = kl_unit.kl_type
|
||||
kl_unit.set_klc(self)
|
||||
|
||||
def __str__(self):
|
||||
fx_token = ""
|
||||
if self.fx == FX_TYPE.TOP:
|
||||
fx_token = "^"
|
||||
elif self.fx == FX_TYPE.BOTTOM:
|
||||
fx_token = "_"
|
||||
return f"{self.idx}th{fx_token}:{self.time_begin}~{self.time_end}({self.kl_type}|{len(self.lst)}) low={self.low} high={self.high}"
|
||||
|
||||
def GetSubKLC(self):
|
||||
# 可能会出现相邻的两个KLC的子KLC会有重复
|
||||
# 因为子KLU合并时正好跨过了父KLC的结束时间边界
|
||||
last_klc = None
|
||||
for klu in self.lst:
|
||||
for sub_klu in klu.get_children():
|
||||
if sub_klu.klc != last_klc:
|
||||
last_klc = sub_klu.klc
|
||||
yield sub_klu.klc
|
||||
|
||||
def get_klu_max_high(self) -> float:
|
||||
return max(x.high for x in self.lst)
|
||||
|
||||
def get_klu_min_low(self) -> float:
|
||||
return min(x.low for x in self.lst)
|
||||
|
||||
def has_gap_with_next(self) -> bool:
|
||||
assert self.next is not None
|
||||
# 相同也算重叠,也就是没有gap
|
||||
return not has_overlap(self.get_klu_min_low(), self.get_klu_max_high(), self.next.get_klu_min_low(), self.next.get_klu_max_high(), equal=True)
|
||||
|
||||
def check_fx_valid(self, item2: "CKLine", method, for_virtual=False):
|
||||
# for_virtual: 虚笔时使用
|
||||
assert self.next is not None and item2.pre is not None
|
||||
assert self.pre is not None
|
||||
assert item2.idx > self.idx
|
||||
if self.fx == FX_TYPE.TOP:
|
||||
assert for_virtual or item2.fx == FX_TYPE.BOTTOM
|
||||
if for_virtual and item2.dir != KLINE_DIR.DOWN:
|
||||
return False
|
||||
if method == FX_CHECK_METHOD.HALF: # 检测前两KLC
|
||||
item2_high = max([item2.pre.high, item2.high])
|
||||
self_low = min([self.low, self.next.low])
|
||||
elif method == FX_CHECK_METHOD.LOSS: # 只检测顶底分形KLC
|
||||
item2_high = item2.high
|
||||
self_low = self.low
|
||||
elif method in (FX_CHECK_METHOD.STRICT, FX_CHECK_METHOD.TOTALLY):
|
||||
if for_virtual:
|
||||
item2_high = max([item2.pre.high, item2.high])
|
||||
else:
|
||||
assert item2.next is not None
|
||||
item2_high = max([item2.pre.high, item2.high, item2.next.high])
|
||||
self_low = min([self.pre.low, self.low, self.next.low])
|
||||
else:
|
||||
raise CChanException("bi_fx_check config error!", ErrCode.CONFIG_ERROR)
|
||||
if method == FX_CHECK_METHOD.TOTALLY:
|
||||
return self.low > item2_high
|
||||
else:
|
||||
return self.high > item2_high and item2.low < self_low
|
||||
elif self.fx == FX_TYPE.BOTTOM:
|
||||
assert for_virtual or item2.fx == FX_TYPE.TOP
|
||||
if for_virtual and item2.dir != KLINE_DIR.UP:
|
||||
return False
|
||||
if method == FX_CHECK_METHOD.HALF:
|
||||
item2_low = min([item2.pre.low, item2.low])
|
||||
cur_high = max([self.high, self.next.high])
|
||||
elif method == FX_CHECK_METHOD.LOSS:
|
||||
item2_low = item2.low
|
||||
cur_high = self.high
|
||||
elif method in (FX_CHECK_METHOD.STRICT, FX_CHECK_METHOD.TOTALLY):
|
||||
if for_virtual:
|
||||
item2_low = min([item2.pre.low, item2.low])
|
||||
else:
|
||||
assert item2.next is not None
|
||||
item2_low = min([item2.pre.low, item2.low, item2.next.low])
|
||||
cur_high = max([self.pre.high, self.high, self.next.high])
|
||||
else:
|
||||
raise CChanException("bi_fx_check config error!", ErrCode.CONFIG_ERROR)
|
||||
if method == FX_CHECK_METHOD.TOTALLY:
|
||||
return self.high < item2_low
|
||||
else:
|
||||
return self.low < item2_low and item2.high > cur_high
|
||||
else:
|
||||
raise CChanException("only top/bottom fx can check_valid_top_button", ErrCode.BI_ERR)
|
||||
@@ -0,0 +1,191 @@
|
||||
import copy
|
||||
from typing import List, Union, overload
|
||||
|
||||
from Bi.Bi import CBi
|
||||
from Bi.BiList import CBiList
|
||||
from BuySellPoint.BSPointList import CBSPointList
|
||||
from ChanConfig import CChanConfig
|
||||
from Common.CEnum import KLINE_DIR, SEG_TYPE
|
||||
from Common.ChanException import CChanException, ErrCode
|
||||
from Seg.Seg import CSeg
|
||||
from Seg.SegConfig import CSegConfig
|
||||
from Seg.SegListComm import CSegListComm
|
||||
from ZS.ZSList import CZSList
|
||||
|
||||
from .KLine import CKLine
|
||||
from .KLine_Unit import CKLine_Unit
|
||||
|
||||
|
||||
def get_seglist_instance(seg_config: CSegConfig, lv) -> CSegListComm:
|
||||
if seg_config.seg_algo == "chan":
|
||||
from Seg.SegListChan import CSegListChan
|
||||
return CSegListChan(seg_config, lv)
|
||||
elif seg_config.seg_algo == "1+1":
|
||||
print(f'Please avoid using seg_algo={seg_config.seg_algo} as it is deprecated and no longer maintained.')
|
||||
from Seg.SegListDYH import CSegListDYH
|
||||
return CSegListDYH(seg_config, lv)
|
||||
elif seg_config.seg_algo == "break":
|
||||
print(f'Please avoid using seg_algo={seg_config.seg_algo} as it is deprecated and no longer maintained.')
|
||||
from Seg.SegListDef import CSegListDef
|
||||
return CSegListDef(seg_config, lv)
|
||||
else:
|
||||
raise CChanException(f"unsupport seg algoright:{seg_config.seg_algo}", ErrCode.PARA_ERROR)
|
||||
|
||||
|
||||
class CKLine_List:
|
||||
def __init__(self, kl_type, conf: CChanConfig):
|
||||
self.kl_type = kl_type
|
||||
self.config = conf
|
||||
self.lst: List[CKLine] = [] # K线列表,可递归 元素KLine类型
|
||||
self.bi_list = CBiList(bi_conf=conf.bi_conf)
|
||||
self.seg_list: CSegListComm[CBi] = get_seglist_instance(seg_config=conf.seg_conf, lv=SEG_TYPE.BI)
|
||||
self.segseg_list: CSegListComm[CSeg[CBi]] = get_seglist_instance(seg_config=conf.seg_conf, lv=SEG_TYPE.SEG)
|
||||
|
||||
self.zs_list = CZSList(zs_config=conf.zs_conf)
|
||||
self.segzs_list = CZSList(zs_config=conf.zs_conf)
|
||||
|
||||
self.bs_point_lst = CBSPointList[CBi, CBiList](bs_point_config=conf.bs_point_conf)
|
||||
self.seg_bs_point_lst = CBSPointList[CSeg, CSegListComm](bs_point_config=conf.seg_bs_point_conf)
|
||||
|
||||
self.metric_model_lst = conf.GetMetricModel()
|
||||
|
||||
self.step_calculation = self.need_cal_step_by_step()
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
new_obj = CKLine_List(self.kl_type, self.config)
|
||||
memo[id(self)] = new_obj
|
||||
for klc in self.lst:
|
||||
klus_new = []
|
||||
for klu in klc.lst:
|
||||
new_klu = copy.deepcopy(klu, memo)
|
||||
memo[id(klu)] = new_klu
|
||||
if klu.pre is not None:
|
||||
new_klu.set_pre_klu(memo[id(klu.pre)])
|
||||
klus_new.append(new_klu)
|
||||
|
||||
new_klc = CKLine(klus_new[0], idx=klc.idx, _dir=klc.dir)
|
||||
new_klc.set_fx(klc.fx)
|
||||
new_klc.kl_type = klc.kl_type
|
||||
for idx, klu in enumerate(klus_new):
|
||||
klu.set_klc(new_klc)
|
||||
if idx != 0:
|
||||
new_klc.add(klu)
|
||||
memo[id(klc)] = new_klc
|
||||
if new_obj.lst:
|
||||
new_obj.lst[-1].set_next(new_klc)
|
||||
new_klc.set_pre(new_obj.lst[-1])
|
||||
new_obj.lst.append(new_klc)
|
||||
new_obj.bi_list = copy.deepcopy(self.bi_list, memo)
|
||||
new_obj.seg_list = copy.deepcopy(self.seg_list, memo)
|
||||
new_obj.segseg_list = copy.deepcopy(self.segseg_list, memo)
|
||||
new_obj.zs_list = copy.deepcopy(self.zs_list, memo)
|
||||
new_obj.segzs_list = copy.deepcopy(self.segzs_list, memo)
|
||||
new_obj.bs_point_lst = copy.deepcopy(self.bs_point_lst, memo)
|
||||
new_obj.metric_model_lst = copy.deepcopy(self.metric_model_lst, memo)
|
||||
new_obj.step_calculation = copy.deepcopy(self.step_calculation, memo)
|
||||
new_obj.seg_bs_point_lst = copy.deepcopy(self.seg_bs_point_lst, memo)
|
||||
return new_obj
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: int) -> CKLine: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: slice) -> List[CKLine]: ...
|
||||
|
||||
def __getitem__(self, index: Union[slice, int]) -> Union[List[CKLine], CKLine]:
|
||||
return self.lst[index]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.lst)
|
||||
|
||||
def cal_seg_and_zs(self):
|
||||
if not self.step_calculation:
|
||||
self.bi_list.try_add_virtual_bi(self.lst[-1])
|
||||
cal_seg(self.bi_list, self.seg_list)
|
||||
self.zs_list.cal_bi_zs(self.bi_list, self.seg_list)
|
||||
update_zs_in_seg(self.bi_list, self.seg_list, self.zs_list) # 计算seg的zs_lst,以及中枢的bi_in, bi_out
|
||||
|
||||
cal_seg(self.seg_list, self.segseg_list)
|
||||
self.segzs_list.cal_bi_zs(self.seg_list, self.segseg_list)
|
||||
update_zs_in_seg(self.seg_list, self.segseg_list, self.segzs_list) # 计算segseg的zs_lst,以及中枢的bi_in, bi_out
|
||||
|
||||
# 计算买卖点
|
||||
self.seg_bs_point_lst.cal(self.seg_list, self.segseg_list) # 线段线段买卖点
|
||||
self.bs_point_lst.cal(self.bi_list, self.seg_list) # 再算笔买卖点
|
||||
|
||||
def need_cal_step_by_step(self):
|
||||
return self.config.trigger_step
|
||||
|
||||
def add_single_klu(self, klu: CKLine_Unit):
|
||||
klu.set_metric(self.metric_model_lst)
|
||||
if len(self.lst) == 0:
|
||||
self.lst.append(CKLine(klu, idx=0))
|
||||
else:
|
||||
_dir = self.lst[-1].try_add(klu)
|
||||
if _dir != KLINE_DIR.COMBINE: # 不需要合并K线
|
||||
self.lst.append(CKLine(klu, idx=len(self.lst), _dir=_dir))
|
||||
if len(self.lst) >= 3:
|
||||
self.lst[-2].update_fx(self.lst[-3], self.lst[-1])
|
||||
if self.bi_list.update_bi(self.lst[-2], self.lst[-1], self.step_calculation) and self.step_calculation:
|
||||
self.cal_seg_and_zs()
|
||||
elif self.step_calculation and self.bi_list.try_add_virtual_bi(self.lst[-1], need_del_end=True): # 这里的必要性参见issue#175
|
||||
self.cal_seg_and_zs()
|
||||
|
||||
def klu_iter(self, klc_begin_idx=0):
|
||||
for klc in self.lst[klc_begin_idx:]:
|
||||
yield from klc.lst
|
||||
|
||||
|
||||
def cal_seg(bi_list, seg_list: CSegListComm):
|
||||
seg_list.update(bi_list)
|
||||
|
||||
sure_seg_cnt = 0
|
||||
if len(seg_list) == 0:
|
||||
for bi in bi_list:
|
||||
bi.set_seg_idx(0)
|
||||
return
|
||||
begin_seg: CSeg = seg_list[-1]
|
||||
for seg in seg_list[::-1]:
|
||||
if seg.is_sure:
|
||||
sure_seg_cnt += 1
|
||||
else:
|
||||
sure_seg_cnt = 0
|
||||
begin_seg = seg
|
||||
if sure_seg_cnt > 2:
|
||||
break
|
||||
|
||||
cur_seg: CSeg = seg_list[-1]
|
||||
for bi in bi_list[::-1]:
|
||||
if bi.seg_idx is not None and bi.idx < begin_seg.start_bi.idx:
|
||||
break
|
||||
if bi.idx > cur_seg.end_bi.idx:
|
||||
bi.set_seg_idx(cur_seg.idx+1)
|
||||
continue
|
||||
if bi.idx < cur_seg.start_bi.idx:
|
||||
assert cur_seg.pre
|
||||
cur_seg = cur_seg.pre
|
||||
bi.set_seg_idx(cur_seg.idx)
|
||||
|
||||
|
||||
def update_zs_in_seg(bi_list, seg_list, zs_list):
|
||||
sure_seg_cnt = 0
|
||||
for seg in seg_list[::-1]:
|
||||
if seg.ele_inside_is_sure:
|
||||
break
|
||||
if seg.is_sure:
|
||||
sure_seg_cnt += 1
|
||||
seg.clear_zs_lst()
|
||||
for zs in zs_list[::-1]:
|
||||
if zs.end.idx < seg.start_bi.get_begin_klu().idx:
|
||||
break
|
||||
if zs.is_inside(seg):
|
||||
seg.add_zs(zs)
|
||||
assert zs.begin_bi.idx > 0
|
||||
zs.set_bi_in(bi_list[zs.begin_bi.idx-1])
|
||||
if zs.end_bi.idx+1 < len(bi_list):
|
||||
zs.set_bi_out(bi_list[zs.end_bi.idx+1])
|
||||
zs.set_bi_lst(list(bi_list[zs.begin_bi.idx:zs.end_bi.idx+1]))
|
||||
|
||||
if sure_seg_cnt > 2:
|
||||
if not seg.ele_inside_is_sure:
|
||||
seg.ele_inside_is_sure = True
|
||||
@@ -0,0 +1,154 @@
|
||||
import copy
|
||||
from typing import Dict, Optional
|
||||
|
||||
from Common.CEnum import DATA_FIELD, TRADE_INFO_LST, TREND_TYPE
|
||||
from Common.ChanException import CChanException, ErrCode
|
||||
from Common.CTime import CTime
|
||||
from Math.BOLL import BOLL_Metric, BollModel
|
||||
from Math.Demark import CDemarkEngine, CDemarkIndex
|
||||
from Math.KDJ import KDJ
|
||||
from Math.MACD import CMACD, CMACD_item
|
||||
from Math.RSI import RSI
|
||||
from Math.TrendModel import CTrendModel
|
||||
|
||||
from .TradeInfo import CTradeInfo
|
||||
|
||||
|
||||
class CKLine_Unit:
|
||||
def __init__(self, kl_dict, autofix=False):
|
||||
# _time, _close, _open, _high, _low, _extra_info={}
|
||||
self.kl_type = None
|
||||
self.time: CTime = kl_dict[DATA_FIELD.FIELD_TIME]
|
||||
self.close = kl_dict[DATA_FIELD.FIELD_CLOSE]
|
||||
self.open = kl_dict[DATA_FIELD.FIELD_OPEN]
|
||||
self.high = kl_dict[DATA_FIELD.FIELD_HIGH]
|
||||
self.low = kl_dict[DATA_FIELD.FIELD_LOW]
|
||||
|
||||
self.check(autofix)
|
||||
|
||||
self.trade_info = CTradeInfo(kl_dict)
|
||||
|
||||
self.demark: CDemarkIndex = CDemarkIndex()
|
||||
|
||||
self.sub_kl_list = [] # 次级别KLU列表
|
||||
self.sup_kl: Optional[CKLine_Unit] = None # 指向更高级别KLU
|
||||
|
||||
from KLine.KLine import CKLine
|
||||
self.__klc: Optional[CKLine] = None # 指向KLine
|
||||
|
||||
# self.macd: Optional[CMACD_item] = None
|
||||
# self.boll: Optional[BOLL_Metric] = None
|
||||
self.trend: Dict[TREND_TYPE, Dict[int, float]] = {} # int -> float
|
||||
|
||||
self.limit_flag = 0 # 0:普通 -1:跌停,1:涨停
|
||||
self.pre: Optional[CKLine_Unit] = None
|
||||
self.next: Optional[CKLine_Unit] = None
|
||||
|
||||
self.set_idx(-1)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
_dict = {
|
||||
DATA_FIELD.FIELD_TIME: self.time,
|
||||
DATA_FIELD.FIELD_CLOSE: self.close,
|
||||
DATA_FIELD.FIELD_OPEN: self.open,
|
||||
DATA_FIELD.FIELD_HIGH: self.high,
|
||||
DATA_FIELD.FIELD_LOW: self.low,
|
||||
}
|
||||
for metric in TRADE_INFO_LST:
|
||||
if metric in self.trade_info.metric:
|
||||
_dict[metric] = self.trade_info.metric[metric]
|
||||
obj = CKLine_Unit(_dict)
|
||||
obj.demark = copy.deepcopy(self.demark, memo)
|
||||
obj.trend = copy.deepcopy(self.trend, memo)
|
||||
obj.limit_flag = self.limit_flag
|
||||
obj.macd = copy.deepcopy(self.macd, memo)
|
||||
obj.boll = copy.deepcopy(self.boll, memo)
|
||||
if hasattr(self, "rsi"):
|
||||
obj.rsi = copy.deepcopy(self.rsi, memo)
|
||||
if hasattr(self, "kdj"):
|
||||
obj.kdj = copy.deepcopy(self.kdj, memo)
|
||||
obj.set_idx(self.idx)
|
||||
memo[id(self)] = obj
|
||||
return obj
|
||||
|
||||
@property
|
||||
def klc(self):
|
||||
assert self.__klc is not None
|
||||
return self.__klc
|
||||
|
||||
def set_klc(self, klc):
|
||||
self.__klc = klc
|
||||
|
||||
@property
|
||||
def idx(self):
|
||||
return self.__idx
|
||||
|
||||
def set_idx(self, idx):
|
||||
self.__idx: int = idx
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.idx}:{self.time}/{self.kl_type} open={self.open} close={self.close} high={self.high} low={self.low} {self.trade_info}"
|
||||
|
||||
def check(self, autofix=False):
|
||||
if self.low > min([self.low, self.open, self.high, self.close]):
|
||||
if autofix:
|
||||
self.low = min([self.low, self.open, self.high, self.close])
|
||||
else:
|
||||
raise CChanException(f"{self.time} low price={self.low} is not min of [low={self.low}, open={self.open}, high={self.high}, close={self.close}]", ErrCode.KL_DATA_INVALID)
|
||||
if self.high < max([self.low, self.open, self.high, self.close]):
|
||||
if autofix:
|
||||
self.high = max([self.low, self.open, self.high, self.close])
|
||||
else:
|
||||
raise CChanException(f"{self.time} high price={self.high} is not max of [low={self.low}, open={self.open}, high={self.high}, close={self.close}]", ErrCode.KL_DATA_INVALID)
|
||||
|
||||
def add_children(self, child):
|
||||
self.sub_kl_list.append(child)
|
||||
|
||||
def set_parent(self, parent: 'CKLine_Unit'):
|
||||
self.sup_kl = parent
|
||||
|
||||
def get_children(self):
|
||||
yield from self.sub_kl_list
|
||||
|
||||
def _low(self):
|
||||
return self.low
|
||||
|
||||
def _high(self):
|
||||
return self.high
|
||||
|
||||
def set_metric(self, metric_model_lst: list) -> None:
|
||||
for metric_model in metric_model_lst:
|
||||
if isinstance(metric_model, CMACD):
|
||||
self.macd: CMACD_item = metric_model.add(self.close)
|
||||
elif isinstance(metric_model, CTrendModel):
|
||||
if metric_model.type not in self.trend:
|
||||
self.trend[metric_model.type] = {}
|
||||
self.trend[metric_model.type][metric_model.T] = metric_model.add(self.close)
|
||||
elif isinstance(metric_model, BollModel):
|
||||
self.boll: BOLL_Metric = metric_model.add(self.close)
|
||||
elif isinstance(metric_model, CDemarkEngine):
|
||||
self.demark = metric_model.update(idx=self.idx, close=self.close, high=self.high, low=self.low)
|
||||
elif isinstance(metric_model, RSI):
|
||||
self.rsi = metric_model.add(self.close)
|
||||
elif isinstance(metric_model, KDJ):
|
||||
self.kdj = metric_model.add(self.high, self.low, self.close)
|
||||
|
||||
def get_parent_klc(self):
|
||||
assert self.sup_kl is not None
|
||||
return self.sup_kl.klc
|
||||
|
||||
def include_sub_lv_time(self, sub_lv_t: str) -> bool:
|
||||
if self.time.to_str() == sub_lv_t:
|
||||
return True
|
||||
for sub_klu in self.sub_kl_list:
|
||||
if sub_klu.time.to_str() == sub_lv_t:
|
||||
return True
|
||||
if sub_klu.include_sub_lv_time(sub_lv_t):
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_pre_klu(self, pre_klu: Optional['CKLine_Unit']):
|
||||
if pre_klu is None:
|
||||
return
|
||||
pre_klu.next = self
|
||||
self.pre = pre_klu
|
||||
@@ -0,0 +1,13 @@
|
||||
from typing import Dict, Optional
|
||||
|
||||
from Common.CEnum import TRADE_INFO_LST
|
||||
|
||||
|
||||
class CTradeInfo:
|
||||
def __init__(self, info: Dict[str, float]):
|
||||
self.metric: Dict[str, Optional[float]] = {}
|
||||
for metric_name in TRADE_INFO_LST:
|
||||
self.metric[metric_name] = info.get(metric_name)
|
||||
|
||||
def __str__(self):
|
||||
return " ".join([f"{metric_name}:{value}" for metric_name, value in self.metric.items()])
|
||||
Reference in New Issue
Block a user