feat: 实物期权计算器 — 后端BSM+二叉树API+前端交互页面
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div class="page-view">
|
||||
<div class="page-header">
|
||||
<h3 class="page-title">实物期权计算器</h3>
|
||||
</div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="10">
|
||||
<el-card>
|
||||
<template #header><span>📋 参数设置</span></template>
|
||||
<el-form label-width="120px" size="small">
|
||||
<el-form-item label="期权类型">
|
||||
<el-select v-model="form.option_type" style="width:100%">
|
||||
<el-option value="expansion" label="扩张期权(看涨)" />
|
||||
<el-option value="abandon" label="放弃期权(看跌)" />
|
||||
<el-option value="delay" label="延迟期权(看涨)" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="计算模型">
|
||||
<el-select v-model="form.model" style="width:100%">
|
||||
<el-option value="bs" label="Black-Scholes" />
|
||||
<el-option value="binomial" label="二叉树" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="标的资产 S₀ (万元)">
|
||||
<el-input-number v-model="form.S0" :min="1" :step="10" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="执行价格 X (万元)">
|
||||
<el-input-number v-model="form.X" :min="1" :step="10" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="到期时间 t (年)">
|
||||
<el-input-number v-model="form.t" :min="0.1" :step="0.5" :precision="1" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="无风险利率 r (%)">
|
||||
<el-input-number v-model="form.r" :min="0.1" :step="0.5" :precision="2" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="波动率 σ (%)">
|
||||
<el-input-number v-model="form.sigma" :min="5" :step="5" :precision="0" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="calculate" :loading="loading" style="width:100%">计算</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="resetDefaults" size="small">重置默认值</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="14">
|
||||
<el-card v-if="result">
|
||||
<template #header><span>📊 计算结果</span></template>
|
||||
<div style="text-align:center;padding:20px 0;">
|
||||
<div style="font-size:12px;color:#999;">期权价值</div>
|
||||
<div style="font-size:48px;font-weight:700;" :style="{ color: result.option_value > 0 ? '#67c23a' : '#f56c6c' }">
|
||||
{{ result.option_value.toFixed(2) }} <span style="font-size:16px;font-weight:400;">万元</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="d₁">{{ result.d1?.toFixed(4) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="d₂">{{ result.d2?.toFixed(4) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="N(d₁)">{{ result.Nd1?.toFixed(4) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="N(d₂)">{{ result.Nd2?.toFixed(4) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="模型" :span="2">{{ form.model === 'bs' ? 'Black-Scholes' : '二叉树' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-alert :type="result.option_value > 0 ? 'success' : 'warning'" show-icon style="margin-top:12px;">
|
||||
<template #title>{{ result.suggestion }}</template>
|
||||
</el-alert>
|
||||
</el-card>
|
||||
<el-card v-else>
|
||||
<el-empty description="设置参数后点击「计算」查看结果" />
|
||||
</el-card>
|
||||
<el-card v-if="result?.sensitivity?.length" style="margin-top:12px;">
|
||||
<template #header><span>📈 波动率敏感性</span></template>
|
||||
<div ref="chartRef" style="height:200px;"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import api from '../api/index'
|
||||
|
||||
const loading = ref(false)
|
||||
const result = ref<any>(null)
|
||||
const chartRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const form = reactive({
|
||||
option_type: 'expansion',
|
||||
model: 'bs',
|
||||
S0: 100, X: 80, t: 3, r: 2.8, sigma: 30
|
||||
})
|
||||
|
||||
async function calculate() {
|
||||
loading.value = true
|
||||
result.value = null
|
||||
try {
|
||||
const payload = {
|
||||
option_type: form.option_type,
|
||||
model: form.model,
|
||||
S0: form.S0,
|
||||
X: form.X,
|
||||
t: form.t,
|
||||
r: form.r / 100,
|
||||
sigma: form.sigma / 100,
|
||||
}
|
||||
const r: any = await api.post('/predict/real-option', payload)
|
||||
result.value = r
|
||||
nextTick(() => drawChart())
|
||||
} catch (e) {
|
||||
ElMessage.error('计算失败')
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function resetDefaults() {
|
||||
form.S0 = 100; form.X = 80; form.t = 3
|
||||
form.r = 2.8; form.sigma = 30
|
||||
form.option_type = 'expansion'; form.model = 'bs'
|
||||
result.value = null
|
||||
}
|
||||
|
||||
function drawChart() {
|
||||
if (!result.value?.sensitivity?.length || !chartRef.value) return
|
||||
const points = result.value.sensitivity
|
||||
const svg = `<svg width="100%" height="200" viewBox="0 0 600 200">
|
||||
<text x="10" y="20" font-size="12" fill="#999">期权价值(万)</text>
|
||||
<text x="580" y="195" font-size="12" fill="#999" text-anchor="end">波动率(%)</text>
|
||||
<polyline fill="none" stroke="#409eff" stroke-width="2"
|
||||
points="${points.map((p: any, i: number) => `${(i / (points.length - 1)) * 580},${180 - (p.value / Math.max(...points.map((x: any) => x.value))) * 160}`).join(' ')}" />
|
||||
${points.map((p: any, i: number) => {
|
||||
const x = (i / (points.length - 1)) * 580
|
||||
const y = 180 - (p.value / Math.max(...points.map((x: any) => x.value))) * 160
|
||||
return `<circle cx="${x}" cy="${y}" r="3" fill="#409eff"/><text x="${x}" y="${y - 8}" font-size="10" text-anchor="middle">${p.sigma_pct}%</text>`
|
||||
}).join('')}
|
||||
<line x1="0" y1="180" x2="580" y2="180" stroke="#e0e0e0" stroke-width="1"/>
|
||||
<line x1="0" y1="20" x2="0" y2="180" stroke="#e0e0e0" stroke-width="1"/>
|
||||
</svg>`
|
||||
chartRef.value.innerHTML = svg
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-card { margin-bottom:12px; }
|
||||
.el-form-item { margin-bottom:12px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user