新增建材价格指数趋势监控仪表盘
This commit is contained in:
@@ -0,0 +1,300 @@
|
|||||||
|
{
|
||||||
|
"summary": {
|
||||||
|
"date": "2026-07-23",
|
||||||
|
"value": 915,
|
||||||
|
"change": 0.2191,
|
||||||
|
"chg_3m": 0.6601,
|
||||||
|
"chg_6m": 5.5363,
|
||||||
|
"chg_1y": -1.507
|
||||||
|
},
|
||||||
|
"mas": {
|
||||||
|
"MA5": {
|
||||||
|
"value": 913.4,
|
||||||
|
"direction": "up",
|
||||||
|
"price_vs_ma": "above"
|
||||||
|
},
|
||||||
|
"MA10": {
|
||||||
|
"value": 911.6,
|
||||||
|
"direction": "up",
|
||||||
|
"price_vs_ma": "above"
|
||||||
|
},
|
||||||
|
"MA20": {
|
||||||
|
"value": 906.2,
|
||||||
|
"direction": "up",
|
||||||
|
"price_vs_ma": "above"
|
||||||
|
},
|
||||||
|
"MA60": {
|
||||||
|
"value": 911.1,
|
||||||
|
"direction": "down",
|
||||||
|
"price_vs_ma": "above"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"signals": [
|
||||||
|
{
|
||||||
|
"period": "短期",
|
||||||
|
"signal": "偏多",
|
||||||
|
"detail": "价格 > MA5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"period": "中期",
|
||||||
|
"signal": "偏多",
|
||||||
|
"detail": "MA5 > MA20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"period": "长期",
|
||||||
|
"signal": "偏空",
|
||||||
|
"detail": "MA20 < MA60"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"period": "动量(5日)",
|
||||||
|
"signal": "偏多",
|
||||||
|
"detail": "+3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"overall": {
|
||||||
|
"signal": "偏多",
|
||||||
|
"color": "#16a34a"
|
||||||
|
},
|
||||||
|
"recent": [
|
||||||
|
{
|
||||||
|
"date": "06-24",
|
||||||
|
"value": 901,
|
||||||
|
"chg": -0.2215,
|
||||||
|
"MA5": 905.8,
|
||||||
|
"MA20": 914.2,
|
||||||
|
"MA60": 920.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "06-25",
|
||||||
|
"value": 900,
|
||||||
|
"chg": -0.111,
|
||||||
|
"MA5": 904.0,
|
||||||
|
"MA20": 913.1,
|
||||||
|
"MA60": 920.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "06-26",
|
||||||
|
"value": 897,
|
||||||
|
"chg": -0.3333,
|
||||||
|
"MA5": 901.6,
|
||||||
|
"MA20": 911.9,
|
||||||
|
"MA60": 920.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "06-27",
|
||||||
|
"value": 896,
|
||||||
|
"chg": -0.1115,
|
||||||
|
"MA5": 899.4,
|
||||||
|
"MA20": 910.6,
|
||||||
|
"MA60": 919.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "06-28",
|
||||||
|
"value": 896,
|
||||||
|
"chg": 0.0,
|
||||||
|
"MA5": 898.0,
|
||||||
|
"MA20": 909.4,
|
||||||
|
"MA60": 919.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "06-29",
|
||||||
|
"value": 899,
|
||||||
|
"chg": 0.3348,
|
||||||
|
"MA5": 897.6,
|
||||||
|
"MA20": 908.6,
|
||||||
|
"MA60": 919.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "06-30",
|
||||||
|
"value": 898,
|
||||||
|
"chg": -0.1112,
|
||||||
|
"MA5": 897.2,
|
||||||
|
"MA20": 907.6,
|
||||||
|
"MA60": 918.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-01",
|
||||||
|
"value": 898,
|
||||||
|
"chg": 0.0,
|
||||||
|
"MA5": 897.4,
|
||||||
|
"MA20": 906.6,
|
||||||
|
"MA60": 918.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-02",
|
||||||
|
"value": 898,
|
||||||
|
"chg": 0.0,
|
||||||
|
"MA5": 897.8,
|
||||||
|
"MA20": 905.6,
|
||||||
|
"MA60": 918.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-03",
|
||||||
|
"value": 899,
|
||||||
|
"chg": 0.1114,
|
||||||
|
"MA5": 898.4,
|
||||||
|
"MA20": 904.6,
|
||||||
|
"MA60": 917.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-04",
|
||||||
|
"value": 899,
|
||||||
|
"chg": 0.0,
|
||||||
|
"MA5": 898.4,
|
||||||
|
"MA20": 903.6,
|
||||||
|
"MA60": 917.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-05",
|
||||||
|
"value": 899,
|
||||||
|
"chg": 0.0,
|
||||||
|
"MA5": 898.6,
|
||||||
|
"MA20": 902.8,
|
||||||
|
"MA60": 917.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-06",
|
||||||
|
"value": 898,
|
||||||
|
"chg": -0.1112,
|
||||||
|
"MA5": 898.6,
|
||||||
|
"MA20": 901.9,
|
||||||
|
"MA60": 916.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-07",
|
||||||
|
"value": 901,
|
||||||
|
"chg": 0.3341,
|
||||||
|
"MA5": 899.2,
|
||||||
|
"MA20": 901.2,
|
||||||
|
"MA60": 916.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-08",
|
||||||
|
"value": 899,
|
||||||
|
"chg": -0.222,
|
||||||
|
"MA5": 899.2,
|
||||||
|
"MA20": 900.8,
|
||||||
|
"MA60": 915.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-09",
|
||||||
|
"value": 900,
|
||||||
|
"chg": 0.1112,
|
||||||
|
"MA5": 899.4,
|
||||||
|
"MA20": 900.3,
|
||||||
|
"MA60": 915.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-10",
|
||||||
|
"value": 903,
|
||||||
|
"chg": 0.3333,
|
||||||
|
"MA5": 900.2,
|
||||||
|
"MA20": 900.0,
|
||||||
|
"MA60": 915.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-11",
|
||||||
|
"value": 903,
|
||||||
|
"chg": 0.0,
|
||||||
|
"MA5": 901.2,
|
||||||
|
"MA20": 899.7,
|
||||||
|
"MA60": 914.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-12",
|
||||||
|
"value": 903,
|
||||||
|
"chg": 0.0,
|
||||||
|
"MA5": 901.6,
|
||||||
|
"MA20": 899.5,
|
||||||
|
"MA60": 914.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-13",
|
||||||
|
"value": 904,
|
||||||
|
"chg": 0.1107,
|
||||||
|
"MA5": 902.6,
|
||||||
|
"MA20": 899.6,
|
||||||
|
"MA60": 913.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-14",
|
||||||
|
"value": 905,
|
||||||
|
"chg": 0.1106,
|
||||||
|
"MA5": 903.6,
|
||||||
|
"MA20": 899.8,
|
||||||
|
"MA60": 913.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-15",
|
||||||
|
"value": 908,
|
||||||
|
"chg": 0.3315,
|
||||||
|
"MA5": 904.6,
|
||||||
|
"MA20": 900.2,
|
||||||
|
"MA60": 913.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-16",
|
||||||
|
"value": 912,
|
||||||
|
"chg": 0.4405,
|
||||||
|
"MA5": 906.4,
|
||||||
|
"MA20": 900.9,
|
||||||
|
"MA60": 912.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-17",
|
||||||
|
"value": 912,
|
||||||
|
"chg": 0.0,
|
||||||
|
"MA5": 908.2,
|
||||||
|
"MA20": 901.7,
|
||||||
|
"MA60": 912.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-18",
|
||||||
|
"value": 912,
|
||||||
|
"chg": 0.0,
|
||||||
|
"MA5": 909.8,
|
||||||
|
"MA20": 902.5,
|
||||||
|
"MA60": 912.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-19",
|
||||||
|
"value": 912,
|
||||||
|
"chg": 0.0,
|
||||||
|
"MA5": 911.2,
|
||||||
|
"MA20": 903.2,
|
||||||
|
"MA60": 912.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-20",
|
||||||
|
"value": 914,
|
||||||
|
"chg": 0.2193,
|
||||||
|
"MA5": 912.4,
|
||||||
|
"MA20": 904.0,
|
||||||
|
"MA60": 911.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-21",
|
||||||
|
"value": 913,
|
||||||
|
"chg": -0.1094,
|
||||||
|
"MA5": 912.6,
|
||||||
|
"MA20": 904.7,
|
||||||
|
"MA60": 911.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-22",
|
||||||
|
"value": 913,
|
||||||
|
"chg": 0.0,
|
||||||
|
"MA5": 912.8,
|
||||||
|
"MA20": 905.4,
|
||||||
|
"MA60": 911.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "07-23",
|
||||||
|
"value": 915,
|
||||||
|
"chg": 0.2191,
|
||||||
|
"MA5": 913.4,
|
||||||
|
"MA20": 906.2,
|
||||||
|
"MA60": 911.1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>建材价格指数 · 趋势监控</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #fff; --fg: #1e293b; --sub: #64748b;
|
||||||
|
--card-bg: #f8fafc; --card-border: #e2e8f0;
|
||||||
|
--th-bg: #f1f5f9; --th-fg: #334155; --th-border: #cbd5e1;
|
||||||
|
--td-border: #e2e8f0; --td-even: #f8fafc;
|
||||||
|
--green: #16a34a; --red: #dc2626; --amber: #d97706;
|
||||||
|
--up-bg: #dcfce7; --down-bg: #fee2e2;
|
||||||
|
--tag-up-bg: #dcfce7; --tag-up-fg: #166534;
|
||||||
|
--tag-down-bg: #fee2e2; --tag-down-fg: #991b1b;
|
||||||
|
--tag-neutral-bg: #fef3c7; --tag-neutral-fg: #92400e;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--bg: #0f172a; --fg: #e2e8f0; --sub: #94a3b8;
|
||||||
|
--card-bg: #1a2332; --card-border: #334155;
|
||||||
|
--th-bg: #1e293b; --th-fg: #cbd5e1; --th-border: #334155;
|
||||||
|
--td-border: #1e293b; --td-even: #1a2332;
|
||||||
|
--up-bg: #1c3b1c; --down-bg: #3b1c1c;
|
||||||
|
--tag-up-bg: #1c3b1c; --tag-up-fg: #86efac;
|
||||||
|
--tag-down-bg: #3b1c1c; --tag-down-fg: #fca5a5;
|
||||||
|
--tag-neutral-bg: #3b2e1c; --tag-neutral-fg: #fcd34d;
|
||||||
|
}
|
||||||
|
body { font-family: -apple-system, 'Segoe UI', system-ui, sans-serif; padding: 24px; max-width: 960px; margin: 0 auto; color: var(--fg); background: var(--bg); }
|
||||||
|
h1 { font-size: 1.4rem; margin-bottom: 4px; }
|
||||||
|
.sub { color: var(--sub); font-size: 0.85rem; margin-bottom: 24px; }
|
||||||
|
|
||||||
|
.toggle-btn { position: fixed; top: 16px; right: 24px; background: var(--th-bg); border: 1px solid var(--th-border); color: var(--th-fg); padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 0.82rem; z-index: 10; }
|
||||||
|
.toggle-btn:hover { opacity: 0.8; }
|
||||||
|
|
||||||
|
/* ---- summary cards ---- */
|
||||||
|
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }
|
||||||
|
.card { background: var(--card-bg); border: 1px solid var(--card-border); border-radius: 8px; padding: 16px; }
|
||||||
|
.card .label { font-size: 0.78rem; color: var(--sub); margin-bottom: 4px; }
|
||||||
|
.card .value { font-size: 1.6rem; font-weight: 700; }
|
||||||
|
.card .delta { font-size: 0.82rem; margin-top: 4px; }
|
||||||
|
.delta.up { color: var(--green); }
|
||||||
|
.delta.down { color: var(--red); }
|
||||||
|
|
||||||
|
/* ---- signal strip ---- */
|
||||||
|
.signal-strip { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 24px; align-items: center; }
|
||||||
|
.signal-tag { padding: 4px 12px; border-radius: 14px; font-size: 0.82rem; font-weight: 600; }
|
||||||
|
.signal-tag.up { background: var(--tag-up-bg); color: var(--tag-up-fg); }
|
||||||
|
.signal-tag.down { background: var(--tag-down-bg); color: var(--tag-down-fg); }
|
||||||
|
.signal-tag.neutral { background: var(--tag-neutral-bg); color: var(--tag-neutral-fg); }
|
||||||
|
.signal-verdict { font-size: 1.05rem; font-weight: 700; margin-left: 8px; }
|
||||||
|
|
||||||
|
/* ---- MA table ---- */
|
||||||
|
table { border-collapse: collapse; width: 100%; font-size: 0.88rem; margin: 14px 0 20px; }
|
||||||
|
th { background: var(--th-bg); color: var(--th-fg); font-weight: 600; padding: 8px 12px; border: 1px solid var(--th-border); text-align: center; white-space: nowrap; }
|
||||||
|
td { padding: 7px 12px; border: 1px solid var(--td-border); text-align: center; }
|
||||||
|
tr:nth-child(even) td { background: var(--td-even); }
|
||||||
|
td:first-child { font-weight: 500; }
|
||||||
|
|
||||||
|
.arrow-up { color: var(--green); }
|
||||||
|
.arrow-down { color: var(--red); }
|
||||||
|
.arrow-flat { color: var(--sub); }
|
||||||
|
|
||||||
|
h2 { font-size: 1.1rem; margin: 28px 0 10px; padding-bottom: 4px; border-bottom: 1px solid var(--card-border); }
|
||||||
|
|
||||||
|
.row-up td { background: var(--up-bg) !important; }
|
||||||
|
.row-down td { background: var(--down-bg) !important; }
|
||||||
|
[data-theme="dark"] .row-up td { background: var(--up-bg) !important; }
|
||||||
|
[data-theme="dark"] .row-down td { background: var(--down-bg) !important; }
|
||||||
|
|
||||||
|
.footnote { color: var(--sub); font-size: 0.78rem; margin-top: 28px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<button class="toggle-btn" id="themeToggle">🌙 暗夜</button>
|
||||||
|
|
||||||
|
<h1>建材价格指数 · 趋势监控</h1>
|
||||||
|
<p class="sub" id="updateTime"></p>
|
||||||
|
|
||||||
|
<!-- ── 概览卡片 ── -->
|
||||||
|
<div class="cards">
|
||||||
|
<div class="card">
|
||||||
|
<div class="label">最新值</div>
|
||||||
|
<div class="value" id="curValue"></div>
|
||||||
|
<div class="delta" id="curDelta"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="label">近3月涨跌</div>
|
||||||
|
<div class="value" id="chg3m"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="label">近6月涨跌</div>
|
||||||
|
<div class="value" id="chg6m"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="label">近1年涨跌</div>
|
||||||
|
<div class="value" id="chg1y"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ── 趋势信号 ── -->
|
||||||
|
<div class="signal-strip" id="signalStrip"></div>
|
||||||
|
|
||||||
|
<!-- ── 均线对比 ── -->
|
||||||
|
<h2>均线系统</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>均线</th><th>数值</th><th>方向</th><th>价格位置</th></tr>
|
||||||
|
<tbody id="maTable"></tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- ── 近30日明细 ── -->
|
||||||
|
<h2>近30日明细</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>日期</th><th>指数</th><th>日涨跌%</th><th>MA5</th><th>MA20</th><th>MA60</th></tr>
|
||||||
|
<tbody id="recentTable"></tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p class="footnote">数据来源:akshare macro_china_construction_index · 建材综合指数</p>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// ── 暗夜模式 ──
|
||||||
|
var themeBtn = document.getElementById('themeToggle');
|
||||||
|
function setTheme(dark) {
|
||||||
|
if (dark) { document.documentElement.setAttribute('data-theme', 'dark'); themeBtn.textContent = '☀️ 日间'; }
|
||||||
|
else { document.documentElement.removeAttribute('data-theme'); themeBtn.textContent = '🌙 暗夜'; }
|
||||||
|
try { localStorage.setItem('ft-bci-theme', dark ? 'dark' : 'light'); } catch(e) {}
|
||||||
|
}
|
||||||
|
themeBtn.addEventListener('click', function() {
|
||||||
|
setTheme(document.documentElement.getAttribute('data-theme') !== 'dark');
|
||||||
|
});
|
||||||
|
(function() {
|
||||||
|
try {
|
||||||
|
var s = localStorage.getItem('ft-bci-theme');
|
||||||
|
if (s === 'dark' || (!s && matchMedia('(prefers-color-scheme: dark)').matches)) setTheme(true);
|
||||||
|
} catch(e) {}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// ── 数据渲染 ──
|
||||||
|
var D = {"summary": {"date": "2026-07-23", "value": 915, "change": 0.2191, "chg_3m": 0.6601, "chg_6m": 5.5363, "chg_1y": -1.507}, "mas": {"MA5": {"value": 913.4, "direction": "up", "price_vs_ma": "above"}, "MA10": {"value": 911.6, "direction": "up", "price_vs_ma": "above"}, "MA20": {"value": 906.2, "direction": "up", "price_vs_ma": "above"}, "MA60": {"value": 911.1, "direction": "down", "price_vs_ma": "above"}}, "signals": [{"period": "短期", "signal": "偏多", "detail": "价格 > MA5"}, {"period": "中期", "signal": "偏多", "detail": "MA5 > MA20"}, {"period": "长期", "signal": "偏空", "detail": "MA20 < MA60"}, {"period": "动量(5日)", "signal": "偏多", "detail": "+3"}], "overall": {"signal": "偏多", "color": "#16a34a"}, "recent": [{"date": "06-24", "value": 901, "chg": -0.2215, "MA5": 905.8, "MA20": 914.2, "MA60": 920.5}, {"date": "06-25", "value": 900, "chg": -0.111, "MA5": 904.0, "MA20": 913.1, "MA60": 920.4}, {"date": "06-26", "value": 897, "chg": -0.3333, "MA5": 901.6, "MA20": 911.9, "MA60": 920.1}, {"date": "06-27", "value": 896, "chg": -0.1115, "MA5": 899.4, "MA20": 910.6, "MA60": 919.8}, {"date": "06-28", "value": 896, "chg": 0.0, "MA5": 898.0, "MA20": 909.4, "MA60": 919.5}, {"date": "06-29", "value": 899, "chg": 0.3348, "MA5": 897.6, "MA20": 908.6, "MA60": 919.2}, {"date": "06-30", "value": 898, "chg": -0.1112, "MA5": 897.2, "MA20": 907.6, "MA60": 918.8}, {"date": "07-01", "value": 898, "chg": 0.0, "MA5": 897.4, "MA20": 906.6, "MA60": 918.4}, {"date": "07-02", "value": 898, "chg": 0.0, "MA5": 897.8, "MA20": 905.6, "MA60": 918.1}, {"date": "07-03", "value": 899, "chg": 0.1114, "MA5": 898.4, "MA20": 904.6, "MA60": 917.7}, {"date": "07-04", "value": 899, "chg": 0.0, "MA5": 898.4, "MA20": 903.6, "MA60": 917.4}, {"date": "07-05", "value": 899, "chg": 0.0, "MA5": 898.6, "MA20": 902.8, "MA60": 917.0}, {"date": "07-06", "value": 898, "chg": -0.1112, "MA5": 898.6, "MA20": 901.9, "MA60": 916.6}, {"date": "07-07", "value": 901, "chg": 0.3341, "MA5": 899.2, "MA20": 901.2, "MA60": 916.2}, {"date": "07-08", "value": 899, "chg": -0.222, "MA5": 899.2, "MA20": 900.8, "MA60": 915.7}, {"date": "07-09", "value": 900, "chg": 0.1112, "MA5": 899.4, "MA20": 900.3, "MA60": 915.3}, {"date": "07-10", "value": 903, "chg": 0.3333, "MA5": 900.2, "MA20": 900.0, "MA60": 915.0}, {"date": "07-11", "value": 903, "chg": 0.0, "MA5": 901.2, "MA20": 899.7, "MA60": 914.5}, {"date": "07-12", "value": 903, "chg": 0.0, "MA5": 901.6, "MA20": 899.5, "MA60": 914.1}, {"date": "07-13", "value": 904, "chg": 0.1107, "MA5": 902.6, "MA20": 899.6, "MA60": 913.7}, {"date": "07-14", "value": 905, "chg": 0.1106, "MA5": 903.6, "MA20": 899.8, "MA60": 913.3}, {"date": "07-15", "value": 908, "chg": 0.3315, "MA5": 904.6, "MA20": 900.2, "MA60": 913.0}, {"date": "07-16", "value": 912, "chg": 0.4405, "MA5": 906.4, "MA20": 900.9, "MA60": 912.8}, {"date": "07-17", "value": 912, "chg": 0.0, "MA5": 908.2, "MA20": 901.7, "MA60": 912.5}, {"date": "07-18", "value": 912, "chg": 0.0, "MA5": 909.8, "MA20": 902.5, "MA60": 912.2}, {"date": "07-19", "value": 912, "chg": 0.0, "MA5": 911.2, "MA20": 903.2, "MA60": 912.0}, {"date": "07-20", "value": 914, "chg": 0.2193, "MA5": 912.4, "MA20": 904.0, "MA60": 911.7}, {"date": "07-21", "value": 913, "chg": -0.1094, "MA5": 912.6, "MA20": 904.7, "MA60": 911.5}, {"date": "07-22", "value": 913, "chg": 0.0, "MA5": 912.8, "MA20": 905.4, "MA60": 911.3}, {"date": "07-23", "value": 915, "chg": 0.2191, "MA5": 913.4, "MA20": 906.2, "MA60": 911.1}]};
|
||||||
|
|
||||||
|
document.getElementById('updateTime').textContent = '更新至 ' + D.summary.date;
|
||||||
|
|
||||||
|
// 卡片
|
||||||
|
var sv = D.summary;
|
||||||
|
document.getElementById('curValue').textContent = sv.value;
|
||||||
|
var chgEl = document.getElementById('curDelta');
|
||||||
|
var chg = sv.change;
|
||||||
|
chgEl.textContent = (chg >= 0 ? '+' : '') + chg.toFixed(2) + '%';
|
||||||
|
chgEl.className = 'delta ' + (chg >= 0 ? 'up' : 'down');
|
||||||
|
|
||||||
|
function fmtPct(v) {
|
||||||
|
var s = (v >= 0 ? '+' : '') + v.toFixed(2) + '%';
|
||||||
|
return '<span class="' + (v >= 0 ? 'arrow-up' : 'arrow-down') + '">' + s + '</span>';
|
||||||
|
}
|
||||||
|
document.getElementById('chg3m').innerHTML = fmtPct(sv.chg_3m);
|
||||||
|
document.getElementById('chg6m').innerHTML = fmtPct(sv.chg_6m);
|
||||||
|
document.getElementById('chg1y').innerHTML = fmtPct(sv.chg_1y);
|
||||||
|
|
||||||
|
// 信号
|
||||||
|
var strip = document.getElementById('signalStrip');
|
||||||
|
D.signals.forEach(function(s) {
|
||||||
|
var cls = s.signal === '偏多' ? 'up' : 'down';
|
||||||
|
strip.innerHTML += '<span class="signal-tag ' + cls + '">' + s.period + ': ' + s.signal + '</span>';
|
||||||
|
});
|
||||||
|
strip.innerHTML += '<span class="signal-verdict" style="color:' + D.overall.color + '">综合: ' + D.overall.signal + '</span>';
|
||||||
|
|
||||||
|
// 均线表
|
||||||
|
var maHtml = '';
|
||||||
|
['MA5','MA10','MA20','MA60'].forEach(function(k) {
|
||||||
|
var m = D.mas[k];
|
||||||
|
var dirIcon = m.direction === 'up' ? '↑' : (m.direction === 'down' ? '↓' : '→');
|
||||||
|
var dirCls = 'arrow-' + m.direction;
|
||||||
|
var posText = m.price_vs_ma === 'above' ? '价格在上 ↑' : '价格在下 ↓';
|
||||||
|
var posCls = m.price_vs_ma === 'above' ? 'arrow-up' : 'arrow-down';
|
||||||
|
maHtml += '<tr><td>' + k + '</td><td><strong>' + m.value + '</strong></td><td><span class="' + dirCls + '">' + dirIcon + '</span></td><td><span class="' + posCls + '">' + posText + '</span></td></tr>';
|
||||||
|
});
|
||||||
|
document.getElementById('maTable').innerHTML = maHtml;
|
||||||
|
|
||||||
|
// 近30日明细
|
||||||
|
var recentHtml = '';
|
||||||
|
D.recent.forEach(function(r) {
|
||||||
|
var rowCls = (r.chg >= 0) ? 'row-up' : 'row-down';
|
||||||
|
recentHtml += '<tr class="' + rowCls + '">';
|
||||||
|
recentHtml += '<td>' + r.date + '</td>';
|
||||||
|
recentHtml += '<td>' + r.value + '</td>';
|
||||||
|
recentHtml += '<td>' + (r.chg >= 0 ? '+' : '') + r.chg.toFixed(2) + '%</td>';
|
||||||
|
recentHtml += '<td>' + (r.MA5 != null ? r.MA5 : '-') + '</td>';
|
||||||
|
recentHtml += '<td>' + (r.MA20 != null ? r.MA20 : '-') + '</td>';
|
||||||
|
recentHtml += '<td>' + (r.MA60 != null ? r.MA60 : '-') + '</td>';
|
||||||
|
recentHtml += '</tr>';
|
||||||
|
});
|
||||||
|
document.getElementById('recentTable').innerHTML = recentHtml;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
"""拉取建材价格指数,更新 data.json 并刷新 index.html。"""
|
||||||
|
import json
|
||||||
|
import pandas as pd
|
||||||
|
import akshare as ak
|
||||||
|
|
||||||
|
df = ak.macro_china_construction_index()
|
||||||
|
df['日期'] = pd.to_datetime(df['日期'])
|
||||||
|
df = df.sort_values('日期').reset_index(drop=True)
|
||||||
|
|
||||||
|
for w in [5, 10, 20, 60]:
|
||||||
|
df[f'MA{w}'] = df['最新值'].rolling(w).mean().round(1)
|
||||||
|
df[f'MA{w}_dir'] = df[f'MA{w}'].diff().apply(lambda x: 'up' if x > 0 else ('down' if x < 0 else 'flat'))
|
||||||
|
|
||||||
|
latest = df.iloc[-1]
|
||||||
|
|
||||||
|
signals = []
|
||||||
|
if latest['最新值'] > latest['MA5']:
|
||||||
|
signals.append(('短期', '偏多', '价格 > MA5'))
|
||||||
|
else:
|
||||||
|
signals.append(('短期', '偏空', '价格 < MA5'))
|
||||||
|
if latest['MA5'] > latest['MA20']:
|
||||||
|
signals.append(('中期', '偏多', 'MA5 > MA20'))
|
||||||
|
else:
|
||||||
|
signals.append(('中期', '偏空', 'MA5 < MA20'))
|
||||||
|
if latest['MA20'] > latest['MA60']:
|
||||||
|
signals.append(('长期', '偏多', 'MA20 > MA60'))
|
||||||
|
else:
|
||||||
|
signals.append(('长期', '偏空', 'MA20 < MA60'))
|
||||||
|
|
||||||
|
momentum_5d = latest['最新值'] - df.iloc[-6]['最新值']
|
||||||
|
signals.append(('动量(5日)', '偏多' if momentum_5d > 0 else '偏空', f"{momentum_5d:+d}"))
|
||||||
|
|
||||||
|
bull_count = sum(1 for _, s, _ in signals if s == '偏多')
|
||||||
|
if bull_count >= 3:
|
||||||
|
overall = ('偏多', '#16a34a')
|
||||||
|
elif bull_count <= 1:
|
||||||
|
overall = ('偏空', '#dc2626')
|
||||||
|
else:
|
||||||
|
overall = ('震荡', '#d97706')
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
'date': latest['日期'].strftime('%Y-%m-%d'),
|
||||||
|
'value': int(latest['最新值']),
|
||||||
|
'change': round(float(latest['涨跌幅']), 4),
|
||||||
|
'chg_3m': round(float(latest['近3月涨跌幅']), 4),
|
||||||
|
'chg_6m': round(float(latest['近6月涨跌幅']), 4),
|
||||||
|
'chg_1y': round(float(latest['近1年涨跌幅']), 4),
|
||||||
|
}
|
||||||
|
|
||||||
|
mas = {}
|
||||||
|
for w in [5, 10, 20, 60]:
|
||||||
|
mas[f'MA{w}'] = {
|
||||||
|
'value': float(latest[f'MA{w}']),
|
||||||
|
'direction': latest[f'MA{w}_dir'],
|
||||||
|
'price_vs_ma': 'above' if latest['最新值'] > latest[f'MA{w}'] else 'below'
|
||||||
|
}
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for _, r in df.tail(30).iterrows():
|
||||||
|
rows.append({
|
||||||
|
'date': r['日期'].strftime('%m-%d'),
|
||||||
|
'value': int(r['最新值']),
|
||||||
|
'chg': round(float(r['涨跌幅']), 4),
|
||||||
|
'MA5': None if pd.isna(r['MA5']) else round(float(r['MA5']), 1),
|
||||||
|
'MA20': None if pd.isna(r['MA20']) else round(float(r['MA20']), 1),
|
||||||
|
'MA60': None if pd.isna(r['MA60']) else round(float(r['MA60']), 1),
|
||||||
|
})
|
||||||
|
|
||||||
|
output = {
|
||||||
|
'summary': summary,
|
||||||
|
'mas': mas,
|
||||||
|
'signals': [{'period': p, 'signal': s, 'detail': d} for p, s, d in signals],
|
||||||
|
'overall': {'signal': overall[0], 'color': overall[1]},
|
||||||
|
'recent': rows,
|
||||||
|
}
|
||||||
|
|
||||||
|
with open('data.json', 'w') as f:
|
||||||
|
json.dump(output, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
SENTINEL = '/*__DATA_SENTINEL__*/{}'
|
||||||
|
html = open('index.html').read()
|
||||||
|
html = html.replace(SENTINEL, json.dumps(output, ensure_ascii=False))
|
||||||
|
open('index.html', 'w').write(html)
|
||||||
|
|
||||||
|
print(f"刷新完成: {summary['date']} → {summary['value']} (日涨跌 {summary['change']:+.2f}%)")
|
||||||
|
print(f"综合信号: {overall[0]}")
|
||||||
|
print("信号明细:")
|
||||||
|
for p, s, d in signals:
|
||||||
|
print(f" {p}: {s} ({d})")
|
||||||
Reference in New Issue
Block a user