openalgo-script 0.7.1 → 0.8.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/CHANGELOG.md +89 -0
- package/README.md +22 -9
- package/dist/adapters/charts/capabilities.d.ts +48 -0
- package/dist/adapters/charts/capabilities.d.ts.map +1 -0
- package/dist/adapters/charts/capabilities.js +82 -0
- package/dist/adapters/charts/capabilities.js.map +1 -0
- package/dist/adapters/charts/columns.d.ts +2 -0
- package/dist/adapters/charts/columns.d.ts.map +1 -1
- package/dist/adapters/charts/columns.js +4 -0
- package/dist/adapters/charts/columns.js.map +1 -1
- package/dist/adapters/charts/contract.d.ts +35 -3
- package/dist/adapters/charts/contract.d.ts.map +1 -1
- package/dist/adapters/charts/descriptor.d.ts +1 -1
- package/dist/adapters/charts/descriptor.d.ts.map +1 -1
- package/dist/adapters/charts/descriptor.js +17 -5
- package/dist/adapters/charts/descriptor.js.map +1 -1
- package/dist/adapters/charts/fills.d.ts +26 -5
- package/dist/adapters/charts/fills.d.ts.map +1 -1
- package/dist/adapters/charts/fills.js +55 -7
- package/dist/adapters/charts/fills.js.map +1 -1
- package/dist/adapters/charts/index.d.ts +9 -3
- package/dist/adapters/charts/index.d.ts.map +1 -1
- package/dist/adapters/charts/index.js +7 -1
- package/dist/adapters/charts/index.js.map +1 -1
- package/dist/adapters/charts/produced.d.ts +3 -2
- package/dist/adapters/charts/produced.d.ts.map +1 -1
- package/dist/adapters/charts/produced.js +1 -1
- package/dist/adapters/charts/produced.js.map +1 -1
- package/dist/adapters/charts/run.d.ts +23 -0
- package/dist/adapters/charts/run.d.ts.map +1 -1
- package/dist/adapters/charts/run.js +2 -1
- package/dist/adapters/charts/run.js.map +1 -1
- package/dist/adapters/charts/surfaces.d.ts +11 -0
- package/dist/adapters/charts/surfaces.d.ts.map +1 -1
- package/dist/adapters/charts/tables.d.ts +21 -11
- package/dist/adapters/charts/tables.d.ts.map +1 -1
- package/dist/adapters/charts/tables.js +17 -6
- package/dist/adapters/charts/tables.js.map +1 -1
- package/dist/adapters/charts/undrawable.d.ts +2 -1
- package/dist/adapters/charts/undrawable.d.ts.map +1 -1
- package/dist/adapters/charts/undrawable.js +24 -14
- package/dist/adapters/charts/undrawable.js.map +1 -1
- package/dist/core/stdlib/volume/accumulation.d.ts +14 -0
- package/dist/core/stdlib/volume/accumulation.d.ts.map +1 -1
- package/dist/core/stdlib/volume/accumulation.js +32 -2
- package/dist/core/stdlib/volume/accumulation.js.map +1 -1
- package/dist/core/stdlib/volume/flow.d.ts.map +1 -1
- package/dist/core/stdlib/volume/flow.js +3 -1
- package/dist/core/stdlib/volume/flow.js.map +1 -1
- package/dist/core/stdlib/volume/weighted.d.ts +7 -0
- package/dist/core/stdlib/volume/weighted.d.ts.map +1 -1
- package/dist/core/stdlib/volume/weighted.js +15 -3
- package/dist/core/stdlib/volume/weighted.js.map +1 -1
- package/dist/core/version/version.generated.d.ts +1 -1
- package/dist/core/version/version.generated.js +1 -1
- package/package.json +1 -1
- package/src/adapters/charts/capabilities.ts +103 -0
- package/src/adapters/charts/columns.ts +5 -0
- package/src/adapters/charts/contract.ts +36 -2
- package/src/adapters/charts/descriptor.ts +21 -6
- package/src/adapters/charts/fills.ts +95 -11
- package/src/adapters/charts/index.ts +9 -1
- package/src/adapters/charts/produced.ts +4 -3
- package/src/adapters/charts/run.ts +25 -1
- package/src/adapters/charts/surfaces.ts +12 -0
- package/src/adapters/charts/tables.ts +31 -14
- package/src/adapters/charts/undrawable.ts +24 -14
- package/src/core/stdlib/volume/accumulation.ts +37 -2
- package/src/core/stdlib/volume/flow.ts +3 -1
- package/src/core/stdlib/volume/weighted.ts +14 -3
- package/src/core/version/version.generated.ts +1 -1
|
@@ -2,23 +2,40 @@
|
|
|
2
2
|
* Declared bands into the chart's shaded bands.
|
|
3
3
|
*
|
|
4
4
|
* A band is two plot keys and two colours, which is the chart's own spec, so the
|
|
5
|
-
* pair of keys crosses unchanged.
|
|
5
|
+
* pair of keys crosses unchanged. Three details do not.
|
|
6
6
|
*
|
|
7
7
|
* **Opacity is a dimmer, not a second alpha.** A colour carries its own alpha
|
|
8
8
|
* everywhere in the language, and the band's `opacity` multiplies whatever the
|
|
9
|
-
* colours already are. That is exactly what the chart does with the field,
|
|
10
|
-
*
|
|
9
|
+
* colours already are. That is exactly what the chart does with the field, to a
|
|
10
|
+
* colour computed per bar as much as to a constant one, so a script that never
|
|
11
|
+
* touches it gets the colour it wrote.
|
|
11
12
|
*
|
|
12
13
|
* **A band with no colour of its own follows the first plot.** `stdlib.md` 14.2
|
|
13
14
|
* says such a band is that plot's colour faded to twelve percent, and the plot's
|
|
14
15
|
* colour may be one the host picked from its palette rather than one the script
|
|
15
16
|
* wrote, so the band is pointed at the plot's settings key rather than at a value
|
|
16
17
|
* read now. The twelve percent is then the dimmer, multiplied by whatever the
|
|
17
|
-
* script asked for.
|
|
18
|
+
* script asked for. A band whose colour is computed per bar has a colour of its
|
|
19
|
+
* own, so neither applies to it.
|
|
20
|
+
*
|
|
21
|
+
* **A colour computed per bar travels as columns and is read back per bar.**
|
|
22
|
+
* It arrives on a channel, rides the values table as the two columns
|
|
23
|
+
* `columns.ts` describes, and the band's colour callback reads them on each bar
|
|
24
|
+
* for the side the band is on there. The side is decided the way the chart
|
|
25
|
+
* decides it, the first plot at or above the second, so the colour answered is
|
|
26
|
+
* the colour of the run the chart is drawing that bar in. An absent colour is a
|
|
27
|
+
* transparent one rather than no answer, because no answer hands the bar to
|
|
28
|
+
* the band's colour for that side, and `docs/visuals/fills.md` teaches an absent
|
|
29
|
+
* colour as how a band switches itself off. The callback exists only on a chart
|
|
30
|
+
* that reads it (`capabilities.ts`); on any other, `undrawable.ts` has refused
|
|
31
|
+
* the program before this is drawn.
|
|
18
32
|
*/
|
|
19
33
|
import type { Band, CompiledProgram } from '../../core/emit/index.js';
|
|
34
|
+
import type { ChartCapabilities } from './capabilities.js';
|
|
20
35
|
import { cssColour } from './colours.js';
|
|
21
|
-
import
|
|
36
|
+
import { bandColourKey, colourAt, colourColumns } from './columns.js';
|
|
37
|
+
import type { ColumnSpec } from './columns.js';
|
|
38
|
+
import type { ChartFill, ChartFillContext } from './contract.js';
|
|
22
39
|
import { boolField, colourField, isReference, numberField } from './fields.js';
|
|
23
40
|
import type { InputLookup } from './fields.js';
|
|
24
41
|
|
|
@@ -30,17 +47,67 @@ import type { InputLookup } from './fields.js';
|
|
|
30
47
|
*/
|
|
31
48
|
const UNCOLOURED_FADE = 0.12;
|
|
32
49
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
50
|
+
/** What a bar whose computed colour is absent is painted in: nothing. */
|
|
51
|
+
const UNPAINTED = 'rgba(0, 0, 0, 0)';
|
|
52
|
+
|
|
53
|
+
export interface FillsBuild {
|
|
54
|
+
readonly fills: readonly ChartFill[];
|
|
55
|
+
/** The colour columns the bands' callbacks read, for the values table. */
|
|
56
|
+
readonly columns: readonly ColumnSpec[];
|
|
37
57
|
}
|
|
38
58
|
|
|
39
|
-
function
|
|
59
|
+
export function buildFills(
|
|
60
|
+
program: CompiledProgram,
|
|
61
|
+
lookup: InputLookup,
|
|
62
|
+
chart: ChartCapabilities,
|
|
63
|
+
): FillsBuild {
|
|
64
|
+
const fills: ChartFill[] = [];
|
|
65
|
+
const columns: ColumnSpec[] = [];
|
|
66
|
+
program.outputs.fills.forEach((band, index) => {
|
|
67
|
+
const perBar = chart.bandColours ? perBarColours(band, index) : undefined;
|
|
68
|
+
if (perBar !== undefined) columns.push(...perBar.columns);
|
|
69
|
+
fills.push(oneBand(program, band, lookup, perBar));
|
|
70
|
+
});
|
|
71
|
+
return { fills, columns };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The keys a band's two computed colours travel under, and their columns. */
|
|
75
|
+
interface PerBar {
|
|
76
|
+
readonly up: string | undefined;
|
|
77
|
+
readonly down: string | undefined;
|
|
78
|
+
readonly columns: readonly ColumnSpec[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The columns a band's computed colours need, or nothing for a band with none.
|
|
83
|
+
*
|
|
84
|
+
* `color = ...` computed per bar writes one channel into both sides, and that
|
|
85
|
+
* channel travels once rather than twice.
|
|
86
|
+
*/
|
|
87
|
+
function perBarColours(band: Band, index: number): PerBar | undefined {
|
|
88
|
+
const upChannel = band.colorUpChannel;
|
|
89
|
+
const downChannel = band.colorDownChannel;
|
|
90
|
+
if (upChannel === null && downChannel === null) return undefined;
|
|
91
|
+
const up = upChannel === null ? undefined : bandColourKey(index, 'up');
|
|
92
|
+
const shared = downChannel !== null && downChannel === upChannel;
|
|
93
|
+
const down = downChannel === null ? undefined : shared ? up : bandColourKey(index, 'down');
|
|
94
|
+
const columns: ColumnSpec[] = [];
|
|
95
|
+
if (up !== undefined && upChannel !== null) columns.push(...colourColumns(up, upChannel));
|
|
96
|
+
if (down !== undefined && downChannel !== null && !shared) columns.push(...colourColumns(down, downChannel));
|
|
97
|
+
return { up, down, columns };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function oneBand(
|
|
101
|
+
program: CompiledProgram,
|
|
102
|
+
band: Band,
|
|
103
|
+
lookup: InputLookup,
|
|
104
|
+
perBar: PerBar | undefined,
|
|
105
|
+
): ChartFill {
|
|
40
106
|
const up = colourField(band.colorUp, lookup);
|
|
41
107
|
const down = colourField(band.colorDown, lookup);
|
|
42
108
|
const overlay = boolField(band.overlay, lookup);
|
|
43
|
-
const
|
|
109
|
+
const computed = band.colorUpChannel !== null || band.colorDownChannel !== null;
|
|
110
|
+
const declared = up !== undefined || down !== undefined || computed;
|
|
44
111
|
const opacity = numberField(band.opacity, lookup, 1) * (declared ? 1 : UNCOLOURED_FADE);
|
|
45
112
|
const follow = declared ? undefined : plotColourKey(program, band.between[0]);
|
|
46
113
|
|
|
@@ -53,6 +120,23 @@ function oneBand(program: CompiledProgram, band: Band, lookup: InputLookup): Cha
|
|
|
53
120
|
...(follow === undefined ? {} : { colorUpKey: follow, colorDownKey: follow }),
|
|
54
121
|
opacity,
|
|
55
122
|
...(overlay === undefined ? {} : { overlay }),
|
|
123
|
+
...(perBar === undefined ? {} : { colorBy: colourBy(perBar) }),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The band's colour on one bar: the computed colour for the side it is on.
|
|
129
|
+
*
|
|
130
|
+
* Nothing where either column is absent, which is a bar the chart does not
|
|
131
|
+
* draw, and nothing where that side's colour is not computed, which leaves the
|
|
132
|
+
* chart the band's own colour for the side.
|
|
133
|
+
*/
|
|
134
|
+
function colourBy(perBar: PerBar): (ctx: ChartFillContext) => string | undefined {
|
|
135
|
+
return (ctx: ChartFillContext): string | undefined => {
|
|
136
|
+
if (typeof ctx.a !== 'number' || typeof ctx.b !== 'number') return undefined;
|
|
137
|
+
const key = ctx.a >= ctx.b ? perBar.up : perBar.down;
|
|
138
|
+
if (key === undefined) return undefined;
|
|
139
|
+
return colourAt(ctx.values, ctx.index, key) ?? UNPAINTED;
|
|
56
140
|
};
|
|
57
141
|
}
|
|
58
142
|
|
|
@@ -16,10 +16,16 @@
|
|
|
16
16
|
* What is mapped is the whole output surface: the declared inputs and the
|
|
17
17
|
* settings dialog a chart generates from them, the plots with their styles and
|
|
18
18
|
* scales, the bands between them, the horizontal levels, the pane's fixed range,
|
|
19
|
-
* the markers, the candle and pane painting, the summary
|
|
19
|
+
* the markers, the candle and pane painting, the summary grids, the drawing
|
|
20
20
|
* objects a script mutates over time, the watched conditions, and the lifecycle
|
|
21
21
|
* a read of another instrument fetches through.
|
|
22
22
|
*
|
|
23
|
+
* **Two of those depend on the chart the host states.** A band coloured per bar
|
|
24
|
+
* and a study with more than one grid need hooks only a newer chart has, so a
|
|
25
|
+
* host passes the chart library's own `VERSION` as
|
|
26
|
+
* `ChartAdapterOptions.chartVersion`, and without it such a study is refused
|
|
27
|
+
* with OS6024 rather than drawn in part. `capabilities.ts` has the versions.
|
|
28
|
+
*
|
|
23
29
|
* What a study can express and this descriptor has no field for is recorded,
|
|
24
30
|
* with its reason, in `spec/chart-narrowings.json`, and each one is also named
|
|
25
31
|
* in the file that would have written it. `scripts/check-chart-surface.mjs`
|
|
@@ -41,6 +47,7 @@ export type {
|
|
|
41
47
|
ChartCalcContext,
|
|
42
48
|
ChartDescriptor,
|
|
43
49
|
ChartFill,
|
|
50
|
+
ChartFillContext,
|
|
44
51
|
ChartInput,
|
|
45
52
|
ChartLevel,
|
|
46
53
|
ChartLevelContext,
|
|
@@ -75,4 +82,5 @@ export type {
|
|
|
75
82
|
ChartSurfaceContext,
|
|
76
83
|
ChartTableOptions,
|
|
77
84
|
ChartTablePosition,
|
|
85
|
+
ChartTableSpec,
|
|
78
86
|
} from './surfaces.js';
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import type { Value } from '../../core/engine/index.js';
|
|
34
34
|
import type { ChartSettings } from './contract.js';
|
|
35
|
-
import type { ChartDrawing,
|
|
35
|
+
import type { ChartDrawing, ChartMarker, ChartTableSpec } from './surfaces.js';
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* One declared alert's message channel, whole, as the run left it.
|
|
@@ -47,13 +47,14 @@ export type MessageColumn = readonly Value[];
|
|
|
47
47
|
/** One run's non-numeric output, as the hooks that follow it read it. */
|
|
48
48
|
export interface Produced {
|
|
49
49
|
readonly markers: readonly ChartMarker[];
|
|
50
|
-
|
|
50
|
+
/** Every declared grid, in declaration order; the single hook reads the first. */
|
|
51
|
+
readonly tables: readonly ChartTableSpec[];
|
|
51
52
|
readonly drawings: readonly ChartDrawing[];
|
|
52
53
|
/** One entry per declared alert, in the order `outputs.alerts` declares them. */
|
|
53
54
|
readonly messages: readonly MessageColumn[];
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
const NOTHING: Produced = { markers: [],
|
|
57
|
+
const NOTHING: Produced = { markers: [], tables: [], drawings: [], messages: [] };
|
|
57
58
|
|
|
58
59
|
const runs = new WeakMap<ChartSettings, Produced>();
|
|
59
60
|
|
|
@@ -38,6 +38,7 @@ import type { CompiledProgram } from '../../core/emit/index.js';
|
|
|
38
38
|
import type { SourceFile } from '../../core/index.js';
|
|
39
39
|
import { hostBar, hostNow, stateFor } from './bars.js';
|
|
40
40
|
import type { ChartBar, ChartCalcContext, ChartSettings, ChartStore } from './contract.js';
|
|
41
|
+
import { capabilitiesOf } from './capabilities.js';
|
|
41
42
|
import { refused, stopped } from './errors.js';
|
|
42
43
|
import { undrawable } from './undrawable.js';
|
|
43
44
|
import { stationIn, stationOf } from './requests.js';
|
|
@@ -60,6 +61,29 @@ export interface ChartAdapterOptions {
|
|
|
60
61
|
readonly id?: string;
|
|
61
62
|
/** The category a picker groups the study under, when `meta.group` is empty. */
|
|
62
63
|
readonly category?: string;
|
|
64
|
+
/**
|
|
65
|
+
* The chart library's own version: the `VERSION` string it exports, which a
|
|
66
|
+
* host that imports the chart already holds, passed on as
|
|
67
|
+
* `descriptorFor(program, { chartVersion: VERSION })`.
|
|
68
|
+
*
|
|
69
|
+
* It says which of the descriptor's hooks the chart this descriptor is
|
|
70
|
+
* registered with will read. Two arrived after the oldest chart the peer
|
|
71
|
+
* range accepts: a band's per-bar colour and the list of grids. From 2.5.4
|
|
72
|
+
* on, a band whose colour the script computes is drawn bar by bar and every
|
|
73
|
+
* declared grid is drawn. Before it, or when nothing is stated, the adapter
|
|
74
|
+
* cannot tell whether the chart will read either hook, and a chart that
|
|
75
|
+
* ignores one draws a band in its own default colours or the first grid
|
|
76
|
+
* alone, with nothing said. So such a program is refused before any bar runs
|
|
77
|
+
* with OS6024, which is what a host that states nothing got before this
|
|
78
|
+
* option existed.
|
|
79
|
+
*
|
|
80
|
+
* The version rather than a switch per hook, because it is one fact the
|
|
81
|
+
* host already holds, it moves with the chart the host actually installed,
|
|
82
|
+
* and a hook the adapter learns to read later needs nothing new from any
|
|
83
|
+
* host. `capabilities.ts` has the rule, prereleases and unreadable strings
|
|
84
|
+
* included.
|
|
85
|
+
*/
|
|
86
|
+
readonly chartVersion?: string;
|
|
63
87
|
/**
|
|
64
88
|
* What the host has stored for this study's inputs, for the declared shape.
|
|
65
89
|
*
|
|
@@ -338,7 +362,7 @@ function start(
|
|
|
338
362
|
|
|
339
363
|
// What this chart has no room for is refused before the engine is asked,
|
|
340
364
|
// because a study drawn without it is a study that looks broken.
|
|
341
|
-
const narrower = undrawable(program);
|
|
365
|
+
const narrower = undrawable(program, capabilitiesOf(options.chartVersion));
|
|
342
366
|
if (narrower !== undefined) throw refused(narrower);
|
|
343
367
|
|
|
344
368
|
const loaded = load(program, {
|
|
@@ -125,6 +125,18 @@ export interface ChartGrid {
|
|
|
125
125
|
readonly options?: ChartTableOptions;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* One grid of several, under an identity the chart keeps it by.
|
|
130
|
+
*
|
|
131
|
+
* The chart reuses the grid a recompute names again and removes one it no
|
|
132
|
+
* longer names, so the id is the declaration's key: stable across recomputes,
|
|
133
|
+
* never empty, and unique within a program because the compiler writes one per
|
|
134
|
+
* declaration.
|
|
135
|
+
*/
|
|
136
|
+
export interface ChartTableSpec extends ChartGrid {
|
|
137
|
+
readonly id: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
128
140
|
/** One end of a drawing: a time on the shared axis, a price on the pane's scale. */
|
|
129
141
|
export interface ChartAnchor {
|
|
130
142
|
readonly time: number;
|
|
@@ -27,13 +27,17 @@
|
|
|
27
27
|
* placed into it, which is what `language.md` 6.7 asks for from the other
|
|
28
28
|
* direction: a cell nothing was written into is blank, never a zero.
|
|
29
29
|
*
|
|
30
|
-
* **
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* grid
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
30
|
+
* **The language declares as many grids as it likes, and a chart with the list
|
|
31
|
+
* of grids draws them all** (`capabilities.ts` says which chart has it). The
|
|
32
|
+
* list holds each grid under an id the chart keeps it by, and every declared
|
|
33
|
+
* grid is built for it here under its declaration's key. The single `table`
|
|
34
|
+
* hook stays beside the list with the first grid in it, because a chart
|
|
35
|
+
* without the list reads that one. Merging two grids into the single hook
|
|
36
|
+
* would put cells somewhere the script never asked for, so on a chart without
|
|
37
|
+
* the list a program declaring a second grid never reaches this file:
|
|
38
|
+
* `undrawable.ts` refuses it before any bar runs, with OS6024.
|
|
39
|
+
* `spec/chart-narrowings.json` records both halves and
|
|
40
|
+
* `scripts/check-chart-surface.mjs` proves both with a study that declares two.
|
|
37
41
|
*/
|
|
38
42
|
import type { CompiledProgram, Grid as DeclaredGrid } from '../../core/emit/index.js';
|
|
39
43
|
import type { Grid, GridCell } from '../../core/engine/index.js';
|
|
@@ -45,6 +49,7 @@ import type {
|
|
|
45
49
|
ChartCellAlign,
|
|
46
50
|
ChartGrid,
|
|
47
51
|
ChartTablePosition,
|
|
52
|
+
ChartTableSpec,
|
|
48
53
|
} from './surfaces.js';
|
|
49
54
|
|
|
50
55
|
/** The language's four corners, in the chart's own words. */
|
|
@@ -58,22 +63,34 @@ const CORNERS: Readonly<Record<string, ChartTablePosition>> = {
|
|
|
58
63
|
const ALIGNMENTS: readonly string[] = ['left', 'center', 'right'];
|
|
59
64
|
|
|
60
65
|
/**
|
|
61
|
-
*
|
|
66
|
+
* Every grid a chart draws, from the declarations and the engine's buffers.
|
|
62
67
|
*
|
|
63
|
-
*
|
|
68
|
+
* Each buffer is paired with its declaration by the key both of them carry
|
|
64
69
|
* rather than by position. The engine reads its grids in declaration order, so
|
|
65
70
|
* the two agree today, and a pairing by position is one reordering away from
|
|
66
71
|
* drawing one grid's cells into another grid's shape, which would be a wrong
|
|
67
72
|
* table that looks like a right one.
|
|
73
|
+
*
|
|
74
|
+
* The key is the id as well. The compiler writes one per declaration and never
|
|
75
|
+
* an empty one, and it does not move between recomputes, which is what lets the
|
|
76
|
+
* chart keep a grid rather than build it again on every tick.
|
|
68
77
|
*/
|
|
69
|
-
export function
|
|
78
|
+
export function buildTables(
|
|
70
79
|
program: CompiledProgram,
|
|
71
80
|
lookup: InputLookup,
|
|
72
81
|
written: readonly Grid[],
|
|
73
|
-
):
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
): readonly ChartTableSpec[] {
|
|
83
|
+
return program.outputs.tables.map((declared) => ({
|
|
84
|
+
id: declared.key,
|
|
85
|
+
...oneTable(declared, lookup, written.find((one) => one.key === declared.key)),
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The first grid alone, for the single hook a chart without the list reads. */
|
|
90
|
+
export function firstGrid(tables: readonly ChartTableSpec[]): ChartGrid | null {
|
|
91
|
+
const first = tables[0];
|
|
92
|
+
if (first === undefined) return null;
|
|
93
|
+
return { rows: first.rows, ...(first.options === undefined ? {} : { options: first.options }) };
|
|
77
94
|
}
|
|
78
95
|
|
|
79
96
|
function oneTable(
|
|
@@ -3,17 +3,24 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `compiled-program.md` section 11: a host draws what its surface has room for,
|
|
5
5
|
* and refuses what it does not with OS6024 rather than drawing part of a study
|
|
6
|
-
* and saying nothing. Two things a version 1 program can declare have
|
|
7
|
-
* on
|
|
6
|
+
* and saying nothing. Two things a version 1 program can declare have a place
|
|
7
|
+
* on a newer chart and none on an older one (`capabilities.ts` says which is
|
|
8
|
+
* which), and on an older one both used to be dropped in silence.
|
|
8
9
|
*
|
|
9
|
-
* - **A second grid.**
|
|
10
|
-
* a pane. Merging two into it would put cells somewhere the script
|
|
11
|
-
* asked for, and drawing the first alone is a study whose second panel
|
|
12
|
-
* appears and whose cells look broken.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* colour the script did not choose.
|
|
10
|
+
* - **A second grid.** An older descriptor has one `table` hook, so one grid
|
|
11
|
+
* reaches a pane. Merging two into it would put cells somewhere the script
|
|
12
|
+
* never asked for, and drawing the first alone is a study whose second panel
|
|
13
|
+
* never appears and whose cells look broken. A newer chart's list of grids
|
|
14
|
+
* carries every one (`tables.ts`).
|
|
15
|
+
* - **A band colour computed per bar.** An older chart's band takes one colour
|
|
16
|
+
* for each side for the whole run, and a band whose colour the script
|
|
17
|
+
* computes would be drawn in a colour the script did not choose. A newer
|
|
18
|
+
* chart's band has a per-bar colour callback (`fills.ts`).
|
|
19
|
+
*
|
|
20
|
+
* Which chart the descriptor is registered with is what the host states, and
|
|
21
|
+
* `capabilities.ts` reads it. A host that states nothing is refused both, as
|
|
22
|
+
* before, because a chart that ignores a hook it does not know draws the
|
|
23
|
+
* silent version of each.
|
|
17
24
|
*
|
|
18
25
|
* The compiled program carries no source position for a declaration, so the
|
|
19
26
|
* refusal names the declaration by its title, which is what a reader sees in
|
|
@@ -22,23 +29,26 @@
|
|
|
22
29
|
import { diagnosticFor, makeSpan } from '../../core/index.js';
|
|
23
30
|
import type { Diagnostic } from '../../core/index.js';
|
|
24
31
|
import type { CompiledProgram, Field } from '../../core/emit/index.js';
|
|
32
|
+
import { lacking } from './capabilities.js';
|
|
33
|
+
import type { ChartCapabilities } from './capabilities.js';
|
|
25
34
|
|
|
26
35
|
/** A load refusal has no line of its own: the program is what was refused. */
|
|
27
36
|
const AT_LOAD = makeSpan(0, 0, 0, 0);
|
|
28
37
|
|
|
29
|
-
export function undrawable(program: CompiledProgram): Diagnostic | undefined {
|
|
38
|
+
export function undrawable(program: CompiledProgram, chart: ChartCapabilities): Diagnostic | undefined {
|
|
30
39
|
const second = program.outputs.tables[1];
|
|
31
|
-
if (second !== undefined) {
|
|
40
|
+
if (second !== undefined && !chart.grids) {
|
|
32
41
|
return diagnosticFor('OS6024', AT_LOAD, {
|
|
33
42
|
what: `the table ${titled(second.title, 'after the first')}, the second grid this study declares`,
|
|
34
|
-
limit: 'a chart pane draws one grid',
|
|
43
|
+
limit: lacking(chart, 'grids', 'a chart pane draws one grid'),
|
|
35
44
|
});
|
|
36
45
|
}
|
|
46
|
+
if (chart.bandColours) return undefined;
|
|
37
47
|
for (const band of program.outputs.fills) {
|
|
38
48
|
if (band.colorUpChannel !== null || band.colorDownChannel !== null) {
|
|
39
49
|
return diagnosticFor('OS6024', AT_LOAD, {
|
|
40
50
|
what: "a band's colour that is computed per bar",
|
|
41
|
-
limit: "a chart's band takes one colour for the whole run",
|
|
51
|
+
limit: lacking(chart, 'bandColours', "a chart's band takes one colour for the whole run"),
|
|
42
52
|
});
|
|
43
53
|
}
|
|
44
54
|
}
|
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
* A running total is the one thing a lookback cannot stand in for, so each of
|
|
11
11
|
* these carries its total in the state region rather than in a closure: that is
|
|
12
12
|
* what lets an engine roll one back with the rest of a moving bar's state.
|
|
13
|
+
*
|
|
14
|
+
* A bar's term is checked after every operation that forms it
|
|
15
|
+
* (`compiled-program.md` section 3.1), and a term that is not finite is an absent
|
|
16
|
+
* term: the bar is absent and the total is left where it was, so one overflowing
|
|
17
|
+
* bar costs its own reading and nothing after it. A total that overflows although
|
|
18
|
+
* its term was finite is kept as the arithmetic produced it, and every later
|
|
19
|
+
* reading is absent, which is what `stdlib.md` section 20.6 says of every running
|
|
20
|
+
* total there.
|
|
13
21
|
*/
|
|
14
22
|
import type { Bar, StateRecord, Tail, Value } from '../values/index.js';
|
|
15
23
|
import { NONE, fold, held, isPresent, result, slot, tailOf } from '../values/index.js';
|
|
@@ -22,11 +30,18 @@ import { emaStep } from '../averages/index.js';
|
|
|
22
30
|
* A bar whose high and low are equal has no position inside it to report, and
|
|
23
31
|
* the settled treatment is that such a bar contributes nothing rather than
|
|
24
32
|
* ending the running total.
|
|
33
|
+
*
|
|
34
|
+
* A span that overflows is not that bar. It is absent (`compiled-program.md`
|
|
35
|
+
* section 3.1), and so is the term, because the one later step that could hide
|
|
36
|
+
* it is this division: a finite numerator over an infinite span is an exact
|
|
37
|
+
* zero, a term for a bar whose position was never computed. Every other step
|
|
38
|
+
* that overflows stays non-finite to the end and the last check catches it.
|
|
25
39
|
*/
|
|
26
40
|
export function moneyFlow(bar: Bar): Value {
|
|
27
41
|
if (!isPresent(bar.high) || !isPresent(bar.low)) return NONE;
|
|
28
42
|
if (!isPresent(bar.close) || !isPresent(bar.volume)) return NONE;
|
|
29
|
-
const span = bar.high - bar.low;
|
|
43
|
+
const span = result(bar.high - bar.low);
|
|
44
|
+
if (!isPresent(span)) return NONE;
|
|
30
45
|
if (!(span > 0)) return 0;
|
|
31
46
|
return result((((bar.close - bar.low) - (bar.high - bar.close)) / span) * bar.volume);
|
|
32
47
|
}
|
|
@@ -115,6 +130,24 @@ export function adOsc(bars: readonly Bar[], fast = 3, slow = 10): Value[] {
|
|
|
115
130
|
return fold(adOscTail(fast, slow), bars);
|
|
116
131
|
}
|
|
117
132
|
|
|
133
|
+
/**
|
|
134
|
+
* The per-bar term `pvt` adds: the change, then the proportion, then the
|
|
135
|
+
* product with the volume, each checked as it is formed.
|
|
136
|
+
*
|
|
137
|
+
* A change can overflow although the true proportion is finite: a close of
|
|
138
|
+
* -1e308 after one of 1e308 is a proportion of -2. Section 3.1 makes that change
|
|
139
|
+
* absent, and so the term. Each step is checked where it rounds because that is
|
|
140
|
+
* how the rule is stated, not because a later step could turn an infinity back
|
|
141
|
+
* into a number here: none of these three can.
|
|
142
|
+
*/
|
|
143
|
+
function trendTerm(close: number, before: number, volume: number): Value {
|
|
144
|
+
const change = result(close - before);
|
|
145
|
+
if (!isPresent(change)) return NONE;
|
|
146
|
+
const proportion = result(change / before);
|
|
147
|
+
if (!isPresent(proportion)) return NONE;
|
|
148
|
+
return result(proportion * volume);
|
|
149
|
+
}
|
|
150
|
+
|
|
118
151
|
/**
|
|
119
152
|
* `pvt()`: the running total of volume weighted by percentage change, from bar
|
|
120
153
|
* 1, seeded 0.
|
|
@@ -136,7 +169,9 @@ export function pvtStep(state: StateRecord, key: string, bar: Bar): Value {
|
|
|
136
169
|
if (!started) return NONE;
|
|
137
170
|
if (!isPresent(before) || before === 0) return NONE;
|
|
138
171
|
if (!isPresent(bar.close) || !isPresent(bar.volume)) return NONE;
|
|
139
|
-
const
|
|
172
|
+
const term = trendTerm(bar.close, before, bar.volume);
|
|
173
|
+
if (!isPresent(term)) return NONE;
|
|
174
|
+
const total = slot(state, totalKey, 0) + term;
|
|
140
175
|
state[totalKey] = total;
|
|
141
176
|
return result(total);
|
|
142
177
|
}
|
|
@@ -21,7 +21,9 @@ export function cmfStep(
|
|
|
21
21
|
): Value {
|
|
22
22
|
const flow = sumStep(state, `${key}f`, moneyFlow(bar), len);
|
|
23
23
|
const traded = sumStep(state, `${key}v`, bar.volume, len);
|
|
24
|
-
|
|
24
|
+
// A ratio is absent where its divisor is zero, and only there: a window whose
|
|
25
|
+
// volume sums below zero divides like any other.
|
|
26
|
+
if (!isPresent(flow) || !isPresent(traded) || traded === 0) return NONE;
|
|
25
27
|
return result(flow / traded);
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -30,6 +30,13 @@ export interface Anchored {
|
|
|
30
30
|
* The bar the condition holds on is the first bar of the new average, not the
|
|
31
31
|
* last bar of the old one. Absent before the condition has ever been true,
|
|
32
32
|
* because there is no anchor to measure from and zero would read as a price.
|
|
33
|
+
*
|
|
34
|
+
* A price times volume that overflows is an absent term (`compiled-program.md`
|
|
35
|
+
* section 3.1): the bar is absent and neither total moves, so the bar costs its
|
|
36
|
+
* own reading and nothing after it. A total that overflows is kept as the
|
|
37
|
+
* arithmetic produced it and is absent, so the average divided by it is absent
|
|
38
|
+
* until the next anchor rather than the exact zero a finite flow over an
|
|
39
|
+
* infinite volume would give.
|
|
33
40
|
*/
|
|
34
41
|
export function vwapAnchorStep(state: StateRecord, key: string, input: Anchored): Value {
|
|
35
42
|
const flowKey = `${key}f`;
|
|
@@ -42,12 +49,16 @@ export function vwapAnchorStep(state: StateRecord, key: string, input: Anchored)
|
|
|
42
49
|
}
|
|
43
50
|
if (!flag(state, anchoredKey)) return NONE;
|
|
44
51
|
if (!isPresent(input.src) || !isPresent(input.volume)) return NONE;
|
|
45
|
-
const
|
|
52
|
+
const term = result(input.src * input.volume);
|
|
53
|
+
if (!isPresent(term)) return NONE;
|
|
54
|
+
const flow = slot(state, flowKey, 0) + term;
|
|
46
55
|
const traded = slot(state, tradedKey, 0) + input.volume;
|
|
47
56
|
state[flowKey] = flow;
|
|
48
57
|
state[tradedKey] = traded;
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
const numerator = result(flow);
|
|
59
|
+
const divisor = result(traded);
|
|
60
|
+
if (!isPresent(numerator) || !isPresent(divisor) || divisor === 0) return NONE;
|
|
61
|
+
return result(numerator / divisor);
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
/** `vwapAnchor(src, resetWhen)` as a tail. */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// spec/compiled-program.md, so neither can drift from the thing it names.
|
|
5
5
|
|
|
6
6
|
/** This build's package version. The release refuses to publish unless the tag, the manifest and this agree. */
|
|
7
|
-
export const VERSION = "0.
|
|
7
|
+
export const VERSION = "0.8.0";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The compiled program format this compiler emits.
|