← Back
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Futures Screener — Статистика сигналов</title>
<style>
  :root{
    --bg:#0a0e17; --card:rgba(255,255,255,.04); --border:rgba(255,255,255,.09);
    --txt:#e6edf3; --muted:#8b98a9; --green:#21d07a; --red:#f6465d; --blue:#3b82f6;
    --purple:#8b5cf6; --amber:#f59e0b; --orange:#f97316; --cyan:#06b6d4;
  }
  *{box-sizing:border-box;margin:0;padding:0}
  body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
    background:radial-gradient(1200px 600px at 80% -10%,rgba(59,130,246,.10),transparent),
               radial-gradient(900px 500px at 0% 0%,rgba(139,92,246,.08),transparent),var(--bg);
    color:var(--txt);min-height:100vh;padding:24px 16px;line-height:1.5}
  .wrap{max-width:1080px;margin:0 auto}
  header{margin-bottom:24px}
  h1{font-size:26px;font-weight:700;letter-spacing:-.5px}
  h1 .em{background:linear-gradient(90deg,var(--blue),var(--purple));-webkit-background-clip:text;background-clip:text;color:transparent}
  .sub{color:var(--muted);font-size:13px;margin-top:6px}
  .glass{background:var(--card);border:1px solid var(--border);border-radius:16px;
    backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}
  .kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:22px}
  .kpi{padding:18px}
  .kpi .lbl{color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:.5px}
  .kpi .val{font-size:28px;font-weight:700;margin-top:6px}
  .section{padding:20px;margin-bottom:22px}
  .section h2{font-size:15px;font-weight:600;margin-bottom:14px;display:flex;align-items:center;gap:8px}
  table{width:100%;border-collapse:collapse;font-size:13.5px}
  th,td{text-align:left;padding:10px 10px}
  th{color:var(--muted);font-weight:500;font-size:11.5px;text-transform:uppercase;letter-spacing:.4px;
    border-bottom:1px solid var(--border)}
  tr+tr td{border-top:1px solid rgba(255,255,255,.045)}
  td.num,th.num{text-align:right;font-variant-numeric:tabular-nums}
  .pill{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border-radius:999px;font-size:12px;font-weight:600}
  .bar{height:7px;border-radius:4px;background:rgba(255,255,255,.07);overflow:hidden;min-width:90px}
  .bar>span{display:block;height:100%;border-radius:4px}
  .pos{color:var(--green)} .neg{color:var(--red)} .mut{color:var(--muted)}
  .two{display:grid;grid-template-columns:1fr 1fr;gap:22px}
  .dirgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;text-align:center}
  .dirgrid .d{padding:16px}
  .dirgrid .dv{font-size:24px;font-weight:700}
  .spark{display:flex;align-items:flex-end;gap:4px;height:120px;margin-top:6px}
  .spark .col{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;min-width:0}
  .spark .b{width:100%;max-width:34px;border-radius:5px 5px 0 0;background:linear-gradient(180deg,var(--blue),rgba(59,130,246,.35))}
  .spark .b.gap{background:repeating-linear-gradient(45deg,rgba(246,70,93,.25),rgba(246,70,93,.25) 4px,transparent 4px,transparent 8px)}
  .spark .dt{font-size:9.5px;color:var(--muted);white-space:nowrap;transform:rotate(-50deg);transform-origin:center;margin-top:8px}
  .chips{display:flex;flex-wrap:wrap;gap:8px}
  .chip{padding:7px 11px;font-size:12.5px;font-weight:600}
  .chip .n{color:var(--muted);font-weight:500;margin-left:5px}
  .foot{color:var(--muted);font-size:12px;text-align:center;margin-top:8px}
  .note{font-size:12px;color:var(--amber);margin-top:10px}
  @media(max-width:720px){.kpis{grid-template-columns:repeat(2,1fr)}.two{grid-template-columns:1fr}h1{font-size:21px}}
</style>
</head>
<body>
<div class="wrap">
  <header>
    <h1>📡 Futures Screener — <span class="em">Статистика сигналов</span></h1>
    <div class="sub" id="period"></div>
  </header>

  <div class="kpis" id="kpis"></div>

  <div class="section glass">
    <h2>🎯 По типам сигналов <span class="mut" style="font-weight:400">— Win Rate &amp; средний PnL</span></h2>
    <table id="typeTbl"><thead><tr>
      <th>Тип</th><th class="num">Всего</th><th class="num">Резолв</th>
      <th class="num">Win Rate</th><th style="width:130px">&nbsp;</th><th class="num">Avg PnL</th>
    </tr></thead><tbody></tbody></table>
    <div class="note" id="typeNote"></div>
  </div>

  <div class="two">
    <div class="section glass">
      <h2>📈 По направлению</h2>
      <div class="dirgrid" id="dir"></div>
    </div>
    <div class="section glass">
      <h2>🔬 Win Rate по уверенности</h2>
      <table id="confTbl"><thead><tr>
        <th>Confidence</th><th class="num">Сигналов</th><th class="num">WR</th><th style="width:90px">&nbsp;</th>
      </tr></thead><tbody></tbody></table>
    </div>
  </div>

  <div class="section glass">
    <h2>📅 Объём сигналов по дням <span class="mut" style="font-weight:400">— последние 30 дней</span></h2>
    <div class="spark" id="spark"></div>
    <div class="note">Пунктирные/пропущенные столбцы = дни без данных (включая залип 27 мая – 1 июня).</div>
  </div>

  <div class="section glass">
    <h2>🔥 Топ-15 символов по числу сигналов</h2>
    <div class="chips" id="symbols"></div>
  </div>

  <div class="foot" id="foot"></div>
</div>

<script>
const DATA = {"overall": {"total": 6449, "first": "2026-05-05 07:00:00", "last": "2026-06-02 13:55:00", "resolved": 6014, "wins": 3242}, "by_type": [{"type": "volume_spike", "total": 3827, "resolved": 3575, "wins": 1953, "avgpnl": 0.61}, {"type": "channel", "total": 2173, "resolved": 2018, "wins": 1063, "avgpnl": 0.37}, {"type": "oi_cvd", "total": 213, "resolved": 198, "wins": 102, "avgpnl": 0.42}, {"type": "liq_sweep", "total": 86, "resolved": 78, "wins": 38, "avgpnl": 0.43}, {"type": "oi_divergence", "total": 74, "resolved": 71, "wins": 43, "avgpnl": 3.03}, {"type": "vpin_toxicity", "total": 49, "resolved": 49, "wins": 32, "avgpnl": 0.24}, {"type": "oi_funding_squeeze", "total": 27, "resolved": 25, "wins": 11, "avgpnl": -1.07}], "by_dir": {"LONG": 3103, "NEUTRAL": 24, "SHORT": 3322}, "by_conf": [{"bucket": "50-59", "total": 170, "resolved": 165, "wins": 100}, {"bucket": "60-69", "total": 4045, "resolved": 3782, "wins": 2056}, {"bucket": "70-79", "total": 1452, "resolved": 1346, "wins": 713}, {"bucket": "80-89", "total": 684, "resolved": 631, "wins": 319}, {"bucket": "90+", "total": 98, "resolved": 90, "wins": 54}], "top_symbols": [{"symbol": "FILUSDT", "n": 105}, {"symbol": "TAOUSDT", "n": 105}, {"symbol": "PUMPUSDT", "n": 100}, {"symbol": "CLUSDT", "n": 97}, {"symbol": "WLDUSDT", "n": 94}, {"symbol": "ENAUSDT", "n": 93}, {"symbol": "PENGUUSDT", "n": 92}, {"symbol": "WLFIUSDT", "n": 90}, {"symbol": "ONDOUSDT", "n": 88}, {"symbol": "TRUMPUSDT", "n": 88}, {"symbol": "UNIUSDT", "n": 88}, {"symbol": "VIRTUALUSDT", "n": 87}, {"symbol": "SUIUSDT", "n": 85}, {"symbol": "1000PEPEUSDT", "n": 84}, {"symbol": "BZUSDT", "n": 83}], "per_day": [{"d": "2026-05-05", "n": 858}, {"d": "2026-05-06", "n": 1324}, {"d": "2026-05-07", "n": 985}, {"d": "2026-05-08", "n": 731}, {"d": "2026-05-09", "n": 922}, {"d": "2026-05-10", "n": 437}, {"d": "2026-05-11", "n": 289}, {"d": "2026-05-12", "n": 152}, {"d": "2026-05-15", "n": 27}, {"d": "2026-05-16", "n": 359}, {"d": "2026-05-26", "n": 318}, {"d": "2026-06-02", "n": 47}]};

const TYPE_META = {
  volume_spike:{label:'Volume Spike',icon:'📊',c:'#3b82f6'},
  oi_cvd:{label:'OI + CVD',icon:'🔮',c:'#8b5cf6'},
  oi_divergence:{label:'OI Divergence',icon:'🔀',c:'#f59e0b'},
  oi_funding_squeeze:{label:'Funding Squeeze',icon:'⚡',c:'#f97316'},
  liq_sweep:{label:'Liq Sweep',icon:'🎯',c:'#ef4444'},
  channel:{label:'Channel',icon:'📐',c:'#06b6d4'},
  vpin_toxicity:{label:'VPIN Toxicity',icon:'☣️',c:'#22d3ee'},
};
const wr = (w,r)=> r? (100*w/r):null;
const wrColor = v => v==null?'var(--muted)': v>=58?'var(--green)': v>=50?'#cbd5e1':'var(--red)';
const fmt = n => n.toLocaleString('ru-RU');

// period
const o=DATA.overall;
document.getElementById('period').textContent =
  `Период: ${o.first.slice(0,10)} → ${o.last.slice(0,10)} · ${fmt(o.total)} сигналов · ${fmt(o.resolved)} с известным исходом`;

// KPIs
const overallWR = wr(o.wins,o.resolved);
const kpis=[
  {lbl:'Всего сигналов',val:fmt(o.total)},
  {lbl:'С исходом',val:fmt(o.resolved)},
  {lbl:'Общий Win Rate',val:overallWR.toFixed(1)+'%',c:wrColor(overallWR)},
  {lbl:'Типов сигналов',val:DATA.by_type.length},
];
document.getElementById('kpis').innerHTML = kpis.map(k=>
  `<div class="kpi glass"><div class="lbl">${k.lbl}</div><div class="val" style="color:${k.c||'var(--txt)'}">${k.val}</div></div>`).join('');

// type table (sorted by WR desc)
const rows=[...DATA.by_type].map(t=>({...t,wrv:wr(t.wins,t.resolved)})).sort((a,b)=>(b.wrv||0)-(a.wrv||0));
document.querySelector('#typeTbl tbody').innerHTML = rows.map(t=>{
  const m=TYPE_META[t.type]||{label:t.type,icon:'•',c:'#888'};
  const v=t.wrv;
  const pnlCls = t.avgpnl>0?'pos':t.avgpnl<0?'neg':'mut';
  const pnlTxt = t.avgpnl==null?'—':(t.avgpnl>0?'+':'')+t.avgpnl.toFixed(2)+'%';
  return `<tr>
    <td><span class="pill" style="background:${m.c}22;color:${m.c}">${m.icon} ${m.label}</span></td>
    <td class="num">${fmt(t.total)}</td>
    <td class="num mut">${fmt(t.resolved)}</td>
    <td class="num" style="color:${wrColor(v)};font-weight:700">${v==null?'—':v.toFixed(1)+'%'}</td>
    <td><div class="bar"><span style="width:${v||0}%;background:${wrColor(v)}"></span></div></td>
    <td class="num ${pnlCls}" style="font-weight:600">${pnlTxt}</td>
  </tr>`;
}).join('');
document.getElementById('typeNote').textContent =
  '⭐ Лучшие по качеству: oi_divergence (PnL +3%) и vpin_toxicity (WR 65%), но выборка мелкая. ⚠️ oi_funding_squeeze — единственный убыточный.';

// direction
const dirMeta={LONG:{c:'var(--green)',i:'🟢'},SHORT:{c:'var(--red)',i:'🔴'},NEUTRAL:{c:'var(--muted)',i:'⚪'}};
const dt=Object.values(DATA.by_dir).reduce((s,v)=>s+v,0);
document.getElementById('dir').innerHTML = ['LONG','SHORT','NEUTRAL'].map(k=>{
  const v=DATA.by_dir[k]||0,m=dirMeta[k];
  return `<div class="d glass"><div class="lbl mut" style="font-size:12px">${m.i} ${k}</div>
    <div class="dv" style="color:${m.c}">${fmt(v)}</div>
    <div class="mut" style="font-size:12px">${(100*v/dt).toFixed(1)}%</div></div>`;
}).join('');

// confidence
document.querySelector('#confTbl tbody').innerHTML = DATA.by_conf.map(c=>{
  const v=wr(c.wins,c.resolved);
  return `<tr><td>${c.bucket}</td><td class="num mut">${fmt(c.total)}</td>
    <td class="num" style="color:${wrColor(v)};font-weight:700">${v==null?'—':v.toFixed(1)+'%'}</td>
    <td><div class="bar"><span style="width:${v||0}%;background:${wrColor(v)}"></span></div></td></tr>`;
}).join('');

// sparkline (per day) — fill missing days as gaps
const pd=DATA.per_day;
const max=Math.max(...pd.map(d=>d.n));
const days=[]; const start=new Date(pd[0].d), end=new Date(pd[pd.length-1].d);
const map=Object.fromEntries(pd.map(d=>[d.d,d.n]));
for(let dt=new Date(start); dt<=end; dt.setDate(dt.getDate()+1)){
  const key=dt.toISOString().slice(0,10); days.push({d:key,n:map[key]??0});
}
document.getElementById('spark').innerHTML = days.map(d=>{
  const h=d.n?Math.max(6,Math.round(110*d.n/max)):4;
  const gap=d.n===0?' gap':'';
  return `<div class="col" title="${d.d}: ${d.n}">
    <div class="b${gap}" style="height:${h}px"></div>
    <div class="dt">${d.d.slice(5)}</div></div>`;
}).join('');

// symbols
document.getElementById('symbols').innerHTML = DATA.top_symbols.map(s=>
  `<span class="chip glass">${s.symbol.replace('USDT','')}<span class="n">${s.n}</span></span>`).join('');

document.getElementById('foot').textContent =
  'Источник: signal_log (futures-screener prod) · Сгенерировано Бендером';
</script>
</body>
</html>