openalgo-script 0.5.0 → 0.7.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 +226 -0
- package/README.md +53 -22
- package/dist/adapters/charts/run.d.ts.map +1 -1
- package/dist/adapters/charts/run.js +6 -0
- package/dist/adapters/charts/run.js.map +1 -1
- package/dist/adapters/charts/tables.d.ts +6 -19
- package/dist/adapters/charts/tables.d.ts.map +1 -1
- package/dist/adapters/charts/tables.js.map +1 -1
- package/dist/adapters/charts/undrawable.d.ts +4 -0
- package/dist/adapters/charts/undrawable.d.ts.map +1 -0
- package/dist/adapters/charts/undrawable.js +47 -0
- package/dist/adapters/charts/undrawable.js.map +1 -0
- package/dist/core/backtest/drive.d.ts +36 -1
- package/dist/core/backtest/drive.d.ts.map +1 -1
- package/dist/core/backtest/drive.js +13 -107
- package/dist/core/backtest/drive.js.map +1 -1
- package/dist/core/backtest/index.d.ts +2 -0
- package/dist/core/backtest/index.d.ts.map +1 -1
- package/dist/core/backtest/index.js.map +1 -1
- package/dist/core/backtest/walk.d.ts +39 -0
- package/dist/core/backtest/walk.d.ts.map +1 -0
- package/dist/core/backtest/walk.js +125 -0
- package/dist/core/backtest/walk.js.map +1 -0
- package/dist/core/catalogue/catalogue.generated.d.ts +178 -1
- package/dist/core/catalogue/catalogue.generated.d.ts.map +1 -1
- package/dist/core/catalogue/catalogue.generated.js +18 -1
- package/dist/core/catalogue/catalogue.generated.js.map +1 -1
- package/dist/core/catalogue/types.d.ts +8 -2
- package/dist/core/catalogue/types.d.ts.map +1 -1
- package/dist/core/catalogue/values.generated.d.ts +51 -0
- package/dist/core/catalogue/values.generated.d.ts.map +1 -1
- package/dist/core/check/assigned.d.ts +19 -0
- package/dist/core/check/assigned.d.ts.map +1 -0
- package/dist/core/check/assigned.js +14 -0
- package/dist/core/check/assigned.js.map +1 -0
- package/dist/core/check/call-sites.d.ts.map +1 -1
- package/dist/core/check/call-sites.js +9 -4
- package/dist/core/check/call-sites.js.map +1 -1
- package/dist/core/check/calls.d.ts.map +1 -1
- package/dist/core/check/calls.js +12 -4
- package/dist/core/check/calls.js.map +1 -1
- package/dist/core/check/check.d.ts.map +1 -1
- package/dist/core/check/check.js +15 -2
- package/dist/core/check/check.js.map +1 -1
- package/dist/core/check/checker.d.ts +26 -0
- package/dist/core/check/checker.d.ts.map +1 -1
- package/dist/core/check/checker.js +38 -2
- package/dist/core/check/checker.js.map +1 -1
- package/dist/core/check/constant.d.ts +16 -0
- package/dist/core/check/constant.d.ts.map +1 -1
- package/dist/core/check/constant.js +52 -0
- package/dist/core/check/constant.js.map +1 -1
- package/dist/core/check/deleted.d.ts +3 -0
- package/dist/core/check/deleted.d.ts.map +1 -0
- package/dist/core/check/deleted.js +109 -0
- package/dist/core/check/deleted.js.map +1 -0
- package/dist/core/check/expressions.js +1 -1
- package/dist/core/check/expressions.js.map +1 -1
- package/dist/core/check/library-output.d.ts.map +1 -1
- package/dist/core/check/library-output.js +1 -0
- package/dist/core/check/library-output.js.map +1 -1
- package/dist/core/check/library-prose.generated.js +10 -10
- package/dist/core/check/library-prose.generated.js.map +1 -1
- package/dist/core/check/library.d.ts +7 -0
- package/dist/core/check/library.d.ts.map +1 -1
- package/dist/core/check/library.js +1 -0
- package/dist/core/check/library.js.map +1 -1
- package/dist/core/check/surface.d.ts +9 -0
- package/dist/core/check/surface.d.ts.map +1 -1
- package/dist/core/check/surface.js +13 -0
- package/dist/core/check/surface.js.map +1 -1
- package/dist/core/engine/bar-source.d.ts +65 -0
- package/dist/core/engine/bar-source.d.ts.map +1 -0
- package/dist/core/engine/bar-source.js +58 -0
- package/dist/core/engine/bar-source.js.map +1 -0
- package/dist/core/engine/channels.js +2 -2
- package/dist/core/engine/channels.js.map +1 -1
- 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 +26 -27
- package/dist/core/engine/engine.js.map +1 -1
- package/dist/core/engine/guard.d.ts.map +1 -1
- package/dist/core/engine/guard.js +10 -0
- package/dist/core/engine/guard.js.map +1 -1
- package/dist/core/engine/host.d.ts +4 -2
- package/dist/core/engine/host.d.ts.map +1 -1
- package/dist/core/engine/host.js.map +1 -1
- package/dist/core/engine/index.d.ts +1 -0
- package/dist/core/engine/index.d.ts.map +1 -1
- package/dist/core/engine/index.js.map +1 -1
- package/dist/core/engine/library/binding.d.ts +2 -0
- package/dist/core/engine/library/binding.d.ts.map +1 -1
- package/dist/core/engine/library/binding.js.map +1 -1
- package/dist/core/engine/library/index.d.ts +8 -0
- package/dist/core/engine/library/index.d.ts.map +1 -1
- package/dist/core/engine/library/index.js +10 -0
- package/dist/core/engine/library/index.js.map +1 -1
- package/dist/core/engine/library/maths.js +2 -2
- package/dist/core/engine/library/maths.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/machine.js +5 -5
- package/dist/core/engine/machine.js.map +1 -1
- package/dist/core/engine/memory.js +2 -2
- package/dist/core/engine/memory.js.map +1 -1
- package/dist/core/engine/registers.js +2 -2
- package/dist/core/engine/registers.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/values/index.d.ts +1 -1
- package/dist/core/engine/values/index.d.ts.map +1 -1
- package/dist/core/engine/values/index.js +1 -1
- package/dist/core/engine/values/index.js.map +1 -1
- package/dist/core/engine/values/value.d.ts +2 -0
- package/dist/core/engine/values/value.d.ts.map +1 -1
- package/dist/core/engine/values/value.js +4 -0
- package/dist/core/engine/values/value.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 +12 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +11 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/stdlib/averages/exponential.d.ts.map +1 -1
- package/dist/core/stdlib/averages/exponential.js +3 -6
- package/dist/core/stdlib/averages/exponential.js.map +1 -1
- package/dist/core/stdlib/averages/gaussian.d.ts +15 -0
- package/dist/core/stdlib/averages/gaussian.d.ts.map +1 -0
- package/dist/core/stdlib/averages/gaussian.js +55 -0
- package/dist/core/stdlib/averages/gaussian.js.map +1 -0
- package/dist/core/stdlib/averages/shaped.d.ts +2 -6
- package/dist/core/stdlib/averages/shaped.d.ts.map +1 -1
- package/dist/core/stdlib/averages/shaped.js +8 -22
- package/dist/core/stdlib/averages/shaped.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/maths/arithmetic.d.ts.map +1 -1
- package/dist/core/stdlib/maths/arithmetic.js +2 -1
- package/dist/core/stdlib/maths/arithmetic.js.map +1 -1
- package/dist/core/stdlib/maths/elementary.d.ts +3 -20
- package/dist/core/stdlib/maths/elementary.d.ts.map +1 -1
- package/dist/core/stdlib/maths/elementary.js +17 -13
- package/dist/core/stdlib/maths/elementary.js.map +1 -1
- package/dist/core/stdlib/maths/hypot.d.ts +8 -0
- package/dist/core/stdlib/maths/hypot.d.ts.map +1 -0
- package/dist/core/stdlib/maths/hypot.js +68 -0
- package/dist/core/stdlib/maths/hypot.js.map +1 -0
- package/dist/core/stdlib/maths/power.d.ts +3 -0
- package/dist/core/stdlib/maths/power.d.ts.map +1 -0
- package/dist/core/stdlib/maths/power.js +83 -0
- package/dist/core/stdlib/maths/power.js.map +1 -0
- package/dist/core/stdlib/maths/transcendental.d.ts +15 -0
- package/dist/core/stdlib/maths/transcendental.d.ts.map +1 -0
- package/dist/core/stdlib/maths/transcendental.js +185 -0
- package/dist/core/stdlib/maths/transcendental.js.map +1 -0
- package/dist/core/stdlib/maths/trigonometric.d.ts +4 -0
- package/dist/core/stdlib/maths/trigonometric.d.ts.map +1 -0
- package/dist/core/stdlib/maths/trigonometric.js +196 -0
- package/dist/core/stdlib/maths/trigonometric.js.map +1 -0
- package/dist/core/stdlib/series/conditions.d.ts +2 -4
- package/dist/core/stdlib/series/conditions.d.ts.map +1 -1
- package/dist/core/stdlib/series/conditions.js +9 -13
- package/dist/core/stdlib/series/conditions.js.map +1 -1
- package/dist/core/stdlib/series/statistics.d.ts.map +1 -1
- package/dist/core/stdlib/series/statistics.js +2 -0
- package/dist/core/stdlib/series/statistics.js.map +1 -1
- package/dist/core/stdlib/values/history.d.ts +26 -0
- package/dist/core/stdlib/values/history.d.ts.map +1 -0
- package/dist/core/stdlib/values/history.js +52 -0
- package/dist/core/stdlib/values/history.js.map +1 -0
- package/dist/core/stdlib/values/index.d.ts +1 -0
- package/dist/core/stdlib/values/index.d.ts.map +1 -1
- package/dist/core/stdlib/values/index.js +1 -0
- package/dist/core/stdlib/values/index.js.map +1 -1
- package/dist/core/stdlib/values/lookback.d.ts +4 -1
- package/dist/core/stdlib/values/lookback.d.ts.map +1 -1
- package/dist/core/stdlib/values/lookback.js +48 -45
- package/dist/core/stdlib/values/lookback.js.map +1 -1
- package/dist/core/stdlib/values/region.d.ts +6 -5
- package/dist/core/stdlib/values/region.d.ts.map +1 -1
- package/dist/core/stdlib/values/region.js +3 -3
- package/dist/core/stdlib/values/region.js.map +1 -1
- package/dist/core/stdlib/volatility/measures.d.ts.map +1 -1
- package/dist/core/stdlib/volatility/measures.js +6 -4
- package/dist/core/stdlib/volatility/measures.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 +13 -2
- package/spec/errors.json +380 -20
- package/src/adapters/charts/run.ts +6 -0
- package/src/adapters/charts/tables.ts +6 -19
- package/src/adapters/charts/undrawable.ts +51 -0
- package/src/core/backtest/drive.ts +66 -154
- package/src/core/backtest/index.ts +2 -0
- package/src/core/backtest/walk.ts +182 -0
- package/src/core/catalogue/catalogue.generated.ts +18 -1
- package/src/core/catalogue/types.ts +8 -2
- package/src/core/catalogue/values.generated.ts +16 -0
- package/src/core/check/assigned.ts +31 -0
- package/src/core/check/call-sites.ts +11 -4
- package/src/core/check/calls.ts +13 -4
- package/src/core/check/check.ts +14 -2
- package/src/core/check/checker.ts +39 -2
- package/src/core/check/constant.ts +57 -0
- package/src/core/check/deleted.ts +135 -0
- package/src/core/check/expressions.ts +1 -1
- package/src/core/check/library-output.ts +1 -0
- package/src/core/check/library-prose.generated.ts +10 -10
- package/src/core/check/library.ts +8 -0
- package/src/core/check/surface.ts +16 -0
- package/src/core/engine/bar-source.ts +113 -0
- package/src/core/engine/channels.ts +2 -2
- package/src/core/engine/engine.ts +29 -28
- package/src/core/engine/guard.ts +12 -1
- package/src/core/engine/host.ts +4 -2
- package/src/core/engine/index.ts +1 -0
- package/src/core/engine/library/binding.ts +2 -0
- package/src/core/engine/library/index.ts +11 -0
- package/src/core/engine/library/maths.ts +2 -2
- package/src/core/engine/library/objects.ts +1 -6
- package/src/core/engine/machine.ts +5 -5
- package/src/core/engine/memory.ts +2 -2
- package/src/core/engine/registers.ts +2 -2
- 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/values/index.ts +1 -0
- package/src/core/engine/values/value.ts +5 -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 +13 -0
- package/src/core/stdlib/averages/exponential.ts +3 -4
- package/src/core/stdlib/averages/gaussian.ts +52 -0
- package/src/core/stdlib/averages/shaped.ts +8 -21
- package/src/core/stdlib/calendar/civil.ts +5 -2
- package/src/core/stdlib/maths/arithmetic.ts +2 -1
- package/src/core/stdlib/maths/elementary.ts +20 -33
- package/src/core/stdlib/maths/hypot.ts +63 -0
- package/src/core/stdlib/maths/power.ts +57 -0
- package/src/core/stdlib/maths/transcendental.ts +160 -0
- package/src/core/stdlib/maths/trigonometric.ts +160 -0
- package/src/core/stdlib/series/conditions.ts +9 -12
- package/src/core/stdlib/series/statistics.ts +1 -0
- package/src/core/stdlib/values/history.ts +70 -0
- package/src/core/stdlib/values/index.ts +1 -0
- package/src/core/stdlib/values/lookback.ts +51 -47
- package/src/core/stdlib/values/region.ts +7 -5
- package/src/core/stdlib/volatility/measures.ts +6 -4
- package/src/core/version/version.generated.ts +1 -1
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a script in the source dialect into logical lines of tokens.
|
|
3
|
+
*
|
|
4
|
+
* The source dialect is line oriented and indentation delimited, like
|
|
5
|
+
* OpenScript, and it differs in the two places a reader has to know about. A
|
|
6
|
+
* statement continues onto the next line when a bracket is still open, when
|
|
7
|
+
* the line ends on an operator that cannot end one, or when the next line is
|
|
8
|
+
* indented by an amount that is not a whole number of blocks: the dialect's
|
|
9
|
+
* blocks are four columns deep, and a continuation is told apart from a block
|
|
10
|
+
* by being indented off that grid. A tab in leading whitespace counts as one
|
|
11
|
+
* block.
|
|
12
|
+
*
|
|
13
|
+
* This reader is deliberately separate from OpenScript's lexer. The two
|
|
14
|
+
* languages share a shape and differ in almost every token rule that matters to
|
|
15
|
+
* a diagnostic: the source dialect has `:=`, `=>` and single quoted strings as
|
|
16
|
+
* ordinary tokens and no `none`, and running its text through OpenScript's
|
|
17
|
+
* lexer would report every one of those as a mistake in a file nobody wrote in
|
|
18
|
+
* OpenScript.
|
|
19
|
+
*
|
|
20
|
+
* Nothing here refuses anything. A character the dialect does not have becomes
|
|
21
|
+
* a `bad` token, and the parser decides what the statement holding it is.
|
|
22
|
+
*/
|
|
23
|
+
import type { SourceFile } from '../source/index.js';
|
|
24
|
+
|
|
25
|
+
export type TokenKind = 'name' | 'number' | 'string' | 'color' | 'op' | 'bad';
|
|
26
|
+
|
|
27
|
+
export interface Token {
|
|
28
|
+
readonly kind: TokenKind;
|
|
29
|
+
/** The characters as written. */
|
|
30
|
+
readonly text: string;
|
|
31
|
+
/** A string literal's decoded value; for every other kind, the text. */
|
|
32
|
+
readonly value: string;
|
|
33
|
+
readonly offset: number;
|
|
34
|
+
readonly length: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A `//` comment, with the text after the two slashes. */
|
|
38
|
+
export interface Remark {
|
|
39
|
+
readonly text: string;
|
|
40
|
+
readonly offset: number;
|
|
41
|
+
readonly line: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** One statement's worth of tokens, which may have spanned several lines. */
|
|
45
|
+
export interface Logical {
|
|
46
|
+
/** Leading columns of the first line, a tab counted as one block. */
|
|
47
|
+
readonly indent: number;
|
|
48
|
+
readonly tokens: readonly Token[];
|
|
49
|
+
/** Comments written after code on any of the statement's lines. */
|
|
50
|
+
readonly remarks: readonly Remark[];
|
|
51
|
+
readonly firstLine: number;
|
|
52
|
+
readonly lastLine: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type Item =
|
|
56
|
+
| { readonly kind: 'code'; readonly logical: Logical }
|
|
57
|
+
| { readonly kind: 'remark'; readonly remark: Remark; readonly indent: number }
|
|
58
|
+
| { readonly kind: 'blank'; readonly line: number };
|
|
59
|
+
|
|
60
|
+
/** The width of one block in the dialect, which a continuation is indented off. */
|
|
61
|
+
const BLOCK = 4;
|
|
62
|
+
|
|
63
|
+
const TWO_CHARACTER = [':=', '==', '!=', '<=', '>=', '=>', '+=', '-=', '*=', '/=', '%='];
|
|
64
|
+
const ONE_CHARACTER = '+-*/%<>=?:,.()[]';
|
|
65
|
+
|
|
66
|
+
/** Tokens after which a statement cannot end, so the next indented line continues it. */
|
|
67
|
+
const CONTINUES = new Set([
|
|
68
|
+
',', '+', '-', '*', '/', '%', '==', '!=', '<', '<=', '>', '>=', '?', ':', ':=', '=',
|
|
69
|
+
'+=', '-=', '*=', '/=', '%=', 'and', 'or', 'not', '(', '[',
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
const NAME_START = /[A-Za-z_]/;
|
|
73
|
+
const NAME_PART = /[A-Za-z0-9_]/;
|
|
74
|
+
const DIGIT = /[0-9]/;
|
|
75
|
+
const HEX = /[0-9A-Fa-f]/;
|
|
76
|
+
|
|
77
|
+
const ESCAPES: Readonly<Record<string, string>> = { n: '\n', t: '\t', r: '\r', '\\': '\\', "'": "'", '"': '"' };
|
|
78
|
+
|
|
79
|
+
interface Physical {
|
|
80
|
+
readonly line: number;
|
|
81
|
+
readonly indent: number;
|
|
82
|
+
readonly tokens: Token[];
|
|
83
|
+
readonly remark: Remark | undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Columns of leading whitespace, and where the text after it starts. */
|
|
87
|
+
function measure(text: string): { indent: number; start: number } {
|
|
88
|
+
let indent = 0;
|
|
89
|
+
let start = 0;
|
|
90
|
+
while (start < text.length) {
|
|
91
|
+
const c = text[start];
|
|
92
|
+
if (c === ' ') indent += 1;
|
|
93
|
+
else if (c === '\t') indent += BLOCK - (indent % BLOCK);
|
|
94
|
+
else break;
|
|
95
|
+
start += 1;
|
|
96
|
+
}
|
|
97
|
+
return { indent, start };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function readString(text: string, at: number): { end: number; value: string; closed: boolean } {
|
|
101
|
+
const quote = text[at];
|
|
102
|
+
let value = '';
|
|
103
|
+
let i = at + 1;
|
|
104
|
+
while (i < text.length) {
|
|
105
|
+
const c = text[i] ?? '';
|
|
106
|
+
if (c === quote) return { end: i + 1, value, closed: true };
|
|
107
|
+
if (c === '\\' && i + 1 < text.length) {
|
|
108
|
+
const next = text[i + 1] ?? '';
|
|
109
|
+
value += ESCAPES[next] ?? next;
|
|
110
|
+
i += 2;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
value += c;
|
|
114
|
+
i += 1;
|
|
115
|
+
}
|
|
116
|
+
return { end: text.length, value, closed: false };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function readNumber(text: string, at: number): number {
|
|
120
|
+
let i = at;
|
|
121
|
+
while (i < text.length && DIGIT.test(text[i] ?? '')) i += 1;
|
|
122
|
+
if (text[i] === '.' && !NAME_START.test(text[i + 1] ?? '')) {
|
|
123
|
+
i += 1;
|
|
124
|
+
while (i < text.length && DIGIT.test(text[i] ?? '')) i += 1;
|
|
125
|
+
}
|
|
126
|
+
if ((text[i] === 'e' || text[i] === 'E') && /[0-9+-]/.test(text[i + 1] ?? '')) {
|
|
127
|
+
let j = i + 1;
|
|
128
|
+
if (text[j] === '+' || text[j] === '-') j += 1;
|
|
129
|
+
if (DIGIT.test(text[j] ?? '')) {
|
|
130
|
+
i = j;
|
|
131
|
+
while (i < text.length && DIGIT.test(text[i] ?? '')) i += 1;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return i;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Every token on one physical line, and the comment that ends it if any. */
|
|
138
|
+
function tokensOf(
|
|
139
|
+
text: string,
|
|
140
|
+
start: number,
|
|
141
|
+
lineStart: number,
|
|
142
|
+
line: number,
|
|
143
|
+
): { tokens: Token[]; remark: Remark | undefined } {
|
|
144
|
+
const tokens: Token[] = [];
|
|
145
|
+
const token = (kind: TokenKind, from: number, to: number, value?: string): void => {
|
|
146
|
+
const written = text.slice(from, to);
|
|
147
|
+
tokens.push({ kind, text: written, value: value ?? written, offset: lineStart + from, length: to - from });
|
|
148
|
+
};
|
|
149
|
+
let i = start;
|
|
150
|
+
while (i < text.length) {
|
|
151
|
+
const c = text[i] ?? '';
|
|
152
|
+
if (c === ' ' || c === '\t') {
|
|
153
|
+
i += 1;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (c === '/' && text[i + 1] === '/') {
|
|
157
|
+
return { tokens, remark: { text: text.slice(i + 2).trimEnd(), offset: lineStart + i, line } };
|
|
158
|
+
}
|
|
159
|
+
if (NAME_START.test(c)) {
|
|
160
|
+
let j = i + 1;
|
|
161
|
+
while (j < text.length && NAME_PART.test(text[j] ?? '')) j += 1;
|
|
162
|
+
token('name', i, j);
|
|
163
|
+
i = j;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (DIGIT.test(c) || (c === '.' && DIGIT.test(text[i + 1] ?? ''))) {
|
|
167
|
+
const j = readNumber(text, i);
|
|
168
|
+
token('number', i, j);
|
|
169
|
+
i = j;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (c === '"' || c === "'") {
|
|
173
|
+
const read = readString(text, i);
|
|
174
|
+
token(read.closed ? 'string' : 'bad', i, read.end, read.value);
|
|
175
|
+
i = read.end;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (c === '#') {
|
|
179
|
+
let j = i + 1;
|
|
180
|
+
while (j < text.length && HEX.test(text[j] ?? '')) j += 1;
|
|
181
|
+
token(j - i === 7 || j - i === 9 ? 'color' : 'bad', i, j);
|
|
182
|
+
i = j;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const pair = text.slice(i, i + 2);
|
|
186
|
+
if (TWO_CHARACTER.includes(pair)) {
|
|
187
|
+
token('op', i, i + 2);
|
|
188
|
+
i += 2;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
token(ONE_CHARACTER.includes(c) ? 'op' : 'bad', i, i + 1);
|
|
192
|
+
i += 1;
|
|
193
|
+
}
|
|
194
|
+
return { tokens, remark: undefined };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** How far a line's brackets leave the statement open. */
|
|
198
|
+
function depthAfter(depth: number, tokens: readonly Token[]): number {
|
|
199
|
+
let open = depth;
|
|
200
|
+
for (const one of tokens) {
|
|
201
|
+
if (one.kind !== 'op') continue;
|
|
202
|
+
if (one.text === '(' || one.text === '[') open += 1;
|
|
203
|
+
else if (one.text === ')' || one.text === ']') open = Math.max(0, open - 1);
|
|
204
|
+
}
|
|
205
|
+
return open;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
interface Building {
|
|
209
|
+
indent: number;
|
|
210
|
+
tokens: Token[];
|
|
211
|
+
remarks: Remark[];
|
|
212
|
+
firstLine: number;
|
|
213
|
+
lastLine: number;
|
|
214
|
+
depth: number;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function continues(current: Building, next: Physical): boolean {
|
|
218
|
+
if (current.depth > 0) return true;
|
|
219
|
+
if (next.indent <= current.indent) return false;
|
|
220
|
+
if (next.indent % BLOCK !== 0) return true;
|
|
221
|
+
const last = current.tokens[current.tokens.length - 1];
|
|
222
|
+
return last !== undefined && CONTINUES.has(last.text);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* The whole file as items: statements, comment lines and blank lines, in order.
|
|
227
|
+
*
|
|
228
|
+
* A comment line or a blank line inside an open statement belongs to that
|
|
229
|
+
* statement and ends nothing, the same rule OpenScript gives them.
|
|
230
|
+
*/
|
|
231
|
+
export function readItems(file: SourceFile): readonly Item[] {
|
|
232
|
+
const items: Item[] = [];
|
|
233
|
+
let current: Building | undefined;
|
|
234
|
+
const held: Item[] = [];
|
|
235
|
+
|
|
236
|
+
const close = (): void => {
|
|
237
|
+
if (current === undefined) return;
|
|
238
|
+
const { indent, tokens, remarks, firstLine, lastLine } = current;
|
|
239
|
+
items.push({ kind: 'code', logical: { indent, tokens, remarks, firstLine, lastLine } });
|
|
240
|
+
current = undefined;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
for (let line = 1; line <= file.lineCount; line += 1) {
|
|
244
|
+
const text = file.lineText(line);
|
|
245
|
+
const { indent, start } = measure(text);
|
|
246
|
+
const read = tokensOf(text, start, file.lineStart(line), line);
|
|
247
|
+
const physical: Physical = { line, indent, tokens: read.tokens, remark: read.remark };
|
|
248
|
+
|
|
249
|
+
if (physical.tokens.length === 0) {
|
|
250
|
+
if (current !== undefined && current.depth > 0) {
|
|
251
|
+
if (physical.remark !== undefined) current.remarks.push(physical.remark);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
held.push(
|
|
255
|
+
physical.remark === undefined
|
|
256
|
+
? { kind: 'blank', line }
|
|
257
|
+
: { kind: 'remark', remark: physical.remark, indent },
|
|
258
|
+
);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (current !== undefined && continues(current, physical)) {
|
|
263
|
+
current.tokens.push(...physical.tokens);
|
|
264
|
+
if (physical.remark !== undefined) current.remarks.push(physical.remark);
|
|
265
|
+
current.lastLine = line;
|
|
266
|
+
current.depth = depthAfter(current.depth, physical.tokens);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
close();
|
|
271
|
+
items.push(...held);
|
|
272
|
+
held.length = 0;
|
|
273
|
+
current = {
|
|
274
|
+
indent,
|
|
275
|
+
tokens: [...physical.tokens],
|
|
276
|
+
remarks: physical.remark === undefined ? [] : [physical.remark],
|
|
277
|
+
firstLine: line,
|
|
278
|
+
lastLine: line,
|
|
279
|
+
depth: depthAfter(0, physical.tokens),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
close();
|
|
283
|
+
items.push(...held);
|
|
284
|
+
return items;
|
|
285
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scopes, and the name each source name is written as.
|
|
3
|
+
*
|
|
4
|
+
* Two rules of the source dialect differ from OpenScript's, and both are about
|
|
5
|
+
* meaning rather than spelling. A declaration inside a block or a function
|
|
6
|
+
* always makes a new name there, hiding any outer name it shares a spelling
|
|
7
|
+
* with, where OpenScript updates the outer name instead and refuses to hide one
|
|
8
|
+
* (`language.md` 12.2 and 12.3). And the source dialect's built-ins live behind
|
|
9
|
+
* namespaces, so `rsi` or `step` is an ordinary name there and a library name or
|
|
10
|
+
* a reserved word here.
|
|
11
|
+
*
|
|
12
|
+
* So a declaration that would hide a visible name is renamed with a number, and
|
|
13
|
+
* a name OpenScript reserves or defines is renamed with `Value` after it. Every
|
|
14
|
+
* new spelling is checked against every name the source uses anywhere and every
|
|
15
|
+
* name OpenScript has, so a rename can never land on another name. Nothing
|
|
16
|
+
* else is renamed: a name that collides with neither keeps its spelling, and
|
|
17
|
+
* two sibling blocks may both declare it, as both languages allow.
|
|
18
|
+
*/
|
|
19
|
+
import { NAMESPACES, isLibraryName } from '../check/index.js';
|
|
20
|
+
import { canonicalNumber } from '../emit/index.js';
|
|
21
|
+
import { RESERVED_WORDS } from '../tokens/index.js';
|
|
22
|
+
|
|
23
|
+
export type BindingKind = 'variable' | 'function' | 'parameter' | 'loop';
|
|
24
|
+
|
|
25
|
+
export interface Binding {
|
|
26
|
+
readonly output: string;
|
|
27
|
+
readonly kind: BindingKind;
|
|
28
|
+
readonly topLevel: boolean;
|
|
29
|
+
/** Bound to a declaration handle, which only `fill` may take. */
|
|
30
|
+
handle: 'plot' | 'level' | undefined;
|
|
31
|
+
/** Never absent on any bar: bound to an input or a literal and never reassigned. */
|
|
32
|
+
present: boolean;
|
|
33
|
+
/** A whole-number constant in the source dialect's sense, which version 5 divides without a fraction. */
|
|
34
|
+
whole: boolean;
|
|
35
|
+
/** A function's parameters, source spelling to output spelling, for its named arguments. */
|
|
36
|
+
labels: ReadonlyMap<string, string> | undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Words OpenScript gives a meaning of its own outside the library. */
|
|
40
|
+
const DECLARATION_WORDS = ['study', 'strategy', 'limits', 'version'];
|
|
41
|
+
|
|
42
|
+
export class Scope {
|
|
43
|
+
readonly names = new Map<string, Binding>();
|
|
44
|
+
readonly parent: Scope | undefined;
|
|
45
|
+
|
|
46
|
+
constructor(parent: Scope | undefined) {
|
|
47
|
+
this.parent = parent;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
find(name: string): Binding | undefined {
|
|
51
|
+
return this.names.get(name) ?? this.parent?.find(name);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get isGlobal(): boolean {
|
|
55
|
+
return this.parent === undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class Namer {
|
|
60
|
+
readonly #taken: Set<string>;
|
|
61
|
+
|
|
62
|
+
/** `used` is every identifier the source writes, anywhere. */
|
|
63
|
+
constructor(used: Iterable<string>) {
|
|
64
|
+
this.#taken = new Set(used);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Whether OpenScript already means something by this name. */
|
|
68
|
+
static reserved(name: string): boolean {
|
|
69
|
+
return (
|
|
70
|
+
(RESERVED_WORDS as readonly string[]).includes(name) ||
|
|
71
|
+
isLibraryName(name) ||
|
|
72
|
+
NAMESPACES.includes(name) ||
|
|
73
|
+
DECLARATION_WORDS.includes(name)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A spelling no source name and no OpenScript name has, starting from `base`. */
|
|
78
|
+
fresh(base: string): string {
|
|
79
|
+
let candidate = base;
|
|
80
|
+
for (let n = 2; this.#taken.has(candidate) || Namer.reserved(candidate); n += 1) {
|
|
81
|
+
candidate = `${base}${canonicalNumber(n)}`;
|
|
82
|
+
}
|
|
83
|
+
this.#taken.add(candidate);
|
|
84
|
+
return candidate;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The spelling of a source name declared in `scope`.
|
|
89
|
+
*
|
|
90
|
+
* `hides` says whether an enclosing scope can already see the name, which is
|
|
91
|
+
* the case the source dialect reads as a new name and OpenScript would read
|
|
92
|
+
* as an update of the old one.
|
|
93
|
+
*/
|
|
94
|
+
declare(name: string, hides: boolean): string {
|
|
95
|
+
if (Namer.reserved(name)) return this.fresh(`${name}Value`);
|
|
96
|
+
if (hides) return this.fresh(name);
|
|
97
|
+
return name;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A binding with the defaults a plain declaration has. */
|
|
102
|
+
export function binding(output: string, kind: BindingKind, topLevel: boolean): Binding {
|
|
103
|
+
return { output, kind, topLevel, handle: undefined, present: false, whole: false, labels: undefined };
|
|
104
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The declaration, and the calls that trade.
|
|
3
|
+
*
|
|
4
|
+
* **The declaration** is written from the source's options where OpenScript
|
|
5
|
+
* has the same option (`language.md` 13.2 and 13.3), and every default the two
|
|
6
|
+
* languages disagree about is written out rather than left to the default: the
|
|
7
|
+
* source dialect starts a strategy with a million in capital where OpenScript
|
|
8
|
+
* starts with a hundred thousand, so a strategy that names no capital is given
|
|
9
|
+
* the source's. An option with no equivalent is left out of the declaration,
|
|
10
|
+
* because the file needs it, with OS9006 where it changes what is computed or
|
|
11
|
+
* traded and OS9009 where it changes only what is shown.
|
|
12
|
+
*
|
|
13
|
+
* **An entry** in the source dialect closes an opposite position before it
|
|
14
|
+
* opens its own, and is ignored while a position on its side is already held at
|
|
15
|
+
* the default pyramiding. It is written as exactly that: a `close()` behind a
|
|
16
|
+
* guard on the opposite side, then the entry behind a guard on its own side.
|
|
17
|
+
* **An exit** that names its entry is guarded by that entry's side, so that the
|
|
18
|
+
* levels of a long exit and a short exit do not replace each other on every
|
|
19
|
+
* bar, and a profit or loss given in ticks is converted to a price distance by
|
|
20
|
+
* the instrument's tick size. Every order call raises OS9010 once, because the
|
|
21
|
+
* two order models are close and are not the same.
|
|
22
|
+
*/
|
|
23
|
+
import { diagnosticFor } from '../diagnostics/index.js';
|
|
24
|
+
import { bind } from './calls.js';
|
|
25
|
+
import type { Bound } from './calls.js';
|
|
26
|
+
import type { Context, OutLine } from './context.js';
|
|
27
|
+
import { quoted } from './context.js';
|
|
28
|
+
import { pathOf } from './syntax.js';
|
|
29
|
+
import type { Arg, CallExpr, Expr } from './syntax.js';
|
|
30
|
+
import { PREC, numberText, translate, wrap } from './values.js';
|
|
31
|
+
|
|
32
|
+
/** The capital a source strategy starts with when it names none. */
|
|
33
|
+
const SOURCE_CAPITAL = '1000000';
|
|
34
|
+
|
|
35
|
+
const DECLARATION_POSITIONAL = ['title', 'shorttitle', 'overlay', 'format', 'precision'];
|
|
36
|
+
|
|
37
|
+
const DISPLAY = new Set([
|
|
38
|
+
'max_bars_back', 'explicit_plot_zorder', 'max_lines_count', 'max_labels_count', 'max_boxes_count',
|
|
39
|
+
'max_polylines_count', 'dynamic_requests', 'behind_chart', 'risk_free_rate', 'calc_bars_count',
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
/** Options that change what is computed or traded, with the literal that changes nothing. */
|
|
43
|
+
const INERT: Readonly<Record<string, string>> = {
|
|
44
|
+
timeframe: '""',
|
|
45
|
+
timeframe_gaps: 'true',
|
|
46
|
+
calc_on_order_fills: 'false',
|
|
47
|
+
calc_on_every_tick: 'false',
|
|
48
|
+
use_bar_magnifier: 'false',
|
|
49
|
+
fill_orders_on_standard_ohlc: 'false',
|
|
50
|
+
backtest_fill_limits_assumption: '0',
|
|
51
|
+
close_entries_rule: '"FIFO"',
|
|
52
|
+
margin_long: 'margin',
|
|
53
|
+
margin_short: 'margin',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const QTY_TYPES: Readonly<Record<string, string>> = { fixed: 'units', cash: 'cash', percent_of_equity: 'equityPercent' };
|
|
57
|
+
const COMMISSIONS: Readonly<Record<string, string>> = { percent: 'percent', cash_per_order: 'perTrade', cash_per_contract: 'perUnit' };
|
|
58
|
+
|
|
59
|
+
function literalOf(expr: Expr | undefined): string | undefined {
|
|
60
|
+
if (expr === undefined) return undefined;
|
|
61
|
+
if (expr.kind === 'number') return numberText(expr.text);
|
|
62
|
+
if (expr.kind === 'bool') return String(expr.value);
|
|
63
|
+
if (expr.kind === 'string') return quoted(expr.value);
|
|
64
|
+
if (expr.kind === 'unary' && expr.op === '-' && expr.operand.kind === 'number') return `-${numberText(expr.operand.text)}`;
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function constantOf(expr: Expr | undefined, prefix: string): string | undefined {
|
|
69
|
+
const path = expr === undefined ? undefined : pathOf(expr);
|
|
70
|
+
return path?.startsWith(prefix) ? path.slice(prefix.length) : undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The declaration line, with every option it can carry. */
|
|
74
|
+
export function declarationLine(ctx: Context, call: CallExpr, path: string): string {
|
|
75
|
+
const strategy = path === 'strategy';
|
|
76
|
+
const bound = bind(call, DECLARATION_POSITIONAL);
|
|
77
|
+
const cannot = (arg: Arg, name: string): void =>
|
|
78
|
+
ctx.note(diagnosticFor('OS9006', ctx.span(arg.at), { argument: name, call: path }));
|
|
79
|
+
const shownOnly = (arg: Arg, name: string): void =>
|
|
80
|
+
ctx.note(diagnosticFor('OS9009', ctx.span(arg.at), { argument: name, call: path }));
|
|
81
|
+
|
|
82
|
+
for (const one of bound.stray) cannot(one.arg, one.name);
|
|
83
|
+
const titleArg = bound.byName.get('title');
|
|
84
|
+
let title = titleArg?.value.kind === 'string' ? titleArg.value.value : undefined;
|
|
85
|
+
if (title === undefined) {
|
|
86
|
+
if (titleArg !== undefined) cannot(titleArg, 'title');
|
|
87
|
+
title = 'Imported script';
|
|
88
|
+
}
|
|
89
|
+
const options: string[] = [quoted(title)];
|
|
90
|
+
const put = (name: string, value: string): void => {
|
|
91
|
+
options.push(`${name} = ${value}`);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const commission = { type: undefined as string | undefined, given: false };
|
|
95
|
+
for (const [name, arg] of bound.byName) {
|
|
96
|
+
const literal = literalOf(arg.value);
|
|
97
|
+
switch (name) {
|
|
98
|
+
case 'title':
|
|
99
|
+
continue;
|
|
100
|
+
case 'shorttitle':
|
|
101
|
+
if (arg.value.kind === 'string') put('short', literal ?? '""');
|
|
102
|
+
else shownOnly(arg, name);
|
|
103
|
+
continue;
|
|
104
|
+
case 'overlay':
|
|
105
|
+
if (arg.value.kind === 'bool') put('overlay', literal ?? 'false');
|
|
106
|
+
else shownOnly(arg, name);
|
|
107
|
+
continue;
|
|
108
|
+
case 'precision':
|
|
109
|
+
if (arg.value.kind === 'number') put('precision', literal ?? '4');
|
|
110
|
+
else shownOnly(arg, name);
|
|
111
|
+
continue;
|
|
112
|
+
case 'format':
|
|
113
|
+
case 'scale': {
|
|
114
|
+
const word = constantOf(arg.value, `${name}.`);
|
|
115
|
+
const known = name === 'format' ? ['price', 'percent', 'volume'] : ['right', 'left', 'none'];
|
|
116
|
+
if (word !== undefined && known.includes(word)) put(name, quoted(word));
|
|
117
|
+
else shownOnly(arg, name);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (DISPLAY.has(name)) {
|
|
122
|
+
shownOnly(arg, name);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const inert = INERT[name];
|
|
126
|
+
if (inert !== undefined) {
|
|
127
|
+
if (literal !== inert) cannot(arg, name);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (!strategy) {
|
|
131
|
+
cannot(arg, name);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const numeric = arg.value.kind === 'number' ? literal : undefined;
|
|
135
|
+
switch (name) {
|
|
136
|
+
case 'initial_capital':
|
|
137
|
+
if (numeric !== undefined) put('capital', numeric);
|
|
138
|
+
else cannot(arg, name);
|
|
139
|
+
break;
|
|
140
|
+
case 'default_qty_value':
|
|
141
|
+
if (numeric !== undefined) put('qty', numeric);
|
|
142
|
+
else cannot(arg, name);
|
|
143
|
+
break;
|
|
144
|
+
case 'default_qty_type': {
|
|
145
|
+
const type = QTY_TYPES[constantOf(arg.value, 'strategy.') ?? ''];
|
|
146
|
+
ctx.qtyInUnits = type === 'units';
|
|
147
|
+
if (type !== undefined) put('qtyType', quoted(type));
|
|
148
|
+
else cannot(arg, name);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'pyramiding':
|
|
152
|
+
if (numeric !== undefined && Number(numeric) <= 1) break;
|
|
153
|
+
if (numeric !== undefined) {
|
|
154
|
+
ctx.pyramiding = Number(numeric);
|
|
155
|
+
put('pyramiding', numeric);
|
|
156
|
+
}
|
|
157
|
+
cannot(arg, name);
|
|
158
|
+
break;
|
|
159
|
+
case 'currency': {
|
|
160
|
+
const code = constantOf(arg.value, 'currency.');
|
|
161
|
+
if (code === undefined) shownOnly(arg, name);
|
|
162
|
+
else if (code !== 'NONE') put('currency', quoted(code));
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case 'slippage':
|
|
166
|
+
if (numeric !== undefined) put('slippage', numeric);
|
|
167
|
+
else cannot(arg, name);
|
|
168
|
+
break;
|
|
169
|
+
case 'commission_type':
|
|
170
|
+
commission.type = COMMISSIONS[constantOf(arg.value, 'strategy.commission.') ?? ''];
|
|
171
|
+
if (commission.type === undefined) cannot(arg, name);
|
|
172
|
+
break;
|
|
173
|
+
case 'commission_value':
|
|
174
|
+
if (numeric !== undefined) {
|
|
175
|
+
put('commission', numeric);
|
|
176
|
+
commission.given = true;
|
|
177
|
+
} else cannot(arg, name);
|
|
178
|
+
break;
|
|
179
|
+
case 'process_orders_on_close':
|
|
180
|
+
if (literal === 'true') put('fillOn', '"close"');
|
|
181
|
+
else if (literal !== 'false') cannot(arg, name);
|
|
182
|
+
break;
|
|
183
|
+
default:
|
|
184
|
+
cannot(arg, name);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (strategy) {
|
|
188
|
+
if (!bound.byName.has('initial_capital')) put('capital', SOURCE_CAPITAL);
|
|
189
|
+
if (commission.given) put('commissionType', quoted(commission.type ?? 'percent'));
|
|
190
|
+
}
|
|
191
|
+
return `${strategy ? 'strategy' : 'study'}(${options.join(', ')})`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** OS9010, once per order call. */
|
|
195
|
+
function orderNote(ctx: Context, call: CallExpr, path: string): void {
|
|
196
|
+
ctx.note(diagnosticFor('OS9010', ctx.span(call.callee.at), { call: path }), `OS9010 ${path}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Refuses the first argument that changes the order with no equivalent, and
|
|
201
|
+
* reports every one that changes only its label. Returns whether to go on.
|
|
202
|
+
*/
|
|
203
|
+
function sortArguments(ctx: Context, path: string, bound: Bound, carried: readonly string[], refused: readonly string[]): boolean {
|
|
204
|
+
const first = bound.stray[0];
|
|
205
|
+
if (first !== undefined) {
|
|
206
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(first.arg.at), { argument: first.name, call: path }));
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
for (const [name, arg] of bound.byName) {
|
|
210
|
+
if (carried.includes(name)) continue;
|
|
211
|
+
if (refused.includes(name) || !(name.startsWith('comment') || name.startsWith('alert') || name === 'disable_alert')) {
|
|
212
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(arg.at), { argument: name, call: path }));
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
ctx.note(diagnosticFor('OS9009', ctx.span(arg.at), { argument: name, call: path }));
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function required(ctx: Context, call: CallExpr, path: string, bound: Bound, name: string): string | undefined {
|
|
221
|
+
const arg = bound.byName.get(name);
|
|
222
|
+
if (arg !== undefined) return translate(ctx, arg.value).text;
|
|
223
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(call.at), { argument: name, call: path }));
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function entryLines(ctx: Context, call: CallExpr, path: string, depth: number): OutLine[] {
|
|
228
|
+
const bound = bind(call, ['id', 'direction', 'qty', 'limit', 'stop']);
|
|
229
|
+
if (!sortArguments(ctx, path, bound, ['id', 'direction', 'qty'], ['limit', 'stop', 'oca_name', 'oca_type'])) return [];
|
|
230
|
+
const tag = required(ctx, call, path, bound, 'id');
|
|
231
|
+
const direction = constantOf(bound.byName.get('direction')?.value, 'strategy.');
|
|
232
|
+
if (tag === undefined) return [];
|
|
233
|
+
if (direction !== 'long' && direction !== 'short') {
|
|
234
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(bound.byName.get('direction')?.at ?? call.at), { argument: 'direction', call: path }));
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
const qty = bound.byName.get('qty');
|
|
238
|
+
if (qty !== undefined && !ctx.qtyInUnits) {
|
|
239
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(qty.at), { argument: 'qty', call: path }));
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
orderNote(ctx, call, path);
|
|
243
|
+
const args = qty === undefined ? `tag = ${tag}` : `qty = ${translate(ctx, qty.value).text}, tag = ${tag}`;
|
|
244
|
+
const long = direction === 'long';
|
|
245
|
+
const lines: OutLine[] = [
|
|
246
|
+
{ depth, text: `if pos.size ${long ? '<' : '>'} 0` },
|
|
247
|
+
{ depth: depth + 1, text: 'close()' },
|
|
248
|
+
];
|
|
249
|
+
if (ctx.pyramiding <= 1) {
|
|
250
|
+
lines.push({ depth, text: `if pos.size ${long ? '<=' : '>='} 0` }, { depth: depth + 1, text: `${long ? 'buy' : 'sell'}(${args})` });
|
|
251
|
+
} else {
|
|
252
|
+
lines.push({ depth, text: `${long ? 'buy' : 'sell'}(${args})` });
|
|
253
|
+
}
|
|
254
|
+
return lines;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function closeLines(ctx: Context, call: CallExpr, path: string, depth: number): OutLine[] {
|
|
258
|
+
const all = path === 'strategy.close_all';
|
|
259
|
+
const bound = bind(call, all ? [] : ['id']);
|
|
260
|
+
if (!sortArguments(ctx, path, bound, ['id'], ['qty', 'qty_percent', 'immediately'])) return [];
|
|
261
|
+
if (all) {
|
|
262
|
+
orderNote(ctx, call, path);
|
|
263
|
+
return [{ depth, text: 'close()' }];
|
|
264
|
+
}
|
|
265
|
+
const tag = required(ctx, call, path, bound, 'id');
|
|
266
|
+
if (tag === undefined) return [];
|
|
267
|
+
orderNote(ctx, call, path);
|
|
268
|
+
return [{ depth, text: `close(tag = ${tag})` }];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function exitLines(ctx: Context, call: CallExpr, path: string, depth: number): OutLine[] {
|
|
272
|
+
const bound = bind(call, ['id', 'from_entry', 'qty', 'qty_percent', 'profit', 'limit', 'loss', 'stop']);
|
|
273
|
+
const carried = ['id', 'from_entry', 'profit', 'limit', 'loss', 'stop'];
|
|
274
|
+
const refused = ['qty', 'qty_percent', 'trail_price', 'trail_points', 'trail_offset', 'oca_name'];
|
|
275
|
+
if (!sortArguments(ctx, path, bound, carried, refused)) return [];
|
|
276
|
+
for (const [distance, price] of [['profit', 'limit'], ['loss', 'stop']] as const) {
|
|
277
|
+
const both = bound.byName.get(distance);
|
|
278
|
+
if (both !== undefined && bound.byName.has(price)) {
|
|
279
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(both.at), { argument: distance, call: path }));
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const tag = required(ctx, call, path, bound, 'id');
|
|
284
|
+
if (tag === undefined) return [];
|
|
285
|
+
let guard: string | undefined;
|
|
286
|
+
const from = bound.byName.get('from_entry');
|
|
287
|
+
if (from !== undefined) {
|
|
288
|
+
const side = from.value.kind === 'string' ? ctx.facts.entries.get(from.value.value) : undefined;
|
|
289
|
+
if (side === undefined) {
|
|
290
|
+
ctx.refuse(diagnosticFor('OS9006', ctx.span(from.at), { argument: 'from_entry', call: path }));
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
guard = side === 'long' ? 'if pos.size >= 0' : 'if pos.size <= 0';
|
|
294
|
+
}
|
|
295
|
+
const parts = [`tag = ${tag}`];
|
|
296
|
+
for (const name of ['stop', 'limit']) {
|
|
297
|
+
const arg = bound.byName.get(name);
|
|
298
|
+
if (arg !== undefined) parts.push(`${name} = ${translate(ctx, arg.value).text}`);
|
|
299
|
+
}
|
|
300
|
+
for (const name of ['profit', 'loss']) {
|
|
301
|
+
const arg = bound.byName.get(name);
|
|
302
|
+
if (arg !== undefined) parts.push(`${name} = ${wrap(translate(ctx, arg.value), PREC.multiplicative)} * chart.tickSize`);
|
|
303
|
+
}
|
|
304
|
+
orderNote(ctx, call, path);
|
|
305
|
+
const line = `exit(${parts.join(', ')})`;
|
|
306
|
+
return guard === undefined ? [{ depth, text: line }] : [{ depth, text: guard }, { depth: depth + 1, text: line }];
|
|
307
|
+
}
|