月K策略 [附程式碼]


〈Python程式碼在文章最下方〉

說到看盤,許多人都習慣看K線圖,但是看K線圖到底在看什麼呢?

除了尋找特定型態、畫趨勢線、找支撐/壓力…

我們還可以直接觀察K線特性,今天分享的策略,就是觀察月K得來的,量化操盤手觀察到月K有一個特性:月K下影線比上影線更常出現,而且下影線比較長。

於是,每個月當中「如果跌深,就搶反彈」這個策略就浮現了,因為下影線就是跌深反彈造成的。

〈定義〉
最大跌幅:(Low / Open - 1) * 100%
反彈:(Close / Low - 1) * 100%
最大漲幅:(High / Open - 1) * 100%
拉回:(Close / High - 1) * 100%

首先,當然要拿數據驗證這個想法,下圖第二象限畫的是(最大跌幅, 反彈),第四象限畫的是(最大漲幅, 拉回)。


我們可以觀察到月K的最大跌幅極端值要比最大漲幅極端的多,這就是一般來說Put都比Call貴的原因,因為莊家要承受的風險較大。

我們先看第四象限(紅點),呈現出一種明顯趨勢:漲得越多、拉回越少。

我們再看第二象限(綠點),不難看出:跌深反彈高。

根據初步分析的結果,除了搶反彈策略,也可加入追漲策略。

嘗試了幾組數字後,決定採用-4%及1%當作策略參數。

交易策略:
1.從月初起算,當月跌幅達到-4%時,做多搶反彈。
2.從月初起算,當月漲幅達到1%時,做多追漲。

接下來,進行績效分析,下圖綠線是搶反彈策略的累積報酬,紅線是追漲策略的累積報酬,藍線代表兩個策略都執行。


乍看之下,有兩個(暫時)結論:
第一,追漲策略績效比搶反彈策略好多了。
第二,兩個策略合計7年多下來賺了90%,績效似乎差強人意。

如果研究只做到這邊,往往會得到錯誤的結論,我們再繼續看下去。

下圖一,綠線是搶反彈策略每次執行的平均報酬,紅線是追漲策略每次執行的平均報酬,隨著樣本增加,兩個策略的平均報酬都非常穩定。

下圖二,綠柱表示搶反彈機會出現,紅柱表示追漲機會出現。


平均下來,搶反彈策略每次可賺1.33%,與追漲策略的0.77%相比之下高多了。

但由於月K跌深出現的機率不高,所以造成搶反彈策略累積報酬較小。

但這只代表大賺的交易機會較少出現,不過一旦機會降臨,賺錢效率可是非常高的。

最後,累積報酬不等於這7年多的交易總收入,因為這兩個策略都是屬於「機會型策略」,也就是當機會出現時,才出手交易,平時並不持有部位,而且每次交易都在一個月內完成,持倉天數平均下來可能落在15天左右。

量化交易者通常會有多套策略同時運行,監控大大小小的交易機會,當部份策略觀望休息時,資金可交由其他策略運用,如此便能提升資金效率,所以交易總收入並不會只有這兩個策略合計的90%。

Python程式碼:
# 載入需要用到的函式庫
import IPython # 互動模組
import pandas as pd # 數據分析函式庫
import matplotlib.pyplot as plt # 繪圖函式庫
from io import StringIO # 記憶體文字檔案函式
from requests import post # HTTP請求函式

# 取得歷史資料
url = "https://query1.finance.yahoo.com/v7/finance/download/^TWII?period1=0&period2=9999999999&interval=1mo&events=history&crumb=hP2rOschxO0" # 下載大盤月資料
df = pd.read_csv(StringIO(post(url).text), index_col='Date', parse_dates=['Date']) # 在記憶體內讀取網頁文字檔

# 選取資料日期
df = df.truncate('2012-1-1', '2019-9-1')

# 計算數據
df['rise'] = 100 * (df['High'] / df['Open'] - 1) # 當月最大漲幅
df['pullback'] = 100 * (df['Close'] / df['High'] - 1) # 當月拉回跌幅

df['fall'] = 100 * (df['Low'] / df['Open'] - 1) # 當月最大跌幅
df['rebound'] = 100 * (df['Close'] / df['Low'] - 1) # 當月反彈漲幅

# 設定圖片輸出位置
IPython.get_ipython().enable_matplotlib(gui='qt') # 圖片輸出於新視窗

# 繪圖-初步分析
plt.figure() # 開啟新圖
plt.scatter(df['rise'], df['pullback'], color='r', label='pullback') # 散布圖(漲幅/拉回)
plt.scatter(df['fall'], df['rebound'], color='g', label='rebound') # 散布圖(跌幅/反彈)
plt.axhline(y=0, color='k') # 加上X零軸
plt.axvline(x=0, color='k') # 加上Y零軸
plt.legend(fontsize=20) # 加上圖例
plt.show() # 顯示圖片

# 回測
df['rise_threshold'] = df['Open'] * (1 + 0.01) # 追漲門檻
df['buy_1'] = (df['High'] >= df['rise_threshold']).astype(int) # 追漲訊號是否發生
df['pnl_1'] = 100 * (df['Close'] / df['rise_threshold'] - 1) * df['buy_1'] # 追漲損益

df['fall_threshold'] = df['Open'] * (1 - 0.04) # 搶反彈門檻
df['buy_2'] = (df['Low'] <= df['fall_threshold']).astype(int) # 搶反彈訊號是否發生
df['pnl_2'] = 100 * (df['Close'] / df['fall_threshold'] - 1) * df['buy_2'] # 搶反彈損益

# 繪圖-績效比較(1)
plt.figure() # 開啟新圖
plt.plot(df.index, df['pnl_1'].cumsum(), color='r', label='Buy@rise') # 追漲策略績效
plt.plot(df.index, df['pnl_2'].cumsum(), color='g', label='Buy@fall') # 搶反彈策略績效
plt.plot(df.index, df['pnl_1'].cumsum()+df['pnl_2'].cumsum(), color='b', label='Buy@rise+fall') # 追漲+搶反彈策略績效
plt.legend(fontsize=20) # 加上圖例
plt.show() # 顯示圖片

# 繪圖-績效比較(2)
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True) # 開啟新圖(2列1行、X軸相同)
ax1.plot(df.index, df['pnl_1'].cumsum() / df['buy_1'].cumsum(), color='r', label='Buy@rise') # 追漲策略平均損益
ax1.plot(df.index, df['pnl_2'].cumsum() / df['buy_2'].cumsum(), color='g', label='Buy@fall') # 搶反彈策略平均損益
ax1.legend(fontsize=20) # 加上圖例
ax2.bar(df.index, df['buy_1'], color='r', width=10) # 顯示追漲次數
ax2.bar(df.index, -df['buy_2'], color='g', width=10) # 顯示搶反彈次數
ax2.axhline(y=0, color='k') # 加上X零軸
plt.show() # 顯示圖片