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.
- chan/__init__.py +27 -0
- chan/bi/__init__.py +1 -0
- chan/bi/bi.py +331 -0
- chan/bi/config.py +30 -0
- chan/bi/list.py +235 -0
- chan/bsp/__init__.py +1 -0
- chan/bsp/config.py +100 -0
- chan/bsp/list.py +413 -0
- chan/bsp/point.py +42 -0
- chan/chan.py +413 -0
- chan/combiner/__init__.py +1 -0
- chan/combiner/combiner.py +171 -0
- chan/combiner/item.py +25 -0
- chan/common/__init__.py +1 -0
- chan/common/cache.py +34 -0
- chan/common/enum.py +129 -0
- chan/common/exception.py +88 -0
- chan/common/time.py +52 -0
- chan/common/util.py +54 -0
- chan/config.py +181 -0
- chan/data/__init__.py +1 -0
- chan/data/base.py +34 -0
- chan/data/cache_api.py +191 -0
- chan/data/csv_api.py +108 -0
- chan/data/eltdx_api.py +239 -0
- chan/kline/__init__.py +1 -0
- chan/kline/kline.py +97 -0
- chan/kline/list.py +206 -0
- chan/kline/trade.py +13 -0
- chan/kline/unit.py +154 -0
- chan/math/__init__.py +1 -0
- chan/math/boll.py +28 -0
- chan/math/demark.py +236 -0
- chan/math/kdj.py +34 -0
- chan/math/macd.py +29 -0
- chan/math/rsi.py +38 -0
- chan/math/trend_line.py +77 -0
- chan/math/trend_model.py +22 -0
- chan/model/__init__.py +1 -0
- chan/model/features.py +18 -0
- chan/seg/__init__.py +1 -0
- chan/seg/config.py +13 -0
- chan/seg/eigen.py +28 -0
- chan/seg/eigen_fx.py +150 -0
- chan/seg/list_chan.py +76 -0
- chan/seg/list_comm.py +174 -0
- chan/seg/list_def.py +60 -0
- chan/seg/list_dyh.py +96 -0
- chan/seg/seg.py +156 -0
- chan/zs/__init__.py +1 -0
- chan/zs/config.py +6 -0
- chan/zs/list.py +161 -0
- chan/zs/zs.py +234 -0
- chansdk-1.0.1.dist-info/METADATA +255 -0
- chansdk-1.0.1.dist-info/RECORD +57 -0
- chansdk-1.0.1.dist-info/WHEEL +5 -0
- 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 -*-
|