COMPREHENSIVE_BUG_FIX_PROMPT.md•35.7 kB
# 🔴 КОМПЛЕКСНОЕ ИСПРАВЛЕНИЕ КРИТИЧЕСКИХ ПРОБЛЕМ - ПОЛНАЯ ИНСТРУКЦИЯ
**Дата создания:** 2025-11-22
**Версия:** 1.0
**Статус:** ГОТОВО К ИСПОЛНЕНИЮ
---
## 📋 EXECUTIVE SUMMARY
Этот документ содержит **ПОЛНЫЕ РЕШЕНИЯ** для всех критических проблем, выявленных в логах системы `autonomous_agent.main`. Каждое решение включает:
- Детальный анализ проблемы
- Точное местоположение в коде
- Готовый код исправления
- Пояснения и best practices
**Общая стратегия исправлений:**
1. **Defensive Programming** - проверка данных перед использованием
2. **Graceful Degradation** - система работает даже при частичных ошибках
3. **Unified Naming** - единообразие в названиях полей
4. **Better Logging** - информативные логи для отладки
5. **Performance** - оптимизация без потери качества
---
## 🎯 ПРОБЛЕМА #1: RuntimeWarning: Mean of empty slice
### 📊 Анализ проблемы
**Частота:** Десятки раз в каждом анализе
**Критичность:** 🔴 ВЫСОКАЯ
**Влияние:**
- Засоряет логи предупреждениями
- Приводит к `NaN` значениям в индикаторах
- Потенциально некорректные расчеты
**Локации проблемы:**
```
mcp_server/technical_analysis.py:136 - df['volume'].tail(20).mean()
mcp_server/technical_analysis.py:233 - df['volume'].rolling(20).mean().iloc[-1]
mcp_server/technical_analysis.py:235 - df['volume'].iloc[-1] / df['volume'].rolling(20).mean().iloc[-1]
mcp_server/technical_analysis.py:697 - np.mean([abs(c['close'] - c['open']) for c in candles[i-5:i]])
mcp_server/technical_analysis.py:836 - np.mean([c['volume'] for c in prev_candles])
```
**Причина:**
- Недостаточно данных для расчета (менее 20 свечей для rolling mean)
- Пустые срезы при расчете среднего для паттернов
- Отсутствие проверки на пустые массивы перед `np.mean()`
### ✅ РЕШЕНИЕ
**Файл:** `mcp_server/technical_analysis.py`
#### 1. Добавить глобальную утилиту в начало файла:
```python
# После импортов, перед классом TechnicalAnalysis
def safe_mean(data, default=0.0):
"""
Безопасный расчет среднего с проверкой на пустые данные
Args:
data: Series, list или np.ndarray для расчета среднего
default: Значение по умолчанию если данные пустые или невалидные
Returns:
float: Среднее значение или default
"""
if data is None:
return default
# Для pandas Series
if hasattr(data, '__len__') and len(data) == 0:
return default
# Для numpy arrays или lists
try:
if isinstance(data, (list, np.ndarray)):
if len(data) == 0:
return default
result = np.mean(data)
else:
# Для pandas Series
result = float(data.mean())
# Проверка на NaN
if np.isnan(result):
return default
return float(result)
except Exception:
return default
def safe_rolling_mean(series, window, default=0.0):
"""
Безопасный расчет rolling mean с проверкой данных
Args:
series: pandas Series для расчета
window: Размер окна
default: Значение по умолчанию
Returns:
float: Rolling mean или default
"""
if series is None or len(series) == 0:
return default
if len(series) < window:
# Если данных меньше окна, используем все доступные
return safe_mean(series, default)
try:
result = float(series.rolling(window).mean().iloc[-1])
if np.isnan(result):
return default
return result
except Exception:
return default
```
#### 2. Исправить строку 136 (_analyze_timeframe):
```python
# БЫЛО:
"volume_avg": float(df['volume'].tail(20).mean())
# ДОЛЖНО БЫТЬ:
"volume_avg": safe_mean(df['volume'].tail(20))
```
#### 3. Исправить строки 233-236 (_calculate_all_indicators):
```python
# БЫЛО:
indicators['volume'] = {
'obv': float(ta.volume.on_balance_volume(df['close'], df['volume']).iloc[-1]),
'volume_sma': float(df['volume'].rolling(20).mean().iloc[-1]),
'current_volume': float(df['volume'].iloc[-1]),
'volume_ratio': float(df['volume'].iloc[-1] / df['volume'].rolling(20).mean().iloc[-1])
}
# ДОЛЖНО БЫТЬ:
volume_sma = safe_rolling_mean(df['volume'], 20, default=float(df['volume'].mean()) if len(df) > 0 else 0)
current_volume = float(df['volume'].iloc[-1]) if len(df) > 0 else 0
volume_ratio = (current_volume / volume_sma) if volume_sma > 0 else 1.0
indicators['volume'] = {
'obv': float(ta.volume.on_balance_volume(df['close'], df['volume']).iloc[-1]) if len(df) > 0 else 0,
'volume_sma': volume_sma,
'current_volume': current_volume,
'volume_ratio': volume_ratio
}
```
#### 4. Исправить строку 697 (find_order_blocks):
```python
# БЫЛО:
avg_body = np.mean([abs(c['close'] - c['open']) for c in candles[i-5:i]])
# ДОЛЖНО БЫТЬ:
candle_slice = candles[max(0, i-5):i]
if len(candle_slice) > 0:
avg_body = safe_mean([abs(c['close'] - c['open']) for c in candle_slice])
else:
avg_body = 0.0
```
#### 5. Исправить строку 836 (detect_liquidity_grabs):
```python
# БЫЛО:
avg_vol = np.mean([c['volume'] for c in prev_candles])
# ДОЛЖНО БЫТЬ:
avg_vol = safe_mean([c['volume'] for c in prev_candles]) if prev_candles else 1.0
```
#### 6. Добавить валидацию данных в начале _analyze_timeframe:
```python
async def _analyze_timeframe(
self,
symbol: str,
timeframe: str,
include_patterns: bool
) -> Dict[str, Any]:
"""Анализ на одном таймфрейме"""
# Получаем OHLCV данные
ohlcv = await self.client.get_ohlcv(symbol, timeframe, limit=200)
# НОВАЯ ВАЛИДАЦИЯ
if not ohlcv or len(ohlcv) < 20:
logger.warning(
f"Insufficient data for {symbol} on {timeframe}: "
f"{len(ohlcv) if ohlcv else 0} candles (need min 20)"
)
return {
"timeframe": timeframe,
"error": "insufficient_data",
"data_points": len(ohlcv) if ohlcv else 0,
"message": f"Only {len(ohlcv) if ohlcv else 0} candles available, need min 20"
}
# Конвертируем в DataFrame
# ... остальной код без изменений
```
---
## 🎯 ПРОБЛЕМА #2: Best LONG/SHORT Score = 0.00 (Несоответствие полей)
### 📊 Анализ проблемы
**Критичность:** 🔴 КРИТИЧНО
**Влияние:**
- Пользователь видит 0.00, хотя есть сигналы с score > 0
- Неправильная оценка качества сигналов
- Некорректная оценка риска
**Причина:**
В разных частях кода используются разные названия для score:
- `confluence_score` - в валидации
- `final_score` - после deep analysis
- `score` - в market scanner
- `publication` использует смесь этих полей
**Локации:**
```
autonomous_agent/detailed_formatter.py:69-70 - использует confluence_score
publish_market_analysis.py:110-111 - использует score
autonomous_agent/autonomous_analyzer.py - создает final_score
```
### ✅ РЕШЕНИЕ
**Стратегия:** Унифицировать на `final_score` как основное поле, добавить fallback для обратной совместимости
#### 1. Исправить detailed_formatter.py (строки 69-70):
```python
# БЫЛО:
best_long_score = max([opp.get("confluence_score", 0) for opp in all_longs], default=0)
best_short_score = max([opp.get("confluence_score", 0) for opp in all_shorts], default=0)
# ДОЛЖНО БЫТЬ:
def get_score(opp):
"""Извлечение score с fallback на разные названия полей"""
return (
opp.get("final_score", 0) or
opp.get("confluence_score", 0) or
opp.get("score", 0) or
0
)
best_long_score = max([get_score(opp) for opp in all_longs], default=0)
best_short_score = max([get_score(opp) for opp in all_shorts], default=0)
```
#### 2. Исправить publish_market_analysis.py (строки 110-111):
```python
# БЫЛО:
best_long_score = max([o['score'] for o in longs], default=0)
best_short_score = max([o['score'] for o in shorts], default=0)
# ДОЛЖНО БЫТЬ:
def get_score(opp):
"""Извлечение score с fallback на разные названия полей"""
return (
opp.get("final_score", 0) or
opp.get("confluence_score", 0) or
opp.get("score", 0) or
0
)
best_long_score = max([get_score(o) for o in longs], default=0)
best_short_score = max([get_score(o) for o in shorts], default=0)
```
#### 3. Унифицировать в autonomous_analyzer.py (_format_opportunity):
```python
def _format_opportunity(self, opp: Dict[str, Any]) -> Dict[str, Any]:
"""Форматирование возможности для публикации"""
entry_plan = opp.get("entry_plan", {})
analysis = opp.get("full_analysis", {})
composite = analysis.get("composite_signal", {}) if analysis else {}
# УНИФИЦИРОВАННЫЙ SCORE
final_score = (
opp.get("final_score", 0) or
opp.get("confluence_score", 0) or
opp.get("score", 0) or
0
)
return {
"symbol": opp.get("symbol", ""),
"current_price": opp.get("current_price", 0),
"side": opp.get("side", "long"),
"entry_price": entry_plan.get("entry_price", opp.get("current_price", 0)),
"stop_loss": entry_plan.get("stop_loss", 0),
"take_profit": entry_plan.get("take_profit", 0),
"risk_reward": entry_plan.get("risk_reward", 0),
# ВСЕ ТРИ ВАРИАНТА для обратной совместимости
"final_score": round(final_score, 1),
"confluence_score": round(final_score, 1),
"score": round(final_score, 1),
"probability": opp.get("probability", 0),
"reasoning": opp.get("why", ""),
"timeframes_alignment": list(analysis.get("timeframes", {}).keys()) if analysis else [],
"key_factors": self._extract_key_factors(opp, analysis),
"validation": opp.get("validation", {})
}
```
#### 4. Также исправить в detailed_formatter.py строку 199:
```python
# БЫЛО:
score = opp.get("confluence_score", opp.get("final_score", 0))
# ДОЛЖНО БЫТЬ:
score = (
opp.get("final_score", 0) or
opp.get("confluence_score", 0) or
opp.get("score", 0) or
0
)
```
---
## 🎯 ПРОБЛЕМА #3: OpenRouter API Error 401 "User not found"
### 📊 Анализ проблемы
**Критичность:** 🔴 КРИТИЧНО
**Влияние:**
- Qwen AI анализ полностью отключен
- Система работает без AI-улучшений
- Потеря важного компонента анализа
**Локация:** `autonomous_agent/qwen_client.py:132`
**Причина:**
- Неверный или истекший API ключ OpenRouter
- Проблема с аутентификацией
- Возможно, ключ не установлен в переменных окружения
### ✅ РЕШЕНИЕ
**Файл:** `autonomous_agent/qwen_client.py`
#### 1. Улучшить обработку ошибок в методе generate (строка 98-198):
```python
async def generate(
self,
prompt: str,
system_prompt: Optional[str] = None,
temperature: float = 0.7,
max_tokens: int = 2000,
top_p: float = 0.8
) -> Dict[str, Any]:
"""
Генерация ответа от Qwen
"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
"HTTP-Referer": "https://github.com/your-repo",
"X-Title": "Trader Agent"
}
# Формируем сообщения в формате OpenAI
messages = []
if system_prompt:
messages.append({
"role": "system",
"content": system_prompt
})
messages.append({
"role": "user",
"content": prompt
})
payload = {
"model": self.model,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens,
"top_p": top_p
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(
self.base_url,
headers=headers,
json=payload,
timeout=aiohttp.ClientTimeout(total=60)
) as response:
response_text = await response.text()
# НОВАЯ ОБРАБОТКА 401 ОШИБКИ
if response.status == 401:
logger.error(
f"OpenRouter API authentication failed (401). "
f"Check your OPENROUTER_API_KEY. Response: {response_text}"
)
return {
"success": False,
"error": "Authentication failed - check API key",
"content": "",
"graceful_fallback": True,
"message": "Qwen AI analysis skipped due to API authentication error"
}
if response.status == 200:
data = await response.json()
if "choices" in data and len(data["choices"]) > 0:
choice = data["choices"][0]
content = choice.get("message", {}).get("content", "")
return {
"success": True,
"content": content,
"usage": data.get("usage", {}),
"model": data.get("model", self.model),
"id": data.get("id", "")
}
logger.warning(f"Unexpected response format: {data}")
return {
"success": True,
"content": json.dumps(data, ensure_ascii=False),
"usage": data.get("usage", {}),
"model": data.get("model", self.model)
}
else:
logger.error(f"OpenRouter API error {response.status}: {response_text}")
# НОВЫЙ GRACEFUL FALLBACK
return {
"success": False,
"error": f"API error {response.status}",
"content": "",
"graceful_fallback": True,
"message": f"Qwen AI analysis skipped (API error {response.status})"
}
except asyncio.TimeoutError:
logger.error("Qwen API request timeout")
return {
"success": False,
"error": "Request timeout",
"content": "",
"graceful_fallback": True,
"message": "Qwen AI analysis skipped (timeout)"
}
except Exception as e:
logger.error(f"Qwen API error: {e}", exc_info=True)
return {
"success": False,
"error": str(e),
"content": "",
"graceful_fallback": True,
"message": f"Qwen AI analysis skipped ({str(e)})"
}
```
#### 2. Обновить autonomous_analyzer.py (строки 286-298) для graceful fallback:
```python
# ШАГ 5: Анализ через Qwen (с graceful fallback)
logger.info("Step 5: Qwen AI analysis...")
market_data = {
"market_overview": market_overview,
"btc_analysis": btc_analysis,
"scanned_opportunities": top_candidates,
"timestamp": datetime.now().isoformat()
}
qwen_analysis = await self.qwen.analyze_market_opportunities(
market_data=market_data,
system_instructions=self.system_instructions
)
# НОВЫЙ GRACEFUL FALLBACK
if not qwen_analysis.get("success"):
if qwen_analysis.get("graceful_fallback"):
logger.warning(
f"Qwen AI analysis skipped: {qwen_analysis.get('message', 'Unknown reason')}. "
"Continuing with technical analysis only."
)
# Продолжаем без Qwen анализа
qwen_analysis = {
"success": False,
"graceful_fallback": True,
"message": qwen_analysis.get("message", "AI analysis unavailable")
}
else:
logger.error(f"Qwen analysis failed: {qwen_analysis.get('error', 'Unknown error')}")
```
#### 3. Добавить проверку API ключа в __init__:
```python
def __init__(self, api_key: str, model: str = "qwen/qwen-turbo"):
"""
Инициализация клиента Qwen через OpenRouter
"""
if not api_key:
raise ValueError("API key is required for Qwen client")
# НОВАЯ ПРОВЕРКА формата ключа
if not api_key.startswith("sk-or-"):
logger.warning(
f"OpenRouter API key should start with 'sk-or-'. "
f"Your key starts with: {api_key[:10]}... "
f"Please verify your OPENROUTER_API_KEY"
)
self.api_key = api_key
self.model = model
self.base_url = self.BASE_URL
self.available_models = [
"qwen/qwen-turbo",
"qwen/qwen-plus",
"qwen/qwen-max"
]
logger.info(f"Qwen client initialized with OpenRouter, model: {model}")
```
---
## 🎯 ПРОБЛЕМА #4: Validation Failed - Hard Stops (Volume = 0.00)
### 📊 Анализ проблемы
**Критичность:** ⚠️ СРЕДНЯЯ
**Влияние:**
- Много сигналов отфильтровывается
- Volume = 0.00 (проблема с данными или расчетом)
- Только 1 LONG из 4 прошел валидацию
**Локация:** `autonomous_agent/autonomous_analyzer.py:_validate_opportunities`
**Связана с:** Проблема #1 (mean of empty slice)
### ✅ РЕШЕНИЕ
**Файл:** `mcp_server/technical_analysis.py`
#### 1. Улучшить _check_hard_stops_for_validation (строки 871-968):
```python
def _check_hard_stops_for_validation(self, analysis: Dict, is_long: bool, entry_timeframe: str = "5m") -> Dict:
"""
Обязательные проверки которые БЛОКИРУЮТ вход (для validate_entry)
"""
stops = []
blocked = False
details = {}
composite = analysis.get('composite_signal', {})
# STOP #1: Composite Signal = HOLD с низкой confidence
signal = composite.get('signal', 'HOLD')
confidence = composite.get('confidence', 0.5)
if signal == 'HOLD' and confidence < 0.5:
stops.append(f"Composite signal HOLD with low confidence ({confidence:.2f} < 0.5)")
blocked = True
details['composite_signal'] = {"signal": signal, "confidence": confidence}
# STOP #2: Confidence слишком низкая
if confidence < 0.4:
stops.append(f"Composite confidence too low ({confidence:.2f} < 0.4)")
blocked = True
details['confidence'] = confidence
# STOP #3: MACD bearish на 3+ коротких TF для LONG (было 2+)
if is_long:
bearish_count = 0
macd_details = {}
for tf in ['1m', '5m', '15m']:
tf_data = analysis.get('timeframes', {}).get(tf, {})
if 'error' in tf_data: # НОВАЯ ПРОВЕРКА
continue
macd = tf_data.get('indicators', {}).get('macd', {})
crossover = macd.get('crossover', 'neutral')
macd_details[tf] = crossover
if crossover == 'bearish':
bearish_count += 1
# ИЗМЕНЕНО: 3+ вместо 2+ для менее строгой фильтрации
if bearish_count >= 3:
stops.append(f"MACD bearish on {bearish_count} short timeframes")
blocked = True
details['macd'] = macd_details
# STOP #4: MACD bullish на 3+ коротких TF для SHORT (было 2+)
if not is_long:
bullish_count = 0
macd_details = {}
for tf in ['1m', '5m', '15m']:
tf_data = analysis.get('timeframes', {}).get(tf, {})
if 'error' in tf_data: # НОВАЯ ПРОВЕРКА
continue
macd = tf_data.get('indicators', {}).get('macd', {})
crossover = macd.get('crossover', 'neutral')
macd_details[tf] = crossover
if crossover == 'bullish':
bullish_count += 1
# ИЗМЕНЕНО: 3+ вместо 2+
if bullish_count >= 3:
stops.append(f"MACD bullish on {bullish_count} short timeframes")
blocked = True
details['macd'] = macd_details
# STOP #5: Volume слишком низкий для скальпинга (УЛУЧШЕННАЯ ПРОВЕРКА)
volume_checks = {}
valid_volume_found = False
for tf in ['1m', '5m', '15m']:
tf_data = analysis.get('timeframes', {}).get(tf, {})
if 'error' in tf_data: # Пропускаем таймфреймы с ошибками
continue
vol_data = tf_data.get('indicators', {}).get('volume', {})
vol_ratio = vol_data.get('volume_ratio', 0)
volume_checks[tf] = vol_ratio
# Если хотя бы на одном TF volume адекватный - не блокируем
if vol_ratio > 0.5:
valid_volume_found = True
# ИЗМЕНЕНО: Блокируем только если ВСЕ volume_ratio = 0 или очень низкие
entry_vol = volume_checks.get(entry_timeframe, 0)
if entry_timeframe in ['1m', '5m'] and not valid_volume_found and entry_vol < 0.3:
avg_vol_ratio = safe_mean(list(volume_checks.values())) if volume_checks else 0
stops.append(
f"Volume too low for scalping on {entry_timeframe}: {entry_vol:.2f} "
f"(avg across TFs: {avg_vol_ratio:.2f})"
)
blocked = True
details['volume'] = volume_checks
# STOP #6: BB Squeeze без volume confirmation (СМЯГЧЕНО)
squeeze_count = 0
for tf in ['1m', '5m', '15m']:
tf_data = analysis.get('timeframes', {}).get(tf, {})
if 'error' in tf_data:
continue
bb = tf_data.get('indicators', {}).get('bollinger_bands', {})
vol_data = tf_data.get('indicators', {}).get('volume', {})
if bb.get('squeeze', False) and vol_data.get('volume_ratio', 1.0) < 0.5:
squeeze_count += 1
# ИЗМЕНЕНО: Блокируем только если squeeze на 2+ таймфреймах (было 1+)
if squeeze_count >= 2:
stops.append(
f"BB Squeeze on {squeeze_count} timeframes without volume confirmation"
)
blocked = True
details['bb_squeeze_count'] = squeeze_count
return {
"blocked": blocked,
"stops": stops,
"can_proceed": not blocked,
"details": details
}
```
---
## 🎯 ПРОБЛЕМА #5: Cache Disabled (Несоответствие)
### 📊 Анализ проблемы
**Критичность:** ⚠️ СРЕДНЯЯ
**Влияние:**
- Больше API запросов к Bybit
- Медленнее выполнение анализа
- Два разных механизма кэширования
**Причина:**
- CacheManager отключен по умолчанию (`enabled=False`)
- bybit_client использует свой внутренний кэш
- Несогласованность в логах
### ✅ РЕШЕНИЕ
**Файл:** `.env` (или создать `.env.local`)
#### Добавить/изменить переменную:
```bash
# Кэширование данных
ENABLE_CACHE=true
CACHE_TTL=300 # 5 минут по умолчанию
```
**Файл:** `mcp_server/cache_manager.py` (если нужна более детальная конфигурация)
```python
# Убедиться что initialization использует переменную окружения:
def __init__(self, enabled: Optional[bool] = None):
"""
Initialize cache manager
Args:
enabled: Enable caching (None = use env variable)
"""
if enabled is None:
# ИСПОЛЬЗУЕМ ПЕРЕМЕННУЮ ОКРУЖЕНИЯ
enabled = os.getenv("ENABLE_CACHE", "false").lower() in ["true", "1", "yes"]
self.enabled = enabled
self.cache: Dict[str, Dict[str, Any]] = {}
self.lock = asyncio.Lock()
logger.info(
f"CacheManager initialized with caching "
f"{'ENABLED' if enabled else 'DISABLED'}"
)
```
---
## 🎯 ПРОБЛЕМА #6: Low Experience Scores
### 📊 Анализ проблемы
**Критичность:** ℹ️ НИЗКАЯ
**Влияние:**
- Накопление опыта с низкими score
- `pattern=unknown` - паттерны не распознаются
**Причина:**
- Низкие confluence scores (это нормально, фильтрация работает)
- Паттерны не всегда детектируются
### ✅ РЕШЕНИЕ
Это не баг, а **ожидаемое поведение**. Система правильно отфильтровывает слабые сигналы.
**Рекомендация:** Добавить лучшее логирование для понимания:
```python
# В autonomous_analyzer.py, метод _calculate_final_score, строка ~855
# УЛУЧШЕННОЕ ЛОГИРОВАНИЕ
if self.ml_predictor:
try:
# ... существующий код ...
opp["experience_data"] = experience_data
logger.info( # ИЗМЕНЕНО: info вместо debug
f"Experience logged for {opp.get('symbol')}: "
f"pattern={pattern_type}, score={score:.1f}, "
f"rsi={rsi:.1f}, volume_ratio={volume_ratio:.2f}"
)
except Exception as e:
logger.warning(f"Experience logging failed: {e}")
```
---
## 📝 ДОПОЛНИТЕЛЬНЫЕ УЛУЧШЕНИЯ
### 1. Добавить глобальную обработку ошибок в autonomous_analyzer.py
```python
# В методе analyze_market, обернуть каждый шаг в try-except
async def analyze_market(self) -> Dict[str, Any]:
"""Полный анализ рынка с поиском топовых точек входа"""
logger.info("Starting comprehensive market analysis...")
results = {
"success": True,
"timestamp": datetime.now().isoformat(),
"errors": [],
"warnings": []
}
try:
# ШАГ 1
try:
logger.info("Step 1: Getting market overview...")
market_overview = await self.bybit_client.get_market_overview("both")
results["market_overview"] = market_overview
except Exception as e:
logger.error(f"Step 1 failed: {e}", exc_info=True)
results["errors"].append(f"Market overview failed: {str(e)}")
results["market_overview"] = {"error": str(e)}
# ШАГ 2
try:
logger.info("Step 2: Analyzing BTC...")
btc_analysis = await self._analyze_btc()
results["btc_analysis"] = btc_analysis
except Exception as e:
logger.error(f"Step 2 failed: {e}", exc_info=True)
results["errors"].append(f"BTC analysis failed: {str(e)}")
results["btc_analysis"] = {"error": str(e)}
# ... и так далее для каждого шага
except Exception as e:
logger.error(f"Fatal error during market analysis: {e}", exc_info=True)
results["success"] = False
results["error"] = str(e)
return results
```
### 2. Добавить мониторинг метрик
```python
# Создать новый файл: mcp_server/metrics_logger.py
import time
from loguru import logger
from typing import Dict, Any
class MetricsLogger:
"""Логирование метрик производительности и качества"""
def __init__(self):
self.metrics = {
"analysis_count": 0,
"total_time": 0,
"avg_time": 0,
"opportunities_found": 0,
"validation_passed": 0,
"validation_failed": 0
}
def log_analysis(self, duration: float, opportunities: int, validated: int):
"""Логировать результаты анализа"""
self.metrics["analysis_count"] += 1
self.metrics["total_time"] += duration
self.metrics["avg_time"] = self.metrics["total_time"] / self.metrics["analysis_count"]
self.metrics["opportunities_found"] += opportunities
self.metrics["validation_passed"] += validated
self.metrics["validation_failed"] += (opportunities - validated)
logger.info(
f"Analysis metrics: "
f"duration={duration:.2f}s, "
f"opportunities={opportunities}, "
f"validated={validated}, "
f"avg_time={self.metrics['avg_time']:.2f}s"
)
def get_summary(self) -> Dict[str, Any]:
"""Получить сводку метрик"""
validation_rate = (
self.metrics["validation_passed"] / self.metrics["opportunities_found"]
if self.metrics["opportunities_found"] > 0
else 0
)
return {
**self.metrics,
"validation_rate": round(validation_rate, 2)
}
```
---
## ✅ ЧЕКЛИСТ ИСПОЛНЕНИЯ
Для Code Mode - выполнить в следующем порядке:
### Фаза 1: Критические исправления (ОБЯЗАТЕЛЬНО)
- [ ] **1.1** Добавить `safe_mean` и `safe_rolling_mean` в `technical_analysis.py`
- [ ] **1.2** Исправить все 5 локаций RuntimeWarning в `technical_analysis.py`
- [ ] **1.3** Добавить валидацию данных в `_analyze_timeframe`
- [ ] **1.4** Унифицировать score fields в `detailed_formatter.py`
- [ ] **1.5** Унифицировать score fields в `publish_market_analysis.py`
- [ ] **1.6** Унифицировать score fields в `autonomous_analyzer.py`
- [ ] **1.7** Добавить graceful fallback в `qwen_client.py`
- [ ] **1.8** Обновить обработку Qwen в `autonomous_analyzer.py`
### Фаза 2: Оптимизации (РЕКОМЕНДУЕТСЯ)
- [ ] **2.1** Смягчить hard stops в `_check_hard_stops_for_validation`
- [ ] **2.2** Включить кэширование в `.env`
- [ ] **2.3** Улучшить логирование experience
- [ ] **2.4** Добавить глобальную обработку ошибок
### Фаза 3: Мониторинг (ОПЦИОНАЛЬНО)
- [ ] **3.1** Создать `metrics_logger.py`
- [ ] **3.2** Интегрировать MetricsLogger в `autonomous_analyzer.py`
---
## 🧪 ТЕСТИРОВАНИЕ
После внесения изменений протестировать:
```bash
# 1. Запустить анализ рынка
python autonomous_agent/main.py
# 2. Проверить логи на отсутствие RuntimeWarning
grep "RuntimeWarning" logs/*.log
# 3. Проверить что score отображается корректно
grep "Best LONG score" logs/*.log
# 4. Проверить graceful fallback Qwen
grep "Qwen AI analysis skipped" logs/*.log
# 5. Проверить метрики валидации
grep "validation_rate" logs/*.log
```
---
## 📚 BEST PRACTICES ПРИМЕНЁННЫЕ
1. **Defensive Programming**
- Проверка данных перед использованием
- Safe defaults для всех операций
- Обработка edge cases
2. **Graceful Degradation**
- Система работает даже при частичных сбоях
- Автоматический fallback для внешних API
- Информативные сообщения об ошибках
3. **Data Validation**
- Проверка типов и размеров данных
- Валидация перед математическими операциями
- Early return при недостаточных данных
4. **Unified Interface**
- Единообразные названия полей
- Fallback для обратной совместимости
- Консистентная структура данных
5. **Comprehensive Logging**
- Информативные логи для отладки
- Разные уровни логирования
- Метрики производительности
6. **Error Handling**
- Try-except для всех критических операций
- Специфичные исключения
- Graceful recovery
---
## 🎯 ОЖИДАЕМЫЕ РЕЗУЛЬТАТЫ
После внесения всех исправлений:
✅ **Логи будут чистыми** - нет RuntimeWarning
✅ **Score отображается корректно** - Best LONG/SHORT > 0
✅ **Qwen работает или gracefully degrades** - система работает в обоих случаях
✅ **Валидация эффективнее** - меньше false negatives
✅ **Кэширование активно** - быстрее анализ
✅ **Метрики собираются** - лучший мониторинг
---
**КОНЕЦ ДОКУМЕНТА**
*Этот документ является самодостаточным и содержит все необходимое для исправления критических проблем системы.*