天天看點

量化投資從0開始系列 ---- 7. 可視化

圖形能幫助我們更直覺的觀察資料的變化。選BOLL和MACD這兩個經典的名額繪制一下。

前面已經把日k線和複權因子的資料都儲存到本地資料庫了,這裡就直接從資料庫取出來用。

因為資料庫儲存的是未複權的價格,建一個view把前複權的價格算出來。

CREATE 
    ALGORITHM = UNDEFINED 
    DEFINER = `tushare`@`localhost` 
    SQL SECURITY DEFINER
VIEW `view_qfq` AS
    SELECT 
        `d`.`ts_code` AS `ts_code`,
        `d`.`trade_date` AS `trade_date`,
        CAST(((`d`.`open` * `f`.`adj_factor`) / `c`.`adj_factor`)
            AS DECIMAL (8 , 3 )) AS `open`,
        CAST(((`d`.`high` * `f`.`adj_factor`) / `c`.`adj_factor`)
            AS DECIMAL (8 , 3 )) AS `high`,
        CAST(((`d`.`low` * `f`.`adj_factor`) / `c`.`adj_factor`)
            AS DECIMAL (8 , 3 )) AS `low`,
        CAST(((`d`.`close` * `f`.`adj_factor`) / `c`.`adj_factor`)
            AS DECIMAL (8 , 3 )) AS `close`,
        `d`.`vol` AS `volume`
    FROM
        ((`stock_daily` `d`
        JOIN `stock_adj_factor` `f`)
        JOIN (SELECT 
            `t1`.`ts_code` AS `ts_code`,
                `t1`.`adj_factor` AS `adj_factor`
        FROM
            (`stock_adj_factor` `t1`
        JOIN (SELECT 
            `stock_adj_factor`.`ts_code` AS `ts_code`,
                MAX(`stock_adj_factor`.`trade_date`) AS `td`
        FROM
            `stock_adj_factor`
        GROUP BY `stock_adj_factor`.`ts_code`) `t2`)
        WHERE
            ((`t1`.`ts_code` = `t2`.`ts_code`)
                AND (`t1`.`trade_date` = `t2`.`td`))) `c`)
    WHERE
        ((`d`.`ts_code` = `f`.`ts_code`)
            AND (`d`.`trade_date` = `f`.`trade_date`)
            AND (`d`.`ts_code` = `c`.`ts_code`))
           

繪制一個圖表可以看作三個步驟:

        1. 擷取資料

        2. 計算名額

        3.繪制

定義一個父類,把不變的部分固化下來,不同的圖表隻需要通過繼承,注入特有的部分即可。

擷取資料都是通路前面建立的view,而股票編号和時間段是可指定的。是以在父類定義一個get_data的方法:

def __init__(self, ts_code, start_date, end_date):
        self._data = self.get_data(ts_code, start_date, end_date)
    
    def get_data(self, ts_code, start_date, end_date):
        df_qfq = pd.read_sql_query(
            f"SELECT * "
            f"FROM view_qfq "
            f"where ts_code='{ts_code}' "
            f"and trade_date >= \'{start_date if start_date is not None else '19700101'}\' "
            f"and trade_date < \'{end_date if end_date is not None else '20991231'}\' "
            f"order by trade_date",
            engine_ts)
        df_qfq.index = pd.to_datetime(df_qfq['trade_date'])
        df_qfq.drop(columns=['trade_date'], inplace=True)

        return df_qfq[['open', 'high', 'low', 'close', 'volume']]
           

計算名額的方式各不相同,留一個空的方法讓子類去實作:

def _get_plots(self):
    pass
           

繪制時調用mplfinance這個module,參數都給定好預設值,保證真正繪制的時候,不需要再設定什麼參數就可以執行,另一方面,如果子類想改變預設行為,可以覆寫父類的方法達到目的。

def _get_default_arguments(self, plots=None):
        arguments = {
            'type': 'candle',
            'style': mpf.make_mpf_style(
                facecolor='black',
                marketcolors=mpf.make_marketcolors(up='red', down='cyan', volume='blue', inherit=True),
                gridaxis='horizontal',
                gridstyle='--',
                y_on_right=False),
            'figratio': (1920, 1080),
            'figscale': 2,
            'volume': True
        }

        addplot = plots if plots is not None else self._get_plots()
        if addplot is not None:
            arguments['addplot'] = addplot

        return arguments

    def show(self, *args, **kwargs):
        arguments = self._get_default_arguments()
        mpf.plot(self._data, **arguments)
           

BOLL

有了父類的基礎上,實作BOLL的時候,隻需要計算出BOLL名額的三條線即可。

class BollChart(AbstractChart):
    def __init__(self, ts_code, start_date, end_date):
        super().__init__(ts_code, start_date, end_date)

    def _get_plots(self):
        upper, middle, lower = talib.BBANDS(self._data.close, timeperiod=20, nbdevup=2, nbdevdn=2, matype=MA_Type.SMA)

        add_plot = [
            mpf.make_addplot(upper, color='y'),
            mpf.make_addplot(middle, color='w'),
            mpf.make_addplot(lower, color='m')
        ]

        return add_plot
           

效果圖

量化投資從0開始系列 ---- 7. 可視化

MACD

同樣的道理,MACD也隻需要計算出名額線的資料即可。MACD稍微做得複雜一點,除了用talib計算MACD本身的三條線,還按macdhist手動簡單計算了買點和賣點。

class MacdChart(AbstractChart):
    def __init__(self, ts_code, start_date, end_date):
        super().__init__(ts_code, start_date, end_date)

    def _get_plots(self):
        DIFF, DEA, MACDHIST = talib.MACD(self._data.close, fastperiod=12, slowperiod=20, signalperiod=9)

        ORIGIN_MACD_HIST_UP = MACDHIST * 2
        ORIGIN_MACD_HIST_UP[ORIGIN_MACD_HIST_UP < 0] = 0

        ORIGIN_MACD_HIST_DOWN = MACDHIST * 2
        ORIGIN_MACD_HIST_DOWN[ORIGIN_MACD_HIST_DOWN >= 0] = 0

        signals = self.__signal_all_fn(MACDHIST)
        signal_buys = self.__signal_buy_fn(signals)
        signal_sells = self.__signal_sell_fn(signals)

        add_plot = [
            mpf.make_addplot(DIFF, panel=2, color='fuchsia', secondary_y=True),
            mpf.make_addplot(DEA, panel=2, color='w', secondary_y=True),
            mpf.make_addplot(ORIGIN_MACD_HIST_UP, type='bar', panel=2, color='r', secondary_y=True),
            mpf.make_addplot(ORIGIN_MACD_HIST_DOWN, type='bar', panel=2, color='cyan', secondary_y=True),
            mpf.make_addplot(signal_buys, type='scatter', markersize=50, marker='^', color='m'),
            mpf.make_addplot(signal_sells, type='scatter', markersize=50, marker='v', color='yellow')
        ]

        return add_plot

    def __signal_all_fn(self, MACDHIST):
        signal_tip = np.where(MACDHIST > 0, 1, -1)

        df_new = pd.DataFrame(signal_tip, self._data.index)

        signal = np.sign(df_new - df_new.shift(1))
        return signal

    def __signal_buy_fn(self, signals):
        temp = copy.deepcopy(signals)
        temp[temp <= 0] = None
        signal_buy = []

        min = self._data['low'].min()
        max = self._data['high'].max()

        padding = (max - min) / 25

        for index, row in temp.iterrows():
            if row[0] > 0:
                signal_buy.append(self._data[self._data.index == index]['low'] - padding)
            else:
                signal_buy.append(np.nan)

        return np.array(signal_buy, dtype=object)

    def __signal_sell_fn(self, signals):
        temp = copy.deepcopy(signals)
        temp[temp >= 0] = None
        signal_sell = []

        min = self._data['low'].min()
        max = self._data['high'].max()

        padding = (max - min) / 25

        for index, row in temp.iterrows():
            if row[0] < 0:
                signal_sell.append(self._data[self._data.index == index]['high'] + padding)
            else:
                signal_sell.append(np.nan)

        return np.array(signal_sell, dtype=object)
           

效果圖:

量化投資從0開始系列 ---- 7. 可視化