Order (Single Strategy)
Broker Integration - Taiwan Market
The live order execution system currently integrates with Taiwan-based brokers (E.SUN Securities, Sinopac Securities, Masterlink Securities, and Fubon Securities). For US market trading, please use your broker's native API. The position calculation and portfolio management features of FinLab can still be used to generate target positions for any market.
Execute Trading Strategy
The trading system requires finlab>=0.3.0.dev1; upgrade before placing orders.
Run your strategy before the next trading day opens:
Calculate Share Quantities
Next, display the current positions:
| stock_id | entry_date | exit_date | entry_sig_date | exit_sig_date | position | period | entry_index | exit_index | return | entry_price | exit_price | mae | gmfe | bmfe | mdd | pdays | next_weights |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1416 | 2021-10-01 | NaT | 2021-09-30 | 2022-06-30 | 0.510197 | 159.0 | 3566.0 | -1.0 | -3.539823e-02 | 10.60 | NaN | -0.053097 | 0.070796 | 0.070796 | -0.115702 | 37.0 | 0.060665 |
| 1453 | 2021-10-01 | NaT | 2021-09-30 | 2022-06-30 | 0.510197 | 159.0 | 3566.0 | -1.0 | 2.348165e+00 | 8.43 | NaN | -0.086763 | 2.403782 | 0.245829 | -0.624585 | 60.0 | 0.171264 |
| 1524 | 2022-04-01 | NaT | 2022-03-31 | 2022-06-30 | 0.611632 | 39.0 | 3686.0 | -1.0 | -3.330669e-16 | 10.60 | NaN | -0.476658 | 0.015152 | 0.000000 | -0.476658 | 2.0 | 0.122168 |
| 2543 | 2021-10-01 | NaT | 2021-09-30 | NaT | 0.510197 | 159.0 | 3566.0 | -1.0 | 1.073232e-01 | 7.46 | NaN | -0.063131 | 0.363636 | 0.008838 | -0.268519 | 44.0 | 0.070298 |
| 2701 | 2022-04-01 | NaT | 2022-03-31 | 2022-06-30 | 0.611632 | 39.0 | 3686.0 | -1.0 | -3.330669e-16 | 12.05 | NaN | -0.025105 | 0.029289 | 0.029289 | -0.052846 | 18.0 | 0.063107 |
Once confirmed, calculate the number of lots (1 lot = 1000 shares in Taiwan) for each stock:
from finlab.online.order_executor import Position
# total fund
fund = 1000000
position = Position.from_report(report, fund)
print(position)
The Position above indicates that your account should hold 1 lot of 2330 (TSMC), using cash settlement.
Advanced Position Adjustments
Odd Lot Trading
To use odd lot positions, simply modify the code as follows:
# Round lots
position = Position.from_report(report, fund)
# Odd lots
position = Position.from_report(report, fund, odd_lot=True)
Custom Position Quantities
You can also construct a Position directly:
[{'stock_id': '2330', 'quantity': 1, 'order_condition': <OrderCondition.CASH: 1>}
{'stock_id': '1101', 'quantity': 1.001, 'order_condition': <OrderCondition.CASH: 1>}]
Position Addition/Subtraction
Position objects support addition and subtraction:
# Remove 1 lot of 2330
new_position = position - Position({'2330': 1})
# Add 1 lot of 1101
new_position = position + Position({'1101': 1})
Multi-Strategy Position Aggregation
If you have multiple positions, you can aggregate them:
from finlab import backtest
from finlab.online.order_executor import Position
report1 = backtest.sim(...)
report2 = backtest.sim(...)
position1 = Position.from_report(report1, 1000000) # Strategy fund: 1 million
position2 = Position.from_report(report2, 1000000) # Strategy fund: 1 million
total_position = position1 + position2
Placing Orders
1. Install Broker API
Currently supports E.SUN Securities, Sinopac Securities, Masterlink Securities, and Fubon Securities trading systems. Choose one:
2. Connect to Brokerage Account
Set account credentials as environment variables; configure only the broker you use.
Choose your broker
- First, follow the config tutorial to obtain the config file.
- Then log in to the E.SUN Securities API to get the market data Token.
from finlab.online.esun_account import EsunAccount
import os
os.environ['ESUN_CONFIG_PATH'] = 'path to config.ini.example'
os.environ['ESUN_MARKET_API_KEY'] = 'E.SUN market API Token'
os.environ['ESUN_ACCOUNT_PASSWORD'] = 'E.SUN account password'
os.environ['ESUN_CERT_PASSWORD'] = 'E.SUN certificate password'
acc = EsunAccount()
Legacy Fugle environment variables (still supported)
from finlab.online.fugle_account import FugleAccount
import os
os.environ['FUGLE_CONFIG_PATH'] = 'path to config.ini.example'
os.environ['FUGLE_MARKET_API_KEY'] = 'market API Token'
os.environ['FUGLE_ACCOUNT_PASSWORD'] = 'account password'
os.environ['FUGLE_CERT_PASSWORD'] = 'certificate password'
acc = FugleAccount()
For error codes, see the E.SUN Securities documentation. If unfamiliar with the broker API, practice with the broker's tutorial first.
-
Please obtain the certificate first:
- Windows certificate download
- MacOS certificate: Currently Sinopac supports MacOS for trading but does not support obtaining certificates on MacOS. You can first use a Windows machine to obtain the certificate, then use it on MacOS.
import os
from finlab.online.sinopac_account import SinopacAccount
os.environ['SHIOAJI_API_KEY'] = 'Sinopac API_KEY'
os.environ['SHIOAJI_SECRET_KEY'] = 'Sinopac SECRET_KEY'
os.environ['SHIOAJI_CERT_PERSON_ID']= 'National ID number'
os.environ['SHIOAJI_CERT_PATH']= 'Sinopac certificate path'
os.environ['SHIOAJI_CERT_PASSWORD'] = 'Sinopac certificate password' # defaults to National ID
acc = SinopacAccount()
Refer to the Masterlink Securities tutorial to obtain the certificate, place it in the appropriate path, install the required packages, and then run:
import os
from finlab.online.masterlink_account import MasterlinkAccount
os.environ['MASTERLINK_NATIONAL_ID'] = 'National ID number'
os.environ['MASTERLINK_ACCOUNT'] = 'Trading account'
os.environ['MASTERLINK_ACCOUNT_PASS'] = 'Password'
os.environ['MASTERLINK_CERT_PATH'] = 'Masterlink certificate path'
os.environ['MASTERLINK_CERT_PASS'] = 'Masterlink certificate password' # defaults to National ID
acc = MasterlinkAccount()
Setup is now complete!
Refer to the Fubon Securities tutorial to obtain the certificate, place it in the appropriate path, install the required packages, and then run:
import os
from fubon_account import FubonAccount
# Set environment variables
import os
os.environ['FUBON_NATIONAL_ID'] = "A123456789"
os.environ['FUBON_ACCOUNT_PASS'] = "your_password"
os.environ['FUBON_CERT_PATH'] = "/path/to/cert.pfx"
account = FubonAccount()
Setup is now complete!
3. Batch Order Execution
Finally, use OrderExecutor to adjust the brokerage account's positions according to the target position.
from finlab.online.order_executor import OrderExecutor
# Order Executor
order_executer = OrderExecutor(position , account=acc)
If the position contains "full-delivery stocks", "disposition stocks", or "alert stocks", you must pre-deposit funds at your brokerage account first.
After completing the deposit based onshow_alerting_stocks results, proceed with order execution:
# Preview orders (view-only mode, no actual orders placed)
order_executer.create_orders(view_only=True)
# Execute orders (will actually place orders; test during market close for first-time use)
# Default: uses the latest transaction price as the limit price
order_executer.create_orders()
# Update limit price (use the latest transaction price as new limit price)
order_executer.update_order_price()
# Cancel all pending orders
order_executer.cancel_orders()
View Account Positions
For any supported broker account, you can check positions as follows:
# Choose your broker (pick one)
# Sinopac
from finlab.online.sinopac_account import SinopacAccount
acc = SinopacAccount()
# E.SUN Securities
from finlab.online.esun_account import EsunAccount
acc = EsunAccount()
# Or use the legacy name (still supported)
# from finlab.online.fugle_account import FugleAccount
# acc = FugleAccount()
print(acc.get_position())
[{'stock_id': '2330', 'quantity': 1, 'order_condition': <OrderCondition.CASH: 1>}
{'stock_id': '1101', 'quantity': 1.001, 'order_condition': <OrderCondition.CASH: 1>}]
Common Errors and Solutions
Important Risk Warning
Live trading involves real money. Errors can lead to financial losses!
Check before placing orders:
1. Test on a simulated account for at least 1 week
2. Initial capital should not exceed 10-20% of total funds
3. Use view_only=True to preview orders
4. Monitor positions and order status daily
5. Set stop-loss mechanisms to avoid large single-trade losses
Error 1: Account Connection Failure
Symptom: acc = SinopacAccount() or acc = EsunAccount() throws a connection error
from finlab.online.sinopac_account import SinopacAccount
acc = SinopacAccount()
# ConnectionError: Unable to connect to broker API
Causes: - Environment variables not correctly set (API KEY, certificate path, etc.) - Certificate file does not exist or path is wrong - Certificate password is incorrect - Network connection issue or broker API service is down
Solution:
import os
from finlab.online.sinopac_account import SinopacAccount
# Step 1: Check if environment variables are set
required_env_vars = [
'SHIOAJI_API_KEY',
'SHIOAJI_SECRET_KEY',
'SHIOAJI_CERT_PERSON_ID',
'SHIOAJI_CERT_PATH',
'SHIOAJI_CERT_PASSWORD'
]
print("=== Environment Variable Check ===")
for var in required_env_vars:
value = os.environ.get(var)
if value:
# Hide sensitive information
if 'KEY' in var or 'PASSWORD' in var:
print(f"✅ {var}: {'*' * 8} (set)")
else:
print(f"✅ {var}: {value}")
else:
print(f"❌ {var}: NOT SET")
# Step 2: Check if certificate file exists
cert_path = os.environ.get('SHIOAJI_CERT_PATH')
if cert_path and os.path.exists(cert_path):
print(f"\n✅ Certificate file exists: {cert_path}")
else:
print(f"\n❌ Certificate file not found: {cert_path}")
print("Please verify the certificate path is correct")
exit(1)
# Step 3: Attempt connection and catch errors
try:
acc = SinopacAccount()
print("\n✅ Account connected successfully")
# Verify account status
balance = acc.get_balance()
print(f"✅ Available balance: NT$ {balance:,.0f}")
except FileNotFoundError as e:
print(f"\n❌ Certificate file error: {e}")
print("Please check that SHIOAJI_CERT_PATH is correct")
except PermissionError as e:
print(f"\n❌ Certificate permission error: {e}")
print("Please confirm the certificate password is correct (SHIOAJI_CERT_PASSWORD)")
except ConnectionError as e:
print(f"\n❌ Network connection error: {e}")
print("Possible causes:")
print("1. Unstable network connection")
print("2. Broker API service temporarily unavailable")
print("3. Incorrect API KEY or SECRET KEY")
print("Please try again later or contact broker customer support")
except Exception as e:
print(f"\n❌ Unknown error: {e}")
print("Please check that all environment variables are correctly set")
print("Reference tutorial: https://doc.finlab.tw/details/order_api/")
E.Sun Securities dedicated check:
import os
from finlab.online.esun_account import EsunAccount
# Check E.Sun Securities environment variables
required_vars = [
'ESUN_CONFIG_PATH',
'ESUN_MARKET_API_KEY',
'ESUN_ACCOUNT_PASSWORD',
'ESUN_CERT_PASSWORD'
]
for var in required_vars:
if not os.environ.get(var):
print(f"❌ {var} is not set")
print(f" Reference: https://www.esunsec.com.tw/trading-platforms/api-trading/docs/trading/quick-start/")
exit(1)
# Check if config.ini exists
config_path = os.environ.get('ESUN_CONFIG_PATH')
if not os.path.exists(config_path):
print(f"❌ Config file not found: {config_path}")
exit(1)
try:
acc = EsunAccount()
print("✅ E.Sun Securities account connected successfully")
except Exception as e:
print(f"❌ Connection failed: {e}")
print("Please verify the contents of config.ini are correct")
Error 2: Pre-Order Safety Check Failure
Symptom: Insufficient funds, too many holdings, or single stock weight too high
Causes:
- Available funds insufficient to buy the stocks specified in position
- Number of holdings generated by the strategy exceeds risk limits
- Single stock weight too high (risk not diversified)
Solution: Implement multi-layer safety checks
from finlab.online.order_executor import OrderExecutor, Position
from finlab.online.sinopac_account import SinopacAccount
def safe_order_check(position, account, max_stocks=50, max_weight=0.15, min_cash_buffer=0.1):
"""
Pre-order safety check function
Args:
position: Position object
account: Broker account object
max_stocks: Maximum number of holdings (default 50)
max_weight: Maximum weight per stock (default 15%)
min_cash_buffer: Minimum cash buffer ratio (default 10%)
Raises:
ValueError: Raised when any check fails
"""
print("=== Pre-Order Safety Check ===\n")
# Check 1: Number of holdings
num_stocks = len(position)
print(f"Number of holdings: {num_stocks}")
if num_stocks > max_stocks:
raise ValueError(
f"❌ Too many holdings ({num_stocks}), exceeds limit of {max_stocks}\n"
f" Suggestion: use .is_largest(N) to limit the number of selected stocks"
)
print(f"✅ Holdings count check passed (< {max_stocks})\n")
# Check 2: Single stock weight
# Assume position is a dict in the form {'stock_id': quantity}
total_value = sum(position.values()) # Total value (sum of lots)
for stock_id, quantity in position.items():
weight = quantity / total_value if total_value > 0 else 0
print(f" {stock_id}: {quantity:.2f} lots ({weight:.1%})")
if weight > max_weight:
raise ValueError(
f"❌ Single stock weight too high: {stock_id} accounts for {weight:.1%}, exceeds limit {max_weight:.0%}\n"
f" Suggestion: use the position_limit parameter to cap single stock weight"
)
print(f"\n✅ Single stock weight check passed (< {max_weight:.0%})\n")
# Check 3: Available funds
available_cash = account.get_balance()
print(f"Available funds: NT$ {available_cash:,.0f}")
# Estimate required funds (simplified; in reality, consider stock prices)
# Here we assume position.get_required_cash() exists
# required_cash = position.get_required_cash()
# Simplified version: assume NT$ 50,000 per lot on average
estimated_cost = total_value * 50000
print(f"Estimated cost: NT$ {estimated_cost:,.0f}")
cash_buffer = available_cash * min_cash_buffer
if estimated_cost > (available_cash - cash_buffer):
raise ValueError(
f"❌ Insufficient funds!\n"
f" Available funds: NT$ {available_cash:,.0f}\n"
f" Estimated cost: NT$ {estimated_cost:,.0f}\n"
f" Cash buffer: NT$ {cash_buffer:,.0f} (reserve {min_cash_buffer:.0%})\n"
f" Suggestion: lower the total fund ratio or add more funds"
)
print(f"✅ Funds check passed (reserving {min_cash_buffer:.0%} buffer)\n")
# Check 4: Disposition/warning stock check (requires additional data)
# In actual use, call show_alerting_stocks()
print("⚠️ Reminder: run order_executer.show_alerting_stocks() before placing orders to check disposition stocks\n")
print("=" * 50)
print("✅ All safety checks passed, ready to place orders")
print("=" * 50)
# Usage example
try:
acc = SinopacAccount()
position = Position.from_report(report, fund=1000000)
# Run the safety check
safe_order_check(position, acc, max_stocks=30, max_weight=0.10)
# After passing the check, execute orders
order_executor = OrderExecutor(position, account=acc)
# Preview first (no actual orders)
print("\n=== Preview Orders ===")
order_executor.create_orders(view_only=True)
# After confirmation, execute orders
# order_executor.create_orders() # Uncomment to actually place orders
except ValueError as e:
print(f"\n{e}")
print("\nPlease fix the issue before placing orders")
except Exception as e:
print(f"\n❌ Unexpected error: {e}")
print("Please contact technical support or review the detailed error message")
Error 3: Orders Rejected
Symptom: After executing create_orders(), some or all orders are rejected by the broker
Common rejection reasons:
- Disposition stocks not pre-deposited
- Stock suspended from trading
- Locked at price limit (up or down)
- Odd lot trading outside of allowed hours
- Insufficient account permissions
Solution:
from finlab.online.order_executor import OrderExecutor
# Create the OrderExecutor
order_executor = OrderExecutor(position, account=acc)
# Step 1: Check disposition/warning stocks
print("=== Checking Disposition Stocks ===")
alerting_stocks = order_executor.show_alerting_stocks()
if alerting_stocks:
print("⚠️ Disposition/warning stocks found. Please pre-deposit via the broker platform before ordering.")
print("Pre-deposit tutorials:")
print(" - Fugle: https://support.fugle.tw/trading/trading-troubleshoot/5231/")
print(" - Sinotrade: https://www.sinotrade.com.tw/richclub/freshman/...")
# Wait for the user to finish pre-depositing manually before continuing
input("After pre-depositing, press Enter to continue...")
# Step 2: Check trading session
from datetime import datetime
now = datetime.now()
hour = now.hour
minute = now.minute
if 9 <= hour < 13 or (hour == 13 and minute <= 30):
print("✅ Currently in trading session (09:00-13:30)")
else:
print("⚠️ Currently outside trading session")
print(" Round-lot session: 09:00-13:30")
print(" Odd-lot session: 09:00-13:40 (intraday), 14:00-14:30 (after-hours)")
# Step 3: Preview first, then place orders
print("\n=== Preview Orders ===")
order_executor.create_orders(view_only=True)
confirmation = input("\nAfter confirming, type 'YES' to place orders: ")
if confirmation == 'YES':
try:
# Execute orders
orders = order_executor.create_orders()
# Check order status
print("\n=== Order Status ===")
for order in orders:
print(f"{order['stock_id']}: {order['status']}")
# Tally success/failure
success_count = sum(1 for o in orders if o['status'] == 'success')
fail_count = len(orders) - success_count
if fail_count > 0:
print(f"\n⚠️ {fail_count} orders failed. Please check the rejection reasons:")
for order in orders:
if order['status'] != 'success':
print(f" {order['stock_id']}: {order['error_message']}")
print("\nCommon rejection reasons:")
print("1. Disposition stocks not pre-deposited")
print("2. Stock suspended from trading (halt, full-delivery)")
print("3. Locked at price limit preventing execution")
print("4. Odd lot trading outside of allowed hours")
print("5. Insufficient funds")
else:
print(f"\n✅ All orders submitted successfully ({success_count} orders)")
except Exception as e:
print(f"\n❌ Order placement failed: {e}")
print("Please check your network connection or contact broker customer support")
else:
print("Order placement cancelled")
Error 4: Odd Lot Order Failure
Symptom: Using odd_lot=True but orders fail
position = Position.from_report(report, fund, odd_lot=True)
order_executor = OrderExecutor(position, account=acc)
order_executor.create_orders()
# Error: odd lot trading session mismatch
Causes: - Odd lot trading hour restrictions (intraday: 09:00-13:40, after-hours: 14:00-14:30) - Some brokers do not support odd lot API trading - Insufficient odd lot trading volume
Solution:
from datetime import datetime
from finlab.online.order_executor import Position, OrderExecutor
# Check whether we are in an odd lot trading session
now = datetime.now()
hour, minute = now.hour, now.minute
# Intraday odd lot: 09:00-13:40
is_intraday_odd_lot = (9 <= hour < 13) or (hour == 13 and minute <= 40)
# After-hours odd lot: 14:00-14:30
is_afterhours_odd_lot = (hour == 14 and 0 <= minute <= 30)
if not (is_intraday_odd_lot or is_afterhours_odd_lot):
print("⚠️ Currently outside odd lot trading sessions")
print(" Intraday odd lot: 09:00-13:40")
print(" After-hours odd lot: 14:00-14:30")
print("\nSuggestions:")
print("1. Wait for an odd lot trading session before placing orders")
print("2. Switch to round-lot trading (remove odd_lot=True)")
exit(0)
# Build the odd lot position
position = Position.from_report(report, fund=500000, odd_lot=True)
print(f"✅ Currently in odd lot trading session, orders can be placed")
# Execute orders
try:
order_executor = OrderExecutor(position, account=acc)
order_executor.create_orders(view_only=True) # Preview first
# order_executor.create_orders() # Uncomment after confirmation
except Exception as e:
print(f"❌ Odd lot order failed: {e}")
print("\nPossible causes:")
print("1. Broker does not support odd lot API trading (contact broker to confirm)")
print("2. Insufficient odd lot trading volume (consider switching to round lot)")
print("3. Network connection issue")
Live Trading Best Practices
1. Test with a Simulated Account
# Most brokers offer simulated accounts; test for 1-2 weeks
# Confirm all workflows are working before using real funds
2. Set Up Daily Scheduling
import schedule
import time
def daily_rebalance():
"""Auto-rebalance after market close"""
try:
# 1. Run strategy
report = backtest.sim(position, resample='M')
# 2. Calculate positions
position = Position.from_report(report, fund=1000000)
# 3. Execute orders
order_executor = OrderExecutor(position, account=acc)
order_executor.create_orders()
print(f"{datetime.now()} - Orders completed")
except Exception as e:
print(f"{datetime.now()} - Order failed: {e}")
# Send notification (LINE/Email)
# Execute daily at 14:00 (after market close)
schedule.every().day.at("14:00").do(daily_rebalance)
while True:
schedule.run_pending()
time.sleep(60)
3. Log All Trades
import json
from datetime import datetime
def log_trade(position, orders, status):
"""Record trade log"""
log_entry = {
'timestamp': datetime.now().isoformat(),
'position': position,
'orders': orders,
'status': status
}
with open('trade_log.json', 'a') as f:
f.write(json.dumps(log_entry, ensure_ascii=False) + '\n')
Reference Resources
- API Reference - online - Complete OrderExecutor parameter documentation
- Live Trading Tutorial (Multi-Strategy) - Multi-strategy portfolio trading
- Risk Management Guide - Live trading risk controls
- Complete Strategy Development Workflow - From backtesting to live trading
- FAQ - More troubleshooting
Final Reminder
Always test thoroughly on a simulated account before using real funds!
Recommended testing checklist: - Account connection is stable and error-free - Order workflow executes completely - Position calculations are correct (match expectations) - Capital controls work normally (no overspending) - Exception handling mechanisms are effective - Runs continuously for 1-2 weeks without issues
Only consider using real funds after all items above are confirmed!