Move 17 library crates into crates/, CLI binary into bin/fxt, consolidate 10 test crates into testing/, split config crate from deployment config files. Root directory reduced from 38+ to ~17 directories. All Cargo.toml paths and build.rs proto refs updated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
130 lines
3.7 KiB
Rust
130 lines
3.7 KiB
Rust
//! 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<ProtoOaPosition>,
|
|
/// Pending orders.
|
|
pub orders: Vec<ProtoOaOrder>,
|
|
}
|
|
|
|
/// Query account trader information.
|
|
pub async fn get_trader_info(
|
|
dispatcher: &MessageDispatcher,
|
|
account_id: i64,
|
|
timeout: Duration,
|
|
) -> Result<TraderInfo> {
|
|
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<ReconcileResult> {
|
|
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,
|
|
})
|
|
}
|