nautilus_model/events/position/
mod.rs1use serde::{Deserialize, Serialize};
17
18use crate::{
19 events::{PositionAdjusted, PositionChanged, PositionClosed, PositionOpened},
20 identifiers::{AccountId, InstrumentId},
21};
22pub mod adjusted;
23pub mod changed;
24pub mod closed;
25pub mod opened;
26pub mod snapshot;
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub enum PositionEvent {
30 PositionOpened(PositionOpened),
31 PositionChanged(PositionChanged),
32 PositionClosed(PositionClosed),
33 PositionAdjusted(PositionAdjusted),
34}
35
36impl PositionEvent {
37 #[must_use]
38 pub fn instrument_id(&self) -> InstrumentId {
39 match self {
40 Self::PositionOpened(position) => position.instrument_id,
41 Self::PositionChanged(position) => position.instrument_id,
42 Self::PositionClosed(position) => position.instrument_id,
43 Self::PositionAdjusted(adjustment) => adjustment.instrument_id,
44 }
45 }
46
47 #[must_use]
48 pub fn account_id(&self) -> AccountId {
49 match self {
50 Self::PositionOpened(position) => position.account_id,
51 Self::PositionChanged(position) => position.account_id,
52 Self::PositionClosed(position) => position.account_id,
53 Self::PositionAdjusted(adjustment) => adjustment.account_id,
54 }
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use nautilus_core::{UUID4, UnixNanos};
61 use rstest::*;
62
63 use super::*;
64 use crate::{
65 enums::{OrderSide, PositionSide},
66 events::{PositionChanged, PositionClosed, PositionOpened},
67 identifiers::{AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TraderId},
68 types::{Currency, Money, Price, Quantity},
69 };
70
71 fn create_test_position_opened() -> PositionOpened {
72 PositionOpened {
73 trader_id: TraderId::from("TRADER-001"),
74 strategy_id: StrategyId::from("EMA-CROSS"),
75 instrument_id: InstrumentId::from("EURUSD.SIM"),
76 position_id: PositionId::from("P-001"),
77 account_id: AccountId::from("SIM-001"),
78 opening_order_id: ClientOrderId::from("O-19700101-000000-001-001-1"),
79 entry: OrderSide::Buy,
80 side: PositionSide::Long,
81 signed_qty: 100.0,
82 quantity: Quantity::from("100"),
83 last_qty: Quantity::from("100"),
84 last_px: Price::from("1.0500"),
85 currency: Currency::USD(),
86 avg_px_open: 1.0500,
87 event_id: UUID4::default(),
88 ts_event: UnixNanos::from(1_000_000_000),
89 ts_init: UnixNanos::from(2_000_000_000),
90 }
91 }
92
93 fn create_test_position_changed() -> PositionChanged {
94 PositionChanged {
95 trader_id: TraderId::from("TRADER-001"),
96 strategy_id: StrategyId::from("EMA-CROSS"),
97 instrument_id: InstrumentId::from("EURUSD.SIM"),
98 position_id: PositionId::from("P-001"),
99 account_id: AccountId::from("SIM-001"),
100 opening_order_id: ClientOrderId::from("O-19700101-000000-001-001-1"),
101 entry: OrderSide::Buy,
102 side: PositionSide::Long,
103 signed_qty: 150.0,
104 quantity: Quantity::from("150"),
105 peak_quantity: Quantity::from("150"),
106 last_qty: Quantity::from("50"),
107 last_px: Price::from("1.0550"),
108 currency: Currency::USD(),
109 avg_px_open: 1.0525,
110 avg_px_close: None,
111 realized_return: 0.0,
112 realized_pnl: None,
113 unrealized_pnl: Money::new(75.0, Currency::USD()),
114 event_id: UUID4::default(),
115 ts_opened: UnixNanos::from(1_000_000_000),
116 ts_event: UnixNanos::from(1_500_000_000),
117 ts_init: UnixNanos::from(2_500_000_000),
118 }
119 }
120
121 fn create_test_position_closed() -> PositionClosed {
122 PositionClosed {
123 trader_id: TraderId::from("TRADER-001"),
124 strategy_id: StrategyId::from("EMA-CROSS"),
125 instrument_id: InstrumentId::from("EURUSD.SIM"),
126 position_id: PositionId::from("P-001"),
127 account_id: AccountId::from("SIM-001"),
128 opening_order_id: ClientOrderId::from("O-19700101-000000-001-001-1"),
129 closing_order_id: Some(ClientOrderId::from("O-19700101-000000-001-001-2")),
130 entry: OrderSide::Buy,
131 side: PositionSide::Flat,
132 signed_qty: 0.0,
133 quantity: Quantity::from("0"),
134 peak_quantity: Quantity::from("150"),
135 last_qty: Quantity::from("150"),
136 last_px: Price::from("1.0600"),
137 currency: Currency::USD(),
138 avg_px_open: 1.0525,
139 avg_px_close: Some(1.0600),
140 realized_return: 0.0071,
141 realized_pnl: Some(Money::new(112.50, Currency::USD())),
142 unrealized_pnl: Money::new(0.0, Currency::USD()),
143 duration: 3_600_000_000_000, event_id: UUID4::default(),
145 ts_opened: UnixNanos::from(1_000_000_000),
146 ts_closed: Some(UnixNanos::from(4_600_000_000)),
147 ts_event: UnixNanos::from(4_600_000_000),
148 ts_init: UnixNanos::from(5_000_000_000),
149 }
150 }
151
152 #[rstest]
153 fn test_position_event_opened_instrument_id() {
154 let opened = create_test_position_opened();
155 let event = PositionEvent::PositionOpened(opened);
156
157 assert_eq!(event.instrument_id(), InstrumentId::from("EURUSD.SIM"));
158 }
159
160 #[rstest]
161 fn test_position_event_changed_instrument_id() {
162 let changed = create_test_position_changed();
163 let event = PositionEvent::PositionChanged(changed);
164
165 assert_eq!(event.instrument_id(), InstrumentId::from("EURUSD.SIM"));
166 }
167
168 #[rstest]
169 fn test_position_event_closed_instrument_id() {
170 let closed = create_test_position_closed();
171 let event = PositionEvent::PositionClosed(closed);
172
173 assert_eq!(event.instrument_id(), InstrumentId::from("EURUSD.SIM"));
174 }
175
176 #[rstest]
177 fn test_position_event_opened_account_id() {
178 let opened = create_test_position_opened();
179 let event = PositionEvent::PositionOpened(opened);
180
181 assert_eq!(event.account_id(), AccountId::from("SIM-001"));
182 }
183
184 #[rstest]
185 fn test_position_event_changed_account_id() {
186 let changed = create_test_position_changed();
187 let event = PositionEvent::PositionChanged(changed);
188
189 assert_eq!(event.account_id(), AccountId::from("SIM-001"));
190 }
191
192 #[rstest]
193 fn test_position_event_closed_account_id() {
194 let closed = create_test_position_closed();
195 let event = PositionEvent::PositionClosed(closed);
196
197 assert_eq!(event.account_id(), AccountId::from("SIM-001"));
198 }
199
200 #[rstest]
201 fn test_position_event_enum_variants() {
202 let opened = create_test_position_opened();
203 let changed = create_test_position_changed();
204 let closed = create_test_position_closed();
205
206 let event_opened = PositionEvent::PositionOpened(opened);
207 let event_changed = PositionEvent::PositionChanged(changed);
208 let event_closed = PositionEvent::PositionClosed(closed);
209
210 match event_opened {
211 PositionEvent::PositionOpened(_) => {}
212 _ => panic!("Expected PositionOpened variant"),
213 }
214
215 match event_changed {
216 PositionEvent::PositionChanged(_) => {}
217 _ => panic!("Expected PositionChanged variant"),
218 }
219
220 match event_closed {
221 PositionEvent::PositionClosed(_) => {}
222 _ => panic!("Expected PositionClosed variant"),
223 }
224 }
225}