Skip to content

finlab.online

Live trading module for deploying backtested strategies to real broker accounts with automated order execution.

Important Risk Warning

Live trading involves real money. Errors can lead to financial losses!

Mandatory Safety Principles:

  1. Always test with a simulated account for at least 1 month first
  2. Start with no more than 10-20% of total capital
  3. Monitor positions and order status daily
  4. Set stop-loss mechanisms to avoid large single losses
  5. Use separate accounts for development/testing and production environments
  6. NEVER use all capital on an untested strategy
  7. NEVER ignore error messages or abnormal states

Use Cases

  • Automated trading: Automatically place orders and rebalance based on strategy signals
  • Multi-account management: Manage multiple broker accounts simultaneously (SinoPac, Fubon, etc.)
  • Live tracking: Record actual trading performance and compare with backtest results
  • Risk control: Automatically detect position anomalies, single-stock overweight, insufficient capital, etc.

Transition from Backtesting to Live Trading

graph LR
    A[Strategy Backtest] --> B[Out-of-Sample Test]
    B --> C[Upload to Cloud]
    C --> D[Simulated Account Test]
    D --> E{Test Passed?}
    E -->|Yes| F[Small Capital Live]
    E -->|No| A
    F --> G[Gradually Increase Capital]

Recommended Timeline: 1. Strategy backtesting (1-2 weeks): Confirm logic is correct 2. Out-of-sample testing (2-4 weeks): Use unseen data 3. Simulated account (4-8 weeks): Real market conditions, no real money 4. Small capital live (8-12 weeks): 10-20% capital test 5. Gradual expansion (as appropriate): Increase capital only after confirming stability

Quick Examples

Basic Usage: Automated Order Execution

from finlab.online.order_executor import Position, OrderExecutor
from finlab.online.base_account import Account

# 1. Configure broker account (example: SinoPac Securities)
account = Account(
    broker='shioaji',            # SinoPac Securities
    account='YOUR_ACCOUNT_ID',   # Account ID
    password='YOUR_PASSWORD'     # Password (use environment variables recommended)
)

# 2. Create order executor
executor = OrderExecutor(
    account=account,
    market='TW',                 # Taiwan stocks
    base_currency='TWD'          # TWD
)

# 3. Get latest positions from cloud
position = Position.from_report(
    report_id='YOUR_REPORT_ID',  # Cloud strategy ID
    total_funds=1000000          # Total capital 1M TWD
)

# 4. Execute orders (please test on simulated account first!)
executor.create_orders(position)

Detailed Guide

See the following tutorials for the complete workflow:

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 代表成功更新委託單

Account Security Best Practices

Use environment variables for sensitive information:

import os

account = Account(
    broker='shioaji',
    account=os.environ.get('BROKER_ACCOUNT'),
    password=os.environ.get('BROKER_PASSWORD')
)
# Never hardcode credentials in source code!

Separate development/production environments:

# Development: Use simulated account
if os.environ.get('ENV') == 'development':
    account = Account(broker='shioaji', simulate=True)
else:
    # Production: Use real account
    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 內

Order Execution Best Practices

Intraday orders (use limit orders):

# Avoid slippage by using limit orders
executor.create_orders(
    position,
    order_type='limit',          # Limit order
    price_offset=-0.01           # Place order 1% below current price
)

Close-price orders (consistent with backtest):

# Use close price for consistency with backtest results
executor.create_orders(
    position,
    order_type='market_close'    # Market-on-close order
)

Batch orders (large orders):

# Split large orders into 3 executions
for i in range(3):
    executor.create_orders(
        position,
        quantity_ratio=1/3       # 1/3 quantity each time
    )
    time.sleep(60)               # 1-minute interval

Common Errors and Risks

1. Not checking disposal stocks

# Wrong: Direct ordering may buy disposal stocks
executor.create_orders(position)

# Correct: Filter out disposal stocks
disposal_stocks = data.get('etl:處置股')
# Remove disposal stocks from position
# position = position[~position.index.isin(disposal_stocks)]
executor.create_orders(position)

2. Exceeding available capital

# Check available capital
available_cash = account.get_balance()
required_cash = position.total_value

if required_cash > available_cash:
    print(f"Insufficient capital! Required {required_cash:,.0f}, available {available_cash:,.0f}")
    # Adjust position or add capital
else:
    executor.create_orders(position)

3. Locked at limit up/down, unable to execute

# Check if near limit up/down
close = data.get('price:收盤價')
limit_up = data.get('price:漲停價')
limit_down = data.get('price:跌停價')

# Filter stocks near limit up/down (e.g., within 2%)
near_limit = (
    (close > limit_up * 0.98) |  # Near limit up
    (close < limit_down * 1.02)   # Near limit down
)
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' (亦可透過 symbol 屬性存取)

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 針對不同券商來建構相對應的操作帳戶

FAQ

Q: How do I ensure order safety?

Implement the following safety checks:

def safe_order_check(position, account, max_stocks=50, max_weight=0.15):
    """Pre-order safety check"""

    # 1. Check position count
    if len(position) > max_stocks:
        raise ValueError(f"Too many positions ({len(position)}), exceeds limit of {max_stocks}")

    # 2. Check single stock weight
    max_stock_weight = position.max()
    if max_stock_weight > max_weight:
        raise ValueError(f"Single stock weight too high ({max_stock_weight:.1%}), exceeds limit of {max_weight:.0%}")

    # 3. Check available capital
    available = account.get_balance()
    required = position.total_value
    if required > available * 1.1:  # 10% buffer
        raise ValueError(f"Insufficient capital! Required {required:,.0f}, available {available:,.0f}")

    print("Safety check passed")
    return True

# Usage
if safe_order_check(position, account):
    executor.create_orders(position)

Q: What if live performance differs from backtest?

Common causes:

  1. Slippage: Actual execution price differs from expected
  2. Solution: Use limit orders or adjust price_offset

  3. Insufficient volume: Cannot fully execute

  4. Solution: Use LiquidityAnalysis to check beforehand

  5. Timing differences: Backtest assumes instant execution

  6. Solution: Set reasonable order timing (e.g., 30 minutes before close)

Q: How do I set up automatic trading schedules?

import schedule
import time

def auto_trade():
    """Daily automatic trading"""
    try:
        # 1. Get latest strategy
        position = Position.from_report(
            report_id='YOUR_REPORT_ID',
            total_funds=1000000
        )

        # 2. Safety check
        if safe_order_check(position, account):
            # 3. Execute orders
            executor.create_orders(position)
            print(f"Order completed: {time.strftime('%Y-%m-%d %H:%M:%S')}")
    except Exception as e:
        # 4. Error notification (LINE/Email)
        print(f"Order failed: {e}")
        # line_notify(f"Order failed: {e}")

# Execute daily at 14:30 (30 minutes before close)
schedule.every().day.at("14:30").do(auto_trade)

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

Q: How do I handle order failures?

try:
    executor.create_orders(position)
    print("Order successful")
except Exception as e:
    print(f"Order failed: {e}")

    # Log error
    with open('order_errors.log', 'a') as f:
        f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {e}\n")

    # Send notification
    # line_notify(f"Order failed: {e}")

    # Decide whether to retry based on error type
    if "network" in str(e).lower():
        time.sleep(30)  # Network issue, retry after 30 seconds
        executor.create_orders(position)

Resources


Final Reminder

Always test thoroughly on a simulated account before using real capital!

Recommended Test Checklist: - Order workflow executes without errors - Position calculations are correct (match expectations) - Capital control is normal (no overspending) - Error handling mechanisms work - Notification mechanisms function properly - Runs continuously for 1 month without issues

Only consider using real capital after all items above are confirmed!