feat(loader): InstrumentFilter::FrontMonth for cross-quarter ES.FUT data

Replaces Option<u32> instrument_id_filter with InstrumentFilter enum {All,
Id(u32), FrontMonth}. FrontMonth runs a two-pass detect over the DBN
stream: pass 1 counts instrument_ids and collects SymbolMapping records,
picks the dominant id, validates it resolves to an ES contract via regex
ES[FGHJKMNQUVXZ]\d{1,2}; pass 2 streams the filtered records.

Motivated by alpha-perception-k54wd: a single-id filter on parent-symbol
ES.FUT data caught Q1 2024 (kept=73M) but kept=0 for Q2-Q9 because ES
front-month rolls quarterly (ESH4 -> ESM4 -> ESU4 -> ESZ4 ...). FrontMonth
self-tunes across the rolls without needing a per-file id table.

Sidecar keys distinguish modes: mbp10 / mbp10_instr<id> / mbp10_front_month.
CLI flag renamed --instrument-id -> --instrument-mode {all,id=N,front-month}
with matching parameter rename in argo-alpha-perception.sh + template.
This commit is contained in:
jgrusewski
2026-05-22 17:38:41 +02:00
parent 11c658d3fb
commit 78a9e08358
18 changed files with 294 additions and 89 deletions

View File

@@ -172,7 +172,10 @@ impl Drop for MappedF32 {
}
}
use data::providers::databento::{dbn_parser::DbnParser, mbp10::Mbp10Snapshot};
use data::providers::databento::{
dbn_parser::{DbnParser, InstrumentFilter},
mbp10::Mbp10Snapshot,
};
use ml::cuda_pipeline::alpha_isv_slots::{
ACTION_ENTROPY_EMA_INDEX, ALPHA_ISV_BLOCK_LO, EARLY_Q_MOVEMENT_EMA_INDEX,
Q_SPREAD_EMA_INDEX, RANDOM_BASELINE_MEAN_INDEX, RANDOM_BASELINE_STD_INDEX,
@@ -391,7 +394,7 @@ fn load_snapshots(
let mut rows: Vec<SnapshotRow> = Vec::with_capacity(max_snapshots);
'files: for file in &files {
let mut hit_limit = false;
let result = parser.parse_mbp10_streaming(file, snapshot_interval, None, |snap: &Mbp10Snapshot| {
let result = parser.parse_mbp10_streaming(file, snapshot_interval, InstrumentFilter::All, |snap: &Mbp10Snapshot| {
if rows.len() >= max_snapshots {
hit_limit = true;
return;

View File

@@ -40,7 +40,7 @@ use clap::Parser;
use tracing::{info, warn};
use data::providers::databento::{
dbn_parser::DbnParser,
dbn_parser::{DbnParser, InstrumentFilter},
mbp10::Mbp10Snapshot,
};
@@ -163,7 +163,7 @@ fn main() -> Result<()> {
let result = parser.parse_mbp10_streaming(
file,
cli.snapshot_interval,
None,
InstrumentFilter::All,
|snap: &Mbp10Snapshot| {
if n_processed >= limit {
hit_limit = true;

View File

@@ -35,7 +35,7 @@ use clap::Parser;
use tracing::{info, warn};
use data::providers::databento::{
dbn_parser::DbnParser,
dbn_parser::{DbnParser, InstrumentFilter},
mbp10::Mbp10Snapshot,
};
use ml::env::action_space::N_ACTIONS;
@@ -168,7 +168,7 @@ fn main() -> Result<()> {
'files: for file in &files {
let mut hit_limit = false;
let result =
parser.parse_mbp10_streaming(file, cli.snapshot_interval, None, |snap: &Mbp10Snapshot| {
parser.parse_mbp10_streaming(file, cli.snapshot_interval, InstrumentFilter::All, |snap: &Mbp10Snapshot| {
if rows.len() >= cli.max_snapshots {
hit_limit = true;
return;

View File

@@ -580,7 +580,7 @@ async fn main() -> Result<()> {
// precompute_features is the multi-symbol feature-cache
// builder; it intentionally consumes every instrument in
// each file. No filter.
match ml::features::predecoded::load_or_predecode_mbp10(file, &predecoded_dir, None) {
match ml::features::predecoded::load_or_predecode_mbp10(file, &predecoded_dir, data::providers::databento::dbn_parser::InstrumentFilter::All) {
Ok(snapshots) => {
info!(
" {} -> {} snapshots",

View File

@@ -314,7 +314,7 @@ impl DQNTrainer {
if !self.hyperparams.mbp10_data_dir.is_empty() {
use crate::features::ofi_calculator::OFICalculator;
use crate::features::trades_loader::get_trades_for_bar;
use data::providers::databento::dbn_parser::DbnParser;
use data::providers::databento::dbn_parser::{DbnParser, InstrumentFilter};
let mbp10_dir = Path::new(&self.hyperparams.mbp10_data_dir);
if mbp10_dir.exists() {
@@ -336,10 +336,10 @@ impl DQNTrainer {
Err(e) => { warn!(" DBN parser init failed: {e}"); return None; }
};
let mut snapshots = Vec::new();
// DQN data loading predates the instrument-filter
// refactor; keep legacy all-instruments behavior
// until the DQN trainer needs filtering.
match parser.parse_mbp10_streaming(file, 100, None, |snap| {
// DQN data loading keeps legacy all-instruments
// behavior; switch to FrontMonth when DQN training
// moves to multi-quarter walk-forward.
match parser.parse_mbp10_streaming(file, 100, InstrumentFilter::All, |snap| {
snapshots.push(snap.clone());
}) {
Ok(_) => {