oakscriptjs 0.10.0 → 0.11.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 (201) hide show
  1. package/dist/cjs/box/index.d.ts.map +1 -1
  2. package/dist/cjs/box/index.js +7 -1
  3. package/dist/cjs/box/index.js.map +2 -2
  4. package/dist/cjs/callsite/index.d.ts +73 -32
  5. package/dist/cjs/callsite/index.d.ts.map +1 -1
  6. package/dist/cjs/callsite/index.js +69 -65
  7. package/dist/cjs/callsite/index.js.map +2 -2
  8. package/dist/cjs/drawing/registry.d.ts +75 -0
  9. package/dist/cjs/drawing/registry.d.ts.map +1 -1
  10. package/dist/cjs/drawing/registry.js +139 -6
  11. package/dist/cjs/drawing/registry.js.map +2 -2
  12. package/dist/cjs/index.d.ts +8 -2
  13. package/dist/cjs/index.d.ts.map +1 -1
  14. package/dist/cjs/index.js +30 -1
  15. package/dist/cjs/index.js.map +2 -2
  16. package/dist/cjs/label/index.d.ts.map +1 -1
  17. package/dist/cjs/label/index.js +3 -0
  18. package/dist/cjs/label/index.js.map +2 -2
  19. package/dist/cjs/line/index.d.ts.map +1 -1
  20. package/dist/cjs/line/index.js +7 -1
  21. package/dist/cjs/line/index.js.map +2 -2
  22. package/dist/cjs/math/index.d.ts.map +1 -1
  23. package/dist/cjs/math/index.js +2 -0
  24. package/dist/cjs/math/index.js.map +2 -2
  25. package/dist/cjs/polyline/index.d.ts.map +1 -1
  26. package/dist/cjs/polyline/index.js +3 -0
  27. package/dist/cjs/polyline/index.js.map +2 -2
  28. package/dist/cjs/runtime/index.d.ts +2 -1
  29. package/dist/cjs/runtime/index.d.ts.map +1 -1
  30. package/dist/cjs/runtime/index.js +2 -0
  31. package/dist/cjs/runtime/index.js.map +2 -2
  32. package/dist/cjs/runtime/input-active.d.ts +27 -0
  33. package/dist/cjs/runtime/input-active.d.ts.map +1 -0
  34. package/dist/cjs/runtime/input-active.js +44 -0
  35. package/dist/cjs/runtime/input-active.js.map +7 -0
  36. package/dist/cjs/runtime/types.d.ts +29 -2
  37. package/dist/cjs/runtime/types.d.ts.map +1 -1
  38. package/dist/cjs/runtime/types.js.map +1 -1
  39. package/dist/cjs/script/index.d.ts +53 -17
  40. package/dist/cjs/script/index.d.ts.map +1 -1
  41. package/dist/cjs/script/index.js +70 -11
  42. package/dist/cjs/script/index.js.map +3 -3
  43. package/dist/cjs/session/bars.d.ts +15 -1
  44. package/dist/cjs/session/bars.d.ts.map +1 -1
  45. package/dist/cjs/session/bars.js +41 -6
  46. package/dist/cjs/session/bars.js.map +3 -3
  47. package/dist/cjs/session/calendar.d.ts +9 -1
  48. package/dist/cjs/session/calendar.d.ts.map +1 -1
  49. package/dist/cjs/session/calendar.js +28 -2
  50. package/dist/cjs/session/calendar.js.map +2 -2
  51. package/dist/cjs/session/context.d.ts +75 -0
  52. package/dist/cjs/session/context.d.ts.map +1 -0
  53. package/dist/cjs/session/context.js +113 -0
  54. package/dist/cjs/session/context.js.map +7 -0
  55. package/dist/cjs/str/index.d.ts +12 -4
  56. package/dist/cjs/str/index.d.ts.map +1 -1
  57. package/dist/cjs/str/index.js +13 -7
  58. package/dist/cjs/str/index.js.map +2 -2
  59. package/dist/cjs/str/numberformat.d.ts +10 -2
  60. package/dist/cjs/str/numberformat.d.ts.map +1 -1
  61. package/dist/cjs/str/numberformat.js +10 -2
  62. package/dist/cjs/str/numberformat.js.map +2 -2
  63. package/dist/cjs/ta/index.d.ts +33 -17
  64. package/dist/cjs/ta/index.d.ts.map +1 -1
  65. package/dist/cjs/ta/index.js +70 -29
  66. package/dist/cjs/ta/index.js.map +2 -2
  67. package/dist/cjs/ta/limits.d.ts +24 -0
  68. package/dist/cjs/ta/limits.d.ts.map +1 -0
  69. package/dist/cjs/ta/limits.js +50 -0
  70. package/dist/cjs/ta/limits.js.map +7 -0
  71. package/dist/cjs/ta/running-sum.d.ts +0 -16
  72. package/dist/cjs/ta/running-sum.d.ts.map +1 -1
  73. package/dist/cjs/ta/running-sum.js +2 -0
  74. package/dist/cjs/ta/running-sum.js.map +2 -2
  75. package/dist/cjs/ta-series.d.ts +7 -7
  76. package/dist/cjs/ta-series.d.ts.map +1 -1
  77. package/dist/cjs/ta-series.js +9 -7
  78. package/dist/cjs/ta-series.js.map +2 -2
  79. package/dist/cjs/time/index.d.ts +5 -2
  80. package/dist/cjs/time/index.d.ts.map +1 -1
  81. package/dist/cjs/time/index.js +2 -2
  82. package/dist/cjs/time/index.js.map +2 -2
  83. package/dist/cjs/time/session.d.ts +17 -1
  84. package/dist/cjs/time/session.d.ts.map +1 -1
  85. package/dist/cjs/time/session.js +61 -7
  86. package/dist/cjs/time/session.js.map +2 -2
  87. package/dist/cjs/types/metadata.d.ts +27 -3
  88. package/dist/cjs/types/metadata.d.ts.map +1 -1
  89. package/dist/cjs/types/metadata.js.map +1 -1
  90. package/dist/esm/box/index.d.ts.map +1 -1
  91. package/dist/esm/box/index.js +8 -2
  92. package/dist/esm/box/index.js.map +1 -1
  93. package/dist/esm/callsite/index.d.ts +73 -32
  94. package/dist/esm/callsite/index.d.ts.map +1 -1
  95. package/dist/esm/callsite/index.js +143 -106
  96. package/dist/esm/callsite/index.js.map +1 -1
  97. package/dist/esm/drawing/registry.d.ts +75 -0
  98. package/dist/esm/drawing/registry.d.ts.map +1 -1
  99. package/dist/esm/drawing/registry.js +237 -6
  100. package/dist/esm/drawing/registry.js.map +1 -1
  101. package/dist/esm/index.d.ts +8 -2
  102. package/dist/esm/index.d.ts.map +1 -1
  103. package/dist/esm/index.js +11 -1
  104. package/dist/esm/index.js.map +1 -1
  105. package/dist/esm/label/index.d.ts.map +1 -1
  106. package/dist/esm/label/index.js +4 -1
  107. package/dist/esm/label/index.js.map +1 -1
  108. package/dist/esm/line/index.d.ts.map +1 -1
  109. package/dist/esm/line/index.js +8 -2
  110. package/dist/esm/line/index.js.map +1 -1
  111. package/dist/esm/math/index.d.ts.map +1 -1
  112. package/dist/esm/math/index.js +3 -0
  113. package/dist/esm/math/index.js.map +1 -1
  114. package/dist/esm/polyline/index.d.ts.map +1 -1
  115. package/dist/esm/polyline/index.js +5 -1
  116. package/dist/esm/polyline/index.js.map +1 -1
  117. package/dist/esm/runtime/index.d.ts +2 -1
  118. package/dist/esm/runtime/index.d.ts.map +1 -1
  119. package/dist/esm/runtime/index.js +1 -0
  120. package/dist/esm/runtime/index.js.map +1 -1
  121. package/dist/esm/runtime/input-active.d.ts +27 -0
  122. package/dist/esm/runtime/input-active.d.ts.map +1 -0
  123. package/dist/esm/runtime/input-active.js +54 -0
  124. package/dist/esm/runtime/input-active.js.map +1 -0
  125. package/dist/esm/runtime/types.d.ts +29 -2
  126. package/dist/esm/runtime/types.d.ts.map +1 -1
  127. package/dist/esm/script/index.d.ts +53 -17
  128. package/dist/esm/script/index.d.ts.map +1 -1
  129. package/dist/esm/script/index.js +80 -13
  130. package/dist/esm/script/index.js.map +1 -1
  131. package/dist/esm/session/bars.d.ts +15 -1
  132. package/dist/esm/session/bars.d.ts.map +1 -1
  133. package/dist/esm/session/bars.js +50 -6
  134. package/dist/esm/session/bars.js.map +1 -1
  135. package/dist/esm/session/calendar.d.ts +9 -1
  136. package/dist/esm/session/calendar.d.ts.map +1 -1
  137. package/dist/esm/session/calendar.js +35 -3
  138. package/dist/esm/session/calendar.js.map +1 -1
  139. package/dist/esm/session/context.d.ts +75 -0
  140. package/dist/esm/session/context.d.ts.map +1 -0
  141. package/dist/esm/session/context.js +122 -0
  142. package/dist/esm/session/context.js.map +1 -0
  143. package/dist/esm/str/index.d.ts +12 -4
  144. package/dist/esm/str/index.d.ts.map +1 -1
  145. package/dist/esm/str/index.js +33 -12
  146. package/dist/esm/str/index.js.map +1 -1
  147. package/dist/esm/str/numberformat.d.ts +10 -2
  148. package/dist/esm/str/numberformat.d.ts.map +1 -1
  149. package/dist/esm/str/numberformat.js +16 -2
  150. package/dist/esm/str/numberformat.js.map +1 -1
  151. package/dist/esm/ta/index.d.ts +33 -17
  152. package/dist/esm/ta/index.d.ts.map +1 -1
  153. package/dist/esm/ta/index.js +130 -56
  154. package/dist/esm/ta/index.js.map +1 -1
  155. package/dist/esm/ta/limits.d.ts +24 -0
  156. package/dist/esm/ta/limits.d.ts.map +1 -0
  157. package/dist/esm/ta/limits.js +42 -0
  158. package/dist/esm/ta/limits.js.map +1 -0
  159. package/dist/esm/ta/running-sum.d.ts +0 -16
  160. package/dist/esm/ta/running-sum.d.ts.map +1 -1
  161. package/dist/esm/ta/running-sum.js +2 -0
  162. package/dist/esm/ta/running-sum.js.map +1 -1
  163. package/dist/esm/ta-series.d.ts +7 -7
  164. package/dist/esm/ta-series.d.ts.map +1 -1
  165. package/dist/esm/ta-series.js +9 -7
  166. package/dist/esm/ta-series.js.map +1 -1
  167. package/dist/esm/time/index.d.ts +5 -2
  168. package/dist/esm/time/index.d.ts.map +1 -1
  169. package/dist/esm/time/index.js +6 -3
  170. package/dist/esm/time/index.js.map +1 -1
  171. package/dist/esm/time/session.d.ts +17 -1
  172. package/dist/esm/time/session.d.ts.map +1 -1
  173. package/dist/esm/time/session.js +97 -10
  174. package/dist/esm/time/session.js.map +1 -1
  175. package/dist/esm/types/metadata.d.ts +27 -3
  176. package/dist/esm/types/metadata.d.ts.map +1 -1
  177. package/package.json +1 -1
  178. package/src/box/index.ts +8 -2
  179. package/src/callsite/index.ts +150 -100
  180. package/src/drawing/registry.ts +255 -5
  181. package/src/index.ts +36 -1
  182. package/src/label/index.ts +4 -1
  183. package/src/line/index.ts +8 -2
  184. package/src/math/index.ts +2 -0
  185. package/src/polyline/index.ts +3 -1
  186. package/src/runtime/index.ts +3 -0
  187. package/src/runtime/input-active.ts +50 -0
  188. package/src/runtime/types.ts +25 -2
  189. package/src/script/index.ts +123 -23
  190. package/src/session/bars.ts +47 -6
  191. package/src/session/calendar.ts +37 -3
  192. package/src/session/context.ts +160 -0
  193. package/src/str/index.ts +30 -12
  194. package/src/str/numberformat.ts +18 -3
  195. package/src/ta/index.ts +148 -66
  196. package/src/ta/limits.ts +47 -0
  197. package/src/ta/running-sum.ts +2 -0
  198. package/src/ta-series.ts +16 -14
  199. package/src/time/index.ts +6 -3
  200. package/src/time/session.ts +89 -9
  201. package/src/types/metadata.ts +27 -3
@@ -8,6 +8,41 @@
8
8
  * - when a creation brings the count above `max_*_count + 5`, the oldest objects are deleted until
9
9
  * `max_*_count` remain (default 50). Copies count as creations. Linefills follow the same rule with
10
10
  * `max_lines_count`; lines deleted by this rule do not delete their linefills
11
+ * - the object that was just created is never deleted by this rule
12
+ * - an object that a `var` variable of the drawing type refers to is never deleted by this rule, and it still counts
13
+ * (measured for lines, labels and boxes: with 9 held boxes and a maximum of 50, 9 held + 46 others stay; with 8
14
+ * held lines and a maximum of 5, the 8 and the newest line stay). The protection starts on the bar after the
15
+ * assignment: 12 `var` lines created on the first bar with a maximum of 5 lose the first 6. Only
16
+ * the variable itself protects: an object in a `var` array, in a field of a user type or in a variable that got
17
+ * another object or `na` later is deleted like any other. JavaScript has no `var` declaration to observe, so the
18
+ * script says it: {@link varDrawing} (a variable), or {@link holdDrawing} / {@link releaseDrawing}
19
+ * - linefills have the limit of `max_lines_count` with another rule for the held ones (measured on one script, two
20
+ * long runs): a fill is skipped when a `var` variable holds it or released it less than 5,000 bars ago (the bar
21
+ * of {@link setDrawingBar}); when the count is still above the maximum + 5 after that, the oldest fills are
22
+ * deleted until the maximum remain, the held ones too
23
+ * - the script deletes count (`line.delete`, `label.delete`, ...): each kind has a number `P` of deleted objects. A
24
+ * delete adds 1; a creation first sets `P` to 0 when it is 5 or more (3 or more for polylines)
25
+ * - polylines use `P` on every bar: a creation cuts when the count is above `max_polylines_count + 5 + P`, down to
26
+ * the maximum + `P`, and the polylines of the cut are added to `P`. A script that deletes and creates one polyline
27
+ * per bar therefore sees the cut 0, 1 or 2 bars late
28
+ * - lines, labels and boxes use `P` on the last bar only, with its value at the start of that bar: there the cut is
29
+ * above the maximum + 5 + `P`, down to the maximum + `P` (up to 4 more objects)
30
+ * - the last bar has rules of its own (measured from the count after each creation, on a closed and on an open last
31
+ * bar): a cut deletes the objects of the bars before first, at most those (a line takes its linefills with it);
32
+ * once none is left, a cut deletes objects of the last bar as on any bar. With 10 lines and a maximum of 10, 13
33
+ * lines on the last bar leave these 13: the cut at the 16th line deletes the 10 old ones only. Lines, labels and
34
+ * boxes: equal to PineScript, count after every operation and objects alive at the end, on 72 of 72 runs checked
35
+ * after the rule was written (and 218 of 218 end states before)
36
+ * - polylines on the last bar: the `P` carried from the bar before is counted twice (the first creation adds it once
37
+ * more, unless it sets `P` to 0), later creations do not set `P` to 0, and the polylines of the bars before that a
38
+ * cut deletes are added to `P`. Once none is
39
+ * left, a cut deletes polylines of the last bar and `P` becomes the number of polylines of the bars before deleted
40
+ * on the last bar (by the count or by the script). Equal to PineScript for the polylines alive at the end on 271 of
41
+ * 281 probe runs; the count inside the bar can differ when the script deletes polylines of the last bar
42
+ *
43
+ * - a bar index (`xloc.bar_index`) more than 10,000 bars before the current bar stops the script (runtime error
44
+ * RE10026) in `line.new`, `box.new`, `label.new`, `polyline.new` and the setters of x. The current bar is the one
45
+ * of `eachBar` in a script run; a port with its own bar loop gives it with {@link setDrawingBar}
11
46
  *
12
47
  * The script API (`executeScript`) resets the registry at the start of every run and applies the
13
48
  * `max_*_count` values of `indicator()`. Outside a script run the registry is global to the module.
@@ -50,6 +85,150 @@ let limits: Required<DrawingLimits> = {
50
85
  polyline: DEFAULT_MAX_COUNT,
51
86
  };
52
87
  const deleted = new WeakSet<object>();
88
+ /** Objects held by a `var` variable, with the number of variables that hold them. */
89
+ let held = new WeakMap<object, number>();
90
+ /** Bar on which the last `var` variable released an object (linefill rule). */
91
+ let releasedAt = new WeakMap<object, number>();
92
+ /** Bars during which a released linefill is still skipped by the maximum count. */
93
+ const LINEFILL_RELEASE_BARS = 5000;
94
+ /** Bar on which a `var` variable got an object: the maximum count skips it from the next bar on. */
95
+ let heldAt = new WeakMap<object, number>();
96
+ /** Kinds with a maximum count of their own. */
97
+ type CountedKind = 'line' | 'label' | 'box' | 'polyline';
98
+ const COUNTED_KINDS: readonly CountedKind[] = ['line', 'label', 'box', 'polyline'];
99
+ const isCounted = (kind: DrawingKind): kind is CountedKind => (COUNTED_KINDS as readonly DrawingKind[]).includes(kind);
100
+ /** `P` of the module notes: objects deleted by the script (for polylines also by the maximum count). */
101
+ let pending: Record<CountedKind, number> = { line: 0, label: 0, box: 0, polyline: 0 };
102
+ /** A creation sets `P` to 0 from this value on. */
103
+ const PENDING_RESET: Record<CountedKind, number> = { line: 5, label: 5, box: 5, polyline: 3 };
104
+
105
+ /**
106
+ * Marks an object as held by a `var` variable (PineScript `var line l = ...` or `l := line.new(...)` on a `var`
107
+ * variable): the maximum count never deletes it, and it still counts. Returns the object.
108
+ */
109
+ export function holdDrawing<T extends object>(obj: T): T {
110
+ if (!held.has(obj)) {
111
+ if (currentBar === null) heldAt.delete(obj);
112
+ else heldAt.set(obj, currentBar);
113
+ }
114
+ held.set(obj, (held.get(obj) ?? 0) + 1);
115
+ return obj;
116
+ }
117
+
118
+ /** The `var` variable no longer refers to the object (it got another object or `na`). */
119
+ export function releaseDrawing(obj: object | null | undefined): void {
120
+ if (!obj) return;
121
+ const count = held.get(obj) ?? 0;
122
+ if (count > 1) held.set(obj, count - 1);
123
+ else if (held.delete(obj) && currentBar !== null) releasedAt.set(obj, currentBar);
124
+ }
125
+
126
+ /** Held by a `var` variable since a bar before the current one (or outside a bar loop). */
127
+ function heldBefore(obj: object): boolean {
128
+ if (!held.has(obj)) return false;
129
+ const bar = heldAt.get(obj);
130
+ return bar === undefined || currentBar === null || bar < currentBar;
131
+ }
132
+
133
+ /** Linefill rule: held, or released less than 5,000 bars ago. */
134
+ function fillProtected(obj: object): boolean {
135
+ if (held.has(obj)) return true;
136
+ const bar = releasedAt.get(obj);
137
+ return bar !== undefined && currentBar !== null && currentBar - bar < LINEFILL_RELEASE_BARS;
138
+ }
139
+
140
+ /** A `var` variable of a drawing type: see {@link varDrawing}. */
141
+ export interface DrawingVariable<T extends object> {
142
+ /** The object of the variable; setting it holds the new object and releases the one before */
143
+ value: T | undefined;
144
+ }
145
+
146
+ /**
147
+ * A PineScript `var` variable of a drawing type (`var line l = na`). The object it refers to is not deleted by the
148
+ * maximum count; assigning another object (or `undefined` for `na`) releases the one before.
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * // Pine: var line level = na
153
+ * // level := line.new(bar_index, close, bar_index + 1, close)
154
+ * const level = varDrawing<Line>();
155
+ * level.value = line.new(i, close, i + 1, close);
156
+ * ```
157
+ */
158
+ export function varDrawing<T extends object>(initial?: T): DrawingVariable<T> {
159
+ let current: T | undefined = initial ? holdDrawing(initial) : undefined;
160
+ return {
161
+ get value() {
162
+ return current;
163
+ },
164
+ set value(next: T | undefined) {
165
+ if (next === current) return;
166
+ releaseDrawing(current);
167
+ current = next ? holdDrawing(next) : undefined;
168
+ },
169
+ };
170
+ }
171
+
172
+ /** Largest distance, in bars, of a bar index before the current bar (PineScript runtime error RE10026 above it). */
173
+ export const MAX_DRAWING_BARS_BACK = 10000;
174
+ let currentBar: number | null = null;
175
+
176
+ /** Kinds whose allowance on the last bar is the `P` they had when the bar started. */
177
+ type AllowanceKind = 'line' | 'label' | 'box';
178
+ const isAllowanceKind = (kind: DrawingKind): kind is AllowanceKind => kind === 'line' || kind === 'label' || kind === 'box';
179
+
180
+ /** State of the last bar (see the module notes). */
181
+ interface LastBar {
182
+ bar: number;
183
+ /** The objects alive when the bar started (the objects of the bars before) */
184
+ old: Set<object>;
185
+ /** `P` of lines, labels and boxes when the bar started: their allowance for the whole bar */
186
+ allowance: Record<AllowanceKind, number>;
187
+ /** `P` of polylines when the bar started */
188
+ carried: number;
189
+ /** A polyline was created on this bar */
190
+ polyline: boolean;
191
+ /** Polylines of the bars before deleted on this bar (by the count or by the script) */
192
+ oldPolylinesGone: number;
193
+ }
194
+ let lastBar: LastBar | null = null;
195
+
196
+ /**
197
+ * Tells the drawing functions which bar is being calculated (`bar_index`), so that a bar index more than 10,000
198
+ * bars back throws as in PineScript; `null` turns the check off. `eachBar` of the script API calls it.
199
+ *
200
+ * The bar also dates the objects held by a `var` variable (protected from the next bar on). `last` says that it is
201
+ * the last bar of the data, which has rules of its own for the maximum count.
202
+ */
203
+ export function setDrawingBar(barIndex: number | null, last: boolean = false): void {
204
+ if (lastBar && !(last && barIndex === lastBar.bar)) lastBar = null;
205
+ currentBar = barIndex;
206
+ if (barIndex !== null && last && !lastBar) {
207
+ const start = (kind: AllowanceKind): number => (pending[kind] >= PENDING_RESET[kind] ? 0 : pending[kind]);
208
+ lastBar = {
209
+ bar: barIndex,
210
+ old: new Set<object>([...live.line, ...live.label, ...live.box, ...live.polyline]),
211
+ allowance: { line: start('line'), label: start('label'), box: start('box') },
212
+ carried: pending.polyline,
213
+ polyline: false,
214
+ oldPolylinesGone: 0,
215
+ };
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Throws the PineScript runtime error RE10026 when `x` is a bar index more than 10,000 bars before the current bar.
221
+ * `method` and `variable` are the names of the PineScript message ("line.new", "x1").
222
+ */
223
+ export function checkBarIndex(method: string, variable: string, x: number, xloc: string | undefined): void {
224
+ if (currentBar === null || (xloc ?? 'bar_index') !== 'bar_index') return;
225
+ if (currentBar - x > MAX_DRAWING_BARS_BACK) {
226
+ throw new Error(
227
+ `Error on bar ${currentBar}: Bar index value of the \`${variable}\` argument (${x}) in \`${method}()\` is too far ` +
228
+ 'from the current bar index. Try using `time` instead.'
229
+ );
230
+ }
231
+ }
53
232
 
54
233
  /** Numeric fields that become NaN when an object is deleted, so that its getters return `na`. */
55
234
  const COORDINATES: { [K in DrawingKind]: Array<keyof DrawingTypes[K]> } = {
@@ -69,14 +248,75 @@ function markDeleted<K extends DrawingKind>(kind: K, obj: DrawingTypes[K], expli
69
248
  }
70
249
  }
71
250
 
251
+ /** Objects above the maximum that the rule allows for, besides the 5: `P` of the module notes. */
252
+ function allowance(kind: DrawingKind): number {
253
+ if (kind === 'polyline') return pending.polyline;
254
+ return lastBar && isAllowanceKind(kind) ? lastBar.allowance[kind] : 0;
255
+ }
256
+
257
+ /**
258
+ * The maximum count after the creation of `obj`: removes the objects to delete from `list` (the oldest first; the new
259
+ * object and an object held by a `var` variable are skipped and still count) and returns them.
260
+ */
261
+ function cutOldest<K extends DrawingKind>(
262
+ kind: K,
263
+ list: Array<DrawingTypes[K]>,
264
+ obj: DrawingTypes[K],
265
+ only?: (old: object) => boolean
266
+ ): Array<DrawingTypes[K]> {
267
+ if (kind === 'table') return []; // tables have no maximum count
268
+ const max = limits[kind === 'linefill' ? 'line' : (kind as keyof DrawingLimits)] + allowance(kind);
269
+ if (list.length <= max + SLACK) return [];
270
+ const kept: Array<DrawingTypes[K]> = [];
271
+ const cut: Array<DrawingTypes[K]> = [];
272
+ let excess = list.length - max;
273
+ for (const old of list) {
274
+ if (excess > 0 && old !== obj && (!only || only(old)) && !(kind === 'linefill' ? fillProtected(old) : heldBefore(old))) {
275
+ excess--;
276
+ cut.push(old);
277
+ } else kept.push(old);
278
+ }
279
+ // linefills: still above the maximum + 5, the oldest go, the held ones too
280
+ if (kind === 'linefill' && kept.length > max + SLACK) cut.push(...kept.splice(0, kept.length - max));
281
+ list.length = 0;
282
+ list.push(...kept);
283
+ return cut;
284
+ }
285
+
72
286
  /** Records a new object and deletes the oldest objects of its kind when the count is too high. */
73
287
  export function register<K extends DrawingKind>(kind: K, obj: DrawingTypes[K]): DrawingTypes[K] {
288
+ const list = live[kind];
289
+ if (lastBar && isCounted(kind)) return registerLast(lastBar, kind, obj);
290
+ if (isCounted(kind) && pending[kind] >= PENDING_RESET[kind]) pending[kind] = 0;
291
+ list.push(obj);
292
+ const cut = cutOldest(kind, list, obj);
293
+ if (kind === 'polyline') pending.polyline += cut.length;
294
+ for (const old of cut) markDeleted(kind, old, false);
295
+ return obj;
296
+ }
297
+
298
+ /** An object created on the last bar (see the module notes). */
299
+ function registerLast<K extends DrawingKind>(bar: LastBar, kind: K, obj: DrawingTypes[K]): DrawingTypes[K] {
300
+ if (kind === 'polyline' && !bar.polyline) {
301
+ bar.polyline = true;
302
+ if (pending.polyline >= PENDING_RESET.polyline) pending.polyline = 0;
303
+ else pending.polyline += bar.carried; // counted twice
304
+ }
74
305
  const list = live[kind];
75
306
  list.push(obj);
76
- if (kind === 'table') return obj; // tables have no maximum count
77
- const max = limits[kind === 'linefill' ? 'line' : (kind as keyof DrawingLimits)];
78
- if (list.length > max + SLACK) {
79
- for (const old of list.splice(0, list.length - max)) markDeleted(kind, old, false);
307
+ const isOld = (old: object): boolean => bar.old.has(old);
308
+ if (list.some((old) => old !== obj && isOld(old) && !heldBefore(old))) {
309
+ // the objects of the bars before only; a line takes its linefills with it
310
+ const cut = cutOldest(kind, list, obj, isOld);
311
+ if (kind === 'polyline') {
312
+ pending.polyline += cut.length;
313
+ bar.oldPolylinesGone += cut.length;
314
+ }
315
+ for (const old of cut) markDeleted(kind, old, true);
316
+ } else {
317
+ const cut = cutOldest(kind, list, obj);
318
+ if (kind === 'polyline' && cut.length > 0) pending.polyline = bar.oldPolylinesGone;
319
+ for (const old of cut) markDeleted(kind, old, false);
80
320
  }
81
321
  return obj;
82
322
  }
@@ -85,7 +325,11 @@ export function register<K extends DrawingKind>(kind: K, obj: DrawingTypes[K]):
85
325
  export function remove<K extends DrawingKind>(kind: K, obj: DrawingTypes[K]): void {
86
326
  const list = live[kind];
87
327
  const index = list.indexOf(obj);
88
- if (index !== -1) list.splice(index, 1);
328
+ if (index !== -1) {
329
+ list.splice(index, 1);
330
+ if (isCounted(kind)) pending[kind]++;
331
+ if (kind === 'polyline' && lastBar?.old.has(obj)) lastBar.oldPolylinesGone++;
332
+ }
89
333
  if (!deleted.has(obj)) markDeleted(kind, obj, true);
90
334
  }
91
335
 
@@ -102,6 +346,12 @@ export function all<K extends DrawingKind>(kind: K): Array<DrawingTypes[K]> {
102
346
  /** Forgets all objects (without deleting them) and sets the maximum counts (default 50). */
103
347
  export function resetDrawings(newLimits: DrawingLimits = {}): void {
104
348
  for (const kind of Object.keys(live) as DrawingKind[]) live[kind].length = 0;
349
+ held = new WeakMap();
350
+ releasedAt = new WeakMap();
351
+ heldAt = new WeakMap();
352
+ pending = { line: 0, label: 0, box: 0, polyline: 0 };
353
+ currentBar = null;
354
+ lastBar = null;
105
355
  limits = {
106
356
  line: newLimits.line ?? DEFAULT_MAX_COUNT,
107
357
  label: newLimits.label ?? DEFAULT_MAX_COUNT,
package/src/index.ts CHANGED
@@ -43,6 +43,39 @@ export { taCore, math, array, map, str, color, time, timeframe, matrix, line, bo
43
43
  export { drawingOutputs, type DrawingOutputs } from './drawing/output.js';
44
44
  export { barInterval, barTime } from './drawing/bar-time.js';
45
45
 
46
+ // Drawing registry limits (PineScript indicator(max_lines_count / max_labels_count / max_boxes_count / max_polylines_count))
47
+ export {
48
+ resetDrawings,
49
+ setDrawingLimits,
50
+ setDrawingBar,
51
+ holdDrawing,
52
+ releaseDrawing,
53
+ varDrawing,
54
+ type DrawingLimits,
55
+ type DrawingVariable,
56
+ } from './drawing/registry.js';
57
+
58
+ // The session of a chart context without a script run: time(tf), time_close(tf), time_tradingday, session.* flags,
59
+ // request.security periods with the session and holidays, and bars after the last bar on the trading calendar
60
+ export {
61
+ chartSessions,
62
+ periodTime,
63
+ periodClose,
64
+ tradingDayTime,
65
+ chartSessionFlags,
66
+ resampleSession,
67
+ barTimeAfter,
68
+ barsUntil,
69
+ type SessionContext,
70
+ type ChartSessions,
71
+ type SessionResample,
72
+ } from './session/context.js';
73
+ export { TradingCalendar, type SessionSpec } from './session/calendar.js';
74
+ export { SessionBars, sessionFlags, type SessionFlags } from './session/bars.js';
75
+
76
+ // Same-symbol request.security on a higher timeframe (completion and lookahead rules) and ticker.heikinashi
77
+ export { resample, periodsOf, afterCompletion, mapToChart, heikinAshi, type Resampled } from './security/resample.js';
78
+
46
79
  // Export Series class (self-contained, no context)
47
80
  export { Series, BarData } from './runtime/series.js';
48
81
  export type { SeriesExtractor } from './runtime/series.js';
@@ -139,6 +172,7 @@ export {
139
172
  input_session,
140
173
  input_time,
141
174
  } from './runtime/inputs.js';
175
+ export { isInputActive } from './runtime/input-active.js';
142
176
 
143
177
  export type {
144
178
  OakScriptContext,
@@ -147,6 +181,7 @@ export type {
147
181
  SeriesHandle,
148
182
  SeriesOptions,
149
183
  InputConfig,
184
+ InputCondition,
150
185
  InputOptions,
151
186
  OhlcvData,
152
187
  PlotConfig,
@@ -170,7 +205,7 @@ export type { LightweightSeriesDefinitions } from './runtime/adapters/Lightweigh
170
205
  export { SimpleInputAdapter } from './runtime/adapters/SimpleInputAdapter.js';
171
206
 
172
207
  // Version
173
- export const VERSION = '0.10.0';
208
+ export const VERSION = '0.11.0';
174
209
 
175
210
  /**
176
211
  * Library information
@@ -10,7 +10,7 @@
10
10
  * @version 6
11
11
  */
12
12
 
13
- import { all as allDrawings, isDeleted, register, remove } from '../drawing/registry.js';
13
+ import { all as allDrawings, checkBarIndex, isDeleted, register, remove } from '../drawing/registry.js';
14
14
  import type { ChartPoint, Label, color } from '../types/index.js';
15
15
 
16
16
  type LabelXloc = 'bar_index' | 'bar_time';
@@ -80,6 +80,7 @@ export function new_label(...args: unknown[]): Label {
80
80
  string?, LabelXloc?, LabelYloc?, color?, LabelStyle?, color?, (string | number)?,
81
81
  ('left' | 'center' | 'right')?, string?, ('default' | 'monospace')?, boolean?, number?,
82
82
  ];
83
+ checkBarIndex('label.new', 'x', x, xloc);
83
84
  return register('label', {
84
85
  x,
85
86
  y,
@@ -188,6 +189,7 @@ export function copy(id: Label): Label {
188
189
  */
189
190
  export function set_x(id: Label, x: number): Label {
190
191
  if (isDeleted(id)) return id;
192
+ checkBarIndex('label.set_x', 'x', x, id.xloc);
191
193
  id.x = x;
192
194
  return id;
193
195
  }
@@ -227,6 +229,7 @@ export function set_y(id: Label, y: number): Label {
227
229
  */
228
230
  export function set_xy(id: Label, x: number, y: number): Label {
229
231
  if (isDeleted(id)) return id;
232
+ checkBarIndex('label.set_x', 'x', x, id.xloc);
230
233
  id.x = x;
231
234
  id.y = y;
232
235
  return id;
package/src/line/index.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * @version 6
11
11
  */
12
12
 
13
- import { all as allDrawings, isDeleted, register, remove } from '../drawing/registry.js';
13
+ import { all as allDrawings, checkBarIndex, isDeleted, register, remove } from '../drawing/registry.js';
14
14
  import type { ChartPoint, Line, color } from '../types/index.js';
15
15
 
16
16
  type LineXloc = 'bar_index' | 'bar_time';
@@ -70,6 +70,8 @@ export function new_line(...args: unknown[]): Line {
70
70
  const [xloc, extend, lineColor, style, width, force_overlay] = rest as [
71
71
  LineXloc?, LineExtend?, color?, LineStyle?, number?, boolean?,
72
72
  ];
73
+ checkBarIndex('line.new', 'x1', x1, xloc);
74
+ checkBarIndex('line.new', 'x2', x2, xloc);
73
75
  return register('line', {
74
76
  x1,
75
77
  y1,
@@ -79,7 +81,7 @@ export function new_line(...args: unknown[]): Line {
79
81
  extend: extend ?? 'none',
80
82
  color: lineColor,
81
83
  style: style || 'solid',
82
- width: width || 1,
84
+ width: width ?? 1,
83
85
  force_overlay: force_overlay ?? false,
84
86
  });
85
87
  }
@@ -265,6 +267,7 @@ export function copy(id: Line): Line {
265
267
  */
266
268
  export function set_x1(id: Line, x: number): Line {
267
269
  if (isDeleted(id)) return id;
270
+ checkBarIndex('line.set_x***', 'x1', x, id.xloc);
268
271
  id.x1 = x;
269
272
  return id;
270
273
  }
@@ -284,6 +287,7 @@ export function set_x1(id: Line, x: number): Line {
284
287
  */
285
288
  export function set_x2(id: Line, x: number): Line {
286
289
  if (isDeleted(id)) return id;
290
+ checkBarIndex('line.set_x***', 'x2', x, id.xloc);
287
291
  id.x2 = x;
288
292
  return id;
289
293
  }
@@ -342,6 +346,7 @@ export function set_y2(id: Line, y: number): Line {
342
346
  */
343
347
  export function set_xy1(id: Line, x: number, y: number): Line {
344
348
  if (isDeleted(id)) return id;
349
+ checkBarIndex('line.set_x***', 'x1', x, id.xloc);
345
350
  id.x1 = x;
346
351
  id.y1 = y;
347
352
  return id;
@@ -363,6 +368,7 @@ export function set_xy1(id: Line, x: number, y: number): Line {
363
368
  */
364
369
  export function set_xy2(id: Line, x: number, y: number): Line {
365
370
  if (isDeleted(id)) return id;
371
+ checkBarIndex('line.set_x***', 'x2', x, id.xloc);
366
372
  id.x2 = x;
367
373
  id.y2 = y;
368
374
  return id;
package/src/math/index.ts CHANGED
@@ -19,6 +19,7 @@
19
19
  import type { float, int, simple_int, series_float } from '../types/index.js';
20
20
  import { Series } from '../runtime/series.js';
21
21
  import { runningSum, runningSumSeries } from '../ta/running-sum.js';
22
+ import { checkSumLength } from '../ta/limits.js';
22
23
 
23
24
  /**
24
25
  * Returns the absolute value of a number.
@@ -489,6 +490,7 @@ export function sum(
489
490
  ): series_float | Series {
490
491
  // Handle Series objects - extract to array first
491
492
  const sourceArray = source instanceof Series ? source.toArray() : source;
493
+ if (typeof length === 'number') checkSumLength('math.sum', length, 0);
492
494
  // PineScript: the sum of the last `length` non-na values (also +/-Infinity skipped); an na bar keeps the previous
493
495
  // result. A compensated running sum, bit for bit as PineScript.
494
496
  const result: series_float =
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import type { ChartPoint, Polyline, color } from '../types/index.js';
14
- import { all as allDrawings, register, remove } from '../drawing/registry.js';
14
+ import { all as allDrawings, checkBarIndex, register, remove } from '../drawing/registry.js';
15
15
 
16
16
  // Re-export the Polyline type for convenience
17
17
  export type { Polyline };
@@ -86,6 +86,8 @@ export function new_polyline(
86
86
  line_width = 1;
87
87
  }
88
88
 
89
+ if (xloc === 'bar_index') for (const point of points) if (point.index !== null) checkBarIndex('polyline.new', 'points', point.index, xloc);
90
+
89
91
  const polyline: Polyline = {
90
92
  id: generateId(),
91
93
  points: [...points], // Create a copy of the points array
@@ -41,11 +41,14 @@ export type {
41
41
  SeriesHandle,
42
42
  SeriesOptions,
43
43
  InputConfig,
44
+ InputCondition,
44
45
  InputOptions,
45
46
  OhlcvData,
46
47
  PlotConfig,
47
48
  } from './types.js';
48
49
 
50
+ export { isInputActive } from './input-active.js';
51
+
49
52
  // Adapter exports
50
53
  export { LightweightChartsAdapter } from './adapters/LightweightChartsAdapter.js';
51
54
  export type { LightweightSeriesDefinitions } from './adapters/LightweightChartsAdapter.js';
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @fileoverview PineScript `active` of an input: whether the settings dialog lets the user edit it
3
+ * @module runtime/input-active
4
+ */
5
+
6
+ import type { InputConfig, InputCondition } from './types.js';
7
+
8
+ /** Current value of an input: the host value, else the default of its config. */
9
+ function inputValue(id: string, inputs: Record<string, unknown>, configs?: readonly InputConfig[]): unknown {
10
+ const value = inputs[id];
11
+ if (value !== undefined) return value;
12
+ return configs?.find((c) => c.id === id)?.defval;
13
+ }
14
+
15
+ function holds(condition: InputCondition, inputs: Record<string, unknown>, configs?: readonly InputConfig[]): boolean {
16
+ if (typeof condition === 'string') return inputValue(condition, inputs, configs) === true;
17
+ if ('not' in condition) return !holds(condition.not, inputs, configs);
18
+ if ('any' in condition) return condition.any.some((c) => holds(c, inputs, configs));
19
+ if ('all' in condition) return condition.all.every((c) => holds(c, inputs, configs));
20
+ const value = inputValue(condition.input, inputs, configs);
21
+ if (!('eq' in condition) && !('ne' in condition)) return value === true;
22
+ return (!('eq' in condition) || value === condition.eq) && (!('ne' in condition) || value !== condition.ne);
23
+ }
24
+
25
+ /**
26
+ * Whether an input is active (PineScript `active`): an inactive input is shown greyed out in the settings and
27
+ * cannot be edited. It still gives its value to the script.
28
+ *
29
+ * @param config - The input
30
+ * @param inputs - Current input values by id
31
+ * @param configs - All the inputs of the indicator: gives the default of an input that has no value in `inputs`
32
+ * @returns true when `config.active` is not set, is true, or is a condition that holds
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const configs: InputConfig[] = [
37
+ * { id: 'showMa1', type: 'bool', defval: true, title: 'MA #1' },
38
+ * { id: 'ma1Length', type: 'int', defval: 20, active: 'showMa1' },
39
+ * { id: 'bbMult', type: 'float', defval: 2, active: { input: 'maType', eq: 'SMA + Bollinger Bands' } },
40
+ * ];
41
+ * isInputActive(configs[1], { showMa1: false }) // false
42
+ * isInputActive(configs[1], {}, configs) // true: the default of showMa1
43
+ * ```
44
+ */
45
+ export function isInputActive(config: InputConfig, inputs: Record<string, unknown> = {}, configs?: readonly InputConfig[]): boolean {
46
+ const { active } = config;
47
+ if (active === undefined) return true;
48
+ if (typeof active === 'boolean') return active;
49
+ return holds(active, inputs, configs);
50
+ }
@@ -46,6 +46,22 @@ export interface ChartAdapter {
46
46
  createPane?(): number;
47
47
  }
48
48
 
49
+ /**
50
+ * Condition on other inputs, as plain data (PineScript `active = <expression>`):
51
+ * - `'showMa'`: the bool input `showMa` is true
52
+ * - `{ input: 'maType', eq: 'SMA' }` / `{ input: 'maType', ne: 'None' }`: the value of an input is / is not a value
53
+ * (strict equality; without `eq` and `ne`: the input is true)
54
+ * - `{ not: c }`, `{ any: [c1, c2] }` (or), `{ all: [c1, c2] }` (and)
55
+ *
56
+ * Evaluated by `isInputActive()`.
57
+ */
58
+ export type InputCondition =
59
+ | string
60
+ | { input: string; eq?: unknown; ne?: unknown }
61
+ | { not: InputCondition }
62
+ | { any: InputCondition[] }
63
+ | { all: InputCondition[] };
64
+
49
65
  /**
50
66
  * Configuration for registering an input
51
67
  */
@@ -78,10 +94,17 @@ export interface InputConfig {
78
94
  /** Ask for the value when the script is added to a chart (PineScript `confirm`) */
79
95
  confirm?: boolean;
80
96
  /**
81
- * Where the input value is shown (PineScript `display`); not set means the PineScript default: 'all', except
82
- * color inputs: 'none'
97
+ * Where the input value is shown (PineScript `display`); not set means the PineScript default: 'none' for
98
+ * bool, color and time inputs, 'all' for the others
83
99
  */
84
100
  display?: 'all' | 'none' | 'data_window' | 'status_line';
101
+ /**
102
+ * Whether the input can be edited (PineScript `active`): an inactive input is shown greyed out in the settings.
103
+ * UI only: an inactive input still gives its value to the script. Not set means always active. A boolean is the
104
+ * value for the current inputs; an `InputCondition` names the inputs it depends on, so a host can apply it
105
+ * without running the script (see `isInputActive()`)
106
+ */
107
+ active?: boolean | InputCondition;
85
108
  }
86
109
 
87
110
  /**