Capture a strategy run automatically with .floxrun¶
The .floxrun recorder API ships standalone. By default the strategy author calls rec.write_signal() / write_order_event() / write_fill() by hand. This page shows how to attach the recorder once and have every event captured automatically.
Adapter classes¶
flox::run::TraceSignalHandler wraps an existing ISignalHandler. Every emitted signal flows through it; the wrapper records it and then delegates to the inner handler. flox::run::TraceExecutionListener does the same for order lifecycle events on IOrderExecutionListener.
#include "flox/run/trace_handlers.h"
#include "flox/run/trace_recorder.h"
flox::run::TraceRecorderOptions opts;
opts.strategy_id = "my-strategy";
flox::run::TraceRecorder rec("./run.floxrun", std::move(opts));
flox::run::TraceSignalHandler trace_signals(real_signal_handler, &rec);
flox::run::TraceExecutionListener trace_exec(2, real_listener, &rec);
strategy.setSignalHandler(&trace_signals);
order_exec_bus.subscribe(&trace_exec);
// Update the feed timestamp once per dispatched market-data event so
// each captured signal / order / fill carries `feed_ts_ns`:
trace_signals.setFeedTsNs(trade.exchangeTsNs);
trace_exec.setFeedTsNs(trade.exchangeTsNs);
The feed_ts_ns setter is the only piece the engine wiring still has to drive — call it once per dispatched tape event so each captured record points back at its trigger.
What the recorder writes¶
| Engine event | .floxrun record |
|---|---|
Signal::market / limit / stop* etc. |
SignalRecord (kind 10) |
IOrderExecutionListener::onOrderSubmitted |
OrderEventRecord kind=submit |
onOrderAccepted |
OrderEventRecord kind=ack |
onOrderCanceled |
OrderEventRecord kind=cancel |
onOrderRejected |
OrderEventRecord kind=reject (carries reason) |
onOrderExpired |
OrderEventRecord kind=expire |
onOrderFilled / onOrderPartiallyFilled |
FillRecord (kind 12) |
A nullptr recorder pointer disables capture without removing the inner-handler delegation, so attaching the wrapper unconditionally during setup is safe.
Phase status¶
This page covers Phase 1: the C++ adapter classes ship in include/flox/run/trace_handlers.h and are exercised by tests/test_trace_handlers.cpp. Phase 2 lifts a one-call Runner.attach_trace_recorder(rec) helper into every binding so polyglot strategies capture without per-language plumbing. Phase 3 adds Runner.trace_order_event(...) and Runner.trace_fill(...) so the user's executor wrapper mirrors order events + fills into the same recorder with two extra lines per callback. End-to-end auto-subscription against the executor's listener bus is a follow-up tracked separately.
One-call attach¶
The Runner (sync mode) now exposes attach_trace_recorder(recorder). Every signal the strategy emits is auto-mirrored into the recorder. Order / fill auto-capture is a follow-up — wire those through TraceExecutionListener against the executor's listener bus until then.
# pybind11
import flox_py
rec = flox_py.TraceRecorder(path="./run.floxrun", strategy_id="trend",
strategy_hash="sha256:abc",
run_started_ns=time.time_ns())
runner = flox_py.Runner(registry, on_signal=lambda sig: None)
runner.attach_trace_recorder(rec)
runner.set_trace_feed_ts_ns(trade.exchange_ts_ns) # call once per tape event
// node
const rec = new flox.TraceRecorder({
path: "./run.floxrun",
strategyId: "trend",
strategyHash: "sha256:abc",
runStartedNs: Date.now() * 1000000,
});
const runner = new flox.Runner(registry, sig => {});
runner.attachTraceRecorder(rec);
runner.setTraceFeedTsNs(trade.exchangeTsNs);
Codon reaches the same C ABI symbols (flox_runner_attach_trace_recorder, flox_runner_set_trace_feed_ts_ns) directly. Pass null / None to detach.
Mirroring order events and fills¶
The signal capture hook above is one half of the trace. To get a complete .floxrun your executor wrapper has to mirror its own callbacks into the recorder. The runner exposes two methods for this:
# pybind11 — inside your executor's on_filled callback
runner.trace_fill(order_id=fill.order_id, fill_id=fill.fill_id,
price=fill.price, qty=fill.qty, fee=fill.fee,
symbol_id=fill.symbol_id, side=fill.side, liquidity=2)
// node — after the executor's on_canceled fires
runner.traceOrderEvent({
orderId, parentSignalId: 0, symbolId,
eventKind: 1 /* Cancel */, side, orderType: 0,
price: 0, qty: 0,
});
Both methods are no-ops when no recorder is attached, so wiring them unconditionally during executor setup is safe. eventKind matches OrderEventKind: 1=Submit, 2=Cancel, 3=Modify, 4=Ack, 5=Reject, 6=Expire. liquidity: 0=Unknown, 1=Maker, 2=Taker.
See also¶
- Record a strategy run as
.floxrun. The recorder API the adapters write into. floxrunstrategy-trace format spec. The on-disk layout.