diff --git a/CLAUDE.md b/CLAUDE.md index b6b96d2..2f67c40 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -56,7 +56,7 @@ globals, and 17-boot.js runs an IIFE last to bootstrap the app. | `05a-merge-open-lots.js` | 113 | `mergeOpenLots(trades, asset)` → `trades'`. Pure helper that merges all open lots for one asset (size-weighted `costBasis`, summed `lotPremiums`, earliest opener kept, CALL `lotNum` cleared). Prefers `lotEngine`, falls back to `compute` or a HOLDING/ASSIGNED heuristic for Node tests | | `05b-pnl.js` | 90 | `computePnl(trades, assetFilter, livePrices)` → `{ realised, unrealised, total, missingSpotAssets, realisedSeries, realisedByMonth }`. Cash-flow-lens P&L calculator. Realised: `Σ settled netPrem + Σ (strike − costBasis) × calledSize` (open contributions are zero). Unrealised: `Σ over open lots of (spot − costBasis) × size`, marked against raw `costBasis` (never `netCost`); assets missing spot are excluded from the sum and reported in `missingSpotAssets`. Total = Realised + Unrealised. HOLDING- and ASSIGNED-originated lots are treated symmetrically in both paths. Pure; dual-exported. ADR: `docs/adr/0003-pnl-cash-flow-lens.md` | | `05c-outcome-distribution.js` | 32 | `outcomeDistribution(trades, assetFilter)` → `[{outcome, count, premium}]`. Pure helper for the Position History outcome treemap. Excludes OPEN, orders EXPIRED/ASSIGNED/CALLED/CLOSED, nets `closeCost` from CLOSED premium (cash-flow lens). Dual-exported | -| `06-render-table.js` | 452 | `sortOpen/sortHist`, `renderExpiryTable` (today badge + mobile cards), `fetchExpiryPrices` (CoinGecko, calls full `render()` on success), `rTable` (holdings cards, open & history tables, history filter application), `rStats` (just delegates to `renderExpiryTable`), `exportHistoryCSV` (downloads filtered history as CSV) | +| `06-render-table.js` | 452 | `sortOpen/sortHist`, `renderExpiryTable(allRows)` (today badge + mobile cards — reads enriched rows from `compute`, uses `r.annual` and `_liveDte`; no longer reads raw `trades[]`), `fetchExpiryPrices` (CoinGecko, calls `render()` on success), `rTable` (holdings cards, open & history tables, history filter application), `rStats(streams, lots, allRows, displayRows)` (forwards `allRows` to `renderExpiryTable`), `exportHistoryCSV` (downloads filtered history as CSV) | | `06a-render-outcome-chart.js` | 75 | `rOutcomeChart()` — renders a horizontal treemap of `outcomeDistribution` into `#hist-outchart` when ≥10 settled trades; otherwise hides itself and shows `#hist-pills`. Each cell click toggles `setHistOutcome`. Cells coloured via CSS vars (EXPIRED/ASSIGNED/CALLED/CLOSED → green/red/orange/blue) | | `07-render-charts.js` | 640 | `setCpnlPeriod` (1M/3M/ALL), `rCpnlChart` (cumulative Realised P&L hero — sources `realisedSeries` from `computePnl` — plus secondary Realised sparkline), `rCharts` (Premium P&L total/monthly tabs — Total tab consumes `computePnl` for the Realised tile), `cOpts` (Chart.js options factory) | | `08-render.js` | 8 | `render()` — orchestrator: `compute → rStats → rTable → rOutcomeChart → rCharts` | diff --git a/docs/architecture.md b/docs/architecture.md index 5147d35..7644569 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -80,7 +80,7 @@ flowchart TD LS -->|load on boot| Trades Trades -->|per-asset slice| LE LE -->|lots + accounting| CO - CO -->|displayRows| RT + CO -->|allRows + displayRows| RT CO -->|streams| RC Trades -->|all trades| CP CP -->|realisedSeries\nrealised + unrealised| RC diff --git a/src/js/06-render-table.js b/src/js/06-render-table.js index 4142de3..b496f80 100644 --- a/src/js/06-render-table.js +++ b/src/js/06-render-table.js @@ -133,11 +133,11 @@ function _histRow(r) { + ''; } -function rStats(streams, lots, displayRows) { - renderExpiryTable(); +function rStats(streams, lots, allRows, displayRows) { + renderExpiryTable(allRows); } -function renderExpiryTable() { +function renderExpiryTable(allRows) { const wrap = document.getElementById('expiry-table-wrap'); if (!wrap) return; @@ -146,12 +146,12 @@ function renderExpiryTable() { const weekOut = new Date(today); weekOut.setDate(weekOut.getDate() + 7); - const expiring = trades.filter(t => { - if (t.outcome !== 'OPEN') return false; - if (t.type !== 'PUT' && t.type !== 'CALL') return false; - if (sFilter !== 'ALL' && t.asset !== sFilter) return false; - if (!t.expiry) return false; - const exp = new Date(t.expiry + 'T00:00:00'); + const expiring = (allRows || []).filter(r => { + if (r.outcome !== 'OPEN') return false; + if (r.type !== 'PUT' && r.type !== 'CALL') return false; + if (sFilter !== 'ALL' && r.asset !== sFilter) return false; + if (!r.expiry) return false; + const exp = new Date(r.expiry + 'T00:00:00'); return exp <= weekOut; }).sort((a, b) => new Date(a.expiry) - new Date(b.expiry) || a.asset.localeCompare(b.asset)); @@ -177,38 +177,32 @@ function renderExpiryTable() { const assetCol = { BTC: 'btc', ETH: 'eth', HYPE: 'hype', SOL: 'sol' }; - const enriched = expiring.map(t => { - const exp = new Date(t.expiry + 'T00:00:00'); - const daysLeft = Math.round((exp - today) / (1000 * 60 * 60 * 24)); - const dteLabel = daysLeft <= 0 - ? 'today' - : daysLeft + 'd'; - let aprHtml = '—'; - if (t.dte > 0 && t.strike > 0 && t.size > 0) { - const ann = (t.premium / (t.strike * t.size)) * (365 / t.dte) * 100; - aprHtml = ann.toFixed(1) + '%'; - } + const enriched = expiring.map(r => { + const exp = new Date(r.expiry + 'T00:00:00'); + const daysLeft = Math.round((exp - today) / 86400000); + const dteLabel = _liveDte(r.expiry); + const aprHtml = r.annual != null ? r.annual.toFixed(1) + '%' : '—'; let statusHtml = '—'; - const spot = livePrices[t.asset]; + const spot = livePrices[r.asset]; if (spot) { - const isPut = t.type === 'PUT'; - const isOTM = isPut ? spot > t.strike : spot < t.strike; - const pct = Math.abs((spot - t.strike) / spot * 100).toFixed(1); + const isPut = r.type === 'PUT'; + const isOTM = isPut ? spot > r.strike : spot < r.strike; + const pct = Math.abs((spot - r.strike) / spot * 100).toFixed(1); statusHtml = isOTM ? 'OTM ' + pct + '%' : 'ITM ' + pct + '%'; } - const col = assetCol[t.asset] || 'mu2'; - const platBadge = (t.platform === 'HSFC') + const col = assetCol[r.asset] || 'mu2'; + const platBadge = (r.platform === 'HSFC') ? 'HSFC' : 'RYSK'; const actionsHtml = '