完整流程
從研究到實盤
本頁內容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}%")以 S&P 500 成分股為範圍,策略假設:
- 基本面:季營收年增率超過 10%
- 技術面:股價突破均線,確認動能
- 風控:跌破均線出場,設定 10% 停損
from finlab import data
import pandas as pd
# 載入 S&P 500 成分股的資料
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
rev = data.get('us_income_statement:revenue')
# 檢查資料範圍(營收是季資料,索引是 2024-US-Q3 這類標籤)
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()from finlab import data
from finlab.backtest import sim
# 1. 資料載入
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
rev = data.get('us_income_statement:revenue')
# 2. 計算技術指標
ma20 = close.average(20)
ma60 = close.average(60)
# 3. 計算營收指標(季資料,對齊每次向 SEC 申報的日期)
rev_yoy = rev.pct_change(4) # 營收年增率
# 4. 定義進場條件
entry_tech = (close > ma20) & (close > ma60) # 技術面:突破均線
entry_fund = rev_yoy > 0.1 # 基本面:營收年增率超過 10%
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%from finlab.optimize.combinations import sim_conditions
# 原有條件
c1 = (close > ma20) & (close > ma60) # 均線多頭
c2 = rev_yoy > 0.1 # 營收成長
# 新增條件
with data.us_universe(index='SP500'):
eps = data.get('us_income_statement:eps_diluted')
raw_close = data.get('price:close') # 估值用未還原價格
pe = raw_close / eps.rolling(4).sum() # 以近四季稀釋 EPS 計算的本益比
c3 = (pe > 0) & (pe < 20) # 低本益比
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%。低成交量比例偏高時大資金需注意,建議加入成交量篩選條件。
LiquidityAnalysis 只支援台股。美股改成要求最低成交金額,重跑回測後比較:
with data.us_universe(index='SP500'):
volume = data.get('price:volume')
# 20 日平均成交金額;要求每天至少成交 500 萬美元
dollar_volume = (close * volume).average(20)
liquid = dollar_volume > 5_000_000
position_liquid = (best_entry & liquid).hold_until(exit=exit_signal, stop_loss=0.1)
report_liquid = sim(position_liquid, resample='M', position_limit=0.1, upload=False)排除流動性不足的股票後,績效若大幅崩落,代表回測靠的是大資金做不到的交易。
年度穩定性
檢視策略在不同年度的表現,看是否在某些年度特別差:
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.schwab_account import SchwabAccount
# 從環境變數讀取 SCHWAB_API_KEY、SCHWAB_SECRET 與 SCHWAB_TOKEN_PATH
account = SchwabAccount()其他券商與環境變數的設定見實盤下單。
計算目標部位
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_conditionfrom finlab.online.order_executor import Position
# 計算目標股數
capital = 100_000 # 實盤資金 10 萬美元
position = Position.from_report(report_final, capital)
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%}")# =============================================================================
# 從研究到實盤:營收成長突破策略完整範例(S&P 500 成分股)
# =============================================================================
from finlab import data
from finlab.backtest import sim
from finlab.optimize.combinations import sim_conditions
# -----------------------------------------------------------------------------
# 階段 1 & 2: 資料載入與策略開發
# -----------------------------------------------------------------------------
# 載入 S&P 500 成分股的資料
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
raw_close = data.get('price:close') # 估值用未還原價格
rev = data.get('us_income_statement:revenue')
eps = data.get('us_income_statement:eps_diluted')
# 計算指標
ma20 = close.average(20)
ma60 = close.average(60)
rev_yoy = rev.pct_change(4)
pe = raw_close / eps.rolling(4).sum()
# 定義條件
c1 = (close > ma20) & (close > ma60) # 均線多頭
c2 = rev_yoy > 0.1 # 營收成長
c3 = (pe > 0) & (pe < 20) # 低本益比
# 出場訊號
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.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 量化平台。
這一頁有幫到你嗎?