use crate::catalog::{IndicatorSignature, IndicatorCategory};
use std::collections::HashMap;
use once_cell::sync::Lazy;
use crate::bar_indicators::average::average_catalog;
use crate::bar_indicators::momentum::momentum_catalog;
use crate::bar_indicators::channels::channels_catalog;
use crate::bar_indicators::volatility::volatility_catalog;
use crate::bar_indicators::volume::volume_catalog;
use crate::bar_indicators::trend::trend_catalog;
use crate::bar_indicators::levels::levels_catalog;
use crate::bar_indicators::entropy::entropy_catalog;
use crate::bar_indicators::kalman::kalman_catalog;
use crate::bar_indicators::signal_processing::signal_processing_catalog;
use crate::bar_indicators::chaos::chaos_catalog;
use crate::bar_indicators::regression::regression_catalog;
use crate::bar_indicators::adaptive::adaptive_catalog;
use crate::bar_indicators::accumulation::accumulation_catalog;
use crate::bar_indicators::book::book_catalog;
use crate::bar_indicators::candles::candles_catalog;
use crate::bar_indicators::clusters::clusters_catalog;
use crate::bar_indicators::divergence::divergence_catalog;
use crate::bar_indicators::ratio::ratio_catalog;
use crate::bar_indicators::trend_stop::trend_stop_catalog;
use crate::bar_indicators::position::position_catalog;
use crate::bar_indicators::statistics::statistics_catalog;
use crate::bar_indicators::statistical_scoring::statistical_scoring_catalog;
use crate::bar_indicators::funding_advanced::funding_advanced_catalog;
use crate::bar_indicators::open_interest::open_interest_catalog;
use crate::bar_indicators::mark_price_advanced::mark_price_advanced_catalog;
use crate::bar_indicators::ticker_advanced::ticker_advanced_catalog;
use crate::bar_indicators::liquidations::liquidations_catalog;
use crate::bar_indicators::tick_advanced::tick_advanced_catalog;
use crate::bar_indicators::book_advanced::book_advanced_catalog;
use crate::bar_indicators::composites::composites_catalog;
use crate::bar_indicators::sentiment::sentiment_catalog;
use crate::bar_indicators::index_basis::index_basis_catalog;
use crate::bar_indicators::volatility_advanced::volatility_advanced_catalog;
use crate::bar_indicators::greeks::greeks_catalog;
use crate::bar_indicators::stress::stress_catalog;
use crate::bar_indicators::microstructure::microstructure_catalog;
use crate::bar_indicators::risk_funding::risk_funding_catalog;
#[derive(Debug, Clone)]
pub enum CatalogError {
NotFound(String),
CategoryNotFound(IndicatorCategory),
Ambiguous(String, Vec<IndicatorCategory>),
}
impl std::fmt::Display for CatalogError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CatalogError::NotFound(id) => write!(f, "Indicator '{}' not found in any catalog", id),
CatalogError::CategoryNotFound(cat) => write!(f, "Category '{:?}' not registered", cat),
CatalogError::Ambiguous(id, cats) => write!(f, "Indicator '{}' found in multiple categories: {:?}", id, cats),
}
}
}
impl std::error::Error for CatalogError {}
pub trait CatalogProvider: Send + Sync {
fn get_signature(&self, id: &str) -> Option<IndicatorSignature>;
fn get_all_ids(&self) -> Vec<&'static str>;
fn category(&self) -> IndicatorCategory;
fn count(&self) -> usize;
}
struct CategoryCatalogWrapper {
category: IndicatorCategory,
get_fn: fn(&str) -> Option<IndicatorSignature>,
all_ids: Vec<&'static str>,
}
impl CategoryCatalogWrapper {
fn new(
category: IndicatorCategory,
get_fn: fn(&str) -> Option<IndicatorSignature>,
all_ids: Vec<&'static str>,
) -> Self {
Self { category, get_fn, all_ids }
}
}
impl CatalogProvider for CategoryCatalogWrapper {
fn get_signature(&self, id: &str) -> Option<IndicatorSignature> {
(self.get_fn)(id)
}
fn get_all_ids(&self) -> Vec<&'static str> {
self.all_ids.clone()
}
fn category(&self) -> IndicatorCategory {
self.category
}
fn count(&self) -> usize {
self.all_ids.len()
}
}
pub struct MasterIndicatorCatalog {
catalogs: HashMap<IndicatorCategory, Box<dyn CatalogProvider>>,
id_to_categories: HashMap<String, Vec<IndicatorCategory>>,
total_count: usize,
}
impl MasterIndicatorCatalog {
pub fn new() -> Self {
let mut catalogs: HashMap<IndicatorCategory, Box<dyn CatalogProvider>> = HashMap::new();
let mut id_to_categories: HashMap<String, Vec<IndicatorCategory>> = HashMap::new();
let catalog_configs = vec![
(IndicatorCategory::Average, average_catalog::get_signature as fn(&str) -> Option<IndicatorSignature>, average_catalog::all_indicator_ids()),
(IndicatorCategory::Momentum, momentum_catalog::get_signature, momentum_catalog::all_indicator_ids()),
(IndicatorCategory::Channels, channels_catalog::get_signature, channels_catalog::all_indicator_ids()),
(IndicatorCategory::Volatility, volatility_catalog::get_signature, volatility_catalog::all_indicator_ids()),
(IndicatorCategory::Volume, volume_catalog::get_signature, volume_catalog::all_indicator_ids()),
(IndicatorCategory::Trend, trend_catalog::get_signature, trend_catalog::all_indicator_ids()),
(IndicatorCategory::Levels, levels_catalog::get_signature, levels_catalog::all_indicator_ids()),
(IndicatorCategory::Entropy, entropy_catalog::get_signature, entropy_catalog::all_indicator_ids()),
(IndicatorCategory::Kalman, kalman_catalog::get_signature, kalman_catalog::all_indicator_ids()),
(IndicatorCategory::SignalProcessing, signal_processing_catalog::get_signature, signal_processing_catalog::all_indicator_ids()),
(IndicatorCategory::Chaos, chaos_catalog::get_signature, chaos_catalog::all_indicator_ids()),
(IndicatorCategory::Regression, regression_catalog::get_signature, regression_catalog::all_indicator_ids()),
(IndicatorCategory::Adaptive, adaptive_catalog::get_signature, adaptive_catalog::all_indicator_ids()),
(IndicatorCategory::Accumulation, accumulation_catalog::get_signature, accumulation_catalog::all_indicator_ids()),
(IndicatorCategory::Book, book_catalog::get_signature, book_catalog::all_indicator_ids()),
(IndicatorCategory::Candles, candles_catalog::get_signature, candles_catalog::all_indicator_ids()),
(IndicatorCategory::Clusters, clusters_catalog::get_signature, clusters_catalog::all_indicator_ids()),
(IndicatorCategory::Divergence, divergence_catalog::get_signature, divergence_catalog::all_indicator_ids()),
(IndicatorCategory::Ratio, ratio_catalog::get_signature, ratio_catalog::all_indicator_ids()),
(IndicatorCategory::TrendStop, trend_stop_catalog::get_signature, trend_stop_catalog::all_indicator_ids()),
(IndicatorCategory::Position, position_catalog::get_signature, position_catalog::all_indicator_ids()),
(IndicatorCategory::Statistics, statistics_catalog::get_signature, statistics_catalog::all_indicator_ids()),
(IndicatorCategory::StatisticalScoring, statistical_scoring_catalog::get_signature, statistical_scoring_catalog::all_indicator_ids()),
(IndicatorCategory::FundingAdvanced, funding_advanced_catalog::get_signature, funding_advanced_catalog::all_indicator_ids()),
(IndicatorCategory::OpenInterest, open_interest_catalog::get_signature, open_interest_catalog::all_indicator_ids()),
(IndicatorCategory::MarkPriceAdvanced, mark_price_advanced_catalog::get_signature, mark_price_advanced_catalog::all_indicator_ids()),
(IndicatorCategory::TickerAdvanced, ticker_advanced_catalog::get_signature, ticker_advanced_catalog::all_indicator_ids()),
(IndicatorCategory::Liquidations, liquidations_catalog::get_signature, liquidations_catalog::all_indicator_ids()),
(IndicatorCategory::TickAdvanced, tick_advanced_catalog::get_signature, tick_advanced_catalog::all_indicator_ids()),
(IndicatorCategory::BookAdvanced, book_advanced_catalog::get_signature, book_advanced_catalog::all_indicator_ids()),
(IndicatorCategory::Composites, composites_catalog::get_signature, composites_catalog::all_indicator_ids()),
(IndicatorCategory::Sentiment, sentiment_catalog::get_signature, sentiment_catalog::all_indicator_ids()),
(IndicatorCategory::IndexBasis, index_basis_catalog::get_signature, index_basis_catalog::all_indicator_ids()),
(IndicatorCategory::VolatilityAdvanced, volatility_advanced_catalog::get_signature, volatility_advanced_catalog::all_indicator_ids()),
(IndicatorCategory::Greeks, greeks_catalog::get_signature, greeks_catalog::all_indicator_ids()),
(IndicatorCategory::Stress, stress_catalog::get_signature, stress_catalog::all_indicator_ids()),
(IndicatorCategory::Microstructure, microstructure_catalog::get_signature, microstructure_catalog::all_indicator_ids()),
(IndicatorCategory::RiskFunding, risk_funding_catalog::get_signature, risk_funding_catalog::all_indicator_ids()),
];
let mut total_count = 0;
for (category, get_fn, ids) in catalog_configs {
total_count += ids.len();
for &id in &ids {
id_to_categories
.entry(id.to_string())
.or_default()
.push(category);
if let Some(sig) = get_fn(id) {
for alias in &sig.aliases {
id_to_categories
.entry(alias.clone())
.or_default()
.push(category);
}
}
}
let wrapper = CategoryCatalogWrapper::new(category, get_fn, ids);
catalogs.insert(category, Box::new(wrapper));
}
Self {
catalogs,
id_to_categories,
total_count,
}
}
pub fn get_signature(&self, id: &str) -> Result<IndicatorSignature, CatalogError> {
match self.id_to_categories.get(id) {
None => Err(CatalogError::NotFound(id.to_string())),
Some(categories) if categories.len() > 1 => {
Err(CatalogError::Ambiguous(id.to_string(), categories.clone()))
}
Some(categories) => {
let category = categories[0];
self.get_by_category(category, id)
}
}
}
pub fn get_by_category(&self, category: IndicatorCategory, id: &str) -> Result<IndicatorSignature, CatalogError> {
let catalog = self.catalogs
.get(&category)
.ok_or(CatalogError::CategoryNotFound(category))?;
catalog.get_signature(id)
.ok_or_else(|| CatalogError::NotFound(format!("{}::{}", category.as_str(), id)))
}
pub fn get_category_indicators(&self, category: IndicatorCategory) -> Result<Vec<IndicatorSignature>, CatalogError> {
let catalog = self.catalogs
.get(&category)
.ok_or(CatalogError::CategoryNotFound(category))?;
Ok(catalog.get_all_ids()
.into_iter()
.filter_map(|id| catalog.get_signature(id))
.collect())
}
pub fn search(&self, query: &str) -> Vec<IndicatorSignature> {
let query_lower = query.to_lowercase();
let mut results = Vec::new();
for catalog in self.catalogs.values() {
for id in catalog.get_all_ids() {
if let Some(sig) = catalog.get_signature(id) {
let matches = sig.id.to_lowercase().contains(&query_lower)
|| sig.name.to_lowercase().contains(&query_lower)
|| sig.metadata.get("description")
.map(|d| d.to_lowercase().contains(&query_lower))
.unwrap_or(false);
if matches {
results.push(sig);
}
}
}
}
results
}
pub fn contains(&self, id: &str) -> bool {
self.id_to_categories.contains_key(id)
}
pub fn total_count(&self) -> usize {
self.total_count
}
pub fn category_count(&self, category: IndicatorCategory) -> usize {
self.catalogs
.get(&category)
.map(|c| c.count())
.unwrap_or(0)
}
pub fn iter_signatures(&self) -> impl Iterator<Item = IndicatorSignature> + '_ {
self.catalogs.values().flat_map(|catalog| {
catalog
.get_all_ids()
.into_iter()
.filter_map(|id| catalog.get_signature(id))
})
}
pub fn stats(&self) -> CatalogStats {
CatalogStats {
total_indicators: self.total_count,
total_categories: self.catalogs.len(),
category_counts: self.catalogs
.iter()
.map(|(cat, catalog)| (*cat, catalog.count()))
.collect(),
}
}
}
impl Default for MasterIndicatorCatalog {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CatalogStats {
pub total_indicators: usize,
pub total_categories: usize,
pub category_counts: HashMap<IndicatorCategory, usize>,
}
impl CatalogStats {
pub fn report(&self) -> String {
let mut report = String::new();
report.push_str("═══════════════════════════════════════════════════════════\n");
report.push_str(" MASTER INDICATOR CATALOG STATISTICS\n");
report.push_str("═══════════════════════════════════════════════════════════\n\n");
report.push_str(&format!("Total Categories: {}\n", self.total_categories));
report.push_str(&format!("Total Indicators: {}\n\n", self.total_indicators));
report.push_str("Category Breakdown:\n");
report.push_str("───────────────────────────────────────────────────────────\n");
let mut sorted: Vec<_> = self.category_counts.iter().collect();
sorted.sort_by_key(|(cat, _)| cat.as_str());
for (category, count) in sorted {
report.push_str(&format!(" {:20} {:3} indicators\n", category.as_str(), count));
}
report.push_str("═══════════════════════════════════════════════════════════\n");
report
}
}
pub static MASTER_CATALOG: Lazy<MasterIndicatorCatalog> = Lazy::new(MasterIndicatorCatalog::new);
pub fn catalog() -> &'static MasterIndicatorCatalog {
&MASTER_CATALOG
}
pub fn get_signature(id: &str) -> Option<IndicatorSignature> {
MASTER_CATALOG.get_signature(id).ok()
}
pub fn all_signatures() -> impl Iterator<Item = IndicatorSignature> {
MASTER_CATALOG.iter_signatures()
}
pub fn indicator_granularity(
id: crate::bar_indicators::bar_indicator_id::BarIndicatorId,
) -> crate::bar_indicators::granularity::GranularitySpec {
use crate::bar_indicators::granularity::{canonical_max_config, config_granularity};
config_granularity(&canonical_max_config(id))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_master_catalog_creation() {
let master = MasterIndicatorCatalog::new();
assert!(master.total_count() > 430, "Should have 430+ indicators");
}
#[test]
fn test_get_signature_unambiguous() {
let master = MasterIndicatorCatalog::new();
let sma = master.get_signature("SMA");
assert!(sma.is_ok(), "SMA should be found");
assert_eq!(sma.unwrap().id, "SMA");
}
#[test]
fn test_get_by_category() {
let master = MasterIndicatorCatalog::new();
let rsi = master.get_by_category(IndicatorCategory::Momentum, "RSI");
assert!(rsi.is_ok(), "RSI should be found in Momentum");
assert_eq!(rsi.unwrap().category, IndicatorCategory::Momentum);
}
#[test]
fn test_get_category_indicators() {
let master = MasterIndicatorCatalog::new();
let avg_indicators = master.get_category_indicators(IndicatorCategory::Average);
assert!(avg_indicators.is_ok());
let indicators = avg_indicators.unwrap();
assert!(indicators.len() >= 20, "Average category should have 20+ indicators");
for ind in indicators {
assert_eq!(ind.category, IndicatorCategory::Average);
}
}
#[test]
fn test_search() {
let master = MasterIndicatorCatalog::new();
let results = master.search("moving average");
assert!(!results.is_empty(), "Should find moving average indicators");
let rsi_results = master.search("rsi");
assert!(!rsi_results.is_empty(), "Should find RSI-related indicators");
}
#[test]
fn test_contains() {
let master = MasterIndicatorCatalog::new();
assert!(master.contains("SMA"), "Should contain SMA");
assert!(master.contains("RSI"), "Should contain RSI");
assert!(!master.contains("NONEXISTENT"), "Should not contain fake indicator");
}
#[test]
fn test_category_count() {
let master = MasterIndicatorCatalog::new();
let avg_count = master.category_count(IndicatorCategory::Average);
assert!(avg_count >= 20, "Average category should have 20+ indicators");
let momentum_count = master.category_count(IndicatorCategory::Momentum);
assert!(momentum_count >= 50, "Momentum category should have 50+ indicators");
}
#[test]
fn test_stats() {
let master = MasterIndicatorCatalog::new();
let stats = master.stats();
assert!(stats.total_categories >= 23, "expected 23+ categories, got {}", stats.total_categories);
assert_eq!(stats.category_counts.len(), stats.total_categories);
assert!(stats.total_indicators > 430);
}
#[test]
fn test_global_catalog() {
let master = catalog();
assert!(master.total_count() > 430);
assert!(master.contains("SMA"));
assert!(master.contains("RSI"));
}
#[test]
fn test_not_found_error() {
let master = MasterIndicatorCatalog::new();
let result = master.get_signature("FAKE_INDICATOR");
assert!(result.is_err());
match result {
Err(CatalogError::NotFound(id)) => {
assert_eq!(id, "FAKE_INDICATOR");
}
_ => panic!("Expected NotFound error"),
}
}
}