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