Strategy¶
Base class for Codon event-driven strategies. Mirrors C++ flox::Strategy.
Class: Strategy¶
Constructor¶
Parameters:
symbols-- List of symbol IDs to subscribe tostrategy_id-- Subscriber id, default1registry-- Symbol-registry handle. When omitted, the underlying handle is created empty andRunner.add_strategyrebuilds it once a registry is present. Pass the strategy into aRunnerto give it an event source; on its own it has none.
Overridable Callbacks¶
on_trade(ctx, trade)¶
Called on each trade event for subscribed symbols.
def on_trade(self, ctx: SymbolContext, trade: TradeData):
price = trade.price.to_double()
# strategy logic here
on_book_update(ctx)¶
Called on each order book update for subscribed symbols.
on_bar(ctx, bar)¶
Called on each closed OHLC bar.
def on_bar(self, ctx: SymbolContext, bar: BarData):
# bar.open, bar.high, bar.low, bar.close, bar.volume, ...
if bar.close > bar.open and self.position() == 0.0:
self.market_buy(0.01)
on_start() / on_stop()¶
Lifecycle callbacks.
on_fill(ctx, ev)¶
Called on each fill (status PARTIALLY_FILLED or FILLED) for orders this strategy emitted. ev
carries order_id, side, fill_qty, fill_price, exchange_ts_ns.
on_order_update(ctx, ev)¶
Called on every order-lifecycle status change for orders this strategy emitted: NEW, ACCEPTED,
CANCELED, REJECTED, REPLACED, TRIGGERED, TRAILING_UPDATED. Fills come through here too —
override on_fill instead if you only want fills.
on_queue_position_change(ctx, ev)¶
A resting limit order's queue position moved with no other lifecycle transition. ev.queue_ahead and
ev.queue_total carry the current snapshot. Backtest only.
on_market_position_change(ctx, ev)¶
A resting limit order's categorical market position transitioned (best, behind_best, mid_spread,
level_empty, crossed). ev.market_position is the new state; ev.distance_to_best_ticks is signed
ticks from best on our side. Backtest only.
Signal Emission¶
emit_market_buy(symbol, qty) -> int¶
Submit a market buy order. Returns order ID.
emit_market_sell(symbol, qty) -> int¶
Submit a market sell order. Returns order ID.
emit_limit_buy(symbol, price, qty) -> int¶
Submit a limit buy order. Returns order ID.
emit_limit_sell(symbol, price, qty) -> int¶
Submit a limit sell order. Returns order ID.
emit_cancel(order_id)¶
Cancel an order by ID.
emit_cancel_all(symbol)¶
Cancel all orders for a symbol.
emit_modify(order_id, new_price, new_qty)¶
Modify an existing order's price and quantity.
emit_stop_market(symbol, side, trigger, qty) -> int¶
Submit a stop market order. side: 0=BUY, 1=SELL.
emit_stop_limit(symbol, side, trigger, limit_price, qty) -> int¶
Submit a stop limit order.
emit_take_profit_market(symbol, side, trigger, qty) -> int¶
Submit a take profit market order.
emit_take_profit_limit(symbol, side, trigger, limit_price, qty) -> int¶
Submit a take profit limit order.
emit_trailing_stop(symbol, side, offset, qty) -> int¶
Submit a trailing stop order.
emit_trailing_stop_percent(symbol, side, callback_bps, qty) -> int¶
Submit a trailing stop with percentage callback. callback_bps: 100 = 1%.
emit_limit_buy_tif(symbol, price, qty, tif) -> int¶
Submit a limit buy with TimeInForce. tif: 0=GTC, 1=IOC, 2=FOK, 4=POST_ONLY.
emit_limit_sell_tif(symbol, price, qty, tif) -> int¶
Submit a limit sell with TimeInForce.
emit_close_position(symbol) -> int¶
Close entire position with a reduce-only market order.
Context Queries¶
position(symbol=None) -> float¶
Current position quantity. If symbol is None, uses the first subscribed symbol.
ctx(symbol=None) -> SymbolContext¶
Get a SymbolContext for querying per-symbol state.
get_order_status(order_id) -> int¶
Get order status. Returns -1 if not found.
String-Symbol Convenience API¶
These take an optional symbol name and resolve it through the strategy's own name map, falling
back to the primary symbol when omitted. Prices and quantities are plain float.
| Method | Returns |
|---|---|
market_buy(qty, symbol=None) |
int order id |
market_sell(qty, symbol=None) |
int order id |
limit_buy(price, qty, symbol=None, tif="gtc") |
int order id |
limit_sell(price, qty, symbol=None, tif="gtc") |
int order id |
stop_market(side, trigger, qty, symbol=None) |
int order id |
stop_limit(side, trigger, limit_price, qty, symbol=None) |
int order id |
take_profit_market(side, trigger, qty, symbol=None) |
int order id |
take_profit_limit(side, trigger, limit_price, qty, symbol=None) |
int order id |
trailing_stop(side, offset, qty, symbol=None) |
int order id |
trailing_stop_percent(side, callback_bps, qty, symbol=None) |
int order id |
close_position(symbol=None) |
int order id |
cancel_order(order_id) |
None |
cancel_all_orders(symbol=None) |
None |
modify_order(order_id, new_price, new_qty) |
None |
pos(symbol=None) |
float position |
last_price(symbol=None) |
float |
best_bid(symbol=None) |
float |
best_ask(symbol=None) |
float |
mid_price(symbol=None) |
float |
order_status(order_id) |
int |
side and tif are lowercase strings here ('buy' / 'sell', 'gtc' / 'ioc' / ...), unlike the
emit_* methods which take the integer constants from flox.types. An unrecognised
side falls back to buy and an unrecognised tif to GTC. An unknown symbol name raises
ValueError.
Multi-Timeframe Bar Ring¶
| Method | Description |
|---|---|
last_closed_bar(symbol, bar_type, param) -> Optional[BarData] |
Most recent closed bar for that (symbol, timeframe), or None before one has closed |
last_n_closed_bars(symbol, bar_type, param, n) -> List[BarData] |
Most recent n bars, oldest first |
bar_ring_capacity() -> int |
Bars retained per (symbol, timeframe) |
set_bar_ring_capacity(n) |
Set the retention count |
param is nanoseconds for time bars, a count for tick bars, a threshold for volume bars.
Properties¶
primary_symbol -> int¶
Returns the first symbol in the subscription list.
primary_symbol_name -> str¶
The name of the primary symbol, as resolved through the registry.
Example¶
from flox.strategy import Strategy
from flox.context import SymbolContext
from flox.types import TradeData
from flox.indicators import EMA
class EmaCrossover(Strategy):
fast_ema: EMA
slow_ema: EMA
def __init__(self, symbols: List[int]):
super().__init__(symbols)
self.fast_ema = EMA(12)
self.slow_ema = EMA(26)
def on_trade(self, ctx: SymbolContext, trade: TradeData):
price = trade.price.to_double()
fast = self.fast_ema.update(price)
slow = self.slow_ema.update(price)
if not self.slow_ema.ready:
return
sym = self.primary_symbol
if fast > slow and ctx.is_flat():
self.emit_market_buy(sym, 1.0)
elif fast < slow and ctx.is_long():
self.emit_close_position(sym)