analyze.py 26.7 KB
Newer Older
Z
zengbin93 已提交
1 2
# coding: utf-8

3 4 5 6 7
import warnings
try:
    import talib
except:
    warnings.warn("ta-lib 没有安装成功,请到 https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib 下载对应版本安装")
Z
zengbin93 已提交
8
import pandas as pd
9
import numpy as np
Z
zengbin93 已提交
10 11 12
from czsc.ta import ma, macd, boll
from czsc.utils import plot_ka, plot_kline

Z
zengbin93 已提交
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

def find_zs(points):
    """输入笔或线段标记点,输出中枢识别结果"""
    if len(points) <= 4:
        return []

    # 当输入为笔的标记点时,新增 xd 值
    for i, x in enumerate(points):
        if x.get("bi", 0):
            points[i]['xd'] = x["bi"]

    k_xd = points
    k_zs = []
    zs_xd = []

    for i in range(len(k_xd)):
        if len(zs_xd) < 5:
            zs_xd.append(k_xd[i])
            continue
        xd_p = k_xd[i]
        zs_d = max([x['xd'] for x in zs_xd[:4] if x['fx_mark'] == 'd'])
        zs_g = min([x['xd'] for x in zs_xd[:4] if x['fx_mark'] == 'g'])
        if zs_g <= zs_d:
            zs_xd.append(k_xd[i])
            zs_xd.pop(0)
            continue

Z
zengbin93 已提交
40 41
        # 定义四个指标,GG=max(gn),G=min(gn),D=max(dn),DD=min(dn),n遍历中枢中所有Zn。
        # 特别地,再定义ZG=min(g1、g2), ZD=max(d1、d2),显然,[ZD,ZG]就是缠中说禅走势中枢的区间
Z
zengbin93 已提交
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
        if xd_p['fx_mark'] == "d" and xd_p['xd'] > zs_g:
            # 线段在中枢上方结束,形成三买
            k_zs.append({
                'ZD': zs_d,
                "ZG": zs_g,
                'G': min([x['xd'] for x in zs_xd if x['fx_mark'] == 'g']),
                'GG': max([x['xd'] for x in zs_xd if x['fx_mark'] == 'g']),
                'D': max([x['xd'] for x in zs_xd if x['fx_mark'] == 'd']),
                'DD': min([x['xd'] for x in zs_xd if x['fx_mark'] == 'd']),
                "points": zs_xd,
                "third_buy": xd_p
            })
            zs_xd = k_xd[i - 1: i + 1]
        elif xd_p['fx_mark'] == "g" and xd_p['xd'] < zs_d:
            # 线段在中枢下方结束,形成三卖
            k_zs.append({
                'ZD': zs_d,
                "ZG": zs_g,
                'G': min([x['xd'] for x in zs_xd if x['fx_mark'] == 'g']),
                'GG': max([x['xd'] for x in zs_xd if x['fx_mark'] == 'g']),
                'D': max([x['xd'] for x in zs_xd if x['fx_mark'] == 'd']),
                'DD': min([x['xd'] for x in zs_xd if x['fx_mark'] == 'd']),
                "points": zs_xd,
                "third_sell": xd_p
            })
            zs_xd = k_xd[i - 1: i + 1]
        else:
            zs_xd.append(xd_p)

    if len(zs_xd) >= 5:
        zs_d = max([x['xd'] for x in zs_xd[:4] if x['fx_mark'] == 'd'])
        zs_g = min([x['xd'] for x in zs_xd[:4] if x['fx_mark'] == 'g'])
        k_zs.append({
            'ZD': zs_d,
            "ZG": zs_g,
            'G': min([x['xd'] for x in zs_xd if x['fx_mark'] == 'g']),
            'GG': max([x['xd'] for x in zs_xd if x['fx_mark'] == 'g']),
            'D': max([x['xd'] for x in zs_xd if x['fx_mark'] == 'd']),
            'DD': min([x['xd'] for x in zs_xd if x['fx_mark'] == 'd']),
            "points": zs_xd,
        })

    return k_zs

Z
zengbin93 已提交
86 87

class KlineAnalyze:
Z
zengbin93 已提交
88 89
    def __init__(self, kline, name="本级别", min_bi_k=5, bi_mode="old",
                 max_raw_len=10000, ma_params=(5, 20, 120), verbose=False):
90 91 92 93 94 95
        """

        :param kline: list or pd.DataFrame
        :param name: str
        :param min_bi_k: int
            笔内部的最少K线数量
Z
zengbin93 已提交
96 97
        :param bi_mode: str
            new 新笔;old 老笔;默认值为 old
98 99
        :param max_raw_len: int
            原始K线序列的最大长度
Z
zengbin93 已提交
100 101
        :param ma_params: tuple of int
            均线系统参数
102 103
        :param verbose: bool
        """
Z
zengbin93 已提交
104 105
        self.name = name
        self.verbose = verbose
Z
zengbin93 已提交
106
        self.min_bi_k = min_bi_k
Z
zengbin93 已提交
107
        self.bi_mode = bi_mode
108
        self.max_raw_len = max_raw_len
109
        self.ma_params = ma_params
Z
zengbin93 已提交
110 111 112
        self.kline_raw = []     # 原始K线序列
        self.kline_new = []     # 去除包含关系的K线序列

113 114 115 116
        # 辅助技术指标
        self.ma = []
        self.macd = []

Z
zengbin93 已提交
117 118 119 120 121
        # 分型、笔、线段
        self.fx_list = []
        self.bi_list = []
        self.xd_list = []

Z
zengbin93 已提交
122 123 124 125 126 127 128 129 130
        # # 中枢识别结果
        # self.zs_list_l1 = []
        # self.zs_list_l2 = []
        # self.zs_list_l3 = []
        #
        # # 走势分段结果
        # self.fd_list_l1 = []
        # self.fd_list_l2 = []
        # self.fd_list_l3 = []
Z
zengbin93 已提交
131

Z
zengbin93 已提交
132
        # 根据输入K线初始化
Z
zengbin93 已提交
133 134
        if isinstance(kline, pd.DataFrame):
            columns = kline.columns.to_list()
Z
zengbin93 已提交
135
            self.kline_raw = [{k: v for k, v in zip(columns, row)} for row in kline.values]
Z
zengbin93 已提交
136
        else:
Z
zengbin93 已提交
137
            self.kline_raw = kline
Z
zengbin93 已提交
138

139
        self.kline_raw = self.kline_raw[-self.max_raw_len:]
Z
zengbin93 已提交
140 141 142 143 144
        self.symbol = self.kline_raw[0]['symbol']
        self.start_dt = self.kline_raw[0]['dt']
        self.end_dt = self.kline_raw[-1]['dt']
        self.latest_price = self.kline_raw[-1]['close']

145
        self._update_ta()
Z
zengbin93 已提交
146 147 148 149
        self._update_kline_new()
        self._update_fx_list()
        self._update_bi_list()
        self._update_xd_list()
Z
zengbin93 已提交
150

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
    def _update_ta(self):
        """更新辅助技术指标"""
        if not self.ma:
            ma_temp = dict()
            close_ = np.array([x["close"] for x in self.kline_raw], dtype=np.double)
            for p in self.ma_params:
                ma_temp['ma%i' % p] = talib.MA(close_, timeperiod=p, matype=talib.MA_Type.SMA)

            for i in range(len(self.kline_raw)):
                ma_ = {'ma%i' % p: ma_temp['ma%i' % p][i] for p in self.ma_params}
                ma_.update({"dt": self.kline_raw[i]['dt']})
                self.ma.append(ma_)
        else:
            ma_ = {'ma%i' % p: sum([x['close'] for x in self.kline_raw[-p:]]) / p
                   for p in self.ma_params}
            ma_.update({"dt": self.kline_raw[-1]['dt']})
167 168 169
            if self.verbose:
                print("ma new: %s" % str(ma_))

170 171 172 173 174
            if self.kline_raw[-2]['dt'] == self.ma[-1]['dt']:
                self.ma.append(ma_)
            else:
                self.ma[-1] = ma_

175
        assert self.ma[-2]['dt'] == self.kline_raw[-2]['dt']
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197

        if not self.macd:
            close_ = np.array([x["close"] for x in self.kline_raw], dtype=np.double)
            # m1 is diff; m2 is dea; m3 is macd
            m1, m2, m3 = talib.MACD(close_, fastperiod=12, slowperiod=26, signalperiod=9)
            for i in range(len(self.kline_raw)):
                self.macd.append({
                    "dt": self.kline_raw[i]['dt'],
                    "diff": m1[i],
                    "dea": m2[i],
                    "macd": m3[i]
                })
        else:
            close_ = np.array([x["close"] for x in self.kline_raw[-200:]], dtype=np.double)
            # m1 is diff; m2 is dea; m3 is macd
            m1, m2, m3 = talib.MACD(close_, fastperiod=12, slowperiod=26, signalperiod=9)
            macd_ = {
                    "dt": self.kline_raw[-1]['dt'],
                    "diff": m1[-1],
                    "dea": m2[-1],
                    "macd": m3[-1]
                }
198 199 200 201
            if self.verbose:
                print("macd new: %s" % str(macd_))

            if self.kline_raw[-2]['dt'] == self.macd[-1]['dt']:
202 203 204 205
                self.macd.append(macd_)
            else:
                self.macd[-1] = macd_

206 207
        assert self.macd[-2]['dt'] == self.kline_raw[-2]['dt']

Z
zengbin93 已提交
208 209 210 211 212 213 214 215 216 217
    def _update_kline_new(self):
        """更新去除包含关系的K线序列

        原始K线序列样例:
         {'symbol': '000001.SH',
          'dt': Timestamp('2020-07-16 15:00:00'),
          'open': 3356.11,
          'close': 3210.1,
          'high': 3373.53,
          'low': 3209.76,
Z
zengbin93 已提交
218
          'vol': 486366915.0}
Z
zengbin93 已提交
219 220 221 222 223 224 225 226

        无包含关系K线对象样例:
         {'symbol': '000001.SH',
          'dt': Timestamp('2020-07-16 15:00:00'),
          'open': 3356.11,
          'close': 3210.1,
          'high': 3373.53,
          'low': 3209.76,
Z
zengbin93 已提交
227
          'vol': 486366915.0}
Z
zengbin93 已提交
228
        """
Z
zengbin93 已提交
229
        if len(self.kline_new) == 0:
Z
zengbin93 已提交
230
            for x in self.kline_raw[:4]:
Z
zengbin93 已提交
231
                self.kline_new.append(dict(x))
Z
zengbin93 已提交
232 233

        # 新K线只会对最后一个去除包含关系K线的结果产生影响
Z
zengbin93 已提交
234
        self.kline_new = self.kline_new[:-2]
Z
zengbin93 已提交
235 236 237 238 239
        if len(self.kline_new) <= 4:
            right_k = [x for x in self.kline_raw if x['dt'] > self.kline_new[-1]['dt']]
        else:
            right_k = [x for x in self.kline_raw[-100:] if x['dt'] > self.kline_new[-1]['dt']]

Z
zengbin93 已提交
240 241
        if len(right_k) == 0:
            return
Z
zengbin93 已提交
242 243 244 245

        for k in right_k:
            k = dict(k)
            last_kn = self.kline_new[-1]
Z
zengbin93 已提交
246 247 248 249
            if self.kline_new[-1]['high'] > self.kline_new[-2]['high']:
                direction = "up"
            else:
                direction = "down"
Z
zengbin93 已提交
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297

            # 判断是否存在包含关系
            cur_h, cur_l = k['high'], k['low']
            last_h, last_l = last_kn['high'], last_kn['low']
            if (cur_h <= last_h and cur_l >= last_l) or (cur_h >= last_h and cur_l <= last_l):
                self.kline_new.pop(-1)
                # 有包含关系,按方向分别处理
                if direction == "up":
                    last_h = max(last_h, cur_h)
                    last_l = max(last_l, cur_l)
                elif direction == "down":
                    last_h = min(last_h, cur_h)
                    last_l = min(last_l, cur_l)
                else:
                    raise ValueError

                k.update({"high": last_h, "low": last_l})
                # 保留红绿不变
                if k['open'] >= k['close']:
                    k.update({"open": last_h, "close": last_l})
                else:
                    k.update({"open": last_l, "close": last_h})
            self.kline_new.append(k)

    def _update_fx_list(self):
        """更新分型序列

        分型对象样例:

         {'dt': Timestamp('2020-06-29 15:00:00'),
          'fx_mark': 'd',
          'fx': 2951.77,
          'fx_high': 2977.91,
          'fx_low': 2951.77}

         {'dt': Timestamp('2020-07-09 15:00:00'),
          'fx_mark': 'g',
          'fx': 3456.97,
          'fx_high': 3456.97,
          'fx_low': 3366.08}
        """
        if len(self.kline_new) < 3:
            return

        self.fx_list = self.fx_list[:-1]
        if len(self.fx_list) == 0:
            kn = self.kline_new
        else:
298
            kn = [x for x in self.kline_new[-100:] if x['dt'] >= self.fx_list[-1]['dt']]
Z
zengbin93 已提交
299 300 301 302 303 304 305

        i = 1
        while i <= len(kn)-2:
            k1, k2, k3 = kn[i-1: i+2]

            if k1['high'] < k2['high'] > k3['high']:
                if self.verbose:
Z
zengbin93 已提交
306
                    print("顶分型:{} - {} - {}".format(k1['dt'], k2['dt'], k3['dt']))
Z
zengbin93 已提交
307 308 309 310 311 312 313 314 315 316 317
                fx = {
                    "dt": k2['dt'],
                    "fx_mark": "g",
                    "fx": k2['high'],
                    "fx_high": k2['high'],
                    "fx_low": max(k1['low'], k3['low']),
                }
                self.fx_list.append(fx)

            elif k1['low'] > k2['low'] < k3['low']:
                if self.verbose:
Z
zengbin93 已提交
318
                    print("底分型:{} - {} - {}".format(k1['dt'], k2['dt'], k3['dt']))
Z
zengbin93 已提交
319 320 321 322 323 324 325 326 327 328 329
                fx = {
                    "dt": k2['dt'],
                    "fx_mark": "d",
                    "fx": k2['low'],
                    "fx_high": min(k1['high'], k2['high']),
                    "fx_low": k2['low'],
                }
                self.fx_list.append(fx)

            else:
                if self.verbose:
Z
zengbin93 已提交
330
                    print("无分型:{} - {} - {}".format(k1['dt'], k2['dt'], k3['dt']))
Z
zengbin93 已提交
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
            i += 1

    def _update_bi_list(self):
        """更新笔序列

        笔标记样例:
         {'dt': Timestamp('2020-05-25 15:00:00'),
          'fx_mark': 'd',
          'fx_high': 2821.5,
          'fx_low': 2802.47,
          'bi': 2802.47}

         {'dt': Timestamp('2020-07-09 15:00:00'),
          'fx_mark': 'g',
          'fx_high': 3456.97,
          'fx_low': 3366.08,
          'bi': 3456.97}

        """
        if len(self.fx_list) < 2:
            return

353
        self.bi_list = self.bi_list[:-1]
Z
zengbin93 已提交
354
        if len(self.bi_list) == 0:
Z
zengbin93 已提交
355
            for fx in self.fx_list[:2]:
Z
zengbin93 已提交
356 357 358 359
                bi = dict(fx)
                bi['bi'] = bi.pop('fx')
                self.bi_list.append(bi)

Z
zengbin93 已提交
360 361
        if len(self.bi_list) <= 2:
            right_fx = [x for x in self.fx_list if x['dt'] > self.bi_list[-1]['dt']]
Z
zengbin93 已提交
362 363 364 365 366 367
            if self.bi_mode == "old":
                right_kn = [x for x in self.kline_new if x['dt'] >= self.bi_list[-1]['dt']]
            elif self.bi_mode == 'new':
                right_kn = [x for x in self.kline_raw if x['dt'] >= self.bi_list[-1]['dt']]
            else:
                raise ValueError
Z
zengbin93 已提交
368
        else:
Z
zengbin93 已提交
369 370 371 372 373 374 375
            right_fx = [x for x in self.fx_list[-50:] if x['dt'] > self.bi_list[-1]['dt']]
            if self.bi_mode == "old":
                right_kn = [x for x in self.kline_new[-300:] if x['dt'] >= self.bi_list[-1]['dt']]
            elif self.bi_mode == 'new':
                right_kn = [x for x in self.kline_raw[-300:] if x['dt'] >= self.bi_list[-1]['dt']]
            else:
                raise ValueError
Z
zengbin93 已提交
376

R
renewjoy 已提交
377 378 379
        # 给数据加索引,加速计算K线的数量
        standard_kn = pd.Series(right_kn, index=[x['dt'] for x in right_kn])

Z
zengbin93 已提交
380 381 382 383 384 385 386 387
        for fx in right_fx:
            last_bi = self.bi_list[-1]
            bi = dict(fx)
            bi['bi'] = bi.pop('fx')
            if last_bi['fx_mark'] == fx['fx_mark']:
                if (last_bi['fx_mark'] == 'g' and last_bi['bi'] < bi['bi']) \
                        or (last_bi['fx_mark'] == 'd' and last_bi['bi'] > bi['bi']):
                    if self.verbose:
Z
zengbin93 已提交
388
                        print("笔标记移动:from {} to {}".format(self.bi_list[-1], bi))
Z
zengbin93 已提交
389 390
                    self.bi_list[-1] = bi
            else:
391 392
                kn_count = len(standard_kn[last_bi['dt']:bi['dt']])
                if kn_count >= self.min_bi_k:
Z
zengbin93 已提交
393 394 395 396
                    # 确保相邻两个顶底之间不存在包含关系
                    if (last_bi['fx_mark'] == 'g' and bi['fx_high'] < last_bi['fx_low']) or \
                            (last_bi['fx_mark'] == 'd' and bi['fx_low'] > last_bi['fx_high']):
                        if self.verbose:
Z
zengbin93 已提交
397
                            print("新增笔标记:{}".format(bi))
Z
zengbin93 已提交
398 399
                        self.bi_list.append(bi)

400 401 402
        if (self.bi_list[-1]['fx_mark'] == 'd' and self.kline_new[-1]['low'] < self.bi_list[-1]['bi']) \
                or (self.bi_list[-1]['fx_mark'] == 'g' and self.kline_new[-1]['high'] > self.bi_list[-1]['bi']):
            if self.verbose:
Z
zengbin93 已提交
403
                print("最后一个笔标记无效,{}".format(self.bi_list[-1]))
404
            self.bi_list.pop(-1)
Z
zengbin93 已提交
405

Z
zengbin93 已提交
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
    @staticmethod
    def _make_standard_seq(bi_seq):
        """计算标准特征序列

        :return: list of dict
        """
        if bi_seq[0]['fx_mark'] == 'd':
            direction = "up"
        elif bi_seq[0]['fx_mark'] == 'g':
            direction = "down"
        else:
            raise ValueError

        raw_seq = [{"dt": bi_seq[i].dt,
                    'high': max(bi_seq[i].price, bi_seq[i + 1].price),
                    'low': min(bi_seq[i].price, bi_seq[i + 1].price)}
                   for i in range(1, len(bi_seq), 2) if i <= len(bi_seq) - 2]

        seq = []
        for row in raw_seq:
            if not seq:
                seq.append(row)
                continue
            last = seq[-1]
            cur_h, cur_l = row['high'], row['low']
            last_h, last_l = last['high'], last['low']

            # 左包含 or 右包含
            if (cur_h <= last_h and cur_l >= last_l) or (cur_h >= last_h and cur_l <= last_l):
                seq.pop(-1)
                # 有包含关系,按方向分别处理
                if direction == "up":
                    last_h = max(last_h, cur_h)
                    last_l = max(last_l, cur_l)
                elif direction == "down":
                    last_h = min(last_h, cur_h)
                    last_l = min(last_l, cur_l)
                else:
                    raise ValueError
                seq.append({"dt": row['dt'], "high": last_h, "low": last_l})
            else:
                seq.append(row)
        return seq

Z
zengbin93 已提交
450 451 452 453 454
    def _update_xd_list(self):
        """更新线段序列"""
        if len(self.bi_list) < 4:
            return

455
        self.xd_list = self.xd_list[:-2]
Z
zengbin93 已提交
456
        if len(self.xd_list) == 0:
Z
zengbin93 已提交
457
            for i in range(3):
Z
zengbin93 已提交
458
                xd = dict(self.bi_list[i])
Z
zengbin93 已提交
459 460 461
                xd['xd'] = xd.pop('bi')
                self.xd_list.append(xd)

Z
zengbin93 已提交
462 463 464 465
        if len(self.xd_list) <= 3:
            right_bi = [x for x in self.bi_list if x['dt'] >= self.xd_list[-1]['dt']]
        else:
            right_bi = [x for x in self.bi_list[-200:] if x['dt'] >= self.xd_list[-1]['dt']]
Z
zengbin93 已提交
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
        xd_p = []
        bi_d = [x for x in right_bi if x['fx_mark'] == 'd']
        bi_g = [x for x in right_bi if x['fx_mark'] == 'g']
        for i in range(1, len(bi_d)-2):
            d1, d2, d3 = bi_d[i-1: i+2]
            if d1['bi'] > d2['bi'] < d3['bi']:
                xd_p.append(d2)
        for j in range(1, len(bi_g)-2):
            g1, g2, g3 = bi_g[j-1: j+2]
            if g1['bi'] < g2['bi'] > g3['bi']:
                xd_p.append(g2)

        xd_p = sorted(xd_p, key=lambda x: x['dt'], reverse=False)
        for xp in xd_p:
            xd = dict(xp)
Z
zengbin93 已提交
481
            xd['xd'] = xd.pop('bi')
Z
zengbin93 已提交
482 483 484 485 486
            last_xd = self.xd_list[-1]
            if last_xd['fx_mark'] == xd['fx_mark']:
                if (last_xd['fx_mark'] == 'd' and last_xd['xd'] > xd['xd']) \
                        or (last_xd['fx_mark'] == 'g' and last_xd['xd'] < xd['xd']):
                    if self.verbose:
Z
zengbin93 已提交
487
                        print("更新线段标记:from {} to {}".format(last_xd, xd))
Z
zengbin93 已提交
488 489
                    self.xd_list[-1] = xd
            else:
Z
zengbin93 已提交
490 491 492 493
                if (last_xd['fx_mark'] == 'd' and last_xd['xd'] > xd['xd']) \
                        or (last_xd['fx_mark'] == 'g' and last_xd['xd'] < xd['xd']):
                    continue

Z
zengbin93 已提交
494 495 496
                bi_inside = [x for x in right_bi if last_xd['dt'] <= x['dt'] <= xd['dt']]
                if len(bi_inside) < 4:
                    if self.verbose:
Z
zengbin93 已提交
497
                        print("{} - {} 之间笔标记数量少于4,跳过".format(last_xd['dt'], xd['dt']))
Z
zengbin93 已提交
498 499
                    continue
                else:
Z
zengbin93 已提交
500 501
                    if len(bi_inside) > 4:
                        if self.verbose:
Z
zengbin93 已提交
502
                            print("新增线段标记(笔标记数量大于4):{}".format(xd))
Z
zengbin93 已提交
503
                        self.xd_list.append(xd)
Z
zengbin93 已提交
504 505
                    else:
                        bi_r = [x for x in right_bi if x['dt'] >= xd['dt']]
Z
zengbin93 已提交
506
                        assert bi_r[1]['fx_mark'] == bi_inside[-2]['fx_mark']
Z
zengbin93 已提交
507 508 509 510
                        # 第一种情况:没有缺口
                        if (bi_r[1]['fx_mark'] == "g" and bi_r[1]['bi'] > bi_inside[-3]['bi']) \
                                or (bi_r[1]['fx_mark'] == "d" and bi_r[1]['bi'] < bi_inside[-3]['bi']):
                            if self.verbose:
Z
zengbin93 已提交
511
                                print("新增线段标记(第一种情况):{}".format(xd))
Z
zengbin93 已提交
512 513 514 515 516 517
                            self.xd_list.append(xd)
                        # 第二种情况:有缺口
                        else:
                            if (bi_r[1]['fx_mark'] == "g" and bi_r[1]['bi'] < bi_inside[-2]['bi']) \
                                    or (bi_r[1]['fx_mark'] == "d" and bi_r[1]['bi'] > bi_inside[-2]['bi']):
                                if self.verbose:
Z
zengbin93 已提交
518
                                    print("新增线段标记(第二种情况):{}".format(xd))
Z
zengbin93 已提交
519
                                self.xd_list.append(xd)
Z
zengbin93 已提交
520

521 522 523
        if (self.xd_list[-1]['fx_mark'] == 'd' and self.kline_new[-1]['low'] < self.xd_list[-1]['xd']) \
                or (self.xd_list[-1]['fx_mark'] == 'g' and self.kline_new[-1]['high'] > self.xd_list[-1]['xd']):
            if self.verbose:
Z
zengbin93 已提交
524
                print("最后一个线段标记无效,{}".format(self.xd_list[-1]))
525
            self.xd_list.pop(-1)
Z
zengbin93 已提交
526 527 528 529 530 531 532 533 534 535 536 537

    def update(self, k):
        """更新分析结果

        :param k: dict
            单根K线对象,样例如下
            {'symbol': '000001.SH',
             'dt': Timestamp('2020-07-16 15:00:00'),
             'open': 3356.11,
             'close': 3210.1,
             'high': 3373.53,
             'low': 3209.76,
Z
zengbin93 已提交
538
             'vol': 486366915.0}
Z
zengbin93 已提交
539
        """
Z
zengbin93 已提交
540 541
        if self.verbose:
            print("=" * 100)
Z
zengbin93 已提交
542
            print("输入新K线:{}".format(k))
Z
zengbin93 已提交
543
        if not self.kline_raw or k['open'] != self.kline_raw[-1]['open']:
Z
zengbin93 已提交
544 545 546
            self.kline_raw.append(k)
        else:
            if self.verbose:
Z
zengbin93 已提交
547
                print("输入K线处于未完成状态,更新:replace {} with {}".format(self.kline_raw[-1], k))
Z
zengbin93 已提交
548 549
            self.kline_raw[-1] = k

550
        self._update_ta()
Z
zengbin93 已提交
551 552 553
        self._update_kline_new()
        self._update_fx_list()
        self._update_bi_list()
Z
zengbin93 已提交
554 555
        self._update_xd_list()

Z
zengbin93 已提交
556 557 558
        self.end_dt = self.kline_raw[-1]['dt']
        self.latest_price = self.kline_raw[-1]['close']

559
        # 根据最大原始K线序列长度限制分析结果长度
Z
zengbin93 已提交
560 561
        if len(self.kline_raw) > self.max_raw_len:
            self.kline_raw = self.kline_raw[-self.max_raw_len:]
562 563
            self.ma = self.ma[-self.max_raw_len:]
            self.macd = self.macd[-self.max_raw_len:]
Z
zengbin93 已提交
564
            self.kline_new = self.kline_new[-self.max_raw_len:]
Z
zengbin93 已提交
565 566 567
            self.fx_list = self.fx_list[-(self.max_raw_len//2):]
            self.bi_list = self.bi_list[-(self.max_raw_len//4):]
            self.xd_list = self.xd_list[-(self.max_raw_len//8):]
568

Z
zengbin93 已提交
569 570
        if self.verbose:
            print("更新结束\n\n")
Z
zengbin93 已提交
571

Z
zengbin93 已提交
572
    def to_df(self, ma_params=(5, 20), use_macd=False, use_boll=False, max_count=1000):
Z
zengbin93 已提交
573 574 575 576 577 578 579 580 581 582
        """整理成 df 输出

        :param ma_params: tuple of int
            均线系统参数
        :param use_macd: bool
        :param use_boll: bool
        :param max_count: int
        :return: pd.DataFrame
        """
        bars = self.kline_raw[-max_count:]
Z
zengbin93 已提交
583 584 585
        fx_list = {x["dt"]: {"fx_mark": x["fx_mark"], "fx": x['fx']} for x in self.fx_list[-(max_count // 2):]}
        bi_list = {x["dt"]: {"fx_mark": x["fx_mark"], "bi": x['bi']} for x in self.bi_list[-(max_count // 4):]}
        xd_list = {x["dt"]: {"fx_mark": x["fx_mark"], "xd": x['xd']} for x in self.xd_list[-(max_count // 8):]}
Z
zengbin93 已提交
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638
        results = []
        for k in bars:
            k['fx_mark'], k['fx'], k['bi'], k['xd'] = "o", None, None, None
            fx_ = fx_list.get(k['dt'], None)
            bi_ = bi_list.get(k['dt'], None)
            xd_ = xd_list.get(k['dt'], None)
            if fx_:
                k['fx_mark'] = fx_["fx_mark"]
                k['fx'] = fx_["fx"]

            if bi_:
                k['bi'] = bi_["bi"]

            if xd_:
                k['xd'] = xd_["xd"]

            results.append(k)
        df = pd.DataFrame(results)
        df = ma(df, ma_params)
        if use_macd:
            df = macd(df)
        if use_boll:
            df = boll(df)
        return df

    def to_html(self, file_html="kline.html", width="1400px", height="680px"):
        """保存成 html

        :param file_html: str
            html文件名
        :param width: str
            页面宽度
        :param height: str
            页面高度
        :return:
        """
        plot_kline(self, file_html=file_html, width=width, height=height)

    def to_image(self, file_image, mav=(5, 20, 120, 250), max_k_count=1000, dpi=50):
        """保存成图片

        :param file_image: str
            图片名称,支持 jpg/png/svg 格式,注意后缀
        :param mav: tuple of int
            均线系统参数
        :param max_k_count: int
            设定最大K线数量,这个值越大,生成的图片越长
        :param dpi: int
            图片分辨率
        :return:
        """
        plot_ka(self, file_image=file_image, mav=mav, max_k_count=max_k_count, dpi=dpi)

639 640
    def is_bei_chi(self, zs1, zs2, mode="bi", adjust=0.9):
        """判断 zs1 对 zs2 是否有背驰
Z
zengbin93 已提交
641

642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
        注意:力度的比较,并没有要求两段走势方向一致;但是如果两段走势之间存在包含关系,这样的力度比较是没有意义的。

        :param zs1: dict
            用于比较的走势,通常是最近的走势,示例如下:
            zs1 = {"start_dt": "2020-02-20 11:30:00", "end_dt": "2020-02-20 14:30:00", "direction": "up"}
        :param zs2: dict
            被比较的走势,通常是较前的走势,示例如下:
            zs2 = {"start_dt": "2020-02-21 11:30:00", "end_dt": "2020-02-21 14:30:00", "direction": "down"}
        :param mode: str
            default `bi`, optional value [`xd`, `bi`]
            xd  判断两个线段之间是否存在背驰
            bi  判断两笔之间是否存在背驰
        :param adjust: float
            调整 zs2 的力度,建议设置范围在 0.6 ~ 1.0 之间,默认设置为 0.9;
            其作用是确保 zs1 相比于 zs2 的力度足够小。
        :return: bool
        """
        assert zs1["start_dt"] > zs2["end_dt"], "zs1 必须是最近的走势,用于比较;zs2 必须是较前的走势,被比较。"
        assert zs1["start_dt"] < zs1["end_dt"], "走势的时间区间定义错误,必须满足 start_dt < end_dt"
        assert zs2["start_dt"] < zs2["end_dt"], "走势的时间区间定义错误,必须满足 start_dt < end_dt"

        min_dt = min(zs1["start_dt"], zs2["start_dt"])
        max_dt = max(zs1["end_dt"], zs2["end_dt"])
Z
zengbin93 已提交
665 666
        macd_ = [x for x in self.macd if x['dt'] >= min_dt]
        macd_ = [x for x in macd_ if max_dt >= x['dt']]
667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
        k1 = [x for x in macd_ if zs1["end_dt"] >= x['dt'] >= zs1["start_dt"]]
        k2 = [x for x in macd_ if zs2["end_dt"] >= x['dt'] >= zs2["start_dt"]]

        bc = False
        if mode == 'bi':
            macd_sum1 = sum([abs(x['macd']) for x in k1])
            macd_sum2 = sum([abs(x['macd']) for x in k2])
            # print("bi: ", macd_sum1, macd_sum2)
            if macd_sum1 < macd_sum2 * adjust:
                bc = True

        elif mode == 'xd':
            assert zs1['direction'] in ['down', 'up'], "走势的 direction 定义错误,可取值为 up 或 down"
            assert zs2['direction'] in ['down', 'up'], "走势的 direction 定义错误,可取值为 up 或 down"

            if zs1['direction'] == "down":
                macd_sum1 = sum([abs(x['macd']) for x in k1 if x['macd'] < 0])
            else:
                macd_sum1 = sum([abs(x['macd']) for x in k1 if x['macd'] > 0])

            if zs2['direction'] == "down":
                macd_sum2 = sum([abs(x['macd']) for x in k2 if x['macd'] < 0])
            else:
                macd_sum2 = sum([abs(x['macd']) for x in k2 if x['macd'] > 0])

            # print("xd: ", macd_sum1, macd_sum2)
            if macd_sum1 < macd_sum2 * adjust:
                bc = True

        else:
            raise ValueError("mode value error")

        return bc
Z
zengbin93 已提交
700