openalgo-script 0.5.0 → 0.6.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 +121 -0
- package/README.md +53 -22
- package/dist/adapters/charts/run.d.ts.map +1 -1
- package/dist/adapters/charts/run.js +6 -0
- package/dist/adapters/charts/run.js.map +1 -1
- package/dist/adapters/charts/tables.d.ts +6 -19
- package/dist/adapters/charts/tables.d.ts.map +1 -1
- package/dist/adapters/charts/tables.js.map +1 -1
- package/dist/adapters/charts/undrawable.d.ts +4 -0
- package/dist/adapters/charts/undrawable.d.ts.map +1 -0
- package/dist/adapters/charts/undrawable.js +47 -0
- package/dist/adapters/charts/undrawable.js.map +1 -0
- package/dist/core/backtest/drive.d.ts +36 -1
- package/dist/core/backtest/drive.d.ts.map +1 -1
- package/dist/core/backtest/drive.js +13 -107
- package/dist/core/backtest/drive.js.map +1 -1
- package/dist/core/backtest/index.d.ts +2 -0
- package/dist/core/backtest/index.d.ts.map +1 -1
- package/dist/core/backtest/index.js.map +1 -1
- package/dist/core/backtest/walk.d.ts +39 -0
- package/dist/core/backtest/walk.d.ts.map +1 -0
- package/dist/core/backtest/walk.js +125 -0
- package/dist/core/backtest/walk.js.map +1 -0
- package/dist/core/catalogue/catalogue.generated.d.ts +178 -1
- package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
- package/dist/core/catalogue/catalogue.generated.js +18 -1
- package/dist/core/catalogue/catalogue.generated.js.map +1 -1
- package/dist/core/catalogue/types.d.ts +8 -2
- package/dist/core/catalogue/types.d.ts.map +1 -1
- package/dist/core/catalogue/values.generated.d.ts +51 -0
- package/dist/core/catalogue/values.generated.d.ts.map +1 -1
- package/dist/core/check/assigned.d.ts +19 -0
- package/dist/core/check/assigned.d.ts.map +1 -0
- package/dist/core/check/assigned.js +14 -0
- package/dist/core/check/assigned.js.map +1 -0
- package/dist/core/check/call-sites.d.ts.map +1 -1
- package/dist/core/check/call-sites.js +9 -4
- package/dist/core/check/call-sites.js.map +1 -1
- package/dist/core/check/calls.d.ts.map +1 -1
- package/dist/core/check/calls.js +12 -4
- package/dist/core/check/calls.js.map +1 -1
- package/dist/core/check/check.d.ts.map +1 -1
- package/dist/core/check/check.js +15 -2
- package/dist/core/check/check.js.map +1 -1
- package/dist/core/check/checker.d.ts +26 -0
- package/dist/core/check/checker.d.ts.map +1 -1
- package/dist/core/check/checker.js +38 -2
- package/dist/core/check/checker.js.map +1 -1
- package/dist/core/check/constant.d.ts +16 -0
- package/dist/core/check/constant.d.ts.map +1 -1
- package/dist/core/check/constant.js +52 -0
- package/dist/core/check/constant.js.map +1 -1
- package/dist/core/check/deleted.d.ts +3 -0
- package/dist/core/check/deleted.d.ts.map +1 -0
- package/dist/core/check/deleted.js +109 -0
- package/dist/core/check/deleted.js.map +1 -0
- package/dist/core/check/expressions.js +1 -1
- package/dist/core/check/expressions.js.map +1 -1
- package/dist/core/check/library-output.d.ts.map +1 -1
- package/dist/core/check/library-output.js +1 -0
- package/dist/core/check/library-output.js.map +1 -1
- package/dist/core/check/library-prose.generated.js +10 -10
- package/dist/core/check/library-prose.generated.js.map +1 -1
- package/dist/core/check/library.d.ts +7 -0
- package/dist/core/check/library.d.ts.map +1 -1
- package/dist/core/check/library.js +1 -0
- package/dist/core/check/library.js.map +1 -1
- package/dist/core/check/surface.d.ts +9 -0
- package/dist/core/check/surface.d.ts.map +1 -1
- package/dist/core/check/surface.js +13 -0
- package/dist/core/check/surface.js.map +1 -1
- package/dist/core/engine/bar-source.d.ts +65 -0
- package/dist/core/engine/bar-source.d.ts.map +1 -0
- package/dist/core/engine/bar-source.js +58 -0
- package/dist/core/engine/bar-source.js.map +1 -0
- package/dist/core/engine/engine.d.ts +9 -11
- package/dist/core/engine/engine.d.ts.map +1 -1
- package/dist/core/engine/engine.js +24 -24
- package/dist/core/engine/engine.js.map +1 -1
- package/dist/core/engine/guard.d.ts.map +1 -1
- package/dist/core/engine/guard.js +10 -0
- package/dist/core/engine/guard.js.map +1 -1
- package/dist/core/engine/host.d.ts +4 -2
- package/dist/core/engine/host.d.ts.map +1 -1
- package/dist/core/engine/host.js.map +1 -1
- package/dist/core/engine/index.d.ts +1 -0
- package/dist/core/engine/index.d.ts.map +1 -1
- package/dist/core/engine/index.js.map +1 -1
- package/dist/core/engine/library/binding.d.ts +2 -0
- package/dist/core/engine/library/binding.d.ts.map +1 -1
- package/dist/core/engine/library/binding.js.map +1 -1
- package/dist/core/engine/library/index.d.ts +8 -0
- package/dist/core/engine/library/index.d.ts.map +1 -1
- package/dist/core/engine/library/index.js +10 -0
- package/dist/core/engine/library/index.js.map +1 -1
- package/dist/core/engine/library/objects.d.ts.map +1 -1
- package/dist/core/engine/library/objects.js +1 -1
- package/dist/core/engine/library/objects.js.map +1 -1
- package/dist/core/engine/request-body.d.ts +3 -1
- package/dist/core/engine/request-body.d.ts.map +1 -1
- package/dist/core/engine/request-body.js +3 -1
- package/dist/core/engine/request-body.js.map +1 -1
- package/dist/core/engine/request-plan.d.ts +3 -3
- package/dist/core/engine/request-plan.js +3 -3
- package/dist/core/engine/requests.d.ts +2 -2
- package/dist/core/engine/requests.d.ts.map +1 -1
- package/dist/core/engine/requests.js +5 -4
- package/dist/core/engine/requests.js.map +1 -1
- package/dist/core/engine/series.d.ts +18 -6
- package/dist/core/engine/series.d.ts.map +1 -1
- package/dist/core/engine/series.js +24 -5
- package/dist/core/engine/series.js.map +1 -1
- package/dist/core/engine/session/record.d.ts +4 -4
- package/dist/core/engine/session/record.js +4 -4
- package/dist/core/importer/calls.d.ts +29 -0
- package/dist/core/importer/calls.d.ts.map +1 -0
- package/dist/core/importer/calls.js +267 -0
- package/dist/core/importer/calls.js.map +1 -0
- package/dist/core/importer/context.d.ts +118 -0
- package/dist/core/importer/context.d.ts.map +1 -0
- package/dist/core/importer/context.js +120 -0
- package/dist/core/importer/context.js.map +1 -0
- package/dist/core/importer/expressions.d.ts +44 -0
- package/dist/core/importer/expressions.d.ts.map +1 -0
- package/dist/core/importer/expressions.js +223 -0
- package/dist/core/importer/expressions.js.map +1 -0
- package/dist/core/importer/importer.d.ts +13 -0
- package/dist/core/importer/importer.d.ts.map +1 -0
- package/dist/core/importer/importer.js +220 -0
- package/dist/core/importer/importer.js.map +1 -0
- package/dist/core/importer/index.d.ts +20 -0
- package/dist/core/importer/index.d.ts.map +1 -0
- package/dist/core/importer/index.js +18 -0
- package/dist/core/importer/index.js.map +1 -0
- package/dist/core/importer/lexer.d.ts +68 -0
- package/dist/core/importer/lexer.d.ts.map +1 -0
- package/dist/core/importer/lexer.js +207 -0
- package/dist/core/importer/lexer.js.map +1 -0
- package/dist/core/importer/names.d.ts +41 -0
- package/dist/core/importer/names.d.ts.map +1 -0
- package/dist/core/importer/names.js +78 -0
- package/dist/core/importer/names.js.map +1 -0
- package/dist/core/importer/orders.d.ts +8 -0
- package/dist/core/importer/orders.d.ts.map +1 -0
- package/dist/core/importer/orders.js +332 -0
- package/dist/core/importer/orders.js.map +1 -0
- package/dist/core/importer/outputs.d.ts +19 -0
- package/dist/core/importer/outputs.d.ts.map +1 -0
- package/dist/core/importer/outputs.js +266 -0
- package/dist/core/importer/outputs.js.map +1 -0
- package/dist/core/importer/parser.d.ts +5 -0
- package/dist/core/importer/parser.d.ts.map +1 -0
- package/dist/core/importer/parser.js +439 -0
- package/dist/core/importer/parser.js.map +1 -0
- package/dist/core/importer/presence.d.ts +32 -0
- package/dist/core/importer/presence.d.ts.map +1 -0
- package/dist/core/importer/presence.js +146 -0
- package/dist/core/importer/presence.js.map +1 -0
- package/dist/core/importer/settle.d.ts +23 -0
- package/dist/core/importer/settle.d.ts.map +1 -0
- package/dist/core/importer/settle.js +135 -0
- package/dist/core/importer/settle.js.map +1 -0
- package/dist/core/importer/statements.d.ts +4 -0
- package/dist/core/importer/statements.d.ts.map +1 -0
- package/dist/core/importer/statements.js +339 -0
- package/dist/core/importer/statements.js.map +1 -0
- package/dist/core/importer/syntax.d.ts +182 -0
- package/dist/core/importer/syntax.d.ts.map +1 -0
- package/dist/core/importer/syntax.js +14 -0
- package/dist/core/importer/syntax.js.map +1 -0
- package/dist/core/importer/table.d.ts +52 -0
- package/dist/core/importer/table.d.ts.map +1 -0
- package/dist/core/importer/table.js +156 -0
- package/dist/core/importer/table.js.map +1 -0
- package/dist/core/importer/values.d.ts +27 -0
- package/dist/core/importer/values.d.ts.map +1 -0
- package/dist/core/importer/values.js +236 -0
- package/dist/core/importer/values.js.map +1 -0
- package/dist/core/index.d.ts +12 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +11 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/stdlib/calendar/civil.d.ts.map +1 -1
- package/dist/core/stdlib/calendar/civil.js +5 -2
- package/dist/core/stdlib/calendar/civil.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 +5 -2
- package/spec/errors.json +380 -20
- package/src/adapters/charts/run.ts +6 -0
- package/src/adapters/charts/tables.ts +6 -19
- package/src/adapters/charts/undrawable.ts +51 -0
- package/src/core/backtest/drive.ts +66 -154
- package/src/core/backtest/index.ts +2 -0
- package/src/core/backtest/walk.ts +182 -0
- package/src/core/catalogue/catalogue.generated.ts +18 -1
- package/src/core/catalogue/types.ts +8 -2
- package/src/core/catalogue/values.generated.ts +16 -0
- package/src/core/check/assigned.ts +31 -0
- package/src/core/check/call-sites.ts +11 -4
- package/src/core/check/calls.ts +13 -4
- package/src/core/check/check.ts +14 -2
- package/src/core/check/checker.ts +39 -2
- package/src/core/check/constant.ts +57 -0
- package/src/core/check/deleted.ts +135 -0
- package/src/core/check/expressions.ts +1 -1
- package/src/core/check/library-output.ts +1 -0
- package/src/core/check/library-prose.generated.ts +10 -10
- package/src/core/check/library.ts +8 -0
- package/src/core/check/surface.ts +16 -0
- package/src/core/engine/bar-source.ts +113 -0
- package/src/core/engine/engine.ts +26 -25
- package/src/core/engine/guard.ts +12 -1
- package/src/core/engine/host.ts +4 -2
- package/src/core/engine/index.ts +1 -0
- package/src/core/engine/library/binding.ts +2 -0
- package/src/core/engine/library/index.ts +11 -0
- package/src/core/engine/library/objects.ts +1 -6
- package/src/core/engine/request-body.ts +5 -7
- package/src/core/engine/request-plan.ts +3 -3
- package/src/core/engine/requests.ts +11 -8
- package/src/core/engine/series.ts +30 -6
- package/src/core/engine/session/record.ts +4 -4
- package/src/core/importer/calls.ts +274 -0
- package/src/core/importer/context.ts +197 -0
- package/src/core/importer/expressions.ts +247 -0
- package/src/core/importer/importer.ts +226 -0
- package/src/core/importer/index.ts +19 -0
- package/src/core/importer/lexer.ts +285 -0
- package/src/core/importer/names.ts +104 -0
- package/src/core/importer/orders.ts +307 -0
- package/src/core/importer/outputs.ts +245 -0
- package/src/core/importer/parser.ts +436 -0
- package/src/core/importer/presence.ts +164 -0
- package/src/core/importer/settle.ts +141 -0
- package/src/core/importer/statements.ts +331 -0
- package/src/core/importer/syntax.ts +144 -0
- package/src/core/importer/table.ts +191 -0
- package/src/core/importer/values.ts +241 -0
- package/src/core/index.ts +13 -0
- package/src/core/stdlib/calendar/civil.ts +5 -2
- package/src/core/version/version.generated.ts +1 -1
|
@@ -21,7 +21,7 @@ import { bindArguments } from './arguments.js';
|
|
|
21
21
|
import { remember, resolveLibraryCall, unresolved } from './calls.js';
|
|
22
22
|
import type { Checker, Placement } from './checker.js';
|
|
23
23
|
import type { CheckedCall, InputKind, RequestMode } from './checked.js';
|
|
24
|
-
import { calleeNameOf, isCompileTimeConstant, isSourceName } from './constant.js';
|
|
24
|
+
import { calleeNameOf, isCompileTimeConstant, isSourceName, readsInputInPart } from './constant.js';
|
|
25
25
|
import { checkExpression } from './expressions.js';
|
|
26
26
|
import { ensureChecked } from './functions.js';
|
|
27
27
|
import { allowHandle, refuseHandle } from './handles.js';
|
|
@@ -59,7 +59,10 @@ export function resolveCall(checker: Checker, call: Call, placement: Placement):
|
|
|
59
59
|
checker.report('OS2010', call.span, {
|
|
60
60
|
name: checker.textOf(call.callee.span),
|
|
61
61
|
type: typeText(UNKNOWN),
|
|
62
|
-
suggestion: checker.
|
|
62
|
+
suggestion: checker.callSuggestionFor(
|
|
63
|
+
checker.textOf(call.callee.span),
|
|
64
|
+
call.args.map((one) => checker.textOf(one.span)),
|
|
65
|
+
),
|
|
63
66
|
});
|
|
64
67
|
return record(checker, call, unresolved(call, 'call'));
|
|
65
68
|
}
|
|
@@ -220,8 +223,12 @@ function checkInput(checker: Checker, call: Call, checked: CheckedCall): Checked
|
|
|
220
223
|
const options = argumentOf(checked, 'options');
|
|
221
224
|
const title = literalOf(checked, 'title');
|
|
222
225
|
|
|
223
|
-
if (value !== undefined && !isSourceName(value.value)
|
|
224
|
-
checker
|
|
226
|
+
if (value !== undefined && !isSourceName(value.value)) {
|
|
227
|
+
if (!isCompileTimeConstant(checker, value.value)) {
|
|
228
|
+
checker.report('OS3003', value.span, { option: 'value' });
|
|
229
|
+
} else if (readsInputInPart(checker, value.value, false)) {
|
|
230
|
+
checker.report('OS3025', value.span, { option: 'value' });
|
|
231
|
+
}
|
|
225
232
|
}
|
|
226
233
|
if (kind !== undefined && PLANNED_KINDS.has(kind)) {
|
|
227
234
|
checker.report('OS2001', call.span, {
|
package/src/core/check/calls.ts
CHANGED
|
@@ -19,7 +19,7 @@ import type { ParameterShape } from './arguments.js';
|
|
|
19
19
|
import type { Checker, Placement } from './checker.js';
|
|
20
20
|
import { reportStrategyOnly } from './checker.js';
|
|
21
21
|
import type { CheckedCall } from './checked.js';
|
|
22
|
-
import { isCompileTimeConstant } from './constant.js';
|
|
22
|
+
import { isCompileTimeConstant, readsInputInPart } from './constant.js';
|
|
23
23
|
import { refuseHandle } from './handles.js';
|
|
24
24
|
import type { LibraryEntry } from './library.js';
|
|
25
25
|
import { literalNumber, literalString } from './literals.js';
|
|
@@ -244,8 +244,14 @@ export function validateArguments(
|
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
if (entry.constant.includes(parameter.name)
|
|
248
|
-
checker
|
|
247
|
+
if (entry.constant.includes(parameter.name)) {
|
|
248
|
+
if (!isCompileTimeConstant(checker, argument.value)) {
|
|
249
|
+
checker.report('OS3003', span, { option: parameter.name });
|
|
250
|
+
} else if (readsInputInPart(checker, argument.value, entry.name !== 'input')) {
|
|
251
|
+
checker.report('OS3025', span, { option: parameter.name });
|
|
252
|
+
} else if (entry.written.includes(parameter.name) && readsInputInPart(checker, argument.value, false)) {
|
|
253
|
+
checker.report('OS3026', span, { option: parameter.name });
|
|
254
|
+
}
|
|
249
255
|
}
|
|
250
256
|
}
|
|
251
257
|
|
|
@@ -331,7 +337,10 @@ export function resolveLibraryCall(
|
|
|
331
337
|
checker.report('OS2010', call.span, {
|
|
332
338
|
name,
|
|
333
339
|
type: held === undefined ? typeText(UNKNOWN) : typeText(held.returns),
|
|
334
|
-
suggestion: checker.
|
|
340
|
+
suggestion: checker.callSuggestionFor(
|
|
341
|
+
name,
|
|
342
|
+
call.args.map((one) => checker.textOf(one.span)),
|
|
343
|
+
),
|
|
335
344
|
});
|
|
336
345
|
return unresolved(call, name);
|
|
337
346
|
}
|
package/src/core/check/check.ts
CHANGED
|
@@ -21,11 +21,12 @@ import type { SourceFile } from '../source/index.js';
|
|
|
21
21
|
import { Checker, TOP_LEVEL, finaliseStorage } from './checker.js';
|
|
22
22
|
import type { CheckedScript } from './checked.js';
|
|
23
23
|
import { checkDeclaration, checkLimits, readHeader } from './declaration.js';
|
|
24
|
+
import { reportDeletedStillHeld } from './deleted.js';
|
|
24
25
|
import { checkFunctionDeclarations, checkRemainingFunctions } from './functions.js';
|
|
25
26
|
import { reportRepeatedAlertIds } from './outputs.js';
|
|
26
27
|
import { checkStatement } from './statements.js';
|
|
27
28
|
import { reportUnplaceableTags } from './tags.js';
|
|
28
|
-
import { elementOf } from './types.js';
|
|
29
|
+
import { elementOf, isHandle } from './types.js';
|
|
29
30
|
|
|
30
31
|
export function check(file: SourceFile, script: Script, sink: DiagnosticSink): CheckedScript {
|
|
31
32
|
const checker = new Checker(file, script, sink);
|
|
@@ -54,6 +55,7 @@ export function check(file: SourceFile, script: Script, sink: DiagnosticSink): C
|
|
|
54
55
|
finaliseStorage(checker);
|
|
55
56
|
reportUnknownElementTypes(checker);
|
|
56
57
|
reportNamesNeverRead(checker);
|
|
58
|
+
reportDeletedStillHeld(checker);
|
|
57
59
|
reportRepeatedTitles(checker);
|
|
58
60
|
reportInputKeys(checker);
|
|
59
61
|
reportRepeatedAlertIds(checker);
|
|
@@ -78,12 +80,22 @@ function reportUnknownElementTypes(checker: Checker): void {
|
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* OS8010 and OS8018: a name, or an input, that nothing in the file reads.
|
|
85
|
+
*
|
|
86
|
+
* A name bound to a declaration handle is exempt, as the entry's cause says: a
|
|
87
|
+
* handle is a compile-time binding, the declaration draws whether or not it is
|
|
88
|
+
* named, and so nothing is left in the bar loop for the warning to be about.
|
|
89
|
+
* `language.md` 5.4 gives `plot`, `plotCandles`, `fill` and `level` the one kind
|
|
90
|
+
* of value, so the exemption is the type rather than a list of the two calls
|
|
91
|
+
* `stdlib.md` 14.2 happens to name.
|
|
92
|
+
*/
|
|
82
93
|
function reportNamesNeverRead(checker: Checker): void {
|
|
83
94
|
for (const binding of checker.bindings) {
|
|
84
95
|
if (binding.isRead) continue;
|
|
85
96
|
if (binding.kind === 'library' || binding.kind === 'function') continue;
|
|
86
97
|
if (binding.kind === 'parameter' || binding.kind === 'loop') continue;
|
|
98
|
+
if (isHandle(binding.type)) continue;
|
|
87
99
|
|
|
88
100
|
if (binding.input !== undefined) {
|
|
89
101
|
const input = checker.inputs[binding.input];
|
|
@@ -35,13 +35,14 @@ import type {
|
|
|
35
35
|
Mutable,
|
|
36
36
|
Storage,
|
|
37
37
|
} from './checked.js';
|
|
38
|
+
import { namesGivenAValue } from './assigned.js';
|
|
38
39
|
import { conditionalCalls } from './conditional.js';
|
|
39
|
-
import { libraryNames } from './surface.js';
|
|
40
|
+
import { libraryFunctionNames, libraryNames } from './surface.js';
|
|
40
41
|
import { closestName } from './suggest.js';
|
|
41
42
|
import type { Type } from './types.js';
|
|
42
43
|
import { UNKNOWN } from './types.js';
|
|
43
44
|
import type { Warmup } from './warmup.js';
|
|
44
|
-
import { BAR_ZERO } from './warmup.js';
|
|
45
|
+
import { BAR_ZERO, weaken } from './warmup.js';
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
48
|
* One nesting of names, `language.md` 12.1.
|
|
@@ -143,6 +144,13 @@ export class Checker {
|
|
|
143
144
|
|
|
144
145
|
/** Every call site a bar can pass without evaluating, `language.md` 11.4. */
|
|
145
146
|
private readonly conditional: ReadonlySet<Call>;
|
|
147
|
+
/**
|
|
148
|
+
* The names some line gives a value, read before a `var` is called never.
|
|
149
|
+
*
|
|
150
|
+
* Built the first time it is asked for, because it is one walk of the whole
|
|
151
|
+
* tree and almost every file never reads a `var` that is still never.
|
|
152
|
+
*/
|
|
153
|
+
private givenAValue: ReadonlySet<string> | undefined = undefined;
|
|
146
154
|
|
|
147
155
|
constructor(file: SourceFile, script: Script, sink: DiagnosticSink) {
|
|
148
156
|
this.file = file;
|
|
@@ -300,6 +308,21 @@ export class Checker {
|
|
|
300
308
|
return this.types.get(expression) ?? UNKNOWN;
|
|
301
309
|
}
|
|
302
310
|
|
|
311
|
+
/**
|
|
312
|
+
* The warmup a read of this name sees, here.
|
|
313
|
+
*
|
|
314
|
+
* A `var` that is still never at this line but that a later line gives a
|
|
315
|
+
* value is not never: the later line wrote it on the bar before, or on an
|
|
316
|
+
* earlier pass of the same loop. The honest answer is a floor of bar 0,
|
|
317
|
+
* which claims nothing and withdraws the claim OS8009 is built on, where an
|
|
318
|
+
* exact bar would need the later line's warmup before it has been read.
|
|
319
|
+
*/
|
|
320
|
+
warmupOfRead(binding: Binding): Warmup {
|
|
321
|
+
if (binding.warmup.kind !== 'never' || binding.persistence === 'none') return binding.warmup;
|
|
322
|
+
this.givenAValue ??= namesGivenAValue(this.script);
|
|
323
|
+
return this.givenAValue.has(binding.name) ? weaken(BAR_ZERO) : binding.warmup;
|
|
324
|
+
}
|
|
325
|
+
|
|
303
326
|
warmupOf(expression: Expression): Warmup {
|
|
304
327
|
return this.warmups.get(expression) ?? BAR_ZERO;
|
|
305
328
|
}
|
|
@@ -318,6 +341,20 @@ export class Checker {
|
|
|
318
341
|
return closestName(written, this.namesInScope());
|
|
319
342
|
}
|
|
320
343
|
|
|
344
|
+
/**
|
|
345
|
+
* OS2010's suggestion: the closest function, called with what was written.
|
|
346
|
+
*
|
|
347
|
+
* The catalogue documents the slot as the library function whose name is
|
|
348
|
+
* closest, with its first argument filled in, so `volume(20)` is offered a
|
|
349
|
+
* function over `volume` and the arguments the reader already wrote,
|
|
350
|
+
* rather than a name that cannot be called either.
|
|
351
|
+
*/
|
|
352
|
+
callSuggestionFor(written: string, args: readonly string[]): string {
|
|
353
|
+
const inStrategy = this.declaration?.form === 'strategy';
|
|
354
|
+
const name = closestName(written, libraryFunctionNames(inStrategy));
|
|
355
|
+
return `${name}(${[written, ...args].join(', ')})`;
|
|
356
|
+
}
|
|
357
|
+
|
|
321
358
|
/** A state region for one call site, `compiled-program.md` 2.11. */
|
|
322
359
|
takeStateId(): number {
|
|
323
360
|
const id = this.stateCount;
|
|
@@ -92,6 +92,63 @@ export function isCompileTimeConstant(checker: Checker, expression: Expression):
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Whether a constant reads a setting as part of something larger, OS3025.
|
|
97
|
+
*
|
|
98
|
+
* `compiled-program.md` 2.3 gives a field fixed before bar 0 two forms: the
|
|
99
|
+
* effective value, which the compiler folds, and `{ "input": "<key>" }`, one
|
|
100
|
+
* setting resolved at load. `precision = input(2, "Decimals") + 1` and
|
|
101
|
+
* `opacity = shade ? 1 : 0` are neither: the compiler cannot fold them, because
|
|
102
|
+
* the setting's value is not known until the host resolves it, and the program
|
|
103
|
+
* has no form for an expression evaluated at load. Nothing refused them, so the
|
|
104
|
+
* emitter met them with no way to write them and reported a defect in itself.
|
|
105
|
+
*
|
|
106
|
+
* `wholeOnly` is false for an input's own options: a default, a bound or a
|
|
107
|
+
* step is what the settings dialog shows before anybody has chosen, and a
|
|
108
|
+
* setting that took its default from another setting would have none to show.
|
|
109
|
+
*/
|
|
110
|
+
export function readsInputInPart(
|
|
111
|
+
checker: Checker,
|
|
112
|
+
expression: Expression,
|
|
113
|
+
wholeOnly: boolean,
|
|
114
|
+
): boolean {
|
|
115
|
+
if (!readsInput(checker, expression)) return false;
|
|
116
|
+
return !wholeOnly || !isWholeInput(checker, expression);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Whether a setting is read anywhere inside the expression. */
|
|
120
|
+
function readsInput(checker: Checker, expression: Expression): boolean {
|
|
121
|
+
if (isWholeInput(checker, expression)) return true;
|
|
122
|
+
const inner = withoutGrouping(expression);
|
|
123
|
+
switch (inner.kind) {
|
|
124
|
+
case 'arrayLiteral':
|
|
125
|
+
return inner.elements.some((element) => readsInput(checker, element));
|
|
126
|
+
case 'unary':
|
|
127
|
+
return readsInput(checker, inner.operand);
|
|
128
|
+
case 'binary':
|
|
129
|
+
return readsInput(checker, inner.left) || readsInput(checker, inner.right);
|
|
130
|
+
case 'ternary':
|
|
131
|
+
return (
|
|
132
|
+
readsInput(checker, inner.condition) ||
|
|
133
|
+
readsInput(checker, inner.whenTrue) ||
|
|
134
|
+
readsInput(checker, inner.whenFalse)
|
|
135
|
+
);
|
|
136
|
+
case 'call':
|
|
137
|
+
return inner.args.some((argument) => readsInput(checker, argument.value));
|
|
138
|
+
default:
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** One setting and nothing else: an `input()` call, or a name that holds one. */
|
|
144
|
+
function isWholeInput(checker: Checker, expression: Expression): boolean {
|
|
145
|
+
const inner = withoutGrouping(expression);
|
|
146
|
+
if (inner.kind === 'call') return calleeName(inner.callee) === 'input';
|
|
147
|
+
if (inner.kind !== 'nameReference') return false;
|
|
148
|
+
const binding = checker.lookup(inner.name);
|
|
149
|
+
return binding !== undefined && inputHeldBy(binding) !== undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
95
152
|
/**
|
|
96
153
|
* Whether the expression names one of the price series an `input` may default
|
|
97
154
|
* to, `stdlib.md` 13.1.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OS8019: a name, or an array, still holding an object the script deleted.
|
|
3
|
+
*
|
|
4
|
+
* `language.md` 5.4: deleting an object touches neither the name nor the array
|
|
5
|
+
* element that refers to it, so what is left is stale rather than absent, and
|
|
6
|
+
* the next setter that reaches it stops the bar with OS4005, usually many bars
|
|
7
|
+
* after the line that caused it. The warning is about the line that caused it.
|
|
8
|
+
*
|
|
9
|
+
* The rule is read on one path, which is the block the delete is written in.
|
|
10
|
+
* A `draw.delete(name)` is cleared by a later statement of the same block that
|
|
11
|
+
* assigns the name again, `none` or a new object alike. A
|
|
12
|
+
* `draw.delete(element(arr, i))`, or `arr[i]`, is cleared by a later statement
|
|
13
|
+
* of the same block that takes an element out of the array or empties it. Only
|
|
14
|
+
* a name that outlives the bar is followed: a plain name is recomputed on the
|
|
15
|
+
* next bar, and a stale value in it cannot reach one.
|
|
16
|
+
*
|
|
17
|
+
* What this does not see, and does not claim to: a clearing line in a nested
|
|
18
|
+
* block, which may not run, and a deletion reached through a function's
|
|
19
|
+
* parameter, where the name the caller holds is not in view.
|
|
20
|
+
*/
|
|
21
|
+
import type { AstNode, Block, Call, Expression, Script } from '../ast/index.js';
|
|
22
|
+
import { childrenOf, withoutGrouping } from '../ast/index.js';
|
|
23
|
+
import type { Binding } from './checked.js';
|
|
24
|
+
import type { Checker } from './checker.js';
|
|
25
|
+
import { elementOf } from './types.js';
|
|
26
|
+
|
|
27
|
+
/** The calls that take an element out of an array or empty it, `language.md` 14.1. */
|
|
28
|
+
const REMOVING = new Set(['shift', 'pop', 'remove', 'clear']);
|
|
29
|
+
|
|
30
|
+
interface Held {
|
|
31
|
+
readonly binding: Binding;
|
|
32
|
+
/** Whether the object sits in an element of the array the binding names. */
|
|
33
|
+
readonly inArray: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function reportDeletedStillHeld(checker: Checker): void {
|
|
37
|
+
// One walk of the whole tree, paid only by a file that deletes something.
|
|
38
|
+
if (!checker.calls.some((one) => one.name === 'draw.delete')) return;
|
|
39
|
+
visit(checker, checker.script);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function visit(checker: Checker, node: AstNode): void {
|
|
43
|
+
if (node.kind === 'script' || node.kind === 'block') {
|
|
44
|
+
reportList(checker, statementsOf(node));
|
|
45
|
+
}
|
|
46
|
+
for (const child of childrenOf(node)) visit(checker, child);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function statementsOf(node: Script | Block): readonly AstNode[] {
|
|
50
|
+
return node.kind === 'script' ? node.items : node.statements;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function reportList(checker: Checker, statements: readonly AstNode[]): void {
|
|
54
|
+
statements.forEach((statement, index) => {
|
|
55
|
+
const call = deleteCall(checker, statement);
|
|
56
|
+
if (call === undefined) return;
|
|
57
|
+
const argument = call.args[0]?.value;
|
|
58
|
+
if (argument === undefined) return;
|
|
59
|
+
const held = heldBy(checker, argument);
|
|
60
|
+
if (held === undefined) return;
|
|
61
|
+
const kind = objectKindOf(checker, argument);
|
|
62
|
+
if (kind === undefined) return;
|
|
63
|
+
const later = statements.slice(index + 1);
|
|
64
|
+
const cleared = held.inArray
|
|
65
|
+
? later.some((one) => removesFrom(checker, one, held.binding))
|
|
66
|
+
: later.some((one) => assigns(one, held.binding.name));
|
|
67
|
+
if (cleared) return;
|
|
68
|
+
checker.report('OS8019', argument.span, {
|
|
69
|
+
name: held.binding.name,
|
|
70
|
+
kind,
|
|
71
|
+
line: call.span.line,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The `draw.delete(...)` a statement is, when it is one. */
|
|
77
|
+
function deleteCall(checker: Checker, statement: AstNode): Call | undefined {
|
|
78
|
+
if (statement.kind !== 'expressionStatement') return undefined;
|
|
79
|
+
const expression = withoutGrouping(statement.expression);
|
|
80
|
+
if (expression.kind !== 'call') return undefined;
|
|
81
|
+
return checker.callSites.get(expression)?.name === 'draw.delete' ? expression : undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The persistent name the deleted object is still reachable through. */
|
|
85
|
+
function heldBy(checker: Checker, argument: Expression): Held | undefined {
|
|
86
|
+
const written = withoutGrouping(argument);
|
|
87
|
+
if (written.kind === 'nameReference') {
|
|
88
|
+
const binding = persistent(checker, written);
|
|
89
|
+
return binding === undefined ? undefined : { binding, inArray: false };
|
|
90
|
+
}
|
|
91
|
+
const array = arrayOf(checker, written);
|
|
92
|
+
if (array === undefined) return undefined;
|
|
93
|
+
const binding = persistent(checker, array);
|
|
94
|
+
return binding === undefined ? undefined : { binding, inArray: true };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** `element(arr, i)` or `arr[i]`, as the array expression it reads. */
|
|
98
|
+
function arrayOf(checker: Checker, written: Expression): Expression | undefined {
|
|
99
|
+
if (written.kind === 'index') return withoutGrouping(written.target);
|
|
100
|
+
if (written.kind === 'call' && checker.callSites.get(written)?.name === 'element') {
|
|
101
|
+
const first = written.args[0]?.value;
|
|
102
|
+
return first === undefined ? undefined : withoutGrouping(first);
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function persistent(checker: Checker, expression: Expression): Binding | undefined {
|
|
108
|
+
if (expression.kind !== 'nameReference') return undefined;
|
|
109
|
+
const binding = checker.references.get(expression);
|
|
110
|
+
return binding !== undefined && binding.persistence !== 'none' ? binding : undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function objectKindOf(checker: Checker, argument: Expression): string | undefined {
|
|
114
|
+
const type = checker.types.get(argument);
|
|
115
|
+
if (type === undefined) return undefined;
|
|
116
|
+
const element = elementOf(type);
|
|
117
|
+
return element.kind === 'object' ? element.object : undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function assigns(statement: AstNode, name: string): boolean {
|
|
121
|
+
return statement.kind === 'assignment' && statement.target.text === name;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Whether any call in the statement takes an element out of this array. */
|
|
125
|
+
function removesFrom(checker: Checker, node: AstNode, binding: Binding): boolean {
|
|
126
|
+
if (node.kind === 'call') {
|
|
127
|
+
const name = checker.callSites.get(node)?.name;
|
|
128
|
+
const first = node.args[0]?.value;
|
|
129
|
+
if (name !== undefined && REMOVING.has(name) && first !== undefined) {
|
|
130
|
+
const target = withoutGrouping(first);
|
|
131
|
+
if (target.kind === 'nameReference' && checker.references.get(target) === binding) return true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return childrenOf(node).some((child) => removesFrom(checker, child, binding));
|
|
135
|
+
}
|
|
@@ -203,7 +203,7 @@ function checkNameReference(checker: Checker, expression: NameReference): Type {
|
|
|
203
203
|
}
|
|
204
204
|
binding.isRead = true;
|
|
205
205
|
checker.references.set(expression, binding);
|
|
206
|
-
return checker.record(expression, binding.type, binding
|
|
206
|
+
return checker.record(expression, binding.type, checker.warmupOfRead(binding));
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
const entries = libraryEntries(expression.name);
|
|
@@ -77,6 +77,7 @@ const declarations: readonly LibraryEntry[] = [
|
|
|
77
77
|
topLevel: true,
|
|
78
78
|
values: { style: PLOT_STYLES, format: FORMATS, scale: SCALES },
|
|
79
79
|
constant: ['title', 'width', 'style', 'offset', 'overlay', 'precision', 'format', 'scale'],
|
|
80
|
+
written: ['style'],
|
|
80
81
|
whole: { precision: wholeRange(0, 10), offset: wholeRange() },
|
|
81
82
|
},
|
|
82
83
|
),
|
|
@@ -118,24 +118,24 @@ export const LIBRARY_PROSE: Readonly<Record<string, LibraryProse>> = {
|
|
|
118
118
|
"dpo": {"summary":"Price with its displaced mean removed, to expose a cycle","warmup":"bar `len + floor(len / 2)`"},
|
|
119
119
|
"draw.box": {"summary":"A zone: supply, demand, an opening range"},
|
|
120
120
|
"draw.count": {"summary":"How many objects this script currently holds"},
|
|
121
|
-
"draw.delete": {"summary":"
|
|
121
|
+
"draw.delete": {"summary":"Remove one object"},
|
|
122
122
|
"draw.deleteAll": {"summary":"Remove every object this script created"},
|
|
123
123
|
"draw.label": {"summary":"A plate of text at a point"},
|
|
124
124
|
"draw.line": {"summary":"A trendline between two points"},
|
|
125
125
|
"draw.polyline": {"summary":"A path or a closed shape through many points"},
|
|
126
126
|
"draw.setAt": {"summary":"Move a label"},
|
|
127
|
-
"draw.setBounds": {"summary":"
|
|
128
|
-
"draw.setColor": {"summary":"
|
|
127
|
+
"draw.setBounds": {"summary":"Move both anchors in one call"},
|
|
128
|
+
"draw.setColor": {"summary":"Change the line or border colour"},
|
|
129
129
|
"draw.setExtend": {"summary":"Continue a line to the pane edge"},
|
|
130
|
-
"draw.setFillColor": {"summary":"
|
|
131
|
-
"draw.setFrom": {"summary":"
|
|
130
|
+
"draw.setFillColor": {"summary":"Change a box's or polyline's fill"},
|
|
131
|
+
"draw.setFrom": {"summary":"Move a line's or box's first anchor"},
|
|
132
132
|
"draw.setPoints": {"summary":"Replace a polyline's path"},
|
|
133
133
|
"draw.setStyle": {"summary":"`\"solid\"`, `\"dashed\"` or `\"dotted\"`"},
|
|
134
|
-
"draw.setText": {"summary":"
|
|
135
|
-
"draw.setTextColor": {"summary":"
|
|
136
|
-
"draw.setTo": {"summary":"
|
|
137
|
-
"draw.setTooltip": {"summary":"
|
|
138
|
-
"draw.setWidth": {"summary":"
|
|
134
|
+
"draw.setText": {"summary":"Change a label's or box's caption"},
|
|
135
|
+
"draw.setTextColor": {"summary":"Change the text colour"},
|
|
136
|
+
"draw.setTo": {"summary":"Move its second anchor"},
|
|
137
|
+
"draw.setTooltip": {"summary":"Detail shown while the pointer rests on the object"},
|
|
138
|
+
"draw.setWidth": {"summary":"Change the line thickness"},
|
|
139
139
|
"element": {"summary":"Read element `i`"},
|
|
140
140
|
"ema": {"summary":"Exponential mean, weight `2 / (len + 1)`, seeded on bar `len - 1` with the simple average of those `len` values","warmup":"bar `len - 1`"},
|
|
141
141
|
"eom": {"summary":"How far price moved per unit of volume","warmup":"bar `len`"},
|
|
@@ -156,6 +156,12 @@ export interface LibraryEntry {
|
|
|
156
156
|
readonly whole: Readonly<Record<string, WholeRange>>;
|
|
157
157
|
/** Arguments read once before bar 0, so a bar-dependent one is OS3003. */
|
|
158
158
|
readonly constant: readonly string[];
|
|
159
|
+
/**
|
|
160
|
+
* Arguments the compiled format carries as a plain value, with no input
|
|
161
|
+
* reference beside it, so a setting written into one is OS3026 rather than
|
|
162
|
+
* folded to its default with nothing said.
|
|
163
|
+
*/
|
|
164
|
+
readonly written: readonly string[];
|
|
159
165
|
/** Pairs that set the same thing two ways, so giving both is OS3010. */
|
|
160
166
|
readonly conflicts: readonly (readonly [string, string])[];
|
|
161
167
|
}
|
|
@@ -171,6 +177,7 @@ export interface EntryOptions {
|
|
|
171
177
|
readonly undeclared?: readonly string[];
|
|
172
178
|
readonly whole?: Readonly<Record<string, WholeRange>>;
|
|
173
179
|
readonly constant?: readonly string[];
|
|
180
|
+
readonly written?: readonly string[];
|
|
174
181
|
readonly conflicts?: readonly (readonly [string, string])[];
|
|
175
182
|
}
|
|
176
183
|
|
|
@@ -327,6 +334,7 @@ export function entry(signature: string, options: EntryOptions = {}): LibraryEnt
|
|
|
327
334
|
undeclared: options.undeclared ?? [],
|
|
328
335
|
whole: options.whole ?? {},
|
|
329
336
|
constant: options.constant ?? [],
|
|
337
|
+
written: options.written ?? [],
|
|
330
338
|
conflicts: options.conflicts ?? [],
|
|
331
339
|
};
|
|
332
340
|
}
|
|
@@ -73,6 +73,22 @@ export function libraryNames(): readonly string[] {
|
|
|
73
73
|
return [...INDEX.keys()];
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Every name the library lets a script call, for OS2010's suggestion.
|
|
78
|
+
*
|
|
79
|
+
* OS2010 is a call to something that is not a function, so the name it offers
|
|
80
|
+
* has to be one that is: offering `blue` for `volume(20)` would hand the reader
|
|
81
|
+
* a second OS2010. A planned name is left out too, because calling it is OS2020,
|
|
82
|
+
* and so is an order function outside a strategy, because calling it is OS7001.
|
|
83
|
+
*/
|
|
84
|
+
export function libraryFunctionNames(inStrategy: boolean): readonly string[] {
|
|
85
|
+
return [...INDEX.entries()]
|
|
86
|
+
.filter(([, entries]) =>
|
|
87
|
+
entries.some((one) => one.callable && !one.planned && (inStrategy || !one.strategyOnly)),
|
|
88
|
+
)
|
|
89
|
+
.map(([name]) => name);
|
|
90
|
+
}
|
|
91
|
+
|
|
76
92
|
/** The members of one namespace, spelled bare, for OS2009's suggestion. */
|
|
77
93
|
export function membersOf(namespace: string): readonly string[] {
|
|
78
94
|
const prefix = `${namespace}.`;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the bars a run reads come from: an array of records, or columns.
|
|
3
|
+
*
|
|
4
|
+
* The bar cycle reads one bar at a time and does not care where its fields come
|
|
5
|
+
* from, so the representation is decided at the door and nowhere else. Two are
|
|
6
|
+
* accepted (issue 0005):
|
|
7
|
+
*
|
|
8
|
+
* - **Records**, one `HostBar` per bar, which is the live path: one bar arrives
|
|
9
|
+
* at a time and an array of one is absurd.
|
|
10
|
+
* - **Columns**, one array per field, owned by the host. A long history held as
|
|
11
|
+
* columns costs about a third of the memory of one object per bar and no
|
|
12
|
+
* allocation per bar at rest, and a store that already holds bars as columns
|
|
13
|
+
* can hand over typed array views with no parse at all. The engine reads
|
|
14
|
+
* `open[i]` when bar `i` executes and builds the one record that execution
|
|
15
|
+
* needs, which is gone by the next bar.
|
|
16
|
+
*
|
|
17
|
+
* **In a column, absence is `null` or `NaN`.** A typed array cannot hold `null`,
|
|
18
|
+
* and a price the host does not have is absent, never zero (`host-interface.md`
|
|
19
|
+
* 3.1), so `NaN` is the typed array's spelling of it. A time that is `NaN` is a
|
|
20
|
+
* bar with no time, which the hand-over refuses with OS6025 exactly as it
|
|
21
|
+
* refuses a record with none.
|
|
22
|
+
*
|
|
23
|
+
* **The number of bars is the length of `time`.** A shorter price, volume or
|
|
24
|
+
* interest column reads as absent past its end, which is the same thing a hole in
|
|
25
|
+
* a record is, and nothing is invented to fill it.
|
|
26
|
+
*/
|
|
27
|
+
import type { HostBar } from './bars.js';
|
|
28
|
+
|
|
29
|
+
/** Numbers by bar index, absent written as `null` or `NaN`. */
|
|
30
|
+
export type BarColumn = ArrayLike<number | null>;
|
|
31
|
+
|
|
32
|
+
/** A history as columns, one per field of `host-interface.md` 3.1. */
|
|
33
|
+
export interface BarColumns {
|
|
34
|
+
readonly time: BarColumn;
|
|
35
|
+
readonly open: BarColumn;
|
|
36
|
+
readonly high: BarColumn;
|
|
37
|
+
readonly low: BarColumn;
|
|
38
|
+
readonly close: BarColumn;
|
|
39
|
+
readonly volume?: BarColumn;
|
|
40
|
+
readonly oi?: BarColumn;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** What the bar cycle and the fold read: how many bars, and bar `i`. */
|
|
44
|
+
export interface BarSource {
|
|
45
|
+
readonly length: number;
|
|
46
|
+
at(index: number): HostBar | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isColumns(bars: readonly HostBar[] | BarColumns): bars is BarColumns {
|
|
50
|
+
return !Array.isArray(bars);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Either form, as the bar cycle and the fold read it. */
|
|
54
|
+
export function sourceOf(bars: readonly HostBar[] | BarColumns): BarSource {
|
|
55
|
+
return isColumns(bars) ? columnSource(bars) : recordSource(bars);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function recordSource(bars: readonly HostBar[]): BarSource {
|
|
59
|
+
return { get length() { return bars.length; }, at: (index) => bars[index] };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cell(column: BarColumn | undefined, index: number): number | null {
|
|
63
|
+
const value = column === undefined || index >= column.length ? null : column[index];
|
|
64
|
+
return value === null || value === undefined || Number.isNaN(value) ? null : value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function columnSource(columns: BarColumns): BarSource {
|
|
68
|
+
const length = columns.time.length;
|
|
69
|
+
return {
|
|
70
|
+
length,
|
|
71
|
+
at(index: number): HostBar | undefined {
|
|
72
|
+
if (index < 0 || index >= length) return undefined;
|
|
73
|
+
return {
|
|
74
|
+
time: cell(columns.time, index),
|
|
75
|
+
open: cell(columns.open, index),
|
|
76
|
+
high: cell(columns.high, index),
|
|
77
|
+
low: cell(columns.low, index),
|
|
78
|
+
close: cell(columns.close, index),
|
|
79
|
+
volume: cell(columns.volume, index),
|
|
80
|
+
oi: cell(columns.oi, index),
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The bars a run has been handed: a whole history, then whatever arrives live.
|
|
88
|
+
*
|
|
89
|
+
* `run` replaces the history; `append` and `update` write one bar over it. A
|
|
90
|
+
* bar written live is held as the record it arrived as, which is the only form
|
|
91
|
+
* a live bar has.
|
|
92
|
+
*/
|
|
93
|
+
export class KnownBars implements BarSource {
|
|
94
|
+
private base: BarSource = recordSource([]);
|
|
95
|
+
private readonly live: HostBar[] = [];
|
|
96
|
+
|
|
97
|
+
get length(): number {
|
|
98
|
+
return Math.max(this.base.length, this.live.length);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
at(index: number): HostBar | undefined {
|
|
102
|
+
return this.live[index] ?? this.base.at(index);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
set(index: number, bar: HostBar): void {
|
|
106
|
+
this.live[index] = bar;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
reset(base: BarSource): void {
|
|
110
|
+
this.base = base;
|
|
111
|
+
this.live.length = 0;
|
|
112
|
+
}
|
|
113
|
+
}
|