lightweight-charts-indicators 0.13.3 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -144,6 +144,20 @@ before, and the indicator pane keeps its height across a redraw.
144
144
 
145
145
  The demo page (`example/`) uses the same renderer.
146
146
 
147
+ ## Source Code of the Built-in Indicators
148
+
149
+ `lightweight-charts-indicators/sources` gives the source code of every standard (built-in) indicator by registry id:
150
+ a plain JavaScript module in the library's indicator shape (`metadata`, `inputConfig`, `plotConfig`, `defaultInputs`,
151
+ `calculate`), with the registry's names in `metadata`. It imports only `oakscriptjs` and
152
+ `lightweight-charts-indicators/helpers` (chart context accessors, higher-timeframe periods, other-symbol requests,
153
+ lower-timeframe volume, bar times), so a host can show it, or run a copy of it as a user script by providing those two
154
+ modules. The table is about 500 kB: load it on demand.
155
+
156
+ ```typescript
157
+ const { builtinSource } = await import('lightweight-charts-indicators/sources');
158
+ const rsiSource = builtinSource('rsi'); // string | undefined
159
+ ```
160
+
147
161
  ## Lightweight-Charts Integration Example
148
162
 
149
163
  Here's a complete example showing how to integrate indicators with lightweight-charts without the renderer:
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Helpers the built-in indicator sources import (`lightweight-charts-indicators/helpers`): the chart context
3
+ * accessors, higher-timeframe periods, other-symbol requests, lower-timeframe volume and bar times. A source given by
4
+ * `lightweight-charts-indicators/sources` imports from here instead of the library's own relative paths, so a host
5
+ * that runs the source as a script provides this one module next to oakscriptjs.
6
+ */
7
+ export * from './chart-context';
8
+ export * from './anchor-period';
9
+ export * from './other-symbol';
10
+ export * from './lower-tf-volume';
11
+ export * from './bar-time';
12
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC"}
@@ -0,0 +1,623 @@
1
+ // src/chart-context.ts
2
+ import {
3
+ MissingSymbolDataError
4
+ } from "oakscriptjs/script";
5
+ function symbolBarsOf(requests, chart) {
6
+ const out = requests.map((r) => chart?.bars?.(r.symbol, r.timeframe));
7
+ const missing = requests.filter((_, i) => out[i] === void 0);
8
+ if (missing.length)
9
+ throw new MissingSymbolDataError(missing);
10
+ return out;
11
+ }
12
+ function mintickOf(chart) {
13
+ if (chart?.mintick !== void 0)
14
+ return chart.mintick;
15
+ if (chart?.minmove !== void 0 && chart.pricescale !== void 0)
16
+ return chart.minmove / chart.pricescale;
17
+ return 0.01;
18
+ }
19
+ function sessionHoursOf(chart) {
20
+ return (typeof chart?.session === "string" ? chart.session : chart?.session?.session) ?? "";
21
+ }
22
+ function timezoneOf(chart) {
23
+ return chart?.timezone ?? "Etc/UTC";
24
+ }
25
+ function tickeridOf(chart) {
26
+ return chart?.tickerid ?? "";
27
+ }
28
+ function tickerOf(chart) {
29
+ const id = tickeridOf(chart);
30
+ return id.includes(":") ? id.slice(id.indexOf(":") + 1) : id;
31
+ }
32
+ function symbolTypeOf(chart) {
33
+ return chart?.symbolType ?? "";
34
+ }
35
+ function currencyOf(chart) {
36
+ return chart?.currency ?? "USD";
37
+ }
38
+ function basecurrencyOf(chart) {
39
+ if (chart?.basecurrency !== void 0)
40
+ return chart.basecurrency;
41
+ const type = symbolTypeOf(chart);
42
+ if (type !== "crypto" && type !== "forex")
43
+ return "";
44
+ const pair = tickerOf(chart).replace(/\.P$/, "");
45
+ const quote = currencyOf(chart);
46
+ return pair.length > quote.length && pair.endsWith(quote) ? pair.slice(0, -quote.length) : "";
47
+ }
48
+ function rootOf(chart) {
49
+ if (chart?.root !== void 0)
50
+ return chart.root;
51
+ const ticker = tickerOf(chart);
52
+ if (symbolTypeOf(chart) !== "futures")
53
+ return ticker;
54
+ return ticker.replace(/\d+!$/, "").replace(/[FGHJKMNQUVXZ]\d{4}$/, "");
55
+ }
56
+ function foregroundColorOf(chart) {
57
+ return chart?.foregroundColor ?? "#DBDBDB";
58
+ }
59
+ function backgroundColorOf(chart) {
60
+ return chart?.backgroundColor ?? "#0F0F0F";
61
+ }
62
+ function lastBarConfirmedOf(chart) {
63
+ return chart?.lastBarConfirmed ?? true;
64
+ }
65
+ function nowOf(chart) {
66
+ return chart?.now ?? Date.now();
67
+ }
68
+ function visibleRangeOf(bars, chart) {
69
+ const toMs = chart?.timeUnit === "ms" ? 1 : 1e3;
70
+ const last = bars.length - 1;
71
+ if (last < 0)
72
+ return { left: 0, right: -1, leftTime: NaN, rightTime: NaN };
73
+ const leftTime = chart?.leftVisibleBarTime ?? bars[0].time * toMs;
74
+ const rightTime = chart?.rightVisibleBarTime ?? bars[last].time * toMs;
75
+ let left = 0;
76
+ while (left < last && bars[left].time * toMs < leftTime)
77
+ left++;
78
+ let right = last;
79
+ while (right > 0 && bars[right].time * toMs > rightTime)
80
+ right--;
81
+ return { left, right, leftTime, rightTime };
82
+ }
83
+
84
+ // src/anchor-period.ts
85
+ import {
86
+ barInterval,
87
+ timeframe,
88
+ periodTime,
89
+ tradingDayTime,
90
+ chartSessionFlags,
91
+ resampleSession,
92
+ chartSessions,
93
+ barTimeAfter,
94
+ barsUntil,
95
+ afterCompletion,
96
+ periodsOf,
97
+ resample
98
+ } from "oakscriptjs";
99
+ var DAY = 864e5;
100
+ var WEEK = 7 * DAY;
101
+ function hasCalendar(chart) {
102
+ return !!chart && !!chart.timezone && !!chart.session;
103
+ }
104
+ function calendarOf(bars, chart) {
105
+ if (!chart || !hasCalendar(chart) || bars.length === 0)
106
+ return null;
107
+ const tf = chartTimeframe(bars, chart);
108
+ if (tf === "")
109
+ return null;
110
+ const ms = bars[0].time >= 1e12;
111
+ const key = `${tf}|${ms ? "ms" : "s"}`;
112
+ let contexts = sessionContexts.get(chart);
113
+ if (!contexts) {
114
+ contexts = /* @__PURE__ */ new Map();
115
+ sessionContexts.set(chart, contexts);
116
+ }
117
+ let context = contexts.get(key);
118
+ if (context === void 0) {
119
+ context = {
120
+ timeframe: tf,
121
+ timezone: chart.timezone,
122
+ session: chart.session,
123
+ regularSession: chart.regularSession,
124
+ timeUnit: ms ? "ms" : "s"
125
+ };
126
+ try {
127
+ chartSessions(context);
128
+ periodTime(bars[0].time, "1D", context);
129
+ } catch {
130
+ context = null;
131
+ }
132
+ contexts.set(key, context);
133
+ }
134
+ return context ? { chart: context, unit: ms ? 1 : 1e3 } : null;
135
+ }
136
+ var sessionContexts = /* @__PURE__ */ new WeakMap();
137
+ function calendarPeriodStart(day, info) {
138
+ const n = info.multiplier;
139
+ const d = new Date(day);
140
+ const year = d.getUTCFullYear();
141
+ if (info.isdaily) {
142
+ if (n === 1)
143
+ return day;
144
+ const jan1 = Date.UTC(year, 0, 1);
145
+ return jan1 + Math.floor((day - jan1) / DAY / n) * n * DAY;
146
+ }
147
+ if (info.isweekly) {
148
+ const monday = day - (d.getUTCDay() + 6) % 7 * DAY;
149
+ if (n === 1)
150
+ return monday;
151
+ const jan1 = Date.UTC(new Date(monday).getUTCFullYear(), 0, 1);
152
+ const firstMonday = jan1 + (8 - new Date(jan1).getUTCDay()) % 7 * DAY;
153
+ return firstMonday + Math.floor((monday - firstMonday) / WEEK / n) * n * WEEK;
154
+ }
155
+ return Date.UTC(year, Math.floor(d.getUTCMonth() / n) * n, 1);
156
+ }
157
+ function periodStarts(bars, tf, chart) {
158
+ if (tf.trim() === "")
159
+ return bars.map((b) => b.time);
160
+ const info = timeframe.info(tf);
161
+ if (info.isticks)
162
+ throw new RangeError(`Unsupported timeframe "${tf}"`);
163
+ const calendar = calendarOf(bars, chart);
164
+ if (calendar) {
165
+ const sessions = chartSessions(calendar.chart).bars;
166
+ const out2 = [];
167
+ bars.forEach((b, i) => {
168
+ const t = periodTime(b.time, tf, calendar.chart);
169
+ if (!Number.isNaN(t))
170
+ out2.push(t / calendar.unit);
171
+ else
172
+ out2.push(i > 0 && sessions.stray(b.time * calendar.unit) ? out2[i - 1] : b.time);
173
+ });
174
+ return out2;
175
+ }
176
+ const ms = bars.length > 0 && bars[0].time >= 1e12;
177
+ const times = bars.map((b) => ms ? b.time : b.time * 1e3);
178
+ const weekend = (t) => {
179
+ const wd = new Date(t).getUTCDay();
180
+ return wd === 0 || wd === 6;
181
+ };
182
+ const everyDay = times.some(weekend);
183
+ const len = info.isintraday ? timeframe.in_seconds(tf) * 1e3 : 0;
184
+ const out = [];
185
+ let prevDay = NaN;
186
+ let sessionStart = NaN;
187
+ let prevKey = NaN;
188
+ let start = NaN;
189
+ let tradingYear = NaN;
190
+ let tradingDays2 = -1;
191
+ let prevTradingDay = NaN;
192
+ for (const t of times) {
193
+ const day = Math.floor(t / DAY) * DAY;
194
+ if (day !== prevDay)
195
+ sessionStart = everyDay ? day : t;
196
+ prevDay = day;
197
+ if (info.isintraday) {
198
+ start = len >= DAY ? sessionStart : sessionStart + Math.floor((t - sessionStart) / len) * len;
199
+ } else if (info.isdaily && info.multiplier > 1 && !everyDay) {
200
+ const year = new Date(day).getUTCFullYear();
201
+ if (year !== tradingYear) {
202
+ tradingYear = year;
203
+ tradingDays2 = -1;
204
+ }
205
+ if (day !== prevTradingDay) {
206
+ tradingDays2++;
207
+ prevTradingDay = day;
208
+ if (tradingDays2 % info.multiplier === 0)
209
+ start = t;
210
+ }
211
+ } else {
212
+ const key = calendarPeriodStart(day, info);
213
+ if (key !== prevKey)
214
+ start = everyDay ? key : t;
215
+ prevKey = key;
216
+ }
217
+ out.push(ms ? start : Math.floor(start / 1e3));
218
+ }
219
+ return out;
220
+ }
221
+ function periodChanges(bars, tf, chart) {
222
+ const calendar = tf.trim() === "" ? null : calendarOf(bars, chart);
223
+ if (calendar) {
224
+ if (timeframe.info(tf).isticks)
225
+ throw new RangeError(`Unsupported timeframe "${tf}"`);
226
+ const times = bars.map((b) => periodTime(b.time, tf, calendar.chart));
227
+ return times.map((t, i) => i > 0 && !Number.isNaN(t) && !Number.isNaN(times[i - 1]) && t !== times[i - 1]);
228
+ }
229
+ const starts = periodStarts(bars, tf, chart);
230
+ return starts.map((t, i) => i > 0 && t !== starts[i - 1]);
231
+ }
232
+ function chartTimeframe(bars, chart) {
233
+ if (chart?.timeframe)
234
+ return timeframe.info(chart.timeframe).period;
235
+ const gap = barInterval(bars);
236
+ const s = Math.round(bars.length > 0 && bars[0].time >= 1e12 ? gap / 1e3 : gap);
237
+ if (!(s > 0))
238
+ return "";
239
+ if (s < 86400)
240
+ return timeframe.from_seconds(s);
241
+ if (s < 7 * 86400)
242
+ return `${Math.round(s / 86400)}D`;
243
+ if (s < 28 * 86400)
244
+ return `${Math.round(s / (7 * 86400))}W`;
245
+ return `${Math.max(1, Math.round(s / timeframe.in_seconds("1M")))}M`;
246
+ }
247
+ function securityPeriods(bars, tf, chart) {
248
+ return resampled(bars.map((b) => ({ time: b.time, open: 1, high: 1, low: 1, close: 1, volume: 1 })), tf, chart)?.periods ?? null;
249
+ }
250
+ function resampled(bars, tf, chart) {
251
+ const calendar = calendarOf(bars, chart);
252
+ if (!calendar)
253
+ return null;
254
+ const n = bars.length;
255
+ const { bars: htf, group, latest } = resampleSession(bars, tf, calendar.chart);
256
+ const starts = group.map((g, i) => Number.isNaN(htf[g].time) ? bars[i].time : htf[g].time);
257
+ return {
258
+ periods: {
259
+ starts,
260
+ group,
261
+ latest,
262
+ after: afterCompletion(group, latest),
263
+ lastCompletes: n > 0 && latest[n - 1] === group[n - 1]
264
+ },
265
+ htf
266
+ };
267
+ }
268
+ function securityStarts(bars, tf, chart) {
269
+ if (tf.trim() !== "" && hasCalendar(chart)) {
270
+ const chartTf = chartTimeframe(bars, chart);
271
+ if (chartTf !== "" && timeframe.in_seconds(tf) >= timeframe.in_seconds(chartTf)) {
272
+ const periods = securityPeriods(bars, tf, chart);
273
+ if (periods)
274
+ return periods.starts;
275
+ }
276
+ }
277
+ return periodStarts(bars, tf, chart);
278
+ }
279
+ function securityBars(bars, tf, chart) {
280
+ const res = resampled(bars, tf, chart);
281
+ if (!res)
282
+ return null;
283
+ const { periods } = res;
284
+ const first = [];
285
+ periods.group.forEach((g, i) => {
286
+ if (g >= first.length)
287
+ first.push(i);
288
+ });
289
+ const htf = res.htf.map((b, g) => ({ ...b, time: periods.starts[first[g]], volume: b.volume ?? NaN }));
290
+ return { ...periods, htf, first };
291
+ }
292
+ function higherTimeframeBars(bars, tf, chart) {
293
+ const exchange = securityBars(bars, tf, chart);
294
+ if (exchange)
295
+ return exchange;
296
+ const n = bars.length;
297
+ const starts = periodStarts(bars, tf);
298
+ const ms = n > 0 && bars[0].time >= 1e12;
299
+ const weekend = (t) => {
300
+ const wd = new Date(ms ? t : t * 1e3).getUTCDay();
301
+ return wd === 0 || wd === 6;
302
+ };
303
+ const interval = barInterval(bars);
304
+ const closes = bars.map((b) => ({ time: b.time + interval }));
305
+ let reaches;
306
+ if (bars.some((b) => weekend(b.time)) && closes.some((b) => weekend(b.time))) {
307
+ const closeStarts = periodStarts(closes, tf);
308
+ reaches = starts.map((s, i) => closeStarts[i] !== s);
309
+ } else {
310
+ const next = timeAfterLastBar(bars, 1);
311
+ const withNext = Number.isNaN(next) ? [] : periodStarts([...bars, { time: next }], tf);
312
+ const lastEnds = withNext.length === 0 || withNext[n] !== withNext[n - 1];
313
+ reaches = starts.map((_s, i) => i < n - 1 || lastEnds);
314
+ }
315
+ const { group, latest } = periodsOf(starts, starts, reaches);
316
+ const first = [];
317
+ group.forEach((g, i) => {
318
+ if (g >= first.length)
319
+ first.push(i);
320
+ });
321
+ return {
322
+ starts,
323
+ group,
324
+ latest,
325
+ after: afterCompletion(group, latest),
326
+ lastCompletes: n > 0 && latest[n - 1] === group[n - 1],
327
+ htf: resample(bars, starts).bars,
328
+ first
329
+ };
330
+ }
331
+ function sessionFlags(bars, chart) {
332
+ const calendar = calendarOf(bars, chart);
333
+ return calendar ? chartSessionFlags(bars.map((b) => b.time), calendar.chart) : null;
334
+ }
335
+ function tradingDays(bars, chart) {
336
+ const calendar = calendarOf(bars, chart);
337
+ return calendar ? bars.map((b) => tradingDayTime(b.time, calendar.chart) / calendar.unit) : null;
338
+ }
339
+ function timeAfterLastBar(bars, count, chart) {
340
+ const n = bars.length;
341
+ if (n === 0)
342
+ return NaN;
343
+ const last = bars[n - 1].time;
344
+ if (count <= 0)
345
+ return last;
346
+ const calendar = calendarOf(bars, chart);
347
+ if (calendar) {
348
+ const t2 = barTimeAfter(last, count, calendar.chart);
349
+ if (!Number.isNaN(t2))
350
+ return t2;
351
+ }
352
+ const next = utcNextBar(bars);
353
+ let t = last;
354
+ for (let k = 0; k < count && !Number.isNaN(t); k++)
355
+ t = next(t);
356
+ return t;
357
+ }
358
+ function barsAfterLastBar(bars, time, chart) {
359
+ const n = bars.length;
360
+ if (n === 0)
361
+ return NaN;
362
+ const last = bars[n - 1].time;
363
+ const calendar = calendarOf(bars, chart);
364
+ if (calendar) {
365
+ const k = barsUntil(last, time, calendar.chart);
366
+ if (!Number.isNaN(k))
367
+ return k;
368
+ }
369
+ const next = utcNextBar(bars);
370
+ let t = last;
371
+ for (let k = 0; k < 1e5; k++) {
372
+ const after = next(t);
373
+ if (Number.isNaN(after))
374
+ return NaN;
375
+ if (after > time)
376
+ return k;
377
+ t = after;
378
+ }
379
+ return NaN;
380
+ }
381
+ function utcNextBar(bars) {
382
+ const n = bars.length;
383
+ const ms = n > 0 && bars[0].time >= 1e12;
384
+ const toMs = (t) => ms ? t : t * 1e3;
385
+ const fromMs = (t) => ms ? t : t / 1e3;
386
+ const interval = toMs(barInterval(bars));
387
+ if (!(interval > 0))
388
+ return () => NaN;
389
+ const weekend = (t) => {
390
+ const wd = new Date(t).getUTCDay();
391
+ return wd === 0 || wd === 6;
392
+ };
393
+ if (bars.some((b) => weekend(toMs(b.time))))
394
+ return (t) => t + fromMs(interval);
395
+ const nextWeekday = (day) => {
396
+ let d = day + DAY;
397
+ while (weekend(d))
398
+ d += DAY;
399
+ return d;
400
+ };
401
+ if (interval >= 28 * DAY) {
402
+ const months = Math.max(1, Math.round(interval / (timeframe.in_seconds("1M") * 1e3)));
403
+ return (t) => {
404
+ const d = new Date(toMs(t));
405
+ let m = Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + months, 1) + toMs(t) % DAY;
406
+ while (weekend(m))
407
+ m += DAY;
408
+ return fromMs(m);
409
+ };
410
+ }
411
+ if (interval >= 7 * DAY)
412
+ return (t) => t + fromMs(interval);
413
+ if (interval >= DAY) {
414
+ const days = Math.round(interval / DAY);
415
+ return (t) => {
416
+ let d = Math.floor(toMs(t) / DAY) * DAY;
417
+ for (let k = 0; k < days; k++)
418
+ d = nextWeekday(d);
419
+ return fromMs(d + toMs(t) % DAY);
420
+ };
421
+ }
422
+ const lastDay = Math.floor(toMs(bars[n - 1].time) / DAY) * DAY;
423
+ let j = n - 1;
424
+ while (j >= 0 && toMs(bars[j].time) >= lastDay)
425
+ j--;
426
+ const refDay = j < 0 ? lastDay : Math.floor(toMs(bars[j].time) / DAY) * DAY;
427
+ const refLast = j < 0 ? toMs(bars[n - 1].time) - lastDay : toMs(bars[j].time) - refDay;
428
+ let i = j < 0 ? n - 1 : j;
429
+ while (i > 0 && toMs(bars[i - 1].time) >= refDay)
430
+ i--;
431
+ const refFirst = toMs(bars[i].time) - refDay;
432
+ return (t) => {
433
+ const day = Math.floor(toMs(t) / DAY) * DAY;
434
+ return fromMs(toMs(t) - day < refLast ? toMs(t) + interval : nextWeekday(day) + refFirst);
435
+ };
436
+ }
437
+
438
+ // src/other-symbol.ts
439
+ import { chartSessions as chartSessions2, tradingDayTime as tradingDayTime2 } from "oakscriptjs";
440
+ import {
441
+ securityOther,
442
+ securityOtherValues,
443
+ securityLowerTf
444
+ } from "oakscriptjs";
445
+ function otherSymbolChart(chartBars, chart) {
446
+ return {
447
+ timeframe: chartTimeframe(chartBars, chart) || "1D",
448
+ timezone: chart?.timezone,
449
+ session: chart?.session,
450
+ timeUnit: chart?.timeUnit ?? (chartBars.length > 0 && chartBars[0].time >= 1e12 ? "ms" : "s"),
451
+ lastBarConfirmed: chart?.lastBarConfirmed
452
+ };
453
+ }
454
+ var DAY2 = 864e5;
455
+ var UTC_24X7 = { timezone: "Etc/UTC", session: "24x7" };
456
+ var calendars = /* @__PURE__ */ new Map();
457
+ function dailyCalendarOf(zone) {
458
+ if (!zone)
459
+ return null;
460
+ const key = `${zone.timezone}|${typeof zone.session === "string" ? zone.session : JSON.stringify(zone.session)}`;
461
+ let context = calendars.get(key);
462
+ if (context === void 0) {
463
+ context = { timeframe: "1D", timezone: zone.timezone, session: zone.session, timeUnit: "ms" };
464
+ try {
465
+ chartSessions2(context);
466
+ } catch {
467
+ context = null;
468
+ }
469
+ if (calendars.size > 64)
470
+ calendars.clear();
471
+ calendars.set(key, context);
472
+ }
473
+ return context;
474
+ }
475
+ function parseSpread(expr) {
476
+ const s = expr.replace(/\s+/g, "");
477
+ const m = /^([^*/]+)([*/])([^*/]+)$/.exec(s);
478
+ return m ? { a: m[1], op: m[2], b: m[3] } : { a: s };
479
+ }
480
+ function spreadSymbols(expr) {
481
+ const e = parseSpread(expr);
482
+ return e.b !== void 0 && e.b !== e.a ? [e.a, e.b] : [e.a];
483
+ }
484
+ function spreadOptions(expr, chart) {
485
+ const e = parseSpread(expr);
486
+ return { other: e.op ? { ...UTC_24X7 } : chart?.symbolInfo?.(e.a) };
487
+ }
488
+ function spreadTradingDays(times, zone) {
489
+ const calendar = dailyCalendarOf(zone);
490
+ return times.map((t) => {
491
+ const d = calendar ? tradingDayTime2(t, calendar) : NaN;
492
+ if (!Number.isNaN(d))
493
+ return d;
494
+ const day = Math.floor(t / DAY2) * DAY2;
495
+ return t - day >= DAY2 / 2 ? day + DAY2 : day;
496
+ });
497
+ }
498
+ function spreadBars(expr, barsBySymbol, chart) {
499
+ const get = (symbol) => {
500
+ const bars = typeof barsBySymbol === "function" ? barsBySymbol(symbol) : barsBySymbol.get(symbol);
501
+ if (!bars)
502
+ throw new RangeError(`No bars for the symbol "${symbol}" of "${expr}"`);
503
+ return bars;
504
+ };
505
+ const e = parseSpread(expr);
506
+ if (!e.op || e.b === void 0)
507
+ return get(e.a).slice();
508
+ const a = get(e.a);
509
+ const b = get(e.b);
510
+ if (a.length === 0 || b.length === 0)
511
+ return [];
512
+ const unit = chart?.timeUnit === "ms" || chart?.timeUnit === void 0 && a[0].time >= 1e12 ? 1 : 1e3;
513
+ const days = (bars, symbol) => {
514
+ const info = chart?.symbolInfo?.(symbol);
515
+ const zone = info?.timezone && info.session ? { timezone: info.timezone, session: info.session } : null;
516
+ return spreadTradingDays(bars.map((x) => x.time * unit), zone);
517
+ };
518
+ const aDays = days(a, e.a);
519
+ const bDays = days(b, e.b);
520
+ const start = Math.max(aDays[0], bDays[0]);
521
+ const out = [];
522
+ let i = -1;
523
+ let j = -1;
524
+ while (i + 1 < a.length || j + 1 < b.length) {
525
+ const next = Math.min(i + 1 < a.length ? aDays[i + 1] : Infinity, j + 1 < b.length ? bDays[j + 1] : Infinity);
526
+ while (i + 1 < a.length && aDays[i + 1] <= next)
527
+ i++;
528
+ while (j + 1 < b.length && bDays[j + 1] <= next)
529
+ j++;
530
+ if (next < start)
531
+ continue;
532
+ const c = e.op === "*" ? a[i].close * b[j].close : a[i].close / b[j].close;
533
+ out.push({ time: next / unit, open: c, high: c, low: c, close: c, volume: 0 });
534
+ }
535
+ return out;
536
+ }
537
+
538
+ // src/lower-tf-volume.ts
539
+ import { timeframe as timeframe2 } from "oakscriptjs";
540
+ var EPS = 1e-10;
541
+ var gt = (a, b) => a - b > EPS;
542
+ function lowerTimeframe(bars, useCustomTimeframe, customTimeframe, context) {
543
+ const chart = chartTimeframe(bars, context);
544
+ if (chart === "")
545
+ return { timeframe: useCustomTimeframe ? customTimeframe : "", chart, exact: false };
546
+ const info = timeframe2.info(chart);
547
+ const tf = useCustomTimeframe ? customTimeframe : info.isseconds ? "1S" : info.isintraday ? "1" : info.isdaily ? "5" : "60";
548
+ const lower = tf.trim() === "" ? timeframe2.in_seconds(chart) : timeframe2.in_seconds(tf);
549
+ const chartSeconds = timeframe2.in_seconds(chart);
550
+ if (lower > chartSeconds) {
551
+ throw new Error(`The chart's timeframe must be greater or equal to the \`${tf}\` timeframe used with \`request.security_lower_tf()\`.`);
552
+ }
553
+ return { timeframe: tf, chart, exact: lower === chartSeconds };
554
+ }
555
+ function upDownVolumeOfChartBars(bars) {
556
+ const up = [];
557
+ const down = [];
558
+ const delta = [];
559
+ let isBuyVolume = true;
560
+ for (let i = 0; i < bars.length; i++) {
561
+ const { open, close } = bars[i];
562
+ const prevClose = i > 0 ? bars[i - 1].close : NaN;
563
+ if (gt(close, open))
564
+ isBuyVolume = true;
565
+ else if (gt(open, close))
566
+ isBuyVolume = false;
567
+ else if (gt(close, prevClose))
568
+ isBuyVolume = true;
569
+ else if (gt(prevClose, close))
570
+ isBuyVolume = false;
571
+ const volume = bars[i].volume ?? 0;
572
+ const pos = isBuyVolume ? volume : 0;
573
+ const neg = isBuyVolume ? 0 : -volume;
574
+ up.push(pos);
575
+ down.push(neg);
576
+ delta.push(pos + neg);
577
+ }
578
+ return { up, down, delta };
579
+ }
580
+
581
+ // src/bar-time.ts
582
+ import { barInterval as barInterval2, barTime } from "oakscriptjs";
583
+ export {
584
+ MissingSymbolDataError,
585
+ backgroundColorOf,
586
+ barInterval2 as barInterval,
587
+ barTime,
588
+ barsAfterLastBar,
589
+ basecurrencyOf,
590
+ chartTimeframe,
591
+ currencyOf,
592
+ foregroundColorOf,
593
+ hasCalendar,
594
+ higherTimeframeBars,
595
+ lastBarConfirmedOf,
596
+ lowerTimeframe,
597
+ mintickOf,
598
+ nowOf,
599
+ otherSymbolChart,
600
+ periodChanges,
601
+ periodStarts,
602
+ rootOf,
603
+ securityBars,
604
+ securityLowerTf,
605
+ securityOther,
606
+ securityOtherValues,
607
+ securityPeriods,
608
+ securityStarts,
609
+ sessionFlags,
610
+ sessionHoursOf,
611
+ spreadBars,
612
+ spreadOptions,
613
+ spreadSymbols,
614
+ symbolBarsOf,
615
+ symbolTypeOf,
616
+ tickerOf,
617
+ tickeridOf,
618
+ timeAfterLastBar,
619
+ timezoneOf,
620
+ tradingDays,
621
+ upDownVolumeOfChartBars,
622
+ visibleRangeOf
623
+ };