Riy777 commited on
Commit
5d0404a
·
verified ·
1 Parent(s): f9cec42

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +436 -421
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # ==============================================================================
2
- # 🚀 app.py (V69.0 - GEM-Architect: Monolithic Web Terminal)
3
  # ==============================================================================
4
 
5
  import os
@@ -15,12 +15,10 @@ from contextlib import asynccontextmanager, redirect_stdout, redirect_stderr
15
  from io import StringIO
16
  from typing import List, Dict, Any, Optional
17
 
18
- # ✅ استبدال Gradio بـ FastAPI و HTMLResponse
19
- import uvicorn
20
  from fastapi import FastAPI
21
- from fastapi.responses import HTMLResponse, JSONResponse
22
- from fastapi.middleware.cors import CORSMiddleware
23
  import pandas as pd
 
24
 
25
  # ------------------------------------------------------------------------------
26
  # Logging Setup
@@ -33,7 +31,7 @@ logging.basicConfig(
33
  logger = logging.getLogger("TitanCore")
34
 
35
  # ------------------------------------------------------------------------------
36
- # Imports (Core Logic)
37
  # ------------------------------------------------------------------------------
38
  try:
39
  from r2 import R2Service, INITIAL_CAPITAL
@@ -45,6 +43,7 @@ try:
45
  from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
46
  from learning_hub.adaptive_hub import AdaptiveHub
47
  from trade_manager import TradeManager
 
48
  from periodic_tuner import ContinuousTuner
49
 
50
  try:
@@ -92,7 +91,7 @@ class SystemState:
92
 
93
  def set_ready(self):
94
  self.ready = True
95
- self.last_cycle_logs = "✅ System Ready. Web Terminal Online."
96
  logger.info("✅ System State set to READY.")
97
 
98
  def set_cycle_start(self):
@@ -138,6 +137,11 @@ def calculate_duration_str(timestamp_str):
138
  return f"{hours:02}:{minutes:02}:{seconds:02}"
139
  except: return "--:--:--"
140
 
 
 
 
 
 
141
  # ------------------------------------------------------------------------------
142
  # Auto-Pilot Daemon
143
  # ------------------------------------------------------------------------------
@@ -154,11 +158,9 @@ async def auto_pilot_loop():
154
 
155
  if trade_manager and len(trade_manager.open_positions) > 0:
156
  wd_status = await trade_manager.ensure_active_guardians()
157
- # لا نقوم بحشو السجلات إذا لم يكن هناك دورة نشطة
158
  if "No active" not in wd_status:
159
  if not sys_state.cycle_running:
160
- # تحديث بسيط للسجل فقط في حالة عدم وجود مسح
161
- pass
162
  continue
163
 
164
  if sys_state.auto_pilot and not sys_state.cycle_running and not sys_state.training_running:
@@ -176,13 +178,13 @@ async def auto_pilot_loop():
176
  await asyncio.sleep(30)
177
 
178
  # ------------------------------------------------------------------------------
179
- # Lifespan (Startup/Shutdown)
180
  # ------------------------------------------------------------------------------
181
  @asynccontextmanager
182
  async def lifespan(app: FastAPI):
183
  global r2, data_manager, ml_processor, adaptive_hub, trade_manager, whale_monitor, news_fetcher, senti_analyzer, sys_state
184
 
185
- logger.info("\n🚀 [System] Startup Sequence (Titan V69.0 - Monolithic Web)...")
186
  try:
187
  r2 = R2Service()
188
  data_manager = DataManager(contracts_db={}, whale_monitor=None, r2_service=r2)
@@ -206,6 +208,7 @@ async def lifespan(app: FastAPI):
206
  trade_manager = TradeManager(r2_service=r2, data_manager=data_manager, processor=ml_processor)
207
  trade_manager.learning_hub = adaptive_hub
208
 
 
209
  adaptive_hub.tuner = ContinuousTuner(adaptive_hub)
210
  logger.info("🔧 [Tuner] Continuous Tuner Injected.")
211
 
@@ -214,7 +217,7 @@ async def lifespan(app: FastAPI):
214
 
215
  sys_state.set_ready()
216
  asyncio.create_task(auto_pilot_loop())
217
- logger.info("✅ [System READY] All modules operational. Serving UI on port 7860.")
218
  yield
219
 
220
  except Exception as e:
@@ -228,7 +231,7 @@ async def lifespan(app: FastAPI):
228
  logger.info("✅ [System] Shutdown Complete.")
229
 
230
  # ------------------------------------------------------------------------------
231
- # Helper Tasks (Logic)
232
  # ------------------------------------------------------------------------------
233
  async def _analyze_symbol_task(candidate_data: Dict[str, Any]) -> Dict[str, Any]:
234
  try:
@@ -267,7 +270,7 @@ async def _analyze_symbol_task(candidate_data: Dict[str, Any]) -> Dict[str, Any]
267
  except Exception: return None
268
 
269
  # ------------------------------------------------------------------------------
270
- # Unified Cycle (Full Logic)
271
  # ------------------------------------------------------------------------------
272
  async def run_unified_cycle():
273
  log_buffer = StringIO()
@@ -302,16 +305,20 @@ async def run_unified_cycle():
302
  tasks = [_analyze_symbol_task(c) for c in candidates]
303
  results = await asyncio.gather(*tasks)
304
 
 
305
  valid_l2 = [res for res in results if res is not None and res.get('is_valid', False)]
306
  rejected_l2 = [res for res in results if res is not None and not res.get('is_valid', False)]
307
 
 
308
  rejected_l2.sort(key=lambda x: x.get('enhanced_final_score', 0.0), reverse=True)
309
 
310
  if not valid_l2:
311
- log_and_print("⚠️ No valid L2 candidates. Top 10 Rejected:")
 
312
  rej_header = f"{'SYM':<9} | {'TITAN':<5} | {'PATT':<5} | {'MC':<5} | {'FINAL':<5} | {'REASON'}"
313
  log_and_print(rej_header)
314
  log_and_print("-" * 100)
 
315
  for rej in rejected_l2[:10]:
316
  sym = rej['symbol']
317
  tit = rej.get('titan_score', 0.0)
@@ -375,10 +382,11 @@ async def run_unified_cycle():
375
 
376
  approved_signals = []
377
 
 
378
  header = (f"{'SYM':<9} | {'TYPE':<10} | {'L2':<5} | {'TITAN':<5} | {'PATT':<5} | "
379
  f"{'NEWS':<5} | {'WHALE':<6} | {'MC(L)':<5} | {'MC(A)':<5} | {'FINAL':<6} | {'ORACLE':<6} | {'STATUS'}")
380
 
381
- log_and_print("-" * 130)
382
  log_and_print(header)
383
  log_and_print("-" * 130)
384
 
@@ -400,17 +408,29 @@ async def run_unified_cycle():
400
  l2_hybrid = sig.get('enhanced_final_score', 0.0)
401
  titan_d = sig.get('titan_score', 0.0)
402
  patt_d = sig.get('patterns_score', 0.0)
 
 
403
  news_d = sig.get('news_score', 0.0)
404
  whale_d = sig.get('whale_score', 0.0)
405
- mcl_d = sig.get('mc_score', 0.0)
406
- mca_d = sig.get('mc_advanced_score', 0.0)
 
407
  final_d = sig.get('final_total_score', 0.0)
408
  strat_type = sig.get('strategy_type', 'N/A')
409
 
410
  log_and_print(
411
- f"{symbol:<9} | {strat_type:<10} | {l2_hybrid:.2f} | {titan_d:.2f} | {patt_d:.2f} | "
412
- f"{news_d:+.2f} | {whale_d:+.2f} | {mcl_d:.2f} | {mca_d:+.2f} | {final_d:.2f} | "
413
- f"{oracle_conf:.2f} | {status_str}"
 
 
 
 
 
 
 
 
 
414
  )
415
 
416
  if approved_signals:
@@ -435,423 +455,418 @@ async def run_unified_cycle():
435
  traceback.print_exc()
436
  sys_state.set_cycle_end(error=e, logs=log_buffer.getvalue())
437
 
438
- # ==============================================================================
439
- # 💻 THE EMBEDDED FRONTEND (HTML/JS/CSS)
440
- # ==============================================================================
441
- # This string contains the entire UI, served by FastAPI as the root route.
442
- # ==============================================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
 
444
- HTML_CONTENT = """
445
- <!DOCTYPE html>
446
- <html lang="en">
447
- <head>
448
- <meta charset="UTF-8">
449
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
450
- <title>TITAN Terminal V69</title>
451
- <script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
452
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
453
- <style>
454
- :root {
455
- --bg-color: #0b0f19;
456
- --panel-bg: #161b29;
457
- --text-color: #d1d5db;
458
- --accent-green: #00ff9d;
459
- --accent-red: #ff3b30;
460
- --accent-blue: #00e5ff;
461
- --border: #2d3748;
462
- }
463
- body { margin: 0; padding: 0; font-family: 'JetBrains Mono', monospace; background-color: var(--bg-color); color: var(--text-color); height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
464
-
465
- /* Layout Grid */
466
- .grid-container {
467
- display: grid;
468
- grid-template-columns: 260px 1fr 320px;
469
- grid-template-rows: 60px 1fr 200px;
470
- height: 100vh;
471
- gap: 1px;
472
- background-color: var(--border);
473
- }
474
 
475
- /* Header */
476
- header { grid-column: 1 / -1; background-color: var(--panel-bg); display: flex; align-items: center; padding: 0 20px; justify-content: space-between; border-bottom: 1px solid var(--border); }
477
- .brand { font-size: 20px; font-weight: bold; color: var(--accent-blue); letter-spacing: 2px; text-shadow: 0 0 10px rgba(0, 229, 255, 0.3); }
478
- .header-stats { display: flex; gap: 20px; font-size: 13px; }
479
- .status-pill { padding: 4px 10px; border-radius: 4px; font-size: 12px; background: #2d3748; border: 1px solid transparent; }
480
- .status-pill.active { background: rgba(0, 255, 157, 0.1); color: var(--accent-green); border-color: var(--accent-green); }
481
- .status-pill.busy { background: rgba(255, 59, 48, 0.1); color: var(--accent-red); border-color: var(--accent-red); }
482
-
483
- /* Sidebar */
484
- aside { grid-row: 2 / -1; background-color: var(--panel-bg); padding: 20px; display: flex; flex-direction: column; gap: 15px; border-right: 1px solid var(--border); }
485
- .sidebar-section { margin-bottom: 10px; }
486
- .section-title { font-size: 11px; color: #6b7280; margin-bottom: 8px; font-weight: bold; letter-spacing: 1px; }
487
-
488
- button {
489
- width: 100%; background: #2d3748; border: 1px solid #4a5568; color: white; padding: 12px; border-radius: 6px; cursor: pointer; font-family: inherit; font-weight: bold; transition: 0.2s; display: flex; align-items: center; justify-content: center; gap: 8px;
490
- }
491
- button:hover { background: #4a5568; transform: translateY(-1px); }
492
- button:active { transform: translateY(0); }
493
- button.primary { background: var(--accent-blue); color: #0b0f19; border: none; box-shadow: 0 0 10px rgba(0, 229, 255, 0.2); }
494
- button.primary:hover { background: #33ecff; }
495
- button.danger { background: rgba(255, 59, 48, 0.1); color: var(--accent-red); border-color: var(--accent-red); }
496
- button.danger:hover { background: rgba(255, 59, 48, 0.3); }
497
-
498
- /* Main Chart Area */
499
- main { grid-column: 2; grid-row: 2; background-color: var(--bg-color); position: relative; }
500
- #chart-container { width: 100%; height: 100%; }
501
- .chart-overlay { position: absolute; top: 15px; left: 15px; background: rgba(11, 15, 25, 0.85); padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border); backdrop-filter: blur(4px); pointer-events: none; z-index: 10; }
502
- .overlay-symbol { font-size: 20px; font-weight: bold; color: white; }
503
- .overlay-price { font-size: 14px; color: var(--accent-green); }
504
-
505
- /* Right Panel (Stats) */
506
- .stats-panel { grid-column: 3; grid-row: 2; background-color: var(--panel-bg); padding: 0; overflow-y: auto; display: flex; flex-direction: column; }
507
- .panel-header { padding: 15px; background: #1f2937; border-bottom: 1px solid var(--border); font-weight: bold; }
508
- .stat-card { padding: 15px; border-bottom: 1px solid var(--border); }
509
- .stat-value { font-size: 26px; margin-top: 5px; color: white; font-weight: bold; }
510
- .stat-label { font-size: 11px; color: #9ca3af; text-transform: uppercase; }
511
- .pnl-pos { color: var(--accent-green); text-shadow: 0 0 10px rgba(0,255,157,0.2); }
512
- .pnl-neg { color: var(--accent-red); }
513
-
514
- /* Positions List */
515
- .positions-container { flex-grow: 1; overflow-y: auto; }
516
- .position-card { padding: 12px 15px; border-bottom: 1px solid var(--border); cursor: pointer; transition: 0.2s; }
517
- .position-card:hover { background: #2d3748; }
518
- .pos-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; }
519
- .pos-symbol { font-weight: bold; color: white; font-size: 14px; }
520
- .pos-pnl { font-weight: bold; font-size: 14px; }
521
- .pos-details { display: flex; justify-content: space-between; font-size: 11px; color: #9ca3af; }
522
-
523
- /* Logs Area */
524
- footer { grid-column: 2 / -1; grid-row: 3; background-color: #0d1117; color: #3fb950; padding: 10px; font-size: 12px; overflow-y: auto; border-top: 1px solid var(--border); font-family: 'Consolas', monospace; }
525
- .log-entry { margin-bottom: 3px; white-space: pre-wrap; line-height: 1.4; }
526
- .log-time { color: #8b949e; margin-right: 8px; }
527
-
528
- /* Scrollbar */
529
- ::-webkit-scrollbar { width: 6px; height: 6px; }
530
- ::-webkit-scrollbar-track { background: var(--bg-color); }
531
- ::-webkit-scrollbar-thumb { background: #4b5563; border-radius: 3px; }
532
- ::-webkit-scrollbar-thumb:hover { background: #6b7280; }
533
- </style>
534
- </head>
535
- <body>
536
-
537
- <div class="grid-container">
538
- <header>
539
- <div class="brand">⚡ TITAN TERMINAL</div>
540
- <div class="header-stats">
541
- <div class="status-pill" id="sys-status">System: Idle</div>
542
- <div class="status-pill" id="auto-pilot-status">Auto-Pilot: ON</div>
543
- <div class="status-pill" id="tuner-status">Tuner: Init</div>
544
- </div>
545
- </header>
546
-
547
- <aside>
548
- <div class="sidebar-section">
549
- <div class="section-title">PRIMARY ACTIONS</div>
550
- <button class="primary" onclick="triggerScan()">
551
- <span>🚀</span> RUN MARKET SCAN
552
- </button>
553
- </div>
554
 
555
- <div class="sidebar-section">
556
- <div class="section-title">CONTROLS</div>
557
- <button onclick="toggleAutoPilot()">✈️ Toggle Auto-Pilot</button>
558
- <button onclick="refreshData()">🔄 Force Refresh</button>
559
- </div>
560
-
561
- <div style="flex-grow: 1;"></div>
562
 
563
- <div class="sidebar-section">
564
- <div class="section-title">DANGER ZONE</div>
565
- <button class="danger" onclick="triggerClose()">🚨 PANIC CLOSE ALL</button>
566
- </div>
567
- </aside>
568
-
569
- <main>
570
- <div id="chart-container"></div>
571
- <div class="chart-overlay">
572
- <div class="overlay-symbol" id="chart-symbol">WAITING...</div>
573
- <div class="overlay-price" id="chart-regime">Scanning for signals...</div>
574
- </div>
575
- </main>
576
-
577
- <div class="stats-panel">
578
- <div class="panel-header">INSTITUTIONAL WALLET</div>
579
- <div class="stat-card">
580
- <div class="stat-label">TOTAL EQUITY</div>
581
- <div class="stat-value" id="equity-val">$0.00</div>
582
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
583
 
584
- <div class="stat-card">
585
- <div class="stat-label">DAILY PNL</div>
586
- <div class="stat-value" id="pnl-val">$0.00</div>
587
  </div>
588
-
589
- <div class="panel-header" style="margin-top: 10px;">OPEN POSITIONS</div>
590
- <div class="positions-container" id="positions-list">
591
- <div style="padding: 20px; text-align: center; color: #6b7280; font-size: 12px;">
592
- No Active Trades
593
- </div>
594
  </div>
 
595
  </div>
596
-
597
- <footer id="logs-area">
598
- <div class="log-entry">System Initialized... Connecting to Neural Core...</div>
599
- </footer>
600
- </div>
601
-
602
- <script>
603
- // --- API Config ---
604
- // Using relative path ensures compatibility with localhost and Hugging Face
605
- const API_URL = "/api";
606
-
607
- // --- Chart Setup ---
608
- const chartContainer = document.getElementById('chart-container');
609
- const chart = LightweightCharts.createChart(chartContainer, {
610
- layout: { background: { color: '#0b0f19' }, textColor: '#d1d5db' },
611
- grid: { vertLines: { color: '#1f2937' }, horzLines: { color: '#1f2937' } },
612
- timeScale: { timeVisible: true, secondsVisible: false, borderColor: '#2d3748' },
613
- rightPriceScale: { borderColor: '#2d3748' },
614
- });
615
-
616
- const candleSeries = chart.addCandlestickSeries({
617
- upColor: '#00ff9d', downColor: '#ff3b30',
618
- borderVisible: false, wickUpColor: '#00ff9d', wickDownColor: '#ff3b30',
619
- });
620
-
621
- window.addEventListener('resize', () => {
622
- chart.resize(chartContainer.clientWidth, chartContainer.clientHeight);
623
- });
624
-
625
- // --- State ---
626
- let currentSymbol = "";
627
- let isScanning = false;
628
-
629
- // --- Core Functions ---
630
-
631
- async function updateStatus() {
632
- try {
633
- const res = await fetch(`${API_URL}/status`);
634
- const data = await res.json();
635
-
636
- // Update Wallet
637
- document.getElementById('equity-val').innerText = `$${data.capital.toFixed(2)}`;
638
- const pnlEl = document.getElementById('pnl-val');
639
- pnlEl.innerText = `$${data.daily_pnl.toFixed(2)}`;
640
- pnlEl.className = `stat-value ${data.daily_pnl >= 0 ? 'pnl-pos' : 'pnl-neg'}`;
641
-
642
- // Update Status Pills
643
- const sysStatus = document.getElementById('sys-status');
644
- if (data.cycle_running) {
645
- sysStatus.innerText = "System: 🌀 SCANNING";
646
- sysStatus.className = "status-pill busy";
647
- isScanning = true;
648
- } else {
649
- sysStatus.innerText = "System: ✅ IDLE";
650
- sysStatus.className = "status-pill active";
651
- isScanning = false;
652
- }
653
-
654
- const apStatus = document.getElementById('auto-pilot-status');
655
- apStatus.innerText = data.auto_pilot ? "Auto-Pilot: ON" : "Auto-Pilot: OFF";
656
- apStatus.className = `status-pill ${data.auto_pilot ? 'active' : ''}`;
657
-
658
- document.getElementById('tuner-status').innerText = data.tuner_status.substring(0, 20) + "..";
659
-
660
- // Update Logs
661
- const logsArea = document.getElementById('logs-area');
662
- if (logsArea.innerText !== data.logs) {
663
- logsArea.innerText = data.logs;
664
- logsArea.scrollTop = logsArea.scrollHeight;
665
- }
666
-
667
- } catch (e) { console.error("Status Sync Error:", e); }
668
- }
669
-
670
- async function updatePositions() {
671
- try {
672
- const res = await fetch(`${API_URL}/positions`);
673
- const data = await res.json();
674
- const container = document.getElementById('positions-list');
675
-
676
- if (data.length === 0) {
677
- container.innerHTML = "<div style='padding: 20px; text-align: center; color: #6b7280; font-size: 12px;'>No Active Trades</div>";
678
- return;
679
- }
680
-
681
- // If trades exist, load chart for the first one if no symbol selected
682
- if (currentSymbol === "" && data.length > 0) {
683
- loadChart(data[0].symbol);
684
- }
685
-
686
- let html = "";
687
- data.forEach(pos => {
688
- const pnlClass = pos.pnl_pct >= 0 ? 'pnl-pos' : 'pnl-neg';
689
- const activeClass = pos.symbol === currentSymbol ? 'background: #2d3748;' : '';
690
-
691
- html += `
692
- <div class="position-card" style="${activeClass}" onclick="loadChart('${pos.symbol}')">
693
- <div class="pos-header">
694
- <span class="pos-symbol">${pos.symbol}</span>
695
- <span class="pos-pnl ${pnlClass}">${pos.pnl_pct}%</span>
696
- </div>
697
- <div class="pos-details">
698
- <span>${pos.strategy}</span>
699
- <span>${pos.duration}</span>
700
- </div>
701
- </div>
702
- `;
703
- });
704
- container.innerHTML = html;
705
-
706
- } catch (e) { console.error("Pos Sync Error:", e); }
707
- }
708
-
709
- async function loadChart(symbol) {
710
- if (symbol === currentSymbol && symbol !== "") return;
711
- currentSymbol = symbol;
712
-
713
- document.getElementById('chart-symbol').innerText = symbol;
714
- document.getElementById('chart-regime').innerText = "Loading data...";
715
-
716
- try {
717
- const res = await fetch(`${API_URL}/chart/${symbol}`);
718
- const data = await res.json();
719
- if (data.length > 0) {
720
- candleSeries.setData(data);
721
- const last = data[data.length - 1];
722
- document.getElementById('chart-regime').innerText = `Price: ${last.close}`;
723
- chart.timeScale().fitContent();
724
- } else {
725
- document.getElementById('chart-regime').innerText = "No Data Available";
726
- }
727
- } catch (e) { console.error("Chart Load Error:", e); }
728
- }
729
-
730
- // --- Actions ---
731
- async function triggerScan() {
732
- if(isScanning) return;
733
- await fetch(`${API_URL}/action/scan`, {method: 'POST'});
734
- updateStatus();
735
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
736
 
737
- async function triggerClose() {
738
- if(confirm("Are you sure you want to close ALL positions?")) {
739
- await fetch(`${API_URL}/action/close_all`, {method: 'POST'});
740
- updateStatus();
741
- }
742
- }
743
 
744
- async function toggleAutoPilot() {
745
- // Simple toggle via endpoint (need to implement in backend if strictly needed,
746
- // for now we just show alert as placeholder or map to an endpoint)
747
- await fetch(`${API_URL}/action/toggle_autopilot`, {method: 'POST'});
748
- updateStatus();
749
- }
750
-
751
- function refreshData() { updateStatus(); updatePositions(); }
752
-
753
- // --- Loop ---
754
- setInterval(() => {
755
- updateStatus();
756
- updatePositions();
757
- }, 2000);
758
-
759
- // --- Init ---
760
- refreshData();
761
-
762
- </script>
763
- </body>
764
- </html>
765
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
766
 
767
  # ------------------------------------------------------------------------------
768
- # 🔌 API Endpoints
769
  # ------------------------------------------------------------------------------
770
- app = FastAPI(lifespan=lifespan, title="Titan Terminal")
771
-
772
- app.add_middleware(
773
- CORSMiddleware,
774
- allow_origins=["*"],
775
- allow_credentials=True,
776
- allow_methods=["*"],
777
- allow_headers=["*"],
778
- )
779
-
780
- @app.get("/")
781
- async def root():
782
- """Serves the Embedded HTML Frontend"""
783
- return HTMLResponse(content=HTML_CONTENT)
784
-
785
- @app.get("/api/status")
786
- async def get_status():
787
- if not trade_manager or not trade_manager.smart_portfolio:
788
- return {"capital": 0.0, "daily_pnl": 0.0, "logs": "Loading...", "tuner_status": "Init"}
789
-
790
- sp = trade_manager.smart_portfolio
791
 
792
- return {
793
- "cycle_running": sys_state.cycle_running,
794
- "auto_pilot": sys_state.auto_pilot,
795
- "capital": sp.state.get("current_capital", 10.0),
796
- "daily_pnl": sp.state.get("daily_net_pnl", 0.0),
797
- "logs": sys_state.last_cycle_logs,
798
- "tuner_status": adaptive_hub.get_status() if adaptive_hub else "Init"
799
- }
800
-
801
- @app.get("/api/positions")
802
- async def get_positions():
803
- positions = []
804
- if trade_manager:
805
- for sym, trade in trade_manager.open_positions.items():
806
- curr_p = await data_manager.get_latest_price_async(sym)
807
- entry_p = float(trade['entry_price'])
808
- pnl_pct = ((curr_p - entry_p) / entry_p) * 100 if entry_p > 0 else 0.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
809
 
810
- positions.append({
811
- "symbol": sym,
812
- "entry": entry_p,
813
- "current": curr_p,
814
- "pnl_pct": round(pnl_pct, 2),
815
- "duration": calculate_duration_str(trade['entry_time']),
816
- "strategy": trade.get('strategy_type', 'NORMAL')
817
- })
818
- return positions
819
-
820
- @app.get("/api/chart/{symbol}")
821
- async def get_chart_data(symbol: str):
822
- ohlcv = await data_manager.get_latest_ohlcv(symbol, '5m', 200)
823
- formatted = []
824
- if ohlcv:
825
- for c in ohlcv:
826
- # [ts, o, h, l, c, v]
827
- formatted.append({
828
- "time": int(c[0] / 1000),
829
- "open": c[1], "high": c[2], "low": c[3], "close": c[4]
830
- })
831
- return formatted
832
-
833
- @app.post("/api/action/scan")
834
- async def action_scan():
835
- if sys_state.cycle_running: return {"msg": "Busy"}
836
- asyncio.create_task(run_unified_cycle())
837
- return {"msg": "Scan Started"}
838
 
839
- @app.post("/api/action/close_all")
840
- async def action_close():
841
- if not trade_manager.open_positions: return {"msg": "No trades"}
842
- # Close all logic
843
- for sym in list(trade_manager.open_positions.keys()):
844
- await trade_manager.force_exit_by_manager(sym, "MANUAL_WEB_PANIC")
845
- return {"msg": "Closing all..."}
 
 
 
 
 
 
 
846
 
847
- @app.post("/api/action/toggle_autopilot")
848
- async def action_toggle_autopilot():
849
- sys_state.auto_pilot = not sys_state.auto_pilot
850
- return {"msg": f"Auto-Pilot: {sys_state.auto_pilot}"}
851
 
852
- # ------------------------------------------------------------------------------
853
- # Entry Point
854
- # ------------------------------------------------------------------------------
855
  if __name__ == "__main__":
856
- # Port 7860 is mandatory for Hugging Face Spaces
857
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
  # ==============================================================================
2
+ # 🚀 app.py (V67.5 - GEM-Architect: Full Visibility & Integrity)
3
  # ==============================================================================
4
 
5
  import os
 
15
  from io import StringIO
16
  from typing import List, Dict, Any, Optional
17
 
 
 
18
  from fastapi import FastAPI
19
+ import gradio as gr
 
20
  import pandas as pd
21
+ import plotly.graph_objects as go
22
 
23
  # ------------------------------------------------------------------------------
24
  # Logging Setup
 
31
  logger = logging.getLogger("TitanCore")
32
 
33
  # ------------------------------------------------------------------------------
34
+ # Imports
35
  # ------------------------------------------------------------------------------
36
  try:
37
  from r2 import R2Service, INITIAL_CAPITAL
 
43
  from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
44
  from learning_hub.adaptive_hub import AdaptiveHub
45
  from trade_manager import TradeManager
46
+ # ✅ Import Continuous Tuner for injection
47
  from periodic_tuner import ContinuousTuner
48
 
49
  try:
 
91
 
92
  def set_ready(self):
93
  self.ready = True
94
+ self.last_cycle_logs = "✅ System Ready. Continuous Tuning ON."
95
  logger.info("✅ System State set to READY.")
96
 
97
  def set_cycle_start(self):
 
137
  return f"{hours:02}:{minutes:02}:{seconds:02}"
138
  except: return "--:--:--"
139
 
140
+ def format_pnl_split(profit, loss):
141
+ """تنسيق الربحية كجزأين (ربح / خسارة) بألوان"""
142
+ # ✅ FIX: Ensure distinct coloring for Profit vs Loss
143
+ return f"<span style='color:#00ff00'>+${profit:,.2f}</span> / <span style='color:#ff0000'>-${abs(loss):,.2f}</span>"
144
+
145
  # ------------------------------------------------------------------------------
146
  # Auto-Pilot Daemon
147
  # ------------------------------------------------------------------------------
 
158
 
159
  if trade_manager and len(trade_manager.open_positions) > 0:
160
  wd_status = await trade_manager.ensure_active_guardians()
 
161
  if "No active" not in wd_status:
162
  if not sys_state.cycle_running:
163
+ sys_state.last_cycle_logs = trade_manager.latest_guardian_log
 
164
  continue
165
 
166
  if sys_state.auto_pilot and not sys_state.cycle_running and not sys_state.training_running:
 
178
  await asyncio.sleep(30)
179
 
180
  # ------------------------------------------------------------------------------
181
+ # Lifespan
182
  # ------------------------------------------------------------------------------
183
  @asynccontextmanager
184
  async def lifespan(app: FastAPI):
185
  global r2, data_manager, ml_processor, adaptive_hub, trade_manager, whale_monitor, news_fetcher, senti_analyzer, sys_state
186
 
187
+ logger.info("\n🚀 [System] Startup Sequence (Titan V67.5 - Full Integrity)...")
188
  try:
189
  r2 = R2Service()
190
  data_manager = DataManager(contracts_db={}, whale_monitor=None, r2_service=r2)
 
208
  trade_manager = TradeManager(r2_service=r2, data_manager=data_manager, processor=ml_processor)
209
  trade_manager.learning_hub = adaptive_hub
210
 
211
+ # ✅ FIX: Inject Continuous Tuner directly into Hub
212
  adaptive_hub.tuner = ContinuousTuner(adaptive_hub)
213
  logger.info("🔧 [Tuner] Continuous Tuner Injected.")
214
 
 
217
 
218
  sys_state.set_ready()
219
  asyncio.create_task(auto_pilot_loop())
220
+ logger.info("✅ [System READY] All modules operational.")
221
  yield
222
 
223
  except Exception as e:
 
231
  logger.info("✅ [System] Shutdown Complete.")
232
 
233
  # ------------------------------------------------------------------------------
234
+ # Helper Tasks
235
  # ------------------------------------------------------------------------------
236
  async def _analyze_symbol_task(candidate_data: Dict[str, Any]) -> Dict[str, Any]:
237
  try:
 
270
  except Exception: return None
271
 
272
  # ------------------------------------------------------------------------------
273
+ # Unified Cycle (FULL LOGIC with VISIBILITY)
274
  # ------------------------------------------------------------------------------
275
  async def run_unified_cycle():
276
  log_buffer = StringIO()
 
305
  tasks = [_analyze_symbol_task(c) for c in candidates]
306
  results = await asyncio.gather(*tasks)
307
 
308
+ # ✅ فصل المقبولين عن المرفوضين (Validity Check)
309
  valid_l2 = [res for res in results if res is not None and res.get('is_valid', False)]
310
  rejected_l2 = [res for res in results if res is not None and not res.get('is_valid', False)]
311
 
312
+ # ترتيب المرفوضين لعرض أفضلهم
313
  rejected_l2.sort(key=lambda x: x.get('enhanced_final_score', 0.0), reverse=True)
314
 
315
  if not valid_l2:
316
+ log_and_print("⚠️ No valid L2 candidates (Hard Gates Active). Top 10 Rejected:")
317
+ # طباعة جدول يوضح سبب الرفض
318
  rej_header = f"{'SYM':<9} | {'TITAN':<5} | {'PATT':<5} | {'MC':<5} | {'FINAL':<5} | {'REASON'}"
319
  log_and_print(rej_header)
320
  log_and_print("-" * 100)
321
+
322
  for rej in rejected_l2[:10]:
323
  sym = rej['symbol']
324
  tit = rej.get('titan_score', 0.0)
 
382
 
383
  approved_signals = []
384
 
385
+ # ✅ FIX: Updated Header with NEWS and MC(L) and MC(A)
386
  header = (f"{'SYM':<9} | {'TYPE':<10} | {'L2':<5} | {'TITAN':<5} | {'PATT':<5} | "
387
  f"{'NEWS':<5} | {'WHALE':<6} | {'MC(L)':<5} | {'MC(A)':<5} | {'FINAL':<6} | {'ORACLE':<6} | {'STATUS'}")
388
 
389
+ log_and_print("-" * 130) # Widen
390
  log_and_print(header)
391
  log_and_print("-" * 130)
392
 
 
408
  l2_hybrid = sig.get('enhanced_final_score', 0.0)
409
  titan_d = sig.get('titan_score', 0.0)
410
  patt_d = sig.get('patterns_score', 0.0)
411
+
412
+ # ✅ FIX: Extract NEW Columns
413
  news_d = sig.get('news_score', 0.0)
414
  whale_d = sig.get('whale_score', 0.0)
415
+ mcl_d = sig.get('mc_score', 0.0) # Light
416
+ mca_d = sig.get('mc_advanced_score', 0.0) # Advanced
417
+
418
  final_d = sig.get('final_total_score', 0.0)
419
  strat_type = sig.get('strategy_type', 'N/A')
420
 
421
  log_and_print(
422
+ f"{symbol:<9} | "
423
+ f"{strat_type:<10} | "
424
+ f"{l2_hybrid:.2f} | "
425
+ f"{titan_d:.2f} | "
426
+ f"{patt_d:.2f} | "
427
+ f"{news_d:+.2f} | "
428
+ f"{whale_d:+.2f} | "
429
+ f"{mcl_d:.2f} | "
430
+ f"{mca_d:+.2f} | "
431
+ f"{final_d:.2f} | "
432
+ f"{oracle_conf:.2f} | "
433
+ f"{status_str}"
434
  )
435
 
436
  if approved_signals:
 
455
  traceback.print_exc()
456
  sys_state.set_cycle_end(error=e, logs=log_buffer.getvalue())
457
 
458
+ # ------------------------------------------------------------------------------
459
+ # Handlers
460
+ # ------------------------------------------------------------------------------
461
+ async def trigger_training_cycle():
462
+ if adaptive_hub: return f"🤖 Adaptive System: {adaptive_hub.get_status()}"
463
+ return "⚠️ System not ready."
464
+
465
+ async def trigger_strategic_backtest():
466
+ if not BACKTEST_AVAILABLE: return "⚠️ Backtest Engine not found."
467
+ if trade_manager and len(trade_manager.open_positions) > 0: return "⛔ Active trades exist."
468
+ if sys_state.training_running: return "⚠️ Running."
469
+
470
+ async def _run_bg_task():
471
+ sys_state.training_running = True
472
+ sys_state.training_status_msg = "🧪 Strategic Backtest Running..."
473
+ try:
474
+ logger.info("🧪 [Manual] Starting Strategic Backtest...")
475
+ await run_strategic_optimization_task()
476
+ if adaptive_hub: await adaptive_hub.initialize()
477
+ logger.info("✅ [Manual] Backtest Complete.")
478
+ except Exception as e: logger.error(f"❌ Backtest Failed: {e}")
479
+ finally:
480
+ sys_state.training_running = False
481
+ sys_state.training_status_msg = adaptive_hub.get_status() if adaptive_hub else "Ready"
482
+
483
+ asyncio.create_task(_run_bg_task())
484
+ return "🧪 Strategic Backtest Started."
485
+
486
+ async def manual_close_current_trade():
487
+ if not trade_manager.open_positions: return "⚠️ No trade."
488
+ symbol = list(trade_manager.open_positions.keys())[0]
489
+ await trade_manager.force_exit_by_manager(symbol, reason="MANUAL_UI")
490
+ return f"✅ Closed {symbol}."
491
+
492
+ async def reset_history_handler():
493
+ if trade_manager.open_positions: return "⚠️ Close active trades first."
494
+ current_state = await r2.get_portfolio_state_async()
495
+ preserved_capital = current_state.get('current_capital_usd', INITIAL_CAPITAL)
496
+ await r2.reset_all_stats_async()
497
+ if trade_manager and trade_manager.smart_portfolio:
498
+ sp = trade_manager.smart_portfolio
499
+ sp.state["current_capital"] = preserved_capital
500
+ sp.state["session_start_balance"] = preserved_capital
501
+ sp.state["allocated_capital_usd"] = 0.0
502
+ sp.state["daily_net_pnl"] = 0.0
503
+ sp.state["is_trading_halted"] = False
504
+ await sp._save_state_to_r2()
505
+ return f"✅ History Cleared. Capital Preserved at ${preserved_capital:.2f}"
506
+
507
+ async def reset_capital_handler():
508
+ if trade_manager.open_positions: return "⚠️ Close active trades first."
509
+ if trade_manager and trade_manager.smart_portfolio:
510
+ sp = trade_manager.smart_portfolio
511
+ sp.state["current_capital"] = INITIAL_CAPITAL
512
+ sp.state["session_start_balance"] = INITIAL_CAPITAL
513
+ sp.state["allocated_capital_usd"] = 0.0
514
+ sp.state["daily_net_pnl"] = 0.0
515
+ sp.state["is_trading_halted"] = False
516
+ await sp._save_state_to_r2()
517
+ return f"✅ Capital Reset to ${INITIAL_CAPITAL} (History Kept)"
518
+
519
+ async def reset_diagnostics_handler():
520
+ await r2.reset_diagnostic_stats_async()
521
+ return "✅ Diagnostic Matrix Reset."
522
+
523
+ async def reset_guardians_handler():
524
+ await r2.reset_guardian_stats_async()
525
+ if trade_manager: trade_manager.ai_stats = await r2.get_guardian_stats_async()
526
+ return "✅ Guardian Stats Reset."
527
+
528
+ async def toggle_auto_pilot(enable):
529
+ sys_state.auto_pilot = enable
530
+ return f"Auto-Pilot: {enable}"
531
+
532
+ async def run_cycle_from_gradio():
533
+ if sys_state.cycle_running: return "Busy."
534
+ asyncio.create_task(run_unified_cycle())
535
+ return "🚀 Launched."
536
 
537
+ # ------------------------------------------------------------------------------
538
+ # UI Updates
539
+ # ------------------------------------------------------------------------------
540
+ async def check_live_pnl_and_status(selected_view="Dual-Core (Hybrid)"):
541
+ empty_chart = go.Figure()
542
+ empty_chart.update_layout(template="plotly_dark", paper_bgcolor="#0b0f19", plot_bgcolor="#0b0f19", xaxis={'visible':False}, yaxis={'visible':False})
543
+ wl_df_empty = pd.DataFrame(columns=["Coin", "Score"])
544
+ diag_df_empty = pd.DataFrame(columns=["Model", "Wins", "Losses", "PnL (USD)"])
545
+ type_df_empty = pd.DataFrame(columns=["Coin Type", "Wins", "Losses", "Profitability"])
546
+
547
+ if not sys_state.ready:
548
+ return "Initializing...", "...", empty_chart, "0.0", "0.0", "0.0", "0.0", "0.0%", wl_df_empty, diag_df_empty, type_df_empty, "Loading...", "Loading...", "Loading..."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
549
 
550
+ try:
551
+ sp = trade_manager.smart_portfolio
552
+ equity = sp.state.get('current_capital', 10.0)
553
+ allocated = sp.state.get('allocated_capital_usd', 0.0)
554
+ free_cap = max(0.0, equity - allocated)
555
+ daily_pnl = sp.state.get('daily_net_pnl', 0.0)
556
+ is_halted = sp.state.get('is_trading_halted', False)
557
+
558
+ symbol = None; entry_p = 0.0; tp_p = 0.0; sl_p = 0.0; curr_p = 0.0; pnl_pct = 0.0; pnl_val_unrealized = 0.0
559
+ active_trade_info = ""
560
+ trade_dur_str = "--:--:--"
561
+
562
+ if trade_manager.open_positions:
563
+ symbol = list(trade_manager.open_positions.keys())[0]
564
+ trade = trade_manager.open_positions[symbol]
565
+ entry_p = float(trade.get('entry_price', 0.0))
566
+ tp_p = float(trade.get('tp_price', 0.0))
567
+ sl_p = float(trade.get('sl_price', 0.0))
568
+ trade_dur_str = calculate_duration_str(trade.get('entry_time'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
569
 
570
+ decision_data = trade.get('decision_data', {})
571
+ gov_grade = decision_data.get('governance_grade', 'N/A')
572
+ gov_score = decision_data.get('governance_score', 0.0)
573
+ sys_conf = decision_data.get('system_confidence', 0.0)
574
+ strat_type = trade.get('strategy_type', 'NORMAL')
 
 
575
 
576
+ grade_color = "#ccc"
577
+ if gov_grade == "ULTRA": grade_color = "#ff00ff"
578
+ elif gov_grade == "STRONG": grade_color = "#00ff00"
579
+ elif gov_grade == "NORMAL": grade_color = "#00e5ff"
580
+ elif gov_grade == "WEAK": grade_color = "#ffff00"
581
+ elif gov_grade == "REJECT": grade_color = "#ff0000"
582
+
583
+ curr_p = await data_manager.get_latest_price_async(symbol)
584
+ if curr_p > 0 and entry_p > 0:
585
+ pnl_pct = ((curr_p - entry_p) / entry_p) * 100
586
+ size = float(trade.get('entry_capital', 0.0))
587
+ pnl_val_unrealized = size * (pnl_pct / 100)
588
+
589
+ active_trade_info = f"""
590
+ <div style='display: flex; justify-content: space-between; font-size: 12px; color: #ccc; margin-top:5px; border-top: 1px solid #333; padding-top: 5px;'>
591
+ <span>⏱️ Time:</span> <span style='color: #ffff00;'>{trade_dur_str}</span>
592
+ </div>
593
+ <div style='display: flex; justify-content: space-between; font-size: 12px; color: #ccc; margin-top:5px;'>
594
+ <span>🏛️ Grade:</span> <span style='color: {grade_color}; font-weight:bold;'>{gov_grade} ({gov_score:.1f})</span>
595
+ </div>
596
+ <div style='display: flex; justify-content: space-between; font-size: 12px; color: #ccc; margin-top:5px;'>
597
+ <span>🏷️ Type:</span> <span style='color: #orange;'>{strat_type}</span>
598
+ </div>
599
+ """
600
+
601
+ virtual_equity = equity + pnl_val_unrealized
602
+ active_pnl_color = "#00ff00" if pnl_val_unrealized >= 0 else "#ff0000"
603
+ portfolio = await r2.get_portfolio_state_async()
604
+ total_t = portfolio.get('total_trades', 0)
605
+ wins = portfolio.get('winning_trades', 0)
606
+ losses = portfolio.get('losing_trades', 0)
607
+ if losses == 0 and total_t > 0: losses = total_t - wins
608
+ tot_prof = portfolio.get('total_profit_usd', 0.0)
609
+ tot_loss = portfolio.get('total_loss_usd', 0.0)
610
+ net_prof = tot_prof - tot_loss
611
+ win_rate = (wins / total_t * 100) if total_t > 0 else 0.0
612
+ halt_status = "<span style='color:red; font-weight:bold;'>HALTED</span>" if is_halted else "<span style='color:#00ff00;'>ACTIVE</span>"
613
+
614
+ wallet_md = f"""
615
+ <div style='background-color: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333; text-align:center;'>
616
+ <h3 style='margin:0; color:#888; font-size:14px;'>💼 Institutional Portfolio</h3>
617
+ <div style='font-size: 24px; font-weight: bold; color: white; margin: 5px 0 0 0;'>${virtual_equity:,.2f}</div>
618
+ <div style='font-size: 14px; color: {active_pnl_color}; margin-bottom: 5px;'>({pnl_val_unrealized:+,.2f} USD)</div>
619
+
620
+ <table style='width:100%; font-size:12px; margin-top:5px; color:#ccc;'>
621
+ <tr><td>Allocated:</td><td style='text-align:right; color:#ffa500;'>${allocated:.2f}</td></tr>
622
+ <tr><td>Free Cap:</td><td style='text-align:right; color:#00ff00;'>${free_cap:.2f}</td></tr>
623
+ <tr><td>Daily PnL:</td><td style='text-align:right; color:{"#00ff00" if daily_pnl>=0 else "#ff0000"};'>${daily_pnl:+.2f}</td></tr>
624
+ </table>
625
+
626
+ <hr style='border-color:#444; margin: 10px 0;'>
627
 
628
+ <div style='display: flex; justify-content: space-between; font-size: 12px; color: #ccc;'>
629
+ <span>🦅 Mood:</span> <span style='color: white;'>{sp.market_trend}</span>
 
630
  </div>
631
+ <div style='display: flex; justify-content: space-between; font-size: 12px; color: #ccc; margin-top:5px;'>
632
+ <span>🛡️ Status:</span> {halt_status}
 
 
 
 
633
  </div>
634
+ {active_trade_info}
635
  </div>
636
+ """
637
+
638
+ # Guardian Stats
639
+ key_map = {
640
+ "Dual-Core (Hybrid)": "hybrid",
641
+ "Hydra: Crash (Panic)": "crash",
642
+ "Hydra: Giveback (Profit)": "giveback",
643
+ "Hydra: Stagnation (Time)": "stagnation"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
644
  }
645
+ target_key = key_map.get(selected_view, "hybrid")
646
+ stats_data = trade_manager.ai_stats.get(target_key, {"total":0, "good":0, "saved":0.0, "missed":0.0})
647
+
648
+ tot_ds = stats_data['total']
649
+ ds_acc = (stats_data['good'] / tot_ds * 100) if tot_ds > 0 else 0.0
650
+
651
+ history_md = f"""
652
+ <div style='background-color: #1a1a1a; padding: 10px; border-radius: 8px; border: 1px solid #333; font-size: 12px;'>
653
+ <h3 style='margin:0 0 5px 0; color:#888; font-size:14px;'>📊 Performance</h3>
654
+ <table style='width:100%; color:white;'>
655
+ <tr><td>Trades:</td><td style='text-align:right;'>{total_t}</td></tr>
656
+ <tr><td>Win Rate:</td><td style='text-align:right; color:{"#00ff00" if win_rate>=50 else "#ff0000"};'>{win_rate:.1f}%</td></tr>
657
+ <tr><td>Wins:</td><td style='text-align:right; color:#00ff00;'>{wins} (+${tot_prof:,.2f})</td></tr>
658
+ <tr><td>Losses:</td><td style='text-align:right; color:#ff0000;'>{losses} (-${tot_loss:,.2f})</td></tr>
659
+ <tr><td style='border-top:1px solid #444;'>Net:</td><td style='border-top:1px solid #444; text-align:right; color:{"#00ff00" if net_prof>=0 else "#ff0000"};'>${net_prof:,.2f}</td></tr>
660
+ </table>
661
+ <hr style='border-color:#444; margin: 8px 0;'>
662
+ <h3 style='margin:0 0 5px 0; color: #00e5ff; font-size:14px;'>🛡️ Guard IQ ({target_key})</h3>
663
+ <table style='width:100%; color:white;'>
664
+ <tr><td>Interventions:</td><td style='text-align:right;'>{tot_ds}</td></tr>
665
+ <tr><td>Accuracy:</td><td style='text-align:right; color:#00e5ff;'>{ds_acc:.1f}%</td></tr>
666
+ <tr><td>Saved:</td><td style='text-align:right; color:#00ff00;'>${stats_data['saved']:.2f}</td></tr>
667
+ <tr><td>Missed:</td><td style='text-align:right; color:#ff0000;'>${stats_data['missed']:.2f}</td></tr>
668
+ </table>
669
+ </div>
670
+ """
671
+
672
+ # TUNER STATUS
673
+ status_msg = "Inactive"
674
+ if adaptive_hub:
675
+ status_msg = adaptive_hub.get_status()
676
+
677
+ neural_md = f"""
678
+ <div style='background-color: #1a1a1a; padding: 10px; border-radius: 8px; border: 1px solid #333; font-size: 12px; margin-top: 10px;'>
679
+ <h3 style='margin:0; color:#00e5ff; font-size:14px;'>🧠 Micro-Tuner</h3>
680
+ <table style='width:100%; color:#ccc;'>
681
+ <tr style='border-bottom: 1px solid #333;'>
682
+ <td style='padding:4px 0;'>⚡ Status:</td>
683
+ <td style='text-align:right; color:#ffff00; font-weight:bold;'>Active</td>
684
+ </tr>
685
+ <tr>
686
+ <td style='padding:4px 0;'>🔧 Logic:</td>
687
+ <td style='text-align:right; color:#fff;'>{status_msg}</td>
688
+ </tr>
689
+ </table>
690
+ </div>
691
+ """
692
 
693
+ # FIX: Diagnostic Matrix with Split PnL (Accumulated Profit vs Loss)
694
+ diag_data = await r2.get_diagnostic_stats_async()
695
+ diag_list = []
696
+ ordered_models = ["Titan", "Patterns", "Oracle", "Sniper", "MonteCarlo_L", "MonteCarlo_A", "News", "Governance"]
 
 
697
 
698
+ for m in ordered_models:
699
+ stats = diag_data.get(m, {"wins": 0, "losses": 0, "pnl": 0.0, "profit_accum": 0.0, "loss_accum": 0.0})
700
+
701
+ profit_accum = stats.get('profit_accum', 0.0)
702
+ loss_accum = stats.get('loss_accum', 0.0)
703
+
704
+ # If accum values are missing (legacy data), try to infer from net PnL (imperfect fallback)
705
+ if profit_accum == 0.0 and loss_accum == 0.0 and stats['pnl'] != 0.0:
706
+ if stats['pnl'] > 0: profit_accum = stats['pnl']
707
+ else: loss_accum = abs(stats['pnl'])
708
+
709
+ pnl_str = format_pnl_split(profit_accum, loss_accum)
710
+ diag_list.append([m, stats['wins'], stats['losses'], pnl_str])
711
+
712
+ diag_df = pd.DataFrame(diag_list, columns=["Model", "Wins", "Losses", "PnL (USD)"])
713
+
714
+ # Type Stats DataFrame
715
+ type_stats_list = []
716
+ if trade_manager and hasattr(trade_manager, 'type_stats'):
717
+ for t_name, t_data in trade_manager.type_stats.items():
718
+ name_clean = t_name.replace("_", " ")
719
+ wins = t_data.get('wins', 0)
720
+ losses = t_data.get('losses', 0)
721
+ prof = t_data.get('profit_usd', 0.0)
722
+ loss_val = t_data.get('loss_usd', 0.0)
723
+ profitability_html = format_pnl_split(prof, loss_val)
724
+ type_stats_list.append([name_clean, wins, losses, profitability_html])
725
+
726
+ type_df = pd.DataFrame(type_stats_list, columns=["Coin Type", "Wins", "Losses", "Profitability"])
727
+
728
+ wl_data = [[k, f"{v.get('final_total_score',0):.2f}"] for k, v in trade_manager.watchlist.items()]
729
+ wl_df = pd.DataFrame(wl_data, columns=["Coin", "Score"])
730
+
731
+ status_txt = sys_state.last_cycle_logs
732
+ status_line = f"Cycle: {'RUNNING' if sys_state.cycle_running else 'IDLE'} | Auto-Pilot: {'ON' if sys_state.auto_pilot else 'OFF'}"
733
+
734
+ fig = empty_chart
735
+ if symbol and curr_p > 0:
736
+ ohlcv = await data_manager.get_latest_ohlcv(symbol, '5m', 120)
737
+ if ohlcv:
738
+ df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
739
+ df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
740
+ fig = go.Figure(data=[go.Candlestick(
741
+ x=df['datetime'], open=df['open'], high=df['high'], low=df['low'], close=df['close'],
742
+ increasing_line_color='#00ff00', decreasing_line_color='#ff0000', name=symbol
743
+ )])
744
+ if entry_p > 0:
745
+ fig.add_hline(y=entry_p, line_dash="dash", line_color="white", annotation_text="ENTRY", annotation_position="top left")
746
+ if tp_p > 0:
747
+ fig.add_hline(y=tp_p, line_color="#00ff00", line_width=2, annotation_text="TP", annotation_position="top left")
748
+ if sl_p > 0:
749
+ fig.add_hline(y=sl_p, line_color="#ff0000", line_width=2, annotation_text="SL", annotation_position="bottom left")
750
+
751
+ fig.update_layout(
752
+ template="plotly_dark",
753
+ paper_bgcolor="#0b0f19",
754
+ plot_bgcolor="#0b0f19",
755
+ margin=dict(l=0, r=40, t=30, b=0),
756
+ height=400,
757
+ xaxis_rangeslider_visible=False,
758
+ title=dict(text=f"{symbol} (Long) | PnL: {pnl_pct:+.2f}%", font=dict(color="white"))
759
+ )
760
+
761
+ train_status = sys_state.training_status_msg
762
+ if sys_state.training_running: train_status = "🧪 Backtest Running..."
763
+
764
+ return (status_txt, status_line, fig, f"{curr_p:.6f}", f"{entry_p:.6f}", f"{tp_p:.6f}", f"{sl_p:.6f}",
765
+ f"{pnl_pct:+.2f}%", wl_df, diag_df, type_df, wallet_md, history_md, neural_md)
766
+
767
+ except Exception:
768
+ traceback.print_exc()
769
+ return "Error", "Error", empty_chart, "0", "0", "0", "0", "0%", wl_df_empty, diag_df_empty, type_df_empty, "Err", "Err", "Err"
770
 
771
  # ------------------------------------------------------------------------------
772
+ # Gradio UI Construction
773
  # ------------------------------------------------------------------------------
774
+ def create_gradio_ui():
775
+ custom_css = ".gradio-container {background:#0b0f19} .dataframe {background:#1a1a1a!important} .html-box {min-height:180px}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
776
 
777
+ with gr.Blocks(title="Titan V67.5 (Full)") as demo:
778
+ gr.HTML(f"<style>{custom_css}</style>")
779
+
780
+ gr.Markdown("# 🚀 Titan V67.5 (Full Visibility)")
781
+
782
+ with gr.Row():
783
+ with gr.Column(scale=3):
784
+ live_chart = gr.Plot(label="Chart")
785
+ with gr.Row():
786
+ t_price = gr.Textbox(label="Price", interactive=False)
787
+ t_pnl = gr.Textbox(label="PnL %", interactive=False)
788
+ with gr.Row():
789
+ t_entry = gr.Textbox(label="Entry", interactive=False)
790
+ t_tp = gr.Textbox(label="TP", interactive=False)
791
+ t_sl = gr.Textbox(label="SL", interactive=False)
792
+
793
+ with gr.Column(scale=1):
794
+ wallet_out = gr.HTML(label="Smart Wallet", elem_classes="html-box")
795
+ neural_out = gr.HTML(label="Neural Cycles", elem_classes="html-box")
796
+
797
+ # Type Stats Table
798
+ gr.Markdown("### 💎 Opportunity Types")
799
+ type_stats_out = gr.Dataframe(
800
+ headers=["Coin Type", "Wins", "Losses", "Profitability"],
801
+ datatype=["str", "number", "number", "html"],
802
+ interactive=False,
803
+ label="Type Performance"
804
+ )
805
+
806
+ gr.Markdown("### 🕵️ Diagnostic Matrix")
807
+ diagnostic_out = gr.Dataframe(
808
+ headers=["Model", "Wins", "Losses", "PnL (USD)"],
809
+ datatype=["str", "number", "number", "html"],
810
+ interactive=False,
811
+ label="Model Performance"
812
+ )
813
+
814
+ with gr.Row():
815
+ btn_reset_diag = gr.Button("🧹 Reset Matrix", size="sm", variant="secondary")
816
+ btn_reset_guard = gr.Button("🛡️ Reset Guardians", size="sm", variant="secondary")
817
+
818
+ gr.HTML("<hr style='border-color:#444; margin: 10px 0;'>")
819
+
820
+ stats_dd = gr.Dropdown([
821
+ "Dual-Core (Hybrid)",
822
+ "Hydra: Crash (Panic)",
823
+ "Hydra: Giveback (Profit)",
824
+ "Hydra: Stagnation (Time)"
825
+ ], value="Dual-Core (Hybrid)", label="View Guard Stats")
826
+ history_out = gr.HTML(label="Stats", elem_classes="html-box")
827
+ watchlist_out = gr.DataFrame(label="Watchlist")
828
+
829
+ gr.HTML("<hr style='border-color:#333'>")
830
+
831
+ with gr.Row():
832
+ with gr.Column(scale=1):
833
+ auto_pilot = gr.Checkbox(label="✈️ Auto-Pilot", value=True)
834
+ with gr.Row():
835
+ btn_run = gr.Button("🚀 Scan", variant="primary")
836
+ btn_close = gr.Button("🚨 Close", variant="stop")
837
+ with gr.Row():
838
+ btn_train = gr.Button("🤖 Status", variant="secondary")
839
+ btn_backtest = gr.Button("🧪 Run Strategic Backtest", variant="secondary")
840
+ with gr.Row():
841
+ btn_history_reset = gr.Button("🗑️ Clear History", variant="secondary")
842
+ btn_cap_reset = gr.Button("💰 Reset Capital", variant="secondary")
843
+
844
+ status = gr.Markdown("Init...")
845
+ alert = gr.Textbox(label="Alerts", interactive=False)
846
 
847
+ with gr.Column(scale=3):
848
+ logs = gr.Textbox(label="Logs", lines=14, autoscroll=True, elem_classes="log-box", type="text")
849
+ gr.HTML("<style>.log-box textarea { font-family: 'Consolas', 'Monaco', monospace !important; font-size: 12px !important; white-space: pre !important; }</style>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
850
 
851
+ btn_run.click(fn=run_cycle_from_gradio, outputs=alert)
852
+ btn_close.click(fn=manual_close_current_trade, outputs=alert)
853
+ btn_history_reset.click(fn=reset_history_handler, outputs=alert)
854
+ btn_cap_reset.click(fn=reset_capital_handler, outputs=alert)
855
+ btn_train.click(fn=trigger_training_cycle, outputs=alert)
856
+ btn_backtest.click(fn=trigger_strategic_backtest, outputs=alert)
857
+ auto_pilot.change(fn=toggle_auto_pilot, inputs=auto_pilot, outputs=alert)
858
+
859
+ btn_reset_diag.click(fn=reset_diagnostics_handler, outputs=alert)
860
+ btn_reset_guard.click(fn=reset_guardians_handler, outputs=alert)
861
+
862
+ gr.Timer(3).tick(fn=check_live_pnl_and_status, inputs=stats_dd,
863
+ outputs=[logs, status, live_chart, t_price, t_entry, t_tp, t_sl, t_pnl, watchlist_out, diagnostic_out, type_stats_out, wallet_out, history_out, neural_out])
864
+ return demo
865
 
866
+ fast_api_server = FastAPI(lifespan=lifespan)
867
+ gradio_dashboard = create_gradio_ui()
868
+ app = gr.mount_gradio_app(app=fast_api_server, blocks=gradio_dashboard, path="/")
 
869
 
 
 
 
870
  if __name__ == "__main__":
871
+ import uvicorn
872
  uvicorn.run(app, host="0.0.0.0", port=7860)