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