跳轉到

finlab.online

實盤交易模組,用於將回測策略部署至真實券商帳戶,執行自動下單。

⚠️ 重要風險警告

實盤交易涉及真實金錢,錯誤可能導致財務損失!

必須遵守的安全原則

  1. 務必先用模擬帳戶測試至少 1 個月
  2. 初期資金不超過總資金的 10-20%
  3. 每日監控持倉與訂單狀態
  4. 設定停損機制,避免單次巨大虧損
  5. 在開發/測試環境與生產環境使用不同帳戶
  6. 絕對不要在未測試的策略上使用全部資金
  7. 不要忽略錯誤訊息或異常狀態

使用情境

  • 自動化交易: 根據策略自動下單、調倉,無需手動操作
  • 多帳戶管理: 同時管理多個券商帳戶(永豐、富邦等)
  • 實盤追蹤: 記錄實際交易績效,與回測結果對比
  • 風險控制: 自動檢測持倉異常、單股過重、資金不足等問題

從回測到實盤的過渡流程

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

module_version = ''

股票帳戶的 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()
玉山富果:
from finlab.online.fugle_account import FugleAccount
import os
os.environ['FUGLE_CONFIG_PATH'] = '玉山富果交易設定檔(config.ini.example)路徑'
os.environ['FUGLE_MARKET_API_KEY'] = '玉山富果的行情API Token'

acc = FugleAccount()

cancel_order abstractmethod

cancel_order(order_id)

刪除委託單

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

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

ATTRIBUTE DESCRIPTION
order_id

券商所提供的委託單 ID

TYPE: str

RETURNS DESCRIPTION
None

代表成功更新委託單

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: Action

stock_id

股票代號 ex: '2330'

TYPE: str

quantity

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

TYPE: Number

price

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

TYPE: Number DEFAULT: None

force

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

TYPE: bool

wait_for_best_price

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

TYPE: bool

RETURNS DESCRIPTION
str

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

get_cash abstractmethod

get_cash()

拿到當前帳戶的現金

get_market

get_market()

拿到當前帳戶的市場

get_orders abstractmethod

get_orders()

拿到現在所有委託單

RETURNS DESCRIPTION
Dict[str, Order]

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

Example

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

get_position abstractmethod

get_position()

拿到當前帳戶的股票部位

RETURNS DESCRIPTION
Position

當前股票部位

get_settlement abstractmethod

get_settlement()

拿到當前帳戶的結算資料

get_stocks abstractmethod

get_stocks(stock_ids)

拿到現在股票報價

ATTRIBUTE DESCRIPTION
stock_ids

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

TYPE: `list` of `str`

RETURNS DESCRIPTION
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_total_balance abstractmethod

get_total_balance()

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

update_order abstractmethod

update_order(order_id, price=None, quantity=None)

產生新的委託單

ATTRIBUTE DESCRIPTION
order_id

券商所提供的委託單 ID

TYPE: str

price

更新的限價

TYPE: Number

quantity

更新的待成交量

TYPE: Number

RETURNS DESCRIPTION
None

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

帳戶安全最佳實踐

使用環境變數存儲敏感資訊:

import os

account = Account(
    broker='shioaji',
    account=os.environ.get('BROKER_ACCOUNT'),
    password=os.environ.get('BROKER_PASSWORD')
)
# 不要在程式碼中硬編碼帳密!

分離開發/生產環境:

# 開發環境:使用模擬帳戶
if os.environ.get('ENV') == 'development':
    account = Account(broker='shioaji', simulate=True)
else:
    # 生產環境:使用真實帳戶
    account = Account(
        broker='shioaji',
        account=os.environ.get('PROD_ACCOUNT'),
        password=os.environ.get('PROD_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

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

TYPE: `dict` of `str`

margin_trading

做多部位是否使用融資

TYPE: bool

short_selling

做空部位是否使用融券

TYPE: bool

day_trading_long

做多部位為當沖先做多

TYPE: bool

day_trading_short

做空部位為當沖先做空

TYPE: bool

Examples:

設計部位,持有一張和 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_json classmethod

from_json(path)

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: str

RETURNS DESCRIPTION

None

from_list classmethod

from_list(position)

利用 dict 建構股票部位

ATTRIBUTE DESCRIPTION
position

股票詳細部位

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_TRADINGDAY_TRADING_LONGSHORT_SELLINGDAY_TRADING_SHORT

TYPE: `list` of `dict`

from_report classmethod

from_report(report, fund, **kwargs)

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

ATTRIBUTE DESCRIPTION
report

回測完的結果報告。

TYPE: Report

fund

希望部屬的資金。

TYPE: int

price

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

TYPE: pd.Series or `dict` of `float`

odd_lot

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

TYPE: bool

board_lot_size

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

TYPE: int

allocation

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

TYPE: func

margin_trading

做多部位是否使用融資

TYPE: bool

short_selling

做空部位是否使用融券

TYPE: bool

day_trading_long

做多部位為當沖先做多

TYPE: bool

day_trading_short

做空部位為當沖先做空

TYPE: bool

leverage

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

TYPE: float

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

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: dict[str, float] 或 pd.Series

fund

資金大小

TYPE: int

price

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

TYPE: None 或 pd.Series 或 dict[str, float]

odd_lot

是否考慮零股

TYPE: bool

board_lot_size

一張股票等於幾股

TYPE: None 或 int

allocation

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

TYPE: function

precision

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

TYPE: None 或 int

leverage

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

TYPE: float

price_history

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

TYPE: None 或 pd.DataFrame

margin_trading

做多部位是否使用融資

TYPE: bool

short_selling

做空部位是否使用融券

TYPE: bool

day_trading_long

做多部位為當沖先做多

TYPE: bool

Examples:

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

from finlab.online.order_executor import Position

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

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

to_json

to_json(path)

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

PARAMETER DESCRIPTION
path

The path where the JSON file will be saved.

TYPE: str

RETURNS DESCRIPTION

None

OrderExecutor

finlab.online.order_executor.OrderExecutor

OrderExecutor(target_position, account)

對比實際帳戶與欲部屬的股票部位,進行同步 Arguments: target_position (Position): 想要部屬的股票部位。 account (Account): 目前支援永豐與富果帳戶,請參考 Account 來實做。

cancel_orders

cancel_orders()

刪除所有未實現委託單

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: bool

best_price_limit

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

TYPE: bool

view_only

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

TYPE: bool

extra_bid_pct

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

TYPE: float

progress

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

TYPE: float

progress_precision

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

TYPE: int

buy_only

若設為 True,只下買單

TYPE: bool

sell_only

若設為 True,只下賣單

TYPE: bool

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

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

TYPE: list

market_order

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

TYPE: bool

best_price_limit

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

TYPE: bool

view_only

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

TYPE: bool

extra_bid_pct

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

TYPE: float

buy_only

若設為 True,只下買單

TYPE: bool

sell_only

若設為 True,只下賣單

TYPE: bool

generate_orders

generate_orders(progress=1, progress_precision=0)

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

Returns: orders (dict): Orders to be executed.

show_alerting_stocks

show_alerting_stocks()

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

update_order_price

update_order_price(extra_bid_pct=0)

更新委託單,將委託單的限價調整成當天最後一筆價格。 (讓沒成交的限價單去追價) Attributes: extra_bid_pct (float): 以該百分比值乘以價格進行追價下單,如設定為 0.1 時,將以超出(低於)現價之10%價格下單,以漲停(跌停)價為限。參數有效範圍為 0 到 0.1 內

下單最佳實踐

盤中下單(使用限價單):

# 避免滑價,使用限價單
executor.create_orders(
    position,
    order_type='limit',          # 限價單
    price_offset=-0.01           # 比當前價格低 1% 掛單
)

收盤下單(與回測一致):

# 使用收盤價,與回測結果更一致
executor.create_orders(
    position,
    order_type='market_close'    # 收盤市價單
)

分批下單(大額訂單):

# 將大額訂單分 3 次執行
for i in range(3):
    executor.create_orders(
        position,
        quantity_ratio=1/3       # 每次 1/3 數量
    )
    time.sleep(60)               # 間隔 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. 漲跌停鎖死無法成交

# 檢查是否接近漲跌停
close = data.get('price:收盤價')
limit_up = data.get('price:漲停價')
limit_down = data.get('price:跌停價')

# 過濾接近漲跌停的股票(如差距 < 2%)
near_limit = (
    (close > limit_up * 0.98) |  # 接近漲停
    (close < limit_down * 1.02)   # 接近跌停
)
position = position[~near_limit]

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: str

stock_id

股票代號 ex: '2330'

TYPE: str

action

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

TYPE: Action

price

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

TYPE: Number

quantity

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

TYPE: Number

filled_quantity

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

TYPE: Number

status

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

TYPE: OrderStatus

time

委託時間

TYPE: datetime

org_order

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

TYPE: Any = None

order_panel()

finlab.online.panel.order_panel

order_panel(account)

下單 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: 實盤與回測有落差怎麼辦?

常見原因:

  1. 滑價:實盤成交價與預期不符
  2. 解決:使用限價單,或調整 price_offset

  3. 成交量不足:無法全部成交

  4. 解決:使用 LiquidityAnalysis 事先檢測

  5. 交易時間差異:回測假設即時成交

  6. 解決:設定合理的下單時間(如收盤前 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)

參考資源


最後提醒

請務必在模擬帳戶充分測試後,才使用真實資金!

建議測試清單: - ✅ 下單流程完整執行無錯誤 - ✅ 持倉計算正確(與預期一致) - ✅ 資金控制正常(不超支) - ✅ 異常處理機制有效 - ✅ 通知機制正常運作 - ✅ 連續運行 1 個月無問題

只有在以上項目全部確認後,才考慮使用真實資金!