AlgoInstructionStrategy
AlgoInstructionStrategy Specification
Version: 1.2.0 Date: 2026-04-30 Status: Draft
Executor-based algorithmic trading strategy that receives per-instruction algo params via userData (CSV signals) and manages entry/exit/risk executors per symbol. Supports position and signal modes, broker locate waterfall for short selling, and futures rollover.
Strategy Configuration
Passed via StrategyConfig.params:
StrategyConfig(
name='my_algo',
type='AlgoInstructionStrategy',
params={
# --- Data Sources ---
'userDataList': 'signal_file_1,signal_file_2', # Comma-separated userData source IDs
'algoConfigPath': '', # Path to algo config CSV file (see Algo Config File section)
'universeFile': '', # Path to universe file for symbol loading
'assetType': 'FUTURES', # Asset class: "FUTURES" or "EQUITY"
# --- Entry Time Window ---
'entryBeginTime': '09:30:00', # Entry window start (HH:MM:SS)
'entryEndTime': '15:45:00', # Entry window end
# --- Exit Time Window ---
'exitBeginTime': '15:45:30', # Exit window start
'exitEndTime': '16:00:00', # Exit window end
# --- Risk Time Window ---
'riskBeginTime': '09:30:00', # Risk window start
'riskEndTime': '15:45:25', # Risk window end (typically before exit window)
# --- Market Hours ---
'marketOpenTime': '09:30:00', # Market open
'marketCloseTime': '16:00:00', # Market close
# --- Global Executor Defaults ---
'entryExecutorType': 'POV', # Default entry executor type
'exitAlgo': 'POV', # Default exit executor type
'participatePercentage': 10.0, # Default entry POV participation (%)
'exitParticipatePercentage': 10.0, # Default exit POV participation (%)
'aggressivePriceMultiplier': 1.0, # Aggressive limit price multiplier
'executorNbboSizePct': 100.0, # NBBO quote size percentage
# --- Order Size Limits ---
'minOrderSize': 100, # Minimum order quantity
'maxOrderSize': 1000000, # Maximum order quantity
'minOrderNotional': 1000.0, # Minimum order notional ($)
'maxOrderNotional': 10000000.0, # Maximum order notional ($)
'exchangeMinOrderNotional': 5, # Exchange minimum notional
# --- Timing ---
'refreshMillis': 100, # Entry evaluation throttle (ms)
'exitRefreshIntervalInMillis': 30000, # Exit evaluation throttle (ms)
'executorRefreshMillis': 300, # Executor internal refresh (ms)
# --- Position Scaling ---
'mm': 1.0, # Market multiplier applied to target position
'mmReset': 1.0, # Market multiplier reset value
# --- Feature Flags ---
'enableExit': True, # Enable automatic exit window execution
'disableExit': False, # Force-disable exit execution
'disableTradingWindows': False, # Ignore time windows entirely
'enableShortSell': True, # Allow short selling
'ignoreETB': False, # Skip ETB locate checks
'reduceEntryPosition': True, # Allow reducing position on new instruction
'enableDynamicSymbolRegistration': False, # Register unknown symbols at runtime
'enableFuturesRollover': False, # Auto-transfer positions on contract rollover
'longSellNotional': 0, # Notional cap for short positions ($)
}
)Market Data Subscriptions
The strategy auto-subscribes to market data in on_start for every symbol in params.symbols, dispatched by assetType:
assetType | Subscription call | Schema |
|---|---|---|
FUTURES | ctx.subscribe_futures_trade_ticks(symbols=...) | fut_trades |
EQUITY | ctx.subscribe_trade_ticks(symbols, asset_type=AssetType.EQUITY) | trades |
OPTIONS | ctx.subscribe_option_snaps(...) per root, with option_expiration_type / option_snap_interval / strike + option_type filters pushed into the data adapter | snaps_<interval> |
Notes:
- The strategy was previously wired to
tbbo/subscribe_*_tbbofor futures and equities. As of v1.2.0 it uses the trade-tick schemas instead — executors that need a quote (peg family) read it from the instrument's cached NBBO snapshot rather than the strategy's data feed. - For options,
subscribe_tbbowould pull the entire chain;subscribe_option_snapspushes filters (expiration, strike, option_type, underlying) down to the adapter so only the contracts you trade flow through. - Subscription happens before
_load_universe()soinstrument.current_contractis resolved by SecurityMaster (e.g.,ES.c.0→ESH6) by the time the universe is built.
Signal File (userData CSV) Format
date,time,sym,ticker,desiredpos,signal1,weight1,locate_id,desk_qty,algo_config_id,exit| Column | Type | Description |
|---|---|---|
date | string | Date in YYYY-MM-DD format |
time | string | Time in HH:MM:SS.mmm format |
sym | string | Symbol (fallback if ticker is empty) |
ticker | string | Primary symbol identifier |
desiredpos | float | Target position (position mode) |
signal1 | float | Signal value (signal mode) |
weight1 | float | Signal weight for position sizing |
locate_id | string | Broker ID for ETB locate allocation |
desk_qty | int | Locate shares for short selling |
algo_config_id | string | Reference to a named config from the algo config file (see Algo Config File) |
exit | string | Set to 1/true to trigger exit instruction (for no-window mode) |
Additional columns recognized: user_shortname, broker_id, locate_qty, risk_qty
Instruction Type Auto-Detection
The instruction type is auto-detected from the columns present, evaluated in this order:
| Priority | Type | Detection Rule |
|---|---|---|
| 1 | etb | locate_id, user_shortname, or broker_id is set, OR event_id contains "etb" |
| 2 | position | desiredpos has a non-zero value |
| 3 | signal | signal1 and/or weight1 have values |
| 4 | risk | risk_qty > 0 column is present on the signal row |
| 5 | exit | exit column is 1/true/yes on the signal row |
Note: Risk is detected from the risk_qty column. Exit is triggered via the exit column. The executor config for risk/exit is resolved from the algo config file — the signal row only needs the trigger column (risk_qty or exit).
Signal-to-Position Conversion
When instruction_type == 'signal':
desired_pos = signal1 * weight1 * (capital / (ref_price * multiplier))algo_params Column Specification
The algo_params column configures per-instruction executor behavior across three slots: entry, exit, and risk. Each slot can have its own executor type and parameters.
Supported Formats
1. Semicolon-Separated (Recommended)
entry=POV;entry_participatePercentage=10;exit=MOC;risk=TWAP;risk_qty=100Syntax rules:
- Bare slot name sets executor type:
entry=POV,exit=MOC,risk=TWAP - Slot-prefixed params:
entry_<param>=<value>,exit_<param>=<value>,risk_<param>=<value> - Executor-type prefixed params:
pov_<param>=<value>,twap_<param>=<value>(routed to the matching slot) - Unprefixed params default to the entry slot
2. Nested JSON
{
"entry": { "executor_type": "POV", "participatePercentage": "10" },
"exit": { "executor_type": "MOC" },
"risk": { "executor_type": "TWAP", "qty": "100" }
}Slot values can be a string (executor type only) or a dict (executor type + params):
{ "entry": "POV", "exit": { "executor_type": "AUCTION", "orderType": "MOC" } }3. Flat JSON (Legacy)
{
"executorType": "POV",
"participatePct": 10,
"exitExecutorType": "AUCTION",
"exitParticipatePct": 20
}Executor Types
| Type | Description |
|---|---|
POV | Percentage of Volume |
TWAP | Time-Weighted Average Price |
VWAP | Volume-Weighted Average Price |
PASSIVE | Passive limit orders posted behind the same-side touch (uses aggressivePriceMultiplier as step-back) |
AUCTION | Auction orders (MOC/MOO) |
POV_PASSIVE | Hybrid POV + Passive |
MID_PRICE | Quote-driven peg at (bid+ask)/2 with tick-aware rounding (rounds buy ↑ / sell ↓ when spread is one tick) |
AGGRESSIVE | Quote-driven peg crossing toward last: buy = max(ask, last), sell = min(bid, last) |
PEG_PASSIVE | Quote-driven peg joining the same-side touch clamped by last: buy = min(bid, last), sell = max(ask, last) |
ALGO_COBRA | Algo variant: Cobra |
ALGO_TWAP | Algo variant: TWAP |
ALGO_VWAP | Algo variant: VWAP |
MOC | Market-on-Close (shorthand for AUCTION + orderType=MOC) |
MOO | Market-on-Open (shorthand for AUCTION + orderType=MOO) |
Peg Executors (MID_PRICE, AGGRESSIVE, PEG_PASSIVE)
These are NBBO-driven, quote-pegged executors that re-price on each refresh based on the current bid/ask and last trade. Use them when you want explicit control over passive vs. aggressive posting rather than schedule-driven (POV/TWAP/VWAP) execution.
Common behaviors (all three):
- Tick-aware rounding — every computed price is snapped to the symbol's quote increment (e.g., $0.05 for SPX options).
- Replace guard — a working order is replaced only when the new price is more favorable (higher for buy, lower for sell). Stale-but-better quotes are kept; no "chasing" the market backwards.
- Reject-saturation gate — after 5 rejects within 60 seconds, new submissions are suppressed until the window elapses (defaults are hard-coded today).
aggressivePriceMultiplier— applied as a side-aware additive offset on top of the pegged base price (positive value = more aggressive, e.g., forMID_PRICEbuy with offset0.02, posts atmid + 0.02).
When to use which:
| Executor | Posting behavior | Typical use |
|---|---|---|
MID_PRICE | Inside the spread at the midpoint | Options market-making, opportunistic mid-fills on wide spreads |
AGGRESSIVE | Crosses the spread toward last trade (takes liquidity) | When fill certainty matters more than price improvement |
PEG_PASSIVE | Joins the same-side touch (provides liquidity) | Patient entries where you want to be in the queue but never cross |
These executors do not take participatePercentage (no schedule), startTime/endTime/duration are inherited from the strategy entry/exit windows.
All Supported algo_params Parameters
Each parameter below can be set per-slot using the prefix convention (e.g., entry_participate_pct, exit_order_type, risk_start_time).
Participation & Pricing
| Parameter | Aliases | Type | Default | Description |
|---|---|---|---|---|
participatePercentage | participate_pct, pov, pov_percentage, participatepct | float | 0.0 (falls back to strategy default) | Participation rate as percentage of volume |
aggressivePriceMultiplier | aggressive_mult, aggr | float | 0.0 (falls back to strategy default) | Multiplier for aggressive limit price calculation |
executorNbboSizePct | nbbo_size_pct, nbbo | float | 100.0 | NBBO quote size percentage used for sizing |
Order Configuration
| Parameter | Aliases | Type | Values | Description |
|---|---|---|---|---|
orderType | order_type, otype | string | LIMIT, MARKET, MOC, MOO | Order type for the executor |
timeInForce | tif, time_in_force | string | DAY, GTX, GTC, IOC | Time-in-force for orders |
marketCenter | market_center, mc | string | Exchange code | Target trading venue / market center |
account | acct | string | Broker account ID | Broker account for order routing |
Time Control
| Parameter | Aliases | Type | Format | Description |
|---|---|---|---|---|
startTime | start_time | string | HH:MM:SS | Executor start time (overrides strategy window) |
endTime | end_time | string | HH:MM:SS | Executor end time (overrides strategy window) |
duration | string | 30s, 5m, 1h, 2h30m | Duration from start (alternative to endTime) |
Duration format: Supports h (hours), m (minutes), s (seconds) suffixes and combinations. Plain integer is treated as seconds. Examples: 30s, 5m, 1h, 2h30m, 1h30m45s, 300.
Risk Slot Specific
| Parameter | Type | Description |
|---|---|---|
qty | int | Risk order quantity (only valid in the risk slot) |
Custom FIX Parameters
| Parameter | Type | Description |
|---|---|---|
custom_fix_<tag> | string | Custom FIX protocol tag-value pair. <tag> is the integer FIX tag number. |
Example: custom_fix_5700=MyAlgo;custom_fix_5701=aggressive
Full algo_params Examples
Position Entry with POV
entry=POV;entry_participatePercentage=15;entry_aggressive_mult=1.2Buy/sell to target position using 15% POV with 1.2x aggressive pricing.
Entry + Timed Exit
entry=TWAP;entry_start_time=09:35:00;entry_end_time=10:00:00;exit=MOCTWAP entry between 09:35-10:00, close position at market-on-close auction.
Entry with Duration
entry=VWAP;entry_duration=30m;exit=AUCTION;exit_order_type=MOCVWAP entry over 30 minutes from instruction time, MOC exit.
Multi-Slot with Risk
entry=POV;entry_participatePercentage=10;exit=TWAP;exit_participatePercentage=20;risk=POV;risk_qty=50;risk_participatePercentage=100POV entry at 10%, TWAP exit at 20%, POV risk order for 50 shares at 100%.
Venue and Account Routing
entry=PASSIVE;entry_market_center=ARCA;entry_account=BROKER_A;entry_tif=DAY;entry_nbbo_size_pct=50Passive limit orders on ARCA via BROKER_A account, DAY TIF, using 50% of NBBO size.
Mid-Price Peg (Options 0DTE)
entry=MID_PRICE;entry_aggressive_mult=0.0;exit=AUCTION;exit_orderType=MOCPosts at NBBO midpoint with tick-aware rounding ($0.05 ticks for SPX options); MOC exit. Re-prices on every refresh; replaces only when the new mid is more favorable than the working order.
Aggressive Peg (Cross to Fill)
entry=AGGRESSIVE;entry_aggressive_mult=0.05Buy at max(ask, last) + 0.05, sell at min(bid, last) - 0.05. Use when fill certainty matters more than price improvement.
Passive Peg (Join the Touch)
entry=PEG_PASSIVE;exit=AUCTION;exit_orderType=MOCBuy at min(bid, last) floor-snapped to tick (sell at max(ask, last) ceil-snapped). Joins the touch but never crosses; combined with the never-move-backwards replace guard, the order stays in queue priority unless the market actually improves.
Custom FIX Tags
entry=POV;entry_participatePercentage=10;entry_custom_fix_5700=StrategyTag;entry_custom_fix_5701=aggressivePOV entry with custom FIX tags 5700 and 5701.
Nested JSON with All Parameters
{
"entry": {
"executor_type": "POV",
"participatePercentage": "15",
"aggressivePriceMultiplier": "1.5",
"orderType": "LIMIT",
"timeInForce": "DAY",
"marketCenter": "ARCA",
"account": "PRIME_1",
"nbbo_size_pct": "75",
"startTime": "09:35:00",
"endTime": "11:00:00",
"custom_fix_5700": "MyTag"
},
"exit": {
"executor_type": "AUCTION",
"orderType": "MOC"
},
"risk": {
"executor_type": "POV",
"participatePercentage": "100",
"qty": "200"
}
}ETB Locate Row
2025-09-16,04:00:00.000,AAPL,AAPL,,,,BROKER_A,5000,entry=POV;entry_participatePercentage=10Allocates 5000 shares of AAPL locate from BROKER_A. Locates are persistent ceiling (only increases).
Signal Mode Row
2025-09-01,18:42:11.000,ES.c.0,ES.c.0,,0.00329,1,,,entry=POV;entry_participatePercentage=10Signal=0.00329, weight=1. Position computed as signal * weight * (capital / (price * multiplier)).
Executor Slot Lifecycle
Each symbol maintains 3 independent executor slots:
| Slot | Purpose | Triggered By |
|---|---|---|
entry | Open or adjust position toward target | Position/signal instruction during entry window |
exit | Close position at end of day | Exit window start, or explicit exit instruction |
risk | Emergency position reduction | Risk instruction with risk_qty |
States: RUNNING → STOPPING → STOPPED
- Active executors are stopped before replacement
- Stopped executors are cleared via the executor state callback
- All slots reset at start of each trading day (broker locates persist)
Operating Modes: Window Mode vs Window-Disabled Mode
The strategy operates in two fundamentally different modes depending on the disableTradingWindows flag (also triggered by disableExit).
Window Mode (disableTradingWindows=False, default)
Time-driven execution with automatic transitions between entry, exit, and risk phases.
How it works:
- Entry window (
entryBeginTime→entryEndTime): Entry executors run. New position/signal instructions are accepted and evaluated each cycle. - Entry window closes: All non-auction entry executors are stopped automatically.
- Risk window (
riskBeginTime→riskEndTime): Risk instructions are evaluated. Risk window runs AFTER entry and BEFORE exit. - Risk window closes: All risk executors are stopped.
- Exit window (
exitBeginTime→exitEndTime): IfenableExit=True, exit is triggered. Entry and risk are stopped before exit starts. - Exit window closes: All exit executors are stopped.
Key behaviors in window mode:
exitAlgo(global exit executor type) is required — logged as warning if not set- Exit uses the global
exitAlgoas fallback if no per-instruction exit config is provided - Entry evaluation is skipped outside the entry window
- Exit evaluation is skipped outside the exit window
- Risk instructions received outside the risk window are deferred until the window opens
- Risk window must end before exit window starts (enforced ordering: entry → risk → exit)
- Overnight windows are supported for all window types (entry, exit, risk)
Typical timeline:
09:30 14:00 14:00 15:30 15:30 15:55
|--- entry ---| |risk--| |-- exit --|Risk window MUST end before exit window starts. The enforced ordering is entry → risk → exit.
Window-Disabled Mode (disableTradingWindows=True)
Instruction-driven execution with no automatic time transitions. Every action must be explicitly triggered by an instruction.
How it works:
- Time windows are ignored — entry, exit, and risk can execute at any time
- Entry runs continuously whenever there is a position delta
- Exit requires an explicit exit instruction with per-instruction executor config (no global fallback)
- Risk evaluates immediately (no window gating)
- Note: setting
disableExit=Truealso activates this mode as a side effect — it disables all time windows, not just exit
Key behaviors in window-disabled mode:
- No automatic entry stop at end of day
- No automatic exit trigger — exit only happens via explicit exit instruction
- Exit instructions require per-instruction exit config via the algo config file (global
exitAlgofallback is not used) - Manual exit with auction type (MOC/MOO): placed immediately alongside active entry (no need to stop entry first — the auction order does not compete with the entry executor for market liquidity)
- Manual exit with non-auction type: entry and risk are stopped first, then exit triggers. If exit can't start immediately (e.g., risk still stopping), it's marked as pending and retried each evaluate cycle
- Entry and risk run on every evaluate cycle without time checks
When to use each mode:
| Mode | Use Case |
|---|---|
| Window mode | Intraday strategies with defined entry/exit periods (e.g., enter 09:30-15:45, MOC exit) |
| Window-disabled | Event-driven strategies, manual trading, strategies that manage their own timing via startTime/endTime in the algo config file |
Window Mode Examples
Strategy config:
params={
'entryBeginTime': '09:30:00',
'entryEndTime': '15:45:00',
'exitBeginTime': '15:45:30',
'exitEndTime': '16:00:00',
'riskBeginTime': '09:30:00',
'riskEndTime': '15:45:25',
'entryExecutorType': 'POV',
'exitAlgo': 'MOC',
'enableExit': True,
'disableTradingWindows': False,
}Timeline:
09:30 15:45 15:45:30 16:00
|--- entry ---| |--- exit ---|
|--- risk window --|Example 1: Simple entry, auto exit at MOC
| time | CSV row | what happens |
|---|---|---|
| 09:35 | 2025-09-16,09:35:00.000,AAPL,AAPL,500,,,,, entry=POV;entry_participatePercentage=10 | Entry starts: POV buying toward 500 shares at 10% participation |
| 15:45 | (automatic) | Entry window closes, entry executor stopped |
| 15:45:30 | (automatic) | Exit window opens, MOC exit triggered automatically (uses global exitAlgo=MOC) |
| 16:00 | (automatic) | Auction fills, position flat |
Example 2: Entry with timed risk, then auto exit
| time | CSV row | what happens |
|---|---|---|
| 10:00 | 2025-09-16,10:00:00.000,AAPL,AAPL,1000,,,,,entry=POV;entry_participatePercentage=15;risk=POV;risk_qty=500;risk_start_time=14:00:00;risk_participatePercentage=100 | Entry starts: POV buying toward 1000 shares. Risk stored, deferred until 14:00 |
| 14:00 | (risk triggers) | Entry stopped. Risk starts: POV selling 500 shares at 100% participation |
| 14:02 | (risk completes) | Risk done. Position reduced to ~500 shares. Entry does NOT restart (risk flag set) |
| 15:45:30 | (automatic) | Exit window opens, MOC exit triggered for remaining ~500 shares |
Example 3: Multiple position updates during entry window
| time | CSV row | what happens |
|---|---|---|
| 09:35 | ...,AAPL,AAPL,300,,,,,entry=POV;entry_participatePercentage=10 | Entry starts toward 300 shares |
| 11:00 | ...,AAPL,AAPL,600,,,,,entry=POV;entry_participatePercentage=10 | Target updated to 600, entry executor target adjusted (delta recalculated) |
| 13:00 | ...,AAPL,AAPL,200,,,,,entry=POV;entry_participatePercentage=10 | Target reduced to 200, entry executor reverses direction (sells excess) |
| 15:45:30 | (automatic) | MOC exit triggered for remaining 200 shares |
Window-Disabled Mode Examples
Strategy config:
params={
'entryExecutorType': 'POV',
'disableTradingWindows': True,
# exitAlgo not needed — exit must be explicit per-instruction
}No timeline diagram — there are no windows. Everything is driven by instructions arriving at any time.
Example 1: Entry then explicit MOC exit (auction overlap)
| time | CSV row | what happens |
|---|---|---|
| 09:35 | 2025-09-16,09:35:00.000,AAPL,AAPL,500,,,,,entry=POV;entry_participatePercentage=10 | Entry starts: POV buying toward 500 shares |
| 15:30 | 2025-09-16,15:30:00.000,AAPL,AAPL,0,,,,,exit=MOC | MOC exit placed alongside active entry (auction overlap — entry keeps running) |
| 16:00 | (auction fills) | Position flat |
Note: The exit instruction has desiredpos=0 and an exit column set, so it is detected as type exit.
Example 2: Entry then explicit POV exit (non-auction)
| time | CSV row | what happens |
|---|---|---|
| 09:35 | 2025-09-16,09:35:00.000,AAPL,AAPL,500,,,,,entry=POV;entry_participatePercentage=10 | Entry starts toward 500 shares |
| 14:00 | 2025-09-16,14:00:00.000,AAPL,AAPL,0,,,,,exit=POV;exit_participatePercentage=20 | Entry and risk stopped first, then POV exit starts at 20% participation to flatten |
Note: Non-auction exit stops entry before starting (unlike auction exit which can overlap).
Example 3: Entry with duration-based timing
| time | CSV row | what happens |
|---|---|---|
| 10:00 | 2025-09-16,10:00:00.000,AAPL,AAPL,1000,,,,,entry=VWAP;entry_duration=2h | VWAP entry starts, executor runs from 10:00 to 12:00 |
| 12:00 | (executor time expires) | Entry executor stops after 2-hour duration |
| 15:30 | 2025-09-16,15:30:00.000,AAPL,AAPL,0,,,,,exit=AUCTION;exit_order_type=MOC | Explicit MOC exit to flatten at close |
Example 4: Entry, risk, then exit — all instruction-driven
| time | CSV row | what happens |
|---|---|---|
| 09:35 | 2025-09-16,09:35:00.000,AAPL,AAPL,1000,,,,,entry=POV;entry_participatePercentage=10 | Entry starts toward 1000 shares |
| 11:00 | 2025-09-16,11:00:00.000,AAPL,AAPL,0,,,,,risk=POV;risk_qty=400;risk_participatePercentage=100 | Entry stopped immediately. Risk starts: sell 400 shares at 100%. desiredpos=0 with risk config → detected as type risk |
| 11:05 | (risk completes) | Position reduced to ~600 shares. Entry blocked (risk flag set) |
| 15:30 | 2025-09-16,15:30:00.000,AAPL,AAPL,0,,,,,exit=MOC | MOC exit placed for remaining ~600 shares |
Example 5: New entry after risk resets the flag
| time | CSV row | what happens |
|---|---|---|
| 09:35 | ...,AAPL,AAPL,1000,,,,,entry=POV;entry_participatePercentage=10 | Entry starts toward 1000 |
| 11:00 | ...,AAPL,AAPL,0,,,,,risk=POV;risk_qty=500;risk_participatePercentage=100 | Entry stopped, risk sells 500 shares |
| 11:05 | (risk completes) | ~500 shares remaining. Entry blocked by risk flag |
| 13:00 | ...,AAPL,AAPL,800,,,,,entry=POV;entry_participatePercentage=10 | New entry instruction resets risk flag. Entry resumes toward new target of 800 shares |
| 15:30 | ...,AAPL,AAPL,0,,,,,exit=MOC | MOC exit to flatten |
What Makes AlgoInstructionStrategy Different
Unlike simple signal-to-order strategies, AlgoInstructionStrategy manages overlapping executor slots with strict mutual exclusion rules. Entry, exit, and risk executors cannot run concurrently for the same symbol — the strategy enforces a priority-based handoff protocol that ensures orderly transitions between execution phases.
Slot Mutual Exclusion Rules
The three slots follow these mutual exclusion constraints:
| Running Slot | Blocked Slots | Behavior |
|---|---|---|
| entry active/stopping | risk cannot start | Risk waits (retries each evaluate cycle) until entry fully stops |
| risk active/stopping | entry skipped, exit skipped | Entry evaluation returns immediately; exit trigger returns immediately |
| risk active/stopping (full flatten) | exit stopped | If risk qty >= current position, exit is stopped and marked as triggered |
| exit triggered | entry blocked | Entry will not start if exit has already been triggered (auction overlap case) |
Key Principle: Risk Always Wins
When a risk instruction arrives while entry is running:
- Entry executor is immediately stopped
- Risk instruction is stored on the risk slot
- Risk executor waits for entry to fully transition from STOPPING → STOPPED (cleared via the executor state callback)
- Once entry is cleared, risk executor starts on the next evaluate cycle
- After risk executes, the
risk_triggeredflag is set — entry will not restart unless a new entry instruction arrives and resets the flag
Workflow Combinations
Below are all supported execution workflows for a single symbol within a trading day:
1. Entry Only
entry(RUNNING) → entry(STOPPED)Position instruction arrives during entry window. Entry executor runs to target, stops when delta < 1.
2. Entry → Exit
entry(RUNNING) → entry(STOPPED) → exit(RUNNING) → exit(STOPPED)Entry runs during entry window. When exit window opens (or in window-disabled mode with an explicit exit instruction), entry is stopped (if still active) and exit executor starts to flatten the position.
3. Entry → Risk (Partial)
entry(RUNNING) → entry(STOPPING) → entry(STOPPED) → risk(RUNNING) → risk(STOPPED)Risk instruction arrives while entry is running. Entry is stopped immediately. Risk executor waits for entry to fully stop, then executes for risk_qty shares (partial reduction). Entry does not restart after risk — the risk_triggered flag blocks it until a new instruction.
4. Entry → Risk (Full Flatten)
entry(RUNNING) → entry(STOPPING) → entry(STOPPED) → risk(RUNNING) → risk(STOPPED)Same as partial risk, but risk_qty >= current_position. Additionally, exit is stopped (if running) and marked as triggered to prevent exit from starting after risk completes.
5. Entry → Exit → Risk
entry(RUNNING) → entry(STOPPED) → exit(RUNNING) → exit(STOPPED) → risk(RUNNING) → risk(STOPPED)Entry completes, exit begins during exit window. A risk instruction arrives — exit continues until risk is ready. Exit evaluation is skipped while risk is active/stopping. If risk arrives as a new standalone instruction, it does not interrupt exit directly but blocks exit re-evaluation while risk is active.
6. Entry → Risk → Exit
entry(RUNNING) → entry(STOPPED) → risk(RUNNING) → risk(STOPPED) → exit(RUNNING) → exit(STOPPED)Entry completes, risk triggers (e.g., timed risk via risk_start_time). Exit is blocked while risk is active/stopping. After risk completes (partial reduction), if there is remaining position and exit window is active, exit starts to flatten the rest.
7. Risk Only (No Entry)
risk(RUNNING) → risk(STOPPED)Standalone risk instruction with no prior entry. Entry is stopped if active. Risk executes to reduce or flatten the existing position.
8. Entry → Risk (Partial) → Exit
entry(RUNNING) → entry(STOPPED) → risk(RUNNING) → risk(STOPPED) → exit(RUNNING) → exit(STOPPED)Entry runs to target. Risk reduces position partially. Exit then flattens remaining position during exit window.
9. Exit Only
exit(RUNNING) → exit(STOPPED)No entry instruction — position already exists. Exit runs during exit window to flatten.
10. New Entry Resets Risk
entry(RUNNING) → entry(STOPPED) → risk(RUNNING) → risk(STOPPED) → [new entry instruction] → entry(RUNNING) → entry(STOPPED)After risk executes, the risk flag blocks entry. A new position instruction with entry config resets the flag, allowing entry to resume toward the new target.
11. Entry (AUCTION) + Exit Overlap (Window Mode)
entry[AUCTION](RUNNING) ──────────────────────────── entry(STOPPED)
exit(RUNNING) → exit(STOPPED)When entry is AUCTION and exit is non-AUCTION, the entry executor is not stopped at exit window open. Both run concurrently because the auction entry order does not compete with the non-auction exit for market liquidity. Once exit is triggered, new entry evaluation is blocked.
12. Entry + Auction Exit Overlap (Window-Disabled Mode)
entry(RUNNING) ──────────────────── entry(RUNNING/STOPPED)
exit[AUCTION](RUNNING) → exit(STOPPED)In window-disabled mode, an explicit exit instruction with auction type (MOC/MOO) is placed immediately without stopping entry. The auction exit runs alongside the active entry executor since they don't compete for market liquidity.
13. Early Auction Exit (Window Mode)
[exit timer fires]
entry(RUNNING) ──── exit[AUCTION](RUNNING) ──── exit window opens ──── exit(continues)When exit is AUCTION type (equity only, not futures), the strategy sends early auction exit orders before the exit window opens, on a separate timer controlled by exitRefreshIntervalInMillis. This allows MOC/MOO orders to be placed well in advance. The exit executor is started/amended on the exit timer, independent of the main evaluate loop.
Deferred Risk Execution
Risk instructions can be deferred (stored but not immediately triggered):
- If
risk_start_timeis in the future — waits until that time - If outside the risk time window (
riskBeginTime/riskEndTime) — waits until window opens
During deferral, entry is already stopped (mutual exclusion is enforced at instruction arrival), but the risk executor won't start until the trigger condition is met.
Auction Entry Overlap
When entry is AUCTION (e.g., MOC/MOO) and exit is NOT AUCTION, the two slots can overlap — the auction entry order does not compete with the non-auction exit for market liquidity. In this mode:
- Entry executors are not stopped when the exit window opens
- Exit can be triggered alongside the running auction entry
- However, once exit is triggered, new entry evaluation is blocked
This enables workflows like placing an MOC entry order early while a POV exit is already working the position.
Futures Rollover
When trading continuous futures contracts (e.g., ES.c.0, ES.v.0), the strategy supports automatic position transfer when a contract rolls to the next expiry.
Enabling Rollover
Rollover requires configuration at both the strategy and backtest level:
Strategy params:
params={
'futuresRollOverEnabled': True, # or 'enableFuturesRollover': True
'enableExit': False, # typically disabled so position carries overnight
}Backtest config:
backtest_config = BacktestConfig(
symbols=['ES.v.0'],
enable_auto_rollover=True, # Auto-injects filter_mode='continuous' and enableFuturesRollover
session_start='18:00', # CME Globex: 6 PM ET previous day
session_end='17:00', # CME Globex: 5 PM ET
)Engine config (optional):
engine_config = EngineConfig(
oms=OMSType.SIGMA.value,
params={
# Pre-close rollover exit window: EXIT old contract while trade data still available
'rollover_exit_window_start_utc': '13:00',
'rollover_exit_window_end_utc': '14:00',
}
)Continuous Contract Formats
| Format | Description | Example |
|---|---|---|
ES.c.0 | Calendar front month | Rolls by expiration date |
ES.v.0 | Volume-weighted front month | Rolls when next contract has more volume |
ESH6 | Specific contract | No rollover (fixed contract) |
Rollover Lifecycle
The rollover is handled by the C++ engine and follows this sequence:
-
ROLLOVER_DUE: Engine detects the continuous contract needs to roll (e.g., ESU5 → ESZ5)
- All Python executors are immediately stopped
- Entry/exit/risk evaluation is blocked (
rollover_in_progressflag)
-
ROLLOVER_EXIT: Engine exits the position on the old contract
- C++ handles the square-off automatically
-
ROLLOVER_ENTRY: Engine re-enters the position on the new contract
- C++ handles the re-entry automatically
-
ROLLOVER_COMPLETE: Rollover finished successfully
- Python-side bookkeeping is updated (symbol mapping, contract references)
- Evaluation resumes on the new contract
- Executor slots are reset, risk/exit flags are cleared
-
ROLLOVER_FAILED: Rollover encountered an error
- Logged as warning, evaluation resumes
Rollover Timing
The rollover date comes from the HiveQ API's fut_continuous_def schema, which provides start_date/end_date for each contract. The engine computes the rollover at 22:00 UTC (18:00 ET) on the day BEFORE the next contract's start_date.
Rollover Callbacks
Strategies can observe rollovers via two callbacks:
on_rollover — fired after a successful rollover:
def on_rollover(self, ctx, event):
rollover = event.data()
print(f"Rolled: {rollover.continuous_symbol}")
print(f" From: {rollover.prev_contract}")
print(f" To: {rollover.current_contract}")on_security_event — fired for each lifecycle stage (ROLLOVER_DUE, ROLLOVER_EXIT, etc.):
def on_security_event(self, ctx, event):
data = event.data()
print(f"Security event: {data.event_type} for {data.symbol}")Rollover Example
UserData CSV (rollover_instructions.csv):
date,time,sym,ticker,desiredpos,signal1,weight1,locate_id,desk_qty
2025-09-16,04:00:00.000,ESU5,ESU5,100,,,,Strategy config:
strategy_configs = [
StrategyConfig(
name='AlgoInstrRollover',
type='AlgoInstructionStrategy',
symbols=['ES.v.0'],
params={
'userDataList': 'RolloverInstructions',
'assetType': 'FUTURES',
'entryBeginTime': '04:00:00',
'entryEndTime': '15:45:00',
'enableExit': False, # Position carries overnight
'futuresRollOverEnabled': True,
}
)
]Data config:
data_configs = [
{
'type': 'hiveq_historical',
'dataset': 'HIVEQ_US_FUT',
'schema': ['fut_trades'],
# filter_mode='continuous' is auto-injected by enable_auto_rollover
},
{
'type': 'csv',
'data_type': 'custom',
'path': 'userdata/rollover_instructions.csv',
'id': 'RolloverInstructions',
},
]Backtest config:
backtest_config = BacktestConfig(
symbols=['ES.v.0'],
start_date='2025-09-16',
end_date='2025-09-17',
initial_capital=1000000.0,
venue='SIM',
session_start='18:00',
session_end='17:00',
fetch_mode='daily',
enable_auto_rollover=True,
)What happens:
- Sep 16: BUY 100 ESU5 via POV at 50% participation
- Sep 19-20 (weekend): Engine detects ESU5 → ESZ5 rollover
- Engine exits 100 ESU5 and re-enters 100 ESZ5 automatically
- Strategy receives
on_rollovercallback with prev_contract=ESU5, current_contract=ESZ5 - Subsequent instructions route to ESZ5
Algo Config File (Decoupled Execution Config)
Execution instructions (algo_params) can be separated from signal data into a standalone config file. This allows changing execution parameters (e.g., switching from POV to TWAP) without regenerating the signal file.
Setup
Set the algoConfigPath strategy param to the path of the algo config CSV file:
StrategyConfig(
type='AlgoInstructionStrategy',
params={
'userDataList': 'signals_feed',
'algoConfigPath': '/path/to/algo_config.csv',
...
}
)The algo config CSV is loaded at strategy startup (not via Sigma's event system) because it has no date/time columns — it's static configuration.
data_configs = [
{'type': 'csv', 'data_type': 'custom', 'path': 'signals.csv', 'id': 'signals_feed'},
]Algo Config File Format
algo_config_id,sym,override,algo_params
default,,true,entry=POV;entry_participatePercentage=5;exit=POV;exit_participatePercentage=20;entryBeginTime=09:30:00;entryEndTime=15:30:00;exitBeginTime=15:30:00;exitEndTime=15:55:00;riskBeginTime=14:00:00;riskEndTime=15:30:00
default_es,ES.c.0,true,entry=POV;entry_participatePercentage=10;exit=POV;exit_participatePercentage=20;risk=POV;risk_participatePercentage=100;entryBeginTime=18:00:00;entryEndTime=15:45:00;riskBeginTime=15:00:00;riskEndTime=15:45:00;exitBeginTime=15:45:30;exitEndTime=16:00:00
aggressive,,false,entry=POV;entry_participatePercentage=25;exit=AUCTION;exit_orderType=MOC
risk_reduce,,false,risk=POV;risk_participatePercentage=100
exit_moc,,false,exit=AUCTION;exit_orderType=MOC| Column | Required | Description |
|---|---|---|
algo_config_id | Yes | Unique name for this config |
sym / ticker | No | Symbol this config applies to (used with override) |
override | No | If true and sym is set, auto-applies to all signals for that symbol. If true and sym is empty, sets the global default for all symbols without one. |
algo_params | Yes | Execution config (same semicolon or JSON format as inline) |
date/timecolumns are optional. If present, config is loaded as a time-series (can change mid-day).- Multiple configs can exist for the same symbol (different IDs), but only one with
override=trueper symbol. - Time window params (
entryBeginTime,entryEndTime,exitBeginTime,exitEndTime,riskBeginTime,riskEndTime) can be included inalgo_paramsto define per-symbol or per-config windows. These override strategy-level defaults.
Override Mode (No Signal File Changes)
With override=true, you don't need to modify signal files at all:
algo_config.csv:
algo_config_id,sym,override,algo_params
default_es,ES.c.0,true,entry=POV;entry_participatePercentage=10signals.csv:
date,time,sym,ticker,desiredpos,signal1,weight1,locate_id,desk_qty
2025-09-01,18:42:11.000,ES.c.0,ES.c.0,,0.00329,1,,To change from POV 10% to TWAP 25%, edit the one line in algo_config.csv — the signal file stays untouched.
Explicit Config ID (Per-Row Override)
For rows that need a different config, use algo_config_id:
algo_config.csv:
algo_config_id,sym,override,algo_params
default_es,ES.c.0,true,entry=POV;entry_participatePercentage=10
aggressive,,,entry=POV;entry_participatePercentage=25signals.csv:
date,time,sym,signal1,weight1,algo_config_id
09:35,ES.c.0,0.00329,1,
14:01,ES.c.0,0.00297,1,aggressive
15:14,ES.c.0,0.00694,1,Row 2 uses aggressive config (25%), all others use the symbol override (10%).
Per-Config Time Windows
Time windows can be defined per-config in the algo_params field, overriding strategy-level defaults:
algo_config.csv:
algo_config_id,sym,override,algo_params
default_es,ES.c.0,true,entry=POV;entry_participatePercentage=10;exit=POV;exit_participatePercentage=20;entryBeginTime=18:00:00;entryEndTime=15:45:00;riskBeginTime=15:00:00;riskEndTime=15:45:00;exitBeginTime=15:45:30;exitEndTime=16:00:00Supported time window params in algo_params:
entryBeginTime/entryEndTimeexitBeginTime/exitEndTimeriskBeginTime/riskEndTime
Format: HH:MM:SS (same as strategy params). Overnight wrapping is supported (begin > end spans midnight).
Resolution: per-config windows → strategy-level windows → built-in defaults.
Cross-Symbol Global Default
A config row with override=true and empty sym becomes the global default for every symbol that does not have a symbol-specific override:
algo_config.csv:
algo_config_id,sym,override,algo_params
default,,true,entry=POV;entry_participatePercentage=5;exit=POV;exit_participatePercentage=20
default_es,ES.c.0,true,entry=POV;entry_participatePercentage=10ES.c.0signals usedefault_es(symbol-specific override wins)- All other symbols use
default(global default) - Only one global default row is supported; if multiple exist, the last one wins (with a warning logged)
Backwards Compatibility
- If
algoConfigPathis not set, the strategy uses global strategy params as defaults. algoConfigEventIDis still supported for runtime config updates via Sigma's event system (requires date/time columns in the CSV).- The
algo_paramscolumn is no longer supported on signal rows. Execution config must be provided via the algo config file or global strategy params.
Parameter Resolution Order
For executor config (entry, exit, risk), the resolution order is:
algo_config_idreference from signal row → named config lookup- Symbol override from algo config file (
override=true+ matchingsym) - Global default from algo config file (
override=true, emptysym) - Strategy config global default (
entryExecutorType,exitAlgo,participatePercentage, etc.) - Built-in default (POV 10%, 1.0x)