use crate::param::{AdjustmentAmount, Asset, Leverage, Pnl, PositionMode, PositionSize, Price};
use crate::{impl_request_has_field, impl_request_has_field_passthrough};
use super::{
HasAccountAdjustmentBalance, HasAccountAdjustmentBalanceAverageEntryPrice,
HasAccountAdjustmentBalanceLowerBound, HasAccountAdjustmentBalanceRealizedPnl,
HasAccountAdjustmentBalanceUpperBound, HasAccountAdjustmentHeld,
HasAccountAdjustmentHeldLowerBound, HasAccountAdjustmentHeldUpperBound,
HasAccountAdjustmentIncoming, HasAccountAdjustmentIncomingLowerBound,
HasAccountAdjustmentIncomingUpperBound, HasAccountAdjustmentPositionLeverage,
HasAverageEntryPrice, HasBalanceAsset, HasCollateralAsset, HasPositionInstrument,
HasPositionMode, Instrument,
};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct AccountAdjustmentAmount {
pub balance: Option<AdjustmentAmount>,
pub held: Option<AdjustmentAmount>,
pub incoming: Option<AdjustmentAmount>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WithAccountAdjustmentAmount<T> {
pub inner: T,
pub amount: AccountAdjustmentAmount,
}
impl_request_has_field!(
AccountAdjustmentAmount,
WithAccountAdjustmentAmount,
amount,
HasAccountAdjustmentBalance, balance, Option<AdjustmentAmount>, balance;
HasAccountAdjustmentHeld, held, Option<AdjustmentAmount>, held;
HasAccountAdjustmentIncoming, incoming, Option<AdjustmentAmount>, incoming;
);
impl_request_has_field_passthrough!(
WithAccountAdjustmentAmount,
inner,
HasBalanceAsset, balance_asset, &Asset;
HasAccountAdjustmentBalanceAverageEntryPrice, balance_average_entry_price, Option<Price>;
HasAccountAdjustmentBalanceRealizedPnl, balance_realized_pnl, Option<Pnl>;
HasPositionInstrument, position_instrument, &Instrument;
HasCollateralAsset, collateral_asset, &Asset;
HasAverageEntryPrice, average_entry_price, Price;
HasPositionMode, position_mode, PositionMode;
HasAccountAdjustmentPositionLeverage, position_leverage, Option<Leverage>;
HasAccountAdjustmentBalanceUpperBound, balance_upper, Option<PositionSize>;
HasAccountAdjustmentBalanceLowerBound, balance_lower, Option<PositionSize>;
HasAccountAdjustmentHeldUpperBound, held_upper, Option<PositionSize>;
HasAccountAdjustmentHeldLowerBound, held_lower, Option<PositionSize>;
HasAccountAdjustmentIncomingUpperBound, incoming_upper, Option<PositionSize>;
HasAccountAdjustmentIncomingLowerBound, incoming_lower, Option<PositionSize>;
);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AccountAdjustmentBalanceOperation {
pub asset: Asset,
pub average_entry_price: Option<Price>,
pub realized_pnl: Option<Pnl>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WithAccountAdjustmentBalanceOperation<T> {
pub inner: T,
pub operation: AccountAdjustmentBalanceOperation,
}
impl_request_has_field!(
AccountAdjustmentBalanceOperation,
WithAccountAdjustmentBalanceOperation,
operation,
HasBalanceAsset, balance_asset, &Asset, asset;
);
impl_request_has_field!(
AccountAdjustmentBalanceOperation,
WithAccountAdjustmentBalanceOperation,
operation,
HasAccountAdjustmentBalanceAverageEntryPrice, balance_average_entry_price, Option<Price>, average_entry_price;
HasAccountAdjustmentBalanceRealizedPnl, balance_realized_pnl, Option<Pnl>, realized_pnl;
);
impl_request_has_field_passthrough!(
WithAccountAdjustmentBalanceOperation,
inner,
HasAverageEntryPrice, average_entry_price, Price;
HasAccountAdjustmentBalance, balance, Option<AdjustmentAmount>;
HasAccountAdjustmentHeld, held, Option<AdjustmentAmount>;
HasAccountAdjustmentIncoming, incoming, Option<AdjustmentAmount>;
HasAccountAdjustmentBalanceUpperBound, balance_upper, Option<PositionSize>;
HasAccountAdjustmentBalanceLowerBound, balance_lower, Option<PositionSize>;
HasAccountAdjustmentHeldUpperBound, held_upper, Option<PositionSize>;
HasAccountAdjustmentHeldLowerBound, held_lower, Option<PositionSize>;
HasAccountAdjustmentIncomingUpperBound, incoming_upper, Option<PositionSize>;
HasAccountAdjustmentIncomingLowerBound, incoming_lower, Option<PositionSize>;
HasPositionInstrument, position_instrument, &Instrument;
HasCollateralAsset, collateral_asset, &Asset;
HasPositionMode, position_mode, PositionMode;
HasAccountAdjustmentPositionLeverage, position_leverage, Option<Leverage>;
);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AccountAdjustmentPositionOperation {
pub instrument: Instrument,
pub collateral_asset: Asset,
pub average_entry_price: Price,
pub mode: PositionMode,
pub leverage: Option<Leverage>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WithAccountAdjustmentPositionOperation<T> {
pub inner: T,
pub operation: AccountAdjustmentPositionOperation,
}
impl_request_has_field!(
AccountAdjustmentPositionOperation,
WithAccountAdjustmentPositionOperation,
operation,
HasPositionInstrument, position_instrument, &Instrument, instrument;
HasCollateralAsset, collateral_asset, &Asset, collateral_asset;
);
impl_request_has_field!(
AccountAdjustmentPositionOperation,
WithAccountAdjustmentPositionOperation,
operation,
HasAverageEntryPrice, average_entry_price, Price, average_entry_price;
HasPositionMode, position_mode, PositionMode, mode;
HasAccountAdjustmentPositionLeverage, position_leverage, Option<Leverage>, leverage;
);
impl_request_has_field_passthrough!(
WithAccountAdjustmentPositionOperation,
inner,
HasBalanceAsset, balance_asset, &Asset;
HasAccountAdjustmentBalanceAverageEntryPrice, balance_average_entry_price, Option<Price>;
HasAccountAdjustmentBalanceRealizedPnl, balance_realized_pnl, Option<Pnl>;
HasAccountAdjustmentBalance, balance, Option<AdjustmentAmount>;
HasAccountAdjustmentHeld, held, Option<AdjustmentAmount>;
HasAccountAdjustmentIncoming, incoming, Option<AdjustmentAmount>;
HasAccountAdjustmentBalanceUpperBound, balance_upper, Option<PositionSize>;
HasAccountAdjustmentBalanceLowerBound, balance_lower, Option<PositionSize>;
HasAccountAdjustmentHeldUpperBound, held_upper, Option<PositionSize>;
HasAccountAdjustmentHeldLowerBound, held_lower, Option<PositionSize>;
HasAccountAdjustmentIncomingUpperBound, incoming_upper, Option<PositionSize>;
HasAccountAdjustmentIncomingLowerBound, incoming_lower, Option<PositionSize>;
);
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct AccountAdjustmentBounds {
pub balance_upper: Option<PositionSize>,
pub balance_lower: Option<PositionSize>,
pub held_upper: Option<PositionSize>,
pub held_lower: Option<PositionSize>,
pub incoming_upper: Option<PositionSize>,
pub incoming_lower: Option<PositionSize>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WithAccountAdjustmentBounds<T> {
pub inner: T,
pub bounds: AccountAdjustmentBounds,
}
impl_request_has_field!(
AccountAdjustmentBounds,
WithAccountAdjustmentBounds,
bounds,
HasAccountAdjustmentBalanceUpperBound, balance_upper, Option<PositionSize>, balance_upper;
HasAccountAdjustmentBalanceLowerBound, balance_lower, Option<PositionSize>, balance_lower;
HasAccountAdjustmentHeldUpperBound, held_upper, Option<PositionSize>, held_upper;
HasAccountAdjustmentHeldLowerBound, held_lower, Option<PositionSize>, held_lower;
HasAccountAdjustmentIncomingUpperBound, incoming_upper, Option<PositionSize>, incoming_upper;
HasAccountAdjustmentIncomingLowerBound, incoming_lower, Option<PositionSize>, incoming_lower;
);
impl_request_has_field_passthrough!(
WithAccountAdjustmentBounds,
inner,
HasBalanceAsset, balance_asset, &Asset;
HasAccountAdjustmentBalanceAverageEntryPrice, balance_average_entry_price, Option<Price>;
HasAccountAdjustmentBalanceRealizedPnl, balance_realized_pnl, Option<Pnl>;
HasPositionInstrument, position_instrument, &Instrument;
HasCollateralAsset, collateral_asset, &Asset;
HasAverageEntryPrice, average_entry_price, Price;
HasPositionMode, position_mode, PositionMode;
HasAccountAdjustmentPositionLeverage, position_leverage, Option<Leverage>;
HasAccountAdjustmentBalance, balance, Option<AdjustmentAmount>;
HasAccountAdjustmentHeld, held, Option<AdjustmentAmount>;
HasAccountAdjustmentIncoming, incoming, Option<AdjustmentAmount>;
);
#[cfg(test)]
mod tests {
use super::{
AccountAdjustmentAmount, AccountAdjustmentBalanceOperation, AccountAdjustmentBounds,
AccountAdjustmentPositionOperation, WithAccountAdjustmentAmount,
WithAccountAdjustmentBalanceOperation, WithAccountAdjustmentBounds,
WithAccountAdjustmentPositionOperation,
};
use crate::param::{AdjustmentAmount, Asset, Leverage, Pnl, PositionMode, PositionSize, Price};
use crate::{
HasAccountAdjustmentBalance, HasAccountAdjustmentBalanceAverageEntryPrice,
HasAccountAdjustmentBalanceLowerBound, HasAccountAdjustmentBalanceRealizedPnl,
HasAccountAdjustmentBalanceUpperBound, HasAccountAdjustmentHeld,
HasAccountAdjustmentHeldLowerBound, HasAccountAdjustmentHeldUpperBound,
HasAccountAdjustmentIncoming, HasAccountAdjustmentIncomingLowerBound,
HasAccountAdjustmentIncomingUpperBound, HasAccountAdjustmentPositionLeverage,
HasAverageEntryPrice, HasBalanceAsset, HasCollateralAsset, HasPositionInstrument,
HasPositionMode, Instrument,
};
#[test]
fn direct_trait_access_for_balance_operation() {
let asset = Asset::new("USD").expect("must be valid");
let average = Price::from_str("1.25").expect("must be valid");
let realized = Pnl::from_str("42").expect("must be valid");
let operation = AccountAdjustmentBalanceOperation {
asset: asset.clone(),
average_entry_price: Some(average),
realized_pnl: Some(realized),
};
assert_eq!(operation.balance_asset(), Ok(&asset));
assert_eq!(operation.balance_average_entry_price(), Ok(Some(average)));
assert_eq!(operation.balance_realized_pnl(), Ok(Some(realized)));
}
#[test]
fn direct_trait_access_for_position_operation() {
let instrument = Instrument::new(
Asset::new("AAPL").expect("must be valid"),
Asset::new("USD").expect("must be valid"),
);
let collateral = Asset::new("USD").expect("must be valid");
let leverage = Leverage::from_u16(25).expect("must be valid");
let operation = AccountAdjustmentPositionOperation {
instrument: instrument.clone(),
collateral_asset: collateral.clone(),
average_entry_price: Price::from_str("100").expect("must be valid"),
mode: PositionMode::Hedged,
leverage: Some(leverage),
};
assert_eq!(operation.position_instrument(), Ok(&instrument));
assert_eq!(operation.collateral_asset(), Ok(&collateral));
assert_eq!(
operation.average_entry_price(),
Ok(Price::from_str("100").expect("must be valid"))
);
assert_eq!(operation.position_mode(), Ok(PositionMode::Hedged));
assert_eq!(operation.position_leverage(), Ok(Some(leverage)));
}
#[test]
fn direct_trait_access_for_position_operation_fractional_leverage() {
let leverage = Leverage::from_f64(100.5).expect("must be valid");
let operation = AccountAdjustmentPositionOperation {
instrument: Instrument::new(
Asset::new("AAPL").expect("must be valid"),
Asset::new("USD").expect("must be valid"),
),
collateral_asset: Asset::new("EUR").expect("must be valid"),
average_entry_price: Price::from_str("100").expect("must be valid"),
mode: PositionMode::Hedged,
leverage: Some(leverage),
};
assert_eq!(operation.position_leverage(), Ok(Some(leverage)));
assert_eq!(leverage.raw(), 1005);
}
#[test]
fn direct_trait_access_for_amount_and_bounds() {
let balance =
AdjustmentAmount::Absolute(PositionSize::from_str("4").expect("must be valid"));
let held = AdjustmentAmount::Delta(PositionSize::from_str("-1").expect("must be valid"));
let amount = AccountAdjustmentAmount {
balance: Some(balance),
held: Some(held),
incoming: None,
};
assert_eq!(amount.balance(), Ok(Some(balance)));
assert_eq!(amount.held(), Ok(Some(held)));
assert_eq!(amount.incoming(), Ok(None));
let bound = PositionSize::from_str("10").expect("must be valid");
let bounds = AccountAdjustmentBounds {
balance_upper: Some(bound),
balance_lower: None,
held_upper: None,
held_lower: None,
incoming_upper: None,
incoming_lower: None,
};
assert_eq!(bounds.balance_upper(), Ok(Some(bound)));
assert_eq!(bounds.incoming_lower(), Ok(None));
}
#[test]
fn with_wrappers_preserve_access_chain() {
let base = WithAccountAdjustmentAmount {
inner: (),
amount: AccountAdjustmentAmount {
balance: Some(AdjustmentAmount::Absolute(
PositionSize::from_str("7").expect("must be valid"),
)),
held: None,
incoming: None,
},
};
let with_bounds = WithAccountAdjustmentBounds {
inner: base,
bounds: AccountAdjustmentBounds {
balance_upper: Some(PositionSize::from_str("8").expect("must be valid")),
balance_lower: None,
held_upper: None,
held_lower: None,
incoming_upper: None,
incoming_lower: None,
},
};
let with_balance = WithAccountAdjustmentBalanceOperation {
inner: with_bounds,
operation: AccountAdjustmentBalanceOperation {
asset: Asset::new("USD").expect("must be valid"),
average_entry_price: None,
realized_pnl: None,
},
};
assert!(with_balance.balance().expect("must be available").is_some());
assert!(with_balance
.balance_upper()
.expect("must be available")
.is_some());
assert_eq!(with_balance.balance_average_entry_price(), Ok(None));
let wrapped_position = WithAccountAdjustmentPositionOperation {
inner: with_balance,
operation: AccountAdjustmentPositionOperation {
instrument: Instrument::new(
Asset::new("AAPL").expect("must be valid"),
Asset::new("USD").expect("must be valid"),
),
collateral_asset: Asset::new("USD").expect("must be valid"),
average_entry_price: Price::from_str("1").expect("must be valid"),
mode: PositionMode::Netting,
leverage: None,
},
};
assert_eq!(wrapped_position.position_mode(), Ok(PositionMode::Netting));
assert_eq!(
wrapped_position.average_entry_price(),
Ok(Price::from_str("1").expect("must be valid"))
);
assert_eq!(wrapped_position.balance_average_entry_price(), Ok(None));
assert_eq!(wrapped_position.position_leverage(), Ok(None));
}
#[test]
fn borrowed_values_come_from_original_fields() {
let instrument = Instrument::new(
Asset::new("AAPL").expect("must be valid"),
Asset::new("USD").expect("must be valid"),
);
let collateral = Asset::new("EUR").expect("must be valid");
let position = AccountAdjustmentPositionOperation {
instrument: instrument.clone(),
collateral_asset: collateral.clone(),
average_entry_price: Price::from_str("10").expect("must be valid"),
mode: PositionMode::Hedged,
leverage: None,
};
assert_eq!(position.position_instrument(), Ok(&instrument));
assert_eq!(position.collateral_asset(), Ok(&collateral));
let balance = AccountAdjustmentBalanceOperation {
asset: collateral.clone(),
average_entry_price: None,
realized_pnl: None,
};
assert_eq!(balance.balance_asset(), Ok(&collateral));
}
#[test]
fn outer_amount_wrapper_passthroughs_position_branch_traits() {
let instrument = Instrument::new(
Asset::new("AAPL").expect("must be valid"),
Asset::new("USD").expect("must be valid"),
);
let collateral = Asset::new("EUR").expect("must be valid");
let average = Price::from_str("123").expect("must be valid");
let leverage = Leverage::from_u16(10).expect("must be valid");
let balance =
AdjustmentAmount::Absolute(PositionSize::from_str("2").expect("must be valid"));
let incoming = AdjustmentAmount::Delta(PositionSize::from_str("1").expect("must be valid"));
let balance_upper = PositionSize::from_str("5").expect("must be valid");
let incoming_lower = PositionSize::from_str("-2").expect("must be valid");
let incoming_upper = PositionSize::from_str("6").expect("must be valid");
let request = WithAccountAdjustmentAmount {
inner: WithAccountAdjustmentBounds {
inner: WithAccountAdjustmentPositionOperation {
inner: (),
operation: AccountAdjustmentPositionOperation {
instrument: instrument.clone(),
collateral_asset: collateral.clone(),
average_entry_price: average,
mode: PositionMode::Hedged,
leverage: Some(leverage),
},
},
bounds: AccountAdjustmentBounds {
balance_upper: Some(balance_upper),
balance_lower: None,
held_upper: None,
held_lower: None,
incoming_upper: Some(incoming_upper),
incoming_lower: Some(incoming_lower),
},
},
amount: AccountAdjustmentAmount {
balance: Some(balance),
held: None,
incoming: Some(incoming),
},
};
assert_eq!(request.balance(), Ok(Some(balance)));
assert_eq!(request.incoming(), Ok(Some(incoming)));
assert_eq!(request.position_instrument(), Ok(&instrument));
assert_eq!(request.collateral_asset(), Ok(&collateral));
assert_eq!(request.average_entry_price(), Ok(average));
assert_eq!(request.position_mode(), Ok(PositionMode::Hedged));
assert_eq!(request.position_leverage(), Ok(Some(leverage)));
assert_eq!(request.balance_upper(), Ok(Some(balance_upper)));
assert_eq!(request.incoming_upper(), Ok(Some(incoming_upper)));
assert_eq!(request.incoming_lower(), Ok(Some(incoming_lower)));
}
#[test]
fn outer_amount_wrapper_passthroughs_balance_branch_traits() {
let asset = Asset::new("EUR").expect("must be valid");
let average = Price::from_str("1.12").expect("must be valid");
let realized = Pnl::from_str("-15").expect("must be valid");
let held = AdjustmentAmount::Delta(PositionSize::from_str("-3").expect("must be valid"));
let incoming =
AdjustmentAmount::Absolute(PositionSize::from_str("4").expect("must be valid"));
let balance_lower = PositionSize::from_str("-8").expect("must be valid");
let held_upper = PositionSize::from_str("9").expect("must be valid");
let held_lower = PositionSize::from_str("-1").expect("must be valid");
let request = WithAccountAdjustmentAmount {
inner: WithAccountAdjustmentBounds {
inner: WithAccountAdjustmentBalanceOperation {
inner: (),
operation: AccountAdjustmentBalanceOperation {
asset: asset.clone(),
average_entry_price: Some(average),
realized_pnl: Some(realized),
},
},
bounds: AccountAdjustmentBounds {
balance_upper: None,
balance_lower: Some(balance_lower),
held_upper: Some(held_upper),
held_lower: Some(held_lower),
incoming_upper: None,
incoming_lower: None,
},
},
amount: AccountAdjustmentAmount {
balance: None,
held: Some(held),
incoming: Some(incoming),
},
};
assert_eq!(request.held(), Ok(Some(held)));
assert_eq!(request.incoming(), Ok(Some(incoming)));
assert_eq!(request.balance_asset(), Ok(&asset));
assert_eq!(request.balance_average_entry_price(), Ok(Some(average)));
assert_eq!(request.balance_realized_pnl(), Ok(Some(realized)));
assert_eq!(request.balance_lower(), Ok(Some(balance_lower)));
assert_eq!(request.held_upper(), Ok(Some(held_upper)));
assert_eq!(request.held_lower(), Ok(Some(held_lower)));
}
}