Per-lifecycle-stage order timestamps¶
Every order event carries eight optional timestamps, one per lifecycle stage. They make it possible to compute latencies (ack, time-to-fill, cancel processing) directly from a single event, without subscribing to every prior status and recording the clock.
What's exposed¶
For every fill or order-update event, the payload has these fields
(Python / Codon names; Node uses camelCase: submittedAtNs, etc.):
submitted_at_ns— when SUBMITTED firedaccepted_at_ns— when ACCEPTED firedfirst_fill_at_ns— first PARTIALLY_FILLED or FILLEDlast_fill_at_ns— most recent fill (refreshes on every fragment)canceled_at_ns— when CANCELED firedrejected_at_ns— when REJECTED firedtriggered_at_ns— when a conditional order transitioned from PENDING_TRIGGER to TRIGGEREDexpired_at_ns— when EXPIRED fired
A timestamp reads zero if the order has not reached that stage. The slot for the status of the current event is always the freshest; earlier stages carry their historical timestamps.
Compute latencies¶
import flox_py as flox
class LatencyWatcher(flox.Strategy):
def on_order_update(self, ctx, ev):
if ev.status == "ACCEPTED":
ack_ns = ev.accepted_at_ns - ev.submitted_at_ns
print(f"order {ev.order_id} ack latency: {ack_ns} ns")
def on_fill(self, ctx, ev):
if ev.status == "FILLED":
ttf = ev.first_fill_at_ns - ev.submitted_at_ns
print(f"order {ev.order_id} time to first fill: {ttf} ns")
Notes¶
- The clock source is engine time. On backtests this is the simulator clock; on live this is whichever clock the connector attached to the engine.
- Latency in backtest resolves at tape-event granularity. Gaps smaller than one event tick read as zero.
- The fields persist for the lifetime of the order, including through partial fills. After the order reaches a terminal state the tracking entry is dropped from the simulator.