Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -35,12 +35,6 @@ logger = logging.getLogger("BK_GTA_ARCHITECT")
|
|
| 35 |
# 0.5 [HuggingFace 专用] DNS 稳定性修复 (增强版)
|
| 36 |
# ==============================================================================
|
| 37 |
def fix_dns():
|
| 38 |
-
"""
|
| 39 |
-
HuggingFace Spaces 容器 DNS 解析器无法解析 discord.com。
|
| 40 |
-
方案: 用 dnspython 通过 Google DNS (8.8.8.8) 直接查询 IP,
|
| 41 |
-
然后写入 /etc/hosts 完全绕过容器的 DNS。
|
| 42 |
-
"""
|
| 43 |
-
# 第一步:尝试注入 resolv.conf 备用 DNS
|
| 44 |
try:
|
| 45 |
with open('/etc/resolv.conf', 'r') as f:
|
| 46 |
current = f.read()
|
|
@@ -51,22 +45,16 @@ def fix_dns():
|
|
| 51 |
except Exception as e:
|
| 52 |
logger.warning(f"⚠️ [DNS] resolv.conf write failed: {e}")
|
| 53 |
|
| 54 |
-
# 第二步(核心):用 dnspython 解析并写入 /etc/hosts
|
| 55 |
hosts_to_resolve = [
|
| 56 |
-
'discord.com',
|
| 57 |
-
'
|
| 58 |
-
'cdn.discordapp.com',
|
| 59 |
-
'api.discord.com',
|
| 60 |
]
|
| 61 |
-
|
| 62 |
try:
|
| 63 |
import dns.resolver
|
| 64 |
-
|
| 65 |
resolver = dns.resolver.Resolver(configure=False)
|
| 66 |
resolver.nameservers = ['8.8.8.8', '1.1.1.1', '8.8.4.4']
|
| 67 |
resolver.timeout = 10
|
| 68 |
resolver.lifetime = 10
|
| 69 |
-
|
| 70 |
hosts_lines = []
|
| 71 |
for hostname in hosts_to_resolve:
|
| 72 |
try:
|
|
@@ -76,7 +64,6 @@ def fix_dns():
|
|
| 76 |
logger.info(f"✅ [DNS] Resolved {hostname} → {ip}")
|
| 77 |
except Exception as e:
|
| 78 |
logger.warning(f"⚠️ [DNS] Failed to resolve {hostname}: {e}")
|
| 79 |
-
|
| 80 |
if hosts_lines:
|
| 81 |
try:
|
| 82 |
with open('/etc/hosts', 'a') as f:
|
|
@@ -90,7 +77,6 @@ def fix_dns():
|
|
| 90 |
logger.warning(f"⚠️ [DNS] hosts write error: {e}")
|
| 91 |
else:
|
| 92 |
logger.error("❌ [DNS] Could not resolve any Discord hosts.")
|
| 93 |
-
|
| 94 |
except ImportError:
|
| 95 |
logger.warning("⚠️ [DNS] dnspython not installed, skipping hosts injection.")
|
| 96 |
except Exception as e:
|
|
@@ -104,10 +90,8 @@ fix_dns()
|
|
| 104 |
DISCORD_TOKEN = os.environ.get('DISCORD_TOKEN', '').strip()
|
| 105 |
SESSION_ID = random.randint(10000, 99999)
|
| 106 |
|
| 107 |
-
# [CORE] API 密钥池加载逻辑 (多路复用驱动)
|
| 108 |
raw_keys_pool = os.environ.get('GEMINI_KEYS_POOL', '')
|
| 109 |
KEY_POOL = []
|
| 110 |
-
|
| 111 |
if raw_keys_pool:
|
| 112 |
parts = raw_keys_pool.split(',')
|
| 113 |
for k in parts:
|
|
@@ -123,24 +107,17 @@ else:
|
|
| 123 |
else:
|
| 124 |
logger.critical("❌ [CRITICAL] NO API KEYS FOUND.")
|
| 125 |
|
| 126 |
-
# 设置 Discord Intent 权限
|
| 127 |
intents = discord.Intents.default()
|
| 128 |
intents.message_content = True
|
| 129 |
intents.members = True
|
| 130 |
|
| 131 |
-
# 默认锁定稳定模型,防止因模型探测导致的异步阻塞
|
| 132 |
ACTIVE_MODEL_ID = "gemini-2.0-flash-lite"
|
| 133 |
-
|
| 134 |
-
# [HuggingFace] 持久化路径 (优先使用 /data 持久卷)
|
| 135 |
DB_PATH = '/data/bk_gta_v62_unified.json' if os.path.exists('/data') else 'bk_gta_v62_unified.json'
|
| 136 |
|
| 137 |
# ==============================================================================
|
| 138 |
# 2. 状态引擎与数据库 (V62.9 Unified Analytics)
|
| 139 |
# ==============================================================================
|
| 140 |
class BKEngine:
|
| 141 |
-
"""
|
| 142 |
-
核心数据引擎:负责量化资产持久化、状态树管理及风险指标对账
|
| 143 |
-
"""
|
| 144 |
def __init__(self):
|
| 145 |
self.db_file = DB_PATH
|
| 146 |
self.data = self._load_db()
|
|
@@ -154,7 +131,6 @@ class BKEngine:
|
|
| 154 |
try:
|
| 155 |
with open(self.db_file, 'r', encoding='utf-8') as f:
|
| 156 |
content = json.load(f)
|
| 157 |
-
# 版本兼容性对账
|
| 158 |
if "users" not in content: content["users"] = {}
|
| 159 |
return content
|
| 160 |
except Exception as e:
|
|
@@ -175,7 +151,6 @@ class BKEngine:
|
|
| 175 |
}
|
| 176 |
|
| 177 |
def save_db(self):
|
| 178 |
-
"""执行物理持久化,确保数据资产安全"""
|
| 179 |
try:
|
| 180 |
temp_file = self.db_file + '.tmp'
|
| 181 |
with open(temp_file, 'w', encoding='utf-8') as f:
|
|
@@ -188,15 +163,9 @@ class BKEngine:
|
|
| 188 |
uid = str(user_id)
|
| 189 |
if uid not in self.data["users"]:
|
| 190 |
self.data["users"][uid] = {
|
| 191 |
-
"elo": 1200,
|
| 192 |
-
"
|
| 193 |
-
"
|
| 194 |
-
"audits_count": 0,
|
| 195 |
-
"total_score": 0.0,
|
| 196 |
-
"highest_score": 0.0,
|
| 197 |
-
"win_streak": 0,
|
| 198 |
-
"mdd_record": 0.0,
|
| 199 |
-
"sharpe_record": 0.0,
|
| 200 |
"performance_log": [],
|
| 201 |
"last_update": str(datetime.datetime.now())
|
| 202 |
}
|
|
@@ -210,38 +179,24 @@ class BKEngine:
|
|
| 210 |
u["highest_score"] = score
|
| 211 |
if mdd is not None: u["mdd_record"] = mdd
|
| 212 |
if sharpe is not None: u["sharpe_record"] = sharpe
|
| 213 |
-
|
| 214 |
-
# 奖励计算:审计积分奖励
|
| 215 |
u["points"] += 50
|
| 216 |
u["last_update"] = str(datetime.datetime.now())
|
| 217 |
u["performance_log"].append({"score": score, "time": str(datetime.datetime.now())})
|
| 218 |
self.save_db()
|
| 219 |
|
| 220 |
def update_elo(self, winner_id, loser_id):
|
| 221 |
-
"""
|
| 222 |
-
基于 ELO 等级分系统的量化能力更新算法
|
| 223 |
-
K = 32 (常数项)
|
| 224 |
-
"""
|
| 225 |
w = self.get_user(winner_id)
|
| 226 |
l = self.get_user(loser_id)
|
| 227 |
-
|
| 228 |
K = 32
|
| 229 |
Rw, Rl = w["elo"], l["elo"]
|
| 230 |
-
# 计算胜率期望
|
| 231 |
Ew = 1 / (1 + 10 ** ((Rl - Rw) / 400))
|
| 232 |
-
|
| 233 |
-
# 更新等级分
|
| 234 |
w["elo"] = round(Rw + K * (1 - Ew))
|
| 235 |
l["elo"] = round(Rl + K * (0 - Ew))
|
| 236 |
-
|
| 237 |
w["win_streak"] += 1
|
| 238 |
l["win_streak"] = 0
|
| 239 |
-
|
| 240 |
-
# 阵营荣誉点累加
|
| 241 |
if w["faction"] and w["faction"] in self.data["factions"]:
|
| 242 |
self.data["factions"][w["faction"]]["score"] += 15
|
| 243 |
self.data["factions"][w["faction"]]["total_wins"] += 1
|
| 244 |
-
|
| 245 |
self.save_db()
|
| 246 |
return w["elo"], l["elo"], w["win_streak"]
|
| 247 |
|
|
@@ -251,7 +206,6 @@ bk_engine = BKEngine()
|
|
| 251 |
# 3. 核心辅助内核 (Helper Kernels & Security)
|
| 252 |
# ==============================================================================
|
| 253 |
def safe_extract_float(text, key):
|
| 254 |
-
"""鲁棒性数值提取引擎,支持多重前缀匹配"""
|
| 255 |
try:
|
| 256 |
patterns = [
|
| 257 |
rf"{key}:\s*([-+]?\d*\.?\d+)",
|
|
@@ -265,7 +219,6 @@ def safe_extract_float(text, key):
|
|
| 265 |
except Exception: return 0.0
|
| 266 |
|
| 267 |
def safe_extract_text(text, key_start, key_end=None):
|
| 268 |
-
"""逻辑块提取引擎,支持长文本切片"""
|
| 269 |
try:
|
| 270 |
if key_end:
|
| 271 |
pattern = rf"{key_start}:(.*?){key_end}:"
|
|
@@ -274,16 +227,11 @@ def safe_extract_text(text, key_start, key_end=None):
|
|
| 274 |
match = re.search(pattern, text, re.DOTALL | re.IGNORECASE)
|
| 275 |
if match:
|
| 276 |
content = match.group(1).strip()
|
| 277 |
-
# 过滤 Markdown 字符防止渲染崩溃
|
| 278 |
return content.replace('*', '').replace('`', '').strip()
|
| 279 |
return "N/A"
|
| 280 |
except Exception: return "N/A"
|
| 281 |
|
| 282 |
async def call_gemini_multiplex(prompt, attachment=None):
|
| 283 |
-
"""
|
| 284 |
-
直接 REST API 调用 Gemini,彻底绕过 SDK 的 AFC 额外请求开销。
|
| 285 |
-
同时支持 v1beta 和 v1 双端点自动降级。
|
| 286 |
-
"""
|
| 287 |
if not KEY_POOL:
|
| 288 |
logger.error("Request Blocked: No API Keys available.")
|
| 289 |
return "ERROR: NO_KEYS"
|
|
@@ -297,22 +245,13 @@ async def call_gemini_multiplex(prompt, attachment=None):
|
|
| 297 |
return "ERROR: IMAGE_READ_FAILURE"
|
| 298 |
|
| 299 |
import aiohttp
|
| 300 |
-
|
| 301 |
-
# 构建请求体(只构建一次,所有 Key 共用)
|
| 302 |
parts = [{"text": prompt}]
|
| 303 |
if image_data:
|
| 304 |
b64 = base64.b64encode(image_data).decode("utf-8")
|
| 305 |
-
parts.append({
|
| 306 |
-
"inlineData": {
|
| 307 |
-
"mimeType": "image/png",
|
| 308 |
-
"data": b64
|
| 309 |
-
}
|
| 310 |
-
})
|
| 311 |
payload = {"contents": [{"parts": parts}]}
|
| 312 |
|
| 313 |
-
# 双端点降级策略:先 v1beta,若 404 则降级 v1
|
| 314 |
api_versions = ["v1beta", "v1"]
|
| 315 |
-
|
| 316 |
for api_ver in api_versions:
|
| 317 |
for idx, api_key in enumerate(KEY_POOL):
|
| 318 |
try:
|
|
@@ -320,34 +259,28 @@ async def call_gemini_multiplex(prompt, attachment=None):
|
|
| 320 |
f"https://generativelanguage.googleapis.com/{api_ver}/models/"
|
| 321 |
f"{ACTIVE_MODEL_ID}:generateContent?key={api_key}"
|
| 322 |
)
|
| 323 |
-
|
| 324 |
async with aiohttp.ClientSession() as session:
|
| 325 |
async with session.post(
|
| 326 |
url, json=payload,
|
| 327 |
timeout=aiohttp.ClientTimeout(total=30)
|
| 328 |
) as resp:
|
| 329 |
status = resp.status
|
| 330 |
-
|
| 331 |
if status == 200:
|
| 332 |
data = await resp.json()
|
| 333 |
text = data["candidates"][0]["content"]["parts"][0]["text"]
|
| 334 |
logger.info(f"✅ Gemini OK via {api_ver} key #{idx+1}")
|
| 335 |
return text
|
| 336 |
-
|
| 337 |
elif status == 429:
|
| 338 |
logger.warning(f"Key #{idx+1} ({api_ver}) got 429, switching...")
|
| 339 |
await asyncio.sleep(3)
|
| 340 |
continue
|
| 341 |
-
|
| 342 |
elif status == 404:
|
| 343 |
logger.warning(f"Model not found on {api_ver}, trying next version...")
|
| 344 |
-
break
|
| 345 |
-
|
| 346 |
else:
|
| 347 |
body = await resp.text()
|
| 348 |
logger.error(f"Key #{idx+1} ({api_ver}) HTTP {status}: {body[:300]}")
|
| 349 |
continue
|
| 350 |
-
|
| 351 |
except asyncio.TimeoutError:
|
| 352 |
logger.warning(f"Key #{idx+1} timeout, switching...")
|
| 353 |
continue
|
|
@@ -360,14 +293,9 @@ async def call_gemini_multiplex(prompt, attachment=None):
|
|
| 360 |
# ==============================================================================
|
| 361 |
# 4. Gradio 保活哨兵 (替代原 Flask keep_alive)
|
| 362 |
# ==============================================================================
|
| 363 |
-
# bot 全局引用,供 get_status 和 keep_alive 使用
|
| 364 |
bot = None
|
| 365 |
|
| 366 |
def get_status():
|
| 367 |
-
"""
|
| 368 |
-
提供 HTTP 心跳接口,返回逻辑状态。
|
| 369 |
-
功能等同原 Flask '/' 路由,迁移至 Gradio 以适配 HuggingFace。
|
| 370 |
-
"""
|
| 371 |
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 372 |
latency = "N/A"
|
| 373 |
bot_status = "OFFLINE"
|
|
@@ -377,9 +305,7 @@ def get_status():
|
|
| 377 |
latency = f"{round(bot.latency * 1000, 2)}ms"
|
| 378 |
except Exception as e:
|
| 379 |
logger.warning(f"Heartbeat probe failed: {e}")
|
| 380 |
-
|
| 381 |
uptime = str(datetime.datetime.now() - bk_engine.system_uptime).split('.')[0]
|
| 382 |
-
|
| 383 |
return {
|
| 384 |
"status": "BK-GTA-V62.9-ULTIMATE-ACTIVE",
|
| 385 |
"engine": "BK-GTA-Logic-Kernel",
|
|
@@ -394,27 +320,18 @@ def get_status():
|
|
| 394 |
}
|
| 395 |
|
| 396 |
def keep_alive():
|
| 397 |
-
"""
|
| 398 |
-
开启独立守护线程运行 Gradio 服务。
|
| 399 |
-
功能等同原 Flask keep_alive(),绑定 7860 端口 (HuggingFace 网关入口)。
|
| 400 |
-
"""
|
| 401 |
def run_gradio():
|
| 402 |
try:
|
| 403 |
-
# 抑制 Gradio 内部日志噪音
|
| 404 |
gradio_logger = logging.getLogger('gradio')
|
| 405 |
gradio_logger.setLevel(logging.WARNING)
|
| 406 |
-
|
| 407 |
iface = gr.Interface(
|
| 408 |
-
fn=get_status,
|
| 409 |
-
inputs=None,
|
| 410 |
-
outputs="json",
|
| 411 |
title="BK-GTA Sentinel AI",
|
| 412 |
description="Physical architecture monitor. Logic Integrity: Secure."
|
| 413 |
)
|
| 414 |
iface.launch(server_name="0.0.0.0", server_port=7860)
|
| 415 |
except Exception as e:
|
| 416 |
logger.error(f"⚠️ [Sentinel] Gradio Server Failure: {e}")
|
| 417 |
-
|
| 418 |
t = threading.Thread(target=run_gradio)
|
| 419 |
t.daemon = True
|
| 420 |
t.start()
|
|
@@ -424,14 +341,9 @@ def keep_alive():
|
|
| 424 |
# 5. Bot 工厂 + 全模块命令注册 (Bot Factory & Full Command Registration)
|
| 425 |
# ==============================================================================
|
| 426 |
def create_bot_with_commands():
|
| 427 |
-
"""
|
| 428 |
-
创建完整的 Bot 实例并注册所有命令/事件。
|
| 429 |
-
支持在网络重试时重建 Bot 实例而不丢失任何功能。
|
| 430 |
-
架构等同原版在模块级定义 @bot.command(),但通过工厂封装实现可重建。
|
| 431 |
-
"""
|
| 432 |
global bot
|
| 433 |
new_bot = commands.Bot(command_prefix='!', intents=intents)
|
| 434 |
-
bot = new_bot
|
| 435 |
|
| 436 |
# ==================================================================
|
| 437 |
# 模块一:全网天梯与个人档案 (Rank & Profile System)
|
|
@@ -441,8 +353,6 @@ def create_bot_with_commands():
|
|
| 441 |
"""展现量化交易员的详细画像与 ELO 评级"""
|
| 442 |
target = member or ctx.author
|
| 443 |
u = bk_engine.get_user(target.id)
|
| 444 |
-
|
| 445 |
-
# 动态段位逻辑展开
|
| 446 |
elo = u['elo']
|
| 447 |
if elo < 1300: rank, color = "Iron (学徒) 🗑️", 0x717d7e
|
| 448 |
elif elo < 1500: rank, color = "Bronze (资深) 🥉", 0xcd7f32
|
|
@@ -454,12 +364,9 @@ def create_bot_with_commands():
|
|
| 454 |
emb = discord.Embed(title=f"📊 Quantitative Profile: {target.display_name}", color=color)
|
| 455 |
emb.add_field(name="Rating (ELO)", value=f"**{elo}**\nRank: `{rank}`", inline=True)
|
| 456 |
emb.add_field(name="Assets (Points)", value=f"💰 **{u['points']}**", inline=True)
|
| 457 |
-
|
| 458 |
f_name = u['faction']
|
| 459 |
f_emoji = bk_engine.data["factions"].get(f_name, {}).get("emoji", "🌐") if f_name else "Ronin"
|
| 460 |
emb.add_field(name="Faction", value=f"{f_emoji} {f_name or 'Unassigned'}", inline=True)
|
| 461 |
-
|
| 462 |
-
# 展开性能统计指标
|
| 463 |
perf_stats = (
|
| 464 |
f"Total Audits: {u['audits_count']}\n"
|
| 465 |
f"Peak Audit Score: {u['highest_score']:.2f}\n"
|
|
@@ -467,10 +374,8 @@ def create_bot_with_commands():
|
|
| 467 |
f"Max Win Streak: {u['win_streak']}"
|
| 468 |
)
|
| 469 |
emb.add_field(name="Risk-Adjusted Performance", value=f"```yaml\n{perf_stats}\n```", inline=False)
|
| 470 |
-
|
| 471 |
if target.avatar:
|
| 472 |
emb.set_thumbnail(url=target.avatar.url)
|
| 473 |
-
|
| 474 |
emb.set_footer(text=f"System Session: {SESSION_ID} | Logic V62.9")
|
| 475 |
await ctx.send(embed=emb)
|
| 476 |
|
|
@@ -480,28 +385,21 @@ def create_bot_with_commands():
|
|
| 480 |
all_users = []
|
| 481 |
for uid, data in bk_engine.data["users"].items():
|
| 482 |
all_users.append({"id": uid, "elo": data["elo"], "score": data["highest_score"]})
|
| 483 |
-
|
| 484 |
-
# 按 ELO 排序
|
| 485 |
top_elo = sorted(all_users, key=lambda x: x["elo"], reverse=True)[:10]
|
| 486 |
-
|
| 487 |
desc = ""
|
| 488 |
for idx, u in enumerate(top_elo):
|
| 489 |
medal = "🥇" if idx == 0 else "🥈" if idx == 1 else "🥉" if idx == 2 else f"{idx+1}."
|
| 490 |
desc += f"{medal} <@{u['id']}> : ELO **{u['elo']}** | Peak Score: **{u['score']:.1f}**\n"
|
| 491 |
-
|
| 492 |
emb = discord.Embed(
|
| 493 |
title="🏆 BK-GTA Institutional Leaderboard",
|
| 494 |
description=desc or "Scanning market data... (Waiting for submissions)",
|
| 495 |
color=0xf1c40f
|
| 496 |
)
|
| 497 |
-
|
| 498 |
-
# 加入阵营战统计
|
| 499 |
f_info = ""
|
| 500 |
for name, info in bk_engine.data["factions"].items():
|
| 501 |
f_info += f"{info['emoji']} {name}: {info['score']} pts "
|
| 502 |
if f_info:
|
| 503 |
emb.add_field(name="⚔️ Faction War Status", value=f_info, inline=False)
|
| 504 |
-
|
| 505 |
await ctx.send(embed=emb)
|
| 506 |
|
| 507 |
# ==================================================================
|
|
@@ -512,36 +410,27 @@ def create_bot_with_commands():
|
|
| 512 |
"""分析新闻/报表截图,并基于机构逻辑给出宏观评分"""
|
| 513 |
if not ctx.message.attachments:
|
| 514 |
return await ctx.send("❌ Audit Refused: 请上传新闻图表。")
|
| 515 |
-
|
| 516 |
m = await ctx.send("📡 **[System] Parsing Macro Pulse...**")
|
| 517 |
-
|
| 518 |
prompt = (
|
| 519 |
"你是一名对冲基金首席策略师。请解析此截图并输出:\n"
|
| 520 |
"1. 情绪评分 (SCORE): -10 (极度利空) 到 +10 (极度利多)。\n"
|
| 521 |
"2. 核心变量 (SUMMARY): 30字内概括。\n"
|
| 522 |
"3. 隐含逻辑 (TAKE): 机构视角的解读 (100字内)。\n"
|
| 523 |
"输出格式必须严格如下:\n"
|
| 524 |
-
"SCORE: [分值]\
|
| 525 |
-
"SUMMARY: [内容]\n"
|
| 526 |
-
"TAKE: [解读]"
|
| 527 |
)
|
| 528 |
-
|
| 529 |
res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
|
| 530 |
-
|
| 531 |
if "ERROR" in res:
|
| 532 |
await m.delete()
|
| 533 |
return await ctx.send("🔴 [Audit Failure] Intelligence node offline.")
|
| 534 |
-
|
| 535 |
score = safe_extract_float(res, "SCORE")
|
| 536 |
summary = safe_extract_text(res, "SUMMARY", "TAKE")
|
| 537 |
take = safe_extract_text(res, "TAKE")
|
| 538 |
-
|
| 539 |
color = 0x2ecc71 if score > 0 else 0xe74c3c if score < 0 else 0xbdc3c7
|
| 540 |
emb = discord.Embed(title="📰 Macro Sentiment Pulse", color=color)
|
| 541 |
emb.add_field(name="Sentiment Score", value=f"**{score:+.1f} / 10**", inline=True)
|
| 542 |
emb.add_field(name="Core Catalyst", value=summary, inline=False)
|
| 543 |
emb.add_field(name="🏦 Institutional Analysis", value=f"```\n{take}\n```", inline=False)
|
| 544 |
-
|
| 545 |
await m.delete()
|
| 546 |
await ctx.send(embed=emb)
|
| 547 |
|
|
@@ -553,11 +442,8 @@ def create_bot_with_commands():
|
|
| 553 |
"""基于截图全维度识别回测/实盘参数,并执行风控打分"""
|
| 554 |
if not ctx.message.attachments:
|
| 555 |
return await ctx.send("❌ Audit Refused: 请上传结算单或净值曲线。")
|
| 556 |
-
|
| 557 |
sid = random.randint(1000, 9999)
|
| 558 |
m = await ctx.send(f"🛡️ **[SID:{sid}] Audit Engine Initializing...**")
|
| 559 |
-
|
| 560 |
-
# 深度审计 Prompt
|
| 561 |
prompt = (
|
| 562 |
"你是一名顶级量化风险官。请执行全维度审计。\n"
|
| 563 |
"【提取要求】:\n"
|
|
@@ -567,23 +453,16 @@ def create_bot_with_commands():
|
|
| 567 |
"输出格式:\n"
|
| 568 |
"RET: [值]\nMDD: [值]\nPF: [值]\nSHARPE: [值]\nTRADES: [笔数]\nS_BASE: [分]\nH_ZH: [优势]"
|
| 569 |
)
|
| 570 |
-
|
| 571 |
res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
|
| 572 |
-
|
| 573 |
if "ERROR" in res:
|
| 574 |
await m.delete()
|
| 575 |
return await ctx.send("🔴 [Audit Crash] Engine disconnected.")
|
| 576 |
-
|
| 577 |
-
# 提取数值
|
| 578 |
ret = safe_extract_float(res, "RET")
|
| 579 |
mdd = safe_extract_float(res, "MDD")
|
| 580 |
pf = safe_extract_float(res, "PF")
|
| 581 |
sharpe = safe_extract_float(res, "SHARPE")
|
| 582 |
trades = int(safe_extract_float(res, "TRADES"))
|
| 583 |
s_base = safe_extract_float(res, "S_BASE")
|
| 584 |
-
|
| 585 |
-
# 逻辑加权算法展开:S = S_base * (log10(Trades)/1.5) * Sharpe_Factor
|
| 586 |
-
# 如果 MDD > 35%,执行风控一票否决
|
| 587 |
if mdd > 35.0:
|
| 588 |
s_final = 0.0
|
| 589 |
status = "⚠️ REJECTED (High MDD Risk)"
|
|
@@ -594,14 +473,10 @@ def create_bot_with_commands():
|
|
| 594 |
s_final = min(max(s_final, 0.0), 100.0)
|
| 595 |
status = "✅ AUDIT CLEARED"
|
| 596 |
color = 0x2ecc71
|
| 597 |
-
|
| 598 |
-
# 持久化
|
| 599 |
bk_engine.update_audit_stats(ctx.author.id, s_final, mdd, sharpe)
|
| 600 |
h_zh = safe_extract_text(res, "H_ZH")
|
| 601 |
-
|
| 602 |
emb = discord.Embed(title="🛡️ BK-GTA Gene Audit Report", color=color)
|
| 603 |
emb.add_field(name="Verdict", value=f"**{status}**", inline=False)
|
| 604 |
-
|
| 605 |
metrics = (
|
| 606 |
f"💰 Return: {ret}% | MDD: {mdd}%\n"
|
| 607 |
f"⚖️ PF: {pf} | Sharpe: {sharpe}\n"
|
|
@@ -610,7 +485,6 @@ def create_bot_with_commands():
|
|
| 610 |
emb.add_field(name="Data Extraction", value=f"```\n{metrics}\n```", inline=False)
|
| 611 |
emb.add_field(name="Analysis Score", value=f"**{s_final:.2f} / 100**", inline=True)
|
| 612 |
emb.add_field(name="Highlights", value=h_zh, inline=False)
|
| 613 |
-
|
| 614 |
emb.set_footer(text=f"SID: {sid} | Assets +50")
|
| 615 |
await m.delete()
|
| 616 |
await ctx.send(embed=emb)
|
|
@@ -624,8 +498,7 @@ def create_bot_with_commands():
|
|
| 624 |
"""由合伙人开启的宏观/行情博弈盘口"""
|
| 625 |
bid = str(random.randint(100, 999))
|
| 626 |
bk_engine.active_bets[bid] = {
|
| 627 |
-
"topic": topic,
|
| 628 |
-
"up": 0, "down": 0,
|
| 629 |
"u_up": {}, "u_down": {},
|
| 630 |
"status": "OPEN",
|
| 631 |
"start_time": str(datetime.datetime.now())
|
|
@@ -637,27 +510,20 @@ def create_bot_with_commands():
|
|
| 637 |
"""交易员消耗积分参与盘口对冲"""
|
| 638 |
if bid not in bk_engine.active_bets:
|
| 639 |
return await ctx.send("❌ Error: Invalid Bet ID.")
|
| 640 |
-
|
| 641 |
b = bk_engine.active_bets[bid]
|
| 642 |
if b["status"] != "OPEN":
|
| 643 |
return await ctx.send("❌ Error: This market is closed.")
|
| 644 |
-
|
| 645 |
u = bk_engine.get_user(ctx.author.id)
|
| 646 |
if u["points"] < amt or amt <= 0:
|
| 647 |
return await ctx.send("❌ Error: Insufficient asset points.")
|
| 648 |
-
|
| 649 |
side = side.upper()
|
| 650 |
if side not in ["UP", "DOWN"]:
|
| 651 |
return await ctx.send("❌ Error: Choose UP or DOWN.")
|
| 652 |
-
|
| 653 |
-
# 锁定积分
|
| 654 |
u["points"] -= amt
|
| 655 |
uid = str(ctx.author.id)
|
| 656 |
-
|
| 657 |
pool_key = "u_up" if side == "UP" else "u_down"
|
| 658 |
b[side.lower()] += amt
|
| 659 |
b[pool_key][uid] = b[pool_key].get(uid, 0) + amt
|
| 660 |
-
|
| 661 |
bk_engine.save_db()
|
| 662 |
await ctx.send(f"✅ Position Locked: {ctx.author.name} added {amt} to {side} pool.")
|
| 663 |
|
|
@@ -668,92 +534,159 @@ def create_bot_with_commands():
|
|
| 668 |
if bid not in bk_engine.active_bets: return
|
| 669 |
b = bk_engine.active_bets[bid]
|
| 670 |
winner = winner.upper()
|
| 671 |
-
|
| 672 |
win_pool = b["u_up"] if winner == "UP" else b["u_down"]
|
| 673 |
total_win = b["up"] if winner == "UP" else b["down"]
|
| 674 |
total_lose = b["down"] if winner == "UP" else b["up"]
|
| 675 |
-
|
| 676 |
if total_win > 0:
|
| 677 |
for uid, amt in win_pool.items():
|
| 678 |
-
# 分配逻辑: 原始积分 + (个人占比 * 输家总积分)
|
| 679 |
profit = (amt / total_win) * total_lose
|
| 680 |
user = bk_engine.get_user(int(uid))
|
| 681 |
user["points"] += round(amt + profit)
|
| 682 |
-
|
| 683 |
b["status"] = "SETTLED"
|
| 684 |
bk_engine.save_db()
|
| 685 |
await ctx.send(f"🏁 Bet `{bid}` Settled! Winner: **{winner}**. Profits Distributed.")
|
| 686 |
|
| 687 |
# ==================================================================
|
| 688 |
-
# 模块五:PVP 竞技场 (Arena PvP Engine)
|
| 689 |
# ==================================================================
|
| 690 |
@new_bot.command()
|
| 691 |
-
async def challenge(ctx, member: discord.Member):
|
| 692 |
-
"""发起基于量化能力的 1对1
|
| 693 |
if member.id == ctx.author.id:
|
| 694 |
return await ctx.send("❌ Logical Error: 不能向自己发起决斗。")
|
| 695 |
-
|
|
|
|
| 696 |
did = f"DUEL-{random.randint(100, 999)}"
|
| 697 |
bk_engine.pending_challenges[member.id] = {
|
| 698 |
"challenger": ctx.author.id,
|
| 699 |
"did": did,
|
|
|
|
| 700 |
"expiry": datetime.datetime.now() + datetime.timedelta(hours=1)
|
| 701 |
}
|
| 702 |
-
|
| 703 |
-
|
|
|
|
|
|
|
| 704 |
|
| 705 |
@new_bot.command()
|
| 706 |
async def accept(ctx):
|
| 707 |
"""接受竞技场挑战"""
|
| 708 |
if ctx.author.id not in bk_engine.pending_challenges:
|
| 709 |
return await ctx.send("❌ Error: No pending challenges.")
|
| 710 |
-
|
| 711 |
req = bk_engine.pending_challenges.pop(ctx.author.id)
|
| 712 |
did = req["did"]
|
| 713 |
-
|
| 714 |
bk_engine.active_duels[did] = {
|
| 715 |
"p1": req["challenger"], "p2": ctx.author.id,
|
| 716 |
"s1": None, "s2": None,
|
|
|
|
| 717 |
"start": str(datetime.datetime.now())
|
| 718 |
}
|
| 719 |
-
|
| 720 |
-
|
|
|
|
|
|
|
| 721 |
|
| 722 |
@new_bot.command()
|
| 723 |
async def duel_submit(ctx, did: str):
|
| 724 |
-
"""
|
| 725 |
if did not in bk_engine.active_duels:
|
| 726 |
return await ctx.send("❌ Error: Duel not active.")
|
| 727 |
-
|
| 728 |
if not ctx.message.attachments:
|
| 729 |
return await ctx.send("❌ Refused: 请上传结算截图。")
|
| 730 |
-
|
| 731 |
duel = bk_engine.active_duels[did]
|
| 732 |
-
if ctx.author.id not in [duel["p1"], duel["p2"]]:
|
| 733 |
-
|
| 734 |
m = await ctx.send(f"🔍 **[Arena] Auditing Submission...**")
|
| 735 |
-
prompt =
|
|
|
|
|
|
|
|
|
|
| 736 |
res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
|
| 737 |
score = safe_extract_float(res, "SCORE")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 738 |
|
| 739 |
-
|
| 740 |
-
else: duel["s2"] = score
|
| 741 |
-
|
| 742 |
-
# 逻辑分歧点:如果双方都交了,执行结算
|
| 743 |
if duel["s1"] is not None and duel["s2"] is not None:
|
| 744 |
-
|
|
|
|
|
|
|
|
|
|
| 745 |
new_w, new_l, strk = bk_engine.update_elo(wid, lid)
|
| 746 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 747 |
emb = discord.Embed(title="🏆 Arena Final Verdict", color=0xf1c40f)
|
| 748 |
-
emb.add_field(
|
| 749 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 750 |
emb.set_footer(text=f"Streak: {strk} | Duel ID: {did}")
|
| 751 |
-
|
| 752 |
await ctx.send(embed=emb)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 753 |
bk_engine.active_duels.pop(did)
|
| 754 |
else:
|
| 755 |
await ctx.send(f"✅ Submission Received (Score: {score:.1f}). Waiting for Opponent...")
|
| 756 |
-
|
| 757 |
await m.delete()
|
| 758 |
|
| 759 |
# ==================================================================
|
|
@@ -764,71 +697,247 @@ def create_bot_with_commands():
|
|
| 764 |
"""提取审计数据并渲染具备社交传播属性的海报图片"""
|
| 765 |
if not ctx.message.attachments:
|
| 766 |
return await ctx.send("❌ 请上传审计截图��")
|
| 767 |
-
|
| 768 |
m = await ctx.send("🎨 **[GPU-Node] Rendering Institutional Poster...**")
|
| 769 |
-
|
| 770 |
prompt = "Extract RET: [val] and SHARPE: [val]. FORMAT: RET: [v] SHARPE: [v]"
|
| 771 |
res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
|
| 772 |
ret, sharpe = safe_extract_float(res, "RET"), safe_extract_float(res, "SHARPE")
|
| 773 |
-
|
| 774 |
-
# 物理画布绘制逻辑展开
|
| 775 |
try:
|
| 776 |
W, H = 600, 800
|
| 777 |
-
# 创建暗黑科技风底色
|
| 778 |
base = Image.new('RGB', (W, H), color=(18, 20, 30))
|
| 779 |
draw = ImageDraw.Draw(base)
|
| 780 |
-
|
| 781 |
-
# 1. 绘制装饰性雷达背景
|
| 782 |
for i in range(0, W, 40): draw.line([(i, 0), (i, H)], fill=(30, 35, 45), width=1)
|
| 783 |
for j in range(0, H, 40): draw.line([(0, j), (W, j)], fill=(30, 35, 45), width=1)
|
| 784 |
-
|
| 785 |
-
# 2. 绘制外边框
|
| 786 |
draw.rectangle([10, 10, 590, 790], outline=(46, 204, 113), width=2)
|
| 787 |
draw.rectangle([20, 20, 580, 780], outline=(46, 204, 113), width=5)
|
| 788 |
-
|
| 789 |
-
# 3. 填充文字数据 (若服务器无字体则降级)
|
| 790 |
draw.text((40, 60), "BK-GTA QUANTITATIVE SYSTEM", fill=(255, 255, 255))
|
| 791 |
draw.text((40, 150), f"TRADER ID: {ctx.author.name.upper()}", fill=(46, 204, 113))
|
| 792 |
draw.text((40, 260), f"ALPHA RETURN: +{ret}%", fill=(255, 255, 255))
|
| 793 |
draw.text((40, 360), f"SHARPE RATIO: {sharpe}", fill=(255, 255, 255))
|
| 794 |
draw.text((40, 460), f"SESSION: {SESSION_ID}", fill=(120, 120, 130))
|
| 795 |
-
|
| 796 |
-
# 加入验证水印
|
| 797 |
draw.text((40, 720), "VERIFIED BY BK-GTA SENTINEL AI", fill=(46, 204, 113))
|
| 798 |
-
|
| 799 |
-
# 内存缓冲交付
|
| 800 |
with io.BytesIO() as image_binary:
|
| 801 |
base.save(image_binary, 'PNG')
|
| 802 |
image_binary.seek(0)
|
| 803 |
await ctx.send(file=discord.File(fp=image_binary, filename=f"Viral_{ctx.author.id}.png"))
|
| 804 |
-
|
| 805 |
except Exception as e:
|
| 806 |
logger.error(f"Render Error: {e}")
|
| 807 |
await ctx.send(f"❌ 渲染引擎报错: `{e}`")
|
| 808 |
finally:
|
| 809 |
await m.delete()
|
| 810 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 811 |
# ==================================================================
|
| 812 |
# 系统对账与自动化任务 (System Reliability)
|
| 813 |
# ==================================================================
|
| 814 |
@tasks.loop(hours=1)
|
| 815 |
async def auto_backup():
|
| 816 |
-
"""本地资产数据库每小时原子化备份"""
|
| 817 |
bk_engine.save_db()
|
| 818 |
logger.info("💾 [Checkpoint] Data persistence executed.")
|
| 819 |
|
| 820 |
@new_bot.event
|
| 821 |
async def on_ready():
|
| 822 |
-
# 激活心跳对账
|
| 823 |
if not auto_backup.is_running(): auto_backup.start()
|
| 824 |
-
|
| 825 |
-
# 修改机器人活跃状态
|
| 826 |
activity = discord.Activity(type=discord.ActivityType.watching, name="HFT Markets")
|
| 827 |
await new_bot.change_presence(status=discord.Status.online, activity=activity)
|
| 828 |
-
|
| 829 |
logger.info(f"--------------------------------------------------")
|
| 830 |
logger.info(f"✅ [V62.9 Ultimate] BK-GTA Architecture Online")
|
| 831 |
-
logger.info(f"🚀 Session: {SESSION_ID} |
|
| 832 |
logger.info(f"--------------------------------------------------")
|
| 833 |
|
| 834 |
@new_bot.event
|
|
@@ -841,12 +950,9 @@ def create_bot_with_commands():
|
|
| 841 |
|
| 842 |
@new_bot.event
|
| 843 |
async def on_command_error(ctx, error):
|
| 844 |
-
"""全局错误拦截器,严禁静默失效"""
|
| 845 |
if isinstance(error, commands.CommandNotFound): return
|
| 846 |
if isinstance(error, commands.MissingPermissions):
|
| 847 |
return await ctx.send("❌ 权限审计未通过:仅限合伙人执行。")
|
| 848 |
-
|
| 849 |
-
# 堆栈追踪打印至 Console
|
| 850 |
exc = "".join(traceback.format_exception(type(error), error, error.__traceback__))
|
| 851 |
logger.error(f"🔥 [System Crash] {exc}")
|
| 852 |
await ctx.send(f"⚠️ [Logic Breach] 内部逻辑断裂: `{error}`")
|
|
@@ -857,7 +963,6 @@ def create_bot_with_commands():
|
|
| 857 |
# ==============================================================================
|
| 858 |
# 6. 物理点火启动链 (Physical Start Engine)
|
| 859 |
# ==============================================================================
|
| 860 |
-
# 创建初始 Bot 实例
|
| 861 |
bot = create_bot_with_commands()
|
| 862 |
|
| 863 |
if __name__ == "__main__":
|
|
@@ -869,17 +974,13 @@ if __name__ == "__main__":
|
|
| 869 |
logger.info(f" Session: {SESSION_ID}")
|
| 870 |
logger.info("=" * 55)
|
| 871 |
|
| 872 |
-
# 1. 物理启动 Gradio 保活接口 (替代原 Flask keep_alive)
|
| 873 |
keep_alive()
|
| 874 |
|
| 875 |
-
# 2. [HuggingFace 专用] 等待容器网络就绪
|
| 876 |
logger.info("⏳ [Boot] Waiting 10s for container network to initialize...")
|
| 877 |
time.sleep(10)
|
| 878 |
|
| 879 |
-
# 3. 逻辑启动 Discord Bot 核心 (主线程,与原 Replit 架构一致)
|
| 880 |
if not DISCORD_TOKEN:
|
| 881 |
logger.critical("❌ CRITICAL: DISCORD_TOKEN is missing in environment.")
|
| 882 |
-
# 保持进程存活以维持 Gradio 哨兵
|
| 883 |
while True:
|
| 884 |
time.sleep(3600)
|
| 885 |
else:
|
|
@@ -890,38 +991,30 @@ if __name__ == "__main__":
|
|
| 890 |
wait = min(30 * attempt, 120)
|
| 891 |
logger.info(f"⏳ [Boot] Retry in {wait}s (attempt {attempt}/{max_retries})...")
|
| 892 |
time.sleep(wait)
|
| 893 |
-
# bot.run() 关闭后实例不可复用,必须重建
|
| 894 |
bot = create_bot_with_commands()
|
| 895 |
-
|
| 896 |
logger.info(f"🚀 [Boot] Starting Discord connection (attempt {attempt}/{max_retries})...")
|
| 897 |
bot.run(DISCORD_TOKEN)
|
| 898 |
-
break
|
| 899 |
-
|
| 900 |
except discord.LoginFailure:
|
| 901 |
logger.critical("❌ [FATAL] Invalid DISCORD_TOKEN! Check token in Secrets.")
|
| 902 |
-
break
|
| 903 |
-
|
| 904 |
except discord.PrivilegedIntentsRequired:
|
| 905 |
logger.critical(
|
| 906 |
"❌ [FATAL] Privileged Intents not enabled!\n"
|
| 907 |
"→ https://discord.com/developers/applications → Bot tab\n"
|
| 908 |
"→ Enable 'SERVER MEMBERS INTENT' and 'MESSAGE CONTENT INTENT'"
|
| 909 |
)
|
| 910 |
-
break
|
| 911 |
-
|
| 912 |
except (OSError, ConnectionError) as e:
|
| 913 |
logger.error(f"🌐 [Boot] Network error (attempt {attempt}): {e}")
|
| 914 |
-
# 网络错误值得重试
|
| 915 |
-
|
| 916 |
except Exception as e:
|
| 917 |
logger.critical(f"🔥 ENGINE COLLAPSE (attempt {attempt}): {e}")
|
| 918 |
traceback.print_exc()
|
| 919 |
|
| 920 |
-
# 即使 Bot 停止,保持主线程存活以维持 Gradio
|
| 921 |
logger.warning("🔴 [Boot] Bot stopped. Keeping process alive for Gradio...")
|
| 922 |
while True:
|
| 923 |
time.sleep(3600)
|
| 924 |
|
| 925 |
# ==============================================================================
|
| 926 |
-
# END OF ARCHITECT SOURCE CODE | BK-GTA V62.9 — HuggingFace Edition
|
| 927 |
# ==============================================================================
|
|
|
|
| 35 |
# 0.5 [HuggingFace 专用] DNS 稳定性修复 (增强版)
|
| 36 |
# ==============================================================================
|
| 37 |
def fix_dns():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
try:
|
| 39 |
with open('/etc/resolv.conf', 'r') as f:
|
| 40 |
current = f.read()
|
|
|
|
| 45 |
except Exception as e:
|
| 46 |
logger.warning(f"⚠️ [DNS] resolv.conf write failed: {e}")
|
| 47 |
|
|
|
|
| 48 |
hosts_to_resolve = [
|
| 49 |
+
'discord.com', 'gateway.discord.gg',
|
| 50 |
+
'cdn.discordapp.com', 'api.discord.com',
|
|
|
|
|
|
|
| 51 |
]
|
|
|
|
| 52 |
try:
|
| 53 |
import dns.resolver
|
|
|
|
| 54 |
resolver = dns.resolver.Resolver(configure=False)
|
| 55 |
resolver.nameservers = ['8.8.8.8', '1.1.1.1', '8.8.4.4']
|
| 56 |
resolver.timeout = 10
|
| 57 |
resolver.lifetime = 10
|
|
|
|
| 58 |
hosts_lines = []
|
| 59 |
for hostname in hosts_to_resolve:
|
| 60 |
try:
|
|
|
|
| 64 |
logger.info(f"✅ [DNS] Resolved {hostname} → {ip}")
|
| 65 |
except Exception as e:
|
| 66 |
logger.warning(f"⚠️ [DNS] Failed to resolve {hostname}: {e}")
|
|
|
|
| 67 |
if hosts_lines:
|
| 68 |
try:
|
| 69 |
with open('/etc/hosts', 'a') as f:
|
|
|
|
| 77 |
logger.warning(f"⚠️ [DNS] hosts write error: {e}")
|
| 78 |
else:
|
| 79 |
logger.error("❌ [DNS] Could not resolve any Discord hosts.")
|
|
|
|
| 80 |
except ImportError:
|
| 81 |
logger.warning("⚠️ [DNS] dnspython not installed, skipping hosts injection.")
|
| 82 |
except Exception as e:
|
|
|
|
| 90 |
DISCORD_TOKEN = os.environ.get('DISCORD_TOKEN', '').strip()
|
| 91 |
SESSION_ID = random.randint(10000, 99999)
|
| 92 |
|
|
|
|
| 93 |
raw_keys_pool = os.environ.get('GEMINI_KEYS_POOL', '')
|
| 94 |
KEY_POOL = []
|
|
|
|
| 95 |
if raw_keys_pool:
|
| 96 |
parts = raw_keys_pool.split(',')
|
| 97 |
for k in parts:
|
|
|
|
| 107 |
else:
|
| 108 |
logger.critical("❌ [CRITICAL] NO API KEYS FOUND.")
|
| 109 |
|
|
|
|
| 110 |
intents = discord.Intents.default()
|
| 111 |
intents.message_content = True
|
| 112 |
intents.members = True
|
| 113 |
|
|
|
|
| 114 |
ACTIVE_MODEL_ID = "gemini-2.0-flash-lite"
|
|
|
|
|
|
|
| 115 |
DB_PATH = '/data/bk_gta_v62_unified.json' if os.path.exists('/data') else 'bk_gta_v62_unified.json'
|
| 116 |
|
| 117 |
# ==============================================================================
|
| 118 |
# 2. 状态引擎与数据库 (V62.9 Unified Analytics)
|
| 119 |
# ==============================================================================
|
| 120 |
class BKEngine:
|
|
|
|
|
|
|
|
|
|
| 121 |
def __init__(self):
|
| 122 |
self.db_file = DB_PATH
|
| 123 |
self.data = self._load_db()
|
|
|
|
| 131 |
try:
|
| 132 |
with open(self.db_file, 'r', encoding='utf-8') as f:
|
| 133 |
content = json.load(f)
|
|
|
|
| 134 |
if "users" not in content: content["users"] = {}
|
| 135 |
return content
|
| 136 |
except Exception as e:
|
|
|
|
| 151 |
}
|
| 152 |
|
| 153 |
def save_db(self):
|
|
|
|
| 154 |
try:
|
| 155 |
temp_file = self.db_file + '.tmp'
|
| 156 |
with open(temp_file, 'w', encoding='utf-8') as f:
|
|
|
|
| 163 |
uid = str(user_id)
|
| 164 |
if uid not in self.data["users"]:
|
| 165 |
self.data["users"][uid] = {
|
| 166 |
+
"elo": 1200, "points": 1000, "faction": None,
|
| 167 |
+
"audits_count": 0, "total_score": 0.0, "highest_score": 0.0,
|
| 168 |
+
"win_streak": 0, "mdd_record": 0.0, "sharpe_record": 0.0,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
"performance_log": [],
|
| 170 |
"last_update": str(datetime.datetime.now())
|
| 171 |
}
|
|
|
|
| 179 |
u["highest_score"] = score
|
| 180 |
if mdd is not None: u["mdd_record"] = mdd
|
| 181 |
if sharpe is not None: u["sharpe_record"] = sharpe
|
|
|
|
|
|
|
| 182 |
u["points"] += 50
|
| 183 |
u["last_update"] = str(datetime.datetime.now())
|
| 184 |
u["performance_log"].append({"score": score, "time": str(datetime.datetime.now())})
|
| 185 |
self.save_db()
|
| 186 |
|
| 187 |
def update_elo(self, winner_id, loser_id):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
w = self.get_user(winner_id)
|
| 189 |
l = self.get_user(loser_id)
|
|
|
|
| 190 |
K = 32
|
| 191 |
Rw, Rl = w["elo"], l["elo"]
|
|
|
|
| 192 |
Ew = 1 / (1 + 10 ** ((Rl - Rw) / 400))
|
|
|
|
|
|
|
| 193 |
w["elo"] = round(Rw + K * (1 - Ew))
|
| 194 |
l["elo"] = round(Rl + K * (0 - Ew))
|
|
|
|
| 195 |
w["win_streak"] += 1
|
| 196 |
l["win_streak"] = 0
|
|
|
|
|
|
|
| 197 |
if w["faction"] and w["faction"] in self.data["factions"]:
|
| 198 |
self.data["factions"][w["faction"]]["score"] += 15
|
| 199 |
self.data["factions"][w["faction"]]["total_wins"] += 1
|
|
|
|
| 200 |
self.save_db()
|
| 201 |
return w["elo"], l["elo"], w["win_streak"]
|
| 202 |
|
|
|
|
| 206 |
# 3. 核心辅助内核 (Helper Kernels & Security)
|
| 207 |
# ==============================================================================
|
| 208 |
def safe_extract_float(text, key):
|
|
|
|
| 209 |
try:
|
| 210 |
patterns = [
|
| 211 |
rf"{key}:\s*([-+]?\d*\.?\d+)",
|
|
|
|
| 219 |
except Exception: return 0.0
|
| 220 |
|
| 221 |
def safe_extract_text(text, key_start, key_end=None):
|
|
|
|
| 222 |
try:
|
| 223 |
if key_end:
|
| 224 |
pattern = rf"{key_start}:(.*?){key_end}:"
|
|
|
|
| 227 |
match = re.search(pattern, text, re.DOTALL | re.IGNORECASE)
|
| 228 |
if match:
|
| 229 |
content = match.group(1).strip()
|
|
|
|
| 230 |
return content.replace('*', '').replace('`', '').strip()
|
| 231 |
return "N/A"
|
| 232 |
except Exception: return "N/A"
|
| 233 |
|
| 234 |
async def call_gemini_multiplex(prompt, attachment=None):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
if not KEY_POOL:
|
| 236 |
logger.error("Request Blocked: No API Keys available.")
|
| 237 |
return "ERROR: NO_KEYS"
|
|
|
|
| 245 |
return "ERROR: IMAGE_READ_FAILURE"
|
| 246 |
|
| 247 |
import aiohttp
|
|
|
|
|
|
|
| 248 |
parts = [{"text": prompt}]
|
| 249 |
if image_data:
|
| 250 |
b64 = base64.b64encode(image_data).decode("utf-8")
|
| 251 |
+
parts.append({"inlineData": {"mimeType": "image/png", "data": b64}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
payload = {"contents": [{"parts": parts}]}
|
| 253 |
|
|
|
|
| 254 |
api_versions = ["v1beta", "v1"]
|
|
|
|
| 255 |
for api_ver in api_versions:
|
| 256 |
for idx, api_key in enumerate(KEY_POOL):
|
| 257 |
try:
|
|
|
|
| 259 |
f"https://generativelanguage.googleapis.com/{api_ver}/models/"
|
| 260 |
f"{ACTIVE_MODEL_ID}:generateContent?key={api_key}"
|
| 261 |
)
|
|
|
|
| 262 |
async with aiohttp.ClientSession() as session:
|
| 263 |
async with session.post(
|
| 264 |
url, json=payload,
|
| 265 |
timeout=aiohttp.ClientTimeout(total=30)
|
| 266 |
) as resp:
|
| 267 |
status = resp.status
|
|
|
|
| 268 |
if status == 200:
|
| 269 |
data = await resp.json()
|
| 270 |
text = data["candidates"][0]["content"]["parts"][0]["text"]
|
| 271 |
logger.info(f"✅ Gemini OK via {api_ver} key #{idx+1}")
|
| 272 |
return text
|
|
|
|
| 273 |
elif status == 429:
|
| 274 |
logger.warning(f"Key #{idx+1} ({api_ver}) got 429, switching...")
|
| 275 |
await asyncio.sleep(3)
|
| 276 |
continue
|
|
|
|
| 277 |
elif status == 404:
|
| 278 |
logger.warning(f"Model not found on {api_ver}, trying next version...")
|
| 279 |
+
break
|
|
|
|
| 280 |
else:
|
| 281 |
body = await resp.text()
|
| 282 |
logger.error(f"Key #{idx+1} ({api_ver}) HTTP {status}: {body[:300]}")
|
| 283 |
continue
|
|
|
|
| 284 |
except asyncio.TimeoutError:
|
| 285 |
logger.warning(f"Key #{idx+1} timeout, switching...")
|
| 286 |
continue
|
|
|
|
| 293 |
# ==============================================================================
|
| 294 |
# 4. Gradio 保活哨兵 (替代原 Flask keep_alive)
|
| 295 |
# ==============================================================================
|
|
|
|
| 296 |
bot = None
|
| 297 |
|
| 298 |
def get_status():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 300 |
latency = "N/A"
|
| 301 |
bot_status = "OFFLINE"
|
|
|
|
| 305 |
latency = f"{round(bot.latency * 1000, 2)}ms"
|
| 306 |
except Exception as e:
|
| 307 |
logger.warning(f"Heartbeat probe failed: {e}")
|
|
|
|
| 308 |
uptime = str(datetime.datetime.now() - bk_engine.system_uptime).split('.')[0]
|
|
|
|
| 309 |
return {
|
| 310 |
"status": "BK-GTA-V62.9-ULTIMATE-ACTIVE",
|
| 311 |
"engine": "BK-GTA-Logic-Kernel",
|
|
|
|
| 320 |
}
|
| 321 |
|
| 322 |
def keep_alive():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
def run_gradio():
|
| 324 |
try:
|
|
|
|
| 325 |
gradio_logger = logging.getLogger('gradio')
|
| 326 |
gradio_logger.setLevel(logging.WARNING)
|
|
|
|
| 327 |
iface = gr.Interface(
|
| 328 |
+
fn=get_status, inputs=None, outputs="json",
|
|
|
|
|
|
|
| 329 |
title="BK-GTA Sentinel AI",
|
| 330 |
description="Physical architecture monitor. Logic Integrity: Secure."
|
| 331 |
)
|
| 332 |
iface.launch(server_name="0.0.0.0", server_port=7860)
|
| 333 |
except Exception as e:
|
| 334 |
logger.error(f"⚠️ [Sentinel] Gradio Server Failure: {e}")
|
|
|
|
| 335 |
t = threading.Thread(target=run_gradio)
|
| 336 |
t.daemon = True
|
| 337 |
t.start()
|
|
|
|
| 341 |
# 5. Bot 工厂 + 全模块命令注册 (Bot Factory & Full Command Registration)
|
| 342 |
# ==============================================================================
|
| 343 |
def create_bot_with_commands():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
global bot
|
| 345 |
new_bot = commands.Bot(command_prefix='!', intents=intents)
|
| 346 |
+
bot = new_bot
|
| 347 |
|
| 348 |
# ==================================================================
|
| 349 |
# 模块一:全网天梯与个人档案 (Rank & Profile System)
|
|
|
|
| 353 |
"""展现量化交易员的详细画像与 ELO 评级"""
|
| 354 |
target = member or ctx.author
|
| 355 |
u = bk_engine.get_user(target.id)
|
|
|
|
|
|
|
| 356 |
elo = u['elo']
|
| 357 |
if elo < 1300: rank, color = "Iron (学徒) 🗑️", 0x717d7e
|
| 358 |
elif elo < 1500: rank, color = "Bronze (资深) 🥉", 0xcd7f32
|
|
|
|
| 364 |
emb = discord.Embed(title=f"📊 Quantitative Profile: {target.display_name}", color=color)
|
| 365 |
emb.add_field(name="Rating (ELO)", value=f"**{elo}**\nRank: `{rank}`", inline=True)
|
| 366 |
emb.add_field(name="Assets (Points)", value=f"💰 **{u['points']}**", inline=True)
|
|
|
|
| 367 |
f_name = u['faction']
|
| 368 |
f_emoji = bk_engine.data["factions"].get(f_name, {}).get("emoji", "🌐") if f_name else "Ronin"
|
| 369 |
emb.add_field(name="Faction", value=f"{f_emoji} {f_name or 'Unassigned'}", inline=True)
|
|
|
|
|
|
|
| 370 |
perf_stats = (
|
| 371 |
f"Total Audits: {u['audits_count']}\n"
|
| 372 |
f"Peak Audit Score: {u['highest_score']:.2f}\n"
|
|
|
|
| 374 |
f"Max Win Streak: {u['win_streak']}"
|
| 375 |
)
|
| 376 |
emb.add_field(name="Risk-Adjusted Performance", value=f"```yaml\n{perf_stats}\n```", inline=False)
|
|
|
|
| 377 |
if target.avatar:
|
| 378 |
emb.set_thumbnail(url=target.avatar.url)
|
|
|
|
| 379 |
emb.set_footer(text=f"System Session: {SESSION_ID} | Logic V62.9")
|
| 380 |
await ctx.send(embed=emb)
|
| 381 |
|
|
|
|
| 385 |
all_users = []
|
| 386 |
for uid, data in bk_engine.data["users"].items():
|
| 387 |
all_users.append({"id": uid, "elo": data["elo"], "score": data["highest_score"]})
|
|
|
|
|
|
|
| 388 |
top_elo = sorted(all_users, key=lambda x: x["elo"], reverse=True)[:10]
|
|
|
|
| 389 |
desc = ""
|
| 390 |
for idx, u in enumerate(top_elo):
|
| 391 |
medal = "🥇" if idx == 0 else "🥈" if idx == 1 else "🥉" if idx == 2 else f"{idx+1}."
|
| 392 |
desc += f"{medal} <@{u['id']}> : ELO **{u['elo']}** | Peak Score: **{u['score']:.1f}**\n"
|
|
|
|
| 393 |
emb = discord.Embed(
|
| 394 |
title="🏆 BK-GTA Institutional Leaderboard",
|
| 395 |
description=desc or "Scanning market data... (Waiting for submissions)",
|
| 396 |
color=0xf1c40f
|
| 397 |
)
|
|
|
|
|
|
|
| 398 |
f_info = ""
|
| 399 |
for name, info in bk_engine.data["factions"].items():
|
| 400 |
f_info += f"{info['emoji']} {name}: {info['score']} pts "
|
| 401 |
if f_info:
|
| 402 |
emb.add_field(name="⚔️ Faction War Status", value=f_info, inline=False)
|
|
|
|
| 403 |
await ctx.send(embed=emb)
|
| 404 |
|
| 405 |
# ==================================================================
|
|
|
|
| 410 |
"""分析新闻/报表截图,并基于机构逻辑给出宏观评分"""
|
| 411 |
if not ctx.message.attachments:
|
| 412 |
return await ctx.send("❌ Audit Refused: 请上传新闻图表。")
|
|
|
|
| 413 |
m = await ctx.send("📡 **[System] Parsing Macro Pulse...**")
|
|
|
|
| 414 |
prompt = (
|
| 415 |
"你是一名对冲基金首席策略师。请解析此截图并输出:\n"
|
| 416 |
"1. 情绪评分 (SCORE): -10 (极度利空) 到 +10 (极度利多)。\n"
|
| 417 |
"2. 核心变量 (SUMMARY): 30字内概括。\n"
|
| 418 |
"3. 隐含逻辑 (TAKE): 机构视角的解读 (100字内)。\n"
|
| 419 |
"输出格式必须严格如下:\n"
|
| 420 |
+
"SCORE: [分值]\nSUMMARY: [内容]\nTAKE: [解读]"
|
|
|
|
|
|
|
| 421 |
)
|
|
|
|
| 422 |
res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
|
|
|
|
| 423 |
if "ERROR" in res:
|
| 424 |
await m.delete()
|
| 425 |
return await ctx.send("🔴 [Audit Failure] Intelligence node offline.")
|
|
|
|
| 426 |
score = safe_extract_float(res, "SCORE")
|
| 427 |
summary = safe_extract_text(res, "SUMMARY", "TAKE")
|
| 428 |
take = safe_extract_text(res, "TAKE")
|
|
|
|
| 429 |
color = 0x2ecc71 if score > 0 else 0xe74c3c if score < 0 else 0xbdc3c7
|
| 430 |
emb = discord.Embed(title="📰 Macro Sentiment Pulse", color=color)
|
| 431 |
emb.add_field(name="Sentiment Score", value=f"**{score:+.1f} / 10**", inline=True)
|
| 432 |
emb.add_field(name="Core Catalyst", value=summary, inline=False)
|
| 433 |
emb.add_field(name="🏦 Institutional Analysis", value=f"```\n{take}\n```", inline=False)
|
|
|
|
| 434 |
await m.delete()
|
| 435 |
await ctx.send(embed=emb)
|
| 436 |
|
|
|
|
| 442 |
"""基于截图全维度识别回测/实盘参数,并执行风控打分"""
|
| 443 |
if not ctx.message.attachments:
|
| 444 |
return await ctx.send("❌ Audit Refused: 请上传结算单或净值曲线。")
|
|
|
|
| 445 |
sid = random.randint(1000, 9999)
|
| 446 |
m = await ctx.send(f"🛡️ **[SID:{sid}] Audit Engine Initializing...**")
|
|
|
|
|
|
|
| 447 |
prompt = (
|
| 448 |
"你是一名顶级量化风险官。请执行全维度审计。\n"
|
| 449 |
"【提取要求】:\n"
|
|
|
|
| 453 |
"输出格式:\n"
|
| 454 |
"RET: [值]\nMDD: [值]\nPF: [值]\nSHARPE: [值]\nTRADES: [笔数]\nS_BASE: [分]\nH_ZH: [优势]"
|
| 455 |
)
|
|
|
|
| 456 |
res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
|
|
|
|
| 457 |
if "ERROR" in res:
|
| 458 |
await m.delete()
|
| 459 |
return await ctx.send("🔴 [Audit Crash] Engine disconnected.")
|
|
|
|
|
|
|
| 460 |
ret = safe_extract_float(res, "RET")
|
| 461 |
mdd = safe_extract_float(res, "MDD")
|
| 462 |
pf = safe_extract_float(res, "PF")
|
| 463 |
sharpe = safe_extract_float(res, "SHARPE")
|
| 464 |
trades = int(safe_extract_float(res, "TRADES"))
|
| 465 |
s_base = safe_extract_float(res, "S_BASE")
|
|
|
|
|
|
|
|
|
|
| 466 |
if mdd > 35.0:
|
| 467 |
s_final = 0.0
|
| 468 |
status = "⚠️ REJECTED (High MDD Risk)"
|
|
|
|
| 473 |
s_final = min(max(s_final, 0.0), 100.0)
|
| 474 |
status = "✅ AUDIT CLEARED"
|
| 475 |
color = 0x2ecc71
|
|
|
|
|
|
|
| 476 |
bk_engine.update_audit_stats(ctx.author.id, s_final, mdd, sharpe)
|
| 477 |
h_zh = safe_extract_text(res, "H_ZH")
|
|
|
|
| 478 |
emb = discord.Embed(title="🛡️ BK-GTA Gene Audit Report", color=color)
|
| 479 |
emb.add_field(name="Verdict", value=f"**{status}**", inline=False)
|
|
|
|
| 480 |
metrics = (
|
| 481 |
f"💰 Return: {ret}% | MDD: {mdd}%\n"
|
| 482 |
f"⚖️ PF: {pf} | Sharpe: {sharpe}\n"
|
|
|
|
| 485 |
emb.add_field(name="Data Extraction", value=f"```\n{metrics}\n```", inline=False)
|
| 486 |
emb.add_field(name="Analysis Score", value=f"**{s_final:.2f} / 100**", inline=True)
|
| 487 |
emb.add_field(name="Highlights", value=h_zh, inline=False)
|
|
|
|
| 488 |
emb.set_footer(text=f"SID: {sid} | Assets +50")
|
| 489 |
await m.delete()
|
| 490 |
await ctx.send(embed=emb)
|
|
|
|
| 498 |
"""由合伙人开启的宏观/行情博弈盘口"""
|
| 499 |
bid = str(random.randint(100, 999))
|
| 500 |
bk_engine.active_bets[bid] = {
|
| 501 |
+
"topic": topic, "up": 0, "down": 0,
|
|
|
|
| 502 |
"u_up": {}, "u_down": {},
|
| 503 |
"status": "OPEN",
|
| 504 |
"start_time": str(datetime.datetime.now())
|
|
|
|
| 510 |
"""交易员消耗积分参与盘口对冲"""
|
| 511 |
if bid not in bk_engine.active_bets:
|
| 512 |
return await ctx.send("❌ Error: Invalid Bet ID.")
|
|
|
|
| 513 |
b = bk_engine.active_bets[bid]
|
| 514 |
if b["status"] != "OPEN":
|
| 515 |
return await ctx.send("❌ Error: This market is closed.")
|
|
|
|
| 516 |
u = bk_engine.get_user(ctx.author.id)
|
| 517 |
if u["points"] < amt or amt <= 0:
|
| 518 |
return await ctx.send("❌ Error: Insufficient asset points.")
|
|
|
|
| 519 |
side = side.upper()
|
| 520 |
if side not in ["UP", "DOWN"]:
|
| 521 |
return await ctx.send("❌ Error: Choose UP or DOWN.")
|
|
|
|
|
|
|
| 522 |
u["points"] -= amt
|
| 523 |
uid = str(ctx.author.id)
|
|
|
|
| 524 |
pool_key = "u_up" if side == "UP" else "u_down"
|
| 525 |
b[side.lower()] += amt
|
| 526 |
b[pool_key][uid] = b[pool_key].get(uid, 0) + amt
|
|
|
|
| 527 |
bk_engine.save_db()
|
| 528 |
await ctx.send(f"✅ Position Locked: {ctx.author.name} added {amt} to {side} pool.")
|
| 529 |
|
|
|
|
| 534 |
if bid not in bk_engine.active_bets: return
|
| 535 |
b = bk_engine.active_bets[bid]
|
| 536 |
winner = winner.upper()
|
|
|
|
| 537 |
win_pool = b["u_up"] if winner == "UP" else b["u_down"]
|
| 538 |
total_win = b["up"] if winner == "UP" else b["down"]
|
| 539 |
total_lose = b["down"] if winner == "UP" else b["up"]
|
|
|
|
| 540 |
if total_win > 0:
|
| 541 |
for uid, amt in win_pool.items():
|
|
|
|
| 542 |
profit = (amt / total_win) * total_lose
|
| 543 |
user = bk_engine.get_user(int(uid))
|
| 544 |
user["points"] += round(amt + profit)
|
|
|
|
| 545 |
b["status"] = "SETTLED"
|
| 546 |
bk_engine.save_db()
|
| 547 |
await ctx.send(f"🏁 Bet `{bid}` Settled! Winner: **{winner}**. Profits Distributed.")
|
| 548 |
|
| 549 |
# ==================================================================
|
| 550 |
+
# 模块五:PVP 竞技场 (Arena PvP Engine) [增强版]
|
| 551 |
# ==================================================================
|
| 552 |
@new_bot.command()
|
| 553 |
+
async def challenge(ctx, member: discord.Member, days: int = 1):
|
| 554 |
+
"""发起基于量化能力的 1对1 竞技(支持 1-30 天周期)"""
|
| 555 |
if member.id == ctx.author.id:
|
| 556 |
return await ctx.send("❌ Logical Error: 不能向自己发起决斗。")
|
| 557 |
+
if days < 1 or days > 30:
|
| 558 |
+
return await ctx.send("❌ 周期范围: 1-30 天。")
|
| 559 |
did = f"DUEL-{random.randint(100, 999)}"
|
| 560 |
bk_engine.pending_challenges[member.id] = {
|
| 561 |
"challenger": ctx.author.id,
|
| 562 |
"did": did,
|
| 563 |
+
"days": days,
|
| 564 |
"expiry": datetime.datetime.now() + datetime.timedelta(hours=1)
|
| 565 |
}
|
| 566 |
+
await ctx.send(
|
| 567 |
+
f"⚔️ **{ctx.author.name}** 发起决斗请求!目标:{member.mention}\n"
|
| 568 |
+
f"ID: `{did}` | 周期: **{days} 天** | `!accept` 接受。"
|
| 569 |
+
)
|
| 570 |
|
| 571 |
@new_bot.command()
|
| 572 |
async def accept(ctx):
|
| 573 |
"""接受竞技场挑战"""
|
| 574 |
if ctx.author.id not in bk_engine.pending_challenges:
|
| 575 |
return await ctx.send("❌ Error: No pending challenges.")
|
|
|
|
| 576 |
req = bk_engine.pending_challenges.pop(ctx.author.id)
|
| 577 |
did = req["did"]
|
| 578 |
+
days = req.get("days", 1)
|
| 579 |
bk_engine.active_duels[did] = {
|
| 580 |
"p1": req["challenger"], "p2": ctx.author.id,
|
| 581 |
"s1": None, "s2": None,
|
| 582 |
+
"days": days,
|
| 583 |
"start": str(datetime.datetime.now())
|
| 584 |
}
|
| 585 |
+
await ctx.send(
|
| 586 |
+
f"🔥 **ARENA LOCKED!** Duel ID: `{did}` | 周期: **{days} 天**\n"
|
| 587 |
+
f"双方请通过 `!duel_submit {did}` 上传审计截图。"
|
| 588 |
+
)
|
| 589 |
|
| 590 |
@new_bot.command()
|
| 591 |
async def duel_submit(ctx, did: str):
|
| 592 |
+
"""在对决中提交截图,执行最终胜负判定(含 AI 解说 + 自动晋升)"""
|
| 593 |
if did not in bk_engine.active_duels:
|
| 594 |
return await ctx.send("❌ Error: Duel not active.")
|
|
|
|
| 595 |
if not ctx.message.attachments:
|
| 596 |
return await ctx.send("❌ Refused: 请上传结算截图。")
|
|
|
|
| 597 |
duel = bk_engine.active_duels[did]
|
| 598 |
+
if ctx.author.id not in [duel["p1"], duel["p2"]]:
|
| 599 |
+
return
|
| 600 |
m = await ctx.send(f"🔍 **[Arena] Auditing Submission...**")
|
| 601 |
+
prompt = (
|
| 602 |
+
"Arena Duel Audit: 请识别此交易截图的综合能力评���。\n"
|
| 603 |
+
"FORMAT: SCORE: [0-100的数值]"
|
| 604 |
+
)
|
| 605 |
res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
|
| 606 |
score = safe_extract_float(res, "SCORE")
|
| 607 |
+
if ctx.author.id == duel["p1"]:
|
| 608 |
+
duel["s1"] = score
|
| 609 |
+
else:
|
| 610 |
+
duel["s2"] = score
|
| 611 |
|
| 612 |
+
# 双方都交了 → 执行最终结算
|
|
|
|
|
|
|
|
|
|
| 613 |
if duel["s1"] is not None and duel["s2"] is not None:
|
| 614 |
+
w_score = max(duel["s1"], duel["s2"])
|
| 615 |
+
l_score = min(duel["s1"], duel["s2"])
|
| 616 |
+
wid = duel["p1"] if duel["s1"] > duel["s2"] else duel["p2"]
|
| 617 |
+
lid = duel["p2"] if wid == duel["p1"] else duel["p1"]
|
| 618 |
new_w, new_l, strk = bk_engine.update_elo(wid, lid)
|
| 619 |
|
| 620 |
+
# ========== Arena Shoutcaster AI 解说 ==========
|
| 621 |
+
shout_prompt = (
|
| 622 |
+
f"你是一名热血电竞风格的交易竞技解说员(Arena Shoutcaster)。\n"
|
| 623 |
+
f"两位交易员刚完成了一场量化对决:\n"
|
| 624 |
+
f"🥇 胜者得分: {w_score:.1f} | 🥈 败者得分: {l_score:.1f}\n"
|
| 625 |
+
f"请用激昂的中文解说风格,输出 80 字以内的赛事评论。\n"
|
| 626 |
+
f"分析胜者核心优势、败者改进方向,像体育赛事解说一样精彩。\n"
|
| 627 |
+
f"FORMAT: COMMENTARY: [解说内容]"
|
| 628 |
+
)
|
| 629 |
+
shout_res = await call_gemini_multiplex(shout_prompt)
|
| 630 |
+
commentary = safe_extract_text(shout_res, "COMMENTARY")
|
| 631 |
+
if commentary == "N/A":
|
| 632 |
+
commentary = (
|
| 633 |
+
shout_res[:200]
|
| 634 |
+
if shout_res and "ERROR" not in shout_res
|
| 635 |
+
else "精彩对决!胜者展现了更出色的风控纪律与策略执行力。"
|
| 636 |
+
)
|
| 637 |
+
|
| 638 |
emb = discord.Embed(title="🏆 Arena Final Verdict", color=0xf1c40f)
|
| 639 |
+
emb.add_field(
|
| 640 |
+
name="🥇 Winner",
|
| 641 |
+
value=f"<@{wid}>\nScore: {w_score:.1f}\nNew ELO: {new_w}",
|
| 642 |
+
inline=True
|
| 643 |
+
)
|
| 644 |
+
emb.add_field(
|
| 645 |
+
name="🥈 Defeated",
|
| 646 |
+
value=f"<@{lid}>\nScore: {l_score:.1f}\nNew ELO: {new_l}",
|
| 647 |
+
inline=True
|
| 648 |
+
)
|
| 649 |
+
emb.add_field(
|
| 650 |
+
name="🎙️ Arena Shoutcaster",
|
| 651 |
+
value=f"```\n{commentary}\n```",
|
| 652 |
+
inline=False
|
| 653 |
+
)
|
| 654 |
emb.set_footer(text=f"Streak: {strk} | Duel ID: {did}")
|
|
|
|
| 655 |
await ctx.send(embed=emb)
|
| 656 |
+
|
| 657 |
+
# ========== 3 连胜自动晋升 Arena Overlord ==========
|
| 658 |
+
if strk >= 3:
|
| 659 |
+
try:
|
| 660 |
+
guild = ctx.guild
|
| 661 |
+
role = discord.utils.get(guild.roles, name="Arena Overlord")
|
| 662 |
+
if not role:
|
| 663 |
+
role = await guild.create_role(
|
| 664 |
+
name="Arena Overlord",
|
| 665 |
+
color=discord.Color.gold(),
|
| 666 |
+
hoist=True,
|
| 667 |
+
reason="BK-GTA 3-Streak Auto Promotion"
|
| 668 |
+
)
|
| 669 |
+
winner_member = guild.get_member(wid)
|
| 670 |
+
if winner_member and role not in winner_member.roles:
|
| 671 |
+
await winner_member.add_roles(role)
|
| 672 |
+
await ctx.send(
|
| 673 |
+
f"🎖️ **PROMOTION!** <@{wid}> 达成 **{strk} 连胜**!\n"
|
| 674 |
+
f"自动晋升为 **Arena Overlord** 👑"
|
| 675 |
+
)
|
| 676 |
+
else:
|
| 677 |
+
await ctx.send(
|
| 678 |
+
f"🔥 <@{wid}> 已达 **{strk} 连胜**!Overlord 霸主地位稳固!"
|
| 679 |
+
)
|
| 680 |
+
except Exception as e:
|
| 681 |
+
logger.warning(f"Auto-promotion failed: {e}")
|
| 682 |
+
await ctx.send(
|
| 683 |
+
f"🎖️ <@{wid}> 达成 **{strk} 连胜**!"
|
| 684 |
+
f"(身份组授予需要 Bot 管理角色权限)"
|
| 685 |
+
)
|
| 686 |
+
|
| 687 |
bk_engine.active_duels.pop(did)
|
| 688 |
else:
|
| 689 |
await ctx.send(f"✅ Submission Received (Score: {score:.1f}). Waiting for Opponent...")
|
|
|
|
| 690 |
await m.delete()
|
| 691 |
|
| 692 |
# ==================================================================
|
|
|
|
| 697 |
"""提取审计数据并渲染具备社交传播属性的海报图片"""
|
| 698 |
if not ctx.message.attachments:
|
| 699 |
return await ctx.send("❌ 请上传审计截图��")
|
|
|
|
| 700 |
m = await ctx.send("🎨 **[GPU-Node] Rendering Institutional Poster...**")
|
|
|
|
| 701 |
prompt = "Extract RET: [val] and SHARPE: [val]. FORMAT: RET: [v] SHARPE: [v]"
|
| 702 |
res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
|
| 703 |
ret, sharpe = safe_extract_float(res, "RET"), safe_extract_float(res, "SHARPE")
|
|
|
|
|
|
|
| 704 |
try:
|
| 705 |
W, H = 600, 800
|
|
|
|
| 706 |
base = Image.new('RGB', (W, H), color=(18, 20, 30))
|
| 707 |
draw = ImageDraw.Draw(base)
|
|
|
|
|
|
|
| 708 |
for i in range(0, W, 40): draw.line([(i, 0), (i, H)], fill=(30, 35, 45), width=1)
|
| 709 |
for j in range(0, H, 40): draw.line([(0, j), (W, j)], fill=(30, 35, 45), width=1)
|
|
|
|
|
|
|
| 710 |
draw.rectangle([10, 10, 590, 790], outline=(46, 204, 113), width=2)
|
| 711 |
draw.rectangle([20, 20, 580, 780], outline=(46, 204, 113), width=5)
|
|
|
|
|
|
|
| 712 |
draw.text((40, 60), "BK-GTA QUANTITATIVE SYSTEM", fill=(255, 255, 255))
|
| 713 |
draw.text((40, 150), f"TRADER ID: {ctx.author.name.upper()}", fill=(46, 204, 113))
|
| 714 |
draw.text((40, 260), f"ALPHA RETURN: +{ret}%", fill=(255, 255, 255))
|
| 715 |
draw.text((40, 360), f"SHARPE RATIO: {sharpe}", fill=(255, 255, 255))
|
| 716 |
draw.text((40, 460), f"SESSION: {SESSION_ID}", fill=(120, 120, 130))
|
|
|
|
|
|
|
| 717 |
draw.text((40, 720), "VERIFIED BY BK-GTA SENTINEL AI", fill=(46, 204, 113))
|
|
|
|
|
|
|
| 718 |
with io.BytesIO() as image_binary:
|
| 719 |
base.save(image_binary, 'PNG')
|
| 720 |
image_binary.seek(0)
|
| 721 |
await ctx.send(file=discord.File(fp=image_binary, filename=f"Viral_{ctx.author.id}.png"))
|
|
|
|
| 722 |
except Exception as e:
|
| 723 |
logger.error(f"Render Error: {e}")
|
| 724 |
await ctx.send(f"❌ 渲染引擎报错: `{e}`")
|
| 725 |
finally:
|
| 726 |
await m.delete()
|
| 727 |
|
| 728 |
+
# ==================================================================
|
| 729 |
+
# 模块七:每日门诊 (Daily Clinic / Sync Log) [新增]
|
| 730 |
+
# ==================================================================
|
| 731 |
+
@new_bot.command()
|
| 732 |
+
async def sync_log(ctx):
|
| 733 |
+
"""每日门诊:AI 深度心理建模,诊断报复性交易与情绪倾斜 (Tilt)"""
|
| 734 |
+
if not ctx.message.attachments:
|
| 735 |
+
return await ctx.send("❌ 请上传今日交易存根/截图进行心理诊断。")
|
| 736 |
+
m = await ctx.send("🏥 **[Clinic] 正在执行深度心理建模...**")
|
| 737 |
+
prompt = (
|
| 738 |
+
"你是一名顶级交易心理学专家与行为金融分析师。请分析此交易记录截图,执行以下诊断:\n"
|
| 739 |
+
"1. TILT_SCORE: 情绪倾斜指数 (0-10, 0=完全理性, 10=严重失控)\n"
|
| 740 |
+
"2. REVENGE_RISK: 报复性交易风险等级 (LOW/MEDIUM/HIGH/CRITICAL)\n"
|
| 741 |
+
"3. PATTERN: 识别的行为模式 (如: 恐惧离场, 过度交易, 追涨杀跌, 锚定偏差等)\n"
|
| 742 |
+
"4. DIAGNOSIS: 中文诊断报告 (100字内, 包含具体问题分析)\n"
|
| 743 |
+
"5. PRESCRIPTION: 中文处方建议 (50字内的具体行动指令)\n"
|
| 744 |
+
"输出格式必须严格如下:\n"
|
| 745 |
+
"TILT_SCORE: [值]\n"
|
| 746 |
+
"REVENGE_RISK: [等级]\n"
|
| 747 |
+
"PATTERN: [模式]\n"
|
| 748 |
+
"DIAGNOSIS: [诊断]\n"
|
| 749 |
+
"PRESCRIPTION: [处方]"
|
| 750 |
+
)
|
| 751 |
+
res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
|
| 752 |
+
if "ERROR" in res:
|
| 753 |
+
await m.delete()
|
| 754 |
+
return await ctx.send("🔴 [Clinic Offline] 诊断节点断连。")
|
| 755 |
+
tilt = safe_extract_float(res, "TILT_SCORE")
|
| 756 |
+
revenge = safe_extract_text(res, "REVENGE_RISK", "PATTERN")
|
| 757 |
+
pattern = safe_extract_text(res, "PATTERN", "DIAGNOSIS")
|
| 758 |
+
diagnosis = safe_extract_text(res, "DIAGNOSIS", "PRESCRIPTION")
|
| 759 |
+
prescription = safe_extract_text(res, "PRESCRIPTION")
|
| 760 |
+
if tilt <= 3:
|
| 761 |
+
color, status = 0x2ecc71, "🟢 心理状态健康"
|
| 762 |
+
elif tilt <= 6:
|
| 763 |
+
color, status = 0xf39c12, "🟡 轻度情绪偏移"
|
| 764 |
+
elif tilt <= 8:
|
| 765 |
+
color, status = 0xe67e22, "🟠 中度情绪倾斜"
|
| 766 |
+
else:
|
| 767 |
+
color, status = 0xe74c3c, "🔴 严重情绪失控"
|
| 768 |
+
emb = discord.Embed(title="🏥 BK-GTA Daily Clinic | 每日门诊", color=color)
|
| 769 |
+
emb.add_field(name="心理状态", value=f"**{status}**", inline=False)
|
| 770 |
+
emb.add_field(name="Tilt Index", value=f"**{tilt:.1f}** / 10", inline=True)
|
| 771 |
+
emb.add_field(name="报复性交易风险", value=f"**{revenge}**", inline=True)
|
| 772 |
+
emb.add_field(name="行为模式识别", value=f"`{pattern}`", inline=False)
|
| 773 |
+
emb.add_field(name="📋 诊断报告", value=f"```\n{diagnosis}\n```", inline=False)
|
| 774 |
+
emb.add_field(name="💊 处方", value=f"```\n{prescription}\n```", inline=False)
|
| 775 |
+
u = bk_engine.get_user(ctx.author.id)
|
| 776 |
+
u["points"] += 30
|
| 777 |
+
bk_engine.save_db()
|
| 778 |
+
emb.set_footer(text=f"Assets +30 | Session: {SESSION_ID}")
|
| 779 |
+
await m.delete()
|
| 780 |
+
await ctx.send(embed=emb)
|
| 781 |
+
|
| 782 |
+
# ==================================================================
|
| 783 |
+
# 模块八:阵营系统 (Faction System) [新增]
|
| 784 |
+
# ==================================================================
|
| 785 |
+
@new_bot.command()
|
| 786 |
+
async def factions(ctx):
|
| 787 |
+
"""查看所有阵营信息与实时对抗比分"""
|
| 788 |
+
emb = discord.Embed(title="⚔️ Faction War | 阵营对抗", color=0xf1c40f)
|
| 789 |
+
for name, info in bk_engine.data["factions"].items():
|
| 790 |
+
member_count = sum(
|
| 791 |
+
1 for u in bk_engine.data["users"].values()
|
| 792 |
+
if u.get("faction") == name
|
| 793 |
+
)
|
| 794 |
+
emb.add_field(
|
| 795 |
+
name=f"{info['emoji']} {name}",
|
| 796 |
+
value=(
|
| 797 |
+
f"Honor Points: **{info['score']}**\n"
|
| 798 |
+
f"Total Wins: **{info['total_wins']}**\n"
|
| 799 |
+
f"Members: **{member_count}**"
|
| 800 |
+
),
|
| 801 |
+
inline=True
|
| 802 |
+
)
|
| 803 |
+
emb.add_field(
|
| 804 |
+
name="📜 加入方式",
|
| 805 |
+
value="`!join BULLS` 或 `!join BEARS`\n⚠️ 绑定后不可更改",
|
| 806 |
+
inline=False
|
| 807 |
+
)
|
| 808 |
+
emb.set_footer(text=f"Session: {SESSION_ID} | V62.9")
|
| 809 |
+
await ctx.send(embed=emb)
|
| 810 |
+
|
| 811 |
+
@new_bot.command()
|
| 812 |
+
async def join(ctx, faction_name: str):
|
| 813 |
+
"""加入阵营,绑定后不可更改"""
|
| 814 |
+
faction_name = faction_name.upper()
|
| 815 |
+
if faction_name not in bk_engine.data["factions"]:
|
| 816 |
+
available = ", ".join(bk_engine.data["factions"].keys())
|
| 817 |
+
return await ctx.send(f"❌ 无效阵营。可选: `{available}`")
|
| 818 |
+
u = bk_engine.get_user(ctx.author.id)
|
| 819 |
+
if u["faction"] is not None:
|
| 820 |
+
emoji = bk_engine.data["factions"][u["faction"]]["emoji"]
|
| 821 |
+
return await ctx.send(f"❌ 你已绑定阵营 {emoji} **{u['faction']}**,不可更改。")
|
| 822 |
+
u["faction"] = faction_name
|
| 823 |
+
bk_engine.save_db()
|
| 824 |
+
f_info = bk_engine.data["factions"][faction_name]
|
| 825 |
+
await ctx.send(
|
| 826 |
+
f"✅ {f_info['emoji']} **{ctx.author.name}** 已加入 **{faction_name}** 阵营!为荣誉而战!"
|
| 827 |
+
)
|
| 828 |
+
|
| 829 |
+
# ==================================================================
|
| 830 |
+
# 模块九:知识库治理 (Knowledge Archive) [新增]
|
| 831 |
+
# ==================================================================
|
| 832 |
+
@new_bot.command()
|
| 833 |
+
@commands.has_permissions(administrator=True)
|
| 834 |
+
async def archive(ctx, msg_id: int):
|
| 835 |
+
"""将优质案例入库至 #📚-case-study 频道永久保存"""
|
| 836 |
+
try:
|
| 837 |
+
source_msg = await ctx.channel.fetch_message(msg_id)
|
| 838 |
+
except discord.NotFound:
|
| 839 |
+
return await ctx.send("❌ 消息未找到,请确认 Message ID 正确。")
|
| 840 |
+
except Exception as e:
|
| 841 |
+
return await ctx.send(f"❌ 获取消息失败: `{e}`")
|
| 842 |
+
|
| 843 |
+
archive_channel = discord.utils.get(ctx.guild.text_channels, name="📚-case-study")
|
| 844 |
+
if not archive_channel:
|
| 845 |
+
archive_channel = discord.utils.get(ctx.guild.text_channels, name="case-study")
|
| 846 |
+
if not archive_channel:
|
| 847 |
+
return await ctx.send("❌ 未找到 `#📚-case-study` 或 `#case-study` 频道,请先创建。")
|
| 848 |
+
|
| 849 |
+
emb = discord.Embed(
|
| 850 |
+
title="📚 Archived Case Study",
|
| 851 |
+
description=source_msg.content[:4096] if source_msg.content else "(Embed/Attachment)",
|
| 852 |
+
color=0x9b59b6,
|
| 853 |
+
timestamp=source_msg.created_at
|
| 854 |
+
)
|
| 855 |
+
emb.add_field(name="Author", value=source_msg.author.mention, inline=True)
|
| 856 |
+
emb.add_field(name="Source", value=ctx.channel.mention, inline=True)
|
| 857 |
+
emb.add_field(name="Archived By", value=ctx.author.mention, inline=True)
|
| 858 |
+
if source_msg.embeds:
|
| 859 |
+
orig = source_msg.embeds[0]
|
| 860 |
+
if orig.description:
|
| 861 |
+
emb.add_field(name="Original Content", value=orig.description[:1024], inline=False)
|
| 862 |
+
|
| 863 |
+
files = []
|
| 864 |
+
for att in source_msg.attachments:
|
| 865 |
+
try:
|
| 866 |
+
file_data = await att.read()
|
| 867 |
+
files.append(discord.File(io.BytesIO(file_data), filename=att.filename))
|
| 868 |
+
except Exception:
|
| 869 |
+
pass
|
| 870 |
+
|
| 871 |
+
await archive_channel.send(embed=emb, files=files if files else None)
|
| 872 |
+
await ctx.send(f"✅ 案例已归档至 {archive_channel.mention}")
|
| 873 |
+
|
| 874 |
+
# ==================================================================
|
| 875 |
+
# 模块十:多语言镜像翻译 (Global Translate Engine) [新增]
|
| 876 |
+
# ==================================================================
|
| 877 |
+
@new_bot.command()
|
| 878 |
+
@commands.has_permissions(administrator=True)
|
| 879 |
+
async def global_translate(ctx, msg_id: int, lang: str = "EN"):
|
| 880 |
+
"""生成指定语言(EN/JP/RU/KR/ES/FR/DE)的镜像报告"""
|
| 881 |
+
try:
|
| 882 |
+
source_msg = await ctx.channel.fetch_message(msg_id)
|
| 883 |
+
except discord.NotFound:
|
| 884 |
+
return await ctx.send("❌ 消息未找到。")
|
| 885 |
+
except Exception as e:
|
| 886 |
+
return await ctx.send(f"❌ 获取消息失败: `{e}`")
|
| 887 |
+
|
| 888 |
+
lang = lang.upper()
|
| 889 |
+
lang_map = {
|
| 890 |
+
"EN": "English", "JP": "Japanese", "RU": "Russian",
|
| 891 |
+
"KR": "Korean", "ES": "Spanish", "FR": "French",
|
| 892 |
+
"DE": "German", "AR": "Arabic", "PT": "Portuguese"
|
| 893 |
+
}
|
| 894 |
+
target_lang = lang_map.get(lang, lang)
|
| 895 |
+
|
| 896 |
+
content = source_msg.content or ""
|
| 897 |
+
if source_msg.embeds:
|
| 898 |
+
for emb in source_msg.embeds:
|
| 899 |
+
if emb.title: content += f"\n{emb.title}"
|
| 900 |
+
if emb.description: content += f"\n{emb.description}"
|
| 901 |
+
for field in emb.fields:
|
| 902 |
+
content += f"\n{field.name}: {field.value}"
|
| 903 |
+
if not content.strip():
|
| 904 |
+
return await ctx.send("❌ 消息内容为空,无法翻译。")
|
| 905 |
+
|
| 906 |
+
m = await ctx.send(f"🌐 **[Translator] Generating {target_lang} mirror...**")
|
| 907 |
+
prompt = (
|
| 908 |
+
f"你是专业金融翻译官。请将以下内容精准翻译为 {target_lang}。\n"
|
| 909 |
+
f"要求:保持金融术语专业性,保留所有数字和格式结构。\n"
|
| 910 |
+
f"原文:\n{content}"
|
| 911 |
+
)
|
| 912 |
+
res = await call_gemini_multiplex(prompt)
|
| 913 |
+
if "ERROR" in res:
|
| 914 |
+
await m.delete()
|
| 915 |
+
return await ctx.send("🔴 翻译引擎离线。")
|
| 916 |
+
emb = discord.Embed(
|
| 917 |
+
title=f"🌐 Mirror Report [{lang}] | {target_lang}",
|
| 918 |
+
description=res[:4096],
|
| 919 |
+
color=0x3498db
|
| 920 |
+
)
|
| 921 |
+
emb.set_footer(text=f"ZH → {target_lang} | Session: {SESSION_ID}")
|
| 922 |
+
await m.delete()
|
| 923 |
+
await ctx.send(embed=emb)
|
| 924 |
+
|
| 925 |
# ==================================================================
|
| 926 |
# 系统对账与自动化任务 (System Reliability)
|
| 927 |
# ==================================================================
|
| 928 |
@tasks.loop(hours=1)
|
| 929 |
async def auto_backup():
|
|
|
|
| 930 |
bk_engine.save_db()
|
| 931 |
logger.info("💾 [Checkpoint] Data persistence executed.")
|
| 932 |
|
| 933 |
@new_bot.event
|
| 934 |
async def on_ready():
|
|
|
|
| 935 |
if not auto_backup.is_running(): auto_backup.start()
|
|
|
|
|
|
|
| 936 |
activity = discord.Activity(type=discord.ActivityType.watching, name="HFT Markets")
|
| 937 |
await new_bot.change_presence(status=discord.Status.online, activity=activity)
|
|
|
|
| 938 |
logger.info(f"--------------------------------------------------")
|
| 939 |
logger.info(f"✅ [V62.9 Ultimate] BK-GTA Architecture Online")
|
| 940 |
+
logger.info(f"🚀 Session: {SESSION_ID} | Commands: {len(new_bot.commands)}")
|
| 941 |
logger.info(f"--------------------------------------------------")
|
| 942 |
|
| 943 |
@new_bot.event
|
|
|
|
| 950 |
|
| 951 |
@new_bot.event
|
| 952 |
async def on_command_error(ctx, error):
|
|
|
|
| 953 |
if isinstance(error, commands.CommandNotFound): return
|
| 954 |
if isinstance(error, commands.MissingPermissions):
|
| 955 |
return await ctx.send("❌ 权限审计未通过:仅限合伙人执行。")
|
|
|
|
|
|
|
| 956 |
exc = "".join(traceback.format_exception(type(error), error, error.__traceback__))
|
| 957 |
logger.error(f"🔥 [System Crash] {exc}")
|
| 958 |
await ctx.send(f"⚠️ [Logic Breach] 内部逻辑断裂: `{error}`")
|
|
|
|
| 963 |
# ==============================================================================
|
| 964 |
# 6. 物理点火启动链 (Physical Start Engine)
|
| 965 |
# ==============================================================================
|
|
|
|
| 966 |
bot = create_bot_with_commands()
|
| 967 |
|
| 968 |
if __name__ == "__main__":
|
|
|
|
| 974 |
logger.info(f" Session: {SESSION_ID}")
|
| 975 |
logger.info("=" * 55)
|
| 976 |
|
|
|
|
| 977 |
keep_alive()
|
| 978 |
|
|
|
|
| 979 |
logger.info("⏳ [Boot] Waiting 10s for container network to initialize...")
|
| 980 |
time.sleep(10)
|
| 981 |
|
|
|
|
| 982 |
if not DISCORD_TOKEN:
|
| 983 |
logger.critical("❌ CRITICAL: DISCORD_TOKEN is missing in environment.")
|
|
|
|
| 984 |
while True:
|
| 985 |
time.sleep(3600)
|
| 986 |
else:
|
|
|
|
| 991 |
wait = min(30 * attempt, 120)
|
| 992 |
logger.info(f"⏳ [Boot] Retry in {wait}s (attempt {attempt}/{max_retries})...")
|
| 993 |
time.sleep(wait)
|
|
|
|
| 994 |
bot = create_bot_with_commands()
|
|
|
|
| 995 |
logger.info(f"🚀 [Boot] Starting Discord connection (attempt {attempt}/{max_retries})...")
|
| 996 |
bot.run(DISCORD_TOKEN)
|
| 997 |
+
break
|
|
|
|
| 998 |
except discord.LoginFailure:
|
| 999 |
logger.critical("❌ [FATAL] Invalid DISCORD_TOKEN! Check token in Secrets.")
|
| 1000 |
+
break
|
|
|
|
| 1001 |
except discord.PrivilegedIntentsRequired:
|
| 1002 |
logger.critical(
|
| 1003 |
"❌ [FATAL] Privileged Intents not enabled!\n"
|
| 1004 |
"→ https://discord.com/developers/applications → Bot tab\n"
|
| 1005 |
"→ Enable 'SERVER MEMBERS INTENT' and 'MESSAGE CONTENT INTENT'"
|
| 1006 |
)
|
| 1007 |
+
break
|
|
|
|
| 1008 |
except (OSError, ConnectionError) as e:
|
| 1009 |
logger.error(f"🌐 [Boot] Network error (attempt {attempt}): {e}")
|
|
|
|
|
|
|
| 1010 |
except Exception as e:
|
| 1011 |
logger.critical(f"🔥 ENGINE COLLAPSE (attempt {attempt}): {e}")
|
| 1012 |
traceback.print_exc()
|
| 1013 |
|
|
|
|
| 1014 |
logger.warning("🔴 [Boot] Bot stopped. Keeping process alive for Gradio...")
|
| 1015 |
while True:
|
| 1016 |
time.sleep(3600)
|
| 1017 |
|
| 1018 |
# ==============================================================================
|
| 1019 |
+
# END OF ARCHITECT SOURCE CODE | BK-GTA V62.9 — HuggingFace Edition (COMPLETE)
|
| 1020 |
# ==============================================================================
|