修正点时刻回测使用最新tick
This commit is contained in:
+122
-35
@@ -367,15 +367,16 @@ where
|
|||||||
.get()
|
.get()
|
||||||
.or(self.intraday_execution_start_time)
|
.or(self.intraday_execution_start_time)
|
||||||
.map(|start_time| date.and_time(start_time));
|
.map(|start_time| date.and_time(start_time));
|
||||||
if let Some(price) = data
|
if let Some(price) = self
|
||||||
.execution_quotes_on(date, symbol)
|
.latest_known_quote_at_or_before(
|
||||||
.iter()
|
data.execution_quotes_on(date, symbol),
|
||||||
.filter(|quote| {
|
start_cursor,
|
||||||
start_cursor
|
snapshot,
|
||||||
.map(|cursor| quote.timestamp >= cursor)
|
OrderSide::Buy,
|
||||||
.unwrap_or(true)
|
MatchingType::NextTickLast,
|
||||||
})
|
false,
|
||||||
.find_map(|quote| {
|
)
|
||||||
|
.and_then(|quote| {
|
||||||
(quote.last_price.is_finite() && quote.last_price > 0.0)
|
(quote.last_price.is_finite() && quote.last_price > 0.0)
|
||||||
.then_some(quote.last_price)
|
.then_some(quote.last_price)
|
||||||
})
|
})
|
||||||
@@ -399,22 +400,17 @@ where
|
|||||||
.get()
|
.get()
|
||||||
.or(self.intraday_execution_start_time)
|
.or(self.intraday_execution_start_time)
|
||||||
.map(|start_time| date.and_time(start_time));
|
.map(|start_time| date.and_time(start_time));
|
||||||
data.execution_quotes_on(date, symbol)
|
let matching_type = self.matching_type_for_algo_request(None);
|
||||||
.iter()
|
self.latest_known_quote_at_or_before(
|
||||||
.filter(|quote| {
|
data.execution_quotes_on(date, symbol),
|
||||||
start_cursor
|
start_cursor,
|
||||||
.map(|cursor| quote.timestamp >= cursor)
|
snapshot,
|
||||||
.unwrap_or(true)
|
side,
|
||||||
})
|
matching_type,
|
||||||
.find_map(|quote| {
|
false,
|
||||||
self.select_quote_reference_price(
|
)
|
||||||
snapshot,
|
.and_then(|quote| self.select_quote_reference_price(snapshot, quote, side, matching_type))
|
||||||
quote,
|
.unwrap_or_else(|| self.sizing_price(snapshot))
|
||||||
side,
|
|
||||||
self.matching_type_for_algo_request(None),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| self.sizing_price(snapshot))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot_execution_price(
|
fn snapshot_execution_price(
|
||||||
@@ -1143,6 +1139,36 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn latest_known_quote_at_or_before<'a>(
|
||||||
|
&self,
|
||||||
|
quotes: &'a [IntradayExecutionQuote],
|
||||||
|
cursor: Option<NaiveDateTime>,
|
||||||
|
snapshot: &crate::data::DailyMarketSnapshot,
|
||||||
|
side: OrderSide,
|
||||||
|
matching_type: MatchingType,
|
||||||
|
require_executable_liquidity: bool,
|
||||||
|
) -> Option<&'a IntradayExecutionQuote> {
|
||||||
|
let Some(cursor) = cursor else {
|
||||||
|
return quotes.iter().find(|quote| {
|
||||||
|
self.select_quote_reference_price(snapshot, quote, side, matching_type)
|
||||||
|
.is_some()
|
||||||
|
&& (!require_executable_liquidity
|
||||||
|
|| self.quote_has_executable_liquidity(quote, side, matching_type))
|
||||||
|
});
|
||||||
|
};
|
||||||
|
quotes
|
||||||
|
.iter()
|
||||||
|
.filter(|quote| {
|
||||||
|
quote.timestamp <= cursor
|
||||||
|
&& self
|
||||||
|
.select_quote_reference_price(snapshot, quote, side, matching_type)
|
||||||
|
.is_some()
|
||||||
|
&& (!require_executable_liquidity
|
||||||
|
|| self.quote_has_executable_liquidity(quote, side, matching_type))
|
||||||
|
})
|
||||||
|
.max_by_key(|quote| quote.timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
fn process_limit_shares(
|
fn process_limit_shares(
|
||||||
&self,
|
&self,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
@@ -4654,15 +4680,31 @@ where
|
|||||||
let quote_quantity_limited =
|
let quote_quantity_limited =
|
||||||
self.quote_quantity_limited_for_window(matching_type, start_cursor, end_cursor);
|
self.quote_quantity_limited_for_window(matching_type, start_cursor, end_cursor);
|
||||||
let lot = round_lot.max(1);
|
let lot = round_lot.max(1);
|
||||||
let eligible_quotes: Vec<&IntradayExecutionQuote> = quotes
|
let use_decision_time_quote = matching_type == MatchingType::NextTickLast
|
||||||
.iter()
|
&& start_cursor.is_some()
|
||||||
.filter(|quote| {
|
&& end_cursor.is_none_or(|end| start_cursor.is_some_and(|start| end <= start));
|
||||||
!start_cursor.is_some_and(|cursor| quote.timestamp < cursor)
|
let eligible_quotes: Vec<&IntradayExecutionQuote> = if use_decision_time_quote {
|
||||||
&& !end_cursor.is_some_and(|cursor| quote.timestamp > cursor)
|
self.latest_known_quote_at_or_before(
|
||||||
&& (!quote_quantity_limited
|
quotes,
|
||||||
|| self.quote_has_executable_liquidity(quote, side, matching_type))
|
start_cursor,
|
||||||
})
|
snapshot,
|
||||||
.collect();
|
side,
|
||||||
|
matching_type,
|
||||||
|
quote_quantity_limited,
|
||||||
|
)
|
||||||
|
.into_iter()
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
quotes
|
||||||
|
.iter()
|
||||||
|
.filter(|quote| {
|
||||||
|
!start_cursor.is_some_and(|cursor| quote.timestamp < cursor)
|
||||||
|
&& !end_cursor.is_some_and(|cursor| quote.timestamp > cursor)
|
||||||
|
&& (!quote_quantity_limited
|
||||||
|
|| self.quote_has_executable_liquidity(quote, side, matching_type))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
let mut filled_qty = 0_u32;
|
let mut filled_qty = 0_u32;
|
||||||
let mut gross_amount = 0.0_f64;
|
let mut gross_amount = 0.0_f64;
|
||||||
let mut last_timestamp = None;
|
let mut last_timestamp = None;
|
||||||
@@ -5077,7 +5119,7 @@ mod tests {
|
|||||||
snapshot.last_price = 10.0;
|
snapshot.last_price = 10.0;
|
||||||
snapshot.close = 10.0;
|
snapshot.close = 10.0;
|
||||||
let mut quote = limit_test_quote(11.0, 10.99, 11.01);
|
let mut quote = limit_test_quote(11.0, 10.99, 11.01);
|
||||||
quote.timestamp = date.and_hms_opt(9, 34, 0).expect("valid timestamp");
|
quote.timestamp = date.and_hms_opt(9, 32, 58).expect("valid timestamp");
|
||||||
let data = DataSet::from_components_with_actions_and_quotes(
|
let data = DataSet::from_components_with_actions_and_quotes(
|
||||||
vec![limit_test_instrument()],
|
vec![limit_test_instrument()],
|
||||||
vec![snapshot.clone()],
|
vec![snapshot.clone()],
|
||||||
@@ -5100,6 +5142,51 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn next_tick_last_execution_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_volume_limit(false)
|
||||||
|
.with_liquidity_limit(false)
|
||||||
|
.with_inactive_limit(false);
|
||||||
|
let snapshot = limit_test_snapshot();
|
||||||
|
let mut quote = limit_test_quote(10.8, 10.79, 10.81);
|
||||||
|
quote.timestamp = date.and_hms_opt(9, 32, 58).expect("valid timestamp");
|
||||||
|
let quote_timestamp = quote.timestamp;
|
||||||
|
let decision_time = date.and_hms_opt(9, 33, 0).expect("valid timestamp");
|
||||||
|
|
||||||
|
let fill = broker
|
||||||
|
.select_execution_fill(
|
||||||
|
&snapshot,
|
||||||
|
&[quote],
|
||||||
|
OrderSide::Sell,
|
||||||
|
MatchingType::NextTickLast,
|
||||||
|
Some(decision_time),
|
||||||
|
Some(decision_time),
|
||||||
|
200,
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("fill from latest known quote before decision time");
|
||||||
|
|
||||||
|
assert_eq!(fill.quantity, 200);
|
||||||
|
assert_eq!(fill.legs.len(), 1);
|
||||||
|
assert_eq!(fill.legs[0].price, 10.8);
|
||||||
|
assert_eq!(
|
||||||
|
fill.next_cursor,
|
||||||
|
quote_timestamp + chrono::Duration::seconds(1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[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)
|
||||||
|
|||||||
Reference in New Issue
Block a user