feat: risk
This commit is contained in:
@@ -141,6 +141,8 @@ function ScenarioExposureCard() {
|
||||
|
||||
const concentration: any[] = exp.concentration ?? []
|
||||
const scenarios: any[] = exp.scenarios ?? []
|
||||
const positionDetails: any[] = exp.position_details ?? []
|
||||
const redundant = exp.effective_n_positions != null && exp.effective_n_positions < exp.positions * 0.75
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
@@ -148,9 +150,29 @@ function ScenarioExposureCard() {
|
||||
<Layers className="w-4 h-4 text-purple-400" /> Alignement du portefeuille sur le Régime Macro
|
||||
</div>
|
||||
<div className="text-[10px] text-slate-500 mb-3">
|
||||
Repricing Black-Scholes réel (pricing Saxo-first) de chaque position sous les 8 scénarios du
|
||||
régime macro — révèle quand plusieurs positions différentes sont en réalité le même pari répété.
|
||||
Vue distincte des Risk Factors ci-dessous (thème/classe d'actif touché, pas le sens du pari).
|
||||
Une position ne compte pour un scénario que si elle y gagne réellement (repricing Black-Scholes
|
||||
réel, Saxo-first), répartie entre tous les scénarios où elle est gagnante — pas un vote unique
|
||||
au scénario "le moins pire". Vue distincte des Risk Factors ci-dessous (thème touché, pas le sens du pari).
|
||||
</div>
|
||||
|
||||
{/* Headline stats */}
|
||||
<div className="grid grid-cols-3 gap-2 mb-3">
|
||||
<div className="bg-dark-700/40 rounded px-2 py-1.5 text-center">
|
||||
<div className="text-sm font-bold font-mono text-slate-200">{exp.positions}</div>
|
||||
<div className="text-[9px] text-slate-500">positions</div>
|
||||
</div>
|
||||
<div className="bg-dark-700/40 rounded px-2 py-1.5 text-center">
|
||||
<div className={clsx('text-sm font-bold font-mono', redundant ? 'text-amber-400' : 'text-slate-200')}>
|
||||
{exp.effective_n_positions ?? '—'}
|
||||
</div>
|
||||
<div className="text-[9px] text-slate-500">paris indépendants (N eff.)</div>
|
||||
</div>
|
||||
<div className="bg-dark-700/40 rounded px-2 py-1.5 text-center">
|
||||
<div className={clsx('text-sm font-bold font-mono', exp.pct_unaligned >= 25 ? 'text-amber-400' : 'text-slate-200')}>
|
||||
{exp.pct_unaligned}%
|
||||
</div>
|
||||
<div className="text-[9px] text-slate-500">non couvert par les 8 scénarios</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{exp.warning && (
|
||||
@@ -164,25 +186,29 @@ function ScenarioExposureCard() {
|
||||
{/* Concentration ranking */}
|
||||
<div>
|
||||
<div className="text-xs font-semibold text-slate-400 mb-2">
|
||||
% du book dont c'est le scénario le plus favorable
|
||||
% du capital à risque aligné avec chaque scénario
|
||||
</div>
|
||||
<div className="space-y-2.5">
|
||||
{concentration.map((c: any) => (
|
||||
<div key={c.key}>
|
||||
<div className="flex justify-between text-xs mb-1">
|
||||
<span className="text-slate-300 flex items-center gap-1.5">
|
||||
<span>{c.emoji}</span>{c.label}
|
||||
</span>
|
||||
<span className="font-mono font-bold" style={{ color: c.color }}>
|
||||
{c.pct_of_portfolio}%
|
||||
</span>
|
||||
{concentration.length === 0 ? (
|
||||
<div className="text-xs text-slate-600">Aucune position ne gagne dans un des 8 scénarios.</div>
|
||||
) : (
|
||||
<div className="space-y-2.5">
|
||||
{concentration.map((c: any) => (
|
||||
<div key={c.key}>
|
||||
<div className="flex justify-between text-xs mb-1">
|
||||
<span className="text-slate-300 flex items-center gap-1.5">
|
||||
<span>{c.emoji}</span>{c.label}
|
||||
</span>
|
||||
<span className="font-mono font-bold" style={{ color: c.color }}>
|
||||
{c.pct_of_portfolio}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 bg-dark-700 rounded-full overflow-hidden">
|
||||
<div className="h-full rounded-full" style={{ width: `${Math.min(c.pct_of_portfolio, 100)}%`, background: c.color }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-2 bg-dark-700 rounded-full overflow-hidden">
|
||||
<div className="h-full rounded-full" style={{ width: `${Math.min(c.pct_of_portfolio, 100)}%`, background: c.color }} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sensitivity matrix */}
|
||||
@@ -206,6 +232,39 @@ function ScenarioExposureCard() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Per-position transparency layer */}
|
||||
{positionDetails.length > 0 && (
|
||||
<div className="mt-4 pt-3 border-t border-slate-700/30">
|
||||
<div className="text-xs font-semibold text-slate-400 mb-2">Détail par position</div>
|
||||
<div className="space-y-1.5">
|
||||
{positionDetails.map((p: any) => (
|
||||
<div key={p.id} className="flex items-center gap-2 text-[11px] bg-dark-700/30 rounded px-2 py-1.5">
|
||||
<span className="text-slate-200 font-medium w-40 truncate shrink-0">{p.title}</span>
|
||||
<span className="text-slate-500 w-32 truncate shrink-0">{p.nature}</span>
|
||||
<span className="text-slate-600 font-mono text-[10px] w-28 shrink-0">
|
||||
Δ {p.delta_dollars != null ? p.delta_dollars.toFixed(0) : '—'} · v {p.vega_dollars != null ? p.vega_dollars.toFixed(0) : '—'}
|
||||
</span>
|
||||
{p.curve_regime && (
|
||||
<span className="text-[9px] text-cyan-400/80 bg-cyan-900/20 rounded px-1.5 py-0.5 shrink-0">{p.curve_regime}</span>
|
||||
)}
|
||||
<span className="flex-1 flex items-center gap-1 justify-end flex-wrap">
|
||||
{p.is_unaligned ? (
|
||||
<span className="text-[9px] text-slate-600 italic">non aligné (perd dans les 8 scénarios)</span>
|
||||
) : (
|
||||
p.aligned_scenarios.map((a: any) => (
|
||||
<span key={a.key} className="text-[9px] rounded px-1.5 py-0.5"
|
||||
style={{ background: `${a.color}22`, color: a.color }}>
|
||||
{a.emoji} {a.label} {a.share_of_position}%
|
||||
</span>
|
||||
))
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{exp.unpriced?.length > 0 && (
|
||||
<div className="text-[10px] text-slate-600 mt-3 pt-2 border-t border-slate-700/30">
|
||||
{exp.unpriced.length} position(s) non pricée(s) (pas de legs/données) : {exp.unpriced.map((u: any) => u.title).join(', ')}
|
||||
|
||||
Reference in New Issue
Block a user