修复 AiQuant 微盘回测撮合语义

This commit is contained in:
boris
2026-05-13 18:43:02 +08:00
parent 2165831708
commit db72f6f515
8 changed files with 849 additions and 162 deletions

View File

@@ -2151,7 +2151,7 @@ impl OmniMicroCapStrategy {
symbol: self.config.benchmark_signal_symbol.clone(),
field: "decision_close",
})?;
// 前一交易日的指数价格(用于市值区间计算,模拟实盘场景)
let prev_level = if let Some(prev_date) = ctx.data.previous_trading_date(date, 1) {
ctx.data
@@ -2160,7 +2160,7 @@ impl OmniMicroCapStrategy {
} else {
current_level
};
let ma_short = ctx
.data
.market_decision_close_moving_average(
@@ -2200,16 +2200,16 @@ impl OmniMicroCapStrategy {
+ self.config.base_cap_floor;
let start = y.round();
let end = start + self.config.cap_span;
// Apply padding to expand the range
let span = end - start;
let padding = (span * self.config.padding_ratio)
.max(self.config.min_padding)
.min(self.config.max_padding);
let lower_bound = (start - padding).max(0.0);
let upper_bound = end + padding;
(lower_bound, upper_bound)
}
@@ -2242,8 +2242,9 @@ impl OmniMicroCapStrategy {
};
// MA filter: ma_short > ma_mid * rsi_rate && ma_mid * rsi_rate > ma_long
let ma_pass = ma_short > ma_mid * self.config.rsi_rate && ma_mid * self.config.rsi_rate > ma_long;
let ma_pass =
ma_short > ma_mid * self.config.rsi_rate && ma_mid * self.config.rsi_rate > ma_long;
// Debug logging for ALL stocks on first decision date
static DEBUG_DATE: std::sync::Mutex<Option<NaiveDate>> = std::sync::Mutex::new(None);
let mut debug_date = DEBUG_DATE.lock().unwrap();
@@ -2253,39 +2254,48 @@ impl OmniMicroCapStrategy {
*debug_date = Some(date);
true
};
if should_debug {
eprintln!("[MA_FILTER] {} cap={:.2} ma5={:.4} ma10={:.4} ma30={:.4} ma10*rsi={:.4} pass={} ({}>{:.4}? {} && {:.4}>{}? {})",
symbol,
eprintln!(
"[MA_FILTER] {} cap={:.2} ma5={:.4} ma10={:.4} ma30={:.4} ma10*rsi={:.4} pass={} ({}>{:.4}? {} && {:.4}>{}? {})",
symbol,
ctx.data.market_decision_close(date, symbol).unwrap_or(0.0),
ma_short, ma_mid, ma_long,
ma_short,
ma_mid,
ma_long,
ma_mid * self.config.rsi_rate,
ma_pass,
ma_short, ma_mid * self.config.rsi_rate, ma_short > ma_mid * self.config.rsi_rate,
ma_mid * self.config.rsi_rate, ma_long, ma_mid * self.config.rsi_rate > ma_long);
ma_short,
ma_mid * self.config.rsi_rate,
ma_short > ma_mid * self.config.rsi_rate,
ma_mid * self.config.rsi_rate,
ma_long,
ma_mid * self.config.rsi_rate > ma_long
);
}
if !ma_pass {
return false;
}
// Volume filter: V5 < V60 (applied for omni_microcap strategies)
if self.config.strategy_name.contains("aiquant") || self.config.strategy_name.contains("AiQuant") || self.config.strategy_name.contains("omni") {
let Some(volume_ma5) = ctx.data.market_decision_volume_moving_average(
date,
symbol,
5,
) else {
if self.config.strategy_name.contains("aiquant")
|| self.config.strategy_name.contains("AiQuant")
|| self.config.strategy_name.contains("omni")
{
let Some(volume_ma5) = ctx
.data
.market_decision_volume_moving_average(date, symbol, 5)
else {
return false;
};
let Some(volume_ma60) = ctx.data.market_decision_volume_moving_average(
date,
symbol,
60,
) else {
let Some(volume_ma60) = ctx
.data
.market_decision_volume_moving_average(date, symbol, 60)
else {
return false;
};
if volume_ma5 >= volume_ma60 {
return false;
}
@@ -2662,28 +2672,31 @@ impl Strategy for OmniMicroCapStrategy {
});
}
let (index_level, prev_index_level, ma_short, ma_long, trading_ratio) = match self.trading_ratio(ctx, date) {
Ok(value) => value,
Err(BacktestError::Execution(message))
if message.contains("insufficient benchmark") =>
{
return Ok(StrategyDecision {
rebalance: false,
target_weights: BTreeMap::new(),
exit_symbols: BTreeSet::new(),
order_intents: Vec::new(),
notes: vec![format!("warmup: {}", message)],
diagnostics: vec![
"insufficient history; skip trading on warmup dates".to_string(),
],
});
}
Err(err) => return Err(err),
};
let (index_level, prev_index_level, ma_short, ma_long, trading_ratio) =
match self.trading_ratio(ctx, date) {
Ok(value) => value,
Err(BacktestError::Execution(message))
if message.contains("insufficient benchmark") =>
{
return Ok(StrategyDecision {
rebalance: false,
target_weights: BTreeMap::new(),
exit_symbols: BTreeSet::new(),
order_intents: Vec::new(),
notes: vec![format!("warmup: {}", message)],
diagnostics: vec![
"insufficient history; skip trading on warmup dates".to_string(),
],
});
}
Err(err) => return Err(err),
};
// 使用前一交易日的指数价格计算市值区间(模拟实盘场景)
let (band_low, band_high) = self.market_cap_band(prev_index_level);
eprintln!("[DEBUG] date={} current_index={:.2} prev_index={:.2} band=[{:.0}, {:.0}]",
date, index_level, prev_index_level, band_low, band_high);
eprintln!(
"[DEBUG] date={} current_index={:.2} prev_index={:.2} band=[{:.0}, {:.0}]",
date, index_level, prev_index_level, band_low, band_high
);
let (stock_list, selection_notes) = self.select_symbols(ctx, date, band_low, band_high)?;
let periodic_rebalance = ctx.decision_index % self.config.refresh_rate == 0;
let mut projected = ctx.portfolio.clone();