文件
開啟 Studio

完整流程

從研究到實盤

本頁內容12

這一頁用一個「營收成長 + 技術突破」的策略,帶你走完量化策略的完整生命週期:構想、回測、優化、驗證,一路到上傳雲端、實盤下單與績效追蹤。

做完這一頁,你會有:一個經過條件組合優化、停損停利調整與樣本外測試的策略;它已上傳到 FinLab 雲端,算好依你的資金該買的股數,能用券商帳戶下單並定期檢視。

開始前你需要:

  • 裝好 finlab、能下載資料,見快速上手。
  • 會寫進出場條件,見寫選股條件。
  • 第 7、8 步要實盤下單時,一個券商帳戶與 API 金鑰,見實盤下單。
  • 樣本外測試用 2023 年之後的資料。免費帳號的台股資料到 2023 年底,VIP 每天更新到最新。

每個階段都可以搭配 AI 編程助手:安裝 FinLab Skill 後,從策略構想到實盤部署,AI 可協助撰寫程式碼、解讀績效、排除錯誤。

整個流程如下。沒通過驗證,或實盤需要調整時,回到策略開發:

是

否

是

否

策略構想

資料探索

策略開發

策略優化

深度分析

樣本外測試

通過驗證?

部署前準備

實盤交易

績效追蹤

需要調整?

1. 寫下策略假設,確認資料夠用

先把策略的邏輯寫清楚,再檢查需要的資料範圍是否足夠、缺失值是否合理。

策略假設:

  • 基本面:營收近期加速成長(月均線上揚)
  • 技術面:股價突破均線,確認動能
  • 風控:跌破均線出場,設定 10% 停損
from finlab import data
import pandas as pd

# 載入所需資料
close = data.get('price:收盤價')
rev = data.get('monthly_revenue:當月營收')

# 檢查資料範圍
print(f"收盤價資料: {close.index[0]} ~ {close.index[-1]}")
print(f"營收資料: {rev.index[0]} ~ {rev.index[-1]}")

# 檢查缺失值比例
print(f"收盤價缺失值: {close.isna().sum().sum() / close.size * 100:.2f}%")
print(f"營收缺失值: {rev.isna().sum().sum() / rev.size * 100:.2f}%")

2. 寫策略並回測

撰寫策略邏輯

from finlab import data
from finlab.backtest import sim

# 1. 資料載入
close = data.get('price:收盤價')
rev = data.get('monthly_revenue:當月營收')

# 2. 計算技術指標
ma20 = close.average(20)
ma60 = close.average(60)

# 3. 計算營收指標
rev_ma3 = rev.average(3)  # 近 3 個月平均營收
rev_ma12 = rev.average(12)  # 近 12 個月平均營收

# 4. 定義進場條件
entry_tech = (close > ma20) & (close > ma60)  # 技術面:突破均線
entry_fund = (rev_ma3 / rev_ma12) > 1.1  # 基本面:營收加速成長

entry_signal = entry_tech & entry_fund

# 5. 定義出場條件
exit_signal = close < ma20  # 跌破 20 日均線

# 6. 組合進出場訊號(含停損)
position = entry_signal.hold_until(
    exit=exit_signal,
    stop_loss=0.1,  # 停損 10%
)

# 7. 回測
report = sim(
    position,
    resample='M',  # 每月調整
    position_limit=0.1,  # 單一持股上限 10%
    upload=False,
    name="營收成長突破策略 v1.0",
)

# 8. 顯示績效
report.display()

初步評估績效

stats = report.get_stats()

print(f"年化報酬率: {stats['cagr'] * 100:.2f}%")
print(f"夏普率: {stats['daily_sharpe']:.2f}")
print(f"最大回撤: {stats['max_drawdown'] * 100:.2f}%")
print(f"勝率: {stats['win_ratio'] * 100:.2f}%")

先和同期大盤比較年化報酬與最大回撤。夏普率明顯低於 1,代表報酬還不足以補償波動;接下來就是優化策略,提升風險調整後報酬。

3. 優化條件組合與停損停利

加入候選條件,測試所有組合

再加三個候選條件,用 sim_conditions() 回測所有組合,找出最好的交集。條件組合的更多用法見策略參數優化。

from finlab.optimize.combinations import sim_conditions

# 原有條件
c1 = (close > ma20) & (close > ma60)  # 均線多頭
c2 = rev_ma3 / rev_ma12 > 1.1  # 營收加速

# 新增條件
pe = data.get('price_earning_ratio:本益比')
c3 = pe < 15  # 低本益比
c4 = close == close.rolling(20).max()  # 創 20 日新高
c5 = rev / rev.shift(1) > 0.9  # 營收月增率 > -10%
# 組合字典
conditions = {
    'c1': c1,  # 均線多頭
    'c2': c2,  # 營收加速
    'c3': c3,  # 低本益比
    'c4': c4,  # 創新高
    'c5': c5,  # 營收月增率
}

# 測試所有組合(31 種)
report_collection = sim_conditions(
    conditions=conditions,
    hold_until={'exit': exit_signal, 'stop_loss': 0.1},
    resample='M',
    position_limit=0.1,
    upload=False,
)

# 視覺化比較
report_collection.plot_stats('heatmap')

找出表現最好的組合

# 取得績效指標表
stats_df = report_collection.get_stats()

# 找出 top 3 組合
top3 = stats_df.T.sort_values('daily_sharpe', ascending=False).head(3)
print(top3[['cagr', 'daily_sharpe', 'max_drawdown', 'win_ratio']])

後面沿用 c1 & c2 & c3(均線多頭 + 營收加速 + 低本益比)。實際請用你跑出來排名第一的組合,並優先選相鄰組合也排名不錯的,而不是單一突出的組合。

採用最佳組合

# 使用最佳組合重新定義策略
best_entry = c1 & c2 & c3

position_optimized = best_entry.hold_until(exit=exit_signal, stop_loss=0.1)

report_opt = sim(
    position_optimized,
    resample='M',
    position_limit=0.1,
    upload=False,
    name="營收成長突破策略 v2.0 (優化後)",
)

用 MAE/MFE 調整停損停利

從每筆交易的最大不利幅度(MAE)與最大有利幅度(GMFE)找停損停利點:停損設為 MAE 第 75 百分位數的 1.2 倍,停利設為 GMFE 第 75 百分位數的 0.8 倍。

# 顯示波動分析
report_opt.display_mae_mfe_analysis()

# 取得交易記錄
trades = report_opt.get_trades()

# 分析 MAE/MFE 分位數
mae_q75 = abs(trades['mae'].quantile(0.75))
gmfe_q75 = trades['gmfe'].quantile(0.75)

print(f"MAE Q75: {mae_q75 * 100:.2f}%")
print(f"GMFE Q75: {gmfe_q75 * 100:.2f}%")

# 調整停損停利
position_final = best_entry.hold_until(
    exit=exit_signal,
    stop_loss=mae_q75 * 1.2,  # 停損設為 MAE Q75 * 1.2
    take_profit=gmfe_q75 * 0.8,  # 停利設為 GMFE Q75 * 0.8
)

report_final = sim(
    position_final,
    resample='M',
    position_limit=0.1,
    upload=False,
    name="營收成長突破策略 v3.0 (最終版)",
)

4. 深度分析:流動性、穩定性與超額報酬

各分析模組的完整說明見策略分析模組。

流動性:適不適合大資金

# 流動性分析(假設資金 1000 萬,單筆進出場至少需要 10 萬股)
report_final.run_analysis('LiquidityAnalysis', required_volume=100000)

結果分進場(entry)與出場(exit)兩列,列出進出場當天成交股數低於 required_volume 的交易比例(low_volume_stocks),以及警示股、處置股、全額交割股的交易比例。對照下方的檢查清單:低成交量比例 < 20%、處置股比例 < 5%。低成交量比例偏高時大資金需注意,建議加入成交量篩選條件。

年度穩定性

檢視策略在不同年度的表現,看是否在某些年度特別差:

report_final.run_analysis('PeriodStatsAnalysis')

# 輸出會顯示每年的夏普率、報酬率、波動率等
# 可檢視策略是否在某些年度特別差

Alpha/Beta:超額報酬有多少

report_final.run_analysis('AlphaBetaAnalysis')

# 會顯示三個區塊:整體摘要、年度 Alpha/Beta、近期 Alpha/Beta
# Alpha:年化超額報酬
# Beta:市場敏感度

如需取得原始數值做進一步分析:

result = report_final.run_analysis('AlphaBetaAnalysis', display=False)
print(f"Alpha: {result['overall']['alpha']:.2%}")
print(f"Beta: {result['overall']['beta']:.2f}")

Alpha 為正,代表策略有大盤解釋不了的超額報酬;Beta 小於 1,代表策略波動小於大盤、風險較低。

5. 樣本外測試

把資料切成訓練集與測試集,驗證策略是否過度配適:

# 定義切分日期
train_end = '2022-12-31'
test_start = '2023-01-01'

# 訓練集回測(2018-2022)
position_train = position_final[position_final.index <= train_end]
report_train = sim(
    position_train,
    resample='M',
    position_limit=0.1,
    upload=False,
    name="訓練集(2018-2022)",
)

# 測試集回測(2023-2024)
position_test = position_final[position_final.index >= test_start]
report_test = sim(
    position_test,
    resample='M',
    position_limit=0.1,
    upload=False,
    name="測試集(2023-2024)",
)

# 績效對比
stats_train = report_train.get_stats()
stats_test = report_test.get_stats()

comparison = pd.DataFrame(
    {
        '訓練集': [
            stats_train['cagr'],
            stats_train['daily_sharpe'],
            stats_train['max_drawdown'],
            stats_train['win_ratio'],
        ],
        '測試集': [
            stats_test['cagr'],
            stats_test['daily_sharpe'],
            stats_test['max_drawdown'],
            stats_test['win_ratio'],
        ],
    },
    index=['年化報酬率', '夏普率', '最大回撤', '勝率'],
)

print(comparison)

比較訓練集與測試集,四項都符合就算通過:

指標通過條件
年化報酬率衰退合理(< 20%)
夏普率略降但仍 > 1
最大回撤差異不大
勝率穩定

測試集績效略降但仍保持優秀,代表策略沒有過度配適,可以進入部署階段。

6. 上傳雲端,準備資金配置

上傳並查看持股清單

# 上傳最終版本至 FinLab 平台
report_final.upload(name="營收成長突破策略 v3.0")

# 取得持股清單
position_info = report_final.position_info()
print(f"下期換股日: {position_info['next_trading_date']}")

# position_info 的 key 是個股代號(如 '2330 台積電'),混合一些 metadata key
# 過濾出持股(排除 metadata key)
metadata_keys = {
    'update_date',
    'next_trading_date',
    'trade_at',
    'freq',
    'market',
    'stop_loss',
    'take_profit',
}
stocks = {k: v for k, v in position_info.items() if k not in metadata_keys}
print(f"當前持股數: {len(stocks)}")
print("持股清單:")
for symbol, info in stocks.items():
    print(f"  {symbol}: 權重={info['weight']:.2%}, 狀態={info['status']}")

依資金算出每檔金額

# 假設實盤資金 100 萬
capital = 1000000

# 計算每檔股票應投入的金額(沿用上一段的 stocks 和 metadata_keys)
for symbol, info in stocks.items():
    info['amount'] = info['weight'] * capital

print("資金配置:")
for symbol, info in stocks.items():
    print(f"  {symbol}: 權重={info['weight']:.2%}, 金額={info['amount']:,.0f}")

7. 實盤下單

設定券商帳戶

以永豐證券為例:

from finlab.online.sinopac_account import SinopacAccount

# 設定券商帳戶(需先設定環境變數,請參考實盤下單教學)
account = SinopacAccount()

其他券商與環境變數的設定見實盤下單。

計算目標部位

from finlab.online.order_executor import Position

# 計算目標部位(整股)
capital = 1000000  # 實盤資金 100 萬
position = Position.from_report(report_final, capital)

# 或使用零股交易
position = Position.from_report(report_final, capital, odd_lot=True)

print(position)  # 每檔股票一筆,含 stock_id、quantity、order_condition

下單

先用瀏覽模式檢查,確認無誤再真的下單:

from finlab.online.order_executor import OrderExecutor

# 建立下單執行器
order_executor = OrderExecutor(position, account=account)

# 下單檢查(瀏覽模式,不會真的下單)
order_executor.create_orders(view_only=True)

# 執行下單(會真的下單,初次使用建議收市時測試)
order_executor.create_orders()

# 更新限價(將最後一筆成交價當成新的限價)
order_executor.update_order_price()

# 刪除所有委託單
order_executor.cancel_orders()

8. 追蹤實盤績效

取得實盤部位與帳戶總值

# 取得實盤部位
account_positions = account.get_position()
print(account_positions)

# 取得帳戶總市值與現金
account_value = account.get_total_balance()
print(f"帳戶總值: {account_value:,.0f}")

每天記錄帳戶淨值

建議每日收盤後執行:

# 定期檢查帳戶狀態(建議每日收盤後執行)
import pandas as pd

# 記錄每日帳戶淨值
daily_value = {
    'date': pd.Timestamp.today(),
    'account_value': account.get_total_balance(),
    'positions': len(account.get_position()),
}

print(f"日期: {daily_value['date']}")
print(f"帳戶淨值: {daily_value['account_value']:,.0f}")
print(f"持股檔數: {daily_value['positions']}")

# 與回測比較
backtest_return = report_final.get_stats()['cagr']
print(f"回測年化報酬率: {backtest_return * 100:.2f}%")

每月檢視

建議每月檢視持股是否按計劃執行、是否觸發停損:

# 1. 持股是否按計劃執行
current_stocks = set(account_positions['stock_id'])
metadata_keys = {
    'update_date',
    'next_trading_date',
    'trade_at',
    'freq',
    'market',
    'stop_loss',
    'take_profit',
}
planned_stocks = {k for k in position_info if k not in metadata_keys}

print(f"計劃持股: {planned_stocks}")
print(f"實際持股: {current_stocks}")
print(f"誤差股票: {planned_stocks.symmetric_difference(current_stocks)}")

# 2. 是否觸發停損
for stock_id, pos in account_positions.items():
    entry_price = pos['avg_price']
    current_price = pos['current_price']
    unrealized_pnl = current_price / entry_price - 1

    if unrealized_pnl < -0.10:  # 停損 10%
        print(f"警告: {stock_id} 已觸發停損,未實現損益: {unrealized_pnl * 100:.2f}%")

想用真實成交、除權息與入出金重建淨值,並和策略目標逐日比對,見實盤覆盤。

完整程式碼

第 1 到第 6 步一次跑完:

# =============================================================================
# 從研究到實盤:營收成長突破策略完整範例
# =============================================================================

from finlab import data
from finlab.backtest import sim
from finlab.optimize.combinations import sim_conditions

# -----------------------------------------------------------------------------
# 階段 1 & 2: 資料載入與策略開發
# -----------------------------------------------------------------------------

# 載入資料
close = data.get('price:收盤價')
rev = data.get('monthly_revenue:當月營收')
pe = data.get('price_earning_ratio:本益比')

# 計算指標
ma20 = close.average(20)
ma60 = close.average(60)
rev_ma3 = rev.average(3)
rev_ma12 = rev.average(12)

# 定義條件
c1 = (close > ma20) & (close > ma60)  # 均線多頭
c2 = rev_ma3 / rev_ma12 > 1.1  # 營收加速
c3 = pe < 15  # 低本益比

# 出場訊號
exit_signal = close < ma20

# -----------------------------------------------------------------------------
# 階段 3: 策略優化
# -----------------------------------------------------------------------------

# 測試條件組合
conditions = {'c1': c1, 'c2': c2, 'c3': c3}
report_collection = sim_conditions(
    conditions=conditions,
    hold_until={'exit': exit_signal, 'stop_loss': 0.1},
    resample='M',
    position_limit=0.1,
    upload=False,
)

# 找出最佳組合
stats_df = report_collection.get_stats()
top1 = stats_df.T.sort_values('daily_sharpe', ascending=False).index[0]
print(f"最佳組合: {top1}")

# 使用最佳組合
best_entry = c1 & c2 & c3

# 優化停損停利
position_temp = best_entry.hold_until(exit=exit_signal, stop_loss=0.1)
report_temp = sim(position_temp, resample='M', position_limit=0.1, upload=False)
trades = report_temp.get_trades()

mae_q75 = abs(trades['mae'].quantile(0.75))
gmfe_q75 = trades['gmfe'].quantile(0.75)

# 最終策略
position_final = best_entry.hold_until(
    exit=exit_signal, stop_loss=mae_q75 * 1.2, take_profit=gmfe_q75 * 0.8
)

report_final = sim(
    position_final,
    resample='M',
    position_limit=0.1,
    upload=False,
    name="營收成長突破策略 v3.0",
)

# -----------------------------------------------------------------------------
# 階段 4: 深度分析
# -----------------------------------------------------------------------------

# 流動性分析
report_final.run_analysis('LiquidityAnalysis', required_volume=100000)

# 波動分析
report_final.display_mae_mfe_analysis()

# 時期穩定性
report_final.run_analysis('PeriodStatsAnalysis')

# Alpha/Beta
report_final.run_analysis('AlphaBetaAnalysis')

# -----------------------------------------------------------------------------
# 階段 5: 樣本外測試
# -----------------------------------------------------------------------------

train_end = '2022-12-31'
test_start = '2023-01-01'

position_train = position_final[position_final.index <= train_end]
position_test = position_final[position_final.index >= test_start]

report_train = sim(position_train, resample='M', position_limit=0.1, upload=False)
report_test = sim(position_test, resample='M', position_limit=0.1, upload=False)

print("訓練集績效:", report_train.get_stats()['daily_sharpe'])
print("測試集績效:", report_test.get_stats()['daily_sharpe'])

# -----------------------------------------------------------------------------
# 階段 6: 部署前準備
# -----------------------------------------------------------------------------

# 上傳至雲端
report_final.upload(name="營收成長突破策略 v3.0")

# 取得持股清單
position_info = report_final.position_info()
metadata_keys = {
    'update_date',
    'next_trading_date',
    'trade_at',
    'freq',
    'market',
    'stop_loss',
    'take_profit',
}
stocks = {k: v for k, v in position_info.items() if k not in metadata_keys}
print("下期持股:")
for symbol, info in stocks.items():
    print(f"  {symbol}: 權重={info['weight']:.2%}")

實盤下單與追蹤,接著連上券商帳戶,執行第 7、8 步。

檢查清單

開發階段

  • 策略構想明確:有清晰的邏輯假設(技術面/基本面/籌碼面)
  • 資料完整性檢查:確認所需資料範圍足夠,缺失值比例合理
  • 初步回測績效可接受:年化報酬 > 10%,夏普率 > 0.5
  • 條件組合優化:使用 sim_conditions() 找出最佳組合
  • 停損停利優化:使用 MAE/MFE 分析設定合理的停損停利點

驗證階段

  • 流動性風險:低成交量比例 < 20%,處置股比例 < 5%
  • 年度穩定性:沒有連續多年表現極差
  • Alpha > 0:策略有超額報酬
  • 樣本外測試通過:測試集績效衰退 < 30%,夏普率仍 > 1

部署階段

  • 策略已上傳雲端:可隨時檢視持股清單
  • 券商帳戶設定完成:API 金鑰正確,權限開通
  • 先用模擬單測試:確認下單邏輯正確
  • 小資金測試:先用小額資金測試 1-2 個月
  • 定期檢視機制:設定每月檢視實盤績效

常見錯誤與檢查方法

在執行完整流程時,務必在每個階段進行錯誤檢查。以下是各階段的關鍵檢查點:

第 1-2 步:找不到資料表、資料為空、回測沒有交易

常見錯誤:

  • KeyError: 'price:收盤價':API token 未設定或資料表名稱錯誤
  • 資料為空或缺失值過多
  • 回測無交易記錄(進場條件過嚴)
try:
    # 1. 檢查資料載入
    close = data.get('price:收盤價')

    if close.empty:
        raise ValueError("❌ 資料為空,請檢查 API token")

    missing_ratio = close.isna().sum().sum() / close.size
    if missing_ratio > 0.1:
        print(f"⚠️  警告:缺失值比例 {missing_ratio:.1%} > 10%")

    # 2. 檢查進場訊號
    entry_count = entry_signal.sum(axis=1).mean()
    if entry_count < 1:
        print("⚠️  警告:進場條件過嚴,平均每日 < 1 檔股票")
        print("建議:放寬條件或使用 .is_largest(N) 確保固定檔數")

    # 3. 檢查回測結果
    report = sim(position, resample='M', position_limit=0.1)
    trades = report.get_trades()

    if len(trades) == 0:
        raise ValueError("❌ 策略無任何交易記錄,請檢查進場條件")

    print(f"✅ 回測成功:{len(trades)} 筆交易")

except KeyError as e:
    print(f"❌ 找不到資料表:{e}")
    print("請至 /database 確認資料表名稱")
    print("或檢查 API token 是否正確設定")

except ValueError as e:
    print(f"❌ 資料驗證失敗:{e}")

詳細的錯誤處理見下載資料的「常見錯誤與解決方法」。

第 3 步:組合太多跑太久、條件日期範圍不一致

常見錯誤:

  • sim_conditions() 組合數量過多導致執行時間過長
  • 條件定義錯誤(日期範圍不一致)
# 限制組合數量(< 50 個組合)
conditions = {
    'c1': c1,
    'c2': c2,
    'c3': c3,
    # 最多 5 個條件(2^5 = 32 種組合)
}

# 估算執行時間
num_combinations = 2 ** len(conditions) - 1
print(
    f"將測試 {num_combinations} 種組合,預估時間:約 {num_combinations * 30 / 60:.1f} 分鐘"
)

if num_combinations > 100:
    print("⚠️  警告:組合數量過多,建議先測試部分條件")

詳細的錯誤處理見策略參數優化。

第 5 步:訓練集與測試集重疊、測試績效落差過大

常見錯誤:

  • 訓練集與測試集日期範圍重疊
  • 測試集績效與訓練集差異過大(過度配適)
# 檢查日期範圍
train_end = '2022-12-31'
test_start = '2023-01-01'

position_train = position_final[position_final.index <= train_end]
position_test = position_final[position_final.index >= test_start]

# 確認無重疊
if position_train.index[-1] >= position_test.index[0]:
    raise ValueError("❌ 訓練集與測試集日期重疊!")

# 檢查績效差異
sharpe_train = report_train.get_stats()['daily_sharpe']
sharpe_test = report_test.get_stats()['daily_sharpe']
degradation = (sharpe_train - sharpe_test) / sharpe_train

if degradation > 0.5:
    print(f"⚠️  警告:夏普率衰退 {degradation:.1%} > 50%,可能過度配適")
    print("建議:")
    print("1. 簡化策略條件")
    print("2. 增加訓練集資料範圍")
    print("3. 使用交叉驗證")

詳細的錯誤處理見歷史回測的「常見錯誤與解決方法」。

第 7 步:帳戶連線失敗、資金不足、訂單被拒

常見錯誤:

  • 券商帳戶連線失敗(API key 錯誤、憑證過期)
  • 資金不足或持倉過多
  • 訂單被拒絕(處置股未圈存、漲跌停鎖死)

實盤下單前的安全檢查:

# 實盤下單前必須檢查
def pre_trading_checks(stocks, account, capital):
    """實盤下單前安全檢查"""

    print("=== 實盤下單安全檢查 ===\n")

    # 1. 檢查持股檔數
    if len(stocks) > 30:
        raise ValueError(f"❌ 持股過多({len(stocks)} 檔),建議 < 30 檔")

    # 2. 檢查可用資金
    available = account.get_cash()
    if capital > available * 0.9:
        raise ValueError(f"❌ 資金不足!需要 {capital:,.0f},可用 {available:,.0f}")

    # 3. 檢查帳戶連線
    try:
        account.get_position()
    except Exception as e:
        raise ConnectionError(f"❌ 券商帳戶連線失敗:{e}")

    print("✅ 所有檢查通過\n")

# 執行檢查
try:
    pre_trading_checks(stocks, account, capital)

    # 先預覽(不會真的下單)
    order_executor.create_orders(view_only=True)

    # 確認後執行(取消註解)
    # order_executor.create_orders()

except (ValueError, ConnectionError) as e:
    print(f"\n{e}")
    print("請修正問題後再執行下單")

詳細的錯誤處理見實盤下單的「常見錯誤與解決方法」。

下一步

其他相關頁面:歷史回測、風險管理、常見問題、FinLab 量化平台。

↑↓ 選擇 Enter 開啟 Esc 關閉