use crate::Decimal;
#[cfg(feature = "serde")]
use pretty_simple_display::{DebugPretty, DisplaySimple};
#[derive(Clone, PartialEq)]
#[cfg_attr(not(feature = "serde"), derive(Debug))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize, DebugPretty, DisplaySimple)
)]
pub struct InventoryPosition {
pub quantity: Decimal,
pub avg_entry_price: Decimal,
pub last_update: u64,
}
impl InventoryPosition {
#[must_use]
pub fn new() -> Self {
Self {
quantity: Decimal::ZERO,
avg_entry_price: Decimal::ZERO,
last_update: 0,
}
}
#[must_use]
pub fn is_flat(&self) -> bool {
self.quantity == Decimal::ZERO
}
#[must_use]
pub fn is_long(&self) -> bool {
self.quantity > Decimal::ZERO
}
#[must_use]
pub fn is_short(&self) -> bool {
self.quantity < Decimal::ZERO
}
pub fn update_fill(&mut self, fill_quantity: Decimal, fill_price: Decimal, timestamp: u64) {
let new_quantity = self.quantity + fill_quantity;
if (self.quantity > Decimal::ZERO && new_quantity < Decimal::ZERO)
|| (self.quantity < Decimal::ZERO && new_quantity > Decimal::ZERO)
{
self.avg_entry_price = fill_price;
}
else if new_quantity.abs() > self.quantity.abs() {
let total_cost = self.quantity * self.avg_entry_price + fill_quantity * fill_price;
self.avg_entry_price = total_cost / new_quantity;
}
self.quantity = new_quantity;
self.last_update = timestamp;
if self.quantity == Decimal::ZERO {
self.avg_entry_price = Decimal::ZERO;
}
}
#[must_use]
pub fn unrealized_pnl(&self, current_price: Decimal) -> Decimal {
if self.is_flat() {
return Decimal::ZERO;
}
self.quantity * (current_price - self.avg_entry_price)
}
}
impl Default for InventoryPosition {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dec;
#[test]
fn test_new_position_is_flat() {
let position = InventoryPosition::new();
assert_eq!(position.quantity, Decimal::ZERO);
assert_eq!(position.avg_entry_price, Decimal::ZERO);
assert_eq!(position.last_update, 0);
assert!(position.is_flat());
}
#[test]
fn test_default_position() {
let position = InventoryPosition::default();
assert_eq!(position.quantity, Decimal::ZERO);
assert!(position.is_flat());
}
#[test]
fn test_is_long() {
let position = InventoryPosition {
quantity: dec!(10.0),
avg_entry_price: dec!(100.0),
last_update: 1000,
};
assert!(position.is_long());
assert!(!position.is_flat());
assert!(!position.is_short());
}
#[test]
fn test_is_short() {
let position = InventoryPosition {
quantity: dec!(-10.0),
avg_entry_price: dec!(100.0),
last_update: 1000,
};
assert!(position.is_short());
assert!(!position.is_flat());
assert!(!position.is_long());
}
#[test]
fn test_is_flat() {
let position = InventoryPosition {
quantity: Decimal::ZERO,
avg_entry_price: dec!(100.0),
last_update: 1000,
};
assert!(position.is_flat());
assert!(!position.is_long());
assert!(!position.is_short());
}
#[test]
fn test_very_small_position_is_flat() {
let position = InventoryPosition {
quantity: Decimal::ZERO, avg_entry_price: dec!(100.0),
last_update: 1000,
};
assert!(position.is_flat());
}
#[test]
fn test_update_fill_buy() {
let mut position = InventoryPosition::new();
position.update_fill(dec!(10.0), dec!(100.0), 1000);
assert_eq!(position.quantity, dec!(10.0));
assert_eq!(position.avg_entry_price, dec!(100.0));
assert_eq!(position.last_update, 1000);
}
#[test]
fn test_update_fill_sell() {
let mut position = InventoryPosition::new();
position.update_fill(dec!(-10.0), dec!(100.0), 1000);
assert_eq!(position.quantity, dec!(-10.0));
assert_eq!(position.avg_entry_price, dec!(100.0));
}
#[test]
fn test_update_fill_weighted_average() {
let mut position = InventoryPosition::new();
position.update_fill(dec!(10.0), dec!(100.0), 1000);
position.update_fill(dec!(5.0), dec!(102.0), 2000);
assert_eq!(position.quantity, dec!(15.0));
let expected = dec!(100.666666666666666667);
assert!((position.avg_entry_price - expected).abs() < dec!(0.000001));
}
#[test]
fn test_update_fill_reduce_position() {
let mut position = InventoryPosition::new();
position.update_fill(dec!(10.0), dec!(100.0), 1000);
position.update_fill(dec!(-5.0), dec!(105.0), 2000);
assert_eq!(position.quantity, dec!(5.0));
assert_eq!(position.avg_entry_price, dec!(100.0));
}
#[test]
fn test_update_fill_flatten_position() {
let mut position = InventoryPosition::new();
position.update_fill(dec!(10.0), dec!(100.0), 1000);
position.update_fill(dec!(-10.0), dec!(105.0), 2000);
assert_eq!(position.quantity, Decimal::ZERO);
assert_eq!(position.avg_entry_price, Decimal::ZERO);
assert!(position.is_flat());
}
#[test]
fn test_update_fill_flip_position() {
let mut position = InventoryPosition::new();
position.update_fill(dec!(10.0), dec!(100.0), 1000);
position.update_fill(dec!(-15.0), dec!(105.0), 2000);
assert_eq!(position.quantity, dec!(-5.0));
assert_eq!(position.avg_entry_price, dec!(105.0));
}
#[test]
fn test_unrealized_pnl_long_profit() {
let mut position = InventoryPosition::new();
position.update_fill(dec!(10.0), dec!(100.0), 1000);
let pnl = position.unrealized_pnl(dec!(105.0));
assert_eq!(pnl, dec!(50.0)); }
#[test]
fn test_unrealized_pnl_long_loss() {
let mut position = InventoryPosition::new();
position.update_fill(dec!(10.0), dec!(100.0), 1000);
let pnl = position.unrealized_pnl(dec!(95.0));
assert_eq!(pnl, dec!(-50.0)); }
#[test]
fn test_unrealized_pnl_short_profit() {
let mut position = InventoryPosition::new();
position.update_fill(dec!(-10.0), dec!(100.0), 1000);
let pnl = position.unrealized_pnl(dec!(95.0));
assert_eq!(pnl, dec!(50.0)); }
#[test]
fn test_unrealized_pnl_flat() {
let position = InventoryPosition::new();
let pnl = position.unrealized_pnl(dec!(100.0));
assert_eq!(pnl, Decimal::ZERO);
}
}