Skip to main content

OrderFsm

Struct OrderFsm 

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

The life of one order as a finite state machine over [OrderState].

The order starts InFlight (the place command is on the wire). A broker answer moves it on. If no answer comes, OrderSignal::Timeout moves it to Unknown. Unknown is not a failure: the order may exist at the broker, and a later ack, fill or cancel (from the stream or from a reconcile) settles it. The command is never repeated, because a retry could create a second order.

The machine takes the order in which news really arrives. The stream of fills can be faster than the answer to the place command, so a fill or a cancel is valid in InFlight too.

StateSignal -> new state
InFlightPlaceSent -> no change, Ack -> Acked, PartialFill -> PartiallyFilled, Filled, Cancelled, Rejected, Timeout -> Unknown
UnknownAck -> Acked, PartialFill -> PartiallyFilled, Filled, Cancelled, Rejected, Timeout -> no change
AckedPartialFill -> PartiallyFilled, Filled, Cancelled, Rejected, Ack -> no change (late repeat)
PartiallyFilledPartialFill -> PartiallyFilled (new quantities, but a smaller filled than known is a stale snapshot and changes nothing), Filled, Cancelled, Rejected, Ack -> no change
Filled, Cancelled, Rejectedthe same final state again, and a late Ack or PartialFill -> no change. A different final state (a cancel of a filled order) -> FsmError::InvalidTransition

News that is late or repeated but agrees with what is known changes nothing (Ok(None)). News that contradicts it is an error and the state is left as it was (the order book of brokapi-runtime logs it at warn level).

§Examples

use std::time::Instant;
use brokapi_core::{BrokerOrderId, ClientOrderId, OrderState};
use brokapi_sansio::{FsmError, OrderFsm, OrderSignal};

let cid = ClientOrderId::new_v4();
let now = Instant::now();
let mut fsm = OrderFsm::new(cid, now);
assert!(matches!(fsm.state(), OrderState::InFlight { .. }));

// No answer in time: the outcome is unknown.
let next = fsm.apply(OrderSignal::Timeout, now)?;
assert!(matches!(next, Some(OrderState::Unknown { .. })));

// A second timeout changes nothing: Ok(None).
assert_eq!(fsm.apply(OrderSignal::Timeout, now), Ok(None));

// The stream later shows the order. It was alive all along.
let next = fsm.apply(OrderSignal::Ack { broker_id: BrokerOrderId::new("42") }, now)?;
assert!(matches!(next, Some(OrderState::Acked { .. })));

fsm.apply(OrderSignal::Filled, now)?;
// A finished order accepts nothing that contradicts it.
assert!(matches!(
    fsm.apply(OrderSignal::Cancelled, now),
    Err(FsmError::InvalidTransition { from: "Filled", signal: "Cancelled" })
));

Implementations§

Source§

impl OrderFsm

Source

pub fn new(cid: ClientOrderId, now: Instant) -> Self

Starts tracking an order whose place command is sent at now, in state InFlight.

Source

pub fn from_state(state: OrderState) -> Self

Starts from a known state, for example an order read back from the broker.

Source

pub fn state(&self) -> &OrderState

The current state.

Source

pub fn cid(&self) -> ClientOrderId

The client order id, the same in every state.

Source

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

Applies a signal at now.

Returns Ok(Some(new_state)) when the state changed, and Ok(None) when the signal is valid but changes nothing (a repeated Timeout in Unknown, PlaceSent in InFlight, a PartialFill with the same quantities). now is stored in the Unknown state as the time the doubt began.

§Errors

FsmError::InvalidTransition when the signal is not valid in the current state, see the table in the type docs. The state is left unchanged.

Trait Implementations§

Source§

impl Clone for OrderFsm

Source§

fn clone(&self) -> OrderFsm

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for OrderFsm

Source§

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

Formats the value using the given formatter. 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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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.