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.
Files changed (66) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +316 -379
  3. package/dist/adapters/charts/alerts.d.ts +11 -0
  4. package/dist/adapters/charts/alerts.d.ts.map +1 -1
  5. package/dist/adapters/charts/alerts.js +16 -1
  6. package/dist/adapters/charts/alerts.js.map +1 -1
  7. package/dist/adapters/charts/capabilities.d.ts +48 -0
  8. package/dist/adapters/charts/capabilities.d.ts.map +1 -0
  9. package/dist/adapters/charts/capabilities.js +82 -0
  10. package/dist/adapters/charts/capabilities.js.map +1 -0
  11. package/dist/adapters/charts/columns.d.ts +19 -2
  12. package/dist/adapters/charts/columns.d.ts.map +1 -1
  13. package/dist/adapters/charts/columns.js +15 -3
  14. package/dist/adapters/charts/columns.js.map +1 -1
  15. package/dist/adapters/charts/contract.d.ts +35 -3
  16. package/dist/adapters/charts/contract.d.ts.map +1 -1
  17. package/dist/adapters/charts/descriptor.d.ts +1 -1
  18. package/dist/adapters/charts/descriptor.d.ts.map +1 -1
  19. package/dist/adapters/charts/descriptor.js +17 -5
  20. package/dist/adapters/charts/descriptor.js.map +1 -1
  21. package/dist/adapters/charts/fills.d.ts +26 -5
  22. package/dist/adapters/charts/fills.d.ts.map +1 -1
  23. package/dist/adapters/charts/fills.js +55 -7
  24. package/dist/adapters/charts/fills.js.map +1 -1
  25. package/dist/adapters/charts/index.d.ts +9 -3
  26. package/dist/adapters/charts/index.d.ts.map +1 -1
  27. package/dist/adapters/charts/index.js +7 -1
  28. package/dist/adapters/charts/index.js.map +1 -1
  29. package/dist/adapters/charts/produced.d.ts +3 -2
  30. package/dist/adapters/charts/produced.d.ts.map +1 -1
  31. package/dist/adapters/charts/produced.js +1 -1
  32. package/dist/adapters/charts/produced.js.map +1 -1
  33. package/dist/adapters/charts/run.d.ts +23 -0
  34. package/dist/adapters/charts/run.d.ts.map +1 -1
  35. package/dist/adapters/charts/run.js +2 -1
  36. package/dist/adapters/charts/run.js.map +1 -1
  37. package/dist/adapters/charts/surfaces.d.ts +11 -0
  38. package/dist/adapters/charts/surfaces.d.ts.map +1 -1
  39. package/dist/adapters/charts/tables.d.ts +21 -11
  40. package/dist/adapters/charts/tables.d.ts.map +1 -1
  41. package/dist/adapters/charts/tables.js +17 -6
  42. package/dist/adapters/charts/tables.js.map +1 -1
  43. package/dist/adapters/charts/undrawable.d.ts +2 -1
  44. package/dist/adapters/charts/undrawable.d.ts.map +1 -1
  45. package/dist/adapters/charts/undrawable.js +24 -14
  46. package/dist/adapters/charts/undrawable.js.map +1 -1
  47. package/dist/core/check/call-sites.js +15 -5
  48. package/dist/core/check/call-sites.js.map +1 -1
  49. package/dist/core/version/version.generated.d.ts +1 -1
  50. package/dist/core/version/version.generated.js +1 -1
  51. package/package.json +1 -1
  52. package/spec/errors.json +1 -1
  53. package/src/adapters/charts/alerts.ts +28 -1
  54. package/src/adapters/charts/capabilities.ts +103 -0
  55. package/src/adapters/charts/columns.ts +29 -8
  56. package/src/adapters/charts/contract.ts +36 -2
  57. package/src/adapters/charts/descriptor.ts +21 -6
  58. package/src/adapters/charts/fills.ts +95 -11
  59. package/src/adapters/charts/index.ts +9 -1
  60. package/src/adapters/charts/produced.ts +4 -3
  61. package/src/adapters/charts/run.ts +25 -1
  62. package/src/adapters/charts/surfaces.ts +12 -0
  63. package/src/adapters/charts/tables.ts +31 -14
  64. package/src/adapters/charts/undrawable.ts +24 -14
  65. package/src/core/check/call-sites.ts +15 -5
  66. 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 a study or a strategy once, plot it, backtest it, trade it.**
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
+ [![npm](https://img.shields.io/npm/v/openalgo-script.svg?label=npm)](https://www.npmjs.com/package/openalgo-script)
9
+ [![PyPI](https://img.shields.io/pypi/v/openscript.svg?label=PyPI)](https://pypi.org/project/openscript/)
10
+ [![CI](https://github.com/marketcalls/openscript/actions/workflows/ci.yml/badge.svg)](https://github.com/marketcalls/openscript/actions/workflows/ci.yml)
11
11
  [![license](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE)
12
- [![status](https://img.shields.io/badge/status-early%20development-orange.svg)](./ROADMAP.md)
13
12
 
14
13
  </div>
15
14
 
16
15
  ---
17
16
 
18
- ## Architecture at a glance
19
-
20
- [![OpenScript connects a trading idea to charts, backtests and planned live trading, using the platform's editor, market data and broker connection.](https://raw.githubusercontent.com/marketcalls/openscript/main/docs/architecture-overview.png)](https://raw.githubusercontent.com/marketcalls/openscript/main/docs/architecture-overview.png)
21
-
22
- Write an indicator or a strategy once, then use it on a chart or test it against
23
- historical data. Your platform supplies the editor, saved scripts, market data
24
- and connections. The live runner is planned, with sandbox testing before live
25
- execution; see [Phase 6](./ROADMAP.md#phase-6-the-second-engine-and-live-running).
26
-
27
- ## Status
28
-
29
- **`0.7.2` runs studies, backtests strategies, draws a strategy on a chart,
30
- imports scripts from another chart language, and ships the language
31
- intelligence an editor needs.** The Python engine is on PyPI
32
- as `openscript`, at the same version, and the two are released together.
33
-
34
- What an install gets you today: the compiler, the engine, the chart adapter and
35
- the backtest. A script compiles in milliseconds in a browser tab and computes,
36
- bar by bar, the same numbers everywhere. One hundred and one independently
37
- written studies compile, load and run, and five of them match arithmetic
38
- transcribed from the specification alone, bit for bit, warmups included.
39
-
40
- A strategy is walked over a range of bars and what comes back is a document
41
- rather than a number: the program, the bars, the settings, every frame, every
42
- fill, the ledger and the report. That document replays to the same report and
43
- reruns to the same bytes, and `npm test` proves it over the shipped strategy
44
- examples on every run rather than asserting it in a page. Two records can be put
45
- beside each other, and a pair over different bars is reported incomparable
46
- instead of being subtracted into a table that reads like a result.
47
-
48
- **New in `0.7.2`:** in both engines, one bar whose term overflows no longer
49
- ends `pvt`, `vwap` or `vwapAnchor` for good, and no longer counts as a false
50
- zero in `ad`, `adOsc` or `cmf`. That bar reads `none` and later bars carry on.
51
- Ordinary vectors and the compiled format are unchanged. Upgrade both packages
52
- together.
53
-
54
- `0.7.1` made Python's ADX recover from overflowing directional movements with
55
- the same values as the JavaScript engine.
56
-
57
- The `0.7.0` numerical work established exact comparisons across
58
- all 116 scalar and stateful numerical signatures. Portable elementary kernels,
59
- changing-window corrections and checkpoint tests remove confirmed numerical
60
- deviations. Compiled cases also cover numeric arrays and conversions. Upgrade
61
- both packages together; source signatures and compiled format remain unchanged.
62
- See the [numerical audit](./docs/integrating/numerical-audit.md) for measured
63
- coverage and limitations, and [CHANGELOG.md](./CHANGELOG.md) for results that
64
- can change after upgrading.
65
-
66
- What it does **not** do yet, stated plainly because the registry page is the
67
- first thing a stranger reads:
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
- fast = input(9, "Fast")
132
- slow = input(21, "Slow")
97
+ fastLength = input(9, "Fast length")
98
+ slowLength = input(21, "Slow length")
133
99
 
134
- ef = ema(close, fast)
135
- es = ema(close, slow)
100
+ fast = ema(close, fastLength)
101
+ slow = ema(close, slowLength)
136
102
 
137
- plot(ef, "Fast", aqua)
138
- plot(es, "Slow", orange)
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(ef, es)
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
- The same file becomes a strategy by adding orders:
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
- strategy("EMA cross", overlay = true)
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
- if crossUp(ef, es)
150
- buy(qty = 1)
162
+ st = supertrend(factor, atrLength)
163
+ trendLine = st[0]
164
+ direction = st[1]
151
165
 
152
- if crossDown(ef, es)
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
- One script. One set of numbers on the chart, in the backtest, and in the market.
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
- [![OpenScript compiler and runtime: source passes through lexing, parsing, checking and emission into a portable data program, then verification, loading and bar-by-bar interpretation produce outputs for the host.](https://raw.githubusercontent.com/marketcalls/openscript/main/docs/architecture-compiler.png)](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
- openalgo-script a chart library
259
- (knows nobody) (knows nobody)
260
- | \ /
261
- | \ /
262
- | .../adapters/charts <- knows both. The only place that does
263
- |
264
- .../editor <- the six headless functions. No DOM, no
265
- | package, nothing on screen
266
- |
267
- .../adapters/codemirror <- knows both. Takes its markup from the
268
- host, so it draws nothing either
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
- The language ships as one package with an entry point per tier, so a consumer who
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
- ## Licence
296
+ ```js
297
+ import { backtest, settingsFor } from 'openalgo-script';
403
298
 
404
- Apache-2.0. See [LICENSE](./LICENSE) and [NOTICE](./NOTICE).
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
- Apache-2.0 was chosen deliberately over a copyleft licence. A trading platform
407
- that wants to embed OpenScript must be able to do so without publishing its own
408
- source, or the language cannot become a shared standard.
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
- ## A note on independence
350
+ ## Licence
411
351
 
412
- OpenScript is an independent open source language. It is not affiliated with,
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).