mylittleindicators 0.1.8

Multi-stream financial indicators library — 556 bar indicators + 21 event primitives across 35 categories. Consumes 27 stream kinds from digdigdig3 exchange connectors: OHLCV bars, ticks, orderbook (snapshot/delta/L3), funding/predicted funding/funding settlement, mark price, index price, open interest, liquidations, ticker, agg trades, long/short ratio, option greeks, volatility index, historical volatility, basis (derived), composite index, settlement events, block trades, insurance fund, risk limit, market warning, and three kline-family variants. Live-verified on 12 exchanges (89% pass-rate on a 150s BTC slice).
Documentation
//! composites_catalog.rs: Indicator catalog for cross-stream composite indicators

use crate::catalog::{
    IndicatorSignature, IndicatorCategory, IndicatorRoleKind, ParamConstraint,
};
use crate::bar_indicators::indicator_value::IndicatorValueKind;
use crate::data_loader::stream_kind::StreamKind;
use super::super::bar_indicator_id::BarIndicatorId;

use once_cell::sync::Lazy;
use std::collections::HashMap;

pub const CATEGORY: IndicatorCategory = IndicatorCategory::Composites;

// ============================================================================
// Individual indicator signatures
// ============================================================================

pub fn signature_adaptive_threshold() -> IndicatorSignature {
    IndicatorSignature::builder("ADAPTIVE_THRESHOLD", CATEGORY)
        .name("Adaptive Threshold")
        .description("Dynamically adjusts signal threshold based on recent volatility regime")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::AdaptiveThreshold)
        .role_kind(IndicatorRoleKind::OscillatorUnbounded)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_TICK)
        .alias("adaptive_threshold")
        .alias("AdaptiveThreshold")
        .build()
}

pub fn signature_adaptive_window_selector() -> IndicatorSignature {
    IndicatorSignature::builder("ADAPTIVE_WINDOW_SELECTOR", CATEGORY)
        .name("Adaptive Window Selector")
        .description("Selects optimal lookback window based on market microstructure conditions")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::AdaptiveWindowSelector)
        .role_kind(IndicatorRoleKind::OscillatorUnbounded)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_TICK)
        .alias("adaptive_window_selector")
        .alias("AdaptiveWindowSelector")
        .build()
}

pub fn signature_block_trade_volume_ratio() -> IndicatorSignature {
    IndicatorSignature::builder("BLOCK_TRADE_VOLUME_RATIO", CATEGORY)
        .name("Block Trade Volume Ratio")
        .description("Ratio of block trade volume to total aggregated trade volume")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::BlockTradeVolumeRatio)
        .role_kind(IndicatorRoleKind::OscillatorUnbounded)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_BLOCK_AGG)
        .alias("block_trade_volume_ratio")
        .alias("BlockTradeVolumeRatio")
        .build()
}

pub fn signature_capitulation_detector() -> IndicatorSignature {
    IndicatorSignature::builder("CAPITULATION_DETECTOR", CATEGORY)
        .name("Capitulation Detector")
        .description("Detects capitulation events from combined liquidation, trade flow, and price signals")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::CapitulationDetector)
        .role_kind(IndicatorRoleKind::OscillatorUnbounded)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_LIQUIDATION_AGGTRADE_MARKPRICE)
        .alias("capitulation_detector")
        .alias("CapitulationDetector")
        .build()
}

pub fn signature_compound_squeeze_probability() -> IndicatorSignature {
    IndicatorSignature::builder("COMPOUND_SQUEEZE_PROBABILITY", CATEGORY)
        .name("Compound Squeeze Probability")
        .description("Probability of a squeeze from OI, liquidations, mark price, and funding combined")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::CompoundSqueezeProbability)
        .role_kind(IndicatorRoleKind::Statistical)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_OI_MARKPRICE_LIQUIDATION_FUNDING)
        .alias("compound_squeeze_probability")
        .alias("CompoundSqueezeProbability")
        .build()
}

pub fn signature_funding_oi_pressure() -> IndicatorSignature {
    IndicatorSignature::builder("FUNDING_OI_PRESSURE", CATEGORY)
        .name("Funding OI Pressure")
        .description("Combined pressure signal from funding rate and open interest changes")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::FundingOiPressure)
        .role_kind(IndicatorRoleKind::OscillatorUnbounded)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_FUNDING_OI)
        .alias("funding_oi_pressure")
        .alias("FundingOiPressure")
        .build()
}

pub fn signature_funding_sentiment_alignment() -> IndicatorSignature {
    IndicatorSignature::builder("FUNDING_SENTIMENT_ALIGNMENT", CATEGORY)
        .name("Funding Sentiment Alignment")
        .description("Alignment score between funding rate direction and long/short ratio sentiment")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::FundingSentimentAlignment)
        .role_kind(IndicatorRoleKind::OscillatorUnbounded)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_FUNDING_LSR)
        .alias("funding_sentiment_alignment")
        .alias("FundingSentimentAlignment")
        .build()
}

pub fn signature_index_tracking_error() -> IndicatorSignature {
    IndicatorSignature::builder("INDEX_TRACKING_ERROR", CATEGORY)
        .name("Index Tracking Error")
        .description("Rolling tracking error between perpetual price and underlying index")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::IndexTrackingError)
        .role_kind(IndicatorRoleKind::OscillatorUnbounded)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_INDEXPRICE_COMPOSITEINDEX)
        .alias("index_tracking_error")
        .alias("IndexTrackingError")
        .build()
}

pub fn signature_iv_hv_spread() -> IndicatorSignature {
    IndicatorSignature::builder("IV_HV_SPREAD", CATEGORY)
        .name("IV-HV Spread")
        .description("Spread between implied volatility index and realized historical volatility")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::IvHvSpread)
        .role_kind(IndicatorRoleKind::OscillatorUnbounded)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_HV_VIXIV)
        .alias("iv_hv_spread")
        .alias("IvHvSpread")
        .build()
}

pub fn signature_market_stress_composite() -> IndicatorSignature {
    IndicatorSignature::builder("MARKET_STRESS_COMPOSITE", CATEGORY)
        .name("Market Stress Composite")
        .description("Composite stress score from vol index, liquidations, funding, and insurance fund")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::MarketStressComposite)
        .role_kind(IndicatorRoleKind::OscillatorUnbounded)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_VIXIV_LIQUIDATION_FUNDING_INSURANCE)
        .alias("market_stress_composite")
        .alias("MarketStressComposite")
        .build()
}

pub fn signature_risk_off_detector() -> IndicatorSignature {
    IndicatorSignature::builder("RISK_OFF_DETECTOR", CATEGORY)
        .name("Risk-Off Detector")
        .description("Detects risk-off regimes from vol index, liquidations, funding, and insurance fund signals")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::RiskOffDetector)
        .role_kind(IndicatorRoleKind::OscillatorUnbounded)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_VIXIV_LIQUIDATION_FUNDING_INSURANCE)
        .alias("risk_off_detector")
        .alias("RiskOffDetector")
        .build()
}

pub fn signature_sentiment_composite() -> IndicatorSignature {
    IndicatorSignature::builder("SENTIMENT_COMPOSITE", CATEGORY)
        .name("Sentiment Composite")
        .description("Composite sentiment from long/short ratio, aggregated trade flow, and funding rate")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::SentimentComposite)
        .role_kind(IndicatorRoleKind::OscillatorUnbounded)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_LSR_AGGTRADE_FUNDING)
        .alias("sentiment_composite")
        .alias("SentimentComposite")
        .build()
}

pub fn signature_squeeze_probability() -> IndicatorSignature {
    IndicatorSignature::builder("SQUEEZE_PROBABILITY", CATEGORY)
        .name("Squeeze Probability")
        .description("Probability of a short/long squeeze from OI, mark price, and liquidation inputs")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::SqueezeProbability)
        .role_kind(IndicatorRoleKind::Statistical)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_OI_MARKPRICE_LIQUIDATION)
        .alias("squeeze_probability")
        .alias("SqueezeProbability")
        .build()
}

pub fn signature_vol_regime_entry() -> IndicatorSignature {
    IndicatorSignature::builder("VOL_REGIME_ENTRY", CATEGORY)
        .name("Vol Regime Entry")
        .description("Entry signal based on volatility regime transition from vol index and mark price")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::VolRegimeEntry)
        .role_kind(IndicatorRoleKind::OscillatorUnbounded)
        .output_kind(IndicatorValueKind::Single)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_VIXIV_MARKPRICE)
        .alias("vol_regime_entry")
        .alias("VolRegimeEntry")
        .build()
}

static AUX_TICK: &[StreamKind] = &[StreamKind::Tick];
static AUX_BLOCK_AGG: &[StreamKind] = &[StreamKind::BlockTrade, StreamKind::AggTrade];
static AUX_LIQUIDATION_AGGTRADE_MARKPRICE: &[StreamKind] = &[StreamKind::Liquidation, StreamKind::AggTrade, StreamKind::MarkPrice];
static AUX_OI_MARKPRICE_LIQUIDATION_FUNDING: &[StreamKind] = &[StreamKind::OpenInterest, StreamKind::MarkPrice, StreamKind::Liquidation, StreamKind::Funding];
static AUX_FUNDING_OI: &[StreamKind] = &[StreamKind::Funding, StreamKind::OpenInterest];
static AUX_FUNDING_LSR: &[StreamKind] = &[StreamKind::Funding, StreamKind::LongShortRatio];
static AUX_INDEXPRICE_COMPOSITEINDEX: &[StreamKind] = &[StreamKind::IndexPrice, StreamKind::CompositeIndex];
static AUX_HV_VIXIV: &[StreamKind] = &[StreamKind::HistoricalVolatility, StreamKind::VolatilityIndex];
static AUX_VIXIV_LIQUIDATION_FUNDING_INSURANCE: &[StreamKind] = &[StreamKind::VolatilityIndex, StreamKind::Liquidation, StreamKind::Funding, StreamKind::InsuranceFund];
static AUX_LSR_AGGTRADE_FUNDING: &[StreamKind] = &[StreamKind::LongShortRatio, StreamKind::AggTrade, StreamKind::Funding];
static AUX_OI_MARKPRICE_LIQUIDATION: &[StreamKind] = &[StreamKind::OpenInterest, StreamKind::MarkPrice, StreamKind::Liquidation];
static AUX_VIXIV_MARKPRICE: &[StreamKind] = &[StreamKind::VolatilityIndex, StreamKind::MarkPrice];
static AUX_FUNDING: &[StreamKind] = &[StreamKind::Funding];

pub fn signature_funding_price_divergence() -> IndicatorSignature {
    IndicatorSignature::builder("FUNDING_PRICE_DIVERGENCE", CATEGORY)
        .name("Funding Price Divergence")
        .description("Composite: funding momentum × price momentum divergence — detects short-squeeze or long-liquidation setups")
        .add_constraint(ParamConstraint::period(2, 200, 14))
        .machine_id(BarIndicatorId::FundingPriceDivergence)
        .role_kind(IndicatorRoleKind::OscillatorUnbounded)
        .output_kind(IndicatorValueKind::Triple)
        .input_stream(StreamKind::Bar)
        .aux_streams(AUX_FUNDING)
        .alias("funding_price_divergence")
        .alias("FundingPriceDivergence")
        .alias("FUNDINGPRICEDIVERGENCE")
        .alias("FundingPriceMomentumDivergence")
        .build()
}

// ============================================================================
// Catalog
// ============================================================================

const BASE_CATALOG: &[(&str, fn() -> IndicatorSignature)] = &[
    ("ADAPTIVE_THRESHOLD", signature_adaptive_threshold as fn() -> IndicatorSignature),
    ("ADAPTIVE_WINDOW_SELECTOR", signature_adaptive_window_selector as fn() -> IndicatorSignature),
    ("BLOCK_TRADE_VOLUME_RATIO", signature_block_trade_volume_ratio as fn() -> IndicatorSignature),
    ("CAPITULATION_DETECTOR", signature_capitulation_detector as fn() -> IndicatorSignature),
    ("COMPOUND_SQUEEZE_PROBABILITY", signature_compound_squeeze_probability as fn() -> IndicatorSignature),
    ("FUNDING_OI_PRESSURE", signature_funding_oi_pressure as fn() -> IndicatorSignature),
    ("FUNDING_SENTIMENT_ALIGNMENT", signature_funding_sentiment_alignment as fn() -> IndicatorSignature),
    ("INDEX_TRACKING_ERROR", signature_index_tracking_error as fn() -> IndicatorSignature),
    ("IV_HV_SPREAD", signature_iv_hv_spread as fn() -> IndicatorSignature),
    ("MARKET_STRESS_COMPOSITE", signature_market_stress_composite as fn() -> IndicatorSignature),
    ("RISK_OFF_DETECTOR", signature_risk_off_detector as fn() -> IndicatorSignature),
    ("SENTIMENT_COMPOSITE", signature_sentiment_composite as fn() -> IndicatorSignature),
    ("SQUEEZE_PROBABILITY", signature_squeeze_probability as fn() -> IndicatorSignature),
    ("VOL_REGIME_ENTRY", signature_vol_regime_entry as fn() -> IndicatorSignature),
    ("FUNDING_PRICE_DIVERGENCE", signature_funding_price_divergence as fn() -> IndicatorSignature),
];

pub static COMPOSITES_CATALOG: Lazy<HashMap<String, fn() -> IndicatorSignature>> = Lazy::new(|| {
    let mut m = HashMap::new();
    for &(main_id, func) in BASE_CATALOG {
        let sig = func();
        m.insert(main_id.to_string(), func);
        for alias in &sig.aliases {
            m.insert(alias.clone(), func);
        }
    }
    m
});

pub fn get_signature(id: &str) -> Option<IndicatorSignature> {
    COMPOSITES_CATALOG.get(id).map(|f| f())
}

pub fn all_indicator_ids() -> Vec<&'static str> {
    BASE_CATALOG.iter().map(|(id, _)| *id).collect()
}

pub fn count() -> usize {
    BASE_CATALOG.len()
}