Skip to main content

StopOrderExecutor

Trait StopOrderExecutor 

Source
pub trait StopOrderExecutor: Send + Sync {
    // Required methods
    fn place_stop<'life0, 'async_trait>(
        &'life0 self,
        req: PlaceStopOrderRequest,
    ) -> Pin<Box<dyn Future<Output = Result<OrderAck, Error>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait;
    fn cancel_stop<'life0, 'async_trait>(
        &'life0 self,
        stop: StopOrderRef,
    ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait;
    fn active_stop_orders<'life0, 'async_trait>(
        &'life0 self,
    ) -> Pin<Box<dyn Future<Output = Result<Vec<StopOrderSnapshot>, Error>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait;
    fn subscribe_stop_orders<'life0, 'async_trait>(
        &'life0 self,
    ) -> Pin<Box<dyn Future<Output = Result<SubscriptionId, Error>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait;
}
Expand description

Stop orders, take-profit and stop-limit - an extra capability next to OrderExecutor (UserDataCaps::supports_stop_orders tells whether the broker has it).

A connector without stop orders implements the trait and answers Error::Unsupported to every method. The command gate of the connector (NotReconciled, Degraded, shutdown) applies to place_stop and cancel_stop like to place and cancel, and a stop order that got no answer is Error::Unknown, never repeated by the library. A stop order waits at the broker and is not an order on the exchange until the price crosses the trigger, see StopOrderKind.

§Examples

use brokapi_bcs::limits::default_limits;
use brokapi_bcs::{UserDataConfig, UserDataConnector};
use brokapi_core::{
    ClientOrderId, Error, InstrumentId, InstrumentKey, NativeId, OrderExecutor,
    PlaceStopOrderRequest, Price, Qty, ShutdownMode, Side, StopExpiry, StopOrderExecutor,
    StopOrderKind, StopOrderRef, StopOrderState,
};
use brokapi_runtime::BrokerLimiter;

let (events_tx, _events) = tokio::sync::mpsc::channel(256);
let limiter = BrokerLimiter::new(&default_limits());
let ud = UserDataConnector::start(UserDataConfig::new(cfg, "bcs-ud"), limiter, events_tx).await?;
ud.reconcile(&[]).await?; // stop orders are behind the same gate as plain orders
assert!(ud.caps().supports_stop_orders);

let sber = InstrumentId::new(
    InstrumentKey::new("SBER", "TQBR"),
    NativeId::TickerClass { ticker: "SBER".into(), class_code: "TQBR".into() },
);

// A stop-loss: sell 1 if the price falls to 200, far below the market so it only waits.
let cid = ClientOrderId::new_v4();
let ack = ud
    .place_stop(PlaceStopOrderRequest {
        cid,
        instrument: sber.clone(),
        side: Side::Sell,
        kind: StopOrderKind::StopLoss,
        qty: Qty::from(1),
        stop_price: Price::from(200),
        limit_price: None, // market order at the trigger
        expiry: StopExpiry::GoodTillCancel,
    })
    .await?;

// It is listed with its parameters while it waits.
let waiting = ud.active_stop_orders().await?;
let mine = waiting.iter().find(|s| s.broker_id == ack.broker_id).expect("listed");
assert_eq!(mine.state, StopOrderState::Active);

// Cancel it by the cid we gave. `StopOrderRef::Broker` names it by the broker's id instead.
ud.cancel_stop(StopOrderRef::Cid(cid)).await?;

// A stop-limit needs a limit price. Without it the request is refused before anything is sent.
let bad = PlaceStopOrderRequest {
    cid: ClientOrderId::new_v4(),
    instrument: sber,
    side: Side::Sell,
    kind: StopOrderKind::StopLimit,
    qty: Qty::from(1),
    stop_price: Price::from(200),
    limit_price: None,
    expiry: StopExpiry::GoodTillCancel,
};
assert!(matches!(ud.place_stop(bad).await, Err(Error::Rejected { .. })));

ud.shutdown(ShutdownMode::Immediate).await;

Required Methods§

Source

fn place_stop<'life0, 'async_trait>( &'life0 self, req: PlaceStopOrderRequest, ) -> Pin<Box<dyn Future<Output = Result<OrderAck, Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Puts a stop order and waits for the broker’s decision.

The cid is the same kind of UUID as for plain orders and follows the same rule: it is sent at most once, a repeat is refused with Error::Rejected (BCS: duplicate_place). Ok means the broker accepted the stop order, it is waiting for its trigger. The library checks the shape of the request (a stop-limit needs limit_price) and nothing else: the trigger direction against the market price is the broker’s to judge.

§Contract
§Errors
  • Error::Unsupported when the broker has no stop orders. Nothing was sent.
  • Error::NotReconciled, Error::Degraded, Error::ShuttingDown: the gate is closed, nothing was sent.
  • Error::Rejected when the request is wrong or the broker says no: a stop-limit without a limit price, a repeated cid, an expiry the broker does not have for this kind.
  • Error::RateLimited: not executed, wait retry_after. Use a new cid for the retry.
  • Error::Unknown when the request may have reached the broker and there is no answer. Do NOT place again. Look in active_stop_orders: a stop order with your cid is yours. A stop order with cid: None on the same instrument and side may be yours too, when the connector could not tell for sure (T-Bank does not return the cid and links it only when exactly one stop order fits). Then do not place another one under a new cid either. Only an empty list means that nothing is at the broker (look a second time after a few seconds first: a slow broker takes the order late), and then a new cid is safe.
  • Error::Transport when the failure happened before the request left.
§Examples

See the example of the trait.

Source

fn cancel_stop<'life0, 'async_trait>( &'life0 self, stop: StopOrderRef, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Cancels a stop order that has not triggered.

Name it by the cid you gave (StopOrderRef::Cid) or by the broker’s id (StopOrderRef::Broker). A cid is known from what this process has seen. After a restart a connector may not be able to find it, use the broker id then (from active_stop_orders).

§Contract
  • When: while the gate is open. After a restart name the stop by the broker’s id.
  • Guarantee: a repeated cancel creates nothing. After Error::Unknown look in active_stop_orders first.
§Errors
§Examples

See the example of the trait.

Source

fn active_stop_orders<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Vec<StopOrderSnapshot>, Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Lists the stop orders that wait for their trigger.

cid of an entry is None when the connector cannot tie the broker’s stop order to a cid you gave (placed by someone else, or placed before this process started and not echoed). Use broker_id then.

§Errors
§Examples

See the example of the trait.

Source

fn subscribe_stop_orders<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<SubscriptionId, Error>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Stream of stop order statuses. Unsupported where the broker has no such stream.

The BCS connector has none: its order events channel is documented for plain orders only. Poll active_stop_orders instead. The library does not poll for you.

§Errors
§Examples
use brokapi_core::{Error, StopOrderExecutor};
match ud.subscribe_stop_orders().await {
    Err(Error::Unsupported { capability }) => assert_eq!(capability, "stop_order_stream"),
    other => panic!("BCS has no stop order stream, got {other:?}"),
}

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§