1use rstest::fixture;
19use rust_decimal::prelude::ToPrimitive;
20
21use crate::{
22 data::order::BookOrder,
23 enums::{BookType, LiquiditySide, OrderSide, OrderType},
24 identifiers::InstrumentId,
25 instruments::{stubs::audusd_sim, CurrencyPair, InstrumentAny},
26 orderbook::OrderBook,
27 orders::{builder::OrderTestBuilder, stubs::TestOrderEventStubs},
28 position::Position,
29 types::{Money, Price, Quantity},
30};
31
32pub fn calculate_commission(
34 instrument: &InstrumentAny,
35 last_qty: Quantity,
36 last_px: Price,
37 use_quote_for_inverse: Option<bool>,
38) -> anyhow::Result<Money> {
39 let liquidity_side = LiquiditySide::Taker;
40 assert_ne!(
41 liquidity_side,
42 LiquiditySide::NoLiquiditySide,
43 "Invalid liquidity side"
44 );
45 let notional = instrument
46 .calculate_notional_value(last_qty, last_px, use_quote_for_inverse)
47 .as_f64();
48 let commission = if liquidity_side == LiquiditySide::Maker {
49 notional * instrument.maker_fee().to_f64().unwrap()
50 } else if liquidity_side == LiquiditySide::Taker {
51 notional * instrument.taker_fee().to_f64().unwrap()
52 } else {
53 panic!("Invalid liquidity side {liquidity_side}")
54 };
55 if instrument.is_inverse() && !use_quote_for_inverse.unwrap_or(false) {
56 Ok(Money::new(commission, instrument.base_currency().unwrap()))
57 } else {
58 Ok(Money::new(commission, instrument.quote_currency()))
59 }
60}
61
62#[fixture]
63pub fn stub_position_long(audusd_sim: CurrencyPair) -> Position {
64 let audusd_sim = InstrumentAny::CurrencyPair(audusd_sim);
65 let order = OrderTestBuilder::new(OrderType::Market)
66 .instrument_id(audusd_sim.id())
67 .side(OrderSide::Buy)
68 .quantity(Quantity::from(1))
69 .build();
70 let filled = TestOrderEventStubs::order_filled(
71 &order,
72 &audusd_sim,
73 None,
74 None,
75 Some(Price::from("1.0002")),
76 None,
77 None,
78 None,
79 None,
80 None,
81 );
82 Position::new(&audusd_sim, filled.into())
83}
84
85#[fixture]
86pub fn stub_position_short(audusd_sim: CurrencyPair) -> Position {
87 let audusd_sim = InstrumentAny::CurrencyPair(audusd_sim);
88 let order = OrderTestBuilder::new(OrderType::Market)
89 .instrument_id(audusd_sim.id())
90 .side(OrderSide::Sell)
91 .quantity(Quantity::from(1))
92 .build();
93 let filled = TestOrderEventStubs::order_filled(
94 &order,
95 &audusd_sim,
96 None,
97 None,
98 Some(Price::from("22000.0")),
99 None,
100 None,
101 None,
102 None,
103 None,
104 );
105 Position::new(&audusd_sim, filled.into())
106}
107
108#[must_use]
109pub fn stub_order_book_mbp_appl_xnas() -> OrderBook {
110 stub_order_book_mbp(
111 InstrumentId::from("AAPL.XNAS"),
112 101.0,
113 100.0,
114 100.0,
115 100.0,
116 2,
117 0.01,
118 0,
119 100.0,
120 10,
121 )
122}
123
124#[allow(clippy::too_many_arguments)]
125#[must_use]
126pub fn stub_order_book_mbp(
127 instrument_id: InstrumentId,
128 top_ask_price: f64,
129 top_bid_price: f64,
130 top_ask_size: f64,
131 top_bid_size: f64,
132 price_precision: u8,
133 price_increment: f64,
134 size_precision: u8,
135 size_increment: f64,
136 num_levels: usize,
137) -> OrderBook {
138 let mut book = OrderBook::new(instrument_id, BookType::L2_MBP);
139
140 for i in 0..num_levels {
142 let price = Price::new(
143 price_increment.mul_add(-(i as f64), top_bid_price),
144 price_precision,
145 );
146 let size = Quantity::new(
147 size_increment.mul_add(i as f64, top_bid_size),
148 size_precision,
149 );
150 let order = BookOrder::new(
151 OrderSide::Buy,
152 price,
153 size,
154 0, );
156 book.add(order, 0, 1, 2.into());
157 }
158
159 for i in 0..num_levels {
161 let price = Price::new(
162 price_increment.mul_add(i as f64, top_ask_price),
163 price_precision,
164 );
165 let size = Quantity::new(
166 size_increment.mul_add(i as f64, top_ask_size),
167 size_precision,
168 );
169 let order = BookOrder::new(
170 OrderSide::Sell,
171 price,
172 size,
173 0, );
175 book.add(order, 0, 1, 2.into());
176 }
177
178 book
179}