圖形能幫助我們更直覺的觀察資料的變化。選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
效果圖

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)
效果圖: