diff --git a/frontend/app/paper-v53/page.tsx b/frontend/app/paper-v53/page.tsx new file mode 100644 index 0000000..a179763 --- /dev/null +++ b/frontend/app/paper-v53/page.tsx @@ -0,0 +1,441 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Link from "next/link"; +import { authFetch, useAuth } from "@/lib/auth"; +import { XAxis, YAxis, Tooltip, ResponsiveContainer, ReferenceLine, Area, AreaChart } from "recharts"; + +function bjt(ms: number) { + const d = new Date(ms + 8 * 3600 * 1000); + return `${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")} ${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}`; +} + +function fmtPrice(p: number) { + return p < 100 ? p.toFixed(4) : p.toLocaleString("en-US", { minimumFractionDigits: 1, maximumFractionDigits: 1 }); +} + +function parseFactors(raw: any) { + if (!raw) return null; + if (typeof raw === "string") { try { return JSON.parse(raw); } catch { return null; } } + return raw; +} + +type StrategyTab = "v53_alt" | "v53_btc"; + +const STRATEGY_LABELS: Record = { + v53_alt: { + label: "๐ŸŸฃ ALT่ฝจ (v53_alt)", + desc: "ETH / XRP / SOL ยท ๅ››ๅฑ‚่ฏ„ๅˆ† 55/25/15/5", + coins: ["ETHUSDT", "XRPUSDT", "SOLUSDT"], + badgeClass: "bg-purple-100 text-purple-700 border border-purple-200", + }, + v53_btc: { + label: "๐Ÿ”ต BTC่ฝจ (v53_btc)", + desc: "BTCUSDT ยท Gate-Control้€ป่พ‘", + coins: ["BTCUSDT"], + badgeClass: "bg-amber-100 text-amber-700 border border-amber-200", + }, +}; + +// โ”€โ”€โ”€ ๆŽงๅˆถ้ขๆฟ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +function ControlPanel() { + const [config, setConfig] = useState(null); + const [saving, setSaving] = useState(false); + + useEffect(() => { + (async () => { + try { const r = await authFetch("/api/paper/config"); if (r.ok) setConfig(await r.json()); } catch {} + })(); + }, []); + + const toggle = async () => { + setSaving(true); + try { + const r = await authFetch("/api/paper/config", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ enabled: !config.enabled }), + }); + if (r.ok) setConfig(await r.json().then((j: any) => j.config)); + } catch {} finally { setSaving(false); } + }; + + if (!config) return null; + return ( +
+
+ + + {config.enabled ? "๐ŸŸข ่ฟ่กŒไธญ" : "โšช ๅทฒๅœๆญข"} + +
+
+ ๅˆๅง‹่ต„้‡‘: ${config.initial_balance?.toLocaleString()} + ๅ•็ฌ”้ฃŽ้™ฉ: {(config.risk_per_trade * 100).toFixed(0)}% + ๆœ€ๅคงๆŒไป“: {config.max_positions} +
+
+ ); +} + +// โ”€โ”€โ”€ ๆ€ป่งˆ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +function SummaryCards({ strategy }: { strategy: StrategyTab }) { + const [data, setData] = useState(null); + useEffect(() => { + const f = async () => { + try { const r = await authFetch(`/api/paper/summary?strategy=${strategy}`); if (r.ok) setData(await r.json()); } catch {} + }; + f(); const iv = setInterval(f, 10000); return () => clearInterval(iv); + }, [strategy]); + + if (!data) return
ๅŠ ่ฝฝไธญ...
; + return ( +
+ {[ + { label: "ๆ€ป็›ˆไบ(R)", value: `${data.total_pnl >= 0 ? "+" : ""}${data.total_pnl}R`, sub: `${data.total_pnl_usdt >= 0 ? "+" : ""}$${data.total_pnl_usdt}`, color: data.total_pnl >= 0 ? "text-emerald-600" : "text-red-500" }, + { label: "่ƒœ็އ", value: `${data.win_rate}%`, sub: `ๅ…ฑ${data.total_trades}็ฌ”`, color: "text-slate-800" }, + { label: "ๆŒไป“ไธญ", value: data.active_positions, sub: "ๆดป่ทƒไป“ไฝ", color: "text-blue-600" }, + { label: "็›ˆไบๆฏ”", value: data.profit_factor, sub: "Profit Factor", color: "text-slate-800" }, + { label: "ๅฝ“ๅ‰่ต„้‡‘", value: `$${data.balance?.toLocaleString()}`, sub: "่™šๆ‹Ÿไฝ™้ข", color: data.balance >= 10000 ? "text-emerald-600" : "text-red-500" }, + { label: "็Šถๆ€", value: data.start_time ? "่ฟ่กŒไธญ โœ…" : "็ญ‰ๅพ…้ฆ–็ฌ”", sub: "signal accumulating", color: "text-slate-600" }, + ].map(({ label, value, sub, color }) => ( +
+

{label}

+

{value}

+

{sub}

+
+ ))} +
+ ); +} + +// โ”€โ”€โ”€ ๅฝ“ๅ‰ๆŒไป“ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +function ActivePositions({ strategy }: { strategy: StrategyTab }) { + const [positions, setPositions] = useState([]); + const [wsPrices, setWsPrices] = useState>({}); + const [paperRiskUsd, setPaperRiskUsd] = useState(200); + const meta = STRATEGY_LABELS[strategy]; + + useEffect(() => { + (async () => { + try { const r = await authFetch("/api/paper/config"); if (r.ok) { const cfg = await r.json(); setPaperRiskUsd((cfg.initial_balance || 10000) * (cfg.risk_per_trade || 0.02)); } } catch {} + })(); + }, []); + + useEffect(() => { + const f = async () => { + try { const r = await authFetch(`/api/paper/positions?strategy=${strategy}`); if (r.ok) setPositions((await r.json()).data || []); } catch {} + }; + f(); const iv = setInterval(f, 10000); return () => clearInterval(iv); + }, [strategy]); + + useEffect(() => { + const streams = ["btcusdt", "ethusdt", "xrpusdt", "solusdt"].map(s => `${s}@aggTrade`).join("/"); + const ws = new WebSocket(`wss://fstream.binance.com/stream?streams=${streams}`); + ws.onmessage = (e) => { + try { + const msg = JSON.parse(e.data); + if (msg.data) { const sym = msg.data.s; const price = parseFloat(msg.data.p); if (sym && price > 0) setWsPrices(prev => ({ ...prev, [sym]: price })); } + } catch {} + }; + return () => ws.close(); + }, []); + + if (positions.length === 0) return ( +
+ {meta.label} ๆš‚ๆ— ๆดป่ทƒๆŒไป“ +
+ ); + + return ( +
+
+

ๅฝ“ๅ‰ๆŒไป“ โ— ๅฎžๆ—ถ

+
+
+ {positions.map((p: any) => { + const sym = p.symbol?.replace("USDT", "") || ""; + const holdMin = Math.round((Date.now() - p.entry_ts) / 60000); + const currentPrice = wsPrices[p.symbol] || p.current_price || 0; + const entry = p.entry_price || 0; + const riskDist = p.risk_distance || Math.abs(entry - (p.sl_price || entry)) || 1; + const tp1R = riskDist > 0 ? (p.direction === "LONG" ? ((p.tp1_price || 0) - entry) / riskDist : (entry - (p.tp1_price || 0)) / riskDist) : 0; + const fullR = riskDist > 0 ? (p.direction === "LONG" ? (currentPrice - entry) / riskDist : (entry - currentPrice) / riskDist) : 0; + const unrealR = p.tp1_hit ? 0.5 * tp1R + 0.5 * fullR : fullR; + const unrealUsdt = unrealR * paperRiskUsd; + return ( +
+
+
+ + {p.direction === "LONG" ? "๐ŸŸข" : "๐Ÿ”ด"} {sym} {p.direction} + + {strategy} + ่ฏ„ๅˆ†{p.score} ยท {p.tier === "heavy" ? "ๅŠ ไป“" : "ๆ ‡ๅ‡†"} +
+
+ = 0 ? "text-emerald-600" : "text-red-500"}`}> + {unrealR >= 0 ? "+" : ""}{unrealR.toFixed(2)}R + + = 0 ? "text-emerald-500" : "text-red-400"}`}> + ({unrealUsdt >= 0 ? "+" : ""}${unrealUsdt.toFixed(0)}) + + {holdMin}m +
+
+
+ ๅ…ฅๅœบ: ${fmtPrice(p.entry_price)} + ็Žฐไปท: ${currentPrice ? fmtPrice(currentPrice) : "-"} + TP1: ${fmtPrice(p.tp1_price)}{p.tp1_hit ? " โœ…" : ""} + TP2: ${fmtPrice(p.tp2_price)} + SL: ${fmtPrice(p.sl_price)} +
+
+ ); + })} +
+
+ ); +} + +// โ”€โ”€โ”€ ๆƒ็›Šๆ›ฒ็บฟ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +function EquityCurve({ strategy }: { strategy: StrategyTab }) { + const [data, setData] = useState([]); + useEffect(() => { + const f = async () => { + try { const r = await authFetch(`/api/paper/equity-curve?strategy=${strategy}`); if (r.ok) setData((await r.json()).data || []); } catch {} + }; + f(); const iv = setInterval(f, 30000); return () => clearInterval(iv); + }, [strategy]); + + return ( +
+
+

ๆƒ็›Šๆ›ฒ็บฟ (็ดฏ่ฎกPnL)

+
+ {data.length < 2 ? ( +
V5.3 ๆš‚ๆ— ่ถณๅคŸๅކๅฒๆ•ฐๆฎ๏ผŒ็งฏ็ดฏไธญ...
+ ) : ( +
+ + + bjt(v)} tick={{ fontSize: 10 }} /> + `${v}R`} /> + bjt(Number(v))} formatter={(v: any) => [`${v}R`, "็ดฏ่ฎกPnL"]} /> + + + + +
+ )} +
+ ); +} + +// โ”€โ”€โ”€ ๅކๅฒไบคๆ˜“ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +type FilterResult = "all" | "win" | "loss"; + +function TradeHistory({ strategy }: { strategy: StrategyTab }) { + const [trades, setTrades] = useState([]); + const [result, setResult] = useState("all"); + const meta = STRATEGY_LABELS[strategy]; + + useEffect(() => { + const f = async () => { + try { + const r = await authFetch(`/api/paper/trades?result=${result}&strategy=${strategy}&limit=50`); + if (r.ok) setTrades((await r.json()).data || []); + } catch {} + }; + f(); const iv = setInterval(f, 10000); return () => clearInterval(iv); + }, [result, strategy]); + + return ( +
+
+

ๅކๅฒไบคๆ˜“

+
+ {strategy} + | + {(["all", "win", "loss"] as FilterResult[]).map((r) => ( + + ))} +
+
+
+ {trades.length === 0 ? ( +
ๆš‚ๆ— ไบคๆ˜“่ฎฐๅฝ•
+ ) : ( + + + + + + + + + + + + + + + {trades.map((t: any) => { + const holdMin = t.exit_ts && t.entry_ts ? Math.round((t.exit_ts - t.entry_ts) / 60000) : 0; + const factors = parseFactors(t.score_factors); + const track = factors?.track || (t.symbol === "BTCUSDT" ? "BTC" : "ALT"); + return ( + + + + + + + + + + + ); + })} + +
ๅธ็งๆ–นๅ‘ๅ…ฅๅœบๅ‡บๅœบPnL(R)็Šถๆ€ๅˆ†ๆ•ฐๆŒไป“
+ {t.symbol?.replace("USDT", "")} + {track} + + {t.direction === "LONG" ? "๐ŸŸข" : "๐Ÿ”ด"} {t.direction} + {fmtPrice(t.entry_price)}{t.exit_price ? fmtPrice(t.exit_price) : "-"} 0 ? "text-emerald-600" : t.pnl_r < 0 ? "text-red-500" : "text-slate-500"}`}> + {t.pnl_r > 0 ? "+" : ""}{t.pnl_r?.toFixed(2)} + + + {t.status === "tp" ? "ๆญข็›ˆ" : t.status === "sl" ? "ๆญขๆŸ" : t.status === "sl_be" ? "ไฟๆœฌ" : t.status === "timeout" ? "่ถ…ๆ—ถ" : t.status === "signal_flip" ? "็ฟป่ฝฌ" : t.status} + + {t.score}{holdMin}m
+ )} +
+
+ ); +} + +// โ”€โ”€โ”€ ็ปŸ่ฎก้ขๆฟ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +function StatsPanel({ strategy }: { strategy: StrategyTab }) { + const [data, setData] = useState(null); + const [tab, setTab] = useState("ALL"); + useEffect(() => { setTab("ALL"); }, [strategy]); + + useEffect(() => { + const f = async () => { + try { const r = await authFetch(`/api/paper/stats?strategy=${strategy}`); if (r.ok) setData(await r.json()); } catch {} + }; + f(); const iv = setInterval(f, 30000); return () => clearInterval(iv); + }, [strategy]); + + if (!data || data.error) return ( +
+

่ฏฆ็ป†็ปŸ่ฎก

+
่ฏฅ่ง†ๅ›พๆš‚ๆ— ็ปŸ่ฎกๆ•ฐๆฎ๏ผŒ็ญ‰ๅพ…ไบคๆ˜“่ฎฐๅฝ•็งฏ็ดฏ
+
+ ); + + const coinTabs = strategy === "v53_btc" ? ["ALL", "BTC"] : ["ALL", "ETH", "XRP", "SOL"]; + const st = tab === "ALL" ? data : (data.by_symbol?.[tab] || null); + + return ( +
+
+

่ฏฆ็ป†็ปŸ่ฎก

+
+ {coinTabs.map(t => ( + + ))} +
+
+ {st ? ( +
+
+
่ƒœ็އ

{st.win_rate}%

+
็›ˆไบๆฏ”

{st.win_loss_ratio}

+
ๅนณๅ‡็›ˆๅˆฉ

+{st.avg_win}R

+
ๅนณๅ‡ไบๆŸ

-{st.avg_loss}R

+
ๆœ€ๅคงๅ›žๆ’ค

{st.mdd}R

+
ๅคๆ™ฎๆฏ”็އ

{st.sharpe}

+
ๆ€ป็›ˆไบ

= 0 ? "text-emerald-600" : "text-red-500"}`}>{(st.total_pnl ?? 0) >= 0 ? "+" : ""}{st.total_pnl ?? "-"}R

+
ๆ€ป็ฌ”ๆ•ฐ

{st.total ?? data.total}

+
ๅšๅคš่ƒœ็އ

{st.long_win_rate}% ({st.long_count}็ฌ”)

+
ๅš็ฉบ่ƒœ็އ

{st.short_win_rate}% ({st.short_count}็ฌ”)

+
+
+ ) : ( +
่ฏฅๅธ็งๆš‚ๆ— ๆ•ฐๆฎ
+ )} +
+ ); +} + +// โ”€โ”€โ”€ ไธป้กต้ข โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +export default function PaperTradingV53Page() { + const { isLoggedIn, loading } = useAuth(); + const [strategyTab, setStrategyTab] = useState("v53_alt"); + const meta = STRATEGY_LABELS[strategyTab]; + + if (loading) return
ๅŠ ่ฝฝไธญ...
; + if (!isLoggedIn) return ( +
+
๐Ÿ”’
+

่ฏทๅ…ˆ็™ปๅฝ•ๆŸฅ็œ‹ๆจกๆ‹Ÿ็›˜

+ ็™ปๅฝ• +
+ ); + + return ( +
+
+
+

๐Ÿ“ˆ ๆจกๆ‹Ÿ็›˜ V5.3

+

ALT่ฝจ(ETH/XRP/SOL) + BTC็‹ฌ็ซ‹Gate-Control

+
+ {/* ็ญ–็•ฅTabๅˆ‡ๆข */} +
+ {(Object.entries(STRATEGY_LABELS) as [StrategyTab, typeof STRATEGY_LABELS[StrategyTab]][]).map(([key, val]) => ( + + ))} +
+
+ +
+ {meta.label} โ€” {meta.desc} +
+ + + + + + + +
+ ); +} diff --git a/frontend/app/signals-v53/page.tsx b/frontend/app/signals-v53/page.tsx new file mode 100644 index 0000000..8ab75a1 --- /dev/null +++ b/frontend/app/signals-v53/page.tsx @@ -0,0 +1,494 @@ +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import { authFetch } from "@/lib/auth"; +import { useAuth } from "@/lib/auth"; +import Link from "next/link"; +import { + ComposedChart, Area, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, + ReferenceLine, CartesianGrid, Legend +} from "recharts"; + +type Symbol = "BTC" | "ETH" | "XRP" | "SOL"; + +interface IndicatorRow { + ts: number; + cvd_fast: number; + cvd_mid: number; + cvd_day: number; + atr_5m: number; + vwap_30m: number; + price: number; + score: number; + signal: string | null; +} + +interface LatestIndicator { + ts: number; + cvd_fast: number; + cvd_mid: number; + cvd_day: number; + cvd_fast_slope: number; + atr_5m: number; + atr_percentile: number; + vwap_30m: number; + price: number; + p95_qty: number; + p99_qty: number; + score: number; + signal: string | null; + tier?: "light" | "standard" | "heavy" | null; + factors?: { + track?: string; + direction?: { score?: number; max?: number; cvd_resonance?: number; p99_flow?: number; accel_bonus?: number }; + crowding?: { score?: number; max?: number; lsr_contrarian?: number; top_trader_position?: number }; + environment?: { score?: number; max?: number }; + auxiliary?: { score?: number; max?: number; coinbase_premium?: number }; + // BTC gate fields + gate_passed?: boolean; + block_reason?: string; + obi_raw?: number; + spot_perp_div?: number; + whale_cvd_ratio?: number; + atr_pct_price?: number; + alt_score_ref?: number; + } | null; +} + +const WINDOWS = [ + { label: "1h", value: 60 }, + { label: "4h", value: 240 }, + { label: "12h", value: 720 }, + { label: "24h", value: 1440 }, +]; + +function bjtStr(ms: number) { + const d = new Date(ms + 8 * 3600 * 1000); + return `${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}`; +} + +function bjtFull(ms: number) { + const d = new Date(ms + 8 * 3600 * 1000); + return `${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")} ${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}:${String(d.getUTCSeconds()).padStart(2, "0")}`; +} + +function fmt(v: number, decimals = 1): string { + if (Math.abs(v) >= 1000000) return `${(v / 1000000).toFixed(1)}M`; + if (Math.abs(v) >= 1000) return `${(v / 1000).toFixed(1)}K`; + return v.toFixed(decimals); +} + +function LayerScore({ label, score, max, colorClass }: { label: string; score: number; max: number; colorClass: string }) { + const ratio = Math.max(0, Math.min((score / max) * 100, 100)); + return ( +
+ {label} +
+
+
+ {score}/{max} +
+ ); +} + +// โ”€โ”€โ”€ BTC Gate ็Šถๆ€ๅก็‰‡ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +function BTCGateCard({ factors }: { factors: LatestIndicator["factors"] }) { + if (!factors) return null; + return ( +
+
+

โšก BTC Gate-Control

+ + {factors.gate_passed ? "โœ… Gate้€š่ฟ‡" : "โŒ ๅฆๅ†ณ"} + +
+
+
+

ๆณขๅŠจ็އ

+

{((factors.atr_pct_price ?? 0) * 100).toFixed(3)}%

+

้œ€ โ‰ฅ0.2%

+
+
+

OBI

+

= 0 ? "text-emerald-600" : "text-red-500"}`}> + {((factors.obi_raw ?? 0) * 100).toFixed(2)}% +

+

็›˜ๅฃๅคฑ่กก

+
+
+

ๆœŸ็Žฐ่ƒŒ็ฆป

+

= 0 ? "text-emerald-600" : "text-red-500"}`}> + {((factors.spot_perp_div ?? 0) * 10000).toFixed(2)}bps +

+

spot-perp

+
+
+

ๅทจ้ฒธCVD

+

= 0 ? "text-emerald-600" : "text-red-500"}`}> + {((factors.whale_cvd_ratio ?? 0) * 100).toFixed(2)}% +

+

>$100k

+
+
+ {factors.block_reason && ( +

+ ๅฆๅ†ณๅŽŸๅ› : {factors.block_reason} +

+ )} + {factors.alt_score_ref !== undefined && ( +

ๅ‚่€ƒ่ฏ„ๅˆ†๏ผˆALT้€ป่พ‘๏ผ‰: {factors.alt_score_ref} ๅˆ†

+ )} +
+ ); +} + +// โ”€โ”€โ”€ ๅฎžๆ—ถๆŒ‡ๆ ‡ๅก็‰‡ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +function IndicatorCards({ symbol }: { symbol: Symbol }) { + const [data, setData] = useState(null); + const strategy = symbol === "BTC" ? "v53_btc" : "v53_alt"; + + useEffect(() => { + const fetch = async () => { + try { + const res = await authFetch(`/api/signals/latest?strategy=${strategy}`); + if (!res.ok) return; + const json = await res.json(); + setData(json[symbol] || null); + } catch {} + }; + fetch(); + const iv = setInterval(fetch, 5000); + return () => clearInterval(iv); + }, [symbol, strategy]); + + if (!data) return
็ญ‰ๅพ…ๆŒ‡ๆ ‡ๆ•ฐๆฎ...
; + + const isBTC = symbol === "BTC"; + const priceVsVwap = data.price > data.vwap_30m ? "ไธŠๆ–น" : "ไธ‹ๆ–น"; + + return ( +
+ {/* CVDไธ‰่ฝจ */} +
+
+

CVD_fast (30m)

+

= 0 ? "text-emerald-600" : "text-red-500"}`}> + {fmt(data.cvd_fast)} +

+

+ ๆ–œ็އ: = 0 ? "text-emerald-600" : "text-red-500"}> + {data.cvd_fast_slope >= 0 ? "โ†‘" : "โ†“"}{fmt(Math.abs(data.cvd_fast_slope))} + +

+
+
+

CVD_mid (4h)

+

= 0 ? "text-emerald-600" : "text-red-500"}`}> + {fmt(data.cvd_mid)} +

+

{data.cvd_mid > 0 ? "ๅคš" : "็ฉบ"}ๅคดๅ ไผ˜

+
+
+

CVDๅ…ฑๆŒฏ

+

= 0 && data.cvd_mid >= 0 ? "text-emerald-600" : data.cvd_fast < 0 && data.cvd_mid < 0 ? "text-red-500" : "text-slate-400"}`}> + {data.cvd_fast >= 0 && data.cvd_mid >= 0 ? "โœ… ๅคšๅคดๅ…ฑๆŒฏ" : data.cvd_fast < 0 && data.cvd_mid < 0 ? "โœ… ็ฉบๅคดๅ…ฑๆŒฏ" : "โš ๏ธ ๅˆ†ๆญง"} +

+

V5.3ๆ ธๅฟƒไฟกๅท

+
+
+ + {/* ATR + VWAP */} +
+
+

ATR

+

${fmt(data.atr_5m, 2)}

+

+ 60 ? "text-amber-600 font-semibold" : "text-slate-400"}> + {data.atr_percentile.toFixed(0)}%{data.atr_percentile > 60 ? "๐Ÿ”ฅ" : ""} + +

+
+
+

VWAP

+

${data.vwap_30m.toLocaleString("en-US", { maximumFractionDigits: 1 })}

+

+ ไปทๆ ผๅœจ data.vwap_30m ? "text-emerald-600" : "text-red-500"}>{priceVsVwap} +

+
+
+

P95

+

{data.p95_qty.toFixed(4)}

+

ๅคงๅ•้˜ˆๅ€ผ

+
+
+

P99

+

{data.p99_qty.toFixed(4)}

+

่ถ…ๅคงๅ•

+
+
+ + {/* ไฟกๅท็Šถๆ€ */} +
+
+
+

+ {isBTC ? "BTC Gate-Control" : "ALT ๅ››ๅฑ‚่ฏ„ๅˆ†"} + {" ยท "}{isBTC ? "v53_btc" : "v53_alt"} +

+

+ {data.signal === "LONG" ? "๐ŸŸข ๅšๅคš" : data.signal === "SHORT" ? "๐Ÿ”ด ๅš็ฉบ" : "โšช ๆ— ไฟกๅท"} +

+
+
+

{data.score}/100

+

{data.tier === "heavy" ? "ๅŠ ไป“" : data.tier === "standard" ? "ๆ ‡ๅ‡†" : "ไธๅผ€ไป“"}

+
+
+ + {/* ALTๅ››ๅฑ‚ */} + {!isBTC && ( +
+ + + + +
+ )} +
+ + {/* BTC Gate ๅก็‰‡ */} + {isBTC && data.factors && } +
+ ); +} + +// โ”€โ”€โ”€ ไฟกๅทๅކๅฒ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +interface SignalRecord { + ts: number; + score: number; + signal: string; +} + +function SignalHistory({ symbol }: { symbol: Symbol }) { + const [data, setData] = useState([]); + const strategy = symbol === "BTC" ? "v53_btc" : "v53_alt"; + + useEffect(() => { + const fetchData = async () => { + try { + const res = await authFetch(`/api/signals/signal-history?symbol=${symbol}&limit=20&strategy=${strategy}`); + if (!res.ok) return; + const json = await res.json(); + setData(json.data || []); + } catch {} + }; + fetchData(); + const iv = setInterval(fetchData, 15000); + return () => clearInterval(iv); + }, [symbol, strategy]); + + if (data.length === 0) return null; + + return ( +
+
+

ๆœ€่ฟ‘ไฟกๅท ({strategy})

+
+
+ {data.map((s, i) => ( +
+
+ + {s.signal === "LONG" ? "๐ŸŸข LONG" : "๐Ÿ”ด SHORT"} + + {bjtFull(s.ts)} +
+
+ {s.score} + = 85 ? "bg-red-100 text-red-700" : + s.score >= 75 ? "bg-blue-100 text-blue-700" : + "bg-slate-100 text-slate-600" + }`}> + {s.score >= 85 ? "ๅŠ ไป“" : s.score >= 75 ? "ๆ ‡ๅ‡†" : "ไธๅผ€ไป“"} + +
+
+ ))} +
+
+ ); +} + +// โ”€โ”€โ”€ CVDๅ›พ่กจ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +function CVDChart({ symbol, minutes }: { symbol: Symbol; minutes: number }) { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const strategy = symbol === "BTC" ? "v53_btc" : "v53_alt"; + + const fetchData = useCallback(async (silent = false) => { + try { + const res = await authFetch(`/api/signals/indicators?symbol=${symbol}&minutes=${minutes}&strategy=${strategy}`); + if (!res.ok) return; + const json = await res.json(); + setData(json.data || []); + if (!silent) setLoading(false); + } catch {} + }, [symbol, minutes, strategy]); + + useEffect(() => { + setLoading(true); + fetchData(); + const iv = setInterval(() => fetchData(true), 30000); + return () => clearInterval(iv); + }, [fetchData]); + + const chartData = data.map(d => ({ + time: bjtStr(d.ts), + fast: parseFloat(d.cvd_fast?.toFixed(2) || "0"), + mid: parseFloat(d.cvd_mid?.toFixed(2) || "0"), + price: d.price, + })); + + const prices = chartData.map(d => d.price).filter(v => v > 0); + const pMin = prices.length ? Math.min(...prices) : 0; + const pMax = prices.length ? Math.max(...prices) : 0; + const pPad = (pMax - pMin) * 0.3 || pMax * 0.001; + + if (loading) return
ๅŠ ่ฝฝๆŒ‡ๆ ‡ๆ•ฐๆฎ...
; + if (data.length === 0) return
ๆš‚ๆ—  V5.3 ๆŒ‡ๆ ‡ๆ•ฐๆฎ๏ผŒsignal-engine ้œ€่ฟ่กŒ็งฏ็ดฏ
; + + return ( + + + + + + v >= 1000 ? `$${(v / 1000).toFixed(1)}k` : `$${v.toFixed(0)}`} + /> + { + if (name === "price") return [`$${Number(v).toLocaleString()}`, "ๅธไปท"]; + if (name === "fast") return [fmt(Number(v)), "CVD_fast(30m)"]; + return [fmt(Number(v)), "CVD_mid(4h)"]; + }} + contentStyle={{ background: "#fff", border: "1px solid #e2e8f0", borderRadius: 8, fontSize: 11 }} + /> + + + + + + + + ); +} + +// โ”€โ”€โ”€ ไธป้กต้ข โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +export default function SignalsV53Page() { + const { isLoggedIn, loading } = useAuth(); + const [symbol, setSymbol] = useState("ETH"); + const [minutes, setMinutes] = useState(240); + + if (loading) return
ๅŠ ่ฝฝไธญ...
; + if (!isLoggedIn) return ( +
+
๐Ÿ”’
+

่ฏทๅ…ˆ็™ปๅฝ•ๆŸฅ็œ‹ไฟกๅทๆ•ฐๆฎ

+
+ ็™ปๅฝ• + ๆณจๅ†Œ +
+
+ ); + + return ( +
+
+
+

โšก ไฟกๅทๅผ•ๆ“Ž V5.3

+

+ ๅ››ๅฑ‚่ฏ„ๅˆ† 55/25/15/5 ยท ALTๅŒ่ฝจ + BTC gate-control ยท + {symbol === "BTC" ? " ๐Ÿ”ต BTC่ฝจ๏ผˆgate-control๏ผ‰" : " ๐ŸŸฃ ALT่ฝจ๏ผˆETH/XRP/SOL๏ผ‰"} +

+
+
+ {(["BTC", "ETH", "XRP", "SOL"] as Symbol[]).map(s => ( + + ))} +
+
+ + + + +
+
+
+

CVDไธ‰่ฝจ + ๅธไปท

+

่“=fast(30m) ยท ็ดซ=mid(4h) ยท ๆฉ™=ไปทๆ ผ

+
+
+ {WINDOWS.map(w => ( + + ))} +
+
+
+ +
+
+ +
+
+

๐Ÿ“– V5.3 ๅŒ่ฝจไฟกๅท่ฏดๆ˜Ž

+
+
+
+ ๐ŸŸฃ ALT่ฝจ๏ผˆETH/XRP/SOL๏ผ‰โ€” ๅ››ๅฑ‚็บฟๆ€ง่ฏ„ๅˆ† +
+

1๏ธโƒฃ ๆ–นๅ‘ๅฑ‚๏ผˆ55ๅˆ†๏ผ‰ โ€” CVDๅ…ฑๆŒฏ30ๅˆ†๏ผˆfast+midๅŒๅ‘๏ผ‰+ P99ๅคงๅ•ๅฏน้ฝ20ๅˆ† + ๅŠ ้€Ÿๅฅ–ๅŠฑ5ๅˆ†ใ€‚ๅˆ ้™ค็‹ฌ็ซ‹็กฎ่ฎคๅฑ‚๏ผŒ่งฃๅ†ณCVDๅŒ้‡่ฎกๅˆ†้—ฎ้ข˜ใ€‚

+

2๏ธโƒฃ ๆ‹ฅๆŒคๅฑ‚๏ผˆ25ๅˆ†๏ผ‰ โ€” LSRๅๅ‘ๆ‹ฅๆŒค15ๅˆ†๏ผˆๆ•ฃๆˆท่ฟ‡ๅบฆๆ‹ฅๆŒค=ไฟกๅท๏ผ‰+ ๅคงๆˆทๆŒไป“ๆ–นๅ‘10ๅˆ†ใ€‚

+

3๏ธโƒฃ ็Žฏๅขƒๅฑ‚๏ผˆ15ๅˆ†๏ผ‰ โ€” OIๅ˜ๅŒ–็އ๏ผŒๆ–ฐ่ต„้‡‘่ฟ›ๅœบvsๆ’ค็ฆป๏ผŒๅˆคๆ–ญ่ถ‹ๅŠฟๆŒ็ปญๆ€งใ€‚

+

4๏ธโƒฃ ่พ…ๅŠฉๅฑ‚๏ผˆ5ๅˆ†๏ผ‰ โ€” Coinbase Premium๏ผŒ็พŽ็ณปๆœบๆž„ๅŠจๅ‘ใ€‚

+
+
+
+ ๐Ÿ”ต BTC่ฝจ โ€” Gate-Control้€ป่พ‘ +
+

ๆณขๅŠจ็އ้—จๆŽง๏ผšATR/Price โ‰ฅ 0.2%๏ผŒไฝŽๆณขๅŠจ่กŒๆƒ…ๆ‹’็ปๅผ€ไป“

+

OBIๅฆๅ†ณ๏ผš่ฎขๅ•็ฐฟๅคฑ่กก่ถ…้˜ˆๅ€ผไธ”ไธŽไฟกๅทๆ–นๅ‘ๅ†ฒ็ชๆ—ถๅฆๅ†ณ๏ผˆๅฎžๆ—ถ100ms๏ผ‰

+

ๆœŸ็Žฐ่ƒŒ็ฆปๅฆๅ†ณ๏ผšspotไธŽperpไปทๅทฎ่ถ…้˜ˆๅ€ผๆ—ถๅฆๅ†ณ๏ผˆๅฎžๆ—ถ1s๏ผ‰

+

ๅทจ้ฒธCVD๏ผš>$100kๆˆไบค้ขๅ‡€CVD๏ผŒ15ๅˆ†้’ŸๆปšๅŠจ็ช—ๅฃๅฎžๆ—ถ่ฎก็ฎ—

+
+
+
+ ๆกฃไฝ๏ผš<75ไธๅผ€ไป“ ยท 75-84ๆ ‡ๅ‡† ยท โ‰ฅ85ๅŠ ไป“ ยท ๅ†ทๅด10ๅˆ†้’Ÿ +
+
+
+
+ ); +} diff --git a/frontend/components/Sidebar.tsx b/frontend/components/Sidebar.tsx index 5e8bffc..6ca0d57 100644 --- a/frontend/components/Sidebar.tsx +++ b/frontend/components/Sidebar.tsx @@ -18,6 +18,8 @@ const navItems = [ { href: "/paper", label: "V5.1 ๆจกๆ‹Ÿ็›˜", icon: LineChart }, { href: "/signals-v52", label: "V5.2 ไฟกๅทๅผ•ๆ“Ž", icon: Sparkles, section: "โ”€โ”€ V5.2 โ”€โ”€" }, { href: "/paper-v52", label: "V5.2 ๆจกๆ‹Ÿ็›˜", icon: LineChart }, + { href: "/signals-v53", label: "V5.3 ไฟกๅทๅผ•ๆ“Ž", icon: Zap, section: "โ”€โ”€ V5.3 โ”€โ”€" }, + { href: "/paper-v53", label: "V5.3 ๆจกๆ‹Ÿ็›˜", icon: LineChart }, { href: "/server", label: "ๆœๅŠกๅ™จ", icon: Monitor }, { href: "/about", label: "่ฏดๆ˜Ž", icon: Info }, ];