Show DTE per leg + per-expiry remaining DTE for multi-expiry strategies

- Legs table: small DTE chip next to each leg's expiry (e.g. "14d", "<1d",
  "exp" — absolute days from today)
- Chart header: when active legs span multiple expiries (calendar/diagonal),
  a "Remaining: 2026-05-15 (0d) · 2026-06-20 (36d)" line shows how many
  days each unique expiry has left at the current slider position

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ojy
2026-05-13 05:27:29 +00:00
parent dbf7c8e9d2
commit 283100c453

View File

@@ -109,11 +109,12 @@
<button class="btn btn-outline-secondary" @click="zoomFit()" title="Reset pan & zoom">Fit</button> <button class="btn btn-outline-secondary" @click="zoomFit()" title="Reset pan & zoom">Fit</button>
</div> </div>
<!-- time slider --> <!-- time slider -->
<div class="d-flex align-items-center gap-2" style="min-width:300px;"> <div class="d-flex align-items-center flex-wrap gap-2" style="min-width:300px;">
<span class="text-secondary small text-nowrap">Now</span> <span class="text-secondary small text-nowrap">Now</span>
<input type="range" class="form-range" min="0" :max="maxDTE" step="1" x-model.number="dteOffset" @input="scheduleRender()" style="min-width:150px;"> <input type="range" class="form-range" min="0" :max="maxDTE" step="1" x-model.number="dteOffset" @input="scheduleRender()" style="min-width:150px;">
<span class="text-secondary small text-nowrap">Exp</span> <span class="text-secondary small text-nowrap">Exp</span>
<span class="badge bg-blue-lt text-nowrap" x-text="dteLabel"></span> <span class="badge bg-blue-lt text-nowrap" x-text="dteLabel"></span>
<small class="text-secondary w-100" x-show="hasMultiExpiry" x-text="'Remaining: ' + expiryBreakdown"></small>
</div> </div>
</div> </div>
</div> </div>
@@ -173,11 +174,14 @@
</select> </select>
<span x-show="!hasStrikeOpts(lv)" x-text="lv.strike"></span> <span x-show="!hasStrikeOpts(lv)" x-text="lv.strike"></span>
</td> </td>
<td class="mono small" style="width:8.5rem"> <td class="mono small" style="width:11rem">
<select x-show="hasExpiryOpts(lv)" class="form-select form-select-sm" :value="lv.expiry" @change="changeExpiry(lv.id, $event.target.value)" title="Change expiry — strike, entry, IV & mark update from the new chain"> <div class="d-flex align-items-center gap-1 flex-nowrap">
<template x-for="e in expiryOpts(lv)" :key="e"><option :value="e" x-text="e"></option></template> <select x-show="hasExpiryOpts(lv)" class="form-select form-select-sm" :value="lv.expiry" @change="changeExpiry(lv.id, $event.target.value)" title="Change expiry — strike, entry, IV & mark update from the new chain">
</select> <template x-for="e in expiryOpts(lv)" :key="e"><option :value="e" x-text="e"></option></template>
<span x-show="!hasExpiryOpts(lv)" x-text="lv.expiry"></span> </select>
<span x-show="!hasExpiryOpts(lv)" x-text="lv.expiry"></span>
<span class="badge bg-secondary-lt text-secondary" :title="'Days to expiry from today'" x-text="legDTEStr(lv)"></span>
</div>
</td> </td>
<td style="width:8.5rem"> <td style="width:8.5rem">
<div class="input-group input-group-sm flex-nowrap"> <div class="input-group input-group-sm flex-nowrap">
@@ -493,6 +497,22 @@
if (this.dteOffset === 0) return 'Today (' + ds + ')'; if (this.dteOffset === 0) return 'Today (' + ds + ')';
return 'T+' + this.dteOffset + 'd · ' + ds; return 'T+' + this.dteOffset + 'd · ' + ds;
}, },
get hasMultiExpiry() {
return new Set(this.activeLegs.map(l => l.expiry)).size > 1;
},
get expiryBreakdown() {
const uniq = [...new Set(this.activeLegs.map(l => l.expiry))].sort();
return uniq.map(e => {
const rem = Math.max(0, Math.round(legDTE({expiry:e}) - this.dteOffset));
return e + ' (' + rem + 'd)';
}).join(' · ');
},
legDTEStr(lv) {
const d = legDTE(lv);
if (d < 0) return 'exp';
if (d < 1) return '<1d';
return Math.round(d) + 'd';
},
get legsView() { get legsView() {
return this.legs.map(l => { return this.legs.map(l => {