max1949 commited on
Commit
d5db0e7
·
verified ·
1 Parent(s): 92e22fd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +130 -87
app.py CHANGED
@@ -1,3 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
1
  import discord
2
  from discord.ext import commands, tasks
3
  from google import genai
@@ -33,9 +44,6 @@ logger = logging.getLogger("BK_GTA_ARCHITECT_TITAN")
33
  # 1. 物理层:Gradio 哨兵与探针 (Gradio Sentinel)
34
  # ==============================================================================
35
  def get_status():
36
- """
37
- 提供给外部(如 UptimeRobot/Cron-Job)的心跳对账数据
38
- """
39
  now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
40
  bot_status = "OFFLINE"
41
  latency = "N/A"
@@ -45,7 +53,7 @@ def get_status():
45
  latency = f"{round(bot.latency * 1000, 2)}ms"
46
  except Exception as e:
47
  logger.error(f"Status check failed: {e}")
48
-
49
  return {
50
  "status": "BK-GTA-TITAN-ACTIVE",
51
  "logic_integrity": "100%",
@@ -63,7 +71,6 @@ def get_status():
63
  DISCORD_TOKEN = os.environ.get('DISCORD_TOKEN', '').strip()
64
  SESSION_ID = random.randint(10000, 99999)
65
 
66
- # [CORE] 8 密钥轮询加载器
67
  raw_keys = os.environ.get('GEMINI_KEYS_POOL', '')
68
  KEY_POOL = [k.strip() for k in raw_keys.split(',') if k.strip()]
69
  if not KEY_POOL:
@@ -73,30 +80,24 @@ if not KEY_POOL:
73
  if not KEY_POOL:
74
  logger.critical("❌ [CRITICAL] NO API KEYS FOUND! Please set GEMINI_KEYS_POOL.")
75
 
76
- # 权限位设置
77
  intents = discord.Intents.default()
78
  intents.message_content = True
79
  intents.members = True
80
  bot = commands.Bot(command_prefix='!', intents=intents)
81
 
82
- # 并发审计锁
83
  audit_lock = asyncio.Lock()
84
- # 优先使用持久化目录 /data
85
  DB_PATH = '/data/bk_gta_v62_unified.json' if os.path.exists('/data') else 'bk_gta_v62_unified.json'
86
 
87
  # ==============================================================================
88
  # 3. 状态引擎 (BKEngine Core)
89
  # ==============================================================================
90
  class BKEngine:
91
- """
92
- 负责量化资产的持久化、ELO 计算及风险对账
93
- """
94
  def __init__(self):
95
  self.db_file = DB_PATH
96
  self.data = self._load_db()
97
  self.pending_challenges = {}
98
  self.active_duels = {}
99
- self.active_bets = {}
100
 
101
  def _load_db(self):
102
  if os.path.exists(self.db_file):
@@ -105,7 +106,7 @@ class BKEngine:
105
  content = json.load(f)
106
  if "users" not in content: content["users"] = {}
107
  return content
108
- except Exception as e:
109
  logger.error(f"DB Load Failure: {e}")
110
  return self._init_schema()
111
  return self._init_schema()
@@ -115,7 +116,7 @@ class BKEngine:
115
  "version": "62.9-Titan",
116
  "users": {},
117
  "factions": {
118
- "BULLS": {"score": 0, "emoji": "🐂", "wins": 0},
119
  "BEARS": {"score": 0, "emoji": "🐻", "wins": 0}
120
  },
121
  "prize_pool": 1000000.0,
@@ -152,7 +153,6 @@ class BKEngine:
152
  if mdd is not None: u["mdd_record"] = mdd
153
  if sharpe is not None: u["sharpe_record"] = sharpe
154
  u["points"] += 50
155
- # 记录最近 5 次审计历史
156
  u["audit_history"].append({"s": score, "t": str(datetime.datetime.now())})
157
  u["audit_history"] = u["audit_history"][-5:]
158
  u["last_active"] = str(datetime.datetime.now())
@@ -163,17 +163,13 @@ class BKEngine:
163
  K = 32
164
  Rw, Rl = w["elo"], l["elo"]
165
  Ew = 1 / (1 + 10 ** ((Rl - Rw) / 400))
166
-
167
  w["elo"] = round(Rw + K * (1 - Ew))
168
  l["elo"] = round(Rl + K * (0 - Ew))
169
-
170
  w["win_streak"] += 1
171
  l["win_streak"] = 0
172
-
173
  if w["faction"] and w["faction"] in self.data["factions"]:
174
  self.data["factions"][w["faction"]]["score"] += 15
175
  self.data["factions"][w["faction"]]["wins"] += 1
176
-
177
  self.save_db()
178
  return w["elo"], l["elo"], w["win_streak"]
179
 
@@ -185,7 +181,7 @@ bk_engine = BKEngine()
185
  def safe_extract_float(text, key):
186
  try:
187
  patterns = [
188
- rf"{key}:\s*([-+]?\d*\.?\d+)",
189
  rf"{key}\s*=\s*([-+]?\d*\.?\d+)",
190
  rf"【{key}】\s*([-+]?\d*\.?\d+)"
191
  ]
@@ -205,11 +201,8 @@ def safe_extract_text(text, start, end=None):
205
  except: return "N/A"
206
 
207
  async def call_gemini(prompt, attachment=None):
208
- """
209
- [Core Logic] 8 密钥池高频轮询与故障自动迁移
210
- """
211
  if not KEY_POOL: return "ERROR: NO_KEYS"
212
-
213
  img_data = None
214
  if attachment:
215
  try: img_data = await attachment.read()
@@ -227,7 +220,7 @@ async def call_gemini(prompt, attachment=None):
227
  contents.append(types.Part.from_bytes(data=img_data, mime_type='image/png'))
228
 
229
  response = await loop.run_in_executor(
230
- None,
231
  functools.partial(client.models.generate_content, model="gemini-1.5-flash", contents=contents)
232
  )
233
  if response and response.text:
@@ -249,8 +242,7 @@ async def profile(ctx, member: discord.Member = None):
249
  t = member or ctx.author
250
  u = bk_engine.get_user(t.id)
251
  elo = u['elo']
252
-
253
- # 动态段位
254
  if elo < 1300: rank, color = "Iron 学徒 🗑️", 0x717d7e
255
  elif elo < 1500: rank, color = "Bronze 资深 🥉", 0xcd7f32
256
  elif elo < 1800: rank, color = "Silver 精英 🥈", 0xbdc3c7
@@ -261,7 +253,7 @@ async def profile(ctx, member: discord.Member = None):
261
  emb = discord.Embed(title=f"📊 Quant Profile: {t.display_name}", color=color)
262
  emb.add_field(name="Rating (ELO)", value=f"**{elo}** ({rank})", inline=True)
263
  emb.add_field(name="Assets", value=f"💰 **{u['points']}**", inline=True)
264
-
265
  f_info = u['faction'] or "Free Agent"
266
  f_emoji = bk_engine.data["factions"].get(f_info, {}).get("emoji", "🌐")
267
  emb.add_field(name="Faction", value=f"{f_emoji} {f_info}", inline=True)
@@ -272,7 +264,7 @@ async def profile(ctx, member: discord.Member = None):
272
  f"Max MDD: {u.get('mdd_record',0):.2f}%"
273
  )
274
  emb.add_field(name="Institutional Metrics", value=f"```yaml\n{stats}\n```", inline=False)
275
-
276
  if target_avatar := t.avatar:
277
  emb.set_thumbnail(url=target_avatar.url)
278
  emb.set_footer(text=f"Node: HuggingFace Titan | Session: {SESSION_ID}")
@@ -283,21 +275,21 @@ async def leaderboard(ctx):
283
  all_users = []
284
  for uid, d in bk_engine.data["users"].items():
285
  all_users.append({"id": uid, "elo": d["elo"], "score": d["highest_score"]})
286
-
287
  top = sorted(all_users, key=lambda x: x["elo"], reverse=True)[:10]
288
  desc = ""
289
  for idx, u in enumerate(top):
290
  icon = ["🥇","🥈","🥉"][idx] if idx < 3 else f"{idx+1}."
291
  desc += f"{icon} <@{u['id']}> : **{u['elo']}** (Peak: {u['score']:.1f})\n"
292
-
293
  emb = discord.Embed(title="🏆 BK-GTA Institutional Leaderboard", description=desc or "No data.", color=0xf1c40f)
294
-
295
  f_info = ""
296
  for k, v in bk_engine.data["factions"].items():
297
  f_info += f"{v['emoji']} {k}: {v['score']} pts "
298
  if f_info:
299
  emb.add_field(name="⚔️ Faction War", value=f_info, inline=False)
300
-
301
  await ctx.send(embed=emb)
302
 
303
  # ==============================================================================
@@ -307,7 +299,7 @@ async def leaderboard(ctx):
307
  async def news(ctx):
308
  if not ctx.message.attachments: return await ctx.send("❌ Error: 请上传情报截图。")
309
  m = await ctx.send("📡 **[Sentinel] Analyzing Macro Pulse...**")
310
-
311
  prompt = (
312
  "你是一名顶级对冲基金策略师。请解析截图内容:\n"
313
  "1. 评分 (SCORE): -10 (极大利空) 到 +10 (极大利多)。\n"
@@ -316,17 +308,17 @@ async def news(ctx):
316
  "格式必须为:\nSCORE: [v]\nSUMMARY: [v]\nTAKE: [v]"
317
  )
318
  res = await call_gemini(prompt, ctx.message.attachments[0])
319
-
320
  score = safe_extract_float(res, "SCORE")
321
  summary = safe_extract_text(res, "SUMMARY", "TAKE")
322
  take = safe_extract_text(res, "TAKE")
323
-
324
  color = 0x2ecc71 if score > 0 else 0xe74c3c if score < 0 else 0xbdc3c7
325
  emb = discord.Embed(title="📰 Macro Intelligence Analysis", color=color)
326
  emb.add_field(name="Sentiment Score", value=f"**{score:+.1f} / 10**", inline=True)
327
  emb.add_field(name="Core Catalyst", value=summary, inline=False)
328
  emb.add_field(name="Institutional View", value=f"```\n{take}\n```", inline=False)
329
-
330
  await m.delete()
331
  await ctx.send(embed=emb)
332
 
@@ -336,7 +328,7 @@ async def news(ctx):
336
  @bot.command()
337
  async def evaluate(ctx):
338
  if not ctx.message.attachments: return await ctx.send("❌ Error: 请上传回测或结算截图。")
339
-
340
  if audit_lock.locked():
341
  return await ctx.send("⚠️ **[System Busy]** 正在处理另一项审计,请稍后。")
342
 
@@ -349,9 +341,9 @@ async def evaluate(ctx):
349
  "H_ZH: 中文核心优势或致命风险概括。\n"
350
  "格式:\nRET: [v]\nMDD: [v]\nPF: [v]\nSHARPE: [v]\nTRADES: [v]\nS_BASE: [v]\nVIOLATION: [TRUE/FALSE]\nH_ZH: [v]"
351
  )
352
-
353
  res = await call_gemini(prompt, ctx.message.attachments[0])
354
-
355
  if "ERROR" in res:
356
  await m.delete()
357
  return await ctx.send("🔴 [Audit Crash] 引擎请求失败。")
@@ -363,13 +355,11 @@ async def evaluate(ctx):
363
  s_base = safe_extract_float(res, "S_BASE")
364
  violation = "TRUE" in res.upper()
365
 
366
- # 风控核心:MDD 否决制
367
  if violation or mdd > 35.0:
368
  s_final = 0.0
369
  status = "❌ REJECTED (High Risk)"
370
  color = 0xe74c3c
371
  else:
372
- # 复合评分逻辑:基础分 * 样本权重 * 风险权重
373
  weight_sample = math.log10(max(trades, 1)) / 1.5
374
  weight_risk = (sharpe / 2.0) if sharpe > 0 else 0.5
375
  s_final = min(max(s_base * weight_sample * weight_risk, 0.0), 100.0)
@@ -378,14 +368,14 @@ async def evaluate(ctx):
378
 
379
  bk_engine.update_audit(ctx.author.id, s_final, mdd, sharpe)
380
  highlights = safe_extract_text(res, "H_ZH")
381
-
382
  emb = discord.Embed(title="🛡️ Quantitative Audit Report", color=color)
383
  emb.add_field(name="Verdict", value=f"**{status}**", inline=False)
384
  metrics = f"Return: {ret}% | MDD: {mdd}%\nSharpe: {sharpe} | Trades: {trades}"
385
  emb.add_field(name="Key Metrics", value=f"```\n{metrics}\n```", inline=False)
386
  emb.add_field(name="Logic Score", value=f"**{s_final:.2f} / 100**", inline=True)
387
  emb.add_field(name="Highlights", value=highlights, inline=False)
388
-
389
  await m.delete()
390
  await ctx.send(embed=emb)
391
 
@@ -397,7 +387,7 @@ async def evaluate(ctx):
397
  async def open_bet(ctx, *, topic):
398
  bid = str(random.randint(100, 999))
399
  bk_engine.active_bets[bid] = {
400
- "topic": topic, "up": 0, "down": 0,
401
  "u_up": {}, "u_down": {}, "status": "OPEN",
402
  "time": str(datetime.datetime.now())
403
  }
@@ -407,20 +397,20 @@ async def open_bet(ctx, *, topic):
407
  async def bet(ctx, bid: str, side: str, amt: int):
408
  if bid not in bk_engine.active_bets or bk_engine.active_bets[bid]["status"] != "OPEN":
409
  return await ctx.send("❌ 盘口不存在或已关闭。")
410
-
411
  u = bk_engine.get_user(ctx.author.id)
412
  if u["points"] < amt or amt <= 0:
413
  return await ctx.send("❌ 资产点数不足。")
414
-
415
  side = side.upper()
416
  if side not in ["UP", "DOWN"]: return await ctx.send("❌ 必须选择 UP 或 DOWN。")
417
-
418
  u["points"] -= amt
419
  b = bk_engine.active_bets[bid]
420
  pool = "u_up" if side == "UP" else "u_down"
421
  b[side.lower()] += amt
422
  b[pool][str(ctx.author.id)] = b[pool].get(str(ctx.author.id), 0) + amt
423
-
424
  bk_engine.save_db()
425
  await ctx.send(f"✅ 已确认:{ctx.author.name} 向 {side} 注入 {amt} 点资产。")
426
 
@@ -430,16 +420,16 @@ async def settle_bet(ctx, bid: str, winner: str):
430
  if bid not in bk_engine.active_bets: return
431
  b = bk_engine.active_bets[bid]
432
  winner = winner.upper()
433
-
434
  win_pool = b["u_up"] if winner == "UP" else b["u_down"]
435
  total_w = b["up"] if winner == "UP" else b["down"]
436
  total_l = b["down"] if winner == "UP" else b["up"]
437
-
438
  if total_w > 0:
439
  for uid, amt in win_pool.items():
440
  profit = (amt / total_w) * total_l
441
  bk_engine.get_user(int(uid))["points"] += int(amt + profit)
442
-
443
  b["status"] = "SETTLED"
444
  bk_engine.save_db()
445
  await ctx.send(f"🏁 盘口 {bid} 已结算!胜利方:**{winner}**。资产已自动划转。")
@@ -458,11 +448,11 @@ async def challenge(ctx, member: discord.Member):
458
  async def accept(ctx):
459
  if ctx.author.id not in bk_engine.pending_challenges:
460
  return await ctx.send("❌ 没有待处理的决斗请求。")
461
-
462
  req = bk_engine.pending_challenges.pop(ctx.author.id)
463
  did = req["id"]
464
  bk_engine.active_duels[did] = {
465
- "p1": req["challenger"], "p2": ctx.author.id,
466
  "s1": None, "s2": None, "t": str(datetime.datetime.now())
467
  }
468
  await ctx.send(f"🔥 **竞技场锁定!** 决斗 ID: `{did}`\n请双方通过 `!duel_submit {did}` 提交审计截图。")
@@ -471,23 +461,23 @@ async def accept(ctx):
471
  async def duel_submit(ctx, did: str):
472
  if did not in bk_engine.active_duels: return
473
  if not ctx.message.attachments: return
474
-
475
  duel = bk_engine.active_duels[did]
476
  if ctx.author.id not in [duel["p1"], duel["p2"]]: return
477
-
478
  m = await ctx.send("🔍 正在审计决斗数据...")
479
  res = await call_gemini("Extract SCORE:[0-100]. Format: SCORE:[v]", ctx.message.attachments[0])
480
  score = safe_extract_float(res, "SCORE")
481
-
482
  if ctx.author.id == duel["p1"]: duel["s1"] = score
483
  else: duel["s2"] = score
484
-
485
  if duel["s1"] is not None and duel["s2"] is not None:
486
  p1, p2 = duel["p1"], duel["p2"]
487
  s1, s2 = duel["s1"], duel["s2"]
488
  wid, lid = (p1, p2) if s1 > s2 else (p2, p1)
489
  w_elo, l_elo, streak = bk_engine.update_elo(wid, lid)
490
-
491
  emb = discord.Embed(title="🏆 Duel Arena Verdict", color=0xf1c40f)
492
  emb.add_field(name="Winner", value=f"<@{wid}>\nScore: {max(s1,s2)}\nELO: {w_elo}", inline=True)
493
  emb.add_field(name="Loser", value=f"<@{lid}>\nScore: {min(s1,s2)}\nELO: {l_elo}", inline=True)
@@ -503,31 +493,24 @@ async def duel_submit(ctx, did: str):
503
  # ==============================================================================
504
  @bot.command()
505
  async def viral_audit(ctx):
506
- """
507
- [完全版] 提取审计数据并渲染具备像素级视觉逻辑的海报
508
- """
509
  if not ctx.message.attachments: return
510
  m = await ctx.send("🎨 **[GPU-Node] 正在渲染视觉海报...**")
511
-
512
  p = "Extract RET: [v] and SHARPE: [v]. Format: RET: [v] SHARPE: [v]"
513
  res = await call_gemini(p, ctx.message.attachments[0])
514
  ret, sharpe = safe_extract_float(res, "RET"), safe_extract_float(res, "SHARPE")
515
 
516
  try:
517
  W, H = 600, 800
518
- # 暗色底板
519
  base = Image.new('RGB', (W, H), (18, 20, 30))
520
  draw = ImageDraw.Draw(base)
521
 
522
- # 绘制背景装饰网格 (逻辑恢复)
523
  for i in range(0, W, 40): draw.line([(i, 0), (i, H)], fill=(30, 35, 45), width=1)
524
  for j in range(0, H, 40): draw.line([(0, j), (W, j)], fill=(30, 35, 45), width=1)
525
-
526
- # 边框
527
  draw.rectangle([10, 10, 590, 790], outline=(46, 204, 113), width=2)
528
  draw.rectangle([20, 20, 580, 780], outline=(46, 204, 113), width=5)
529
 
530
- # 文字渲染
531
  draw.text((40, 60), "BK-GTA QUANTITATIVE SYSTEM", fill=(255, 255, 255))
532
  draw.text((40, 150), f"TRADER: {ctx.author.name.upper()}", fill=(46, 204, 113))
533
  draw.text((40, 260), f"ALPHA RET: +{ret}%", fill=(255, 255, 255))
@@ -559,7 +542,7 @@ async def on_ready():
559
  auto_backup.start()
560
  await bot.change_presence(
561
  activity=discord.Activity(
562
- type=discord.ActivityType.watching,
563
  name="HuggingFace Node"
564
  )
565
  )
@@ -573,40 +556,81 @@ async def on_disconnect():
573
  async def on_resumed():
574
  logger.info("✅ [Gateway] Session resumed successfully.")
575
 
576
-
 
 
577
  def run_bot():
578
  """
579
- [修复] 不再使用 while True 循环。
580
- discord.py 内置自动重连机制 (reconnect=True 是默认值),
581
- 无需手动重启。bot.run() 只能调用一次。
 
582
  """
583
  if not DISCORD_TOKEN:
584
- logger.critical("❌ [FATAL] DISCORD_TOKEN Missing.")
585
  return
586
-
 
 
 
 
 
 
587
  try:
588
- bot.run(DISCORD_TOKEN, reconnect=True)
 
589
  except discord.LoginFailure:
590
- logger.critical("❌ [FATAL] Invalid DISCORD_TOKEN! Check your token.")
 
 
 
591
  except discord.PrivilegedIntentsRequired:
592
  logger.critical(
593
  "❌ [FATAL] Privileged Intents not enabled!\n"
594
- "Go to https://discord.com/developers/applications Your Bot → Bot tab\n"
595
- "Enable 'SERVER MEMBERS INTENT' and 'MESSAGE CONTENT INTENT'"
596
  )
597
  except Exception as e:
598
- logger.error(f"⚠️ [Fatal Error] Bot terminated: {e}")
599
  traceback.print_exc()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
600
 
601
 
602
  if __name__ == "__main__":
603
- # 1. 启动 Discord 后台线程
604
- threading.Thread(target=run_bot, daemon=True).start()
605
-
606
- # 2. bot 一点启动时间,避免 get_status 立即报错
 
 
 
 
 
 
 
 
 
 
607
  time.sleep(3)
608
-
609
- # 3. 启动 Gradio 前台哨兵
 
610
  iface = gr.Interface(
611
  fn=get_status,
612
  inputs=None,
@@ -614,4 +638,23 @@ if __name__ == "__main__":
614
  title="BK-GTA Sentinel AI",
615
  description="Physical architecture monitor. Logic Integrity: Secure."
616
  )
617
- iface.launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 核心问题
2
+
3
+ `bot.run()` 内部调用 `asyncio.run()`,它会注册**信号处理器**——这在子线程中会抛出 `ValueError: signal only works in main thread`,线程直接静默崩溃。
4
+
5
+ 日志里你看到**零条** Bot 输出就是证据。
6
+
7
+ ---
8
+
9
+ 以下是完整修复后的 `app.py`,可以直接覆盖:
10
+
11
+ ```python
12
  import discord
13
  from discord.ext import commands, tasks
14
  from google import genai
 
44
  # 1. 物理层:Gradio 哨兵与探针 (Gradio Sentinel)
45
  # ==============================================================================
46
  def get_status():
 
 
 
47
  now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
48
  bot_status = "OFFLINE"
49
  latency = "N/A"
 
53
  latency = f"{round(bot.latency * 1000, 2)}ms"
54
  except Exception as e:
55
  logger.error(f"Status check failed: {e}")
56
+
57
  return {
58
  "status": "BK-GTA-TITAN-ACTIVE",
59
  "logic_integrity": "100%",
 
71
  DISCORD_TOKEN = os.environ.get('DISCORD_TOKEN', '').strip()
72
  SESSION_ID = random.randint(10000, 99999)
73
 
 
74
  raw_keys = os.environ.get('GEMINI_KEYS_POOL', '')
75
  KEY_POOL = [k.strip() for k in raw_keys.split(',') if k.strip()]
76
  if not KEY_POOL:
 
80
  if not KEY_POOL:
81
  logger.critical("❌ [CRITICAL] NO API KEYS FOUND! Please set GEMINI_KEYS_POOL.")
82
 
 
83
  intents = discord.Intents.default()
84
  intents.message_content = True
85
  intents.members = True
86
  bot = commands.Bot(command_prefix='!', intents=intents)
87
 
 
88
  audit_lock = asyncio.Lock()
 
89
  DB_PATH = '/data/bk_gta_v62_unified.json' if os.path.exists('/data') else 'bk_gta_v62_unified.json'
90
 
91
  # ==============================================================================
92
  # 3. 状态引擎 (BKEngine Core)
93
  # ==============================================================================
94
  class BKEngine:
 
 
 
95
  def __init__(self):
96
  self.db_file = DB_PATH
97
  self.data = self._load_db()
98
  self.pending_challenges = {}
99
  self.active_duels = {}
100
+ self.active_bets = {}
101
 
102
  def _load_db(self):
103
  if os.path.exists(self.db_file):
 
106
  content = json.load(f)
107
  if "users" not in content: content["users"] = {}
108
  return content
109
+ except Exception as e:
110
  logger.error(f"DB Load Failure: {e}")
111
  return self._init_schema()
112
  return self._init_schema()
 
116
  "version": "62.9-Titan",
117
  "users": {},
118
  "factions": {
119
+ "BULLS": {"score": 0, "emoji": "🐂", "wins": 0},
120
  "BEARS": {"score": 0, "emoji": "🐻", "wins": 0}
121
  },
122
  "prize_pool": 1000000.0,
 
153
  if mdd is not None: u["mdd_record"] = mdd
154
  if sharpe is not None: u["sharpe_record"] = sharpe
155
  u["points"] += 50
 
156
  u["audit_history"].append({"s": score, "t": str(datetime.datetime.now())})
157
  u["audit_history"] = u["audit_history"][-5:]
158
  u["last_active"] = str(datetime.datetime.now())
 
163
  K = 32
164
  Rw, Rl = w["elo"], l["elo"]
165
  Ew = 1 / (1 + 10 ** ((Rl - Rw) / 400))
 
166
  w["elo"] = round(Rw + K * (1 - Ew))
167
  l["elo"] = round(Rl + K * (0 - Ew))
 
168
  w["win_streak"] += 1
169
  l["win_streak"] = 0
 
170
  if w["faction"] and w["faction"] in self.data["factions"]:
171
  self.data["factions"][w["faction"]]["score"] += 15
172
  self.data["factions"][w["faction"]]["wins"] += 1
 
173
  self.save_db()
174
  return w["elo"], l["elo"], w["win_streak"]
175
 
 
181
  def safe_extract_float(text, key):
182
  try:
183
  patterns = [
184
+ rf"{key}:\s*([-+]?\d*\.?\d+)",
185
  rf"{key}\s*=\s*([-+]?\d*\.?\d+)",
186
  rf"【{key}】\s*([-+]?\d*\.?\d+)"
187
  ]
 
201
  except: return "N/A"
202
 
203
  async def call_gemini(prompt, attachment=None):
 
 
 
204
  if not KEY_POOL: return "ERROR: NO_KEYS"
205
+
206
  img_data = None
207
  if attachment:
208
  try: img_data = await attachment.read()
 
220
  contents.append(types.Part.from_bytes(data=img_data, mime_type='image/png'))
221
 
222
  response = await loop.run_in_executor(
223
+ None,
224
  functools.partial(client.models.generate_content, model="gemini-1.5-flash", contents=contents)
225
  )
226
  if response and response.text:
 
242
  t = member or ctx.author
243
  u = bk_engine.get_user(t.id)
244
  elo = u['elo']
245
+
 
246
  if elo < 1300: rank, color = "Iron 学徒 🗑️", 0x717d7e
247
  elif elo < 1500: rank, color = "Bronze 资深 🥉", 0xcd7f32
248
  elif elo < 1800: rank, color = "Silver 精英 🥈", 0xbdc3c7
 
253
  emb = discord.Embed(title=f"📊 Quant Profile: {t.display_name}", color=color)
254
  emb.add_field(name="Rating (ELO)", value=f"**{elo}** ({rank})", inline=True)
255
  emb.add_field(name="Assets", value=f"💰 **{u['points']}**", inline=True)
256
+
257
  f_info = u['faction'] or "Free Agent"
258
  f_emoji = bk_engine.data["factions"].get(f_info, {}).get("emoji", "🌐")
259
  emb.add_field(name="Faction", value=f"{f_emoji} {f_info}", inline=True)
 
264
  f"Max MDD: {u.get('mdd_record',0):.2f}%"
265
  )
266
  emb.add_field(name="Institutional Metrics", value=f"```yaml\n{stats}\n```", inline=False)
267
+
268
  if target_avatar := t.avatar:
269
  emb.set_thumbnail(url=target_avatar.url)
270
  emb.set_footer(text=f"Node: HuggingFace Titan | Session: {SESSION_ID}")
 
275
  all_users = []
276
  for uid, d in bk_engine.data["users"].items():
277
  all_users.append({"id": uid, "elo": d["elo"], "score": d["highest_score"]})
278
+
279
  top = sorted(all_users, key=lambda x: x["elo"], reverse=True)[:10]
280
  desc = ""
281
  for idx, u in enumerate(top):
282
  icon = ["🥇","🥈","🥉"][idx] if idx < 3 else f"{idx+1}."
283
  desc += f"{icon} <@{u['id']}> : **{u['elo']}** (Peak: {u['score']:.1f})\n"
284
+
285
  emb = discord.Embed(title="🏆 BK-GTA Institutional Leaderboard", description=desc or "No data.", color=0xf1c40f)
286
+
287
  f_info = ""
288
  for k, v in bk_engine.data["factions"].items():
289
  f_info += f"{v['emoji']} {k}: {v['score']} pts "
290
  if f_info:
291
  emb.add_field(name="⚔️ Faction War", value=f_info, inline=False)
292
+
293
  await ctx.send(embed=emb)
294
 
295
  # ==============================================================================
 
299
  async def news(ctx):
300
  if not ctx.message.attachments: return await ctx.send("❌ Error: 请上传情报截图。")
301
  m = await ctx.send("📡 **[Sentinel] Analyzing Macro Pulse...**")
302
+
303
  prompt = (
304
  "你是一名顶级对冲基金策略师。请解析截图内容:\n"
305
  "1. 评分 (SCORE): -10 (极大利空) 到 +10 (极大利多)。\n"
 
308
  "格式必须为:\nSCORE: [v]\nSUMMARY: [v]\nTAKE: [v]"
309
  )
310
  res = await call_gemini(prompt, ctx.message.attachments[0])
311
+
312
  score = safe_extract_float(res, "SCORE")
313
  summary = safe_extract_text(res, "SUMMARY", "TAKE")
314
  take = safe_extract_text(res, "TAKE")
315
+
316
  color = 0x2ecc71 if score > 0 else 0xe74c3c if score < 0 else 0xbdc3c7
317
  emb = discord.Embed(title="📰 Macro Intelligence Analysis", color=color)
318
  emb.add_field(name="Sentiment Score", value=f"**{score:+.1f} / 10**", inline=True)
319
  emb.add_field(name="Core Catalyst", value=summary, inline=False)
320
  emb.add_field(name="Institutional View", value=f"```\n{take}\n```", inline=False)
321
+
322
  await m.delete()
323
  await ctx.send(embed=emb)
324
 
 
328
  @bot.command()
329
  async def evaluate(ctx):
330
  if not ctx.message.attachments: return await ctx.send("❌ Error: 请上传回测或结算截图。")
331
+
332
  if audit_lock.locked():
333
  return await ctx.send("⚠️ **[System Busy]** 正在处理另一项审计,请稍后。")
334
 
 
341
  "H_ZH: 中文核心优势或致命风险概括。\n"
342
  "格式:\nRET: [v]\nMDD: [v]\nPF: [v]\nSHARPE: [v]\nTRADES: [v]\nS_BASE: [v]\nVIOLATION: [TRUE/FALSE]\nH_ZH: [v]"
343
  )
344
+
345
  res = await call_gemini(prompt, ctx.message.attachments[0])
346
+
347
  if "ERROR" in res:
348
  await m.delete()
349
  return await ctx.send("🔴 [Audit Crash] 引擎请求失败。")
 
355
  s_base = safe_extract_float(res, "S_BASE")
356
  violation = "TRUE" in res.upper()
357
 
 
358
  if violation or mdd > 35.0:
359
  s_final = 0.0
360
  status = "❌ REJECTED (High Risk)"
361
  color = 0xe74c3c
362
  else:
 
363
  weight_sample = math.log10(max(trades, 1)) / 1.5
364
  weight_risk = (sharpe / 2.0) if sharpe > 0 else 0.5
365
  s_final = min(max(s_base * weight_sample * weight_risk, 0.0), 100.0)
 
368
 
369
  bk_engine.update_audit(ctx.author.id, s_final, mdd, sharpe)
370
  highlights = safe_extract_text(res, "H_ZH")
371
+
372
  emb = discord.Embed(title="🛡️ Quantitative Audit Report", color=color)
373
  emb.add_field(name="Verdict", value=f"**{status}**", inline=False)
374
  metrics = f"Return: {ret}% | MDD: {mdd}%\nSharpe: {sharpe} | Trades: {trades}"
375
  emb.add_field(name="Key Metrics", value=f"```\n{metrics}\n```", inline=False)
376
  emb.add_field(name="Logic Score", value=f"**{s_final:.2f} / 100**", inline=True)
377
  emb.add_field(name="Highlights", value=highlights, inline=False)
378
+
379
  await m.delete()
380
  await ctx.send(embed=emb)
381
 
 
387
  async def open_bet(ctx, *, topic):
388
  bid = str(random.randint(100, 999))
389
  bk_engine.active_bets[bid] = {
390
+ "topic": topic, "up": 0, "down": 0,
391
  "u_up": {}, "u_down": {}, "status": "OPEN",
392
  "time": str(datetime.datetime.now())
393
  }
 
397
  async def bet(ctx, bid: str, side: str, amt: int):
398
  if bid not in bk_engine.active_bets or bk_engine.active_bets[bid]["status"] != "OPEN":
399
  return await ctx.send("❌ 盘口不存在或已关闭。")
400
+
401
  u = bk_engine.get_user(ctx.author.id)
402
  if u["points"] < amt or amt <= 0:
403
  return await ctx.send("❌ 资产点数不足。")
404
+
405
  side = side.upper()
406
  if side not in ["UP", "DOWN"]: return await ctx.send("❌ 必须选择 UP 或 DOWN。")
407
+
408
  u["points"] -= amt
409
  b = bk_engine.active_bets[bid]
410
  pool = "u_up" if side == "UP" else "u_down"
411
  b[side.lower()] += amt
412
  b[pool][str(ctx.author.id)] = b[pool].get(str(ctx.author.id), 0) + amt
413
+
414
  bk_engine.save_db()
415
  await ctx.send(f"✅ 已确认:{ctx.author.name} 向 {side} 注入 {amt} 点资产。")
416
 
 
420
  if bid not in bk_engine.active_bets: return
421
  b = bk_engine.active_bets[bid]
422
  winner = winner.upper()
423
+
424
  win_pool = b["u_up"] if winner == "UP" else b["u_down"]
425
  total_w = b["up"] if winner == "UP" else b["down"]
426
  total_l = b["down"] if winner == "UP" else b["up"]
427
+
428
  if total_w > 0:
429
  for uid, amt in win_pool.items():
430
  profit = (amt / total_w) * total_l
431
  bk_engine.get_user(int(uid))["points"] += int(amt + profit)
432
+
433
  b["status"] = "SETTLED"
434
  bk_engine.save_db()
435
  await ctx.send(f"🏁 盘口 {bid} 已结算!胜利方:**{winner}**。资产已自动划转。")
 
448
  async def accept(ctx):
449
  if ctx.author.id not in bk_engine.pending_challenges:
450
  return await ctx.send("❌ 没有待处理的决斗请求。")
451
+
452
  req = bk_engine.pending_challenges.pop(ctx.author.id)
453
  did = req["id"]
454
  bk_engine.active_duels[did] = {
455
+ "p1": req["challenger"], "p2": ctx.author.id,
456
  "s1": None, "s2": None, "t": str(datetime.datetime.now())
457
  }
458
  await ctx.send(f"🔥 **竞技场锁定!** 决斗 ID: `{did}`\n请双方通过 `!duel_submit {did}` 提交审计截图。")
 
461
  async def duel_submit(ctx, did: str):
462
  if did not in bk_engine.active_duels: return
463
  if not ctx.message.attachments: return
464
+
465
  duel = bk_engine.active_duels[did]
466
  if ctx.author.id not in [duel["p1"], duel["p2"]]: return
467
+
468
  m = await ctx.send("🔍 正在审计决斗数据...")
469
  res = await call_gemini("Extract SCORE:[0-100]. Format: SCORE:[v]", ctx.message.attachments[0])
470
  score = safe_extract_float(res, "SCORE")
471
+
472
  if ctx.author.id == duel["p1"]: duel["s1"] = score
473
  else: duel["s2"] = score
474
+
475
  if duel["s1"] is not None and duel["s2"] is not None:
476
  p1, p2 = duel["p1"], duel["p2"]
477
  s1, s2 = duel["s1"], duel["s2"]
478
  wid, lid = (p1, p2) if s1 > s2 else (p2, p1)
479
  w_elo, l_elo, streak = bk_engine.update_elo(wid, lid)
480
+
481
  emb = discord.Embed(title="🏆 Duel Arena Verdict", color=0xf1c40f)
482
  emb.add_field(name="Winner", value=f"<@{wid}>\nScore: {max(s1,s2)}\nELO: {w_elo}", inline=True)
483
  emb.add_field(name="Loser", value=f"<@{lid}>\nScore: {min(s1,s2)}\nELO: {l_elo}", inline=True)
 
493
  # ==============================================================================
494
  @bot.command()
495
  async def viral_audit(ctx):
 
 
 
496
  if not ctx.message.attachments: return
497
  m = await ctx.send("🎨 **[GPU-Node] 正在渲染视觉海报...**")
498
+
499
  p = "Extract RET: [v] and SHARPE: [v]. Format: RET: [v] SHARPE: [v]"
500
  res = await call_gemini(p, ctx.message.attachments[0])
501
  ret, sharpe = safe_extract_float(res, "RET"), safe_extract_float(res, "SHARPE")
502
 
503
  try:
504
  W, H = 600, 800
 
505
  base = Image.new('RGB', (W, H), (18, 20, 30))
506
  draw = ImageDraw.Draw(base)
507
 
 
508
  for i in range(0, W, 40): draw.line([(i, 0), (i, H)], fill=(30, 35, 45), width=1)
509
  for j in range(0, H, 40): draw.line([(0, j), (W, j)], fill=(30, 35, 45), width=1)
510
+
 
511
  draw.rectangle([10, 10, 590, 790], outline=(46, 204, 113), width=2)
512
  draw.rectangle([20, 20, 580, 780], outline=(46, 204, 113), width=5)
513
 
 
514
  draw.text((40, 60), "BK-GTA QUANTITATIVE SYSTEM", fill=(255, 255, 255))
515
  draw.text((40, 150), f"TRADER: {ctx.author.name.upper()}", fill=(46, 204, 113))
516
  draw.text((40, 260), f"ALPHA RET: +{ret}%", fill=(255, 255, 255))
 
542
  auto_backup.start()
543
  await bot.change_presence(
544
  activity=discord.Activity(
545
+ type=discord.ActivityType.watching,
546
  name="HuggingFace Node"
547
  )
548
  )
 
556
  async def on_resumed():
557
  logger.info("✅ [Gateway] Session resumed successfully.")
558
 
559
+ # ==============================================================================
560
+ # 12. 启动入口 — 关键修复区域
561
+ # ==============================================================================
562
  def run_bot():
563
  """
564
+ [核心修复] bot.run() 内部调用 asyncio.run(),会注册信号处理器,
565
+ 但信号处理器只能在主线程注册。在子线程中必须:
566
+ 1. 手动创建事件循环 asyncio.new_event_loop()
567
+ 2. 使用 bot.start()(协程版本)而非 bot.run()
568
  """
569
  if not DISCORD_TOKEN:
570
+ logger.critical("❌ [FATAL] DISCORD_TOKEN is missing! Set it in HuggingFace Secrets.")
571
  return
572
+
573
+ logger.info("🚀 [Bot Thread] Initializing Discord connection...")
574
+
575
+ # ====== 关键:为子线程创建专属事件循环 ======
576
+ loop = asyncio.new_event_loop()
577
+ asyncio.set_event_loop(loop)
578
+
579
  try:
580
+ # bot.start() 是纯协程,不会触发信号注册
581
+ loop.run_until_complete(bot.start(DISCORD_TOKEN, reconnect=True))
582
  except discord.LoginFailure:
583
+ logger.critical(
584
+ "❌ [FATAL] DISCORD_TOKEN is invalid!\n"
585
+ "Go to https://discord.com/developers/applications → Bot → Reset Token"
586
+ )
587
  except discord.PrivilegedIntentsRequired:
588
  logger.critical(
589
  "❌ [FATAL] Privileged Intents not enabled!\n"
590
+ "Go to Discord Developer Portal → Bot tab →\n"
591
+ "Enable 'SERVER MEMBERS INTENT' and 'MESSAGE CONTENT INTENT'"
592
  )
593
  except Exception as e:
594
+ logger.error(f" [Bot Fatal Error] {e}")
595
  traceback.print_exc()
596
+ finally:
597
+ logger.warning("🔴 [Bot Thread] Event loop ending, cleaning up...")
598
+ try:
599
+ # 清理所有未完成的任务
600
+ pending = asyncio.all_tasks(loop)
601
+ for task in pending:
602
+ task.cancel()
603
+ # 等待取消完成
604
+ if pending:
605
+ loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
606
+ if not bot.is_closed():
607
+ loop.run_until_complete(bot.close())
608
+ except Exception as cleanup_err:
609
+ logger.error(f"Cleanup error: {cleanup_err}")
610
+ finally:
611
+ loop.close()
612
+ logger.error("🔴 [Bot Thread] Discord bot has stopped.")
613
 
614
 
615
  if __name__ == "__main__":
616
+ logger.info("=" * 60)
617
+ logger.info(" BK-GTA V62.9-Titan Starting...")
618
+ logger.info(f" Token present: {'YES' if DISCORD_TOKEN else 'NO (!!)'}")
619
+ logger.info(f" Token length: {len(DISCORD_TOKEN)} chars")
620
+ logger.info(f" Gemini keys: {len(KEY_POOL)}")
621
+ logger.info(f" DB path: {DB_PATH}")
622
+ logger.info("=" * 60)
623
+
624
+ # 1. 启动 Discord Bot 后台线程
625
+ bot_thread = threading.Thread(target=run_bot, daemon=True)
626
+ bot_thread.start()
627
+ logger.info("📡 [Main] Bot thread launched.")
628
+
629
+ # 2. 给 bot 线程一点启动时间
630
  time.sleep(3)
631
+
632
+ # 3. 启动 Gradio 前台哨兵 (占据主线程)
633
+ logger.info("📡 [Main] Starting Gradio on port 7860...")
634
  iface = gr.Interface(
635
  fn=get_status,
636
  inputs=None,
 
638
  title="BK-GTA Sentinel AI",
639
  description="Physical architecture monitor. Logic Integrity: Secure."
640
  )
641
+ iface.launch(server_name="0.0.0.0", server_port=7860)
642
+ ```
643
+
644
+ ---
645
+
646
+ ## 改了什么(只有第 12 节不同)
647
+
648
+ | 旧代码(崩溃) | 新代码(修复) |
649
+ |---|---|
650
+ | `bot.run(DISCORD_TOKEN)` | `loop.run_until_complete(bot.start(DISCORD_TOKEN))` |
651
+ | 无事件循环管理 | `asyncio.new_event_loop()` + `asyncio.set_event_loop(loop)` |
652
+ | 无启动诊断 | 启动时打印 Token 长度、密钥数、DB 路径 |
653
+ | 无 cleanup | `finally` 中取消任务 + 关闭 bot + 关闭 loop |
654
+
655
+ **核心一行修复:**
656
+ ```python
657
+ # ❌ 旧:子线程中会因信号注册崩溃
658
+ bot.run(DISCORD_TOKEN, reconnect=True)
659
+
660
+ # ✅ 新:纯协程,不注册信号,子