#[non_exhaustive]pub struct Trade<AssetKey, InstrumentKey> {Show 14 fields
pub id: TradeId,
pub order_id: OrderId,
pub instrument: InstrumentKey,
pub strategy: StrategyId,
pub time_exchange: DateTime<Utc>,
pub side: Side,
pub price: Decimal,
pub quantity: Decimal,
pub order_filled_quantity: Option<Decimal>,
pub fees: AssetFees<AssetKey>,
pub origin: TradeOrigin,
pub position_side: Option<Side>,
pub client_order_id: Option<ClientOrderId>,
pub time_source: TimeSource,
}Expand description
One execution: a fill the venue reported, or one the engine settled itself.
#[non_exhaustive], so a field can be added without breaking a caller. Outside this crate,
build one with Trade::new and the with_* methods, and change a copy by assigning its
public fields.
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.id: TradeId§order_id: OrderId§instrument: InstrumentKey§strategy: StrategyId§time_exchange: DateTime<Utc>When the fill happened, as the venue stamped it, unless
time_source says this host’s clock filled it in.
side: Side§price: Decimal§quantity: DecimalThe size of this execution.
order_filled_quantity: Option<Decimal>The order’s cumulative filled quantity as of this execution, as the venue reported it –
not the size of this execution, which is Trade::quantity.
A fill carries two facts: that an execution happened, and what it left the order at. A consumer that tracks order state needs both, and they arrive together or not at all. Where the venue supplies the second, carrying it here lets the order be advanced from the fill itself rather than from a separate message that may never come.
Advancing from a cumulative is idempotent: re-applying the same fill, or applying two out of order, cannot double-count, because the receiver takes the greater of what it holds and what is reported rather than adding to a running total.
None means the venue’s fill payload does not carry it, and the order’s state must be
learned some other way – an order snapshot, or a reconciliation fetch. It is not a claim
that nothing is filled. Known producers, as of writing:
| Source | Reports it |
|---|---|
Binance Spot / Margin WebSocket executionReport | yes (z) |
| Binance Spot / Margin fill recovery after a disconnect | yes, rebuilt from the order’s executions; None where that lookup failed or ran out of time |
Binance Spot / Margin fetch_trades (myTrades REST) | no |
Alpaca WebSocket trade_updates | yes (order.filled_qty) |
| Alpaca account-activities REST | yes (cum_qty) |
Interactive Brokers ExecutionData | yes (cumulative_quantity) |
Hyperliquid userFills | no |
SimulatedVenue | yes |
#[serde(default)] so trades serialised before this field existed still deserialise, as
None.
fees: AssetFees<AssetKey>§origin: TradeOriginWhy the fill happened, as far as its producer can tell. See TradeOrigin for what each
venue reports.
#[serde(default)] so trades serialised before this field existed still deserialise, as
TradeOrigin::Order.
position_side: Option<Side>The side of the position the fill applies to, when the venue names one: Side::Buy for
a long, Side::Sell for a short.
A venue in hedge mode, holding a long and a short in one instrument at once, names the
position each fill belongs to (Binance USD-M futures’ LONG and SHORT). The engine in
OmsMode::Hedging checks a forced fill, which matches none of its orders, against it: a
forced fill must reduce the position the venue names. It only ever confirms the side a
reducing fill already implies, the side opposite its own, and does not choose among
several of the engine’s positions on that side.
It takes part in the derived Eq, Hash and Ord, as every field does: deduplicate fills
by instrument and id, not by the whole Trade.
None when the venue names none, as every client in this crate does as of writing: none
trades a venue in hedge mode.
#[serde(default)] so trades serialised before this field existed still deserialise, as
None.
client_order_id: Option<ClientOrderId>The client order id of the order this fill belongs to, as the venue reported it with the fill.
The id is chosen before the order is sent, so a consumer can attribute a fill by it at
once. That includes a fill of an order placed outside the engine on the same account,
which no order the engine knows matches: order_id is the venue’s, and
the fill can arrive before the venue’s response to the order names it, or long after, in a
recovery read. A consumer that trades an account outside the engine can mark those orders’
client order ids, by a prefix say, and keep their fills from the engine.
None means the fill’s payload did not carry one. It is not a claim that the order has
none. Known producers, as of writing:
| Source | Reports it |
|---|---|
Binance Spot / Margin WebSocket executionReport | yes (c) |
| Binance Spot / Margin fill recovery after a disconnect | yes, read from each recovered fill’s order; None where that lookup failed or ran out of time |
Binance Spot / Margin fetch_trades (myTrades REST) | no |
Alpaca WebSocket trade_updates | yes (order.client_order_id) |
Alpaca account-activities REST (fill recovery, fetch_trades), fetch_option_events | no |
| Interactive Brokers executions | yes, the order reference, which this crate’s client sets to the client order id; None for an order without one |
Hyperliquid userFills | yes, the client order id the order’s cloid maps to; None for an order placed without a cloid |
SimulatedVenue | yes |
The rustrade engine’s expiry settlement | no |
#[serde(default)] so trades serialised before this field existed still deserialise, as
None.
time_source: TimeSourceWhose clock time_exchange was read from. See TimeSource for
when a producer fills in this host’s.
#[serde(default)] so trades serialised before this field existed still deserialise, as
TimeSource::Venue.
Implementations§
Source§impl<AssetKey, InstrumentKey> Trade<AssetKey, InstrumentKey>
impl<AssetKey, InstrumentKey> Trade<AssetKey, InstrumentKey>
Sourcepub fn new(
id: TradeId,
order_id: OrderId,
instrument: InstrumentKey,
strategy: StrategyId,
time_exchange: DateTime<Utc>,
side: Side,
price: Decimal,
quantity: Decimal,
order_filled_quantity: Option<Decimal>,
fees: AssetFees<AssetKey>,
) -> Self
pub fn new( id: TradeId, order_id: OrderId, instrument: InstrumentKey, strategy: StrategyId, time_exchange: DateTime<Utc>, side: Side, price: Decimal, quantity: Decimal, order_filled_quantity: Option<Decimal>, fees: AssetFees<AssetKey>, ) -> Self
A trade of TradeOrigin::Order. Set another origin with
with_origin.
Sourcepub fn with_origin(self, origin: TradeOrigin) -> Self
pub fn with_origin(self, origin: TradeOrigin) -> Self
This trade with its origin set.
Sourcepub fn with_position_side(self, position_side: Side) -> Self
pub fn with_position_side(self, position_side: Side) -> Self
This trade with the position_side the venue named.
Sourcepub fn with_client_order_id(self, client_order_id: ClientOrderId) -> Self
pub fn with_client_order_id(self, client_order_id: ClientOrderId) -> Self
This trade with the client_order_id the venue reported.
Sourcepub fn with_time_source(self, time_source: TimeSource) -> Self
pub fn with_time_source(self, time_source: TimeSource) -> Self
This trade with its time_source set.
pub fn value_quote(&self) -> Decimal
Sourcepub fn holding_move<ExchangeKey>(
&self,
instrument: &Instrument<ExchangeKey, AssetKey>,
held: Decimal,
reduce_only: ReduceOnly,
) -> Option<HoldingMove>where
AssetKey: PartialEq,
pub fn holding_move<ExchangeKey>(
&self,
instrument: &Instrument<ExchangeKey, AssetKey>,
held: Decimal,
reduce_only: ReduceOnly,
) -> Option<HoldingMove>where
AssetKey: PartialEq,
How this fill moves the position it applies to, when that is not by its own
side and quantity: a spot fill whose fee the venue
charged in instrument’s base asset, or a reduce_only fill that would grow or flip the
position. None otherwise: the fill then moves the position by its own side and quantity.
The rustrade engine applies every fill with this function. A component that mirrors
positions outside the engine, such as a ledger of the fills an execution client forwarded,
agrees with the engine by calling it too.
held is the position the fill applies to, as it stands before the fill, signed: positive
for a long, negative for a short, zero for none. In an account holding a long and a short
at once, it is the one position the fill is routed to. reduce_only is what the fill’s
order was sent as, its
RequestOpen::reduce_only. Both
reduce-only values move the position alike: they differ only in how the engine books the
excess (see ReduceOnly).
§The rule
In this order:
- The fee is netted. It leaves the account in base on either side, so the holding moves
by the fill’s signed quantity less the fee: a buy of
qwith a base feefholdsq − f, and a sell deliversq + f. A negative fee, a rebate, adds to it. Only on a spot instrument, spot margin included. A derivative’s position is counted in contracts, and an inverse contract pays its fees in the settlement coin, so a fee there never changes the quantity. A fee in quote or a third asset, such as BNB, and a fee of zero are not netted. - A reduce-only move never grows or flips the position. When
ReduceOnly::is_reduce_only, a netted move larger than the opposite positionheldis cut to land it flat, and one that does not reduce it at all, becauseheldis zero or on the move’s side, moves nothing. What is cut off isHoldingMove::excess. A cut move is not rounded below: flat is on any step, and a position the fill left as it was is not the fill’s to round. This applies to any instrument kind. - The position is rounded toward zero to the step. Only when the fee was netted and
the move not cut. When
Instrument::quantity_stepgives a step, the position the fill leaves,heldplus the netted move, is rounded toward zero to a whole multiple of it, and the move is what lands the position there. A venue sells only whole steps, so a position off the step could never be closed to flat. It is the position that is rounded, not the move: a sell of 1 with a fee of 0.001 from a long of 2, in steps of 0.01, leaves 0.999, so the position is 0.99 and the move a sell of 1.01. What is rounded off isHoldingMove::dust. Aheldalready off the step is rounded too, so the dust can include a remainder this fill did not create. Without a step, the dust is zero. - The move’s side is its sign. A fee larger than a buy’s quantity sells the difference. A move of zero keeps the netted move’s side, which is the fill’s own unless the fee reversed it, with a quantity of zero.
§What it leaves to the caller
- The price and the fee. The netted quantity keeps the fill’s price, so PnL is charged
the fee’s quote equivalent, its
fees_quote: buyingq − fatpand payingf × pcostsq × p, what the fill paid. The engine charges nothing for the dust: it is held, a long’s as base the account holds and a short’s as base still owed on the loan, and the position books neither it nor the quote it cost. So the position agrees with the account at the fill’s price, and the dust’s value moves with the price outside it. - Only the fee the venue reported is netted. A fee the caller computes itself, as the engine’s fee model does, is in quote, and reaches PnL alone.
- The excess and its share of the fee. No position takes the excess: the account
holds it, or the venue spent it, as on a margin repayment sized to cover interest, or it
was free base the order was sized to trade, under
ReduceOnly::PositionAndFreeBase. The engine charges the position the share of the fill’s fees that the move is of the netted move, and the rest to its tear sheet as fees no position carries. It reports the excess without charging its value to PnL. - A settlement fill larger than its position, in a hedged account. Where an account
can hold a long and a short at once (the engine’s
OmsMode::Hedging), the engine does not call this for a settlement fill, such as a liquidation, that matches none of its orders and is larger than the position it reduces: it cuts that fill to the position, unnetted, so the position lands flat, and reports the excess. A mirror that matches the engine does the same rather than calling this. Such a fill is of no order of the engine’s, so it is never reduce-only. With one position per instrument (OmsMode::Netting), the engine never cuts a settlement fill, so this applies to every fill.
Trait Implementations§
Source§impl<'de, AssetKey, InstrumentKey> Deserialize<'de> for Trade<AssetKey, InstrumentKey>where
AssetKey: Deserialize<'de>,
InstrumentKey: Deserialize<'de>,
impl<'de, AssetKey, InstrumentKey> Deserialize<'de> for Trade<AssetKey, InstrumentKey>where
AssetKey: Deserialize<'de>,
InstrumentKey: Deserialize<'de>,
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl<AssetKey: Eq, InstrumentKey: Eq> Eq for Trade<AssetKey, InstrumentKey>
Source§impl<ExchangeKey, AssetKey, InstrumentKey> From<Trade<AssetKey, InstrumentKey>> for AccountEventKind<ExchangeKey, AssetKey, InstrumentKey>
impl<ExchangeKey, AssetKey, InstrumentKey> From<Trade<AssetKey, InstrumentKey>> for AccountEventKind<ExchangeKey, AssetKey, InstrumentKey>
Source§impl<AssetKey: Ord, InstrumentKey: Ord> Ord for Trade<AssetKey, InstrumentKey>
impl<AssetKey: Ord, InstrumentKey: Ord> Ord for Trade<AssetKey, InstrumentKey>
1.21.0 (const: unstable) · Source§fn max(self, other: Self) -> Selfwhere
Self: Sized,
fn max(self, other: Self) -> Selfwhere
Self: Sized,
1.21.0 (const: unstable) · Source§fn min(self, other: Self) -> Selfwhere
Self: Sized,
fn min(self, other: Self) -> Selfwhere
Self: Sized,
Source§impl<AssetKey: PartialEq, InstrumentKey: PartialEq> PartialEq for Trade<AssetKey, InstrumentKey>
impl<AssetKey: PartialEq, InstrumentKey: PartialEq> PartialEq for Trade<AssetKey, InstrumentKey>
Source§impl<AssetKey: PartialOrd, InstrumentKey: PartialOrd> PartialOrd for Trade<AssetKey, InstrumentKey>
impl<AssetKey: PartialOrd, InstrumentKey: PartialOrd> PartialOrd for Trade<AssetKey, InstrumentKey>
impl<AssetKey: PartialEq, InstrumentKey: PartialEq> StructuralPartialEq for Trade<AssetKey, InstrumentKey>
Auto Trait Implementations§
impl<AssetKey, InstrumentKey> Freeze for Trade<AssetKey, InstrumentKey>
impl<AssetKey, InstrumentKey> RefUnwindSafe for Trade<AssetKey, InstrumentKey>
impl<AssetKey, InstrumentKey> Send for Trade<AssetKey, InstrumentKey>
impl<AssetKey, InstrumentKey> Sync for Trade<AssetKey, InstrumentKey>
impl<AssetKey, InstrumentKey> Unpin for Trade<AssetKey, InstrumentKey>
impl<AssetKey, InstrumentKey> UnsafeUnpin for Trade<AssetKey, InstrumentKey>
impl<AssetKey, InstrumentKey> UnwindSafe for Trade<AssetKey, InstrumentKey>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Comparable<K> for Q
impl<Q, K> Comparable<K> for Q
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.Source§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.Source§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.Source§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.Source§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.Source§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.Source§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.Source§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreimpl<T> JsonSchemaMaybe for T
Source§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.Source§impl<T> ToStringFallible for Twhere
T: Display,
impl<T> ToStringFallible for Twhere
T: Display,
Source§fn try_to_string(&self) -> Result<String, TryReserveError>
fn try_to_string(&self) -> Result<String, TryReserveError>
ToString::to_string, but without panic on OOM.