1use nautilus_core::{UUID4, UnixNanos};
17
18use crate::{
19 enums::{OrderSide, PositionSide},
20 events::OrderFilled,
21 identifiers::{AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TraderId},
22 position::Position,
23 types::{Currency, Price, Quantity},
24};
25
26#[repr(C)]
28#[derive(Clone, PartialEq, Debug)]
29pub struct PositionOpened {
30 pub trader_id: TraderId,
32 pub strategy_id: StrategyId,
34 pub instrument_id: InstrumentId,
36 pub position_id: PositionId,
38 pub account_id: AccountId,
40 pub opening_order_id: ClientOrderId,
42 pub entry: OrderSide,
44 pub side: PositionSide,
46 pub signed_qty: f64,
48 pub quantity: Quantity,
50 pub last_qty: Quantity,
52 pub last_px: Price,
54 pub currency: Currency,
56 pub avg_px_open: f64,
58 pub event_id: UUID4,
60 pub ts_event: UnixNanos,
62 pub ts_init: UnixNanos,
64}
65
66impl PositionOpened {
67 pub fn create(
68 position: &Position,
69 fill: &OrderFilled,
70 event_id: UUID4,
71 ts_init: UnixNanos,
72 ) -> PositionOpened {
73 PositionOpened {
74 trader_id: position.trader_id,
75 strategy_id: position.strategy_id,
76 instrument_id: position.instrument_id,
77 position_id: position.id,
78 account_id: position.account_id,
79 opening_order_id: position.opening_order_id,
80 entry: position.entry,
81 side: position.side,
82 signed_qty: position.signed_qty,
83 quantity: position.quantity,
84 last_qty: fill.last_qty,
85 last_px: fill.last_px,
86 currency: position.quote_currency,
87 avg_px_open: position.avg_px_open,
88 event_id,
89 ts_event: fill.ts_event,
90 ts_init,
91 }
92 }
93}
94
95#[cfg(test)]
99mod tests {
100 use nautilus_core::UnixNanos;
101 use rstest::*;
102
103 use super::*;
104 use crate::{
105 enums::{LiquiditySide, OrderSide, OrderType, PositionSide},
106 events::OrderFilled,
107 identifiers::{
108 AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId, TraderId,
109 VenueOrderId,
110 },
111 instruments::{InstrumentAny, stubs::audusd_sim},
112 position::Position,
113 types::{Currency, Money, Price, Quantity},
114 };
115
116 fn create_test_position_opened() -> PositionOpened {
117 PositionOpened {
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 entry: OrderSide::Buy,
125 side: PositionSide::Long,
126 signed_qty: 100.0,
127 quantity: Quantity::from("100"),
128 last_qty: Quantity::from("100"),
129 last_px: Price::from("1.0500"),
130 currency: Currency::USD(),
131 avg_px_open: 1.0500,
132 event_id: Default::default(),
133 ts_event: UnixNanos::from(1_000_000_000),
134 ts_init: UnixNanos::from(2_000_000_000),
135 }
136 }
137
138 fn create_test_order_filled() -> OrderFilled {
139 OrderFilled::new(
140 TraderId::from("TRADER-001"),
141 StrategyId::from("EMA-CROSS"),
142 InstrumentId::from("AUD/USD.SIM"),
143 ClientOrderId::from("O-19700101-000000-001-001-1"),
144 VenueOrderId::from("1"),
145 AccountId::from("SIM-001"),
146 TradeId::from("T-001"),
147 OrderSide::Buy,
148 OrderType::Market,
149 Quantity::from("100"),
150 Price::from("0.8000"),
151 Currency::USD(),
152 LiquiditySide::Taker,
153 Default::default(),
154 UnixNanos::from(1_000_000_000),
155 UnixNanos::from(2_000_000_000),
156 false,
157 Some(PositionId::from("P-001")),
158 Some(Money::new(2.0, Currency::USD())),
159 )
160 }
161
162 #[rstest]
163 fn test_position_opened_new() {
164 let position_opened = create_test_position_opened();
165
166 assert_eq!(position_opened.trader_id, TraderId::from("TRADER-001"));
167 assert_eq!(position_opened.strategy_id, StrategyId::from("EMA-CROSS"));
168 assert_eq!(
169 position_opened.instrument_id,
170 InstrumentId::from("EURUSD.SIM")
171 );
172 assert_eq!(position_opened.position_id, PositionId::from("P-001"));
173 assert_eq!(position_opened.account_id, AccountId::from("SIM-001"));
174 assert_eq!(
175 position_opened.opening_order_id,
176 ClientOrderId::from("O-19700101-000000-001-001-1")
177 );
178 assert_eq!(position_opened.entry, OrderSide::Buy);
179 assert_eq!(position_opened.side, PositionSide::Long);
180 assert_eq!(position_opened.signed_qty, 100.0);
181 assert_eq!(position_opened.quantity, Quantity::from("100"));
182 assert_eq!(position_opened.last_qty, Quantity::from("100"));
183 assert_eq!(position_opened.last_px, Price::from("1.0500"));
184 assert_eq!(position_opened.currency, Currency::USD());
185 assert_eq!(position_opened.avg_px_open, 1.0500);
186 assert_eq!(position_opened.ts_event, UnixNanos::from(1_000_000_000));
187 assert_eq!(position_opened.ts_init, UnixNanos::from(2_000_000_000));
188 }
189
190 #[rstest]
191 fn test_position_opened_create() {
192 let instrument = audusd_sim();
193 let fill = create_test_order_filled();
194 let position = Position::new(&InstrumentAny::CurrencyPair(instrument), fill);
195 let event_id = Default::default();
196 let ts_init = UnixNanos::from(3_000_000_000);
197
198 let position_opened = PositionOpened::create(&position, &fill, event_id, ts_init);
199
200 assert_eq!(position_opened.trader_id, position.trader_id);
201 assert_eq!(position_opened.strategy_id, position.strategy_id);
202 assert_eq!(position_opened.instrument_id, position.instrument_id);
203 assert_eq!(position_opened.position_id, position.id);
204 assert_eq!(position_opened.account_id, position.account_id);
205 assert_eq!(position_opened.opening_order_id, position.opening_order_id);
206 assert_eq!(position_opened.entry, position.entry);
207 assert_eq!(position_opened.side, position.side);
208 assert_eq!(position_opened.signed_qty, position.signed_qty);
209 assert_eq!(position_opened.quantity, position.quantity);
210 assert_eq!(position_opened.last_qty, fill.last_qty);
211 assert_eq!(position_opened.last_px, fill.last_px);
212 assert_eq!(position_opened.currency, position.quote_currency);
213 assert_eq!(position_opened.avg_px_open, position.avg_px_open);
214 assert_eq!(position_opened.event_id, event_id);
215 assert_eq!(position_opened.ts_event, fill.ts_event);
216 assert_eq!(position_opened.ts_init, ts_init);
217 }
218
219 #[rstest]
220 fn test_position_opened_clone() {
221 let position_opened1 = create_test_position_opened();
222 let position_opened2 = position_opened1.clone();
223
224 assert_eq!(position_opened1, position_opened2);
225 }
226
227 #[rstest]
228 fn test_position_opened_debug() {
229 let position_opened = create_test_position_opened();
230 let debug_str = format!("{position_opened:?}");
231
232 assert!(debug_str.contains("PositionOpened"));
233 assert!(debug_str.contains("TRADER-001"));
234 assert!(debug_str.contains("EMA-CROSS"));
235 assert!(debug_str.contains("EURUSD.SIM"));
236 assert!(debug_str.contains("P-001"));
237 }
238
239 #[rstest]
240 fn test_position_opened_partial_eq() {
241 let mut position_opened1 = create_test_position_opened();
242 let mut position_opened2 = create_test_position_opened();
243 let event_id = Default::default();
244 position_opened1.event_id = event_id;
245 position_opened2.event_id = event_id;
246
247 let mut position_opened3 = create_test_position_opened();
248 position_opened3.event_id = event_id;
249 position_opened3.quantity = Quantity::from("200");
250
251 assert_eq!(position_opened1, position_opened2);
252 assert_ne!(position_opened1, position_opened3);
253 }
254
255 #[rstest]
256 fn test_position_opened_with_different_sides() {
257 let mut long_position = create_test_position_opened();
258 long_position.side = PositionSide::Long;
259 long_position.entry = OrderSide::Buy;
260 long_position.signed_qty = 100.0;
261
262 let mut short_position = create_test_position_opened();
263 short_position.side = PositionSide::Short;
264 short_position.entry = OrderSide::Sell;
265 short_position.signed_qty = -100.0;
266
267 assert_eq!(long_position.side, PositionSide::Long);
268 assert_eq!(long_position.entry, OrderSide::Buy);
269 assert_eq!(long_position.signed_qty, 100.0);
270
271 assert_eq!(short_position.side, PositionSide::Short);
272 assert_eq!(short_position.entry, OrderSide::Sell);
273 assert_eq!(short_position.signed_qty, -100.0);
274 }
275
276 #[rstest]
277 fn test_position_opened_different_currencies() {
278 let mut usd_position = create_test_position_opened();
279 usd_position.currency = Currency::USD();
280
281 let mut eur_position = create_test_position_opened();
282 eur_position.currency = Currency::EUR();
283
284 assert_eq!(usd_position.currency, Currency::USD());
285 assert_eq!(eur_position.currency, Currency::EUR());
286 assert_ne!(usd_position, eur_position);
287 }
288
289 #[rstest]
290 fn test_position_opened_timestamps() {
291 let position_opened = create_test_position_opened();
292
293 assert_eq!(position_opened.ts_event, UnixNanos::from(1_000_000_000));
294 assert_eq!(position_opened.ts_init, UnixNanos::from(2_000_000_000));
295 assert!(position_opened.ts_event < position_opened.ts_init);
296 }
297
298 #[rstest]
299 fn test_position_opened_quantities() {
300 let mut position_opened = create_test_position_opened();
301 position_opened.quantity = Quantity::from("500");
302 position_opened.last_qty = Quantity::from("250");
303
304 assert_eq!(position_opened.quantity, Quantity::from("500"));
305 assert_eq!(position_opened.last_qty, Quantity::from("250"));
306 }
307
308 #[rstest]
309 fn test_position_opened_prices() {
310 let mut position_opened = create_test_position_opened();
311 position_opened.last_px = Price::from("1.2345");
312 position_opened.avg_px_open = 1.2345;
313
314 assert_eq!(position_opened.last_px, Price::from("1.2345"));
315 assert_eq!(position_opened.avg_px_open, 1.2345);
316 }
317}