daafa999 commited on
Commit
2f70065
·
verified ·
1 Parent(s): 1df2f12

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +452 -516
app.py CHANGED
@@ -18,21 +18,20 @@ from xgboost import XGBClassifier
18
 
19
  from ta.trend import ADXIndicator
20
 
21
- # ============================================================
22
 
 
23
  # CONFIG
24
-
25
  # ============================================================
26
 
27
  SYMBOLS = [
28
- "BTC-USD",
29
- "ETH-USD",
30
- "SOL-USD",
31
- "BNB-USD",
32
- "XRP-USD",
33
- "DOGE-USD",
34
- "AVAX-USD",
35
- "LINK-USD"
36
  ]
37
 
38
  WINDOW = 20
@@ -48,25 +47,22 @@ os.makedirs(LOG_DIR, exist_ok=True)
48
  BOT_TOKEN = "YOUR_BOT_TOKEN"
49
  CHAT_ID = "YOUR_CHAT_ID"
50
 
51
- # ============================================================
52
 
 
53
  # LOGGING
54
-
55
  # ============================================================
56
 
57
  logging.basicConfig(level=logging.INFO)
58
 
59
  logger = logging.getLogger("crypto_ai")
60
 
61
- # ============================================================
62
 
 
63
  # PATHS
64
-
65
  # ============================================================
66
 
67
  def model_path(symbol):
68
 
69
-
70
  return os.path.join(
71
  MODEL_DIR,
72
  f"{symbol}_model.pkl"
@@ -75,7 +71,6 @@ def model_path(symbol):
75
 
76
  def scaler_path(symbol):
77
 
78
-
79
  return os.path.join(
80
  MODEL_DIR,
81
  f"{symbol}_scaler.pkl"
@@ -84,7 +79,6 @@ def scaler_path(symbol):
84
 
85
  def data_path(symbol):
86
 
87
-
88
  return os.path.join(
89
  DATA_DIR,
90
  f"{symbol}.csv"
@@ -92,611 +86,566 @@ def data_path(symbol):
92
 
93
 
94
  # ============================================================
95
-
96
  # TELEGRAM
97
-
98
  # ============================================================
99
 
100
  def send_telegram(message):
101
 
 
102
 
103
- try:
104
-
105
- if "YOUR_BOT_TOKEN" in BOT_TOKEN:
106
- return
107
 
108
- url = (
109
- f"https://api.telegram.org/"
110
- f"bot{BOT_TOKEN}/sendMessage"
111
- )
112
 
113
- requests.post(
114
- url,
115
- data={
116
- "chat_id": CHAT_ID,
117
- "text": message
118
- },
119
- timeout=10
120
- )
121
 
122
- except Exception as e:
123
 
124
- logger.warning(e)
125
 
126
 
127
  # ============================================================
128
-
129
- # DOWNLOAD
130
-
131
  # ============================================================
132
 
133
  def clean_columns(df):
134
 
 
135
 
136
- if isinstance(df.columns, pd.MultiIndex):
 
 
137
 
138
- df.columns = (
139
- df.columns.get_level_values(0)
140
- )
 
141
 
142
- df.columns = [
143
- str(c).lower()
144
- for c in df.columns
145
- ]
146
 
147
- return df
148
 
 
 
 
149
 
150
  def safe_download(symbol):
151
 
 
152
 
153
- try:
154
-
155
- df = yf.download(
156
- symbol,
157
- period="90d",
158
- interval="1h",
159
- progress=False,
160
- auto_adjust=True,
161
- threads=False
162
- )
163
 
164
- if df is not None and not df.empty:
165
 
166
- df = clean_columns(df)
167
 
168
- df.to_csv(data_path(symbol))
169
 
170
- return df, True
171
 
172
- except Exception as e:
173
 
174
- logger.warning(e)
175
 
176
- return None, False
177
 
178
 
179
  # ============================================================
180
-
181
  # FEATURE ENGINEERING
182
-
183
  # ============================================================
184
 
185
  def fetch_and_prepare(symbol):
186
 
 
187
 
188
- df, is_real = safe_download(symbol)
189
 
190
- if df is None:
191
 
192
- return None, None, None, None
193
 
194
- df.index = pd.to_datetime(df.index)
 
 
 
 
 
 
195
 
196
- df = df.resample("4h").agg({
197
- "open": "first",
198
- "high": "max",
199
- "low": "min",
200
- "close": "last",
201
- "volume": "sum"
202
- }).dropna()
203
 
204
- # RSI
205
 
206
- delta = df["close"].diff()
 
 
 
207
 
208
- gain = delta.where(
209
- delta > 0,
210
- 0
211
- ).rolling(14).mean()
212
 
213
- loss = (-delta.where(
214
- delta < 0,
215
- 0
216
- )).rolling(14).mean()
217
 
218
- rs = gain / loss.replace(0, np.nan)
 
 
219
 
220
- df["rsi"] = (
221
- 100 - (100 / (1 + rs))
222
- ).fillna(50)
223
 
224
- # MACD
 
 
 
 
225
 
226
- ema12 = (
227
- df["close"]
228
- .ewm(span=12)
229
- .mean()
230
- )
231
 
232
- ema26 = (
233
- df["close"]
234
- .ewm(span=26)
235
- .mean()
236
- )
237
 
238
- df["macd"] = ema12 - ema26
 
 
 
 
239
 
240
- df["macd_signal"] = (
241
- df["macd"]
242
- .ewm(span=9)
243
- .mean()
244
- )
245
 
246
- # EMA
 
 
 
 
247
 
248
- df["ema9"] = (
249
- df["close"]
250
- .ewm(span=9)
251
- .mean()
252
- )
253
 
254
- df["ema21"] = (
255
- df["close"]
256
- .ewm(span=21)
257
- .mean()
258
- )
259
 
260
- df["ema50"] = (
261
- df["close"]
262
- .ewm(span=50)
263
- .mean()
264
- )
265
 
266
- df["ema200"] = (
267
- df["close"]
268
- .ewm(span=200)
269
- .mean()
270
- )
271
 
272
- # BOLLINGER
 
 
 
 
273
 
274
- sma20 = (
275
- df["close"]
276
- .rolling(20)
277
- .mean()
278
- )
279
 
280
- std20 = (
281
- df["close"]
282
- .rolling(20)
283
- .std()
284
- )
285
 
286
- df["bb_upper"] = sma20 + (2 * std20)
287
- df["bb_lower"] = sma20 - (2 * std20)
288
 
289
- # ATR
290
 
291
- df["atr"] = (
292
- (df["high"] - df["low"])
293
- .rolling(14)
294
- .mean()
295
- )
296
-
297
- # VOLUME
298
-
299
- df["vol_sma"] = (
300
- df["volume"]
301
- .rolling(20)
302
- .mean()
303
- )
304
-
305
- df["volume_spike"] = (
306
- df["volume"]
307
- >
308
- df["vol_sma"] * 2
309
- )
310
-
311
- # ADX
312
-
313
- adx = ADXIndicator(
314
- high=df["high"],
315
- low=df["low"],
316
- close=df["close"]
317
- )
318
-
319
- df["adx"] = adx.adx()
320
-
321
- # VOLATILITY
322
-
323
- df["volatility"] = (
324
- df["close"]
325
- .pct_change()
326
- .rolling(20)
327
- .std()
328
- )
329
-
330
- # REGIME
331
-
332
- def detect_regime(row):
333
-
334
- if row["volatility"] < 0.01:
335
-
336
- return "SIDEWAYS"
337
-
338
- if row["ema50"] > row["ema200"]:
339
-
340
- return "BULL"
341
-
342
- return "BEAR"
343
-
344
- df["regime"] = df.apply(
345
- detect_regime,
346
- axis=1
347
- )
348
-
349
- # RETURNS
350
-
351
- df["return1"] = (
352
- df["close"]
353
- .pct_change()
354
- )
355
-
356
- df["return4"] = (
357
- df["close"]
358
- .pct_change(4)
359
- )
360
-
361
- # LABEL
362
-
363
- df["next_close"] = (
364
- df["close"]
365
- .shift(-1)
366
- )
367
-
368
- df["label"] = (
369
- df["next_close"]
370
- >
371
- df["close"]
372
- ).astype(int)
373
-
374
- df = df.replace(
375
- [np.inf, -np.inf],
376
- np.nan
377
- )
378
-
379
- df = df.dropna()
380
-
381
- feat_cols = [
382
- "open",
383
- "high",
384
- "low",
385
- "close",
386
- "volume",
387
- "rsi",
388
- "macd",
389
- "macd_signal",
390
- "ema9",
391
- "ema21",
392
- "ema50",
393
- "ema200",
394
- "bb_upper",
395
- "bb_lower",
396
- "atr",
397
- "adx",
398
- "return1",
399
- "return4"
400
- ]
401
 
402
- X_list = []
403
- y_list = []
404
 
405
- for i in range(WINDOW, len(df)):
406
 
407
- window = df.iloc[
408
- i-WINDOW:i
409
- ][feat_cols].values
410
 
411
- if np.isnan(window).any():
 
412
 
413
- continue
414
 
415
- X_list.append(
416
- window.flatten()
 
417
  )
418
 
419
- y_list.append(
420
- df.iloc[i]["label"]
 
 
 
421
  )
422
 
423
- X = np.array(X_list)
424
- y = np.array(y_list)
 
 
425
 
426
- return X, y, df, feat_cols
427
 
 
 
 
 
428
 
429
- # ============================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
 
431
- # TRAIN
432
 
 
 
433
  # ============================================================
434
 
435
  def run_train(symbol):
436
 
 
 
 
437
 
438
- X, y, df, feat_cols = (
439
- fetch_and_prepare(symbol)
440
- )
441
 
442
- if X is None:
443
 
444
- return
445
 
446
- scaler = MinMaxScaler()
 
 
 
 
 
 
 
 
447
 
448
- X_scaled = scaler.fit_transform(X)
 
 
449
 
450
- model = XGBClassifier(
451
- n_estimators=300,
452
- max_depth=6,
453
- learning_rate=0.03,
454
- subsample=0.8,
455
- colsample_bytree=0.8,
456
- eval_metric="logloss",
457
- random_state=42
458
- )
459
 
460
- tscv = TimeSeriesSplit(
461
- n_splits=5
462
- )
463
 
464
- for train_idx, test_idx in tscv.split(X_scaled):
465
 
466
- X_train = X_scaled[train_idx]
467
- y_train = y[train_idx]
 
 
468
 
469
- model.fit(
470
- X_train,
471
- y_train
472
  )
473
 
474
- joblib.dump(
475
- model,
476
- model_path(symbol)
477
- )
478
-
479
- joblib.dump(
480
- scaler,
481
- scaler_path(symbol)
482
- )
483
 
484
- logger.info(
485
- f"TRAINED: {symbol}"
486
- )
487
 
488
 
489
  # ============================================================
490
-
491
  # TRAIN ALL
492
-
493
  # ============================================================
494
 
495
  def train_all():
496
 
 
497
 
498
- for symbol in SYMBOLS:
499
 
500
- try:
501
 
502
- run_train(symbol)
503
 
504
- except Exception as e:
505
-
506
- logger.error(e)
507
 
508
 
509
  # ============================================================
510
-
511
  # TRADE LEVELS
512
-
513
  # ============================================================
514
 
515
  def trade_levels(df, signal):
516
 
 
517
 
518
- last = df.iloc[-1]
 
 
519
 
520
- close_price = float(
521
- last["close"]
522
- )
523
 
524
- atr = float(
525
- last["atr"]
526
- )
527
 
528
- if signal == "BUY":
 
 
529
 
530
- tp1 = close_price + atr
531
- tp2 = close_price + atr * 2
532
- tp3 = close_price + atr * 3
533
 
534
- sl = close_price - atr * 1.5
535
 
536
- elif signal == "SELL":
 
 
537
 
538
- tp1 = close_price - atr
539
- tp2 = close_price - atr * 2
540
- tp3 = close_price - atr * 3
541
 
542
- sl = close_price + atr * 1.5
543
 
544
- else:
 
 
545
 
546
- tp1 = close_price
547
- tp2 = close_price
548
- tp3 = close_price
549
- sl = close_price
550
 
551
- return {
552
- "entry": close_price,
553
- "tp1": tp1,
554
- "tp2": tp2,
555
- "tp3": tp3,
556
- "sl": sl
557
- }
558
 
559
 
560
  # ============================================================
561
-
562
  # PREDICT
563
-
564
  # ============================================================
565
 
566
  LAST_SIGNALS = {}
567
 
568
  def run_predict(symbol):
569
 
 
 
 
570
 
571
- if not os.path.exists(
572
- model_path(symbol)
573
- ):
574
-
575
- run_train(symbol)
576
-
577
- X, y, df, feat_cols = (
578
- fetch_and_prepare(symbol)
579
- )
580
-
581
- scaler = joblib.load(
582
- scaler_path(symbol)
583
- )
584
 
585
- model = joblib.load(
586
- model_path(symbol)
587
- )
588
 
589
- latest = (
590
- df.iloc[-WINDOW:][feat_cols]
591
- .values
592
- .flatten()
593
- .reshape(1, -1)
594
- )
595
 
596
- latest_scaled = scaler.transform(
597
- latest
598
- )
599
 
600
- pred = model.predict(
601
- latest_scaled
602
- )[0]
 
 
 
603
 
604
- proba = model.predict_proba(
605
- latest_scaled
606
- )[0]
607
 
608
- confidence = (
609
- float(np.max(proba))
610
- * 100
611
- )
612
 
613
- last = df.iloc[-1]
 
 
614
 
615
- bull_trend = (
616
- last["ema50"]
617
- >
618
- last["ema200"]
619
- )
620
 
621
- strong_volume = (
622
- last["volume"]
623
- >
624
- last["vol_sma"]
625
- )
626
 
627
- strong_adx = (
628
- last["adx"] > 25
629
- )
 
 
630
 
631
- regime = last["regime"]
 
 
 
 
632
 
633
- score = 0
 
 
634
 
635
- if bull_trend:
636
- score += 20
637
 
638
- if strong_volume:
639
- score += 20
640
 
641
- if strong_adx:
642
- score += 20
643
 
644
- if last["rsi"] > 55:
645
- score += 20
646
 
647
- if last["macd"] > last["macd_signal"]:
648
- score += 20
649
 
650
- signal = "NO TRADE"
 
651
 
652
- if regime == "SIDEWAYS":
 
653
 
654
  signal = "NO TRADE"
655
 
656
- else:
657
 
658
- if pred == 1 and score >= 60:
659
 
660
- signal = "BUY"
661
 
662
- elif pred == 0 and score >= 60:
663
 
664
- signal = "SELL"
665
 
666
- levels = trade_levels(
667
- df,
668
- signal
669
- )
670
 
671
- # LOG
672
 
673
- with open(
674
- os.path.join(
675
- LOG_DIR,
676
- "signals.log"
677
- ),
678
- "a"
679
- ) as f:
680
-
681
- f.write(
682
- f"{datetime.utcnow()} | "
683
- f"{symbol} | "
684
- f"{signal} | "
685
- f"{confidence:.2f}\n"
686
  )
687
 
688
- # TELEGRAM
689
 
690
- previous = LAST_SIGNALS.get(symbol)
691
-
692
- if previous != signal:
693
-
694
- LAST_SIGNALS[symbol] = signal
695
-
696
- send_telegram(
697
- f"""
698
 
 
699
 
 
 
700
  🚨 NEW SIGNAL
701
 
702
  PAIR: {symbol}
@@ -705,122 +654,109 @@ SIGNAL: {signal}
705
 
706
  CONFIDENCE: {confidence:.2f}%
707
  """
708
- )
709
-
710
 
711
- return {
712
- "Pair": symbol,
713
- "Signal": signal,
714
- "Confidence": round(confidence, 2),
715
- "Score": score,
716
- "Entry": round(levels["entry"], 2),
717
- "TP1": round(levels["tp1"], 2),
718
- "TP2": round(levels["tp2"], 2),
719
- "TP3": round(levels["tp3"], 2),
720
- "SL": round(levels["sl"], 2),
721
- "RSI": round(last["rsi"], 2),
722
- "ADX": round(last["adx"], 2),
723
- "Regime": regime
724
- }
725
 
726
 
727
  # ============================================================
728
-
729
  # REALTIME SCAN
730
-
731
  # ============================================================
732
 
733
  LAST_RETRAIN = 0
734
 
735
  def realtime_scan():
736
 
 
737
 
738
- global LAST_RETRAIN
739
-
740
- now = time.time()
741
 
742
- # AUTO RETRAIN 6 JAM
743
 
744
- if now - LAST_RETRAIN > 21600:
745
-
746
- logger.info(
747
- "AUTO RETRAIN..."
748
- )
749
 
750
- train_all()
751
 
752
- LAST_RETRAIN = now
753
 
754
- rows = []
755
 
756
- for symbol in SYMBOLS:
757
 
758
- try:
759
 
760
- result = run_predict(symbol)
761
 
762
- rows.append(result)
763
 
764
- except Exception as e:
765
 
766
- logger.error(
767
- traceback.format_exc()
768
- )
769
 
770
- df = pd.DataFrame(rows)
771
 
772
- if len(df) > 0:
773
 
774
- df = df.sort_values(
775
- by="Score",
776
- ascending=False
777
- )
778
 
779
- return df
780
 
781
 
782
  # ============================================================
783
-
784
  # UI
785
-
786
  # ============================================================
787
 
788
  with gr.Blocks(
789
- title="Crypto AI Scanner"
790
  ) as demo:
791
 
 
 
 
792
 
793
- gr.Markdown(
794
- "# 🤖 Crypto AI Scanner PRO"
795
- )
796
-
797
- table = gr.Dataframe(
798
- interactive=False
799
- )
800
 
801
- timer = gr.Timer(20)
802
 
803
- timer.tick(
804
- fn=realtime_scan,
805
- outputs=table
806
- )
807
 
808
 
809
  # ============================================================
810
-
811
  # START
812
-
813
  # ============================================================
814
 
815
- if name == "main":
816
-
817
 
818
- train_all()
819
-
820
- demo.queue()
821
 
822
- demo.launch(
823
- server_name="0.0.0.0",
824
- server_port=7860
825
- )
826
 
 
 
 
 
 
18
 
19
  from ta.trend import ADXIndicator
20
 
 
21
 
22
+ # ============================================================
23
  # CONFIG
 
24
  # ============================================================
25
 
26
  SYMBOLS = [
27
+ "BTC-USD",
28
+ "ETH-USD",
29
+ "SOL-USD",
30
+ "BNB-USD",
31
+ "XRP-USD",
32
+ "DOGE-USD",
33
+ "AVAX-USD",
34
+ "LINK-USD",
35
  ]
36
 
37
  WINDOW = 20
 
47
  BOT_TOKEN = "YOUR_BOT_TOKEN"
48
  CHAT_ID = "YOUR_CHAT_ID"
49
 
 
50
 
51
+ # ============================================================
52
  # LOGGING
 
53
  # ============================================================
54
 
55
  logging.basicConfig(level=logging.INFO)
56
 
57
  logger = logging.getLogger("crypto_ai")
58
 
 
59
 
60
+ # ============================================================
61
  # PATHS
 
62
  # ============================================================
63
 
64
  def model_path(symbol):
65
 
 
66
  return os.path.join(
67
  MODEL_DIR,
68
  f"{symbol}_model.pkl"
 
71
 
72
  def scaler_path(symbol):
73
 
 
74
  return os.path.join(
75
  MODEL_DIR,
76
  f"{symbol}_scaler.pkl"
 
79
 
80
  def data_path(symbol):
81
 
 
82
  return os.path.join(
83
  DATA_DIR,
84
  f"{symbol}.csv"
 
86
 
87
 
88
  # ============================================================
 
89
  # TELEGRAM
 
90
  # ============================================================
91
 
92
  def send_telegram(message):
93
 
94
+ try:
95
 
96
+ if "YOUR_BOT_TOKEN" in BOT_TOKEN:
97
+ return
 
 
98
 
99
+ url = (
100
+ f"https://api.telegram.org/"
101
+ f"bot{BOT_TOKEN}/sendMessage"
102
+ )
103
 
104
+ requests.post(
105
+ url,
106
+ data={
107
+ "chat_id": CHAT_ID,
108
+ "text": message
109
+ },
110
+ timeout=10
111
+ )
112
 
113
+ except Exception as e:
114
 
115
+ logger.warning(e)
116
 
117
 
118
  # ============================================================
119
+ # CLEAN COLUMNS
 
 
120
  # ============================================================
121
 
122
  def clean_columns(df):
123
 
124
+ if isinstance(df.columns, pd.MultiIndex):
125
 
126
+ df.columns = (
127
+ df.columns.get_level_values(0)
128
+ )
129
 
130
+ df.columns = [
131
+ str(c).lower()
132
+ for c in df.columns
133
+ ]
134
 
135
+ return df
 
 
 
136
 
 
137
 
138
+ # ============================================================
139
+ # DOWNLOAD DATA
140
+ # ============================================================
141
 
142
  def safe_download(symbol):
143
 
144
+ try:
145
 
146
+ df = yf.download(
147
+ symbol,
148
+ period="90d",
149
+ interval="1h",
150
+ progress=False,
151
+ auto_adjust=True,
152
+ threads=False
153
+ )
 
 
154
 
155
+ if df is not None and not df.empty:
156
 
157
+ df = clean_columns(df)
158
 
159
+ df.to_csv(data_path(symbol))
160
 
161
+ return df, True
162
 
163
+ except Exception as e:
164
 
165
+ logger.warning(e)
166
 
167
+ return None, False
168
 
169
 
170
  # ============================================================
 
171
  # FEATURE ENGINEERING
 
172
  # ============================================================
173
 
174
  def fetch_and_prepare(symbol):
175
 
176
+ df, is_real = safe_download(symbol)
177
 
178
+ if df is None:
179
 
180
+ return None, None, None, None
181
 
182
+ df.index = pd.to_datetime(df.index)
183
 
184
+ df = df.resample("4h").agg({
185
+ "open": "first",
186
+ "high": "max",
187
+ "low": "min",
188
+ "close": "last",
189
+ "volume": "sum"
190
+ }).dropna()
191
 
192
+ # RSI
 
 
 
 
 
 
193
 
194
+ delta = df["close"].diff()
195
 
196
+ gain = delta.where(
197
+ delta > 0,
198
+ 0
199
+ ).rolling(14).mean()
200
 
201
+ loss = (-delta.where(
202
+ delta < 0,
203
+ 0
204
+ )).rolling(14).mean()
205
 
206
+ rs = gain / loss.replace(0, np.nan)
 
 
 
207
 
208
+ df["rsi"] = (
209
+ 100 - (100 / (1 + rs))
210
+ ).fillna(50)
211
 
212
+ # MACD
 
 
213
 
214
+ ema12 = (
215
+ df["close"]
216
+ .ewm(span=12)
217
+ .mean()
218
+ )
219
 
220
+ ema26 = (
221
+ df["close"]
222
+ .ewm(span=26)
223
+ .mean()
224
+ )
225
 
226
+ df["macd"] = ema12 - ema26
 
 
 
 
227
 
228
+ df["macd_signal"] = (
229
+ df["macd"]
230
+ .ewm(span=9)
231
+ .mean()
232
+ )
233
 
234
+ # EMA
 
 
 
 
235
 
236
+ df["ema9"] = (
237
+ df["close"]
238
+ .ewm(span=9)
239
+ .mean()
240
+ )
241
 
242
+ df["ema21"] = (
243
+ df["close"]
244
+ .ewm(span=21)
245
+ .mean()
246
+ )
247
 
248
+ df["ema50"] = (
249
+ df["close"]
250
+ .ewm(span=50)
251
+ .mean()
252
+ )
253
 
254
+ df["ema200"] = (
255
+ df["close"]
256
+ .ewm(span=200)
257
+ .mean()
258
+ )
259
 
260
+ # BOLLINGER
 
 
 
 
261
 
262
+ sma20 = (
263
+ df["close"]
264
+ .rolling(20)
265
+ .mean()
266
+ )
267
 
268
+ std20 = (
269
+ df["close"]
270
+ .rolling(20)
271
+ .std()
272
+ )
273
 
274
+ df["bb_upper"] = sma20 + (2 * std20)
 
 
 
 
275
 
276
+ df["bb_lower"] = sma20 - (2 * std20)
 
277
 
278
+ # ATR
279
 
280
+ df["atr"] = (
281
+ (df["high"] - df["low"])
282
+ .rolling(14)
283
+ .mean()
284
+ )
285
+
286
+ # VOLUME
287
+
288
+ df["vol_sma"] = (
289
+ df["volume"]
290
+ .rolling(20)
291
+ .mean()
292
+ )
293
+
294
+ # ADX
295
+
296
+ adx = ADXIndicator(
297
+ high=df["high"],
298
+ low=df["low"],
299
+ close=df["close"]
300
+ )
301
+
302
+ df["adx"] = adx.adx()
303
+
304
+ # VOLATILITY
305
+
306
+ df["volatility"] = (
307
+ df["close"]
308
+ .pct_change()
309
+ .rolling(20)
310
+ .std()
311
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
 
313
+ # REGIME
 
314
 
315
+ def detect_regime(row):
316
 
317
+ if row["volatility"] < 0.01:
318
+ return "SIDEWAYS"
 
319
 
320
+ if row["ema50"] > row["ema200"]:
321
+ return "BULL"
322
 
323
+ return "BEAR"
324
 
325
+ df["regime"] = df.apply(
326
+ detect_regime,
327
+ axis=1
328
  )
329
 
330
+ # RETURNS
331
+
332
+ df["return1"] = (
333
+ df["close"]
334
+ .pct_change()
335
  )
336
 
337
+ df["return4"] = (
338
+ df["close"]
339
+ .pct_change(4)
340
+ )
341
 
342
+ # LABEL
343
 
344
+ df["next_close"] = (
345
+ df["close"]
346
+ .shift(-1)
347
+ )
348
 
349
+ df["label"] = (
350
+ df["next_close"]
351
+ >
352
+ df["close"]
353
+ ).astype(int)
354
+
355
+ df = df.replace(
356
+ [np.inf, -np.inf],
357
+ np.nan
358
+ )
359
+
360
+ df = df.dropna()
361
+
362
+ feat_cols = [
363
+ "open",
364
+ "high",
365
+ "low",
366
+ "close",
367
+ "volume",
368
+ "rsi",
369
+ "macd",
370
+ "macd_signal",
371
+ "ema9",
372
+ "ema21",
373
+ "ema50",
374
+ "ema200",
375
+ "bb_upper",
376
+ "bb_lower",
377
+ "atr",
378
+ "adx",
379
+ "return1",
380
+ "return4"
381
+ ]
382
+
383
+ X_list = []
384
+ y_list = []
385
+
386
+ for i in range(WINDOW, len(df)):
387
+
388
+ window = df.iloc[
389
+ i-WINDOW:i
390
+ ][feat_cols].values
391
+
392
+ if np.isnan(window).any():
393
+ continue
394
+
395
+ X_list.append(
396
+ window.flatten()
397
+ )
398
+
399
+ y_list.append(
400
+ df.iloc[i]["label"]
401
+ )
402
+
403
+ X = np.array(X_list)
404
+
405
+ y = np.array(y_list)
406
+
407
+ return X, y, df, feat_cols
408
 
 
409
 
410
+ # ============================================================
411
+ # TRAIN
412
  # ============================================================
413
 
414
  def run_train(symbol):
415
 
416
+ X, y, df, feat_cols = (
417
+ fetch_and_prepare(symbol)
418
+ )
419
 
420
+ if X is None:
421
+ return
 
422
 
423
+ scaler = MinMaxScaler()
424
 
425
+ X_scaled = scaler.fit_transform(X)
426
 
427
+ model = XGBClassifier(
428
+ n_estimators=300,
429
+ max_depth=6,
430
+ learning_rate=0.03,
431
+ subsample=0.8,
432
+ colsample_bytree=0.8,
433
+ eval_metric="logloss",
434
+ random_state=42
435
+ )
436
 
437
+ tscv = TimeSeriesSplit(
438
+ n_splits=5
439
+ )
440
 
441
+ for train_idx, test_idx in tscv.split(X_scaled):
 
 
 
 
 
 
 
 
442
 
443
+ X_train = X_scaled[train_idx]
 
 
444
 
445
+ y_train = y[train_idx]
446
 
447
+ model.fit(
448
+ X_train,
449
+ y_train
450
+ )
451
 
452
+ joblib.dump(
453
+ model,
454
+ model_path(symbol)
455
  )
456
 
457
+ joblib.dump(
458
+ scaler,
459
+ scaler_path(symbol)
460
+ )
 
 
 
 
 
461
 
462
+ logger.info(
463
+ f"TRAINED: {symbol}"
464
+ )
465
 
466
 
467
  # ============================================================
 
468
  # TRAIN ALL
 
469
  # ============================================================
470
 
471
  def train_all():
472
 
473
+ for symbol in SYMBOLS:
474
 
475
+ try:
476
 
477
+ run_train(symbol)
478
 
479
+ except Exception as e:
480
 
481
+ logger.error(e)
 
 
482
 
483
 
484
  # ============================================================
 
485
  # TRADE LEVELS
 
486
  # ============================================================
487
 
488
  def trade_levels(df, signal):
489
 
490
+ last = df.iloc[-1]
491
 
492
+ close_price = float(
493
+ last["close"]
494
+ )
495
 
496
+ atr = float(
497
+ last["atr"]
498
+ )
499
 
500
+ if signal == "BUY":
 
 
501
 
502
+ tp1 = close_price + atr
503
+ tp2 = close_price + atr * 2
504
+ tp3 = close_price + atr * 3
505
 
506
+ sl = close_price - atr * 1.5
 
 
507
 
508
+ elif signal == "SELL":
509
 
510
+ tp1 = close_price - atr
511
+ tp2 = close_price - atr * 2
512
+ tp3 = close_price - atr * 3
513
 
514
+ sl = close_price + atr * 1.5
 
 
515
 
516
+ else:
517
 
518
+ tp1 = close_price
519
+ tp2 = close_price
520
+ tp3 = close_price
521
 
522
+ sl = close_price
 
 
 
523
 
524
+ return {
525
+ "entry": close_price,
526
+ "tp1": tp1,
527
+ "tp2": tp2,
528
+ "tp3": tp3,
529
+ "sl": sl
530
+ }
531
 
532
 
533
  # ============================================================
 
534
  # PREDICT
 
535
  # ============================================================
536
 
537
  LAST_SIGNALS = {}
538
 
539
  def run_predict(symbol):
540
 
541
+ if not os.path.exists(
542
+ model_path(symbol)
543
+ ):
544
 
545
+ run_train(symbol)
 
 
 
 
 
 
 
 
 
 
 
 
546
 
547
+ X, y, df, feat_cols = (
548
+ fetch_and_prepare(symbol)
549
+ )
550
 
551
+ scaler = joblib.load(
552
+ scaler_path(symbol)
553
+ )
 
 
 
554
 
555
+ model = joblib.load(
556
+ model_path(symbol)
557
+ )
558
 
559
+ latest = (
560
+ df.iloc[-WINDOW:][feat_cols]
561
+ .values
562
+ .flatten()
563
+ .reshape(1, -1)
564
+ )
565
 
566
+ latest_scaled = scaler.transform(
567
+ latest
568
+ )
569
 
570
+ pred = model.predict(
571
+ latest_scaled
572
+ )[0]
 
573
 
574
+ proba = model.predict_proba(
575
+ latest_scaled
576
+ )[0]
577
 
578
+ confidence = (
579
+ float(np.max(proba))
580
+ * 100
581
+ )
 
582
 
583
+ last = df.iloc[-1]
 
 
 
 
584
 
585
+ bull_trend = (
586
+ last["ema50"]
587
+ >
588
+ last["ema200"]
589
+ )
590
 
591
+ strong_volume = (
592
+ last["volume"]
593
+ >
594
+ last["vol_sma"]
595
+ )
596
 
597
+ strong_adx = (
598
+ last["adx"] > 25
599
+ )
600
 
601
+ regime = last["regime"]
 
602
 
603
+ score = 0
 
604
 
605
+ if bull_trend:
606
+ score += 20
607
 
608
+ if strong_volume:
609
+ score += 20
610
 
611
+ if strong_adx:
612
+ score += 20
613
 
614
+ if last["rsi"] > 55:
615
+ score += 20
616
 
617
+ if last["macd"] > last["macd_signal"]:
618
+ score += 20
619
 
620
  signal = "NO TRADE"
621
 
622
+ if regime == "SIDEWAYS":
623
 
624
+ signal = "NO TRADE"
625
 
626
+ else:
627
 
628
+ if pred == 1 and score >= 60:
629
 
630
+ signal = "BUY"
631
 
632
+ elif pred == 0 and score >= 60:
 
 
 
633
 
634
+ signal = "SELL"
635
 
636
+ levels = trade_levels(
637
+ df,
638
+ signal
 
 
 
 
 
 
 
 
 
 
639
  )
640
 
641
+ previous = LAST_SIGNALS.get(symbol)
642
 
643
+ if previous != signal:
 
 
 
 
 
 
 
644
 
645
+ LAST_SIGNALS[symbol] = signal
646
 
647
+ send_telegram(
648
+ f"""
649
  🚨 NEW SIGNAL
650
 
651
  PAIR: {symbol}
 
654
 
655
  CONFIDENCE: {confidence:.2f}%
656
  """
657
+ )
 
658
 
659
+ return {
660
+ "Pair": symbol,
661
+ "Signal": signal,
662
+ "Confidence": round(confidence, 2),
663
+ "Score": score,
664
+ "Entry": round(levels["entry"], 2),
665
+ "TP1": round(levels["tp1"], 2),
666
+ "TP2": round(levels["tp2"], 2),
667
+ "TP3": round(levels["tp3"], 2),
668
+ "SL": round(levels["sl"], 2),
669
+ "RSI": round(last["rsi"], 2),
670
+ "ADX": round(last["adx"], 2),
671
+ "Regime": regime
672
+ }
673
 
674
 
675
  # ============================================================
 
676
  # REALTIME SCAN
 
677
  # ============================================================
678
 
679
  LAST_RETRAIN = 0
680
 
681
  def realtime_scan():
682
 
683
+ global LAST_RETRAIN
684
 
685
+ now = time.time()
 
 
686
 
687
+ if now - LAST_RETRAIN > 21600:
688
 
689
+ logger.info(
690
+ "AUTO RETRAIN..."
691
+ )
 
 
692
 
693
+ train_all()
694
 
695
+ LAST_RETRAIN = now
696
 
697
+ rows = []
698
 
699
+ for symbol in SYMBOLS:
700
 
701
+ try:
702
 
703
+ result = run_predict(symbol)
704
 
705
+ rows.append(result)
706
 
707
+ except Exception:
708
 
709
+ logger.error(
710
+ traceback.format_exc()
711
+ )
712
 
713
+ df = pd.DataFrame(rows)
714
 
715
+ if len(df) > 0:
716
 
717
+ df = df.sort_values(
718
+ by="Score",
719
+ ascending=False
720
+ )
721
 
722
+ return df
723
 
724
 
725
  # ============================================================
 
726
  # UI
 
727
  # ============================================================
728
 
729
  with gr.Blocks(
730
+ title="Crypto AI Scanner"
731
  ) as demo:
732
 
733
+ gr.Markdown(
734
+ "# 🤖 Crypto AI Scanner PRO"
735
+ )
736
 
737
+ table = gr.Dataframe(
738
+ interactive=False
739
+ )
 
 
 
 
740
 
741
+ timer = gr.Timer(20)
742
 
743
+ timer.tick(
744
+ fn=realtime_scan,
745
+ outputs=table
746
+ )
747
 
748
 
749
  # ============================================================
 
750
  # START
 
751
  # ============================================================
752
 
753
+ if __name__ == "__main__":
 
754
 
755
+ train_all()
 
 
756
 
757
+ demo.queue()
 
 
 
758
 
759
+ demo.launch(
760
+ server_name="0.0.0.0",
761
+ server_port=7860
762
+ )