daafa999 commited on
Commit
317f760
ยท
verified ยท
1 Parent(s): 77bf815

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +83 -71
app.py CHANGED
@@ -8,115 +8,105 @@ from sklearn.ensemble import RandomForestClassifier
8
  from sklearn.preprocessing import MinMaxScaler
9
  import joblib
10
  import os
11
- from pydantic import BaseModel
 
12
 
13
  app = FastAPI(title="Crypto ML Strategy (Paper 2105.06827v3)")
14
 
15
- # ๐Ÿ”ง Konfigurasi
16
  SYMBOL = "ETH/USDT"
17
  TIMEFRAME = "4h"
18
- FEE = 0.0015 # 0.15% sesuai paper
19
- WINDOW = 60 # 60 candle * 4h = 240h lookback
20
  MODEL_PATH = "rf_model.pkl"
21
  SCALER_PATH = "scaler.pkl"
22
 
23
- # ๐Ÿ“ฅ 1. Ambil Data Historis
24
  def fetch_data(symbol: str, timeframe: str, limit: int = 2000) -> pd.DataFrame:
25
- exchange = ccxt.binance()
26
  ohlcv = exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
27
  df = pd.DataFrame(ohlcv, columns=["timestamp", "open", "high", "low", "close", "volume"])
28
  df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
29
  return df
30
 
31
- # ๐Ÿงฎ 2. Tambah Indikator & Siapkan Fitur/Label
32
  def prepare_dataset(df: pd.DataFrame):
33
  df = df.copy()
34
- # Indikator teknikal (sesuai Appendix A paper)
35
  df.ta.rsi(append=True)
36
  df.ta.macd(append=True)
37
  df.ta.bbands(append=True)
38
  df.ta.cci(append=True)
39
  df.dropna(inplace=True)
40
-
41
- # Label: 1 jika pergerakan 4h ke depan > fee (0.15%), else 0
42
  df["next_close"] = df["close"].shift(-1)
43
  df["label"] = (df["next_close"] > df["close"] * (1 + FEE)).astype(int)
44
  df.dropna(inplace=True)
45
-
46
- # Buat sliding window (60 candle per sampel)
47
  X_list, y_list, idx_list = [], [], []
48
  for i in range(WINDOW, len(df)):
49
  window = df.iloc[i-WINDOW:i].drop(columns=["timestamp", "next_close", "label"])
50
  X_list.append(window.values.flatten())
51
  y_list.append(df.iloc[i]["label"])
52
  idx_list.append(i)
53
-
54
  X = np.array(X_list)
55
  y = np.array(y_list)
56
  scaler = MinMaxScaler()
57
  X_scaled = scaler.fit_transform(X)
58
  return X_scaled, y, idx_list, df, scaler
59
 
60
- # ๐Ÿ“Š 3. FastAPI Endpoints
61
- @app.get("/train")
62
  def train_and_backtest():
63
- if os.path.exists(MODEL_PATH):
64
- return {"message": "Model sudah ada. Hapus model.pkl & scaler.pkl untuk retrain."}
65
-
66
- df = fetch_data(SYMBOL, TIMEFRAME)
67
- X, y, idx, df_full, scaler = prepare_dataset(df)
68
-
69
- # Split 95% train / 5% test (sesuai paper)
70
- split = int(len(X) * 0.95)
71
- X_train, X_test = X[:split], X[split:]
72
- y_train, y_test = y[:split], y[split:]
73
-
74
- # Training
75
- model = RandomForestClassifier(n_estimators=500, max_depth=None, random_state=42)
76
- model.fit(X_train, y_train)
77
- acc = float(model.score(X_test, y_test))
78
-
79
- joblib.dump(model, MODEL_PATH)
80
- joblib.dump(scaler, SCALER_PATH)
81
-
82
- # ๐Ÿงช Backtest pada data unseen (5% terakhir)
83
- test_indices = idx[split:]
84
- df_test = df_full.loc[test_indices].copy()
85
- df_test["pred"] = model.predict(X_test)
86
-
87
- pnl_list = []
88
- for i in range(len(df_test) - 1):
89
- if df_test.iloc[i]["pred"] == 1:
90
- entry = df_test.iloc[i]["close"]
91
- exit_ = df_test.iloc[i + 1]["close"]
92
- ret = (exit_ - entry) / entry
93
- pnl = ret - FEE # Kurangi fee exchange
94
- pnl_list.append(pnl)
95
-
96
- wins = sum(1 for p in pnl_list if p > 0)
97
- losses = len(pnl_list) - wins
98
- gross_profit = sum(p for p in pnl_list if p > 0)
99
- gross_loss = sum(abs(p) for p in pnl_list if p <= 0)
100
- profit_factor = gross_profit / gross_loss if gross_loss > 0 else float("inf")
101
-
102
- return {
103
- "accuracy_test": round(acc, 4),
104
- "profit_factor": round(profit_factor, 3),
105
- "net_profit_pct": round(sum(pnl_list) * 100, 3),
106
- "wins": wins,
107
- "losses": losses,
108
- "win_rate_pct": round(wins / len(pnl_list) * 100, 2) if pnl_list else 0,
109
- "message": "Model trained & backtested on unseen data"
110
- }
111
-
112
- @app.get("/predict")
113
  def predict_next_move():
114
  if not os.path.exists(MODEL_PATH):
115
- raise HTTPException(400, "Model belum dilatih. Panggil /train terlebih dahulu.")
116
-
117
  df = fetch_data(SYMBOL, TIMEFRAME, limit=WINDOW + 10)
118
  _, _, _, df_full, scaler = prepare_dataset(df)
119
-
120
  latest_window = df_full.iloc[-WINDOW:].drop(columns=["timestamp", "next_close", "label"]).values.flatten().reshape(1, -1)
121
  latest_scaled = scaler.transform(latest_window)
122
 
@@ -124,8 +114,30 @@ def predict_next_move():
124
  pred = int(model.predict(latest_scaled)[0])
125
 
126
  return {
127
- "symbol": SYMBOL,
128
- "timeframe": TIMEFRAME,
129
  "prediction": "BUY (Hold 4h)" if pred == 1 else "HOLD/SELL",
130
  "logic": "Prediksi >0.15% movement dalam 4 jam ke depan"
131
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  from sklearn.preprocessing import MinMaxScaler
9
  import joblib
10
  import os
11
+ import warnings
12
+ warnings.filterwarnings('ignore')
13
 
14
  app = FastAPI(title="Crypto ML Strategy (Paper 2105.06827v3)")
15
 
 
16
  SYMBOL = "ETH/USDT"
17
  TIMEFRAME = "4h"
18
+ FEE = 0.0015
19
+ WINDOW = 60
20
  MODEL_PATH = "rf_model.pkl"
21
  SCALER_PATH = "scaler.pkl"
22
 
 
23
  def fetch_data(symbol: str, timeframe: str, limit: int = 2000) -> pd.DataFrame:
24
+ exchange = ccxt.binance({"enableRateLimit": True})
25
  ohlcv = exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
26
  df = pd.DataFrame(ohlcv, columns=["timestamp", "open", "high", "low", "close", "volume"])
27
  df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
28
  return df
29
 
 
30
  def prepare_dataset(df: pd.DataFrame):
31
  df = df.copy()
 
32
  df.ta.rsi(append=True)
33
  df.ta.macd(append=True)
34
  df.ta.bbands(append=True)
35
  df.ta.cci(append=True)
36
  df.dropna(inplace=True)
 
 
37
  df["next_close"] = df["close"].shift(-1)
38
  df["label"] = (df["next_close"] > df["close"] * (1 + FEE)).astype(int)
39
  df.dropna(inplace=True)
40
+
 
41
  X_list, y_list, idx_list = [], [], []
42
  for i in range(WINDOW, len(df)):
43
  window = df.iloc[i-WINDOW:i].drop(columns=["timestamp", "next_close", "label"])
44
  X_list.append(window.values.flatten())
45
  y_list.append(df.iloc[i]["label"])
46
  idx_list.append(i)
47
+
48
  X = np.array(X_list)
49
  y = np.array(y_list)
50
  scaler = MinMaxScaler()
51
  X_scaled = scaler.fit_transform(X)
52
  return X_scaled, y, idx_list, df, scaler
53
 
54
+ @app.get("/api/train")
 
55
  def train_and_backtest():
56
+ try:
57
+ if os.path.exists(MODEL_PATH):
58
+ return {"message": "Model sudah ada. Hapus rf_model.pkl & scaler.pkl untuk retrain."}
59
+
60
+ df = fetch_data(SYMBOL, TIMEFRAME)
61
+ X, y, idx, df_full, scaler = prepare_dataset(df)
62
+
63
+ split = int(len(X) * 0.95)
64
+ X_train, X_test = X[:split], X[split:]
65
+ y_train, y_test = y[:split], y[split:]
66
+
67
+ model = RandomForestClassifier(n_estimators=500, max_depth=None, random_state=42)
68
+ model.fit(X_train, y_train)
69
+ acc = float(model.score(X_test, y_test))
70
+
71
+ joblib.dump(model, MODEL_PATH)
72
+ joblib.dump(scaler, SCALER_PATH)
73
+
74
+ test_indices = idx[split:]
75
+ df_test = df_full.loc[test_indices].copy()
76
+ df_test["pred"] = model.predict(X_test)
77
+
78
+ pnl_list = []
79
+ for i in range(len(df_test) - 1):
80
+ if df_test.iloc[i]["pred"] == 1:
81
+ entry = df_test.iloc[i]["close"]
82
+ exit_ = df_test.iloc[i + 1]["close"]
83
+ pnl = (exit_ - entry) / entry - FEE
84
+ pnl_list.append(pnl)
85
+
86
+ wins = sum(1 for p in pnl_list if p > 0)
87
+ losses = len(pnl_list) - wins
88
+ gross_profit = sum(p for p in pnl_list if p > 0)
89
+ gross_loss = sum(abs(p) for p in pnl_list if p <= 0)
90
+ profit_factor = gross_profit / gross_loss if gross_loss > 0 else float("inf")
91
+
92
+ return {
93
+ "accuracy_test": round(acc, 4),
94
+ "profit_factor": round(profit_factor, 3),
95
+ "net_profit_pct": round(sum(pnl_list) * 100, 3),
96
+ "wins": wins, "losses": losses,
97
+ "win_rate_pct": round(wins / len(pnl_list) * 100, 2) if pnl_list else 0,
98
+ "message": "Model trained & backtested successfully"
99
+ }
100
+ except Exception as e:
101
+ raise HTTPException(500, f"Training failed: {str(e)}")
102
+
103
+ @app.get("/api/predict")
 
 
104
  def predict_next_move():
105
  if not os.path.exists(MODEL_PATH):
106
+ raise HTTPException(400, "Model belum dilatih. Panggil /api/train terlebih dahulu.")
107
+
108
  df = fetch_data(SYMBOL, TIMEFRAME, limit=WINDOW + 10)
109
  _, _, _, df_full, scaler = prepare_dataset(df)
 
110
  latest_window = df_full.iloc[-WINDOW:].drop(columns=["timestamp", "next_close", "label"]).values.flatten().reshape(1, -1)
111
  latest_scaled = scaler.transform(latest_window)
112
 
 
114
  pred = int(model.predict(latest_scaled)[0])
115
 
116
  return {
117
+ "symbol": SYMBOL, "timeframe": TIMEFRAME,
 
118
  "prediction": "BUY (Hold 4h)" if pred == 1 else "HOLD/SELL",
119
  "logic": "Prediksi >0.15% movement dalam 4 jam ke depan"
120
+ }
121
+
122
+ # ๐ŸŽจ Gradio UI
123
+ def run_train():
124
+ return train_and_backtest()
125
+
126
+ def run_predict():
127
+ return predict_next_move()
128
+
129
+ with gr.Blocks(title="Crypto ML Predictor") as demo:
130
+ gr.Markdown("## ๐Ÿ“ˆ Crypto ML Strategy (ETH/USDT 4h)")
131
+ gr.Markdown("Berdasarkan paper 2105.06827v3. Membutuhkan koneksi internet untuk fetch data Binance.")
132
+ with gr.Row():
133
+ train_btn = gr.Button("๐Ÿ”„ Train Model & Backtest")
134
+ train_out = gr.JSON(label="Hasil Training & Backtest")
135
+ with gr.Row():
136
+ pred_btn = gr.Button("๐Ÿ”ฎ Prediksi Pergerakan Berikutnya")
137
+ pred_out = gr.JSON(label="Sinyal Prediksi")
138
+ train_btn.click(run_train, outputs=train_out)
139
+ pred_btn.click(run_predict, outputs=pred_out)
140
+ gr.Markdown("โš ๏ธ *File model bersifat temporary. Retrain otomatis jika Space restart.*")
141
+
142
+ # โš™๏ธ Mount Gradio ke FastAPI
143
+ app = gr.mount_gradio_app(app, demo, path="/")