|
|
<!DOCTYPE html> |
|
|
<html lang="zh-CN"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>财顾报价通 - 理财公司预期收益率报价系统</title> |
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
|
|
<style> |
|
|
:root { |
|
|
--primary-color: #2c3e50; |
|
|
--secondary-color: #3498db; |
|
|
--success-color: #27ae60; |
|
|
--warning-color: #f39c12; |
|
|
--danger-color: #e74c3c; |
|
|
--light-color: #ecf0f1; |
|
|
--dark-color: #34495e; |
|
|
} |
|
|
|
|
|
* { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
box-sizing: border-box; |
|
|
} |
|
|
|
|
|
body { |
|
|
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif; |
|
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); |
|
|
color: #333; |
|
|
line-height: 1.6; |
|
|
min-height: 100vh; |
|
|
} |
|
|
|
|
|
.container { |
|
|
max-width: 1200px; |
|
|
margin: 0 auto; |
|
|
padding: 20px; |
|
|
} |
|
|
|
|
|
header { |
|
|
background: white; |
|
|
padding: 20px; |
|
|
border-radius: 10px; |
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1); |
|
|
margin-bottom: 30px; |
|
|
text-align: center; |
|
|
} |
|
|
|
|
|
.logo { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
gap: 15px; |
|
|
margin-bottom: 10px; |
|
|
} |
|
|
|
|
|
.logo h1 { |
|
|
color: var(--primary-color); |
|
|
font-size: 2.2rem; |
|
|
} |
|
|
|
|
|
.subtitle { |
|
|
color: var(--dark-color); |
|
|
opacity: 0.8; |
|
|
} |
|
|
|
|
|
.calculator { |
|
|
display: grid; |
|
|
grid-template-columns: 1fr 1fr; |
|
|
gap: 30px; |
|
|
margin-bottom: 40px; |
|
|
} |
|
|
|
|
|
@media (max-width: 768px) { |
|
|
.calculator { |
|
|
grid-template-columns: 1fr; |
|
|
} |
|
|
} |
|
|
|
|
|
.panel { |
|
|
background: white; |
|
|
padding: 25px; |
|
|
border-radius: 10px; |
|
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1); |
|
|
} |
|
|
|
|
|
.panel h2 { |
|
|
color: var(--primary-color); |
|
|
margin-bottom: 20px; |
|
|
padding-bottom: 15px; |
|
|
border-bottom: 2px solid var(--light-color); |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 10px; |
|
|
} |
|
|
|
|
|
.form-group { |
|
|
margin-bottom: 25px; |
|
|
} |
|
|
|
|
|
.form-group label { |
|
|
display: block; |
|
|
margin-bottom: 10px; |
|
|
font-weight: 600; |
|
|
color: var(--dark-color); |
|
|
font-size: 1.1rem; |
|
|
} |
|
|
|
|
|
.button-group { |
|
|
display: flex; |
|
|
gap: 10px; |
|
|
flex-wrap: wrap; |
|
|
} |
|
|
|
|
|
.btn { |
|
|
padding: 12px 20px; |
|
|
border: 2px solid #ddd; |
|
|
background: white; |
|
|
color: var(--dark-color); |
|
|
border-radius: 6px; |
|
|
cursor: pointer; |
|
|
font-weight: 500; |
|
|
transition: all 0.3s ease; |
|
|
flex: 1; |
|
|
min-width: 120px; |
|
|
text-align: center; |
|
|
} |
|
|
|
|
|
.btn:hover { |
|
|
border-color: var(--secondary-color); |
|
|
color: var(--secondary-color); |
|
|
} |
|
|
|
|
|
.btn.active { |
|
|
background: var(--secondary-color); |
|
|
color: white; |
|
|
border-color: var(--secondary-color); |
|
|
} |
|
|
|
|
|
.calculate-btn { |
|
|
width: 100%; |
|
|
padding: 16px; |
|
|
background: linear-gradient(135deg, var(--secondary-color), #2980b9); |
|
|
color: white; |
|
|
border: none; |
|
|
border-radius: 8px; |
|
|
font-size: 1.1rem; |
|
|
font-weight: 600; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s ease; |
|
|
margin-top: 10px; |
|
|
} |
|
|
|
|
|
.calculate-btn:hover { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 5px 15px rgba(0,0,0,0.2); |
|
|
} |
|
|
|
|
|
.preview-item { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
margin-bottom: 15px; |
|
|
padding-bottom: 15px; |
|
|
border-bottom: 1px solid var(--light-color); |
|
|
} |
|
|
|
|
|
.preview-label { |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
.preview-value { |
|
|
font-weight: 600; |
|
|
color: var(--secondary-color); |
|
|
} |
|
|
|
|
|
.result-section { |
|
|
background: white; |
|
|
padding: 30px; |
|
|
border-radius: 10px; |
|
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1); |
|
|
margin-top: 30px; |
|
|
} |
|
|
|
|
|
.result-grid { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); |
|
|
gap: 25px; |
|
|
margin: 25px 0; |
|
|
} |
|
|
|
|
|
.result-card { |
|
|
background: #f8f9fa; |
|
|
padding: 20px; |
|
|
border-radius: 8px; |
|
|
border-left: 4px solid var(--secondary-color); |
|
|
} |
|
|
|
|
|
.result-item { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
margin-bottom: 12px; |
|
|
} |
|
|
|
|
|
.highlight { |
|
|
color: var(--success-color); |
|
|
font-weight: 600; |
|
|
font-size: 1.2rem; |
|
|
} |
|
|
|
|
|
.loading { |
|
|
text-align: center; |
|
|
padding: 30px; |
|
|
} |
|
|
|
|
|
.spinner { |
|
|
width: 40px; |
|
|
height: 40px; |
|
|
border: 4px solid #f3f3f3; |
|
|
border-top: 4px solid var(--secondary-color); |
|
|
border-radius: 50%; |
|
|
animation: spin 1s linear infinite; |
|
|
margin: 0 auto 15px; |
|
|
} |
|
|
|
|
|
@keyframes spin { |
|
|
0% { transform: rotate(0deg); } |
|
|
100% { transform: rotate(360deg); } |
|
|
} |
|
|
|
|
|
.hidden { |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.feature-list { |
|
|
list-style: none; |
|
|
padding-left: 0; |
|
|
} |
|
|
|
|
|
.feature-list li { |
|
|
padding: 8px 0; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 10px; |
|
|
} |
|
|
|
|
|
.feature-list li i { |
|
|
color: var(--success-color); |
|
|
} |
|
|
|
|
|
footer { |
|
|
text-align: center; |
|
|
margin-top: 40px; |
|
|
padding-top: 20px; |
|
|
border-top: 1px solid #ddd; |
|
|
color: #666; |
|
|
font-size: 0.9rem; |
|
|
} |
|
|
|
|
|
.contribution-breakdown { |
|
|
background: #f0f8ff; |
|
|
padding: 15px; |
|
|
border-radius: 6px; |
|
|
margin-top: 15px; |
|
|
} |
|
|
|
|
|
.contribution-item { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
margin-bottom: 8px; |
|
|
font-size: 0.95rem; |
|
|
} |
|
|
|
|
|
.range-indicator { |
|
|
display: inline-block; |
|
|
padding: 2px 8px; |
|
|
border-radius: 4px; |
|
|
font-size: 0.85rem; |
|
|
margin-left: 5px; |
|
|
} |
|
|
|
|
|
.range-pessimistic { |
|
|
background: #ffebee; |
|
|
color: #c62828; |
|
|
} |
|
|
|
|
|
.range-neutral { |
|
|
background: #fff3e0; |
|
|
color: #ef6c00; |
|
|
} |
|
|
|
|
|
.range-optimistic { |
|
|
background: #e8f5e9; |
|
|
color: #2e7d32; |
|
|
} |
|
|
|
|
|
.bp-unit { |
|
|
font-size: 0.85rem; |
|
|
color: #666; |
|
|
margin-left: 2px; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<header> |
|
|
<div class="logo"> |
|
|
<h1>财顾报价通</h1> |
|
|
</div> |
|
|
<p class="subtitle">理财公司预期收益率报价系统 - 增强版</p> |
|
|
</header> |
|
|
|
|
|
<div class="calculator"> |
|
|
<div class="panel"> |
|
|
<h2>⚙️ 参数设置</h2> |
|
|
|
|
|
<div class="form-group"> |
|
|
<label>策略类型</label> |
|
|
<div class="button-group"> |
|
|
<button class="btn active" data-type="strategy" data-value="PURE_BOND">纯债策略</button> |
|
|
<button class="btn" data-type="strategy" data-value="FIXED_INCOME_PLUS">固收+策略</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="form-group"> |
|
|
<label>投资期限</label> |
|
|
<div class="button-group"> |
|
|
<button class="btn active" data-type="period" data-value="SHORT">短期(0-3个月)</button> |
|
|
<button class="btn" data-type="period" data-value="MEDIUM">中期(3-6个月)</button> |
|
|
<button class="btn" data-type="period" data-value="LONG">长期(6-12个月)</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="form-group"> |
|
|
<label>风险风格</label> |
|
|
<div class="button-group"> |
|
|
<button class="btn active" data-type="risk" data-value="CONSERVATIVE">保守型</button> |
|
|
<button class="btn" data-type="risk" data-value="NEUTRAL">中性型</button> |
|
|
<button class="btn" data-type="risk" data-value="AGGRESSIVE">激进型</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="plus-section" class="form-group hidden"> |
|
|
<label>增强资产类别</label> |
|
|
<div class="button-group"> |
|
|
<button class="btn active" data-type="asset" data-value="CONVERTIBLE_BOND">转债</button> |
|
|
<button class="btn" data-type="asset" data-value="EQUITY">权益</button> |
|
|
<button class="btn" data-type="asset" data-value="REITS">REITs</button> |
|
|
</div> |
|
|
|
|
|
<label style="margin-top: 20px; display: block;">资产预期</label> |
|
|
<div class="button-group"> |
|
|
<button class="btn" data-type="expectation" data-value="PESSIMISTIC">悲观</button> |
|
|
<button class="btn active" data-type="expectation" data-value="NEUTRAL">中性</button> |
|
|
<button class="btn" data-type="expectation" data-value="OPTIMISTIC">乐观</button> |
|
|
</div> |
|
|
<div style="margin-top: 10px; font-size: 0.9rem; color: #666; padding: 8px; background: #f5f5f5; border-radius: 4px;"> |
|
|
<div><strong>配置比例:</strong> 悲观(95%债+5%资产) | 中性(90%债+10%资产) | 乐观(80%债+20%资产)</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<button id="calculate" class="calculate-btn">🚀 计算预期收益率</button> |
|
|
</div> |
|
|
|
|
|
<div class="panel"> |
|
|
<h2>👁️ 参数预览</h2> |
|
|
<div id="preview"> |
|
|
<div class="preview-item"> |
|
|
<span class="preview-label">策略类型:</span> |
|
|
<span id="preview-strategy" class="preview-value">纯债策略</span> |
|
|
</div> |
|
|
<div class="preview-item"> |
|
|
<span class="preview-label">投资期限:</span> |
|
|
<span id="preview-period" class="preview-value">短期(0-3个月)</span> |
|
|
</div> |
|
|
<div class="preview-item"> |
|
|
<span class="preview-label">风险风格:</span> |
|
|
<span id="preview-risk" class="preview-value">保守型</span> |
|
|
</div> |
|
|
<div class="preview-item hidden" id="preview-asset-item"> |
|
|
<span class="preview-label">增强资产:</span> |
|
|
<span id="preview-asset" class="preview-value">转债</span> |
|
|
</div> |
|
|
<div class="preview-item hidden" id="preview-expectation-item"> |
|
|
<span class="preview-label">资产预期:</span> |
|
|
<span id="preview-expectation" class="preview-value">中性</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<h2 style="margin-top: 30px;">✨ 系统特点</h2> |
|
|
<ul class="feature-list"> |
|
|
<li><i>✓</i> 基于量化模型计算</li> |
|
|
<li><i>✓</i> 正态分布资本利得模型</li> |
|
|
<li><i>✓</i> 详细收益贡献分解</li> |
|
|
<li><i>✓</i> 市场实际参考区间</li> |
|
|
<li><i>✓</i> 纯前端计算,保护隐私</li> |
|
|
</ul> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="loading" class="loading hidden"> |
|
|
<div class="spinner"></div> |
|
|
<p>正在计算预期收益率...</p> |
|
|
</div> |
|
|
|
|
|
<div id="result" class="result-section hidden"> |
|
|
<h2>📊 报价结果详情</h2> |
|
|
<div id="result-content"></div> |
|
|
</div> |
|
|
|
|
|
<footer> |
|
|
<p>© 2024 财顾报价通 | 仅供演示使用 | 计算结果仅供参考,不构成投资建议</p> |
|
|
</footer> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
const state = { |
|
|
strategy: 'PURE_BOND', |
|
|
period: 'SHORT', |
|
|
risk: 'CONSERVATIVE', |
|
|
asset: 'CONVERTIBLE_BOND', |
|
|
expectation: 'NEUTRAL', |
|
|
|
|
|
updatePreview() { |
|
|
document.getElementById('preview-strategy').textContent = |
|
|
this.strategy === 'PURE_BOND' ? '纯债策略' : '固收+策略'; |
|
|
document.getElementById('preview-period').textContent = |
|
|
this.getPeriodLabel(this.period); |
|
|
document.getElementById('preview-risk').textContent = |
|
|
this.getRiskLabel(this.risk); |
|
|
|
|
|
const plusSection = document.getElementById('plus-section'); |
|
|
const assetPreview = document.getElementById('preview-asset-item'); |
|
|
const expectationPreview = document.getElementById('preview-expectation-item'); |
|
|
|
|
|
if (this.strategy === 'FIXED_INCOME_PLUS') { |
|
|
plusSection.classList.remove('hidden'); |
|
|
assetPreview.classList.remove('hidden'); |
|
|
expectationPreview.classList.remove('hidden'); |
|
|
|
|
|
document.getElementById('preview-asset').textContent = |
|
|
this.getAssetLabel(this.asset); |
|
|
document.getElementById('preview-expectation').textContent = |
|
|
this.getExpectationLabel(this.expectation); |
|
|
} else { |
|
|
plusSection.classList.add('hidden'); |
|
|
assetPreview.classList.add('hidden'); |
|
|
expectationPreview.classList.add('hidden'); |
|
|
} |
|
|
}, |
|
|
|
|
|
getPeriodLabel(value) { |
|
|
const labels = { |
|
|
'SHORT': '短期(0-3个月)', |
|
|
'MEDIUM': '中期(3-6个月)', |
|
|
'LONG': '长期(6-12个月)' |
|
|
}; |
|
|
return labels[value]; |
|
|
}, |
|
|
|
|
|
getRiskLabel(value) { |
|
|
const labels = { |
|
|
'CONSERVATIVE': '保守型', |
|
|
'NEUTRAL': '中性型', |
|
|
'AGGRESSIVE': '激进型' |
|
|
}; |
|
|
return labels[value]; |
|
|
}, |
|
|
|
|
|
getAssetLabel(value) { |
|
|
const labels = { |
|
|
'CONVERTIBLE_BOND': '转债', |
|
|
'EQUITY': '权益', |
|
|
'REITS': 'REITs' |
|
|
}; |
|
|
return labels[value]; |
|
|
}, |
|
|
|
|
|
getExpectationLabel(value) { |
|
|
const labels = { |
|
|
'PESSIMISTIC': '悲观', |
|
|
'NEUTRAL': '中性', |
|
|
'OPTIMISTIC': '乐观' |
|
|
}; |
|
|
return labels[value]; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
function normalCDF(x, mean = 0, std = 1) { |
|
|
return 0.5 * (1 + erf((x - mean) / (std * Math.sqrt(2)))); |
|
|
} |
|
|
|
|
|
function erf(x) { |
|
|
const a1 = 0.254829592; |
|
|
const a2 = -0.284496736; |
|
|
const a3 = 1.421413741; |
|
|
const a4 = -1.453152027; |
|
|
const a5 = 1.061405429; |
|
|
const p = 0.3275911; |
|
|
|
|
|
const sign = (x >= 0) ? 1 : -1; |
|
|
x = Math.abs(x); |
|
|
|
|
|
const t = 1.0 / (1.0 + p * x); |
|
|
const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x); |
|
|
|
|
|
return sign * y; |
|
|
} |
|
|
|
|
|
function normalQuantile(p, mean = 0, std = 1) { |
|
|
if (p <= 0 || p >= 1) return mean; |
|
|
|
|
|
const a1 = -39.6968302866538, a2 = 220.946098424521, a3 = -275.928510446969; |
|
|
const a4 = 138.357751867269, a5 = -30.6647980661472, a6 = 2.50662827745924; |
|
|
|
|
|
const b1 = -54.4760987982241, b2 = 161.585836858041, b3 = -155.698979859887; |
|
|
const b4 = 66.8013118877197, b5 = -13.2806815528857; |
|
|
|
|
|
const c1 = -7.78489400243029E-03, c2 = -0.322396458041136, c3 = -2.40075827716184; |
|
|
const c4 = -2.54973253934373, c5 = 4.37466414146497, c6 = 2.93816398269878; |
|
|
|
|
|
const d1 = 7.78469570904146E-03, d2 = 0.32246712907004, d3 = 2.445134137143; |
|
|
const d4 = 3.75440866190742; |
|
|
|
|
|
let q, r; |
|
|
|
|
|
if (p < 0.02425) { |
|
|
q = Math.sqrt(-2 * Math.log(p)); |
|
|
return mean + std * (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / |
|
|
((((d1 * q + d2) * q + d3) * q + d4) * q + 1); |
|
|
} else if (p <= 0.97575) { |
|
|
q = p - 0.5; |
|
|
r = q * q; |
|
|
return mean + std * (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q / |
|
|
(((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1); |
|
|
} else { |
|
|
q = Math.sqrt(-2 * Math.log(1 - p)); |
|
|
return mean + std * -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / |
|
|
((((d1 * q + d2) * q + d3) * q + d4) * q + 1); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function calculateCapitalGainRange(period) { |
|
|
|
|
|
let cycles; |
|
|
switch(period) { |
|
|
case 'SHORT': cycles = 1; break; |
|
|
case 'MEDIUM': cycles = 1.5; break; |
|
|
case 'LONG': cycles = 3; break; |
|
|
default: cycles = 1; |
|
|
} |
|
|
|
|
|
|
|
|
const singleStd = 18; |
|
|
const singleMean = 0; |
|
|
|
|
|
|
|
|
const annualMean = singleMean * cycles; |
|
|
const annualStd = singleStd * Math.sqrt(cycles); |
|
|
|
|
|
|
|
|
const p80 = normalQuantile(0.8, annualMean, annualStd); |
|
|
|
|
|
return { |
|
|
lower: 0, |
|
|
upper: p80 / 100 |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
document.querySelectorAll('.btn').forEach(btn => { |
|
|
btn.addEventListener('click', function() { |
|
|
const type = this.dataset.type; |
|
|
const value = this.dataset.value; |
|
|
|
|
|
document.querySelectorAll(`.btn[data-type="${type}"]`).forEach(b => { |
|
|
b.classList.remove('active'); |
|
|
}); |
|
|
|
|
|
this.classList.add('active'); |
|
|
|
|
|
state[type] = value; |
|
|
state.updatePreview(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
document.getElementById('calculate').addEventListener('click', calculateQuotation); |
|
|
}); |
|
|
|
|
|
|
|
|
function calculateQuotation() { |
|
|
document.getElementById('loading').classList.remove('hidden'); |
|
|
document.getElementById('result').classList.add('hidden'); |
|
|
|
|
|
setTimeout(() => { |
|
|
try { |
|
|
const result = performCalculation(); |
|
|
displayResults(result); |
|
|
} catch (error) { |
|
|
alert('计算错误: ' + error.message); |
|
|
} finally { |
|
|
document.getElementById('loading').classList.add('hidden'); |
|
|
} |
|
|
}, 800); |
|
|
} |
|
|
|
|
|
|
|
|
function performCalculation() { |
|
|
const { strategy, period, risk, asset, expectation } = state; |
|
|
|
|
|
|
|
|
const defaultReturns = { |
|
|
"7d_notice_deposit": 1.35, |
|
|
"3m_state_ncd": 1.55, |
|
|
"overnight_rate": 1.28, |
|
|
|
|
|
"gov_bond_under_1y": 1.57, |
|
|
"gov_bond_under_2y": 1.60, |
|
|
"gov_bond_under_3y": 1.70, |
|
|
"gov_bond_under_5y": 1.88, |
|
|
|
|
|
"ncd_1y_state": 1.62, |
|
|
"ncd_1y_aaa": 1.65, |
|
|
"ncd_1y_aa": 1.71, |
|
|
|
|
|
"credit_1y_aaa_plus": 1.7, |
|
|
"credit_1y_aaa": 1.81, |
|
|
"credit_1y_aa": 2.1, |
|
|
"credit_2y_aaa_plus": 1.8, |
|
|
"credit_2y_aaa": 2.09, |
|
|
"credit_2y_aa": 2.54, |
|
|
}; |
|
|
|
|
|
|
|
|
const assetReturnRanges = { |
|
|
'CONVERTIBLE_BOND': { |
|
|
'PESSIMISTIC': { min: -5, max: 0 }, |
|
|
'NEUTRAL': { min: 0, max: 6 }, |
|
|
'OPTIMISTIC': { min: 6, max: 12 } |
|
|
}, |
|
|
'EQUITY': { |
|
|
'PESSIMISTIC': { min: -10, max: 0 }, |
|
|
'NEUTRAL': { min: 0, max: 10 }, |
|
|
'OPTIMISTIC': { min: 10, max: 20 } |
|
|
}, |
|
|
'REITS': { |
|
|
'PESSIMISTIC': { min: -5, max: -1 }, |
|
|
'NEUTRAL': { min: -1, max: 3 }, |
|
|
'OPTIMISTIC': { min: 3, max: 7 } |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const marketRef = { |
|
|
pure_bond: { |
|
|
'SHORT': [1.24, 1.40], |
|
|
'MEDIUM': [1.74, 3.00], |
|
|
'LONG': [2.05, 3.24] |
|
|
}, |
|
|
fixed_income_plus: { |
|
|
'SHORT': [1.24, 1.60], |
|
|
'MEDIUM': [1.84, 2.94], |
|
|
'LONG': [2.18, 3.03] |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
function calculateBaseReturn() { |
|
|
let baseReturn = 0; |
|
|
let leverage = 100; |
|
|
|
|
|
if (period === 'SHORT') { |
|
|
|
|
|
if (risk === 'CONSERVATIVE') { |
|
|
|
|
|
baseReturn = defaultReturns['7d_notice_deposit'] * 0.5 + |
|
|
defaultReturns['3m_state_ncd'] * 0.2 + |
|
|
defaultReturns['overnight_rate'] * 0.3; |
|
|
} else if (risk === 'NEUTRAL') { |
|
|
|
|
|
baseReturn = defaultReturns['7d_notice_deposit'] * 0.4 + |
|
|
defaultReturns['3m_state_ncd'] * 0.35 + |
|
|
defaultReturns['overnight_rate'] * 0.25; |
|
|
} else { |
|
|
|
|
|
baseReturn = defaultReturns['7d_notice_deposit'] * 0.3 + |
|
|
defaultReturns['3m_state_ncd'] * 0.5 + |
|
|
defaultReturns['overnight_rate'] * 0.2; |
|
|
} |
|
|
} else if (period === 'MEDIUM') { |
|
|
|
|
|
if (risk === 'CONSERVATIVE') { |
|
|
|
|
|
|
|
|
baseReturn = defaultReturns['gov_bond_under_2y'] * 0.15 + |
|
|
defaultReturns['credit_1y_aaa_plus'] * 0.15 + |
|
|
defaultReturns['credit_1y_aaa'] * 0.2 + |
|
|
defaultReturns['credit_1y_aa'] * 0.5; |
|
|
leverage = 100; |
|
|
} else if (risk === 'NEUTRAL') { |
|
|
|
|
|
|
|
|
baseReturn = defaultReturns['gov_bond_under_2y'] * 0.2 + |
|
|
defaultReturns['credit_1y_aaa_plus'] * 0.1 + |
|
|
defaultReturns['credit_1y_aaa'] * 0.1 + |
|
|
defaultReturns['credit_1y_aa'] * 0.6; |
|
|
leverage = 120; |
|
|
} else { |
|
|
|
|
|
|
|
|
baseReturn = defaultReturns['gov_bond_under_2y'] * 0.2 + |
|
|
defaultReturns['credit_1y_aaa'] * 0.1 + |
|
|
defaultReturns['credit_1y_aa'] * 0.7; |
|
|
leverage = 140; |
|
|
} |
|
|
} else { |
|
|
if (risk === 'CONSERVATIVE') { |
|
|
|
|
|
|
|
|
baseReturn = defaultReturns['gov_bond_under_5y'] * 0.15 + |
|
|
defaultReturns['credit_2y_aaa_plus'] * 0.15 + |
|
|
defaultReturns['credit_2y_aaa'] * 0.2 + |
|
|
defaultReturns['credit_2y_aa'] * 0.5; |
|
|
leverage = 100; |
|
|
} else if (risk === 'NEUTRAL') { |
|
|
|
|
|
|
|
|
baseReturn = defaultReturns['gov_bond_under_5y'] * 0.2 + |
|
|
defaultReturns['credit_2y_aaa_plus'] * 0.15 + |
|
|
defaultReturns['credit_2y_aaa'] * 0.15 + |
|
|
defaultReturns['credit_2y_aa'] * 0.5; |
|
|
leverage = 120; |
|
|
} else { |
|
|
|
|
|
|
|
|
baseReturn = defaultReturns['gov_bond_under_5y'] * 0.2 + |
|
|
defaultReturns['credit_2y_aaa'] * 0.1 + |
|
|
defaultReturns['credit_2y_aa'] * 0.7; |
|
|
leverage = 140; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const fundingCost = defaultReturns['overnight_rate']; |
|
|
let leveredReturn = baseReturn; |
|
|
|
|
|
if (leverage > 100) { |
|
|
const L = leverage / 100; |
|
|
leveredReturn = L * baseReturn - (L - 1) * fundingCost; |
|
|
} |
|
|
|
|
|
return { |
|
|
baseReturn: leveredReturn, |
|
|
leverage: leverage, |
|
|
fundingCost: fundingCost |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
if (strategy === 'PURE_BOND') { |
|
|
const bondResult = calculateBaseReturn(); |
|
|
const capGain = calculateCapitalGainRange(period); |
|
|
|
|
|
|
|
|
const totalLower = bondResult.baseReturn + capGain.lower; |
|
|
const totalUpper = bondResult.baseReturn + capGain.upper; |
|
|
|
|
|
return { |
|
|
strategy: '纯债策略', |
|
|
period: state.getPeriodLabel(period), |
|
|
risk: state.getRiskLabel(risk), |
|
|
leverage: bondResult.leverage + '%', |
|
|
fundingCost: bondResult.fundingCost.toFixed(2) + '%', |
|
|
baseReturn: bondResult.baseReturn.toFixed(2) + '%', |
|
|
capitalGainRange: `${(capGain.lower * 100).toFixed(2)}bp ~ ${(capGain.upper * 100).toFixed(2)}bp`, |
|
|
capitalGainLower: capGain.lower, |
|
|
capitalGainUpper: capGain.upper, |
|
|
totalReturn: `${totalLower.toFixed(2)}% ~ ${totalUpper.toFixed(2)}%`, |
|
|
totalLower: totalLower, |
|
|
totalUpper: totalUpper, |
|
|
marketRef: `${marketRef.pure_bond[period][0]}% - ${marketRef.pure_bond[period][1]}%`, |
|
|
hasCapitalGain: true |
|
|
}; |
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
const bondResult = calculateBaseReturn(); |
|
|
const capGain = calculateCapitalGainRange(period); |
|
|
|
|
|
|
|
|
const assetRange = assetReturnRanges[asset][expectation]; |
|
|
|
|
|
|
|
|
let bondWeight, assetWeight; |
|
|
if (expectation === 'PESSIMISTIC') { |
|
|
bondWeight = 95; |
|
|
assetWeight = 5; |
|
|
} else if (expectation === 'NEUTRAL') { |
|
|
bondWeight = 90; |
|
|
assetWeight = 10; |
|
|
} else { |
|
|
bondWeight = 80; |
|
|
assetWeight = 20; |
|
|
} |
|
|
|
|
|
|
|
|
const capitalGainLower = capGain.lower * bondWeight / 100; |
|
|
const capitalGainUpper = capGain.upper * bondWeight / 100; |
|
|
|
|
|
|
|
|
const assetContributionLower = assetRange.min * assetWeight / 100; |
|
|
const assetContributionUpper = assetRange.max * assetWeight / 100; |
|
|
|
|
|
|
|
|
const baseBondContribution = bondResult.baseReturn * bondWeight / 100; |
|
|
|
|
|
|
|
|
|
|
|
const totalLower = baseBondContribution + capitalGainLower + assetContributionLower; |
|
|
|
|
|
|
|
|
const totalUpper = baseBondContribution + capitalGainUpper + assetContributionUpper; |
|
|
|
|
|
return { |
|
|
strategy: '固收+策略', |
|
|
period: state.getPeriodLabel(period), |
|
|
risk: state.getRiskLabel(risk), |
|
|
asset: state.getAssetLabel(asset), |
|
|
expectation: state.getExpectationLabel(expectation), |
|
|
expectationType: expectation, |
|
|
bondWeight: bondWeight + '%', |
|
|
assetWeight: assetWeight + '%', |
|
|
baseBondReturn: bondResult.baseReturn.toFixed(2) + '%', |
|
|
baseBondContribution: baseBondContribution.toFixed(2) + '%', |
|
|
capitalGainRange: `${(capGain.lower * 100).toFixed(2)}bp ~ ${(capGain.upper * 100).toFixed(2)}bp`, |
|
|
capitalGainLower: capitalGainLower, |
|
|
capitalGainUpper: capitalGainUpper, |
|
|
assetReturnRange: `${assetRange.min}% ~ ${assetRange.max}%`, |
|
|
assetContributionRange: `${assetContributionLower.toFixed(2)}% ~ ${assetContributionUpper.toFixed(2)}%`, |
|
|
totalReturn: `${totalLower.toFixed(2)}% ~ ${totalUpper.toFixed(2)}%`, |
|
|
totalLower: totalLower, |
|
|
totalUpper: totalUpper, |
|
|
marketRef: `${marketRef.fixed_income_plus[period][0]}% - ${marketRef.fixed_income_plus[period][1]}%`, |
|
|
hasCapitalGain: true |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function displayResults(result) { |
|
|
const content = document.getElementById('result-content'); |
|
|
|
|
|
let html = ` |
|
|
<div class="result-grid"> |
|
|
<div class="result-card"> |
|
|
<h3>📋 基本信息</h3> |
|
|
<div class="result-item"> |
|
|
<span>策略类型:</span> |
|
|
<span class="highlight">${result.strategy}</span> |
|
|
</div> |
|
|
<div class="result-item"> |
|
|
<span>投资期限:</span> |
|
|
<span>${result.period}</span> |
|
|
</div> |
|
|
<div class="result-item"> |
|
|
<span>风险风格:</span> |
|
|
<span>${result.risk}</span> |
|
|
</div>`; |
|
|
|
|
|
if (result.strategy === '纯债策略') { |
|
|
html += ` |
|
|
<div class="result-item"> |
|
|
<span>杠杆率:</span> |
|
|
<span>${result.leverage}</span> |
|
|
</div> |
|
|
<div class="result-item"> |
|
|
<span>融资成本:</span> |
|
|
<span>${result.fundingCost}</span> |
|
|
</div>`; |
|
|
} else { |
|
|
html += ` |
|
|
<div class="result-item"> |
|
|
<span>增强资产:</span> |
|
|
<span>${result.asset}</span> |
|
|
</div> |
|
|
<div class="result-item"> |
|
|
<span>资产预期:</span> |
|
|
<span>${result.expectation} |
|
|
<span class="range-indicator range-${result.expectationType.toLowerCase()}"> |
|
|
${result.expectationType === 'PESSIMISTIC' ? '悲观' : |
|
|
result.expectationType === 'NEUTRAL' ? '中性' : '乐观'} |
|
|
</span> |
|
|
</span> |
|
|
</div> |
|
|
<div class="result-item"> |
|
|
<span>债券比例:</span> |
|
|
<span>${result.bondWeight}</span> |
|
|
</div> |
|
|
<div class="result-item"> |
|
|
<span>资产比例:</span> |
|
|
<span>${result.assetWeight}</span> |
|
|
</div>`; |
|
|
} |
|
|
|
|
|
html += ` |
|
|
</div> |
|
|
|
|
|
<div class="result-card"> |
|
|
<h3>📈 收益率分析</h3> |
|
|
<div class="contribution-breakdown">`; |
|
|
|
|
|
if (result.strategy === '纯债策略') { |
|
|
html += ` |
|
|
<div class="contribution-item"> |
|
|
<span>基础收益率:</span> |
|
|
<span>${result.baseReturn}</span> |
|
|
</div> |
|
|
<div class="contribution-item"> |
|
|
<span>资本利得贡献:</span> |
|
|
<span>${result.capitalGainRange}<span class="bp-unit">bp</span></span> |
|
|
</div>`; |
|
|
} else { |
|
|
html += ` |
|
|
<div class="contribution-item"> |
|
|
<span>债券基础部分:</span> |
|
|
<span>${result.baseBondContribution}</span> |
|
|
</div> |
|
|
<div class="contribution-item"> |
|
|
<span>资本利得贡献:</span> |
|
|
<span>${result.capitalGainRange}<span class="bp-unit">bp</span></span> |
|
|
</div> |
|
|
<div class="contribution-item"> |
|
|
<span>增强资产贡献:</span> |
|
|
<span>${result.assetContributionRange}</span> |
|
|
</div> |
|
|
<div class="contribution-item" style="font-size: 0.9rem; color: #666;"> |
|
|
<span>(资产预期收益率:</span> |
|
|
<span>${result.assetReturnRange})</span> |
|
|
</div>`; |
|
|
} |
|
|
|
|
|
html += ` |
|
|
<div class="contribution-item" style="margin-top: 10px; padding-top: 10px; border-top: 2px solid #ddd; font-weight: 600;"> |
|
|
<span>理论预期收益率:</span> |
|
|
<span class="highlight">${result.totalReturn}</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="result-item" style="margin-top: 15px;"> |
|
|
<span>市场实际参考区间:</span> |
|
|
<span>${result.marketRef}</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div style="margin-top: 30px; padding: 20px; background: #f0f8ff; border-radius: 8px;"> |
|
|
<h3>💡 计算说明</h3> |
|
|
<p><strong>资本利得模型:</strong>基于正态分布N(0, 18bp),不同期限对应不同波段次数,计算第50-80百分位交易员收益区间:</p> |
|
|
<ul style="margin-left: 20px; margin-top: 5px;"> |
|
|
<li>短期(0-3个月):1次波段</li> |
|
|
<li>中期(3-6个月):1.5次波段</li> |
|
|
<li>长期(6-12个月):3次波段</li> |
|
|
</ul> |
|
|
<p><strong>资本利得区间:</strong>下限为0bp(第50百分位不亏钱),上限为第80百分位值</p> |
|
|
<p><strong>资产预期:</strong>${result.strategy === '固收+策略' ? |
|
|
`采用${result.expectation}预期,${result.asset}指数预测区间为${result.assetReturnRange}` : |
|
|
'纯债策略主要依赖票息收入和资本利得'}</p> |
|
|
<p><strong>最终收益率:</strong>基础收益率 + 资本利得区间 + 增强资产贡献区间(固收+策略)</p> |
|
|
</div>`; |
|
|
|
|
|
content.innerHTML = html; |
|
|
document.getElementById('result').classList.remove('hidden'); |
|
|
document.getElementById('result').scrollIntoView({ behavior: 'smooth' }); |
|
|
} |
|
|
</script> |
|
|
</body> |
|
|
</html> |