fix(loader): soft-validate FrontMonth symbol when no streaming SymbolMapping
Databento historical bulk DBN files emit symbol mappings only in the metadata header (date-range form), not as in-stream SymbolMappingMsg records. The strict 'no mapping = error' branch from the previous commit blocked the front-month smoke (alpha-perception-vstrr) at Q1 2024 where the dominant id 5602 has no streaming mapping entry. Volume-leader detection itself doesn't depend on the symbol; the regex check was defense-in-depth. Log-warn on symbol-mismatch and proceed with id=metadata-only when no streaming mapping exists. Calendar-spread anomalies still surface in logs without blocking training.
This commit is contained in:
@@ -1023,33 +1023,36 @@ impl DbnParser {
|
||||
})?;
|
||||
let total: u64 = counts.values().sum();
|
||||
|
||||
let winner_symbol = id_to_symbol.get(&winner_id).cloned().ok_or_else(|| {
|
||||
DataError::InvalidFormat(format!(
|
||||
"front-month detect: no SymbolMapping record for dominant instrument_id={} in {}",
|
||||
winner_id,
|
||||
path.display()
|
||||
))
|
||||
})?;
|
||||
|
||||
// ES futures month codes: F=Jan G=Feb H=Mar J=Apr K=May M=Jun
|
||||
// N=Jul Q=Aug U=Sep V=Oct X=Nov Z=Dec; year suffix 1-2 digits.
|
||||
// Examples: ESH4, ESM4, ESU4, ESZ4, ESH25. Calendar spreads
|
||||
// (ES-ESM4) and outright spreads fail this match by design.
|
||||
// Soft-validate via SymbolMapping records when present. Databento
|
||||
// historical bulk files often emit mappings only in the metadata
|
||||
// header (date-range form) and skip in-stream SymbolMappingMsg
|
||||
// entirely; in that case we trust the dominant-id (volume leader =
|
||||
// front-month) and proceed. When the in-stream symbol IS present,
|
||||
// we check it against the ES contract regex and warn (not fail) on
|
||||
// mismatch so calendar-spread anomalies surface in logs without
|
||||
// blocking training.
|
||||
let winner_symbol = id_to_symbol.get(&winner_id).cloned();
|
||||
let es_re = regex::Regex::new(r"^ES[FGHJKMNQUVXZ]\d{1,2}$")
|
||||
.expect("static ES futures regex compiles");
|
||||
if !es_re.is_match(&winner_symbol) {
|
||||
return Err(DataError::InvalidFormat(format!(
|
||||
"front-month detect: dominant id {} resolved to symbol '{}' which is not an ES \
|
||||
contract (regex ES[F-Z]\\d{{1,2}}) in {}",
|
||||
winner_id,
|
||||
winner_symbol,
|
||||
path.display()
|
||||
)));
|
||||
let symbol_status: &str = match &winner_symbol {
|
||||
Some(sym) if es_re.is_match(sym) => "es-contract",
|
||||
Some(_) => "symbol-mismatch",
|
||||
None => "no-streaming-mapping",
|
||||
};
|
||||
if symbol_status == "symbol-mismatch" {
|
||||
warn!(
|
||||
instrument_id = winner_id,
|
||||
symbol = %winner_symbol.as_deref().unwrap_or(""),
|
||||
path = %path.display(),
|
||||
"front-month dominant id resolves to a non-ES symbol via SymbolMapping; \
|
||||
proceeding anyway (calendar spread or unexpected contract?)"
|
||||
);
|
||||
}
|
||||
|
||||
info!(
|
||||
instrument_id = winner_id,
|
||||
symbol = %winner_symbol,
|
||||
symbol = %winner_symbol.as_deref().unwrap_or("(metadata-only)"),
|
||||
symbol_status,
|
||||
count = winner_count,
|
||||
total = total,
|
||||
distinct_ids = counts.len(),
|
||||
|
||||
Reference in New Issue
Block a user