daafa999 commited on
Commit
35533ec
ยท
verified ยท
1 Parent(s): ebed64b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -86
app.py CHANGED
@@ -1,160 +1,227 @@
1
  import gradio as gr
2
- import ccxt
3
  import pandas as pd
4
- import pandas_ta as ta
5
  import numpy as np
6
  from sklearn.ensemble import RandomForestClassifier
7
  from sklearn.preprocessing import MinMaxScaler
8
  import joblib
9
  import os
10
- import logging
11
  import sys
12
  import warnings
13
 
14
- # ๐Ÿ”ง Supress pandas-ta deprecation warnings di pandas 2.2+
15
- warnings.filterwarnings("ignore", category=FutureWarning)
16
- warnings.filterwarnings("ignore", category=DeprecationWarning)
17
 
18
- logging.basicConfig(stream=sys.stdout, level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
19
- logger = logging.getLogger("crypto_ml")
20
-
21
- SYMBOL = "ETH/USDT"
22
- TIMEFRAME = "4h"
23
  FEE = 0.0015
24
  WINDOW = 60
25
- MODEL_PATH = "rf_model.pkl"
26
- SCALER_PATH = "scaler.pkl"
27
 
28
- def fetch_data(symbol: str, timeframe: str, limit: int = 2000) -> pd.DataFrame:
29
- logger.info(f"๐Ÿ“ฅ Fetching {limit} candles for {symbol} ({timeframe})...")
30
- exchange = ccxt.binance({
31
- "enableRateLimit": True,
32
- "timeout": 30000,
33
- "options": {"defaultType": "spot"}
34
- })
35
- ohlcv = exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
36
- df = pd.DataFrame(ohlcv, columns=["timestamp", "open", "high", "low", "close", "volume"])
37
- df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
38
- logger.info(f"โœ… Fetched {len(df)} rows.")
39
- return df
40
 
41
- def prepare_dataset(df: pd.DataFrame):
42
- logger.info("๐Ÿ“Š Calculating indicators & preparing features...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  df = df.copy()
44
 
45
- # pandas-ta bekerja di pandas 2.2+, hanya butuh warning suppression
46
- df.ta.rsi(append=True)
47
- df.ta.macd(append=True)
48
- df.ta.bbands(append=True)
49
- df.ta.cci(append=True)
50
- df.dropna(inplace=True)
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  df["next_close"] = df["close"].shift(-1)
53
  df["label"] = (df["next_close"] > df["close"] * (1 + FEE)).astype(int)
54
- df.dropna(inplace=True)
 
 
 
 
 
55
 
56
- X_list, y_list, idx_list = [], [], []
57
  for i in range(WINDOW, len(df)):
58
- window = df.iloc[i-WINDOW:i].drop(columns=["timestamp", "next_close", "label"])
59
- X_list.append(window.values.flatten())
60
  y_list.append(df.iloc[i]["label"])
61
- idx_list.append(i)
62
-
63
  X = np.array(X_list)
64
  y = np.array(y_list)
 
65
  scaler = MinMaxScaler()
66
  X_scaled = scaler.fit_transform(X)
67
- logger.info(f"โœ… Dataset ready: {X.shape[0]} samples, {X.shape[1]} features.")
68
- return X_scaled, y, idx_list, df, scaler
69
 
70
  def run_train():
71
  try:
72
  if os.path.exists(MODEL_PATH):
73
- return {"status": "โš ๏ธ Info", "message": "Model already exists. Delete .pkl files in the Files tab to retrain."}
74
-
75
- df = fetch_data(SYMBOL, TIMEFRAME)
76
- X, y, idx, df_full, scaler = prepare_dataset(df)
 
77
 
 
 
 
78
  split = int(len(X) * 0.95)
79
  X_train, X_test = X[:split], X[split:]
80
  y_train, y_test = y[:split], y[split:]
81
 
82
- logger.info("๐Ÿง  Training RandomForest model...")
83
- model = RandomForestClassifier(n_estimators=500, max_depth=None, random_state=42, n_jobs=-1)
84
  model.fit(X_train, y_train)
85
  acc = float(model.score(X_test, y_test))
86
 
87
  joblib.dump(model, MODEL_PATH)
88
  joblib.dump(scaler, SCALER_PATH)
89
 
90
- test_indices = idx[split:]
91
- df_test = df_full.loc[test_indices].copy()
92
- df_test["pred"] = model.predict(X_test)
93
 
94
- pnl_list = []
95
- for i in range(len(df_test) - 1):
96
- if df_test.iloc[i]["pred"] == 1:
97
  entry = df_test.iloc[i]["close"]
98
- exit_ = df_test.iloc[i + 1]["close"]
99
- pnl = (exit_ - entry) / entry - FEE
100
- pnl_list.append(pnl)
101
-
102
- wins = sum(1 for p in pnl_list if p > 0)
103
- losses = len(pnl_list) - wins
104
- gross_profit = sum(p for p in pnl_list if p > 0)
105
- gross_loss = sum(abs(p) for p in pnl_list if p <= 0)
106
- profit_factor = gross_profit / gross_loss if gross_loss > 0 else float("inf")
107
 
108
  return {
109
  "status": "โœ… Success",
110
  "accuracy_test": round(acc, 4),
111
- "profit_factor": round(profit_factor, 3),
112
- "net_profit_pct": round(sum(pnl_list) * 100, 3),
113
- "wins": wins, "losses": losses,
114
- "win_rate_pct": round(wins / len(pnl_list) * 100, 2) if pnl_list else 0,
115
- "message": "Model trained & backtested successfully."
116
  }
117
  except Exception as e:
118
- logger.error(f"โŒ Training failed: {e}")
119
- return {"status": "โŒ Error", "message": str(e)}
120
 
121
  def run_predict():
122
  try:
123
  if not os.path.exists(MODEL_PATH):
124
- return {"status": "โŒ Error", "message": "Model not found. Train the model first."}
125
-
126
- df = fetch_data(SYMBOL, TIMEFRAME, limit=WINDOW + 10)
127
- _, _, _, df_full, scaler = prepare_dataset(df)
128
- latest_window = df_full.iloc[-WINDOW:].drop(columns=["timestamp", "next_close", "label"]).values.flatten().reshape(1, -1)
129
- latest_scaled = scaler.transform(latest_window)
 
 
 
 
 
 
 
 
130
 
131
  model = joblib.load(MODEL_PATH)
132
  pred = int(model.predict(latest_scaled)[0])
 
133
 
134
  return {
135
  "status": "โœ… Prediction",
136
- "symbol": SYMBOL,
137
- "timeframe": TIMEFRAME,
138
  "prediction": "BUY (Hold 4h)" if pred == 1 else "HOLD/SELL",
139
- "logic": "Predicts >0.15% movement in next 4h candle"
 
140
  }
141
  except Exception as e:
142
- logger.error(f"โŒ Prediction failed: {e}")
143
- return {"status": "โŒ Error", "message": str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
- # ๐ŸŽจ Gradio UI
146
- with gr.Blocks(title="Crypto ML Predictor", theme=gr.themes.Soft()) as demo:
147
  gr.Markdown("## ๐Ÿ“ˆ Crypto ML Strategy (ETH/USDT 4h)")
148
- gr.Markdown("Paper: 2105.06827v3 | Python 3.13 Ready | Gradio 6.14.0")
 
 
 
 
149
  with gr.Row():
150
  train_btn = gr.Button("๐Ÿ”„ Train Model & Backtest", variant="primary")
151
- train_out = gr.JSON(label="Training & Backtest Results")
152
  with gr.Row():
153
  pred_btn = gr.Button("๐Ÿ”ฎ Predict Next Move", variant="secondary")
154
  pred_out = gr.JSON(label="Prediction Signal")
155
-
156
  train_btn.click(run_train, outputs=train_out)
157
  pred_btn.click(run_predict, outputs=pred_out)
158
- gr.Markdown("๐Ÿ’ก *Check `App Logs` in the Space tab for real-time debugging.*")
 
159
 
160
  demo.queue(default_concurrency_limit=1)
 
1
  import gradio as gr
2
+ import yfinance as yf
3
  import pandas as pd
 
4
  import numpy as np
5
  from sklearn.ensemble import RandomForestClassifier
6
  from sklearn.preprocessing import MinMaxScaler
7
  import joblib
8
  import os
 
9
  import sys
10
  import warnings
11
 
12
+ warnings.filterwarnings("ignore")
 
 
13
 
14
+ # ๐Ÿ”ง Config
15
+ SYMBOL = "ETH-USD" # Yahoo Finance format
16
+ TIMEFRAME = "4h" # yfinance supports: 1h, 1d, 1wk, etc. We'll use 1h & resample
 
 
17
  FEE = 0.0015
18
  WINDOW = 60
19
+ MODEL_PATH = "/tmp/rf_model.pkl"
20
+ SCALER_PATH = "/tmp/scaler.pkl"
21
 
22
+ def log(msg):
23
+ print(f"[LOG] {msg}", flush=True)
 
 
 
 
 
 
 
 
 
 
24
 
25
+ def fetch_data(symbol: str, period: str = "60d", interval: str = "1h") -> pd.DataFrame:
26
+ """Fetch ETH-USD data from Yahoo Finance, resample to 4h"""
27
+ log(f"๐Ÿ“ฅ Fetching {symbol} from Yahoo Finance...")
28
+ try:
29
+ df = yf.download(symbol, period=period, interval=interval, progress=False)
30
+ if df.empty:
31
+ raise ValueError("No data returned")
32
+
33
+ # Resample to 4h candles (OHLCV aggregation)
34
+ df = df.resample("4h").agg({
35
+ "Open": "first", "High": "max", "Low": "min",
36
+ "Close": "last", "Volume": "sum"
37
+ }).dropna()
38
+
39
+ df.columns = ["open", "high", "low", "close", "volume"]
40
+ df.index = pd.to_datetime(df.index)
41
+ df = df.reset_index()
42
+ log(f"โœ… Fetched & resampled to {len(df)} 4h candles")
43
+ return df
44
+ except Exception as e:
45
+ log(f"โŒ Fetch error: {e}")
46
+ return None
47
+
48
+ def calculate_indicators(df: pd.DataFrame) -> pd.DataFrame:
49
+ """Manual TA calculations (pandas-ta alternative for stability)"""
50
  df = df.copy()
51
 
52
+ # RSI (14)
53
+ delta = df["close"].diff()
54
+ gain = (delta.where(delta > 0, 0)).rolling(14).mean()
55
+ loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
56
+ rs = gain / loss
57
+ df["rsi"] = 100 - (100 / (1 + rs))
58
 
59
+ # MACD (12,26,9)
60
+ exp1 = df["close"].ewm(span=12, adjust=False).mean()
61
+ exp2 = df["close"].ewm(span=26, adjust=False).mean()
62
+ df["macd"] = exp1 - exp2
63
+ df["macd_signal"] = df["macd"].ewm(span=9, adjust=False).mean()
64
+ df["macd_hist"] = df["macd"] - df["macd_signal"]
65
+
66
+ # Bollinger Bands (20,2)
67
+ sma = df["close"].rolling(20).mean()
68
+ std = df["close"].rolling(20).std()
69
+ df["bb_upper"] = sma + (std * 2)
70
+ df["bb_lower"] = sma - (std * 2)
71
+
72
+ # CCI (20)
73
+ tp = (df["high"] + df["low"] + df["close"]) / 3
74
+ cci_ma = tp.rolling(20).mean()
75
+ mad = tp.rolling(20).apply(lambda x: np.fabs(x - x.mean()).mean())
76
+ df["cci"] = (tp - cci_ma) / (0.015 * mad)
77
+
78
+ return df
79
+
80
+ def prepare_dataset(df: pd.DataFrame):
81
+ log("๐Ÿ“Š Preparing features & labels...")
82
+ df = calculate_indicators(df).dropna()
83
+
84
+ # Label: 1 if next 4h close > current close * (1 + fee)
85
  df["next_close"] = df["close"].shift(-1)
86
  df["label"] = (df["next_close"] > df["close"] * (1 + FEE)).astype(int)
87
+ df = df.dropna()
88
+
89
+ # Sliding window features
90
+ feature_cols = ["open", "high", "low", "close", "volume",
91
+ "rsi", "macd", "macd_signal", "macd_hist",
92
+ "bb_upper", "bb_lower", "cci"]
93
 
94
+ X_list, y_list = [], []
95
  for i in range(WINDOW, len(df)):
96
+ window = df.iloc[i-WINDOW:i][feature_cols].values.flatten()
97
+ X_list.append(window)
98
  y_list.append(df.iloc[i]["label"])
99
+
 
100
  X = np.array(X_list)
101
  y = np.array(y_list)
102
+
103
  scaler = MinMaxScaler()
104
  X_scaled = scaler.fit_transform(X)
105
+ log(f"โœ… Dataset: {X_scaled.shape[0]} samples, {X_scaled.shape[1]} features")
106
+ return X_scaled, y, df, scaler
107
 
108
  def run_train():
109
  try:
110
  if os.path.exists(MODEL_PATH):
111
+ return {"status": "โš ๏ธ Info", "message": "Model exists. Delete /tmp/*.pkl to retrain."}
112
+
113
+ df = fetch_data(SYMBOL)
114
+ if df is None:
115
+ return {"status": "โŒ Error", "message": "Failed to fetch data"}
116
 
117
+ X, y, df_full, scaler = prepare_dataset(df)
118
+
119
+ # Split 95/5
120
  split = int(len(X) * 0.95)
121
  X_train, X_test = X[:split], X[split:]
122
  y_train, y_test = y[:split], y[split:]
123
 
124
+ log("๐Ÿง  Training RandomForest...")
125
+ model = RandomForestClassifier(n_estimators=100, max_depth=10, random_state=42, n_jobs=-1)
126
  model.fit(X_train, y_train)
127
  acc = float(model.score(X_test, y_test))
128
 
129
  joblib.dump(model, MODEL_PATH)
130
  joblib.dump(scaler, SCALER_PATH)
131
 
132
+ # Simple backtest on test set
133
+ df_test = df_full.iloc[-len(X_test)-10:].copy()
134
+ preds = model.predict(X_test)
135
 
136
+ pnl = []
137
+ for i in range(len(preds) - 1):
138
+ if preds[i] == 1:
139
  entry = df_test.iloc[i]["close"]
140
+ exit_ = df_test.iloc[i+1]["close"]
141
+ pnl.append((exit_ - entry) / entry - FEE)
142
+
143
+ wins = sum(1 for p in pnl if p > 0)
144
+ total = len(pnl)
145
+ net = sum(pnl) * 100 if pnl else 0
 
 
 
146
 
147
  return {
148
  "status": "โœ… Success",
149
  "accuracy_test": round(acc, 4),
150
+ "net_profit_pct": round(net, 3),
151
+ "win_rate_pct": round(wins/total*100, 2) if total else 0,
152
+ "message": "Model trained successfully"
 
 
153
  }
154
  except Exception as e:
155
+ log(f"โŒ Train error: {e}")
156
+ return {"status": "โŒ Error", "message": str(e)[:200]}
157
 
158
  def run_predict():
159
  try:
160
  if not os.path.exists(MODEL_PATH):
161
+ return {"status": "โŒ Error", "message": "Train model first"}
162
+
163
+ df = fetch_data(SYMBOL, period="30d")
164
+ if df is None:
165
+ return {"status": "โŒ Error", "message": "Data fetch failed"}
166
+
167
+ df = calculate_indicators(df).dropna()
168
+ feature_cols = ["open", "high", "low", "close", "volume",
169
+ "rsi", "macd", "macd_signal", "macd_hist",
170
+ "bb_upper", "bb_lower", "cci"]
171
+
172
+ latest = df.iloc[-WINDOW:][feature_cols].values.flatten().reshape(1, -1)
173
+ scaler = joblib.load(SCALER_PATH)
174
+ latest_scaled = scaler.transform(latest)
175
 
176
  model = joblib.load(MODEL_PATH)
177
  pred = int(model.predict(latest_scaled)[0])
178
+ prob = float(model.predict_proba(latest_scaled)[0][1])
179
 
180
  return {
181
  "status": "โœ… Prediction",
182
+ "symbol": SYMBOL.replace("-USD", "/USDT"),
 
183
  "prediction": "BUY (Hold 4h)" if pred == 1 else "HOLD/SELL",
184
+ "confidence_pct": round(prob * 100, 1),
185
+ "logic": f"Model predicts {'>' if pred==1 else '<='}0.15% move in next 4h"
186
  }
187
  except Exception as e:
188
+ log(f"โŒ Predict error: {e}")
189
+ return {"status": "โŒ Error", "message": str(e)[:200]}
190
+
191
+ # ๐ŸŽจ Gradio UI + TradingView Widget
192
+ tradingview_widget = """
193
+ <div class="tradingview-widget-container">
194
+ <div id="tradingview_chart"></div>
195
+ <script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
196
+ <script type="text/javascript">
197
+ new TradingView.widget({
198
+ "width": "100%", "height": 400, "symbol": "BINANCE:ETHUSDT",
199
+ "interval": "240", "timezone": "UTC", "theme": "dark",
200
+ "style": "1", "locale": "en", "toolbar_bg": "#1a1a1a",
201
+ "enable_publishing": false, "allow_symbol_change": true,
202
+ "container_id": "tradingview_chart"
203
+ });
204
+ </script>
205
+ </div>
206
+ """
207
 
208
+ with gr.Blocks(title="Crypto ML Strategy", css=".tradingview-widget-container { margin: 10px 0; }") as demo:
 
209
  gr.Markdown("## ๐Ÿ“ˆ Crypto ML Strategy (ETH/USDT 4h)")
210
+ gr.Markdown("Paper: 2105.06827v3 | Data: Yahoo Finance | Chart: TradingView Widget")
211
+
212
+ # TradingView Chart Embed
213
+ gr.HTML(tradingview_widget)
214
+
215
  with gr.Row():
216
  train_btn = gr.Button("๐Ÿ”„ Train Model & Backtest", variant="primary")
217
+ train_out = gr.JSON(label="Training Results")
218
  with gr.Row():
219
  pred_btn = gr.Button("๐Ÿ”ฎ Predict Next Move", variant="secondary")
220
  pred_out = gr.JSON(label="Prediction Signal")
221
+
222
  train_btn.click(run_train, outputs=train_out)
223
  pred_btn.click(run_predict, outputs=pred_out)
224
+
225
+ gr.Markdown("๐Ÿ’ก *Data source: Yahoo Finance (same as TradingView). Model retrains on each session.*")
226
 
227
  demo.queue(default_concurrency_limit=1)