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