#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成「第一票 · 亮黄历史」看板页：读 CSV → 输出 /root/work/kpl/第一票.html
每日由 daily_update.sh 调用。
"""
import csv, os, statistics, time, html

CSV_PATH = "/root/work/kpl/竞价主表/第一票_亮黄历史.csv"
OUT = "/root/work/kpl/第一票.html"

rows = []
with open(CSV_PATH, encoding="utf-8-sig") as f:
    for r in csv.DictReader(f):
        rows.append(r)

def num(v):
    try:
        return float(v)
    except (TypeError, ValueError):
        return None

done = [r for r in rows if num(r.get("T1收盘盈亏%")) is not None]
rets = [num(r["T1收盘盈亏%"]) for r in done]
wins = [x for x in rets if x > 0]
stat = dict(n=len(rets),
            win=len(wins) / len(rets) * 100 if rets else 0,
            avg=sum(rets) / len(rets) if rets else 0,
            med=statistics.median(rets) if rets else 0)

years = {}
for r in done:
    y = r["日期"][:4]
    years.setdefault(y, []).append(num(r["T1收盘盈亏%"]))

def yrow(y, v):
    w = sum(1 for x in v if x > 0) / len(v) * 100
    return "<tr><td>%s</td><td>%d</td><td>%.1f%%</td><td>%+.2f%%</td><td>%+.2f%%</td></tr>" % (
        y, len(v), w, sum(v) / len(v), statistics.median(v))

yrows = "".join(yrow(y, v) for y, v in sorted(years.items()))
latest = rows[0] if rows else {}
last_day = latest.get("日期", "—")

def cls(v):
    x = num(v)
    if x is None:
        return ""
    return "up" if x > 0 else ("down" if x < 0 else "")

trs = []
for i, r in enumerate(rows):
    t1c = r.get("T1收盘盈亏%") or ""
    t1h = r.get("T1最高盈亏%") or ""
    trs.append(
        '<tr data-s="%s %s %s">'
        '<td class="d">%s</td><td class="c">%s</td><td class="n">%s</td>'
        '<td class="r">%s</td><td class="r">%s</td><td class="r">%s</td>'
        '<td class="r">%s</td><td class="r %s">%s</td><td class="r %s">%s</td>'
        '<td class="t">%s</td></tr>' % (
            html.escape(r.get("代码", "")), html.escape(r.get("名称", "")), html.escape(r.get("日期", "")),
            r.get("日期", "")[4:6] + "-" + r.get("日期", "")[6:], r.get("代码", ""), html.escape(r.get("名称", "")),
            r.get("涨停委买额万", ""), r.get("竞价涨幅%", ""), r.get("自由流通市值亿", ""),
            r.get("买入价", ""), cls(t1c), t1c or "待 T+1", cls(t1h), t1h,
            html.escape((r.get("题材", "") or "")[:16])))

page = """<!doctype html><html lang="zh-CN"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>第一票 · 亮黄历史</title>
<style>
:root{--bg:#14120F;--surface:#1B1814;--surface2:#221E18;--line:#332C23;--text:#F1EADF;
--dim:#9C9082;--dim2:#8A7F72;--accent:#E8A33D;--accent-soft:rgba(232,163,61,.12);
--up:#E5544B;--down:#46B87A;}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--text);font:15px/1.55 -apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;
background-image:radial-gradient(120% 65% at 50% -10%,#221D16 0%,#14120F 60%);min-height:100vh;padding:20px 14px 48px}
.wrap{max-width:1100px;margin:0 auto}
h1{font-size:20px;margin:0 0 4px;letter-spacing:.5px}
.sub{color:var(--dim);font-size:12.5px;margin-bottom:18px}
.sub a{color:var(--accent);text-decoration:none}
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(118px,1fr));gap:10px;margin-bottom:18px}
.card{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:12px 14px}
.card .k{font-size:11.5px;color:var(--dim2);letter-spacing:.4px}
.card .v{font-size:20px;font-weight:600;margin-top:3px;font-variant-numeric:tabular-nums}
.card .v.up{color:var(--up)}.card .v.down{color:var(--down)}
h2{font-size:13.5px;color:var(--dim);font-weight:600;margin:22px 0 8px;letter-spacing:.4px}
table{width:100%;border-collapse:collapse;background:var(--surface);border:1px solid var(--line);border-radius:12px;overflow:hidden}
th,td{padding:8px 9px;text-align:right;font-size:13px;border-bottom:1px solid rgba(51,44,35,.65);white-space:nowrap}
th{position:sticky;top:0;background:var(--surface2);color:var(--dim);font-weight:600;font-size:11.5px;cursor:pointer;user-select:none;letter-spacing:.3px}
th:hover{color:var(--accent)}
td.d,th.d{text-align:left;color:var(--dim)}td.c{text-align:left;color:var(--dim);font-variant-numeric:tabular-nums}
td.n{text-align:left;font-weight:500}td.r{font-variant-numeric:tabular-nums}
td.t{text-align:left;color:var(--dim2);font-size:12px;max-width:190px;overflow:hidden;text-overflow:ellipsis}
tr:hover td{background:rgba(232,163,61,.05)}
.up{color:var(--up)}.down{color:var(--down)}
.yt td{font-size:13px}.yt th{position:static;cursor:default}
.bar{display:flex;gap:8px;align-items:center;margin:6px 0 10px;flex-wrap:wrap}
input[type=search]{background:var(--surface);border:1px solid var(--line);color:var(--text);
border-radius:9px;padding:8px 12px;font-size:13.5px;width:220px;outline:none}
input[type=search]:focus{border-color:var(--accent)}
.count{color:var(--dim2);font-size:12px}
.note{color:var(--dim2);font-size:12px;margin-top:14px;line-height:1.7}
@media(max-width:720px){td.t,th.t{display:none}body{padding:14px 8px 40px}th,td{padding:7px 6px;font-size:12.5px}}
</style></head><body><div class="wrap">
<h1>第一票 · 亮黄历史</h1>
<div class="sub">亮黄条件：涨停委买额 &gt; 2000 万　+　昨日首板　+　非 ST；第一票 = 当日清单里委买额最大。<br>
口径：T 日竞价买入、T+1 收盘卖出（A 股 T+1），扣双边 0.12%。最新 <b><<last>></b>　<a href="/竞价主表/">原始文件 ›</a></div>

<div class="cards">
<div class="card"><div class="k">交易日</div><div class="v">%(ndays)d</div></div>
<div class="card"><div class="k">已结算笔数</div><div class="v">%(n)d</div></div>
<div class="card"><div class="k">胜率</div><div class="v">%(win).1f%%</div></div>
<div class="card"><div class="k">平均</div><div class="v <<avgcls>>">%(avg)+.2f%%</div></div>
<div class="card"><div class="k">中位</div><div class="v <<medcls>>">%(med)+.2f%%</div></div>
</div>

<h2>分年（T+1 收盘）</h2>
<table class="yt"><thead><tr><th>年份</th><th>笔数</th><th>胜率</th><th>均值</th><th>中位</th></tr></thead>
<tbody><<yrows>></tbody></table>

<h2>明细（新 → 旧）</h2>
<div class="bar"><input id="q" type="search" placeholder="搜代码 / 名称 / 日期"><span class="count" id="cnt"></span></div>
<table id="tb"><thead><tr>
<th class="d" data-k="0">日期</th><th class="c" data-k="1">代码</th><th class="n" data-k="2">名称</th>
<th data-k="3">委买额万</th><th data-k="4">竞价%</th><th data-k="5">自由流通亿</th>
<th data-k="6">买入价</th><th data-k="7">T1收盘%</th><th data-k="8">T1最高%</th><th class="t">题材</th>
</tr></thead><tbody><<trs>></tbody></table>

<div class="note">T1收盘% 是唯一可执行口径；T1最高% 是事后上限，只能参考。未结算的行显示「待 T+1」。<br>
数据源：开盘啦竞价历史接口（涨停委买额/竞价涨幅/金额）+ TUSHARE Pro（流通市值、自由流通股本、日线）。每日自动更新。</div>
</div>
<script>
(function(){
 var tb=document.getElementById('tb'),rows=[].slice.call(tb.tBodies[0].rows);
 var q=document.getElementById('q'),cnt=document.getElementById('cnt');
 function upd(){var v=q.value.trim().toLowerCase(),n=0;
  rows.forEach(function(r){var ok=!v||r.dataset.s.toLowerCase().indexOf(v)>-1;r.style.display=ok?'':'none';if(ok)n++;});
  cnt.textContent=n+' 行';}
 q.addEventListener('input',upd);upd();
 var dir={};
 [].forEach.call(tb.tHead.rows[0].cells,function(th){
  if(th.classList.contains('t'))return;
  th.addEventListener('click',function(){
   var k=+th.dataset.k,asc=dir[k]=!dir[k];
   rows.sort(function(a,b){
    var x=a.cells[k].innerText.replace(/[%%,]/g,''),y=b.cells[k].innerText.replace(/[%%,]/g,'');
    var nx=parseFloat(x),ny=parseFloat(y);
    if(!isNaN(nx)&&!isNaN(ny))return asc?nx-ny:ny-nx;
    return asc?x.localeCompare(y):y.localeCompare(x);});
   rows.forEach(function(r){tb.tBodies[0].appendChild(r);});});
 });
})();
</script></body></html>"""
vals = dict(last=last_day, ndays=len(rows), n=stat["n"], win=stat["win"],
            avg=stat["avg"], med=stat["med"],
            avgcls="up" if stat["avg"] > 0 else ("down" if stat["avg"] < 0 else ""),
            medcls="up" if stat["med"] > 0 else ("down" if stat["med"] < 0 else ""),
            yrows=yrows, trs="".join(trs))
for _k, _v in vals.items():
    page = page.replace("<<" + _k + ">>", ("%.1f" % _v) if _k == "win" else str(_v))

open(OUT, "w", encoding="utf-8").write(page)
print("生成 %s  %d 行  更新于 %s" % (OUT, len(rows), time.strftime("%Y-%m-%d %H:%M")))
