""" Main Market Analysis Engine - Orchestrates all analysis components Pure API mode - no Redis dependency """ import logging from typing import Dict, Any import pandas as pd from .data_reader import MarketDataReader from .indicators import TechnicalIndicators from .market_structure import MarketStructureAnalyzer from .orderflow import OrderFlowAnalyzer from .llm_context import LLMContextBuilder from .config import config logger = logging.getLogger(__name__) class MarketAnalysisEngine: """ Main analysis engine that orchestrates all market analysis components """ def __init__(self, symbol: str = None): """ Initialize analysis engine Args: symbol: Trading symbol (default from config) """ self.symbol = symbol or config.SYMBOL self.data_reader = MarketDataReader(symbol=self.symbol) self.llm_builder = LLMContextBuilder() def analyze_current_market( self, timeframe: str = '5m', symbol: str = None ) -> Dict[str, Any]: """ Perform complete market analysis for current state Args: timeframe: Primary timeframe for analysis (5m, 15m, 1h, 4h) symbol: Trading symbol (uses default if None) Returns: Complete analysis dictionary """ if symbol is None: symbol = self.symbol try: logger.info(f"Starting market analysis for {symbol} on {timeframe}") # Fetch data df = self._fetch_and_prepare_data(timeframe) if df.empty: logger.error(f"No data available for {timeframe}") return {'error': 'No data available'} # Get current price current_price = float(df.iloc[-1]['close']) # Fetch order book depth_data = self.data_reader.read_latest_depth() # Perform analysis components analysis = { 'symbol': symbol, 'timeframe': timeframe, 'current_price': round(current_price, 2), 'timestamp': df.index[-1].isoformat(), 'trend_analysis': MarketStructureAnalyzer.identify_trend(df), 'support_resistance': MarketStructureAnalyzer.find_support_resistance( df, current_price ), 'momentum': MarketStructureAnalyzer.calculate_momentum(df), 'indicators': TechnicalIndicators.get_latest_indicators(df), 'price_changes': TechnicalIndicators.calculate_price_changes(df), } # Add order flow if depth data available if depth_data: analysis['orderflow'] = { 'imbalance': OrderFlowAnalyzer.analyze_orderbook_imbalance(depth_data), 'liquidity': OrderFlowAnalyzer.analyze_liquidity_depth( depth_data, current_price ), 'large_orders': OrderFlowAnalyzer.detect_large_orders(depth_data), } # Calculate order flow strength analysis['orderflow']['strength'] = OrderFlowAnalyzer.calculate_orderflow_strength( analysis['orderflow']['imbalance'], analysis['orderflow']['large_orders'], analysis['orderflow']['liquidity'], ) # Add breakout detection analysis['breakout'] = MarketStructureAnalyzer.detect_breakout( df, analysis['support_resistance'] ) # Add volatility and volume analysis for LLM gate analysis['volatility_analysis'] = { 'atr': analysis['indicators'].get('atr', 0), 'atr_pct': (analysis['indicators'].get('atr', 0) / current_price * 100) if current_price > 0 else 0, 'bb_status': self._get_bb_status(df), } analysis['volume_analysis'] = { 'current_volume': float(df.iloc[-1]['volume']), 'avg_volume': float(df['volume'].tail(20).mean()), 'volume_status': self._get_volume_status(df), 'obv_trend': self._get_obv_trend(df), } # Add metadata for LLM gate analysis['metadata'] = { 'candle_count': len(df), 'timeframe': timeframe, 'analysis_timestamp': df.index[-1].isoformat(), } logger.info( f"Analysis complete: trend={analysis['trend_analysis']['direction']}, " f"rsi={analysis['momentum']['rsi']}, " f"candles={len(df)}" ) return analysis except Exception as e: logger.error(f"Error in market analysis: {e}", exc_info=True) return {'error': str(e)} def get_llm_context(self, format: str = 'full') -> Dict[str, Any]: """ Get market context formatted for LLM consumption Args: format: 'full' or 'simplified' Returns: LLM-ready context dictionary """ if format == 'simplified': return self.llm_builder.get_simplified_context() else: return self.llm_builder.build_full_context() def get_multi_timeframe_analysis(self) -> Dict[str, Any]: """ Get analysis across all timeframes Returns: Dict mapping timeframe to analysis """ timeframes = ['5m', '15m', '1h', '4h'] results = {} for tf in timeframes: analysis = self.analyze_current_market(timeframe=tf) if 'error' not in analysis: results[tf] = { 'trend': analysis['trend_analysis'].get('direction', 'unknown'), 'strength': analysis['trend_analysis'].get('strength', 'weak'), 'rsi': analysis['momentum'].get('rsi', 50), 'adx': analysis['trend_analysis'].get('adx', 0), } return results def _fetch_and_prepare_data(self, timeframe: str) -> pd.DataFrame: """ Fetch data and add all technical indicators Args: timeframe: Timeframe to fetch (5m, 15m, 1h, 4h) Returns: DataFrame with OHLCV and indicators """ # Fetch data directly from API df = self.data_reader.fetch_klines(interval=timeframe, limit=config.LOOKBACK_PERIODS) if df.empty: return df # Add all technical indicators df = TechnicalIndicators.add_all_indicators(df) return df def check_data_availability(self) -> Dict[str, Any]: """ Check what data is available from API Returns: Dict with data availability status """ status = { 'klines': {}, 'depth': False, 'trades': False, } # Check kline data for each timeframe for tf in ['5m', '15m', '1h', '4h']: df = self.data_reader.fetch_klines(interval=tf, limit=1) status['klines'][tf] = { 'available': not df.empty, 'latest': df.index[-1].isoformat() if not df.empty else None, } # Check depth depth = self.data_reader.read_latest_depth() status['depth'] = depth is not None # Check trades trades = self.data_reader.read_recent_trades(count=1) status['trades'] = len(trades) > 0 return status def _get_bb_status(self, df: pd.DataFrame) -> str: """Get Bollinger Bands status""" if 'bb_upper' not in df.columns or 'bb_lower' not in df.columns: return 'unknown' last_close = df.iloc[-1]['close'] bb_upper = df.iloc[-1]['bb_upper'] bb_lower = df.iloc[-1]['bb_lower'] bb_middle = df.iloc[-1].get('bb_middle', (bb_upper + bb_lower) / 2) if last_close > bb_upper: return 'overbought' elif last_close < bb_lower: return 'oversold' elif last_close > bb_middle: return 'upper_half' else: return 'lower_half' def _get_volume_status(self, df: pd.DataFrame) -> str: """Get volume status compared to average""" if len(df) < 20: return 'unknown' current_volume = df.iloc[-1]['volume'] avg_volume = df['volume'].tail(20).mean() if current_volume > avg_volume * 1.5: return 'high' elif current_volume > avg_volume * 0.8: return 'normal' else: return 'low' def _get_obv_trend(self, df: pd.DataFrame) -> str: """Get OBV (On-Balance Volume) trend""" if 'obv' not in df.columns or len(df) < 20: return 'unknown' obv_current = df.iloc[-1]['obv'] obv_sma = df['obv'].tail(20).mean() if obv_current > obv_sma * 1.05: return 'bullish' elif obv_current < obv_sma * 0.95: return 'bearish' else: return 'neutral'