Multi-symbol strategy baskets + symbol picker
The store now keeps one basket per symbol (v2 schema, auto-migrates v1). Adding a leg from a different symbol no longer wipes the basket — it creates that symbol's basket and makes it active. The Strategy page shows a symbol picker at the top (when >1 symbol is saved) to switch between them; switching auto-fetches that symbol's live marks. "Clear all" clears only the current symbol's legs; new StrategyStore.clearAll() wipes everything. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -55,13 +55,20 @@
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto" x-show="symbols.length > 1" x-cloak>
|
||||
<label class="form-label text-secondary mb-1" for="symPicker">Strategy</label>
|
||||
<select id="symPicker" class="form-select form-select-sm fw-bold" style="min-width:7rem" :value="symbol" @change="switchSymbol($event.target.value)" aria-label="Pick strategy symbol">
|
||||
<template x-for="s in symbols" :key="s"><option :value="s" x-text="s"></option></template>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h2 class="page-title">Strategy P/L Analyzer</h2>
|
||||
<div class="text-secondary mt-1" x-show="legs.length > 0" x-cloak>
|
||||
<span class="badge bg-purple-lt fs-6 me-2" x-text="strategyName"></span>
|
||||
<span x-text="symbol"></span> ·
|
||||
<span x-show="symbols.length <= 1" x-text="symbol"></span><span x-show="symbols.length <= 1"> · </span>
|
||||
<span x-text="legs.length + ' leg' + (legs.length===1?'':'s')"></span> ·
|
||||
Spot <strong class="mono" x-text="spot > 0 ? '$'+spot.toFixed(2) : '—'"></strong>
|
||||
<span x-show="symbols.length > 1" class="ms-2 text-secondary" x-text="'(' + symbols.length + ' symbols saved)'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto" x-show="legs.length > 0" x-cloak>
|
||||
@@ -291,7 +298,7 @@
|
||||
function strategyApp() {
|
||||
return {
|
||||
// state
|
||||
symbol: '', spot: 0, legs: [],
|
||||
symbol: '', symbols: [], spot: 0, legs: [],
|
||||
dteOffset: 0,
|
||||
refreshing: false, showManual: false, toast: '',
|
||||
manual: { side:'long', type:'call', qty:1, strike:null, expiry:'', entryPrice:null, ivPct:null },
|
||||
@@ -308,10 +315,19 @@
|
||||
reload() {
|
||||
const st = StrategyStore.load();
|
||||
this.symbol = st.symbol || '';
|
||||
this.symbols = st.symbols || [];
|
||||
this.spot = st.spotSnapshot || 0;
|
||||
this.legs = st.legs || [];
|
||||
if (this.dteOffset > this.maxDTE) this.dteOffset = this.maxDTE;
|
||||
if (this.legs.length > 0) this.$nextTick(() => this.renderChart());
|
||||
if (this.legs.length > 0) this.$nextTick(() => this.renderChart()); else if (this.chart) this.chart.updateSeries([{name:'P/L',data:[]},{name:'P/L',data:[]}]);
|
||||
},
|
||||
|
||||
switchSymbol(sym) {
|
||||
if (!sym || sym === this.symbol) return;
|
||||
StrategyStore.setActive(sym);
|
||||
this.dteOffset = 0;
|
||||
this.reload();
|
||||
if (this.legs.length > 0) this.reloadMarket(false);
|
||||
},
|
||||
|
||||
// ── derived ───────────────────────────────────────────
|
||||
@@ -421,10 +437,11 @@
|
||||
this.reload();
|
||||
},
|
||||
clearAll() {
|
||||
if (!confirm('Clear all legs?')) return;
|
||||
if (!confirm('Clear all ' + (this.symbol || '') + ' legs?')) return;
|
||||
StrategyStore.clear();
|
||||
this.reload();
|
||||
this.dteOffset = 0;
|
||||
if (this.chart) { this.chart.destroy(); this.chart = null; }
|
||||
this.reload(); // re-renders the chart if another symbol's basket is now active
|
||||
},
|
||||
addManualLeg() {
|
||||
const m = this.manual;
|
||||
|
||||
Reference in New Issue
Block a user