daafa999 commited on
Commit
252fab2
·
verified ·
1 Parent(s): 0ba15fc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +60 -140
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import gradio as gr
 
2
  import ccxt
3
  import pandas as pd
4
  import pandas_ta as ta
@@ -7,205 +8,124 @@ from sklearn.ensemble import RandomForestClassifier
7
  from sklearn.preprocessing import MinMaxScaler
8
  import joblib
9
  import os
10
- import json
11
- from datetime import datetime
12
 
13
- # Konfigurasi
 
 
14
  SYMBOL = "ETH/USDT"
15
  TIMEFRAME = "4h"
16
- FEE = 0.0015
17
- WINDOW = 60
18
  MODEL_PATH = "rf_model.pkl"
19
  SCALER_PATH = "scaler.pkl"
20
 
 
21
  def fetch_data(symbol: str, timeframe: str, limit: int = 2000) -> pd.DataFrame:
22
- """Ambil data dari Binance"""
23
- try:
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
- except Exception as e:
30
- return pd.DataFrame()
31
 
 
32
  def prepare_dataset(df: pd.DataFrame):
33
- """Siapkan dataset dengan indikator teknikal"""
34
- if df.empty:
35
- return None, None, None, None, None
36
-
37
  df = df.copy()
38
-
39
- try:
40
- df.ta.rsi(length=14, append=True)
41
- df.ta.macd(fast=12, slow=26, signal=9, append=True)
42
- df.ta.bbands(length=20, append=True)
43
- df.ta.cci(length=14, append=True)
44
- except Exception as e:
45
- return None, None, None, None, None
46
-
47
  df.dropna(inplace=True)
48
-
 
49
  df["next_close"] = df["close"].shift(-1)
50
  df["label"] = (df["next_close"] > df["close"] * (1 + FEE)).astype(int)
51
  df.dropna(inplace=True)
52
-
 
53
  X_list, y_list, idx_list = [], [], []
54
- feature_cols = [col for col in df.columns if col not in ["timestamp", "next_close", "label"]]
55
-
56
  for i in range(WINDOW, len(df)):
57
- window = df.iloc[i-WINDOW:i][feature_cols]
58
  X_list.append(window.values.flatten())
59
  y_list.append(df.iloc[i]["label"])
60
  idx_list.append(i)
61
-
62
  X = np.array(X_list)
63
  y = np.array(y_list)
64
  scaler = MinMaxScaler()
65
  X_scaled = scaler.fit_transform(X)
66
-
67
  return X_scaled, y, idx_list, df, scaler
68
 
69
- def train_model():
70
- """Training model dan backtest"""
 
71
  if os.path.exists(MODEL_PATH):
72
- return "Model sudah ada. Hapus file model untuk retrain."
73
-
74
  df = fetch_data(SYMBOL, TIMEFRAME)
75
- if df.empty:
76
- return "❌ Gagal mengambil data"
77
-
78
  X, y, idx, df_full, scaler = prepare_dataset(df)
79
- if X is None:
80
- return "❌ Gagal menyiapkan dataset"
81
-
82
  split = int(len(X) * 0.95)
83
  X_train, X_test = X[:split], X[split:]
84
  y_train, y_test = y[:split], y[split:]
85
-
86
- model = RandomForestClassifier(n_estimators=500, max_depth=None, random_state=42, n_jobs=-1)
 
87
  model.fit(X_train, y_train)
88
  acc = float(model.score(X_test, y_test))
89
-
90
  joblib.dump(model, MODEL_PATH)
91
  joblib.dump(scaler, SCALER_PATH)
92
-
93
- # Backtest
94
  test_indices = idx[split:]
95
  df_test = df_full.loc[test_indices].copy()
96
  df_test["pred"] = model.predict(X_test)
97
-
98
  pnl_list = []
99
  for i in range(len(df_test) - 1):
100
  if df_test.iloc[i]["pred"] == 1:
101
  entry = df_test.iloc[i]["close"]
102
  exit_ = df_test.iloc[i + 1]["close"]
103
  ret = (exit_ - entry) / entry
104
- pnl = ret - FEE
105
  pnl_list.append(pnl)
106
-
107
  wins = sum(1 for p in pnl_list if p > 0)
108
  losses = len(pnl_list) - wins
109
  gross_profit = sum(p for p in pnl_list if p > 0)
110
  gross_loss = sum(abs(p) for p in pnl_list if p <= 0)
111
  profit_factor = gross_profit / gross_loss if gross_loss > 0 else float("inf")
112
-
113
- result = f"""
114
- ✅ **Training Selesai!**
115
-
116
- 📊 **Hasil Backtest (66 hari):**
117
- - Accuracy: {acc:.2%}
118
- - Profit Factor: {profit_factor:.3f}
119
- - Net Profit: {sum(pnl_list)*100:.2f}%
120
- - Win Rate: {wins/len(pnl_list)*100:.2f}%
121
- - Total Trades: {len(pnl_list)}
122
- - Wins: {wins} | Losses: {losses}
123
- """
124
- return result
125
 
 
 
 
 
 
 
 
 
 
 
 
126
  def predict_next_move():
127
- """Prediksi pergerakan harga berikutnya"""
128
  if not os.path.exists(MODEL_PATH):
129
- return "❌ Model belum dilatih. Klik tombol 'Train Model' terlebih dahulu."
130
 
131
  df = fetch_data(SYMBOL, TIMEFRAME, limit=WINDOW + 10)
132
  _, _, _, df_full, scaler = prepare_dataset(df)
133
 
134
- if df_full is None or len(df_full) < WINDOW:
135
- return "❌ Data tidak cukup untuk prediksi"
136
-
137
- feature_cols = [col for col in df_full.columns if col not in ["timestamp", "next_close", "label"]]
138
- latest_window = df_full.iloc[-WINDOW:][feature_cols].values.flatten().reshape(1, -1)
139
  latest_scaled = scaler.transform(latest_window)
140
 
141
  model = joblib.load(MODEL_PATH)
142
  pred = int(model.predict(latest_scaled)[0])
143
- proba = model.predict_proba(latest_scaled)[0]
144
-
145
- signal = "🟢 **BUY** (Hold 4 jam)" if pred == 1 else "🔴 **HOLD/SELL**"
146
-
147
- result = f"""
148
- 📈 **Prediksi {SYMBOL} - {TIMEFRAME}**
149
-
150
- Signal: {signal}
151
-
152
- **Probabilitas:**
153
- - Naik (>0.15%): {proba[1]*100:.2f}%
154
- - Turun/Stagnan: {proba[0]*100:.2f}%
155
- - Confidence: {max(proba)*100:.2f}%
156
-
157
- **Waktu Prediksi:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
158
- """
159
- return result
160
-
161
- def get_price_chart():
162
- """Generate price chart data"""
163
- df = fetch_data(SYMBOL, TIMEFRAME, limit=200)
164
- if df.empty:
165
- return None
166
-
167
- # Buat simple chart data
168
- chart_data = df[['timestamp', 'close']].tail(100).copy()
169
- return chart_data
170
-
171
- # Gradio Interface
172
- with gr.Blocks(title="Crypto ML Trading Strategy", theme=gr.themes.Soft()) as demo:
173
- gr.Markdown("""
174
- # Crypto ML Trading Strategy
175
- **Paper Reference:** [2105.06827v3](https://arxiv.org/abs/2105.06827)
176
-
177
- Prediksi arah harga cryptocurrency menggunakan Machine Learning (Random Forest) dengan indikator teknikal.
178
- """)
179
-
180
- with gr.Row():
181
- with gr.Column():
182
- gr.Markdown("### 📊 Kontrol")
183
- train_btn = gr.Button("🎯 Train Model", variant="primary")
184
- predict_btn = gr.Button("🔮 Prediksi Sekarang")
185
- chart_btn = gr.Button("📈 Lihat Chart")
186
-
187
- with gr.Column():
188
- gr.Markdown("### 💡 Informasi")
189
- gr.Markdown("""
190
- - **Asset:** ETH/USDT
191
- - **Timeframe:** 4 Jam
192
- - **Model:** Random Forest
193
- - **Threshold:** 0.15% (fee Binance)
194
- - **Data:** 2017-2021
195
- """)
196
-
197
- output = gr.Textbox(label="Hasil", lines=15)
198
- chart_output = gr.Dataframe(label="Price Data")
199
-
200
- train_btn.click(fn=train_model, inputs=None, outputs=output)
201
- predict_btn.click(fn=predict_next_move, inputs=None, outputs=output)
202
- chart_btn.click(fn=get_price_chart, inputs=None, outputs=chart_output)
203
 
204
- gr.Markdown("""
205
- ---
206
- **Disclaimer:** Ini adalah proof-of-concept akademis. Bukan saran finansial.
207
- Trading cryptocurrency berisiko tinggi.
208
- """)
209
-
210
- if __name__ == "__main__":
211
- demo.launch()
 
1
  import gradio as gr
2
+ from fastapi import FastAPI, HTTPException
3
  import ccxt
4
  import pandas as pd
5
  import pandas_ta as ta
 
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
 
123
  model = joblib.load(MODEL_PATH)
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
+ }