恢复Source Lake滚动因子运行模式
This commit is contained in:
@@ -9,8 +9,8 @@ use rhai::{AST, Dynamic, Engine, Map, Scope};
|
||||
use crate::broker::{MatchingType, RebalanceCashMode, SlippageModel};
|
||||
use crate::cost::ChinaAShareCostModel;
|
||||
use crate::data::{
|
||||
DailyMarketSnapshot, EligibleUniverseSnapshot, PriceField, decision_free_float_cap_bn,
|
||||
decision_market_cap_bn,
|
||||
DailyMarketSnapshot, EligibleUniverseSnapshot, NumericFactorMap, PriceField,
|
||||
decision_free_float_cap_bn, decision_market_cap_bn,
|
||||
};
|
||||
use crate::engine::BacktestError;
|
||||
use crate::events::OrderSide;
|
||||
@@ -424,6 +424,11 @@ pub struct PlatformExprStrategyConfig {
|
||||
pub matching_type: MatchingType,
|
||||
pub quote_quantity_limit: bool,
|
||||
pub current_day_precomputed_factors: bool,
|
||||
/// Use audited Strategy Factory Source Lake rolling fields when the
|
||||
/// runtime intentionally loads only the latest decision rows. This is
|
||||
/// opt-in; ordinary backtests always recompute rolling values from the
|
||||
/// canonical market series.
|
||||
pub prefer_precomputed_rolling_factors: bool,
|
||||
pub intraday_execution_time: Option<NaiveTime>,
|
||||
pub delayed_limit_open_exit_enabled: bool,
|
||||
pub delayed_limit_open_exit_time: Option<NaiveTime>,
|
||||
@@ -495,6 +500,7 @@ impl PlatformExprStrategyConfig {
|
||||
matching_type: MatchingType::CurrentBarClose,
|
||||
quote_quantity_limit: true,
|
||||
current_day_precomputed_factors: false,
|
||||
prefer_precomputed_rolling_factors: false,
|
||||
intraday_execution_time: None,
|
||||
delayed_limit_open_exit_enabled: false,
|
||||
delayed_limit_open_exit_time: None,
|
||||
@@ -802,6 +808,42 @@ fn framework_stock_rolling_factor_requirement(key: &str) -> Option<(&'static str
|
||||
.map(|window| (field, window))
|
||||
}
|
||||
|
||||
fn precomputed_stock_rolling_mean<F>(get: F, field: &str, lookback: usize) -> Option<f64>
|
||||
where
|
||||
F: Fn(&str) -> Option<f64>,
|
||||
{
|
||||
if lookback == 0 {
|
||||
return None;
|
||||
}
|
||||
let value_for = |key: String| get(&key).filter(|value| value.is_finite());
|
||||
match field.trim().to_ascii_lowercase().as_str() {
|
||||
"close" | "prev_close" | "stock_close" | "price" => {
|
||||
value_for(format!("ma{lookback}_prev_close"))
|
||||
.or_else(|| value_for(format!("ma{lookback}")))
|
||||
}
|
||||
"volume" | "stock_volume" => value_for(format!("avg_volume{lookback}"))
|
||||
.or_else(|| value_for(format!("vma{lookback}"))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn precomputed_stock_current_rolling_mean<F>(get: F, field: &str, lookback: usize) -> Option<f64>
|
||||
where
|
||||
F: Fn(&str) -> Option<f64>,
|
||||
{
|
||||
if lookback == 0 {
|
||||
return None;
|
||||
}
|
||||
let value_for = |key: String| get(&key).filter(|value| value.is_finite());
|
||||
match field.trim().to_ascii_lowercase().as_str() {
|
||||
"close" | "prev_close" | "stock_close" | "price" => {
|
||||
value_for(format!("ma{lookback}_current_back_adjusted_close"))
|
||||
}
|
||||
"volume" | "stock_volume" => value_for(format!("avg_volume{lookback}_current")),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
struct SelectiveExpressionScope<'a> {
|
||||
inner: Scope<'static>,
|
||||
required_identifiers: &'a AHashSet<String>,
|
||||
@@ -3775,13 +3817,24 @@ impl PlatformExprStrategy {
|
||||
date: NaiveDate,
|
||||
symbol_id: u32,
|
||||
symbol: &str,
|
||||
extra_factors: Option<&NumericFactorMap>,
|
||||
field: &str,
|
||||
lookback: usize,
|
||||
) -> Option<f64> {
|
||||
ctx.data
|
||||
.market_decision_numeric_moving_average_by_symbol_id(
|
||||
date, symbol_id, symbol, field, lookback,
|
||||
)
|
||||
let computed = || {
|
||||
ctx.data
|
||||
.market_decision_numeric_moving_average_by_symbol_id(
|
||||
date, symbol_id, symbol, field, lookback,
|
||||
)
|
||||
};
|
||||
let precomputed = extra_factors.and_then(|factors| {
|
||||
precomputed_stock_rolling_mean(|key| factors.get(key).copied(), field, lookback)
|
||||
});
|
||||
if self.config.prefer_precomputed_rolling_factors {
|
||||
precomputed.or_else(computed)
|
||||
} else {
|
||||
computed().or(precomputed)
|
||||
}
|
||||
}
|
||||
|
||||
fn stock_current_rolling_mean(
|
||||
@@ -3790,12 +3843,23 @@ impl PlatformExprStrategy {
|
||||
date: NaiveDate,
|
||||
symbol_id: u32,
|
||||
symbol: &str,
|
||||
extra_factors: Option<&NumericFactorMap>,
|
||||
field: &str,
|
||||
lookback: usize,
|
||||
) -> Option<f64> {
|
||||
ctx.data.market_current_numeric_moving_average_by_symbol_id(
|
||||
date, symbol_id, symbol, field, lookback,
|
||||
)
|
||||
let computed = || {
|
||||
ctx.data.market_current_numeric_moving_average_by_symbol_id(
|
||||
date, symbol_id, symbol, field, lookback,
|
||||
)
|
||||
};
|
||||
let precomputed = extra_factors.and_then(|factors| {
|
||||
precomputed_stock_current_rolling_mean(|key| factors.get(key).copied(), field, lookback)
|
||||
});
|
||||
if self.config.prefer_precomputed_rolling_factors {
|
||||
precomputed.or_else(computed)
|
||||
} else {
|
||||
computed().or(precomputed)
|
||||
}
|
||||
}
|
||||
|
||||
fn stock_state_at_time(
|
||||
@@ -3920,8 +3984,16 @@ impl PlatformExprStrategy {
|
||||
if !self.stock_rolling_requirements.requires(field, lookback) {
|
||||
return f64::NAN;
|
||||
}
|
||||
self.stock_decision_rolling_mean(ctx, date, symbol_id, symbol, field, lookback)
|
||||
.unwrap_or(f64::NAN)
|
||||
self.stock_decision_rolling_mean(
|
||||
ctx,
|
||||
date,
|
||||
symbol_id,
|
||||
symbol,
|
||||
Some(&factor.extra_factors),
|
||||
field,
|
||||
lookback,
|
||||
)
|
||||
.unwrap_or(f64::NAN)
|
||||
};
|
||||
let stock_ma_short = rolling("close", self.config.stock_short_ma_days);
|
||||
let stock_ma_mid = rolling("close", self.config.stock_mid_ma_days);
|
||||
@@ -6037,6 +6109,9 @@ impl PlatformExprStrategy {
|
||||
day.date,
|
||||
stock.symbol_id,
|
||||
&stock.symbol,
|
||||
ctx.data
|
||||
.factor_by_symbol_id(day.date, stock.symbol_id)
|
||||
.map(|factor| &factor.extra_factors),
|
||||
field,
|
||||
lookback,
|
||||
)
|
||||
@@ -6692,6 +6767,9 @@ impl PlatformExprStrategy {
|
||||
day.date,
|
||||
stock.symbol_id,
|
||||
&stock.symbol,
|
||||
ctx.data
|
||||
.factor_by_symbol_id(day.date, stock.symbol_id)
|
||||
.map(|factor| &factor.extra_factors),
|
||||
other,
|
||||
lookback,
|
||||
)
|
||||
@@ -6741,6 +6819,9 @@ impl PlatformExprStrategy {
|
||||
day.date,
|
||||
stock.symbol_id,
|
||||
&stock.symbol,
|
||||
ctx.data
|
||||
.factor_by_symbol_id(day.date, stock.symbol_id)
|
||||
.map(|factor| &factor.extra_factors),
|
||||
other,
|
||||
lookback,
|
||||
)
|
||||
@@ -12092,6 +12173,7 @@ mod tests {
|
||||
PlatformRebalanceSchedule, PlatformScheduleFrequency, PlatformStopTakeReferencePriceMode,
|
||||
PlatformTradeAction, PlatformUniverseActionKind, RuntimeHelperResolution,
|
||||
SelectionRiskDeferral, StockFilterQuoteUsage, framework_stock_rolling_factor_requirement,
|
||||
precomputed_stock_current_rolling_mean, precomputed_stock_rolling_mean,
|
||||
};
|
||||
use crate::{
|
||||
AlgoOrderStyle, BenchmarkSnapshot, CandidateEligibility, CorporateAction,
|
||||
@@ -12159,6 +12241,39 @@ mod tests {
|
||||
assert_eq!(framework_stock_rolling_factor_requirement("alpha001"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_lake_precomputed_rolling_aliases_use_only_canonical_fields() {
|
||||
let factors = BTreeMap::from([
|
||||
("ma5_prev_close".to_string(), 11.0),
|
||||
("ma5".to_string(), 12.0),
|
||||
("ma5_current_close".to_string(), 99.0),
|
||||
("ma5_current_back_adjusted_close".to_string(), 13.0),
|
||||
("avg_volume5".to_string(), 100.0),
|
||||
("avg_volume5_current".to_string(), 120.0),
|
||||
]);
|
||||
let get = |key: &str| factors.get(key).copied();
|
||||
assert_eq!(precomputed_stock_rolling_mean(get, "close", 5), Some(11.0));
|
||||
assert_eq!(
|
||||
precomputed_stock_current_rolling_mean(get, "close", 5),
|
||||
Some(13.0)
|
||||
);
|
||||
assert_eq!(
|
||||
precomputed_stock_rolling_mean(get, "volume", 5),
|
||||
Some(100.0)
|
||||
);
|
||||
assert_eq!(
|
||||
precomputed_stock_current_rolling_mean(get, "volume", 5),
|
||||
Some(120.0)
|
||||
);
|
||||
// A source row that only has a raw current-close alias must not be
|
||||
// mistaken for the adjusted rolling value.
|
||||
let raw_only = BTreeMap::from([("ma5_current_close".to_string(), 99.0)]);
|
||||
assert_eq!(
|
||||
precomputed_stock_current_rolling_mean(|key| raw_only.get(key).copied(), "close", 5,),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_runtime_numbers_preserve_legacy_rhai_formatting() {
|
||||
let RuntimeHelperResolution::Number(value) =
|
||||
|
||||
Reference in New Issue
Block a user