feat(tick): precompute pipeline wires MicrostructureState — 20 OFI features
12 new tick-level features computed per bar from MBP-10 snapshots: OFI trajectory, realized variance, Hawkes intensity, book pressure, spread dynamics, aggression, queue depletion, order count flux, intra-bar momentum, regime score, OFI acceleration, toxicity gradient. Combined with existing 8 OFI into [f64; 20] per bar in fxcache. Also fixes pre-existing bug: no-MBP-10 fallback was [0.0; 8] not [0.0; 20]. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -391,32 +391,58 @@ async fn main() -> Result<()> {
|
||||
};
|
||||
|
||||
// Compute per-bar OFI
|
||||
use ml::features::mbp10_loader::get_snapshots_for_timestamp;
|
||||
use ml::features::trades_loader::get_trades_for_bar;
|
||||
use ml::features::ofi_calculator::MicrostructureState;
|
||||
let mut calculator = OFICalculator::new();
|
||||
let mut ofi_per_bar = Vec::with_capacity(n);
|
||||
|
||||
for i in 0..n {
|
||||
let bar = &all_bars[i + WARMUP];
|
||||
let bar_ts = bar.timestamp.timestamp_nanos_opt().unwrap_or(0) as u64;
|
||||
let bar_end_ts = all_bars.get(i + WARMUP + 1)
|
||||
.map(|b| b.timestamp.timestamp_nanos_opt().unwrap_or(0) as u64)
|
||||
.unwrap_or(bar_ts + 60_000_000_000);
|
||||
let bar_duration_ns = bar_end_ts - bar_ts;
|
||||
|
||||
// Feed trades for this bar window
|
||||
let mut micro_state = MicrostructureState::new(bar_ts, bar_duration_ns);
|
||||
|
||||
// Feed trades for this bar window (OFICalculator + MicrostructureState)
|
||||
if let Some(ref trades) = all_trades {
|
||||
let bar_end_ts = all_bars.get(i + WARMUP + 1)
|
||||
.map(|b| b.timestamp.timestamp_nanos_opt().unwrap_or(0) as u64)
|
||||
.unwrap_or(bar_ts + 60_000_000_000);
|
||||
for trade in get_trades_for_bar(trades, bar_ts, bar_end_ts) {
|
||||
calculator.feed_trade(trade.price, trade.volume, trade.is_buy);
|
||||
micro_state.update_trade(trade.price, trade.volume, trade.is_buy, trade.timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
let window = get_snapshots_for_timestamp(&all_snapshots, bar_ts, 1);
|
||||
if let Some(snap) = window.first() {
|
||||
// Get ALL MBP-10 snapshots within this bar window for tick-level features.
|
||||
// Binary search for bar range: [bar_ts, bar_end_ts)
|
||||
let snap_start = all_snapshots.partition_point(|s| s.timestamp < bar_ts);
|
||||
let snap_end = all_snapshots.partition_point(|s| s.timestamp < bar_end_ts);
|
||||
let bar_snapshots = &all_snapshots[snap_start..snap_end];
|
||||
|
||||
// Feed every snapshot to MicrostructureState for tick-level resolution
|
||||
for snap in bar_snapshots {
|
||||
micro_state.update_snapshot(snap);
|
||||
}
|
||||
|
||||
// Use the LAST snapshot in the bar for OFI calculation (matches prior behavior)
|
||||
let last_snap = bar_snapshots.last()
|
||||
.or_else(|| {
|
||||
// Fallback: nearest snapshot at/after bar_ts (prior behavior)
|
||||
all_snapshots.get(snap_start)
|
||||
});
|
||||
|
||||
if let Some(snap) = last_snap {
|
||||
match calculator.calculate(snap) {
|
||||
Ok(f) if f.is_valid() => {
|
||||
// Feed OFI-derived values to MicrostructureState
|
||||
micro_state.update_ofi_derived(f.ofi_level1, f.vpin);
|
||||
|
||||
let arr8 = f.to_array();
|
||||
let micro_12 = micro_state.snapshot();
|
||||
let mut arr20 = [0.0_f64; 20];
|
||||
arr20[..8].copy_from_slice(&arr8);
|
||||
arr20[8..20].copy_from_slice(µ_12);
|
||||
ofi_per_bar.push(arr20);
|
||||
}
|
||||
_ => ofi_per_bar.push([0.0; 20]),
|
||||
@@ -435,7 +461,7 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
} else {
|
||||
info!("No MBP-10 directory, OFI will be zeros");
|
||||
vec![[0.0; 8]; n]
|
||||
vec![[0.0; 20]; n]
|
||||
};
|
||||
|
||||
let total_len = n;
|
||||
|
||||
Reference in New Issue
Block a user