First Strategy¶
Write a simple trading strategy that reacts to market data. After your language quickstart you should already have FLOX building. This tutorial introduces the callback model that every binding shares.
The model¶
Every Strategy subclass gets four callbacks. You override the ones you need:
| Callback | Fires when |
|---|---|
on_trade(ctx, trade) |
A trade tick arrives |
on_book_update(ctx) |
Top-of-book moves (bid/ask change) |
on_bar(ctx, bar) |
A closed OHLC bar is dispatched |
on_start() / on_stop() |
Strategy lifecycle |
Inside callbacks you query state via ctx and place orders with helpers like market_buy(qty) / limit_sell(price, qty).
A printing strategy¶
This strategy logs every trade and tracks the best bid / ask.
import flox_py as flox
class PrintingStrategy(flox.Strategy):
def __init__(self, symbols):
super().__init__(symbols)
self.trade_count = 0
def on_start(self):
print(f"PrintingStrategy started")
def on_stop(self):
print(f"PrintingStrategy stopped. Trades seen: {self.trade_count}")
def on_trade(self, ctx, trade):
self.trade_count += 1
side = "BUY" if trade.is_buy else "SELL"
print(f"Trade: {trade.price:.2f} x {trade.quantity:.4f} ({side})")
def on_book_update(self, ctx):
print(f"Book: {ctx.best_bid:.2f} / {ctx.best_ask:.2f}")
class PrintingStrategy {
constructor(symbols) {
this.symbols = symbols;
this.tradeCount = 0;
}
onStart() { console.log("PrintingStrategy started"); }
onStop() { console.log(`PrintingStrategy stopped. Trades seen: ${this.tradeCount}`); }
onTrade(ctx, trade) {
this.tradeCount++;
const side = trade.isBuy ? "BUY" : "SELL";
console.log(`Trade: ${trade.price.toFixed(2)} x ${trade.qty.toFixed(4)} (${side})`);
}
onBookUpdate(ctx) {
console.log(`Book: ${ctx.bestBid.toFixed(2)} / ${ctx.bestAsk.toFixed(2)}`);
}
}
from flox.strategy import Strategy
from flox.context import SymbolContext
from flox.types import TradeData
class PrintingStrategy(Strategy):
trade_count: int = 0
def __init__(self, symbols: List[int]):
super().__init__(symbols)
def on_start(self):
print("PrintingStrategy started")
def on_trade(self, ctx: SymbolContext, trade: TradeData):
self.trade_count += 1
side = "BUY" if trade.is_buy else "SELL"
print(f"Trade: {trade.price.to_double():.2f} ({side})")
#include "flox/strategy/strategy.h"
using namespace flox;
class PrintingStrategy : public Strategy {
public:
PrintingStrategy(SymbolId symbol, const SymbolRegistry& reg)
: Strategy(/*id=*/1, symbol, reg) {}
void start() override { FLOX_LOG("PrintingStrategy started"); }
void stop() override { FLOX_LOG("Trades seen: " << _tradeCount); }
protected:
void onSymbolTrade(SymbolContext& /*ctx*/, const TradeEvent& ev) override {
++_tradeCount;
FLOX_LOG("Trade: " << ev.trade.price.toDouble()
<< " x " << ev.trade.quantity.toDouble()
<< " (" << (ev.trade.isBuy ? "BUY" : "SELL") << ")");
}
void onSymbolBook(SymbolContext& ctx, const BookUpdateEvent& /*ev*/) override {
FLOX_LOG("Book: " << ctx.book.bestBid()->toDouble()
<< " / " << ctx.book.bestAsk()->toDouble());
}
private:
uint64_t _tradeCount{0};
};
A trading strategy¶
Submit a buy after every 10th trade.
Wiring it up¶
Each language has a small bit of boilerplate to register your symbols and run the strategy.
auto tradeBus = std::make_unique<TradeBus>();
auto bookBus = std::make_unique<BookUpdateBus>();
SymbolRegistry registry;
SymbolInfo info{ .exchange = "binance", .symbol = "BTCUSDT",
.tickSize = Price::fromDouble(0.01) };
auto symId = registry.registerSymbol(info);
auto strat = std::make_unique<PrintingStrategy>(symId, registry);
tradeBus->subscribe(strat.get());
bookBus ->subscribe(strat.get());
std::vector<std::unique_ptr<ISubsystem>> subsystems;
subsystems.push_back(std::move(tradeBus));
subsystems.push_back(std::move(bookBus));
subsystems.push_back(std::move(strat));
EngineConfig config{};
Engine engine(config, std::move(subsystems), std::move(connectors));
engine.start();
Best practices¶
Do:
- Keep callbacks fast and non-blocking
- Filter or short-circuit on the first line if the event isn't relevant
- Let
ctx.is_flat()/ctx.positiondecide entries — don't track position state yourself - Use the strategy emit helpers (
market_buy(qty)) — they wire fees, queue position, and reduce-only flags correctly
Don't:
- Block in callbacks (no I/O, no locks, no big allocations)
- Hold references to event structs after the callback returns — they're recycled
- Throw exceptions from callbacks
Next¶
- Multi-Timeframe Strategy — work with multiple bar timeframes
- Recording Data — capture market data to disk
- Backtesting — replay recorded data
- Architecture overview — how events flow