@stocksharp/trading-controls 1.1.1 → 1.3.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 +95 -16
- package/dist/esm/active-orders-widget.js +38 -25
- package/dist/esm/active-orders-widget.js.map +1 -1
- package/dist/esm/black-scholes.js +147 -0
- package/dist/esm/black-scholes.js.map +1 -0
- package/dist/esm/control-types.js +4 -0
- package/dist/esm/control-types.js.map +1 -1
- package/dist/esm/index.js +18 -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/option-desk-widget.js +322 -0
- package/dist/esm/option-desk-widget.js.map +1 -0
- package/dist/esm/pnl-curve.js +129 -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/trade-history-widget.js +14 -6
- package/dist/esm/trade-history-widget.js.map +1 -1
- package/dist/esm/tradefeed-widget.js +3 -3
- package/dist/esm/tradefeed-widget.js.map +1 -1
- package/dist/esm/trading-host.js +1 -0
- package/dist/esm/trading-host.js.map +1 -1
- package/dist/sstradingcontrols.js +1358 -78
- package/dist/sstradingcontrols.js.map +4 -4
- package/dist/types/active-orders-widget.d.ts +11 -3
- package/dist/types/active-orders-widget.d.ts.map +1 -1
- package/dist/types/black-scholes.d.ts +28 -0
- package/dist/types/black-scholes.d.ts.map +1 -0
- package/dist/types/control-types.d.ts +4 -0
- package/dist/types/control-types.d.ts.map +1 -1
- package/dist/types/index.d.ts +16 -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/option-desk-widget.d.ts +68 -0
- package/dist/types/option-desk-widget.d.ts.map +1 -0
- package/dist/types/pnl-curve.d.ts +43 -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/trade-history-widget.d.ts +3 -1
- package/dist/types/trade-history-widget.d.ts.map +1 -1
- package/dist/types/trading-data.d.ts +9 -0
- package/dist/types/trading-data.d.ts.map +1 -1
- package/dist/types/trading-host.d.ts +1 -0
- package/dist/types/trading-host.d.ts.map +1 -1
- package/package.json +27 -2
- package/screenshots/log-monitor.png +0 -0
- package/screenshots/option-desk.png +0 -0
- package/screenshots/panels.jpg +0 -0
- package/screenshots/statistics.png +0 -0
- package/screenshots/strategies.png +0 -0
- package/src/active-orders-widget.ts +49 -25
- package/src/black-scholes.ts +199 -0
- package/src/control-types.ts +4 -0
- package/src/index.ts +41 -0
- package/src/log-monitor-widget.ts +312 -0
- package/src/log-tree.ts +131 -0
- package/src/option-desk-widget.ts +422 -0
- package/src/pnl-curve.ts +204 -0
- package/src/statistics-widget.ts +226 -0
- package/src/strategies-widget.ts +435 -0
- package/src/trade-history-widget.ts +19 -6
- package/src/tradefeed-widget.ts +3 -3
- package/src/trading-data.ts +22 -0
- package/src/trading-host.ts +8 -0
- package/styles/trading-controls.css +356 -0
- package/translation-keys.json +70 -1
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// The table is `DataGrid` from `@stocksharp/grids`: the columns below are this
|
|
13
13
|
// blotter's single declaration and the <thead> is left empty for the grid to
|
|
14
14
|
// fill.
|
|
15
|
-
import { formatPrice, formatQty
|
|
15
|
+
import { formatPrice, formatQty } from './formatters.js';
|
|
16
16
|
import { makeElement, makeIconButton, makePanelId, makePanelRoot } from './dom.js';
|
|
17
17
|
import { ControlTypes } from './control-types.js';
|
|
18
18
|
import { makeGridMenu } from './grid-menu.js';
|
|
@@ -24,6 +24,10 @@ import { DataGrid, GridColumn } from '@stocksharp/grids/source/data-grid';
|
|
|
24
24
|
/// portfolio and its lifecycle all arrive through it.
|
|
25
25
|
export interface TradeHistoryDeps {
|
|
26
26
|
host: TradingHost;
|
|
27
|
+
/// A blotter over a finished run - a backtest report, an archived session.
|
|
28
|
+
/// There is no account to load from and nothing to reload, so the rows come
|
|
29
|
+
/// in through `update` and the refresh gesture is not rendered.
|
|
30
|
+
readOnly?: boolean;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
export class TradeHistoryWidget {
|
|
@@ -44,7 +48,7 @@ export class TradeHistoryWidget {
|
|
|
44
48
|
// host, so a missing host has to fail here rather than render a panel
|
|
45
49
|
// captioned with raw English keys.
|
|
46
50
|
const host = assertHost(deps?.host, 'TradeHistoryWidget');
|
|
47
|
-
const root = TradeHistoryWidget._buildRoot(host);
|
|
51
|
+
const root = TradeHistoryWidget._buildRoot(host, deps?.readOnly === true);
|
|
48
52
|
root.id = makePanelId(TradeHistoryWidget.TYPE);
|
|
49
53
|
hostEl.appendChild(root);
|
|
50
54
|
return new TradeHistoryWidget(root, state || {}, deps);
|
|
@@ -52,7 +56,7 @@ export class TradeHistoryWidget {
|
|
|
52
56
|
|
|
53
57
|
// The panel's markup. The host stylesheet reads this structure, and a
|
|
54
58
|
// docking host lifts `.panel-header`'s children into its tab strip.
|
|
55
|
-
static _buildRoot(host: TradingHost): HTMLElement {
|
|
59
|
+
static _buildRoot(host: TradingHost, readOnly = false): HTMLElement {
|
|
56
60
|
const title = host.t('TradeHistory');
|
|
57
61
|
return makePanelRoot('trade-history-panel', title, [
|
|
58
62
|
makeElement('div', 'panel-header', {}, [
|
|
@@ -67,7 +71,7 @@ export class TradeHistoryWidget {
|
|
|
67
71
|
]),
|
|
68
72
|
]),
|
|
69
73
|
makeElement('div', 'panel-rail', { role: 'toolbar', 'aria-label': host.t('TradeHistoryActions') }, [
|
|
70
|
-
makeIconButton('bt-icon-btn panel-refresh-btn', host.t('Refresh'), 'bi-arrow-clockwise', {}),
|
|
74
|
+
...(readOnly ? [] : [makeIconButton('bt-icon-btn panel-refresh-btn', host.t('Refresh'), 'bi-arrow-clockwise', {})]),
|
|
71
75
|
makeIconButton('bt-icon-btn panel-export-btn', host.t('ExportToExcel'), 'bi-file-earmark-spreadsheet', {}),
|
|
72
76
|
]),
|
|
73
77
|
]),
|
|
@@ -157,6 +161,15 @@ export class TradeHistoryWidget {
|
|
|
157
161
|
}
|
|
158
162
|
}
|
|
159
163
|
|
|
164
|
+
/// Show these rows. The push path, for a consumer that already holds the
|
|
165
|
+
/// trades - a finished run's report - rather than an account to load them
|
|
166
|
+
/// from. `refresh` is the pull path and the two do not mix: whichever ran
|
|
167
|
+
/// last is what the grid shows.
|
|
168
|
+
update(rows: TradeRow[]): void {
|
|
169
|
+
this._rows = rows || [];
|
|
170
|
+
this._grid?.setRows(this._rows);
|
|
171
|
+
}
|
|
172
|
+
|
|
160
173
|
// Export the table to .xlsx in the currently rendered (sorted) order.
|
|
161
174
|
_export(): void {
|
|
162
175
|
this._grid?.download('trades', this._host.t('TradeHistory'));
|
|
@@ -184,8 +197,8 @@ export class TradeHistoryWidget {
|
|
|
184
197
|
header: label('Time'),
|
|
185
198
|
exportable: true,
|
|
186
199
|
value: (t) => t.executedAt || t.time,
|
|
187
|
-
render: (t) =>
|
|
188
|
-
exportValue: (t) =>
|
|
200
|
+
render: (t) => presentation.timeText(t.executedAt || t.time!),
|
|
201
|
+
exportValue: (t) => presentation.timeText(t.executedAt || t.time!),
|
|
189
202
|
},
|
|
190
203
|
{
|
|
191
204
|
key: 'symbol',
|
package/src/tradefeed-widget.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// geometry comes out of `tradefeed-bubbles.ts` as numbers and its colours out
|
|
21
21
|
// of the host's `canvasPalette()`, so the package still paints nothing from a
|
|
22
22
|
// palette of its own.
|
|
23
|
-
import { formatPrice, formatQty
|
|
23
|
+
import { formatPrice, formatQty } from './formatters.js';
|
|
24
24
|
import { makeElement, makeIconButton, makePanelId, makePanelRoot } from './dom.js';
|
|
25
25
|
import { ControlTypes } from './control-types.js';
|
|
26
26
|
import { makeGridMenu } from './grid-menu.js';
|
|
@@ -372,7 +372,7 @@ export class TradeFeedWidget {
|
|
|
372
372
|
header: label('Time'),
|
|
373
373
|
exportable: true,
|
|
374
374
|
value: (t) => String(t.time || t.executedAt || ''),
|
|
375
|
-
render: (t) =>
|
|
375
|
+
render: (t) => this._host.presentation.timeText(t.time || t.executedAt!),
|
|
376
376
|
},
|
|
377
377
|
{
|
|
378
378
|
key: 'symbol',
|
|
@@ -708,7 +708,7 @@ export class TradeFeedWidget {
|
|
|
708
708
|
|
|
709
709
|
const lines: HTMLElement[] = [];
|
|
710
710
|
if (this._extraSymbols.size > 0) lines.push(this._tooltipLine(this._host.t('Symbol'), bubble.symbol, ''));
|
|
711
|
-
lines.push(this._tooltipLine(this._host.t('Time'),
|
|
711
|
+
lines.push(this._tooltipLine(this._host.t('Time'), this._host.presentation.timeText(bubble.time), ''));
|
|
712
712
|
lines.push(this._tooltipLine(aggregated ? this._host.t('VWAP') : this._host.t('Price'), formatPrice(bubble.price), ''));
|
|
713
713
|
lines.push(this._tooltipLine(aggregated ? this._host.t('Total qty') : this._host.t('Qty'), formatQty(bubble.quantity), ''));
|
|
714
714
|
lines.push(this._tooltipLine(this._host.t('Side'), presentation.sideText(side), presentation.sideClass(side)));
|
package/src/trading-data.ts
CHANGED
|
@@ -100,6 +100,28 @@ export interface TradeRow {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/// One tradable instrument, as the instrument search returns it.
|
|
103
|
+
/// One statistic a strategy reports about itself.
|
|
104
|
+
///
|
|
105
|
+
/// The desktop grid reflects these off the strategy's parameter objects; a browser is handed
|
|
106
|
+
/// them already resolved. `category` groups and `order` sorts - both are the registry's, not
|
|
107
|
+
/// the reader's, and neither is the translated text beside it.
|
|
108
|
+
export interface StatisticRow {
|
|
109
|
+
/// Stable identity, and what the row is addressed by. The parameter's own kind.
|
|
110
|
+
key: string;
|
|
111
|
+
/// Grouping key. Stable across languages.
|
|
112
|
+
category: string;
|
|
113
|
+
/// What that group is called here.
|
|
114
|
+
categoryText?: string;
|
|
115
|
+
/// Where the parameter sits among its peers. Banded by category by the registry.
|
|
116
|
+
order: number;
|
|
117
|
+
/// Localized caption.
|
|
118
|
+
name: string;
|
|
119
|
+
/// Localized explanation, shown on hover.
|
|
120
|
+
description?: string;
|
|
121
|
+
/// A number, a moment, or a word - whatever the parameter measures. Null until measured.
|
|
122
|
+
value?: number | string | Date | null;
|
|
123
|
+
}
|
|
124
|
+
|
|
103
125
|
export interface InstrumentRow {
|
|
104
126
|
/// Qualified symbol — `BTC@IMEX`. The display form drops the venue.
|
|
105
127
|
symbol?: string;
|
package/src/trading-host.ts
CHANGED
|
@@ -86,6 +86,13 @@ export interface TradingPresentation {
|
|
|
86
86
|
typeText(type: OrderType, limitPrice: number, stopPrice: number): string;
|
|
87
87
|
/// Localized order status.
|
|
88
88
|
statusText(status: OrderStatus): string;
|
|
89
|
+
/// A moment, worded. The host owns this for the same reason it owns a side or a
|
|
90
|
+
/// status: what a time should read as is not a property of the data. A live tape wants
|
|
91
|
+
/// the time of day in the reader's own zone; a blotter over a finished run wants the
|
|
92
|
+
/// date too, and in the zone the run was recorded in, or every row reads as the same
|
|
93
|
+
/// unplaceable day. `formatTime` is this package's tape answer, exported so a host that
|
|
94
|
+
/// wants exactly that need not restate it.
|
|
95
|
+
timeText(value: string | number | Date): string;
|
|
89
96
|
/// CSS class colouring a cell by side. The shipped stylesheet styles
|
|
90
97
|
/// `side-buy` and `side-sell`; a host on its own palette may answer with
|
|
91
98
|
/// its own names and style those instead.
|
|
@@ -271,6 +278,7 @@ export function assertHost(host: TradingHost, controlName: string): TradingHost
|
|
|
271
278
|
_required(host.presentation.isBuy, 'host.presentation.isBuy', 'function', controlName);
|
|
272
279
|
_required(host.presentation.typeText, 'host.presentation.typeText', 'function', controlName);
|
|
273
280
|
_required(host.presentation.statusText, 'host.presentation.statusText', 'function', controlName);
|
|
281
|
+
_required(host.presentation.timeText, 'host.presentation.timeText', 'function', controlName);
|
|
274
282
|
_required(host.presentation.sideClass, 'host.presentation.sideClass', 'function', controlName);
|
|
275
283
|
_required(host.presentation.pnlClass, 'host.presentation.pnlClass', 'function', controlName);
|
|
276
284
|
_required(host.presentation.canvasPalette, 'host.presentation.canvasPalette', 'function', controlName);
|
|
@@ -1378,3 +1378,359 @@
|
|
|
1378
1378
|
}
|
|
1379
1379
|
|
|
1380
1380
|
.oe-estimate.oe-estimate-error { color: var(--t-red); }
|
|
1381
|
+
|
|
1382
|
+
/* Statistics: a figure column, right-aligned and lined up on the digits so a column of
|
|
1383
|
+
numbers can be compared down rather than read across. */
|
|
1384
|
+
.statistic-value {
|
|
1385
|
+
text-align: right;
|
|
1386
|
+
font-family: var(--t-mono);
|
|
1387
|
+
font-variant-numeric: tabular-nums;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
/* ---- Log monitor -------------------------------------------------------- */
|
|
1391
|
+
|
|
1392
|
+
/* Two panes side by side: the sources, which are as wide as their names, and the messages,
|
|
1393
|
+
which take everything left. Both scroll in their own right so a long log does not push the
|
|
1394
|
+
tree off the panel. */
|
|
1395
|
+
.log-monitor-body {
|
|
1396
|
+
display: flex;
|
|
1397
|
+
min-height: 0;
|
|
1398
|
+
overflow: hidden;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
.log-sources {
|
|
1402
|
+
flex: 0 0 14rem;
|
|
1403
|
+
min-height: 0;
|
|
1404
|
+
overflow: auto;
|
|
1405
|
+
display: flex;
|
|
1406
|
+
flex-direction: column;
|
|
1407
|
+
border-right: 1px solid var(--t-border);
|
|
1408
|
+
background: var(--t-panel);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/* A source reads as a row, not as a button: the whole width is the target, the caption sits
|
|
1412
|
+
where its depth puts it, and only the selected one is filled. */
|
|
1413
|
+
.log-source {
|
|
1414
|
+
flex: 0 0 auto;
|
|
1415
|
+
border: 0;
|
|
1416
|
+
background: transparent;
|
|
1417
|
+
color: var(--t-text);
|
|
1418
|
+
font: inherit;
|
|
1419
|
+
text-align: left;
|
|
1420
|
+
padding: .25rem .5rem;
|
|
1421
|
+
cursor: pointer;
|
|
1422
|
+
white-space: nowrap;
|
|
1423
|
+
overflow: hidden;
|
|
1424
|
+
text-overflow: ellipsis;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
.log-source:hover { background: var(--t-hover); }
|
|
1428
|
+
|
|
1429
|
+
.log-source.is-selected {
|
|
1430
|
+
background: var(--t-hover);
|
|
1431
|
+
color: var(--t-text-bright);
|
|
1432
|
+
box-shadow: inset 2px 0 0 var(--t-accent);
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
.log-messages {
|
|
1436
|
+
flex: 1 1 auto;
|
|
1437
|
+
min-width: 0;
|
|
1438
|
+
min-height: 0;
|
|
1439
|
+
display: flex;
|
|
1440
|
+
flex-direction: column;
|
|
1441
|
+
overflow: hidden;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
.log-toolbar {
|
|
1445
|
+
flex: 0 0 auto;
|
|
1446
|
+
display: flex;
|
|
1447
|
+
align-items: center;
|
|
1448
|
+
gap: .3rem;
|
|
1449
|
+
padding: .3rem .5rem;
|
|
1450
|
+
border-bottom: 1px solid var(--t-border);
|
|
1451
|
+
background: var(--t-header);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/* One letter per level, lit when that level is shown. Off is dimmed rather than hidden, so the
|
|
1455
|
+
row of them reads as a set of switches and not as a shrinking list. */
|
|
1456
|
+
.log-level-toggle {
|
|
1457
|
+
border: 1px solid var(--t-border);
|
|
1458
|
+
background: var(--t-panel);
|
|
1459
|
+
color: var(--t-text-dim);
|
|
1460
|
+
font-family: var(--t-mono);
|
|
1461
|
+
font-size: .72rem;
|
|
1462
|
+
line-height: 1;
|
|
1463
|
+
width: 1.4rem;
|
|
1464
|
+
height: 1.4rem;
|
|
1465
|
+
border-radius: var(--t-radius);
|
|
1466
|
+
cursor: pointer;
|
|
1467
|
+
opacity: .45;
|
|
1468
|
+
transition: opacity var(--t-transition), color var(--t-transition);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
.log-level-toggle.is-on { opacity: 1; color: var(--t-text); }
|
|
1472
|
+
.log-level-toggle.log-level-error.is-on { color: var(--t-red); }
|
|
1473
|
+
.log-level-toggle.log-level-warning.is-on { color: var(--t-orange); }
|
|
1474
|
+
|
|
1475
|
+
.log-filter {
|
|
1476
|
+
flex: 1 1 auto;
|
|
1477
|
+
min-width: 4rem;
|
|
1478
|
+
max-width: 14rem;
|
|
1479
|
+
margin-left: .3rem;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
/* The scroll box, not the table, is the flex child. A table given `flex: 1` stretches its rows
|
|
1483
|
+
to fill the space instead of leaving the space empty, which drew a one-line log as one line a
|
|
1484
|
+
panel tall; and with nothing scrollable around it, a long log was simply unreachable past the
|
|
1485
|
+
height of the panel. */
|
|
1486
|
+
.log-table-scroll {
|
|
1487
|
+
flex: 1 1 auto;
|
|
1488
|
+
min-height: 0;
|
|
1489
|
+
overflow: auto;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
/* The header stays put while the messages scroll under it - a log is read by scrolling back,
|
|
1493
|
+
and a column heading that scrolls away takes the meaning of the columns with it. */
|
|
1494
|
+
.log-table thead th {
|
|
1495
|
+
position: sticky;
|
|
1496
|
+
top: 0;
|
|
1497
|
+
z-index: 1;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
/* The level column is one letter wide and coloured by what it says; the message takes the rest
|
|
1501
|
+
and wraps rather than being cut, because the end of an error is usually the part that names
|
|
1502
|
+
the cause. */
|
|
1503
|
+
.log-level-col { width: 2rem; }
|
|
1504
|
+
|
|
1505
|
+
.log-level-cell {
|
|
1506
|
+
text-align: center;
|
|
1507
|
+
font-family: var(--t-mono);
|
|
1508
|
+
color: var(--t-text-dim);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
.log-level-cell.log-level-error { color: var(--t-red); }
|
|
1512
|
+
.log-level-cell.log-level-warning { color: var(--t-orange); }
|
|
1513
|
+
|
|
1514
|
+
.log-message-cell {
|
|
1515
|
+
white-space: pre-wrap;
|
|
1516
|
+
word-break: break-word;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
.log-row-error td { color: var(--t-red); }
|
|
1520
|
+
|
|
1521
|
+
/* ---- Strategies dashboard ----------------------------------------------- */
|
|
1522
|
+
|
|
1523
|
+
/* The state reads twice: a dot that carries the colour across a long list, and the word beside
|
|
1524
|
+
it, because a colour cannot tell Starting from Started. */
|
|
1525
|
+
.strategy-state { white-space: nowrap; }
|
|
1526
|
+
|
|
1527
|
+
.strategy-dot {
|
|
1528
|
+
margin-right: .35rem;
|
|
1529
|
+
color: var(--t-text-dim);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
.strategy-state-started .strategy-dot { color: var(--t-green); }
|
|
1533
|
+
.strategy-state-starting .strategy-dot,
|
|
1534
|
+
.strategy-state-stopping .strategy-dot { color: var(--t-orange); }
|
|
1535
|
+
|
|
1536
|
+
/* A least width in characters, so the column does not jump as a run passes through Stopping on
|
|
1537
|
+
its way to Stopped, or Forming on its way to Running. Sized to the longest of those words; a
|
|
1538
|
+
floor rather than a cap, so a language that words them longer still gets its room. */
|
|
1539
|
+
.strategy-state-text {
|
|
1540
|
+
color: var(--t-text-dim);
|
|
1541
|
+
display: inline-block;
|
|
1542
|
+
min-width: 8ch;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
.strategy-actions,
|
|
1546
|
+
.strategy-position {
|
|
1547
|
+
white-space: nowrap;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/* A least width in digits, so the flatten button beside the figure does not move when the
|
|
1551
|
+
position gains a minus sign or loses a digit. It is a floor, not a cap: a genuinely wider
|
|
1552
|
+
number still gets the room it needs. */
|
|
1553
|
+
.strategy-position-value {
|
|
1554
|
+
display: inline-block;
|
|
1555
|
+
min-width: 5ch;
|
|
1556
|
+
text-align: right;
|
|
1557
|
+
font-variant-numeric: tabular-nums;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
.strategy-actions .bt-icon-btn { margin-right: .1rem; }
|
|
1561
|
+
|
|
1562
|
+
/* Flattening sits with the number it acts on, quiet until the position is worth closing. */
|
|
1563
|
+
.strategy-flatten-btn { margin-left: .35rem; }
|
|
1564
|
+
|
|
1565
|
+
.strategy-online { text-align: center; color: var(--t-text-dim); }
|
|
1566
|
+
.strategy-online.is-on { color: var(--t-green); }
|
|
1567
|
+
|
|
1568
|
+
.strategy-mode { min-width: 7rem; }
|
|
1569
|
+
|
|
1570
|
+
/* The curve fills the cell it was measured for; the cell claims that width so a column of them
|
|
1571
|
+
lines up whether or not a given strategy has traded yet. */
|
|
1572
|
+
.strategy-spark { width: 140px; padding-top: 2px; padding-bottom: 2px; }
|
|
1573
|
+
.strategy-spark-canvas { display: block; }
|
|
1574
|
+
|
|
1575
|
+
/* An error is the one cell allowed to be long: it is read when something is wrong and truncating
|
|
1576
|
+
it hides the part that names the cause. */
|
|
1577
|
+
.strategy-error {
|
|
1578
|
+
color: var(--t-red);
|
|
1579
|
+
white-space: pre-wrap;
|
|
1580
|
+
word-break: break-word;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
.strategy-failed .strategy-dot { color: var(--t-red); }
|
|
1584
|
+
|
|
1585
|
+
/* ---- Option desk -------------------------------------------------------- */
|
|
1586
|
+
|
|
1587
|
+
/* A bar behind a figure rather than beside it: the number stays readable and the cell keeps its
|
|
1588
|
+
width, so a column of them can be compared down the strip. */
|
|
1589
|
+
.option-bar-cell {
|
|
1590
|
+
position: relative;
|
|
1591
|
+
display: block;
|
|
1592
|
+
text-align: right;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
.option-bar {
|
|
1596
|
+
position: absolute;
|
|
1597
|
+
inset: 0 auto 0 0;
|
|
1598
|
+
border-radius: 1px;
|
|
1599
|
+
opacity: .35;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
.option-bar-text { position: relative; }
|
|
1603
|
+
|
|
1604
|
+
/* Calls and puts are told apart by which side the bar is tinted; volatility is neither side's,
|
|
1605
|
+
so it takes the neutral one. */
|
|
1606
|
+
.option-bar-call { background: var(--t-green); }
|
|
1607
|
+
.option-bar-put { background: var(--t-red); }
|
|
1608
|
+
.option-bar-iv { background: var(--t-text-dim); }
|
|
1609
|
+
|
|
1610
|
+
.option-bid { color: var(--t-green); }
|
|
1611
|
+
.option-ask { color: var(--t-red); }
|
|
1612
|
+
|
|
1613
|
+
/* The strike is the spine of the chain: it is what the eye runs down, so it is the one column
|
|
1614
|
+
given weight, and the value beside it sits in the same block. */
|
|
1615
|
+
.option-strike {
|
|
1616
|
+
text-align: center;
|
|
1617
|
+
font-weight: 600;
|
|
1618
|
+
color: var(--t-text-bright);
|
|
1619
|
+
background: var(--t-header);
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
.option-intrinsic {
|
|
1623
|
+
text-align: center;
|
|
1624
|
+
color: var(--t-text-dim);
|
|
1625
|
+
background: var(--t-header);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
/* Which half is in the money, shaded rather than outlined: the boundary between the two is the
|
|
1629
|
+
first thing a reader looks for and it moves with the underlying. */
|
|
1630
|
+
.option-itm-call td:first-child,
|
|
1631
|
+
.option-itm-put td:last-child { box-shadow: none; }
|
|
1632
|
+
|
|
1633
|
+
.option-itm-call .option-strike { border-left: 2px solid var(--t-green); }
|
|
1634
|
+
.option-itm-put .option-strike { border-right: 2px solid var(--t-red); }
|
|
1635
|
+
|
|
1636
|
+
/* ---- Fields the controls build themselves -------------------------------- */
|
|
1637
|
+
|
|
1638
|
+
/* A control that ships its own panel ships its own fields. These carry Bootstrap's class names
|
|
1639
|
+
because a host that has Bootstrap should keep its look, but a host that does not was getting
|
|
1640
|
+
the browser's default control: a white box with a native arrow, sitting on a dark panel. The
|
|
1641
|
+
rules below are what the package paints when nothing else has, and any real Bootstrap or host
|
|
1642
|
+
stylesheet loaded after this one still wins. */
|
|
1643
|
+
.terminal-table input.form-control,
|
|
1644
|
+
.terminal-table select.form-control,
|
|
1645
|
+
.panel-body input.form-control,
|
|
1646
|
+
.panel-body select.form-control {
|
|
1647
|
+
background: var(--t-bg);
|
|
1648
|
+
color: var(--t-text);
|
|
1649
|
+
border: 1px solid var(--t-border);
|
|
1650
|
+
border-radius: var(--t-radius);
|
|
1651
|
+
font-family: inherit;
|
|
1652
|
+
font-size: 11px;
|
|
1653
|
+
padding: 2px 6px;
|
|
1654
|
+
line-height: 1.4;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
.terminal-table select.form-control,
|
|
1658
|
+
.panel-body select.form-control {
|
|
1659
|
+
/* The native arrow is painted by the platform and cannot be themed, so it is replaced. */
|
|
1660
|
+
appearance: none;
|
|
1661
|
+
background-image: linear-gradient(45deg, transparent 50%, var(--t-text-dim) 50%),
|
|
1662
|
+
linear-gradient(135deg, var(--t-text-dim) 50%, transparent 50%);
|
|
1663
|
+
background-position: right 11px center, right 6px center;
|
|
1664
|
+
background-size: 5px 5px, 5px 5px;
|
|
1665
|
+
background-repeat: no-repeat;
|
|
1666
|
+
padding-right: 20px;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
.panel-body input.form-control::placeholder { color: var(--t-text-dim); }
|
|
1670
|
+
|
|
1671
|
+
.terminal-table input.form-control:focus,
|
|
1672
|
+
.terminal-table select.form-control:focus,
|
|
1673
|
+
.panel-body input.form-control:focus,
|
|
1674
|
+
.panel-body select.form-control:focus {
|
|
1675
|
+
outline: none;
|
|
1676
|
+
border-color: var(--t-accent);
|
|
1677
|
+
box-shadow: 0 0 0 2px var(--t-accent-glow-soft);
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
/* The tables carry their own text colour rather than inheriting the page's. A panel is dropped
|
|
1681
|
+
into whatever document a host has, and one that never set a colour was rendering near-black
|
|
1682
|
+
rows on a near-black panel. */
|
|
1683
|
+
.terminal-table { color: var(--t-text); }
|
|
1684
|
+
|
|
1685
|
+
/* The scrollbars inside a panel, painted from the same tokens as everything else in it.
|
|
1686
|
+
A control makes its own scroll containers - the body, the table, the log's source tree, the
|
|
1687
|
+
watchlist pane - so a panel that brings its own background brings the bar over it too;
|
|
1688
|
+
without this a themed dark panel scrolls with the operating system's light grey.
|
|
1689
|
+
|
|
1690
|
+
Scoped to `.terminal-panel` on purpose: a host's page keeps whatever scrollbars it has, and
|
|
1691
|
+
only the boxes this package created are restyled.
|
|
1692
|
+
|
|
1693
|
+
Both spellings, because they are not interchangeable. `scrollbar-width` / `scrollbar-color`
|
|
1694
|
+
are the standard ones and are all Firefox reads; the `::-webkit-scrollbar` pseudo-elements
|
|
1695
|
+
are what WebKit and Chromium read, and they win there when both are present. */
|
|
1696
|
+
.terminal-panel,
|
|
1697
|
+
.terminal-panel * {
|
|
1698
|
+
scrollbar-width: thin;
|
|
1699
|
+
scrollbar-color: var(--t-border) transparent;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
.terminal-panel ::-webkit-scrollbar,
|
|
1703
|
+
.terminal-panel::-webkit-scrollbar {
|
|
1704
|
+
width: 10px;
|
|
1705
|
+
height: 10px;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/* Transparent rather than a colour of its own: a scroll container sits on the panel, the header
|
|
1709
|
+
band or a grouped row, and a track painted one flat colour shows a seam against the other two. */
|
|
1710
|
+
.terminal-panel ::-webkit-scrollbar-track,
|
|
1711
|
+
.terminal-panel::-webkit-scrollbar-track {
|
|
1712
|
+
background: transparent;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
.terminal-panel ::-webkit-scrollbar-thumb,
|
|
1716
|
+
.terminal-panel::-webkit-scrollbar-thumb {
|
|
1717
|
+
background: var(--t-border);
|
|
1718
|
+
border-radius: var(--t-radius);
|
|
1719
|
+
/* An inset border keeps the thumb off the edge of the track, so it reads as a handle in a
|
|
1720
|
+
gutter rather than as a filled column. The border is the padding: it has to be
|
|
1721
|
+
transparent and clipped to the content box, or it paints as a second colour. */
|
|
1722
|
+
border: 2px solid transparent;
|
|
1723
|
+
background-clip: content-box;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
.terminal-panel ::-webkit-scrollbar-thumb:hover,
|
|
1727
|
+
.terminal-panel::-webkit-scrollbar-thumb:hover {
|
|
1728
|
+
background: var(--t-text-dim);
|
|
1729
|
+
background-clip: content-box;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
/* Where two bars meet. Left unpainted it is a white square in the corner of a dark panel. */
|
|
1733
|
+
.terminal-panel ::-webkit-scrollbar-corner,
|
|
1734
|
+
.terminal-panel::-webkit-scrollbar-corner {
|
|
1735
|
+
background: transparent;
|
|
1736
|
+
}
|