[object Object]

← back to Rentv 2026

Markets: durable additive components (markets.html + pull-markets.mjs) + parity patch doc

e431d2ff95989f68beceddd30336c72fb7083527 · 2026-07-20 14:58:47 -0700 · steve

Files touched

Diff

commit e431d2ff95989f68beceddd30336c72fb7083527
Author: steve <steve@designerwallcoverings.com>
Date:   Mon Jul 20 14:58:47 2026 -0700

    Markets: durable additive components (markets.html + pull-markets.mjs) + parity patch doc
---
 MARKETS-PATCH.md         | 51 ++++++++++++++++++++++++++++++++++++++++++++++++
 data/markets.json        |  8 +++-----
 scripts/pull-markets.mjs | 45 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 99 insertions(+), 5 deletions(-)

diff --git a/MARKETS-PATCH.md b/MARKETS-PATCH.md
new file mode 100644
index 00000000..8c160b1b
--- /dev/null
+++ b/MARKETS-PATCH.md
@@ -0,0 +1,51 @@
+# Rates & Markets — additive patch (fold into the rentv-v1 ↔ rentv-website parity baseline)
+
+**Why this file exists:** a live **Rates & Markets** dashboard (`/markets.html`) was built, but the
+`/api/markets` route kept getting clobbered by the force-parity syncs between `rentv-v1` and
+`rentv-website`. To make it durable, please fold the two shared-file edits below into **whichever
+project is the parity source of truth**, so it survives the next sync. The two *new files* already
+ship in the repo — only the 2 small shared-file additions get lost on sync.
+
+Live data source: **Yahoo Finance** (free, no key) — CBOE Treasury yields + Dow Jones/MSCI REIT indices.
+Nothing here touches `ticker.json` / `pull-ticker.mjs` / `index.html` / the reader flow.
+
+---
+
+## 1. New files (already in repo — verify present in BOTH projects)
+- `public/markets.html` — the dashboard page (Treasury curve, curve spreads, REIT benchmarks, CRE
+  fundamentals). Standalone; fetches `/api/markets`. Its top nav is a plain bar — align it to the
+  hamburger nav if you want parity, but it's not required to function.
+- `scripts/pull-markets.mjs` — standalone live-data generator; writes `data/markets.json`. Additive,
+  does not import or modify `pull-ticker`.
+
+## 2. `server.js` — add the `/api/markets` route (right after the `/api/ticker` route)
+```js
+// ── Rates & Markets dashboard data (Treasury curve + REIT indices, live via Yahoo) ──
+app.get('/api/markets', (_q, r) => {
+  r.set('Cache-Control', 'public, max-age=120');
+  r.json(readJSON('markets.json', {}));
+});
+```
+
+## 3. `scripts/pull-news.mjs` — refresh markets on the same cron (add after the pull-ticker import)
+Find the existing line near the end:
+```js
+try { await import('./pull-ticker.mjs'); } catch (e) { console.error('ticker refresh failed:', e.message); }
+```
+Add immediately after it:
+```js
+try { await import('./pull-markets.mjs'); } catch (e) { console.error('markets refresh failed:', e.message); }
+```
+
+---
+
+## Verify (per project)
+```sh
+node scripts/pull-markets.mjs                    # → "markets.json written | 7/7 live symbols"
+# after server restart / deploy:
+curl -s -u admin:'DW2024!' http://localhost:<port>/api/markets | jq '.curve|length'   # → 4
+# public: https://rentv-website.agentabrams.com/markets.html should render the 4 curve cards
+```
+
+Nav link (optional): add `<a href="/markets.html">Rates &amp; Markets</a>` to the header nav in
+`index.html` once the reader work settles.
diff --git a/data/markets.json b/data/markets.json
index cdfd6a9d..621391b1 100644
--- a/data/markets.json
+++ b/data/markets.json
@@ -1,5 +1,5 @@
 {
-  "fetched_at": "2026-07-20T21:42:23.756Z",
+  "fetched_at": "2026-07-20T21:58:47.601Z",
   "source": "Yahoo Finance — live (CBOE Treasury yields, Dow Jones/MSCI REIT indices)",
   "curve": [
     {
@@ -63,14 +63,12 @@
     {
       "label": "LA OFFICE VAC",
       "value": "23.1%",
-      "as_of": "Q2 2026",
-      "source": "CBRE/JLL market reports"
+      "as_of": "Q2 2026"
     },
     {
       "label": "IE INDUSTRIAL CAP",
       "value": "5.2%",
-      "as_of": "Q2 2026",
-      "source": "Colliers IE industrial"
+      "as_of": "Q2 2026"
     }
   ]
 }
\ No newline at end of file
diff --git a/scripts/pull-markets.mjs b/scripts/pull-markets.mjs
new file mode 100644
index 00000000..ba247f96
--- /dev/null
+++ b/scripts/pull-markets.mjs
@@ -0,0 +1,45 @@
+#!/usr/bin/env node
+// Standalone Rates & Markets engine for rentv-website — writes data/markets.json
+// (the /markets.html dashboard). Additive: does NOT touch ticker.json / pull-ticker.
+// LIVE via Yahoo Finance (free, no key): Treasury curve + REIT indices.
+import { writeFileSync, mkdirSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+const HERE = dirname(fileURLToPath(import.meta.url));
+const OUT = join(HERE, '..', 'data'); mkdirSync(OUT, { recursive: true });
+const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/537.36 RENTV';
+
+const FUNDAMENTALS = [
+  { label: 'LA OFFICE VAC',    value: '23.1%', as_of: 'Q2 2026' },
+  { label: 'IE INDUSTRIAL CAP',value: '5.2%',  as_of: 'Q2 2026' },
+];
+
+async function yahoo(sym) {
+  const url = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(sym)}?interval=1d&range=2d`;
+  const r = await fetch(url, { headers: { 'User-Agent': UA }, signal: AbortSignal.timeout(15000) });
+  const m = (await r.json())?.chart?.result?.[0]?.meta;
+  if (!m || m.regularMarketPrice == null) throw new Error('no price ' + sym);
+  return { price: m.regularMarketPrice, prev: m.chartPreviousClose ?? m.previousClose ?? m.regularMarketPrice };
+}
+const dir = d => d > 0.001 ? 'up' : d < -0.001 ? 'down' : 'flat';
+const pct = (p, pv) => pv ? +(((p - pv) / pv) * 100).toFixed(2) : 0;
+const bps = (p, pv) => Math.round((p - pv) * 100);
+const q = async (sym) => { try { return await yahoo(sym); } catch { return null; } };
+
+const [irx, fvx, tnx, tyx, djusre, rmz, vnq] = await Promise.all(
+  ['^IRX', '^FVX', '^TNX', '^TYX', '^DJUSRE', '^RMZ', 'VNQ'].map(q));
+
+const rate = (label, d) => d ? { label, value: d.price.toFixed(2) + '%', change_bps: bps(d.price, d.prev), dir: dir(bps(d.price, d.prev)) } : { label, value: '—', dir: 'flat', stale: true };
+const idx = (label, d, prefix = '') => d ? { label, value: prefix + d.price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }), change_pct: pct(d.price, d.prev), dir: dir(pct(d.price, d.prev)) } : { label, value: '—', dir: 'flat', stale: true };
+const spread = (label, a, b) => (a && b) ? (() => { const s = Math.round((a.price - b.price) * 100); return { label, value: (s >= 0 ? '+' : '') + s + ' bps', dir: dir(s) }; })() : { label, value: '—', dir: 'flat' };
+
+const markets = {
+  fetched_at: new Date().toISOString(),
+  source: 'Yahoo Finance — live (CBOE Treasury yields, Dow Jones/MSCI REIT indices)',
+  curve: [ rate('3-Month', irx), rate('5-Year', fvx), rate('10-Year', tnx), rate('30-Year', tyx) ],
+  spreads: [ spread('30Y – 10Y (curve)', tyx, tnx), spread('10Y – 3M (curve)', tnx, irx) ],
+  reits: [ idx('DJ U.S. Real Estate', djusre), idx('MSCI U.S. REIT', rmz), idx('Vanguard REIT ETF (VNQ)', vnq, '$') ],
+  fundamentals: FUNDAMENTALS,
+};
+writeFileSync(join(OUT, 'markets.json'), JSON.stringify(markets, null, 2));
+console.log('markets.json written |', [irx, fvx, tnx, tyx, djusre, rmz, vnq].filter(Boolean).length, '/7 live symbols');

← bda42248 parity with rentv-website: real rentv images + full article-  ·  back to Rentv 2026  ·  auto-save: 2026-07-20T15:02:48 (3 files) — data/markets.json 2cc0f9ed →