desic-okx-agent 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/i18n/messages.d.ts +27 -0
- package/dist/i18n/messages.js +55 -1
- package/dist/i18n/messages.js.map +1 -1
- package/dist/report/brand-font.d.ts +1 -0
- package/dist/report/brand-font.js +4 -0
- package/dist/report/brand-font.js.map +1 -0
- package/dist/report/chart-script.d.ts +3 -8
- package/dist/report/chart-script.js +735 -115
- package/dist/report/chart-script.js.map +1 -1
- package/dist/report/compare-html.d.ts +1 -6
- package/dist/report/compare-html.js +177 -122
- package/dist/report/compare-html.js.map +1 -1
- package/dist/report/compare-script.d.ts +1 -11
- package/dist/report/compare-script.js +310 -64
- package/dist/report/compare-script.js.map +1 -1
- package/dist/report/html.d.ts +1 -1
- package/dist/report/html.js +846 -268
- package/dist/report/html.js.map +1 -1
- package/package.json +1 -1
- package/skills/okx-strategy-research/SKILL.md +50 -19
- package/skills/okx-strategy-research/agents/openai.yaml +1 -1
- package/skills/okx-strategy-research/references/tools-and-data.md +17 -4
package/dist/report/html.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { activeLocale, activeMessages } from "../i18n/locale.js";
|
|
2
2
|
import { MINIMUM_ANNUALIZATION_DAYS } from "../strategy/constants.js";
|
|
3
3
|
import { CHART_SCRIPT } from "./chart-script.js";
|
|
4
|
+
import { BRAND_FONT_WOFF2 } from "./brand-font.js";
|
|
4
5
|
/**
|
|
5
6
|
* Renders a self-contained HTML report.
|
|
6
7
|
*
|
|
@@ -16,7 +17,7 @@ export function renderReportHtml(input) {
|
|
|
16
17
|
const { run, equity, trades } = input;
|
|
17
18
|
const text = activeMessages();
|
|
18
19
|
const metrics = (run.metrics ?? {});
|
|
19
|
-
const sampled = downsample(equity,
|
|
20
|
+
const sampled = downsample(equity, 3_000);
|
|
20
21
|
const drawdown = drawdownSeries(sampled, num(metrics.initialEquityUsdt) ?? 10_000);
|
|
21
22
|
return `<!DOCTYPE html>
|
|
22
23
|
<html lang="${activeLang()}">
|
|
@@ -24,37 +25,47 @@ export function renderReportHtml(input) {
|
|
|
24
25
|
<meta charset="utf-8">
|
|
25
26
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
26
27
|
<title>${escapeHtml(run.id)} · ${escapeHtml(run.strategyName)}</title>
|
|
27
|
-
<style
|
|
28
|
+
<style>@font-face{font-family:BubbledotICG-FinePos;src:url(data:font/woff2;base64,${BRAND_FONT_WOFF2}) format("woff2");font-weight:400;font-style:normal;font-display:swap}${STYLES}</style>
|
|
28
29
|
</head>
|
|
29
|
-
<body>
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<p class="sub">${escapeHtml(run.instId)} · ${escapeHtml(run.id)}</p>
|
|
35
|
-
</div>
|
|
36
|
-
<div class="headline ${sign(num(metrics.returnPct))}">
|
|
37
|
-
<span class="value">${signed(num(metrics.returnPct), "%")}</span>
|
|
38
|
-
<span class="label">${escapeHtml(text.returnLabel)}</span>
|
|
39
|
-
</div>
|
|
40
|
-
</header>
|
|
41
|
-
|
|
42
|
-
${renderFacts(run, metrics, text)}
|
|
30
|
+
<body class="single-report">
|
|
31
|
+
<div class="scroll-progress" aria-hidden="true"></div>
|
|
32
|
+
${renderCommandRail(text)}
|
|
33
|
+
<main class="report">
|
|
34
|
+
${renderCover(run, metrics, text)}
|
|
43
35
|
${renderWarnings(metrics, text)}
|
|
44
36
|
|
|
45
|
-
<section>
|
|
46
|
-
<
|
|
47
|
-
|
|
37
|
+
<section class="observatory-section reveal reveal-2" id="observatory" data-report-section>
|
|
38
|
+
<div class="observatory-heading">
|
|
39
|
+
${renderSectionHeading("01", text.observatoryHeading)}
|
|
40
|
+
${renderModeControls(text)}
|
|
41
|
+
</div>
|
|
42
|
+
<div class="observatory-frame" data-mode="overview">
|
|
43
|
+
<div class="plot-module">
|
|
44
|
+
${renderRangeControls(text)}
|
|
45
|
+
${renderChart(sampled, drawdown, trades, num(metrics.initialEquityUsdt) ?? 10_000, text, equity)}
|
|
46
|
+
${renderWindowMetrics(run, metrics, text)}
|
|
47
|
+
</div>
|
|
48
|
+
<aside class="telemetry-module">
|
|
49
|
+
${renderExecutionOrbit(metrics, text)}
|
|
50
|
+
${renderKeyMetrics(metrics, text)}
|
|
51
|
+
</aside>
|
|
52
|
+
</div>
|
|
53
|
+
${renderMetricGroups(metrics, text)}
|
|
48
54
|
</section>
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
<section class="report-section regime-section" id="regime" data-report-section>
|
|
57
|
+
${renderSectionHeading("02", text.regimeHeading)}
|
|
58
|
+
<div class="regime-layout">
|
|
59
|
+
${renderMonthly(equity, text)}
|
|
60
|
+
${renderDistribution(trades, text)}
|
|
61
|
+
</div>
|
|
62
|
+
</section>
|
|
51
63
|
${renderAssumptions(run, text)}
|
|
52
|
-
${renderMonthly(equity, text)}
|
|
53
|
-
${renderDistribution(trades, text)}
|
|
54
64
|
${renderTrades(trades, text)}
|
|
55
65
|
|
|
56
|
-
<footer>
|
|
66
|
+
<footer class="report-footer">
|
|
57
67
|
<p>${escapeHtml(text.simulationNote)}</p>
|
|
68
|
+
<p class="type-credit">BubbledotICG-FinePos · Web Fonts · CC BY 4.0</p>
|
|
58
69
|
<p class="mono">${escapeHtml(run.dataSnapshotId ?? "-")}</p>
|
|
59
70
|
</footer>
|
|
60
71
|
</main>
|
|
@@ -62,12 +73,150 @@ export function renderReportHtml(input) {
|
|
|
62
73
|
</body>
|
|
63
74
|
</html>`;
|
|
64
75
|
}
|
|
76
|
+
function renderCover(run, metrics, text) {
|
|
77
|
+
const returnPct = num(metrics.returnPct);
|
|
78
|
+
const statusTone = run.status === "completed" ? "complete" : run.status === "failed" ? "failed" : "active";
|
|
79
|
+
const netProfit = num(metrics.netProfitUsdt);
|
|
80
|
+
return `<header class="observatory-mast">
|
|
81
|
+
<div class="mast-rail reveal reveal-1">
|
|
82
|
+
<span class="wordmark">DESIC / OBSERVATORY</span>
|
|
83
|
+
<span class="run-status status-${statusTone}"><i></i>${escapeHtml(run.status)}</span>
|
|
84
|
+
<span class="run-id mono">${escapeHtml(run.id)}</span>
|
|
85
|
+
<button class="theme-toggle" type="button" data-theme-toggle aria-label="${escapeHtml(text.themeLabel)}" aria-pressed="false"><span aria-hidden="true"></span></button>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="mast-grid reveal reveal-1">
|
|
88
|
+
<div class="mast-identity">
|
|
89
|
+
<p class="instrument mono">${escapeHtml(run.instId)}</p>
|
|
90
|
+
<h1>${breakableIdentifier(run.strategyName)}</h1>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="mast-outcome ${sign(returnPct)}">
|
|
93
|
+
<span>${escapeHtml(text.returnLabel)}</span>
|
|
94
|
+
<strong>${signed(returnPct, "%")}</strong>
|
|
95
|
+
<small>${signed(netProfit, "")} USDT</small>
|
|
96
|
+
</div>
|
|
97
|
+
${renderFacts(run, metrics, text)}
|
|
98
|
+
</div>
|
|
99
|
+
</header>`;
|
|
100
|
+
}
|
|
101
|
+
function renderCommandRail(text) {
|
|
102
|
+
const items = [
|
|
103
|
+
["observatory", "01", "OBS", text.observatoryHeading],
|
|
104
|
+
["regime", "02", "MAP", text.regimeHeading],
|
|
105
|
+
["method", "03", "MODEL", text.assumptionsHeading],
|
|
106
|
+
["trades", "04", "TAPE", text.tradesHeading]
|
|
107
|
+
];
|
|
108
|
+
return `<nav class="command-rail" aria-label="${escapeHtml(text.observatoryHeading)}">${items
|
|
109
|
+
.map(([id, number, short, label], index) => `<a href="#${id}" data-section-link="${id}"${index === 0 ? ' class="active"' : ""} aria-label="${escapeHtml(label)}"><span>${number}</span><b>${short}</b></a>`)
|
|
110
|
+
.join("")}</nav>`;
|
|
111
|
+
}
|
|
112
|
+
function renderModeControls(text) {
|
|
113
|
+
const modes = [
|
|
114
|
+
["overview", text.modeOverview],
|
|
115
|
+
["equity", text.modeEquity],
|
|
116
|
+
["drawdown", text.modeDrawdown],
|
|
117
|
+
["trades", text.modeTrades]
|
|
118
|
+
];
|
|
119
|
+
return `<div class="mode-controls" role="group" aria-label="${escapeHtml(text.observatoryHeading)}">${modes
|
|
120
|
+
.map(([mode, label], index) => `<button type="button" data-chart-mode="${mode}" aria-pressed="${index === 0 ? "true" : "false"}">${escapeHtml(label)}</button>`)
|
|
121
|
+
.join("")}</div>`;
|
|
122
|
+
}
|
|
123
|
+
function renderRangeControls(text) {
|
|
124
|
+
const ranges = [["30", "30D"], ["90", "90D"], ["180", "180D"], ["all", text.rangeAll]];
|
|
125
|
+
return `<div class="range-toolbar">
|
|
126
|
+
<span>${escapeHtml(text.selectedWindow)}</span>
|
|
127
|
+
<div role="group" aria-label="${escapeHtml(text.selectedWindow)}">${ranges
|
|
128
|
+
.map(([days, label], index) => `<button type="button" data-range-days="${days}" aria-pressed="${index === ranges.length - 1 ? "true" : "false"}">${escapeHtml(label)}</button>`)
|
|
129
|
+
.join("")}</div>
|
|
130
|
+
</div>`;
|
|
131
|
+
}
|
|
132
|
+
function renderWindowMetrics(run, metrics, text) {
|
|
133
|
+
const start = num(run.assumptions?.evaluationStartMs);
|
|
134
|
+
const end = num(run.assumptions?.evaluationEndMs);
|
|
135
|
+
const period = start === null || end === null ? "-" : `${isoMinute(start)} — ${isoMinute(end)}`;
|
|
136
|
+
const returnPct = num(metrics.returnPct);
|
|
137
|
+
const drawdown = num(metrics.maxDrawdownPct);
|
|
138
|
+
return `<div class="window-readouts" aria-live="polite">
|
|
139
|
+
<div class="window-period"><span>${escapeHtml(text.selectedWindow)}</span><strong class="mono" id="window-period">${escapeHtml(period)}</strong></div>
|
|
140
|
+
<div><span>${escapeHtml(text.returnLabel)}</span><strong id="window-return" class="${sign(returnPct)}">${signed(returnPct, "%")}</strong></div>
|
|
141
|
+
<div><span>${escapeHtml(text.maxDrawdown)}</span><strong id="window-drawdown" class="down">${plain(drawdown, "%")}</strong></div>
|
|
142
|
+
<div><span>${escapeHtml(text.hoverEquity)}</span><strong id="window-equity">${plain(num(metrics.finalEquityUsdt), "")} USDT</strong></div>
|
|
143
|
+
</div>`;
|
|
144
|
+
}
|
|
145
|
+
function renderExecutionOrbit(metrics, text) {
|
|
146
|
+
const trades = Math.max(0, num(metrics.tradeCount) ?? 0);
|
|
147
|
+
const longTrades = Math.max(0, num(metrics.longTradeCount) ?? 0);
|
|
148
|
+
const shortTrades = Math.max(0, num(metrics.shortTradeCount) ?? 0);
|
|
149
|
+
const longPct = trades > 0 ? Math.min(100, Math.max(0, (longTrades / trades) * 100)) : 0;
|
|
150
|
+
const shortPct = trades > 0 ? Math.min(100, Math.max(0, (shortTrades / trades) * 100)) : 0;
|
|
151
|
+
const winPct = trades > 0 ? Math.min(100, Math.max(0, num(metrics.winRatePct) ?? 0)) : 0;
|
|
152
|
+
const lossPct = trades > 0 ? Math.max(0, 100 - winPct) : 0;
|
|
153
|
+
const wins = Math.round((trades * winPct) / 100);
|
|
154
|
+
const losses = Math.max(0, Math.round(trades) - wins);
|
|
155
|
+
const feePct = Math.max(0, num(metrics.feesPctOfGross) ?? 0);
|
|
156
|
+
const feeArc = Math.min(100, feePct);
|
|
157
|
+
const retainedPct = Math.max(0, 100 - feeArc);
|
|
158
|
+
const defaultValue = plain(num(metrics.profitFactor), "");
|
|
159
|
+
const tradeDetail = `${formatNumber(trades)} ${text.tradesLabel}`;
|
|
160
|
+
const segment = (key, className, radius, value, offset, label, detail, displayValue = `${value.toFixed(1)}%`) => {
|
|
161
|
+
const dash = `${value.toFixed(2)} ${(100 - value).toFixed(2)}`;
|
|
162
|
+
const dashOffset = (-offset).toFixed(2);
|
|
163
|
+
const aria = `${label} ${displayValue}, ${detail}`;
|
|
164
|
+
return `<circle class="orbit-segment ${className}" data-orbit-shape="${key}" pathLength="100" stroke-dasharray="${dash}" stroke-dashoffset="${dashOffset}" cx="140" cy="140" r="${radius}" aria-hidden="true"/>
|
|
165
|
+
<circle class="orbit-hit" pathLength="100" stroke-dasharray="${dash}" stroke-dashoffset="${dashOffset}" cx="140" cy="140" r="${radius}" tabindex="0" focusable="true" role="button" aria-pressed="false" aria-label="${escapeHtml(aria)}" data-orbit-key="${key}" data-orbit-label="${escapeHtml(label)}" data-orbit-value="${escapeHtml(displayValue)}" data-orbit-detail="${escapeHtml(detail)}"/>`;
|
|
166
|
+
};
|
|
167
|
+
return `<div class="execution-orbit">
|
|
168
|
+
<div class="orbit-heading"><span>${escapeHtml(text.executionOrbit)}</span><small>${tradeDetail}</small></div>
|
|
169
|
+
<div class="orbit-visual" role="group" aria-label="${escapeHtml(text.executionOrbit)}" data-default-label="${escapeHtml(text.profitFactor)}" data-default-value="${escapeHtml(defaultValue)}" data-default-detail="${escapeHtml(tradeDetail)}">
|
|
170
|
+
<svg viewBox="0 0 280 280" role="group" aria-label="${escapeHtml(text.executionOrbit)}">
|
|
171
|
+
<circle class="orbit-track" cx="140" cy="140" r="112" aria-hidden="true"/>
|
|
172
|
+
${segment("short", "orbit-short", 112, shortPct, longPct, text.shortLabel, `${formatNumber(shortTrades)} / ${tradeDetail}`)}
|
|
173
|
+
${segment("long", "orbit-long", 112, longPct, 0, text.longLabel, `${formatNumber(longTrades)} / ${tradeDetail}`)}
|
|
174
|
+
<circle class="orbit-track" cx="140" cy="140" r="84" aria-hidden="true"/>
|
|
175
|
+
${segment("loss", "orbit-loss", 84, lossPct, winPct, text.lossesLabel, `${formatNumber(losses)} / ${tradeDetail}`)}
|
|
176
|
+
${segment("win", "orbit-win", 84, winPct, 0, text.winsLabel, `${formatNumber(wins)} / ${tradeDetail}`)}
|
|
177
|
+
<circle class="orbit-track" cx="140" cy="140" r="56" aria-hidden="true"/>
|
|
178
|
+
${segment("retained", "orbit-retained", 56, retainedPct, feeArc, text.retainedLabel, text.netOfFees)}
|
|
179
|
+
${segment("fee", "orbit-fee", 56, feeArc, 0, text.feesLabel, `${plain(num(metrics.totalFeesUsdt), "")} USDT`, `${feePct.toFixed(1)}%`)}
|
|
180
|
+
</svg>
|
|
181
|
+
<div class="orbit-core" aria-live="polite" aria-atomic="true"><span>${escapeHtml(text.profitFactor)}</span><strong>${defaultValue}</strong><small>${escapeHtml(tradeDetail)}</small></div>
|
|
182
|
+
</div>
|
|
183
|
+
<div class="orbit-legend">
|
|
184
|
+
<span data-orbit-legend="long"><i class="long"></i>${escapeHtml(text.longLabel)} <b>${longPct.toFixed(1)}%</b></span>
|
|
185
|
+
<span data-orbit-legend="short"><i class="short"></i>${escapeHtml(text.shortLabel)} <b>${shortPct.toFixed(1)}%</b></span>
|
|
186
|
+
<span data-orbit-legend="win"><i class="win"></i>${escapeHtml(text.winsLabel)} <b>${winPct.toFixed(1)}%</b></span>
|
|
187
|
+
<span data-orbit-legend="loss"><i class="loss"></i>${escapeHtml(text.lossesLabel)} <b>${lossPct.toFixed(1)}%</b></span>
|
|
188
|
+
<span data-orbit-legend="fee"><i class="fee"></i>${escapeHtml(text.feesLabel)} <b>${feePct.toFixed(1)}%</b></span>
|
|
189
|
+
<span data-orbit-legend="retained"><i class="retained"></i>${escapeHtml(text.retainedLabel)} <b>${retainedPct.toFixed(1)}%</b></span>
|
|
190
|
+
</div>
|
|
191
|
+
</div>`;
|
|
192
|
+
}
|
|
193
|
+
function renderKeyMetrics(metrics, text) {
|
|
194
|
+
const maxDrawdown = num(metrics.maxDrawdownPct);
|
|
195
|
+
const sharpe = num(metrics.sharpe);
|
|
196
|
+
const profitFactor = num(metrics.profitFactor);
|
|
197
|
+
const winRate = num(metrics.winRatePct);
|
|
198
|
+
const rows = [
|
|
199
|
+
{ name: text.hoverEquity, value: `${plain(num(metrics.finalEquityUsdt), "")} USDT`, featured: true },
|
|
200
|
+
{
|
|
201
|
+
name: text.maxDrawdown,
|
|
202
|
+
value: plain(maxDrawdown, "%"),
|
|
203
|
+
detail: `${plain(num(metrics.maxDrawdownUsdt), "")} USDT`,
|
|
204
|
+
tone: maxDrawdown === null ? "neutral" : "negative",
|
|
205
|
+
featured: true
|
|
206
|
+
},
|
|
207
|
+
{ name: text.sharpeLabel, value: plain(sharpe, ""), tone: ratioMetricTone(sharpe) },
|
|
208
|
+
{ name: text.profitFactor, value: plain(profitFactor, ""), tone: profitFactorMetricTone(profitFactor) },
|
|
209
|
+
{ name: text.winRate, value: plain(winRate, "%"), tone: winRate === null ? "neutral" : "info" }
|
|
210
|
+
];
|
|
211
|
+
return `<dl class="telemetry-readouts" aria-label="${escapeHtml(text.performance)}">${rows
|
|
212
|
+
.map((row) => renderMetric(row, "telemetry-readout"))
|
|
213
|
+
.join("")}</dl>`;
|
|
214
|
+
}
|
|
65
215
|
function renderFacts(run, metrics, text) {
|
|
66
216
|
const days = num(metrics.evaluationDays);
|
|
67
217
|
const items = [
|
|
68
|
-
[text.statusLabel, run.status],
|
|
69
218
|
[text.barsLabel, `${formatNumber(run.barCount)} (${formatNumber(run.preloadBars)} preload)`],
|
|
70
|
-
[
|
|
219
|
+
[text.settingDays, days === null ? "-" : `${days.toFixed(1)}d`],
|
|
71
220
|
[
|
|
72
221
|
text.elapsedLabel,
|
|
73
222
|
run.startedAt !== null && run.finishedAt !== null
|
|
@@ -76,10 +225,12 @@ function renderFacts(run, metrics, text) {
|
|
|
76
225
|
]
|
|
77
226
|
];
|
|
78
227
|
return `<div class="facts">${items
|
|
79
|
-
.filter(([label]) => label !== "")
|
|
80
228
|
.map(([label, value]) => `<div><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong></div>`)
|
|
81
229
|
.join("")}</div>`;
|
|
82
230
|
}
|
|
231
|
+
function renderSectionHeading(index, title) {
|
|
232
|
+
return `<div class="section-heading"><span class="section-index mono">${escapeHtml(index)}</span><h2>${escapeHtml(title)}</h2></div>`;
|
|
233
|
+
}
|
|
83
234
|
function renderWarnings(metrics, text) {
|
|
84
235
|
const warnings = [];
|
|
85
236
|
const feeShare = num(metrics.feesPctOfGross);
|
|
@@ -104,39 +255,79 @@ function renderWarnings(metrics, text) {
|
|
|
104
255
|
* Drawdown is the question a bare equity line answers badly: two runs ending at
|
|
105
256
|
* the same number can differ entirely in how much they gave back on the way.
|
|
106
257
|
*/
|
|
107
|
-
function renderChart(points, drawdown, initialEquity, text) {
|
|
258
|
+
function renderChart(points, drawdown, trades, initialEquity, text, rawPoints) {
|
|
108
259
|
if (points.length < 2)
|
|
109
260
|
return `<p class="empty">${escapeHtml(text.notEnoughPoints)}</p>`;
|
|
110
261
|
const width = 1000;
|
|
111
|
-
const equityHeight =
|
|
112
|
-
const drawdownHeight =
|
|
262
|
+
const equityHeight = 300;
|
|
263
|
+
const drawdownHeight = 120;
|
|
113
264
|
const values = points.map((point) => point.equityUsdt);
|
|
114
265
|
const minimum = Math.min(...values);
|
|
115
266
|
const maximum = Math.max(...values);
|
|
116
267
|
const span = maximum - minimum || 1;
|
|
117
268
|
const worst = Math.max(...drawdown, 0.0001);
|
|
118
|
-
const
|
|
269
|
+
const first = points[0];
|
|
270
|
+
const last = points.at(-1);
|
|
271
|
+
const timeSpan = Math.max(1, last.timeMs - first.timeMs);
|
|
272
|
+
const x = (index) => ((points[index].timeMs - first.timeMs) / timeSpan) * width;
|
|
119
273
|
const y = (value) => equityHeight - ((value - minimum) / span) * equityHeight;
|
|
274
|
+
const pointIndexAt = (timeMs) => {
|
|
275
|
+
let low = 0;
|
|
276
|
+
let high = points.length - 1;
|
|
277
|
+
while (low < high) {
|
|
278
|
+
const middle = Math.floor((low + high) / 2);
|
|
279
|
+
if (points[middle].timeMs < timeMs)
|
|
280
|
+
low = middle + 1;
|
|
281
|
+
else
|
|
282
|
+
high = middle;
|
|
283
|
+
}
|
|
284
|
+
if (low > 0 && Math.abs(points[low - 1].timeMs - timeMs) < Math.abs(points[low].timeMs - timeMs))
|
|
285
|
+
return low - 1;
|
|
286
|
+
return low;
|
|
287
|
+
};
|
|
120
288
|
const line = values.map((value, index) => `${index === 0 ? "M" : "L"}${x(index).toFixed(1)},${y(value).toFixed(1)}`).join("");
|
|
121
289
|
const area = `${line}L${width},${equityHeight}L0,${equityHeight}Z`;
|
|
122
290
|
const ddPath = drawdown
|
|
123
291
|
.map((value, index) => `${index === 0 ? "M" : "L"}${x(index).toFixed(1)},${((value / worst) * drawdownHeight).toFixed(1)}`)
|
|
124
292
|
.join("");
|
|
125
293
|
const ddArea = `${ddPath}L${width},0L0,0Z`;
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
294
|
+
const grid = [0, 1, 2, 3, 4]
|
|
295
|
+
.map((index) => `<line x1="0" y1="${(index * equityHeight) / 4}" x2="${width}" y2="${(index * equityHeight) / 4}"/>`)
|
|
296
|
+
.join("") + [0, 1, 2, 3, 4, 5, 6]
|
|
297
|
+
.map((index) => `<line x1="${(index * width) / 6}" y1="0" x2="${(index * width) / 6}" y2="${equityHeight}"/>`)
|
|
298
|
+
.join("");
|
|
299
|
+
const worstIndex = drawdown.indexOf(worst);
|
|
300
|
+
let peakIndex = 0;
|
|
301
|
+
for (let index = 1; index <= Math.max(0, worstIndex); index += 1) {
|
|
302
|
+
if (values[index] > values[peakIndex])
|
|
303
|
+
peakIndex = index;
|
|
304
|
+
}
|
|
305
|
+
const eventChips = [
|
|
306
|
+
[peakIndex, text.peakLabel, plain(values[peakIndex] ?? null, "")],
|
|
307
|
+
[Math.max(0, worstIndex), text.troughLabel, `-${worst.toFixed(2)}%`]
|
|
308
|
+
]
|
|
309
|
+
.map(([index, label, value]) => `<button type="button" class="event-chip" data-point-index="${index}" style="--event-x:${((x(Number(index)) / width) * 100).toFixed(2)}%"><span>${escapeHtml(String(label))}</span><b>${escapeHtml(String(value))}</b></button>`)
|
|
310
|
+
.join("");
|
|
311
|
+
const markerStep = Math.max(1, Math.ceil(trades.length / 120));
|
|
312
|
+
const tradeMarkers = trades
|
|
313
|
+
.filter((_, index) => index % markerStep === 0)
|
|
314
|
+
.map((trade) => {
|
|
315
|
+
const closed = num(trade.closedAtMs);
|
|
316
|
+
const net = num(trade.netPnlUsdt ?? trade.realizedPnlUsdt);
|
|
317
|
+
if (closed === null || net === null)
|
|
318
|
+
return "";
|
|
319
|
+
const index = pointIndexAt(closed);
|
|
320
|
+
return `<ellipse class="trade-marker ${sign(net)}" cx="${x(index).toFixed(1)}" cy="${y(values[index]).toFixed(1)}" rx="3.2" ry="3.2" data-marker-radius="3.2"/>`;
|
|
321
|
+
})
|
|
322
|
+
.join("");
|
|
130
323
|
const breakEven = initialEquity > minimum && initialEquity < maximum
|
|
131
324
|
? `<line class="baseline" x1="0" y1="${y(initialEquity).toFixed(1)}" x2="${width}" y2="${y(initialEquity).toFixed(1)}"/>`
|
|
132
325
|
: "";
|
|
133
|
-
// Series data for the hover readout. Sent as three parallel arrays rather than
|
|
134
|
-
// an array of objects: on a 900-point curve that is roughly a third of the
|
|
135
|
-
// bytes, and the report is read as one file.
|
|
136
326
|
const series = {
|
|
137
327
|
t: points.map((point) => point.timeMs),
|
|
138
328
|
e: points.map((point) => Number(point.equityUsdt.toFixed(4))),
|
|
139
329
|
d: drawdown.map((value) => Number(value.toFixed(4))),
|
|
330
|
+
timeline: encodeEquityTimeline(rawPoints, points.length),
|
|
140
331
|
initial: initialEquity,
|
|
141
332
|
labels: {
|
|
142
333
|
time: text.hoverTime,
|
|
@@ -145,111 +336,172 @@ function renderChart(points, drawdown, initialEquity, text) {
|
|
|
145
336
|
drawdown: text.hoverDrawdown
|
|
146
337
|
}
|
|
147
338
|
};
|
|
148
|
-
return
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
<
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
<
|
|
161
|
-
<
|
|
162
|
-
</
|
|
163
|
-
<div class="
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
<
|
|
170
|
-
|
|
171
|
-
</
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
339
|
+
return `<div class="chart-stack">
|
|
340
|
+
<div class="chart equity-track" id="equity-chart" data-series='${escapeAttribute(JSON.stringify(series))}'>
|
|
341
|
+
<div class="event-rail">${eventChips}</div>
|
|
342
|
+
<svg viewBox="0 -8 ${width} ${equityHeight + 16}" preserveAspectRatio="none" role="img" aria-label="equity curve">
|
|
343
|
+
<g class="plot-grid">${grid}</g>
|
|
344
|
+
<path class="equity-area" d="${area}"/>
|
|
345
|
+
${breakEven}
|
|
346
|
+
<path class="equity-line" pathLength="1" d="${line}" vector-effect="non-scaling-stroke"/>
|
|
347
|
+
<g class="trade-markers">${tradeMarkers}</g>
|
|
348
|
+
<line class="crosshair" x1="0" y1="-8" x2="0" y2="${equityHeight}" hidden/>
|
|
349
|
+
<ellipse class="marker" rx="4" ry="4" cx="0" cy="0" data-marker-radius="4" hidden/>
|
|
350
|
+
</svg>
|
|
351
|
+
<div class="axis"><span>${formatNumber(maximum)}</span><span>${formatNumber(minimum)}</span></div>
|
|
352
|
+
<div class="tooltip" hidden></div>
|
|
353
|
+
</div>
|
|
354
|
+
<div class="chart drawdown-track" id="drawdown-chart">
|
|
355
|
+
<svg viewBox="0 0 ${width} ${drawdownHeight}" preserveAspectRatio="none" role="img" aria-label="drawdown">
|
|
356
|
+
<path class="drawdown-area" d="${ddArea}"/>
|
|
357
|
+
<path class="drawdown-line" pathLength="1" d="${ddPath}" vector-effect="non-scaling-stroke"/>
|
|
358
|
+
<line class="crosshair" x1="0" y1="0" x2="0" y2="${drawdownHeight}" hidden/>
|
|
359
|
+
</svg>
|
|
360
|
+
<div class="axis"><span>0%</span><span>-${worst.toFixed(1)}%</span></div>
|
|
361
|
+
</div>
|
|
362
|
+
<p class="chart-range mono">${escapeHtml(isoMinute(first.timeMs))} → ${escapeHtml(isoMinute(last.timeMs))} UTC · ${formatNumber(points.length)} · ${escapeHtml(text.hoverPrompt)}</p>
|
|
363
|
+
</div>`;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Encodes a uniform raw equity timeline without repeating flat values.
|
|
367
|
+
*
|
|
368
|
+
* The SVG only needs the sampled curve, while pointer inspection needs every bar.
|
|
369
|
+
* A sparse change list restores the exact value for any minute with a binary
|
|
370
|
+
* search and is especially compact for low-exposure strategies.
|
|
371
|
+
*/
|
|
372
|
+
function encodeEquityTimeline(points, sampledCount) {
|
|
373
|
+
if (points.length <= sampledCount || points.length < 2)
|
|
374
|
+
return undefined;
|
|
375
|
+
const step = points[1].timeMs - points[0].timeMs;
|
|
376
|
+
if (step <= 0)
|
|
377
|
+
return undefined;
|
|
378
|
+
for (let index = 2; index < points.length; index += 1) {
|
|
379
|
+
if (points[index].timeMs !== points[0].timeMs + step * index)
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
const quantum = 100;
|
|
383
|
+
const indexDeltas = [0];
|
|
384
|
+
const firstEquity = Math.round(points[0].equityUsdt * quantum);
|
|
385
|
+
const equityDeltas = [firstEquity];
|
|
386
|
+
let previousIndex = 0;
|
|
387
|
+
let previousEquity = firstEquity;
|
|
388
|
+
const maximumSparseChanges = sampledCount * 4;
|
|
389
|
+
for (let index = 1; index < points.length; index += 1) {
|
|
390
|
+
if (points[index].equityUsdt === points[index - 1].equityUsdt)
|
|
391
|
+
continue;
|
|
392
|
+
const equity = Math.round(points[index].equityUsdt * quantum);
|
|
393
|
+
indexDeltas.push(index - previousIndex);
|
|
394
|
+
equityDeltas.push(equity - previousEquity);
|
|
395
|
+
previousIndex = index;
|
|
396
|
+
previousEquity = equity;
|
|
397
|
+
// A continuously changing high-frequency series is not sparse. In that case
|
|
398
|
+
// the sampled arrays remain the bounded fallback instead of doubling the file.
|
|
399
|
+
if (indexDeltas.length > maximumSparseChanges)
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
return { t: points[0].timeMs, s: step, n: points.length, q: quantum, d: indexDeltas, v: equityDeltas };
|
|
403
|
+
}
|
|
404
|
+
function renderMetric(row, extraClass = "") {
|
|
405
|
+
const { name, value, detail, tone = "neutral", featured = false } = row;
|
|
406
|
+
return `<div class="metric metric-${tone}${featured ? " featured" : ""}${extraClass ? ` ${extraClass}` : ""}"><dt>${escapeHtml(name)}</dt><dd>${escapeHtml(value)}${detail ? `<small>${escapeHtml(detail)}</small>` : ""}</dd></div>`;
|
|
175
407
|
}
|
|
176
408
|
function renderMetricGroups(metrics, text) {
|
|
409
|
+
const annualizedReturn = num(metrics.annualizedReturnPct);
|
|
410
|
+
const sortino = num(metrics.sortino);
|
|
411
|
+
const calmar = num(metrics.calmar);
|
|
412
|
+
const expectancy = num(metrics.expectancyUsdt);
|
|
413
|
+
const feesPctOfGross = num(metrics.feesPctOfGross);
|
|
177
414
|
const groups = [
|
|
178
|
-
|
|
179
|
-
text.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
metrics.annualizedReturnPct === null || metrics.annualizedReturnPct === undefined
|
|
415
|
+
{
|
|
416
|
+
title: text.riskAdjusted,
|
|
417
|
+
className: "risk-block",
|
|
418
|
+
rows: [
|
|
419
|
+
{
|
|
420
|
+
name: text.annualized,
|
|
421
|
+
value: metrics.annualizedReturnPct === null || metrics.annualizedReturnPct === undefined
|
|
186
422
|
? "-"
|
|
187
|
-
: signed(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
[
|
|
194
|
-
text.riskAdjusted,
|
|
195
|
-
[
|
|
196
|
-
[text.sharpeLabel, plain(num(metrics.sharpe), "")],
|
|
197
|
-
[text.sortinoLabel, plain(num(metrics.sortino), "")],
|
|
198
|
-
[text.calmarLabel, plain(num(metrics.calmar), "")]
|
|
423
|
+
: signed(annualizedReturn, "%"),
|
|
424
|
+
tone: signedMetricTone(annualizedReturn)
|
|
425
|
+
},
|
|
426
|
+
{ name: text.sortinoLabel, value: plain(sortino, ""), tone: ratioMetricTone(sortino) },
|
|
427
|
+
{ name: text.calmarLabel, value: plain(calmar, ""), tone: ratioMetricTone(calmar) },
|
|
428
|
+
{ name: text.drawdownRecovery, value: `${formatNumber(num(metrics.longestDrawdownBars) ?? 0)} bars` }
|
|
199
429
|
]
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
text.activity,
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
[text.tradesPerDay, plain(num(metrics.tradesPerDay), "")]
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
title: text.activity,
|
|
433
|
+
className: "activity-block",
|
|
434
|
+
rows: [
|
|
435
|
+
{
|
|
436
|
+
name: text.tradesLabel,
|
|
437
|
+
value: `${formatNumber(num(metrics.tradeCount) ?? 0)} (${text.longShort(num(metrics.longTradeCount) ?? 0, num(metrics.shortTradeCount) ?? 0)})`
|
|
438
|
+
},
|
|
439
|
+
{ name: text.expectancy, value: `${signed(expectancy, "")} USDT`, tone: signedMetricTone(expectancy) },
|
|
440
|
+
{
|
|
441
|
+
name: text.averageWinLoss,
|
|
442
|
+
value: `${signed(num(metrics.averageWinUsdt), "")} / ${signed(num(metrics.averageLossUsdt), "")}`
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
name: text.largestWinLoss,
|
|
446
|
+
value: `${signed(num(metrics.largestWinUsdt), "")} / ${signed(num(metrics.largestLossUsdt), "")}`
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
name: text.streaks,
|
|
450
|
+
value: `${formatNumber(num(metrics.maxConsecutiveWins) ?? 0)}W / ${formatNumber(num(metrics.maxConsecutiveLosses) ?? 0)}L`
|
|
451
|
+
},
|
|
452
|
+
{ name: text.holdingTime, value: `${plain(num(metrics.averageHoldingBars), "")} bars` },
|
|
453
|
+
{ name: text.tradesPerDay, value: plain(num(metrics.tradesPerDay), "") }
|
|
225
454
|
]
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
text.costs,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
title: text.costs,
|
|
458
|
+
className: "cost-block",
|
|
459
|
+
rows: [
|
|
460
|
+
{ name: text.feesLabel, value: `${plain(num(metrics.totalFeesUsdt), "")} USDT` },
|
|
461
|
+
{ name: text.feesOfGross, value: plain(feesPctOfGross, "%"), tone: feeMetricTone(feesPctOfGross) },
|
|
462
|
+
{ name: text.turnover, value: `${plain(num(metrics.turnoverUsdt), "")} USDT` },
|
|
463
|
+
{ name: text.exposure, value: plain(num(metrics.exposurePct), "%") }
|
|
234
464
|
]
|
|
235
|
-
|
|
465
|
+
}
|
|
236
466
|
];
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
467
|
+
const erosion = Math.min(100, Math.max(0, feesPctOfGross ?? 0));
|
|
468
|
+
return `<div class="telemetry-band">${groups
|
|
469
|
+
.map(({ title, className, rows }) => `<article class="telemetry-channel ${className}">
|
|
470
|
+
<div class="channel-heading"><span></span><h3>${escapeHtml(title)}</h3></div>
|
|
471
|
+
<dl class="metric-list">${rows.map((row) => renderMetric(row, "detail-metric")).join("")}</dl>
|
|
472
|
+
${className === "cost-block" ? `<div class="erosion-meter"><span style="--erosion:${erosion.toFixed(1)}%"></span></div>` : ""}
|
|
473
|
+
</article>`)
|
|
474
|
+
.join("")}</div>`;
|
|
475
|
+
}
|
|
476
|
+
function signedMetricTone(value) {
|
|
477
|
+
if (value === null || value === 0)
|
|
478
|
+
return "neutral";
|
|
479
|
+
return value > 0 ? "positive" : "negative";
|
|
480
|
+
}
|
|
481
|
+
function ratioMetricTone(value) {
|
|
482
|
+
if (value === null)
|
|
483
|
+
return "neutral";
|
|
484
|
+
if (value < 0)
|
|
485
|
+
return "negative";
|
|
486
|
+
if (value < 1)
|
|
487
|
+
return "warning";
|
|
488
|
+
return "positive";
|
|
489
|
+
}
|
|
490
|
+
function profitFactorMetricTone(value) {
|
|
491
|
+
if (value === null || value === 1)
|
|
492
|
+
return "neutral";
|
|
493
|
+
return value > 1 ? "positive" : "negative";
|
|
494
|
+
}
|
|
495
|
+
function feeMetricTone(value) {
|
|
496
|
+
if (value === null || value < 50)
|
|
497
|
+
return "neutral";
|
|
498
|
+
return value > 100 ? "negative" : "warning";
|
|
246
499
|
}
|
|
247
500
|
function renderTrades(trades, text) {
|
|
248
501
|
if (trades.length === 0) {
|
|
249
|
-
return `<section
|
|
502
|
+
return `<section class="report-section trades-section" id="trades" data-report-section>${renderSectionHeading("04", text.tradesHeading)}<p class="empty">${escapeHtml(text.noClosedTrades)}</p></section>`;
|
|
250
503
|
}
|
|
251
|
-
|
|
252
|
-
// individual results cannot.
|
|
504
|
+
const maximum = Math.max(...trades.map((trade) => Math.abs(num(trade.netPnlUsdt ?? trade.realizedPnlUsdt) ?? 0)), 1);
|
|
253
505
|
let cumulative = 0;
|
|
254
506
|
const rows = trades
|
|
255
507
|
.map((trade, index) => {
|
|
@@ -257,38 +509,39 @@ function renderTrades(trades, text) {
|
|
|
257
509
|
cumulative += net ?? 0;
|
|
258
510
|
const opened = num(trade.openedAtMs) ?? 0;
|
|
259
511
|
const closed = num(trade.closedAtMs) ?? 0;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
512
|
+
const side = String(trade.side ?? "-").toLowerCase();
|
|
513
|
+
const outcome = net === null || net === 0 ? "flat" : net > 0 ? "wins" : "losses";
|
|
514
|
+
const magnitude = net === null ? 0 : Math.max(3, (Math.abs(net) / maximum) * 100);
|
|
515
|
+
return `<div class="tape-row ${sign(net)}" data-side="${escapeHtml(side)}" data-outcome="${outcome}">
|
|
516
|
+
<span class="tape-index mono">${String(index + 1).padStart(2, "0")}</span>
|
|
517
|
+
<div class="tape-side"><strong>${escapeHtml(side)}</strong><small>${escapeHtml(text.colQty)} ${plain(num(trade.quantity), "")}</small></div>
|
|
518
|
+
<div class="tape-prices"><span><small>${escapeHtml(text.colEntry)}</small>${plain(num(trade.entryPrice), "")}</span><i>→</i><span><small>${escapeHtml(text.colExit)}</small>${plain(num(trade.exitPrice), "")}</span></div>
|
|
519
|
+
<div class="tape-result">
|
|
520
|
+
<span class="pnl-bar" style="--pnl-size:${magnitude.toFixed(1)}%"></span>
|
|
521
|
+
<strong>${signed(net, "")} USDT</strong>
|
|
522
|
+
<small>${signed(num(trade.returnPct), "%")} · ${escapeHtml(text.colCumulative)} ${signed(cumulative, "")}</small>
|
|
523
|
+
</div>
|
|
524
|
+
<div class="tape-meta"><span>${escapeHtml(text.colFees)} <b>${plain(num(trade.feesUsdt), "")}</b></span><span>${escapeHtml(text.colHeld)} <b>${escapeHtml(formatHolding(num(trade.holdingBars) ?? 0))}</b></span></div>
|
|
525
|
+
<div class="tape-time mono"><span><small>${escapeHtml(text.colOpened)}</small>${escapeHtml(isoMinute(opened))}</span><span><small>${escapeHtml(text.colClosed)}</small>${escapeHtml(closed > 0 ? isoMinute(closed) : "-")}</span></div>
|
|
526
|
+
</div>`;
|
|
274
527
|
})
|
|
275
528
|
.join("");
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
<
|
|
290
|
-
</table>
|
|
529
|
+
const filters = [
|
|
530
|
+
["all", text.tradeFilterAll],
|
|
531
|
+
["long", text.longLabel],
|
|
532
|
+
["short", text.shortLabel],
|
|
533
|
+
["wins", text.tradeFilterWins],
|
|
534
|
+
["losses", text.tradeFilterLosses]
|
|
535
|
+
];
|
|
536
|
+
return `<section class="report-section trades-section" id="trades" data-report-section>
|
|
537
|
+
<div class="tape-heading">
|
|
538
|
+
${renderSectionHeading("04", text.tradesHeading)}
|
|
539
|
+
<div class="tape-controls" role="group" aria-label="${escapeHtml(text.tradesHeading)}">${filters
|
|
540
|
+
.map(([filter, label], index) => `<button type="button" data-trade-filter="${filter}" aria-pressed="${index === 0 ? "true" : "false"}">${escapeHtml(label)}</button>`)
|
|
541
|
+
.join("")}</div>
|
|
542
|
+
<output id="trade-visible-count">${formatNumber(trades.length)}</output>
|
|
291
543
|
</div>
|
|
544
|
+
<div class="trade-tape">${rows}</div>
|
|
292
545
|
</section>`;
|
|
293
546
|
}
|
|
294
547
|
/**
|
|
@@ -303,12 +556,22 @@ function renderAssumptions(run, text) {
|
|
|
303
556
|
return "";
|
|
304
557
|
const contract = (assumptions.contract ?? {});
|
|
305
558
|
const fixedBudget = num(assumptions.entryBudgetUsdt);
|
|
559
|
+
const evaluationStart = num(assumptions.evaluationStartMs);
|
|
560
|
+
const evaluationEnd = num(assumptions.evaluationEndMs);
|
|
561
|
+
const intervals = Array.isArray(assumptions.intervals) ? assumptions.intervals : [];
|
|
306
562
|
const rows = [
|
|
563
|
+
[
|
|
564
|
+
text.settingEvaluationWindow,
|
|
565
|
+
evaluationStart === null || evaluationEnd === null
|
|
566
|
+
? "-"
|
|
567
|
+
: `${isoMinute(evaluationStart)} — ${isoMinute(evaluationEnd)}`
|
|
568
|
+
],
|
|
569
|
+
[text.settingTimeframes, intervals.length ? intervals.join(", ") : "-"],
|
|
307
570
|
[text.settingEquity, `${plain(num(assumptions.initialEquityUsdt), "")} USDT`],
|
|
308
571
|
[text.settingLeverage, `${plain(num(assumptions.leverage), "")}x`],
|
|
309
572
|
[text.settingMarginSafety, `${plain(num(assumptions.marginSafetyMultiplier), "")}x`],
|
|
310
573
|
[
|
|
311
|
-
text.settingBudgetPct,
|
|
574
|
+
fixedBudget === null ? text.settingBudgetPct : text.settingBudgetUsdt,
|
|
312
575
|
fixedBudget === null
|
|
313
576
|
? asPercent(num(assumptions.entryBudgetPct))
|
|
314
577
|
: `${plain(fixedBudget, "")} USDT`
|
|
@@ -320,27 +583,54 @@ function renderAssumptions(run, text) {
|
|
|
320
583
|
[text.settingPreload, `${formatNumber(num(assumptions.preloadBars) ?? 0)}`],
|
|
321
584
|
[text.settingCloseAtEnd, assumptions.closeAtEnd === false ? text.no : text.yes],
|
|
322
585
|
[
|
|
323
|
-
|
|
324
|
-
`${plain(num(contract.contractValue), "")} ${String(contract.contractValueCurrency ?? "")}
|
|
586
|
+
text.settingContract,
|
|
587
|
+
`${plain(num(contract.contractValue), "")} ${String(contract.contractValueCurrency ?? "")} × ${plain(num(contract.contractMultiplier), "")}`
|
|
325
588
|
],
|
|
326
|
-
[
|
|
589
|
+
[text.settingLotAndMinimum, `${plain(num(contract.lotSize), "")} / ${plain(num(contract.minSize), "")}`]
|
|
327
590
|
];
|
|
328
591
|
const params = assumptions.params;
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
592
|
+
const parameterEntries = Object.entries(params ?? {}).sort(([left], [right]) => left.localeCompare(right));
|
|
593
|
+
const parameterContent = parameterEntries.length
|
|
594
|
+
? parameterEntries
|
|
595
|
+
.map(([key, value]) => `<div><code>${escapeHtml(key)}</code><strong>${escapeHtml(formatSettingValue(value))}</strong></div>`)
|
|
596
|
+
.join("")
|
|
597
|
+
: `<p class="empty">${escapeHtml(text.settingNoStrategyParams)}</p>`;
|
|
598
|
+
return `<section class="report-section method-section" id="method" data-report-section>
|
|
599
|
+
${renderSectionHeading("03", text.assumptionsHeading)}
|
|
600
|
+
<div class="blueprint-grid">
|
|
601
|
+
<details class="blueprint-panel settings-panel" open>
|
|
602
|
+
<summary><span>01</span><strong>${escapeHtml(text.settingsTitle)}</strong><i></i></summary>
|
|
603
|
+
<dl class="settings-ledger">${rows
|
|
340
604
|
.map(([name, value]) => `<div><dt>${escapeHtml(name)}</dt><dd>${escapeHtml(value)}</dd></div>`)
|
|
341
|
-
.join("")}
|
|
605
|
+
.join("")}</dl>
|
|
606
|
+
</details>
|
|
607
|
+
<details class="blueprint-panel parameter-panel" open>
|
|
608
|
+
<summary><span>02</span><strong>${escapeHtml(text.settingStrategyParams)}</strong><i></i></summary>
|
|
609
|
+
<div class="parameter-grid">${parameterContent}</div>
|
|
610
|
+
</details>
|
|
611
|
+
<details class="blueprint-panel provenance-panel">
|
|
612
|
+
<summary><span>03</span><strong>PROVENANCE</strong><i></i></summary>
|
|
613
|
+
<dl class="provenance-list">
|
|
614
|
+
<div><dt>run</dt><dd class="mono">${escapeHtml(run.id)}</dd></div>
|
|
615
|
+
<div><dt>strategy sha256</dt><dd class="mono break">${escapeHtml(run.strategyHash)}</dd></div>
|
|
616
|
+
<div><dt>data snapshot</dt><dd class="mono break">${escapeHtml(run.dataSnapshotId ?? "-")}</dd></div>
|
|
617
|
+
</dl>
|
|
618
|
+
</details>
|
|
619
|
+
</div>
|
|
342
620
|
</section>`;
|
|
343
621
|
}
|
|
622
|
+
function formatSettingValue(value) {
|
|
623
|
+
if (typeof value === "string")
|
|
624
|
+
return value;
|
|
625
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null)
|
|
626
|
+
return String(value);
|
|
627
|
+
try {
|
|
628
|
+
return JSON.stringify(value) ?? String(value);
|
|
629
|
+
}
|
|
630
|
+
catch {
|
|
631
|
+
return String(value);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
344
634
|
function asBps(value) {
|
|
345
635
|
return value === null ? "-" : `${(value * 10_000).toFixed(2)} bps`;
|
|
346
636
|
}
|
|
@@ -361,38 +651,40 @@ function renderMonthly(equity, text) {
|
|
|
361
651
|
const key = new Date(point.timeMs).toISOString().slice(0, 7);
|
|
362
652
|
const found = months.get(key);
|
|
363
653
|
if (!found) {
|
|
364
|
-
months.set(key, {
|
|
654
|
+
months.set(key, {
|
|
655
|
+
start: point.equityUsdt,
|
|
656
|
+
end: point.equityUsdt,
|
|
657
|
+
low: point.equityUsdt,
|
|
658
|
+
high: point.equityUsdt,
|
|
659
|
+
startTime: point.timeMs,
|
|
660
|
+
endTime: point.timeMs
|
|
661
|
+
});
|
|
365
662
|
continue;
|
|
366
663
|
}
|
|
367
664
|
found.end = point.equityUsdt;
|
|
368
665
|
found.low = Math.min(found.low, point.equityUsdt);
|
|
369
666
|
found.high = Math.max(found.high, point.equityUsdt);
|
|
667
|
+
found.endTime = point.timeMs;
|
|
370
668
|
}
|
|
371
669
|
if (months.size < 2)
|
|
372
670
|
return "";
|
|
373
|
-
const
|
|
671
|
+
const cells = [...months.entries()]
|
|
374
672
|
.map(([month, values]) => {
|
|
375
673
|
const change = values.start > 0 ? (values.end / values.start - 1) * 100 : 0;
|
|
376
674
|
const depth = values.high > 0 ? ((values.high - values.low) / values.high) * 100 : 0;
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
675
|
+
const intensity = Math.min(1, Math.abs(change) / 5);
|
|
676
|
+
return `<button type="button" class="month-cell ${sign(change)}" data-month-start="${values.startTime}" data-month-end="${values.endTime}" aria-pressed="false" style="--month-fill:${(96 - intensity * 24).toFixed(1)}%">
|
|
677
|
+
<span class="mono">${escapeHtml(month)}</span>
|
|
678
|
+
<strong>${signed(change, "%")}</strong>
|
|
679
|
+
<small>${plain(values.start, "")} → ${plain(values.end, "")}</small>
|
|
680
|
+
<em>${escapeHtml(text.colWorstDrop)} ${plain(depth, "%")}</em>
|
|
681
|
+
</button>`;
|
|
384
682
|
})
|
|
385
683
|
.join("");
|
|
386
|
-
return
|
|
387
|
-
|
|
388
|
-
<
|
|
389
|
-
|
|
390
|
-
<table>
|
|
391
|
-
<thead><tr><th>${escapeHtml(text.colMonth)}</th><th class="n">${escapeHtml(text.colOpenEquity)}</th><th class="n">${escapeHtml(text.colCloseEquity)}</th><th class="n">${escapeHtml(text.colChange)}</th><th class="n">${escapeHtml(text.colWorstDrop)}</th></tr></thead>
|
|
392
|
-
<tbody>${rows}</tbody>
|
|
393
|
-
</table>
|
|
394
|
-
</div>
|
|
395
|
-
</section>`;
|
|
684
|
+
return `<div class="month-map">
|
|
685
|
+
<div class="map-heading"><h3>${escapeHtml(text.monthlyHeading)}</h3><span>${formatNumber(months.size)}M</span></div>
|
|
686
|
+
<div class="month-strip">${cells}</div>
|
|
687
|
+
</div>`;
|
|
396
688
|
}
|
|
397
689
|
/**
|
|
398
690
|
* Histogram of per-trade results.
|
|
@@ -423,19 +715,21 @@ function renderDistribution(trades, text) {
|
|
|
423
715
|
const from = minimum + index * width;
|
|
424
716
|
const to = from + width;
|
|
425
717
|
const share = peak > 0 ? (count / peak) * 100 : 0;
|
|
718
|
+
const tradeShare = (count / values.length) * 100;
|
|
426
719
|
const losing = to <= 0;
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
720
|
+
const range = `${compact(from)}…${compact(to)} USDT`;
|
|
721
|
+
const detail = `${formatNumber(count)} ${text.tradesLabel} · ${tradeShare.toFixed(1)}%`;
|
|
722
|
+
return `<button type="button" class="spectrum-bin" aria-pressed="false" aria-label="${escapeHtml(`${range}, ${detail}`)}" data-spectrum-range="${escapeHtml(range)}" data-spectrum-detail="${escapeHtml(detail)}">
|
|
723
|
+
<span>${formatNumber(count)}</span>
|
|
724
|
+
<i class="${losing ? "down" : "up"}" style="--bin-height:${share.toFixed(1)}%"></i>
|
|
725
|
+
<small class="mono">${escapeHtml(compact((from + to) / 2))}</small>
|
|
726
|
+
</button>`;
|
|
432
727
|
})
|
|
433
728
|
.join("");
|
|
434
|
-
return
|
|
435
|
-
|
|
436
|
-
<
|
|
437
|
-
|
|
438
|
-
</section>`;
|
|
729
|
+
return `<div class="distribution-map">
|
|
730
|
+
<div class="map-heading"><h3>${escapeHtml(text.distributionHeading)}</h3><span>${formatNumber(values.length)}T</span></div>
|
|
731
|
+
<div class="pnl-spectrum"><output class="spectrum-readout" aria-live="polite" aria-atomic="true" hidden><strong></strong><span></span></output>${bars}</div>
|
|
732
|
+
</div>`;
|
|
439
733
|
}
|
|
440
734
|
/** Bars as a duration, since "4210 bars" is harder to judge than "2d 22h". */
|
|
441
735
|
function formatHolding(bars) {
|
|
@@ -456,6 +750,26 @@ function formatHolding(bars) {
|
|
|
456
750
|
* extreme, which erases exactly the moves a reader is scanning for.
|
|
457
751
|
*/
|
|
458
752
|
export function downsample(points, target) {
|
|
753
|
+
if (points.length <= target)
|
|
754
|
+
return points;
|
|
755
|
+
// A time-bucket envelope protects the global shape and long flat periods. Most
|
|
756
|
+
// points in a low-frequency strategy are flat, though, so that envelope alone
|
|
757
|
+
// spends almost its entire budget on empty time and reduces a held position to
|
|
758
|
+
// its entry/exit extremes. Reserve the rest for actual mark-to-market changes.
|
|
759
|
+
const backbone = extremaSample(points, Math.max(2, Math.floor(target * 0.3)));
|
|
760
|
+
const changing = points.filter((point, index) => {
|
|
761
|
+
if (index === 0 || index === points.length - 1)
|
|
762
|
+
return true;
|
|
763
|
+
return point.equityUsdt !== points[index - 1].equityUsdt || point.equityUsdt !== points[index + 1].equityUsdt;
|
|
764
|
+
});
|
|
765
|
+
const detailBudget = Math.max(2, target - backbone.length);
|
|
766
|
+
const detail = changing.length <= detailBudget ? changing : extremaSample(changing, detailBudget);
|
|
767
|
+
const merged = [...new Set([...backbone, ...detail, points[0], points.at(-1)])].sort((left, right) => left.timeMs - right.timeMs);
|
|
768
|
+
if (merged.length <= target)
|
|
769
|
+
return merged;
|
|
770
|
+
return [merged[0], ...extremaSample(merged.slice(1, -1), target - 2), merged.at(-1)];
|
|
771
|
+
}
|
|
772
|
+
function extremaSample(points, target) {
|
|
459
773
|
if (points.length <= target)
|
|
460
774
|
return points;
|
|
461
775
|
const buckets = Math.max(1, Math.floor(target / 2));
|
|
@@ -544,6 +858,10 @@ export function escapeHtml(value) {
|
|
|
544
858
|
.replace(/"/g, """)
|
|
545
859
|
.replace(/'/g, "'");
|
|
546
860
|
}
|
|
861
|
+
/** Adds safe wrap opportunities to long strategy identifiers. */
|
|
862
|
+
function breakableIdentifier(value) {
|
|
863
|
+
return escapeHtml(value).replace(/_/g, "_<wbr>");
|
|
864
|
+
}
|
|
547
865
|
/**
|
|
548
866
|
* Escapes a value for a single-quoted HTML attribute.
|
|
549
867
|
*
|
|
@@ -556,68 +874,330 @@ export function escapeAttribute(value) {
|
|
|
556
874
|
return value.replace(/&/g, "&").replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
557
875
|
}
|
|
558
876
|
const STYLES = `
|
|
559
|
-
:root{--bg:#
|
|
560
|
-
@media(prefers-color-scheme:light){:root{--bg:#
|
|
877
|
+
:root{--bg:#0d1015;--panel:#141922;--panel-raised:#1a202b;--line:#28303d;--line-strong:#3a4555;--text:#edf1f7;--dim:#96a0b1;--faint:#7f8b9e;--up:#47c875;--down:#ff6b74;--accent:#5794ff;--warning:oklch(78% .13 78);--info:oklch(75% .13 252);--shadow:0 24px 70px rgba(0,0,0,.22);--ease-out:cubic-bezier(.22,1,.36,1)}
|
|
878
|
+
@media(prefers-color-scheme:light){:root{--bg:#f1f4f7;--panel:#fbfcfd;--panel-raised:#fff;--line:#dbe1e8;--line-strong:#bcc7d4;--text:#151a22;--dim:#626d7d;--faint:#647080;--up:#147a3d;--down:#c8323c;--accent:#155dd7;--warning:oklch(51% .13 70);--info:oklch(48% .16 255);--shadow:0 24px 70px rgba(37,49,65,.10)}}
|
|
561
879
|
*{box-sizing:border-box}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
.
|
|
567
|
-
.
|
|
568
|
-
.
|
|
569
|
-
|
|
880
|
+
[hidden]{display:none!important}
|
|
881
|
+
html{background:var(--bg)}
|
|
882
|
+
body{margin:0;background:var(--bg);color:var(--text);font:14px/1.55 "Avenir Next","Segoe UI Variable","PingFang SC","Microsoft YaHei",sans-serif;text-rendering:optimizeLegibility}
|
|
883
|
+
main{width:min(1240px,100%);margin:0 auto;padding:36px 36px 72px}
|
|
884
|
+
main>header:not(.report-cover){display:flex;justify-content:space-between;align-items:flex-end;gap:24px;flex-wrap:wrap;padding-bottom:22px;border-bottom:1px solid var(--line)}
|
|
885
|
+
main:not(.report) section{margin-top:44px}
|
|
886
|
+
main:not(.report) section>.chart{margin-top:18px;padding:14px;border:1px solid var(--line);border-radius:8px;background:var(--panel)}
|
|
887
|
+
h1{margin:0;font-size:28px;font-weight:650;letter-spacing:0;overflow-wrap:anywhere}
|
|
888
|
+
h2{margin:0 0 16px;color:var(--text);font-size:18px;font-weight:650;letter-spacing:0}
|
|
889
|
+
h3{margin:0;color:var(--text);font-size:13px;font-weight:650;letter-spacing:0;text-transform:uppercase}
|
|
890
|
+
.sub{margin:5px 0 0;color:var(--dim);font-size:13px}
|
|
570
891
|
.up{color:var(--up)}.down{color:var(--down)}.flat{color:var(--dim)}
|
|
571
|
-
.
|
|
572
|
-
.
|
|
573
|
-
.
|
|
574
|
-
.
|
|
575
|
-
.
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
.
|
|
584
|
-
.
|
|
585
|
-
.
|
|
586
|
-
.
|
|
587
|
-
.
|
|
588
|
-
.
|
|
589
|
-
.
|
|
590
|
-
.
|
|
591
|
-
.
|
|
592
|
-
.
|
|
892
|
+
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}
|
|
893
|
+
.break{overflow-wrap:anywhere;word-break:break-word}
|
|
894
|
+
.report-cover{padding:0 0 34px;border-bottom:1px solid var(--line-strong)}
|
|
895
|
+
.cover-rail{display:grid;grid-template-columns:auto auto 1fr;align-items:center;gap:24px;padding:0 0 18px;border-bottom:1px solid var(--line);color:var(--dim);font-size:11px;text-transform:uppercase;letter-spacing:.08em}
|
|
896
|
+
.wordmark{color:var(--accent);font-weight:750}
|
|
897
|
+
.run-status{display:inline-flex;align-items:center;gap:7px}
|
|
898
|
+
.run-status i{width:7px;height:7px;border-radius:50%;background:var(--warning)}
|
|
899
|
+
.run-status.status-complete i{background:var(--up)}
|
|
900
|
+
.run-status.status-failed i{background:var(--down)}
|
|
901
|
+
.run-id{justify-self:end;color:var(--faint);letter-spacing:0;text-transform:none}
|
|
902
|
+
.cover-grid{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:56px;padding:46px 0 38px}
|
|
903
|
+
.instrument{margin:0 0 12px;color:var(--accent);font-weight:650;letter-spacing:.06em}
|
|
904
|
+
.cover-identity{min-width:0}
|
|
905
|
+
.cover-identity h1{max-width:820px;font-size:56px;line-height:1.02;font-weight:700}
|
|
906
|
+
.headline{text-align:right}
|
|
907
|
+
.hero-result{min-width:250px;padding-bottom:4px}
|
|
908
|
+
.hero-result .label{display:block;margin-bottom:8px;color:var(--dim);font-size:11px;font-weight:650;text-transform:uppercase;letter-spacing:.08em}
|
|
909
|
+
.hero-result .value{display:block;font-size:72px;line-height:.9;font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
910
|
+
.facts{display:flex;gap:28px;flex-wrap:wrap;margin-top:25px}
|
|
911
|
+
.facts span{display:block;margin-bottom:3px;color:var(--faint);font-size:10px;font-weight:650;text-transform:uppercase;letter-spacing:.08em}
|
|
912
|
+
.facts strong{color:var(--text);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums}
|
|
913
|
+
.key-metrics{display:grid;grid-template-columns:1.25fr repeat(4,1fr);margin:0;padding:0;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
|
|
914
|
+
.metric{--metric-color:var(--text);min-width:0}
|
|
915
|
+
.metric-positive{--metric-color:var(--up)}
|
|
916
|
+
.metric-negative{--metric-color:var(--down)}
|
|
917
|
+
.metric-warning{--metric-color:var(--warning)}
|
|
918
|
+
.metric-info{--metric-color:var(--info)}
|
|
919
|
+
.metric:not(.metric-neutral) dt::before{content:"";display:inline-block;width:6px;height:6px;margin-right:7px;border-radius:2px;background:var(--metric-color);vertical-align:1px}
|
|
920
|
+
.metric:not(.metric-neutral) dd{color:var(--metric-color)}
|
|
921
|
+
.key-metric{padding:18px 20px;border-left:1px solid var(--line)}
|
|
922
|
+
.key-metric:first-child{padding-left:0;border-left:0}
|
|
923
|
+
.key-metric:last-child{padding-right:0}
|
|
924
|
+
dt{color:var(--dim);font-size:10px;font-weight:650;text-transform:uppercase;letter-spacing:.07em}
|
|
925
|
+
dd{margin:5px 0 0;color:var(--text);font-size:18px;font-weight:650;font-variant-numeric:tabular-nums}
|
|
926
|
+
.key-metric.featured dd{font-size:25px}
|
|
927
|
+
.key-metric dd small{display:block;margin-top:2px;color:var(--dim);font-size:10px;font-weight:650}
|
|
928
|
+
.warnings{margin:28px 0 0;padding:15px 18px;border:1px solid color-mix(in srgb,var(--down) 34%,var(--line));border-radius:6px;background:color-mix(in srgb,var(--down) 7%,var(--panel));color:var(--text)}
|
|
929
|
+
.warnings p{margin:3px 0;font-size:13px}
|
|
930
|
+
.report-section{margin-top:80px}
|
|
931
|
+
.section-heading{display:grid;grid-template-columns:48px minmax(0,1fr);align-items:baseline;gap:14px;padding-top:16px;border-top:1px solid var(--line-strong)}
|
|
932
|
+
.section-heading h2{margin:0;font-size:22px;line-height:1.25}
|
|
933
|
+
.section-index{color:var(--accent);font-weight:700}
|
|
934
|
+
.chart-stage{margin-top:24px;padding:15px;border:1px solid var(--line);border-radius:8px;background:var(--panel);box-shadow:var(--shadow)}
|
|
935
|
+
.chart{position:relative;display:flex;align-items:stretch;gap:12px;padding:8px;background:transparent}
|
|
936
|
+
.chart.drawdown{margin-top:6px;padding-top:14px;border-top:1px solid var(--line)}
|
|
937
|
+
.chart svg{display:block;flex:1;width:100%;height:360px;cursor:crosshair}
|
|
938
|
+
.chart.drawdown svg{height:96px}
|
|
939
|
+
.equity-area{fill:color-mix(in srgb,var(--accent) 14%,transparent);opacity:1}
|
|
940
|
+
.equity-line{fill:none;stroke:var(--accent);stroke-width:1.8}
|
|
941
|
+
.drawdown-area{fill:color-mix(in srgb,var(--down) 19%,transparent)}
|
|
942
|
+
.drawdown-line{fill:none;stroke:var(--down);stroke-width:1.25}
|
|
943
|
+
.axis{display:flex;min-width:62px;flex-direction:column;justify-content:space-between;color:var(--faint);font-size:10px;font-variant-numeric:tabular-nums;text-align:right}
|
|
944
|
+
.range{margin:9px 8px 2px;color:var(--faint);font-size:11px}
|
|
945
|
+
.chart:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
|
|
946
|
+
.baseline{stroke:var(--faint);stroke-width:1;stroke-dasharray:3 5;opacity:.55;vector-effect:non-scaling-stroke}
|
|
593
947
|
.crosshair{stroke:var(--dim);stroke-width:1;vector-effect:non-scaling-stroke;pointer-events:none}
|
|
594
948
|
.marker{fill:var(--accent);stroke:var(--panel);stroke-width:1.5;pointer-events:none}
|
|
595
|
-
.tooltip{position:absolute;top:10px;
|
|
596
|
-
.tooltip .row{display:flex;justify-content:space-between;gap:
|
|
949
|
+
.tooltip{position:absolute;top:10px;min-width:190px;padding:9px 11px;border:1px solid var(--line-strong);border-radius:6px;background:var(--panel-raised);box-shadow:var(--shadow);font-size:12px;pointer-events:none;z-index:2}
|
|
950
|
+
.tooltip .row{display:flex;justify-content:space-between;gap:16px;padding:2px 0}
|
|
597
951
|
.tooltip .row span{color:var(--dim)}
|
|
598
|
-
.tooltip .row b{font-weight:
|
|
599
|
-
.
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
952
|
+
.tooltip .row b{font-weight:650;font-variant-numeric:tabular-nums}
|
|
953
|
+
.analysis-grid{display:grid;grid-template-columns:.9fr 1.45fr .9fr;gap:0;margin-top:28px;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
|
|
954
|
+
.diagnostic-block{min-width:0;padding:24px 28px;border-left:1px solid var(--line)}
|
|
955
|
+
.diagnostic-block:first-child{padding-left:0;border-left:0}
|
|
956
|
+
.diagnostic-block:last-child{padding-right:0}
|
|
957
|
+
.diagnostic-block h3{margin-bottom:12px;color:var(--dim)}
|
|
958
|
+
.metric-list{display:grid;margin:0}
|
|
959
|
+
.activity-block .metric-list{grid-template-columns:repeat(2,minmax(0,1fr));column-gap:26px}
|
|
960
|
+
.detail-metric{display:flex;align-items:baseline;justify-content:space-between;gap:18px;padding:10px 0;border-bottom:1px solid var(--line)}
|
|
961
|
+
.detail-metric:nth-last-child(-n+1){border-bottom:0}
|
|
962
|
+
.activity-block .detail-metric:nth-last-child(-n+2){border-bottom:0}
|
|
963
|
+
.detail-metric dt{min-width:0}
|
|
964
|
+
.detail-metric dd{flex-shrink:0;margin:0;font-size:14px;text-align:right}
|
|
965
|
+
.method-layout{display:grid;grid-template-columns:minmax(0,1.65fr) minmax(280px,.75fr);gap:52px;margin-top:28px}
|
|
966
|
+
.settings-ledger{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:36px;margin:0}
|
|
967
|
+
.settings-ledger>div{display:flex;align-items:baseline;justify-content:space-between;gap:20px;padding:11px 0;border-bottom:1px solid var(--line)}
|
|
968
|
+
.settings-ledger dd{flex-shrink:0;margin:0;font-size:14px;text-align:right}
|
|
969
|
+
.method-aside{min-width:0;padding-left:32px;border-left:1px solid var(--line-strong)}
|
|
970
|
+
.parameter-sheet h3{margin-bottom:14px;color:var(--dim)}
|
|
971
|
+
.parameter-grid{display:grid;gap:0}
|
|
972
|
+
.parameter-grid>div{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:9px 0;border-bottom:1px solid var(--line)}
|
|
973
|
+
.parameter-grid code{color:var(--dim);font:12px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
974
|
+
.parameter-grid strong{font-size:13px;font-weight:650;overflow-wrap:anywhere;text-align:right}
|
|
975
|
+
.provenance-list{display:grid;gap:12px;margin:30px 0 0;padding-top:18px;border-top:1px solid var(--line)}
|
|
976
|
+
.provenance-list>div{min-width:0}
|
|
977
|
+
.provenance-list dd{margin-top:4px;color:var(--faint);font-size:11px;font-weight:500}
|
|
978
|
+
.secondary-grid{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(360px,.75fr);gap:34px;align-items:start}
|
|
979
|
+
.secondary-grid>.report-section{margin-top:80px}
|
|
980
|
+
.scroll{overflow-x:auto;border:1px solid var(--line);border-radius:6px;background:var(--panel)}
|
|
981
|
+
table{width:100%;border-collapse:collapse;font-size:12px}
|
|
982
|
+
th,td{padding:10px 12px;border-bottom:1px solid var(--line);text-align:left;white-space:nowrap}
|
|
983
|
+
th{position:sticky;top:0;background:var(--panel-raised);color:var(--dim);font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em}
|
|
984
|
+
tbody tr:last-child td{border-bottom:0}
|
|
985
|
+
tbody tr{transition:background-color 160ms ease}
|
|
986
|
+
tbody tr:hover{background:color-mix(in srgb,var(--accent) 5%,transparent)}
|
|
604
987
|
td.n{text-align:right;font-variant-numeric:tabular-nums}
|
|
605
|
-
tr.up
|
|
606
|
-
tr.
|
|
607
|
-
.
|
|
988
|
+
tbody tr.up,tbody tr.down{color:var(--text)}
|
|
989
|
+
.trades-section tr.up td:nth-child(6),.trades-section tr.up td:nth-child(7){color:var(--up)}
|
|
990
|
+
.trades-section tr.down td:nth-child(6),.trades-section tr.down td:nth-child(7){color:var(--down)}
|
|
608
991
|
td.dim{color:var(--dim)}
|
|
609
|
-
.scroll.tall{max-height:
|
|
610
|
-
.histogram{
|
|
611
|
-
.bin{display:flex;align-items:center;gap:10px;padding:
|
|
612
|
-
.bin .range{min-width:
|
|
613
|
-
.bin .track{flex:1;height:
|
|
614
|
-
.bin .fill{display:block;height:100%;border-radius:
|
|
992
|
+
.scroll.tall{max-height:560px;overflow-y:auto}
|
|
993
|
+
.histogram{padding:15px 16px;border:1px solid var(--line);border-radius:6px;background:var(--panel)}
|
|
994
|
+
.bin{display:flex;align-items:center;gap:10px;padding:5px 0}
|
|
995
|
+
.bin .range{min-width:116px;margin:0;color:var(--dim);text-align:right;flex-shrink:0}
|
|
996
|
+
.bin .track{flex:1;height:8px;border-radius:2px;background:var(--line);overflow:hidden}
|
|
997
|
+
.bin .fill{display:block;height:100%;border-radius:2px}
|
|
615
998
|
.bin .fill.up{background:var(--up)}
|
|
616
999
|
.bin .fill.down{background:var(--down)}
|
|
617
|
-
.bin>.count{min-width:
|
|
618
|
-
.empty{color:var(--dim);font-size:13px}
|
|
619
|
-
footer{margin-top:
|
|
620
|
-
footer p{margin:
|
|
1000
|
+
.bin>.count{min-width:28px;color:var(--dim);font-size:11px;font-variant-numeric:tabular-nums;text-align:right;flex-shrink:0}
|
|
1001
|
+
.empty{margin:0;color:var(--dim);font-size:13px}
|
|
1002
|
+
.report-footer,footer{display:flex;justify-content:space-between;gap:24px;margin-top:72px;padding-top:18px;border-top:1px solid var(--line-strong);color:var(--faint);font-size:11px}
|
|
1003
|
+
footer p{margin:0}
|
|
1004
|
+
@media(prefers-reduced-motion:no-preference){.reveal{animation:reveal .72s var(--ease-out) both}.reveal-1{animation-delay:40ms}.reveal-2{animation-delay:110ms}.reveal-3{animation-delay:180ms}.reveal-4{animation-delay:250ms}.reveal-5{animation-delay:320ms}.equity-line,.drawdown-line{stroke-dasharray:1;stroke-dashoffset:1;animation:trace 1.1s var(--ease-out) .28s forwards}.equity-area,.drawdown-area{opacity:0;animation:plot-in .7s ease-out .55s forwards}}
|
|
1005
|
+
@keyframes reveal{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:translateY(0)}}
|
|
1006
|
+
@keyframes trace{to{stroke-dashoffset:0}}
|
|
1007
|
+
@keyframes plot-in{to{opacity:1}}
|
|
1008
|
+
@media(max-width:900px){main{padding:28px 24px 60px}.cover-identity h1{font-size:44px}.hero-result .value{font-size:60px}.analysis-grid{grid-template-columns:1fr 1fr}.cost-block{grid-column:1/-1;padding-left:0;border-left:0;border-top:1px solid var(--line)}.cost-block .metric-list{grid-template-columns:repeat(4,minmax(0,1fr));column-gap:24px}.method-layout{grid-template-columns:1fr;gap:34px}.method-aside{padding:26px 0 0;border-top:1px solid var(--line-strong);border-left:0}.secondary-grid{grid-template-columns:1fr}}
|
|
1009
|
+
@media(max-width:640px){main{padding:22px 18px 48px}.cover-rail{grid-template-columns:auto 1fr;gap:10px 16px}.run-id{grid-column:1/-1;justify-self:start}.cover-grid{grid-template-columns:1fr;gap:28px;padding:34px 0 28px}.cover-identity h1{font-size:36px;line-height:1.08}.headline{text-align:left}.hero-result{min-width:0}.hero-result .value{font-size:54px}.facts{gap:18px}.key-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.key-metric,.key-metric:first-child,.key-metric:last-child{padding:14px 12px;border-top:1px solid var(--line);border-left:1px solid var(--line)}.key-metric:nth-child(odd){padding-left:0;border-left:0}.key-metric:nth-child(even){padding-right:0}.key-metric:nth-child(-n+2){border-top:0}.key-metric:last-child{grid-column:1/-1;padding-left:0;border-left:0}.key-metric.featured dd{font-size:21px}.report-section{margin-top:58px}.section-heading{grid-template-columns:34px 1fr;gap:10px}.section-heading h2{font-size:19px}.chart-stage{margin-top:18px;padding:7px}.chart{padding:5px;gap:7px}.chart svg{height:250px}.chart.drawdown svg{height:76px}.axis{min-width:50px}.analysis-grid{grid-template-columns:1fr}.diagnostic-block,.diagnostic-block:first-child,.diagnostic-block:last-child{padding:22px 0;border-top:1px solid var(--line);border-left:0}.diagnostic-block:first-child{border-top:0}.activity-block .metric-list,.cost-block .metric-list{grid-template-columns:repeat(2,minmax(0,1fr));column-gap:22px}.settings-ledger{grid-template-columns:1fr}.secondary-grid>.report-section{margin-top:58px}.report-footer,footer{display:block}.report-footer p+ p,footer p+ p{margin-top:7px}}
|
|
1010
|
+
@media(prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}
|
|
1011
|
+
|
|
1012
|
+
/* Trading Observatory: the single-run report is an instrument, not a document. */
|
|
1013
|
+
body.single-report{--bg:#020305;--panel:#080a0e;--panel-raised:#11131a;--panel-soft:#05070a;--line:rgba(174,186,210,.17);--line-strong:rgba(174,186,210,.34);--text:#f5f7fb;--dim:#aab1c0;--faint:#8a93a3;--accent:#955cff;--accent-2:#7138e8;--accent-ink:#e6d9ff;--focus:#b89aff;--up:#0ecb81;--down:#f6465d;--warning:#f5a524;--info:#9e9eff;--orbit-short:#596170;--grid:rgba(174,186,210,.05);--brand-tint:rgba(149,92,255,.14);color-scheme:dark;background-color:var(--bg);background-image:radial-gradient(58% 42% at 50% 18%,var(--brand-tint),transparent 74%),linear-gradient(var(--grid) 1px,transparent 1px),linear-gradient(90deg,var(--grid) 1px,transparent 1px);background-size:auto,88px 88px,88px 88px;min-height:100vh}
|
|
1014
|
+
@media(prefers-color-scheme:light){body.single-report{--bg:#f3f4f6;--panel:#fff;--panel-raised:#fafafa;--panel-soft:#f0f1f3;--line:rgba(28,29,33,.14);--line-strong:rgba(28,29,33,.31);--text:#171719;--dim:#585962;--faint:#6b6d78;--accent:#7138e8;--accent-2:#955cff;--accent-ink:#3d176f;--focus:#7138e8;--up:#087653;--down:#c92f48;--warning:#946000;--info:#5940b5;--orbit-short:#85808d;--grid:rgba(28,29,33,.045);--brand-tint:rgba(149,92,255,.055);color-scheme:light}}
|
|
1015
|
+
body.single-report[data-theme=dark]{--bg:#020305;--panel:#080a0e;--panel-raised:#11131a;--panel-soft:#05070a;--line:rgba(174,186,210,.17);--line-strong:rgba(174,186,210,.34);--text:#f5f7fb;--dim:#aab1c0;--faint:#8a93a3;--accent:#955cff;--accent-2:#7138e8;--accent-ink:#e6d9ff;--focus:#b89aff;--up:#0ecb81;--down:#f6465d;--warning:#f5a524;--info:#9e9eff;--orbit-short:#596170;--grid:rgba(174,186,210,.05);--brand-tint:rgba(149,92,255,.14);color-scheme:dark}
|
|
1016
|
+
body.single-report[data-theme=light]{--bg:#f3f4f6;--panel:#fff;--panel-raised:#fafafa;--panel-soft:#f0f1f3;--line:rgba(28,29,33,.14);--line-strong:rgba(28,29,33,.31);--text:#171719;--dim:#585962;--faint:#6b6d78;--accent:#7138e8;--accent-2:#955cff;--accent-ink:#3d176f;--focus:#7138e8;--up:#087653;--down:#c92f48;--warning:#946000;--info:#5940b5;--orbit-short:#85808d;--grid:rgba(28,29,33,.045);--brand-tint:rgba(149,92,255,.055);color-scheme:light}
|
|
1017
|
+
.single-report *{letter-spacing:0}
|
|
1018
|
+
.single-report{scroll-behavior:smooth}
|
|
1019
|
+
.single-report .scroll-progress{position:fixed;z-index:30;top:0;left:0;width:100%;height:3px;background:var(--accent);transform:scaleX(0);transform-origin:left center;box-shadow:0 0 18px color-mix(in srgb,var(--accent) 55%,transparent)}
|
|
1020
|
+
.single-report .command-rail{position:fixed;z-index:20;left:18px;top:50%;display:flex;flex-direction:column;gap:3px;padding:5px;border:0;border-radius:28px;background:#28282a;box-shadow:0 20px 60px rgba(0,0,0,.45);transform:translateY(-50%)}
|
|
1021
|
+
.single-report .command-rail a{display:grid;place-items:center;width:50px;height:52px;border:0;border-radius:22px;color:#a9a9ad;text-decoration:none;transition:color 180ms ease,background-color 180ms ease,transform 180ms ease}
|
|
1022
|
+
.single-report .command-rail a:hover,.single-report .command-rail a:focus-visible{color:#fff;background:#353538;outline:0}
|
|
1023
|
+
.single-report .command-rail a.active{color:#171719;background:#fff;transform:scale(.96)}
|
|
1024
|
+
.single-report .command-rail span{font:700 9px/1 ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
1025
|
+
.single-report .command-rail b{margin-top:4px;font-size:9px;font-weight:750}
|
|
1026
|
+
.single-report main.report{width:min(1500px,calc(100% - 112px));margin:0 auto;padding:30px 34px 88px}
|
|
1027
|
+
.single-report .observatory-mast{padding:0;border:0}
|
|
1028
|
+
.single-report .mast-rail{display:grid;grid-template-columns:auto auto 1fr auto;align-items:center;gap:22px;padding-bottom:14px;border-bottom:1px solid var(--line);color:var(--dim);font-size:10px;text-transform:uppercase}
|
|
1029
|
+
.single-report .wordmark{color:var(--focus);font-family:BubbledotICG-FinePos,ui-monospace,SFMono-Regular,monospace;font-size:12px;font-weight:400}
|
|
1030
|
+
.single-report .run-status{display:inline-flex;align-items:center;gap:7px}
|
|
1031
|
+
.single-report .run-status i{width:6px;height:6px;border-radius:50%;background:var(--warning);box-shadow:0 0 0 4px color-mix(in srgb,var(--warning) 12%,transparent)}
|
|
1032
|
+
.single-report .run-status.status-complete i{background:var(--up);box-shadow:0 0 0 4px color-mix(in srgb,var(--up) 12%,transparent)}
|
|
1033
|
+
.single-report .run-status.status-failed i{background:var(--down);box-shadow:0 0 0 4px color-mix(in srgb,var(--down) 12%,transparent)}
|
|
1034
|
+
.single-report .run-id{justify-self:end;color:var(--faint);text-transform:none}
|
|
1035
|
+
.single-report .theme-toggle{display:grid;place-items:center;width:34px;height:34px;padding:0;border:1px solid rgba(255,255,255,.34);border-radius:50%;background:#28282a;color:#fff;cursor:pointer;box-shadow:0 4px 14px rgba(0,0,0,.16);transition:transform 160ms ease,box-shadow 160ms ease}
|
|
1036
|
+
.single-report .theme-toggle:hover{transform:rotate(14deg);box-shadow:0 0 0 1px color-mix(in srgb,var(--focus) 55%,transparent),0 0 20px color-mix(in srgb,var(--focus) 24%,transparent)}
|
|
1037
|
+
.single-report .theme-toggle span{width:14px;height:14px;border:1px solid currentColor;border-radius:50%;background:linear-gradient(90deg,currentColor 50%,transparent 50%)}
|
|
1038
|
+
.single-report .theme-toggle[aria-pressed=true] span{transform:rotate(180deg)}
|
|
1039
|
+
.single-report .mast-grid{display:grid;grid-template-columns:minmax(0,1fr) 270px;gap:20px 56px;align-items:end;padding:30px 0 24px}
|
|
1040
|
+
.single-report .mast-identity{min-width:0}
|
|
1041
|
+
.single-report .instrument{margin:0 0 8px;color:var(--accent);font-size:11px;font-weight:750}
|
|
1042
|
+
.single-report .mast-identity h1{max-width:940px;margin:0;font:400 58px/1.04 BubbledotICG-FinePos,ui-monospace,SFMono-Regular,"PingFang SC",monospace;overflow-wrap:normal;word-break:normal}
|
|
1043
|
+
.single-report .mast-outcome{align-self:stretch;display:flex;flex-direction:column;justify-content:flex-end;padding-left:24px;border-left:1px solid var(--line-strong);text-align:right}
|
|
1044
|
+
.single-report .mast-outcome span{color:var(--dim);font-size:10px;font-weight:700;text-transform:uppercase}
|
|
1045
|
+
.single-report .mast-outcome strong{margin-top:2px;font:800 52px/.95 "Avenir Next Condensed","Arial Narrow",sans-serif;font-variant-numeric:tabular-nums}
|
|
1046
|
+
.single-report .mast-outcome small{margin-top:7px;color:currentColor;font-size:12px;font-weight:700}
|
|
1047
|
+
.single-report .mast-grid .facts{grid-column:1/-1;display:flex;gap:0;margin:0;padding-top:17px;border-top:1px solid var(--line)}
|
|
1048
|
+
.single-report .facts div{min-width:150px;padding-right:32px}
|
|
1049
|
+
.single-report .facts span{display:block;color:var(--faint);font-size:9px;font-weight:700;text-transform:uppercase}
|
|
1050
|
+
.single-report .facts strong{display:block;margin-top:4px;color:var(--text);font-size:12px;font-weight:700;font-variant-numeric:tabular-nums}
|
|
1051
|
+
.single-report .warning{margin:0 0 20px;border:1px solid color-mix(in srgb,var(--down) 42%,var(--line));border-left:4px solid var(--down);border-radius:2px;background:color-mix(in srgb,var(--down) 7%,var(--panel));box-shadow:none}
|
|
1052
|
+
.single-report .observatory-section{scroll-margin-top:24px;margin-top:10px}
|
|
1053
|
+
.single-report .observatory-heading,.single-report .tape-heading{display:flex;align-items:end;justify-content:space-between;gap:24px;margin-bottom:14px}
|
|
1054
|
+
.single-report .section-heading{display:grid;grid-template-columns:44px 1fr;align-items:center;gap:12px;margin:0;padding:0;border:0}
|
|
1055
|
+
.single-report .section-heading .section-index{display:grid;place-items:center;width:44px;height:44px;border:1px solid var(--line-strong);color:var(--focus);font:400 14px/1 BubbledotICG-FinePos,ui-monospace,SFMono-Regular,monospace}
|
|
1056
|
+
.single-report .section-heading h2{margin:0;font:700 27px/1.08 Inter,"Segoe UI","PingFang SC",system-ui,sans-serif}
|
|
1057
|
+
.single-report .mode-controls,.single-report .range-toolbar>div,.single-report .tape-controls{display:flex;gap:2px;padding:4px;border:0;border-radius:999px;background:#fff;box-shadow:0 4px 14px rgba(0,0,0,.16)}
|
|
1058
|
+
.single-report button{font:inherit}
|
|
1059
|
+
.single-report .mode-controls button,.single-report .range-toolbar button,.single-report .tape-controls button{min-height:36px;padding:0 14px;border:0;border-radius:999px;background:transparent;color:#777;font-size:10px;font-weight:700;cursor:pointer;transition:background-color 160ms ease,color 160ms ease,transform 120ms ease}
|
|
1060
|
+
.single-report .mode-controls button:hover,.single-report .range-toolbar button:hover,.single-report .tape-controls button:hover{color:#111;background:#eee}
|
|
1061
|
+
.single-report .mode-controls button:active,.single-report .range-toolbar button:active,.single-report .tape-controls button:active{transform:scale(.97)}
|
|
1062
|
+
.single-report .mode-controls button[aria-pressed=true],.single-report .range-toolbar button[aria-pressed=true],.single-report .tape-controls button[aria-pressed=true]{background:#28282a;color:#fff}
|
|
1063
|
+
.single-report button:focus-visible,.single-report summary:focus-visible,.single-report a:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
1064
|
+
.single-report .observatory-frame{display:grid;grid-template-columns:minmax(0,1fr) 340px;border:1px solid var(--line-strong);border-radius:4px;background:var(--panel);box-shadow:0 28px 90px rgba(0,0,0,.22);overflow:hidden}
|
|
1065
|
+
.single-report .plot-module{min-width:0;border-right:1px solid var(--line-strong)}
|
|
1066
|
+
.single-report .range-toolbar{display:flex;align-items:center;justify-content:space-between;gap:20px;min-height:54px;padding:8px 14px;border-bottom:1px solid var(--line);background:var(--panel-soft)}
|
|
1067
|
+
.single-report .range-toolbar>span{color:var(--faint);font-size:9px;font-weight:750;text-transform:uppercase}
|
|
1068
|
+
.single-report .range-toolbar button{min-height:30px;min-width:50px;padding:0 10px}
|
|
1069
|
+
.single-report .chart-stack{display:grid;grid-template-rows:340px 122px auto;padding:10px 12px 0;background:var(--panel);overflow:hidden}
|
|
1070
|
+
.single-report .chart{position:relative;display:block;min-width:0;padding:0;border:0;border-radius:0;background:transparent;overflow:hidden;user-select:none;transition:opacity 260ms var(--ease-out),transform 340ms var(--ease-out),filter 260ms ease}
|
|
1071
|
+
.single-report .chart svg{cursor:grab;touch-action:pan-y}.single-report .chart.is-dragging svg{cursor:grabbing}
|
|
1072
|
+
.single-report .chart svg{display:block;width:100%;height:100%;overflow:visible}
|
|
1073
|
+
.single-report .equity-track{border-bottom:1px solid var(--line)}
|
|
1074
|
+
.single-report .drawdown-track{transform-origin:center top}
|
|
1075
|
+
.single-report .plot-grid line{stroke:var(--line);stroke-width:1;vector-effect:non-scaling-stroke;opacity:.68}
|
|
1076
|
+
.single-report .equity-area{fill:color-mix(in srgb,var(--accent) 17%,transparent)}
|
|
1077
|
+
.single-report .equity-line{fill:none;stroke:var(--accent);stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;animation:none;filter:drop-shadow(0 0 5px color-mix(in srgb,var(--accent) 32%,transparent))}
|
|
1078
|
+
.single-report .baseline{stroke:var(--dim);stroke-width:1;stroke-dasharray:5 6;opacity:.5;vector-effect:non-scaling-stroke}
|
|
1079
|
+
.single-report .drawdown-area{fill:color-mix(in srgb,var(--down) 20%,transparent)}
|
|
1080
|
+
.single-report .drawdown-line{fill:none;stroke:var(--down);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;animation:none}
|
|
1081
|
+
.single-report .crosshair{stroke:var(--text);stroke-width:1;stroke-dasharray:3 4;opacity:.56;vector-effect:non-scaling-stroke}
|
|
1082
|
+
.single-report .marker{fill:var(--panel-raised);stroke:var(--accent);stroke-width:3;vector-effect:non-scaling-stroke}
|
|
1083
|
+
.single-report .trade-markers{opacity:.18;transition:opacity 260ms ease}
|
|
1084
|
+
.single-report .trade-marker{stroke:var(--panel);stroke-width:1.5;vector-effect:non-scaling-stroke}
|
|
1085
|
+
.single-report .trade-marker.up{fill:var(--up)}.single-report .trade-marker.down{fill:var(--down)}
|
|
1086
|
+
.single-report .axis{position:absolute;z-index:2;top:10px;right:5px;bottom:10px;display:flex;flex-direction:column;justify-content:space-between;min-width:54px;color:var(--faint);font:600 9px/1 ui-monospace,SFMono-Regular,Menlo,monospace;text-align:right;pointer-events:none}
|
|
1087
|
+
.single-report .drawdown-track .axis{top:9px;bottom:9px}
|
|
1088
|
+
.single-report .event-rail{position:absolute;z-index:4;top:9px;left:9px;right:62px;height:48px;pointer-events:none}
|
|
1089
|
+
.single-report .event-chip{position:absolute;left:var(--event-x);display:grid;gap:1px;padding:5px 7px;border:1px solid var(--line-strong);border-radius:2px;background:color-mix(in srgb,var(--panel-raised) 90%,transparent);color:var(--text);box-shadow:0 8px 24px rgba(0,0,0,.16);transform:translateX(-50%);pointer-events:auto;cursor:pointer;white-space:nowrap}
|
|
1090
|
+
.single-report .event-chip::after{content:"";position:absolute;top:100%;left:50%;width:1px;height:30px;background:linear-gradient(var(--line-strong),transparent)}
|
|
1091
|
+
.single-report .event-chip span{color:var(--faint);font-size:8px;text-transform:uppercase}
|
|
1092
|
+
.single-report .event-chip b{font-size:10px;font-variant-numeric:tabular-nums}
|
|
1093
|
+
.single-report .tooltip{position:absolute;z-index:8;top:66px;min-width:205px;padding:11px 13px;border:1px solid var(--line-strong);border-radius:3px;background:color-mix(in srgb,var(--panel-raised) 96%,transparent);box-shadow:0 18px 48px rgba(0,0,0,.28);font-size:11px;pointer-events:none;backdrop-filter:blur(10px)}
|
|
1094
|
+
.single-report .tooltip .row{display:flex;justify-content:space-between;gap:22px;padding:3px 0}.single-report .tooltip span{color:var(--dim)}
|
|
1095
|
+
.single-report .chart-range{margin:0;padding:9px 2px 11px;color:var(--faint);font-size:9px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
1096
|
+
.single-report .observatory-frame[data-mode=equity] .drawdown-track{opacity:.14;transform:scaleY(.72)}
|
|
1097
|
+
.single-report .observatory-frame[data-mode=equity] .trade-markers{opacity:0}
|
|
1098
|
+
.single-report .observatory-frame[data-mode=drawdown] .equity-track{opacity:.18;filter:saturate(.2)}
|
|
1099
|
+
.single-report .observatory-frame[data-mode=drawdown] .event-rail{opacity:.25}
|
|
1100
|
+
.single-report .observatory-frame[data-mode=drawdown] .drawdown-track{transform:scaleY(1.18);filter:drop-shadow(0 0 7px color-mix(in srgb,var(--down) 24%,transparent))}
|
|
1101
|
+
.single-report .observatory-frame[data-mode=trades] .equity-area{opacity:.28}
|
|
1102
|
+
.single-report .observatory-frame[data-mode=trades] .trade-markers{opacity:1}
|
|
1103
|
+
.single-report .observatory-frame[data-mode=trades] .trade-marker{filter:drop-shadow(0 0 4px currentColor)}
|
|
1104
|
+
.single-report .window-readouts{display:grid;grid-template-columns:minmax(220px,1.5fr) repeat(3,minmax(120px,1fr));border-top:1px solid var(--line-strong);background:var(--panel-soft)}
|
|
1105
|
+
.single-report .window-readouts>div{min-width:0;padding:13px 16px;border-left:1px solid var(--line)}
|
|
1106
|
+
.single-report .window-readouts>div:first-child{border-left:0}
|
|
1107
|
+
.single-report .window-readouts span{display:block;color:var(--faint);font-size:8px;font-weight:750;text-transform:uppercase}
|
|
1108
|
+
.single-report .window-readouts strong{display:block;margin-top:4px;font-size:15px;font-weight:750;font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
1109
|
+
.single-report .window-readouts .window-period strong{font-size:10px;color:var(--dim)}
|
|
1110
|
+
.single-report .telemetry-module{display:flex;flex-direction:column;min-width:0;background:var(--panel-soft)}
|
|
1111
|
+
.single-report .execution-orbit{padding:18px 20px 16px;border-bottom:1px solid var(--line-strong)}
|
|
1112
|
+
.single-report .orbit-heading,.single-report .map-heading{display:flex;align-items:center;justify-content:space-between;gap:16px}
|
|
1113
|
+
.single-report .orbit-heading span,.single-report .map-heading h3{color:var(--text);font-size:11px;font-weight:800;text-transform:uppercase}
|
|
1114
|
+
.single-report .orbit-heading small,.single-report .map-heading span{color:var(--faint);font:650 9px/1 ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
1115
|
+
.single-report .orbit-visual{position:relative;width:min(250px,100%);aspect-ratio:1;margin:6px auto 0;contain:layout paint}
|
|
1116
|
+
.single-report .orbit-visual svg{display:block;width:100%;height:100%;transform:rotate(-90deg);overflow:visible}
|
|
1117
|
+
.single-report .orbit-visual circle{fill:none}
|
|
1118
|
+
.single-report .orbit-track{stroke:var(--line);stroke-width:11}
|
|
1119
|
+
.single-report .orbit-segment{stroke-width:11;stroke-linecap:butt;pointer-events:none;transition:opacity 180ms ease,stroke-width 220ms var(--ease-out),filter 220ms ease}
|
|
1120
|
+
.single-report .orbit-long{stroke:var(--accent)}.single-report .orbit-short{stroke:var(--orbit-short)}.single-report .orbit-win{stroke:var(--up)}.single-report .orbit-loss{stroke:var(--down)}.single-report .orbit-fee{stroke:var(--warning)}.single-report .orbit-retained{stroke:var(--line-strong)}
|
|
1121
|
+
.single-report .orbit-hit{stroke:transparent;stroke-width:28;pointer-events:stroke;cursor:pointer}
|
|
1122
|
+
.single-report .orbit-hit:focus{outline:none;stroke:color-mix(in srgb,var(--focus) 22%,transparent)}
|
|
1123
|
+
.single-report .orbit-visual.is-exploring [data-orbit-shape]{opacity:.18}
|
|
1124
|
+
.single-report .orbit-visual.is-exploring [data-orbit-shape].is-active{opacity:1;stroke-width:15;filter:drop-shadow(0 0 7px color-mix(in srgb,var(--focus) 34%,transparent))}
|
|
1125
|
+
.single-report .orbit-core{position:absolute;inset:50% auto auto 50%;display:grid;place-items:center;width:98px;text-align:center;pointer-events:none;transform:translate(-50%,-50%)}
|
|
1126
|
+
.single-report .orbit-core span{max-width:96px;color:var(--faint);font-size:8px;line-height:1.15;text-transform:uppercase}
|
|
1127
|
+
.single-report .orbit-core strong{margin-top:3px;font:800 28px/1 "Avenir Next Condensed","Arial Narrow",sans-serif;font-variant-numeric:tabular-nums}
|
|
1128
|
+
.single-report .orbit-core small{min-height:17px;margin-top:4px;color:var(--dim);font:650 7px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
1129
|
+
.single-report .orbit-legend{display:grid;grid-template-columns:1fr 1fr;gap:5px 14px}
|
|
1130
|
+
.single-report .orbit-legend span{display:grid;grid-template-columns:7px 1fr auto;align-items:center;gap:6px;padding:3px 4px;color:var(--dim);font-size:9px;transition:color 160ms ease,background-color 160ms ease}
|
|
1131
|
+
.single-report .orbit-legend span.is-active{color:var(--text);background:color-mix(in srgb,var(--focus) 9%,transparent)}
|
|
1132
|
+
.single-report .orbit-legend i{width:6px;height:6px;border-radius:50%;background:var(--line)}
|
|
1133
|
+
.single-report .orbit-legend i.long{background:var(--accent)}.single-report .orbit-legend i.short{background:var(--orbit-short)}.single-report .orbit-legend i.win{background:var(--up)}.single-report .orbit-legend i.loss{background:var(--down)}.single-report .orbit-legend i.fee{background:var(--warning)}.single-report .orbit-legend i.retained{background:var(--line-strong)}
|
|
1134
|
+
.single-report .orbit-legend b{color:var(--text);font-weight:700;font-variant-numeric:tabular-nums}
|
|
1135
|
+
.single-report .telemetry-readouts{display:grid;grid-template-columns:1fr 1fr;margin:0}
|
|
1136
|
+
.single-report .telemetry-readout{min-width:0;padding:13px 15px;border-right:1px solid var(--line);border-bottom:1px solid var(--line)}
|
|
1137
|
+
.single-report .telemetry-readout:nth-child(even){border-right:0}.single-report .telemetry-readout:last-child{grid-column:1/-1;border-bottom:0}
|
|
1138
|
+
.single-report .telemetry-readout dt{color:var(--faint);font-size:8px;font-weight:750;text-transform:uppercase}
|
|
1139
|
+
.single-report .telemetry-readout dd{margin:5px 0 0;font-size:17px;font-weight:800;font-variant-numeric:tabular-nums}
|
|
1140
|
+
.single-report .telemetry-readout dd small{display:block;margin-top:2px;color:var(--dim);font-size:9px}
|
|
1141
|
+
.single-report .telemetry-band{display:grid;grid-template-columns:1fr 1.35fr .9fr;margin-top:12px;border:1px solid var(--line-strong);background:var(--panel-soft)}
|
|
1142
|
+
.single-report .telemetry-channel{min-width:0;padding:18px 20px;border-left:1px solid var(--line)}
|
|
1143
|
+
.single-report .telemetry-channel:first-child{border-left:0}
|
|
1144
|
+
.single-report .channel-heading{display:flex;align-items:center;gap:9px;margin-bottom:12px}
|
|
1145
|
+
.single-report .channel-heading span{width:18px;height:2px;background:var(--accent)}
|
|
1146
|
+
.single-report .activity-block .channel-heading span{background:var(--up)}.single-report .cost-block .channel-heading span{background:var(--warning)}
|
|
1147
|
+
.single-report .channel-heading h3{font-size:10px}
|
|
1148
|
+
.single-report .metric-list{display:grid;grid-template-columns:1fr 1fr;gap:0 20px;margin:0}
|
|
1149
|
+
.single-report .detail-metric{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;padding:7px 0;border-bottom:1px solid var(--line)}
|
|
1150
|
+
.single-report .detail-metric dt{color:var(--dim);font-size:9px}.single-report .detail-metric dd{margin:0;font-size:10px;font-weight:750;text-align:right;font-variant-numeric:tabular-nums}
|
|
1151
|
+
.single-report .detail-metric dd small{display:block;color:var(--faint);font-size:8px}
|
|
1152
|
+
.single-report .erosion-meter{height:4px;margin-top:13px;background:var(--line);overflow:hidden}.single-report .erosion-meter span{display:block;width:var(--erosion);height:100%;background:var(--warning)}
|
|
1153
|
+
.single-report .report-section{scroll-margin-top:30px;margin-top:90px}
|
|
1154
|
+
.single-report .regime-layout{display:grid;grid-template-columns:minmax(0,1.45fr) minmax(360px,.75fr);margin-top:18px;border:1px solid var(--line-strong);background:var(--panel)}
|
|
1155
|
+
.single-report .month-map,.single-report .distribution-map{min-width:0;padding:20px}
|
|
1156
|
+
.single-report .distribution-map{border-left:1px solid var(--line-strong)}
|
|
1157
|
+
.single-report .month-strip{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;margin-top:16px;background:var(--line)}
|
|
1158
|
+
.single-report .month-cell{position:relative;display:grid;grid-template-columns:1fr auto;gap:8px;padding:15px 14px;border:0;border-radius:0;background:color-mix(in srgb,var(--panel-raised) var(--month-fill),var(--up));color:var(--text);text-align:left;cursor:pointer;transition:transform 200ms var(--ease-out),filter 200ms ease,box-shadow 200ms ease}
|
|
1159
|
+
.single-report .month-cell.down{background:color-mix(in srgb,var(--panel-raised) var(--month-fill),var(--down));color:var(--text)}
|
|
1160
|
+
.single-report .month-cell:hover,.single-report .month-cell:focus-visible{z-index:2;transform:translateY(-3px);filter:saturate(1.25);box-shadow:0 14px 28px rgba(0,0,0,.2)}
|
|
1161
|
+
.single-report .month-cell[aria-pressed=true]{box-shadow:inset 0 0 0 2px var(--accent)}
|
|
1162
|
+
.single-report .month-cell>span{color:var(--dim);font-size:9px}.single-report .month-cell strong{font-size:16px;font-variant-numeric:tabular-nums;text-align:right}.single-report .month-cell small{color:var(--dim);font-size:9px}.single-report .month-cell em{color:var(--faint);font-size:8px;font-style:normal;text-align:right}
|
|
1163
|
+
.single-report .pnl-spectrum{position:relative;display:flex;align-items:end;gap:4px;height:252px;margin-top:16px;padding-top:44px;border-top:1px solid var(--line)}
|
|
1164
|
+
.single-report .spectrum-readout{position:absolute;z-index:3;top:8px;left:var(--spectrum-x,50%);display:flex;align-items:baseline;gap:8px;max-width:calc(100% - 12px);padding:5px 7px;border:1px solid var(--line-strong);border-radius:2px;background:var(--panel-raised);color:var(--text);box-shadow:0 8px 20px rgba(0,0,0,.14);font-size:8px;white-space:nowrap;transform:translateX(-50%);pointer-events:none}
|
|
1165
|
+
.single-report .spectrum-readout strong{font:700 8px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace}.single-report .spectrum-readout span{color:var(--dim)}
|
|
1166
|
+
.single-report .spectrum-bin{display:grid;grid-template-rows:18px 1fr 24px;justify-items:center;flex:1;height:100%;min-width:20px;padding:0;border:0;border-radius:0;background:transparent;color:inherit;font:inherit;cursor:pointer}
|
|
1167
|
+
.single-report .spectrum-bin>span{color:var(--dim);font-size:8px}.single-report .spectrum-bin i{align-self:end;width:100%;height:var(--bin-height);min-height:2px;background:var(--up);transform-origin:center bottom;transition:opacity 160ms ease,filter 160ms ease,transform 180ms var(--ease-out)}.single-report .spectrum-bin i.down{background:var(--down)}.single-report .spectrum-bin:hover i,.single-report .spectrum-bin:focus-visible i,.single-report .spectrum-bin.is-active i{filter:brightness(1.16);transform:scaleX(1.1)}.single-report .spectrum-bin:focus-visible{outline:1px solid var(--focus);outline-offset:2px}.single-report .pnl-spectrum.is-exploring .spectrum-bin i{opacity:.24}.single-report .pnl-spectrum.is-exploring .spectrum-bin.is-active i{opacity:1}.single-report .spectrum-bin small{align-self:end;color:var(--faint);font-size:7px;writing-mode:vertical-rl;transform:rotate(180deg)}
|
|
1168
|
+
.single-report .blueprint-grid{display:grid;grid-template-columns:1.35fr .85fr;gap:1px;margin-top:18px;border:1px solid var(--line-strong);background:var(--line)}
|
|
1169
|
+
.single-report .blueprint-panel{min-width:0;background:var(--panel)}
|
|
1170
|
+
.single-report .settings-panel{grid-row:span 2}
|
|
1171
|
+
.single-report .blueprint-panel summary{display:grid;grid-template-columns:32px 1fr 16px;align-items:center;gap:10px;min-height:54px;padding:8px 16px;border-bottom:1px solid var(--line);background:var(--panel-soft);cursor:pointer;list-style:none}
|
|
1172
|
+
.single-report .blueprint-panel summary::-webkit-details-marker{display:none}.single-report .blueprint-panel summary span{color:var(--accent);font:750 9px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.single-report .blueprint-panel summary strong{font-size:10px;text-transform:uppercase}.single-report .blueprint-panel summary i{position:relative;width:12px;height:12px}.single-report .blueprint-panel summary i::before,.single-report .blueprint-panel summary i::after{content:"";position:absolute;top:5px;left:1px;width:10px;height:1px;background:var(--dim);transition:transform 180ms ease}.single-report .blueprint-panel summary i::after{transform:rotate(90deg)}.single-report .blueprint-panel[open] summary i::after{transform:rotate(0)}
|
|
1173
|
+
.single-report .settings-ledger{display:grid;grid-template-columns:1fr 1fr;margin:0;padding:5px 18px 18px}
|
|
1174
|
+
.single-report .settings-ledger>div{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:18px;padding:10px 0;border-bottom:1px solid var(--line)}
|
|
1175
|
+
.single-report .settings-ledger>div:nth-child(odd){margin-right:18px}.single-report .settings-ledger dt{color:var(--dim);font-size:9px}.single-report .settings-ledger dd{margin:0;color:var(--text);font-size:10px;font-weight:700;text-align:right;font-variant-numeric:tabular-nums}
|
|
1176
|
+
.single-report .parameter-grid,.single-report .provenance-list{padding:14px 18px 18px}
|
|
1177
|
+
.single-report .parameter-grid>div,.single-report .provenance-list>div{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:18px;padding:9px 0;border-bottom:1px solid var(--line)}
|
|
1178
|
+
.single-report .parameter-grid code,.single-report .provenance-list dt{color:var(--dim);font-size:9px}.single-report .parameter-grid strong,.single-report .provenance-list dd{margin:0;color:var(--text);font-size:10px;text-align:right;overflow-wrap:anywhere}
|
|
1179
|
+
.single-report .parameter-grid .empty{padding:12px 0}
|
|
1180
|
+
.single-report .tape-heading{display:grid;grid-template-columns:minmax(240px,1fr) auto 48px;align-items:center}
|
|
1181
|
+
.single-report .tape-heading output{display:grid;place-items:center;width:48px;height:42px;border:1px solid var(--line-strong);color:var(--accent);font-weight:800;font-variant-numeric:tabular-nums}
|
|
1182
|
+
.single-report .trade-tape{border-top:1px solid var(--line-strong)}
|
|
1183
|
+
.single-report .tape-row{position:relative;display:grid;grid-template-columns:38px 100px 210px minmax(190px,1fr) 160px 190px;align-items:center;min-height:74px;border-bottom:1px solid var(--line);background:color-mix(in srgb,var(--panel) 70%,transparent);transition:background-color 170ms ease,transform 170ms var(--ease-out)}
|
|
1184
|
+
.single-report .tape-row:hover{z-index:1;background:var(--panel-raised);transform:translateX(4px)}
|
|
1185
|
+
.single-report .tape-row>div,.single-report .tape-index{min-width:0;padding:11px 14px;border-left:1px solid var(--line)}
|
|
1186
|
+
.single-report .tape-index{border-left:0;color:var(--faint);font-size:9px;text-align:center}
|
|
1187
|
+
.single-report .tape-side strong{display:block;color:var(--text);font-size:11px;text-transform:uppercase}.single-report .tape-side small,.single-report .tape-result small{display:block;margin-top:3px;color:var(--faint);font-size:8px}
|
|
1188
|
+
.single-report .tape-prices{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:10px;font-size:10px;font-variant-numeric:tabular-nums}.single-report .tape-prices span{font-weight:700}.single-report .tape-prices small{display:block;color:var(--faint);font-size:8px}.single-report .tape-prices i{color:var(--faint);font-style:normal}
|
|
1189
|
+
.single-report .tape-result{position:relative;align-self:stretch;display:flex;flex-direction:column;justify-content:center;overflow:hidden}.single-report .tape-result .pnl-bar{position:absolute;left:0;bottom:0;width:var(--pnl-size);height:3px;background:currentColor;opacity:.86}.single-report .tape-result strong{font-size:15px;font-weight:800;font-variant-numeric:tabular-nums}
|
|
1190
|
+
.single-report .tape-meta{display:grid;gap:5px;color:var(--faint);font-size:8px}.single-report .tape-meta span{display:flex;justify-content:space-between;gap:8px}.single-report .tape-meta b{color:var(--text);font-weight:700}
|
|
1191
|
+
.single-report .tape-time{display:grid;gap:5px;color:var(--dim);font-size:8px}.single-report .tape-time small{display:inline-block;min-width:48px;color:var(--faint);font:700 7px/1.2 Inter,"Segoe UI","PingFang SC",system-ui,sans-serif;text-transform:uppercase}
|
|
1192
|
+
.single-report .report-footer{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;margin-top:90px}.single-report .type-credit{color:var(--faint);font-size:9px}
|
|
1193
|
+
@media(prefers-reduced-motion:no-preference){.single-report .event-chip{animation:event-in .45s var(--ease-out) both}.single-report .event-chip:nth-child(2){animation-delay:140ms}.single-report .spectrum-bin i{animation:spectrum-in .65s var(--ease-out) both}}
|
|
1194
|
+
@keyframes event-in{from{opacity:0;transform:translateX(-50%) translateY(-8px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}
|
|
1195
|
+
@keyframes spectrum-in{from{transform:scaleY(0)}to{transform:scaleY(1)}}
|
|
1196
|
+
@media(max-width:1180px){.single-report main.report{width:calc(100% - 92px);padding-inline:24px}.single-report .observatory-frame{grid-template-columns:minmax(0,1fr) 300px}.single-report .mast-identity h1{font-size:52px}.single-report .telemetry-band{grid-template-columns:1fr 1fr}.single-report .cost-block{grid-column:1/-1;border-top:1px solid var(--line);border-left:0}.single-report .cost-block .metric-list{grid-template-columns:repeat(4,minmax(0,1fr))}.single-report .tape-row{grid-template-columns:34px 90px 190px minmax(170px,1fr) 140px 170px}}
|
|
1197
|
+
@media(max-width:980px){.single-report .command-rail{top:auto;bottom:12px;left:50%;flex-direction:row;transform:translateX(-50%)}.single-report .command-rail a{width:64px;height:48px;border-left:0;border-bottom:2px solid transparent}.single-report .command-rail a.active{border-left:0;border-bottom-color:var(--accent)}.single-report main.report{width:100%;padding:24px 24px 96px}.single-report .observatory-frame{grid-template-columns:1fr}.single-report .plot-module{border-right:0}.single-report .telemetry-module{display:grid;grid-template-columns:300px 1fr;border-top:1px solid var(--line-strong)}.single-report .execution-orbit{border-right:1px solid var(--line-strong);border-bottom:0}.single-report .regime-layout{grid-template-columns:1fr}.single-report .distribution-map{border-top:1px solid var(--line-strong);border-left:0}.single-report .tape-row{grid-template-columns:34px 88px minmax(180px,1fr) minmax(180px,1fr)}.single-report .tape-meta,.single-report .tape-time{grid-column:span 2;border-top:1px solid var(--line)}}
|
|
1198
|
+
@media(max-width:720px){body.single-report{font-size:16px}.single-report main.report{padding:18px 14px 96px}.single-report .mast-rail{grid-template-columns:auto 1fr auto;gap:10px 14px}.single-report .theme-toggle{grid-column:3;grid-row:1}.single-report .run-id{grid-column:1/-1;grid-row:2;justify-self:start}.single-report .mast-grid{grid-template-columns:1fr;gap:22px;padding:24px 0 18px}.single-report .mast-identity h1{font-size:38px;line-height:.98}.single-report .mast-outcome{align-items:flex-start;padding:16px 0 0;border-top:1px solid var(--line-strong);border-left:0;text-align:left}.single-report .mast-outcome strong{font-size:48px}.single-report .mast-grid .facts{overflow-x:auto}.single-report .facts div{min-width:128px;padding-right:18px}.single-report .observatory-heading{align-items:stretch;flex-direction:column}.single-report .mode-controls{display:grid;grid-template-columns:repeat(4,1fr)}.single-report .mode-controls button{min-height:44px;padding:0 8px}.single-report .section-heading h2{font-size:24px}.single-report .observatory-frame{border-inline:0}.single-report .range-toolbar{align-items:stretch;flex-direction:column;gap:8px}.single-report .range-toolbar>div{display:grid;grid-template-columns:repeat(4,1fr)}.single-report .range-toolbar button{min-height:44px}.single-report .chart-stack{grid-template-rows:260px 90px auto;padding-inline:7px}.single-report .event-rail{right:48px}.single-report .event-chip{padding:4px 5px}.single-report .event-chip b{display:none}.single-report .window-readouts{grid-template-columns:1fr 1fr}.single-report .window-readouts .window-period{grid-column:1/-1}.single-report .window-readouts>div:nth-child(3){border-left:0}.single-report .window-readouts strong{font-size:14px}.single-report .telemetry-module{display:block}.single-report .execution-orbit{border-right:0;border-bottom:1px solid var(--line-strong)}.single-report .orbit-visual{width:230px}.single-report .telemetry-band{grid-template-columns:1fr}.single-report .telemetry-channel,.single-report .telemetry-channel:first-child{border-top:1px solid var(--line);border-left:0}.single-report .telemetry-channel:first-child{border-top:0}.single-report .cost-block{grid-column:auto}.single-report .cost-block .metric-list{grid-template-columns:1fr 1fr}.single-report .report-section{margin-top:70px}.single-report .regime-layout{border-inline:0}.single-report .month-strip{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(155px,68vw);grid-template-columns:none;overflow-x:auto;scroll-snap-type:x mandatory}.single-report .month-cell{min-height:114px;scroll-snap-align:start}.single-report .pnl-spectrum{height:220px;overflow-x:auto}.single-report .spectrum-bin{min-width:26px}.single-report .blueprint-grid{grid-template-columns:1fr;border-inline:0}.single-report .settings-panel{grid-row:auto}.single-report .settings-ledger{grid-template-columns:1fr}.single-report .settings-ledger>div:nth-child(odd){margin-right:0}.single-report .blueprint-panel summary{min-height:56px}.single-report .tape-heading{grid-template-columns:1fr 48px}.single-report .tape-heading .section-heading{grid-column:1/-1}.single-report .tape-controls{grid-column:1/-1;overflow-x:auto}.single-report .tape-controls button{min-width:max-content;min-height:44px}.single-report .tape-row{grid-template-columns:38px 1fr auto;align-items:start}.single-report .tape-prices{grid-column:2/-1;border-top:1px solid var(--line)}.single-report .tape-result{grid-column:2/-1;min-height:62px;border-top:1px solid var(--line)}.single-report .tape-meta,.single-report .tape-time{grid-column:2/-1}.single-report .report-footer{display:block}.single-report .report-footer p+ p{margin-top:8px}.single-report .command-rail{width:calc(100% - 24px);justify-content:space-between}.single-report .command-rail a{flex:1}}
|
|
1199
|
+
@media(max-width:390px){.single-report .mast-identity h1{font-size:34px}.single-report .section-heading{grid-template-columns:40px 1fr}.single-report .section-heading .section-index{width:40px;height:40px}.single-report .metric-list{grid-template-columns:1fr}.single-report .cost-block .metric-list{grid-template-columns:1fr}.single-report .window-readouts>div{padding:11px 12px}.single-report .orbit-legend{grid-template-columns:1fr 1fr}.single-report .tape-row>div,.single-report .tape-index{padding:10px}}
|
|
1200
|
+
@media print{.single-report{background:#fff!important;color:#111}.single-report .command-rail,.single-report .scroll-progress,.single-report .mode-controls,.single-report .range-toolbar button,.single-report .tape-controls{display:none!important}.single-report main.report{width:100%;padding:0}.single-report .observatory-frame,.single-report .telemetry-band,.single-report .regime-layout,.single-report .blueprint-grid{break-inside:avoid;box-shadow:none}.single-report details>*{display:block!important}.single-report .report-section{margin-top:36px}}
|
|
621
1201
|
`;
|
|
622
1202
|
/**
|
|
623
1203
|
* Styles for the comparison document.
|
|
@@ -626,16 +1206,14 @@ footer p{margin:4px 0}
|
|
|
626
1206
|
* second copy: the two documents should not drift apart visually, and a duplicated
|
|
627
1207
|
* sheet is exactly how that happens.
|
|
628
1208
|
*/
|
|
629
|
-
export const COMPARE_STYLES =
|
|
630
|
-
|
|
631
|
-
.
|
|
632
|
-
.legend i,th i{display:inline-block;width:10px;height:10px;border-radius:2px;flex-shrink:0}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
.chart .tooltip .swatch{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:6px}
|
|
639
|
-
.chart .tooltip .row.head{border-bottom:1px solid var(--line);margin-bottom:3px;padding-bottom:3px}
|
|
1209
|
+
export const COMPARE_STYLES = String.raw `
|
|
1210
|
+
:root{color-scheme:light;--bg:#f3f4f7;--panel:#fbfbfd;--panel-raised:#fff;--text:#17181d;--dim:#60636d;--faint:#9296a1;--line:#d8dae1;--line-strong:#b8bbc4;--accent:#6f3ff5;--focus:#8f6bff;--up:#007f64;--down:#c4384e;--warning:#9a6700;--shadow:0 20px 60px rgba(21,18,32,.12);--ease-out:cubic-bezier(.16,1,.3,1)}
|
|
1211
|
+
*{box-sizing:border-box}html{scroll-behavior:smooth}body.comparison-report{margin:0;min-width:320px;background-color:var(--bg);background-image:linear-gradient(var(--line) 1px,transparent 1px),linear-gradient(90deg,var(--line) 1px,transparent 1px);background-size:88px 88px;color:var(--text);font:14px/1.5 "Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;letter-spacing:0}body.comparison-report[data-theme=dark]{color-scheme:dark;--bg:#020305;--panel:#080a0e;--panel-raised:#0d1016;--text:#f5f7fb;--dim:#a7aab4;--faint:#6f737f;--line:#20242d;--line-strong:#3a3f4a;--accent:#955cff;--focus:#b89aff;--up:#0ecb81;--down:#f6465d;--warning:#d6a62d;--shadow:0 24px 72px rgba(0,0,0,.46)}
|
|
1212
|
+
button{font:inherit;color:inherit}button:focus-visible,summary:focus-visible,.compare-chart:focus-visible{outline:2px solid var(--focus);outline-offset:3px}.mono,code{font-family:"SFMono-Regular",Consolas,"Liberation Mono",monospace;font-variant-numeric:tabular-nums}.positive{color:var(--up)!important}.negative{color:var(--down)!important}.neutral{color:var(--text)!important}.compare-progress{position:fixed;z-index:30;top:0;left:0;width:100%;height:3px;background:var(--accent);transform:scaleX(0);transform-origin:left center}.compare-shell{width:min(1600px,calc(100% - 112px));margin:0 auto;padding:30px 0 96px}.compare-mast{border-bottom:1px solid var(--line-strong)}.compare-rail{display:grid;grid-template-columns:auto auto minmax(0,1fr) 38px;align-items:center;gap:22px;min-height:52px;border-bottom:1px solid var(--line)}.brand-mark{color:var(--accent);font:11px/1 "SFMono-Regular",Consolas,monospace}.compare-state{display:flex;align-items:center;gap:7px;color:var(--dim);font-size:10px;text-transform:uppercase}.compare-state i{width:7px;height:7px;border-radius:50%;background:var(--up);box-shadow:0 0 0 4px color-mix(in srgb,var(--up) 12%,transparent)}.run-signature{overflow:hidden;color:var(--faint);font-size:10px;text-align:right;text-overflow:ellipsis;white-space:nowrap}.compare-theme{display:grid;place-items:center;width:34px;height:34px;border:1px solid var(--line-strong);border-radius:50%;background:var(--text);color:var(--bg);cursor:pointer}.compare-identity{display:grid;grid-template-columns:minmax(0,1fr) 260px;align-items:end;gap:48px;padding:62px 0 34px}.instrument-line{display:block;margin-bottom:18px;color:var(--accent);font-size:11px}.compare-identity h1{max-width:1100px;margin:0;font:400 clamp(58px,8vw,128px)/.78 BubbledotICG-FinePos,"Arial Black",sans-serif;text-transform:uppercase;overflow-wrap:anywhere}.compare-identity p{margin:24px 0 0;color:var(--dim);font-size:15px}.run-total{display:flex;align-items:flex-end;justify-content:flex-end;gap:16px;padding:12px 0 6px;border-left:1px solid var(--line-strong)}.run-total span{padding-bottom:13px;color:var(--dim);font-size:10px;text-transform:uppercase}.run-total strong{font:400 86px/.75 BubbledotICG-FinePos,sans-serif}.compare-facts{display:grid;grid-template-columns:2fr repeat(3,1fr);margin:0;border-top:1px solid var(--line)}.compare-facts div{min-width:0;padding:16px 22px;border-right:1px solid var(--line)}.compare-facts div:last-child{border-right:0}.compare-facts dt{color:var(--faint);font-size:9px;text-transform:uppercase}.compare-facts dd{margin:5px 0 0;overflow:hidden;font-weight:750;text-overflow:ellipsis;white-space:nowrap}.warnings{margin:28px 0 0;border:1px solid color-mix(in srgb,var(--warning) 50%,var(--line));background:color-mix(in srgb,var(--warning) 7%,var(--panel))}.warnings div{display:grid;grid-template-columns:44px minmax(0,1fr) auto;align-items:center;border-bottom:1px solid color-mix(in srgb,var(--warning) 28%,var(--line))}.warnings div:last-child{border-bottom:0}.warnings span,.warnings code{padding:13px 15px;color:var(--warning);font-size:9px}.warnings p{margin:0;padding:13px 18px;border-inline:1px solid color-mix(in srgb,var(--warning) 28%,var(--line));color:var(--text)}.compare-section{margin-top:84px}.compare-heading{display:grid;grid-template-columns:54px minmax(0,1fr);align-items:center;margin-bottom:16px}.compare-heading>span{display:grid;place-items:center;width:54px;height:48px;border:1px solid var(--line-strong);color:var(--accent);font:11px/1 "SFMono-Regular",Consolas,monospace}.compare-heading h2{margin:0 0 0 16px;font-size:32px;line-height:1;letter-spacing:0}.run-deck{display:grid;grid-template-columns:repeat(var(--run-columns,2),minmax(0,1fr));border:1px solid var(--line-strong);border-bottom:0;background:var(--panel)}.run-track{position:relative;display:grid;grid-template-columns:38px minmax(130px,1fr) auto auto;align-items:center;gap:16px;min-width:0;min-height:78px;padding:12px 18px;border:0;border-right:1px solid var(--line);border-bottom:1px solid var(--line);background:transparent;text-align:left;cursor:pointer;transition:background-color 160ms ease,opacity 160ms ease}.run-track:nth-child(2n){border-right:0}.run-track::before{position:absolute;top:0;right:0;left:0;height:3px;background:var(--series);content:""}.run-track:hover{background:var(--panel-raised)}.run-track.is-muted{opacity:.38}.run-number{color:var(--series);font:10px/1 "SFMono-Regular",Consolas,monospace}.run-name{min-width:0}.run-name strong,.run-name small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.run-name strong{font-size:13px}.run-name small{margin-top:4px;color:var(--faint);font-size:9px}.run-metric{display:grid;gap:3px;min-width:78px;text-align:right}.run-metric small{color:var(--faint);font-size:8px;text-transform:uppercase}.run-metric b{font-size:14px;font-variant-numeric:tabular-nums}.compare-chart-frame{border:1px solid var(--line-strong);background:var(--panel);box-shadow:var(--shadow)}.compare-toolbar{display:flex;align-items:center;justify-content:space-between;min-height:58px;padding:8px 12px 8px 18px;border-bottom:1px solid var(--line-strong)}.compare-toolbar>span{color:var(--dim);font-size:10px;text-transform:uppercase}.compare-toolbar>div{display:flex;padding:4px;border-radius:999px;background:var(--panel-raised);box-shadow:0 5px 18px rgba(22,19,31,.1)}.compare-toolbar button{min-width:70px;height:38px;padding:0 16px;border:0;border-radius:999px;background:transparent;color:var(--dim);font-size:11px;font-weight:750;cursor:pointer}.compare-toolbar button[aria-pressed=true]{background:var(--text);color:var(--bg)}.compare-chart{position:relative;height:430px;overflow:hidden;cursor:crosshair;touch-action:pan-y}.compare-chart.is-dragging{cursor:grabbing}.compare-chart svg{display:block;width:100%;height:100%}.compare-grid line{stroke:var(--line);stroke-width:1;vector-effect:non-scaling-stroke}.compare-baseline{stroke:var(--faint);stroke-width:1;stroke-dasharray:5 7;vector-effect:non-scaling-stroke}.compare-series{stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;filter:drop-shadow(0 0 4px color-mix(in srgb,currentColor 22%,transparent));transition:opacity 180ms ease}.compare-series.is-hidden{opacity:0}.compare-crosshair{stroke:var(--faint);stroke-width:1;stroke-dasharray:3 4;vector-effect:non-scaling-stroke}.compare-marker{fill:var(--panel-raised);stroke-width:2.5;vector-effect:non-scaling-stroke}.compare-axis{position:absolute;inset:10px 8px 12px auto;display:flex;flex-direction:column;justify-content:space-between;color:var(--dim);font:700 9px/1 "SFMono-Regular",Consolas,monospace;pointer-events:none}.compare-tooltip{position:absolute;z-index:5;top:22px;width:min(340px,calc(100% - 28px));padding:12px 14px;border:1px solid var(--line-strong);border-radius:4px;background:var(--panel-raised);box-shadow:var(--shadow);pointer-events:none}.compare-tooltip .row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:18px;padding:5px 0;color:var(--dim);font-size:11px}.compare-tooltip .row span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.compare-tooltip .row b{color:var(--text);font-variant-numeric:tabular-nums}.compare-tooltip .row.head{margin-bottom:5px;padding-bottom:8px;border-bottom:1px solid var(--line)}.compare-tooltip .swatch{display:inline-block;width:8px;height:8px;margin-right:7px;border-radius:2px}.compare-range{margin:0;padding:12px 18px;border-top:1px solid var(--line);color:var(--faint);font-size:9px}.compare-window-strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));border-top:1px solid var(--line-strong)}.compare-window-strip>div{position:relative;display:grid;grid-template-columns:1fr auto;gap:4px 16px;padding:14px 18px;border-right:1px solid var(--line);transition:opacity 160ms ease}.compare-window-strip>div::before{position:absolute;top:0;right:0;left:0;height:2px;background:var(--series);content:""}.compare-window-strip>div:last-child{border-right:0}.compare-window-strip>div.is-hidden{opacity:.35}.compare-window-strip span{overflow:hidden;color:var(--faint);font:9px/1.3 "SFMono-Regular",Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.compare-window-strip strong{grid-row:span 2;font-size:18px;font-variant-numeric:tabular-nums}.compare-window-strip small{color:var(--dim);font-size:9px}.compare-window-strip small span{margin-right:6px}.matrix-shell{overflow:auto;border:1px solid var(--line-strong);background:var(--panel);box-shadow:var(--shadow)}table.matrix{width:100%;min-width:max(780px,calc(190px + var(--run-count,2)*210px));border-collapse:separate;border-spacing:0}table.matrix th,table.matrix td{height:54px;padding:10px 16px;border-right:1px solid var(--line);border-bottom:1px solid var(--line)}table.matrix tr>*:last-child{border-right:0}table.matrix tbody tr:last-child>*{border-bottom:0}table.matrix thead th{position:sticky;top:0;z-index:2;height:70px;background:var(--panel-raised);text-align:right}table.matrix thead th:first-child{text-align:left}table.matrix thead th.n{box-shadow:inset 0 3px var(--series)}table.matrix thead i{display:inline-block;width:8px;height:8px;margin-right:8px;border-radius:2px;background:var(--series)}table.matrix thead .dim{display:block;margin-top:5px;color:var(--faint);font-size:8px;font-weight:400}table.matrix th[scope=row]{position:sticky;left:0;z-index:1;min-width:190px;background:var(--panel);color:var(--dim);font-size:11px;font-weight:550;text-align:left}table.matrix td{text-align:right;font-variant-numeric:tabular-nums}table.matrix td span{display:inline-block;min-width:74px}table.matrix td.best{position:relative;background:color-mix(in srgb,var(--up) 8%,var(--panel));color:var(--up);font-weight:800}table.matrix td.best::after{position:absolute;top:8px;right:8px;width:5px;height:5px;border-radius:50%;background:var(--up);content:""}.assumption-matrix th code{color:var(--accent);font-size:10px}.assumption-matrix td.differs{background:color-mix(in srgb,var(--accent) 5%,var(--panel))}.shared-assumptions{margin-top:12px;border:1px solid var(--line-strong);background:var(--panel)}.shared-assumptions summary{padding:16px 18px;color:var(--dim);font-size:11px;cursor:pointer}.shared-assumptions dl{display:grid;grid-template-columns:1fr 1fr;margin:0;padding:0 18px 18px}.shared-assumptions dl div{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:18px;padding:9px 0;border-bottom:1px solid var(--line)}.shared-assumptions dl div:nth-child(odd){margin-right:24px}.shared-assumptions dt{min-width:0;color:var(--dim);overflow-wrap:anywhere}.shared-assumptions dd{margin:0;color:var(--text);font-size:11px;text-align:right;overflow-wrap:anywhere}.empty{margin:0;padding:28px;border:1px solid var(--line-strong);background:var(--panel);color:var(--dim)}.compare-footer{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr) auto;gap:30px;margin-top:90px;padding-top:22px;border-top:1px solid var(--line-strong);color:var(--faint);font-size:9px}.compare-footer p{margin:0}.type-credit{text-align:right}
|
|
1213
|
+
@media(max-width:1100px){.compare-shell{width:calc(100% - 48px)}.compare-identity{grid-template-columns:minmax(0,1fr) 190px}.compare-identity h1{font-size:72px}.run-total strong{font-size:66px}.run-track{grid-template-columns:32px minmax(100px,1fr) auto}.run-track .run-metric:last-child{display:none}.compare-facts{grid-template-columns:2fr repeat(3,1fr)}}
|
|
1214
|
+
@media(max-width:760px){body.comparison-report{background-size:64px 64px}.compare-shell{width:100%;padding:18px 14px 72px}.compare-rail{grid-template-columns:auto 1fr 36px;gap:10px}.compare-state{display:none}.run-signature{grid-column:1/-1;grid-row:2;text-align:left}.compare-identity{grid-template-columns:1fr;gap:22px;padding:38px 0 24px}.compare-identity h1{font-size:clamp(48px,17vw,72px)}.run-total{justify-content:flex-start;border-top:1px solid var(--line);border-left:0}.compare-facts{grid-template-columns:1fr 1fr}.compare-facts div:nth-child(2){border-right:0}.compare-facts div:first-child{grid-column:1/-1;border-right:0}.warnings div{grid-template-columns:38px minmax(0,1fr)}.warnings code{display:none}.compare-section{margin-top:54px}.compare-heading h2{font-size:24px}.run-deck{grid-template-columns:1fr}.run-track{border-right:0}.compare-toolbar{align-items:stretch;flex-direction:column;gap:8px}.compare-toolbar>div{display:grid;grid-template-columns:repeat(4,1fr)}.compare-toolbar button{min-width:0;padding:0 8px}.compare-chart{height:330px}.compare-window-strip{grid-template-columns:1fr 1fr}.compare-window-strip>div:nth-child(2n){border-right:0}.shared-assumptions dl{grid-template-columns:1fr}.shared-assumptions dl div:nth-child(odd){margin-right:0}.compare-footer{grid-template-columns:1fr}.type-credit{text-align:left}}
|
|
1215
|
+
@media(max-width:420px){.compare-shell{padding-inline:10px}.brand-mark{font-size:9px}.compare-facts{grid-template-columns:1fr}.compare-facts div,.compare-facts div:nth-child(2){grid-column:auto;border-right:0}.run-track{grid-template-columns:28px minmax(0,1fr) auto;padding-inline:12px}.run-track .run-metric{min-width:68px}.compare-window-strip{grid-template-columns:1fr}.compare-window-strip>div{border-right:0}.compare-tooltip{top:10px;padding:10px}.compare-tooltip .row{font-size:9px}}
|
|
1216
|
+
@media(prefers-reduced-motion:no-preference){.compare-mast{animation:compare-in .65s var(--ease-out) both}.compare-section{animation:compare-in .65s var(--ease-out) both}.compare-section:nth-of-type(2){animation-delay:80ms}.compare-section:nth-of-type(3){animation-delay:150ms}@keyframes compare-in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}}
|
|
1217
|
+
@media print{body.comparison-report{background:#fff;color:#111}.compare-progress,.compare-theme,.compare-toolbar,.run-deck{display:none!important}.compare-shell{width:100%;padding:0}.compare-chart-frame,.matrix-shell{box-shadow:none}.compare-section{margin-top:36px}.compare-footer{margin-top:40px}}
|
|
640
1218
|
`;
|
|
641
1219
|
//# sourceMappingURL=html.js.map
|