pub fn create_architect_trade_id(
ts_event: UnixNanos,
price: Price,
quantity: Quantity,
aggressor_side: AggressorSide,
) -> Result<TradeId>Expand description
Creates a [TradeId] for an AX market-data trade.
AX publishes no trade identifier for market data: GET /trades and the market-data WebSocket
both carry only ts, tn, s, p, q, and d, and tn is the nanosecond component of the
timestamp rather than a sequence number. The composed timestamp alone is not unique either,
because one aggressor sweeping several levels reports multiple prints at an identical ts and
tn. Across 100 sandbox trades on 2026-07-25, GBPUSD-PERP yielded only 64 distinct
timestamps.
The identity is therefore the composed timestamp plus a digest over the price, quantity, and
aggressor side, which separated 99 of those 100 prints. Two prints identical in all five
fields remain indistinguishable; nothing the venue publishes separates them. A sandbox run on
2026-07-25 observed exactly that, two JPYUSD-PERP prints agreeing on timestamp, price,
quantity, and side, so the residual is real rather than theoretical at roughly 1 to 4 percent
of prints.
That residual is accepted because a duplicate here cannot reach an execution. Live fills carry
the venue’s own identifiers, fill.trade_id on REST and execution.tid on the orders
WebSocket, and the backtest matching engine mints its own trade IDs with a per-timestamp
counter. This function is called only from the two market-data parse_trade_tick functions, so
a duplicate is a market-data fidelity limit and never an execution or position-accounting risk.
Only a consumer that itself deduplicates on [TradeId] is affected; nothing in the Nautilus
data path compares them.
Both transports must call this so the same trade fetched historically and received live gets
one identity. The digest covers the parsed [Price] and [Quantity] rather than the wire
text, because AX sends the same price as "1.339700000000" over REST and in a shorter form
over the WebSocket.
Parity relies on both transports reporting the aggressor side. GET /trades always does, and
no sandbox WebSocket trade has omitted it, but AxMdTrade::d is modelled as optional and an
omitted side resolves to [AggressorSide::NoAggressor], which would not match the REST
identity for that trade.
The result is exactly 36 characters, which is [TradeId]’s maximum, so a timestamp beyond
19 digits (year 2262) returns an error rather than silently truncating.
§Errors
Returns an error if the composed identity is not a valid [TradeId].