tradusai/analysis/engine.py
2025-12-02 22:54:03 +08:00

267 lines
8.7 KiB
Python

"""
Main Market Analysis Engine - Orchestrates all analysis components
"""
import logging
from typing import Dict, Any, Optional
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
logger = logging.getLogger(__name__)
class MarketAnalysisEngine:
"""
Main analysis engine that orchestrates all market analysis components
"""
def __init__(self):
self.data_reader = MarketDataReader()
self.llm_builder = LLMContextBuilder()
def analyze_current_market(
self, timeframe: str = '5m', symbol: str = 'BTCUSDT'
) -> Dict[str, Any]:
"""
Perform complete market analysis for current state
Args:
timeframe: Primary timeframe for analysis (5m, 15m, 1h, 4h)
symbol: Trading symbol
Returns:
Complete analysis dictionary
"""
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
"""
# Map timeframe to stream key
stream_key = f"binance:raw:kline:{timeframe}"
# Fetch data
df = self.data_reader.read_kline_stream(stream_key)
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 in Redis
Returns:
Dict with data availability status
"""
status = {
'klines': {},
'depth': False,
'trades': False,
}
# Check kline streams
for tf in ['5m', '15m', '1h', '4h']:
stream_key = f"binance:raw:kline:{tf}"
df = self.data_reader.read_kline_stream(stream_key, count=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'