Order (Single Strategy)
Supported Brokers
Live order execution connects to Charles Schwab for U.S. stocks and to E.SUN, SinoPac, Masterlink and Fubon Securities for Taiwan stocks. The position calculation in this guide works for any market; only the last step needs a supported broker.
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:
from finlab import backtest, data
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
# Your own strategy goes here; this one holds the 20 strongest 6-month performers
position = (close / close.shift(126)).is_largest(20)
report = backtest.sim(position, resample='M')
Calculate Share Quantities
Next, display the current positions:
current_trades lists the trades that are open or change at the latest signal date, with their entry and exit dates, return, MAE/MFE, the current target weight and the next_weights for the next rebalance.
Once confirmed, calculate the quantity of each stock:
from finlab.online.order_executor import Position
# total fund, in the market's currency (USD for U.S. stocks)
fund = 100_000
position = Position.from_report(report, fund)
print(position)
Each row lists a symbol, the quantity to hold, and the order condition (CASH for a regular cash position). For U.S. stocks the quantity is in shares; for Taiwan stocks it is in lots of 1,000 shares.
Advanced Position Adjustments
Odd Lot Trading (Taiwan)
U.S. accounts already trade single shares. Taiwan accounts trade whole lots of 1,000 shares by default; to allow odd lots, 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:
In a U.S. account this means 10 shares of AAPL and 5 shares of MSFT, both bought with cash.
Position Addition/Subtraction
Position objects support addition and subtraction:
# Remove 10 shares of AAPL
new_position = position - Position({'AAPL': 10})
# Add 5 shares of MSFT
new_position = position + Position({'MSFT': 5})
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(
(close / close.shift(126)).is_largest(10), resample='M'
) # 6-month momentum
report2 = backtest.sim(
(close / close.shift(20)).is_smallest(10), resample='M'
) # 1-month reversal
position1 = Position.from_report(report1, 50_000) # Strategy fund: $50,000
position2 = Position.from_report(report2, 50_000) # Strategy fund: $50,000
total_position = position1 + position2
Placing Orders
1. Install Broker API
Install the package of the broker you use:
# Charles Schwab (U.S.)
pip install schwab-py
# E.SUN Securities (Taiwan)
pip install esun-trade
# SinoPac (Taiwan)
pip install shioaji
2. Connect to Brokerage Account
Set account credentials as environment variables; configure only the broker you use.
Choose your broker
- Register an app on the Schwab Developer Portal to get an app key and secret.
- Create a token file with the login flow of schwab-py;
SchwabAccountreads that file.
- 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: SinoPac supports trading on macOS 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 finlab.online.fubon_account import FubonAccount
os.environ['FUBON_NATIONAL_ID'] = "A123456789"
os.environ['FUBON_ACCOUNT_PASS'] = "your_password"
os.environ['FUBON_CERT_PATH'] = "/path/to/cert.pfx"
acc = 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)
For Taiwan stocks, positions in full-delivery, disposition or alert stocks require cash or shares deposited with the broker in advance. order_executer.show_alerting_stocks() (Taiwan market only) lists the pending orders affected.
Then execute the orders:
# Preview orders (view-only mode, no actual orders placed)
order_executer.create_orders(view_only=True)
# Execute orders (will actually place orders)
# 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:
from finlab.online.schwab_account import SchwabAccount
acc = SchwabAccount()
print(acc.get_position())
The result is a Position with one row per holding: symbol, quantity and order condition. Taiwan accounts work the same way, for example with SinopacAccount() or EsunAccount().
Common Errors and Solutions
Important Risk Warning
Live trading involves real money. Errors can lead to financial losses!
Check before placing orders:
1. Run the full workflow with view_only=True 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 = SchwabAccount() (or a Taiwan broker account) throws an error
Causes:
- Environment variables not set (SchwabAccount raises KeyError for a missing variable)
- Token or certificate file does not exist, has expired, or the path is wrong
- Network connection issue or broker API service is down
Solution:
import os
from finlab.online.schwab_account import SchwabAccount
# Step 1: Check if environment variables are set
# (Taiwan brokers use their own variables, e.g. SinoPac: SHIOAJI_API_KEY,
# SHIOAJI_SECRET_KEY, SHIOAJI_CERT_PERSON_ID, SHIOAJI_CERT_PATH, SHIOAJI_CERT_PASSWORD)
required_env_vars = ['SCHWAB_API_KEY', 'SCHWAB_SECRET', 'SCHWAB_TOKEN_PATH']
print("=== Environment Variable Check ===")
missing = [var for var in required_env_vars if not os.environ.get(var)]
for var in required_env_vars:
print(
f"{'❌' if var in missing else '✅'} {var}: {'NOT SET' if var in missing else 'set'}"
)
if missing:
raise SystemExit("Set the missing variables first")
# Step 2: Check if the token file exists
token_path = os.environ['SCHWAB_TOKEN_PATH']
if not os.path.exists(token_path):
raise SystemExit(
f"Token file not found: {token_path}. Create it with schwab-py's login flow."
)
# Step 3: Attempt connection and catch errors
try:
acc = SchwabAccount()
print("✅ Account connected successfully")
print(f"✅ Cash balance: ${acc.get_cash():,.2f}")
except Exception as e:
print(f"❌ Connection failed: {e}")
print("Check the app key, secret and token file, then try again later")
E.SUN Securities (Taiwan) 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.schwab_account import SchwabAccount
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")
# Market value of each target holding at the latest price
# (for Taiwan accounts, multiply by 1,000 shares per lot)
price = account.get_price([p['symbol'] for p in position])
values = {p['symbol']: float(p['quantity']) * price[p['symbol']] for p in position}
total_value = sum(values.values())
# Check 1: Number of holdings
print(f"Number of holdings: {len(values)}")
if len(values) > max_stocks:
raise ValueError(
f"❌ Too many holdings ({len(values)}), 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
for symbol, value in values.items():
weight = value / total_value if total_value > 0 else 0
print(f" {symbol}: ${value:,.0f} ({weight:.1%})")
if weight > max_weight:
raise ValueError(
f"❌ Single stock weight too high: {symbol} 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 = account.get_total_balance()
cash_buffer = available * min_cash_buffer
print(f"Account value: ${available:,.0f}, target value: ${total_value:,.0f}")
if total_value > (available - cash_buffer):
raise ValueError(
f"❌ Insufficient funds!\n"
f" Account value: ${available:,.0f}\n"
f" Target value: ${total_value:,.0f}\n"
f" Cash buffer: ${cash_buffer:,.0f} (reserve {min_cash_buffer:.0%})\n"
f" Suggestion: lower the fund passed to Position.from_report() or add more funds"
)
print(f"✅ Funds check passed (reserving {min_cash_buffer:.0%} buffer)\n")
print("=" * 50)
print("✅ All safety checks passed, ready to place orders")
print("=" * 50)
# Usage example
try:
acc = SchwabAccount()
position = Position.from_report(report, fund=100_000)
# 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")
Error 3: Orders Rejected
Symptom: After executing create_orders(), some or all orders are rejected by the broker
Common rejection reasons:
- Market closed: regular U.S. trading hours are 09:30-16:00 New York time
- Insufficient buying power
- Symbol halted or not tradable through the API
- Insufficient account permissions (for example margin or short selling)
- Taiwan market only: disposition stocks without advance deposits, prices locked at the daily limit, or odd-lot orders outside the odd-lot session
Solution:
from datetime import datetime
from zoneinfo import ZoneInfo
from finlab.online.order_executor import OrderExecutor
order_executor = OrderExecutor(position, account=acc)
# Step 1: Check the trading session (U.S. regular hours, New York time)
now = datetime.now(ZoneInfo('America/New_York'))
if now.weekday() < 5 and (9, 30) <= (now.hour, now.minute) < (16, 0):
print("✅ Currently in the regular trading session (09:30-16:00 ET)")
else:
print("⚠️ Currently outside the regular trading session (09:30-16:00 ET)")
# Step 2: 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:
order_executor.create_orders()
# Step 3: Check the orders that are still open
print("\n=== Open Orders ===")
for order_id, order in acc.get_orders().items():
print(
f"{order_id} {order.stock_id} {order.action.name} "
f"{order.filled_quantity}/{order.quantity} {order.status.name}"
)
except Exception as e:
print(f"\n❌ Order placement failed: {e}")
print(
"Check the broker's error message, your buying power and the trading session"
)
else:
print("Order placement cancelled")
Error 4: Odd Lot Order Failure (Taiwan)
Symptom: Using odd_lot=True with a Taiwan account 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 hours in Taiwan (intraday: 09:00-13:30, after-hours: 13:40-14:30, Taiwan time) - Some brokers do not support odd lot API trading - Insufficient odd lot trading volume
Solution:
from datetime import datetime
from zoneinfo import ZoneInfo
from finlab.online.order_executor import OrderExecutor, Position
now = datetime.now(ZoneInfo('Asia/Taipei'))
minutes = now.hour * 60 + now.minute
is_intraday_odd_lot = 9 * 60 <= minutes <= 13 * 60 + 30
is_afterhours_odd_lot = 13 * 60 + 40 <= minutes <= 14 * 60 + 30
if not (is_intraday_odd_lot or is_afterhours_odd_lot):
print("⚠️ Currently outside odd lot trading sessions")
print(
"Wait for an odd lot session, or switch to round-lot trading (remove odd_lot=True)"
)
else:
position = Position.from_report(report, fund=500_000, odd_lot=True)
order_executor = OrderExecutor(position, account=acc)
order_executor.create_orders(view_only=True) # Preview first
# order_executor.create_orders() # Uncomment after confirmation
Error 5: Signal but No Orders
Symptom: The strategy selects stocks, but the position from Position.from_report() / Position.from_weight() is empty (or missing some stocks), create_orders() places nothing, and a ZeroShareAllocationWarning is shown:
ZeroShareAllocationWarning: 2 of 2 stocks with a non-zero target weight get 0 shares because their allocated fund cannot buy one board lot (1 shares): <symbol> (one lot <price> vs allocated <amount>), ... The position is empty, so no orders will be placed. Use odd_lot=True to trade odd lots, or increase fund.
Cause: Each stock's allocated fund (fund × weight) is floored to whole board lots. A U.S. board lot is one share, so a stock whose allocation is below its share price gets 0 shares. A Taiwan board lot is 1,000 shares (price × 1000) unless odd_lot=True. If every stock falls short, the position is empty.
Solution: Increase fund or hold fewer stocks. For Taiwan accounts, you can also trade odd lots:
Live Trading Best Practices
1. Preview Before Trading
# Run the whole workflow daily with view_only=True for 1-2 weeks
# Confirm the printed orders match your expectations before using real funds
order_executor.create_orders(view_only=True)
2. Set Up Daily Scheduling
import time
from datetime import datetime
import schedule
def daily_rebalance():
"""Rebalance once a day"""
try:
# 1. Run strategy (your own function returning backtest.sim(...))
report = run_strategy()
# 2. Calculate positions
position = Position.from_report(report, fund=100_000)
# 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 (Email, chat webhook)
# Run daily at 15:30, 30 minutes before the 16:00 U.S. close (machine clock on New York time)
schedule.every().day.at("15:30").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, default=str) + '\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 preview orders and start small 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!