-
-
Notifications
You must be signed in to change notification settings - Fork 0
CLI
ibctl is the command-line interface to a running TWS Headless engine. It communicates over a Unix socket (/tmp/tws_headless.sock by default).
./ibctl.py [command] [args...] [--socket PATH] [--timeout SECS] [--json]- How do I see what's going on?
- How do I fund a plugin?
- How do I transfer assets between plugins?
- How do I load and start a plugin?
- How do I stop or pause a plugin?
- How do I place a manual trade?
- How do I place a manual trade as a plugin instance?
- How do I send a custom request to a plugin?
- How do I load multiple instances of a plugin?
- How do I manage a plugin's instrument list?
- How do I reconcile plugin holdings with IB?
- How do I move a plugin from paper to live?
- How do I shut down gracefully?
- Full Command Reference
./ibctl.py status
./ibctl.py summary
./ibctl.py summary --json # machine-readable./ibctl.py positions./ibctl.py plugin list./ibctl.py plugin dump momentum_5day
./ibctl.py plugin dump momentum_5day --json./ibctl.py plugin status momentum_5dayFunding a plugin means moving cash (or positions) from the _unassigned pool into the plugin's ledger. No IB orders are placed — this is bookkeeping only.
./ibctl.py transfer list _unassigned./ibctl.py transfer cash _unassigned momentum_5day 25000The engine shows what would change without making any changes.
./ibctl.py transfer cash _unassigned momentum_5day 25000 --confirm# Preview
./ibctl.py transfer position _unassigned momentum_5day SPY 100
# Execute
./ibctl.py transfer position _unassigned momentum_5day SPY 100 --confirmRun two transfers in sequence — one for cash, one for each position.
Transfer in the opposite direction:
./ibctl.py transfer cash momentum_5day _unassigned 25000 --confirmTransfers between any two plugins work the same as transfers to/from _unassigned.
# From plugin A to plugin B
./ibctl.py transfer cash plugin_a plugin_b 5000 --confirm./ibctl.py transfer position plugin_a plugin_b SPY 50 --confirm./ibctl.py transfer list plugin_a- Both plugins must be loaded (not necessarily started)
- The source must have sufficient cash/quantity
- Negative amounts are not allowed — reverse the from/to instead
- No confirmation prompt is shown without
--confirm; always preview first
./ibctl.py plugin load plugins.momentum_5day.pluginThe path is a Python import path relative to the engine's working directory.
Append =slot_name to the path to assign a stable instance key. Each slot
gets independent state, holdings, and a distinct CLI address:
./ibctl.py plugin load plugins.momentum_5day.plugin=spy_momentum
./ibctl.py plugin load plugins.momentum_5day.plugin=qqq_momentum
# Now address each by slot name
./ibctl.py plugin start spy_momentum
./ibctl.py plugin start qqq_momentum
./ibctl.py transfer cash _unassigned spy_momentum 25000 --confirm
./ibctl.py transfer cash _unassigned qqq_momentum 25000 --confirmWithout =slot, the plugin's own name (from super().__init__("name", ...)) is used as the slot.
With an optional descriptor (metadata tag visible in plugin list):
./ibctl.py plugin load plugins.momentum_5day.plugin "SPY momentum strategy"./ibctl.py plugin start momentum_5dayYou can also use the plugin's UUID (shown in plugin list):
./ibctl.py plugin start a3f1c2d4-...plugin load automatically transitions through LOADED; you still need plugin start to move to STARTED. The typical sequence is:
./ibctl.py plugin load plugins.momentum_5day.plugin
./ibctl.py plugin start momentum_5day
./ibctl.py transfer cash _unassigned momentum_5day 25000 --confirm./ibctl.py plugin list
# Output:
# momentum_5day 33bb850c [running]
./ibctl.py plugin status momentum_5day
# Output: Plugin 'momentum_5day': running (id: 33bb850c-...)The plugin saves its state, calculate_signals stops being called, but streams and subscriptions remain active. Resume any time.
./ibctl.py plugin freeze momentum_5day
./ibctl.py plugin resume momentum_5dayThe plugin saves state, unsubscribes from streams and MessageBus, and transitions to STOPPED. Holdings are preserved.
./ibctl.py plugin stop momentum_5dayplugin stop is idempotent — it returns [OK] even if the plugin is already in idle or stopped state. Only plugins in running or frozen state have their stop() method invoked.
Stops (if running) then removes it from the engine. Holdings remain in SQLite for the next load.
./ibctl.py plugin unload momentum_5dayThe plugin keeps running (receiving data, callbacks fire) but calculate_signals is skipped.
./ibctl.py plugin disable momentum_5day
./ibctl.py plugin enable momentum_5day./ibctl.py pause
./ibctl.py resume./ibctl.py order buy SPY 100
./ibctl.py order sell QQQ 50 --confirm./ibctl.py order buy AAPL 25 limit 175.00 --confirm
./ibctl.py order sell MSFT 30 limit 420.00./ibctl.py order sell SPY 100 stop 445.00 --confirm# stop price, then limit price
./ibctl.py order sell SPY 100 stop-limit 445 443 --confirm./ibctl.py order sell SPY 100 trail 2.50 --confirm./ibctl.py order sell SPY 100 trail 0.5% --confirm./ibctl.py order buy SPY 100 moc --confirm
./ibctl.py order buy SPY 100 moo --confirm./ibctl.py order sell QQQ 50 loc 390.00 --confirm
./ibctl.py order buy QQQ 50 loo 385.00 --confirmAppend --tif to any order type:
./ibctl.py order buy SPY 100 limit 450 --tif gtc --confirm # Good-til-cancelled
./ibctl.py order buy SPY 100 limit 450 --tif ioc # Immediate-or-cancel
./ibctl.py order buy SPY 100 limit 450 --tif fok # Fill-or-killDefault TIF is day.
Use trade instead of order to place a real IB order and book the fill against a specific plugin's holdings, commission tracking, and P&L ledger.
# By plugin name (works when only one instance is loaded)
./ibctl.py trade momentum_5day BUY SPY 50 --confirm
# By slot name (required when multiple instances of the same class are running)
./ibctl.py trade spy_momentum BUY SPY 100 --confirm
./ibctl.py trade qqq_momentum BUY QQQ 75 --confirm
# By UUID (always unambiguous)
./ibctl.py trade a3f1c2d4-17e7-4ee5-ac03-282e0cd05c2b BUY SPY 50 --confirm
# With a reason (logged in execution history)
./ibctl.py trade spy_momentum SELL SPY 50 --reason "stop hit" --confirmThe fill updates that instance's holdings (current_cash, current_positions), commissions are attributed to it, and P&L is reported against it — identical to a signal the plugin generated itself.
Without --confirm, the command is a dry-run preview that shows what would be placed without placing it.
./ibctl.py liquidate SPY --confirm # One symbol
./ibctl.py liquidate --confirm # EverythingPlugins implement handle_request(request_type, payload). You can invoke it from the CLI:
# No payload
./ibctl.py plugin request momentum_5day get_status
# With JSON payload
./ibctl.py plugin request momentum_5day set_period '{"period": 20}'
# Get the response as JSON
./ibctl.py plugin request momentum_5day get_status --jsonThe response always has a "success" key. On success, data is under "data".
Use plugin message to send arbitrary JSON to a plugin without specifying a request type.
Inside the plugin, request_type will be "message":
./ibctl.py plugin message momentum_5day '{"action": "reset", "value": 0.5}'Well-behaved plugins implement cli_help() to document their commands:
./ibctl.py plugin help momentum_5dayUseful for testing calculate_signals without waiting for the next bar:
./ibctl.py plugin trigger momentum_5dayPlugins that set INSTRUMENT_COMPLIANCE = True can only trade symbols in their registered instrument set. The list is stored per-slot in SQLite and can be read and modified live from the CLI without restarting the plugin.
./ibctl.py plugin instruments list spy_momentumShows every registered symbol along with its weight bounds, exchange, currency, security type, and whether it is enabled.
# Minimal — just a symbol
./ibctl.py plugin instruments add spy_momentum SPY
# With metadata
./ibctl.py plugin instruments add spy_momentum QQQ --name "Nasdaq 100 ETF" \
--weight 0.5 --min-weight 0 --max-weight 1.0 \
--exchange SMART --currency USD --sec-type STK
# Add a foreign stock
./ibctl.py plugin instruments add global_plugin SMSN.IL \
--exchange IEX --currency ILS --sec-type STKIf the symbol already exists its metadata is replaced. The change is written to SQLite immediately; a running plugin sees it after the next plugin instruments reload.
./ibctl.py plugin instruments remove spy_momentum AAPL./ibctl.py plugin instruments disable spy_momentum QQQ
./ibctl.py plugin instruments enable spy_momentum QQQA disabled instrument stays in the list but calculate_signals should treat it as inactive. The compliance check still blocks fills for disabled symbols when INSTRUMENT_COMPLIANCE = True.
./ibctl.py plugin instruments clear spy_momentumRemoves every instrument for that slot. If compliance enforcement is on the plugin can no longer generate any trades until instruments are re-added.
./ibctl.py plugin instruments reload spy_momentumForces the running plugin to re-read its instrument list from SQLite into memory. Useful after bulk edits via sqlite3 or after migrating instruments from a JSON file.
Reconciliation compares what each plugin's ledger says it holds against the actual IB account positions.
./ibctl.py reconcile
./ibctl.py reconcile --json # Machine-readable reportThe report shows:
- Positions the plugin claims but IB doesn't have
- Positions IB has that no plugin claims
- Cash discrepancies
Reconciliation is advisory — it reports differences but does not auto-correct them. Use transfers to realign bookkeeping.
Paper and live accounts are completely isolated — each account gets its own plugin state, holdings, and registry. The export/import commands carry a plugin's configuration and tuning across the boundary without mixing the two accounts' ledgers.
| Transfers | Does not transfer |
|---|---|
| Instrument list (symbols, weights, bounds) | Cash balance |
| Algorithm parameters | Positions |
| Plugin state (run counters, signal history, learned metrics) | Commission history |
| Source file path (so the class can be reloaded) | Account-specific P&L |
Cash and positions start at zero on live — you fund the live instance through the normal transfer workflow after import.
With the paper engine running:
./ibctl.py plugin export gld_usd_swap gld_usd_swap_paper.jsonStop the paper engine:
./ibctl.py stopRestart the engine pointed at your live account port (7496 for TWS live, 4001 for Gateway live):
python -m ib.run_engine --port 7496 --mode immediate./ibctl.py plugin import gld_usd_swap_paper.jsonThe plugin is now in idle state with all instruments and parameters intact. Holdings start at zero — the paper positions are not carried over.
# Move cash from the unassigned pool into the plugin
./ibctl.py transfer cash _unassigned gld_usd_swap 150000 --confirm
# Start signal generation
./ibctl.py plugin start gld_usd_swap./ibctl.py plugin dump gld_usd_swap
./ibctl.py reconcileReconciliation should show zero discrepancies at this point since no positions have been taken yet.
- If you want to run paper and live simultaneously under different slot names, import with a slot override:
plugin import gld_usd_swap_paper.json gld_usd_swap_live - The export file is plain JSON — you can edit the
slotoraccount_idfields by hand before importing if needed - The engine must be connected before
plugin importwill work; the class file must be accessible at the path recorded in the export
./ibctl.py stopThis signals the engine to:
- Stop all plugins (saving state)
- Cancel open IB subscriptions
- Close the IB connection
- Exit
| Flag | Default | Description |
|---|---|---|
--socket PATH |
/tmp/tws_headless.sock |
Unix socket path |
--timeout SECS |
10 | Command timeout |
--json |
off | Force JSON output |
| Command | Description |
|---|---|
status |
Account overview and net liquidation |
positions |
All positions with P&L |
summary |
Plugin-by-plugin breakdown |
help |
List all commands |
| Command | Description |
|---|---|
plugin list |
All plugins, their state, instance ID |
plugin load PATH[=SLOT] [DESC] |
Load plugin; optional =SLOT assigns instance key |
plugin unload NAME|SLOT|ID |
Unload plugin (stops first if needed) |
plugin status NAME|SLOT|ID |
Plugin state, holdings summary |
plugin start NAME|SLOT|ID |
Transition idle → running |
plugin stop NAME|SLOT|ID |
Transition running/frozen → stopped (idempotent from idle/stopped) |
plugin freeze NAME|SLOT|ID |
Pause signal generation, keep streams |
plugin resume NAME|SLOT|ID |
Resume from FROZEN |
plugin enable NAME|SLOT|ID |
Re-enable signal generation |
plugin disable NAME|SLOT|ID |
Suppress signals without stopping |
plugin trigger NAME|SLOT|ID |
Run one calculate_signals cycle now |
plugin dump NAME|SLOT|ID |
Positions, open orders, holdings detail |
plugin request NAME TYPE [JSON] |
Send typed request to plugin's handle_request
|
plugin message NAME [JSON] |
Send arbitrary JSON (delivers request_type="message") |
plugin help NAME |
Show plugin CLI help (calls cli_help()) |
plugin instruments list NAME |
List registered instruments for a plugin instance |
plugin instruments add NAME SYM [opts] |
Add or update an instrument (see flags below) |
plugin instruments remove NAME SYM |
Remove an instrument |
plugin instruments enable NAME SYM |
Enable an instrument |
plugin instruments disable NAME SYM |
Disable an instrument without removing it |
plugin instruments clear NAME |
Remove all instruments for a plugin instance |
plugin instruments reload NAME |
Re-read instruments from SQLite into plugin memory |
instruments add flags: --name TEXT, --weight FLOAT, --min-weight FLOAT, --max-weight FLOAT, --exchange TEXT, --currency TEXT, --sec-type TEXT, --disabled
NAME is the plugin's class name; SLOT is the instance key assigned at load; ID is the UUID shown in plugin list. All are accepted interchangeably.
| Command | Description |
|---|---|
transfer list PLUGIN |
Show cash and positions available to transfer |
transfer cash FROM TO AMOUNT [--confirm] |
Move cash between plugins |
transfer position FROM TO SYMBOL QTY [--confirm] |
Move position between plugins |
Without --confirm, all transfer commands are dry-run previews.
| Command | Description |
|---|---|
order buy|sell SYMBOL QTY [TYPE] [options] [--confirm] |
Place any order type |
trade NAME|SLOT|ID buy|sell SYMBOL QTY [--confirm] [--reason TEXT] |
Order attributed to a plugin instance; updates its holdings and P&L |
liquidate [SYMBOL] [--confirm] |
Liquidate one or all positions |
Order types:
| Type syntax | IB order type |
|---|---|
| (omitted) | Market (MKT) |
limit PRICE |
Limit (LMT) |
stop PRICE |
Stop (STP) |
stop-limit STOP LIMIT |
Stop-Limit (STP LMT) |
trail AMOUNT |
Trailing Stop — fixed |
trail N% |
Trailing Stop — percentage |
moc |
Market on Close |
loc PRICE |
Limit on Close |
moo |
Market on Open |
loo PRICE |
Limit on Open |
TIF values: day (default) · gtc · ioc · fok
| Command | Description |
|---|---|
pause |
Freeze all plugins |
resume |
Resume all plugins |
reconcile [--json] |
Compare plugin ledgers to IB account |
stop |
Graceful engine shutdown |
TWS Headless
- Startup sequence
- Market data & streams
- Plugin execution
- Holdings & bookkeeping
- Order lifecycle
- State persistence
- See what's going on
- Fund a plugin
- Transfer assets
- Load and start a plugin
- Stop or pause a plugin
- Place a manual trade
- Send a plugin request
- Manage instrument list
- Reconcile holdings
- Move paper → live
- Shut down
- Full command reference
Plugin Manual ← complete reference
- File layout
- Lifecycle methods
- State persistence
- Market data streams
- Trade signals
- Order callbacks
- Holdings management
- MessageBus
- ContractBuilder
- Instrument compliance
- Multiple instances (slots)
- CLI help & messaging
- Threading rules
- Full example