use crate::{
error::OrderError,
margin::MarginEffect,
market::MarketSnapshot,
order::{
OrderEvent, OrderKind, TimeInForce,
id::{PositionId, VenueOrderId},
state::Cancelled,
},
};
use derive_more::Constructor;
use rust_decimal::Decimal;
use rustrade_instrument::{
Side,
asset::{AssetIndex, name::AssetNameExchange},
exchange::{ExchangeId, ExchangeIndex},
instrument::{InstrumentIndex, name::InstrumentNameExchange},
};
use serde::{Deserialize, Serialize};
pub type OrderRequestOpen<ExchangeKey = ExchangeIndex, InstrumentKey = InstrumentIndex> =
OrderEvent<RequestOpen, ExchangeKey, InstrumentKey>;
pub type OrderRequestCancel<ExchangeKey = ExchangeIndex, InstrumentKey = InstrumentIndex> =
OrderEvent<RequestCancel, ExchangeKey, InstrumentKey>;
pub type OrderResponseCancel<
ExchangeKey = ExchangeIndex,
AssetKey = AssetIndex,
InstrumentKey = InstrumentIndex,
> = OrderEvent<Result<Cancelled, OrderError<AssetKey, InstrumentKey>>, ExchangeKey, InstrumentKey>;
pub type UnindexedOrderResponseCancel =
OrderResponseCancel<ExchangeId, AssetNameExchange, InstrumentNameExchange>;
#[non_exhaustive]
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
pub struct RequestOpen {
pub side: Side,
pub price: Option<Decimal>,
pub quantity: Decimal,
pub kind: OrderKind,
pub time_in_force: TimeInForce,
#[serde(default)]
pub position_id: Option<PositionId>,
#[serde(default)]
pub reduce_only: bool,
#[serde(default)]
pub market: Option<MarketSnapshot>,
#[serde(default)]
pub margin_effect: Option<MarginEffect>,
}
impl RequestOpen {
pub fn new(
side: Side,
price: Option<Decimal>,
quantity: Decimal,
kind: OrderKind,
time_in_force: TimeInForce,
) -> Self {
Self {
side,
price,
quantity,
kind,
time_in_force,
position_id: None,
reduce_only: false,
market: None,
margin_effect: None,
}
}
#[must_use]
pub fn with_position_id(self, position_id: PositionId) -> Self {
Self {
position_id: Some(position_id),
..self
}
}
#[must_use]
pub fn with_reduce_only(self, reduce_only: bool) -> Self {
Self {
reduce_only,
..self
}
}
#[must_use]
pub fn with_market(self, market: MarketSnapshot) -> Self {
Self {
market: Some(market),
..self
}
}
#[must_use]
pub fn with_margin_effect(self, margin_effect: MarginEffect) -> Self {
Self {
margin_effect: Some(margin_effect),
..self
}
}
}
#[derive(
Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default, Deserialize, Serialize, Constructor,
)]
pub struct RequestCancel {
pub id: Option<VenueOrderId>,
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
fn limit() -> RequestOpen {
RequestOpen::new(
Side::Sell,
Some(dec!(101)),
dec!(2),
OrderKind::Limit,
TimeInForce::GoodUntilCancelled { post_only: false },
)
}
#[test]
fn new_sets_the_given_fields_and_defaults_the_rest() {
let request = limit();
assert_eq!(request.side, Side::Sell);
assert_eq!(request.price, Some(dec!(101)));
assert_eq!(request.quantity, dec!(2));
assert_eq!(request.kind, OrderKind::Limit);
assert_eq!(
request.time_in_force,
TimeInForce::GoodUntilCancelled { post_only: false }
);
assert_eq!(request.position_id, None);
assert!(!request.reduce_only);
assert_eq!(request.market, None);
assert_eq!(request.margin_effect, None);
}
#[test]
#[allow(clippy::unwrap_used)] fn a_request_serialised_without_a_margin_effect_still_loads() {
let mut json = serde_json::to_value(limit()).unwrap();
json.as_object_mut().unwrap().remove("margin_effect");
let request: RequestOpen = serde_json::from_value(json).unwrap();
assert_eq!(request.margin_effect, None);
}
#[test]
fn with_methods_set_only_their_own_field() {
let set = RequestOpen {
position_id: Some(PositionId::new("a")),
reduce_only: true,
market: Some(MarketSnapshot::from_last_price(Some(dec!(100)))),
margin_effect: Some(MarginEffect::Borrow),
..limit()
};
assert_eq!(
set.clone().with_position_id(PositionId::new("b")),
RequestOpen {
position_id: Some(PositionId::new("b")),
..set.clone()
}
);
assert_eq!(
set.clone().with_reduce_only(false),
RequestOpen {
reduce_only: false,
..set.clone()
}
);
let market = MarketSnapshot::from_last_price(Some(dec!(200)));
assert_eq!(
set.clone().with_market(market),
RequestOpen {
market: Some(market),
..set.clone()
}
);
assert_eq!(
set.clone().with_margin_effect(MarginEffect::Repay),
RequestOpen {
margin_effect: Some(MarginEffect::Repay),
..set
}
);
assert_eq!(
limit().with_position_id(PositionId::NETTING).position_id,
Some(PositionId::NETTING)
);
assert!(limit().with_reduce_only(true).reduce_only);
assert_eq!(limit().with_market(market).market, Some(market));
assert_eq!(
limit()
.with_margin_effect(MarginEffect::Repay)
.margin_effect,
Some(MarginEffect::Repay)
);
}
}