finlab.online
實盤交易模組,用於將回測策略部署至真實券商帳戶,執行自動下單。
⚠️ 重要風險警告
實盤交易涉及真實金錢,錯誤可能導致財務損失!
必須遵守的安全原則:
- ✅ 務必先用模擬帳戶測試至少 1 個月
- ✅ 初期資金不超過總資金的 10-20%
- ✅ 每日監控持倉與訂單狀態
- ✅ 設定停損機制,避免單次巨大虧損
- ✅ 在開發/測試環境與生產環境使用不同帳戶
- ❌ 絕對不要在未測試的策略上使用全部資金
- ❌ 不要忽略錯誤訊息或異常狀態
使用情境
- 自動化交易: 根據策略自動下單、調倉,無需手動操作
- 多帳戶管理: 同時管理多個券商帳戶(永豐、富邦等)
- 實盤追蹤: 記錄實際交易績效,與回測結果對比
- 風險控制: 自動檢測持倉異常、單股過重、資金不足等問題
從回測到實盤的過渡流程
graph LR
A[策略回測] --> B[樣本外測試]
B --> C[上傳至雲端]
C --> D[模擬帳戶測試]
D --> E{測試通過?}
E -->|是| F[小資金實盤]
E -->|否| A
F --> G[逐步增加資金]
建議時程: 1. 策略回測(1-2 週):確認邏輯正確 2. 樣本外測試(2-4 週):使用未見過的資料 3. 模擬帳戶(4-8 週):真實市場條件,但不用真錢 4. 小資金實盤(8-12 週):10-20% 資金測試 5. 逐步擴大(視情況):確認穩定後才增加資金
快速範例
基礎用法:自動下單
from finlab.online.order_executor import Position, OrderExecutor
from finlab.online.base_account import Account
# 1. 設定券商帳戶(範例使用永豐證券)
account = Account(
broker='shioaji', # 永豐證券
account='YOUR_ACCOUNT_ID', # 帳號
password='YOUR_PASSWORD' # 密碼(建議使用環境變數)
)
# 2. 建立訂單執行器
executor = OrderExecutor(
account=account,
market='TW', # 台股
base_currency='TWD' # 台幣
)
# 3. 從雲端取得最新持倉
position = Position.from_report(
report_id='YOUR_REPORT_ID', # 雲端策略 ID
total_funds=1000000 # 總資金 100 萬
)
# 4. 執行下單(請在模擬帳戶測試!)
executor.create_orders(position)
詳細教學
參考以下教學了解完整流程:
- 實盤下單教學(單策略) - 基礎下單流程
- 實盤下單教學(多策略) - 組合下單管理
- 風險管理完整指南 - 實盤風險控制
- 完整策略開發流程 - 從研究到實盤
API Reference
Account
finlab.online.base_account.Account
Bases: ABC
module_version
class-attribute
instance-attribute
股票帳戶的 abstract class 可以繼承此 Account,來實做券商的帳戶買賣動作,目前已經實做 SinopacAccount (永豐證券) 以及 FugleAccount (玉山富果),來進行交易。可以用以下方式建構物件並用來交易:
永豐證券
import os
from finlab.online.sinopac_account import SinopacAccount
# 舊版請使用
# shioaji < 1.0.0 and finlab < 0.3.18
os.environ['SHIOAJI_ACCOUNT']= '永豐證券帳號'
os.environ['SHIOAJI_PASSWORD']= '永豐證券密碼'
# 新版請使用
# shioaji >= 1.0.0 and finlab >= 0.3.18
os.environ['SHIOAJI_API_KEY'] = '永豐證券API_KEY'
os.environ['SHIOAJI_SECRET_KEY'] = '永豐證券SECRET_KEY'
os.environ['SHIOAJI_CERT_PERSON_ID']= '身份證字號'
# shioaji
os.environ['SHIOAJI_CERT_PATH']= '永豐證券憑證路徑'
os.environ['SHIOAJI_CERT_PASSWORD'] = '永豐證券憑證密碼' # 預設與身份證字號
acc = SinopacAccount()
cancel_order
abstractmethod
create_order
abstractmethod
create_order(action, stock_id, quantity, price=None, odd_lot=False, market_order=False, best_price_limit=None, order_cond=OrderCondition.CASH)
產生新的委託單
| PARAMETER | DESCRIPTION |
|---|---|
action
|
買賣方向,通常為 'BUY' 或是 'SELL'
TYPE:
|
stock_id
|
股票代號 ex: '2330'
TYPE:
|
quantity
|
委託股票的總數量(張數),允許小數點
TYPE:
|
price
|
股票買賣的價格(限價單)
TYPE:
|
force
|
是否用最差之價格(長跌停)強制成交? 當成交量足夠時,可以比較快成交,然而當成交量低時,容易有大的滑價
TYPE:
|
wait_for_best_price
|
是否用最佳之價格(長跌停),無限時間等待?當今天要出場時,可以開啟等漲停價來購買,當今天要買入時,可以掛跌停價等待買入時機。
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
order id 券商提供的委託單編號 |
get_orders
abstractmethod
拿到現在所有委託單
| RETURNS | DESCRIPTION |
|---|---|
Dict[str, Order]
|
所有委託單 id 與委託單資料 Example
|
get_stocks
abstractmethod
拿到現在股票報價
| ATTRIBUTE | DESCRIPTION |
|---|---|
stock_ids |
一次拿取所有股票的報價,ex: ['1101', '2330']
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
dict
|
報價資料, Example
|
帳戶安全最佳實踐
使用環境變數存儲敏感資訊:
import os
account = Account(
broker='shioaji',
account=os.environ.get('BROKER_ACCOUNT'),
password=os.environ.get('BROKER_PASSWORD')
)
# 不要在程式碼中硬編碼帳密!
分離開發/生產環境:
Position
finlab.online.order_executor.Position
Position(stocks, weights=None, margin_trading=False, short_selling=False, day_trading_long=False, day_trading_short=False)
使用者可以利用 Position 輕鬆建構股票的部位,並且利用 OrderExecuter 將此部位同步於實際的股票帳戶。
建構股票部位
| ATTRIBUTE | DESCRIPTION |
|---|---|
stocks |
TYPE:
|
margin_trading |
做多部位是否使用融資
TYPE:
|
short_selling |
做空部位是否使用融券
TYPE:
|
day_trading_long |
做多部位為當沖先做多
TYPE:
|
day_trading_short |
做空部位為當沖先做空
TYPE:
|
Examples:
設計部位,持有一張和 100 股 1101
output將兩個部位相加
from finlab.online.order_executor import Position
p1 = Position({'1101': 1})
p2 = Position({'2330': 1})
p1 + p2
[
{'stock_id': '1101', 'quantity': 1.0, 'order_condition': <OrderCondition.CASH: 1>},
{'stock_id': '2330', 'quantity': 1.0, 'order_condition': <OrderCondition.CASH: 1>}
]
from_json
classmethod
Load a JSON file from the given path and convert it to a list of positions.
| PARAMETER | DESCRIPTION |
|---|---|
path
|
The path to the JSON file.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
|
None |
from_list
classmethod
利用 dict 建構股票部位
from_report
classmethod
利用回測完的報告 finlab.report.Report 建構股票部位。
| ATTRIBUTE | DESCRIPTION |
|---|---|
report |
回測完的結果報告。
TYPE:
|
fund |
希望部屬的資金。
TYPE:
|
price |
股票代號對應到的價格,若無則使用最近個交易日的收盤價。
TYPE:
|
odd_lot |
是否考慮零股。預設為 False,只使用整張操作。
TYPE:
|
board_lot_size |
一張股票等於幾股。預設為1000,一張等於1000股。
TYPE:
|
allocation |
資產配置演算法選定,預設為
TYPE:
|
margin_trading |
做多部位是否使用融資
TYPE:
|
short_selling |
做空部位是否使用融券
TYPE:
|
day_trading_long |
做多部位為當沖先做多
TYPE:
|
day_trading_short |
做空部位為當沖先做空
TYPE:
|
leverage |
目標槓桿倍數,預設為1.0(不使用融資)。若>1.0,會根據波動度分配融資。
TYPE:
|
Example
from_weight
classmethod
from_weight(weights, fund, price=None, odd_lot=False, board_lot_size=None, allocation=greedy_allocation, precision=None, leverage=1.0, price_history=None, **kwargs)
利用 weight 建構股票部位
| ATTRIBUTE | DESCRIPTION |
|---|---|
weights |
股票詳細部位,股票代號對應權重
TYPE:
|
fund |
資金大小
TYPE:
|
price |
股票代號對應到的價格,若無則使用最近個交易日的收盤價。
TYPE:
|
odd_lot |
是否考慮零股
TYPE:
|
board_lot_size |
一張股票等於幾股
TYPE:
|
allocation |
資產配置演算法選定,預設為
TYPE:
|
precision |
計算張數時的精度,預設為 None 代表依照 board_lot_size 而定,而 1 代表 0.1 張,2 代表 0.01 張,以此類推。
TYPE:
|
leverage |
目標槓桿倍數,預設為1.0(不使用融資)。若>1.0,會根據波動度分配融資。
TYPE:
|
price_history |
股票歷史價格,若 leverage > 1.0 時必須提供。
TYPE:
|
margin_trading |
做多部位是否使用融資
TYPE:
|
short_selling |
做空部位是否使用融券
TYPE:
|
day_trading_long |
做多部位為當沖先做多
TYPE:
|
Examples:
例如,用 100 萬的資金,全部投入,持有 1101 和 2330 各一半:
from finlab.online.order_executor import Position
Position.from_weight({
'1101': 0.5,
'2330': 0.5,
}, fund=1000000)
{'stock_id': '1101', 'quantity': 13, 'order_condition': <OrderCondition.CASH: 1>},
{'stock_id': '2330', 'quantity': 1, 'order_condition': <OrderCondition.CASH: 1>}
]
```
OrderExecutor
finlab.online.order_executor.OrderExecutor
對比實際帳戶與欲部屬的股票部位,進行同步 Arguments: target_position (Position): 想要部屬的股票部位。 account (Account): 目前支援永豐與富果帳戶,請參考 Account 來實做。
create_orders
create_orders(market_order=False, best_price_limit=False, view_only=False, extra_bid_pct=0, progress=1, progress_precision=0, buy_only=False, sell_only=False)
產生委託單,將部位同步成 self.target_position 預設以該商品最後一筆成交價設定為限價來下單
| ATTRIBUTE | DESCRIPTION |
|---|---|
market_order |
以類市價盡量即刻成交:所有買單掛漲停價,所有賣單掛跌停價
TYPE:
|
best_price_limit |
掛芭樂價:所有買單掛跌停價,所有賣單掛漲停價
TYPE:
|
view_only |
預設為 False,會實際下單。若設為 True,不會下單,只會回傳欲執行的委託單資料(dict)
TYPE:
|
extra_bid_pct |
以該百分比值乘以價格進行追價下單,如設定為 0.05 時,將以當前價的 +(-)5% 的限價進買入(賣出),也就是更有機會可以成交,但是成交價格可能不理想; 假如設定為 -0.05 時,將以當前價的 -(+)5% 進行買入賣出,也就是限價單將不會立即成交,然而假如成交後,價格比較理想。參數有效範圍為 -0.1 到 0.1 內。
TYPE:
|
progress |
進度,預設為 1,即全部下單。若設定為 0.5,則只下一半的單。
TYPE:
|
progress_precision |
進度的精度,預設為 0,即只下整數張。若設定為 1,則下到 0.1 張。
TYPE:
|
buy_only |
若設為 True,只下買單
TYPE:
|
sell_only |
若設為 True,只下賣單
TYPE:
|
execute_orders
execute_orders(orders, market_order=False, best_price_limit=False, view_only=False, extra_bid_pct=0, cancel_orders=True, buy_only=False, sell_only=False)
產生委託單,將部位同步成 self.target_position 預設以該商品最後一筆成交價設定為限價來下單
| ATTRIBUTE | DESCRIPTION |
|---|---|
orders |
欲下單的部位,通常是由
TYPE:
|
market_order |
以類市價盡量即刻成交:所有買單掛漲停價,所有賣單掛跌停價
TYPE:
|
best_price_limit |
掛芭樂價:所有買單掛跌停價,所有賣單掛漲停價
TYPE:
|
view_only |
預設為 False,會實際下單。若設為 True,不會下單,只會回傳欲執行的委託單資料(dict)
TYPE:
|
extra_bid_pct |
以該百分比值乘以價格進行追價下單,如設定為 0.05 時,將以當前價的 +(-)5% 的限價進買入(賣出),也就是更有機會可以成交,但是成交價格可能不理想; 假如設定為 -0.05 時,將以當前價的 -(+)5% 進行買入賣出,也就是限價單將不會立即成交,然而假如成交後,價格比較理想。參數有效範圍為 -0.1 到 0.1 內。
TYPE:
|
buy_only |
若設為 True,只下買單
TYPE:
|
sell_only |
若設為 True,只下賣單
TYPE:
|
generate_orders
Generate orders based on the difference between target position and present position.
Returns: orders (dict): Orders to be executed.
下單最佳實踐
盤中下單(使用限價單):
# 避免滑價,使用限價單
executor.create_orders(
position,
order_type='limit', # 限價單
price_offset=-0.01 # 比當前價格低 1% 掛單
)
收盤下單(與回測一致):
分批下單(大額訂單):
常見錯誤與風險
1. 未檢查處置股
# 錯誤:直接下單可能買到處置股
executor.create_orders(position) # ❌
# 正確:過濾處置股
disposal_stocks = data.get('etl:處置股')
# 將處置股從 position 中移除
# position = position[~position.index.isin(disposal_stocks)]
executor.create_orders(position) # ✅
2. 超過可用資金
# 檢查可用資金
available_cash = account.get_balance()
required_cash = position.total_value
if required_cash > available_cash:
print(f"❌ 資金不足!需要 {required_cash:,.0f},可用 {available_cash:,.0f}")
# 調整 position 或追加資金
else:
executor.create_orders(position)
3. 漲跌停鎖死無法成交
Order
finlab.online.base_account.Order
dataclass
Order(order_id, stock_id, action, price, quantity, filled_quantity, status, order_condition, time, org_order=None)
Order status
委託單的狀態
| ATTRIBUTE | DESCRIPTION |
|---|---|
order_id |
委託單的 id,與券商 API 所提供的 id 一致
TYPE:
|
stock_id |
股票代號 ex: '2330'
TYPE:
|
action |
買賣方向,通常為 'BUY' 或是 'SELL'
TYPE:
|
price |
股票買賣的價格(限價單)
TYPE:
|
quantity |
委託股票的總數量(張數),允許小數點
TYPE:
|
filled_quantity |
以成交股票的數量(張數),允許小數點
TYPE:
|
status |
委託狀態,可以設定為:'NEW', 'PARTIALLY_FILLED', 'FILLED', 'CANCEL'
TYPE:
|
time |
委託時間
TYPE:
|
org_order |
券商所提供的委託物件格式
TYPE:
|
order_panel()
finlab.online.panel.order_panel
下單 GUI 介面 Arguments: account (Account): 請參考 Account 針對不同券商來建構相對應的操作帳戶
常見問題
Q: 如何確保下單安全?
實施以下檢查機制:
def safe_order_check(position, account, max_stocks=50, max_weight=0.15):
"""下單前安全檢查"""
# 1. 檢查持倉數量
if len(position) > max_stocks:
raise ValueError(f"❌ 持倉過多({len(position)} 檔),超過限制 {max_stocks} 檔")
# 2. 檢查單一持股權重
max_stock_weight = position.max()
if max_stock_weight > max_weight:
raise ValueError(f"❌ 單股權重過高({max_stock_weight:.1%}),超過限制 {max_weight:.0%}")
# 3. 檢查可用資金
available = account.get_balance()
required = position.total_value
if required > available * 1.1: # 留 10% 緩衝
raise ValueError(f"❌ 資金不足!需要 {required:,.0f},可用 {available:,.0f}")
print("✅ 安全檢查通過")
return True
# 使用
if safe_order_check(position, account):
executor.create_orders(position)
Q: 實盤與回測有落差怎麼辦?
常見原因:
- 滑價:實盤成交價與預期不符
-
解決:使用限價單,或調整
price_offset -
成交量不足:無法全部成交
-
解決:使用 LiquidityAnalysis 事先檢測
-
交易時間差異:回測假設即時成交
- 解決:設定合理的下單時間(如收盤前 30 分鐘)
Q: 如何設定自動下單排程?
import schedule
import time
def auto_trade():
"""每日自動下單"""
try:
# 1. 取得最新策略
position = Position.from_report(
report_id='YOUR_REPORT_ID',
total_funds=1000000
)
# 2. 安全檢查
if safe_order_check(position, account):
# 3. 執行下單
executor.create_orders(position)
print(f"✅ 下單完成:{time.strftime('%Y-%m-%d %H:%M:%S')}")
except Exception as e:
# 4. 錯誤通知(LINE/Email)
print(f"❌ 下單失敗:{e}")
# line_notify(f"下單失敗:{e}")
# 每日 14:30 執行(收盤前 30 分鐘)
schedule.every().day.at("14:30").do(auto_trade)
while True:
schedule.run_pending()
time.sleep(60)
Q: 如何處理下單失敗?
try:
executor.create_orders(position)
print("✅ 下單成功")
except Exception as e:
print(f"❌ 下單失敗:{e}")
# 記錄錯誤
with open('order_errors.log', 'a') as f:
f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {e}\n")
# 發送通知
# line_notify(f"下單失敗:{e}")
# 根據錯誤類型決定是否重試
if "network" in str(e).lower():
time.sleep(30) # 網路問題,30 秒後重試
executor.create_orders(position)
參考資源
- 實盤下單教學(單策略) - 詳細步驟
- 實盤下單教學(多策略) - 組合管理
- 風險管理完整指南 - 風險控制
- 完整策略開發流程 - 完整流程
- GitHub 原始碼
最後提醒
請務必在模擬帳戶充分測試後,才使用真實資金!
建議測試清單: - ✅ 下單流程完整執行無錯誤 - ✅ 持倉計算正確(與預期一致) - ✅ 資金控制正常(不超支) - ✅ 異常處理機制有效 - ✅ 通知機制正常運作 - ✅ 連續運行 1 個月無問題
只有在以上項目全部確認後,才考慮使用真實資金!