oakscriptjs 0.9.5 → 0.10.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 +1 -1
- package/dist/cjs/array/index.d.ts +93 -50
- package/dist/cjs/array/index.d.ts.map +1 -1
- package/dist/cjs/array/index.js +239 -78
- package/dist/cjs/array/index.js.map +3 -3
- package/dist/cjs/callsite/index.d.ts +81 -2
- package/dist/cjs/callsite/index.d.ts.map +1 -1
- package/dist/cjs/callsite/index.js +62 -1
- package/dist/cjs/callsite/index.js.map +2 -2
- package/dist/cjs/color/index.d.ts +7 -3
- package/dist/cjs/color/index.d.ts.map +1 -1
- package/dist/cjs/color/index.js +6 -1
- package/dist/cjs/color/index.js.map +2 -2
- package/dist/cjs/drawing/bar-time.d.ts +24 -0
- package/dist/cjs/drawing/bar-time.d.ts.map +1 -0
- package/dist/cjs/drawing/bar-time.js +47 -0
- package/dist/cjs/drawing/bar-time.js.map +7 -0
- package/dist/cjs/drawing/output.d.ts +32 -0
- package/dist/cjs/drawing/output.d.ts.map +1 -0
- package/dist/cjs/drawing/output.js +184 -0
- package/dist/cjs/drawing/output.js.map +7 -0
- package/dist/cjs/drawing/registry.d.ts +5 -4
- package/dist/cjs/drawing/registry.d.ts.map +1 -1
- package/dist/cjs/drawing/registry.js +11 -2
- package/dist/cjs/drawing/registry.js.map +2 -2
- package/dist/cjs/index.d.ts +6 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +9 -2
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/label/index.d.ts.map +1 -1
- package/dist/cjs/label/index.js +2 -1
- package/dist/cjs/label/index.js.map +2 -2
- package/dist/cjs/linefill/index.d.ts.map +1 -1
- package/dist/cjs/linefill/index.js +1 -0
- package/dist/cjs/linefill/index.js.map +2 -2
- package/dist/cjs/math/index.d.ts +76 -21
- package/dist/cjs/math/index.d.ts.map +1 -1
- package/dist/cjs/math/index.js +155 -41
- package/dist/cjs/math/index.js.map +3 -3
- package/dist/cjs/matrix/index.d.ts.map +1 -1
- package/dist/cjs/matrix/index.js +42 -36
- package/dist/cjs/matrix/index.js.map +2 -2
- package/dist/cjs/polyline/index.d.ts +3 -5
- package/dist/cjs/polyline/index.d.ts.map +1 -1
- package/dist/cjs/polyline/index.js +0 -5
- package/dist/cjs/polyline/index.js.map +2 -2
- package/dist/cjs/runtime/series.d.ts +9 -0
- package/dist/cjs/runtime/series.d.ts.map +1 -1
- package/dist/cjs/runtime/series.js +16 -2
- package/dist/cjs/runtime/series.js.map +2 -2
- package/dist/cjs/runtime/types.d.ts +22 -0
- package/dist/cjs/runtime/types.d.ts.map +1 -1
- package/dist/cjs/runtime/types.js.map +1 -1
- package/dist/cjs/script/index.d.ts +97 -36
- package/dist/cjs/script/index.d.ts.map +1 -1
- package/dist/cjs/script/index.js +173 -26
- package/dist/cjs/script/index.js.map +3 -3
- package/dist/cjs/session/calendar.d.ts.map +1 -1
- package/dist/cjs/session/calendar.js +15 -2
- package/dist/cjs/session/calendar.js.map +2 -2
- package/dist/cjs/str/index.d.ts +50 -38
- package/dist/cjs/str/index.d.ts.map +1 -1
- package/dist/cjs/str/index.js +43 -15
- package/dist/cjs/str/index.js.map +2 -2
- package/dist/cjs/str/messageformat.d.ts +2 -1
- package/dist/cjs/str/messageformat.d.ts.map +1 -1
- package/dist/cjs/str/messageformat.js +4 -1
- package/dist/cjs/str/messageformat.js.map +2 -2
- package/dist/cjs/str/numberformat.d.ts +4 -0
- package/dist/cjs/str/numberformat.d.ts.map +1 -1
- package/dist/cjs/str/numberformat.js +26 -4
- package/dist/cjs/str/numberformat.js.map +3 -3
- package/dist/cjs/ta/index.d.ts +86 -43
- package/dist/cjs/ta/index.d.ts.map +1 -1
- package/dist/cjs/ta/index.js +173 -267
- package/dist/cjs/ta/index.js.map +3 -3
- package/dist/cjs/ta/running-sum.d.ts +54 -2
- package/dist/cjs/ta/running-sum.d.ts.map +1 -1
- package/dist/cjs/ta/running-sum.js +132 -52
- package/dist/cjs/ta/running-sum.js.map +2 -2
- package/dist/cjs/ta-series.d.ts +4 -4
- package/dist/cjs/ta-series.d.ts.map +1 -1
- package/dist/cjs/ta-series.js +4 -3
- package/dist/cjs/ta-series.js.map +2 -2
- package/dist/cjs/table/index.d.ts +87 -0
- package/dist/cjs/table/index.d.ts.map +1 -0
- package/dist/cjs/table/index.js +150 -0
- package/dist/cjs/table/index.js.map +7 -0
- package/dist/cjs/text/index.d.ts +17 -2
- package/dist/cjs/text/index.d.ts.map +1 -1
- package/dist/cjs/text/index.js +15 -1
- package/dist/cjs/text/index.js.map +2 -2
- package/dist/cjs/time/datestring.d.ts +1 -0
- package/dist/cjs/time/datestring.d.ts.map +1 -1
- package/dist/cjs/time/datestring.js +7 -0
- package/dist/cjs/time/datestring.js.map +2 -2
- package/dist/cjs/time/index.d.ts.map +1 -1
- package/dist/cjs/time/index.js +2 -1
- package/dist/cjs/time/index.js.map +2 -2
- package/dist/cjs/time/session.d.ts +28 -9
- package/dist/cjs/time/session.d.ts.map +1 -1
- package/dist/cjs/time/session.js +74 -28
- package/dist/cjs/time/session.js.map +2 -2
- package/dist/cjs/time/timezone.d.ts +5 -0
- package/dist/cjs/time/timezone.d.ts.map +1 -1
- package/dist/cjs/time/timezone.js +15 -1
- package/dist/cjs/time/timezone.js.map +2 -2
- package/dist/cjs/types/index.d.ts +30 -1
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +12 -0
- package/dist/cjs/types/index.js.map +2 -2
- package/dist/cjs/types/metadata.d.ts +258 -26
- package/dist/cjs/types/metadata.d.ts.map +1 -1
- package/dist/cjs/types/metadata.js.map +1 -1
- package/dist/esm/array/index.d.ts +93 -50
- package/dist/esm/array/index.d.ts.map +1 -1
- package/dist/esm/array/index.js +383 -133
- package/dist/esm/array/index.js.map +1 -1
- package/dist/esm/callsite/index.d.ts +81 -2
- package/dist/esm/callsite/index.d.ts.map +1 -1
- package/dist/esm/callsite/index.js +145 -3
- package/dist/esm/callsite/index.js.map +1 -1
- package/dist/esm/color/index.d.ts +7 -3
- package/dist/esm/color/index.d.ts.map +1 -1
- package/dist/esm/color/index.js +15 -3
- package/dist/esm/color/index.js.map +1 -1
- package/dist/esm/drawing/bar-time.d.ts +24 -0
- package/dist/esm/drawing/bar-time.d.ts.map +1 -0
- package/dist/esm/drawing/bar-time.js +44 -0
- package/dist/esm/drawing/bar-time.js.map +1 -0
- package/dist/esm/drawing/output.d.ts +32 -0
- package/dist/esm/drawing/output.d.ts.map +1 -0
- package/dist/esm/drawing/output.js +190 -0
- package/dist/esm/drawing/output.js.map +1 -0
- package/dist/esm/drawing/registry.d.ts +5 -4
- package/dist/esm/drawing/registry.d.ts.map +1 -1
- package/dist/esm/drawing/registry.js +12 -2
- package/dist/esm/drawing/registry.js.map +1 -1
- package/dist/esm/index.d.ts +6 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +7 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/label/index.d.ts.map +1 -1
- package/dist/esm/label/index.js +2 -1
- package/dist/esm/label/index.js.map +1 -1
- package/dist/esm/linefill/index.d.ts.map +1 -1
- package/dist/esm/linefill/index.js +3 -0
- package/dist/esm/linefill/index.js.map +1 -1
- package/dist/esm/math/index.d.ts +76 -21
- package/dist/esm/math/index.d.ts.map +1 -1
- package/dist/esm/math/index.js +227 -61
- package/dist/esm/math/index.js.map +1 -1
- package/dist/esm/matrix/index.d.ts.map +1 -1
- package/dist/esm/matrix/index.js +57 -43
- package/dist/esm/matrix/index.js.map +1 -1
- package/dist/esm/polyline/index.d.ts +3 -5
- package/dist/esm/polyline/index.d.ts.map +1 -1
- package/dist/esm/polyline/index.js +3 -9
- package/dist/esm/polyline/index.js.map +1 -1
- package/dist/esm/runtime/series.d.ts +9 -0
- package/dist/esm/runtime/series.d.ts.map +1 -1
- package/dist/esm/runtime/series.js +18 -1
- package/dist/esm/runtime/series.js.map +1 -1
- package/dist/esm/runtime/types.d.ts +22 -0
- package/dist/esm/runtime/types.d.ts.map +1 -1
- package/dist/esm/script/index.d.ts +97 -36
- package/dist/esm/script/index.d.ts.map +1 -1
- package/dist/esm/script/index.js +211 -22
- package/dist/esm/script/index.js.map +1 -1
- package/dist/esm/session/calendar.d.ts.map +1 -1
- package/dist/esm/session/calendar.js +22 -2
- package/dist/esm/session/calendar.js.map +1 -1
- package/dist/esm/str/index.d.ts +50 -38
- package/dist/esm/str/index.d.ts.map +1 -1
- package/dist/esm/str/index.js +103 -52
- package/dist/esm/str/index.js.map +1 -1
- package/dist/esm/str/messageformat.d.ts +2 -1
- package/dist/esm/str/messageformat.d.ts.map +1 -1
- package/dist/esm/str/messageformat.js +7 -2
- package/dist/esm/str/messageformat.js.map +1 -1
- package/dist/esm/str/numberformat.d.ts +4 -0
- package/dist/esm/str/numberformat.d.ts.map +1 -1
- package/dist/esm/str/numberformat.js +38 -4
- package/dist/esm/str/numberformat.js.map +1 -1
- package/dist/esm/ta/index.d.ts +86 -43
- package/dist/esm/ta/index.d.ts.map +1 -1
- package/dist/esm/ta/index.js +296 -348
- package/dist/esm/ta/index.js.map +1 -1
- package/dist/esm/ta/running-sum.d.ts +54 -2
- package/dist/esm/ta/running-sum.d.ts.map +1 -1
- package/dist/esm/ta/running-sum.js +166 -71
- package/dist/esm/ta/running-sum.js.map +1 -1
- package/dist/esm/ta-series.d.ts +4 -4
- package/dist/esm/ta-series.d.ts.map +1 -1
- package/dist/esm/ta-series.js +5 -4
- package/dist/esm/ta-series.js.map +1 -1
- package/dist/esm/table/index.d.ts +87 -0
- package/dist/esm/table/index.d.ts.map +1 -0
- package/dist/esm/table/index.js +169 -0
- package/dist/esm/table/index.js.map +1 -0
- package/dist/esm/text/index.d.ts +17 -2
- package/dist/esm/text/index.d.ts.map +1 -1
- package/dist/esm/text/index.js +17 -2
- package/dist/esm/text/index.js.map +1 -1
- package/dist/esm/time/datestring.d.ts +1 -0
- package/dist/esm/time/datestring.d.ts.map +1 -1
- package/dist/esm/time/datestring.js +12 -0
- package/dist/esm/time/datestring.js.map +1 -1
- package/dist/esm/time/index.d.ts.map +1 -1
- package/dist/esm/time/index.js +3 -1
- package/dist/esm/time/index.js.map +1 -1
- package/dist/esm/time/session.d.ts +28 -9
- package/dist/esm/time/session.d.ts.map +1 -1
- package/dist/esm/time/session.js +113 -42
- package/dist/esm/time/session.js.map +1 -1
- package/dist/esm/time/timezone.d.ts +5 -0
- package/dist/esm/time/timezone.d.ts.map +1 -1
- package/dist/esm/time/timezone.js +21 -1
- package/dist/esm/time/timezone.js.map +1 -1
- package/dist/esm/types/index.d.ts +30 -1
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/esm/types/index.js +14 -0
- package/dist/esm/types/index.js.map +1 -1
- package/dist/esm/types/metadata.d.ts +258 -26
- package/dist/esm/types/metadata.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/array/index.ts +364 -150
- package/src/callsite/index.ts +147 -3
- package/src/color/index.ts +15 -5
- package/src/drawing/bar-time.ts +42 -0
- package/src/drawing/output.ts +214 -0
- package/src/drawing/registry.ts +16 -6
- package/src/index.ts +9 -3
- package/src/label/index.ts +2 -1
- package/src/linefill/index.ts +2 -0
- package/src/math/index.ts +256 -74
- package/src/matrix/index.ts +52 -52
- package/src/polyline/index.ts +3 -12
- package/src/runtime/series.ts +22 -1
- package/src/runtime/types.ts +23 -0
- package/src/script/index.ts +307 -25
- package/src/session/calendar.ts +22 -2
- package/src/str/index.ts +95 -51
- package/src/str/messageformat.ts +7 -2
- package/src/str/numberformat.ts +39 -4
- package/src/ta/index.ts +306 -386
- package/src/ta/running-sum.ts +187 -74
- package/src/ta-series.ts +8 -7
- package/src/table/index.ts +221 -0
- package/src/text/index.ts +18 -2
- package/src/time/datestring.ts +10 -0
- package/src/time/index.ts +3 -1
- package/src/time/session.ts +97 -47
- package/src/time/timezone.ts +21 -1
- package/src/types/index.ts +32 -1
- package/src/types/metadata.ts +325 -26
package/src/runtime/series.ts
CHANGED
|
@@ -174,6 +174,17 @@ export class BarData {
|
|
|
174
174
|
* const materialized = complex.materialize(); // Breaks closure chain
|
|
175
175
|
* ```
|
|
176
176
|
*/
|
|
177
|
+
/** Largest history reference of a script series (PineScript max_bars_back limit). */
|
|
178
|
+
export const MAX_BARS_BACK = 5000;
|
|
179
|
+
|
|
180
|
+
/** PineScript's runtime error RE10008 text. */
|
|
181
|
+
export function historyError(bar: number, barsBack: number): string {
|
|
182
|
+
return (
|
|
183
|
+
`Error on bar ${bar}: The script attempts to reference historical data that is too far from the current bar ` +
|
|
184
|
+
`(${barsBack} bars back). The history-referencing length for the expression must be a value between 0 and ${MAX_BARS_BACK}.`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
177
188
|
export class Series {
|
|
178
189
|
private extractor: SeriesExtractor;
|
|
179
190
|
private dataSource: BarData;
|
|
@@ -186,6 +197,12 @@ export class Series {
|
|
|
186
197
|
* @param data - Bar data (Bar[] or BarData)
|
|
187
198
|
* @param extractor - Function to extract/compute value for each bar
|
|
188
199
|
*/
|
|
200
|
+
/**
|
|
201
|
+
* True for PineScript's built-in price variables (open, high, low, close, volume, hl2...): their history covers
|
|
202
|
+
* every bar, so references further than 5000 bars back are allowed (measured: close[bar_index] runs).
|
|
203
|
+
*/
|
|
204
|
+
fullHistory = false;
|
|
205
|
+
|
|
189
206
|
constructor(data: Bar[] | BarData, extractor: SeriesExtractor) {
|
|
190
207
|
// Support both Bar[] and BarData for backward compatibility
|
|
191
208
|
this.dataSource = data instanceof BarData ? data : new BarData(data);
|
|
@@ -219,7 +236,9 @@ export class Series {
|
|
|
219
236
|
* @returns Series with extracted field values
|
|
220
237
|
*/
|
|
221
238
|
static fromBars(bars: Bar[] | BarData, field: 'open' | 'high' | 'low' | 'close' | 'volume'): Series {
|
|
222
|
-
|
|
239
|
+
const series = new Series(bars, (bar) => bar[field] ?? NaN);
|
|
240
|
+
series.fullHistory = true;
|
|
241
|
+
return series;
|
|
223
242
|
}
|
|
224
243
|
|
|
225
244
|
/**
|
|
@@ -516,6 +535,8 @@ export class Series {
|
|
|
516
535
|
*/
|
|
517
536
|
offset(offset: number): Series {
|
|
518
537
|
if (offset < 0) throw new RangeError(`Series.offset(${offset}): the offset cannot be negative (it would read a future bar).`);
|
|
538
|
+
// PineScript runtime error RE10008: a script series keeps at most 5000 bars of history
|
|
539
|
+
if (offset > MAX_BARS_BACK && !this.fullHistory) throw new Error(historyError(0, Math.floor(offset)));
|
|
519
540
|
return new Series(this.dataSource, (_bar, i, data) => {
|
|
520
541
|
const targetIndex = i - offset;
|
|
521
542
|
if (targetIndex < 0 || targetIndex >= data.length) {
|
package/src/runtime/types.ts
CHANGED
|
@@ -138,6 +138,29 @@ export interface ArrowConfig {
|
|
|
138
138
|
forceOverlay?: boolean;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Configuration of a PineScript `plotcandle()` (kind 'candle') or `plotbar()` (kind 'bar') declaration. The candles
|
|
143
|
+
* are in `result.plotCandles[id]`, the bars in `result.plotBars[id]`.
|
|
144
|
+
*/
|
|
145
|
+
export interface CandleConfig {
|
|
146
|
+
/** Unique identifier (key of the data in the result) */
|
|
147
|
+
id: string;
|
|
148
|
+
/** 'candle' for plotcandle, 'bar' for plotbar */
|
|
149
|
+
kind: 'candle' | 'bar';
|
|
150
|
+
/** Display title */
|
|
151
|
+
title?: string;
|
|
152
|
+
/** Static body colour (plotcandle) or bar colour (plotbar) */
|
|
153
|
+
color?: string;
|
|
154
|
+
/** Static wick colour (plotcandle only) */
|
|
155
|
+
wickColor?: string;
|
|
156
|
+
/** Static border colour (plotcandle only) */
|
|
157
|
+
borderColor?: string;
|
|
158
|
+
/** Display mode */
|
|
159
|
+
display?: 'all' | 'none' | 'data_window' | 'status_line' | 'pane';
|
|
160
|
+
/** Drawn on the main chart pane even when the script is not an overlay (PineScript `force_overlay`); default false */
|
|
161
|
+
forceOverlay?: boolean;
|
|
162
|
+
}
|
|
163
|
+
|
|
141
164
|
/**
|
|
142
165
|
* Configuration for horizontal line outputs in generated indicators
|
|
143
166
|
* Matches PineScript hline() function parameters
|
package/src/script/index.ts
CHANGED
|
@@ -37,25 +37,42 @@ import type { Bar } from '../types/index.js';
|
|
|
37
37
|
import type {
|
|
38
38
|
ArrowData,
|
|
39
39
|
BarColorData,
|
|
40
|
+
BgColorData,
|
|
41
|
+
BoxData,
|
|
40
42
|
FillData,
|
|
41
43
|
FillGradient,
|
|
42
44
|
IndicatorResult,
|
|
45
|
+
LabelData,
|
|
46
|
+
LabelStyle,
|
|
47
|
+
LineDrawingData,
|
|
48
|
+
LinefillData,
|
|
43
49
|
MarkerData,
|
|
44
50
|
MarkerLocation,
|
|
51
|
+
MarkerPosition,
|
|
52
|
+
MarkerShape,
|
|
45
53
|
MarkerSize,
|
|
54
|
+
PineSize,
|
|
55
|
+
PlotBarData,
|
|
56
|
+
PlotCandleData,
|
|
57
|
+
PolylineData,
|
|
46
58
|
ShapeStyle,
|
|
59
|
+
TableCellData,
|
|
60
|
+
TableData,
|
|
61
|
+
TableMergeData,
|
|
62
|
+
TablePosition,
|
|
47
63
|
TimeValue,
|
|
48
64
|
} from '../types/metadata.js';
|
|
49
65
|
import type {
|
|
50
66
|
ArrowConfig,
|
|
51
67
|
BarColorConfig,
|
|
68
|
+
CandleConfig,
|
|
52
69
|
FillConfig,
|
|
53
70
|
HLineConfig,
|
|
54
71
|
InputConfig,
|
|
55
72
|
PlotConfig,
|
|
56
73
|
ShapeConfig,
|
|
57
74
|
} from '../runtime/types.js';
|
|
58
|
-
import { BarData, Series } from '../runtime/series.js';
|
|
75
|
+
import { BarData, Series, MAX_BARS_BACK, historyError } from '../runtime/series.js';
|
|
59
76
|
import * as taSeries from '../ta-series.js';
|
|
60
77
|
import * as colorCore from '../color/index.js';
|
|
61
78
|
import { inputColorDefault } from '../color/parse.js';
|
|
@@ -65,10 +82,12 @@ import * as labelCore from '../label/index.js';
|
|
|
65
82
|
import * as boxCore from '../box/index.js';
|
|
66
83
|
import * as linefillCore from '../linefill/index.js';
|
|
67
84
|
import * as polylineCore from '../polyline/index.js';
|
|
85
|
+
import * as tableCore from '../table/index.js';
|
|
68
86
|
import * as chartPointCore from '../chartpoint/index.js';
|
|
69
87
|
import * as textCore from '../text/index.js';
|
|
70
88
|
import * as mathCore from '../math/index.js';
|
|
71
89
|
import { resetDrawings, setDrawingLimits } from '../drawing/registry.js';
|
|
90
|
+
import { drawingOutputs } from '../drawing/output.js';
|
|
72
91
|
import * as timeframeCore from '../timeframe/index.js';
|
|
73
92
|
import * as timeCore from '../time/index.js';
|
|
74
93
|
import * as strCore from '../str/index.js';
|
|
@@ -76,6 +95,7 @@ import { formatMessage } from '../str/messageformat.js';
|
|
|
76
95
|
import { TradingCalendar, type SessionSpec } from '../session/calendar.js';
|
|
77
96
|
import { SessionBars, sessionFlags, type SessionFlags } from '../session/bars.js';
|
|
78
97
|
import { afterCompletion, heikinAshi, mapToChart, periodsOf, resample } from '../security/resample.js';
|
|
98
|
+
import { position } from '../types/index.js';
|
|
79
99
|
import type { ChartPoint } from '../types/index.js';
|
|
80
100
|
import {
|
|
81
101
|
STRATEGY_CONSTANTS,
|
|
@@ -98,6 +118,7 @@ import {
|
|
|
98
118
|
type StrategyTrade,
|
|
99
119
|
type StrategyVariable,
|
|
100
120
|
} from '../strategy/index.js';
|
|
121
|
+
import { parsePineSession } from '../time/session.js';
|
|
101
122
|
|
|
102
123
|
/** Everything one run of a script produced. */
|
|
103
124
|
export interface ScriptRunResult {
|
|
@@ -109,11 +130,17 @@ export interface ScriptRunResult {
|
|
|
109
130
|
shapeConfig: ShapeConfig[];
|
|
110
131
|
arrowConfig: ArrowConfig[];
|
|
111
132
|
barColorConfig: BarColorConfig[];
|
|
133
|
+
/** plotcandle() / plotbar() declarations */
|
|
134
|
+
candleConfig: CandleConfig[];
|
|
112
135
|
defaultInputs: Record<string, unknown>;
|
|
113
136
|
alertConfig: AlertConditionConfig[];
|
|
114
137
|
/** Strategy properties (declared values, else the PineScript defaults) when the script declares strategy(). */
|
|
115
138
|
strategyConfig?: StrategyProperties;
|
|
116
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* The renderable output: plots keyed by plotConfig ids, plot-pair fills, markers, arrows, background and bar
|
|
141
|
+
* colours, candles and bars, and the drawings (labels, lines, boxes, linefills, polylines, tables) alive after the
|
|
142
|
+
* last bar.
|
|
143
|
+
*/
|
|
117
144
|
result: IndicatorResult & { alerts?: AlertState[] };
|
|
118
145
|
}
|
|
119
146
|
|
|
@@ -158,8 +185,11 @@ interface Collector {
|
|
|
158
185
|
arrowConfig: ArrowConfig[];
|
|
159
186
|
arrows: ArrowData[];
|
|
160
187
|
barColorConfig: BarColorConfig[];
|
|
161
|
-
|
|
162
|
-
|
|
188
|
+
bgColors: BgColorData[];
|
|
189
|
+
barColors: BarColorData[];
|
|
190
|
+
candleConfig: CandleConfig[];
|
|
191
|
+
plotCandles: Record<string, PlotCandleData[]>;
|
|
192
|
+
plotBars: Record<string, PlotBarData[]>;
|
|
163
193
|
alertConfig: AlertConditionConfig[];
|
|
164
194
|
alerts: AlertState[];
|
|
165
195
|
strategy?: StrategyRun;
|
|
@@ -209,8 +239,11 @@ function freshCollector(inputValues: Record<string, unknown>): Collector {
|
|
|
209
239
|
arrowConfig: [],
|
|
210
240
|
arrows: [],
|
|
211
241
|
barColorConfig: [],
|
|
212
|
-
|
|
213
|
-
|
|
242
|
+
bgColors: [],
|
|
243
|
+
barColors: [],
|
|
244
|
+
candleConfig: [],
|
|
245
|
+
plotCandles: {},
|
|
246
|
+
plotBars: {},
|
|
214
247
|
alertConfig: [],
|
|
215
248
|
alerts: [],
|
|
216
249
|
};
|
|
@@ -264,6 +297,7 @@ export function executeScript(
|
|
|
264
297
|
scriptOptions = options;
|
|
265
298
|
ctx = freshCollector(inputs);
|
|
266
299
|
resetDrawings();
|
|
300
|
+
mathCore.resetRandom();
|
|
267
301
|
try {
|
|
268
302
|
body();
|
|
269
303
|
const c = ctx;
|
|
@@ -282,6 +316,7 @@ export function executeScript(
|
|
|
282
316
|
shapeConfig: c.shapeConfig,
|
|
283
317
|
arrowConfig: c.arrowConfig,
|
|
284
318
|
barColorConfig: c.barColorConfig,
|
|
319
|
+
candleConfig: c.candleConfig,
|
|
285
320
|
defaultInputs: c.defaultInputs,
|
|
286
321
|
alertConfig: c.alertConfig,
|
|
287
322
|
strategyConfig: c.strategy?.properties,
|
|
@@ -291,8 +326,11 @@ export function executeScript(
|
|
|
291
326
|
fills: c.fills.length ? c.fills : undefined,
|
|
292
327
|
markers: c.markers.length ? c.markers : undefined,
|
|
293
328
|
arrows: c.arrows.length ? c.arrows : undefined,
|
|
294
|
-
|
|
295
|
-
|
|
329
|
+
bgColors: c.bgColors.length ? c.bgColors : undefined,
|
|
330
|
+
barColors: c.barColors.length ? c.barColors : undefined,
|
|
331
|
+
plotCandles: Object.keys(c.plotCandles).length ? c.plotCandles : undefined,
|
|
332
|
+
plotBars: Object.keys(c.plotBars).length ? c.plotBars : undefined,
|
|
333
|
+
...drawingOutputs(ctxBars.bars, chartCtx.timeUnit),
|
|
296
334
|
alerts: c.alerts.length ? c.alerts : undefined,
|
|
297
335
|
},
|
|
298
336
|
};
|
|
@@ -449,6 +487,8 @@ export const hl2 = new Series(ctxBars, (b) => (b.high + b.low) / 2);
|
|
|
449
487
|
export const hlc3 = new Series(ctxBars, (b) => (b.high + b.low + b.close) / 3);
|
|
450
488
|
export const ohlc4 = new Series(ctxBars, (b) => (b.open + b.high + b.low + b.close) / 4);
|
|
451
489
|
export const hlcc4 = new Series(ctxBars, (b) => (b.high + b.low + b.close + b.close) / 4);
|
|
490
|
+
// built-in price variables keep the whole history (no 5000-bar limit)
|
|
491
|
+
for (const s of [hl2, hlc3, ohlc4, hlcc4]) s.fullHistory = true;
|
|
452
492
|
|
|
453
493
|
const SOURCES: Record<string, Series> = { open, high, low, close, volume, hl2, hlc3, ohlc4, hlcc4 };
|
|
454
494
|
|
|
@@ -775,8 +815,46 @@ export interface PlotCharOptions extends Omit<PlotShapeOptions, 'style'> {
|
|
|
775
815
|
char?: string;
|
|
776
816
|
}
|
|
777
817
|
|
|
818
|
+
/** PineScript shape.* style -> marker shape. */
|
|
819
|
+
const MARKER_SHAPES: Record<ShapeStyle, MarkerShape> = {
|
|
820
|
+
xcross: 'xcross',
|
|
821
|
+
cross: 'cross',
|
|
822
|
+
triangleup: 'triangleUp',
|
|
823
|
+
triangledown: 'triangleDown',
|
|
824
|
+
flag: 'flag',
|
|
825
|
+
circle: 'circle',
|
|
826
|
+
arrowup: 'arrowUp',
|
|
827
|
+
arrowdown: 'arrowDown',
|
|
828
|
+
labelup: 'labelUp',
|
|
829
|
+
labeldown: 'labelDown',
|
|
830
|
+
square: 'square',
|
|
831
|
+
diamond: 'diamond',
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Marker position of a PineScript location. With location.absolute the shape sits on the price: a label pointing
|
|
836
|
+
* down above it, a label pointing up below it, the other shapes (and the characters) centred on it.
|
|
837
|
+
*/
|
|
838
|
+
function markerPosition(location: MarkerLocation, shape: ShapeStyle | undefined): MarkerPosition {
|
|
839
|
+
switch (location) {
|
|
840
|
+
case 'abovebar':
|
|
841
|
+
return 'aboveBar';
|
|
842
|
+
case 'belowbar':
|
|
843
|
+
return 'belowBar';
|
|
844
|
+
case 'top':
|
|
845
|
+
return 'top';
|
|
846
|
+
case 'bottom':
|
|
847
|
+
return 'bottom';
|
|
848
|
+
case 'absolute':
|
|
849
|
+
return shape === 'labeldown' ? 'atPriceTop' : shape === 'labelup' ? 'atPriceBottom' : 'atPriceMiddle';
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/** Default colour of plotshape / plotchar markers. */
|
|
854
|
+
const MARKER_COLOR = '#2962FF';
|
|
855
|
+
|
|
778
856
|
/** Shared emitter for plotshape()/plotchar(). Emits one marker per bar where
|
|
779
|
-
* the condition is truthy (not na, not 0). */
|
|
857
|
+
* the condition is truthy (not na, not 0). A plotchar marker has the shape 'circle' and its character in `char`. */
|
|
780
858
|
function emitMarkers(
|
|
781
859
|
kind: 'shape' | 'char',
|
|
782
860
|
condition: Series,
|
|
@@ -802,6 +880,19 @@ function emitMarkers(
|
|
|
802
880
|
offset: options.offset,
|
|
803
881
|
forceOverlay: options.force_overlay,
|
|
804
882
|
});
|
|
883
|
+
const shapeStyle = kind === 'shape' ? (style as ShapeStyle) : undefined;
|
|
884
|
+
const template: Omit<MarkerData, 'time'> = {
|
|
885
|
+
position: markerPosition(location, shapeStyle),
|
|
886
|
+
shape: shapeStyle ? MARKER_SHAPES[shapeStyle] : 'circle',
|
|
887
|
+
color: options.color ?? MARKER_COLOR,
|
|
888
|
+
id,
|
|
889
|
+
};
|
|
890
|
+
if (char !== undefined) template.char = char;
|
|
891
|
+
if (options.text !== undefined) template.text = options.text;
|
|
892
|
+
if (options.textcolor !== undefined) template.textColor = options.textcolor;
|
|
893
|
+
if (options.size !== undefined) template.size = options.size;
|
|
894
|
+
if (options.tooltip !== undefined) template.tooltip = options.tooltip;
|
|
895
|
+
if (options.force_overlay) template.forceOverlay = true;
|
|
805
896
|
const values = condition.toArray();
|
|
806
897
|
const b = bars();
|
|
807
898
|
const off = options.offset ?? 0;
|
|
@@ -810,19 +901,8 @@ function emitMarkers(
|
|
|
810
901
|
if (v === undefined || Number.isNaN(v) || v === 0) continue;
|
|
811
902
|
const j = i + off;
|
|
812
903
|
if (j < 0 || j >= b.length) continue;
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
id,
|
|
816
|
-
location,
|
|
817
|
-
style,
|
|
818
|
-
char,
|
|
819
|
-
color: options.color,
|
|
820
|
-
text: options.text,
|
|
821
|
-
textcolor: options.textcolor,
|
|
822
|
-
size: options.size,
|
|
823
|
-
tooltip: options.tooltip,
|
|
824
|
-
price: location === 'absolute' ? v : undefined,
|
|
825
|
-
});
|
|
904
|
+
const time = b[j]!.time;
|
|
905
|
+
c.markers.push(location === 'absolute' ? { time, ...template, price: v } : { time, ...template });
|
|
826
906
|
}
|
|
827
907
|
}
|
|
828
908
|
|
|
@@ -902,13 +982,14 @@ function emitBarColors(
|
|
|
902
982
|
c.barColorConfig.push({ id, kind, title: options.title, offset: options.offset, forceOverlay: options.force_overlay });
|
|
903
983
|
const b = bars();
|
|
904
984
|
const off = options.offset ?? 0;
|
|
905
|
-
const target = kind === 'bgcolor' ? c.
|
|
985
|
+
const target: BgColorData[] = kind === 'bgcolor' ? c.bgColors : c.barColors;
|
|
986
|
+
const forceOverlay = kind === 'bgcolor' && options.force_overlay === true;
|
|
906
987
|
for (let i = 0; i < b.length; i++) {
|
|
907
988
|
const col = typeof colors === 'string' ? colors : colors[i];
|
|
908
989
|
if (!col) continue;
|
|
909
990
|
const j = i + off;
|
|
910
991
|
if (j < 0 || j >= b.length) continue;
|
|
911
|
-
target.push({ time: b[j]!.time, color: col });
|
|
992
|
+
target.push(forceOverlay ? { time: b[j]!.time, color: col, forceOverlay } : { time: b[j]!.time, color: col });
|
|
912
993
|
}
|
|
913
994
|
}
|
|
914
995
|
|
|
@@ -931,6 +1012,129 @@ export function barcolor(
|
|
|
931
1012
|
emitBarColors('barcolor', colors, options);
|
|
932
1013
|
}
|
|
933
1014
|
|
|
1015
|
+
/** A colour argument of plotcandle() / plotbar(): a colour, or per-bar colours (null / undefined entries are na). */
|
|
1016
|
+
export type ScriptCandleColor = string | Array<string | null | undefined>;
|
|
1017
|
+
|
|
1018
|
+
export interface PlotCandleOptions {
|
|
1019
|
+
/** Body colour: a colour or a per-bar array (color.when); an na entry gives a transparent body */
|
|
1020
|
+
color?: ScriptCandleColor;
|
|
1021
|
+
/** Wick colour: a colour or a per-bar array */
|
|
1022
|
+
wickcolor?: ScriptCandleColor;
|
|
1023
|
+
/** Border colour: a colour or a per-bar array */
|
|
1024
|
+
bordercolor?: ScriptCandleColor;
|
|
1025
|
+
display?: CandleConfig['display'];
|
|
1026
|
+
/** Draw on the main chart pane even when the script is not an overlay (PineScript `force_overlay`); default false. */
|
|
1027
|
+
force_overlay?: boolean;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
export interface PlotBarOptions {
|
|
1031
|
+
/** Bar colour: a colour or a per-bar array (color.when); an na entry gives a transparent bar */
|
|
1032
|
+
color?: ScriptCandleColor;
|
|
1033
|
+
display?: CandleConfig['display'];
|
|
1034
|
+
/** Draw on the main chart pane even when the script is not an overlay (PineScript `force_overlay`); default false. */
|
|
1035
|
+
force_overlay?: boolean;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/** Colour of bar i: undefined when the argument is not given, 'transparent' for an na entry. */
|
|
1039
|
+
function candleColor(col: ScriptCandleColor | undefined, i: number): string | undefined {
|
|
1040
|
+
if (col === undefined) return undefined;
|
|
1041
|
+
if (typeof col === 'string') return col;
|
|
1042
|
+
return col[i] ?? 'transparent';
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
interface OhlcRow {
|
|
1046
|
+
i: number;
|
|
1047
|
+
open: number;
|
|
1048
|
+
high: number;
|
|
1049
|
+
low: number;
|
|
1050
|
+
close: number;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/** The bars where open, high, low and close are all not na (PineScript draws no candle / bar otherwise). */
|
|
1054
|
+
function ohlcRows(o: Series, h: Series, l: Series, cl: Series): OhlcRow[] {
|
|
1055
|
+
const [ov, hv, lv, cv] = [o.toArray(), h.toArray(), l.toArray(), cl.toArray()];
|
|
1056
|
+
const rows: OhlcRow[] = [];
|
|
1057
|
+
for (let i = 0; i < bars().length; i++) {
|
|
1058
|
+
const [open, high, low, close] = [ov[i]!, hv[i]!, lv[i]!, cv[i]!];
|
|
1059
|
+
if (Number.isFinite(open) && Number.isFinite(high) && Number.isFinite(low) && Number.isFinite(close)) {
|
|
1060
|
+
rows.push({ i, open, high, low, close });
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
return rows;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const staticColor = (col: ScriptCandleColor | undefined): string | undefined => (typeof col === 'string' ? col : undefined);
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* PineScript `plotcandle(open, high, low, close, title?, { color, wickcolor, bordercolor, display, force_overlay })`:
|
|
1070
|
+
* candles in `result.plotCandles[id]`, the declaration in `candleConfig`. A bar with an na value has no candle.
|
|
1071
|
+
*/
|
|
1072
|
+
export function plotcandle(
|
|
1073
|
+
open: Series,
|
|
1074
|
+
high: Series,
|
|
1075
|
+
low: Series,
|
|
1076
|
+
close: Series,
|
|
1077
|
+
title?: string,
|
|
1078
|
+
options: PlotCandleOptions = {}
|
|
1079
|
+
): void {
|
|
1080
|
+
const c = collector();
|
|
1081
|
+
const id = `plotcandle${c.candleConfig.filter((x) => x.kind === 'candle').length}`;
|
|
1082
|
+
c.candleConfig.push({
|
|
1083
|
+
id,
|
|
1084
|
+
kind: 'candle',
|
|
1085
|
+
title,
|
|
1086
|
+
color: staticColor(options.color),
|
|
1087
|
+
wickColor: staticColor(options.wickcolor),
|
|
1088
|
+
borderColor: staticColor(options.bordercolor),
|
|
1089
|
+
display: options.display,
|
|
1090
|
+
forceOverlay: options.force_overlay,
|
|
1091
|
+
});
|
|
1092
|
+
const b = bars();
|
|
1093
|
+
c.plotCandles[id] = ohlcRows(open, high, low, close).map(({ i, ...ohlc }) => {
|
|
1094
|
+
const candle: PlotCandleData = { time: b[i]!.time, ...ohlc };
|
|
1095
|
+
const color = candleColor(options.color, i);
|
|
1096
|
+
const wickColor = candleColor(options.wickcolor, i);
|
|
1097
|
+
const borderColor = candleColor(options.bordercolor, i);
|
|
1098
|
+
if (color !== undefined) candle.color = color;
|
|
1099
|
+
if (wickColor !== undefined) candle.wickColor = wickColor;
|
|
1100
|
+
if (borderColor !== undefined) candle.borderColor = borderColor;
|
|
1101
|
+
if (options.force_overlay) candle.forceOverlay = true;
|
|
1102
|
+
return candle;
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* PineScript `plotbar(open, high, low, close, title?, { color, display, force_overlay })`: OHLC bars in
|
|
1108
|
+
* `result.plotBars[id]`, the declaration in `candleConfig`. A bar with an na value is not drawn.
|
|
1109
|
+
*/
|
|
1110
|
+
export function plotbar(
|
|
1111
|
+
open: Series,
|
|
1112
|
+
high: Series,
|
|
1113
|
+
low: Series,
|
|
1114
|
+
close: Series,
|
|
1115
|
+
title?: string,
|
|
1116
|
+
options: PlotBarOptions = {}
|
|
1117
|
+
): void {
|
|
1118
|
+
const c = collector();
|
|
1119
|
+
const id = `plotbar${c.candleConfig.filter((x) => x.kind === 'bar').length}`;
|
|
1120
|
+
c.candleConfig.push({
|
|
1121
|
+
id,
|
|
1122
|
+
kind: 'bar',
|
|
1123
|
+
title,
|
|
1124
|
+
color: staticColor(options.color),
|
|
1125
|
+
display: options.display,
|
|
1126
|
+
forceOverlay: options.force_overlay,
|
|
1127
|
+
});
|
|
1128
|
+
const b = bars();
|
|
1129
|
+
c.plotBars[id] = ohlcRows(open, high, low, close).map(({ i, ...ohlc }) => {
|
|
1130
|
+
const bar: PlotBarData = { time: b[i]!.time, ...ohlc };
|
|
1131
|
+
const color = candleColor(options.color, i);
|
|
1132
|
+
if (color !== undefined) bar.color = color;
|
|
1133
|
+
if (options.force_overlay) bar.forceOverlay = true;
|
|
1134
|
+
return bar;
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
|
|
934
1138
|
// ── Per-bar execution (eachBar) ──────────────────────────────────────────────
|
|
935
1139
|
|
|
936
1140
|
/**
|
|
@@ -975,11 +1179,30 @@ export interface BarContext {
|
|
|
975
1179
|
* });
|
|
976
1180
|
* ```
|
|
977
1181
|
*/
|
|
1182
|
+
/** Initial history buffer of a script series and its growth factor (measured, issue #149). */
|
|
1183
|
+
const HISTORY_START = 242;
|
|
1184
|
+
const HISTORY_GROWTH = 1.618034;
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* History buffer of one script series in eachBar(), as PineScript grows it (measured on PineScript, issue #149):
|
|
1188
|
+
* the buffer starts with 242 bars; when the largest reference of a bar goes past it, a jump (the reference grew by
|
|
1189
|
+
* more than 1 since the previous bar) sets the buffer to the reference, and a step of 1 grows it to
|
|
1190
|
+
* round(size * 1.618034); a buffer above 5000 stops the script on the next bar (RE10008), with that size as
|
|
1191
|
+
* "bars back". A reference above 5000 stops it at once.
|
|
1192
|
+
*/
|
|
1193
|
+
interface HistoryBuffer {
|
|
1194
|
+
size: number;
|
|
1195
|
+
previous: number;
|
|
1196
|
+
barMax: number;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
978
1199
|
export function eachBar(fn: (c: BarContext) => number | boolean | void): Series {
|
|
979
1200
|
collector(); // same lifecycle guard (and error message) as the declaration calls
|
|
980
1201
|
const b = bars();
|
|
981
1202
|
const out: number[] = new Array(b.length).fill(NaN);
|
|
982
1203
|
const cache = new Map<Series, number[]>();
|
|
1204
|
+
const buffers = new Map<Series, HistoryBuffer>();
|
|
1205
|
+
let pendingError: { bar: number; barsBack: number } | null = null;
|
|
983
1206
|
let idx = 0;
|
|
984
1207
|
const ctx: BarContext = {
|
|
985
1208
|
get i() {
|
|
@@ -1006,6 +1229,13 @@ export function eachBar(fn: (c: BarContext) => number | boolean | void): Series
|
|
|
1006
1229
|
},
|
|
1007
1230
|
get(src: Series, offset: number = 0): number {
|
|
1008
1231
|
if (offset < 0) throw new RangeError(`c.get(): the offset cannot be negative (${offset} would read a future bar).`);
|
|
1232
|
+
if (!src.fullHistory && offset > 0) {
|
|
1233
|
+
const k = Math.floor(offset);
|
|
1234
|
+
if (k > MAX_BARS_BACK) throw new Error(historyError(idx, k));
|
|
1235
|
+
let buffer = buffers.get(src);
|
|
1236
|
+
if (!buffer) buffers.set(src, (buffer = { size: HISTORY_START, previous: 0, barMax: 0 }));
|
|
1237
|
+
if (k > buffer.barMax) buffer.barMax = k;
|
|
1238
|
+
}
|
|
1009
1239
|
let vals = cache.get(src);
|
|
1010
1240
|
if (!vals) {
|
|
1011
1241
|
vals = src.toArray();
|
|
@@ -1021,9 +1251,21 @@ export function eachBar(fn: (c: BarContext) => number | boolean | void): Series
|
|
|
1021
1251
|
};
|
|
1022
1252
|
try {
|
|
1023
1253
|
for (idx = 0; idx < b.length; idx++) {
|
|
1254
|
+
if (pendingError && pendingError.bar === idx) throw new Error(historyError(idx, pendingError.barsBack));
|
|
1024
1255
|
currentBar = idx;
|
|
1025
1256
|
const r = fn(ctx);
|
|
1026
1257
|
out[idx] = typeof r === 'number' ? r : typeof r === 'boolean' ? (r ? 1 : 0) : NaN;
|
|
1258
|
+
// grow the history buffers from the largest reference of this bar
|
|
1259
|
+
for (const buffer of buffers.values()) {
|
|
1260
|
+
const k = buffer.barMax;
|
|
1261
|
+
if (k > buffer.size) {
|
|
1262
|
+
const next = k - buffer.previous > 1 ? k : Math.round(buffer.size * HISTORY_GROWTH);
|
|
1263
|
+
if (next > MAX_BARS_BACK && !pendingError) pendingError = { bar: idx + 1, barsBack: next };
|
|
1264
|
+
buffer.size = next;
|
|
1265
|
+
}
|
|
1266
|
+
buffer.previous = k;
|
|
1267
|
+
buffer.barMax = 0;
|
|
1268
|
+
}
|
|
1027
1269
|
}
|
|
1028
1270
|
} finally {
|
|
1029
1271
|
currentBar = -1;
|
|
@@ -1467,6 +1709,9 @@ export function inSession(time: number, session: string, timezone?: string): boo
|
|
|
1467
1709
|
/** PineScript `str.*`; `format_time` and the dates of `format` use the exchange time zone by default. */
|
|
1468
1710
|
export const str = {
|
|
1469
1711
|
...strCore,
|
|
1712
|
+
// format.mintick rounds to the chart symbol's tick (syminfo.mintick)
|
|
1713
|
+
tostring: ((value: unknown, format?: string, mintick?: number) =>
|
|
1714
|
+
strCore.tostring(value as never, format, format === 'mintick' ? (mintick ?? syminfo.mintick) : mintick)) as typeof strCore.tostring,
|
|
1470
1715
|
format_time(time: number, format: string = "yyyy-MM-dd'T'HH:mm:ssZ", timezone?: string): string {
|
|
1471
1716
|
return strCore.format_time(time, format, timezone ?? exchangeTimezone());
|
|
1472
1717
|
},
|
|
@@ -1514,6 +1759,13 @@ function calendarFor(session?: string, timezone?: string): { bars: SessionBars;
|
|
|
1514
1759
|
const { bars } = sessionBars();
|
|
1515
1760
|
if (!session && !timezone) return { bars, test: false };
|
|
1516
1761
|
const tz = timezone ?? exchangeTimezone();
|
|
1762
|
+
// a session string that is empty or does not start with a digit is the symbol session: "regular" the regular
|
|
1763
|
+
// hours, "extended" and any other name the loaded hours (PineScript)
|
|
1764
|
+
if (session !== undefined && parsePineSession(session) === null) {
|
|
1765
|
+
const name = session.trim();
|
|
1766
|
+
const symbol = name === 'regular' && chartCtx.regularSession ? chartCtx.regularSession : chartCtx.session!;
|
|
1767
|
+
return { bars: new SessionBars(new TradingCalendar(tz, { session: toSpec(symbol).session }, 'symbol'), bars.chart.period), test: true };
|
|
1768
|
+
}
|
|
1517
1769
|
const spec = session ? { session } : { session: toSpec(chartCtx.session!).session };
|
|
1518
1770
|
return { bars: new SessionBars(new TradingCalendar(tz, spec, session ? 'pine' : 'symbol'), bars.chart.period), test: true };
|
|
1519
1771
|
}
|
|
@@ -1779,7 +2031,20 @@ export const polyline = {
|
|
|
1779
2031
|
},
|
|
1780
2032
|
};
|
|
1781
2033
|
|
|
1782
|
-
/**
|
|
2034
|
+
/**
|
|
2035
|
+
* PineScript `table.*`; `table.all` lists the live tables in creation order. The live tables go to `result.tables`.
|
|
2036
|
+
*/
|
|
2037
|
+
export const table = {
|
|
2038
|
+
...tableCore,
|
|
2039
|
+
get all() {
|
|
2040
|
+
return tableCore.all();
|
|
2041
|
+
},
|
|
2042
|
+
};
|
|
2043
|
+
|
|
2044
|
+
/** PineScript `position.*` constants (table positions). */
|
|
2045
|
+
export { position };
|
|
2046
|
+
|
|
2047
|
+
/** PineScript `text.format_*`, `text.align_*` and `text.wrap_*` constants. */
|
|
1783
2048
|
export const text = { ...textCore };
|
|
1784
2049
|
|
|
1785
2050
|
/**
|
|
@@ -1861,10 +2126,27 @@ export type {
|
|
|
1861
2126
|
ShapeConfig,
|
|
1862
2127
|
ArrowConfig,
|
|
1863
2128
|
BarColorConfig,
|
|
2129
|
+
CandleConfig,
|
|
1864
2130
|
MarkerData,
|
|
2131
|
+
MarkerPosition,
|
|
2132
|
+
MarkerShape,
|
|
1865
2133
|
ArrowData,
|
|
1866
2134
|
BarColorData,
|
|
2135
|
+
BgColorData,
|
|
2136
|
+
PlotCandleData,
|
|
2137
|
+
PlotBarData,
|
|
2138
|
+
LabelData,
|
|
2139
|
+
LabelStyle,
|
|
2140
|
+
LineDrawingData,
|
|
2141
|
+
BoxData,
|
|
2142
|
+
LinefillData,
|
|
2143
|
+
PolylineData,
|
|
2144
|
+
TableData,
|
|
2145
|
+
TableCellData,
|
|
2146
|
+
TableMergeData,
|
|
2147
|
+
TablePosition,
|
|
1867
2148
|
MarkerLocation,
|
|
1868
2149
|
MarkerSize,
|
|
2150
|
+
PineSize,
|
|
1869
2151
|
ShapeStyle,
|
|
1870
2152
|
};
|
package/src/session/calendar.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { zonedFields, zonedToUnix } from '../time/timezone.js';
|
|
19
|
+
import { parsePineSession } from '../time/session.js';
|
|
19
20
|
|
|
20
21
|
/** Session information of a symbol (hours, corrections, holidays). */
|
|
21
22
|
export interface SessionSpec {
|
|
@@ -44,6 +45,8 @@ interface Version {
|
|
|
44
45
|
periods: Period[];
|
|
45
46
|
/** Index 1 (Sunday) to 7 (Saturday) */
|
|
46
47
|
days: boolean[];
|
|
48
|
+
/** PineScript session strings: the periods of each weekday (index 1 to 7), from the "|" sections */
|
|
49
|
+
byDay?: Period[][];
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
export const toDateNum = (y: number, m: number, d: number): DateNum => y * 10000 + m * 100 + d;
|
|
@@ -82,10 +85,26 @@ function parsePeriods(text: string, spec: string): Period[] {
|
|
|
82
85
|
});
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
/** A PineScript session string as periods per weekday (rules in time/session.ts). */
|
|
89
|
+
function pineVersion(text: string, from: DateNum): Version {
|
|
90
|
+
const ranges = parsePineSession(text);
|
|
91
|
+
if (ranges === null) throw new SyntaxError(`Session "${text}" is the symbol session`);
|
|
92
|
+
const byDay: Period[][] = Array.from({ length: 8 }, () => []);
|
|
93
|
+
for (const { start, end, days } of ranges) {
|
|
94
|
+
const period: Period =
|
|
95
|
+
start === 0 && end === 0
|
|
96
|
+
? { start: 0, end: 1440, startOffset: 0, endOffset: 0 }
|
|
97
|
+
: { start, end, startOffset: end <= start ? 1 : 0, endOffset: 0 };
|
|
98
|
+
for (const d of days) byDay[d]!.push(period);
|
|
99
|
+
}
|
|
100
|
+
return { from, periods: [], days: byDay.map((p) => p.length > 0), byDay };
|
|
101
|
+
}
|
|
102
|
+
|
|
85
103
|
function parseVersion(text: string, from: DateNum, spec: string, rule: 'symbol' | 'pine'): Version {
|
|
104
|
+
if (rule === 'pine') return pineVersion(text, from);
|
|
86
105
|
const [periodText = '', dayText] = text.trim().split(':');
|
|
87
106
|
const periods = parsePeriods(periodText, spec);
|
|
88
|
-
const allDays = periodText.trim() === '24x7'
|
|
107
|
+
const allDays = periodText.trim() === '24x7';
|
|
89
108
|
const digits = dayText ?? (allDays ? '1234567' : '23456');
|
|
90
109
|
if (!/^[1-7]+$/.test(digits)) throw new SyntaxError(`Invalid session days in "${spec}"`);
|
|
91
110
|
const days = new Array<boolean>(8).fill(false);
|
|
@@ -137,7 +156,8 @@ export class TradingCalendar {
|
|
|
137
156
|
periods = correction ?? [];
|
|
138
157
|
} else if (!this.holidays.has(date)) {
|
|
139
158
|
const version = [...this.versions].reverse().find((v) => v.from <= date)!;
|
|
140
|
-
if (version.
|
|
159
|
+
if (version.byDay) periods = version.byDay[weekdayOf(date)]!;
|
|
160
|
+
else if (version.days[weekdayOf(date)]) periods = version.periods;
|
|
141
161
|
}
|
|
142
162
|
const y = Math.floor(date / 10000);
|
|
143
163
|
const m = Math.floor(date / 100) % 100;
|