跳轉到

風險管理完整指南

本文介紹量化交易中的風險管理策略,包括部位控制、停損停利、資金管理與回撤控制。

風險管理的重要性

在量化交易中,不虧錢比賺錢更重要。良好的風險管理可以: - 避免單次巨大虧損 - 降低整體投資組合波動 - 提升長期穩定報酬


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)

參考資源