Riy777 commited on
Commit
e71ba1c
ยท
verified ยท
1 Parent(s): 5efae6d

Update ml_engine/data_manager.py

Browse files
Files changed (1) hide show
  1. ml_engine/data_manager.py +200 -52
ml_engine/data_manager.py CHANGED
@@ -1,5 +1,5 @@
1
  # ml_engine/data_manager.py
2
- # (V15.2 - GEM-Architect: Pure Liquidity Edition)
3
 
4
  import asyncio
5
  import httpx
@@ -10,7 +10,7 @@ import pandas as pd
10
  import numpy as np
11
  from typing import List, Dict, Any
12
 
13
- # ุฅุนุฏุงุฏุงุช ุงู„ุชุณุฌูŠู„ - ุชู‚ู„ูŠู„ ุงู„ุถุฌูŠุฌ
14
  logging.getLogger("httpx").setLevel(logging.WARNING)
15
  logging.getLogger("httpcore").setLevel(logging.WARNING)
16
  logging.getLogger("ccxt").setLevel(logging.WARNING)
@@ -34,10 +34,10 @@ class DataManager:
34
  self.http_client = None
35
  self.market_cache = {}
36
 
37
- # ู‚ูˆุงุฆู… ุงู„ุงุณุชุจุนุงุฏ (ุงู„ุนู…ู„ุงุช ุงู„ู…ุณุชู‚ุฑุฉ ูˆุงู„ุฑู…ูˆุฒ ุงู„ุฑุงูุนุฉ ุงู„ุฎุทุฑุฉ)
38
  self.BLACKLIST_TOKENS = [
39
  'USDT', 'USDC', 'DAI', 'TUSD', 'BUSD', 'FDUSD', 'EUR', 'PAX',
40
- 'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L', '2S', '2L', '5S', '5L'
41
  ]
42
 
43
  async def initialize(self):
@@ -45,7 +45,7 @@ class DataManager:
45
  print(" > [DataManager] Starting initialization...")
46
  self.http_client = httpx.AsyncClient(timeout=30.0)
47
  await self._load_markets()
48
- print(f"โœ… [DataManager V15.2] Ready (Strategy: Top 100 Volume).")
49
 
50
  async def _load_markets(self):
51
  try:
@@ -74,80 +74,229 @@ class DataManager:
74
  return self.contracts_db
75
 
76
  # ==================================================================
77
- # ๐Ÿ›ก๏ธ Layer 1: Rapid Screening (Top 100 Volume Only)
78
  # ==================================================================
79
  async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
80
- """
81
- ุฌู„ุจ ุฃุนู„ู‰ 100 ุนู…ู„ุฉ ู…ู† ุญูŠุซ ุญุฌู… ุงู„ุชุฏุงูˆู„ (USDT Volume).
82
- ุงู„ู…ู†ุทู‚: ุงู„ุณูŠูˆู„ุฉ ู‡ูŠ ุงู„ู…ู„ูƒ. ุงู„ุนู…ู„ุงุช ุงู„ู†ุดุทุฉ ู‡ูŠ ุงู„ุฃูุถู„ ู„ู„ุชุฏุงูˆู„ ุงู„ุขู„ูŠ.
83
- """
84
- print(f"๐Ÿ” [Layer 1] Screening Top 100 by Volume...")
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  try:
87
- # 1. ุฌู„ุจ ูƒู„ ุงู„ุฃุณุนุงุฑ ูˆุงู„ุจูŠุงู†ุงุช ุงู„ู„ุญุธูŠุฉ
88
  tickers = await self.exchange.fetch_tickers()
89
  candidates = []
90
 
91
  for symbol, ticker in tickers.items():
92
- # ุชุตููŠุฉ ุงู„ุฃุฒูˆุงุฌ ุบูŠุฑ ุงู„ู…ุฑุบูˆุจุฉ
93
  if not symbol.endswith('/USDT'): continue
94
 
95
- # ุชุตููŠุฉ ุงู„ุนู…ู„ุงุช ุงู„ู…ุญุธูˆุฑุฉ (stablecoins, leveraged tokens)
96
  base_curr = symbol.split('/')[0]
97
  if any(bad in base_curr for bad in self.BLACKLIST_TOKENS): continue
98
 
99
- # ุงู„ุชุฃูƒุฏ ู…ู† ุตุญุฉ ุงู„ุจูŠุงู†ุงุช
100
- quote_vol = ticker.get('quoteVolume') # ุญุฌู… ุงู„ุชุฏุงูˆู„ ุจุงู„ุฏูˆู„ุงุฑ
101
- last_price = ticker.get('last')
102
 
103
- if quote_vol is None or last_price is None: continue
104
- if last_price <= 0: continue
105
-
106
- # ุงู„ุญุฏ ุงู„ุฃุฏู†ู‰ ู„ู„ู‚ุจูˆู„ (ู…ู„ูŠูˆู† ุฏูˆู„ุงุฑ ุญุฌู… ุชุฏุงูˆู„ ูŠูˆู…ูŠ) ู„ุชุฌู†ุจ ุงู„ุนู…ู„ุงุช ุงู„ู…ูŠุชุฉ ุชู…ุงู…ุงู‹
107
- if quote_vol < 1_000_000: continue
108
 
109
  candidates.append({
110
  'symbol': symbol,
111
  'quote_volume': quote_vol,
112
  'current_price': last_price,
113
- 'change_24h': ticker.get('percentage', 0.0)
114
  })
115
 
116
- # 2. ุงู„ุชุฑุชูŠุจ ุงู„ุชู†ุงุฒู„ูŠ ุญุณุจ ุญุฌู… ุงู„ุชุฏุงูˆู„
117
  candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
 
118
 
119
- # 3. ุงุฎุชูŠุงุฑ ุฃุนู„ู‰ 100 ุนู…ู„ุฉ ูู‚ุท
120
- top_100 = candidates[:100]
121
-
122
- # 4. ุชู†ุณูŠู‚ ุงู„ุจูŠุงู†ุงุช ู„ู„ู†ุธุงู… ุงู„ุชุงู„ูŠ (Layer 2)
123
- final_selection = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- if top_100:
126
- max_vol = top_100[0]['quote_volume']
127
 
128
- for item in top_100:
129
- # ุญุณุงุจ ุฏุฑุฌุฉ ุจุณูŠุทุฉ (Score) ุจู†ุงุกู‹ ุนู„ู‰ ุงู„ุญุฌู… ุงู„ู†ุณุจูŠ
130
- # ุฃุนู„ู‰ ุนู…ู„ุฉ ุชุฃุฎุฐ 1.0ุŒ ูˆุงู„ุจุงู‚ูŠ ู†ุณุจุฉ ู…ู†ู‡ุง
131
- vol_score = item['quote_volume'] / max_vol if max_vol > 0 else 0.0
132
-
133
- final_selection.append({
134
- 'symbol': item['symbol'],
135
- 'quote_volume': item['quote_volume'],
136
- 'current_price': item['current_price'],
137
- 'type': 'TOP_LIQUIDITY', # ูˆุณู… ู…ูˆุญุฏ ู„ู„ุฌู…ูŠุน
138
- 'l1_score': vol_score # ูŠุณุชุฎุฏู… ู„ู„ุชุฑุชูŠุจ ุงู„ุฃูˆู„ูŠ ููŠ app.py
139
- })
140
-
141
- print(f"โœ… [Layer 1] Selected {len(final_selection)} assets based on pure volume.")
142
- return final_selection
143
 
144
- except Exception as e:
145
- print(f"โŒ [Layer 1 Error] Screening failed: {e}")
146
- traceback.print_exc()
147
- return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
  # ==================================================================
150
- # ๐ŸŽฏ Public Helpers (Used by Processor & TradeManager)
151
  # ==================================================================
152
  async def get_latest_price_async(self, symbol: str) -> float:
153
  try:
@@ -157,7 +306,6 @@ class DataManager:
157
 
158
  async def get_latest_ohlcv(self, symbol: str, timeframe: str = '5m', limit: int = 100) -> List[List[float]]:
159
  try:
160
- # ุฏุงู„ุฉ ู…ุณุงุนุฏุฉ ู„ุฌู„ุจ ุงู„ุดู…ูˆุน ู„ู„ู…ุญุฑูƒุงุช ุงู„ุฃุฎุฑู‰ (Processor/Guardian)
161
  candles = await self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
162
  return candles or []
163
  except Exception: return []
 
1
  # ml_engine/data_manager.py
2
+ # (V15.1 - GEM-Architect: Tuned Logic Tree - Marksman Mode)
3
 
4
  import asyncio
5
  import httpx
 
10
  import numpy as np
11
  from typing import List, Dict, Any
12
 
13
+ # ุฅุนุฏุงุฏุงุช ุงู„ุชุณุฌูŠู„
14
  logging.getLogger("httpx").setLevel(logging.WARNING)
15
  logging.getLogger("httpcore").setLevel(logging.WARNING)
16
  logging.getLogger("ccxt").setLevel(logging.WARNING)
 
34
  self.http_client = None
35
  self.market_cache = {}
36
 
37
+ # ู‚ูˆุงุฆู… ุงู„ุงุณุชุจุนุงุฏ
38
  self.BLACKLIST_TOKENS = [
39
  'USDT', 'USDC', 'DAI', 'TUSD', 'BUSD', 'FDUSD', 'EUR', 'PAX',
40
+ 'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L'
41
  ]
42
 
43
  async def initialize(self):
 
45
  print(" > [DataManager] Starting initialization...")
46
  self.http_client = httpx.AsyncClient(timeout=30.0)
47
  await self._load_markets()
48
+ print(f"โœ… [DataManager V15.1] Ready (Logic Tree: Tuned/Flexible).")
49
 
50
  async def _load_markets(self):
51
  try:
 
74
  return self.contracts_db
75
 
76
  # ==================================================================
77
+ # ๐Ÿ›ก๏ธ Layer 1: The Tuned Decision Tree Screening
78
  # ==================================================================
79
  async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
80
+ print(f"๐Ÿ” [Layer 1] Initiating Tuned Logic Tree Screening...")
 
 
 
 
81
 
82
+ # 1. ุงู„ู…ุฑุญู„ุฉ 0: ูู„ุชุฑ ุงู„ูƒูˆู† (ู…ุฎูู)
83
+ initial_candidates = await self._stage0_universe_filter()
84
+
85
+ if not initial_candidates:
86
+ return []
87
+
88
+ # 2. ุฌู„ุจ ุงู„ุจูŠุงู†ุงุช ุงู„ูู†ูŠุฉ
89
+ top_liquid_candidates = initial_candidates[:300]
90
+ enriched_data = await self._fetch_technical_data_batch(top_liquid_candidates)
91
+
92
+ # 3. ุชุทุจูŠู‚ ุดุฌุฑุฉ ุงู„ู‚ุฑุงุฑ (Overbought -> Classify)
93
+ breakout_list = []
94
+ reversal_list = []
95
+
96
+ for item in enriched_data:
97
+ classification = self._apply_logic_tree(item)
98
+
99
+ if classification['type'] == 'BREAKOUT':
100
+ item['l1_sort_score'] = classification['score']
101
+ breakout_list.append(item)
102
+ elif classification['type'] == 'REVERSAL':
103
+ item['l1_sort_score'] = classification['score']
104
+ reversal_list.append(item)
105
+
106
+ print(f" -> [L1 Logic] Found: {len(breakout_list)} Breakouts, {len(reversal_list)} Reversals.")
107
+
108
+ # 4. ุงู„ุชุฑุชูŠุจ ูˆุงู„ุฏู…ุฌ ุงู„ู†ู‡ุงุฆูŠ
109
+ breakout_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
110
+ reversal_list.sort(key=lambda x: x['l1_sort_score'], reverse=False)
111
+
112
+ final_selection = breakout_list[:80] + reversal_list[:70]
113
+
114
+ cleaned_selection = []
115
+ for item in final_selection:
116
+ cleaned_selection.append({
117
+ 'symbol': item['symbol'],
118
+ 'quote_volume': item.get('quote_volume', 0),
119
+ 'current_price': item.get('current_price', 0),
120
+ 'type': item.get('type', 'UNKNOWN'), # ู†ู…ุฑุฑ ุงู„ู†ูˆุน ู„ู€ app.py ุฅุฐุง ุฑุบุจ ุจุงุณุชุฎุฏุงู…ู‡
121
+ 'l1_score': item.get('l1_sort_score', 0)
122
+ })
123
+
124
+ print(f"โœ… [Layer 1] Final Selection: {len(cleaned_selection)} candidates passed to models.")
125
+ return cleaned_selection
126
+
127
+ # ------------------------------------------------------------------
128
+ # Stage 0: Universe Filter (RELAXED)
129
+ # ------------------------------------------------------------------
130
+ async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
131
  try:
 
132
  tickers = await self.exchange.fetch_tickers()
133
  candidates = []
134
 
135
  for symbol, ticker in tickers.items():
 
136
  if not symbol.endswith('/USDT'): continue
137
 
 
138
  base_curr = symbol.split('/')[0]
139
  if any(bad in base_curr for bad in self.BLACKLIST_TOKENS): continue
140
 
141
+ # ๐Ÿ‘‡ [Tuning] ุฎูุถู†ุง ุงู„ุณูŠูˆู„ุฉ ุงู„ู…ุทู„ูˆุจุฉ ู„ู…ู„ูŠูˆู† ูˆุงุญุฏ ูู‚ุท
142
+ quote_vol = ticker.get('quoteVolume')
143
+ if not quote_vol or quote_vol < 1_000_000: continue
144
 
145
+ last_price = ticker.get('last')
146
+ if not last_price or last_price < 0.0005: continue
 
 
 
147
 
148
  candidates.append({
149
  'symbol': symbol,
150
  'quote_volume': quote_vol,
151
  'current_price': last_price,
152
+ 'change_24h': ticker.get('percentage', 0.0)
153
  })
154
 
 
155
  candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
156
+ return candidates
157
 
158
+ except Exception as e:
159
+ print(f"โŒ [L1 Error] Universe filter failed: {e}")
160
+ return []
161
+
162
+ # ------------------------------------------------------------------
163
+ # Data Fetching Helpers
164
+ # ------------------------------------------------------------------
165
+ async def _fetch_technical_data_batch(self, candidates: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
166
+ chunk_size = 10
167
+ results = []
168
+ for i in range(0, len(candidates), chunk_size):
169
+ chunk = candidates[i:i + chunk_size]
170
+ chunk_tasks = [self._fetch_single_tech_data(c) for c in chunk]
171
+ chunk_results = await asyncio.gather(*chunk_tasks)
172
+ results.extend([r for r in chunk_results if r is not None])
173
+ await asyncio.sleep(0.1) # ุชุณุฑูŠุน ู‚ู„ูŠู„
174
+ return results
175
+
176
+ async def _fetch_single_tech_data(self, candidate: Dict[str, Any]) -> Any:
177
+ symbol = candidate['symbol']
178
+ try:
179
+ ohlcv_1h = await self.exchange.fetch_ohlcv(symbol, '1h', limit=60)
180
+ ohlcv_15m = await self.exchange.fetch_ohlcv(symbol, '15m', limit=60)
181
 
182
+ if not ohlcv_1h or len(ohlcv_1h) < 55 or not ohlcv_15m or len(ohlcv_15m) < 55:
183
+ return None
184
 
185
+ candidate['ohlcv_1h'] = ohlcv_1h
186
+ candidate['ohlcv_15m'] = ohlcv_15m
187
+ return candidate
188
+ except Exception:
189
+ return None
 
 
 
 
 
 
 
 
 
 
190
 
191
+ # ------------------------------------------------------------------
192
+ # ๐Ÿง  The Logic Core: Math & Decision Tree (RELAXED)
193
+ # ------------------------------------------------------------------
194
+ def _apply_logic_tree(self, data: Dict[str, Any]) -> Dict[str, Any]:
195
+ try:
196
+ df_1h = self._calc_indicators(data['ohlcv_1h'])
197
+ df_15m = self._calc_indicators(data['ohlcv_15m'])
198
+ except:
199
+ return {'type': 'NONE'}
200
+
201
+ curr_1h = df_1h.iloc[-1]
202
+ curr_15m = df_15m.iloc[-1]
203
+
204
+ # --- Stage 2: Overbought Filter ---
205
+ try:
206
+ close_4h_ago = df_1h.iloc[-5]['close']
207
+ change_4h = ((curr_1h['close'] - close_4h_ago) / close_4h_ago) * 100
208
+ except: change_4h = 0.0
209
+
210
+ if change_4h > 15.0: return {'type': 'NONE'}
211
+ if data.get('change_24h', 0) > 25.0: return {'type': 'NONE'}
212
+ if curr_1h['rsi'] > 80: return {'type': 'NONE'} # ๐Ÿ‘‡ [Tuning] ุณู…ุญู†ุง ุจู€ RSI ุฃุนู„ู‰ ู‚ู„ูŠู„ุงู‹
213
+
214
+ deviation = (curr_1h['close'] - curr_1h['ema20']) / curr_1h['atr'] if curr_1h['atr'] > 0 else 0
215
+ if deviation > 2.5: return {'type': 'NONE'} # ๐Ÿ‘‡ [Tuning] ุณู…ุญู†ุง ุจุฅู†ุญุฑุงู ุฃูƒุจุฑ ู‚ู„ูŠู„ุงู‹
216
+
217
+ # --- Stage 3: Classification ---
218
+
219
+ # === A. Breakout Logic (RELAXED) ===
220
+ is_breakout = False
221
+ breakout_score = 0.0
222
+
223
+ # Trend check (EMA Cross OR Price above both EMAs)
224
+ bullish_structure = (curr_1h['ema20'] > curr_1h['ema50']) or (curr_1h['close'] > curr_1h['ema20'])
225
+
226
+ if bullish_structure:
227
+ # ๐Ÿ‘‡ [Tuning] RSI range expanded
228
+ if 40 <= curr_1h['rsi'] <= 70:
229
+ # 15m bullish
230
+ if curr_15m['close'] >= curr_15m['ema20']:
231
+ # Volatility check (Range)
232
+ avg_range = (df_15m['high'] - df_15m['low']).rolling(10).mean().iloc[-1]
233
+ # ๐Ÿ‘‡ [Tuning] Less strict squeeze check (1.5x avg range allowed)
234
+ if (curr_15m['high'] - curr_15m['low']) <= avg_range * 1.5:
235
+ vol_ma20 = df_15m['volume'].rolling(20).mean().iloc[-1]
236
+ # ๐Ÿ‘‡ [Tuning] Volume Spike lowered to 1.2x
237
+ if curr_15m['volume'] >= 1.2 * vol_ma20:
238
+ is_breakout = True
239
+ breakout_score = curr_15m['volume'] / vol_ma20 if vol_ma20 > 0 else 1.0
240
+
241
+ if is_breakout:
242
+ data['type'] = 'BREAKOUT'
243
+ return {'type': 'BREAKOUT', 'score': breakout_score}
244
+
245
+ # === B. Reversal Logic (RELAXED) ===
246
+ is_reversal = False
247
+ reversal_score = 100.0
248
+
249
+ # ๐Ÿ‘‡ [Tuning] RSI threshold increased to 40
250
+ if 10 <= curr_1h['rsi'] <= 40:
251
+ # ๐Ÿ‘‡ [Tuning] Drop requirement reduced to -3%
252
+ if change_4h <= -3.0:
253
+ # Rejection check (Any bullish closing in last 3 candles)
254
+ last_3 = df_15m.iloc[-3:]
255
+ found_rejection = False
256
+ for _, row in last_3.iterrows():
257
+ # ๐Ÿ‘‡ [Tuning] Simple logic: Green candle OR Close in upper half
258
+ rng = row['high'] - row['low']
259
+ if rng > 0:
260
+ is_green = row['close'] > row['open']
261
+ upper_half = row['close'] > (row['low'] + rng * 0.5)
262
+ if is_green or upper_half:
263
+ found_rejection = True
264
+ break
265
+
266
+ if found_rejection:
267
+ is_reversal = True
268
+ reversal_score = curr_1h['rsi']
269
+
270
+ if is_reversal:
271
+ data['type'] = 'REVERSAL'
272
+ return {'type': 'REVERSAL', 'score': reversal_score}
273
+
274
+ return {'type': 'NONE'}
275
+
276
+ def _calc_indicators(self, ohlcv_list):
277
+ df = pd.DataFrame(ohlcv_list, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
278
+
279
+ delta = df['close'].diff()
280
+ gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
281
+ loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
282
+ rs = gain / loss
283
+ df['rsi'] = 100 - (100 / (1 + rs))
284
+
285
+ df['ema20'] = df['close'].ewm(span=20, adjust=False).mean()
286
+ df['ema50'] = df['close'].ewm(span=50, adjust=False).mean()
287
+
288
+ high_low = df['high'] - df['low']
289
+ high_close = np.abs(df['high'] - df['close'].shift())
290
+ low_close = np.abs(df['low'] - df['close'].shift())
291
+ ranges = pd.concat([high_low, high_close, low_close], axis=1)
292
+ true_range = np.max(ranges, axis=1)
293
+ df['atr'] = true_range.rolling(14).mean()
294
+
295
+ df.fillna(0, inplace=True)
296
+ return df
297
 
298
  # ==================================================================
299
+ # ๐ŸŽฏ Public Helpers
300
  # ==================================================================
301
  async def get_latest_price_async(self, symbol: str) -> float:
302
  try:
 
306
 
307
  async def get_latest_ohlcv(self, symbol: str, timeframe: str = '5m', limit: int = 100) -> List[List[float]]:
308
  try:
 
309
  candles = await self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
310
  return candles or []
311
  except Exception: return []