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§
Sourcefn 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 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
- When: after the first successful
OrderExecutor::reconcile, same gate asOrderExecutor::place. Writereq.cidto your journal first. - Guarantee: at most one request with this cid ever leaves the process. After
Error::Unknownlook inactive_stop_orders, do not place again.
§Errors
Error::Unsupportedwhen the broker has no stop orders. Nothing was sent.Error::NotReconciled,Error::Degraded,Error::ShuttingDown: the gate is closed, nothing was sent.Error::Rejectedwhen 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, waitretry_after. Use a new cid for the retry.Error::Unknownwhen the request may have reached the broker and there is no answer. Do NOT place again. Look inactive_stop_orders: a stop order with your cid is yours. A stop order withcid: Noneon 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::Transportwhen the failure happened before the request left.
§Examples
See the example of the trait.
Sourcefn 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 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::Unknownlook inactive_stop_ordersfirst.
§Errors
Error::Unsupportedwhen the broker has no stop orders.Error::NotReconciled,Error::Degraded,Error::ShuttingDown: the gate is closed.Error::Rejectedwhen the broker refuses: the stop order is unknown (BCS:not_found), or it already triggered.Error::RateLimited: not executed, waitretry_after.Error::Unknownwhen the answer is missing or leaves the stop order alive. Look inactive_stop_ordersbefore you decide.Error::Transportwhen the failure happened before the request left.
§Examples
See the example of the trait.
Sourcefn 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 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
Error::Unsupportedwhen the broker has no stop orders.Error::RateLimited,Error::Transport: wait or repeat later.
§Examples
See the example of the trait.
Sourcefn 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,
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
Error::Unsupportedwhen the broker has no such stream. BCS: capabilitystop_order_stream.Error::Transportwhen the subscription could not be made.
§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".