oakscriptjs 0.13.2 → 0.14.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.
Files changed (119) hide show
  1. package/AGENTS.md +6 -1
  2. package/README.md +1 -1
  3. package/bin/oakscript.mjs +5 -0
  4. package/dist/cjs/check/checks.d.ts +78 -0
  5. package/dist/cjs/check/checks.d.ts.map +1 -0
  6. package/dist/cjs/check/checks.js +109 -0
  7. package/dist/cjs/check/checks.js.map +7 -0
  8. package/dist/cjs/check/format.d.ts +26 -0
  9. package/dist/cjs/check/format.d.ts.map +1 -0
  10. package/dist/cjs/check/format.js +106 -0
  11. package/dist/cjs/check/format.js.map +7 -0
  12. package/dist/cjs/check/harness.d.ts +94 -0
  13. package/dist/cjs/check/harness.d.ts.map +1 -0
  14. package/dist/cjs/check/harness.js +302 -0
  15. package/dist/cjs/check/harness.js.map +7 -0
  16. package/dist/cjs/check/index.d.ts +253 -0
  17. package/dist/cjs/check/index.d.ts.map +1 -0
  18. package/dist/cjs/check/index.js +466 -0
  19. package/dist/cjs/check/index.js.map +7 -0
  20. package/dist/cjs/check/node.d.ts +53 -0
  21. package/dist/cjs/check/node.d.ts.map +1 -0
  22. package/dist/cjs/check/node.js +318 -0
  23. package/dist/cjs/check/node.js.map +7 -0
  24. package/dist/cjs/check/spec.d.ts +44 -0
  25. package/dist/cjs/check/spec.d.ts.map +1 -0
  26. package/dist/cjs/check/spec.js +199 -0
  27. package/dist/cjs/check/spec.js.map +7 -0
  28. package/dist/cjs/index.d.ts +1 -1
  29. package/dist/cjs/index.js +1 -1
  30. package/dist/cjs/index.js.map +1 -1
  31. package/dist/cjs/runtime/series.d.ts +6 -0
  32. package/dist/cjs/runtime/series.d.ts.map +1 -1
  33. package/dist/cjs/runtime/series.js +15 -0
  34. package/dist/cjs/runtime/series.js.map +2 -2
  35. package/dist/cjs/script/constants.d.ts +200 -0
  36. package/dist/cjs/script/constants.d.ts.map +1 -0
  37. package/dist/cjs/script/constants.js +217 -0
  38. package/dist/cjs/script/constants.js.map +7 -0
  39. package/dist/cjs/script/drawing-args.d.ts +25 -0
  40. package/dist/cjs/script/drawing-args.d.ts.map +1 -0
  41. package/dist/cjs/script/drawing-args.js +203 -0
  42. package/dist/cjs/script/drawing-args.js.map +7 -0
  43. package/dist/cjs/script/globals.d.ts +127 -0
  44. package/dist/cjs/script/globals.d.ts.map +1 -0
  45. package/dist/cjs/script/globals.js +17 -0
  46. package/dist/cjs/script/globals.js.map +7 -0
  47. package/dist/cjs/script/index.d.ts +499 -166
  48. package/dist/cjs/script/index.d.ts.map +1 -1
  49. package/dist/cjs/script/index.js +236 -96
  50. package/dist/cjs/script/index.js.map +3 -3
  51. package/dist/esm/check/checks.d.ts +78 -0
  52. package/dist/esm/check/checks.d.ts.map +1 -0
  53. package/dist/esm/check/checks.js +95 -0
  54. package/dist/esm/check/checks.js.map +1 -0
  55. package/dist/esm/check/format.d.ts +26 -0
  56. package/dist/esm/check/format.d.ts.map +1 -0
  57. package/dist/esm/check/format.js +105 -0
  58. package/dist/esm/check/format.js.map +1 -0
  59. package/dist/esm/check/harness.d.ts +94 -0
  60. package/dist/esm/check/harness.d.ts.map +1 -0
  61. package/dist/esm/check/harness.js +317 -0
  62. package/dist/esm/check/harness.js.map +1 -0
  63. package/dist/esm/check/index.d.ts +253 -0
  64. package/dist/esm/check/index.d.ts.map +1 -0
  65. package/dist/esm/check/index.js +470 -0
  66. package/dist/esm/check/index.js.map +1 -0
  67. package/dist/esm/check/node.d.ts +53 -0
  68. package/dist/esm/check/node.d.ts.map +1 -0
  69. package/dist/esm/check/node.js +347 -0
  70. package/dist/esm/check/node.js.map +1 -0
  71. package/dist/esm/check/spec.d.ts +44 -0
  72. package/dist/esm/check/spec.d.ts.map +1 -0
  73. package/dist/esm/check/spec.js +198 -0
  74. package/dist/esm/check/spec.js.map +1 -0
  75. package/dist/esm/index.d.ts +1 -1
  76. package/dist/esm/index.js +1 -1
  77. package/dist/esm/runtime/series.d.ts +6 -0
  78. package/dist/esm/runtime/series.d.ts.map +1 -1
  79. package/dist/esm/runtime/series.js +14 -0
  80. package/dist/esm/runtime/series.js.map +1 -1
  81. package/dist/esm/script/constants.d.ts +200 -0
  82. package/dist/esm/script/constants.d.ts.map +1 -0
  83. package/dist/esm/script/constants.js +151 -0
  84. package/dist/esm/script/constants.js.map +1 -0
  85. package/dist/esm/script/drawing-args.d.ts +25 -0
  86. package/dist/esm/script/drawing-args.d.ts.map +1 -0
  87. package/dist/esm/script/drawing-args.js +171 -0
  88. package/dist/esm/script/drawing-args.js.map +1 -0
  89. package/dist/esm/script/globals.d.ts +127 -0
  90. package/dist/esm/script/globals.d.ts.map +1 -0
  91. package/dist/esm/script/globals.js +28 -0
  92. package/dist/esm/script/globals.js.map +1 -0
  93. package/dist/esm/script/index.d.ts +499 -166
  94. package/dist/esm/script/index.d.ts.map +1 -1
  95. package/dist/esm/script/index.js +256 -83
  96. package/dist/esm/script/index.js.map +1 -1
  97. package/package.json +31 -1
  98. package/skills/oakscript/SKILL.md +62 -26
  99. package/skills/oakscript/examples/ma-cross.js +2 -2
  100. package/skills/oakscript/examples/mtf-trend-filter.js +3 -3
  101. package/skills/oakscript/reference/check.md +150 -0
  102. package/skills/oakscript/reference/errors.md +31 -7
  103. package/skills/oakscript/reference/pine-differences.md +17 -16
  104. package/skills/oakscript/reference/plotting.md +25 -19
  105. package/skills/oakscript/reference/script-structure.md +8 -5
  106. package/skills/oakscript/reference/timeframes-symbols.md +5 -1
  107. package/skills/oakscript/spec.schema.json +199 -0
  108. package/src/check/checks.ts +175 -0
  109. package/src/check/format.ts +117 -0
  110. package/src/check/harness.ts +372 -0
  111. package/src/check/index.ts +708 -0
  112. package/src/check/node.ts +368 -0
  113. package/src/check/spec.ts +200 -0
  114. package/src/index.ts +1 -1
  115. package/src/runtime/series.ts +19 -0
  116. package/src/script/constants.ts +169 -0
  117. package/src/script/drawing-args.ts +179 -0
  118. package/src/script/globals.ts +129 -0
  119. package/src/script/index.ts +457 -81
@@ -27,8 +27,8 @@ fill(fastPlot, slowPlot, {
27
27
 
28
28
  const crossUp = ta.crossover(fast, slow);
29
29
  const crossDown = ta.crossunder(fast, slow);
30
- plotshape(crossUp, "Cross up", { style: "triangleup", location: "belowbar", color: color.teal, size: "small" });
31
- plotshape(crossDown, "Cross down", { style: "triangledown", location: "abovebar", color: color.red, size: "small" });
30
+ plotshape(crossUp, "Cross up", { style: shape.triangleup, location: location.belowbar, color: color.teal, size: size.small });
31
+ plotshape(crossDown, "Cross down", { style: shape.triangledown, location: location.abovebar, color: color.red, size: size.small });
32
32
 
33
33
  alertcondition(crossUp, "Cross up", "Fast MA crossed above the slow MA");
34
34
  alertcondition(crossDown, "Cross down", "Fast MA crossed below the slow MA");
@@ -33,14 +33,14 @@ const signal = eachBar((c) => {
33
33
  return 0;
34
34
  });
35
35
 
36
- plotshape(signal.eq(1), "Long", { style: "labelup", location: "belowbar", color: color.teal, text: "L", textcolor: color.white });
37
- plotshape(signal.eq(-1), "Short", { style: "labeldown", location: "abovebar", color: color.red, text: "S", textcolor: color.white });
36
+ plotshape(signal.eq(1), "Long", { style: shape.labelup, location: location.belowbar, color: color.teal, text: "L", textcolor: color.white });
37
+ plotshape(signal.eq(-1), "Short", { style: shape.labeldown, location: location.abovebar, color: color.red, text: "S", textcolor: color.white });
38
38
  bgcolor(color.when(upTrend, color.new(color.teal, 92), color.new(color.red, 92)), { title: "Trend" });
39
39
 
40
40
  // One label on the last bar with the filter state.
41
41
  eachBar((c) => {
42
42
  if (c.i === c.n - 1) {
43
43
  const text = (c.get(upTrend) === 1 ? "Uptrend " : "Downtrend ") + filterTf;
44
- label.new(c.i, c.high, text, "bar_index", "price", color.gray, "label_down", color.white);
44
+ label.new(c.i, c.high, text, { color: color.gray, style: label.style_label_down, textcolor: color.white });
45
45
  }
46
46
  });
@@ -0,0 +1,150 @@
1
+ # Checking a script: `oakscript check`
2
+
3
+ `oakscript check` runs a script the way an app runs it (the declarations on zero bars, then a run on bars) and
4
+ prints what it declares and what it outputs, the errors with their line, warnings for the mistakes that give no error,
5
+ and the result of a spec: the inputs, outputs and signals the user asked for.
6
+
7
+ ```sh
8
+ npx oakscript check my-indicator.js # synthetic bars (1,500, hourly)
9
+ npx oakscript check my-indicator.js --spec spec.json # + the user's spec (and its checks file)
10
+ npx oakscript check my-indicator.js --scenarios # + edge-case bars
11
+ npx oakscript check my-port.js --export chart-export.csv # + bars and expected values of a chart export
12
+ ```
13
+
14
+ Exit code: `0` clean, `2` warnings only, `1` an error or a failed spec / reference check. Run it after each change
15
+ until it gives `0` (or `2` with warnings you have read and explained).
16
+
17
+ ## Workflow
18
+
19
+ 1. Write the spec from the user's request **before** the script (below): title, pane, inputs with their defaults,
20
+ outputs, what the signals should look like; and a checks file for the behaviour the spec cannot count ("Buy only
21
+ when the RSI is below 30"). Add `"scenarios": true`.
22
+ 2. Write the script.
23
+ 3. `npx oakscript check script.js --spec spec.json`; fix the errors, then the failed spec items and checks, then
24
+ read the warnings.
25
+ 4. For a port of a Pine script with known values, add `--export` (or `--bars` and `--reference`).
26
+
27
+ ## What it checks
28
+
29
+ | Stage | Reports |
30
+ |---|---|
31
+ | types | TypeScript errors of the script with the script globals (`TS2362` operator on a Series, `TS2551` unknown name...), when `typescript` is installed in the project; `--no-types` skips it |
32
+ | syntax | the JavaScript syntax error and its line |
33
+ | declaration | errors of the top-level code (the declaration run of an app), e.g. `close - open` |
34
+ | run | errors on the bars (inside `eachBar`), with the line |
35
+ | timeout | a run longer than `--timeout` (default 10 s) |
36
+ | spec | an unknown key or a wrong value in the spec (`"signal"` for `"signals"`): a misspelt spec would check nothing |
37
+ | scenario | an error on the edge-case bars of `--scenarios` |
38
+
39
+ Then, on a clean run: the inputs (id, type, default, options), each plot (number of values, first bar, min / max,
40
+ last 5 values, colours), each signal (shapes, characters, arrows, alert conditions, background and bar colours: bars,
41
+ share, first and last bar), drawings, `alert()` calls, and for a strategy the trades (count, wins, net profit, the
42
+ first trades) on a small built-in order engine (next-bar-open fills, stop / limit exits).
43
+
44
+ ## Warnings
45
+
46
+ | Code | Meaning |
47
+ |---|---|
48
+ | `empty-plot` | a plot with no value (na on every bar) |
49
+ | `constant-plot` | the same value on every bar (a level is an `hline()`) |
50
+ | `late-start` | the first value after half of the bars |
51
+ | `signal-never` | a signal that is never true |
52
+ | `signal-everywhere` | a signal on more than half of the bars: often a Series used as a JavaScript boolean |
53
+ | `one-colour` | per-bar colours that are the same on every bar |
54
+ | `unused-input` | changing the input changes no output (the check runs again with each input changed) |
55
+ | `input-error` | another valid value of the input stops the script |
56
+ | `no-trades` | a strategy without any trade |
57
+ | `no-output` | nothing is drawn |
58
+
59
+ A warning is not always a mistake (a rare signal on 1,500 bars): read it and keep it when it is what the user wants.
60
+
61
+ ## The spec
62
+
63
+ A JSON file; every entry is optional and only the given ones are checked. Inputs, plots and signals are found by id
64
+ or title (case insensitive). An unknown key or a wrong value is an error (with the closest key), so a misspelt entry
65
+ does not pass unchecked. Its JSON Schema is `node_modules/oakscriptjs/skills/oakscript/spec.schema.json`: point
66
+ `$schema` at it to get the keys in an editor.
67
+
68
+ ```json
69
+ {
70
+ "$schema": "./node_modules/oakscriptjs/skills/oakscript/spec.schema.json",
71
+ "kind": "indicator",
72
+ "title": "MACD Histogram",
73
+ "overlay": false,
74
+ "inputs": {
75
+ "fast_length": { "type": "int", "defval": 12 },
76
+ "Source": { "type": "source", "defval": "close" },
77
+ "MA type": { "options": ["SMA", "EMA"] }
78
+ },
79
+ "outputs": ["MACD", "Signal", { "title": "Zero", "kind": "hline" }],
80
+ "plots": { "MACD": { "warmup": 40 }, "RSI": { "min": 0, "max": 100 } },
81
+ "signals": { "Buy": { "min": 1, "maxShare": 0.1 } },
82
+ "trades": { "min": 1 },
83
+ "checks": "checks.js",
84
+ "scenarios": true,
85
+ "reference": "expected.csv",
86
+ "tolerance": 1e-10
87
+ }
88
+ ```
89
+
90
+ - `outputs`: titles that must exist; `kind` is one of `plot`, `shape`, `char`, `arrow`, `candle`, `bar`, `hline`,
91
+ `bgcolor`, `barcolor`, `alertcondition`.
92
+ - `plots`: `min` / `max` bound every value, `warmup` is the latest bar of the first value.
93
+ - `signals`: `min` / `max` count bars, `minShare` / `maxShare` the share of the bars (0 to 1).
94
+ - `checks`, `scenarios`, `reference`, `export`, `tolerance`: as `--checks`, `--scenarios`, `--reference`, `--export`,
95
+ `--tolerance` (paths relative to the spec file).
96
+
97
+ ## Behaviour checks
98
+
99
+ What a spec cannot write as counts and ranges goes in a checks file: plain JavaScript, the body of a function of `t`
100
+ (no import or export). `t.plot(name)` gives a plot per bar (`values`, `at(i)`, `colors`), `t.signal(name)` the bars of
101
+ a signal (`bars`, `on(i)`), `t.trades` the closed trades of a strategy, `t.input(name)` the value of an input, `t.bars`
102
+ and `t.n` the bars. Record each check with `t.expect(name, ok, detail?)` or `t.everyBar(name, (i) => ..., { from, to })`
103
+ (it fails on the first bar where the function is false).
104
+
105
+ ```js
106
+ const rsi = t.plot("RSI");
107
+ t.everyBar("RSI between 0 and 100", (i) => rsi.at(i) === null || (rsi.at(i) >= 0 && rsi.at(i) <= 100));
108
+ t.expect("Buy only when the RSI is below 30", t.signal("Buy").bars.every((i) => rsi.at(i) < 30));
109
+ t.expect("Buy right after the RSI was at or above 30", t.signal("Buy").bars.every((i) => rsi.at(i - 1) >= 30));
110
+ ```
111
+
112
+ The checks run on the main run; their results come after the spec items. An error in the file (an unknown plot, a
113
+ typo) fails the checks with its line.
114
+
115
+ ## Scenarios
116
+
117
+ `--scenarios` (or `"scenarios": true`) runs the script again on edge-case bars: 30 bars (a short history), 800 daily
118
+ bars, 1,000 one-minute bars, 200 flat bars (zero ranges, no volume), 500 bars without volume, and a 3-day gap. An
119
+ error there fails the check; a plot with no value that the main run does not have is listed per scenario (on 30
120
+ bars it is often expected, a division by a zero range is not).
121
+
122
+ ## Bars and reference files
123
+
124
+ - `--bars`: CSV with the columns `time` (UNIX seconds or milliseconds, or a date), `open`, `high`, `low`, `close`,
125
+ optional `volume`; or a JSON array of `{ time, open, high, low, close, volume }`. The timeframe comes from the bar
126
+ spacing unless `--timeframe` gives it. Other symbols of `request.security` get synthetic bars.
127
+ - `--reference`: CSV with a `time` column and one column per plot (id or title); empty or `na` is na. A value is
128
+ equal when `|a - b| <= tolerance * max(1, |a|, |b|)` (default `1e-10`) or both are na. Use the bars the reference
129
+ was computed on. The bar columns (`open`, `high`, `low`, `close`, `volume`) are skipped.
130
+ - `--export`: one CSV with both: the chart data exported with the original script on the chart (a `time` column,
131
+ the bars, then one column per plot title, as charting apps export them). The bars are the bars of the run, the
132
+ other columns the expected values; a column with no plot of that title (another script on the chart) is ignored.
133
+
134
+ ## Other options
135
+
136
+ `--inputs '{"length": 50}'` (input values by id), `--count 3000` (synthetic bars), `--timeframe D`, `--no-sweep`
137
+ (no reruns for unused inputs), `--json` (the whole report as JSON).
138
+
139
+ ## From code
140
+
141
+ ```ts
142
+ import { assertCheck, checkScript } from 'oakscriptjs/check';
143
+ const report = checkScript(source, { spec, bars, scenarios: true, checks: (t) => { /* as a checks file */ } });
144
+ // report.ok, errors, warnings, plots, signals, spec, reference, scenarios
145
+
146
+ // in a test of any runner: throws the report as text when the check fails ({ warnings: false }: also on warnings)
147
+ assertCheck(checkScript(source, { spec }));
148
+ ```
149
+
150
+ `checkScript` runs anywhere (no Node API); the command adds the file lines, the time limit and the type check.
@@ -1,7 +1,22 @@
1
1
  # Errors and how to read them
2
2
 
3
3
  A script fails in one of three ways: it does not compile, it stops with a runtime error, or it runs and gives a
4
- wrong output. Fix them in that order, then check the values.
4
+ wrong output. Fix them in that order, then check the values. `npx oakscript check script.js` ([check](check.md))
5
+ reports all three, with the lines, and warns about the silent mistakes of the last table.
6
+
7
+ ## Type-check errors
8
+
9
+ | Error | Cause | Fix |
10
+ |---|---|---|
11
+ | `TS2362` / `TS2363` `The left-hand (right-hand) side of an arithmetic operation must be of type...` | `close - open`, `rsi * 2` | Series methods: `close.sub(open)`, `rsi.mul(2)` |
12
+ | `TS2365` `Operator '>' cannot be applied to types 'Series' and 'number'` | `rsi > 50`, `c.close > ta.sma(close, 5)` in a loop | `rsi.gt(50)`; in a loop `c.close > c.get(sma)` with `sma` computed before |
13
+ | `TS2339` / `TS2551` `Property 'smaa' does not exist on type...` | a misspelt or Pine-only name | the name it suggests, [pine-differences.md](pine-differences.md) |
14
+ | `TS2322` `Type '"triangel"' is not assignable to type 'ShapeStyle'` | a bad constant string | one of the strings it lists |
15
+ | `TS2561` / `TS2353` `Object literal may only specify known properties` | a misspelt option (`colour`) | the option it suggests |
16
+ | `TS2554` `Expected 4 arguments, but got 3` | a missing argument (`ta.stoch(close, high, low)`) | pass it: `ta.stoch(close, high, low, 14)` |
17
+ | `TS2339` `Property 'entry_price' does not exist on type 'number'` | `strategy.opentrades.entry_price(0)` (`strategy.opentrades` is the count) | `strategy.opentrade(0)?.entry_price`, `strategy.closedtrade(i)?.profit` |
18
+ | `TS2769` `No overload matches this call` on a drawing | a misspelt named argument (`{ textcolour: ... }`) | the Pine name (`textcolor`) |
19
+ | `TS8010` `Type annotations can only be used in TypeScript files` | TypeScript syntax | remove the types |
5
20
 
6
21
  ## Compile errors (syntax)
7
22
 
@@ -22,9 +37,18 @@ The run stops at the first error; the message names the call. The ones you will
22
37
 
23
38
  | Message | Cause | Fix |
24
39
  |---|---|---|
25
- | `TypeError: series.toArray is not a function` | a number or NaN passed where a Series is expected: `plot(close - open)`, `plot(5)`, `ta.sma(a * 2, 10)` | Series methods: `close.sub(open)`, `a.mul(2)`; a constant level: `hline(5)` |
26
- | `TypeError: ta.xyz is not a function` / `xyz is not defined` | a name that does not exist (typo, Pine-only name such as `shape`, `location`, `size`, `xloc`, `bar_index`) | [pine-differences.md](pine-differences.md): strings for the constants, `c.i` for `bar_index` |
27
- | `TypeError: Cannot read properties of undefined (reading 'toArray')` | a missing Series argument, e.g. `ta.vwma(close, 20)` | pass it: `ta.vwma(close, 20, volume)` |
40
+ | `TypeError: JavaScript operators (+ - * / % ** < > <= >= ==) do not work on a Series: use its methods...` | an operator on a Series: `close - open`, `rsi > 50`, `fast > slow ? a : b` | the method it lists: `close.sub(open)`, `rsi.gt(50)`, `fast.gt(slow).iff(a, b)`; a colour: `color.when(fast.gt(slow), a, b)` |
41
+ | `TypeError: A Series is not a text...` | a Series in a text: `` `RSI ${rsi}` ``, `"RSI " + rsi` | in a loop: `str.tostring(c.get(rsi))` |
42
+ | `ta.sma() was called inside eachBar(): a ta.* call computes the whole Series at once...` | a `ta.*` function in a bar callback | compute it before the loop, read it with `c.get(x)`; a Pine call in an `if` block: `callsite.*` |
43
+ | `TypeError: ta.smaa does not exist. Did you mean ta.sma?` (also `math.`, `str.`, `color.`, `input.`, `request.`) | a misspelt or Pine-only member | a suggested name, [pine-differences.md](pine-differences.md) |
44
+ | `TypeError: series.toArray is not a function` | a number or NaN passed where a Series is expected: `plot(5)`, `ta.sma(c.close, 10)` | a Series; a constant level: `hline(5)` |
45
+ | `xyz is not defined` | a global that does not exist (Pine-only name such as `shape`, `location`, `size`, `xloc`, `bar_index`) | [pine-differences.md](pine-differences.md): strings for the constants, `c.i` for `bar_index` |
46
+ | `TypeError: Cannot read properties of undefined (reading 'toArray')` | a missing Series argument, e.g. `ta.stoch(close, high, low)` | pass it: `ta.stoch(close, high, low, 14)` |
47
+ | `TypeError: label.new(): unknown argument "textcolour". Did you mean textcolor?` | a misspelt named argument of a drawing | the name it suggests |
48
+ | `TypeError: input.int(): unknown argument "min". Did you mean minval?` | an input option that is not Pine's (`min`, `max`, `choices`) | Pine's name: `minval`, `maxval`, `options` |
49
+ | `TypeError: request.security(): unknown argument "lookahed"...` | a misspelt named argument of `request.security` | `gaps`, `lookahead`, `ignore_invalid_symbol` |
50
+ | `TypeError: label.new(): argument 1 is a Series, a drawing takes one value: call label.new() inside eachBar()...` | a drawing created at the top level with a Series (`label.new(bar_index, high)`) | create it in a bar loop |
51
+ | `TypeError: strategy.opentrades.entry_price is not a function` | `strategy.opentrades` is the count | `strategy.opentrade(0)?.entry_price` |
28
52
  | `Error on bar N: Invalid value of the 'length' argument (0.0) in the 'sma' function. It must be > 0.` | a length of 0, negative or NaN (often an input) | `minval: 1` on the input, or guard the value |
29
53
  | `Error on bar N: The 'sma' function references too many historical candles (6000), the limit is 5000.` | a window longer than 5000 bars | a shorter length |
30
54
  | `Error on bar N: The script attempts to reference historical data that is too far from the current bar (K bars back)...` | `x.offset(k)` / `c.get(x, k)` with k above 5000, or a history that keeps growing (`c.get(x, c.i)`) | bounded offsets |
@@ -49,9 +73,9 @@ The run stops at the first error; the message names the call. The ones you will
49
73
 
50
74
  | Symptom | Cause | Fix |
51
75
  |---|---|---|
52
- | a branch taken on every bar, or never | `if (ta.crossover(a, b))`, `cond ? x : y`, `a > b` with Series at the top level: an object is always true, an object comparison always false | `cond.iff(x, y)`, `a.gt(b)`, or `c.get(cond) === 1` in a loop |
53
- | one colour on every bar | `color: fast > slow ? color.green : color.red` | `color.when(fast.gt(slow), color.green, color.red)` |
54
- | an empty plot | NaN on every bar: a `ta.*` Series returned from a bar loop, a division by zero, a NaN input | compute `ta.*` before the loop, guard divisions (`range.gt(0).iff(x.div(range), 0)`) |
76
+ | a branch taken on every bar | `if (ta.crossover(a, b))`, `cond ? x : y`, `!cond`, `cond && x` with a Series `cond`: an object is always true | `cond.iff(x, y)`, `cond.not()`, `cond.and(x)`, or `c.get(cond) === 1` in a loop |
77
+ | one colour on every bar | `color: cond ? color.green : color.red` with a Series `cond` | `color.when(cond, color.green, color.red)` |
78
+ | an empty plot | NaN on every bar: a division by zero, a NaN input | guard divisions (`range.gt(0).iff(x.div(range), 0)`) |
55
79
  | a plot that starts late | the warm-up of long lengths, or of a higher timeframe (20 daily bars on an hourly chart is 480 bars) | expected; shorter lengths or more bars |
56
80
  | values that look like the future (backtests too good) | `barmerge.lookahead_on` on a series that is not offset | lookahead off, or `.offset(1)` inside the expression |
57
81
  | a strategy without trades | conditions read as `c.get(cond)` on NaN-only Series, orders placed outside the conditions you expect, `pyramiding: 0` blocking a second entry in the same direction | plot the conditions (`plotshape`) and check them first |
@@ -10,8 +10,8 @@ from the vectorized execution.
10
10
  |---|---|
11
11
  | `//@version=6` | nothing |
12
12
  | `indicator("T", overlay=true)` | `indicator("T", { overlay: true })` |
13
- | `len = input.int(14, "Length", minval=1)` | `const len = input.int(14, "Length", { minval: 1 })` |
14
- | `src = input.source(close, "Source")` | `const src = input.source("close", "Source")` (the default is a name) |
13
+ | `len = input.int(14, "Length", minval=1)` | `const len = input.int(14, "Length", { minval: 1 })` or `input.int(14, { title: "Length", minval: 1 })` |
14
+ | `src = input.source(close, "Source")` | `const src = input.source(close, "Source")` |
15
15
  | `a + b`, `a - b`, `a * b`, `a / b` on series | `a.add(b)`, `a.sub(b)`, `a.mul(b)`, `a.div(b)` |
16
16
  | `a > b`, `a == b`, `a and b`, `not a` on series | `a.gt(b)`, `a.eq(b)`, `a.and(b)`, `a.not()` |
17
17
  | `cond ? x : y` on series | `cond.iff(x, y)` |
@@ -21,27 +21,30 @@ from the vectorized execution.
21
21
  | `if close > open` (per bar) | in a bar loop: `if (c.close > c.open)` |
22
22
  | `for i = 0 to n - 1` | `for (let i = 0; i < n; i++)` in a bar loop |
23
23
  | `switch` / `if` returning a value | JS `switch` / `if` in a bar loop, or `iff` chains on Series |
24
- | `bar_index`, `last_bar_index` | `c.i`, `c.n - 1` |
24
+ | `bar_index`, `last_bar_index` | the Series `bar_index`, `last_bar_index`; in a bar loop `c.i`, `c.n - 1` |
25
25
  | `myFunc(x) => ...` | a JS `function`; on Series it composes Series methods |
26
26
  | `[a, b] = f()` | `const [a, b] = f()` |
27
27
  | `plot(x, "T", color=c, style=plot.style_columns)` | `plot(x, "T", { color: c, style: plot.style_columns })` |
28
28
  | `plot(x, color = cond ? c1 : c2)` | `plot(x, "T", { color: color.when(cond, c1, c2) })` |
29
- | `plotshape(cond, style=shape.triangleup, location=location.belowbar, size=size.small)` | `plotshape(cond, "T", { style: "triangleup", location: "belowbar", size: "small" })` |
30
- | `label.new(bar_index, high, "A", style=label.style_label_down)` | `label.new(c.i, c.high, "A", undefined, undefined, undefined, "label_down")` (positional) |
31
- | `line.new(x1, y1, x2, y2, extend=extend.right)` | `line.new(x1, y1, x2, y2, "bar_index", "right")` |
32
- | `xloc.bar_time`, `yloc.abovebar`, `line.style_dashed`, `format.volume`, `display.none` | `"bar_time"`, `"abovebar"`, `"dashed"`, `"volume"`, `"none"` |
33
- | `dayofweek.monday` | `2` (1 = Sunday ... 7 = Saturday) |
29
+ | `plotshape(cond, style=shape.triangleup, location=location.belowbar, size=size.small)` | `plotshape(cond, "T", { style: shape.triangleup, location: location.belowbar, size: size.small })` |
30
+ | `label.new(bar_index, high, "A", style=label.style_label_down)` (in an `if`) | in a bar loop: `label.new(bar_index, high, "A", { style: label.style_label_down })` (or `c.i`, `c.high`) |
31
+ | `line.new(x1, y1, x2, y2, extend=extend.right)` | `line.new(x1, y1, x2, y2, { extend: extend.right })` |
32
+ | `xloc.bar_time`, `yloc.abovebar`, `line.style_dashed`, `format.volume`, `display.none`, `dayofweek.monday` | the same |
34
33
  | `request.security(sym, "D", ta.ema(close, 20))` | `request.security(sym, "D", () => ta.ema(close, 20))` |
34
+ | `request.security(sym, "W", high[1], lookahead = barmerge.lookahead_on)` | `request.security(sym, "W", () => high.offset(1), { lookahead: barmerge.lookahead_on })` |
35
35
  | `strategy.entry("L", strategy.long, when = cond)` | `strategy.eachBar((c) => { if (c.get(cond) === 1) strategy.entry("L", strategy.long); })` |
36
- | `strategy.opentrades.entry_price(0)` | `strategy.opentrade(0)?.entry_price` |
36
+ | `strategy.opentrades.entry_price(0)` | `strategy.opentrade(0)?.entry_price` (`strategy.opentrades` is the count: a JavaScript number cannot also hold the functions) |
37
37
  | `strategy.closedtrades.profit(i)` | `strategy.closedtrade(i)?.profit` |
38
38
  | `array.new_float(10, 0)`, `matrix.new<float>()`, `map.new<string, float>()` | `import { array, matrix, map } from "oakscriptjs";` then `array.new_float(10, 0)`, `matrix.new_matrix(...)`, `map.new_map()`; or plain JS arrays / `Map` |
39
39
  | `log.info("x = " + str.tostring(x))` | not available: plot it with `display: "data_window"` or put it in a label |
40
40
 
41
- Constants that **do** exist as namespaces: `color.*`, `plot.style_*`, `plot.linestyle_*`, `hline.style_*`,
41
+ The Pine constants exist with their Pine names: `shape.*`, `location.*`, `size.*`, `xloc.*`, `yloc.*`,
42
+ `extend.*`, `display.*`, `format.*`, `font.family_*`, `scale.*`, `order.*`, `currency.*`, `adjustment.*`,
43
+ `label.style_*`, `line.style_*`, `dayofweek.*`, `color.*`, `plot.style_*`, `plot.linestyle_*`, `hline.style_*`,
42
44
  `strategy.long / short / fixed / cash / percent_of_equity`, `strategy.commission.*`, `strategy.direction.*`,
43
45
  `strategy.oca.*`, `barmerge.*`, `position.*`, `text.align_* / wrap_* / format_*`, `alert.freq_*`,
44
- `session.regular / extended`, `math.pi / e / phi / rphi`.
46
+ `session.regular / extended`, `math.pi / e / phi / rphi`. Their values are the strings the functions take
47
+ (`shape.triangleup` is `"triangleup"`).
45
48
 
46
49
  ## Execution differences
47
50
 
@@ -62,16 +65,14 @@ Constants that **do** exist as namespaces: `color.*`, `plot.style_*`, `plot.line
62
65
 
63
66
  ## API differences
64
67
 
65
- - `ta.vwma(source, length, volume)` needs the volume Series (Pine reads the chart volume).
66
68
  - `color.from_gradient` takes numbers: build per-bar gradients in a bar loop.
67
- - `plot()` has no `offset`, `show_last`, `trackprice` or `join` option; shift a series right with
68
- `x.offset(k)`. `plotshape`, `plotchar`, `plotarrow` and `bgcolor` accept `offset` (negative too).
69
+ - `plot()` has no `show_last`, `trackprice` or `join` option. `offset` shifts the plot as in Pine (values past the
70
+ first or last bar are not drawn).
69
71
  - `fill()` accepts `show_last` / `fillgaps` / `display` and ignores them.
70
- - `input.enum` and `input.price` do not exist (use `input.string(..., { options })` and `input.float`).
72
+ - `input.enum` does not exist (no `enum` declarations): use `input.string(..., { options })`.
71
73
  - `request.*`: only `request.security` and `request.security_lower_tf`. No `request.financial`,
72
74
  `request.dividends`, `request.earnings`, `request.splits`, `request.economic`, `request.seed`,
73
75
  `request.currency_rate`, `request.footprint`. `ticker.*`: only `ticker.heikinashi` and `ticker.standard`.
74
- - `box.set_xloc()` is not implemented.
75
76
  - `math.random(min, max, seed)`: with a seed, Pine's sequence, but one generator per seed value (Pine has one
76
77
  per call site): two calls with the same seed need `callsite.random(seed)`.
77
78
  - `math.log`, `math.exp`, `math.pow`, `math.sin`, `math.cos`, `math.tan`, `math.log10` can differ from Pine in
@@ -75,10 +75,11 @@ Note: `ta.bb` returns `[basis, upper, lower]` as in Pine.
75
75
  ## Shapes, characters, arrows
76
76
 
77
77
  - `plotshape(cond, title, { style, location, color, text, textcolor, size, offset, tooltip })`: a shape on
78
- each bar where `cond` is not 0 / na. `style`: `"xcross"` (default), `"cross"`, `"triangleup"`,
79
- `"triangledown"`, `"flag"`, `"circle"`, `"arrowup"`, `"arrowdown"`, `"labelup"`, `"labeldown"`, `"square"`,
80
- `"diamond"`. `location`: `"abovebar"` (default), `"belowbar"`, `"top"`, `"bottom"`, `"absolute"` (at the
81
- series value). `size`: `"auto"`, `"tiny"`, `"small"`, `"normal"`, `"large"`, `"huge"`.
78
+ each bar where `cond` is not 0 / na. `style`: `shape.xcross` (default), `shape.cross`, `shape.triangleup`,
79
+ `shape.triangledown`, `shape.flag`, `shape.circle`, `shape.arrowup`, `shape.arrowdown`, `shape.labelup`,
80
+ `shape.labeldown`, `shape.square`, `shape.diamond`. `location`: `location.abovebar` (default), `location.belowbar`,
81
+ `location.top`, `location.bottom`, `location.absolute` (at the series value). `size`: `size.auto`, `size.tiny`,
82
+ `size.small`, `size.normal`, `size.large`, `size.huge`.
82
83
  - `plotchar(cond, title, { char, location, color, size, offset })`: a character.
83
84
  - `plotarrow(series, title, { colorup, colordown, minheight, maxheight })`: an up arrow for a positive value,
84
85
  a down arrow for a negative one.
@@ -86,11 +87,11 @@ Note: `ta.bb` returns `[basis, upper, lower]` as in Pine.
86
87
  ```js
87
88
  indicator("Markers", { overlay: true });
88
89
  const ema = ta.ema(close, 20);
89
- plotshape(ta.crossover(close, ema), "Buy", { style: "labelup", location: "belowbar", color: color.green, text: "Buy", textcolor: color.white });
90
- plotshape(ta.crossunder(close, ema), "Sell", { style: "labeldown", location: "abovebar", color: color.red, text: "Sell", textcolor: color.white });
90
+ plotshape(ta.crossover(close, ema), "Buy", { style: shape.labelup, location: location.belowbar, color: color.green, text: "Buy", textcolor: color.white });
91
+ plotshape(ta.crossunder(close, ema), "Sell", { style: shape.labeldown, location: location.abovebar, color: color.red, text: "Sell", textcolor: color.white });
91
92
  // a pivot is known 5 bars later: offset -5 puts the mark on the pivot bar; "absolute" places it at the value
92
- plotshape(ta.pivothigh(5, 5), "Pivot high", { style: "diamond", location: "absolute", offset: -5, color: color.orange, size: "tiny" });
93
- plotchar(ta.pivotlow(5, 5), "Pivot low", { char: "^", location: "belowbar", offset: -5, color: color.teal });
93
+ plotshape(ta.pivothigh(5, 5), "Pivot high", { style: shape.diamond, location: location.absolute, offset: -5, color: color.orange, size: size.tiny });
94
+ plotchar(ta.pivotlow(5, 5), "Pivot low", { char: "^", location: location.belowbar, offset: -5, color: color.teal });
94
95
  plotarrow(ta.change(close, 3), "3-bar change", { colorup: color.teal, colordown: color.red });
95
96
  plot(ema, "EMA");
96
97
  ```
@@ -131,21 +132,26 @@ plotcandle(haOpen, seriesOf(highs), seriesOf(lows), haClose, "HA", {
131
132
 
132
133
  ## Drawings
133
134
 
134
- Drawings are created in a bar loop, with **positional** arguments in the Pine order and string constants. Skip
135
- an argument with `undefined`.
135
+ Drawings are created in a bar loop, with the Pine arguments: the first ones by position, the others by name in an
136
+ object after them (Pine's named arguments), e.g. `label.new(c.i, c.high, "A", { style: label.style_label_down })`.
137
+ Every argument can be named (`line.new({ x1, y1, x2, y2, extend: extend.right })`); an unknown name throws with the
138
+ closest names. In the loop, a Series argument is its value on the current bar: `label.new(bar_index, high, "A")` is
139
+ `label.new(c.i, c.high, "A")`.
136
140
 
137
141
  | Call | Arguments |
138
142
  |---|---|
139
- | `label.new` | `x, y, text, xloc ("bar_index" / "bar_time"), yloc ("price" / "abovebar" / "belowbar"), color, style, textcolor, size, textalign, tooltip` |
140
- | `line.new` | `x1, y1, x2, y2, xloc, extend ("none" / "left" / "right" / "both"), color, style ("solid" / "dashed" / "dotted" / "arrow_left" / "arrow_right" / "arrow_both"), width` |
141
- | `box.new` | `left, top, right, bottom, border_color, border_width, border_style, extend, xloc, bgcolor, text, text_size, text_color` |
143
+ | `label.new` | `x, y, text, xloc (xloc.bar_index / xloc.bar_time), yloc (yloc.price / yloc.abovebar / yloc.belowbar), color, style, textcolor, size, textalign, tooltip, text_font_family, force_overlay, text_formatting` |
144
+ | `line.new` | `x1, y1, x2, y2, xloc, extend (extend.none / left / right / both), color, style (line.style_solid / dashed / dotted / arrow_left / arrow_right / arrow_both), width, force_overlay` |
145
+ | `box.new` | `left, top, right, bottom, border_color, border_width, border_style, extend, xloc, bgcolor, text, text_size, text_color, text_halign, text_valign, text_wrap, text_font_family, force_overlay, text_formatting` |
142
146
  | `table.new` | `position, columns, rows, bgcolor, frame_color, frame_width, border_color, border_width` |
143
- | `table.cell` | `table, column, row, text, width, height, text_color, text_halign, text_valign, text_size, bgcolor, tooltip` |
147
+ | `table.cell` | `table_id, column, row, text, width, height, text_color, text_halign, text_valign, text_size, bgcolor, tooltip, text_font_family, text_formatting` |
144
148
  | `polyline.new` | `points (chart.point.from_index(i, price)...), curved, closed, xloc, line_color, fill_color, line_style, line_width` |
145
149
  | `linefill.new` | `line1, line2, color` |
146
150
 
147
- Label styles: `"label_down"`, `"label_up"`, `"label_left"`, `"label_right"`, `"label_center"`, `"none"`, and the
148
- shape names. `x` is a bar index (`c.i`, or `c.i + 10` for a future bar) or, with `"bar_time"`, a UNIX time in ms.
151
+ Label styles: `label.style_label_down`, `label.style_label_up`, `label.style_label_left`, `label.style_label_right`,
152
+ `label.style_label_center`, `label.style_none`, and `label.style_<shape>`. `x` is a bar index (`c.i`, or `c.i + 10`
153
+ for a future bar) or, with `xloc.bar_time`, a UNIX time in ms. The constants are the strings (`"label_down"`), which
154
+ also work.
149
155
  Positions of tables: `position.top_right`, `position.bottom_left`...
150
156
 
151
157
  ```js
@@ -156,11 +162,11 @@ const lo = ta.lowest(50);
156
162
  eachBar((c) => {
157
163
  const p = c.get(ph);
158
164
  if (!Number.isNaN(p)) {
159
- label.new(c.i - 5, p, str.tostring(p, "#.##"), "bar_index", "price", color.red, "label_down", color.white, "small");
165
+ label.new(c.i - 5, p, str.tostring(p, "#.##"), { color: color.red, style: label.style_label_down, textcolor: color.white, size: size.small });
160
166
  }
161
167
  if (c.i === c.n - 1) {
162
- line.new(c.i - 49, c.get(hi), c.i + 10, c.get(hi), "bar_index", "none", color.green, "dashed", 2);
163
- box.new(c.i - 49, c.get(hi), c.i, c.get(lo), color.gray, 1, "dotted", "none", "bar_index", color.new(color.gray, 90), "50-bar range");
168
+ line.new(c.i - 49, c.get(hi), c.i + 10, c.get(hi), { color: color.green, style: line.style_dashed, width: 2 });
169
+ box.new(c.i - 49, c.get(hi), c.i, c.get(lo), { border_color: color.gray, border_style: line.style_dotted, bgcolor: color.new(color.gray, 90), text: "50-bar range" });
164
170
  const t = table.new(position.bottom_right, 1, 1, color.new(color.black, 30));
165
171
  table.cell(t, 0, 0, "Range " + str.tostring(c.get(hi) - c.get(lo), "#.##"), 0, 0, color.white);
166
172
  }
@@ -19,7 +19,7 @@ values they plot.
19
19
  ```js
20
20
  indicator("Average True Range Bands", {
21
21
  shorttitle: "ATR Bands", // legend title
22
- overlay: true, // default true: price pane; false: own pane
22
+ overlay: true, // true: price pane; false (default): own pane
23
23
  precision: 2, // decimals shown
24
24
  max_labels_count: 100, // also max_lines_count, max_boxes_count, max_polylines_count (default 50)
25
25
  });
@@ -29,7 +29,7 @@ plot(close.sub(atr.mul(2)), "Lower");
29
29
  ```
30
30
 
31
31
  `strategy(title, options)` replaces `indicator()` for a strategy (default `overlay: false`); its properties are in
32
- [strategies.md](strategies.md). A script without a declaration runs with the title "Untitled" and overlay on.
32
+ [strategies.md](strategies.md). A script without a declaration runs with the title "Untitled" in its own pane.
33
33
 
34
34
  ## Inputs
35
35
 
@@ -50,7 +50,10 @@ override when there is one, else the default.
50
50
  | `input.symbol("NASDAQ:AAPL", title)` | ticker id for `request.security` |
51
51
  | `input.time(timestamp("2024-01-01"), title)` | UNIX time in ms |
52
52
 
53
- Options of every kind: `group`, `inline`, `tooltip`, `confirm`, `display`, `active` (false greys the input out).
53
+ Options of every kind: `group`, `inline`, `tooltip`, `confirm`, `display`, `active` (false greys the input out). They
54
+ have Pine's names (`minval`, not `min`): an unknown name throws with the closest names. The title can also be in the
55
+ options object (`input.int(14, { title: "Length", minval: 1 })`), or every argument named (`input.int({ defval: 14,
56
+ title: "Length" })`).
54
57
 
55
58
  ```js
56
59
  indicator("Inputs demo");
@@ -132,7 +135,7 @@ const trail = eachBar((c) => {
132
135
  });
133
136
  const up = seriesOf(dirs).eq(1);
134
137
  plot(trail, "Stop", { style: plot.style_linebr, color: color.when(up, color.teal, color.red) });
135
- plotshape(up.and(up.offset(1).not()), "Flip up", { style: "circle", location: "belowbar", color: color.teal, size: "tiny" });
138
+ plotshape(up.and(up.offset(1).not()), "Flip up", { style: shape.circle, location: location.belowbar, color: color.teal, size: size.tiny });
136
139
  ```
137
140
 
138
141
  - A `let` before the loop is Pine `var`: it keeps its value from bar to bar. A `const` inside the callback is a
@@ -169,7 +172,7 @@ executeScript(body, bars, inputs, { ... })".
169
172
  indicator("Session marks", { overlay: true });
170
173
  const newDay = timeframe.change("D");
171
174
  bgcolor(color.when(newDay, color.new(color.blue, 80)), { title: "New day" });
172
- plotchar(barstate.islast, "Last", { char: "L", location: "abovebar" });
175
+ plotchar(barstate.islast, "Last", { char: "L", location: location.abovebar });
173
176
  plot(close, "Close", { display: "none" });
174
177
  ```
175
178
 
@@ -35,10 +35,14 @@ plot(prevLow, "Previous day low", { color: color.red, style: plot.style_linebr }
35
35
 
36
36
  ```js
37
37
  indicator("Weekly open", { overlay: true });
38
- const weekOpen = request.security("", "W", () => open, barmerge.gaps_off, barmerge.lookahead_on);
38
+ const weekOpen = request.security("", "W", () => open, { lookahead: barmerge.lookahead_on });
39
39
  plot(weekOpen, "Week open", { color: color.purple, style: plot.style_stepline });
40
40
  ```
41
41
 
42
+ The arguments after the expression are Pine's, by position (`gaps, lookahead, ignore_invalid_symbol`) or by name in an
43
+ object (`{ gaps, lookahead, ignore_invalid_symbol }`); an unknown name throws with the closest names. `currency` and
44
+ `calc_bars_count` are not supported (they throw).
45
+
42
46
  ### Other symbols
43
47
 
44
48
  ```js