from typing import Generic, Iterable, List, Optional, Self, TypeVar, Union, overload from Common.cache import make_cache from Common.CEnum import FX_TYPE, KLINE_DIR from Common.ChanException import CChanException, ErrCode from KLine.KLine_Unit import CKLine_Unit from .Combine_Item import CCombine_Item T = TypeVar('T') class CKLine_Combiner(Generic[T]): def __init__(self, kl_unit: T, _dir): item = CCombine_Item(kl_unit) self.__time_begin = item.time_begin self.__time_end = item.time_end self.__high = item.high self.__low = item.low self.__lst: List[T] = [kl_unit] # 本级别每一根单位K线 self.__dir = _dir self.__fx = FX_TYPE.UNKNOWN self.__pre: Optional[Self] = None self.__next: Optional[Self] = None def clean_cache(self): self._memoize_cache = {} @property def time_begin(self): return self.__time_begin @property def time_end(self): return self.__time_end @property def high(self): return self.__high @property def low(self): return self.__low @property def lst(self): return self.__lst @property def dir(self): return self.__dir @property def fx(self): return self.__fx @property def pre(self) -> Self: assert self.__pre is not None return self.__pre @property def next(self): return self.__next def get_next(self) -> Self: assert self.next is not None return self.next def test_combine(self, item: CCombine_Item, exclude_included=False, allow_top_equal=None): if (self.high >= item.high and self.low <= item.low): return KLINE_DIR.COMBINE if (self.high <= item.high and self.low >= item.low): if allow_top_equal == 1 and self.high == item.high and self.low > item.low: return KLINE_DIR.DOWN elif allow_top_equal == -1 and self.low == item.low and self.high < item.high: return KLINE_DIR.UP return KLINE_DIR.INCLUDED if exclude_included else KLINE_DIR.COMBINE if (self.high > item.high and self.low > item.low): return KLINE_DIR.DOWN if (self.high < item.high and self.low < item.low): return KLINE_DIR.UP else: raise CChanException("combine type unknown", ErrCode.COMBINER_ERR) def add(self, unit_kl: T): # only for deepcopy self.__lst.append(unit_kl) def set_fx(self, fx: FX_TYPE): # only for deepcopy self.__fx = fx def try_add(self, unit_kl: T, exclude_included=False, allow_top_equal=None): # allow_top_equal = None普通模式 # allow_top_equal = 1 被包含,顶部相等不合并 # allow_top_equal = -1 被包含,底部相等不合并 combine_item = CCombine_Item(unit_kl) _dir = self.test_combine(combine_item, exclude_included, allow_top_equal) if _dir == KLINE_DIR.COMBINE: self.__lst.append(unit_kl) if isinstance(unit_kl, CKLine_Unit): unit_kl.set_klc(self) if self.dir == KLINE_DIR.UP: if combine_item.high != combine_item.low or combine_item.high != self.high: # 处理一字K线 self.__high = max([self.high, combine_item.high]) self.__low = max([self.low, combine_item.low]) elif self.dir == KLINE_DIR.DOWN: if combine_item.high != combine_item.low or combine_item.low != self.low: # 处理一字K线 self.__high = min([self.high, combine_item.high]) self.__low = min([self.low, combine_item.low]) else: raise CChanException(f"KLINE_DIR = {self.dir} err!!! must be {KLINE_DIR.UP}/{KLINE_DIR.DOWN}", ErrCode.COMBINER_ERR) self.__time_end = combine_item.time_end self.clean_cache() # 返回UP/DOWN/COMBINE给KL_LIST,设置下一个的方向 return _dir def get_peak_klu(self, is_high) -> T: # 获取最大值 or 最小值所在klu/bi return self.get_high_peak_klu() if is_high else self.get_low_peak_klu() @make_cache def get_high_peak_klu(self) -> T: for kl in self.lst[::-1]: if CCombine_Item(kl).high == self.high: return kl raise CChanException("can't find peak...", ErrCode.COMBINER_ERR) @make_cache def get_low_peak_klu(self) -> T: for kl in self.lst[::-1]: if CCombine_Item(kl).low == self.low: return kl raise CChanException("can't find peak...", ErrCode.COMBINER_ERR) def update_fx(self, _pre: Self, _next: Self, exclude_included=False, allow_top_equal=None): # allow_top_equal = None普通模式 # allow_top_equal = 1 被包含,顶部相等不合并 # allow_top_equal = -1 被包含,底部相等不合并 self.set_next(_next) self.set_pre(_pre) _next.set_pre(self) if exclude_included: if _pre.high < self.high and _next.high <= self.high and _next.low < self.low: if allow_top_equal == 1 or _next.high < self.high: self.__fx = FX_TYPE.TOP elif _next.high > self.high and _pre.low > self.low and _next.low >= self.low: if allow_top_equal == -1 or _next.low > self.low: self.__fx = FX_TYPE.BOTTOM elif _pre.high < self.high and _next.high < self.high and _pre.low < self.low and _next.low < self.low: self.__fx = FX_TYPE.TOP elif _pre.high > self.high and _next.high > self.high and _pre.low > self.low and _next.low > self.low: self.__fx = FX_TYPE.BOTTOM self.clean_cache() def __str__(self): return f"{self.time_begin}~{self.time_end} {self.low}->{self.high}" @overload def __getitem__(self, index: int) -> T: ... @overload def __getitem__(self, index: slice) -> List[T]: ... def __getitem__(self, index: Union[slice, int]) -> Union[List[T], T]: return self.lst[index] def __len__(self): return len(self.lst) def __iter__(self) -> Iterable[T]: yield from self.lst def set_pre(self, _pre: Self): self.__pre = _pre self.clean_cache() def set_next(self, _next: Self): self.__next = _next self.clean_cache()