Skip to main content

sandbox_quant/model/
position.rs

1use super::order::{Fill, OrderSide};
2
3#[derive(Debug, Clone)]
4pub struct Position {
5    pub symbol: String,
6    pub side: Option<OrderSide>,
7    pub qty: f64,
8    pub entry_price: f64,
9    pub realized_pnl: f64,
10    pub unrealized_pnl: f64,
11    pub trade_count: u32,
12}
13
14impl Position {
15    pub fn new(symbol: String) -> Self {
16        Self {
17            symbol,
18            side: None,
19            qty: 0.0,
20            entry_price: 0.0,
21            realized_pnl: 0.0,
22            unrealized_pnl: 0.0,
23            trade_count: 0,
24        }
25    }
26
27    pub fn is_flat(&self) -> bool {
28        self.side.is_none() || self.qty <= 0.0
29    }
30
31    pub fn apply_fill(&mut self, side: OrderSide, fills: &[Fill]) {
32        for fill in fills {
33            match (self.side, side) {
34                // Opening a new position
35                (None, _) => {
36                    self.side = Some(side);
37                    self.qty = fill.qty;
38                    self.entry_price = fill.price;
39                }
40                // Adding to existing position (same side)
41                (Some(pos_side), fill_side) if pos_side == fill_side => {
42                    let total_cost = self.entry_price * self.qty + fill.price * fill.qty;
43                    self.qty += fill.qty;
44                    self.entry_price = total_cost / self.qty;
45                }
46                // Closing position (opposite side)
47                (Some(_pos_side), _fill_side) => {
48                    let close_qty = fill.qty.min(self.qty);
49                    let pnl = match self.side {
50                        Some(OrderSide::Buy) => (fill.price - self.entry_price) * close_qty,
51                        Some(OrderSide::Sell) => (self.entry_price - fill.price) * close_qty,
52                        None => 0.0,
53                    };
54                    self.realized_pnl += pnl;
55                    self.qty -= close_qty;
56                    if self.qty <= f64::EPSILON {
57                        self.side = None;
58                        self.qty = 0.0;
59                        self.entry_price = 0.0;
60                    }
61                }
62            }
63        }
64        self.trade_count += 1;
65    }
66
67    pub fn update_unrealized_pnl(&mut self, current_price: f64) {
68        if self.is_flat() {
69            self.unrealized_pnl = 0.0;
70            return;
71        }
72        self.unrealized_pnl = match self.side {
73            Some(OrderSide::Buy) => (current_price - self.entry_price) * self.qty,
74            Some(OrderSide::Sell) => (self.entry_price - current_price) * self.qty,
75            None => 0.0,
76        };
77    }
78}
79