第五階段:動手 Lab
Lab 03:自定義指標與策略動手實作:打造專屬於你的獨家指標
目標
- 學習如何繼承
Indicator基類 - 實現一個 Z-Score 指標(均值回歸常用)
- 將指標整合進
Strategy - 編寫基於 Z-Score 的 Bolinger Band 策略邏輯
步驟 1:實現 Z-Score 指標
Z-Score = (當前價格 – 平均值) / 標準差。
它反映價格偏離均值的程度,通常超過 +2 或低於 -2 視為超買/超賣。
from nautilus_trader.indicators.base.indicator import Indicator
from nautilus_trader.indicators.average.sma import SimpleMovingAverage
from nautilus_trader.indicators.volatility.std_dev import StandardDeviation
class ZScore(Indicator):
def __init__(self, period: int):
super().__init__() # 必須調用父類構造函數
self.period = period
# 組合現有指標
self.sma = SimpleMovingAverage(period)
self.std = StandardDeviation(period)
self._value = 0.0
@property
def value(self) -> float:
return self._value
@property
def initialized(self) -> bool:
return self.sma.initialized and self.std.initialized
def update(self, price: float):
# 1. 更新內部指標
self.sma.update(price)
self.std.update(price)
# 2. 如果內部指標未初始化,直接返回
if not self.initialized:
return
# 3. 計算 Z-Score
# 防止除以零
if self.std.value == 0:
self._value = 0.0
else:
self._value = (price - self.sma.value) / self.std.value
# 4. 觸發更新事件(可選,如果其他組件訂閱了此指標)
self._set_value(self._value)💡 組合 vs 繼承
這裡是組合 (Composition) 的最佳範例。我們沒有從零計算均值和標準差,而是直接使用了現有的 SMA 和 StdDev 指標。這樣代碼更簡潔、更不容易出錯。
步驟 2:編寫 Mean Reversion 策略
策略邏輯:
- Z-Score > 2.0:價格過高,做空
- Z-Score < -2.0:價格過低,做多
- Z-Score 回歸到 0 附近(如 -0.5 ~ 0.5):平倉
from nautilus_trader.trading.strategy import Strategy
from nautilus_trader.config import StrategyConfig
from nautilus_trader.model.enums import OrderSide
class MeanReversionConfig(StrategyConfig):
instrument_id: str
period: int = 20
entry_z: float = 2.0
exit_z: float = 0.5
trade_size: str = "0.01"
class MeanReversionStrategy(Strategy):
def __init__(self, config: MeanReversionConfig):
super().__init__(config)
# 實例化我們的自定義指標
self.z_score = ZScore(config.period)
def on_bar(self, bar):
# 更新指標
self.z_score.update(bar.close)
if not self.z_score.initialized:
return
z = self.z_score.value
self.log.info(f"Z-Score: {z:.2f}")
# 獲取持倉
pos = self.portfolio.position(self.config.instrument_id)
# --- 進場邏輯 ---
if not pos:
if z < -self.config.entry_z:
# 價格過低 -> 買入
self.buy()
elif z > self.config.entry_z:
# 價格過高 -> 賣出
self.sell()
# --- 出場邏輯 ---
else:
if pos.is_long and z > -self.config.exit_z:
# 多單:回歸到均線下方附近 -> 平倉
self.close_all_positions(self.config.instrument_id)
elif pos.is_short and z < self.config.exit_z:
# 空單:回歸到均線上方附近 -> 平倉
self.close_all_positions(self.config.instrument_id)
def buy(self):
order = self.order_factory.market(
instrument_id=self.config.instrument_id,
order_side=OrderSide.BUY,
quantity=self.config.trade_size,
)
self.submit_order(order)
def sell(self):
order = self.order_factory.market(
instrument_id=self.config.instrument_id,
order_side=OrderSide.SELL,
quantity=self.config.trade_size,
)
self.submit_order(order)步驟 3:回測驗證
現在你可以把這個策略放到 Lab 01 的 run_backtest.py 中運行,看看均值回歸策略在 ETHUSDT 上的表現如何。
作業
📝 挑戰任務
1. 嘗試修改 ZCheck 指標,加入「動量過濾」:只有當 RSI 同時處於超賣區(<30)時,才發出做多信號(Z < -2)。
2. 嘗試將此策略部署到 Lab 02 的沙箱環境中,觀察實時運行的效果。
3. 進階挑戰:實現一個 SuperTrend 指標(需要計算 ATR)。