Vol Surface: HV-vs-IV card as rowspan-2 to the right of toolbar
Wrap the toolbar card and the skew-metric badges (ATM IV / RR25 / Fly25) in a left column, and place the HV-vs-IV card in a sibling right column with h-100 d-flex flex-column so it stretches to the full height of both stacked items — effectively a rowspan=2 layout. On screens narrower than lg the right column drops below as a single full-width card. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -353,135 +353,141 @@
|
|||||||
<div class="page-body">
|
<div class="page-body">
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
|
|
||||||
<!-- Toolbar -->
|
<!-- Top section: toolbar + skew badges on the left, HV/IV card (rowspan-2 style) on the right -->
|
||||||
<div class="card mb-4" style="background:#161824; border:1px solid #2d3045;">
|
<div class="row g-3 mb-4 align-items-stretch">
|
||||||
<div class="card-body">
|
<div class="col-12 col-lg">
|
||||||
<div class="row g-3 align-items-end">
|
<!-- Toolbar -->
|
||||||
<div class="col-12 col-sm-auto">
|
<div class="card mb-3" style="background:#161824; border:1px solid #2d3045;">
|
||||||
<label class="form-label text-secondary" for="symbolInput">Symbol</label>
|
<div class="card-body">
|
||||||
<div class="input-group">
|
<div class="row g-3 align-items-end">
|
||||||
<input
|
<div class="col-12 col-sm-auto">
|
||||||
id="symbolInput"
|
<label class="form-label text-secondary" for="symbolInput">Symbol</label>
|
||||||
type="text"
|
<div class="input-group">
|
||||||
class="form-control"
|
<input
|
||||||
style="background:#1e2030; border-color:#2d3045; color:#fff; width:100px; text-transform:uppercase;"
|
id="symbolInput"
|
||||||
placeholder="SPY"
|
type="text"
|
||||||
x-model="symbol"
|
class="form-control"
|
||||||
@keydown.enter="fetchExpirations()"
|
style="background:#1e2030; border-color:#2d3045; color:#fff; width:100px; text-transform:uppercase;"
|
||||||
@input="symbol = symbol.toUpperCase()"
|
placeholder="SPY"
|
||||||
:disabled="loading"
|
x-model="symbol"
|
||||||
aria-label="Ticker symbol"
|
@keydown.enter="fetchExpirations()"
|
||||||
>
|
@input="symbol = symbol.toUpperCase()"
|
||||||
<button
|
:disabled="loading"
|
||||||
class="btn btn-secondary"
|
aria-label="Ticker symbol"
|
||||||
@click="fetchExpirations()"
|
>
|
||||||
:disabled="lookingUp || !symbol"
|
<button
|
||||||
aria-label="Look up expirations for symbol"
|
class="btn btn-secondary"
|
||||||
>
|
@click="fetchExpirations()"
|
||||||
<span x-show="lookingUp" class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
:disabled="lookingUp || !symbol"
|
||||||
<span x-text="lookingUp ? '…' : 'Lookup'"></span>
|
aria-label="Look up expirations for symbol"
|
||||||
</button>
|
>
|
||||||
</div>
|
<span x-show="lookingUp" class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||||||
</div>
|
<span x-text="lookingUp ? '…' : 'Lookup'"></span>
|
||||||
|
</button>
|
||||||
<div class="col-12 col-sm-auto">
|
|
||||||
<label class="form-label text-secondary" for="expirySelect">Expiry</label>
|
|
||||||
<select
|
|
||||||
id="expirySelect"
|
|
||||||
class="form-select"
|
|
||||||
style="background:#1e2030; border-color:#2d3045; color:#fff; min-width:160px;"
|
|
||||||
x-model="expiry"
|
|
||||||
:disabled="loading || expirations.length === 0"
|
|
||||||
aria-label="Select expiry date"
|
|
||||||
>
|
|
||||||
<option value="" disabled>Select expiry…</option>
|
|
||||||
<template x-for="exp in expirations" :key="exp">
|
|
||||||
<option :value="exp" x-text="exp"></option>
|
|
||||||
</template>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-sm-auto">
|
|
||||||
<button
|
|
||||||
class="btn btn-primary"
|
|
||||||
@click="loadSurface()"
|
|
||||||
:disabled="loading || !symbol || !expiry"
|
|
||||||
aria-label="Load volatility surface"
|
|
||||||
>
|
|
||||||
<span x-show="loading" class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
|
||||||
<span x-text="loading ? 'Loading…' : 'Load Surface'"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-sm-auto" x-show="errorMsg" x-cloak>
|
|
||||||
<div class="alert alert-danger mb-0 py-2 px-3" role="alert">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
|
|
||||||
fill="none" stroke="currentColor" stroke-width="2" class="me-1">
|
|
||||||
<circle cx="12" cy="12" r="10"></circle>
|
|
||||||
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
||||||
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
||||||
</svg>
|
|
||||||
<span x-text="errorMsg"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-sm-auto ms-sm-auto" x-show="hasData" x-cloak>
|
|
||||||
<div class="hviv-card">
|
|
||||||
<div class="hviv-head">
|
|
||||||
<span class="hviv-title">HV vs IV</span>
|
|
||||||
<span class="hviv-badge" :class="hvIvVerdict.cls" x-text="hvIvVerdict.label"></span>
|
|
||||||
</div>
|
|
||||||
<div class="hviv-row atm">
|
|
||||||
<span class="label">ATM IV</span>
|
|
||||||
<span class="val" x-text="formatPct(currentMetrics.atmIV)"></span>
|
|
||||||
<span class="spread"> </span>
|
|
||||||
</div>
|
|
||||||
<template x-for="w in hvRows" :key="w.key">
|
|
||||||
<div class="hviv-row">
|
|
||||||
<span class="label" x-text="w.label"></span>
|
|
||||||
<span class="val" x-text="w.value > 0 ? formatPct(w.value) : '—'"></span>
|
|
||||||
<span class="spread"
|
|
||||||
:class="w.value > 0 ? (w.spread >= 0 ? 'positive' : 'negative') : ''"
|
|
||||||
x-text="w.value > 0 ? ((w.spread >= 0 ? '+' : '') + (w.spread * 100).toFixed(1) + ' pts') : ''"></span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<div class="hviv-foot" x-text="hvIvFooter"></div>
|
|
||||||
|
<div class="col-12 col-sm-auto">
|
||||||
|
<label class="form-label text-secondary" for="expirySelect">Expiry</label>
|
||||||
|
<select
|
||||||
|
id="expirySelect"
|
||||||
|
class="form-select"
|
||||||
|
style="background:#1e2030; border-color:#2d3045; color:#fff; min-width:160px;"
|
||||||
|
x-model="expiry"
|
||||||
|
:disabled="loading || expirations.length === 0"
|
||||||
|
aria-label="Select expiry date"
|
||||||
|
>
|
||||||
|
<option value="" disabled>Select expiry…</option>
|
||||||
|
<template x-for="exp in expirations" :key="exp">
|
||||||
|
<option :value="exp" x-text="exp"></option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-auto">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="loadSurface()"
|
||||||
|
:disabled="loading || !symbol || !expiry"
|
||||||
|
aria-label="Load volatility surface"
|
||||||
|
>
|
||||||
|
<span x-show="loading" class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
||||||
|
<span x-text="loading ? 'Loading…' : 'Load Surface'"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-auto ms-sm-auto" x-show="errorMsg" x-cloak>
|
||||||
|
<div class="alert alert-danger mb-0 py-2 px-3" role="alert">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
|
||||||
|
fill="none" stroke="currentColor" stroke-width="2" class="me-1">
|
||||||
|
<circle cx="12" cy="12" r="10"></circle>
|
||||||
|
<line x1="12" y1="8" x2="12" y2="12"></line>
|
||||||
|
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
||||||
|
</svg>
|
||||||
|
<span x-text="errorMsg"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Skew metric badges -->
|
||||||
|
<div x-show="hasData" x-cloak>
|
||||||
|
<div class="d-flex flex-wrap gap-3">
|
||||||
|
|
||||||
|
<div class="stat-inline">
|
||||||
|
<span class="stat-label">ATM IV</span>
|
||||||
|
<span class="stat-value" x-text="formatPct(currentMetrics.atmIV)"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-inline">
|
||||||
|
<span class="stat-label">RR25</span>
|
||||||
|
<span
|
||||||
|
class="stat-value"
|
||||||
|
:class="{
|
||||||
|
'positive': currentMetrics.rr25 > 0.005,
|
||||||
|
'negative': currentMetrics.rr25 < -0.005
|
||||||
|
}"
|
||||||
|
x-text="formatPctSigned(currentMetrics.rr25)"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-inline">
|
||||||
|
<span class="stat-label">Fly25</span>
|
||||||
|
<span
|
||||||
|
class="stat-value"
|
||||||
|
:class="{ 'amber': Math.abs(currentMetrics.fly25) > 0.002 }"
|
||||||
|
x-text="formatPctSigned(currentMetrics.fly25)"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Skew metric badges -->
|
<!-- HV vs IV card — spans full height of left column (toolbar + skew badges) -->
|
||||||
<div class="mb-4" x-show="hasData" x-cloak>
|
<div class="col-12 col-lg-auto" x-show="hasData" x-cloak>
|
||||||
<div class="d-flex flex-wrap gap-3">
|
<div class="hviv-card h-100 d-flex flex-column">
|
||||||
|
<div class="hviv-head">
|
||||||
<div class="stat-inline">
|
<span class="hviv-title">HV vs IV</span>
|
||||||
<span class="stat-label">ATM IV</span>
|
<span class="hviv-badge" :class="hvIvVerdict.cls" x-text="hvIvVerdict.label"></span>
|
||||||
<span class="stat-value" x-text="formatPct(currentMetrics.atmIV)"></span>
|
</div>
|
||||||
|
<div class="hviv-row atm">
|
||||||
|
<span class="label">ATM IV</span>
|
||||||
|
<span class="val" x-text="formatPct(currentMetrics.atmIV)"></span>
|
||||||
|
<span class="spread"> </span>
|
||||||
|
</div>
|
||||||
|
<template x-for="w in hvRows" :key="w.key">
|
||||||
|
<div class="hviv-row">
|
||||||
|
<span class="label" x-text="w.label"></span>
|
||||||
|
<span class="val" x-text="w.value > 0 ? formatPct(w.value) : '—'"></span>
|
||||||
|
<span class="spread"
|
||||||
|
:class="w.value > 0 ? (w.spread >= 0 ? 'positive' : 'negative') : ''"
|
||||||
|
x-text="w.value > 0 ? ((w.spread >= 0 ? '+' : '') + (w.spread * 100).toFixed(1) + ' pts') : ''"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="hviv-foot mt-auto" x-text="hvIvFooter"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat-inline">
|
|
||||||
<span class="stat-label">RR25</span>
|
|
||||||
<span
|
|
||||||
class="stat-value"
|
|
||||||
:class="{
|
|
||||||
'positive': currentMetrics.rr25 > 0.005,
|
|
||||||
'negative': currentMetrics.rr25 < -0.005
|
|
||||||
}"
|
|
||||||
x-text="formatPctSigned(currentMetrics.rr25)"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stat-inline">
|
|
||||||
<span class="stat-label">Fly25</span>
|
|
||||||
<span
|
|
||||||
class="stat-value"
|
|
||||||
:class="{ 'amber': Math.abs(currentMetrics.fly25) > 0.002 }"
|
|
||||||
x-text="formatPctSigned(currentMetrics.fly25)"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user