fix: 修复反向推算引擎中Theta单位切换时的空值问题 ; 修复样式问题

master
Lxy 1 month ago
parent 1dea8319d5
commit 5692411d93

@ -49,8 +49,6 @@ export default function GuideSection() {
<GreekGuideCard
title="Delta (Δ)"
symbol="∂V/∂S"
color="text-emerald-400"
borderColor="border-emerald-700/50"
icon={<TrendingUp className="w-5 h-5" />}
description="方向敏感度:标的价格每变动 1 单位,期权价格变动多少"
callFormula="Δ_call = N(d₁)"
@ -63,8 +61,6 @@ export default function GuideSection() {
<GreekGuideCard
title="Gamma (Γ)"
symbol="∂²V/∂S²"
color="text-blue-400"
borderColor="border-blue-700/50"
icon={<Zap className="w-5 h-5" />}
description="凸性风险Delta 对价格变动的敏感度,衡量对冲误差"
callFormula="Γ = N'(d₁) / (S·σ·√T)"
@ -77,8 +73,6 @@ export default function GuideSection() {
<GreekGuideCard
title="Theta (Θ)"
symbol="∂V/∂t"
color="text-rose-400"
borderColor="border-rose-700/50"
icon={<Clock className="w-5 h-5" />}
description="时间衰减:每过一天,期权价值因时间流逝损失多少"
callFormula="Θ = -S·N'(d₁)·σ / (2√T)"
@ -91,8 +85,6 @@ export default function GuideSection() {
<GreekGuideCard
title="Vega (ν)"
symbol="∂V/∂σ"
color="text-purple-400"
borderColor="border-purple-700/50"
icon={<Percent className="w-5 h-5" />}
description="波动率敏感度:隐含波动率每变化 1 个百分点,期权价格变动多少"
callFormula="ν = S·√T·N'(d₁)·0.01"
@ -576,8 +568,8 @@ export default function GuideSection() {
);
}
function GreekGuideCard({ title, symbol, color, borderColor, icon, description, callFormula, putFormula, range, keyInsight, behavior }: {
title: string; symbol: string; color: string; borderColor: string; icon: React.ReactNode;
function GreekGuideCard({ title, symbol, icon, description, callFormula, putFormula, range, keyInsight, behavior }: {
title: string; symbol: string; icon: React.ReactNode;
description: string; callFormula: string; putFormula: string; range: string; keyInsight: string; behavior: string;
}) {
return (

@ -35,7 +35,7 @@ export default function ReverseSolver() {
const T = tUnit === 'days' ? contract.T_days / 365 : contract.T_days;
const upd = (fn: Function, key: string, val: string) => { const n = parseFloat(val); if (!isNaN(n)) fn((p: any) => ({ ...p, [key]: n })); };
const upd = (fn: Function, key: string, val: string) => { if (val === '') return; const n = parseFloat(val); if (!isNaN(n)) fn((p: any) => ({ ...p, [key]: n })); };
const res1 = useMemo(() => {
const t: any = {};
@ -204,24 +204,24 @@ export default function ReverseSolver() {
</div>
{res1 && (
<div className="bg-slate-800/50 rounded-lg p-6 border border-slate-700">
<div className="bg-[#F5F5F5] rounded-lg p-6 border border-[#E6E8EA]">
<div className="flex items-center justify-between mb-4">
<h4 className="text-lg font-semibold text-white"></h4>
<h4 className="text-lg font-semibold text-[#1E2026]"></h4>
<div className="flex gap-2">
<Badge className="bg-emerald-600"></Badge>
{res1.T_from_theta && <Badge className="bg-rose-600">T Theta </Badge>}
<Badge className="bg-[#0ECB81] text-white"></Badge>
{res1.T_from_theta && <Badge className="bg-[#F6465D] text-white">T Theta </Badge>}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="text-center p-4 bg-slate-800 rounded-lg space-y-3">
<p className="text-sm text-slate-400"></p>
<p className="text-3xl font-bold text-emerald-400">${res1.price.toFixed(4)}</p>
<p className="text-xs text-slate-500">σ = {res1.sigma.toFixed(4)} ({(res1.sigma * 100).toFixed(2)}%)</p>
</div>
<div className="text-center p-4 bg-slate-800 rounded-lg space-y-3">
<p className="text-sm text-slate-400"></p>
<p className="text-3xl font-bold text-rose-400">{res1.DTE} <span className="text-lg"></span></p>
<p className="text-xs text-slate-500">T = {res1.T.toFixed(4)} </p>
<div className="text-center p-4 bg-white rounded-lg border border-[#E6E8EA] space-y-3">
<p className="text-sm text-[#848E9C]"></p>
<p className="text-3xl font-bold text-[#0ECB81]">${res1.price.toFixed(4)}</p>
<p className="text-xs text-[#848E9C]">σ = {res1.sigma.toFixed(4)} ({(res1.sigma * 100).toFixed(2)}%)</p>
</div>
<div className="text-center p-4 bg-white rounded-lg border border-[#E6E8EA] space-y-3">
<p className="text-sm text-[#848E9C]"></p>
<p className="text-3xl font-bold text-[#F6465D]">{res1.DTE} <span className="text-lg"></span></p>
<p className="text-xs text-[#848E9C]">T = {res1.T.toFixed(4)} </p>
<div className="flex justify-center mt-1"><DecayBadge dte={res1.DTE} /></div>
</div>
<div className="space-y-2">
@ -233,8 +233,8 @@ export default function ReverseSolver() {
</div>
</div>
{truth1 && (
<div className="mt-4 bg-slate-800/30 p-3 rounded text-xs text-slate-500">
<strong className="text-slate-300"></strong>={truth1.intrinsic.toFixed(4)} ={truth1.timeValue.toFixed(4)} ITM={(truth1.itmProb * 100).toFixed(2)}%
<div className="mt-4 bg-[#F5F5F5] p-3 rounded text-xs text-[#848E9C]">
<strong className="text-[#1E2026]"></strong>={truth1.intrinsic.toFixed(4)} ={truth1.timeValue.toFixed(4)} ITM={(truth1.itmProb * 100).toFixed(2)}%
</div>
)}
</div>
@ -245,10 +245,10 @@ export default function ReverseSolver() {
{/* Mode 2 */}
<TabsContent value="price-greeks-to-s" className="mt-6 space-y-6">
<Card className="bg-slate-900 border-slate-800">
<Card className="bg-white border border-[#E6E8EA] rounded-[12px] shadow-[rgba(32,32,37,0.05)_0px_3px_5px_0px] hover:shadow-[rgba(8,8,8,0.05)_0px_3px_5px_5px] transition-all duration-200">
<CardHeader>
<CardTitle className="text-white text-base flex items-center gap-2"><Search className="w-5 h-5 text-blue-400" /> + S</CardTitle>
<p className="text-sm text-slate-400"> Theta <strong className="text-amber-400">SσT</strong></p>
<CardTitle className="text-[#1E2026] text-base flex items-center gap-2"><Search className="w-5 h-5 text-[#F0B90B]" /> + S</CardTitle>
<p className="text-sm text-[#848E9C]"> Theta <strong className="text-[#F0B90B]">SσT</strong></p>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
@ -271,24 +271,24 @@ export default function ReverseSolver() {
</div>
{res2 && (
<div className="bg-slate-800/50 rounded-lg p-6 border border-slate-700">
<div className="bg-[#F5F5F5] rounded-lg p-6 border border-[#E6E8EA]">
<div className="flex items-center justify-between mb-4">
<h4 className="text-lg font-semibold text-white"></h4>
<h4 className="text-lg font-semibold text-[#1E2026]"></h4>
<div className="flex gap-2">
<Badge className="bg-blue-600"></Badge>
{res2.T_from_theta && <Badge className="bg-rose-600">T Theta </Badge>}
<Badge className="bg-[#1EAEDB] text-white"></Badge>
{res2.T_from_theta && <Badge className="bg-[#F6465D] text-white">T Theta </Badge>}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="text-center p-4 bg-slate-800 rounded-lg space-y-3">
<p className="text-sm text-slate-400"> S</p>
<p className="text-3xl font-bold text-blue-400">${res2.S.toFixed(4)}</p>
<p className="text-xs text-slate-500">σ = {res2.sigma.toFixed(4)} ({(res2.sigma * 100).toFixed(2)}%)</p>
</div>
<div className="text-center p-4 bg-slate-800 rounded-lg space-y-3">
<p className="text-sm text-slate-400"></p>
<p className="text-3xl font-bold text-rose-400">{res2.DTE} <span className="text-lg"></span></p>
<p className="text-xs text-slate-500">T = {res2.T.toFixed(4)} </p>
<div className="text-center p-4 bg-white rounded-lg border border-[#E6E8EA] space-y-3">
<p className="text-sm text-[#848E9C]"> S</p>
<p className="text-3xl font-bold text-[#1EAEDB]">${res2.S.toFixed(4)}</p>
<p className="text-xs text-[#848E9C]">σ = {res2.sigma.toFixed(4)} ({(res2.sigma * 100).toFixed(2)}%)</p>
</div>
<div className="text-center p-4 bg-white rounded-lg border border-[#E6E8EA] space-y-3">
<p className="text-sm text-[#848E9C]"></p>
<p className="text-3xl font-bold text-[#F6465D]">{res2.DTE} <span className="text-lg"></span></p>
<p className="text-xs text-[#848E9C]">T = {res2.T.toFixed(4)} </p>
<div className="flex justify-center mt-1"><DecayBadge dte={res2.DTE} /></div>
</div>
<div className="space-y-2">
@ -300,8 +300,8 @@ export default function ReverseSolver() {
<RRow label="Rho" tgt={m2On.rho ? m2.rho : undefined} cmp={res2.greeks.rho} res={res2.residuals.rho} />
</div>
</div>
<div className="mt-4 bg-slate-800/30 p-3 rounded text-xs text-slate-500">
<strong className="text-slate-300"></strong>={res2.greeks.intrinsic.toFixed(4)} ={res2.greeks.timeValue.toFixed(4)} ITM={(res2.greeks.itmProb * 100).toFixed(2)}%
<div className="mt-4 bg-[#F5F5F5] p-3 rounded text-xs text-[#848E9C]">
<strong className="text-[#1E2026]"></strong>={res2.greeks.intrinsic.toFixed(4)} ={res2.greeks.timeValue.toFixed(4)} ITM={(res2.greeks.itmProb * 100).toFixed(2)}%
</div>
</div>
)}
@ -311,25 +311,25 @@ export default function ReverseSolver() {
{/* Mode 3 */}
<TabsContent value="price-to-sigma" className="mt-6 space-y-6">
<Card className="bg-slate-900 border-slate-800">
<Card className="bg-white border border-[#E6E8EA] rounded-[12px] shadow-[rgba(32,32,37,0.05)_0px_3px_5px_0px] hover:shadow-[rgba(8,8,8,0.05)_0px_3px_5px_5px] transition-all duration-200">
<CardHeader>
<CardTitle className="text-white text-base flex items-center gap-2"><Search className="w-5 h-5 text-purple-400" /> + </CardTitle>
<CardTitle className="text-[#1E2026] text-base flex items-center gap-2"><Search className="w-5 h-5 text-[#F0B90B]" /> + </CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="space-y-2"><Label className="text-slate-400 text-xs"></Label><Input type="number" value={m3.price} onChange={e => upd(setM3, 'price', e.target.value)} className="bg-slate-800 border-slate-700 text-white" /></div>
<div className="space-y-2"><Label className="text-slate-400 text-xs"> S</Label><Input type="number" value={m3.S} onChange={e => upd(setM3, 'S', e.target.value)} className="bg-slate-800 border-slate-700 text-white" /></div>
<div className="space-y-2"><Label className="text-[#848E9C] text-xs"></Label><Input type="number" value={m3.price} onChange={e => upd(setM3, 'price', e.target.value)} className="border-[#E6E8EA] text-[#1E2026]" /></div>
<div className="space-y-2"><Label className="text-[#848E9C] text-xs"> S</Label><Input type="number" value={m3.S} onChange={e => upd(setM3, 'S', e.target.value)} className="border-[#E6E8EA] text-[#1E2026]" /></div>
</div>
{res3 !== null && (
<div className="bg-slate-800/50 rounded-lg p-6 border border-slate-700 mt-4">
<div className="bg-[#F5F5F5] rounded-lg p-6 border border-[#E6E8EA] mt-4">
<div className="flex items-center justify-between mb-4">
<h4 className="text-lg font-semibold text-white"></h4>
<Badge className="bg-purple-600">Newton-Raphson </Badge>
<h4 className="text-lg font-semibold text-[#1E2026]"></h4>
<Badge className="bg-[#F0B90B] text-[#1E2026]">Newton-Raphson </Badge>
</div>
<div className="text-center p-4 bg-slate-800 rounded-lg">
<p className="text-sm text-slate-400 mb-2"> σ</p>
<p className="text-3xl font-bold text-purple-400">{(res3 * 100).toFixed(2)}%</p>
<p className="text-xs text-slate-500 mt-1">: {res3.toFixed(6)}</p>
<div className="text-center p-4 bg-white rounded-lg border border-[#E6E8EA]">
<p className="text-sm text-[#848E9C] mb-2"> σ</p>
<p className="text-3xl font-bold text-[#F0B90B]">{(res3 * 100).toFixed(2)}%</p>
<p className="text-xs text-[#848E9C] mt-1">: {res3.toFixed(6)}</p>
</div>
</div>
)}
@ -339,9 +339,9 @@ export default function ReverseSolver() {
{/* Mode 4 */}
<TabsContent value="s-sigma-greeks-to-t" className="mt-6 space-y-6">
<Card className="bg-slate-900 border-slate-800">
<Card className="bg-white border border-[#E6E8EA] rounded-[12px] shadow-[rgba(32,32,37,0.05)_0px_3px_5px_0px] hover:shadow-[rgba(8,8,8,0.05)_0px_3px_5px_5px] transition-all duration-200">
<CardHeader>
<CardTitle className="text-white text-base flex items-center gap-2"><Clock className="w-5 h-5 text-rose-400" /> S + σ + T + </CardTitle>
<CardTitle className="text-[#1E2026] text-base flex items-center gap-2"><Clock className="w-5 h-5 text-[#F0B90B]" /> S + σ + T + </CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
@ -354,9 +354,9 @@ export default function ReverseSolver() {
<GInput label={`Theta (${m4ThetaUnit === 'daily' ? '日' : '年'})`} value={m4.theta} onChange={v => upd(setM4, 'theta', v)} color="text-rose-400" icon={<Clock className="w-4 h-4" />} on={m4On.theta} toggle={() => setM4On(p => ({ ...p, theta: !p.theta }))} />
{m4On.theta && (
<div className="absolute top-0 right-0 -mt-1 -mr-1">
<div className="flex bg-slate-800 rounded border border-slate-700 overflow-hidden">
<button onClick={() => setM4ThetaUnit('daily')} className={`px-2 py-0.5 text-[10px] ${m4ThetaUnit === 'daily' ? 'bg-rose-600 text-white' : 'text-slate-400'}`}></button>
<button onClick={() => setM4ThetaUnit('annual')} className={`px-2 py-0.5 text-[10px] ${m4ThetaUnit === 'annual' ? 'bg-rose-600 text-white' : 'text-slate-400'}`}></button>
<div className="flex bg-[#F5F5F5] rounded border border-[#E6E8EA] overflow-hidden">
<button onClick={() => setM4ThetaUnit('daily')} className={`px-2 py-0.5 text-[10px] ${m4ThetaUnit === 'daily' ? 'bg-[#F6465D] text-white' : 'text-[#848E9C]'}`}></button>
<button onClick={() => setM4ThetaUnit('annual')} className={`px-2 py-0.5 text-[10px] ${m4ThetaUnit === 'annual' ? 'bg-[#F6465D] text-white' : 'text-[#848E9C]'}`}></button>
</div>
</div>
)}
@ -364,22 +364,22 @@ export default function ReverseSolver() {
<GInput label="Vega" value={m4.vega} onChange={v => upd(setM4, 'vega', v)} color="text-purple-400" icon={<Activity className="w-4 h-4" />} on={m4On.vega} toggle={() => setM4On(p => ({ ...p, vega: !p.vega }))} />
</div>
{res4 && (
<div className="bg-slate-800/50 rounded-lg p-6 border border-slate-700">
<div className="bg-[#F5F5F5] rounded-lg p-6 border border-[#E6E8EA]">
<div className="flex items-center justify-between mb-4">
<h4 className="text-lg font-semibold text-white"></h4>
<Badge className="bg-rose-600"></Badge>
<h4 className="text-lg font-semibold text-[#1E2026]"></h4>
<Badge className="bg-[#F6465D] text-white"></Badge>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="text-center p-4 bg-slate-800 rounded-lg space-y-3">
<p className="text-sm text-slate-400"> T</p>
<p className="text-3xl font-bold text-rose-400">{Math.round(res4.T * 365)} <span className="text-lg"></span></p>
<p className="text-xs text-slate-500">{res4.T.toFixed(4)} </p>
<div className="text-center p-4 bg-white rounded-lg border border-[#E6E8EA] space-y-3">
<p className="text-sm text-[#848E9C]"> T</p>
<p className="text-3xl font-bold text-[#F6465D]">{Math.round(res4.T * 365)} <span className="text-lg"></span></p>
<p className="text-xs text-[#848E9C]">{res4.T.toFixed(4)} </p>
<div className="flex justify-center mt-1"><DecayBadge dte={Math.round(res4.T * 365)} /></div>
</div>
<div className="text-center p-4 bg-slate-800 rounded-lg space-y-3">
<p className="text-sm text-slate-400"></p>
<p className="text-3xl font-bold text-emerald-400">${res4.price.toFixed(4)}</p>
<p className="text-xs text-slate-500">S={m4.S}, σ={m4.sigma}</p>
<div className="text-center p-4 bg-white rounded-lg border border-[#E6E8EA] space-y-3">
<p className="text-sm text-[#848E9C]"></p>
<p className="text-3xl font-bold text-[#0ECB81]">${res4.price.toFixed(4)}</p>
<p className="text-xs text-[#848E9C]">S={m4.S}, σ={m4.sigma}</p>
</div>
<div className="space-y-2">
{res4.residuals.price !== undefined && <RRow label="权利金残差" tgt={m4.price} cmp={res4.greeks.price} res={res4.residuals.price} />}
@ -389,8 +389,8 @@ export default function ReverseSolver() {
{res4.residuals.vega !== undefined && <RRow label="Vega" tgt={m4.vega} cmp={res4.greeks.vega} res={res4.residuals.vega} />}
</div>
</div>
<div className="mt-4 bg-slate-800/30 p-3 rounded text-xs text-slate-500">
<strong className="text-slate-300"></strong>Delta = N(d) d = N¹(Δ) d = [ln(S/K) + 0.5σ²T]/(σT) T
<div className="mt-4 bg-[#F5F5F5] p-3 rounded text-xs text-[#848E9C]">
<strong className="text-[#1E2026]"></strong>Delta = N(d) d = N¹(Δ) d = [ln(S/K) + 0.5σ²T]/(σT) T
</div>
</div>
)}
@ -399,24 +399,24 @@ export default function ReverseSolver() {
</TabsContent>
</Tabs>
<Card className="bg-slate-900 border-slate-800">
<CardHeader><CardTitle className="text-white text-base"></CardTitle></CardHeader>
<CardContent className="text-sm text-slate-400 space-y-4">
<Card className="bg-white border border-[#E6E8EA] rounded-[12px] shadow-[rgba(32,32,37,0.05)_0px_3px_5px_0px] hover:shadow-[rgba(8,8,8,0.05)_0px_3px_5px_5px] transition-all duration-200">
<CardHeader><CardTitle className="text-[#1E2026] text-base"></CardTitle></CardHeader>
<CardContent className="text-sm text-[#848E9C] space-y-4">
<div>
<h4 className="text-slate-200 font-medium mb-1">1. S + Delta(+Theta) </h4>
<h4 className="text-[#1E2026] font-medium mb-1">1. S + Delta(+Theta) </h4>
<p> ThetaT Delta σ</p>
<p><strong className="text-amber-400"> Theta</strong> T [1/365, 3] T Delta σ Theta (σ, T) </p>
<p className="text-xs text-slate-500 mt-1">Theta -0.016 $0.016 -5.84 $5.84</p>
<p><strong className="text-[#F0B90B]"> Theta</strong> T [1/365, 3] T Delta σ Theta (σ, T) </p>
<p className="text-xs text-[#848E9C] mt-1">Theta -0.016 $0.016 -5.84 $5.84</p>
</div>
<div>
<h4 className="text-slate-200 font-medium mb-1">2. Theta T </h4>
<p className="font-mono text-xs text-slate-500">Θ -S·N'(d)·σ/(2T)</p>
<h4 className="text-[#1E2026] font-medium mb-1">2. Theta T </h4>
<p className="font-mono text-xs text-[#848E9C]">Θ -S·N'(d)·σ/(2T)</p>
<p>Theta T SσTheta T</p>
</div>
<div>
<h4 className="text-slate-200 font-medium mb-1">3. </h4>
<h4 className="text-[#1E2026] font-medium mb-1">3. </h4>
<p> r=0, q=0 BSM </p>
<p className="font-mono text-xs text-slate-500">d = [ln(S/K) + 0.5σ²T] / (σT) · C = S·N(d) - K·N(d)</p>
<p className="font-mono text-xs text-[#848E9C]">d = [ln(S/K) + 0.5σ²T] / (σT) · C = S·N(d) - K·N(d)</p>
</div>
</CardContent>
</Card>

Loading…
Cancel
Save