Skip to main content

UserDataConnector

Struct UserDataConnector 

pub struct UserDataConnector { /* private fields */ }
Expand description

Finam user data connector for ONE account: orders, stop orders, own trades and the portfolio.

It implements [OrderExecutor], [StopOrderExecutor], [ExecutionSource] and [PortfolioSource]. To trade two accounts, start two connectors (they may share one token manager and must share one rate limiter).

Rules that protect your money:

  • Call reconcile first. place, cancel, place_stop and cancel_stop return Error::NotReconciled until reconcile has succeeded once. Pass the cids you know from before the restart (an empty list at a clean start).
  • A place command is sent once. If it gets no answer in time, the result is Error::Unknown and the library does not repeat it, because a repeat could create a second order. Ask order_state or active_orders to see what happened. Sending the same cid again is refused locally with Error::Rejected and code duplicate_place.
  • A refusal is final. Error::Rejected means the broker took nothing. Use a NEW cid for the next try.
  • The gate is checked twice. Once at the start, and again when the command’s turn in the queue of its instrument comes: a gate that closed while it waited (Degraded, shutdown) stops it, and nothing is sent.
  • A cut of the order or trade stream is told. Event::Reconnected on the subscription, the gate closes until reconcile.
  • The cid travels in the order comment (bc1: and 32 hex digits), so it survives a restart. See FinamCidCodec. That the broker echoes the comment unchanged is not verified against the live API.
  • No replace. Finam has no amend call. replace returns Error::Unsupported. Cancel plus a new order is not offered, because it is not atomic.
  • Quantity is in pieces (units of the instrument, like at every broker), price in the instrument currency. A quantity that is not a whole number of lots is refused with invalid_qty before anything is sent, when the lot is known (price_grid().set_lot(..) or .learn(&params)).
  • Finam has no sandbox. Test against the mock, then with a tiny order far from the market.

§Examples

Place, look at the order and cancel it. Then a refusal and a stop order. The broker is the in-process imitation, which refuses a limit order at REJECT_PRICE:

use brokapi_conformance::REJECT_PRICE;
use brokapi_core::{
    ClientOrderId, Error, InstrumentId, InstrumentKey, NativeId, OrderExecutor, OrderState, OrderType,
    PlaceOrderRequest, PlaceStopOrderRequest, Price, Qty, ShutdownMode, Side, StopExpiry,
    StopOrderExecutor, StopOrderKind, StopOrderRef,
};
use brokapi_finam::limits::default_limits;
use brokapi_finam::{UserDataConfig, UserDataConnector};
use brokapi_runtime::BrokerLimiter;
use tokio::sync::mpsc;

// `api`: your FinamApiConfig. `account`: the account id, as Finam writes it.
let (events_tx, _events) = mpsc::channel(256);
let limiter = BrokerLimiter::new(&default_limits());
let ud = UserDataConnector::start(UserDataConfig::new(api, account, "finam-ud"), limiter, events_tx).await?;

// Before reconcile every command is refused and nothing is sent.
let sber = InstrumentId::new(
    InstrumentKey::new("SBER", "MISX"),
    NativeId::SymbolMic { symbol: "SBER".into(), mic: "MISX".into() },
);
let limit_buy = |price: Price| PlaceOrderRequest {
    cid: ClientOrderId::new_v4(),
    instrument: sber.clone(),
    side: Side::Buy,
    order_type: OrderType::Limit,
    qty: Qty::from(1),
    price: Some(price),
};
assert!(matches!(ud.place(limit_buy(Price::from(1))).await, Err(Error::NotReconciled)));

ud.reconcile(&[]).await?;

// Place, read back, cancel.
let req = limit_buy(Price::from(1));
let cid = req.cid;
let ack = ud.place(req).await?;
assert_eq!(ack.cid, cid);
let snapshot = ud.order_state(cid).await?;
assert!(!matches!(snapshot.state, OrderState::Rejected { .. }));
ud.cancel(cid).await?;

// The broker says no: a clean refusal. Nothing is open at the broker. Use a new cid to try again.
match ud.place(limit_buy(REJECT_PRICE)).await {
    Err(Error::Rejected { code, .. }) => assert_eq!(code, "invalid_argument"),
    other => panic!("expected a refusal, got {other:?}"),
}

// A stop-loss: a SELL that triggers when the price falls to 100.
let stop = PlaceStopOrderRequest {
    cid: ClientOrderId::new_v4(),
    instrument: sber,
    side: Side::Sell,
    kind: StopOrderKind::StopLoss,
    qty: Qty::from(5),
    stop_price: Price::from(100),
    limit_price: None,
    expiry: StopExpiry::GoodTillCancel,
};
let stop_cid = stop.cid;
ud.place_stop(stop).await?;
assert_eq!(ud.active_stop_orders().await?.len(), 1);
ud.cancel_stop(StopOrderRef::Cid(stop_cid)).await?;
assert!(ud.active_stop_orders().await?.is_empty());

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

Own orders as events. The connector keeps a lossless buffer for this subscription, so no order event is lost. When the buffer overflows, Event::Overflow arrives and trading is refused until reconcile:

use std::time::Duration;
use brokapi_core::{
    ClientOrderId, Event, InstrumentId, InstrumentKey, NativeId, OrderExecutor, OrderType,
    PlaceOrderRequest, Price, Qty, ShutdownMode, Side,
};
use brokapi_finam::limits::default_limits;
use brokapi_finam::{UserDataConfig, UserDataConnector};
use brokapi_runtime::BrokerLimiter;
use tokio::sync::mpsc;

let (events_tx, mut events) = mpsc::channel(256);
let ud = UserDataConnector::start(
    UserDataConfig::new(api, "ACC1", "finam-ud"),
    BrokerLimiter::new(&default_limits()),
    events_tx,
)
.await?;
ud.reconcile(&[]).await?;
let sub = ud.subscribe_order_state().await?;

let cid = ClientOrderId::new_v4();
ud.place(PlaceOrderRequest {
    cid,
    instrument: InstrumentId::new(
        InstrumentKey::new("SBER", "MISX"),
        NativeId::SymbolMic { symbol: "SBER".into(), mic: "MISX".into() },
    ),
    side: Side::Buy,
    order_type: OrderType::Limit,
    qty: Qty::from(1),
    price: Some(Price::from(1)),
})
.await?;

// Wait for the event of OUR order, with a timeout, never a sleep.
let ours = tokio::time::timeout(Duration::from_secs(5), async {
    while let Some(envelope) = events.recv().await {
        if let Event::Order(order) = envelope.payload {
            if order.cid == cid {
                return Some(envelope.sub);
            }
        }
    }
    None
})
.await?;
assert_eq!(ours, Some(Some(sub)));

ud.cancel(cid).await?;
ud.shutdown(ShutdownMode::Immediate).await;

Implementations§

§

impl UserDataConnector

pub async fn start( cfg: UserDataConfig, limiter: Arc<BrokerLimiter>, out: Sender<Envelope>, ) -> Result<UserDataConnector, Error>

Checks the config and the cid codec, logs in, and starts the event pump. Streams are opened later, by the subscribe_* methods.

The connector starts NOT reconciled: commands are refused until you call reconcile. limiter is the rate limiter of your Finam user, shared by every connector. Events go to out.

When the token lists its accounts and cfg.account_id is not one of them, start fails.

§Errors
  • [Error::Config]: empty account id, empty secret, invalid endpoint URL, the cid codec does not fit the broker limits, or the account is not available to this secret.
  • [Error::Config] also: the broker refused the secret (a wrong token is a configuration problem at every broker).
  • [Error::RateLimited]: the broker answered RESOURCE_EXHAUSTED to the login.
  • [Error::Transport]: the broker could not be reached.
§Examples
use brokapi_core::{Error, ShutdownMode};
use brokapi_finam::limits::default_limits;
use brokapi_finam::{FinamApiConfig, UserDataConfig, UserDataConnector};
use brokapi_runtime::BrokerLimiter;
use tokio::sync::mpsc;

let limiter = BrokerLimiter::new(&default_limits());
let (events, _rx) = mpsc::channel(256);

let ud = UserDataConnector::start(UserDataConfig::new(api.clone(), "ACC1", "finam-ud"), limiter.clone(), events.clone()).await?;
assert_eq!(ud.account_id(), "ACC1");
ud.shutdown(ShutdownMode::Immediate).await;

// An account that the secret cannot reach is a configuration error.
let err = UserDataConnector::start(UserDataConfig::new(api, "NOT-MINE", "finam-ud"), limiter, events).await.err().unwrap();
assert!(matches!(err, Error::Config(_)));

pub fn in_flight_orders(&self) -> usize

How many commands are in flight: sent, and the answer has not been taken yet. Zero when all of them are settled. A place runs to its end even if the caller drops its future, so this number goes back to zero by itself. For health checks and tests.

§Examples
use brokapi_finam::limits::default_limits;
use brokapi_finam::{FinamApiConfig, UserDataConfig, UserDataConnector};
use brokapi_core::ShutdownMode;
use brokapi_runtime::BrokerLimiter;

let (events_tx, _events) = tokio::sync::mpsc::channel(16);
let ud = UserDataConnector::start(UserDataConfig::new(api, "ACC1", "finam-ud"), BrokerLimiter::new(&default_limits()), events_tx).await?;
assert_eq!(ud.in_flight_orders(), 0);
ud.shutdown(ShutdownMode::Immediate).await;

pub fn account_id(&self) -> &str

Account this connector trades and reads.

§Examples

See start.

pub fn raw(&self) -> FinamRaw

Any other method of the Finam API with this connector’s token, endpoint and rate limiter. account_id() of the result is this connector’s account. See FinamRaw.

Orders placed through raw() are not tracked by this connector: no events, no reconcile. Use the shared traits for orders.

§Examples
use brokapi_core::ShutdownMode;
use brokapi_finam::limits::default_limits;
use brokapi_finam::{FinamApiConfig, UserDataConfig, UserDataConnector};
use brokapi_runtime::BrokerLimiter;
use tokio::sync::mpsc;

let ud = UserDataConnector::start(UserDataConfig::new(api, "ACC1", "finam-ud"), BrokerLimiter::new(&default_limits()), events).await?;
assert_eq!(ud.raw().account_id(), Some("ACC1"));

pub fn price_grid(&self) -> &PriceGrid

The price steps this connector checks order prices against. Fill it from TradingParams (grid.learn(&params)) to have a price off the step refused before anything is sent. See [PriceGrid].

pub fn caps(&self) -> UserDataCaps

Capabilities.

  • place_idempotency is LabelOnly until a live experiment proves otherwise. The client_order_id is a label for us, nothing says Finam rejects or merges a repeat of it.
  • No replace call exists in the Finam API.
  • supports_stop_orders is true: stop-loss and stop-limit go through PlaceOrder (STOP, STOP_LIMIT), take-profit through PlaceSLTPOrder, see [StopOrderExecutor]. Not verified against the live API.
  • Executions stream (SubscribeTrades) exists. Portfolio also streams (SubscribeAccount).
  • Finam Trade API has no sandbox.
§Examples
use brokapi_core::{PlaceIdempotency, ShutdownMode};
use brokapi_finam::limits::default_limits;
use brokapi_finam::{FinamApiConfig, UserDataConfig, UserDataConnector};
use brokapi_runtime::BrokerLimiter;
use tokio::sync::mpsc;

let caps = ud.caps();
assert!(!caps.supports_replace);
assert!(caps.supports_stop_orders);
assert_eq!(caps.place_idempotency, PlaceIdempotency::LabelOnly);

pub fn sub_status(&self, sub: SubscriptionId) -> Option<SubStatus>

State of a subscription’s stream (connecting, active, reconnecting, failed).

None for an id this connector does not know. Use it after subscribe_* returned and the stream may still be opening (Finam may send the headers of a quiet stream only with its first message). A stream that the broker refuses (no permission) shows as SubStatus::Failed and is retried slowly.

§Examples
use std::time::Duration;
use brokapi_core::{OrderExecutor, ShutdownMode};
use brokapi_finam::limits::default_limits;
use brokapi_finam::{FinamApiConfig, SubStatus, UserDataConfig, UserDataConnector};
use brokapi_runtime::BrokerLimiter;
use tokio::sync::mpsc;

let sub = ud.subscribe_order_state().await?;
// Registered at once. Active when the stream is open.
let status = tokio::time::timeout(Duration::from_secs(5), async {
    loop {
        match ud.sub_status(sub) {
            Some(SubStatus::Active) => return SubStatus::Active,
            _ => tokio::time::sleep(Duration::from_millis(20)).await,
        }
    }
})
.await?;
assert_eq!(status, SubStatus::Active);

pub async fn shutdown(self, mode: ShutdownMode) -> ShutdownReport

Stops the connector in the order that protects your orders.

  1. New commands are refused (Error::ShuttingDown).
  2. Every stream is cancelled (the gRPC way of unsubscribing). Graceful waits for each up to deadline, Immediate aborts them.
  3. Commands still in flight are abandoned and listed in the report (inflight_abandoned). Their orders may exist at the broker. Read them with active_orders after you start again.
  4. The event pump stops.

Orders already on the exchange stay there. Shutdown does NOT cancel them.

§Examples
use std::time::Duration;
use brokapi_core::{Error, OrderExecutor, ShutdownMode};
use brokapi_finam::limits::default_limits;
use brokapi_finam::{FinamApiConfig, UserDataConfig, UserDataConnector};
use brokapi_runtime::BrokerLimiter;
use tokio::sync::mpsc;

let ud = UserDataConnector::start(UserDataConfig::new(api, "ACC1", "finam-ud"), BrokerLimiter::new(&default_limits()), events).await?;
ud.reconcile(&[]).await?;
ud.subscribe_order_state().await?;

let report = ud.shutdown(ShutdownMode::Graceful { deadline: Duration::from_secs(2) }).await;
assert!(report.inflight_abandoned.is_empty());

Trait Implementations§

§

impl Connector for UserDataConnector

§

type Caps = UserDataCaps

The type that describes what this connector can do.
§

fn caps(&self) -> UserDataCaps

What this connector can do. Read it before you use a feature that not every broker has.
§

fn shutdown<'async_trait>( self, mode: ShutdownMode, ) -> Pin<Box<dyn Future<Output = ShutdownReport> + Send + 'async_trait>>
where UserDataConnector: 'async_trait,

Stops the connector: closes its streams, gives up commands that have no answer yet (they become [Error::Unknown], see [ShutdownReport]) and ends its tasks. Takes the connector by value, so it cannot be used afterwards.
§

impl ExecutionSource for UserDataConnector

§

fn executions<'life0, 'async_trait>( &'life0 self, range: TimeRange, ) -> Pin<Box<dyn Future<Output = Result<Vec<Execution>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, UserDataConnector: 'async_trait,

Own trades in range (Trades), read page by page until the period is complete.

One Trades request returns at most executions_limit trades (1000 by default). When an answer is full, the connector asks again for the part of the period that is still open (from the time of the last trade it got, in the direction the answer is sorted) and keeps each trade once by its trade id. Every page spends quota of Trades. When the period still does not fit (more than executions_limit trades with the same timestamp, or more than 100 pages) the answer is [Error::Rejected] with the code range_too_long, never a silently cut list: read a shorter range. That Finam sorts the answer by time and cuts it at the limit is not verified against the live API, the paging works for both directions of sort.

The commission is in the account currency, its currency code is not part of Execution. Only historical trades carry a commission.

§Errors

[Error::RateLimited], [Error::Rejected] (including range_too_long) or [Error::Transport], as for any read.

§

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

Opens a SubscribeTrades stream for the account. Envelopes carry Event::Execution, with the cid when the order is ours. Lossless buffer, errors as in subscribe_order_state.

§

impl OrderExecutor for UserDataConnector

§

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

Sends a PlaceOrder. The request is checked locally first, then the quota is taken, then the command goes out once.

The cid goes into the order comment (lossless) and into a 20 character client_order_id tag. Quantity is in pieces. A market order ignores price, a limit order needs it.

Commands on one instrument go one at a time, in arrival order. A refusal does not block the instrument.

§Errors
  • [Error::NotReconciled], [Error::Degraded], [Error::ShuttingDown]: the command gate is closed, at the start or when the command’s turn in the queue came. Nothing was sent. Call reconcile.
  • [Error::Rejected]: a local check failed (code invalid_order, missing_price, invalid_qty), the same cid was sent before (code duplicate_place), or the broker refused (a gRPC status such as invalid_argument, or an order status such as rejected). The broker holds no order.
  • [Error::RateLimited]: the local quota is spent or the broker said RESOURCE_EXHAUSTED. Nothing was sent. The cid is still unused: send the same request later.
  • [Error::Config]: the broker refused the token (UNAUTHENTICATED). Nothing was done, fix the secret.
  • [Error::Unknown]: timeout, broken connection or server fault, or the answer carried no order id. The order MAY exist. It is never repeated by the library. Look it up with order_state or active_orders.

See the type docs for an example with a refusal.

§

fn cancel<'life0, 'async_trait>( &'life0 self, cid: Uuid, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, UserDataConnector: 'async_trait,

Sends a CancelOrder for the order of cid.

Finam cancels by its own order id. The connector knows the id of orders it placed or has seen (stream, reconcile). For any other cid it reads the order list once (GetOrders, one more request against the quota) and looks the cid up in the comments.

A cancel is a request, not a result: the order may fill before the cancel reaches the exchange. Watch the order state.

§Errors
  • [Error::NotReconciled], [Error::Degraded], [Error::ShuttingDown]: the gate is closed.
  • [Error::RateLimited]: the local quota is spent. Nothing was sent.
  • [Error::Rejected]: the broker refused, or no order with this cid exists at the broker (code not_found).
  • [Error::Unknown]: no answer. The cancel may or may not have been applied. Read the order state.
§

fn replace<'life0, 'async_trait>( &'life0 self, _req: ReplaceOrderRequest, ) -> Pin<Box<dyn Future<Output = Result<OrderAck, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, UserDataConnector: 'async_trait,

Finam has no replace/amend call, so this is Unsupported (and caps().supports_replace is false). Cancel + place is not emulated here: it is not atomic. The client decides whether to risk it.

§Errors

Always [Error::Unsupported] (capability replace_order). Nothing is sent.

§

fn order_state<'life0, 'async_trait>( &'life0 self, cid: Uuid, ) -> Pin<Box<dyn Future<Output = Result<OrderSnapshot, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, UserDataConnector: 'async_trait,

Reads one order (GetOrder) and reports its state under cid. This is the way to resolve an [Error::Unknown] from place or cancel.

The order id is found as in cancel: a cid that was never seen costs one GetOrders.

§Errors
  • [Error::Rejected] with code not_found: no order with this cid at the broker. After an Unknown place this means the order did not arrive (or is not visible yet, not verified).
  • [Error::RateLimited], [Error::Transport]: as for any read.
§

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

Orders of the account that are still working (GetOrders, filtered). Stop orders are not in this list, they have active_stop_orders.

An order placed by someone else (the web terminal, another program) is listed under a made-up cid. A status the library does not know counts as working, so an order is never hidden.

§Errors

[Error::RateLimited], [Error::Rejected] or [Error::Transport], as for any read.

§

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

Opens a SubscribeOrders stream for the account. Envelopes carry Event::Order.

The buffer is lossless (size order_state_buffer): when it overflows the connector turns degraded and refuses commands until reconcile. Errors as in subscribe_* of the market data connector: [Error::Rejected] for a refusal or for too many streams (code subscription_quota).

§

fn reconcile<'life0, 'life1, 'async_trait>( &'life0 self, ledger: &'life1 [Uuid], ) -> Pin<Box<dyn Future<Output = Result<ReconcileReport, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, UserDataConnector: 'async_trait,

Ledger cids are matched against the account’s order list. A ledger cid the broker does not list is reported in closed_offline. An order the broker lists that the ledger does not know is foreign (only orders still working are looked at for this). Success opens the command gate and clears Degraded.

Stop orders are not part of it: restore them with active_stop_orders, and keep their cids out of ledger.

Call it once after start (an empty ledger at a clean start), and again after Event::Overflow or after an Unknown outcome you could not resolve. It fills the cid index, so later cancels of old orders find their broker ids.

§Errors

[Error::RateLimited], [Error::Rejected] or [Error::Transport] from the order list request. On error the gate stays as it was.

§

impl PortfolioSource for UserDataConnector

§

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

The account state (GetAccount): one balance per currency and one position per holding.

Quantity is signed (short is negative). Finam reports no blocked amount per currency, so blocked is 0. The average price is missing for futures positions.

Theoretical positions. The broker’s list of positions holds the open ones plus the theoretical ones that active orders would create (“открытые, плюс теоретические (по неисполненным активным заявкам)” in its description of GetAccount). The answer has no field that tells the two apart, so the connector cannot filter them out or mark them. A working buy order for 10 shares you do not hold can show as a position of 10. Do not size an order from this list while orders are working: take the position from your own fills ([ExecutionSource]) or look at it when active_orders is empty. Not verified against the live API how the broker prices and prints such a position.

§Errors

[Error::Rejected] (unknown account), [Error::RateLimited], [Error::Transport].

§

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

The positions of portfolio. One GetAccount request.

§

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

The balances of portfolio. One GetAccount request.

§

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

Streams SubscribeAccount: every message is a full account state (Event::Portfolio).

Each event replaces the previous one. opts is your choice of buffer policy: Conflate keeps only the latest state, which is what you usually want here.

§

impl StopOrderExecutor for UserDataConnector

§

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

Stop-loss and stop-limit: PlaceOrder (STOP / STOP_LIMIT). Take-profit: PlaceSLTPOrder with the take-profit leg only. The cid travels in comment (lossless) and in the client_order_id tag, like for plain orders.

The trigger direction follows the side and the kind: a sell stop-loss triggers when the price falls, a buy stop-limit when it rises. Checks that need no network run first. The cid is sent once: after a definite refusal it is free again, after Unknown it stays used.

§Errors
  • [Error::NotReconciled], [Error::Degraded], [Error::ShuttingDown]: the gate is closed.
  • [Error::Rejected]: a local check failed (invalid_price, invalid_order, missing_limit_price for a stop-limit without a limit price, unsupported_expiry for a date on a stop-loss, unsupported_limit_price for a take-profit with a limit price), the cid was sent before (duplicate_place), or the broker refused.
  • [Error::RateLimited]: the local quota is spent or the broker said RESOURCE_EXHAUSTED.
  • [Error::Unknown]: no answer. Look with active_stop_orders.

That the live broker accepts these orders the way the mock does is not verified against the live API.

See the type docs for an example.

§

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

CancelOrder with the stop order’s id. That Finam cancels stop orders through the same call is an assumption from the API layout, not verified against the live API.

StopOrderRef::Broker uses the id as given. StopOrderRef::Cid looks the id up like cancel does (one extra GetOrders for a cid not seen yet).

§Errors

As for cancel: [Error::NotReconciled] and its siblings, [Error::RateLimited], [Error::Rejected] (including code not_found), [Error::Unknown].

§

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

Stop orders that are waiting for their trigger (GetOrders, filtered to stop orders in the active state).

cid is Some only for orders this library placed. A stop order set by hand has none, and can still be cancelled by its broker id.

§Errors

[Error::RateLimited], [Error::Rejected] or [Error::Transport], as for any read.

§

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

No separate stream: SubscribeOrders delivers orders, and whether stop orders are among them is not verified against the live API. Use subscribe_order_state.

§Errors

Always [Error::Unsupported] (capability stop_order_stream).

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
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

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.

§

impl<T> IntoRequest<T> for T

§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
§

impl<L> LayerExt<L> for L

§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in [Layered].
§

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

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
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<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

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