361 lines
11 KiB
Python
361 lines
11 KiB
Python
"""
|
|
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)
|