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
reconcilefirst.place,cancel,place_stopandcancel_stopreturnError::NotReconcileduntilreconcilehas 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::Unknownand the library does not repeat it, because a repeat could create a second order. Askorder_stateoractive_ordersto see what happened. Sending the same cid again is refused locally withError::Rejectedand codeduplicate_place. - A refusal is final.
Error::Rejectedmeans 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::Reconnectedon the subscription, the gate closes untilreconcile. - The cid travels in the order
comment(bc1:and 32 hex digits), so it survives a restart. SeeFinamCidCodec. That the broker echoes the comment unchanged is not verified against the live API. - No replace. Finam has no amend call.
replacereturnsError::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_qtybefore anything is sent, when the lot is known (price_grid().set_lot(..)or.learn(¶ms)). - 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§
Source§impl UserDataConnector
impl UserDataConnector
Sourcepub async fn start(
cfg: UserDataConfig,
limiter: Arc<BrokerLimiter>,
out: Sender<Envelope>,
) -> Result<Self, Error>
pub async fn start( cfg: UserDataConfig, limiter: Arc<BrokerLimiter>, out: Sender<Envelope>, ) -> Result<Self, 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 answeredRESOURCE_EXHAUSTEDto 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(_)));Sourcepub fn in_flight_orders(&self) -> usize
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;Sourcepub fn account_id(&self) -> &str
pub fn account_id(&self) -> &str
Sourcepub fn raw(&self) -> FinamRaw
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"));Sourcepub fn price_grid(&self) -> &PriceGrid
pub fn price_grid(&self) -> &PriceGrid
The price steps this connector checks order prices against. Fill it from
TradingParams (grid.learn(¶ms)) to have a price off the
step refused before anything is sent. See [PriceGrid].
Sourcepub fn caps(&self) -> UserDataCaps
pub fn caps(&self) -> UserDataCaps
Capabilities.
place_idempotencyisLabelOnlyuntil a live experiment proves otherwise. Theclient_order_idis a label for us, nothing says Finam rejects or merges a repeat of it.- No replace call exists in the Finam API.
supports_stop_ordersis true: stop-loss and stop-limit go throughPlaceOrder(STOP, STOP_LIMIT), take-profit throughPlaceSLTPOrder, 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);Sourcepub fn sub_status(&self, sub: SubscriptionId) -> Option<SubStatus>
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);Sourcepub async fn shutdown(self, mode: ShutdownMode) -> ShutdownReport
pub async fn shutdown(self, mode: ShutdownMode) -> ShutdownReport
Stops the connector in the order that protects your orders.
- New commands are refused (
Error::ShuttingDown). - Every stream is cancelled (the gRPC way of unsubscribing).
Gracefulwaits for each up todeadline,Immediateaborts them. - Commands still in flight are abandoned and listed in the report (
inflight_abandoned). Their orders may exist at the broker. Read them withactive_ordersafter you start again. - 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§
Source§impl Connector for UserDataConnector
impl Connector for UserDataConnector
Source§fn caps(&self) -> UserDataCaps
fn caps(&self) -> UserDataCaps
Source§fn shutdown<'async_trait>(
self,
mode: ShutdownMode,
) -> Pin<Box<dyn Future<Output = ShutdownReport> + Send + 'async_trait>>where
Self: 'async_trait,
fn shutdown<'async_trait>(
self,
mode: ShutdownMode,
) -> Pin<Box<dyn Future<Output = ShutdownReport> + Send + 'async_trait>>where
Self: 'async_trait,
Error::Unknown], see [ShutdownReport]) and ends its tasks. Takes the connector by value, so it
cannot be used afterwards.Source§impl ExecutionSource for UserDataConnector
impl ExecutionSource for UserDataConnector
Source§fn executions<'life0, 'async_trait>(
&'life0 self,
range: TimeRange,
) -> Pin<Box<dyn Future<Output = Result<Vec<Execution>, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn executions<'life0, 'async_trait>(
&'life0 self,
range: TimeRange,
) -> Pin<Box<dyn Future<Output = Result<Vec<Execution>, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: '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.
Source§fn subscribe_executions<'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_executions<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<SubscriptionId, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: '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.
Source§impl OrderExecutor for UserDataConnector
impl OrderExecutor for UserDataConnector
Source§fn place<'life0, 'async_trait>(
&'life0 self,
req: PlaceOrderRequest,
) -> Pin<Box<dyn Future<Output = Result<OrderAck, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn place<'life0, 'async_trait>(
&'life0 self,
req: PlaceOrderRequest,
) -> Pin<Box<dyn Future<Output = Result<OrderAck, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: '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. Callreconcile. - [
Error::Rejected]: a local check failed (codeinvalid_order,missing_price,invalid_qty), the same cid was sent before (codeduplicate_place), or the broker refused (a gRPC status such asinvalid_argument, or an order status such asrejected). The broker holds no order. - [
Error::RateLimited]: the local quota is spent or the broker saidRESOURCE_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 withorder_stateoractive_orders.
See the type docs for an example with a refusal.
Source§fn cancel<'life0, 'async_trait>(
&'life0 self,
cid: ClientOrderId,
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn cancel<'life0, 'async_trait>(
&'life0 self,
cid: ClientOrderId,
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: '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 (codenot_found). - [
Error::Unknown]: no answer. The cancel may or may not have been applied. Read the order state.
Source§fn replace<'life0, 'async_trait>(
&'life0 self,
_req: ReplaceOrderRequest,
) -> Pin<Box<dyn Future<Output = Result<OrderAck, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn replace<'life0, 'async_trait>(
&'life0 self,
_req: ReplaceOrderRequest,
) -> Pin<Box<dyn Future<Output = Result<OrderAck, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: '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.
Source§fn order_state<'life0, 'async_trait>(
&'life0 self,
cid: ClientOrderId,
) -> Pin<Box<dyn Future<Output = Result<OrderSnapshot, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn order_state<'life0, 'async_trait>(
&'life0 self,
cid: ClientOrderId,
) -> Pin<Box<dyn Future<Output = Result<OrderSnapshot, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: '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 codenot_found: no order with this cid at the broker. After anUnknownplace this means the order did not arrive (or is not visible yet, not verified). - [
Error::RateLimited], [Error::Transport]: as for any read.
Source§fn active_orders<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<OrderSnapshot>, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn active_orders<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<OrderSnapshot>, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: '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.
Source§fn subscribe_order_state<'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_order_state<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<SubscriptionId, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: '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).
Source§fn reconcile<'life0, 'life1, 'async_trait>(
&'life0 self,
ledger: &'life1 [ClientOrderId],
) -> Pin<Box<dyn Future<Output = Result<ReconcileReport, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn reconcile<'life0, 'life1, 'async_trait>(
&'life0 self,
ledger: &'life1 [ClientOrderId],
) -> Pin<Box<dyn Future<Output = Result<ReconcileReport, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: '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.
Source§impl PortfolioSource for UserDataConnector
impl PortfolioSource for UserDataConnector
Source§fn portfolio<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Portfolio, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn portfolio<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Portfolio, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: '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].
Source§fn positions<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<Position>, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn positions<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<Position>, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
The positions of portfolio. One GetAccount request.
Source§fn balances<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<Balance>, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn balances<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<Balance>, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
The balances of portfolio. One GetAccount request.
Source§fn subscribe_portfolio<'life0, 'async_trait>(
&'life0 self,
opts: SubscribeOpts,
) -> Pin<Box<dyn Future<Output = Result<SubscriptionId, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn subscribe_portfolio<'life0, 'async_trait>(
&'life0 self,
opts: SubscribeOpts,
) -> Pin<Box<dyn Future<Output = Result<SubscriptionId, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: '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.
Source§impl StopOrderExecutor for UserDataConnector
impl StopOrderExecutor for UserDataConnector
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,
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,
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_pricefor a stop-limit without a limit price,unsupported_expiryfor a date on a stop-loss,unsupported_limit_pricefor 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 saidRESOURCE_EXHAUSTED. - [
Error::Unknown]: no answer. Look withactive_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.
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,
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,
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].
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,
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,
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.
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,
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,
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§
impl !RefUnwindSafe for UserDataConnector
impl !UnwindSafe for UserDataConnector
impl Freeze for UserDataConnector
impl Send for UserDataConnector
impl Sync for UserDataConnector
impl Unpin for UserDataConnector
impl UnsafeUnpin for UserDataConnector
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request§impl<L> LayerExt<L> for L
impl<L> LayerExt<L> for L
§fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>where
L: Layer<S>,
fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>where
L: Layer<S>,
Layered].