-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
567 lines (531 loc) · 35.4 KB
/
Copy pathindex.html
File metadata and controls
567 lines (531 loc) · 35.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="QuantPulse — a live US equity quant research terminal: streaming quotes, professional candlestick charting, factor analytics, in-browser machine learning (ridge / elastic net / neural net / gradient boosting via purged walk-forward), backtesting and risk diagnostics on 11+ years of real S&P 500 data.">
<meta name="theme-color" content="#080b1d">
<title>QuantPulse · Live US Equity Quant Terminal</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><defs><linearGradient id='g' x1='0' y1='1' x2='1' y2='0'><stop offset='0' stop-color='%2325d3e0'/><stop offset='1' stop-color='%23a78bfa'/></linearGradient></defs><rect width='32' height='32' rx='8' fill='%23080b1d'/><path d='M3 17h5l3-8 4 14 3-10 2 4h9' stroke='url(%23g)' stroke-width='2.4' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="bg-orbs" aria-hidden="true">
<div class="orb orb-a"></div>
<div class="orb orb-b"></div>
<div class="orb orb-c"></div>
</div>
<a class="skip-link" href="#main">Skip to content</a>
<!-- ================= HEADER ================= -->
<header class="topbar">
<div class="brand">
<svg class="brand-mark" viewBox="0 0 32 32" aria-hidden="true">
<defs>
<linearGradient id="brandGrad" x1="0" y1="1" x2="1" y2="0">
<stop offset="0" stop-color="#25d3e0"/><stop offset="1" stop-color="#a78bfa"/>
</linearGradient>
</defs>
<path d="M3 17h5l3-8 4 14 3-10 2 4h9" stroke="url(#brandGrad)" stroke-width="2.4"
fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<div class="brand-text">
<h1 class="brand-name">QuantPulse</h1>
<span class="brand-sub">Live US equity quant terminal</span>
</div>
</div>
<div id="market-strip" class="market-strip" aria-live="off"></div>
<div class="hd-right">
<span id="hd-state" class="hd-state">…</span>
<span id="hd-clock" class="hd-clock">--:--:-- ET</span>
<span id="live-badge" class="live-badge connecting" title="Connecting…">CONNECTING</span>
<span id="hd-updated" class="hd-updated">—</span>
<div class="seg" id="hd-basis" role="group" aria-label="Return basis" title="Return basis for analytics — PRICE excludes dividends; TOTAL adds cash dividends back on their ex-date (matters most for high-yield names)">
<button data-v="price" class="seg-btn active">PRICE</button>
<button data-v="total" class="seg-btn">TOTAL</button>
</div>
<select id="hd-interval" class="select select-sm" title="Auto-refresh interval" aria-label="Auto-refresh interval">
<option value="15">15s</option>
<option value="30" selected>30s</option>
<option value="60">60s</option>
<option value="0">Paused</option>
</select>
<button id="hd-refresh" class="icon-btn" title="Refresh live quotes" aria-label="Refresh live quotes">
<svg viewBox="0 0 20 20" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M16.5 10a6.5 6.5 0 1 1-1.9-4.6M16.5 2.5v3.2h-3.2"/></svg>
</button>
</div>
</header>
<div id="hd-note" class="hd-note hidden">
Live feed unavailable — showing the bundled snapshot. Deploy to Netlify (or run the local dev server) to enable real-time updates.
</div>
<!-- ================= TABS ================= -->
<nav class="tabs" role="tablist" aria-label="Terminal modules">
<button class="tab" id="tab-overview" role="tab" aria-selected="true" aria-controls="panel-overview">Overview</button>
<button class="tab" id="tab-terminal" role="tab" aria-selected="false" aria-controls="panel-terminal">Terminal</button>
<button class="tab" id="tab-screener" role="tab" aria-selected="false" aria-controls="panel-screener">Screener</button>
<button class="tab" id="tab-factors" role="tab" aria-selected="false" aria-controls="panel-factors">Factors</button>
<button class="tab" id="tab-backtest" role="tab" aria-selected="false" aria-controls="panel-backtest">Backtest</button>
<button class="tab" id="tab-ml" role="tab" aria-selected="false" aria-controls="panel-ml">ML Lab</button>
<button class="tab" id="tab-risk" role="tab" aria-selected="false" aria-controls="panel-risk">Risk</button>
</nav>
<main id="main">
<!-- ================= OVERVIEW ================= -->
<section id="panel-overview" class="panel" role="tabpanel" aria-labelledby="tab-overview">
<div class="stat-row" id="ov-stats"></div>
<div class="grid-2">
<article class="card">
<h2>Equal-weight universe index <span class="hint">base 100 · full history</span></h2>
<div id="ov-benchmark" class="chart-md"></div>
</article>
<article class="card">
<h2>Session movers <span class="hint">day change · live</span></h2>
<div id="ov-movers" class="chart-md"></div>
</article>
</div>
<div class="grid-2">
<article class="card">
<h2>Sector breadth <span class="hint">average day change by GICS sector</span></h2>
<div id="ov-sectors" class="chart-md"></div>
</article>
<article class="card">
<h2>Trailing 1-year return <span class="hint" data-basis-hint data-static="by ticker">by ticker</span></h2>
<div id="ov-yearret" class="chart-md"></div>
</article>
</div>
<article class="card card-wide">
<h2>Inside the terminal <span class="hint">what runs where</span></h2>
<ul class="feature-list">
<li><b>Live data pipeline</b> — a Netlify serverless function streams fresh quotes; completed sessions merge into the analytics history automatically, today's session stays a forming bar.</li>
<li><b>Professional charting</b> — candlestick / line / area charts with wheel zoom, drag pan, full crosshair, SMA/EMA/Bollinger overlays, RSI & MACD panels, log scale and 1D/5D intraday.</li>
<li><b>Quant research suite</b> — live factor cross-sections, rank-IC history with t-stats, quintile spreads, factor correlation, a position-based backtester (long & long-short, with costs) and a full risk lab.</li>
<li><b>ML lab</b> — four from-scratch machine-learning models (ridge, elastic net, neural net, gradient boosting) predicting next-month returns through a purged walk-forward protocol, with out-of-sample ICs, permutation feature importance and strategy backtests — all trained in your browser.</li>
<li><b>Zero-build static bundle</b> — every computation runs in your browser; deployable to Netlify by drag & drop.</li>
</ul>
</article>
</section>
<!-- ================= TERMINAL ================= -->
<section id="panel-terminal" class="panel hidden" role="tabpanel" aria-labelledby="tab-terminal">
<article class="card">
<div class="term-bar">
<div class="controls">
<label for="t-ticker" class="visually-hidden">Ticker</label>
<select id="t-ticker" class="select"></select>
<div class="seg" id="t-range" role="group" aria-label="Daily time range">
<button data-v="1" class="seg-btn">1M</button>
<button data-v="3" class="seg-btn">3M</button>
<button data-v="6" class="seg-btn">6M</button>
<button data-v="12" class="seg-btn">1Y</button>
<button data-v="24" class="seg-btn active">2Y</button>
<button data-v="36" class="seg-btn">3Y</button>
<button data-v="0" class="seg-btn">ALL</button>
</div>
<div class="seg" id="t-intraday" role="group" aria-label="Intraday">
<button data-v="off" class="seg-btn active">DAILY</button>
<button data-v="1d" class="seg-btn">1D 5m</button>
<button data-v="5d" class="seg-btn">5D 15m</button>
</div>
<div class="seg" id="t-type" role="group" aria-label="Chart type">
<button data-v="candle" class="seg-btn active">Candles</button>
<button data-v="line" class="seg-btn">Line</button>
<button data-v="area" class="seg-btn">Area</button>
</div>
<button id="t-log" class="seg-btn seg-standalone" title="Toggle logarithmic price scale">LOG</button>
</div>
<div class="quote" id="t-quote"></div>
</div>
<div class="term-bar term-bar-sub">
<div class="chip-row" id="t-overlays" role="group" aria-label="Overlays">
<span class="chip-label">Overlays</span>
<button class="chip-btn active" data-k="ma20">MA 20</button>
<button class="chip-btn active" data-k="ma50">MA 50</button>
<button class="chip-btn" data-k="ma200">MA 200</button>
<button class="chip-btn" data-k="ema50">EMA 50</button>
<button class="chip-btn" data-k="bb">Bollinger</button>
</div>
<div class="chip-row" id="t-panels" role="group" aria-label="Panels">
<span class="chip-label">Panels</span>
<button class="chip-btn active" data-k="vol">Volume</button>
<button class="chip-btn" data-k="rsi">RSI 14</button>
<button class="chip-btn" data-k="macd">MACD</button>
</div>
<div class="chip-row">
<span class="chip-label">Compare</span>
<select id="t-peer" class="select select-sm" aria-label="Compare with ticker"></select>
</div>
<span class="term-help">scroll = zoom · drag = pan · double-click = reset</span>
</div>
<div id="t-intraday-note" class="term-note hidden"></div>
<div id="t-candles" class="chart-xl"></div>
</article>
<div class="grid-2">
<article class="card">
<h2>Snapshot <span class="hint" id="t-snap-name"></span></h2>
<div id="t-snap-meta" class="snap-meta"></div>
<div class="kv-table" id="t-snapshot"></div>
</article>
<article class="card">
<h2>Relative strength <span class="hint">vs equal-weight universe</span></h2>
<div id="t-relative" class="chart-md"></div>
</article>
</div>
<article class="card card-wide">
<h2>Performance comparison <span class="hint">rebased to 100 over the selected daily range</span></h2>
<div id="t-compare" class="chart-md"></div>
</article>
</section>
<!-- ================= SCREENER ================= -->
<section id="panel-screener" class="panel hidden" role="tabpanel" aria-labelledby="tab-screener">
<article class="card card-wide">
<div class="scr-bar">
<h2 class="scr-title">Live cross-section <span class="hint" id="scr-count"></span></h2>
<div class="scr-controls">
<input id="scr-search" class="input" type="search" placeholder="Search ticker or company…" aria-label="Search">
<select id="scr-sector" class="select" aria-label="Filter by sector">
<option value="">All sectors</option>
</select>
<select id="scr-yld" class="select" aria-label="Filter by minimum dividend yield" title="Show only names whose trailing 12-month dividend yield is at least this">
<option value="">Any yield</option>
<option value="0.01">Yld ≥ 1%</option>
<option value="0.02">Yld ≥ 2%</option>
<option value="0.03">Yld ≥ 3%</option>
<option value="0.04">Yld ≥ 4%</option>
<option value="0.05">Yld ≥ 5%</option>
</select>
</div>
</div>
<p class="card-note">Click any row to open it in the Terminal. Day change and price are live when the feed is connected; returns, volatility, RSI and volume ratios are computed from the merged daily history. Div yld is the trailing 12-month cash dividend over the last close and stays visible under both return bases; the yield filter keeps only names paying at least the chosen trailing yield (non-payers drop out).</p>
<div class="table-wrap scr-wrap">
<table class="data-table scr-table" id="scr-table">
<thead>
<tr>
<th data-k="ticker">Ticker</th>
<th data-k="name">Company</th>
<th data-k="sector">Sector</th>
<th data-k="price">Last</th>
<th data-k="day">Day</th>
<th data-k="w1">1W</th>
<th data-k="m1">1M</th>
<th data-k="m3">3M</th>
<th data-k="ytd">YTD</th>
<th data-k="y1">1Y</th>
<th data-k="yld" title="Trailing 12-month cash dividend / last close">Div yld</th>
<th data-k="vol">Vol</th>
<th data-k="rsi">RSI</th>
<th data-k="off52">vs 52W-H</th>
<th data-k="vratio">Vol×</th>
<th>30D</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</article>
</section>
<!-- ================= FACTORS ================= -->
<section id="panel-factors" class="panel hidden" role="tabpanel" aria-labelledby="tab-factors">
<div class="chips" id="f-chips" role="group" aria-label="Factor selection"></div>
<div class="grid-2">
<article class="card">
<h2>Current cross-section <span class="hint" id="f-rank-hint"></span></h2>
<div id="f-ranks" class="chart-lg"></div>
</article>
<article class="card">
<h2>Score vs trailing 1M return <span class="hint">latest cross-section · descriptive</span></h2>
<div id="f-scatter" class="chart-lg"></div>
<p class="card-note">Each dot is one ticker: x = factor score today, y = return over the past 21 trading days. This view is descriptive; predictive power is measured by the rank-IC series and quintile spreads below.</p>
</article>
</div>
<div class="grid-2">
<article class="card">
<h2>Monthly rank IC <span class="hint" id="f-ic-hint"></span></h2>
<div id="f-ic-series" class="chart-md"></div>
</article>
<article class="card">
<h2>Quintile spread <span class="hint">avg next-21-day return by score quintile</span></h2>
<div id="f-quintiles" class="chart-md"></div>
</article>
</div>
<div class="grid-2">
<article class="card">
<h2>Factor scoreboard <span class="hint">monthly rank ICs · click a row to select</span></h2>
<div class="table-wrap">
<table class="data-table" id="f-summary">
<thead>
<tr><th>Factor</th><th>IC mean</th><th>IC IR</th><th>t-stat</th><th>Hit rate</th><th>Q1−Q5 /mo</th><th>Months</th></tr>
</thead>
<tbody></tbody>
</table>
</div>
<p class="card-note">On a 38-name mega-cap panel, cross-sectional edges are thin: classic 12-1 momentum has all but vanished over the last decade, while high-volatility names outperformed (negative low-vol IC). That is a real property of this universe — not a bug.</p>
</article>
<article class="card">
<h2>Factor correlation <span class="hint">score Spearman ρ · last 36 months</span></h2>
<div id="f-corr" class="chart-md"></div>
</article>
</div>
</section>
<!-- ================= BACKTEST ================= -->
<section id="panel-backtest" class="panel hidden" role="tabpanel" aria-labelledby="tab-backtest">
<article class="card card-wide">
<div class="card-head">
<h2>Strategy <span class="hint" data-basis-hint data-static="position-based engine · runs in your browser">position-based engine · runs in your browser</span></h2>
<div class="controls">
<label for="b-strat" class="visually-hidden">Signal</label>
<select id="b-strat" class="select"></select>
<div class="seg" id="b-mode" role="group" aria-label="Book mode">
<button data-v="long" class="seg-btn active">Long-only</button>
<button data-v="longshort" class="seg-btn">Long-Short</button>
</div>
<div class="seg" id="b-topn" role="group" aria-label="Portfolio size">
<button data-v="5" class="seg-btn">Top 5</button>
<button data-v="10" class="seg-btn active">Top 10</button>
<button data-v="15" class="seg-btn">Top 15</button>
<button data-v="20" class="seg-btn">Top 20</button>
</div>
<div class="seg" id="b-rebal" role="group" aria-label="Rebalance frequency">
<button data-v="1" class="seg-btn active">Monthly</button>
<button data-v="3" class="seg-btn">Quarterly</button>
</div>
<div class="seg" id="b-cost" role="group" aria-label="Transaction cost">
<button data-v="0" class="seg-btn">0 bps</button>
<button data-v="10" class="seg-btn active">10 bps</button>
<button data-v="25" class="seg-btn">25 bps</button>
</div>
<button id="b-export" class="seg-btn seg-standalone" title="Export the equity curve as CSV">⤓ CSV</button>
</div>
</div>
<div class="bt-metrics" id="b-metrics"></div>
</article>
<div class="grid-2">
<article class="card">
<h2>Equity curve <span class="hint" id="b-eq-hint">net of costs · log scale</span></h2>
<div id="b-equity" class="chart-lg"></div>
</article>
<article class="card">
<h2>Drawdown <span class="hint">from running peak</span></h2>
<div id="b-drawdown" class="chart-lg"></div>
</article>
</div>
<div class="grid-2">
<article class="card">
<h2>Monthly returns <span class="hint">% per month</span></h2>
<div id="b-monthly" class="chart-md"></div>
<p class="card-note" id="b-monthly-note"></p>
</article>
<article class="card">
<h2>Current holdings <span class="hint">latest rebalance · live prices</span></h2>
<div class="table-wrap holdings-wrap">
<table class="data-table" id="b-holdings">
<thead><tr><th>#</th><th>Ticker</th><th>Company</th><th>Sector</th><th>Weight</th><th>Last</th><th>Day</th></tr></thead>
<tbody></tbody>
</table>
</div>
</article>
</div>
</section>
<!-- ================= ML LAB ================= -->
<section id="panel-ml" class="panel hidden" role="tabpanel" aria-labelledby="tab-ml">
<article class="card card-wide">
<div class="card-head">
<h2>Predictive models <span class="hint">purged walk-forward · trained in your browser</span></h2>
<div class="controls">
<label for="ml-model" class="visually-hidden">Model</label>
<select id="ml-model" class="select">
<option value="ridge">Ridge Regression</option>
<option value="enet">Elastic Net</option>
<option value="mlp">Neural Net (MLP)</option>
<option value="gbm">Gradient Boosting</option>
<option value="ensemble">Ensemble (all four)</option>
</select>
<div class="seg" id="ml-window" role="group" aria-label="Training window">
<button data-v="36" class="seg-btn">36M</button>
<button data-v="60" class="seg-btn active">60M</button>
<button data-v="96" class="seg-btn">96M</button>
</div>
<button id="ml-run" class="seg-btn seg-standalone ml-run-btn" title="Retrain the selected model from scratch (seeded RNG — runs are reproducible, so results repeat exactly)">▶ Run walk-forward</button>
<button id="ml-compare" class="seg-btn seg-standalone" title="Run every model through the same protocol and fill the scoreboard">Benchmark all</button>
</div>
</div>
<div class="ml-progress-wrap">
<div class="ml-progress" id="ml-progress"><div id="ml-progress-bar"></div></div>
<span id="ml-status" class="ml-status"></span>
</div>
<div class="stat-row" id="ml-stats"></div>
</article>
<div class="grid-2">
<article class="card">
<h2>Out-of-sample rank IC <span class="hint" id="ml-ic-hint"></span></h2>
<div id="ml-ic" class="chart-md"></div>
</article>
<article class="card">
<h2>OOS quintile spread <span class="hint">avg next-21d return by predicted-score quintile</span></h2>
<div id="ml-quintiles" class="chart-md"></div>
</article>
</div>
<div class="grid-2">
<article class="card">
<h2>Feature importance <span class="hint">OOS permutation · last 12 test months</span></h2>
<div id="ml-importance" class="chart-lg"></div>
<p class="card-note">Permutation importance: shuffle one feature, re-predict, and measure how far the model's out-of-sample rank IC drops. Averaged over the last twelve walk-forward test months — importance measured where it matters, out of sample.</p>
</article>
<article class="card">
<h2>Model scoreboard <span class="hint">identical purged walk-forward protocol</span></h2>
<div class="table-wrap">
<table class="data-table" id="ml-compare-table">
<thead><tr><th>Model</th><th>OOS IC</th><th>IC IR</th><th>t-stat</th><th>Hit rate</th><th>Q1−Q5 /mo</th><th>Fit time</th></tr></thead>
<tbody></tbody>
</table>
</div>
<p class="card-note">Composite (7-factor) is the no-ML baseline. On a 38-name mega-cap panel out-of-sample edges are thin — read differences within ±1 t-stat as noise, not skill.</p>
</article>
</div>
<article class="card card-wide">
<div class="card-head">
<h2>ML strategy backtest <span class="hint" id="ml-bt-hint">monthly rebalance on model scores · net of costs</span></h2>
<div class="controls">
<div class="seg" id="ml-mode" role="group" aria-label="Book mode">
<button data-v="long" class="seg-btn active">Long-only</button>
<button data-v="longshort" class="seg-btn">Long-Short</button>
</div>
<div class="seg" id="ml-topn" role="group" aria-label="Portfolio size">
<button data-v="5" class="seg-btn">Top 5</button>
<button data-v="10" class="seg-btn active">Top 10</button>
<button data-v="20" class="seg-btn">Top 20</button>
</div>
<div class="seg" id="ml-cost" role="group" aria-label="Transaction cost">
<button data-v="0" class="seg-btn">0 bps</button>
<button data-v="10" class="seg-btn active">10 bps</button>
<button data-v="25" class="seg-btn">25 bps</button>
</div>
<button id="ml-csv" class="seg-btn seg-standalone" title="Export the ML equity curve as CSV">⤓ CSV</button>
</div>
</div>
<div class="bt-metrics" id="ml-bt-metrics"></div>
<div id="ml-equity" class="chart-lg"></div>
<p class="card-note" id="ml-bt-note"></p>
</article>
<div class="grid-2">
<article class="card">
<h2>Model's current picks <span class="hint" id="ml-picks-hint"></span></h2>
<div class="table-wrap holdings-wrap">
<table class="data-table" id="ml-holdings">
<thead><tr><th>#</th><th>Ticker</th><th>Company</th><th>Sector</th><th>Score</th><th>Pred. 1M</th><th>Day</th></tr></thead>
<tbody></tbody>
</table>
</div>
</article>
<article class="card">
<h2>How it works <span class="hint">methodology in one minute</span></h2>
<ul class="feature-list">
<li><b>Features</b> — nine cross-sectional signals (momentum 3M/6M/12-1, 1M reversal, 20D & 60D low-vol, RSI-14, 52W-high proximity, 1Y relative strength), winsorised at ±3σ and z-scored across the universe every month-end. Volume-trend is excluded on purpose: the bundle ships candle volume for recent years only.</li>
<li><b>Targets</b> — each stock's next 21 trading days' return, i.e. a one-month horizon.</li>
<li><b>Purged walk-forward</b> — at every month-end the model retrains on the trailing window; training rows whose 21-day label has not fully realised before the test date are embargoed, so no label can leak into a live prediction. Nothing is ever trained on the future.</li>
<li><b>Four from-scratch models</b> — ridge regression (closed form), elastic net (coordinate descent), a one-hidden-layer neural network (ReLU + Adam) and gradient boosting over histogram regression trees — plus their equal-weight ensemble. Zero ML libraries; seeded RNG for reproducible runs.</li>
<li><b>Evaluation</b> — out-of-sample Spearman rank IC per test month, quintile spreads, hit rates and t-stats; permutation feature importance measured out-of-sample; the same position engine as the Backtest tab turns scores into tradable books.</li>
</ul>
</article>
</div>
</section>
<!-- ================= RISK ================= -->
<section id="panel-risk" class="panel hidden" role="tabpanel" aria-labelledby="tab-risk">
<div class="stat-row" id="r-stats"></div>
<article class="card card-wide">
<div class="card-head">
<h2>Correlation matrix <span class="hint">daily returns · grouped by sector</span></h2>
<div class="seg" id="r-lookback" role="group" aria-label="Correlation lookback">
<button data-v="252" class="seg-btn">1Y</button>
<button data-v="504" class="seg-btn active">2Y</button>
<button data-v="9999" class="seg-btn">ALL</button>
</div>
</div>
<div id="r-heatmap" class="chart-xl"></div>
</article>
<div class="grid-2">
<article class="card">
<h2>Risk / return map <span class="hint">annualized · full sample · colour = max drawdown</span></h2>
<div id="r-scatter" class="chart-lg"></div>
<p class="card-note">x = annualized volatility, y = annualized return. Upper-left names earned more per unit of risk.</p>
</article>
<article class="card">
<h2>Asset risk table <span class="hint" data-basis-hint data-static="click a header to sort">click a header to sort</span></h2>
<div class="table-wrap risk-table-wrap">
<table class="data-table" id="r-table">
<thead>
<tr>
<th data-k="ticker">Ticker</th>
<th data-k="vol">Vol</th>
<th data-k="sharpe" title="Sharpe ratio (rf = 0); the amber chip is the dividend uplift TOTAL − PRICE">Sharpe</th>
<th data-k="sortino">Sortino</th>
<th data-k="maxdd">Max DD</th>
<th data-k="var95">VaR 95</th>
<th data-k="cvar95">CVaR 95</th>
<th data-k="beta">Beta</th>
<th data-k="cagr" title="Compound annual growth rate; the amber chip is the dividend uplift TOTAL − PRICE">CAGR</th>
<th data-k="yld" title="Trailing 12-month cash dividend / last close">Div yld</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</article>
</div>
<div class="grid-2">
<article class="card">
<div class="card-head">
<h2>Rolling volatility <span class="hint">60-day · annualized</span></h2>
<label for="r-ticker" class="visually-hidden">Ticker</label>
<select id="r-ticker" class="select select-sm"></select>
</div>
<div id="r-rolling" class="chart-md"></div>
</article>
<article class="card">
<h2>Underwater plot <span class="hint">drawdown from all-time peak</span></h2>
<div id="r-underwater" class="chart-md"></div>
</article>
</div>
<article class="card card-wide">
<h2>Return distribution <span class="hint" data-basis-hint data-static="daily · VaR & CVaR at 95%">daily · VaR & CVaR at 95%</span></h2>
<div class="hist-wrap"><canvas id="r-histogram"></canvas></div>
</article>
</section>
</main>
<footer class="footer">
<span>QuantPulse · data through <b id="ft-date"></b> · source: Yahoo Finance (split-adjusted)</span>
<span id="ft-splits" class="ft-splits hidden"></span>
<span class="footer-links"><a href="#" id="ft-methodology">Methodology</a></span>
<span class="footer-disclaimer">For research and education only — not investment advice.</span>
</footer>
<div id="modal-methodology" class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
<div class="modal-box">
<div class="modal-head">
<h2 id="modal-title">Methodology & data notes</h2>
<button id="modal-close" class="icon-btn" aria-label="Close">×</button>
</div>
<div class="modal-body">
<h3>Data</h3>
<p>The bundle ships split-adjusted daily OHLCV for 38 US mega caps across all 11 GICS sectors (Jan 2015 → snapshot date), sourced from Yahoo Finance. A Netlify serverless function (<code>/api/quotes</code>) fetches fresh data server-side; completed sessions are merged into the history and today's session is carried as a forming bar that never contaminates backtests or factor statistics.</p>
<h3>Factors</h3>
<p>Seven cross-sectional signals are computed client-side from the merged history: momentum 12-1 and 6M, 1M reversal, 20-day low volatility, RSI-14 (Cutler SMA variant, negated), 52-week-high proximity and 20/120-day volume trend. The composite is the cross-sectional average of each factor's z-score. Efficacy is measured with Spearman rank ICs: at each month-end the score is correlated with the next 21 trading days' return. IC history shown here is recomputed live from the merged panel (falling back to the offline bundle where candle volume history is unavailable), so it extends automatically as new sessions merge.</p>
<h3>Backtests</h3>
<p>Positions are held between rebalance dates with daily weight drift (buys-and-holds within each period); transaction costs are charged as one-way turnover × cost bps at each rebalance. Fills assume the rebalance day's close. Long-short books split capital 50/50 between top and bottom scores. VaR is the empirical 5th percentile of daily returns; CVaR is the mean of the tail. Alpha and beta come from a daily OLS regression on the equal-weight universe. No borrow costs, dividends-as-cash or slippage are modelled.</p>
<h3>Machine learning</h3>
<p>The ML lab predicts cross-sectional next-month returns from nine z-scored features. Every month-end the selected model is retrained on the trailing window and asked to score the current cross-section; predictions, ICs and quintile spreads are strictly out-of-sample. Training rows whose 21-day label has not fully realised before the test date are embargoed (a purged walk-forward), features are winsorised and standardised within each cross-section, and a fixed random seed makes runs reproducible. Four models are implemented from scratch — ridge regression, elastic net, a one-hidden-layer neural network (Adam) and gradient-boosted histogram trees — plus an equal-weight ensemble. Feature importance is measured by permuting each feature out-of-sample over the last twelve test months. Heavy models train in a few seconds because the maths runs on flat Float32Array buffers with no allocations in the inner loops.</p>
<h3>Live updates</h3>
<p>Quotes refresh on the selected interval while the tab is visible; the endpoint alternates between <code>/api/quotes</code> and direct fallbacks. If the live feed is unreachable, the terminal degrades gracefully to the bundled snapshot — every module keeps working.</p>
<h3>Corporate actions — splits & dividends</h3>
<p>The live feed carries Yahoo's split and dividend event calendar. When a split's ex-date falls after the snapshot date, the entire bundled history for that stock is rescaled to the new share basis (prices × denominator/numerator, volumes × numerator/denominator) before the merge — so a 10-for-1 split never contaminates returns, factors, backtests or risk stats with a fake −90% day, and split events that arrive after their bar has merged are repaired retroactively. The split detector factors cash dividends out of boundary gaps before judging them, so an ex-dividend drop is never mistaken for a split. Boundary gaps that neither a split event nor a cash dividend explains are re-checked against a one-year event calendar before anything is adjusted — and when the evidence is inconclusive, QuantPulse warns and leaves the data untouched rather than guessing silently.</p>
<h3>Return basis — price vs total return</h3>
<p>The header's PRICE / TOTAL switch selects the return convention for analytics. PRICE chains raw closes, so cash dividends appear as ex-date price drops — the honest choice when comparing price charts. TOTAL adds each cash dividend back on its ex-date ((close + dividend) / previous close − 1), the standard approximation to a dividend-reinvested index — the honest choice for high-yield names, where price return understates realized performance year after year. The basis propagates to risk statistics (vol, Sharpe, Sortino, VaR/CVaR, beta, correlations, drawdowns), backtests and their benchmark, IC and quintile forward returns, ML training targets, screener windows and the universe index. Candlesticks, the Terminal's price charts and factor signals themselves stay price-based — a candle cannot embed a cash payout, momentum-type signals are classically price-based, and the low-volatility signal chains raw closes so no ranking quietly changes with the basis. The dividend calendar (1,400+ ex-dates since 2015) ships with the bundle and extends live as new ex-dates merge. Day-change percentages on quote screens remain price-based by convention. To make the basis switch concrete, the risk table's CAGR and Sharpe cells carry an amber chip with the dividend uplift (total minus price basis, full sample) — sort the table by Div yld and toggle TOTAL to see high-yield names like PFE, DUK or AMT gain several points of CAGR per year, while the screener's Div yld column ranks the same names without switching views, and its yield filter narrows the cross-section to names paying at least a chosen trailing yield. The Backtest lab's CAGR and Sharpe cards carry the same chip: the engine reruns the identical holdings path on the other basis's return chain, so the chip is the strategy's dividend contribution under the same weights and costs — positive for long books, and negative for long-short books whose short legs owe the ex-dividend drop.</p>
</div>
</div>
</div>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<div id="tooltip" class="tooltip hidden" role="tooltip"></div>
<script src="js/data.js"></script>
<script src="js/divs.js"></script>
<script src="js/charts.js"></script>
<script src="js/live.js"></script>
<script src="js/core.js"></script>
<script src="js/ml.js"></script>
<script src="js/app.js"></script>
</body>
</html>