sandbox_quant/model/
position.rs1use 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 (None, _) => {
36 self.side = Some(side);
37 self.qty = fill.qty;
38 self.entry_price = fill.price;
39 }
40 (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 (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