Compile strategy broker
CompileStrategyBroker: pending orders, OHLC fills, OCA, commission, and event export.
This page
Compile strategy broker
Abstract
When CompilerVisitor detects strategy usage, object-mode emission instantiates CompileStrategyBroker—a lightweight broker aligned with the interpreter’s fill-before-script cadence, but designed for generated loops (no AST, no full StrategyState metric surface). It supports market entry/close and pending limit/stop/stop-limit orders with per-bar OHLC triggers, OCA cancel/reduce, commission, and slippage.
Conceptual model
Rendering…
Interface surface
Construction (from strategy() kwargs when lowered)
CompileStrategyBroker(
initial_capital=100_000.0,
commission_value=0.0,
commission_type="percent", # percent | cash_per_order | cash_per_contract
slippage_ticks=0,
mintick=0.01,
pyramiding=0,
default_qty_type="fixed",
default_qty_value=1.0,
avg_price_model="stock", # stock | futures | inverse (pynescript extension)
leverage=1.0, # futures UI: buying power / margin divisor
)
avg_price_model is lowered from strategy(..., avg_price_model=...). The compile broker is a single net lot: partial reduce already keeps position_avg_price sticky (futures-like). Multi-leg stock reweight on partial close is interpret-first until the compile path grows an open-trade list.
leverage scales percent/cash default qty (margin × leverage / price) and free cash (equity − notional/leverage).
Bar context
# Preferred generated hot path (one call; skips pending walk when empty)
broker.begin_bar(bar_index, open_, high, low, close, bar_time=…)
# Equivalent two-step still exists:
broker.set_bar(bar_index, bar_time, mark, open_=…, high=…, low=…, close=…)
broker.process_pending_orders(open_=…, high=…, low=…, close=…)
Orders
| Method | Role |
|---|---|
entry(...) | Market or pending entry; reverse flattens opposite |
close / close_all | Reduce/flat with optional price |
| Pending book | PendingOrder with type, direction, qty, limit/stop, OCA, max_fill_per_bar, is_entry |
Fill logic (_trigger_price)
| Type | Long trigger | Short trigger | Fill price idea |
|---|---|---|---|
| market | — | — | close (immediate path) |
| limit | low <= limit | high >= limit | gap-aware vs open |
| stop | high >= stop | low <= stop | gap-aware vs open |
| stop-limit | stop traded and limit available | symmetric | limit price |
Partial fills: max_fill_per_bar caps quantity per bar; residual stays pending.
OCA
After a fill, siblings in the same oca_name:
cancel— remove sibling, emitcancelreduce— decrease sibling qty by fill sizenone— no interaction
Economics
- Slippage:
± slippage_ticks * mintickby direction on fills. - Commission: percent of notional, cash per order, or cash per contract.
- Position: signed
position_size,position_avg_price,netprofit,equityhelpers for return payload.
Events
_emit appends dicts with keys compatible with strategy event serialization (kind, id, direction, qty, order_type, limit, stop, oca_name, comment, bar_index, bar_time, ohlc). Object-mode return includes '__events': __strategy.to_events() plus position/netprofit/equity snapshots.
Internals
| Path | Role |
|---|---|
src/pynescript/compiler/strategy_broker.py | Broker implementation |
src/pynescript/compiler/compiler.py | Emits __strategy wiring in object mode |
src/pynescript/ast/evaluator/builtins/strategy.py | Interpreter counterpart (richer) |
tests/test_compiler_strategy.py | Compile strategy coverage |
NA helpers treat None, blank/na strings, and float NaN as missing prices when classifying order types.
Invariants & edge cases
- Same order as interpreter:
begin_bar(orset_bar→process_pending_orders) → script body. - Closed-trade list is partial. The compile broker now keeps
closed_trade_recordsand someclosedtrades_*accessors (profit, comments, max drawdown/runup). Prefer interpret for the fullstrategy.closedtrades.*/opentrades.*metric surface. - Direction normalization accepts
strategy.long,long,1,buy, etc. - Reverse on opposite entry via
close_all(comment="reverse")then open. - Event dicts are mutable lists—fine for run output; not frozen dataclasses.
Worked examples
Generated prologue (illustrative)
__strategy = CompileStrategyBroker(initial_capital=100000.0)
for __bar_idx in range(n_bars):
__strategy.begin_bar(__bar_idx,
float(open_arr[__bar_idx]), float(high_arr[__bar_idx]),
float(low_arr[__bar_idx]), float(close_arr[__bar_idx]),
bar_time=int(time_arr[__bar_idx]))
# lowered strategy.entry / close calls
return {..., '__events': __strategy.to_events(),
'__position_size': __strategy.position_size,
'__netprofit': __strategy.netprofit,
'__equity': __strategy.equity}
Limit buy pending
strategy.entry("L", strategy.long, limit=low - syminfo.mintick)
Object-mode lowers to a pending limit; subsequent bars fill when low trades through.
Failure modes
| Symptom | Cause |
|---|---|
| No fills | Triggers never met; or market path not invoked |
| OCA sibling remains | oca_type none / name mismatch |
| PnL vs interpret drift | Commission/slippage/mintick defaults differ |
| Missing events in API envelope | Host only maps plots—not __events |