1use crate::types::{FillEvent, PortfolioSnapshot, Side};
2
3#[derive(Debug, Clone)]
4pub struct Portfolio {
5 pub cash: f64,
6 pub position: f64,
7 pub transaction_cost_rate: f64,
8}
9
10impl Portfolio {
11 pub fn new(cash: f64, transaction_cost_rate: f64) -> Self {
12 Self {
13 cash,
14 position: 0.0,
15 transaction_cost_rate,
16 }
17 }
18
19 pub fn update(&mut self, fill: &FillEvent) {
20 let value = fill.price * fill.quantity;
21 let cost = value * self.transaction_cost_rate;
22
23 match fill.side {
24 Side::Buy => {
25 self.cash -= value + cost;
26 self.position += fill.quantity;
27 }
28 Side::Sell => {
29 self.cash += value - cost;
30 self.position -= fill.quantity;
31 }
32 }
33 }
34
35 pub fn snapshot(&self) -> PortfolioSnapshot {
36 PortfolioSnapshot {
37 cash: self.cash,
38 position: self.position,
39 }
40 }
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46 use crate::types::Side;
47
48 #[test]
49 fn test_snapshot_matches_portfolio() {
50 let mut p = Portfolio::new(10000.0, 0.0);
51 p.position = 3.0;
52 let snap = p.snapshot();
53 assert_eq!(snap.cash, 10000.0);
54 assert_eq!(snap.position, 3.0);
55 }
56
57 #[test]
58 fn test_update_buy() {
59 let mut p = Portfolio::new(10000.0, 0.0);
60 let fill = FillEvent {
61 order_id: 1,
62 timestamp: 0.0,
63 side: Side::Buy,
64 price: 100.0,
65 quantity: 2.0,
66 };
67 p.update(&fill);
68 assert!((p.cash - 9800.0).abs() < 1e-10);
69 assert!((p.position - 2.0).abs() < 1e-10);
70 }
71
72 #[test]
73 fn test_update_sell_with_cost() {
74 let mut p = Portfolio::new(10000.0, 0.001);
75 p.position = 5.0;
76 let fill = FillEvent {
77 order_id: 1,
78 timestamp: 0.0,
79 side: Side::Sell,
80 price: 100.0,
81 quantity: 2.0,
82 };
83 p.update(&fill);
84 assert!((p.cash - 10199.8).abs() < 1e-10);
86 assert!((p.position - 3.0).abs() < 1e-10);
87 }
88}