Initial commit
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
from typing import Self
|
||||
|
||||
from Bi.Bi import CBi
|
||||
from Combiner.KLine_Combiner import CKLine_Combiner
|
||||
from Common.CEnum import BI_DIR, FX_TYPE
|
||||
|
||||
|
||||
class CEigen(CKLine_Combiner[CBi]):
|
||||
def __init__(self, bi, _dir):
|
||||
super(CEigen, self).__init__(bi, _dir)
|
||||
self.gap = False
|
||||
|
||||
def update_fx(self, _pre: Self, _next: Self, exclude_included=False, allow_top_equal=None):
|
||||
super(CEigen, self).update_fx(_pre, _next, exclude_included, allow_top_equal)
|
||||
if (self.fx == FX_TYPE.TOP and _pre.high < self.low) or \
|
||||
(self.fx == FX_TYPE.BOTTOM and _pre.low > self.high):
|
||||
self.gap = True
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.lst[0].idx}~{self.lst[-1].idx} gap={self.gap} fx={self.fx}"
|
||||
|
||||
def GetPeakBiIdx(self):
|
||||
assert self.fx != FX_TYPE.UNKNOWN
|
||||
bi_dir = self.lst[0].dir
|
||||
if bi_dir == BI_DIR.UP: # 下降线段
|
||||
return self.get_peak_klu(is_high=False).idx-1
|
||||
else:
|
||||
return self.get_peak_klu(is_high=True).idx-1
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from Bi.Bi import CBi
|
||||
from Bi.BiList import CBiList
|
||||
from Common.CEnum import BI_DIR, FX_TYPE, KLINE_DIR, SEG_TYPE
|
||||
from Common.ChanException import CChanException, ErrCode
|
||||
from Common.func_util import revert_bi_dir
|
||||
|
||||
from .Eigen import CEigen
|
||||
|
||||
|
||||
class CEigenFX:
|
||||
def __init__(self, _dir: BI_DIR, exclude_included=True, lv=SEG_TYPE.BI):
|
||||
self.lv = lv
|
||||
self.dir = _dir # 线段方向
|
||||
self.ele: List[Optional[CEigen]] = [None, None, None]
|
||||
self.lst: List[CBi] = []
|
||||
self.exclude_included = exclude_included
|
||||
self.kl_dir = KLINE_DIR.UP if _dir == BI_DIR.UP else KLINE_DIR.DOWN
|
||||
self.last_evidence_bi: Optional[CBi] = None
|
||||
|
||||
def treat_first_ele(self, bi: CBi) -> bool:
|
||||
self.ele[0] = CEigen(bi, self.kl_dir)
|
||||
return False
|
||||
|
||||
def treat_second_ele(self, bi: CBi) -> bool:
|
||||
assert self.ele[0] is not None
|
||||
combine_dir = self.ele[0].try_add(bi, exclude_included=self.exclude_included)
|
||||
if combine_dir != KLINE_DIR.COMBINE: # 不能合并
|
||||
self.ele[1] = CEigen(bi, self.kl_dir)
|
||||
if (self.is_up() and self.ele[1].high < self.ele[0].high) or \
|
||||
(self.is_down() and self.ele[1].low > self.ele[0].low): # 前两元素不可能成为分形
|
||||
return self.reset()
|
||||
return False
|
||||
|
||||
def treat_third_ele(self, bi: CBi) -> bool:
|
||||
assert self.ele[0] is not None
|
||||
assert self.ele[1] is not None
|
||||
self.last_evidence_bi = bi
|
||||
allow_top_equal = (1 if bi.is_down() else -1) if self.exclude_included else None
|
||||
combine_dir = self.ele[1].try_add(bi, allow_top_equal=allow_top_equal)
|
||||
if combine_dir == KLINE_DIR.COMBINE:
|
||||
return False
|
||||
self.ele[2] = CEigen(bi, combine_dir)
|
||||
if not self.actual_break():
|
||||
return self.reset()
|
||||
self.ele[1].update_fx(self.ele[0], self.ele[2], exclude_included=self.exclude_included, allow_top_equal=allow_top_equal) # type: ignore
|
||||
fx = self.ele[1].fx
|
||||
is_fx = (self.is_up() and fx == FX_TYPE.TOP) or (self.is_down() and fx == FX_TYPE.BOTTOM)
|
||||
return True if is_fx else self.reset()
|
||||
|
||||
def add(self, bi: CBi) -> bool: # 返回是否出现分形
|
||||
assert bi.dir != self.dir
|
||||
self.lst.append(bi)
|
||||
if self.ele[0] is None: # 第一元素
|
||||
return self.treat_first_ele(bi)
|
||||
elif self.ele[1] is None: # 第二元素
|
||||
return self.treat_second_ele(bi)
|
||||
elif self.ele[2] is None: # 第三元素
|
||||
return self.treat_third_ele(bi)
|
||||
else:
|
||||
raise CChanException(f"特征序列3个都找齐了还没处理!! 当前笔:{bi.idx},当前:{str(self)}", ErrCode.SEG_EIGEN_ERR)
|
||||
|
||||
def reset(self):
|
||||
bi_tmp_list = list(self.lst[1:])
|
||||
if self.exclude_included:
|
||||
self.clear()
|
||||
for bi in bi_tmp_list:
|
||||
if self.add(bi):
|
||||
return True
|
||||
else:
|
||||
assert self.ele[1] is not None
|
||||
ele2_begin_idx = self.ele[1].lst[0].idx
|
||||
self.ele[0], self.ele[1], self.ele[2] = self.ele[1], self.ele[2], None
|
||||
self.lst = [bi for bi in bi_tmp_list if bi.idx >= ele2_begin_idx] # 从第二元素开始
|
||||
|
||||
return False
|
||||
|
||||
def can_be_end(self, bi_lst: CBiList):
|
||||
assert self.ele[1] is not None
|
||||
if self.ele[1].gap:
|
||||
assert self.ele[0] is not None
|
||||
end_bi_idx = self.GetPeakBiIdx()
|
||||
thred_value = bi_lst[end_bi_idx].get_end_val()
|
||||
break_thred = self.ele[0].low if self.is_up() else self.ele[0].high
|
||||
return self.find_revert_fx(bi_lst, end_bi_idx+2, thred_value, break_thred)
|
||||
else:
|
||||
return True
|
||||
|
||||
def is_down(self):
|
||||
return self.dir == BI_DIR.DOWN
|
||||
|
||||
def is_up(self):
|
||||
return self.dir == BI_DIR.UP
|
||||
|
||||
def GetPeakBiIdx(self):
|
||||
assert self.ele[1] is not None
|
||||
return self.ele[1].GetPeakBiIdx()
|
||||
|
||||
def all_bi_is_sure(self):
|
||||
assert self.last_evidence_bi is not None
|
||||
return next((False for bi in self.lst if not bi.is_sure), self.last_evidence_bi.is_sure)
|
||||
|
||||
def clear(self):
|
||||
self.ele = [None, None, None]
|
||||
self.lst = []
|
||||
|
||||
def __str__(self):
|
||||
_t = [f"{[] if ele is None else ','.join([str(b.idx) for b in ele.lst])}" for ele in self.ele]
|
||||
return " | ".join(_t)
|
||||
|
||||
def actual_break(self):
|
||||
if not self.exclude_included:
|
||||
return True
|
||||
assert self.ele[2] and self.ele[1]
|
||||
if (self.is_up() and self.ele[2].low < self.ele[1][-1]._low()) or \
|
||||
(self.is_down() and self.ele[2].high > self.ele[1][-1]._high()): # 防止第二元素因为合并导致后面没有实际突破
|
||||
return True
|
||||
assert len(self.ele[2]) == 1
|
||||
ele2_bi = self.ele[2][0]
|
||||
if ele2_bi.next and ele2_bi.next.next:
|
||||
if ele2_bi.is_down() and ele2_bi.next.next._low() < ele2_bi._low():
|
||||
self.last_evidence_bi = ele2_bi.next.next
|
||||
return True
|
||||
elif ele2_bi.is_up() and ele2_bi.next.next._high() > ele2_bi._high():
|
||||
self.last_evidence_bi = ele2_bi.next.next
|
||||
return True
|
||||
return False
|
||||
|
||||
def find_revert_fx(self, bi_list: CBiList, begin_idx: int, thred_value: float, break_thred: float):
|
||||
COMMON_COMBINE = True # 是否用普通分形合并规则处理
|
||||
# 如果返回None,表示找到最后了
|
||||
first_bi_dir = bi_list[begin_idx].dir # down则是要找顶分型
|
||||
egien_fx = CEigenFX(revert_bi_dir(first_bi_dir), exclude_included=not COMMON_COMBINE, lv=self.lv) # 顶分型的话要找上升线段
|
||||
for bi in bi_list[begin_idx::2]:
|
||||
if egien_fx.add(bi):
|
||||
if COMMON_COMBINE:
|
||||
return True
|
||||
|
||||
while True:
|
||||
_test = egien_fx.can_be_end(bi_list)
|
||||
if _test in [True, None]:
|
||||
self.last_evidence_bi = bi
|
||||
return _test
|
||||
elif not egien_fx.reset():
|
||||
break
|
||||
if (bi.is_down() and bi._low() < thred_value) or (bi.is_up() and bi._high() > thred_value):
|
||||
return False
|
||||
return None
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
from typing import Generic, List, Optional, Self, TypeVar
|
||||
|
||||
from Bi.Bi import CBi
|
||||
from Common.CEnum import BI_DIR, MACD_ALGO, TREND_LINE_SIDE
|
||||
from Common.ChanException import CChanException, ErrCode
|
||||
from KLine.KLine_Unit import CKLine_Unit
|
||||
from Math.TrendLine import CTrendLine
|
||||
|
||||
from .EigenFX import CEigenFX
|
||||
|
||||
LINE_TYPE = TypeVar('LINE_TYPE', CBi, "CSeg")
|
||||
|
||||
|
||||
class CSeg(Generic[LINE_TYPE]):
|
||||
def __init__(self, idx: int, start_bi: LINE_TYPE, end_bi: LINE_TYPE, is_sure=True, seg_dir=None, reason="normal"):
|
||||
assert start_bi.idx == 0 or start_bi.dir == end_bi.dir or not is_sure, f"{start_bi.idx} {end_bi.idx} {start_bi.dir} {end_bi.dir}"
|
||||
self.idx = idx
|
||||
self.start_bi = start_bi
|
||||
self.end_bi = end_bi
|
||||
self.is_sure = is_sure
|
||||
self.dir = end_bi.dir if seg_dir is None else seg_dir
|
||||
|
||||
from ZS.ZS import CZS
|
||||
self.zs_lst: List[CZS[LINE_TYPE]] = []
|
||||
|
||||
self.eigen_fx: Optional[CEigenFX] = None
|
||||
self.seg_idx = None # 线段的线段用
|
||||
self.parent_seg: Optional[CSeg] = None # 在哪个线段里面
|
||||
self.pre: Optional[Self] = None
|
||||
self.next: Optional[Self] = None
|
||||
|
||||
from BuySellPoint.BS_Point import CBS_Point
|
||||
self.bsp: Optional[CBS_Point] = None # 尾部是不是买卖点
|
||||
|
||||
self.bi_list: List[LINE_TYPE] = [] # 仅通过self.update_bi_list来更新
|
||||
self.reason = reason
|
||||
self.support_trend_line = None
|
||||
self.resistance_trend_line = None
|
||||
if end_bi.idx - start_bi.idx < 2:
|
||||
self.is_sure = False
|
||||
self.check()
|
||||
|
||||
self.ele_inside_is_sure = False
|
||||
|
||||
def set_seg_idx(self, idx):
|
||||
self.seg_idx = idx
|
||||
|
||||
def check(self):
|
||||
if not self.is_sure:
|
||||
return
|
||||
if self.is_down():
|
||||
if self.start_bi.get_begin_val() < self.end_bi.get_end_val():
|
||||
raise CChanException(f"下降线段起始点应该高于结束点! idx={self.idx}", ErrCode.SEG_END_VALUE_ERR)
|
||||
elif self.start_bi.get_begin_val() > self.end_bi.get_end_val():
|
||||
raise CChanException(f"上升线段起始点应该低于结束点! idx={self.idx}", ErrCode.SEG_END_VALUE_ERR)
|
||||
if self.end_bi.idx - self.start_bi.idx < 2:
|
||||
raise CChanException(f"线段({self.start_bi.idx}-{self.end_bi.idx})长度不能小于2! idx={self.idx}", ErrCode.SEG_LEN_ERR)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.start_bi.idx}->{self.end_bi.idx}: {self.dir} {self.is_sure}"
|
||||
|
||||
def add_zs(self, zs):
|
||||
self.zs_lst = [zs] + self.zs_lst # 因为中枢是反序加入的
|
||||
|
||||
def cal_klu_slope(self):
|
||||
assert self.end_bi.idx >= self.start_bi.idx
|
||||
return (self.get_end_val()-self.get_begin_val())/(self.get_end_klu().idx-self.get_begin_klu().idx)/self.get_begin_val()
|
||||
|
||||
def cal_amp(self):
|
||||
return (self.get_end_val()-self.get_begin_val())/self.get_begin_val()
|
||||
|
||||
def cal_bi_cnt(self):
|
||||
return self.end_bi.idx-self.start_bi.idx+1
|
||||
|
||||
def clear_zs_lst(self):
|
||||
self.zs_lst = []
|
||||
|
||||
def _low(self):
|
||||
return self.end_bi.get_end_klu().low if self.is_down() else self.start_bi.get_begin_klu().low
|
||||
|
||||
def _high(self):
|
||||
return self.end_bi.get_end_klu().high if self.is_up() else self.start_bi.get_begin_klu().high
|
||||
|
||||
def is_down(self):
|
||||
return self.dir == BI_DIR.DOWN
|
||||
|
||||
def is_up(self):
|
||||
return self.dir == BI_DIR.UP
|
||||
|
||||
def get_end_val(self):
|
||||
return self.end_bi.get_end_val()
|
||||
|
||||
def get_begin_val(self):
|
||||
return self.start_bi.get_begin_val()
|
||||
|
||||
def amp(self):
|
||||
return abs(self.get_end_val() - self.get_begin_val())
|
||||
|
||||
def get_end_klu(self) -> CKLine_Unit:
|
||||
return self.end_bi.get_end_klu()
|
||||
|
||||
def get_begin_klu(self) -> CKLine_Unit:
|
||||
return self.start_bi.get_begin_klu()
|
||||
|
||||
def get_klu_cnt(self):
|
||||
return self.get_end_klu().idx - self.get_begin_klu().idx + 1
|
||||
|
||||
def cal_macd_metric(self, macd_algo, is_reverse):
|
||||
if macd_algo == MACD_ALGO.SLOPE:
|
||||
return self.Cal_MACD_slope()
|
||||
elif macd_algo == MACD_ALGO.AMP:
|
||||
return self.Cal_MACD_amp()
|
||||
else:
|
||||
raise CChanException(f"unsupport macd_algo={macd_algo} of Seg, should be one of slope/amp", ErrCode.PARA_ERROR)
|
||||
|
||||
def Cal_MACD_slope(self):
|
||||
begin_klu = self.get_begin_klu()
|
||||
end_klu = self.get_end_klu()
|
||||
if self.is_up():
|
||||
return (end_klu.high - begin_klu.low)/end_klu.high/(end_klu.idx - begin_klu.idx + 1)
|
||||
else:
|
||||
return (begin_klu.high - end_klu.low)/begin_klu.high/(end_klu.idx - begin_klu.idx + 1)
|
||||
|
||||
def Cal_MACD_amp(self):
|
||||
begin_klu = self.get_begin_klu()
|
||||
end_klu = self.get_end_klu()
|
||||
if self.is_down():
|
||||
return (begin_klu.high-end_klu.low)/begin_klu.high
|
||||
else:
|
||||
return (end_klu.high-begin_klu.low)/begin_klu.low
|
||||
|
||||
def update_bi_list(self, bi_lst, idx1, idx2):
|
||||
for bi_idx in range(idx1, idx2+1):
|
||||
bi_lst[bi_idx].parent_seg = self
|
||||
self.bi_list.append(bi_lst[bi_idx])
|
||||
if len(self.bi_list) >= 3:
|
||||
self.support_trend_line = CTrendLine(self.bi_list, TREND_LINE_SIDE.INSIDE)
|
||||
self.resistance_trend_line = CTrendLine(self.bi_list, TREND_LINE_SIDE.OUTSIDE)
|
||||
|
||||
def get_first_multi_bi_zs(self):
|
||||
return next((zs for zs in self.zs_lst if not zs.is_one_bi_zs()), None)
|
||||
|
||||
def get_final_multi_bi_zs(self):
|
||||
return next((zs for zs in self.zs_lst[::-1] if not zs.is_one_bi_zs()), None)
|
||||
|
||||
def get_multi_bi_zs_cnt(self):
|
||||
return sum(not zs.is_one_bi_zs() for zs in self.zs_lst)
|
||||
@@ -0,0 +1,13 @@
|
||||
from Common.CEnum import LEFT_SEG_METHOD
|
||||
from Common.ChanException import CChanException, ErrCode
|
||||
|
||||
|
||||
class CSegConfig:
|
||||
def __init__(self, seg_algo="chan", left_method="peak"):
|
||||
self.seg_algo = seg_algo
|
||||
if left_method == "all":
|
||||
self.left_method = LEFT_SEG_METHOD.ALL
|
||||
elif left_method == "peak":
|
||||
self.left_method = LEFT_SEG_METHOD.PEAK
|
||||
else:
|
||||
raise CChanException(f"unknown left_seg_method={left_method}", ErrCode.PARA_ERROR)
|
||||
@@ -0,0 +1,76 @@
|
||||
from Bi.BiList import CBiList
|
||||
from Common.CEnum import BI_DIR, SEG_TYPE
|
||||
|
||||
from .EigenFX import CEigenFX
|
||||
from .SegConfig import CSegConfig
|
||||
from .SegListComm import CSegListComm
|
||||
|
||||
|
||||
class CSegListChan(CSegListComm):
|
||||
def __init__(self, seg_config=CSegConfig(), lv=SEG_TYPE.BI):
|
||||
super(CSegListChan, self).__init__(seg_config=seg_config, lv=lv)
|
||||
|
||||
def do_init(self):
|
||||
# 删除末尾不确定的线段
|
||||
while len(self) and not self.lst[-1].is_sure:
|
||||
_seg = self[-1]
|
||||
for bi in _seg.bi_list:
|
||||
bi.parent_seg = None
|
||||
if _seg.pre:
|
||||
_seg.pre.next = None
|
||||
self.lst.pop()
|
||||
if len(self):
|
||||
assert self.lst[-1].eigen_fx and self.lst[-1].eigen_fx.ele[-1]
|
||||
if not self.lst[-1].eigen_fx.ele[-1].lst[-1].is_sure:
|
||||
# 如果确定线段的分形的第三元素包含不确定笔,也需要重新算,不然线段分形元素的高低点可能不对
|
||||
self.lst.pop()
|
||||
|
||||
def update(self, bi_lst: CBiList):
|
||||
self.do_init()
|
||||
if len(self) == 0:
|
||||
self.cal_seg_sure(bi_lst, begin_idx=0)
|
||||
else:
|
||||
self.cal_seg_sure(bi_lst, begin_idx=self[-1].end_bi.idx+1)
|
||||
self.collect_left_seg(bi_lst)
|
||||
|
||||
def cal_seg_sure(self, bi_lst: CBiList, begin_idx: int):
|
||||
up_eigen = CEigenFX(BI_DIR.UP, lv=self.lv) # 上升线段下降笔
|
||||
down_eigen = CEigenFX(BI_DIR.DOWN, lv=self.lv) # 下降线段上升笔
|
||||
last_seg_dir = None if len(self) == 0 else self[-1].dir
|
||||
for bi in bi_lst[begin_idx:]:
|
||||
fx_eigen = None
|
||||
if bi.is_down() and last_seg_dir != BI_DIR.UP:
|
||||
if up_eigen.add(bi):
|
||||
fx_eigen = up_eigen
|
||||
elif bi.is_up() and last_seg_dir != BI_DIR.DOWN:
|
||||
if down_eigen.add(bi):
|
||||
fx_eigen = down_eigen
|
||||
if len(self) == 0: # 尝试确定第一段方向,不要以谁先成为分形来决定,反例:US.EVRG
|
||||
if up_eigen.ele[1] is not None and bi.is_down():
|
||||
last_seg_dir = BI_DIR.DOWN
|
||||
down_eigen.clear()
|
||||
elif down_eigen.ele[1] is not None and bi.is_up():
|
||||
up_eigen.clear()
|
||||
last_seg_dir = BI_DIR.UP
|
||||
if up_eigen.ele[1] is None and last_seg_dir == BI_DIR.DOWN and bi.dir == BI_DIR.DOWN:
|
||||
last_seg_dir = None
|
||||
elif down_eigen.ele[1] is None and last_seg_dir == BI_DIR.UP and bi.dir == BI_DIR.UP:
|
||||
last_seg_dir = None
|
||||
|
||||
if fx_eigen:
|
||||
self.treat_fx_eigen(fx_eigen, bi_lst)
|
||||
break
|
||||
|
||||
def treat_fx_eigen(self, fx_eigen, bi_lst: CBiList):
|
||||
_test = fx_eigen.can_be_end(bi_lst)
|
||||
end_bi_idx = fx_eigen.GetPeakBiIdx()
|
||||
if _test in [True, None]: # None表示反向分型找到尾部也没找到
|
||||
is_true = _test is not None # 如果是正常结束
|
||||
if not self.add_new_seg(bi_lst, end_bi_idx, is_sure=is_true and fx_eigen.all_bi_is_sure()): # 防止第一根线段的方向与首尾值异常
|
||||
self.cal_seg_sure(bi_lst, end_bi_idx+1)
|
||||
return
|
||||
self.lst[-1].eigen_fx = fx_eigen
|
||||
if is_true:
|
||||
self.cal_seg_sure(bi_lst, end_bi_idx + 1)
|
||||
else:
|
||||
self.cal_seg_sure(bi_lst, fx_eigen.lst[1].idx)
|
||||
@@ -0,0 +1,169 @@
|
||||
import abc
|
||||
from typing import Generic, List, TypeVar, Union, overload
|
||||
|
||||
from Bi.Bi import CBi
|
||||
from Bi.BiList import CBiList
|
||||
from Common.CEnum import BI_DIR, LEFT_SEG_METHOD, SEG_TYPE
|
||||
from Common.ChanException import CChanException, ErrCode
|
||||
|
||||
from .Seg import CSeg
|
||||
from .SegConfig import CSegConfig
|
||||
|
||||
SUB_LINE_TYPE = TypeVar('SUB_LINE_TYPE', CBi, "CSeg")
|
||||
|
||||
|
||||
class CSegListComm(Generic[SUB_LINE_TYPE]):
|
||||
def __init__(self, seg_config=CSegConfig(), lv=SEG_TYPE.BI):
|
||||
self.lst: List[CSeg[SUB_LINE_TYPE]] = []
|
||||
self.lv = lv
|
||||
self.do_init()
|
||||
self.config = seg_config
|
||||
|
||||
def do_init(self):
|
||||
self.lst = []
|
||||
|
||||
def __iter__(self):
|
||||
yield from self.lst
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: int) -> CSeg[SUB_LINE_TYPE]: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: slice) -> List[CSeg[SUB_LINE_TYPE]]: ...
|
||||
|
||||
def __getitem__(self, index: Union[slice, int]) -> Union[List[CSeg[SUB_LINE_TYPE]], CSeg[SUB_LINE_TYPE]]:
|
||||
return self.lst[index]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.lst)
|
||||
|
||||
def left_bi_break(self, bi_lst: CBiList):
|
||||
# 最后一个确定线段之后的笔有突破该线段最后一笔的
|
||||
if len(self) == 0:
|
||||
return False
|
||||
last_seg_end_bi = self[-1].end_bi
|
||||
for bi in bi_lst[last_seg_end_bi.idx+1:]:
|
||||
if last_seg_end_bi.is_up() and bi._high() > last_seg_end_bi._high():
|
||||
return True
|
||||
elif last_seg_end_bi.is_down() and bi._low() < last_seg_end_bi._low():
|
||||
return True
|
||||
return False
|
||||
|
||||
def collect_first_seg(self, bi_lst: CBiList):
|
||||
if len(bi_lst) < 3:
|
||||
return
|
||||
if self.config.left_method == LEFT_SEG_METHOD.PEAK:
|
||||
_high = max(bi._high() for bi in bi_lst)
|
||||
_low = min(bi._low() for bi in bi_lst)
|
||||
if abs(_high-bi_lst[0].get_begin_val()) >= abs(_low-bi_lst[0].get_begin_val()):
|
||||
peak_bi = FindPeakBi(bi_lst, is_high=True)
|
||||
assert peak_bi is not None
|
||||
self.add_new_seg(bi_lst, peak_bi.idx, is_sure=False, seg_dir=BI_DIR.UP, split_first_seg=False, reason="0seg_find_high")
|
||||
else:
|
||||
peak_bi = FindPeakBi(bi_lst, is_high=False)
|
||||
assert peak_bi is not None
|
||||
self.add_new_seg(bi_lst, peak_bi.idx, is_sure=False, seg_dir=BI_DIR.DOWN, split_first_seg=False, reason="0seg_find_low")
|
||||
self.collect_left_as_seg(bi_lst)
|
||||
elif self.config.left_method == LEFT_SEG_METHOD.ALL:
|
||||
_dir = BI_DIR.UP if bi_lst[-1].get_end_val() >= bi_lst[0].get_begin_val() else BI_DIR.DOWN
|
||||
self.add_new_seg(bi_lst, bi_lst[-1].idx, is_sure=False, seg_dir=_dir, split_first_seg=False, reason="0seg_collect_all")
|
||||
else:
|
||||
raise CChanException(f"unknown seg left_method = {self.config.left_method}", ErrCode.PARA_ERROR)
|
||||
|
||||
def collect_left_seg_peak_method(self, last_seg_end_bi, bi_lst):
|
||||
if last_seg_end_bi.is_down():
|
||||
peak_bi = FindPeakBi(bi_lst[last_seg_end_bi.idx+3:], is_high=True)
|
||||
if peak_bi and peak_bi.idx - last_seg_end_bi.idx >= 3:
|
||||
self.add_new_seg(bi_lst, peak_bi.idx, is_sure=False, seg_dir=BI_DIR.UP, reason="collectleft_find_high")
|
||||
else:
|
||||
peak_bi = FindPeakBi(bi_lst[last_seg_end_bi.idx+3:], is_high=False)
|
||||
if peak_bi and peak_bi.idx - last_seg_end_bi.idx >= 3:
|
||||
self.add_new_seg(bi_lst, peak_bi.idx, is_sure=False, seg_dir=BI_DIR.DOWN, reason="collectleft_find_low")
|
||||
last_seg_end_bi = self[-1].end_bi
|
||||
|
||||
self.collect_left_as_seg(bi_lst)
|
||||
|
||||
def collect_segs(self, bi_lst):
|
||||
last_bi = bi_lst[-1]
|
||||
last_seg_end_bi = self[-1].end_bi
|
||||
if last_bi.idx-last_seg_end_bi.idx < 3:
|
||||
return
|
||||
if last_seg_end_bi.is_down() and last_bi.get_end_val() <= last_seg_end_bi.get_end_val():
|
||||
if peak_bi := FindPeakBi(bi_lst[last_seg_end_bi.idx+3:], is_high=True):
|
||||
self.add_new_seg(bi_lst, peak_bi.idx, is_sure=False, seg_dir=BI_DIR.UP, reason="collectleft_find_high_force")
|
||||
self.collect_left_seg(bi_lst)
|
||||
elif last_seg_end_bi.is_up() and last_bi.get_end_val() >= last_seg_end_bi.get_end_val():
|
||||
if peak_bi := FindPeakBi(bi_lst[last_seg_end_bi.idx+3:], is_high=False):
|
||||
self.add_new_seg(bi_lst, peak_bi.idx, is_sure=False, seg_dir=BI_DIR.DOWN, reason="collectleft_find_low_force")
|
||||
self.collect_left_seg(bi_lst)
|
||||
# 剩下线段的尾部相比于最后一个线段的尾部,高低关系和最后一个虚线段的方向一致
|
||||
elif self.config.left_method == LEFT_SEG_METHOD.ALL: # 容易找不到二类买卖点!!
|
||||
self.collect_left_as_seg(bi_lst)
|
||||
elif self.config.left_method == LEFT_SEG_METHOD.PEAK:
|
||||
self.collect_left_seg_peak_method(last_seg_end_bi, bi_lst)
|
||||
else:
|
||||
raise CChanException(f"unknown seg left_method = {self.config.left_method}", ErrCode.PARA_ERROR)
|
||||
|
||||
def collect_left_seg(self, bi_lst: CBiList):
|
||||
if len(self) == 0:
|
||||
self.collect_first_seg(bi_lst)
|
||||
else:
|
||||
self.collect_segs(bi_lst)
|
||||
|
||||
def collect_left_as_seg(self, bi_lst: CBiList):
|
||||
last_bi = bi_lst[-1]
|
||||
last_seg_end_bi = self[-1].end_bi
|
||||
if last_seg_end_bi.idx+1 >= len(bi_lst):
|
||||
return
|
||||
if last_seg_end_bi.dir == last_bi.dir:
|
||||
self.add_new_seg(bi_lst, last_bi.idx-1, is_sure=False, reason="collect_left_1")
|
||||
else:
|
||||
self.add_new_seg(bi_lst, last_bi.idx, is_sure=False, reason="collect_left_0")
|
||||
|
||||
def try_add_new_seg(self, bi_lst, end_bi_idx: int, is_sure=True, seg_dir=None, split_first_seg=True, reason="normal"):
|
||||
if len(self) == 0 and split_first_seg and end_bi_idx >= 3:
|
||||
if peak_bi := FindPeakBi(bi_lst[end_bi_idx-3::-1], bi_lst[end_bi_idx].is_down()):
|
||||
if (peak_bi.is_down() and (peak_bi._low() < bi_lst[0]._low() or peak_bi.idx == 0)) or \
|
||||
(peak_bi.is_up() and (peak_bi._high() > bi_lst[0]._high() or peak_bi.idx == 0)): # 要比第一笔开头还高/低(因为没有比较到)
|
||||
self.add_new_seg(bi_lst, peak_bi.idx, is_sure=False, seg_dir=peak_bi.dir, reason="split_first_1st")
|
||||
self.add_new_seg(bi_lst, end_bi_idx, is_sure=False, reason="split_first_2nd")
|
||||
return
|
||||
bi1_idx = 0 if len(self) == 0 else self[-1].end_bi.idx+1
|
||||
bi1 = bi_lst[bi1_idx]
|
||||
bi2 = bi_lst[end_bi_idx]
|
||||
self.lst.append(CSeg(len(self.lst), bi1, bi2, is_sure=is_sure, seg_dir=seg_dir, reason=reason))
|
||||
|
||||
if len(self.lst) >= 2:
|
||||
self.lst[-2].next = self.lst[-1]
|
||||
self.lst[-1].pre = self.lst[-2]
|
||||
self.lst[-1].update_bi_list(bi_lst, bi1_idx, end_bi_idx)
|
||||
|
||||
def add_new_seg(self, bi_lst: CBiList, end_bi_idx: int, is_sure=True, seg_dir=None, split_first_seg=True, reason="normal"):
|
||||
try:
|
||||
self.try_add_new_seg(bi_lst, end_bi_idx, is_sure, seg_dir, split_first_seg, reason)
|
||||
except CChanException as e:
|
||||
if e.errcode == ErrCode.SEG_END_VALUE_ERR and len(self.lst) == 0:
|
||||
return False
|
||||
raise e
|
||||
except Exception as e:
|
||||
raise e
|
||||
return True
|
||||
|
||||
@abc.abstractmethod
|
||||
def update(self, bi_lst: CBiList):
|
||||
...
|
||||
|
||||
def exist_sure_seg(self):
|
||||
return any(seg.is_sure for seg in self.lst)
|
||||
|
||||
|
||||
def FindPeakBi(bi_lst: Union[CBiList, List[CBi]], is_high):
|
||||
peak_val = float("-inf") if is_high else float("inf")
|
||||
peak_bi = None
|
||||
for bi in bi_lst:
|
||||
if (is_high and bi.get_end_val() >= peak_val and bi.is_up()) or (not is_high and bi.get_end_val() <= peak_val and bi.is_down()):
|
||||
if bi.pre and bi.pre.pre and ((is_high and bi.pre.pre.get_end_val() > bi.get_end_val()) or (not is_high and bi.pre.pre.get_end_val() < bi.get_end_val())):
|
||||
continue
|
||||
peak_val = bi.get_end_val()
|
||||
peak_bi = bi
|
||||
return peak_bi
|
||||
@@ -0,0 +1,96 @@
|
||||
from Bi.BiList import CBiList
|
||||
from Common.CEnum import BI_DIR, SEG_TYPE
|
||||
|
||||
from .SegConfig import CSegConfig
|
||||
from .SegListComm import CSegListComm
|
||||
|
||||
|
||||
def situation1(cur_bi, next_bi, pre_bi):
|
||||
if cur_bi.is_down() and cur_bi._low() > pre_bi._low():
|
||||
if next_bi._high() < cur_bi._high() and next_bi._low() < cur_bi._low():
|
||||
return True
|
||||
elif cur_bi.is_up() and cur_bi._high() < pre_bi._high():
|
||||
if next_bi._low() > cur_bi._low() and next_bi._high() > cur_bi._high():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def situation2(cur_bi, next_bi, pre_bi):
|
||||
if cur_bi.is_down() and cur_bi._low() < pre_bi._low():
|
||||
if next_bi._high() < cur_bi._high() and next_bi._low() < pre_bi._low():
|
||||
return True
|
||||
elif cur_bi.is_up() and cur_bi._high() > pre_bi._high():
|
||||
if next_bi._low() > cur_bi._low() and next_bi._high() > pre_bi._high():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class CSegListDYH(CSegListComm):
|
||||
def __init__(self, seg_config=CSegConfig(), lv=SEG_TYPE.BI):
|
||||
super(CSegListDYH, self).__init__(seg_config=seg_config, lv=lv)
|
||||
self.sure_seg_update_end = False
|
||||
|
||||
def update(self, bi_lst: CBiList):
|
||||
self.do_init()
|
||||
self.cal_bi_sure(bi_lst)
|
||||
self.try_update_last_seg(bi_lst)
|
||||
if self.left_bi_break(bi_lst):
|
||||
self.cal_bi_unsure(bi_lst)
|
||||
self.collect_left_seg(bi_lst)
|
||||
|
||||
def cal_bi_sure(self, bi_lst):
|
||||
BI_LEN = len(bi_lst)
|
||||
next_begin_bi = bi_lst[0]
|
||||
for idx, bi in enumerate(bi_lst):
|
||||
if idx + 2 >= BI_LEN or idx < 2:
|
||||
continue
|
||||
if len(self) > 0 and bi.dir != self[-1].end_bi.dir:
|
||||
continue
|
||||
if bi.is_down() and bi_lst[idx-1]._high() < next_begin_bi._low():
|
||||
continue
|
||||
if bi.is_up() and bi_lst[idx-1]._low() > next_begin_bi._high():
|
||||
continue
|
||||
if self.sure_seg_update_end and len(self) and ((bi.is_down() and bi._low() < self[-1].end_bi._low()) or (bi.is_up() and bi._high() > self[-1].end_bi._high())):
|
||||
self[-1].end_bi = bi
|
||||
if idx != BI_LEN-1:
|
||||
next_begin_bi = bi_lst[idx+1]
|
||||
continue
|
||||
if (len(self) == 0 or bi.idx - self[-1].end_bi.idx >= 4) and (situation1(bi, bi_lst[idx + 2], bi_lst[idx - 2]) or situation2(bi, bi_lst[idx + 2], bi_lst[idx - 2])):
|
||||
self.add_new_seg(bi_lst, idx-1)
|
||||
next_begin_bi = bi
|
||||
|
||||
def cal_bi_unsure(self, bi_lst: CBiList):
|
||||
if len(self) == 0:
|
||||
return
|
||||
last_seg_dir = self[-1].end_bi.dir
|
||||
end_bi = None
|
||||
peak_value = float("inf") if last_seg_dir == BI_DIR.UP else float("-inf")
|
||||
for bi in bi_lst[self[-1].end_bi.idx+3:]:
|
||||
if bi.dir == last_seg_dir:
|
||||
continue
|
||||
cur_value = bi._low() if last_seg_dir == BI_DIR.UP else bi._high()
|
||||
if (last_seg_dir == BI_DIR.UP and cur_value < peak_value) or \
|
||||
(last_seg_dir == BI_DIR.DOWN and cur_value > peak_value):
|
||||
end_bi = bi
|
||||
peak_value = cur_value
|
||||
if end_bi:
|
||||
self.add_new_seg(bi_lst, end_bi.idx, is_sure=False)
|
||||
|
||||
def try_update_last_seg(self, bi_lst: CBiList):
|
||||
if len(self) == 0:
|
||||
return
|
||||
last_bi = self[-1].end_bi
|
||||
peak_value = last_bi.get_end_val()
|
||||
new_peak_bi = None
|
||||
for bi in bi_lst[self[-1].end_bi.idx+1:]:
|
||||
if bi.dir != last_bi.dir:
|
||||
continue
|
||||
if bi.is_down() and bi._low() < peak_value:
|
||||
peak_value = bi._low()
|
||||
new_peak_bi = bi
|
||||
elif bi.is_up() and bi._high() > peak_value:
|
||||
peak_value = bi._high()
|
||||
new_peak_bi = bi
|
||||
if new_peak_bi:
|
||||
self[-1].end_bi = new_peak_bi
|
||||
self[-1].is_sure = False
|
||||
@@ -0,0 +1,60 @@
|
||||
from Bi.BiList import CBiList
|
||||
from Common.CEnum import SEG_TYPE
|
||||
|
||||
from .SegConfig import CSegConfig
|
||||
from .SegListComm import CSegListComm
|
||||
|
||||
|
||||
def is_up_seg(bi, pre_bi):
|
||||
return bi._high() > pre_bi._high()
|
||||
|
||||
|
||||
def is_down_seg(bi, pre_bi):
|
||||
return bi._low() < pre_bi._low()
|
||||
|
||||
|
||||
class CSegListDef(CSegListComm):
|
||||
def __init__(self, seg_config=CSegConfig(), lv=SEG_TYPE.BI):
|
||||
super(CSegListDef, self).__init__(seg_config=seg_config, lv=lv)
|
||||
self.sure_seg_update_end = False
|
||||
|
||||
def update(self, bi_lst: CBiList):
|
||||
self.do_init()
|
||||
self.cal_bi_sure(bi_lst)
|
||||
self.collect_left_seg(bi_lst)
|
||||
|
||||
def update_last_end(self, bi_lst, new_endbi_idx: int):
|
||||
last_endbi_idx = self[-1].end_bi.idx
|
||||
assert new_endbi_idx >= last_endbi_idx + 2
|
||||
self[-1].end_bi = bi_lst[new_endbi_idx]
|
||||
self.lst[-1].update_bi_list(bi_lst, last_endbi_idx, new_endbi_idx)
|
||||
|
||||
def cal_bi_sure(self, bi_lst):
|
||||
peak_bi = None
|
||||
if len(bi_lst) == 0:
|
||||
return
|
||||
for idx, bi in enumerate(bi_lst):
|
||||
if idx < 2:
|
||||
continue
|
||||
if peak_bi and ((bi.is_up() and peak_bi.is_up() and bi._high() >= peak_bi._high()) or (bi.is_down() and peak_bi.is_down() and bi._low() <= peak_bi._low())):
|
||||
peak_bi = bi
|
||||
continue
|
||||
if self.sure_seg_update_end and len(self) and bi.dir == self[-1].dir and ((bi.is_up() and bi._high() >= self[-1].end_bi._high()) or (bi.is_down() and bi._low() <= self[-1].end_bi._low())):
|
||||
self.update_last_end(bi_lst, bi.idx)
|
||||
peak_bi = None
|
||||
continue
|
||||
pre_bi = bi_lst[idx-2]
|
||||
if (bi.is_up() and is_up_seg(bi, pre_bi)) or \
|
||||
(bi.is_down() and is_down_seg(bi, pre_bi)):
|
||||
if peak_bi is None:
|
||||
if len(self) == 0 or bi.dir != self[-1].dir:
|
||||
peak_bi = bi
|
||||
continue
|
||||
elif peak_bi.dir != bi.dir:
|
||||
if bi.idx - peak_bi.idx <= 2:
|
||||
continue
|
||||
self.add_new_seg(bi_lst, peak_bi.idx)
|
||||
peak_bi = bi
|
||||
continue
|
||||
if peak_bi is not None:
|
||||
self.add_new_seg(bi_lst, peak_bi.idx, is_sure=False)
|
||||
Reference in New Issue
Block a user