openalgo-script 0.4.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 +1050 -0
- package/README.md +111 -27
- 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 +89 -16
- 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/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 +101 -8
- package/dist/core/backtest/drive.d.ts.map +1 -1
- package/dist/core/backtest/drive.js +114 -158
- package/dist/core/backtest/drive.js.map +1 -1
- package/dist/core/backtest/index.d.ts +26 -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/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 +179 -2
- package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
- package/dist/core/catalogue/catalogue.generated.js +19 -2
- 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-orders.js +2 -2
- package/dist/core/check/library-orders.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 +12 -12
- 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/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/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 +2 -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/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/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 +13 -0
- package/dist/core/engine/library/index.d.ts.map +1 -1
- package/dist/core/engine/library/index.js +15 -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/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/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/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/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 +22 -5
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +19 -3
- 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/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 +17 -2
- package/spec/README.md +2 -1
- package/spec/errors.json +383 -23
- package/src/adapters/charts/driving.ts +109 -0
- package/src/adapters/charts/run.ts +126 -28
- package/src/adapters/charts/tables.ts +6 -19
- package/src/adapters/charts/undrawable.ts +51 -0
- 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 +233 -217
- package/src/core/backtest/index.ts +26 -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/backtest/walk.ts +182 -0
- package/src/core/catalogue/catalogue.generated.ts +19 -2
- 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-orders.ts +2 -2
- package/src/core/check/library-output.ts +1 -0
- package/src/core/check/library-prose.generated.ts +12 -12
- package/src/core/check/library.ts +8 -0
- package/src/core/check/surface.ts +16 -0
- package/src/core/emit/canonical.ts +67 -9
- package/src/core/engine/arithmetic.ts +23 -3
- 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 +2 -1
- package/src/core/engine/library/arrays.ts +8 -2
- package/src/core/engine/library/binding.ts +2 -0
- package/src/core/engine/library/code-points.ts +73 -0
- package/src/core/engine/library/index.ts +17 -0
- package/src/core/engine/library/objects.ts +1 -6
- package/src/core/engine/library/text.ts +53 -35
- package/src/core/engine/load.ts +68 -0
- 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/engine/verify-tables.ts +43 -0
- 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 +29 -2
- package/src/core/stdlib/calendar/civil.ts +5 -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
|
@@ -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);
|
|
@@ -240,7 +240,7 @@ const legs: readonly LibraryEntry[] = [
|
|
|
240
240
|
}),
|
|
241
241
|
entry('leg.stop(name: string, price: number) -> nothing', { ...strategyOnly, planned: true }),
|
|
242
242
|
entry('leg.target(name: string, price: number) -> nothing', { ...strategyOnly, planned: true }),
|
|
243
|
-
entry('leg.trail(name: string, distance: number,
|
|
243
|
+
entry('leg.trail(name: string, distance: number, activateAt?: number = none) -> nothing', {
|
|
244
244
|
...strategyOnly,
|
|
245
245
|
planned: true,
|
|
246
246
|
}),
|
|
@@ -258,7 +258,7 @@ const book: readonly LibraryEntry[] = [
|
|
|
258
258
|
entry('book.stop(amount: number) -> nothing', { ...strategyOnly, planned: true }),
|
|
259
259
|
entry('book.target(amount: number) -> nothing', { ...strategyOnly, planned: true }),
|
|
260
260
|
entry(
|
|
261
|
-
'book.lockProfit(
|
|
261
|
+
'book.lockProfit(activateAt: number, lock: number, step?: number = none, advance?: number = none) -> nothing',
|
|
262
262
|
{ ...strategyOnly, planned: true },
|
|
263
263
|
),
|
|
264
264
|
entry('book.trailStopsToEntry(at: number) -> nothing', { ...strategyOnly, planned: true }),
|
|
@@ -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
|
),
|
|
@@ -49,7 +49,7 @@ export const LIBRARY_PROSE: Readonly<Record<string, LibraryProse>> = {
|
|
|
49
49
|
"book.exit": {"summary":"Exit the whole book as a unit"},
|
|
50
50
|
"book.exitAt": {"summary":"Square off every leg at this `\"HHMM\"` in the chart's timezone"},
|
|
51
51
|
"book.isOpen": {"summary":"Whether any leg holds a position","warmup":"bar 0"},
|
|
52
|
-
"book.lockProfit": {"summary":"
|
|
52
|
+
"book.lockProfit": {"summary":"Activate a floor at a profit, then advance it as profit grows"},
|
|
53
53
|
"book.profit": {"summary":"The book's profit in money, open and realised since it was last flat","warmup":"bar 0"},
|
|
54
54
|
"book.squareOffAtExpiry": {"summary":"Square off a leg this many minutes before its contract expires"},
|
|
55
55
|
"book.stop": {"summary":"Square off every leg when the book's profit falls to `-amount`"},
|
|
@@ -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`"},
|
|
@@ -332,7 +332,7 @@ export const LIBRARY_PROSE: Readonly<Record<string, LibraryProse>> = {
|
|
|
332
332
|
"str.split": {"summary":"Split into parts"},
|
|
333
333
|
"str.startsWith": {"summary":"Prefix test"},
|
|
334
334
|
"str.substring": {"summary":"`from` inclusive, `to` exclusive, to the end when `to` is absent"},
|
|
335
|
-
"str.trim": {"summary":"Leading and trailing
|
|
335
|
+
"str.trim": {"summary":"Leading and trailing whitespace removed: the code points listed below"},
|
|
336
336
|
"str.upper": {"summary":"Upper case, invariant, not locale dependent"},
|
|
337
337
|
"sum": {"summary":"Total over the window","warmup":"bar `len - 1`"},
|
|
338
338
|
"sumSkip": {"summary":"Total over the window, ignoring absent bars","warmup":"bar `len - 1`"},
|
|
@@ -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}.`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The canonical encoding, `compiled-program.md` 2.14.
|
|
2
|
+
* The canonical encoding, `compiled-program.md` 2.14, and the one number writer.
|
|
3
3
|
*
|
|
4
4
|
* It exists so that a hash of a program means something: two compilers, in two
|
|
5
5
|
* languages, handed the same source, produce the same bytes. So none of the
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* - Object keys sorted ascending by Unicode code point. Sorted rather than in
|
|
10
10
|
* the specification's listed order, because a sort is a rule an emitter in any
|
|
11
11
|
* language can follow without a table.
|
|
12
|
-
* - A number is the
|
|
13
|
-
*
|
|
12
|
+
* - A number is written by the rule of `language.md` 5.5, which is the same
|
|
13
|
+
* rule for every number that becomes text anywhere in the language.
|
|
14
14
|
* - A string escapes only the quote, the backslash and the code points below
|
|
15
15
|
* 0x20, the last as `\u00XX` except for `\n`, `\r` and `\t`.
|
|
16
16
|
*/
|
|
@@ -18,16 +18,74 @@ import type { CompiledProgram } from './program.js';
|
|
|
18
18
|
import { sha256 } from './sha256.js';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* A number
|
|
21
|
+
* A number as text: the one writer.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* Two engines compare numbers as bits, but they compare text in a case file, an
|
|
24
|
+
* expected column, a table cell and `text(x)`, so how a number becomes text has
|
|
25
|
+
* to be one rule that both implement, and in this repository it has to be one
|
|
26
|
+
* function. This is that function. `scripts/check-number-writer.mjs` reads
|
|
27
|
+
* every file under `src` and refuses a conversion outside this module, so a
|
|
28
|
+
* number cannot reach text by a host's default somewhere else and disagree with
|
|
29
|
+
* this one by a plus sign.
|
|
30
|
+
*
|
|
31
|
+
* **The digits are the host's and the layout is not.** The shortest decimal
|
|
32
|
+
* digit string that reads back as the same binary64 is what every host this
|
|
33
|
+
* engine runs on produces, and what a second engine's host produces too; that
|
|
34
|
+
* part is taken from the host's own shortest form. Where the two hosts part
|
|
35
|
+
* company is the layout, when a value is written positionally and when with an
|
|
36
|
+
* exponent, and how the exponent is spelled, so that half is written here from
|
|
37
|
+
* the rule in `language.md` 5.5 and not left to the host. The rule is the one
|
|
38
|
+
* the first host follows natively, minus the `+` it writes on a positive
|
|
39
|
+
* exponent, and `spec/vectors/number-text.json` holds the boundary cases a
|
|
40
|
+
* second engine checks itself against.
|
|
27
41
|
*/
|
|
28
42
|
export function canonicalNumber(value: number): string {
|
|
29
43
|
if (!Number.isFinite(value)) throw new Error('a compiled program holds finite numbers only');
|
|
30
|
-
|
|
44
|
+
// Zero and negative zero are one value to the language (compiled-program.md
|
|
45
|
+
// 3.1) and one spelling here.
|
|
46
|
+
if (value === 0) return '0';
|
|
47
|
+
const { digits, point } = shortestDigits(Math.abs(value));
|
|
48
|
+
return (value < 0 ? '-' : '') + layout(digits, point);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The shortest round trip digits of a positive finite magnitude, and where the
|
|
53
|
+
* decimal point falls: the value is `0.d1d2...dk` times ten to the `point`.
|
|
54
|
+
*
|
|
55
|
+
* Read out of the host's own shortest form rather than computed here, because
|
|
56
|
+
* the search for the shortest digit string is the one part of the conversion
|
|
57
|
+
* every host already agrees on. Whatever layout the host chose is undone: the
|
|
58
|
+
* digits and the point are all that is kept.
|
|
59
|
+
*/
|
|
60
|
+
function shortestDigits(magnitude: number): { readonly digits: string; readonly point: number } {
|
|
61
|
+
const shown = String(magnitude);
|
|
62
|
+
const e = shown.indexOf('e');
|
|
63
|
+
const mantissa = e < 0 ? shown : shown.slice(0, e);
|
|
64
|
+
const exponent = e < 0 ? 0 : Number(shown.slice(e + 1));
|
|
65
|
+
const dot = mantissa.indexOf('.');
|
|
66
|
+
const whole = dot < 0 ? mantissa : mantissa.slice(0, dot);
|
|
67
|
+
const fraction = dot < 0 ? '' : mantissa.slice(dot + 1);
|
|
68
|
+
let digits = whole + fraction;
|
|
69
|
+
let point = whole.length + exponent;
|
|
70
|
+
// A positional form below one carries leading zeros that are not digits of
|
|
71
|
+
// the value, and a whole number carries trailing zeros that are its layout.
|
|
72
|
+
while (digits.startsWith('0')) {
|
|
73
|
+
digits = digits.slice(1);
|
|
74
|
+
point -= 1;
|
|
75
|
+
}
|
|
76
|
+
while (digits.endsWith('0')) digits = digits.slice(0, -1);
|
|
77
|
+
return { digits, point };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** `language.md` 5.5: positional between the two thresholds, an exponent outside them. */
|
|
81
|
+
function layout(digits: string, point: number): string {
|
|
82
|
+
const count = digits.length;
|
|
83
|
+
if (count <= point && point <= 21) return digits + '0'.repeat(point - count);
|
|
84
|
+
if (0 < point && point <= 21) return `${digits.slice(0, point)}.${digits.slice(point)}`;
|
|
85
|
+
if (-6 < point && point <= 0) return `0.${'0'.repeat(-point)}${digits}`;
|
|
86
|
+
const exponent = point - 1;
|
|
87
|
+
const lead = count === 1 ? digits : `${digits[0]}.${digits.slice(1)}`;
|
|
88
|
+
return `${lead}e${exponent < 0 ? '-' : ''}${String(Math.abs(exponent))}`;
|
|
31
89
|
}
|
|
32
90
|
|
|
33
91
|
export function canonicalString(value: string): string {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* what a script wants when it wraps an index. Collapsing them would silently
|
|
24
24
|
* change one of the two.
|
|
25
25
|
*/
|
|
26
|
+
import { compareStrings } from './library/index.js';
|
|
26
27
|
import type { Value } from './values/index.js';
|
|
27
28
|
import { ABSENT, numberValue, valuesEqual } from './values/index.js';
|
|
28
29
|
|
|
@@ -99,12 +100,17 @@ function numeric(
|
|
|
99
100
|
* absence is treated two ways, and it is deliberate: an operator that can
|
|
100
101
|
* itself be absent gives a script no way to ask whether a value is absent at
|
|
101
102
|
* all, so equality is the exception and every other comparison is not.
|
|
103
|
+
*
|
|
104
|
+
* **Two strings are ordered by code point** (`language.md` 9.3, `stdlib.md`
|
|
105
|
+
* section 10), through the one order `sort` uses, and not by the host's own
|
|
106
|
+
* operator, which orders by sixteen bit unit and puts a symbol outside the
|
|
107
|
+
* basic plane below the last thousands of the plane. Numbers take the host's
|
|
108
|
+
* operator, which is the binary64 order every engine shares.
|
|
102
109
|
*/
|
|
103
110
|
export function compare(opcode: 'LT' | 'LE' | 'GT' | 'GE', a: Value, b: Value): Value {
|
|
104
111
|
if (a === null || b === null) return ABSENT;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (!bothNumbers && !bothStrings) throw new OperandMismatch(opcode);
|
|
112
|
+
if (typeof a === 'string' && typeof b === 'string') return ordered(opcode, compareStrings(a, b));
|
|
113
|
+
if (typeof a !== 'number' || typeof b !== 'number') throw new OperandMismatch(opcode);
|
|
108
114
|
switch (opcode) {
|
|
109
115
|
case 'LT':
|
|
110
116
|
return a < b;
|
|
@@ -117,6 +123,20 @@ export function compare(opcode: 'LT' | 'LE' | 'GT' | 'GE', a: Value, b: Value):
|
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
|
|
126
|
+
/** An ordering comparison read off a three way order: negative, zero or positive. */
|
|
127
|
+
function ordered(opcode: 'LT' | 'LE' | 'GT' | 'GE', order: number): boolean {
|
|
128
|
+
switch (opcode) {
|
|
129
|
+
case 'LT':
|
|
130
|
+
return order < 0;
|
|
131
|
+
case 'LE':
|
|
132
|
+
return order <= 0;
|
|
133
|
+
case 'GT':
|
|
134
|
+
return order > 0;
|
|
135
|
+
default:
|
|
136
|
+
return order >= 0;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
120
140
|
/** `EQ` and `NE` are total: they always produce a boolean. */
|
|
121
141
|
export function equals(a: Value, b: Value): boolean {
|
|
122
142
|
return valuesEqual(a, b);
|
|
@@ -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
|
+
}
|