use crate::{
UnindexedAccountSnapshot,
balance::AssetBalance,
exchange::mock::orders::{OpenOrders, as_open},
order::{
Order,
id::ClientOrderId,
state::{Cancelled, Expired, InactiveOrderState, Open, OrderState},
},
trade::Trade,
};
use chrono::{DateTime, Utc};
use fnv::{FnvHashMap, FnvHashSet};
use rust_decimal::Decimal;
use rustrade_instrument::{
asset::name::AssetNameExchange, exchange::ExchangeId, instrument::name::InstrumentNameExchange,
};
#[derive(Debug)]
pub struct AccountState {
balances: FnvHashMap<AssetNameExchange, AssetBalance<AssetNameExchange>>,
orders_open: OpenOrders,
orders_cancelled:
FnvHashMap<ClientOrderId, Order<ExchangeId, InstrumentNameExchange, Cancelled>>,
orders_filled: FnvHashSet<ClientOrderId>,
orders_expired: FnvHashMap<ClientOrderId, Order<ExchangeId, InstrumentNameExchange, Expired>>,
trades: Vec<Trade<AssetNameExchange, InstrumentNameExchange>>,
}
impl AccountState {
pub fn new(
balances: FnvHashMap<AssetNameExchange, AssetBalance<AssetNameExchange>>,
orders_open: OpenOrders,
orders_cancelled: FnvHashMap<
ClientOrderId,
Order<ExchangeId, InstrumentNameExchange, Cancelled>,
>,
trades: Vec<Trade<AssetNameExchange, InstrumentNameExchange>>,
) -> Self {
Self {
balances,
orders_open,
orders_cancelled,
orders_filled: FnvHashSet::default(),
orders_expired: FnvHashMap::default(),
trades,
}
}
pub fn orders(&self) -> &OpenOrders {
&self.orders_open
}
pub fn orders_mut(&mut self) -> &mut OpenOrders {
&mut self.orders_open
}
pub fn ack_cancelled(&mut self, order: Order<ExchangeId, InstrumentNameExchange, Cancelled>) {
self.orders_cancelled.insert(order.key.cid.clone(), order);
}
pub fn is_cancelled(&self, cid: &ClientOrderId) -> bool {
self.orders_cancelled.contains_key(cid)
}
pub fn ack_filled(&mut self, cid: ClientOrderId) {
self.orders_filled.insert(cid);
}
pub fn is_filled(&self, cid: &ClientOrderId) -> bool {
self.orders_filled.contains(cid)
}
pub fn ack_expired(&mut self, order: Order<ExchangeId, InstrumentNameExchange, Expired>) {
self.orders_expired.insert(order.key.cid.clone(), order);
}
pub fn is_expired(&self, cid: &ClientOrderId) -> bool {
self.orders_expired.contains_key(cid)
}
pub fn update_time_exchange(&mut self, time_exchange: DateTime<Utc>) {
for balance in self.balances.values_mut() {
balance.time_exchange = time_exchange;
}
}
pub fn balances(&self) -> impl Iterator<Item = &AssetBalance<AssetNameExchange>> + '_ {
self.balances.values()
}
pub fn orders_open(
&self,
) -> impl Iterator<Item = &Order<ExchangeId, InstrumentNameExchange, Open>> + '_ {
self.orders_open.iter()
}
pub fn orders_cancelled(
&self,
) -> impl Iterator<Item = &Order<ExchangeId, InstrumentNameExchange, Cancelled>> + '_ {
self.orders_cancelled.values()
}
pub fn orders_expired(
&self,
) -> impl Iterator<Item = &Order<ExchangeId, InstrumentNameExchange, Expired>> + '_ {
self.orders_expired.values()
}
pub fn trades(
&self,
time_since: DateTime<Utc>,
) -> impl Iterator<Item = &Trade<AssetNameExchange, InstrumentNameExchange>> + '_ {
self.trades
.iter()
.filter(move |trade| trade.time_exchange >= time_since)
}
pub fn balance_mut(
&mut self,
asset: &AssetNameExchange,
) -> Option<&mut AssetBalance<AssetNameExchange>> {
self.balances.get_mut(asset)
}
pub fn ack_trade(&mut self, trade: Trade<AssetNameExchange, InstrumentNameExchange>) {
self.trades.push(trade);
}
pub fn reserve(
&mut self,
asset: &AssetNameExchange,
amount: Decimal,
time_exchange: DateTime<Utc>,
) -> Result<AssetBalance<AssetNameExchange>, BalanceInsufficient> {
let balance = self.balance_expect(asset);
if balance.balance.free < amount {
return Err(BalanceInsufficient {
free: balance.balance.free,
required: amount,
});
}
balance.balance.free -= amount;
balance.time_exchange = time_exchange;
Ok(balance.clone())
}
pub fn settle(
&mut self,
asset: &AssetNameExchange,
amount: Decimal,
time_exchange: DateTime<Utc>,
) -> AssetBalance<AssetNameExchange> {
let balance = self.balance_expect(asset);
debug_assert!(
balance.balance.total - amount >= balance.balance.free,
"settling {amount} of {asset} would take total {} below free {}: only a reserved \
amount may be settled",
balance.balance.total,
balance.balance.free
);
balance.balance.total -= amount;
balance.time_exchange = time_exchange;
balance.clone()
}
pub fn release(
&mut self,
asset: &AssetNameExchange,
amount: Decimal,
time_exchange: DateTime<Utc>,
) -> AssetBalance<AssetNameExchange> {
let balance = self.balance_expect(asset);
debug_assert!(
balance.balance.free + amount <= balance.balance.total,
"releasing {amount} of {asset} would take free {} above total {}: only a reserved \
amount may be released",
balance.balance.free,
balance.balance.total
);
balance.balance.free += amount;
balance.time_exchange = time_exchange;
balance.clone()
}
pub fn commit(
&mut self,
debit: &Debit,
time_exchange: DateTime<Utc>,
) -> Result<AssetBalance<AssetNameExchange>, BalanceInsufficient> {
self.reserve(&debit.asset, debit.settled + debit.reserved, time_exchange)?;
Ok(self.settle(&debit.asset, debit.settled, time_exchange))
}
#[allow(clippy::expect_used)] fn balance_expect(
&mut self,
asset: &AssetNameExchange,
) -> &mut AssetBalance<AssetNameExchange> {
self.balances
.get_mut(asset)
.expect("SimulatedVenue has Balance for all configured Instrument assets")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Debit {
pub asset: AssetNameExchange,
pub settled: Decimal,
pub reserved: Decimal,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct BalanceInsufficient {
pub free: Decimal,
pub required: Decimal,
}
impl From<UnindexedAccountSnapshot> for AccountState {
fn from(value: UnindexedAccountSnapshot) -> Self {
let UnindexedAccountSnapshot {
exchange: _,
balances,
instruments,
} = value;
let balances = balances
.into_iter()
.map(|asset_balance| (asset_balance.asset.clone(), asset_balance))
.collect();
let (orders_open, orders_cancelled) = instruments.into_iter().fold(
(OpenOrders::default(), FnvHashMap::default()),
|(mut orders_open, mut orders_cancelled), snapshot| {
for order in snapshot.orders {
match &order.state {
OrderState::Active(_) => {
if let Some(open) = as_open(order) {
let cid = open.key.cid.clone();
let displaced = orders_open.insert(open, None);
debug_assert!(
displaced.is_none(),
"account snapshot lists {cid} as open more than once, so only \
the last of them reaches the book"
);
}
}
OrderState::Inactive(InactiveOrderState::Cancelled(cancelled)) => {
let cancelled = cancelled.clone();
orders_cancelled.insert(
order.key.cid.clone(),
Order {
key: order.key,
side: order.side,
price: order.price,
quantity: order.quantity,
kind: order.kind,
time_in_force: order.time_in_force,
state: cancelled,
},
);
}
_ => {}
}
}
(orders_open, orders_cancelled)
},
);
Self {
balances,
orders_open,
orders_cancelled,
orders_filled: FnvHashSet::default(),
orders_expired: FnvHashMap::default(),
trades: vec![],
}
}
}