openalgo-script 0.2.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 +1198 -0
- package/README.md +127 -40
- 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/surfaces.d.ts +17 -0
- package/dist/adapters/charts/surfaces.d.ts.map +1 -1
- package/dist/adapters/charts/tables.js +82 -0
- package/dist/adapters/charts/tables.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/adapters/codemirror/commands.d.ts +14 -0
- package/dist/adapters/codemirror/commands.d.ts.map +1 -0
- package/dist/adapters/codemirror/commands.js +52 -0
- package/dist/adapters/codemirror/commands.js.map +1 -0
- package/dist/adapters/codemirror/completion.d.ts +15 -0
- package/dist/adapters/codemirror/completion.d.ts.map +1 -0
- package/dist/adapters/codemirror/completion.js +64 -0
- package/dist/adapters/codemirror/completion.js.map +1 -0
- package/dist/adapters/codemirror/contract.d.ts +156 -0
- package/dist/adapters/codemirror/contract.d.ts.map +1 -0
- package/dist/adapters/codemirror/contract.js +42 -0
- package/dist/adapters/codemirror/contract.js.map +1 -0
- package/dist/adapters/codemirror/index.d.ts +43 -0
- package/dist/adapters/codemirror/index.d.ts.map +1 -0
- package/dist/adapters/codemirror/index.js +8 -0
- package/dist/adapters/codemirror/index.js.map +1 -0
- package/dist/adapters/codemirror/lint.d.ts +17 -0
- package/dist/adapters/codemirror/lint.d.ts.map +1 -0
- package/dist/adapters/codemirror/lint.js +62 -0
- package/dist/adapters/codemirror/lint.js.map +1 -0
- package/dist/adapters/codemirror/positions.d.ts +16 -0
- package/dist/adapters/codemirror/positions.d.ts.map +1 -0
- package/dist/adapters/codemirror/positions.js +65 -0
- package/dist/adapters/codemirror/positions.js.map +1 -0
- package/dist/adapters/codemirror/stream.d.ts +23 -0
- package/dist/adapters/codemirror/stream.d.ts.map +1 -0
- package/dist/adapters/codemirror/stream.js +70 -0
- package/dist/adapters/codemirror/stream.js.map +1 -0
- package/dist/adapters/codemirror/tokens.d.ts +44 -0
- package/dist/adapters/codemirror/tokens.d.ts.map +1 -0
- package/dist/adapters/codemirror/tokens.js +34 -0
- package/dist/adapters/codemirror/tokens.js.map +1 -0
- package/dist/adapters/codemirror/tooltips.d.ts +35 -0
- package/dist/adapters/codemirror/tooltips.d.ts.map +1 -0
- package/dist/adapters/codemirror/tooltips.js +108 -0
- package/dist/adapters/codemirror/tooltips.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/charges.d.ts +136 -0
- package/dist/core/accounting/charges.d.ts.map +1 -0
- package/dist/core/accounting/charges.js +362 -0
- package/dist/core/accounting/charges.js.map +1 -0
- package/dist/core/accounting/equity.d.ts +191 -0
- package/dist/core/accounting/equity.d.ts.map +1 -0
- package/dist/core/accounting/equity.js +167 -0
- package/dist/core/accounting/equity.js.map +1 -0
- package/dist/core/accounting/index.d.ts +46 -0
- package/dist/core/accounting/index.d.ts.map +1 -0
- package/dist/core/accounting/index.js +37 -0
- package/dist/core/accounting/index.js.map +1 -0
- package/dist/core/accounting/markers.d.ts +43 -0
- package/dist/core/accounting/markers.d.ts.map +1 -0
- package/dist/core/accounting/markers.js +64 -0
- package/dist/core/accounting/markers.js.map +1 -0
- package/dist/core/accounting/monthly.d.ts +52 -0
- package/dist/core/accounting/monthly.d.ts.map +1 -0
- package/dist/core/accounting/monthly.js +99 -0
- package/dist/core/accounting/monthly.js.map +1 -0
- package/dist/core/accounting/report.d.ts +41 -0
- package/dist/core/accounting/report.d.ts.map +1 -0
- package/dist/core/accounting/report.js +65 -0
- package/dist/core/accounting/report.js.map +1 -0
- package/dist/core/accounting/shapes.d.ts +70 -0
- package/dist/core/accounting/shapes.d.ts.map +1 -0
- package/dist/core/accounting/shapes.js +21 -0
- package/dist/core/accounting/shapes.js.map +1 -0
- package/dist/core/accounting/statistics.d.ts +87 -0
- package/dist/core/accounting/statistics.d.ts.map +1 -0
- package/dist/core/accounting/statistics.js +268 -0
- package/dist/core/accounting/statistics.js.map +1 -0
- package/dist/core/accounting/trades.d.ts +118 -0
- package/dist/core/accounting/trades.d.ts.map +1 -0
- package/dist/core/accounting/trades.js +186 -0
- package/dist/core/accounting/trades.js.map +1 -0
- 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 +38 -0
- package/dist/core/backtest/compare.d.ts.map +1 -0
- package/dist/core/backtest/compare.js +191 -0
- package/dist/core/backtest/compare.js.map +1 -0
- package/dist/core/backtest/declaration.d.ts +52 -0
- package/dist/core/backtest/declaration.d.ts.map +1 -0
- package/dist/core/backtest/declaration.js +48 -0
- package/dist/core/backtest/declaration.js.map +1 -0
- 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 +87 -0
- package/dist/core/backtest/drive.d.ts.map +1 -0
- package/dist/core/backtest/drive.js +312 -0
- package/dist/core/backtest/drive.js.map +1 -0
- package/dist/core/backtest/index.d.ts +58 -0
- package/dist/core/backtest/index.d.ts.map +1 -0
- package/dist/core/backtest/index.js +47 -0
- package/dist/core/backtest/index.js.map +1 -0
- package/dist/core/backtest/range.d.ts +84 -0
- package/dist/core/backtest/range.d.ts.map +1 -0
- package/dist/core/backtest/range.js +90 -0
- package/dist/core/backtest/range.js.map +1 -0
- package/dist/core/backtest/record.d.ts +299 -0
- package/dist/core/backtest/record.d.ts.map +1 -0
- package/dist/core/backtest/record.js +243 -0
- package/dist/core/backtest/record.js.map +1 -0
- package/dist/core/backtest/replay.d.ts +48 -0
- package/dist/core/backtest/replay.d.ts.map +1 -0
- package/dist/core/backtest/replay.js +137 -0
- package/dist/core/backtest/replay.js.map +1 -0
- package/dist/core/backtest/resting.d.ts +62 -0
- package/dist/core/backtest/resting.d.ts.map +1 -0
- package/dist/core/backtest/resting.js +59 -0
- package/dist/core/backtest/resting.js.map +1 -0
- package/dist/core/backtest/settings.d.ts +117 -0
- package/dist/core/backtest/settings.d.ts.map +1 -0
- package/dist/core/backtest/settings.js +207 -0
- package/dist/core/backtest/settings.js.map +1 -0
- package/dist/core/backtest/simulate.d.ts +258 -0
- package/dist/core/backtest/simulate.d.ts.map +1 -0
- package/dist/core/backtest/simulate.js +335 -0
- package/dist/core/backtest/simulate.js.map +1 -0
- package/dist/core/catalogue/catalogue.generated.d.ts +66 -0
- package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
- package/dist/core/catalogue/catalogue.generated.js +6 -0
- package/dist/core/catalogue/catalogue.generated.js.map +1 -1
- package/dist/core/catalogue/values.generated.d.ts +24 -0
- package/dist/core/catalogue/values.generated.d.ts.map +1 -1
- package/dist/core/check/index.d.ts +2 -1
- package/dist/core/check/index.d.ts.map +1 -1
- package/dist/core/check/index.js +1 -1
- package/dist/core/check/index.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.d.ts +16 -0
- package/dist/core/check/library-prose.generated.d.ts.map +1 -0
- package/dist/core/check/library-prose.generated.js +353 -0
- package/dist/core/check/library-prose.generated.js.map +1 -0
- package/dist/core/check/surface.d.ts +24 -0
- package/dist/core/check/surface.d.ts.map +1 -1
- package/dist/core/check/surface.js +29 -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/emit/defaults.d.ts +35 -16
- package/dist/core/emit/defaults.d.ts.map +1 -1
- package/dist/core/emit/defaults.js +66 -0
- package/dist/core/emit/defaults.js.map +1 -1
- package/dist/core/emit/index.d.ts +2 -0
- package/dist/core/emit/index.d.ts.map +1 -1
- package/dist/core/emit/index.js +2 -0
- package/dist/core/emit/index.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 +2 -2
- package/dist/core/engine/index.d.ts.map +1 -1
- package/dist/core/engine/index.js +2 -2
- 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 +35 -1
- package/dist/core/engine/load.d.ts.map +1 -1
- package/dist/core/engine/load.js +63 -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 +43 -4
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +23 -2
- 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/dist/editor/complete.d.ts +40 -0
- package/dist/editor/complete.d.ts.map +1 -0
- package/dist/editor/complete.js +206 -0
- package/dist/editor/complete.js.map +1 -0
- package/dist/editor/diagnose.d.ts +18 -0
- package/dist/editor/diagnose.d.ts.map +1 -0
- package/dist/editor/diagnose.js +70 -0
- package/dist/editor/diagnose.js.map +1 -0
- package/dist/editor/format.d.ts +11 -0
- package/dist/editor/format.d.ts.map +1 -0
- package/dist/editor/format.js +50 -0
- package/dist/editor/format.js.map +1 -0
- package/dist/editor/highlight.d.ts +28 -0
- package/dist/editor/highlight.d.ts.map +1 -0
- package/dist/editor/highlight.js +65 -0
- package/dist/editor/highlight.js.map +1 -0
- package/dist/editor/hover.d.ts +40 -0
- package/dist/editor/hover.d.ts.map +1 -0
- package/dist/editor/hover.js +147 -0
- package/dist/editor/hover.js.map +1 -0
- package/dist/editor/index.d.ts +60 -0
- package/dist/editor/index.d.ts.map +1 -0
- package/dist/editor/index.js +7 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/editor/kinds.d.ts +27 -0
- package/dist/editor/kinds.d.ts.map +1 -0
- package/dist/editor/kinds.js +118 -0
- package/dist/editor/kinds.js.map +1 -0
- package/dist/editor/layout.d.ts +47 -0
- package/dist/editor/layout.d.ts.map +1 -0
- package/dist/editor/layout.js +135 -0
- package/dist/editor/layout.js.map +1 -0
- package/dist/editor/manifest.d.ts +47 -0
- package/dist/editor/manifest.d.ts.map +1 -0
- package/dist/editor/manifest.js +93 -0
- package/dist/editor/manifest.js.map +1 -0
- package/dist/editor/reading.d.ts +41 -0
- package/dist/editor/reading.d.ts.map +1 -0
- package/dist/editor/reading.js +51 -0
- package/dist/editor/reading.js.map +1 -0
- package/dist/editor/scan.d.ts +26 -0
- package/dist/editor/scan.d.ts.map +1 -0
- package/dist/editor/scan.js +139 -0
- package/dist/editor/scan.js.map +1 -0
- package/dist/editor/scope.d.ts +19 -0
- package/dist/editor/scope.d.ts.map +1 -0
- package/dist/editor/scope.js +99 -0
- package/dist/editor/scope.js.map +1 -0
- package/dist/editor/signature.d.ts +38 -0
- package/dist/editor/signature.d.ts.map +1 -0
- package/dist/editor/signature.js +112 -0
- package/dist/editor/signature.js.map +1 -0
- package/dist/editor/site.d.ts +46 -0
- package/dist/editor/site.d.ts.map +1 -0
- package/dist/editor/site.js +184 -0
- package/dist/editor/site.js.map +1 -0
- package/dist/editor/spacing.d.ts +29 -0
- package/dist/editor/spacing.d.ts.map +1 -0
- package/dist/editor/spacing.js +116 -0
- package/dist/editor/spacing.js.map +1 -0
- package/package.json +42 -3
- package/spec/README.md +2 -1
- package/spec/errors.json +141 -1
- package/src/adapters/charts/driving.ts +109 -0
- package/src/adapters/charts/run.ts +120 -28
- package/src/adapters/charts/surfaces.ts +17 -0
- package/src/adapters/charts/tables.ts +88 -0
- package/src/adapters/charts/venue.ts +132 -0
- package/src/adapters/codemirror/commands.ts +53 -0
- package/src/adapters/codemirror/completion.ts +74 -0
- package/src/adapters/codemirror/contract.ts +156 -0
- package/src/adapters/codemirror/index.ts +66 -0
- package/src/adapters/codemirror/lint.ts +66 -0
- package/src/adapters/codemirror/positions.ts +79 -0
- package/src/adapters/codemirror/stream.ts +87 -0
- package/src/adapters/codemirror/tokens.ts +64 -0
- package/src/adapters/codemirror/tooltips.ts +113 -0
- package/src/core/accounting/analysis.ts +188 -0
- package/src/core/accounting/charges.ts +452 -0
- package/src/core/accounting/equity.ts +314 -0
- package/src/core/accounting/index.ts +51 -0
- package/src/core/accounting/markers.ts +95 -0
- package/src/core/accounting/monthly.ts +137 -0
- package/src/core/accounting/report.ts +94 -0
- package/src/core/accounting/shapes.ts +73 -0
- package/src/core/accounting/statistics.ts +387 -0
- package/src/core/accounting/trades.ts +313 -0
- package/src/core/backtest/case.ts +395 -0
- package/src/core/backtest/compare.ts +246 -0
- package/src/core/backtest/declaration.ts +97 -0
- package/src/core/backtest/deliver.ts +161 -0
- package/src/core/backtest/drive.ts +468 -0
- package/src/core/backtest/index.ts +64 -0
- package/src/core/backtest/range.ts +137 -0
- package/src/core/backtest/record.ts +470 -0
- package/src/core/backtest/replay.ts +168 -0
- package/src/core/backtest/resting.ts +125 -0
- package/src/core/backtest/settings.ts +280 -0
- package/src/core/backtest/simulate.ts +499 -0
- package/src/core/catalogue/catalogue.generated.ts +6 -0
- package/src/core/catalogue/values.generated.ts +6 -0
- package/src/core/check/index.ts +3 -0
- package/src/core/check/library-orders.ts +2 -2
- package/src/core/check/library-prose.generated.ts +367 -0
- package/src/core/check/surface.ts +34 -0
- package/src/core/emit/canonical.ts +67 -9
- package/src/core/emit/defaults.ts +70 -0
- package/src/core/emit/index.ts +2 -0
- package/src/core/engine/arithmetic.ts +23 -3
- package/src/core/engine/index.ts +2 -2
- 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 +84 -2
- package/src/core/engine/verify-tables.ts +43 -0
- package/src/core/index.ts +100 -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
- package/src/editor/complete.ts +266 -0
- package/src/editor/diagnose.ts +71 -0
- package/src/editor/format.ts +85 -0
- package/src/editor/highlight.ts +110 -0
- package/src/editor/hover.ts +199 -0
- package/src/editor/index.ts +65 -0
- package/src/editor/kinds.ts +157 -0
- package/src/editor/layout.ts +189 -0
- package/src/editor/manifest.ts +119 -0
- package/src/editor/reading.ts +69 -0
- package/src/editor/scan.ts +162 -0
- package/src/editor/scope.ts +122 -0
- package/src/editor/signature.ts +150 -0
- package/src/editor/site.ts +233 -0
- package/src/editor/spacing.ts +132 -0
package/spec/errors.json
CHANGED
|
@@ -2605,6 +2605,99 @@
|
|
|
2605
2605
|
"refines": null,
|
|
2606
2606
|
"test": "tests/engine/verify.test.ts"
|
|
2607
2607
|
},
|
|
2608
|
+
{
|
|
2609
|
+
"code": "OS6020",
|
|
2610
|
+
"title": "The report window holds no bars",
|
|
2611
|
+
"severity": "error",
|
|
2612
|
+
"stage": "host",
|
|
2613
|
+
"since": 1,
|
|
2614
|
+
"message": "The window from {from} to {to} holds none of the {count} bars supplied.",
|
|
2615
|
+
"placeholders": {
|
|
2616
|
+
"from": "the first moment the window covers, or the first bar supplied where the host named none",
|
|
2617
|
+
"to": "the last moment it covers",
|
|
2618
|
+
"count": "how many bars were supplied"
|
|
2619
|
+
},
|
|
2620
|
+
"cause": "A report is about the bars inside the window the host chose, and a window holding none of them has nothing to report: no equity point, no trade and no summary. The bars outside it are warmup, which execute and whose orders are real, so an empty window is not an empty run, and reporting one as a flat curve would tell a reader that nothing happened when something did.",
|
|
2621
|
+
"fix": "Widen the window until it covers bars, or supply the bars it covers. Both bounds are inclusive and are compared against the times of the bars supplied rather than against a calendar, so a window that falls inside a gap in the data is empty however wide it looks.",
|
|
2622
|
+
"autofix": false,
|
|
2623
|
+
"example": {
|
|
2624
|
+
"kind": "transcript",
|
|
2625
|
+
"before": "bars supplied: 1240, first day 1, last day 1240\nreport window: day 1300 to day 1400, bars inside 0",
|
|
2626
|
+
"after": "bars supplied: 1240, first day 1, last day 1240\nreport window: day 1100 to day 1240, bars inside 141"
|
|
2627
|
+
},
|
|
2628
|
+
"spec": "language.md 7.1",
|
|
2629
|
+
"refines": null,
|
|
2630
|
+
"test": "tests/backtest/range.test.ts"
|
|
2631
|
+
},
|
|
2632
|
+
{
|
|
2633
|
+
"code": "OS6021",
|
|
2634
|
+
"title": "A run setting cannot be applied as stated",
|
|
2635
|
+
"severity": "error",
|
|
2636
|
+
"stage": "host",
|
|
2637
|
+
"since": 1,
|
|
2638
|
+
"message": "{setting} cannot be applied: {problem}.",
|
|
2639
|
+
"placeholders": {
|
|
2640
|
+
"setting": "the setting that cannot be applied, such as the charge schedule or the comparison tolerance",
|
|
2641
|
+
"problem": "what makes it unusable, such as a charge line levied on a line declared after it"
|
|
2642
|
+
},
|
|
2643
|
+
"cause": "A run states the settings it is carried out under before its first bar, and a setting that cannot be carried out is refused there rather than quietly producing a figure. A charge line levied on lines not declared before it has no single evaluation order, so two engines would charge two different amounts and both would be defensible. A slippage stated in ticks with no tick size to measure a tick in would charge nothing at all, which is a backtest that lies in the strategy's favour. A comparison tolerance carrying a bound and no reason is a failed comparison somebody switched off, and one past the cap conformance.md section 6 puts on a declared tolerance is a comparison the suite will not accept, so a run that declared one is refused a case rather than written into one that fails every runner it meets.",
|
|
2644
|
+
"fix": "State the setting so it can be carried out: declare a charge line after every line it is levied on, supply the tick size a slippage in ticks is measured in, write down the reason a tolerance needs a bound, or bring a bound past the cap back inside it. Nothing has been computed at the point this is refused, so correcting the setting and running again costs one run.",
|
|
2645
|
+
"autofix": false,
|
|
2646
|
+
"example": {
|
|
2647
|
+
"kind": "transcript",
|
|
2648
|
+
"before": "charge lines: tax on levy, levy\nslippage: 2 ticks, tick size not supplied",
|
|
2649
|
+
"after": "charge lines: levy, tax on levy\nslippage: 2 ticks, tick size 0.05"
|
|
2650
|
+
},
|
|
2651
|
+
"spec": "stdlib.md 17.1",
|
|
2652
|
+
"refines": null,
|
|
2653
|
+
"test": "tests/accounting/schedules.test.ts"
|
|
2654
|
+
},
|
|
2655
|
+
{
|
|
2656
|
+
"code": "OS6022",
|
|
2657
|
+
"title": "The bars are not the bars the record was made from",
|
|
2658
|
+
"severity": "error",
|
|
2659
|
+
"stage": "host",
|
|
2660
|
+
"since": 1,
|
|
2661
|
+
"message": "The bars supplied hash to {found}, and the record was made from {expected}.",
|
|
2662
|
+
"placeholders": {
|
|
2663
|
+
"found": "the hash of the bars supplied now",
|
|
2664
|
+
"expected": "the hash the record names"
|
|
2665
|
+
},
|
|
2666
|
+
"cause": "A record names the bars it was made from by a hash over their canonical form, so a replay can prove it is replaying the same run rather than producing a different study under the same name. Bars are revised: a feed corrects a print, a session is extended, a split is applied to history. A replay over revised bars that reported the original figures would be the most convincing wrong answer this system can produce.",
|
|
2667
|
+
"fix": "Replay against the bars the record names. Where the revision is the point, make a second record over the revised bars and compare the two runs, rather than overwriting one run with the other under one name.",
|
|
2668
|
+
"autofix": false,
|
|
2669
|
+
"example": {
|
|
2670
|
+
"kind": "transcript",
|
|
2671
|
+
"before": "record: bars 1240, hash 9f2c4e...\nsupplied: bars 1240, hash 4ab70d...",
|
|
2672
|
+
"after": "record: bars 1240, hash 9f2c4e...\nsupplied: bars 1240, hash 9f2c4e..."
|
|
2673
|
+
},
|
|
2674
|
+
"spec": "conformance.md 3",
|
|
2675
|
+
"refines": null,
|
|
2676
|
+
"test": "tests/backtest/record.test.ts"
|
|
2677
|
+
},
|
|
2678
|
+
{
|
|
2679
|
+
"code": "OS6023",
|
|
2680
|
+
"title": "Two cost models are stated at once",
|
|
2681
|
+
"severity": "error",
|
|
2682
|
+
"stage": "host",
|
|
2683
|
+
"since": 1,
|
|
2684
|
+
"message": "A charge schedule was supplied, and the declaration states a commission of {commission} in {commissionType}.",
|
|
2685
|
+
"placeholders": {
|
|
2686
|
+
"commission": "the commission the declaration states",
|
|
2687
|
+
"commissionType": "the unit that commission is stated in"
|
|
2688
|
+
},
|
|
2689
|
+
"cause": "The declaration's commission is the script's own statement of what trading costs and a supplied schedule is the platform's, and the two describe the same money. Applied together they charge it twice; applied one at a time they charge whichever an engine happened to prefer, which is a rule nobody wrote down and a figure nobody can explain afterwards. So exactly one of the two is stated for a run, and stating both is refused before the first bar rather than reconciled behind the reader.",
|
|
2690
|
+
"fix": "Supply the schedule and leave the declaration's commission at its default of zero, or state the commission in the declaration and supply no schedule. A schedule is the one of the two that can carry a floor, a cap, a charge levied on a charge, and a cost that falls on one side of the trade only.",
|
|
2691
|
+
"autofix": false,
|
|
2692
|
+
"example": {
|
|
2693
|
+
"kind": "transcript",
|
|
2694
|
+
"before": "declaration: commission 20, per trade\nhost: charge schedule supplied, 4 lines",
|
|
2695
|
+
"after": "declaration: commission 0, per trade\nhost: charge schedule supplied, 4 lines"
|
|
2696
|
+
},
|
|
2697
|
+
"spec": "language.md 13.3; stdlib.md 17.1",
|
|
2698
|
+
"refines": null,
|
|
2699
|
+
"test": "tests/backtest/settings.test.ts"
|
|
2700
|
+
},
|
|
2608
2701
|
{
|
|
2609
2702
|
"code": "OS7001",
|
|
2610
2703
|
"title": "Only a strategy can do that",
|
|
@@ -2928,7 +3021,7 @@
|
|
|
2928
3021
|
"since": 1,
|
|
2929
3022
|
"message": "This strategy placed an order and the host supplied no destination.",
|
|
2930
3023
|
"placeholders": {},
|
|
2931
|
-
"cause": "A strategy needs somewhere for orders to go: a
|
|
3024
|
+
"cause": "A strategy needs somewhere for orders to go: a sandbox engine, a backtest simulator or a broker connection. Running one with no destination would compute a position nothing ever took.",
|
|
2932
3025
|
"fix": "Connect a destination in the host, or run the file as a study(): replace buy() with signal(\"BUY\").",
|
|
2933
3026
|
"autofix": false,
|
|
2934
3027
|
"example": {
|
|
@@ -2984,6 +3077,53 @@
|
|
|
2984
3077
|
"refines": null,
|
|
2985
3078
|
"test": "tests/engine/closing.test.ts"
|
|
2986
3079
|
},
|
|
3080
|
+
{
|
|
3081
|
+
"code": "OS7018",
|
|
3082
|
+
"title": "A frame names an order this strategy did not place",
|
|
3083
|
+
"severity": "error",
|
|
3084
|
+
"stage": "host",
|
|
3085
|
+
"since": 1,
|
|
3086
|
+
"message": "The frame names intent {intent}, and this strategy holds no such order.",
|
|
3087
|
+
"placeholders": {
|
|
3088
|
+
"intent": "the intent id the frame named"
|
|
3089
|
+
},
|
|
3090
|
+
"cause": "Step 1 of the fold locates the row a frame is about, and a frame naming no row cannot be folded into anything. It is a fact about the host rather than about the strategy: a destination answering for an order another strategy placed, or answering for a run that has already ended. The frame changes nothing and is reported, because a fold that passed over it in silence would leave a host's mistake invisible to the only party who can correct it.",
|
|
3091
|
+
"fix": "Answer with the intent id the engine sent. A destination's own reference is carried in the frame's reference field, where the engine records it and never parses it, and it is not what an answer is addressed by.",
|
|
3092
|
+
"autofix": false,
|
|
3093
|
+
"example": {
|
|
3094
|
+
"kind": "transcript",
|
|
3095
|
+
"before": "engine sent: intent 7, intent 8\nframe: intent 11, status filled, filled qty 1",
|
|
3096
|
+
"after": "engine sent: intent 7, intent 8\nframe: intent 8, status filled, filled qty 1"
|
|
3097
|
+
},
|
|
3098
|
+
"spec": "stdlib.md 17.8, 17.14",
|
|
3099
|
+
"refines": null,
|
|
3100
|
+
"test": null,
|
|
3101
|
+
"deferred": "Nothing raises this yet. The fold refuses the frame and records the refusal as a word on the outcome it hands back, and a word is not a catalogue code, so nothing reports it against the run and a host answering for orders it was never handed stays invisible. Raised when the outcome of a fold carries a code and a bar carries a channel for a diagnostic that does not stop it, stdlib.md 17.8."
|
|
3102
|
+
},
|
|
3103
|
+
{
|
|
3104
|
+
"code": "OS7019",
|
|
3105
|
+
"title": "A fill was reported with no price",
|
|
3106
|
+
"severity": "error",
|
|
3107
|
+
"stage": "host",
|
|
3108
|
+
"since": 1,
|
|
3109
|
+
"message": "The frame reports {qty} filled for intent {intent}, and no average fill price.",
|
|
3110
|
+
"placeholders": {
|
|
3111
|
+
"qty": "the cumulative filled quantity the frame reports",
|
|
3112
|
+
"intent": "the intent id the frame named"
|
|
3113
|
+
},
|
|
3114
|
+
"cause": "Step 3 of the fold takes the destination's own average over the cumulative quantity, because the engine never averages two averages of its own. A frame reporting more filled than the row holds and no price to take is a fill that can be marked against nothing: no position average, no realised profit, no equity point. It is refused whole rather than folded for its quantity alone, because a position holding a size and no price is worse than no position at all.",
|
|
3115
|
+
"fix": "Report the average fill price the destination computed over the cumulative quantity, on every frame that reports a quantity greater than the last one. A frame carrying no new quantity needs no price.",
|
|
3116
|
+
"autofix": false,
|
|
3117
|
+
"example": {
|
|
3118
|
+
"kind": "transcript",
|
|
3119
|
+
"before": "frame: intent 8, status filled, filled qty 3, average fill price absent",
|
|
3120
|
+
"after": "frame: intent 8, status filled, filled qty 3, average fill price 104.25"
|
|
3121
|
+
},
|
|
3122
|
+
"spec": "stdlib.md 17.8, 17.14",
|
|
3123
|
+
"refines": null,
|
|
3124
|
+
"test": null,
|
|
3125
|
+
"deferred": "Nothing raises this yet. The fold refuses the frame and records the refusal as a word on the outcome it hands back, and a word is not a catalogue code, so a destination reporting a fill that can be marked against nothing is refused in silence. Raised when the outcome of a fold carries a code and a bar carries a channel for a diagnostic that does not stop it, stdlib.md 17.8."
|
|
3126
|
+
},
|
|
2987
3127
|
{
|
|
2988
3128
|
"code": "OS8001",
|
|
2989
3129
|
"title": "A stateful call inside a branch",
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walking the bars, for a study and for a strategy.
|
|
3
|
+
*
|
|
4
|
+
* Split from `run.ts` because they are two things. That file decides what an
|
|
5
|
+
* engine is loaded with: the host record, the clock, the settings, the venue.
|
|
6
|
+
* This one decides how the bars are then handed over, and the two differ by
|
|
7
|
+
* exactly one thing, whether frames have to reach the engine between them.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { CompiledProgram } from '../../core/emit/index.js';
|
|
11
|
+
import type { Engine } from '../../core/engine/index.js';
|
|
12
|
+
import { hostBar, stateFor } from './bars.js';
|
|
13
|
+
import type { ChartBar, ChartCalcContext, ChartSettings } from './contract.js';
|
|
14
|
+
import { stopped } from './errors.js';
|
|
15
|
+
import { signatureOf } from './settings.js';
|
|
16
|
+
import { answersFor } from './venue.js';
|
|
17
|
+
import type { HeldVenue } from './venue.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A run in hand, held between a chart's recomputes.
|
|
21
|
+
*
|
|
22
|
+
* The signature and the bar times are what a tail run is checked against before
|
|
23
|
+
* it is trusted: splicing a tail onto a history that has changed underneath it
|
|
24
|
+
* draws a wrong study that looks entirely plausible.
|
|
25
|
+
*/
|
|
26
|
+
export interface Held {
|
|
27
|
+
readonly engine: Engine;
|
|
28
|
+
readonly signature: string;
|
|
29
|
+
count: number;
|
|
30
|
+
firstTime: number;
|
|
31
|
+
lastTime: number;
|
|
32
|
+
/**
|
|
33
|
+
* The destination this run's orders go to, for a strategy.
|
|
34
|
+
*
|
|
35
|
+
* Held with the engine because the two are one run: the venue holds the
|
|
36
|
+
* orders that have not filled yet, and an engine continued onto a new bar
|
|
37
|
+
* against a fresh venue would have its working orders silently forgotten.
|
|
38
|
+
*/
|
|
39
|
+
readonly venue: HeldVenue | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A study: every bar in one hand-over, which is what `run` is for. */
|
|
43
|
+
export function runWhole(
|
|
44
|
+
engine: Engine,
|
|
45
|
+
bars: readonly ChartBar[],
|
|
46
|
+
ctx: ChartCalcContext | undefined,
|
|
47
|
+
program: CompiledProgram,
|
|
48
|
+
settings: ChartSettings,
|
|
49
|
+
): Held {
|
|
50
|
+
const result = engine.run(
|
|
51
|
+
bars.map(hostBar),
|
|
52
|
+
bars.map((_, index) => stateFor(index, bars, ctx)),
|
|
53
|
+
);
|
|
54
|
+
if (result.diagnostic !== undefined) throw stopped(result.diagnostic);
|
|
55
|
+
return heldFrom(engine, null, bars, program, settings);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A strategy: one bar at a time, with the venue answering between them.
|
|
60
|
+
*
|
|
61
|
+
* The order is deliver, execute, then ask. A driver that asked the venue before
|
|
62
|
+
* executing would price a fill against a bar the strategy had not seen yet, and
|
|
63
|
+
* one that delivered after executing would let a script read a position its own
|
|
64
|
+
* order on this bar had just created.
|
|
65
|
+
*
|
|
66
|
+
* `supplied` is the whole count rather than the index reached, so `bar.isLast`
|
|
67
|
+
* means the same on this path as on the one above it: a strategy written to act
|
|
68
|
+
* on the final bar acts on the final bar, not on every bar in turn.
|
|
69
|
+
*/
|
|
70
|
+
export function walkWith(
|
|
71
|
+
engine: Engine,
|
|
72
|
+
held: HeldVenue,
|
|
73
|
+
bars: readonly ChartBar[],
|
|
74
|
+
ctx: ChartCalcContext | undefined,
|
|
75
|
+
program: CompiledProgram,
|
|
76
|
+
settings: ChartSettings,
|
|
77
|
+
): Held {
|
|
78
|
+
for (let index = 0; index < bars.length; index += 1) {
|
|
79
|
+
const bar = bars[index];
|
|
80
|
+
if (bar === undefined) continue;
|
|
81
|
+
|
|
82
|
+
for (const frame of held.pending) engine.deliver(frame);
|
|
83
|
+
held.pending = [];
|
|
84
|
+
|
|
85
|
+
const result = engine.append(hostBar(bar), stateFor(index, bars, ctx), bars.length);
|
|
86
|
+
if (result.diagnostic !== undefined) throw stopped(result.diagnostic);
|
|
87
|
+
|
|
88
|
+
answersFor(held, index);
|
|
89
|
+
}
|
|
90
|
+
return heldFrom(engine, held, bars, program, settings);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function heldFrom(
|
|
94
|
+
engine: Engine,
|
|
95
|
+
venue: HeldVenue | null,
|
|
96
|
+
bars: readonly ChartBar[],
|
|
97
|
+
program: CompiledProgram,
|
|
98
|
+
settings: ChartSettings,
|
|
99
|
+
): Held {
|
|
100
|
+
return {
|
|
101
|
+
engine,
|
|
102
|
+
venue,
|
|
103
|
+
signature: signatureOf(program, settings),
|
|
104
|
+
count: bars.length,
|
|
105
|
+
firstTime: bars[0]?.time ?? 0,
|
|
106
|
+
lastTime: bars[bars.length - 1]?.time ?? 0,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
@@ -41,6 +41,11 @@ import type { ChartBar, ChartCalcContext, ChartSettings, ChartStore } from './co
|
|
|
41
41
|
import { refused, stopped } from './errors.js';
|
|
42
42
|
import { stationIn, stationOf } from './requests.js';
|
|
43
43
|
import { engineSettings, signatureOf } from './settings.js';
|
|
44
|
+
import { answersFor, needsVenue, routeInto, venueFor } from './venue.js';
|
|
45
|
+
import type { HeldVenue } from './venue.js';
|
|
46
|
+
import { runWhole, walkWith } from './driving.js';
|
|
47
|
+
import type { Held } from './driving.js';
|
|
48
|
+
import type { Simulator } from '../../core/backtest/index.js';
|
|
44
49
|
|
|
45
50
|
/** What a host tells the adapter that neither the chart nor the program says. */
|
|
46
51
|
export interface ChartAdapterOptions {
|
|
@@ -116,6 +121,26 @@ export interface ChartAdapterOptions {
|
|
|
116
121
|
* (`stdlib.md` 17.1), never handed over by the chart.
|
|
117
122
|
*/
|
|
118
123
|
readonly orders?: EffectRoute;
|
|
124
|
+
/**
|
|
125
|
+
* Run a strategy against a simulated destination where the host gives none.
|
|
126
|
+
*
|
|
127
|
+
* **Off by default, because the refusal it replaces is useful.** A strategy
|
|
128
|
+
* with nowhere to send an order is refused at load with OS6006 naming the
|
|
129
|
+
* capability, which is exactly what a host that meant to wire a destination
|
|
130
|
+
* and forgot needs to be told. A chart that quietly filled one in for them
|
|
131
|
+
* would draw a convincing strategy that routed nothing.
|
|
132
|
+
*
|
|
133
|
+
* **On, it is the venue a backtest uses.** A chart drawing a strategy then
|
|
134
|
+
* fills the same orders at the same prices as the report of the same script,
|
|
135
|
+
* so the marks on the price and the trades in the report are one answer
|
|
136
|
+
* rather than two. This is what a host turns on to draw a strategy the way it
|
|
137
|
+
* draws a study: the plots, the legend row and the settings all follow from
|
|
138
|
+
* the program running at all.
|
|
139
|
+
*
|
|
140
|
+
* It does not place an order anywhere. A host that wants that supplies
|
|
141
|
+
* `orders`, which wins over this.
|
|
142
|
+
*/
|
|
143
|
+
readonly simulateOrders?: boolean;
|
|
119
144
|
readonly limits?: Partial<EngineLimits>;
|
|
120
145
|
readonly clock?: Clock;
|
|
121
146
|
/**
|
|
@@ -130,14 +155,6 @@ export interface ChartAdapterOptions {
|
|
|
130
155
|
}
|
|
131
156
|
|
|
132
157
|
/** The engine one chart instance is holding, between recomputes. */
|
|
133
|
-
interface Held {
|
|
134
|
-
readonly engine: Engine;
|
|
135
|
-
readonly signature: string;
|
|
136
|
-
count: number;
|
|
137
|
-
firstTime: number;
|
|
138
|
-
lastTime: number;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
158
|
/** The store key. Namespaced, because the store belongs to the host as well. */
|
|
142
159
|
const HELD = 'openscript';
|
|
143
160
|
|
|
@@ -168,20 +185,20 @@ export function fullRun(
|
|
|
168
185
|
options: ChartAdapterOptions,
|
|
169
186
|
): RunOutput {
|
|
170
187
|
const station = stationIn(store);
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
bars.map(hostBar),
|
|
174
|
-
bars.map((_, index) => stateFor(index, bars, ctx)),
|
|
175
|
-
);
|
|
176
|
-
if (result.diagnostic !== undefined) throw stopped(result.diagnostic);
|
|
188
|
+
const started = start(program, settings, ctx, options, station.provider(bars), bars);
|
|
189
|
+
const engine = started.engine;
|
|
177
190
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
191
|
+
// **A strategy is walked bar by bar and a study is handed the lot.** The
|
|
192
|
+
// difference is the venue: its frames reach the engine between bars, which is
|
|
193
|
+
// the only place they can, so a strategy's position is right on the bar after
|
|
194
|
+
// the one it traded on. `run()` has no gap to put them in.
|
|
195
|
+
//
|
|
196
|
+
// It is also the loop the backtest uses, which is the point: a chart drawing a
|
|
197
|
+
// strategy and a report of the same strategy walk the same bars in the same
|
|
198
|
+
// order against the same venue, so they cannot disagree about what filled.
|
|
199
|
+
const held: Held = started.venue === null
|
|
200
|
+
? runWhole(engine, bars, ctx, program, settings)
|
|
201
|
+
: walkWith(engine, started.venue, bars, ctx, program, settings);
|
|
185
202
|
store[HELD] = held;
|
|
186
203
|
station.settle();
|
|
187
204
|
return outputOf(program, engine);
|
|
@@ -218,11 +235,29 @@ export function tailRun(
|
|
|
218
235
|
const bar = bars[index];
|
|
219
236
|
if (bar === undefined) return null;
|
|
220
237
|
const state = stateFor(index, bars, ctx);
|
|
238
|
+
|
|
239
|
+
// The venue's answers about the bar before this one, delivered before this
|
|
240
|
+
// one runs, exactly as the full walk does. A tail that skipped this would
|
|
241
|
+
// draw the first bars of a strategy correctly and then quietly stop folding
|
|
242
|
+
// its fills the moment the chart went live, which is the half of the run
|
|
243
|
+
// nobody re-checks.
|
|
244
|
+
//
|
|
245
|
+
// Only when the bar is new. Re-executing the bar that moved must not
|
|
246
|
+
// deliver again: a frame is cumulative and folding one twice is harmless,
|
|
247
|
+
// but the bar's own orders have been rolled back and answering them a
|
|
248
|
+
// second time would fill an order the engine no longer knows it sent.
|
|
249
|
+
if (held.venue !== null && index !== from) {
|
|
250
|
+
for (const frame of held.venue.pending) held.engine.deliver(frame);
|
|
251
|
+
held.venue.pending = [];
|
|
252
|
+
}
|
|
253
|
+
|
|
221
254
|
const result =
|
|
222
255
|
index === from
|
|
223
256
|
? held.engine.update(hostBar(bar), state)
|
|
224
257
|
: held.engine.append(hostBar(bar), state);
|
|
225
258
|
if (result.diagnostic !== undefined) throw stopped(result.diagnostic);
|
|
259
|
+
|
|
260
|
+
if (held.venue !== null) answersFor(held.venue, index);
|
|
226
261
|
}
|
|
227
262
|
|
|
228
263
|
held.count = bars.length;
|
|
@@ -257,23 +292,65 @@ function outputOf(program: CompiledProgram, engine: Engine): RunOutput {
|
|
|
257
292
|
return { columns, tables: engine.tables(), drawings: engine.drawings() };
|
|
258
293
|
}
|
|
259
294
|
|
|
295
|
+
/** A loaded engine, and the destination its orders go to where it has one. */
|
|
296
|
+
interface Started {
|
|
297
|
+
readonly engine: Engine;
|
|
298
|
+
readonly venue: HeldVenue | null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Load the program, and give a strategy somewhere for its orders to go.
|
|
303
|
+
*
|
|
304
|
+
* **The venue is built after the load and reached through a holder, because
|
|
305
|
+
* neither can come first.** The route has to be handed to `load`, since that is
|
|
306
|
+
* what declares the `orders` capability and a program needing one is otherwise
|
|
307
|
+
* refused. The venue has to be built after it, because the declaration it reads
|
|
308
|
+
* may state its fill rule through an `input()`, and inputs are not resolved
|
|
309
|
+
* until `load` has run. The route is only ever called from inside an execution,
|
|
310
|
+
* which is after both, so the holder is always filled by the time anything
|
|
311
|
+
* reaches it.
|
|
312
|
+
*
|
|
313
|
+
* **A host that supplied its own route keeps it.** Somewhere real to send an
|
|
314
|
+
* order is a better destination than a simulated one, and a host that wired one
|
|
315
|
+
* up meant it.
|
|
316
|
+
*
|
|
317
|
+
* **And simulation is asked for rather than assumed.** Without
|
|
318
|
+
* `simulateOrders` a strategy with nowhere to send an order is still refused at
|
|
319
|
+
* load, which is the answer a host that meant to wire a destination and forgot
|
|
320
|
+
* needs to see. Filling in a venue for them would turn that mistake into a
|
|
321
|
+
* chart that draws convincingly and routes nothing, discovered whenever
|
|
322
|
+
* somebody next looked for the orders.
|
|
323
|
+
*/
|
|
260
324
|
function start(
|
|
261
325
|
program: CompiledProgram,
|
|
262
326
|
settings: ChartSettings,
|
|
263
327
|
ctx: ChartCalcContext | undefined,
|
|
264
328
|
options: ChartAdapterOptions,
|
|
265
329
|
requests: RequestProvider,
|
|
266
|
-
|
|
330
|
+
bars: readonly ChartBar[],
|
|
331
|
+
): Started {
|
|
332
|
+
const holder: { current: Simulator | null } = { current: null };
|
|
333
|
+
const simulate = options.simulateOrders === true && options.orders === undefined && needsVenue(program);
|
|
334
|
+
const withRoute: ChartAdapterOptions = simulate
|
|
335
|
+
? { ...options, orders: routeInto(holder) }
|
|
336
|
+
: options;
|
|
337
|
+
|
|
267
338
|
const loaded = load(program, {
|
|
268
339
|
settings: engineSettings(program, settings),
|
|
269
|
-
host: hostFor(ctx,
|
|
340
|
+
host: hostFor(ctx, withRoute, requests),
|
|
270
341
|
time: timeFor(options, ctx?.timezone ?? ''),
|
|
271
342
|
...(options.limits === undefined ? {} : { limits: options.limits }),
|
|
272
343
|
...(options.source === undefined ? {} : { source: options.source }),
|
|
273
344
|
...(options.clock === undefined ? {} : { clock: options.clock }),
|
|
274
345
|
});
|
|
275
346
|
if (!loaded.ok) throw refused(loaded.diagnostic);
|
|
276
|
-
|
|
347
|
+
|
|
348
|
+
if (!simulate) return { engine: loaded.engine, venue: null };
|
|
349
|
+
|
|
350
|
+
const venue = venueFor(program, bars, loaded.inputs, instrumentFor(ctx, options));
|
|
351
|
+
if (venue === null) return { engine: loaded.engine, venue: null };
|
|
352
|
+
holder.current = venue;
|
|
353
|
+
return { engine: loaded.engine, venue: { venue, pending: [] } };
|
|
277
354
|
}
|
|
278
355
|
|
|
279
356
|
/**
|
|
@@ -286,12 +363,19 @@ function start(
|
|
|
286
363
|
* the chart's own sentence in `req.error`, instead of being refused at load
|
|
287
364
|
* with OS6006 naming a capability the chart does have.
|
|
288
365
|
*/
|
|
289
|
-
|
|
366
|
+
/**
|
|
367
|
+
* The instrument record, from what the host stated and what the chart knows.
|
|
368
|
+
*
|
|
369
|
+
* Its own function because two callers need it and they must not each build
|
|
370
|
+
* one: the engine is loaded with this record, and the venue prices fills
|
|
371
|
+
* against the tick size in it. Two spellings of the same record is how a fill
|
|
372
|
+
* gets rounded to a tick the strategy was never told about.
|
|
373
|
+
*/
|
|
374
|
+
function instrumentFor(
|
|
290
375
|
ctx: ChartCalcContext | undefined,
|
|
291
376
|
options: ChartAdapterOptions,
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const instrument: Instrument = {
|
|
377
|
+
): Instrument {
|
|
378
|
+
return {
|
|
295
379
|
...(options.instrument ?? {}),
|
|
296
380
|
...(ctx?.symbol === undefined ? {} : { symbol: ctx.symbol }),
|
|
297
381
|
...(ctx?.interval === undefined ? {} : { interval: ctx.interval }),
|
|
@@ -302,6 +386,14 @@ function hostFor(
|
|
|
302
386
|
// the chart it was drawn on.
|
|
303
387
|
...(ctx === undefined || ctx.timezone === '' ? {} : { timezone: ctx.timezone }),
|
|
304
388
|
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function hostFor(
|
|
392
|
+
ctx: ChartCalcContext | undefined,
|
|
393
|
+
options: ChartAdapterOptions,
|
|
394
|
+
requests: RequestProvider,
|
|
395
|
+
): EngineHost {
|
|
396
|
+
const instrument = instrumentFor(ctx, options);
|
|
305
397
|
return {
|
|
306
398
|
instrument,
|
|
307
399
|
requestBars: requests,
|
|
@@ -100,6 +100,23 @@ export interface ChartTableOptions {
|
|
|
100
100
|
readonly borderWidth: number;
|
|
101
101
|
/** The backdrop behind the whole grid, which is the grid's own `bgColor`. */
|
|
102
102
|
readonly background?: string;
|
|
103
|
+
/**
|
|
104
|
+
* Column widths in media pixels, one per column.
|
|
105
|
+
*
|
|
106
|
+
* The chart's own default is one flat width for every column, which is a
|
|
107
|
+
* width chosen without seeing the text. A grid whose first column says
|
|
108
|
+
* "Moving averages" and whose second says "RSI: 27.22" has two columns that
|
|
109
|
+
* want different room, and one number cannot give it to both.
|
|
110
|
+
*/
|
|
111
|
+
readonly cellWidth?: readonly number[];
|
|
112
|
+
/**
|
|
113
|
+
* `'auto'` to shrink a cell's type until its text fits the cell it is in.
|
|
114
|
+
*
|
|
115
|
+
* The chart measures the text it is about to draw, which nothing upstream of
|
|
116
|
+
* it can do, so this is what makes a column width that is slightly wrong
|
|
117
|
+
* merely slightly loose instead of two cells written over each other.
|
|
118
|
+
*/
|
|
119
|
+
readonly fontSize?: number | 'auto';
|
|
103
120
|
}
|
|
104
121
|
|
|
105
122
|
/** A grid of cells pinned to a corner, and the options it is drawn with. */
|
|
@@ -118,10 +118,98 @@ function oneTable(
|
|
|
118
118
|
position: CORNERS[position] ?? 'top-right',
|
|
119
119
|
borderWidth: numberField(declared.options.borderWidth, lookup, 0),
|
|
120
120
|
...(bgColour === undefined ? {} : { background: cssColour(bgColour) }),
|
|
121
|
+
...(cols > 0 ? { cellWidth: columnWidths(grid, cols) } : {}),
|
|
122
|
+
// The chart measures the text it is about to draw and shrinks it to fit,
|
|
123
|
+
// which is the half of this that cannot be done from here.
|
|
124
|
+
fontSize: 'auto',
|
|
121
125
|
},
|
|
122
126
|
};
|
|
123
127
|
}
|
|
124
128
|
|
|
129
|
+
/**
|
|
130
|
+
* How wide each column has to be, in media pixels.
|
|
131
|
+
*
|
|
132
|
+
* The chart's default is one width for every column, chosen without seeing the
|
|
133
|
+
* text, and a grid whose header says "Moving averages" over cells saying
|
|
134
|
+
* "RSI: 27.22" has one column bleeding into the next. Nothing downstream can
|
|
135
|
+
* choose better, because by the time the chart has the grid it has lost which
|
|
136
|
+
* cells belong together; and nothing upstream can, because the script declares
|
|
137
|
+
* a size and writes text, not a layout.
|
|
138
|
+
*
|
|
139
|
+
* **This is an estimate, and it is allowed to be.** A real width needs the font
|
|
140
|
+
* the chart will draw with, and only the chart has it. So the width is counted
|
|
141
|
+
* off the characters and the chart's own `fontSize: 'auto'` corrects whatever
|
|
142
|
+
* this gets wrong: too narrow and the type shrinks a little, too wide and the
|
|
143
|
+
* column is a little loose. Either is a table that reads. The failure this
|
|
144
|
+
* replaces was neither.
|
|
145
|
+
*
|
|
146
|
+
* Delete it when the chart can size a column from its own measurements. It
|
|
147
|
+
* exists because that is not a thing it can be asked to do yet.
|
|
148
|
+
*/
|
|
149
|
+
function columnWidths(grid: readonly (readonly ChartCell[])[], cols: number): number[] {
|
|
150
|
+
const widths: number[] = [];
|
|
151
|
+
for (let col = 0; col < cols; col += 1) {
|
|
152
|
+
let widest = 0;
|
|
153
|
+
for (const row of grid) {
|
|
154
|
+
const text = row[col]?.text ?? '';
|
|
155
|
+
if (text.length > 0) widest = Math.max(widest, textWidth(text));
|
|
156
|
+
}
|
|
157
|
+
widths.push(Math.min(MAX_COLUMN, Math.max(MIN_COLUMN, Math.ceil(widest) + CELL_PADDING * 2)));
|
|
158
|
+
}
|
|
159
|
+
return widths;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Roughly how wide a string is at the type size the chart starts from.
|
|
164
|
+
*
|
|
165
|
+
* Counted rather than measured, and counted in two weights rather than one: a
|
|
166
|
+
* column of "Histogram: -7.01" is half punctuation and narrow digits, and
|
|
167
|
+
* treating every character as an em-and-a-bit makes that column half again as
|
|
168
|
+
* wide as it needs to be while "Moving averages" stays too narrow. Two weights
|
|
169
|
+
* is not typography, but it is enough to tell those two apart.
|
|
170
|
+
*/
|
|
171
|
+
function textWidth(text: string): number {
|
|
172
|
+
let width = 0;
|
|
173
|
+
for (const character of text) {
|
|
174
|
+
width += NARROW.has(character) ? BASE_SIZE * NARROW_EM : BASE_SIZE * WIDE_EM;
|
|
175
|
+
}
|
|
176
|
+
return width;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The characters that take noticeably less than an average advance. */
|
|
180
|
+
const NARROW = new Set([...' .,:;!|ijlt1IJfr()[]{}-']);
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The type size the widths are counted at.
|
|
184
|
+
*
|
|
185
|
+
* The chart's own default row is 18 media pixels and it draws at 62% of the
|
|
186
|
+
* row, so this is the size a cell gets before anything shrinks it. Counting at
|
|
187
|
+
* a larger size would reserve room the text never uses.
|
|
188
|
+
*/
|
|
189
|
+
const BASE_SIZE = 11;
|
|
190
|
+
const NARROW_EM = 0.32;
|
|
191
|
+
const WIDE_EM = 0.56;
|
|
192
|
+
|
|
193
|
+
/** What the chart insets a cell's text by, on each side. */
|
|
194
|
+
const CELL_PADDING = 4;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The narrowest a column is drawn.
|
|
198
|
+
*
|
|
199
|
+
* A column of one-character cells sized to its content is a sliver, and a grid
|
|
200
|
+
* of slivers reads as a rendering fault rather than as a narrow column.
|
|
201
|
+
*/
|
|
202
|
+
const MIN_COLUMN = 56;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The widest a column is drawn.
|
|
206
|
+
*
|
|
207
|
+
* A cell holding a sentence would otherwise push the grid past the pane it is
|
|
208
|
+
* pinned inside, taking the chart with it. Past this the type shrinks instead,
|
|
209
|
+
* which is the chart's job and it is better at it.
|
|
210
|
+
*/
|
|
211
|
+
const MAX_COLUMN = 220;
|
|
212
|
+
|
|
125
213
|
/**
|
|
126
214
|
* One written cell, or none.
|
|
127
215
|
*
|