風險管理完整指南
本文介紹量化交易中的風險管理策略,包括部位控制、停損停利、資金管理與回撤控制。
風險管理的重要性
在量化交易中,不虧錢比賺錢更重要。良好的風險管理可以: - 避免單次巨大虧損 - 降低整體投資組合波動 - 提升長期穩定報酬
1. 部位控制 (Position Sizing)
1.1 設定單一持股上限
from finlab import data
from finlab.backtest import sim
close = data.get('price:收盤價')
position = close > close.average(20)
# 單一持股上限 10%
report = sim(
position,
resample='M',
position_limit=0.1 # 單一持股最多佔總資金 10%
)
1.2 設定最大持股數
# 方法 1: 使用 is_largest 限制持股數
top_n_position = (close > close.average(20)).is_largest(30)
# 方法 2: 在回測時限制
report = sim(
position,
resample='M',
position_limit=0.1,
# 最多持有 30 檔 (每檔 10%,最多 30 檔 = 300% 槓桿)
)
1.3 根據波動度調整部位
# 計算波動度
returns = close.pct_change()
volatility = returns.rolling(60).std()
# 波動度越高,持倉越小
inverse_vol_weight = 1 / volatility
normalized_weight = inverse_vol_weight / inverse_vol_weight.sum(axis=1)
# 與選股訊號結合
position_with_vol = (close > close.average(20)) * normalized_weight
2. 停損停利設定
2.1 固定百分比停損停利
# 停損 10%,停利 20%
position = entry_signal.hold_until(
exit=exit_signal,
stop_loss=0.10,
stop_profit=0.20
)
report = sim(position, resample='M')
2.2 根據 MAE/MFE 優化停損停利
# 先執行初步回測
report_initial = sim(position_without_stops, resample='M', upload=False)
# 查看 MAE/MFE
trades = report_initial.get_trades()
mae_q75 = abs(trades['mae'].quantile(0.75)) # 例如 8.5%
gmfe_q75 = trades['gmfe'].quantile(0.75) # 例如 18.3%
print(f"建議停損: {mae_q75*1.2*100:.1f}%") # MAE Q75 * 1.2 = 10.2%
print(f"建議停利: {gmfe_q75*0.8*100:.1f}%") # GMFE Q75 * 0.8 = 14.6%
# 使用優化後的停損停利
position_optimized = entry_signal.hold_until(
exit=exit_signal,
stop_loss=mae_q75 * 1.2,
stop_profit=gmfe_q75 * 0.8
)
2.3 移動停利 (Trailing Stop)
# 回檔 8% 即出場
position = entry_signal.hold_until(
exit=exit_signal,
stop_loss=0.10,
trailing_stop_profit=0.08 # 從最高點回檔 8%
)
2.4 時間停損
import pandas as pd
# 持有超過 30 天自動出場
def add_time_stop(position, max_hold_days=30):
"""新增時間停損"""
hold_days = (position > 0).astype(int).groupby(level=1).cumsum()
time_exit = hold_days > max_hold_days
return position & (~time_exit)
position_with_time_stop = add_time_stop(position, max_hold_days=30)
3. 資金管理
3.1 凱利公式 (Kelly Criterion)
def kelly_criterion(win_rate, avg_win, avg_loss):
"""
計算凱利比例
Args:
win_rate: 勝率 (0-1)
avg_win: 平均獲利率
avg_loss: 平均虧損率 (正值)
Returns:
float: 建議投入比例
"""
if avg_loss == 0:
return 0
kelly = (win_rate * avg_win - (1 - win_rate) * avg_loss) / avg_win
return max(0, min(kelly, 1)) # 限制在 0-1 之間
# 使用範例
stats = report.get_stats()
kelly_ratio = kelly_criterion(
win_rate=stats['win_ratio'],
avg_win=trades[trades['return'] > 0]['return'].mean(),
avg_loss=abs(trades[trades['return'] < 0]['return'].mean())
)
print(f"凱利建議投入比例: {kelly_ratio*100:.1f}%")
# 實務上使用 0.5 * Kelly (半凱利) 較保守
print(f"半凱利建議: {kelly_ratio*0.5*100:.1f}%")
3.2 固定分數法 (Fixed Fractional)
# 每次只投入總資金的固定比例
RISK_PER_TRADE = 0.02 # 單次風險 2%
# 根據停損幅度計算部位大小
stop_loss = 0.10 # 停損 10%
position_size = RISK_PER_TRADE / stop_loss # = 0.2 = 20%
print(f"建議部位大小: {position_size*100:.0f}%")
4. 回撤控制
4.1 監控回撤並暫停交易
def check_drawdown_limit(report, max_drawdown=0.20):
"""檢查是否超過最大回撤限制"""
stats = report.get_stats()
current_dd = stats['max_drawdown']
if abs(current_dd) > max_drawdown:
print(f"警告:回撤 {current_dd*100:.1f}% 超過限制 {max_drawdown*100:.0f}%")
print("建議暫停交易,檢視策略!")
return False
return True
# 使用
if not check_drawdown_limit(report, max_drawdown=0.25):
# 暫停交易邏輯
pass
4.2 動態調整部位(根據回撤)
def adjust_position_by_drawdown(position, creturn, base_size=1.0):
"""根據當前回撤動態調整部位
回撤越大,部位越小
"""
# 計算當前回撤
peak = creturn.expanding().max()
drawdown = (creturn - peak) / peak
# 調整係數 (回撤 -20% 時,部位減半)
adj_factor = 1 + drawdown / 0.20
adj_factor = adj_factor.clip(0.5, 1.0) # 限制在 0.5-1.0
# 調整部位
adjusted_position = position * adj_factor.reindex(position.index, method='ffill')
return adjusted_position
# 使用
position_adjusted = adjust_position_by_drawdown(
position,
report.creturn,
base_size=1.0
)
5. 風險指標監控
5.1 設定風險預警
def risk_alert(report):
"""風險預警檢查"""
stats = report.get_stats()
alerts = []
# 檢查 1: 夏普率過低
if stats['daily_sharpe'] < 0.5:
alerts.append(f"⚠️ 夏普率過低: {stats['daily_sharpe']:.2f}")
# 檢查 2: 最大回撤過大
if abs(stats['max_drawdown']) > 0.30:
alerts.append(f"⚠️ 最大回撤過大: {stats['max_drawdown']*100:.1f}%")
# 檢查 3: 勝率過低
if stats['win_ratio'] < 0.40:
alerts.append(f"⚠️ 勝率過低: {stats['win_ratio']*100:.1f}%")
# 檢查 4: 平均獲利/虧損比過低
trades = report.get_trades()
avg_win = trades[trades['return'] > 0]['return'].mean()
avg_loss = abs(trades[trades['return'] < 0]['return'].mean())
win_loss_ratio = avg_win / avg_loss if avg_loss > 0 else 0
if win_loss_ratio < 1.5:
alerts.append(f"⚠️ 獲利/虧損比過低: {win_loss_ratio:.2f}")
# 輸出警報
if alerts:
print("=" * 50)
print("風險警報:")
for alert in alerts:
print(alert)
print("=" * 50)
else:
print("✅ 風險檢查通過")
return len(alerts) == 0
# 使用
risk_alert(report)
5.2 定期風險報告
def generate_risk_report(report):
"""產生風險報告"""
stats = report.get_stats()
trades = report.get_trades()
risk_metrics = {
'年化報酬率': f"{stats['daily_mean']*100:.2f}%",
'年化波動率': f"{stats['daily_std']*100:.2f}%",
'夏普率': f"{stats['daily_sharpe']:.2f}",
'最大回撤': f"{stats['max_drawdown']*100:.2f}%",
'勝率': f"{stats['win_ratio']*100:.1f}%",
'平均持有天數': f"{trades['period'].mean():.1f} 天",
'總交易次數': len(trades),
}
print("\n" + "=" * 50)
print("風險報告")
print("=" * 50)
for key, value in risk_metrics.items():
print(f"{key:12s}: {value}")
print("=" * 50 + "\n")
# 使用
generate_risk_report(report)
6. 實戰風險管理檢查清單
回測階段
- [ ] 單一持股權重 ≤ 10%
- [ ] 設定合理的停損(8-12%)與停利(15-25%)
- [ ] 最大回撤 < 30%
- [ ] 夏普率 > 1.0
- [ ] 勝率 > 45% 或 平均獲利/虧損比 > 1.5
實盤階段
- [ ] 使用模擬單測試至少 1 個月
- [ ] 初期資金 ≤ 總資金的 20%
- [ ] 每日監控回撤與持倉
- [ ] 每週檢視實盤 vs 回測的偏差
- [ ] 每月產生風險報告
緊急狀況
- [ ] 回撤 > 20%:暫停新增部位
- [ ] 回撤 > 30%:平倉 50%
- [ ] 回撤 > 40%:全部平倉,檢討策略
- [ ] 連續虧損 > 5 筆:暫停交易,檢視策略
完整程式碼範例
from finlab import data
from finlab.backtest import sim
# 載入資料
close = data.get('price:收盤價')
# 策略邏輯
entry = close > close.average(20)
exit_signal = close < close.average(10)
# 加入完整風險控制
position = entry.hold_until(
exit=exit_signal,
stop_loss=0.10, # 停損 10%
stop_profit=0.20, # 停利 20%
trailing_stop_profit=0.08 # 移動停利 8%
)
# 回測(單一持股上限 10%)
report = sim(
position,
resample='M',
position_limit=0.1,
upload=False
)
# 風險檢查
if risk_alert(report):
print("策略通過風險檢查,可考慮實盤")
else:
print("策略未通過風險檢查,需要優化")
# 產生風險報告
generate_risk_report(report)