Writing Your First Codon Strategy¶
This tutorial walks through creating an SMA crossover strategy in Codon. The strategy buys when a fast SMA crosses above a slow SMA, and sells on the reverse crossover.
Prerequisites¶
- Flox built with C API enabled
- Codon compiler installed
cmake -B build -DFLOX_BUILD_CAPI=ON -DFLOX_ENABLE_BACKTEST=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build
Step 1: Create the Strategy File¶
Create my_sma_strategy.codon:
from flox.strategy import Strategy
from flox.context import SymbolContext
from flox.types import TradeData
from flox.indicators import SMA
class SmaCrossover(Strategy):
fast_sma: SMA
slow_sma: SMA
order_size: float
long_position: bool
def __init__(self, symbols: List[int], fast: int = 10, slow: int = 30):
super().__init__(symbols)
self.fast_sma = SMA(fast)
self.slow_sma = SMA(slow)
self.order_size = 1.0
self.long_position = False
def on_trade(self, ctx: SymbolContext, trade: TradeData):
price = trade.price.to_double()
fast = self.fast_sma.update(price)
slow = self.slow_sma.update(price)
# Wait until both SMAs have enough data
if not self.slow_sma.ready:
return
sym = self.primary_symbol
# Golden cross: fast crosses above slow
if fast > slow and not self.long_position:
self.emit_market_buy(sym, self.order_size)
self.long_position = True
# Death cross: fast crosses below slow
elif fast < slow and self.long_position:
self.emit_market_sell(sym, self.order_size)
self.long_position = False
def on_start(self):
print("Strategy started!")
def on_stop(self):
pos = self.position()
print(f"Strategy stopped. Final position: {pos}")
Step 2: Compile to Native Binary¶
Step 3: Understanding the Code¶
Imports¶
Strategy-- base class providing emit/query methodsSymbolContext-- per-symbol state (position, book, prices)TradeData-- trade event with price, quantity, sideSMA-- SMA indicator class with bothcompute()(batch) andupdate()/value/ready/reset(incremental)
Key Patterns¶
- Override
on_tradeto receive trade events - Use streaming indicators for per-tick computation
- Call
emit_*methods to submit orders - Query
position()to check current state
Performance¶
Because Codon compiles to native code:
- on_trade is compiled to a native function, not interpreted
- No GIL, no interpreter, no garbage collector pauses
SMA.update() is not an O(1) online recurrence. The indicator classes
in flox.indicators accumulate the full history and re-run the batch C-API
function over all of it on every call, which guarantees parity with
compute() but costs one FFI call over the whole history per tick. For
tick-rate hot paths, buffer the prices and call compute() on the batch.
Step 4: Compare with C++ and Python¶
The same strategy in C++:
void onSymbolTrade(SymbolContext& c, const TradeEvent& ev) override {
prices_.push_back(ev.trade.price.toDouble());
// ... SMA logic ...
if (fast_above && !long_position_)
emitMarketBuy(symbol(), orderSize_);
}
And in Python:
def on_trade(self, ctx, trade):
self.prices.append(trade.price)
# ... SMA logic ...
if fast > slow and not self.long_position:
self.emit_market_buy(ctx.symbol_id, 1.0)
All three produce identical trading behavior with the same API shape.
Next Steps¶
- See Indicators Reference for all available indicators
- See Strategy Classes for the full API reference