utils.py 9.1 KB
Newer Older
Z
zengbin93 已提交
1 2 3
# coding: utf-8

import pandas as pd
4 5 6
import mplfinance as mpf
import matplotlib as mpl
import matplotlib.pyplot as plt
Z
zengbin93 已提交
7
from .plot import kline_pro
Z
zengbin93 已提交
8 9


Z
zengbin93 已提交
10
def ka_to_image(ka, file_image, mav=(5, 20, 120, 250), max_k_count=1000, dpi=50):
11
    """绘制 ka,保存到 file_image"""
Z
zengbin93 已提交
12
    df = ka.to_df(use_macd=True, ma_params=(5, 20,), max_count=max_k_count)
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 40 41 42 43
    df.rename({"open": "Open", "close": "Close", "high": "High",
               "low": "Low", "vol": "Volume"}, axis=1, inplace=True)
    df.index = pd.to_datetime(df['dt'])
    df = df.tail(max_k_count)
    kwargs = dict(type='candle', mav=mav, volume=True)

    bi_xd = [
        [(x['dt'], x['bi']) for _, x in df.iterrows() if x['bi'] > 0],
        [(x['dt'], x['xd']) for _, x in df.iterrows() if x['xd'] > 0]
    ]

    mc = mpf.make_marketcolors(
        up='red',
        down='green',
        edge='i',
        wick='i',
        volume='in',
        inherit=True)

    s = mpf.make_mpf_style(
        gridaxis='both',
        gridstyle='-.',
        y_on_right=False,
        marketcolors=mc)

    mpl.rcParams['font.sans-serif'] = ['KaiTi']
    mpl.rcParams['font.serif'] = ['KaiTi']
    mpl.rcParams['font.size'] = 48
    mpl.rcParams['axes.unicode_minus'] = False
    mpl.rcParams['lines.linewidth'] = 1.0

Z
zengbin93 已提交
44
    title = '%s@%s(%s - %s)' % (ka.symbol, ka.name, df.index[0].__str__(), df.index[-1].__str__())
45 46 47 48 49
    fig, axes = mpf.plot(df, columns=['Open', 'High', 'Low', 'Close', 'Volume'], style=s,
                         title=title, ylabel='K线', ylabel_lower='成交量', **kwargs,
                         alines=dict(alines=bi_xd, colors=['r', 'g'], linewidths=8, alpha=0.35),
                         returnfig=True)

50
    w = len(df) * 0.15
51 52
    fig.set_size_inches(w, 30)
    ax = plt.gca()
53
    ax.set_xbound(-1, len(df) + 1)
54
    fig.savefig(fname=file_image, dpi=dpi, bbox_inches='tight')
Z
zengbin93 已提交
55
    plt.close()
56 57


Z
zengbin93 已提交
58 59 60
def ka_to_echarts(ka,
                  width: str = "1500px",
                  height: str = '800px'):
Z
zengbin93 已提交
61 62 63 64 65 66 67
    """用 pyecharts 绘制分析结果

    :param ka: KlineAnalyze
    :param width: str
    :param height: str
    :return:
    """
Z
zengbin93 已提交
68 69 70
    chart = kline_pro(ka.kline_raw, ma=ka.ma, macd=ka.macd, fx=ka.fx_list,
                      bi=ka.bi_list, xd=ka.xd_list, width=width, height=height)
    return chart
Z
zengbin93 已提交
71

Z
zengbin93 已提交
72 73
class KlineGenerator:
    """K线生成器,仿实盘"""
74 75 76 77 78 79 80 81 82 83 84 85 86 87

    def __init__(self, max_count=5000, freqs=None):
        """

        :param max_count: int
            最大K线数量
        :param freqs: list of str
            级别列表,默认值为 ['周线', '日线', '60分钟', '30分钟', '15分钟', '5分钟', '1分钟']
        """
        self.max_count = max_count
        if freqs is None:
            self.freqs = ['周线', '日线', '60分钟', '30分钟', '15分钟', '5分钟', '1分钟']
        else:
            self.freqs = freqs
Z
zengbin93 已提交
88 89 90 91 92 93 94
        self.m1 = []
        self.m5 = []
        self.m15 = []
        self.m30 = []
        self.m60 = []
        self.D = []
        self.W = []
95 96
        self.end_dt = None
        self.symbol = None
Z
zengbin93 已提交
97 98

    def __repr__(self):
99
        return "<KlineGenerator for {}; latest_dt={}>".format(self.symbol, self.end_dt)
Z
zengbin93 已提交
100 101 102 103 104 105 106 107 108 109 110 111 112

    def update(self, k):
        """输入1分钟最新K线,更新其他级别K线

        :param k: dict
            {'symbol': '000001.XSHG',
             'dt': Timestamp('2020-07-16 14:51:00'),  # 必须是K线结束时间
             'open': 3216.8,
             'close': 3216.63,
             'high': 3216.95,
             'low': 3216.2,
             'vol': '270429600'}
        """
113 114 115
        self.end_dt = k['dt']
        self.symbol = k['symbol']

116
        # 更新1分钟线
117 118
        if "1分钟" in self.freqs:
            if not self.m1:
119 120
                self.m1.append(k)
            else:
121 122 123 124 125 126 127
                if k['dt'] > self.m1[-1]['dt']:
                    self.m1.append(k)
                elif k['dt'] == self.m1[-1]['dt']:
                    self.m1[-1] = k
                else:
                    raise ValueError("1分钟新K线的时间必须大于等于最后一根K线的时间")
            self.m1 = self.m1[-self.max_count:]
Z
zengbin93 已提交
128 129

        # 更新5分钟线
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
        if "5分钟" in self.freqs:
            if not self.m5:
                self.m5.append(k)
            last_m5 = self.m5[-1]
            if last_m5['dt'].minute % 5 == 0 and k['dt'].minute % 5 != 0:
                self.m5.append(k)
            else:
                new = dict(last_m5)
                new.update({
                    'close': k['close'],
                    "dt": k['dt'],
                    "high": max(k['high'], last_m5['high']),
                    "low": min(k['low'], last_m5['low']),
                    "vol": k['vol'] + last_m5['vol']
                })
                self.m5[-1] = new
            self.m5 = self.m5[-self.max_count:]
Z
zengbin93 已提交
147 148

        # 更新15分钟线
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
        if "15分钟" in self.freqs:
            if not self.m15:
                self.m15.append(k)
            last_m15 = self.m15[-1]
            if last_m15['dt'].minute % 15 == 0 and k['dt'].minute % 15 != 0:
                self.m15.append(k)
            else:
                new = dict(last_m15)
                new.update({
                    'close': k['close'],
                    "dt": k['dt'],
                    "high": max(k['high'], last_m15['high']),
                    "low": min(k['low'], last_m15['low']),
                    "vol": k['vol'] + last_m15['vol']
                })
                self.m15[-1] = new
            self.m15 = self.m15[-self.max_count:]
Z
zengbin93 已提交
166 167

        # 更新30分钟线
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
        if "30分钟" in self.freqs:
            if not self.m30:
                self.m30.append(k)
            last_m30 = self.m30[-1]
            if last_m30['dt'].minute % 30 == 0 and k['dt'].minute % 30 != 0:
                self.m30.append(k)
            else:
                new = dict(last_m30)
                new.update({
                    'close': k['close'],
                    "dt": k['dt'],
                    "high": max(k['high'], last_m30['high']),
                    "low": min(k['low'], last_m30['low']),
                    "vol": k['vol'] + last_m30['vol']
                })
                self.m30[-1] = new
            self.m30 = self.m30[-self.max_count:]
Z
zengbin93 已提交
185 186

        # 更新60分钟线
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
        if "60分钟" in self.freqs:
            if not self.m60:
                self.m60.append(k)
            last_m60 = self.m60[-1]
            if last_m60['dt'].minute % 60 == 0 and k['dt'].minute % 60 != 0:
                self.m60.append(k)
            else:
                new = dict(last_m60)
                new.update({
                    'close': k['close'],
                    "dt": k['dt'],
                    "high": max(k['high'], last_m60['high']),
                    "low": min(k['low'], last_m60['low']),
                    "vol": k['vol'] + last_m60['vol']
                })
                self.m60[-1] = new
            self.m60 = self.m60[-self.max_count:]
Z
zengbin93 已提交
204 205

        # 更新日线
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
        if "日线" in self.freqs:
            if not self.D:
                self.D.append(k)
            last_d = self.D[-1]
            if k['dt'].date() != last_d['dt'].date():
                self.D.append(k)
            else:
                new = dict(last_d)
                new.update({
                    'close': k['close'],
                    "dt": k['dt'],
                    "high": max(k['high'], last_d['high']),
                    "low": min(k['low'], last_d['low']),
                    "vol": k['vol'] + last_d['vol']
                })
                self.D[-1] = new
            self.D = self.D[-self.max_count:]
Z
zengbin93 已提交
223 224

        # 更新周线
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
        if "周线" in self.freqs:
            if not self.W:
                self.W.append(k)
            last_w = self.W[-1]
            if k['dt'].weekday() == 0 and k['dt'].weekday() != last_w['dt'].weekday():
                self.W.append(k)
            else:
                new = dict(last_w)
                new.update({
                    'close': k['close'],
                    "dt": k['dt'],
                    "high": max(k['high'], last_w['high']),
                    "low": min(k['low'], last_w['low']),
                    "vol": k['vol'] + last_w['vol']
                })
                self.W[-1] = new
            self.W = self.W[-self.max_count:]
Z
zengbin93 已提交
242 243

    def get_kline(self, freq, count):
Z
zengbin93 已提交
244 245 246 247 248 249 250 251
        """获取单个级别的K线

        :param freq: str
            级别名称,可选值 1分钟;5分钟;15分钟;30分钟;60分钟;日线;周线
        :param count: int
            数量
        :return: list of dict
        """
252 253 254
        freqs_map = {"1分钟": self.m1, "5分钟": self.m5, "15分钟": self.m15,
                     "30分钟": self.m30, "60分钟": self.m60, "日线": self.D, "周线": self.W}
        return [dict(x) for x in freqs_map[freq][-count:]]
Z
zengbin93 已提交
255 256

    def get_klines(self, counts=None):
Z
zengbin93 已提交
257 258 259 260 261 262
        """获取多个级别的K线

        :param counts: dict
            默认值 {"1分钟": 1000, "5分钟": 1000, "30分钟": 1000, "日线": 100}
        :return: dict of list of dict
        """
Z
zengbin93 已提交
263 264
        if counts is None:
            counts = {"1分钟": 1000, "5分钟": 1000, "30分钟": 1000, "日线": 100}
265
        return {k: self.get_kline(k, v) for k, v in counts.items()}