babaTEEpe commited on
Commit
0bb0a33
Β·
verified Β·
1 Parent(s): ccb0fbe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +114 -3
app.py CHANGED
@@ -43,7 +43,55 @@ symbol_volumes = {
43
  "EURUSD": 0.01,
44
  "_DEFAULT_FOREX": 0.01, # catch-all for any forex pair not explicitly listed
45
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  logger.info(f"Per-symbol volumes initialized: {symbol_volumes}")
 
47
 
48
  # Global caches to eliminate latency of symbol resolution and limits querying
49
  SYMBOL_ID_CACHE = {} # symbol_name_upper -> symbol_id
@@ -782,20 +830,44 @@ async def dashboard(request: Request):
782
  if sym not in symbol_volumes:
783
  continue
784
  vol = symbol_volumes[sym]
 
 
 
785
  display_name = "Default Forex" if sym == "_DEFAULT_FOREX" else sym
786
  row_bg = "#0f172a" if display_order.index(sym) % 2 == 0 else "#111827"
 
 
 
 
 
 
 
 
787
  volume_rows_html += f'''
788
  <tr style="border-bottom: 1px solid #1e293b; background: {row_bg};">
789
  <td style="padding: 8px 12px; color: #e2e8f0; font-size: 14px; font-weight: 600;">{display_name}</td>
790
  <td style="padding: 8px 12px;">
791
- <form action="/set-volume" method="post" style="display: flex; gap: 8px; align-items: center;">
792
  <input type="hidden" name="symbol" value="{sym}">
793
  <input type="text" name="volume" value="{vol:.2f}"
794
  style="background: #1e293b; border: 1px solid #334155; border-radius: 6px; padding: 6px 10px; color: #e2e8f0; font-size: 14px; width: 80px;">
795
  <button type="submit" class="btn" style="padding: 6px 14px; font-size: 12px;">Update</button>
796
  </form>
797
  </td>
798
- <td style="padding: 8px 12px; color: #64748b; font-size: 11px;">{vol:.2f} lots</td>
 
 
 
 
 
 
 
 
 
 
 
 
 
799
  </tr>'''
800
 
801
  # 2. Construct OAuth status card HTML
@@ -834,7 +906,7 @@ async def dashboard(request: Request):
834
  <tr style="border-bottom: 1px solid #334155;">
835
  <th style="text-align: left; padding: 8px 12px; color: #94a3b8; font-size: 13px;">Symbol</th>
836
  <th style="text-align: left; padding: 8px 12px; color: #94a3b8; font-size: 13px;">Lot Size</th>
837
- <th style="padding: 8px 12px;"></th>
838
  </tr>
839
  </thead>
840
  <tbody>
@@ -1151,6 +1223,7 @@ async def set_volume(request: Request, symbol: str = Form(...), volume: str = Fo
1151
  vol_float = float(volume.strip())
1152
  symbol_upper = symbol.strip().upper()
1153
  symbol_volumes[symbol_upper] = vol_float
 
1154
  logger.info(f"Trade volume for symbol '{symbol_upper}' updated to {vol_float} lots.")
1155
  except Exception as e:
1156
  logger.error(f"Invalid volume update for symbol '{symbol}': {volume}. Error: {e}")
@@ -1164,6 +1237,31 @@ async def set_volume(request: Request, symbol: str = Form(...), volume: str = Fo
1164
  redirect_url = f"{protocol}://{host}/"
1165
  return RedirectResponse(redirect_url, status_code=303)
1166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1167
  @app.post("/webhook")
1168
  async def webhook(request: Request):
1169
  # Parse Webhook JSON data
@@ -1178,6 +1276,19 @@ async def webhook(request: Request):
1178
 
1179
  if action not in ["BUY", "SELL", "STOP-TRADE", "CLOSE", "ANCHOR"]:
1180
  raise HTTPException(status_code=400, detail="Invalid action. Must be BUY, SELL, STOP-TRADE, CLOSE, or ANCHOR")
 
 
 
 
 
 
 
 
 
 
 
 
 
1181
 
1182
  # Load and refresh tokens
1183
  tokens = get_valid_tokens()
 
43
  "EURUSD": 0.01,
44
  "_DEFAULT_FOREX": 0.01, # catch-all for any forex pair not explicitly listed
45
  }
46
+
47
+ # Per-symbol BUY/SELL toggle (controlled from dashboard, NOT TradingView)
48
+ symbol_status = {
49
+ "NAS100": {"buy": True, "sell": True},
50
+ "NAS100m": {"buy": True, "sell": True},
51
+ "USTEC": {"buy": True, "sell": True},
52
+ "US500": {"buy": True, "sell": True},
53
+ "US500m": {"buy": True, "sell": True},
54
+ "US30": {"buy": True, "sell": True},
55
+ "US30m": {"buy": True, "sell": True},
56
+ "BTCUSD": {"buy": True, "sell": True},
57
+ "EURUSD": {"buy": True, "sell": True},
58
+ "_DEFAULT_FOREX": {"buy": True, "sell": True},
59
+ }
60
+
61
+ CONFIG_FILE = "bot_config.json"
62
+
63
+ def load_config():
64
+ """Load persisted volumes + status toggles from disk."""
65
+ global symbol_volumes, symbol_status
66
+ if os.path.exists(CONFIG_FILE):
67
+ try:
68
+ with open(CONFIG_FILE, "r") as f_conf:
69
+ config = json.load(f_conf)
70
+ if "volumes" in config:
71
+ symbol_volumes.update(config["volumes"])
72
+ if "status" in config:
73
+ for k, v in config["status"].items():
74
+ if k in symbol_status:
75
+ symbol_status[k].update(v)
76
+ else:
77
+ symbol_status[k] = v
78
+ logger.info("Configuration loaded from bot_config.json")
79
+ except Exception as e:
80
+ logger.error(f"Error loading bot_config.json: {e}")
81
+
82
+ def save_config():
83
+ """Persist current volumes + status toggles to disk."""
84
+ try:
85
+ config = {"volumes": symbol_volumes, "status": symbol_status}
86
+ with open(CONFIG_FILE, "w") as f_conf:
87
+ json.dump(config, f_conf, indent=4)
88
+ logger.info("Configuration saved to bot_config.json")
89
+ except Exception as e:
90
+ logger.error(f"Error saving bot_config.json: {e}")
91
+
92
+ load_config()
93
  logger.info(f"Per-symbol volumes initialized: {symbol_volumes}")
94
+ logger.info(f"Per-symbol statuses initialized: {symbol_status}")
95
 
96
  # Global caches to eliminate latency of symbol resolution and limits querying
97
  SYMBOL_ID_CACHE = {} # symbol_name_upper -> symbol_id
 
830
  if sym not in symbol_volumes:
831
  continue
832
  vol = symbol_volumes[sym]
833
+ status = symbol_status.get(sym, {"buy": True, "sell": True})
834
+ buy_on = status.get("buy", True)
835
+ sell_on = status.get("sell", True)
836
  display_name = "Default Forex" if sym == "_DEFAULT_FOREX" else sym
837
  row_bg = "#0f172a" if display_order.index(sym) % 2 == 0 else "#111827"
838
+ # BUY toggle button: green if ON, grey if OFF
839
+ buy_color = "#00E676" if buy_on else "#475569"
840
+ buy_text = "BUY βœ“" if buy_on else "BUY βœ—"
841
+ buy_val = "false" if buy_on else "true" # clicking toggles to opposite
842
+ # SELL toggle button: red if ON, grey if OFF
843
+ sell_color = "#FF5252" if sell_on else "#475569"
844
+ sell_text = "SELL βœ“" if sell_on else "SELL βœ—"
845
+ sell_val = "false" if sell_on else "true"
846
  volume_rows_html += f'''
847
  <tr style="border-bottom: 1px solid #1e293b; background: {row_bg};">
848
  <td style="padding: 8px 12px; color: #e2e8f0; font-size: 14px; font-weight: 600;">{display_name}</td>
849
  <td style="padding: 8px 12px;">
850
+ <form action="/set-volume" method="post" style="display: inline-flex; gap: 8px; align-items: center;">
851
  <input type="hidden" name="symbol" value="{sym}">
852
  <input type="text" name="volume" value="{vol:.2f}"
853
  style="background: #1e293b; border: 1px solid #334155; border-radius: 6px; padding: 6px 10px; color: #e2e8f0; font-size: 14px; width: 80px;">
854
  <button type="submit" class="btn" style="padding: 6px 14px; font-size: 12px;">Update</button>
855
  </form>
856
  </td>
857
+ <td style="padding: 8px 12px;">
858
+ <form action="/toggle-status" method="post" style="display: inline-flex; gap: 6px;">
859
+ <input type="hidden" name="symbol" value="{sym}">
860
+ <input type="hidden" name="direction" value="buy">
861
+ <input type="hidden" name="new_value" value="{buy_val}">
862
+ <button type="submit" style="background: {buy_color}; color: white; border: none; border-radius: 6px; padding: 6px 12px; font-size: 12px; font-weight: 700; cursor: pointer;">{buy_text}</button>
863
+ </form>
864
+ <form action="/toggle-status" method="post" style="display: inline-flex; gap: 6px;">
865
+ <input type="hidden" name="symbol" value="{sym}">
866
+ <input type="hidden" name="direction" value="sell">
867
+ <input type="hidden" name="new_value" value="{sell_val}">
868
+ <button type="submit" style="background: {sell_color}; color: white; border: none; border-radius: 6px; padding: 6px 12px; font-size: 12px; font-weight: 700; cursor: pointer;">{sell_text}</button>
869
+ </form>
870
+ </td>
871
  </tr>'''
872
 
873
  # 2. Construct OAuth status card HTML
 
906
  <tr style="border-bottom: 1px solid #334155;">
907
  <th style="text-align: left; padding: 8px 12px; color: #94a3b8; font-size: 13px;">Symbol</th>
908
  <th style="text-align: left; padding: 8px 12px; color: #94a3b8; font-size: 13px;">Lot Size</th>
909
+ <th style="text-align: left; padding: 8px 12px; color: #94a3b8; font-size: 13px;">Allow Trade</th>
910
  </tr>
911
  </thead>
912
  <tbody>
 
1223
  vol_float = float(volume.strip())
1224
  symbol_upper = symbol.strip().upper()
1225
  symbol_volumes[symbol_upper] = vol_float
1226
+ save_config()
1227
  logger.info(f"Trade volume for symbol '{symbol_upper}' updated to {vol_float} lots.")
1228
  except Exception as e:
1229
  logger.error(f"Invalid volume update for symbol '{symbol}': {volume}. Error: {e}")
 
1237
  redirect_url = f"{protocol}://{host}/"
1238
  return RedirectResponse(redirect_url, status_code=303)
1239
 
1240
+ @app.post("/toggle-status")
1241
+ async def toggle_status(request: Request, symbol: str = Form(...), direction: str = Form(...), new_value: str = Form(...)):
1242
+ """Instantly toggle Allow BUY or Allow SELL for a symbol from the dashboard."""
1243
+ global symbol_status
1244
+ symbol_upper = symbol.strip().upper()
1245
+ dir_lower = direction.strip().lower()
1246
+ new_bool = new_value.strip().lower() == "true"
1247
+
1248
+ if symbol_upper not in symbol_status:
1249
+ symbol_status[symbol_upper] = {"buy": True, "sell": True}
1250
+ symbol_status[symbol_upper][dir_lower] = new_bool
1251
+ save_config()
1252
+
1253
+ state_str = "ENABLED" if new_bool else "DISABLED"
1254
+ logger.info(f"{dir_lower.upper()} {state_str} for {symbol_upper}")
1255
+
1256
+ space_host = os.environ.get("SPACE_HOST")
1257
+ if space_host:
1258
+ redirect_url = f"https://{space_host}/"
1259
+ else:
1260
+ host = request.headers.get("host", "")
1261
+ protocol = "https" if "hf.space" in host or request.headers.get("x-forwarded-proto") == "https" else "http"
1262
+ redirect_url = f"{protocol}://{host}/"
1263
+ return RedirectResponse(redirect_url, status_code=303)
1264
+
1265
  @app.post("/webhook")
1266
  async def webhook(request: Request):
1267
  # Parse Webhook JSON data
 
1276
 
1277
  if action not in ["BUY", "SELL", "STOP-TRADE", "CLOSE", "ANCHOR"]:
1278
  raise HTTPException(status_code=400, detail="Invalid action. Must be BUY, SELL, STOP-TRADE, CLOSE, or ANCHOR")
1279
+
1280
+ # ── Dashboard BUY/SELL toggle filter ──
1281
+ # If the user disabled BUY or SELL for this symbol on the dashboard, ignore it instantly
1282
+ if action in ["BUY", "SELL"]:
1283
+ status = symbol_status.get(ticker, symbol_status.get("_DEFAULT_FOREX", {"buy": True, "sell": True}))
1284
+ is_allowed = status.get(action.lower(), True)
1285
+ if not is_allowed:
1286
+ ignore_msg = f"{action} for {ticker} IGNORED β€” disabled on dashboard"
1287
+ logger.info(ignore_msg)
1288
+ WEBHOOK_LOGS.append({"time": datetime.now().strftime('%H:%M:%S'), "action": f"{action} BLOCKED", "color": "#94a3b8", "msg": ignore_msg})
1289
+ if len(WEBHOOK_LOGS) > 50:
1290
+ WEBHOOK_LOGS.pop(0)
1291
+ return {"status": "ignored", "message": ignore_msg}
1292
 
1293
  # Load and refresh tokens
1294
  tokens = get_valid_tokens()