AlgoInstructionStrategy
AlgoInstructionStrategy Specification
Version: 1.1.0 Date: 2026-03-27 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
'algoConfigEventID': '', # Algo config file source ID (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 ($)
}
)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 |
AUCTION | Auction orders (MOC/MOO) |
POV_PASSIVE | Hybrid POV + Passive |
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) |
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.
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.
- Exit window (
exitBeginTime→exitEndTime): IfenableExit=True, exit is triggered automatically for every symbol with a position. Entry is stopped before exit starts. - Exit window closes: All exit executors are stopped.
- Risk window (
riskBeginTime→riskEndTime): Risk instructions are evaluated whenever the risk window is active (can overlap with entry/exit windows, but mutual exclusion still applies).
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
- Overnight windows are supported (e.g.,
entryBeginTime=17:45:00,entryEndTime=15:59:00spans midnight)
Typical timeline:
09:30 15:45 15:45:30 16:00
|--- entry ---| |--- exit ---|
|--- risk window --|Risk window typically ends before the exit window opens (e.g., riskEndTime=15:45:25) since risk and exit cannot run concurrently — risk blocks exit and vice versa. Configuring them to overlap is allowed but the mutual exclusion rules mean only one will be active at a time.
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
Add a new strategy param and data config:
StrategyConfig(
type='AlgoInstructionStrategy',
params={
'userDataList': 'signals_feed',
'algoConfigEventID': 'algo_config_feed', # NEW: references the config data source
...
}
)data_configs = [
{'type': 'csv', 'data_type': 'custom', 'path': 'signals.csv', 'id': 'signals_feed'},
{'type': 'csv', 'data_type': 'custom', 'path': 'algo_config.csv', 'id': 'algo_config_feed'},
]Algo Config File Format
algo_config_id,sym,override,algo_params
default_es,ES.c.0,true,entry=POV;entry_participatePercentage=10;exit=AUCTION;exit_orderType=MOC
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 |
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.
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%).
Backwards Compatibility
- If
algoConfigEventIDis not set, the strategy uses global strategy params as defaults. - 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) - Strategy config global default (
entryExecutorType,exitAlgo,participatePercentage, etc.) - Built-in default (POV 10%, 1.0x)