use std::num::NonZeroU16;
use const_decimal::Decimal;
use getset::{
CopyGetters,
Getters,
};
use num::Zero;
use super::Balances;
use crate::{
prelude::{
ActiveLimitOrders,
Position,
},
types::{
CancelBy,
Currency,
Filled,
LimitOrder,
MarginCurrency,
MaxNumberOfActiveOrders,
Mon,
OrderId,
OrderIdNotFound,
Pending,
QuoteCurrency,
Side,
TimestampNs,
UserOrderId,
},
};
#[derive(Debug, Clone, CopyGetters, Getters)]
pub struct Account<I, const D: u8, BaseOrQuote, UserOrderIdT>
where
I: Mon<D>,
BaseOrQuote: Currency<I, D>,
BaseOrQuote::PairedCurrency: MarginCurrency<I, D>,
UserOrderIdT: UserOrderId,
{
#[getset(get = "pub")]
position: Position<I, D, BaseOrQuote>,
#[getset(get = "pub")]
balances: Balances<I, D, BaseOrQuote::PairedCurrency>,
#[getset(get_copy = "pub")]
init_margin_req: Decimal<I, D>,
maker_fee: Decimal<I, D>,
#[getset(get = "pub")]
active_limit_orders: ActiveLimitOrders<I, D, BaseOrQuote, UserOrderIdT>,
}
impl<I, const D: u8, BaseOrQuote, UserOrderIdT> Account<I, D, BaseOrQuote, UserOrderIdT>
where
I: Mon<D>,
BaseOrQuote: Currency<I, D>,
BaseOrQuote::PairedCurrency: MarginCurrency<I, D>,
UserOrderIdT: UserOrderId,
{
pub fn new(
balances: Balances<I, D, BaseOrQuote::PairedCurrency>,
max_active_orders: NonZeroU16,
init_margin_req: Decimal<I, D>,
maker_fee: Decimal<I, D>,
) -> Self {
assert2::assert!(init_margin_req > Decimal::zero());
assert2::assert!(init_margin_req <= Decimal::ONE);
Self {
active_limit_orders: ActiveLimitOrders::with_capacity(max_active_orders),
position: Position::default(),
balances,
init_margin_req,
maker_fee,
}
}
#[inline(always)]
#[must_use]
pub fn reserved_maker_fees(&self) -> BaseOrQuote::PairedCurrency {
(self.active_limit_orders.bids().notional_sum()
+ self.active_limit_orders.asks().notional_sum())
* self.maker_fee.max(Decimal::zero())
}
#[inline(always)]
#[must_use]
pub fn required_collateral(&self) -> BaseOrQuote::PairedCurrency {
self.position_margin() + self.order_margin() + self.reserved_maker_fees()
}
#[inline(always)]
#[must_use]
pub fn margin_excess(&self) -> BaseOrQuote::PairedCurrency {
self.balances.equity() - self.required_collateral()
}
#[inline(always)]
#[must_use]
pub fn available_balance(&self) -> BaseOrQuote::PairedCurrency {
self.margin_excess().max(Zero::zero())
}
#[inline(always)]
#[must_use]
pub(crate) fn largest_collateral_contributor(&self) -> Option<OrderId> {
self.active_limit_orders.largest_collateral_contributor(
self.init_margin_req,
&self.position,
self.maker_fee,
)
}
#[inline(always)]
#[must_use]
pub(crate) fn margin_excess_with_order(
&self,
new_order: &LimitOrder<I, D, BaseOrQuote, UserOrderIdT, Pending<I, D, BaseOrQuote>>,
) -> BaseOrQuote::PairedCurrency {
let new_fee_reserve = new_order.notional() * self.maker_fee.max(Decimal::zero());
self.balances.equity()
- self.position_margin()
- self.order_margin_with_order(new_order)
- self.reserved_maker_fees()
- new_fee_reserve
}
#[inline(always)]
#[must_use]
pub fn position_margin(&self) -> BaseOrQuote::PairedCurrency {
self.position.notional() * self.init_margin_req
}
#[inline(always)]
#[must_use]
pub fn order_margin(&self) -> BaseOrQuote::PairedCurrency {
self.active_limit_orders
.order_margin(self.init_margin_req, &self.position)
}
#[inline(always)]
#[must_use]
pub(crate) fn order_margin_with_order(
&self,
new_order: &LimitOrder<I, D, BaseOrQuote, UserOrderIdT, Pending<I, D, BaseOrQuote>>,
) -> BaseOrQuote::PairedCurrency {
self.active_limit_orders.order_margin_with_order(
new_order,
self.init_margin_req,
&self.position,
)
}
#[inline(always)]
pub fn change_position(
&mut self,
filled_qty: BaseOrQuote,
fill_price: QuoteCurrency<I, D>,
side: Side,
fee: BaseOrQuote::PairedCurrency,
) {
assert2::debug_assert!(filled_qty > BaseOrQuote::zero());
assert2::debug_assert!(fill_price > QuoteCurrency::zero());
self.position
.change(filled_qty, fill_price, side, &mut self.balances);
self.balances.account_for_fee(fee);
}
#[inline(always)]
pub fn try_insert_order(
&mut self,
order: LimitOrder<I, D, BaseOrQuote, UserOrderIdT, Pending<I, D, BaseOrQuote>>,
) -> Result<(), MaxNumberOfActiveOrders> {
self.active_limit_orders.try_insert(order)
}
#[inline(always)]
#[must_use]
pub fn fill_best(
&mut self,
side: Side,
filled_quantity: BaseOrQuote,
limit_price: QuoteCurrency<I, D>,
fee: BaseOrQuote::PairedCurrency,
ts_ns: TimestampNs,
) -> Option<LimitOrder<I, D, BaseOrQuote, UserOrderIdT, Filled<I, D, BaseOrQuote>>> {
self.change_position(filled_quantity, limit_price, side, fee);
self.active_limit_orders
.fill_best(side, filled_quantity, ts_ns)
}
#[allow(clippy::complexity, reason = "How is this hard to read?")]
#[inline(always)]
pub fn cancel_limit_order(
&mut self,
by: CancelBy<UserOrderIdT>,
) -> Result<
LimitOrder<I, D, BaseOrQuote, UserOrderIdT, Pending<I, D, BaseOrQuote>>,
OrderIdNotFound<UserOrderIdT>,
> {
self.active_limit_orders.remove_limit_order(by)
}
}