bitcoin-heuristics 0.1.0

Pattern detection heuristics for Bitcoin on-chain analytics — consolidations, distributions, CoinJoin, fee spikes, dormant supply reactivation and more
Documentation
use crate::models::FeePercentileTier;

/// Given the `fee_rate_sat_vb` of a tx and the block statistics p10/p25/p50/p75/p90/p99.
pub fn calculate_fee_percentile(
    fee_rate_sat_vb: Option<f64>,
    p10: f64,
    p25: f64,
    p50: f64,
    p75: f64,
    p90: f64,
    p99: f64,
) -> FeePercentileTier {
    let rate = match fee_rate_sat_vb {
        Some(r) => r,
        None => return FeePercentileTier::BelowP10,
    };

    if rate < p10 {
        FeePercentileTier::BelowP10
    } else if rate < p25 {
        FeePercentileTier::P10ToP25
    } else if rate < p50 {
        FeePercentileTier::P25ToP50
    } else if rate < p75 {
        FeePercentileTier::P50ToP75
    } else if rate < p90 {
        FeePercentileTier::P75ToP90
    } else if rate < p99 {
        FeePercentileTier::P90ToP99
    } else {
        FeePercentileTier::AboveP99
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn percentiles() -> (f64, f64, f64, f64, f64, f64) {
        (1.0, 3.0, 10.0, 25.0, 50.0, 100.0)
    }

    #[test]
    fn test_none_returns_below_p10() {
        let (p10, p25, p50, p75, p90, p99) = percentiles();
        assert_eq!(
            calculate_fee_percentile(None, p10, p25, p50, p75, p90, p99),
            FeePercentileTier::BelowP10
        );
    }

    #[test]
    fn test_below_p10() {
        let (p10, p25, p50, p75, p90, p99) = percentiles();
        assert_eq!(
            calculate_fee_percentile(Some(0.5), p10, p25, p50, p75, p90, p99),
            FeePercentileTier::BelowP10
        );
    }

    #[test]
    fn test_above_p99() {
        let (p10, p25, p50, p75, p90, p99) = percentiles();
        assert_eq!(
            calculate_fee_percentile(Some(200.0), p10, p25, p50, p75, p90, p99),
            FeePercentileTier::AboveP99
        );
    }

    #[test]
    fn test_p75_to_p90() {
        // rate=30 is between p75=25 and p90=50 → P75ToP90
        let (p10, p25, p50, p75, p90, p99) = percentiles();
        assert_eq!(
            calculate_fee_percentile(Some(30.0), p10, p25, p50, p75, p90, p99),
            FeePercentileTier::P75ToP90
        );
    }
}