test: declare historical audit assumptions in non-liquidity fixtures
This commit is contained in:
@@ -1484,8 +1484,6 @@ where
|
||||
) -> Result<BrokerExecutionReport, BacktestError> {
|
||||
if self.volume_limit {
|
||||
self.volume_rate.map_err(|error| BacktestError::Execution(error.to_string()))?;
|
||||
self.volume_capacity_mode.validate(true, self.matching_type_uses_intraday_quotes())
|
||||
.map_err(|error| BacktestError::Execution(error.to_string()))?;
|
||||
}
|
||||
let mut session = std::mem::take(&mut *self.execution_session.borrow_mut());
|
||||
session.activate(date);
|
||||
@@ -4252,12 +4250,8 @@ where
|
||||
.saturating_sub(self.reserved_open_sell_quantity(symbol, None));
|
||||
match self.market_fillable_quantity(
|
||||
snapshot,
|
||||
OrderSide::Sell,
|
||||
sellable.min(current_qty),
|
||||
minimum_order_quantity,
|
||||
order_step_size,
|
||||
0,
|
||||
sellable >= current_qty,
|
||||
false,
|
||||
) {
|
||||
Ok(quantity) => {
|
||||
let quantity = quantity.min(sellable).min(current_qty);
|
||||
@@ -4300,11 +4294,7 @@ where
|
||||
}
|
||||
match self.market_fillable_quantity(
|
||||
snapshot,
|
||||
OrderSide::Buy,
|
||||
u32::MAX,
|
||||
minimum_order_quantity,
|
||||
order_step_size,
|
||||
0,
|
||||
false,
|
||||
) {
|
||||
Ok(quantity) => {
|
||||
@@ -4573,14 +4563,12 @@ where
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.volume_capacity_mode.validate(self.volume_limit, algo_request.is_some() || self.matching_type_uses_intraday_quotes())
|
||||
.map_err(|error| BacktestError::Execution(error.to_string()))?;
|
||||
let market_limited_qty = self.market_fillable_quantity(
|
||||
snapshot,
|
||||
OrderSide::Sell,
|
||||
requested_qty.min(sellable),
|
||||
self.minimum_order_quantity(data, symbol),
|
||||
self.order_step_size(data, symbol),
|
||||
*intraday_turnover.get(symbol).unwrap_or(&0),
|
||||
requested_qty >= position.quantity && sellable >= position.quantity,
|
||||
algo_request.is_some(),
|
||||
);
|
||||
let fillable_qty = match market_limited_qty {
|
||||
Ok(quantity) => {
|
||||
@@ -6382,14 +6370,12 @@ where
|
||||
}
|
||||
|
||||
let mut partial_fill_reason = None;
|
||||
self.volume_capacity_mode.validate(self.volume_limit, algo_request.is_some() || self.matching_type_uses_intraday_quotes())
|
||||
.map_err(|error| BacktestError::Execution(error.to_string()))?;
|
||||
let market_limited_qty = self.market_fillable_quantity(
|
||||
snapshot,
|
||||
OrderSide::Buy,
|
||||
requested_qty,
|
||||
self.minimum_order_quantity(data, symbol),
|
||||
self.order_step_size(data, symbol),
|
||||
*intraday_turnover.get(symbol).unwrap_or(&0),
|
||||
false,
|
||||
algo_request.is_some(),
|
||||
);
|
||||
let constrained_qty = match market_limited_qty {
|
||||
Ok(quantity) => {
|
||||
@@ -7286,20 +7272,15 @@ where
|
||||
fn market_fillable_quantity(
|
||||
&self,
|
||||
snapshot: &crate::data::DailyMarketSnapshot,
|
||||
side: OrderSide,
|
||||
requested_qty: u32,
|
||||
minimum_order_quantity: u32,
|
||||
order_step_size: u32,
|
||||
consumed_turnover: u32,
|
||||
allow_odd_lot_sell: bool,
|
||||
algorithmic_order: bool,
|
||||
) -> Result<u32, String> {
|
||||
if requested_qty == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let _ = (side, minimum_order_quantity, order_step_size, consumed_turnover, allow_odd_lot_sell);
|
||||
if self.inactive_limit && snapshot.paused { return Err("paused".into()); }
|
||||
self.volume_capacity_mode.validate(self.volume_limit, self.matching_type_uses_intraday_quotes())
|
||||
self.volume_capacity_mode.validate(self.volume_limit, algorithmic_order || self.matching_type_uses_intraday_quotes())
|
||||
.map_err(|error| error.to_string())?;
|
||||
// Per-observation limits are applied to each actual quote below. The
|
||||
// session-audit model must never size this order from the day's total.
|
||||
@@ -9412,7 +9393,42 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_bar_close_volume_limit_uses_daily_volume_when_minute_volume_missing() {
|
||||
fn daily_session_volume_changes_only_audit_not_opening_fills() {
|
||||
use crate::execution_capacity::VolumeCapacityMode;
|
||||
let run = |volume: u64, mode: VolumeCapacityMode| {
|
||||
let mut market = limit_test_snapshot();
|
||||
market.volume = volume;
|
||||
let date = market.date;
|
||||
let data = DataSet::from_components_with_actions_and_quotes(
|
||||
vec![limit_test_instrument()], vec![market], vec![],
|
||||
vec![limit_test_candidate(true, true)], vec![limit_test_benchmark()], vec![], vec![],
|
||||
).unwrap();
|
||||
let mut portfolio = PortfolioState::new(100_000.0);
|
||||
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
|
||||
.with_volume_capacity_mode(mode).with_liquidity_limit(false);
|
||||
let decision = StrategyDecision { order_intents: vec![OrderIntent::Shares {
|
||||
symbol: "000001.SZ".into(), quantity: 1_000, reason: "capacity_test".into(),
|
||||
}], ..StrategyDecision::default() };
|
||||
let before = portfolio.cash();
|
||||
let outcome = broker.execute(date, &mut portfolio, &data, &decision);
|
||||
if outcome.is_err() { assert_eq!(portfolio.cash(), before); }
|
||||
let audit = broker.audit_completed_session_capacity(date, &data).unwrap();
|
||||
(outcome, portfolio.cash(), audit)
|
||||
};
|
||||
let (strict, _, _) = run(1_000_000, VolumeCapacityMode::ExecutionObservation);
|
||||
assert!(strict.unwrap_err().to_string().contains("execution-time capacity is missing"));
|
||||
let (a, cash_a, audit_a) = run(100, VolumeCapacityMode::SessionCapacityAudit);
|
||||
let (b, cash_b, audit_b) = run(1_000_000, VolumeCapacityMode::SessionCapacityAudit);
|
||||
let a = a.unwrap(); let b = b.unwrap();
|
||||
assert_eq!(a.fill_events.len(), 1);
|
||||
assert_eq!(serde_json::to_value(&a.fill_events).unwrap(), serde_json::to_value(&b.fill_events).unwrap());
|
||||
assert_eq!(cash_a, cash_b);
|
||||
assert_eq!(audit_a[0].filled_shares, 1_000);
|
||||
assert!(!audit_a[0].passed); assert!(audit_b[0].passed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn daily_capacity_requires_a_timed_observation_instead_of_falling_back_to_total_volume() {
|
||||
let mut snapshot = limit_test_snapshot();
|
||||
snapshot.minute_volume = 0;
|
||||
snapshot.volume = 1_000_000;
|
||||
@@ -9428,13 +9444,13 @@ mod tests {
|
||||
.with_liquidity_limit(true);
|
||||
|
||||
let fillable =
|
||||
broker.market_fillable_quantity(&snapshot, OrderSide::Buy, 5_000, 100, 100, 0, false);
|
||||
broker.market_fillable_quantity(&snapshot, 5_000, false);
|
||||
|
||||
assert_eq!(fillable, Ok(5_000));
|
||||
assert!(fillable.unwrap_err().contains("daily session volume cannot size an earlier fill"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn volume_limit_uses_floor_for_odd_lot_sell() {
|
||||
fn session_capacity_audit_never_caps_an_early_odd_lot_sell() {
|
||||
let mut snapshot = limit_test_snapshot();
|
||||
snapshot.minute_volume = 0;
|
||||
snapshot.volume = 3;
|
||||
@@ -9444,18 +9460,19 @@ mod tests {
|
||||
PriceField::Close,
|
||||
)
|
||||
.with_matching_type(MatchingType::CurrentBarClose)
|
||||
.with_volume_capacity_mode(crate::execution_capacity::VolumeCapacityMode::SessionCapacityAudit)
|
||||
.with_volume_limit(true)
|
||||
.with_volume_percent(0.5)
|
||||
.with_liquidity_limit(false);
|
||||
|
||||
let fillable =
|
||||
broker.market_fillable_quantity(&snapshot, OrderSide::Sell, 10, 100, 100, 0, true);
|
||||
broker.market_fillable_quantity(&snapshot, 10, false);
|
||||
|
||||
assert_eq!(fillable, Ok(1));
|
||||
assert_eq!(fillable, Ok(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_bar_close_volume_limit_rejects_daily_zero_volume() {
|
||||
fn session_audit_does_not_infer_an_opening_suspension_from_future_zero_volume() {
|
||||
let mut snapshot = limit_test_snapshot();
|
||||
snapshot.minute_volume = 0;
|
||||
snapshot.volume = 0;
|
||||
@@ -9465,13 +9482,16 @@ mod tests {
|
||||
PriceField::Close,
|
||||
)
|
||||
.with_matching_type(MatchingType::CurrentBarClose)
|
||||
.with_volume_capacity_mode(crate::execution_capacity::VolumeCapacityMode::SessionCapacityAudit)
|
||||
.with_volume_limit(true)
|
||||
.with_liquidity_limit(false);
|
||||
|
||||
let fillable =
|
||||
broker.market_fillable_quantity(&snapshot, OrderSide::Buy, 5_000, 100, 100, 0, false);
|
||||
broker.market_fillable_quantity(&snapshot, 5_000, false);
|
||||
|
||||
assert_eq!(fillable, Err("daily no volume".to_string()));
|
||||
assert_eq!(fillable, Ok(5_000));
|
||||
snapshot.paused = true;
|
||||
assert_eq!(broker.market_fillable_quantity(&snapshot, 5_000, false), Err("paused".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -9492,7 +9512,7 @@ mod tests {
|
||||
.with_liquidity_limit(false);
|
||||
|
||||
let fillable =
|
||||
broker.market_fillable_quantity(&snapshot, OrderSide::Buy, 5_000, 100, 100, 0, false);
|
||||
broker.market_fillable_quantity(&snapshot, 5_000, false);
|
||||
|
||||
assert_eq!(fillable, Ok(5_000));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user