|
@@ -45,11 +45,6 @@
|
|
|
<div class="value">{{ formatPct(stats.up2) }}</div>
|
|
<div class="value">{{ formatPct(stats.up2) }}</div>
|
|
|
<div class="chg">下沿 {{ formatPct(stats.down2) }}</div>
|
|
<div class="chg">下沿 {{ formatPct(stats.down2) }}</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="kpi-card">
|
|
|
|
|
- <div class="label">历史分位</div>
|
|
|
|
|
- <div class="value">{{ formatPctP(pos * 100 - 100) }}</div>
|
|
|
|
|
- <div class="chg">{{ posLabel }}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
</section>
|
|
</section>
|
|
|
|
|
|
|
|
<!-- 隐含利差主图 -->
|
|
<!-- 隐含利差主图 -->
|
|
@@ -80,75 +75,6 @@
|
|
|
</div>
|
|
</div>
|
|
|
</section>
|
|
</section>
|
|
|
|
|
|
|
|
- <!-- 历史分位 + 指标解读 -->
|
|
|
|
|
- <section class="section">
|
|
|
|
|
- <div class="section-hd">
|
|
|
|
|
- <span class="dot"></span>
|
|
|
|
|
- <h2>历史分位区间 · 指标解读</h2>
|
|
|
|
|
- <span class="meta">基于 2017 年以来日度数据 · 百分位越大代表估值越便宜</span>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div class="card-grid cols-2-1">
|
|
|
|
|
- <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">位于 {{ formatPctP(pos * 100 - 100) }} 分位</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="card-body">
|
|
|
|
|
- <Chart :option="gaugeOpt" height="220px" />
|
|
|
|
|
-
|
|
|
|
|
- <div class="band-list">
|
|
|
|
|
- <div
|
|
|
|
|
- v-for="b in bands"
|
|
|
|
|
- :key="b.label"
|
|
|
|
|
- class="band-row"
|
|
|
|
|
- :class="{ on: bandLabel === b.label }"
|
|
|
|
|
- >
|
|
|
|
|
- <div class="band-bar" :style="{ background: b.axisColor }"></div>
|
|
|
|
|
- <div class="band-label">{{ b.label }}</div>
|
|
|
|
|
- <div class="band-range">{{ b.range }}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div class="card">
|
|
|
|
|
- <div class="card-head">
|
|
|
|
|
- <div class="title">
|
|
|
|
|
- <span class="ic">说</span>
|
|
|
|
|
- <span class="title-text">指标解读</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="card-body">
|
|
|
|
|
- <div class="note-block" v-for="n in notes" :key="n.title">
|
|
|
|
|
- <div class="note-t">{{ n.title }}</div>
|
|
|
|
|
- <div class="note-d">{{ n.desc }}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div class="quick-row">
|
|
|
|
|
- <div class="qk">
|
|
|
|
|
- <div class="qk-k">位置结论</div>
|
|
|
|
|
- <div class="qk-v" :style="{ color: bandColor }">{{ bandLabel }}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="qk">
|
|
|
|
|
- <div class="qk-k">距均值差</div>
|
|
|
|
|
- <div class="qk-v">{{ formatPct(latest.value - stats.mean) }}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="qk">
|
|
|
|
|
- <div class="qk-k">距 -1σ 差</div>
|
|
|
|
|
- <div class="qk-v">{{ formatPct(latest.value - stats.down1) }}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </section>
|
|
|
|
|
-
|
|
|
|
|
<footer class="footer">
|
|
<footer class="footer">
|
|
|
数据源:4.3-多资产分析.xlsx · 1-转债 sheet · 数据截至 {{ latest.date }} · 转债隐含利差反映转债相对纯债的估值溢价
|
|
数据源:4.3-多资产分析.xlsx · 1-转债 sheet · 数据截至 {{ latest.date }} · 转债隐含利差反映转债相对纯债的估值溢价
|
|
|
</footer>
|
|
</footer>
|
|
@@ -163,36 +89,19 @@ import {
|
|
|
BOND_STATS as stats,
|
|
BOND_STATS as stats,
|
|
|
BOND_LATEST as latest,
|
|
BOND_LATEST as latest,
|
|
|
BOND_PREV as prev,
|
|
BOND_PREV as prev,
|
|
|
- BOND_HISTORICAL_VALUES,
|
|
|
|
|
- BOND_NOTES as notes,
|
|
|
|
|
- classifyPercentile,
|
|
|
|
|
} from './data'
|
|
} from './data'
|
|
|
-import {
|
|
|
|
|
- impliedSpreadSeriesOption,
|
|
|
|
|
- percentileGaugeOption,
|
|
|
|
|
-} from './charts'
|
|
|
|
|
|
|
+import { impliedSpreadSeriesOption } from './charts'
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
const loading = ref(false)
|
|
|
|
|
|
|
|
-// 计算历史分位
|
|
|
|
|
-const percentile = computed(() => classifyPercentile(latest.value, BOND_HISTORICAL_VALUES))
|
|
|
|
|
-const pos = computed(() => percentile.value.pos)
|
|
|
|
|
-const posLabel = computed(() => {
|
|
|
|
|
- if (pos.value < 0.1) return '历史最低 10% 区间内'
|
|
|
|
|
- if (pos.value < 0.25) return '历史 10%-25% 区间内'
|
|
|
|
|
- if (pos.value < 0.5) return '历史 25%-50% 区间内'
|
|
|
|
|
- if (pos.value < 0.75) return '历史 50%-75% 区间内'
|
|
|
|
|
- return '历史 75% 分位以上(估值最便宜)'
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
// 区间标签(按当前隐含利差 vs 标准差区间)
|
|
// 区间标签(按当前隐含利差 vs 标准差区间)
|
|
|
const BAND_DEFS = [
|
|
const BAND_DEFS = [
|
|
|
- { lo: -Infinity, hi: stats.down2, label: '极低估', cls: 'deep-low', bg: '#0BB0A2', fg: '#0A8A7E', axisColor: '#0BB0A2', range: '< -2σ' },
|
|
|
|
|
- { lo: stats.down2, hi: stats.down1, label: '低估', cls: 'low', bg: '#E4F5EE', fg: '#189A65', axisColor: '#189A65', range: '[-2σ, -1σ)' },
|
|
|
|
|
- { lo: stats.down1, hi: stats.mean, label: '中性偏低', cls: 'mid-low', bg: '#EEF2F7', fg: '#6B7A96', axisColor: '#6B7A96', range: '[-1σ, 均值)' },
|
|
|
|
|
- { lo: stats.mean, hi: stats.up1, label: '中性偏高', cls: 'mid-high', bg: '#FFF4E5', fg: '#E8912D', axisColor: '#E8912D', range: '[均值, +1σ)' },
|
|
|
|
|
- { lo: stats.up1, hi: stats.up2, label: '高估', cls: 'high', bg: '#FDEAEA', fg: '#E13B3B', axisColor: '#E13B3B', range: '[+1σ, +2σ)' },
|
|
|
|
|
- { lo: stats.up2, hi: Infinity, label: '极高估', cls: 'deep-high', bg: '#B72828', fg: '#fff', axisColor: '#B72828', range: '≥ +2σ' },
|
|
|
|
|
|
|
+ { lo: -Infinity, hi: stats.down2, label: '极低估', cls: 'deep-low' },
|
|
|
|
|
+ { lo: stats.down2, hi: stats.down1, label: '低估', cls: 'low' },
|
|
|
|
|
+ { lo: stats.down1, hi: stats.mean, label: '中性偏低', cls: 'mid-low' },
|
|
|
|
|
+ { lo: stats.mean, hi: stats.up1, label: '中性偏高', cls: 'mid-high' },
|
|
|
|
|
+ { lo: stats.up1, hi: stats.up2, label: '高估', cls: 'high' },
|
|
|
|
|
+ { lo: stats.up2, hi: Infinity, label: '极高估', cls: 'deep-high' },
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
function classify(v) {
|
|
function classify(v) {
|
|
@@ -203,13 +112,6 @@ function classify(v) {
|
|
|
const currentBand = computed(() => classify(latest.value))
|
|
const currentBand = computed(() => classify(latest.value))
|
|
|
const bandCls = computed(() => currentBand.value.cls)
|
|
const bandCls = computed(() => currentBand.value.cls)
|
|
|
const bandLabel = computed(() => currentBand.value.label)
|
|
const bandLabel = computed(() => currentBand.value.label)
|
|
|
-const bandColor = computed(() => currentBand.value.fg)
|
|
|
|
|
-
|
|
|
|
|
-const bands = BAND_DEFS.map((b) => ({
|
|
|
|
|
- label: b.label,
|
|
|
|
|
- range: b.range,
|
|
|
|
|
- axisColor: b.axisColor,
|
|
|
|
|
-}))
|
|
|
|
|
|
|
|
|
|
const arrow = computed(() => {
|
|
const arrow = computed(() => {
|
|
|
const d = +(latest.value - prev.value).toFixed(4)
|
|
const d = +(latest.value - prev.value).toFixed(4)
|
|
@@ -225,14 +127,8 @@ function formatPct(v) {
|
|
|
if (v == null) return '—'
|
|
if (v == null) return '—'
|
|
|
return (v * 100).toFixed(2) + '%'
|
|
return (v * 100).toFixed(2) + '%'
|
|
|
}
|
|
}
|
|
|
-function formatPctP(v) {
|
|
|
|
|
- if (v == null) return '—'
|
|
|
|
|
- // v is the relative position (e.g. -52.3 means 52.3% from low)
|
|
|
|
|
- return Math.abs(v).toFixed(1) + '%'
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
const seriesOpt = computed(() => impliedSpreadSeriesOption(BOND_RECENT, latest))
|
|
const seriesOpt = computed(() => impliedSpreadSeriesOption(BOND_RECENT, latest))
|
|
|
-const gaugeOpt = computed(() => percentileGaugeOption(latest.value, BOND_HISTORICAL_VALUES))
|
|
|
|
|
|
|
|
|
|
function reload() {
|
|
function reload() {
|
|
|
loading.value = true
|
|
loading.value = true
|
|
@@ -314,7 +210,7 @@ function reload() {
|
|
|
|
|
|
|
|
.kpi-grid {
|
|
.kpi-grid {
|
|
|
display: grid;
|
|
display: grid;
|
|
|
- grid-template-columns: 1.4fr repeat(4, 1fr);
|
|
|
|
|
|
|
+ grid-template-columns: 1.4fr repeat(3, 1fr);
|
|
|
gap: 10px;
|
|
gap: 10px;
|
|
|
margin-bottom: 16px;
|
|
margin-bottom: 16px;
|
|
|
}
|
|
}
|
|
@@ -410,7 +306,6 @@ function reload() {
|
|
|
display: grid;
|
|
display: grid;
|
|
|
gap: 14px;
|
|
gap: 14px;
|
|
|
}
|
|
}
|
|
|
-.card-grid.cols-2-1 { grid-template-columns: 2fr 1fr; }
|
|
|
|
|
.card-grid.cols-1 { grid-template-columns: 1fr; }
|
|
.card-grid.cols-1 { grid-template-columns: 1fr; }
|
|
|
.card-grid.cols-2 { grid-template-columns: 1fr 1fr; }
|
|
.card-grid.cols-2 { grid-template-columns: 1fr 1fr; }
|
|
|
|
|
|
|
@@ -471,84 +366,6 @@ function reload() {
|
|
|
min-height: 0;
|
|
min-height: 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.band-list {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 6px;
|
|
|
|
|
- margin-top: 12px;
|
|
|
|
|
-}
|
|
|
|
|
-.band-row {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 10px;
|
|
|
|
|
- padding: 7px 10px;
|
|
|
|
|
- border-radius: 6px;
|
|
|
|
|
- background: #FAFBFE;
|
|
|
|
|
- border: 1px solid transparent;
|
|
|
|
|
- transition: 0.15s;
|
|
|
|
|
-}
|
|
|
|
|
-.band-row.on {
|
|
|
|
|
- background: #fff;
|
|
|
|
|
- border-color: var(--brand-lt, #E0F7F4);
|
|
|
|
|
- box-shadow: 0 1px 3px rgba(16,32,64,0.06);
|
|
|
|
|
-}
|
|
|
|
|
-.band-bar {
|
|
|
|
|
- width: 4px;
|
|
|
|
|
- height: 18px;
|
|
|
|
|
- border-radius: 2px;
|
|
|
|
|
-}
|
|
|
|
|
-.band-label {
|
|
|
|
|
- font-size: 12.5px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- color: var(--ink);
|
|
|
|
|
- flex: 1;
|
|
|
|
|
-}
|
|
|
|
|
-.band-range {
|
|
|
|
|
- font-size: 11.5px;
|
|
|
|
|
- color: var(--mut, #6B7A96);
|
|
|
|
|
- font-variant-numeric: tabular-nums;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.note-block {
|
|
|
|
|
- margin-bottom: 12px;
|
|
|
|
|
-}
|
|
|
|
|
-.note-t {
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- color: var(--ink, #1A2438);
|
|
|
|
|
- margin-bottom: 4px;
|
|
|
|
|
-}
|
|
|
|
|
-.note-d {
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- color: var(--mut, #6B7A96);
|
|
|
|
|
- line-height: 1.6;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.quick-row {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
- margin-top: 14px;
|
|
|
|
|
- padding-top: 12px;
|
|
|
|
|
- border-top: 1px dashed var(--line, #E4E9F2);
|
|
|
|
|
-}
|
|
|
|
|
-.qk {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- background: #FAFBFE;
|
|
|
|
|
- padding: 9px 10px;
|
|
|
|
|
- border-radius: 6px;
|
|
|
|
|
-}
|
|
|
|
|
-.qk-k {
|
|
|
|
|
- font-size: 11px;
|
|
|
|
|
- color: var(--mut, #6B7A96);
|
|
|
|
|
- margin-bottom: 4px;
|
|
|
|
|
-}
|
|
|
|
|
-.qk-v {
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- color: var(--ink, #1A2438);
|
|
|
|
|
- font-variant-numeric: tabular-nums;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
.footer {
|
|
.footer {
|
|
|
padding: 18px 24px 12px;
|
|
padding: 18px 24px 12px;
|
|
|
color: #98A5BD;
|
|
color: #98A5BD;
|
|
@@ -566,7 +383,6 @@ function reload() {
|
|
|
}
|
|
}
|
|
|
@media (max-width: 980px) {
|
|
@media (max-width: 980px) {
|
|
|
.kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
.kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
|
- .card-grid.cols-2-1,
|
|
|
|
|
.card-grid.cols-2 { grid-template-columns: 1fr; }
|
|
.card-grid.cols-2 { grid-template-columns: 1fr; }
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|