nautilus_model/events/position/
adjusted.rs1use nautilus_core::{UUID4, UnixNanos};
17use rust_decimal::Decimal;
18use serde::{Deserialize, Serialize};
19use ustr::Ustr;
20
21use crate::{
22 enums::PositionAdjustmentType,
23 identifiers::{AccountId, InstrumentId, PositionId, StrategyId, TraderId},
24 types::Money,
25};
26
27#[repr(C)]
34#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
35#[serde(tag = "type")]
36#[cfg_attr(
37 feature = "python",
38 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
39)]
40#[cfg_attr(
41 feature = "python",
42 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
43)]
44pub struct PositionAdjusted {
45 pub trader_id: TraderId,
47 pub strategy_id: StrategyId,
49 pub instrument_id: InstrumentId,
51 pub position_id: PositionId,
53 pub account_id: AccountId,
55 pub adjustment_type: PositionAdjustmentType,
57 pub quantity_change: Option<Decimal>,
59 pub pnl_change: Option<Money>,
61 pub reason: Option<Ustr>,
63 pub event_id: UUID4,
65 pub ts_event: UnixNanos,
67 pub ts_init: UnixNanos,
69}
70
71impl PositionAdjusted {
72 #[expect(clippy::too_many_arguments)]
74 #[must_use]
75 pub fn new(
76 trader_id: TraderId,
77 strategy_id: StrategyId,
78 instrument_id: InstrumentId,
79 position_id: PositionId,
80 account_id: AccountId,
81 adjustment_type: PositionAdjustmentType,
82 quantity_change: Option<Decimal>,
83 pnl_change: Option<Money>,
84 reason: Option<Ustr>,
85 event_id: UUID4,
86 ts_event: UnixNanos,
87 ts_init: UnixNanos,
88 ) -> Self {
89 Self {
90 trader_id,
91 strategy_id,
92 instrument_id,
93 position_id,
94 account_id,
95 adjustment_type,
96 quantity_change,
97 pnl_change,
98 reason,
99 event_id,
100 ts_event,
101 ts_init,
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use std::str::FromStr;
109
110 use nautilus_core::UnixNanos;
111 use rstest::*;
112
113 use super::*;
114 use crate::{
115 enums::PositionAdjustmentType,
116 identifiers::{AccountId, InstrumentId, PositionId, StrategyId, TraderId},
117 types::{Currency, Money},
118 };
119
120 fn create_test_commission_adjustment() -> PositionAdjusted {
121 PositionAdjusted::new(
122 TraderId::from("TRADER-001"),
123 StrategyId::from("EMA-CROSS"),
124 InstrumentId::from("BTCUSDT.BINANCE"),
125 PositionId::from("P-001"),
126 AccountId::from("BINANCE-001"),
127 PositionAdjustmentType::Commission,
128 Some(Decimal::from_str("-0.001").unwrap()),
129 None,
130 Some(Ustr::from("O-123")),
131 UUID4::default(),
132 UnixNanos::from(1_000_000_000),
133 UnixNanos::from(2_000_000_000),
134 )
135 }
136
137 fn create_test_funding_adjustment() -> PositionAdjusted {
138 PositionAdjusted::new(
139 TraderId::from("TRADER-001"),
140 StrategyId::from("EMA-CROSS"),
141 InstrumentId::from("BTCUSD-PERP.BINANCE"),
142 PositionId::from("P-002"),
143 AccountId::from("BINANCE-001"),
144 PositionAdjustmentType::Funding,
145 None,
146 Some(Money::new(-5.50, Currency::USD())),
147 Some(Ustr::from("funding_2024_01_15_08:00")),
148 UUID4::default(),
149 UnixNanos::from(1_000_000_000),
150 UnixNanos::from(2_000_000_000),
151 )
152 }
153
154 #[rstest]
155 fn test_position_adjustment_different_types() {
156 let commission = create_test_commission_adjustment();
157 let funding = create_test_funding_adjustment();
158
159 assert_eq!(
160 commission.adjustment_type,
161 PositionAdjustmentType::Commission
162 );
163 assert_eq!(funding.adjustment_type, PositionAdjustmentType::Funding);
164 assert_ne!(commission.adjustment_type, funding.adjustment_type);
165 }
166
167 #[rstest]
168 fn test_position_adjustment_serialization() {
169 let original = create_test_commission_adjustment();
170
171 let json = serde_json::to_string(&original).unwrap();
172 let deserialized: PositionAdjusted = serde_json::from_str(&json).unwrap();
173
174 assert_eq!(original, deserialized);
175 }
176}