chansdk 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. chan/__init__.py +27 -0
  2. chan/bi/__init__.py +1 -0
  3. chan/bi/bi.py +331 -0
  4. chan/bi/config.py +30 -0
  5. chan/bi/list.py +235 -0
  6. chan/bsp/__init__.py +1 -0
  7. chan/bsp/config.py +100 -0
  8. chan/bsp/list.py +413 -0
  9. chan/bsp/point.py +42 -0
  10. chan/chan.py +413 -0
  11. chan/combiner/__init__.py +1 -0
  12. chan/combiner/combiner.py +171 -0
  13. chan/combiner/item.py +25 -0
  14. chan/common/__init__.py +1 -0
  15. chan/common/cache.py +34 -0
  16. chan/common/enum.py +129 -0
  17. chan/common/exception.py +88 -0
  18. chan/common/time.py +52 -0
  19. chan/common/util.py +54 -0
  20. chan/config.py +181 -0
  21. chan/data/__init__.py +1 -0
  22. chan/data/base.py +34 -0
  23. chan/data/cache_api.py +191 -0
  24. chan/data/csv_api.py +108 -0
  25. chan/data/eltdx_api.py +239 -0
  26. chan/kline/__init__.py +1 -0
  27. chan/kline/kline.py +97 -0
  28. chan/kline/list.py +206 -0
  29. chan/kline/trade.py +13 -0
  30. chan/kline/unit.py +154 -0
  31. chan/math/__init__.py +1 -0
  32. chan/math/boll.py +28 -0
  33. chan/math/demark.py +236 -0
  34. chan/math/kdj.py +34 -0
  35. chan/math/macd.py +29 -0
  36. chan/math/rsi.py +38 -0
  37. chan/math/trend_line.py +77 -0
  38. chan/math/trend_model.py +22 -0
  39. chan/model/__init__.py +1 -0
  40. chan/model/features.py +18 -0
  41. chan/seg/__init__.py +1 -0
  42. chan/seg/config.py +13 -0
  43. chan/seg/eigen.py +28 -0
  44. chan/seg/eigen_fx.py +150 -0
  45. chan/seg/list_chan.py +76 -0
  46. chan/seg/list_comm.py +174 -0
  47. chan/seg/list_def.py +60 -0
  48. chan/seg/list_dyh.py +96 -0
  49. chan/seg/seg.py +156 -0
  50. chan/zs/__init__.py +1 -0
  51. chan/zs/config.py +6 -0
  52. chan/zs/list.py +161 -0
  53. chan/zs/zs.py +234 -0
  54. chansdk-1.0.1.dist-info/METADATA +255 -0
  55. chansdk-1.0.1.dist-info/RECORD +57 -0
  56. chansdk-1.0.1.dist-info/WHEEL +5 -0
  57. chansdk-1.0.1.dist-info/top_level.txt +1 -0
chan/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ # -*- coding: utf-8 -*-
2
+ """缠论核心算法 SDK - 零外部依赖"""
3
+
4
+ from chan.chan import CChan
5
+ from chan.config import CChanConfig
6
+ from chan.common.enum import (
7
+ KL_TYPE, DATA_SRC, AUTYPE, BSP_TYPE,
8
+ KLINE_DIR, FX_TYPE, BI_DIR, BI_TYPE,
9
+ )
10
+ from chan.common.exception import CChanException
11
+ from chan.common.time import CTime
12
+ from chan.kline.unit import CKLine_Unit
13
+ from chan.bi.bi import CBi
14
+ from chan.seg.seg import CSeg
15
+ from chan.zs.zs import CZS
16
+ from chan.bsp.point import CBS_Point
17
+ from chan.data.base import CCommonStockApi
18
+
19
+ __version__ = "1.0.1"
20
+ __all__ = [
21
+ "CChan", "CChanConfig",
22
+ "KL_TYPE", "DATA_SRC", "AUTYPE", "BSP_TYPE",
23
+ "KLINE_DIR", "FX_TYPE", "BI_DIR", "BI_TYPE",
24
+ "CChanException", "CTime",
25
+ "CKLine_Unit", "CBi", "CSeg", "CZS", "CBS_Point",
26
+ "CCommonStockApi",
27
+ ]
chan/bi/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # -*- coding: utf-8 -*-
chan/bi/bi.py ADDED
@@ -0,0 +1,331 @@
1
+ from typing import List, Optional
2
+
3
+ from chan.common.cache import make_cache
4
+ from chan.common.enum import BI_DIR, BI_TYPE, DATA_FIELD, FX_TYPE, MACD_ALGO
5
+ from chan.common.exception import CChanException, ErrCode
6
+ from chan.kline.kline import CKLine
7
+ from chan.kline.unit import CKLine_Unit
8
+
9
+
10
+ class CBi:
11
+ def __init__(self, begin_klc: CKLine, end_klc: CKLine, idx: int, is_sure: bool):
12
+ # self.__begin_klc = begin_klc
13
+ # self.__end_klc = end_klc
14
+ self.__dir = None
15
+ self.__idx = idx
16
+ self.__type = BI_TYPE.STRICT
17
+
18
+ self.set(begin_klc, end_klc)
19
+
20
+ self.__is_sure = is_sure
21
+ self.__sure_end: List[CKLine] = []
22
+
23
+ self.__seg_idx: Optional[int] = None
24
+
25
+ from chan.seg.seg import CSeg
26
+ self.parent_seg: Optional[CSeg[CBi]] = None # 在哪个线段里�?
27
+
28
+ from chan.bsp.point import CBS_Point
29
+ self.bsp: Optional[CBS_Point] = None # 尾部是不是买卖点
30
+
31
+ self.next: Optional[CBi] = None
32
+ self.pre: Optional[CBi] = None
33
+
34
+ def clean_cache(self):
35
+ self._memoize_cache = {}
36
+
37
+ @property
38
+ def begin_klc(self): return self.__begin_klc
39
+
40
+ @property
41
+ def end_klc(self): return self.__end_klc
42
+
43
+ @property
44
+ def dir(self): return self.__dir
45
+
46
+ @property
47
+ def idx(self): return self.__idx
48
+
49
+ @property
50
+ def type(self): return self.__type
51
+
52
+ @property
53
+ def is_sure(self): return self.__is_sure
54
+
55
+ @property
56
+ def sure_end(self): return self.__sure_end
57
+
58
+ @property
59
+ def klc_lst(self):
60
+ klc = self.begin_klc
61
+ while True:
62
+ yield klc
63
+ klc = klc.next
64
+ if not klc or klc.idx > self.end_klc.idx:
65
+ break
66
+
67
+ @property
68
+ def klc_lst_re(self):
69
+ klc = self.end_klc
70
+ while True:
71
+ yield klc
72
+ klc = klc.pre
73
+ if not klc or klc.idx < self.begin_klc.idx:
74
+ break
75
+
76
+ @property
77
+ def seg_idx(self): return self.__seg_idx
78
+
79
+ def set_seg_idx(self, idx):
80
+ self.__seg_idx = idx
81
+
82
+ def __str__(self):
83
+ return f"{self.dir}|{self.begin_klc} ~ {self.end_klc}"
84
+
85
+ def check(self):
86
+ try:
87
+ if self.is_down():
88
+ assert self.begin_klc.high > self.end_klc.low
89
+ else:
90
+ assert self.begin_klc.low < self.end_klc.high
91
+ except Exception as e:
92
+ raise CChanException(f"{self.idx}:{self.begin_klc[0].time}~{self.end_klc[-1].time}笔的方向和收尾位置不一�?", ErrCode.BI_ERR) from e
93
+
94
+ def set(self, begin_klc: CKLine, end_klc: CKLine):
95
+ self.__begin_klc: CKLine = begin_klc
96
+ self.__end_klc: CKLine = end_klc
97
+ if begin_klc.fx == FX_TYPE.BOTTOM:
98
+ self.__dir = BI_DIR.UP
99
+ elif begin_klc.fx == FX_TYPE.TOP:
100
+ self.__dir = BI_DIR.DOWN
101
+ else:
102
+ raise CChanException("ERROR DIRECTION when creating bi", ErrCode.BI_ERR)
103
+ self.check()
104
+ self.clean_cache()
105
+
106
+ @make_cache
107
+ def get_begin_val(self):
108
+ return self.begin_klc.low if self.is_up() else self.begin_klc.high
109
+
110
+ @make_cache
111
+ def get_end_val(self):
112
+ return self.end_klc.high if self.is_up() else self.end_klc.low
113
+
114
+ @make_cache
115
+ def get_begin_klu(self) -> CKLine_Unit:
116
+ if self.is_up():
117
+ return self.begin_klc.get_peak_klu(is_high=False)
118
+ else:
119
+ return self.begin_klc.get_peak_klu(is_high=True)
120
+
121
+ @make_cache
122
+ def get_end_klu(self) -> CKLine_Unit:
123
+ if self.is_up():
124
+ return self.end_klc.get_peak_klu(is_high=True)
125
+ else:
126
+ return self.end_klc.get_peak_klu(is_high=False)
127
+
128
+ @make_cache
129
+ def amp(self):
130
+ return abs(self.get_end_val() - self.get_begin_val())
131
+
132
+ @make_cache
133
+ def get_klu_cnt(self):
134
+ return self.get_end_klu().idx - self.get_begin_klu().idx + 1
135
+
136
+ @make_cache
137
+ def get_klc_cnt(self):
138
+ assert self.end_klc.idx == self.get_end_klu().klc.idx
139
+ assert self.begin_klc.idx == self.get_begin_klu().klc.idx
140
+ return self.end_klc.idx - self.begin_klc.idx + 1
141
+
142
+ @make_cache
143
+ def _high(self):
144
+ return self.end_klc.high if self.is_up() else self.begin_klc.high
145
+
146
+ @make_cache
147
+ def _low(self):
148
+ return self.begin_klc.low if self.is_up() else self.end_klc.low
149
+
150
+ @make_cache
151
+ def _mid(self):
152
+ return (self._high() + self._low()) / 2 # 笔的中位�?
153
+
154
+ @make_cache
155
+ def is_down(self):
156
+ return self.dir == BI_DIR.DOWN
157
+
158
+ @make_cache
159
+ def is_up(self):
160
+ return self.dir == BI_DIR.UP
161
+
162
+ def update_virtual_end(self, new_klc: CKLine):
163
+ self.append_sure_end(self.end_klc)
164
+ self.update_new_end(new_klc)
165
+ self.__is_sure = False
166
+
167
+ def restore_from_virtual_end(self, sure_end: CKLine):
168
+ self.__is_sure = True
169
+ self.update_new_end(new_klc=sure_end)
170
+ self.__sure_end = []
171
+
172
+ def append_sure_end(self, klc: CKLine):
173
+ self.__sure_end.append(klc)
174
+
175
+ def update_new_end(self, new_klc: CKLine):
176
+ self.__end_klc = new_klc
177
+ self.check()
178
+ self.clean_cache()
179
+
180
+ def cal_macd_metric(self, macd_algo, is_reverse):
181
+ if macd_algo == MACD_ALGO.AREA:
182
+ return self.Cal_MACD_half(is_reverse)
183
+ elif macd_algo == MACD_ALGO.PEAK:
184
+ return self.Cal_MACD_peak()
185
+ elif macd_algo == MACD_ALGO.FULL_AREA:
186
+ return self.Cal_MACD_area()
187
+ elif macd_algo == MACD_ALGO.DIFF:
188
+ return self.Cal_MACD_diff()
189
+ elif macd_algo == MACD_ALGO.SLOPE:
190
+ return self.Cal_MACD_slope()
191
+ elif macd_algo == MACD_ALGO.AMP:
192
+ return self.Cal_MACD_amp()
193
+ elif macd_algo == MACD_ALGO.AMOUNT:
194
+ return self.Cal_MACD_trade_metric(DATA_FIELD.FIELD_TURNOVER, cal_avg=False)
195
+ elif macd_algo == MACD_ALGO.VOLUMN:
196
+ return self.Cal_MACD_trade_metric(DATA_FIELD.FIELD_VOLUME, cal_avg=False)
197
+ elif macd_algo == MACD_ALGO.VOLUMN_AVG:
198
+ return self.Cal_MACD_trade_metric(DATA_FIELD.FIELD_VOLUME, cal_avg=True)
199
+ elif macd_algo == MACD_ALGO.AMOUNT_AVG:
200
+ return self.Cal_MACD_trade_metric(DATA_FIELD.FIELD_TURNOVER, cal_avg=True)
201
+ elif macd_algo == MACD_ALGO.TURNRATE_AVG:
202
+ return self.Cal_MACD_trade_metric(DATA_FIELD.FIELD_TURNRATE, cal_avg=True)
203
+ elif macd_algo == MACD_ALGO.RSI:
204
+ return self.Cal_Rsi()
205
+ else:
206
+ raise CChanException(f"unsupport macd_algo={macd_algo}, should be one of area/full_area/peak/diff/slope/amp", ErrCode.PARA_ERROR)
207
+
208
+ @make_cache
209
+ def Cal_Rsi(self):
210
+ rsi_lst: List[float] = []
211
+ for klc in self.klc_lst:
212
+ rsi_lst.extend(klu.rsi for klu in klc.lst)
213
+ return 10000.0/(min(rsi_lst)+1e-7) if self.is_down() else max(rsi_lst)
214
+
215
+ @make_cache
216
+ def Cal_MACD_area(self):
217
+ _s = 1e-7
218
+ begin_klu = self.get_begin_klu()
219
+ end_klu = self.get_end_klu()
220
+ for klc in self.klc_lst:
221
+ for klu in klc.lst:
222
+ if klu.idx < begin_klu.idx or klu.idx > end_klu.idx:
223
+ continue
224
+ if (self.is_down() and klu.macd.macd < 0) or (self.is_up() and klu.macd.macd > 0):
225
+ _s += abs(klu.macd.macd)
226
+ return _s
227
+
228
+ @make_cache
229
+ def Cal_MACD_peak(self):
230
+ peak = 1e-7
231
+ for klc in self.klc_lst:
232
+ for klu in klc.lst:
233
+ if abs(klu.macd.macd) > peak:
234
+ if self.is_down() and klu.macd.macd < 0:
235
+ peak = abs(klu.macd.macd)
236
+ elif self.is_up() and klu.macd.macd > 0:
237
+ peak = abs(klu.macd.macd)
238
+ return peak
239
+
240
+ def Cal_MACD_half(self, is_reverse):
241
+ if is_reverse:
242
+ return self.Cal_MACD_half_reverse()
243
+ else:
244
+ return self.Cal_MACD_half_obverse()
245
+
246
+ @make_cache
247
+ def Cal_MACD_half_obverse(self):
248
+ _s = 1e-7
249
+ begin_klu = self.get_begin_klu()
250
+ peak_macd = begin_klu.macd.macd
251
+ for klc in self.klc_lst:
252
+ for klu in klc.lst:
253
+ if klu.idx < begin_klu.idx:
254
+ continue
255
+ if klu.macd.macd*peak_macd > 0:
256
+ _s += abs(klu.macd.macd)
257
+ else:
258
+ break
259
+ else: # 没有被break,继续找写一个KLC
260
+ continue
261
+ break
262
+ return _s
263
+
264
+ @make_cache
265
+ def Cal_MACD_half_reverse(self):
266
+ _s = 1e-7
267
+ begin_klu = self.get_end_klu()
268
+ peak_macd = begin_klu.macd.macd
269
+ for klc in self.klc_lst_re:
270
+ for klu in klc[::-1]:
271
+ if klu.idx > begin_klu.idx:
272
+ continue
273
+ if klu.macd.macd*peak_macd > 0:
274
+ _s += abs(klu.macd.macd)
275
+ else:
276
+ break
277
+ else: # 没有被break,继续找写一个KLC
278
+ continue
279
+ break
280
+ return _s
281
+
282
+ @make_cache
283
+ def Cal_MACD_diff(self):
284
+ """
285
+ macd红绿柱最大值最小值之�?
286
+ """
287
+ _max, _min = float("-inf"), float("inf")
288
+ for klc in self.klc_lst:
289
+ for klu in klc.lst:
290
+ macd = klu.macd.macd
291
+ if macd > _max:
292
+ _max = macd
293
+ if macd < _min:
294
+ _min = macd
295
+ return _max-_min
296
+
297
+ @make_cache
298
+ def Cal_MACD_slope(self):
299
+ begin_klu = self.get_begin_klu()
300
+ end_klu = self.get_end_klu()
301
+ if self.is_up():
302
+ denominator = end_klu.high
303
+ numerator = end_klu.high - begin_klu.low
304
+ else:
305
+ denominator = begin_klu.high
306
+ numerator = begin_klu.high - end_klu.low
307
+ if denominator == 0:
308
+ return 0.0
309
+ return numerator / denominator / (end_klu.idx - begin_klu.idx + 1)
310
+
311
+ @make_cache
312
+ def Cal_MACD_amp(self):
313
+ begin_klu = self.get_begin_klu()
314
+ end_klu = self.get_end_klu()
315
+ if self.is_down():
316
+ return (begin_klu.high-end_klu.low)/begin_klu.high
317
+ else:
318
+ return (end_klu.high-begin_klu.low)/begin_klu.low
319
+
320
+ def Cal_MACD_trade_metric(self, metric: str, cal_avg=False) -> float:
321
+ _s = 0
322
+ for klc in self.klc_lst:
323
+ for klu in klc.lst:
324
+ metric_res = klu.trade_info.metric[metric]
325
+ if metric_res is None:
326
+ return 0.0
327
+ _s += metric_res
328
+ return _s / self.get_klu_cnt() if cal_avg else _s
329
+
330
+ # def set_klc_lst(self, lst):
331
+ # self.__klc_lst = lst
chan/bi/config.py ADDED
@@ -0,0 +1,30 @@
1
+ from chan.common.enum import FX_CHECK_METHOD
2
+ from chan.common.exception import CChanException, ErrCode
3
+
4
+
5
+ class CBiConfig:
6
+ def __init__(
7
+ self,
8
+ bi_algo="normal",
9
+ is_strict=True,
10
+ bi_fx_check="half",
11
+ gap_as_kl=True,
12
+ bi_end_is_peak=True,
13
+ bi_allow_sub_peak=True,
14
+ ):
15
+ self.bi_algo = bi_algo
16
+ self.is_strict = is_strict
17
+ if bi_fx_check == "strict":
18
+ self.bi_fx_check = FX_CHECK_METHOD.STRICT
19
+ elif bi_fx_check == "loss":
20
+ self.bi_fx_check = FX_CHECK_METHOD.LOSS
21
+ elif bi_fx_check == "half":
22
+ self.bi_fx_check = FX_CHECK_METHOD.HALF
23
+ elif bi_fx_check == 'totally':
24
+ self.bi_fx_check = FX_CHECK_METHOD.TOTALLY
25
+ else:
26
+ raise CChanException(f"unknown bi_fx_check={bi_fx_check}", ErrCode.PARA_ERROR)
27
+
28
+ self.gap_as_kl = gap_as_kl
29
+ self.bi_end_is_peak = bi_end_is_peak
30
+ self.bi_allow_sub_peak = bi_allow_sub_peak
chan/bi/list.py ADDED
@@ -0,0 +1,235 @@
1
+ from typing import List, Optional, Union, overload
2
+
3
+ from chan.common.enum import FX_TYPE, KLINE_DIR
4
+ from chan.kline.kline import CKLine
5
+
6
+ from .bi import CBi
7
+ from .config import CBiConfig
8
+
9
+
10
+ class CBiList:
11
+ def __init__(self, bi_conf=CBiConfig()):
12
+ self.bi_list: List[CBi] = []
13
+ self.last_end = None # 最后一笔的尾部
14
+ self.config = bi_conf
15
+
16
+ self.free_klc_lst = [] # 仅仅用作第一笔未画出来之前的缓存,为了获得更精准的结果而已,不加这块逻辑其实对后续计算没太大影响
17
+
18
+ def __str__(self):
19
+ return "\n".join([str(bi) for bi in self.bi_list])
20
+
21
+ def __iter__(self):
22
+ yield from self.bi_list
23
+
24
+ @overload
25
+ def __getitem__(self, index: int) -> CBi: ...
26
+
27
+ @overload
28
+ def __getitem__(self, index: slice) -> List[CBi]: ...
29
+
30
+ def __getitem__(self, index: Union[slice, int]) -> Union[List[CBi], CBi]:
31
+ return self.bi_list[index]
32
+
33
+ def __len__(self):
34
+ return len(self.bi_list)
35
+
36
+ def try_create_first_bi(self, klc: CKLine) -> bool:
37
+ for exist_free_klc in self.free_klc_lst:
38
+ if exist_free_klc.fx == klc.fx:
39
+ continue
40
+ if self.can_make_bi(klc, exist_free_klc):
41
+ self.add_new_bi(exist_free_klc, klc)
42
+ self.last_end = klc
43
+ return True
44
+ self.free_klc_lst.append(klc)
45
+ self.last_end = klc
46
+ return False
47
+
48
+ def update_bi(self, klc: CKLine, last_klc: CKLine, cal_virtual: bool) -> bool:
49
+ # klc: 倒数第二根klc
50
+ # last_klc: 倒数�?根klc
51
+ flag1 = self.update_bi_sure(klc)
52
+ if cal_virtual:
53
+ flag2 = self.try_add_virtual_bi(last_klc)
54
+ return flag1 or flag2
55
+ else:
56
+ return flag1
57
+
58
+ def can_update_peak(self, klc: CKLine):
59
+ if self.config.bi_allow_sub_peak or len(self.bi_list) < 2:
60
+ return False
61
+ if self.bi_list[-1].is_down() and klc.high < self.bi_list[-1].get_begin_val():
62
+ return False
63
+ if self.bi_list[-1].is_up() and klc.low > self.bi_list[-1].get_begin_val():
64
+ return False
65
+ if not end_is_peak(self.bi_list[-2].begin_klc, klc):
66
+ return False
67
+ if self[-1].is_down() and self[-1].get_end_val() < self[-2].get_begin_val():
68
+ return False
69
+ if self[-1].is_up() and self[-1].get_end_val() > self[-2].get_begin_val():
70
+ return False
71
+ return True
72
+
73
+ def update_peak(self, klc: CKLine, for_virtual=False):
74
+ if not self.can_update_peak(klc):
75
+ return False
76
+ _tmp_last_bi = self.bi_list[-1]
77
+ self.bi_list.pop()
78
+ if not self.try_update_end(klc, for_virtual=for_virtual):
79
+ self.bi_list.append(_tmp_last_bi)
80
+ return False
81
+ else:
82
+ if for_virtual:
83
+ self.bi_list[-1].append_sure_end(_tmp_last_bi.end_klc)
84
+ return True
85
+
86
+ def update_bi_sure(self, klc: CKLine) -> bool:
87
+ # klc: 倒数第二根klc
88
+ _tmp_end = self.get_last_klu_of_last_bi()
89
+ self.delete_virtual_bi()
90
+ # 返回值:是否出现新笔
91
+ if klc.fx == FX_TYPE.UNKNOWN:
92
+ return _tmp_end != self.get_last_klu_of_last_bi() # 虚笔是否有变
93
+ if self.last_end is None or len(self.bi_list) == 0:
94
+ return self.try_create_first_bi(klc)
95
+ if klc.fx == self.last_end.fx:
96
+ return self.try_update_end(klc)
97
+ elif self.can_make_bi(klc, self.last_end):
98
+ self.add_new_bi(self.last_end, klc)
99
+ self.last_end = klc
100
+ return True
101
+ elif self.update_peak(klc):
102
+ return True
103
+ return _tmp_end != self.get_last_klu_of_last_bi()
104
+
105
+ def delete_virtual_bi(self):
106
+ if len(self) > 0 and not self.bi_list[-1].is_sure:
107
+ sure_end_list = [klc for klc in self.bi_list[-1].sure_end]
108
+ if len(sure_end_list):
109
+ self.bi_list[-1].restore_from_virtual_end(sure_end_list[0])
110
+ self.last_end = self[-1].end_klc
111
+ for sure_end in sure_end_list[1:]:
112
+ self.add_new_bi(self.last_end, sure_end, is_sure=True)
113
+ self.last_end = self[-1].end_klc
114
+ else:
115
+ del self.bi_list[-1]
116
+ self.last_end = self[-1].end_klc if len(self) > 0 else None
117
+ if len(self) > 0:
118
+ self[-1].next = None
119
+
120
+ def try_add_virtual_bi(self, klc: CKLine, need_del_end=False):
121
+ if need_del_end:
122
+ self.delete_virtual_bi()
123
+ if len(self) == 0:
124
+ return False
125
+ if klc.idx == self[-1].end_klc.idx:
126
+ return False
127
+ if (self[-1].is_up() and klc.high >= self[-1].end_klc.high) or (self[-1].is_down() and klc.low <= self[-1].end_klc.low):
128
+ # 更新最后一�?
129
+ self.bi_list[-1].update_virtual_end(klc)
130
+ return True
131
+ _tmp_klc = klc
132
+ while _tmp_klc and _tmp_klc.idx > self[-1].end_klc.idx:
133
+ assert _tmp_klc is not None
134
+ if self.can_make_bi(_tmp_klc, self[-1].end_klc, for_virtual=True):
135
+ # 新增一�?
136
+ self.add_new_bi(self.last_end, _tmp_klc, is_sure=False)
137
+ return True
138
+ elif self.update_peak(_tmp_klc, for_virtual=True):
139
+ return True
140
+ _tmp_klc = _tmp_klc.pre
141
+ return False
142
+
143
+ def add_new_bi(self, pre_klc, cur_klc, is_sure=True):
144
+ self.bi_list.append(CBi(pre_klc, cur_klc, idx=len(self.bi_list), is_sure=is_sure))
145
+ if len(self.bi_list) >= 2:
146
+ self.bi_list[-2].next = self.bi_list[-1]
147
+ self.bi_list[-1].pre = self.bi_list[-2]
148
+
149
+ def satisfy_bi_span(self, klc: CKLine, last_end: CKLine):
150
+ bi_span = self.get_klc_span(klc, last_end)
151
+ if self.config.is_strict:
152
+ return bi_span >= 4
153
+ uint_kl_cnt = 0
154
+ tmp_klc = last_end.next
155
+ while tmp_klc:
156
+ uint_kl_cnt += len(tmp_klc.lst)
157
+ if not tmp_klc.next: # 最后尾部虚笔的时候,可能klc.idx == last_end.idx+1
158
+ return False
159
+ if tmp_klc.next.idx < klc.idx:
160
+ tmp_klc = tmp_klc.next
161
+ else:
162
+ break
163
+ return bi_span >= 3 and uint_kl_cnt >= 3
164
+
165
+ def get_klc_span(self, klc: CKLine, last_end: CKLine) -> int:
166
+ span = klc.idx - last_end.idx
167
+ if not self.config.gap_as_kl:
168
+ return span
169
+ if span >= 4: # 加速运算,如果span需要真正精确的值,需要去掉这一�?
170
+ return span
171
+ tmp_klc = last_end
172
+ while tmp_klc and tmp_klc.idx < klc.idx:
173
+ if tmp_klc.has_gap_with_next():
174
+ span += 1
175
+ tmp_klc = tmp_klc.next
176
+ return span
177
+
178
+ def can_make_bi(self, klc: CKLine, last_end: CKLine, for_virtual: bool = False):
179
+ satisify_span = True if self.config.bi_algo == 'fx' else self.satisfy_bi_span(klc, last_end)
180
+ if not satisify_span:
181
+ return False
182
+ if not last_end.check_fx_valid(klc, self.config.bi_fx_check, for_virtual):
183
+ return False
184
+ if self.config.bi_end_is_peak and not end_is_peak(last_end, klc):
185
+ return False
186
+ return True
187
+
188
+ def try_update_end(self, klc: CKLine, for_virtual=False) -> bool:
189
+ def check_top(klc: CKLine, for_virtual):
190
+ if for_virtual:
191
+ return klc.dir == KLINE_DIR.UP
192
+ else:
193
+ return klc.fx == FX_TYPE.TOP
194
+
195
+ def check_bottom(klc: CKLine, for_virtual):
196
+ if for_virtual:
197
+ return klc.dir == KLINE_DIR.DOWN
198
+ else:
199
+ return klc.fx == FX_TYPE.BOTTOM
200
+
201
+ if len(self.bi_list) == 0:
202
+ return False
203
+ last_bi = self.bi_list[-1]
204
+ if (last_bi.is_up() and check_top(klc, for_virtual) and klc.high >= last_bi.get_end_val()) or \
205
+ (last_bi.is_down() and check_bottom(klc, for_virtual) and klc.low <= last_bi.get_end_val()):
206
+ last_bi.update_virtual_end(klc) if for_virtual else last_bi.update_new_end(klc)
207
+ self.last_end = klc
208
+ return True
209
+ else:
210
+ return False
211
+
212
+ def get_last_klu_of_last_bi(self) -> Optional[int]:
213
+ return self.bi_list[-1].get_end_klu().idx if len(self) > 0 else None
214
+
215
+
216
+ def end_is_peak(last_end: CKLine, cur_end: CKLine) -> bool:
217
+ if last_end.fx == FX_TYPE.BOTTOM:
218
+ cmp_thred = cur_end.high # 或者严格点选择get_klu_max_high()
219
+ klc = last_end.get_next()
220
+ while True:
221
+ if klc.idx >= cur_end.idx:
222
+ return True
223
+ if klc.high > cmp_thred:
224
+ return False
225
+ klc = klc.get_next()
226
+ elif last_end.fx == FX_TYPE.TOP:
227
+ cmp_thred = cur_end.low # 或者严格点选择get_klu_min_low()
228
+ klc = last_end.get_next()
229
+ while True:
230
+ if klc.idx >= cur_end.idx:
231
+ return True
232
+ if klc.low < cmp_thred:
233
+ return False
234
+ klc = klc.get_next()
235
+ return True
chan/bsp/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # -*- coding: utf-8 -*-