openalgo-script 0.4.0 → 0.5.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 +929 -0
- package/README.md +69 -16
- package/dist/adapters/charts/driving.d.ts +50 -0
- package/dist/adapters/charts/driving.d.ts.map +1 -0
- package/dist/adapters/charts/driving.js +57 -0
- package/dist/adapters/charts/driving.js.map +1 -0
- package/dist/adapters/charts/run.d.ts +20 -0
- package/dist/adapters/charts/run.d.ts.map +1 -1
- package/dist/adapters/charts/run.js +83 -16
- package/dist/adapters/charts/run.js.map +1 -1
- package/dist/adapters/charts/venue.d.ts +73 -0
- package/dist/adapters/charts/venue.d.ts.map +1 -0
- package/dist/adapters/charts/venue.js +104 -0
- package/dist/adapters/charts/venue.js.map +1 -0
- package/dist/core/accounting/analysis.d.ts +111 -0
- package/dist/core/accounting/analysis.d.ts.map +1 -0
- package/dist/core/accounting/analysis.js +123 -0
- package/dist/core/accounting/analysis.js.map +1 -0
- package/dist/core/accounting/equity.d.ts +33 -0
- package/dist/core/accounting/equity.d.ts.map +1 -1
- package/dist/core/accounting/equity.js +12 -0
- package/dist/core/accounting/equity.js.map +1 -1
- package/dist/core/accounting/index.d.ts +2 -0
- package/dist/core/accounting/index.d.ts.map +1 -1
- package/dist/core/accounting/index.js +1 -0
- package/dist/core/accounting/index.js.map +1 -1
- package/dist/core/accounting/report.d.ts +3 -0
- package/dist/core/accounting/report.d.ts.map +1 -1
- package/dist/core/accounting/report.js +2 -0
- package/dist/core/accounting/report.js.map +1 -1
- package/dist/core/accounting/statistics.d.ts +12 -0
- package/dist/core/accounting/statistics.d.ts.map +1 -1
- package/dist/core/accounting/statistics.js +23 -1
- package/dist/core/accounting/statistics.js.map +1 -1
- package/dist/core/backtest/case.d.ts +60 -0
- package/dist/core/backtest/case.d.ts.map +1 -0
- package/dist/core/backtest/case.js +319 -0
- package/dist/core/backtest/case.js.map +1 -0
- package/dist/core/backtest/compare.d.ts.map +1 -1
- package/dist/core/backtest/compare.js +2 -0
- package/dist/core/backtest/compare.js.map +1 -1
- package/dist/core/backtest/deliver.d.ts +93 -0
- package/dist/core/backtest/deliver.d.ts.map +1 -0
- package/dist/core/backtest/deliver.js +94 -0
- package/dist/core/backtest/deliver.js.map +1 -0
- package/dist/core/backtest/drive.d.ts +66 -8
- package/dist/core/backtest/drive.d.ts.map +1 -1
- package/dist/core/backtest/drive.js +109 -59
- package/dist/core/backtest/drive.js.map +1 -1
- package/dist/core/backtest/index.d.ts +24 -12
- package/dist/core/backtest/index.d.ts.map +1 -1
- package/dist/core/backtest/index.js +21 -10
- package/dist/core/backtest/index.js.map +1 -1
- package/dist/core/backtest/record.d.ts +63 -2
- package/dist/core/backtest/record.d.ts.map +1 -1
- package/dist/core/backtest/record.js +71 -4
- package/dist/core/backtest/record.js.map +1 -1
- package/dist/core/backtest/replay.d.ts.map +1 -1
- package/dist/core/backtest/replay.js +11 -1
- package/dist/core/backtest/replay.js.map +1 -1
- package/dist/core/backtest/simulate.d.ts +113 -1
- package/dist/core/backtest/simulate.d.ts.map +1 -1
- package/dist/core/backtest/simulate.js +123 -5
- package/dist/core/backtest/simulate.js.map +1 -1
- package/dist/core/catalogue/catalogue.generated.d.ts +1 -1
- package/dist/core/catalogue/catalogue.generated.js +1 -1
- package/dist/core/catalogue/catalogue.generated.js.map +1 -1
- package/dist/core/check/library-orders.js +2 -2
- package/dist/core/check/library-orders.js.map +1 -1
- package/dist/core/check/library-prose.generated.js +2 -2
- package/dist/core/check/library-prose.generated.js.map +1 -1
- package/dist/core/emit/canonical.d.ts +22 -8
- package/dist/core/emit/canonical.d.ts.map +1 -1
- package/dist/core/emit/canonical.js +67 -6
- package/dist/core/emit/canonical.js.map +1 -1
- package/dist/core/engine/arithmetic.d.ts +6 -25
- package/dist/core/engine/arithmetic.d.ts.map +1 -1
- package/dist/core/engine/arithmetic.js +48 -3
- package/dist/core/engine/arithmetic.js.map +1 -1
- package/dist/core/engine/index.d.ts +1 -1
- package/dist/core/engine/index.d.ts.map +1 -1
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/index.js.map +1 -1
- package/dist/core/engine/library/arrays.d.ts.map +1 -1
- package/dist/core/engine/library/arrays.js +8 -2
- package/dist/core/engine/library/arrays.js.map +1 -1
- package/dist/core/engine/library/code-points.d.ts +40 -0
- package/dist/core/engine/library/code-points.d.ts.map +1 -0
- package/dist/core/engine/library/code-points.js +74 -0
- package/dist/core/engine/library/code-points.js.map +1 -0
- package/dist/core/engine/library/index.d.ts +5 -0
- package/dist/core/engine/library/index.d.ts.map +1 -1
- package/dist/core/engine/library/index.js +5 -0
- package/dist/core/engine/library/index.js.map +1 -1
- package/dist/core/engine/library/text.d.ts.map +1 -1
- package/dist/core/engine/library/text.js +51 -34
- package/dist/core/engine/library/text.js.map +1 -1
- package/dist/core/engine/load.d.ts +20 -0
- package/dist/core/engine/load.d.ts.map +1 -1
- package/dist/core/engine/load.js +62 -0
- package/dist/core/engine/load.js.map +1 -1
- package/dist/core/engine/verify-tables.d.ts.map +1 -1
- package/dist/core/engine/verify-tables.js +41 -0
- package/dist/core/engine/verify-tables.js.map +1 -1
- package/dist/core/index.d.ts +10 -5
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +8 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/stdlib/index.d.ts +1 -1
- package/dist/core/stdlib/index.d.ts.map +1 -1
- package/dist/core/stdlib/index.js +1 -1
- package/dist/core/stdlib/index.js.map +1 -1
- package/dist/core/stdlib/maths/index.d.ts +1 -1
- package/dist/core/stdlib/maths/index.d.ts.map +1 -1
- package/dist/core/stdlib/maths/index.js +1 -1
- package/dist/core/stdlib/maths/index.js.map +1 -1
- package/dist/core/stdlib/maths/rounding.d.ts +5 -0
- package/dist/core/stdlib/maths/rounding.d.ts.map +1 -1
- package/dist/core/stdlib/maths/rounding.js +19 -1
- package/dist/core/stdlib/maths/rounding.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 +14 -2
- package/spec/README.md +2 -1
- package/spec/errors.json +3 -3
- package/src/adapters/charts/driving.ts +109 -0
- package/src/adapters/charts/run.ts +120 -28
- package/src/adapters/charts/venue.ts +132 -0
- package/src/core/accounting/analysis.ts +188 -0
- package/src/core/accounting/equity.ts +38 -0
- package/src/core/accounting/index.ts +2 -0
- package/src/core/accounting/report.ts +5 -0
- package/src/core/accounting/statistics.ts +38 -1
- package/src/core/backtest/case.ts +395 -0
- package/src/core/backtest/compare.ts +2 -0
- package/src/core/backtest/deliver.ts +161 -0
- package/src/core/backtest/drive.ts +175 -71
- package/src/core/backtest/index.ts +24 -12
- package/src/core/backtest/record.ts +134 -5
- package/src/core/backtest/replay.ts +11 -1
- package/src/core/backtest/simulate.ts +201 -6
- package/src/core/catalogue/catalogue.generated.ts +1 -1
- package/src/core/check/library-orders.ts +2 -2
- package/src/core/check/library-prose.generated.ts +2 -2
- package/src/core/emit/canonical.ts +67 -9
- package/src/core/engine/arithmetic.ts +23 -3
- package/src/core/engine/index.ts +1 -1
- package/src/core/engine/library/arrays.ts +8 -2
- package/src/core/engine/library/code-points.ts +73 -0
- package/src/core/engine/library/index.ts +6 -0
- package/src/core/engine/library/text.ts +53 -35
- package/src/core/engine/load.ts +68 -0
- package/src/core/engine/verify-tables.ts +43 -0
- package/src/core/index.ts +16 -2
- package/src/core/stdlib/index.ts +1 -0
- package/src/core/stdlib/maths/index.ts +1 -0
- package/src/core/stdlib/maths/rounding.ts +23 -1
- package/src/core/version/version.generated.ts +1 -1
|
@@ -8,34 +8,50 @@
|
|
|
8
8
|
* one element, or two engines will disagree about the length of a string
|
|
9
9
|
* holding a symbol outside the basic plane and about every substring taken
|
|
10
10
|
* after one. That is what the spread into an array is doing everywhere below:
|
|
11
|
-
* it costs a pass over the string and buys agreement.
|
|
11
|
+
* it costs a pass over the string and buys agreement. The trimmed set and the
|
|
12
|
+
* order of two strings are in `code-points.ts`, written from the page.
|
|
12
13
|
*
|
|
13
14
|
* **Number to string is specified, not the host's default.** `text(x)` is the
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* reader of a price expects.
|
|
15
|
+
* rule of `language.md` 5.5 and goes through the one writer that implements
|
|
16
|
+
* it, `canonicalNumber`, as does every digit this file writes: a host's own
|
|
17
|
+
* conversion is never asked. `text(x, d)` rounds to `d` decimals with halves
|
|
18
|
+
* away from zero and always emits exactly `d` digits after the point, because
|
|
19
|
+
* this is a display conversion and half up is what a reader of a price expects.
|
|
19
20
|
*/
|
|
20
|
-
import {
|
|
21
|
+
import { canonicalNumber } from '../../emit/index.js';
|
|
22
|
+
import { roundHalfAway, scaleOf } from '../../stdlib/index.js';
|
|
21
23
|
import type { Value } from '../values/index.js';
|
|
22
24
|
import { isColour, reference } from '../values/index.js';
|
|
23
25
|
import { entry, numberAt, refAt, stringAt, valueAt, wholeAt } from './binding.js';
|
|
24
26
|
import type { CallContext, ManifestEntry } from './binding.js';
|
|
27
|
+
import { points, trimmed } from './code-points.js';
|
|
25
28
|
|
|
26
29
|
/** A value as `text(x)` spells it. */
|
|
27
30
|
export function spell(ctx: CallContext, value: Value): string {
|
|
28
31
|
if (value === null) return 'none';
|
|
29
|
-
if (typeof value === 'number') return
|
|
30
|
-
if (typeof value === 'boolean') return
|
|
32
|
+
if (typeof value === 'number') return canonicalNumber(value);
|
|
33
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
31
34
|
if (typeof value === 'string') return value;
|
|
32
35
|
if (isColour(value)) {
|
|
33
|
-
const
|
|
34
|
-
return `#${
|
|
36
|
+
const alpha = roundHalfAway(value.a * 255);
|
|
37
|
+
return `#${hexByte(value.r)}${hexByte(value.g)}${hexByte(value.b)}${hexByte(alpha)}`;
|
|
35
38
|
}
|
|
36
39
|
return ctx.heap.get(value.id)?.kind ?? 'none';
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
/**
|
|
43
|
+
* A whole number from 0 to 255 as two hex digits, from a table.
|
|
44
|
+
*
|
|
45
|
+
* A colour's channels are whole numbers (`compiled-program.md` 3.1), so this
|
|
46
|
+
* is not the decimal rendering rule and asks no host conversion: each nibble
|
|
47
|
+
* indexes a string of sixteen characters.
|
|
48
|
+
*/
|
|
49
|
+
const HEX_DIGITS = '0123456789abcdef';
|
|
50
|
+
|
|
51
|
+
function hexByte(channel: number): string {
|
|
52
|
+
return `${HEX_DIGITS[channel >> 4] ?? '0'}${HEX_DIGITS[channel & 15] ?? '0'}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
39
55
|
/** A magnitude as decimal digits, and how many of them fall before the point. */
|
|
40
56
|
interface Spread {
|
|
41
57
|
readonly digits: string;
|
|
@@ -108,24 +124,31 @@ function written(of: Spread): string {
|
|
|
108
124
|
* least one digit, and exactly `d` digits after the point. `spread` is what
|
|
109
125
|
* makes that true of every magnitude rather than of the ones below a threshold.
|
|
110
126
|
*
|
|
111
|
-
* **
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
127
|
+
* **The scale is `round(x, d)`'s**, `scaleOf` from the numeric library, so
|
|
128
|
+
* the display conversion and the rounding call multiply by one value
|
|
129
|
+
* (`stdlib.md` 20.7) and cannot part by an ulp at the one count where the
|
|
130
|
+
* host's power does.
|
|
131
|
+
*
|
|
132
|
+
* **The digits are the shortest form's, zero filled, at every magnitude.**
|
|
133
|
+
* Below 2 ** 53 a rounded whole number has no digits but its own, so nothing
|
|
134
|
+
* ordinary changes; at or above it the shortest form is what both engines
|
|
135
|
+
* write, and it is what this uses inside the scaling range as well as past it,
|
|
136
|
+
* where scaling by ten to the `d` leaves binary64 altogether and there is
|
|
137
|
+
* nothing to round: a binary64 that large is a whole number already, and a
|
|
138
|
+
* decimal place that far from the leading digit is past every digit the value
|
|
139
|
+
* carries. One rule for the digits at every magnitude, rather than the exact
|
|
140
|
+
* binary expansion below a threshold and the shortest form above it.
|
|
119
141
|
*/
|
|
120
142
|
function fixed(x: number, decimals: number): string {
|
|
121
|
-
const scaled = roundHalfAway(x *
|
|
143
|
+
const scaled = roundHalfAway(x * scaleOf(decimals));
|
|
122
144
|
const usable = Number.isFinite(scaled);
|
|
123
145
|
const sign = (usable ? scaled < 0 : x < 0) ? '-' : '';
|
|
124
|
-
//
|
|
125
|
-
//
|
|
146
|
+
// The digits are the shortest form's at every magnitude, through the one
|
|
147
|
+
// writer: a rounded whole below 2 ** 53 has no other digits, and above it
|
|
148
|
+
// the shortest form is the one rule both engines write (language.md 5.5).
|
|
126
149
|
const magnitude = usable
|
|
127
|
-
? spread(Math.abs(scaled)
|
|
128
|
-
: spread(Math.abs(x)
|
|
150
|
+
? spread(canonicalNumber(Math.abs(scaled)), 0)
|
|
151
|
+
: spread(canonicalNumber(Math.abs(x)), decimals);
|
|
129
152
|
const digits = written(magnitude).padStart(decimals + 1, '0');
|
|
130
153
|
if (decimals === 0) return sign + digits;
|
|
131
154
|
const whole = digits.slice(0, digits.length - decimals);
|
|
@@ -143,7 +166,7 @@ function fixed(x: number, decimals: number): string {
|
|
|
143
166
|
* have. `str.repeat` measures first for the same reason.
|
|
144
167
|
*/
|
|
145
168
|
function lengthOf(x: number, decimals: number): number {
|
|
146
|
-
const before = Math.max(1, spread(Math.abs(x)
|
|
169
|
+
const before = Math.max(1, spread(canonicalNumber(Math.abs(x)), 0).point);
|
|
147
170
|
return before + decimals + (decimals > 0 ? 1 : 0);
|
|
148
171
|
}
|
|
149
172
|
|
|
@@ -154,22 +177,17 @@ function lengthOf(x: number, decimals: number): number {
|
|
|
154
177
|
* which accepts hexadecimal, infinities and a bare leading point in some
|
|
155
178
|
* languages and not others. Absence rather than zero for text that is not a
|
|
156
179
|
* number, so a script can tell text that is not a number apart from the number
|
|
157
|
-
* zero.
|
|
180
|
+
* zero. The whitespace ignored at either end is `str.trim`'s set and no other.
|
|
158
181
|
*/
|
|
159
182
|
const NUMERIC = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
160
183
|
|
|
161
184
|
function parseNumber(text: string): Value {
|
|
162
|
-
const
|
|
163
|
-
if (!NUMERIC.test(
|
|
164
|
-
const parsed = Number(
|
|
185
|
+
const bare = trimmed(text);
|
|
186
|
+
if (!NUMERIC.test(bare)) return null;
|
|
187
|
+
const parsed = Number(bare);
|
|
165
188
|
return Number.isFinite(parsed) ? (parsed === 0 ? 0 : parsed) : null;
|
|
166
189
|
}
|
|
167
190
|
|
|
168
|
-
/** Code points, which is what every index in this file counts. */
|
|
169
|
-
function points(text: string): string[] {
|
|
170
|
-
return [...text];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
191
|
function makeArray(ctx: CallContext, items: Value[]): Value {
|
|
174
192
|
ctx.guard.array(ctx.span, 'the array', items.length);
|
|
175
193
|
return reference(ctx.heap.allocate({ kind: 'array', items }));
|
|
@@ -198,7 +216,7 @@ export const TEXT_ENTRIES: readonly ManifestEntry[] = [
|
|
|
198
216
|
|
|
199
217
|
entry('str.upper', 's', (ctx, args) => map(ctx, args, (s) => s.toUpperCase())),
|
|
200
218
|
entry('str.lower', 's', (ctx, args) => map(ctx, args, (s) => s.toLowerCase())),
|
|
201
|
-
entry('str.trim', 's', (ctx, args) => map(ctx, args,
|
|
219
|
+
entry('str.trim', 's', (ctx, args) => map(ctx, args, trimmed)),
|
|
202
220
|
|
|
203
221
|
entry('str.contains', 's part', (_ctx, args) => pair(args, (s, part) => s.includes(part))),
|
|
204
222
|
entry('str.startsWith', 's part', (_ctx, args) => pair(args, (s, part) => s.startsWith(part))),
|
package/src/core/engine/load.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* bar four thousand with half a study on the screen.
|
|
11
11
|
*/
|
|
12
12
|
import type { Diagnostic } from '../diagnostics/index.js';
|
|
13
|
+
import { canonicalise } from '../emit/index.js';
|
|
13
14
|
import type { SourceFile } from '../source/index.js';
|
|
14
15
|
import { BAR_FIELDS } from './bars.js';
|
|
15
16
|
import { limitsWith } from './budget.js';
|
|
@@ -116,3 +117,70 @@ export function load(program: unknown, options: LoadOptions = {}): LoadResult {
|
|
|
116
117
|
inputs: resolved.inputs,
|
|
117
118
|
};
|
|
118
119
|
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Loads a compiled program from text, which is where canonicity is required.
|
|
123
|
+
*
|
|
124
|
+
* `load` takes a parsed object because a host that compiled in this process
|
|
125
|
+
* never serialised, and there is nothing for such an object to be canonical
|
|
126
|
+
* about. Text is the other boundary, `compiled-program.md` 9.4 step 1 and
|
|
127
|
+
* section 13 (decision 57): a program an engine reads from outside its process
|
|
128
|
+
* arrives as the canonical encoding of 2.14, and text that parses to a program
|
|
129
|
+
* but is not that encoding is refused, because the hash a host recorded was
|
|
130
|
+
* taken over canonical bytes and text in any other spelling is text that hash
|
|
131
|
+
* does not name. So the text is parsed, written out again by the one canonical
|
|
132
|
+
* writer, and the two are compared character for character. Either failure is
|
|
133
|
+
* OS6018 naming where the text stops being readable or stops being canonical,
|
|
134
|
+
* counted in characters of the text, which is where an editor's cursor lands.
|
|
135
|
+
*
|
|
136
|
+
* What passes here is the object `load` verifies from step 2 on, so every
|
|
137
|
+
* refusal that applies to an object applies to text as well and in the same
|
|
138
|
+
* order.
|
|
139
|
+
*/
|
|
140
|
+
export function loadText(text: string, options: LoadOptions = {}): LoadResult {
|
|
141
|
+
let parsed: unknown;
|
|
142
|
+
try {
|
|
143
|
+
parsed = JSON.parse(text);
|
|
144
|
+
} catch (thrown) {
|
|
145
|
+
const message = thrown instanceof Error ? thrown.message : String(thrown);
|
|
146
|
+
const at = /position (\d+)/.exec(message);
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
diagnostic: malformed(
|
|
150
|
+
at === null ? 'the text' : `character ${at[1]}`,
|
|
151
|
+
'the text stops being the object notation of section 2.14 there, so it is not a program',
|
|
152
|
+
),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let canonical: string;
|
|
157
|
+
try {
|
|
158
|
+
canonical = canonicalise(parsed);
|
|
159
|
+
} catch (thrown) {
|
|
160
|
+
const message = thrown instanceof Error ? thrown.message : String(thrown);
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
diagnostic: malformed('the text', `it parses but cannot be written in the canonical form: ${message}`),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (canonical !== text) {
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
diagnostic: malformed(
|
|
170
|
+
`character ${firstDifference(text, canonical)}`,
|
|
171
|
+
'the text is not the canonical encoding of the program it parses to, and a program ' +
|
|
172
|
+
'read from outside the process has to be, because that is what its hash was taken over',
|
|
173
|
+
),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return load(parsed, options);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Where two texts part, as an index into either, or the shorter one's length. */
|
|
180
|
+
function firstDifference(a: string, b: string): number {
|
|
181
|
+
const shorter = Math.min(a.length, b.length);
|
|
182
|
+
for (let i = 0; i < shorter; i += 1) {
|
|
183
|
+
if (a[i] !== b[i]) return i;
|
|
184
|
+
}
|
|
185
|
+
return shorter;
|
|
186
|
+
}
|
|
@@ -21,8 +21,51 @@ const META_KINDS = ['study', 'strategy'] as const;
|
|
|
21
21
|
const CHANNEL_TYPES = ['number', 'string', 'color', 'bool'] as const;
|
|
22
22
|
const EFFECTS = ['none', 'signal', 'order', 'draw', 'log'] as const;
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* The tables a later minor of this format major added, with the minor that
|
|
26
|
+
* added each.
|
|
27
|
+
*
|
|
28
|
+
* 9.2's rule read from the other side, which 9.4 step 3 states and decision 56
|
|
29
|
+
* settled: a table a later minor added and an earlier program lacks reads as
|
|
30
|
+
* empty, never as a refusal, because 9.5's first line is a promise about that
|
|
31
|
+
* program. A program stamped at this minor or a later one has no such excuse.
|
|
32
|
+
* Section 2 says an empty table is written as an empty array and never
|
|
33
|
+
* omitted, so its absence there is the defect check 1 exists for, and the
|
|
34
|
+
* version is what tells the two apart.
|
|
35
|
+
*
|
|
36
|
+
* `spec/format-history.json` records the same additions, one entry per format
|
|
37
|
+
* version, and `tests/engine/format-minors.test.ts` holds this list to that
|
|
38
|
+
* file: a table the history says a later minor added has to be one a program
|
|
39
|
+
* at the earlier minor may lack.
|
|
40
|
+
*/
|
|
41
|
+
const ADDED_AT_MINOR: readonly { readonly table: string; readonly minor: number }[] = [
|
|
42
|
+
{ table: 'requests', minor: 1 },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/** The minor of a `major.minor` the version step has already proved is one. */
|
|
46
|
+
function minorOf(raw: Readonly<Record<string, unknown>>): number {
|
|
47
|
+
const version = raw['openscript'] as Readonly<Record<string, unknown>>;
|
|
48
|
+
return Number(String(version['format']).split('.')[1] ?? '0');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Writes the empty table an earlier minor is owed into the program itself.
|
|
53
|
+
*
|
|
54
|
+
* Into the object rather than into a local, because every later step reads
|
|
55
|
+
* the program as its own type and would find the table missing again: the
|
|
56
|
+
* verifier hands the same object on, and the engine holds it.
|
|
57
|
+
*/
|
|
58
|
+
function supplyAddedTables(raw: Readonly<Record<string, unknown>>): void {
|
|
59
|
+
const minor = minorOf(raw);
|
|
60
|
+
for (const added of ADDED_AT_MINOR) {
|
|
61
|
+
if (raw[added.table] !== undefined || minor >= added.minor) continue;
|
|
62
|
+
(raw as Record<string, unknown>)[added.table] = [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
24
66
|
/** Check 1 over every table the machine indexes, and every declaration field. */
|
|
25
67
|
export function checkTables(shape: ShapeCheck, raw: Readonly<Record<string, unknown>>): boolean {
|
|
68
|
+
supplyAddedTables(raw);
|
|
26
69
|
for (const name of ['requires', 'inputs', 'channels', 'consts', 'series', 'cells', 'states',
|
|
27
70
|
'functions', 'callSites', 'loops', 'code', 'requests']) {
|
|
28
71
|
if (!shape.array(raw[name], name)) return false;
|
package/src/core/index.ts
CHANGED
|
@@ -250,10 +250,15 @@ export { DECLARATION_CALLS, declarationDefaultText, namedColour } from './emit/i
|
|
|
250
250
|
* same check on its own, for a host that keeps compiled programs and wants to
|
|
251
251
|
* refuse a bad one when it arrives rather than when it is first drawn.
|
|
252
252
|
*
|
|
253
|
+
* `loadText` is `load` for a program that arrives as text, which is the one
|
|
254
|
+
* boundary where the canonical encoding is required (`compiled-program.md`
|
|
255
|
+
* section 13): text that parses to a program but is not that encoding is
|
|
256
|
+
* refused, because the hash a host recorded was taken over canonical bytes.
|
|
257
|
+
*
|
|
253
258
|
* `capabilitiesFor` and `LANGUAGE_VERSIONS` are this engine's half of the
|
|
254
259
|
* compatibility statement in 9.1, beside `COMPILED_FORMAT_VERSION` above.
|
|
255
260
|
*/
|
|
256
|
-
export { load } from './engine/index.js';
|
|
261
|
+
export { load, loadText } from './engine/index.js';
|
|
257
262
|
export type { BarResult, Engine, LoadOptions, LoadResult, RunResult } from './engine/index.js';
|
|
258
263
|
export type {
|
|
259
264
|
BarState,
|
|
@@ -311,21 +316,25 @@ export type {
|
|
|
311
316
|
MonthlyReturn,
|
|
312
317
|
RecordedFill,
|
|
313
318
|
Report,
|
|
319
|
+
SideAnalysis,
|
|
314
320
|
Summary,
|
|
315
321
|
Trade,
|
|
322
|
+
TradeAnalysis,
|
|
316
323
|
TradeMarker,
|
|
317
324
|
} from './accounting/index.js';
|
|
318
325
|
export { chargeFor, scheduleFromDeclaration, scheduleProblem } from './accounting/index.js';
|
|
319
326
|
export { markersOf, monthlyOver, reportOf } from './accounting/index.js';
|
|
320
327
|
export { tradesOf } from './accounting/index.js';
|
|
321
|
-
export { equityOver, summaryOf } from './accounting/index.js';
|
|
328
|
+
export { analysisOf, equityOver, summaryOf } from './accounting/index.js';
|
|
322
329
|
export {
|
|
323
330
|
DEFAULT_FILL,
|
|
324
331
|
EXACT,
|
|
325
332
|
WHOLE_RANGE,
|
|
326
333
|
RECORD_VERSION,
|
|
327
334
|
backtest,
|
|
335
|
+
backtestSupplied,
|
|
328
336
|
barsHash,
|
|
337
|
+
caseFilesFrom,
|
|
329
338
|
checkSettings,
|
|
330
339
|
compareRuns,
|
|
331
340
|
recordFromJson,
|
|
@@ -341,8 +350,13 @@ export type { BacktestResult, ReplayResult, ReportWindow } from './backtest/inde
|
|
|
341
350
|
export type {
|
|
342
351
|
BacktestSettings,
|
|
343
352
|
BarsInRecord,
|
|
353
|
+
CaseFiles,
|
|
354
|
+
CaseIdentity,
|
|
355
|
+
CaseResult,
|
|
344
356
|
DateRange,
|
|
357
|
+
DriveOptions,
|
|
345
358
|
FillPolicy,
|
|
359
|
+
InstrumentFacts,
|
|
346
360
|
RecordedBar,
|
|
347
361
|
RecordedDiagnostic,
|
|
348
362
|
RecordedFrame,
|
package/src/core/stdlib/index.ts
CHANGED
|
@@ -31,6 +31,28 @@ export function round(x: Value): Value {
|
|
|
31
31
|
return isPresent(x) ? result(roundHalfAway(x)) : NONE;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Ten to each whole power a binary64 can hold, as the binary64 nearest to it.
|
|
36
|
+
*
|
|
37
|
+
* `stdlib.md` 20.7: the scale a fixed decimal rounding or conversion multiplies
|
|
38
|
+
* by is the nearest binary64 to the power of ten, the value the literal `1e23`
|
|
39
|
+
* reads as, and not what a floating point power returns, which is an ulp away
|
|
40
|
+
* from it for one count on this engine's host. Built once from exact integer
|
|
41
|
+
* arithmetic, and it is the one table: `text(x, decimals)` in the engine's
|
|
42
|
+
* library scales by it through `scaleOf` rather than holding a copy.
|
|
43
|
+
*/
|
|
44
|
+
const POWERS_OF_TEN: readonly number[] = Array.from({ length: 309 }, (_, count) =>
|
|
45
|
+
Number(10n ** BigInt(count)),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The scale for a digit count: the nearest binary64 to ten to that power, and
|
|
50
|
+
* past the last finite power the infinity the power would have been.
|
|
51
|
+
*/
|
|
52
|
+
export function scaleOf(decimals: number): number {
|
|
53
|
+
return POWERS_OF_TEN[decimals] ?? Infinity;
|
|
54
|
+
}
|
|
55
|
+
|
|
34
56
|
/**
|
|
35
57
|
* `round(x, decimals)`: to a fixed number of decimals, halves away from zero.
|
|
36
58
|
*
|
|
@@ -40,7 +62,7 @@ export function round(x: Value): Value {
|
|
|
40
62
|
export function roundTo(x: Value, decimals: number): Value {
|
|
41
63
|
if (!isPresent(x)) return NONE;
|
|
42
64
|
if (!Number.isInteger(decimals) || decimals < 0) return NONE;
|
|
43
|
-
const scale =
|
|
65
|
+
const scale = scaleOf(decimals);
|
|
44
66
|
return result(roundHalfAway(x * scale) / scale);
|
|
45
67
|
}
|
|
46
68
|
|
|
@@ -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.5.0";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The compiled program format this compiler emits.
|