nautilus_model/events/position/
opened.rs1use nautilus_core::{UUID4, UnixNanos};
17use serde::{Deserialize, Serialize};
18
19use crate::{
20 enums::{OrderSide, PositionSide},
21 events::OrderFilled,
22 identifiers::{AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TraderId},
23 position::Position,
24 types::{Currency, Price, Quantity},
25};
26
27#[repr(C)]
29#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
30#[cfg_attr(
31 feature = "python",
32 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
33)]
34#[cfg_attr(
35 feature = "python",
36 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
37)]
38pub struct PositionOpened {
39 pub trader_id: TraderId,
41 pub strategy_id: StrategyId,
43 pub instrument_id: InstrumentId,
45 pub position_id: PositionId,
47 pub account_id: AccountId,
49 pub opening_order_id: ClientOrderId,
51 pub entry: OrderSide,
53 pub side: PositionSide,
55 pub signed_qty: f64,
57 pub quantity: Quantity,
59 pub last_qty: Quantity,
61 pub last_px: Price,
63 pub currency: Currency,
65 pub avg_px_open: f64,
67 pub event_id: UUID4,
69 pub ts_event: UnixNanos,
71 pub ts_init: UnixNanos,
73}
74
75impl PositionOpened {
76 #[must_use]
77 pub fn create(
78 position: &Position,
79 fill: &OrderFilled,
80 event_id: UUID4,
81 ts_init: UnixNanos,
82 ) -> Self {
83 Self {
84 trader_id: position.trader_id,
85 strategy_id: position.strategy_id,
86 instrument_id: position.instrument_id,
87 position_id: position.id,
88 account_id: position.account_id,
89 opening_order_id: position.opening_order_id,
90 entry: position.entry,
91 side: position.side,
92 signed_qty: position.signed_qty,
93 quantity: position.quantity,
94 last_qty: fill.last_qty,
95 last_px: fill.last_px,
96 currency: position.quote_currency,
97 avg_px_open: position.avg_px_open,
98 event_id,
99 ts_event: fill.ts_event,
100 ts_init,
101 }
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use nautilus_core::UnixNanos;
108 use rstest::*;
109
110 use super::*;
111 use crate::{
112 enums::{LiquiditySide, OrderSide, OrderType, PositionSide},
113 events::OrderFilled,
114 identifiers::{
115 AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId, TraderId,
116 VenueOrderId,
117 },
118 instruments::{InstrumentAny, stubs::audusd_sim},
119 position::Position,
120 types::{Currency, Money, Price, Quantity},
121 };
122
123 fn create_test_position_opened() -> PositionOpened {
124 PositionOpened {
125 trader_id: TraderId::from("TRADER-001"),
126 strategy_id: StrategyId::from("EMA-CROSS"),
127 instrument_id: InstrumentId::from("EURUSD.SIM"),
128 position_id: PositionId::from("P-001"),
129 account_id: AccountId::from("SIM-001"),
130 opening_order_id: ClientOrderId::from("O-19700101-000000-001-001-1"),
131 entry: OrderSide::Buy,
132 side: PositionSide::Long,
133 signed_qty: 100.0,
134 quantity: Quantity::from("100"),
135 last_qty: Quantity::from("100"),
136 last_px: Price::from("1.0500"),
137 currency: Currency::USD(),
138 avg_px_open: 1.0500,
139 event_id: UUID4::default(),
140 ts_event: UnixNanos::from(1_000_000_000),
141 ts_init: UnixNanos::from(2_000_000_000),
142 }
143 }
144
145 fn create_test_order_filled() -> OrderFilled {
146 OrderFilled::new(
147 TraderId::from("TRADER-001"),
148 StrategyId::from("EMA-CROSS"),
149 InstrumentId::from("AUD/USD.SIM"),
150 ClientOrderId::from("O-19700101-000000-001-001-1"),
151 VenueOrderId::from("1"),
152 AccountId::from("SIM-001"),
153 TradeId::from("T-001"),
154 OrderSide::Buy,
155 OrderType::Market,
156 Quantity::from("100"),
157 Price::from("0.8000"),
158 Currency::USD(),
159 LiquiditySide::Taker,
160 UUID4::default(),
161 UnixNanos::from(1_000_000_000),
162 UnixNanos::from(2_000_000_000),
163 false,
164 Some(PositionId::from("P-001")),
165 Some(Money::new(2.0, Currency::USD())),
166 )
167 }
168
169 #[rstest]
170 fn test_position_opened_create() {
171 let instrument = audusd_sim();
172 let fill = create_test_order_filled();
173 let position = Position::new(&InstrumentAny::CurrencyPair(instrument), fill);
174 let event_id = UUID4::default();
175 let ts_init = UnixNanos::from(3_000_000_000);
176
177 let position_opened = PositionOpened::create(&position, &fill, event_id, ts_init);
178
179 assert_eq!(position_opened.trader_id, position.trader_id);
180 assert_eq!(position_opened.strategy_id, position.strategy_id);
181 assert_eq!(position_opened.instrument_id, position.instrument_id);
182 assert_eq!(position_opened.position_id, position.id);
183 assert_eq!(position_opened.account_id, position.account_id);
184 assert_eq!(position_opened.opening_order_id, position.opening_order_id);
185 assert_eq!(position_opened.entry, position.entry);
186 assert_eq!(position_opened.side, position.side);
187 assert_eq!(position_opened.signed_qty, position.signed_qty);
188 assert_eq!(position_opened.quantity, position.quantity);
189 assert_eq!(position_opened.last_qty, fill.last_qty);
190 assert_eq!(position_opened.last_px, fill.last_px);
191 assert_eq!(position_opened.currency, position.quote_currency);
192 assert_eq!(position_opened.avg_px_open, position.avg_px_open);
193 assert_eq!(position_opened.event_id, event_id);
194 assert_eq!(position_opened.ts_event, fill.ts_event);
195 assert_eq!(position_opened.ts_init, ts_init);
196 }
197
198 #[rstest]
199 fn test_position_opened_with_different_sides() {
200 let mut long_position = create_test_position_opened();
201 long_position.side = PositionSide::Long;
202 long_position.entry = OrderSide::Buy;
203 long_position.signed_qty = 100.0;
204
205 let mut short_position = create_test_position_opened();
206 short_position.side = PositionSide::Short;
207 short_position.entry = OrderSide::Sell;
208 short_position.signed_qty = -100.0;
209
210 assert_eq!(long_position.side, PositionSide::Long);
211 assert_eq!(long_position.entry, OrderSide::Buy);
212 assert_eq!(long_position.signed_qty, 100.0);
213
214 assert_eq!(short_position.side, PositionSide::Short);
215 assert_eq!(short_position.entry, OrderSide::Sell);
216 assert_eq!(short_position.signed_qty, -100.0);
217 }
218}