daafa999 commited on
Commit
e7cbe26
Β·
verified Β·
1 Parent(s): 8e0fe91

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +85 -31
app.py CHANGED
@@ -1,7 +1,7 @@
1
  # -*- coding: utf-8 -*-
2
  """
3
  HUGGING FACE SPACE: BINANCE SPOT TRADING SIMULATOR
4
- Gradio Dashboard | No API Key Required | Rule-Based Virtual Execution
5
  """
6
  import os, time, json, threading, logging, requests, pandas as pd
7
  from datetime import datetime, timezone
@@ -35,7 +35,8 @@ logging.basicConfig(level=logging.INFO, format="%(message)s")
35
  # ==========================================
36
  def load_state():
37
  if os.path.exists(STATE_FILE):
38
- with open(STATE_FILE) as f: return json.load(f)
 
39
  return {
40
  "date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
41
  "balance": 1000.0, "trades": 0, "daily_pnl": 0.0,
@@ -43,7 +44,8 @@ def load_state():
43
  }
44
 
45
  def save_state(s):
46
- with open(STATE_FILE, "w") as f: json.dump(s, f, indent=2)
 
47
 
48
  # ==========================================
49
  # PUBLIC BINANCE FETCHER (NO API KEY)
@@ -56,7 +58,8 @@ def fetch_klines(sym, interval="1h", limit=100):
56
  d = r.json()
57
  df = pd.DataFrame(d, columns=["ts","o","h","l","c","v"] + ["_"]*6)
58
  df["ts"] = pd.to_datetime(df["ts"], unit="ms")
59
- for c in "o h l c v".split(): df[c] = df[c].astype(float)
 
60
  return df[["ts","o","h","l","c","v"]]
61
  except Exception as e:
62
  return pd.DataFrame()
@@ -65,11 +68,13 @@ def fetch_klines(sym, interval="1h", limit=100):
65
  # INDIKATOR & AI SCORE
66
  # ==========================================
67
  def add_ema(df, periods=[20, 50]):
68
- for p in periods: df[f"ema_{p}"] = df["c"].ewm(span=p, adjust=False).mean()
 
69
  return df
70
 
71
  def calc_ai_score(df):
72
- if len(df) < 50: return 0
 
73
  trend = 30 if df["ema_20"].iloc[-1] > df["ema_50"].iloc[-1] else 0
74
  vol_ma = df["v"].rolling(20).mean().iloc[-1]
75
  vol_ratio = df["v"].iloc[-1] / max(vol_ma, 1e-9)
@@ -81,11 +86,13 @@ def calc_ai_score(df):
81
  # TELEGRAM ALERT
82
  # ==========================================
83
  def tg_send(txt):
84
- if not CONFIG["TELEGRAM_TOKEN"] or not CONFIG["TELEGRAM_CHAT_ID"]: return
 
85
  try:
86
  requests.post(f"https://api.telegram.org/bot{CONFIG['TELEGRAM_TOKEN']}/sendMessage",
87
  json={"chat_id": CONFIG["TELEGRAM_CHAT_ID"], "text": txt, "parse_mode": "HTML"}, timeout=5)
88
- except: pass
 
89
 
90
  def tg_fmt(emoji, title, msg):
91
  return f"{emoji} <b>{title}</b>\n{msg}\n⏰ {datetime.now(timezone.utc).strftime('%H:%M UTC')}"
@@ -102,7 +109,8 @@ class SimEngine:
102
  self._thread = None
103
 
104
  def start(self):
105
- if self._running: return "⚠️ Sudah berjalan"
 
106
  self._running = True
107
  self._stop.clear()
108
  self._thread = threading.Thread(target=self._loop, daemon=True)
@@ -123,9 +131,12 @@ class SimEngine:
123
 
124
  def _can_trade(self):
125
  with self._lock:
126
- if self.state["trades"] >= CONFIG["MAX_DAILY_TRADES"]: return False, "Max 2 trade/hari"
127
- if self.state["daily_pnl"] <= CONFIG["DAILY_LOSS_LIMIT"]: return False, "Daily Loss Limit -3%"
128
- if self.state["daily_pnl"] >= CONFIG["DAILY_PROFIT_LOCK"]: return False, "Profit Lock +5%"
 
 
 
129
  return True, "OK"
130
 
131
  def _check_positions(self):
@@ -133,10 +144,12 @@ class SimEngine:
133
  for p in self.state["positions"][:]:
134
  sym = p["symbol"]
135
  df = fetch_klines(sym, CONFIG["INTERVAL"], limit=1)
136
- if df.empty: continue
 
137
  price = df["c"].iloc[-1]
138
  pnl = (price - p["entry"]) / p["entry"]
139
- if price > p["trail_h"]: p["trail_h"] = price
 
140
 
141
  if pnl >= CONFIG["TP1"] and not p["tp1"]:
142
  p["tp1"] = True
@@ -148,7 +161,6 @@ class SimEngine:
148
  tg_send(tg_fmt("πŸ’°", f"TP2 {sym}", f"+5% | 30% closed | Virtual"))
149
 
150
  trail_sl = p["trail_h"] * (1 - CONFIG["TRAIL_PCT"])
151
- exit_price = price
152
  if price <= max(trail_sl, p["sl"]):
153
  close_share = CONFIG["TP3_SHARE"] if p["tp2"] else 1.0
154
  pnl_real = pnl * close_share
@@ -174,18 +186,22 @@ class SimEngine:
174
  self._reset_daily()
175
  ok, reason = self._can_trade()
176
  if not ok:
177
- time.sleep(60); continue
 
178
 
179
  # BTC Filter
180
  btc = add_ema(fetch_klines("BTCUSDT", CONFIG["INTERVAL"]))
181
  if btc.empty or btc["ema_20"].iloc[-1] <= btc["ema_50"].iloc[-1]:
182
- time.sleep(60); continue
 
183
 
184
  # Scan Watchlist
185
  for sym in CONFIG["WATCHLIST"]:
186
- if sym == "BTCUSDT" or self._stop.is_set(): continue
 
187
  df = add_ema(fetch_klines(sym, CONFIG["INTERVAL"]))
188
- if df.empty: continue
 
189
  score = calc_ai_score(df)
190
  vol_ma = df["v"].rolling(20).mean().iloc[-1]
191
  vol_r = df["v"].iloc[-1] / max(vol_ma, 1e-9)
@@ -219,16 +235,17 @@ class SimEngine:
219
  ts = datetime.now().strftime("%H:%M:%S")
220
  with self._lock:
221
  self.state["logs"].append(f"[{ts}] {msg}")
222
- if len(self.state["logs"]) > 150: self.state["logs"] = self.state["logs"][-50:]
 
223
 
224
  def get_state(self):
225
  with self._lock:
226
- return json.loads(json.dumps(self.state)) # Deep copy
227
 
228
  engine = SimEngine()
229
 
230
  # ==========================================
231
- # GRADIO DASHBOARD
232
  # ==========================================
233
  def ui_update():
234
  s = engine.get_state()
@@ -240,22 +257,59 @@ def ui_update():
240
  f"Daily PnL: {s['daily_pnl']:.2%}",
241
  pos, logs)
242
 
243
- with gr.Blocks(title="Binance Spot Simulator") as demo:
 
 
 
 
 
244
  gr.Markdown("# πŸ“Š System Trading + Profit Management (SIMULATOR)")
245
  gr.Markdown("*βœ… Tanpa API Key | βœ… Data Publik Binance | βœ… Semua Rule Aktif | βœ… Telegram Ready*")
 
246
  with gr.Row():
247
- btn_start = gr.Button("β–Ά START SIMULATOR", variant="primary")
248
- btn_stop = gr.Button("⏹ STOP")
 
 
249
  with gr.Row():
250
  bal = gr.Textbox(label="πŸ’° Balance", value="Balance: $1000.00", interactive=False)
251
  trades = gr.Textbox(label="πŸ“ˆ Trades Hari Ini", value="Trades: 0/2", interactive=False)
252
  pnl = gr.Textbox(label="πŸ“Š Daily PnL", value="Daily PnL: 0.00%", interactive=False)
253
- pos_table = gr.Dataframe(headers=["Pair", "Entry", "Qty", "SL", "Status"], value=[], interactive=False, label="πŸ“ Open Positions")
254
- log_box = gr.Textbox(label="πŸ“ System Log", value="", lines=10, interactive=False)
 
 
 
 
 
 
 
 
 
 
255
 
256
- btn_start.click(lambda: engine.start(), outputs=None)
257
- btn_stop.click(lambda: engine.stop(), outputs=None)
258
- demo.load(ui_update, inputs=None, outputs=[bal, trades, pnl, pos_table, log_box], every=5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
  if __name__ == "__main__":
261
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
1
  # -*- coding: utf-8 -*-
2
  """
3
  HUGGING FACE SPACE: BINANCE SPOT TRADING SIMULATOR
4
+ βœ… Fixed: Gradio Timer Compatible | No API Key | Rule-Based
5
  """
6
  import os, time, json, threading, logging, requests, pandas as pd
7
  from datetime import datetime, timezone
 
35
  # ==========================================
36
  def load_state():
37
  if os.path.exists(STATE_FILE):
38
+ with open(STATE_FILE) as f:
39
+ return json.load(f)
40
  return {
41
  "date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
42
  "balance": 1000.0, "trades": 0, "daily_pnl": 0.0,
 
44
  }
45
 
46
  def save_state(s):
47
+ with open(STATE_FILE, "w") as f:
48
+ json.dump(s, f, indent=2)
49
 
50
  # ==========================================
51
  # PUBLIC BINANCE FETCHER (NO API KEY)
 
58
  d = r.json()
59
  df = pd.DataFrame(d, columns=["ts","o","h","l","c","v"] + ["_"]*6)
60
  df["ts"] = pd.to_datetime(df["ts"], unit="ms")
61
+ for c in "o h l c v".split():
62
+ df[c] = df[c].astype(float)
63
  return df[["ts","o","h","l","c","v"]]
64
  except Exception as e:
65
  return pd.DataFrame()
 
68
  # INDIKATOR & AI SCORE
69
  # ==========================================
70
  def add_ema(df, periods=[20, 50]):
71
+ for p in periods:
72
+ df[f"ema_{p}"] = df["c"].ewm(span=p, adjust=False).mean()
73
  return df
74
 
75
  def calc_ai_score(df):
76
+ if len(df) < 50:
77
+ return 0
78
  trend = 30 if df["ema_20"].iloc[-1] > df["ema_50"].iloc[-1] else 0
79
  vol_ma = df["v"].rolling(20).mean().iloc[-1]
80
  vol_ratio = df["v"].iloc[-1] / max(vol_ma, 1e-9)
 
86
  # TELEGRAM ALERT
87
  # ==========================================
88
  def tg_send(txt):
89
+ if not CONFIG["TELEGRAM_TOKEN"] or not CONFIG["TELEGRAM_CHAT_ID"]:
90
+ return
91
  try:
92
  requests.post(f"https://api.telegram.org/bot{CONFIG['TELEGRAM_TOKEN']}/sendMessage",
93
  json={"chat_id": CONFIG["TELEGRAM_CHAT_ID"], "text": txt, "parse_mode": "HTML"}, timeout=5)
94
+ except:
95
+ pass
96
 
97
  def tg_fmt(emoji, title, msg):
98
  return f"{emoji} <b>{title}</b>\n{msg}\n⏰ {datetime.now(timezone.utc).strftime('%H:%M UTC')}"
 
109
  self._thread = None
110
 
111
  def start(self):
112
+ if self._running:
113
+ return "⚠️ Sudah berjalan"
114
  self._running = True
115
  self._stop.clear()
116
  self._thread = threading.Thread(target=self._loop, daemon=True)
 
131
 
132
  def _can_trade(self):
133
  with self._lock:
134
+ if self.state["trades"] >= CONFIG["MAX_DAILY_TRADES"]:
135
+ return False, "Max 2 trade/hari"
136
+ if self.state["daily_pnl"] <= CONFIG["DAILY_LOSS_LIMIT"]:
137
+ return False, "Daily Loss Limit -3%"
138
+ if self.state["daily_pnl"] >= CONFIG["DAILY_PROFIT_LOCK"]:
139
+ return False, "Profit Lock +5%"
140
  return True, "OK"
141
 
142
  def _check_positions(self):
 
144
  for p in self.state["positions"][:]:
145
  sym = p["symbol"]
146
  df = fetch_klines(sym, CONFIG["INTERVAL"], limit=1)
147
+ if df.empty:
148
+ continue
149
  price = df["c"].iloc[-1]
150
  pnl = (price - p["entry"]) / p["entry"]
151
+ if price > p["trail_h"]:
152
+ p["trail_h"] = price
153
 
154
  if pnl >= CONFIG["TP1"] and not p["tp1"]:
155
  p["tp1"] = True
 
161
  tg_send(tg_fmt("πŸ’°", f"TP2 {sym}", f"+5% | 30% closed | Virtual"))
162
 
163
  trail_sl = p["trail_h"] * (1 - CONFIG["TRAIL_PCT"])
 
164
  if price <= max(trail_sl, p["sl"]):
165
  close_share = CONFIG["TP3_SHARE"] if p["tp2"] else 1.0
166
  pnl_real = pnl * close_share
 
186
  self._reset_daily()
187
  ok, reason = self._can_trade()
188
  if not ok:
189
+ time.sleep(60)
190
+ continue
191
 
192
  # BTC Filter
193
  btc = add_ema(fetch_klines("BTCUSDT", CONFIG["INTERVAL"]))
194
  if btc.empty or btc["ema_20"].iloc[-1] <= btc["ema_50"].iloc[-1]:
195
+ time.sleep(60)
196
+ continue
197
 
198
  # Scan Watchlist
199
  for sym in CONFIG["WATCHLIST"]:
200
+ if sym == "BTCUSDT" or self._stop.is_set():
201
+ continue
202
  df = add_ema(fetch_klines(sym, CONFIG["INTERVAL"]))
203
+ if df.empty:
204
+ continue
205
  score = calc_ai_score(df)
206
  vol_ma = df["v"].rolling(20).mean().iloc[-1]
207
  vol_r = df["v"].iloc[-1] / max(vol_ma, 1e-9)
 
235
  ts = datetime.now().strftime("%H:%M:%S")
236
  with self._lock:
237
  self.state["logs"].append(f"[{ts}] {msg}")
238
+ if len(self.state["logs"]) > 150:
239
+ self.state["logs"] = self.state["logs"][-50:]
240
 
241
  def get_state(self):
242
  with self._lock:
243
+ return json.loads(json.dumps(self.state))
244
 
245
  engine = SimEngine()
246
 
247
  # ==========================================
248
+ # GRADIO DASHBOARD (FIXED)
249
  # ==========================================
250
  def ui_update():
251
  s = engine.get_state()
 
257
  f"Daily PnL: {s['daily_pnl']:.2%}",
258
  pos, logs)
259
 
260
+ def refresh_logs():
261
+ """Fungsi khusus untuk timer refresh log saja"""
262
+ s = engine.get_state()
263
+ return "\n".join(s["logs"][-20:])
264
+
265
+ with gr.Blocks(title="Binance Spot Simulator", css=".footer {text-align: center; margin-top: 20px;}") as demo:
266
  gr.Markdown("# πŸ“Š System Trading + Profit Management (SIMULATOR)")
267
  gr.Markdown("*βœ… Tanpa API Key | βœ… Data Publik Binance | βœ… Semua Rule Aktif | βœ… Telegram Ready*")
268
+
269
  with gr.Row():
270
+ btn_start = gr.Button("β–Ά START SIMULATOR", variant="primary", scale=1)
271
+ btn_stop = gr.Button("⏹ STOP", variant="stop", scale=1)
272
+ btn_reset = gr.Button("πŸ”„ RESET STATE", variant="secondary", scale=1)
273
+
274
  with gr.Row():
275
  bal = gr.Textbox(label="πŸ’° Balance", value="Balance: $1000.00", interactive=False)
276
  trades = gr.Textbox(label="πŸ“ˆ Trades Hari Ini", value="Trades: 0/2", interactive=False)
277
  pnl = gr.Textbox(label="πŸ“Š Daily PnL", value="Daily PnL: 0.00%", interactive=False)
278
+
279
+ pos_table = gr.Dataframe(
280
+ headers=["Pair", "Entry", "Qty", "SL", "Status"],
281
+ value=[],
282
+ interactive=False,
283
+ label="πŸ“ Open Positions",
284
+ wrap=True
285
+ )
286
+
287
+ log_box = gr.Textbox(label="πŸ“ System Log", value="", lines=10, interactive=False, max_lines=20)
288
+
289
+ gr.Markdown('<div class="footer">πŸ” Mode: SIMULATOR (Virtual Trading) | Data: Binance Public API</div>', elem_classes="footer")
290
 
291
+ # Event handlers
292
+ btn_start.click(lambda: engine.start(), inputs=None, outputs=None)
293
+ btn_stop.click(lambda: engine.stop(), inputs=None, outputs=None)
294
+
295
+ def do_reset():
296
+ engine.state = {
297
+ "date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
298
+ "balance": 1000.0, "trades": 0, "daily_pnl": 0.0,
299
+ "positions": [], "logs": ["πŸ”„ State di-reset"]
300
+ }
301
+ save_state(engine.state)
302
+ return "Balance: $1000.00", "Trades: 0/2", "Daily PnL: 0.00%", [], "πŸ”„ State di-reset"
303
+
304
+ btn_reset.click(do_reset, inputs=None, outputs=[bal, trades, pnl, pos_table, log_box])
305
+
306
+ # βœ… FIXED: Gunakan gr.Timer untuk auto-refresh (Gradio 4.x compatible)
307
+ timer = gr.Timer(value=5) # Refresh setiap 5 detik
308
+ timer.tick(ui_update, inputs=None, outputs=[bal, trades, pnl, pos_table, log_box])
309
+
310
+ # Refresh log lebih sering agar real-time
311
+ log_timer = gr.Timer(value=2)
312
+ log_timer.tick(refresh_logs, inputs=None, outputs=log_box)
313
 
314
  if __name__ == "__main__":
315
+ demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860")))