Upload 6 files
Browse files- LLM (31).py +1051 -0
- ML (31).py +1402 -0
- app (32).py +953 -0
- data_manager (23).py +1431 -0
- learning_engine (19).py +644 -0
- r2 (29).py +648 -0
LLM (31).py
ADDED
|
@@ -0,0 +1,1051 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, traceback, asyncio, json, re, ast
|
| 2 |
+
from datetime import datetime, timedelta
|
| 3 |
+
from functools import wraps
|
| 4 |
+
from backoff import on_exception, expo
|
| 5 |
+
from openai import OpenAI, RateLimitError, APITimeoutError, APIStatusError
|
| 6 |
+
import numpy as np, httpx, pandas as pd
|
| 7 |
+
from gnews import GNews
|
| 8 |
+
import feedparser
|
| 9 |
+
|
| 10 |
+
NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY")
|
| 11 |
+
PRIMARY_MODEL = "nvidia/llama-3.1-nemotron-ultra-253b-v1"
|
| 12 |
+
NVIDIA_RATE_LIMIT_CALLS = 20
|
| 13 |
+
NVIDIA_RATE_LIMIT_PERIOD = 60
|
| 14 |
+
|
| 15 |
+
CRYPTO_RSS_FEEDS = {
|
| 16 |
+
"Cointelegraph": "https://cointelegraph.com/rss",
|
| 17 |
+
"CoinDesk": "https://www.coindesk.com/arc/outboundfeeds/rss/",
|
| 18 |
+
"CryptoSlate": "https://cryptoslate.com/feed/",
|
| 19 |
+
"NewsBTC": "https://www.newsbtc.com/feed/",
|
| 20 |
+
"Bitcoin.com": "https://news.bitcoin.com/feed/"
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
class NewsFetcher:
|
| 24 |
+
def __init__(self):
|
| 25 |
+
self.http_client = httpx.AsyncClient(
|
| 26 |
+
timeout=10.0, follow_redirects=True,
|
| 27 |
+
headers={
|
| 28 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
| 29 |
+
'Accept': 'application/json, text/plain, */*',
|
| 30 |
+
'Accept-Language': 'en-US,en;q=0.9',
|
| 31 |
+
'Cache-Control': 'no-cache'
|
| 32 |
+
}
|
| 33 |
+
)
|
| 34 |
+
self.gnews = GNews(language='en', country='US', period='3h', max_results=8)
|
| 35 |
+
|
| 36 |
+
async def _fetch_from_gnews(self, symbol: str) -> list:
|
| 37 |
+
try:
|
| 38 |
+
base_symbol = symbol.split("/")[0]
|
| 39 |
+
query = f'"{base_symbol}" cryptocurrency -bitcoin -ethereum -BTC -ETH'
|
| 40 |
+
print(f"📰 Fetching specific news from GNews for {base_symbol}...")
|
| 41 |
+
news_items = await asyncio.to_thread(self.gnews.get_news, query)
|
| 42 |
+
print(f"✅ GNews fetched {len(news_items)} specific items for {base_symbol}.")
|
| 43 |
+
return news_items
|
| 44 |
+
except Exception as e:
|
| 45 |
+
print(f"❌ Failed to fetch specific news from GNews for {symbol}: {e}")
|
| 46 |
+
return []
|
| 47 |
+
|
| 48 |
+
async def _fetch_from_rss_feed(self, feed_url: str, source_name: str, symbol: str) -> list:
|
| 49 |
+
try:
|
| 50 |
+
base_symbol = symbol.split('/')[0]
|
| 51 |
+
print(f"📰 Fetching specific news from {source_name} RSS for {base_symbol}...")
|
| 52 |
+
max_redirects = 2
|
| 53 |
+
current_url = feed_url
|
| 54 |
+
for attempt in range(max_redirects):
|
| 55 |
+
try:
|
| 56 |
+
response = await self.http_client.get(current_url)
|
| 57 |
+
response.raise_for_status()
|
| 58 |
+
break
|
| 59 |
+
except httpx.HTTPStatusError as e:
|
| 60 |
+
if e.response.status_code in [301, 302, 307, 308] and 'Location' in e.response.headers:
|
| 61 |
+
current_url = e.response.headers['Location']
|
| 62 |
+
print(f"🔄 Following redirect to: {current_url}")
|
| 63 |
+
continue
|
| 64 |
+
else:
|
| 65 |
+
raise
|
| 66 |
+
feed = feedparser.parse(response.text)
|
| 67 |
+
news_items = []
|
| 68 |
+
search_term = base_symbol.lower()
|
| 69 |
+
for entry in feed.entries[:15]:
|
| 70 |
+
title = entry.title.lower() if hasattr(entry, 'title') else ''
|
| 71 |
+
summary = entry.summary.lower() if hasattr(entry, 'summary') else entry.description.lower() if hasattr(entry, 'description') else ''
|
| 72 |
+
if search_term in title or search_term in summary:
|
| 73 |
+
news_items.append({
|
| 74 |
+
'title': entry.title,
|
| 75 |
+
'description': summary,
|
| 76 |
+
'source': source_name,
|
| 77 |
+
'published': entry.get('published', '')
|
| 78 |
+
})
|
| 79 |
+
print(f"✅ {source_name} RSS fetched {len(news_items)} specific items for {base_symbol}.")
|
| 80 |
+
return news_items
|
| 81 |
+
except Exception as e:
|
| 82 |
+
print(f"❌ Failed to fetch specific news from {source_name} RSS for {symbol}: {e}")
|
| 83 |
+
return []
|
| 84 |
+
|
| 85 |
+
async def get_news_for_symbol(self, symbol: str) -> str:
|
| 86 |
+
base_symbol = symbol.split("/")[0]
|
| 87 |
+
tasks = [self._fetch_from_gnews(symbol)]
|
| 88 |
+
for name, url in CRYPTO_RSS_FEEDS.items():
|
| 89 |
+
tasks.append(self._fetch_from_rss_feed(url, name, symbol))
|
| 90 |
+
|
| 91 |
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
| 92 |
+
all_news_text = []
|
| 93 |
+
|
| 94 |
+
for result in results:
|
| 95 |
+
if isinstance(result, Exception):
|
| 96 |
+
print(f"⚠️ A news source failed with error: {result}")
|
| 97 |
+
continue
|
| 98 |
+
for item in result:
|
| 99 |
+
if self._is_directly_relevant_to_symbol(item, base_symbol):
|
| 100 |
+
title = item.get('title', 'No Title')
|
| 101 |
+
description = item.get('description', 'No Description')
|
| 102 |
+
source = item.get('source', 'Unknown Source')
|
| 103 |
+
published = item.get('published', '')
|
| 104 |
+
|
| 105 |
+
news_entry = f"[{source}] {title}. {description}"
|
| 106 |
+
if published:
|
| 107 |
+
news_entry += f" (Published: {published})"
|
| 108 |
+
|
| 109 |
+
all_news_text.append(news_entry)
|
| 110 |
+
|
| 111 |
+
if not all_news_text:
|
| 112 |
+
return f"📰 No specific news found for {base_symbol} in the last 3 hours."
|
| 113 |
+
|
| 114 |
+
important_news = all_news_text[:5]
|
| 115 |
+
return " | ".join(important_news)
|
| 116 |
+
|
| 117 |
+
def _is_directly_relevant_to_symbol(self, news_item, base_symbol):
|
| 118 |
+
title = news_item.get('title', '').lower()
|
| 119 |
+
description = news_item.get('description', '').lower()
|
| 120 |
+
symbol_lower = base_symbol.lower()
|
| 121 |
+
|
| 122 |
+
if symbol_lower not in title and symbol_lower not in description:
|
| 123 |
+
return False
|
| 124 |
+
|
| 125 |
+
crypto_keywords = [
|
| 126 |
+
'crypto', 'cryptocurrency', 'token', 'blockchain',
|
| 127 |
+
'price', 'market', 'trading', 'exchange', 'defi',
|
| 128 |
+
'coin', 'digital currency', 'altcoin'
|
| 129 |
+
]
|
| 130 |
+
|
| 131 |
+
return any(keyword in title or keyword in description for keyword in crypto_keywords)
|
| 132 |
+
|
| 133 |
+
class PatternAnalysisEngine:
|
| 134 |
+
def __init__(self, llm_service):
|
| 135 |
+
self.llm = llm_service
|
| 136 |
+
self.pattern_templates = {
|
| 137 |
+
'reversal': ['head_shoulders', 'double_top', 'triple_top', 'rising_wedge', 'falling_wedge'],
|
| 138 |
+
'continuation': ['flags', 'pennants', 'triangles', 'rectangles', 'cup_and_handle'],
|
| 139 |
+
'consolidation': ['symmetrical_triangle', 'ascending_triangle', 'descending_triangle']
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
def _format_chart_data_for_llm(self, ohlcv_data):
|
| 143 |
+
"""تنسيق بيانات الشموع بشكل محسن للنموذج"""
|
| 144 |
+
if not ohlcv_data or len(ohlcv_data) < 20:
|
| 145 |
+
return "❌ Insufficient chart data for pattern analysis (minimum 20 candles required)"
|
| 146 |
+
|
| 147 |
+
try:
|
| 148 |
+
# استخدام آخر 50 شمعة للتحليل الدقيق
|
| 149 |
+
candles_to_analyze = ohlcv_data[-50:] if len(ohlcv_data) > 50 else ohlcv_data
|
| 150 |
+
|
| 151 |
+
chart_description = [
|
| 152 |
+
"📊 **CANDLE DATA FOR PATTERN ANALYSIS:**",
|
| 153 |
+
f"Total candles available: {len(ohlcv_data)}",
|
| 154 |
+
f"Candles used for analysis: {len(candles_to_analyze)}",
|
| 155 |
+
""
|
| 156 |
+
]
|
| 157 |
+
|
| 158 |
+
# إضافة معلومات عن الشموع الرئيسية
|
| 159 |
+
if len(candles_to_analyze) >= 10:
|
| 160 |
+
recent_candles = candles_to_analyze[-10:]
|
| 161 |
+
chart_description.append("**Recent 10 Candles (Latest First):**")
|
| 162 |
+
for i, candle in enumerate(reversed(recent_candles)):
|
| 163 |
+
candle_idx = len(candles_to_analyze) - i
|
| 164 |
+
desc = f"Candle {candle_idx}: O:{candle[1]:.6f} H:{candle[2]:.6f} L:{candle[3]:.6f} C:{candle[4]:.6f} V:{candle[5]:.0f}"
|
| 165 |
+
chart_description.append(f" {desc}")
|
| 166 |
+
|
| 167 |
+
# تحليل الاتجاه العام
|
| 168 |
+
if len(candles_to_analyze) >= 2:
|
| 169 |
+
first_close = candles_to_analyze[0][4]
|
| 170 |
+
last_close = candles_to_analyze[-1][4]
|
| 171 |
+
price_change = ((last_close - first_close) / first_close) * 100
|
| 172 |
+
trend = "📈 BULLISH" if price_change > 2 else "📉 BEARISH" if price_change < -2 else "➡️ SIDEWAYS"
|
| 173 |
+
|
| 174 |
+
# حساب أعلى وأقل سعر
|
| 175 |
+
highs = [c[2] for c in candles_to_analyze]
|
| 176 |
+
lows = [c[3] for c in candles_to_analyze]
|
| 177 |
+
high_max = max(highs)
|
| 178 |
+
low_min = min(lows)
|
| 179 |
+
volatility = ((high_max - low_min) / low_min) * 100
|
| 180 |
+
|
| 181 |
+
chart_description.extend([
|
| 182 |
+
"",
|
| 183 |
+
"**MARKET STRUCTURE ANALYSIS:**",
|
| 184 |
+
f"Trend Direction: {trend}",
|
| 185 |
+
f"Price Change: {price_change:+.2f}%",
|
| 186 |
+
f"Volatility Range: {volatility:.2f}%",
|
| 187 |
+
f"Highest Price: {high_max:.6f}",
|
| 188 |
+
f"Lowest Price: {low_min:.6f}"
|
| 189 |
+
])
|
| 190 |
+
|
| 191 |
+
# تحليل حجم التداول
|
| 192 |
+
if len(candles_to_analyze) >= 5:
|
| 193 |
+
volumes = [c[5] for c in candles_to_analyze]
|
| 194 |
+
avg_volume = sum(volumes) / len(volumes)
|
| 195 |
+
current_volume = candles_to_analyze[-1][5]
|
| 196 |
+
volume_ratio = current_volume / avg_volume if avg_volume > 0 else 1
|
| 197 |
+
|
| 198 |
+
volume_signal = "🚀 HIGH" if volume_ratio > 2 else "📊 NORMAL" if volume_ratio > 0.5 else "📉 LOW"
|
| 199 |
+
chart_description.extend([
|
| 200 |
+
"",
|
| 201 |
+
"**VOLUME ANALYSIS:**",
|
| 202 |
+
f"Current Volume: {current_volume:,.0f}",
|
| 203 |
+
f"Volume Ratio: {volume_ratio:.2f}x average",
|
| 204 |
+
f"Volume Signal: {volume_signal}"
|
| 205 |
+
])
|
| 206 |
+
|
| 207 |
+
return "\n".join(chart_description)
|
| 208 |
+
|
| 209 |
+
except Exception as e:
|
| 210 |
+
return f"❌ Error formatting chart data: {str(e)}"
|
| 211 |
+
|
| 212 |
+
async def analyze_chart_patterns(self, symbol, ohlcv_data):
|
| 213 |
+
"""تحليل الأنماط البيانية مع تحسينات كبيرة"""
|
| 214 |
+
try:
|
| 215 |
+
if not ohlcv_data or len(ohlcv_data) < 20:
|
| 216 |
+
return {
|
| 217 |
+
"pattern_detected": "insufficient_data",
|
| 218 |
+
"pattern_confidence": 0.1,
|
| 219 |
+
"pattern_strength": "weak",
|
| 220 |
+
"predicted_direction": "unknown",
|
| 221 |
+
"pattern_analysis": "Insufficient candle data for pattern analysis"
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
chart_text = self._format_chart_data_for_llm(ohlcv_data)
|
| 225 |
+
|
| 226 |
+
prompt = f"""
|
| 227 |
+
🔍 **CRYPTO CHART PATTERN ANALYSIS REQUEST**
|
| 228 |
+
|
| 229 |
+
You are an expert cryptocurrency technical analyst with 10+ years experience.
|
| 230 |
+
Analyze the following candle data for {symbol} and identify STRONG, ACTIONABLE patterns.
|
| 231 |
+
|
| 232 |
+
**ANALYSIS REQUIREMENTS:**
|
| 233 |
+
1. Focus on CLEAR, HIGH-PROBABILITY patterns only
|
| 234 |
+
2. Consider volume confirmation for all patterns
|
| 235 |
+
3. Evaluate pattern strength based on candle formations
|
| 236 |
+
4. Provide SPECIFIC price targets and stop levels
|
| 237 |
+
5. Assess timeframe suitability for 5-45 minute trades
|
| 238 |
+
|
| 239 |
+
**CANDLE DATA FOR ANALYSIS:**
|
| 240 |
+
{chart_text}
|
| 241 |
+
|
| 242 |
+
**PATTERNS TO LOOK FOR:**
|
| 243 |
+
🎯 REVERSAL PATTERNS: Head & Shoulders, Double Top/Bottom, Triple Top/Bottom
|
| 244 |
+
🎯 CONTINUATION PATTERNS: Flags, Pennants, Triangles, Rectangles
|
| 245 |
+
🎯 CONSOLIDATION PATTERNS: Symmetrical/Descending/Ascending Triangles
|
| 246 |
+
🎯 SUPPORT/RESISTANCE: Key levels from recent highs/lows
|
| 247 |
+
|
| 248 |
+
**MANDATORY OUTPUT FORMAT (JSON):**
|
| 249 |
+
{{
|
| 250 |
+
"pattern_detected": "pattern_name",
|
| 251 |
+
"pattern_confidence": 0.85,
|
| 252 |
+
"pattern_strength": "strong/medium/weak",
|
| 253 |
+
"predicted_direction": "up/down/sideways",
|
| 254 |
+
"predicted_movement_percent": 5.50,
|
| 255 |
+
"timeframe_expectation": "15-25 minutes",
|
| 256 |
+
"entry_suggestion": 0.1234,
|
| 257 |
+
"target_suggestion": 0.1357,
|
| 258 |
+
"stop_suggestion": 0.1189,
|
| 259 |
+
"key_support": 0.1200,
|
| 260 |
+
"key_resistance": 0.1300,
|
| 261 |
+
"pattern_analysis": "Detailed explanation of the pattern, why it's valid, and volume confirmation"
|
| 262 |
+
}}
|
| 263 |
+
|
| 264 |
+
**CRITICAL:**
|
| 265 |
+
- Only identify patterns if you have ≥ 70% confidence
|
| 266 |
+
- MUST consider volume in pattern confirmation
|
| 267 |
+
- Provide SPECIFIC numbers for entry/target/stop
|
| 268 |
+
- If no clear pattern, set pattern_detected to "no_clear_pattern"
|
| 269 |
+
"""
|
| 270 |
+
|
| 271 |
+
print(f"🔍 Analyzing chart patterns for {symbol} with {len(ohlcv_data)} candles...")
|
| 272 |
+
response = await self.llm._call_llm(prompt)
|
| 273 |
+
|
| 274 |
+
pattern_result = self._parse_pattern_response(response)
|
| 275 |
+
if pattern_result and pattern_result.get('pattern_detected') != 'no_clear_pattern':
|
| 276 |
+
print(f"✅ Pattern detected for {symbol}: {pattern_result.get('pattern_detected')} "
|
| 277 |
+
f"(Confidence: {pattern_result.get('pattern_confidence', 0):.2f})")
|
| 278 |
+
else:
|
| 279 |
+
print(f"ℹ️ No clear patterns for {symbol}")
|
| 280 |
+
|
| 281 |
+
return pattern_result
|
| 282 |
+
|
| 283 |
+
except Exception as e:
|
| 284 |
+
print(f"❌ Chart pattern analysis failed for {symbol}: {e}")
|
| 285 |
+
return None
|
| 286 |
+
|
| 287 |
+
def _parse_pattern_response(self, response_text):
|
| 288 |
+
"""تحليل رد النموذج مع تحسينات التعامل مع الأخطاء"""
|
| 289 |
+
try:
|
| 290 |
+
# البحث عن JSON في الرد
|
| 291 |
+
json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
| 292 |
+
if not json_match:
|
| 293 |
+
return {
|
| 294 |
+
"pattern_detected": "parse_error",
|
| 295 |
+
"pattern_confidence": 0.1,
|
| 296 |
+
"pattern_analysis": "Could not parse pattern analysis response"
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
pattern_data = json.loads(json_match.group())
|
| 300 |
+
|
| 301 |
+
# التحقق من الحقول الأساسية
|
| 302 |
+
required = ['pattern_detected', 'pattern_confidence', 'predicted_direction']
|
| 303 |
+
if not all(field in pattern_data for field in required):
|
| 304 |
+
return {
|
| 305 |
+
"pattern_detected": "incomplete_data",
|
| 306 |
+
"pattern_confidence": 0.1,
|
| 307 |
+
"pattern_analysis": "Incomplete pattern analysis data"
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
return pattern_data
|
| 311 |
+
|
| 312 |
+
except Exception as e:
|
| 313 |
+
print(f"❌ Error parsing pattern response: {e}")
|
| 314 |
+
return {
|
| 315 |
+
"pattern_detected": "parse_error",
|
| 316 |
+
"pattern_confidence": 0.1,
|
| 317 |
+
"pattern_analysis": f"Error parsing pattern analysis: {str(e)}"
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
class LLMService:
|
| 321 |
+
def __init__(self, api_key=NVIDIA_API_KEY, model_name=PRIMARY_MODEL, temperature=0.7):
|
| 322 |
+
self.api_key = api_key
|
| 323 |
+
self.model_name = model_name
|
| 324 |
+
self.temperature = temperature
|
| 325 |
+
self.client = OpenAI(base_url="https://integrate.api.nvidia.com/v1", api_key=self.api_key)
|
| 326 |
+
self.news_fetcher = NewsFetcher()
|
| 327 |
+
self.pattern_engine = PatternAnalysisEngine(self)
|
| 328 |
+
self.semaphore = asyncio.Semaphore(5)
|
| 329 |
+
|
| 330 |
+
def _rate_limit_nvidia_api(func):
|
| 331 |
+
@wraps(func)
|
| 332 |
+
@on_exception(expo, RateLimitError, max_tries=5)
|
| 333 |
+
async def wrapper(*args, **kwargs):
|
| 334 |
+
return await func(*args, **kwargs)
|
| 335 |
+
return wrapper
|
| 336 |
+
|
| 337 |
+
async def get_trading_decision(self, data_payload: dict):
|
| 338 |
+
try:
|
| 339 |
+
symbol = data_payload.get('symbol', 'unknown')
|
| 340 |
+
target_strategy = data_payload.get('target_strategy', 'GENERIC')
|
| 341 |
+
print(f"🧠 Starting LLM analysis for {symbol} with strategy: {target_strategy}...")
|
| 342 |
+
|
| 343 |
+
news_text = await self.news_fetcher.get_news_for_symbol(symbol)
|
| 344 |
+
pattern_analysis = await self._get_pattern_analysis(data_payload)
|
| 345 |
+
prompt = self._create_enhanced_trading_prompt(data_payload, news_text, pattern_analysis)
|
| 346 |
+
|
| 347 |
+
print(f"🧠 Sending enhanced prompt to LLM for {symbol}...")
|
| 348 |
+
async with self.semaphore:
|
| 349 |
+
response = await self._call_llm(prompt)
|
| 350 |
+
|
| 351 |
+
decision_dict = self._parse_llm_response_enhanced(response, target_strategy, symbol)
|
| 352 |
+
if decision_dict:
|
| 353 |
+
decision_dict['model_source'] = self.model_name
|
| 354 |
+
decision_dict['pattern_analysis'] = pattern_analysis
|
| 355 |
+
|
| 356 |
+
# ✅ التحقق النهائي من الاستراتيجية
|
| 357 |
+
final_strategy = decision_dict.get('strategy')
|
| 358 |
+
if not final_strategy or final_strategy == 'unknown' or final_strategy is None:
|
| 359 |
+
decision_dict['strategy'] = target_strategy
|
| 360 |
+
print(f"🔧 Final strategy correction for {symbol}: {target_strategy}")
|
| 361 |
+
else:
|
| 362 |
+
print(f"✅ LLM successfully selected strategy '{final_strategy}' for {symbol}.")
|
| 363 |
+
|
| 364 |
+
print(f"✅ LLM analysis completed for {symbol} - Strategy: {decision_dict['strategy']}")
|
| 365 |
+
else:
|
| 366 |
+
print(f"❌ LLM analysis failed for {symbol}")
|
| 367 |
+
return local_analyze_opportunity(data_payload)
|
| 368 |
+
|
| 369 |
+
return decision_dict
|
| 370 |
+
|
| 371 |
+
except Exception as e:
|
| 372 |
+
print(f"❌ An error occurred while getting LLM decision for {data_payload.get('symbol', 'unknown')}: {e}")
|
| 373 |
+
traceback.print_exc()
|
| 374 |
+
return local_analyze_opportunity(data_payload)
|
| 375 |
+
|
| 376 |
+
def _parse_llm_response_enhanced(self, response_text: str, fallback_strategy: str = 'GENERIC', symbol: str = 'unknown') -> dict:
|
| 377 |
+
"""✅ الإصلاح النهائي: تحليل رد الـ LLM مع إعطاء الثقة لقراره"""
|
| 378 |
+
try:
|
| 379 |
+
json_match = re.search(r'```json\n(.*?)\n```', response_text, re.DOTALL)
|
| 380 |
+
if json_match:
|
| 381 |
+
json_str = json_match.group(1).strip()
|
| 382 |
+
else:
|
| 383 |
+
json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
| 384 |
+
if json_match:
|
| 385 |
+
json_str = json_match.group()
|
| 386 |
+
else:
|
| 387 |
+
print(f"❌ No JSON found in LLM response for {symbol}: {response_text}")
|
| 388 |
+
return None
|
| 389 |
+
|
| 390 |
+
decision_data = json.loads(json_str)
|
| 391 |
+
|
| 392 |
+
required_fields = ['action', 'reasoning', 'risk_assessment', 'trade_type',
|
| 393 |
+
'stop_loss', 'take_profit', 'expected_target_minutes', 'confidence_level']
|
| 394 |
+
|
| 395 |
+
for field in required_fields:
|
| 396 |
+
if field not in decision_data:
|
| 397 |
+
print(f"❌ Missing required field '{field}' in LLM response for {symbol}")
|
| 398 |
+
return None
|
| 399 |
+
|
| 400 |
+
strategy_value = decision_data.get('strategy')
|
| 401 |
+
# 💡 التحقق: هل الاستراتيجية التي أرجعها النموذج صالحة؟
|
| 402 |
+
if not strategy_value or strategy_value == 'unknown' or strategy_value is None:
|
| 403 |
+
# إذا كانت غير صالحة، استخدم الاستراتيجية العامة كخطة بديلة آمنة
|
| 404 |
+
print(f"⚠️ LLM returned invalid strategy '{strategy_value}' for {symbol}. Forcing fallback: {fallback_strategy}")
|
| 405 |
+
decision_data['strategy'] = fallback_strategy
|
| 406 |
+
else:
|
| 407 |
+
# إذا كانت صالحة، اعتمدها مباشرةً!
|
| 408 |
+
print(f"✅ LLM successfully selected strategy '{strategy_value}' for {symbol}.")
|
| 409 |
+
|
| 410 |
+
return decision_data
|
| 411 |
+
|
| 412 |
+
except Exception as e:
|
| 413 |
+
print(f"❌ Unexpected error parsing LLM response for {symbol}: {e}")
|
| 414 |
+
return None
|
| 415 |
+
|
| 416 |
+
async def _get_pattern_analysis(self, data_payload):
|
| 417 |
+
try:
|
| 418 |
+
symbol = data_payload['symbol']
|
| 419 |
+
# ✅ الحصول على بيانات الشموع الخام من البيانات المعالجة
|
| 420 |
+
if 'raw_ohlcv' in data_payload and '1h' in data_payload['raw_ohlcv']:
|
| 421 |
+
ohlcv_data = data_payload['raw_ohlcv']['1h']
|
| 422 |
+
if ohlcv_data and len(ohlcv_data) >= 20:
|
| 423 |
+
print(f"🔍 Using raw OHLCV data for pattern analysis: {len(ohlcv_data)} candles")
|
| 424 |
+
return await self.pattern_engine.analyze_chart_patterns(symbol, ohlcv_data)
|
| 425 |
+
|
| 426 |
+
# ✅ الحصول على بيانات OHLCV من 'advanced_indicators' كبديل
|
| 427 |
+
if 'advanced_indicators' in data_payload and '1h' in data_payload['advanced_indicators']:
|
| 428 |
+
ohlcv_data = data_payload['advanced_indicators']['1h']
|
| 429 |
+
if ohlcv_data and len(ohlcv_data) >= 20:
|
| 430 |
+
print(f"🔍 Using advanced indicators data for pattern analysis: {len(ohlcv_data)} candles")
|
| 431 |
+
return await self.pattern_engine.analyze_chart_patterns(symbol, ohlcv_data)
|
| 432 |
+
|
| 433 |
+
print(f"⚠️ No sufficient OHLCV data for pattern analysis on {symbol}")
|
| 434 |
+
return None
|
| 435 |
+
except Exception as e:
|
| 436 |
+
print(f"⚠️ Pattern analysis failed for {data_payload.get('symbol')}: {e}")
|
| 437 |
+
return None
|
| 438 |
+
|
| 439 |
+
def _create_enhanced_trading_prompt(self, payload: dict, news_text: str, pattern_analysis: dict) -> str:
|
| 440 |
+
symbol = payload.get('symbol', 'N/A')
|
| 441 |
+
current_price = payload.get('current_price', 'N/A')
|
| 442 |
+
reasons = payload.get('reasons_for_candidacy', [])
|
| 443 |
+
sentiment_data = payload.get('sentiment_data', {})
|
| 444 |
+
advanced_indicators = payload.get('advanced_indicators', {})
|
| 445 |
+
strategy_scores = payload.get('strategy_scores', {})
|
| 446 |
+
recommended_strategy = payload.get('recommended_strategy', 'N/A')
|
| 447 |
+
target_strategy = payload.get('target_strategy', 'GENERIC')
|
| 448 |
+
final_score = payload.get('final_score', 'N/A')
|
| 449 |
+
enhanced_final_score = payload.get('enhanced_final_score', 'N/A')
|
| 450 |
+
whale_data = payload.get('whale_data', {})
|
| 451 |
+
|
| 452 |
+
general_whale_activity = sentiment_data.get('general_whale_activity', {})
|
| 453 |
+
|
| 454 |
+
final_score_display = f"{final_score:.2f}" if isinstance(final_score, (int, float)) else str(final_score)
|
| 455 |
+
enhanced_score_display = f"{enhanced_final_score:.2f}" if isinstance(enhanced_final_score, (int, float)) else str(enhanced_final_score)
|
| 456 |
+
|
| 457 |
+
indicators_summary = self._format_advanced_indicators(advanced_indicators)
|
| 458 |
+
strategies_summary = self._format_strategies_analysis(strategy_scores, recommended_strategy)
|
| 459 |
+
pattern_summary = self._format_pattern_analysis_enhanced(pattern_analysis, payload)
|
| 460 |
+
whale_analysis_section = self._format_whale_analysis_for_llm(general_whale_activity, whale_data, symbol)
|
| 461 |
+
|
| 462 |
+
strategy_instructions = {
|
| 463 |
+
"AGGRESSIVE_GROWTH": "**Strategy: AGGRESSIVE_GROWTH**: Focus on strong price movements (5-10%) and accept higher risk for higher rewards. Aim for 8-15% on successful trades.",
|
| 464 |
+
"DEFENSIVE_GROWTH": "**Strategy: DEFENSIVE_GROWTH**: Look for safer 3-6% moves with tight stop-losses. Aim for 4-8% while protecting capital.",
|
| 465 |
+
"CONSERVATIVE": "**Strategy: CONSERVATIVE**: Focus on only 2-4% moves with wider stop-losses. Aim for 2-5% with minimal risk.",
|
| 466 |
+
"HIGH_FREQUENCY": "**Strategy: HIGH_FREQUENCY**: Look for quick 1-3% scalps with very tight stop-losses. Aim for 1-4% on multiple trades.",
|
| 467 |
+
"WHALE_FOLLOWING": "**Strategy: WHALE_FOLLOWING**: Prioritize whale tracking signals and unusual volume. Aim for 5-12% with medium risk.",
|
| 468 |
+
"GENERIC": "**Strategy: GENERIC**: Make balanced decisions considering risk and reward across all factors."
|
| 469 |
+
}
|
| 470 |
+
strategy_instruction = strategy_instructions.get(target_strategy, strategy_instructions["GENERIC"])
|
| 471 |
+
|
| 472 |
+
data_availability_section = self._format_data_availability(sentiment_data, whale_data, news_text, pattern_analysis)
|
| 473 |
+
|
| 474 |
+
prompt = f"""
|
| 475 |
+
🎯 **ENHANCED TRADING ANALYSIS WITH CHART PATTERNS**
|
| 476 |
+
|
| 477 |
+
**ACTIVE STRATEGY: {target_strategy}**
|
| 478 |
+
{strategy_instruction}
|
| 479 |
+
|
| 480 |
+
**CRITICAL CHART PATTERN ANALYSIS:**
|
| 481 |
+
{pattern_summary}
|
| 482 |
+
|
| 483 |
+
**STRATEGIC TIMEFRAME:**
|
| 484 |
+
- Max trade duration: 45 minutes (will be automatically enforced).
|
| 485 |
+
- Optimal range: 8-25 minutes for ideal capital rotation.
|
| 486 |
+
- Minimum duration: 5 minutes for active monitoring.
|
| 487 |
+
|
| 488 |
+
{data_availability_section}
|
| 489 |
+
|
| 490 |
+
**AVAILABLE DATA FOR {symbol}:**
|
| 491 |
+
|
| 492 |
+
**1. 🎯 CANDIDACY REASON:**
|
| 493 |
+
- This symbol was selected for: {reasons}
|
| 494 |
+
|
| 495 |
+
**2. 📊 OVERVIEW:**
|
| 496 |
+
- Symbol: {symbol}
|
| 497 |
+
- Current Price: {current_price} USDT
|
| 498 |
+
- Initial System Score: {final_score_display}
|
| 499 |
+
- Enhanced System Score: {enhanced_score_display}
|
| 500 |
+
- Recommended Internal Strategy: {recommended_strategy}
|
| 501 |
+
- **Target Trading Strategy: {target_strategy}**
|
| 502 |
+
|
| 503 |
+
**3. 🎪 STRATEGY ANALYSIS (INTERNAL SCORES):**
|
| 504 |
+
{strategies_summary}
|
| 505 |
+
|
| 506 |
+
**4. 📈 ADVANCED TECHNICAL INDICATORS:**
|
| 507 |
+
{indicators_summary}
|
| 508 |
+
|
| 509 |
+
**5. 🌍 COMPREHENSIVE MARKET CONTEXT:**
|
| 510 |
+
- BTC Trend: {sentiment_data.get('btc_sentiment', 'N/A')}
|
| 511 |
+
- Fear & Greed Index: {sentiment_data.get('fear_and_greed_index', 'N/A')} ({sentiment_data.get('sentiment_class', 'N/A')})
|
| 512 |
+
- Market Regime: {sentiment_data.get('market_trend', 'N/A')}
|
| 513 |
+
|
| 514 |
+
**6. 🐋 ADVANCED WHALE ANALYSIS:**
|
| 515 |
+
{whale_analysis_section}
|
| 516 |
+
|
| 517 |
+
**7. 📰 RECENT NEWS (LAST 3 HOURS):**
|
| 518 |
+
{news_text}
|
| 519 |
+
|
| 520 |
+
**YOUR MISSION:**
|
| 521 |
+
Integrate the chart pattern analysis above with all other available data to make a FINAL trading decision.
|
| 522 |
+
|
| 523 |
+
**IF PATTERN ANALYSIS SHOWS STRONG SIGNALS:**
|
| 524 |
+
- Give it significant weight in your decision
|
| 525 |
+
- Use the pattern's entry/target/stop suggestions
|
| 526 |
+
- Consider the pattern's confidence level
|
| 527 |
+
|
| 528 |
+
**IF NO CLEAR PATTERNS:**
|
| 529 |
+
- Rely more on technical indicators and market context
|
| 530 |
+
- Be more conservative with targets and stops
|
| 531 |
+
|
| 532 |
+
**REQUIRED OUTPUTS (JSON ONLY):**
|
| 533 |
+
- `action`: Must be one of ("BUY", "SELL", "HOLD")
|
| 534 |
+
- `reasoning`: Detailed explanation focusing on {target_strategy} AND SPECIFICALLY MENTIONING chart pattern analysis
|
| 535 |
+
- `risk_assessment`: Risk analysis aligned with {target_strategy} and available data
|
| 536 |
+
- `trade_type`: ("LONG" for BUY, "SHORT" for SELL)
|
| 537 |
+
- `stop_loss`: Stop loss price (consider {target_strategy} risk profile AND pattern suggestions)
|
| 538 |
+
- `take_profit`: Take profit price (realistic for {target_strategy} AND pattern targets)
|
| 539 |
+
- `expected_target_minutes`: Realistic expectation (5-45 minutes)
|
| 540 |
+
- `confidence_level`: Your confidence level (0.00-1.00) based on data quality AND pattern confidence
|
| 541 |
+
- `strategy`: "{target_strategy}" # ⚠️ MUST BE EXACTLY: {target_strategy}
|
| 542 |
+
- `pattern_influence`: "Describe how chart pattern affected decision"
|
| 543 |
+
|
| 544 |
+
**CRITICAL: You MUST include the 'strategy' field with the exact value: "{target_strategy}"**
|
| 545 |
+
|
| 546 |
+
**SPECIAL INSTRUCTIONS FOR PATTERN INTEGRATION:**
|
| 547 |
+
- If pattern_confidence > 0.7, you MUST reference it prominently in reasoning
|
| 548 |
+
- If pattern suggests specific levels, strongly consider using them
|
| 549 |
+
- Always explain how patterns influenced your final decision in 'pattern_influence'
|
| 550 |
+
|
| 551 |
+
**Example output format (JSON only):**
|
| 552 |
+
```json
|
| 553 |
+
{{
|
| 554 |
+
"action": "BUY",
|
| 555 |
+
"reasoning": "Strong bullish signals aligned with {target_strategy}. High-confidence Double Top pattern detected with 85% confidence suggesting upward movement. Whale activity is positive. Limited news data, but technicals and pattern are strong.",
|
| 556 |
+
"risk_assessment": "Moderate risk suitable for {target_strategy}. Pattern provides clear stop and target levels. Note: Some data sources unavailable.",
|
| 557 |
+
"trade_type": "LONG",
|
| 558 |
+
"stop_loss": 0.0285,
|
| 559 |
+
"take_profit": 0.0320,
|
| 560 |
+
"expected_target_minutes": 12,
|
| 561 |
+
"confidence_level": 0.82,
|
| 562 |
+
"strategy": "{target_strategy}",
|
| 563 |
+
"pattern_influence": "Double Top pattern provided clear entry and target levels, increasing confidence in the trade setup."
|
| 564 |
+
}}
|
| 565 |
+
```
|
| 566 |
+
"""
|
| 567 |
+
return prompt
|
| 568 |
+
|
| 569 |
+
def _format_data_availability(self, sentiment_data, whale_data, news_text, pattern_analysis):
|
| 570 |
+
general_whale_available = sentiment_data.get('general_whale_activity', {}).get('data_available', False)
|
| 571 |
+
symbol_whale_available = whale_data.get('data_available', False)
|
| 572 |
+
news_available = "No specific news found" not in news_text
|
| 573 |
+
pattern_available = pattern_analysis is not None and pattern_analysis.get('pattern_detected') != 'no_clear_pattern'
|
| 574 |
+
|
| 575 |
+
return f"""
|
| 576 |
+
**📊 REAL DATA AVAILABILITY STATUS:**
|
| 577 |
+
- Market Sentiment: {'✅ Available' if sentiment_data.get('fear_and_greed_index') else '❌ Not Available'}
|
| 578 |
+
- General Whale Activity: {'✅ Available' if general_whale_available else '❌ Not Available'}
|
| 579 |
+
- Symbol Whale Activity: {'✅ Available' if symbol_whale_available else '❌ Not Available'}
|
| 580 |
+
- News Data: {'✅ Available' if news_available else '❌ Not Available'}
|
| 581 |
+
- Chart Patterns: {'✅ STRONG PATTERN' if pattern_available and pattern_analysis.get('pattern_confidence', 0) > 0.7 else '✅ WEAK PATTERN' if pattern_available else '❌ Not Available'}
|
| 582 |
+
|
| 583 |
+
**⚠️ IMPORTANT: Decisions should be based ONLY on available real data.**
|
| 584 |
+
**🎯 PATTERN PRIORITY: Give significant weight to chart patterns when available with high confidence.**
|
| 585 |
+
"""
|
| 586 |
+
|
| 587 |
+
def _format_advanced_indicators(self, advanced_indicators):
|
| 588 |
+
if not advanced_indicators:
|
| 589 |
+
return "❌ No data for advanced indicators."
|
| 590 |
+
|
| 591 |
+
summary = []
|
| 592 |
+
for timeframe, indicators in advanced_indicators.items():
|
| 593 |
+
if indicators:
|
| 594 |
+
parts = []
|
| 595 |
+
if 'rsi' in indicators: parts.append(f"RSI: {indicators['rsi']:.2f}")
|
| 596 |
+
if 'macd_hist' in indicators: parts.append(f"MACD Hist: {indicators['macd_hist']:.4f}")
|
| 597 |
+
if 'volume_ratio' in indicators: parts.append(f"Volume: {indicators['volume_ratio']:.2f}x")
|
| 598 |
+
if parts:
|
| 599 |
+
summary.append(f"\n📊 **{timeframe}:** {', '.join(parts)}")
|
| 600 |
+
|
| 601 |
+
return "\n".join(summary) if summary else "⚠️ Insufficient indicator data."
|
| 602 |
+
|
| 603 |
+
def _format_strategies_analysis(self, strategy_scores, recommended_strategy):
|
| 604 |
+
if not strategy_scores:
|
| 605 |
+
return "❌ No strategy data available."
|
| 606 |
+
|
| 607 |
+
summary = [f"🎯 **Recommended Strategy:** {recommended_strategy}"]
|
| 608 |
+
sorted_scores = sorted(strategy_scores.items(), key=lambda item: item[1], reverse=True)
|
| 609 |
+
for strategy, score in sorted_scores:
|
| 610 |
+
if isinstance(score, (int, float)):
|
| 611 |
+
score_display = f"{score:.3f}"
|
| 612 |
+
else:
|
| 613 |
+
score_display = str(score)
|
| 614 |
+
summary.append(f" • {strategy}: {score_display}")
|
| 615 |
+
|
| 616 |
+
return "\n".join(summary)
|
| 617 |
+
|
| 618 |
+
def _format_pattern_analysis_enhanced(self, pattern_analysis, payload):
|
| 619 |
+
"""تنسيق محسن لقسم تحليل النمط"""
|
| 620 |
+
if not pattern_analysis:
|
| 621 |
+
return """
|
| 622 |
+
❌ **CHART PATTERN STATUS: NO CLEAR PATTERNS DETECTED**
|
| 623 |
+
- Reason: Insufficient data or no recognizable patterns in current chart
|
| 624 |
+
- Impact: Decision will rely more on technical indicators and market context
|
| 625 |
+
- Recommendation: Proceed with caution, use wider stops
|
| 626 |
+
"""
|
| 627 |
+
|
| 628 |
+
confidence = pattern_analysis.get('pattern_confidence', 0)
|
| 629 |
+
pattern_name = pattern_analysis.get('pattern_detected', 'unknown')
|
| 630 |
+
strength = pattern_analysis.get('pattern_strength', 'unknown')
|
| 631 |
+
|
| 632 |
+
if confidence >= 0.7:
|
| 633 |
+
status = "✅ **HIGH-CONFIDENCE PATTERN DETECTED**"
|
| 634 |
+
influence = "This pattern should SIGNIFICANTLY influence your trading decision"
|
| 635 |
+
elif confidence >= 0.5:
|
| 636 |
+
status = "⚠️ **MEDIUM-CONFIDENCE PATTERN DETECTED**"
|
| 637 |
+
influence = "Consider this pattern but verify with other indicators"
|
| 638 |
+
else:
|
| 639 |
+
status = "📊 **LOW-CONFIDENCE PATTERN DETECTED**"
|
| 640 |
+
influence = "Use this pattern as supplementary information only"
|
| 641 |
+
|
| 642 |
+
analysis_lines = [
|
| 643 |
+
status,
|
| 644 |
+
f"**Pattern:** {pattern_name}",
|
| 645 |
+
f"**Confidence:** {confidence:.1%}",
|
| 646 |
+
f"**Strength:** {strength}",
|
| 647 |
+
f"**Predicted Move:** {pattern_analysis.get('predicted_direction', 'N/A')} "
|
| 648 |
+
f"by {pattern_analysis.get('predicted_movement_percent', 0):.2f}%",
|
| 649 |
+
f"**Timeframe:** {pattern_analysis.get('timeframe_expectation', 'N/A')}",
|
| 650 |
+
f"**Influence:** {influence}",
|
| 651 |
+
"",
|
| 652 |
+
"**PATTERN-SPECIFIC SUGGESTIONS:**",
|
| 653 |
+
f"Entry: {pattern_analysis.get('entry_suggestion', 'N/A')}",
|
| 654 |
+
f"Target: {pattern_analysis.get('target_suggestion', 'N/A')}",
|
| 655 |
+
f"Stop: {pattern_analysis.get('stop_suggestion', 'N/A')}",
|
| 656 |
+
f"Key Support: {pattern_analysis.get('key_support', 'N/A')}",
|
| 657 |
+
f"Key Resistance: {pattern_analysis.get('key_resistance', 'N/A')}",
|
| 658 |
+
"",
|
| 659 |
+
f"**Analysis:** {pattern_analysis.get('pattern_analysis', 'No detailed analysis available')}"
|
| 660 |
+
]
|
| 661 |
+
|
| 662 |
+
return "\n".join(analysis_lines)
|
| 663 |
+
|
| 664 |
+
def _format_whale_analysis_for_llm(self, general_whale_activity, symbol_whale_data, symbol):
|
| 665 |
+
analysis_parts = []
|
| 666 |
+
|
| 667 |
+
if general_whale_activity.get('data_available', False):
|
| 668 |
+
critical_flag = " 🚨 CRITICAL ALERT" if general_whale_activity.get('critical_alert') else ""
|
| 669 |
+
analysis_parts.append(f"📊 **General Market:** {general_whale_activity.get('description', 'Activity detected')}{critical_flag}")
|
| 670 |
+
else:
|
| 671 |
+
analysis_parts.append("📊 **General Market:** No significant general whale data available")
|
| 672 |
+
|
| 673 |
+
if symbol_whale_data.get('data_available', False):
|
| 674 |
+
analysis_parts.append(f"🎯 **{symbol} Specific:** {symbol_whale_data.get('description', 'Activity detected')}")
|
| 675 |
+
else:
|
| 676 |
+
analysis_parts.append(f"🎯 **{symbol} Specific:** No contract-based whale data available")
|
| 677 |
+
|
| 678 |
+
return "\n".join(analysis_parts)
|
| 679 |
+
|
| 680 |
+
async def re_analyze_trade_async(self, trade_data: dict, processed_data: dict):
|
| 681 |
+
try:
|
| 682 |
+
symbol = trade_data['symbol']
|
| 683 |
+
original_strategy = trade_data.get('strategy', 'GENERIC')
|
| 684 |
+
|
| 685 |
+
if not original_strategy or original_strategy == 'unknown':
|
| 686 |
+
original_strategy = trade_data.get('decision_data', {}).get('strategy', 'GENERIC')
|
| 687 |
+
print(f"🔧 Fixed missing original strategy for {symbol}: {original_strategy}")
|
| 688 |
+
|
| 689 |
+
print(f"🧠 Starting LLM re-analysis for {symbol} with strategy: {original_strategy}...")
|
| 690 |
+
|
| 691 |
+
news_text = await self.news_fetcher.get_news_for_symbol(symbol)
|
| 692 |
+
pattern_analysis = await self._get_pattern_analysis(processed_data)
|
| 693 |
+
prompt = self._create_enhanced_re_analysis_prompt(trade_data, processed_data, news_text, pattern_analysis)
|
| 694 |
+
|
| 695 |
+
async with self.semaphore:
|
| 696 |
+
response = await self._call_llm(prompt)
|
| 697 |
+
|
| 698 |
+
re_analysis_dict = self._parse_re_analysis_response_enhanced(response, original_strategy, symbol)
|
| 699 |
+
if re_analysis_dict:
|
| 700 |
+
re_analysis_dict['model_source'] = self.model_name
|
| 701 |
+
|
| 702 |
+
final_strategy = re_analysis_dict.get('strategy')
|
| 703 |
+
if not final_strategy or final_strategy == 'unknown':
|
| 704 |
+
re_analysis_dict['strategy'] = original_strategy
|
| 705 |
+
print(f"🔧 Final re-analysis strategy correction for {symbol}: {original_strategy}")
|
| 706 |
+
else:
|
| 707 |
+
print(f"✅ LLM re-analysis confirmed strategy '{final_strategy}' for {symbol}.")
|
| 708 |
+
|
| 709 |
+
print(f"✅ LLM re-analysis completed for {symbol} - Strategy: {re_analysis_dict['strategy']}")
|
| 710 |
+
else:
|
| 711 |
+
print(f"❌ LLM re-analysis failed for {symbol}")
|
| 712 |
+
return local_re_analyze_trade(trade_data, processed_data)
|
| 713 |
+
|
| 714 |
+
return re_analysis_dict
|
| 715 |
+
|
| 716 |
+
except Exception as e:
|
| 717 |
+
print(f"❌ Unexpected error in enhanced LLM re-analysis: {e}")
|
| 718 |
+
return local_re_analyze_trade(trade_data, processed_data)
|
| 719 |
+
|
| 720 |
+
def _parse_re_analysis_response_enhanced(self, response_text: str, fallback_strategy: str = 'GENERIC', symbol: str = 'unknown') -> dict:
|
| 721 |
+
"""✅ الإصلاح النهائي: تحليل رد إعادة التحليل مع إعطاء الثقة لقراره"""
|
| 722 |
+
try:
|
| 723 |
+
json_match = re.search(r'```json\n(.*?)\n```', response_text, re.DOTALL)
|
| 724 |
+
if json_match:
|
| 725 |
+
json_str = json_match.group(1).strip()
|
| 726 |
+
else:
|
| 727 |
+
json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
| 728 |
+
if json_match:
|
| 729 |
+
json_str = json_match.group()
|
| 730 |
+
else:
|
| 731 |
+
print(f"❌ No JSON found in re-analysis response for {symbol}: {response_text}")
|
| 732 |
+
return None
|
| 733 |
+
|
| 734 |
+
decision_data = json.loads(json_str)
|
| 735 |
+
|
| 736 |
+
strategy_value = decision_data.get('strategy')
|
| 737 |
+
# 💡 التحقق: هل الاستراتيجية التي أرجعها النموذج صالحة؟
|
| 738 |
+
if not strategy_value or strategy_value == 'unknown' or strategy_value is None:
|
| 739 |
+
# إذا كانت غير صالحة، استخدم الاستراتيجية الأصلية كخطة بديلة آمنة
|
| 740 |
+
print(f"⚠️ LLM re-analysis returned invalid strategy '{strategy_value}' for {symbol}. Forcing fallback: {fallback_strategy}")
|
| 741 |
+
decision_data['strategy'] = fallback_strategy
|
| 742 |
+
else:
|
| 743 |
+
# إذا كانت صالحة، اعتمدها مباشرةً!
|
| 744 |
+
print(f"✅ LLM re-analysis confirmed strategy '{strategy_value}' for {symbol}.")
|
| 745 |
+
|
| 746 |
+
return decision_data
|
| 747 |
+
|
| 748 |
+
except Exception as e:
|
| 749 |
+
print(f"❌ Unexpected error parsing re-analysis response for {symbol}: {e}")
|
| 750 |
+
return None
|
| 751 |
+
|
| 752 |
+
def _create_enhanced_re_analysis_prompt(self, trade_data: dict, processed_data: dict, news_text: str, pattern_analysis: dict) -> str:
|
| 753 |
+
symbol = trade_data.get('symbol', 'N/A')
|
| 754 |
+
entry_price = trade_data.get('entry_price', 'N/A')
|
| 755 |
+
current_price = processed_data.get('current_price', 'N/A')
|
| 756 |
+
strategy = trade_data.get('strategy', 'GENERIC')
|
| 757 |
+
|
| 758 |
+
if not strategy or strategy == 'unknown':
|
| 759 |
+
strategy = 'GENERIC'
|
| 760 |
+
|
| 761 |
+
try:
|
| 762 |
+
price_change = ((current_price - entry_price) / entry_price) * 100
|
| 763 |
+
performance_status = "Profit" if price_change > 0 else "Loss"
|
| 764 |
+
price_change_display = f"{price_change:+.2f}%"
|
| 765 |
+
except (TypeError, ZeroDivisionError):
|
| 766 |
+
price_change_display = "N/A"
|
| 767 |
+
performance_status = "Unknown"
|
| 768 |
+
|
| 769 |
+
indicators_summary = self._format_advanced_indicators(processed_data.get('advanced_indicators', {}))
|
| 770 |
+
pattern_summary = self._format_pattern_analysis_enhanced(pattern_analysis, processed_data)
|
| 771 |
+
whale_analysis_section = self._format_whale_analysis_for_llm(processed_data.get('sentiment_data', {}).get('general_whale_activity', {}), processed_data.get('whale_data', {}), symbol)
|
| 772 |
+
|
| 773 |
+
prompt = f"""
|
| 774 |
+
🔄 **ENHANCED TRADE RE-ANALYSIS WITH CHART PATTERNS**
|
| 775 |
+
|
| 776 |
+
You are re-analyzing an open trade with new market data and chart patterns.
|
| 777 |
+
|
| 778 |
+
**TRADE CONTEXT ({strategy} STRATEGY):**
|
| 779 |
+
- Original Strategy: {strategy}
|
| 780 |
+
- Symbol: {symbol}
|
| 781 |
+
- Entry Price: {entry_price} USDT
|
| 782 |
+
- Current Price: {current_price} USDT
|
| 783 |
+
- Current Performance: {price_change_display} ({performance_status})
|
| 784 |
+
- Original Strategy: {strategy}
|
| 785 |
+
|
| 786 |
+
**UPDATED CHART PATTERN ANALYSIS:**
|
| 787 |
+
{pattern_summary}
|
| 788 |
+
|
| 789 |
+
**NEW MARKET DATA:**
|
| 790 |
+
- Updated Technicals: {indicators_summary}
|
| 791 |
+
- Updated Whale Intel: {whale_analysis_section}
|
| 792 |
+
- Latest News: {news_text}
|
| 793 |
+
|
| 794 |
+
**DECISION STRATEGY FOR {strategy}:**
|
| 795 |
+
- If pattern shows MORE profit potential: UPDATE with new targets and time
|
| 796 |
+
- If pattern suggests WEAKNESS: CLOSE immediately
|
| 797 |
+
- If pattern still VALID but needs more time: UPDATE with extended timing
|
| 798 |
+
- If pattern INVALIDATED: CLOSE to protect capital
|
| 799 |
+
|
| 800 |
+
**PATTERN-BASED DECISION GUIDELINES:**
|
| 801 |
+
- High-confidence patterns (>70%): Give them primary decision weight
|
| 802 |
+
- Medium-confidence patterns (50-70%): Use as supporting evidence
|
| 803 |
+
- Low-confidence patterns (<50%): Use cautiously with other factors
|
| 804 |
+
|
| 805 |
+
**REQUIRED OUTPUTS (JSON ONLY):**
|
| 806 |
+
- `action`: Must be ("HOLD", "CLOSE_TRADE", "UPDATE_TRADE")
|
| 807 |
+
- `reasoning`: Justification based on new data AND pattern analysis
|
| 808 |
+
- `new_stop_loss`: New stop loss if updating (consider pattern levels)
|
| 809 |
+
- `new_take_profit`: New take profit if updating (consider pattern targets)
|
| 810 |
+
- `new_expected_minutes`: New expected time if updating (null otherwise)
|
| 811 |
+
- `confidence_level`: Confidence in this decision (0.00-1.00)
|
| 812 |
+
- `strategy`: "{strategy}" # ⚠️ MUST BE EXACTLY: {strategy}
|
| 813 |
+
- `pattern_influence_reanalysis`: "Describe how updated pattern analysis affected decision"
|
| 814 |
+
|
| 815 |
+
**CRITICAL: You MUST include the 'strategy' field with the exact value: "{strategy}"**
|
| 816 |
+
"""
|
| 817 |
+
return prompt
|
| 818 |
+
|
| 819 |
+
@_rate_limit_nvidia_api
|
| 820 |
+
async def _call_llm(self, prompt: str) -> str:
|
| 821 |
+
try:
|
| 822 |
+
response = self.client.chat.completions.create(
|
| 823 |
+
model=self.model_name,
|
| 824 |
+
messages=[{"role": "user", "content": prompt}],
|
| 825 |
+
temperature=self.temperature,
|
| 826 |
+
seed=42
|
| 827 |
+
)
|
| 828 |
+
return response.choices[0].message.content
|
| 829 |
+
except (RateLimitError, APITimeoutError) as e:
|
| 830 |
+
print(f"❌ LLM API Error: {e}. Retrying...")
|
| 831 |
+
raise
|
| 832 |
+
except Exception as e:
|
| 833 |
+
print(f"❌ Unexpected LLM API error: {e}")
|
| 834 |
+
raise
|
| 835 |
+
|
| 836 |
+
# نظام تتبع أداء الأنماط
|
| 837 |
+
class PatternPerformanceTracker:
|
| 838 |
+
def __init__(self):
|
| 839 |
+
self.pattern_success_rates = {}
|
| 840 |
+
self.pattern_history = []
|
| 841 |
+
|
| 842 |
+
async def track_pattern_performance(self, trade_data, pattern_analysis, outcome, profit_percent):
|
| 843 |
+
"""تتبع أداء الأنماط المختلفة"""
|
| 844 |
+
pattern_name = pattern_analysis.get('pattern_detected', 'unknown')
|
| 845 |
+
confidence = pattern_analysis.get('pattern_confidence', 0)
|
| 846 |
+
|
| 847 |
+
if pattern_name not in self.pattern_success_rates:
|
| 848 |
+
self.pattern_success_rates[pattern_name] = {
|
| 849 |
+
'success_count': 0,
|
| 850 |
+
'total_count': 0,
|
| 851 |
+
'total_profit': 0,
|
| 852 |
+
'avg_profit': 0,
|
| 853 |
+
'confidence_sum': 0,
|
| 854 |
+
'avg_confidence': 0
|
| 855 |
+
}
|
| 856 |
+
|
| 857 |
+
stats = self.pattern_success_rates[pattern_name]
|
| 858 |
+
stats['total_count'] += 1
|
| 859 |
+
stats['confidence_sum'] += confidence
|
| 860 |
+
|
| 861 |
+
success = outcome in ["SUCCESS", "CLOSED_BY_REANALYSIS", "CLOSED_BY_MONITOR"] and profit_percent > 0
|
| 862 |
+
if success:
|
| 863 |
+
stats['success_count'] += 1
|
| 864 |
+
stats['total_profit'] += profit_percent
|
| 865 |
+
stats['avg_profit'] = stats['total_profit'] / stats['success_count']
|
| 866 |
+
|
| 867 |
+
stats['avg_confidence'] = stats['confidence_sum'] / stats['total_count']
|
| 868 |
+
|
| 869 |
+
success_rate = stats['success_count'] / stats['total_count']
|
| 870 |
+
|
| 871 |
+
# تسجيل التاريخ
|
| 872 |
+
self.pattern_history.append({
|
| 873 |
+
'timestamp': datetime.now().isoformat(),
|
| 874 |
+
'pattern': pattern_name,
|
| 875 |
+
'confidence': confidence,
|
| 876 |
+
'success': success,
|
| 877 |
+
'profit_percent': profit_percent,
|
| 878 |
+
'symbol': trade_data.get('symbol', 'unknown')
|
| 879 |
+
})
|
| 880 |
+
|
| 881 |
+
print(f"📊 Pattern {pattern_name}: Success Rate {success_rate:.1%}, Avg Profit: {stats['avg_profit']:.2f}%, Avg Confidence: {stats['avg_confidence']:.1%}")
|
| 882 |
+
|
| 883 |
+
return success_rate
|
| 884 |
+
|
| 885 |
+
def get_pattern_recommendations(self):
|
| 886 |
+
"""الحصول على توصيات بناءً على أداء الأنماط"""
|
| 887 |
+
recommendations = []
|
| 888 |
+
|
| 889 |
+
for pattern, stats in self.pattern_success_rates.items():
|
| 890 |
+
if stats['total_count'] >= 3: # على الأقل 3 صفقات لتكوين توصية
|
| 891 |
+
success_rate = stats['success_count'] / stats['total_count']
|
| 892 |
+
|
| 893 |
+
if success_rate > 0.7:
|
| 894 |
+
recommendations.append(f"✅ **{pattern}**: Excellent performance ({success_rate:.1%} success) - Prioritize this pattern")
|
| 895 |
+
elif success_rate > 0.5:
|
| 896 |
+
recommendations.append(f"⚠️ **{pattern}**: Good performance ({success_rate:.1%} success) - Use with confidence")
|
| 897 |
+
elif success_rate < 0.3:
|
| 898 |
+
recommendations.append(f"❌ **{pattern}**: Poor performance ({success_rate:.1%} success) - Use cautiously")
|
| 899 |
+
|
| 900 |
+
return recommendations
|
| 901 |
+
|
| 902 |
+
# إنشاء نسخة عالمية من متتبع الأداء
|
| 903 |
+
pattern_tracker_global = PatternPerformanceTracker()
|
| 904 |
+
|
| 905 |
+
def local_analyze_opportunity(candidate_data):
|
| 906 |
+
"""تحليل محسن مع مراعاة مخاطر RSI"""
|
| 907 |
+
score = candidate_data.get('enhanced_final_score', candidate_data.get('final_score', 0))
|
| 908 |
+
quality_warnings = candidate_data.get('quality_warnings', [])
|
| 909 |
+
|
| 910 |
+
strategy = candidate_data.get('target_strategy', 'GENERIC')
|
| 911 |
+
|
| 912 |
+
rsi_critical = any('🚨 RSI CRITICAL' in warning for warning in quality_warnings)
|
| 913 |
+
rsi_warning = any('⚠️ RSI WARNING' in warning for warning in quality_warnings)
|
| 914 |
+
|
| 915 |
+
if rsi_critical:
|
| 916 |
+
return {
|
| 917 |
+
"action": "HOLD",
|
| 918 |
+
"reasoning": "Local analysis: CRITICAL RSI levels detected - extreme overbought condition. High risk of correction.",
|
| 919 |
+
"trade_type": "NONE",
|
| 920 |
+
"stop_loss": None,
|
| 921 |
+
"take_profit": None,
|
| 922 |
+
"expected_target_minutes": 15,
|
| 923 |
+
"confidence_level": 0.1,
|
| 924 |
+
"model_source": "local_safety_filter",
|
| 925 |
+
"strategy": strategy
|
| 926 |
+
}
|
| 927 |
+
|
| 928 |
+
advanced_indicators = candidate_data.get('advanced_indicators', {})
|
| 929 |
+
strategy_scores = candidate_data.get('strategy_scores', {})
|
| 930 |
+
|
| 931 |
+
if not advanced_indicators:
|
| 932 |
+
return {
|
| 933 |
+
"action": "HOLD",
|
| 934 |
+
"reasoning": "Local analysis: Insufficient advanced indicator data.",
|
| 935 |
+
"trade_type": "NONE",
|
| 936 |
+
"stop_loss": None,
|
| 937 |
+
"take_profit": None,
|
| 938 |
+
"expected_target_minutes": 15,
|
| 939 |
+
"confidence_level": 0.3,
|
| 940 |
+
"model_source": "local",
|
| 941 |
+
"strategy": strategy
|
| 942 |
+
}
|
| 943 |
+
|
| 944 |
+
action = "HOLD"
|
| 945 |
+
reasoning = "Local analysis: No strong buy signal based on enhanced rules."
|
| 946 |
+
trade_type = "NONE"
|
| 947 |
+
stop_loss = None
|
| 948 |
+
take_profit = None
|
| 949 |
+
expected_minutes = 15
|
| 950 |
+
confidence = 0.3
|
| 951 |
+
|
| 952 |
+
five_minute_indicators = advanced_indicators.get('5m', {})
|
| 953 |
+
one_hour_indicators = advanced_indicators.get('1h', {})
|
| 954 |
+
|
| 955 |
+
buy_conditions = 0
|
| 956 |
+
total_conditions = 0
|
| 957 |
+
|
| 958 |
+
if isinstance(score, (int, float)) and score > 0.70:
|
| 959 |
+
buy_conditions += 1
|
| 960 |
+
total_conditions += 1
|
| 961 |
+
|
| 962 |
+
rsi_five_minute = five_minute_indicators.get('rsi', 50)
|
| 963 |
+
if 30 <= rsi_five_minute <= 65:
|
| 964 |
+
buy_conditions += 1
|
| 965 |
+
total_conditions += 1
|
| 966 |
+
|
| 967 |
+
if five_minute_indicators.get('macd_hist', 0) > 0:
|
| 968 |
+
buy_conditions += 1
|
| 969 |
+
total_conditions += 1
|
| 970 |
+
|
| 971 |
+
if (five_minute_indicators.get('ema_9', 0) > five_minute_indicators.get('ema_21', 0) and
|
| 972 |
+
one_hour_indicators.get('ema_9', 0) > one_hour_indicators.get('ema_21', 0)):
|
| 973 |
+
buy_conditions += 1
|
| 974 |
+
total_conditions += 1
|
| 975 |
+
|
| 976 |
+
if five_minute_indicators.get('volume_ratio', 0) > 1.5:
|
| 977 |
+
buy_conditions += 1
|
| 978 |
+
total_conditions += 1
|
| 979 |
+
|
| 980 |
+
confidence = buy_conditions / total_conditions if total_conditions > 0 else 0.3
|
| 981 |
+
|
| 982 |
+
if rsi_warning:
|
| 983 |
+
confidence *= 0.7
|
| 984 |
+
reasoning += " RSI warning applied."
|
| 985 |
+
|
| 986 |
+
if confidence >= 0.6:
|
| 987 |
+
action = "BUY"
|
| 988 |
+
current_price = candidate_data['current_price']
|
| 989 |
+
trade_type = "LONG"
|
| 990 |
+
|
| 991 |
+
if rsi_warning:
|
| 992 |
+
stop_loss = current_price * 0.93
|
| 993 |
+
else:
|
| 994 |
+
stop_loss = current_price * 0.95
|
| 995 |
+
|
| 996 |
+
if 'bb_upper' in five_minute_indicators:
|
| 997 |
+
take_profit = five_minute_indicators['bb_upper'] * 1.02
|
| 998 |
+
else:
|
| 999 |
+
take_profit = current_price * 1.05
|
| 1000 |
+
|
| 1001 |
+
if confidence >= 0.8:
|
| 1002 |
+
expected_minutes = 10
|
| 1003 |
+
elif confidence >= 0.6:
|
| 1004 |
+
expected_minutes = 18
|
| 1005 |
+
else:
|
| 1006 |
+
expected_minutes = 25
|
| 1007 |
+
|
| 1008 |
+
reasoning = f"Local enhanced analysis: Strong buy signal with {buy_conditions}/{total_conditions} conditions met. Strategy: {strategy}. Confidence: {confidence:.2f}"
|
| 1009 |
+
if rsi_warning:
|
| 1010 |
+
reasoning += " (RSI warning - trading with caution)"
|
| 1011 |
+
|
| 1012 |
+
return {
|
| 1013 |
+
"action": action,
|
| 1014 |
+
"reasoning": reasoning,
|
| 1015 |
+
"trade_type": trade_type,
|
| 1016 |
+
"stop_loss": stop_loss,
|
| 1017 |
+
"take_profit": take_profit,
|
| 1018 |
+
"expected_target_minutes": expected_minutes,
|
| 1019 |
+
"confidence_level": confidence,
|
| 1020 |
+
"model_source": "local",
|
| 1021 |
+
"strategy": strategy
|
| 1022 |
+
}
|
| 1023 |
+
|
| 1024 |
+
def local_re_analyze_trade(trade_data, processed_data):
|
| 1025 |
+
current_price = processed_data['current_price']
|
| 1026 |
+
stop_loss = trade_data['stop_loss']
|
| 1027 |
+
take_profit = trade_data['take_profit']
|
| 1028 |
+
action = "HOLD"
|
| 1029 |
+
reasoning = "Local re-analysis: No significant change to trigger an update or close."
|
| 1030 |
+
if stop_loss and current_price <= stop_loss:
|
| 1031 |
+
action = "CLOSE_TRADE"
|
| 1032 |
+
reasoning = "Local re-analysis: Stop loss has been hit."
|
| 1033 |
+
elif take_profit and current_price >= take_profit:
|
| 1034 |
+
action = "CLOSE_TRADE"
|
| 1035 |
+
reasoning = "Local re-analysis: Take profit has been hit."
|
| 1036 |
+
|
| 1037 |
+
strategy = trade_data.get('strategy', 'GENERIC')
|
| 1038 |
+
if strategy == 'unknown':
|
| 1039 |
+
strategy = trade_data.get('decision_data', {}).get('strategy', 'GENERIC')
|
| 1040 |
+
|
| 1041 |
+
return {
|
| 1042 |
+
"action": action,
|
| 1043 |
+
"reasoning": reasoning,
|
| 1044 |
+
"new_stop_loss": None,
|
| 1045 |
+
"new_take_profit": None,
|
| 1046 |
+
"new_expected_minutes": None,
|
| 1047 |
+
"model_source": "local",
|
| 1048 |
+
"strategy": strategy
|
| 1049 |
+
}
|
| 1050 |
+
|
| 1051 |
+
print("✅ ENHANCED LLM Service loaded successfully - ADVANCED PATTERN ANALYSIS - Performance Tracking - Real-time Pattern Integration")
|
ML (31).py
ADDED
|
@@ -0,0 +1,1402 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ML.py - الإصدار المحدث مع إصلاح الأخطاء الحرجة
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import pandas_ta as ta
|
| 4 |
+
import numpy as np
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
import asyncio
|
| 7 |
+
from data_manager import DataManager
|
| 8 |
+
|
| 9 |
+
class AdvancedTechnicalAnalyzer:
|
| 10 |
+
def __init__(self):
|
| 11 |
+
self.indicators_config = {
|
| 12 |
+
'trend': ['ema_9', 'ema_21', 'ema_50', 'ema_200', 'ichimoku', 'adx', 'parabolic_sar', 'dmi'],
|
| 13 |
+
'momentum': ['rsi', 'stoch_rsi', 'macd', 'williams_r', 'cci', 'awesome_oscillator', 'momentum'],
|
| 14 |
+
'volatility': ['bbands', 'atr', 'keltner', 'donchian', 'rvi'],
|
| 15 |
+
'volume': ['vwap', 'obv', 'mfi', 'volume_profile', 'ad', 'volume_oscillator'],
|
| 16 |
+
'cycle': ['hull_ma', 'supertrend', 'zigzag', 'fisher_transform']
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
def calculate_all_indicators(self, dataframe, timeframe):
|
| 20 |
+
if dataframe.empty:
|
| 21 |
+
return {}
|
| 22 |
+
indicators = {}
|
| 23 |
+
indicators.update(self._calculate_trend_indicators(dataframe))
|
| 24 |
+
indicators.update(self._calculate_momentum_indicators(dataframe))
|
| 25 |
+
indicators.update(self._calculate_volatility_indicators(dataframe))
|
| 26 |
+
indicators.update(self._calculate_volume_indicators(dataframe))
|
| 27 |
+
indicators.update(self._calculate_cycle_indicators(dataframe))
|
| 28 |
+
return indicators
|
| 29 |
+
|
| 30 |
+
def _calculate_trend_indicators(self, dataframe):
|
| 31 |
+
trend = {}
|
| 32 |
+
trend['ema_9'] = float(ta.ema(dataframe['close'], length=9).iloc[-1]) if len(dataframe) >= 9 else None
|
| 33 |
+
trend['ema_21'] = float(ta.ema(dataframe['close'], length=21).iloc[-1]) if len(dataframe) >= 21 else None
|
| 34 |
+
trend['ema_50'] = float(ta.ema(dataframe['close'], length=50).iloc[-1]) if len(dataframe) >= 50 else None
|
| 35 |
+
trend['ema_200'] = float(ta.ema(dataframe['close'], length=200).iloc[-1]) if len(dataframe) >= 200 else None
|
| 36 |
+
|
| 37 |
+
if len(dataframe) >= 26:
|
| 38 |
+
ichimoku = ta.ichimoku(dataframe['high'], dataframe['low'], dataframe['close'])
|
| 39 |
+
if ichimoku is not None:
|
| 40 |
+
trend['ichimoku_conversion'] = float(ichimoku[0]['ITS_9'].iloc[-1]) if not ichimoku[0]['ITS_9'].empty else None
|
| 41 |
+
trend['ichimoku_base'] = float(ichimoku[0]['IKS_26'].iloc[-1]) if not ichimoku[0]['IKS_26'].empty else None
|
| 42 |
+
trend['ichimoku_span_a'] = float(ichimoku[0]['ISA_9'].iloc[-1]) if not ichimoku[0]['ISA_9'].empty else None
|
| 43 |
+
trend['ichimoku_span_b'] = float(ichimoku[0]['ISB_26'].iloc[-1]) if not ichimoku[0]['ISB_26'].empty else None
|
| 44 |
+
|
| 45 |
+
if len(dataframe) >= 14:
|
| 46 |
+
adx_result = ta.adx(dataframe['high'], dataframe['low'], dataframe['close'], length=14)
|
| 47 |
+
if adx_result is not None:
|
| 48 |
+
trend['adx'] = float(adx_result['ADX_14'].iloc[-1]) if not adx_result['ADX_14'].empty else None
|
| 49 |
+
trend['dmi_plus'] = float(adx_result['DMP_14'].iloc[-1]) if not adx_result['DMP_14'].empty else None
|
| 50 |
+
trend['dmi_minus'] = float(adx_result['DMN_14'].iloc[-1]) if not adx_result['DMN_14'].empty else None
|
| 51 |
+
|
| 52 |
+
if len(dataframe) >= 5:
|
| 53 |
+
psar = ta.psar(dataframe['high'], dataframe['low'], dataframe['close'])
|
| 54 |
+
if psar is not None:
|
| 55 |
+
trend['psar'] = float(psar['PSARl_0.02_0.2'].iloc[-1]) if not psar['PSARl_0.02_0.2'].empty else None
|
| 56 |
+
return {key: value for key, value in trend.items() if value is not None}
|
| 57 |
+
|
| 58 |
+
def _calculate_momentum_indicators(self, dataframe):
|
| 59 |
+
momentum = {}
|
| 60 |
+
if len(dataframe) >= 14:
|
| 61 |
+
rsi = ta.rsi(dataframe['close'], length=14)
|
| 62 |
+
momentum['rsi'] = float(rsi.iloc[-1]) if not rsi.empty else None
|
| 63 |
+
|
| 64 |
+
if len(dataframe) >= 14:
|
| 65 |
+
stoch_rsi = ta.stochrsi(dataframe['close'], length=14)
|
| 66 |
+
if stoch_rsi is not None:
|
| 67 |
+
momentum['stoch_rsi_k'] = float(stoch_rsi['STOCHRSIk_14_14_3_3'].iloc[-1]) if not stoch_rsi['STOCHRSIk_14_14_3_3'].empty else None
|
| 68 |
+
momentum['stoch_rsi_d'] = float(stoch_rsi['STOCHRSId_14_14_3_3'].iloc[-1]) if not stoch_rsi['STOCHRSId_14_14_3_3'].empty else None
|
| 69 |
+
|
| 70 |
+
if len(dataframe) >= 26:
|
| 71 |
+
macd = ta.macd(dataframe['close'])
|
| 72 |
+
if macd is not None:
|
| 73 |
+
momentum['macd_line'] = float(macd['MACD_12_26_9'].iloc[-1]) if not macd['MACD_12_26_9'].empty else None
|
| 74 |
+
momentum['macd_signal'] = float(macd['MACDs_12_26_9'].iloc[-1]) if not macd['MACDs_12_26_9'].empty else None
|
| 75 |
+
momentum['macd_hist'] = float(macd['MACDh_12_26_9'].iloc[-1]) if not macd['MACDh_12_26_9'].empty else None
|
| 76 |
+
|
| 77 |
+
if len(dataframe) >= 14:
|
| 78 |
+
williams = ta.willr(dataframe['high'], dataframe['low'], dataframe['close'], length=14)
|
| 79 |
+
momentum['williams_r'] = float(williams.iloc[-1]) if not williams.empty else None
|
| 80 |
+
|
| 81 |
+
if len(dataframe) >= 20:
|
| 82 |
+
cci = ta.cci(dataframe['high'], dataframe['low'], dataframe['close'], length=20)
|
| 83 |
+
momentum['cci'] = float(cci.iloc[-1]) if not cci.empty else None
|
| 84 |
+
|
| 85 |
+
if len(dataframe) >= 34:
|
| 86 |
+
awesome_oscillator = ta.ao(dataframe['high'], dataframe['low'])
|
| 87 |
+
momentum['awesome_oscillator'] = float(awesome_oscillator.iloc[-1]) if not awesome_oscillator.empty else None
|
| 88 |
+
|
| 89 |
+
if len(dataframe) >= 10:
|
| 90 |
+
momentum_indicator = ta.mom(dataframe['close'], length=10)
|
| 91 |
+
momentum['momentum'] = float(momentum_indicator.iloc[-1]) if not momentum_indicator.empty else None
|
| 92 |
+
return {key: value for key, value in momentum.items() if value is not None}
|
| 93 |
+
|
| 94 |
+
def _calculate_volatility_indicators(self, dataframe):
|
| 95 |
+
volatility = {}
|
| 96 |
+
if len(dataframe) >= 20:
|
| 97 |
+
bollinger_bands = ta.bbands(dataframe['close'], length=20, std=2)
|
| 98 |
+
if bollinger_bands is not None:
|
| 99 |
+
volatility['bb_upper'] = float(bollinger_bands['BBU_20_2.0'].iloc[-1]) if not bollinger_bands['BBU_20_2.0'].empty else None
|
| 100 |
+
volatility['bb_middle'] = float(bollinger_bands['BBM_20_2.0'].iloc[-1]) if not bollinger_bands['BBM_20_2.0'].empty else None
|
| 101 |
+
volatility['bb_lower'] = float(bollinger_bands['BBL_20_2.0'].iloc[-1]) if not bollinger_bands['BBL_20_2.0'].empty else None
|
| 102 |
+
if all(key in volatility for key in ['bb_upper', 'bb_lower', 'bb_middle']):
|
| 103 |
+
volatility['bb_width'] = (volatility['bb_upper'] - volatility['bb_lower']) / volatility['bb_middle']
|
| 104 |
+
|
| 105 |
+
if len(dataframe) >= 14:
|
| 106 |
+
average_true_range = ta.atr(dataframe['high'], dataframe['low'], dataframe['close'], length=14)
|
| 107 |
+
volatility['atr'] = float(average_true_range.iloc[-1]) if not average_true_range.empty else None
|
| 108 |
+
if volatility['atr']:
|
| 109 |
+
volatility['atr_percent'] = volatility['atr'] / dataframe['close'].iloc[-1]
|
| 110 |
+
|
| 111 |
+
if len(dataframe) >= 20:
|
| 112 |
+
keltner_channel = ta.kc(dataframe['high'], dataframe['low'], dataframe['close'], length=20)
|
| 113 |
+
if keltner_channel is not None:
|
| 114 |
+
volatility['kc_upper'] = float(keltner_channel['KCUe_20_2'].iloc[-1]) if not keltner_channel['KCUe_20_2'].empty else None
|
| 115 |
+
volatility['kc_lower'] = float(keltner_channel['KCLe_20_2'].iloc[-1]) if not keltner_channel['KCLe_20_2'].empty else None
|
| 116 |
+
|
| 117 |
+
if len(dataframe) >= 20:
|
| 118 |
+
donchian_channel = ta.donchian(dataframe['high'], dataframe['low'], length=20)
|
| 119 |
+
if donchian_channel is not None:
|
| 120 |
+
volatility['dc_upper'] = float(donchian_channel['DCU_20_20'].iloc[-1]) if not donchian_channel['DCU_20_20'].empty else None
|
| 121 |
+
volatility['dc_lower'] = float(donchian_channel['DCL_20_20'].iloc[-1]) if not donchian_channel['DCL_20_20'].empty else None
|
| 122 |
+
|
| 123 |
+
if len(dataframe) >= 14:
|
| 124 |
+
relative_volatility_index = ta.rvi(dataframe['close'], length=14)
|
| 125 |
+
volatility['rvi'] = float(relative_volatility_index.iloc[-1]) if not relative_volatility_index.empty else None
|
| 126 |
+
return {key: value for key, value in volatility.items() if value is not None}
|
| 127 |
+
|
| 128 |
+
def _calculate_volume_indicators(self, dataframe):
|
| 129 |
+
volume = {}
|
| 130 |
+
if len(dataframe) >= 1:
|
| 131 |
+
volume_weighted_average_price = ta.vwap(dataframe['high'], dataframe['low'], dataframe['close'], dataframe['volume'])
|
| 132 |
+
volume['vwap'] = float(volume_weighted_average_price.iloc[-1]) if not volume_weighted_average_price.empty else None
|
| 133 |
+
|
| 134 |
+
on_balance_volume = ta.obv(dataframe['close'], dataframe['volume'])
|
| 135 |
+
volume['obv'] = float(on_balance_volume.iloc[-1]) if not on_balance_volume.empty else None
|
| 136 |
+
|
| 137 |
+
if len(dataframe) >= 14:
|
| 138 |
+
money_flow_index = ta.mfi(dataframe['high'], dataframe['low'], dataframe['close'], dataframe['volume'], length=14)
|
| 139 |
+
volume['mfi'] = float(money_flow_index.iloc[-1]) if not money_flow_index.empty else None
|
| 140 |
+
|
| 141 |
+
accumulation_distribution = ta.ad(dataframe['high'], dataframe['low'], dataframe['close'], dataframe['volume'])
|
| 142 |
+
volume['ad_line'] = float(accumulation_distribution.iloc[-1]) if not accumulation_distribution.empty else None
|
| 143 |
+
|
| 144 |
+
if len(dataframe) >= 20:
|
| 145 |
+
volume_oscillator = ta.pvo(dataframe['volume'])
|
| 146 |
+
if volume_oscillator is not None:
|
| 147 |
+
volume['volume_oscillator'] = float(volume_oscillator['PVO_12_26_9'].iloc[-1]) if not volume_oscillator['PVO_12_26_9'].empty else None
|
| 148 |
+
|
| 149 |
+
volume['volume_avg_20'] = float(dataframe['volume'].tail(20).mean()) if len(dataframe) >= 20 else None
|
| 150 |
+
if volume['volume_avg_20'] and volume['volume_avg_20'] > 0:
|
| 151 |
+
volume['volume_ratio'] = float(dataframe['volume'].iloc[-1] / volume['volume_avg_20'])
|
| 152 |
+
return {key: value for key, value in volume.items() if value is not None}
|
| 153 |
+
|
| 154 |
+
def _calculate_cycle_indicators(self, dataframe):
|
| 155 |
+
cycle = {}
|
| 156 |
+
if len(dataframe) >= 9:
|
| 157 |
+
hull_moving_average = ta.hma(dataframe['close'], length=9)
|
| 158 |
+
cycle['hull_ma'] = float(hull_moving_average.iloc[-1]) if not hull_moving_average.empty else None
|
| 159 |
+
|
| 160 |
+
if len(dataframe) >= 10:
|
| 161 |
+
supertrend = ta.supertrend(dataframe['high'], dataframe['low'], dataframe['close'], length=10, multiplier=3)
|
| 162 |
+
if supertrend is not None:
|
| 163 |
+
cycle['supertrend'] = float(supertrend['SUPERT_10_3.0'].iloc[-1]) if not supertrend['SUPERT_10_3.0'].empty else None
|
| 164 |
+
cycle['supertrend_direction'] = float(supertrend['SUPERTd_10_3.0'].iloc[-1]) if not supertrend['SUPERTd_10_3.0'].empty else None
|
| 165 |
+
|
| 166 |
+
if len(dataframe) >= 10:
|
| 167 |
+
fisher_transform = ta.fisher(dataframe['high'], dataframe['low'], length=10)
|
| 168 |
+
if fisher_transform is not None:
|
| 169 |
+
cycle['fisher_transform'] = float(fisher_transform['FISHERT_10_1'].iloc[-1]) if not fisher_transform['FISHERT_10_1'].empty else None
|
| 170 |
+
return {key: value for key, value in cycle.items() if value is not None}
|
| 171 |
+
|
| 172 |
+
class PatternEnhancedStrategyEngine:
|
| 173 |
+
"""محرك استراتيجيات محسن مع دعم متقدم للأنماط البيانية"""
|
| 174 |
+
|
| 175 |
+
def __init__(self, data_manager, learning_engine):
|
| 176 |
+
self.data_manager = data_manager
|
| 177 |
+
self.learning_engine = learning_engine
|
| 178 |
+
self.pattern_success_tracker = PatternPerformanceTracker()
|
| 179 |
+
|
| 180 |
+
async def enhance_strategy_with_patterns(self, strategy_scores, pattern_analysis, symbol):
|
| 181 |
+
"""تعزيز درجات الاستراتيجية بناءً على تحليل الأنماط"""
|
| 182 |
+
if not pattern_analysis or pattern_analysis.get('pattern_detected') in ['no_clear_pattern', 'insufficient_data']:
|
| 183 |
+
return strategy_scores
|
| 184 |
+
|
| 185 |
+
pattern_confidence = pattern_analysis.get('pattern_confidence', 0)
|
| 186 |
+
pattern_name = pattern_analysis.get('pattern_detected', '')
|
| 187 |
+
predicted_direction = pattern_analysis.get('predicted_direction', '')
|
| 188 |
+
|
| 189 |
+
# فقط تعزيز إذا كانت الثقة في النمط عالية
|
| 190 |
+
if pattern_confidence >= 0.7:
|
| 191 |
+
enhancement_factor = self._calculate_pattern_enhancement(pattern_confidence, pattern_name)
|
| 192 |
+
|
| 193 |
+
# تحديد الاستراتيجيات المناسبة للنمط
|
| 194 |
+
enhanced_strategies = self._get_pattern_appropriate_strategies(pattern_name, predicted_direction)
|
| 195 |
+
|
| 196 |
+
for strategy in enhanced_strategies:
|
| 197 |
+
if strategy in strategy_scores:
|
| 198 |
+
strategy_scores[strategy] *= enhancement_factor
|
| 199 |
+
print(f"🎯 Enhanced {strategy} for {symbol} by {enhancement_factor:.1%} due to {pattern_name} pattern")
|
| 200 |
+
|
| 201 |
+
return strategy_scores
|
| 202 |
+
|
| 203 |
+
def _calculate_pattern_enhancement(self, pattern_confidence, pattern_name):
|
| 204 |
+
"""حساب عامل التعزيز بناءً على ثقة النمط ونوعه"""
|
| 205 |
+
base_enhancement = 1.0 + (pattern_confidence * 0.5) # حتى 50% زيادة
|
| 206 |
+
|
| 207 |
+
# أنماط عالية المصداقية تحصل على تعزيز إضافي
|
| 208 |
+
high_reliability_patterns = ['Double Top', 'Double Bottom', 'Head & Shoulders', 'Cup and Handle']
|
| 209 |
+
if pattern_name in high_reliability_patterns:
|
| 210 |
+
base_enhancement *= 1.2
|
| 211 |
+
|
| 212 |
+
return min(base_enhancement, 2.0) # حد أقصى 100% زيادة
|
| 213 |
+
|
| 214 |
+
def _get_pattern_appropriate_strategies(self, pattern_name, direction):
|
| 215 |
+
"""الحصول على الاستراتيجيات المناسبة لنوع النمط"""
|
| 216 |
+
reversal_patterns = ['Double Top', 'Double Bottom', 'Head & Shoulders', 'Triple Top', 'Triple Bottom']
|
| 217 |
+
continuation_patterns = ['Flags', 'Pennants', 'Triangles', 'Rectangles']
|
| 218 |
+
|
| 219 |
+
if pattern_name in reversal_patterns:
|
| 220 |
+
if direction == 'down':
|
| 221 |
+
return ['breakout_momentum', 'trend_following']
|
| 222 |
+
else:
|
| 223 |
+
return ['mean_reversion', 'breakout_momentum']
|
| 224 |
+
|
| 225 |
+
elif pattern_name in continuation_patterns:
|
| 226 |
+
return ['trend_following', 'breakout_momentum']
|
| 227 |
+
|
| 228 |
+
else: # أنماط أخرى
|
| 229 |
+
return ['breakout_momentum', 'hybrid_ai']
|
| 230 |
+
|
| 231 |
+
class PatternPerformanceTracker:
|
| 232 |
+
"""متتبع أداء الأنماط البيانية"""
|
| 233 |
+
|
| 234 |
+
def __init__(self):
|
| 235 |
+
self.pattern_performance = {}
|
| 236 |
+
self.pattern_success_rates = {}
|
| 237 |
+
|
| 238 |
+
async def track_pattern_outcome(self, symbol, pattern_analysis, success, profit_percent):
|
| 239 |
+
"""تتبع نتيجة النمط البياني"""
|
| 240 |
+
if not pattern_analysis:
|
| 241 |
+
return
|
| 242 |
+
|
| 243 |
+
pattern_name = pattern_analysis.get('pattern_detected')
|
| 244 |
+
confidence = pattern_analysis.get('pattern_confidence', 0)
|
| 245 |
+
|
| 246 |
+
if pattern_name not in ['no_clear_pattern', 'insufficient_data']:
|
| 247 |
+
if pattern_name not in self.pattern_performance:
|
| 248 |
+
self.pattern_performance[pattern_name] = {
|
| 249 |
+
'total_trades': 0,
|
| 250 |
+
'successful_trades': 0,
|
| 251 |
+
'total_profit': 0,
|
| 252 |
+
'total_confidence': 0
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
stats = self.pattern_performance[pattern_name]
|
| 256 |
+
stats['total_trades'] += 1
|
| 257 |
+
stats['total_confidence'] += confidence
|
| 258 |
+
|
| 259 |
+
if success:
|
| 260 |
+
stats['successful_trades'] += 1
|
| 261 |
+
stats['total_profit'] += profit_percent
|
| 262 |
+
|
| 263 |
+
success_rate = stats['successful_trades'] / stats['total_trades']
|
| 264 |
+
avg_profit = stats['total_profit'] / stats['successful_trades'] if stats['successful_trades'] > 0 else 0
|
| 265 |
+
avg_confidence = stats['total_confidence'] / stats['total_trades']
|
| 266 |
+
|
| 267 |
+
print(f"📊 Pattern Performance: {pattern_name} - "
|
| 268 |
+
f"Success: {success_rate:.1%} - Avg Profit: {avg_profit:.2f}% - "
|
| 269 |
+
f"Avg Confidence: {avg_confidence:.1%}")
|
| 270 |
+
|
| 271 |
+
def get_pattern_reliability(self, pattern_name):
|
| 272 |
+
"""الحصول على موثوقية النمط"""
|
| 273 |
+
if pattern_name in self.pattern_performance:
|
| 274 |
+
stats = self.pattern_performance[pattern_name]
|
| 275 |
+
if stats['total_trades'] > 0:
|
| 276 |
+
return stats['successful_trades'] / stats['total_trades']
|
| 277 |
+
return 0.5 # قيمة افتراضية
|
| 278 |
+
|
| 279 |
+
class MultiStrategyEngine:
|
| 280 |
+
def __init__(self, data_manager, learning_engine):
|
| 281 |
+
self.data_manager = data_manager
|
| 282 |
+
self.learning_engine = learning_engine
|
| 283 |
+
self.pattern_enhancer = PatternEnhancedStrategyEngine(data_manager, learning_engine)
|
| 284 |
+
self.strategies = {
|
| 285 |
+
'trend_following': self._trend_following_strategy,
|
| 286 |
+
'mean_reversion': self._mean_reversion_strategy,
|
| 287 |
+
'breakout_momentum': self._breakout_momentum_strategy,
|
| 288 |
+
'volume_spike': self._volume_spike_strategy,
|
| 289 |
+
'whale_tracking': self._whale_tracking_strategy,
|
| 290 |
+
'pattern_recognition': self._pattern_recognition_strategy,
|
| 291 |
+
'hybrid_ai': self._hybrid_ai_strategy
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
async def evaluate_all_strategies(self, symbol_data, market_context):
|
| 295 |
+
"""تقييم جميع الاستراتيجيات مع استخدام أوزان نظام التعلم - الإصدار المحسّن"""
|
| 296 |
+
try:
|
| 297 |
+
# ✅ الحصول على الأوزان المحسنة من نظام التعلم
|
| 298 |
+
market_condition = market_context.get('market_trend', 'sideways_market')
|
| 299 |
+
|
| 300 |
+
# ✅ التحقق من وجود learning_engine وتهيئته
|
| 301 |
+
if self.learning_engine and hasattr(self.learning_engine, 'initialized') and self.learning_engine.initialized:
|
| 302 |
+
try:
|
| 303 |
+
optimized_weights = await self.learning_engine.get_optimized_strategy_weights(market_condition)
|
| 304 |
+
print(f"🎯 الأوزان المستخدمة: {optimized_weights}")
|
| 305 |
+
except Exception as e:
|
| 306 |
+
print(f"⚠️ فشل الحصول على الأوزان المحسنة: {e}")
|
| 307 |
+
optimized_weights = await self.get_default_weights()
|
| 308 |
+
else:
|
| 309 |
+
print("⚠️ نظام التعلم غير متوفر، استخدام الأوزان الافتراضية")
|
| 310 |
+
optimized_weights = await self.get_default_weights()
|
| 311 |
+
|
| 312 |
+
strategy_scores = {}
|
| 313 |
+
base_scores = {} # ✅ تخزين الدرجات الأساسية
|
| 314 |
+
|
| 315 |
+
# ✅ تقييم كل استراتيجية مع تطبيق الأوزان
|
| 316 |
+
for strategy_name, strategy_function in self.strategies.items():
|
| 317 |
+
try:
|
| 318 |
+
base_score = await strategy_function(symbol_data, market_context)
|
| 319 |
+
base_scores[strategy_name] = base_score # ✅ حفظ الدرجة الأساسية
|
| 320 |
+
|
| 321 |
+
# ✅ تطبيق الوزن المحسن
|
| 322 |
+
weight = optimized_weights.get(strategy_name, 0.1)
|
| 323 |
+
weighted_score = base_score * weight
|
| 324 |
+
|
| 325 |
+
strategy_scores[strategy_name] = min(weighted_score, 1.0)
|
| 326 |
+
|
| 327 |
+
print(f"📊 {strategy_name}: {base_score:.3f} × {weight:.3f} = {weighted_score:.3f}")
|
| 328 |
+
|
| 329 |
+
except Exception as error:
|
| 330 |
+
print(f"⚠️ Strategy {strategy_name} failed: {error}")
|
| 331 |
+
base_score = await self._fallback_strategy_score(strategy_name, symbol_data, market_context)
|
| 332 |
+
base_scores[strategy_name] = base_score
|
| 333 |
+
strategy_scores[strategy_name] = base_score * optimized_weights.get(strategy_name, 0.1)
|
| 334 |
+
|
| 335 |
+
# ✅ تعزيز الاستراتيجيات بناءً على تحليل الأنماط
|
| 336 |
+
pattern_analysis = symbol_data.get('pattern_analysis')
|
| 337 |
+
if pattern_analysis:
|
| 338 |
+
strategy_scores = await self.pattern_enhancer.enhance_strategy_with_patterns(
|
| 339 |
+
strategy_scores, pattern_analysis, symbol_data.get('symbol')
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
# ✅ تحديد أفضل استراتيجية بناءً على الدرجات الأساسية (بدون أوزان)
|
| 343 |
+
if base_scores:
|
| 344 |
+
best_strategy = max(base_scores.items(), key=lambda x: x[1])
|
| 345 |
+
best_strategy_name = best_strategy[0]
|
| 346 |
+
best_strategy_score = best_strategy[1]
|
| 347 |
+
|
| 348 |
+
symbol_data['recommended_strategy'] = best_strategy_name
|
| 349 |
+
symbol_data['strategy_confidence'] = best_strategy_score
|
| 350 |
+
|
| 351 |
+
print(f"🏆 أفضل استراتيجية (أساسي): {best_strategy_name} بدرجة {best_strategy_score:.3f}")
|
| 352 |
+
|
| 353 |
+
# ✅ تعزيز اختيار الاستراتيجية إذا كان هناك نمط عالي الثقة
|
| 354 |
+
if (pattern_analysis and
|
| 355 |
+
pattern_analysis.get('pattern_confidence', 0) > 0.7 and
|
| 356 |
+
self._is_strategy_pattern_aligned(best_strategy_name, pattern_analysis)):
|
| 357 |
+
|
| 358 |
+
pattern_bonus = pattern_analysis.get('pattern_confidence', 0) * 0.3
|
| 359 |
+
enhanced_confidence = min(best_strategy_score + pattern_bonus, 1.0)
|
| 360 |
+
symbol_data['strategy_confidence'] = enhanced_confidence
|
| 361 |
+
print(f"🎯 تعزيز ثقة الاستراتيجية بواسطة النمط: {enhanced_confidence:.3f}")
|
| 362 |
+
|
| 363 |
+
return strategy_scores, base_scores # ✅ إرجاع كلا النوعين
|
| 364 |
+
|
| 365 |
+
except Exception as error:
|
| 366 |
+
print(f"❌ فشل تقييم الاستراتيجيات: {error}")
|
| 367 |
+
fallback_scores = await self.get_fallback_scores()
|
| 368 |
+
return fallback_scores, fallback_scores
|
| 369 |
+
|
| 370 |
+
def _is_strategy_pattern_aligned(self, strategy_name, pattern_analysis):
|
| 371 |
+
"""التحقق من محاذاة الاستراتيجية مع النمط البياني"""
|
| 372 |
+
pattern_direction = pattern_analysis.get('predicted_direction', '')
|
| 373 |
+
pattern_type = pattern_analysis.get('pattern_detected', '')
|
| 374 |
+
|
| 375 |
+
# استراتيجيات التوجه الصعودي
|
| 376 |
+
bullish_strategies = ['trend_following', 'breakout_momentum']
|
| 377 |
+
# استراتيجيات التوجه الهبوطي
|
| 378 |
+
bearish_strategies = ['mean_reversion', 'breakout_momentum']
|
| 379 |
+
|
| 380 |
+
if pattern_direction == 'up' and strategy_name in bullish_strategies:
|
| 381 |
+
return True
|
| 382 |
+
elif pattern_direction == 'down' and strategy_name in bearish_strategies:
|
| 383 |
+
return True
|
| 384 |
+
|
| 385 |
+
return False
|
| 386 |
+
|
| 387 |
+
async def get_default_weights(self):
|
| 388 |
+
"""الأوزان الافتراضية عندما لا يتوفر نظام التعلم"""
|
| 389 |
+
return {
|
| 390 |
+
'trend_following': 0.15, 'mean_reversion': 0.12,
|
| 391 |
+
'breakout_momentum': 0.18, 'volume_spike': 0.10,
|
| 392 |
+
'whale_tracking': 0.20, 'pattern_recognition': 0.15,
|
| 393 |
+
'hybrid_ai': 0.10
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
async def get_fallback_scores(self):
|
| 397 |
+
"""درجات احتياطية عند فشل التقييم"""
|
| 398 |
+
return {
|
| 399 |
+
'trend_following': 0.5, 'mean_reversion': 0.5,
|
| 400 |
+
'breakout_momentum': 0.5, 'volume_spike': 0.5,
|
| 401 |
+
'whale_tracking': 0.5, 'pattern_recognition': 0.5,
|
| 402 |
+
'hybrid_ai': 0.5
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
async def _trend_following_strategy(self, symbol_data, market_context):
|
| 406 |
+
"""استراتيجية تتبع الاتجاه المحسنة - درجات أعلى"""
|
| 407 |
+
try:
|
| 408 |
+
score = 0.0
|
| 409 |
+
indicators = symbol_data.get('advanced_indicators', {})
|
| 410 |
+
timeframes = ['4h', '1h', '15m']
|
| 411 |
+
|
| 412 |
+
for timeframe in timeframes:
|
| 413 |
+
if timeframe in indicators:
|
| 414 |
+
timeframe_indicators = indicators[timeframe]
|
| 415 |
+
|
| 416 |
+
# تحقق من محاذاة المتوسطات المتحركة
|
| 417 |
+
if self._check_ema_alignment(timeframe_indicators):
|
| 418 |
+
score += 0.20 # ⬆️ زيادة من 0.15 إلى 0.20
|
| 419 |
+
|
| 420 |
+
# مؤشر ADX للقوة الاتجاهية
|
| 421 |
+
adx_value = timeframe_indicators.get('adx', 0)
|
| 422 |
+
if adx_value > 20: # ⬇️ تخفيض من 25 إلى 20
|
| 423 |
+
score += 0.15 # ⬆️ زيادة من 0.1 إلى 0.15
|
| 424 |
+
|
| 425 |
+
# تحليل الحجم
|
| 426 |
+
volume_ratio = timeframe_indicators.get('volume_ratio', 0)
|
| 427 |
+
if volume_ratio > 1.2: # ⬇️ تخفيض من 1.5 إلى 1.2
|
| 428 |
+
score += 0.10 # ⬆️ زيادة من 0.05 إلى 0.10
|
| 429 |
+
|
| 430 |
+
# ✅ تعزيز بناءً على تحليل الأنماط
|
| 431 |
+
pattern_analysis = symbol_data.get('pattern_analysis')
|
| 432 |
+
if (pattern_analysis and
|
| 433 |
+
pattern_analysis.get('pattern_confidence', 0) > 0.7 and
|
| 434 |
+
pattern_analysis.get('predicted_direction') == 'up'):
|
| 435 |
+
pattern_bonus = pattern_analysis.get('pattern_confidence', 0) * 0.2
|
| 436 |
+
score += pattern_bonus
|
| 437 |
+
print(f"📈 Trend following enhanced by pattern: +{pattern_bonus:.3f}")
|
| 438 |
+
|
| 439 |
+
return min(score, 1.0)
|
| 440 |
+
|
| 441 |
+
except Exception as error:
|
| 442 |
+
print(f"⚠️ Trend following strategy error: {error}")
|
| 443 |
+
return 0.3 # ⬆️ زيادة من 0.3 إلى 0.3 (نفس القيمة)
|
| 444 |
+
|
| 445 |
+
def _check_ema_alignment(self, indicators):
|
| 446 |
+
"""التحقق من محاذاة المتوسطات المتحركة"""
|
| 447 |
+
required_emas = ['ema_9', 'ema_21', 'ema_50']
|
| 448 |
+
if all(ema in indicators for ema in required_emas):
|
| 449 |
+
return (indicators['ema_9'] > indicators['ema_21'] > indicators['ema_50'])
|
| 450 |
+
return False
|
| 451 |
+
|
| 452 |
+
async def _mean_reversion_strategy(self, symbol_data, market_context):
|
| 453 |
+
"""استراتيجية العودة إلى المتوسط المحسنة - درجات أعلى"""
|
| 454 |
+
try:
|
| 455 |
+
score = 0.0
|
| 456 |
+
current_price = symbol_data['current_price']
|
| 457 |
+
indicators = symbol_data.get('advanced_indicators', {})
|
| 458 |
+
|
| 459 |
+
if '1h' in indicators:
|
| 460 |
+
hourly_indicators = indicators['1h']
|
| 461 |
+
|
| 462 |
+
# تحليل Bollinger Bands
|
| 463 |
+
if all(key in hourly_indicators for key in ['bb_upper', 'bb_lower', 'bb_middle']):
|
| 464 |
+
position_in_band = (current_price - hourly_indicators['bb_lower']) / (hourly_indicators['bb_upper'] - hourly_indicators['bb_lower'])
|
| 465 |
+
|
| 466 |
+
if position_in_band < 0.1 and hourly_indicators.get('rsi', 50) < 35:
|
| 467 |
+
score += 0.45 # ⬆️ زيادة من 0.4 إلى 0.45
|
| 468 |
+
if position_in_band > 0.9 and hourly_indicators.get('rsi', 50) > 65:
|
| 469 |
+
score += 0.45 # ⬆️ زيادة من 0.4 إلى 0.45
|
| 470 |
+
|
| 471 |
+
# تحليل RSI
|
| 472 |
+
rsi_value = hourly_indicators.get('rsi', 50)
|
| 473 |
+
if rsi_value < 30:
|
| 474 |
+
score += 0.35 # ⬆️ زيادة من 0.3 إلى 0.35
|
| 475 |
+
elif rsi_value > 70:
|
| 476 |
+
score += 0.35 # ⬆️ زيادة من 0.3 إلى 0.35
|
| 477 |
+
|
| 478 |
+
# ✅ تعزيز بناءً على تحليل الأنماط
|
| 479 |
+
pattern_analysis = symbol_data.get('pattern_analysis')
|
| 480 |
+
if (pattern_analysis and
|
| 481 |
+
pattern_analysis.get('pattern_confidence', 0) > 0.7 and
|
| 482 |
+
pattern_analysis.get('predicted_direction') in ['up', 'down']):
|
| 483 |
+
pattern_bonus = pattern_analysis.get('pattern_confidence', 0) * 0.15
|
| 484 |
+
score += pattern_bonus
|
| 485 |
+
print(f"🔄 Mean reversion enhanced by pattern: +{pattern_bonus:.3f}")
|
| 486 |
+
|
| 487 |
+
return min(score, 1.0)
|
| 488 |
+
|
| 489 |
+
except Exception as error:
|
| 490 |
+
print(f"⚠️ Mean reversion strategy error: {error}")
|
| 491 |
+
return 0.3
|
| 492 |
+
|
| 493 |
+
async def _breakout_momentum_strategy(self, symbol_data, market_context):
|
| 494 |
+
"""استراتيجية كسر الزخم المحسنة - درجات أعلى"""
|
| 495 |
+
try:
|
| 496 |
+
score = 0.0
|
| 497 |
+
indicators = symbol_data.get('advanced_indicators', {})
|
| 498 |
+
|
| 499 |
+
for timeframe in ['1h', '15m', '5m']: # ✅ إضافة timeframe إضافية
|
| 500 |
+
if timeframe in indicators:
|
| 501 |
+
timeframe_indicators = indicators[timeframe]
|
| 502 |
+
|
| 503 |
+
# ✅ تحليل الحجم - عتبات مخفضة
|
| 504 |
+
volume_ratio = timeframe_indicators.get('volume_ratio', 0)
|
| 505 |
+
if volume_ratio > 1.8: # ⬇️ تخفيض من 2.0 إلى 1.8
|
| 506 |
+
score += 0.25 # ⬆️ زيادة من 0.2 إلى 0.25
|
| 507 |
+
elif volume_ratio > 1.3: # ✅ إضافة شرط وسيط
|
| 508 |
+
score += 0.15
|
| 509 |
+
|
| 510 |
+
# ✅ تحليل MACD
|
| 511 |
+
if timeframe_indicators.get('macd_hist', 0) > 0:
|
| 512 |
+
score += 0.20 # ⬆️ زيادة من 0.15 إلى 0.20
|
| 513 |
+
|
| 514 |
+
# ✅ تحليل VWAP
|
| 515 |
+
if 'vwap' in timeframe_indicators and symbol_data['current_price'] > timeframe_indicators['vwap']:
|
| 516 |
+
score += 0.15 # ⬆️ زيادة من 0.1 إلى 0.15
|
| 517 |
+
|
| 518 |
+
# ✅ إضافة شرط RSI إضافي
|
| 519 |
+
rsi_value = timeframe_indicators.get('rsi', 50)
|
| 520 |
+
if 40 <= rsi_value <= 70: # نطاق RSI صحي
|
| 521 |
+
score += 0.10
|
| 522 |
+
|
| 523 |
+
# ✅ تعزيز بناءً على تحليل الأنماط - تأثير كبير على breakout
|
| 524 |
+
pattern_analysis = symbol_data.get('pattern_analysis')
|
| 525 |
+
if pattern_analysis and pattern_analysis.get('pattern_confidence', 0) > 0.6:
|
| 526 |
+
pattern_bonus = pattern_analysis.get('pattern_confidence', 0) * 0.3
|
| 527 |
+
score += pattern_bonus
|
| 528 |
+
print(f"🚀 Breakout momentum significantly enhanced by pattern: +{pattern_bonus:.3f}")
|
| 529 |
+
|
| 530 |
+
# ✅ ضمان حد أدنى للدرجة إذا كانت هناك إشارات إيجابية
|
| 531 |
+
if score > 0.2:
|
| 532 |
+
score = max(score, 0.4) # ⬆️ ضمان حد أدنى 0.4 إذا كانت هناك إشارات
|
| 533 |
+
|
| 534 |
+
return min(score, 1.0)
|
| 535 |
+
|
| 536 |
+
except Exception as error:
|
| 537 |
+
print(f"⚠️ Breakout momentum strategy error: {error}")
|
| 538 |
+
return 0.4 # ⬆️ زيادة من 0.3 إلى 0.4
|
| 539 |
+
|
| 540 |
+
async def _volume_spike_strategy(self, symbol_data, market_context):
|
| 541 |
+
"""استراتيجية ارتفاع الحجم المحسنة - درجات أعلى"""
|
| 542 |
+
try:
|
| 543 |
+
score = 0.0
|
| 544 |
+
indicators = symbol_data.get('advanced_indicators', {})
|
| 545 |
+
|
| 546 |
+
for timeframe in ['1h', '15m', '5m']:
|
| 547 |
+
if timeframe in indicators:
|
| 548 |
+
volume_ratio = indicators[timeframe].get('volume_ratio', 0)
|
| 549 |
+
if volume_ratio > 3.0:
|
| 550 |
+
score += 0.45 # ⬆️ زيادة من 0.4 إلى 0.45
|
| 551 |
+
elif volume_ratio > 2.0:
|
| 552 |
+
score += 0.25 # ⬆️ زيادة من 0.2 إلى 0.25
|
| 553 |
+
elif volume_ratio > 1.5: # ✅ إضافة شرط وسيط
|
| 554 |
+
score += 0.15
|
| 555 |
+
|
| 556 |
+
# ✅ تعزيز بناءً على تحليل الأنماط مع ارتفاع الحجم
|
| 557 |
+
pattern_analysis = symbol_data.get('pattern_analysis')
|
| 558 |
+
if (pattern_analysis and
|
| 559 |
+
pattern_analysis.get('pattern_confidence', 0) > 0.7 and
|
| 560 |
+
any(indicators[tf].get('volume_ratio', 0) > 2.0 for tf in ['1h', '15m'] if tf in indicators)):
|
| 561 |
+
pattern_bonus = pattern_analysis.get('pattern_confidence', 0) * 0.2
|
| 562 |
+
score += pattern_bonus
|
| 563 |
+
print(f"💧 Volume spike enhanced by pattern: +{pattern_bonus:.3f}")
|
| 564 |
+
|
| 565 |
+
return min(score, 1.0)
|
| 566 |
+
|
| 567 |
+
except Exception as error:
|
| 568 |
+
print(f"⚠️ Volume spike strategy error: {error}")
|
| 569 |
+
return 0.3
|
| 570 |
+
|
| 571 |
+
async def _whale_tracking_strategy(self, symbol_data, market_context):
|
| 572 |
+
"""استراتيجية تتبع الحيتان المحسنة - درجات أعلى"""
|
| 573 |
+
try:
|
| 574 |
+
score = 0.0
|
| 575 |
+
# ✅ الإصلاح: استخدام الدالة الآمنة الجديدة
|
| 576 |
+
whale_data = await self.data_manager.get_whale_data_safe_async(symbol_data['symbol'])
|
| 577 |
+
|
| 578 |
+
# استخدام البيانات الحقيقية فقط
|
| 579 |
+
if not whale_data.get('data_available', False):
|
| 580 |
+
return 0.2 # ⬆️ زيادة من 0.1 إلى 0.2 (قيمة أساسية أعلى)
|
| 581 |
+
|
| 582 |
+
total_transactions = whale_data.get('transfer_count', 0)
|
| 583 |
+
whale_volume = whale_data.get('total_volume', 0)
|
| 584 |
+
|
| 585 |
+
# ✅ معايير مرنة للسماح بمزيد من المرشحين
|
| 586 |
+
if total_transactions >= 2: # ⬇️ تخفيض من 3 إلى 2
|
| 587 |
+
score += 0.35 # ⬆️ زيادة من 0.3 إلى 0.35
|
| 588 |
+
elif total_transactions >= 1: # ⬇️ تخفيض من 5 إلى 1
|
| 589 |
+
score += 0.25 # ⬆️ زيادة من 0.15 إلى 0.25
|
| 590 |
+
|
| 591 |
+
if whale_volume > 25000: # ⬇️ تخفيض من 50000 إلى 25000
|
| 592 |
+
score += 0.25 # ⬆️ زيادة من 0.2 إلى 0.25
|
| 593 |
+
elif whale_volume > 5000: # ⬇️ تخفيض من 10000 إلى 5000
|
| 594 |
+
score += 0.15 # ⬆️ زيادة من 0.1 إلى 0.15
|
| 595 |
+
|
| 596 |
+
# ✅ إضافة نقاط إضافية بناءً على نشاط الحيتان العام
|
| 597 |
+
general_whale = market_context.get('general_whale_activity', {})
|
| 598 |
+
if general_whale.get('data_available', False) and general_whale.get('transaction_count', 0) > 0:
|
| 599 |
+
score += 0.15 # ⬆️ زيادة من 0.1 إلى 0.15
|
| 600 |
+
|
| 601 |
+
return min(score, 1.0)
|
| 602 |
+
|
| 603 |
+
except Exception as error:
|
| 604 |
+
print(f"⚠️ Whale tracking failed: {error}")
|
| 605 |
+
return 0.2 # ⬆️ زيادة من 0.1 إلى 0.2
|
| 606 |
+
|
| 607 |
+
async def _pattern_recognition_strategy(self, symbol_data, market_context):
|
| 608 |
+
"""استراتيجية التعرف على الأنماط المحسنة - درجات أعلى"""
|
| 609 |
+
try:
|
| 610 |
+
score = 0.0
|
| 611 |
+
indicators = symbol_data.get('advanced_indicators', {})
|
| 612 |
+
|
| 613 |
+
# ✅ تعزيز كبير بناءً على تحليل الأنماط من LLM
|
| 614 |
+
pattern_analysis = symbol_data.get('pattern_analysis')
|
| 615 |
+
if pattern_analysis and pattern_analysis.get('pattern_confidence', 0) > 0.6:
|
| 616 |
+
score += pattern_analysis.get('pattern_confidence', 0) * 0.8
|
| 617 |
+
print(f"🎯 Pattern recognition significantly enhanced: +{score:.3f}")
|
| 618 |
+
else:
|
| 619 |
+
# التحليل التقليدي إذا لم يكن هناك نمط من LLM
|
| 620 |
+
for timeframe in ['4h', '1h']:
|
| 621 |
+
if timeframe in indicators:
|
| 622 |
+
timeframe_indicators = indicators[timeframe]
|
| 623 |
+
|
| 624 |
+
# نمط الزخم الصعودي
|
| 625 |
+
if (timeframe_indicators.get('rsi', 50) > 60 and
|
| 626 |
+
timeframe_indicators.get('macd_hist', 0) > 0 and
|
| 627 |
+
timeframe_indicators.get('volume_ratio', 0) > 1.5):
|
| 628 |
+
score += 0.35 # ⬆️ زيادة من 0.3 إلى 0.35
|
| 629 |
+
|
| 630 |
+
# نمط الزخم الهبوطي
|
| 631 |
+
if (timeframe_indicators.get('rsi', 50) < 40 and
|
| 632 |
+
timeframe_indicators.get('stoch_rsi_k', 50) < 20):
|
| 633 |
+
score += 0.35 # ⬆️ زيادة من 0.3 إلى 0.35
|
| 634 |
+
|
| 635 |
+
return min(score, 1.0)
|
| 636 |
+
|
| 637 |
+
except Exception as error:
|
| 638 |
+
print(f"⚠️ Pattern recognition strategy error: {error}")
|
| 639 |
+
return 0.3
|
| 640 |
+
|
| 641 |
+
async def _hybrid_ai_strategy(self, symbol_data, market_context):
|
| 642 |
+
"""استراتيجية الهجين الذكية المحسنة - درجات أعلى"""
|
| 643 |
+
try:
|
| 644 |
+
score = 0.0
|
| 645 |
+
monte_carlo_probability = symbol_data.get('monte_carlo_probability', 0.5)
|
| 646 |
+
final_score = symbol_data.get('final_score', 0.5)
|
| 647 |
+
|
| 648 |
+
score += monte_carlo_probability * 0.4
|
| 649 |
+
score += final_score * 0.3
|
| 650 |
+
|
| 651 |
+
# تحليل سياق السوق
|
| 652 |
+
if market_context.get('btc_sentiment') == 'BULLISH':
|
| 653 |
+
score += 0.25 # ⬆️ زيادة من 0.2 إلى 0.25
|
| 654 |
+
elif market_context.get('btc_sentiment') == 'BEARISH':
|
| 655 |
+
score -= 0.08 # ⬆️ تخفيض من 0.1 إلى 0.08
|
| 656 |
+
|
| 657 |
+
# تحليل نشاط الحيتان العام
|
| 658 |
+
whale_activity = market_context.get('general_whale_activity', {})
|
| 659 |
+
if whale_activity.get('sentiment') == 'BULLISH':
|
| 660 |
+
score += 0.15 # ⬆️ زيادة من 0.1 إلى 0.15
|
| 661 |
+
|
| 662 |
+
# ✅ تعزيز بناءً على تحليل الأنماط
|
| 663 |
+
pattern_analysis = symbol_data.get('pattern_analysis')
|
| 664 |
+
if pattern_analysis and pattern_analysis.get('pattern_confidence', 0) > 0.7:
|
| 665 |
+
pattern_bonus = pattern_analysis.get('pattern_confidence', 0) * 0.25
|
| 666 |
+
score += pattern_bonus
|
| 667 |
+
print(f"🤖 Hybrid AI enhanced by pattern: +{pattern_bonus:.3f}")
|
| 668 |
+
|
| 669 |
+
return max(0.0, min(score, 1.0))
|
| 670 |
+
|
| 671 |
+
except Exception as error:
|
| 672 |
+
print(f"⚠️ Hybrid AI strategy error: {error}")
|
| 673 |
+
return 0.3
|
| 674 |
+
|
| 675 |
+
async def _fallback_strategy_score(self, strategy_name, symbol_data, market_context):
|
| 676 |
+
"""درجات استراتيجية احتياطية محسنة"""
|
| 677 |
+
try:
|
| 678 |
+
base_score = symbol_data.get('final_score', 0.5)
|
| 679 |
+
|
| 680 |
+
if strategy_name == 'trend_following':
|
| 681 |
+
indicators = symbol_data.get('advanced_indicators', {})
|
| 682 |
+
if '1h' in indicators:
|
| 683 |
+
rsi_value = indicators['1h'].get('rsi', 50)
|
| 684 |
+
ema_9 = indicators['1h'].get('ema_9')
|
| 685 |
+
ema_21 = indicators['1h'].get('ema_21')
|
| 686 |
+
|
| 687 |
+
if ema_9 and ema_21 and ema_9 > ema_21 and 40 <= rsi_value <= 60:
|
| 688 |
+
return 0.6
|
| 689 |
+
return 0.4
|
| 690 |
+
|
| 691 |
+
elif strategy_name == 'mean_reversion':
|
| 692 |
+
current_price = symbol_data.get('current_price', 0)
|
| 693 |
+
indicators = symbol_data.get('advanced_indicators', {})
|
| 694 |
+
if '1h' in indicators:
|
| 695 |
+
rsi_value = indicators['1h'].get('rsi', 50)
|
| 696 |
+
bb_lower = indicators['1h'].get('bb_lower')
|
| 697 |
+
|
| 698 |
+
if bb_lower and current_price <= bb_lower * 1.02 and rsi_value < 35:
|
| 699 |
+
return 0.7
|
| 700 |
+
return 0.3
|
| 701 |
+
|
| 702 |
+
elif strategy_name == 'breakout_momentum':
|
| 703 |
+
volume_ratio = symbol_data.get('advanced_indicators', {}).get('1h', {}).get('volume_ratio', 0)
|
| 704 |
+
if volume_ratio > 1.5:
|
| 705 |
+
return 0.6
|
| 706 |
+
return 0.4
|
| 707 |
+
|
| 708 |
+
elif strategy_name == 'whale_tracking':
|
| 709 |
+
whale_data = symbol_data.get('whale_data', {})
|
| 710 |
+
if not whale_data.get('data_available', False):
|
| 711 |
+
return 0.2 # ⬆️ زيادة من 0.1 إلى 0.2
|
| 712 |
+
|
| 713 |
+
total_transactions = whale_data.get('transfer_count', 0)
|
| 714 |
+
if total_transactions >= 3:
|
| 715 |
+
return 0.5
|
| 716 |
+
return 0.3
|
| 717 |
+
|
| 718 |
+
return base_score
|
| 719 |
+
|
| 720 |
+
except Exception as error:
|
| 721 |
+
print(f"⚠️ Fallback strategy failed for {strategy_name}: {error}")
|
| 722 |
+
return 0.3
|
| 723 |
+
|
| 724 |
+
class MLProcessor:
|
| 725 |
+
def __init__(self, market_context, data_manager, learning_engine):
|
| 726 |
+
self.market_context = market_context
|
| 727 |
+
self.data_manager = data_manager
|
| 728 |
+
self.learning_engine = learning_engine
|
| 729 |
+
self.technical_analyzer = AdvancedTechnicalAnalyzer()
|
| 730 |
+
self.strategy_engine = MultiStrategyEngine(data_manager, learning_engine)
|
| 731 |
+
self.pattern_tracker = PatternPerformanceTracker()
|
| 732 |
+
|
| 733 |
+
def _validate_rsi_safety(self, indicators):
|
| 734 |
+
"""التحقق العاجل من سلامة مؤشر RSI"""
|
| 735 |
+
rsi_warnings = []
|
| 736 |
+
critical_issues = 0
|
| 737 |
+
|
| 738 |
+
timeframes_to_check = ['5m', '15m', '1h', '4h']
|
| 739 |
+
for timeframe in timeframes_to_check:
|
| 740 |
+
if timeframe in indicators:
|
| 741 |
+
rsi_value = indicators[timeframe].get('rsi')
|
| 742 |
+
if rsi_value:
|
| 743 |
+
if rsi_value > 80:
|
| 744 |
+
rsi_warnings.append(f"🚨 RSI CRITICAL in {timeframe}: {rsi_value} - EXTREME OVERBOUGHT")
|
| 745 |
+
critical_issues += 1
|
| 746 |
+
elif rsi_value > 75:
|
| 747 |
+
rsi_warnings.append(f"⚠️ RSI WARNING in {timeframe}: {rsi_value} - STRONG OVERBOUGHT")
|
| 748 |
+
elif rsi_value > 70:
|
| 749 |
+
rsi_warnings.append(f"📈 RSI HIGH in {timeframe}: {rsi_value} - OVERBOUGHT")
|
| 750 |
+
|
| 751 |
+
# إذا كان هناك إطارين زمنيين أو أكثر في منطقة الخطر، نرفض المرشح
|
| 752 |
+
is_safe = critical_issues < 2
|
| 753 |
+
return is_safe, rsi_warnings
|
| 754 |
+
|
| 755 |
+
def _validate_indicators_quality_enhanced(self, indicators, current_price):
|
| 756 |
+
"""تحسين التحقق من جودة المؤشرات"""
|
| 757 |
+
quality_issues = []
|
| 758 |
+
|
| 759 |
+
# التحقق من RSI
|
| 760 |
+
rsi_safe, rsi_warnings = self._validate_rsi_safety(indicators)
|
| 761 |
+
if not rsi_safe:
|
| 762 |
+
quality_issues.extend(rsi_warnings)
|
| 763 |
+
|
| 764 |
+
# التحقق من تناقض المؤشرات
|
| 765 |
+
bullish_signals = 0
|
| 766 |
+
bearish_signals = 0
|
| 767 |
+
|
| 768 |
+
for timeframe, data in indicators.items():
|
| 769 |
+
# إشارات صعودية
|
| 770 |
+
if data.get('macd_hist', 0) > 0:
|
| 771 |
+
bullish_signals += 1
|
| 772 |
+
if data.get('rsi', 50) > 70: # RSI مرتفع يعتبر إشارة بيع
|
| 773 |
+
bearish_signals += 1
|
| 774 |
+
if 'ema_9' in data and 'ema_21' in data:
|
| 775 |
+
if data['ema_9'] > data['ema_21']:
|
| 776 |
+
bullish_signals += 1
|
| 777 |
+
|
| 778 |
+
if bullish_signals > 0 and bearish_signals > bullish_signals:
|
| 779 |
+
quality_issues.append("⚠️ Conflicting signals: More bearish than bullish indicators")
|
| 780 |
+
|
| 781 |
+
return quality_issues
|
| 782 |
+
|
| 783 |
+
def _calculate_enhanced_score_with_safety(self, base_analysis, strategy_scores, quality_issues):
|
| 784 |
+
"""حساب النقاط مع مراعاة عوامل السلامة"""
|
| 785 |
+
base_score = base_analysis.get('final_score', 0.5)
|
| 786 |
+
strategy_average = sum(strategy_scores.values()) / len(strategy_scores) if strategy_scores else 0.5
|
| 787 |
+
|
| 788 |
+
# خصم النقاط بناءً على مشاكل الجودة
|
| 789 |
+
safety_penalty = 0.0
|
| 790 |
+
for issue in quality_issues:
|
| 791 |
+
if '🚨 RSI CRITICAL' in issue:
|
| 792 |
+
safety_penalty += 0.3
|
| 793 |
+
elif '⚠️ RSI WARNING' in issue:
|
| 794 |
+
safety_penalty += 0.15
|
| 795 |
+
elif '📈 RSI HIGH' in issue:
|
| 796 |
+
safety_penalty += 0.05
|
| 797 |
+
|
| 798 |
+
enhanced_score = (base_score * 0.4) + (strategy_average * 0.6)
|
| 799 |
+
enhanced_score = max(0.0, enhanced_score - safety_penalty)
|
| 800 |
+
|
| 801 |
+
return min(enhanced_score, 1.0)
|
| 802 |
+
|
| 803 |
+
async def process_and_score_symbol_enhanced(self, raw_data):
|
| 804 |
+
"""معالجة وتحليل الرمز مع نظام التعلم والاستراتيجيات - الإصدار المحسّن"""
|
| 805 |
+
try:
|
| 806 |
+
if not raw_data or not raw_data.get('ohlcv'):
|
| 807 |
+
print(f"⚠️ Skipping {raw_data.get('symbol', 'unknown')} - no OHLCV data")
|
| 808 |
+
return None
|
| 809 |
+
|
| 810 |
+
# ✅ تمرير بيانات الشموع الخام للتحليل اللاحق
|
| 811 |
+
raw_data['raw_ohlcv'] = raw_data.get('ohlcv', {})
|
| 812 |
+
|
| 813 |
+
base_analysis = await self.process_and_score_symbol(raw_data)
|
| 814 |
+
if not base_analysis:
|
| 815 |
+
return None
|
| 816 |
+
|
| 817 |
+
try:
|
| 818 |
+
# التحقق المحسن من الجودة
|
| 819 |
+
current_price = base_analysis.get('current_price', 0)
|
| 820 |
+
quality_issues = self._validate_indicators_quality_enhanced(
|
| 821 |
+
base_analysis.get('advanced_indicators', {}),
|
| 822 |
+
current_price
|
| 823 |
+
)
|
| 824 |
+
|
| 825 |
+
# طباعة تحذيرات الجودة
|
| 826 |
+
if quality_issues:
|
| 827 |
+
print(f"🔍 Quality issues for {base_analysis.get('symbol')}:")
|
| 828 |
+
for issue in quality_issues:
|
| 829 |
+
print(f" {issue}")
|
| 830 |
+
|
| 831 |
+
# ✅ تقييم الاستراتيجيات باستخدام نظام التعلم مع التحقق من الوجود
|
| 832 |
+
if hasattr(self, 'strategy_engine') and self.strategy_engine:
|
| 833 |
+
strategy_scores, base_scores = await self.strategy_engine.evaluate_all_strategies(base_analysis, self.market_context)
|
| 834 |
+
base_analysis['strategy_scores'] = strategy_scores
|
| 835 |
+
base_analysis['base_strategy_scores'] = base_scores # ✅ حفظ الدرجات الأساسية
|
| 836 |
+
|
| 837 |
+
# ✅ تحديد أفضل استراتيجية بناءً على الدرجات الأساسية (بدون أوزان)
|
| 838 |
+
if base_scores:
|
| 839 |
+
best_strategy = max(base_scores.items(), key=lambda x: x[1])
|
| 840 |
+
best_strategy_name = best_strategy[0]
|
| 841 |
+
best_strategy_score = best_strategy[1]
|
| 842 |
+
|
| 843 |
+
base_analysis['recommended_strategy'] = best_strategy_name
|
| 844 |
+
base_analysis['strategy_confidence'] = best_strategy_score
|
| 845 |
+
|
| 846 |
+
print(f"🎯 أفضل استراتيجية لـ {base_analysis.get('symbol')}: {best_strategy_name} (ثقة: {best_strategy_score:.3f})")
|
| 847 |
+
|
| 848 |
+
# ✅ تخفيض عتبة الثقة للسماح بمزيد من الاستراتيجيات
|
| 849 |
+
if best_strategy_score > 0.3: # ⬇️ تخفيض من 0.6 إلى 0.3
|
| 850 |
+
base_analysis['target_strategy'] = best_strategy_name
|
| 851 |
+
print(f"✅ استخدام استراتيجية متخصصة: {best_strategy_name}")
|
| 852 |
+
else:
|
| 853 |
+
base_analysis['target_strategy'] = 'GENERIC'
|
| 854 |
+
print(f"🔄 استخدام استراتيجية عامة (ثقة منخفضة: {best_strategy_score:.3f})")
|
| 855 |
+
else:
|
| 856 |
+
base_analysis['recommended_strategy'] = 'GENERIC'
|
| 857 |
+
base_analysis['strategy_confidence'] = 0.3 # ⬆️ زيادة من 0.5 إلى 0.3
|
| 858 |
+
base_analysis['target_strategy'] = 'GENERIC'
|
| 859 |
+
print("🔄 استخدام استراتيجية عامة (لا توجد درجات استراتيجية)")
|
| 860 |
+
|
| 861 |
+
# ✅ استخدام الدالة المحسنة لحساب النقاط
|
| 862 |
+
enhanced_score = self._calculate_enhanced_score_with_safety(
|
| 863 |
+
base_analysis, strategy_scores, quality_issues
|
| 864 |
+
)
|
| 865 |
+
|
| 866 |
+
base_analysis['enhanced_final_score'] = enhanced_score
|
| 867 |
+
else:
|
| 868 |
+
print("⚠️ Strategy engine not available, using base analysis only")
|
| 869 |
+
base_analysis['strategy_scores'] = {}
|
| 870 |
+
base_analysis['enhanced_final_score'] = base_analysis.get('final_score', 0.5)
|
| 871 |
+
base_analysis['recommended_strategy'] = 'GENERIC'
|
| 872 |
+
base_analysis['strategy_confidence'] = 0.3
|
| 873 |
+
base_analysis['target_strategy'] = 'GENERIC'
|
| 874 |
+
|
| 875 |
+
base_analysis['quality_warnings'] = quality_issues
|
| 876 |
+
|
| 877 |
+
except Exception as strategy_error:
|
| 878 |
+
print(f"⚠️ Strategy evaluation failed for {base_analysis.get('symbol')}: {strategy_error}")
|
| 879 |
+
base_analysis['strategy_scores'] = {}
|
| 880 |
+
base_analysis['enhanced_final_score'] = base_analysis.get('final_score', 0.5)
|
| 881 |
+
base_analysis['recommended_strategy'] = 'GENERIC'
|
| 882 |
+
base_analysis['strategy_confidence'] = 0.3 # ⬆️ زيادة من 0.5 إلى 0.3
|
| 883 |
+
base_analysis['target_strategy'] = 'GENERIC'
|
| 884 |
+
base_analysis['quality_warnings'] = ['Strategy evaluation failed']
|
| 885 |
+
|
| 886 |
+
return base_analysis
|
| 887 |
+
|
| 888 |
+
except Exception as error:
|
| 889 |
+
print(f"❌ Enhanced processing failed for {raw_data.get('symbol')}: {error}")
|
| 890 |
+
return await self.process_and_score_symbol(raw_data)
|
| 891 |
+
|
| 892 |
+
def _improve_fibonacci_levels(self, daily_dataframe, current_price):
|
| 893 |
+
"""تحسين حساب مستويات Fibonacci لتتوافق مع السعر الحالي"""
|
| 894 |
+
if len(daily_dataframe) < 50:
|
| 895 |
+
return {}
|
| 896 |
+
|
| 897 |
+
# استخدام آخر 50 يومًا لحساب القمة والقاع
|
| 898 |
+
recent_high = float(daily_dataframe['high'].iloc[-50:].max())
|
| 899 |
+
recent_low = float(daily_dataframe['low'].iloc[-50:].min())
|
| 900 |
+
|
| 901 |
+
# إذا كان السعر الحالي خارج النطاق، نعدل النطاق
|
| 902 |
+
if current_price > recent_high:
|
| 903 |
+
recent_high = current_price * 1.05 # نضيف هامش 5%
|
| 904 |
+
if current_price < recent_low:
|
| 905 |
+
recent_low = current_price * 0.95 # نخصم هامش 5%
|
| 906 |
+
|
| 907 |
+
difference = recent_high - recent_low
|
| 908 |
+
if difference <= 0: # تجنب القسمة على الصفر
|
| 909 |
+
return {}
|
| 910 |
+
|
| 911 |
+
return {
|
| 912 |
+
"0.0%": recent_high,
|
| 913 |
+
"23.6%": recent_high - 0.236 * difference,
|
| 914 |
+
"38.2%": recent_high - 0.382 * difference,
|
| 915 |
+
"50.0%": recent_high - 0.50 * difference,
|
| 916 |
+
"61.8%": recent_high - 0.618 * difference,
|
| 917 |
+
"78.6%": recent_high - 0.786 * difference,
|
| 918 |
+
"100.0%": recent_low
|
| 919 |
+
}
|
| 920 |
+
|
| 921 |
+
async def process_and_score_symbol(self, raw_data):
|
| 922 |
+
"""معالجة وتحليل الرمز الأساسي"""
|
| 923 |
+
symbol = raw_data['symbol']
|
| 924 |
+
ohlcv_data = raw_data['ohlcv']
|
| 925 |
+
reasons_for_candidacy = raw_data.get('reasons', [])
|
| 926 |
+
|
| 927 |
+
if not ohlcv_data:
|
| 928 |
+
print(f"❌ No OHLCV data for {symbol}")
|
| 929 |
+
return None
|
| 930 |
+
|
| 931 |
+
try:
|
| 932 |
+
all_indicators = {}
|
| 933 |
+
for timeframe, candles in ohlcv_data.items():
|
| 934 |
+
if candles:
|
| 935 |
+
dataframe = pd.DataFrame(candles, columns=['time', 'open', 'high', 'low', 'close', 'volume'])
|
| 936 |
+
dataframe[['open', 'high', 'low', 'close', 'volume']] = dataframe[['open', 'high', 'low', 'close', 'volume']].astype(float)
|
| 937 |
+
all_indicators[timeframe] = self._calculate_indicators(dataframe, timeframe)
|
| 938 |
+
|
| 939 |
+
hourly_dataframe = pd.DataFrame(ohlcv_data.get('1h', []), columns=['time', 'open', 'high', 'low', 'close', 'volume'])
|
| 940 |
+
if hourly_dataframe.empty:
|
| 941 |
+
print(f"❌ Skipping {symbol} due to insufficient 1h data.")
|
| 942 |
+
return None
|
| 943 |
+
|
| 944 |
+
hourly_dataframe[['open', 'high', 'low', 'close', 'volume']] = hourly_dataframe[['open', 'high', 'low', 'close', 'volume']].astype(float)
|
| 945 |
+
|
| 946 |
+
try:
|
| 947 |
+
current_price = float(hourly_dataframe['close'].iloc[-1])
|
| 948 |
+
if ohlcv_data.get('5m'):
|
| 949 |
+
five_minute_dataframe = pd.DataFrame(ohlcv_data['5m'], columns=['time', 'open', 'high', 'low', 'close', 'volume'])
|
| 950 |
+
if not five_minute_dataframe.empty:
|
| 951 |
+
five_minute_dataframe[['open', 'high', 'low', 'close', 'volume']] = five_minute_dataframe[['open', 'high', 'low', 'close', 'volume']].astype(float)
|
| 952 |
+
current_price = float(five_minute_dataframe['close'].iloc[-1])
|
| 953 |
+
|
| 954 |
+
liquidity_score = self._calculate_liquidity_score(hourly_dataframe)
|
| 955 |
+
daily_dataframe = pd.DataFrame(ohlcv_data.get('1d', []), columns=['time', 'open', 'high', 'low', 'close', 'volume'])
|
| 956 |
+
if not daily_dataframe.empty:
|
| 957 |
+
daily_dataframe[['open', 'high', 'low', 'close', 'volume']] = daily_dataframe[['open', 'high', 'low', 'close', 'volume']].astype(float)
|
| 958 |
+
|
| 959 |
+
average_daily_volume = float(daily_dataframe['volume'].mean()) if not daily_dataframe.empty else 0.0
|
| 960 |
+
|
| 961 |
+
# استخدام الدالة المحسنة لحساب مستويات Fibonacci
|
| 962 |
+
fibonacci_levels = self._improve_fibonacci_levels(daily_dataframe, current_price)
|
| 963 |
+
|
| 964 |
+
try:
|
| 965 |
+
whale_data = await self.data_manager.get_whale_data_safe_async(symbol)
|
| 966 |
+
except Exception as whale_error:
|
| 967 |
+
print(f"⚠️ Whale data failed for {symbol}: {whale_error}. No whale data available.")
|
| 968 |
+
whale_data = {
|
| 969 |
+
"transfer_count": 0,
|
| 970 |
+
"total_volume": 0,
|
| 971 |
+
"source": "no_data",
|
| 972 |
+
"data_available": False
|
| 973 |
+
}
|
| 974 |
+
|
| 975 |
+
whale_score = self._calculate_whale_activity_score(whale_data)
|
| 976 |
+
opportunity_classification = self.classify_opportunity_type(all_indicators, current_price)
|
| 977 |
+
initial_score = self._calculate_initial_score(all_indicators, current_price, self.market_context)
|
| 978 |
+
monte_carlo_probability = self._run_monte_carlo_simulation(hourly_dataframe)
|
| 979 |
+
|
| 980 |
+
print(f"🎲 Monte Carlo Simulation for {symbol}: Success Probability = {monte_carlo_probability:.2%}")
|
| 981 |
+
|
| 982 |
+
final_score = (0.35 * initial_score) + (0.50 * monte_carlo_probability) + (0.15 * whale_score)
|
| 983 |
+
final_score *= opportunity_classification['confidence']
|
| 984 |
+
|
| 985 |
+
normalized_indicators = {timeframe: self._normalize_features_corrected(indicators) for timeframe, indicators in all_indicators.items()}
|
| 986 |
+
|
| 987 |
+
return {
|
| 988 |
+
'symbol': symbol, 'reasons_for_candidacy': reasons_for_candidacy, 'current_price': float(current_price),
|
| 989 |
+
'liquidity_score': float(liquidity_score) if not np.isnan(liquidity_score) else 0.0, 'avg_daily_volume': float(average_daily_volume),
|
| 990 |
+
'whale_data': whale_data, 'whale_score': float(whale_score), 'opportunity_type': opportunity_classification,
|
| 991 |
+
'sentiment_data': self.market_context, 'fibonacci_levels': fibonacci_levels, 'final_score': float(final_score),
|
| 992 |
+
'initial_score': float(initial_score), 'monte_carlo_probability': float(monte_carlo_probability),
|
| 993 |
+
'indicators': normalized_indicators, 'advanced_indicators': all_indicators, 'strategy_scores': {},
|
| 994 |
+
'recommended_strategy': 'GENERIC', 'enhanced_final_score': float(final_score), 'target_strategy': 'GENERIC',
|
| 995 |
+
'raw_ohlcv': ohlcv_data # ✅ إضافة بيانات الشموع الخام
|
| 996 |
+
}
|
| 997 |
+
|
| 998 |
+
except (KeyError, IndexError) as error:
|
| 999 |
+
print(f"⚠️ Missing data for {symbol}: {error}")
|
| 1000 |
+
return None
|
| 1001 |
+
|
| 1002 |
+
except Exception as error:
|
| 1003 |
+
print(f"❌ Failed to process {symbol}: {error}")
|
| 1004 |
+
import traceback
|
| 1005 |
+
traceback.print_exc()
|
| 1006 |
+
return None
|
| 1007 |
+
|
| 1008 |
+
def _calculate_indicators(self, dataframe, timeframe):
|
| 1009 |
+
"""حساب المؤشرات الفنية"""
|
| 1010 |
+
indicators = {}
|
| 1011 |
+
if dataframe.empty:
|
| 1012 |
+
return indicators
|
| 1013 |
+
|
| 1014 |
+
if not isinstance(dataframe.index, pd.DatetimeIndex):
|
| 1015 |
+
try:
|
| 1016 |
+
dataframe['time'] = pd.to_datetime(dataframe['time'], unit='ms')
|
| 1017 |
+
dataframe = dataframe.set_index('time', drop=True)
|
| 1018 |
+
except:
|
| 1019 |
+
dataframe['time'] = pd.to_datetime(dataframe['time'])
|
| 1020 |
+
dataframe = dataframe.set_index('time', drop=True)
|
| 1021 |
+
|
| 1022 |
+
dataframe = dataframe.sort_index()
|
| 1023 |
+
|
| 1024 |
+
if len(dataframe) >= 1 and all(column in dataframe.columns for column in ['high', 'low', 'close', 'volume']):
|
| 1025 |
+
try:
|
| 1026 |
+
typical_price = (dataframe['high'] + dataframe['low'] + dataframe['close']) / 3
|
| 1027 |
+
volume_weighted_average_price = (typical_price * dataframe['volume']).cumsum() / dataframe['volume'].cumsum()
|
| 1028 |
+
if not volume_weighted_average_price.empty and not pd.isna(volume_weighted_average_price.iloc[-1]):
|
| 1029 |
+
indicators['vwap'] = float(volume_weighted_average_price.iloc[-1])
|
| 1030 |
+
except Exception as error:
|
| 1031 |
+
print(f"⚠️ VWAP calculation failed for {timeframe}: {error}")
|
| 1032 |
+
|
| 1033 |
+
if len(dataframe) >= 14:
|
| 1034 |
+
rsi_series = ta.rsi(dataframe['close'], length=14)
|
| 1035 |
+
if rsi_series is not None and not rsi_series.empty and rsi_series.iloc[-1] is not np.nan:
|
| 1036 |
+
indicators['rsi'] = float(rsi_series.iloc[-1])
|
| 1037 |
+
|
| 1038 |
+
if len(dataframe) >= 26:
|
| 1039 |
+
macd = ta.macd(dataframe['close'])
|
| 1040 |
+
if macd is not None and not macd.empty:
|
| 1041 |
+
if 'MACDh_12_26_9' in macd.columns and macd['MACDh_12_26_9'].iloc[-1] is not np.nan:
|
| 1042 |
+
indicators['macd_hist'] = float(macd['MACDh_12_26_9'].iloc[-1])
|
| 1043 |
+
if 'MACD_12_26_9' in macd.columns and macd['MACD_12_26_9'].iloc[-1] is not np.nan:
|
| 1044 |
+
indicators['macd_line'] = float(macd['MACD_12_26_9'].iloc[-1])
|
| 1045 |
+
if 'MACDs_12_26_9' in macd.columns and macd['MACDs_12_26_9'].iloc[-1] is not np.nan:
|
| 1046 |
+
indicators['macd_signal'] = float(macd['MACDs_12_26_9'].iloc[-1])
|
| 1047 |
+
|
| 1048 |
+
if len(dataframe) >= 20:
|
| 1049 |
+
bollinger_bands = ta.bbands(dataframe['close'], length=20, std=2)
|
| 1050 |
+
if bollinger_bands is not None and not bollinger_bands.empty:
|
| 1051 |
+
if 'BBL_20_2.0' in bollinger_bands.columns and bollinger_bands['BBL_20_2.0'].iloc[-1] is not np.nan:
|
| 1052 |
+
indicators['bb_lower'] = float(bollinger_bands['BBL_20_2.0'].iloc[-1])
|
| 1053 |
+
if 'BBU_20_2.0' in bollinger_bands.columns and bollinger_bands['BBU_20_2.0'].iloc[-1] is not np.nan:
|
| 1054 |
+
indicators['bb_upper'] = float(bollinger_bands['BBU_20_2.0'].iloc[-1])
|
| 1055 |
+
if 'BBM_20_2.0' in bollinger_bands.columns and bollinger_bands['BBM_20_2.0'].iloc[-1] is not np.nan:
|
| 1056 |
+
indicators['bb_middle'] = float(bollinger_bands['BBM_20_2.0'].iloc[-1])
|
| 1057 |
+
|
| 1058 |
+
if len(dataframe) >= 14:
|
| 1059 |
+
average_true_range = ta.atr(high=dataframe['high'], low=dataframe['low'], close=dataframe['close'], length=14)
|
| 1060 |
+
if average_true_range is not None and not average_true_range.empty and average_true_range.iloc[-1] is not np.nan:
|
| 1061 |
+
indicators['atr'] = float(average_true_range.iloc[-1])
|
| 1062 |
+
|
| 1063 |
+
if len(dataframe) >= 26:
|
| 1064 |
+
ema_12 = ta.ema(dataframe['close'], length=12)
|
| 1065 |
+
ema_26 = ta.ema(dataframe['close'], length=26)
|
| 1066 |
+
if ema_12 is not None and not ema_12.empty and ema_12.iloc[-1] is not np.nan:
|
| 1067 |
+
indicators['ema_12'] = float(ema_12.iloc[-1])
|
| 1068 |
+
if ema_26 is not None and not ema_26.empty and ema_26.iloc[-1] is not np.nan:
|
| 1069 |
+
indicators['ema_26'] = float(ema_26.iloc[-1])
|
| 1070 |
+
|
| 1071 |
+
return indicators
|
| 1072 |
+
|
| 1073 |
+
def _normalize_features_corrected(self, features):
|
| 1074 |
+
"""تطبيع الميزات بشكل صحيح"""
|
| 1075 |
+
normalized_features = {}
|
| 1076 |
+
for key, value in features.items():
|
| 1077 |
+
if value is None:
|
| 1078 |
+
normalized_features[key] = 0.0
|
| 1079 |
+
continue
|
| 1080 |
+
if key == 'rsi':
|
| 1081 |
+
normalized_features[key] = max(0, min(100, value))
|
| 1082 |
+
elif key in ['macd_hist', 'macd_line', 'macd_signal', 'vwap', 'atr']:
|
| 1083 |
+
normalized_features[key] = value
|
| 1084 |
+
elif 'ema' in key or 'bb_' in key:
|
| 1085 |
+
normalized_features[key] = value
|
| 1086 |
+
else:
|
| 1087 |
+
try:
|
| 1088 |
+
if abs(value) > 1000:
|
| 1089 |
+
normalized_features[key] = value / 1000
|
| 1090 |
+
else:
|
| 1091 |
+
normalized_features[key] = value
|
| 1092 |
+
except:
|
| 1093 |
+
normalized_features[key] = value
|
| 1094 |
+
return normalized_features
|
| 1095 |
+
|
| 1096 |
+
def _run_monte_carlo_simulation(self, dataframe, number_of_simulations=1000, number_of_steps=20):
|
| 1097 |
+
"""تشغيل محاكاة مونت كارلو"""
|
| 1098 |
+
if dataframe.empty or len(dataframe) < 2:
|
| 1099 |
+
return 0.0
|
| 1100 |
+
log_returns = np.log(dataframe['close'] / dataframe['close'].shift(1)).dropna()
|
| 1101 |
+
if log_returns.empty:
|
| 1102 |
+
return 0.0
|
| 1103 |
+
mean_return = log_returns.mean()
|
| 1104 |
+
volatility = log_returns.std()
|
| 1105 |
+
initial_price = dataframe['close'].iloc[-1]
|
| 1106 |
+
success_count = 0
|
| 1107 |
+
for _ in range(number_of_simulations):
|
| 1108 |
+
random_values = np.random.normal(0, 1, number_of_steps)
|
| 1109 |
+
daily_returns = np.exp(mean_return - 0.5 * volatility**2 + volatility * random_values)
|
| 1110 |
+
simulated_prices = initial_price * daily_returns.cumprod()
|
| 1111 |
+
if (simulated_prices[-1] / initial_price) > 1.02:
|
| 1112 |
+
success_count += 1
|
| 1113 |
+
return success_count / number_of_simulations
|
| 1114 |
+
|
| 1115 |
+
def _calculate_initial_score(self, indicators, current_price, market_context):
|
| 1116 |
+
"""حساب النقاط الأولية"""
|
| 1117 |
+
score = 0.5
|
| 1118 |
+
fast_timeframes = ['5m', '15m']
|
| 1119 |
+
for timeframe in fast_timeframes:
|
| 1120 |
+
timeframe_indicators = indicators.get(timeframe, {})
|
| 1121 |
+
if not timeframe_indicators:
|
| 1122 |
+
continue
|
| 1123 |
+
if 'rsi' in timeframe_indicators:
|
| 1124 |
+
rsi_value = timeframe_indicators['rsi']
|
| 1125 |
+
if isinstance(rsi_value, (int, float)):
|
| 1126 |
+
if rsi_value < 30:
|
| 1127 |
+
score += 0.2
|
| 1128 |
+
elif rsi_value < 40:
|
| 1129 |
+
score += 0.1
|
| 1130 |
+
elif rsi_value > 70:
|
| 1131 |
+
score -= 0.1
|
| 1132 |
+
if 'macd_hist' in timeframe_indicators and timeframe_indicators['macd_hist'] > 0:
|
| 1133 |
+
score += 0.15
|
| 1134 |
+
if all(key in timeframe_indicators for key in ['ema_12', 'ema_26']):
|
| 1135 |
+
if timeframe_indicators['ema_12'] > timeframe_indicators['ema_26']:
|
| 1136 |
+
score += 0.15
|
| 1137 |
+
slow_timeframes = ['1h', '4h', '1d']
|
| 1138 |
+
for timeframe in slow_timeframes:
|
| 1139 |
+
timeframe_indicators = indicators.get(timeframe, {})
|
| 1140 |
+
if not timeframe_indicators:
|
| 1141 |
+
continue
|
| 1142 |
+
if all(key in timeframe_indicators for key in ['ema_12', 'ema_26']):
|
| 1143 |
+
if timeframe_indicators['ema_12'] > timeframe_indicators['ema_26']:
|
| 1144 |
+
score += 0.10
|
| 1145 |
+
if all(key in timeframe_indicators for key in ['bb_upper', 'bb_lower']):
|
| 1146 |
+
if current_price > timeframe_indicators['bb_upper']:
|
| 1147 |
+
score += 0.10
|
| 1148 |
+
elif current_price <= timeframe_indicators['bb_lower']:
|
| 1149 |
+
score += 0.05
|
| 1150 |
+
if '5m' in indicators and 'vwap' in indicators['5m'] and current_price > indicators['5m']['vwap']:
|
| 1151 |
+
score += 0.10
|
| 1152 |
+
if market_context:
|
| 1153 |
+
bitcoin_sentiment = market_context.get('btc_sentiment')
|
| 1154 |
+
fear_greed_index = market_context.get('fear_and_greed_index', 50)
|
| 1155 |
+
if bitcoin_sentiment == 'BULLISH' and fear_greed_index > 60:
|
| 1156 |
+
score *= 1.2
|
| 1157 |
+
elif bitcoin_sentiment == 'BEARISH' or fear_greed_index < 30:
|
| 1158 |
+
score *= 0.8
|
| 1159 |
+
return min(max(score, 0.0), 1.0)
|
| 1160 |
+
|
| 1161 |
+
def _normalize_features(self, features):
|
| 1162 |
+
return self._normalize_features_corrected(features)
|
| 1163 |
+
|
| 1164 |
+
def _prepare_data_for_ml(self, all_indicators, current_price):
|
| 1165 |
+
feature_vector = []
|
| 1166 |
+
timeframes = ['5m', '15m', '1h', '4h', '1d']
|
| 1167 |
+
indicator_keys = ['rsi', 'macd_hist', 'macd_line', 'bb_upper', 'bb_lower', 'atr', 'ema_12', 'ema_26', 'vwap']
|
| 1168 |
+
for timeframe in timeframes:
|
| 1169 |
+
timeframe_indicators = all_indicators.get(timeframe, {})
|
| 1170 |
+
for key in indicator_keys:
|
| 1171 |
+
feature_vector.append(timeframe_indicators.get(key, 0.0))
|
| 1172 |
+
feature_vector.append(current_price)
|
| 1173 |
+
return feature_vector
|
| 1174 |
+
|
| 1175 |
+
def _calculate_liquidity_score(self, hourly_dataframe):
|
| 1176 |
+
if hourly_dataframe.empty:
|
| 1177 |
+
return 0.0
|
| 1178 |
+
hourly_dataframe['dollar_volume'] = hourly_dataframe['volume'] * hourly_dataframe['close']
|
| 1179 |
+
return float(hourly_dataframe['dollar_volume'].mean())
|
| 1180 |
+
|
| 1181 |
+
def _calculate_fibonacci_levels(self, daily_dataframe):
|
| 1182 |
+
"""مهملة: استخدام _improve_fibonacci_levels بدلاً من ذلك"""
|
| 1183 |
+
return self._improve_fibonacci_levels(daily_dataframe, 0)
|
| 1184 |
+
|
| 1185 |
+
def classify_opportunity_type(self, indicators, current_price):
|
| 1186 |
+
fast_signals = 0
|
| 1187 |
+
slow_signals = 0
|
| 1188 |
+
for timeframe in ['5m', '15m']:
|
| 1189 |
+
timeframe_indicators = indicators.get(timeframe, {})
|
| 1190 |
+
if not timeframe_indicators:
|
| 1191 |
+
continue
|
| 1192 |
+
if timeframe_indicators.get('rsi', 100) < 35:
|
| 1193 |
+
fast_signals += 1
|
| 1194 |
+
if timeframe_indicators.get('macd_hist', 0) > 0:
|
| 1195 |
+
fast_signals += 1
|
| 1196 |
+
if all(key in timeframe_indicators for key in ['ema_12', 'ema_26']):
|
| 1197 |
+
if timeframe_indicators['ema_12'] > timeframe_indicators['ema_26']:
|
| 1198 |
+
fast_signals += 1
|
| 1199 |
+
if timeframe == '5m' and timeframe_indicators.get('vwap') and current_price > timeframe_indicators['vwap'] * 1.02:
|
| 1200 |
+
fast_signals += 1
|
| 1201 |
+
for timeframe in ['1h', '4h', '1d']:
|
| 1202 |
+
timeframe_indicators = indicators.get(timeframe, {})
|
| 1203 |
+
if not timeframe_indicators:
|
| 1204 |
+
continue
|
| 1205 |
+
if 40 <= timeframe_indicators.get('rsi', 50) <= 60:
|
| 1206 |
+
slow_signals += 1
|
| 1207 |
+
if all(key in timeframe_indicators for key in ['ema_12', 'ema_26']):
|
| 1208 |
+
if timeframe_indicators['ema_12'] > timeframe_indicators['ema_26']:
|
| 1209 |
+
slow_signals += 1
|
| 1210 |
+
if timeframe_indicators.get('bb_middle') and current_price > timeframe_indicators['bb_middle']:
|
| 1211 |
+
slow_signals += 1
|
| 1212 |
+
if fast_signals >= 3:
|
| 1213 |
+
return {
|
| 1214 |
+
"type": "FAST_PUMP", "timeframe": "15m-1h", "take_profit_multiplier": 1.08, "stop_loss_multiplier": 0.97,
|
| 1215 |
+
"confidence": min(fast_signals / 6.0, 1.0), "description": "فرصة صعود سريع قوية على الأطر الزمنية القصيرة"
|
| 1216 |
+
}
|
| 1217 |
+
elif slow_signals >= 3:
|
| 1218 |
+
return {
|
| 1219 |
+
"type": "SLOW_GROWTH", "timeframe": "4h-1d", "take_profit_multiplier": 1.05, "stop_loss_multiplier": 0.95,
|
| 1220 |
+
"confidence": min(slow_signals / 6.0, 1.0), "description": "فرصة نمو مستدام على الأطر الزمنية الطويلة"
|
| 1221 |
+
}
|
| 1222 |
+
return {
|
| 1223 |
+
"type": "NEUTRAL", "timeframe": "N/A", "take_profit_multiplier": 1.05, "stop_loss_multiplier": 0.95,
|
| 1224 |
+
"confidence": 0.3, "description": "لا توجد إشارات واضحة لنوع محدد من الفرص"
|
| 1225 |
+
}
|
| 1226 |
+
|
| 1227 |
+
def _calculate_whale_activity_score(self, whale_data):
|
| 1228 |
+
"""حساب درجة نشاط الحيتان بناءً على البيانات الحقيقية فقط"""
|
| 1229 |
+
if not whale_data.get('data_available', False):
|
| 1230 |
+
return 0.0
|
| 1231 |
+
|
| 1232 |
+
total_transactions = whale_data.get('transfer_count', 0)
|
| 1233 |
+
total_volume = whale_data.get('total_volume', 0)
|
| 1234 |
+
|
| 1235 |
+
score = 0.0
|
| 1236 |
+
if total_transactions >= 10:
|
| 1237 |
+
score += 0.3
|
| 1238 |
+
elif total_transactions >= 5:
|
| 1239 |
+
score += 0.15
|
| 1240 |
+
|
| 1241 |
+
if total_volume > 500000:
|
| 1242 |
+
score += 0.2
|
| 1243 |
+
elif total_volume > 100000:
|
| 1244 |
+
score += 0.1
|
| 1245 |
+
|
| 1246 |
+
return min(score, 0.5)
|
| 1247 |
+
|
| 1248 |
+
def filter_top_candidates(self, candidates, number_of_candidates=10):
|
| 1249 |
+
"""تصفية أفضل المرشحين"""
|
| 1250 |
+
valid_candidates = [candidate for candidate in candidates if candidate is not None]
|
| 1251 |
+
return sorted(valid_candidates, key=lambda candidate: candidate.get('enhanced_final_score', candidate.get('final_score', 0)), reverse=True)[:number_of_candidates]
|
| 1252 |
+
|
| 1253 |
+
# دوال المحاكاة المحلية للاستخدام كبديل
|
| 1254 |
+
def local_analyze_opportunity(candidate_data):
|
| 1255 |
+
"""تحليل محسن مع مراعاة مخاطر RSI"""
|
| 1256 |
+
score = candidate_data.get('enhanced_final_score', candidate_data.get('final_score', 0))
|
| 1257 |
+
quality_warnings = candidate_data.get('quality_warnings', [])
|
| 1258 |
+
|
| 1259 |
+
# التحقق من تحذيرات RSI
|
| 1260 |
+
rsi_critical = any('🚨 RSI CRITICAL' in warning for warning in quality_warnings)
|
| 1261 |
+
rsi_warning = any('⚠️ RSI WARNING' in warning for warning in quality_warnings)
|
| 1262 |
+
|
| 1263 |
+
if rsi_critical:
|
| 1264 |
+
return {
|
| 1265 |
+
"action": "HOLD",
|
| 1266 |
+
"reasoning": "Local analysis: CRITICAL RSI levels detected - extreme overbought condition. High risk of correction.",
|
| 1267 |
+
"trade_type": "NONE",
|
| 1268 |
+
"stop_loss": None,
|
| 1269 |
+
"take_profit": None,
|
| 1270 |
+
"expected_target_minutes": 15,
|
| 1271 |
+
"confidence_level": 0.1,
|
| 1272 |
+
"model_source": "local_safety_filter",
|
| 1273 |
+
"strategy": "GENERIC"
|
| 1274 |
+
}
|
| 1275 |
+
|
| 1276 |
+
advanced_indicators = candidate_data.get('advanced_indicators', {})
|
| 1277 |
+
strategy_scores = candidate_data.get('strategy_scores', {})
|
| 1278 |
+
|
| 1279 |
+
if not advanced_indicators:
|
| 1280 |
+
return {
|
| 1281 |
+
"action": "HOLD",
|
| 1282 |
+
"reasoning": "Local analysis: Insufficient advanced indicator data.",
|
| 1283 |
+
"trade_type": "NONE",
|
| 1284 |
+
"stop_loss": None,
|
| 1285 |
+
"take_profit": None,
|
| 1286 |
+
"expected_target_minutes": 15,
|
| 1287 |
+
"confidence_level": 0.3,
|
| 1288 |
+
"model_source": "local",
|
| 1289 |
+
"strategy": "GENERIC"
|
| 1290 |
+
}
|
| 1291 |
+
|
| 1292 |
+
action = "HOLD"
|
| 1293 |
+
reasoning = "Local analysis: No strong buy signal based on enhanced rules."
|
| 1294 |
+
trade_type = "NONE"
|
| 1295 |
+
stop_loss = None
|
| 1296 |
+
take_profit = None
|
| 1297 |
+
expected_minutes = 15
|
| 1298 |
+
confidence = 0.3
|
| 1299 |
+
|
| 1300 |
+
five_minute_indicators = advanced_indicators.get('5m', {})
|
| 1301 |
+
one_hour_indicators = advanced_indicators.get('1h', {})
|
| 1302 |
+
|
| 1303 |
+
buy_conditions = 0
|
| 1304 |
+
total_conditions = 0
|
| 1305 |
+
|
| 1306 |
+
if isinstance(score, (int, float)) and score > 0.70:
|
| 1307 |
+
buy_conditions += 1
|
| 1308 |
+
total_conditions += 1
|
| 1309 |
+
|
| 1310 |
+
# شرط RSI أكثر تحفظاً
|
| 1311 |
+
rsi_five_minute = five_minute_indicators.get('rsi', 50)
|
| 1312 |
+
if 30 <= rsi_five_minute <= 65: # نطاق آمن لـ RSI
|
| 1313 |
+
buy_conditions += 1
|
| 1314 |
+
total_conditions += 1
|
| 1315 |
+
|
| 1316 |
+
if five_minute_indicators.get('macd_hist', 0) > 0:
|
| 1317 |
+
buy_conditions += 1
|
| 1318 |
+
total_conditions += 1
|
| 1319 |
+
|
| 1320 |
+
if (five_minute_indicators.get('ema_9', 0) > five_minute_indicators.get('ema_21', 0) and
|
| 1321 |
+
one_hour_indicators.get('ema_9', 0) > one_hour_indicators.get('ema_21', 0)):
|
| 1322 |
+
buy_conditions += 1
|
| 1323 |
+
total_conditions += 1
|
| 1324 |
+
|
| 1325 |
+
if five_minute_indicators.get('volume_ratio', 0) > 1.5:
|
| 1326 |
+
buy_conditions += 1
|
| 1327 |
+
total_conditions += 1
|
| 1328 |
+
|
| 1329 |
+
confidence = buy_conditions / total_conditions if total_conditions > 0 else 0.3
|
| 1330 |
+
|
| 1331 |
+
# خصم الثقة بناءً على تحذيرات RSI
|
| 1332 |
+
if rsi_warning:
|
| 1333 |
+
confidence *= 0.7 # خصم 30% للتحذيرات
|
| 1334 |
+
reasoning += " RSI warning applied."
|
| 1335 |
+
|
| 1336 |
+
if confidence >= 0.6:
|
| 1337 |
+
action = "BUY"
|
| 1338 |
+
current_price = candidate_data['current_price']
|
| 1339 |
+
trade_type = "LONG"
|
| 1340 |
+
|
| 1341 |
+
# وقف خسارة أكثر تحفظاً لـ RSI المرتفع
|
| 1342 |
+
if rsi_warning:
|
| 1343 |
+
stop_loss = current_price * 0.93 # 7% stop loss للتحذيرات
|
| 1344 |
+
else:
|
| 1345 |
+
stop_loss = current_price * 0.95 # 5% stop loss عادي
|
| 1346 |
+
|
| 1347 |
+
if 'bb_upper' in five_minute_indicators:
|
| 1348 |
+
take_profit = five_minute_indicators['bb_upper'] * 1.02
|
| 1349 |
+
else:
|
| 1350 |
+
take_profit = current_price * 1.05
|
| 1351 |
+
|
| 1352 |
+
if confidence >= 0.8:
|
| 1353 |
+
expected_minutes = 10
|
| 1354 |
+
elif confidence >= 0.6:
|
| 1355 |
+
expected_minutes = 18
|
| 1356 |
+
else:
|
| 1357 |
+
expected_minutes = 25
|
| 1358 |
+
|
| 1359 |
+
reasoning = f"Local enhanced analysis: Strong buy signal with {buy_conditions}/{total_conditions} conditions met. Confidence: {confidence:.2f}"
|
| 1360 |
+
if rsi_warning:
|
| 1361 |
+
reasoning += " (RSI warning - trading with caution)"
|
| 1362 |
+
|
| 1363 |
+
return {
|
| 1364 |
+
"action": action,
|
| 1365 |
+
"reasoning": reasoning,
|
| 1366 |
+
"trade_type": trade_type,
|
| 1367 |
+
"stop_loss": stop_loss,
|
| 1368 |
+
"take_profit": take_profit,
|
| 1369 |
+
"expected_target_minutes": expected_minutes,
|
| 1370 |
+
"confidence_level": confidence,
|
| 1371 |
+
"model_source": "local",
|
| 1372 |
+
"strategy": "GENERIC"
|
| 1373 |
+
}
|
| 1374 |
+
|
| 1375 |
+
def local_re_analyze_trade(trade_data, processed_data):
|
| 1376 |
+
current_price = processed_data['current_price']
|
| 1377 |
+
stop_loss = trade_data['stop_loss']
|
| 1378 |
+
take_profit = trade_data['take_profit']
|
| 1379 |
+
action = "HOLD"
|
| 1380 |
+
reasoning = "Local re-analysis: No significant change to trigger an update or close."
|
| 1381 |
+
if stop_loss and current_price <= stop_loss:
|
| 1382 |
+
action = "CLOSE_TRADE"
|
| 1383 |
+
reasoning = "Local re-analysis: Stop loss has been hit."
|
| 1384 |
+
elif take_profit and current_price >= take_profit:
|
| 1385 |
+
action = "CLOSE_TRADE"
|
| 1386 |
+
reasoning = "Local re-analysis: Take profit has been hit."
|
| 1387 |
+
|
| 1388 |
+
strategy = trade_data.get('strategy', 'GENERIC')
|
| 1389 |
+
if strategy == 'unknown':
|
| 1390 |
+
strategy = trade_data.get('decision_data', {}).get('strategy', 'GENERIC')
|
| 1391 |
+
|
| 1392 |
+
return {
|
| 1393 |
+
"action": action,
|
| 1394 |
+
"reasoning": reasoning,
|
| 1395 |
+
"new_stop_loss": None,
|
| 1396 |
+
"new_take_profit": None,
|
| 1397 |
+
"new_expected_minutes": None,
|
| 1398 |
+
"model_source": "local",
|
| 1399 |
+
"strategy": strategy
|
| 1400 |
+
}
|
| 1401 |
+
|
| 1402 |
+
print("✅ Enhanced ML System Loaded - Integrated with Learning Engine - REAL DATA ONLY - Optimized Strategy Scoring with Pattern Enhancement")
|
app (32).py
ADDED
|
@@ -0,0 +1,953 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py - الإصدار المحدث مع إصلاح الأخطاء الحرجة
|
| 2 |
+
import os
|
| 3 |
+
import traceback
|
| 4 |
+
import signal
|
| 5 |
+
import sys
|
| 6 |
+
import uvicorn
|
| 7 |
+
import asyncio
|
| 8 |
+
from contextlib import asynccontextmanager
|
| 9 |
+
from fastapi import FastAPI, BackgroundTasks, HTTPException
|
| 10 |
+
from datetime import datetime, timedelta
|
| 11 |
+
from r2 import R2Service
|
| 12 |
+
from LLM import LLMService, local_analyze_opportunity, local_re_analyze_trade
|
| 13 |
+
from data_manager import DataManager
|
| 14 |
+
from ML import MLProcessor as FeatureProcessor
|
| 15 |
+
from learning_engine import LearningEngine
|
| 16 |
+
import time
|
| 17 |
+
import json
|
| 18 |
+
import state
|
| 19 |
+
import re
|
| 20 |
+
|
| 21 |
+
# إعدادات النظام
|
| 22 |
+
TOP_N_SYMBOLS = 100
|
| 23 |
+
OPPORTUNITY_COUNT = 10
|
| 24 |
+
CHUNK_SIZE = 5
|
| 25 |
+
|
| 26 |
+
# المتغيرات العامة للنظام
|
| 27 |
+
r2_service_global = None
|
| 28 |
+
data_manager_global = None
|
| 29 |
+
llm_service_global = None
|
| 30 |
+
learning_engine_global = None
|
| 31 |
+
realtime_monitor = None
|
| 32 |
+
|
| 33 |
+
# Real-time trade monitoring with enhanced risk management
|
| 34 |
+
class RealTimeTradeMonitor:
|
| 35 |
+
def __init__(self):
|
| 36 |
+
self.monitoring_tasks = {}
|
| 37 |
+
self.is_running = False
|
| 38 |
+
|
| 39 |
+
async def start_monitoring(self):
|
| 40 |
+
"""بدء مراقبة جميع الصفقات المفتوحة"""
|
| 41 |
+
self.is_running = True
|
| 42 |
+
print("🔍 Starting real-time trade monitoring...")
|
| 43 |
+
|
| 44 |
+
while self.is_running:
|
| 45 |
+
try:
|
| 46 |
+
open_trades = await r2_service_global.get_open_trades_async()
|
| 47 |
+
|
| 48 |
+
for trade in open_trades:
|
| 49 |
+
symbol = trade['symbol']
|
| 50 |
+
if symbol not in self.monitoring_tasks:
|
| 51 |
+
asyncio.create_task(self._monitor_single_trade(trade))
|
| 52 |
+
self.monitoring_tasks[symbol] = trade
|
| 53 |
+
|
| 54 |
+
current_symbols = {trade['symbol'] for trade in open_trades}
|
| 55 |
+
for symbol in list(self.monitoring_tasks.keys()):
|
| 56 |
+
if symbol not in current_symbols:
|
| 57 |
+
del self.monitoring_tasks[symbol]
|
| 58 |
+
|
| 59 |
+
await asyncio.sleep(10)
|
| 60 |
+
|
| 61 |
+
except Exception as error:
|
| 62 |
+
print(f"❌ Real-time monitor error: {error}")
|
| 63 |
+
await asyncio.sleep(30)
|
| 64 |
+
|
| 65 |
+
async def _monitor_single_trade(self, trade):
|
| 66 |
+
"""مراقبة صفقة فردية في الوقت الحقيقي"""
|
| 67 |
+
symbol = trade['symbol']
|
| 68 |
+
strategy = trade.get('strategy', 'GENERIC')
|
| 69 |
+
print(f"📊 Starting real-time monitoring for {symbol} (Strategy: {strategy})")
|
| 70 |
+
|
| 71 |
+
while symbol in self.monitoring_tasks and self.is_running:
|
| 72 |
+
try:
|
| 73 |
+
current_price = await data_manager_global.get_latest_price_async(symbol)
|
| 74 |
+
if not current_price:
|
| 75 |
+
await asyncio.sleep(15)
|
| 76 |
+
continue
|
| 77 |
+
|
| 78 |
+
entry_price = trade['entry_price']
|
| 79 |
+
stop_loss = trade.get('stop_loss')
|
| 80 |
+
take_profit = trade.get('take_profit')
|
| 81 |
+
|
| 82 |
+
should_close = False
|
| 83 |
+
close_reason = ""
|
| 84 |
+
|
| 85 |
+
if stop_loss and current_price <= stop_loss:
|
| 86 |
+
should_close = True
|
| 87 |
+
close_reason = f"Stop loss hit: {current_price} <= {stop_loss}"
|
| 88 |
+
elif take_profit and current_price >= take_profit:
|
| 89 |
+
should_close = True
|
| 90 |
+
close_reason = f"Take profit hit: {current_price} >= {take_profit}"
|
| 91 |
+
|
| 92 |
+
if not should_close and current_price > entry_price:
|
| 93 |
+
dynamic_stop = current_price * 0.98
|
| 94 |
+
if dynamic_stop > (stop_loss or 0):
|
| 95 |
+
trade['stop_loss'] = dynamic_stop
|
| 96 |
+
print(f"🔒 Updated trailing stop for {symbol}: {dynamic_stop:.4f}")
|
| 97 |
+
|
| 98 |
+
if should_close:
|
| 99 |
+
print(f"🚨 IMMEDIATE CLOSE: {symbol} - {close_reason} - Strategy: {strategy}")
|
| 100 |
+
|
| 101 |
+
if r2_service_global.acquire_lock():
|
| 102 |
+
try:
|
| 103 |
+
await r2_service_global.close_trade_async(trade, current_price)
|
| 104 |
+
print(f"✅ Trade {symbol} closed immediately at {current_price}. Strategy: {strategy}")
|
| 105 |
+
|
| 106 |
+
if learning_engine_global and learning_engine_global.initialized:
|
| 107 |
+
await learning_engine_global.analyze_trade_outcome(trade, 'CLOSED_BY_MONITOR')
|
| 108 |
+
|
| 109 |
+
asyncio.create_task(run_bot_cycle_async())
|
| 110 |
+
|
| 111 |
+
finally:
|
| 112 |
+
r2_service_global.release_lock()
|
| 113 |
+
|
| 114 |
+
if symbol in self.monitoring_tasks:
|
| 115 |
+
del self.monitoring_tasks[symbol]
|
| 116 |
+
break
|
| 117 |
+
|
| 118 |
+
await asyncio.sleep(15)
|
| 119 |
+
|
| 120 |
+
except Exception as error:
|
| 121 |
+
print(f"❌ Real-time monitoring error for {symbol}: {error}")
|
| 122 |
+
await asyncio.sleep(30)
|
| 123 |
+
|
| 124 |
+
def stop_monitoring(self):
|
| 125 |
+
"""إيقاف جميع مهام المراقبة"""
|
| 126 |
+
self.is_running = False
|
| 127 |
+
self.monitoring_tasks.clear()
|
| 128 |
+
print("🛑 Real-time trade monitoring stopped")
|
| 129 |
+
|
| 130 |
+
async def monitor_market_async():
|
| 131 |
+
"""Background task to continuously monitor market health"""
|
| 132 |
+
global data_manager_global
|
| 133 |
+
|
| 134 |
+
init_attempts = 0
|
| 135 |
+
while data_manager_global is None and init_attempts < 10:
|
| 136 |
+
print(f"⏳ Waiting for data manager initialization... (attempt {init_attempts + 1}/10)")
|
| 137 |
+
await asyncio.sleep(3)
|
| 138 |
+
init_attempts += 1
|
| 139 |
+
|
| 140 |
+
if data_manager_global is None:
|
| 141 |
+
print("❌ Data manager failed to initialize after 10 attempts")
|
| 142 |
+
return
|
| 143 |
+
|
| 144 |
+
while True:
|
| 145 |
+
try:
|
| 146 |
+
print("👁️ Monitoring market sentiment...")
|
| 147 |
+
|
| 148 |
+
try:
|
| 149 |
+
market_context = await data_manager_global.get_market_context_async()
|
| 150 |
+
except Exception as error:
|
| 151 |
+
print(f"⚠️ Failed to get market context: {error}")
|
| 152 |
+
market_context = await get_fallback_market_context()
|
| 153 |
+
|
| 154 |
+
if not market_context:
|
| 155 |
+
print("❌ Failed to get market context. Assuming neutral state.")
|
| 156 |
+
state.MARKET_STATE_OK = True
|
| 157 |
+
await asyncio.sleep(60)
|
| 158 |
+
continue
|
| 159 |
+
|
| 160 |
+
whale_analysis = market_context.get('general_whale_activity', {})
|
| 161 |
+
whale_sentiment = whale_analysis.get('sentiment', 'NEUTRAL')
|
| 162 |
+
is_critical = whale_analysis.get('critical_alert', False)
|
| 163 |
+
total_volume = whale_analysis.get('total_volume_usd', 0)
|
| 164 |
+
|
| 165 |
+
print(f"🐋 Whale Analysis: {whale_sentiment} | Critical: {is_critical} | Volume: ${total_volume:,.0f}")
|
| 166 |
+
print(f"📈 Whale Description: {whale_analysis.get('description', 'No data')}")
|
| 167 |
+
|
| 168 |
+
bitcoin_sentiment = market_context.get('btc_sentiment')
|
| 169 |
+
fear_greed_index = market_context.get('fear_and_greed_index')
|
| 170 |
+
|
| 171 |
+
should_halt_trading = False
|
| 172 |
+
halt_reason = ""
|
| 173 |
+
|
| 174 |
+
if is_critical:
|
| 175 |
+
should_halt_trading = True
|
| 176 |
+
halt_reason = f"CRITICAL whale activity detected: {whale_analysis.get('description')}"
|
| 177 |
+
elif bitcoin_sentiment == 'BEARISH' and (fear_greed_index is not None and fear_greed_index < 30):
|
| 178 |
+
should_halt_trading = True
|
| 179 |
+
halt_reason = f"Bearish market conditions (BTC: {bitcoin_sentiment}, F&G: {fear_greed_index})"
|
| 180 |
+
|
| 181 |
+
if should_halt_trading:
|
| 182 |
+
print(f"🚨🚨🚨 MARKET HALT: {halt_reason} 🚨🚨🚨")
|
| 183 |
+
state.MARKET_STATE_OK = False
|
| 184 |
+
|
| 185 |
+
try:
|
| 186 |
+
await r2_service_global.save_system_logs_async({
|
| 187 |
+
"market_halt": True,
|
| 188 |
+
"reason": halt_reason,
|
| 189 |
+
"whale_sentiment": whale_sentiment,
|
| 190 |
+
"is_critical": is_critical
|
| 191 |
+
})
|
| 192 |
+
except Exception as log_error:
|
| 193 |
+
print(f"⚠️ Failed to save market halt log: {log_error}")
|
| 194 |
+
else:
|
| 195 |
+
if not state.MARKET_STATE_OK:
|
| 196 |
+
print("🟢 Market conditions improved. Resuming normal operations.")
|
| 197 |
+
state.MARKET_STATE_OK = True
|
| 198 |
+
|
| 199 |
+
await asyncio.sleep(60)
|
| 200 |
+
except Exception as error:
|
| 201 |
+
print(f"❌ An error occurred during COMPREHENSIVE market monitoring: {error}")
|
| 202 |
+
traceback.print_exc()
|
| 203 |
+
state.MARKET_STATE_OK = True
|
| 204 |
+
await asyncio.sleep(60)
|
| 205 |
+
|
| 206 |
+
async def get_fallback_market_context():
|
| 207 |
+
"""Fallback function when main market context fails"""
|
| 208 |
+
return {
|
| 209 |
+
'timestamp': datetime.now().isoformat(),
|
| 210 |
+
'general_whale_activity': {
|
| 211 |
+
'sentiment': 'NEUTRAL',
|
| 212 |
+
'description': 'Fallback mode - system initializing',
|
| 213 |
+
'critical_alert': False,
|
| 214 |
+
'transaction_count': 0,
|
| 215 |
+
'total_volume_usd': 0
|
| 216 |
+
},
|
| 217 |
+
'btc_sentiment': 'NEUTRAL',
|
| 218 |
+
'fear_and_greed_index': 50
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
def safe_float_conversion(value, default=0.0):
|
| 222 |
+
"""تحويل آمن للقيم إلى أرقام"""
|
| 223 |
+
try:
|
| 224 |
+
if value is None:
|
| 225 |
+
return default
|
| 226 |
+
if isinstance(value, (int, float)):
|
| 227 |
+
return float(value)
|
| 228 |
+
if isinstance(value, str):
|
| 229 |
+
cleaned = ''.join(character for character in value if character.isdigit() or character in '.-')
|
| 230 |
+
return float(cleaned) if cleaned else default
|
| 231 |
+
return default
|
| 232 |
+
except (ValueError, TypeError):
|
| 233 |
+
return default
|
| 234 |
+
|
| 235 |
+
async def validate_candidate_data_enhanced(candidate):
|
| 236 |
+
"""✨ تحسين التحقق من جودة المرشحين"""
|
| 237 |
+
try:
|
| 238 |
+
required_fields = ['symbol', 'current_price', 'final_score', 'enhanced_final_score']
|
| 239 |
+
|
| 240 |
+
for field in required_fields:
|
| 241 |
+
if field not in candidate:
|
| 242 |
+
candidate[field] = 0.0 if field.endswith('_score') or field == 'current_price' else 'UNKNOWN'
|
| 243 |
+
|
| 244 |
+
candidate['current_price'] = safe_float_conversion(candidate.get('current_price'), 0.0)
|
| 245 |
+
candidate['final_score'] = safe_float_conversion(candidate.get('final_score'), 0.5)
|
| 246 |
+
candidate['enhanced_final_score'] = safe_float_conversion(candidate.get('enhanced_final_score'), candidate['final_score'])
|
| 247 |
+
|
| 248 |
+
if 'reasons_for_candidacy' not in candidate or not candidate['reasons_for_candidacy']:
|
| 249 |
+
candidate['reasons_for_candidacy'] = ['unknown_reason']
|
| 250 |
+
|
| 251 |
+
if 'sentiment_data' not in candidate or not candidate['sentiment_data']:
|
| 252 |
+
candidate['sentiment_data'] = {
|
| 253 |
+
'btc_sentiment': 'NEUTRAL',
|
| 254 |
+
'fear_and_greed_index': 50,
|
| 255 |
+
'general_whale_activity': {'sentiment': 'NEUTRAL', 'critical_alert': False}
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
if 'advanced_indicators' not in candidate:
|
| 259 |
+
candidate['advanced_indicators'] = {}
|
| 260 |
+
|
| 261 |
+
if 'strategy_scores' not in candidate:
|
| 262 |
+
candidate['strategy_scores'] = {}
|
| 263 |
+
|
| 264 |
+
if 'recommended_strategy' not in candidate:
|
| 265 |
+
candidate['recommended_strategy'] = 'unknown'
|
| 266 |
+
|
| 267 |
+
# ✅ الإصلاح: التأكد من وجود استراتيجية مستهدفة صالحة
|
| 268 |
+
if 'target_strategy' not in candidate or not candidate['target_strategy'] or candidate['target_strategy'] == 'unknown':
|
| 269 |
+
candidate['target_strategy'] = 'GENERIC'
|
| 270 |
+
|
| 271 |
+
return True
|
| 272 |
+
|
| 273 |
+
except Exception as error:
|
| 274 |
+
print(f"❌ Failed to validate candidate data for {candidate.get('symbol')}: {error}")
|
| 275 |
+
return False
|
| 276 |
+
|
| 277 |
+
async def analyze_market_strategy(market_context):
|
| 278 |
+
"""تحديد الاستراتيجية المثلى بناءً على ظروف السوق"""
|
| 279 |
+
try:
|
| 280 |
+
prompt = f"""
|
| 281 |
+
You are a professional crypto portfolio manager. Analyze the current market conditions and determine the most suitable strategy.
|
| 282 |
+
|
| 283 |
+
**Market Data:**
|
| 284 |
+
- BTC Price: {market_context.get('bitcoin_price_usd')}
|
| 285 |
+
- BTC Sentiment: {market_context.get('btc_sentiment')}
|
| 286 |
+
- Fear & Greed Index: {market_context.get('fear_and_greed_index')}
|
| 287 |
+
- Whale Analysis: {market_context.get('general_whale_activity', {}).get('sentiment')}
|
| 288 |
+
- Critical Whale Alert: {market_context.get('general_whale_activity', {}).get('critical_alert')}
|
| 289 |
+
|
| 290 |
+
**Available Strategies:**
|
| 291 |
+
1. AGGRESSIVE_GROWTH - For strong bull markets.
|
| 292 |
+
2. DEFENSIVE_GROWTH - For volatile or uncertain markets.
|
| 293 |
+
3. CONSERVATIVE - For bearish or high-risk markets.
|
| 294 |
+
4. HIGH_FREQUENCY - For sideways markets.
|
| 295 |
+
5. WHALE_FOLLOWING - When whale activity is high and clear.
|
| 296 |
+
6. GENERIC - Balanced approach for normal conditions.
|
| 297 |
+
|
| 298 |
+
**Required:**
|
| 299 |
+
- Choose one primary strategy.
|
| 300 |
+
- Explain why in a single sentence.
|
| 301 |
+
- Set an acceptable risk tolerance (1 to 10).
|
| 302 |
+
- Determine the optimal number of coins to scan (50 to 200).
|
| 303 |
+
|
| 304 |
+
**Output (JSON only):**
|
| 305 |
+
{{
|
| 306 |
+
"primary_strategy": "STRATEGY_NAME",
|
| 307 |
+
"reasoning": "Brief reasoning.",
|
| 308 |
+
"risk_tolerance": 5,
|
| 309 |
+
"optimal_scan_count": 100
|
| 310 |
+
}}
|
| 311 |
+
"""
|
| 312 |
+
|
| 313 |
+
response = await llm_service_global._call_llm(prompt)
|
| 314 |
+
|
| 315 |
+
try:
|
| 316 |
+
json_match = re.search(r'\{.*\}', response, re.DOTALL)
|
| 317 |
+
strategy_data = json.loads(json_match.group())
|
| 318 |
+
except:
|
| 319 |
+
strategy_data = {
|
| 320 |
+
"primary_strategy": "GENERIC",
|
| 321 |
+
"reasoning": "Fallback strategy for market stability",
|
| 322 |
+
"risk_tolerance": 5,
|
| 323 |
+
"optimal_scan_count": 100,
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
return strategy_data
|
| 327 |
+
|
| 328 |
+
except Exception as error:
|
| 329 |
+
print(f"❌ Failed to analyze market strategy: {error}")
|
| 330 |
+
return {
|
| 331 |
+
"primary_strategy": "GENERIC",
|
| 332 |
+
"reasoning": "Fallback due to analysis error",
|
| 333 |
+
"risk_tolerance": 5,
|
| 334 |
+
"optimal_scan_count": 100,
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
async def find_strategy_specific_candidates(strategy, scan_count):
|
| 338 |
+
"""✨ نظام فلترة ذكي يستخدم الاستراتيجيات المتخصصة - عتبات مخفضة"""
|
| 339 |
+
try:
|
| 340 |
+
# 1. جلب قائمة المرشحين الأولية
|
| 341 |
+
all_candidates = await data_manager_global.find_high_potential_candidates(scan_count * 2)
|
| 342 |
+
|
| 343 |
+
if not all_candidates:
|
| 344 |
+
print(f"⚠️ الماسح العام لم يجد أي مرشحين أوليين.")
|
| 345 |
+
return []
|
| 346 |
+
|
| 347 |
+
# 2. تحديث market_context قبل المعالجة
|
| 348 |
+
market_context = await data_manager_global.get_market_context_async()
|
| 349 |
+
if not market_context:
|
| 350 |
+
print("❌ Failed to get market context for strategy analysis")
|
| 351 |
+
return []
|
| 352 |
+
|
| 353 |
+
feature_processor = FeatureProcessor(market_context, data_manager_global, learning_engine_global)
|
| 354 |
+
|
| 355 |
+
processed_candidates = []
|
| 356 |
+
for candidate in all_candidates[:30]: # ⬇️ تخفيض من 50 إلى 30 للأداء
|
| 357 |
+
try:
|
| 358 |
+
# تحويل البيانات الخام إلى بيانات معالجة
|
| 359 |
+
symbol_with_reasons = [{'symbol': candidate['symbol'], 'reasons': candidate.get('reasons', [])}]
|
| 360 |
+
ohlcv_data = await data_manager_global.get_fast_pass_data_async(symbol_with_reasons)
|
| 361 |
+
|
| 362 |
+
if ohlcv_data and ohlcv_data[0]:
|
| 363 |
+
# ✅ تحديث market_context قبل كل معالجة لمنع الخطأ
|
| 364 |
+
try:
|
| 365 |
+
updated_market_context = await data_manager_global.get_market_context_async()
|
| 366 |
+
if updated_market_context:
|
| 367 |
+
feature_processor.market_context = updated_market_context
|
| 368 |
+
except Exception as e:
|
| 369 |
+
print(f"⚠️ Failed to update market context for {candidate['symbol']}: {e}")
|
| 370 |
+
# الاستمرار بالسياق القديم إذا فشل التحديث
|
| 371 |
+
|
| 372 |
+
processed = await feature_processor.process_and_score_symbol_enhanced(ohlcv_data[0])
|
| 373 |
+
if processed:
|
| 374 |
+
processed_candidates.append(processed)
|
| 375 |
+
except Exception as e:
|
| 376 |
+
print(f"⚠️ فشل معالجة {candidate.get('symbol')}: {e}")
|
| 377 |
+
|
| 378 |
+
if not processed_candidates:
|
| 379 |
+
print("⚠️ لم يتم معالجة أي مرشح بنجاح")
|
| 380 |
+
return []
|
| 381 |
+
|
| 382 |
+
# 3. فرز المرشحين حسب الاستراتيجية المطلوبة
|
| 383 |
+
if strategy != 'GENERIC':
|
| 384 |
+
# تحديد أفضل المرشحين للاستراتيجية المحددة
|
| 385 |
+
strategy_candidates = []
|
| 386 |
+
for candidate in processed_candidates:
|
| 387 |
+
# ✅ استخدام الدرجات الأساسية بدلاً من المرجحة
|
| 388 |
+
base_scores = candidate.get('base_strategy_scores', {})
|
| 389 |
+
strategy_score = base_scores.get(strategy, 0)
|
| 390 |
+
|
| 391 |
+
# ✅ تخفيض كبير في عتبة القبول
|
| 392 |
+
if strategy_score > 0.2: # ⬇️ تخفيض من 0.4 إلى 0.2
|
| 393 |
+
candidate['strategy_match_score'] = strategy_score
|
| 394 |
+
strategy_candidates.append(candidate)
|
| 395 |
+
print(f"✅ {candidate['symbol']} مناسب لـ {strategy} (درجة: {strategy_score:.3f})")
|
| 396 |
+
|
| 397 |
+
# فرز حسب تطابق الاستراتيجية
|
| 398 |
+
sorted_candidates = sorted(strategy_candidates,
|
| 399 |
+
key=lambda x: x.get('strategy_match_score', 0),
|
| 400 |
+
reverse=True)
|
| 401 |
+
top_candidates = sorted_candidates[:15] # ⬇️ تخفيض من 20 إلى 15
|
| 402 |
+
|
| 403 |
+
print(f"✅ تم اختيار {len(top_candidates)} مرشحًا لاستراتيجية {strategy}")
|
| 404 |
+
else:
|
| 405 |
+
# للاستراتيجية العامة، استخدم النقاط المحسنة
|
| 406 |
+
sorted_candidates = sorted(processed_candidates,
|
| 407 |
+
key=lambda x: x.get('enhanced_final_score', 0),
|
| 408 |
+
reverse=True)
|
| 409 |
+
top_candidates = sorted_candidates[:15] # ⬇️ تخفيض من 20 إلى 15
|
| 410 |
+
print(f"✅ تم اختيار {len(top_candidates)} مرشحًا للاستراتيجية العامة")
|
| 411 |
+
|
| 412 |
+
return top_candidates
|
| 413 |
+
|
| 414 |
+
except Exception as error:
|
| 415 |
+
print(f"❌ فشل في نظام الفلترة المتقدم: {error}")
|
| 416 |
+
traceback.print_exc()
|
| 417 |
+
return []
|
| 418 |
+
|
| 419 |
+
async def find_new_opportunities_async():
|
| 420 |
+
"""✨ NEW: المسح المحسن باستراتيجية مسبقة مع عتبات مخفضة"""
|
| 421 |
+
print("🔍 Scanning for new opportunities with reduced thresholds...")
|
| 422 |
+
try:
|
| 423 |
+
await r2_service_global.save_system_logs_async({
|
| 424 |
+
"opportunity_scan_started": True, "timestamp": datetime.now().isoformat()
|
| 425 |
+
})
|
| 426 |
+
|
| 427 |
+
print("🧠 Determining trading strategy...")
|
| 428 |
+
market_context = await data_manager_global.get_market_context_async()
|
| 429 |
+
if not market_context:
|
| 430 |
+
print("❌ Failed to fetch market context. Cannot determine strategy.")
|
| 431 |
+
return
|
| 432 |
+
|
| 433 |
+
strategy_decision = await analyze_market_strategy(market_context)
|
| 434 |
+
|
| 435 |
+
print(f"🎯 Selected Strategy: {strategy_decision['primary_strategy']}")
|
| 436 |
+
print(f"📝 Reasoning: {strategy_decision['reasoning']}")
|
| 437 |
+
print(f"⚡ Risk Tolerance: {strategy_decision.get('risk_tolerance', 5)}/10")
|
| 438 |
+
print(f"🔍 Optimal Scan Count: {strategy_decision.get('optimal_scan_count', 100)}")
|
| 439 |
+
|
| 440 |
+
print(f"🔍 Finding top candidates using dynamic ranking...")
|
| 441 |
+
high_potential_candidates = await find_strategy_specific_candidates(
|
| 442 |
+
strategy_decision['primary_strategy'],
|
| 443 |
+
strategy_decision.get('optimal_scan_count', 100)
|
| 444 |
+
)
|
| 445 |
+
|
| 446 |
+
if not high_potential_candidates:
|
| 447 |
+
print("🔄 لا توجد مرشحين متخصصين، جلب مرشحين عامين...")
|
| 448 |
+
# ✅ استرجاع مرشحين عامين كبديل
|
| 449 |
+
high_potential_candidates = await data_manager_global.find_high_potential_candidates(20)
|
| 450 |
+
if high_potential_candidates:
|
| 451 |
+
for candidate in high_potential_candidates:
|
| 452 |
+
candidate['target_strategy'] = 'GENERIC'
|
| 453 |
+
print(f"✅ تم تحميل {len(high_potential_candidates)} مرشح عام")
|
| 454 |
+
else:
|
| 455 |
+
print("✅ No new candidates found after dynamic ranking.")
|
| 456 |
+
await r2_service_global.save_system_logs_async({
|
| 457 |
+
"no_candidates_found": True, "strategy": strategy_decision['primary_strategy'],
|
| 458 |
+
"reason": "Scanner did not return any initial candidates."
|
| 459 |
+
})
|
| 460 |
+
return
|
| 461 |
+
|
| 462 |
+
all_processed_candidates = []
|
| 463 |
+
for index in range(0, len(high_potential_candidates), CHUNK_SIZE):
|
| 464 |
+
chunk = high_potential_candidates[index:index+CHUNK_SIZE]
|
| 465 |
+
|
| 466 |
+
chunk_data = await data_manager_global.get_fast_pass_data_async(chunk)
|
| 467 |
+
|
| 468 |
+
print(f"⏳ Processing and scoring chunk {index//CHUNK_SIZE + 1}...")
|
| 469 |
+
# ✅ تحديث market_context قبل معالجة كل شريحة
|
| 470 |
+
updated_market_context = await data_manager_global.get_market_context_async()
|
| 471 |
+
if not updated_market_context:
|
| 472 |
+
updated_market_context = market_context # استخدام السياق القديم كبديل
|
| 473 |
+
|
| 474 |
+
feature_processor = FeatureProcessor(updated_market_context, data_manager_global, learning_engine_global)
|
| 475 |
+
|
| 476 |
+
processed_chunk = await asyncio.gather(*[
|
| 477 |
+
feature_processor.process_and_score_symbol_enhanced(data) for data in chunk_data
|
| 478 |
+
])
|
| 479 |
+
all_processed_candidates.extend([c for c in processed_chunk if c is not None])
|
| 480 |
+
|
| 481 |
+
await asyncio.sleep(1)
|
| 482 |
+
|
| 483 |
+
if not all_processed_candidates:
|
| 484 |
+
print("❌ No candidates were processed successfully.")
|
| 485 |
+
return
|
| 486 |
+
|
| 487 |
+
# ✅ استخدام السياق المحدث للتصفية النهائية
|
| 488 |
+
updated_market_context = await data_manager_global.get_market_context_async()
|
| 489 |
+
if not updated_market_context:
|
| 490 |
+
updated_market_context = market_context
|
| 491 |
+
|
| 492 |
+
feature_processor = FeatureProcessor(updated_market_context, data_manager_global, learning_engine_global)
|
| 493 |
+
top_candidates = feature_processor.filter_top_candidates(all_processed_candidates, OPPORTUNITY_COUNT)
|
| 494 |
+
|
| 495 |
+
print(f"✅ Identified {len(top_candidates)} top candidates after final scoring.")
|
| 496 |
+
|
| 497 |
+
await r2_service_global.save_candidates_data_async(
|
| 498 |
+
candidates_data=top_candidates,
|
| 499 |
+
reanalysis_data={"strategy_used": strategy_decision, "market_conditions": market_context}
|
| 500 |
+
)
|
| 501 |
+
|
| 502 |
+
if not top_candidates:
|
| 503 |
+
print("❌ No strong candidates left after final filtering.")
|
| 504 |
+
await r2_service_global.save_system_logs_async({
|
| 505 |
+
"no_strong_candidates": True, "strategy": strategy_decision['primary_strategy'],
|
| 506 |
+
"initial_candidates_count": len(high_potential_candidates)
|
| 507 |
+
})
|
| 508 |
+
return
|
| 509 |
+
|
| 510 |
+
print("🧠 Getting LLM analysis for top candidates...")
|
| 511 |
+
|
| 512 |
+
for candidate in top_candidates:
|
| 513 |
+
try:
|
| 514 |
+
if not await validate_candidate_data_enhanced(candidate):
|
| 515 |
+
print(f"⚠️ Skipping {candidate.get('symbol')} due to quality issues")
|
| 516 |
+
continue
|
| 517 |
+
|
| 518 |
+
llm_analysis_data = await llm_service_global.get_trading_decision(candidate)
|
| 519 |
+
|
| 520 |
+
if not llm_analysis_data:
|
| 521 |
+
print(f"⚠️ LLM analysis failed for {candidate['symbol']}. Moving to next.")
|
| 522 |
+
continue
|
| 523 |
+
|
| 524 |
+
if llm_analysis_data.get('action') == "HOLD":
|
| 525 |
+
print(f"🧠 LLM decided to HOLD on {candidate['symbol']}. Moving to next.")
|
| 526 |
+
continue
|
| 527 |
+
|
| 528 |
+
if llm_analysis_data.get('action') in ["BUY", "SELL"]:
|
| 529 |
+
# ✅ التحقق النهائي من الاستراتيجية
|
| 530 |
+
final_strategy = llm_analysis_data.get('strategy')
|
| 531 |
+
candidate_strategy = candidate.get('target_strategy', 'GENERIC')
|
| 532 |
+
|
| 533 |
+
# إذا كانت استراتيجية LLM غير صالحة، استخدم استراتيجية المرشح
|
| 534 |
+
if not final_strategy or final_strategy == 'unknown' or final_strategy == 'GENERIC':
|
| 535 |
+
final_strategy = candidate_strategy
|
| 536 |
+
llm_analysis_data['strategy'] = final_strategy
|
| 537 |
+
print(f"🔧 تصحيح استراتيجية LLM لـ {candidate['symbol']}: {final_strategy}")
|
| 538 |
+
|
| 539 |
+
print(f"🎯 الاستراتيجية النهائية: {final_strategy}")
|
| 540 |
+
|
| 541 |
+
print("\n========================================================")
|
| 542 |
+
print(f"💎💎💎 New Trading Opportunity Identified! 💎💎💎")
|
| 543 |
+
print(f" Symbol: {candidate['symbol']}")
|
| 544 |
+
print(f" Action: {llm_analysis_data.get('action')}")
|
| 545 |
+
print(f" Strategy: {final_strategy}")
|
| 546 |
+
print(f" Reasoning: {llm_analysis_data.get('reasoning')}")
|
| 547 |
+
print(f" Confidence: {llm_analysis_data.get('confidence_level')}")
|
| 548 |
+
print("========================================================\n")
|
| 549 |
+
|
| 550 |
+
await r2_service_global.save_system_logs_async({
|
| 551 |
+
"new_opportunity_found": True, "symbol": candidate['symbol'],
|
| 552 |
+
"action": llm_analysis_data.get('action'), "strategy": final_strategy,
|
| 553 |
+
"confidence": llm_analysis_data.get('confidence_level', 0)
|
| 554 |
+
})
|
| 555 |
+
|
| 556 |
+
return {
|
| 557 |
+
"symbol": candidate['symbol'],
|
| 558 |
+
"decision": llm_analysis_data,
|
| 559 |
+
"current_price": candidate['current_price'],
|
| 560 |
+
"strategy": final_strategy
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
except Exception as error:
|
| 564 |
+
print(f"❌ LLM/Fallback error for {candidate.get('symbol', 'unknown')}: {error}")
|
| 565 |
+
traceback.print_exc()
|
| 566 |
+
|
| 567 |
+
print("✅ Cycle finished. No actionable BUY/SELL opportunities found by LLM.")
|
| 568 |
+
return None
|
| 569 |
+
|
| 570 |
+
except Exception as error:
|
| 571 |
+
print(f"❌ An error occurred while scanning for opportunities: {error}")
|
| 572 |
+
traceback.print_exc()
|
| 573 |
+
await r2_service_global.save_system_logs_async({"opportunity_scan_error": True, "error": str(error)})
|
| 574 |
+
return None
|
| 575 |
+
|
| 576 |
+
async def re_analyze_open_trade_async(trade_data):
|
| 577 |
+
"""Re-analyzes an open trade with enhanced strategy preservation"""
|
| 578 |
+
symbol = trade_data.get('symbol')
|
| 579 |
+
|
| 580 |
+
try:
|
| 581 |
+
entry_time = datetime.fromisoformat(trade_data['entry_timestamp'])
|
| 582 |
+
current_time = datetime.now()
|
| 583 |
+
hold_minutes = (current_time - entry_time).total_seconds() / 60
|
| 584 |
+
|
| 585 |
+
print(f"⏳ Re-analyzing trade: {symbol} (held for {hold_minutes:.1f} minutes)")
|
| 586 |
+
|
| 587 |
+
# ✅ الإصلاح المحسن: الحفاظ على الاستراتيجية الأصلية مع التحقق الشامل
|
| 588 |
+
original_strategy = trade_data.get('strategy')
|
| 589 |
+
if not original_strategy or original_strategy == 'unknown':
|
| 590 |
+
original_strategy = trade_data.get('decision_data', {}).get('strategy', 'GENERIC')
|
| 591 |
+
print(f"🔧 Fixed original strategy for {symbol}: {original_strategy}")
|
| 592 |
+
|
| 593 |
+
reanalysis_context = {
|
| 594 |
+
'trade_data': {
|
| 595 |
+
'symbol': trade_data.get('symbol'),
|
| 596 |
+
'entry_price': trade_data.get('entry_price'),
|
| 597 |
+
'entry_time': trade_data.get('entry_timestamp'),
|
| 598 |
+
'hold_minutes': hold_minutes,
|
| 599 |
+
'strategy': original_strategy
|
| 600 |
+
}
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
try:
|
| 604 |
+
market_context = await data_manager_global.get_market_context_async()
|
| 605 |
+
except Exception as error:
|
| 606 |
+
print(f"⚠️ Failed to get market context: {error}. Using basic market data...")
|
| 607 |
+
market_context = {'btc_sentiment': 'NEUTRAL'}
|
| 608 |
+
|
| 609 |
+
symbol_with_reasons = [{'symbol': symbol, 'reasons': ['re-analysis']}]
|
| 610 |
+
|
| 611 |
+
ohlcv_data_list = await data_manager_global.get_fast_pass_data_async(symbol_with_reasons)
|
| 612 |
+
if not ohlcv_data_list:
|
| 613 |
+
print(f"❌ Failed to fetch latest data for {symbol}.")
|
| 614 |
+
return None
|
| 615 |
+
|
| 616 |
+
raw_data = ohlcv_data_list[0]
|
| 617 |
+
# ✅ تحديث market_context قبل المعالجة
|
| 618 |
+
try:
|
| 619 |
+
updated_market_context = await data_manager_global.get_market_context_async()
|
| 620 |
+
if updated_market_context:
|
| 621 |
+
market_context = updated_market_context
|
| 622 |
+
except Exception as e:
|
| 623 |
+
print(f"⚠️ Failed to update market context for re-analysis: {e}")
|
| 624 |
+
|
| 625 |
+
feature_processor = FeatureProcessor(market_context, data_manager_global, learning_engine_global)
|
| 626 |
+
processed_data = await feature_processor.process_and_score_symbol(raw_data)
|
| 627 |
+
|
| 628 |
+
if not processed_data:
|
| 629 |
+
print(f"❌ Failed to process latest data for {symbol}.")
|
| 630 |
+
return None
|
| 631 |
+
|
| 632 |
+
await r2_service_global.save_candidates_data_async(
|
| 633 |
+
candidates_data=None,
|
| 634 |
+
reanalysis_data={'market_context': market_context, 'processed_data': processed_data}
|
| 635 |
+
)
|
| 636 |
+
|
| 637 |
+
print(f"🧠 Getting LLM re-analysis for {symbol}...")
|
| 638 |
+
try:
|
| 639 |
+
re_analysis_decision = await llm_service_global.re_analyze_trade_async(trade_data, processed_data)
|
| 640 |
+
source = re_analysis_decision.get('model_source', 'LLM')
|
| 641 |
+
except Exception as error:
|
| 642 |
+
print(f"❌ LLM re-analysis error: {error}. Falling back to local.")
|
| 643 |
+
re_analysis_decision = local_re_analyze_trade(trade_data, processed_data)
|
| 644 |
+
source = 'local_fallback'
|
| 645 |
+
|
| 646 |
+
final_decision = _apply_patience_logic(re_analysis_decision, hold_minutes, trade_data, processed_data)
|
| 647 |
+
|
| 648 |
+
# ✅ الإصلاح النهائي: التأكد من وجود الاستراتيجية في القرار النهائي
|
| 649 |
+
if not final_decision.get('strategy') or final_decision['strategy'] == 'unknown':
|
| 650 |
+
final_decision['strategy'] = original_strategy
|
| 651 |
+
print(f"🔧 Final re-analysis strategy fix for {symbol}: {original_strategy}")
|
| 652 |
+
|
| 653 |
+
print(f"✅ Re-analysis decision for {symbol}: {final_decision.get('action')}. Strategy: {final_decision.get('strategy')}. Source: {source}")
|
| 654 |
+
|
| 655 |
+
await r2_service_global.save_system_logs_async({
|
| 656 |
+
"trade_reanalyzed": True, "symbol": symbol, "action": final_decision.get('action'),
|
| 657 |
+
"hold_minutes": hold_minutes, "source": source, "strategy": final_decision.get('strategy')
|
| 658 |
+
})
|
| 659 |
+
|
| 660 |
+
return {
|
| 661 |
+
"symbol": symbol, "decision": final_decision,
|
| 662 |
+
"current_price": processed_data.get('current_price'), "hold_minutes": hold_minutes
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
except Exception as error:
|
| 666 |
+
print(f"❌ An error occurred during trade re-analysis: {error}")
|
| 667 |
+
traceback.print_exc()
|
| 668 |
+
await r2_service_global.save_system_logs_async({"reanalysis_error": True, "symbol": symbol, "error": str(error)})
|
| 669 |
+
return None
|
| 670 |
+
|
| 671 |
+
def _apply_patience_logic(decision, hold_minutes, trade_data, processed_data):
|
| 672 |
+
"""Apply patience logic to prevent premature selling decisions"""
|
| 673 |
+
action = decision.get('action')
|
| 674 |
+
|
| 675 |
+
if action == "CLOSE_TRADE" and hold_minutes < 20:
|
| 676 |
+
current_price = processed_data.get('current_price', 0)
|
| 677 |
+
entry_price = trade_data.get('entry_price', 0)
|
| 678 |
+
|
| 679 |
+
try:
|
| 680 |
+
profit_loss_percent = ((current_price - entry_price) / entry_price) * 100
|
| 681 |
+
except (TypeError, ZeroDivisionError):
|
| 682 |
+
profit_loss_percent = 0
|
| 683 |
+
|
| 684 |
+
if profit_loss_percent < 2:
|
| 685 |
+
print(f"🛑 Blocked premature selling! Only {hold_minutes:.1f} minutes held, PnL: {profit_loss_percent:.2f}%")
|
| 686 |
+
decision['action'] = "HOLD"
|
| 687 |
+
decision['reasoning'] = f"Patience Filter: Blocked premature sell. Held for {hold_minutes:.1f}m. Giving trade more time."
|
| 688 |
+
return decision
|
| 689 |
+
|
| 690 |
+
return decision
|
| 691 |
+
|
| 692 |
+
async def run_bot_cycle_async():
|
| 693 |
+
"""The main asynchronous bot cycle with enhanced strategy validation"""
|
| 694 |
+
print(f"\n{'='*70}")
|
| 695 |
+
print(f"⏳ New cycle initiated at: {datetime.now().isoformat()}")
|
| 696 |
+
print(f"{'='*70}")
|
| 697 |
+
|
| 698 |
+
try:
|
| 699 |
+
await r2_service_global.save_system_logs_async({"cycle_started": True})
|
| 700 |
+
|
| 701 |
+
if not r2_service_global.acquire_lock():
|
| 702 |
+
print("❌ Failed to acquire lock. Skipping cycle.")
|
| 703 |
+
return
|
| 704 |
+
|
| 705 |
+
open_trades = []
|
| 706 |
+
try:
|
| 707 |
+
open_trades = await r2_service_global.get_open_trades_async()
|
| 708 |
+
print(f"✅ Found {len(open_trades)} open trade(s).")
|
| 709 |
+
|
| 710 |
+
# ✅ الإصلاح المحسن: فحص وإصلاح الاستراتيجيات الفارغة في الصفقات المفتوحة
|
| 711 |
+
trades_fixed = 0
|
| 712 |
+
for trade in open_trades:
|
| 713 |
+
if not trade.get('strategy') or trade['strategy'] == 'unknown':
|
| 714 |
+
original_strategy = trade.get('decision_data', {}).get('strategy', 'GENERIC')
|
| 715 |
+
trade['strategy'] = original_strategy
|
| 716 |
+
trades_fixed += 1
|
| 717 |
+
print(f"🔧 Fixed missing strategy for {trade['symbol']}: {trade['strategy']}")
|
| 718 |
+
|
| 719 |
+
if trades_fixed > 0:
|
| 720 |
+
print(f"✅ Fixed strategies for {trades_fixed} trades.")
|
| 721 |
+
await r2_service_global.save_open_trades_async(open_trades)
|
| 722 |
+
|
| 723 |
+
should_look_for_new_trade = not open_trades
|
| 724 |
+
|
| 725 |
+
if open_trades:
|
| 726 |
+
now = datetime.now()
|
| 727 |
+
trades_to_reanalyze = [
|
| 728 |
+
trade for trade in open_trades
|
| 729 |
+
if now >= datetime.fromisoformat(trade.get('expected_target_time', now.isoformat()))
|
| 730 |
+
]
|
| 731 |
+
|
| 732 |
+
if trades_to_reanalyze:
|
| 733 |
+
print(f"🔍 Re-analyzing {len(trades_to_reanalyze)} trade(s)...")
|
| 734 |
+
for trade in trades_to_reanalyze:
|
| 735 |
+
result = await re_analyze_open_trade_async(trade)
|
| 736 |
+
if result and result['decision'].get('action') == "CLOSE_TRADE":
|
| 737 |
+
await r2_service_global.close_trade_async(trade, result['current_price'])
|
| 738 |
+
print(f"✅ Trade for {trade['symbol']} CLOSED. Strategy: {trade.get('strategy', 'unknown')}")
|
| 739 |
+
if learning_engine_global and learning_engine_global.initialized:
|
| 740 |
+
trade_with_strategy = trade.copy()
|
| 741 |
+
strategy = result['decision'].get('strategy', trade.get('strategy', 'GENERIC'))
|
| 742 |
+
trade_with_strategy['strategy'] = strategy
|
| 743 |
+
await learning_engine_global.analyze_trade_outcome(trade_with_strategy, 'CLOSED_BY_REANALYSIS')
|
| 744 |
+
should_look_for_new_trade = True
|
| 745 |
+
elif result and result['decision'].get('action') == "UPDATE_TRADE":
|
| 746 |
+
await r2_service_global.update_trade_async(trade, result['decision'])
|
| 747 |
+
print(f"✅ Trade for {trade['symbol']} UPDATED. Strategy: {trade.get('strategy', 'unknown')}")
|
| 748 |
+
else:
|
| 749 |
+
print(f"✅ Trade for {trade['symbol']} is on HOLD. Strategy: {trade.get('strategy', 'unknown')}")
|
| 750 |
+
else:
|
| 751 |
+
print("✅ No trades due for re-analysis yet.")
|
| 752 |
+
|
| 753 |
+
if should_look_for_new_trade:
|
| 754 |
+
portfolio_state = await r2_service_global.get_portfolio_state_async()
|
| 755 |
+
if portfolio_state.get("current_capital_usd", 0) > 1:
|
| 756 |
+
print(f"✅ Capital available (${portfolio_state['current_capital_usd']:.2f}). Scanning...")
|
| 757 |
+
new_opportunity = await find_new_opportunities_async()
|
| 758 |
+
if new_opportunity:
|
| 759 |
+
print(f"✅ Opportunity for {new_opportunity['symbol']} confirmed! Saving trade. Strategy: {new_opportunity.get('strategy')}")
|
| 760 |
+
|
| 761 |
+
# ✅ التحقق النهائي قبل الحفظ
|
| 762 |
+
if not new_opportunity['decision'].get('strategy'):
|
| 763 |
+
new_opportunity['decision']['strategy'] = new_opportunity.get('strategy', 'GENERIC')
|
| 764 |
+
print(f"🔧 Final pre-save strategy fix: {new_opportunity['decision']['strategy']}")
|
| 765 |
+
|
| 766 |
+
await r2_service_global.save_new_trade_async(
|
| 767 |
+
new_opportunity['symbol'],
|
| 768 |
+
new_opportunity['decision'],
|
| 769 |
+
new_opportunity['current_price']
|
| 770 |
+
)
|
| 771 |
+
newly_opened_trades = await r2_service_global.get_open_trades_async()
|
| 772 |
+
for trade in newly_opened_trades:
|
| 773 |
+
if trade['symbol'] == new_opportunity['symbol']:
|
| 774 |
+
asyncio.create_task(realtime_monitor._monitor_single_trade(trade))
|
| 775 |
+
break
|
| 776 |
+
else:
|
| 777 |
+
print("✅ Scan complete. No actionable opportunities identified.")
|
| 778 |
+
else:
|
| 779 |
+
print("😴 No available capital. Waiting for current trade to close.")
|
| 780 |
+
|
| 781 |
+
finally:
|
| 782 |
+
print("✅ Cycle finished. Releasing lock.")
|
| 783 |
+
r2_service_global.release_lock()
|
| 784 |
+
await r2_service_global.save_system_logs_async({"cycle_completed": True, "open_trades": len(open_trades)})
|
| 785 |
+
|
| 786 |
+
except Exception as error:
|
| 787 |
+
print(f"❌ Unhandled error in main cycle: {error}")
|
| 788 |
+
traceback.print_exc()
|
| 789 |
+
await r2_service_global.save_system_logs_async({"cycle_error": True, "error": str(error)})
|
| 790 |
+
if r2_service_global.lock_acquired:
|
| 791 |
+
r2_service_global.release_lock()
|
| 792 |
+
|
| 793 |
+
@asynccontextmanager
|
| 794 |
+
async def lifespan(application: FastAPI):
|
| 795 |
+
global r2_service_global, data_manager_global, llm_service_global, learning_engine_global, realtime_monitor
|
| 796 |
+
print("===== Application Startup =====")
|
| 797 |
+
|
| 798 |
+
try:
|
| 799 |
+
r2_service_global = R2Service()
|
| 800 |
+
llm_service_global = LLMService()
|
| 801 |
+
contracts_database = await r2_service_global.load_contracts_db_async()
|
| 802 |
+
|
| 803 |
+
data_manager_global = DataManager(contracts_database)
|
| 804 |
+
await data_manager_global.initialize()
|
| 805 |
+
|
| 806 |
+
# ✅ تهيئة نظام التعلم مع تمرير data_manager
|
| 807 |
+
learning_engine_global = LearningEngine(r2_service_global, data_manager_global)
|
| 808 |
+
await learning_engine_global.initialize_enhanced() # ✅ استخدام التهيئة المحسنة
|
| 809 |
+
|
| 810 |
+
# ✅ إجبار تحديث الاستراتيجيات من البيانات الحالية
|
| 811 |
+
await learning_engine_global.force_strategy_learning()
|
| 812 |
+
|
| 813 |
+
# ✅ التحقق من أن الأوزان يتم تحميلها
|
| 814 |
+
if learning_engine_global.initialized:
|
| 815 |
+
weights = await learning_engine_global.get_optimized_strategy_weights("bull_market")
|
| 816 |
+
print(f"🎯 الأوزان المحملة: {weights}")
|
| 817 |
+
|
| 818 |
+
realtime_monitor = RealTimeTradeMonitor()
|
| 819 |
+
|
| 820 |
+
asyncio.create_task(monitor_market_async())
|
| 821 |
+
asyncio.create_task(realtime_monitor.start_monitoring())
|
| 822 |
+
|
| 823 |
+
await r2_service_global.save_system_logs_async({"application_started": True})
|
| 824 |
+
|
| 825 |
+
print("\n✅ All services initialized. Application is ready.\n")
|
| 826 |
+
yield
|
| 827 |
+
|
| 828 |
+
except Exception as error:
|
| 829 |
+
print(f"❌ Application startup failed: {error}")
|
| 830 |
+
traceback.print_exc()
|
| 831 |
+
if r2_service_global:
|
| 832 |
+
await r2_service_global.save_system_logs_async({"application_startup_failed": True, "error": str(error)})
|
| 833 |
+
raise
|
| 834 |
+
finally:
|
| 835 |
+
await cleanup_on_shutdown()
|
| 836 |
+
|
| 837 |
+
application = FastAPI(lifespan=lifespan)
|
| 838 |
+
|
| 839 |
+
@application.get("/run-cycle")
|
| 840 |
+
async def run_cycle_api():
|
| 841 |
+
"""API endpoint to trigger the bot cycle."""
|
| 842 |
+
asyncio.create_task(run_bot_cycle_async())
|
| 843 |
+
return {"message": "Bot cycle initiated in the background."}
|
| 844 |
+
|
| 845 |
+
@application.get("/health")
|
| 846 |
+
async def health_check():
|
| 847 |
+
"""Detailed health check."""
|
| 848 |
+
learning_metrics = {}
|
| 849 |
+
if learning_engine_global and learning_engine_global.initialized:
|
| 850 |
+
learning_metrics = await learning_engine_global.calculate_performance_metrics()
|
| 851 |
+
|
| 852 |
+
return {
|
| 853 |
+
"status": "healthy",
|
| 854 |
+
"timestamp": datetime.now().isoformat(),
|
| 855 |
+
"services": {
|
| 856 |
+
"r2_service": "initialized" if r2_service_global else "uninitialized",
|
| 857 |
+
"llm_service": "initialized" if llm_service_global else "uninitialized",
|
| 858 |
+
"data_manager": "initialized" if data_manager_global else "uninitialized",
|
| 859 |
+
"learning_engine": "active" if learning_engine_global and learning_engine_global.initialized else "inactive",
|
| 860 |
+
"realtime_monitor": "running" if realtime_monitor and realtime_monitor.is_running else "stopped"
|
| 861 |
+
},
|
| 862 |
+
"market_state_ok": state.MARKET_STATE_OK,
|
| 863 |
+
"learning_engine": learning_metrics
|
| 864 |
+
}
|
| 865 |
+
|
| 866 |
+
@application.get("/stats")
|
| 867 |
+
async def get_performance_stats():
|
| 868 |
+
"""Get performance statistics for all services."""
|
| 869 |
+
try:
|
| 870 |
+
market_context = await data_manager_global.get_market_context_async() if data_manager_global else {}
|
| 871 |
+
|
| 872 |
+
learning_stats = {}
|
| 873 |
+
improvement_suggestions = []
|
| 874 |
+
if learning_engine_global and learning_engine_global.initialized:
|
| 875 |
+
learning_stats = await learning_engine_global.calculate_performance_metrics()
|
| 876 |
+
improvement_suggestions = await learning_engine_global.suggest_improvements()
|
| 877 |
+
|
| 878 |
+
stats = {
|
| 879 |
+
"timestamp": datetime.now().isoformat(),
|
| 880 |
+
"data_manager": data_manager_global.get_performance_stats() if data_manager_global else {},
|
| 881 |
+
"market_state": {
|
| 882 |
+
"is_healthy": state.MARKET_STATE_OK,
|
| 883 |
+
"description": "Market is healthy for trading" if state.MARKET_STATE_OK else "Market conditions are unfavorable",
|
| 884 |
+
"context": market_context
|
| 885 |
+
},
|
| 886 |
+
"realtime_monitoring": {
|
| 887 |
+
"active_trades": len(realtime_monitor.monitoring_tasks) if realtime_monitor else 0,
|
| 888 |
+
"is_running": realtime_monitor.is_running if realtime_monitor else False
|
| 889 |
+
},
|
| 890 |
+
"learning_engine": learning_stats,
|
| 891 |
+
"improvement_suggestions": improvement_suggestions
|
| 892 |
+
}
|
| 893 |
+
return stats
|
| 894 |
+
except Exception as error:
|
| 895 |
+
raise HTTPException(status_code=500, detail=f"Failed to retrieve stats: {str(error)}")
|
| 896 |
+
|
| 897 |
+
@application.get("/logs/status")
|
| 898 |
+
async def get_logs_status():
|
| 899 |
+
"""Get status of logging system."""
|
| 900 |
+
try:
|
| 901 |
+
open_trades = await r2_service_global.get_open_trades_async()
|
| 902 |
+
portfolio_state = await r2_service_global.get_portfolio_state_async()
|
| 903 |
+
|
| 904 |
+
return {
|
| 905 |
+
"logging_system": "active",
|
| 906 |
+
"open_trades_count": len(open_trades),
|
| 907 |
+
"current_capital": portfolio_state.get("current_capital_usd", 0),
|
| 908 |
+
"total_trades": portfolio_state.get("total_trades", 0),
|
| 909 |
+
"timestamp": datetime.now().isoformat()
|
| 910 |
+
}
|
| 911 |
+
except Exception as error:
|
| 912 |
+
raise HTTPException(status_code=500, detail=f"Failed to get logs status: {str(error)}")
|
| 913 |
+
|
| 914 |
+
async def cleanup_on_shutdown():
|
| 915 |
+
"""Cleanup function for graceful shutdown."""
|
| 916 |
+
global r2_service_global, data_manager_global, realtime_monitor, learning_engine_global
|
| 917 |
+
print("\n🛑 Shutdown signal received. Cleaning up...")
|
| 918 |
+
|
| 919 |
+
if r2_service_global:
|
| 920 |
+
try:
|
| 921 |
+
await r2_service_global.save_system_logs_async({"application_shutdown": True})
|
| 922 |
+
except Exception as log_error:
|
| 923 |
+
print(f"⚠️ Failed to save shutdown log: {log_error}")
|
| 924 |
+
|
| 925 |
+
if learning_engine_global and learning_engine_global.initialized:
|
| 926 |
+
try:
|
| 927 |
+
await learning_engine_global.save_weights_to_r2()
|
| 928 |
+
await learning_engine_global.save_performance_history()
|
| 929 |
+
print("✅ Learning engine data saved.")
|
| 930 |
+
except Exception as e:
|
| 931 |
+
print(f"⚠️ Failed to save learning engine data: {e}")
|
| 932 |
+
|
| 933 |
+
if realtime_monitor:
|
| 934 |
+
realtime_monitor.stop_monitoring()
|
| 935 |
+
|
| 936 |
+
if r2_service_global and r2_service_global.lock_acquired:
|
| 937 |
+
r2_service_global.release_lock()
|
| 938 |
+
print("✅ Lock released.")
|
| 939 |
+
if data_manager_global:
|
| 940 |
+
await data_manager_global.close()
|
| 941 |
+
print("✅ Cleanup completed.")
|
| 942 |
+
|
| 943 |
+
def signal_handler(signum, frame):
|
| 944 |
+
"""Handle shutdown signals."""
|
| 945 |
+
print(f"\n⚠️ Received signal {signum}")
|
| 946 |
+
asyncio.create_task(cleanup_on_shutdown())
|
| 947 |
+
sys.exit(0)
|
| 948 |
+
|
| 949 |
+
signal.signal(signal.SIGINT, signal_handler)
|
| 950 |
+
signal.signal(signal.SIGTERM, signal_handler)
|
| 951 |
+
|
| 952 |
+
if __name__ == "__main__":
|
| 953 |
+
uvicorn.run(application, host="0.0.0.0", port=7860)
|
data_manager (23).py
ADDED
|
@@ -0,0 +1,1431 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# data_manager_fixed.py - الإصدار النهائي مع إصلاح الأخطاء الحرجة
|
| 2 |
+
import os, asyncio, httpx, json, traceback, backoff, re, time, hashlib
|
| 3 |
+
from datetime import datetime, timedelta
|
| 4 |
+
from functools import wraps
|
| 5 |
+
import ccxt.pro as ccxt
|
| 6 |
+
from ccxt.base.errors import RateLimitExceeded, DDoSProtection, NetworkError
|
| 7 |
+
import pandas as pd
|
| 8 |
+
import numpy as np
|
| 9 |
+
from state import MARKET_STATE_OK
|
| 10 |
+
|
| 11 |
+
# --- 🐋 نظام مراقبة الحيتان العام مع مصادر مؤكدة ---
|
| 12 |
+
class ReliableWhaleMonitor:
|
| 13 |
+
def __init__(self, contracts_db=None):
|
| 14 |
+
self._http_client = None
|
| 15 |
+
self.contracts_db = contracts_db or {}
|
| 16 |
+
|
| 17 |
+
self.whale_threshold_usd = 50000
|
| 18 |
+
self.token_whale_threshold_usd = 25000
|
| 19 |
+
|
| 20 |
+
# ✅ مصادر بيانات مؤكدة وعاملة 100%
|
| 21 |
+
self.reliable_data_sources = {
|
| 22 |
+
'coin_gecko': 'https://api.coingecko.com/api/v3',
|
| 23 |
+
'dex_screener': 'https://api.dexscreener.com/latest/dex',
|
| 24 |
+
'fear_greed': 'https://api.alternative.me/fng/',
|
| 25 |
+
'binance_api': 'https://api.binance.com/api/v3',
|
| 26 |
+
'bybit_api': 'https://api.bybit.com/v2/public'
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
# ✅ RPC endpoints لكل الشبكات الرئيسية
|
| 30 |
+
self.rpc_endpoints = {
|
| 31 |
+
'ethereum': [
|
| 32 |
+
'https://eth-mainnet.public.blastapi.io',
|
| 33 |
+
'https://rpc.ankr.com/eth',
|
| 34 |
+
'https://cloudflare-eth.com'
|
| 35 |
+
],
|
| 36 |
+
'bsc': [
|
| 37 |
+
'https://bsc-dataseed.binance.org/',
|
| 38 |
+
'https://bsc-dataseed1.defibit.io/',
|
| 39 |
+
'https://bsc-dataseed1.ninicoin.io/'
|
| 40 |
+
],
|
| 41 |
+
'polygon': [
|
| 42 |
+
'https://polygon-rpc.com/',
|
| 43 |
+
'https://rpc-mainnet.maticvigil.com/',
|
| 44 |
+
'https://rpc.ankr.com/polygon'
|
| 45 |
+
],
|
| 46 |
+
'arbitrum': [
|
| 47 |
+
'https://arb1.arbitrum.io/rpc',
|
| 48 |
+
'https://arbitrum.public-rpc.com'
|
| 49 |
+
],
|
| 50 |
+
'optimism': [
|
| 51 |
+
'https://mainnet.optimism.io',
|
| 52 |
+
'https://optimism.public-rpc.com'
|
| 53 |
+
],
|
| 54 |
+
'avalanche': [
|
| 55 |
+
'https://api.avax.network/ext/bc/C/rpc',
|
| 56 |
+
'https://avalanche.public-rpc.com'
|
| 57 |
+
],
|
| 58 |
+
'fantom': [
|
| 59 |
+
'https://rpc.ftm.tools/',
|
| 60 |
+
'https://fantom.public-rpc.com'
|
| 61 |
+
],
|
| 62 |
+
'base': [
|
| 63 |
+
'https://mainnet.base.org',
|
| 64 |
+
'https://base.public-rpc.com'
|
| 65 |
+
]
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
self.current_rpc_index = {network: 0 for network in self.rpc_endpoints.keys()}
|
| 69 |
+
|
| 70 |
+
# ✅ عناوين التبادلات الرئيسية للمراقبة
|
| 71 |
+
self.major_exchanges = {
|
| 72 |
+
'binance': [
|
| 73 |
+
'0x3f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be',
|
| 74 |
+
'0xd551234ae421e3bcba99a0da6d736074f22192ff',
|
| 75 |
+
'0x564286362092d8e7936f0549571a803b203aaced'
|
| 76 |
+
],
|
| 77 |
+
'coinbase': [
|
| 78 |
+
'0xa910f92acdaf488fa6ef02174fb86208ad7722ba',
|
| 79 |
+
'0x4fabb145d64652a948d72533023f6e7a623c7c53'
|
| 80 |
+
],
|
| 81 |
+
'kraken': [
|
| 82 |
+
'0x2910543af39aba0cd09dbb2d50200b3e800a63d2',
|
| 83 |
+
'0x689c56aef474df92d44a1b70850f808488f9769c'
|
| 84 |
+
]
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
self.price_cache = {}
|
| 88 |
+
self.volume_cache = {}
|
| 89 |
+
self.whale_activity_cache = {}
|
| 90 |
+
|
| 91 |
+
@property
|
| 92 |
+
def http_client(self):
|
| 93 |
+
if self._http_client is None:
|
| 94 |
+
self._http_client = httpx.AsyncClient(timeout=20.0)
|
| 95 |
+
return self._http_client
|
| 96 |
+
|
| 97 |
+
async def close(self):
|
| 98 |
+
"""إغلاق جميع العملاء بشكل آمن"""
|
| 99 |
+
if self._http_client:
|
| 100 |
+
await self._http_client.aclose()
|
| 101 |
+
self._http_client = None
|
| 102 |
+
|
| 103 |
+
async def _get_working_rpc(self, network):
|
| 104 |
+
"""الحصول على RPC عامل للشبكة المطلوبة"""
|
| 105 |
+
if network not in self.rpc_endpoints:
|
| 106 |
+
return None
|
| 107 |
+
|
| 108 |
+
endpoints = self.rpc_endpoints[network]
|
| 109 |
+
start_index = self.current_rpc_index[network]
|
| 110 |
+
|
| 111 |
+
for i in range(len(endpoints)):
|
| 112 |
+
index = (start_index + i) % len(endpoints)
|
| 113 |
+
endpoint = endpoints[index]
|
| 114 |
+
|
| 115 |
+
try:
|
| 116 |
+
# اختبار اتصال RPC
|
| 117 |
+
payload = {
|
| 118 |
+
"jsonrpc": "2.0",
|
| 119 |
+
"method": "eth_blockNumber",
|
| 120 |
+
"params": [],
|
| 121 |
+
"id": 1
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
async with httpx.AsyncClient(timeout=10) as client:
|
| 125 |
+
response = await client.post(endpoint, json=payload)
|
| 126 |
+
if response.status_code == 200:
|
| 127 |
+
self.current_rpc_index[network] = index
|
| 128 |
+
return endpoint
|
| 129 |
+
except Exception:
|
| 130 |
+
continue
|
| 131 |
+
|
| 132 |
+
return None
|
| 133 |
+
|
| 134 |
+
async def _get_token_contract_from_coingecko(self, symbol):
|
| 135 |
+
"""جلب عقد العملة من CoinGecko"""
|
| 136 |
+
try:
|
| 137 |
+
url = f"https://api.coingecko.com/api/v3/coins/{symbol.lower()}"
|
| 138 |
+
async with httpx.AsyncClient(timeout=10) as client:
|
| 139 |
+
response = await client.get(url)
|
| 140 |
+
if response.status_code == 200:
|
| 141 |
+
data = response.json()
|
| 142 |
+
platforms = data.get('platforms', {})
|
| 143 |
+
|
| 144 |
+
# إرجاع كل المنصات المتاحة
|
| 145 |
+
contract_info = {}
|
| 146 |
+
for platform, address in platforms.items():
|
| 147 |
+
if address and address != "":
|
| 148 |
+
# تحويل أسماء المنصات إلى أسماء شبكات قياسية
|
| 149 |
+
network_map = {
|
| 150 |
+
'ethereum': 'ethereum',
|
| 151 |
+
'binance-smart-chain': 'bsc',
|
| 152 |
+
'polygon-pos': 'polygon',
|
| 153 |
+
'arbitrum-one': 'arbitrum',
|
| 154 |
+
'optimistic-ethereum': 'optimism',
|
| 155 |
+
'avalanche': 'avalanche',
|
| 156 |
+
'fantom': 'fantom',
|
| 157 |
+
'base': 'base'
|
| 158 |
+
}
|
| 159 |
+
network = network_map.get(platform, platform)
|
| 160 |
+
contract_info[network] = {
|
| 161 |
+
'address': address,
|
| 162 |
+
'network': network,
|
| 163 |
+
'symbol': symbol.upper(),
|
| 164 |
+
'name': data.get('name', ''),
|
| 165 |
+
'coingecko_id': data.get('id', '')
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
return contract_info if contract_info else None
|
| 169 |
+
|
| 170 |
+
except Exception as e:
|
| 171 |
+
print(f"⚠️ فشل جلب عقد {symbol} من CoinGecko: {e}")
|
| 172 |
+
|
| 173 |
+
return None
|
| 174 |
+
|
| 175 |
+
async def _get_reliable_price(self, coin_id):
|
| 176 |
+
"""جلب سعر موثوق من CoinGecko"""
|
| 177 |
+
now = time.time()
|
| 178 |
+
cache_key = f"price_{coin_id}"
|
| 179 |
+
|
| 180 |
+
if cache_key in self.price_cache and (now - self.price_cache[cache_key]['timestamp']) < 300:
|
| 181 |
+
return self.price_cache[cache_key]['price']
|
| 182 |
+
|
| 183 |
+
try:
|
| 184 |
+
url = f"https://api.coingecko.com/api/v3/simple/price?ids={coin_id}&vs_currencies=usd"
|
| 185 |
+
async with httpx.AsyncClient(timeout=10) as client:
|
| 186 |
+
response = await client.get(url)
|
| 187 |
+
if response.status_code == 200:
|
| 188 |
+
data = response.json()
|
| 189 |
+
price = data.get(coin_id, {}).get('usd')
|
| 190 |
+
if price and price > 0:
|
| 191 |
+
self.price_cache[cache_key] = {'price': price, 'timestamp': now}
|
| 192 |
+
return price
|
| 193 |
+
elif response.status_code == 429:
|
| 194 |
+
print("⚠️ CoinGecko rate limit reached, using fallback")
|
| 195 |
+
return None
|
| 196 |
+
except Exception as e:
|
| 197 |
+
print(f"⚠️ فشل جلب سعر {coin_id}: {e}")
|
| 198 |
+
|
| 199 |
+
return None
|
| 200 |
+
|
| 201 |
+
async def _get_btc_price(self):
|
| 202 |
+
"""جلب سعر البيتكوين"""
|
| 203 |
+
return await self._get_reliable_price('bitcoin')
|
| 204 |
+
|
| 205 |
+
async def _get_eth_price(self):
|
| 206 |
+
"""جلب سعر الإيثيريوم"""
|
| 207 |
+
return await self._get_reliable_price('ethereum')
|
| 208 |
+
|
| 209 |
+
async def _get_top_crypto_volumes(self):
|
| 210 |
+
"""جلب أحجام التداول للعملات الرائدة من CoinGecko"""
|
| 211 |
+
try:
|
| 212 |
+
url = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=volume_desc&per_page=20&page=1&sparkline=false"
|
| 213 |
+
async with httpx.AsyncClient(timeout=10) as client:
|
| 214 |
+
response = await client.get(url)
|
| 215 |
+
if response.status_code == 200:
|
| 216 |
+
coins = response.json()
|
| 217 |
+
|
| 218 |
+
volume_data = []
|
| 219 |
+
for coin in coins:
|
| 220 |
+
if isinstance(coin, dict): # ✅ التحقق من النوع
|
| 221 |
+
volume_data.append({
|
| 222 |
+
'symbol': coin.get('symbol', '').upper(),
|
| 223 |
+
'name': coin.get('name', ''),
|
| 224 |
+
'volume_24h': coin.get('total_volume', 0),
|
| 225 |
+
'price': coin.get('current_price', 0),
|
| 226 |
+
'price_change_24h': coin.get('price_change_percentage_24h', 0)
|
| 227 |
+
})
|
| 228 |
+
|
| 229 |
+
return volume_data
|
| 230 |
+
elif response.status_code == 429:
|
| 231 |
+
print("⚠️ CoinGecko rate limit reached for volumes")
|
| 232 |
+
return [] # إرجاع قائمة فارغة بدلاً من None
|
| 233 |
+
else:
|
| 234 |
+
print(f"⚠️ CoinGecko returned status {response.status_code}")
|
| 235 |
+
return [] # ✅ إرجاع قائمة فارغة بدلاً من None
|
| 236 |
+
|
| 237 |
+
except Exception as e:
|
| 238 |
+
print(f"⚠️ فشل جلب أحجام التداول: {e}")
|
| 239 |
+
return [] # ✅ إرجاع قائمة فارغة بدلاً من None
|
| 240 |
+
|
| 241 |
+
async def _get_dex_screener_data(self, token_address=None):
|
| 242 |
+
"""جلب بيانات DEX من DexScreener (مصدر موثوق)"""
|
| 243 |
+
try:
|
| 244 |
+
if token_address:
|
| 245 |
+
url = f"https://api.dexscreener.com/latest/dex/tokens/{token_address}"
|
| 246 |
+
else:
|
| 247 |
+
url = "https://api.dexscreener.com/latest/dex/search?q=ETH"
|
| 248 |
+
|
| 249 |
+
async with httpx.AsyncClient(timeout=15) as client:
|
| 250 |
+
response = await client.get(url)
|
| 251 |
+
if response.status_code == 200:
|
| 252 |
+
return response.json()
|
| 253 |
+
except Exception as e:
|
| 254 |
+
print(f"⚠️ فشل جلب بيانات DexScreener: {e}")
|
| 255 |
+
|
| 256 |
+
return None
|
| 257 |
+
|
| 258 |
+
async def _analyze_dex_whale_activity(self):
|
| 259 |
+
"""تحليل نشاط الحيتان على DEXs باستخدام DexScreener"""
|
| 260 |
+
print("🔍 تحليل نشاط الحيتان على DEXs...")
|
| 261 |
+
|
| 262 |
+
whale_activity = []
|
| 263 |
+
|
| 264 |
+
try:
|
| 265 |
+
# الحصول على بيانات ETH كمرجع رئيسي
|
| 266 |
+
eth_data = await self._get_dex_screener_data("0x2170ed0880ac9a755fd29b2688956bd959f933f8")
|
| 267 |
+
|
| 268 |
+
if eth_data and 'pairs' in eth_data:
|
| 269 |
+
for pair in eth_data['pairs'][:15]: # أول 15 زوج
|
| 270 |
+
try:
|
| 271 |
+
volume_24h = float(pair.get('volume', {}).get('h24', 0))
|
| 272 |
+
liquidity = float(pair.get('liquidity', {}).get('usd', 0))
|
| 273 |
+
|
| 274 |
+
# اعتبار التداولات الكبيرة كحركات حيتان
|
| 275 |
+
if volume_24h >= self.whale_threshold_usd:
|
| 276 |
+
whale_activity.append({
|
| 277 |
+
'protocol': 'DEX',
|
| 278 |
+
'symbol': pair.get('baseToken', {}).get('symbol', 'UNKNOWN'),
|
| 279 |
+
'volume_usd': volume_24h,
|
| 280 |
+
'liquidity_usd': liquidity,
|
| 281 |
+
'price': float(pair.get('priceUsd', 0)),
|
| 282 |
+
'exchange': pair.get('dexId', 'UNKNOWN'),
|
| 283 |
+
'price_change_24h': float(pair.get('priceChange', {}).get('h24', 0))
|
| 284 |
+
})
|
| 285 |
+
except (ValueError, TypeError) as e:
|
| 286 |
+
continue
|
| 287 |
+
|
| 288 |
+
print(f"✅ تم تحليل {len(whale_activity)} حركة على DEXs")
|
| 289 |
+
|
| 290 |
+
except Exception as e:
|
| 291 |
+
print(f"❌ خطأ في تحليل DEXs: {e}")
|
| 292 |
+
|
| 293 |
+
return whale_activity
|
| 294 |
+
|
| 295 |
+
async def _analyze_cex_whale_activity(self):
|
| 296 |
+
"""تحليل نشاط الحيتان على التبادلات المركزية باستخدام CCXT"""
|
| 297 |
+
print("🏦 تحليل نشاط الحيتان على CEXs...")
|
| 298 |
+
|
| 299 |
+
whale_activity = []
|
| 300 |
+
|
| 301 |
+
try:
|
| 302 |
+
# استخدام KuCoin كمثال (يعمل بدون API key للبيانات العامة)
|
| 303 |
+
exchange = ccxt.kucoin()
|
| 304 |
+
markets = await exchange.fetch_markets()
|
| 305 |
+
|
| 306 |
+
# تحليل أكبر 20 سوق من حيث الحجم
|
| 307 |
+
for market in markets[:20]:
|
| 308 |
+
try:
|
| 309 |
+
symbol = market['symbol']
|
| 310 |
+
if '/USDT' in symbol or '/BTC' in symbol:
|
| 311 |
+
ticker = await exchange.fetch_ticker(symbol)
|
| 312 |
+
volume_24h = ticker.get('quoteVolume', 0)
|
| 313 |
+
|
| 314 |
+
if volume_24h >= self.whale_threshold_usd:
|
| 315 |
+
whale_activity.append({
|
| 316 |
+
'protocol': 'CEX',
|
| 317 |
+
'symbol': symbol,
|
| 318 |
+
'volume_usd': volume_24h,
|
| 319 |
+
'price': ticker.get('last', 0),
|
| 320 |
+
'price_change_24h': ticker.get('percentage', 0),
|
| 321 |
+
'exchange': 'kucoin'
|
| 322 |
+
})
|
| 323 |
+
except Exception:
|
| 324 |
+
continue
|
| 325 |
+
|
| 326 |
+
print(f"✅ تم تحليل {len(whale_activity)} حركة على CEXs")
|
| 327 |
+
|
| 328 |
+
except Exception as e:
|
| 329 |
+
print(f"⚠️ فشل تحليل CEXs: {e}")
|
| 330 |
+
|
| 331 |
+
return whale_activity
|
| 332 |
+
|
| 333 |
+
async def _get_market_sentiment_analysis(self):
|
| 334 |
+
"""تحليل مشاعر السوق بناءً على بيانات متعددة"""
|
| 335 |
+
print("🎭 تحليل مشاعر السوق...")
|
| 336 |
+
|
| 337 |
+
try:
|
| 338 |
+
# مؤشر الخوف والجشع
|
| 339 |
+
async with httpx.AsyncClient(timeout=10) as client:
|
| 340 |
+
response = await client.get("https://api.alternative.me/fng/")
|
| 341 |
+
if response.status_code == 200:
|
| 342 |
+
data = response.json()
|
| 343 |
+
fear_greed = int(data['data'][0]['value'])
|
| 344 |
+
fear_greed_class = data['data'][0]['value_classification']
|
| 345 |
+
else:
|
| 346 |
+
fear_greed = 50
|
| 347 |
+
fear_greed_class = "Neutral"
|
| 348 |
+
|
| 349 |
+
# اتجاه السوق بناءً على سعر البيتكوين
|
| 350 |
+
btc_price = await self._get_btc_price()
|
| 351 |
+
if btc_price:
|
| 352 |
+
if btc_price > 60000:
|
| 353 |
+
market_trend = "BULLISH"
|
| 354 |
+
elif btc_price < 55000:
|
| 355 |
+
market_trend = "BEARISH"
|
| 356 |
+
else:
|
| 357 |
+
market_trend = "NEUTRAL"
|
| 358 |
+
else:
|
| 359 |
+
market_trend = "NEUTRAL"
|
| 360 |
+
|
| 361 |
+
return {
|
| 362 |
+
'fear_greed_index': fear_greed,
|
| 363 |
+
'fear_greed_class': fear_greed_class,
|
| 364 |
+
'market_trend': market_trend,
|
| 365 |
+
'btc_price': btc_price
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
except Exception as e:
|
| 369 |
+
print(f"⚠️ فشل تحليل المشاعر: {e}")
|
| 370 |
+
return {
|
| 371 |
+
'fear_greed_index': 50,
|
| 372 |
+
'fear_greed_class': "Neutral",
|
| 373 |
+
'market_trend': "NEUTRAL",
|
| 374 |
+
'btc_price': None
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
async def get_comprehensive_whale_analysis(self):
|
| 378 |
+
"""تحليل شامل للحيتان باستخدام مصادر موثوقة"""
|
| 379 |
+
print("🐋 بدء التحليل الشامل للحيتان...")
|
| 380 |
+
|
| 381 |
+
try:
|
| 382 |
+
# ✅ تشغيل جميع التحليلات بالتوازي
|
| 383 |
+
dex_task = self._analyze_dex_whale_activity()
|
| 384 |
+
cex_task = self._analyze_cex_whale_activity()
|
| 385 |
+
sentiment_task = self._get_market_sentiment_analysis()
|
| 386 |
+
volumes_task = self._get_top_crypto_volumes()
|
| 387 |
+
|
| 388 |
+
results = await asyncio.gather(
|
| 389 |
+
dex_task, cex_task, sentiment_task, volumes_task,
|
| 390 |
+
return_exceptions=True
|
| 391 |
+
)
|
| 392 |
+
|
| 393 |
+
# ✅ معالجة النتائج
|
| 394 |
+
dex_whales = results[0] if not isinstance(results[0], Exception) else []
|
| 395 |
+
cex_whales = results[1] if not isinstance(results[1], Exception) else []
|
| 396 |
+
sentiment = results[2] if not isinstance(results[2], Exception) else {}
|
| 397 |
+
top_volumes = results[3] if not isinstance(results[3], Exception) else []
|
| 398 |
+
|
| 399 |
+
# ✅ دمج جميع البيانات
|
| 400 |
+
all_whale_activity = dex_whales + cex_whales
|
| 401 |
+
|
| 402 |
+
# ✅ حساب الإحصائيات
|
| 403 |
+
total_volume = sum(activity.get('volume_usd', 0) for activity in all_whale_activity)
|
| 404 |
+
total_transactions = len(all_whale_activity)
|
| 405 |
+
|
| 406 |
+
# ✅ تحليل أحجام العملات الرائدة
|
| 407 |
+
high_volume_coins = [coin for coin in top_volumes if coin.get('volume_24h', 0) > 100000000] # 100M+
|
| 408 |
+
|
| 409 |
+
# ✅ حساب التأثير الشامل
|
| 410 |
+
overall_impact = self._calculate_overall_impact(
|
| 411 |
+
total_volume, total_transactions, len(high_volume_coins), sentiment
|
| 412 |
+
)
|
| 413 |
+
|
| 414 |
+
# ✅ تحليل النوايا
|
| 415 |
+
intent_analysis = self._analyze_trading_intents(all_whale_activity, top_volumes)
|
| 416 |
+
|
| 417 |
+
# ✅ إنشاء التقرير
|
| 418 |
+
description = self._generate_comprehensive_description(
|
| 419 |
+
overall_impact, total_volume, total_transactions,
|
| 420 |
+
len(high_volume_coins), intent_analysis, sentiment
|
| 421 |
+
)
|
| 422 |
+
|
| 423 |
+
print(f"📊 التحليل الشامل: {overall_impact}")
|
| 424 |
+
print(f"💰 الحجم الإجمالي: ${total_volume:,.0f}")
|
| 425 |
+
print(f"🔢 عدد المعاملات: {total_transactions}")
|
| 426 |
+
print(f"🎯 العملات عالية الحجم: {len(high_volume_coins)}")
|
| 427 |
+
|
| 428 |
+
return {
|
| 429 |
+
'overall_impact': overall_impact,
|
| 430 |
+
'total_volume_usd': total_volume,
|
| 431 |
+
'total_transactions': total_transactions,
|
| 432 |
+
'description': description,
|
| 433 |
+
'high_volume_coins_count': len(high_volume_coins),
|
| 434 |
+
'whale_activity': all_whale_activity[:10], # أهم 10 حركة
|
| 435 |
+
'market_sentiment': sentiment,
|
| 436 |
+
'trading_intents': intent_analysis,
|
| 437 |
+
'timestamp': datetime.now().isoformat(),
|
| 438 |
+
'data_available': len(all_whale_activity) > 0,
|
| 439 |
+
'data_sources': ['coin_gecko', 'dex_screener', 'kucoin_api', 'fear_greed']
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
except Exception as e:
|
| 443 |
+
print(f"❌ فشل التحليل الشامل: {e}")
|
| 444 |
+
traceback.print_exc()
|
| 445 |
+
return self._get_fallback_analysis()
|
| 446 |
+
|
| 447 |
+
def _calculate_overall_impact(self, total_volume, total_transactions, high_volume_coins_count, sentiment):
|
| 448 |
+
"""حساب التأثير الشامل"""
|
| 449 |
+
if total_volume == 0 and total_transactions == 0:
|
| 450 |
+
return 'LOW'
|
| 451 |
+
|
| 452 |
+
# ✅ تحليل الحجم
|
| 453 |
+
volume_score = 0
|
| 454 |
+
if total_volume > 10000000:
|
| 455 |
+
volume_score = 4
|
| 456 |
+
elif total_volume > 5000000:
|
| 457 |
+
volume_score = 3
|
| 458 |
+
elif total_volume > 1000000:
|
| 459 |
+
volume_score = 2
|
| 460 |
+
elif total_volume > 500000:
|
| 461 |
+
volume_score = 1
|
| 462 |
+
|
| 463 |
+
# ✅ تحليل النشاط
|
| 464 |
+
activity_score = 0
|
| 465 |
+
if total_transactions > 15:
|
| 466 |
+
activity_score = 3
|
| 467 |
+
elif total_transactions > 8:
|
| 468 |
+
activity_score = 2
|
| 469 |
+
elif total_transactions > 3:
|
| 470 |
+
activity_score = 1
|
| 471 |
+
|
| 472 |
+
# ✅ تحليل العملات عالية الحجم
|
| 473 |
+
volume_coins_score = min(high_volume_coins_count, 3)
|
| 474 |
+
|
| 475 |
+
# ✅ تحليل المشاعر
|
| 476 |
+
sentiment_score = 0
|
| 477 |
+
market_trend = sentiment.get('market_trend', 'NEUTRAL')
|
| 478 |
+
if market_trend == 'BULLISH':
|
| 479 |
+
sentiment_score = 1
|
| 480 |
+
elif market_trend == 'BEARISH':
|
| 481 |
+
sentiment_score = -1
|
| 482 |
+
|
| 483 |
+
total_score = volume_score + activity_score + volume_coins_score + sentiment_score
|
| 484 |
+
|
| 485 |
+
if total_score >= 6:
|
| 486 |
+
return 'HIGH'
|
| 487 |
+
elif total_score >= 3:
|
| 488 |
+
return 'MEDIUM'
|
| 489 |
+
else:
|
| 490 |
+
return 'LOW'
|
| 491 |
+
|
| 492 |
+
def _analyze_trading_intents(self, whale_activity, top_volumes):
|
| 493 |
+
"""تحليل نوايا التداول"""
|
| 494 |
+
try:
|
| 495 |
+
# تحليل اتجاهات الأسعار في الحركات الكبيرة
|
| 496 |
+
bullish_count = 0
|
| 497 |
+
bearish_count = 0
|
| 498 |
+
|
| 499 |
+
for activity in whale_activity:
|
| 500 |
+
price_change = activity.get('price_change_24h', 0)
|
| 501 |
+
if price_change > 0:
|
| 502 |
+
bullish_count += 1
|
| 503 |
+
elif price_change < 0:
|
| 504 |
+
bearish_count += 1
|
| 505 |
+
|
| 506 |
+
# تحليل العملات عالية الحجم
|
| 507 |
+
volume_bullish = 0
|
| 508 |
+
volume_bearish = 0
|
| 509 |
+
|
| 510 |
+
for coin in top_volumes[:10]: # أهم 10 عملة
|
| 511 |
+
price_change = coin.get('price_change_24h', 0)
|
| 512 |
+
if price_change > 0:
|
| 513 |
+
volume_bullish += 1
|
| 514 |
+
elif price_change < 0:
|
| 515 |
+
volume_bearish += 1
|
| 516 |
+
|
| 517 |
+
total_bullish = bullish_count + volume_bullish
|
| 518 |
+
total_bearish = bearish_count + volume_bearish
|
| 519 |
+
|
| 520 |
+
if total_bullish > total_bearish:
|
| 521 |
+
dominant_intent = 'BUY'
|
| 522 |
+
buy_sell_ratio = total_bullish / max(total_bearish, 1)
|
| 523 |
+
elif total_bearish > total_bullish:
|
| 524 |
+
dominant_intent = 'SELL'
|
| 525 |
+
buy_sell_ratio = total_bullish / max(total_bearish, 1)
|
| 526 |
+
else:
|
| 527 |
+
dominant_intent = 'HOLD'
|
| 528 |
+
buy_sell_ratio = 1.0
|
| 529 |
+
|
| 530 |
+
# تحديد مشاعر السوق
|
| 531 |
+
if buy_sell_ratio > 1.5:
|
| 532 |
+
market_sentiment = 'BULLISH'
|
| 533 |
+
elif buy_sell_ratio > 1.0:
|
| 534 |
+
market_sentiment = 'SLIGHTLY_BULLISH'
|
| 535 |
+
elif buy_sell_ratio == 1.0:
|
| 536 |
+
market_sentiment = 'NEUTRAL'
|
| 537 |
+
elif buy_sell_ratio > 0.5:
|
| 538 |
+
market_sentiment = 'SLIGHTLY_BEARISH'
|
| 539 |
+
else:
|
| 540 |
+
market_sentiment = 'BEARISH'
|
| 541 |
+
|
| 542 |
+
return {
|
| 543 |
+
'dominant_intent': dominant_intent,
|
| 544 |
+
'buy_sell_ratio': buy_sell_ratio,
|
| 545 |
+
'market_sentiment': market_sentiment,
|
| 546 |
+
'bullish_signals': total_bullish,
|
| 547 |
+
'bearish_signals': total_bearish
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
except Exception as e:
|
| 551 |
+
print(f"⚠️ خطأ في تحليل النوايا: {e}")
|
| 552 |
+
return {
|
| 553 |
+
'dominant_intent': 'UNKNOWN',
|
| 554 |
+
'buy_sell_ratio': 1.0,
|
| 555 |
+
'market_sentiment': 'NEUTRAL',
|
| 556 |
+
'bullish_signals': 0,
|
| 557 |
+
'bearish_signals': 0
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
def _generate_comprehensive_description(self, overall_impact, total_volume, total_transactions, high_volume_coins_count, intent_analysis, sentiment):
|
| 561 |
+
"""إنشاء وصف شامل"""
|
| 562 |
+
if overall_impact == 'HIGH':
|
| 563 |
+
base_desc = "🚨 نشاط تداول عالي الخطورة"
|
| 564 |
+
elif overall_impact == 'MEDIUM':
|
| 565 |
+
base_desc = "⚠️ نشاط تداول متوسط"
|
| 566 |
+
else:
|
| 567 |
+
base_desc = "✅ نشاط تداول طبيعي"
|
| 568 |
+
|
| 569 |
+
# ✅ إضافة تفاصيل الحجم والنشاط
|
| 570 |
+
base_desc += f" (حجم: ${total_volume:,.0f}, معاملات: {total_transactions}"
|
| 571 |
+
|
| 572 |
+
if high_volume_coins_count > 0:
|
| 573 |
+
base_desc += f", عملات عالية الحجم: {high_volume_coins_count}"
|
| 574 |
+
|
| 575 |
+
base_desc += ")"
|
| 576 |
+
|
| 577 |
+
# ✅ إضافة تحليل النوايا
|
| 578 |
+
dominant_intent = intent_analysis.get('dominant_intent', 'UNKNOWN')
|
| 579 |
+
market_sentiment = intent_analysis.get('market_sentiment', 'NEUTRAL')
|
| 580 |
+
|
| 581 |
+
if dominant_intent != 'UNKNOWN':
|
| 582 |
+
base_desc += f" - النوايا: {dominant_intent}"
|
| 583 |
+
|
| 584 |
+
# ✅ إضافة مؤشر المشاعر
|
| 585 |
+
sentiment_emoji = {
|
| 586 |
+
'BULLISH': '📈',
|
| 587 |
+
'SLIGHTLY_BULLISH': '↗️',
|
| 588 |
+
'NEUTRAL': '➡️',
|
| 589 |
+
'SLIGHTLY_BEARISH': '↘️',
|
| 590 |
+
'BEARISH': '📉'
|
| 591 |
+
}.get(market_sentiment, '➡️')
|
| 592 |
+
|
| 593 |
+
base_desc += f" {sentiment_emoji}"
|
| 594 |
+
|
| 595 |
+
return base_desc
|
| 596 |
+
|
| 597 |
+
def _get_fallback_analysis(self):
|
| 598 |
+
"""تحليل احتياطي عند فشل المصادر"""
|
| 599 |
+
return {
|
| 600 |
+
'overall_impact': 'LOW',
|
| 601 |
+
'total_volume_usd': 0,
|
| 602 |
+
'total_transactions': 0,
|
| 603 |
+
'description': 'بيانات الحيتان غير متاحة حاليًا',
|
| 604 |
+
'high_volume_coins_count': 0,
|
| 605 |
+
'whale_activity': [],
|
| 606 |
+
'market_sentiment': {
|
| 607 |
+
'fear_greed_index': 50,
|
| 608 |
+
'fear_greed_class': 'Neutral',
|
| 609 |
+
'market_trend': 'NEUTRAL'
|
| 610 |
+
},
|
| 611 |
+
'trading_intents': {
|
| 612 |
+
'dominant_intent': 'UNKNOWN',
|
| 613 |
+
'buy_sell_ratio': 1.0,
|
| 614 |
+
'market_sentiment': 'NEUTRAL'
|
| 615 |
+
},
|
| 616 |
+
'timestamp': datetime.now().isoformat(),
|
| 617 |
+
'data_available': False,
|
| 618 |
+
'data_sources': []
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
async def get_whale_activity_legacy(self):
|
| 622 |
+
"""وظيفة legacy للحفاظ على التوافق"""
|
| 623 |
+
analysis = await self.get_comprehensive_whale_analysis()
|
| 624 |
+
|
| 625 |
+
sentiment_map = {
|
| 626 |
+
'BULLISH': 'BULLISH',
|
| 627 |
+
'SLIGHTLY_BULLISH': 'BULLISH',
|
| 628 |
+
'NEUTRAL': 'NEUTRAL',
|
| 629 |
+
'SLIGHTLY_BEARISH': 'BEARISH',
|
| 630 |
+
'BEARISH': 'BEARISH'
|
| 631 |
+
}
|
| 632 |
+
|
| 633 |
+
sentiment = sentiment_map.get(
|
| 634 |
+
analysis['trading_intents'].get('market_sentiment', 'NEUTRAL'),
|
| 635 |
+
'NEUTRAL'
|
| 636 |
+
)
|
| 637 |
+
|
| 638 |
+
critical_alert = analysis['overall_impact'] == 'HIGH'
|
| 639 |
+
|
| 640 |
+
return {
|
| 641 |
+
'data_available': analysis['data_available'],
|
| 642 |
+
'description': analysis['description'],
|
| 643 |
+
'critical_alert': critical_alert,
|
| 644 |
+
'sentiment': sentiment,
|
| 645 |
+
'total_volume_usd': analysis['total_volume_usd'],
|
| 646 |
+
'transaction_count': analysis['total_transactions'],
|
| 647 |
+
'market_impact': analysis['overall_impact'],
|
| 648 |
+
'buy_sell_ratio': analysis['trading_intents'].get('buy_sell_ratio', 0.5),
|
| 649 |
+
'whale_intent_analysis': analysis['trading_intents'],
|
| 650 |
+
'scan_summary': {
|
| 651 |
+
'data_sources_used': analysis.get('data_sources', []),
|
| 652 |
+
'high_volume_coins': analysis.get('high_volume_coins_count', 0),
|
| 653 |
+
'time_range_minutes': 60
|
| 654 |
+
}
|
| 655 |
+
}
|
| 656 |
+
|
| 657 |
+
# --- 🎯 نظام مراقبة الحيتان الخاص بالعملات المحددة ---
|
| 658 |
+
class SpecificWhaleMonitor:
|
| 659 |
+
"""نظام مراقبة الحيتان الخاص بالعملات المحددة"""
|
| 660 |
+
|
| 661 |
+
def __init__(self, contracts_db, general_monitor):
|
| 662 |
+
self.contracts_db = contracts_db
|
| 663 |
+
self.general_monitor = general_monitor
|
| 664 |
+
self.specific_activity_cache = {}
|
| 665 |
+
|
| 666 |
+
async def get_specific_whale_activity(self, symbols):
|
| 667 |
+
"""جلب تحركات الحيتان الخاصة برموز محددة"""
|
| 668 |
+
specific_activity = {}
|
| 669 |
+
|
| 670 |
+
for symbol in symbols:
|
| 671 |
+
try:
|
| 672 |
+
# تنظيف الرمز (إزالة /USDT etc.)
|
| 673 |
+
clean_symbol = symbol.replace('/USDT', '').replace('/BUSD', '').split('/')[0]
|
| 674 |
+
|
| 675 |
+
# البحث في قاعدة البيانات أولاً
|
| 676 |
+
contract_info = self.contracts_db.get(clean_symbol.upper())
|
| 677 |
+
|
| 678 |
+
if not contract_info:
|
| 679 |
+
# إذا لم يوجد، جلب من CoinGecko
|
| 680 |
+
contract_info = await self.general_monitor._get_token_contract_from_coingecko(clean_symbol)
|
| 681 |
+
if contract_info:
|
| 682 |
+
# حفظ في قاعدة البيانات للمستقبل
|
| 683 |
+
self.contracts_db[clean_symbol.upper()] = contract_info
|
| 684 |
+
|
| 685 |
+
if contract_info:
|
| 686 |
+
whale_data = await self._analyze_specific_whale_activity(clean_symbol, contract_info)
|
| 687 |
+
if whale_data:
|
| 688 |
+
specific_activity[symbol] = whale_data
|
| 689 |
+
|
| 690 |
+
except Exception as e:
|
| 691 |
+
print(f"⚠️ فشل مراقبة الحيتان الخاصة لـ {symbol}: {e}")
|
| 692 |
+
continue
|
| 693 |
+
|
| 694 |
+
return specific_activity
|
| 695 |
+
|
| 696 |
+
async def _analyze_specific_whale_activity(self, symbol, contract_info):
|
| 697 |
+
"""ت��ليل تحركات الحيتان لعملة محددة"""
|
| 698 |
+
try:
|
| 699 |
+
whale_activity = {
|
| 700 |
+
'symbol': symbol,
|
| 701 |
+
'contract_info': contract_info,
|
| 702 |
+
'large_transactions': [],
|
| 703 |
+
'dex_activity': [],
|
| 704 |
+
'total_volume_24h': 0,
|
| 705 |
+
'whale_transaction_count': 0,
|
| 706 |
+
'last_updated': datetime.now().isoformat()
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
# 1. تحليل نشاط DEX للعملة المحددة
|
| 710 |
+
dex_activity = await self._get_dex_activity_for_token(contract_info)
|
| 711 |
+
if dex_activity:
|
| 712 |
+
whale_activity['dex_activity'] = dex_activity
|
| 713 |
+
whale_activity['total_volume_24h'] = sum(tx.get('volume_usd', 0) for tx in dex_activity)
|
| 714 |
+
whale_activity['whale_transaction_count'] = len(dex_activity)
|
| 715 |
+
|
| 716 |
+
# 2. تحليل التحركات على الشبكة (إذا كان لدينا عقد)
|
| 717 |
+
for network, contract_data in contract_info.items():
|
| 718 |
+
if isinstance(contract_data, dict) and 'address' in contract_data:
|
| 719 |
+
network_activity = await self._get_whale_transactions_from_network(
|
| 720 |
+
contract_data['address'], network
|
| 721 |
+
)
|
| 722 |
+
whale_activity['large_transactions'].extend(network_activity)
|
| 723 |
+
|
| 724 |
+
return whale_activity if whale_activity['whale_transaction_count'] > 0 else None
|
| 725 |
+
|
| 726 |
+
except Exception as e:
|
| 727 |
+
print(f"⚠️ فشل تحليل الحيتان الخاصة لـ {symbol}: {e}")
|
| 728 |
+
return None
|
| 729 |
+
|
| 730 |
+
async def _get_dex_activity_for_token(self, contract_info):
|
| 731 |
+
"""جلب نشاط DEX للعملة المحددة"""
|
| 732 |
+
dex_activity = []
|
| 733 |
+
|
| 734 |
+
try:
|
| 735 |
+
# استخدام أول عقد متاح للبحث في DexScreener
|
| 736 |
+
for network, contract_data in contract_info.items():
|
| 737 |
+
if isinstance(contract_data, dict) and 'address' in contract_data:
|
| 738 |
+
dex_data = await self.general_monitor._get_dex_screener_data(contract_data['address'])
|
| 739 |
+
|
| 740 |
+
if dex_data and 'pairs' in dex_data:
|
| 741 |
+
for pair in dex_data['pairs'][:5]: # أول 5 أزواج فقط
|
| 742 |
+
volume_24h = float(pair.get('volume', {}).get('h24', 0))
|
| 743 |
+
|
| 744 |
+
if volume_24h >= self.general_monitor.token_whale_threshold_usd:
|
| 745 |
+
dex_activity.append({
|
| 746 |
+
'dex': pair.get('dexId', 'UNKNOWN'),
|
| 747 |
+
'pair': pair.get('pairAddress', ''),
|
| 748 |
+
'volume_usd': volume_24h,
|
| 749 |
+
'price': float(pair.get('priceUsd', 0)),
|
| 750 |
+
'price_change_24h': float(pair.get('priceChange', {}).get('h24', 0)),
|
| 751 |
+
'liquidity_usd': float(pair.get('liquidity', {}).get('usd', 0)),
|
| 752 |
+
'network': network
|
| 753 |
+
})
|
| 754 |
+
break # استخدام أول شبكة تعمل
|
| 755 |
+
|
| 756 |
+
except Exception as e:
|
| 757 |
+
print(f"⚠️ فشل جلب نشاط DEX الخاص: {e}")
|
| 758 |
+
|
| 759 |
+
return dex_activity
|
| 760 |
+
|
| 761 |
+
async def _get_whale_transactions_from_network(self, contract_address, network):
|
| 762 |
+
"""جلب تحركات الحيتان من الشبكة باستخدام RPC"""
|
| 763 |
+
try:
|
| 764 |
+
rpc_url = await self.general_monitor._get_working_rpc(network)
|
| 765 |
+
if not rpc_url:
|
| 766 |
+
return []
|
| 767 |
+
|
| 768 |
+
# هنا يمكن إضافة كود لتحليل التحركات الكبيرة من الشبكة
|
| 769 |
+
# هذا مثال مبسط - في الواقع تحتاج لخدمات متخصصة مثل Moralis, Alchemy, etc.
|
| 770 |
+
|
| 771 |
+
payload = {
|
| 772 |
+
"jsonrpc": "2.0",
|
| 773 |
+
"method": "eth_getLogs",
|
| 774 |
+
"params": [{
|
| 775 |
+
"address": contract_address,
|
| 776 |
+
"fromBlock": "latest",
|
| 777 |
+
"toBlock": "latest",
|
| 778 |
+
"topics": [
|
| 779 |
+
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" # Transfer event
|
| 780 |
+
]
|
| 781 |
+
}],
|
| 782 |
+
"id": 1
|
| 783 |
+
}
|
| 784 |
+
|
| 785 |
+
async with httpx.AsyncClient(timeout=15) as client:
|
| 786 |
+
response = await client.post(rpc_url, json=payload)
|
| 787 |
+
if response.status_code == 200:
|
| 788 |
+
data = response.json()
|
| 789 |
+
# معالجة البيانات هنا
|
| 790 |
+
return self._process_transfer_events(data.get('result', []), network)
|
| 791 |
+
|
| 792 |
+
except Exception as e:
|
| 793 |
+
print(f"⚠️ فشل جلب تحركات من {network}: {e}")
|
| 794 |
+
|
| 795 |
+
return []
|
| 796 |
+
|
| 797 |
+
def _process_transfer_events(self, events, network):
|
| 798 |
+
"""معالجة أحداث التحويل لاكتشاف التحركات الكبيرة"""
|
| 799 |
+
large_transfers = []
|
| 800 |
+
|
| 801 |
+
for event in events[:10]: # تحليل أول 10 أحداث فقط للأداء
|
| 802 |
+
try:
|
| 803 |
+
# استخراج كمية التحويل (هذا مثال مبسط)
|
| 804 |
+
# في التطبيق الحقيقي تحتاج لتحليل أكثر تعقيداً
|
| 805 |
+
if 'data' in event and len(event['data']) >= 66:
|
| 806 |
+
# تحويل hex إلى قيمة رقمية
|
| 807 |
+
value_hex = event['data'][2:66] # إزالة 0x
|
| 808 |
+
value = int(value_hex, 16)
|
| 809 |
+
|
| 810 |
+
# اعتبار التحويلات الكبيرة كتحركات حيتان
|
| 811 |
+
if value > 1000000000000000000: # 1 ETH أو ما يعادله
|
| 812 |
+
large_transfers.append({
|
| 813 |
+
'network': network,
|
| 814 |
+
'value': value,
|
| 815 |
+
'transaction_hash': event.get('transactionHash', ''),
|
| 816 |
+
'from': event.get('topics', [])[1] if len(event.get('topics', [])) > 1 else '',
|
| 817 |
+
'to': event.get('topics', [])[2] if len(event.get('topics', [])) > 2 else '',
|
| 818 |
+
'timestamp': datetime.now().isoformat()
|
| 819 |
+
})
|
| 820 |
+
except Exception:
|
| 821 |
+
continue
|
| 822 |
+
|
| 823 |
+
return large_transfers
|
| 824 |
+
|
| 825 |
+
# إنشاء نسخة عالمية
|
| 826 |
+
whale_monitor_global = ReliableWhaleMonitor()
|
| 827 |
+
|
| 828 |
+
class DataManager:
|
| 829 |
+
def __init__(self, contracts_db):
|
| 830 |
+
self.contracts_db = contracts_db or {}
|
| 831 |
+
self.exchange = ccxt.kucoin()
|
| 832 |
+
self.exchange.rateLimit = 1000
|
| 833 |
+
self._http_client = None
|
| 834 |
+
self.fetch_stats = {'successful_fetches': 0, 'failed_fetches': 0, 'rate_limit_hits': 0}
|
| 835 |
+
|
| 836 |
+
# ✅ نظامي مراقبة الحيتان
|
| 837 |
+
self.general_whale_monitor = ReliableWhaleMonitor(contracts_db)
|
| 838 |
+
self.specific_whale_monitor = SpecificWhaleMonitor(contracts_db, self.general_whale_monitor)
|
| 839 |
+
|
| 840 |
+
async def initialize(self):
|
| 841 |
+
print("✅ DataManager initialized - DUAL WHALE MONITORING SYSTEM WITH RPC SUPPORT")
|
| 842 |
+
|
| 843 |
+
async def close(self):
|
| 844 |
+
"""إغلاق جميع العملاء بشكل آمن"""
|
| 845 |
+
if self._http_client:
|
| 846 |
+
await self._http_client.aclose()
|
| 847 |
+
self._http_client = None
|
| 848 |
+
await self.general_whale_monitor.close()
|
| 849 |
+
if hasattr(self.exchange, 'close'):
|
| 850 |
+
await self.exchange.close()
|
| 851 |
+
|
| 852 |
+
async def get_whale_data_safe_async(self, symbol):
|
| 853 |
+
"""دالة آمنة لجلب بيانات الحيتان - الإصلاح"""
|
| 854 |
+
try:
|
| 855 |
+
# استخدام البيانات العامة للحيتان كبديل
|
| 856 |
+
general_analysis = await self.general_whale_monitor.get_comprehensive_whale_analysis()
|
| 857 |
+
|
| 858 |
+
return {
|
| 859 |
+
'data_available': general_analysis.get('data_available', False),
|
| 860 |
+
'transfer_count': general_analysis.get('total_transactions', 0),
|
| 861 |
+
'total_volume': general_analysis.get('total_volume_usd', 0),
|
| 862 |
+
'source': 'general_whale_monitor',
|
| 863 |
+
'symbol': symbol
|
| 864 |
+
}
|
| 865 |
+
except Exception as e:
|
| 866 |
+
print(f"⚠️ فشل جلب بيانات الحيتان لـ {symbol}: {e}")
|
| 867 |
+
return {
|
| 868 |
+
'data_available': False,
|
| 869 |
+
'transfer_count': 0,
|
| 870 |
+
'total_volume': 0,
|
| 871 |
+
'source': 'fallback',
|
| 872 |
+
'symbol': symbol
|
| 873 |
+
}
|
| 874 |
+
|
| 875 |
+
async def get_enhanced_market_context(self):
|
| 876 |
+
"""جلب سياق السوق مع بيانات الحيتان العامة والخاصة"""
|
| 877 |
+
try:
|
| 878 |
+
print("📊 جلب سياق السوق المحسن...")
|
| 879 |
+
|
| 880 |
+
# ✅ جلب البيانات الأساسية
|
| 881 |
+
sentiment_task = self.get_sentiment_safe_async()
|
| 882 |
+
price_task = self._get_prices_safe_async()
|
| 883 |
+
general_whale_task = self.general_whale_monitor.get_comprehensive_whale_analysis()
|
| 884 |
+
|
| 885 |
+
results = await asyncio.gather(
|
| 886 |
+
sentiment_task, price_task, general_whale_task,
|
| 887 |
+
return_exceptions=True
|
| 888 |
+
)
|
| 889 |
+
|
| 890 |
+
sentiment_data = results[0] if not isinstance(results[0], Exception) else None
|
| 891 |
+
price_data = results[1] if not isinstance(results[1], Exception) else {}
|
| 892 |
+
general_whale_impact = results[2] if not isinstance(results[2], Exception) else None
|
| 893 |
+
|
| 894 |
+
bitcoin_price = price_data.get('bitcoin')
|
| 895 |
+
ethereum_price = price_data.get('ethereum')
|
| 896 |
+
|
| 897 |
+
# ✅ استخدام بيانات KuCoin كبديل إذا فشل CoinGecko
|
| 898 |
+
if not bitcoin_price or not ethereum_price:
|
| 899 |
+
print("🔄 استخدام بيانات KuCoin كبديل للأسعار...")
|
| 900 |
+
try:
|
| 901 |
+
btc_ticker = await self.exchange.fetch_ticker('BTC/USDT')
|
| 902 |
+
eth_ticker = await self.exchange.fetch_ticker('ETH/USDT')
|
| 903 |
+
bitcoin_price = btc_ticker.get('last')
|
| 904 |
+
ethereum_price = eth_ticker.get('last')
|
| 905 |
+
print(f"✅ أسعار KuCoin البديلة: BTC=${bitcoin_price}, ETH=${ethereum_price}")
|
| 906 |
+
except Exception as e:
|
| 907 |
+
print(f"❌ فشل جلب الأسعار البديلة: {e}")
|
| 908 |
+
|
| 909 |
+
if not bitcoin_price or not ethereum_price:
|
| 910 |
+
print("❌ لا توجد بيانات أسعار حقيقية")
|
| 911 |
+
return None
|
| 912 |
+
|
| 913 |
+
# ✅ تحليل اتجاه السوق
|
| 914 |
+
market_trend = self._determine_market_trend(bitcoin_price, sentiment_data, general_whale_impact)
|
| 915 |
+
|
| 916 |
+
# ✅ استخدام البيانات الاحتياطية إذا لزم الأمر
|
| 917 |
+
if not general_whale_impact:
|
| 918 |
+
print("⚠️ استخدام بيانات حيتان احتياطية")
|
| 919 |
+
general_whale_impact = self.general_whale_monitor._get_fallback_analysis()
|
| 920 |
+
|
| 921 |
+
market_context = {
|
| 922 |
+
'timestamp': datetime.now().isoformat(),
|
| 923 |
+
'bitcoin_price_usd': bitcoin_price,
|
| 924 |
+
'ethereum_price_usd': ethereum_price,
|
| 925 |
+
'fear_and_greed_index': sentiment_data.get('feargreed_value') if sentiment_data else None,
|
| 926 |
+
'sentiment_class': sentiment_data.get('feargreed_class') if sentiment_data else None,
|
| 927 |
+
'general_whale_activity': await self._get_legacy_whale_data(general_whale_impact),
|
| 928 |
+
'whale_impact_analysis': general_whale_impact,
|
| 929 |
+
'market_trend': market_trend,
|
| 930 |
+
'btc_sentiment': 'BULLISH' if bitcoin_price and bitcoin_price > 60000 else 'BEARISH' if bitcoin_price and bitcoin_price < 55000 else 'NEUTRAL',
|
| 931 |
+
'data_sources': {
|
| 932 |
+
'prices': bool(bitcoin_price),
|
| 933 |
+
'sentiment': bool(sentiment_data and sentiment_data.get('feargreed_value')),
|
| 934 |
+
'general_whale_data': general_whale_impact.get('data_available', False)
|
| 935 |
+
},
|
| 936 |
+
'data_quality': 'HIGH' if bitcoin_price and sentiment_data and general_whale_impact else 'LOW'
|
| 937 |
+
}
|
| 938 |
+
|
| 939 |
+
print(f"📊 سياق السوق المحسن: BTC=${bitcoin_price:,.0f}, F&G={market_context['fear_and_greed_index']}")
|
| 940 |
+
|
| 941 |
+
return market_context
|
| 942 |
+
|
| 943 |
+
except Exception as e:
|
| 944 |
+
print(f"❌ فشل جلب سياق السوق المحسن: {e}")
|
| 945 |
+
return None
|
| 946 |
+
|
| 947 |
+
async def get_candidate_specific_whale_analysis(self, candidates):
|
| 948 |
+
"""جلب تحليل الحيتان الخاص للمرشحين"""
|
| 949 |
+
if not candidates:
|
| 950 |
+
return {}
|
| 951 |
+
|
| 952 |
+
symbols = [candidate.get('symbol') for candidate in candidates if candidate.get('symbol')]
|
| 953 |
+
return await self.specific_whale_monitor.get_specific_whale_activity(symbols)
|
| 954 |
+
|
| 955 |
+
async def get_sentiment_safe_async(self):
|
| 956 |
+
"""جلب بيانات المشاعر الحقيقية فقط"""
|
| 957 |
+
max_retries = 3
|
| 958 |
+
for attempt in range(max_retries):
|
| 959 |
+
try:
|
| 960 |
+
print(f"🎭 جلب بيانات المشاعر الحقيقية - المحاولة {attempt + 1}/{max_retries}...")
|
| 961 |
+
|
| 962 |
+
async with httpx.AsyncClient(timeout=15.0) as client:
|
| 963 |
+
response = await client.get("https://api.alternative.me/fng/")
|
| 964 |
+
response.raise_for_status()
|
| 965 |
+
data = response.json()
|
| 966 |
+
|
| 967 |
+
if 'data' not in data or not data['data']:
|
| 968 |
+
raise ValueError("بيانات المشاعر غير متوفرة في الاستجابة")
|
| 969 |
+
|
| 970 |
+
latest_data = data['data'][0]
|
| 971 |
+
print(f"✅ تم جلب بيانات المشاعر الحقيقية: {latest_data['value']} ({latest_data['value_classification']})")
|
| 972 |
+
|
| 973 |
+
return {
|
| 974 |
+
"feargreed_value": int(latest_data['value']),
|
| 975 |
+
"feargreed_class": latest_data['value_classification'],
|
| 976 |
+
"source": "alternative.me",
|
| 977 |
+
"timestamp": datetime.now().isoformat()
|
| 978 |
+
}
|
| 979 |
+
|
| 980 |
+
except Exception as e:
|
| 981 |
+
print(f"⚠️ فشل محاولة {attempt + 1}/{max_retries} لجلب بيانات المشاعر: {e}")
|
| 982 |
+
if attempt < max_retries - 1:
|
| 983 |
+
await asyncio.sleep(2)
|
| 984 |
+
|
| 985 |
+
print("🚨 فشل جميع محاولات جلب بيانات المشاعر الحقيقية")
|
| 986 |
+
return None
|
| 987 |
+
|
| 988 |
+
async def get_market_context_async(self):
|
| 989 |
+
"""جلب سياق السوق مع البيانات الحقيقية فقط"""
|
| 990 |
+
return await self.get_enhanced_market_context()
|
| 991 |
+
|
| 992 |
+
async def _get_prices_safe_async(self):
|
| 993 |
+
"""جلب الأسعار الحقيقية فقط"""
|
| 994 |
+
try:
|
| 995 |
+
async with httpx.AsyncClient(timeout=15.0) as client:
|
| 996 |
+
response = await client.get('https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd')
|
| 997 |
+
if response.status_code == 200:
|
| 998 |
+
data = response.json()
|
| 999 |
+
bitcoin_price = data.get('bitcoin', {}).get('usd')
|
| 1000 |
+
ethereum_price = data.get('ethereum', {}).get('usd')
|
| 1001 |
+
|
| 1002 |
+
if bitcoin_price and ethereum_price:
|
| 1003 |
+
return {
|
| 1004 |
+
'bitcoin': bitcoin_price,
|
| 1005 |
+
'ethereum': ethereum_price
|
| 1006 |
+
}
|
| 1007 |
+
elif response.status_code == 429:
|
| 1008 |
+
print("⚠️ CoinGecko rate limit reached, using exchange prices")
|
| 1009 |
+
except Exception as e:
|
| 1010 |
+
print(f"⚠️ فشل جلب الأسعار الحقيقية: {e}")
|
| 1011 |
+
|
| 1012 |
+
return {}
|
| 1013 |
+
|
| 1014 |
+
async def _get_legacy_whale_data(self, whale_impact):
|
| 1015 |
+
"""تحويل بيانات التأثير إلى تنسيق legacy"""
|
| 1016 |
+
if not whale_impact:
|
| 1017 |
+
return {
|
| 1018 |
+
'data_available': False,
|
| 1019 |
+
'description': 'No real data available',
|
| 1020 |
+
'critical_alert': False,
|
| 1021 |
+
'sentiment': 'NEUTRAL',
|
| 1022 |
+
'total_volume_usd': 0,
|
| 1023 |
+
'transaction_count': 0
|
| 1024 |
+
}
|
| 1025 |
+
|
| 1026 |
+
sentiment_map = {
|
| 1027 |
+
'BULLISH': 'BULLISH',
|
| 1028 |
+
'SLIGHTLY_BULLISH': 'BULLISH',
|
| 1029 |
+
'NEUTRAL': 'NEUTRAL',
|
| 1030 |
+
'SLIGHTLY_BEARISH': 'BEARISH',
|
| 1031 |
+
'BEARISH': 'BEARISH'
|
| 1032 |
+
}
|
| 1033 |
+
|
| 1034 |
+
sentiment = sentiment_map.get(
|
| 1035 |
+
whale_impact.get('trading_intents', {}).get('market_sentiment', 'NEUTRAL'),
|
| 1036 |
+
'NEUTRAL'
|
| 1037 |
+
)
|
| 1038 |
+
|
| 1039 |
+
critical_alert = whale_impact['overall_impact'] == 'HIGH'
|
| 1040 |
+
|
| 1041 |
+
return {
|
| 1042 |
+
'data_available': whale_impact['data_available'],
|
| 1043 |
+
'description': whale_impact['description'],
|
| 1044 |
+
'critical_alert': critical_alert,
|
| 1045 |
+
'sentiment': sentiment,
|
| 1046 |
+
'total_volume_usd': whale_impact['total_volume_usd'],
|
| 1047 |
+
'transaction_count': whale_impact['total_transactions']
|
| 1048 |
+
}
|
| 1049 |
+
|
| 1050 |
+
def _determine_market_trend(self, bitcoin_price, sentiment_data, whale_impact):
|
| 1051 |
+
"""تحديد اتجاه السوق بناءً على البيانات الحقيقية"""
|
| 1052 |
+
try:
|
| 1053 |
+
if not bitcoin_price:
|
| 1054 |
+
return "unknown"
|
| 1055 |
+
|
| 1056 |
+
score = 0
|
| 1057 |
+
|
| 1058 |
+
# تحليل سعر البيتكوين الحقيقي
|
| 1059 |
+
if bitcoin_price > 60000:
|
| 1060 |
+
score += 1
|
| 1061 |
+
elif bitcoin_price < 55000:
|
| 1062 |
+
score -= 1
|
| 1063 |
+
|
| 1064 |
+
# تحليل مؤشر الخوف والجشع الحقيقي
|
| 1065 |
+
if sentiment_data:
|
| 1066 |
+
fear_greed = sentiment_data.get('feargreed_value')
|
| 1067 |
+
if fear_greed and fear_greed > 60:
|
| 1068 |
+
score += 1
|
| 1069 |
+
elif fear_greed and fear_greed < 40:
|
| 1070 |
+
score -= 1
|
| 1071 |
+
|
| 1072 |
+
# تحليل تأثير الحيتان الحقيقي
|
| 1073 |
+
if whale_impact:
|
| 1074 |
+
if whale_impact.get('overall_impact') == 'HIGH':
|
| 1075 |
+
score -= 1
|
| 1076 |
+
elif whale_impact.get('overall_impact') == 'MEDIUM':
|
| 1077 |
+
score += 0
|
| 1078 |
+
|
| 1079 |
+
# ✅ تحليل نوايا الحيتان
|
| 1080 |
+
trading_intents = whale_impact.get('trading_intents', {})
|
| 1081 |
+
market_sentiment = trading_intents.get('market_sentiment', 'NEUTRAL')
|
| 1082 |
+
|
| 1083 |
+
if market_sentiment in ['BULLISH', 'SLIGHTLY_BULLISH']:
|
| 1084 |
+
score += 1
|
| 1085 |
+
elif market_sentiment in ['BEARISH', 'SLIGHTLY_BEARISH']:
|
| 1086 |
+
score -= 1
|
| 1087 |
+
|
| 1088 |
+
if score >= 2:
|
| 1089 |
+
return "bull_market"
|
| 1090 |
+
elif score <= -2:
|
| 1091 |
+
return "bear_market"
|
| 1092 |
+
elif -1 <= score <= 1:
|
| 1093 |
+
return "sideways_market"
|
| 1094 |
+
else:
|
| 1095 |
+
return "volatile_market"
|
| 1096 |
+
|
| 1097 |
+
except Exception as e:
|
| 1098 |
+
print(f"⚠️ خطأ في تحديد اتجاه السوق: {e}")
|
| 1099 |
+
return "unknown"
|
| 1100 |
+
|
| 1101 |
+
# ✅ باقي دوال DataManager الأساسية
|
| 1102 |
+
@staticmethod
|
| 1103 |
+
def adaptive_backoff(func):
|
| 1104 |
+
@backoff.on_exception(backoff.expo, (RateLimitExceeded, DDoSProtection, NetworkError, httpx.TimeoutException), max_tries=3, max_time=20)
|
| 1105 |
+
@wraps(func)
|
| 1106 |
+
async def wrapper(*args, **kwargs):
|
| 1107 |
+
return await func(*args, **kwargs)
|
| 1108 |
+
return wrapper
|
| 1109 |
+
|
| 1110 |
+
@adaptive_backoff
|
| 1111 |
+
async def fetch_ohlcv_with_retry(self, symbol: str, timeframe: str, limit: int = 100):
|
| 1112 |
+
"""جلب بيانات OHLCV الحقيقية مع إعادة المحاولة"""
|
| 1113 |
+
try:
|
| 1114 |
+
candles = await self.exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=limit)
|
| 1115 |
+
self.fetch_stats['successful_fetches'] += 1
|
| 1116 |
+
return candles
|
| 1117 |
+
except RateLimitExceeded:
|
| 1118 |
+
self.fetch_stats['rate_limit_hits'] += 1
|
| 1119 |
+
print(f"⚠️ Rate limit hit for {symbol} on {timeframe}")
|
| 1120 |
+
raise
|
| 1121 |
+
except Exception as e:
|
| 1122 |
+
self.fetch_stats['failed_fetches'] += 1
|
| 1123 |
+
print(f"❌ Failed to fetch {symbol} {timeframe}: {e}")
|
| 1124 |
+
raise
|
| 1125 |
+
|
| 1126 |
+
async def find_high_potential_candidates(self, n=100):
|
| 1127 |
+
"""إيجاد مرشحين عاليي الإمكانية مع بيانات الحيتان الخاصة"""
|
| 1128 |
+
print("🚀 Starting High Potential Candidate Scan with Specific Whale Data...")
|
| 1129 |
+
|
| 1130 |
+
try:
|
| 1131 |
+
all_tickers = list((await self.exchange.fetch_tickers()).values())
|
| 1132 |
+
if not all_tickers:
|
| 1133 |
+
print("❌ Failed to fetch real tickers data")
|
| 1134 |
+
return []
|
| 1135 |
+
|
| 1136 |
+
usdt_tickers = [
|
| 1137 |
+
t for t in all_tickers
|
| 1138 |
+
if '/USDT' in t.get('symbol', '')
|
| 1139 |
+
and t.get('quoteVolume', 0) > 100000
|
| 1140 |
+
]
|
| 1141 |
+
|
| 1142 |
+
if not usdt_tickers:
|
| 1143 |
+
print("❌ No USDT pairs with sufficient real volume")
|
| 1144 |
+
return []
|
| 1145 |
+
|
| 1146 |
+
top_volume_symbols = [
|
| 1147 |
+
t['symbol'] for t in sorted(usdt_tickers, key=lambda x: x.get('quoteVolume', 0), reverse=True)[:200]
|
| 1148 |
+
]
|
| 1149 |
+
|
| 1150 |
+
print(f"📊 Analyzing {len(top_volume_symbols)} symbols with highest real volume")
|
| 1151 |
+
|
| 1152 |
+
# المسوحات الأساسية ببيانات حقيقية
|
| 1153 |
+
momentum_candidates = await self._scan_for_momentum(usdt_tickers, top_n=30)
|
| 1154 |
+
breakout_candidates = await self._scan_for_breakouts(top_volume_symbols, top_n=30)
|
| 1155 |
+
volume_spike_candidates = await self._scan_for_volume_spike(top_volume_symbols, top_n=30)
|
| 1156 |
+
|
| 1157 |
+
combined_candidates = {}
|
| 1158 |
+
for candidates_dict in [momentum_candidates, breakout_candidates, volume_spike_candidates]:
|
| 1159 |
+
for symbol, reasons in candidates_dict.items():
|
| 1160 |
+
combined_candidates.setdefault(symbol, set()).update(reasons)
|
| 1161 |
+
|
| 1162 |
+
if not MARKET_STATE_OK:
|
| 1163 |
+
print("🚨 Risk filter: Market state is NOT OK. Halting search.")
|
| 1164 |
+
return []
|
| 1165 |
+
|
| 1166 |
+
final_list = []
|
| 1167 |
+
tickers_map = {t['symbol']: t for t in usdt_tickers}
|
| 1168 |
+
|
| 1169 |
+
for symbol, reasons in combined_candidates.items():
|
| 1170 |
+
ticker_info = tickers_map.get(symbol)
|
| 1171 |
+
if not ticker_info:
|
| 1172 |
+
continue
|
| 1173 |
+
|
| 1174 |
+
change_percent = ticker_info.get('change', 0) or 0
|
| 1175 |
+
if change_percent > 500:
|
| 1176 |
+
continue
|
| 1177 |
+
|
| 1178 |
+
quote_volume = ticker_info.get('quoteVolume', 0)
|
| 1179 |
+
if quote_volume < 50000:
|
| 1180 |
+
continue
|
| 1181 |
+
|
| 1182 |
+
final_list.append({
|
| 1183 |
+
'symbol': symbol,
|
| 1184 |
+
'reasons': list(reasons),
|
| 1185 |
+
'score': len(reasons),
|
| 1186 |
+
'change_percent': change_percent,
|
| 1187 |
+
'volume': quote_volume
|
| 1188 |
+
})
|
| 1189 |
+
|
| 1190 |
+
sorted_final_list = sorted(final_list, key=lambda x: x['score'], reverse=True)
|
| 1191 |
+
|
| 1192 |
+
# ✅ إضافة بيانات الحيتان الخاصة للمرشحين
|
| 1193 |
+
if sorted_final_list:
|
| 1194 |
+
specific_whale_data = await self.get_candidate_specific_whale_analysis(sorted_final_list)
|
| 1195 |
+
|
| 1196 |
+
for candidate in sorted_final_list:
|
| 1197 |
+
symbol = candidate.get('symbol')
|
| 1198 |
+
if symbol in specific_whale_data:
|
| 1199 |
+
candidate['specific_whale_activity'] = specific_whale_data[symbol]
|
| 1200 |
+
# تحسين النقاط بناءً على نشاط الحيتان الخاص
|
| 1201 |
+
whale_score = self._calculate_whale_score(specific_whale_data[symbol])
|
| 1202 |
+
candidate['whale_score'] = whale_score
|
| 1203 |
+
candidate['final_score'] = candidate.get('final_score', 0.5) + (whale_score * 0.2)
|
| 1204 |
+
print(f"🐋 Added specific whale data for {symbol} - Score: {whale_score:.2f}")
|
| 1205 |
+
|
| 1206 |
+
print(f"✅ Real data scan complete. Found {len(sorted_final_list)} potential candidates with whale data.")
|
| 1207 |
+
|
| 1208 |
+
return sorted_final_list[:n]
|
| 1209 |
+
|
| 1210 |
+
except Exception as e:
|
| 1211 |
+
print(f"❌ Failed to find high potential candidates with real data: {e}")
|
| 1212 |
+
traceback.print_exc()
|
| 1213 |
+
return []
|
| 1214 |
+
|
| 1215 |
+
def _calculate_whale_score(self, whale_data):
|
| 1216 |
+
"""حساب نقاط الحيتان بناءً على النشاط"""
|
| 1217 |
+
if not whale_data:
|
| 1218 |
+
return 0.0
|
| 1219 |
+
|
| 1220 |
+
score = 0.0
|
| 1221 |
+
|
| 1222 |
+
# نقاط بناءً على حجم التداول
|
| 1223 |
+
total_volume = whale_data.get('total_volume_24h', 0)
|
| 1224 |
+
if total_volume > 1000000: # 1M+
|
| 1225 |
+
score += 3.0
|
| 1226 |
+
elif total_volume > 500000: # 500K+
|
| 1227 |
+
score += 2.0
|
| 1228 |
+
elif total_volume > 100000: # 100K+
|
| 1229 |
+
score += 1.0
|
| 1230 |
+
|
| 1231 |
+
# نقاط بناءً على عدد المعاملات
|
| 1232 |
+
transaction_count = whale_data.get('whale_transaction_count', 0)
|
| 1233 |
+
if transaction_count > 10:
|
| 1234 |
+
score += 2.0
|
| 1235 |
+
elif transaction_count > 5:
|
| 1236 |
+
score += 1.0
|
| 1237 |
+
|
| 1238 |
+
# نقاط بناءً على التحركات الكبيرة على الشبكة
|
| 1239 |
+
large_tx_count = len(whale_data.get('large_transactions', []))
|
| 1240 |
+
if large_tx_count > 5:
|
| 1241 |
+
score += 2.0
|
| 1242 |
+
elif large_tx_count > 2:
|
| 1243 |
+
score += 1.0
|
| 1244 |
+
|
| 1245 |
+
return min(score / 7.0, 1.0) # تطبيع إلى 0-1
|
| 1246 |
+
|
| 1247 |
+
async def _scan_for_momentum(self, tickers, top_n=30):
|
| 1248 |
+
"""مسح الزخم ببيانات حقيقية"""
|
| 1249 |
+
print("🔍 Running Real Momentum Scanner...")
|
| 1250 |
+
valid_tickers = [t for t in tickers if t.get('change') is not None]
|
| 1251 |
+
if not valid_tickers:
|
| 1252 |
+
print("⚠️ No valid tickers for real momentum analysis")
|
| 1253 |
+
return {}
|
| 1254 |
+
|
| 1255 |
+
sorted_by_change = sorted(valid_tickers, key=lambda x: x.get('change', 0), reverse=True)
|
| 1256 |
+
return {ticker['symbol']: {'momentum'} for ticker in sorted_by_change[:top_n]}
|
| 1257 |
+
|
| 1258 |
+
async def _scan_for_breakouts(self, symbols, top_n=30):
|
| 1259 |
+
"""مسح الانكسارات ببيانات حقيقية"""
|
| 1260 |
+
print("🧱 Running Real Breakout Scanner...")
|
| 1261 |
+
candidates = {}
|
| 1262 |
+
tasks = [self.exchange.fetch_ohlcv(symbol, '1h', limit=24) for symbol in symbols]
|
| 1263 |
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
| 1264 |
+
|
| 1265 |
+
for i, result in enumerate(results):
|
| 1266 |
+
if isinstance(result, Exception) or not result:
|
| 1267 |
+
continue
|
| 1268 |
+
|
| 1269 |
+
df = pd.DataFrame(result, columns=['time', 'open', 'high', 'low', 'close', 'volume'])
|
| 1270 |
+
if len(df) < 2:
|
| 1271 |
+
continue
|
| 1272 |
+
|
| 1273 |
+
recent_high = df['high'].iloc[:-1].max()
|
| 1274 |
+
current_price = df['close'].iloc[-1]
|
| 1275 |
+
|
| 1276 |
+
if current_price > recent_high * 1.02:
|
| 1277 |
+
candidates[symbols[i]] = {'breakout'}
|
| 1278 |
+
if len(candidates) >= top_n:
|
| 1279 |
+
break
|
| 1280 |
+
|
| 1281 |
+
print(f"✅ Found {len(candidates)} real breakout candidates")
|
| 1282 |
+
return candidates
|
| 1283 |
+
|
| 1284 |
+
async def _scan_for_volume_spike(self, symbols, top_n=30):
|
| 1285 |
+
"""مسح ارتفاع الحجم ببيانات حقيقية"""
|
| 1286 |
+
print("💧 Running Real Volume Spike Scanner...")
|
| 1287 |
+
candidates = {}
|
| 1288 |
+
tasks = [self.exchange.fetch_ohlcv(symbol, '1h', limit=24) for symbol in symbols]
|
| 1289 |
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
| 1290 |
+
|
| 1291 |
+
for i, result in enumerate(results):
|
| 1292 |
+
if isinstance(result, Exception) or not result:
|
| 1293 |
+
continue
|
| 1294 |
+
|
| 1295 |
+
df = pd.DataFrame(result, columns=['time', 'open', 'high', 'low', 'close', 'volume'])
|
| 1296 |
+
df['volume'] = pd.to_numeric(df['volume'])
|
| 1297 |
+
if len(df) < 2:
|
| 1298 |
+
continue
|
| 1299 |
+
|
| 1300 |
+
average_volume = df['volume'].iloc[:-1].mean()
|
| 1301 |
+
current_volume = df['volume'].iloc[-1]
|
| 1302 |
+
|
| 1303 |
+
if current_volume > average_volume * 3 and current_volume > 10000:
|
| 1304 |
+
candidates[symbols[i]] = {'volume_spike'}
|
| 1305 |
+
if len(candidates) >= top_n:
|
| 1306 |
+
break
|
| 1307 |
+
|
| 1308 |
+
print(f"✅ Found {len(candidates)} real volume spike candidates")
|
| 1309 |
+
return candidates
|
| 1310 |
+
|
| 1311 |
+
async def get_fast_pass_data_async(self, symbols_with_reasons):
|
| 1312 |
+
"""جلب بيانات سريعة حقيقية للمرشحين"""
|
| 1313 |
+
timeframes = ['5m', '15m', '1h', '4h', '1d', '1w']
|
| 1314 |
+
data = []
|
| 1315 |
+
total = len(symbols_with_reasons)
|
| 1316 |
+
completed = 0
|
| 1317 |
+
success = 0
|
| 1318 |
+
failed = 0
|
| 1319 |
+
semaphore = asyncio.Semaphore(5)
|
| 1320 |
+
|
| 1321 |
+
async def fetch_symbol_data(symbol_data, index):
|
| 1322 |
+
nonlocal completed, success, failed
|
| 1323 |
+
symbol = symbol_data['symbol']
|
| 1324 |
+
reasons = symbol_data['reasons']
|
| 1325 |
+
|
| 1326 |
+
async with semaphore:
|
| 1327 |
+
try:
|
| 1328 |
+
print(f"⏳ [{index+1}/{total}] Fetching real data for {symbol}...")
|
| 1329 |
+
ohlcv_data = {}
|
| 1330 |
+
timeframes_fetched = 0
|
| 1331 |
+
|
| 1332 |
+
for timeframe in timeframes:
|
| 1333 |
+
try:
|
| 1334 |
+
candles = await self.fetch_ohlcv_with_retry(symbol, timeframe, limit=100)
|
| 1335 |
+
|
| 1336 |
+
if candles and len(candles) >= 20:
|
| 1337 |
+
cleaned_candles = []
|
| 1338 |
+
for candle in candles:
|
| 1339 |
+
if len(candle) >= 6:
|
| 1340 |
+
cleaned_candles.append([
|
| 1341 |
+
candle[0],
|
| 1342 |
+
float(candle[1]),
|
| 1343 |
+
float(candle[2]),
|
| 1344 |
+
float(candle[3]),
|
| 1345 |
+
float(candle[4]),
|
| 1346 |
+
float(candle[5])
|
| 1347 |
+
])
|
| 1348 |
+
|
| 1349 |
+
if len(cleaned_candles) >= 20:
|
| 1350 |
+
ohlcv_data[timeframe] = cleaned_candles
|
| 1351 |
+
timeframes_fetched += 1
|
| 1352 |
+
else:
|
| 1353 |
+
ohlcv_data[timeframe] = []
|
| 1354 |
+
else:
|
| 1355 |
+
ohlcv_data[timeframe] = []
|
| 1356 |
+
|
| 1357 |
+
except Exception as e:
|
| 1358 |
+
ohlcv_data[timeframe] = []
|
| 1359 |
+
|
| 1360 |
+
await asyncio.sleep(0.2)
|
| 1361 |
+
|
| 1362 |
+
completed += 1
|
| 1363 |
+
|
| 1364 |
+
if timeframes_fetched >= 2:
|
| 1365 |
+
success += 1
|
| 1366 |
+
print(f"✅ [{index+1}/{total}] {symbol} - {timeframes_fetched}/{len(timeframes)} real timeframes")
|
| 1367 |
+
return {
|
| 1368 |
+
'symbol': symbol,
|
| 1369 |
+
'ohlcv': ohlcv_data,
|
| 1370 |
+
'reasons': reasons,
|
| 1371 |
+
'successful_timeframes': timeframes_fetched
|
| 1372 |
+
}
|
| 1373 |
+
else:
|
| 1374 |
+
failed += 1
|
| 1375 |
+
print(f"⚠️ [{index+1}/{total}] {symbol} - Insufficient real data")
|
| 1376 |
+
return None
|
| 1377 |
+
|
| 1378 |
+
except Exception as e:
|
| 1379 |
+
completed += 1
|
| 1380 |
+
failed += 1
|
| 1381 |
+
print(f"❌ [{index+1}/{total}] {symbol} - Real data error")
|
| 1382 |
+
return None
|
| 1383 |
+
|
| 1384 |
+
print(f"📊 Starting real data fetch for {total} symbols")
|
| 1385 |
+
|
| 1386 |
+
tasks = [fetch_symbol_data(symbol_data, i) for i, symbol_data in enumerate(symbols_with_reasons)]
|
| 1387 |
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
| 1388 |
+
|
| 1389 |
+
for result in results:
|
| 1390 |
+
if isinstance(result, Exception):
|
| 1391 |
+
continue
|
| 1392 |
+
elif result is not None:
|
| 1393 |
+
data.append(result)
|
| 1394 |
+
|
| 1395 |
+
success_rate = (success/total*100) if total > 0 else 0
|
| 1396 |
+
print(f"✅ Real data fetching complete! Success: {success}/{total} ({success_rate:.1f}%)")
|
| 1397 |
+
|
| 1398 |
+
return data
|
| 1399 |
+
|
| 1400 |
+
async def get_latest_price_async(self, symbol):
|
| 1401 |
+
"""جلب آخر سعر حقيقي لرمز"""
|
| 1402 |
+
max_retries = 2
|
| 1403 |
+
for attempt in range(max_retries):
|
| 1404 |
+
try:
|
| 1405 |
+
ticker = await self.exchange.fetch_ticker(symbol)
|
| 1406 |
+
price = ticker.get('last')
|
| 1407 |
+
if price and price > 0:
|
| 1408 |
+
return price
|
| 1409 |
+
except Exception as e:
|
| 1410 |
+
print(f"⚠️ فشل محاولة {attempt + 1}/{max_retries} لجلب سعر حقيقي لـ {symbol}: {e}")
|
| 1411 |
+
if attempt < max_retries - 1:
|
| 1412 |
+
await asyncio.sleep(1)
|
| 1413 |
+
|
| 1414 |
+
print(f"❌ فشل جميع محاولات جلب سعر حقيقي لـ {symbol}")
|
| 1415 |
+
return None
|
| 1416 |
+
|
| 1417 |
+
def get_performance_stats(self):
|
| 1418 |
+
"""الحصول على إحصائيات الأداء الحقيقية"""
|
| 1419 |
+
total_attempts = self.fetch_stats['successful_fetches'] + self.fetch_stats['failed_fetches']
|
| 1420 |
+
success_rate = (self.fetch_stats['successful_fetches'] / total_attempts * 100) if total_attempts > 0 else 0
|
| 1421 |
+
|
| 1422 |
+
return {
|
| 1423 |
+
'total_attempts': total_attempts,
|
| 1424 |
+
'successful_fetches': self.fetch_stats['successful_fetches'],
|
| 1425 |
+
'failed_fetches': self.fetch_stats['failed_fetches'],
|
| 1426 |
+
'rate_limit_hits': self.fetch_stats['rate_limit_hits'],
|
| 1427 |
+
'success_rate': f"{success_rate:.1f}%",
|
| 1428 |
+
'timestamp': datetime.now().isoformat()
|
| 1429 |
+
}
|
| 1430 |
+
|
| 1431 |
+
print("✅ ENHANCED Data Manager Loaded - DUAL WHALE MONITORING SYSTEM - GENERAL & SPECIFIC ANALYSIS - MULTI-NETWORK RPC SUPPORT")
|
learning_engine (19).py
ADDED
|
@@ -0,0 +1,644 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import asyncio
|
| 4 |
+
import pandas as pd
|
| 5 |
+
import numpy as np
|
| 6 |
+
from datetime import datetime, timedelta
|
| 7 |
+
from typing import Dict, List, Any
|
| 8 |
+
import hashlib
|
| 9 |
+
|
| 10 |
+
class LearningEngine:
|
| 11 |
+
def __init__(self, r2_service, data_manager):
|
| 12 |
+
self.r2_service = r2_service
|
| 13 |
+
self.data_manager = data_manager
|
| 14 |
+
self.weights = {}
|
| 15 |
+
self.performance_history = []
|
| 16 |
+
self.strategy_effectiveness = {}
|
| 17 |
+
self.market_patterns = {}
|
| 18 |
+
self.risk_profiles = {}
|
| 19 |
+
self.initialized = False
|
| 20 |
+
self.initialization_lock = asyncio.Lock()
|
| 21 |
+
|
| 22 |
+
async def initialize(self):
|
| 23 |
+
"""تهيئة نظام التعلم من R2"""
|
| 24 |
+
async with self.initialization_lock:
|
| 25 |
+
if self.initialized:
|
| 26 |
+
return
|
| 27 |
+
|
| 28 |
+
print("🧠 تهيئة نظام التعلم الذاتي...")
|
| 29 |
+
try:
|
| 30 |
+
await self.load_weights_from_r2()
|
| 31 |
+
await self.load_performance_history()
|
| 32 |
+
self.initialized = True
|
| 33 |
+
print("✅ نظام التعلم جاهز - الأوزان محملة بنجاح")
|
| 34 |
+
except Exception as e:
|
| 35 |
+
print(f"⚠️ لم يتم تحميل الأوزان السابقة: {e} - سيتم البدء بأوزان افتراضية")
|
| 36 |
+
await self.initialize_default_weights()
|
| 37 |
+
self.initialized = True
|
| 38 |
+
|
| 39 |
+
async def initialize_enhanced(self):
|
| 40 |
+
"""تهيئة محسنة لنظام التعلم"""
|
| 41 |
+
async with self.initialization_lock:
|
| 42 |
+
if self.initialized:
|
| 43 |
+
return
|
| 44 |
+
|
| 45 |
+
print("🧠 تهيئة نظام التعلم الذاتي المحسّن...")
|
| 46 |
+
try:
|
| 47 |
+
await self.load_weights_from_r2()
|
| 48 |
+
await self.load_performance_history()
|
| 49 |
+
|
| 50 |
+
# إصلاح هيكل الأوزان إذا لزم الأمر
|
| 51 |
+
await self.fix_weights_structure()
|
| 52 |
+
|
| 53 |
+
# إذا لم تكن هناك بيانات كافية، بدء التعلم من الصفر
|
| 54 |
+
if not self.performance_history:
|
| 55 |
+
print("🔰 بدء التعلم من الصفر - لا توجد بيانات تاريخية")
|
| 56 |
+
await self.initialize_default_weights()
|
| 57 |
+
|
| 58 |
+
self.initialized = True
|
| 59 |
+
print("✅ نظام التعلم المحسّن جاهز")
|
| 60 |
+
|
| 61 |
+
except Exception as e:
|
| 62 |
+
print(f"⚠️ فشل التهيئة المحسنة: {e}")
|
| 63 |
+
await self.initialize_default_weights()
|
| 64 |
+
self.initialized = True
|
| 65 |
+
|
| 66 |
+
async def fix_weights_structure(self):
|
| 67 |
+
"""إصلاح هيكل الأوزان ليتوافق مع الكود"""
|
| 68 |
+
try:
|
| 69 |
+
# تحميل البيانات الحالية
|
| 70 |
+
key = "learning_engine_weights.json"
|
| 71 |
+
response = self.r2_service.s3_client.get_object(Bucket="trading", Key=key)
|
| 72 |
+
current_data = json.loads(response['Body'].read())
|
| 73 |
+
|
| 74 |
+
# إذا كان الهيكل قديماً، قم بتحديثه
|
| 75 |
+
if 'strategy_weights' in current_data and 'last_updated' not in current_data:
|
| 76 |
+
fixed_data = {
|
| 77 |
+
"weights": current_data,
|
| 78 |
+
"last_updated": datetime.now().isoformat(),
|
| 79 |
+
"version": "2.0",
|
| 80 |
+
"performance_metrics": await self.calculate_performance_metrics()
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
data_json = json.dumps(fixed_data, indent=2, ensure_ascii=False).encode('utf-8')
|
| 84 |
+
self.r2_service.s3_client.put_object(
|
| 85 |
+
Bucket="trading", Key=key, Body=data_json, ContentType="application/json"
|
| 86 |
+
)
|
| 87 |
+
print("✅ تم إصلاح هيكل الأوزان بنجاح")
|
| 88 |
+
|
| 89 |
+
except Exception as e:
|
| 90 |
+
print(f"⚠️ لم يتم إصلاح هيكل الأوزان: {e}")
|
| 91 |
+
|
| 92 |
+
async def initialize_default_weights(self):
|
| 93 |
+
"""تهيئة الأوزان الافتراضية - موزعة بشكل أفضل"""
|
| 94 |
+
self.weights = {
|
| 95 |
+
"strategy_weights": {
|
| 96 |
+
"trend_following": 0.18,
|
| 97 |
+
"mean_reversion": 0.15,
|
| 98 |
+
"breakout_momentum": 0.22,
|
| 99 |
+
"volume_spike": 0.12,
|
| 100 |
+
"whale_tracking": 0.15,
|
| 101 |
+
"pattern_recognition": 0.10,
|
| 102 |
+
"hybrid_ai": 0.08
|
| 103 |
+
},
|
| 104 |
+
"technical_weights": {
|
| 105 |
+
"rsi": 0.15,
|
| 106 |
+
"macd": 0.18,
|
| 107 |
+
"ema_cross": 0.12,
|
| 108 |
+
"bollinger_bands": 0.10,
|
| 109 |
+
"volume_analysis": 0.15,
|
| 110 |
+
"support_resistance": 0.12,
|
| 111 |
+
"market_sentiment": 0.18
|
| 112 |
+
},
|
| 113 |
+
"risk_parameters": {
|
| 114 |
+
"max_position_size": 0.1,
|
| 115 |
+
"max_daily_loss": 0.02,
|
| 116 |
+
"stop_loss_base": 0.02,
|
| 117 |
+
"risk_reward_ratio": 2.0,
|
| 118 |
+
"volatility_adjustment": 1.0
|
| 119 |
+
},
|
| 120 |
+
"market_condition_weights": {
|
| 121 |
+
"bull_market": {
|
| 122 |
+
"trend_following": 0.25,
|
| 123 |
+
"breakout_momentum": 0.20,
|
| 124 |
+
"whale_tracking": 0.15
|
| 125 |
+
},
|
| 126 |
+
"bear_market": {
|
| 127 |
+
"mean_reversion": 0.25,
|
| 128 |
+
"pattern_recognition": 0.20,
|
| 129 |
+
"hybrid_ai": 0.15
|
| 130 |
+
},
|
| 131 |
+
"sideways_market": {
|
| 132 |
+
"mean_reversion": 0.30,
|
| 133 |
+
"volume_spike": 0.20,
|
| 134 |
+
"pattern_recognition": 0.15
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
async def load_weights_from_r2(self):
|
| 140 |
+
"""تحميل الأوزان من R2"""
|
| 141 |
+
try:
|
| 142 |
+
key = "learning_engine_weights.json"
|
| 143 |
+
response = self.r2_service.s3_client.get_object(
|
| 144 |
+
Bucket="trading", Key=key
|
| 145 |
+
)
|
| 146 |
+
weights_data = json.loads(response['Body'].read())
|
| 147 |
+
|
| 148 |
+
# التعامل مع الهيكل الجديد والقديم
|
| 149 |
+
if isinstance(weights_data, dict):
|
| 150 |
+
if 'weights' in weights_data:
|
| 151 |
+
self.weights = weights_data['weights']
|
| 152 |
+
else:
|
| 153 |
+
self.weights = weights_data
|
| 154 |
+
print(f"✅ تم تحميل الأوزان من R2 بنجاح. إصدار الهيكل: {'جديد' if 'weights' in weights_data else 'قديم'}")
|
| 155 |
+
else:
|
| 156 |
+
raise ValueError("هيكل الأوزان غير صحيح")
|
| 157 |
+
|
| 158 |
+
except Exception as e:
|
| 159 |
+
print(f"❌ فشل تحميل الأوزان: {e}")
|
| 160 |
+
await self.initialize_default_weights()
|
| 161 |
+
await self.save_weights_to_r2()
|
| 162 |
+
|
| 163 |
+
async def save_weights_to_r2(self):
|
| 164 |
+
"""حفظ الأوزان إلى R2"""
|
| 165 |
+
try:
|
| 166 |
+
key = "learning_engine_weights.json"
|
| 167 |
+
weights_data = {
|
| 168 |
+
"weights": self.weights,
|
| 169 |
+
"last_updated": datetime.now().isoformat(),
|
| 170 |
+
"version": "2.0",
|
| 171 |
+
"performance_metrics": await self.calculate_performance_metrics()
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
data_json = json.dumps(weights_data, indent=2, ensure_ascii=False).encode('utf-8')
|
| 175 |
+
self.r2_service.s3_client.put_object(
|
| 176 |
+
Bucket="trading",
|
| 177 |
+
Key=key,
|
| 178 |
+
Body=data_json,
|
| 179 |
+
ContentType="application/json"
|
| 180 |
+
)
|
| 181 |
+
print("✅ تم حفظ الأوزان إلى R2 بنجاح")
|
| 182 |
+
except Exception as e:
|
| 183 |
+
print(f"❌ فشل حفظ الأوزان: {e}")
|
| 184 |
+
|
| 185 |
+
async def load_performance_history(self):
|
| 186 |
+
"""تحميل سجل الأداء"""
|
| 187 |
+
try:
|
| 188 |
+
key = "learning_performance_history.json"
|
| 189 |
+
response = self.r2_service.s3_client.get_object(
|
| 190 |
+
Bucket="trading", Key=key
|
| 191 |
+
)
|
| 192 |
+
history_data = json.loads(response['Body'].read())
|
| 193 |
+
self.performance_history = history_data.get("history", [])
|
| 194 |
+
print(f"✅ تم تحميل سجل الأداء - {len(self.performance_history)} تسجيل")
|
| 195 |
+
except Exception as e:
|
| 196 |
+
print(f"⚠️ لم يتم تحميل سجل الأداء: {e}")
|
| 197 |
+
self.performance_history = []
|
| 198 |
+
|
| 199 |
+
async def save_performance_history(self):
|
| 200 |
+
"""حفظ سجل الأداء"""
|
| 201 |
+
try:
|
| 202 |
+
key = "learning_performance_history.json"
|
| 203 |
+
history_data = {
|
| 204 |
+
"history": self.performance_history[-1000:],
|
| 205 |
+
"last_updated": datetime.now().isoformat()
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
data_json = json.dumps(history_data, indent=2, ensure_ascii=False).encode('utf-8')
|
| 209 |
+
self.r2_service.s3_client.put_object(
|
| 210 |
+
Bucket="trading",
|
| 211 |
+
Key=key,
|
| 212 |
+
Body=data_json,
|
| 213 |
+
ContentType="application/json"
|
| 214 |
+
)
|
| 215 |
+
except Exception as e:
|
| 216 |
+
print(f"❌ فشل حفظ سجل الأداء: {e}")
|
| 217 |
+
|
| 218 |
+
async def analyze_trade_outcome(self, trade_data, outcome):
|
| 219 |
+
"""تحليل نتيجة الصفقة وتحديث الأوزان - الإصدار المحسّن"""
|
| 220 |
+
if not self.initialized:
|
| 221 |
+
await self.initialize()
|
| 222 |
+
|
| 223 |
+
try:
|
| 224 |
+
# استخراج الاستراتيجية من بيانات الصفقة
|
| 225 |
+
strategy = trade_data.get('strategy', 'unknown')
|
| 226 |
+
if strategy == 'unknown':
|
| 227 |
+
decision_data = trade_data.get('decision_data', {})
|
| 228 |
+
strategy = decision_data.get('strategy', 'unknown')
|
| 229 |
+
|
| 230 |
+
# الحصول على سياق السوق الحقيقي
|
| 231 |
+
market_context = await self.get_current_market_conditions()
|
| 232 |
+
|
| 233 |
+
analysis_entry = {
|
| 234 |
+
"timestamp": datetime.now().isoformat(),
|
| 235 |
+
"trade_data": trade_data,
|
| 236 |
+
"outcome": outcome,
|
| 237 |
+
"market_conditions": market_context,
|
| 238 |
+
"strategy_used": strategy,
|
| 239 |
+
"symbol": trade_data.get('symbol', 'unknown'),
|
| 240 |
+
"pnl_usd": trade_data.get('pnl_usd', 0),
|
| 241 |
+
"pnl_percent": trade_data.get('pnl_percent', 0)
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
self.performance_history.append(analysis_entry)
|
| 245 |
+
|
| 246 |
+
await self.update_strategy_effectiveness(analysis_entry)
|
| 247 |
+
await self.update_market_patterns(analysis_entry)
|
| 248 |
+
|
| 249 |
+
# ✅ التحديث الهام: تحديث الأوزان بعد كل صفقة في البداية
|
| 250 |
+
if len(self.performance_history) <= 10: # أول 10 صفقات
|
| 251 |
+
await self.adapt_weights_based_on_performance()
|
| 252 |
+
await self.save_weights_to_r2()
|
| 253 |
+
await self.save_performance_history()
|
| 254 |
+
else:
|
| 255 |
+
# بعد ذلك، تحديث كل 3 صفقات
|
| 256 |
+
if len(self.performance_history) % 3 == 0:
|
| 257 |
+
await self.adapt_weights_based_on_performance()
|
| 258 |
+
await self.save_weights_to_r2()
|
| 259 |
+
await self.save_performance_history()
|
| 260 |
+
|
| 261 |
+
print(f"📊 تم تحليل صفقة {trade_data.get('symbol')} - الاستراتيجية: {strategy} - النتيجة: {outcome} - PnL: {trade_data.get('pnl_percent', 0):.2f}%")
|
| 262 |
+
|
| 263 |
+
except Exception as e:
|
| 264 |
+
print(f"❌ فشل تحليل نتيجة الصفقة: {e}")
|
| 265 |
+
|
| 266 |
+
async def update_strategy_effectiveness(self, analysis_entry):
|
| 267 |
+
"""تحديث فعالية الاستراتيجيات"""
|
| 268 |
+
strategy = analysis_entry['strategy_used']
|
| 269 |
+
outcome = analysis_entry['outcome']
|
| 270 |
+
market_condition = analysis_entry['market_conditions']['current_trend']
|
| 271 |
+
pnl_percent = analysis_entry.get('pnl_percent', 0)
|
| 272 |
+
|
| 273 |
+
if strategy not in self.strategy_effectiveness:
|
| 274 |
+
self.strategy_effectiveness[strategy] = {
|
| 275 |
+
"total_trades": 0,
|
| 276 |
+
"successful_trades": 0,
|
| 277 |
+
"total_profit": 0,
|
| 278 |
+
"total_pnl_percent": 0,
|
| 279 |
+
"market_conditions": {}
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
self.strategy_effectiveness[strategy]["total_trades"] += 1
|
| 283 |
+
self.strategy_effectiveness[strategy]["total_pnl_percent"] += pnl_percent
|
| 284 |
+
|
| 285 |
+
# تحديد النجاح بناءً على النتيجة والأداء
|
| 286 |
+
is_success = outcome in ["SUCCESS", "CLOSED_BY_REANALYSIS", "CLOSED_BY_MONITOR"] and pnl_percent > 0
|
| 287 |
+
if is_success:
|
| 288 |
+
self.strategy_effectiveness[strategy]["successful_trades"] += 1
|
| 289 |
+
|
| 290 |
+
if market_condition not in self.strategy_effectiveness[strategy]["market_conditions"]:
|
| 291 |
+
self.strategy_effectiveness[strategy]["market_conditions"][market_condition] = {
|
| 292 |
+
"trades": 0,
|
| 293 |
+
"successes": 0,
|
| 294 |
+
"total_pnl": 0
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
self.strategy_effectiveness[strategy]["market_conditions"][market_condition]["trades"] += 1
|
| 298 |
+
self.strategy_effectiveness[strategy]["market_conditions"][market_condition]["total_pnl"] += pnl_percent
|
| 299 |
+
if is_success:
|
| 300 |
+
self.strategy_effectiveness[strategy]["market_conditions"][market_condition]["successes"] += 1
|
| 301 |
+
|
| 302 |
+
async def update_market_patterns(self, analysis_entry):
|
| 303 |
+
"""تحديث أنماط السوق"""
|
| 304 |
+
market_condition = analysis_entry['market_conditions']['current_trend']
|
| 305 |
+
symbol = analysis_entry['symbol']
|
| 306 |
+
outcome = analysis_entry['outcome']
|
| 307 |
+
pnl_percent = analysis_entry.get('pnl_percent', 0)
|
| 308 |
+
|
| 309 |
+
if market_condition not in self.market_patterns:
|
| 310 |
+
self.market_patterns[market_condition] = {
|
| 311 |
+
"total_trades": 0,
|
| 312 |
+
"successful_trades": 0,
|
| 313 |
+
"total_pnl_percent": 0,
|
| 314 |
+
"best_performing_strategies": {},
|
| 315 |
+
"best_performing_symbols": {}
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
self.market_patterns[market_condition]["total_trades"] += 1
|
| 319 |
+
self.market_patterns[market_condition]["total_pnl_percent"] += pnl_percent
|
| 320 |
+
|
| 321 |
+
is_success = outcome in ["SUCCESS", "CLOSED_BY_REANALYSIS", "CLOSED_BY_MONITOR"] and pnl_percent > 0
|
| 322 |
+
if is_success:
|
| 323 |
+
self.market_patterns[market_condition]["successful_trades"] += 1
|
| 324 |
+
|
| 325 |
+
strategy = analysis_entry['strategy_used']
|
| 326 |
+
if strategy not in self.market_patterns[market_condition]["best_performing_strategies"]:
|
| 327 |
+
self.market_patterns[market_condition]["best_performing_strategies"][strategy] = {
|
| 328 |
+
"count": 0,
|
| 329 |
+
"total_pnl": 0
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
self.market_patterns[market_condition]["best_performing_strategies"][strategy]["count"] += 1
|
| 333 |
+
self.market_patterns[market_condition]["best_performing_strategies"][strategy]["total_pnl"] += pnl_percent
|
| 334 |
+
|
| 335 |
+
if symbol not in self.market_patterns[market_condition]["best_performing_symbols"]:
|
| 336 |
+
self.market_patterns[market_condition]["best_performing_symbols"][symbol] = {
|
| 337 |
+
"count": 0,
|
| 338 |
+
"total_pnl": 0
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
self.market_patterns[market_condition]["best_performing_symbols"][symbol]["count"] += 1
|
| 342 |
+
self.market_patterns[market_condition]["best_performing_symbols"][symbol]["total_pnl"] += pnl_percent
|
| 343 |
+
|
| 344 |
+
async def adapt_weights_based_on_performance(self):
|
| 345 |
+
"""تعديل الأوزان بناءً على الأداء - الإصدار المحسّن"""
|
| 346 |
+
print("🔄 تحديث الأوزان بناءً على الأداء...")
|
| 347 |
+
|
| 348 |
+
try:
|
| 349 |
+
# إذا لم تكن هناك بيانات كافية، استخدم تحديثاً تدريجياً
|
| 350 |
+
if not self.strategy_effectiveness:
|
| 351 |
+
print("⚠️ لا توجد بيانات أداء كافية، استخدام تحديث تدريجي")
|
| 352 |
+
await self.gradual_weights_adjustment()
|
| 353 |
+
return
|
| 354 |
+
|
| 355 |
+
# تحديث أوزان الاستراتيجيات بناءً على الأداء الحقيقي
|
| 356 |
+
total_performance = 0
|
| 357 |
+
strategy_performance = {}
|
| 358 |
+
|
| 359 |
+
for strategy, data in self.strategy_effectiveness.items():
|
| 360 |
+
if data["total_trades"] > 0:
|
| 361 |
+
success_rate = data["successful_trades"] / data["total_trades"]
|
| 362 |
+
avg_pnl = data["total_pnl_percent"] / data["total_trades"]
|
| 363 |
+
|
| 364 |
+
# حساب الأداء المركب
|
| 365 |
+
composite_performance = (success_rate * 0.7) + (min(avg_pnl, 10) / 10 * 0.3)
|
| 366 |
+
strategy_performance[strategy] = composite_performance
|
| 367 |
+
total_performance += composite_performance
|
| 368 |
+
|
| 369 |
+
# إذا كان هناك أداء كافٍ، قم بالتحديث
|
| 370 |
+
if total_performance > 0 and strategy_performance:
|
| 371 |
+
for strategy, performance in strategy_performance.items():
|
| 372 |
+
current_weight = self.weights["strategy_weights"].get(strategy, 0.1)
|
| 373 |
+
# تحديث تدريجي لتجنب التغيرات المفاجئة
|
| 374 |
+
new_weight = current_weight * 0.7 + (performance * 0.3)
|
| 375 |
+
self.weights["strategy_weights"][strategy] = new_weight
|
| 376 |
+
|
| 377 |
+
# تطبيع الأوزان
|
| 378 |
+
self.normalize_weights()
|
| 379 |
+
print("✅ تم تحديث الأوزان بناءً على الأداء الحقيقي")
|
| 380 |
+
else:
|
| 381 |
+
await self.gradual_weights_adjustment()
|
| 382 |
+
|
| 383 |
+
except Exception as e:
|
| 384 |
+
print(f"❌ فشل تحديث الأوزان: {e}")
|
| 385 |
+
await self.gradual_weights_adjustment()
|
| 386 |
+
|
| 387 |
+
async def gradual_weights_adjustment(self):
|
| 388 |
+
"""تعديل تدريجي للأوزان لتحسين الأداء"""
|
| 389 |
+
print("📈 إجراء تعديل تدريجي على الأوزان...")
|
| 390 |
+
|
| 391 |
+
# زيادة وزن الاستراتيجيات التي تعتمد على البيانات المتاحة
|
| 392 |
+
if self.market_patterns:
|
| 393 |
+
for market_condition, data in self.market_patterns.items():
|
| 394 |
+
if data.get("total_trades", 0) > 0:
|
| 395 |
+
best_strategy = max(data["best_performing_strategies"].items(),
|
| 396 |
+
key=lambda x: x[1]["total_pnl"])[0] if data["best_performing_strategies"] else None
|
| 397 |
+
if best_strategy:
|
| 398 |
+
current_weight = self.weights["strategy_weights"].get(best_strategy, 0.1)
|
| 399 |
+
self.weights["strategy_weights"][best_strategy] = min(current_weight * 1.1, 0.3)
|
| 400 |
+
|
| 401 |
+
self.normalize_weights()
|
| 402 |
+
print("✅ تم التعديل التدريجي للأوزان")
|
| 403 |
+
|
| 404 |
+
def normalize_weights(self):
|
| 405 |
+
"""تطبيع الأوزان للتأكد من أن مجموعها 1"""
|
| 406 |
+
total = sum(self.weights["strategy_weights"].values())
|
| 407 |
+
if total > 0:
|
| 408 |
+
for strategy in self.weights["strategy_weights"]:
|
| 409 |
+
self.weights["strategy_weights"][strategy] /= total
|
| 410 |
+
|
| 411 |
+
async def get_current_market_conditions(self):
|
| 412 |
+
"""الحصول على ظروف السوق الحالية - بيانات حقيقية"""
|
| 413 |
+
try:
|
| 414 |
+
if not self.data_manager:
|
| 415 |
+
raise ValueError("DataManager غير متوفر")
|
| 416 |
+
|
| 417 |
+
market_context = await self.data_manager.get_market_context_async()
|
| 418 |
+
if not market_context:
|
| 419 |
+
raise ValueError("فشل جلب سياق السوق")
|
| 420 |
+
|
| 421 |
+
return {
|
| 422 |
+
"current_trend": market_context.get('market_trend', 'sideways_market'),
|
| 423 |
+
"volatility": self._calculate_market_volatility(market_context),
|
| 424 |
+
"market_sentiment": market_context.get('btc_sentiment', 'NEUTRAL'),
|
| 425 |
+
"whale_activity": market_context.get('general_whale_activity', {}).get('sentiment', 'NEUTRAL'),
|
| 426 |
+
"fear_greed_index": market_context.get('fear_and_greed_index', 50)
|
| 427 |
+
}
|
| 428 |
+
except Exception as e:
|
| 429 |
+
print(f"⚠️ فشل الحصول على ظروف السوق: {e}")
|
| 430 |
+
return {
|
| 431 |
+
"current_trend": "sideways_market",
|
| 432 |
+
"volatility": "medium",
|
| 433 |
+
"market_sentiment": "neutral",
|
| 434 |
+
"whale_activity": "low",
|
| 435 |
+
"fear_greed_index": 50
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
def _calculate_market_volatility(self, market_context):
|
| 439 |
+
"""حساب تقلبية السوق بناءً على البيانات الحقيقية"""
|
| 440 |
+
try:
|
| 441 |
+
btc_price = market_context.get('bitcoin_price_usd', 0)
|
| 442 |
+
fear_greed = market_context.get('fear_and_greed_index', 50)
|
| 443 |
+
whale_sentiment = market_context.get('general_whale_activity', {}).get('sentiment', 'NEUTRAL')
|
| 444 |
+
|
| 445 |
+
volatility_score = 0
|
| 446 |
+
|
| 447 |
+
# تحليل سعر البيتكوين (تغيرات كبيرة = تقلبية عالية)
|
| 448 |
+
if btc_price > 0:
|
| 449 |
+
# هذا مؤشر مبسط - في التطبيق الحقيقي نحتاج بيانات تاريخية
|
| 450 |
+
if abs(fear_greed - 50) > 20:
|
| 451 |
+
volatility_score += 1
|
| 452 |
+
|
| 453 |
+
# تحليل نشاط الحيتان
|
| 454 |
+
if whale_sentiment in ['BULLISH', 'BEARISH']:
|
| 455 |
+
volatility_score += 1
|
| 456 |
+
elif whale_sentiment == 'SLIGHTLY_BULLISH':
|
| 457 |
+
volatility_score += 0.5
|
| 458 |
+
|
| 459 |
+
if volatility_score >= 1.5:
|
| 460 |
+
return "high"
|
| 461 |
+
elif volatility_score >= 0.5:
|
| 462 |
+
return "medium"
|
| 463 |
+
else:
|
| 464 |
+
return "low"
|
| 465 |
+
|
| 466 |
+
except Exception as e:
|
| 467 |
+
print(f"⚠️ خطأ في حساب التقلبية: {e}")
|
| 468 |
+
return "medium"
|
| 469 |
+
|
| 470 |
+
async def calculate_performance_metrics(self):
|
| 471 |
+
"""حساب مقاييس الأداء"""
|
| 472 |
+
if not self.performance_history:
|
| 473 |
+
return {"status": "لا توجد بيانات أداء بعد"}
|
| 474 |
+
|
| 475 |
+
recent_trades = self.performance_history[-50:] # آخر 50 صفقة فقط
|
| 476 |
+
|
| 477 |
+
total_trades = len(recent_trades)
|
| 478 |
+
successful_trades = sum(1 for trade in recent_trades
|
| 479 |
+
if trade['outcome'] in ["SUCCESS", "CLOSED_BY_REANALYSIS", "CLOSED_BY_MONITOR"] and trade.get('pnl_percent', 0) > 0)
|
| 480 |
+
success_rate = successful_trades / total_trades if total_trades > 0 else 0
|
| 481 |
+
|
| 482 |
+
total_pnl = sum(trade.get('pnl_percent', 0) for trade in recent_trades)
|
| 483 |
+
avg_pnl = total_pnl / total_trades if total_trades > 0 else 0
|
| 484 |
+
|
| 485 |
+
strategy_performance = {}
|
| 486 |
+
for strategy, data in self.strategy_effectiveness.items():
|
| 487 |
+
if data["total_trades"] > 0:
|
| 488 |
+
strategy_success_rate = data["successful_trades"] / data["total_trades"]
|
| 489 |
+
strategy_avg_pnl = data["total_pnl_percent"] / data["total_trades"]
|
| 490 |
+
strategy_performance[strategy] = {
|
| 491 |
+
"success_rate": strategy_success_rate,
|
| 492 |
+
"avg_pnl_percent": strategy_avg_pnl,
|
| 493 |
+
"total_trades": data["total_trades"],
|
| 494 |
+
"successful_trades": data["successful_trades"]
|
| 495 |
+
}
|
| 496 |
+
|
| 497 |
+
market_performance = {}
|
| 498 |
+
for condition, data in self.market_patterns.items():
|
| 499 |
+
if data["total_trades"] > 0:
|
| 500 |
+
market_success_rate = data["successful_trades"] / data["total_trades"]
|
| 501 |
+
market_avg_pnl = data["total_pnl_percent"] / data["total_trades"]
|
| 502 |
+
market_performance[condition] = {
|
| 503 |
+
"success_rate": market_success_rate,
|
| 504 |
+
"avg_pnl_percent": market_avg_pnl,
|
| 505 |
+
"total_trades": data["total_trades"]
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
return {
|
| 509 |
+
"overall_success_rate": success_rate,
|
| 510 |
+
"overall_avg_pnl_percent": avg_pnl,
|
| 511 |
+
"total_analyzed_trades": len(self.performance_history),
|
| 512 |
+
"recent_trades_analyzed": total_trades,
|
| 513 |
+
"strategy_performance": strategy_performance,
|
| 514 |
+
"market_performance": market_performance,
|
| 515 |
+
"last_updated": datetime.now().isoformat()
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
async def get_optimized_strategy_weights(self, market_condition):
|
| 519 |
+
"""الحصول على أوزان استراتيجية محسنة - الإصدار المصحح"""
|
| 520 |
+
try:
|
| 521 |
+
if not self.initialized:
|
| 522 |
+
print("⚠️ نظام التعلم غير مهيء، استخدام الأوزان الافتراضية")
|
| 523 |
+
return await self.get_default_strategy_weights()
|
| 524 |
+
|
| 525 |
+
# ✅ التحقق من وجود الأوزان وهيكلتها بشكل صحيح
|
| 526 |
+
if (not self.weights or
|
| 527 |
+
"strategy_weights" not in self.weights or
|
| 528 |
+
not self.weights["strategy_weights"]):
|
| 529 |
+
print("⚠️ الأوزان غير متوفرة أو فارغة، استخدام الأوزان الافتراضية")
|
| 530 |
+
return await self.get_default_strategy_weights()
|
| 531 |
+
|
| 532 |
+
base_weights = self.weights["strategy_weights"].copy()
|
| 533 |
+
|
| 534 |
+
# ✅ التحقق من أن الأوزان تحتوي على استراتيجيات فعلية
|
| 535 |
+
if not any(weight > 0 for weight in base_weights.values()):
|
| 536 |
+
print("⚠️ جميع الأوزان صفر، استخدام الأوزان الافتراضية")
|
| 537 |
+
return await self.get_default_strategy_weights()
|
| 538 |
+
|
| 539 |
+
print(f"✅ استخدام الأوزان المتعلمة: {base_weights}")
|
| 540 |
+
return base_weights
|
| 541 |
+
|
| 542 |
+
except Exception as e:
|
| 543 |
+
print(f"❌ فشل في حساب الأوزان المحسنة: {e}")
|
| 544 |
+
return await self.get_default_strategy_weights()
|
| 545 |
+
|
| 546 |
+
async def get_default_strategy_weights(self):
|
| 547 |
+
"""إرجاع الأوزان الافتراضية"""
|
| 548 |
+
return {
|
| 549 |
+
"trend_following": 0.18,
|
| 550 |
+
"mean_reversion": 0.15,
|
| 551 |
+
"breakout_momentum": 0.22,
|
| 552 |
+
"volume_spike": 0.12,
|
| 553 |
+
"whale_tracking": 0.15,
|
| 554 |
+
"pattern_recognition": 0.10,
|
| 555 |
+
"hybrid_ai": 0.08
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
async def get_risk_parameters(self, symbol_volatility):
|
| 559 |
+
"""الحصول على معايير المخاطرة المحسنة"""
|
| 560 |
+
if not self.weights or "risk_parameters" not in self.weights:
|
| 561 |
+
await self.initialize_default_weights()
|
| 562 |
+
|
| 563 |
+
risk_params = self.weights.get("risk_parameters", {}).copy()
|
| 564 |
+
|
| 565 |
+
# تعديل معايير المخاطرة بناءً على تقلبية الرمز
|
| 566 |
+
if symbol_volatility == "HIGH":
|
| 567 |
+
risk_params["stop_loss_base"] *= 1.5
|
| 568 |
+
risk_params["max_position_size"] *= 0.7
|
| 569 |
+
risk_params["risk_reward_ratio"] = 1.5 # تخفيض نسبة المكافأة/المخاطرة للتقليل العالي
|
| 570 |
+
elif symbol_volatility == "LOW":
|
| 571 |
+
risk_params["stop_loss_base"] *= 0.7
|
| 572 |
+
risk_params["max_position_size"] *= 1.2
|
| 573 |
+
risk_params["risk_reward_ratio"] = 2.5 # زيادة النسبة للتقليل المنخفض
|
| 574 |
+
|
| 575 |
+
return risk_params
|
| 576 |
+
|
| 577 |
+
async def suggest_improvements(self):
|
| 578 |
+
"""اقتراح تحسينات بناءً على تحليل الأداء"""
|
| 579 |
+
improvements = []
|
| 580 |
+
|
| 581 |
+
if not self.performance_history:
|
| 582 |
+
improvements.append("📊 ابدأ بجمع بيانات الأداء من الصفقات الأولى")
|
| 583 |
+
return improvements
|
| 584 |
+
|
| 585 |
+
# تحليل أداء الاستراتيجيات
|
| 586 |
+
for strategy, data in self.strategy_effectiveness.items():
|
| 587 |
+
if data["total_trades"] >= 3:
|
| 588 |
+
success_rate = data["successful_trades"] / data["total_trades"]
|
| 589 |
+
avg_pnl = data["total_pnl_percent"] / data["total_trades"]
|
| 590 |
+
|
| 591 |
+
if success_rate < 0.3 and avg_pnl < 0:
|
| 592 |
+
improvements.append(f"🚨 استراتيجية {strategy} ضعيفة الأداء ({success_rate:.1%} نجاح، {avg_pnl:+.1f}% متوسط) - يقترح تقليل استخدامها")
|
| 593 |
+
elif success_rate > 0.6 and avg_pnl > 2:
|
| 594 |
+
improvements.append(f"✅ استراتيجية {strategy} ممتازة الأداء ({success_rate:.1%} نجاح، {avg_pnl:+.1f}% متوسط) - يقترح زيادة استخدامها")
|
| 595 |
+
elif success_rate > 0.7:
|
| 596 |
+
improvements.append(f"🎯 استراتيجية {strategy} عالية النجاح ({success_rate:.1%}) - التركيز على جودة الصفقات")
|
| 597 |
+
|
| 598 |
+
# تحليل أداء ظروف السوق
|
| 599 |
+
for market_condition, data in self.market_patterns.items():
|
| 600 |
+
if data["total_trades"] >= 5:
|
| 601 |
+
success_rate = data["successful_trades"] / data["total_trades"]
|
| 602 |
+
avg_pnl = data["total_pnl_percent"] / data["total_trades"]
|
| 603 |
+
|
| 604 |
+
if success_rate < 0.4:
|
| 605 |
+
improvements.append(f"⚠️ الأداء ضعيف في سوق {market_condition} ({success_rate:.1%} نجاح) - يحتاج مراجعة الاستراتيجيات")
|
| 606 |
+
|
| 607 |
+
# العثور على أفضل استراتيجية لهذا السوق
|
| 608 |
+
best_strategy = None
|
| 609 |
+
best_performance = -100
|
| 610 |
+
for strategy, stats in data["best_performing_strategies"].items():
|
| 611 |
+
if stats["count"] >= 2:
|
| 612 |
+
strategy_avg_pnl = stats["total_pnl"] / stats["count"]
|
| 613 |
+
if strategy_avg_pnl > best_performance:
|
| 614 |
+
best_performance = strategy_avg_pnl
|
| 615 |
+
best_strategy = strategy
|
| 616 |
+
|
| 617 |
+
if best_strategy and best_performance > 1:
|
| 618 |
+
improvements.append(f"📈 أفضل استراتيجية في {market_condition}: {best_strategy} ({best_performance:+.1f}% متوسط ربح)")
|
| 619 |
+
|
| 620 |
+
if not improvements:
|
| 621 |
+
improvements.append("📊 لا توجد تحسينات مقترحة حالياً - استمر في جمع البيانات")
|
| 622 |
+
|
| 623 |
+
return improvements
|
| 624 |
+
|
| 625 |
+
async def force_strategy_learning(self):
|
| 626 |
+
"""إجبار النظام على التعلم من البيانات الحالية"""
|
| 627 |
+
print("🧠 إجبار تحديث الاستراتيجيات من البيانات الحالية...")
|
| 628 |
+
|
| 629 |
+
if not self.performance_history:
|
| 630 |
+
print("⚠️ لا توجد بيانات أداء للتعلم منها")
|
| 631 |
+
return
|
| 632 |
+
|
| 633 |
+
# تحديث فعالية الاستراتيجيات من البيانات التاريخية
|
| 634 |
+
for entry in self.performance_history:
|
| 635 |
+
await self.update_strategy_effectiveness(entry)
|
| 636 |
+
await self.update_market_patterns(entry)
|
| 637 |
+
|
| 638 |
+
# تحديث الأوزان فوراً
|
| 639 |
+
await self.adapt_weights_based_on_performance()
|
| 640 |
+
await self.save_weights_to_r2()
|
| 641 |
+
|
| 642 |
+
print("✅ تم إجبار تحديث الاستراتيجيات بنجاح")
|
| 643 |
+
|
| 644 |
+
print("✅ نظام التعلم الذاتي المحسن محمل - جاهز للتعلم والتكيف المستمر")
|
r2 (29).py
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# r2 (23).py - الإصلاحات
|
| 2 |
+
import os
|
| 3 |
+
import traceback
|
| 4 |
+
import json
|
| 5 |
+
from datetime import datetime, timedelta
|
| 6 |
+
import asyncio
|
| 7 |
+
import time
|
| 8 |
+
import boto3
|
| 9 |
+
from botocore.exceptions import NoCredentialsError, ClientError
|
| 10 |
+
|
| 11 |
+
# --- R2 Service Configuration ---
|
| 12 |
+
R2_ACCOUNT_ID = os.getenv("R2_ACCOUNT_ID")
|
| 13 |
+
R2_ACCESS_KEY_ID = os.getenv("R2_ACCESS_KEY_ID")
|
| 14 |
+
R2_SECRET_ACCESS_KEY = os.getenv("R2_SECRET_ACCESS_KEY")
|
| 15 |
+
BUCKET_NAME = "trading"
|
| 16 |
+
INITIAL_CAPITAL = 10.0
|
| 17 |
+
|
| 18 |
+
class R2Service:
|
| 19 |
+
def __init__(self):
|
| 20 |
+
try:
|
| 21 |
+
endpoint_url = f"https://{R2_ACCOUNT_ID}.r2.cloudflarestorage.com"
|
| 22 |
+
self.s3_client = boto3.client(
|
| 23 |
+
's3',
|
| 24 |
+
endpoint_url=endpoint_url,
|
| 25 |
+
aws_access_key_id=R2_ACCESS_KEY_ID,
|
| 26 |
+
aws_secret_access_key=R2_SECRET_ACCESS_KEY,
|
| 27 |
+
)
|
| 28 |
+
self.lock_acquired = False
|
| 29 |
+
self.BUCKET_NAME = BUCKET_NAME
|
| 30 |
+
|
| 31 |
+
self._open_trades_warning_printed = False
|
| 32 |
+
self._portfolio_warning_printed = False
|
| 33 |
+
self._contracts_warning_printed = False
|
| 34 |
+
|
| 35 |
+
except Exception as e:
|
| 36 |
+
raise RuntimeError(f"Failed to initialize S3 client: {e}")
|
| 37 |
+
|
| 38 |
+
def acquire_lock(self, max_retries=3):
|
| 39 |
+
"""Acquires a lock file in R2 with retry logic."""
|
| 40 |
+
lock_path = "lock.txt"
|
| 41 |
+
|
| 42 |
+
for attempt in range(max_retries):
|
| 43 |
+
try:
|
| 44 |
+
try:
|
| 45 |
+
self.s3_client.head_object(Bucket=BUCKET_NAME, Key=lock_path)
|
| 46 |
+
print(f"🔒 Lock file exists. Attempt {attempt + 1}/{max_retries}. Waiting...")
|
| 47 |
+
time.sleep(1)
|
| 48 |
+
except ClientError as e:
|
| 49 |
+
if e.response['Error']['Code'] == '404':
|
| 50 |
+
self.s3_client.put_object(Bucket=BUCKET_NAME, Key=lock_path, Body=b'')
|
| 51 |
+
self.lock_acquired = True
|
| 52 |
+
print("✅ Lock acquired.")
|
| 53 |
+
return True
|
| 54 |
+
else:
|
| 55 |
+
raise
|
| 56 |
+
except Exception as e:
|
| 57 |
+
print(f"❌ Failed to acquire lock: {e}")
|
| 58 |
+
time.sleep(1)
|
| 59 |
+
|
| 60 |
+
print(f"❌ Failed to acquire lock after {max_retries} attempts.")
|
| 61 |
+
return False
|
| 62 |
+
|
| 63 |
+
def release_lock(self):
|
| 64 |
+
"""Releases the lock file from R2."""
|
| 65 |
+
lock_path = "lock.txt"
|
| 66 |
+
if self.lock_acquired:
|
| 67 |
+
try:
|
| 68 |
+
self.s3_client.delete_object(Bucket=BUCKET_NAME, Key=lock_path)
|
| 69 |
+
print("✅ Lock released.")
|
| 70 |
+
self.lock_acquired = False
|
| 71 |
+
except Exception as e:
|
| 72 |
+
print(f"❌ Failed to release lock: {e}")
|
| 73 |
+
|
| 74 |
+
async def close_trade_async(self, trade_to_close, close_price):
|
| 75 |
+
"""Closes a trade, archives it, updates summary, and updates portfolio capital."""
|
| 76 |
+
try:
|
| 77 |
+
trade_to_close['status'] = 'CLOSED'
|
| 78 |
+
trade_to_close['close_price'] = close_price
|
| 79 |
+
trade_to_close['close_timestamp'] = datetime.now().isoformat()
|
| 80 |
+
trade_to_close['is_monitored'] = False
|
| 81 |
+
|
| 82 |
+
entry_price = trade_to_close['entry_price']
|
| 83 |
+
position_size = trade_to_close['position_size_usd']
|
| 84 |
+
trade_type = trade_to_close.get('trade_type', 'LONG')
|
| 85 |
+
strategy = trade_to_close.get('strategy', 'unknown')
|
| 86 |
+
|
| 87 |
+
# ✅ الإصلاح: حساب PnL بشكل صحيح
|
| 88 |
+
pnl = 0.0
|
| 89 |
+
pnl_percent = 0.0
|
| 90 |
+
|
| 91 |
+
if entry_price and entry_price > 0 and close_price and close_price > 0:
|
| 92 |
+
try:
|
| 93 |
+
if trade_type == 'LONG':
|
| 94 |
+
pnl_percent = ((close_price - entry_price) / entry_price) * 100
|
| 95 |
+
pnl = position_size * (pnl_percent / 100)
|
| 96 |
+
elif trade_type == 'SHORT':
|
| 97 |
+
pnl_percent = ((entry_price - close_price) / entry_price) * 100
|
| 98 |
+
pnl = position_size * (pnl_percent / 100)
|
| 99 |
+
|
| 100 |
+
print(f"💰 PnL Calculation: Entry=${entry_price:.6f}, Close=${close_price:.6f}, "
|
| 101 |
+
f"Position=${position_size:.2f}, Type={trade_type}, "
|
| 102 |
+
f"PnL=${pnl:.4f} ({pnl_percent:+.4f}%)")
|
| 103 |
+
|
| 104 |
+
except (TypeError, ZeroDivisionError) as calc_error:
|
| 105 |
+
print(f"⚠️ PnL calculation error: {calc_error}")
|
| 106 |
+
pnl = 0.0
|
| 107 |
+
pnl_percent = 0.0
|
| 108 |
+
else:
|
| 109 |
+
print(f"⚠️ Invalid prices for PnL calculation: Entry={entry_price}, Close={close_price}")
|
| 110 |
+
|
| 111 |
+
trade_to_close['pnl_usd'] = pnl
|
| 112 |
+
trade_to_close['pnl_percent'] = pnl_percent
|
| 113 |
+
|
| 114 |
+
await self._archive_closed_trade_async(trade_to_close)
|
| 115 |
+
await self._update_trade_summary_async(trade_to_close)
|
| 116 |
+
|
| 117 |
+
# ✅ الإصلاح: تحديث رأس المال بشكل صحيح
|
| 118 |
+
portfolio_state = await self.get_portfolio_state_async()
|
| 119 |
+
current_capital = portfolio_state.get("current_capital_usd", 0)
|
| 120 |
+
invested_capital = portfolio_state.get("invested_capital_usd", 0)
|
| 121 |
+
|
| 122 |
+
# حساب رأس المال الجديد
|
| 123 |
+
new_capital = current_capital + position_size + pnl
|
| 124 |
+
|
| 125 |
+
portfolio_state["current_capital_usd"] = new_capital
|
| 126 |
+
portfolio_state["invested_capital_usd"] = 0.0
|
| 127 |
+
|
| 128 |
+
if pnl > 0:
|
| 129 |
+
portfolio_state["winning_trades"] = portfolio_state.get("winning_trades", 0) + 1
|
| 130 |
+
portfolio_state["total_profit_usd"] = portfolio_state.get("total_profit_usd", 0.0) + pnl
|
| 131 |
+
elif pnl < 0:
|
| 132 |
+
portfolio_state["total_loss_usd"] = portfolio_state.get("total_loss_usd", 0.0) + abs(pnl)
|
| 133 |
+
|
| 134 |
+
await self.save_portfolio_state_async(portfolio_state)
|
| 135 |
+
|
| 136 |
+
print(f"📈 Trade PnL: ${pnl:.4f} ({pnl_percent:+.4f}%). "
|
| 137 |
+
f"New available capital: ${new_capital:.4f}. Strategy: {strategy}")
|
| 138 |
+
|
| 139 |
+
open_trades = await self.get_open_trades_async()
|
| 140 |
+
trades_to_keep = [t for t in open_trades if t.get('id') != trade_to_close.get('id')]
|
| 141 |
+
await self.save_open_trades_async(trades_to_keep)
|
| 142 |
+
|
| 143 |
+
print(f"✅ Trade for {trade_to_close.get('symbol')} closed and archived successfully. Strategy: {strategy}")
|
| 144 |
+
|
| 145 |
+
await self.save_system_logs_async({
|
| 146 |
+
"trade_closed": True,
|
| 147 |
+
"symbol": trade_to_close.get('symbol'),
|
| 148 |
+
"entry_price": entry_price,
|
| 149 |
+
"close_price": close_price,
|
| 150 |
+
"pnl_usd": pnl,
|
| 151 |
+
"pnl_percent": pnl_percent,
|
| 152 |
+
"new_capital": new_capital,
|
| 153 |
+
"strategy": strategy,
|
| 154 |
+
"position_size": position_size,
|
| 155 |
+
"trade_type": trade_type
|
| 156 |
+
})
|
| 157 |
+
|
| 158 |
+
except Exception as e:
|
| 159 |
+
print(f"❌ Failed to close trade: {e}")
|
| 160 |
+
traceback.print_exc()
|
| 161 |
+
raise
|
| 162 |
+
|
| 163 |
+
# باقي الدوال تبقى كما هي بدون تغيير
|
| 164 |
+
async def save_candidates_data_async(self, candidates_data, reanalysis_data):
|
| 165 |
+
"""حفظ بيانات المرشحين العشرة وبيانات إعادة التحليل"""
|
| 166 |
+
try:
|
| 167 |
+
key = "candidates_data.json"
|
| 168 |
+
data = {
|
| 169 |
+
"timestamp": datetime.now().isoformat(),
|
| 170 |
+
"top_candidates": candidates_data,
|
| 171 |
+
"reanalysis_data": reanalysis_data
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
data_json = json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8')
|
| 175 |
+
self.s3_client.put_object(
|
| 176 |
+
Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
|
| 177 |
+
)
|
| 178 |
+
print(f"✅ Candidates data saved to R2: {len(candidates_data) if candidates_data else 0} candidates")
|
| 179 |
+
except Exception as e:
|
| 180 |
+
print(f"❌ Failed to save candidates data: {e}")
|
| 181 |
+
|
| 182 |
+
async def save_llm_responses_async(self, symbol, prompt, full_response, parsed_decision):
|
| 183 |
+
"""حفظ إجابات النموذج الكاملة"""
|
| 184 |
+
try:
|
| 185 |
+
key = "llm_responses.json"
|
| 186 |
+
try:
|
| 187 |
+
response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
|
| 188 |
+
existing_data = json.loads(response['Body'].read())
|
| 189 |
+
except ClientError as e:
|
| 190 |
+
if e.response['Error']['Code'] == 'NoSuchKey':
|
| 191 |
+
existing_data = {"responses": []}
|
| 192 |
+
else:
|
| 193 |
+
raise
|
| 194 |
+
|
| 195 |
+
new_response = {
|
| 196 |
+
"timestamp": datetime.now().isoformat(),
|
| 197 |
+
"symbol": symbol,
|
| 198 |
+
"prompt": prompt[:2000] + "..." if len(prompt) > 2000 else prompt,
|
| 199 |
+
"full_response": full_response,
|
| 200 |
+
"parsed_decision": parsed_decision
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
existing_data["responses"].append(new_response)
|
| 204 |
+
|
| 205 |
+
if len(existing_data["responses"]) > 1000:
|
| 206 |
+
existing_data["responses"] = existing_data["responses"][-1000:]
|
| 207 |
+
|
| 208 |
+
data_json = json.dumps(existing_data, indent=2, ensure_ascii=False).encode('utf-8')
|
| 209 |
+
self.s3_client.put_object(
|
| 210 |
+
Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
|
| 211 |
+
)
|
| 212 |
+
print(f"✅ LLM response saved for {symbol}")
|
| 213 |
+
except Exception as e:
|
| 214 |
+
print(f"❌ Failed to save LLM response: {e}")
|
| 215 |
+
|
| 216 |
+
async def save_system_logs_async(self, log_data):
|
| 217 |
+
"""حفظ سجلات النظام"""
|
| 218 |
+
try:
|
| 219 |
+
key = "system_logs.json"
|
| 220 |
+
try:
|
| 221 |
+
response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
|
| 222 |
+
existing_logs = json.loads(response['Body'].read())
|
| 223 |
+
except ClientError as e:
|
| 224 |
+
if e.response['Error']['Code'] == 'NoSuchKey':
|
| 225 |
+
existing_logs = {"logs": []}
|
| 226 |
+
else:
|
| 227 |
+
raise
|
| 228 |
+
|
| 229 |
+
log_entry = {
|
| 230 |
+
"timestamp": datetime.now().isoformat(),
|
| 231 |
+
**log_data
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
existing_logs["logs"].append(log_entry)
|
| 235 |
+
|
| 236 |
+
if len(existing_logs["logs"]) > 2000:
|
| 237 |
+
existing_logs["logs"] = existing_logs["logs"][-2000:]
|
| 238 |
+
|
| 239 |
+
data_json = json.dumps(existing_logs, indent=2, ensure_ascii=False).encode('utf-8')
|
| 240 |
+
self.s3_client.put_object(
|
| 241 |
+
Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
|
| 242 |
+
)
|
| 243 |
+
print(f"✅ System log saved: {log_data.get('cycle_started', log_data.get('cycle_completed', 'event'))}")
|
| 244 |
+
except Exception as e:
|
| 245 |
+
print(f"❌ Failed to save system logs: {e}")
|
| 246 |
+
|
| 247 |
+
async def save_learning_data_async(self, learning_data):
|
| 248 |
+
"""حفظ بيانات التعلم"""
|
| 249 |
+
try:
|
| 250 |
+
key = "learning_data.json"
|
| 251 |
+
data = {
|
| 252 |
+
"timestamp": datetime.now().isoformat(),
|
| 253 |
+
"learning_data": learning_data
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
data_json = json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8')
|
| 257 |
+
self.s3_client.put_object(
|
| 258 |
+
Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
|
| 259 |
+
)
|
| 260 |
+
print("✅ Learning data saved to R2")
|
| 261 |
+
except Exception as e:
|
| 262 |
+
print(f"❌ Failed to save learning data: {e}")
|
| 263 |
+
|
| 264 |
+
async def load_learning_data_async(self):
|
| 265 |
+
"""تحميل بيانات التعلم"""
|
| 266 |
+
try:
|
| 267 |
+
key = "learning_data.json"
|
| 268 |
+
response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
|
| 269 |
+
data = json.loads(response['Body'].read())
|
| 270 |
+
print("✅ Learning data loaded from R2")
|
| 271 |
+
return data
|
| 272 |
+
except ClientError as e:
|
| 273 |
+
if e.response['Error']['Code'] == 'NoSuchKey':
|
| 274 |
+
print("⚠️ No learning data found. Starting fresh.")
|
| 275 |
+
return {}
|
| 276 |
+
else:
|
| 277 |
+
raise
|
| 278 |
+
|
| 279 |
+
async def get_portfolio_state_async(self):
|
| 280 |
+
"""Fetches the current portfolio state from R2, or initializes it."""
|
| 281 |
+
key = "portfolio_state.json"
|
| 282 |
+
try:
|
| 283 |
+
response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
|
| 284 |
+
state = json.loads(response['Body'].read())
|
| 285 |
+
|
| 286 |
+
if hasattr(self, '_portfolio_warning_printed'):
|
| 287 |
+
delattr(self, '_portfolio_warning_printed')
|
| 288 |
+
|
| 289 |
+
print(f"💰 Portfolio state loaded: Current Capital ${state.get('current_capital_usd', 0):.2f}")
|
| 290 |
+
return state
|
| 291 |
+
except ClientError as e:
|
| 292 |
+
if e.response['Error']['Code'] == 'NoSuchKey':
|
| 293 |
+
if not hasattr(self, '_portfolio_warning_printed'):
|
| 294 |
+
print(f"⚠️ No portfolio state file found. Initializing with ${INITIAL_CAPITAL:.2f}")
|
| 295 |
+
self._portfolio_warning_printed = True
|
| 296 |
+
|
| 297 |
+
initial_state = {
|
| 298 |
+
"current_capital_usd": INITIAL_CAPITAL,
|
| 299 |
+
"invested_capital_usd": 0.0,
|
| 300 |
+
"initial_capital_usd": INITIAL_CAPITAL,
|
| 301 |
+
"total_trades": 0,
|
| 302 |
+
"winning_trades": 0,
|
| 303 |
+
"total_profit_usd": 0.0,
|
| 304 |
+
"total_loss_usd": 0.0
|
| 305 |
+
}
|
| 306 |
+
await self.save_portfolio_state_async(initial_state)
|
| 307 |
+
return initial_state
|
| 308 |
+
else:
|
| 309 |
+
raise
|
| 310 |
+
|
| 311 |
+
async def save_portfolio_state_async(self, state):
|
| 312 |
+
"""Saves the portfolio state to R2."""
|
| 313 |
+
key = "portfolio_state.json"
|
| 314 |
+
try:
|
| 315 |
+
data_json = json.dumps(state, indent=2).encode('utf-8')
|
| 316 |
+
self.s3_client.put_object(
|
| 317 |
+
Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
|
| 318 |
+
)
|
| 319 |
+
print(f"💾 Portfolio state saved: Current Capital ${state.get('current_capital_usd', 0):.2f}")
|
| 320 |
+
except Exception as e:
|
| 321 |
+
print(f"❌ Failed to save portfolio state: {e}")
|
| 322 |
+
raise
|
| 323 |
+
|
| 324 |
+
async def get_open_trades_async(self):
|
| 325 |
+
"""Fetches all open trades from R2 with reduced noise."""
|
| 326 |
+
try:
|
| 327 |
+
response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key="open_trades.json")
|
| 328 |
+
trades = json.loads(response['Body'].read())
|
| 329 |
+
|
| 330 |
+
if hasattr(self, '_open_trades_warning_printed'):
|
| 331 |
+
delattr(self, '_open_trades_warning_printed')
|
| 332 |
+
|
| 333 |
+
return trades
|
| 334 |
+
except ClientError as e:
|
| 335 |
+
if e.response['Error']['Code'] == 'NoSuchKey':
|
| 336 |
+
if not hasattr(self, '_open_trades_warning_printed'):
|
| 337 |
+
print("⚠️ No open trades file found. Starting with an empty list.")
|
| 338 |
+
print("💡 This is normal for first-time runs or when all trades are closed.")
|
| 339 |
+
self._open_trades_warning_printed = True
|
| 340 |
+
return []
|
| 341 |
+
else:
|
| 342 |
+
raise
|
| 343 |
+
|
| 344 |
+
async def save_open_trades_async(self, trades):
|
| 345 |
+
"""Saves the list of open trades to R2."""
|
| 346 |
+
try:
|
| 347 |
+
data_json = json.dumps(trades, indent=2).encode('utf-8')
|
| 348 |
+
self.s3_client.put_object(
|
| 349 |
+
Bucket=BUCKET_NAME, Key="open_trades.json", Body=data_json, ContentType="application/json"
|
| 350 |
+
)
|
| 351 |
+
print(f"✅ Open trades saved to R2. Total open trades: {len(trades)}")
|
| 352 |
+
except Exception as e:
|
| 353 |
+
print(f"❌ Failed to save open trades: {e}")
|
| 354 |
+
raise
|
| 355 |
+
|
| 356 |
+
async def save_new_trade_async(self, symbol, decision, current_price):
|
| 357 |
+
"""Creates a new trade using the full available capital and saves it."""
|
| 358 |
+
try:
|
| 359 |
+
portfolio_state = await self.get_portfolio_state_async()
|
| 360 |
+
available_capital = portfolio_state.get("current_capital_usd", 0)
|
| 361 |
+
|
| 362 |
+
if available_capital < 1:
|
| 363 |
+
print(f"❌ Insufficient capital (${available_capital:.2f}) to open a new trade.")
|
| 364 |
+
return
|
| 365 |
+
|
| 366 |
+
expected_target_minutes = decision.get('expected_target_minutes', 15)
|
| 367 |
+
expected_target_minutes = max(5, min(expected_target_minutes, 45))
|
| 368 |
+
expected_target_time = (datetime.now() + timedelta(minutes=expected_target_minutes)).isoformat()
|
| 369 |
+
|
| 370 |
+
# ✅ الإصلاح النهائي: التأكد من وجود استراتيجية صالحة
|
| 371 |
+
strategy = decision.get('strategy')
|
| 372 |
+
if not strategy or strategy == 'unknown':
|
| 373 |
+
strategy = 'GENERIC'
|
| 374 |
+
print(f"⚠️ Strategy was missing or unknown. Setting to GENERIC for {symbol}")
|
| 375 |
+
|
| 376 |
+
trades = await self.get_open_trades_async()
|
| 377 |
+
new_trade = {
|
| 378 |
+
"id": str(int(time.time())),
|
| 379 |
+
"symbol": symbol,
|
| 380 |
+
"entry_price": current_price,
|
| 381 |
+
"entry_timestamp": datetime.now().isoformat(),
|
| 382 |
+
"decision_data": decision,
|
| 383 |
+
"status": "OPEN",
|
| 384 |
+
"stop_loss": decision.get("stop_loss"),
|
| 385 |
+
"take_profit": decision.get("take_profit"),
|
| 386 |
+
"trade_type": decision.get("trade_type"),
|
| 387 |
+
"position_size_usd": available_capital,
|
| 388 |
+
"expected_target_minutes": expected_target_minutes,
|
| 389 |
+
"expected_target_time": expected_target_time,
|
| 390 |
+
"is_monitored": True,
|
| 391 |
+
"strategy": strategy # ✅ استخدام الاستراتيجية المؤكدة
|
| 392 |
+
}
|
| 393 |
+
trades.append(new_trade)
|
| 394 |
+
await self.save_open_trades_async(trades)
|
| 395 |
+
|
| 396 |
+
original_expected = decision.get('expected_target_minutes', 15)
|
| 397 |
+
if original_expected > 45:
|
| 398 |
+
print(f"⚠️ LLM wanted {original_expected} minutes, CAPPED to 45 minutes for strategy consistency")
|
| 399 |
+
|
| 400 |
+
print(f"✅ New trade for {symbol} saved with position size ${available_capital:.2f}. Strategy: {strategy}. Expected results in {expected_target_minutes} minutes.")
|
| 401 |
+
|
| 402 |
+
portfolio_state["invested_capital_usd"] = available_capital
|
| 403 |
+
portfolio_state["current_capital_usd"] = 0.0
|
| 404 |
+
portfolio_state["total_trades"] = portfolio_state.get("total_trades", 0) + 1
|
| 405 |
+
await self.save_portfolio_state_async(portfolio_state)
|
| 406 |
+
|
| 407 |
+
await self.save_system_logs_async({
|
| 408 |
+
"new_trade_opened": True,
|
| 409 |
+
"symbol": symbol,
|
| 410 |
+
"position_size": available_capital,
|
| 411 |
+
"expected_minutes": expected_target_minutes,
|
| 412 |
+
"trade_type": decision.get("trade_type", "LONG"),
|
| 413 |
+
"strategy": strategy # ✅ استخدام الاستراتيجية المؤكدة
|
| 414 |
+
})
|
| 415 |
+
|
| 416 |
+
except Exception as e:
|
| 417 |
+
print(f"❌ Failed to save new trade: {e}")
|
| 418 |
+
raise
|
| 419 |
+
|
| 420 |
+
async def update_trade_async(self, trade_to_update, re_analysis_decision):
|
| 421 |
+
"""Updates an existing trade with new parameters from re-analysis."""
|
| 422 |
+
try:
|
| 423 |
+
if re_analysis_decision.get('new_stop_loss'):
|
| 424 |
+
trade_to_update['stop_loss'] = re_analysis_decision['new_stop_loss']
|
| 425 |
+
if re_analysis_decision.get('new_take_profit'):
|
| 426 |
+
trade_to_update['take_profit'] = re_analysis_decision['new_take_profit']
|
| 427 |
+
|
| 428 |
+
new_expected_minutes = re_analysis_decision.get('new_expected_minutes')
|
| 429 |
+
if new_expected_minutes:
|
| 430 |
+
new_expected_minutes = max(5, min(new_expected_minutes, 45))
|
| 431 |
+
trade_to_update['expected_target_minutes'] = new_expected_minutes
|
| 432 |
+
trade_to_update['expected_target_time'] = (datetime.now() + timedelta(minutes=new_expected_minutes)).isoformat()
|
| 433 |
+
print(f"⏰ Trade time expectation updated to {new_expected_minutes} minutes.")
|
| 434 |
+
|
| 435 |
+
# ✅ الإصلاح: الحفاظ على الاستراتيجية الأصلية
|
| 436 |
+
original_strategy = trade_to_update.get('strategy')
|
| 437 |
+
if not original_strategy or original_strategy == 'unknown':
|
| 438 |
+
original_strategy = re_analysis_decision.get('strategy', 'GENERIC')
|
| 439 |
+
|
| 440 |
+
trade_to_update['strategy'] = original_strategy
|
| 441 |
+
trade_to_update['decision_data'] = re_analysis_decision
|
| 442 |
+
trade_to_update['is_monitored'] = True
|
| 443 |
+
|
| 444 |
+
open_trades = await self.get_open_trades_async()
|
| 445 |
+
for i, trade in enumerate(open_trades):
|
| 446 |
+
if trade.get('id') == trade_to_update.get('id'):
|
| 447 |
+
open_trades[i] = trade_to_update
|
| 448 |
+
break
|
| 449 |
+
|
| 450 |
+
await self.save_open_trades_async(open_trades)
|
| 451 |
+
print(f"✅ Trade for {trade_to_update.get('symbol')} updated successfully. Strategy: {original_strategy}")
|
| 452 |
+
|
| 453 |
+
await self.save_system_logs_async({
|
| 454 |
+
"trade_updated": True,
|
| 455 |
+
"symbol": trade_to_update.get('symbol'),
|
| 456 |
+
"new_expected_minutes": new_expected_minutes,
|
| 457 |
+
"action": "UPDATE_TRADE",
|
| 458 |
+
"strategy": original_strategy
|
| 459 |
+
})
|
| 460 |
+
|
| 461 |
+
except Exception as e:
|
| 462 |
+
print(f"❌ Failed to update trade: {e}")
|
| 463 |
+
raise
|
| 464 |
+
|
| 465 |
+
async def immediate_close_trade_async(self, symbol, close_price, reason="Real-time monitoring"):
|
| 466 |
+
"""Closes a trade immediately without full re-analysis."""
|
| 467 |
+
try:
|
| 468 |
+
open_trades = await self.get_open_trades_async()
|
| 469 |
+
trade_to_close = None
|
| 470 |
+
|
| 471 |
+
for trade in open_trades:
|
| 472 |
+
if trade['symbol'] == symbol and trade['status'] == 'OPEN':
|
| 473 |
+
trade_to_close = trade
|
| 474 |
+
break
|
| 475 |
+
|
| 476 |
+
if not trade_to_close:
|
| 477 |
+
print(f"❌ No open trade found for {symbol}")
|
| 478 |
+
return False
|
| 479 |
+
|
| 480 |
+
await self.close_trade_async(trade_to_close, close_price)
|
| 481 |
+
print(f"🚨 IMMEDIATE CLOSE: {symbol} at {close_price} - {reason}")
|
| 482 |
+
|
| 483 |
+
await self.save_system_logs_async({
|
| 484 |
+
"immediate_close": True,
|
| 485 |
+
"symbol": symbol,
|
| 486 |
+
"close_price": close_price,
|
| 487 |
+
"reason": reason,
|
| 488 |
+
"strategy": trade_to_close.get('strategy', 'unknown')
|
| 489 |
+
})
|
| 490 |
+
|
| 491 |
+
return True
|
| 492 |
+
|
| 493 |
+
except Exception as e:
|
| 494 |
+
print(f"❌ Failed to immediately close trade {symbol}: {e}")
|
| 495 |
+
return False
|
| 496 |
+
|
| 497 |
+
async def _archive_closed_trade_async(self, closed_trade):
|
| 498 |
+
"""Appends a closed trade to the history file."""
|
| 499 |
+
key = "closed_trades_history.json"
|
| 500 |
+
try:
|
| 501 |
+
response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
|
| 502 |
+
history = json.loads(response['Body'].read())
|
| 503 |
+
except ClientError as e:
|
| 504 |
+
if e.response['Error']['Code'] == 'NoSuchKey':
|
| 505 |
+
history = []
|
| 506 |
+
else:
|
| 507 |
+
raise
|
| 508 |
+
|
| 509 |
+
history.append(closed_trade)
|
| 510 |
+
|
| 511 |
+
data_json = json.dumps(history, indent=2).encode('utf-8')
|
| 512 |
+
self.s3_client.put_object(
|
| 513 |
+
Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
|
| 514 |
+
)
|
| 515 |
+
print(f"📚 Trade archived. Total archived trades: {len(history)}")
|
| 516 |
+
|
| 517 |
+
async def _update_trade_summary_async(self, closed_trade):
|
| 518 |
+
"""Updates the trade summary statistics file."""
|
| 519 |
+
key = "trade_summary.json"
|
| 520 |
+
try:
|
| 521 |
+
try:
|
| 522 |
+
response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
|
| 523 |
+
summary = json.loads(response['Body'].read())
|
| 524 |
+
except ClientError as e:
|
| 525 |
+
if e.response['Error']['Code'] == 'NoSuchKey':
|
| 526 |
+
summary = {
|
| 527 |
+
"total_trades": 0, "winning_trades": 0, "losing_trades": 0,
|
| 528 |
+
"total_profit_usd": 0.0, "total_loss_usd": 0.0, "win_percentage": 0.0,
|
| 529 |
+
"avg_profit_per_trade": 0.0, "avg_loss_per_trade": 0.0,
|
| 530 |
+
"largest_win": 0.0, "largest_loss": 0.0
|
| 531 |
+
}
|
| 532 |
+
else:
|
| 533 |
+
raise
|
| 534 |
+
|
| 535 |
+
pnl = closed_trade.get('pnl_usd', 0.0)
|
| 536 |
+
|
| 537 |
+
summary['total_trades'] += 1
|
| 538 |
+
if pnl >= 0:
|
| 539 |
+
summary['winning_trades'] += 1
|
| 540 |
+
summary['total_profit_usd'] += pnl
|
| 541 |
+
if pnl > summary.get('largest_win', 0):
|
| 542 |
+
summary['largest_win'] = pnl
|
| 543 |
+
else:
|
| 544 |
+
summary['losing_trades'] += 1
|
| 545 |
+
summary['total_loss_usd'] += abs(pnl)
|
| 546 |
+
if abs(pnl) > summary.get('largest_loss', 0):
|
| 547 |
+
summary['largest_loss'] = abs(pnl)
|
| 548 |
+
|
| 549 |
+
if summary['total_trades'] > 0:
|
| 550 |
+
summary['win_percentage'] = (summary['winning_trades'] / summary['total_trades']) * 100
|
| 551 |
+
|
| 552 |
+
if summary['winning_trades'] > 0:
|
| 553 |
+
summary['avg_profit_per_trade'] = summary['total_profit_usd'] / summary['winning_trades']
|
| 554 |
+
if summary['losing_trades'] > 0:
|
| 555 |
+
summary['avg_loss_per_trade'] = summary['total_loss_usd'] / summary['losing_trades']
|
| 556 |
+
|
| 557 |
+
data_json = json.dumps(summary, indent=2).encode('utf-8')
|
| 558 |
+
self.s3_client.put_object(
|
| 559 |
+
Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
|
| 560 |
+
)
|
| 561 |
+
print(f"📊 Trade summary updated. Win Rate: {summary['win_percentage']:.2f}%")
|
| 562 |
+
|
| 563 |
+
except Exception as e:
|
| 564 |
+
print(f"❌ Failed to update trade summary: {e}")
|
| 565 |
+
raise
|
| 566 |
+
|
| 567 |
+
async def load_contracts_db_async(self):
|
| 568 |
+
"""Loads the contracts database from R2 with reduced noise."""
|
| 569 |
+
key = "contracts.json"
|
| 570 |
+
try:
|
| 571 |
+
response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
|
| 572 |
+
contracts_db = json.loads(response['Body'].read())
|
| 573 |
+
|
| 574 |
+
if hasattr(self, '_contracts_warning_printed'):
|
| 575 |
+
delattr(self, '_contracts_warning_printed')
|
| 576 |
+
|
| 577 |
+
print(f"💾 Contracts database loaded from R2. Total entries: {len(contracts_db)}")
|
| 578 |
+
return contracts_db
|
| 579 |
+
except ClientError as e:
|
| 580 |
+
if e.response['Error']['Code'] == 'NoSuchKey':
|
| 581 |
+
if not hasattr(self, '_contracts_warning_printed'):
|
| 582 |
+
print("⚠️ No existing contracts database found. Initializing new one.")
|
| 583 |
+
self._contracts_warning_printed = True
|
| 584 |
+
return {}
|
| 585 |
+
else:
|
| 586 |
+
raise
|
| 587 |
+
|
| 588 |
+
async def save_contracts_db_async(self, data):
|
| 589 |
+
"""Saves the contracts database to R2."""
|
| 590 |
+
key = "contracts.json"
|
| 591 |
+
try:
|
| 592 |
+
data_json = json.dumps(data, indent=2).encode('utf-8')
|
| 593 |
+
self.s3_client.put_object(
|
| 594 |
+
Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
|
| 595 |
+
)
|
| 596 |
+
print(f"✅ Contracts database saved to R2 successfully. Total entries: {len(data)}")
|
| 597 |
+
except Exception as e:
|
| 598 |
+
print(f"❌ Failed to save contracts database to R2: {e}")
|
| 599 |
+
raise
|
| 600 |
+
|
| 601 |
+
async def get_trade_by_symbol_async(self, symbol):
|
| 602 |
+
"""Fetches a specific trade by symbol."""
|
| 603 |
+
try:
|
| 604 |
+
open_trades = await self.get_open_trades_async()
|
| 605 |
+
for trade in open_trades:
|
| 606 |
+
if trade['symbol'] == symbol and trade['status'] == 'OPEN':
|
| 607 |
+
return trade
|
| 608 |
+
return None
|
| 609 |
+
except Exception as e:
|
| 610 |
+
print(f"❌ Failed to get trade by symbol {symbol}: {e}")
|
| 611 |
+
return None
|
| 612 |
+
|
| 613 |
+
async def update_trade_monitoring_status_async(self, symbol, is_monitored):
|
| 614 |
+
"""Updates the monitoring status of a trade."""
|
| 615 |
+
try:
|
| 616 |
+
open_trades = await self.get_open_trades_async()
|
| 617 |
+
updated = False
|
| 618 |
+
|
| 619 |
+
for trade in open_trades:
|
| 620 |
+
if trade['symbol'] == symbol:
|
| 621 |
+
trade['is_monitored'] = is_monitored
|
| 622 |
+
updated = True
|
| 623 |
+
break
|
| 624 |
+
|
| 625 |
+
if updated:
|
| 626 |
+
await self.save_open_trades_async(open_trades)
|
| 627 |
+
status = "ENABLED" if is_monitored else "DISABLED"
|
| 628 |
+
print(f"✅ Real-time monitoring {status} for {symbol}")
|
| 629 |
+
else:
|
| 630 |
+
print(f"⚠️ Trade {symbol} not found for monitoring status update")
|
| 631 |
+
|
| 632 |
+
return updated
|
| 633 |
+
|
| 634 |
+
except Exception as e:
|
| 635 |
+
print(f"❌ Failed to update monitoring status for {symbol}: {e}")
|
| 636 |
+
return False
|
| 637 |
+
|
| 638 |
+
async def get_monitored_trades_async(self):
|
| 639 |
+
"""Fetches all trades that are currently being monitored."""
|
| 640 |
+
try:
|
| 641 |
+
open_trades = await self.get_open_trades_async()
|
| 642 |
+
monitored_trades = [trade for trade in open_trades if trade.get('is_monitored', False)]
|
| 643 |
+
return monitored_trades
|
| 644 |
+
except Exception as e:
|
| 645 |
+
print(f"❌ Failed to get monitored trades: {e}")
|
| 646 |
+
return []
|
| 647 |
+
|
| 648 |
+
print("✅ Enhanced R2 Service Loaded - Comprehensive Logging System with Learning Support")
|