use nautilus_core::{UUID4, UnixNanos};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use ustr::Ustr;
use crate::{
enums::PositionAdjustmentType,
identifiers::{AccountId, InstrumentId, PositionId, StrategyId, TraderId},
types::Money,
};
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
)]
pub struct PositionAdjusted {
pub trader_id: TraderId,
pub strategy_id: StrategyId,
pub instrument_id: InstrumentId,
pub position_id: PositionId,
pub account_id: AccountId,
pub adjustment_type: PositionAdjustmentType,
pub quantity_change: Option<Decimal>,
pub pnl_change: Option<Money>,
pub reason: Option<Ustr>,
pub event_id: UUID4,
pub ts_event: UnixNanos,
pub ts_init: UnixNanos,
}
impl PositionAdjusted {
#[allow(clippy::too_many_arguments)]
pub fn new(
trader_id: TraderId,
strategy_id: StrategyId,
instrument_id: InstrumentId,
position_id: PositionId,
account_id: AccountId,
adjustment_type: PositionAdjustmentType,
quantity_change: Option<Decimal>,
pnl_change: Option<Money>,
reason: Option<Ustr>,
event_id: UUID4,
ts_event: UnixNanos,
ts_init: UnixNanos,
) -> Self {
Self {
trader_id,
strategy_id,
instrument_id,
position_id,
account_id,
adjustment_type,
quantity_change,
pnl_change,
reason,
event_id,
ts_event,
ts_init,
}
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use nautilus_core::UnixNanos;
use rstest::*;
use super::*;
use crate::{
enums::PositionAdjustmentType,
identifiers::{AccountId, InstrumentId, PositionId, StrategyId, TraderId},
types::{Currency, Money},
};
fn create_test_commission_adjustment() -> PositionAdjusted {
PositionAdjusted::new(
TraderId::from("TRADER-001"),
StrategyId::from("EMA-CROSS"),
InstrumentId::from("BTCUSDT.BINANCE"),
PositionId::from("P-001"),
AccountId::from("BINANCE-001"),
PositionAdjustmentType::Commission,
Some(Decimal::from_str("-0.001").unwrap()),
None,
Some(Ustr::from("O-123")),
UUID4::default(),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
)
}
fn create_test_funding_adjustment() -> PositionAdjusted {
PositionAdjusted::new(
TraderId::from("TRADER-001"),
StrategyId::from("EMA-CROSS"),
InstrumentId::from("BTCUSD-PERP.BINANCE"),
PositionId::from("P-002"),
AccountId::from("BINANCE-001"),
PositionAdjustmentType::Funding,
None,
Some(Money::new(-5.50, Currency::USD())),
Some(Ustr::from("funding_2024_01_15_08:00")),
UUID4::default(),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
)
}
#[rstest]
fn test_position_adjustment_different_types() {
let commission = create_test_commission_adjustment();
let funding = create_test_funding_adjustment();
assert_eq!(
commission.adjustment_type,
PositionAdjustmentType::Commission
);
assert_eq!(funding.adjustment_type, PositionAdjustmentType::Funding);
assert_ne!(commission.adjustment_type, funding.adjustment_type);
}
#[rstest]
fn test_position_adjustment_serialization() {
let original = create_test_commission_adjustment();
let json = serde_json::to_string(&original).unwrap();
let deserialized: PositionAdjusted = serde_json::from_str(&json).unwrap();
assert_eq!(original, deserialized);
}
}