修正点时刻执行报价口径

This commit is contained in:
boris
2026-06-13 21:55:08 +08:00
parent 4f5e3f7162
commit 9512a5dd2f
2 changed files with 89 additions and 1 deletions
+81 -1
View File
@@ -4632,6 +4632,7 @@ where
quotes, quotes,
start_cursor, start_cursor,
end_cursor, end_cursor,
matching_type == MatchingType::NextTickLast && start_cursor.is_some(),
)), )),
}); });
} }
@@ -4644,7 +4645,16 @@ where
quotes: &[IntradayExecutionQuote], quotes: &[IntradayExecutionQuote],
start_cursor: Option<NaiveDateTime>, start_cursor: Option<NaiveDateTime>,
end_cursor: Option<NaiveDateTime>, end_cursor: Option<NaiveDateTime>,
use_decision_time_quote: bool,
) -> &'static str { ) -> &'static str {
if use_decision_time_quote {
let saw_quote_at_or_before_start = start_cursor
.is_some_and(|cursor| quotes.iter().any(|quote| quote.timestamp <= cursor));
if saw_quote_at_or_before_start {
return "intraday quote liquidity exhausted";
}
return "no execution quotes at or before start";
}
let saw_quote_in_window = quotes.iter().any(|quote| { let saw_quote_in_window = quotes.iter().any(|quote| {
!start_cursor.is_some_and(|cursor| quote.timestamp < cursor) !start_cursor.is_some_and(|cursor| quote.timestamp < cursor)
&& !end_cursor.is_some_and(|cursor| quote.timestamp > cursor) && !end_cursor.is_some_and(|cursor| quote.timestamp > cursor)
@@ -4987,7 +4997,9 @@ fn sell_reason(decision: &StrategyDecision, symbol: &str) -> &'static str {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{BrokerSimulator, MatchingType}; use std::collections::BTreeMap;
use super::{BrokerExecutionReport, BrokerSimulator, MatchingType, SlippageModel};
use crate::cost::ChinaAShareCostModel; use crate::cost::ChinaAShareCostModel;
use crate::data::{ use crate::data::{
BenchmarkSnapshot, CandidateEligibility, DailyMarketSnapshot, DataSet, BenchmarkSnapshot, CandidateEligibility, DailyMarketSnapshot, DataSet,
@@ -4995,6 +5007,7 @@ mod tests {
}; };
use crate::events::OrderSide; use crate::events::OrderSide;
use crate::instrument::Instrument; use crate::instrument::Instrument;
use crate::portfolio::PortfolioState;
use crate::rules::ChinaEquityRuleHooks; use crate::rules::ChinaEquityRuleHooks;
fn limit_test_snapshot() -> DailyMarketSnapshot { fn limit_test_snapshot() -> DailyMarketSnapshot {
@@ -5186,6 +5199,73 @@ mod tests {
); );
} }
#[test]
fn value_buy_process_uses_latest_quote_before_decision_time() {
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
let broker = BrokerSimulator::new_with_execution_price(
ChinaAShareCostModel::default(),
ChinaEquityRuleHooks,
PriceField::Last,
)
.with_intraday_execution_start_time(date.and_hms_opt(9, 33, 0).unwrap().time())
.with_slippage_model(SlippageModel::PriceRatio(0.002))
.with_strict_value_budget(true)
.with_volume_limit(false)
.with_liquidity_limit(false)
.with_inactive_limit(false);
let mut snapshot = limit_test_snapshot();
snapshot.day_open = 8.70;
snapshot.open = 8.70;
snapshot.high = 8.95;
snapshot.low = 8.60;
snapshot.last_price = 8.94;
snapshot.bid1 = 8.93;
snapshot.ask1 = 8.94;
snapshot.close = 8.94;
snapshot.upper_limit = 9.72;
snapshot.lower_limit = 7.96;
let mut quote = limit_test_quote(8.69, 8.69, 8.70);
quote.timestamp = date.and_hms_opt(9, 32, 55).expect("valid timestamp");
let data = DataSet::from_components_with_actions_and_quotes(
vec![limit_test_instrument()],
vec![snapshot],
Vec::new(),
vec![limit_test_candidate(true, true)],
vec![limit_test_benchmark()],
Vec::new(),
vec![quote],
)
.expect("valid dataset");
let mut portfolio = PortfolioState::new(10_000_000.0);
let mut report = BrokerExecutionReport::default();
broker
.process_value(
date,
&mut portfolio,
&data,
"000001.SZ",
125_000.0,
"periodic_rebalance_buy",
&mut BTreeMap::new(),
&mut BTreeMap::new(),
&mut None,
&mut BTreeMap::new(),
&mut report,
)
.expect("value buy processed");
let position = portfolio.position("000001.SZ").unwrap_or_else(|| {
panic!(
"position created from latest known quote; events={:?}",
report.order_events
)
});
assert_eq!(position.quantity, 14_300);
assert_eq!(report.order_events.len(), 1);
assert_eq!(report.order_events[0].filled_quantity, 14_300);
}
#[test] #[test]
fn instantaneous_twap_without_limits_does_not_cap_quote_quantity() { fn instantaneous_twap_without_limits_does_not_cap_quote_quantity() {
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks) let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
+8
View File
@@ -3197,6 +3197,14 @@ fn has_execution_quote_in_window(
) -> bool { ) -> bool {
let start_cursor = start_time.map(|time| date.and_time(time)); let start_cursor = start_time.map(|time| date.and_time(time));
let end_cursor = end_time.map(|time| date.and_time(time)); let end_cursor = end_time.map(|time| date.and_time(time));
if let Some(cursor) = start_cursor
&& end_cursor.is_none()
{
return data
.execution_quotes_on(date, symbol)
.iter()
.any(|quote| quote.timestamp <= cursor);
}
data.execution_quotes_on(date, symbol).iter().any(|quote| { data.execution_quotes_on(date, symbol).iter().any(|quote| {
!start_cursor.is_some_and(|cursor| quote.timestamp < cursor) !start_cursor.is_some_and(|cursor| quote.timestamp < cursor)
&& !end_cursor.is_some_and(|cursor| quote.timestamp > cursor) && !end_cursor.is_some_and(|cursor| quote.timestamp > cursor)