按选股表达式跳过无关盘中quote
This commit is contained in:
@@ -553,14 +553,12 @@ pub struct PlatformExprStrategy {
|
||||
prelude_numeric_constants: HashMap<String, f64>,
|
||||
compact_stock_filter_expr: String,
|
||||
stock_filter_quote_usage: StockFilterQuoteUsage,
|
||||
selection_quote_usage: StockFilterQuoteUsage,
|
||||
stock_rolling_requirements: StockRollingRequirements,
|
||||
stock_extra_factors_required: bool,
|
||||
stock_state_cache_date: RefCell<Option<NaiveDate>>,
|
||||
stock_state_cache:
|
||||
RefCell<HashMap<(NaiveDate, NaiveDate, String, Option<NaiveTime>), StockExpressionState>>,
|
||||
scheduled_quote_cache_date: RefCell<Option<NaiveDate>>,
|
||||
scheduled_quote_cache: RefCell<
|
||||
HashMap<(NaiveDate, String, NaiveDateTime), Option<crate::data::IntradayExecutionQuote>>,
|
||||
stock_state_cache: RefCell<
|
||||
HashMap<(NaiveDate, NaiveDate, String, Option<NaiveTime>, bool), StockExpressionState>,
|
||||
>,
|
||||
}
|
||||
|
||||
@@ -649,6 +647,8 @@ impl PlatformExprStrategy {
|
||||
let compact_stock_filter_expr = Self::compact_expr(&normalized_stock_filter_expr);
|
||||
let stock_filter_quote_usage =
|
||||
Self::stock_filter_quote_usage_for_expr(&normalized_stock_filter_expr);
|
||||
let selection_quote_usage =
|
||||
Self::selection_quote_usage_for_config(&config, &normalized_stock_filter_expr);
|
||||
let stock_rolling_requirements =
|
||||
Self::stock_rolling_requirements_for_config(&config, &normalized_stock_filter_expr);
|
||||
let stock_extra_factors_required = Self::stock_extra_factors_required_for_config(
|
||||
@@ -672,12 +672,11 @@ impl PlatformExprStrategy {
|
||||
prelude_numeric_constants,
|
||||
compact_stock_filter_expr,
|
||||
stock_filter_quote_usage,
|
||||
selection_quote_usage,
|
||||
stock_rolling_requirements,
|
||||
stock_extra_factors_required,
|
||||
stock_state_cache_date: RefCell::new(None),
|
||||
stock_state_cache: RefCell::new(HashMap::new()),
|
||||
scheduled_quote_cache_date: RefCell::new(None),
|
||||
scheduled_quote_cache: RefCell::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1198,22 +1197,22 @@ impl PlatformExprStrategy {
|
||||
)
|
||||
}
|
||||
|
||||
fn aiquant_scheduled_quote(
|
||||
fn aiquant_scheduled_quote<'a>(
|
||||
&self,
|
||||
ctx: &StrategyContext<'_>,
|
||||
ctx: &'a StrategyContext<'_>,
|
||||
date: NaiveDate,
|
||||
symbol: &str,
|
||||
) -> Option<crate::data::IntradayExecutionQuote> {
|
||||
) -> Option<&'a crate::data::IntradayExecutionQuote> {
|
||||
self.aiquant_scheduled_quote_at_time(ctx, date, symbol, None)
|
||||
}
|
||||
|
||||
fn aiquant_scheduled_quote_at_time(
|
||||
fn aiquant_scheduled_quote_at_time<'a>(
|
||||
&self,
|
||||
ctx: &StrategyContext<'_>,
|
||||
ctx: &'a StrategyContext<'_>,
|
||||
date: NaiveDate,
|
||||
symbol: &str,
|
||||
execution_time: Option<NaiveTime>,
|
||||
) -> Option<crate::data::IntradayExecutionQuote> {
|
||||
) -> Option<&'a crate::data::IntradayExecutionQuote> {
|
||||
if !self.config.aiquant_transaction_cost {
|
||||
return None;
|
||||
}
|
||||
@@ -1224,28 +1223,11 @@ impl PlatformExprStrategy {
|
||||
&ProjectedExecutionState::default(),
|
||||
execution_time,
|
||||
);
|
||||
{
|
||||
let mut cache_date = self.scheduled_quote_cache_date.borrow_mut();
|
||||
if *cache_date != Some(date) {
|
||||
self.scheduled_quote_cache.borrow_mut().clear();
|
||||
*cache_date = Some(date);
|
||||
}
|
||||
}
|
||||
let cache_key = (date, symbol.to_string(), start_cursor);
|
||||
if let Some(cached) = self.scheduled_quote_cache.borrow().get(&cache_key) {
|
||||
return cached.clone();
|
||||
}
|
||||
let quote = ctx
|
||||
.data
|
||||
ctx.data
|
||||
.execution_quotes_on(date, symbol)
|
||||
.iter()
|
||||
.filter(|quote| quote.timestamp <= start_cursor)
|
||||
.max_by_key(|quote| quote.timestamp)
|
||||
.cloned();
|
||||
self.scheduled_quote_cache
|
||||
.borrow_mut()
|
||||
.insert(cache_key, quote.clone());
|
||||
quote
|
||||
}
|
||||
|
||||
fn aiquant_scheduled_buy_price(
|
||||
@@ -1255,7 +1237,7 @@ impl PlatformExprStrategy {
|
||||
symbol: &str,
|
||||
) -> Option<f64> {
|
||||
self.aiquant_scheduled_quote(ctx, date, symbol)
|
||||
.and_then(|quote| self.projected_quote_raw_price("e, OrderSide::Buy))
|
||||
.and_then(|quote| self.projected_quote_raw_price(quote, OrderSide::Buy))
|
||||
}
|
||||
|
||||
fn aiquant_scheduled_sell_price_at_time(
|
||||
@@ -1266,7 +1248,7 @@ impl PlatformExprStrategy {
|
||||
execution_time: Option<NaiveTime>,
|
||||
) -> Option<f64> {
|
||||
self.aiquant_scheduled_quote_at_time(ctx, date, symbol, execution_time)
|
||||
.and_then(|quote| self.projected_quote_raw_price("e, OrderSide::Sell))
|
||||
.and_then(|quote| self.projected_quote_raw_price(quote, OrderSide::Sell))
|
||||
}
|
||||
|
||||
fn aiquant_scheduled_last_price(
|
||||
@@ -2211,7 +2193,25 @@ impl PlatformExprStrategy {
|
||||
factor_date: NaiveDate,
|
||||
symbol: &str,
|
||||
) -> Result<StockExpressionState, BacktestError> {
|
||||
self.stock_state_with_factor_date_and_time(ctx, date, factor_date, symbol, None)
|
||||
self.stock_state_with_factor_date_and_time(ctx, date, factor_date, symbol, None, true)
|
||||
}
|
||||
|
||||
fn selection_stock_state_with_factor_date(
|
||||
&self,
|
||||
ctx: &StrategyContext<'_>,
|
||||
date: NaiveDate,
|
||||
factor_date: NaiveDate,
|
||||
symbol: &str,
|
||||
) -> Result<StockExpressionState, BacktestError> {
|
||||
let use_intraday_quote = self.selection_quote_usage != StockFilterQuoteUsage::DailyOnly;
|
||||
self.stock_state_with_factor_date_and_time(
|
||||
ctx,
|
||||
date,
|
||||
factor_date,
|
||||
symbol,
|
||||
None,
|
||||
use_intraday_quote,
|
||||
)
|
||||
}
|
||||
|
||||
fn stock_decision_rolling_mean(
|
||||
@@ -2244,7 +2244,7 @@ impl PlatformExprStrategy {
|
||||
symbol: &str,
|
||||
execution_time: Option<NaiveTime>,
|
||||
) -> Result<StockExpressionState, BacktestError> {
|
||||
self.stock_state_with_factor_date_and_time(ctx, date, date, symbol, execution_time)
|
||||
self.stock_state_with_factor_date_and_time(ctx, date, date, symbol, execution_time, true)
|
||||
}
|
||||
|
||||
fn stock_is_at_upper_limit_at_time(
|
||||
@@ -2313,6 +2313,7 @@ impl PlatformExprStrategy {
|
||||
factor_date: NaiveDate,
|
||||
symbol: &str,
|
||||
execution_time: Option<NaiveTime>,
|
||||
use_intraday_quote: bool,
|
||||
) -> Result<StockExpressionState, BacktestError> {
|
||||
{
|
||||
let mut cache_date = self.stock_state_cache_date.borrow_mut();
|
||||
@@ -2321,7 +2322,13 @@ impl PlatformExprStrategy {
|
||||
*cache_date = Some(date);
|
||||
}
|
||||
}
|
||||
let cache_key = (date, factor_date, symbol.to_string(), execution_time);
|
||||
let cache_key = (
|
||||
date,
|
||||
factor_date,
|
||||
symbol.to_string(),
|
||||
execution_time,
|
||||
use_intraday_quote,
|
||||
);
|
||||
if let Some(state) = self.stock_state_cache.borrow().get(&cache_key) {
|
||||
return Ok(state.clone());
|
||||
}
|
||||
@@ -2329,8 +2336,11 @@ impl PlatformExprStrategy {
|
||||
let market = ctx.data.require_market(date, symbol)?;
|
||||
let feature_market = ctx.data.market(factor_date, symbol).unwrap_or(market);
|
||||
let intraday_same_day_factor = self.config.aiquant_transaction_cost && factor_date == date;
|
||||
let decision_quote =
|
||||
self.aiquant_scheduled_quote_at_time(ctx, date, symbol, execution_time);
|
||||
let decision_quote = if use_intraday_quote {
|
||||
self.aiquant_scheduled_quote_at_time(ctx, date, symbol, execution_time)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let factor = ctx.data.require_factor(factor_date, symbol)?;
|
||||
let candidate = ctx.data.require_candidate(date, symbol)?;
|
||||
let instrument = ctx.data.instrument(symbol);
|
||||
@@ -2451,12 +2461,11 @@ impl PlatformExprStrategy {
|
||||
low: expression_low,
|
||||
close: expression_close,
|
||||
last: decision_quote
|
||||
.as_ref()
|
||||
.and_then(|quote| {
|
||||
(quote.last_price.is_finite() && quote.last_price > 0.0)
|
||||
.then_some(quote.last_price)
|
||||
})
|
||||
.or_else(|| decision_quote.as_ref().and_then(|quote| quote.buy_price()))
|
||||
.or_else(|| decision_quote.and_then(|quote| quote.buy_price()))
|
||||
.unwrap_or(market.last_price),
|
||||
prev_close: feature_market.prev_close,
|
||||
amount,
|
||||
@@ -6208,8 +6217,12 @@ impl PlatformExprStrategy {
|
||||
let mut diagnostics = Vec::new();
|
||||
let mut candidates = Vec::new();
|
||||
for candidate in universe {
|
||||
let stock =
|
||||
self.stock_state_with_factor_date(ctx, date, stock_factor_date, &candidate.symbol)?;
|
||||
let stock = self.selection_stock_state_with_factor_date(
|
||||
ctx,
|
||||
date,
|
||||
stock_factor_date,
|
||||
&candidate.symbol,
|
||||
)?;
|
||||
let field_value = self.selection_field_value(&candidate, &stock);
|
||||
if !field_value.is_finite() {
|
||||
if diagnostics.len() < 12 {
|
||||
@@ -6296,6 +6309,34 @@ impl PlatformExprStrategy {
|
||||
usage
|
||||
}
|
||||
|
||||
fn merge_quote_usage(
|
||||
lhs: StockFilterQuoteUsage,
|
||||
rhs: StockFilterQuoteUsage,
|
||||
) -> StockFilterQuoteUsage {
|
||||
match (lhs, rhs) {
|
||||
(StockFilterQuoteUsage::IntradayQuote, _)
|
||||
| (_, StockFilterQuoteUsage::IntradayQuote) => StockFilterQuoteUsage::IntradayQuote,
|
||||
(StockFilterQuoteUsage::LimitStateOnly, _)
|
||||
| (_, StockFilterQuoteUsage::LimitStateOnly) => StockFilterQuoteUsage::LimitStateOnly,
|
||||
_ => StockFilterQuoteUsage::DailyOnly,
|
||||
}
|
||||
}
|
||||
|
||||
fn selection_quote_usage_for_config(
|
||||
config: &PlatformExprStrategyConfig,
|
||||
normalized_stock_filter_expr: &str,
|
||||
) -> StockFilterQuoteUsage {
|
||||
[
|
||||
normalized_stock_filter_expr,
|
||||
config.market_cap_field.as_str(),
|
||||
config.rank_by.as_str(),
|
||||
config.rank_expr.as_str(),
|
||||
]
|
||||
.into_iter()
|
||||
.map(Self::stock_filter_quote_usage_for_expr)
|
||||
.fold(StockFilterQuoteUsage::DailyOnly, Self::merge_quote_usage)
|
||||
}
|
||||
|
||||
fn stock_rolling_requirements_for_config(
|
||||
config: &PlatformExprStrategyConfig,
|
||||
normalized_stock_filter_expr: &str,
|
||||
@@ -6571,10 +6612,15 @@ impl PlatformExprStrategy {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn stock_filter_uses_intraday_quote_fields(&self) -> bool {
|
||||
self.stock_filter_quote_usage() != StockFilterQuoteUsage::DailyOnly
|
||||
}
|
||||
|
||||
fn selection_uses_intraday_quote_fields(&self) -> bool {
|
||||
self.selection_quote_usage != StockFilterQuoteUsage::DailyOnly
|
||||
}
|
||||
|
||||
fn quote_plan_candidate_limit(&self, selection_limit: usize) -> usize {
|
||||
selection_limit
|
||||
.saturating_mul(3)
|
||||
@@ -6637,8 +6683,12 @@ impl PlatformExprStrategy {
|
||||
let quote_usage = self.stock_filter_quote_usage();
|
||||
let quote_candidate_limit = self.quote_plan_candidate_limit(selection_limit);
|
||||
for candidate in universe {
|
||||
let stock =
|
||||
self.stock_state_with_factor_date(ctx, date, stock_factor_date, &candidate.symbol)?;
|
||||
let stock = self.selection_stock_state_with_factor_date(
|
||||
ctx,
|
||||
date,
|
||||
stock_factor_date,
|
||||
&candidate.symbol,
|
||||
)?;
|
||||
let field_value = self.selection_field_value(&candidate, &stock);
|
||||
if !field_value.is_finite() {
|
||||
if diagnostics.len() < 12 {
|
||||
@@ -6748,7 +6798,7 @@ impl PlatformExprStrategy {
|
||||
|
||||
let day = self.day_state(ctx, ctx.decision_date)?;
|
||||
let (selection_date, universe_factor_date, stock_factor_date) = self.selection_dates(ctx);
|
||||
let requires_intraday_selection_quotes = self.stock_filter_uses_intraday_quote_fields();
|
||||
let requires_intraday_selection_quotes = self.selection_uses_intraday_quote_fields();
|
||||
let (band_low, band_high) = self.market_cap_band(ctx, &day)?;
|
||||
let selection_limit = self
|
||||
.selection_limit(ctx, &day)?
|
||||
@@ -20898,6 +20948,29 @@ mod tests {
|
||||
strategy.stock_filter_uses_intraday_quote_fields(),
|
||||
"actual tick/quote fields still require intraday selection quotes"
|
||||
);
|
||||
|
||||
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||
cfg.stock_filter_expr = "close > 1.0".to_string();
|
||||
cfg.rank_by = "last_price".to_string();
|
||||
let strategy = PlatformExprStrategy::new(cfg);
|
||||
assert_eq!(
|
||||
strategy.stock_filter_quote_usage(),
|
||||
StockFilterQuoteUsage::DailyOnly
|
||||
);
|
||||
assert!(
|
||||
strategy.selection_uses_intraday_quote_fields(),
|
||||
"selection ranking by tick fields must still request intraday selection quotes"
|
||||
);
|
||||
|
||||
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||
cfg.stock_filter_expr =
|
||||
"rolling_mean(\"close\", 5) > rolling_mean(\"close\", 10)".to_string();
|
||||
cfg.rank_by = "market_cap".to_string();
|
||||
let strategy = PlatformExprStrategy::new(cfg);
|
||||
assert!(
|
||||
!strategy.selection_uses_intraday_quote_fields(),
|
||||
"pure daily rolling selection must not prefetch intraday quotes"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user