Initial commit
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
from typing import Generic, List, Optional, TypeVar
|
||||
|
||||
from Bi.Bi import CBi
|
||||
from BuySellPoint.BSPointConfig import CPointConfig
|
||||
from Common.ChanException import CChanException, ErrCode
|
||||
from Common.func_util import has_overlap
|
||||
from KLine.KLine_Unit import CKLine_Unit
|
||||
from Seg.Seg import CSeg
|
||||
|
||||
LINE_TYPE = TypeVar('LINE_TYPE', CBi, "CSeg")
|
||||
|
||||
|
||||
class CZS(Generic[LINE_TYPE]):
|
||||
def __init__(self, lst: Optional[List[LINE_TYPE]], is_sure=True):
|
||||
# begin/end:永远指向 klu
|
||||
# low/high: 中枢的范围
|
||||
# peak_low/peak_high: 中枢所涉及到的笔的最大值,最小值
|
||||
self.__is_sure = is_sure
|
||||
self.__sub_zs_lst: List[CZS] = []
|
||||
|
||||
if lst is None:
|
||||
return
|
||||
|
||||
self.__begin: CKLine_Unit = lst[0].get_begin_klu()
|
||||
self.__begin_bi: LINE_TYPE = lst[0] # 中枢内部的笔
|
||||
|
||||
# self.__low = None
|
||||
# self.__high = None
|
||||
# self.__mid = None
|
||||
self.update_zs_range(lst)
|
||||
|
||||
# self.__end: CKLine_Unit = None
|
||||
# self.__end_bi: CBi = None # 中枢内部的笔
|
||||
self.__peak_high = float("-inf")
|
||||
self.__peak_low = float("inf")
|
||||
for item in lst:
|
||||
self.update_zs_end(item)
|
||||
|
||||
self.__bi_in: Optional[LINE_TYPE] = None # 进中枢那一笔
|
||||
self.__bi_out: Optional[LINE_TYPE] = None # 出中枢那一笔
|
||||
|
||||
self.__bi_lst: List[LINE_TYPE] = [] # begin_bi~end_bi之间的笔,在update_zs_in_seg函数中更新
|
||||
|
||||
def clean_cache(self):
|
||||
self._memoize_cache = {}
|
||||
|
||||
@property
|
||||
def is_sure(self): return self.__is_sure
|
||||
|
||||
@property
|
||||
def sub_zs_lst(self): return self.__sub_zs_lst
|
||||
|
||||
@property
|
||||
def begin(self): return self.__begin
|
||||
|
||||
@property
|
||||
def begin_bi(self): return self.__begin_bi
|
||||
|
||||
@property
|
||||
def low(self): return self.__low
|
||||
|
||||
@property
|
||||
def high(self): return self.__high
|
||||
|
||||
@property
|
||||
def mid(self): return self.__mid
|
||||
|
||||
@property
|
||||
def end(self): return self.__end
|
||||
|
||||
@property
|
||||
def end_bi(self): return self.__end_bi
|
||||
|
||||
@property
|
||||
def peak_high(self): return self.__peak_high
|
||||
|
||||
@property
|
||||
def peak_low(self): return self.__peak_low
|
||||
|
||||
@property
|
||||
def bi_in(self): return self.__bi_in
|
||||
|
||||
@property
|
||||
def bi_out(self): return self.__bi_out
|
||||
|
||||
@property
|
||||
def bi_lst(self): return self.__bi_lst
|
||||
|
||||
def update_zs_range(self, lst):
|
||||
self.__low: float = max(bi._low() for bi in lst)
|
||||
self.__high: float = min(bi._high() for bi in lst)
|
||||
self.__mid: float = (self.__low + self.__high) / 2 # 中枢的中点
|
||||
self.clean_cache()
|
||||
|
||||
def is_one_bi_zs(self):
|
||||
assert self.end_bi is not None
|
||||
return self.begin_bi.idx == self.end_bi.idx
|
||||
|
||||
def update_zs_end(self, item):
|
||||
self.__end: CKLine_Unit = item.get_end_klu()
|
||||
self.__end_bi: CBi = item
|
||||
if item._low() < self.peak_low:
|
||||
self.__peak_low = item._low()
|
||||
if item._high() > self.peak_high:
|
||||
self.__peak_high = item._high()
|
||||
self.clean_cache()
|
||||
|
||||
def __str__(self):
|
||||
_str = f"{self.begin_bi.idx}->{self.end_bi.idx}"
|
||||
if _str2 := ",".join([str(sub_zs) for sub_zs in self.sub_zs_lst]):
|
||||
return f"{_str}({_str2})"
|
||||
else:
|
||||
return _str
|
||||
|
||||
def combine(self, zs2: 'CZS', combine_mode) -> bool:
|
||||
if zs2.is_one_bi_zs():
|
||||
return False
|
||||
if self.begin_bi.seg_idx != zs2.begin_bi.seg_idx:
|
||||
return False
|
||||
if combine_mode == 'zs':
|
||||
if not has_overlap(self.low, self.high, zs2.low, zs2.high, equal=True):
|
||||
return False
|
||||
self.do_combine(zs2)
|
||||
return True
|
||||
elif combine_mode == 'peak':
|
||||
if has_overlap(self.peak_low, self.peak_high, zs2.peak_low, zs2.peak_high):
|
||||
self.do_combine(zs2)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
raise CChanException(f"{combine_mode} is unsupport zs conbine mode", ErrCode.PARA_ERROR)
|
||||
|
||||
def do_combine(self, zs2: 'CZS'):
|
||||
if len(self.sub_zs_lst) == 0:
|
||||
self.__sub_zs_lst.append(self.make_copy())
|
||||
self.__sub_zs_lst.append(zs2)
|
||||
|
||||
self.__low = min([self.low, zs2.low])
|
||||
self.__high = max([self.high, zs2.high])
|
||||
self.__peak_low = min([self.peak_low, zs2.peak_low])
|
||||
self.__peak_high = max([self.peak_high, zs2.peak_high])
|
||||
self.__end = zs2.end
|
||||
self.__bi_out = zs2.bi_out
|
||||
self.__end_bi = zs2.end_bi
|
||||
self.clean_cache()
|
||||
|
||||
def try_add_to_end(self, item):
|
||||
if not self.in_range(item):
|
||||
return False
|
||||
if self.is_one_bi_zs():
|
||||
self.update_zs_range([self.begin_bi, item])
|
||||
self.update_zs_end(item)
|
||||
return True
|
||||
|
||||
def in_range(self, item):
|
||||
return has_overlap(self.low, self.high, item._low(), item._high())
|
||||
|
||||
def is_inside(self, seg: CSeg):
|
||||
return seg.start_bi.idx <= self.begin_bi.idx <= seg.end_bi.idx
|
||||
|
||||
def is_divergence(self, config: CPointConfig, out_bi=None):
|
||||
if not self.end_bi_break(out_bi): # 最后一笔必须突破中枢
|
||||
return False, None
|
||||
in_metric = self.get_bi_in().cal_macd_metric(config.macd_algo, is_reverse=False)
|
||||
if out_bi is None:
|
||||
out_metric = self.get_bi_out().cal_macd_metric(config.macd_algo, is_reverse=True)
|
||||
else:
|
||||
out_metric = out_bi.cal_macd_metric(config.macd_algo, is_reverse=True)
|
||||
|
||||
if config.divergence_rate > 100: # 保送
|
||||
return True, out_metric/in_metric
|
||||
else:
|
||||
return out_metric <= config.divergence_rate*in_metric, out_metric/in_metric
|
||||
|
||||
def init_from_zs(self, zs: 'CZS'):
|
||||
self.__begin = zs.begin
|
||||
self.__end = zs.end
|
||||
self.__low = zs.low
|
||||
self.__high = zs.high
|
||||
self.__peak_high = zs.peak_high
|
||||
self.__peak_low = zs.peak_low
|
||||
self.__begin_bi = zs.begin_bi
|
||||
self.__end_bi = zs.end_bi
|
||||
self.__bi_in = zs.bi_in
|
||||
self.__bi_out = zs.bi_out
|
||||
|
||||
def make_copy(self) -> 'CZS':
|
||||
copy = CZS(lst=None, is_sure=self.is_sure)
|
||||
copy.init_from_zs(zs=self)
|
||||
return copy
|
||||
|
||||
def end_bi_break(self, end_bi=None) -> bool:
|
||||
if end_bi is None:
|
||||
end_bi = self.get_bi_out()
|
||||
assert end_bi is not None
|
||||
return (end_bi.is_down() and end_bi._low() < self.low) or \
|
||||
(end_bi.is_up() and end_bi._high() > self.high)
|
||||
|
||||
def out_bi_is_peak(self, end_bi_idx: int):
|
||||
# 返回 (是否最低点,bi_out与中枢里面尾部最接近它的差距比例)
|
||||
assert len(self.bi_lst) > 0
|
||||
if self.bi_out is None:
|
||||
return False, None
|
||||
peak_rate = float("inf")
|
||||
for bi in self.bi_lst:
|
||||
if bi.idx > end_bi_idx:
|
||||
break
|
||||
if (self.bi_out.is_down() and bi._low() < self.bi_out._low()) or (self.bi_out.is_up() and bi._high() > self.bi_out._high()):
|
||||
return False, None
|
||||
r = abs(bi.get_end_val()-self.bi_out.get_end_val())/self.bi_out.get_end_val()
|
||||
if r < peak_rate:
|
||||
peak_rate = r
|
||||
return True, peak_rate
|
||||
|
||||
def get_bi_in(self) -> LINE_TYPE:
|
||||
assert self.bi_in is not None
|
||||
return self.bi_in
|
||||
|
||||
def get_bi_out(self) -> LINE_TYPE:
|
||||
assert self.__bi_out is not None
|
||||
return self.__bi_out
|
||||
|
||||
def set_bi_in(self, bi):
|
||||
self.__bi_in = bi
|
||||
self.clean_cache()
|
||||
|
||||
def set_bi_out(self, bi):
|
||||
self.__bi_out = bi
|
||||
self.clean_cache()
|
||||
|
||||
def set_bi_lst(self, bi_lst):
|
||||
self.__bi_lst = bi_lst
|
||||
self.clean_cache()
|
||||
@@ -0,0 +1,6 @@
|
||||
class CZSConfig:
|
||||
def __init__(self, need_combine=True, zs_combine_mode="zs", one_bi_zs=False, zs_algo="normal"):
|
||||
self.need_combine = need_combine
|
||||
self.zs_combine_mode = zs_combine_mode
|
||||
self.one_bi_zs = one_bi_zs
|
||||
self.zs_algo = zs_algo
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
from typing import List, Union, overload
|
||||
|
||||
from Bi.Bi import CBi
|
||||
from Bi.BiList import CBiList
|
||||
from Common.func_util import revert_bi_dir
|
||||
from Seg.Seg import CSeg
|
||||
from Seg.SegListComm import CSegListComm
|
||||
from ZS.ZSConfig import CZSConfig
|
||||
|
||||
from .ZS import CZS
|
||||
|
||||
|
||||
class CZSList:
|
||||
def __init__(self, zs_config=CZSConfig()):
|
||||
self.zs_lst: List[CZS] = []
|
||||
|
||||
self.config = zs_config
|
||||
self.free_item_lst = []
|
||||
|
||||
self.last_sure_pos = -1
|
||||
|
||||
def update_last_pos(self, seg_list: CSegListComm):
|
||||
self.last_sure_pos = -1
|
||||
for seg in seg_list[::-1]:
|
||||
if seg.is_sure:
|
||||
self.last_sure_pos = seg.start_bi.idx
|
||||
return
|
||||
|
||||
def seg_need_cal(self, seg: CSeg):
|
||||
return seg.start_bi.idx >= self.last_sure_pos
|
||||
|
||||
def add_to_free_lst(self, item, is_sure, zs_algo):
|
||||
if len(self.free_item_lst) != 0 and item.idx == self.free_item_lst[-1].idx:
|
||||
# 防止笔新高或新低的更新带来bug
|
||||
self.free_item_lst = self.free_item_lst[:-1]
|
||||
self.free_item_lst.append(item)
|
||||
res = self.try_construct_zs(self.free_item_lst, is_sure, zs_algo) # 可能是一笔中枢
|
||||
if res is not None and res.begin_bi.idx > 0: # 禁止第一笔就是中枢的起点
|
||||
self.zs_lst.append(res)
|
||||
self.clear_free_lst()
|
||||
self.try_combine()
|
||||
|
||||
def clear_free_lst(self):
|
||||
self.free_item_lst = []
|
||||
|
||||
def update(self, bi: CBi, is_sure=True):
|
||||
if len(self.free_item_lst) == 0 and self.try_add_to_end(bi):
|
||||
# zs_combine_mode=peak合并模式下会触发生效,=zs合并一定无效返回
|
||||
self.try_combine() # 新形成的中枢尝试和之前的中枢合并
|
||||
return
|
||||
self.add_to_free_lst(bi, is_sure, "normal")
|
||||
|
||||
def try_add_to_end(self, bi):
|
||||
return False if len(self.zs_lst) == 0 else self[-1].try_add_to_end(bi)
|
||||
|
||||
def add_zs_from_bi_range(self, seg_bi_lst: list, seg_dir, seg_is_sure):
|
||||
deal_bi_cnt = 0
|
||||
for bi in seg_bi_lst:
|
||||
if bi.dir == seg_dir:
|
||||
continue
|
||||
if deal_bi_cnt < 1: # 防止try_add_to_end执行到上一个线段的中枢里面去
|
||||
self.add_to_free_lst(bi, seg_is_sure, "normal")
|
||||
deal_bi_cnt += 1
|
||||
else:
|
||||
self.update(bi, seg_is_sure)
|
||||
|
||||
def try_construct_zs(self, lst, is_sure, zs_algo):
|
||||
if zs_algo == "normal":
|
||||
if not self.config.one_bi_zs:
|
||||
if len(lst) == 1:
|
||||
return None
|
||||
else:
|
||||
lst = lst[-2:]
|
||||
elif zs_algo == "over_seg":
|
||||
if len(lst) < 3:
|
||||
return None
|
||||
lst = lst[-3:]
|
||||
if lst[0].dir == lst[0].parent_seg.dir:
|
||||
lst = lst[1:]
|
||||
return None
|
||||
min_high = min(item._high() for item in lst)
|
||||
max_low = max(item._low() for item in lst)
|
||||
return CZS(lst, is_sure=is_sure) if min_high > max_low else None
|
||||
|
||||
def cal_bi_zs(self, bi_lst: Union[CBiList, CSegListComm], seg_lst: CSegListComm):
|
||||
while self.zs_lst and self.zs_lst[-1].begin_bi.idx >= self.last_sure_pos:
|
||||
self.zs_lst.pop()
|
||||
if self.config.zs_algo == "normal":
|
||||
for seg in seg_lst:
|
||||
if not self.seg_need_cal(seg):
|
||||
continue
|
||||
self.clear_free_lst()
|
||||
seg_bi_lst = bi_lst[seg.start_bi.idx:seg.end_bi.idx+1]
|
||||
self.add_zs_from_bi_range(seg_bi_lst, seg.dir, seg.is_sure)
|
||||
|
||||
# 处理未生成新线段的部分
|
||||
if len(seg_lst):
|
||||
self.clear_free_lst()
|
||||
self.add_zs_from_bi_range(bi_lst[seg_lst[-1].end_bi.idx+1:], revert_bi_dir(seg_lst[-1].dir), False)
|
||||
elif self.config.zs_algo == "over_seg":
|
||||
assert self.config.one_bi_zs is False
|
||||
self.clear_free_lst()
|
||||
begin_bi_idx = self.zs_lst[-1].end_bi.idx+1 if self.zs_lst else 0
|
||||
for bi in bi_lst[begin_bi_idx:]:
|
||||
self.update_overseg_zs(bi)
|
||||
elif self.config.zs_algo == "auto":
|
||||
sure_seg_appear = False
|
||||
exist_sure_seg = seg_lst.exist_sure_seg()
|
||||
for seg in seg_lst:
|
||||
if seg.is_sure:
|
||||
sure_seg_appear = True
|
||||
if not self.seg_need_cal(seg):
|
||||
continue
|
||||
if seg.is_sure or (not sure_seg_appear and exist_sure_seg):
|
||||
self.clear_free_lst()
|
||||
self.add_zs_from_bi_range(bi_lst[seg.start_bi.idx:seg.end_bi.idx+1], seg.dir, seg.is_sure)
|
||||
else:
|
||||
self.clear_free_lst()
|
||||
for bi in bi_lst[seg.start_bi.idx:]:
|
||||
self.update_overseg_zs(bi)
|
||||
break
|
||||
else:
|
||||
raise Exception(f"unknown zs_algo {self.config.zs_algo}")
|
||||
self.update_last_pos(seg_lst)
|
||||
|
||||
def update_overseg_zs(self, bi: CBi | CSeg):
|
||||
if len(self.zs_lst) and len(self.free_item_lst) == 0:
|
||||
if bi.next is None:
|
||||
return
|
||||
if bi.idx - self.zs_lst[-1].end_bi.idx <= 1 and self.zs_lst[-1].in_range(bi.next) and self.zs_lst[-1].try_add_to_end(bi):
|
||||
return
|
||||
if len(self.zs_lst) and len(self.free_item_lst) == 0 and self.zs_lst[-1].in_range(bi) and bi.idx - self.zs_lst[-1].end_bi.idx <= 1:
|
||||
return
|
||||
self.add_to_free_lst(bi, bi.is_sure, zs_algo="over_seg")
|
||||
|
||||
def __iter__(self):
|
||||
yield from self.zs_lst
|
||||
|
||||
def __len__(self):
|
||||
return len(self.zs_lst)
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: int) -> CZS: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: slice) -> List[CZS]: ...
|
||||
|
||||
def __getitem__(self, index: Union[slice, int]) -> Union[List[CZS], CZS]:
|
||||
return self.zs_lst[index]
|
||||
|
||||
def try_combine(self):
|
||||
if not self.config.need_combine:
|
||||
return
|
||||
while len(self.zs_lst) >= 2 and self.zs_lst[-2].combine(self.zs_lst[-1], combine_mode=self.config.zs_combine_mode):
|
||||
self.zs_lst = self.zs_lst[:-1] # 合并后删除最后一个
|
||||
Reference in New Issue
Block a user