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::{OrderSide, PositionSide},
113 events::{OrderFilled, order::spec::OrderFilledSpec},
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 OrderFilledSpec::builder()
147 .strategy_id(StrategyId::from("EMA-CROSS"))
148 .instrument_id(InstrumentId::from("AUD/USD.SIM"))
149 .client_order_id(ClientOrderId::from("O-19700101-000000-001-001-1"))
150 .venue_order_id(VenueOrderId::from("1"))
151 .trade_id(TradeId::from("T-001"))
152 .last_qty(Quantity::from("100"))
153 .last_px(Price::from("0.8000"))
154 .ts_event(UnixNanos::from(1_000_000_000))
155 .ts_init(UnixNanos::from(2_000_000_000))
156 .position_id(PositionId::from("P-001"))
157 .commission(Money::new(2.0, Currency::USD()))
158 .build()
159 }
160
161 #[rstest]
162 fn test_position_opened_create() {
163 let instrument = audusd_sim();
164 let fill = create_test_order_filled();
165 let position = Position::new(&InstrumentAny::CurrencyPair(instrument), fill);
166 let event_id = UUID4::default();
167 let ts_init = UnixNanos::from(3_000_000_000);
168
169 let position_opened = PositionOpened::create(&position, &fill, event_id, ts_init);
170
171 assert_eq!(position_opened.trader_id, position.trader_id);
172 assert_eq!(position_opened.strategy_id, position.strategy_id);
173 assert_eq!(position_opened.instrument_id, position.instrument_id);
174 assert_eq!(position_opened.position_id, position.id);
175 assert_eq!(position_opened.account_id, position.account_id);
176 assert_eq!(position_opened.opening_order_id, position.opening_order_id);
177 assert_eq!(position_opened.entry, position.entry);
178 assert_eq!(position_opened.side, position.side);
179 assert_eq!(position_opened.signed_qty, position.signed_qty);
180 assert_eq!(position_opened.quantity, position.quantity);
181 assert_eq!(position_opened.last_qty, fill.last_qty);
182 assert_eq!(position_opened.last_px, fill.last_px);
183 assert_eq!(position_opened.currency, position.quote_currency);
184 assert_eq!(position_opened.avg_px_open, position.avg_px_open);
185 assert_eq!(position_opened.event_id, event_id);
186 assert_eq!(position_opened.ts_event, fill.ts_event);
187 assert_eq!(position_opened.ts_init, ts_init);
188 }
189
190 #[rstest]
191 fn test_position_opened_with_different_sides() {
192 let mut long_position = create_test_position_opened();
193 long_position.side = PositionSide::Long;
194 long_position.entry = OrderSide::Buy;
195 long_position.signed_qty = 100.0;
196
197 let mut short_position = create_test_position_opened();
198 short_position.side = PositionSide::Short;
199 short_position.entry = OrderSide::Sell;
200 short_position.signed_qty = -100.0;
201
202 assert_eq!(long_position.side, PositionSide::Long);
203 assert_eq!(long_position.entry, OrderSide::Buy);
204 assert_eq!(long_position.signed_qty, 100.0);
205
206 assert_eq!(short_position.side, PositionSide::Short);
207 assert_eq!(short_position.entry, OrderSide::Sell);
208 assert_eq!(short_position.signed_qty, -100.0);
209 }
210}