新增行情查询、价差图表和 spread 分析表
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
生成 spread 数据图表(HTML)。
|
||||
用法: python3 chart.py
|
||||
"""
|
||||
import sqlite3
|
||||
import json
|
||||
|
||||
DB_PATH = "/Users/vipg/Documents/futures-data-warehouse/db/futures.db"
|
||||
|
||||
def main():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
rows = conn.execute(
|
||||
"SELECT trade_date, spread, main_chg FROM spread ORDER BY trade_date"
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
dates = [r[0] for r in rows]
|
||||
spreads = [r[1] for r in rows]
|
||||
chgs = [r[2] if r[2] is not None else None for r in rows]
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>玻璃期货 spread 走势</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
|
||||
<style>
|
||||
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||||
body {{ background: #0f172a; color: #e2e8f0; font-family: system-ui, sans-serif; padding: 24px; }}
|
||||
h1 {{ font-size: 20px; margin-bottom: 24px; color: #f1f5f9; }}
|
||||
.chart-box {{ background: #1e293b; border-radius: 12px; padding: 20px; }}
|
||||
canvas {{ width: 100% !important; height: auto !important; }}
|
||||
.stats {{ display: flex; gap: 24px; flex-wrap: wrap; margin-top: 24px; }}
|
||||
.stat {{ background: #1e293b; border-radius: 12px; padding: 16px 20px; min-width: 130px; }}
|
||||
.stat-label {{ font-size: 12px; color: #94a3b8; }}
|
||||
.stat-value {{ font-size: 22px; font-weight: 600; margin-top: 4px; }}
|
||||
.stat-value.pos {{ color: #22c55e; }}
|
||||
.stat-value.neg {{ color: #ef4444; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>玻璃期货 — 合约链差价 & 主力合约日内涨跌</h1>
|
||||
|
||||
<div class="chart-box">
|
||||
<canvas id="chart"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="stats" id="stats"></div>
|
||||
|
||||
<script>
|
||||
const dates = {json.dumps(dates)};
|
||||
const spreads = {json.dumps(spreads)};
|
||||
const chgs = {json.dumps(chgs)};
|
||||
|
||||
new Chart(document.getElementById('chart'), {{
|
||||
type: 'bar',
|
||||
data: {{
|
||||
labels: dates,
|
||||
datasets: [
|
||||
{{
|
||||
label: '主力日内涨跌',
|
||||
data: chgs,
|
||||
yAxisID: 'y1',
|
||||
backgroundColor: chgs.map(v => v === null ? 'transparent' : v >= 0 ? 'rgba(34,197,94,0.35)' : 'rgba(239,68,68,0.35)'),
|
||||
borderColor: chgs.map(v => v === null ? 'transparent' : v >= 0 ? '#22c55e' : '#ef4444'),
|
||||
borderWidth: 0.3,
|
||||
order: 2,
|
||||
}},
|
||||
{{
|
||||
label: '差价合计',
|
||||
data: spreads,
|
||||
yAxisID: 'y',
|
||||
type: 'line',
|
||||
borderColor: '#facc15',
|
||||
backgroundColor: 'rgba(250,204,21,0.06)',
|
||||
borderWidth: 1.5,
|
||||
pointRadius: 0,
|
||||
fill: true,
|
||||
tension: 0.1,
|
||||
order: 1,
|
||||
}},
|
||||
]
|
||||
}},
|
||||
options: {{
|
||||
responsive: true,
|
||||
interaction: {{ mode: 'index', intersect: false }},
|
||||
plugins: {{
|
||||
legend: {{ labels: {{ color: '#94a3b8', boxWidth: 14, padding: 16 }} }},
|
||||
tooltip: {{
|
||||
backgroundColor: '#0f172a',
|
||||
titleColor: '#f1f5f9',
|
||||
bodyColor: '#e2e8f0',
|
||||
borderColor: '#334155',
|
||||
borderWidth: 1,
|
||||
callbacks: {{
|
||||
label: ctx => ctx.parsed.y !== null && ctx.parsed.y !== undefined
|
||||
? `${{ctx.dataset.label}}: ${{ctx.parsed.y.toFixed(1)}}` : ''
|
||||
}}
|
||||
}}
|
||||
}},
|
||||
scales: {{
|
||||
x: {{ ticks: {{ color: '#64748b', maxTicksLimit: 20, font: {{ size: 10 }} }}, grid: {{ color: '#1e293b' }} }},
|
||||
y: {{
|
||||
position: 'left',
|
||||
ticks: {{ color: '#facc15' }},
|
||||
grid: {{ color: '#334155' }},
|
||||
title: {{ display: true, text: '差价合计(元/吨)', color: '#facc15' }}
|
||||
}},
|
||||
y1: {{
|
||||
position: 'right',
|
||||
ticks: {{ color: '#94a3b8' }},
|
||||
grid: {{ drawOnChartArea: false }},
|
||||
title: {{ display: true, text: '主力日内涨跌(元/吨)', color: '#94a3b8' }}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}});
|
||||
|
||||
// 统计
|
||||
const pos = spreads.filter(v => v > 0).length;
|
||||
const neg = spreads.filter(v => v < 0).length;
|
||||
const avg = spreads.reduce((a,b) => a+b, 0) / spreads.length;
|
||||
const maxV = Math.max(...spreads);
|
||||
const minV = Math.min(...spreads);
|
||||
const totalChg = chgs.filter(v => v !== null).reduce((a,b) => a+b, 0);
|
||||
const avgChg = totalChg / chgs.filter(v => v !== null).length;
|
||||
|
||||
const statsHtml = `
|
||||
<div class="stat"><div class="stat-label">数据天数</div><div class="stat-value">${{spreads.length}}</div></div>
|
||||
<div class="stat"><div class="stat-label">平均差价</div><div class="stat-value ${{avg>=0?'pos':'neg'}}">${{avg.toFixed(1)}}</div></div>
|
||||
<div class="stat"><div class="stat-label">升水天数</div><div class="stat-value pos">${{pos}}</div></div>
|
||||
<div class="stat"><div class="stat-label">贴水天数</div><div class="stat-value neg">${{neg}}</div></div>
|
||||
<div class="stat"><div class="stat-label">最大升水</div><div class="stat-value pos">+${{maxV.toFixed(1)}}</div></div>
|
||||
<div class="stat"><div class="stat-label">最大贴水</div><div class="stat-value neg">${{minV.toFixed(1)}}</div></div>
|
||||
<div class="stat"><div class="stat-label">主力日均涨跌</div><div class="stat-value ${{avgChg>=0?'pos':'neg'}}">${{avgChg.toFixed(2)}}</div></div>
|
||||
`;
|
||||
document.getElementById('stats').innerHTML = statsHtml;
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
out_path = "/Users/vipg/Documents/futures-data-warehouse/chart.html"
|
||||
with open(out_path, "w") as f:
|
||||
f.write(html)
|
||||
print(f"已生成: {out_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user