1use crate::{
4 AccountEventKind, InstrumentAccountSnapshot, UnindexedAccountEvent, UnindexedAccountSnapshot,
5 balance::{AssetBalance, MarginDetails},
6 borrow_capacity::BorrowCapacity,
7 cash_flow::{CashFlow, CashFlowId, CashFlowKind, InterestTiming},
8 client::mock::MockExecutionConfig,
9 error::{ApiError, BorrowReject, BorrowRejectReason, UnindexedApiError, UnindexedOrderError},
10 exchange::mock::{
11 account::{AccountState, BalanceInsufficient, Committed, Credit, Debit, Delivery, Lending},
12 orders::{AlreadyResting, OpenOrder, Reservation, RestingOrder},
13 },
14 fee::{FeeAssetConfig, FeeModel, FeeModelConfig, Liquidity, SpotFeeAsset},
15 fill::{FillContext, FillModel, MarketOrderTiming, NextBarPrice, StaleMarketPolicy},
16 holding_cost::{HeldPosition, HoldingCostModel},
17 margin::{InterestDue, MarginEffect, MarginInterestModel, SpotMarginConfig},
18 margin_debt::{MarginDebtKind, MarginDebtUpdate},
19 market::{MarketBar, MarketDepth, MarketObservation, MarketSnapshot},
20 order::{
21 Order, OrderKind, TimeInForce, UnindexedInactiveOrder, UnindexedOrder, UnindexedOrderKey,
22 id::{ClientOrderId, OrderId, VenueOrderId},
23 request::{OrderRequestCancel, OrderRequestOpen, UnindexedOrderResponseCancel},
24 state::{Cancelled, Expired, Filled, Open, OrderState, UnindexedOrderState},
25 },
26 position::{Position, PositionReport},
27 shortability::ShortabilityProvider,
28 slippage::Slippage,
29 trade::{AssetFees, TimeSource, Trade, TradeId, TradeOrigin},
30};
31use chrono::{DateTime, Utc};
32use fnv::{FnvHashMap, FnvHashSet};
33use itertools::Itertools;
34use rust_decimal::Decimal;
35use rustrade_instrument::{
36 Side, Underlying,
37 asset::name::AssetNameExchange,
38 exchange::ExchangeId,
39 instrument::{Instrument, kind::InstrumentKind, name::InstrumentNameExchange},
40};
41use rustrade_integration::collection::snapshot::Snapshot;
42use smol_str::ToSmolStr;
43use std::{sync::Arc, time::Duration};
44use tracing::warn;
45
46mod liquidation;
47mod spot_pairs;
48
49use spot_pairs::{SpotPair, SpotPairs};
50
51#[derive(Debug)]
59pub struct VenueOutcome<Response> {
60 pub events: Vec<UnindexedAccountEvent>,
62 pub response: Response,
64}
65
66pub type OpenOutcome = VenueOutcome<Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>>;
68
69pub type CancelOutcome = VenueOutcome<UnindexedOrderResponseCancel>;
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
81pub struct VenueInstrumentMarket {
82 pub snapshot: MarketSnapshot,
84
85 pub depth: MarketDepth,
94
95 pub time_exchange: DateTime<Utc>,
97
98 pub bar: Option<MarketBar>,
104
105 pub fresh_at: Option<DateTime<Utc>>,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum VenueRegime {
122 RequestPriced,
128
129 MarketDriven,
136}
137
138#[derive(Debug)]
534pub struct SimulatedVenue {
535 pub exchange: ExchangeId,
536 pub fee_model: FeeModelConfig,
537 pub spot_fee_asset: SpotFeeAsset,
539 fill_model: Arc<dyn FillModel>,
543 slippage: Slippage,
546 instruments: FnvHashMap<InstrumentNameExchange, Instrument<ExchangeId, AssetNameExchange>>,
550 spot_pairs: SpotPairs,
553 pub account: AccountState,
554 market: FnvHashMap<InstrumentNameExchange, VenueInstrumentMarket>,
560 positions: FnvHashMap<InstrumentNameExchange, NetPosition>,
567 order_sequence: u64,
573 order_id_start: u128,
580 trade_sequence: u64,
587 time_exchange_latest: DateTime<Utc>,
592 regime: VenueRegime,
596 holding_costs: Vec<Arc<dyn HoldingCostModel>>,
600 accrued_through: Option<DateTime<Utc>>,
612 next_charge: Option<DateTime<Utc>>,
621 cash_flow_sequence: u64,
623 valued_at_entry: bool,
626 shortability: Option<Arc<dyn ShortabilityProvider>>,
629 market_order_timing: MarketOrderTiming,
633 stale_market: StaleMarketPolicy,
637 spot_margin: Option<SpotMarginConfig>,
640 fee_asset: Option<FeeAssetConfig>,
644 margin_interest: Option<Arc<dyn MarginInterestModel>>,
648 next_interest: Option<DateTime<Utc>>,
654 next_interest_loans: u64,
657 reported_debt: FnvHashMap<AssetNameExchange, MarginDetails>,
661 liquidation: liquidation::LiquidationState,
665}
666
667impl SimulatedVenue {
668 pub fn new(
680 config: &MockExecutionConfig,
681 instruments: FnvHashMap<InstrumentNameExchange, Instrument<ExchangeId, AssetNameExchange>>,
682 ) -> Self {
683 Self::with_regime(config, instruments, VenueRegime::RequestPriced)
684 }
685
686 pub fn new_market_driven(
701 config: &MockExecutionConfig,
702 instruments: FnvHashMap<InstrumentNameExchange, Instrument<ExchangeId, AssetNameExchange>>,
703 ) -> Self {
704 Self::with_regime(config, instruments, VenueRegime::MarketDriven)
705 }
706
707 fn with_regime(
708 config: &MockExecutionConfig,
709 instruments: FnvHashMap<InstrumentNameExchange, Instrument<ExchangeId, AssetNameExchange>>,
710 regime: VenueRegime,
711 ) -> Self {
712 let positions = seeded_positions(&config.initial_state, &instruments);
713 let mut account = match AccountState::try_from(config.initial_state.clone()) {
714 Ok(account) => account,
715 Err(error) => panic!("invalid MockExecutionConfig::initial_state: {error}"),
716 };
717 if config.spot_margin.is_some() {
718 account.enable_margin();
719 }
720 let order_id_start = first_unseeded_order_id(&account);
721 let reported_debt = account
722 .balances()
723 .filter_map(|balance| Some((balance.asset.clone(), balance.balance.margin?)))
724 .collect();
725
726 Self {
727 exchange: config.mocked_exchange,
728 fee_model: config.fee_model,
729 spot_fee_asset: config.spot_fee_asset,
730 fill_model: Arc::new(config.fill_model),
731 slippage: Slippage::new(config.slippage),
732 spot_pairs: SpotPairs::new(&instruments),
733 instruments,
734 account,
735 market: FnvHashMap::default(),
736 positions,
737 order_sequence: 0,
738 order_id_start,
739 trade_sequence: 0,
740 time_exchange_latest: Default::default(),
741 regime,
742 holding_costs: Vec::new(),
743 accrued_through: None,
744 next_charge: None,
745 cash_flow_sequence: 0,
746 valued_at_entry: false,
747 shortability: None,
748 market_order_timing: config.market_order_timing,
749 stale_market: config.stale_market,
750 spot_margin: config.spot_margin.clone(),
751 fee_asset: config.fee_asset.clone(),
752 margin_interest: None,
753 next_interest: None,
754 next_interest_loans: 0,
755 reported_debt,
756 liquidation: Default::default(),
757 }
758 }
759
760 #[must_use]
768 pub fn with_holding_costs(mut self, models: Vec<Arc<dyn HoldingCostModel>>) -> Self {
769 self.holding_costs = models;
770 self.refresh_next_charge();
771 self
772 }
773
774 #[must_use]
786 pub fn with_margin_interest(mut self, model: Arc<dyn MarginInterestModel>) -> Self {
787 assert!(
788 self.spot_margin.is_some(),
789 "SimulatedVenue given a margin interest model without MockExecutionConfig::spot_margin: \
790 a cash account takes no loans to charge"
791 );
792 self.margin_interest = Some(model);
793 self.refresh_next_interest();
794 self
795 }
796
797 #[must_use]
810 pub fn with_fill_model(mut self, model: Arc<dyn FillModel>) -> Self {
811 self.fill_model = model;
812 self
813 }
814
815 #[must_use]
820 pub fn with_shortability(mut self, provider: Arc<dyn ShortabilityProvider>) -> Self {
821 self.shortability = Some(provider);
822 self
823 }
824
825 pub fn next_deadline(&self) -> Option<DateTime<Utc>> {
837 let expiry = self.account.orders().next_expiry();
838 expiry
839 .into_iter()
840 .chain(self.next_charge)
841 .chain(self.next_interest)
842 .min()
843 }
844
845 fn refresh_next_charge(&mut self) {
847 self.next_charge = self.accrued_through.and_then(|after| {
848 self.positions
849 .keys()
850 .flat_map(|instrument| {
851 self.holding_costs
852 .iter()
853 .filter_map(move |model| next_boundary(model.as_ref(), instrument, after))
854 })
855 .min()
856 });
857 }
858
859 fn refresh_next_interest(&mut self) {
861 self.next_interest_loans = self.account.loans_changed();
862 let Some(model) = self.margin_interest.as_deref() else {
863 return;
864 };
865 self.next_interest = self.accrued_through.and_then(|after| {
866 self.account
867 .loans()
868 .filter_map(|(asset, _)| next_interest_boundary(model, asset, after))
869 .min()
870 });
871 }
872
873 fn refresh_next_interest_if_loans_changed(&mut self) {
877 if self.account.loans_changed() != self.next_interest_loans {
878 self.refresh_next_interest();
879 }
880 }
881
882 pub fn regime(&self) -> VenueRegime {
884 self.regime
885 }
886
887 #[must_use]
957 pub fn advance_time(&mut self, time_exchange: DateTime<Utc>) -> Vec<UnindexedAccountEvent> {
958 let time_exchange = self.not_before_reached(time_exchange);
959 let mut events = self.accrue(time_exchange);
962 self.time_exchange_latest = time_exchange;
963 self.account.update_time_exchange(time_exchange);
964 events.append(&mut self.sweep_expired(time_exchange));
965 self.check_margin(time_exchange, &mut events);
966 self.refresh_next_interest_if_loans_changed();
967 self.report_debt_changes(&mut events);
968 events
969 }
970
971 fn not_before_reached(&self, time_exchange: DateTime<Utc>) -> DateTime<Utc> {
975 self.accrued_through
976 .map_or(time_exchange, |reached| reached.max(time_exchange))
977 }
978
979 fn accrue(&mut self, now: DateTime<Utc>) -> Vec<UnindexedAccountEvent> {
994 let after = match self.accrued_through {
995 None => {
997 self.accrued_through = Some(now);
998 self.refresh_next_charge();
999 self.refresh_next_interest();
1000 return Vec::new();
1001 }
1002 Some(after) if now <= after => return Vec::new(),
1003 Some(after) => after,
1004 };
1005 self.refresh_next_interest_if_loans_changed();
1008 self.accrued_through = Some(now);
1009
1010 let falls = |next: Option<DateTime<Utc>>| next.is_some_and(|due| due <= now);
1013 if !falls(self.next_charge) && !falls(self.next_interest) {
1014 return Vec::new();
1015 }
1016
1017 let mut due = Vec::new();
1018 for instrument in self.positions.keys() {
1019 for (index, model) in self.holding_costs.iter().enumerate() {
1020 let boundaries = boundaries_through(after, now, |from| {
1021 next_boundary(model.as_ref(), instrument, from)
1022 });
1023 due.extend(
1024 boundaries.map(|boundary| (boundary, Due::Holding(instrument.clone(), index))),
1025 );
1026 }
1027 }
1028 if let Some(model) = self.margin_interest.as_deref() {
1029 for (asset, principal) in self.account.loans() {
1030 let boundaries = boundaries_through(after, now, |from| {
1031 next_interest_boundary(model, asset, from)
1032 });
1033 due.extend(
1034 boundaries.map(|boundary| (boundary, Due::Interest(asset.clone(), principal))),
1035 );
1036 }
1037 }
1038 due.sort_unstable();
1041
1042 let mut events = Vec::with_capacity(due.len() * 2);
1043 for (boundary, due) in due {
1044 match due {
1045 Due::Holding(instrument, index) => {
1046 self.charge_holding_cost(boundary, &instrument, index, &mut events);
1047 }
1048 Due::Interest(asset, principal) => {
1049 self.charge_interest_boundary(boundary, &asset, principal, &mut events);
1050 }
1051 }
1052 }
1053 self.refresh_next_charge();
1054 self.refresh_next_interest();
1055 events
1056 }
1057
1058 fn charge_interest_boundary(
1061 &mut self,
1062 boundary: DateTime<Utc>,
1063 asset: &AssetNameExchange,
1064 principal: Decimal,
1065 events: &mut Vec<UnindexedAccountEvent>,
1066 ) {
1067 if let Some((balance, flow)) =
1068 self.charge_interest(asset, principal, InterestDue::Boundary(boundary))
1069 {
1070 events.push(self.build_account_event(Snapshot(balance)));
1071 events.push(self.build_account_event(AccountEventKind::CashFlow(flow)));
1072 }
1073 }
1074
1075 fn charge_interest(
1082 &mut self,
1083 asset: &AssetNameExchange,
1084 principal: Decimal,
1085 due: InterestDue,
1086 ) -> Option<(
1087 AssetBalance<AssetNameExchange>,
1088 CashFlow<AssetNameExchange, InstrumentNameExchange>,
1089 )> {
1090 let model = self.margin_interest.as_deref()?;
1091 let charge = model.charge(asset, principal, due)?;
1092 assert!(
1093 charge.interest >= Decimal::ZERO,
1094 "MarginInterestModel {model:?} charged negative interest {} on {principal} {asset} \
1095 due {due:?}",
1096 charge.interest
1097 );
1098 if charge.interest.is_zero() {
1099 return None;
1100 }
1101
1102 let time = due.time();
1103 let balance = self.account.charge_interest(asset, charge.interest, time);
1104 let id = CashFlowId::new(self.cash_flow_sequence.to_smolstr());
1105 self.cash_flow_sequence += 1;
1106
1107 let flow = CashFlow::new(
1108 CashFlowKind::MarginInterest {
1109 rate: charge.rate,
1110 principal: Some(principal),
1111 timing: Some(match due {
1112 InterestDue::Borrow(_) => InterestTiming::OnBorrow,
1113 InterestDue::Boundary(_) => InterestTiming::Periodic,
1114 }),
1115 },
1116 asset.clone(),
1117 -charge.interest,
1118 None,
1119 time,
1120 Some(id),
1121 );
1122 Some((balance, flow))
1123 }
1124
1125 fn charge_holding_cost(
1128 &mut self,
1129 boundary: DateTime<Utc>,
1130 instrument: &InstrumentNameExchange,
1131 model: usize,
1132 events: &mut Vec<UnindexedAccountEvent>,
1133 ) {
1134 let (Some(position), Some(terms)) = (
1135 self.positions.get(instrument).copied(),
1136 self.instruments.get(instrument),
1137 ) else {
1138 return;
1139 };
1140 let quote = terms.underlying.quote.clone();
1141 let contract_size = terms.kind.contract_size();
1142 let price = self.valuation(instrument, position);
1143
1144 let held = HeldPosition::new(
1145 instrument,
1146 position.quantity,
1147 contract_size,
1148 position.entry,
1149 price,
1150 );
1151 let Some(charge) = self.holding_costs[model].charge(&held, boundary) else {
1152 return;
1153 };
1154 if charge.amount.is_zero() {
1155 return;
1156 }
1157
1158 let balance = self.account.charge("e, charge.amount, boundary);
1159 let free = balance.balance.free;
1163 if free.is_sign_negative() && !(free - charge.amount).is_sign_negative() {
1164 warn!(
1165 exchange = %self.exchange,
1166 %instrument,
1167 asset = %quote,
1168 amount = %charge.amount,
1169 free = %balance.balance.free,
1170 %boundary,
1171 "SimulatedVenue charged a holding cost the account could not cover: free balance \
1172 is negative, and this venue liquidates no CFD position"
1173 );
1174 }
1175
1176 let id = CashFlowId::new(self.cash_flow_sequence.to_smolstr());
1177 self.cash_flow_sequence += 1;
1178
1179 events.push(self.build_account_event(Snapshot(balance)));
1180 events.push(
1181 self.build_account_event(AccountEventKind::CashFlow(CashFlow::new(
1182 charge.kind,
1183 quote,
1184 charge.amount,
1185 Some(instrument.clone()),
1186 boundary,
1187 Some(id),
1188 ))),
1189 );
1190 }
1191
1192 fn valuation(&mut self, instrument: &InstrumentNameExchange, position: NetPosition) -> Decimal {
1199 let price = self
1200 .market(instrument)
1201 .and_then(|market| mid_or_last(&market.snapshot));
1202
1203 price.unwrap_or_else(|| {
1204 if !self.valued_at_entry {
1205 self.valued_at_entry = true;
1206 warn!(
1207 exchange = %self.exchange,
1208 %instrument,
1209 entry = %position.entry,
1210 "SimulatedVenue has no market price to value a holding cost at, so values it \
1211 at the position's entry price; warned once per venue"
1212 );
1213 }
1214 position.entry
1215 })
1216 }
1217
1218 fn margin_effect(&self, requested: Option<MarginEffect>) -> MarginEffect {
1222 match &self.spot_margin {
1223 Some(config) => requested.unwrap_or(config.margin_effect()),
1224 None => MarginEffect::NoSideEffect,
1225 }
1226 }
1227
1228 fn refused_margin_effect(
1232 &self,
1233 requested: Option<MarginEffect>,
1234 terms: &InstrumentTerms,
1235 ) -> Option<MarginEffect> {
1236 let borrows_here = self.spot_margin.is_some() && !terms.cash_settled;
1237 requested.filter(|effect| !borrows_here && *effect != MarginEffect::NoSideEffect)
1238 }
1239
1240 fn release_reservation(
1244 &mut self,
1245 reservation: Reservation,
1246 time_exchange: DateTime<Utc>,
1247 ) -> Released {
1248 let repays = self.repays_at_cancel();
1249 let balance = self
1250 .account
1251 .release_reservation(&reservation, repays, time_exchange);
1252 Released {
1253 balance,
1254 interest: reservation.unreported_interest,
1255 }
1256 }
1257
1258 fn repays_at_cancel(&self) -> bool {
1261 self.spot_margin
1262 .as_ref()
1263 .is_some_and(SpotMarginConfig::auto_repay_at_cancel)
1264 }
1265
1266 fn borrowable(
1276 &self,
1277 debit: &Debit,
1278 effect: MarginEffect,
1279 terms: &InstrumentTerms,
1280 market: Option<&MarketSnapshot>,
1281 ) -> Decimal {
1282 let Some(config) = self.spot_margin.as_ref() else {
1283 return Decimal::ZERO;
1284 };
1285 if effect != MarginEffect::Borrow
1286 || terms.cash_settled
1287 || self.liquidation.under_way()
1288 || self.account.free(&debit.asset) >= debit.settled + debit.reserved
1289 {
1290 return Decimal::ZERO;
1291 }
1292
1293 let priced = market.map(|market| (&terms.underlying, market));
1294 self.leverage_room(config, &debit.asset, priced)
1296 .unwrap_or(Decimal::ZERO)
1297 }
1298
1299 fn leverage_room(
1307 &self,
1308 config: &SpotMarginConfig,
1309 asset: &AssetNameExchange,
1310 priced: Option<(&Underlying<AssetNameExchange>, &MarketSnapshot)>,
1311 ) -> Option<Decimal> {
1312 let asset_price = self.price_in(asset, config.valuation_asset(), priced)?;
1313 let Valued { equity, debt } = self.valued(config, MarginCheck::Borrow, priced)?;
1314
1315 let headroom = (config.max_leverage() - Decimal::ONE) * equity - debt;
1316 Some(headroom.max(Decimal::ZERO) / asset_price)
1318 }
1319
1320 fn insufficient_error(
1331 &self,
1332 insufficient: BalanceInsufficient,
1333 side: Side,
1334 ) -> UnindexedApiError {
1335 let BalanceInsufficient {
1336 asset,
1337 free,
1338 required,
1339 borrowable,
1340 min_borrow,
1341 max_borrow,
1342 } = insufficient;
1343 let mut message =
1344 format!("Available Balance: {free}, Required Balance inc. fees: {required}");
1345 if let Some(borrowable) = borrowable {
1346 message.push_str(&format!(", Borrowable: {borrowable}"));
1347 }
1348 if let Some(min_borrow) = min_borrow {
1349 message.push_str(&format!(", Minimum Borrow: {min_borrow}"));
1350 }
1351 let Some(max_borrow) = max_borrow else {
1352 return ApiError::BalanceInsufficient(Some(asset), message);
1353 };
1354
1355 let message = format!("{asset} borrow exceeds Max Borrow: {max_borrow}; {message}");
1356 let reject = BorrowReject::new(BorrowRejectReason::BorrowLimitExceeded, None, message);
1357 let reject = match (side, borrowable) {
1358 (Side::Sell, Some(left)) => {
1359 let least = self
1360 .spot_margin
1361 .as_ref()
1362 .map_or(Decimal::ZERO, |config| config.min_borrow(&asset));
1363 reject.with_available(if left < least { Decimal::ZERO } else { left })
1364 }
1365 _ => reject,
1366 };
1367 ApiError::BorrowRejected(reject)
1368 }
1369
1370 fn valued(
1377 &self,
1378 config: &SpotMarginConfig,
1379 check: MarginCheck,
1380 priced: Option<(&Underlying<AssetNameExchange>, &MarketSnapshot)>,
1381 ) -> Option<Valued> {
1382 let mut valued = Valued::default();
1383 for balance in self.account.balances() {
1384 let owed = owed(balance);
1385 let Some(price) = self.price_in(&balance.asset, config.valuation_asset(), priced)
1386 else {
1387 if owed > Decimal::ZERO {
1388 return None;
1389 }
1390 continue;
1391 };
1392 let net = balance.balance.net_asset();
1393 let ratio = match net > Decimal::ZERO {
1396 true => check.collateral_ratio(config, &balance.asset),
1397 false => Decimal::ONE,
1398 };
1399 valued.equity += net * ratio * price;
1400 valued.debt += owed * price;
1401 }
1402 Some(valued)
1403 }
1404
1405 fn price_in(
1413 &self,
1414 asset: &AssetNameExchange,
1415 valuation: &AssetNameExchange,
1416 priced: Option<(&Underlying<AssetNameExchange>, &MarketSnapshot)>,
1417 ) -> Option<Decimal> {
1418 if asset == valuation {
1419 return Some(Decimal::ONE);
1420 }
1421
1422 let snapshot_of = |pair: &SpotPair| {
1425 self.market(&pair.instrument)
1426 .map(|market| market.snapshot)
1427 .or_else(|| {
1428 let (underlying, snapshot) = priced?;
1429 let (base, quote) = match pair.inverse {
1430 true => (valuation, asset),
1431 false => (asset, valuation),
1432 };
1433 (underlying.base == *base && underlying.quote == *quote).then_some(*snapshot)
1434 })
1435 };
1436
1437 self.spot_pairs
1439 .between(asset, valuation)
1440 .iter()
1441 .find_map(|pair| {
1442 let price = mid_or_last(&snapshot_of(pair)?)?;
1443 if price <= Decimal::ZERO {
1445 return None;
1446 }
1447 Some(match pair.inverse {
1448 true => Decimal::ONE / price,
1449 false => price,
1450 })
1451 })
1452 }
1453
1454 fn sweep_expired(&mut self, time_exchange: DateTime<Utc>) -> Vec<UnindexedAccountEvent> {
1465 let expired = self.account.orders().expired_as_of(time_exchange);
1466
1467 if expired.is_empty() {
1468 return Vec::new();
1469 }
1470
1471 let mut events = Vec::with_capacity(expired.len() * 2);
1472
1473 for cid in expired {
1474 if let Some(resting) = self.account.remove_order(&cid) {
1476 events.extend(self.retire_expired(resting, time_exchange));
1477 }
1478 }
1479
1480 events
1481 }
1482
1483 fn retire_expired(
1488 &mut self,
1489 RestingOrder { order, reservation }: RestingOrder,
1490 time_exchange: DateTime<Utc>,
1491 ) -> Vec<UnindexedAccountEvent> {
1492 let flows = reservation
1493 .as_ref()
1494 .map_or(0, |reservation| reservation.unreported_interest.len());
1495 let mut events = Vec::with_capacity(2 + flows);
1496
1497 if let Some(reservation) = reservation {
1501 let Released { balance, interest } =
1502 self.release_reservation(reservation, time_exchange);
1503 events.push(self.build_account_event(Snapshot(balance)));
1504 self.push_interest(&mut events, interest, time_exchange);
1505 }
1506
1507 let expired_id = order.state.id.or_client_id(&order.key.cid);
1508 let expired_order = Order {
1509 key: order.key,
1510 side: order.side,
1511 price: order.price,
1512 quantity: order.quantity,
1513 kind: order.kind,
1514 time_in_force: order.time_in_force,
1515 state: Expired {
1516 id: expired_id,
1517 time_exchange,
1518 filled_quantity: Some(order.state.filled_quantity),
1519 },
1520 };
1521
1522 self.account.ack_expired(expired_order.clone());
1523
1524 events.push(self.build_account_event(Snapshot(UnindexedOrder::from(expired_order))));
1525 events
1526 }
1527
1528 pub fn time_exchange(&self) -> DateTime<Utc> {
1531 self.time_exchange_latest
1532 }
1533
1534 #[must_use]
1636 pub fn apply_market(
1637 &mut self,
1638 instrument: &InstrumentNameExchange,
1639 observation: MarketObservation,
1640 time_exchange: DateTime<Utc>,
1641 ) -> Vec<UnindexedAccountEvent> {
1642 let time_exchange = self.not_before_reached(time_exchange);
1643 let mut events = self.accrue(time_exchange);
1646
1647 let MarketObservation {
1648 snapshot,
1649 depth,
1650 bar,
1651 fresh,
1652 } = observation;
1653 let entry = self.market.entry(instrument.clone()).or_default();
1654 entry.snapshot = snapshot;
1655 entry.depth = depth;
1656 entry.time_exchange = time_exchange;
1657 if bar.is_some() {
1658 entry.bar = bar;
1659 }
1660 if fresh {
1661 entry.fresh_at = Some(time_exchange);
1662 }
1663
1664 events.append(&mut self.sweep_expired(time_exchange));
1667 let crosses_resting = fresh || !matches!(self.stale_market, StaleMarketPolicy::Wait { .. });
1669 match self.market_order_timing {
1670 MarketOrderTiming::NextBar(price) => {
1671 if let Some(bar) = bar {
1672 if self.bar_releases(&observation) {
1673 events.append(&mut self.release(
1674 instrument,
1675 Release::Bar(&bar, price),
1676 time_exchange,
1677 ));
1678 } else {
1679 events.append(&mut self.cancel_held_immediate(instrument, time_exchange));
1680 }
1681 }
1682 if crosses_resting {
1683 events.append(&mut self.match_resting(instrument, snapshot, time_exchange));
1684 }
1685 }
1686 MarketOrderTiming::OnArrival => {
1687 if crosses_resting {
1688 events.append(&mut self.match_resting(instrument, snapshot, time_exchange));
1689 }
1690 if fresh && matches!(self.stale_market, StaleMarketPolicy::Wait { .. }) {
1693 events.append(&mut self.release(
1694 instrument,
1695 Release::Market(&snapshot),
1696 time_exchange,
1697 ));
1698 }
1699 }
1700 }
1701 self.check_margin(time_exchange, &mut events);
1702 self.refresh_next_interest_if_loans_changed();
1703 self.report_debt_changes(&mut events);
1704 events
1705 }
1706
1707 pub fn fills_ahead_of(
1715 &self,
1716 instrument: &InstrumentNameExchange,
1717 observation: &MarketObservation,
1718 ) -> bool {
1719 matches!(self.market_order_timing, MarketOrderTiming::NextBar(_))
1720 && self.bar_releases(observation)
1721 && self.account.orders().holds(instrument)
1722 }
1723
1724 fn bar_releases(&self, observation: &MarketObservation) -> bool {
1727 observation.bar.is_some()
1728 && (observation.fresh || !matches!(self.stale_market, StaleMarketPolicy::Wait { .. }))
1729 }
1730
1731 #[must_use]
1743 pub fn expire_held(&mut self, time_exchange: DateTime<Utc>) -> Vec<UnindexedAccountEvent> {
1744 let held = self.account.orders().held();
1745 let mut events = Vec::with_capacity(held.len() * 2);
1746 for cid in held {
1747 if let Some(resting) = self.account.remove_order(&cid) {
1749 events.extend(self.retire_expired(resting, time_exchange));
1750 }
1751 }
1752 self.refresh_next_interest_if_loans_changed();
1753 self.report_debt_changes(&mut events);
1754 events
1755 }
1756
1757 fn match_resting(
1773 &mut self,
1774 instrument: &InstrumentNameExchange,
1775 snapshot: MarketSnapshot,
1776 time_exchange: DateTime<Utc>,
1777 ) -> Vec<UnindexedAccountEvent> {
1778 let mut crossing: Vec<(ClientOrderId, Decimal)> = Vec::new();
1782 for side in [Side::Buy, Side::Sell] {
1783 crossing.extend(
1784 self.account
1785 .orders()
1786 .resting(instrument, side)
1787 .take_while(|order| {
1788 order
1789 .price
1790 .is_some_and(|limit| crosses(side, limit, &snapshot))
1791 })
1792 .filter_map(|order| Some((order.key.cid.clone(), order.price?))),
1793 );
1794 }
1795
1796 if crossing.is_empty() {
1797 return Vec::new();
1798 }
1799
1800 let terms = match self.instrument_terms(instrument) {
1802 Ok(terms) => terms,
1803 Err(error) => panic!(
1807 "SimulatedVenue holds resting orders on an instrument it cannot price: {error}"
1808 ),
1809 };
1810
1811 let mut events = Vec::with_capacity(crossing.len() * 4);
1812
1813 for (cid, limit) in crossing {
1814 let Some(RestingOrder { order, reservation }) = self.account.remove_order(&cid) else {
1816 continue;
1817 };
1818
1819 let remaining = order.state.quantity_remaining(order.quantity);
1824
1825 debug_assert!(
1826 remaining > Decimal::ZERO,
1827 "resting order {cid} arrived with {} of {} already filled, so crossing it trades \
1828 nothing: an order with no quantity left to trade does not belong on the book",
1829 order.state.filled_quantity,
1830 order.quantity
1831 );
1832
1833 let settlement = self.fill_settlement(
1838 &terms,
1839 self.net_position(&terms, instrument),
1840 order.side,
1841 remaining,
1842 limit,
1843 Liquidity::Maker,
1844 );
1845
1846 let mut committed = match self.settle_resting(
1847 &order,
1848 &terms,
1849 reservation,
1850 &settlement,
1851 time_exchange,
1852 ) {
1853 RestingSettlement::Settled(committed) => committed,
1854 RestingSettlement::Unaffordable { released } => {
1855 events.extend(self.cancel_unfilled(order, released, time_exchange));
1856 continue;
1857 }
1858 };
1859 self.take_fee_apart(&settlement, &mut committed, time_exchange);
1860
1861 let trade = self.print_fill(
1862 &terms,
1863 &order,
1864 Fill {
1865 quantity: remaining,
1866 price: limit,
1867 },
1868 settlement.fees,
1869 time_exchange,
1870 );
1871 let order_id = trade.order_id.clone();
1872
1873 let filled = Order {
1884 key: order.key,
1885 side: order.side,
1886 price: order.price,
1887 quantity: order.quantity,
1888 kind: order.kind,
1889 time_in_force: order.time_in_force,
1890 state: Filled::new(
1891 order_id,
1892 time_exchange,
1893 order.quantity,
1894 order.state.filled_quantity.is_zero().then_some(limit),
1895 ),
1896 };
1897 self.account.ack_filled(filled.clone());
1898 let filled = filled.map_state(OrderState::fully_filled);
1899
1900 self.push_committed(&mut events, committed, Some(trade));
1901 events.push(self.build_account_event(Snapshot(filled)));
1902 }
1903
1904 events
1905 }
1906
1907 fn release(
1913 &mut self,
1914 instrument: &InstrumentNameExchange,
1915 release: Release<'_>,
1916 time_exchange: DateTime<Utc>,
1917 ) -> Vec<UnindexedAccountEvent> {
1918 let held = self.account.take_held(instrument);
1919 if held.is_empty() {
1920 return Vec::new();
1921 }
1922
1923 let terms = match self.instrument_terms(instrument) {
1924 Ok(terms) => terms,
1925 Err(error) => {
1928 panic!("SimulatedVenue holds orders on an instrument it cannot price: {error}")
1929 }
1930 };
1931
1932 let mut events = Vec::with_capacity(held.len() * 4);
1933 for cid in held {
1934 let Some(resting) = self.account.remove_order(&cid) else {
1936 continue;
1937 };
1938 debug_assert_eq!(
1940 resting.order.kind == OrderKind::Limit,
1941 resting.order.price.is_some(),
1942 "SimulatedVenue held {} as {:?} with price {:?}: only a limit order is held with \
1943 a price",
1944 resting.order.key.cid,
1945 resting.order.kind,
1946 resting.order.price
1947 );
1948 events.extend(match resting.order.price {
1949 Some(limit) => {
1950 self.release_held_limit(resting, limit, release, &terms, time_exchange)
1951 }
1952 None => self.fill_held(resting, release, &terms, time_exchange),
1953 });
1954 }
1955 events
1956 }
1957
1958 fn cancel_held_immediate(
1964 &mut self,
1965 instrument: &InstrumentNameExchange,
1966 time_exchange: DateTime<Utc>,
1967 ) -> Vec<UnindexedAccountEvent> {
1968 let orders = self.account.orders();
1969 let immediate = orders
1970 .held_on(instrument)
1971 .iter()
1972 .filter(|cid| {
1973 orders.get(cid).is_some_and(|order| {
1974 matches!(
1975 order.time_in_force,
1976 TimeInForce::ImmediateOrCancel | TimeInForce::FillOrKill
1977 )
1978 })
1979 })
1980 .cloned()
1981 .collect::<Vec<_>>();
1982
1983 let mut events = Vec::with_capacity(immediate.len() * 2);
1984 for cid in immediate {
1985 if let Some(RestingOrder { order, reservation }) = self.account.remove_order(&cid) {
1987 let released = reservation
1988 .map(|reservation| self.release_reservation(reservation, time_exchange));
1989 events.extend(self.cancel_unfilled(order, released, time_exchange));
1990 }
1991 }
1992 events
1993 }
1994
1995 fn fill_held(
2004 &mut self,
2005 RestingOrder { order, reservation }: RestingOrder,
2006 release: Release<'_>,
2007 terms: &InstrumentTerms,
2008 time_exchange: DateTime<Utc>,
2009 ) -> Vec<UnindexedAccountEvent> {
2010 let side = order.side;
2011 let quantity = order.state.quantity_remaining(order.quantity);
2012
2013 let reference = match release {
2014 Release::Bar(bar, price) => {
2015 let reference = price.reference(side, bar);
2020 let at_reference = MarketSnapshot::from_last_price(Some(reference));
2021 Some(
2022 self.fill_model
2023 .fill_price(&FillContext::new(side, quantity, &at_reference))
2024 .unwrap_or(reference),
2025 )
2026 }
2027 Release::Market(market) => self
2029 .fill_model
2030 .fill_price(&FillContext::new(side, quantity, market)),
2031 };
2032 let Some(reference) = reference else {
2036 let released =
2037 reservation.map(|reservation| self.release_reservation(reservation, time_exchange));
2038 return self.cancel_unfilled(order, released, time_exchange);
2039 };
2040
2041 let filled_quantity = match self.depth_available(&order.key.instrument, side) {
2044 Some(available) => quantity.min(available),
2045 None => quantity,
2046 };
2047 let kill = order.time_in_force == TimeInForce::FillOrKill;
2050 if filled_quantity <= Decimal::ZERO || (filled_quantity < quantity && kill) {
2051 let released =
2052 reservation.map(|reservation| self.release_reservation(reservation, time_exchange));
2053 return self.cancel_unfilled(order, released, time_exchange);
2054 }
2055
2056 let slippage_before = self.slippage.clone();
2059 let fill_price = self.slippage.apply(side, reference);
2060
2061 let settlement = self.fill_settlement(
2062 terms,
2063 self.net_position(terms, &order.key.instrument),
2064 side,
2065 filled_quantity,
2066 fill_price,
2067 Liquidity::Taker,
2068 );
2069
2070 let mut committed =
2071 match self.settle_resting_afresh(terms, reservation, &settlement, time_exchange) {
2072 RestingSettlement::Settled(committed) => committed,
2073 RestingSettlement::Unaffordable { released } => {
2074 self.slippage = slippage_before;
2075 return self.cancel_unfilled(order, released, time_exchange);
2076 }
2077 };
2078 self.take_fee_apart(&settlement, &mut committed, time_exchange);
2079
2080 self.consume_depth(&order.key.instrument, side, filled_quantity);
2081 let fill = Fill {
2082 quantity: filled_quantity,
2083 price: fill_price,
2084 };
2085 let trade = self.print_fill(terms, &order, fill, settlement.fees, time_exchange);
2086
2087 let terminal = if filled_quantity == quantity {
2090 let filled = Order {
2091 key: order.key,
2092 side,
2093 price: order.price,
2094 quantity: order.quantity,
2095 kind: order.kind,
2096 time_in_force: order.time_in_force,
2097 state: Filled::new(
2098 trade.order_id.clone(),
2099 time_exchange,
2100 order.quantity,
2101 Some(fill_price),
2102 ),
2103 };
2104 self.account.ack_filled(filled.clone());
2105 filled.map_state(OrderState::fully_filled)
2106 } else {
2107 let cancelled = Order {
2108 key: order.key,
2109 side,
2110 price: order.price,
2111 quantity: order.quantity,
2112 kind: order.kind,
2113 time_in_force: order.time_in_force,
2114 state: Cancelled {
2115 id: trade.order_id.clone(),
2116 time_exchange,
2117 filled_quantity: Some(filled_quantity),
2118 },
2119 };
2120 self.account.ack_cancelled(cancelled.clone());
2121 UnindexedOrder::from(cancelled)
2122 };
2123
2124 let mut events = Vec::with_capacity(4);
2125 self.push_committed(&mut events, committed, Some(trade));
2126 events.push(self.build_account_event(Snapshot(terminal)));
2127 events
2128 }
2129
2130 fn release_held_limit(
2152 &mut self,
2153 RestingOrder { order, reservation }: RestingOrder,
2154 limit: Decimal,
2155 release: Release<'_>,
2156 terms: &InstrumentTerms,
2157 time_exchange: DateTime<Utc>,
2158 ) -> Vec<UnindexedAccountEvent> {
2159 let side = order.side;
2160 let quantity = order.state.quantity_remaining(order.quantity);
2161
2162 let (market, reference) = match release {
2163 Release::Bar(bar, price) => {
2164 let reference = price.reference(side, bar);
2165 (
2166 MarketSnapshot::from_last_price(Some(reference)),
2167 Some(reference),
2168 )
2169 }
2170 Release::Market(market) => (*market, None),
2171 };
2172
2173 let marketable = crosses(side, limit, &market);
2174 let post_only = matches!(
2175 order.time_in_force,
2176 TimeInForce::GoodUntilCancelled { post_only: true }
2177 );
2178 if marketable && post_only {
2179 let released =
2180 reservation.map(|reservation| self.release_reservation(reservation, time_exchange));
2181 return self.cancel_unfilled(order, released, time_exchange);
2182 }
2183
2184 let filled_quantity = match self.depth_available(&order.key.instrument, side) {
2187 _ if !marketable => Decimal::ZERO,
2188 Some(available) => quantity.min(available),
2189 None => quantity,
2190 };
2191 let unfilled = quantity - filled_quantity;
2192
2193 let slippage_before = self.slippage.clone();
2196 let position = self.net_position(terms, &order.key.instrument);
2197 let fill = (filled_quantity > Decimal::ZERO).then(|| {
2198 let price = self
2201 .fill_model
2202 .fill_price(&FillContext::new(side, quantity, &market))
2203 .or(reference)
2204 .unwrap_or(limit);
2205 let price = clamp_to_limit(side, self.slippage.apply(side, price), limit);
2206 let settlement = self.fill_settlement(
2207 terms,
2208 position,
2209 side,
2210 filled_quantity,
2211 price,
2212 Liquidity::Taker,
2213 );
2214 (price, settlement)
2215 });
2216
2217 let held = (unfilled > Decimal::ZERO).then(|| {
2220 let position = match &fill {
2221 Some((price, _)) => position.after(side, filled_quantity, *price),
2222 None => position,
2223 };
2224 self.settlement(terms, position, side, unfilled, limit, Liquidity::Maker)
2225 });
2226
2227 let (debit, credit) = match (&fill, &held) {
2228 (Some((_, fill)), Some(held)) => (fill.settled_holding(held), Some(&fill.credit)),
2229 (Some((_, fill)), None) => (fill.settled(), Some(&fill.credit)),
2230 (None, Some(held)) => (held.reserved(), None),
2231 (None, None) => unreachable!(
2232 "SimulatedVenue released {} with nothing to trade or rest: it holds only an order \
2233 with a positive quantity, which `open_order_inner` checks, and such an order \
2234 either trades or rests some of it",
2235 order.key.cid
2236 ),
2237 };
2238 let effect = reservation.as_ref().map_or_else(
2239 || self.margin_effect(None),
2240 |reservation| reservation.effect,
2241 );
2242 let (mut committed, loan, excess_borrowed) =
2243 match self.commit_afresh(terms, reservation, &debit, credit, time_exchange) {
2244 Recommitted::Committed {
2245 commitment,
2246 loan,
2247 excess_borrowed,
2248 } => (commitment, loan, excess_borrowed),
2249 Recommitted::Unaffordable { released } => {
2250 self.slippage = slippage_before;
2251 return self.cancel_unfilled(order, released, time_exchange);
2252 }
2253 };
2254
2255 let mut events = Vec::with_capacity(4);
2256 let mut filled_total = order.state.filled_quantity;
2257 let mut trade = None;
2258 if let Some((price, settlement)) = fill {
2259 self.take_fee_apart(&settlement, &mut committed, time_exchange);
2260 self.consume_depth(&order.key.instrument, side, filled_quantity);
2261 let fill = Fill {
2262 quantity: filled_quantity,
2263 price,
2264 };
2265 trade = Some(self.print_fill(terms, &order, fill, settlement.fees, time_exchange));
2266 filled_total += filled_quantity;
2267 }
2268
2269 let terminal = match held {
2270 Some(held) => {
2273 let open = Open::new(order.state.id.clone(), time_exchange, filled_total);
2274 let rested = Order {
2275 key: order.key,
2276 side,
2277 price: order.price,
2278 quantity: order.quantity,
2279 kind: order.kind,
2280 time_in_force: order.time_in_force,
2281 state: open,
2282 };
2283 let unreported_interest = match trade {
2287 Some(_) => Vec::new(),
2288 None => {
2289 let mut held = std::mem::take(&mut committed.held_interest);
2290 held.extend(committed.interest.take().map(|flow| *flow));
2291 held
2292 }
2293 };
2294 self.book_rested(
2295 rested.clone(),
2296 Reservation {
2297 borrowed: loan.min(held.requirement()),
2298 excess_borrowed,
2299 amount: held.requirement(),
2300 asset: held.asset,
2301 effect,
2302 unreported_interest,
2303 },
2304 );
2305 rested.map_state(OrderState::active)
2306 }
2307 None => {
2310 let Some(trade) = &trade else {
2311 unreachable!("an order with nothing left to rest has traded");
2312 };
2313 let filled = Order {
2314 key: order.key,
2315 side,
2316 price: order.price,
2317 quantity: order.quantity,
2318 kind: order.kind,
2319 time_in_force: order.time_in_force,
2320 state: Filled::new(
2321 trade.order_id.clone(),
2322 time_exchange,
2323 order.quantity,
2324 Some(trade.price),
2325 ),
2326 };
2327 self.account.ack_filled(filled.clone());
2328 filled.map_state(OrderState::fully_filled)
2329 }
2330 };
2331
2332 self.push_committed(&mut events, committed, trade);
2333 events.push(self.build_account_event(Snapshot(terminal)));
2334 events
2335 }
2336
2337 fn print_fill(
2342 &mut self,
2343 terms: &InstrumentTerms,
2344 order: &OpenOrder,
2345 fill: Fill,
2346 fees: AssetFees<AssetNameExchange>,
2347 time_exchange: DateTime<Utc>,
2348 ) -> Trade<AssetNameExchange, InstrumentNameExchange> {
2349 let Fill { quantity, price } = fill;
2350 self.record_fill(terms, &order.key.instrument, order.side, fill);
2351
2352 let trade = Trade {
2353 id: self.trade_id_sequence_fetch_add(),
2354 order_id: order.state.id.or_client_id(&order.key.cid),
2355 instrument: order.key.instrument.clone(),
2356 strategy: order.key.strategy.clone(),
2357 time_exchange,
2358 side: order.side,
2359 price,
2360 quantity,
2361 order_filled_quantity: Some(order.state.filled_quantity + quantity),
2362 fees,
2363 origin: TradeOrigin::Order,
2364 position_side: None,
2365 client_order_id: Some(order.key.cid.clone()),
2366 time_source: TimeSource::Venue,
2367 };
2368
2369 self.account.ack_trade(trade.clone());
2370 trade
2371 }
2372
2373 fn settle_resting(
2392 &mut self,
2393 order: &OpenOrder,
2394 terms: &InstrumentTerms,
2395 reservation: Option<Reservation>,
2396 settlement: &Settlement,
2397 time_exchange: DateTime<Utc>,
2398 ) -> RestingSettlement {
2399 let delivery = match &settlement.credit {
2400 _ if settlement.fee_apart => {
2403 return self.settle_resting_afresh(terms, reservation, settlement, time_exchange);
2404 }
2405 Credit::Delivery(delivery) if !delivery.amount.is_sign_negative() => delivery,
2406 Credit::PaidBack(_) | Credit::Delivery(_) => {
2408 return self.settle_resting_afresh(terms, reservation, settlement, time_exchange);
2409 }
2410 };
2411
2412 let effect = reservation.as_ref().map_or_else(
2414 || self.margin_effect(None),
2415 |reservation| reservation.effect,
2416 );
2417 let Some(Reservation {
2418 asset,
2419 amount,
2420 unreported_interest,
2421 ..
2422 }) = reservation
2423 else {
2424 let debit = settlement.settled();
2429 return RestingSettlement::Settled(
2430 self.commit(
2431 &debit,
2432 Some(&settlement.credit),
2433 effect,
2434 terms,
2435 None,
2436 time_exchange,
2437 )
2438 .unwrap_or_else(|insufficient| {
2439 panic!(
2440 "SimulatedVenue cannot afford the fill of resting order {}, which was \
2441 seeded by `initial_state` and so reserved nothing: {} of {} free, {} \
2442 required",
2443 order.key.cid, insufficient.free, insufficient.asset, insufficient.required
2444 )
2445 }),
2446 );
2447 };
2448
2449 debug_assert!(
2456 asset == settlement.asset && amount == settlement.amount,
2457 "resting order {} reserved {amount} of {asset} but its fill settles {} of {}: the \
2458 reservation and the fill disagree, so a balance the account never held would be \
2459 reported",
2460 order.key.cid,
2461 settlement.amount,
2462 settlement.asset
2463 );
2464
2465 let paid = self.account.settle(&asset, amount, time_exchange);
2468 let received = self.account.deliver(delivery, effect, time_exchange);
2469 RestingSettlement::Settled(Commitment {
2470 held_interest: unreported_interest,
2471 ..Commitment::from(Committed {
2472 paid,
2473 received: Some(received),
2474 borrowed: Decimal::ZERO,
2475 excess_borrowed: Decimal::ZERO,
2476 })
2477 })
2478 }
2479
2480 fn settle_resting_afresh(
2488 &mut self,
2489 terms: &InstrumentTerms,
2490 reservation: Option<Reservation>,
2491 settlement: &Settlement,
2492 time_exchange: DateTime<Utc>,
2493 ) -> RestingSettlement {
2494 match self.commit_afresh(
2495 terms,
2496 reservation,
2497 &settlement.settled(),
2498 Some(&settlement.credit),
2499 time_exchange,
2500 ) {
2501 Recommitted::Committed { commitment, .. } => RestingSettlement::Settled(commitment),
2502 Recommitted::Unaffordable { released } => RestingSettlement::Unaffordable { released },
2503 }
2504 }
2505
2506 fn commit_afresh(
2510 &mut self,
2511 terms: &InstrumentTerms,
2512 mut reservation: Option<Reservation>,
2513 debit: &Debit,
2514 credit: Option<&Credit>,
2515 time_exchange: DateTime<Utc>,
2516 ) -> Recommitted {
2517 let held_interest = reservation
2520 .as_mut()
2521 .map(|reservation| std::mem::take(&mut reservation.unreported_interest))
2522 .unwrap_or_default();
2523 let effect = reservation.as_ref().map_or_else(
2524 || self.margin_effect(None),
2525 |reservation| reservation.effect,
2526 );
2527 let released = reservation.map(|reservation| {
2528 let balance =
2529 self.account
2530 .release(&reservation.asset, reservation.amount, time_exchange);
2531 (reservation, balance)
2532 });
2533
2534 let repays_at_cancel = self.repays_at_cancel();
2535 match self.commit(debit, credit, effect, terms, None, time_exchange) {
2536 Ok(mut commitment) => {
2537 let mut loan = commitment.committed.shortfall_borrowed();
2538 let mut excess_borrowed = commitment.committed.excess_borrowed;
2539 if let Some((reservation, _)) = &released {
2540 loan += reservation.borrowed;
2541 excess_borrowed += reservation.excess_borrowed;
2542 }
2543 if let Some((reservation, _)) = released.filter(|_| repays_at_cancel) {
2547 let surplus = reservation.amount - (debit.settled + debit.reserved);
2548 let unused = reservation.borrowed.min(surplus);
2549 if unused > Decimal::ZERO {
2550 debug_assert_eq!(commitment.committed.paid.asset, reservation.asset);
2551 commitment.committed.paid =
2552 self.account
2553 .repay_loan(&reservation.asset, unused, time_exchange);
2554 loan -= unused;
2555 }
2556 }
2557 commitment.held_interest = held_interest;
2558 Recommitted::Committed {
2559 commitment,
2560 loan,
2561 excess_borrowed,
2562 }
2563 }
2564 Err(_) => Recommitted::Unaffordable {
2565 released: released.map(|(reservation, balance)| Released {
2566 balance: match !repays_at_cancel || reservation.loan().is_zero() {
2567 true => balance,
2568 false => self.account.repay_loan(
2569 &reservation.asset,
2570 reservation.loan(),
2571 time_exchange,
2572 ),
2573 },
2574 interest: held_interest,
2575 }),
2576 },
2577 }
2578 }
2579
2580 fn push_committed(
2590 &self,
2591 events: &mut Vec<UnindexedAccountEvent>,
2592 commitment: Commitment,
2593 trade: Option<Trade<AssetNameExchange, InstrumentNameExchange>>,
2594 ) {
2595 let Commitment {
2596 committed: Committed { paid, received, .. },
2597 interest,
2598 held_interest,
2599 fee,
2600 } = commitment;
2601 let reported_at = trade
2603 .as_ref()
2604 .map_or(paid.time_exchange, |trade| trade.time_exchange);
2605 events.push(self.build_account_event(Snapshot(paid)));
2606 if let Some(received) = received {
2607 events.push(self.build_account_event(Snapshot(received)));
2608 }
2609 if let Some(fee) = fee {
2610 events.push(self.build_account_event(Snapshot(*fee)));
2611 }
2612 if let Some(trade) = trade {
2613 events.push(self.build_account_event(trade));
2614 }
2615 self.push_interest(
2616 events,
2617 held_interest.into_iter().chain(interest.map(|flow| *flow)),
2618 reported_at,
2619 );
2620 }
2621
2622 fn push_interest(
2626 &self,
2627 events: &mut Vec<UnindexedAccountEvent>,
2628 interest: impl IntoIterator<Item = CashFlow<AssetNameExchange, InstrumentNameExchange>>,
2629 time_exchange: DateTime<Utc>,
2630 ) {
2631 events.extend(interest.into_iter().map(|mut flow| {
2632 flow.time_exchange = time_exchange;
2633 self.build_account_event(AccountEventKind::CashFlow(flow))
2634 }));
2635 }
2636
2637 fn take_fee_apart(
2640 &mut self,
2641 settlement: &Settlement,
2642 commitment: &mut Commitment,
2643 time_exchange: DateTime<Utc>,
2644 ) {
2645 if !settlement.fee_apart || settlement.fees.fees.is_zero() {
2648 return;
2649 }
2650 commitment.fee = Some(Box::new(self.account.charge(
2654 &settlement.fees.asset,
2655 -settlement.fees.fees,
2656 time_exchange,
2657 )));
2658 }
2659
2660 fn commit(
2671 &mut self,
2672 debit: &Debit,
2673 credit: Option<&Credit>,
2674 effect: MarginEffect,
2675 terms: &InstrumentTerms,
2676 market: Option<&MarketSnapshot>,
2677 time_exchange: DateTime<Utc>,
2678 ) -> Result<Commitment, BalanceInsufficient> {
2679 let lending = Lending {
2680 borrowable: self.borrowable(debit, effect, terms, market),
2681 min_borrow: self
2682 .spot_margin
2683 .as_ref()
2684 .map_or(Decimal::ZERO, |config| config.min_borrow(&debit.asset)),
2685 max_borrow: self
2686 .spot_margin
2687 .as_ref()
2688 .and_then(|config| config.max_borrow(&debit.asset)),
2689 };
2690 let mut committed = self
2691 .account
2692 .commit(debit, credit, effect, lending, time_exchange)?;
2693
2694 let interest = match committed.borrowed > Decimal::ZERO {
2695 true => self
2696 .charge_interest(
2697 &debit.asset,
2698 committed.borrowed,
2699 InterestDue::Borrow(time_exchange),
2700 )
2701 .map(|(balance, flow)| {
2702 committed.paid = balance;
2703 Box::new(flow)
2704 }),
2705 false => None,
2706 };
2707 Ok(Commitment {
2708 interest,
2709 ..Commitment::from(committed)
2710 })
2711 }
2712
2713 fn cancel_unfilled(
2721 &mut self,
2722 order: OpenOrder,
2723 released: Option<Released>,
2724 time_exchange: DateTime<Utc>,
2725 ) -> Vec<UnindexedAccountEvent> {
2726 let flows = released
2727 .as_ref()
2728 .map_or(0, |released| released.interest.len());
2729 let mut events = Vec::with_capacity(2 + flows);
2730 if let Some(Released { balance, interest }) = released {
2731 events.push(self.build_account_event(Snapshot(balance)));
2732 self.push_interest(&mut events, interest, time_exchange);
2733 }
2734
2735 let id = order.state.id.or_client_id(&order.key.cid);
2736 let cancelled = Order {
2737 key: order.key,
2738 side: order.side,
2739 price: order.price,
2740 quantity: order.quantity,
2741 kind: order.kind,
2742 time_in_force: order.time_in_force,
2743 state: Cancelled {
2744 id,
2745 time_exchange,
2746 filled_quantity: Some(order.state.filled_quantity),
2747 },
2748 };
2749 self.account.ack_cancelled(cancelled.clone());
2750 events.push(self.build_account_event(Snapshot(UnindexedOrder::from(cancelled))));
2751
2752 events
2753 }
2754
2755 pub fn instruments(
2761 &self,
2762 ) -> &FnvHashMap<InstrumentNameExchange, Instrument<ExchangeId, AssetNameExchange>> {
2763 &self.instruments
2764 }
2765
2766 pub fn instrument(
2768 &self,
2769 name: &InstrumentNameExchange,
2770 ) -> Option<&Instrument<ExchangeId, AssetNameExchange>> {
2771 self.instruments.get(name)
2772 }
2773
2774 pub fn market(&self, instrument: &InstrumentNameExchange) -> Option<&VenueInstrumentMarket> {
2776 self.market.get(instrument)
2777 }
2778
2779 pub fn order_sequence(&self) -> u64 {
2789 self.order_sequence
2790 }
2791
2792 pub fn balances(&self, assets: &[AssetNameExchange]) -> Vec<AssetBalance<AssetNameExchange>> {
2793 self.account
2795 .balances()
2796 .filter(|balance| assets.is_empty() || assets.contains(&balance.asset))
2797 .cloned()
2798 .collect()
2799 }
2800
2801 pub fn borrow_capacity(
2830 &self,
2831 instrument: &InstrumentNameExchange,
2832 side: Side,
2833 ) -> Result<BorrowCapacity, UnindexedApiError> {
2834 let terms = self.instrument_terms(instrument)?;
2835 let config = match &self.spot_margin {
2836 Some(config) if !terms.cash_settled => config,
2837 Some(_) => {
2838 return Err(ApiError::InstrumentInvalid(
2839 instrument.clone(),
2840 format!(
2841 "SimulatedVenue lends nothing against {instrument}: it is cash-settled"
2842 ),
2843 ));
2844 }
2845 None => {
2846 return Err(ApiError::InstrumentInvalid(
2847 instrument.clone(),
2848 format!(
2849 "SimulatedVenue lends nothing against {instrument}: it has no spot margin \
2850 configuration"
2851 ),
2852 ));
2853 }
2854 };
2855 let asset = match side {
2856 Side::Buy => &terms.underlying.quote,
2857 Side::Sell => &terms.underlying.base,
2858 };
2859
2860 let mut capacity = BorrowCapacity::new(asset.clone());
2861 let cap = config.max_borrow(asset);
2862 if let Some(cap) = cap {
2863 capacity = capacity.with_account_limit(cap);
2864 }
2865 let cap_room = cap.map(|cap| self.account.cap_room(asset, cap));
2866 let min_borrow = config.min_borrow(asset);
2867
2868 let ruled_out = self.liquidation.under_way()
2871 || cap_room.is_some_and(|room| room.is_zero() || room < min_borrow);
2872 let room = match ruled_out {
2873 true => Some(Decimal::ZERO),
2874 false => self
2875 .values_every_holding(config.valuation_asset())
2876 .then(|| self.leverage_room(config, asset, None))
2877 .flatten()
2878 .map(|room| cap_room.map_or(room, |cap_room| cap_room.min(room))),
2879 };
2880 if let Some(room) = room {
2881 capacity = capacity.with_borrowable_now(match room < min_borrow {
2882 true => Decimal::ZERO,
2883 false => room,
2884 });
2885 }
2886 Ok(capacity)
2887 }
2888
2889 fn values_every_holding(&self, valuation: &AssetNameExchange) -> bool {
2894 self.account.balances().all(|balance| {
2895 balance.balance.total.is_zero()
2896 || self.price_in(&balance.asset, valuation, None).is_some()
2897 })
2898 }
2899
2900 pub fn orders_open(
2901 &self,
2902 instruments: &[InstrumentNameExchange],
2903 ) -> Vec<Order<ExchangeId, InstrumentNameExchange, Open>> {
2904 self.account
2906 .orders_open()
2907 .filter(|order| instruments.is_empty() || instruments.contains(&order.key.instrument))
2908 .cloned()
2909 .collect()
2910 }
2911
2912 pub fn orders_ended(&self, orders: &[UnindexedOrderKey]) -> Vec<UnindexedInactiveOrder> {
2924 let mut reported = FnvHashSet::default();
2925 orders
2926 .iter()
2927 .filter_map(|key| {
2928 let order = self
2929 .account
2930 .order_ended(&key.cid)
2931 .filter(|order| order.key.instrument == key.instrument)?;
2932 reported.insert(&key.cid).then(|| Order {
2933 key: key.clone(),
2934 ..order
2935 })
2936 })
2937 .collect()
2938 }
2939
2940 pub fn trades(
2941 &self,
2942 time_since: DateTime<Utc>,
2943 ) -> Vec<Trade<AssetNameExchange, InstrumentNameExchange>> {
2944 self.account.trades(time_since).cloned().collect()
2945 }
2946
2947 pub fn open_order(
2970 &mut self,
2971 request: OrderRequestOpen<ExchangeId, InstrumentNameExchange>,
2972 ) -> OpenOutcome {
2973 let market = self.pricing_market(&request);
2976 let (response, notifications) = self.open_order_inner(request, market);
2977
2978 let mut events = match notifications {
2983 Some(OpenOrderNotifications::Filled { commitment, trade }) => {
2984 let mut events = Vec::with_capacity(3);
2985 self.push_committed(&mut events, commitment, Some(trade));
2986 events
2987 }
2988 Some(OpenOrderNotifications::Rested { balance }) => {
2993 vec![self.build_account_event(balance)]
2994 }
2995 None => Vec::new(),
2996 };
2997 self.refresh_next_interest_if_loans_changed();
2998 self.report_debt_changes(&mut events);
2999
3000 VenueOutcome { events, response }
3001 }
3002
3003 pub fn cancel_order(
3023 &mut self,
3024 request: OrderRequestCancel<ExchangeId, InstrumentNameExchange>,
3025 ) -> CancelOutcome {
3026 let time_exchange = self.time_exchange();
3027
3028 let Some(RestingOrder { order, reservation }) = self.account.remove_order(&request.key.cid)
3029 else {
3030 return VenueOutcome {
3031 events: Vec::new(),
3032 response: UnindexedOrderResponseCancel {
3033 state: Err(self.cancel_missing_reason(&request.key.cid)),
3034 key: request.key,
3035 },
3036 };
3037 };
3038
3039 let mut events = match reservation {
3044 Some(reservation) => {
3045 let Released { balance, interest } =
3046 self.release_reservation(reservation, time_exchange);
3047 let mut events = vec![self.build_account_event(Snapshot(balance))];
3048 self.push_interest(&mut events, interest, time_exchange);
3049 events
3050 }
3051 None => Vec::new(),
3052 };
3053 self.refresh_next_interest_if_loans_changed();
3054 self.report_debt_changes(&mut events);
3055
3056 let cancelled = Cancelled {
3057 id: order.state.id.or_client_id(&order.key.cid),
3058 time_exchange,
3059 filled_quantity: Some(order.state.filled_quantity),
3060 };
3061
3062 self.account.ack_cancelled(Order {
3063 key: order.key,
3064 side: order.side,
3065 price: order.price,
3066 quantity: order.quantity,
3067 kind: order.kind,
3068 time_in_force: order.time_in_force,
3069 state: cancelled.clone(),
3070 });
3071
3072 VenueOutcome {
3073 events,
3074 response: UnindexedOrderResponseCancel {
3075 key: request.key,
3076 state: Ok(cancelled),
3077 },
3078 }
3079 }
3080
3081 fn report_debt_changes(&mut self, events: &mut Vec<UnindexedAccountEvent>) {
3090 if self.spot_margin.is_none() {
3091 return;
3092 }
3093 let mut index = 0;
3094 while index < events.len() {
3095 index += 1;
3096 let AccountEventKind::BalanceSnapshot(Snapshot(balance)) = &events[index - 1].kind
3097 else {
3098 continue;
3099 };
3100 let Some(debt) = balance.balance.margin else {
3101 continue;
3102 };
3103 let before = self
3104 .reported_debt
3105 .insert(balance.asset.clone(), debt)
3106 .unwrap_or_default();
3107 if before == debt {
3108 continue;
3109 }
3110 let kind = if debt.borrowed > before.borrowed {
3111 MarginDebtKind::Borrow
3112 } else if debt.borrowed < before.borrowed || debt.interest < before.interest {
3113 MarginDebtKind::Repay
3114 } else {
3115 MarginDebtKind::Interest
3116 };
3117 let update = MarginDebtUpdate::new(
3118 balance.asset.clone(),
3119 None,
3120 debt,
3121 kind,
3122 kind.to_smolstr(),
3123 balance.time_exchange,
3124 );
3125 events.insert(
3126 index,
3127 self.build_account_event(AccountEventKind::MarginDebt(update)),
3128 );
3129 }
3130 }
3131
3132 fn cancel_missing_reason(&self, cid: &ClientOrderId) -> UnindexedOrderError {
3134 if self.account.is_filled(cid) {
3135 ApiError::OrderAlreadyFullyFilled.into()
3136 } else if self.account.is_cancelled(cid) {
3137 ApiError::OrderAlreadyCancelled.into()
3138 } else if self.account.is_expired(cid) {
3139 ApiError::OrderAlreadyExpired.into()
3140 } else {
3141 UnindexedOrderError::Rejected(ApiError::OrderRejected(format!(
3142 "SimulatedVenue is not holding an open order with {cid}"
3143 )))
3144 }
3145 }
3146
3147 pub fn account_snapshot(&self) -> UnindexedAccountSnapshot {
3148 let balances = self.account.balances().cloned().collect();
3149
3150 let orders_open = self
3151 .account
3152 .orders_open()
3153 .cloned()
3154 .map(UnindexedOrder::from);
3155
3156 let orders_cancelled = self
3157 .account
3158 .orders_cancelled()
3159 .cloned()
3160 .map(UnindexedOrder::from);
3161
3162 let orders_expired = self
3163 .account
3164 .orders_expired()
3165 .cloned()
3166 .map(UnindexedOrder::from);
3167
3168 let orders_all = orders_open.chain(orders_cancelled).chain(orders_expired);
3169 let orders_all = orders_all
3174 .sorted_unstable_by_key(|order| (order.key.instrument.clone(), order.key.cid.clone()));
3175 let orders_by_instrument = orders_all.chunk_by(|order| order.key.instrument.clone());
3176
3177 let mut orders_by_instrument: FnvHashMap<_, Vec<_>> = orders_by_instrument
3178 .into_iter()
3179 .map(|(instrument, orders)| (instrument, orders.collect()))
3180 .collect();
3181
3182 let mut instruments: Vec<_> = self
3186 .instruments
3187 .iter()
3188 .filter_map(|(name, instrument)| {
3189 let orders = orders_by_instrument.remove(name);
3190 let position = self.position_report(name, instrument);
3191 if orders.is_none() && position == PositionReport::Unreported {
3192 return None;
3193 }
3194 Some(InstrumentAccountSnapshot {
3195 instrument: name.clone(),
3196 orders: orders.unwrap_or_default(),
3197 orders_complete: true,
3201 position,
3202 isolated: None,
3203 })
3204 })
3205 .collect();
3206
3207 instruments.extend(
3210 orders_by_instrument
3211 .into_iter()
3212 .map(|(instrument, orders)| InstrumentAccountSnapshot {
3213 instrument,
3214 orders,
3215 orders_complete: true,
3216 position: PositionReport::Unreported,
3217 isolated: None,
3218 }),
3219 );
3220 instruments.sort_unstable_by(|a, b| a.instrument.cmp(&b.instrument));
3221
3222 UnindexedAccountSnapshot {
3223 exchange: self.exchange,
3224 balances,
3225 instruments,
3226 }
3227 }
3228
3229 fn position_report(
3235 &self,
3236 name: &InstrumentNameExchange,
3237 instrument: &Instrument<ExchangeId, AssetNameExchange>,
3238 ) -> PositionReport {
3239 if !matches!(instrument.kind, InstrumentKind::Cfd(_)) {
3240 return PositionReport::Unreported;
3241 }
3242
3243 let position = self.positions.get(name).copied().unwrap_or_default();
3244 PositionReport::from_position(Position::new(
3245 position.quantity,
3246 (!position.quantity.is_zero()).then_some(position.entry),
3247 None,
3248 None,
3249 None,
3250 None,
3251 None,
3252 self.time_exchange(),
3253 ))
3254 }
3255
3256 fn pricing_market(
3293 &self,
3294 request: &OrderRequestOpen<ExchangeId, InstrumentNameExchange>,
3295 ) -> Option<MarketSnapshot> {
3296 match self.regime {
3297 VenueRegime::RequestPriced => request.state.market,
3298 VenueRegime::MarketDriven => self
3300 .market(&request.key.instrument)
3301 .map(|market| market.snapshot),
3302 }
3303 }
3304
3305 fn open_order_inner(
3306 &mut self,
3307 request: OrderRequestOpen<ExchangeId, InstrumentNameExchange>,
3308 market: Option<MarketSnapshot>,
3309 ) -> (
3310 Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>,
3311 Option<OpenOrderNotifications>,
3312 ) {
3313 if let Err(error) = self.validate_order_kind_supported(request.state.kind) {
3314 return (build_open_order_err_response(request, error), None);
3315 }
3316
3317 if let Err(error) =
3318 validate_time_in_force_supported(request.state.kind, request.state.time_in_force)
3319 {
3320 return (build_open_order_err_response(request, error), None);
3321 }
3322
3323 if request.state.quantity <= Decimal::ZERO {
3327 let reason = format!(
3328 "cannot open {} for {}: quantity {} is not positive",
3329 request.key.instrument, request.key.exchange, request.state.quantity
3330 );
3331 return (
3332 build_open_order_err_response(
3333 request,
3334 UnindexedOrderError::Rejected(ApiError::OrderRejected(reason)),
3335 ),
3336 None,
3337 );
3338 }
3339
3340 let terms = match self.instrument_terms(&request.key.instrument) {
3342 Ok(terms) => terms,
3343 Err(error) => return (build_open_order_err_response(request, error), None),
3344 };
3345
3346 if let Some(effect) = self.refused_margin_effect(request.state.margin_effect, &terms) {
3349 return (
3350 build_open_order_err_response(
3351 request,
3352 UnindexedOrderError::UnsupportedMarginEffect(effect),
3353 ),
3354 None,
3355 );
3356 }
3357
3358 if self.account.orders().contains(&request.key.cid) {
3361 let reason = format!(
3362 "{} already names an order resting on {}",
3363 request.key.cid, request.key.exchange
3364 );
3365 return (
3366 build_open_order_err_response(request, ApiError::DuplicateClientOrderId(reason)),
3367 None,
3368 );
3369 }
3370
3371 if let Err(error) = self.check_shortability(&request, &terms, market.as_ref()) {
3373 return (build_open_order_err_response(request, error), None);
3374 }
3375
3376 let now = self.time_exchange();
3377
3378 let limit = match request.state.kind {
3382 OrderKind::Limit => match request.state.price {
3383 Some(limit) => Some(limit),
3384 None => {
3385 let reason = format!(
3386 "cannot open {} for {}: OrderKind::Limit carries no limit price",
3387 request.key.instrument, request.key.exchange
3388 );
3389 return (
3390 build_open_order_err_response(
3391 request,
3392 UnindexedOrderError::Rejected(ApiError::OrderRejected(reason)),
3393 ),
3394 None,
3395 );
3396 }
3397 },
3398 _ => None,
3399 };
3400
3401 if let StaleMarketPolicy::Wait { max_age } = self.stale_market
3406 && !matches!(
3407 request.state.time_in_force,
3408 TimeInForce::GoodTillDate { expiry } if now >= expiry
3409 )
3410 {
3411 match self.market_age(&request.key.instrument, max_age, now) {
3412 MarketAge::NoFeed => {
3413 let reason = format!(
3414 "cannot accept {} on {}: StaleMarketPolicy::Wait needs a market feed to \
3415 measure {}'s age from, and this venue has none",
3416 request.key.cid, request.key.exchange, request.key.instrument
3417 );
3418 return (
3419 build_open_order_err_response(
3420 request,
3421 UnindexedOrderError::Rejected(ApiError::OrderRejected(reason)),
3422 ),
3423 None,
3424 );
3425 }
3426 MarketAge::Stale { fresh_at } => {
3427 if matches!(
3428 request.state.time_in_force,
3429 TimeInForce::ImmediateOrCancel | TimeInForce::FillOrKill
3430 ) {
3431 let last = fresh_at.map_or_else(
3432 || "never observed fresh".to_owned(),
3433 |fresh_at| format!("last observed fresh at {fresh_at}"),
3434 );
3435 let reason = format!(
3436 "cannot fill {} on {}: {}'s market is stale under \
3437 StaleMarketPolicy::Wait (max_age {max_age:?}; {last}), and \
3438 TimeInForce::{} does not wait for a fresh observation",
3439 request.key.cid,
3440 request.key.exchange,
3441 request.key.instrument,
3442 request.state.time_in_force
3443 );
3444 return (
3445 build_open_order_err_response(
3446 request,
3447 UnindexedOrderError::Rejected(ApiError::OrderRejected(reason)),
3448 ),
3449 None,
3450 );
3451 }
3452 return self.hold_on_arrival(request, market, &terms);
3453 }
3454 MarketAge::Fresh | MarketAge::Unpriced => {}
3455 }
3456 }
3457
3458 let Some(limit) = limit else {
3461 return match disposition(request.state.kind, request.state.time_in_force, true, now) {
3475 Disposition::FillAndCancel | Disposition::FillOrNothing
3476 if matches!(self.market_order_timing, MarketOrderTiming::NextBar(_)) =>
3477 {
3478 self.hold_on_arrival(request, market, &terms)
3479 }
3480 Disposition::FillAndCancel => {
3481 self.fill_on_arrival(request, market, &terms, Remainder::Cancel)
3482 }
3483 Disposition::FillOrNothing => {
3484 self.fill_on_arrival(request, market, &terms, Remainder::Kill)
3485 }
3486 Disposition::Expire => self.expire_on_arrival(request, now),
3487 Disposition::FillAndRest | Disposition::CancelUnfilled | Disposition::Rest => {
3488 self.cancel_on_arrival(request, now)
3489 }
3490 };
3491 };
3492
3493 let marketable = market.is_some_and(|market| crosses(request.state.side, limit, &market));
3498
3499 match disposition(
3500 request.state.kind,
3501 request.state.time_in_force,
3502 marketable,
3503 now,
3504 ) {
3505 Disposition::FillAndRest => {
3508 self.fill_on_arrival(request, market, &terms, Remainder::Rest { limit })
3509 }
3510 Disposition::FillAndCancel => {
3511 self.fill_on_arrival(request, market, &terms, Remainder::Cancel)
3512 }
3513 Disposition::FillOrNothing => {
3514 self.fill_on_arrival(request, market, &terms, Remainder::Kill)
3515 }
3516 Disposition::Rest => self.rest_order(request, limit, &terms, Decimal::ZERO),
3518 Disposition::CancelUnfilled => self.cancel_on_arrival(request, now),
3519 Disposition::Expire => self.expire_on_arrival(request, now),
3520 }
3521 }
3522
3523 fn market_age(
3526 &self,
3527 instrument: &InstrumentNameExchange,
3528 max_age: Duration,
3529 now: DateTime<Utc>,
3530 ) -> MarketAge {
3531 if self.regime == VenueRegime::RequestPriced {
3532 return MarketAge::NoFeed;
3533 }
3534 let Some(market) = self
3535 .market(instrument)
3536 .filter(|market| !market.snapshot.is_empty())
3537 else {
3538 return MarketAge::Unpriced;
3539 };
3540 let Some(fresh_at) = market.fresh_at else {
3541 return MarketAge::Stale { fresh_at: None };
3542 };
3543 let stale = chrono::TimeDelta::from_std(max_age)
3545 .is_ok_and(|max_age| now.signed_duration_since(fresh_at) > max_age);
3546 if stale {
3547 MarketAge::Stale {
3548 fresh_at: Some(fresh_at),
3549 }
3550 } else {
3551 MarketAge::Fresh
3552 }
3553 }
3554
3555 fn hold_on_arrival(
3571 &mut self,
3572 request: OrderRequestOpen<ExchangeId, InstrumentNameExchange>,
3573 market: Option<MarketSnapshot>,
3574 terms: &InstrumentTerms,
3575 ) -> (
3576 Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>,
3577 Option<OpenOrderNotifications>,
3578 ) {
3579 if matches!(self.market_order_timing, MarketOrderTiming::NextBar(_))
3580 && self
3581 .market(&request.key.instrument)
3582 .and_then(|market| market.bar)
3583 .is_none()
3584 {
3585 let reason = format!(
3586 "cannot hold {} on {} for {}'s next bar: MarketOrderTiming::NextBar holds it for \
3587 one and needs a bar feed, and this venue has been fed no bar for it",
3588 request.key.cid, request.key.exchange, request.key.instrument
3589 );
3590 return (
3591 build_open_order_err_response(
3592 request,
3593 UnindexedOrderError::Rejected(ApiError::OrderRejected(reason)),
3594 ),
3595 None,
3596 );
3597 }
3598
3599 let side = request.state.side;
3600 let limit = match request.state.kind {
3601 OrderKind::Limit => request.state.price,
3602 _ => None,
3603 };
3604 let post_only = matches!(
3605 request.state.time_in_force,
3606 TimeInForce::GoodUntilCancelled { post_only: true }
3607 );
3608
3609 let taker_estimate = || {
3612 market.and_then(|market| {
3613 self.fill_model
3614 .fill_price(&FillContext::new(side, request.state.quantity, &market))
3615 })
3616 };
3617 let (estimate, liquidity) = match limit {
3618 Some(limit)
3619 if !post_only && market.is_some_and(|market| crosses(side, limit, &market)) =>
3620 {
3621 let estimate = taker_estimate().unwrap_or(limit);
3624 (
3625 Some(clamp_to_limit(side, estimate, limit)),
3626 Liquidity::Taker,
3627 )
3628 }
3629 Some(limit) => (Some(limit), Liquidity::Maker),
3630 None => (taker_estimate(), Liquidity::Taker),
3631 };
3632 let Some(estimate) = estimate else {
3633 let reason = format!(
3634 "cannot price {} for {}: no market price available yet and OrderKind::{:?} carries \
3635 no limit price",
3636 request.key.instrument, request.key.exchange, request.state.kind
3637 );
3638 return (
3639 build_open_order_err_response(
3640 request,
3641 UnindexedOrderError::Rejected(ApiError::OrderRejected(reason)),
3642 ),
3643 None,
3644 );
3645 };
3646
3647 let time_exchange = self.time_exchange();
3648 let settlement = self.settlement(
3649 terms,
3650 self.net_position(terms, &request.key.instrument),
3651 side,
3652 request.state.quantity,
3653 estimate,
3654 liquidity,
3655 );
3656
3657 let debit = settlement.reserved();
3658 let effect = self.margin_effect(request.state.margin_effect);
3659 let (committed, interest) =
3660 match self.commit(&debit, None, effect, terms, market.as_ref(), time_exchange) {
3661 Ok(Commitment {
3664 committed,
3665 interest,
3666 ..
3667 }) => (committed, interest),
3668 Err(insufficient) => {
3669 return (
3670 build_open_order_err_response(
3671 request,
3672 self.insufficient_error(insufficient, side),
3673 ),
3674 None,
3675 );
3676 }
3677 };
3678
3679 let open = Open::new(
3680 VenueOrderId::Assigned(self.order_id_sequence_fetch_add()),
3681 time_exchange,
3682 Decimal::ZERO,
3683 );
3684 let order = Order {
3687 key: request.key,
3688 side,
3689 price: limit,
3690 quantity: request.state.quantity,
3691 kind: request.state.kind,
3692 time_in_force: request.state.time_in_force,
3693 state: open,
3694 };
3695
3696 let reservation = Reservation {
3699 amount: settlement.requirement(),
3700 asset: settlement.asset,
3701 borrowed: committed.shortfall_borrowed(),
3702 excess_borrowed: committed.excess_borrowed,
3703 effect,
3704 unreported_interest: interest.map(|flow| vec![*flow]).unwrap_or_default(),
3705 };
3706 if let Err(AlreadyResting(refused)) = self.account.hold(order.clone(), Some(reservation)) {
3707 unreachable!(
3708 "SimulatedVenue held {} over a resting order: open_order_inner checks first",
3709 refused.order.key.cid
3710 );
3711 }
3712
3713 (
3714 order.map_state(OrderState::active),
3715 Some(OpenOrderNotifications::Rested {
3716 balance: Snapshot(committed.paid),
3717 }),
3718 )
3719 }
3720
3721 fn check_shortability(
3731 &self,
3732 request: &OrderRequestOpen<ExchangeId, InstrumentNameExchange>,
3733 terms: &InstrumentTerms,
3734 market: Option<&MarketSnapshot>,
3735 ) -> Result<(), UnindexedApiError> {
3736 let Some(provider) = &self.shortability else {
3737 return Ok(());
3738 };
3739 if !terms.cash_settled || request.state.side != Side::Sell {
3740 return Ok(());
3741 }
3742
3743 let instrument = &request.key.instrument;
3744 let resting_sells: Decimal = self
3745 .account
3746 .orders_open()
3747 .filter(|order| order.key.instrument == *instrument && order.side == Side::Sell)
3748 .map(|order| order.state.quantity_remaining(order.quantity))
3749 .sum();
3750 let committed = self.net_position(terms, instrument).quantity - resting_sells;
3752 let short_before = (-committed).max(Decimal::ZERO);
3753 let short_after = (request.state.quantity - committed).max(Decimal::ZERO);
3754 if short_after <= short_before {
3755 return Ok(());
3756 }
3757
3758 let now = self.time_exchange();
3759 let exchange = request.key.exchange;
3760 let shortability = provider.shortability(instrument, now);
3761
3762 if let Some(delisting_at) = shortability.delisting_at
3763 && delisting_at <= now
3764 {
3765 return Err(ApiError::BorrowRejected(BorrowReject::new(
3766 BorrowRejectReason::NotShortable,
3767 None,
3768 format!(
3769 "{instrument} cannot be sold short on {exchange} at {now}: it is delisted at \
3770 {delisting_at}"
3771 ),
3772 )));
3773 }
3774 if shortability.shortable == Some(false) {
3775 return Err(ApiError::BorrowRejected(BorrowReject::new(
3776 BorrowRejectReason::NotShortable,
3777 None,
3778 format!("{instrument} cannot be sold short on {exchange} at {now}"),
3779 )));
3780 }
3781
3782 if let Some(available) = shortability.available
3783 && short_after > available
3784 {
3785 let message = format!(
3786 "selling {} {instrument} on {exchange} would take its short, resting sells \
3787 included, to {short_after}, past the {available} there is to borrow at {now}",
3788 request.state.quantity
3789 );
3790 let left = (available - short_before).max(Decimal::ZERO);
3791 return Err(ApiError::BorrowRejected(
3792 BorrowReject::new(BorrowRejectReason::InventoryUnavailable, None, message)
3793 .with_available(left),
3794 ));
3795 }
3796
3797 let marketable = || {
3803 market.is_some_and(|market| match request.state.kind {
3804 OrderKind::Limit => request
3805 .state
3806 .price
3807 .is_some_and(|limit| crosses(Side::Sell, limit, market)),
3808 _ => self
3809 .fill_model
3810 .fill_price(&FillContext::new(
3811 Side::Sell,
3812 request.state.quantity,
3813 market,
3814 ))
3815 .is_some(),
3816 })
3817 };
3818 if provider.short_sale_restricted(instrument, now) && marketable() {
3819 return Err(ApiError::BorrowRejected(BorrowReject::new(
3820 BorrowRejectReason::ShortSaleRestricted,
3821 None,
3822 format!(
3823 "{instrument} is under a short-sale restriction on {exchange} at {now}: a \
3824 short sale must be priced above the best bid"
3825 ),
3826 )));
3827 }
3828
3829 Ok(())
3830 }
3831
3832 fn cancel_on_arrival(
3839 &mut self,
3840 request: OrderRequestOpen<ExchangeId, InstrumentNameExchange>,
3841 now: DateTime<Utc>,
3842 ) -> (
3843 Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>,
3844 Option<OpenOrderNotifications>,
3845 ) {
3846 let cancelled = Cancelled {
3847 id: self.order_id_sequence_fetch_add(),
3848 time_exchange: now,
3849 filled_quantity: Some(Decimal::ZERO),
3850 };
3851
3852 self.account.ack_cancelled(Order {
3853 key: request.key.clone(),
3854 side: request.state.side,
3855 price: request.state.price,
3856 quantity: request.state.quantity,
3857 kind: request.state.kind,
3858 time_in_force: request.state.time_in_force,
3859 state: cancelled.clone(),
3860 });
3861
3862 (
3863 Order {
3864 key: request.key,
3865 side: request.state.side,
3866 price: request.state.price,
3867 quantity: request.state.quantity,
3868 kind: request.state.kind,
3869 time_in_force: request.state.time_in_force,
3870 state: OrderState::inactive(cancelled),
3871 },
3872 None,
3873 )
3874 }
3875
3876 fn expire_on_arrival(
3890 &mut self,
3891 request: OrderRequestOpen<ExchangeId, InstrumentNameExchange>,
3892 now: DateTime<Utc>,
3893 ) -> (
3894 Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>,
3895 Option<OpenOrderNotifications>,
3896 ) {
3897 let expired = Expired {
3898 id: self.order_id_sequence_fetch_add(),
3899 time_exchange: now,
3900 filled_quantity: Some(Decimal::ZERO),
3901 };
3902
3903 self.account.ack_expired(Order {
3904 key: request.key.clone(),
3905 side: request.state.side,
3906 price: request.state.price,
3907 quantity: request.state.quantity,
3908 kind: request.state.kind,
3909 time_in_force: request.state.time_in_force,
3910 state: expired.clone(),
3911 });
3912
3913 (
3914 Order {
3915 key: request.key,
3916 side: request.state.side,
3917 price: request.state.price,
3918 quantity: request.state.quantity,
3919 kind: request.state.kind,
3920 time_in_force: request.state.time_in_force,
3921 state: OrderState::expired(expired),
3922 },
3923 None,
3924 )
3925 }
3926
3927 fn fill_on_arrival(
3952 &mut self,
3953 request: OrderRequestOpen<ExchangeId, InstrumentNameExchange>,
3954 market: Option<MarketSnapshot>,
3955 terms: &InstrumentTerms,
3956 remainder: Remainder,
3957 ) -> (
3958 Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>,
3959 Option<OpenOrderNotifications>,
3960 ) {
3961 let market_supplied = market.is_some();
3962 let market = market.unwrap_or_default();
3963
3964 let limit = match request.state.kind {
3967 OrderKind::Limit => request.state.price,
3968 _ => None,
3969 };
3970
3971 let maybe_reference = self
3975 .fill_model
3976 .fill_price(&FillContext::new(
3977 request.state.side,
3978 request.state.quantity,
3979 &market,
3980 ))
3981 .or(limit);
3985
3986 let Some(reference) = maybe_reference else {
3998 let cause = if market_supplied {
3999 "no market price available yet"
4000 } else {
4001 "the request carried no market snapshot (RequestOpen::market was None)"
4002 };
4003 let reason = format!(
4004 "cannot price {} for {}: {cause} and OrderKind::{:?} carries no limit price",
4005 request.key.instrument, request.key.exchange, request.state.kind
4006 );
4007 return (
4008 build_open_order_err_response(
4009 request,
4010 UnindexedOrderError::Rejected(ApiError::OrderRejected(reason)),
4011 ),
4012 None,
4013 );
4014 };
4015
4016 let time_exchange = self.time_exchange();
4017 let side = request.state.side;
4018
4019 let filled_quantity = match self.depth_available(&request.key.instrument, side) {
4021 Some(available) => request.state.quantity.min(available),
4022 None => request.state.quantity,
4023 };
4024 let unfilled = request.state.quantity - filled_quantity;
4025
4026 if filled_quantity <= Decimal::ZERO || (unfilled > Decimal::ZERO && remainder.is_kill()) {
4031 return match remainder {
4032 Remainder::Rest { limit } => self.rest_order(request, limit, terms, Decimal::ZERO),
4034 Remainder::Cancel | Remainder::Kill => {
4035 self.cancel_on_arrival(request, time_exchange)
4036 }
4037 };
4038 }
4039
4040 let slippage_before = self.slippage.clone();
4044 let fill_price = self.slippage.apply(side, reference);
4045
4046 let fill_price = match limit {
4052 Some(limit) => clamp_to_limit(side, fill_price, limit),
4053 None => fill_price,
4054 };
4055
4056 let position = self.net_position(terms, &request.key.instrument);
4058 let fill = self.fill_settlement(
4059 terms,
4060 position,
4061 side,
4062 filled_quantity,
4063 fill_price,
4064 Liquidity::Taker,
4065 );
4066
4067 let held = match remainder {
4072 Remainder::Rest { limit } if unfilled > Decimal::ZERO => Some(self.settlement(
4073 terms,
4074 position.after(side, filled_quantity, fill_price),
4075 side,
4076 unfilled,
4077 limit,
4078 Liquidity::Maker,
4079 )),
4080 _ => None,
4081 };
4082
4083 let debit = match &held {
4087 Some(held) => fill.settled_holding(held),
4088 None => fill.settled(),
4089 };
4090
4091 let effect = self.margin_effect(request.state.margin_effect);
4092 let mut commitment = match self.commit(
4093 &debit,
4094 Some(&fill.credit),
4095 effect,
4096 terms,
4097 Some(&market),
4098 time_exchange,
4099 ) {
4100 Ok(commitment) => commitment,
4101 Err(insufficient) => {
4102 self.slippage = slippage_before;
4105 return (
4106 build_open_order_err_response(
4107 request,
4108 self.insufficient_error(insufficient, side),
4109 ),
4110 None,
4111 );
4112 }
4113 };
4114
4115 self.take_fee_apart(&fill, &mut commitment, time_exchange);
4116
4117 self.consume_depth(&request.key.instrument, side, filled_quantity);
4119 self.record_fill(
4120 terms,
4121 &request.key.instrument,
4122 side,
4123 Fill {
4124 quantity: filled_quantity,
4125 price: fill_price,
4126 },
4127 );
4128
4129 let order_id = self.order_id_sequence_fetch_add();
4130 let trade = Trade {
4131 id: self.trade_id_sequence_fetch_add(),
4132 order_id: order_id.clone(),
4133 instrument: request.key.instrument.clone(),
4134 strategy: request.key.strategy.clone(),
4135 time_exchange,
4136 side,
4137 price: fill_price,
4138 quantity: filled_quantity,
4139 order_filled_quantity: Some(filled_quantity),
4142 fees: fill.fees,
4143 origin: TradeOrigin::Order,
4144 position_side: None,
4145 client_order_id: Some(request.key.cid.clone()),
4146 time_source: TimeSource::Venue,
4147 };
4148
4149 self.account.ack_trade(trade.clone());
4152
4153 let state = self.retire_filled(
4154 &request,
4155 order_id,
4156 Fill {
4157 quantity: filled_quantity,
4158 price: fill_price,
4159 },
4160 held,
4161 &commitment.committed,
4162 time_exchange,
4163 );
4164
4165 (
4166 Order {
4167 key: request.key,
4168 side,
4169 price: request.state.price,
4170 quantity: request.state.quantity,
4171 kind: request.state.kind,
4172 time_in_force: request.state.time_in_force,
4173 state,
4174 },
4175 Some(OpenOrderNotifications::Filled { commitment, trade }),
4176 )
4177 }
4178
4179 fn retire_filled(
4185 &mut self,
4186 request: &OrderRequestOpen<ExchangeId, InstrumentNameExchange>,
4187 order_id: OrderId,
4188 fill: Fill,
4189 held: Option<Settlement>,
4190 committed: &Committed,
4191 time_exchange: DateTime<Utc>,
4192 ) -> UnindexedOrderState {
4193 match held {
4196 Some(held) => {
4200 let open = Open::new(
4201 VenueOrderId::Assigned(order_id),
4202 time_exchange,
4203 fill.quantity,
4204 );
4205 self.book_rested(
4206 Order {
4207 key: request.key.clone(),
4208 side: request.state.side,
4209 price: request.state.price,
4210 quantity: request.state.quantity,
4211 kind: request.state.kind,
4212 time_in_force: request.state.time_in_force,
4213 state: open.clone(),
4214 },
4215 Reservation {
4216 borrowed: committed.shortfall_borrowed().min(held.requirement()),
4219 excess_borrowed: committed.excess_borrowed,
4220 amount: held.requirement(),
4221 asset: held.asset,
4222 effect: self.margin_effect(request.state.margin_effect),
4223 unreported_interest: Vec::new(),
4226 },
4227 );
4228
4229 OrderState::active(open)
4230 }
4231
4232 None if fill.quantity == request.state.quantity => {
4234 let filled = Filled::new(
4237 order_id,
4238 time_exchange,
4239 request.state.quantity,
4240 Some(fill.price),
4241 );
4242 self.account.ack_filled(Order {
4243 key: request.key.clone(),
4244 side: request.state.side,
4245 price: request.state.price,
4246 quantity: request.state.quantity,
4247 kind: request.state.kind,
4248 time_in_force: request.state.time_in_force,
4249 state: filled.clone(),
4250 });
4251
4252 OrderState::fully_filled(filled)
4253 }
4254
4255 None => {
4259 let cancelled = Cancelled {
4260 id: order_id,
4261 time_exchange,
4262 filled_quantity: Some(fill.quantity),
4263 };
4264 self.account.ack_cancelled(Order {
4265 key: request.key.clone(),
4266 side: request.state.side,
4267 price: request.state.price,
4268 quantity: request.state.quantity,
4269 kind: request.state.kind,
4270 time_in_force: request.state.time_in_force,
4271 state: cancelled.clone(),
4272 });
4273
4274 OrderState::inactive(cancelled)
4275 }
4276 }
4277 }
4278
4279 fn depth_available(&self, instrument: &InstrumentNameExchange, side: Side) -> Option<Decimal> {
4281 self.market
4282 .get(instrument)
4283 .and_then(|market| market.depth.available_to(side))
4284 }
4285
4286 fn consume_depth(
4288 &mut self,
4289 instrument: &InstrumentNameExchange,
4290 side: Side,
4291 quantity: Decimal,
4292 ) {
4293 if let Some(market) = self.market.get_mut(instrument) {
4294 market.depth.consume(side, quantity);
4295 }
4296 }
4297
4298 fn book_rested(&mut self, order: OpenOrder, reservation: Reservation) {
4306 if let Err(AlreadyResting(refused)) = self.account.book(order, Some(reservation)) {
4307 unreachable!(
4308 "SimulatedVenue booked {} over a resting order: open_order_inner checks first",
4309 refused.order.key.cid
4310 );
4311 }
4312 }
4313
4314 fn rest_order(
4333 &mut self,
4334 request: OrderRequestOpen<ExchangeId, InstrumentNameExchange>,
4335 limit: Decimal,
4336 terms: &InstrumentTerms,
4337 filled_quantity: Decimal,
4338 ) -> (
4339 Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>,
4340 Option<OpenOrderNotifications>,
4341 ) {
4342 let time_exchange = self.time_exchange();
4343
4344 let settlement = self.settlement(
4347 terms,
4348 self.net_position(terms, &request.key.instrument),
4349 request.state.side,
4350 request.state.quantity - filled_quantity,
4351 limit,
4352 Liquidity::Maker,
4353 );
4354
4355 let debit = settlement.reserved();
4356 let market = self
4357 .market(&request.key.instrument)
4358 .map(|market| market.snapshot);
4359 let effect = self.margin_effect(request.state.margin_effect);
4360 let (committed, interest) =
4361 match self.commit(&debit, None, effect, terms, market.as_ref(), time_exchange) {
4362 Ok(Commitment {
4365 committed,
4366 interest,
4367 ..
4368 }) => (committed, interest),
4369 Err(insufficient) => {
4370 let error = self.insufficient_error(insufficient, request.state.side);
4371 return (build_open_order_err_response(request, error), None);
4372 }
4373 };
4374
4375 let open = Open::new(
4378 VenueOrderId::Assigned(self.order_id_sequence_fetch_add()),
4379 time_exchange,
4380 filled_quantity,
4381 );
4382
4383 self.book_rested(
4384 Order {
4385 key: request.key.clone(),
4386 side: request.state.side,
4387 price: request.state.price,
4388 quantity: request.state.quantity,
4389 kind: request.state.kind,
4390 time_in_force: request.state.time_in_force,
4391 state: open.clone(),
4392 },
4393 Reservation {
4394 amount: settlement.requirement(),
4395 asset: settlement.asset,
4396 borrowed: committed.shortfall_borrowed(),
4397 excess_borrowed: committed.excess_borrowed,
4398 effect,
4399 unreported_interest: interest.map(|flow| vec![*flow]).unwrap_or_default(),
4401 },
4402 );
4403
4404 let order_response = Order {
4405 key: request.key,
4406 side: request.state.side,
4407 price: request.state.price,
4408 quantity: request.state.quantity,
4409 kind: request.state.kind,
4410 time_in_force: request.state.time_in_force,
4411 state: OrderState::active(open),
4412 };
4413
4414 (
4415 order_response,
4416 Some(OpenOrderNotifications::Rested {
4417 balance: Snapshot(committed.paid),
4418 }),
4419 )
4420 }
4421
4422 fn instrument_terms(
4424 &self,
4425 instrument: &InstrumentNameExchange,
4426 ) -> Result<InstrumentTerms, UnindexedApiError> {
4427 let instrument = self.find_instrument_data(instrument)?;
4428 let cash_settled = Self::settlement_of_supported_kind(instrument)?;
4429
4430 Ok(InstrumentTerms {
4431 underlying: instrument.underlying.clone(),
4432 contract_size: instrument.kind.contract_size(),
4433 cash_settled,
4434 })
4435 }
4436
4437 fn net_position(
4441 &self,
4442 terms: &InstrumentTerms,
4443 instrument: &InstrumentNameExchange,
4444 ) -> NetPosition {
4445 if !terms.cash_settled {
4446 return NetPosition::default();
4447 }
4448 self.positions.get(instrument).copied().unwrap_or_default()
4449 }
4450
4451 fn record_fill(
4454 &mut self,
4455 terms: &InstrumentTerms,
4456 instrument: &InstrumentNameExchange,
4457 side: Side,
4458 fill: Fill,
4459 ) {
4460 if !terms.cash_settled {
4461 return;
4462 }
4463
4464 let opened_or_closed = match self.positions.get_mut(instrument) {
4466 Some(position) => {
4467 *position = position.after(side, fill.quantity, fill.price);
4468 let closed = position.quantity.is_zero();
4469 if closed {
4470 self.positions.remove(instrument);
4471 }
4472 closed
4473 }
4474 None => {
4475 let position = NetPosition::default().after(side, fill.quantity, fill.price);
4476 let opened = !position.quantity.is_zero();
4477 if opened {
4478 self.positions.insert(instrument.clone(), position);
4479 }
4480 opened
4481 }
4482 };
4483
4484 if opened_or_closed && !self.holding_costs.is_empty() {
4487 self.refresh_next_charge();
4488 }
4489 }
4490
4491 fn settlement(
4511 &self,
4512 terms: &InstrumentTerms,
4513 position: NetPosition,
4514 side: Side,
4515 quantity: Decimal,
4516 price: Decimal,
4517 liquidity: Liquidity,
4518 ) -> Settlement {
4519 let fees_quote =
4520 self.fee_model
4521 .compute_fee(price, quantity.abs(), terms.contract_size, liquidity);
4522 self.settlement_with_fee(terms, position, side, quantity, price, fees_quote)
4523 }
4524
4525 fn fill_settlement(
4533 &self,
4534 terms: &InstrumentTerms,
4535 position: NetPosition,
4536 side: Side,
4537 quantity: Decimal,
4538 price: Decimal,
4539 liquidity: Liquidity,
4540 ) -> Settlement {
4541 let settlement = self.settlement(terms, position, side, quantity, price, liquidity);
4542 match self.fee_in_fee_asset(terms, &settlement.fees) {
4543 Some(fees) => Settlement {
4544 fees,
4545 fee_apart: true,
4546 ..self.settlement_with_fee(terms, position, side, quantity, price, Decimal::ZERO)
4547 },
4548 None => settlement,
4549 }
4550 }
4551
4552 fn fee_in_fee_asset(
4557 &self,
4558 terms: &InstrumentTerms,
4559 fees: &AssetFees<AssetNameExchange>,
4560 ) -> Option<AssetFees<AssetNameExchange>> {
4561 let config = self.fee_asset.as_ref()?;
4562 let asset = config.asset();
4563 let underlying = &terms.underlying;
4564 let fees_quote = fees.fees_quote?;
4565 if terms.cash_settled
4566 || *asset == underlying.base
4567 || *asset == underlying.quote
4568 || fees_quote <= Decimal::ZERO
4569 {
4570 return None;
4571 }
4572
4573 let price = self.price_in(asset, &underlying.quote, None)?;
4574 let fees_quote = fees_quote * (Decimal::ONE - config.discount());
4575 let fee = fees_quote.checked_div(price)?;
4576 (self.account.free(asset) >= fee)
4577 .then(|| AssetFees::new(asset.clone(), fee, Some(fees_quote)))
4578 }
4579
4580 fn settlement_with_fee(
4582 &self,
4583 terms: &InstrumentTerms,
4584 position: NetPosition,
4585 side: Side,
4586 quantity: Decimal,
4587 price: Decimal,
4588 fees_quote: Decimal,
4589 ) -> Settlement {
4590 let quantity = quantity.abs();
4591 let fees = AssetFees::new(terms.underlying.quote.clone(), fees_quote, Some(fees_quote));
4592
4593 if terms.cash_settled {
4594 let closing = position.closing(side, quantity);
4607 let opening = quantity - closing;
4608 let open_margin = price * opening * terms.contract_size;
4609 let close_proceeds = closing
4610 * terms.contract_size
4611 * match position.is_long() {
4612 true => price,
4613 false => position.entry + position.entry - price,
4614 };
4615
4616 let (credit, shortfall) = match close_proceeds.is_sign_negative() {
4620 true => (Decimal::ZERO, -close_proceeds),
4621 false => (close_proceeds, Decimal::ZERO),
4622 };
4623
4624 return Settlement {
4625 asset: terms.underlying.quote.clone(),
4626 amount: open_margin + fees_quote + shortfall,
4627 credit: Credit::PaidBack(credit),
4628 fees,
4629 fee_apart: false,
4630 };
4631 }
4632
4633 let notional = price * quantity * terms.contract_size;
4639 match side {
4640 Side::Buy
4641 if self.spot_fee_asset == SpotFeeAsset::Received
4642 && price > Decimal::ZERO
4643 && !fees_quote.is_zero() =>
4644 {
4645 let fee_base = fees_quote / price;
4646 Settlement {
4647 asset: terms.underlying.quote.clone(),
4648 amount: notional,
4649 credit: Credit::Delivery(Delivery {
4650 asset: terms.underlying.base.clone(),
4651 amount: quantity - fee_base,
4652 }),
4653 fees: AssetFees::new(terms.underlying.base.clone(), fee_base, Some(fees_quote)),
4654 fee_apart: false,
4655 }
4656 }
4657 Side::Buy => Settlement {
4658 asset: terms.underlying.quote.clone(),
4659 amount: notional + fees_quote,
4660 credit: Credit::Delivery(Delivery {
4661 asset: terms.underlying.base.clone(),
4662 amount: quantity,
4663 }),
4664 fees,
4665 fee_apart: false,
4666 },
4667 Side::Sell => Settlement {
4668 asset: terms.underlying.base.clone(),
4669 amount: quantity,
4670 credit: Credit::Delivery(Delivery {
4671 asset: terms.underlying.quote.clone(),
4672 amount: notional - fees_quote,
4673 }),
4674 fees,
4675 fee_apart: false,
4676 },
4677 }
4678 }
4679
4680 pub fn validate_order_kind_supported(
4690 &self,
4691 order_kind: OrderKind,
4692 ) -> Result<(), UnindexedOrderError> {
4693 let reason = match (order_kind, self.regime) {
4694 (OrderKind::Market, _) | (OrderKind::Limit, VenueRegime::MarketDriven) => return Ok(()),
4695 (OrderKind::Limit, VenueRegime::RequestPriced) => format!(
4696 "SimulatedVenue does not support OrderKind::{order_kind:?} in \
4697 VenueRegime::RequestPriced: it has no market feed, so an order that rested could \
4698 never be matched. Construct it with SimulatedVenue::new_market_driven, and feed \
4699 it with apply_market"
4700 ),
4701 _ => format!("SimulatedVenue does not support OrderKind::{order_kind:?}"),
4702 };
4703
4704 Err(UnindexedOrderError::Rejected(ApiError::OrderRejected(
4705 reason,
4706 )))
4707 }
4708
4709 fn settlement_of_supported_kind(
4724 instrument: &Instrument<ExchangeId, AssetNameExchange>,
4725 ) -> Result<bool, UnindexedApiError> {
4726 match &instrument.kind {
4727 InstrumentKind::Spot => Ok(false),
4728 InstrumentKind::Cfd(_) => Ok(true),
4729 unsupported => Err(ApiError::InstrumentInvalid(
4730 instrument.name_exchange.clone(),
4731 format!(
4732 "SimulatedVenue does not support {}; only Spot and Cfd are modelled",
4733 match unsupported {
4734 InstrumentKind::Perpetual(_) => "InstrumentKind::Perpetual",
4735 InstrumentKind::Future(_) => "InstrumentKind::Future",
4736 InstrumentKind::Option(_) => "InstrumentKind::Option",
4737 InstrumentKind::Spot | InstrumentKind::Cfd(_) => "InstrumentKind",
4740 }
4741 ),
4742 )),
4743 }
4744 }
4745
4746 pub fn find_instrument_data(
4747 &self,
4748 instrument: &InstrumentNameExchange,
4749 ) -> Result<&Instrument<ExchangeId, AssetNameExchange>, UnindexedApiError> {
4750 self.instruments.get(instrument).ok_or_else(|| {
4751 ApiError::InstrumentInvalid(
4752 instrument.clone(),
4753 format!("SimulatedVenue is not set-up for managing: {instrument}"),
4754 )
4755 })
4756 }
4757
4758 fn order_id_sequence_fetch_add(&mut self) -> OrderId {
4759 let sequence = self.order_sequence;
4760 self.order_sequence += 1;
4761 OrderId((self.order_id_start + u128::from(sequence)).to_smolstr())
4762 }
4763
4764 fn trade_id_sequence_fetch_add(&mut self) -> TradeId {
4770 let sequence = self.trade_sequence;
4771 self.trade_sequence += 1;
4772 TradeId(sequence.to_smolstr())
4773 }
4774
4775 fn build_account_event<Kind>(&self, kind: Kind) -> UnindexedAccountEvent
4776 where
4777 Kind: Into<AccountEventKind<ExchangeId, AssetNameExchange, InstrumentNameExchange>>,
4778 {
4779 UnindexedAccountEvent {
4780 exchange: self.exchange,
4781 kind: kind.into(),
4782 }
4783 }
4784}
4785
4786fn next_boundary(
4793 model: &dyn HoldingCostModel,
4794 instrument: &InstrumentNameExchange,
4795 after: DateTime<Utc>,
4796) -> Option<DateTime<Utc>> {
4797 let boundary = model.next_boundary(instrument, after)?;
4798 assert!(
4799 boundary > after,
4800 "HoldingCostModel {model:?} returned boundary {boundary} for {instrument}, which is not \
4801 after {after}"
4802 );
4803 Some(boundary)
4804}
4805
4806fn next_interest_boundary(
4811 model: &dyn MarginInterestModel,
4812 asset: &AssetNameExchange,
4813 after: DateTime<Utc>,
4814) -> Option<DateTime<Utc>> {
4815 let boundary = model.next_boundary(asset, after)?;
4816 assert!(
4817 boundary > after,
4818 "MarginInterestModel {model:?} returned boundary {boundary} for {asset}, which is not \
4819 after {after}"
4820 );
4821 Some(boundary)
4822}
4823
4824fn boundaries_through(
4826 after: DateTime<Utc>,
4827 through: DateTime<Utc>,
4828 mut next: impl FnMut(DateTime<Utc>) -> Option<DateTime<Utc>>,
4829) -> impl Iterator<Item = DateTime<Utc>> {
4830 std::iter::successors(next(after), move |&boundary| next(boundary))
4831 .take_while(move |&boundary| boundary <= through)
4832}
4833
4834#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
4837enum Due {
4838 Holding(InstrumentNameExchange, usize),
4840 Interest(AssetNameExchange, Decimal),
4843}
4844
4845fn seeded_positions(
4857 initial_state: &UnindexedAccountSnapshot,
4858 instruments: &FnvHashMap<InstrumentNameExchange, Instrument<ExchangeId, AssetNameExchange>>,
4859) -> FnvHashMap<InstrumentNameExchange, NetPosition> {
4860 initial_state
4861 .instruments
4862 .iter()
4863 .filter(|snapshot| {
4864 instruments
4865 .get(&snapshot.instrument)
4866 .is_some_and(|instrument| matches!(instrument.kind, InstrumentKind::Cfd(_)))
4867 })
4868 .filter_map(|snapshot| {
4869 let position = snapshot.position.open()?;
4870 let Some(entry) = position.entry_price else {
4871 panic!(
4872 "SimulatedVenue's initial_state reports an open position in {} with no entry \
4873 price, so the margin and PnL its close would credit cannot be computed",
4874 snapshot.instrument
4875 );
4876 };
4877 Some((
4878 snapshot.instrument.clone(),
4879 NetPosition {
4880 quantity: position.quantity,
4881 entry,
4882 },
4883 ))
4884 })
4885 .collect()
4886}
4887
4888fn owed(balance: &AssetBalance<AssetNameExchange>) -> Decimal {
4890 balance
4891 .balance
4892 .margin
4893 .map_or(Decimal::ZERO, |margin| margin.owed())
4894}
4895
4896#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4899enum MarginCheck {
4900 Borrow,
4902 Liquidation,
4904}
4905
4906impl MarginCheck {
4907 fn collateral_ratio(self, config: &SpotMarginConfig, asset: &AssetNameExchange) -> Decimal {
4909 match self {
4910 Self::Borrow => config.borrow_collateral_ratio(asset),
4911 Self::Liquidation => config.liquidation_collateral_ratio(asset),
4912 }
4913 }
4914}
4915
4916#[derive(Debug, Clone, Copy, Default)]
4918struct Valued {
4919 equity: Decimal,
4922 debt: Decimal,
4924}
4925
4926fn mid_or_last(snapshot: &MarketSnapshot) -> Option<Decimal> {
4928 match (snapshot.best_bid, snapshot.best_ask) {
4929 (Some(bid), Some(ask)) => Some((bid + ask) / Decimal::TWO),
4930 _ => snapshot.last_price,
4931 }
4932}
4933
4934fn crosses(side: Side, limit: Decimal, market: &MarketSnapshot) -> bool {
4942 match side {
4943 Side::Buy => match market.best_ask {
4944 Some(best_ask) => best_ask <= limit,
4945 None => market.last_price.is_some_and(|last| last <= limit),
4946 },
4947 Side::Sell => match market.best_bid {
4948 Some(best_bid) => best_bid >= limit,
4949 None => market.last_price.is_some_and(|last| last >= limit),
4950 },
4951 }
4952}
4953
4954fn clamp_to_limit(side: Side, price: Decimal, limit: Decimal) -> Decimal {
4956 match side {
4957 Side::Buy => price.min(limit),
4958 Side::Sell => price.max(limit),
4959 }
4960}
4961
4962fn validate_time_in_force_supported(
4992 order_kind: OrderKind,
4993 time_in_force: TimeInForce,
4994) -> Result<(), UnindexedOrderError> {
4995 const NO_CALENDAR: &str = "a session calendar, which this venue does not have";
4996
4997 let missing = match (order_kind, time_in_force) {
4998 (_, TimeInForce::AtOpen | TimeInForce::AtClose) => NO_CALENDAR,
5000
5001 (OrderKind::Market, TimeInForce::GoodUntilCancelled { post_only: true }) => {
5003 "an order that both takes liquidity and refuses to"
5004 }
5005
5006 (OrderKind::Market, _) => return Ok(()),
5009
5010 (OrderKind::Limit, TimeInForce::GoodUntilEndOfDay) => NO_CALENDAR,
5011
5012 (
5014 OrderKind::Limit,
5015 TimeInForce::GoodUntilCancelled { .. }
5016 | TimeInForce::ImmediateOrCancel
5017 | TimeInForce::FillOrKill
5018 | TimeInForce::GoodTillDate { .. },
5019 ) => return Ok(()),
5020
5021 (_, _) => return Ok(()),
5023 };
5024
5025 Err(UnindexedOrderError::Rejected(ApiError::OrderRejected(
5026 format!(
5027 "SimulatedVenue does not support TimeInForce::{time_in_force} on \
5028 OrderKind::{order_kind:?}: it does not model {missing}"
5029 ),
5030 )))
5031}
5032
5033#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5048enum Disposition {
5049 FillAndRest,
5051 FillAndCancel,
5053 FillOrNothing,
5056 Rest,
5058 CancelUnfilled,
5062 Expire,
5064}
5065
5066fn disposition(
5084 order_kind: OrderKind,
5085 time_in_force: TimeInForce,
5086 marketable: bool,
5087 now: DateTime<Utc>,
5088) -> Disposition {
5089 match time_in_force {
5090 TimeInForce::GoodUntilCancelled { post_only: true } if marketable => {
5092 Disposition::CancelUnfilled
5093 }
5094
5095 TimeInForce::GoodTillDate { expiry } if now >= expiry => Disposition::Expire,
5099
5100 TimeInForce::FillOrKill if marketable => Disposition::FillOrNothing,
5103
5104 _ if marketable => match (order_kind, time_in_force) {
5108 (
5109 OrderKind::Limit,
5110 TimeInForce::GoodUntilCancelled { .. } | TimeInForce::GoodTillDate { .. },
5111 ) => Disposition::FillAndRest,
5112 _ => Disposition::FillAndCancel,
5113 },
5114
5115 TimeInForce::ImmediateOrCancel | TimeInForce::FillOrKill => Disposition::CancelUnfilled,
5116
5117 _ => Disposition::Rest,
5118 }
5119}
5120
5121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5124enum MarketAge {
5125 NoFeed,
5127 Unpriced,
5130 Fresh,
5132 Stale { fresh_at: Option<DateTime<Utc>> },
5135}
5136
5137#[derive(Debug, Clone, Copy)]
5139enum Release<'a> {
5140 Bar(&'a MarketBar, NextBarPrice),
5142 Market(&'a MarketSnapshot),
5145}
5146
5147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5155enum Remainder {
5156 Rest { limit: Decimal },
5158 Cancel,
5160 Kill,
5164}
5165
5166impl Remainder {
5167 fn is_kill(self) -> bool {
5170 matches!(self, Self::Kill)
5171 }
5172}
5173
5174fn build_open_order_err_response<E>(
5175 request: OrderRequestOpen<ExchangeId, InstrumentNameExchange>,
5176 error: E,
5177) -> Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>
5178where
5179 E: Into<UnindexedOrderError>,
5180{
5181 Order {
5182 key: request.key,
5183 side: request.state.side,
5184 price: request.state.price,
5185 quantity: request.state.quantity,
5186 kind: request.state.kind,
5187 time_in_force: request.state.time_in_force,
5188 state: OrderState::inactive(error.into()),
5189 }
5190}
5191
5192#[allow(clippy::large_enum_variant)]
5215#[derive(Debug)]
5216enum OpenOrderNotifications {
5217 Filled {
5219 commitment: Commitment,
5220 trade: Trade<AssetNameExchange, InstrumentNameExchange>,
5221 },
5222 Rested {
5226 balance: Snapshot<AssetBalance<AssetNameExchange>>,
5227 },
5228}
5229
5230#[derive(Debug)]
5233struct Commitment {
5234 committed: Committed,
5235 interest: Option<Box<CashFlow<AssetNameExchange, InstrumentNameExchange>>>,
5242 held_interest: Vec<CashFlow<AssetNameExchange, InstrumentNameExchange>>,
5246 fee: Option<Box<AssetBalance<AssetNameExchange>>>,
5252}
5253
5254impl From<Committed> for Commitment {
5255 fn from(committed: Committed) -> Self {
5256 Self {
5257 committed,
5258 interest: None,
5259 held_interest: Vec::new(),
5260 fee: None,
5261 }
5262 }
5263}
5264
5265#[derive(Debug)]
5268#[must_use = "its interest must be reported, or carried into the order's next reservation"]
5269struct Released {
5270 balance: AssetBalance<AssetNameExchange>,
5271 interest: Vec<CashFlow<AssetNameExchange, InstrumentNameExchange>>,
5273}
5274
5275#[derive(Debug, Clone)]
5280struct InstrumentTerms {
5281 underlying: Underlying<AssetNameExchange>,
5282 contract_size: Decimal,
5283 cash_settled: bool,
5284}
5285
5286#[derive(Debug)]
5288enum RestingSettlement {
5289 Settled(Commitment),
5291 Unaffordable { released: Option<Released> },
5296}
5297
5298#[allow(clippy::large_enum_variant)]
5304enum Recommitted {
5305 Committed {
5312 commitment: Commitment,
5313 loan: Decimal,
5314 excess_borrowed: Decimal,
5315 },
5316 Unaffordable { released: Option<Released> },
5318}
5319
5320#[derive(Debug, Clone, Copy)]
5325struct Fill {
5326 quantity: Decimal,
5327 price: Decimal,
5328}
5329
5330#[derive(Debug, Clone)]
5332struct Settlement {
5333 asset: AssetNameExchange,
5335 amount: Decimal,
5337 credit: Credit,
5342 fees: AssetFees<AssetNameExchange>,
5346 fee_apart: bool,
5351}
5352
5353impl Settlement {
5354 fn requirement(&self) -> Decimal {
5361 match &self.credit {
5362 Credit::PaidBack(credit) => (self.amount - credit).max(Decimal::ZERO),
5363 Credit::Delivery(_) => self.amount,
5364 }
5365 }
5366
5367 fn settled(&self) -> Debit {
5369 Debit {
5370 asset: self.asset.clone(),
5371 settled: self.amount,
5372 reserved: Decimal::ZERO,
5373 }
5374 }
5375
5376 fn reserved(&self) -> Debit {
5379 Debit {
5380 asset: self.asset.clone(),
5381 settled: Decimal::ZERO,
5382 reserved: self.requirement(),
5383 }
5384 }
5385
5386 fn settled_holding(&self, remainder: &Settlement) -> Debit {
5395 debug_assert!(
5396 self.asset == remainder.asset,
5397 "one order settled {} of {} and reserved {} of {}: an order pays with one asset, so \
5398 two settlements of it cannot name two",
5399 self.amount,
5400 self.asset,
5401 remainder.amount,
5402 remainder.asset
5403 );
5404
5405 Debit {
5406 asset: self.asset.clone(),
5407 settled: self.amount,
5408 reserved: remainder.requirement(),
5409 }
5410 }
5411}
5412
5413#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
5420struct NetPosition {
5421 quantity: Decimal,
5423 entry: Decimal,
5428}
5429
5430impl NetPosition {
5431 fn is_long(&self) -> bool {
5432 self.quantity.is_sign_positive() && !self.quantity.is_zero()
5433 }
5434
5435 fn closing(&self, side: Side, quantity: Decimal) -> Decimal {
5437 let opposes = match side {
5438 Side::Buy => self.quantity.is_sign_negative() && !self.quantity.is_zero(),
5439 Side::Sell => self.is_long(),
5440 };
5441 match opposes {
5442 true => quantity.min(self.quantity.abs()),
5443 false => Decimal::ZERO,
5444 }
5445 }
5446
5447 fn after(self, side: Side, quantity: Decimal, price: Decimal) -> Self {
5453 let signed = match side {
5454 Side::Buy => quantity,
5455 Side::Sell => -quantity,
5456 };
5457 let next = self.quantity + signed;
5458
5459 let entry = if next.is_zero() {
5460 Decimal::ZERO
5461 } else if self.quantity.is_zero()
5462 || next.is_sign_negative() != self.quantity.is_sign_negative()
5463 {
5464 price
5465 } else if signed.is_sign_negative() == self.quantity.is_sign_negative() {
5466 (self.entry * self.quantity.abs() + price * quantity) / next.abs()
5467 } else {
5468 self.entry
5469 };
5470
5471 Self {
5472 quantity: next,
5473 entry,
5474 }
5475 }
5476}
5477
5478fn first_unseeded_order_id(account: &AccountState) -> u128 {
5485 let open = account
5486 .orders_open()
5487 .filter_map(|order| match &order.state.id {
5488 VenueOrderId::Assigned(id) => Some(id),
5489 VenueOrderId::ClientAssigned => None,
5490 });
5491 let cancelled = account.orders_cancelled().map(|order| &order.state.id);
5492
5493 open.chain(cancelled)
5494 .filter_map(|id| id.0.parse::<u64>().ok())
5495 .max()
5496 .map_or(0, |highest| u128::from(highest) + 1)
5497}
5498
5499#[cfg(test)]
5500#[allow(clippy::unwrap_used, clippy::expect_used)] mod tests {
5502 use super::*;
5503 use crate::{
5504 balance::{Balance, MarginDetails},
5505 cash_flow::CashFlowKind,
5506 error::OrderError,
5507 exchange::mock::{
5508 fixtures::*,
5509 orders::{OpenOrders, as_open},
5510 },
5511 fee::{PerContractFeeModel, PercentageFeeModel},
5512 fill::{BidAskFillModel, NextBarPrice, SimFillConfig},
5513 holding_cost::{
5514 AccrualDays, BorrowFeeModel, DailyAccrual, DayCount, FinancingModel, FinancingRates,
5515 FundingModel, HoldingCharge, RateSeries, WeekendsOnly,
5516 },
5517 margin::{InterestCharge, InterestSchedule, MarginEffect, PeriodicMarginInterest},
5518 order::{
5519 OrderEvent, OrderKey, TimeInForce,
5520 id::{ClientOrderId, StrategyId},
5521 request::RequestCancel,
5522 state::{ActiveOrderState, InactiveOrderState},
5523 },
5524 shortability::{Shortability, ShortabilityTable},
5525 slippage::SlippageConfig,
5526 };
5527 use chrono::{NaiveTime, TimeDelta};
5528 use rustrade_instrument::{
5529 Underlying,
5530 instrument::{
5531 kind::cfd::CfdContract, name::InstrumentNameInternal, quote::InstrumentQuoteAsset,
5532 },
5533 };
5534
5535 fn make_venue(btc: &str, usdt: &str) -> SimulatedVenue {
5536 make_venue_with_fee(btc, usdt, FeeModelConfig::default())
5537 }
5538
5539 fn make_venue_with_fee(btc: &str, usdt: &str, fee_model: FeeModelConfig) -> SimulatedVenue {
5540 SimulatedVenue::new(&spot_config(btc, usdt, fee_model), spot_instruments())
5541 }
5542
5543 fn maker_taker_fee() -> FeeModelConfig {
5546 FeeModelConfig::Percentage(PercentageFeeModel::maker_taker(d("0.0002"), d("0.001")))
5547 }
5548
5549 fn make_market_venue(btc: &str, usdt: &str) -> SimulatedVenue {
5551 make_market_venue_with(btc, usdt, maker_taker_fee(), SimFillConfig::default())
5552 }
5553
5554 fn make_market_venue_with(
5555 btc: &str,
5556 usdt: &str,
5557 fee_model: FeeModelConfig,
5558 fill_model: SimFillConfig,
5559 ) -> SimulatedVenue {
5560 SimulatedVenue::new_market_driven(
5561 &spot_config_with_fill(btc, usdt, fee_model, fill_model),
5562 spot_instruments(),
5563 )
5564 }
5565
5566 fn sans_debt(events: &[UnindexedAccountEvent]) -> Vec<&UnindexedAccountEvent> {
5569 events
5570 .iter()
5571 .filter(|event| !matches!(event.kind, AccountEventKind::MarginDebt(_)))
5572 .collect()
5573 }
5574
5575 fn balance_of(event: &UnindexedAccountEvent) -> &AssetBalance<AssetNameExchange> {
5577 match &event.kind {
5578 AccountEventKind::BalanceSnapshot(balance) => &balance.0,
5579 other => panic!("expected a balance restatement, got: {other:?}"),
5580 }
5581 }
5582
5583 fn trade_of(
5584 event: &UnindexedAccountEvent,
5585 ) -> &Trade<AssetNameExchange, InstrumentNameExchange> {
5586 match &event.kind {
5587 AccountEventKind::Trade(trade) => trade,
5588 other => panic!("expected a trade, got: {other:?}"),
5589 }
5590 }
5591
5592 fn order_of(
5593 event: &UnindexedAccountEvent,
5594 ) -> &Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState> {
5595 match &event.kind {
5596 AccountEventKind::OrderSnapshot(order) => &order.0,
5597 other => panic!("expected an order snapshot, got: {other:?}"),
5598 }
5599 }
5600
5601 fn rested_open(outcome: &OpenOutcome) -> &Open {
5603 match &outcome.response.state {
5604 OrderState::Active(ActiveOrderState::Open(open)) => open,
5605 other => panic!("expected a resting open order, got: {other:?}"),
5606 }
5607 }
5608
5609 fn filled_price(
5611 order: &Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>,
5612 ) -> Decimal {
5613 match &order.state {
5614 OrderState::Inactive(InactiveOrderState::FullyFilled(filled)) => filled
5615 .avg_price
5616 .expect("a filled order reports the price it filled at"),
5617 other => panic!("expected a fully filled order, got: {other:?}"),
5618 }
5619 }
5620
5621 fn venue_resting_one_buy(limit: &str) -> (SimulatedVenue, OpenOutcome) {
5626 let mut venue = make_market_venue("10", "1000000");
5627 advance(&mut venue, time(1));
5628
5629 assert!(
5630 venue
5631 .apply_market(
5632 &instrument_name(),
5633 MarketObservation::new(book("49000", "49100")),
5634 time(1)
5635 )
5636 .is_empty(),
5637 "an empty book fills nothing"
5638 );
5639
5640 let outcome = venue.open_order(limit_request("resting", Side::Buy, "1", limit, gtc()));
5641 assert_eq!(
5642 outcome.events.len(),
5643 1,
5644 "a rested order restates one balance and books no trade"
5645 );
5646
5647 (venue, outcome)
5648 }
5649
5650 fn advance(venue: &mut SimulatedVenue, time_exchange: DateTime<Utc>) {
5657 assert!(
5658 venue.advance_time(time_exchange).is_empty(),
5659 "advancing to {time_exchange} retired an order this test never gave a deadline"
5660 );
5661 }
5662
5663 fn time(seconds: i64) -> DateTime<Utc> {
5664 DateTime::<Utc>::from_timestamp(1_700_000_000 + seconds, 0).expect("representable instant")
5665 }
5666
5667 fn filled_balance(outcome: &OpenOutcome) -> &AssetBalance<AssetNameExchange> {
5672 match sans_debt(&outcome.events).first().map(|event| &event.kind) {
5673 Some(AccountEventKind::BalanceSnapshot(balance)) => &balance.0,
5674 other => panic!("a filled open must emit its balance first, got: {other:?}"),
5675 }
5676 }
5677
5678 fn filled_received(outcome: &OpenOutcome) -> &AssetBalance<AssetNameExchange> {
5680 match sans_debt(&outcome.events).get(1).map(|event| &event.kind) {
5681 Some(AccountEventKind::BalanceSnapshot(balance)) => &balance.0,
5682 other => panic!("a filled spot open must emit what it received second, got: {other:?}"),
5683 }
5684 }
5685
5686 fn filled_trade(outcome: &OpenOutcome) -> &Trade<AssetNameExchange, InstrumentNameExchange> {
5689 let Some(at) = outcome
5690 .events
5691 .iter()
5692 .position(|event| matches!(event.kind, AccountEventKind::Trade(_)))
5693 else {
5694 panic!(
5695 "a filled open must emit its trade, got: {:?}",
5696 outcome.events
5697 );
5698 };
5699 assert!(
5700 at > 0
5701 && outcome.events[..at].iter().all(|event| matches!(
5702 event.kind,
5703 AccountEventKind::BalanceSnapshot(_)
5704 | AccountEventKind::CashFlow(_)
5705 | AccountEventKind::MarginDebt(_)
5706 )),
5707 "a filled open must emit its balances before its trade, got: {:?}",
5708 outcome.events
5709 );
5710 trade_of(&outcome.events[at])
5711 }
5712
5713 #[test]
5720 fn a_filled_open_reports_its_balance_before_its_trade_and_debits_monotonically() {
5721 const FILLS: usize = 4;
5722
5723 let mut venue = make_venue("100", "10000000");
5724 let mut balances = Vec::with_capacity(FILLS);
5725
5726 for _ in 0..FILLS {
5727 let outcome = venue.open_order(buy_request("0.01", market_prices("50000")));
5728
5729 assert_eq!(
5730 outcome.events.len(),
5731 3,
5732 "a spot fill owes the balance it paid with, the one it received, and one trade"
5733 );
5734 let balance = filled_balance(&outcome);
5736 let received = filled_received(&outcome);
5737 let trade = filled_trade(&outcome);
5738
5739 assert_eq!(balance.asset, quote(), "a spot buy debits quote");
5740 assert_eq!(received.asset, base(), "and credits base");
5741 assert_eq!(trade.price, d("50000"));
5742 balances.push(balance.balance.total);
5743 }
5744
5745 let expected = (1..=FILLS)
5747 .map(|nth| d("10000000") - d("500") * Decimal::from(nth))
5748 .collect::<Vec<_>>();
5749 assert_eq!(
5750 balances, expected,
5751 "successive fills must restate a strictly decreasing balance"
5752 );
5753 }
5754
5755 #[test]
5762 fn a_filled_open_is_in_the_ledger_before_its_events_are_delivered() {
5763 let mut venue = make_venue("100", "10000000");
5764 let before = venue.time_exchange();
5765
5766 let outcome = venue.open_order(buy_request("0.01", market_prices("50000")));
5767 assert_eq!(outcome.events.len(), 3, "the fill must owe events");
5768
5769 let trades = venue.trades(before);
5771 assert_eq!(trades.len(), 1, "the venue must acknowledge its own trade");
5772 assert_eq!(trades[0].price, d("50000"));
5773 }
5774
5775 #[test]
5777 fn a_fill_is_stamped_with_the_instant_the_venue_was_last_advanced() {
5778 let mut venue = make_venue("100", "10000000");
5779 let time_exchange = "2025-01-01T00:00:00Z".parse::<DateTime<Utc>>().unwrap();
5780
5781 advance(&mut venue, time_exchange);
5782 assert_eq!(venue.time_exchange(), time_exchange);
5783
5784 let outcome = venue.open_order(buy_request("0.01", market_prices("50000")));
5785 assert_eq!(filled_trade(&outcome).time_exchange, time_exchange);
5786 assert_eq!(filled_balance(&outcome).time_exchange, time_exchange);
5787 }
5788
5789 #[test]
5792 fn the_first_instant_may_precede_the_epoch() {
5793 let mut venue = make_venue("100", "10000000");
5794 let start = "1965-03-01T00:00:00Z".parse::<DateTime<Utc>>().unwrap();
5795
5796 advance(&mut venue, start);
5797
5798 assert_eq!(venue.time_exchange(), start);
5799 }
5800
5801 #[test]
5804 fn a_request_behind_a_later_market_tick_is_stamped_at_the_tick() {
5805 let mut venue = make_cfd_venue_charging("200000", Vec::new());
5806 let tick = time(10);
5807 let fills = venue.apply_market(
5808 &cfd_instrument_name(),
5809 MarketObservation::new(MarketSnapshot {
5810 best_bid: Some(d("5000")),
5811 best_ask: Some(d("5000")),
5812 last_price: Some(d("5000")),
5813 }),
5814 tick,
5815 );
5816 assert!(fills.is_empty());
5817
5818 assert!(venue.advance_time(time(5)).is_empty());
5819 assert_eq!(venue.time_exchange(), tick, "the clock is held at the tick");
5820
5821 let outcome = venue.open_order(cfd_request(Side::Buy, "1", None));
5822 assert_eq!(filled_trade(&outcome).time_exchange, tick);
5823 assert_eq!(filled_balance(&outcome).time_exchange, tick);
5824 }
5825
5826 #[test]
5830 fn a_market_tick_behind_the_clock_is_applied_at_the_clock() {
5831 let (mut venue, _) = venue_resting_one_buy("48000");
5832 let reached = time(10);
5833 advance(&mut venue, reached);
5834
5835 let events = venue.apply_market(
5836 &instrument_name(),
5837 MarketObservation::new(book("47800", "47900")),
5838 time(5),
5839 );
5840
5841 assert_eq!(
5842 events.len(),
5843 4,
5844 "the tick still crosses the resting order: two balances, a trade, an order"
5845 );
5846 assert_eq!(balance_of(&events[0]).time_exchange, reached);
5847 assert_eq!(balance_of(&events[1]).time_exchange, reached);
5848 assert_eq!(trade_of(&events[2]).time_exchange, reached);
5849 assert!(
5850 venue
5851 .account
5852 .balances()
5853 .all(|balance| balance.time_exchange == reached),
5854 "no balance is restamped earlier than the venue has reported"
5855 );
5856 let market = venue.market(&instrument_name()).unwrap();
5857 assert_eq!(market.time_exchange, reached);
5858 assert_eq!(market.fresh_at, Some(reached));
5859 assert_eq!(venue.time_exchange(), reached);
5860 }
5861
5862 #[test]
5869 fn advancing_the_clock_does_not_restamp_a_resting_order() {
5870 let arrived = "2025-01-01T00:00:00Z".parse::<DateTime<Utc>>().unwrap();
5871 let much_later = "2025-06-01T00:00:00Z".parse::<DateTime<Utc>>().unwrap();
5872
5873 let mut venue = make_venue("100", "10000000");
5874 venue.account = AccountState::new(
5875 venue
5876 .account
5877 .balances()
5878 .cloned()
5879 .map(|balance| (balance.asset.clone(), balance))
5880 .collect(),
5881 {
5882 let mut book = OpenOrders::default();
5883 book.insert(
5884 Order {
5885 key: OrderKey {
5886 exchange: EXCHANGE,
5887 instrument: instrument_name(),
5888 strategy: StrategyId::new("test"),
5889 cid: ClientOrderId::new("resting"),
5890 },
5891 side: Side::Buy,
5892 price: Some(d("50000")),
5893 quantity: d("1"),
5894 kind: OrderKind::Limit,
5895 time_in_force: TimeInForce::GoodUntilCancelled { post_only: false },
5896 state: Open {
5897 id: VenueOrderId::Assigned(OrderId::new("resting")),
5898 time_exchange: arrived,
5899 filled_quantity: Decimal::ZERO,
5900 },
5901 },
5902 None,
5903 )
5904 .unwrap();
5905 book
5906 },
5907 Default::default(),
5908 Vec::new(),
5909 );
5910
5911 advance(&mut venue, much_later);
5912
5913 let resting = venue.orders_open(&[]);
5914 assert_eq!(resting.len(), 1, "the seeded order must still be open");
5915 assert_eq!(
5916 resting[0].state.time_exchange, arrived,
5917 "an order does not become a different order because time passed"
5918 );
5919
5920 let usdt = venue.balances(&[quote()]);
5922 assert_eq!(usdt[0].time_exchange, much_later);
5923 }
5924
5925 #[test]
5931 fn a_cancel_for_an_unknown_order_is_rejected_as_unknown() {
5932 let mut venue = make_venue("100", "10000000");
5933 let key = buy_request("1", market_prices("50000")).key;
5934
5935 let outcome = venue.cancel_order(OrderEvent {
5936 key: key.clone(),
5937 state: RequestCancel { id: None },
5938 });
5939
5940 assert!(
5941 outcome.events.is_empty(),
5942 "a rejected cancel moves nothing, so it owes no events"
5943 );
5944 assert_eq!(
5945 outcome.response.key, key,
5946 "the response must name its request"
5947 );
5948 match outcome.response.state {
5949 Err(OrderError::Rejected(ApiError::OrderRejected(ref reason))) => {
5950 assert!(
5951 reason.contains("test-cid"),
5952 "the rejection must name the order it could not find, got: {reason}"
5953 );
5954 }
5955 ref other => panic!("expected a rejected cancel, got: {other:?}"),
5956 }
5957
5958 assert!(
5960 venue.account_snapshot().instruments.is_empty(),
5961 "a rejected cancel must not enter the ledger"
5962 );
5963 }
5964
5965 const CFD_CONTRACT_SIZE: &str = "25";
5967
5968 fn cfd_instrument_name() -> InstrumentNameExchange {
5969 InstrumentNameExchange::new("spx500_usd")
5970 }
5971
5972 fn make_cfd_venue(usd: &str, fee_model: FeeModelConfig) -> SimulatedVenue {
5976 make_cfd_venue_with_balances(
5977 vec![funded(AssetNameExchange::new("usd"), d(usd))],
5978 fee_model,
5979 )
5980 }
5981
5982 fn make_cfd_venue_with_balances(
5983 balances: Vec<AssetBalance<AssetNameExchange>>,
5984 fee_model: FeeModelConfig,
5985 ) -> SimulatedVenue {
5986 SimulatedVenue::new(
5987 &config_from_balances(balances, fee_model),
5988 instruments_of(cfd_instrument()),
5989 )
5990 }
5991
5992 fn cfd_instrument() -> Instrument<ExchangeId, AssetNameExchange> {
5993 Instrument {
5994 exchange: EXCHANGE,
5995 name_internal: InstrumentNameInternal::new("spx500_usd"),
5996 name_exchange: cfd_instrument_name(),
5997 underlying: Underlying {
5998 base: AssetNameExchange::new("spx500"),
5999 quote: AssetNameExchange::new("usd"),
6000 },
6001 quote: InstrumentQuoteAsset::UnderlyingQuote,
6002 kind: InstrumentKind::Cfd(CfdContract {
6003 contract_size: d(CFD_CONTRACT_SIZE),
6004 settlement_asset: AssetNameExchange::new("gbp"),
6005 }),
6006 spec: None,
6007 data_venue: None,
6008 }
6009 }
6010
6011 fn cfd_request(
6012 side: Side,
6013 quantity: &str,
6014 market: Option<MarketSnapshot>,
6015 ) -> OrderRequestOpen<ExchangeId, InstrumentNameExchange> {
6016 request(cfd_instrument_name(), side, quantity, market)
6017 }
6018
6019 #[test]
6022 fn cfd_buy_debits_the_contract_size_scaled_notional() {
6023 let mut venue = make_cfd_venue("200000", FeeModelConfig::default());
6024
6025 let outcome = venue.open_order(cfd_request(Side::Buy, "1", market_prices("5000")));
6026
6027 assert!(
6028 outcome.response.state.is_accepted(),
6029 "cfd buy should fill: {:?}",
6030 outcome.response.state
6031 );
6032 assert_eq!(outcome.events.len(), 2);
6033
6034 let usd = venue
6036 .account
6037 .balance_mut(&AssetNameExchange::new("usd"))
6038 .unwrap();
6039 assert_eq!(usd.balance.free, d("200000") - d("125000"));
6040 assert_eq!(usd.balance.total, usd.balance.free);
6041 }
6042
6043 #[test]
6047 fn cfd_sell_needs_no_base_inventory_and_debits_quote() {
6048 let mut venue = make_cfd_venue("200000", FeeModelConfig::default());
6049
6050 let outcome = venue.open_order(cfd_request(Side::Sell, "1", market_prices("5000")));
6051
6052 assert!(
6053 outcome.response.state.is_accepted(),
6054 "cfd short should fill without base inventory: {:?}",
6055 outcome.response.state
6056 );
6057 let balance = filled_balance(&outcome);
6058 assert_eq!(
6059 balance.asset,
6060 AssetNameExchange::new("usd"),
6061 "a cash-settled short debits quote, not base"
6062 );
6063 assert_eq!(balance.balance.free, d("75000"));
6064 }
6065
6066 #[test]
6069 fn cfd_buy_is_rejected_when_only_the_unscaled_notional_is_funded() {
6070 let mut venue = make_cfd_venue("10000", FeeModelConfig::default());
6071
6072 let outcome = venue.open_order(cfd_request(Side::Buy, "1", market_prices("5000")));
6073
6074 assert!(outcome.events.is_empty());
6075 match outcome.response.state {
6076 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
6077 ApiError::BalanceInsufficient(Some(ref asset), _),
6078 ))) => {
6079 assert_eq!(*asset, AssetNameExchange::new("usd"));
6080 }
6081 other => panic!("expected BalanceInsufficient, got: {other:?}"),
6082 }
6083 }
6084
6085 #[test]
6092 fn cfd_fee_is_quote_denominated_on_both_sides() {
6093 for side in [Side::Buy, Side::Sell] {
6094 let mut venue = make_cfd_venue(
6095 "200000",
6096 FeeModelConfig::Percentage(PercentageFeeModel::new(d("0.001"))),
6098 );
6099
6100 let outcome = venue.open_order(cfd_request(side, "1", market_prices("5000")));
6101
6102 assert!(
6103 outcome.response.state.is_accepted(),
6104 "{side:?} should fill: {:?}",
6105 outcome.response.state
6106 );
6107 assert_eq!(
6108 filled_trade(&outcome).fees.asset,
6109 AssetNameExchange::new("usd"),
6110 "{side:?} fee asset"
6111 );
6112
6113 assert_eq!(
6115 filled_trade(&outcome).fees.fees,
6116 d("125"),
6117 "{side:?} fee must be 0.1% of the contract_size-scaled notional"
6118 );
6119 assert_eq!(
6120 filled_balance(&outcome).balance.free,
6121 d("200000") - d("125000") - d("125"),
6122 "{side:?} debit must be notional + fee"
6123 );
6124 }
6125 }
6126
6127 #[test]
6132 #[should_panic(expected = "SimulatedVenue has Balance for all configured Instrument assets")]
6133 fn cfd_panics_when_the_quote_asset_is_unfunded() {
6134 let mut venue = make_cfd_venue_with_balances(
6137 vec![funded(AssetNameExchange::new("gbp"), d("200000"))],
6138 FeeModelConfig::default(),
6139 );
6140
6141 let _ = venue.open_order(cfd_request(Side::Buy, "1", market_prices("5000")));
6142 }
6143
6144 fn usd() -> AssetNameExchange {
6147 AssetNameExchange::new("usd")
6148 }
6149
6150 fn free_usd(venue: &SimulatedVenue) -> Decimal {
6151 venue.balances(&[usd()]).remove(0).balance.free
6152 }
6153
6154 fn total_usd(venue: &SimulatedVenue) -> Decimal {
6155 venue.balances(&[usd()]).remove(0).balance.total
6156 }
6157
6158 fn cfd_fill(
6160 venue: &mut SimulatedVenue,
6161 side: Side,
6162 quantity: &str,
6163 price: &str,
6164 ) -> OpenOutcome {
6165 let outcome = venue.open_order(cfd_request(side, quantity, market_prices(price)));
6166 assert!(
6167 outcome.response.state.is_accepted(),
6168 "{side:?} {quantity} @ {price} should fill: {:?}",
6169 outcome.response.state
6170 );
6171 outcome
6172 }
6173
6174 fn cfd_position(venue: &SimulatedVenue) -> Option<(Decimal, Option<Decimal>)> {
6176 let snapshot = venue.account_snapshot();
6177 let Some(instrument) = snapshot
6178 .instruments
6179 .iter()
6180 .find(|instrument| instrument.instrument == cfd_instrument_name())
6181 else {
6182 panic!("a CFD instrument is always in the snapshot, flat or not");
6183 };
6184 match &instrument.position {
6185 PositionReport::Open(position) => Some((position.quantity, position.entry_price)),
6186 PositionReport::Flat => None,
6187 PositionReport::Unreported => panic!("a CFD position is reported, not Unreported"),
6188 }
6189 }
6190
6191 #[test]
6194 fn a_cfd_round_trip_returns_the_margin_and_settles_the_pnl() {
6195 for (side, open, close, end) in [
6197 (Side::Buy, "5000", "5100", "152500"), (Side::Sell, "5000", "4900", "152500"), (Side::Sell, "5000", "5200", "145000"), ] {
6201 let mut venue = make_cfd_venue("150000", FeeModelConfig::default());
6202
6203 cfd_fill(&mut venue, side, "1", open);
6204 assert_eq!(
6205 free_usd(&venue),
6206 d("25000"),
6207 "{side:?}: opening posts 125,000"
6208 );
6209
6210 let closing = match side {
6211 Side::Buy => Side::Sell,
6212 Side::Sell => Side::Buy,
6213 };
6214 let outcome = cfd_fill(&mut venue, closing, "1", close);
6215
6216 assert_eq!(
6217 filled_balance(&outcome).balance.free,
6218 d(end),
6219 "{side:?} @ {close}"
6220 );
6221 assert_eq!(total_usd(&venue), d(end), "{side:?}: nothing is left held");
6222 assert_eq!(
6223 cfd_position(&venue),
6224 None,
6225 "{side:?}: the round trip is flat"
6226 );
6227 }
6228 }
6229
6230 #[test]
6233 fn a_cfd_flip_closes_the_position_and_opens_the_rest_in_one_fill() {
6234 let mut venue = make_cfd_venue("300000", FeeModelConfig::default());
6235 cfd_fill(&mut venue, Side::Buy, "1", "5000");
6236 assert_eq!(free_usd(&venue), d("175000"));
6237
6238 cfd_fill(&mut venue, Side::Sell, "3", "5100");
6241
6242 assert_eq!(free_usd(&venue), d("175000") + d("127500") - d("255000"));
6243 assert_eq!(cfd_position(&venue), Some((d("-2"), Some(d("5100")))));
6244 }
6245
6246 #[test]
6247 fn a_partial_cfd_reduce_keeps_the_entry_and_pays_back_its_share() {
6248 let mut venue = make_cfd_venue("300000", FeeModelConfig::default());
6249 cfd_fill(&mut venue, Side::Buy, "2", "5000");
6250 assert_eq!(free_usd(&venue), d("50000"));
6251
6252 cfd_fill(&mut venue, Side::Sell, "1", "5200");
6253
6254 assert_eq!(free_usd(&venue), d("180000"));
6256 assert_eq!(
6257 cfd_position(&venue),
6258 Some((d("2") - d("1"), Some(d("5000"))))
6259 );
6260 }
6261
6262 #[test]
6263 fn increasing_a_cfd_position_averages_its_entry() {
6264 let mut venue = make_cfd_venue("300000", FeeModelConfig::default());
6265 cfd_fill(&mut venue, Side::Buy, "1", "5000");
6266 cfd_fill(&mut venue, Side::Buy, "1", "5200");
6267
6268 assert_eq!(free_usd(&venue), d("300000") - d("125000") - d("130000"));
6269 assert_eq!(cfd_position(&venue), Some((d("2"), Some(d("5100")))));
6270 }
6271
6272 #[test]
6275 fn a_cfd_close_is_charged_its_fee_on_top_of_the_proceeds() {
6276 let mut venue = make_cfd_venue(
6277 "200000",
6278 FeeModelConfig::Percentage(PercentageFeeModel::new(d("0.001"))),
6279 );
6280 cfd_fill(&mut venue, Side::Buy, "1", "5000"); let outcome = cfd_fill(&mut venue, Side::Sell, "1", "5100"); assert_eq!(filled_trade(&outcome).fees.fees, d("127.5"));
6284 assert_eq!(
6285 free_usd(&venue),
6286 d("200000") + d("2500") - d("125") - d("127.5")
6287 );
6288 }
6289
6290 #[test]
6293 fn a_cfd_short_that_loses_more_than_its_margin_pays_the_shortfall() {
6294 let mut venue = make_cfd_venue("200000", FeeModelConfig::default());
6295 cfd_fill(&mut venue, Side::Sell, "1", "1000"); cfd_fill(&mut venue, Side::Buy, "1", "2500"); assert_eq!(free_usd(&venue), d("200000") - d("37500"));
6299 assert_eq!(cfd_position(&venue), None);
6300 }
6301
6302 #[test]
6303 fn a_spot_instrument_still_reports_no_position() {
6304 let mut venue = make_venue("10", "1000000");
6305 let _ = venue.open_order(request(
6306 instrument_name(),
6307 Side::Buy,
6308 "1",
6309 market_prices("50000"),
6310 ));
6311
6312 let snapshot = venue.account_snapshot();
6313 assert!(
6314 snapshot
6315 .instruments
6316 .iter()
6317 .all(|instrument| instrument.position == PositionReport::Unreported),
6318 "a spot holding is a balance, not a position"
6319 );
6320 }
6321
6322 #[test]
6323 fn a_cfd_position_in_the_initial_state_is_where_the_venue_starts() {
6324 let config = MockExecutionConfig::new(
6325 EXCHANGE,
6326 UnindexedAccountSnapshot {
6327 exchange: EXCHANGE,
6328 balances: vec![funded(usd(), d("10000"))],
6329 instruments: vec![InstrumentAccountSnapshot {
6330 instrument: cfd_instrument_name(),
6331 orders: vec![],
6332 orders_complete: true,
6333 position: PositionReport::from_position(Position::new(
6334 d("-1"),
6335 Some(d("5000")),
6336 None,
6337 None,
6338 None,
6339 None,
6340 None,
6341 time(0),
6342 )),
6343 isolated: None,
6344 }],
6345 },
6346 0,
6347 FeeModelConfig::default(),
6348 SimFillConfig::default(),
6349 SpotFeeAsset::default(),
6350 crate::slippage::SlippageConfig::default(),
6351 crate::fill::MarketOrderTiming::default(),
6352 crate::fill::StaleMarketPolicy::default(),
6353 None,
6354 None,
6355 );
6356 let mut venue = SimulatedVenue::new(&config, instruments_of(cfd_instrument()));
6357 assert_eq!(cfd_position(&venue), Some((d("-1"), Some(d("5000")))));
6358
6359 cfd_fill(&mut venue, Side::Buy, "1", "4800");
6361 assert_eq!(free_usd(&venue), d("10000") + d("130000"));
6362 }
6363
6364 #[test]
6365 #[should_panic(expected = "no entry price")]
6366 fn a_cfd_position_in_the_initial_state_without_an_entry_price_panics() {
6367 let config = MockExecutionConfig::new(
6368 EXCHANGE,
6369 UnindexedAccountSnapshot {
6370 exchange: EXCHANGE,
6371 balances: vec![funded(usd(), d("10000"))],
6372 instruments: vec![InstrumentAccountSnapshot {
6373 instrument: cfd_instrument_name(),
6374 orders: vec![],
6375 orders_complete: true,
6376 position: PositionReport::from_position(Position::new(
6377 d("-1"),
6378 None,
6379 None,
6380 None,
6381 None,
6382 None,
6383 None,
6384 time(0),
6385 )),
6386 isolated: None,
6387 }],
6388 },
6389 0,
6390 FeeModelConfig::default(),
6391 SimFillConfig::default(),
6392 SpotFeeAsset::default(),
6393 crate::slippage::SlippageConfig::default(),
6394 crate::fill::MarketOrderTiming::default(),
6395 crate::fill::StaleMarketPolicy::default(),
6396 None,
6397 None,
6398 );
6399 let _ = SimulatedVenue::new(&config, instruments_of(cfd_instrument()));
6400 }
6401
6402 fn make_cfd_market_venue(usd: &str) -> SimulatedVenue {
6405 let mut venue = SimulatedVenue::new_market_driven(
6406 &config_from_balances(vec![funded(self::usd(), d(usd))], FeeModelConfig::default()),
6407 instruments_of(cfd_instrument()),
6408 );
6409 advance(&mut venue, time(1));
6410 venue
6411 }
6412
6413 fn cfd_limit(
6414 cid: &str,
6415 side: Side,
6416 quantity: &str,
6417 price: &str,
6418 ) -> OrderRequestOpen<ExchangeId, InstrumentNameExchange> {
6419 let mut request = limit_request(cid, side, quantity, price, gtc());
6420 request.key.instrument = cfd_instrument_name();
6421 request
6422 }
6423
6424 fn cfd_market(
6426 venue: &mut SimulatedVenue,
6427 bid: &str,
6428 ask: &str,
6429 last: &str,
6430 ) -> Vec<UnindexedAccountEvent> {
6431 let time_exchange = venue.time_exchange();
6432 venue.apply_market(
6433 &cfd_instrument_name(),
6434 MarketObservation::new(MarketSnapshot {
6435 best_bid: Some(d(bid)),
6436 best_ask: Some(d(ask)),
6437 last_price: Some(d(last)),
6438 }),
6439 time_exchange,
6440 )
6441 }
6442
6443 fn cfd_market_fill(venue: &mut SimulatedVenue, side: Side, quantity: &str) {
6444 let outcome = venue.open_order(cfd_request(side, quantity, None));
6445 assert!(
6446 outcome.response.state.is_accepted(),
6447 "{side:?} {quantity} should fill: {:?}",
6448 outcome.response.state
6449 );
6450 }
6451
6452 #[test]
6455 fn a_resting_order_that_only_reduces_a_cfd_position_holds_nothing() {
6456 let mut venue = make_cfd_market_venue("125000");
6457 assert!(cfd_market(&mut venue, "4990", "5000", "5000").is_empty());
6458 cfd_market_fill(&mut venue, Side::Buy, "1");
6459 assert_eq!(
6460 free_usd(&venue),
6461 Decimal::ZERO,
6462 "the long took the whole balance"
6463 );
6464
6465 let outcome = venue.open_order(cfd_limit("close", Side::Sell, "1", "5200"));
6466 assert!(rested_open(&outcome).filled_quantity.is_zero());
6467 assert_eq!(
6468 total_usd(&venue) - free_usd(&venue),
6469 Decimal::ZERO,
6470 "nothing is held"
6471 );
6472
6473 let events = cfd_market(&mut venue, "5200", "5210", "5200");
6474 assert_eq!(balance_of(&events[0]).balance.free, d("130000"));
6475 assert_eq!(filled_price(order_of(&events[2])), d("5200"));
6476 assert_eq!(cfd_position(&venue), None);
6477 }
6478
6479 #[test]
6480 fn a_resting_order_that_opens_a_cfd_position_holds_its_margin_until_cancelled() {
6481 let mut venue = make_cfd_market_venue("200000");
6482 assert!(cfd_market(&mut venue, "4990", "5000", "5000").is_empty());
6483
6484 let _ = venue.open_order(cfd_limit("open", Side::Buy, "1", "4800"));
6485 assert_eq!(free_usd(&venue), d("200000") - d("120000"));
6486 assert_eq!(total_usd(&venue), d("200000"));
6487
6488 let cancelled = venue.cancel_order(OrderRequestCancel {
6489 key: cfd_limit("open", Side::Buy, "1", "4800").key,
6490 state: RequestCancel { id: None },
6491 });
6492 assert!(cancelled.response.state.is_ok());
6493 assert_eq!(free_usd(&venue), d("200000"));
6494 }
6495
6496 #[test]
6500 fn a_resting_cfd_order_is_costed_against_the_position_when_it_fills() {
6501 let mut venue = make_cfd_market_venue("300000");
6502 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
6503 cfd_market_fill(&mut venue, Side::Buy, "1"); let _ = venue.open_order(cfd_limit("close", Side::Sell, "1", "5200"));
6506 assert_eq!(
6507 free_usd(&venue),
6508 d("175000"),
6509 "a reducing order holds nothing"
6510 );
6511
6512 cfd_market_fill(&mut venue, Side::Sell, "1"); assert_eq!(free_usd(&venue), d("300000"));
6514 assert_eq!(cfd_position(&venue), None);
6515
6516 let events = cfd_market(&mut venue, "5200", "5210", "5200");
6517 assert_eq!(
6518 balance_of(&events[0]).balance.free,
6519 d("300000") - d("130000"),
6520 "the resting sell now opens a short of 1 at 5,200"
6521 );
6522 assert_eq!(cfd_position(&venue), Some((d("-1"), Some(d("5200")))));
6523 }
6524
6525 #[test]
6528 fn a_resting_cfd_order_the_account_can_no_longer_afford_is_cancelled() {
6529 let mut venue = make_cfd_market_venue("126000");
6530 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
6531 cfd_market_fill(&mut venue, Side::Buy, "1");
6532 let _ = venue.open_order(cfd_limit("close", Side::Sell, "1", "5200"));
6533 cfd_market_fill(&mut venue, Side::Sell, "1");
6534 assert_eq!(free_usd(&venue), d("126000"));
6535
6536 let events = cfd_market(&mut venue, "5200", "5210", "5200");
6537
6538 let Some((last, released)) = events.split_last() else {
6541 panic!("an unaffordable fill reports its cancelled order");
6542 };
6543 assert!(
6544 matches!(
6545 order_of(last).state,
6546 OrderState::Inactive(InactiveOrderState::Cancelled(_))
6547 ),
6548 "{:?}",
6549 order_of(last).state
6550 );
6551 assert!(
6552 released
6553 .iter()
6554 .all(|event| balance_of(event).balance.free == d("126000")),
6555 "no balance moved"
6556 );
6557 assert_eq!(free_usd(&venue), d("126000"), "nothing moved");
6558 assert_eq!(cfd_position(&venue), None);
6559 assert!(venue.account.is_cancelled(&ClientOrderId::new("close")));
6560 }
6561
6562 #[test]
6567 fn a_resting_cfd_order_cancelled_as_unaffordable_releases_its_hold() {
6568 let mut venue = make_cfd_market_venue("260000");
6569 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
6570 cfd_market_fill(&mut venue, Side::Buy, "1"); let _ = venue.open_order(cfd_limit("flip", Side::Sell, "3", "5200"));
6573 assert_eq!(
6574 free_usd(&venue),
6575 d("260000") - d("125000") - (d("260000") - d("130000")),
6576 "holds the short's 260,000 net of the long's 130,000"
6577 );
6578
6579 cfd_market_fill(&mut venue, Side::Sell, "1"); assert_eq!(free_usd(&venue), d("130000"));
6581 assert_eq!(total_usd(&venue), d("260000"));
6582
6583 let events = cfd_market(&mut venue, "5200", "5210", "5200");
6584
6585 let Some((last, released)) = events.split_last() else {
6586 panic!("an unaffordable fill reports its cancelled order");
6587 };
6588 assert!(
6589 matches!(
6590 order_of(last).state,
6591 OrderState::Inactive(InactiveOrderState::Cancelled(_))
6592 ),
6593 "{:?}",
6594 order_of(last).state
6595 );
6596 let [released] = released else {
6597 panic!("exactly the released hold restates: {released:?}");
6598 };
6599 assert_eq!(
6600 balance_of(released).balance.free,
6601 d("260000"),
6602 "the hold is given back"
6603 );
6604 assert_eq!(free_usd(&venue), d("260000"));
6605 assert_eq!(total_usd(&venue), d("260000"), "nothing settled");
6606 assert_eq!(cfd_position(&venue), None);
6607 assert!(venue.account.is_cancelled(&ClientOrderId::new("flip")));
6608 }
6609
6610 #[test]
6611 fn sell_order_decrements_base_and_credits_quote_proceeds() {
6612 let mut venue = make_venue("1.0", "10000");
6613 let initial_usdt = d("10000");
6614
6615 let outcome = venue.open_order(sell_request("0.5", market_prices("50000")));
6616
6617 assert!(
6618 outcome.response.state.is_accepted(),
6619 "sell should succeed: {:?}",
6620 outcome.response.state
6621 );
6622 assert_eq!(outcome.events.len(), 3, "a successful sell must notify");
6623 assert_eq!(
6624 filled_balance(&outcome).asset,
6625 base(),
6626 "a sell pays with base"
6627 );
6628 assert_eq!(
6629 filled_received(&outcome).asset,
6630 quote(),
6631 "and receives quote"
6632 );
6633
6634 let btc = venue.account.balance_mut(&base()).unwrap();
6636 assert_eq!(
6637 btc.balance.free,
6638 d("0.5"),
6639 "base balance should decrease by quantity sold"
6640 );
6641
6642 let usdt = venue.account.balance_mut("e()).unwrap();
6644 assert_eq!(
6645 usdt.balance.free,
6646 initial_usdt + d("25000"),
6647 "quote balance should rise by the proceeds"
6648 );
6649 }
6650
6651 #[test]
6657 fn unsupported_instrument_kinds_are_rejected_rather_than_filled_as_spot() {
6658 use rustrade_instrument::instrument::kind::{
6659 future::FutureContract,
6660 option::{OptionContract, OptionExercise, OptionKind},
6661 perpetual::PerpetualContract,
6662 };
6663
6664 let settlement = AssetNameExchange::new("USDT");
6665 let expiry = Utc::now();
6666
6667 let unsupported = [
6668 InstrumentKind::Perpetual(PerpetualContract {
6669 contract_size: d("10"),
6670 settlement_asset: settlement.clone(),
6671 }),
6672 InstrumentKind::Future(FutureContract {
6673 contract_size: d("10"),
6674 settlement_asset: settlement.clone(),
6675 expiry,
6676 }),
6677 InstrumentKind::Option(OptionContract {
6678 contract_size: d("10"),
6679 settlement_asset: settlement.clone(),
6680 kind: OptionKind::Call,
6681 exercise: OptionExercise::European,
6682 expiry,
6683 strike: d("50000"),
6684 }),
6685 ];
6686
6687 for kind in unsupported {
6688 let mut instruments = spot_instruments();
6690 instruments.get_mut(&instrument_name()).unwrap().kind = kind.clone();
6691 let config = spot_config("10", "10000000", FeeModelConfig::default());
6692 let mut venue = SimulatedVenue::new(&config, instruments);
6693
6694 let outcome = venue.open_order(buy_request("1.0", market_prices("50000")));
6695
6696 assert!(
6697 outcome.events.is_empty(),
6698 "{kind:?} must produce no account events"
6699 );
6700 assert!(
6701 matches!(
6702 outcome.response.state,
6703 OrderState::Inactive(InactiveOrderState::OpenFailed(
6704 UnindexedOrderError::Connectivity(_) | UnindexedOrderError::Rejected(_)
6705 ))
6706 ),
6707 "{kind:?} must be rejected, got {:?}",
6708 outcome.response.state
6709 );
6710
6711 let usdt = venue.account.balance_mut("e()).unwrap();
6714 assert_eq!(
6715 usdt.balance.free,
6716 d("10000000"),
6717 "{kind:?} must not move the quote balance"
6718 );
6719 }
6720 }
6721
6722 #[test]
6723 fn sell_order_insufficient_balance_names_base_asset() {
6724 let mut venue = make_venue("0.1", "10000");
6728
6729 let outcome = venue.open_order(sell_request("1.0", market_prices("50000")));
6731
6732 assert!(
6733 outcome.events.is_empty(),
6734 "failed order must produce no account events"
6735 );
6736 match outcome.response.state {
6737 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
6738 ApiError::BalanceInsufficient(Some(ref asset), _),
6739 ))) => {
6740 assert_eq!(
6741 *asset,
6742 base(),
6743 "BalanceInsufficient must name the base asset (BTC), not the quote (USDT)"
6744 );
6745 }
6746 other => panic!("expected BalanceInsufficient, got: {other:?}"),
6747 }
6748 }
6749
6750 #[test]
6751 fn bid_ask_fill_model_fills_at_ask_price_and_deducts_correct_balance() {
6752 let mut venue = make_venue("0", "10000"); venue.fill_model = Arc::new(SimFillConfig::BidAsk(BidAskFillModel));
6754
6755 let market = Some(MarketSnapshot {
6756 best_bid: Some(d("99.5")),
6757 best_ask: Some(d("100.5")),
6758 last_price: Some(d("100.0")),
6759 });
6760
6761 let outcome = venue.open_order(buy_request("1", market));
6764
6765 assert!(
6766 outcome.response.state.is_accepted(),
6767 "buy should succeed: {:?}",
6768 outcome.response.state
6769 );
6770
6771 assert_eq!(
6773 filled_trade(&outcome).price,
6774 d("100.5"),
6775 "fill price must be best_ask"
6776 );
6777
6778 let usdt = venue.account.balance_mut("e()).unwrap();
6780 assert_eq!(
6781 usdt.balance.free,
6782 d("9899.5"),
6783 "quote balance must decrease by fill_price * qty"
6784 );
6785 }
6786
6787 fn slipped_config(slippage: SlippageConfig) -> MockExecutionConfig {
6791 MockExecutionConfig {
6792 slippage,
6793 ..spot_config("10", "1000000", FeeModelConfig::default())
6794 }
6795 }
6796
6797 fn last(price: &str) -> Option<MarketSnapshot> {
6798 Some(MarketSnapshot {
6799 last_price: Some(d(price)),
6800 ..MarketSnapshot::default()
6801 })
6802 }
6803
6804 fn uniform(seed: u64) -> SlippageConfig {
6805 SlippageConfig::uniform(d("0.01"), d("0.025"), seed).unwrap()
6806 }
6807
6808 #[test]
6809 fn a_fixed_slippage_moves_a_market_fill_against_the_order_and_settles_that_price() {
6810 let config = slipped_config(SlippageConfig::fixed(d("0.01")).unwrap());
6811 let mut venue = SimulatedVenue::new(&config, spot_instruments());
6812
6813 let bought = venue.open_order(buy_request("2", last("100")));
6814 assert_eq!(filled_trade(&bought).price, d("101"), "a buy pays 1% above");
6815 assert_eq!(
6816 venue.account.balance_mut("e()).unwrap().balance.free,
6817 d("999798"),
6818 "the quote debited is the slipped price times the quantity"
6819 );
6820
6821 let sold = venue.open_order(sell_request("2", last("100")));
6822 assert_eq!(
6823 filled_trade(&sold).price,
6824 d("99"),
6825 "a sell receives 1% below"
6826 );
6827 assert_eq!(
6828 venue.account.balance_mut("e()).unwrap().balance.free,
6829 d("999996"),
6830 "the round trip cost the 1% on each side"
6831 );
6832 }
6833
6834 #[test]
6836 fn a_slipped_marketable_limit_never_fills_past_its_limit() {
6837 let fill = |rate: &str, side: Side, limit: &str| {
6838 let config = slipped_config(SlippageConfig::fixed(d(rate)).unwrap());
6839 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
6840 advance(&mut venue, time(1));
6841 let fed = venue.apply_market(
6842 &instrument_name(),
6843 MarketObservation::new(book("49000", "49100")),
6844 time(1),
6845 );
6846 assert!(fed.is_empty(), "an empty book fills nothing");
6847 let outcome = venue.open_order(limit_request("cid", side, "1", limit, gtc()));
6848 filled_trade(&outcome).price
6849 };
6850
6851 assert_eq!(
6852 fill("0.01", Side::Buy, "49200"),
6853 d("49200"),
6854 "49591 slipped, clamped to the limit"
6855 );
6856 assert_eq!(
6857 fill("0.01", Side::Sell, "48800"),
6858 d("48800"),
6859 "48510 slipped, clamped to the limit"
6860 );
6861 assert_eq!(
6862 fill("0.001", Side::Buy, "49200"),
6863 d("49149.1"),
6864 "inside the limit, the slipped price stands"
6865 );
6866 }
6867
6868 #[test]
6870 fn a_resting_fill_is_not_slipped_and_takes_no_draw() {
6871 let config = MockExecutionConfig {
6872 slippage: uniform(5),
6873 ..spot_config("10", "1000000", maker_taker_fee())
6874 };
6875 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
6876 advance(&mut venue, time(1));
6877 let fed = venue.apply_market(
6878 &instrument_name(),
6879 MarketObservation::new(book("49000", "49100")),
6880 time(1),
6881 );
6882 assert!(fed.is_empty(), "an empty book fills nothing");
6883 let rested = venue.open_order(limit_request("resting", Side::Buy, "1", "48000", gtc()));
6884 rested_open(&rested);
6885 assert_eq!(
6886 venue.slippage,
6887 Slippage::new(config.slippage),
6888 "resting drew nothing"
6889 );
6890
6891 advance(&mut venue, time(2));
6892 let events = venue.apply_market(
6893 &instrument_name(),
6894 MarketObservation::new(book("47800", "47900")),
6895 time(2),
6896 );
6897 assert_eq!(trade_of(&events[2]).price, d("48000"));
6898 assert_eq!(
6899 venue.slippage,
6900 Slippage::new(config.slippage),
6901 "the maker's fill drew nothing"
6902 );
6903 }
6904
6905 #[test]
6908 fn an_order_the_book_cannot_fill_takes_no_draw() {
6909 let config = slipped_config(uniform(3));
6910 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
6911 advance(&mut venue, time(1));
6912 let fed = venue.apply_market(
6913 &instrument_name(),
6914 MarketObservation::new(book("47800", "47900"))
6915 .with_depth(MarketDepth::new(Some(d("1")), Some(Decimal::ZERO))),
6916 time(1),
6917 );
6918 assert!(fed.is_empty(), "an empty book fills nothing");
6919
6920 let rested = venue.open_order(limit_request("rests", Side::Buy, "1", "48000", gtc()));
6921 assert_eq!(rested_open(&rested).filled_quantity, Decimal::ZERO);
6922 let cancelled = venue.open_order(limit_request(
6923 "cancels",
6924 Side::Buy,
6925 "1",
6926 "48000",
6927 TimeInForce::ImmediateOrCancel,
6928 ));
6929 assert!(
6930 matches!(
6931 cancelled.response.state,
6932 OrderState::Inactive(InactiveOrderState::Cancelled(_))
6933 ),
6934 "{:?}",
6935 cancelled.response.state
6936 );
6937
6938 assert_eq!(venue.slippage, Slippage::new(config.slippage));
6939 }
6940
6941 fn slipped_venue_offering(
6943 usdt: &str,
6944 slippage: SlippageConfig,
6945 depth: MarketDepth,
6946 ) -> SimulatedVenue {
6947 let config = MockExecutionConfig {
6948 slippage,
6949 ..spot_config("10", usdt, FeeModelConfig::default())
6950 };
6951 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
6952 advance(&mut venue, time(1));
6953 let fed = venue.apply_market(
6954 &instrument_name(),
6955 MarketObservation::new(book("47800", "47900")).with_depth(depth),
6956 time(1),
6957 );
6958 assert!(fed.is_empty(), "an empty book fills nothing");
6959 venue
6960 }
6961
6962 #[test]
6965 fn a_part_filled_order_is_slipped_once_and_rests_the_rest_at_its_limit() {
6966 let mut venue = slipped_venue_offering("1000000", uniform(8), sized("1"));
6967 let mut expected = Slippage::new(uniform(8));
6968 let price = expected.apply(Side::Buy, d("47900"));
6969
6970 let outcome = venue.open_order(limit_request("part", Side::Buy, "3", "50000", gtc()));
6971 let trade = filled_trade(&outcome);
6972 assert_eq!((trade.quantity, trade.price), (d("1"), price));
6973 assert_eq!(rested_open(&outcome).filled_quantity, d("1"));
6974 assert_eq!(venue.slippage, expected, "one fill, one draw");
6975 assert_eq!(
6976 venue
6977 .account
6978 .orders()
6979 .resting(&instrument_name(), Side::Buy)
6980 .map(|order| order.price)
6981 .collect::<Vec<_>>(),
6982 vec![Some(d("50000"))],
6983 "the remainder rests at its limit, unslipped"
6984 );
6985 }
6986
6987 #[test]
6989 fn a_killed_order_takes_no_draw() {
6990 let mut venue = slipped_venue_offering("1000000", uniform(8), sized("1"));
6991 let outcome = venue.open_order(limit_request(
6992 "kill",
6993 Side::Buy,
6994 "3",
6995 "50000",
6996 TimeInForce::FillOrKill,
6997 ));
6998 assert!(
6999 matches!(
7000 outcome.response.state,
7001 OrderState::Inactive(InactiveOrderState::Cancelled(_))
7002 ),
7003 "{:?}",
7004 outcome.response.state
7005 );
7006 assert_eq!(venue.slippage, Slippage::new(uniform(8)));
7007 }
7008
7009 #[test]
7011 fn a_part_filled_order_refused_for_its_hold_hands_back_its_draw() {
7012 let mut venue = slipped_venue_offering("60000", uniform(8), sized("1"));
7013 let outcome = venue.open_order(limit_request("split", Side::Buy, "3", "50000", gtc()));
7014 assert!(
7015 matches!(
7016 outcome.response.state,
7017 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
7018 ApiError::BalanceInsufficient(..)
7019 )))
7020 ),
7021 "{:?}",
7022 outcome.response.state
7023 );
7024 assert_eq!(venue.slippage, Slippage::new(uniform(8)));
7025 }
7026
7027 #[test]
7030 fn a_refused_order_hands_back_its_draw() {
7031 let config = slipped_config(uniform(11));
7032 let mut venue = SimulatedVenue::new(&config, spot_instruments());
7033
7034 let refused = venue.open_order(buy_request("1000000", last("100")));
7035 assert!(
7036 matches!(
7037 refused.response.state,
7038 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
7039 ApiError::BalanceInsufficient(..)
7040 )))
7041 ),
7042 "{:?}",
7043 refused.response.state
7044 );
7045 assert_eq!(venue.slippage, Slippage::new(config.slippage));
7046
7047 let mut fresh = SimulatedVenue::new(&config, spot_instruments());
7048 assert_eq!(
7049 filled_trade(&venue.open_order(buy_request("1", last("100")))).price,
7050 filled_trade(&fresh.open_order(buy_request("1", last("100")))).price,
7051 );
7052 }
7053
7054 fn slipped_prices(config: &MockExecutionConfig) -> Vec<Decimal> {
7057 let mut venue = SimulatedVenue::new(config, spot_instruments());
7058 (0..20)
7059 .map(|i| {
7060 let request = if i % 2 == 0 {
7061 buy_request("0.1", last("100"))
7062 } else {
7063 sell_request("0.1", last("100"))
7064 };
7065 filled_trade(&venue.open_order(request)).price
7066 })
7067 .collect()
7068 }
7069
7070 #[test]
7071 fn a_seeded_venue_reproduces_its_prices_within_the_range() {
7072 let prices = slipped_prices(&slipped_config(uniform(42)));
7073 assert_eq!(prices, slipped_prices(&slipped_config(uniform(42))));
7074 assert_ne!(prices, slipped_prices(&slipped_config(uniform(43))));
7075
7076 for (i, price) in prices.iter().enumerate() {
7077 let (low, high) = if i % 2 == 0 {
7079 (d("101"), d("102.5"))
7080 } else {
7081 (d("97.5"), d("99"))
7082 };
7083 assert!(
7084 low <= *price && *price <= high,
7085 "fill {i} at {price} outside [{low}, {high}]"
7086 );
7087 }
7088 }
7089
7090 #[test]
7093 fn a_zero_rate_is_identical_to_no_slippage() {
7094 let none = slipped_prices(&slipped_config(SlippageConfig::none()));
7095 let zero = slipped_prices(&slipped_config(
7096 SlippageConfig::uniform(Decimal::ZERO, Decimal::ZERO, 42).unwrap(),
7097 ));
7098 assert_eq!(
7099 none.iter().map(Decimal::to_string).collect::<Vec<_>>(),
7100 zero.iter().map(Decimal::to_string).collect::<Vec<_>>()
7101 );
7102 }
7103
7104 #[derive(Debug)]
7106 struct SizeImpact;
7107
7108 impl FillModel for SizeImpact {
7109 fn fill_price(&self, fill: &FillContext<'_>) -> Option<Decimal> {
7110 fill.market.last_price.map(|last| last + fill.quantity)
7111 }
7112 }
7113
7114 #[test]
7117 fn a_custom_fill_model_sees_the_quantity_and_is_still_slipped() {
7118 let config = slipped_config(SlippageConfig::fixed(d("0.01")).unwrap());
7119 let mut venue =
7120 SimulatedVenue::new(&config, spot_instruments()).with_fill_model(Arc::new(SizeImpact));
7121
7122 let outcome = venue.open_order(buy_request("3", last("100")));
7123 assert_eq!(
7124 filled_trade(&outcome).price,
7125 d("104.03"),
7126 "(100 + 3) x 1.01"
7127 );
7128 }
7129
7130 #[test]
7131 fn percentage_fee_model_deducts_correct_fee_on_buy() {
7132 let fee_model = FeeModelConfig::Percentage(PercentageFeeModel::new(d("0.001")));
7134 let mut venue = make_venue_with_fee("0", "10000", fee_model);
7135
7136 let outcome = venue.open_order(buy_request("10", market_prices("100")));
7141
7142 assert!(
7143 outcome.response.state.is_accepted(),
7144 "buy should succeed: {:?}",
7145 outcome.response.state
7146 );
7147
7148 assert_eq!(
7150 filled_trade(&outcome).fees.fees,
7151 d("1"),
7152 "trade fee must be 1 USDT"
7153 );
7154
7155 let usdt = venue.account.balance_mut("e()).unwrap();
7157 assert_eq!(
7158 usdt.balance.free,
7159 d("8999"),
7160 "quote balance must decrease by notional + fee"
7161 );
7162 }
7163
7164 #[test]
7165 fn percentage_fee_model_deducts_correct_fee_on_sell() {
7166 let fee_model = FeeModelConfig::Percentage(PercentageFeeModel::new(d("0.001")));
7168 let mut venue = make_venue_with_fee("10", "0", fee_model);
7169
7170 let outcome = venue.open_order(sell_request("1", market_prices("100")));
7175
7176 assert!(
7177 outcome.response.state.is_accepted(),
7178 "sell should succeed: {:?}",
7179 outcome.response.state
7180 );
7181
7182 assert_eq!(
7184 filled_trade(&outcome).fees.fees,
7185 d("0.1"),
7186 "trade fee must be 0.1 USDT"
7187 );
7188
7189 let btc = venue.account.balance_mut(&base()).unwrap();
7191 assert_eq!(
7192 btc.balance.free,
7193 d("9"),
7194 "base balance must decrease by the quantity alone"
7195 );
7196
7197 let usdt = venue.account.balance_mut("e()).unwrap();
7199 assert_eq!(
7200 usdt.balance.free,
7201 d("99.9"),
7202 "quote balance must rise by the proceeds less the fee"
7203 );
7204 }
7205
7206 #[test]
7211 fn a_spot_round_trip_returns_the_base_and_moves_quote_by_pnl_less_fees() {
7212 let fee_model = FeeModelConfig::Percentage(PercentageFeeModel::new(d("0.001")));
7214 let mut venue = make_venue_with_fee("0", "1000", fee_model);
7215
7216 let buy = venue.open_order(buy_request("1", market_prices("100")));
7217 assert_eq!(
7219 filled_balance(&buy).balance,
7220 Balance::new(d("899.9"), d("899.9"))
7221 );
7222 assert_eq!(filled_received(&buy).balance, Balance::new(d("1"), d("1")));
7223
7224 let sell = venue.open_order(sell_request("1", market_prices("110")));
7225 assert_eq!(filled_balance(&sell).balance, Balance::new(d("0"), d("0")));
7226 assert_eq!(
7228 filled_received(&sell).balance,
7229 Balance::new(d("1009.79"), d("1009.79"))
7230 );
7231
7232 let fees = filled_trade(&buy).fees.fees + filled_trade(&sell).fees.fees;
7233 assert_eq!(
7234 d("1009.79") - d("1000"),
7235 d("10") - fees,
7236 "quote moves by the realised PnL less the fees the trades report"
7237 );
7238 }
7239
7240 #[test]
7243 fn a_buy_creates_the_base_balance_the_account_never_held() {
7244 let mut venue = SimulatedVenue::new(
7245 &config_from_balances(vec![funded(quote(), d("1000"))], FeeModelConfig::default()),
7246 spot_instruments(),
7247 );
7248
7249 let buy = venue.open_order(buy_request("2", market_prices("100")));
7250 let received = filled_received(&buy);
7251 assert_eq!(received.asset, base());
7252 assert_eq!(received.balance, Balance::new(d("2"), d("2")));
7253
7254 let sell = venue.open_order(sell_request("2", market_prices("100")));
7255 assert!(
7256 sell.response.state.is_accepted(),
7257 "the base the buy received can be sold: {:?}",
7258 sell.response.state
7259 );
7260 assert_eq!(filled_balance(&sell).balance, Balance::new(d("0"), d("0")));
7261 assert_eq!(
7262 filled_received(&sell).balance,
7263 Balance::new(d("1000"), d("1000"))
7264 );
7265 }
7266
7267 #[test]
7270 fn a_per_unit_commission_on_a_spot_sell_comes_out_of_the_proceeds() {
7271 let fee_model = FeeModelConfig::PerContract(PerContractFeeModel {
7272 commission_per_contract: d("0.5"),
7273 });
7274 let mut venue = make_venue_with_fee("10", "0", fee_model);
7275
7276 let sell = venue.open_order(sell_request("2", market_prices("100")));
7277
7278 assert_eq!(filled_trade(&sell).fees.fees, d("1"));
7279 assert_eq!(filled_balance(&sell).balance, Balance::new(d("8"), d("8")));
7280 assert_eq!(
7281 filled_received(&sell).balance,
7282 Balance::new(d("199"), d("199"))
7283 );
7284 }
7285
7286 #[test]
7289 fn a_sell_whose_fee_exceeds_its_proceeds_needs_the_excess_in_quote() {
7290 let fee_model = FeeModelConfig::PerContract(PerContractFeeModel {
7291 commission_per_contract: d("2"),
7292 });
7293
7294 let mut short_of_quote = make_venue_with_fee("10", "0", fee_model);
7296 let refused = short_of_quote.open_order(sell_request("1", market_prices("1")));
7297 match refused.response.state {
7298 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
7299 ApiError::BalanceInsufficient(Some(asset), _),
7300 ))) => assert_eq!(asset, quote(), "the asset short is quote, not base"),
7301 other => panic!("expected BalanceInsufficient, got: {other:?}"),
7302 }
7303 assert!(refused.events.is_empty(), "a refusal moves nothing");
7304 assert_eq!(
7305 short_of_quote.account.balance_mut(&base()).unwrap().balance,
7306 Balance::new(d("10"), d("10"))
7307 );
7308
7309 let mut covered = make_venue_with_fee("10", "5", fee_model);
7310 let filled = covered.open_order(sell_request("1", market_prices("1")));
7311 assert_eq!(
7312 filled_balance(&filled).balance,
7313 Balance::new(d("9"), d("9"))
7314 );
7315 assert_eq!(
7316 filled_received(&filled).balance,
7317 Balance::new(d("4"), d("4"))
7318 );
7319 }
7320
7321 #[test]
7324 fn a_resting_sell_whose_fee_exceeds_its_proceeds_is_cancelled_when_quote_cannot_cover() {
7325 let fee_model = FeeModelConfig::PerContract(PerContractFeeModel {
7326 commission_per_contract: d("2"),
7327 });
7328 let mut venue = make_market_venue_with("10", "0", fee_model, SimFillConfig::default());
7329 advance(&mut venue, time(1));
7330 assert!(
7331 venue
7332 .apply_market(
7333 &instrument_name(),
7334 MarketObservation::new(book("0.5", "0.6")),
7335 time(1)
7336 )
7337 .is_empty()
7338 );
7339
7340 let rested = venue.open_order(limit_request("s", Side::Sell, "1", "1", gtc()));
7341 assert_eq!(rested.events.len(), 1, "a 0.5 bid does not reach 1");
7342 assert_eq!(
7343 balance_of(&rested.events[0]).balance,
7344 Balance::new(d("10"), d("9"))
7345 );
7346
7347 advance(&mut venue, time(2));
7348 let events = venue.apply_market(
7349 &instrument_name(),
7350 MarketObservation::new(book("1.1", "1.2")),
7351 time(2),
7352 );
7353
7354 let [released, cancelled] = events.as_slice() else {
7355 panic!("expected the released hold, then the cancel: {events:?}");
7356 };
7357 assert_eq!(balance_of(released).balance, Balance::new(d("10"), d("10")));
7358 assert!(
7359 matches!(
7360 order_of(cancelled).state,
7361 OrderState::Inactive(InactiveOrderState::Cancelled(_))
7362 ),
7363 "{:?}",
7364 order_of(cancelled).state
7365 );
7366 assert!(venue.trades(time(0)).is_empty(), "nothing traded");
7367 }
7368
7369 #[test]
7372 fn a_resting_sell_whose_fee_exceeds_its_proceeds_fills_when_quote_covers_it() {
7373 let fee_model = FeeModelConfig::PerContract(PerContractFeeModel {
7374 commission_per_contract: d("2"),
7375 });
7376 let mut venue = make_market_venue_with("10", "5", fee_model, SimFillConfig::default());
7377 advance(&mut venue, time(1));
7378 assert!(
7379 venue
7380 .apply_market(
7381 &instrument_name(),
7382 MarketObservation::new(book("0.5", "0.6")),
7383 time(1)
7384 )
7385 .is_empty()
7386 );
7387 let rested = venue.open_order(limit_request("s", Side::Sell, "1", "1", gtc()));
7388 assert_eq!(rested.events.len(), 1, "a 0.5 bid does not reach 1");
7389
7390 advance(&mut venue, time(2));
7391 let events = venue.apply_market(
7392 &instrument_name(),
7393 MarketObservation::new(book("1.1", "1.2")),
7394 time(2),
7395 );
7396
7397 assert_eq!(
7398 events.len(),
7399 4,
7400 "the base paid, the quote received, the trade, then the terminal order"
7401 );
7402 assert_eq!(balance_of(&events[0]).balance, Balance::new(d("9"), d("9")));
7403 assert_eq!(balance_of(&events[1]).balance, Balance::new(d("4"), d("4")));
7405 assert_eq!(trade_of(&events[2]).fees.fees, d("2"));
7406 assert!(
7407 matches!(
7408 order_of(&events[3]).state,
7409 OrderState::Inactive(InactiveOrderState::FullyFilled(_))
7410 ),
7411 "{:?}",
7412 order_of(&events[3]).state
7413 );
7414 }
7415
7416 fn received(mut venue: SimulatedVenue) -> SimulatedVenue {
7419 venue.spot_fee_asset = SpotFeeAsset::Received;
7420 venue
7421 }
7422
7423 #[test]
7426 fn received_takes_a_buys_fee_from_the_base_it_receives() {
7427 let fee_model = FeeModelConfig::Percentage(PercentageFeeModel::new(d("0.001")));
7428 let mut venue = received(make_venue_with_fee("0", "10000", fee_model));
7429
7430 let outcome = venue.open_order(buy_request("10", market_prices("100")));
7432
7433 assert_eq!(
7434 filled_balance(&outcome).balance,
7435 Balance::new(d("9000"), d("9000"))
7436 );
7437 assert_eq!(filled_received(&outcome).asset, base());
7438 assert_eq!(
7439 filled_received(&outcome).balance,
7440 Balance::new(d("9.99"), d("9.99"))
7441 );
7442 assert_eq!(
7443 filled_trade(&outcome).fees,
7444 AssetFees::new(base(), d("0.01"), Some(d("1")))
7445 );
7446 }
7447
7448 #[test]
7450 fn received_takes_a_sells_fee_from_its_proceeds() {
7451 let fee_model = FeeModelConfig::Percentage(PercentageFeeModel::new(d("0.001")));
7452 let mut venue = received(make_venue_with_fee("10", "0", fee_model));
7453
7454 let outcome = venue.open_order(sell_request("1", market_prices("100")));
7455
7456 assert_eq!(
7457 filled_balance(&outcome).balance,
7458 Balance::new(d("9"), d("9"))
7459 );
7460 assert_eq!(
7461 filled_received(&outcome).balance,
7462 Balance::new(d("99.9"), d("99.9"))
7463 );
7464 assert_eq!(
7465 filled_trade(&outcome).fees,
7466 AssetFees::new(quote(), d("0.1"), Some(d("0.1")))
7467 );
7468 }
7469
7470 #[test]
7473 fn a_received_buy_whose_fee_exceeds_its_quantity_needs_the_excess_in_base() {
7474 let fee_model = FeeModelConfig::PerContract(PerContractFeeModel {
7475 commission_per_contract: d("2"),
7476 });
7477
7478 let mut short_of_base = received(make_venue_with_fee("0", "10", fee_model));
7480 let refused = short_of_base.open_order(buy_request("1", market_prices("1")));
7481 match refused.response.state {
7482 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
7483 ApiError::BalanceInsufficient(Some(asset), _),
7484 ))) => assert_eq!(asset, base(), "the asset short is base, not quote"),
7485 other => panic!("expected BalanceInsufficient, got: {other:?}"),
7486 }
7487 assert!(refused.events.is_empty(), "a refusal moves nothing");
7488 assert_eq!(
7489 short_of_base.account.balance_mut("e()).unwrap().balance,
7490 Balance::new(d("10"), d("10"))
7491 );
7492
7493 let mut covered = received(make_venue_with_fee("5", "10", fee_model));
7494 let filled = covered.open_order(buy_request("1", market_prices("1")));
7495 assert_eq!(
7496 filled_balance(&filled).balance,
7497 Balance::new(d("9"), d("9"))
7498 );
7499 assert_eq!(
7500 filled_received(&filled).balance,
7501 Balance::new(d("4"), d("4"))
7502 );
7503 }
7504
7505 #[test]
7508 fn a_resting_received_buy_holds_only_its_notional() {
7509 let fee_model = FeeModelConfig::Percentage(PercentageFeeModel::new(d("0.001")));
7510 let mut venue = received(make_market_venue_with(
7511 "0",
7512 "10000",
7513 fee_model,
7514 SimFillConfig::default(),
7515 ));
7516 advance(&mut venue, time(1));
7517 assert!(
7518 venue
7519 .apply_market(
7520 &instrument_name(),
7521 MarketObservation::new(book("101", "102")),
7522 time(1)
7523 )
7524 .is_empty()
7525 );
7526
7527 let rested = venue.open_order(limit_request("b", Side::Buy, "10", "100", gtc()));
7528 assert_eq!(rested.events.len(), 1, "a 102 ask does not reach 100");
7529 assert_eq!(
7530 balance_of(&rested.events[0]).balance,
7531 Balance::new(d("10000"), d("9000"))
7532 );
7533
7534 advance(&mut venue, time(2));
7535 let events = venue.apply_market(
7536 &instrument_name(),
7537 MarketObservation::new(book("98", "99")),
7538 time(2),
7539 );
7540
7541 assert_eq!(
7542 events.len(),
7543 4,
7544 "the quote paid, the base received, the trade, then the terminal order"
7545 );
7546 assert_eq!(
7547 balance_of(&events[0]).balance,
7548 Balance::new(d("9000"), d("9000"))
7549 );
7550 assert_eq!(
7551 balance_of(&events[1]).balance,
7552 Balance::new(d("9.99"), d("9.99"))
7553 );
7554 assert_eq!(
7555 trade_of(&events[2]).fees,
7556 AssetFees::new(base(), d("0.01"), Some(d("1")))
7557 );
7558 }
7559
7560 #[test]
7563 fn a_received_buy_at_a_zero_price_is_charged_in_quote() {
7564 let fee_model = FeeModelConfig::PerContract(PerContractFeeModel {
7565 commission_per_contract: d("1"),
7566 });
7567 let mut venue = received(make_venue_with_fee("0", "10", fee_model));
7568
7569 let outcome = venue.open_order(buy_request("1", market_prices("0")));
7570
7571 assert_eq!(
7572 filled_balance(&outcome).balance,
7573 Balance::new(d("9"), d("9"))
7574 );
7575 assert_eq!(
7576 filled_received(&outcome).balance,
7577 Balance::new(d("1"), d("1"))
7578 );
7579 assert_eq!(filled_trade(&outcome).fees.asset, quote());
7580 }
7581
7582 #[test]
7583 fn percentage_fee_with_zero_price_returns_zero_fee() {
7584 let fee_model = FeeModelConfig::Percentage(PercentageFeeModel::new(d("0.001")));
7586 let mut venue = make_venue_with_fee("10", "0", fee_model);
7587
7588 let outcome = venue.open_order(sell_request("1", market_prices("0")));
7592
7593 assert!(
7594 outcome.response.state.is_accepted(),
7595 "sell at zero price should succeed: {:?}",
7596 outcome.response.state
7597 );
7598
7599 assert_eq!(
7601 filled_trade(&outcome).fees.fees,
7602 Decimal::ZERO,
7603 "fee must be zero when price is zero"
7604 );
7605
7606 let btc = venue.account.balance_mut(&base()).unwrap();
7608 assert_eq!(
7609 btc.balance.free,
7610 d("9"),
7611 "base balance must decrease by quantity only"
7612 );
7613 }
7614
7615 #[test]
7627 fn a_market_order_that_cannot_be_priced_is_rejected_rather_than_panicking() {
7628 for market in [None, Some(MarketSnapshot::default())] {
7629 let mut venue = make_venue("10", "10000000");
7630
7631 let outcome = venue.open_order(buy_request("1.0", market));
7632
7633 assert!(
7634 outcome.events.is_empty(),
7635 "a rejected order must not notify a fill (market={market:?})"
7636 );
7637 assert!(
7638 matches!(
7639 outcome.response.state,
7640 OrderState::Inactive(InactiveOrderState::OpenFailed(
7641 UnindexedOrderError::Rejected(_)
7642 ))
7643 ),
7644 "expected a rejection, got {:?} (market={market:?})",
7645 outcome.response.state
7646 );
7647
7648 let usd = venue.account.balance_mut("e()).unwrap();
7649 assert_eq!(
7650 usd.balance.free,
7651 d("10000000"),
7652 "a rejected order must not move the balance (market={market:?})"
7653 );
7654 }
7655 }
7656
7657 fn priced_market_order(
7659 side: Side,
7660 price: &str,
7661 market: Option<MarketSnapshot>,
7662 ) -> OrderRequestOpen<ExchangeId, InstrumentNameExchange> {
7663 let mut request = request(instrument_name(), side, "1", market);
7664 request.state.price = Some(d(price));
7665 request
7666 }
7667
7668 #[test]
7675 fn a_market_order_is_never_filled_at_a_price_it_carries() {
7676 let mut market_driven = make_market_venue("10", "10000000");
7677 advance(&mut market_driven, time(1));
7678
7679 let cases = [
7680 ("request-priced", make_venue("10", "10000000"), None),
7681 (
7682 "request-priced",
7683 make_venue("10", "10000000"),
7684 Some(MarketSnapshot::default()),
7685 ),
7686 ("market-driven", market_driven, None),
7687 ];
7688
7689 for (regime, mut venue, market) in cases {
7690 let outcome = venue.open_order(priced_market_order(Side::Buy, "1", market));
7691
7692 assert!(
7693 matches!(
7694 outcome.response.state,
7695 OrderState::Inactive(InactiveOrderState::OpenFailed(
7696 UnindexedOrderError::Rejected(ApiError::OrderRejected(_))
7697 ))
7698 ),
7699 "expected the unpriceable rejection, got {:?} ({regime}, market={market:?})",
7700 outcome.response.state
7701 );
7702 assert!(
7703 outcome.events.is_empty(),
7704 "nothing filled ({regime}, market={market:?})"
7705 );
7706 }
7707 }
7708
7709 #[test]
7711 fn a_market_order_is_not_clamped_by_a_price_it_carries() {
7712 for (side, stray) in [(Side::Buy, "90"), (Side::Sell, "110")] {
7713 let mut venue = make_venue("10", "10000000");
7714 let market = Some(MarketSnapshot {
7715 last_price: Some(d("100")),
7716 ..MarketSnapshot::default()
7717 });
7718
7719 let outcome = venue.open_order(priced_market_order(side, stray, market));
7720
7721 assert_eq!(
7722 filled_trade(&outcome).price,
7723 d("100"),
7724 "a {side:?} fills at the market, not clamped to its stray {stray}"
7725 );
7726 }
7727 }
7728
7729 #[test]
7733 fn an_unpriceable_rejection_distinguishes_an_absent_snapshot_from_an_empty_one() {
7734 let reason_of = |market| {
7735 let mut venue = make_venue("10", "10000000");
7736 match venue.open_order(buy_request("1.0", market)).response.state {
7737 OrderState::Inactive(InactiveOrderState::OpenFailed(
7738 UnindexedOrderError::Rejected(ApiError::OrderRejected(reason)),
7739 )) => reason,
7740 other => panic!("expected an OrderRejected, got {other:?}"),
7741 }
7742 };
7743
7744 assert!(
7745 reason_of(None).contains("no market snapshot"),
7746 "an absent snapshot must be named as such, got: {}",
7747 reason_of(None)
7748 );
7749 assert!(
7750 reason_of(Some(MarketSnapshot::default())).contains("no market price available yet"),
7751 "an empty snapshot must read as a cold start, got: {}",
7752 reason_of(Some(MarketSnapshot::default()))
7753 );
7754 }
7755
7756 #[test]
7762 fn a_market_order_fills_at_the_price_its_snapshot_carried() {
7763 let mut venue = make_venue("0", "10000");
7764
7765 let outcome = venue.open_order(buy_request(
7766 "1.0",
7767 Some(MarketSnapshot::from_last_price(Some(d("100")))),
7768 ));
7769
7770 let OrderState::Inactive(InactiveOrderState::FullyFilled(ref filled)) =
7771 outcome.response.state
7772 else {
7773 panic!("expected a filled order, got {:?}", outcome.response.state)
7774 };
7775 assert_eq!(
7776 filled.avg_price,
7777 Some(d("100")),
7778 "a market order must fill at its snapshot's last price"
7779 );
7780 assert_eq!(filled.filled_quantity, d("1.0"));
7781
7782 assert_eq!(filled_trade(&outcome).price, d("100"));
7783
7784 let usd = venue.account.balance_mut("e()).unwrap();
7785 assert_eq!(
7786 usd.balance.free,
7787 d("9900"),
7788 "a 1 BTC buy at 100 must debit 100 quote"
7789 );
7790 }
7791
7792 #[test]
7800 fn a_configured_quote_reservation_survives_a_fill() {
7801 let mut venue = make_venue("10", "10000000");
7802 venue.account.balance_mut("e()).unwrap().balance.total = d("20000000");
7803
7804 let outcome = venue.open_order(buy_request("1.0", market_prices("50000")));
7805
7806 assert!(
7807 matches!(outcome.response.state, OrderState::Inactive(_)),
7808 "a market order fills on arrival, got {:?}",
7809 outcome.response.state
7810 );
7811
7812 let usdt = venue.account.balance_mut("e()).unwrap().balance;
7813 assert_eq!(
7814 usdt.free,
7815 d("10000000") - d("50000"),
7816 "the fill draws the notional from the spendable side"
7817 );
7818 assert_eq!(
7819 usdt.total - usdt.free,
7820 d("10000000"),
7821 "the configured reservation must be untouched by an unrelated fill"
7822 );
7823 }
7824
7825 #[test]
7827 fn a_configured_base_reservation_survives_a_fill() {
7828 let mut venue = make_venue("10", "10000000");
7829 venue.account.balance_mut(&base()).unwrap().balance.total = d("20");
7830
7831 let outcome = venue.open_order(sell_request("1.0", market_prices("50000")));
7832
7833 assert!(
7834 matches!(outcome.response.state, OrderState::Inactive(_)),
7835 "a market order fills on arrival, got {:?}",
7836 outcome.response.state
7837 );
7838
7839 let btc = venue.account.balance_mut(&base()).unwrap().balance;
7840 assert_eq!(
7841 btc.free,
7842 d("10") - d("1"),
7843 "the fill delivers one base unit from the spendable side"
7844 );
7845 assert_eq!(
7846 btc.total - btc.free,
7847 d("10"),
7848 "the configured reservation must be untouched by an unrelated fill"
7849 );
7850 }
7851
7852 #[test]
7857 fn a_reservation_is_not_spendable() {
7858 let mut venue = make_venue("10", "100");
7859 venue.account.balance_mut("e()).unwrap().balance.total = d("10000000");
7861
7862 let outcome = venue.open_order(buy_request("1.0", market_prices("50000")));
7863
7864 assert!(
7865 matches!(
7866 outcome.response.state,
7867 OrderState::Inactive(InactiveOrderState::OpenFailed(
7868 UnindexedOrderError::Rejected(ApiError::BalanceInsufficient(_, _))
7869 ))
7870 ),
7871 "expected an insufficient-balance rejection, got {:?}",
7872 outcome.response.state
7873 );
7874 assert!(
7875 outcome.events.is_empty(),
7876 "a refused order moves nothing, so it restates nothing"
7877 );
7878
7879 let usdt = venue.account.balance_mut("e()).unwrap().balance;
7880 assert_eq!(usdt.free, d("100"), "a refused reserve must not move free");
7881 assert_eq!(
7882 usdt.total,
7883 d("10000000"),
7884 "a refused reserve must not move total"
7885 );
7886 }
7887
7888 #[test]
7896 fn an_account_snapshot_orders_one_instruments_orders_deterministically() {
7897 const CIDS: [&str; 8] = ["h", "d", "a", "g", "c", "f", "b", "e"];
7900
7901 let resting = |cid: &str| UnindexedOrder {
7902 key: OrderKey {
7903 exchange: EXCHANGE,
7904 instrument: instrument_name(),
7905 strategy: StrategyId::new("test"),
7906 cid: ClientOrderId::new(cid),
7907 },
7908 side: Side::Buy,
7909 price: Some(d("50000")),
7910 quantity: d("1"),
7911 kind: OrderKind::Limit,
7912 time_in_force: TimeInForce::GoodUntilCancelled { post_only: false },
7913 state: OrderState::active(Open {
7914 id: VenueOrderId::Assigned(OrderId::new(cid)),
7915 time_exchange: Default::default(),
7916 filled_quantity: Decimal::ZERO,
7917 }),
7918 };
7919
7920 let mut config = spot_config("10", "10000000", FeeModelConfig::default());
7921 config.initial_state.instruments = vec![InstrumentAccountSnapshot {
7922 instrument: instrument_name(),
7923 orders: CIDS.into_iter().map(resting).collect(),
7924 orders_complete: true,
7925 position: PositionReport::Unreported,
7926 isolated: None,
7927 }];
7928
7929 let venue = SimulatedVenue::new(&config, spot_instruments());
7930
7931 let cids = venue
7932 .account_snapshot()
7933 .instruments
7934 .into_iter()
7935 .flat_map(|instrument| instrument.orders)
7936 .map(|order| order.key.cid)
7937 .collect::<Vec<_>>();
7938
7939 let mut expected = CIDS.map(ClientOrderId::new).to_vec();
7940 expected.sort();
7941 assert_eq!(
7942 cids, expected,
7943 "orders on one instrument must be ordered by a key no two of them share"
7944 );
7945 }
7946
7947 #[test]
7951 fn minted_order_ids_start_past_the_highest_decimal_id_seeded() {
7952 let resting = |cid: &str, id: &str| UnindexedOrder {
7953 key: OrderKey {
7954 exchange: EXCHANGE,
7955 instrument: instrument_name(),
7956 strategy: StrategyId::new("test"),
7957 cid: ClientOrderId::new(cid),
7958 },
7959 side: Side::Buy,
7960 price: Some(d("50000")),
7961 quantity: d("1"),
7962 kind: OrderKind::Limit,
7963 time_in_force: TimeInForce::GoodUntilCancelled { post_only: false },
7964 state: OrderState::active(Open {
7965 id: VenueOrderId::Assigned(OrderId::new(id)),
7966 time_exchange: Default::default(),
7967 filled_quantity: Decimal::ZERO,
7968 }),
7969 };
7970
7971 let seeded_venue = |orders| {
7972 let mut config = spot_config("10", "10000000", FeeModelConfig::default());
7973 config.initial_state.instruments = vec![InstrumentAccountSnapshot {
7974 instrument: instrument_name(),
7975 orders,
7976 orders_complete: true,
7977 position: PositionReport::Unreported,
7978 isolated: None,
7979 }];
7980 SimulatedVenue::new(&config, spot_instruments())
7981 };
7982
7983 let mut venue = seeded_venue(vec![
7985 resting("a", "3"),
7986 resting("b", "1"),
7987 resting("c", "zzz"),
7988 ]);
7989 assert_eq!(venue.order_id_sequence_fetch_add(), OrderId::new("4"));
7990 assert_eq!(venue.order_id_sequence_fetch_add(), OrderId::new("5"));
7991 assert_eq!(venue.order_sequence(), 2);
7993
7994 let mut cancelled = resting("d", "9");
7996 cancelled.state = OrderState::inactive(Cancelled::new(
7997 OrderId::new("9"),
7998 Default::default(),
7999 Some(Decimal::ZERO),
8000 ));
8001 let mut venue = seeded_venue(vec![resting("a", "3"), cancelled]);
8002 assert_eq!(venue.order_id_sequence_fetch_add(), OrderId::new("10"));
8003
8004 let mut venue = seeded_venue(vec![resting("c", "zzz")]);
8006 assert_eq!(venue.order_id_sequence_fetch_add(), OrderId::new("0"));
8007 }
8008
8009 #[test]
8011 fn minted_order_ids_start_at_zero_when_nothing_is_seeded() {
8012 let config = spot_config("10", "10000000", FeeModelConfig::default());
8013 let mut venue = SimulatedVenue::new(&config, spot_instruments());
8014
8015 assert_eq!(venue.order_id_sequence_fetch_add(), OrderId::new("0"));
8016 }
8017
8018 #[test]
8020 fn a_seeded_u64_max_order_id_does_not_overflow_the_next() {
8021 let mut account = AccountState::try_from(
8022 spot_config("10", "10000000", FeeModelConfig::default()).initial_state,
8023 )
8024 .unwrap();
8025 let mut seeded = seeded_part_filled("48000", "1", "0");
8026 seeded.state.id = VenueOrderId::Assigned(OrderId::new(u64::MAX.to_string()));
8027 assert!(
8028 account.book(seeded, None).is_ok(),
8029 "the book is vacant under this id"
8030 );
8031
8032 assert_eq!(first_unseeded_order_id(&account), u128::from(u64::MAX) + 1);
8033 }
8034
8035 #[test]
8036 fn an_order_for_a_quantity_that_is_not_positive_is_rejected_before_the_ledger_moves() {
8037 for request in [
8038 buy_request("0", market_prices("50000")),
8039 sell_request("-1", market_prices("50000")),
8040 ] {
8041 let mut venue = make_venue("100", "10000000");
8042 let quantity = request.state.quantity;
8043
8044 let outcome = venue.open_order(request);
8045
8046 assert_eq!(
8047 rejection_reason(&outcome),
8048 format!(
8049 "cannot open {} for {EXCHANGE}: quantity {quantity} is not positive",
8050 instrument_name()
8051 )
8052 );
8053 assert!(outcome.events.is_empty(), "a refused order moves nothing");
8054 assert!(venue.trades(time(0)).is_empty(), "and nothing traded");
8055 assert!(venue.orders_open(&[]).is_empty(), "or rests");
8056 assert_eq!(free_quote(&venue), d("10000000"));
8057 assert_eq!(total_base(&venue), d("100"));
8058 }
8059
8060 let mut venue = make_venue("100", "10000000");
8062 let outcome = venue.open_order(limit_request("limit", Side::Buy, "0", "50000", gtc()));
8063 let reason = rejection_reason(&outcome);
8064 assert!(
8065 reason.contains("does not support OrderKind::Limit"),
8066 "{reason}"
8067 );
8068 }
8069
8070 #[test]
8079 fn a_limit_order_is_rejected_before_a_fill_model_is_consulted() {
8080 let mut venue = make_venue("100", "10000000");
8081 venue.fill_model = Arc::new(SimFillConfig::Midpoint(crate::fill::MidpointFillModel));
8082
8083 let mut request = buy_request("1", market_prices("50000"));
8084 request.state.kind = OrderKind::Limit;
8085 request.state.price = Some(d("40000"));
8086
8087 let outcome = venue.open_order(request);
8088
8089 assert!(
8090 outcome.events.is_empty(),
8091 "a rejected order moves no balance and books no trade"
8092 );
8093 match outcome.response.state {
8094 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
8095 ApiError::OrderRejected(ref reason),
8096 ))) => assert!(
8097 reason.contains("does not support OrderKind::Limit"),
8098 "the rejection must name the unsupported kind, got: {reason}"
8099 ),
8100 other => panic!("a limit order must be rejected by kind, got: {other:?}"),
8101 }
8102 }
8103
8104 #[test]
8109 fn a_request_priced_venue_rejects_a_limit_order_and_says_what_would_accept_one() {
8110 let mut venue = make_venue("10", "1000000");
8111 assert_eq!(venue.regime(), VenueRegime::RequestPriced);
8112
8113 let outcome = venue.open_order(limit_request("a", Side::Buy, "1", "48000", gtc()));
8114
8115 assert!(
8116 outcome.events.is_empty(),
8117 "a rejected order moves no balance"
8118 );
8119 match outcome.response.state {
8120 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
8121 ApiError::OrderRejected(ref reason),
8122 ))) => {
8123 assert!(
8124 reason.contains("new_market_driven"),
8125 "the rejection must name the constructor that would accept it, got: {reason}"
8126 );
8127 }
8128 ref other => panic!("a limit order must be rejected by the regime, got: {other:?}"),
8129 }
8130 }
8131
8132 #[test]
8135 fn a_limit_order_that_does_not_cross_rests_and_reserves_what_its_fill_will_cost() {
8136 let (venue, rested) = venue_resting_one_buy("48000");
8137
8138 let reserved = d("48009.6");
8140
8141 let balance = venue.balances(&[quote()]).remove(0);
8142 assert_eq!(
8143 balance.balance.total,
8144 d("1000000"),
8145 "resting an order spends nothing: `total` moves only on a fill"
8146 );
8147 assert_eq!(
8148 balance.balance.free,
8149 d("1000000") - reserved,
8150 "`free` falls by the notional plus the MAKER fee the fill will charge"
8151 );
8152 assert_eq!(
8153 balance.balance.used(),
8154 reserved,
8155 "the held portion is what a client sees as committed to the order"
8156 );
8157
8158 assert_eq!(
8159 rested_open(&rested).filled_quantity,
8160 Decimal::ZERO,
8161 "the client is told an order is working with none of its quantity done"
8162 );
8163
8164 let open = venue
8165 .orders_open(&[instrument_name()])
8166 .pop()
8167 .expect("the order is on the book");
8168 assert_eq!(open.price, Some(d("48000")));
8169 assert_eq!(
8170 open.state,
8171 *rested_open(&rested),
8172 "the book and the response describe the same order"
8173 );
8174 }
8175
8176 #[test]
8179 fn a_resting_order_fills_at_its_own_limit_and_is_never_improved_by_the_book() {
8180 let (mut venue, _) = venue_resting_one_buy("48000");
8181 advance(&mut venue, time(2));
8182
8183 let events = venue.apply_market(
8185 &instrument_name(),
8186 MarketObservation::new(book("47800", "47900")),
8187 time(2),
8188 );
8189
8190 assert_eq!(
8191 events.len(),
8192 4,
8193 "one filled spot order owes two balances, a trade and a terminal order snapshot"
8194 );
8195
8196 let trade = trade_of(&events[2]);
8197 assert_eq!(
8198 trade.price,
8199 d("48000"),
8200 "a resting order fills at its own limit; 47900 would credit it improvement no maker \
8201 can obtain"
8202 );
8203 assert_eq!(
8204 trade.fees.fees,
8205 d("9.6"),
8206 "2bp MAKER on 48000, not 10bp taker: the resting side supplied liquidity"
8207 );
8208
8209 assert_eq!(
8210 filled_price(order_of(&events[3])),
8211 d("48000"),
8212 "the terminal snapshot reports the same price as the trade"
8213 );
8214 }
8215
8216 #[test]
8219 fn a_resting_fill_reports_its_trade_before_the_order_that_produced_it() {
8220 let (mut venue, _) = venue_resting_one_buy("48000");
8221 advance(&mut venue, time(2));
8222
8223 let events = venue.apply_market(
8224 &instrument_name(),
8225 MarketObservation::new(book("47800", "47900")),
8226 time(2),
8227 );
8228
8229 assert_eq!(
8230 balance_of(&events[0]).asset,
8231 quote(),
8232 "the balance the fill paid with comes first"
8233 );
8234 assert_eq!(
8235 balance_of(&events[1]).asset,
8236 base(),
8237 "then the balance it received"
8238 );
8239 assert!(
8240 matches!(events[2].kind, AccountEventKind::Trade(_)),
8241 "then the trade, got: {:?}",
8242 events[2].kind
8243 );
8244 assert!(
8245 matches!(events[3].kind, AccountEventKind::OrderSnapshot(_)),
8246 "and only then the order it terminated, got: {:?}",
8247 events[3].kind
8248 );
8249 }
8250
8251 #[test]
8254 fn a_resting_fill_reports_its_order_as_terminal_rather_than_as_a_complete_open() {
8255 let (mut venue, _) = venue_resting_one_buy("48000");
8256 advance(&mut venue, time(2));
8257
8258 let events = venue.apply_market(
8259 &instrument_name(),
8260 MarketObservation::new(book("47800", "47900")),
8261 time(2),
8262 );
8263
8264 assert!(
8265 matches!(
8266 order_of(&events[3]).state,
8267 OrderState::Inactive(InactiveOrderState::FullyFilled(_))
8268 ),
8269 "got: {:?}",
8270 order_of(&events[3]).state
8271 );
8272 assert!(
8273 venue.orders_open(&[]).is_empty(),
8274 "a filled order leaves the book"
8275 );
8276 }
8277
8278 #[test]
8281 fn a_resting_order_settles_exactly_what_it_reserved() {
8282 let (mut venue, _) = venue_resting_one_buy("48000");
8283 advance(&mut venue, time(2));
8284
8285 let reserved = d("48009.6");
8286 let free_while_resting = venue.balances(&[quote()]).remove(0).balance.free;
8287
8288 let events = venue.apply_market(
8289 &instrument_name(),
8290 MarketObservation::new(book("47800", "47900")),
8291 time(2),
8292 );
8293
8294 let balance = balance_of(&events[0]);
8295 assert_eq!(
8296 balance.balance.total,
8297 d("1000000") - reserved,
8298 "`total` falls by exactly what was held"
8299 );
8300 assert_eq!(
8301 balance.balance.free, free_while_resting,
8302 "`free` was already reduced when the order rested, so the fill does not move it again"
8303 );
8304 assert_eq!(
8305 balance.balance.used(),
8306 Decimal::ZERO,
8307 "nothing is held any more: the order that held it is gone"
8308 );
8309 }
8310
8311 fn seeded_part_filled(price: &str, quantity: &str, filled: &str) -> OpenOrder {
8316 as_open(Order {
8317 key: OrderKey {
8318 exchange: EXCHANGE,
8319 instrument: instrument_name(),
8320 strategy: StrategyId::new("test"),
8321 cid: ClientOrderId::new("part_filled"),
8322 },
8323 side: Side::Buy,
8324 price: Some(d(price)),
8325 quantity: d(quantity),
8326 kind: OrderKind::Limit,
8327 time_in_force: gtc(),
8328 state: OrderState::active(Open::new(
8329 VenueOrderId::Assigned(OrderId::new("part_filled")),
8330 time(1),
8331 d(filled),
8332 )),
8333 })
8334 .expect("the seeded order is Open")
8335 }
8336
8337 #[test]
8340 #[should_panic(expected = "cannot afford the fill of resting order part_filled")]
8341 fn a_seeded_order_the_account_cannot_afford_panics_when_it_fills() {
8342 let mut venue = make_market_venue("10", "100");
8343 advance(&mut venue, time(1));
8344 assert!(
8345 venue
8346 .account
8347 .book(seeded_part_filled("48000", "1", "0"), None)
8348 .is_ok()
8349 );
8350
8351 advance(&mut venue, time(2));
8352 let _ = venue.apply_market(
8353 &instrument_name(),
8354 MarketObservation::new(book("47800", "47900")),
8355 time(2),
8356 );
8357 }
8358
8359 #[test]
8366 fn a_part_filled_order_on_the_book_settles_and_prints_only_its_remainder() {
8367 let mut venue = make_market_venue("10", "1000000");
8368 advance(&mut venue, time(1));
8369
8370 assert!(
8371 venue
8372 .account
8373 .book(
8374 seeded_part_filled("48000", "1", "0.4"),
8375 None,
8377 )
8378 .is_ok(),
8379 "the book is vacant under this id"
8380 );
8381
8382 advance(&mut venue, time(2));
8383 let events = venue.apply_market(
8384 &instrument_name(),
8385 MarketObservation::new(book("47800", "47900")),
8386 time(2),
8387 );
8388
8389 assert_eq!(
8390 events.len(),
8391 4,
8392 "one filled spot order owes two balances, a trade and a terminal order snapshot"
8393 );
8394
8395 let trade = trade_of(&events[2]);
8396 assert_eq!(
8397 trade.quantity,
8398 d("0.6"),
8399 "0.6 is what was left to trade; printing 1 would report a fill that did not happen"
8400 );
8401 assert_eq!(
8402 trade.fees.fees,
8403 d("5.76"),
8404 "2bp maker on 0.6 at 48000, not on the whole quantity"
8405 );
8406 assert_eq!(
8407 trade.order_filled_quantity,
8408 Some(d("1")),
8409 "the order's cumulative is the 0.4 it arrived with plus the 0.6 this cross traded"
8410 );
8411
8412 assert_eq!(
8413 balance_of(&events[0]).balance.total,
8414 d("1000000") - d("28805.76"),
8415 "the fill costs what the remainder costs; 48009.6 would pay a second time for the 0.4 \
8416 that traded elsewhere"
8417 );
8418 }
8419
8420 #[test]
8429 fn a_fill_completing_a_part_filled_order_reports_no_average_price() {
8430 let mut venue = make_market_venue("10", "1000000");
8431 advance(&mut venue, time(1));
8432
8433 assert!(
8434 venue
8435 .account
8436 .book(seeded_part_filled("48000", "1", "0.4"), None)
8437 .is_ok(),
8438 "the book is vacant under this id"
8439 );
8440
8441 advance(&mut venue, time(2));
8442 let events = venue.apply_market(
8443 &instrument_name(),
8444 MarketObservation::new(book("47800", "47900")),
8445 time(2),
8446 );
8447
8448 let filled = match &order_of(&events[3]).state {
8449 OrderState::Inactive(InactiveOrderState::FullyFilled(filled)) => filled,
8450 other => panic!("expected a fully filled order, got: {other:?}"),
8451 };
8452
8453 assert_eq!(
8454 filled.filled_quantity,
8455 d("1"),
8456 "the order has now done its whole quantity, which is what this field reports"
8457 );
8458 assert_eq!(
8459 filled.avg_price, None,
8460 "48000 is one of the two prices behind that quantity, not their mean"
8461 );
8462 }
8463
8464 #[test]
8473 fn resting_a_part_filled_order_reserves_and_settles_exactly_its_remainder() {
8474 let mut venue = make_market_venue("10", "1000000");
8475 advance(&mut venue, time(1));
8476
8477 let terms = venue
8478 .instrument_terms(&instrument_name())
8479 .expect("the spot fixture is priceable");
8480
8481 let (response, notifications) = venue.rest_order(
8482 limit_request("part_filled", Side::Buy, "1", "48000", gtc()),
8483 d("48000"),
8484 &terms,
8485 d("0.4"),
8486 );
8487
8488 match &response.state {
8489 OrderState::Active(ActiveOrderState::Open(open)) => assert_eq!(
8490 open.filled_quantity,
8491 d("0.4"),
8492 "the order rests carrying what it has already done"
8493 ),
8494 other => panic!("expected a resting open order, got: {other:?}"),
8495 }
8496
8497 match notifications {
8498 Some(OpenOrderNotifications::Rested { balance, .. }) => assert_eq!(
8499 balance.0.balance.free,
8500 d("1000000") - d("28805.76"),
8501 "0.6 at 48000 plus 2bp maker is held; 48009.6 would hold against the 0.4 that has \
8502 already settled"
8503 ),
8504 other => panic!("a rested order restates one balance, got: {other:?}"),
8505 }
8506
8507 advance(&mut venue, time(2));
8508 let events = venue.apply_market(
8509 &instrument_name(),
8510 MarketObservation::new(book("47800", "47900")),
8511 time(2),
8512 );
8513
8514 assert_eq!(events.len(), 4);
8515 assert_eq!(
8516 trade_of(&events[2]).quantity,
8517 d("0.6"),
8518 "the cross prints the remainder that was held for"
8519 );
8520 assert_eq!(
8521 balance_of(&events[0]).balance.total,
8522 d("1000000") - d("28805.76"),
8523 "and settles exactly that: one restatement, with nothing left over to release"
8524 );
8525 assert_eq!(
8526 balance_of(&events[0]).balance.used(),
8527 Decimal::ZERO,
8528 "nothing is held any more: the order that held it is gone"
8529 );
8530 }
8531
8532 #[test]
8540 fn a_marketable_order_aggresses_the_way_its_own_terms_asked_for() {
8541 let now = time(1);
8542 let gtd = TimeInForce::GoodTillDate { expiry: time(9) };
8543
8544 assert_eq!(
8545 disposition(OrderKind::Limit, gtc(), true, now),
8546 Disposition::FillAndRest,
8547 "a limit order with somewhere to wait keeps whatever it could not fill"
8548 );
8549 assert_eq!(
8550 disposition(OrderKind::Limit, gtd, true, now),
8551 Disposition::FillAndRest,
8552 "a deadline it has not reached is still somewhere to wait"
8553 );
8554 assert_eq!(
8555 disposition(OrderKind::Limit, TimeInForce::ImmediateOrCancel, true, now),
8556 Disposition::FillAndCancel,
8557 "immediate-or-cancel asked not to wait"
8558 );
8559 assert_eq!(
8560 disposition(OrderKind::Limit, TimeInForce::FillOrKill, true, now),
8561 Disposition::FillOrNothing,
8562 "fill-or-kill would rather trade nothing than trade in part"
8563 );
8564 assert_eq!(
8565 disposition(OrderKind::Market, gtc(), true, now),
8566 Disposition::FillAndCancel,
8567 "an order carrying no limit price has nothing to wait at, whatever its time in force"
8568 );
8569 assert_eq!(
8570 disposition(OrderKind::Market, gtd, true, now),
8571 Disposition::FillAndCancel,
8572 "including one whose deadline is still ahead of it"
8573 );
8574 assert_eq!(
8575 disposition(OrderKind::Market, TimeInForce::FillOrKill, true, now),
8576 Disposition::FillOrNothing,
8577 "and all-or-nothing stays all-or-nothing whatever the kind"
8578 );
8579 }
8580
8581 #[test]
8584 fn a_marketable_limit_order_fills_on_arrival_at_the_book_bounded_by_its_limit() {
8585 let mut venue = make_market_venue_with(
8586 "10",
8587 "1000000",
8588 maker_taker_fee(),
8589 SimFillConfig::BidAsk(BidAskFillModel),
8590 );
8591 advance(&mut venue, time(1));
8592 assert!(
8593 venue
8594 .apply_market(
8595 &instrument_name(),
8596 MarketObservation::new(book("47800", "47900")),
8597 time(1)
8598 )
8599 .is_empty()
8600 );
8601
8602 let outcome = venue.open_order(limit_request("taker", Side::Buy, "1", "48000", gtc()));
8604
8605 assert_eq!(
8606 outcome.events.len(),
8607 3,
8608 "a filled open owes both balances, then the trade"
8609 );
8610 let trade = filled_trade(&outcome);
8611 assert_eq!(
8612 trade.price,
8613 d("47900"),
8614 "the aggressor takes the offer, which is better than its limit"
8615 );
8616 assert_eq!(
8617 trade.fees.fees,
8618 d("47.9"),
8619 "10bp TAKER on 47900: it crossed the spread rather than resting"
8620 );
8621 assert!(
8622 venue.orders_open(&[]).is_empty(),
8623 "it filled on arrival, so it never rested"
8624 );
8625 }
8626
8627 #[test]
8635 fn a_marketable_limit_order_never_fills_worse_than_its_limit() {
8636 let mut venue = make_market_venue_with(
8637 "10",
8638 "1000000",
8639 FeeModelConfig::default(),
8640 SimFillConfig::default(),
8642 );
8643 advance(&mut venue, time(1));
8644
8645 let stale = MarketSnapshot {
8648 best_bid: Some(d("46900")),
8649 best_ask: Some(d("47000")),
8650 last_price: Some(d("48000")),
8651 };
8652 assert!(
8653 venue
8654 .apply_market(&instrument_name(), MarketObservation::new(stale), time(1))
8655 .is_empty()
8656 );
8657
8658 let outcome = venue.open_order(limit_request("clamped", Side::Buy, "1", "47100", gtc()));
8659
8660 assert_eq!(
8661 filled_trade(&outcome).price,
8662 d("47100"),
8663 "the model priced it at 48000, which the order never agreed to pay"
8664 );
8665 }
8666
8667 #[test]
8670 fn the_two_pricing_rules_agree_where_the_book_meets_the_limit() {
8671 let limit = "48000";
8672 let touching = || book("47900", "48000");
8673
8674 let mut aggressor = make_market_venue_with(
8676 "10",
8677 "1000000",
8678 maker_taker_fee(),
8679 SimFillConfig::BidAsk(BidAskFillModel),
8680 );
8681 advance(&mut aggressor, time(1));
8682 assert!(
8683 aggressor
8684 .apply_market(
8685 &instrument_name(),
8686 MarketObservation::new(touching()),
8687 time(1)
8688 )
8689 .is_empty()
8690 );
8691 let on_arrival = aggressor.open_order(limit_request("a", Side::Buy, "1", limit, gtc()));
8692 let arrival_price = filled_trade(&on_arrival).price;
8693
8694 let mut maker = make_market_venue_with(
8696 "10",
8697 "1000000",
8698 maker_taker_fee(),
8699 SimFillConfig::BidAsk(BidAskFillModel),
8700 );
8701 advance(&mut maker, time(1));
8702 assert!(
8703 maker
8704 .apply_market(
8705 &instrument_name(),
8706 MarketObservation::new(book("49000", "49100")),
8707 time(1)
8708 )
8709 .is_empty()
8710 );
8711 let rested = maker.open_order(limit_request("b", Side::Buy, "1", limit, gtc()));
8712 assert_eq!(rested.events.len(), 1, "it rested rather than filling");
8713 advance(&mut maker, time(2));
8714 let resting_events = maker.apply_market(
8715 &instrument_name(),
8716 MarketObservation::new(touching()),
8717 time(2),
8718 );
8719 let resting_price = trade_of(&resting_events[2]).price;
8720
8721 assert_eq!(
8722 arrival_price,
8723 d(limit),
8724 "an order arriving when the book is at its limit fills at the limit"
8725 );
8726 assert_eq!(
8727 resting_price, arrival_price,
8728 "the two pricing rules must agree exactly at the crossing point; they may differ only \
8729 where the order genuinely is, or is not, marketable"
8730 );
8731 }
8732
8733 #[test]
8736 fn matching_fills_in_price_time_order_and_stops_at_the_first_order_that_does_not_cross() {
8737 let mut venue = make_market_venue("10", "1000000");
8738 advance(&mut venue, time(1));
8739 assert!(
8740 venue
8741 .apply_market(
8742 &instrument_name(),
8743 MarketObservation::new(book("49000", "49100")),
8744 time(1)
8745 )
8746 .is_empty()
8747 );
8748
8749 for (cid, price) in [("worst", "47000"), ("mid", "48000"), ("best", "48500")] {
8751 let outcome = venue.open_order(limit_request(cid, Side::Buy, "1", price, gtc()));
8752 assert_eq!(outcome.events.len(), 1, "{cid} must rest");
8753 }
8754
8755 advance(&mut venue, time(2));
8756 let events = venue.apply_market(
8758 &instrument_name(),
8759 MarketObservation::new(book("47900", "48000")),
8760 time(2),
8761 );
8762
8763 let filled: Vec<_> = events
8764 .iter()
8765 .filter_map(|event| match &event.kind {
8766 AccountEventKind::Trade(trade) => Some(trade.price),
8767 _ => None,
8768 })
8769 .collect();
8770
8771 assert_eq!(
8772 filled,
8773 vec![d("48500"), d("48000")],
8774 "best bid first, and each at its own limit"
8775 );
8776 assert_eq!(
8777 venue
8778 .orders_open(&[])
8779 .into_iter()
8780 .map(|order| order.key.cid)
8781 .collect::<Vec<_>>(),
8782 vec![ClientOrderId::new("worst")],
8783 "the order the market did not reach is untouched"
8784 );
8785 }
8786
8787 #[test]
8790 fn a_feed_with_no_book_matches_on_the_trade_price() {
8791 let mut venue = make_market_venue("10", "1000000");
8792 advance(&mut venue, time(1));
8793 assert!(
8794 venue
8795 .apply_market(
8796 &instrument_name(),
8797 MarketObservation::new(traded("49000")),
8798 time(1)
8799 )
8800 .is_empty()
8801 );
8802
8803 let outcome = venue.open_order(limit_request("t", Side::Buy, "1", "48000", gtc()));
8804 assert_eq!(outcome.events.len(), 1, "49000 traded does not reach 48000");
8805
8806 advance(&mut venue, time(2));
8807 let events = venue.apply_market(
8808 &instrument_name(),
8809 MarketObservation::new(traded("47500")),
8810 time(2),
8811 );
8812
8813 assert_eq!(events.len(), 4, "a print through the limit fills it");
8814 assert_eq!(trade_of(&events[2]).price, d("48000"));
8815 }
8816
8817 #[test]
8820 fn a_price_inside_the_spread_does_not_cross_a_resting_order() {
8821 let (mut venue, _) = venue_resting_one_buy("48000");
8822 advance(&mut venue, time(2));
8823
8824 let inside = MarketSnapshot {
8826 best_bid: Some(d("47900")),
8827 best_ask: Some(d("48100")),
8828 last_price: Some(d("47950")),
8829 };
8830
8831 assert!(
8832 venue
8833 .apply_market(&instrument_name(), MarketObservation::new(inside), time(2))
8834 .is_empty(),
8835 "the offer is 48100, so a 48000 bid is not crossed however the trade price reads"
8836 );
8837 assert_eq!(
8838 venue.orders_open(&[]).len(),
8839 1,
8840 "the order is still resting"
8841 );
8842 }
8843
8844 #[test]
8846 fn a_resting_sell_reserves_base_and_settles_it() {
8847 let mut venue = make_market_venue("10", "1000000");
8848 advance(&mut venue, time(1));
8849 assert!(
8850 venue
8851 .apply_market(
8852 &instrument_name(),
8853 MarketObservation::new(book("47000", "47100")),
8854 time(1)
8855 )
8856 .is_empty()
8857 );
8858
8859 let outcome = venue.open_order(limit_request("s", Side::Sell, "1", "48000", gtc()));
8860 assert_eq!(outcome.events.len(), 1, "47000 bid does not reach 48000");
8861 assert_eq!(
8862 balance_of(&outcome.events[0]).asset,
8863 base(),
8864 "a spot sell delivers the base asset, so that is what is held"
8865 );
8866
8867 advance(&mut venue, time(2));
8868 let events = venue.apply_market(
8869 &instrument_name(),
8870 MarketObservation::new(book("48100", "48200")),
8871 time(2),
8872 );
8873
8874 assert_eq!(trade_of(&events[2]).price, d("48000"), "at its own limit");
8875 let balance = balance_of(&events[0]);
8876 assert_eq!(balance.asset, base());
8877 assert_eq!(balance.balance.total, d("10") - d("1"));
8879 let received = balance_of(&events[1]);
8880 assert_eq!(received.asset, quote());
8881 assert_eq!(received.balance.total, d("1000000") + d("48000") - d("9.6"));
8883 }
8884
8885 #[test]
8888 fn a_maker_rebate_is_held_and_settled_as_a_smaller_amount_than_the_notional() {
8889 let rebate =
8890 FeeModelConfig::Percentage(PercentageFeeModel::maker_taker(d("-0.0001"), d("0.001")));
8891 let mut venue = make_market_venue_with("10", "1000000", rebate, SimFillConfig::default());
8892 advance(&mut venue, time(1));
8893 assert!(
8894 venue
8895 .apply_market(
8896 &instrument_name(),
8897 MarketObservation::new(book("49000", "49100")),
8898 time(1)
8899 )
8900 .is_empty()
8901 );
8902
8903 let outcome = venue.open_order(limit_request("r", Side::Buy, "1", "48000", gtc()));
8904
8905 let held = d("48000") - d("4.8");
8907 assert_eq!(
8908 balance_of(&outcome.events[0]).balance.used(),
8909 held,
8910 "a rebate means less is held than the notional"
8911 );
8912
8913 advance(&mut venue, time(2));
8914 let events = venue.apply_market(
8915 &instrument_name(),
8916 MarketObservation::new(book("47800", "47900")),
8917 time(2),
8918 );
8919
8920 assert_eq!(
8921 balance_of(&events[0]).balance.total,
8922 d("1000000") - held,
8923 "and exactly that less is settled, so the rebate is kept"
8924 );
8925 assert_eq!(trade_of(&events[2]).fees.fees, d("-4.8"));
8926 }
8927
8928 #[test]
8930 fn a_limit_order_that_cannot_be_afforded_is_rejected_without_resting() {
8931 let mut venue = make_market_venue("10", "1000");
8932 advance(&mut venue, time(1));
8933 assert!(
8934 venue
8935 .apply_market(
8936 &instrument_name(),
8937 MarketObservation::new(book("49000", "49100")),
8938 time(1)
8939 )
8940 .is_empty()
8941 );
8942
8943 let outcome = venue.open_order(limit_request("poor", Side::Buy, "1", "48000", gtc()));
8944
8945 assert!(
8946 outcome.events.is_empty(),
8947 "a refused reservation moves nothing"
8948 );
8949 assert!(matches!(
8950 outcome.response.state,
8951 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
8952 ApiError::BalanceInsufficient(..)
8953 )))
8954 ));
8955 assert!(venue.orders_open(&[]).is_empty(), "it never rested");
8956 assert_eq!(
8957 venue.balances(&[quote()]).remove(0).balance.free,
8958 d("1000"),
8959 "the ledger is left exactly as it was"
8960 );
8961 }
8962
8963 #[test]
8968 fn a_cancel_releases_the_reservation_and_restates_the_balance() {
8969 let (mut venue, _) = venue_resting_one_buy("48000");
8970 advance(&mut venue, time(2));
8971
8972 let outcome = venue.cancel_order(OrderEvent {
8973 key: OrderKey {
8974 exchange: EXCHANGE,
8975 instrument: instrument_name(),
8976 strategy: StrategyId::new("test"),
8977 cid: ClientOrderId::new("resting"),
8978 },
8979 state: RequestCancel { id: None },
8980 });
8981
8982 assert_eq!(outcome.events.len(), 1, "a cancel restates one balance");
8983 let balance = balance_of(&outcome.events[0]);
8984 assert_eq!(
8985 balance.balance.free,
8986 d("1000000"),
8987 "everything held against the order is spendable again"
8988 );
8989 assert_eq!(
8990 balance.balance.total,
8991 d("1000000"),
8992 "nothing ever left the account, so `total` never moved"
8993 );
8994
8995 let cancelled = outcome.response.state.expect("the order was on the book");
8996 assert_eq!(cancelled.filled_quantity, Some(Decimal::ZERO));
8997 assert_eq!(cancelled.time_exchange, time(2));
8998 assert!(venue.orders_open(&[]).is_empty());
8999 }
9000
9001 #[test]
9004 fn a_cancel_that_loses_the_race_to_its_own_fill_reports_the_fill() {
9005 let (mut venue, _) = venue_resting_one_buy("48000");
9006 advance(&mut venue, time(2));
9007 let events = venue.apply_market(
9008 &instrument_name(),
9009 MarketObservation::new(book("47800", "47900")),
9010 time(2),
9011 );
9012 assert_eq!(events.len(), 4, "the order filled first");
9013
9014 let outcome = venue.cancel_order(OrderEvent {
9015 key: OrderKey {
9016 exchange: EXCHANGE,
9017 instrument: instrument_name(),
9018 strategy: StrategyId::new("test"),
9019 cid: ClientOrderId::new("resting"),
9020 },
9021 state: RequestCancel { id: None },
9022 });
9023
9024 assert!(outcome.events.is_empty(), "a failed cancel moves nothing");
9025 assert!(
9026 matches!(
9027 outcome.response.state,
9028 Err(OrderError::Rejected(ApiError::OrderAlreadyFullyFilled))
9029 ),
9030 "got: {:?}",
9031 outcome.response.state
9032 );
9033 }
9034
9035 #[test]
9037 fn a_second_cancel_reports_the_order_as_already_cancelled() {
9038 let (mut venue, _) = venue_resting_one_buy("48000");
9039 let cancel = || OrderEvent {
9040 key: OrderKey {
9041 exchange: EXCHANGE,
9042 instrument: instrument_name(),
9043 strategy: StrategyId::new("test"),
9044 cid: ClientOrderId::new("resting"),
9045 },
9046 state: RequestCancel { id: None },
9047 };
9048
9049 assert!(venue.cancel_order(cancel()).response.state.is_ok());
9050 let second = venue.cancel_order(cancel());
9051
9052 assert!(
9053 matches!(
9054 second.response.state,
9055 Err(OrderError::Rejected(ApiError::OrderAlreadyCancelled))
9056 ),
9057 "got: {:?}",
9058 second.response.state
9059 );
9060 }
9061
9062 #[test]
9065 fn a_cancelled_order_enters_the_account_snapshot() {
9066 let (mut venue, _) = venue_resting_one_buy("48000");
9067 venue.cancel_order(OrderEvent {
9068 key: OrderKey {
9069 exchange: EXCHANGE,
9070 instrument: instrument_name(),
9071 strategy: StrategyId::new("test"),
9072 cid: ClientOrderId::new("resting"),
9073 },
9074 state: RequestCancel { id: None },
9075 });
9076
9077 let snapshot = venue.account_snapshot();
9078 let orders = &snapshot.instruments[0].orders;
9079 assert_eq!(orders.len(), 1);
9080 assert!(matches!(
9081 orders[0].state,
9082 OrderState::Inactive(InactiveOrderState::Cancelled(_))
9083 ));
9084 }
9085
9086 #[test]
9092 fn a_limit_order_is_rejected_for_every_time_in_force_this_venue_cannot_honour() {
9093 let unsupported = [
9094 TimeInForce::GoodUntilEndOfDay,
9095 TimeInForce::AtOpen,
9096 TimeInForce::AtClose,
9097 ];
9098
9099 for time_in_force in unsupported {
9100 let mut venue = make_market_venue("10", "1000000");
9101 advance(&mut venue, time(1));
9102 assert!(
9103 venue
9104 .apply_market(
9105 &instrument_name(),
9106 MarketObservation::new(book("49000", "49100")),
9107 time(1)
9108 )
9109 .is_empty()
9110 );
9111
9112 let outcome =
9113 venue.open_order(limit_request("tif", Side::Buy, "1", "48000", time_in_force));
9114
9115 assert!(
9116 outcome.events.is_empty(),
9117 "{time_in_force} must move no balance"
9118 );
9119 assert!(
9120 venue.orders_open(&[]).is_empty(),
9121 "{time_in_force} must not rest"
9122 );
9123 match outcome.response.state {
9124 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
9125 ApiError::OrderRejected(ref reason),
9126 ))) => assert!(
9127 reason.contains("does not model"),
9128 "the rejection must say what is missing, got: {reason}"
9129 ),
9130 ref other => panic!("{time_in_force} must be rejected, got: {other:?}"),
9131 }
9132 }
9133 }
9134
9135 #[test]
9138 fn a_market_order_is_accepted_for_every_time_in_force_that_only_bounds_how_long_it_works() {
9139 for time_in_force in [
9140 TimeInForce::GoodUntilCancelled { post_only: false },
9141 TimeInForce::ImmediateOrCancel,
9142 TimeInForce::FillOrKill,
9143 TimeInForce::GoodTillDate { expiry: time(9) },
9144 TimeInForce::GoodUntilEndOfDay,
9145 ] {
9146 let mut venue = make_venue("10", "1000000");
9147 let mut request = buy_request("1", market_prices("48000"));
9148 request.state.time_in_force = time_in_force;
9149
9150 let outcome = venue.open_order(request);
9151
9152 assert!(
9153 matches!(
9154 outcome.response.state,
9155 OrderState::Inactive(InactiveOrderState::FullyFilled(_))
9156 ),
9157 "{time_in_force} on a market order must still fill, got: {:?}",
9158 outcome.response.state
9159 );
9160 }
9161 }
9162
9163 #[test]
9172 fn a_market_order_is_rejected_for_a_time_in_force_it_cannot_keep() {
9173 for time_in_force in [
9174 TimeInForce::GoodUntilCancelled { post_only: true },
9175 TimeInForce::AtOpen,
9176 TimeInForce::AtClose,
9177 ] {
9178 let mut venue = make_venue("10", "1000000");
9179 let mut request = buy_request("1", market_prices("48000"));
9180 request.state.time_in_force = time_in_force;
9181
9182 let outcome = venue.open_order(request);
9183
9184 assert!(
9185 outcome.events.is_empty(),
9186 "{time_in_force} must move no balance"
9187 );
9188 match outcome.response.state {
9189 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
9190 ApiError::OrderRejected(ref reason),
9191 ))) => assert!(
9192 reason.contains("does not model"),
9193 "the rejection must say what is missing, got: {reason}"
9194 ),
9195 ref other => panic!("{time_in_force} must be rejected, got: {other:?}"),
9196 }
9197 }
9198 }
9199
9200 #[test]
9207 fn a_post_only_order_that_would_take_liquidity_is_cancelled_having_traded_nothing() {
9208 let mut venue = make_market_venue("10", "1000000");
9209 advance(&mut venue, time(1));
9210 assert!(
9211 venue
9212 .apply_market(
9213 &instrument_name(),
9214 MarketObservation::new(book("49000", "49100")),
9215 time(1)
9216 )
9217 .is_empty()
9218 );
9219
9220 let outcome = venue.open_order(limit_request(
9222 "post",
9223 Side::Buy,
9224 "1",
9225 "50000",
9226 TimeInForce::GoodUntilCancelled { post_only: true },
9227 ));
9228
9229 assert!(outcome.events.is_empty(), "nothing was reserved or traded");
9230 assert!(venue.orders_open(&[]).is_empty(), "it must not rest");
9231 assert!(venue.trades(time(0)).is_empty(), "it must not trade");
9232
9233 match outcome.response.state {
9234 OrderState::Inactive(InactiveOrderState::Cancelled(ref cancelled)) => {
9235 assert_eq!(cancelled.filled_quantity, Some(Decimal::ZERO))
9236 }
9237 ref other => panic!("a marketable post-only order must be cancelled, got: {other:?}"),
9238 }
9239 }
9240
9241 #[test]
9243 fn a_post_only_order_that_would_rest_is_accepted_and_reserves() {
9244 let mut venue = make_market_venue("10", "1000000");
9245 advance(&mut venue, time(1));
9246 assert!(
9247 venue
9248 .apply_market(
9249 &instrument_name(),
9250 MarketObservation::new(book("49000", "49100")),
9251 time(1)
9252 )
9253 .is_empty()
9254 );
9255
9256 let outcome = venue.open_order(limit_request(
9257 "post",
9258 Side::Buy,
9259 "1",
9260 "48000",
9261 TimeInForce::GoodUntilCancelled { post_only: true },
9262 ));
9263
9264 assert_eq!(rested_open(&outcome).filled_quantity, Decimal::ZERO);
9265 assert_eq!(venue.orders_open(&[]).len(), 1);
9266 assert_eq!(
9267 balance_of(&outcome.events[0]).balance.free,
9268 d("1000000") - d("48000") - d("9.6"),
9269 "resting post-only reserves the notional plus the 2bp maker fee its fill will charge"
9270 );
9271 }
9272
9273 #[test]
9279 fn a_non_marketable_immediate_order_is_cancelled_having_traded_nothing() {
9280 for time_in_force in [TimeInForce::ImmediateOrCancel, TimeInForce::FillOrKill] {
9281 let mut venue = make_market_venue("10", "1000000");
9282 advance(&mut venue, time(1));
9283 assert!(
9284 venue
9285 .apply_market(
9286 &instrument_name(),
9287 MarketObservation::new(book("49000", "49100")),
9288 time(1)
9289 )
9290 .is_empty()
9291 );
9292
9293 let outcome =
9294 venue.open_order(limit_request("ioc", Side::Buy, "1", "48000", time_in_force));
9295
9296 assert!(
9297 outcome.events.is_empty(),
9298 "{time_in_force} must move no balance"
9299 );
9300 assert!(
9301 venue.orders_open(&[]).is_empty(),
9302 "{time_in_force} must not rest"
9303 );
9304 match outcome.response.state {
9305 OrderState::Inactive(InactiveOrderState::Cancelled(ref cancelled)) => assert_eq!(
9306 cancelled.filled_quantity,
9307 Some(Decimal::ZERO),
9308 "{time_in_force} traded nothing"
9309 ),
9310 ref other => panic!("{time_in_force} must be cancelled, got: {other:?}"),
9311 }
9312 }
9313 }
9314
9315 #[test]
9317 fn a_marketable_immediate_order_fills_in_full() {
9318 for time_in_force in [TimeInForce::ImmediateOrCancel, TimeInForce::FillOrKill] {
9319 let mut venue = make_market_venue("10", "1000000");
9320 advance(&mut venue, time(1));
9321 assert!(
9322 venue
9323 .apply_market(
9324 &instrument_name(),
9325 MarketObservation::new(traded("49000")),
9326 time(1)
9327 )
9328 .is_empty()
9329 );
9330
9331 let outcome =
9332 venue.open_order(limit_request("ioc", Side::Buy, "1", "50000", time_in_force));
9333
9334 assert!(
9335 matches!(
9336 outcome.response.state,
9337 OrderState::Inactive(InactiveOrderState::FullyFilled(_))
9338 ),
9339 "{time_in_force} must fill, got: {:?}",
9340 outcome.response.state
9341 );
9342 }
9343 }
9344
9345 #[test]
9349 fn a_good_till_date_order_rests_until_its_deadline() {
9350 let mut venue = make_market_venue("10", "1000000");
9351 advance(&mut venue, time(1));
9352
9353 let outcome = venue.open_order(limit_request(
9354 "gtd",
9355 Side::Buy,
9356 "1",
9357 "48000",
9358 TimeInForce::GoodTillDate { expiry: time(9) },
9359 ));
9360
9361 assert_eq!(rested_open(&outcome).filled_quantity, Decimal::ZERO);
9362
9363 advance(&mut venue, time(8));
9365 assert_eq!(venue.orders_open(&[]).len(), 1, "still working at time(8)");
9366 }
9367
9368 #[test]
9370 fn advancing_to_a_deadline_releases_the_reservation_then_reports_the_order() {
9371 let mut venue = make_market_venue("10", "1000000");
9372 advance(&mut venue, time(1));
9373
9374 let outcome = venue.open_order(limit_request(
9375 "gtd",
9376 Side::Buy,
9377 "1",
9378 "48000",
9379 TimeInForce::GoodTillDate { expiry: time(9) },
9380 ));
9381 let reserved = balance_of(&outcome.events[0]).balance.free;
9382
9383 let events = venue.advance_time(time(9));
9384
9385 assert_eq!(events.len(), 2, "one release and one terminal snapshot");
9386 assert_eq!(
9387 balance_of(&events[0]).balance.free,
9388 reserved + d("48000") + d("9.6"),
9389 "the release restates exactly what resting took"
9390 );
9391
9392 let order = order_of(&events[1]);
9393 match order.state {
9394 OrderState::Inactive(InactiveOrderState::Expired(ref expired)) => {
9395 assert_eq!(expired.time_exchange, time(9));
9396 assert_eq!(expired.filled_quantity, Some(Decimal::ZERO));
9397 }
9398 ref other => panic!("the order must be reported expired, got: {other:?}"),
9399 }
9400
9401 assert!(venue.orders_open(&[]).is_empty(), "it must leave the book");
9402 }
9403
9404 #[test]
9409 fn a_deadline_is_reached_at_its_instant_not_after_it() {
9410 let mut venue = make_market_venue("10", "1000000");
9411 advance(&mut venue, time(1));
9412
9413 let _ = venue.open_order(limit_request(
9414 "gtd",
9415 Side::Buy,
9416 "1",
9417 "48000",
9418 TimeInForce::GoodTillDate { expiry: time(5) },
9419 ));
9420
9421 advance(&mut venue, time(4));
9423 assert_eq!(venue.orders_open(&[]).len(), 1);
9424
9425 assert_eq!(venue.advance_time(time(5)).len(), 2);
9427 assert!(venue.orders_open(&[]).is_empty());
9428 }
9429
9430 #[test]
9435 fn a_deadline_is_swept_before_the_tick_that_would_have_filled_it() {
9436 let mut venue = make_market_venue("10", "1000000");
9437 advance(&mut venue, time(1));
9438
9439 let _ = venue.open_order(limit_request(
9440 "gtd",
9441 Side::Buy,
9442 "1",
9443 "48000",
9444 TimeInForce::GoodTillDate { expiry: time(5) },
9445 ));
9446
9447 let events = venue.apply_market(
9450 &instrument_name(),
9451 MarketObservation::new(book("46900", "47000")),
9452 time(5),
9453 );
9454
9455 assert_eq!(events.len(), 2, "a release and an expiry, not a fill");
9456 assert!(
9457 venue.trades(time(0)).is_empty(),
9458 "an expired order must not trade on the tick that retired it"
9459 );
9460 assert!(matches!(
9461 order_of(&events[1]).state,
9462 OrderState::Inactive(InactiveOrderState::Expired(_))
9463 ));
9464 }
9465
9466 #[test]
9471 fn a_good_till_date_order_that_has_already_expired_is_never_rested() {
9472 let mut venue = make_market_venue("10", "1000000");
9473 advance(&mut venue, time(9));
9474
9475 let outcome = venue.open_order(limit_request(
9476 "stale",
9477 Side::Buy,
9478 "1",
9479 "48000",
9480 TimeInForce::GoodTillDate { expiry: time(5) },
9481 ));
9482
9483 assert!(outcome.events.is_empty(), "nothing was reserved");
9484 assert!(venue.orders_open(&[]).is_empty(), "it must not rest");
9485 match outcome.response.state {
9486 OrderState::Inactive(InactiveOrderState::Expired(ref expired)) => {
9487 assert_eq!(expired.time_exchange, time(9), "stamped when it was found");
9488 assert_eq!(expired.filled_quantity, Some(Decimal::ZERO));
9489 }
9490 ref other => panic!("a stale deadline must expire, not {other:?}"),
9491 }
9492 }
9493
9494 #[test]
9501 fn a_stale_deadline_retires_an_order_the_book_would_have_filled() {
9502 let mut venue = make_market_venue("10", "1000000");
9503 advance(&mut venue, time(9));
9504 assert!(
9505 venue
9506 .apply_market(
9507 &instrument_name(),
9508 MarketObservation::new(traded("47000")),
9509 time(9)
9510 )
9511 .is_empty()
9512 );
9513
9514 let outcome = venue.open_order(limit_request(
9515 "stale",
9516 Side::Buy,
9517 "1",
9518 "48000",
9519 TimeInForce::GoodTillDate { expiry: time(5) },
9520 ));
9521
9522 assert!(
9523 matches!(
9524 outcome.response.state,
9525 OrderState::Inactive(InactiveOrderState::Expired(_))
9526 ),
9527 "a marketable order past its deadline must expire, not fill: {:?}",
9528 outcome.response.state
9529 );
9530 assert!(
9531 venue.trades(time(0)).is_empty(),
9532 "it must not trade on its way out"
9533 );
9534 }
9535
9536 #[test]
9538 fn a_cancel_after_a_deadline_is_answered_already_expired() {
9539 let mut venue = make_market_venue("10", "1000000");
9540 advance(&mut venue, time(1));
9541
9542 let _ = venue.open_order(limit_request(
9543 "gtd",
9544 Side::Buy,
9545 "1",
9546 "48000",
9547 TimeInForce::GoodTillDate { expiry: time(5) },
9548 ));
9549 assert_eq!(venue.advance_time(time(6)).len(), 2);
9550
9551 let outcome = venue.cancel_order(OrderEvent {
9552 key: OrderKey {
9553 exchange: EXCHANGE,
9554 instrument: instrument_name(),
9555 strategy: StrategyId::new("test"),
9556 cid: ClientOrderId::new("gtd"),
9557 },
9558 state: RequestCancel { id: None },
9559 });
9560
9561 assert_eq!(
9562 outcome.response.state,
9563 Err(OrderError::Rejected(ApiError::OrderAlreadyExpired)),
9564 "a cancel that lost to a deadline must not read as an unknown order"
9565 );
9566 }
9567
9568 #[test]
9570 fn an_expired_order_enters_the_account_snapshot() {
9571 let mut venue = make_market_venue("10", "1000000");
9572 advance(&mut venue, time(1));
9573
9574 let _ = venue.open_order(limit_request(
9575 "gtd",
9576 Side::Buy,
9577 "1",
9578 "48000",
9579 TimeInForce::GoodTillDate { expiry: time(5) },
9580 ));
9581 assert_eq!(venue.advance_time(time(5)).len(), 2);
9582
9583 let snapshot = venue.account_snapshot();
9584 let orders = &snapshot.instruments[0].orders;
9585 assert_eq!(orders.len(), 1);
9586 assert!(matches!(
9587 orders[0].state,
9588 OrderState::Inactive(InactiveOrderState::Expired(_))
9589 ));
9590 }
9591
9592 #[test]
9594 fn deadlines_are_swept_earliest_first() {
9595 let mut venue = make_market_venue("10", "1000000");
9596 advance(&mut venue, time(1));
9597
9598 for (cid, expiry) in [("late", time(7)), ("early", time(5)), ("middle", time(6))] {
9600 let _ = venue.open_order(limit_request(
9601 cid,
9602 Side::Buy,
9603 "1",
9604 "48000",
9605 TimeInForce::GoodTillDate { expiry },
9606 ));
9607 }
9608
9609 let events = venue.advance_time(time(9));
9610
9611 let swept: Vec<_> = events
9613 .iter()
9614 .skip(1)
9615 .step_by(2)
9616 .map(|event| order_of(event).key.cid.0.to_string())
9617 .collect();
9618
9619 assert_eq!(swept, ["early", "middle", "late"]);
9620 }
9621
9622 #[test]
9625 fn a_deadline_on_a_quiet_instrument_is_swept_by_a_tick_on_another() {
9626 let mut venue = make_market_venue("10", "1000000");
9627 advance(&mut venue, time(1));
9628
9629 let _ = venue.open_order(limit_request(
9630 "gtd",
9631 Side::Buy,
9632 "1",
9633 "48000",
9634 TimeInForce::GoodTillDate { expiry: time(5) },
9635 ));
9636
9637 let elsewhere = InstrumentNameExchange::new("eth_usdt");
9639 let events = venue.apply_market(
9640 &elsewhere,
9641 MarketObservation::new(book("2000", "2001")),
9642 time(6),
9643 );
9644
9645 assert_eq!(
9646 events.len(),
9647 2,
9648 "the quiet instrument's order still retires"
9649 );
9650 assert!(venue.orders_open(&[]).is_empty());
9651 }
9652
9653 #[test]
9655 fn expiring_an_order_seeded_with_no_reservation_restates_no_balance() {
9656 let expiry = time(5);
9657 let seeded = Order {
9658 key: OrderKey {
9659 exchange: EXCHANGE,
9660 instrument: instrument_name(),
9661 strategy: StrategyId::new("test"),
9662 cid: ClientOrderId::new("seeded"),
9663 },
9664 side: Side::Buy,
9665 price: Some(d("48000")),
9666 quantity: d("1"),
9667 kind: OrderKind::Limit,
9668 time_in_force: TimeInForce::GoodTillDate { expiry },
9669 state: OrderState::active(Open::new(
9670 VenueOrderId::Assigned(OrderId::new("seeded")),
9671 time(1),
9672 Decimal::ZERO,
9673 )),
9674 };
9675
9676 let mut venue = make_market_venue("10", "1000000");
9677 assert!(
9678 venue
9679 .account
9680 .book(
9681 as_open(seeded).expect("the seeded order is Open"),
9682 None,
9684 )
9685 .is_ok(),
9686 "the book is vacant under this id"
9687 );
9688
9689 let events = venue.advance_time(expiry);
9690
9691 assert_eq!(events.len(), 1, "the terminal snapshot alone, no release");
9692 assert!(matches!(
9693 order_of(&events[0]).state,
9694 OrderState::Inactive(InactiveOrderState::Expired(_))
9695 ));
9696 }
9697
9698 #[test]
9706 fn a_market_driven_venue_prices_a_market_order_from_its_own_book() {
9707 let mut venue = make_market_venue("10", "1000000");
9708 advance(&mut venue, time(1));
9709 assert!(
9710 venue
9711 .apply_market(
9712 &instrument_name(),
9713 MarketObservation::new(traded("49000")),
9714 time(1)
9715 )
9716 .is_empty()
9717 );
9718
9719 let outcome = venue.open_order(buy_request("1.0", Some(traded("40000"))));
9721
9722 assert_eq!(
9723 filled_price(&outcome.response),
9724 d("49000"),
9725 "the fill is struck against the venue's book, not the snapshot the request carried"
9726 );
9727 }
9728
9729 #[test]
9734 fn a_request_priced_venue_still_prices_a_market_order_from_the_request() {
9735 let mut venue = make_venue("10", "1000000");
9736
9737 let outcome = venue.open_order(buy_request("1.0", Some(traded("40000"))));
9738
9739 assert_eq!(
9740 filled_price(&outcome.response),
9741 d("40000"),
9742 "with no book of its own, the request's snapshot is still the only price source"
9743 );
9744 }
9745
9746 #[test]
9753 fn a_market_driven_venue_with_no_book_rejects_a_market_order_rather_than_using_the_request() {
9754 let mut venue = make_market_venue("10", "1000000");
9755 advance(&mut venue, time(1));
9756
9757 let outcome = venue.open_order(buy_request("1.0", Some(traded("49000"))));
9758
9759 assert!(outcome.events.is_empty(), "nothing was debited or traded");
9760 match outcome.response.state {
9761 OrderState::Inactive(InactiveOrderState::OpenFailed(
9762 UnindexedOrderError::Rejected(ApiError::OrderRejected(ref reason)),
9763 )) => assert!(
9764 reason.contains("no market snapshot"),
9765 "the rejection must name the venue's absent market, got: {reason}"
9766 ),
9767 ref other => panic!("an unpriceable market order must be rejected, got: {other:?}"),
9768 }
9769 }
9770
9771 fn sized(amount: &str) -> MarketDepth {
9777 MarketDepth::new(Some(d(amount)), Some(d(amount)))
9778 }
9779
9780 fn venue_offering(depth: MarketDepth) -> SimulatedVenue {
9784 venue_offering_funded("1000000", depth)
9785 }
9786
9787 fn venue_offering_funded(usdt: &str, depth: MarketDepth) -> SimulatedVenue {
9788 let mut venue = make_market_venue_with(
9789 "10",
9790 usdt,
9791 maker_taker_fee(),
9792 SimFillConfig::BidAsk(BidAskFillModel),
9793 );
9794 advance(&mut venue, time(1));
9795 assert!(
9796 venue
9797 .apply_market(
9798 &instrument_name(),
9799 MarketObservation::new(book("47800", "47900")).with_depth(depth),
9800 time(1)
9801 )
9802 .is_empty(),
9803 "an empty book fills nothing"
9804 );
9805 venue
9806 }
9807
9808 fn free_quote(venue: &SimulatedVenue) -> Decimal {
9809 venue.balances(&[quote()])[0].balance.free
9810 }
9811
9812 fn total_quote(venue: &SimulatedVenue) -> Decimal {
9813 venue.balances(&[quote()])[0].balance.total
9814 }
9815
9816 #[test]
9818 fn a_market_order_fills_what_the_book_has_and_cancels_what_it_has_not() {
9819 let mut venue = venue_offering(sized("0.4"));
9820
9821 let outcome = venue.open_order(buy_request("1", None));
9822
9823 assert_eq!(
9824 outcome.events.len(),
9825 3,
9826 "a fill owes both balances, then the trade"
9827 );
9828 let trade = filled_trade(&outcome);
9829 assert_eq!(
9830 trade.quantity,
9831 d("0.4"),
9832 "it took the whole offer and no more"
9833 );
9834 assert_eq!(trade.price, d("47900"));
9835
9836 match &outcome.response.state {
9837 OrderState::Inactive(InactiveOrderState::Cancelled(cancelled)) => assert_eq!(
9838 cancelled.filled_quantity,
9839 Some(d("0.4")),
9840 "the remainder retires carrying what did trade"
9841 ),
9842 other => panic!("a market order's unfillable remainder is cancelled, got: {other:?}"),
9843 }
9844 assert!(
9845 venue.orders_open(&[]).is_empty(),
9846 "a market order has no price to rest the remainder at"
9847 );
9848 }
9849
9850 #[test]
9852 fn fill_or_kill_refuses_the_partial_fill_immediate_or_cancel_accepts() {
9853 let mut immediate = venue_offering(sized("0.4"));
9855 let mut all_or_nothing = venue_offering(sized("0.4"));
9856
9857 let ioc = immediate.open_order(limit_request(
9858 "ioc",
9859 Side::Buy,
9860 "1",
9861 "48000",
9862 TimeInForce::ImmediateOrCancel,
9863 ));
9864 let fok = all_or_nothing.open_order(limit_request(
9865 "fok",
9866 Side::Buy,
9867 "1",
9868 "48000",
9869 TimeInForce::FillOrKill,
9870 ));
9871
9872 assert_eq!(
9873 filled_trade(&ioc).quantity,
9874 d("0.4"),
9875 "immediate-or-cancel keeps what the book could give it"
9876 );
9877
9878 assert!(
9879 fok.events.is_empty(),
9880 "fill-or-kill would rather trade nothing than trade in part"
9881 );
9882 assert!(
9883 all_or_nothing.trades(time(0)).is_empty(),
9884 "and nothing reached the ledger either"
9885 );
9886 assert_eq!(
9887 free_quote(&all_or_nothing),
9888 d("1000000"),
9889 "a killed order moves no balance"
9890 );
9891 }
9892
9893 #[test]
9895 fn a_capped_limit_order_fills_what_it_can_and_rests_the_remainder() {
9896 let mut venue = venue_offering(sized("0.4"));
9897
9898 let outcome = venue.open_order(limit_request("split", Side::Buy, "1", "48000", gtc()));
9899
9900 assert_eq!(
9901 outcome.events.len(),
9902 3,
9903 "one arrival restates each asset once and prints one trade, even settling and holding \
9904 at once"
9905 );
9906 let trade = filled_trade(&outcome);
9907 assert_eq!(trade.quantity, d("0.4"));
9908 assert_eq!(
9909 trade.price,
9910 d("47900"),
9911 "the part that traded took the offer"
9912 );
9913
9914 let open = rested_open(&outcome);
9915 assert_eq!(
9916 open.filled_quantity,
9917 d("0.4"),
9918 "the remainder rests carrying what has already traded"
9919 );
9920 assert_eq!(open.quantity_remaining(d("1")), d("0.6"));
9921 assert_eq!(venue.orders_open(&[]).len(), 1);
9922
9923 assert_eq!(total_quote(&venue), d("1000000") - d("19179.16"));
9925 assert_eq!(
9926 free_quote(&venue),
9927 d("1000000") - d("19179.16") - d("28805.76")
9928 );
9929 }
9930
9931 #[test]
9935 fn a_capped_spot_sell_settles_and_holds_base_and_receives_quote_for_what_traded() {
9936 let mut venue = venue_offering(sized("0.4"));
9937
9938 let outcome = venue.open_order(limit_request("split", Side::Sell, "1", "47700", gtc()));
9939
9940 assert_eq!(
9941 outcome.events.len(),
9942 3,
9943 "the base paid, the quote received, then the trade"
9944 );
9945 let trade = filled_trade(&outcome);
9946 assert_eq!(trade.quantity, d("0.4"));
9947 assert_eq!(trade.price, d("47800"), "the part that traded took the bid");
9948
9949 let paid = filled_balance(&outcome);
9951 assert_eq!(paid.asset, base());
9952 assert_eq!(paid.balance, Balance::new(d("9.6"), d("9")));
9953
9954 let received = filled_received(&outcome);
9956 assert_eq!(received.asset, quote());
9957 assert_eq!(
9958 received.balance,
9959 Balance::new(d("1019100.88"), d("1019100.88"))
9960 );
9961 assert_eq!(rested_open(&outcome).quantity_remaining(d("1")), d("0.6"));
9962 }
9963
9964 #[test]
9967 fn an_order_filled_in_part_then_crossed_settles_the_remainder_and_no_more() {
9968 let mut venue = venue_offering(sized("0.4"));
9969
9970 let arrival = venue.open_order(limit_request("split", Side::Buy, "1", "48000", gtc()));
9971 let taker = filled_trade(&arrival).clone();
9972
9973 let fills = venue.apply_market(
9975 &instrument_name(),
9976 MarketObservation::new(book("48000", "48000")),
9977 time(2),
9978 );
9979 assert_eq!(
9980 fills.len(),
9981 4,
9982 "a resting fill owes both balances, the trade, the order"
9983 );
9984 let maker = trade_of(&fills[2]);
9985
9986 assert_eq!(maker.quantity, d("0.6"), "only the remainder crosses");
9987 assert_eq!(
9988 maker.price,
9989 d("48000"),
9990 "a maker is paid the price it quoted, not the one the book moved to"
9991 );
9992 assert_eq!(
9993 taker.quantity + maker.quantity,
9994 d("1"),
9995 "the two fills are the whole order, and no more than it"
9996 );
9997
9998 assert_ne!(
9999 taker.id, maker.id,
10000 "one order printing twice must print two distinguishable trades"
10001 );
10002 assert_eq!(
10003 taker.order_id, maker.order_id,
10004 "and both must still say which order they belong to"
10005 );
10006
10007 let spent = d("19179.16") + d("28805.76");
10009 assert_eq!(total_quote(&venue), d("1000000") - spent);
10010 assert_eq!(
10011 free_quote(&venue),
10012 d("1000000") - spent,
10013 "the order is done, so nothing is still held against it"
10014 );
10015 }
10016
10017 #[test]
10019 fn a_second_order_on_one_observation_sees_only_what_the_first_left() {
10020 let mut venue = venue_offering(sized("1"));
10021
10022 let first = venue.open_order(limit_request(
10023 "first",
10024 Side::Buy,
10025 "0.6",
10026 "48000",
10027 TimeInForce::ImmediateOrCancel,
10028 ));
10029 assert_eq!(filled_trade(&first).quantity, d("0.6"));
10030
10031 let second = venue.open_order(limit_request(
10032 "second",
10033 Side::Buy,
10034 "0.6",
10035 "48000",
10036 TimeInForce::ImmediateOrCancel,
10037 ));
10038 assert_eq!(
10039 filled_trade(&second).quantity,
10040 d("0.4"),
10041 "the offer was displayed once, so the two of them share it"
10042 );
10043
10044 let third = venue.open_order(limit_request(
10045 "third",
10046 Side::Buy,
10047 "0.6",
10048 "48000",
10049 TimeInForce::ImmediateOrCancel,
10050 ));
10051 assert!(
10052 third.events.is_empty(),
10053 "the offer is exhausted until the next observation replaces it"
10054 );
10055 }
10056
10057 #[test]
10059 fn a_new_observation_replaces_the_remaining_size_rather_than_adding_to_it() {
10060 let mut venue = venue_offering(sized("1"));
10061
10062 let taken = venue.open_order(limit_request(
10063 "first",
10064 Side::Buy,
10065 "0.6",
10066 "48000",
10067 TimeInForce::ImmediateOrCancel,
10068 ));
10069 assert_eq!(filled_trade(&taken).quantity, d("0.6"));
10070
10071 assert!(
10072 venue
10073 .apply_market(
10074 &instrument_name(),
10075 MarketObservation::new(book("47800", "47900")).with_depth(sized("1")),
10076 time(2)
10077 )
10078 .is_empty()
10079 );
10080
10081 let after = venue.open_order(limit_request(
10082 "second",
10083 Side::Buy,
10084 "1",
10085 "48000",
10086 TimeInForce::ImmediateOrCancel,
10087 ));
10088 assert_eq!(
10089 filled_trade(&after).quantity,
10090 d("1"),
10091 "the fresh observation offers a whole unit again"
10092 );
10093 }
10094
10095 #[test]
10097 fn a_feed_supplying_no_size_caps_nothing() {
10098 let mut venue = venue_offering(MarketDepth::UNKNOWN);
10099
10100 let outcome = venue.open_order(limit_request(
10101 "uncapped",
10102 Side::Buy,
10103 "5",
10104 "48000",
10105 TimeInForce::ImmediateOrCancel,
10106 ));
10107
10108 assert_eq!(
10109 filled_trade(&outcome).quantity,
10110 d("5"),
10111 "an absent size caps nothing, however large the order"
10112 );
10113 }
10114
10115 #[test]
10117 fn a_marketable_order_with_nothing_on_offer_rests_rather_than_filling() {
10118 let mut venue = venue_offering(MarketDepth::new(Some(d("1")), Some(Decimal::ZERO)));
10119
10120 let outcome = venue.open_order(limit_request("nothing", Side::Buy, "1", "48000", gtc()));
10121
10122 assert_eq!(
10123 outcome.events.len(),
10124 1,
10125 "nothing traded, so it owes only the hold it took"
10126 );
10127 assert_eq!(rested_open(&outcome).filled_quantity, Decimal::ZERO);
10128 }
10129
10130 #[test]
10135 fn an_order_that_cannot_fund_both_legs_is_refused_with_nothing_moved() {
10136 const FUNDED: &str = "47950";
10138
10139 let mut uncapped = venue_offering_funded(FUNDED, MarketDepth::UNKNOWN);
10140 let affordable =
10141 uncapped.open_order(limit_request("whole", Side::Buy, "1", "48000", gtc()));
10142 assert_eq!(
10143 filled_trade(&affordable).quantity,
10144 d("1"),
10145 "uncapped, this very order is affordable and fills outright"
10146 );
10147
10148 let mut venue = venue_offering_funded(FUNDED, sized("0.4"));
10149 let outcome = venue.open_order(limit_request("split", Side::Buy, "1", "48000", gtc()));
10150
10151 assert!(outcome.events.is_empty(), "a refused order moves nothing");
10152 assert!(
10153 matches!(
10154 outcome.response.state,
10155 OrderState::Inactive(InactiveOrderState::OpenFailed(_))
10156 ),
10157 "and says so, rather than filling the part it could afford: {:?}",
10158 outcome.response.state
10159 );
10160 assert_eq!(free_quote(&venue), d(FUNDED), "the ledger is as it was");
10161 assert_eq!(total_quote(&venue), d(FUNDED));
10162 assert!(venue.trades(time(0)).is_empty(), "and nothing traded");
10163 assert!(venue.orders_open(&[]).is_empty());
10164 }
10165
10166 fn is_duplicate_cid(state: &UnindexedOrderState) -> bool {
10167 matches!(
10168 state,
10169 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
10170 ApiError::DuplicateClientOrderId(_)
10171 )))
10172 )
10173 }
10174
10175 #[test]
10178 fn an_open_under_a_resting_id_is_rejected_and_the_resting_order_is_untouched() {
10179 let (mut venue, _) = venue_resting_one_buy("48000");
10180 let free = free_quote(&venue);
10181
10182 let second = venue.open_order(limit_request("resting", Side::Buy, "0.5", "47000", gtc()));
10184
10185 assert!(
10186 is_duplicate_cid(&second.response.state),
10187 "rejected as a duplicate: {:?}",
10188 second.response.state
10189 );
10190 assert!(second.events.is_empty(), "a rejection moves nothing");
10191 let [resting] = &venue.orders_open(&[])[..] else {
10192 panic!("one cid, one resting order");
10193 };
10194 assert_eq!(
10195 (resting.quantity, resting.price),
10196 (d("1"), Some(d("48000"))),
10197 "the resting order is the first one"
10198 );
10199 assert_eq!(
10200 free_quote(&venue),
10201 free,
10202 "its hold is still held, and only its"
10203 );
10204 assert_eq!(total_quote(&venue), d("1000000"));
10205 }
10206
10207 #[test]
10210 fn a_market_order_under_a_resting_id_is_rejected_before_it_trades() {
10211 let (mut venue, _) = venue_resting_one_buy("48000");
10212 let mut market = buy_request("0.1", None);
10213 market.key.cid = ClientOrderId::new("resting");
10214
10215 let outcome = venue.open_order(market);
10216
10217 assert!(
10218 is_duplicate_cid(&outcome.response.state),
10219 "rejected as a duplicate: {:?}",
10220 outcome.response.state
10221 );
10222 assert!(venue.trades(time(0)).is_empty(), "nothing traded");
10223 assert_eq!(venue.orders_open(&[]).len(), 1);
10224 }
10225
10226 #[test]
10228 fn a_malformed_open_under_a_resting_id_is_rejected_as_malformed() {
10229 let (mut venue, _) = venue_resting_one_buy("48000");
10230
10231 let outcome = venue.open_order(limit_request(
10232 "resting",
10233 Side::Buy,
10234 "1",
10235 "47000",
10236 TimeInForce::AtOpen,
10237 ));
10238
10239 assert!(
10240 matches!(
10241 outcome.response.state,
10242 OrderState::Inactive(InactiveOrderState::OpenFailed(_))
10243 ) && !is_duplicate_cid(&outcome.response.state),
10244 "rejected on its time in force: {:?}",
10245 outcome.response.state
10246 );
10247 }
10248
10249 #[test]
10252 fn a_cancelled_orders_id_is_free_again_and_the_latest_order_is_reported() {
10253 let (mut venue, _) = venue_resting_one_buy("48000");
10254 let cancel = |venue: &mut SimulatedVenue| {
10255 venue.cancel_order(OrderRequestCancel {
10256 key: limit_request("resting", Side::Buy, "1", "48000", gtc()).key,
10257 state: RequestCancel { id: None },
10258 })
10259 };
10260 assert!(cancel(&mut venue).response.state.is_ok());
10261
10262 let second = venue.open_order(limit_request("resting", Side::Buy, "0.5", "47000", gtc()));
10263
10264 assert!(
10265 matches!(
10266 second.response.state,
10267 OrderState::Active(ActiveOrderState::Open(_))
10268 ),
10269 "the id is free again: {:?}",
10270 second.response.state
10271 );
10272 assert!(
10273 venue.orders_ended(&[key_of("resting")]).is_empty(),
10274 "the order under the id is working, whatever became of the one before it"
10275 );
10276 let snapshot = venue.account_snapshot();
10277 let listed = snapshot
10278 .instruments
10279 .iter()
10280 .flat_map(|instrument| &instrument.orders)
10281 .filter(|order| order.key.cid == ClientOrderId::new("resting"))
10282 .count();
10283 assert_eq!(
10284 listed, 1,
10285 "a snapshot lists the id once, as the working order"
10286 );
10287
10288 assert!(
10289 cancel(&mut venue).response.state.is_ok(),
10290 "and the cancel reaches it"
10291 );
10292 let [ended] = &venue.orders_ended(&[key_of("resting")])[..] else {
10293 panic!("the second order has ended");
10294 };
10295 assert_eq!(ended.quantity, d("0.5"), "the latest order under the id");
10296 }
10297
10298 #[test]
10301 fn a_filled_orders_id_is_free_again() {
10302 let (mut venue, _) = venue_resting_one_buy("48000");
10303 let filled = venue.open_order(limit_request("reused", Side::Buy, "1", "49100", gtc()));
10304 assert!(
10305 matches!(
10306 filled.response.state,
10307 OrderState::Inactive(InactiveOrderState::FullyFilled(_))
10308 ),
10309 "crosses the ask, so fills on arrival"
10310 );
10311
10312 let second = venue.open_order(limit_request("reused", Side::Buy, "1", "47000", gtc()));
10313 assert!(
10314 matches!(
10315 second.response.state,
10316 OrderState::Active(ActiveOrderState::Open(_))
10317 ),
10318 "the id is free again: {:?}",
10319 second.response.state
10320 );
10321
10322 let cancelled = venue.cancel_order(OrderRequestCancel {
10323 key: limit_request("reused", Side::Buy, "1", "47000", gtc()).key,
10324 state: RequestCancel { id: None },
10325 });
10326 assert!(
10327 cancelled.response.state.is_ok(),
10328 "the cancel reaches the working order: {:?}",
10329 cancelled.response.state
10330 );
10331 }
10332
10333 fn key_of(cid: &str) -> UnindexedOrderKey {
10336 OrderKey {
10337 exchange: EXCHANGE,
10338 instrument: instrument_name(),
10339 strategy: StrategyId::new("asker"),
10340 cid: ClientOrderId::new(cid),
10341 }
10342 }
10343
10344 #[test]
10347 fn orders_ended_reports_each_ended_order_and_omits_the_rest() {
10348 let mut venue = make_market_venue("10", "1000000");
10349 advance(&mut venue, time(1));
10350 assert!(
10351 venue
10352 .apply_market(
10353 &instrument_name(),
10354 MarketObservation::new(book("49000", "49100")),
10355 time(1)
10356 )
10357 .is_empty()
10358 );
10359
10360 let filled = venue.open_order(limit_request("filled", Side::Buy, "1", "49100", gtc()));
10361 assert!(
10362 matches!(
10363 filled.response.state,
10364 OrderState::Inactive(InactiveOrderState::FullyFilled(_))
10365 ),
10366 "crosses the ask, so fills on arrival"
10367 );
10368 let _ = venue.open_order(limit_request("working", Side::Buy, "1", "48000", gtc()));
10369 let _ = venue.open_order(limit_request("cancelled", Side::Buy, "1", "47500", gtc()));
10370 let _ = venue.cancel_order(OrderEvent {
10371 key: key_of("cancelled"),
10372 state: RequestCancel { id: None },
10373 });
10374 let _ = venue.open_order(limit_request(
10375 "expired",
10376 Side::Buy,
10377 "1",
10378 "47000",
10379 TimeInForce::GoodTillDate { expiry: time(9) },
10380 ));
10381 assert_eq!(
10382 venue.advance_time(time(9)).len(),
10383 2,
10384 "the deadline retires it"
10385 );
10386
10387 let asked = ["filled", "working", "cancelled", "expired", "unknown"].map(key_of);
10388 let ended = venue.orders_ended(&asked);
10389
10390 assert_eq!(ended.len(), 3, "exactly the three ended orders: {ended:?}");
10391 assert!(
10392 ended
10393 .iter()
10394 .all(|order| order.key.strategy == StrategyId::new("asker")),
10395 "each comes back under the key it was asked for"
10396 );
10397 let ended_as = |cid: &str| {
10398 let Some(order) = ended
10399 .iter()
10400 .find(|order| order.key.cid == ClientOrderId::new(cid))
10401 else {
10402 panic!("{cid} is reported: {ended:?}");
10403 };
10404 order
10405 };
10406 let (filled, cancelled, expired) = (
10407 ended_as("filled"),
10408 ended_as("cancelled"),
10409 ended_as("expired"),
10410 );
10411 match &filled.state {
10412 InactiveOrderState::FullyFilled(state) => {
10413 assert_eq!(state.filled_quantity, d("1"));
10414 assert_eq!(state.avg_price, Some(d("49100")));
10415 }
10416 other => panic!("filled, got {other:?}"),
10417 }
10418 match &cancelled.state {
10419 InactiveOrderState::Cancelled(state) => {
10420 assert_eq!(state.filled_quantity, Some(Decimal::ZERO))
10421 }
10422 other => panic!("cancelled, got {other:?}"),
10423 }
10424 match &expired.state {
10425 InactiveOrderState::Expired(state) => assert_eq!(state.time_exchange, time(9)),
10426 other => panic!("expired, got {other:?}"),
10427 }
10428
10429 assert!(
10430 venue.orders_ended(&[]).is_empty(),
10431 "an empty request asks about nothing, not about everything"
10432 );
10433 }
10434
10435 #[test]
10438 fn orders_ended_reports_a_market_remainder_as_cancelled_with_its_fill() {
10439 let mut venue = venue_offering(sized("0.4"));
10440 let outcome = venue.open_order(buy_request("1", None));
10441
10442 let ended = venue.orders_ended(std::slice::from_ref(&outcome.response.key));
10443
10444 let [order] = ended.as_slice() else {
10445 panic!("the remainder's order is reported: {ended:?}");
10446 };
10447 match &order.state {
10448 InactiveOrderState::Cancelled(state) => {
10449 assert_eq!(state.filled_quantity, Some(d("0.4")))
10450 }
10451 other => panic!("cancelled carrying its fill, got {other:?}"),
10452 }
10453 }
10454
10455 #[test]
10458 fn orders_ended_reports_a_resting_fill_and_its_average_price_as_the_fill_did() {
10459 let (mut venue, _) = venue_resting_one_buy("48000");
10460 assert!(
10461 venue
10462 .account
10463 .book(seeded_part_filled("48000", "1", "0.4"), None)
10464 .is_ok(),
10465 "the book is vacant under this id"
10466 );
10467 advance(&mut venue, time(2));
10468 let events = venue.apply_market(
10469 &instrument_name(),
10470 MarketObservation::new(book("47800", "47900")),
10471 time(2),
10472 );
10473 assert_eq!(
10474 events.len(),
10475 8,
10476 "two fills, each two balances, a trade and an order"
10477 );
10478
10479 let ended = venue.orders_ended(&[key_of("resting"), key_of("part_filled")]);
10480
10481 let avg_price_of = |cid: &str| {
10482 let Some(order) = ended
10483 .iter()
10484 .find(|order| order.key.cid == ClientOrderId::new(cid))
10485 else {
10486 panic!("{cid} is reported: {ended:?}");
10487 };
10488 match &order.state {
10489 InactiveOrderState::FullyFilled(filled) => {
10490 assert_eq!(filled.filled_quantity, d("1"));
10491 filled.avg_price
10492 }
10493 other => panic!("{cid} filled, got {other:?}"),
10494 }
10495 };
10496 assert_eq!(avg_price_of("resting"), Some(d("48000")));
10497 assert_eq!(
10498 avg_price_of("part_filled"),
10499 None,
10500 "this venue saw only one of the two fills behind it"
10501 );
10502 }
10503
10504 #[test]
10506 fn orders_ended_reports_an_order_the_initial_state_seeded_as_cancelled() {
10507 let seeded = Order {
10508 key: key_of("seeded"),
10509 side: Side::Buy,
10510 price: Some(d("48000")),
10511 quantity: d("1"),
10512 kind: OrderKind::Limit,
10513 time_in_force: gtc(),
10514 state: OrderState::inactive(Cancelled {
10515 id: OrderId::new("seeded"),
10516 time_exchange: time(0),
10517 filled_quantity: Some(d("0.25")),
10518 }),
10519 };
10520 let venue = SimulatedVenue::new(
10521 &spot_config_holding("10", "1000000", seeded),
10522 spot_instruments(),
10523 );
10524
10525 let ended = venue.orders_ended(&[key_of("seeded")]);
10526
10527 let [order] = ended.as_slice() else {
10528 panic!("the seeded cancel is reported: {ended:?}");
10529 };
10530 match &order.state {
10531 InactiveOrderState::Cancelled(cancelled) => {
10532 assert_eq!(cancelled.filled_quantity, Some(d("0.25")))
10533 }
10534 other => panic!("cancelled, got {other:?}"),
10535 }
10536 }
10537
10538 #[test]
10541 fn orders_ended_reports_an_id_once_and_only_for_its_own_instrument() {
10542 let mut venue = venue_offering(sized("0.4"));
10543 let outcome = venue.open_order(buy_request("1", None));
10544 let key = outcome.response.key.clone();
10545 let other_instrument = OrderKey {
10546 instrument: InstrumentNameExchange::new("ETHUSDT"),
10547 ..key.clone()
10548 };
10549 let second_ask = OrderKey {
10550 strategy: StrategyId::new("second"),
10551 ..key.clone()
10552 };
10553
10554 let ended = venue.orders_ended(&[key.clone(), second_ask]);
10555 let [order] = ended.as_slice() else {
10556 panic!("one entry for one id: {ended:?}");
10557 };
10558 assert_eq!(order.key, key, "the first key that finds it");
10559
10560 let ended = venue.orders_ended(&[other_instrument.clone(), key.clone()]);
10561 let [order] = ended.as_slice() else {
10562 panic!("a key that finds nothing does not stop a later one: {ended:?}");
10563 };
10564 assert_eq!(order.key, key);
10565
10566 assert!(
10567 venue.orders_ended(&[other_instrument]).is_empty(),
10568 "a key for another instrument does not find it"
10569 );
10570 }
10571
10572 fn hourly_funding(rate: &str) -> Arc<dyn HoldingCostModel> {
10576 Arc::new(
10577 FundingModel::new(TimeDelta::hours(1))
10578 .with_rates(cfd_instrument_name(), RateSeries::constant(d(rate))),
10579 )
10580 }
10581
10582 fn boundary(n: i64) -> DateTime<Utc> {
10584 time(2800 + 3600 * n)
10585 }
10586
10587 fn make_cfd_venue_charging(
10589 usd: &str,
10590 models: Vec<Arc<dyn HoldingCostModel>>,
10591 ) -> SimulatedVenue {
10592 let mut venue = SimulatedVenue::new_market_driven(
10593 &config_from_balances(vec![funded(self::usd(), d(usd))], FeeModelConfig::default()),
10594 instruments_of(cfd_instrument()),
10595 )
10596 .with_holding_costs(models);
10597 advance(&mut venue, time(1));
10598 venue
10599 }
10600
10601 fn charges(
10603 events: &[UnindexedAccountEvent],
10604 ) -> Vec<(
10605 &AssetBalance<AssetNameExchange>,
10606 &CashFlow<AssetNameExchange, InstrumentNameExchange>,
10607 )> {
10608 sans_debt(events)
10609 .chunks(2)
10610 .map(
10611 |pair| match (&pair[0].kind, pair.get(1).map(|event| &event.kind)) {
10612 (
10613 AccountEventKind::BalanceSnapshot(balance),
10614 Some(AccountEventKind::CashFlow(flow)),
10615 ) => (&balance.0, flow),
10616 other => panic!("a charge is [balance, cash flow], got: {other:?}"),
10617 },
10618 )
10619 .collect()
10620 }
10621
10622 #[test]
10626 fn a_position_held_across_a_boundary_is_charged_there_as_balance_then_cash_flow() {
10627 let mut venue = make_cfd_venue_charging("200000", vec![hourly_funding("0.0001")]);
10628 assert!(cfd_market(&mut venue, "4990", "5010", "4000").is_empty());
10629 cfd_market_fill(&mut venue, Side::Buy, "1");
10630 assert_eq!(free_usd(&venue), d("100000"));
10631
10632 let events = venue.advance_time(boundary(0));
10633 let charged = charges(&events);
10634
10635 assert_eq!(charged.len(), 1);
10636 let (balance, flow) = charged[0];
10637 assert_eq!(balance.asset, usd());
10638 assert_eq!(balance.balance, Balance::new(d("99987.5"), d("99987.5")));
10639 assert_eq!(balance.time_exchange, boundary(0));
10640 assert_eq!(
10641 *flow,
10642 CashFlow::new(
10643 CashFlowKind::Funding {
10644 rate: Some(d("0.0001")),
10645 position_quantity: Some(d("1")),
10646 },
10647 usd(),
10648 d("-12.5"),
10649 Some(cfd_instrument_name()),
10650 boundary(0),
10651 Some(CashFlowId::new("0")),
10652 )
10653 );
10654 assert_eq!(total_usd(&venue), d("99987.5"));
10655 }
10656
10657 #[test]
10660 fn a_gap_of_several_boundaries_is_caught_up_once_each() {
10661 let mut venue = make_cfd_venue_charging("200000", vec![hourly_funding("0.0001")]);
10662 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
10663 cfd_market_fill(&mut venue, Side::Sell, "1");
10664
10665 let events = venue.advance_time(boundary(2) + TimeDelta::seconds(1));
10666 let charged = charges(&events);
10667
10668 let stamps: Vec<_> = charged.iter().map(|(_, flow)| flow.time_exchange).collect();
10669 assert_eq!(stamps, vec![boundary(0), boundary(1), boundary(2)]);
10670 let ids: Vec<_> = charged.iter().map(|(_, flow)| flow.id.clone()).collect();
10671 assert_eq!(
10672 ids,
10673 ["0", "1", "2"].map(|id| Some(CashFlowId::new(id))).to_vec()
10674 );
10675 assert!(
10676 charged.iter().all(|(_, flow)| flow.amount == d("12.5")),
10677 "a short receives a positive funding rate"
10678 );
10679 assert_eq!(free_usd(&venue), d("75000") + d("37.5"));
10680
10681 assert!(
10682 venue
10683 .advance_time(boundary(2) + TimeDelta::seconds(1))
10684 .is_empty()
10685 );
10686 assert!(
10687 venue.advance_time(boundary(1)).is_empty(),
10688 "the clock moving back"
10689 );
10690 assert_eq!(free_usd(&venue), d("75000") + d("37.5"));
10691 }
10692
10693 #[test]
10696 fn an_earlier_instant_moves_neither_the_clock_nor_the_accrual_back() {
10697 let mut venue = make_cfd_venue_charging("200000", vec![hourly_funding("0.0001")]);
10698 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
10699 cfd_market_fill(&mut venue, Side::Sell, "1");
10700 let reached = boundary(1) + TimeDelta::seconds(1);
10701 assert_eq!(charges(&venue.advance_time(reached)).len(), 2);
10702 let free = free_usd(&venue);
10703
10704 assert!(
10705 venue.advance_time(boundary(0)).is_empty(),
10706 "nothing is charged again"
10707 );
10708 assert_eq!(venue.time_exchange(), reached);
10709 assert!(
10710 venue
10711 .account
10712 .balances()
10713 .all(|balance| balance.time_exchange == reached),
10714 "no balance is restamped earlier than the venue has reported"
10715 );
10716 assert_eq!(free_usd(&venue), free);
10717
10718 let events = venue.advance_time(boundary(2));
10719 let stamps: Vec<_> = charges(&events)
10720 .iter()
10721 .map(|(_, flow)| flow.time_exchange)
10722 .collect();
10723 assert_eq!(stamps, vec![boundary(2)]);
10724 }
10725
10726 #[test]
10729 fn charging_starts_at_the_first_clock_move_and_values_at_entry_without_a_market() {
10730 let mut venue = make_cfd_venue("200000", FeeModelConfig::default())
10731 .with_holding_costs(vec![hourly_funding("0.0001")]);
10732 cfd_fill(&mut venue, Side::Buy, "1", "4000");
10733
10734 assert!(
10735 venue
10736 .advance_time(boundary(0) + TimeDelta::seconds(1))
10737 .is_empty(),
10738 "the first move charges nothing before it"
10739 );
10740
10741 let events = venue.advance_time(boundary(1));
10742 let charged = charges(&events);
10743 assert_eq!(charged.len(), 1);
10744 assert_eq!(charged[0].1.amount, d("-10"));
10746 }
10747
10748 #[test]
10751 fn a_position_opened_at_a_boundary_is_not_charged_for_it_and_one_closed_there_is() {
10752 let mut venue = make_cfd_venue_charging("200000", vec![hourly_funding("0.0001")]);
10753 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
10754
10755 assert!(venue.advance_time(boundary(0)).is_empty());
10756 cfd_market_fill(&mut venue, Side::Buy, "1");
10757
10758 let events = venue.advance_time(boundary(1));
10759 let stamps: Vec<_> = charges(&events)
10760 .iter()
10761 .map(|(_, flow)| flow.time_exchange)
10762 .collect();
10763 assert_eq!(stamps, vec![boundary(1)]);
10764
10765 cfd_market_fill(&mut venue, Side::Sell, "1");
10766 assert!(venue.advance_time(boundary(3)).is_empty(), "flat");
10767 }
10768
10769 #[test]
10771 fn a_market_tick_past_a_boundary_charges_it_at_the_market_before_the_tick() {
10772 let mut venue = make_cfd_venue_charging("200000", vec![hourly_funding("0.0001")]);
10773 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
10774 cfd_market_fill(&mut venue, Side::Buy, "1");
10775
10776 let six_thousand = MarketSnapshot {
10777 best_bid: Some(d("6000")),
10778 best_ask: Some(d("6000")),
10779 last_price: Some(d("6000")),
10780 };
10781 let events = venue.apply_market(
10782 &cfd_instrument_name(),
10783 MarketObservation::new(six_thousand),
10784 boundary(0) + TimeDelta::seconds(1),
10785 );
10786
10787 let charged = charges(&events);
10788 assert_eq!(charged.len(), 1);
10789 assert_eq!(charged[0].1.amount, d("-12.5"), "valued at 5000, not 6000");
10790 }
10791
10792 #[test]
10795 fn a_charge_the_account_cannot_cover_takes_free_below_zero() {
10796 let mut venue = make_cfd_venue_charging("125000", vec![hourly_funding("0.0001")]);
10797 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
10798 cfd_market_fill(&mut venue, Side::Buy, "1");
10799 assert_eq!(free_usd(&venue), Decimal::ZERO);
10800
10801 let events = venue.advance_time(boundary(0));
10802
10803 assert_eq!(
10804 charges(&events)[0].0.balance,
10805 Balance::new(d("-12.5"), d("-12.5"))
10806 );
10807 }
10808
10809 fn warnings<T>(f: impl FnOnce() -> T) -> (T, Vec<String>) {
10811 let (value, events) = rustrade_instrument::test_utils::logs::capture(f);
10812 let warnings = events
10813 .iter()
10814 .filter(|event| event.level == tracing::Level::WARN)
10815 .map(|event| event.message().to_owned())
10816 .collect();
10817 (value, warnings)
10818 }
10819
10820 #[test]
10823 fn a_negative_balance_is_warned_of_on_the_charge_that_crosses_zero() {
10824 let rates = RateSeries::new(vec![
10826 (DateTime::<Utc>::MIN_UTC, d("0.0001")),
10827 (boundary(3), d("-0.00001")),
10828 ])
10829 .unwrap();
10830 let funding =
10831 FundingModel::new(TimeDelta::hours(1)).with_rates(cfd_instrument_name(), rates);
10832 let mut venue = make_cfd_venue_charging("125000", vec![Arc::new(funding)]);
10833 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
10834 cfd_market_fill(&mut venue, Side::Buy, "1");
10835
10836 let (events, logged) = warnings(|| venue.advance_time(boundary(2)));
10837 assert_eq!(charges(&events).len(), 3, "three charges, all below zero");
10838 assert_eq!(logged.len(), 1, "{logged:?}");
10839 assert!(logged[0].contains("could not cover"), "{logged:?}");
10840
10841 let (events, logged) = warnings(|| venue.advance_time(boundary(3)));
10842 assert_eq!(charges(&events)[0].1.amount, d("1.25"), "the long receives");
10843 assert_eq!(free_usd(&venue), d("-36.25"));
10844 assert!(logged.is_empty(), "a credit is not warned of: {logged:?}");
10845 }
10846
10847 #[test]
10849 fn a_zero_charge_is_not_reported() {
10850 let mut venue = make_cfd_venue_charging("200000", vec![hourly_funding("0")]);
10851 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
10852 cfd_market_fill(&mut venue, Side::Buy, "1");
10853
10854 assert!(venue.advance_time(boundary(1)).is_empty());
10855 }
10856
10857 #[test]
10860 fn the_next_deadline_is_the_earliest_expiry_or_boundary() {
10861 let mut venue = make_cfd_venue_charging("200000", vec![hourly_funding("0.0001")]);
10862 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
10863 assert_eq!(venue.next_deadline(), None, "flat, no orders");
10864
10865 cfd_market_fill(&mut venue, Side::Buy, "1");
10866 assert_eq!(venue.next_deadline(), Some(boundary(0)));
10867
10868 let expiry = boundary(0) - TimeDelta::seconds(60);
10870 let mut gtd = limit_request(
10871 "gtd",
10872 Side::Sell,
10873 "1",
10874 "6000",
10875 TimeInForce::GoodTillDate { expiry },
10876 );
10877 gtd.key.instrument = cfd_instrument_name();
10878 assert!(venue.open_order(gtd).response.state.is_accepted());
10879 assert_eq!(venue.next_deadline(), Some(expiry));
10880
10881 let events = venue.advance_time(expiry);
10882 assert!(!events.is_empty(), "the order expired");
10883 assert_eq!(venue.next_deadline(), Some(boundary(0)));
10884 }
10885
10886 #[test]
10892 fn a_short_pays_its_borrow_by_settlement_day_and_receives_its_financing() {
10893 let cutoff = NaiveTime::from_hms_opt(21, 0, 0).unwrap();
10894 let borrow = BorrowFeeModel::new(DailyAccrual::new(
10895 cutoff,
10896 Utc,
10897 AccrualDays::Settlement {
10898 calendar: Arc::new(WeekendsOnly),
10899 lag: 1,
10900 },
10901 DayCount::Act360,
10902 ))
10903 .with_rates(cfd_instrument_name(), RateSeries::constant(d("0.36")));
10904 let financing = FinancingModel::new(DailyAccrual::new(
10905 cutoff,
10906 Utc,
10907 AccrualDays::Calendar,
10908 DayCount::Act365,
10909 ))
10910 .with_rates(
10911 cfd_instrument_name(),
10912 FinancingRates::new(
10913 RateSeries::constant(d("0.073")),
10914 RateSeries::constant(d("-0.0365")),
10915 ),
10916 );
10917 let mut venue =
10918 make_cfd_venue_charging("200000", vec![Arc::new(borrow), Arc::new(financing)]);
10919 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
10920 cfd_market_fill(&mut venue, Side::Sell, "1");
10921
10922 let friday = DateTime::parse_from_rfc3339("2023-11-17T00:00:00Z")
10923 .unwrap()
10924 .to_utc();
10925 let events = venue.advance_time(friday);
10926 let charged: Vec<_> = charges(&events)
10927 .iter()
10928 .map(|(_, flow)| (flow.time_exchange, flow.kind.clone(), flow.amount))
10929 .collect();
10930
10931 let wednesday = DateTime::parse_from_rfc3339("2023-11-15T21:00:00Z")
10932 .unwrap()
10933 .to_utc();
10934 let thursday = wednesday + TimeDelta::days(1);
10935 let borrow_fee = CashFlowKind::BorrowFee {
10936 rate: Some(d("0.36")),
10937 quantity: Some(d("25")),
10938 };
10939 let financed = CashFlowKind::Financing {
10940 rate: Some(d("-0.0365")),
10941 notional: Some(d("125000")),
10942 };
10943 assert_eq!(
10945 charged,
10946 vec![
10947 (wednesday, borrow_fee.clone(), d("-125")),
10948 (wednesday, financed.clone(), d("12.5")),
10949 (thursday, borrow_fee, d("-375")),
10950 (thursday, financed, d("12.5")),
10951 ]
10952 );
10953 }
10954
10955 #[test]
10958 fn positions_charged_at_one_boundary_are_charged_in_instrument_order() {
10959 let nas = InstrumentNameExchange::new("nas100_usd");
10960 let dax = InstrumentNameExchange::new("dax40_usd");
10961 let mut instruments = instruments_of(cfd_instrument());
10962 let mut funding = FundingModel::new(TimeDelta::hours(1))
10963 .with_rates(cfd_instrument_name(), RateSeries::constant(d("0.0001")));
10964 for name in [&nas, &dax] {
10965 let mut other = cfd_instrument();
10966 other.name_exchange = name.clone();
10967 instruments.insert(name.clone(), other);
10968 funding = funding.with_rates(name.clone(), RateSeries::constant(d("0.0001")));
10969 }
10970 let mut venue = SimulatedVenue::new_market_driven(
10971 &config_from_balances(vec![funded(usd(), d("1000000"))], FeeModelConfig::default()),
10972 instruments,
10973 )
10974 .with_holding_costs(vec![Arc::new(funding)]);
10975 advance(&mut venue, time(1));
10976
10977 for name in [cfd_instrument_name(), nas.clone(), dax.clone()] {
10978 let fills = venue.apply_market(
10979 &name,
10980 MarketObservation::new(market_prices("5000").unwrap()),
10981 time(1),
10982 );
10983 assert!(fills.is_empty());
10984 let outcome = venue.open_order(request(name, Side::Buy, "1", None));
10985 assert!(outcome.response.state.is_accepted());
10986 }
10987
10988 let events = venue.advance_time(boundary(0));
10989 let order: Vec<_> = charges(&events)
10990 .iter()
10991 .map(|(_, flow)| flow.instrument.clone().unwrap())
10992 .collect();
10993 assert_eq!(order, vec![dax, nas, cfd_instrument_name()]);
10994 }
10995
10996 #[test]
10998 fn a_position_flipped_before_a_boundary_is_charged_as_what_it_became() {
10999 let mut venue = make_cfd_venue_charging("400000", vec![hourly_funding("0.0001")]);
11000 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
11001 cfd_market_fill(&mut venue, Side::Buy, "1");
11002 cfd_market_fill(&mut venue, Side::Sell, "2");
11003
11004 let events = venue.advance_time(boundary(0));
11005 let charged = charges(&events);
11006 assert_eq!(charged.len(), 1);
11007 assert_eq!(
11008 charged[0].1.amount,
11009 d("12.5"),
11010 "a short receives a positive rate"
11011 );
11012 }
11013
11014 #[test]
11017 #[should_panic(expected = "which is not after")]
11018 fn a_model_whose_boundary_does_not_advance_panics_rather_than_charging_forever() {
11019 #[derive(Debug)]
11020 struct Stuck;
11021 impl HoldingCostModel for Stuck {
11022 fn next_boundary(
11023 &self,
11024 _: &InstrumentNameExchange,
11025 after: DateTime<Utc>,
11026 ) -> Option<DateTime<Utc>> {
11027 Some(after)
11028 }
11029 fn charge(&self, _: &HeldPosition<'_>, _: DateTime<Utc>) -> Option<HoldingCharge> {
11030 None
11031 }
11032 }
11033
11034 let mut venue = make_cfd_venue_charging("200000", vec![Arc::new(Stuck)]);
11035 assert!(cfd_market(&mut venue, "5000", "5000", "5000").is_empty());
11036 cfd_market_fill(&mut venue, Side::Buy, "1");
11037 let _ = venue.advance_time(time(2));
11038 }
11039
11040 fn make_cfd_venue_lending(table: ShortabilityTable) -> SimulatedVenue {
11045 let mut venue = SimulatedVenue::new_market_driven(
11046 &config_from_balances(
11047 vec![funded(self::usd(), d("10000000"))],
11048 FeeModelConfig::default(),
11049 ),
11050 instruments_of(cfd_instrument()),
11051 )
11052 .with_shortability(Arc::new(table));
11053 advance(&mut venue, time(1));
11054 assert!(cfd_market(&mut venue, "4990", "5000", "5000").is_empty());
11055 venue
11056 }
11057
11058 fn lending(shortability: Shortability) -> ShortabilityTable {
11059 ShortabilityTable::new().with_shortability(cfd_instrument_name(), time(0), shortability)
11060 }
11061
11062 fn borrow_rejection(outcome: &OpenOutcome) -> &BorrowReject {
11064 assert!(
11065 outcome.events.is_empty(),
11066 "a refused short moves nothing, so it owes no events"
11067 );
11068 match &outcome.response.state {
11069 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
11070 ApiError::BorrowRejected(reject),
11071 ))) => reject,
11072 other => panic!("expected BorrowRejected, got: {other:?}"),
11073 }
11074 }
11075
11076 fn open_cfd(venue: &mut SimulatedVenue, side: Side, quantity: &str) -> OpenOutcome {
11077 venue.open_order(cfd_request(side, quantity, None))
11078 }
11079
11080 #[test]
11081 fn a_short_of_an_instrument_that_is_not_shortable_is_refused_before_the_ledger_moves() {
11082 let mut venue = make_cfd_venue_lending(lending(Shortability::new().with_shortable(false)));
11083
11084 let outcome = open_cfd(&mut venue, Side::Sell, "1");
11085
11086 let reject = borrow_rejection(&outcome);
11087 assert_eq!(reject.reason, BorrowRejectReason::NotShortable);
11088 assert_eq!(reject.venue_code, None);
11089 assert_eq!(reject.available, None);
11090 assert!(reject.message.contains("spx500_usd"), "{}", reject.message);
11091 assert_eq!(free_usd(&venue), d("10000000"));
11092 assert_eq!(cfd_position(&venue), None);
11093 assert!(venue.orders_open(&[]).is_empty());
11094 }
11095
11096 #[test]
11099 fn a_short_at_or_after_the_delisting_time_is_refused_and_one_before_it_is_not() {
11100 let delisted = Shortability::new()
11101 .with_shortable(true)
11102 .with_delisting_at(time(1));
11103 let mut venue = make_cfd_venue_lending(lending(delisted));
11104
11105 let outcome = open_cfd(&mut venue, Side::Sell, "1");
11106
11107 let reject = borrow_rejection(&outcome);
11108 assert_eq!(reject.reason, BorrowRejectReason::NotShortable);
11109 assert!(reject.message.contains("delisted"), "{}", reject.message);
11110 assert_eq!(cfd_position(&venue), None);
11111
11112 let ahead = Shortability::new()
11113 .with_shortable(true)
11114 .with_delisting_at(time(2));
11115 let mut venue = make_cfd_venue_lending(lending(ahead));
11116 cfd_market_fill(&mut venue, Side::Sell, "1");
11117 assert!(cfd_position(&venue).is_some(), "not delisted yet");
11118 }
11119
11120 #[test]
11121 fn a_buy_and_a_sell_that_only_reduces_a_long_are_never_refused() {
11122 let mut venue = make_cfd_venue_lending(lending(Shortability::new().with_shortable(false)));
11123
11124 cfd_market_fill(&mut venue, Side::Buy, "2");
11125 cfd_market_fill(&mut venue, Side::Sell, "1");
11126 cfd_market_fill(&mut venue, Side::Sell, "1");
11127 assert_eq!(cfd_position(&venue), None);
11128
11129 let outcome = open_cfd(&mut venue, Side::Sell, "1");
11130 assert_eq!(
11131 borrow_rejection(&outcome).reason,
11132 BorrowRejectReason::NotShortable,
11133 "once flat, the next sell opens a short"
11134 );
11135 }
11136
11137 #[test]
11138 fn a_sell_reducing_a_long_that_resting_sells_already_close_opens_a_short() {
11139 let mut venue = make_cfd_venue_lending(lending(Shortability::new().with_shortable(false)));
11140 cfd_market_fill(&mut venue, Side::Buy, "2");
11141 let rested = venue.open_order(cfd_limit("close", Side::Sell, "2", "5200"));
11142 assert!(rested_open(&rested).filled_quantity.is_zero());
11143
11144 let outcome = open_cfd(&mut venue, Side::Sell, "1");
11145
11146 assert_eq!(
11147 borrow_rejection(&outcome).reason,
11148 BorrowRejectReason::NotShortable
11149 );
11150 }
11151
11152 #[test]
11155 fn a_short_past_what_is_available_net_of_the_own_short_and_resting_sells_is_refused() {
11156 let mut venue = make_cfd_venue_lending(lending(
11157 Shortability::new()
11158 .with_shortable(true)
11159 .with_available(d("5")),
11160 ));
11161 cfd_market_fill(&mut venue, Side::Sell, "2");
11162 let rested = venue.open_order(cfd_limit("rest", Side::Sell, "2", "5200"));
11163 assert!(rested_open(&rested).filled_quantity.is_zero());
11164 let free = free_usd(&venue);
11165
11166 let outcome = open_cfd(&mut venue, Side::Sell, "2");
11167
11168 let reject = borrow_rejection(&outcome);
11169 assert_eq!(reject.reason, BorrowRejectReason::InventoryUnavailable);
11170 assert_eq!(reject.available, Some(d("1")));
11171 assert_eq!(free_usd(&venue), free);
11172 assert_eq!(
11173 cfd_position(&venue).map(|(quantity, _)| quantity),
11174 Some(d("-2"))
11175 );
11176
11177 cfd_market_fill(&mut venue, Side::Sell, "1");
11178 assert_eq!(
11179 cfd_position(&venue).map(|(quantity, _)| quantity),
11180 Some(d("-3"))
11181 );
11182 }
11183
11184 #[test]
11185 fn what_is_available_is_never_reported_below_zero() {
11186 let mut venue = make_cfd_venue_lending(
11187 ShortabilityTable::new()
11188 .with_shortability(cfd_instrument_name(), time(0), Shortability::new())
11189 .with_shortability(
11190 cfd_instrument_name(),
11191 time(2),
11192 Shortability::new().with_available(d("1")),
11193 ),
11194 );
11195 cfd_market_fill(&mut venue, Side::Sell, "3");
11196 advance(&mut venue, time(2));
11197
11198 let outcome = open_cfd(&mut venue, Side::Sell, "1");
11199
11200 let reject = borrow_rejection(&outcome);
11201 assert_eq!(reject.reason, BorrowRejectReason::InventoryUnavailable);
11202 assert_eq!(reject.available, Some(Decimal::ZERO));
11203 }
11204
11205 #[test]
11208 fn a_flip_is_gated_on_the_short_it_opens_only() {
11209 let mut venue = make_cfd_venue_lending(lending(Shortability::new().with_available(d("3"))));
11210 cfd_market_fill(&mut venue, Side::Buy, "5");
11211
11212 let outcome = open_cfd(&mut venue, Side::Sell, "9");
11213 let reject = borrow_rejection(&outcome);
11214 assert_eq!(reject.reason, BorrowRejectReason::InventoryUnavailable);
11215 assert_eq!(reject.available, Some(d("3")));
11216 assert_eq!(
11217 cfd_position(&venue).map(|(quantity, _)| quantity),
11218 Some(d("5"))
11219 );
11220
11221 cfd_market_fill(&mut venue, Side::Sell, "8");
11222 assert_eq!(
11223 cfd_position(&venue).map(|(quantity, _)| quantity),
11224 Some(d("-3"))
11225 );
11226 }
11227
11228 #[test]
11229 fn a_short_sale_restriction_refuses_a_marketable_short_and_lets_one_above_the_bid_rest() {
11230 let mut venue =
11231 make_cfd_venue_lending(ShortabilityTable::new().with_short_sale_restriction(
11232 cfd_instrument_name(),
11233 time(0),
11234 time(10),
11235 ));
11236
11237 cfd_market_fill(&mut venue, Side::Buy, "1");
11239 cfd_market_fill(&mut venue, Side::Sell, "1");
11240
11241 let market = open_cfd(&mut venue, Side::Sell, "1");
11242 assert_eq!(
11243 borrow_rejection(&market).reason,
11244 BorrowRejectReason::ShortSaleRestricted
11245 );
11246 for (cid, limit) in [("at-bid", "4990"), ("below-bid", "4980")] {
11247 let outcome = venue.open_order(cfd_limit(cid, Side::Sell, "1", limit));
11248 assert_eq!(
11249 borrow_rejection(&outcome).reason,
11250 BorrowRejectReason::ShortSaleRestricted,
11251 "a sell limit at {limit} crosses the 4990 bid"
11252 );
11253 }
11254
11255 let above = venue.open_order(cfd_limit("above-bid", Side::Sell, "1", "4995"));
11256 assert!(rested_open(&above).filled_quantity.is_zero());
11257 assert_eq!(cfd_position(&venue), None);
11258
11259 advance(&mut venue, time(10));
11261 cfd_market_fill(&mut venue, Side::Sell, "1");
11262 assert_eq!(
11263 cfd_position(&venue).map(|(quantity, _)| quantity),
11264 Some(d("-1"))
11265 );
11266 }
11267
11268 #[test]
11271 fn a_restricted_market_short_the_fill_model_can_price_is_refused_for_the_restriction() {
11272 let mut venue =
11273 make_cfd_venue_lending(ShortabilityTable::new().with_short_sale_restriction(
11274 cfd_instrument_name(),
11275 time(0),
11276 time(10),
11277 ));
11278 venue.fill_model = Arc::new(SimFillConfig::Midpoint(crate::fill::MidpointFillModel));
11279
11280 let outcome = open_cfd(&mut venue, Side::Sell, "1");
11281
11282 assert_eq!(
11283 borrow_rejection(&outcome).reason,
11284 BorrowRejectReason::ShortSaleRestricted
11285 );
11286 }
11287
11288 #[test]
11289 fn missing_shortability_data_allows_the_short() {
11290 let mut venue = make_cfd_venue_lending(
11291 ShortabilityTable::new()
11292 .with_shortability(
11293 cfd_instrument_name(),
11294 time(100),
11295 Shortability::new().with_shortable(false),
11296 )
11297 .with_shortability(
11298 InstrumentNameExchange::new("other"),
11299 time(0),
11300 Shortability::new().with_shortable(false),
11301 ),
11302 );
11303
11304 cfd_market_fill(&mut venue, Side::Sell, "1");
11305
11306 assert_eq!(
11307 cfd_position(&venue).map(|(quantity, _)| quantity),
11308 Some(d("-1"))
11309 );
11310 }
11311
11312 #[test]
11315 fn a_spot_short_is_refused_for_its_balance_not_by_the_provider() {
11316 let table = ShortabilityTable::new().with_shortability(
11317 instrument_name(),
11318 time(0),
11319 Shortability::new().with_shortable(false),
11320 );
11321 let mut venue = make_venue("0", "10000").with_shortability(Arc::new(table));
11322
11323 let outcome = venue.open_order(sell_request("1", market_prices("50000")));
11324
11325 match outcome.response.state {
11326 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
11327 ApiError::BalanceInsufficient(..),
11328 ))) => {}
11329 other => panic!("expected BalanceInsufficient, got: {other:?}"),
11330 }
11331 }
11332
11333 #[test]
11337 fn a_restricted_short_with_no_price_is_refused_as_unpriceable() {
11338 let reason_of = |fill_model: SimFillConfig, market| {
11339 let mut venue = make_cfd_venue("10000000", FeeModelConfig::default())
11340 .with_shortability(Arc::new(
11341 ShortabilityTable::new().with_short_sale_restriction(
11342 cfd_instrument_name(),
11343 time(0),
11344 time(10),
11345 ),
11346 ));
11347 venue.fill_model = Arc::new(fill_model);
11348 advance(&mut venue, time(1));
11349 match venue
11350 .open_order(cfd_request(Side::Sell, "1", market))
11351 .response
11352 .state
11353 {
11354 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
11355 ApiError::OrderRejected(reason),
11356 ))) => reason,
11357 other => panic!("expected the unpriceable OrderRejected, got: {other:?}"),
11358 }
11359 };
11360
11361 let absent = reason_of(SimFillConfig::default(), None);
11362 assert!(absent.contains("no market snapshot"), "{absent}");
11363 let empty = reason_of(SimFillConfig::default(), Some(MarketSnapshot::default()));
11364 assert!(empty.contains("no market price available yet"), "{empty}");
11365
11366 let bid_only = MarketSnapshot {
11368 best_bid: Some(d("4990")),
11369 ..MarketSnapshot::default()
11370 };
11371 let midpoint = reason_of(
11372 SimFillConfig::Midpoint(crate::fill::MidpointFillModel),
11373 Some(bid_only),
11374 );
11375 assert!(
11376 midpoint.contains("no market price available yet"),
11377 "{midpoint}"
11378 );
11379 }
11380
11381 #[test]
11384 fn the_first_refusal_that_applies_is_the_one_reported() {
11385 let restricted = |shortability| {
11386 lending(shortability).with_short_sale_restriction(
11387 cfd_instrument_name(),
11388 time(0),
11389 time(10),
11390 )
11391 };
11392
11393 let mut venue = make_cfd_venue_lending(restricted(
11394 Shortability::new()
11395 .with_shortable(false)
11396 .with_available(Decimal::ZERO),
11397 ));
11398 let outcome = open_cfd(&mut venue, Side::Sell, "1");
11399 assert_eq!(
11400 borrow_rejection(&outcome).reason,
11401 BorrowRejectReason::NotShortable
11402 );
11403
11404 let mut venue = make_cfd_venue_lending(restricted(
11405 Shortability::new().with_available(Decimal::ZERO),
11406 ));
11407 let outcome = open_cfd(&mut venue, Side::Sell, "1");
11408 assert_eq!(
11409 borrow_rejection(&outcome).reason,
11410 BorrowRejectReason::InventoryUnavailable
11411 );
11412 }
11413
11414 #[test]
11417 fn a_short_sale_restriction_refuses_a_marketable_flip_but_not_a_close() {
11418 let mut venue =
11419 make_cfd_venue_lending(ShortabilityTable::new().with_short_sale_restriction(
11420 cfd_instrument_name(),
11421 time(0),
11422 time(10),
11423 ));
11424 cfd_market_fill(&mut venue, Side::Buy, "2");
11425
11426 let outcome = open_cfd(&mut venue, Side::Sell, "3");
11427 assert_eq!(
11428 borrow_rejection(&outcome).reason,
11429 BorrowRejectReason::ShortSaleRestricted
11430 );
11431 assert_eq!(
11432 cfd_position(&venue).map(|(quantity, _)| quantity),
11433 Some(d("2"))
11434 );
11435
11436 cfd_market_fill(&mut venue, Side::Sell, "2");
11437 assert_eq!(cfd_position(&venue), None);
11438 }
11439
11440 fn next_bar_config(usdt: &str, price: NextBarPrice) -> MockExecutionConfig {
11445 MockExecutionConfig {
11446 market_order_timing: MarketOrderTiming::NextBar(price),
11447 ..spot_config("10", usdt, FeeModelConfig::default())
11448 }
11449 }
11450
11451 fn ohlc(open: &str, high: &str, low: &str, close: &str) -> MarketBar {
11452 MarketBar::new(d(open), d(high), d(low), d(close))
11453 }
11454
11455 fn feed_bar(
11457 venue: &mut SimulatedVenue,
11458 seconds: i64,
11459 bar: MarketBar,
11460 ) -> Vec<UnindexedAccountEvent> {
11461 feed_bar_sized(venue, seconds, bar, MarketDepth::UNKNOWN)
11462 }
11463
11464 fn feed_bar_sized(
11465 venue: &mut SimulatedVenue,
11466 seconds: i64,
11467 bar: MarketBar,
11468 depth: MarketDepth,
11469 ) -> Vec<UnindexedAccountEvent> {
11470 let snapshot = MarketSnapshot::from_last_price(Some(bar.close));
11471 venue.apply_market(
11472 &instrument_name(),
11473 MarketObservation::new(snapshot)
11474 .with_depth(depth)
11475 .with_bar(Some(bar)),
11476 time(seconds),
11477 )
11478 }
11479
11480 fn venue_holding(price: NextBarPrice, usdt: &str) -> SimulatedVenue {
11482 let mut venue =
11483 SimulatedVenue::new_market_driven(&next_bar_config(usdt, price), spot_instruments());
11484 advance(&mut venue, time(1));
11485 assert!(feed_bar(&mut venue, 1, ohlc("95", "105", "90", "100")).is_empty());
11486 venue
11487 }
11488
11489 fn market_order(
11490 cid: &str,
11491 side: Side,
11492 quantity: &str,
11493 time_in_force: TimeInForce,
11494 ) -> OrderRequestOpen<ExchangeId, InstrumentNameExchange> {
11495 let mut request = request(instrument_name(), side, quantity, None);
11496 request.key.cid = ClientOrderId::new(cid);
11497 request.state.time_in_force = time_in_force;
11498 request
11499 }
11500
11501 fn ioc_buy(cid: &str, quantity: &str) -> OrderRequestOpen<ExchangeId, InstrumentNameExchange> {
11502 market_order(cid, Side::Buy, quantity, TimeInForce::ImmediateOrCancel)
11503 }
11504
11505 fn total_base(venue: &SimulatedVenue) -> Decimal {
11506 venue.balances(&[base()])[0].balance.total
11507 }
11508
11509 fn rejection_reason(outcome: &OpenOutcome) -> &str {
11510 match &outcome.response.state {
11511 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
11512 ApiError::OrderRejected(reason),
11513 ))) => reason,
11514 other => panic!("expected an OrderRejected, got: {other:?}"),
11515 }
11516 }
11517
11518 #[test]
11519 fn a_market_order_held_for_the_next_bar_is_open_and_holds_its_cost_at_the_current_price() {
11520 let mut venue = venue_holding(NextBarPrice::Open, "10000");
11521
11522 let outcome = venue.open_order(ioc_buy("held", "1"));
11523
11524 assert_eq!(
11525 rested_open(&outcome).filled_quantity,
11526 Decimal::ZERO,
11527 "accepted as open, with nothing traded yet"
11528 );
11529 assert_eq!(
11530 outcome.events.len(),
11531 1,
11532 "it owes the one balance its hold restates"
11533 );
11534 assert_eq!(
11535 balance_of(&outcome.events[0]).balance.free,
11536 d("9900"),
11537 "held at the close the order arrived on"
11538 );
11539 assert_eq!(total_quote(&venue), d("10000"), "and nothing paid yet");
11540 assert!(venue.trades(time(0)).is_empty());
11541 assert_eq!(venue.orders_open(&[]).len(), 1);
11542 assert!(venue.account.orders().holds(&instrument_name()));
11543 }
11544
11545 #[test]
11546 fn the_next_bar_fills_a_held_order_at_its_open_and_settles_that_price() {
11547 let mut venue = venue_holding(NextBarPrice::Open, "10000");
11548 let _ = venue.open_order(ioc_buy("held", "1"));
11549
11550 let events = feed_bar(&mut venue, 2, ohlc("102", "110", "98", "108"));
11551
11552 assert_eq!(
11553 events.len(),
11554 4,
11555 "the balance paid, the one received, the trade, then the order: {events:?}"
11556 );
11557 let paid = balance_of(&events[0]);
11558 assert_eq!(paid.asset, quote());
11559 assert_eq!(
11560 (paid.balance.total, paid.balance.free),
11561 (d("9898"), d("9898")),
11562 "the hold is released and the fill settled at the open, not the close it was held at"
11563 );
11564 assert_eq!(balance_of(&events[1]).asset, base());
11565
11566 let trade = trade_of(&events[2]);
11567 assert_eq!(
11568 (trade.price, trade.quantity, trade.time_exchange),
11569 (d("102"), d("1"), time(2)),
11570 "filled at the bar's open, stamped at the bar"
11571 );
11572 assert!(
11573 matches!(
11574 order_of(&events[3]).state,
11575 OrderState::Inactive(InactiveOrderState::FullyFilled(_))
11576 ),
11577 "{:?}",
11578 order_of(&events[3]).state
11579 );
11580
11581 assert!(venue.orders_open(&[]).is_empty());
11582 assert!(!venue.account.orders().holds(&instrument_name()));
11583 assert_eq!(total_base(&venue), d("11"));
11584 assert_eq!(free_quote(&venue), d("9898"));
11585 }
11586
11587 #[test]
11588 fn adverse_extreme_fills_a_held_buy_at_the_bar_high_and_a_sell_at_its_low() {
11589 for (side, expected) in [(Side::Buy, "110"), (Side::Sell, "98")] {
11590 let mut venue = venue_holding(NextBarPrice::AdverseExtreme, "10000");
11591 let _ = venue.open_order(market_order(
11592 "held",
11593 side,
11594 "1",
11595 TimeInForce::ImmediateOrCancel,
11596 ));
11597
11598 let events = feed_bar(&mut venue, 2, ohlc("102", "110", "98", "108"));
11599
11600 assert_eq!(trade_of(&events[2]).price, d(expected), "a {side:?}");
11601 }
11602 }
11603
11604 #[test]
11605 fn a_tick_between_bars_does_not_release_a_held_order() {
11606 let mut venue = venue_holding(NextBarPrice::Open, "10000");
11607 let _ = venue.open_order(ioc_buy("held", "1"));
11608
11609 let ticked = venue.apply_market(
11610 &instrument_name(),
11611 MarketObservation::new(traded("101")),
11612 time(2),
11613 );
11614
11615 assert!(ticked.is_empty(), "a tick is not a bar: {ticked:?}");
11616 assert!(venue.account.orders().holds(&instrument_name()));
11617
11618 let events = feed_bar(&mut venue, 3, ohlc("103", "104", "102", "103"));
11619 assert_eq!(
11620 trade_of(&events[2]).price,
11621 d("103"),
11622 "the next bar fills it"
11623 );
11624 }
11625
11626 #[test]
11627 fn a_held_order_fills_at_the_bar_price_moved_by_the_configured_slippage() {
11628 let config = MockExecutionConfig {
11629 slippage: SlippageConfig::fixed(d("0.01")).unwrap(),
11630 ..next_bar_config("10000", NextBarPrice::Open)
11631 };
11632 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
11633 advance(&mut venue, time(1));
11634 assert!(feed_bar(&mut venue, 1, ohlc("95", "105", "90", "100")).is_empty());
11635 let _ = venue.open_order(ioc_buy("held", "1"));
11636
11637 let events = feed_bar(&mut venue, 2, ohlc("102", "110", "98", "108"));
11638
11639 assert_eq!(
11640 trade_of(&events[2]).price,
11641 d("103.02"),
11642 "the open, 1% against the buy"
11643 );
11644 }
11645
11646 #[test]
11647 fn a_market_order_with_no_bar_to_wait_for_is_rejected_naming_the_setting() {
11648 let mut ticked = SimulatedVenue::new_market_driven(
11650 &next_bar_config("10000", NextBarPrice::Open),
11651 spot_instruments(),
11652 );
11653 advance(&mut ticked, time(1));
11654 assert!(
11655 ticked
11656 .apply_market(
11657 &instrument_name(),
11658 MarketObservation::new(traded("100")),
11659 time(1)
11660 )
11661 .is_empty()
11662 );
11663 let mut request_priced = SimulatedVenue::new(
11665 &next_bar_config("10000", NextBarPrice::Open),
11666 spot_instruments(),
11667 );
11668
11669 for (venue, request) in [
11670 (&mut ticked, ioc_buy("held", "1")),
11671 (&mut request_priced, buy_request("1", market_prices("100"))),
11672 ] {
11673 let outcome = venue.open_order(request);
11674
11675 assert!(outcome.events.is_empty());
11676 assert!(
11677 rejection_reason(&outcome).contains("MarketOrderTiming::NextBar"),
11678 "{}",
11679 rejection_reason(&outcome)
11680 );
11681 assert_eq!(
11682 free_quote(venue),
11683 d("10000"),
11684 "a refused order holds nothing"
11685 );
11686 }
11687 }
11688
11689 #[test]
11690 fn a_held_order_the_account_cannot_cover_is_refused_on_arrival() {
11691 let mut venue = venue_holding(NextBarPrice::Open, "99");
11692
11693 let outcome = venue.open_order(ioc_buy("held", "1"));
11694
11695 assert!(
11696 matches!(
11697 outcome.response.state,
11698 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
11699 ApiError::BalanceInsufficient(..)
11700 )))
11701 ),
11702 "{:?}",
11703 outcome.response.state
11704 );
11705 assert!(!venue.account.orders().holds(&instrument_name()));
11706 }
11707
11708 #[test]
11709 fn a_held_order_is_cancelled_like_a_resting_one_and_releases_its_hold() {
11710 let mut venue = venue_holding(NextBarPrice::Open, "10000");
11711 let request = ioc_buy("held", "1");
11712 let key = request.key.clone();
11713 let _ = venue.open_order(request);
11714
11715 let cancelled = venue.cancel_order(OrderEvent {
11716 key,
11717 state: RequestCancel { id: None },
11718 });
11719
11720 assert!(cancelled.response.state.is_ok());
11721 assert_eq!(free_quote(&venue), d("10000"));
11722 assert!(!venue.account.orders().holds(&instrument_name()));
11723 assert!(
11724 feed_bar(&mut venue, 2, ohlc("102", "110", "98", "108")).is_empty(),
11725 "the next bar has nothing left to fill"
11726 );
11727 }
11728
11729 #[test]
11732 fn a_held_market_order_carrying_a_price_is_held_without_it() {
11733 let mut venue = venue_holding(NextBarPrice::Open, "10000");
11734 let request = priced_market_order(Side::Buy, "200", None);
11735 let key = request.key.clone();
11736
11737 let outcome = venue.open_order(request);
11738 assert_eq!(rested_open(&outcome).filled_quantity, Decimal::ZERO);
11739 assert_eq!(outcome.response.price, None, "reported as it is held");
11740 assert_eq!(venue.orders_open(&[])[0].price, None);
11741
11742 let ticked = venue.apply_market(
11743 &instrument_name(),
11744 MarketObservation::new(traded("150")),
11745 time(2),
11746 );
11747 assert!(
11748 ticked.is_empty(),
11749 "a tick below the stray price matches nothing: {ticked:?}"
11750 );
11751
11752 let cancelled = venue.cancel_order(OrderEvent {
11753 key,
11754 state: RequestCancel { id: None },
11755 });
11756 assert!(cancelled.response.state.is_ok());
11757 assert!(!venue.account.orders().holds(&instrument_name()));
11758 }
11759
11760 #[test]
11763 fn a_held_order_whose_deadline_the_bar_reaches_expires_unfilled() {
11764 let mut venue = venue_holding(NextBarPrice::Open, "10000");
11765 let _ = venue.open_order(market_order(
11766 "gtd",
11767 Side::Buy,
11768 "1",
11769 TimeInForce::GoodTillDate { expiry: time(2) },
11770 ));
11771
11772 let events = feed_bar(&mut venue, 2, ohlc("102", "110", "98", "108"));
11773
11774 assert_eq!(
11775 events.len(),
11776 2,
11777 "the released hold, then the order: {events:?}"
11778 );
11779 assert!(
11780 matches!(
11781 order_of(&events[1]).state,
11782 OrderState::Inactive(InactiveOrderState::Expired(_))
11783 ),
11784 "{:?}",
11785 order_of(&events[1]).state
11786 );
11787 assert!(venue.trades(time(0)).is_empty());
11788 assert_eq!(free_quote(&venue), d("10000"));
11789 assert!(!venue.account.orders().holds(&instrument_name()));
11790 }
11791
11792 #[test]
11794 fn a_held_spot_sell_holds_the_base_it_would_deliver() {
11795 let mut venue = venue_holding(NextBarPrice::Open, "10000");
11796 let free_base = |venue: &SimulatedVenue| venue.balances(&[base()])[0].balance.free;
11797
11798 let _ = venue.open_order(market_order(
11799 "sell",
11800 Side::Sell,
11801 "1",
11802 TimeInForce::ImmediateOrCancel,
11803 ));
11804 assert_eq!(free_base(&venue), d("9"));
11805 assert_eq!(total_base(&venue), d("10"), "and nothing delivered yet");
11806
11807 let events = feed_bar(&mut venue, 2, ohlc("102", "110", "98", "108"));
11808
11809 assert_eq!(trade_of(&events[2]).price, d("102"));
11810 assert_eq!(total_base(&venue), d("9"));
11811 assert_eq!(free_base(&venue), d("9"));
11812 }
11813
11814 #[test]
11815 fn a_held_order_the_account_can_no_longer_afford_at_the_bar_is_cancelled() {
11816 let mut venue = venue_holding(NextBarPrice::Open, "105");
11818 let _ = venue.open_order(ioc_buy("held", "1"));
11819 assert_eq!(free_quote(&venue), d("5"));
11820
11821 let events = feed_bar(&mut venue, 2, ohlc("110", "112", "108", "111"));
11822
11823 assert_eq!(
11824 events.len(),
11825 2,
11826 "the released hold, then the order: {events:?}"
11827 );
11828 assert_eq!(balance_of(&events[0]).balance.free, d("105"));
11829 assert!(
11830 matches!(
11831 &order_of(&events[1]).state,
11832 OrderState::Inactive(InactiveOrderState::Cancelled(cancelled))
11833 if cancelled.filled_quantity == Some(Decimal::ZERO)
11834 ),
11835 "{:?}",
11836 order_of(&events[1]).state
11837 );
11838 assert!(venue.trades(time(0)).is_empty());
11839 assert_eq!(free_quote(&venue), d("105"));
11840 }
11841
11842 #[test]
11843 fn a_bar_with_less_size_than_a_held_order_fills_immediate_or_cancel_in_part_and_kills_fill_or_kill()
11844 {
11845 let bar = ohlc("102", "110", "98", "108");
11846
11847 let mut immediate = venue_holding(NextBarPrice::Open, "10000");
11848 let _ = immediate.open_order(ioc_buy("ioc", "1"));
11849 let events = feed_bar_sized(&mut immediate, 2, bar, sized("0.4"));
11850 assert_eq!(trade_of(&events[2]).quantity, d("0.4"));
11851 assert!(
11852 matches!(
11853 &order_of(&events[3]).state,
11854 OrderState::Inactive(InactiveOrderState::Cancelled(cancelled))
11855 if cancelled.filled_quantity == Some(d("0.4"))
11856 ),
11857 "immediate-or-cancel keeps what the bar gave it: {:?}",
11858 order_of(&events[3]).state
11859 );
11860 assert_eq!(free_quote(&immediate), d("10000") - d("40.8"));
11861
11862 let mut all_or_nothing = venue_holding(NextBarPrice::Open, "10000");
11863 let _ =
11864 all_or_nothing.open_order(market_order("fok", Side::Buy, "1", TimeInForce::FillOrKill));
11865 let events = feed_bar_sized(&mut all_or_nothing, 2, bar, sized("0.4"));
11866 assert_eq!(
11867 events.len(),
11868 2,
11869 "the released hold, then the order: {events:?}"
11870 );
11871 assert!(all_or_nothing.trades(time(0)).is_empty());
11872 assert_eq!(free_quote(&all_or_nothing), d("10000"));
11873 }
11874
11875 #[test]
11877 fn a_bar_with_size_for_the_whole_held_order_fills_fill_or_kill() {
11878 let mut venue = venue_holding(NextBarPrice::Open, "10000");
11879 let _ = venue.open_order(market_order("fok", Side::Buy, "1", TimeInForce::FillOrKill));
11880
11881 let events = feed_bar_sized(&mut venue, 2, ohlc("102", "110", "98", "108"), sized("1"));
11882
11883 assert_eq!(trade_of(&events[2]).quantity, d("1"));
11884 assert!(
11885 matches!(
11886 order_of(&events[3]).state,
11887 OrderState::Inactive(InactiveOrderState::FullyFilled(_))
11888 ),
11889 "{:?}",
11890 order_of(&events[3]).state
11891 );
11892 }
11893
11894 #[test]
11895 fn orders_held_on_one_instrument_fill_in_arrival_order_against_one_size() {
11896 let mut venue = venue_holding(NextBarPrice::Open, "10000");
11897 let _ = venue.open_order(ioc_buy("first", "1"));
11898 let _ = venue.open_order(ioc_buy("second", "1"));
11899
11900 let events = feed_bar_sized(&mut venue, 2, ohlc("102", "110", "98", "108"), sized("1.5"));
11901
11902 let fills = events
11903 .iter()
11904 .filter_map(|event| match &event.kind {
11905 AccountEventKind::Trade(trade) => Some((
11906 trade.client_order_id.clone().unwrap().0.to_string(),
11907 trade.quantity,
11908 )),
11909 _ => None,
11910 })
11911 .collect::<Vec<_>>();
11912 assert_eq!(
11913 fills,
11914 [
11915 ("first".to_string(), d("1")),
11916 ("second".to_string(), d("0.5"))
11917 ],
11918 "earliest first, and the second gets what the first left"
11919 );
11920 }
11921
11922 #[test]
11923 fn expiring_the_held_orders_retires_each_as_expired_and_releases_its_hold() {
11924 let mut venue = venue_holding(NextBarPrice::Open, "10000");
11925 let _ = venue.open_order(ioc_buy("held", "1"));
11926
11927 let events = venue.expire_held(time(5));
11928
11929 assert_eq!(
11930 events.len(),
11931 2,
11932 "the released hold, then the order: {events:?}"
11933 );
11934 assert_eq!(balance_of(&events[0]).balance.free, d("10000"));
11935 let order = order_of(&events[1]);
11936 assert!(
11937 matches!(
11938 &order.state,
11939 OrderState::Inactive(InactiveOrderState::Expired(expired))
11940 if expired.filled_quantity == Some(Decimal::ZERO)
11941 && expired.time_exchange == time(5)
11942 ),
11943 "{:?}",
11944 order.state
11945 );
11946 assert!(venue.orders_open(&[]).is_empty());
11947 assert!(
11948 venue.expire_held(time(6)).is_empty(),
11949 "nothing is held twice"
11950 );
11951 assert!(feed_bar(&mut venue, 7, ohlc("102", "110", "98", "108")).is_empty());
11952 }
11953
11954 #[test]
11955 fn a_limit_order_is_not_held_for_the_next_bar() {
11956 let mut venue = venue_holding(NextBarPrice::Open, "10000");
11957
11958 let outcome = venue.open_order(limit_request("limit", Side::Buy, "1", "101", gtc()));
11959
11960 assert_eq!(
11961 filled_trade(&outcome).price,
11962 d("100"),
11963 "a marketable limit still fills on arrival"
11964 );
11965 assert!(!venue.account.orders().holds(&instrument_name()));
11966 }
11967
11968 fn stale_config(max_age: Duration) -> MockExecutionConfig {
11973 MockExecutionConfig {
11974 stale_market: StaleMarketPolicy::Wait { max_age },
11975 ..spot_config("10", "10000", FeeModelConfig::default())
11976 }
11977 }
11978
11979 const MAX_AGE: Duration = Duration::from_secs(10);
11981
11982 fn venue_waiting() -> SimulatedVenue {
11984 let mut venue =
11985 SimulatedVenue::new_market_driven(&stale_config(MAX_AGE), spot_instruments());
11986 advance(&mut venue, time(1));
11987 assert!(observe(&mut venue, 1, traded("100")).is_empty());
11988 venue
11989 }
11990
11991 fn observe(
11993 venue: &mut SimulatedVenue,
11994 seconds: i64,
11995 snapshot: MarketSnapshot,
11996 ) -> Vec<UnindexedAccountEvent> {
11997 venue.apply_market(
11998 &instrument_name(),
11999 MarketObservation::new(snapshot),
12000 time(seconds),
12001 )
12002 }
12003
12004 fn observe_idle(
12007 venue: &mut SimulatedVenue,
12008 seconds: i64,
12009 snapshot: MarketSnapshot,
12010 ) -> Vec<UnindexedAccountEvent> {
12011 venue.apply_market(
12012 &instrument_name(),
12013 MarketObservation::new(snapshot).with_fresh(false),
12014 time(seconds),
12015 )
12016 }
12017
12018 fn gtc_buy(cid: &str, quantity: &str) -> OrderRequestOpen<ExchangeId, InstrumentNameExchange> {
12019 market_order(cid, Side::Buy, quantity, gtc())
12020 }
12021
12022 fn trades_in(
12023 events: &[UnindexedAccountEvent],
12024 ) -> Vec<&Trade<AssetNameExchange, InstrumentNameExchange>> {
12025 events
12026 .iter()
12027 .filter_map(|event| match &event.kind {
12028 AccountEventKind::Trade(trade) => Some(trade),
12029 _ => None,
12030 })
12031 .collect()
12032 }
12033
12034 #[test]
12035 fn a_market_observed_fresh_within_the_max_age_fills_on_arrival() {
12036 let mut venue = venue_waiting();
12037 advance(&mut venue, time(11));
12039
12040 let outcome = venue.open_order(gtc_buy("fresh", "1"));
12041
12042 assert_eq!(filled_trade(&outcome).price, d("100"));
12043 assert!(!venue.account.orders().holds(&instrument_name()));
12044 }
12045
12046 #[test]
12047 fn a_market_order_on_a_stale_market_waits_open_holding_its_cost() {
12048 let mut venue = venue_waiting();
12049 advance(&mut venue, time(12));
12050
12051 let outcome = venue.open_order(gtc_buy("waiting", "1"));
12052
12053 assert_eq!(rested_open(&outcome).filled_quantity, Decimal::ZERO);
12054 assert_eq!(
12055 outcome.events.len(),
12056 1,
12057 "it owes the one balance its hold restates"
12058 );
12059 assert_eq!(
12060 balance_of(&outcome.events[0]).balance.free,
12061 d("9900"),
12062 "held at the stale price it arrived on"
12063 );
12064 assert!(venue.trades(time(0)).is_empty());
12065 assert!(venue.account.orders().holds(&instrument_name()));
12066 }
12067
12068 #[test]
12069 fn the_next_fresh_observation_fills_a_waiting_order_at_its_price() {
12070 let mut venue = venue_waiting();
12071 advance(&mut venue, time(12));
12072 let _ = venue.open_order(gtc_buy("waiting", "1"));
12073
12074 let events = observe(&mut venue, 20, traded("105"));
12075
12076 assert_eq!(
12077 events.len(),
12078 4,
12079 "the balance paid, the one received, the trade, then the order: {events:?}"
12080 );
12081 assert_eq!(
12082 balance_of(&events[0]).balance.free,
12083 d("9895"),
12084 "the hold is released and the fill settled at the fresh price"
12085 );
12086 let trade = trade_of(&events[2]);
12087 assert_eq!(
12088 (trade.price, trade.quantity, trade.time_exchange),
12089 (d("105"), d("1"), time(20)),
12090 "filled at the observation's price, stamped at it"
12091 );
12092 assert!(matches!(
12093 order_of(&events[3]).state,
12094 OrderState::Inactive(InactiveOrderState::FullyFilled(_))
12095 ));
12096 assert!(!venue.account.orders().holds(&instrument_name()));
12097 assert_eq!(total_base(&venue), d("11"));
12098 }
12099
12100 #[test]
12101 fn a_waiting_order_fills_after_the_resting_orders_its_observation_crosses() {
12102 let mut venue = venue_waiting();
12103 let _ = venue.open_order(limit_request("resting", Side::Buy, "1", "99", gtc()));
12104 advance(&mut venue, time(12));
12105 let _ = venue.open_order(gtc_buy("waiting", "1"));
12106
12107 let events = observe(&mut venue, 20, traded("98"));
12108
12109 let prices = trades_in(&events)
12110 .iter()
12111 .map(|trade| (trade.client_order_id.clone(), trade.price))
12112 .collect::<Vec<_>>();
12113 assert_eq!(
12114 prices,
12115 vec![
12116 (Some(ClientOrderId::new("resting")), d("99")),
12117 (Some(ClientOrderId::new("waiting")), d("98")),
12118 ],
12119 "the resting order was crossed by the observation, the waiting one arrived after it"
12120 );
12121 }
12122
12123 #[test]
12124 fn an_observation_that_is_not_fresh_neither_refreshes_the_market_nor_releases_an_order() {
12125 let mut venue = venue_waiting();
12126 assert!(observe_idle(&mut venue, 5, traded("101")).is_empty());
12128 advance(&mut venue, time(12));
12129
12130 let outcome = venue.open_order(gtc_buy("waiting", "1"));
12131 assert_eq!(
12132 balance_of(&outcome.events[0]).balance.free,
12133 d("9899"),
12134 "it waits, holding the price the idle observation carried"
12135 );
12136
12137 assert!(observe_idle(&mut venue, 13, traded("102")).is_empty());
12138 assert!(venue.account.orders().holds(&instrument_name()));
12139
12140 let events = observe(&mut venue, 14, traded("103"));
12141 assert_eq!(trade_of(&events[2]).price, d("103"));
12142 }
12143
12144 #[test]
12145 fn a_market_never_observed_fresh_is_stale() {
12146 let mut venue =
12147 SimulatedVenue::new_market_driven(&stale_config(MAX_AGE), spot_instruments());
12148 advance(&mut venue, time(1));
12149 assert!(observe_idle(&mut venue, 1, traded("100")).is_empty());
12150
12151 let outcome = venue.open_order(ioc_buy("immediate", "1"));
12152 let reason = rejection_reason(&outcome);
12153 assert!(reason.contains("never observed fresh"), "{reason}");
12154
12155 let outcome = venue.open_order(gtc_buy("waiting", "1"));
12156 assert_eq!(rested_open(&outcome).filled_quantity, Decimal::ZERO);
12157 }
12158
12159 #[test]
12160 fn an_immediate_or_cancel_or_fill_or_kill_order_on_a_stale_market_is_rejected_naming_the_policy()
12161 {
12162 for time_in_force in [TimeInForce::ImmediateOrCancel, TimeInForce::FillOrKill] {
12163 let mut venue = venue_waiting();
12164 advance(&mut venue, time(12));
12165
12166 let outcome = venue.open_order(market_order("now", Side::Buy, "1", time_in_force));
12167
12168 assert!(outcome.events.is_empty());
12169 let reason = rejection_reason(&outcome);
12170 assert!(
12171 reason.contains("StaleMarketPolicy::Wait")
12172 && reason.contains(&time(1).to_string())
12173 && reason.contains(&time_in_force.to_string()),
12174 "{reason}"
12175 );
12176 assert_eq!(
12177 free_quote(&venue),
12178 d("10000"),
12179 "a refused order holds nothing"
12180 );
12181 assert!(!venue.account.orders().holds(&instrument_name()));
12182 }
12183 }
12184
12185 #[test]
12186 fn a_market_order_on_an_unpriced_instrument_is_rejected_as_unpriceable_not_as_stale() {
12187 let mut venue =
12188 SimulatedVenue::new_market_driven(&stale_config(MAX_AGE), spot_instruments());
12189 advance(&mut venue, time(1));
12190 assert!(observe(&mut venue, 1, MarketSnapshot::default()).is_empty());
12191
12192 for request in [gtc_buy("waiting", "1"), ioc_buy("immediate", "1")] {
12193 let outcome = venue.open_order(request);
12194
12195 let reason = rejection_reason(&outcome);
12196 assert!(
12197 reason.contains("cannot price") && !reason.contains("StaleMarketPolicy"),
12198 "{reason}"
12199 );
12200 }
12201 }
12202
12203 #[test]
12204 fn a_venue_with_no_market_feed_rejects_market_orders_naming_the_policy() {
12205 let mut venue = SimulatedVenue::new(&stale_config(MAX_AGE), spot_instruments());
12206
12207 let outcome = venue.open_order(buy_request("1", market_prices("100")));
12208
12209 assert!(
12210 rejection_reason(&outcome).contains("StaleMarketPolicy::Wait needs a market feed"),
12211 "{}",
12212 rejection_reason(&outcome)
12213 );
12214 assert_eq!(free_quote(&venue), d("10000"));
12215 }
12216
12217 #[test]
12218 fn a_max_age_longer_than_any_run_never_makes_a_market_stale() {
12219 let mut venue =
12220 SimulatedVenue::new_market_driven(&stale_config(Duration::MAX), spot_instruments());
12221 advance(&mut venue, time(1));
12222 assert!(observe(&mut venue, 1, traded("100")).is_empty());
12223 advance(&mut venue, time(1_000_000));
12224
12225 let outcome = venue.open_order(ioc_buy("immediate", "1"));
12226
12227 assert_eq!(filled_trade(&outcome).price, d("100"));
12228 }
12229
12230 #[test]
12231 fn a_waiting_order_the_fresh_market_cannot_price_is_cancelled_and_releases_its_hold() {
12232 let mut venue = venue_waiting();
12233 advance(&mut venue, time(12));
12234 let _ = venue.open_order(gtc_buy("waiting", "1"));
12235
12236 let events = observe(
12238 &mut venue,
12239 20,
12240 MarketSnapshot::new(Some(d("99")), None, None),
12241 );
12242
12243 assert_eq!(
12244 events.len(),
12245 2,
12246 "the released hold, then the order: {events:?}"
12247 );
12248 assert_eq!(balance_of(&events[0]).balance.free, d("10000"));
12249 assert!(matches!(
12250 &order_of(&events[1]).state,
12251 OrderState::Inactive(InactiveOrderState::Cancelled(cancelled))
12252 if cancelled.filled_quantity == Some(Decimal::ZERO)
12253 ));
12254 assert!(!venue.account.orders().holds(&instrument_name()));
12255 }
12256
12257 #[test]
12258 fn only_a_fresh_observation_releases_a_waiting_order_never_the_clock() {
12259 let mut venue = venue_waiting();
12260 advance(&mut venue, time(12));
12261 let _ = venue.open_order(gtc_buy("waiting", "1"));
12262
12263 advance(&mut venue, time(1_000));
12264
12265 assert!(venue.account.orders().holds(&instrument_name()));
12266 assert!(venue.trades(time(0)).is_empty());
12267 }
12268
12269 #[test]
12270 fn a_waiting_order_still_waiting_at_the_end_of_the_data_expires() {
12271 let mut venue = venue_waiting();
12272 advance(&mut venue, time(12));
12273 let _ = venue.open_order(gtc_buy("waiting", "1"));
12274
12275 let events = venue.expire_held(time(30));
12276
12277 assert_eq!(events.len(), 2, "{events:?}");
12278 assert_eq!(balance_of(&events[0]).balance.free, d("10000"));
12279 assert!(matches!(
12280 order_of(&events[1]).state,
12281 OrderState::Inactive(InactiveOrderState::Expired(_))
12282 ));
12283 }
12284
12285 #[test]
12286 fn only_a_bar_released_under_next_bar_fills_ahead_of_its_observation() {
12287 let bar = ohlc("102", "110", "98", "108");
12288 let fresh_bar = MarketObservation::new(traded("108")).with_bar(Some(bar));
12289 let idle_bar = fresh_bar.with_fresh(false);
12290 let tick = MarketObservation::new(traded("108"));
12291
12292 let mut waiting = venue_waiting();
12294 advance(&mut waiting, time(12));
12295 let _ = waiting.open_order(gtc_buy("waiting", "1"));
12296 assert!(waiting.account.orders().holds(&instrument_name()));
12297 assert!(!waiting.fills_ahead_of(&instrument_name(), &fresh_bar));
12298
12299 let mut held = venue_holding(NextBarPrice::Open, "10000");
12301 assert!(
12302 !held.fills_ahead_of(&instrument_name(), &fresh_bar),
12303 "nothing held yet"
12304 );
12305 let _ = held.open_order(ioc_buy("held", "1"));
12306 assert!(held.fills_ahead_of(&instrument_name(), &fresh_bar));
12307 assert!(held.fills_ahead_of(&instrument_name(), &idle_bar));
12308 assert!(!held.fills_ahead_of(&instrument_name(), &tick));
12309
12310 let mut both = venue_holding_fresh_bars();
12312 let _ = both.open_order(gtc_buy("held", "1"));
12313 assert!(both.fills_ahead_of(&instrument_name(), &fresh_bar));
12314 assert!(!both.fills_ahead_of(&instrument_name(), &idle_bar));
12315 }
12316
12317 fn venue_holding_fresh_bars() -> SimulatedVenue {
12320 let config = MockExecutionConfig {
12321 stale_market: StaleMarketPolicy::Wait { max_age: MAX_AGE },
12322 ..next_bar_config("10000", NextBarPrice::Open)
12323 };
12324 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
12325 advance(&mut venue, time(1));
12326 assert!(feed_bar(&mut venue, 1, ohlc("95", "105", "90", "100")).is_empty());
12327 venue
12328 }
12329
12330 #[test]
12331 fn under_next_bar_an_idle_bar_cancels_held_immediate_orders_and_leaves_the_rest_held() {
12332 let mut venue = venue_holding_fresh_bars();
12333 let _ = venue.open_order(ioc_buy("immediate", "1"));
12334 let _ = venue.open_order(market_order("all", Side::Buy, "1", TimeInForce::FillOrKill));
12335 let _ = venue.open_order(gtc_buy("patient", "1"));
12336
12337 let bar = ohlc("100", "100", "100", "100");
12338 let snapshot = MarketSnapshot::from_last_price(Some(bar.close));
12339 let events = venue.apply_market(
12340 &instrument_name(),
12341 MarketObservation::new(snapshot)
12342 .with_bar(Some(bar))
12343 .with_fresh(false),
12344 time(2),
12345 );
12346
12347 assert_eq!(
12348 events.len(),
12349 4,
12350 "a released hold and a cancel for each: {events:?}"
12351 );
12352 for (order, cid) in [(&events[1], "immediate"), (&events[3], "all")] {
12353 let order = order_of(order);
12354 assert_eq!(order.key.cid, ClientOrderId::new(cid));
12355 assert!(matches!(
12356 &order.state,
12357 OrderState::Inactive(InactiveOrderState::Cancelled(cancelled))
12358 if cancelled.filled_quantity == Some(Decimal::ZERO)
12359 ));
12360 }
12361 assert_eq!(
12362 free_quote(&venue),
12363 d("9900"),
12364 "only the patient order's hold remains"
12365 );
12366 assert_eq!(
12367 venue.account.orders().held_on(&instrument_name()),
12368 [ClientOrderId::new("patient")]
12369 );
12370
12371 let events = feed_bar(&mut venue, 3, ohlc("103", "104", "102", "103"));
12372 assert_eq!(
12373 trade_of(&events[2]).price,
12374 d("103"),
12375 "the next bar something traded in fills it at its open"
12376 );
12377 }
12378
12379 #[test]
12380 fn under_next_bar_an_immediate_order_on_a_stale_market_is_rejected_and_a_patient_one_held() {
12381 let mut venue = venue_holding_fresh_bars();
12382 advance(&mut venue, time(12));
12383
12384 let outcome = venue.open_order(ioc_buy("immediate", "1"));
12385 assert!(
12386 rejection_reason(&outcome).contains("StaleMarketPolicy::Wait"),
12387 "{}",
12388 rejection_reason(&outcome)
12389 );
12390
12391 let outcome = venue.open_order(gtc_buy("patient", "1"));
12392 assert_eq!(rested_open(&outcome).filled_quantity, Decimal::ZERO);
12393 assert!(venue.account.orders().holds(&instrument_name()));
12394 }
12395
12396 fn gtc_limit(
12401 cid: &str,
12402 side: Side,
12403 quantity: &str,
12404 limit: &str,
12405 ) -> OrderRequestOpen<ExchangeId, InstrumentNameExchange> {
12406 limit_request(cid, side, quantity, limit, gtc())
12407 }
12408
12409 fn post_only() -> TimeInForce {
12410 TimeInForce::GoodUntilCancelled { post_only: true }
12411 }
12412
12413 fn resting_on(venue: &SimulatedVenue, side: Side) -> Vec<ClientOrderId> {
12415 venue
12416 .account
12417 .orders()
12418 .resting(&instrument_name(), side)
12419 .map(|order| order.key.cid.clone())
12420 .collect()
12421 }
12422
12423 #[test]
12424 fn a_limit_order_on_a_stale_market_waits_open_holding_what_it_would_cost_arriving_now() {
12425 for (limit, free) in [("105", "9900"), ("95", "9905")] {
12428 let mut venue = venue_waiting();
12429 advance(&mut venue, time(12));
12430
12431 let outcome = venue.open_order(gtc_limit("waiting", Side::Buy, "1", limit));
12432
12433 assert_eq!(rested_open(&outcome).filled_quantity, Decimal::ZERO);
12434 assert_eq!(outcome.events.len(), 1, "{:?}", outcome.events);
12435 assert_eq!(balance_of(&outcome.events[0]).balance.free, d(free));
12436 assert!(venue.trades(time(0)).is_empty());
12437 assert!(venue.account.orders().holds(&instrument_name()));
12438 assert!(
12439 resting_on(&venue, Side::Buy).is_empty(),
12440 "held, so nothing matches it"
12441 );
12442 assert_eq!(
12443 venue
12444 .account
12445 .orders()
12446 .get(&ClientOrderId::new("waiting"))
12447 .unwrap()
12448 .price,
12449 Some(d(limit)),
12450 "held with its limit"
12451 );
12452 }
12453 }
12454
12455 #[test]
12456 fn a_held_limit_order_the_fresh_market_crosses_fills_as_a_taker_at_the_fresh_price() {
12457 let mut venue = venue_waiting();
12458 advance(&mut venue, time(12));
12459 let _ = venue.open_order(gtc_limit("waiting", Side::Buy, "1", "105"));
12460
12461 let events = observe(&mut venue, 20, traded("102"));
12462
12463 assert_eq!(events.len(), 4, "{events:?}");
12464 assert_eq!(balance_of(&events[0]).balance.free, d("9898"));
12465 let trade = trade_of(&events[2]);
12466 assert_eq!(
12467 (trade.price, trade.quantity, trade.time_exchange),
12468 (d("102"), d("1"), time(20)),
12469 "at the fresh market, better than its limit, stamped at the observation"
12470 );
12471 assert!(matches!(
12472 &order_of(&events[3]).state,
12473 OrderState::Inactive(InactiveOrderState::FullyFilled(filled))
12474 if filled.avg_price == Some(d("102"))
12475 ));
12476 assert!(!venue.account.orders().holds(&instrument_name()));
12477 assert!(venue.account.orders().is_empty());
12478 assert_eq!(total_base(&venue), d("11"));
12479 }
12480
12481 #[test]
12482 fn a_held_limit_order_is_capped_at_its_limit_however_far_the_slippage_moves_it() {
12483 let config = MockExecutionConfig {
12484 slippage: SlippageConfig::fixed(d("0.1")).unwrap(),
12485 ..stale_config(MAX_AGE)
12486 };
12487 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
12488 advance(&mut venue, time(1));
12489 assert!(observe(&mut venue, 1, traded("100")).is_empty());
12490 advance(&mut venue, time(12));
12491 let _ = venue.open_order(gtc_limit("waiting", Side::Buy, "1", "105"));
12492
12493 let events = observe(&mut venue, 20, traded("102"));
12495
12496 assert_eq!(trade_of(&events[2]).price, d("105"));
12497 }
12498
12499 #[test]
12500 fn a_held_limit_order_the_fresh_market_does_not_cross_joins_the_book_at_its_limit() {
12501 let mut venue = venue_waiting();
12502 advance(&mut venue, time(12));
12503 let _ = venue.open_order(gtc_limit("waiting", Side::Buy, "1", "105"));
12504
12505 let events = observe(&mut venue, 20, traded("110"));
12506
12507 assert_eq!(
12508 events.len(),
12509 2,
12510 "the hold restated, then the order: {events:?}"
12511 );
12512 assert_eq!(
12513 balance_of(&events[0]).balance.free,
12514 d("9895"),
12515 "now held at its limit, as the maker it rests as"
12516 );
12517 let order = order_of(&events[1]);
12518 assert!(matches!(
12519 &order.state,
12520 OrderState::Active(ActiveOrderState::Open(open))
12521 if open.time_exchange == time(20) && open.filled_quantity.is_zero()
12522 ));
12523 assert!(!venue.account.orders().holds(&instrument_name()));
12524 assert_eq!(
12525 resting_on(&venue, Side::Buy),
12526 [ClientOrderId::new("waiting")]
12527 );
12528
12529 let events = observe(&mut venue, 21, traded("104"));
12530 assert_eq!(
12531 trade_of(&events[2]).price,
12532 d("105"),
12533 "resting, it fills at its limit"
12534 );
12535 }
12536
12537 #[test]
12538 fn a_limit_order_on_a_stale_market_is_decided_by_the_fresh_market_not_the_stale_one() {
12539 let mut venue = venue_waiting();
12542 advance(&mut venue, time(12));
12543 let _ = venue.open_order(gtc_limit("crossing", Side::Buy, "1", "101"));
12544 let _ = venue.open_order(gtc_limit("passive", Side::Buy, "1", "99"));
12545
12546 let events = observe(&mut venue, 20, traded("95"));
12547
12548 let prices = trades_in(&events)
12549 .iter()
12550 .map(|trade| (trade.client_order_id.clone(), trade.price))
12551 .collect::<Vec<_>>();
12552 assert_eq!(
12553 prices,
12554 vec![
12555 (Some(ClientOrderId::new("crossing")), d("95")),
12556 (Some(ClientOrderId::new("passive")), d("95")),
12557 ],
12558 "earliest arrival first"
12559 );
12560 }
12561
12562 #[test]
12563 fn a_held_limit_order_the_fresh_size_cannot_fill_rests_the_rest_at_its_limit() {
12564 let mut venue = venue_waiting();
12565 advance(&mut venue, time(12));
12566 let _ = venue.open_order(gtc_limit("waiting", Side::Buy, "1", "105"));
12567
12568 let events = venue.apply_market(
12569 &instrument_name(),
12570 MarketObservation::new(book("101", "102"))
12571 .with_depth(MarketDepth::new(Some(d("1")), Some(d("0.4")))),
12572 time(20),
12573 );
12574
12575 assert_eq!(events.len(), 4, "{events:?}");
12576 assert_eq!(
12577 balance_of(&events[0]).balance.free,
12578 d("9896.20"),
12579 "0.4 paid at 102 and 0.6 held at 105: 10000 - 40.8 - 63"
12580 );
12581 let trade = trade_of(&events[2]);
12582 assert_eq!(
12583 (trade.price, trade.quantity, trade.order_filled_quantity),
12584 (d("102"), d("0.4"), Some(d("0.4")))
12585 );
12586 assert!(matches!(
12587 &order_of(&events[3]).state,
12588 OrderState::Active(ActiveOrderState::Open(open)) if open.filled_quantity == d("0.4")
12589 ));
12590 assert_eq!(
12591 resting_on(&venue, Side::Buy),
12592 [ClientOrderId::new("waiting")]
12593 );
12594
12595 let events = observe(&mut venue, 21, traded("104"));
12596 let trade = trade_of(&events[2]);
12597 assert_eq!(
12598 (trade.price, trade.quantity, trade.order_filled_quantity),
12599 (d("105"), d("0.6"), Some(d("1"))),
12600 "the remainder fills at its limit, as the maker"
12601 );
12602 assert_eq!(free_quote(&venue), d("9896.20"));
12603 }
12604
12605 #[test]
12606 fn a_held_post_only_order_the_fresh_market_crosses_is_cancelled_and_one_it_does_not_rests() {
12607 let mut venue = venue_waiting();
12608 advance(&mut venue, time(12));
12609 let outcome = venue.open_order(limit_request("maker", Side::Buy, "1", "105", post_only()));
12610 assert_eq!(
12611 balance_of(&outcome.events[0]).balance.free,
12612 d("9895"),
12613 "held at its limit, as the maker it promises to be"
12614 );
12615
12616 let events = observe(&mut venue, 20, traded("104"));
12617
12618 assert_eq!(events.len(), 2, "{events:?}");
12619 assert_eq!(balance_of(&events[0]).balance.free, d("10000"));
12620 assert!(matches!(
12621 &order_of(&events[1]).state,
12622 OrderState::Inactive(InactiveOrderState::Cancelled(cancelled))
12623 if cancelled.filled_quantity == Some(Decimal::ZERO)
12624 ));
12625
12626 advance(&mut venue, time(40));
12627 let _ = venue.open_order(limit_request("passive", Side::Buy, "1", "100", post_only()));
12628 let events = observe(&mut venue, 41, traded("104"));
12629 assert!(matches!(
12630 &order_of(&events[1]).state,
12631 OrderState::Active(ActiveOrderState::Open(_))
12632 ));
12633 assert_eq!(
12634 resting_on(&venue, Side::Buy),
12635 [ClientOrderId::new("passive")]
12636 );
12637 }
12638
12639 #[test]
12640 fn an_immediate_or_cancel_or_fill_or_kill_limit_on_a_stale_market_is_rejected_whether_or_not_it_crosses()
12641 {
12642 for (time_in_force, limit) in [
12643 (TimeInForce::ImmediateOrCancel, "105"),
12644 (TimeInForce::ImmediateOrCancel, "95"),
12645 (TimeInForce::FillOrKill, "105"),
12646 (TimeInForce::FillOrKill, "95"),
12647 ] {
12648 let mut venue = venue_waiting();
12649 advance(&mut venue, time(12));
12650
12651 let outcome =
12652 venue.open_order(limit_request("now", Side::Buy, "1", limit, time_in_force));
12653
12654 assert!(outcome.events.is_empty());
12655 let reason = rejection_reason(&outcome);
12656 assert!(
12657 reason.contains("StaleMarketPolicy::Wait")
12658 && reason.contains(&time_in_force.to_string()),
12659 "{reason}"
12660 );
12661 assert!(venue.account.orders().is_empty());
12662 }
12663 }
12664
12665 #[test]
12666 fn a_limit_order_past_its_deadline_on_a_stale_market_expires_rather_than_waits() {
12667 let mut venue = venue_waiting();
12668 advance(&mut venue, time(12));
12669
12670 let outcome = venue.open_order(limit_request(
12671 "late",
12672 Side::Buy,
12673 "1",
12674 "105",
12675 TimeInForce::GoodTillDate { expiry: time(12) },
12676 ));
12677
12678 assert!(matches!(
12679 outcome.response.state,
12680 OrderState::Inactive(InactiveOrderState::Expired(_))
12681 ));
12682 assert!(venue.account.orders().is_empty());
12683 }
12684
12685 #[test]
12686 fn a_held_limit_order_reaching_its_deadline_expires_and_releases_its_hold() {
12687 let mut venue = venue_waiting();
12688 advance(&mut venue, time(12));
12689 let _ = venue.open_order(limit_request(
12690 "dated",
12691 Side::Buy,
12692 "1",
12693 "105",
12694 TimeInForce::GoodTillDate { expiry: time(15) },
12695 ));
12696
12697 let events = observe(&mut venue, 15, traded("102"));
12698
12699 assert!(trades_in(&events).is_empty(), "{events:?}");
12700 assert!(events.iter().any(|event| matches!(
12701 &event.kind,
12702 AccountEventKind::OrderSnapshot(order)
12703 if matches!(order.0.state, OrderState::Inactive(InactiveOrderState::Expired(_)))
12704 )));
12705 assert_eq!(free_quote(&venue), d("10000"));
12706 assert!(venue.account.orders().is_empty());
12707 }
12708
12709 #[test]
12710 fn a_held_limit_order_can_be_cancelled_and_one_still_held_at_the_end_of_the_data_expires() {
12711 let mut venue = venue_waiting();
12712 advance(&mut venue, time(12));
12713 let _ = venue.open_order(gtc_limit("cancelled", Side::Buy, "1", "105"));
12714 let _ = venue.open_order(gtc_limit("expired", Side::Buy, "1", "95"));
12715
12716 let cancelled = venue.cancel_order(OrderRequestCancel {
12717 key: gtc_limit("cancelled", Side::Buy, "1", "105").key,
12718 state: RequestCancel { id: None },
12719 });
12720 assert!(cancelled.response.state.is_ok());
12721 assert_eq!(free_quote(&venue), d("9905"));
12722 assert_eq!(
12723 venue.account.orders().held_on(&instrument_name()),
12724 [ClientOrderId::new("expired")]
12725 );
12726
12727 let events = venue.expire_held(time(30));
12728 assert_eq!(events.len(), 2, "{events:?}");
12729 assert_eq!(balance_of(&events[0]).balance.free, d("10000"));
12730 assert!(matches!(
12731 order_of(&events[1]).state,
12732 OrderState::Inactive(InactiveOrderState::Expired(_))
12733 ));
12734 assert!(venue.account.orders().is_empty());
12735 }
12736
12737 #[test]
12738 fn a_limit_order_on_an_unpriced_instrument_rests_as_it_does_without_the_policy() {
12739 let mut venue =
12740 SimulatedVenue::new_market_driven(&stale_config(MAX_AGE), spot_instruments());
12741 advance(&mut venue, time(12));
12742
12743 let outcome = venue.open_order(gtc_limit("resting", Side::Buy, "1", "100"));
12744
12745 assert_eq!(rested_open(&outcome).filled_quantity, Decimal::ZERO);
12746 assert!(!venue.account.orders().holds(&instrument_name()));
12747 assert_eq!(
12748 resting_on(&venue, Side::Buy),
12749 [ClientOrderId::new("resting")]
12750 );
12751 }
12752
12753 #[test]
12756 fn an_order_for_a_quantity_that_is_not_positive_is_refused_rather_than_held() {
12757 for (now, quantity) in [(1, "0"), (1, "-1"), (12, "0"), (12, "-1")] {
12758 for request in [
12759 gtc_limit("limit", Side::Buy, quantity, "105"),
12760 gtc_buy("market", quantity),
12761 ] {
12762 let mut venue = venue_waiting();
12763 advance(&mut venue, time(now));
12764
12765 let outcome = venue.open_order(request);
12766
12767 assert_eq!(
12768 rejection_reason(&outcome),
12769 format!(
12770 "cannot open {} for {EXCHANGE}: quantity {quantity} is not positive",
12771 instrument_name()
12772 )
12773 );
12774 assert!(outcome.events.is_empty(), "a refused order moves nothing");
12775 assert!(
12776 venue.account.orders().is_empty(),
12777 "and is neither held nor rested"
12778 );
12779 assert_eq!(free_quote(&venue), d("10000"));
12780 assert!(
12781 observe(&mut venue, 20, traded("102")).is_empty(),
12782 "so a fresh observation has nothing to release"
12783 );
12784 }
12785 }
12786 }
12787
12788 #[test]
12789 fn a_held_limit_order_the_account_can_no_longer_afford_is_cancelled_and_hands_back_its_draw() {
12790 let config = MockExecutionConfig {
12791 slippage: uniform(5),
12792 ..MockExecutionConfig {
12793 stale_market: StaleMarketPolicy::Wait { max_age: MAX_AGE },
12794 ..spot_config("10", "100", FeeModelConfig::default())
12795 }
12796 };
12797 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
12798 advance(&mut venue, time(1));
12799 assert!(observe(&mut venue, 1, traded("100")).is_empty());
12800 advance(&mut venue, time(12));
12801 let outcome = venue.open_order(gtc_limit("waiting", Side::Buy, "1", "105"));
12803 assert_eq!(balance_of(&outcome.events[0]).balance.free, Decimal::ZERO);
12804
12805 let events = observe(&mut venue, 20, traded("104"));
12806
12807 assert_eq!(
12808 events.len(),
12809 2,
12810 "the released hold, then the order: {events:?}"
12811 );
12812 assert_eq!(balance_of(&events[0]).balance.free, d("100"));
12813 assert!(matches!(
12814 &order_of(&events[1]).state,
12815 OrderState::Inactive(InactiveOrderState::Cancelled(cancelled))
12816 if cancelled.filled_quantity == Some(Decimal::ZERO)
12817 ));
12818 assert!(venue.account.orders().is_empty());
12819 assert_eq!(venue.slippage, Slippage::new(uniform(5)));
12820 }
12821
12822 #[test]
12823 fn a_held_sell_limit_order_fills_at_the_fresh_price_or_rests_at_its_limit() {
12824 let mut venue = venue_waiting();
12825 advance(&mut venue, time(12));
12826 let _ = venue.open_order(gtc_limit("crossed", Side::Sell, "1", "95"));
12828 let _ = venue.open_order(gtc_limit("passive", Side::Sell, "1", "99"));
12829
12830 let events = observe(&mut venue, 20, traded("97"));
12831
12832 let prices = trades_in(&events)
12833 .iter()
12834 .map(|trade| (trade.client_order_id.clone(), trade.price))
12835 .collect::<Vec<_>>();
12836 assert_eq!(
12837 prices,
12838 vec![(Some(ClientOrderId::new("crossed")), d("97"))],
12839 "above its limit, at the fresh price"
12840 );
12841 assert_eq!(
12842 resting_on(&venue, Side::Sell),
12843 [ClientOrderId::new("passive")]
12844 );
12845 assert_eq!(total_base(&venue), d("9"));
12846 }
12847
12848 #[test]
12849 fn a_held_limit_order_paying_its_fee_in_a_third_asset_takes_the_fee_apart_at_its_release() {
12850 let config = MockExecutionConfig {
12851 stale_market: StaleMarketPolicy::Wait { max_age: MAX_AGE },
12852 ..fee_asset_config("10")
12853 };
12854 let mut venue = fee_asset_venue_from(&config, true);
12855 advance(&mut venue, time(12));
12856 let _ = venue.open_order(gtc_limit("waiting", Side::Buy, "1", "51000"));
12857
12858 let events = observe(&mut venue, 20, traded("50000"));
12859
12860 assert_eq!(
12861 restated(&events)
12862 .iter()
12863 .map(|(asset, _)| asset.clone())
12864 .collect::<Vec<_>>(),
12865 [quote(), base(), bnb()],
12866 "paid, received, then the fee: {events:?}"
12867 );
12868 let trade = trades_in(&events)[0];
12869 assert_eq!(trade.price, d("50000"));
12870 assert_eq!(
12872 restated(&events)[2].1.free,
12873 d("10") - d("50") * d("0.75") / d("600")
12874 );
12875 assert_eq!(restated(&events)[0].1.total, d("50000"), "no fee in USDT");
12876 }
12877
12878 fn cfd_venue_with(usd: &str, stale_market: StaleMarketPolicy) -> SimulatedVenue {
12879 let config = MockExecutionConfig {
12880 stale_market,
12881 ..config_from_balances(vec![funded(self::usd(), d(usd))], FeeModelConfig::default())
12882 };
12883 let mut venue =
12884 SimulatedVenue::new_market_driven(&config, instruments_of(cfd_instrument()));
12885 advance(&mut venue, time(1));
12886 venue
12887 }
12888
12889 #[test]
12893 fn a_held_cfd_limit_order_settles_against_the_position_at_its_release_as_on_arrival() {
12894 let fresh = MarketObservation::new(MarketSnapshot {
12895 best_bid: Some(d("4950")),
12896 best_ask: Some(d("4960")),
12897 last_price: Some(d("4950")),
12898 });
12899 for depth in [MarketDepth::UNKNOWN, MarketDepth::new(Some(d("1.5")), None)] {
12900 let observation = fresh.with_depth(depth);
12901
12902 let mut held = cfd_venue_with(
12903 "400000",
12904 StaleMarketPolicy::Wait {
12905 max_age: Duration::from_secs(10),
12906 },
12907 );
12908 assert!(cfd_market(&mut held, "5000", "5000", "5000").is_empty());
12909 cfd_market_fill(&mut held, Side::Buy, "1");
12910 advance(&mut held, time(12));
12911 let outcome = held.open_order(cfd_limit("flip", Side::Sell, "2", "4900"));
12912 assert!(outcome.response.state.is_accepted());
12913 assert!(held.account.orders().holds(&cfd_instrument_name()));
12914 let released = held.apply_market(&cfd_instrument_name(), observation, time(12));
12915 assert!(!trades_in(&released).is_empty(), "{released:?}");
12916
12917 let mut arriving = cfd_venue_with("400000", StaleMarketPolicy::Off);
12918 assert!(cfd_market(&mut arriving, "5000", "5000", "5000").is_empty());
12919 cfd_market_fill(&mut arriving, Side::Buy, "1");
12920 advance(&mut arriving, time(12));
12921 assert!(
12922 arriving
12923 .apply_market(&cfd_instrument_name(), observation, time(12))
12924 .is_empty()
12925 );
12926 let _ = arriving.open_order(cfd_limit("flip", Side::Sell, "2", "4900"));
12927
12928 assert_eq!(
12929 (free_usd(&held), total_usd(&held), cfd_position(&held)),
12930 (
12931 free_usd(&arriving),
12932 total_usd(&arriving),
12933 cfd_position(&arriving)
12934 ),
12935 "depth {depth:?}"
12936 );
12937 assert_eq!(
12938 resting_count(&held),
12939 resting_count(&arriving),
12940 "depth {depth:?}"
12941 );
12942 }
12943 }
12944
12945 fn resting_count(venue: &SimulatedVenue) -> usize {
12946 venue
12947 .account
12948 .orders()
12949 .resting(&cfd_instrument_name(), Side::Sell)
12950 .count()
12951 }
12952
12953 #[test]
12954 fn under_wait_an_observation_that_is_not_fresh_crosses_no_resting_order() {
12955 let mut venue = venue_waiting();
12956 let _ = venue.open_order(gtc_limit("resting", Side::Buy, "1", "99"));
12957 let _ = venue.open_order(gtc_limit("selling", Side::Sell, "1", "101"));
12958
12959 assert!(observe_idle(&mut venue, 5, traded("95")).is_empty());
12960 assert!(observe_idle(&mut venue, 6, traded("105")).is_empty());
12961 assert_eq!(
12962 resting_on(&venue, Side::Buy),
12963 [ClientOrderId::new("resting")]
12964 );
12965 assert_eq!(
12966 resting_on(&venue, Side::Sell),
12967 [ClientOrderId::new("selling")]
12968 );
12969
12970 let events = observe(&mut venue, 7, traded("98"));
12971 assert_eq!(
12972 trade_of(&events[2]).price,
12973 d("99"),
12974 "a fresh observation crosses it"
12975 );
12976 }
12977
12978 #[test]
12979 fn without_the_policy_an_observation_that_is_not_fresh_still_crosses_a_resting_order() {
12980 let mut venue = SimulatedVenue::new_market_driven(
12981 &spot_config("10", "10000", FeeModelConfig::default()),
12982 spot_instruments(),
12983 );
12984 advance(&mut venue, time(1));
12985 assert!(observe(&mut venue, 1, traded("100")).is_empty());
12986 let _ = venue.open_order(gtc_limit("resting", Side::Buy, "1", "99"));
12987
12988 let events = observe_idle(&mut venue, 5, traded("95"));
12989
12990 assert_eq!(trade_of(&events[2]).price, d("99"));
12991 }
12992
12993 #[test]
12994 fn a_held_limit_order_is_not_released_by_an_observation_that_is_not_fresh() {
12995 let mut venue = venue_waiting();
12996 advance(&mut venue, time(12));
12997 let _ = venue.open_order(gtc_limit("waiting", Side::Buy, "1", "105"));
12998
12999 assert!(observe_idle(&mut venue, 13, traded("90")).is_empty());
13000 assert!(venue.account.orders().holds(&instrument_name()));
13001
13002 let events = observe(&mut venue, 14, traded("103"));
13003 assert_eq!(trade_of(&events[2]).price, d("103"));
13004 }
13005
13006 #[test]
13007 fn under_next_bar_a_limit_order_on_a_stale_market_is_decided_against_the_next_fresh_bar() {
13008 let mut venue = venue_holding_fresh_bars();
13009 advance(&mut venue, time(12));
13010 let _ = venue.open_order(gtc_limit("crossed", Side::Buy, "1", "104"));
13011 let _ = venue.open_order(gtc_limit("passive", Side::Buy, "1", "101"));
13012 let bar = ohlc("102", "106", "101", "105");
13013 assert!(venue.fills_ahead_of(
13014 &instrument_name(),
13015 &MarketObservation::new(traded("105")).with_bar(Some(bar))
13016 ));
13017
13018 let events = feed_bar(&mut venue, 13, bar);
13019
13020 let prices = trades_in(&events)
13021 .iter()
13022 .map(|trade| (trade.client_order_id.clone(), trade.price))
13023 .collect::<Vec<_>>();
13024 assert_eq!(
13025 prices,
13026 vec![(Some(ClientOrderId::new("crossed")), d("102"))],
13027 "the open crosses 104 and fills it there; it does not cross 101, which rests"
13028 );
13029 assert_eq!(
13030 resting_on(&venue, Side::Buy),
13031 [ClientOrderId::new("passive")]
13032 );
13033 }
13034
13035 #[test]
13036 fn under_next_bar_a_held_limit_order_its_open_does_not_cross_can_be_crossed_by_its_close() {
13037 let mut venue = venue_holding_fresh_bars();
13038 advance(&mut venue, time(12));
13039 let _ = venue.open_order(gtc_limit("waiting", Side::Buy, "1", "101"));
13040
13041 let events = feed_bar(&mut venue, 13, ohlc("102", "103", "99", "100"));
13042
13043 let trades = trades_in(&events);
13044 assert_eq!(trades.len(), 1, "{events:?}");
13045 assert_eq!(
13046 trades[0].price,
13047 d("101"),
13048 "rested at its limit when the open did not reach it, then crossed by the close"
13049 );
13050 assert!(venue.account.orders().is_empty());
13051 }
13052
13053 #[test]
13054 fn under_next_bar_a_limit_order_on_a_fresh_market_is_decided_on_arrival() {
13055 let mut venue = venue_holding_fresh_bars();
13056
13057 let outcome = venue.open_order(gtc_limit("crossing", Side::Buy, "1", "104"));
13058
13059 assert_eq!(filled_trade(&outcome).price, d("100"));
13060 }
13061
13062 #[test]
13063 fn a_held_limit_buy_that_rests_a_remainder_holds_the_loan_left_against_it() {
13064 let config = MockExecutionConfig {
13065 stale_market: StaleMarketPolicy::Wait { max_age: MAX_AGE },
13066 ..margin_config("0", "100", "2")
13067 };
13068 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
13069 advance(&mut venue, time(1));
13070 assert!(observe(&mut venue, 1, traded("100")).is_empty());
13071 advance(&mut venue, time(12));
13072 let outcome = venue.open_order(gtc_limit("waiting", Side::Buy, "1.5", "100"));
13074 assert_eq!(
13075 balance_of(sans_debt(&outcome.events)[0]).balance,
13076 Balance::new_margin(d("150"), Decimal::ZERO, d("50"), Decimal::ZERO)
13077 );
13078
13079 let events = venue.apply_market(
13081 &instrument_name(),
13082 MarketObservation::new(book("79", "80"))
13083 .with_depth(MarketDepth::new(Some(d("1")), Some(d("0.5")))),
13084 time(20),
13085 );
13086 assert_eq!(
13087 balance_of(sans_debt(&events)[0]).balance,
13088 Balance::new_margin(d("100"), Decimal::ZERO, d("40"), Decimal::ZERO)
13089 );
13090 assert_eq!(trade_of(sans_debt(&events)[2]).quantity, d("0.5"));
13091
13092 let cancelled = venue.cancel_order(OrderRequestCancel {
13093 key: gtc_limit("waiting", Side::Buy, "1.5", "100").key,
13094 state: RequestCancel { id: None },
13095 });
13096 assert!(cancelled.response.state.is_ok());
13097 assert_eq!(
13098 venue.balances(&[quote()])[0].balance,
13099 Balance::new_margin(d("60"), d("60"), Decimal::ZERO, Decimal::ZERO),
13100 "cancelling the remainder repays the 40 it held"
13101 );
13102 }
13103
13104 #[test]
13105 fn cancelling_a_released_remainder_repays_only_its_own_loan_and_not_one_already_owed() {
13106 let config = MockExecutionConfig {
13108 stale_market: StaleMarketPolicy::Wait { max_age: MAX_AGE },
13109 spot_margin: Some(
13110 SpotMarginConfig::new(quote(), d("2"), MarginEffect::Borrow).unwrap(),
13111 ),
13112 ..config_from_balances(
13113 vec![
13114 funded(base(), Decimal::ZERO),
13115 AssetBalance::new(
13116 quote(),
13117 Balance::new_margin(d("120"), d("120"), d("20"), Decimal::ZERO),
13118 Utc::now(),
13119 ),
13120 ],
13121 FeeModelConfig::default(),
13122 )
13123 };
13124 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
13125 advance(&mut venue, time(1));
13126 assert!(observe(&mut venue, 1, traded("100")).is_empty());
13127 advance(&mut venue, time(12));
13128 let _ = venue.open_order(gtc_limit("waiting", Side::Buy, "1.5", "100"));
13130
13131 let events = venue.apply_market(
13133 &instrument_name(),
13134 MarketObservation::new(book("79", "80"))
13135 .with_depth(MarketDepth::new(Some(d("1")), Some(d("0.5")))),
13136 time(20),
13137 );
13138 assert_eq!(
13139 balance_of(&events[0]).balance,
13140 Balance::new_margin(d("100"), Decimal::ZERO, d("40"), Decimal::ZERO)
13141 );
13142
13143 let cancelled = venue.cancel_order(OrderRequestCancel {
13144 key: gtc_limit("waiting", Side::Buy, "1.5", "100").key,
13145 state: RequestCancel { id: None },
13146 });
13147 assert!(cancelled.response.state.is_ok());
13148 assert_eq!(
13149 venue.balances(&[quote()])[0].balance,
13150 Balance::new_margin(d("80"), d("80"), d("20"), Decimal::ZERO),
13151 "the remainder repays the 20 it borrowed, and the 20 owed before stays owed"
13152 );
13153 }
13154
13155 fn margin_config(btc: &str, usdt: &str, max_leverage: &str) -> MockExecutionConfig {
13160 MockExecutionConfig {
13161 spot_margin: Some(
13162 SpotMarginConfig::new(quote(), d(max_leverage), MarginEffect::Borrow).unwrap(),
13163 ),
13164 ..spot_config(btc, usdt, FeeModelConfig::default())
13165 }
13166 }
13167
13168 fn margin_venue(btc: &str, usdt: &str, max_leverage: &str) -> SimulatedVenue {
13170 SimulatedVenue::new(&margin_config(btc, usdt, max_leverage), spot_instruments())
13171 }
13172
13173 fn margin_market_venue(btc: &str, usdt: &str) -> SimulatedVenue {
13175 let mut venue =
13176 SimulatedVenue::new_market_driven(&margin_config(btc, usdt, "3"), spot_instruments());
13177 advance(&mut venue, time(1));
13178 assert!(
13179 venue
13180 .apply_market(
13181 &instrument_name(),
13182 MarketObservation::new(book("49000", "49100")),
13183 time(1)
13184 )
13185 .is_empty()
13186 );
13187 venue
13188 }
13189
13190 fn balance_now(venue: &SimulatedVenue, asset: AssetNameExchange) -> Balance {
13192 venue.balances(&[asset])[0].balance
13193 }
13194
13195 fn insufficient_reason(outcome: &OpenOutcome) -> &str {
13197 match &outcome.response.state {
13198 OrderState::Inactive(InactiveOrderState::OpenFailed(OrderError::Rejected(
13199 ApiError::BalanceInsufficient(_, reason),
13200 ))) => reason,
13201 other => panic!("expected BalanceInsufficient, got: {other:?}"),
13202 }
13203 }
13204
13205 #[test]
13206 fn a_cash_venue_reports_no_loans_and_refuses_a_sell_of_base_it_lacks() {
13207 let mut venue = make_venue("0.25", "100000");
13208
13209 let outcome = venue.open_order(sell_request("1", market_prices("50000")));
13210
13211 assert!(
13212 !insufficient_reason(&outcome).contains("Borrowable"),
13213 "{}",
13214 insufficient_reason(&outcome)
13215 );
13216 assert_eq!(
13217 balance_now(&venue, base()),
13218 Balance::new(d("0.25"), d("0.25"))
13219 );
13220 }
13221
13222 #[test]
13223 fn a_margin_sell_borrows_only_the_shortfall_of_base() {
13224 let mut venue = margin_venue("0.25", "100000", "3");
13225
13226 let outcome = venue.open_order(sell_request("1", market_prices("50000")));
13227
13228 assert_eq!(
13229 filled_balance(&outcome).balance,
13230 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.75"), Decimal::ZERO),
13231 "the sell delivers all the base it borrowed and what the account held"
13232 );
13233 assert_eq!(
13234 filled_received(&outcome).balance,
13235 Balance::new_margin(d("150000"), d("150000"), Decimal::ZERO, Decimal::ZERO)
13236 );
13237 assert_eq!(filled_trade(&outcome).quantity, d("1"));
13238 }
13239
13240 #[test]
13241 fn a_margin_sell_of_base_the_account_never_held_borrows_it() {
13242 let config = MockExecutionConfig {
13243 spot_margin: Some(
13244 SpotMarginConfig::new(quote(), d("3"), MarginEffect::Borrow).unwrap(),
13245 ),
13246 ..config_from_balances(vec![funded(quote(), d("10000"))], FeeModelConfig::default())
13247 };
13248 let mut venue = SimulatedVenue::new(&config, spot_instruments());
13249
13250 let outcome = venue.open_order(sell_request("0.1", market_prices("50000")));
13251
13252 assert_eq!(
13253 filled_balance(&outcome).balance,
13254 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.1"), Decimal::ZERO),
13255 "the sell opens a base balance with the loan, and delivers what it borrowed"
13256 );
13257 }
13258
13259 #[test]
13260 fn a_margin_buy_borrows_quote_valued_through_the_inverse_of_its_pair() {
13261 let config = MockExecutionConfig {
13263 spot_margin: Some(SpotMarginConfig::new(base(), d("2"), MarginEffect::Borrow).unwrap()),
13264 ..spot_config("1", "0", FeeModelConfig::default())
13265 };
13266 let mut venue = SimulatedVenue::new(&config, spot_instruments());
13267
13268 let outcome = venue.open_order(buy_request("1.5", market_prices("50000")));
13270 assert!(
13271 insufficient_reason(&outcome).contains("Borrowable: 50000"),
13272 "{}",
13273 insufficient_reason(&outcome)
13274 );
13275
13276 let outcome = venue.open_order(buy_request("0.5", market_prices("50000")));
13277 assert_eq!(
13278 filled_balance(&outcome).balance,
13279 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("25000"), Decimal::ZERO)
13280 );
13281 assert_eq!(balance_now(&venue, base()).total, d("1.5"));
13282 }
13283
13284 #[test]
13285 fn a_request_snapshot_values_only_the_pair_it_names() {
13286 let venue = SimulatedVenue::new(
13288 &spot_config("1", "0", FeeModelConfig::default()),
13289 spot_instruments(),
13290 );
13291 let snapshot = market_prices("50000").unwrap();
13292 let eth = || AssetNameExchange::new("ETH");
13293 let price_in = |asset: AssetNameExchange,
13294 valuation: AssetNameExchange,
13295 priced: Underlying<AssetNameExchange>| {
13296 venue.price_in(&asset, &valuation, Some((&priced, &snapshot)))
13297 };
13298
13299 assert_eq!(
13300 price_in(base(), quote(), Underlying::new(base(), quote())),
13301 Some(d("50000"))
13302 );
13303 assert_eq!(
13304 price_in(quote(), base(), Underlying::new(base(), quote())),
13305 Some(d("0.00002")),
13306 "valued through the inverse of the pair the snapshot names"
13307 );
13308 assert_eq!(
13309 price_in(base(), quote(), Underlying::new(base(), eth())),
13310 None,
13311 "a snapshot of another pair sharing the base prices nothing"
13312 );
13313 assert_eq!(
13314 price_in(base(), quote(), Underlying::new(eth(), quote())),
13315 None,
13316 "a snapshot of another pair sharing the quote prices nothing"
13317 );
13318 }
13319
13320 #[test]
13321 fn a_venue_reads_back_the_instruments_it_was_constructed_with() {
13322 let instruments = fee_asset_instruments();
13323 let venue = SimulatedVenue::new(
13324 &spot_config("1", "0", FeeModelConfig::default()),
13325 instruments.clone(),
13326 );
13327
13328 assert_eq!(venue.instruments(), &instruments);
13329 assert_eq!(
13330 venue.instrument(&instrument_name()),
13331 instruments.get(&instrument_name())
13332 );
13333 assert!(venue.instrument(&instrument_name()).is_some());
13334 assert_eq!(
13335 venue.instrument(&InstrumentNameExchange::new("ETHUSDT")),
13336 None
13337 );
13338 }
13339
13340 #[test]
13341 fn a_margin_buy_repays_interest_then_principal_and_frees_the_rest() {
13342 let config = MockExecutionConfig {
13343 spot_margin: Some(
13344 SpotMarginConfig::new(quote(), d("3"), MarginEffect::Borrow).unwrap(),
13345 ),
13346 ..config_from_balances(
13347 vec![
13348 AssetBalance::new(
13349 base(),
13350 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.75"), d("0.01")),
13351 Utc::now(),
13352 ),
13353 funded(quote(), d("100000")),
13354 ],
13355 FeeModelConfig::default(),
13356 )
13357 };
13358 let mut venue = SimulatedVenue::new(&config, spot_instruments());
13359
13360 let outcome = venue.open_order(with_effect(
13361 buy_request("1", market_prices("50000")),
13362 MarginEffect::Repay,
13363 ));
13364
13365 assert_eq!(
13366 filled_received(&outcome).balance,
13367 Balance::new_margin(d("0.24"), d("0.24"), Decimal::ZERO, Decimal::ZERO),
13368 "1 BTC repays 0.01 of interest and 0.75 of principal, leaving 0.24 free"
13369 );
13370 }
13371
13372 #[test]
13373 fn a_buy_smaller_than_the_debt_repays_interest_before_principal() {
13374 let config = MockExecutionConfig {
13375 spot_margin: Some(
13376 SpotMarginConfig::new(quote(), d("3"), MarginEffect::Borrow).unwrap(),
13377 ),
13378 ..config_from_balances(
13379 vec![
13380 AssetBalance::new(
13381 base(),
13382 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.75"), d("0.01")),
13383 Utc::now(),
13384 ),
13385 funded(quote(), d("100000")),
13386 ],
13387 FeeModelConfig::default(),
13388 )
13389 };
13390 let mut venue = SimulatedVenue::new(&config, spot_instruments());
13391
13392 let outcome = venue.open_order(with_effect(
13393 buy_request("0.11", market_prices("50000")),
13394 MarginEffect::Repay,
13395 ));
13396
13397 assert_eq!(
13398 filled_received(&outcome).balance,
13399 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.65"), Decimal::ZERO)
13400 );
13401 }
13402
13403 #[test]
13404 fn a_borrow_beyond_the_leverage_limit_is_refused_naming_what_would_be_lent() {
13405 let mut venue = margin_venue("0", "10000", "2");
13407
13408 let outcome = venue.open_order(sell_request("0.3", market_prices("50000")));
13409 assert!(
13410 insufficient_reason(&outcome).contains("Borrowable: 0.2"),
13411 "{}",
13412 insufficient_reason(&outcome)
13413 );
13414 assert_eq!(
13415 balance_now(&venue, base()),
13416 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, Decimal::ZERO, Decimal::ZERO),
13417 "a refused order borrows nothing"
13418 );
13419
13420 let outcome = venue.open_order(sell_request("0.2", market_prices("50000")));
13421 assert_eq!(filled_trade(&outcome).quantity, d("0.2"));
13422 }
13423
13424 #[test]
13425 fn debt_already_owed_counts_against_what_the_venue_lends() {
13426 let mut venue = margin_venue("0", "10000", "2");
13428 let outcome = venue.open_order(sell_request("0.15", market_prices("50000")));
13429 assert_eq!(filled_trade(&outcome).quantity, d("0.15"));
13430
13431 let outcome = venue.open_order(sell_request("0.06", market_prices("50000")));
13433 assert!(
13434 insufficient_reason(&outcome).contains("Borrowable: 0.05"),
13435 "{}",
13436 insufficient_reason(&outcome)
13437 );
13438 }
13439
13440 #[test]
13441 fn a_leverage_of_one_lends_nothing_but_every_balance_reports_its_loan() {
13442 let mut venue = margin_venue("0", "10000", "1");
13443
13444 for balance in venue.balances(&[base(), quote()]) {
13445 assert_eq!(
13446 balance.balance.margin,
13447 Some(MarginDetails::default()),
13448 "{balance:?}"
13449 );
13450 }
13451
13452 let outcome = venue.open_order(sell_request("0.1", market_prices("50000")));
13453 assert!(
13454 insufficient_reason(&outcome).contains("Borrowable: 0"),
13455 "{}",
13456 insufficient_reason(&outcome)
13457 );
13458 }
13459
13460 #[test]
13461 fn no_side_effect_lends_nothing_and_repays_nothing() {
13462 let config = MockExecutionConfig {
13463 spot_margin: Some(
13464 SpotMarginConfig::new(quote(), d("3"), MarginEffect::NoSideEffect).unwrap(),
13465 ),
13466 ..config_from_balances(
13467 vec![
13468 AssetBalance::new(
13469 base(),
13470 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.5"), Decimal::ZERO),
13471 Utc::now(),
13472 ),
13473 funded(quote(), d("100000")),
13474 ],
13475 FeeModelConfig::default(),
13476 )
13477 };
13478 let mut venue = SimulatedVenue::new(&config, spot_instruments());
13479
13480 let outcome = venue.open_order(sell_request("0.1", market_prices("50000")));
13481 assert!(
13482 !insufficient_reason(&outcome).contains("Borrowable"),
13483 "{}",
13484 insufficient_reason(&outcome)
13485 );
13486
13487 let outcome = venue.open_order(buy_request("1", market_prices("50000")));
13488 assert_eq!(
13489 filled_received(&outcome).balance,
13490 Balance::new_margin(d("1"), d("1"), d("0.5"), Decimal::ZERO),
13491 "the bought base is free; the loan is left as it was"
13492 );
13493 }
13494
13495 #[test]
13496 fn an_asset_the_venue_cannot_value_is_not_lent() {
13497 let config = MockExecutionConfig {
13499 spot_margin: Some(
13500 SpotMarginConfig::new(AssetNameExchange::new("EUR"), d("3"), MarginEffect::Borrow)
13501 .unwrap(),
13502 ),
13503 ..spot_config("0", "100000", FeeModelConfig::default())
13504 };
13505 let mut venue = SimulatedVenue::new(&config, spot_instruments());
13506
13507 let outcome = venue.open_order(sell_request("0.1", market_prices("50000")));
13508 assert!(
13509 insufficient_reason(&outcome).contains("Borrowable: 0"),
13510 "{}",
13511 insufficient_reason(&outcome)
13512 );
13513 }
13514
13515 #[test]
13516 fn a_cfd_order_is_not_lent_its_margin() {
13517 let config = MockExecutionConfig {
13518 spot_margin: Some(SpotMarginConfig::new(usd(), d("3"), MarginEffect::Borrow).unwrap()),
13519 ..config_from_balances(vec![funded(usd(), d("1000"))], FeeModelConfig::default())
13520 };
13521 let mut venue = SimulatedVenue::new(&config, instruments_of(cfd_instrument()));
13522
13523 let outcome = venue.open_order(cfd_request(Side::Sell, "1", market_prices("5000")));
13524 assert!(
13525 insufficient_reason(&outcome).contains("Borrowable: 0"),
13526 "{}",
13527 insufficient_reason(&outcome)
13528 );
13529 assert_eq!(
13530 balance_now(&venue, usd()),
13531 Balance::new_margin(d("1000"), d("1000"), Decimal::ZERO, Decimal::ZERO)
13532 );
13533 }
13534
13535 fn open_error(outcome: &OpenOutcome) -> &UnindexedOrderError {
13537 match &outcome.response.state {
13538 OrderState::Inactive(InactiveOrderState::OpenFailed(error)) => error,
13539 other => panic!("expected the order to be refused, got: {other:?}"),
13540 }
13541 }
13542
13543 fn config_owing_base(effect: MarginEffect) -> MockExecutionConfig {
13545 MockExecutionConfig {
13546 spot_margin: Some(SpotMarginConfig::new(quote(), d("3"), effect).unwrap()),
13547 ..config_from_balances(
13548 vec![
13549 AssetBalance::new(
13550 base(),
13551 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.75"), d("0.01")),
13552 Utc::now(),
13553 ),
13554 funded(quote(), d("100000")),
13555 ],
13556 FeeModelConfig::default(),
13557 )
13558 }
13559 }
13560
13561 fn market_venue_for(config: &MockExecutionConfig) -> SimulatedVenue {
13563 let mut venue = SimulatedVenue::new_market_driven(config, spot_instruments());
13564 advance(&mut venue, time(1));
13565 assert!(
13566 venue
13567 .apply_market(
13568 &instrument_name(),
13569 MarketObservation::new(book("49000", "49100")),
13570 time(1),
13571 )
13572 .is_empty()
13573 );
13574 venue
13575 }
13576
13577 #[test]
13578 fn a_borrow_order_repays_nothing_from_what_it_receives() {
13579 let mut venue =
13580 SimulatedVenue::new(&config_owing_base(MarginEffect::Borrow), spot_instruments());
13581
13582 let outcome = venue.open_order(buy_request("1", market_prices("50000")));
13583
13584 assert_eq!(
13585 filled_received(&outcome).balance,
13586 Balance::new_margin(d("1"), d("1"), d("0.75"), d("0.01")),
13587 "the bought base is free and the loan, interest included, is left owed"
13588 );
13589 }
13590
13591 #[test]
13592 fn a_request_effect_overrides_the_configured_default() {
13593 let mut venue = margin_venue("0", "100000", "3");
13595 let outcome = venue.open_order(with_effect(
13596 sell_request("1", market_prices("50000")),
13597 MarginEffect::NoSideEffect,
13598 ));
13599 assert!(
13600 !insufficient_reason(&outcome).contains("Borrowable"),
13601 "{}",
13602 insufficient_reason(&outcome)
13603 );
13604
13605 let config = MockExecutionConfig {
13607 spot_margin: Some(
13608 SpotMarginConfig::new(quote(), d("3"), MarginEffect::NoSideEffect).unwrap(),
13609 ),
13610 ..spot_config("0", "100000", FeeModelConfig::default())
13611 };
13612 let mut venue = SimulatedVenue::new(&config, spot_instruments());
13613 let outcome = venue.open_order(with_effect(
13614 sell_request("1", market_prices("50000")),
13615 MarginEffect::Borrow,
13616 ));
13617 assert_eq!(
13618 filled_balance(&outcome).balance,
13619 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("1"), Decimal::ZERO)
13620 );
13621 }
13622
13623 #[test]
13624 fn a_repay_order_borrows_nothing() {
13625 let mut venue = margin_venue("0.25", "100000", "3");
13626
13627 let outcome = venue.open_order(with_effect(
13628 sell_request("1", market_prices("50000")),
13629 MarginEffect::Repay,
13630 ));
13631
13632 assert!(
13633 !insufficient_reason(&outcome).contains("Borrowable"),
13634 "{}",
13635 insufficient_reason(&outcome)
13636 );
13637 assert_eq!(
13638 balance_now(&venue, base()),
13639 Balance::new_margin(d("0.25"), d("0.25"), Decimal::ZERO, Decimal::ZERO),
13640 "a refused order borrows nothing"
13641 );
13642 }
13643
13644 #[test]
13645 fn a_cfd_order_naming_a_borrow_or_repay_is_refused_unsent() {
13646 let config = MockExecutionConfig {
13647 spot_margin: Some(SpotMarginConfig::new(usd(), d("3"), MarginEffect::Borrow).unwrap()),
13648 ..config_from_balances(vec![funded(usd(), d("1000"))], FeeModelConfig::default())
13649 };
13650 let mut venue = SimulatedVenue::new(&config, instruments_of(cfd_instrument()));
13651
13652 for effect in [MarginEffect::Borrow, MarginEffect::Repay] {
13653 let outcome = venue.open_order(with_effect(
13654 cfd_request(Side::Buy, "1", market_prices("5000")),
13655 effect,
13656 ));
13657 assert_eq!(
13658 open_error(&outcome),
13659 &UnindexedOrderError::UnsupportedMarginEffect(effect)
13660 );
13661 }
13662 assert_eq!(
13663 balance_now(&venue, usd()),
13664 Balance::new_margin(d("1000"), d("1000"), Decimal::ZERO, Decimal::ZERO),
13665 "a refused order touches nothing"
13666 );
13667
13668 let outcome = venue.open_order(with_effect(
13671 cfd_request(Side::Buy, "0.01", market_prices("5000")),
13672 MarginEffect::NoSideEffect,
13673 ));
13674 assert!(
13675 !matches!(
13676 &outcome.response.state,
13677 OrderState::Inactive(InactiveOrderState::OpenFailed(
13678 UnindexedOrderError::UnsupportedMarginEffect(_)
13679 ))
13680 ),
13681 "{:?}",
13682 outcome.response.state
13683 );
13684 }
13685
13686 #[test]
13687 fn a_cash_venue_takes_no_effect_but_no_side_effect() {
13688 let mut venue = make_venue("1", "100000");
13689
13690 for effect in [MarginEffect::Borrow, MarginEffect::Repay] {
13691 let outcome = venue.open_order(with_effect(
13692 sell_request("0.1", market_prices("50000")),
13693 effect,
13694 ));
13695 assert_eq!(
13696 open_error(&outcome),
13697 &UnindexedOrderError::UnsupportedMarginEffect(effect)
13698 );
13699 }
13700
13701 let outcome = venue.open_order(with_effect(
13702 sell_request("0.1", market_prices("50000")),
13703 MarginEffect::NoSideEffect,
13704 ));
13705 assert_eq!(filled_trade(&outcome).quantity, d("0.1"));
13706 }
13707
13708 #[test]
13709 fn a_resting_order_repays_from_its_fill_as_it_was_placed_to() {
13710 let mut venue = market_venue_for(&config_owing_base(MarginEffect::Borrow));
13713 let rested = venue.open_order(with_effect(
13714 limit_request("rest", Side::Buy, "1", "49050", gtc()),
13715 MarginEffect::Repay,
13716 ));
13717 assert_eq!(rested_open(&rested).filled_quantity, Decimal::ZERO);
13718
13719 let events = venue.apply_market(
13720 &instrument_name(),
13721 MarketObservation::new(book("48900", "49000")),
13722 time(2),
13723 );
13724
13725 assert_eq!(
13726 balance_of(&events[1]).balance,
13727 Balance::new_margin(d("0.24"), d("0.24"), Decimal::ZERO, Decimal::ZERO),
13728 "1 BTC repays 0.01 of interest and 0.75 of principal, leaving 0.24 free"
13729 );
13730 }
13731
13732 #[test]
13733 fn a_resting_order_placed_to_borrow_repays_nothing_from_its_fill() {
13734 let mut venue = market_venue_for(&config_owing_base(MarginEffect::Repay));
13735 venue.open_order(with_effect(
13736 limit_request("rest", Side::Buy, "1", "49050", gtc()),
13737 MarginEffect::Borrow,
13738 ));
13739
13740 let events = venue.apply_market(
13741 &instrument_name(),
13742 MarketObservation::new(book("48900", "49000")),
13743 time(2),
13744 );
13745
13746 assert_eq!(
13747 balance_of(&events[1]).balance,
13748 Balance::new_margin(d("1"), d("1"), d("0.75"), d("0.01")),
13749 "the loan is left owed although the venue's default would have repaid it"
13750 );
13751 }
13752
13753 #[test]
13754 fn without_auto_repay_at_cancel_a_cancelled_order_leaves_its_loan_owed() {
13755 let config = MockExecutionConfig {
13756 spot_margin: Some(
13757 SpotMarginConfig::new(quote(), d("3"), MarginEffect::Borrow)
13758 .unwrap()
13759 .with_auto_repay_at_cancel(false),
13760 ),
13761 ..spot_config("0", "100000", FeeModelConfig::default())
13762 };
13763 let mut venue = market_venue_for(&config);
13764 venue.open_order(limit_request("rest", Side::Sell, "1", "50000", gtc()));
13765
13766 let cancelled = venue.cancel_order(OrderRequestCancel {
13767 key: limit_request("rest", Side::Sell, "1", "50000", gtc()).key,
13768 state: RequestCancel { id: None },
13769 });
13770
13771 assert!(cancelled.response.state.is_ok());
13772 assert_eq!(
13773 balance_of(&cancelled.events[0]).balance,
13774 Balance::new_margin(d("1"), d("1"), d("1"), Decimal::ZERO),
13775 "the released base is free again and the loan that paid for it stays owed"
13776 );
13777 }
13778
13779 #[test]
13780 fn a_resting_sell_borrows_when_placed_and_a_cancel_repays_it() {
13781 let mut venue = margin_market_venue("0", "100000");
13782
13783 let outcome = venue.open_order(limit_request("rest", Side::Sell, "1", "50000", gtc()));
13784 assert_eq!(rested_open(&outcome).filled_quantity, Decimal::ZERO);
13785 assert_eq!(
13786 balance_of(&outcome.events[0]).balance,
13787 Balance::new_margin(d("1"), Decimal::ZERO, d("1"), Decimal::ZERO),
13788 "the borrowed base is held against the order"
13789 );
13790
13791 let cancelled = venue.cancel_order(OrderRequestCancel {
13792 key: limit_request("rest", Side::Sell, "1", "50000", gtc()).key,
13793 state: RequestCancel { id: None },
13794 });
13795 assert!(cancelled.response.state.is_ok());
13796 assert_eq!(
13797 balance_of(&cancelled.events[0]).balance,
13798 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, Decimal::ZERO, Decimal::ZERO),
13799 "the cancel repays what the order borrowed"
13800 );
13801 }
13802
13803 #[test]
13804 fn a_resting_sell_that_fills_keeps_its_loan() {
13805 let mut venue = margin_market_venue("0", "100000");
13806 venue.open_order(limit_request("rest", Side::Sell, "1", "50000", gtc()));
13807
13808 let events = venue.apply_market(
13809 &instrument_name(),
13810 MarketObservation::new(book("50100", "50200")),
13811 time(2),
13812 );
13813
13814 assert_eq!(
13815 balance_of(&events[0]).balance,
13816 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("1"), Decimal::ZERO)
13817 );
13818 assert_eq!(trade_of(&events[2]).quantity, d("1"));
13819 }
13820
13821 #[test]
13822 fn a_partly_filled_sell_holds_its_loan_against_the_remainder() {
13823 let mut venue = margin_market_venue("0.4", "100000");
13824 assert!(
13826 venue
13827 .apply_market(
13828 &instrument_name(),
13829 MarketObservation::new(book("49000", "49100"))
13830 .with_depth(MarketDepth::new(Some(d("0.5")), None)),
13831 time(2),
13832 )
13833 .is_empty()
13834 );
13835
13836 let outcome = venue.open_order(limit_request("part", Side::Sell, "1", "49000", gtc()));
13837 assert_eq!(rested_open(&outcome).filled_quantity, d("0.5"));
13838 assert_eq!(
13839 filled_balance(&outcome).balance,
13840 Balance::new_margin(d("0.5"), Decimal::ZERO, d("0.6"), Decimal::ZERO),
13841 "0.4 held plus 0.6 borrowed: 0.5 sold and 0.5 held against the rest"
13842 );
13843
13844 let cancelled = venue.cancel_order(OrderRequestCancel {
13845 key: limit_request("part", Side::Sell, "1", "49000", gtc()).key,
13846 state: RequestCancel { id: None },
13847 });
13848 assert_eq!(
13849 balance_of(&cancelled.events[0]).balance,
13850 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.1"), Decimal::ZERO),
13851 "the fill spent the account's own 0.4 first, so the cancel repays 0.5 of the loan"
13852 );
13853 }
13854
13855 #[test]
13856 fn a_held_market_sell_borrows_when_accepted_and_keeps_the_loan_when_its_bar_fills_it() {
13857 let config = MockExecutionConfig {
13858 market_order_timing: MarketOrderTiming::NextBar(NextBarPrice::Open),
13859 ..margin_config("0", "1000", "3")
13860 };
13861 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
13862 advance(&mut venue, time(1));
13863 assert!(feed_bar(&mut venue, 1, ohlc("95", "105", "90", "100")).is_empty());
13864
13865 let outcome = venue.open_order(market_order("held", Side::Sell, "2", gtc()));
13866 assert_eq!(
13867 balance_of(&outcome.events[0]).balance,
13868 Balance::new_margin(d("2"), Decimal::ZERO, d("2"), Decimal::ZERO)
13869 );
13870
13871 let events = feed_bar(&mut venue, 2, ohlc("98", "99", "97", "98"));
13872 assert_eq!(
13873 balance_of(&events[0]).balance,
13874 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("2"), Decimal::ZERO)
13875 );
13876 assert_eq!(trade_of(&events[2]).price, d("98"));
13877 }
13878
13879 #[test]
13880 fn a_held_market_sell_that_expires_repays_its_loan() {
13881 let config = MockExecutionConfig {
13882 market_order_timing: MarketOrderTiming::NextBar(NextBarPrice::Open),
13883 ..margin_config("0", "1000", "3")
13884 };
13885 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
13886 advance(&mut venue, time(1));
13887 assert!(feed_bar(&mut venue, 1, ohlc("95", "105", "90", "100")).is_empty());
13888 venue.open_order(market_order("held", Side::Sell, "2", gtc()));
13889
13890 let events = venue.expire_held(time(2));
13891
13892 assert_eq!(
13893 balance_of(&events[0]).balance,
13894 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, Decimal::ZERO, Decimal::ZERO)
13895 );
13896 }
13897
13898 fn margin_venue_holding() -> SimulatedVenue {
13901 let config = MockExecutionConfig {
13902 market_order_timing: MarketOrderTiming::NextBar(NextBarPrice::Open),
13903 ..margin_config("0", "100", "2")
13904 };
13905 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
13906 advance(&mut venue, time(1));
13907 assert!(feed_bar(&mut venue, 1, ohlc("95", "105", "90", "100")).is_empty());
13908 venue
13909 }
13910
13911 #[test]
13912 fn a_held_buy_costed_higher_at_its_bar_borrows_the_difference() {
13913 let mut venue = margin_venue_holding();
13914 let outcome = venue.open_order(market_order("held", Side::Buy, "1.5", gtc()));
13916 assert_eq!(
13917 balance_of(sans_debt(&outcome.events)[0]).balance,
13918 Balance::new_margin(d("150"), Decimal::ZERO, d("50"), Decimal::ZERO)
13919 );
13920
13921 let events = feed_bar(&mut venue, 2, ohlc("120", "121", "119", "120"));
13923 assert_eq!(
13924 balance_of(sans_debt(&events)[0]).balance,
13925 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("80"), Decimal::ZERO)
13926 );
13927 assert_eq!(trade_of(sans_debt(&events)[2]).price, d("120"));
13928 }
13929
13930 #[test]
13931 fn a_held_buy_costed_lower_at_its_bar_repays_the_loan_it_no_longer_needs() {
13932 let mut venue = margin_venue_holding();
13933 venue.open_order(market_order("held", Side::Buy, "1.5", gtc()));
13935
13936 let events = feed_bar(&mut venue, 2, ohlc("80", "81", "79", "80"));
13939 assert_eq!(
13940 balance_of(sans_debt(&events)[0]).balance,
13941 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("20"), Decimal::ZERO)
13942 );
13943 assert_eq!(
13944 balance_of(sans_debt(&events)[1]).balance,
13945 Balance::new_margin(d("1.5"), d("1.5"), Decimal::ZERO, Decimal::ZERO)
13946 );
13947 assert_eq!(trade_of(sans_debt(&events)[2]).price, d("80"));
13948 }
13949
13950 #[test]
13951 fn a_held_buy_unaffordable_at_its_bar_is_cancelled_and_repays_its_loan() {
13952 let mut venue = margin_venue_holding();
13953 venue.open_order(market_order("held", Side::Buy, "1.5", gtc()));
13954
13955 let events = feed_bar(&mut venue, 2, ohlc("250", "251", "249", "250"));
13957
13958 assert_eq!(
13959 balance_of(sans_debt(&events)[0]).balance,
13960 Balance::new_margin(d("100"), d("100"), Decimal::ZERO, Decimal::ZERO),
13961 "the cancel repays the 50 the order borrowed when it was accepted"
13962 );
13963 assert!(matches!(
13964 order_of(sans_debt(&events)[1]).state,
13965 OrderState::Inactive(InactiveOrderState::Cancelled(_))
13966 ));
13967 }
13968
13969 fn margin_venue_holding_with(
13971 adjust: impl FnOnce(SpotMarginConfig) -> SpotMarginConfig,
13972 effect: MarginEffect,
13973 ) -> SimulatedVenue {
13974 let mut config = MockExecutionConfig {
13975 market_order_timing: MarketOrderTiming::NextBar(NextBarPrice::Open),
13976 ..margin_config("0", "100", "2")
13977 };
13978 config.spot_margin = Some(adjust(
13979 SpotMarginConfig::new(quote(), d("2"), effect).unwrap(),
13980 ));
13981 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
13982 advance(&mut venue, time(1));
13983 assert!(feed_bar(&mut venue, 1, ohlc("95", "105", "90", "100")).is_empty());
13984 venue
13985 }
13986
13987 #[test]
13988 fn without_auto_repay_at_cancel_a_held_buy_costed_lower_keeps_the_loan_it_no_longer_needs() {
13989 let mut venue = margin_venue_holding_with(
13990 |config| config.with_auto_repay_at_cancel(false),
13991 MarginEffect::Borrow,
13992 );
13993 venue.open_order(market_order("held", Side::Buy, "1.5", gtc()));
13994
13995 let events = feed_bar(&mut venue, 2, ohlc("80", "81", "79", "80"));
13997
13998 assert_eq!(
13999 balance_of(&events[0]).balance,
14000 Balance::new_margin(d("30"), d("30"), d("50"), Decimal::ZERO)
14001 );
14002 }
14003
14004 #[test]
14005 fn without_auto_repay_at_cancel_a_held_buy_unaffordable_at_its_bar_keeps_its_loan() {
14006 let mut venue = margin_venue_holding_with(
14007 |config| config.with_auto_repay_at_cancel(false),
14008 MarginEffect::Borrow,
14009 );
14010 venue.open_order(market_order("held", Side::Buy, "1.5", gtc()));
14011
14012 let events = feed_bar(&mut venue, 2, ohlc("250", "251", "249", "250"));
14013
14014 assert_eq!(
14015 balance_of(&events[0]).balance,
14016 Balance::new_margin(d("150"), d("150"), d("50"), Decimal::ZERO),
14017 "the cancel gives the hold back and leaves the 50 the order borrowed owed"
14018 );
14019 assert!(matches!(
14020 order_of(&events[1]).state,
14021 OrderState::Inactive(InactiveOrderState::Cancelled(_))
14022 ));
14023 }
14024
14025 #[test]
14026 fn without_auto_repay_at_cancel_a_held_sell_that_expires_keeps_its_loan() {
14027 let mut venue = margin_venue_holding_with(
14028 |config| config.with_auto_repay_at_cancel(false),
14029 MarginEffect::Borrow,
14030 );
14031 venue.open_order(market_order("held", Side::Sell, "1", gtc()));
14032
14033 let events = venue.expire_held(time(2));
14034
14035 assert_eq!(
14036 balance_of(&events[0]).balance,
14037 Balance::new_margin(d("1"), d("1"), d("1"), Decimal::ZERO)
14038 );
14039 }
14040
14041 #[test]
14042 fn a_held_buy_costed_afresh_at_its_bar_borrows_as_it_was_placed_to() {
14043 let mut venue = margin_venue_holding_with(|config| config, MarginEffect::NoSideEffect);
14047 let mut request = market_order("held", Side::Buy, "1.5", gtc());
14048 request.state = request.state.with_margin_effect(MarginEffect::Borrow);
14049 venue.open_order(request);
14050
14051 let events = feed_bar(&mut venue, 2, ohlc("120", "121", "119", "120"));
14052
14053 assert_eq!(
14054 balance_of(sans_debt(&events)[0]).balance,
14055 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("80"), Decimal::ZERO)
14056 );
14057 assert_eq!(trade_of(sans_debt(&events)[2]).price, d("120"));
14058 }
14059
14060 fn with_min_borrow(
14066 mut config: MockExecutionConfig,
14067 asset: AssetNameExchange,
14068 min_borrow: &str,
14069 ) -> MockExecutionConfig {
14070 config.spot_margin = config
14071 .spot_margin
14072 .map(|margin| margin.with_min_borrow(asset, d(min_borrow)).unwrap());
14073 config
14074 }
14075
14076 #[test]
14077 fn a_shortfall_below_the_minimum_borrows_the_minimum_and_the_rest_stays_free() {
14078 let config = with_min_borrow(margin_config("0.25", "100000", "3"), base(), "0.5");
14079 let mut venue = SimulatedVenue::new(&config, spot_instruments());
14080
14081 let outcome = venue.open_order(sell_request("0.3", market_prices("50000")));
14083
14084 assert_eq!(
14085 filled_balance(&outcome).balance,
14086 Balance::new_margin(d("0.45"), d("0.45"), d("0.5"), Decimal::ZERO),
14087 "0.25 + 0.5 lent − 0.3 sold, and what the sell did not spend is free"
14088 );
14089 assert_eq!(filled_trade(&outcome).quantity, d("0.3"));
14090 }
14091
14092 #[test]
14093 fn interest_is_charged_on_the_whole_minimum_lent() {
14094 let config = with_min_borrow(margin_config("0.25", "100000", "3"), base(), "0.5");
14095 let mut venue = SimulatedVenue::new(&config, spot_instruments())
14096 .with_margin_interest(Arc::new(hourly_interest(base())));
14097 advance(&mut venue, time(1));
14098
14099 let outcome = venue.open_order(sell_request("0.3", market_prices("50000")));
14100
14101 assert_eq!(
14102 filled_balance(&outcome).balance,
14103 Balance::new_margin(d("0.45"), d("0.45"), d("0.5"), d("0.00005"))
14104 );
14105 assert_eq!(
14106 *cash_flow_of(sans_debt(&outcome.events)[3]),
14107 interest_flow(
14108 base(),
14109 "0.5",
14110 "0.00005",
14111 time(1),
14112 InterestTiming::OnBorrow,
14113 "0"
14114 ),
14115 );
14116 }
14117
14118 #[test]
14119 fn a_shortfall_above_the_minimum_borrows_only_the_shortfall() {
14120 let config = with_min_borrow(margin_config("0.25", "100000", "3"), base(), "0.5");
14121 let mut venue = SimulatedVenue::new(&config, spot_instruments());
14122
14123 let outcome = venue.open_order(sell_request("1", market_prices("50000")));
14124
14125 assert_eq!(
14126 filled_balance(&outcome).balance,
14127 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.75"), Decimal::ZERO)
14128 );
14129 }
14130
14131 #[test]
14132 fn a_minimum_on_another_asset_lends_only_the_shortfall() {
14133 let config = with_min_borrow(margin_config("0.25", "100000", "3"), quote(), "1000");
14134 let mut venue = SimulatedVenue::new(&config, spot_instruments());
14135
14136 let outcome = venue.open_order(sell_request("0.3", market_prices("50000")));
14137
14138 assert_eq!(
14139 filled_balance(&outcome).balance,
14140 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.05"), Decimal::ZERO)
14141 );
14142 }
14143
14144 #[test]
14145 fn a_minimum_borrow_beyond_what_the_venue_lends_refuses_the_order() {
14146 let config = with_min_borrow(margin_config("0", "10000", "2"), base(), "0.21");
14148 let mut venue = SimulatedVenue::new(&config, spot_instruments());
14149
14150 let outcome = venue.open_order(sell_request("0.1", market_prices("50000")));
14151
14152 assert!(
14153 insufficient_reason(&outcome).contains("Borrowable: 0.2")
14154 && insufficient_reason(&outcome).ends_with("Minimum Borrow: 0.21"),
14155 "the reason names what the venue would lend and the minimum it would not: {}",
14156 insufficient_reason(&outcome)
14157 );
14158 assert_eq!(
14159 balance_now(&venue, base()),
14160 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, Decimal::ZERO, Decimal::ZERO),
14161 "a refused order borrows nothing"
14162 );
14163 }
14164
14165 #[test]
14166 fn a_shortfall_above_the_minimum_refused_by_the_leverage_limit_names_no_minimum() {
14167 let config = with_min_borrow(margin_config("0", "10000", "2"), base(), "0.05");
14168 let mut venue = SimulatedVenue::new(&config, spot_instruments());
14169
14170 let outcome = venue.open_order(sell_request("0.3", market_prices("50000")));
14171
14172 assert!(
14173 insufficient_reason(&outcome).contains("Borrowable: 0.2")
14174 && !insufficient_reason(&outcome).contains("Minimum Borrow"),
14175 "{}",
14176 insufficient_reason(&outcome)
14177 );
14178 }
14179
14180 #[test]
14181 fn a_shortfall_beyond_what_the_venue_lends_names_no_minimum() {
14182 let config = with_min_borrow(margin_config("0", "10000", "2"), base(), "0.5");
14184 let mut venue = SimulatedVenue::new(&config, spot_instruments());
14185
14186 let outcome = venue.open_order(sell_request("0.3", market_prices("50000")));
14187
14188 assert!(
14189 insufficient_reason(&outcome).contains("Borrowable: 0.2")
14190 && !insufficient_reason(&outcome).contains("Minimum Borrow"),
14191 "{}",
14192 insufficient_reason(&outcome)
14193 );
14194 }
14195
14196 #[test]
14197 fn a_shortfall_of_exactly_what_the_venue_lends_names_the_minimum_that_refused_it() {
14198 let config = with_min_borrow(margin_config("0", "10000", "2"), base(), "0.5");
14200 let mut venue = SimulatedVenue::new(&config, spot_instruments());
14201
14202 let outcome = venue.open_order(sell_request("0.2", market_prices("50000")));
14203
14204 assert!(
14205 insufficient_reason(&outcome).ends_with("Minimum Borrow: 0.5"),
14206 "{}",
14207 insufficient_reason(&outcome)
14208 );
14209 }
14210
14211 #[test]
14212 fn a_minimum_borrow_of_exactly_what_the_venue_lends_is_lent() {
14213 let config = with_min_borrow(margin_config("0", "10000", "2"), base(), "0.2");
14214 let mut venue = SimulatedVenue::new(&config, spot_instruments());
14215
14216 let outcome = venue.open_order(sell_request("0.1", market_prices("50000")));
14217
14218 assert_eq!(
14219 filled_balance(&outcome).balance,
14220 Balance::new_margin(d("0.1"), d("0.1"), d("0.2"), Decimal::ZERO)
14221 );
14222 }
14223
14224 #[test]
14225 fn a_resting_sell_lent_the_minimum_holds_its_shortfall_and_a_cancel_repays_the_whole_loan() {
14226 let config = with_min_borrow(margin_config("0", "100000", "3"), base(), "0.5");
14227 let mut venue = market_venue_for(&config);
14228
14229 let outcome = venue.open_order(limit_request("rest", Side::Sell, "0.1", "50000", gtc()));
14230 assert_eq!(
14231 balance_of(&outcome.events[0]).balance,
14232 Balance::new_margin(d("0.5"), d("0.4"), d("0.5"), Decimal::ZERO),
14233 "0.1 is held against the order, and the 0.4 lent beyond it is free"
14234 );
14235
14236 let cancelled = venue.cancel_order(OrderRequestCancel {
14237 key: limit_request("rest", Side::Sell, "0.1", "50000", gtc()).key,
14238 state: RequestCancel { id: None },
14239 });
14240 assert!(cancelled.response.state.is_ok());
14241 assert_eq!(
14242 balance_of(&cancelled.events[0]).balance,
14243 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, Decimal::ZERO, Decimal::ZERO),
14244 "the cancel repays all 0.5 the order borrowed"
14245 );
14246 }
14247
14248 #[test]
14249 fn without_auto_repay_at_cancel_a_cancel_leaves_the_whole_minimum_owed() {
14250 let mut config = with_min_borrow(margin_config("0", "100000", "3"), base(), "0.5");
14251 config.spot_margin = config
14252 .spot_margin
14253 .map(|margin| margin.with_auto_repay_at_cancel(false));
14254 let mut venue = market_venue_for(&config);
14255 venue.open_order(limit_request("rest", Side::Sell, "0.1", "50000", gtc()));
14256
14257 let cancelled = venue.cancel_order(OrderRequestCancel {
14258 key: limit_request("rest", Side::Sell, "0.1", "50000", gtc()).key,
14259 state: RequestCancel { id: None },
14260 });
14261
14262 assert_eq!(
14263 balance_of(&cancelled.events[0]).balance,
14264 Balance::new_margin(d("0.5"), d("0.5"), d("0.5"), Decimal::ZERO)
14265 );
14266 }
14267
14268 #[test]
14269 fn a_resting_sell_lent_the_minimum_that_fills_keeps_the_whole_loan() {
14270 let config = with_min_borrow(margin_config("0", "100000", "3"), base(), "0.5");
14271 let mut venue = market_venue_for(&config);
14272 venue.open_order(limit_request("rest", Side::Sell, "0.1", "50000", gtc()));
14273
14274 let events = venue.apply_market(
14275 &instrument_name(),
14276 MarketObservation::new(book("50100", "50200")),
14277 time(2),
14278 );
14279
14280 assert_eq!(
14281 balance_of(&events[0]).balance,
14282 Balance::new_margin(d("0.4"), d("0.4"), d("0.5"), Decimal::ZERO),
14283 "the filled order's loan stays owed, the part it did not spend included"
14284 );
14285 assert_eq!(trade_of(&events[2]).quantity, d("0.1"));
14286 }
14287
14288 #[test]
14289 fn a_partly_filled_sell_lent_the_minimum_repays_the_unspent_loan_at_cancel() {
14290 let config = with_min_borrow(margin_config("0.4", "100000", "3"), base(), "0.8");
14291 let mut venue = market_venue_for(&config);
14292 assert!(
14294 venue
14295 .apply_market(
14296 &instrument_name(),
14297 MarketObservation::new(book("49000", "49100"))
14298 .with_depth(MarketDepth::new(Some(d("0.5")), None)),
14299 time(2),
14300 )
14301 .is_empty()
14302 );
14303
14304 let outcome = venue.open_order(limit_request("part", Side::Sell, "1", "49000", gtc()));
14306 assert_eq!(rested_open(&outcome).filled_quantity, d("0.5"));
14307 assert_eq!(
14308 filled_balance(&outcome).balance,
14309 Balance::new_margin(d("0.7"), d("0.2"), d("0.8"), Decimal::ZERO),
14310 "0.4 + 0.8 lent − 0.5 sold; 0.5 held against the rest and 0.2 free"
14311 );
14312
14313 let cancelled = venue.cancel_order(OrderRequestCancel {
14314 key: limit_request("part", Side::Sell, "1", "49000", gtc()).key,
14315 state: RequestCancel { id: None },
14316 });
14317 assert_eq!(
14318 balance_of(&cancelled.events[0]).balance,
14319 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.1"), Decimal::ZERO),
14320 "the 0.5 held against the rest and the 0.2 lent beyond the shortfall are repaid"
14321 );
14322 }
14323
14324 #[test]
14325 fn a_held_market_sell_lent_the_minimum_that_expires_repays_the_whole_loan() {
14326 let config = with_min_borrow(
14327 MockExecutionConfig {
14328 market_order_timing: MarketOrderTiming::NextBar(NextBarPrice::Open),
14329 ..margin_config("0", "1000", "3")
14330 },
14331 base(),
14332 "3",
14333 );
14334 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
14335 advance(&mut venue, time(1));
14336 assert!(feed_bar(&mut venue, 1, ohlc("95", "105", "90", "100")).is_empty());
14337 let outcome = venue.open_order(market_order("held", Side::Sell, "2", gtc()));
14338 assert_eq!(
14339 balance_of(&outcome.events[0]).balance,
14340 Balance::new_margin(d("3"), d("1"), d("3"), Decimal::ZERO)
14341 );
14342
14343 let events = venue.expire_held(time(2));
14344
14345 assert_eq!(
14346 balance_of(&events[0]).balance,
14347 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, Decimal::ZERO, Decimal::ZERO)
14348 );
14349 }
14350
14351 #[test]
14352 fn a_held_buy_lent_the_minimum_and_costed_lower_repays_only_the_held_loan_it_does_not_need() {
14353 let mut venue = margin_venue_holding_with(
14354 |config| config.with_min_borrow(quote(), d("80")).unwrap(),
14355 MarginEffect::Borrow,
14356 );
14357 let outcome = venue.open_order(market_order("held", Side::Buy, "1.5", gtc()));
14359 assert_eq!(
14360 balance_of(&outcome.events[0]).balance,
14361 Balance::new_margin(d("180"), d("30"), d("80"), Decimal::ZERO)
14362 );
14363
14364 let events = feed_bar(&mut venue, 2, ohlc("80", "81", "79", "80"));
14367 assert_eq!(
14368 balance_of(&events[0]).balance,
14369 Balance::new_margin(d("30"), d("30"), d("50"), Decimal::ZERO)
14370 );
14371 }
14372
14373 #[test]
14374 fn a_held_buy_lent_the_minimum_that_needs_none_of_its_loan_keeps_only_the_excess_owed() {
14375 let mut venue = margin_venue_holding_with(
14376 |config| config.with_min_borrow(quote(), d("80")).unwrap(),
14377 MarginEffect::Borrow,
14378 );
14379 venue.open_order(market_order("held", Side::Buy, "1.5", gtc()));
14381
14382 let events = feed_bar(&mut venue, 2, ohlc("20", "21", "19", "20"));
14385 assert_eq!(
14386 balance_of(&events[0]).balance,
14387 Balance::new_margin(d("100"), d("100"), d("30"), Decimal::ZERO)
14388 );
14389 }
14390
14391 #[test]
14392 fn a_held_buy_costed_higher_at_its_bar_borrows_at_least_the_minimum_more() {
14393 let mut venue = margin_venue_holding_with(
14394 |config| config.with_min_borrow(quote(), d("40")).unwrap(),
14395 MarginEffect::Borrow,
14396 );
14397 let outcome = venue.open_order(market_order("held", Side::Buy, "1.5", gtc()));
14399 assert_eq!(
14400 balance_of(&outcome.events[0]).balance,
14401 Balance::new_margin(d("150"), Decimal::ZERO, d("50"), Decimal::ZERO)
14402 );
14403
14404 let events = feed_bar(&mut venue, 2, ohlc("120", "121", "119", "120"));
14406 assert_eq!(
14407 balance_of(&events[0]).balance,
14408 Balance::new_margin(d("10"), d("10"), d("90"), Decimal::ZERO)
14409 );
14410 }
14411
14412 #[test]
14413 fn a_held_buy_lent_the_minimum_and_unaffordable_at_its_bar_repays_the_whole_loan() {
14414 let mut venue = margin_venue_holding_with(
14415 |config| config.with_min_borrow(quote(), d("80")).unwrap(),
14416 MarginEffect::Borrow,
14417 );
14418 venue.open_order(market_order("held", Side::Buy, "1.5", gtc()));
14419
14420 let events = feed_bar(&mut venue, 2, ohlc("250", "251", "249", "250"));
14422
14423 assert_eq!(
14424 balance_of(sans_debt(&events)[0]).balance,
14425 Balance::new_margin(d("100"), d("100"), Decimal::ZERO, Decimal::ZERO),
14426 "the cancel repays all 80 the order borrowed"
14427 );
14428 assert!(matches!(
14429 order_of(sans_debt(&events)[1]).state,
14430 OrderState::Inactive(InactiveOrderState::Cancelled(_))
14431 ));
14432 }
14433
14434 #[test]
14435 fn a_held_limit_buy_lent_the_minimum_carries_it_to_the_remainder_it_rests() {
14436 let config = with_min_borrow(
14437 MockExecutionConfig {
14438 stale_market: StaleMarketPolicy::Wait { max_age: MAX_AGE },
14439 ..margin_config("0", "100", "2")
14440 },
14441 quote(),
14442 "80",
14443 );
14444 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments());
14445 advance(&mut venue, time(1));
14446 assert!(observe(&mut venue, 1, traded("100")).is_empty());
14447 advance(&mut venue, time(12));
14448 let outcome = venue.open_order(gtc_limit("waiting", Side::Buy, "1.5", "100"));
14450 assert_eq!(
14451 balance_of(&outcome.events[0]).balance,
14452 Balance::new_margin(d("180"), d("30"), d("80"), Decimal::ZERO)
14453 );
14454
14455 let events = venue.apply_market(
14457 &instrument_name(),
14458 MarketObservation::new(book("79", "80"))
14459 .with_depth(MarketDepth::new(Some(d("1")), Some(d("0.5")))),
14460 time(20),
14461 );
14462 assert_eq!(
14463 balance_of(&events[0]).balance,
14464 Balance::new_margin(d("130"), d("30"), d("70"), Decimal::ZERO)
14465 );
14466
14467 let cancelled = venue.cancel_order(OrderRequestCancel {
14468 key: gtc_limit("waiting", Side::Buy, "1.5", "100").key,
14469 state: RequestCancel { id: None },
14470 });
14471 assert!(cancelled.response.state.is_ok());
14472 assert_eq!(
14473 venue.balances(&[quote()])[0].balance,
14474 Balance::new_margin(d("60"), d("60"), Decimal::ZERO, Decimal::ZERO),
14475 "cancelling the remainder repays the 40 it held and the 30 lent beyond the shortfall"
14476 );
14477 }
14478
14479 #[test]
14480 fn a_held_limit_that_rests_untraded_at_its_release_keeps_its_interest_until_its_end() {
14481 let config = MockExecutionConfig {
14482 stale_market: StaleMarketPolicy::Wait { max_age: MAX_AGE },
14483 ..margin_config("0", "100", "2")
14484 };
14485 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments())
14486 .with_margin_interest(Arc::new(hourly_interest(quote())));
14487 advance(&mut venue, time(1));
14488 assert!(observe(&mut venue, 1, traded("100")).is_empty());
14489 advance(&mut venue, time(12));
14490 let outcome = venue.open_order(gtc_limit("waiting", Side::Buy, "1.5", "100"));
14492 assert_eq!(sans_debt(&outcome.events).len(), 1, "{:?}", outcome.events);
14493
14494 let events = venue.apply_market(
14496 &instrument_name(),
14497 MarketObservation::new(book("101", "102")),
14498 time(20),
14499 );
14500 assert!(
14501 !events
14502 .iter()
14503 .any(|event| matches!(event.kind, AccountEventKind::CashFlow(_))),
14504 "still no position for the charge: {events:?}"
14505 );
14506
14507 advance(&mut venue, time(30));
14508 let cancelled = venue.cancel_order(OrderRequestCancel {
14509 key: gtc_limit("waiting", Side::Buy, "1.5", "100").key,
14510 state: RequestCancel { id: None },
14511 });
14512 assert_eq!(
14513 *cash_flow_of(sans_debt(&cancelled.events)[1]),
14514 interest_flow(
14515 quote(),
14516 "50",
14517 "0.005",
14518 time(30),
14519 InterestTiming::OnBorrow,
14520 "0"
14521 ),
14522 "carried to the book, and reported at the order's end"
14523 );
14524 }
14525
14526 #[test]
14527 fn a_held_limit_that_trades_in_part_at_its_release_reports_its_interest_once_after_the_trade() {
14528 let config = MockExecutionConfig {
14529 stale_market: StaleMarketPolicy::Wait { max_age: MAX_AGE },
14530 ..margin_config("0", "100", "2")
14531 };
14532 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments())
14533 .with_margin_interest(Arc::new(hourly_interest(quote())));
14534 advance(&mut venue, time(1));
14535 assert!(observe(&mut venue, 1, traded("100")).is_empty());
14536 advance(&mut venue, time(12));
14537 let outcome = venue.open_order(gtc_limit("partial", Side::Buy, "1.5", "100"));
14539 assert_eq!(sans_debt(&outcome.events).len(), 1, "{:?}", outcome.events);
14540
14541 let events = venue.apply_market(
14543 &instrument_name(),
14544 MarketObservation::new(book("98", "99")).with_depth(sized("0.5")),
14545 time(20),
14546 );
14547 let events = sans_debt(&events);
14548 let traded_at = events
14549 .iter()
14550 .position(|event| matches!(event.kind, AccountEventKind::Trade(_)))
14551 .unwrap_or_else(|| panic!("a trade: {events:?}"));
14552 assert_eq!(trade_of(events[traded_at]).quantity, d("0.5"));
14553 assert_eq!(
14554 *cash_flow_of(events[traded_at + 1]),
14555 interest_flow(
14556 quote(),
14557 "50",
14558 "0.005",
14559 time(20),
14560 InterestTiming::OnBorrow,
14561 "0"
14562 ),
14563 "the hold's charge after the trade, stamped at it: {events:?}"
14564 );
14565 assert_eq!(
14566 events
14567 .iter()
14568 .filter(|event| matches!(event.kind, AccountEventKind::CashFlow(_)))
14569 .count(),
14570 1,
14571 "reported once: {events:?}"
14572 );
14573
14574 advance(&mut venue, time(30));
14575 let cancelled = venue.cancel_order(OrderRequestCancel {
14576 key: gtc_limit("partial", Side::Buy, "1.5", "100").key,
14577 state: RequestCancel { id: None },
14578 });
14579 assert!(
14580 !cancelled
14581 .events
14582 .iter()
14583 .any(|event| matches!(event.kind, AccountEventKind::CashFlow(_))),
14584 "the remainder carries no charge already reported: {:?}",
14585 cancelled.events
14586 );
14587 }
14588
14589 #[test]
14590 fn a_held_immediate_order_an_idle_bar_cancels_reports_its_interest_with_its_cancel() {
14591 let config = MockExecutionConfig {
14592 market_order_timing: MarketOrderTiming::NextBar(NextBarPrice::Open),
14593 stale_market: StaleMarketPolicy::Wait { max_age: MAX_AGE },
14594 ..margin_config("0", "100", "2")
14595 };
14596 let mut venue = SimulatedVenue::new_market_driven(&config, spot_instruments())
14597 .with_margin_interest(Arc::new(hourly_interest(quote())));
14598 advance(&mut venue, time(1));
14599 assert!(feed_bar(&mut venue, 1, ohlc("95", "105", "90", "100")).is_empty());
14600 let outcome = venue.open_order(market_order(
14602 "immediate",
14603 Side::Buy,
14604 "1.5",
14605 TimeInForce::ImmediateOrCancel,
14606 ));
14607 assert_eq!(sans_debt(&outcome.events).len(), 1, "{:?}", outcome.events);
14608
14609 let bar = ohlc("100", "100", "100", "100");
14610 let events = venue.apply_market(
14611 &instrument_name(),
14612 MarketObservation::new(MarketSnapshot::from_last_price(Some(bar.close)))
14613 .with_bar(Some(bar))
14614 .with_fresh(false),
14615 time(2),
14616 );
14617 let events = sans_debt(&events);
14618
14619 assert_eq!(events.len(), 3, "{events:?}");
14620 assert_eq!(
14621 *cash_flow_of(events[1]),
14622 interest_flow(
14623 quote(),
14624 "50",
14625 "0.005",
14626 time(2),
14627 InterestTiming::OnBorrow,
14628 "0"
14629 ),
14630 "the order never traded, so its cancel reports the charge, stamped then"
14631 );
14632 assert!(matches!(
14633 order_of(events[2]).state,
14634 OrderState::Inactive(InactiveOrderState::Cancelled(_))
14635 ));
14636 }
14637
14638 fn margin_venue_with_eth(eth: &str, eth_owed: &str) -> SimulatedVenue {
14640 let config = MockExecutionConfig {
14641 spot_margin: Some(
14642 SpotMarginConfig::new(quote(), d("2"), MarginEffect::Borrow).unwrap(),
14643 ),
14644 ..config_from_balances(
14645 vec![
14646 funded(base(), Decimal::ZERO),
14647 funded(quote(), d("10000")),
14648 AssetBalance::new(
14649 AssetNameExchange::new("ETH"),
14650 Balance::new_margin(d(eth), d(eth), d(eth_owed), Decimal::ZERO),
14651 Utc::now(),
14652 ),
14653 ],
14654 FeeModelConfig::default(),
14655 )
14656 };
14657 SimulatedVenue::new(&config, spot_instruments())
14658 }
14659
14660 #[test]
14661 fn an_asset_the_venue_cannot_value_counts_as_no_equity() {
14662 let mut venue = margin_venue_with_eth("5", "0");
14664
14665 let outcome = venue.open_order(sell_request("0.3", market_prices("50000")));
14666 assert!(
14667 insufficient_reason(&outcome).contains("Borrowable: 0.2"),
14668 "{}",
14669 insufficient_reason(&outcome)
14670 );
14671 }
14672
14673 #[test]
14674 fn a_debt_the_venue_cannot_value_stops_it_lending() {
14675 let mut venue = margin_venue_with_eth("5", "1");
14676
14677 let outcome = venue.open_order(sell_request("0.1", market_prices("50000")));
14678 assert!(
14679 insufficient_reason(&outcome).contains("Borrowable: 0"),
14680 "{}",
14681 insufficient_reason(&outcome)
14682 );
14683 }
14684
14685 #[test]
14686 fn interest_owed_counts_as_debt_against_what_the_venue_lends() {
14687 let config = MockExecutionConfig {
14688 spot_margin: Some(
14689 SpotMarginConfig::new(quote(), d("2"), MarginEffect::Borrow).unwrap(),
14690 ),
14691 ..config_from_balances(
14692 vec![
14693 AssetBalance::new(
14694 base(),
14695 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.05"), d("0.01")),
14696 Utc::now(),
14697 ),
14698 funded(quote(), d("20000")),
14699 ],
14700 FeeModelConfig::default(),
14701 )
14702 };
14703 let mut venue = SimulatedVenue::new(&config, spot_instruments());
14704
14705 let outcome = venue.open_order(sell_request("1", market_prices("50000")));
14708 assert!(
14709 insufficient_reason(&outcome).contains("Borrowable: 0.28"),
14710 "{}",
14711 insufficient_reason(&outcome)
14712 );
14713 }
14714
14715 #[test]
14716 fn an_asset_a_margin_account_first_receives_reports_its_loan() {
14717 let config = MockExecutionConfig {
14718 spot_margin: Some(
14719 SpotMarginConfig::new(quote(), d("3"), MarginEffect::Borrow).unwrap(),
14720 ),
14721 ..config_from_balances(
14722 vec![funded(quote(), d("100000"))],
14723 FeeModelConfig::default(),
14724 )
14725 };
14726 let mut venue = SimulatedVenue::new(&config, spot_instruments());
14727
14728 let outcome = venue.open_order(buy_request("1", market_prices("50000")));
14729
14730 assert_eq!(
14731 filled_received(&outcome).balance,
14732 Balance::new_margin(d("1"), d("1"), Decimal::ZERO, Decimal::ZERO)
14733 );
14734 }
14735
14736 #[test]
14737 fn an_asset_priced_at_zero_is_not_lent() {
14738 let mut venue = margin_venue("0", "10000", "2");
14739
14740 let outcome = venue.open_order(sell_request("0.1", market_prices("0")));
14741 assert!(
14742 insufficient_reason(&outcome).contains("Borrowable: 0"),
14743 "{}",
14744 insufficient_reason(&outcome)
14745 );
14746 }
14747
14748 #[test]
14749 fn a_pair_quoting_the_asset_in_another_does_not_value_it() {
14750 let mut instruments = spot_instruments();
14752 let aave_btc = Instrument {
14753 exchange: EXCHANGE,
14754 name_internal: InstrumentNameInternal::new("aavebtc"),
14755 name_exchange: InstrumentNameExchange::new("AAVEBTC"),
14756 underlying: Underlying {
14757 base: AssetNameExchange::new("AAVE"),
14758 quote: base(),
14759 },
14760 quote: InstrumentQuoteAsset::UnderlyingQuote,
14761 kind: InstrumentKind::Spot,
14762 spec: None,
14763 data_venue: None,
14764 };
14765 instruments.insert(aave_btc.name_exchange.clone(), aave_btc);
14766 let mut venue =
14767 SimulatedVenue::new_market_driven(&margin_config("0", "10000", "2"), instruments);
14768 advance(&mut venue, time(1));
14769 for (name, snapshot) in [
14770 (instrument_name(), book("50000", "50000")),
14771 (InstrumentNameExchange::new("AAVEBTC"), book("0.05", "0.05")),
14772 ] {
14773 assert!(
14774 venue
14775 .apply_market(&name, MarketObservation::new(snapshot), time(1))
14776 .is_empty()
14777 );
14778 }
14779
14780 let outcome = venue.open_order(market_order(
14782 "sell",
14783 Side::Sell,
14784 "0.3",
14785 TimeInForce::ImmediateOrCancel,
14786 ));
14787 assert!(
14788 insufficient_reason(&outcome).contains("Borrowable: 0.2"),
14789 "{}",
14790 insufficient_reason(&outcome)
14791 );
14792 }
14793
14794 fn with_max_borrow(
14800 mut config: MockExecutionConfig,
14801 asset: AssetNameExchange,
14802 max_borrow: &str,
14803 ) -> MockExecutionConfig {
14804 config.spot_margin = config
14805 .spot_margin
14806 .map(|margin| margin.with_max_borrow(asset, d(max_borrow)).unwrap());
14807 config
14808 }
14809
14810 fn capped_venue(cap: &str) -> SimulatedVenue {
14813 let config = with_max_borrow(margin_config("0", "10000", "3"), base(), cap);
14814 SimulatedVenue::new(&config, spot_instruments())
14815 }
14816
14817 #[test]
14818 fn a_borrow_of_exactly_what_the_cap_leaves_is_lent() {
14819 let mut venue = capped_venue("0.3");
14820
14821 let outcome = venue.open_order(sell_request("0.3", market_prices("50000")));
14822
14823 assert_eq!(
14824 filled_balance(&outcome).balance,
14825 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.3"), Decimal::ZERO)
14826 );
14827 }
14828
14829 #[test]
14830 fn a_borrow_past_a_cap_tighter_than_the_leverage_limit_is_rejected_as_over_the_limit() {
14831 let mut venue = capped_venue("0.3");
14832
14833 let outcome = venue.open_order(sell_request("0.31", market_prices("50000")));
14834
14835 let reject = borrow_rejection(&outcome);
14836 assert_eq!(reject.reason, BorrowRejectReason::BorrowLimitExceeded);
14837 assert_eq!(reject.venue_code, None);
14838 assert_eq!(
14839 reject.available,
14840 Some(d("0.3")),
14841 "a smaller sell could still borrow what the cap leaves"
14842 );
14843 assert!(
14844 reject
14845 .message
14846 .starts_with("BTC borrow exceeds Max Borrow: 0.3;")
14847 && reject.message.ends_with("Borrowable: 0.3"),
14848 "the reason names the cap and what it leaves: {}",
14849 reject.message
14850 );
14851 assert_eq!(
14852 balance_now(&venue, base()),
14853 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, Decimal::ZERO, Decimal::ZERO),
14854 "a refused order borrows nothing"
14855 );
14856 }
14857
14858 #[test]
14859 fn a_leverage_limit_tighter_than_the_cap_rejects_as_unaffordable_naming_no_cap() {
14860 let mut venue = capped_venue("1");
14861
14862 let outcome = venue.open_order(sell_request("0.5", market_prices("50000")));
14863
14864 assert!(
14865 insufficient_reason(&outcome).contains("Borrowable: 0.4")
14866 && !insufficient_reason(&outcome).contains("Max Borrow"),
14867 "{}",
14868 insufficient_reason(&outcome)
14869 );
14870 }
14871
14872 #[test]
14873 fn a_cap_leaving_exactly_what_the_leverage_limit_does_leaves_the_leverage_limit_deciding() {
14874 let mut venue = capped_venue("0.4");
14875
14876 let outcome = venue.open_order(sell_request("0.5", market_prices("50000")));
14877
14878 assert!(
14879 insufficient_reason(&outcome).contains("Borrowable: 0.4")
14880 && !insufficient_reason(&outcome).contains("Max Borrow"),
14881 "{}",
14882 insufficient_reason(&outcome)
14883 );
14884 }
14885
14886 #[test]
14887 fn a_cap_on_another_asset_does_not_limit_this_one() {
14888 let config = with_max_borrow(margin_config("0", "10000", "3"), quote(), "0");
14889 let mut venue = SimulatedVenue::new(&config, spot_instruments());
14890
14891 let outcome = venue.open_order(sell_request("0.3", market_prices("50000")));
14892
14893 assert_eq!(
14894 filled_balance(&outcome).balance,
14895 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.3"), Decimal::ZERO)
14896 );
14897 }
14898
14899 #[test]
14900 fn a_cap_of_zero_lends_none_of_the_asset() {
14901 let mut venue = capped_venue("0");
14902
14903 let outcome = venue.open_order(sell_request("0.01", market_prices("50000")));
14904
14905 let reject = borrow_rejection(&outcome);
14906 assert_eq!(reject.reason, BorrowRejectReason::BorrowLimitExceeded);
14907 assert_eq!(reject.available, Some(Decimal::ZERO));
14908 }
14909
14910 #[test]
14911 fn the_cap_counts_what_is_already_owed_interest_included() {
14912 let config = with_max_borrow(margin_config("0", "10000", "3"), base(), "0.3");
14913 let mut venue = SimulatedVenue::new(&config, spot_instruments())
14914 .with_margin_interest(Arc::new(hourly_interest(base())));
14915 advance(&mut venue, time(1));
14916 venue.open_order(sell_request("0.2", market_prices("50000")));
14918
14919 let outcome = venue.open_order(sell_request("0.1", market_prices("50000")));
14920 assert_eq!(borrow_rejection(&outcome).available, Some(d("0.09998")));
14921
14922 let outcome = venue.open_order(sell_request("0.09998", market_prices("50000")));
14923 assert_eq!(
14924 filled_balance(&outcome).balance.margin,
14925 Some(MarginDetails::new(d("0.29998"), d("0.00003"))),
14926 "0.00002 on the first loan, and 0.000009998 on the second, rounded up to 0.00001"
14927 );
14928 }
14929
14930 #[test]
14931 fn the_interest_a_new_loan_is_charged_when_made_is_not_counted_against_the_cap() {
14932 let config = with_max_borrow(margin_config("0", "10000", "3"), base(), "0.3");
14933 let mut venue = SimulatedVenue::new(&config, spot_instruments())
14934 .with_margin_interest(Arc::new(hourly_interest(base())));
14935 advance(&mut venue, time(1));
14936
14937 let outcome = venue.open_order(sell_request("0.3", market_prices("50000")));
14938
14939 assert_eq!(
14940 filled_balance(&outcome).balance.margin,
14941 Some(MarginDetails::new(d("0.3"), d("0.00003"))),
14942 "lent the whole 0.3 the cap leaves, its first charge taking the debt past the cap"
14943 );
14944
14945 let outcome = venue.open_order(sell_request("0.01", market_prices("50000")));
14946 let reject = borrow_rejection(&outcome);
14947 assert_eq!(
14948 reject.available,
14949 Some(Decimal::ZERO),
14950 "a debt past the cap leaves nothing to lend"
14951 );
14952 assert!(
14953 reject.message.ends_with("Borrowable: 0"),
14954 "{}",
14955 reject.message
14956 );
14957 }
14958
14959 #[test]
14960 fn a_minimum_borrow_past_what_the_cap_leaves_is_refused_with_nothing_available() {
14961 let config = with_min_borrow(
14962 with_max_borrow(margin_config("0", "10000", "3"), base(), "0.3"),
14963 base(),
14964 "0.5",
14965 );
14966 let mut venue = SimulatedVenue::new(&config, spot_instruments());
14967
14968 let outcome = venue.open_order(sell_request("0.1", market_prices("50000")));
14970 let reject = borrow_rejection(&outcome);
14971 assert_eq!(reject.reason, BorrowRejectReason::BorrowLimitExceeded);
14972 assert_eq!(
14973 reject.available,
14974 Some(Decimal::ZERO),
14975 "no loan can both reach the minimum and stay under the cap"
14976 );
14977 assert!(
14978 reject
14979 .message
14980 .ends_with("Borrowable: 0.3, Minimum Borrow: 0.5"),
14981 "{}",
14982 reject.message
14983 );
14984
14985 let outcome = venue.open_order(sell_request("0.35", market_prices("50000")));
14988 let reject = borrow_rejection(&outcome);
14989 assert_eq!(reject.available, Some(Decimal::ZERO));
14990 assert!(
14991 !reject.message.contains("Minimum Borrow"),
14992 "{}",
14993 reject.message
14994 );
14995 }
14996
14997 #[test]
14998 fn a_minimum_borrow_within_what_the_cap_leaves_keeps_what_it_leaves_available() {
14999 let config = with_min_borrow(
15000 with_max_borrow(margin_config("0", "10000", "3"), base(), "0.3"),
15001 base(),
15002 "0.3",
15003 );
15004 let mut venue = SimulatedVenue::new(&config, spot_instruments());
15005
15006 let outcome = venue.open_order(sell_request("0.35", market_prices("50000")));
15007
15008 assert_eq!(borrow_rejection(&outcome).available, Some(d("0.3")));
15009 }
15010
15011 #[test]
15012 fn a_buy_past_the_cap_on_its_quote_is_rejected_with_no_available_in_base() {
15013 let config = with_max_borrow(margin_config("1", "0", "3"), quote(), "5000");
15015 let mut venue = SimulatedVenue::new(&config, spot_instruments());
15016
15017 let outcome = venue.open_order(buy_request("0.2", market_prices("50000")));
15018
15019 let reject = borrow_rejection(&outcome);
15020 assert_eq!(reject.reason, BorrowRejectReason::BorrowLimitExceeded);
15021 assert_eq!(
15022 reject.available, None,
15023 "the loan is in USDT, and the order is sized in BTC"
15024 );
15025 assert!(
15026 reject
15027 .message
15028 .starts_with("USDT borrow exceeds Max Borrow: 5000;"),
15029 "{}",
15030 reject.message
15031 );
15032 }
15033
15034 #[test]
15035 fn a_resting_sell_past_the_cap_is_rejected_as_over_the_limit() {
15036 let config = with_max_borrow(margin_config("0", "100000", "3"), base(), "0.3");
15037 let mut venue = market_venue_for(&config);
15038
15039 let outcome = venue.open_order(limit_request("rest", Side::Sell, "0.4", "50000", gtc()));
15040
15041 let reject = borrow_rejection(&outcome);
15042 assert_eq!(reject.reason, BorrowRejectReason::BorrowLimitExceeded);
15043 assert_eq!(reject.available, Some(d("0.3")));
15044 }
15045
15046 #[test]
15047 fn a_held_sell_past_the_cap_is_rejected_on_arrival_as_over_the_limit() {
15048 let mut venue = margin_venue_holding_with(
15050 |config| config.with_max_borrow(base(), d("0.5")).unwrap(),
15051 MarginEffect::Borrow,
15052 );
15053
15054 let outcome = venue.open_order(market_order("held", Side::Sell, "0.6", gtc()));
15055
15056 let reject = borrow_rejection(&outcome);
15057 assert_eq!(reject.reason, BorrowRejectReason::BorrowLimitExceeded);
15058 assert_eq!(reject.available, Some(d("0.5")));
15059 }
15060
15061 #[test]
15062 fn a_held_sell_lent_the_whole_cap_fills_at_its_bar_though_interest_took_the_debt_past_it() {
15063 let mut venue = margin_venue_holding_with(
15064 |config| config.with_max_borrow(base(), d("0.5")).unwrap(),
15065 MarginEffect::Borrow,
15066 )
15067 .with_margin_interest(Arc::new(hourly_interest(base())));
15068 let outcome = venue.open_order(market_order("held", Side::Sell, "0.5", gtc()));
15069 assert_eq!(
15070 balance_of(sans_debt(&outcome.events)[0]).balance.margin,
15071 Some(MarginDetails::new(d("0.5"), d("0.00005"))),
15072 "the loan's first charge takes what is owed past the 0.5 cap"
15073 );
15074
15075 let events = feed_bar(&mut venue, 2, ohlc("98", "99", "97", "98"));
15077
15078 let trade = events
15079 .iter()
15080 .find_map(|event| match &event.kind {
15081 AccountEventKind::Trade(trade) => Some(trade),
15082 _ => None,
15083 })
15084 .expect("the held sell fills at its bar");
15085 assert_eq!(trade.quantity, d("0.5"));
15086 }
15087
15088 #[test]
15089 fn a_held_buy_whose_bar_needs_a_loan_past_the_cap_is_cancelled() {
15090 let mut venue = margin_venue_holding_with(
15092 |config| config.with_max_borrow(quote(), d("50")).unwrap(),
15093 MarginEffect::Borrow,
15094 );
15095 venue.open_order(market_order("held", Side::Buy, "1.5", gtc()));
15096
15097 let events = feed_bar(&mut venue, 2, ohlc("120", "121", "119", "120"));
15100
15101 assert_eq!(
15102 balance_of(sans_debt(&events)[0]).balance,
15103 Balance::new_margin(d("100"), d("100"), Decimal::ZERO, Decimal::ZERO),
15104 "the cancel repays the 50 the order borrowed when it was accepted"
15105 );
15106 assert!(matches!(
15107 order_of(sans_debt(&events)[1]).state,
15108 OrderState::Inactive(InactiveOrderState::Cancelled(_))
15109 ));
15110 }
15111
15112 const HOURLY_BASIS_POINT: &str = "0.876";
15118
15119 fn hourly_interest(asset: AssetNameExchange) -> PeriodicMarginInterest {
15121 PeriodicMarginInterest::new(TimeDelta::hours(1), DayCount::Act365)
15122 .with_rates(asset, RateSeries::constant(d(HOURLY_BASIS_POINT)))
15123 }
15124
15125 fn interest_flow(
15127 asset: AssetNameExchange,
15128 principal: &str,
15129 interest: &str,
15130 time: DateTime<Utc>,
15131 timing: InterestTiming,
15132 id: &str,
15133 ) -> CashFlow<AssetNameExchange, InstrumentNameExchange> {
15134 CashFlow::new(
15135 CashFlowKind::MarginInterest {
15136 rate: Some(d(HOURLY_BASIS_POINT)),
15137 principal: Some(d(principal)),
15138 timing: Some(timing),
15139 },
15140 asset,
15141 -d(interest),
15142 None,
15143 time,
15144 Some(CashFlowId::new(id)),
15145 )
15146 }
15147
15148 fn cash_flow_of(
15149 event: &UnindexedAccountEvent,
15150 ) -> &CashFlow<AssetNameExchange, InstrumentNameExchange> {
15151 match &event.kind {
15152 AccountEventKind::CashFlow(flow) => flow,
15153 other => panic!("expected a cash flow, got: {other:?}"),
15154 }
15155 }
15156
15157 fn margin_venue_short_btc() -> SimulatedVenue {
15160 let mut venue = margin_venue("0.25", "100000", "3")
15161 .with_margin_interest(Arc::new(hourly_interest(base())));
15162 advance(&mut venue, time(1));
15163 venue.open_order(sell_request("1", market_prices("50000")));
15164 venue
15165 }
15166
15167 #[test]
15168 fn a_sell_that_borrows_is_charged_a_period_of_interest_reported_after_its_trade() {
15169 let mut venue = margin_venue("0.25", "100000", "3")
15170 .with_margin_interest(Arc::new(hourly_interest(base())));
15171 advance(&mut venue, time(1));
15172
15173 let outcome = venue.open_order(sell_request("1", market_prices("50000")));
15174
15175 assert_eq!(
15176 filled_balance(&outcome).balance,
15177 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.75"), d("0.000075")),
15178 "the interest is added to the loan, leaving total and free as they were"
15179 );
15180 assert_eq!(trade_of(sans_debt(&outcome.events)[2]).quantity, d("1"));
15181 assert_eq!(
15182 *cash_flow_of(sans_debt(&outcome.events)[3]),
15183 interest_flow(
15184 base(),
15185 "0.75",
15186 "0.000075",
15187 time(1),
15188 InterestTiming::OnBorrow,
15189 "0"
15190 ),
15191 "charged on the amount lent, stamped at the borrow, after the trade that opened the \
15192 short, so the position it belongs to exists when it is read"
15193 );
15194 }
15195
15196 #[test]
15197 fn a_loan_is_charged_on_its_principal_at_each_boundary_and_never_on_its_interest() {
15198 let mut venue = margin_venue_short_btc();
15199 assert_eq!(venue.next_deadline(), Some(boundary(0)));
15200
15201 let events = venue.advance_time(boundary(1) + TimeDelta::seconds(1));
15202 let charged = charges(&events);
15203
15204 assert_eq!(charged.len(), 2);
15205 assert_eq!(
15206 charged[0].0.balance,
15207 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.75"), d("0.00015"))
15208 );
15209 assert_eq!(charged[0].0.time_exchange, boundary(0));
15210 assert_eq!(
15211 *charged[0].1,
15212 interest_flow(
15213 base(),
15214 "0.75",
15215 "0.000075",
15216 boundary(0),
15217 InterestTiming::Periodic,
15218 "1"
15219 )
15220 );
15221 assert_eq!(
15222 charged[1].0.balance,
15223 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.75"), d("0.000225"))
15224 );
15225 assert_eq!(
15226 *charged[1].1,
15227 interest_flow(
15228 base(),
15229 "0.75",
15230 "0.000075",
15231 boundary(1),
15232 InterestTiming::Periodic,
15233 "2"
15234 )
15235 );
15236 assert_eq!(venue.next_deadline(), Some(boundary(2)));
15237 }
15238
15239 fn debt_updates(
15242 events: &[UnindexedAccountEvent],
15243 ) -> Vec<(AssetNameExchange, MarginDebtKind, MarginDetails)> {
15244 events
15245 .iter()
15246 .enumerate()
15247 .filter_map(|(at, event)| {
15248 let AccountEventKind::MarginDebt(update) = &event.kind else {
15249 return None;
15250 };
15251 let restated = at
15252 .checked_sub(1)
15253 .map(|before| balance_of(&events[before]))
15254 .expect("a debt update follows a restatement");
15255 assert_eq!(update.asset, restated.asset);
15256 assert_eq!(Some(update.debt), restated.balance.margin);
15257 assert_eq!(update.time_exchange, restated.time_exchange);
15258 assert_eq!(update.instrument, None);
15259 assert_eq!(update.venue_kind, update.kind.to_smolstr());
15260 Some((update.asset.clone(), update.kind, update.debt))
15261 })
15262 .collect()
15263 }
15264
15265 #[test]
15268 fn a_margin_venue_reports_each_debt_change_after_its_restatement() {
15269 let mut venue = margin_venue("0.25", "100000", "3")
15270 .with_margin_interest(Arc::new(hourly_interest(base())));
15271 advance(&mut venue, time(1));
15272
15273 let sold = venue.open_order(sell_request("1", market_prices("50000")));
15274 assert_eq!(
15275 debt_updates(&sold.events),
15276 [(
15277 base(),
15278 MarginDebtKind::Borrow,
15279 MarginDetails::new(d("0.75"), d("0.000075"))
15280 )]
15281 );
15282
15283 let charged = venue.advance_time(boundary(0));
15284 assert_eq!(
15285 debt_updates(&charged),
15286 [(
15287 base(),
15288 MarginDebtKind::Interest,
15289 MarginDetails::new(d("0.75"), d("0.00015"))
15290 )]
15291 );
15292
15293 let repaid = venue.open_order(with_effect(
15294 buy_request("1", market_prices("50000")),
15295 MarginEffect::Repay,
15296 ));
15297 assert_eq!(
15298 debt_updates(&repaid.events),
15299 [(base(), MarginDebtKind::Repay, MarginDetails::default())]
15300 );
15301
15302 let traded = venue.open_order(sell_request("0.1", market_prices("50000")));
15304 assert!(debt_updates(&traded.events).is_empty(), "{traded:?}");
15305 }
15306
15307 #[test]
15310 fn a_repay_of_either_part_of_the_debt_reports_a_repay() {
15311 let mut venue = margin_venue_short_btc();
15313 let repaid = venue.open_order(with_effect(
15314 buy_request("0.00005", market_prices("50000")),
15315 MarginEffect::Repay,
15316 ));
15317 assert_eq!(
15318 debt_updates(&repaid.events),
15319 [(
15320 base(),
15321 MarginDebtKind::Repay,
15322 MarginDetails::new(d("0.75"), d("0.000025"))
15323 )]
15324 );
15325
15326 let mut venue = margin_venue("0.25", "100000", "3");
15328 advance(&mut venue, time(1));
15329 venue.open_order(sell_request("1", market_prices("50000")));
15330 let repaid = venue.open_order(with_effect(
15331 buy_request("0.5", market_prices("50000")),
15332 MarginEffect::Repay,
15333 ));
15334 assert_eq!(
15335 debt_updates(&repaid.events),
15336 [(
15337 base(),
15338 MarginDebtKind::Repay,
15339 MarginDetails::new(d("0.25"), Decimal::ZERO)
15340 )]
15341 );
15342 }
15343
15344 #[test]
15347 fn only_a_change_on_a_margin_venue_reports_debt() {
15348 let config = config_owing_base(MarginEffect::Borrow);
15349 let mut venue = market_venue_for(&config);
15350 let bought = venue.open_order(market_order("buy", Side::Buy, "0.1", gtc()));
15351 assert!(debt_updates(&bought.events).is_empty(), "{bought:?}");
15352
15353 let mut venue = make_venue("10", "1000000");
15354 let sold = venue.open_order(sell_request("1", market_prices("50000")));
15355 assert!(
15356 !sold
15357 .events
15358 .iter()
15359 .any(|event| matches!(event.kind, AccountEventKind::MarginDebt(_))),
15360 "{sold:?}"
15361 );
15362 }
15363
15364 #[test]
15365 fn a_loan_repaid_before_a_boundary_is_not_charged_there() {
15366 let mut venue = margin_venue_short_btc();
15367
15368 let outcome = venue.open_order(with_effect(
15370 buy_request("0.76", market_prices("50000")),
15371 MarginEffect::Repay,
15372 ));
15373 assert_eq!(
15374 filled_received(&outcome).balance,
15375 Balance::new_margin(d("0.009925"), d("0.009925"), Decimal::ZERO, Decimal::ZERO)
15376 );
15377
15378 assert_eq!(venue.next_deadline(), None);
15379 assert!(venue.advance_time(boundary(1)).is_empty());
15380 }
15381
15382 #[test]
15383 fn a_boundary_charges_the_principal_left_after_a_partial_repay() {
15384 let mut venue = margin_venue_short_btc();
15385
15386 venue.open_order(with_effect(
15388 buy_request("0.5", market_prices("50000")),
15389 MarginEffect::Repay,
15390 ));
15391
15392 let events = venue.advance_time(boundary(0));
15393 assert_eq!(
15394 *charges(&events)[0].1,
15395 interest_flow(
15396 base(),
15397 "0.250075",
15398 "0.00002501",
15399 boundary(0),
15400 InterestTiming::Periodic,
15401 "1"
15402 ),
15403 "0.250075 at 0.01% is 0.0000250075, rounded up to the satoshi"
15404 );
15405 }
15406
15407 #[test]
15408 fn a_loan_taken_at_a_boundary_s_instant_is_charged_there_only_at_borrow() {
15409 let mut venue = margin_venue("0.25", "100000", "3")
15410 .with_margin_interest(Arc::new(hourly_interest(base())));
15411 advance(&mut venue, time(1));
15412 advance(&mut venue, boundary(0));
15413
15414 let outcome = venue.open_order(sell_request("1", market_prices("50000")));
15415 assert_eq!(
15416 *cash_flow_of(sans_debt(&outcome.events)[3]),
15417 interest_flow(
15418 base(),
15419 "0.75",
15420 "0.000075",
15421 boundary(0),
15422 InterestTiming::OnBorrow,
15423 "0"
15424 )
15425 );
15426
15427 assert!(
15428 venue
15429 .advance_time(boundary(0) + TimeDelta::seconds(1))
15430 .is_empty()
15431 );
15432 assert_eq!(venue.next_deadline(), Some(boundary(1)));
15433 }
15434
15435 #[test]
15436 fn a_resting_sell_is_charged_when_placed_reported_at_its_cancel_which_repays_interest_first() {
15437 let mut venue = margin_market_venue("0", "100000")
15438 .with_margin_interest(Arc::new(hourly_interest(base())));
15439
15440 let outcome = venue.open_order(limit_request("rest", Side::Sell, "1", "50000", gtc()));
15441 assert_eq!(
15442 sans_debt(&outcome.events).len(),
15443 1,
15444 "the hold's balance alone: nothing traded, so the interest has no position to belong \
15445 to yet: {:?}",
15446 outcome.events
15447 );
15448 assert_eq!(
15449 balance_of(sans_debt(&outcome.events)[0]).balance,
15450 Balance::new_margin(d("1"), Decimal::ZERO, d("1"), d("0.0001")),
15451 "the balance already carries the charge"
15452 );
15453
15454 advance(&mut venue, time(5));
15455 let cancelled = venue.cancel_order(OrderRequestCancel {
15456 key: limit_request("rest", Side::Sell, "1", "50000", gtc()).key,
15457 state: RequestCancel { id: None },
15458 });
15459 assert_eq!(
15460 balance_of(sans_debt(&cancelled.events)[0]).balance,
15461 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("0.0001"), Decimal::ZERO),
15462 "the released base repays the interest first, so as much principal stays owed"
15463 );
15464 assert_eq!(
15465 *cash_flow_of(sans_debt(&cancelled.events)[1]),
15466 interest_flow(
15467 base(),
15468 "1",
15469 "0.0001",
15470 time(5),
15471 InterestTiming::OnBorrow,
15472 "0"
15473 ),
15474 "reported with the cancel, the order's end, stamped then"
15475 );
15476 assert_eq!(sans_debt(&cancelled.events).len(), 2);
15477 assert_eq!(venue.next_deadline(), Some(boundary(0)));
15478 }
15479
15480 #[test]
15481 fn a_held_buy_reports_the_interest_of_its_hold_and_of_its_bar_after_its_fill() {
15482 let mut venue =
15483 margin_venue_holding().with_margin_interest(Arc::new(hourly_interest(quote())));
15484
15485 let outcome = venue.open_order(market_order("held", Side::Buy, "1.5", gtc()));
15487 assert_eq!(
15488 sans_debt(&outcome.events).len(),
15489 1,
15490 "the hold's balance alone: the interest waits for the fill: {:?}",
15491 outcome.events
15492 );
15493 assert_eq!(
15494 balance_of(sans_debt(&outcome.events)[0]).balance,
15495 Balance::new_margin(d("150"), Decimal::ZERO, d("50"), d("0.005"))
15496 );
15497
15498 let events = feed_bar(&mut venue, 2, ohlc("120", "121", "119", "120"));
15500 let events = sans_debt(&events);
15501 assert_eq!(
15502 balance_of(events[0]).balance,
15503 Balance::new_margin(Decimal::ZERO, Decimal::ZERO, d("80"), d("0.008"))
15504 );
15505 assert_eq!(trade_of(events[2]).price, d("120"));
15506 assert_eq!(
15507 *cash_flow_of(events[3]),
15508 interest_flow(
15509 quote(),
15510 "50",
15511 "0.005",
15512 time(2),
15513 InterestTiming::OnBorrow,
15514 "0"
15515 ),
15516 "the hold's charge after the fill, stamped at the fill, so a position entered then \
15517 takes it"
15518 );
15519 assert_eq!(
15520 cash_flow_of(events[4]).amount,
15521 d("-0.003"),
15522 "then what the bar borrowed"
15523 );
15524 assert_eq!(cash_flow_of(events[4]).time_exchange, time(2));
15525 assert_eq!(events.len(), 6, "and the filled order last: {events:?}");
15526 }
15527
15528 #[test]
15529 fn a_resting_sell_that_borrowed_reports_its_interest_after_its_fill() {
15530 let mut venue = margin_market_venue("0", "100000")
15531 .with_margin_interest(Arc::new(hourly_interest(base())));
15532 venue.open_order(limit_request("rest", Side::Sell, "1", "50000", gtc()));
15533
15534 advance(&mut venue, time(7));
15535 let events = venue.apply_market(
15536 &instrument_name(),
15537 MarketObservation::new(book("50100", "50200")),
15538 time(7),
15539 );
15540 let events = sans_debt(&events);
15541
15542 assert_eq!(trade_of(events[2]).quantity, d("1"));
15543 assert_eq!(
15544 *cash_flow_of(events[3]),
15545 interest_flow(
15546 base(),
15547 "1",
15548 "0.0001",
15549 time(7),
15550 InterestTiming::OnBorrow,
15551 "0"
15552 ),
15553 "after the fill that opened the short, stamped at it"
15554 );
15555 assert_eq!(events.len(), 5, "and the filled order last: {events:?}");
15556 }
15557
15558 #[test]
15559 fn a_held_buy_unaffordable_at_its_bar_reports_its_interest_with_its_cancel() {
15560 let mut venue =
15561 margin_venue_holding().with_margin_interest(Arc::new(hourly_interest(quote())));
15562 venue.open_order(market_order("held", Side::Buy, "1.5", gtc()));
15563
15564 let events = feed_bar(&mut venue, 2, ohlc("250", "251", "249", "250"));
15566 let events = sans_debt(&events);
15567
15568 assert_eq!(
15569 *cash_flow_of(events[1]),
15570 interest_flow(
15571 quote(),
15572 "50",
15573 "0.005",
15574 time(2),
15575 InterestTiming::OnBorrow,
15576 "0"
15577 ),
15578 "the order never traded, so its end reports the charge, stamped then"
15579 );
15580 assert!(matches!(
15581 order_of(events[2]).state,
15582 OrderState::Inactive(InactiveOrderState::Cancelled(_))
15583 ));
15584 }
15585
15586 #[test]
15587 fn a_held_sell_that_expires_reports_its_interest_before_its_end() {
15588 let mut venue = margin_venue_holding_with(|config| config, MarginEffect::Borrow)
15589 .with_margin_interest(Arc::new(hourly_interest(base())));
15590 venue.open_order(market_order("held", Side::Sell, "1", gtc()));
15591
15592 let events = venue.expire_held(time(2));
15593 let events = sans_debt(&events);
15594
15595 assert_eq!(
15596 *cash_flow_of(events[1]),
15597 interest_flow(
15598 base(),
15599 "1",
15600 "0.0001",
15601 time(2),
15602 InterestTiming::OnBorrow,
15603 "0"
15604 )
15605 );
15606 assert!(matches!(
15607 order_of(events[2]).state,
15608 OrderState::Inactive(InactiveOrderState::Expired(_))
15609 ));
15610 }
15611
15612 #[test]
15613 fn a_held_buy_whose_bar_repays_its_whole_loan_owes_no_more_boundaries() {
15614 let free_loans = PeriodicMarginInterest::new(TimeDelta::hours(1), DayCount::Act365)
15616 .with_rates(quote(), RateSeries::constant(Decimal::ZERO));
15617 let mut venue = margin_venue_holding().with_margin_interest(Arc::new(free_loans));
15618
15619 venue.open_order(market_order("held", Side::Buy, "1.5", gtc()));
15621 assert_eq!(venue.next_deadline(), Some(boundary(0)));
15622
15623 let events = feed_bar(&mut venue, 2, ohlc("60", "61", "59", "60"));
15625 assert_eq!(
15626 balance_of(&events[0]).balance,
15627 Balance::new_margin(d("10"), d("10"), Decimal::ZERO, Decimal::ZERO)
15628 );
15629 assert_eq!(venue.next_deadline(), None);
15630 }
15631
15632 #[test]
15633 fn a_seeded_loan_is_charged_at_boundaries_from_the_clock_s_first_move_only() {
15634 let config = MockExecutionConfig {
15635 spot_margin: Some(
15636 SpotMarginConfig::new(quote(), d("3"), MarginEffect::Borrow).unwrap(),
15637 ),
15638 ..config_from_balances(
15639 vec![
15640 AssetBalance::new(
15641 base(),
15642 Balance::new_margin(d("0.5"), d("0.5"), d("0.5"), Decimal::ZERO),
15643 Utc::now(),
15644 ),
15645 funded(quote(), d("100000")),
15646 ],
15647 FeeModelConfig::default(),
15648 )
15649 };
15650 let mut venue = SimulatedVenue::new(&config, spot_instruments())
15651 .with_margin_interest(Arc::new(hourly_interest(base())));
15652 assert_eq!(
15653 venue.next_deadline(),
15654 None,
15655 "charging starts when the clock moves"
15656 );
15657
15658 advance(&mut venue, time(1));
15659 assert_eq!(venue.next_deadline(), Some(boundary(0)));
15660
15661 let events = venue.advance_time(boundary(0));
15662 assert_eq!(
15663 *charges(&events)[0].1,
15664 interest_flow(
15665 base(),
15666 "0.5",
15667 "0.00005",
15668 boundary(0),
15669 InterestTiming::Periodic,
15670 "0"
15671 )
15672 );
15673 }
15674
15675 #[test]
15676 fn at_borrow_only_charges_the_borrow_and_no_boundary() {
15677 let mut venue = margin_venue("0.25", "100000", "3").with_margin_interest(Arc::new(
15678 hourly_interest(base()).with_schedule(InterestSchedule::AtBorrowOnly),
15679 ));
15680 advance(&mut venue, time(1));
15681
15682 let outcome = venue.open_order(sell_request("1", market_prices("50000")));
15683
15684 assert_eq!(
15685 filled_balance(&outcome).balance.margin.unwrap().interest,
15686 d("0.000075")
15687 );
15688 assert_eq!(venue.next_deadline(), None);
15689 assert!(venue.advance_time(boundary(3)).is_empty());
15690 }
15691
15692 #[test]
15693 #[should_panic(expected = "without MockExecutionConfig::spot_margin")]
15694 fn a_cash_venue_given_an_interest_model_panics() {
15695 let _ = make_venue("1", "100000").with_margin_interest(Arc::new(hourly_interest(base())));
15696 }
15697
15698 #[derive(Debug)]
15701 struct Misbehaving;
15702
15703 impl MarginInterestModel for Misbehaving {
15704 fn next_boundary(
15705 &self,
15706 _: &AssetNameExchange,
15707 after: DateTime<Utc>,
15708 ) -> Option<DateTime<Utc>> {
15709 Some(after)
15710 }
15711
15712 fn charge(
15713 &self,
15714 _: &AssetNameExchange,
15715 _: Decimal,
15716 _: InterestDue,
15717 ) -> Option<InterestCharge> {
15718 Some(InterestCharge::new(d("-0.00000001"), None))
15719 }
15720 }
15721
15722 #[derive(Debug)]
15724 struct FlatAtBorrow;
15725
15726 impl MarginInterestModel for FlatAtBorrow {
15727 fn next_boundary(&self, _: &AssetNameExchange, _: DateTime<Utc>) -> Option<DateTime<Utc>> {
15728 None
15729 }
15730
15731 fn charge(
15732 &self,
15733 _: &AssetNameExchange,
15734 _: Decimal,
15735 due: InterestDue,
15736 ) -> Option<InterestCharge> {
15737 matches!(due, InterestDue::Borrow(_))
15738 .then(|| InterestCharge::new(d("0.00000001"), None))
15739 }
15740 }
15741
15742 #[test]
15743 fn an_order_that_borrows_nothing_is_not_asked_about_interest() {
15744 let mut venue =
15745 margin_venue("0.25", "100000", "3").with_margin_interest(Arc::new(FlatAtBorrow));
15746 advance(&mut venue, time(1));
15747
15748 let outcome = venue.open_order(sell_request("0.1", market_prices("50000")));
15749
15750 assert_eq!(filled_trade(&outcome).quantity, d("0.1"));
15751 assert!(
15752 !outcome
15753 .events
15754 .iter()
15755 .any(|event| matches!(event.kind, AccountEventKind::CashFlow(_))),
15756 "0.1 of the 0.25 held lends nothing, so nothing is charged"
15757 );
15758 }
15759
15760 #[test]
15761 #[should_panic(expected = "charged negative interest")]
15762 fn a_model_charging_negative_interest_panics() {
15763 let mut venue =
15764 margin_venue("0.25", "100000", "3").with_margin_interest(Arc::new(Misbehaving));
15765 advance(&mut venue, time(1));
15766
15767 let _ = venue.open_order(sell_request("1", market_prices("50000")));
15768 }
15769
15770 #[test]
15771 #[should_panic(expected = "which is not after")]
15772 fn a_model_whose_boundary_is_not_after_the_instant_asked_panics() {
15773 let config = MockExecutionConfig {
15774 spot_margin: Some(
15775 SpotMarginConfig::new(quote(), d("3"), MarginEffect::Borrow).unwrap(),
15776 ),
15777 ..config_from_balances(
15778 vec![
15779 AssetBalance::new(
15780 base(),
15781 Balance::new_margin(d("0.5"), d("0.5"), d("0.5"), Decimal::ZERO),
15782 Utc::now(),
15783 ),
15784 funded(quote(), d("100000")),
15785 ],
15786 FeeModelConfig::default(),
15787 )
15788 };
15789 let mut venue = SimulatedVenue::new(&config, spot_instruments())
15790 .with_margin_interest(Arc::new(Misbehaving));
15791
15792 let _ = venue.advance_time(time(1));
15793 }
15794
15795 fn bnb() -> AssetNameExchange {
15800 AssetNameExchange::new("BNB")
15801 }
15802
15803 fn spot_instrument(
15804 name: &str,
15805 base: &str,
15806 quote: &str,
15807 ) -> Instrument<ExchangeId, AssetNameExchange> {
15808 Instrument {
15809 exchange: EXCHANGE,
15810 name_internal: InstrumentNameInternal::new(name.to_lowercase()),
15811 name_exchange: InstrumentNameExchange::new(name),
15812 underlying: Underlying {
15813 base: AssetNameExchange::new(base),
15814 quote: AssetNameExchange::new(quote),
15815 },
15816 quote: InstrumentQuoteAsset::UnderlyingQuote,
15817 kind: InstrumentKind::Spot,
15818 spec: None,
15819 data_venue: None,
15820 }
15821 }
15822
15823 fn fee_asset_instruments()
15825 -> FnvHashMap<InstrumentNameExchange, Instrument<ExchangeId, AssetNameExchange>> {
15826 let mut instruments = spot_instruments();
15827 let bnb_usdt = spot_instrument("BNBUSDT", "BNB", "USDT");
15828 instruments.insert(bnb_usdt.name_exchange.clone(), bnb_usdt);
15829 instruments
15830 }
15831
15832 fn fee_asset_config(bnb: &str) -> MockExecutionConfig {
15835 MockExecutionConfig {
15836 fee_asset: Some(FeeAssetConfig::new(self::bnb(), d("0.25")).unwrap()),
15837 ..config_from_balances(
15838 vec![
15839 funded(base(), d("10")),
15840 funded(quote(), d("100000")),
15841 funded(self::bnb(), d(bnb)),
15842 ],
15843 maker_taker_fee(),
15844 )
15845 }
15846 }
15847
15848 fn fee_asset_venue_from(config: &MockExecutionConfig, priced: bool) -> SimulatedVenue {
15851 let mut venue = SimulatedVenue::new_market_driven(config, fee_asset_instruments());
15852 advance(&mut venue, time(1));
15853 if priced {
15854 assert!(
15855 venue
15856 .apply_market(
15857 &InstrumentNameExchange::new("BNBUSDT"),
15858 MarketObservation::new(book("599", "601")),
15859 time(1)
15860 )
15861 .is_empty()
15862 );
15863 }
15864 assert!(
15865 venue
15866 .apply_market(
15867 &instrument_name(),
15868 MarketObservation::new(traded("50000")),
15869 time(1)
15870 )
15871 .is_empty()
15872 );
15873 venue
15874 }
15875
15876 fn fee_asset_venue(bnb: &str) -> SimulatedVenue {
15877 fee_asset_venue_from(&fee_asset_config(bnb), true)
15878 }
15879
15880 fn restated(events: &[UnindexedAccountEvent]) -> Vec<(AssetNameExchange, Balance)> {
15882 events
15883 .iter()
15884 .filter_map(|event| match &event.kind {
15885 AccountEventKind::BalanceSnapshot(balance) => {
15886 Some((balance.0.asset.clone(), balance.0.balance))
15887 }
15888 _ => None,
15889 })
15890 .collect()
15891 }
15892
15893 #[test]
15896 fn a_fill_pays_its_fee_in_the_fee_asset_at_a_discount() {
15897 let mut venue = fee_asset_venue("1");
15898
15899 let outcome = venue.open_order(buy_request("1", None));
15900
15901 assert_eq!(
15902 filled_trade(&outcome).fees,
15903 AssetFees::new(bnb(), d("0.0625"), Some(d("37.5")))
15904 );
15905 assert_eq!(
15906 restated(&outcome.events),
15907 vec![
15908 (quote(), Balance::new(d("50000"), d("50000"))),
15909 (base(), Balance::new(d("11"), d("11"))),
15910 (bnb(), Balance::new(d("0.9375"), d("0.9375"))),
15911 ]
15912 );
15913 }
15914
15915 #[test]
15916 fn a_sell_receives_its_whole_proceeds_and_pays_its_fee_in_the_fee_asset() {
15917 let mut venue = fee_asset_venue("1");
15918
15919 let outcome = venue.open_order(sell_request("1", None));
15920
15921 assert_eq!(filled_trade(&outcome).fees.asset, bnb());
15922 assert_eq!(
15923 restated(&outcome.events),
15924 vec![
15925 (base(), Balance::new(d("9"), d("9"))),
15926 (quote(), Balance::new(d("150000"), d("150000"))),
15927 (bnb(), Balance::new(d("0.9375"), d("0.9375"))),
15928 ]
15929 );
15930 }
15931
15932 #[test]
15935 fn a_fee_asset_balance_short_of_the_fee_falls_back_whole() {
15936 for bnb in ["0.0624", "0"] {
15937 let mut venue = fee_asset_venue(bnb);
15938
15939 let outcome = venue.open_order(buy_request("1", None));
15940
15941 assert_eq!(
15942 filled_trade(&outcome).fees,
15943 AssetFees::new(quote(), d("50"), Some(d("50"))),
15944 "{bnb} BNB"
15945 );
15946 assert_eq!(
15947 restated(&outcome.events),
15948 vec![
15949 (quote(), Balance::new(d("49950"), d("49950"))),
15950 (base(), Balance::new(d("11"), d("11"))),
15951 ],
15952 "{bnb} BNB"
15953 );
15954 }
15955 }
15956
15957 #[test]
15959 fn a_fee_asset_balance_of_exactly_the_fee_pays_it() {
15960 let mut venue = fee_asset_venue("0.0625");
15961
15962 let outcome = venue.open_order(buy_request("1", None));
15963
15964 assert_eq!(filled_trade(&outcome).fees.asset, bnb());
15965 assert_eq!(balance_now(&venue, bnb()), Balance::new(d("0"), d("0")));
15966 }
15967
15968 #[test]
15969 fn a_fee_asset_the_venue_cannot_price_falls_back() {
15970 let mut venue = fee_asset_venue_from(&fee_asset_config("1"), false);
15971
15972 let outcome = venue.open_order(buy_request("1", None));
15973
15974 assert_eq!(
15975 filled_trade(&outcome).fees,
15976 AssetFees::new(quote(), d("50"), Some(d("50")))
15977 );
15978 assert_eq!(balance_now(&venue, bnb()), Balance::new(d("1"), d("1")));
15979 }
15980
15981 #[test]
15983 fn a_fill_on_the_fee_assets_own_instrument_falls_back() {
15984 let mut venue = fee_asset_venue("1");
15985 assert!(
15986 venue
15987 .apply_market(
15988 &InstrumentNameExchange::new("BNBUSDT"),
15989 MarketObservation::new(traded("600")),
15990 time(1)
15991 )
15992 .is_empty()
15993 );
15994 let mut request = buy_request("1", None);
15995 request.key.instrument = InstrumentNameExchange::new("BNBUSDT");
15996
15997 let outcome = venue.open_order(request);
15998
15999 assert_eq!(
16001 filled_trade(&outcome).fees,
16002 AssetFees::new(quote(), d("0.6"), Some(d("0.6")))
16003 );
16004 assert_eq!(balance_now(&venue, bnb()), Balance::new(d("2"), d("2")));
16005 }
16006
16007 #[test]
16010 fn a_resting_fill_paid_in_the_fee_asset_releases_the_fee_it_held() {
16011 let mut venue = fee_asset_venue("1");
16012 assert!(
16013 venue
16014 .apply_market(
16015 &instrument_name(),
16016 MarketObservation::new(book("49000", "49100")),
16017 time(1)
16018 )
16019 .is_empty()
16020 );
16021 let outcome = venue.open_order(limit_request("resting", Side::Buy, "1", "49000", gtc()));
16022 assert_eq!(
16024 filled_balance(&outcome).balance,
16025 Balance::new(d("100000"), d("50990.2"))
16026 );
16027
16028 let events = venue.apply_market(
16029 &instrument_name(),
16030 MarketObservation::new(book("48900", "48950")),
16031 time(2),
16032 );
16033
16034 let trade = events
16036 .iter()
16037 .find_map(|event| match &event.kind {
16038 AccountEventKind::Trade(trade) => Some(trade),
16039 _ => None,
16040 })
16041 .unwrap();
16042 assert_eq!(
16043 trade.fees,
16044 AssetFees::new(bnb(), d("0.01225"), Some(d("7.35")))
16045 );
16046 assert_eq!(
16047 restated(&events),
16048 vec![
16049 (quote(), Balance::new(d("51000"), d("51000"))),
16050 (base(), Balance::new(d("11"), d("11"))),
16051 (bnb(), Balance::new(d("0.98775"), d("0.98775"))),
16052 ]
16053 );
16054 }
16055
16056 #[test]
16058 fn under_received_a_buy_paying_in_the_fee_asset_receives_its_whole_quantity() {
16059 let config = MockExecutionConfig {
16060 spot_fee_asset: SpotFeeAsset::Received,
16061 ..fee_asset_config("1")
16062 };
16063 let mut venue = fee_asset_venue_from(&config, true);
16064
16065 let outcome = venue.open_order(buy_request("1", None));
16066
16067 assert_eq!(filled_trade(&outcome).fees.asset, bnb());
16068 assert_eq!(balance_now(&venue, base()).total, d("11"));
16069 assert_eq!(balance_now(&venue, quote()).total, d("50000"));
16070 }
16071
16072 #[test]
16073 fn a_zero_fee_is_charged_without_the_fee_asset() {
16074 let config = MockExecutionConfig {
16075 fee_model: FeeModelConfig::default(),
16076 ..fee_asset_config("1")
16077 };
16078 let mut venue = fee_asset_venue_from(&config, true);
16079
16080 let outcome = venue.open_order(buy_request("1", None));
16081
16082 assert_eq!(
16083 filled_trade(&outcome).fees,
16084 AssetFees::new(quote(), Decimal::ZERO, Some(Decimal::ZERO))
16085 );
16086 assert_eq!(balance_now(&venue, bnb()), Balance::new(d("1"), d("1")));
16087 }
16088
16089 #[test]
16091 fn a_full_discount_charges_nothing_and_needs_no_fee_asset_balance() {
16092 let config = MockExecutionConfig {
16093 fee_asset: Some(FeeAssetConfig::new(bnb(), Decimal::ONE).unwrap()),
16094 ..config_from_balances(
16095 vec![funded(base(), d("10")), funded(quote(), d("100000"))],
16096 maker_taker_fee(),
16097 )
16098 };
16099 let mut venue = fee_asset_venue_from(&config, true);
16100
16101 let outcome = venue.open_order(buy_request("1", None));
16102
16103 assert_eq!(
16104 filled_trade(&outcome).fees,
16105 AssetFees::new(bnb(), Decimal::ZERO, Some(Decimal::ZERO))
16106 );
16107 assert_eq!(
16108 restated(&outcome.events),
16109 vec![
16110 (quote(), Balance::new(d("50000"), d("50000"))),
16111 (base(), Balance::new(d("11"), d("11"))),
16112 ]
16113 );
16114 }
16115
16116 #[test]
16118 fn a_held_market_order_pays_its_fee_in_the_fee_asset() {
16119 let config = MockExecutionConfig {
16120 market_order_timing: MarketOrderTiming::NextBar(NextBarPrice::Open),
16121 ..fee_asset_config("1")
16122 };
16123 let mut venue = SimulatedVenue::new_market_driven(&config, fee_asset_instruments());
16124 advance(&mut venue, time(1));
16125 assert!(
16126 venue
16127 .apply_market(
16128 &InstrumentNameExchange::new("BNBUSDT"),
16129 MarketObservation::new(book("599", "601")),
16130 time(1)
16131 )
16132 .is_empty()
16133 );
16134 assert!(feed_bar(&mut venue, 1, ohlc("95", "105", "90", "100")).is_empty());
16135 let held = venue.open_order(ioc_buy("held", "1"));
16136 assert!(held.response.state.is_accepted());
16137
16138 advance(&mut venue, time(2));
16139 let events = feed_bar(&mut venue, 2, ohlc("200", "210", "190", "205"));
16140
16141 assert_eq!(
16143 restated(&events),
16144 vec![
16145 (quote(), Balance::new(d("99800"), d("99800"))),
16146 (base(), Balance::new(d("11"), d("11"))),
16147 (bnb(), Balance::new(d("0.99975"), d("0.99975"))),
16148 ]
16149 );
16150 }
16151
16152 #[test]
16155 fn a_fill_on_an_instrument_quoted_in_the_fee_asset_falls_back() {
16156 let mut instruments = fee_asset_instruments();
16157 let eth_bnb = spot_instrument("ETHBNB", "ETH", "BNB");
16158 instruments.insert(eth_bnb.name_exchange.clone(), eth_bnb);
16159 let eth = || AssetNameExchange::new("ETH");
16160 let config = MockExecutionConfig {
16161 fee_asset: Some(FeeAssetConfig::new(bnb(), d("0.25")).unwrap()),
16162 ..config_from_balances(
16163 vec![
16164 funded(base(), d("10")),
16165 funded(quote(), d("100000")),
16166 funded(bnb(), d("10")),
16167 funded(eth(), d("1")),
16168 ],
16169 maker_taker_fee(),
16170 )
16171 };
16172
16173 for (side, eth_after, bnb_after) in [
16174 (Side::Buy, "2", "9.4995"),
16176 (Side::Sell, "0", "10.4995"),
16178 ] {
16179 let mut venue = SimulatedVenue::new_market_driven(&config, instruments.clone());
16180 advance(&mut venue, time(1));
16181 assert!(
16182 venue
16183 .apply_market(
16184 &InstrumentNameExchange::new("ETHBNB"),
16185 MarketObservation::new(traded("0.5")),
16186 time(1)
16187 )
16188 .is_empty()
16189 );
16190 let mut request = match side {
16191 Side::Buy => buy_request("1", None),
16192 Side::Sell => sell_request("1", None),
16193 };
16194 request.key.instrument = InstrumentNameExchange::new("ETHBNB");
16195
16196 let outcome = venue.open_order(request);
16197
16198 assert_eq!(
16200 filled_trade(&outcome).fees,
16201 AssetFees::new(bnb(), d("0.0005"), Some(d("0.0005"))),
16202 "{side:?}"
16203 );
16204 assert_eq!(balance_now(&venue, eth()).total, d(eth_after), "{side:?}");
16205 assert_eq!(balance_now(&venue, bnb()).total, d(bnb_after), "{side:?}");
16206 }
16207 }
16208
16209 #[test]
16212 fn a_negative_fee_is_paid_without_the_fee_asset() {
16213 let rebate =
16214 FeeModelConfig::Percentage(PercentageFeeModel::maker_taker(d("-0.0001"), d("-0.0001")));
16215 let config = MockExecutionConfig {
16216 fee_model: rebate,
16217 ..fee_asset_config("1")
16218 };
16219 let mut venue = fee_asset_venue_from(&config, true);
16220
16221 let outcome = venue.open_order(buy_request("1", None));
16222
16223 assert_eq!(
16224 filled_trade(&outcome).fees,
16225 AssetFees::new(quote(), d("-5"), Some(d("-5")))
16226 );
16227 assert_eq!(balance_now(&venue, quote()).total, d("50005"));
16228 assert_eq!(balance_now(&venue, bnb()), Balance::new(d("1"), d("1")));
16229 }
16230
16231 #[test]
16234 fn the_fee_asset_is_priced_by_a_reciprocal_pair_or_the_pair_first_by_name() {
16235 let usdt_bnb = spot_instrument("USDTBNB", "USDT", "BNB");
16236 let feed = |venue: &mut SimulatedVenue, name: &str, price: &str| {
16237 assert!(
16238 venue
16239 .apply_market(
16240 &InstrumentNameExchange::new(name),
16241 MarketObservation::new(traded(price)),
16242 time(1)
16243 )
16244 .is_empty()
16245 );
16246 };
16247
16248 let mut instruments = spot_instruments();
16250 instruments.insert(usdt_bnb.name_exchange.clone(), usdt_bnb.clone());
16251 let mut venue = SimulatedVenue::new_market_driven(&fee_asset_config("1"), instruments);
16252 advance(&mut venue, time(1));
16253 feed(&mut venue, "USDTBNB", "0.0016");
16254 feed(&mut venue, "BTCUSDT", "50000");
16255 let outcome = venue.open_order(buy_request("1", None));
16256 assert_eq!(
16257 filled_trade(&outcome).fees,
16258 AssetFees::new(bnb(), d("0.06"), Some(d("37.5")))
16259 );
16260
16261 let mut instruments = fee_asset_instruments();
16263 instruments.insert(usdt_bnb.name_exchange.clone(), usdt_bnb);
16264 let mut venue = SimulatedVenue::new_market_driven(&fee_asset_config("1"), instruments);
16265 advance(&mut venue, time(1));
16266 feed(&mut venue, "BNBUSDT", "600");
16267 feed(&mut venue, "USDTBNB", "0.0016");
16268 feed(&mut venue, "BTCUSDT", "50000");
16269 let outcome = venue.open_order(buy_request("1", None));
16270 assert_eq!(
16271 filled_trade(&outcome).fees,
16272 AssetFees::new(bnb(), d("0.0625"), Some(d("37.5")))
16273 );
16274 }
16275
16276 #[test]
16279 fn a_partly_filled_order_pays_its_fill_in_the_fee_asset_and_holds_the_rest_in_quote() {
16280 let mut venue = fee_asset_venue("1");
16281 assert!(
16282 venue
16283 .apply_market(
16284 &instrument_name(),
16285 MarketObservation::new(book("49000", "49100"))
16286 .with_depth(MarketDepth::new(None, Some(d("0.5")))),
16287 time(1),
16288 )
16289 .is_empty()
16290 );
16291
16292 let outcome = venue.open_order(limit_request("part", Side::Buy, "1", "49100", gtc()));
16293
16294 assert_eq!(rested_open(&outcome).filled_quantity, d("0.5"));
16295 assert_eq!(
16297 filled_trade(&outcome).fees,
16298 AssetFees::new(bnb(), d("0.0306875"), Some(d("18.4125")))
16299 );
16300 assert_eq!(
16302 balance_now(&venue, quote()),
16303 Balance::new(d("75450"), d("50895.09"))
16304 );
16305 assert_eq!(
16306 balance_now(&venue, bnb()),
16307 Balance::new(d("0.9693125"), d("0.9693125"))
16308 );
16309 }
16310
16311 fn fee_asset_margin_venue(bnb: &str) -> SimulatedVenue {
16314 let config = MockExecutionConfig {
16315 spot_margin: Some(
16316 SpotMarginConfig::new(quote(), d("3"), MarginEffect::Borrow).unwrap(),
16317 ),
16318 fee_asset: Some(FeeAssetConfig::new(self::bnb(), d("0.25")).unwrap()),
16319 ..config_from_balances(
16320 vec![
16321 funded(base(), Decimal::ZERO),
16322 funded(quote(), d("10000")),
16323 funded(self::bnb(), d(bnb)),
16324 ],
16325 maker_taker_fee(),
16326 )
16327 };
16328 fee_asset_venue_from(&config, true).with_margin_interest(Arc::new(hourly_interest(quote())))
16329 }
16330
16331 #[test]
16335 fn a_margin_buy_paying_in_the_fee_asset_borrows_only_its_notional() {
16336 let mut venue = fee_asset_margin_venue("1");
16337
16338 let outcome = venue.open_order(buy_request("0.3", None));
16339
16340 assert_eq!(
16342 filled_trade(&outcome).fees,
16343 AssetFees::new(bnb(), d("0.01875"), Some(d("11.25")))
16344 );
16345 let paid = balance_of(sans_debt(&outcome.events)[0]);
16346 assert_eq!(paid.asset, quote());
16347 assert_eq!(
16348 paid.balance.margin.map(|margin| margin.borrowed),
16349 Some(d("5000"))
16350 );
16351 assert_eq!(balance_of(sans_debt(&outcome.events)[1]).asset, base());
16352 assert_eq!(
16353 *balance_of(sans_debt(&outcome.events)[2]),
16354 AssetBalance::new(
16355 bnb(),
16356 Balance::new_margin(d("0.98125"), d("0.98125"), d("0"), d("0")),
16357 time(1)
16358 )
16359 );
16360 assert_eq!(trade_of(sans_debt(&outcome.events)[3]).quantity, d("0.3"));
16361 assert_eq!(
16363 *cash_flow_of(sans_debt(&outcome.events)[4]),
16364 interest_flow(
16365 quote(),
16366 "5000",
16367 "0.5",
16368 time(1),
16369 InterestTiming::OnBorrow,
16370 "0"
16371 )
16372 );
16373 }
16374
16375 #[test]
16378 fn a_margin_account_short_of_the_fee_asset_borrows_the_fallback_fee_not_the_fee_asset() {
16379 let mut venue = fee_asset_margin_venue("0");
16380
16381 let outcome = venue.open_order(buy_request("0.3", None));
16382
16383 assert_eq!(
16384 filled_trade(&outcome).fees,
16385 AssetFees::new(quote(), d("15"), Some(d("15")))
16386 );
16387 assert_eq!(
16388 balance_now(&venue, quote())
16389 .margin
16390 .map(|margin| margin.borrowed),
16391 Some(d("5015"))
16392 );
16393 assert_eq!(
16394 balance_now(&venue, bnb()),
16395 Balance::new_margin(d("0"), d("0"), d("0"), d("0"))
16396 );
16397 }
16398
16399 #[test]
16402 fn a_resting_margin_fill_paying_in_the_fee_asset_repays_the_fee_it_borrowed() {
16403 let mut venue = fee_asset_margin_venue("1");
16404 assert!(
16405 venue
16406 .apply_market(
16407 &instrument_name(),
16408 MarketObservation::new(book("49000", "49100")),
16409 time(1)
16410 )
16411 .is_empty()
16412 );
16413 let outcome = venue.open_order(limit_request("resting", Side::Buy, "0.3", "49000", gtc()));
16414 assert_eq!(
16416 filled_balance(&outcome).balance,
16417 Balance::new_margin(d("14702.94"), d("0"), d("4702.94"), d("0.470294"))
16418 );
16419
16420 let events = venue.apply_market(
16421 &instrument_name(),
16422 MarketObservation::new(book("48900", "48950")),
16423 time(2),
16424 );
16425
16426 let trade = events
16428 .iter()
16429 .find_map(|event| match &event.kind {
16430 AccountEventKind::Trade(trade) => Some(trade),
16431 _ => None,
16432 })
16433 .unwrap();
16434 assert_eq!(
16435 trade.fees,
16436 AssetFees::new(bnb(), d("0.003675"), Some(d("2.205")))
16437 );
16438 assert_eq!(
16440 balance_now(&venue, quote()),
16441 Balance::new_margin(d("0"), d("0"), d("4700.470294"), d("0"))
16442 );
16443 assert_eq!(balance_now(&venue, bnb()).total, d("0.996325"));
16444 }
16445
16446 #[test]
16448 fn a_cfd_fill_is_charged_without_the_fee_asset() {
16449 let mut instruments = instruments_of(cfd_instrument());
16450 let bnb_usd = spot_instrument("BNBUSD", "BNB", "usd");
16451 instruments.insert(bnb_usd.name_exchange.clone(), bnb_usd);
16452 let config = MockExecutionConfig {
16453 fee_asset: Some(FeeAssetConfig::new(bnb(), d("0.25")).unwrap()),
16454 ..config_from_balances(
16455 vec![funded(usd(), d("1000000")), funded(bnb(), d("10"))],
16456 maker_taker_fee(),
16457 )
16458 };
16459 let mut venue = SimulatedVenue::new_market_driven(&config, instruments);
16460 advance(&mut venue, time(1));
16461 assert!(
16462 venue
16463 .apply_market(
16464 &InstrumentNameExchange::new("BNBUSD"),
16465 MarketObservation::new(traded("600")),
16466 time(1)
16467 )
16468 .is_empty()
16469 );
16470 assert!(cfd_market(&mut venue, "4999", "5001", "5000").is_empty());
16471
16472 let outcome = venue.open_order(cfd_request(Side::Buy, "1", None));
16473
16474 assert_eq!(filled_trade(&outcome).fees.asset, usd());
16475 assert_eq!(balance_now(&venue, bnb()).total, d("10"));
16476 }
16477
16478 fn eth() -> AssetNameExchange {
16479 AssetNameExchange::new("ETH")
16480 }
16481
16482 fn eth_instrument_name() -> InstrumentNameExchange {
16483 InstrumentNameExchange::new("ETHUSDT")
16484 }
16485
16486 fn btc_and_eth_instruments()
16488 -> FnvHashMap<InstrumentNameExchange, Instrument<ExchangeId, AssetNameExchange>> {
16489 let mut instruments = spot_instruments();
16490 instruments.insert(
16491 eth_instrument_name(),
16492 Instrument {
16493 exchange: EXCHANGE,
16494 name_internal: InstrumentNameInternal::new("ethusdt"),
16495 name_exchange: eth_instrument_name(),
16496 underlying: Underlying {
16497 base: eth(),
16498 quote: quote(),
16499 },
16500 quote: InstrumentQuoteAsset::UnderlyingQuote,
16501 kind: InstrumentKind::Spot,
16502 spec: None,
16503 data_venue: None,
16504 },
16505 );
16506 instruments
16507 }
16508
16509 mod borrow_capacity;
16510 mod collateral_ratio;
16511 mod liquidation;
16512}