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