Skip to content

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:

from finlab import backtest

# write your own strategy

report = backtest.sim(...)

Calculate Share Quantities

Next, display the current positions:

print(report.current_trades)
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)
This produces the following result:
[{'stock_id': '2330', 'quantity': 1, 'order_condition': <OrderCondition.CASH: 1>}]

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:

position = Position({'2330': 1, '1101': 1.001})
This produces the following result:
[{'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:

# E.SUN Securities
pip install esun-trade

# Sinopac
pip install shioaji

2. Connect to Brokerage Account

Set account credentials as environment variables; configure only the broker you use.

Choose your broker

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()
If unfamiliar with the broker API, practice with the broker's tutorial first.

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.

# Show if any alerting stocks exist in the position
order_executer.show_alerting_stocks()

Buy 8101 0.429 lots - estimated total: 2672.67
After completing the deposit based on show_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())
This prints the following result:
[{'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

order_executor.create_orders()
# Order status: partially successful, partially failed

Common rejection reasons:

  1. Disposition stocks not pre-deposited
  2. Stock suspended from trading
  3. Locked at price limit (up or down)
  4. Odd lot trading outside of allowed hours
  5. 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


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!