//! Account information and position reconciliation. use std::time::Duration; use prost::Message; use tracing::info; use crate::dispatch::MessageDispatcher; use crate::error::{CTraderError, Result}; use crate::proto::{self, ProtoMessage, ProtoOaOrder, ProtoOaPosition, ProtoOaTrader}; /// Account trader information. #[derive(Debug, Clone)] pub struct TraderInfo { /// Account balance in deposit currency (cents / base units). pub balance: i64, /// Deposit currency asset ID. pub deposit_asset_id: i64, /// Leverage in cents (e.g. 10000 = 1:100). pub leverage_in_cents: u32, /// Registration timestamp (Unix ms). pub registration_timestamp: i64, /// Raw trader proto for additional fields. pub raw: ProtoOaTrader, } /// Result of a position/order reconciliation. #[derive(Debug, Clone)] pub struct ReconcileResult { /// Open positions. pub positions: Vec, /// Pending orders. pub orders: Vec, } /// Query account trader information. pub async fn get_trader_info( dispatcher: &MessageDispatcher, account_id: i64, timeout: Duration, ) -> Result { info!("querying trader info"); let req = proto::ProtoOaTraderReq { payload_type: Some(proto::PT_TRADER_REQ as i32), ctid_trader_account_id: account_id, }; let envelope = ProtoMessage { payload_type: proto::PT_TRADER_REQ, payload: Some(req.encode_to_vec()), client_msg_id: None, }; let resp = dispatcher.send_request(envelope, timeout).await?; if resp.payload_type != proto::PT_TRADER_RES { return Err(CTraderError::ProtocolError(format!( "expected TraderRes ({}), got {}", proto::PT_TRADER_RES, resp.payload_type ))); } let trader_res = proto::ProtoOaTraderRes::decode( resp.payload .as_deref() .ok_or_else(|| CTraderError::ProtocolError("empty TraderRes".into()))?, )?; // `trader` is a required field in the proto — prost gives it directly let trader = trader_res.trader; Ok(TraderInfo { balance: trader.balance, deposit_asset_id: trader.deposit_asset_id, leverage_in_cents: trader.leverage_in_cents.unwrap_or(0), registration_timestamp: trader.registration_timestamp.unwrap_or(0), raw: trader, }) } /// Reconcile open positions and pending orders. pub async fn reconcile( dispatcher: &MessageDispatcher, account_id: i64, timeout: Duration, ) -> Result { info!("reconciling positions and orders"); let req = proto::ProtoOaReconcileReq { payload_type: Some(proto::PT_RECONCILE_REQ as i32), ctid_trader_account_id: account_id, return_protection_orders: Some(false), }; let envelope = ProtoMessage { payload_type: proto::PT_RECONCILE_REQ, payload: Some(req.encode_to_vec()), client_msg_id: None, }; let resp = dispatcher.send_request(envelope, timeout).await?; if resp.payload_type != proto::PT_RECONCILE_RES { return Err(CTraderError::ProtocolError(format!( "expected ReconcileRes ({}), got {}", proto::PT_RECONCILE_RES, resp.payload_type ))); } let reconcile_res = proto::ProtoOaReconcileRes::decode( resp.payload .as_deref() .ok_or_else(|| CTraderError::ProtocolError("empty ReconcileRes".into()))?, )?; info!( positions = reconcile_res.position.len(), orders = reconcile_res.order.len(), "reconcile complete" ); Ok(ReconcileResult { positions: reconcile_res.position, orders: reconcile_res.order, }) }