""" Mapping layer between TA-Lib indicators and TradingView indicators. This module provides bidirectional conversion between our internal TA-Lib-based indicator representation and TradingView's indicator system. """ from typing import Dict, Any, Optional, Tuple, List import logging logger = logging.getLogger(__name__) # Mapping of TA-Lib indicator names to TradingView indicator names # Only includes indicators that are present in BOTH systems (inner join) # Format: {talib_name: tv_name} TALIB_TO_TV_NAMES = { # Overlap Studies (14) "SMA": "Moving Average", "EMA": "Moving Average Exponential", "WMA": "Weighted Moving Average", "DEMA": "DEMA", "TEMA": "TEMA", "TRIMA": "Triangular Moving Average", "KAMA": "KAMA", "MAMA": "MESA Adaptive Moving Average", "T3": "T3", "BBANDS": "Bollinger Bands", "MIDPOINT": "Midpoint", "MIDPRICE": "Midprice", "SAR": "Parabolic SAR", "HT_TRENDLINE": "Hilbert Transform - Instantaneous Trendline", # Momentum Indicators (21) "RSI": "Relative Strength Index", "MOM": "Momentum", "ROC": "Rate of Change", "TRIX": "TRIX", "CMO": "Chande Momentum Oscillator", "DX": "Directional Movement Index", "ADX": "Average Directional Movement Index", "ADXR": "Average Directional Movement Index Rating", "APO": "Absolute Price Oscillator", "PPO": "Percentage Price Oscillator", "MACD": "MACD", "MFI": "Money Flow Index", "STOCH": "Stochastic", "STOCHF": "Stochastic Fast", "STOCHRSI": "Stochastic RSI", "WILLR": "Williams %R", "CCI": "Commodity Channel Index", "AROON": "Aroon", "AROONOSC": "Aroon Oscillator", "BOP": "Balance Of Power", "ULTOSC": "Ultimate Oscillator", # Volume Indicators (3) "AD": "Chaikin A/D Line", "ADOSC": "Chaikin A/D Oscillator", "OBV": "On Balance Volume", # Volatility Indicators (3) "ATR": "Average True Range", "NATR": "Normalized Average True Range", "TRANGE": "True Range", # Price Transform (4) "AVGPRICE": "Average Price", "MEDPRICE": "Median Price", "TYPPRICE": "Typical Price", "WCLPRICE": "Weighted Close Price", # Cycle Indicators (5) "HT_DCPERIOD": "Hilbert Transform - Dominant Cycle Period", "HT_DCPHASE": "Hilbert Transform - Dominant Cycle Phase", "HT_PHASOR": "Hilbert Transform - Phasor Components", "HT_SINE": "Hilbert Transform - SineWave", "HT_TRENDMODE": "Hilbert Transform - Trend vs Cycle Mode", # Statistic Functions (9) "BETA": "Beta", "CORREL": "Pearson's Correlation Coefficient", "LINEARREG": "Linear Regression", "LINEARREG_ANGLE": "Linear Regression Angle", "LINEARREG_INTERCEPT": "Linear Regression Intercept", "LINEARREG_SLOPE": "Linear Regression Slope", "STDDEV": "Standard Deviation", "TSF": "Time Series Forecast", "VAR": "Variance", } # Total: 60 indicators supported in both systems # Custom indicators (TradingView indicators implemented in our backend) CUSTOM_TO_TV_NAMES = { "VWAP": "VWAP", "VWMA": "VWMA", "HMA": "Hull Moving Average", "SUPERTREND": "SuperTrend", "DONCHIAN": "Donchian Channels", "KELTNER": "Keltner Channels", "CMF": "Chaikin Money Flow", "VORTEX": "Vortex Indicator", "AO": "Awesome Oscillator", "AC": "Accelerator Oscillator", "CHOP": "Choppiness Index", "MASS": "Mass Index", } # Combined mapping (TA-Lib + Custom) ALL_BACKEND_TO_TV_NAMES = {**TALIB_TO_TV_NAMES, **CUSTOM_TO_TV_NAMES} # Total: 72 indicators (60 TA-Lib + 12 Custom) # Reverse mapping TV_TO_TALIB_NAMES = {v: k for k, v in TALIB_TO_TV_NAMES.items()} TV_TO_CUSTOM_NAMES = {v: k for k, v in CUSTOM_TO_TV_NAMES.items()} TV_TO_BACKEND_NAMES = {v: k for k, v in ALL_BACKEND_TO_TV_NAMES.items()} def get_tv_indicator_name(talib_name: str) -> str: """ Convert TA-Lib indicator name to TradingView indicator name. Args: talib_name: TA-Lib indicator name (e.g., 'RSI') Returns: TradingView indicator name """ return TALIB_TO_TV_NAMES.get(talib_name, talib_name) def get_talib_indicator_name(tv_name: str) -> Optional[str]: """ Convert TradingView indicator name to TA-Lib indicator name. Args: tv_name: TradingView indicator name Returns: TA-Lib indicator name or None if not mapped """ return TV_TO_TALIB_NAMES.get(tv_name) def convert_talib_params_to_tv_inputs( talib_name: str, talib_params: Dict[str, Any] ) -> Dict[str, Any]: """ Convert TA-Lib parameters to TradingView input format. Args: talib_name: TA-Lib indicator name talib_params: TA-Lib parameter dictionary Returns: TradingView inputs dictionary """ tv_inputs = {} # Common parameter mappings param_mapping = { "timeperiod": "length", "fastperiod": "fastLength", "slowperiod": "slowLength", "signalperiod": "signalLength", "nbdevup": "mult", # Standard deviations for upper band "nbdevdn": "mult", # Standard deviations for lower band "fastlimit": "fastLimit", "slowlimit": "slowLimit", "acceleration": "start", "maximum": "increment", "fastk_period": "kPeriod", "slowk_period": "kPeriod", "slowd_period": "dPeriod", "fastd_period": "dPeriod", "matype": "maType", } # Special handling for specific indicators if talib_name == "BBANDS": # Bollinger Bands tv_inputs["length"] = talib_params.get("timeperiod", 20) tv_inputs["mult"] = talib_params.get("nbdevup", 2) tv_inputs["source"] = "close" elif talib_name == "MACD": # MACD tv_inputs["fastLength"] = talib_params.get("fastperiod", 12) tv_inputs["slowLength"] = talib_params.get("slowperiod", 26) tv_inputs["signalLength"] = talib_params.get("signalperiod", 9) tv_inputs["source"] = "close" elif talib_name == "RSI": # RSI tv_inputs["length"] = talib_params.get("timeperiod", 14) tv_inputs["source"] = "close" elif talib_name in ["SMA", "EMA", "WMA", "DEMA", "TEMA", "TRIMA"]: # Moving averages tv_inputs["length"] = talib_params.get("timeperiod", 14) tv_inputs["source"] = "close" elif talib_name == "STOCH": # Stochastic tv_inputs["kPeriod"] = talib_params.get("fastk_period", 14) tv_inputs["dPeriod"] = talib_params.get("slowd_period", 3) tv_inputs["smoothK"] = talib_params.get("slowk_period", 3) elif talib_name == "ATR": # ATR tv_inputs["length"] = talib_params.get("timeperiod", 14) elif talib_name == "CCI": # CCI tv_inputs["length"] = talib_params.get("timeperiod", 20) else: # Generic parameter conversion for talib_param, value in talib_params.items(): tv_param = param_mapping.get(talib_param, talib_param) tv_inputs[tv_param] = value logger.debug(f"Converted TA-Lib params for {talib_name}: {talib_params} -> TV inputs: {tv_inputs}") return tv_inputs def convert_tv_inputs_to_talib_params( tv_name: str, tv_inputs: Dict[str, Any] ) -> Tuple[Optional[str], Dict[str, Any]]: """ Convert TradingView inputs to TA-Lib parameters. Args: tv_name: TradingView indicator name tv_inputs: TradingView inputs dictionary Returns: Tuple of (talib_name, talib_params) """ talib_name = get_talib_indicator_name(tv_name) if not talib_name: logger.warning(f"No TA-Lib mapping for TradingView indicator: {tv_name}") return None, {} talib_params = {} # Reverse parameter mappings reverse_mapping = { "length": "timeperiod", "fastLength": "fastperiod", "slowLength": "slowperiod", "signalLength": "signalperiod", "mult": "nbdevup", # Use same for both up and down "fastLimit": "fastlimit", "slowLimit": "slowlimit", "start": "acceleration", "increment": "maximum", "kPeriod": "fastk_period", "dPeriod": "slowd_period", "smoothK": "slowk_period", "maType": "matype", } # Special handling for specific indicators if talib_name == "BBANDS": # Bollinger Bands talib_params["timeperiod"] = tv_inputs.get("length", 20) talib_params["nbdevup"] = tv_inputs.get("mult", 2) talib_params["nbdevdn"] = tv_inputs.get("mult", 2) talib_params["matype"] = 0 # SMA elif talib_name == "MACD": # MACD talib_params["fastperiod"] = tv_inputs.get("fastLength", 12) talib_params["slowperiod"] = tv_inputs.get("slowLength", 26) talib_params["signalperiod"] = tv_inputs.get("signalLength", 9) elif talib_name == "RSI": # RSI talib_params["timeperiod"] = tv_inputs.get("length", 14) elif talib_name in ["SMA", "EMA", "WMA", "DEMA", "TEMA", "TRIMA"]: # Moving averages talib_params["timeperiod"] = tv_inputs.get("length", 14) elif talib_name == "STOCH": # Stochastic talib_params["fastk_period"] = tv_inputs.get("kPeriod", 14) talib_params["slowd_period"] = tv_inputs.get("dPeriod", 3) talib_params["slowk_period"] = tv_inputs.get("smoothK", 3) talib_params["slowk_matype"] = 0 # SMA talib_params["slowd_matype"] = 0 # SMA elif talib_name == "ATR": # ATR talib_params["timeperiod"] = tv_inputs.get("length", 14) elif talib_name == "CCI": # CCI talib_params["timeperiod"] = tv_inputs.get("length", 20) else: # Generic parameter conversion for tv_param, value in tv_inputs.items(): if tv_param == "source": continue # Skip source parameter talib_param = reverse_mapping.get(tv_param, tv_param) talib_params[talib_param] = value logger.debug(f"Converted TV inputs for {tv_name}: {tv_inputs} -> TA-Lib {talib_name} params: {talib_params}") return talib_name, talib_params def is_indicator_supported(talib_name: str) -> bool: """ Check if a TA-Lib indicator is supported in TradingView. Args: talib_name: TA-Lib indicator name Returns: True if supported """ return talib_name in TALIB_TO_TV_NAMES def get_supported_indicators() -> List[str]: """ Get list of supported TA-Lib indicators. Returns: List of TA-Lib indicator names """ return list(TALIB_TO_TV_NAMES.keys()) def get_supported_indicator_count() -> int: """ Get count of supported indicators. Returns: Number of indicators supported in both systems (TA-Lib + Custom) """ return len(ALL_BACKEND_TO_TV_NAMES) def is_custom_indicator(indicator_name: str) -> bool: """ Check if an indicator is a custom implementation (not TA-Lib). Args: indicator_name: Indicator name Returns: True if custom indicator """ return indicator_name in CUSTOM_TO_TV_NAMES def get_backend_indicator_name(tv_name: str) -> Optional[str]: """ Get backend indicator name from TradingView name (TA-Lib or custom). Args: tv_name: TradingView indicator name Returns: Backend indicator name or None if not mapped """ return TV_TO_BACKEND_NAMES.get(tv_name)