const { useState, useEffect, useCallback } = React; const API = "/api"; let ADMIN_TOKEN = (typeof window !== "undefined" && window.localStorage) ? (window.localStorage.getItem("ASTER_ADMIN_TOKEN") || "") : ""; function ensureAdminToken() { if (!ADMIN_TOKEN && typeof window !== "undefined") { const t = window.prompt("请输入 Dashboard Admin Token(一次保存)", ""); if (t && t.trim()) { ADMIN_TOKEN = t.trim(); try { window.localStorage.setItem("ASTER_ADMIN_TOKEN", ADMIN_TOKEN); } catch {} } } return ADMIN_TOKEN; } // ─── Palette: dark terminal aesthetic ─── const C = { bg: "#0a0e17", card: "#111827", cardHover: "#1a2236", border: "#1e293b", borderActive: "#334155", text: "#e2e8f0", textDim: "#64748b", textMuted: "#475569", accent: "#22d3ee", accentDim: "#0891b2", green: "#10b981", greenDim: "#065f46", red: "#ef4444", redDim: "#7f1d1d", yellow: "#f59e0b", yellowDim: "#78350f", purple: "#a78bfa", }; // ─── Utility ─── const fmt = (n, d = 2) => (n !== null && n !== undefined ? Number(n).toFixed(d) : "—"); const fmtUsd = (n) => (n !== null && n !== undefined ? `$${Number(n).toFixed(2)}` : "—"); async function api(path, opts = {}) { try { const token = ensureAdminToken(); const authHeaders = token ? { Authorization: `Bearer ${token}` } : {}; const res = await fetch(`${API}${path}`, { headers: { "Content-Type": "application/json", ...authHeaders, ...(opts.headers || {}) }, ...opts, }); if (!res.ok) { const err = await res.json().catch(() => ({})); if (res.status === 401) { ADMIN_TOKEN = ""; try { window.localStorage.removeItem("ASTER_ADMIN_TOKEN"); } catch {} } throw new Error(err.detail || `HTTP ${res.status}`); } return await res.json(); } catch (e) { console.error(`API Error [${path}]:`, e); throw e; } } // ─── Components ─── function StatusDot({ active, color }) { const c = color || (active ? C.green : C.textMuted); return ( ); } function Badge({ children, color = C.accent }) { return ( {children} ); } function Card({ title, children, action, style = {} }) { return (
{(title || action) && (
{title && (

{title}

)} {action}
)} {children}
); } function Btn({ children, onClick, color = C.accent, small, disabled, style = {} }) { const [hover, setHover] = useState(false); return ( ); } function Input({ label, value, onChange, placeholder, type = "text", style = {} }) { return (
{label && } onChange(e.target.value)} placeholder={placeholder} style={{ width: "100%", background: C.bg, border: `1px solid ${C.border}`, borderRadius: 6, padding: "8px 12px", color: C.text, fontSize: 13, outline: "none", boxSizing: "border-box", fontFamily: "'JetBrains Mono', 'Fira Code', monospace", }} />
); } function Select({ label, value, onChange, options, style = {} }) { return (
{label && }
); } // ─── Setup Panel ─── function SetupPanel({ onConnected }) { const [apiKey, setApiKey] = useState(""); const [apiSecret, setApiSecret] = useState(""); const [baseUrl, setBaseUrl] = useState("https://fapi.asterdex.com"); const [chain, setChain] = useState("bnb"); const [status, setStatus] = useState(null); const [loading, setLoading] = useState(false); const connect = async () => { setLoading(true); try { const res = await api("/account/configure", { method: "POST", body: JSON.stringify({ api_key: apiKey, api_secret: apiSecret, base_url: baseUrl, chain }), }); setStatus(res); if (res.status === "connected") { setTimeout(() => onConnected(), 500); } } catch (e) { setStatus({ status: "error", message: e.message }); } setLoading(false); }; return (
⬡ ASTER
Trading System Configuration
setForm({ ...form, type: v })} options={types.length ? types : ["twap", "iceberg", "grid", "momentum"]} /> setForm({ ...form, name: v })} placeholder="strategy-01" /> setForm({ ...form, symbol: v })} placeholder="BTCUSDT" />
{form.type === "twap" ? (
TWAP 简单模式
setTwap({ ...twap, total_quantity: v })} type="number" /> setTwap({ ...twap, num_slices: v })} type="number" /> setTwap({ ...twap, duration_minutes: v })} type="number" /> setTwap({ ...twap, order_type: v })} options={["MARKET", "LIMIT"]} />
applyTwapPreset("safe")}>保守模板 applyTwapPreset("normal")}>标准模板 applyTwapPreset("fast")}>快速模板
{twapPreview}
) : form.type === "iceberg" ? (
Iceberg 简单模式(隐藏式 maker 执行)
setIceberg({ ...iceberg, total_quantity: v })} type="number" /> setIceberg({ ...iceberg, duration_minutes: v })} type="number" /> setIceberg({ ...iceberg, min_qty: v })} type="number" /> setIceberg({ ...iceberg, max_qty: v })} type="number" /> setIceberg({ ...iceberg, min_interval: v })} type="number" /> setIceberg({ ...iceberg, max_interval: v })} type="number" /> setIceberg({ ...iceberg, price_offset_ticks: v })} type="number" /> setIceberg({ ...iceberg, max_offset_ticks: v })} type="number" /> setIceberg({ ...iceberg, pause_on_spread_bps: v })} type="number" /> setIceberg({ ...iceberg, max_deviation_bps: v })} type="number" /> setIceberg({ ...iceberg, buy_ceiling_price: v })} type="number" /> setIceberg({ ...iceberg, sell_floor_price: v })} type="number" /> setIceberg({ ...iceberg, leverage: v })} type="number" /> setIceberg({ ...iceberg, use_binance_anchor: v === "yes" })} options={[{label:"否", value:"no"}, {label:"是", value:"yes"}]} /> setIceberg({ ...iceberg, binance_symbol: v })} /> setIceberg({ ...iceberg, binance_offset_pct: v })} type="number" />
applyIcebergPreset("safe")}>保守模板 applyIcebergPreset("normal")}>标准模板 applyIcebergPreset("fast")}>快速模板 applyIcebergPreset("uai_short")}>UAI做空3x applyIcebergPreset("uai_bn_short")}>UAI-BN(-0.3%)空3x
{icebergPreview}
) : ( setForm({ ...form, params: v })} placeholder='{"quantity": 0.001}' style={{ marginTop: 10 }} /> )} Create Strategy )} {strategies.length === 0 ? (
No strategies created yet. Click "+ New" to create one.
) : ( strategies.map((s) => (
{s.name} {s.symbol} {s.strategy_id}
{Object.entries(s.params || {}).slice(0, 4).map(([k, v]) => k + "=" + v).join(" · ")}
toggleStrategy(s.strategy_id, s.state)} color={s.state === "running" ? C.red : C.green}>{s.state === "running" ? "Stop" : "Start"} deleteStrategy(s.strategy_id)} color={C.textDim}>✕
)) )} ); } // ─── Orders Table ─── function OrdersTable({ orders }) { return ( {orders.length === 0 ? (
No orders yet
) : (
{["Time", "Strategy", "Symbol", "Side", "Type", "Qty", "Price", "Status"].map((h) => ( ))} {orders.slice(0, 50).map((o, i) => ( ))}
{h}
{o.created_at?.slice(5, 19)} {o.strategy_id?.slice(0, 6) || "manual"} {o.symbol} {o.side} {o.order_type} {fmt(o.quantity, 4)} {fmt(o.price)} {o.status}
)}
); } // ─── Alerts Panel ─── function AlertsPanel({ alerts }) { const levelColor = { CRITICAL: C.red, WARNING: C.yellow, INFO: C.accent }; return ( {alerts.length === 0 ? (
No alerts — system healthy
) : (
{alerts.map((a, i) => (
{a.level}
{a.message}
{a.category} · {a.timestamp?.slice(0, 19)}
))}
)}
); } // ─── Risk Config Panel ─── function RiskPanel({ riskStatus }) { if (!riskStatus) return null; const config = riskStatus.config || {}; const items = [ { label: "Max Order Value", value: fmtUsd(config.max_order_value_usd) }, { label: "Max Daily Loss", value: fmtUsd(config.max_daily_loss_usd) }, { label: "Max Drawdown", value: `${config.max_drawdown_pct}%` }, { label: "Max Orders/min", value: config.max_orders_per_minute }, { label: "Daily PnL", value: fmtUsd(riskStatus.daily_pnl), color: (riskStatus.daily_pnl || 0) >= 0 ? C.green : C.red }, { label: "Enabled", value: config.enabled ? "YES" : "NO", color: config.enabled ? C.green : C.red }, ]; return (
{items.map((item) => (
{item.label}
{item.value}
))}
); } // ─── Manual Trade Panel ─── function ManualTradePanel() { const [symbol, setSymbol] = useState("BTCUSDT"); const [side, setSide] = useState("BUY"); const [qty, setQty] = useState(""); const [price, setPrice] = useState(""); const [orderType, setOrderType] = useState("MARKET"); const [result, setResult] = useState(null); const placeTrade = async () => { try { const body = { symbol, side, order_type: orderType, quantity: parseFloat(qty) }; if (orderType === "LIMIT" && price) body.price = parseFloat(price); if (orderType === "LIMIT") body.time_in_force = "GTC"; const res = await api("/trading/order", { method: "POST", body: JSON.stringify(body) }); setResult({ ok: true, msg: `Order placed: ${res.order_id} ${res.status}` }); } catch (e) { setResult({ ok: false, msg: e.message }); } }; return (
{orderType === "LIMIT" && }
{side} {symbol} {result && (
{result.msg}
)}
); } // ─── Main Dashboard ─── function Dashboard() { const [connected, setConnected] = useState(null); // null = checking, true, false const [strategies, setStrategies] = useState([]); const [positions, setPositions] = useState([]); const [orders, setOrders] = useState([]); const [alerts, setAlerts] = useState([]); const [stats, setStats] = useState({}); const [riskStatus, setRiskStatus] = useState(null); const [tab, setTab] = useState("overview"); const [error, setError] = useState(null); const checkHealth = useCallback(async () => { try { const h = await api("/health"); setConnected(h.connected); setError(null); } catch { setError("Cannot reach trading server. Make sure it's running on port 8888."); setConnected(false); } }, []); const fetchAll = useCallback(async () => { try { const [strats, pos, ords, alrts, sts, risk] = await Promise.allSettled([ api("/strategies"), api("/account/positions"), api("/data/orders?limit=50"), api("/risk/alerts?limit=20"), api("/data/stats"), api("/risk/status"), ]); if (strats.status === "fulfilled") setStrategies(strats.value || []); if (pos.status === "fulfilled") setPositions(pos.value || []); if (ords.status === "fulfilled") setOrders(ords.value || []); if (alrts.status === "fulfilled") setAlerts(alrts.value || []); if (sts.status === "fulfilled") setStats(sts.value || {}); if (risk.status === "fulfilled") setRiskStatus(risk.value); } catch {} }, []); useEffect(() => { checkHealth(); const interval = setInterval(checkHealth, 10000); return () => clearInterval(interval); }, [checkHealth]); useEffect(() => { if (connected) { fetchAll(); const interval = setInterval(fetchAll, 5000); return () => clearInterval(interval); } }, [connected, fetchAll]); // Server unreachable if (error) { return (
Server Unreachable
{error}
cd aster-trading && python main.py
); } // Not connected to Aster if (connected === false) { return (
{ setConnected(true); fetchAll(); }} />
); } // Loading if (connected === null) { return (
Connecting...
); } const tabs = [ { id: "overview", label: "Overview" }, { id: "strategies", label: "Strategies" }, { id: "trade", label: "Manual Trade" }, { id: "risk", label: "Risk" }, ]; return (
{/* Header */}
⬡ ASTER Trading System
{tabs.map((t) => ( ))} Connected
{/* Content */}
{tab === "overview" && (
)} {tab === "strategies" && (
)} {tab === "trade" && (
)} {tab === "risk" && (
)}
); } window.Dashboard = Dashboard;