文件
開啟 Studio

API 參考

finlab.online

本頁內容10

finlab.online 把回測算出的目標持股,變成券商帳戶裡的實際委託:先用 Position 算出每檔要持有幾張,再由 OrderExecutor 和帳戶現有持股比對,送出補足差額的委託。永豐、玉山、富邦、台新、口袋等台股券商,以及美股的嘉信、加密貨幣的幣安都能接。

選哪個函式

你想做的事用這個
把回測報告換成現在要持有的張數Position.from_report()
用權重和資金算出張數Position.from_weight()
直接寫出要持有的股票與張數Position()
連上券商帳戶各券商的帳戶類別,例如 SinopacAccount()
預覽委託,再讓帳戶持股對齊目標OrderExecutor(...).create_orders()
讓沒成交的限價單追價executor.update_order_price()
取消所有未成交委託executor.cancel_orders()
查詢委託單狀態account.get_orders(),回傳 Order
多個策略合併後下單PortfolioSyncManager,見 finlab.portfolio

從回測到實盤

是

否

策略回測

樣本外測試

上傳至雲端

模擬帳戶測試

測試通過?

小資金實盤

逐步增加資金

建議時程:

  1. 策略回測(1-2 週):確認邏輯正確
  2. 樣本外測試(2-4 週):使用未見過的資料
  3. 模擬帳戶(4-8 週):真實市場條件,但不用真錢
  4. 小資金實盤(8-12 週):10-20% 資金測試
  5. 逐步擴大(視情況):確認穩定後才增加資金

第一次自動下單

執行策略、算出張數、連上券商、預覽委託,四步就能把回測結果送到帳戶。view_only=True 只印出委託,確認無誤再拿掉。

from finlab import data
from finlab.backtest import sim
from finlab.online.order_executor import OrderExecutor, Position
from finlab.online.sinopac_account import SinopacAccount

# 1. 執行策略
close = data.get('price:收盤價')
position = (close / close.shift(120)).is_largest(20)
report = sim(position, resample='M', upload=False)

# 2. 把最新的目標權重換算成總資金 100 萬的張數
target = Position.from_report(report, fund=1_000_000)

# 3. 連線永豐證券(先設定 SHIOAJI_API_KEY 等環境變數,見實盤下單教學)
account = SinopacAccount()

# 4. 先預覽委託,確認後再下單
executor = OrderExecutor(target, account=account)
executor.create_orders(view_only=True)  # 只印出委託,不會下單
# executor.create_orders()  # 實際下單

連上券商帳戶

每家券商有自己的帳戶類別,都實作同一個 Account 介面,所以 OrderExecutor 和 PortfolioSyncManager 換券商也能照用;你也可以繼承 Account 接上自己的券商。

券商市場帳戶類別
永豐(Shioaji)台股finlab.online.sinopac_account.SinopacAccount
玉山台股finlab.online.esun_account.EsunAccount(即 FugleAccount)
富邦台股finlab.online.fubon_account.FubonAccount
台新(原元富)台股finlab.online.taishin_account.TaishinAccount(即 MasterlinkAccount)
口袋台股finlab.online.pocket_account.PocketAccount
嘉信(Charles Schwab)美股finlab.online.schwab_account.SchwabAccount
幣安(Binance)加密貨幣finlab.online.binance_account.BinanceAccount

數量的單位跟著帳戶的市場走:台股帳戶是張(1 張 = 1,000 股,小數部分是零股),美股帳戶是股。

用環境變數保存帳密

帳密不要寫進程式碼。在程式以外設定好環境變數(shell 設定檔、密鑰管理服務、CI secrets),帳戶類別會自己讀取;也可以從你的設定明確傳入:

import os

from finlab.online.sinopac_account import SinopacAccount

# 在程式碼以外設定 SHIOAJI_API_KEY、SHIOAJI_SECRET_KEY、SHIOAJI_CERT_PERSON_ID、
# SHIOAJI_CERT_PATH、SHIOAJI_CERT_PASSWORD(shell 設定檔、密鑰管理服務、CI secrets)
account = SinopacAccount()

# 或從自己的設定明確傳入
account = SinopacAccount(
    api_key=os.environ['SHIOAJI_API_KEY'],
    secret_key=os.environ['SHIOAJI_SECRET_KEY'],
    certificate_person_id=os.environ['SHIOAJI_CERT_PERSON_ID'],
    certificate_path=os.environ['SHIOAJI_CERT_PATH'],
    certificate_password=os.environ['SHIOAJI_CERT_PASSWORD'],
)
# 不要在程式碼中硬編碼帳密!

各券商要設定哪些變數,見實盤下單教學。

安裝口袋證券

PocketAccount 使用口袋官方的 pocketx。請在獨立的 Python 3.12 環境安裝:

python -m pip install finlab pocketx==0.0.4
from finlab.online import PocketAccount

正式下單的憑證簽章僅支援 Windows。先依官方事前準備完成 API 申請與憑證申請,再設定 POCKET_API_KEY、POCKET_SECRET_KEY(登入密碼)、POCKET_CERT_PATH、POCKET_CERT_PASSWORD、POCKET_CERT_PERSON_ID。之後呼叫 PocketAccount() 會登入並啟用憑證;單純匯入類別不會登入。

為什麼要用 Python 3.12?

pocketx 0.0.4 的預編譯 wheel 僅支援 Python 3.9;Python 3.12 會從原始碼編譯,需先安裝 C 編譯器(Windows 可使用 Visual Studio Build Tools 的 C++ 建置工具)。SDK 使用已於 Python 3.13 移除的 audioop,請勿使用 Python 3.13 以上。PyPI 的 pocket 是另一個服務的套件,請勿安裝它來連接口袋證券。

類別

finlab.online.Account

Account()

方法與屬性

create_order
函式

create_order

create_order(action, stock_id, quantity, price, odd_lot, market_order, best_price_limit, order_cond) -> str

產生新的委託單

參數

action Action

買賣方向,通常為 'BUY' 或是 'SELL'

stock_id str

股票代號 ex: '2330'

quantity Number

委託股票的總數量(張數),允許小數點

price Number

股票買賣的價格(限價單)

force bool

是否用最差之價格(長跌停)強制成交? 當成交量足夠時,可以比較快成交,然而當成交量低時,容易有大的滑價

wait_for_best_price bool

是否用最佳之價格(長跌停),無限時間等待?當今天要出場時,可以開啟等漲停價來購買,當今天要買入時,可以掛跌停價等待買入時機。

odd_lot bool 預設 False
market_order bool 預設 False
best_price_limit Number | None
order_cond OrderCondition 預設 OrderCondition.CASH

回傳

str

order id 券商提供的委託單編號

update_order
函式

update_order

update_order(order_id, price, quantity) -> None

產生新的委託單

參數

order_id str

券商所提供的委託單 ID

price Number

更新的限價

quantity Number

更新的待成交量

回傳

None

無跳出 erorr 代表成功更新委託單

cancel_order
函式

cancel_order

cancel_order(order_id) -> None

刪除委託單

建議使用 刪除委託單此功能前,先使用 update_order() 來更新委託單的狀況!如下

acc.update_order()
acc.cancel_order('ORDER_ID')

參數

order_id str

券商所提供的委託單 ID

回傳

None

代表成功更新委託單

get_orders
函式

get_orders

get_orders() -> dict[str, Order]

拿到現在所有委託單

回傳

Dict[str, Order]

所有委託單 id 與委託單資料

Example

{'12345A': Order(order_id='12345A', stock_id='5410',...),...}

get_stocks
函式

get_stocks

get_stocks(stock_ids) -> dict[str, Stock]

拿到現在股票報價

參數

stock_ids `list` of `str`

一次拿取所有股票的報價,ex: ['1101', '2330']

回傳

dict

報價資料,

Example

{'1101': Stock(stock_id='1101', open=31.15, high=31.85, low=31.1, close=31.65, bid_price=31.6, bid_volume=728.0, ask_price=31.65, ask_volume=202)}

get_position
函式

get_position

get_position() -> Position

拿到當前帳戶的股票部位

回傳

Position

當前股票部位

get_total_balance
函式

get_total_balance

get_total_balance() -> int

拿到當前帳戶的股票部位淨值

get_cash
函式

get_cash

get_cash() -> int

拿到當前帳戶的現金

get_settlement
函式

get_settlement

get_settlement() -> int

拿到當前帳戶的結算資料

get_market
函式

get_market

get_market() -> Market

拿到當前帳戶的市場

account.get_price()

取得每檔股票的最後成交價;還沒有成交時,改用買賣報價的中間值。

price = account.get_price(['2330', '2317'])
參數型別預設說明
stock_idslist[str]必填股票代號清單。

設定目標部位

Position 是要讓帳戶持有的股票與數量。最常用的是從回測報告直接換算:

from finlab.online.order_executor import Position

# 策略操作金額各一百萬,兩個部位可以相加
position1 = Position.from_report(report1, 1_000_000)
position2 = Position.from_report(report2, 1_000_000)
total_position = position1 + position2

# 也可以直接寫:持有 1 張 2330,以及 1 張又 100 股 1101
manual = Position({'2330': 1, '1101': 1.1})

Position.from_report() 算的是「現在」該持有的部位:下次換股前用 report.weights,換股時間一到(與 report.is_rebalance_due() 同一個規則)就改用 report.next_weights;有停損停利待出場的股票權重為 0。一份報告裡包含多個策略時,請改用 finlab.portfolio.Portfolio。

Position.from_weight() 和 backtest.sim() 一樣,權重絕對值總和大於 1 時會等比例縮到 1。odd_lot=False(預設)只買整張,資金不夠買一張的股票會是 0 股,並發出 ZeroShareAllocationWarning。

類別

finlab.online.Position

Position(stocks, weights, margin_trading, short_selling, day_trading_long, day_trading_short)

使用者可以利用 Position 輕鬆建構股票的部位,並且利用 OrderExecuter 將此部位同步於實際的股票帳戶。

參數

stocks `dict` of `str`

number.Number): 股票代號與張數 ex: {'1101': 1} 是指持有一張 1101 台泥,可以接受負數,代表做空。

margin_trading bool 預設 False

做多部位是否使用融資

short_selling bool 預設 False

做空部位是否使用融券

day_trading_long bool 預設 False

做多部位為當沖先做多

day_trading_short bool 預設 False

做空部位為當沖先做空

weights dict[str, float] | pd.Series | None

範例

設計部位,持有一張和 100 股 1101

from finlab.online.order_executor import Position

Position({'1101': 1.1})

output

[
    {'stock_id': '1101',
     'quantity': 1.1,
     'order_condition': <OrderCondition.CASH: 1>
    }
]

將兩個部位相加

from finlab.online.order_executor import Position

p1 = Position({'1101': 1})
p2 = Position({'2330': 1})
p1 + p2

output

[
    {'stock_id': '1101', 'quantity': 1.0, 'order_condition': <OrderCondition.CASH: 1>},
    {'stock_id': '2330', 'quantity': 1.0, 'order_condition': <OrderCondition.CASH: 1>}
]

方法與屬性

from_list
函式

from_list

from_list(position) -> Position

利用 dict 建構股票部位

參數

position `list` of `dict`

股票詳細部位

from finlab.online.enums import OrderCondition
from finlab.online.order_executor import Position

Position.from_list(
[{
  'stock_id': '1101', # 股票代號
  'quantity': 1.1, # 張數
  'order_condition': OrderCondition.CASH # 現股融資融券、先買後賣
}])

其中 OrderCondition 除了 CASH 外,還有 MARGIN_TRADING、DAY_TRADING_LONG、SHORT_SELLING、DAY_TRADING_SHORT。

from_entries
函式

from_entries

from_entries(entries) -> Position

Build Position from schema entries (e.g. finlab.schemas.PositionEntry).

參數

entries list[dict[str, Any] | Any]
to_entries
函式

to_entries

to_entries() -> list[Any]

Convert Position internal payload to schema entries.

from_weight
函式

from_weight

from_weight(weights, fund, price, odd_lot, board_lot_size, allocation, precision, leverage, price_history, **kwargs) -> Position

利用 weight 建構股票部位

{'stock_id': '1101', 'quantity': 13, 'order_condition': <OrderCondition.CASH: 1>},
{'stock_id': '2330', 'quantity': 1, 'order_condition': <OrderCondition.CASH: 1>}
  ]
  ```

參數

weights dict[str, float] 或 pd.Series

股票詳細部位,股票代號對應權重。 與 backtest.sim() 相同,若權重絕對值總和大於 1,會等比例縮放至總和為 1。

fund int

資金大小

price None 或 pd.Series 或 dict[str, float]

股票代號對應到的價格,若無則使用最近個交易日的收盤價。

odd_lot bool 預設 False

是否考慮零股。為 False 時只買整張,資金不足一張的股票會是 0 股, 並發出 ZeroShareAllocationWarning。

board_lot_size None 或 int

一張股票等於幾股

allocation function 預設 greedy_allocation

資產配置演算法選定,預設為finlab.online.utils.greedy_allocation(最大資金部屬貪婪法)。 呼叫方式為 allocation(weights, price * board_lot_size, effective_fund * 10**precision), 其中 effective_fund 在 leverage > 1 時為 fund * leverage,否則為 fund; precision 為依零股與 board_lot_size 調整後的有效精度。 需回傳 (dict[股票代號, 張數 * 10**precision], 剩餘資金)。

precision None 或 int

計算張數時的精度,預設為 None 代表依照 board_lot_size 而定,而 1 代表 0.1 張,2 代表 0.01 張,以此類推。

leverage float 預設 1.0

目標槓桿倍數,預設為1.0(不使用融資)。若>1.0,會根據波動度分配融資。

price_history None 或 pd.DataFrame

股票歷史價格,若 leverage > 1.0 時必須提供。

margin_trading bool

做多部位是否使用融資

short_selling bool

做空部位是否使用融券

day_trading_long bool

做多部位為當沖先做多

kwargs Any 預設 {}

範例

例如,用 100 萬的資金,全部投入,持有 1101 和 2330 各一半:

from finlab.online.order_executor import Position

Position.from_weight({
'1101': 0.5,
'2330': 0.5,
}, fund=1000000)

output

[
from_report
函式

from_report

from_report(report, fund, **kwargs) -> Position

利用回測完的報告 finlab.report.Report 建構股票部位。

Example

from finlab import backtest
from finlab.online.order_executor import Position

report1 = backtest.sim(...)
report2 = backtest.sim(...)

position1 = Position.from_report(report1, 1000000) # 策略操作金額一百萬
position2 = Position.from_report(report2, 1000000) # 策略操作金額一百萬

total_position = position1 + position2

參數

report finlab.report.Report

回測完的結果報告。

fund int

希望部屬的資金。

price pd.Series or `dict` of `float`

股票代號對應到的價格,若無則使用最近個交易日的收盤價。

odd_lot bool

是否考慮零股。預設為 False,只使用整張操作。

board_lot_size int

一張股票等於幾股。預設為1000,一張等於1000股。

allocation func

資產配置演算法選定,預設為finlab.online.utils.greedy_allocation(最大資金部屬貪婪法)。

margin_trading bool

做多部位是否使用融資

short_selling bool

做空部位是否使用融券

day_trading_long bool

做多部位為當沖先做多

day_trading_short bool

做空部位為當沖先做空

leverage float

目標槓桿倍數,預設為1.0(不使用融資)。若>1.0,會根據波動度分配融資。

kwargs Any 預設 {}
to_json
函式

to_json

to_json(path) -> None

Converts the position dictionary to a JSON file and saves it to the specified path.

參數

path str

The path where the JSON file will be saved.

回傳

None

None

from_json
函式

from_json

from_json(path) -> Position

Load a JSON file from the given path and convert it to a list of positions.

參數

path str

The path to the JSON file.

回傳

Position

None

讓帳戶持股對齊目標

create_orders() 會先取消帳戶裡未成交的委託,再送出讓帳戶持股變成目標部位的委託,預設以最後一筆成交價掛限價單,回傳送出(或預覽時將送出)的委託清單。market_order、best_price_limit、extra_bid_pct 三者只能擇一設定。

調整掛單價格

# 預設以最後成交價掛限價單,extra_bid_pct 可微調價格
# 負值 = 比市價低(較保守),正值 = 比市價高(較積極)
executor.create_orders(
    extra_bid_pct=-0.01  # 比當前價格低 1% 掛單,避免滑價
)

# 類市價單:買單掛漲停價、賣單掛跌停價,盡量即刻成交
executor.create_orders(market_order=True)

大額委託分批送出

import time

# 將大額訂單分 3 次執行
# progress 是「剩餘差距」的比例,不是原始目標的比例
# 每輪成交後剩餘量會減少,所以比例要遞增
for p in [1 / 3, 1 / 2, 1]:
    executor.create_orders(
        progress=p  # 依序執行 1/3、1/2、1 的剩餘量
    )
    time.sleep(60)  # 間隔 1 分鐘

分開開發與正式環境

import os

# 開發環境:跑完整流程,但只印出委託
executor = OrderExecutor(target, account=account)
if os.environ.get('ENV') == 'development':
    executor.create_orders(view_only=True)
else:
    executor.create_orders()
類別

finlab.online.OrderExecutor

OrderExecutor(target_position, account)

對比實際帳戶與欲部屬的股票部位,進行同步

參數

target_position Position

想要部屬的股票部位。

account Account

目前支援永豐與富果帳戶,請參考 Account 來實做。

方法與屬性

show_alerting_stocks
函式

show_alerting_stocks

show_alerting_stocks() -> None

產生下單部位是否有警示股,以及相關資訊

cancel_orders
函式

cancel_orders

cancel_orders(buy_only, sell_only) -> None

刪除所有未實現委託單

參數

buy_only bool 預設 False

若設為 True,只取消買單

sell_only bool 預設 False

若設為 True,只取消賣單

generate_orders
函式

generate_orders

generate_orders(progress, progress_precision, as_entries, quantity_type, _internal) -> list[dict[str, Any]] | list[Any]

Generate orders based on the difference between target position and present position.

參數

progress float 預設 1
progress_precision int | None 預設 0
as_entries bool | None
quantity_type str 預設 'decimal'
_internal bool 預設 False

回傳

ordersdict

Orders to be executed.

execute_orders
函式

execute_orders

execute_orders(orders, market_order, best_price_limit, view_only, extra_bid_pct, cancel_orders, buy_only, sell_only) -> list[dict[str, Any]]

產生委託單,將部位同步成 self.target_position 預設以該商品最後一筆成交價設定為限價來下單

參數

orders list

欲下單的部位,通常是由 self.generate_orders 產生。

market_order bool 預設 False

以類市價盡量即刻成交:所有買單掛漲停價,所有賣單掛跌停價

best_price_limit bool 預設 False

掛芭樂價:所有買單掛跌停價,所有賣單掛漲停價

view_only bool 預設 False

預設為 False,會實際下單。若設為 True,不會下單,只會回傳欲執行的委託單資料(dict)

extra_bid_pct float 預設 0

以該百分比值乘以價格進行追價下單,如設定為 0.05 時,將以當前價的 +(-)5% 的限價進買入(賣出),也就是更有機會可以成交,但是成交價格可能不理想; 假如設定為 -0.05 時,將以當前價的 -(+)5% 進行買入賣出,也就是限價單將不會立即成交,然而假如成交後,價格比較理想。參數有效範圍為 -0.1 到 0.1 內。

buy_only bool 預設 False

若設為 True,只下買單

sell_only bool 預設 False

若設為 True,只下賣單

cancel_orders bool 預設 True
create_orders
函式

create_orders

create_orders(market_order, best_price_limit, view_only, extra_bid_pct, progress, progress_precision, buy_only, sell_only) -> list[dict[str, Any]]

產生委託單,將部位同步成 self.target_position 預設以該商品最後一筆成交價設定為限價來下單

參數

market_order bool 預設 False

以類市價盡量即刻成交:所有買單掛漲停價,所有賣單掛跌停價

best_price_limit bool 預設 False

掛芭樂價:所有買單掛跌停價,所有賣單掛漲停價

view_only bool 預設 False

預設為 False,會實際下單。若設為 True,不會下單,只會回傳欲執行的委託單資料(dict)

extra_bid_pct float 預設 0

以該百分比值乘以價格進行追價下單,如設定為 0.05 時,將以當前價的 +(-)5% 的限價進買入(賣出),也就是更有機會可以成交,但是成交價格可能不理想; 假如設定為 -0.05 時,將以當前價的 -(+)5% 進行買入賣出,也就是限價單將不會立即成交,然而假如成交後,價格比較理想。參數有效範圍為 -0.1 到 0.1 內。

progress float 預設 1

進度,預設為 1,即全部下單。若設定為 0.5,則只下一半的單。

progress_precision int 預設 0

進度的精度,預設為 0,即只下整數張。若設定為 1,則下到 0.1 張。

buy_only bool 預設 False

若設為 True,只下買單

sell_only bool 預設 False

若設為 True,只下賣單

update_order_price
函式

update_order_price

update_order_price(extra_bid_pct, buy_only, sell_only) -> None

更新委託單,將委託單的限價調整成當天最後一筆價格。 (讓沒成交的限價單去追價)

參數

extra_bid_pct float 預設 0

以該百分比值乘以價格進行追價下單,如設定為 0.1 時,將以超出(低於)現價之10%價格下單,以漲停(跌停)價為限。參數有效範圍為 0 到 0.1 內

buy_only bool 預設 False

若設為 True,只更新買單

sell_only bool 預設 False

若設為 True,只更新賣單

下單前要檢查的事

過濾處置股

直接下單可能買到處置股。OrderExecutor 的目標不是回測用的布林 DataFrame,要先用 to_list() 取出部位、過濾後再用 Position.from_list() 組回來:

from finlab import data
from finlab.online.order_executor import OrderExecutor, Position

# 錯誤:直接下單可能買到處置股
# OrderExecutor(target, account=account).create_orders()  # ❌

# 正確:使用處置股 filter 過濾 finlab.online.Position
disposal_stock_filter = data.get('etl:disposal_stock_filter')
latest_filter = disposal_stock_filter.iloc[-1].fillna(False)

filtered_target = Position.from_list(
    [entry for entry in target.to_list() if latest_filter.get(entry['stock_id'], False)]
)

OrderExecutor(filtered_target, account=account).create_orders()  # ✅

executor.show_alerting_stocks() 會列出委託中屬於處置股或信用交易警示股的標的,這些股票需要事先向券商圈存現金或股票(僅限台股)。

確認資金足夠

下單前比較目標部位市值與 account.get_total_balance(),不足時調低 Position.from_report() 的 fund 或追加資金。檢查方式見下方常見問題「如何確保下單安全?」的 safe_order_check()。

漲跌停鎖死可能整天無法成交

台股有漲跌停限制,create_orders() 會用券商回報的漲跌停價夾住限價;股票鎖漲停時的買單、鎖跌停時的賣單可能整天無法成交。隔日的漲跌停價可查參考價表 reference_price(每檔股票一列):

from finlab import data

ref = data.get('reference_price').set_index('stock_id')
print(ref.loc[['2330', '2317'], ['收盤價', '漲停價', '跌停價']])

查詢委託狀態

account.get_orders() 回傳目前所有委託單,key 是券商的委託單編號,value 是 Order:

for order_id, order in account.get_orders().items():
    print(order_id, order.stock_id, order.status, order.filled_quantity)

status 是 NEW、PARTIALLY_FILLED、FILLED 或 CANCEL;stock_id 也可以用 symbol 取得。

類別

finlab.online.Order

Order(order_id, stock_id, action, price, quantity, filled_quantity, status, order_condition, time, org_order)

Order status

委託單的狀態

參數

order_id str

委託單的 id,與券商 API 所提供的 id 一致

stock_id str

股票代號 ex: '2330' (亦可透過 symbol 屬性存取)

action Action

買賣方向,通常為 'BUY' 或是 'SELL'

price Number

股票買賣的價格(限價單)

quantity Number

委託股票的總數量(張數),允許小數點

filled_quantity Number

以成交股票的數量(張數),允許小數點

status OrderStatus

委託狀態,可以設定為:'NEW', 'PARTIALLY_FILLED', 'FILLED', 'CANCEL'

time datetime.datetime

委託時間

org_order Any = None

券商所提供的委託物件格式

order_condition OrderCondition

常見問題

如何確保下單安全?

實施以下檢查機制:

def safe_order_check(target, account, max_stocks=50, max_weight=0.15):
    """下單前安全檢查"""
    price = account.get_price([p['symbol'] for p in target])
    # 台股的 quantity 單位是張(1 張 = 1000 股)
    values = [abs(float(p['quantity'])) * 1000 * price[p['symbol']] for p in target]
    total = sum(values)

    # 1. 檢查持倉數量
    if len(values) > max_stocks:
        raise ValueError(f"❌ 持倉過多({len(values)} 檔),超過限制 {max_stocks} 檔")

    # 2. 檢查單一持股權重
    largest = max(values) / total if total else 0
    if largest > max_weight:
        raise ValueError(f"❌ 單股權重過高({largest:.1%}),超過限制 {max_weight:.0%}")

    # 3. 檢查可用資金
    available = account.get_total_balance()
    if total > available * 1.1:  # 留 10% 緩衝
        raise ValueError(f"❌ 資金不足!需要 {total:,.0f},可用 {available:,.0f}")

    print("✅ 安全檢查通過")
    return True

# 使用
if safe_order_check(target, account):
    executor.create_orders()
實盤與回測有落差怎麼辦?

常見原因:

  1. 滑價:實盤成交價與預期不符
    • 解決:使用限價單,或調整 extra_bid_pct
  2. 成交量不足:無法全部成交
  3. 交易時間差異:回測假設即時成交
    • 解決:設定合理的下單時間(如收盤前 30 分鐘)
如何設定自動下單排程?
import time

import schedule
from finlab import data
from finlab.backtest import sim
from finlab.online.order_executor import OrderExecutor, Position

def auto_trade():
    """每日自動下單"""
    try:
        # 1. 重新執行策略,取得最新目標部位
        close = data.get('price:收盤價')
        position = (close / close.shift(120)).is_largest(20)
        report = sim(position, resample='M', upload=False)
        target = Position.from_report(report, fund=1_000_000)

        # 2. 安全檢查
        if safe_order_check(target, account):
            # 3. 執行下單
            OrderExecutor(target, account=account).create_orders()
            print(f"✅ 下單完成:{time.strftime('%Y-%m-%d %H:%M:%S')}")
    except Exception as e:
        # 4. 錯誤通知(Email、聊天群組 webhook)
        print(f"❌ 下單失敗:{e}")

# 每日 13:00 執行(台股 13:30 收盤前 30 分鐘)
schedule.every().day.at("13:00").do(auto_trade)

while True:
    schedule.run_pending()
    time.sleep(60)

不想自己維持一個常駐程式,也可以用 FinLab CLI 每天重跑策略。

如何處理下單失敗?
import time

try:
    executor.create_orders()
    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")

    # 根據錯誤類型決定是否重試
    if "network" in str(e).lower():
        time.sleep(30)  # 網路問題,30 秒後重試
        executor.create_orders()

下一步

原始碼在 GitHub。

↑↓ 選擇 Enter 開啟 Esc 關閉