修正点时刻执行报价口径
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user