Docs
Open Studio

Guides

Orders: many strategies

On this page5

This guide is for users with well-defined trading strategies who want to run several at once: combine them into one portfolio, work out the quantities the whole account should hold each day, and sync the account in one go. It covers four steps:

  1. Execute trading strategies
  2. Combine trading positions
  3. Calculate share quantities
  4. Place orders

With a single strategy, Orders: one strategy is simpler.

Get a report for each strategy

Before the next trading day opens, run your strategies and obtain their reports. Use any one of these three approaches:

Write your own strategies following Backtesting and get reports by backtesting:

from finlab import backtest

# Write your strategies

report1 = backtest.sim(...)
report2 = backtest.sim(...)

This approach runs the complete code and may take some time.

Combine them into one portfolio

Give Portfolio each strategy's report and weight to simulate the combined performance:

from finlab.portfolio import Portfolio

port = Portfolio({'strategy1': (report1, 0.5), 'strategy2': (report2, 0.5)})
port.display()

This simulates the combined portfolio and displays the same interactive report as a single backtest: cumulative return against the benchmark, risk and return metrics, and the holdings of every strategy.

Calculate share quantities

PortfolioSyncManager (the PM) turns the portfolio into quantities: shares for U.S. stocks, lots of 1,000 shares for Taiwan stocks. It can:

  • Determine whether positions need updating
  • On rebalancing days, recalculate the quantity of each stock
  • After each trading day, check if stop-loss or take-profit needs to be triggered
  • During rebalancing, ensure trading stays within the specified total_balance

Create or load the PortfolioSyncManager

Create a new PM the first time; after that, load the existing positions from the cloud or from local storage:

from finlab.portfolio import PortfolioSyncManager

# Create a new database and save it, so later runs can load it
pm = PortfolioSyncManager()
pm.to_local()

Update the holdings

Pass the portfolio and your total balance to pm.update(), in the way that fits your account:

# The program will only rebalance on scheduled rebalancing dates.
# On first creation, positions will be empty.
pm.update(port, total_balance=100_000)

Check the result:

print(pm.get_position())

get_position() returns the combined target Position: one row per symbol with its quantity and order condition. For Taiwan portfolios, print(pm) also prints an estimated value and a holdings table priced with Taiwan market data (quantity, price, weight, close price, volume, the strategies holding each stock, and the order type).

Please run PortfolioSyncManager.update daily to ensure positions remain accurate.

Save the positions

pm.to_cloud()

A script to run every day

First-time setup:

# Initialize PortfolioSyncManager
pm = PortfolioSyncManager()
pm.to_local()

Then run daily:

# Initialize PortfolioSyncManager
pm = PortfolioSyncManager.from_local()

# Perform operations
# pm.update(...)

# Save results
pm.to_local()
PortfolioSyncManager.update parameters

PortfolioSyncManager.update has the following parameters. Please read carefully to ensure your position updates are correct:

portfolio (Portfolio or Report)

The portfolio containing investment positions. This is the core data structure for stock synchronization, and all share quantity calculations reference this portfolio.

total_balance (float)

A float value representing total assets, including all cash, assets, and stocks combined.

total_balance is supplied by the caller; the PM never reads it from the broker. On a rebalance, available funds are the smaller of total_balance minus the holdings' market value in sleeves not being updated and the updated sleeves' weighted budget, less the safety reserve.

When this update actually rebuilds a positive-weight sleeve and total_balance minus the holdings' market value in sleeves not being updated is <= 1e-6, update() raises finlab.exceptions.PortfolioError before writing state or rebuilding positions. The absolute currency tolerance 1e-6, far below 0.01, treats equality and small floating-point differences from decimal-price multiplication consistently as no usable capital. It does not scale with portfolio value. Daily calls with no rebuild, removal-only calls, and stop-loss/take-profit-only calls remain allowed when holdings appreciate above a fixed total_balance. An explicit zero budget with zero retained asset value keeps its previous behavior.

The error aborts the entire update(): no config/history writes, sleeve removals or rebuilds, and no stop-loss/take-profit processing in any sleeve, including other sleeves. Immediately raise total_balance according to the lower bound in the exception and retry update() before saving or calling sync(); do not catch the exception and sync the old target. For example, retained holdings worth 600000 require at least 600001 in whole currency units to pass this boundary check. Also budget enough for target securities and the safety reserve; the lower bound does not guarantee a tradable lot. Empty positions caused by a genuinely positive balance too small to buy one share are tracked separately in Issue #296. To let capital grow with the account, pass account.get_total_balance().

rebalance_safety_weight (float)

The cash weight that ensures the total value of the new strategy portfolio does not exceed the original portfolio value when buying/selling at market prices. For example, if set to 20%, then 20% of sold assets will be retained as cash, and the rest will be used to purchase new assets. This helps manage investment risk and ensure liquidity. If using limit orders, this can be set to around 5%.

smooth_transition (bool)

Determines whether to use smooth transition for portfolio updates. When adding/removing strategies or updating weights, if set to True, the portfolio will not change immediately but will update at each strategy's next scheduled date. This avoids frequent trading, reduces transaction costs, and helps portfolio stability. Default is True.

force_override_difference (bool)

Determines whether to force override existing different positions. If it is not a rebalancing day and there is no stop-loss/take-profit trigger, the program will still verify whether current positions match simulated positions (they should theoretically match, but may differ due to delayed data registration, user forgetting to run the strategy, or running the strategy before data registration). If set to True, any detected differences will be forcibly adjusted according to the new strategy. Default is False to avoid unnecessary trading and potential risks.

These parameters are crucial for portfolio synchronization and rebalancing. Properly setting and understanding them helps investors achieve more effective asset management and risk control.

How update reallocates capital: steps and an example

The update function keeps the portfolio balanced and rebalances investments when necessary to follow strategy allocations.

Steps

  1. Identify strategies that need updating
    • The function first checks which strategies need updating based on their next scheduled trading dates. If the next trading date has arrived or passed, or strategy weights have changed, an update is needed.
    • It also considers whether smooth transition is enabled, meaning updates will only occur on the next scheduled trading date to avoid immediate changes.
  2. Check current holdings
    • The function checks the current stock holdings and confirms whether they match the strategy report. If there are discrepancies, warnings are logged and forced updates may occur.
  3. Calculate asset value and liquid funds
    • Total asset value: Sum of all current holding values.
    • Liquid funds: Funds available from selling current positions of strategies due for rebalancing, plus remaining balance.
    • Limited liquid funds: Capped to not exceed total_balance * sum of strategy weights.
    • Safety funds: A percentage of liquid funds retained for stability (e.g., 20%).
    • Investable funds: Liquid funds minus safety funds.
  4. Update each strategy's holdings
    • For each strategy needing update, funds are allocated based on strategy weight and investable funds.
    • Old holdings are liquidated and new holdings created. Stocks appearing in both old and new are retained.
    • Stocks triggering stop-loss/take-profit have positions set to zero without rebalancing the entire strategy.
  5. Save updated portfolio
    • Updated portfolio information is saved locally or to the cloud.

Example

  • Initial setup: Portfolio with strategies A and B, each weighted 50%.
  • Current value: Total NAV is $1,000,000. Strategy A = $300,000, Strategy B = $600,000. Strategy A is lower due to stop-loss liquidations; Strategy B is higher due to gains.
  • Rebalancing condition: Strategy B has a scheduled rebalancing tomorrow; A does not.

Update process:

  1. Strategy B is identified for update.
  2. Liquid funds from Strategy B: $600,000 + $100,000 remaining = $700,000, capped at $500,000 (50% weight).
  3. Safety funds: $500,000 * 20% = $100,000.
  4. Investable funds: $500,000 - $100,000 = $400,000.
  5. Since idle funds ($200,000) exceed safety funds ($100,000), no safety reserve needed. Investable = $500,000.
  6. Strategy B's $600,000 holdings are liquidated and $500,000 is reinvested per Strategy B.
  7. Stop-loss/take-profit triggered stocks in Strategy A are set to zero without rebalancing B.

Place orders and sync the account

Connect your broker account

Connect the account of the broker you use: Charles Schwab for U.S. stocks, or E.SUN, SinoPac, Taishin (formerly Masterlink) or Fubon Securities for Taiwan stocks. Set the credentials as environment variables; you only need the broker you use.

For U.S. stocks.

  1. Register an app on the Schwab Developer Portal to get an app key and secret.
  2. Create a token file with the login flow of schwab-py (pip install schwab-py); SchwabAccount reads that file.
  3. Set the variables and connect:
import os
from finlab.online.schwab_account import SchwabAccount

os.environ['SCHWAB_API_KEY'] = 'Schwab app key'
os.environ['SCHWAB_SECRET'] = 'Schwab app secret'
os.environ['SCHWAB_TOKEN_PATH'] = '/path/to/token.json'

account = SchwabAccount()

Sync the account

Preview with simulated orders first, then pick how orders are priced:

# Simulation only, no actual orders placed
pm.sync(account, view_only=True)

Update the limit price or cancel orders

For orders that have not filled yet:

# Update limit price
order_executor = pm.create_order_executor(account)
order_executor.update_order_price()

# Cancel all pending orders
order_executor.cancel_orders()

Deposit in advance for restricted Taiwan stocks

For Taiwan stocks, positions in full-delivery, disposition or alert stocks require cash or shares deposited with the broker in advance; order_executor.show_alerting_stocks() (Taiwan market only) lists the pending orders affected.

Next steps

↑↓ Move Enter Open Esc Close