Live Trading Guide
[!WARNING] DISCLAIMER: This software is for educational and research purposes only. Trading involves significant risk of loss and is not suitable for all investors. Use of "Live Trading" features is strictly at your own risk. The authors and contributors are not liable for any financial losses, damages, or unintended trades incurred. Always test strategies thoroughly in a paper-trading environment before deploying real capital.
The Trading Bot Framework can mirror your paper-trade portfolios (stored in PostgreSQL) to a live brokerage account. This is handled by a separate Live Trade Copier layer that runs independently of your bots.
[!CAUTION] ALPHA STATUS: The live-trade copier is currently in Alpha. While endpoints have been validated against broker APIs (C2 v4, IB, eToro, Darwinex), behavior has not yet been confirmed against a live capital account. Use extreme caution and start with very low weights.
🚀 Quick Start (5-Minute Dry Run)
You can verify the copier logic immediately without any complex setup.
- Set Environment Variables:
- Run the Copier:
- Review the Log: Look for
[DRY RUN] Would BUY/SELL ...lines to see what the copier would have done.
🔎 Inspect Account & Positions
Each broker module is runnable as a script and prints a summary of the configured account — equity, cash, and current open positions. Use it to sanity-check credentials and account IDs before running the copier:
# Collective2 — reads COLLECTIVE2_API_KEY + COLLECTIVE2_SYSTEM_ID
uv run python -m tradingbot.livetrade.collective2
# Interactive Brokers — IBKR Web API via headless OAuth 1.0a.
# Reads IB_ACCOUNT_ID + IBIND_OAUTH1A_* credentials; prints an account summary.
uv run python -m tradingbot.livetrade.interactive_brokers
# eToro — reads ETORO_API_KEY + ETORO_USER_KEY + ETORO_DEMO
uv run python -m tradingbot.livetrade.etoro
# Darwinex — reads DARWINEX_USERNAME + DARWINEX_PASSWORD + DARWINEX_DEMO
uv run python -m tradingbot.livetrade.darwinex
# Hyperliquid — reads HYPERLIQUID_PRIVATE_KEY + _ACCOUNT_ADDRESS + _VAULT_ADDRESS
# + _TESTNET. Also prints effective leverage, which must stay <= 1.0x.
uv run python -m tradingbot.livetrade.hyperliquid
All brokers expose print_account_summary() on the broker class, so you can
call it from any script after constructing the broker.
📊 How Orders are Calculated
The copier does not just "blindly" copy signals; it synchronizes target state:
- Per-Bot Normalization: For each bot in
LIVETRADE_BOT_WEIGHTS, the current paper portfolio is converted to percentage weights (e.g., AAPL is 10% of Bot A). - Weighted Aggregation: Individual bot weights are aggregated based on your allocation (e.g., if Bot A is 60% of your capital, AAPL becomes 6% of your live total).
- Broker Equity Sync: The copier fetches your Live Total Equity (Cash + Open Positions) from the broker.
- Target Value: (Total Equity) × (Aggregate Target Weight) = Target USD Value for each symbol.
- The Diff: The copier compares the Target Value vs. your Current Live Position value at the broker.
- Order Generation: It generates the BUY or SELL orders needed to close the gap.
- Safety Filters: Orders smaller than
LIVETRADE_MIN_ORDER_USDare skipped to avoid excessive fees.
Price Fallback: If the broker cannot provide a real-time quote for a symbol, the framework falls back to yfinance to ensure calculations remain accurate.
Multi-Bot Weighting Example
If you have $100,000 in your live account and configure:
LIVETRADE_BOT_WEIGHTS='{"adaptivemeanreversionbot": 0.6, "feargreedbot": 0.4}'
- Bot A (Adaptive): Paper portfolio is 100% QQQ.
- Bot B (FearGreed): Paper portfolio is 50% QQQ, 50% CASH.
- Aggregation:
- QQQ Target = (1.0 × 0.6) + (0.5 × 0.4) = 0.8 (80%)
- CASH Target = (0.0 × 0.6) + (0.5 × 0.4) = 0.2 (20%)
- Final Target: The copier will try to make your live account hold $80,000 of QQQ.
🛠 Configuration Reference
| Variable | Description |
|---|---|
COLLECTIVE2_API_KEY |
Your C2 API v4 key. Get it from the C2 API Dashboard. |
COLLECTIVE2_SYSTEM_ID |
The ID of your C2 strategy. |
IB_ACCOUNT_ID |
Your Interactive Brokers account ID (e.g., DU1234567 for paper, U1234567 for live). |
IBIND_USE_OAUTH |
Enable headless OAuth 1.0a for the IBKR Web API. Set to True. |
IBIND_OAUTH1A_CONSUMER_KEY |
9-char consumer key registered in the IBKR self-service OAuth portal. |
IBIND_OAUTH1A_ACCESS_TOKEN |
OAuth access token generated in the IBKR portal. |
IBIND_OAUTH1A_ACCESS_TOKEN_SECRET |
OAuth access token secret generated in the IBKR portal. |
IBIND_OAUTH1A_DH_PRIME |
Diffie-Hellman prime (hex string from dhparam.pem). |
IBIND_OAUTH1A_ENCRYPTION_KEY_FP |
Path to the private encryption key file (private_encryption.pem). |
IBIND_OAUTH1A_SIGNATURE_KEY_FP |
Path to the private signature key file (private_signature.pem). |
ETORO_API_KEY |
Your eToro Public API Key from the eToro API Portal. |
ETORO_USER_KEY |
Your eToro User Key (generated in API Portal settings). |
ETORO_DEMO |
true for demo/paper account, false for live account (default: true). |
DARWINEX_USERNAME |
Your Darwinex DXtrade username. |
DARWINEX_PASSWORD |
Your Darwinex DXtrade master password. |
DARWINEX_ACCOUNT_ID |
Optional: Specific Darwinex account ID to use. |
DARWINEX_DEMO |
true for demo/paper account, false for live account (default: true). |
LIVETRADE_BOT_WEIGHTS |
JSON: {"botname": 0.6, "otherbot": 0.4}. Weights are normalized to 1.0. |
LIVETRADE_MIN_ORDER_USD |
Skip trades smaller than this amount (default: $50). |
LIVETRADE_DRY_RUN |
true: Logs orders without sending them. Always start here. |
LIVETRADE_STRICT_MAPPING |
true: Aborts the sync if any target ticker is unmapped, instead of silently skipping it. |
LIVETRADE_PORTFOLIO_FRACTION |
Fraction of broker equity to allocate to copy-trading. Default 1.0 (use the full account); 0.5 would mirror the bot portfolios into half the account and leave the rest as cash. Range: (0, 1]. |
LIVETRADE_CASH_PROXY |
T-bill ETF that idle target weight (bot cash plus legs the broker cannot trade) is parked in, so it earns ~4–5%/yr at zero beta instead of 0%. It is netted across bots and only trades when the cash share moves by more than LIVETRADE_MIN_ORDER_USD. Unset means the broker's default: SHV for Collective2, IBKR and eToro, plain cash for Darwinex and Hyperliquid (no US ETFs there). none means always plain cash. |
LIVETRADE_CASH_BUFFER |
Share of equity kept as real cash for fees and fills when parking in the cash proxy (default 0.02). |
Enabling the Copiers in Helm
Each broker is its own CronJob, gated by a per-broker enabled flag nested under
liveTrade in helm/tradingbots/values.yaml.
Settings directly under liveTrade are shared by every broker; each broker block
carries only its own connection details and schedule:
liveTrade:
# shared across all brokers
botWeights: '{"SomeBot": 0.5, "OtherBot": 0.5}'
dryRun: "false"
strictMapping: "true"
portfolioFraction: "1.0"
collective2:
enabled: true
schedule: "10 21 * * 1-5"
systemId: "155809898"
interactiveBrokers:
enabled: true
# ...
etoro:
enabled: false
darwinex:
enabled: false
You can run any combination of brokers (Collective2, IBKR, eToro, Darwinex), or none.
Running Multiple Collective2 Strategies
A C2 API key is account-scoped and every request addresses a strategy by its
numeric StrategyId, so one COLLECTIVE2_API_KEY can drive several strategies.
To publish a different set of bots as a separate, independently-tracked C2
strategy, add another CronJob rather than another credential.
The XAUZerifine strategy is the worked example — see
cronjob-livetrade-collective2-xauzerifine.yaml.
It is a copy of the base Collective2 template that overrides only the strategy id
and the per-instance settings:
liveTrade:
collective2XauZerifine:
enabled: false # flip on once systemId is filled in
schedule: "50 21 * * 1-5"
systemId: "" # the XAUZerifine StrategyId
botWeights: '{"XAUZenbotTreeBot": 1.0}' # overrides the shared botWeights
dryRun: "true" # overrides the shared dryRun
# apiKeySecretKey: "COLLECTIVE2_API_KEY_XAUZERIFINE" # only for a different C2 account
botWeights, dryRun and portfolioFraction fall back to the shared
liveTrade.* values when omitted. The template refuses to render if systemId
is empty, or if it duplicates liveTrade.collective2.systemId while both
instances are enabled — two copiers pointed at one strategy would each
liquidate the other's positions, since sync() is a full target-state
reconciliation.
Sharing a systemId is legitimate when you are handing a strategy over:
disable the old instance in the same commit that enables the new one, so only one
copier ever runs against it. Be aware that the C2 strategy keeps its existing
track record and equity curve — the first run of the new instance liquidates
whatever the old bots held and rebuilds the portfolio from the new ones. If you
want a clean track record, create a fresh C2 strategy instead.
Each strategy needs its bots' tickers to be tradeable on C2. XAUZenbotTreeBot
trades ^XAU (the PHLX Gold/Silver index), which is mapped to the GDX
miners ETF in symbol_map.json —
without that override it could not trade at all. See the warning about index
tickers under Ticker Mapping (Discovery).
🏦 Interactive Brokers (IBKR)
The framework connects to Interactive Brokers through the IBKR Web API (Client Portal REST) using the ibind library with fully headless OAuth 1.0a authentication. There is no IB Gateway / TWS container and no daily browser login — the live-session token is obtained programmatically and self-renews on a 24h cycle.
1. One-time OAuth setup (in the IBKR self-service portal)
This is first-party self-service OAuth: you register against your own account and issue your own tokens. It replaces the Client Portal Gateway — the small Java program IBKR's "Getting Started → Retail" section points at. With OAuth 1.0a there is no gateway process to run, which is the whole reason for this integration: a CronJob cannot babysit a long-lived local Java daemon.
Eligibility. The live account must be fully open, funded, and of the IBKR Pro type (not Lite). Note that IBKR's own docs file OAuth 1.0a under "Web API Access for Organizations" and steer individuals toward the CP Gateway; in practice individual Pro accountholders do use the self-service portal, and IBKR API support has stated there is no technical limitation preventing it. If the portal refuses your login, that assumption is what broke — fall back to asking API support to enable OAuth on the username.
Activation lag. There is no formal approval process here (unlike third-party OAuth, which carries an 8–14 week compliance review). But consumer keys are not always live immediately: reported activation delays run from 24 hours to about two weeks, possibly tied to IBKR's weekend server restarts. Register the key before you need it.
Paper accounts are supported. Log into the portal with your paper credentials and follow the identical flow. Generate a separate keypair for paper — do not reuse the live account's keys.
- Generate the keys and DH params:
mkdir -p ~/ibkr-oauth-paper && cd ~/ibkr-oauth-paper
openssl genrsa -out private_signature.pem 2048
openssl rsa -in private_signature.pem -outform PEM -pubout -out public_signature.pem
openssl genrsa -out private_encryption.pem 2048
openssl rsa -in private_encryption.pem -outform PEM -pubout -out public_encryption.pem
openssl dhparam -out dhparam.pem 2048 # slow, a minute or two
-
Open the OAuth self-service portal and log in with the username you want the API to trade as (your paper username). Register a 9-character consumer key (you choose the string), then upload
public_signature.pem,public_encryption.pem, anddhparam.pem. Finally generate the access token and access token secret. -
Extract the DH prime as a hex string:
python3 -c "
import subprocess, re
out = subprocess.run(['openssl','dhparam','-in','dhparam.pem','-text'],
capture_output=True, text=True).stdout
m = re.search(r'(?:prime|P):\s*((?:\s*[0-9a-fA-F:]+\s*)+)', out)
print(re.sub(r'[\s:]', '', m.group(1)) if m else 'No prime found')
"
- Load them into the cluster — the two PEMs as a file-mounted secret, the rest as env keys:
kubectl -n tradingbots-2025 create secret generic ib-oauth-keys \
--from-file=private_encryption.pem --from-file=private_signature.pem
kubectl -n tradingbots-2025 patch secret tradingbot-secrets --type=merge -p "$(cat <<JSON
{"stringData": {
"IBIND_OAUTH1A_CONSUMER_KEY": "<9-char key>",
"IBIND_OAUTH1A_ACCESS_TOKEN": "<token>",
"IBIND_OAUTH1A_ACCESS_TOKEN_SECRET": "<token secret>",
"IBIND_OAUTH1A_DH_PRIME": "<hex from step 3>",
"IB_ACCOUNT_ID": "<DU… paper account id>"
}}
JSON
)"
- Smoke-test locally before enabling the CronJob (see the account-summary command above),
then set
liveTrade.interactiveBrokers.enabled: trueand do one run withliveTrade.dryRun: "true".
2. Configuration
Set these environment variables (see the reference table above for full descriptions):
| Variable | Description | Default |
|---|---|---|
IB_ACCOUNT_ID |
Your IB account ID (e.g., DU1234567). |
Required |
IBIND_USE_OAUTH |
Enable headless OAuth 1.0a. | True |
IBIND_OAUTH1A_CONSUMER_KEY |
Consumer key from the portal. | Required |
IBIND_OAUTH1A_ACCESS_TOKEN |
Access token from the portal. | Required |
IBIND_OAUTH1A_ACCESS_TOKEN_SECRET |
Access token secret from the portal. | Required |
IBIND_OAUTH1A_DH_PRIME |
DH prime (hex). | Required |
IBIND_OAUTH1A_ENCRYPTION_KEY_FP |
Path to private_encryption.pem. |
Required |
IBIND_OAUTH1A_SIGNATURE_KEY_FP |
Path to private_signature.pem. |
Required |
LIVETRADE_DRY_RUN |
Paper-safety: defaults to true. |
true |
[!IMPORTANT] Order Isolation: The copier is idempotent. Before each sync it calls
cancel_open_orders()to clear stale working orders from previous runs. Over the Web API this cancels the account's live orders — run the copier against a dedicated account (or accept that it manages that account's open orders) so it does not clear unrelated manual orders.[!NOTE] Scope: order routing currently supports US equities/ETFs (conid resolved via
stock_conid_by_symbol, which the live bots use). Forex/crypto/futures over the Web API is not yet implemented and raisesNotImplementedError.[!WARNING] One brokerage session per username, across all IBKR platforms. If you are logged into TWS, IBKR Mobile, or Client Portal with the same username when the CronJob fires, the two sessions compete and one gets displaced. Give the copier a dedicated username rather than the one you log in with manually, or stay logged out around 21:20 UTC.
[!NOTE] No market data subscription is needed.
_get_native_price()returns0.0on purpose, so the base class falls back to yfinance for pricing. The copier therefore never touches/iserver/marketdata/*and consumes none of the account's 100 market data lines. Rate limits worth knowing if this grows: 10 req/s globally, but/iserver/ordersand/portfolio/accountsare 1 req/5s each, and/tickleis 1 req/s. Exceeding them returns 429 and can put the source IP in a 10-minute penalty box.
3. Usage
4. Ticker Discovery for IB
🐂 eToro
The framework supports eToro via their Public REST API.
1. Requirements
- eToro Public API Key & User Key. Obtain them from the eToro API Portal.
- Paper Account (Virtual Portfolio) is strongly recommended. Set
ETORO_DEMO=true.
2. Configuration
Set these environment variables:
| Variable | Description | Default |
|---|---|---|
ETORO_API_KEY |
Your eToro API Key. | Required |
ETORO_USER_KEY |
Your eToro User Key. | Required |
ETORO_DEMO |
true for paper trading, false for live. |
true |
LIVETRADE_DRY_RUN |
Safety: defaults to true. |
true |
3. Order Model (USD Amount)
eToro orders are placed using USD Amount (notional) for BUY orders, which allows for fractional shares automatically. For SELL orders, the framework closes existing positions by their positionId.
4. Ticker Mapping
eToro uses numeric Instrument IDs. The framework automatically resolves these via the eToro search API during the mapping phase.
5. Usage
📈 Darwinex (DXtrade)
The framework supports Darwinex via the DXtrade REST API (/dxsca-web).
1. Requirements
- Darwinex DXtrade Account (Username + Master Password).
- Demo Account strongly recommended for initial testing.
2. Configuration
Set these environment variables:
| Variable | Description | Default |
|---|---|---|
DARWINEX_USERNAME |
Your DXtrade username. | Required |
DARWINEX_PASSWORD |
Your DXtrade master password. | Required |
DARWINEX_DEMO |
true for demo/paper trading, false for live. |
true |
DARWINEX_ACCOUNT_ID |
Optional: defaults to the first account found. | None |
3. Symbol Mapping (CFDs)
Darwinex is a CFD broker. Common equity tickers like QQQ are often mapped to QQQ.US. The framework uses a two-step resolution:
1. Check symbol_mappings.json for manual overrides.
2. Search the Darwinex catalog for the ticker (and ticker + .US).
4. Native Quotes
DXtrade REST API does not provide a simple "last price" endpoint (it is WebSocket only). For v1, the framework falls back to yfinance for real-time price lookups to calculate equity and order sizing.
5. Usage
⚡ Hyperliquid (Perpetuals / On-Chain Vault)
Runs one strategy on Hyperliquid perpetuals, optionally inside a user vault so outside depositors can follow it. This is the only perps broker here; every other one is spot. Three things behave differently and none of them are cosmetic.
1. Requirements
- An API (agent) wallet — generate at https://app.hyperliquid.xyz/API. Never use your master wallet key. Use separate agent wallets for testnet and mainnet: nonces are tracked per signer.
- For a vault: 100 USDC minimum seed, a one-time 10,000 USDC creation fee, and the leader must hold ≥5% of vault equity at all times. Depositors are subject to a lock-up. Read Hyperliquid's docs — these are protocol rules.
2. Configuration
| Variable | Description | Default |
|---|---|---|
HYPERLIQUID_PRIVATE_KEY |
API/agent wallet private key (the signer). | Required |
HYPERLIQUID_ACCOUNT_ADDRESS |
Master account the agent signs for. | wallet address |
HYPERLIQUID_VAULT_ADDRESS |
Routes all trades into the vault. Empty = your own account. | "" |
HYPERLIQUID_TESTNET |
true for testnet, false for mainnet. |
true |
Helm: liveTrade.hyperliquid in helm/tradingbots/values.yaml. The template
hard-fails at render time if testnet: "false" and vaultAddress is empty,
unless you also set allowMainnetWithoutVault: "true" — that combination trades
real personal funds, so it has to be deliberate.
3. Why the settings are what they are
portfolioFraction: "0.95", not1.0. The copier clamps buys toget_cash() * 0.98. At 1.0 the target notional exceeds that budget on every run, so it scales every order down and sits on the margin boundary. At 0.95 the scaling branch never fires.minOrderUsd: "25". Hyperliquid rejects orders below $10 notional (exact reduce-only closes are exempt); 25 leaves headroom for that 0.98 scale-down. Raise it as the vault grows — target roughly 1–2% of equity. Past ~$50k TVL a fixed $25 floor churns taker fees on noise every single run.- 1x cross leverage, set by the broker before the first opening order on each coin. This is what makes "notional ≤ equity" true even if the copier's weight maths is wrong: at 1x the exchange itself rejects an oversized order instead of silently levering up. It also makes liquidation of a single cross position effectively impossible.
get_cash()returnswithdrawable, notaccountValue. Perps have no settled cash; free collateral is the real constraint on new notional.accountValuewould let the copier submit orders the exchange rejects, andplace_ordercan only log that — a silent no-op is harder to debug than a clamp.get_positions()returns SIGNED sizes (like Darwinex, unlike C2/IB). Withabs(), a stray short would take the copier's full-liquidation branch and emit a SELL, doubling the short — every run. Signed falls through to the general diff and buys it back.test_copier_flattens_a_stray_shortlocks this in.
4. Staged rollout — do not skip stages
| Stage | Where | Config | Advance when |
|---|---|---|---|
| 0 | laptop | TESTNET=true, no vault, DRY_RUN=true |
Summary prints; sync logs [DRY RUN] Would BUY; nothing unmapped; no websocket hang |
| 1 | laptop | DRY_RUN=false, ~$1000 faucet USDC |
Order fills; effective leverage ≈0.95x; immediate re-run emits no orders; flipping the paper bot to cash closes to exactly 0 |
| 2 | laptop | testnet vault created, VAULT_ADDRESS set |
Vault page shows the position and your personal account shows nothing. Confirms an agent wallet can sign for a vault |
| 3 | cluster | enabled: true, dryRun: true → then false |
7 full days including a weekend; live_equity has 7 rows |
| 4 | cluster | mainnet key, testnet: false, no vault, portfolioFraction: "0.20", ~$200 real |
Loud no-vault warning fires; one clean fill; leverage ≈0.2x; 3–5 days clean |
| 5 | cluster | vault created, vaultAddress set, portfolioFraction: "0.95" |
First run liquidates nothing and opens the target long |
Trigger a run manually:
Open question, answered at stage 2: Hyperliquid's "Nonces and API wallets"
docs only describe master → sub-account delegation, not agent → vault. vaultAddress
is part of the signed preimage so it is not a security hole, and third-party docs
say it works — but verify with a $10 testnet order. If it is rejected, fall back
to the leader's master key and note that in the runbook.
5. ⚠️ Rollback leaves the position open
Setting enabled: false and running helm upgrade stops new orders. It does
not flatten what is already open — there is no job left to close it. To go flat:
- point
LIVETRADE_BOT_WEIGHTSat a bot that is currently in cash and run once withdryRun: "false", or - close the position manually in the Hyperliquid UI.
Disabling the copier and walking away leaves a live, unmanaged, funding-accruing perp position.
6. Vault equity tracking
Real equity is recorded to the live_equity table — deliberately not
portfolio_worth, which is the paper leaderboard ($10k starts, no fees, no
funding); mixing real money into it makes both curves uninterpretable.
Two writers, one row per (broker, account, UTC day), idempotent:
- livetrade_hyperliquid.py records in its finally block (free — the sync
already made those API calls).
- record_live_equity.py runs as its own CronJob at 23:05 UTC so the curve stays
gapless on days the copier errors, days dryRun is on, and after it is disabled.
is_testnet keeps validation runs out of the published curve. The website
generator reads this table via generator/vault.py.
7. Usage
🔍 Ticker Mapping (Discovery)
Broker symbols (e.g., EURUSD) rarely match yfinance tickers (EURUSD=X) exactly.
⚠️ Index tickers need an explicit override. A bot whose ticker is an index (
^XAU,^VIX, …) has nothing tradeable to buy.SymbolMapperonly translates^GSPC/^NDX/^IXICby default; any other^ticker is returned unchanged, so it is notNoneand does not look "unmapped". The copier therefore rejects any mapped symbol that still starts with^, andLIVETRADE_STRICT_MAPPING=truewill abort the sync — which is what you want, since the alternative is silently submitting orders the broker rejects on every run. Fix it by mapping the index to a tradeable proxy ETF insymbol_map.json, as^XAU→GDXdoes for Collective2.
1. Run Discovery
Find unmapped tickers in your bot portfolios:
2. Review and Approve
Open symbol_map.review.json. For each ticker, manually add the selected_symbol and selected_type keys:
"BTC-USD": {
"candidates": [...],
"selected_symbol": "BTCUSD", // YOU ADD THIS
"selected_type": "crypto" // YOU ADD THIS
}
3. Apply
🛡 Going Live Checklist
- Dry Run: Run for at least 3-5 days with
LIVETRADE_DRY_RUN=true. - Verify Equity: Confirm the "Broker total equity" logged matches your broker dashboard.
- Strict Mode: Set
LIVETRADE_STRICT_MAPPING=true. - Start Small: Use one bot with a low weight (e.g.,
0.1) first. - Manual Check: After the first live sync, verify the orders appear in your broker's "Open Orders" or "Positions" tab.
🏗 Deployment (Kubernetes)
Deploy the copier as a CronJob that runs after your trading bots finish.
apiVersion: batch/v1
kind: CronJob
metadata:
name: livetrade-copier
spec:
schedule: "5 21 * * 1-5" # 9:05 PM UTC (Post-market)
jobTemplate:
spec:
template:
spec:
containers:
- name: copier
image: your-repo/tradingbot:latest
command: ["python", "-m", "tradingbot.livetrade_collective2"]
envFrom:
- secretRef:
name: tradingbot-secrets
🔌 Adding a New Broker
To support a new broker, implement the LiveBroker interface in tradingbot/livetrade/broker.py:
get_positions(): ReturnDict[symbol, qty].get_total_equity(): Return float (Cash + Market Value).place_order(symbol, qty, side, type): Execute the trade.get_latest_price(symbol): Return current price for a broker symbol.map_symbol(yf_symbol): Convert yf ticker to broker ticker.search_symbol(query): Helper for ticker discovery.
Reference tradingbot/livetrade/collective2.py for a complete example.
❓ Troubleshooting
| Issue | Root Cause | Solution |
|---|---|---|
| 401/403 Error | Invalid API Key | Ensure you are using a v4 key from the C2 dashboard. |
| Empty "Results" | Wrong System ID | Verify COLLECTIVE2_SYSTEM_ID matches your strategy URL. |
| "Order Failed" Log | Broker Rejection | Check the log line for ErrorCode; usually due to buying power or invalid symbol. |
| All targets unmapped | Typo or Empty Bot | Verify bot names in LIVETRADE_BOT_WEIGHTS match the DB exactly. |
| Quantity 0 warning | Min Order Filter | Increase a specific bot's weight or decrease LIVETRADE_MIN_ORDER_USD. |
🚫 When NOT to Use
- Before you have at least 1 month of paper trading history.
- With a
System IDthat you do not own/manage. - If you require sub-second execution (this is a scheduled copier, not a HFT engine).