@stocksharp/trading-controls 1.2.0 → 1.4.0
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/README.md +204 -17
- package/dist/esm/black-scholes.js +147 -0
- package/dist/esm/black-scholes.js.map +1 -0
- package/dist/esm/chart-engine.js +15 -0
- package/dist/esm/chart-engine.js.map +1 -0
- package/dist/esm/control-types.js +8 -0
- package/dist/esm/control-types.js.map +1 -1
- package/dist/esm/equity-widget.js +261 -0
- package/dist/esm/equity-widget.js.map +1 -0
- package/dist/esm/heatmap-grid.js +271 -0
- package/dist/esm/heatmap-grid.js.map +1 -0
- package/dist/esm/index.js +37 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/log-monitor-widget.js +265 -0
- package/dist/esm/log-monitor-widget.js.map +1 -0
- package/dist/esm/log-tree.js +96 -0
- package/dist/esm/log-tree.js.map +1 -0
- package/dist/esm/optimization-heatmap-widget.js +303 -0
- package/dist/esm/optimization-heatmap-widget.js.map +1 -0
- package/dist/esm/option-desk-widget.js +322 -0
- package/dist/esm/option-desk-widget.js.map +1 -0
- package/dist/esm/option-smile-widget.js +252 -0
- package/dist/esm/option-smile-widget.js.map +1 -0
- package/dist/esm/pnl-curve.js +133 -0
- package/dist/esm/pnl-curve.js.map +1 -0
- package/dist/esm/statistics-widget.js +194 -0
- package/dist/esm/statistics-widget.js.map +1 -0
- package/dist/esm/strategies-widget.js +348 -0
- package/dist/esm/strategies-widget.js.map +1 -0
- package/dist/esm/surface-grid.js +282 -0
- package/dist/esm/surface-grid.js.map +1 -0
- package/dist/esm/surface-widget.js +379 -0
- package/dist/esm/surface-widget.js.map +1 -0
- package/dist/sstradingcontrols.js +2982 -295
- package/dist/sstradingcontrols.js.map +4 -4
- package/dist/types/black-scholes.d.ts +28 -0
- package/dist/types/black-scholes.d.ts.map +1 -0
- package/dist/types/chart-engine.d.ts +3 -0
- package/dist/types/chart-engine.d.ts.map +1 -0
- package/dist/types/control-types.d.ts +8 -0
- package/dist/types/control-types.d.ts.map +1 -1
- package/dist/types/equity-widget.d.ts +34 -0
- package/dist/types/equity-widget.d.ts.map +1 -0
- package/dist/types/heatmap-grid.d.ts +88 -0
- package/dist/types/heatmap-grid.d.ts.map +1 -0
- package/dist/types/index.d.ts +31 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/log-monitor-widget.d.ts +42 -0
- package/dist/types/log-monitor-widget.d.ts.map +1 -0
- package/dist/types/log-tree.d.ts +34 -0
- package/dist/types/log-tree.d.ts.map +1 -0
- package/dist/types/optimization-heatmap-widget.d.ts +45 -0
- package/dist/types/optimization-heatmap-widget.d.ts.map +1 -0
- package/dist/types/option-desk-widget.d.ts +68 -0
- package/dist/types/option-desk-widget.d.ts.map +1 -0
- package/dist/types/option-smile-widget.d.ts +39 -0
- package/dist/types/option-smile-widget.d.ts.map +1 -0
- package/dist/types/pnl-curve.d.ts +46 -0
- package/dist/types/pnl-curve.d.ts.map +1 -0
- package/dist/types/statistics-widget.d.ts +29 -0
- package/dist/types/statistics-widget.d.ts.map +1 -0
- package/dist/types/strategies-widget.d.ts +63 -0
- package/dist/types/strategies-widget.d.ts.map +1 -0
- package/dist/types/surface-grid.d.ts +64 -0
- package/dist/types/surface-grid.d.ts.map +1 -0
- package/dist/types/surface-widget.d.ts +64 -0
- package/dist/types/surface-widget.d.ts.map +1 -0
- package/dist/types/trading-data.d.ts +9 -0
- package/dist/types/trading-data.d.ts.map +1 -1
- package/package.json +54 -3
- package/screenshots/equity.png +0 -0
- package/screenshots/log-monitor.png +0 -0
- package/screenshots/optimization-surface.png +0 -0
- package/screenshots/optimization.png +0 -0
- package/screenshots/option-desk.png +0 -0
- package/screenshots/option-smile.png +0 -0
- package/screenshots/panels.jpg +0 -0
- package/screenshots/statistics.png +0 -0
- package/screenshots/strategies.png +0 -0
- package/src/black-scholes.ts +199 -0
- package/src/chart-engine.global.ts +64 -0
- package/src/chart-engine.ts +29 -0
- package/src/control-types.ts +8 -0
- package/src/equity-widget.ts +289 -0
- package/src/heatmap-grid.ts +405 -0
- package/src/index.ts +87 -0
- package/src/log-monitor-widget.ts +312 -0
- package/src/log-tree.ts +131 -0
- package/src/optimization-heatmap-widget.ts +347 -0
- package/src/option-desk-widget.ts +422 -0
- package/src/option-smile-widget.ts +281 -0
- package/src/pnl-curve.ts +216 -0
- package/src/statistics-widget.ts +226 -0
- package/src/strategies-widget.ts +435 -0
- package/src/surface-grid.ts +410 -0
- package/src/surface-widget.ts +457 -0
- package/src/trading-data.ts +22 -0
- package/styles/trading-controls.css +637 -0
- package/translation-keys.json +83 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// Equity curve — multi-instance.
|
|
2
|
+
//
|
|
3
|
+
// A run's cumulative P&L at the size of a chart, drawn by @stocksharp/chart. It used to be a
|
|
4
|
+
// canvas of its own with a crosshair, a legend and a zoom written here; all three are the
|
|
5
|
+
// engine's, better than the versions this file had, and an equity curve over time is exactly
|
|
6
|
+
// what a time-series engine is for.
|
|
7
|
+
//
|
|
8
|
+
// What is left here is the part that is this package's: turning a run into a series, wording the
|
|
9
|
+
// readout through the host, and taking the engine's colours from `presentation.canvasPalette()`
|
|
10
|
+
// so a curve in a panel matches the sparkline in a table cell beside it.
|
|
11
|
+
//
|
|
12
|
+
// The sparkline is NOT this. A cell forty pixels tall gets `pnl-curve.ts` and a canvas, because
|
|
13
|
+
// a chart engine per table row is absurd - the two draw the same run at sizes that want
|
|
14
|
+
// different machinery.
|
|
15
|
+
import { formatPnl } from './formatters.js';
|
|
16
|
+
import { makeElement, makeIconButton, makePanelId, makePanelRoot } from './dom.js';
|
|
17
|
+
import { ControlTypes } from './control-types.js';
|
|
18
|
+
import { assertHost } from './trading-host.js';
|
|
19
|
+
import { AreaSeries, CrosshairMode, createChart, } from './chart-engine.js';
|
|
20
|
+
// How solid the area under the curve is drawn. Not a colour - the two colours are the host's -
|
|
21
|
+
// but how far they are faded, which is this chart's own business.
|
|
22
|
+
const FILL_TOP_ALPHA = 0.28;
|
|
23
|
+
const FILL_BOTTOM_ALPHA = 0.02;
|
|
24
|
+
const LINE_WIDTH = 2;
|
|
25
|
+
/// One sample as the engine takes it. Time is unix milliseconds on the way in and seconds on the
|
|
26
|
+
/// way through: the engine counts in seconds, and a host that hands over milliseconds - which is
|
|
27
|
+
/// what `Date.now()` and `presentation.timeText` both deal in - should not have to know that.
|
|
28
|
+
function toSeries(points) {
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
const out = [];
|
|
31
|
+
for (const point of [...points].sort((a, b) => a.time - b.time)) {
|
|
32
|
+
if (!Number.isFinite(point.time) || !Number.isFinite(point.value))
|
|
33
|
+
continue;
|
|
34
|
+
// The engine holds one value per moment. Two fills in the same second are two samples of
|
|
35
|
+
// one instant, and the later one is where the run actually stood.
|
|
36
|
+
const second = Math.floor(point.time / 1000);
|
|
37
|
+
if (seen.has(second))
|
|
38
|
+
out[out.length - 1] = { time: second, value: point.value };
|
|
39
|
+
else
|
|
40
|
+
out.push({ time: second, value: point.value });
|
|
41
|
+
seen.add(second);
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
export class EquityWidget {
|
|
46
|
+
static create(hostEl, state, deps) {
|
|
47
|
+
const host = assertHost(deps?.host, 'EquityWidget');
|
|
48
|
+
const root = EquityWidget._buildRoot(host);
|
|
49
|
+
root.id = makePanelId(EquityWidget.TYPE);
|
|
50
|
+
hostEl.appendChild(root);
|
|
51
|
+
return new EquityWidget(root, state || {}, deps);
|
|
52
|
+
}
|
|
53
|
+
static _buildRoot(host) {
|
|
54
|
+
const title = host.t('Equity');
|
|
55
|
+
return makePanelRoot('equity-panel', title, [
|
|
56
|
+
makeElement('div', 'panel-header', {}, [
|
|
57
|
+
makeElement('span', '', {}, [title]),
|
|
58
|
+
makeElement('span', 'equity-last', {}, []),
|
|
59
|
+
makeIconButton('bt-icon-btn equity-reset-btn', host.t('ResetView'), 'bi-arrow-clockwise', { type: 'button' }),
|
|
60
|
+
makeIconButton('bt-icon-btn bt-icon-cancel panel-close-btn', host.t('ClosePanel'), 'bi-x', { type: 'button' }),
|
|
61
|
+
]),
|
|
62
|
+
makeElement('div', 'panel-body', {}, [
|
|
63
|
+
makeElement('div', 'equity-chart', { role: 'img', 'aria-label': host.t('PnLChart') }, [
|
|
64
|
+
// Over the curve rather than in the panel header, for the same reason the
|
|
65
|
+
// smile's is: a reading about a moment belongs beside that moment, and a host
|
|
66
|
+
// that lifts panel headers into a tab strip would put it in a tab title.
|
|
67
|
+
makeElement('div', 'equity-hover', {}, []),
|
|
68
|
+
makeElement('div', 'equity-empty', {}, [host.t('NoEquity')]),
|
|
69
|
+
]),
|
|
70
|
+
]),
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
constructor(rootEl, _state, deps) {
|
|
74
|
+
this._host = assertHost(deps?.host, 'EquityWidget');
|
|
75
|
+
this.rootEl = rootEl;
|
|
76
|
+
this._closeBtn = this.rootEl.querySelector('.panel-close-btn');
|
|
77
|
+
this._resetBtn = this.rootEl.querySelector('.equity-reset-btn');
|
|
78
|
+
this._lastEl = this.rootEl.querySelector('.equity-last');
|
|
79
|
+
this._hoverEl = this.rootEl.querySelector('.equity-hover');
|
|
80
|
+
this._emptyEl = this.rootEl.querySelector('.equity-empty');
|
|
81
|
+
this._chartEl = this.rootEl.querySelector('.equity-chart');
|
|
82
|
+
this._chart = null;
|
|
83
|
+
this._series = null;
|
|
84
|
+
this._points = [];
|
|
85
|
+
this._resizeObserver = null;
|
|
86
|
+
this._closeBtn?.addEventListener('click', (e) => { e.preventDefault(); this._host.close(); });
|
|
87
|
+
this._resetBtn?.addEventListener('click', (e) => { e.preventDefault(); this.resetZoom(); });
|
|
88
|
+
// The panel has no size until it is laid out, and a docked one is resized afterwards.
|
|
89
|
+
// The engine sizes its canvases only through resize(), so it is told rather than left to
|
|
90
|
+
// notice.
|
|
91
|
+
if (this._chartEl !== null && typeof ResizeObserver !== 'undefined') {
|
|
92
|
+
this._resizeObserver = new ResizeObserver(() => this._fit());
|
|
93
|
+
this._resizeObserver.observe(this._chartEl);
|
|
94
|
+
}
|
|
95
|
+
this._render();
|
|
96
|
+
this._host.register(this);
|
|
97
|
+
}
|
|
98
|
+
dispose() {
|
|
99
|
+
try {
|
|
100
|
+
this._resizeObserver?.disconnect();
|
|
101
|
+
}
|
|
102
|
+
catch { /* already torn down */ }
|
|
103
|
+
this._resizeObserver = null;
|
|
104
|
+
try {
|
|
105
|
+
this._chart?.remove();
|
|
106
|
+
}
|
|
107
|
+
catch { /* already gone */ }
|
|
108
|
+
this._chart = null;
|
|
109
|
+
this._series = null;
|
|
110
|
+
this._host.unregister(this);
|
|
111
|
+
try {
|
|
112
|
+
this.rootEl.remove();
|
|
113
|
+
}
|
|
114
|
+
catch { /* already detached */ }
|
|
115
|
+
}
|
|
116
|
+
/// Show this run. The whole curve at once: it is a cumulative figure, so a set of points that
|
|
117
|
+
/// no longer contains an earlier sample is a different run rather than a longer one.
|
|
118
|
+
///
|
|
119
|
+
/// `time` is unix milliseconds.
|
|
120
|
+
update(points) {
|
|
121
|
+
this._points = points || [];
|
|
122
|
+
this._render();
|
|
123
|
+
}
|
|
124
|
+
/// Show the whole run again, after a zoom.
|
|
125
|
+
resetZoom() {
|
|
126
|
+
try {
|
|
127
|
+
this._chart?.timeScale().fitContent();
|
|
128
|
+
}
|
|
129
|
+
catch { /* no chart yet */ }
|
|
130
|
+
}
|
|
131
|
+
/// The engine, for a host that wants to add to this chart - a benchmark line, a marker at a
|
|
132
|
+
/// drawdown. Null until there is something to draw.
|
|
133
|
+
chart() {
|
|
134
|
+
return this._chart;
|
|
135
|
+
}
|
|
136
|
+
// Built on first use rather than in the constructor: the engine measures its container, and
|
|
137
|
+
// a panel that has not been laid out yet has none.
|
|
138
|
+
_ensureChart() {
|
|
139
|
+
if (this._chart !== null)
|
|
140
|
+
return true;
|
|
141
|
+
if (this._chartEl === null)
|
|
142
|
+
return false;
|
|
143
|
+
const palette = this._host.presentation.canvasPalette();
|
|
144
|
+
this._chart = createChart(this._chartEl, {
|
|
145
|
+
layout: {
|
|
146
|
+
background: { type: 'solid', color: 'transparent' },
|
|
147
|
+
textColor: palette.grid,
|
|
148
|
+
fontFamily: palette.font,
|
|
149
|
+
fontSize: 11,
|
|
150
|
+
attributionLogo: false,
|
|
151
|
+
},
|
|
152
|
+
grid: { vertLines: { color: palette.grid }, horzLines: { color: palette.grid } },
|
|
153
|
+
crosshair: { mode: CrosshairMode.Normal },
|
|
154
|
+
rightPriceScale: { borderColor: palette.grid },
|
|
155
|
+
timeScale: {
|
|
156
|
+
borderColor: palette.grid,
|
|
157
|
+
timeVisible: true,
|
|
158
|
+
secondsVisible: true,
|
|
159
|
+
// The engine dates an axis in UTC unless told otherwise, and everything else on
|
|
160
|
+
// the page - the readout beside this one included - reads in the browser's zone.
|
|
161
|
+
// Two labels for one instant, three hours apart, is worse than either.
|
|
162
|
+
timeZone: browserTimeZone(),
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
this._series = this._chart.addSeries(AreaSeries, {
|
|
166
|
+
lineWidth: LINE_WIDTH,
|
|
167
|
+
lineColor: palette.up,
|
|
168
|
+
topColor: withAlpha(palette.up, FILL_TOP_ALPHA),
|
|
169
|
+
bottomColor: withAlpha(palette.up, FILL_BOTTOM_ALPHA),
|
|
170
|
+
});
|
|
171
|
+
// The readout. The engine says which point the pointer is over; wording it is the host's,
|
|
172
|
+
// which is why this is here rather than in the engine's own crosshair label.
|
|
173
|
+
this._chart.subscribeCrosshairMove((param) => this._renderHover(param));
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
_render() {
|
|
177
|
+
const series = toSeries(this._points);
|
|
178
|
+
const empty = series.length < 2;
|
|
179
|
+
if (this._emptyEl !== null)
|
|
180
|
+
this._emptyEl.hidden = !empty;
|
|
181
|
+
this._renderLast(empty ? null : series[series.length - 1].value);
|
|
182
|
+
if (empty)
|
|
183
|
+
return;
|
|
184
|
+
if (!this._ensureChart() || this._series === null)
|
|
185
|
+
return;
|
|
186
|
+
// Coloured by where the run ended, the way the sparkline is: a run that peaked and gave
|
|
187
|
+
// it all back is a loss, and the colour says so.
|
|
188
|
+
const palette = this._host.presentation.canvasPalette();
|
|
189
|
+
const last = series[series.length - 1].value;
|
|
190
|
+
const colour = last >= 0 ? palette.up : palette.down;
|
|
191
|
+
this._series.applyOptions({
|
|
192
|
+
lineColor: colour,
|
|
193
|
+
topColor: withAlpha(colour, FILL_TOP_ALPHA),
|
|
194
|
+
bottomColor: withAlpha(colour, FILL_BOTTOM_ALPHA),
|
|
195
|
+
});
|
|
196
|
+
this._series.setData(series);
|
|
197
|
+
this._fit();
|
|
198
|
+
}
|
|
199
|
+
_fit() {
|
|
200
|
+
if (this._chart === null || this._chartEl === null)
|
|
201
|
+
return;
|
|
202
|
+
const rect = this._chartEl.getBoundingClientRect();
|
|
203
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
204
|
+
return;
|
|
205
|
+
try {
|
|
206
|
+
this._chart.resize(rect.width, rect.height);
|
|
207
|
+
}
|
|
208
|
+
catch { /* the engine is between frames */ }
|
|
209
|
+
}
|
|
210
|
+
// Where the run stands, in the header.
|
|
211
|
+
_renderLast(value) {
|
|
212
|
+
const el = this._lastEl;
|
|
213
|
+
if (el === null)
|
|
214
|
+
return;
|
|
215
|
+
el.textContent = value === null ? '' : formatPnl(value);
|
|
216
|
+
el.className = value === null
|
|
217
|
+
? 'equity-last'
|
|
218
|
+
: `equity-last ${this._host.presentation.pnlClass(value)}`;
|
|
219
|
+
}
|
|
220
|
+
// What the pointer is over. The engine hands over the moment and the value at it; the moment
|
|
221
|
+
// is worded by the host, because what a time reads as is the page's business and not this
|
|
222
|
+
// package's.
|
|
223
|
+
_renderHover(param) {
|
|
224
|
+
const el = this._hoverEl;
|
|
225
|
+
if (el === null)
|
|
226
|
+
return;
|
|
227
|
+
const point = this._series === null ? undefined : param.seriesData.get(this._series);
|
|
228
|
+
const value = point?.value;
|
|
229
|
+
// Off the series entirely: the engine reports a crosshair with no time when the pointer
|
|
230
|
+
// has left the plot, and a stale readout is worse than none.
|
|
231
|
+
if (param.time === null || value === undefined || !Number.isFinite(value)) {
|
|
232
|
+
el.textContent = '';
|
|
233
|
+
el.className = 'equity-hover';
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// Back to milliseconds for the host: the engine counts in seconds, and every other moment
|
|
237
|
+
// this package hands over is a Date.
|
|
238
|
+
const moment = new Date(Number(param.time) * 1000);
|
|
239
|
+
el.textContent = `${this._host.presentation.timeText(moment)} ${formatPnl(value)}`;
|
|
240
|
+
el.className = `equity-hover ${this._host.presentation.pnlClass(value)}`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
EquityWidget.TYPE = ControlTypes.Equity;
|
|
244
|
+
/// The zone a moment reads in: the browser's own, which is the zone
|
|
245
|
+
/// `presentation.timeText` words one in. UTC when the environment cannot say.
|
|
246
|
+
function browserTimeZone() {
|
|
247
|
+
try {
|
|
248
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return 'UTC';
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/// A colour at a given opacity.
|
|
255
|
+
///
|
|
256
|
+
/// The host answers with whatever CSS understands, so this cannot take it apart - it wraps it
|
|
257
|
+
/// instead, which `color-mix` accepts for every form a token can be written in.
|
|
258
|
+
function withAlpha(colour, alpha) {
|
|
259
|
+
return `color-mix(in srgb, ${colour} ${Math.round(alpha * 100)}%, transparent)`;
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=equity-widget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"equity-widget.js","sourceRoot":"","sources":["../../src/equity-widget.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// The geometry of a heatmap - one metric over two discrete axes - as pure functions over numbers.
|
|
2
|
+
//
|
|
3
|
+
// A heatmap is a lattice: every distinct value of one axis crossed with every distinct value of
|
|
4
|
+
// the other, one cell per pair, coloured by what that pair measured. What produced the pairs is
|
|
5
|
+
// not here and does not need to be - an optimisation sweep is one source of them, and the object
|
|
6
|
+
// is the same shape whatever the source.
|
|
7
|
+
//
|
|
8
|
+
// None of this needs a canvas, which is what makes it checkable: a fake 2D context can only
|
|
9
|
+
// record the calls a control made, so the numbers those calls carry are checked here, against
|
|
10
|
+
// arithmetic rather than against pixels.
|
|
11
|
+
//
|
|
12
|
+
// The colour comes out as a signed number in -1..1 rather than as a colour. The sign says which
|
|
13
|
+
// side of the anchor a cell fell on and the magnitude how far, and the control turns that into
|
|
14
|
+
// one of the two direction colours its host answered with. Nothing in this file knows a colour.
|
|
15
|
+
/// Which way is better. Stated wherever a scale is built, never inferred: a metric does not
|
|
16
|
+
/// carry its own direction, and a map of drawdowns would otherwise paint its worst corner in
|
|
17
|
+
/// the winning colour.
|
|
18
|
+
export const HeatDirections = {
|
|
19
|
+
Higher: 'higher',
|
|
20
|
+
Lower: 'lower',
|
|
21
|
+
};
|
|
22
|
+
// Proportions, not looks: a canvas has no cascade to read them from, and moving them into CSS
|
|
23
|
+
// would only mean reading them back out again.
|
|
24
|
+
const LEFT_GUTTER = 54;
|
|
25
|
+
const RIGHT_PAD = 6;
|
|
26
|
+
const TOP_BAND = 26;
|
|
27
|
+
const BOTTOM_BAND = 30;
|
|
28
|
+
const TICK_GAP = 4;
|
|
29
|
+
const LABEL_HEIGHT = 13;
|
|
30
|
+
/// Half a pixel off each side, so abutting cells show a hairline of the panel between them and
|
|
31
|
+
/// the lattice reads as cells rather than as one continuous wash.
|
|
32
|
+
const CELL_INSET = 0.5;
|
|
33
|
+
const LEGEND_WIDTH = 140;
|
|
34
|
+
const LEGEND_HEIGHT = 8;
|
|
35
|
+
const LEGEND_Y = 14;
|
|
36
|
+
const LEGEND_STEPS = 28;
|
|
37
|
+
/// Room one label wants. Below this the labels touch, and an axis whose values overprint each
|
|
38
|
+
/// other is worse than an axis showing every fourth value.
|
|
39
|
+
const MIN_TICK_WIDTH = 34;
|
|
40
|
+
const MIN_TICK_HEIGHT = 13;
|
|
41
|
+
/// A value that is a number written as text, trailing unit included: "12", "-0.5", "7.5%".
|
|
42
|
+
const NUMERIC = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?\s*%?$/;
|
|
43
|
+
/// Every run at a pair, as one cell.
|
|
44
|
+
///
|
|
45
|
+
/// A run whose metric is not a number is dropped rather than folded in: a report that carries a
|
|
46
|
+
/// missing figure as an empty string would otherwise turn the whole cell into a NaN, and a NaN
|
|
47
|
+
/// cell paints as nothing while claiming the pair was measured.
|
|
48
|
+
export function foldCells(cells) {
|
|
49
|
+
const folded = new Map();
|
|
50
|
+
const order = [];
|
|
51
|
+
for (const cell of cells) {
|
|
52
|
+
if (cell === null || cell === undefined || !Number.isFinite(cell.value))
|
|
53
|
+
continue;
|
|
54
|
+
const id = pairKey(cell.x, cell.y);
|
|
55
|
+
const found = folded.get(id);
|
|
56
|
+
if (found === undefined) {
|
|
57
|
+
folded.set(id, { x: cell.x, y: cell.y, sum: cell.value, count: 1 });
|
|
58
|
+
order.push(id);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
found.sum += cell.value;
|
|
62
|
+
found.count += 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return order.map(id => {
|
|
66
|
+
const fold = folded.get(id);
|
|
67
|
+
return { x: fold.x, y: fold.y, value: fold.sum / fold.count, count: fold.count };
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/// One axis, in the order it is read.
|
|
71
|
+
///
|
|
72
|
+
/// Numerically when every value on it is a number, which is the whole point: a text sort puts
|
|
73
|
+
/// "10" before "2" and the axis stops being monotonic, so the map's shape becomes an artefact of
|
|
74
|
+
/// how the values happened to be spelled. Text order otherwise - and it is the right answer for
|
|
75
|
+
/// the shapes that actually arrive, because .NET writes a time span at a fixed width, so
|
|
76
|
+
/// "00:05:00" < "00:15:00" < "01:00:00" is also chronological.
|
|
77
|
+
export function axisValues(values) {
|
|
78
|
+
const unique = [];
|
|
79
|
+
const seen = new Set();
|
|
80
|
+
for (const value of values) {
|
|
81
|
+
if (seen.has(value))
|
|
82
|
+
continue;
|
|
83
|
+
seen.add(value);
|
|
84
|
+
unique.push(value);
|
|
85
|
+
}
|
|
86
|
+
if (unique.every(value => NUMERIC.test(value)))
|
|
87
|
+
return unique.sort((left, right) => numberOf(left) - numberOf(right));
|
|
88
|
+
return unique.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
|
|
89
|
+
}
|
|
90
|
+
/// What the colours are measured against.
|
|
91
|
+
///
|
|
92
|
+
/// Two anchors, and which one applies is decided by the data rather than by the caller. When the
|
|
93
|
+
/// values straddle zero, zero is the anchor: profit and loss are different in kind, not merely
|
|
94
|
+
/// in degree, and a map that shaded a loss green because it was less bad than the others would
|
|
95
|
+
/// be lying about the only thing a reader checks first. When zero is nowhere in the range - a
|
|
96
|
+
/// Sharpe of 1.8 to 2.2, a profit of 100 to 5000 - the anchor is the middle of the range,
|
|
97
|
+
/// because anchoring at a zero the sweep never came near produces one flat block with no
|
|
98
|
+
/// contrast, and comparing the cells with each other is what the reader came for.
|
|
99
|
+
///
|
|
100
|
+
/// The reach is the same on both sides of the anchor, so one unit of colour means one amount of
|
|
101
|
+
/// metric everywhere on the map. Scaling each side to its own extreme instead would make a five
|
|
102
|
+
/// dollar loss look exactly as red as a five hundred dollar one.
|
|
103
|
+
export function heatScale(buckets) {
|
|
104
|
+
let min = Infinity;
|
|
105
|
+
let max = -Infinity;
|
|
106
|
+
for (const bucket of buckets) {
|
|
107
|
+
if (bucket.value < min)
|
|
108
|
+
min = bucket.value;
|
|
109
|
+
if (bucket.value > max)
|
|
110
|
+
max = bucket.value;
|
|
111
|
+
}
|
|
112
|
+
if (!isFinite(min) || !isFinite(max)) {
|
|
113
|
+
min = 0;
|
|
114
|
+
max = 0;
|
|
115
|
+
}
|
|
116
|
+
const anchor = min <= 0 && max >= 0 ? 0 : (min + max) / 2;
|
|
117
|
+
const reach = Math.max(Math.abs(max - anchor), Math.abs(anchor - min));
|
|
118
|
+
return { anchor, reach: reach > 0 ? reach : 1, min, max };
|
|
119
|
+
}
|
|
120
|
+
/// How good a value is, as -1..1. Positive is better whichever way better runs.
|
|
121
|
+
export function tintOf(value, scale, betterWhen) {
|
|
122
|
+
const distance = (value - scale.anchor) / scale.reach;
|
|
123
|
+
const signed = betterWhen === HeatDirections.Lower ? -distance : distance;
|
|
124
|
+
return Math.max(-1, Math.min(1, signed));
|
|
125
|
+
}
|
|
126
|
+
/// The inverse, for labelling a legend: what value a tint stands for.
|
|
127
|
+
export function valueAt(tint, scale, betterWhen) {
|
|
128
|
+
const distance = betterWhen === HeatDirections.Lower ? -tint : tint;
|
|
129
|
+
return scale.anchor + distance * scale.reach;
|
|
130
|
+
}
|
|
131
|
+
/// Lay one map out, or null when nothing was measured.
|
|
132
|
+
export function layoutHeatmap(input) {
|
|
133
|
+
const buckets = foldCells(input.cells);
|
|
134
|
+
if (buckets.length === 0)
|
|
135
|
+
return null;
|
|
136
|
+
const columns = axisValues(buckets.map(bucket => bucket.x));
|
|
137
|
+
const rows = axisValues(buckets.map(bucket => bucket.y));
|
|
138
|
+
const scale = heatScale(buckets);
|
|
139
|
+
const plot = {
|
|
140
|
+
x: LEFT_GUTTER,
|
|
141
|
+
y: TOP_BAND,
|
|
142
|
+
width: Math.max(1, input.width - LEFT_GUTTER - RIGHT_PAD),
|
|
143
|
+
height: Math.max(1, input.height - TOP_BAND - BOTTOM_BAND),
|
|
144
|
+
};
|
|
145
|
+
const cellWidth = plot.width / columns.length;
|
|
146
|
+
const cellHeight = plot.height / rows.length;
|
|
147
|
+
const rectOf = (column, row) => ({
|
|
148
|
+
x: plot.x + column * cellWidth + CELL_INSET,
|
|
149
|
+
// Row 0 sits at the FOOT of the plot: this is a chart, and a chart's Y grows upward. A
|
|
150
|
+
// table would put it at the top, and then the map and its axis would disagree about
|
|
151
|
+
// which corner is which.
|
|
152
|
+
y: plot.y + plot.height - (row + 1) * cellHeight + CELL_INSET,
|
|
153
|
+
width: Math.max(1, cellWidth - CELL_INSET * 2),
|
|
154
|
+
height: Math.max(1, cellHeight - CELL_INSET * 2),
|
|
155
|
+
});
|
|
156
|
+
const measured = new Map(buckets.map(bucket => [pairKey(bucket.x, bucket.y), bucket]));
|
|
157
|
+
const cells = [];
|
|
158
|
+
const gaps = [];
|
|
159
|
+
for (let row = 0; row < rows.length; row++) {
|
|
160
|
+
for (let column = 0; column < columns.length; column++) {
|
|
161
|
+
const bucket = measured.get(pairKey(columns[column], rows[row]));
|
|
162
|
+
if (bucket === undefined) {
|
|
163
|
+
gaps.push({ x: columns[column], y: rows[row], rect: rectOf(column, row) });
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
cells.push({
|
|
167
|
+
bucket,
|
|
168
|
+
rect: rectOf(column, row),
|
|
169
|
+
tint: tintOf(bucket.value, scale, input.betterWhen),
|
|
170
|
+
best: false,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// The highest tint is the best cell whichever way better runs, which is the reason the tint
|
|
175
|
+
// is signed by direction rather than by the metric. A tie goes to the first in scan order -
|
|
176
|
+
// the cell nearest the origin - rather than to whichever run happened to arrive first.
|
|
177
|
+
let best = cells[0];
|
|
178
|
+
for (const shape of cells)
|
|
179
|
+
if (shape.tint > best.tint)
|
|
180
|
+
best = shape;
|
|
181
|
+
best.best = true;
|
|
182
|
+
const xTicks = tickIndices(columns.length, tickStep(columns.length, plot.width, MIN_TICK_WIDTH))
|
|
183
|
+
.map(index => ({
|
|
184
|
+
text: columns[index],
|
|
185
|
+
x: plot.x + (index + 0.5) * cellWidth,
|
|
186
|
+
y: plot.y + plot.height + TICK_GAP,
|
|
187
|
+
}));
|
|
188
|
+
const yTicks = tickIndices(rows.length, tickStep(rows.length, plot.height, MIN_TICK_HEIGHT))
|
|
189
|
+
.map(index => ({
|
|
190
|
+
text: rows[index],
|
|
191
|
+
x: plot.x - TICK_GAP,
|
|
192
|
+
y: plot.y + plot.height - (index + 0.5) * cellHeight,
|
|
193
|
+
}));
|
|
194
|
+
return {
|
|
195
|
+
plot,
|
|
196
|
+
columns,
|
|
197
|
+
rows,
|
|
198
|
+
cells,
|
|
199
|
+
gaps,
|
|
200
|
+
xTicks,
|
|
201
|
+
yTicks,
|
|
202
|
+
xTitle: { text: input.xLabel, x: plot.x + plot.width / 2, y: plot.y + plot.height + TICK_GAP + LABEL_HEIGHT },
|
|
203
|
+
// Above the plot rather than beside it: a title down the left edge would have to be
|
|
204
|
+
// rotated, and a rotation is a transform the control would then have to unwind around
|
|
205
|
+
// everything else it draws.
|
|
206
|
+
yTitle: { text: input.yLabel, x: 0, y: 0 },
|
|
207
|
+
legend: legendOf(plot, scale, input.betterWhen),
|
|
208
|
+
scale,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/// The cell under a point, or null. Cells never overlap, so the first match is the only one -
|
|
212
|
+
/// unlike a scatter, where the hit test has to walk the draw order backwards.
|
|
213
|
+
export function hitHeatmap(layout, x, y) {
|
|
214
|
+
for (const shape of layout.cells) {
|
|
215
|
+
const rect = shape.rect;
|
|
216
|
+
if (x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height)
|
|
217
|
+
return shape;
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
function legendOf(plot, scale, betterWhen) {
|
|
222
|
+
const right = plot.x + plot.width;
|
|
223
|
+
const left = Math.max(plot.x, right - LEGEND_WIDTH);
|
|
224
|
+
const stepWidth = (right - left) / LEGEND_STEPS;
|
|
225
|
+
const steps = [];
|
|
226
|
+
for (let step = 0; step < LEGEND_STEPS; step++) {
|
|
227
|
+
steps.push({
|
|
228
|
+
// Half a pixel of overlap: abutting fills leave a seam wherever the boundary is
|
|
229
|
+
// fractional, and a key with a comb of background through it reads as a scale with
|
|
230
|
+
// holes in it.
|
|
231
|
+
rect: { x: left + step * stepWidth, y: LEGEND_Y, width: stepWidth + 0.5, height: LEGEND_HEIGHT },
|
|
232
|
+
tint: -1 + (2 * step) / (LEGEND_STEPS - 1),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
steps,
|
|
237
|
+
low: { value: valueAt(-1, scale, betterWhen), x: left, y: 0 },
|
|
238
|
+
anchor: { value: scale.anchor, x: (left + right) / 2, y: 0 },
|
|
239
|
+
high: { value: valueAt(1, scale, betterWhen), x: right, y: 0 },
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/// How many values to skip between labels so they do not touch.
|
|
243
|
+
function tickStep(count, span, room) {
|
|
244
|
+
const slots = Math.max(1, Math.floor(span / room));
|
|
245
|
+
return Math.max(1, Math.ceil(count / slots));
|
|
246
|
+
}
|
|
247
|
+
/// Which values get a label. The last one always does - the end of an axis is what a reader
|
|
248
|
+
/// checks first - and the label before it gives way when the step would land them on top of
|
|
249
|
+
/// each other.
|
|
250
|
+
function tickIndices(count, step) {
|
|
251
|
+
const indices = [];
|
|
252
|
+
for (let index = 0; index < count; index += step)
|
|
253
|
+
indices.push(index);
|
|
254
|
+
const last = count - 1;
|
|
255
|
+
if (indices[indices.length - 1] !== last) {
|
|
256
|
+
if (last - indices[indices.length - 1] < step)
|
|
257
|
+
indices.pop();
|
|
258
|
+
indices.push(last);
|
|
259
|
+
}
|
|
260
|
+
return indices;
|
|
261
|
+
}
|
|
262
|
+
/// A pair as one map key. NUL rather than a printable separator: an axis value may contain any
|
|
263
|
+
/// character a parameter's own notation uses, and "a|b" crossed with "c" must not collide with
|
|
264
|
+
/// "a" crossed with "b|c".
|
|
265
|
+
function pairKey(x, y) {
|
|
266
|
+
return `${x}\u0000${y}`;
|
|
267
|
+
}
|
|
268
|
+
function numberOf(value) {
|
|
269
|
+
return Number(value.trim().replace(/%$/, ''));
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=heatmap-grid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heatmap-grid.js","sourceRoot":"","sources":["../../src/heatmap-grid.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/index.js
CHANGED
|
@@ -11,10 +11,47 @@ export { TradeHistoryWidget } from './trade-history-widget.js';
|
|
|
11
11
|
export { WatchlistWidget } from './watchlist-widget.js';
|
|
12
12
|
export { OrderEntrySides, OrderEntryTypes, OrderEntryWidget } from './order-entry-widget.js';
|
|
13
13
|
export { OrderBookWidget } from './orderbook-widget.js';
|
|
14
|
+
// The cumulative P&L curve, shared by everything that draws one: a strategy row's
|
|
15
|
+
// sparkline and a statistics card are the same curve at two sizes. A backtest's own
|
|
16
|
+
// equity is not here - that one shares the price axis and belongs to the chart engine.
|
|
17
|
+
export { compressPnl, drawPnlCurve, pnlCurve } from './pnl-curve.js';
|
|
18
|
+
// The same curve at the size of a chart: one control for an equity panel, so a host does not
|
|
19
|
+
// hand-roll the arithmetic a sparkline already does.
|
|
20
|
+
export { EquityWidget } from './equity-widget.js';
|
|
21
|
+
// The chart engine the two chart panels are built on, re-exported so a host can reach the same
|
|
22
|
+
// createChart these panels use rather than resolving a second copy of it.
|
|
23
|
+
export { AreaSeries, CrosshairMode, LineSeries, createChart } from './chart-engine.js';
|
|
24
|
+
// One metric over two parameters. It knows nothing about optimisation - a grid of {x, y, value}
|
|
25
|
+
// is the same object whatever produced it - which is what lets a backtest sweep and anything
|
|
26
|
+
// else that varies two things share a control.
|
|
27
|
+
export { OptimizationHeatmapWidget } from './optimization-heatmap-widget.js';
|
|
28
|
+
export { HeatDirections, heatScale, hitHeatmap, layoutHeatmap, tintOf, valueAt } from './heatmap-grid.js';
|
|
29
|
+
// The same measurements as a landscape, turned by hand. No library under it: the projection is
|
|
30
|
+
// arithmetic and the gestures are pointer events, so a mouse and a thumb take the same path.
|
|
31
|
+
export { SurfaceWidget } from './surface-widget.js';
|
|
32
|
+
export { DEFAULT_VIEW, MAX_PITCH, MIN_PITCH, clampView, dragView, project, surfaceLayout, zoomView, } from './surface-grid.js';
|
|
33
|
+
export { OptionDeskWidget, greekPlaces, greekScales, scaleChain, sideGreeks } from './option-desk-widget.js';
|
|
34
|
+
// The smile: the same chain the desk reads, drawn as the shape a trader looks for rather than as
|
|
35
|
+
// a column of percentages.
|
|
36
|
+
export { OptionSmileWidget } from './option-smile-widget.js';
|
|
37
|
+
export { sideVolatility, sortedChain, toSmileSeries } from './option-smile-widget.js';
|
|
38
|
+
// Option pricing, for a host that has quotes and an expiry but no pricing service. A host
|
|
39
|
+
// that computes its own greeks sends them instead and none of this is reached.
|
|
40
|
+
export { OptionTypes, d1, d2, greeks, impliedVolatility, normalCdf, normalPdf, premium } from './black-scholes.js';
|
|
41
|
+
export { StrategiesWidget, StrategyStates } from './strategies-widget.js';
|
|
42
|
+
export { LogMonitorWidget } from './log-monitor-widget.js';
|
|
43
|
+
// The log's own shapes: how sources nest, and which messages a view of them keeps. A host
|
|
44
|
+
// rendering its own view of the same feed reads these rather than reinventing the filter.
|
|
45
|
+
export { LogLevels, buildLogTree, keepLog, subtreeOf } from './log-tree.js';
|
|
46
|
+
export { StatisticsWidget, formatStatistic } from './statistics-widget.js';
|
|
14
47
|
export { TradeFeedWidget } from './tradefeed-widget.js';
|
|
15
48
|
// The tape's own shapes: one print as the chart reads it, and the bubble a
|
|
16
49
|
// print (or a slice of them) is drawn as. A host that renders its own view of
|
|
17
50
|
// the same feed reads these rather than reinventing the compaction.
|
|
18
51
|
export { aggregateBubbles } from './tradefeed-aggregator.js';
|
|
19
52
|
export { layoutBubbles } from './tradefeed-bubbles.js';
|
|
53
|
+
// The grid's own menu, worded by the host. Exported because a host that builds a table of its
|
|
54
|
+
// own - a run's log beside the blotters, say - needs the same menu in the same words, and the
|
|
55
|
+
// alternative is every consumer restating thirty labels the package already knows.
|
|
56
|
+
export { makeGridMenu } from './grid-menu.js';
|
|
20
57
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|