openalgo-script 0.7.2 → 0.8.1
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 +101 -0
- package/README.md +316 -379
- package/dist/adapters/charts/alerts.d.ts +11 -0
- package/dist/adapters/charts/alerts.d.ts.map +1 -1
- package/dist/adapters/charts/alerts.js +16 -1
- package/dist/adapters/charts/alerts.js.map +1 -1
- package/dist/adapters/charts/capabilities.d.ts +48 -0
- package/dist/adapters/charts/capabilities.d.ts.map +1 -0
- package/dist/adapters/charts/capabilities.js +82 -0
- package/dist/adapters/charts/capabilities.js.map +1 -0
- package/dist/adapters/charts/columns.d.ts +19 -2
- package/dist/adapters/charts/columns.d.ts.map +1 -1
- package/dist/adapters/charts/columns.js +15 -3
- package/dist/adapters/charts/columns.js.map +1 -1
- package/dist/adapters/charts/contract.d.ts +35 -3
- package/dist/adapters/charts/contract.d.ts.map +1 -1
- package/dist/adapters/charts/descriptor.d.ts +1 -1
- package/dist/adapters/charts/descriptor.d.ts.map +1 -1
- package/dist/adapters/charts/descriptor.js +17 -5
- package/dist/adapters/charts/descriptor.js.map +1 -1
- package/dist/adapters/charts/fills.d.ts +26 -5
- package/dist/adapters/charts/fills.d.ts.map +1 -1
- package/dist/adapters/charts/fills.js +55 -7
- package/dist/adapters/charts/fills.js.map +1 -1
- package/dist/adapters/charts/index.d.ts +9 -3
- package/dist/adapters/charts/index.d.ts.map +1 -1
- package/dist/adapters/charts/index.js +7 -1
- package/dist/adapters/charts/index.js.map +1 -1
- package/dist/adapters/charts/produced.d.ts +3 -2
- package/dist/adapters/charts/produced.d.ts.map +1 -1
- package/dist/adapters/charts/produced.js +1 -1
- package/dist/adapters/charts/produced.js.map +1 -1
- package/dist/adapters/charts/run.d.ts +23 -0
- package/dist/adapters/charts/run.d.ts.map +1 -1
- package/dist/adapters/charts/run.js +2 -1
- package/dist/adapters/charts/run.js.map +1 -1
- package/dist/adapters/charts/surfaces.d.ts +11 -0
- package/dist/adapters/charts/surfaces.d.ts.map +1 -1
- package/dist/adapters/charts/tables.d.ts +21 -11
- package/dist/adapters/charts/tables.d.ts.map +1 -1
- package/dist/adapters/charts/tables.js +17 -6
- package/dist/adapters/charts/tables.js.map +1 -1
- package/dist/adapters/charts/undrawable.d.ts +2 -1
- package/dist/adapters/charts/undrawable.d.ts.map +1 -1
- package/dist/adapters/charts/undrawable.js +24 -14
- package/dist/adapters/charts/undrawable.js.map +1 -1
- package/dist/core/check/call-sites.js +15 -5
- package/dist/core/check/call-sites.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 +1 -1
- package/spec/errors.json +1 -1
- package/src/adapters/charts/alerts.ts +28 -1
- package/src/adapters/charts/capabilities.ts +103 -0
- package/src/adapters/charts/columns.ts +29 -8
- package/src/adapters/charts/contract.ts +36 -2
- package/src/adapters/charts/descriptor.ts +21 -6
- package/src/adapters/charts/fills.ts +95 -11
- package/src/adapters/charts/index.ts +9 -1
- package/src/adapters/charts/produced.ts +4 -3
- package/src/adapters/charts/run.ts +25 -1
- package/src/adapters/charts/surfaces.ts +12 -0
- package/src/adapters/charts/tables.ts +31 -14
- package/src/adapters/charts/undrawable.ts +24 -14
- package/src/core/check/call-sites.ts +15 -5
- package/src/core/version/version.generated.ts +1 -1
package/README.md
CHANGED
|
@@ -2,414 +2,351 @@
|
|
|
2
2
|
|
|
3
3
|
# OpenScript
|
|
4
4
|
|
|
5
|
-
**An open trading language. Write
|
|
6
|
-
|
|
7
|
-
Compiles in the browser in milliseconds with no build step and no `eval`, runs
|
|
8
|
-
the same compiled program on a server, and is specified well enough that anyone
|
|
9
|
-
can write their own engine for it.
|
|
5
|
+
**An open trading language. Write an indicator or a strategy once, then plot it,
|
|
6
|
+
backtest it and trade it.**
|
|
10
7
|
|
|
8
|
+
[](https://www.npmjs.com/package/openalgo-script)
|
|
9
|
+
[](https://pypi.org/project/openscript/)
|
|
10
|
+
[](https://github.com/marketcalls/openscript/actions/workflows/ci.yml)
|
|
11
11
|
[](./LICENSE)
|
|
12
|
-
[](./ROADMAP.md)
|
|
13
12
|
|
|
14
13
|
</div>
|
|
15
14
|
|
|
16
15
|
---
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- **The backtest does not model everything, and says which.** A bracket's stop
|
|
70
|
-
cannot fill, because the engine appends no order row for a bracket. A quantity
|
|
71
|
-
stated in cash or in a percentage of equity is refused rather than filled,
|
|
72
|
-
because a backtest works out no running equity to size against. The equity
|
|
73
|
-
curve marks a trade at the size it ended up entering, so a strategy that
|
|
74
|
-
scales in is reported with a drawdown deeper than the account had. A script
|
|
75
|
-
still cannot read its own equity mid-run. [`CHANGELOG.md`](./CHANGELOG.md) is
|
|
76
|
-
the full list, release by release, and every item on it is there because
|
|
77
|
-
somebody would otherwise find it inside a report they had already believed.
|
|
78
|
-
- **No editor on screen, and that is the design.** The six headless functions are
|
|
79
|
-
here and resolve as `openalgo-script/editor`: highlight, complete, diagnose,
|
|
80
|
-
hover, signature and format, text in and data out, with no DOM at any tier. The
|
|
81
|
-
text component, the panel, the apply button, saving and the theme are yours,
|
|
82
|
-
and the drop-in adapter for one editor component wires the six into it without
|
|
83
|
-
drawing anything itself. What each function gives you, and what stays yours, is
|
|
84
|
-
in
|
|
85
|
-
[`docs/integrating/the-editor-half.md`](./docs/integrating/the-editor-half.md).
|
|
86
|
-
The language server that would put the same errors in a desktop editor is the
|
|
87
|
-
rest of Phase 4 and is not written.
|
|
88
|
-
- **No engine anybody else wrote has run the suite, so the portability claim is
|
|
89
|
-
still untested.** The second engine is here: `engine/` holds a complete engine
|
|
90
|
-
in Python, published to PyPI as `openscript`, with a test suite of its own,
|
|
91
|
-
and `npm test` runs both on every build. A run record carries its own source
|
|
92
|
-
text rather than only a hash of it (`sourceText` in
|
|
93
|
-
[`src/core/backtest/record.ts`](./src/core/backtest/record.ts)), and
|
|
94
|
-
`caseFilesFrom` in [`src/core/backtest/case.ts`](./src/core/backtest/case.ts)
|
|
95
|
-
writes the files a suite runs from straight out of a record, which is where the
|
|
96
|
-
strategy cases under [`cases/`](./cases) came from.
|
|
97
|
-
|
|
98
|
-
There are 121 cases now: 67 in the `core` profile, 46 in `chart` and 8 in
|
|
99
|
-
`strategy`. They assert eight of the channels section 4 of
|
|
100
|
-
[`spec/conformance.md`](./spec/conformance.md) defines: diagnostics, a value
|
|
101
|
-
per bar per plot, the log, drawing objects, grid cells, orders, trades and the
|
|
102
|
-
report. Markers, fills, levels, paint and alerts have no case yet, and neither
|
|
103
|
-
engine's adapter answers them. So the `semantics` and
|
|
104
|
-
`numerics` categories, which are the reason the suite exists, now hold real
|
|
105
|
-
cases, each with expected values computed independently of both engines.
|
|
106
|
-
|
|
107
|
-
`npm run suite:agree` reports 102 pass and 19 skipped of 121 between the two
|
|
108
|
-
engines, exactly, the skips being the compiler-diagnostic cases the Python
|
|
109
|
-
engine rightly has no compiler for. The two engines hold the same 251 library
|
|
110
|
-
entries, which a check asks each of them for on every build. What that run
|
|
111
|
-
does not prove is the thing the suite exists for: both engines were written
|
|
112
|
-
in this repository, from the same specification, by the same hands, so their
|
|
113
|
-
agreement is evidence about this repository rather than about the
|
|
114
|
-
specification. Until an engine written by somebody who had only the
|
|
115
|
-
specification passes these cases, portability is a design with one
|
|
116
|
-
corroborating implementation, not a result. That is why no conformance badge
|
|
117
|
-
is shown here, though `npm run badge` will make one for an engine that passes.
|
|
118
|
-
|
|
119
|
-
The version is `0.7.2` rather than `1.0` because of that list. The studies
|
|
120
|
-
surface is the part that is finished, and it is the part to build on.
|
|
121
|
-
|
|
122
|
-
`ROADMAP.md` says what each phase owes before it is allowed to finish. The
|
|
123
|
-
specification is written first and the implementation follows it, which is why
|
|
124
|
-
there is still more specification here than compiler.
|
|
125
|
-
|
|
126
|
-
## What it looks like
|
|
17
|
+
OpenScript is a small language for writing what you want to see on a chart, and
|
|
18
|
+
what you want to trade when you see it.
|
|
19
|
+
|
|
20
|
+
- A script is **one plain text file**. You own it, you can keep it in version
|
|
21
|
+
control, and you can send it to a friend.
|
|
22
|
+
- A script **runs once per bar**, oldest bar first. There is no main function to
|
|
23
|
+
write: the file itself is the loop.
|
|
24
|
+
- The same file gives **the same numbers** on the chart, in the backtest and in
|
|
25
|
+
live trading, because all three run the same compiled program.
|
|
26
|
+
- It compiles in the browser in milliseconds with no `eval`, and a server can run
|
|
27
|
+
the same compiled program in Python.
|
|
28
|
+
|
|
29
|
+
## Contents
|
|
30
|
+
|
|
31
|
+
1. [Install](#install)
|
|
32
|
+
2. [Your first indicator](#your-first-indicator)
|
|
33
|
+
3. [Two averages and a signal](#two-averages-and-a-signal)
|
|
34
|
+
4. [An indicator in its own pane](#an-indicator-in-its-own-pane)
|
|
35
|
+
5. [A built-in trend indicator](#a-built-in-trend-indicator)
|
|
36
|
+
6. [Your first strategy](#your-first-strategy)
|
|
37
|
+
7. [A strategy with a stop loss](#a-strategy-with-a-stop-loss)
|
|
38
|
+
8. [Run a script from your own code](#run-a-script-from-your-own-code)
|
|
39
|
+
9. [Words you will see](#words-you-will-see)
|
|
40
|
+
10. [Where to go next](#where-to-go-next)
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
If your trading platform already has OpenScript, there is nothing to install:
|
|
45
|
+
open its script editor, paste a script from this page and press Apply.
|
|
46
|
+
|
|
47
|
+
To use it from your own code:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
npm install openalgo-script # the compiler and the engine, for JavaScript
|
|
51
|
+
pip install openscript # the engine, for Python 3.12 or newer
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Neither package has a runtime dependency.
|
|
55
|
+
|
|
56
|
+
## Your first indicator
|
|
57
|
+
|
|
58
|
+
A 20 bar moving average drawn over price:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
version 1
|
|
62
|
+
|
|
63
|
+
study("My moving average", overlay = true)
|
|
64
|
+
|
|
65
|
+
length = input(20, "Length")
|
|
66
|
+
average = sma(close, length)
|
|
127
67
|
|
|
68
|
+
plot(average, "Average", orange, width = 2)
|
|
128
69
|
```
|
|
70
|
+
|
|
71
|
+
Line by line:
|
|
72
|
+
|
|
73
|
+
- `version 1` says which version of the language the file is written in. Every
|
|
74
|
+
script starts with it.
|
|
75
|
+
- `study(...)` names the indicator. `overlay = true` draws it on top of the
|
|
76
|
+
price. Leave it out and the indicator gets a pane of its own.
|
|
77
|
+
- `input(20, "Length")` makes a setting a trader can change without editing the
|
|
78
|
+
file. 20 is the default.
|
|
79
|
+
- `sma(close, length)` is the simple average of the last `length` closes.
|
|
80
|
+
`close`, `open`, `high`, `low` and `volume` are the current bar's prices.
|
|
81
|
+
- `plot(...)` draws one line: the value, its name in the legend, its colour and
|
|
82
|
+
its thickness.
|
|
83
|
+
|
|
84
|
+
The line starts on the 20th bar. That is correct: a 20 bar average does not exist
|
|
85
|
+
until 20 bars do.
|
|
86
|
+
|
|
87
|
+
## Two averages and a signal
|
|
88
|
+
|
|
89
|
+
A fast and a slow average, the space between them shaded, and a marker where
|
|
90
|
+
they cross:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
version 1
|
|
94
|
+
|
|
129
95
|
study("EMA cross", overlay = true)
|
|
130
96
|
|
|
131
|
-
|
|
132
|
-
|
|
97
|
+
fastLength = input(9, "Fast length")
|
|
98
|
+
slowLength = input(21, "Slow length")
|
|
133
99
|
|
|
134
|
-
|
|
135
|
-
|
|
100
|
+
fast = ema(close, fastLength)
|
|
101
|
+
slow = ema(close, slowLength)
|
|
136
102
|
|
|
137
|
-
plot(
|
|
138
|
-
plot(
|
|
103
|
+
fastPlot = plot(fast, "Fast", aqua, width = 2)
|
|
104
|
+
slowPlot = plot(slow, "Slow", orange, width = 2)
|
|
105
|
+
fill(fastPlot, slowPlot, fade(aqua, 90))
|
|
139
106
|
|
|
140
|
-
if crossUp(
|
|
141
|
-
signal("BUY")
|
|
107
|
+
if crossUp(fast, slow)
|
|
108
|
+
signal("BUY", lime, at = "below")
|
|
109
|
+
|
|
110
|
+
if crossDown(fast, slow)
|
|
111
|
+
signal("SELL", red)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- `ema` is an exponential moving average, which follows price faster than `sma`.
|
|
115
|
+
- `fill` shades between two plots. `fade(aqua, 90)` is aqua at 90 percent
|
|
116
|
+
transparency.
|
|
117
|
+
- `crossUp(fast, slow)` is true on the one bar where `fast` moves above `slow`.
|
|
118
|
+
- `signal` puts a labelled marker on that bar.
|
|
119
|
+
- Indentation marks the body of an `if`, the way it does in Python.
|
|
120
|
+
|
|
121
|
+
## An indicator in its own pane
|
|
122
|
+
|
|
123
|
+
RSI below the chart, with lines at 70 and 30, and an alert when it climbs back
|
|
124
|
+
above 30:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
version 1
|
|
128
|
+
|
|
129
|
+
study("RSI", precision = 2, range = [0, 100])
|
|
130
|
+
|
|
131
|
+
length = input(14, "Length")
|
|
132
|
+
value = rsi(close, length)
|
|
133
|
+
|
|
134
|
+
level(70, "Overbought", red)
|
|
135
|
+
level(30, "Oversold", lime)
|
|
136
|
+
|
|
137
|
+
plot(value, "RSI", purple, width = 2)
|
|
138
|
+
|
|
139
|
+
if crossUp(value, 30)
|
|
140
|
+
alert("RSI is back above 30", id = "rsiRecovered")
|
|
142
141
|
```
|
|
143
142
|
|
|
144
|
-
|
|
143
|
+
- No `overlay = true`, so the study gets its own pane. `range = [0, 100]` fixes
|
|
144
|
+
that pane's scale.
|
|
145
|
+
- `level` draws a fixed horizontal line.
|
|
146
|
+
- `alert` raises an alert on the bar where the condition is true. The `id` names
|
|
147
|
+
it, so your settings for it survive an edit to the file. Your platform decides
|
|
148
|
+
how it reaches you.
|
|
149
|
+
|
|
150
|
+
## A built-in trend indicator
|
|
151
|
+
|
|
152
|
+
Supertrend, green in an uptrend and red in a downtrend:
|
|
145
153
|
|
|
146
154
|
```
|
|
147
|
-
|
|
155
|
+
version 1
|
|
156
|
+
|
|
157
|
+
study("Supertrend", overlay = true)
|
|
158
|
+
|
|
159
|
+
factor = input(3.0, "Factor")
|
|
160
|
+
atrLength = input(10, "ATR length")
|
|
148
161
|
|
|
149
|
-
|
|
150
|
-
|
|
162
|
+
st = supertrend(factor, atrLength)
|
|
163
|
+
trendLine = st[0]
|
|
164
|
+
direction = st[1]
|
|
151
165
|
|
|
152
|
-
|
|
166
|
+
plot(direction < 0 ? trendLine : none, "Uptrend", lime, width = 2)
|
|
167
|
+
plot(direction > 0 ? trendLine : none, "Downtrend", red, width = 2)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
- Some indicators give back more than one number. `supertrend` gives the line
|
|
171
|
+
and the direction, read as `st[0]` and `st[1]`. The direction is `-1` in an
|
|
172
|
+
uptrend and `1` in a downtrend.
|
|
173
|
+
- `condition ? a : b` picks `a` when the condition is true and `b` otherwise.
|
|
174
|
+
- `none` means no value. A plot given `none` draws nothing on that bar, which is
|
|
175
|
+
how one line becomes two colours.
|
|
176
|
+
|
|
177
|
+
`macd`, `bollinger`, `vwap`, `atr`, `stoch` and many more are built in. The
|
|
178
|
+
[function reference](./docs/reference/functions/ta.md) lists them all.
|
|
179
|
+
|
|
180
|
+
## Your first strategy
|
|
181
|
+
|
|
182
|
+
A strategy is a study that also places orders. This one buys when the fast
|
|
183
|
+
average crosses above the slow one, and sells when it crosses back:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
version 1
|
|
187
|
+
|
|
188
|
+
strategy("EMA cross strategy", overlay = true, qty = 1)
|
|
189
|
+
|
|
190
|
+
fastLength = input(9, "Fast length")
|
|
191
|
+
slowLength = input(21, "Slow length")
|
|
192
|
+
|
|
193
|
+
fast = ema(close, fastLength)
|
|
194
|
+
slow = ema(close, slowLength)
|
|
195
|
+
|
|
196
|
+
if crossUp(fast, slow)
|
|
197
|
+
buy()
|
|
198
|
+
|
|
199
|
+
if crossDown(fast, slow)
|
|
153
200
|
close()
|
|
201
|
+
|
|
202
|
+
plot(fast, "Fast", aqua)
|
|
203
|
+
plot(slow, "Slow", orange)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
- `strategy(...)` in place of `study(...)` is what allows orders. `qty = 1` is
|
|
207
|
+
the size of each order.
|
|
208
|
+
- `buy()` opens a long position. `close()` closes it.
|
|
209
|
+
- An order placed on a bar fills at the **next bar's open**, because a bar's
|
|
210
|
+
close is not known until the bar has finished.
|
|
211
|
+
|
|
212
|
+
Run it as a backtest and you get every trade, the equity curve and a report:
|
|
213
|
+
net profit, win rate, drawdown and more.
|
|
214
|
+
|
|
215
|
+
## A strategy with a stop loss
|
|
216
|
+
|
|
217
|
+
The same entry, with a stop loss and a target measured from the entry price. Both
|
|
218
|
+
are checked on each bar's close, and the exit fills at the next bar's open:
|
|
219
|
+
|
|
154
220
|
```
|
|
221
|
+
version 1
|
|
222
|
+
|
|
223
|
+
strategy("EMA cross with stop and target", overlay = true, qty = 1)
|
|
224
|
+
|
|
225
|
+
fastLength = input(9, "Fast length")
|
|
226
|
+
slowLength = input(21, "Slow length")
|
|
227
|
+
stopPercent = input(1.0, "Stop loss, percent")
|
|
228
|
+
targetPercent = input(2.0, "Target, percent")
|
|
229
|
+
|
|
230
|
+
fast = ema(close, fastLength)
|
|
231
|
+
slow = ema(close, slowLength)
|
|
232
|
+
crossedUp = crossUp(fast, slow)
|
|
233
|
+
crossedDown = crossDown(fast, slow)
|
|
234
|
+
|
|
235
|
+
inTrade = pos.size > 0
|
|
236
|
+
stopPrice = pos.avgPrice * (1 - stopPercent / 100)
|
|
237
|
+
targetPrice = pos.avgPrice * (1 + targetPercent / 100)
|
|
238
|
+
|
|
239
|
+
if crossedUp and not inTrade
|
|
240
|
+
buy()
|
|
155
241
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
## Why it exists
|
|
159
|
-
|
|
160
|
-
Chart scripting today is closed. You write in someone's editor, your script runs
|
|
161
|
-
on their servers under their limits, and the only way out is a webhook. The
|
|
162
|
-
numbers you backtest are not the numbers you trade, and you cannot check either.
|
|
163
|
-
|
|
164
|
-
OpenScript is the opposite of that:
|
|
165
|
-
|
|
166
|
-
- **Yours.** Plain text files in a folder. Version control, diffs, your own editor.
|
|
167
|
-
- **Local.** It compiles and runs on your machine. No execution quota, no loop
|
|
168
|
-
timeout, and nothing you drew silently dropped to make room for the next one.
|
|
169
|
-
- **Honest.** A higher timeframe read has to say whether it repaints. The
|
|
170
|
-
compiler warns when a script would.
|
|
171
|
-
- **Connected.** Designed to route orders through your own broker connection,
|
|
172
|
-
with sandbox testing before live execution. The live runner is planned.
|
|
173
|
-
- **Open.** Apache-2.0, a written specification, and a conformance suite anyone
|
|
174
|
-
can run against their own implementation: the compiler's diagnostics, the
|
|
175
|
-
runtime errors and limits, the per-bar values, the calendar, drawing objects,
|
|
176
|
-
grids, reads of other timeframes and instruments, and strategy fills and
|
|
177
|
-
reports. [The suite's own page](./spec/conformance.md) says what a pass does
|
|
178
|
-
and does not entitle an implementation to claim.
|
|
179
|
-
|
|
180
|
-
## How it is built
|
|
181
|
-
|
|
182
|
-
The compiler does not emit JavaScript. It emits a **compiled program**: a plain
|
|
183
|
-
data structure of instructions, defined by a versioned schema.
|
|
184
|
-
|
|
185
|
-
That one decision carries the whole project:
|
|
186
|
-
|
|
187
|
-
1. It runs in a browser under a strict content security policy, with no `eval`
|
|
188
|
-
and nothing for a security team to approve.
|
|
189
|
-
2. A server-side engine is a few hundred lines that walk an instruction list,
|
|
190
|
-
not a second implementation of the language.
|
|
191
|
-
3. Anyone can write an engine in any language, and hold it to the conformance
|
|
192
|
-
suite. What the suite reaches today is written down rather than implied: an
|
|
193
|
-
engine claiming the narrowest profile is handed cases and fails if it
|
|
194
|
-
answers none of them, which it was not before the `core` cases existed.
|
|
195
|
-
|
|
196
|
-
One compiler. One compiled format. Many small engines, all of which must agree
|
|
197
|
-
to the last decimal.
|
|
198
|
-
|
|
199
|
-
The same decision shapes the editor. This project ships the language
|
|
200
|
-
intelligence as pure functions with no DOM: highlight, complete, diagnose, hover,
|
|
201
|
-
signature, format. Highlighting comes from the real lexer, completions from the
|
|
202
|
-
standard library manifest, and the errors you see while typing are the compiler's
|
|
203
|
-
own, with their fixes taken from the error catalogue. A host supplies the text
|
|
204
|
-
component and the panel around it, and keeps its own design. The editor is not a
|
|
205
|
-
second implementation of the language to be kept in step.
|
|
206
|
-
|
|
207
|
-
[](https://raw.githubusercontent.com/marketcalls/openscript/main/docs/architecture-compiler.png)
|
|
208
|
-
|
|
209
|
-
### From source to a compiled program
|
|
210
|
-
|
|
211
|
-
| Stage | What happens | Implementation |
|
|
212
|
-
|---|---|---|
|
|
213
|
-
| Lex | Turns text into tokens with source positions. Newlines, indentation and dedentation become explicit tokens, so later stages do not reinterpret whitespace. | [`src/core/lex`](./src/core/lex/index.ts) |
|
|
214
|
-
| Parse | Builds the abstract syntax tree (AST). It recovers around malformed statements so an editor can still work with a partially typed file. | [`src/core/parse`](./src/core/parse/index.ts) |
|
|
215
|
-
| Check | Resolves names, checks types and calls, determines storage and tracks when a value becomes available. `CheckedScript` keeps those answers beside the original tree. | [`src/core/check`](./src/core/check/index.ts) |
|
|
216
|
-
| Emit | Allocates registers and persistent state, lowers statements into instructions, links calls and requests, and checks stack depths and limits. | [`src/core/emit`](./src/core/emit/emit.ts) |
|
|
217
|
-
|
|
218
|
-
The result is a [`CompiledProgram`](./src/core/emit/program.ts), not executable
|
|
219
|
-
code in the host's language. Alongside its instructions it carries the constants,
|
|
220
|
-
inputs, output declarations, storage layout and source mapping the engine needs.
|
|
221
|
-
Its [canonical encoding](./src/core/emit/canonical.ts) makes the program portable
|
|
222
|
-
and gives it stable bytes for hashing. The language and compiled format have
|
|
223
|
-
separate versions, defined by the [compiled program specification](./spec/compiled-program.md).
|
|
224
|
-
|
|
225
|
-
Every compiler stage reports through the same diagnostic system. Codes, messages
|
|
226
|
-
and fixes come from the [error catalogue](./spec/errors.json); the
|
|
227
|
-
[headless editor](./src/editor/index.ts) reuses the compiler and its language
|
|
228
|
-
tables for highlighting, completion, diagnostics, hover, signatures and formatting.
|
|
229
|
-
|
|
230
|
-
### From a compiled program to results
|
|
231
|
-
|
|
232
|
-
Before the first bar, [`load`](./src/core/engine/load.ts) verifies the program's
|
|
233
|
-
format, capabilities, tables and instructions, including stack and address
|
|
234
|
-
bounds. It checks the host's limits, resolves inputs and plans requested series.
|
|
235
|
-
A rejected program returns a diagnostic before execution starts.
|
|
236
|
-
|
|
237
|
-
The [engine](./src/core/engine/engine.ts) then advances one bar at a time. Its
|
|
238
|
-
[interpreter](./src/core/engine/machine.ts) walks the instruction list using
|
|
239
|
-
registers, persistent memory and the standard library, with instruction, memory
|
|
240
|
-
and time budgets. Updating the newest bar restores its checkpoint before
|
|
241
|
-
recomputing it; explicitly live state is retained. Drawing values are published
|
|
242
|
-
on updates, while signals, alerts and orders follow the confirmation policy.
|
|
243
|
-
|
|
244
|
-
The [chart adapter](./src/adapters/charts/index.ts) maps study outputs into the
|
|
245
|
-
host's chart. The [backtest driver](./src/core/backtest/drive.ts) supplies a
|
|
246
|
-
simulated order destination and records frames, fills, orders and the report.
|
|
247
|
-
[Replay and rerun](./src/core/backtest/replay.ts) let a stored result be checked
|
|
248
|
-
again. The host owns data access, rendering, persistence and order routing;
|
|
249
|
-
the language core does not reach into those systems itself.
|
|
250
|
-
|
|
251
|
-
## The pieces, and which way they point
|
|
252
|
-
|
|
253
|
-
A platform adopting OpenScript usually already has a chart, or an editor, or a
|
|
254
|
-
broker connection, and sometimes all three. So the pieces are separate packages
|
|
255
|
-
and the dependencies only ever point one way.
|
|
242
|
+
if inTrade and (close < stopPrice or close > targetPrice or crossedDown)
|
|
243
|
+
close()
|
|
256
244
|
|
|
245
|
+
plot(fast, "Fast", aqua)
|
|
246
|
+
plot(slow, "Slow", orange)
|
|
247
|
+
plot(inTrade ? stopPrice : none, "Stop", red, style = "step")
|
|
248
|
+
plot(inTrade ? targetPrice : none, "Target", lime, style = "step")
|
|
257
249
|
```
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
250
|
+
|
|
251
|
+
- `pos.size` is the size of the open position, 0 when there is none, and
|
|
252
|
+
`pos.avgPrice` is its entry price.
|
|
253
|
+
- `not inTrade` stops the strategy buying again while it already holds a
|
|
254
|
+
position.
|
|
255
|
+
- The crosses are worked out once, at the top, before any `if`. A cross checked
|
|
256
|
+
only inside a condition is only tracked on the bars where that condition ran,
|
|
257
|
+
and the compiler warns you when you do it.
|
|
258
|
+
- The last two plots draw the stop and the target only while a trade is open.
|
|
259
|
+
|
|
260
|
+
A strategy can also declare its starting capital, commission and slippage, trade
|
|
261
|
+
both directions and size each trade from the risk. The
|
|
262
|
+
[first strategy guide](./docs/first-strategy.md) walks through all of it.
|
|
263
|
+
|
|
264
|
+
## Run a script from your own code
|
|
265
|
+
|
|
266
|
+
Compile a script, then run it over your bars:
|
|
267
|
+
|
|
268
|
+
```js
|
|
269
|
+
import {
|
|
270
|
+
sourceFile, DiagnosticBag, parse, check, emit, renderDiagnostics, load,
|
|
271
|
+
} from 'openalgo-script';
|
|
272
|
+
|
|
273
|
+
function compile(name, text) {
|
|
274
|
+
const file = sourceFile(name, text);
|
|
275
|
+
const errors = new DiagnosticBag();
|
|
276
|
+
const { program } = emit(file, check(file, parse(file, errors), errors), errors);
|
|
277
|
+
if (program === undefined) throw new Error(renderDiagnostics(file, errors.ordered()));
|
|
278
|
+
return program;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// One object per bar, oldest first. time is in milliseconds.
|
|
282
|
+
const bars = [
|
|
283
|
+
{ time: 1735689600000, open: 100, high: 102, low: 99, close: 101, volume: 1200, oi: null },
|
|
284
|
+
// ...
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
const loaded = load(compile('my-average.oscript', scriptText));
|
|
288
|
+
if (!loaded.ok) throw new Error(loaded.diagnostic.message);
|
|
289
|
+
|
|
290
|
+
const run = loaded.engine.run(bars);
|
|
291
|
+
console.log(run.bars.at(-1).columns); // the plotted values on the last bar
|
|
269
292
|
```
|
|
270
293
|
|
|
271
|
-
|
|
272
|
-
wants only the compiler never pays for an adapter. A tier is declared only once it
|
|
273
|
-
exists: an entry point that resolves to nothing fails at a consumer's run time
|
|
274
|
-
rather than honestly at install.
|
|
275
|
-
|
|
276
|
-
These four resolve today, from an install holding nothing but the manifest and the
|
|
277
|
-
built output. That is a check rather than a sentence: `scripts/check-entry-points.mjs`
|
|
278
|
-
builds exactly that install in a temporary directory, with no package of any kind
|
|
279
|
-
beside it, and imports every one of them.
|
|
280
|
-
|
|
281
|
-
| Entry point | Is | Depends on |
|
|
282
|
-
|---|---|---|
|
|
283
|
-
| `openalgo-script` | Compiler and engine | Nothing |
|
|
284
|
-
| `openalgo-script/editor` | The six headless language functions an editor needs | The compiler |
|
|
285
|
-
| `openalgo-script/adapters/charts` | Turns a compiled study into a chart's indicator descriptor | The compiler and a chart |
|
|
286
|
-
| `openalgo-script/adapters/codemirror` | Wires the six into a text component | The editor half and a text component |
|
|
287
|
-
|
|
288
|
-
The server-side engine is not among them: it is a separate package in another
|
|
289
|
-
language, and the roadmap says which phase owes it.
|
|
290
|
-
|
|
291
|
-
An adapter is the only thing allowed to know two worlds at once, which is what
|
|
292
|
-
makes it the piece a platform replaces rather than the piece they patch. A
|
|
293
|
-
platform with its own chart writes their own chart adapter and keeps everything
|
|
294
|
-
else. A platform with its own editor does the same on that side.
|
|
295
|
-
|
|
296
|
-
This is enforced rather than promised. `scripts/check-layering.mjs` runs in
|
|
297
|
-
continuous integration and fails the build if the compiler imports a chart, if
|
|
298
|
-
anything outside an adapter imports a package, or if anything under `src` so much
|
|
299
|
-
as mentions a browser global. That last one holds of the adapters too: the editor
|
|
300
|
-
adapter takes a tooltip's markup from the host rather than building an element,
|
|
301
|
-
so every file in the package loads in a worker and on a server.
|
|
302
|
-
|
|
303
|
-
## Taking it, one step at a time
|
|
304
|
-
|
|
305
|
-
Each row is usable on its own. Nobody has to take the next one.
|
|
306
|
-
|
|
307
|
-
| You want | You add | Roughly |
|
|
308
|
-
|---|---|---|
|
|
309
|
-
| Scripts that produce numbers | `openalgo-script`, and the six-item host interface | An afternoon |
|
|
310
|
-
| Those studies on your chart | the charts adapter, plus a chart | Days. Free if the chart is the one this adapter already targets |
|
|
311
|
-
| Traders authoring in your app | The editor half, and your own text component or the drop-in adapter | Days |
|
|
312
|
-
| Traders trading from it | Wire the order half of the host interface to your order API | About a week |
|
|
313
|
-
| To run it on your own stack | Implement the compiled program format in your language, then pass the conformance suite | Weeks |
|
|
314
|
-
|
|
315
|
-
The last row is the one that matters for the standard. A platform that will not
|
|
316
|
-
run our code at all reads the compiled program specification, writes its own
|
|
317
|
-
engine, passes the suite, and its traders' scripts are the same scripts as
|
|
318
|
-
everyone else's.
|
|
319
|
-
|
|
320
|
-
## What is guaranteed, and what proves it
|
|
321
|
-
|
|
322
|
-
A platform's engineering review asks two questions about a dependency: what do you
|
|
323
|
-
promise, and how would I know. This table is the answer to the second one. Where a
|
|
324
|
-
row says a check enforces something, that check runs in continuous integration and
|
|
325
|
-
fails the build.
|
|
326
|
-
|
|
327
|
-
| Guarantee | How you can tell | Today |
|
|
328
|
-
|---|---|---|
|
|
329
|
-
| The package cannot build code out of text: every door to a generator is a module, and it imports none of them | `scripts/check-layering.mjs`. Nothing under `src` may import a module from the runtime's own namespace, at either spelling, and no load may have a specifier that is not written down. So there is no virtual machine, no worker and no child process in the graph to reach. Unreachable by construction, not unmatched by a pattern | Enforced |
|
|
330
|
-
| The two names that need no import, the string evaluator and the function builder, are refused | A runtime switch you set on your own process, `--disallow-code-generation-from-strings`. Our own suite runs under it. It refuses those two names and closes no other door: see [`docs/integrating/running-the-engine.md`](./docs/integrating/running-the-engine.md) for what it does not cover and what is yours to do | Yours to turn on |
|
|
331
|
-
| No generator anywhere in this repository's own text | `scripts/check-no-eval.mjs`, over the source, both built outputs, the tooling, the hooks and the build steps, attacking itself with every form it knows before it reads a file. **It is a lint, not a proof.** It has now been got past five times, the last by a constructor reached through a key assembled at run time, and a list of spellings only ever has to be beaten once more. Treat a pass as evidence that no known form is present | **Lint** |
|
|
332
|
-
| A generator that runs is refused | The suite runs under the setting that refuses to compile text, so a builder throws the moment its path executes, however it was spelled. This caught the form the scan missed. What it cannot cover: a line no test reaches, and the module doors above, which is why the row above it matters more than this one | Enforced, for code the tests execute |
|
|
333
|
-
| Zero runtime dependencies | `dependencies` is empty and stays empty | Enforced |
|
|
334
|
-
| The pieces are separable: take the language without the chart, or the chart without the language | `scripts/check-layering.mjs`. The core may not import a package or touch a browser global | Enforced |
|
|
335
|
-
| Small modules with a stated surface | `scripts/check-modularity.mjs`. A module's index is its only door | Enforced |
|
|
336
|
-
| Every error is documented, with a code, a cause and a fix | `scripts/check-error-codes.mjs` reads every file in the tree, and the code type is generated from the catalogue so an invented code will not compile. `scripts/check-catalogue-tests.mjs` compares the catalogue a reader opens with the file the compiler is generated from, string for string, so the two cannot say different things | Enforced |
|
|
337
|
-
| No fact is stated in two places | `scripts/check-duplication.mjs` | Enforced, with recorded debt |
|
|
338
|
-
| The compiled program is implementable without reading our code | `spec/compiled-program.md` and `spec/host-interface.md`. Every engine test drives a host built from those pages rather than one we wrote | Written, and used |
|
|
339
|
-
| Two engines agree to the last decimal | The conformance suite, run against both. A disagreement blocks a release | Phase 6 gate |
|
|
340
|
-
| A runaway script stops | Instruction, memory and wall clock budgets, counters in the loop the engine owns. `tests/engine/budget.test.ts` | Enforced |
|
|
341
|
-
| A failing script does not take anything else down | One script's failure is a diagnostic on that script and reaches nothing else | Enforced |
|
|
342
|
-
| Every entry point in the export map resolves from a real install | `scripts/check-entry-points.mjs`. Each one is imported from a temporary install built from the `files` list alone, with no package beside it, which is also what says both adapters' peer dependencies are optional in fact and not only in the manifest | Enforced |
|
|
343
|
-
| A completion, a tooltip and a default come from the compiler, not from a list | The names are the standard library manifest the checker resolves against; what a call is for is the cell `spec/stdlib.md` prints, read at build time by `scripts/generate-library-prose.mjs`; a default is the one `scripts/check-defaults.mjs` holds the compiler to. `tests/editor/hover.test.ts` fails if the manifest and the specification describe different sets of names | Enforced |
|
|
344
|
-
| Laying a script out again cannot change what it computes | `tests/editor/format.test.ts`. Every example and every gate script is formatted, both texts are compiled, and the compiled programs are compared. Each call also checks itself against the lexer, so a rule that is wrong returns your source untouched rather than a changed program | Enforced |
|
|
345
|
-
| A saved script never stops working | The language version is declared per file and old front ends are retained | Phase 7, with a test per retained version |
|
|
346
|
-
| Performance | Eight benchmarks with recorded budgets, run by `npm test` and in continuous integration. A regression past a budget fails the build | Enforced |
|
|
347
|
-
|
|
348
|
-
The rows marked as gates are not promises we intend to keep. They are conditions a
|
|
349
|
-
phase does not finish without, and each one is written into the roadmap beside the
|
|
350
|
-
phase that owes it.
|
|
351
|
-
|
|
352
|
-
### Why a script cannot reach anything
|
|
353
|
-
|
|
354
|
-
This is the row a security review spends its time on, so it is worth stating
|
|
355
|
-
plainly rather than leaving as a property of the architecture.
|
|
356
|
-
|
|
357
|
-
A compiled program is **data**. It is a list of instructions the engine walks. A
|
|
358
|
-
script has no way to name a function the instruction set does not expose, so there
|
|
359
|
-
is no call into the host, no network, no filesystem, no access to the object graph
|
|
360
|
-
of the process it runs in. There is nothing to escape from, because nothing was
|
|
361
|
-
ever handed over.
|
|
362
|
-
|
|
363
|
-
That also makes the budgets real rather than best-effort. The engine owns the
|
|
364
|
-
loop, so an instruction count per bar, a memory ceiling and a wall clock are
|
|
365
|
-
counters in that loop rather than something to hope about. A platform running many
|
|
366
|
-
customers' scripts in one process needs exactly this, and a design that generates
|
|
367
|
-
code and runs it cannot offer it.
|
|
368
|
-
|
|
369
|
-
## The host interface
|
|
370
|
-
|
|
371
|
-
Whatever a platform takes, it supplies six things and nothing more:
|
|
372
|
-
|
|
373
|
-
1. Bars: open, high, low, close, volume, time.
|
|
374
|
-
2. Instrument facts: tick size, lot size, session, timezone.
|
|
375
|
-
3. More bars on request, for another instrument or another timeframe.
|
|
376
|
-
4. Somewhere to draw.
|
|
377
|
-
5. Somewhere to send orders, if scripts are allowed to trade.
|
|
378
|
-
6. Somewhere to save settings.
|
|
379
|
-
|
|
380
|
-
No instrument naming scheme, no exchange rules and no broker concepts appear in
|
|
381
|
-
the language. A symbol is opaque to it: a script names a contract by what the
|
|
382
|
-
contract is, and the platform resolves that to whatever its own symbology calls
|
|
383
|
-
it. A format built around one market's derivatives means nothing on a crypto
|
|
384
|
-
exchange, and portability is the entire objective.
|
|
385
|
-
|
|
386
|
-
## Errors
|
|
387
|
-
|
|
388
|
-
Every error has a stable code, a message, the cause, the fix and an example,
|
|
389
|
-
held in one machine-readable catalogue. The compiler, the editor and the
|
|
390
|
-
documentation all read that same file, so the documentation cannot drift from
|
|
391
|
-
the compiler. The build fails if an error exists without a documented entry, or
|
|
392
|
-
an entry without a test.
|
|
393
|
-
|
|
394
|
-
## Documentation
|
|
395
|
-
|
|
396
|
-
- [ROADMAP.md](./ROADMAP.md) - what is being built, in what order
|
|
397
|
-
- [docs/](./docs) - guides, once there is something to guide
|
|
398
|
-
- [spec/](./spec) - the language specification and the compiled program schema
|
|
399
|
-
- [CONTRIBUTING.md](./CONTRIBUTING.md) - how to work on this
|
|
400
|
-
- [RELEASING.md](./RELEASING.md) - how a release is published, and the one manual step that cannot be automated
|
|
294
|
+
Backtest a strategy over the same bars:
|
|
401
295
|
|
|
402
|
-
|
|
296
|
+
```js
|
|
297
|
+
import { backtest, settingsFor } from 'openalgo-script';
|
|
403
298
|
|
|
404
|
-
|
|
299
|
+
const result = backtest(compile('ema-cross.oscript', strategyText), bars, settingsFor({
|
|
300
|
+
symbol: 'DEMO', exchange: null, currency: 'INR',
|
|
301
|
+
tickSize: 0.05, lotSize: 1, pointValue: 1, digits: 2,
|
|
302
|
+
}));
|
|
405
303
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
304
|
+
if (result.ok) {
|
|
305
|
+
const { summary, trades } = result.record.report;
|
|
306
|
+
console.log(summary.netProfit, summary.winRate, trades.length);
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
To run the same compiled program on a server in Python, see
|
|
311
|
+
[the Python engine](./docs/integrating/the-python-engine.md). To put OpenScript
|
|
312
|
+
inside your own platform, with its chart and its editor, start at
|
|
313
|
+
[integrating OpenScript](./docs/integrating/README.md).
|
|
314
|
+
|
|
315
|
+
## Words you will see
|
|
316
|
+
|
|
317
|
+
| Word | Means |
|
|
318
|
+
|---|---|
|
|
319
|
+
| Script | One `.oscript` file |
|
|
320
|
+
| Study | A script that only draws: lines, bands, markers, alerts |
|
|
321
|
+
| Strategy | A study that also places orders |
|
|
322
|
+
| Bar | One candle: open, high, low, close, volume and a time |
|
|
323
|
+
| Series | A value with one number per bar, such as `close` or an average |
|
|
324
|
+
| Input | A setting a trader can change without editing the file |
|
|
325
|
+
| `none` | No value on this bar. It is not zero |
|
|
326
|
+
| Backtest | Running a strategy over past bars to see how it would have traded |
|
|
327
|
+
|
|
328
|
+
## Where to go next
|
|
329
|
+
|
|
330
|
+
- [Getting started](./docs/getting-started.md): what happens between pressing
|
|
331
|
+
Apply and a line appearing.
|
|
332
|
+
- [Your first study](./docs/first-study.md) and
|
|
333
|
+
[your first strategy](./docs/first-strategy.md): complete scripts built one
|
|
334
|
+
step at a time.
|
|
335
|
+
- [Examples](./examples/README.md): twelve complete scripts, from an EMA cross to
|
|
336
|
+
an opening range strategy.
|
|
337
|
+
- [The documentation](./docs/README.md): every page, grouped by what it answers.
|
|
338
|
+
- [How OpenScript is built, and what it guarantees](./docs/integrating/architecture.md):
|
|
339
|
+
the compiler, the engines, the conformance suite and what is checked on every
|
|
340
|
+
build.
|
|
341
|
+
- [CHANGELOG.md](./CHANGELOG.md): what changed in each release.
|
|
342
|
+
|
|
343
|
+
## Contributing
|
|
344
|
+
|
|
345
|
+
Questions, bug reports and pull requests are welcome. Read
|
|
346
|
+
[CONTRIBUTING.md](./CONTRIBUTING.md) first, and the
|
|
347
|
+
[code of conduct](./CODE_OF_CONDUCT.md). Report a security issue privately, as
|
|
348
|
+
[SECURITY.md](./SECURITY.md) describes.
|
|
409
349
|
|
|
410
|
-
##
|
|
350
|
+
## Licence
|
|
411
351
|
|
|
412
|
-
|
|
413
|
-
sponsored by, or endorsed by any charting or trading platform, and it is not a
|
|
414
|
-
reimplementation of any existing product. Its specification, its documentation
|
|
415
|
-
and its error text are original work.
|
|
352
|
+
Apache-2.0. See [LICENSE](./LICENSE) and [NOTICE](./NOTICE).
|