Skip to content
Ron Hinchley edited this page Mar 26, 2026 · 7 revisions

CLI — Task Guide

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]

Table of Contents


How do I see what's going on?

Account and portfolio overview

./ibctl.py status
./ibctl.py summary
./ibctl.py summary --json        # machine-readable

All open positions

./ibctl.py positions

All plugins

./ibctl.py plugin list

A specific plugin's positions and open orders

./ibctl.py plugin dump momentum_5day
./ibctl.py plugin dump momentum_5day --json

Plugin internal state

./ibctl.py plugin status momentum_5day

How do I fund a plugin?

Funding 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.

Step 1 — See what's available in the unassigned pool

./ibctl.py transfer list _unassigned

Step 2 — Preview the transfer (no --confirm)

./ibctl.py transfer cash _unassigned momentum_5day 25000

The engine shows what would change without making any changes.

Step 3 — Execute the transfer

./ibctl.py transfer cash _unassigned momentum_5day 25000 --confirm

Fund with an existing position instead of cash

# Preview
./ibctl.py transfer position _unassigned momentum_5day SPY 100

# Execute
./ibctl.py transfer position _unassigned momentum_5day SPY 100 --confirm

Fund with both cash and a position

Run two transfers in sequence — one for cash, one for each position.

Return funds to the pool

Transfer in the opposite direction:

./ibctl.py transfer cash momentum_5day _unassigned 25000 --confirm

How do I transfer assets between plugins?

Transfers between any two plugins work the same as transfers to/from _unassigned.

Transfer cash

# From plugin A to plugin B
./ibctl.py transfer cash plugin_a plugin_b 5000 --confirm

Transfer a position

./ibctl.py transfer position plugin_a plugin_b SPY 50 --confirm

See what a plugin holds that can be transferred

./ibctl.py transfer list plugin_a

Notes

  • 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

How do I load and start a plugin?

Load a plugin

./ibctl.py plugin load plugins.momentum_5day.plugin

The path is a Python import path relative to the engine's working directory.

Load with a named slot (for multiple instances)

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 --confirm

Without =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"

Start an idle plugin

./ibctl.py plugin start momentum_5day

You can also use the plugin's UUID (shown in plugin list):

./ibctl.py plugin start a3f1c2d4-...

Load and start in one go

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

Check it's running

./ibctl.py plugin list
# Output:
#   momentum_5day   33bb850c  [running]

./ibctl.py plugin status momentum_5day
# Output: Plugin 'momentum_5day': running  (id: 33bb850c-...)

How do I stop or pause a plugin?

Freeze (pause without stopping)

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_5day

Stop a plugin

The plugin saves state, unsubscribes from streams and MessageBus, and transitions to STOPPED. Holdings are preserved.

./ibctl.py plugin stop momentum_5day

plugin 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.

Unload a plugin

Stops (if running) then removes it from the engine. Holdings remain in SQLite for the next load.

./ibctl.py plugin unload momentum_5day

Disable signal generation without stopping

The plugin keeps running (receiving data, callbacks fire) but calculate_signals is skipped.

./ibctl.py plugin disable momentum_5day
./ibctl.py plugin enable momentum_5day

Pause all plugins at once

./ibctl.py pause
./ibctl.py resume

How do I place a manual trade?

Simple market order

./ibctl.py order buy  SPY 100
./ibctl.py order sell QQQ  50 --confirm

Limit order

./ibctl.py order buy  AAPL 25 limit 175.00 --confirm
./ibctl.py order sell MSFT 30 limit 420.00

Stop order

./ibctl.py order sell SPY 100 stop 445.00 --confirm

Stop-limit order

# stop price, then limit price
./ibctl.py order sell SPY 100 stop-limit 445 443 --confirm

Trailing stop — fixed amount

./ibctl.py order sell SPY 100 trail 2.50 --confirm

Trailing stop — percentage

./ibctl.py order sell SPY 100 trail 0.5% --confirm

Market on Close / Market on Open

./ibctl.py order buy SPY 100 moc --confirm
./ibctl.py order buy SPY 100 moo --confirm

Limit on Close / Limit on Open

./ibctl.py order sell QQQ 50 loc 390.00 --confirm
./ibctl.py order buy  QQQ 50 loo 385.00 --confirm

Time in force

Append --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-kill

Default TIF is day.

Trade as a plugin instance

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" --confirm

The 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.

Liquidate positions

./ibctl.py liquidate SPY --confirm        # One symbol
./ibctl.py liquidate --confirm            # Everything

How do I send a custom request to a plugin?

Plugins 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 --json

The response always has a "success" key. On success, data is under "data".

General-purpose messaging

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}'

Get a plugin's CLI help

Well-behaved plugins implement cli_help() to document their commands:

./ibctl.py plugin help momentum_5day

Trigger one execution cycle manually

Useful for testing calculate_signals without waiting for the next bar:

./ibctl.py plugin trigger momentum_5day

How do I manage a plugin's instrument list?

Plugins 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.

List instruments

./ibctl.py plugin instruments list spy_momentum

Shows every registered symbol along with its weight bounds, exchange, currency, security type, and whether it is enabled.

Add or update an instrument

# 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 STK

If 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.

Remove an instrument

./ibctl.py plugin instruments remove spy_momentum AAPL

Enable / disable without removing

./ibctl.py plugin instruments disable spy_momentum QQQ
./ibctl.py plugin instruments enable  spy_momentum QQQ

A 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.

Clear all instruments

./ibctl.py plugin instruments clear spy_momentum

Removes every instrument for that slot. If compliance enforcement is on the plugin can no longer generate any trades until instruments are re-added.

Reload after external changes

./ibctl.py plugin instruments reload spy_momentum

Forces 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.


How do I reconcile plugin holdings with IB?

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 report

The 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.


How do I move a plugin from paper to live?

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.

What transfers

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.

Step 1 — Export the paper instance

With the paper engine running:

./ibctl.py plugin export gld_usd_swap gld_usd_swap_paper.json

Stop the paper engine:

./ibctl.py stop

Step 2 — Start the live engine

Restart 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

Step 3 — Import the plugin

./ibctl.py plugin import gld_usd_swap_paper.json

The plugin is now in idle state with all instruments and parameters intact. Holdings start at zero — the paper positions are not carried over.

Step 4 — Fund and start

# 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

Step 5 — Verify

./ibctl.py plugin dump gld_usd_swap
./ibctl.py reconcile

Reconciliation should show zero discrepancies at this point since no positions have been taken yet.

Notes

  • 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 slot or account_id fields by hand before importing if needed
  • The engine must be connected before plugin import will work; the class file must be accessible at the path recorded in the export

How do I shut down gracefully?

./ibctl.py stop

This signals the engine to:

  1. Stop all plugins (saving state)
  2. Cancel open IB subscriptions
  3. Close the IB connection
  4. Exit

Full Command Reference

Connection options (all commands)

Flag Default Description
--socket PATH /tmp/tws_headless.sock Unix socket path
--timeout SECS 10 Command timeout
--json off Force JSON output

Status & information

Command Description
status Account overview and net liquidation
positions All positions with P&L
summary Plugin-by-plugin breakdown
help List all commands

Plugin management

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.


Transfers

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.


Orders

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


System control

Command Description
pause Freeze all plugins
resume Resume all plugins
reconcile [--json] Compare plugin ledgers to IB account
stop Graceful engine shutdown

TWS Headless


Theory of Operation

  • Startup sequence
  • Market data & streams
  • Plugin execution
  • Holdings & bookkeeping
  • Order lifecycle
  • State persistence

CLI — Task Guide


Plugin Manual ← complete reference

Bar Store

Plugin Design

  • 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

Clone this wiki locally