mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-12 11:38:13 -07:00
heatmaps: part 12
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
use std::ops::Range;
|
||||
|
||||
/// First height the oracle computes on-chain, with the slow cold-start EMA
|
||||
/// ([`slow`](Config::slow)). Below it, prices come from [`PRICES`](crate::PRICES).
|
||||
pub const START_HEIGHT_SLOW: usize = 340_000;
|
||||
@@ -61,4 +63,41 @@ impl Config {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Split a block range into sub-ranges with a single EMA configuration.
|
||||
pub fn segments_for_range(range: Range<usize>) -> impl Iterator<Item = Range<usize>> {
|
||||
let split = START_HEIGHT_FAST.clamp(range.start, range.end);
|
||||
[range.start..split, split..range.end]
|
||||
.into_iter()
|
||||
.filter(|range| !range.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn segments_for_range_splits_at_fast_start() {
|
||||
let segments: Vec<_> =
|
||||
Config::segments_for_range((START_HEIGHT_FAST - 2)..(START_HEIGHT_FAST + 2)).collect();
|
||||
assert_eq!(
|
||||
segments,
|
||||
vec![
|
||||
(START_HEIGHT_FAST - 2)..START_HEIGHT_FAST,
|
||||
START_HEIGHT_FAST..(START_HEIGHT_FAST + 2),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn segments_for_range_omits_empty_sides() {
|
||||
let slow: Vec<_> =
|
||||
Config::segments_for_range((START_HEIGHT_FAST - 2)..START_HEIGHT_FAST).collect();
|
||||
assert_eq!(slow, vec![(START_HEIGHT_FAST - 2)..START_HEIGHT_FAST]);
|
||||
|
||||
let fast: Vec<_> =
|
||||
Config::segments_for_range(START_HEIGHT_FAST..(START_HEIGHT_FAST + 2)).collect();
|
||||
assert_eq!(fast, vec![START_HEIGHT_FAST..(START_HEIGHT_FAST + 2)]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,4 +169,46 @@ mod tests {
|
||||
|
||||
assert!(switched.ema().iter().eq(fresh.ema().iter()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sequential_ema_matches_fresh_warmups() {
|
||||
let hists: Vec<HistogramRaw> = (0..80)
|
||||
.map(|i| {
|
||||
let mut h = HistogramRaw::zeros();
|
||||
h.increment(1000 + i % 11);
|
||||
h.increment(1300 + i % 13);
|
||||
h.increment(1700 + i % 17);
|
||||
h
|
||||
})
|
||||
.collect();
|
||||
|
||||
for config in [Config::slow(), Config::default()] {
|
||||
let query_start = config.window_size + 5;
|
||||
let query_end = query_start + 20;
|
||||
let seed = 1600.0;
|
||||
let mut sequential = Oracle::from_checkpoint(seed, config.clone(), |o| {
|
||||
hists[query_start + 1 - config.window_size..query_start + 1]
|
||||
.iter()
|
||||
.for_each(|h| {
|
||||
o.process_histogram(h);
|
||||
});
|
||||
});
|
||||
|
||||
for height in query_start..query_end {
|
||||
if height != query_start {
|
||||
sequential.process_histogram(&hists[height]);
|
||||
}
|
||||
|
||||
let fresh = Oracle::from_checkpoint(seed, config.clone(), |o| {
|
||||
hists[height + 1 - config.window_size..height + 1]
|
||||
.iter()
|
||||
.for_each(|h| {
|
||||
o.process_histogram(h);
|
||||
});
|
||||
});
|
||||
|
||||
assert!(sequential.ema().iter().eq(fresh.ema().iter()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,11 @@ impl ShapeAnchor {
|
||||
/// shifts off the round-USD ladder. 0 for an empty (no-mass) center.
|
||||
fn shape_match(&self, ema: &HistogramEma, center: i64) -> f64 {
|
||||
match normalized_arms_at(ema, center) {
|
||||
Some(arms) => 1.0 - (0..N_ARMS).map(|i| (arms[i] - self.profile[i]).abs()).sum::<f64>(),
|
||||
Some(arms) => {
|
||||
1.0 - (0..N_ARMS)
|
||||
.map(|i| (arms[i] - self.profile[i]).abs())
|
||||
.sum::<f64>()
|
||||
}
|
||||
None => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user