趨勢跟隨策略實作:Python 訊號、回報與財富曲線|Quant Theory Lab 3 筆記
第 20 課・共 34 課
目錄
這是 Quant Theory 課程的第三個實驗課(Lab 3),把第 11 講講解過的雙均線趨勢跟隨 Trend Following 策略完整實作出來。Liu Peng 在片中用 pandas 與 numpy,由交易訊號、對數回報、交易動作一路計算到財富曲線,最後與買入持有基準比較績效。本篇筆記按實作步驟重構整理,方便讀者對照原片重做一次。
學習重點
- 用
np.where比較短、長兩條移動平均線,產生取值 1、0、−1 的交易訊號欄。 - 用
np.log(...).diff()計算買入持有策略的對數回報,作為比較基準。 - 策略即期回報只需一行:訊號乘以基準對數回報,方向自動對調。
- 對訊號欄做
diff()推導交易動作:+2 是由空翻多,−2 是由多翻空。 - 財富曲線用
np.exp(...).cumprod()繪製,期末取最後一值減一即總回報。
詳細筆記
實驗起點:數據框的準備
承接之前實驗的成果,數據框 df2 已包含以下時間序列欄位:調整後收市價 Adjusted Close、短窗口簡單移動平均線(SMA-3)、長窗口簡單移動平均線(SMA-20),以及兩條不同平滑參數的指數移動平均線(EMM_0.1 與 EMM_0.5)。用 df2.info() 檢視,索引是 2022 年 1 月至 12 月的日期時間,共 251 個交易日,五個欄位全部為浮點數。由於長均線需要 20 日數據起算,SMA-20 欄只有 232 個非空值,頭 19 日是缺失值,稍後要清理。本次實驗只動用 Adj Close、SMA-3、SMA-20 三欄,指數均線留待其他策略使用。
講者提醒,這些衍生欄位本身也是時間序列,與原始價格一樣可以逐日比較、畫圖和做運算——這正是整個實驗的操作對象。
第一步:產生交易訊號
講者先釐清「訊號」的定義:訊號就是每期應該持有的倉位方向,由兩條均線的相對關係決定。短均線對近期價格反應快,代表短期動能;長均線反應慢,代表長期趨勢。當短均線由下方升穿長均線,代表短期動能轉強,應持好倉;當短均線跌穿長均線,動能轉弱,應持淡倉。數學上:
$$ S_t = \begin{cases} 1, & \text{SMA}_t^{(3)} > \text{SMA}_t^{(20)} \\ -1, & \text{SMA}_t^{(3)} < \text{SMA}_t^{(20)} \\ 0, & \text{兩者相等} \end{cases} $$實作分兩行完成:第一個 np.where 建立買入訊號——短均線高於長均線取 1,否則暫時取 0;第二個 np.where 疊加賣出訊號——短均線低於長均線時改寫為 −1,否則保留上一行的原值。這種「先寫正向、再補反向」的寫法,比一次過寫巢狀條件易讀,也方便除錯。最後用 dropna 清除長均線起算期留下的缺失值,避免之後計算回報時出現錯誤:
# 摘錄自原片教學
df2['signal'] = np.where(df2['SMA-3'] > df2['SMA-20'], 1, 0)
df2['signal'] = np.where(df2['SMA-3'] < df2['SMA-20'], -1, df2['signal'])
df2.dropna(inplace=True)
數據框結構與訊號欄的產生方式。截圖出自原片(Liu Peng)
檢視數據框首幾行,可見 signal 欄取值 1 或 −1,若兩線剛好相等則為 0(實務上極少出現)。講者接着用 value_counts() 做頻率統計:−1 出現 135 次、1 出現 96 次,即超過一半時間策略處於淡倉。這與 2022 年該時段市況整體向下吻合——跌市中短均線長期壓在長均線之下,策略自然多數時間造淡。這一步也揭示趨勢跟隨的特性:訊號不是預測,而是對已發生價格關係的系統化反應。
第二步:建立基準——買入持有的對數回報
要比較策略表現,需要一條基準線。講者選用最簡單的買入並持有 Buy-and-Hold:在期初買入一個單位資產,全程持有到期末,期間不做任何操作。這個策略的每期回報,就是價格本身的對數回報 Log Return:
$$ r_t = \ln P_t - \ln P_{t-1} = \ln\!\left(\frac{P_t}{P_{t-1}}\right) $$先對價格取對數、再做一階差分,等價於原價格相除後取對數——對數函數把除法變減法,這正是對數回報的定義。程式只需一行:np.log(df2['Adj Close']).diff(),新欄命名為 log_return_buy_n_hold。印出首幾行核對,價格欄與對數回報欄並排,第一日因沒有前期數據而為空值。往後所有策略都與這條基準線比較,做法如同學術論文中的對照組。
第三步:趨勢跟隨策略的即期回報
由基準回報變成策略回報,只需乘上訊號,方向自動修正:
$$ r_t^{TF} = S_t \times r_t $$逐種情況檢驗這條乘法的邏輯:價格上升($r_t > 0$)時,持好倉($S_t = 1$)錄得正回報,持淡倉($S_t = -1$)則變成虧損;價格下跌($r_t < 0$)時反轉,持淡倉因負負得正而獲利,持好倉則虧錢。一行乘法已完整捕捉「倉位方向 × 價格方向」的四種組合,是整個實驗最精煉的一步:
# 摘錄自原片教學
df2['log_return_trend_follow'] = df2['signal'] * df2['log_return_buy_n_hold']
策略回報欄與 action 欄的計算。截圖出自原片(Liu Peng)
對照數據框可驗證:signal 為 1 的日子,兩欄回報完全相同;signal 為 −1 的日子,策略回報是基準回報的正負倒轉。值得留意這裏的隱含假設:當期訊號直接乘以當期回報,即假設訊號在期初已生效、建倉沒有延遲,亦未扣除任何交易成本。講者在片尾特別提醒,假設一變,財富曲線就會變。
第四步:由訊號推導交易動作
訊號欄記錄「應持甚麼倉」,但未交代「何時要操作」。交易動作欄補上這一塊——對訊號做差分:
$$ A_t = S_t - S_{t-1} $$三種情況:$A_t = 0$ 表示倉位不變、繼續持有,無需操作;$A_t = +2$ 表示由淡倉(−1)翻向好倉(+1),即平掉淡倉再買入;$A_t = -2$ 表示由好倉翻向淡倉,即平掉好倉再造淡。程式是 df2['action'] = df2['signal'].diff(),再用 value_counts() 統計:全年 217 日無需操作,+2 與 −2 各出現 7 次。換言之,全年只有 14 個交易動作,其餘時間都是靜坐持有——這正是機械式趨勢策略的節奏,與每日進出的短炒完全相反。
講者把兩條均線與交易動作畫在同一張圖:藍線是收市價,橙線是 SMA-3,綠線是 SMA-20;綠色向上三角標記買入點(action 為 +2),紅色向下三角標記賣出點(action 為 −2),用較大的標記尺寸突出顯示。圖中可見每個三角都落在均線交叉 Crossover 的位置:橙線由上向下穿過綠線,出現紅三角;由下向上穿過,出現綠三角。他同時指出背後的假設:平掉現有倉位與建立反向新倉視為同一瞬間完成,沒有考慮滑點、買賣差價與成交時間。
SMA-3 與 SMA-20 交叉時的買賣標記。截圖出自原片(Liu Peng)
第五步:財富曲線與期末回報
最後比較兩個策略的財富演變。把對數回報用指數函數轉回「一加簡單回報」形式,再取累積乘積:
$$ W_t = \prod_{k=1}^{t} e^{r_k} = \prod_{k=1}^{t} (1 + R_k) $$講者補充,這與「先對對數回報取累積和、再取指數」完全等價:$\exp(\sum_{k=1}^{t} r_k) = \prod_{k=1}^{t} e^{r_k}$,指數函數把加法變回乘法。財富曲線起點為 1,代表期初投入一元本金,之後每點數值即該元本金的演變。兩個策略各算一條,畫在同一張圖上:
# 摘錄自原片教學
plt.plot(np.exp(df2['log_return_buy_n_hold']).cumprod(), label='Buy-n-hold')
plt.plot(np.exp(df2['log_return_trend_follow']).cumprod(), label='Trend following')
plt.legend(loc=2)
趨勢跟隨明顯跑贏買入持有。截圖出自原片(Liu Peng)
圖中兩線同由 1.0 起步,但其後漸行漸遠:藍線(買入持有)大部分時間在 0.8 至 1.0 之間浮沉,年末跌至約 0.75;橙線(趨勢跟隨)則拾級而上,十一月曾觸及 1.9 附近,年末收於約 1.8。期末回報的計法是取累積乘積最後一值再減一:買入持有約 −25.2%,趨勢跟隨約 +80.4%(截至 2022 年末的回測數字)。原因不難理解——該年資產整體下跌,買入持有全程捱打,而趨勢跟隨大部分時間持淡倉,跌市反而成為利潤來源。
講者收結時再次強調:結果依賴多項簡化假設——無交易成本、訊號即時生效、可即時平倉兼轉倉、回報計算方式亦有多種選擇。改動任何一項,財富曲線都會不同。這個亮麗數字應視為教學示範,展示計算流程多於證明策略優越,切勿當作績效承諾。
🙏 覺得內容有用?本站所有內容免費提供,使用推薦碼開戶就是支持我們繼續營運的最大鼓勵:
ZA Bank 邀請碼 P20923
—— 你享 HKD 2,000 迎新獎賞,本站獲得營運支持。
立即開戶 →
(聯盟連結|投資涉及風險,受條款及細則約束,此資料僅可於香港境內分發)
欣利克的看法
- 訊號與回報的時間對齊是常見誤區。片中寫法假設第 $t$ 期訊號賺取第 $t$ 期回報,即收市產生訊號、同一刻已建倉。真實操作最早也要下一交易日才成交,較嚴謹的做法是把訊號延後一期(
shift(1))再乘回報,否則會引入前視偏差,回測結果必然偏樂觀。 - 全年只有 14 個動作,樣本太少。七次交叉就決定了整條財富曲線,任何一次交叉的時機稍有差池,結果都可能改寫。單一年份、單一資產的回測只能說明「這段歷史上行得通」,不足以推斷策略有穩定優勢。
- +80% 與 −25% 的對比要放回時空脈絡。2022 年是單邊跌市,恰好是趨勢跟隨最如魚得水的環境;換成橫行反覆的年份,雙均線策略往往被來回假訊號消磨。比較策略優劣,應看橫跨不同市況的多年數據,而非揀選對自己有利的時段。
- 成本與執行假設不可省略。每次翻倉涉及兩筆交易(平倉加開倉),加上滑點與借貨造淡成本,14 個動作累計的摩擦不容忽視。教學示範略去這些是為了聚焦概念,自行回測時應逐項加回。
- 對數回報轉財富曲線的數學值得記牢。$e^{\sum r_k} = \prod (1+R_k)$ 這個等價關係在之後的課堂與實作會反覆出現,理解它比拼湊程式碼更重要。
名詞解釋
- 趨勢跟隨 Trend Following:順着已形成的價格趨勢持倉的交易策略類型。
- 簡單移動平均線 SMA:將固定窗口內價格取算術平均的技術指標。
- 均線交叉 Crossover:兩條不同週期均線互相穿越所形成的交易訊號。
- 對數回報 Log Return:價格取對數後相減所得的回報度量,方便跨期累加。
- 財富指數 Wealth Index:以本金一元為起點、按回報累積乘積描繪的資產曲線。
- 回測 Backtesting:用歷史數據模擬策略表現的驗證方法。
常見問題 FAQ
為甚麼訊號欄會出現 0?
初始寫法先用 np.where 產生 1 與 0,第二行才把短均線低於長均線的情況改為 −1。若兩條均線剛好相等,兩個條件都不成立,訊號便保留為 0。實務上完全相等極少發生,0 主要是程式邏輯的預設值。
action 欄的 +2 和 −2 代表甚麼?
action 是訊號欄的差分。由 −1(淡倉)變 +1(好倉),差值是 +2,代表買入翻多;由 +1 變 −1,差值是 −2,代表賣出翻空;數值為 0 則代表倉位不變。片中的統計顯示全年 +2 與 −2 各出現 7 次。
財富曲線為甚麼用累積乘積而非累積和?
回報是乘法關係:本期資產等於上期資產乘以 $(1+R_t)$,所以跨期累積必須用乘積。對數回報的好處是可以先相加再取指數還原,兩條路徑數學上等價,程式上 np.exp(...).cumprod() 一步完成。
片中 +80% 的回報可以當真嗎?
不能。該數字來自 2022 年單一資產的教學示範,未計交易成本、滑點與執行延遲,且假設訊號即時生效。講者本人亦提醒假設一改結果就變,應視為概念演示而非績效參考。
原影片
本文整理自以下影片,版權屬原創作者所有,建議配合原片觀看: