Explorar o código

refactor(stock/etf): 将 ETF 策略模型拆分为 ETF 配置模型与全天候策略两个独立页面

ligb hai 19 horas
pai
achega
bd63de6bd2

+ 10 - 4
web-app/src/router/index.js

@@ -224,10 +224,16 @@ const router = createRouter({
       meta: { layout: 'app', title: '权益分析 · 美股分析' },
     },
     {
-      path: '/stock/etf',
-      name: 'stock-etf',
-      component: () => import('@/views/stock/etf/index.vue'),
-      meta: { layout: 'app', title: 'ETF 策略模型' },
+      path: '/stock/etf/config',
+      name: 'stock-etf-config',
+      component: () => import('@/views/stock/etf/config/index.vue'),
+      meta: { layout: 'app', title: 'ETF 策略模型 · ETF 配置模型' },
+    },
+    {
+      path: '/stock/etf/allweather',
+      name: 'stock-etf-allweather',
+      component: () => import('@/views/stock/etf/allweather/index.vue'),
+      meta: { layout: 'app', title: 'ETF 策略模型 · 全天候策略' },
     },
     {
       path: '/gold/index',

+ 581 - 0
web-app/src/views/stock/etf/allweather/index.vue

@@ -0,0 +1,581 @@
+<template>
+  <div class="etf-page">
+    <header class="page-hd">
+      <div>
+        <div class="page-title">全天候策略模型</div>
+        <div class="page-sub">
+          全天候策略 · 数据截至 {{ allweatherModel.asOf }}
+        </div>
+      </div>
+      <div class="page-actions">
+        <button class="btn" @click="reload" :disabled="loading">
+          <span :class="['ic-refresh', { spinning: loading }]">↻</span> 刷新
+        </button>
+      </div>
+    </header>
+
+    <!-- 全天候 KPI 总览 -->
+    <section class="kpi-grid">
+      <div class="kpi-card hero" :class="awSummaryCls">
+        <div class="label">全天候策略 · 累计收益</div>
+        <div class="value">
+          {{ formatPct(allweatherSummary.totalReturn) }}<span class="unit"> %</span>
+        </div>
+        <div class="chg">
+          起始 2025-01-01 · 当前净值 {{ allweatherSummary.latest.toFixed(4) }} · 最大回撤 {{ formatPct(allweatherSummary.maxDrawdown) }}%
+        </div>
+      </div>
+      <div class="kpi-card">
+        <div class="label">全天候策略 · 本周收益</div>
+        <div class="value">
+          {{ formatPct(allweatherSummary.weeklyReturn) }}<span class="unit"> %</span>
+        </div>
+        <div class="chg">日频净值 · 数据周期(2026.9.28-9.30)</div>
+      </div>
+    </section>
+
+    <section class="section">
+      <div class="section-hd">
+        <span class="dot" style="background:#1552D0"></span>
+        <h2>全天候策略模型</h2>
+        <span class="meta">Bridgewater All-Weather · 风险平价 · 月末调仓</span>
+      </div>
+
+      <!-- 模型说明卡 -->
+      <div class="card desc-card">
+        <div class="desc-hd">
+          <span class="ic">说</span>
+          <span class="desc-title">模型说明</span>
+        </div>
+        <div class="desc-body">
+          <div class="desc-row">
+            <span class="k">底层标的</span>
+            <span class="v">{{ allweatherModel.pool }}</span>
+          </div>
+          <div class="desc-row">
+            <span class="k">主要逻辑</span>
+            <span class="v">{{ allweatherModel.description }}</span>
+          </div>
+        </div>
+      </div>
+
+      <div class="card-grid cols-1-1" style="margin-top:14px">
+        <div class="card">
+          <div class="card-head">
+            <div class="title">
+              <span class="ic">图</span>
+              <span class="title-text">模型净值走势(日频)</span>
+            </div>
+            <div class="acts">
+              <span class="meta">基准 1.00 · 当前 {{ allweatherSummary.latest.toFixed(4) }}</span>
+            </div>
+          </div>
+          <div class="card-body">
+            <Chart :option="awNetOpt" height="320px" />
+          </div>
+        </div>
+        <div class="card">
+          <div class="card-head">
+            <div class="title">
+              <span class="ic">图</span>
+              <span class="title-text">历史回撤曲线</span>
+            </div>
+            <div class="acts">
+              <span class="meta">最大回撤 {{ formatPct(allweatherSummary.maxDrawdown) }}%</span>
+            </div>
+          </div>
+          <div class="card-body">
+            <Chart :option="awDrawdownOpt" height="320px" />
+          </div>
+        </div>
+      </div>
+
+      <div class="card-grid cols-2-1" style="margin-top:14px">
+        <div class="card">
+          <div class="card-head">
+            <div class="title">
+              <span class="ic">图</span>
+              <span class="title-text">风险平价 · 六类 ETF 配置占比变化</span>
+            </div>
+            <div class="acts">
+              <span class="meta">{{ allweatherWeights.length }} 期</span>
+            </div>
+          </div>
+          <div class="card-body">
+            <Chart :option="awWeightOpt" height="340px" />
+          </div>
+        </div>
+        <div class="card">
+          <div class="card-head">
+            <div class="title">
+              <span class="ic">图</span>
+              <span class="title-text">最新一期权重</span>
+            </div>
+            <div class="acts">
+              <span class="meta">{{ allweatherWeights[allweatherWeights.length - 1].date }}</span>
+            </div>
+          </div>
+          <div class="card-body">
+            <Chart :option="awLatestWeightOpt" height="200px" />
+            <div class="note-block">
+              <div class="kv-list">
+                <div class="kv"><span class="k">信号日</span><span class="v">{{ allweatherWeights[allweatherWeights.length - 1].signalDate }}</span></div>
+                <div class="kv"><span class="k">沪深300ETF</span><span class="v">{{ (allweatherWeights[allweatherWeights.length - 1].hs300 * 100).toFixed(1) }}%</span></div>
+                <div class="kv"><span class="k">中证500ETF</span><span class="v">{{ (allweatherWeights[allweatherWeights.length - 1].zz500 * 100).toFixed(1) }}%</span></div>
+                <div class="kv"><span class="k">五年国债ETF</span><span class="v">{{ (allweatherWeights[allweatherWeights.length - 1].c5 * 100).toFixed(1) }}%</span></div>
+                <div class="kv"><span class="k">十年国债ETF</span><span class="v">{{ (allweatherWeights[allweatherWeights.length - 1].c10 * 100).toFixed(1) }}%</span></div>
+                <div class="kv"><span class="k">黄金ETF</span><span class="v">{{ (allweatherWeights[allweatherWeights.length - 1].gold * 100).toFixed(1) }}%</span></div>
+                <div class="kv"><span class="k">豆粕ETF</span><span class="v">{{ (allweatherWeights[allweatherWeights.length - 1].meal * 100).toFixed(1) }}%</span></div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 调仓权重明细表 -->
+      <div class="card-grid cols-1" style="margin-top:14px">
+        <div class="card">
+          <div class="card-head">
+            <div class="title">
+              <span class="ic">表</span>
+              <span class="title-text">六类 ETF 配置占比变化 · 明细表</span>
+            </div>
+            <div class="acts">
+              <span class="meta">风险平价 · 月末调仓 · 信号日 T-1</span>
+            </div>
+          </div>
+          <div class="card-body">
+            <div class="table-wrap">
+              <table class="weight-tbl aw">
+                <thead>
+                  <tr>
+                    <th>调仓日期</th>
+                    <th>信号日</th>
+                    <th class="num">沪深300</th>
+                    <th class="num">中证500</th>
+                    <th class="num">5年国债</th>
+                    <th class="num">10年国债</th>
+                    <th class="num">黄金</th>
+                    <th class="num">豆粕</th>
+                  </tr>
+                </thead>
+                <tbody>
+                  <tr v-for="(w, i) in allweatherWeights" :key="i">
+                    <td><b>{{ w.date }}</b></td>
+                    <td>{{ w.signalDate }}</td>
+                    <td class="num">{{ (w.hs300 * 100).toFixed(1) }}%</td>
+                    <td class="num">{{ (w.zz500 * 100).toFixed(1) }}%</td>
+                    <td class="num">{{ (w.c5 * 100).toFixed(1) }}%</td>
+                    <td class="num">{{ (w.c10 * 100).toFixed(1) }}%</td>
+                    <td class="num">{{ (w.gold * 100).toFixed(1) }}%</td>
+                    <td class="num">{{ (w.meal * 100).toFixed(1) }}%</td>
+                  </tr>
+                </tbody>
+              </table>
+            </div>
+          </div>
+        </div>
+      </div>
+    </section>
+
+    <footer class="footer">
+      数据源:4.2.3-ETF模型-数据底稿.xlsx · 全天候策略-净值走势图 / 全天候策略-每月调仓权重 · 截至 {{ allweatherModel.asOf }}
+    </footer>
+  </div>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue'
+import Chart from '@/components/Chart.vue'
+import {
+  allweatherModel,
+  allweatherNetSeries,
+  allweatherWeights,
+  allweatherSummary,
+} from '../data'
+import {
+  allweatherNetLineOption,
+  allweatherDrawdownOption,
+  allweatherWeightStackOption,
+  allweatherLatestWeightOption,
+} from '../charts'
+
+const loading = ref(false)
+
+function formatPct(v) {
+  if (v == null || !Number.isFinite(v)) return '—'
+  const sign = v > 0 ? '+' : ''
+  return `${sign}${v.toFixed(2)}`
+}
+
+function summaryCls(total) {
+  if (total == null) return 'neutral'
+  if (total >= 30) return 'gold'
+  if (total >= 10) return 'over'
+  if (total >= 0) return 'neutral'
+  return 'under'
+}
+
+const awSummaryCls = computed(() => summaryCls(allweatherSummary.totalReturn))
+
+const awNetOpt = computed(() => allweatherNetLineOption(allweatherNetSeries))
+const awDrawdownOpt = computed(() => allweatherDrawdownOption(allweatherNetSeries))
+const awWeightOpt = computed(() => allweatherWeightStackOption(allweatherWeights))
+const awLatestWeightOpt = computed(() => allweatherLatestWeightOption(allweatherWeights))
+
+function reload() {
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 600)
+}
+</script>
+
+<style scoped>
+.etf-page {
+  padding: 18px 24px 32px;
+  color: var(--ink, #1A2438);
+}
+
+.page-hd {
+  display: flex;
+  align-items: flex-end;
+  justify-content: space-between;
+  margin-bottom: 14px;
+  flex-wrap: wrap;
+  gap: 12px;
+}
+.page-title {
+  font-size: 18px;
+  font-weight: 600;
+  color: var(--ink, #1A2438);
+  margin-bottom: 3px;
+}
+.page-sub {
+  font-size: 12px;
+  color: var(--mut, #6B7A96);
+}
+.page-actions {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+}
+.btn {
+  height: 30px;
+  padding: 0 14px;
+  border: 1px solid var(--line, #E4E9F2);
+  border-radius: 6px;
+  background: #fff;
+  color: var(--ink, #1A2438);
+  font-size: 12.5px;
+  cursor: pointer;
+  display: inline-flex;
+  align-items: center;
+  gap: 5px;
+  transition: 0.15s;
+}
+.btn:hover {
+  border-color: var(--brand, #1552D0);
+  color: var(--brand, #1552D0);
+}
+
+.ic-refresh {
+  display: inline-block;
+  transition: transform 0.6s;
+}
+.ic-refresh.spinning {
+  animation: spin 0.8s linear infinite;
+}
+@keyframes spin {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
+}
+
+.kpi-grid {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 10px;
+  margin-bottom: 16px;
+}
+.kpi-card {
+  background: #fff;
+  border: 1px solid var(--line, #E4E9F2);
+  border-radius: 8px;
+  padding: 13px 14px;
+  position: relative;
+  transition: 0.15s;
+  min-width: 0;
+}
+.kpi-card:hover {
+  box-shadow: 0 1px 3px rgba(16, 32, 64, 0.06);
+  border-color: #D1D8E5;
+}
+.kpi-card.hero {
+  background: linear-gradient(135deg, #0E3A96, #1552D0);
+  color: #fff;
+  border-color: #0E3A96;
+}
+.kpi-card.hero .label { color: rgba(255,255,255,0.82); }
+.kpi-card.hero .value .unit { color: rgba(255,255,255,0.95); }
+.kpi-card.hero .chg { color: rgba(255,255,255,0.88); }
+.kpi-card.hero.gold {
+  background: linear-gradient(135deg, #8C6A14, #C49A2A);
+  border-color: #8C6A14;
+}
+.kpi-card.hero.under {
+  background: linear-gradient(135deg, #B72828, #E13B3B);
+  border-color: #B72828;
+}
+.kpi-card.hero.over {
+  background: linear-gradient(135deg, #13754C, #189A65);
+  border-color: #13754C;
+}
+.kpi-card.hero.neutral {
+  background: linear-gradient(135deg, #4D5A75, #6B7A96);
+  border-color: #4D5A75;
+}
+.kpi-card .label {
+  font-size: 11.5px;
+  color: var(--mut, #6B7A96);
+  margin-bottom: 6px;
+}
+.kpi-card .value {
+  font-size: 22px;
+  font-weight: 600;
+  color: var(--ink, #1A2438);
+  font-variant-numeric: tabular-nums;
+  letter-spacing: -0.5px;
+  line-height: 1.1;
+}
+.kpi-card .value .unit {
+  font-size: 12px;
+  color: var(--mut, #6B7A96);
+  font-weight: 500;
+  margin-left: 2px;
+}
+.kpi-card .chg {
+  font-size: 11px;
+  margin-top: 5px;
+  color: var(--mut, #6B7A96);
+  font-variant-numeric: tabular-nums;
+}
+
+.section {
+  margin-top: 18px;
+}
+.section-hd {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 12px;
+}
+.section-hd .dot {
+  width: 4px;
+  height: 18px;
+  background: var(--brand, #1552D0);
+  border-radius: 2px;
+}
+.section-hd h2 {
+  font-size: 14.5px;
+  font-weight: 600;
+  color: var(--ink, #1A2438);
+  margin: 0;
+}
+.section-hd .meta {
+  font-size: 11.5px;
+  color: #98A5BD;
+  margin-left: 8px;
+}
+
+.card-grid {
+  display: grid;
+  gap: 14px;
+}
+.card-grid.cols-2-1 { grid-template-columns: 2fr 1fr; }
+.card-grid.cols-1-1 { grid-template-columns: 1fr 1fr; }
+.card-grid.cols-1 { grid-template-columns: 1fr; }
+
+.card {
+  background: #fff;
+  border: 1px solid var(--line, #E4E9F2);
+  border-radius: 8px;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+.card:hover {
+  box-shadow: 0 1px 3px rgba(16, 32, 64, 0.06);
+}
+.card-head {
+  padding: 11px 16px;
+  border-bottom: 1px solid var(--line, #E4E9F2);
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.card-head .title {
+  font-size: 13.5px;
+  font-weight: 600;
+  color: var(--ink, #1A2438);
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  flex: 1;
+  min-width: 0;
+}
+.card-head .title .ic {
+  display: inline-grid;
+  place-items: center;
+  width: 18px;
+  height: 18px;
+  background: var(--brand-lt, #EAF1FF);
+  color: var(--brand, #1552D0);
+  font-size: 10px;
+  border-radius: 4px;
+  font-weight: 700;
+  flex: none;
+}
+.card-head .title-text {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.card-head .acts {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  flex: none;
+  font-size: 11px;
+  color: var(--mut, #6B7A96);
+}
+.card-body {
+  padding: 14px 16px;
+  flex: 1;
+  min-height: 0;
+}
+
+/* 模型说明卡 */
+.desc-card {
+  background: linear-gradient(180deg, #FAFBFE 0%, #fff 100%);
+}
+.desc-hd {
+  padding: 11px 16px;
+  border-bottom: 1px solid var(--line, #E4E9F2);
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.desc-hd .ic {
+  display: inline-grid;
+  place-items: center;
+  width: 18px;
+  height: 18px;
+  background: var(--brand-lt, #EAF1FF);
+  color: var(--brand, #1552D0);
+  font-size: 10px;
+  border-radius: 4px;
+  font-weight: 700;
+}
+.desc-title {
+  font-size: 13.5px;
+  font-weight: 600;
+  color: var(--ink, #1A2438);
+}
+.desc-body {
+  padding: 14px 16px;
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 10px 20px;
+}
+.desc-row {
+  display: flex;
+  gap: 10px;
+  font-size: 12px;
+  line-height: 1.7;
+}
+.desc-row .k {
+  flex: none;
+  width: 70px;
+  color: var(--mut, #6B7A96);
+}
+.desc-row .v {
+  flex: 1;
+  color: var(--ink, #1A2438);
+  font-weight: 500;
+}
+
+/* 备注 / 最新权重说明 */
+.note-block {
+  margin-top: 12px;
+  padding-top: 10px;
+  border-top: 1px dashed var(--line, #E4E9F2);
+}
+.kv-list {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 6px 12px;
+  font-size: 11.5px;
+}
+.kv {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.kv .k { color: var(--mut, #6B7A96); }
+.kv .v {
+  color: var(--ink, #1A2438);
+  font-weight: 600;
+  font-variant-numeric: tabular-nums;
+}
+
+/* 调仓权重表 */
+.table-wrap {
+  overflow-x: auto;
+}
+.weight-tbl {
+  width: 100%;
+  border-collapse: collapse;
+  font-size: 12px;
+}
+.weight-tbl th,
+.weight-tbl td {
+  padding: 9px 10px;
+  border-bottom: 1px solid var(--line, #E4E9F2);
+  text-align: left;
+  vertical-align: middle;
+}
+.weight-tbl th {
+  background: #F5F7FB;
+  color: var(--mut, #6B7A96);
+  font-weight: 600;
+  font-size: 11.5px;
+}
+.weight-tbl td.num,
+.weight-tbl th.num {
+  text-align: right;
+  font-variant-numeric: tabular-nums;
+  white-space: nowrap;
+}
+.weight-tbl tbody tr:hover {
+  background: #FAFBFE;
+}
+
+.footer {
+  padding: 18px 24px 12px;
+  color: #98A5BD;
+  font-size: 11.5px;
+  text-align: center;
+  border-top: 1px solid var(--line, #E4E9F2);
+  background: #FAFBFE;
+  margin-top: 14px;
+  border-radius: 8px;
+}
+
+@media (max-width: 980px) {
+  .kpi-grid { grid-template-columns: 1fr; }
+  .desc-body { grid-template-columns: 1fr; }
+  .card-grid.cols-2-1,
+  .card-grid.cols-1-1,
+  .card-grid.cols-1 { grid-template-columns: 1fr; }
+}
+</style>

+ 8 - 213
web-app/src/views/stock/etf/index.vue → web-app/src/views/stock/etf/config/index.vue

@@ -2,25 +2,19 @@
   <div class="etf-page">
     <header class="page-hd">
       <div>
-        <div class="page-title">ETF 策略模型</div>
+        <div class="page-title">ETF 配置模型</div>
         <div class="page-sub">
-          ETF配置模型(全天候策略)· 数据截至 ETF {{ etfModel.asOf }} / AW {{ allweatherModel.asOf }}
+          ETF配置模型 · 数据截至 {{ etfModel.asOf }}
         </div>
       </div>
       <div class="page-actions">
-        <!-- <span class="chip" :class="etfSummaryCls">
-          ETF · 累计 {{ etfSummaryText }}
-        </span>
-        <span class="chip" :class="awSummaryCls">
-          全天候 · 累计 {{ awSummaryText }}
-        </span> -->
         <button class="btn" @click="reload" :disabled="loading">
           <span :class="['ic-refresh', { spinning: loading }]">↻</span> 刷新
         </button>
       </div>
     </header>
 
-    <!-- 双模型 KPI 总览 -->
+    <!-- ETF 模型 KPI 总览 -->
     <section class="kpi-grid">
       <div class="kpi-card hero" :class="etfSummaryCls">
         <div class="label">ETF 配置模型 · 累计收益</div>
@@ -28,7 +22,7 @@
           {{ formatPct(etfSummary.totalReturn) }}<span class="unit"> %</span>
         </div>
         <div class="chg">
-          起始 {{ etfModel.startDate }} · 当前净值 {{ etfSummary.latest.toFixed(4) }} · 最大回撤 -12.37%
+          起始 2025-01-01 · 当前净值 {{ etfSummary.latest.toFixed(4) }} · 最大回撤 -12.37%
         </div>
       </div>
       <div class="kpi-card">
@@ -38,25 +32,8 @@
         </div>
         <div class="chg">日频净值 · 数据周期(2026.9.28-9.30)</div>
       </div>
-      <div class="kpi-card hero" :class="awSummaryCls">
-        <div class="label">全天候策略 · 累计收益</div>
-        <div class="value">
-          {{ formatPct(allweatherSummary.totalReturn) }}<span class="unit"> %</span>
-        </div>
-        <div class="chg">
-          起始 {{ allweatherModel.startDate }} · 当前净值 {{ allweatherSummary.latest.toFixed(4) }} · 最大回撤 {{ formatPct(allweatherSummary.maxDrawdown) }}%
-        </div>
-      </div>
-      <div class="kpi-card">
-        <div class="label">全天候策略 · 本周收益</div>
-        <div class="value">
-          {{ formatPct(allweatherSummary.weeklyReturn) }}<span class="unit"> %</span>
-        </div>
-        <div class="chg">日频净值 · 数据周期(2026.9.28-9.30)</div>
-      </div>
     </section>
 
-    <!-- ====== 模型一:ETF 配置模型 ====== -->
     <section class="section">
       <div class="section-hd">
         <span class="dot" style="background:#C49A2A"></span>
@@ -210,157 +187,8 @@
       </div>
     </section>
 
-    <!-- ====== 模型二:全天候策略 ====== -->
-    <section class="section">
-      <div class="section-hd">
-        <span class="dot" style="background:#1552D0"></span>
-        <h2>全天候策略模型</h2>
-        <span class="meta">Bridgewater All-Weather · 风险平价 · 月末调仓</span>
-      </div>
-
-      <!-- 模型说明卡 -->
-      <div class="card desc-card">
-        <div class="desc-hd">
-          <span class="ic">说</span>
-          <span class="desc-title">模型说明</span>
-        </div>
-        <div class="desc-body">
-          <div class="desc-row">
-            <span class="k">底层标的</span>
-            <span class="v">{{ allweatherModel.pool }}</span>
-          </div>
-          <div class="desc-row">
-            <span class="k">主要逻辑</span>
-            <span class="v">{{ allweatherModel.description }}</span>
-          </div>
-          <!-- <div class="desc-row">
-            <span class="k">数据源</span>
-            <span class="v">{{ allweatherModel.source }}</span>
-          </div> -->
-        </div>
-      </div>
-
-      <div class="card-grid cols-1-1" style="margin-top:14px">
-        <div class="card">
-          <div class="card-head">
-            <div class="title">
-              <span class="ic">图</span>
-              <span class="title-text">模型净值走势(日频)</span>
-            </div>
-            <div class="acts">
-              <span class="meta">基准 1.00 · 当前 {{ allweatherSummary.latest.toFixed(4) }}</span>
-            </div>
-          </div>
-          <div class="card-body">
-            <Chart :option="awNetOpt" height="320px" />
-          </div>
-        </div>
-        <div class="card">
-          <div class="card-head">
-            <div class="title">
-              <span class="ic">图</span>
-              <span class="title-text">历史回撤曲线</span>
-            </div>
-            <div class="acts">
-              <span class="meta">最大回撤 {{ formatPct(allweatherSummary.maxDrawdown) }}%</span>
-            </div>
-          </div>
-          <div class="card-body">
-            <Chart :option="awDrawdownOpt" height="320px" />
-          </div>
-        </div>
-      </div>
-
-      <div class="card-grid cols-2-1" style="margin-top:14px">
-        <div class="card">
-          <div class="card-head">
-            <div class="title">
-              <span class="ic">图</span>
-              <span class="title-text">风险平价 · 六类 ETF 配置占比变化</span>
-            </div>
-            <div class="acts">
-              <span class="meta">{{ allweatherWeights.length }} 期</span>
-            </div>
-          </div>
-          <div class="card-body">
-            <Chart :option="awWeightOpt" height="340px" />
-          </div>
-        </div>
-        <div class="card">
-          <div class="card-head">
-            <div class="title">
-              <span class="ic">图</span>
-              <span class="title-text">最新一期权重</span>
-            </div>
-            <div class="acts">
-              <span class="meta">{{ allweatherWeights[allweatherWeights.length - 1].date }}</span>
-            </div>
-          </div>
-          <div class="card-body">
-            <Chart :option="awLatestWeightOpt" height="200px" />
-            <div class="note-block">
-              <div class="kv-list">
-                <div class="kv"><span class="k">信号日</span><span class="v">{{ allweatherWeights[allweatherWeights.length - 1].signalDate }}</span></div>
-                <div class="kv"><span class="k">沪深300ETF</span><span class="v">{{ (allweatherWeights[allweatherWeights.length - 1].hs300 * 100).toFixed(1) }}%</span></div>
-                <div class="kv"><span class="k">中证500ETF</span><span class="v">{{ (allweatherWeights[allweatherWeights.length - 1].zz500 * 100).toFixed(1) }}%</span></div>
-                <div class="kv"><span class="k">五年国债ETF</span><span class="v">{{ (allweatherWeights[allweatherWeights.length - 1].c5 * 100).toFixed(1) }}%</span></div>
-                <div class="kv"><span class="k">十年国债ETF</span><span class="v">{{ (allweatherWeights[allweatherWeights.length - 1].c10 * 100).toFixed(1) }}%</span></div>
-                <div class="kv"><span class="k">黄金ETF</span><span class="v">{{ (allweatherWeights[allweatherWeights.length - 1].gold * 100).toFixed(1) }}%</span></div>
-                <div class="kv"><span class="k">豆粕ETF</span><span class="v">{{ (allweatherWeights[allweatherWeights.length - 1].meal * 100).toFixed(1) }}%</span></div>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <!-- 调仓权重明细表 -->
-      <div class="card-grid cols-1" style="margin-top:14px">
-        <div class="card">
-          <div class="card-head">
-            <div class="title">
-              <span class="ic">表</span>
-              <span class="title-text">六类 ETF 配置占比变化 · 明细表</span>
-            </div>
-            <div class="acts">
-              <span class="meta">风险平价 · 月末调仓 · 信号日 T-1</span>
-            </div>
-          </div>
-          <div class="card-body">
-            <div class="table-wrap">
-              <table class="weight-tbl aw">
-                <thead>
-                  <tr>
-                    <th>调仓日期</th>
-                    <th>信号日</th>
-                    <th class="num">沪深300</th>
-                    <th class="num">中证500</th>
-                    <th class="num">5年国债</th>
-                    <th class="num">10年国债</th>
-                    <th class="num">黄金</th>
-                    <th class="num">豆粕</th>
-                  </tr>
-                </thead>
-                <tbody>
-                  <tr v-for="(w, i) in allweatherWeights" :key="i">
-                    <td><b>{{ w.date }}</b></td>
-                    <td>{{ w.signalDate }}</td>
-                    <td class="num">{{ (w.hs300 * 100).toFixed(1) }}%</td>
-                    <td class="num">{{ (w.zz500 * 100).toFixed(1) }}%</td>
-                    <td class="num">{{ (w.c5 * 100).toFixed(1) }}%</td>
-                    <td class="num">{{ (w.c10 * 100).toFixed(1) }}%</td>
-                    <td class="num">{{ (w.gold * 100).toFixed(1) }}%</td>
-                    <td class="num">{{ (w.meal * 100).toFixed(1) }}%</td>
-                  </tr>
-                </tbody>
-              </table>
-            </div>
-          </div>
-        </div>
-      </div>
-    </section>
-
     <footer class="footer">
-      数据源:4.2.3-ETF模型-数据底稿.xlsx · ETF配置-净值走势图 / ETF配置-每月调仓权重 / 全天候策略-净值走势图 / 全天候策略-每月调仓权重 · 截至 {{ etfModel.asOf }} / {{ allweatherModel.asOf }}
+      数据源:4.2.3-ETF模型-数据底稿.xlsx · ETF配置-净值走势图 / ETF配置-每月调仓权重 · 截至 {{ etfModel.asOf }}
     </footer>
   </div>
 </template>
@@ -374,20 +202,12 @@ import {
   etfBenchmarkSeries,
   etfWeights,
   etfSummary,
-  allweatherModel,
-  allweatherNetSeries,
-  allweatherWeights,
-  allweatherSummary,
-} from './data'
+} from '../data'
 import {
   etfNetLineOption,
   etfWeightStackOption,
   etfLatestWeightOption,
-  allweatherNetLineOption,
-  allweatherDrawdownOption,
-  allweatherWeightStackOption,
-  allweatherLatestWeightOption,
-} from './charts'
+} from '../charts'
 
 const loading = ref(false)
 
@@ -406,9 +226,6 @@ function summaryCls(total) {
 }
 
 const etfSummaryCls = computed(() => summaryCls(etfSummary.totalReturn))
-const awSummaryCls = computed(() => summaryCls(allweatherSummary.totalReturn))
-const etfSummaryText = computed(() => `${formatPct(etfSummary.totalReturn)}%`)
-const awSummaryText = computed(() => `${formatPct(allweatherSummary.totalReturn)}%`)
 
 function leaderName(w) {
   const max = Math.max(w.coal, w.bank, w.tech)
@@ -430,11 +247,6 @@ const etfNetOpt = computed(() => etfNetLineOption(etfNetSeries, etfBenchmarkSeri
 const etfWeightOpt = computed(() => etfWeightStackOption(etfWeights))
 const etfLatestWeightOpt = computed(() => etfLatestWeightOption(etfWeights))
 
-const awNetOpt = computed(() => allweatherNetLineOption(allweatherNetSeries))
-const awDrawdownOpt = computed(() => allweatherDrawdownOption(allweatherNetSeries))
-const awWeightOpt = computed(() => allweatherWeightStackOption(allweatherWeights))
-const awLatestWeightOpt = computed(() => allweatherLatestWeightOption(allweatherWeights))
-
 function reload() {
   loading.value = true
   setTimeout(() => {
@@ -490,18 +302,6 @@ function reload() {
   border-color: var(--brand, #1552D0);
   color: var(--brand, #1552D0);
 }
-.chip {
-  display: inline-block;
-  padding: 4px 11px;
-  border-radius: 12px;
-  font-size: 12px;
-  font-weight: 600;
-  white-space: nowrap;
-}
-.chip.under    { background: #FDEAEA; color: #E13B3B; }
-.chip.neutral  { background: #EEF2F7; color: #6B7A96; }
-.chip.over     { background: #E4F5EE; color: #189A65; }
-.chip.gold     { background: #FBF1D9; color: #8C6A14; }
 
 .ic-refresh {
   display: inline-block;
@@ -517,7 +317,7 @@ function reload() {
 
 .kpi-grid {
   display: grid;
-  grid-template-columns: repeat(4, minmax(0, 1fr));
+  grid-template-columns: repeat(2, minmax(0, 1fr));
   gap: 10px;
   margin-bottom: 16px;
 }
@@ -616,7 +416,6 @@ function reload() {
   gap: 14px;
 }
 .card-grid.cols-2-1 { grid-template-columns: 2fr 1fr; }
-.card-grid.cols-1-1 { grid-template-columns: 1fr 1fr; }
 .card-grid.cols-1 { grid-template-columns: 1fr; }
 
 .card {
@@ -840,15 +639,11 @@ function reload() {
   border-radius: 8px;
 }
 
-@media (max-width: 1280px) {
-  .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
-}
 @media (max-width: 980px) {
   .kpi-grid { grid-template-columns: 1fr; }
   .desc-body { grid-template-columns: 1fr; }
   .desc-body-cols { grid-template-columns: 1fr; }
   .card-grid.cols-2-1,
-  .card-grid.cols-1-1,
   .card-grid.cols-1 { grid-template-columns: 1fr; }
 }
 </style>