Skip to main content

OrderRuntime

Struct OrderRuntime 

Source
pub struct OrderRuntime { /* private fields */ }
Expand description

Runtime order book: one state machine per client order id, a timeout sweep, and a guard that never lets the same place command go out twice.

The rule: a place command that got no answer is never repeated. A retry could create a second order at the broker. Instead the order goes to [OrderState::Unknown] and is settled by reading the broker (reconcile or order state). try_place_once enforces the “once”, and sweep_timeouts turns silent in-flight orders into Unknown.

§Examples

use std::time::{Duration, Instant};
use brokapi_core::{BrokerOrderId, ClientOrderId, OrderState};
use brokapi_runtime::OrderRuntime;
use brokapi_sansio::OrderSignal;

let mut orders = OrderRuntime::with_timeout(Duration::from_secs(30));
let t0 = Instant::now();
let cid = ClientOrderId::new_v4();

// First send of this cid is allowed. A second send of the same cid is not.
assert!(orders.try_place_once(cid));
orders.register_place(cid, t0);
assert!(!orders.try_place_once(cid));

// No answer within the timeout: the outcome is unknown, not "failed".
let changed = orders.sweep_timeouts(t0 + Duration::from_secs(31));
assert!(matches!(changed[0].1, OrderState::Unknown { .. }));

// A late ack from the stream or from a reconcile settles it.
let acked = orders.apply_signal(cid, OrderSignal::Ack { broker_id: BrokerOrderId::new("42") }, t0);
assert!(matches!(acked, Some(OrderState::Acked { .. })));

§Memory

A finished order (filled, cancelled, rejected) is kept for a window and then forgotten, so a bot that trades for months does not grow without end. The window is RETENTION (24 hours) and at most RETAINED_FINISHED finished orders, whichever ends first (see with_retention). The guarantee “a cid is placed once” holds inside the window. A cid older than the window is no longer remembered here, so a place with it would pass the local check (the broker may still refuse it: T-Bank does, by its idempotency key). Never reuse a cid: make a new one for every order.

Implementations§

Source§

impl OrderRuntime

Source

pub fn new() -> Self

Creates an empty order book with a command timeout of 30 seconds and the default memory window (RETENTION, RETAINED_FINISHED).

Source

pub fn with_timeout(command_timeout: Duration) -> Self

Creates an empty order book with the given command timeout, the age after which an in-flight order is considered unanswered by sweep_timeouts.

Source

pub fn set_command_timeout(&mut self, command_timeout: Duration)

Changes the command timeout of sweep_timeouts. A connector sets it a little above its request timeout, so that an order is not given up while its call is still running.

Source

pub fn with_retention(self, window: Duration, max_finished: usize) -> Self

Sets the memory window of finished orders: they are forgotten after window, or earlier when more than max_finished are kept (the oldest go first). See the type docs for what this means for the “a cid is placed once” guarantee.

§Examples
use std::time::{Duration, Instant};
use brokapi_core::ClientOrderId;
use brokapi_runtime::OrderRuntime;
use brokapi_sansio::OrderSignal;

let mut orders = OrderRuntime::new().with_retention(Duration::from_secs(60), 1000);
let t0 = Instant::now();
let cid = ClientOrderId::new_v4();
assert!(orders.try_place_once(cid));
orders.register_place(cid, t0);
orders.apply_signal(cid, OrderSignal::Rejected { reason: "no".into() }, t0);

// Inside the window the cid is still refused ...
orders.prune(t0 + Duration::from_secs(30));
assert!(!orders.try_place_once(cid));
// ... after it the order is forgotten.
orders.prune(t0 + Duration::from_secs(61));
assert!(orders.state(cid).is_none());
assert!(orders.try_place_once(cid));
Source

pub fn prune(&mut self, now: Instant)

Forgets the finished orders that are older than the window, or beyond the count limit, as of now. Called by apply_signal itself, so a caller rarely needs it.

Source

pub fn tracked(&self) -> usize

How many orders are tracked now, in any state. For tests and metrics.

Source

pub fn remembered_places(&self) -> usize

How many cids are remembered as used for a place. For tests and metrics.

Source

pub fn in_flight_count(&self) -> usize

How many orders are InFlight now: sent, with no answer yet.

Source

pub fn register_place(&mut self, cid: ClientOrderId, now: Instant)

Starts tracking a place command that is being sent at now, in state InFlight.

Records the first (and only allowed) attempt for cid. Registering a cid again restarts its state machine from InFlight.

Source

pub fn register_cancel(&mut self, cid: ClientOrderId, now: Instant)

Starts tracking a cid known only from a cancel, if it is not tracked yet.

An order placed by an earlier run is not in memory. A cid that is already tracked keeps its state. A new one starts as InFlight, and does not count as a place attempt.

Source

pub fn may_send_place(&mut self, cid: ClientOrderId) -> bool

Returns true and records the attempt when cid has never been used for a place, false when it has.

Why: the library must not retry a place on its own. Compare try_place_once, which does the same.

Source

pub fn try_place_once(&mut self, cid: ClientOrderId) -> bool

Returns true only on the first place attempt for this cid, and records that attempt.

Call it before sending. The attempt is recorded even when the send then fails locally (for example a rate-limit refusal), so the same cid is never used twice. To try again, make a new cid. After register_place the answer is always false. The record is kept for the memory window of the type, see there.

§Examples
use brokapi_core::ClientOrderId;
use brokapi_runtime::OrderRuntime;

let mut orders = OrderRuntime::new();
let cid = ClientOrderId::new_v4();
assert!(orders.try_place_once(cid));
assert!(!orders.try_place_once(cid));
assert!(orders.try_place_once(ClientOrderId::new_v4()));
Source

pub fn apply_signal( &mut self, cid: ClientOrderId, signal: OrderSignal, now: Instant, ) -> Option<OrderState>

Feeds a signal (ack, fill, cancel, reject, timeout) into the order’s state machine.

Returns the new state when the state changed. Returns None when nothing changed, when the cid is not tracked, and when the signal is not valid in the current state (for example a cancel after Filled). An invalid signal is logged at warn level. A caller that needs to tell these apart uses try_apply_signal.

Source

pub fn try_apply_signal( &mut self, cid: ClientOrderId, signal: OrderSignal, now: Instant, ) -> Result<Option<OrderState>, FsmError>

Like apply_signal but tells the reason when the signal is not applied.

§Errors

[FsmError::UnknownOrder] for a cid that is not tracked, [FsmError::InvalidTransition] for a signal that contradicts the state (a cancel of a filled order). The state is left as it was.

§Examples
use std::time::Instant;
use brokapi_core::ClientOrderId;
use brokapi_runtime::OrderRuntime;
use brokapi_sansio::{FsmError, OrderSignal};

let mut orders = OrderRuntime::new();
let t0 = Instant::now();
let cid = ClientOrderId::new_v4();
assert!(matches!(
    orders.try_apply_signal(cid, OrderSignal::Filled, t0),
    Err(FsmError::UnknownOrder(_))
));

// A fill that is faster than the answer to the place command is taken.
orders.register_place(cid, t0);
assert!(orders.try_apply_signal(cid, OrderSignal::Filled, t0).unwrap().is_some());
// A cancel of a filled order contradicts it.
assert!(orders.try_apply_signal(cid, OrderSignal::Cancelled, t0).is_err());
Source

pub fn sweep_timeouts( &mut self, now: Instant, ) -> Vec<(ClientOrderId, OrderState)>

Moves every InFlight order older than the command timeout to Unknown.

Returns the orders that changed, with their new state. It does not cancel or retry anything: the exchange order may well exist.

Source

pub fn state(&self, cid: ClientOrderId) -> Option<&OrderState>

The current state of a tracked order, or None for an unknown cid.

Source

pub fn abandon_inflight(&mut self, now: Instant) -> Vec<ClientOrderId> ⓘ

Moves every InFlight order to Unknown now, whatever its age. Used at shutdown.

Returns the cids that changed. Orders at the broker are untouched, only the local wait for an answer is given up.

Trait Implementations§

Source§

impl Debug for OrderRuntime

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for OrderRuntime

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more