
止損是量化策略裡最容易被忽視、也最容易出問題的模組。很多新手策略回測看起來不錯,實盤一跑就崩,往往根源在止損設計有問題:要麼止損太寬導致單次虧損過大,要麼止損太緊被市場噪音訊繁觸發。
這篇文章從 北極星策略 的實際設計出發,講通用的止損設計思路。
為什麼需要止損
止損本質上是承認錯誤的機制。趨勢跟蹤策略的邏輯是:大多數時候市場沒有趨勢,策略會小虧;少數時候有趨勢,策略要大賺,用大賺覆蓋小虧。
如果沒有止損,那次"大虧"有可能把賬戶打穿,後面的大賺機會都沒了。
數學上,止損控制的是單次最大虧損,進而控制最大回撤:
最大回撤 ≈ f(止損比例, 連續虧損次數, 勝率)
策略在某段時間內 5 連敗是很正常的,如果每次虧 10%,5 連敗後賬戶還剩 59%;如果每次虧 3%,5 連敗後還剩 85%,差距非常大。
止損型別
1. 固定百分比止損
最簡單的方式:入場後虧損超過固定比例就出場。
# 入場價 40000,止損 3%
entry_price = 40000
stop_loss_pct = 0.03
stop_price_long = entry_price * (1 - stop_loss_pct) # 做多止損:38800
stop_price_short = entry_price * (1 + stop_loss_pct) # 做空止損:41200
優點: 簡單、可計算、風險固定 缺點: 忽略了市場的波動率,BTC 平時日內波動可能就有 3%,容易被噪音觸發
適用場景: 高時間週期(日線以上),或者已經驗證止損比例遠大於平均波動
2. ATR 動態止損(推薦)
ATR(Average True Range,平均真實波幅)衡量的是市場最近的波動率。用 ATR 倍數設止損,波動大的時候止損寬,波動小的時候止損窄,更貼近市場實際。
import pandas as pd
def calculate_atr(df, period=14):
"""計算 ATR"""
high = df['high']
low = df['low']
close = df['close']
tr1 = high - low
tr2 = abs(high - close.shift(1))
tr3 = abs(low - close.shift(1))
true_range = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
atr = true_range.rolling(period).mean()
return atr
def get_stop_price(entry_price, atr_value, direction, multiplier=2.0):
"""基於 ATR 的止損價"""
if direction == 'long':
return entry_price - atr_value * multiplier
else:
return entry_price + atr_value * multiplier
# 示例:BTC 1H,ATR=1200,入場 40000,做多
# 止損 = 40000 - 1200 * 2 = 37600(-6%)
ATR 倍數選擇:
- 1.0 - 1.5:較緊,訊雜比低的市場容易被觸發
- 2.0 - 3.0:適中,大多數趨勢策略的合理範圍
- 4.0+:寬鬆,適合捕捉大趨勢但單次虧損大
北極星策略在 1H 時間框架上,經過回測驗證,固定百分比止損比 ATR 止損更穩定。原因是 BTC 的 ATR 在趨勢行情裡會快速擴大,導致止損越來越寬。
3. 追蹤止損(Trailing Stop Loss)
入場後止損跟隨價格移動,鎖定浮盈,不讓盈利全部回吐。
class TrailingStop:
def __init__(self, entry_price, direction, trail_pct=0.05):
self.direction = direction
self.trail_pct = trail_pct
if direction == 'long':
self.best_price = entry_price
self.stop_price = entry_price * (1 - trail_pct)
else:
self.best_price = entry_price
self.stop_price = entry_price * (1 + trail_pct)
def update(self, current_price):
"""每根 K 線更新一次"""
if self.direction == 'long':
if current_price > self.best_price:
self.best_price = current_price
self.stop_price = current_price * (1 - self.trail_pct)
else:
if current_price < self.best_price:
self.best_price = current_price
self.stop_price = current_price * (1 + self.trail_pct)
return self.stop_price
def is_triggered(self, current_price):
if self.direction == 'long':
return current_price <= self.stop_price
else:
return current_price >= self.stop_price
追蹤止損適合的場景:
- 已經有一定浮盈,不想全部虧回去
- 趨勢行情,想持倉跑行情同時保護利潤
不適合的場景:
- 震盪行情,價格來回波動,追蹤止損頻繁觸發又頻繁進場
4. 時間止損
持倉超過一定時間沒有達到預期方向就出場,無論是否虧損:
def check_time_stop(entry_time, current_time, max_bars=48):
"""持倉超過 48 根 K 線(1H = 2天)強制出場"""
bars_held = (current_time - entry_time).total_seconds() / 3600
return bars_held >= max_bars
時間止損的邏輯:如果入場後行情遲遲不啟動,說明判斷可能有誤,解放資金去找更好的機會。
北極星策略的止損設計
不展開具體引數,但講一下設計思路:
核心原則:固定止損比例 + 冷卻期保護
用固定百分比止損而不是 ATR,原因是:
- 引數更穩定,不隨市場波動率變化
- 回測中訊號更清晰
- 配合分批止盈,整體期望值經過最佳化
止損觸發後有冷卻期,這段時間內不允許再開新倉。冷卻期的作用:
- 避免在震盪行情裡反覆被打止損
- 讓市場方向更清晰後再介入
- 減少手續費損耗
止盈和止損的配合:
好的策略,止盈設計和止損一樣重要。北極星策略採用分批止盈:
- 第一筆在較小盈利時止盈,降低風險敞口
- 持有剩餘倉位跟隨趨勢
- 最終全部止盈或被整體止損帶走
這樣的設計保證了:即使後續行情反轉,前幾筆止盈已經鎖定了部分利潤。
止損引數如何確定
回測是唯一可靠的方法,但要注意避坑:
1. 在樣本外資料驗證
用 2020-2023 年資料最佳化引數,然後用 2024-2025 年資料驗證,不能用同一段資料。
2. 關注最大回撤而不是勝率
勝率高不代表好策略,最大回撤控制在多少以內才是關鍵:
- 20% 以內:比較舒適,心理承受範圍內
- 30% 以上:大多數人在這裡扛不住會手動干預
3. 別過擬合
止損如果精確到小數點後兩位(比如 3.17%),大機率是過擬合。通用引數用整數或半整數(1%、2%、2.5%)即可。
4. 用蒙特卡洛模擬
隨機打亂交易順序,模擬 1000 種可能的序列,看最大回撤分佈:
import numpy as np
def monte_carlo_max_drawdown(returns, n_simulations=1000):
"""蒙特卡洛模擬最大回撤"""
max_drawdowns = []
for _ in range(n_simulations):
shuffled = np.random.permutation(returns)
cumulative = np.cumprod(1 + shuffled)
peak = np.maximum.accumulate(cumulative)
drawdown = (peak - cumulative) / peak
max_drawdowns.append(drawdown.max())
return {
'median': np.median(max_drawdowns),
'p95': np.percentile(max_drawdowns, 95),
'worst': max(max_drawdowns)
}
如果 p95 的最大回撤在你承受範圍內,這套止損引數才算穩健。
實盤中的止損執行
設計好止損只是第一步,實盤執行也有坑:
1. 用限價單還是市價單
- 市價單:確保成交,但滑點大(BTC 波動劇烈時可能滑點 0.1-0.5%)
- 限價單:控制成交價,但可能沒成交(價格跳過止損價)
極端行情建議用市價單,寧可付滑點也要確保執行。
2. 止損單要放到交易所
不要只在程式裡判斷,要把止損單掛到交易所:
# Binance 建立止損單
order = client.create_order(
symbol='BTCUSDT',
side='SELL',
type='STOP_MARKET',
stopPrice=stop_price,
closePosition=True, # 全倉平倉
timeInForce='GTC'
)
即使策略程式宕機,交易所裡的止損單仍然有效。
3. 定期驗證止損單是否存在
策略程式啟動時檢查掛單:
def verify_stop_loss(client, symbol):
"""驗證止損單是否存在"""
open_orders = client.get_open_orders(symbol=symbol)
stop_orders = [o for o in open_orders if o['type'] == 'STOP_MARKET']
if not stop_orders:
print("⚠️ 警告:止損單不存在!")
# 重新掛單邏輯
延伸閱讀:
📷 封面圖 · Flux Schnell AI 生成 · panshi.io 品牌風格