225 lines
6.6 KiB
Python
225 lines
6.6 KiB
Python
"""
|
|
Technical indicator calculation engine
|
|
"""
|
|
import logging
|
|
import pandas as pd
|
|
import numpy as np
|
|
from ta import trend, momentum, volatility, volume
|
|
|
|
from .config import config
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TechnicalIndicators:
|
|
"""Calculate technical indicators for market analysis"""
|
|
|
|
@staticmethod
|
|
def add_all_indicators(df: pd.DataFrame) -> pd.DataFrame:
|
|
"""
|
|
Add all technical indicators to DataFrame
|
|
|
|
Args:
|
|
df: DataFrame with OHLCV data
|
|
|
|
Returns:
|
|
DataFrame with all indicators added
|
|
"""
|
|
# Minimum data needed for indicators is ~60 candles
|
|
# (based on EMA_SLOW=50 + some buffer)
|
|
MIN_CANDLES = 60
|
|
if df.empty or len(df) < MIN_CANDLES:
|
|
logger.warning(f"Insufficient data for indicators: {len(df)} candles (min: {MIN_CANDLES})")
|
|
return df
|
|
|
|
df = df.copy()
|
|
|
|
# Trend indicators
|
|
df = TechnicalIndicators.add_trend_indicators(df)
|
|
|
|
# Momentum indicators
|
|
df = TechnicalIndicators.add_momentum_indicators(df)
|
|
|
|
# Volatility indicators
|
|
df = TechnicalIndicators.add_volatility_indicators(df)
|
|
|
|
# Volume indicators
|
|
df = TechnicalIndicators.add_volume_indicators(df)
|
|
|
|
return df
|
|
|
|
@staticmethod
|
|
def add_trend_indicators(df: pd.DataFrame) -> pd.DataFrame:
|
|
"""Add trend-following indicators"""
|
|
|
|
# EMAs
|
|
df[f'ema_{config.EMA_FAST}'] = trend.EMAIndicator(
|
|
df['close'], window=config.EMA_FAST
|
|
).ema_indicator()
|
|
df[f'ema_{config.EMA_SLOW}'] = trend.EMAIndicator(
|
|
df['close'], window=config.EMA_SLOW
|
|
).ema_indicator()
|
|
|
|
# MACD
|
|
macd = trend.MACD(
|
|
df['close'],
|
|
window_slow=config.MACD_SLOW,
|
|
window_fast=config.MACD_FAST,
|
|
window_sign=config.MACD_SIGNAL
|
|
)
|
|
df['macd'] = macd.macd()
|
|
df['macd_signal'] = macd.macd_signal()
|
|
df['macd_hist'] = macd.macd_diff()
|
|
|
|
# ADX (trend strength)
|
|
adx = trend.ADXIndicator(
|
|
df['high'], df['low'], df['close'], window=config.ADX_PERIOD
|
|
)
|
|
df['adx'] = adx.adx()
|
|
df['dmp'] = adx.adx_pos() # +DI
|
|
df['dmn'] = adx.adx_neg() # -DI
|
|
|
|
return df
|
|
|
|
@staticmethod
|
|
def add_momentum_indicators(df: pd.DataFrame) -> pd.DataFrame:
|
|
"""Add momentum indicators"""
|
|
|
|
# RSI
|
|
df['rsi'] = momentum.RSIIndicator(
|
|
df['close'], window=config.RSI_PERIOD
|
|
).rsi()
|
|
|
|
# Stochastic
|
|
stoch = momentum.StochasticOscillator(
|
|
df['high'],
|
|
df['low'],
|
|
df['close'],
|
|
window=14,
|
|
smooth_window=3
|
|
)
|
|
df['stoch_k'] = stoch.stoch()
|
|
df['stoch_d'] = stoch.stoch_signal()
|
|
|
|
# Williams %R
|
|
df['willr'] = momentum.WilliamsRIndicator(
|
|
df['high'], df['low'], df['close'], lbp=14
|
|
).williams_r()
|
|
|
|
return df
|
|
|
|
@staticmethod
|
|
def add_volatility_indicators(df: pd.DataFrame) -> pd.DataFrame:
|
|
"""Add volatility indicators"""
|
|
|
|
# ATR (Average True Range)
|
|
df['atr'] = volatility.AverageTrueRange(
|
|
df['high'], df['low'], df['close'], window=config.ATR_PERIOD
|
|
).average_true_range()
|
|
|
|
# Bollinger Bands
|
|
bbands = volatility.BollingerBands(
|
|
df['close'],
|
|
window=config.BB_PERIOD,
|
|
window_dev=config.BB_STD
|
|
)
|
|
df['bb_upper'] = bbands.bollinger_hband()
|
|
df['bb_middle'] = bbands.bollinger_mavg()
|
|
df['bb_lower'] = bbands.bollinger_lband()
|
|
df['bb_width'] = bbands.bollinger_wband()
|
|
|
|
# Historical Volatility (20-period)
|
|
df['hist_vol'] = df['close'].pct_change().rolling(20).std() * np.sqrt(24 * 365) * 100
|
|
|
|
return df
|
|
|
|
@staticmethod
|
|
def add_volume_indicators(df: pd.DataFrame) -> pd.DataFrame:
|
|
"""Add volume-based indicators"""
|
|
|
|
# Volume SMA
|
|
df['volume_ma'] = df['volume'].rolling(window=config.VOLUME_MA_PERIOD).mean()
|
|
|
|
# Volume ratio
|
|
df['volume_ratio'] = df['volume'] / df['volume_ma']
|
|
|
|
# OBV (On-Balance Volume)
|
|
df['obv'] = volume.OnBalanceVolumeIndicator(
|
|
df['close'], df['volume']
|
|
).on_balance_volume()
|
|
|
|
# VWAP (Volume Weighted Average Price) - for intraday
|
|
if 'quote_volume' in df.columns:
|
|
df['vwap'] = (df['quote_volume'].cumsum() / df['volume'].cumsum())
|
|
|
|
return df
|
|
|
|
@staticmethod
|
|
def calculate_price_changes(df: pd.DataFrame) -> dict:
|
|
"""
|
|
Calculate price changes over various periods
|
|
|
|
Returns:
|
|
Dict with price change percentages
|
|
"""
|
|
if df.empty:
|
|
return {}
|
|
|
|
latest_close = df['close'].iloc[-1]
|
|
|
|
changes = {}
|
|
for periods, label in [(1, '1candle'), (5, '5candles'), (20, '20candles')]:
|
|
if len(df) > periods:
|
|
old_close = df['close'].iloc[-periods - 1]
|
|
change_pct = ((latest_close - old_close) / old_close) * 100
|
|
changes[label] = round(change_pct, 2)
|
|
|
|
return changes
|
|
|
|
@staticmethod
|
|
def get_latest_indicators(df: pd.DataFrame) -> dict:
|
|
"""
|
|
Extract latest indicator values for analysis
|
|
|
|
Returns:
|
|
Dict with latest indicator values
|
|
"""
|
|
if df.empty:
|
|
return {}
|
|
|
|
latest = df.iloc[-1]
|
|
|
|
indicators = {
|
|
# Trend
|
|
'ema_20': round(latest.get(f'ema_{config.EMA_FAST}', 0), 2),
|
|
'ema_50': round(latest.get(f'ema_{config.EMA_SLOW}', 0), 2),
|
|
'macd': round(latest.get('macd', 0), 4),
|
|
'macd_signal': round(latest.get('macd_signal', 0), 4),
|
|
'macd_hist': round(latest.get('macd_hist', 0), 4),
|
|
'adx': round(latest.get('adx', 0), 1),
|
|
|
|
# Momentum
|
|
'rsi': round(latest.get('rsi', 0), 1),
|
|
'stoch_k': round(latest.get('stoch_k', 0), 1),
|
|
'willr': round(latest.get('willr', 0), 1),
|
|
|
|
# Volatility
|
|
'atr': round(latest.get('atr', 0), 2),
|
|
'bb_upper': round(latest.get('bb_upper', 0), 2),
|
|
'bb_lower': round(latest.get('bb_lower', 0), 2),
|
|
'bb_width': round(latest.get('bb_width', 0), 4),
|
|
'hist_vol': round(latest.get('hist_vol', 0), 2),
|
|
|
|
# Volume
|
|
'volume_ratio': round(latest.get('volume_ratio', 0), 2),
|
|
'obv': int(latest.get('obv', 0)),
|
|
|
|
# Price
|
|
'close': round(latest['close'], 2),
|
|
'high': round(latest['high'], 2),
|
|
'low': round(latest['low'], 2),
|
|
}
|
|
|
|
return indicators
|