fix: IV rank fallback to history when live options fetch fails + Entrée date from cycle logs

- iv_engine/options_vol: when get_atm_iv() returns None (yfinance chain unavailable),
  fall back to most recent iv_history row so IV Rank is always computable from
  bootstrapped data; live vs history source tagged as iv_source field
- Dashboard: build mtmMap from tradeMtmData.trades (trade_entry_prices, cycle auto-log)
  keyed by pattern_id; getAddedInfo() falls back to mtmMap so Entrée/Durée columns
  populate automatically after each AI cycle without manual portfolio add
- OptionsLab: show '~' prefix and 'IV estimée' label when IV comes from history fallback;
  fix near-invisible text-slate-700 on 'Sans historique' section header

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-18 11:55:57 +02:00
parent 592918c230
commit 3b4e035819
4 changed files with 43 additions and 9 deletions

View File

@@ -651,6 +651,19 @@ export default function Dashboard() {
return map
}, [positions])
// Fallback map from cycle-auto-logged trades (trade_entry_prices), keyed by pattern_id
const mtmMap = useMemo(() => {
const map: Record<string, { entry_date: string; expiry_days?: number }> = {}
for (const t of ((tradeMtmData as any)?.trades ?? [])) {
const pid = t.pattern_id as string
if (!pid) continue
const entry_date = (t.entry_date as string) ?? ''
const expiry_days = (t.horizon_days as number) ?? undefined
if (!map[pid] || entry_date > map[pid].entry_date) map[pid] = { entry_date, expiry_days }
}
return map
}, [tradeMtmData])
const macroInfo = useMemo(() => {
if (!macroData?.scenarios) return null
const sc = macroData.scenarios
@@ -778,6 +791,7 @@ export default function Dashboard() {
return (
addedMap[`trigger:${trigger}:${strategy}`] ??
addedMap[`ticker:${underly}:${strategy}`] ??
mtmMap[item.patternId] ??
null
)
}

View File

@@ -200,9 +200,9 @@ function WatchlistRow({ item }: { item: any }) {
<div className="w-16 shrink-0">
<span className={clsx('text-sm font-bold font-mono', ivRankColor(item.iv_rank))}>
{item.iv_current_pct != null ? `${item.iv_current_pct}%` : '—'}
{item.iv_current_pct != null ? `${item.iv_source === 'history' ? '~' : ''}${item.iv_current_pct}%` : '—'}
</span>
<div className="text-[8px] text-slate-600">IV actuelle</div>
<div className="text-[8px] text-slate-600">{item.iv_source === 'history' ? 'IV estimée' : 'IV actuelle'}</div>
</div>
<div className="flex-1 min-w-0 space-y-1">
@@ -229,7 +229,7 @@ function WatchlistRow({ item }: { item: any }) {
</div>
)}
{item.history_days > 0 && (
<div className="text-[8px] text-slate-700">{item.history_days}j historique</div>
<div className="text-[8px] text-slate-500">{item.history_days}j historique</div>
)}
</div>
@@ -384,7 +384,7 @@ export default function OptionsLab() {
)}
{noData.length > 0 && (
<div>
<div className="text-xs text-slate-700 uppercase tracking-wide mb-2">Sans historique ({noData.length})</div>
<div className="text-xs text-slate-500 uppercase tracking-wide mb-2">Sans historique ({noData.length})</div>
<div className="space-y-1.5">{noData.map(item => <WatchlistRow key={item.ticker} item={item} />)}</div>
</div>
)}