| """ |
| MarketMind — Gradio Dashboard |
| A premium trading terminal UI for multi-agent financial market simulation. |
| Optimized for Hugging Face Spaces deployment. |
| """ |
|
|
| import sys |
| import os |
| import time |
| import json |
| import pandas as pd |
| import numpy as np |
| import plotly.graph_objects as go |
| from plotly.subplots import make_subplots |
| import gradio as gr |
| from datetime import datetime |
|
|
| |
| sys.path.insert(0, os.path.dirname(__file__)) |
|
|
| from engine.simulation import SimulationEngine, SimulationConfig |
| from agents.momentum_agent import MomentumAgent |
| from agents.mean_reversion_agent import MeanReversionAgent |
| from agents.fundamental_agent import FundamentalAgent |
| from agents.market_maker_agent import MarketMakerAgent |
| from agents.noise_trader import NoiseTrader |
|
|
|
|
| |
| def build_agents(n_mom, n_mr, n_fund, n_noise, n_mm): |
| agents = [] |
| for i in range(n_mom): |
| agents.append(MomentumAgent(f"momentum_{i+1}")) |
| for i in range(n_mr): |
| agents.append(MeanReversionAgent(f"meanrev_{i+1}")) |
| for i in range(n_fund): |
| agents.append(FundamentalAgent(f"fundamental_{i+1}", fair_value=100.0)) |
| for i in range(n_noise): |
| agents.append(NoiseTrader(f"noise_{i+1}")) |
| for i in range(n_mm): |
| agents.append(MarketMakerAgent(f"marketmaker_{i+1}")) |
| return agents |
|
|
|
|
| |
|
|
| COLORS = { |
| "price": "#00d4ff", |
| "fair_value": "#ff3366", |
| "spread": "#ffaa00", |
| "volume": "#7c4dff", |
| "bg": "rgba(0,0,0,0)", |
| "grid": "rgba(255,255,255,0.04)", |
| "text": "#8892b0", |
| "agents": ["#00d4ff", "#00ff88", "#ff3366", "#ffaa00", "#7c4dff", |
| "#ff6b9d", "#c084fc", "#34d399", "#f87171", "#fbbf24"], |
| } |
|
|
|
|
| def build_main_chart(ticks_data): |
| """Build the primary price + volume + spread multi-panel chart.""" |
| ticks = [r["tick"] for r in ticks_data] |
| prices = [r["mid_price"] if r["mid_price"] else 100.0 for r in ticks_data] |
| fair_vals = [r.get("true_fair_value", 100.0) for r in ticks_data] |
| spreads = [r["spread"] if r["spread"] else 0.0 for r in ticks_data] |
| volumes = [r["volume"] for r in ticks_data] |
| regimes = [r["regime"] for r in ticks_data] |
|
|
| fig = make_subplots( |
| rows=3, cols=1, |
| shared_xaxes=True, |
| vertical_spacing=0.03, |
| row_heights=[0.6, 0.2, 0.2], |
| subplot_titles=None, |
| ) |
|
|
| |
| fig.add_trace(go.Scatter( |
| x=ticks, y=prices, |
| mode="lines", |
| line=dict(color=COLORS["price"], width=2.5), |
| name="Market Price", |
| fill="tozeroy", |
| fillcolor="rgba(0, 212, 255, 0.05)", |
| ), row=1, col=1) |
|
|
| |
| fig.add_trace(go.Scatter( |
| x=ticks, y=fair_vals, |
| mode="lines", |
| line=dict(color=COLORS["fair_value"], width=1.5, dash="dot"), |
| name="Fair Value", |
| ), row=1, col=1) |
|
|
| |
| regime_colors = {"Efficient": "rgba(0,255,136,0.06)", |
| "Trending": "rgba(255,170,0,0.06)", |
| "Volatile": "rgba(255,51,102,0.06)", |
| "Crashed": "rgba(255,0,0,0.10)"} |
| |
| regimes = [t["regime"] for t in ticks_data] |
| ticks = [t["tick"] for t in ticks_data] |
| |
| if regimes: |
| prev_regime = regimes[0] |
| band_start = ticks[0] |
| for i in range(1, len(regimes)): |
| if regimes[i] != prev_regime or i == len(regimes) - 1: |
| fig.add_vrect( |
| x0=band_start, x1=ticks[i], |
| fillcolor=regime_colors.get(prev_regime, "rgba(0,0,0,0)"), |
| layer="below", line_width=0, row=1, col=1, |
| ) |
| band_start = ticks[i] |
| prev_regime = regimes[i] |
|
|
| |
| fig.add_trace(go.Bar( |
| x=ticks, y=volumes, |
| marker_color=COLORS["volume"], |
| opacity=0.6, |
| name="Volume", |
| ), row=2, col=1) |
|
|
| |
| fig.add_trace(go.Scatter( |
| x=ticks, y=spreads, |
| mode="lines", |
| line=dict(color=COLORS["spread"], width=2), |
| fill="tozeroy", |
| fillcolor="rgba(255,170,0,0.08)", |
| name="Spread", |
| ), row=3, col=1) |
|
|
| |
| |
| prices_only = [t["price"] for t in ticks_data] if ticks_data else [100.0] |
| min_p, max_p = min(prices_only), max(prices_only) |
| if max_p - min_p < 1.0: |
| center = (max_p + min_p) / 2 |
| min_p, max_p = center - 0.5, center + 0.5 |
|
|
| fig.update_layout( |
| template="plotly_dark", |
| paper_bgcolor=COLORS["bg"], |
| plot_bgcolor=COLORS["bg"], |
| font=dict(family="JetBrains Mono, monospace", color=COLORS["text"]), |
| height=620, |
| margin=dict(l=50, r=20, t=30, b=30), |
| legend=dict( |
| orientation="h", yanchor="bottom", y=1.02, |
| xanchor="right", x=1, |
| bgcolor="rgba(0,0,0,0)", |
| font=dict(size=11), |
| ), |
| showlegend=True, |
| ) |
| |
| |
| fig.update_yaxes(range=[min_p * 0.998, max_p * 1.002], row=1, col=1) |
| |
| |
| fig.update_layout(modebar_remove=['zoom', 'pan', 'select', 'lasso2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d']) |
|
|
| for row in range(1, 4): |
| fig.update_xaxes( |
| gridcolor=COLORS["grid"], zeroline=False, |
| showticklabels=(row == 3), row=row, col=1, |
| ) |
| fig.update_yaxes( |
| gridcolor=COLORS["grid"], zeroline=False, |
| row=row, col=1, |
| ) |
|
|
| fig.update_yaxes(title_text="Price", row=1, col=1) |
| fig.update_yaxes(title_text="Vol", row=2, col=1) |
| fig.update_yaxes(title_text="Spread", row=3, col=1) |
| fig.update_xaxes(title_text="Tick", row=3, col=1) |
|
|
| return fig |
|
|
|
|
| def build_pnl_chart(pnl_data, agents): |
| """Build the agent PnL leaderboard chart.""" |
| fig = go.Figure() |
|
|
| agent_ids = [a.agent_id for a in agents] |
| for idx, aid in enumerate(agent_ids): |
| agent_rows = [r for r in pnl_data if r["agent_id"] == aid] |
| ticks = [r["tick"] for r in agent_rows] |
| pnls = [r["pnl"] for r in agent_rows] |
| fig.add_trace(go.Scatter( |
| x=ticks, y=pnls, |
| mode="lines", |
| line=dict(color=COLORS["agents"][idx % len(COLORS["agents"])], width=2), |
| name=aid, |
| )) |
|
|
| fig.update_layout( |
| template="plotly_dark", |
| paper_bgcolor=COLORS["bg"], |
| plot_bgcolor=COLORS["bg"], |
| font=dict(family="JetBrains Mono, monospace", color=COLORS["text"]), |
| height=350, |
| margin=dict(l=50, r=20, t=30, b=30), |
| legend=dict( |
| orientation="h", yanchor="bottom", y=1.02, |
| xanchor="right", x=1, |
| bgcolor="rgba(0,0,0,0)", |
| font=dict(size=10), |
| ), |
| yaxis_title="PnL ($)", |
| xaxis_title="Tick", |
| xaxis=dict(gridcolor=COLORS["grid"], zeroline=False), |
| yaxis=dict(gridcolor=COLORS["grid"], zeroline=False), |
| ) |
|
|
| |
| fig.add_hline(y=0, line_dash="dash", line_color="rgba(255,255,255,0.15)", line_width=1) |
| fig.update_layout(dragmode=False, hovermode="x unified") |
| fig.update_layout(modebar_orientation='h', modebar_remove=['zoom', 'pan', 'select', 'lasso2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d']) |
|
|
| return fig |
|
|
|
|
| def build_leaderboard(agent_pnl_rows, ticks_data): |
| """Create a pandas dataframe for the agent leaderboard with advanced metrics.""" |
| from engine.metrics import calculate_sharpe_ratio, calculate_max_drawdown, calculate_win_rate |
| |
| if not agent_pnl_rows: |
| return pd.DataFrame() |
|
|
| |
| pnl_map = {} |
| for row in agent_pnl_rows: |
| aid = row["agent_id"] |
| if aid not in pnl_map: |
| pnl_map[aid] = [] |
| pnl_map[aid].append(row["pnl"]) |
|
|
| leaderboard_data = [] |
| for aid, pnl_series in pnl_map.items(): |
| final_pnl = pnl_series[-1] |
| sharpe = calculate_sharpe_ratio(pnl_series) |
| mdd = calculate_max_drawdown(pnl_series) |
| wr = calculate_win_rate(pnl_series) |
| |
| leaderboard_data.append({ |
| "Agent ID": aid, |
| "Total PnL": f"${final_pnl:,.2f}", |
| "Sharpe": f"{sharpe:.2f}", |
| "Max DD": f"{mdd:.1%}", |
| "Win Rate": f"{wr:.1%}" |
| }) |
|
|
| df = pd.DataFrame(leaderboard_data) |
| if not df.empty: |
| df = df.sort_values(by="Total PnL", ascending=False) |
| return df |
|
|
|
|
| def build_stats_html(ticks_data, pnl_data, elapsed): |
| """Build the live stats panel as HTML.""" |
| from datetime import datetime |
| if not ticks_data: |
| return "<p>No data</p>" |
|
|
| last = ticks_data[-1] |
| first_price = ticks_data[0]["mid_price"] or 100.0 |
| last_price = last["mid_price"] or 100.0 |
| pct_change = ((last_price - first_price) / first_price) * 100 |
| total_volume = sum(r["volume"] for r in ticks_data) |
| total_trades = sum(r["trade_count"] for r in ticks_data) |
| avg_spread = np.mean([r["spread"] for r in ticks_data if r["spread"]]) if ticks_data else 0 |
| regime = last.get("regime", "Unknown") |
| timestamp = datetime.now().strftime("%H:%M:%S") |
|
|
| regime_colors = { |
| "Efficient": "#00ff88", |
| "Trending": "#ffaa00", |
| "Volatile": "#ff3366", |
| "Crashed": "#ff0000", |
| } |
| rc = regime_colors.get(regime, "#8892b0") |
|
|
| return f""" |
| <div class="stats-container" style="display:grid; grid-template-columns: 1fr 1fr; gap: 12px;"> |
| <div class="stat-card"> |
| <div class="stat-label">TOTAL TRADES</div> |
| <div class="stat-value">{total_trades}</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-label">VOLUME</div> |
| <div class="stat-value">{total_volume:,}</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-label">AVG SPREAD</div> |
| <div class="stat-value">{avg_spread:.4f}</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-label">LAST UPDATE</div> |
| <div class="stat-value">{datetime.now().strftime('%H:%M:%S')}</div> |
| </div> |
| </div> |
| """ |
|
|
|
|
| |
|
|
| def run_simulation(n_mom, n_mr, n_fund, n_noise, n_mm, |
| num_ticks, warmup_ticks, volatility, use_llm, api_key, hf_model, vllm_url, |
| progress=gr.Progress()): |
| """Run the full simulation and return all visualization components.""" |
| print(f"DEBUG: Starting simulation - LLM: {use_llm}, URL: {vllm_url}") |
| |
| if use_llm and not api_key.strip(): |
| |
| is_local = "localhost" in vllm_url or "127.0.0.1" in vllm_url or "0.0.0.0" in vllm_url |
| if not is_local: |
| raise gr.Error("API Key is required when Live LLM Mode is enabled for remote providers.") |
|
|
| agents = build_agents(int(n_mom), int(n_mr), int(n_fund), int(n_noise), int(n_mm)) |
| if not agents: |
| raise gr.Error("Add at least one agent to run the simulation.") |
|
|
| config = SimulationConfig( |
| num_ticks=int(num_ticks), |
| initial_price=100.0, |
| use_llm=use_llm, |
| vllm_base_url=vllm_url if vllm_url else "https://api-inference.huggingface.co/v1", |
| vllm_model=hf_model if hf_model else "Qwen/Qwen2.5-7B-Instruct", |
| vllm_api_key=api_key if api_key else "EMPTY", |
| log_to_csv=False, |
| base_volatility=volatility, |
| warmup_ticks=int(warmup_ticks), |
| enable_seed_liquidity=True, |
| fee_per_trade=0.01 |
| ) |
|
|
| engine = SimulationEngine(agents, config) |
|
|
| try: |
| t0 = time.time() |
| |
| |
| os.makedirs(config.output_dir, exist_ok=True) |
| |
| |
| print(f"DEBUG: Executing simulation loop - LLM Mode: {use_llm}") |
| for tick in engine.run_generator(): |
| is_llm_tick = use_llm and tick > int(warmup_ticks) |
|
|
| |
| if True: |
| ticks_data = engine.csv_rows |
| pnl_data = engine.agent_pnl_rows |
|
|
| if ticks_data: |
|
|
| import pandas as pd |
| raw_df = pd.DataFrame(ticks_data) |
| |
| |
| main_df = raw_df.drop(columns=['price'], errors='ignore').melt( |
| id_vars=['tick'], |
| value_vars=['mid_price', 'true_fair_value'], |
| var_name='metric', |
| value_name='price' |
| ) |
| main_df['metric'] = main_df['metric'].map({ |
| 'mid_price': '📈 Mid Price', |
| 'true_fair_value': '🎯 Fair Value' |
| }) |
| |
| spread_df = raw_df[['tick', 'spread']].copy() |
| volume_df = raw_df[['tick', 'volume']].copy() |
| |
| pnl_df = pd.DataFrame(pnl_data) |
| |
| leaderboard = build_leaderboard(pnl_data, ticks_data) |
| stats_html = build_stats_html(ticks_data, pnl_data, time.time() - t0) |
|
|
| api_status = "🟢 API OK" |
| if use_llm and engine.llm_client and engine.llm_client.error_count > 0: |
| api_status = f"🔴 API ERROR ({engine.llm_client.error_count})" |
|
|
| |
| status_msg = f"{api_status} | Market Status: {engine.metrics.classify_regime()} | Current Price: ${ticks_data[-1]['mid_price']:.2f}" |
|
|
| yield main_df, pnl_df, spread_df, volume_df, leaderboard, stats_html, None, status_msg |
|
|
| |
| if not is_llm_tick: |
| time.sleep(0.2) |
| else: |
| time.sleep(0.05) |
| print(f"DEBUG: Simulation complete in {time.time()-t0:.2f}s") |
| |
| |
| ticks_data = engine.csv_rows |
| pnl_data = engine.agent_pnl_rows |
| |
| import pandas as pd |
| raw_df = pd.DataFrame(ticks_data) |
| main_df = raw_df.drop(columns=['price'], errors='ignore').melt( |
| id_vars=['tick'], value_vars=['mid_price', 'true_fair_value'], |
| var_name='metric', value_name='price' |
| ) |
| main_df['metric'] = main_df['metric'].map({ |
| 'mid_price': '📈 Mid Price', |
| 'true_fair_value': '🎯 Fair Value' |
| }) |
| spread_df = raw_df[['tick', 'spread']].copy() |
| volume_df = raw_df[['tick', 'volume']].copy() |
| pnl_df = pd.DataFrame(pnl_data) |
| leaderboard = build_leaderboard(pnl_data, ticks_data) |
| stats_html = build_stats_html(ticks_data, pnl_data, time.time() - t0) |
| |
| |
| export_path = "marketmind_simulation.csv" |
| raw_df.to_csv(export_path, index=False) |
| |
| |
| engine._write_csvs() |
| |
| status_msg = "✅ Simulation Complete" |
| yield main_df, pnl_df, spread_df, volume_df, leaderboard, stats_html, export_path, status_msg |
| except Exception as e: |
| print(f"CRITICAL ERROR in run_simulation: {str(e)}") |
| import traceback |
| traceback.print_exc() |
| raise gr.Error(f"Simulation Failed: {str(e)}") |
|
|
|
|
| |
|
|
| CUSTOM_CSS = """ |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;800&family=JetBrains+Mono:wght@400;500&display=swap'); |
| |
| /* ── Global ─────────────────────────────────────── */ |
| html, body { |
| background: #0a0b10 !important; |
| } |
| .gradio-container { |
| max-width: 100% !important; |
| font-family: 'Inter', sans-serif !important; |
| background: linear-gradient(160deg, #0a0b10 0%, #111827 50%, #0d1117 100%) !important; |
| min-height: 100vh; |
| } |
| .main { |
| background: transparent !important; |
| } |
| footer { |
| display: none !important; |
| } |
| |
| /* ── Top Bar ────────────────────────────────────── */ |
| .title-bar { |
| background: linear-gradient(135deg, rgba(0,212,255,0.08), rgba(124,77,255,0.08)); |
| border: 1px solid rgba(0,212,255,0.12); |
| border-radius: 16px; |
| padding: 24px 32px; |
| margin-bottom: 16px; |
| position: relative; |
| overflow: hidden; |
| } |
| .title-bar::before { |
| content: ''; |
| position: absolute; |
| top: 0; left: 0; right: 0; |
| height: 2px; |
| background: linear-gradient(90deg, #00d4ff, #7c4dff, #ff3366); |
| } |
| .title-bar h1 { |
| margin: 0 0 4px 0; |
| font-size: 2em; |
| font-weight: 800; |
| background: linear-gradient(135deg, #00d4ff 0%, #7c4dff 50%, #ff3366 100%); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| letter-spacing: -1px; |
| } |
| .title-bar p { |
| margin: 0; |
| color: #8892b0; |
| font-size: 0.95em; |
| max-width: 700px; |
| } |
| |
| /* ── Stat Cards ─────────────────────────────────── */ |
| .stat-card { |
| background: rgba(17,24,39,0.7); |
| border: 1px solid rgba(255,255,255,0.06); |
| border-radius: 12px; |
| padding: 14px 16px; |
| text-align: center; |
| } |
| .stat-label { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 0.65em; |
| color: #5a6785; |
| letter-spacing: 1.5px; |
| text-transform: uppercase; |
| margin-bottom: 4px; |
| } |
| .stat-value { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 1.3em; |
| font-weight: 600; |
| color: #e2e8f0; |
| } |
| .stat-delta { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 0.85em; |
| font-weight: 500; |
| } |
| |
| /* ── Panel Sections ─────────────────────────────── */ |
| .panel-header { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 0.75em; |
| color: #00d4ff; |
| letter-spacing: 2px; |
| text-transform: uppercase; |
| margin: 16px 0 8px 0; |
| padding-bottom: 6px; |
| border-bottom: 1px solid rgba(0,212,255,0.15); |
| } |
| |
| /* ── Gradio Overrides ───────────────────────────── */ |
| .dark .block { |
| background: rgba(17,24,39,0.5) !important; |
| border: 1px solid rgba(255,255,255,0.05) !important; |
| border-radius: 12px !important; |
| } |
| .dark .label-wrap { |
| color: #8892b0 !important; |
| } |
| .dark input, .dark textarea, .dark select { |
| background: rgba(15,20,35,0.8) !important; |
| border: 1px solid rgba(255,255,255,0.08) !important; |
| color: #e2e8f0 !important; |
| border-radius: 8px !important; |
| } |
| .dark .primary { |
| background: linear-gradient(135deg, #00d4ff 0%, #7c4dff 100%) !important; |
| border: none !important; |
| font-weight: 600 !important; |
| letter-spacing: 0.5px !important; |
| transition: all 0.3s ease !important; |
| box-shadow: 0 4px 15px rgba(0,212,255,0.25) !important; |
| } |
| .dark .primary:hover { |
| box-shadow: 0 6px 25px rgba(0,212,255,0.4) !important; |
| transform: translateY(-1px) !important; |
| } |
| .dark table { |
| font-family: 'JetBrains Mono', monospace !important; |
| font-size: 0.85em !important; |
| } |
| /* -- Checkbox Pop -- */ |
| input[type="checkbox"] { |
| appearance: none; |
| -webkit-appearance: none; |
| height: 20px; |
| width: 20px; |
| background-color: rgba(0,212,255,0.1); |
| border: 2px solid #00d4ff !important; |
| border-radius: 4px; |
| cursor: pointer; |
| display: inline-block; |
| position: relative; |
| vertical-align: middle; |
| } |
| input[type="checkbox"]:checked { |
| background-color: #00d4ff !important; |
| box-shadow: 0 0 10px rgba(0,212,255,0.5); |
| } |
| input[type="checkbox"]:checked::after { |
| content: '✓'; |
| position: absolute; |
| color: #0a0b10; |
| font-size: 14px; |
| font-weight: 800; |
| left: 4px; |
| top: -2px; |
| } |
| .dark label span { |
| color: #00d4ff !important; |
| font-weight: 800 !important; |
| letter-spacing: 0.5px; |
| } |
| |
| /* ── Scrollbar ──────────────────────────────────── */ |
| ::-webkit-scrollbar { width: 6px; } |
| ::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); } |
| ::-webkit-scrollbar-thumb { background: rgba(0,212,255,0.3); border-radius: 3px; } |
| """ |
|
|
|
|
| |
|
|
| def create_app(): |
| with gr.Blocks( |
| title="MarketMind | Multi-Agent Market Simulation", |
| ) as app: |
|
|
| |
| gr.HTML(""" |
| <div class="title-bar"> |
| <h1>⚡ MarketMind</h1> |
| <p>Multi-agent financial market simulation powered by LLM agents competing inside a |
| continuous double auction. Adjust the agent composition to discover if the market |
| self-organizes to efficiency — or collapses into chaos.</p> |
| </div> |
| """) |
|
|
| with gr.Row(): |
| |
| |
| |
| with gr.Column(scale=1, min_width=280): |
|
|
| gr.HTML('<div class="panel-header">⚙ Engine</div>') |
| |
| use_llm = gr.Checkbox(label="Live LLM Mode", value=False, |
| info="Check this to use external API for live inference") |
|
|
| with gr.Accordion("🔑 Live LLM Settings", open=True) as llm_settings: |
| engine_preset = gr.Radio( |
| ["AMD Cloud / HF", "Groq", "Together AI", "Google Gemini", "Local (vLLM/Ollama)", "Custom"], |
| label="Infrastructure Preset", |
| value="AMD Cloud / HF" |
| ) |
| api_key = gr.Textbox(label="API Key", type="password", |
| placeholder="hf_... or gsk_...", interactive=True) |
| hf_model = gr.Textbox(label="Model ID", value="Qwen/Qwen2.5-7B-Instruct", interactive=True) |
| vllm_url = gr.Textbox(label="Inference Base URL", |
| value="https://api-inference.huggingface.co/v1", |
| placeholder="http://YOUR_AMD_IP:8000/v1", interactive=True) |
|
|
| def update_preset(preset): |
| if preset == "Groq": |
| return "llama-3.1-8b-instant", "https://api.groq.com/openai/v1" |
| elif preset == "Together AI": |
| return "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo", "https://api.together.xyz/v1" |
| elif preset == "Google Gemini": |
| return "gemini-1.5-flash", "https://generativelanguage.googleapis.com/v1beta/openai/" |
| elif preset == "Local (vLLM/Ollama)": |
| return "llama3", "http://localhost:8000/v1" |
| elif preset == "Custom": |
| return "", "" |
| else: |
| return "Qwen/Qwen2.5-7B-Instruct", "https://api-inference.huggingface.co/v1" |
|
|
| engine_preset.change( |
| fn=update_preset, |
| inputs=[engine_preset], |
| outputs=[hf_model, vllm_url] |
| ) |
|
|
| gr.HTML('<div class="panel-header">🧬 Agent Composition</div>') |
| n_mom = gr.Slider(0, 10, value=2, step=1, label="Momentum Traders") |
| n_mr = gr.Slider(0, 10, value=1, step=1, label="Mean Reversion") |
| n_fund = gr.Slider(0, 10, value=1, step=1, label="Fundamental") |
| n_noise = gr.Slider(0, 10, value=1, step=1, label="Noise Traders") |
| n_mm = gr.Slider(0, 5, value=1, step=1, label="Market Makers") |
|
|
| gr.HTML('<div class="panel-header">🔧 Parameters</div>') |
| num_ticks = gr.Slider(20, 500, value=150, step=10, label="Simulation Ticks") |
| warmup_ticks = gr.Slider(0, 50, value=5, step=5, label="Market Warm-up (Ticks)", |
| info="Establishing baseline before LLMs take over") |
| volatility = gr.Slider(0.0, 0.05, value=0.005, step=0.001, |
| label="Market Volatility") |
|
|
| run_btn = gr.Button("▶ Execute Simulation", variant="primary", size="lg") |
| live_status = gr.Markdown("Ready to simulate...") |
|
|
| |
| gr.HTML('<div class="panel-header">📊 Session Stats</div>') |
| stats_panel = gr.HTML("<p style='color:#5a6785;text-align:center;padding:20px;'>Run a simulation to see stats</p>") |
| |
| gr.HTML('<div class="panel-header">💾 Export Data</div>') |
| export_file = gr.File(label="📥 Download Tick Data (CSV)", interactive=False) |
|
|
| |
| |
| |
| with gr.Column(scale=3): |
|
|
| main_chart = gr.LinePlot( |
| label="Market Overview", |
| x="tick", |
| y="price", |
| color="metric", |
| title="Live Mid Price vs Fair Value", |
| tooltip=["tick", "metric", "price"], |
| height=300 |
| ) |
| pnl_chart = gr.LinePlot( |
| label="Agent PnL Tracker", |
| x="tick", |
| y="pnl", |
| color="agent_id", |
| title="Agent PnL (Mark-to-Market)", |
| tooltip=["tick", "agent_id", "pnl"], |
| height=300 |
| ) |
| with gr.Row(): |
| spread_chart = gr.LinePlot( |
| label="Bid-Ask Spread", |
| x="tick", |
| y="spread", |
| title="Spread", |
| tooltip=["tick", "spread"], |
| height=200 |
| ) |
| volume_chart = gr.BarPlot( |
| label="Trade Volume", |
| x="tick", |
| y="volume", |
| title="Volume per Tick", |
| tooltip=["tick", "volume"], |
| height=200 |
| ) |
| leaderboard = gr.DataFrame( |
| label="🏆 Global Performance Metrics", |
| interactive=False, |
| wrap=True, |
| ) |
|
|
| |
| run_btn.click( |
| fn=run_simulation, |
| inputs=[n_mom, n_mr, n_fund, n_noise, n_mm, |
| num_ticks, warmup_ticks, volatility, use_llm, api_key, hf_model, vllm_url], |
| outputs=[main_chart, pnl_chart, spread_chart, volume_chart, leaderboard, stats_panel, export_file, live_status] |
| ) |
|
|
| |
| |
| app.queue(default_concurrency_limit=5) |
| return app |
|
|
|
|
| |
|
|
| if __name__ == "__main__": |
| app = create_app() |
| app.launch( |
| server_port=7860, |
| css=CUSTOM_CSS, |
| theme=gr.themes.Base( |
| primary_hue=gr.themes.colors.cyan, |
| secondary_hue=gr.themes.colors.purple, |
| neutral_hue=gr.themes.colors.slate, |
| font=gr.themes.GoogleFont("Inter"), |
| font_mono=gr.themes.GoogleFont("JetBrains Mono"), |
| ), |
| ) |
|
|