feat: 量化模型迁移——K线获取(日/分钟)+多因子打分+IC自适应微调+推荐卡推送/REST/前端

This commit is contained in:
lookt
2026-09-15 21:18:11 +08:00
parent d178653c89
commit 419b0af1f8
6 changed files with 804 additions and 2 deletions
+89
View File
@@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
"""前端追加「量化推荐」区(筛选按钮 + 事件渲染 + 表格 + 权重展示)"""
import io
p = r'src\web\index.html'
s = io.open(p, encoding='utf-8').read()
# 1) 筛选按钮
old1 = ' <button data-f="事件影响分析">事件影响</button>'
new1 = (old1 + '\n <button data-f="量化推荐">量化推荐</button>\n'
' <button data-f="模型微调">微调</button>')
assert old1 in s, 'filters not found'
s = s.replace(old1, new1)
# 2) 主区追加量化推荐表格
old2 = ' <ul id="feed"><li class="empty">等待服务端推送…</li></ul>\n</main>'
new2 = (' <ul id="feed"><li class="empty">等待服务端推送…</li></ul>\n</main>\n'
'<section style="max-width:1100px;margin:0 auto 30px">\n'
' <h2 style="font-size:15px;color:var(--text)">量化模型 · 自适应推荐'
'(多因子 + IC 微调)</h2>\n'
' <div style="font-size:12px;color:var(--text2);margin-bottom:8px">'
'权重:<span id="qw">加载中…</span></div>\n'
' <div style="background:var(--surface);border:1px solid var(--border);'
'border-radius:8px;overflow:auto">\n'
' <table style="width:100%;border-collapse:collapse;font-size:13px" id="qtable">\n'
' <thead><tr style="color:var(--text2);text-align:left">\n'
' <th style="padding:8px 10px">代码</th><th style="padding:8px 10px">名称</th>\n'
' <th style="padding:8px 10px">现价</th><th style="padding:8px 10px">模型分</th>\n'
' <th style="padding:8px 10px">购入区间</th>'
'<th style="padding:8px 10px">预计收益(回测口径)</th>\n'
' <th style="padding:8px 10px">推荐指数</th>'
'<th style="padding:8px 10px">简易原因</th>\n'
' </tr></thead><tbody></tbody>\n'
' </table>\n'
' </div>\n'
' <div style="margin-top:6px;font-size:11px;color:#6b7280">'
'⚠ 购入区间与预计收益为模型回测/推测口径,不构成投资建议。</div>\n'
'</section>')
assert old2 in s, 'feed ul not found'
s = s.replace(old2, new2)
# 3) WS 事件渲染分支(量化推荐)
old3 = (" } else {\n"
" const txt = typeof d === 'object' ? JSON.stringify(d) : String(d)\n"
" li.innerHTML = `<span class=\"t\">${e.ts}</span><span class=\"k ${e.kind}\">${e.kind}"
"</span><span class=\"d\">${esc(txt)}</span>`\n }")
new3 = (" } else if (e.kind === '量化推荐') {\n"
" renderQuant(e.data)\n"
" const txt = '自适应模型推送 ' + ((d.list || []).length) + ' 只推荐'\n"
" li.innerHTML = `<span class=\"t\">${e.ts}</span><span class=\"k ${e.kind}\">${e.kind}"
"</span><span class=\"d\">${esc(txt)}</span>`\n"
" } else {\n"
" const txt = typeof d === 'object' ? JSON.stringify(d) : String(d)\n"
" li.innerHTML = `<span class=\"t\">${e.ts}</span><span class=\"k ${e.kind}\">${e.kind}"
"</span><span class=\"d\">${esc(txt)}</span>`\n }")
assert old3 in s, 'render branch not found'
s = s.replace(old3, new3)
# 4) 渲染函数与加载
old4 = 'connect()'
new4 = (
"function renderQuant(d) {\n"
" const tb = document.querySelector('#qtable tbody')\n"
" if (!tb) return\n"
" tb.innerHTML = (d.list || []).map(r =>\n"
" '<tr><td style=\"padding:6px 10px\">' + esc(r.code) + '</td>' +\n"
" '<td style=\"padding:6px 10px\">' + esc(r.name) + '</td>' +\n"
" '<td style=\"padding:6px 10px\">' + r.price + '</td>' +\n"
" '<td style=\"padding:6px 10px\">' + r.score + '</td>' +\n"
" '<td style=\"padding:6px 10px\">' + r.buy_low + ' ~ ' + r.buy_high + '</td>' +\n"
" '<td style=\"padding:6px 10px\">' + (r.expected_return_pct == null ? '' : r.expected_return_pct + '%') + '</td>' +\n"
" '<td style=\"padding:6px 10px\" class=\"stars\">' + ''.repeat(r.stars) + '</td>' +\n"
" '<td style=\"padding:6px 10px\">' + esc(r.reason) + '</td></tr>').join('')\n"
"}\n"
"function loadQuant() {\n"
" fetch('/api/quant/recommendations').then(r => r.json()).then(renderQuant).catch(() => {})\n"
" fetch('/api/quant/weights').then(r => r.json()).then(w => {\n"
" document.getElementById('qw').textContent =\n"
" Object.entries(w || {}).map(([k, v]) => k + '=' + Number(v).toFixed(2)).join(' ') || ''\n"
" }).catch(() => {})\n"
"}\n"
"loadQuant()\n"
"setInterval(loadQuant, 60000)\n"
"connect()")
assert old4 in s
s = s.replace(old4, new4, 1)
io.open(p, 'w', encoding='utf-8').write(s)
print('index.html 量化推荐区 OK')