修正点时刻执行报价口径
This commit is contained in:
@@ -4632,6 +4632,7 @@ where
|
||||
quotes,
|
||||
start_cursor,
|
||||
end_cursor,
|
||||
matching_type == MatchingType::NextTickLast && start_cursor.is_some(),
|
||||
)),
|
||||
});
|
||||
}
|
||||
@@ -4644,7 +4645,16 @@ where
|
||||
quotes: &[IntradayExecutionQuote],
|
||||
start_cursor: Option<NaiveDateTime>,
|
||||
end_cursor: Option<NaiveDateTime>,
|
||||
use_decision_time_quote: bool,
|
||||
) -> &'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| {
|
||||
!start_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)]
|
||||
mod tests {
|
||||
use super::{BrokerSimulator, MatchingType};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::{BrokerExecutionReport, BrokerSimulator, MatchingType, SlippageModel};
|
||||
use crate::cost::ChinaAShareCostModel;
|
||||
use crate::data::{
|
||||
BenchmarkSnapshot, CandidateEligibility, DailyMarketSnapshot, DataSet,
|
||||
@@ -4995,6 +5007,7 @@ mod tests {
|
||||
};
|
||||
use crate::events::OrderSide;
|
||||
use crate::instrument::Instrument;
|
||||
use crate::portfolio::PortfolioState;
|
||||
use crate::rules::ChinaEquityRuleHooks;
|
||||
|
||||
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]
|
||||
fn instantaneous_twap_without_limits_does_not_cap_quote_quantity() {
|
||||
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
|
||||
|
||||
@@ -3197,6 +3197,14 @@ fn has_execution_quote_in_window(
|
||||
) -> bool {
|
||||
let start_cursor = start_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| {
|
||||
!start_cursor.is_some_and(|cursor| quote.timestamp < cursor)
|
||||
&& !end_cursor.is_some_and(|cursor| quote.timestamp > cursor)
|
||||
|
||||
Reference in New Issue
Block a user