desic-okx-agent 0.3.1 → 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.
Files changed (50) hide show
  1. package/README.en.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/i18n/messages.d.ts +27 -0
  4. package/dist/i18n/messages.js +55 -1
  5. package/dist/i18n/messages.js.map +1 -1
  6. package/dist/report/brand-font.d.ts +1 -0
  7. package/dist/report/brand-font.js +4 -0
  8. package/dist/report/brand-font.js.map +1 -0
  9. package/dist/report/chart-script.d.ts +3 -8
  10. package/dist/report/chart-script.js +735 -115
  11. package/dist/report/chart-script.js.map +1 -1
  12. package/dist/report/compare-html.d.ts +1 -6
  13. package/dist/report/compare-html.js +177 -122
  14. package/dist/report/compare-html.js.map +1 -1
  15. package/dist/report/compare-script.d.ts +1 -11
  16. package/dist/report/compare-script.js +310 -64
  17. package/dist/report/compare-script.js.map +1 -1
  18. package/dist/report/fetch.d.ts +32 -0
  19. package/dist/report/fetch.js +42 -0
  20. package/dist/report/fetch.js.map +1 -1
  21. package/dist/report/html.d.ts +1 -1
  22. package/dist/report/html.js +846 -268
  23. package/dist/report/html.js.map +1 -1
  24. package/dist/strategy/service.js +4 -1
  25. package/dist/strategy/service.js.map +1 -1
  26. package/dist/strategy/store.d.ts +12 -1
  27. package/dist/strategy/store.js +32 -1
  28. package/dist/strategy/store.js.map +1 -1
  29. package/dist/tools/catalog.js +20 -1
  30. package/dist/tools/catalog.js.map +1 -1
  31. package/dist/tui/app.js +106 -35
  32. package/dist/tui/app.js.map +1 -1
  33. package/dist/tui/commands.d.ts +33 -0
  34. package/dist/tui/commands.js +59 -14
  35. package/dist/tui/commands.js.map +1 -1
  36. package/dist/tui/file-completion.d.ts +13 -0
  37. package/dist/tui/file-completion.js +57 -0
  38. package/dist/tui/file-completion.js.map +1 -0
  39. package/docs/strategy-research.md +16 -3
  40. package/package.json +1 -1
  41. package/python/desic_strategy/context.py +23 -5
  42. package/python/desic_strategy/engine.py +4 -1
  43. package/python/desic_strategy/live.py +1 -1
  44. package/python/desic_strategy/policy.py +51 -0
  45. package/python/desic_strategy/runner.py +36 -9
  46. package/python/desic_strategy/timeframe.py +17 -0
  47. package/skills/okx-strategy-research/SKILL.md +55 -1
  48. package/skills/okx-strategy-research/agents/openai.yaml +1 -1
  49. package/skills/okx-strategy-research/references/python-api.md +7 -0
  50. package/skills/okx-strategy-research/references/tools-and-data.md +20 -0
@@ -1,109 +1,355 @@
1
- /**
2
- * Hover readout for the comparison overlay.
3
- *
4
- * Shows every curve's value at one instant, which is the question an overlay
5
- * raises and a static image cannot answer: two lines crossing tells you they
6
- * crossed, not by how much.
7
- *
8
- * Same constraints as the single-run script — one IIFE, no network, no storage,
9
- * data read from a `data-series` attribute — so the report stays a self-contained
10
- * file that sends nothing.
11
- */
1
+ /** Interactive controller for the self-contained comparison observatory. */
12
2
  export const COMPARE_SCRIPT = String.raw `
13
3
  (function () {
14
4
  var chart = document.getElementById("compare-chart");
15
- if (!chart) return;
5
+ var themeToggle = document.querySelector("[data-theme-toggle]");
6
+ if (themeToggle) {
7
+ var systemDark = matchMedia("(prefers-color-scheme: dark)").matches;
8
+ themeToggle.setAttribute("aria-pressed", systemDark ? "true" : "false");
9
+ themeToggle.addEventListener("click", function () {
10
+ var explicit = document.body.getAttribute("data-theme");
11
+ var dark = explicit ? explicit === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
12
+ document.body.setAttribute("data-theme", dark ? "light" : "dark");
13
+ themeToggle.setAttribute("aria-pressed", dark ? "false" : "true");
14
+ });
15
+ }
16
16
 
17
+ var progress = document.querySelector(".compare-progress");
18
+ var progressFrame = 0;
19
+ addEventListener("scroll", function () {
20
+ if (progressFrame) return;
21
+ progressFrame = requestAnimationFrame(function () {
22
+ var maximum = Math.max(1, document.documentElement.scrollHeight - innerHeight);
23
+ if (progress) progress.style.transform = "scaleX(" + Math.min(1, scrollY / maximum) + ")";
24
+ progressFrame = 0;
25
+ });
26
+ }, { passive: true });
27
+
28
+ if (!chart) return;
17
29
  var payload;
18
30
  try {
19
31
  payload = JSON.parse(chart.getAttribute("data-series") || "");
20
32
  } catch (error) {
21
33
  return;
22
34
  }
23
- if (!payload || !payload.series || !payload.series.length) return;
35
+ if (!payload || !Array.isArray(payload.series) || payload.series.length === 0) return;
24
36
 
25
37
  var svg = chart.querySelector("svg");
26
- var tooltip = chart.querySelector(".tooltip");
27
- var crosshair = chart.querySelector(".crosshair");
28
- if (!svg || !tooltip) return;
38
+ var tooltip = chart.querySelector(".compare-tooltip");
39
+ var crosshair = chart.querySelector(".compare-crosshair");
40
+ if (!svg || !tooltip || !crosshair) return;
29
41
 
30
- var span = payload.endMs - payload.startMs || 1;
42
+ var reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
43
+ var fullStart = payload.startMs;
44
+ var fullEnd = payload.endMs;
45
+ var fullDuration = Math.max(1, fullEnd - fullStart);
46
+ var currentStart = fullStart;
47
+ var currentEnd = fullEnd;
48
+ var active = payload.series.map(function () { return true; });
49
+ var viewWidth = payload.width;
50
+ var viewHeight = payload.height;
51
+ var animation = new WeakMap();
52
+ var minimumGap = fullDuration;
53
+ payload.series.forEach(function (series) {
54
+ for (var index = 1; index < series.t.length; index += 1) {
55
+ var gap = series.t[index] - series.t[index - 1];
56
+ if (gap > 0) minimumGap = Math.min(minimumGap, gap);
57
+ }
58
+ });
59
+
60
+ function escapeText(value) {
61
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&#39;");
62
+ }
31
63
 
32
64
  function formatTime(ms) {
33
65
  return new Date(ms).toISOString().slice(0, 16).replace("T", " ");
34
66
  }
35
67
 
36
- // Nearest sample at or before the cursor's time, by binary search: a linear scan
37
- // per mouse move is visibly slow once six series carry 900 points each.
68
+ function signed(value) {
69
+ return (value > 0 ? "+" : "") + value.toFixed(2) + "%";
70
+ }
71
+
72
+ function tone(element, value) {
73
+ element.classList.remove("positive", "negative", "neutral");
74
+ element.classList.add(value > 0 ? "positive" : value < 0 ? "negative" : "neutral");
75
+ }
76
+
77
+ function xForTime(timeMs) {
78
+ return ((timeMs - fullStart) / fullDuration) * viewWidth;
79
+ }
80
+
81
+ function yFor(value) {
82
+ return viewHeight - ((value - payload.minimum) / Math.max(0.0001, payload.maximum - payload.minimum)) * viewHeight;
83
+ }
84
+
38
85
  function sampleAt(series, timeMs) {
39
86
  var times = series.t;
40
- if (timeMs < times[0] || timeMs > times[times.length - 1]) return null;
87
+ if (!times.length || timeMs < times[0] || timeMs > times[times.length - 1]) return null;
41
88
  var low = 0;
42
89
  var high = times.length - 1;
43
90
  while (low < high) {
44
- var mid = (low + high + 1) >> 1;
45
- if (times[mid] <= timeMs) low = mid;
46
- else high = mid - 1;
91
+ var middle = (low + high + 1) >> 1;
92
+ if (times[middle] <= timeMs) low = middle;
93
+ else high = middle - 1;
94
+ }
95
+ return { index: low, time: times[low], value: series.v[low] };
96
+ }
97
+
98
+ function samplesInWindow(series, startTime, endTime) {
99
+ var values = [];
100
+ var start = sampleAt(series, Math.max(startTime, series.t[0]));
101
+ if (start) values.push(start.value);
102
+ for (var index = 0; index < series.t.length; index += 1) {
103
+ if (series.t[index] < startTime) continue;
104
+ if (series.t[index] > endTime) break;
105
+ values.push(series.v[index]);
106
+ }
107
+ var end = sampleAt(series, Math.min(endTime, series.t[series.t.length - 1]));
108
+ if (end) values.push(end.value);
109
+ return values;
110
+ }
111
+
112
+ function animateViewBox(target, immediate) {
113
+ var prior = animation.get(svg);
114
+ if (prior) cancelAnimationFrame(prior);
115
+ if (immediate || reduced) {
116
+ svg.setAttribute("viewBox", target.join(" "));
117
+ sizeMarkers();
118
+ return;
119
+ }
120
+ var from = (svg.getAttribute("viewBox") || target.join(" ")).split(/\s+/).map(Number);
121
+ var started = performance.now();
122
+ function frame(now) {
123
+ var progress = Math.min(1, (now - started) / 260);
124
+ var eased = 1 - Math.pow(1 - progress, 4);
125
+ var values = target.map(function (value, index) { return from[index] + (value - from[index]) * eased; });
126
+ svg.setAttribute("viewBox", values.join(" "));
127
+ sizeMarkers();
128
+ if (progress < 1) animation.set(svg, requestAnimationFrame(frame));
129
+ else animation.delete(svg);
130
+ }
131
+ animation.set(svg, requestAnimationFrame(frame));
132
+ }
133
+
134
+ function sizeMarkers() {
135
+ var bounds = svg.getBoundingClientRect();
136
+ if (bounds.width <= 0 || bounds.height <= 0) return;
137
+ var box = (svg.getAttribute("viewBox") || "0 0 1000 320").split(/\s+/).map(Number);
138
+ chart.querySelectorAll("[data-marker-radius]").forEach(function (marker) {
139
+ var radius = Number(marker.getAttribute("data-marker-radius")) || 4;
140
+ marker.setAttribute("rx", Math.max(0.001, (radius * box[2]) / bounds.width));
141
+ marker.setAttribute("ry", Math.max(0.001, (radius * box[3]) / bounds.height));
142
+ });
143
+ }
144
+
145
+ function updateWindowStrip() {
146
+ payload.series.forEach(function (series, index) {
147
+ var panel = document.querySelector('[data-window-series="' + index + '"]');
148
+ if (!panel) return;
149
+ panel.classList.toggle("is-hidden", !active[index]);
150
+ var start = sampleAt(series, Math.max(currentStart, series.t[0]));
151
+ var end = sampleAt(series, Math.min(currentEnd, series.t[series.t.length - 1]));
152
+ var output = panel.querySelector("[data-window-return]");
153
+ var value = panel.querySelector("[data-window-value]");
154
+ if (!start || !end || end.time < currentStart || start.time > currentEnd) {
155
+ output.textContent = "-";
156
+ value.textContent = "-";
157
+ tone(output, 0);
158
+ return;
159
+ }
160
+ var change = start.value !== 0 ? (end.value / start.value - 1) * 100 : 0;
161
+ output.textContent = signed(change);
162
+ value.textContent = end.value.toFixed(2);
163
+ tone(output, change);
164
+ });
165
+ var period = document.getElementById("compare-window-period");
166
+ if (period) period.textContent = formatTime(currentStart) + " → " + formatTime(currentEnd);
167
+ }
168
+
169
+ function setRange(startTime, endTime, source, immediate) {
170
+ var duration = Math.max(minimumGap * 8, Math.min(fullDuration, endTime - startTime));
171
+ startTime = Math.max(fullStart, Math.min(fullEnd - duration, startTime));
172
+ endTime = startTime + duration;
173
+ currentStart = startTime;
174
+ currentEnd = endTime;
175
+
176
+ var localMinimum = Infinity;
177
+ var localMaximum = -Infinity;
178
+ payload.series.forEach(function (series, index) {
179
+ if (!active[index]) return;
180
+ samplesInWindow(series, currentStart, currentEnd).forEach(function (value) {
181
+ localMinimum = Math.min(localMinimum, value);
182
+ localMaximum = Math.max(localMaximum, value);
183
+ });
184
+ });
185
+ if (!Number.isFinite(localMinimum) || !Number.isFinite(localMaximum)) {
186
+ localMinimum = payload.minimum;
187
+ localMaximum = payload.maximum;
47
188
  }
48
- return { time: times[low], value: series.v[low] };
189
+ var centre = (localMinimum + localMaximum) / 2;
190
+ var visibleRange = Math.max(4, (localMaximum - localMinimum) * 1.3);
191
+ var visibleMaximum = centre + visibleRange / 2;
192
+ var visibleMinimum = centre - visibleRange / 2;
193
+ var xStart = xForTime(currentStart);
194
+ var xEnd = xForTime(currentEnd);
195
+ var yTop = yFor(visibleMaximum);
196
+ var yBottom = yFor(visibleMinimum);
197
+ animateViewBox([xStart, yTop, Math.max(0.001, xEnd - xStart), Math.max(0.001, yBottom - yTop)], immediate);
198
+
199
+ var maximumLabel = chart.querySelector("[data-axis-maximum]");
200
+ var minimumLabel = chart.querySelector("[data-axis-minimum]");
201
+ if (maximumLabel) maximumLabel.textContent = visibleMaximum.toFixed(1);
202
+ if (minimumLabel) minimumLabel.textContent = visibleMinimum.toFixed(1);
203
+ document.querySelectorAll("[data-compare-range]").forEach(function (button) {
204
+ button.setAttribute("aria-pressed", button === source ? "true" : "false");
205
+ });
206
+ updateWindowStrip();
207
+ hide();
49
208
  }
50
209
 
51
210
  function show(clientX) {
52
211
  var box = svg.getBoundingClientRect();
53
212
  if (box.width <= 0) return;
54
- var ratio = Math.min(1, Math.max(0, (clientX - box.left) / box.width));
55
- var timeMs = payload.startMs + ratio * span;
56
-
57
- var rows = '<div class="row head"><span>' + payload.labels.time + "</span><b>" + formatTime(timeMs) + "</b></div>";
58
- for (var index = 0; index < payload.series.length; index += 1) {
59
- var series = payload.series[index];
213
+ var ratio = Math.max(0, Math.min(1, (clientX - box.left) / box.width));
214
+ var timeMs = currentStart + ratio * (currentEnd - currentStart);
215
+ var rows = '<div class="row head"><span>' + escapeText(payload.labels.time) + "</span><b>" + formatTime(timeMs) + "</b></div>";
216
+ payload.series.forEach(function (series, index) {
217
+ var marker = chart.querySelector('[data-series-marker="' + index + '"]');
218
+ if (!active[index]) {
219
+ if (marker) marker.setAttribute("hidden", "");
220
+ return;
221
+ }
60
222
  var sample = sampleAt(series, timeMs);
61
- // A run whose window does not cover this instant shows a dash rather than
62
- // its nearest endpoint, which would imply data it does not have.
63
- var reading = sample === null ? "-" : sample.value.toFixed(2);
64
- rows +=
65
- '<div class="row"><span><i class="swatch" style="background:' +
66
- series.colour +
67
- '"></i>' +
68
- series.label +
69
- "</span><b>" +
70
- reading +
71
- "</b></div>";
72
- }
223
+ rows += '<div class="row"><span><i class="swatch" style="background:' + series.colour + '"></i>' + escapeText(series.label) + "</span><b>" + (sample ? sample.value.toFixed(2) : "-") + "</b></div>";
224
+ if (marker) {
225
+ if (sample) {
226
+ marker.setAttribute("cx", xForTime(sample.time));
227
+ marker.setAttribute("cy", yFor(sample.value));
228
+ marker.removeAttribute("hidden");
229
+ } else marker.setAttribute("hidden", "");
230
+ }
231
+ });
73
232
  tooltip.innerHTML = rows;
74
233
  tooltip.hidden = false;
75
-
76
- // Flip the tooltip to the other side near the right edge so it stays on screen.
77
- var offset = ratio > 0.6 ? 12 : box.width * ratio + 18;
78
- if (ratio > 0.6) {
79
- tooltip.style.left = "auto";
80
- tooltip.style.right = offset + "px";
81
- } else {
82
- tooltip.style.right = "auto";
83
- tooltip.style.left = offset + "px";
84
- }
85
-
86
- if (crosshair) {
87
- var viewBox = svg.viewBox.baseVal;
88
- crosshair.setAttribute("x1", String(ratio * viewBox.width));
89
- crosshair.setAttribute("x2", String(ratio * viewBox.width));
90
- crosshair.hidden = false;
91
- }
234
+ tooltip.style.left = ratio > 0.62 ? "auto" : box.width * ratio + 18 + "px";
235
+ tooltip.style.right = ratio > 0.62 ? 14 + "px" : "auto";
236
+ var x = xForTime(timeMs);
237
+ crosshair.setAttribute("x1", x);
238
+ crosshair.setAttribute("x2", x);
239
+ crosshair.removeAttribute("hidden");
240
+ sizeMarkers();
92
241
  }
93
242
 
94
243
  function hide() {
95
244
  tooltip.hidden = true;
96
- if (crosshair) crosshair.hidden = true;
245
+ crosshair.setAttribute("hidden", "");
246
+ chart.querySelectorAll("[data-series-marker]").forEach(function (marker) { marker.setAttribute("hidden", ""); });
97
247
  }
98
248
 
99
- chart.addEventListener("mousemove", function (event) {
100
- show(event.clientX);
249
+ document.querySelectorAll("[data-series-toggle]").forEach(function (button) {
250
+ button.addEventListener("click", function () {
251
+ var index = Number(button.getAttribute("data-series-toggle"));
252
+ if (active[index] && active.filter(Boolean).length === 1) return;
253
+ active[index] = !active[index];
254
+ button.setAttribute("aria-pressed", active[index] ? "true" : "false");
255
+ button.classList.toggle("is-muted", !active[index]);
256
+ var path = chart.querySelector('[data-series-path="' + index + '"]');
257
+ if (path) path.classList.toggle("is-hidden", !active[index]);
258
+ setRange(currentStart, currentEnd, document.querySelector('[data-compare-range][aria-pressed="true"]'), true);
259
+ });
260
+ });
261
+
262
+ document.querySelectorAll("[data-compare-range]").forEach(function (button) {
263
+ button.addEventListener("click", function () {
264
+ var value = button.getAttribute("data-compare-range");
265
+ var start = value === "all" ? fullStart : Math.max(fullStart, fullEnd - Number(value) * 86400000);
266
+ setRange(start, fullEnd, button, false);
267
+ });
268
+ });
269
+
270
+ var pendingWheelDelta = 0;
271
+ var pendingWheelX = 0;
272
+ var wheelFrame = 0;
273
+ chart.addEventListener("wheel", function (event) {
274
+ if (!event.deltaY) return;
275
+ event.preventDefault();
276
+ var delta = event.deltaY;
277
+ if (event.deltaMode === 1) delta *= 16;
278
+ else if (event.deltaMode === 2) delta *= Math.max(1, svg.getBoundingClientRect().height);
279
+ pendingWheelDelta += delta;
280
+ pendingWheelX = event.clientX;
281
+ if (wheelFrame) return;
282
+ wheelFrame = requestAnimationFrame(function () {
283
+ var bounded = Math.max(-80, Math.min(80, pendingWheelDelta));
284
+ pendingWheelDelta = 0;
285
+ wheelFrame = 0;
286
+ var factor = Math.exp(bounded * 0.0015);
287
+ var duration = currentEnd - currentStart;
288
+ var targetDuration = Math.max(minimumGap * 8, Math.min(fullDuration, duration * factor));
289
+ var bounds = svg.getBoundingClientRect();
290
+ var ratio = Math.max(0, Math.min(1, (pendingWheelX - bounds.left) / Math.max(1, bounds.width)));
291
+ var anchor = currentStart + ratio * duration;
292
+ setRange(anchor - ratio * targetDuration, anchor + (1 - ratio) * targetDuration, null, false);
293
+ });
294
+ }, { passive: false });
295
+
296
+ var drag = null;
297
+ chart.addEventListener("pointerdown", function (event) {
298
+ if (event.pointerType !== "mouse" || event.button !== 0) return;
299
+ drag = { id: event.pointerId, x: event.clientX, start: currentStart, end: currentEnd };
300
+ chart.classList.add("is-dragging");
301
+ chart.setPointerCapture(event.pointerId);
302
+ hide();
303
+ event.preventDefault();
304
+ });
305
+ chart.addEventListener("pointermove", function (event) {
306
+ if (!drag || event.pointerId !== drag.id) return;
307
+ var bounds = svg.getBoundingClientRect();
308
+ var duration = drag.end - drag.start;
309
+ var shift = ((drag.x - event.clientX) / Math.max(1, bounds.width)) * duration;
310
+ setRange(drag.start + shift, drag.end + shift, null, true);
311
+ });
312
+ function endDrag(event) {
313
+ if (!drag || (typeof event.pointerId === "number" && event.pointerId !== drag.id)) return;
314
+ var id = drag.id;
315
+ drag = null;
316
+ chart.classList.remove("is-dragging");
317
+ if (chart.hasPointerCapture(id)) chart.releasePointerCapture(id);
318
+ }
319
+ chart.addEventListener("pointerup", endDrag);
320
+ chart.addEventListener("pointercancel", endDrag);
321
+ chart.addEventListener("dblclick", function () {
322
+ setRange(fullStart, fullEnd, document.querySelector('[data-compare-range="all"]'), false);
101
323
  });
102
- chart.addEventListener("mouseleave", hide);
324
+
325
+ chart.addEventListener("mousemove", function (event) { if (!drag) show(event.clientX); });
326
+ chart.addEventListener("mouseleave", function () { if (!drag) hide(); });
327
+ chart.addEventListener("touchstart", function (event) {
328
+ if (event.touches && event.touches.length) show(event.touches[0].clientX);
329
+ }, { passive: true });
103
330
  chart.addEventListener("touchmove", function (event) {
104
331
  if (event.touches && event.touches.length) show(event.touches[0].clientX);
105
- });
332
+ }, { passive: true });
106
333
  chart.addEventListener("touchend", hide);
334
+
335
+ var cursorTime = fullStart;
336
+ chart.setAttribute("tabindex", "0");
337
+ chart.addEventListener("keydown", function (event) {
338
+ var step = event.shiftKey ? Math.max(minimumGap, (currentEnd - currentStart) / 20) : minimumGap;
339
+ if (event.key === "ArrowRight") cursorTime = Math.min(currentEnd, Math.max(currentStart, cursorTime) + step);
340
+ else if (event.key === "ArrowLeft") cursorTime = Math.max(currentStart, Math.min(currentEnd, cursorTime) - step);
341
+ else if (event.key === "Home") cursorTime = currentStart;
342
+ else if (event.key === "End") cursorTime = currentEnd;
343
+ else if (event.key === "Escape") { hide(); return; }
344
+ else return;
345
+ event.preventDefault();
346
+ var bounds = svg.getBoundingClientRect();
347
+ show(bounds.left + ((cursorTime - currentStart) / Math.max(1, currentEnd - currentStart)) * bounds.width);
348
+ });
349
+ chart.addEventListener("blur", hide);
350
+
351
+ addEventListener("resize", sizeMarkers, { passive: true });
352
+ setRange(fullStart, fullEnd, document.querySelector('[data-compare-range="all"]'), true);
107
353
  })();
108
354
  `;
109
355
  //# sourceMappingURL=compare-script.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"compare-script.js","sourceRoot":"","sources":["../../src/report/compare-script.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgGvC,CAAC"}
1
+ {"version":3,"file":"compare-script.js","sourceRoot":"","sources":["../../src/report/compare-script.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgWvC,CAAC"}
@@ -1,6 +1,12 @@
1
1
  import type { RuntimeClient } from "../runtime/client.js";
2
+ import type { EquityPoint } from "../strategy/types.js";
2
3
  import type { ReportInput } from "./html.js";
3
4
  import type { ComparisonInput } from "./compare.js";
5
+ import type { RunSummary } from "../strategy/types.js";
6
+ interface Page<T> {
7
+ rows: T[];
8
+ total: number;
9
+ }
4
10
  /**
5
11
  * Gathers everything a report needs, following pagination.
6
12
  *
@@ -18,3 +24,29 @@ export declare function collectReport(client: RuntimeClient, runId: string): Pro
18
24
  * it shows. Fetched concurrently, since the reads are independent.
19
25
  */
20
26
  export declare function collectComparison(client: RuntimeClient, runIds: string[]): Promise<ComparisonInput>;
27
+ /**
28
+ * The same gathering, for a caller already inside the runtime.
29
+ *
30
+ * A tool runs in the process that owns the store, so it reads the service directly
31
+ * rather than issuing HTTP calls back to itself. The pagination is duplicated
32
+ * deliberately: the page cap is the store's, not the transport's, so a one-shot read
33
+ * would return a prefix here exactly as it would over the wire.
34
+ */
35
+ export declare function collectReportLocal(strategy: LocalReportSource, runId: string): ReportInput;
36
+ export declare function collectComparisonLocal(strategy: LocalReportSource, runIds: string[]): ComparisonInput;
37
+ /** What the local collectors need, so a test can supply it without a full service. */
38
+ export interface LocalReportSource {
39
+ getRun(id: string): {
40
+ data: RunSummary;
41
+ };
42
+ equity(id: string, offset?: number, limit?: number): {
43
+ data: Page<EquityPoint>;
44
+ };
45
+ trades(id: string, offset?: number, limit?: number): {
46
+ data: Page<unknown>;
47
+ };
48
+ actions(id: string, offset?: number, limit?: number): {
49
+ data: Page<unknown>;
50
+ };
51
+ }
52
+ export {};
@@ -53,4 +53,46 @@ async function collect(client, tool, runId, ceiling) {
53
53
  }
54
54
  return rows;
55
55
  }
56
+ /**
57
+ * The same gathering, for a caller already inside the runtime.
58
+ *
59
+ * A tool runs in the process that owns the store, so it reads the service directly
60
+ * rather than issuing HTTP calls back to itself. The pagination is duplicated
61
+ * deliberately: the page cap is the store's, not the transport's, so a one-shot read
62
+ * would return a prefix here exactly as it would over the wire.
63
+ */
64
+ export function collectReportLocal(strategy, runId) {
65
+ const run = strategy.getRun(runId).data;
66
+ return {
67
+ run,
68
+ equity: pageLocal((offset, limit) => strategy.equity(runId, offset, limit).data, MAX_POINTS),
69
+ trades: pageLocal((offset, limit) => strategy.trades(runId, offset, limit).data, 50_000),
70
+ actions: pageLocal((offset, limit) => strategy.actions(runId, offset, limit).data, 50_000)
71
+ };
72
+ }
73
+ export function collectComparisonLocal(strategy, runIds) {
74
+ return {
75
+ runs: runIds.map((runId) => {
76
+ const run = strategy.getRun(runId).data;
77
+ // A run that never completed has no curve; it is still listed so the comparison
78
+ // can say why it is missing rather than dropping it silently.
79
+ const equity = run.status === "completed"
80
+ ? pageLocal((offset, limit) => strategy.equity(runId, offset, limit).data, MAX_POINTS)
81
+ : [];
82
+ return { run, equity };
83
+ })
84
+ };
85
+ }
86
+ function pageLocal(read, ceiling) {
87
+ const rows = [];
88
+ let offset = 0;
89
+ for (;;) {
90
+ const page = read(offset, MAX_PAGE_ROWS);
91
+ rows.push(...page.rows);
92
+ offset += page.rows.length;
93
+ if (page.rows.length === 0 || offset >= page.total || rows.length >= ceiling)
94
+ break;
95
+ }
96
+ return rows;
97
+ }
56
98
  //# sourceMappingURL=fetch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/report/fetch.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAGzD;;;;GAIG;AACH,MAAM,UAAU,GAAG,OAAO,CAAC;AAO3B;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAqB,EAAE,KAAa;IACtE,MAAM,GAAG,GAAI,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,KAAK,EAAE,CAAC,CAA0B,CAAC,IAAI,CAAC;IAC9F,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAClD,OAAO,CAAc,MAAM,EAAE,yBAAyB,EAAE,KAAK,EAAE,UAAU,CAAC;QAC1E,OAAO,CAA0B,MAAM,EAAE,yBAAyB,EAAE,KAAK,EAAE,MAAM,CAAC;QAClF,OAAO,CAA0B,MAAM,EAAE,0BAA0B,EAAE,KAAK,EAAE,MAAM,CAAC;KACpF,CAAC,CAAC;IACH,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAqB,EACrB,MAAgB;IAEhB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,CAC5B,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACzB,MAAM,GAAG,GAAI,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,KAAK,EAAE,CAAC,CAA0B,CAAC,IAAI,CAAC;QAC9F,6EAA6E;QAC7E,wCAAwC;QACxC,MAAM,MAAM,GACV,GAAG,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,OAAO,CAAc,MAAM,EAAE,yBAAyB,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACrH,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IACzB,CAAC,CAAC,CACH,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,OAAO,CACpB,MAAqB,EACrB,IAAY,EACZ,KAAa,EACb,OAAe;IAEf,MAAM,IAAI,GAAQ,EAAE,CAAC;IACrB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,SAAS,CAAC;QACR,MAAM,IAAI,GAAI,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAuB,CAAC,IAAI,CAAC;QAC5G,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAC3B,4EAA4E;QAC5E,sCAAsC;QACtC,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO;YAAE,MAAM;IACtF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/report/fetch.ts"],"names":[],"mappings":"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"}
@@ -51,4 +51,4 @@ export declare function escapeAttribute(value: string): string;
51
51
  * second copy: the two documents should not drift apart visually, and a duplicated
52
52
  * sheet is exactly how that happens.
53
53
  */
54
- export declare const COMPARE_STYLES = "\n:root{--bg:#0f1116;--panel:#171a21;--line:#242833;--text:#e6e8ee;--dim:#8b90a0;--up:#3fb950;--down:#f2777a;--accent:#4f9cf9}\n@media(prefers-color-scheme:light){:root{--bg:#f7f8fa;--panel:#fff;--line:#e3e6ec;--text:#1a1d24;--dim:#6b7280;--up:#1a7f37;--down:#c9372c}}\n*{box-sizing:border-box}\nbody{margin:0;background:var(--bg);color:var(--text);font:14px/1.55 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",\"PingFang SC\",\"Microsoft YaHei\",sans-serif}\nmain{max-width:1080px;margin:0 auto;padding:32px 24px 64px}\nheader{display:flex;justify-content:space-between;align-items:flex-end;gap:24px;flex-wrap:wrap;padding-bottom:20px;border-bottom:1px solid var(--line)}\nh1{margin:0;font-size:26px;font-weight:600;letter-spacing:-.01em}\n.sub{margin:4px 0 0;color:var(--dim);font-size:13px}\n.headline{text-align:right}\n.headline .value{display:block;font-size:34px;font-weight:650;letter-spacing:-.02em;font-variant-numeric:tabular-nums}\n.headline .label{color:var(--dim);font-size:12px;text-transform:uppercase;letter-spacing:.08em}\n.up{color:var(--up)}.down{color:var(--down)}.flat{color:var(--dim)}\n.facts{display:flex;gap:28px;flex-wrap:wrap;margin:20px 0 8px;color:var(--dim);font-size:13px}\n.facts span{display:block;text-transform:uppercase;letter-spacing:.06em;font-size:11px}\n.facts strong{color:var(--text);font-weight:550;font-variant-numeric:tabular-nums}\n.warnings{margin:20px 0 0;padding:12px 16px;border-left:3px solid var(--down);background:color-mix(in srgb,var(--down) 8%,transparent);border-radius:0 6px 6px 0}\n.warnings p{margin:4px 0;font-size:13px}\nsection{margin-top:32px}\nh2{margin:0 0 14px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.1em;color:var(--dim)}\nh2 .count{color:var(--dim);font-weight:400}\ndl{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:1px;margin:0;background:var(--line);border:1px solid var(--line);border-radius:8px;overflow:hidden}\ndl>div{background:var(--panel);padding:12px 14px}\ndt{color:var(--dim);font-size:11px;text-transform:uppercase;letter-spacing:.06em}\ndd{margin:3px 0 0;font-size:16px;font-weight:550;font-variant-numeric:tabular-nums}\n.chart{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:10px 12px;display:flex;gap:10px;align-items:stretch}\n.chart.drawdown{margin-top:8px;border-radius:8px}\n.chart svg{flex:1;height:240px;width:100%;display:block}\n.chart.drawdown svg{height:80px}\n.axis{display:flex;flex-direction:column;justify-content:space-between;color:var(--dim);font-size:11px;font-variant-numeric:tabular-nums;min-width:62px;text-align:right}\n.range{margin:8px 0 0;color:var(--dim);font-size:12px}\n.chart{position:relative}\n.chart svg{cursor:crosshair}\n.chart:focus-visible{outline:2px solid var(--accent);outline-offset:2px}\n.baseline{stroke:var(--dim);stroke-width:1;stroke-dasharray:3 4;opacity:.5;vector-effect:non-scaling-stroke}\n.crosshair{stroke:var(--dim);stroke-width:1;vector-effect:non-scaling-stroke;pointer-events:none}\n.marker{fill:var(--accent);stroke:var(--panel);stroke-width:1.5;pointer-events:none}\n.tooltip{position:absolute;top:10px;pointer-events:none;background:var(--panel);border:1px solid var(--line);border-radius:6px;padding:8px 10px;font-size:12px;min-width:180px;box-shadow:0 4px 16px rgba(0,0,0,.28);z-index:2}\n.tooltip .row{display:flex;justify-content:space-between;gap:14px;padding:1px 0}\n.tooltip .row span{color:var(--dim)}\n.tooltip .row b{font-weight:550;font-variant-numeric:tabular-nums}\n.scroll{overflow-x:auto;border:1px solid var(--line);border-radius:8px}\ntable{width:100%;border-collapse:collapse;font-size:13px}\nth,td{padding:8px 12px;text-align:left;border-bottom:1px solid var(--line);white-space:nowrap}\nth{background:var(--panel);color:var(--dim);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;position:sticky;top:0}\ntbody tr:last-child td{border-bottom:none}\ntd.n{text-align:right;font-variant-numeric:tabular-nums}\ntr.up td:nth-child(5),tr.up td:nth-child(6){color:var(--up)}\ntr.down td:nth-child(5),tr.down td:nth-child(6){color:var(--down)}\n.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}\ntd.dim{color:var(--dim)}\n.scroll.tall{max-height:520px;overflow-y:auto}\n.histogram{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:12px 14px}\n.bin{display:flex;align-items:center;gap:10px;padding:3px 0}\n.bin .range{min-width:132px;color:var(--dim);text-align:right;flex-shrink:0}\n.bin .track{flex:1;height:12px;background:color-mix(in srgb,var(--line) 60%,transparent);border-radius:3px;overflow:hidden}\n.bin .fill{display:block;height:100%;border-radius:3px}\n.bin .fill.up{background:var(--up)}\n.bin .fill.down{background:var(--down)}\n.bin>.count{min-width:44px;text-align:right;color:var(--dim);font-size:12px;font-variant-numeric:tabular-nums;flex-shrink:0}\n.empty{color:var(--dim);font-size:13px}\nfooter{margin-top:40px;padding-top:16px;border-top:1px solid var(--line);color:var(--dim);font-size:12px}\nfooter p{margin:4px 0}\n\n.legend{display:flex;gap:20px;flex-wrap:wrap;margin:20px 0 0;font-size:13px}\n.legend div{display:flex;align-items:center;gap:7px}\n.legend i,th i{display:inline-block;width:10px;height:10px;border-radius:2px;flex-shrink:0}\nth i{margin-right:6px;vertical-align:middle}\ntable.matrix th[scope=row]{background:var(--panel);color:var(--dim);text-transform:none;letter-spacing:0;font-size:12px;font-weight:500;text-align:left;position:sticky;left:0}\ntable.matrix thead th{text-align:right}\ntable.matrix thead th span.dim{display:block;font-weight:400;text-transform:none;letter-spacing:0;font-size:10px}\ntd.best{color:var(--up);font-weight:600}\n.chart .tooltip .swatch{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:6px}\n.chart .tooltip .row.head{border-bottom:1px solid var(--line);margin-bottom:3px;padding-bottom:3px}\n";
54
+ export declare const COMPARE_STYLES: string;