oakscriptjs 0.11.0 → 0.11.2
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/dist/cjs/array/index.d.ts +8 -2
- package/dist/cjs/array/index.d.ts.map +1 -1
- package/dist/cjs/array/index.js +2 -1
- package/dist/cjs/array/index.js.map +2 -2
- package/dist/cjs/box/index.js +6 -6
- package/dist/cjs/box/index.js.map +2 -2
- package/dist/cjs/callsite/index.d.ts.map +1 -1
- package/dist/cjs/callsite/index.js +6 -3
- package/dist/cjs/callsite/index.js.map +2 -2
- package/dist/cjs/drawing/registry.d.ts +15 -6
- package/dist/cjs/drawing/registry.d.ts.map +1 -1
- package/dist/cjs/drawing/registry.js +16 -2
- package/dist/cjs/drawing/registry.js.map +2 -2
- package/dist/cjs/index.d.ts +4 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +7 -1
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/input.d.ts +9 -1
- package/dist/cjs/input.d.ts.map +1 -1
- package/dist/cjs/input.js +16 -0
- package/dist/cjs/input.js.map +2 -2
- package/dist/cjs/label/index.js +3 -3
- package/dist/cjs/label/index.js.map +2 -2
- package/dist/cjs/line/index.d.ts +10 -27
- package/dist/cjs/line/index.d.ts.map +1 -1
- package/dist/cjs/line/index.js +10 -27
- package/dist/cjs/line/index.js.map +2 -2
- package/dist/cjs/linefill/index.d.ts +3 -0
- package/dist/cjs/linefill/index.d.ts.map +1 -1
- package/dist/cjs/linefill/index.js +15 -1
- package/dist/cjs/linefill/index.js.map +3 -3
- package/dist/cjs/runtime/adapters/SimpleInputAdapter.js +1 -1
- package/dist/cjs/runtime/adapters/SimpleInputAdapter.js.map +2 -2
- package/dist/cjs/runtime/index.d.ts +1 -1
- package/dist/cjs/runtime/index.d.ts.map +1 -1
- package/dist/cjs/runtime/index.js +1 -0
- package/dist/cjs/runtime/index.js.map +2 -2
- package/dist/cjs/runtime/inputs.d.ts +9 -0
- package/dist/cjs/runtime/inputs.d.ts.map +1 -1
- package/dist/cjs/runtime/inputs.js +8 -0
- package/dist/cjs/runtime/inputs.js.map +2 -2
- package/dist/cjs/runtime/types.d.ts +1 -1
- 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 +66 -13
- package/dist/cjs/script/index.d.ts.map +1 -1
- package/dist/cjs/script/index.js +139 -35
- package/dist/cjs/script/index.js.map +3 -3
- package/dist/cjs/security/other.d.ts +92 -0
- package/dist/cjs/security/other.d.ts.map +1 -0
- package/dist/cjs/security/other.js +164 -0
- package/dist/cjs/security/other.js.map +7 -0
- package/dist/cjs/session/bars.d.ts +10 -1
- package/dist/cjs/session/bars.d.ts.map +1 -1
- package/dist/cjs/session/bars.js +16 -2
- package/dist/cjs/session/bars.js.map +2 -2
- package/dist/cjs/session/calendar.d.ts +4 -1
- package/dist/cjs/session/calendar.d.ts.map +1 -1
- package/dist/cjs/session/calendar.js +12 -7
- package/dist/cjs/session/calendar.js.map +2 -2
- package/dist/cjs/session/context.d.ts +22 -5
- package/dist/cjs/session/context.d.ts.map +1 -1
- package/dist/cjs/session/context.js +67 -6
- package/dist/cjs/session/context.js.map +2 -2
- package/dist/cjs/str/floatingdecimal.d.ts +31 -0
- package/dist/cjs/str/floatingdecimal.d.ts.map +1 -0
- package/dist/cjs/str/floatingdecimal.js +298 -0
- package/dist/cjs/str/floatingdecimal.js.map +7 -0
- package/dist/cjs/str/index.d.ts +4 -3
- package/dist/cjs/str/index.d.ts.map +1 -1
- package/dist/cjs/str/index.js +1 -1
- package/dist/cjs/str/index.js.map +2 -2
- package/dist/cjs/str/numberformat.d.ts +6 -4
- package/dist/cjs/str/numberformat.d.ts.map +1 -1
- package/dist/cjs/str/numberformat.js +3 -2
- package/dist/cjs/str/numberformat.js.map +2 -2
- package/dist/cjs/ta/index.d.ts +64 -45
- package/dist/cjs/ta/index.d.ts.map +1 -1
- package/dist/cjs/ta/index.js +140 -126
- package/dist/cjs/ta/index.js.map +3 -3
- package/dist/cjs/ta/limits.d.ts +11 -0
- package/dist/cjs/ta/limits.d.ts.map +1 -1
- package/dist/cjs/ta/limits.js +15 -1
- package/dist/cjs/ta/limits.js.map +2 -2
- package/dist/cjs/ta/running-sum.js +1 -1
- package/dist/cjs/ta/running-sum.js.map +2 -2
- package/dist/cjs/ta-series.d.ts +1 -1
- package/dist/cjs/ta-series.d.ts.map +1 -1
- package/dist/cjs/ta-series.js +1 -1
- package/dist/cjs/ta-series.js.map +2 -2
- package/dist/cjs/table/index.d.ts +10 -3
- package/dist/cjs/table/index.d.ts.map +1 -1
- package/dist/cjs/table/index.js +11 -1
- package/dist/cjs/table/index.js.map +3 -3
- package/dist/cjs/time/session.d.ts +9 -0
- package/dist/cjs/time/session.d.ts.map +1 -1
- package/dist/cjs/time/session.js +42 -14
- package/dist/cjs/time/session.js.map +3 -3
- package/dist/cjs/time/timezone.d.ts +6 -1
- package/dist/cjs/time/timezone.d.ts.map +1 -1
- package/dist/cjs/time/timezone.js +2 -2
- package/dist/cjs/time/timezone.js.map +2 -2
- package/dist/cjs/types/metadata.d.ts +1 -1
- 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 +8 -2
- package/dist/esm/array/index.d.ts.map +1 -1
- package/dist/esm/array/index.js +12 -3
- package/dist/esm/array/index.js.map +1 -1
- package/dist/esm/box/index.js +6 -6
- package/dist/esm/box/index.js.map +1 -1
- package/dist/esm/callsite/index.d.ts.map +1 -1
- package/dist/esm/callsite/index.js +6 -3
- package/dist/esm/callsite/index.js.map +1 -1
- package/dist/esm/drawing/registry.d.ts +15 -6
- package/dist/esm/drawing/registry.d.ts.map +1 -1
- package/dist/esm/drawing/registry.js +30 -7
- package/dist/esm/drawing/registry.js.map +1 -1
- package/dist/esm/index.d.ts +4 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +8 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/input.d.ts +9 -1
- package/dist/esm/input.d.ts.map +1 -1
- package/dist/esm/input.js +16 -0
- package/dist/esm/input.js.map +1 -1
- package/dist/esm/label/index.js +3 -3
- package/dist/esm/label/index.js.map +1 -1
- package/dist/esm/line/index.d.ts +10 -27
- package/dist/esm/line/index.d.ts.map +1 -1
- package/dist/esm/line/index.js +20 -64
- package/dist/esm/line/index.js.map +1 -1
- package/dist/esm/linefill/index.d.ts +3 -0
- package/dist/esm/linefill/index.d.ts.map +1 -1
- package/dist/esm/linefill/index.js +20 -1
- package/dist/esm/linefill/index.js.map +1 -1
- package/dist/esm/runtime/adapters/SimpleInputAdapter.js +1 -1
- package/dist/esm/runtime/adapters/SimpleInputAdapter.js.map +1 -1
- package/dist/esm/runtime/index.d.ts +1 -1
- package/dist/esm/runtime/index.d.ts.map +1 -1
- package/dist/esm/runtime/index.js +1 -1
- package/dist/esm/runtime/index.js.map +1 -1
- package/dist/esm/runtime/inputs.d.ts +9 -0
- package/dist/esm/runtime/inputs.d.ts.map +1 -1
- package/dist/esm/runtime/inputs.js +15 -0
- package/dist/esm/runtime/inputs.js.map +1 -1
- package/dist/esm/runtime/types.d.ts +1 -1
- package/dist/esm/runtime/types.d.ts.map +1 -1
- package/dist/esm/script/index.d.ts +66 -13
- package/dist/esm/script/index.d.ts.map +1 -1
- package/dist/esm/script/index.js +187 -54
- package/dist/esm/script/index.js.map +1 -1
- package/dist/esm/security/other.d.ts +92 -0
- package/dist/esm/security/other.d.ts.map +1 -0
- package/dist/esm/security/other.js +211 -0
- package/dist/esm/security/other.js.map +1 -0
- package/dist/esm/session/bars.d.ts +10 -1
- package/dist/esm/session/bars.d.ts.map +1 -1
- package/dist/esm/session/bars.js +26 -1
- package/dist/esm/session/bars.js.map +1 -1
- package/dist/esm/session/calendar.d.ts +4 -1
- package/dist/esm/session/calendar.d.ts.map +1 -1
- package/dist/esm/session/calendar.js +23 -7
- package/dist/esm/session/calendar.js.map +1 -1
- package/dist/esm/session/context.d.ts +22 -5
- package/dist/esm/session/context.d.ts.map +1 -1
- package/dist/esm/session/context.js +86 -10
- package/dist/esm/session/context.js.map +1 -1
- package/dist/esm/str/floatingdecimal.d.ts +31 -0
- package/dist/esm/str/floatingdecimal.d.ts.map +1 -0
- package/dist/esm/str/floatingdecimal.js +272 -0
- package/dist/esm/str/floatingdecimal.js.map +1 -0
- package/dist/esm/str/index.d.ts +4 -3
- package/dist/esm/str/index.d.ts.map +1 -1
- package/dist/esm/str/index.js +6 -5
- package/dist/esm/str/index.js.map +1 -1
- package/dist/esm/str/numberformat.d.ts +6 -4
- package/dist/esm/str/numberformat.d.ts.map +1 -1
- package/dist/esm/str/numberformat.js +9 -5
- package/dist/esm/str/numberformat.js.map +1 -1
- package/dist/esm/ta/index.d.ts +64 -45
- package/dist/esm/ta/index.d.ts.map +1 -1
- package/dist/esm/ta/index.js +198 -228
- package/dist/esm/ta/index.js.map +1 -1
- package/dist/esm/ta/limits.d.ts +11 -0
- package/dist/esm/ta/limits.d.ts.map +1 -1
- package/dist/esm/ta/limits.js +27 -2
- package/dist/esm/ta/limits.js.map +1 -1
- package/dist/esm/ta/running-sum.js +1 -1
- package/dist/esm/ta/running-sum.js.map +1 -1
- package/dist/esm/ta-series.d.ts +1 -1
- package/dist/esm/ta-series.d.ts.map +1 -1
- package/dist/esm/ta-series.js +2 -1
- package/dist/esm/ta-series.js.map +1 -1
- package/dist/esm/table/index.d.ts +10 -3
- package/dist/esm/table/index.d.ts.map +1 -1
- package/dist/esm/table/index.js +23 -3
- package/dist/esm/table/index.js.map +1 -1
- package/dist/esm/time/session.d.ts +9 -0
- package/dist/esm/time/session.d.ts.map +1 -1
- package/dist/esm/time/session.js +53 -15
- package/dist/esm/time/session.js.map +1 -1
- package/dist/esm/time/timezone.d.ts +6 -1
- package/dist/esm/time/timezone.d.ts.map +1 -1
- package/dist/esm/time/timezone.js +8 -2
- package/dist/esm/time/timezone.js.map +1 -1
- package/dist/esm/types/metadata.d.ts +1 -1
- package/dist/esm/types/metadata.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/array/index.ts +11 -3
- package/src/box/index.ts +6 -6
- package/src/callsite/index.ts +6 -3
- package/src/drawing/registry.ts +30 -7
- package/src/index.ts +16 -3
- package/src/input.ts +18 -1
- package/src/label/index.ts +3 -3
- package/src/line/index.ts +20 -68
- package/src/linefill/index.ts +20 -1
- package/src/runtime/adapters/SimpleInputAdapter.ts +1 -1
- package/src/runtime/index.ts +1 -0
- package/src/runtime/inputs.ts +16 -0
- package/src/runtime/types.ts +1 -1
- package/src/script/index.ts +262 -58
- package/src/security/other.ts +269 -0
- package/src/session/bars.ts +24 -1
- package/src/session/calendar.ts +26 -7
- package/src/session/context.ts +104 -10
- package/src/str/floatingdecimal.ts +268 -0
- package/src/str/index.ts +6 -5
- package/src/str/numberformat.ts +11 -6
- package/src/ta/index.ts +200 -225
- package/src/ta/limits.ts +28 -1
- package/src/ta/running-sum.ts +1 -1
- package/src/ta-series.ts +3 -2
- package/src/table/index.ts +22 -3
- package/src/time/session.ts +58 -16
- package/src/time/timezone.ts +9 -2
- package/src/types/metadata.ts +1 -1
package/src/ta/limits.ts
CHANGED
|
@@ -37,11 +37,38 @@ export function windowError(name: string, length: number, bar?: number, max = MA
|
|
|
37
37
|
/** Throws RE10004 when the (truncated) length of a window function is above the limit. */
|
|
38
38
|
export function checkWindowLength(name: string, length: number, bar?: number, max = MAX_WINDOW_LENGTH): void {
|
|
39
39
|
const len = Math.trunc(length);
|
|
40
|
+
if (len <= 0) throw new Error(lengthError(name, len, bar));
|
|
40
41
|
if (len > max) throw new Error(windowError(name, len, bar, max));
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/** Throws RE10004 for a function of {@link SUM_FUNCTIONS} ("ta.sma", ...) with a length above 5,000. */
|
|
44
45
|
export function checkSumLength(name: string, length: number, bar?: number): void {
|
|
45
46
|
const fn = SUM_FUNCTIONS[name];
|
|
46
|
-
if (fn
|
|
47
|
+
if (fn === undefined) return;
|
|
48
|
+
// RE10001 names 'bb' itself, RE10004 its inner 'sma'
|
|
49
|
+
checkPositiveLength(name === 'ta.bb' ? 'bb' : fn, length, bar);
|
|
50
|
+
checkWindowLength(fn, length, bar, MAX_SUM_LENGTH);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* PineScript's runtime error RE10001 text for a length that is 0 or negative. `name` is the function, or a call path
|
|
55
|
+
* "atr->rma" (printed 'atr'->'rma'); the value prints as a float ("0.0", "-1.0").
|
|
56
|
+
*/
|
|
57
|
+
export function lengthError(name: string, value: number, bar?: number, arg = 'length'): string {
|
|
58
|
+
const fn = name.split('->').map((part) => `'${part}'`).join('->');
|
|
59
|
+
const text = Number.isInteger(value) ? value.toFixed(1) : String(value);
|
|
60
|
+
return (
|
|
61
|
+
(bar === undefined ? '' : `Error on bar ${bar}: `) +
|
|
62
|
+
`Invalid value of the '${arg}' argument (${text}) in the ${fn} function. It must be > 0.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Throws RE10001 when the (truncated) length is 0 or negative, as PineScript does for every window function on the
|
|
68
|
+
* bar of the call (#175): `ta.sma(close, 0)` stops on bar 0, a series length that becomes 0 on the bar where it
|
|
69
|
+
* does. `ta.hma(src, 1)` stops in its inner `wma(src, 0)`. An na length is not checked here.
|
|
70
|
+
*/
|
|
71
|
+
export function checkPositiveLength(name: string, length: number, bar?: number, arg = 'length'): void {
|
|
72
|
+
const len = Math.trunc(length);
|
|
73
|
+
if (len <= 0) throw new Error(lengthError(name, len, bar, arg));
|
|
47
74
|
}
|
package/src/ta/running-sum.ts
CHANGED
|
@@ -177,8 +177,8 @@ function seriesLengths(lengths: ArrayLike<number>, count: number, name: string):
|
|
|
177
177
|
}
|
|
178
178
|
return Array.from({ length: count }, (_, i) => {
|
|
179
179
|
const len = Math.floor(lengths[i]!);
|
|
180
|
-
if (!(len >= 1)) throw new Error(`${name}: length must be at least 1 (bar ${i}: ${lengths[i]})`);
|
|
181
180
|
checkSumLength(name, len, i);
|
|
181
|
+
if (!(len >= 1)) throw new Error(`${name}: length must be at least 1 (bar ${i}: ${lengths[i]})`);
|
|
182
182
|
return len;
|
|
183
183
|
});
|
|
184
184
|
}
|
package/src/ta-series.ts
CHANGED
|
@@ -66,10 +66,11 @@ export function rma(source: Series, length: number): Series {
|
|
|
66
66
|
* @param length - Period length
|
|
67
67
|
* @returns Series with RSI values
|
|
68
68
|
*/
|
|
69
|
-
export function rsi(source: Series, length: number): Series {
|
|
69
|
+
export function rsi(source: Series, length: number | Series): Series {
|
|
70
70
|
const bars = source.bars as Bar[];
|
|
71
71
|
const sourceValues = source.toArray();
|
|
72
|
-
|
|
72
|
+
// a Series as second argument: PineScript v4 rsi(x, y) of two averages (#180)
|
|
73
|
+
const result = length instanceof Series ? taCore.rsi(sourceValues, length.toArray()) : taCore.rsi(sourceValues, length);
|
|
73
74
|
return Series.fromArray(bars, result);
|
|
74
75
|
}
|
|
75
76
|
|
package/src/table/index.ts
CHANGED
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
* - `table.cell_set_*` changes one property of a cell (a cell that was not defined is created)
|
|
13
13
|
* - a column or row outside the table is a runtime error
|
|
14
14
|
* - a deleted table is `na`: its functions do nothing
|
|
15
|
+
* - one live table per position and pane: `table.new` or `table.set_position` at the position of a live table with
|
|
16
|
+
* the same `force_overlay` deletes that table (#176, #182); a table with `force_overlay = true` and one without
|
|
17
|
+
* keep each their own position, also in an overlay script
|
|
15
18
|
*
|
|
16
19
|
* @version 6
|
|
17
20
|
*/
|
|
@@ -24,8 +27,16 @@ type HAlign = 'left' | 'center' | 'right';
|
|
|
24
27
|
type VAlign = 'top' | 'center' | 'bottom';
|
|
25
28
|
type FontFamily = 'default' | 'monospace';
|
|
26
29
|
|
|
30
|
+
/** Deletes the live tables other than `keep` at `position` with the same `force_overlay` (#176, #182). */
|
|
31
|
+
function freePosition(position: TablePosition, force_overlay: boolean, keep?: Table): void {
|
|
32
|
+
for (const old of allDrawings('table')) {
|
|
33
|
+
if (old !== keep && old.position === position && (old.force_overlay ?? false) === force_overlay) remove('table', old);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
/**
|
|
28
|
-
* Creates a new table (PineScript `table.new`).
|
|
38
|
+
* Creates a new table (PineScript `table.new`). A live table at the same position with the same `force_overlay` is
|
|
39
|
+
* deleted, as PineScript keeps one table per position and pane (#176, #182).
|
|
29
40
|
*
|
|
30
41
|
* @example
|
|
31
42
|
* ```typescript
|
|
@@ -44,6 +55,7 @@ export function new_table(
|
|
|
44
55
|
border_width?: number,
|
|
45
56
|
force_overlay?: boolean
|
|
46
57
|
): Table {
|
|
58
|
+
freePosition(position, force_overlay ?? false);
|
|
47
59
|
return register('table', {
|
|
48
60
|
position,
|
|
49
61
|
columns,
|
|
@@ -193,8 +205,15 @@ function setTable<K extends keyof Table>(field: K) {
|
|
|
193
205
|
};
|
|
194
206
|
}
|
|
195
207
|
|
|
196
|
-
/**
|
|
197
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Moves the table to another position (`position.*`). A live table at that position with the same `force_overlay` is
|
|
210
|
+
* deleted (#182).
|
|
211
|
+
*/
|
|
212
|
+
export function set_position(table_id: Table, position: TablePosition): void {
|
|
213
|
+
if (isDeleted(table_id)) return;
|
|
214
|
+
freePosition(position, table_id.force_overlay ?? false, table_id);
|
|
215
|
+
table_id.position = position;
|
|
216
|
+
}
|
|
198
217
|
/** Sets the background colour of the table. */
|
|
199
218
|
export const set_bgcolor = setTable('bgcolor');
|
|
200
219
|
/** Sets the colour of the outer frame. */
|
package/src/time/session.ts
CHANGED
|
@@ -14,11 +14,13 @@
|
|
|
14
14
|
* "0000-0000" and "24x7" are the whole day, every day
|
|
15
15
|
* - a run that ends at 0000 ("2000-0000") ends at 24:00 of the day it starts and belongs to that day
|
|
16
16
|
* ("2000-0000:6" is Friday 20:00 to midnight)
|
|
17
|
+
* - "F" after the start moves it one day before the day the run ends on ("1700F-1900:2": Sunday 17:00 to Monday
|
|
18
|
+
* 19:00; "1900-1900:3456|1700F-1900:2" is the TVC:DXY symbol session) (#173)
|
|
17
19
|
* - a string that is empty or does not start with a digit ("regular", "extended") is the symbol's own session
|
|
18
20
|
* - a malformed string ("1100 -1400", "0930-16", ":8") is an error, as the PineScript runtime error
|
|
19
21
|
*/
|
|
20
22
|
|
|
21
|
-
import { zoneOffset, zonedFields } from './timezone.js';
|
|
23
|
+
import { zoneOffset, zonedFields, zonedToUnix } from './timezone.js';
|
|
22
24
|
|
|
23
25
|
const MINUTES_PER_DAY = 1440;
|
|
24
26
|
const WEEKDAYS = [2, 3, 4, 5, 6];
|
|
@@ -29,17 +31,27 @@ export interface SessionRange {
|
|
|
29
31
|
start: number;
|
|
30
32
|
end: number;
|
|
31
33
|
days: Set<number>;
|
|
34
|
+
/** Days between the start and the day the run ends on, given by "F" after the start (absent otherwise) */
|
|
35
|
+
startDays?: number;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
function invalid(session: string): SyntaxError {
|
|
35
39
|
return new SyntaxError(`Invalid session "${session}": expected "HHMM-HHMM[,HHMM-HHMM...][:days][|...]"`);
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
function parseRanges(text: string, session: string): Array<[number, number]> {
|
|
42
|
+
function parseRanges(text: string, session: string): Array<[number, number, number?]> {
|
|
39
43
|
if (!text) return [];
|
|
40
44
|
const entries = text.split(',');
|
|
41
45
|
if (entries.length > 1 && entries[entries.length - 1] === '') entries.pop();
|
|
42
|
-
return entries.map((entry) => {
|
|
46
|
+
return entries.map((entry): [number, number, number?] => {
|
|
47
|
+
// "HHMMF-HHMM": the start is one day before the day the run ends on
|
|
48
|
+
const f = /^(\d{4})F-(\d{4})$/.exec(entry);
|
|
49
|
+
if (f) {
|
|
50
|
+
const minutes = (hhmm: string): number => Number(hhmm.slice(0, 2)) * 60 + Number(hhmm.slice(2, 4));
|
|
51
|
+
const [start, end] = [minutes(f[1]!), minutes(f[2]!)];
|
|
52
|
+
if (start >= MINUTES_PER_DAY || end >= MINUTES_PER_DAY) throw invalid(session);
|
|
53
|
+
return [start, end, 1];
|
|
54
|
+
}
|
|
43
55
|
if (!/^\d{4}-\d{4}$/.test(entry)) throw invalid(session);
|
|
44
56
|
let start = Number(entry.slice(0, 2)) * 60 + Number(entry.slice(2, 4));
|
|
45
57
|
let end = Number(entry.slice(5, 7)) * 60 + Number(entry.slice(7, 9));
|
|
@@ -60,6 +72,9 @@ function parseDays(text: string, session: string): Set<number> {
|
|
|
60
72
|
|
|
61
73
|
const parsed = new Map<string, SessionRange[] | null>();
|
|
62
74
|
|
|
75
|
+
const range = (start: number, end: number, days: Set<number>, startDays?: number): SessionRange =>
|
|
76
|
+
startDays === undefined ? { start, end, days } : { start, end, days, startDays };
|
|
77
|
+
|
|
63
78
|
/**
|
|
64
79
|
* Runs of a PineScript session string, or null for the symbol's own session (empty or not starting with a digit).
|
|
65
80
|
* @throws SyntaxError when the string is malformed (a PineScript runtime error)
|
|
@@ -74,7 +89,7 @@ export function parsePineSession(session: string): SessionRange[] | null {
|
|
|
74
89
|
} else if (spec === '24x7') {
|
|
75
90
|
out = [{ start: 0, end: 0, days: new Set(ALL_DAYS) }];
|
|
76
91
|
} else if (!/[:,|]/.test(spec)) {
|
|
77
|
-
out = parseRanges(spec, session).map(([start, end]) => (
|
|
92
|
+
out = parseRanges(spec, session).map(([start, end, startDays]) => range(start, end, new Set(ALL_DAYS), startDays));
|
|
78
93
|
} else {
|
|
79
94
|
const sections = spec.split('|');
|
|
80
95
|
if (sections.length > 1 && sections[sections.length - 1] === '') sections.pop();
|
|
@@ -99,7 +114,7 @@ export function parsePineSession(session: string): SessionRange[] | null {
|
|
|
99
114
|
items.forEach((it, i) => {
|
|
100
115
|
if (it.days === null) for (const d of WEEKDAYS) if (!claimed.has(d)) own[i]!.add(d);
|
|
101
116
|
});
|
|
102
|
-
out = items.flatMap((it, i) => (own[i]!.size ? it.ranges.map(([start, end]) => (
|
|
117
|
+
out = items.flatMap((it, i) => (own[i]!.size ? it.ranges.map(([start, end, startDays]) => range(start, end, own[i]!, startDays)) : []));
|
|
103
118
|
}
|
|
104
119
|
parsed.set(session, out);
|
|
105
120
|
return out;
|
|
@@ -147,29 +162,56 @@ function clockChangeNear(time: number, timezone: string): { at: number; before:
|
|
|
147
162
|
* `time(timeframe, session)` in a script with the chart session
|
|
148
163
|
*/
|
|
149
164
|
export function inSession(time: number, session: string, timezone: string, barMs?: number): boolean {
|
|
150
|
-
|
|
165
|
+
return sessionRun(time, session, timezone, barMs) !== null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The run of `session` that contains `time`, as [start, end) UNIX times (ms), or null outside the session; rules in
|
|
170
|
+
* {@link inSession}. PineScript aligns the intraday periods of `time(tf, session)` on the start of this run (#173).
|
|
171
|
+
*/
|
|
172
|
+
export function sessionRun(time: number, session: string, timezone: string, barMs?: number): [number, number] | null {
|
|
173
|
+
if (Number.isNaN(time)) return null;
|
|
151
174
|
const ranges = parsePineSession(session);
|
|
152
175
|
if (ranges === null) throw new Error(`Session "${session}" is the symbol session: it needs the chart session (time(timeframe, session) in a script)`);
|
|
153
176
|
const change = clockChangeNear(time, timezone);
|
|
154
177
|
const f = zonedFields(time, timezone);
|
|
155
178
|
const minute = f.hour * 60 + f.minute;
|
|
156
179
|
const nextDay = (f.dayOfWeek % 7) + 1;
|
|
157
|
-
for (const { start, end: rawEnd, days } of ranges) {
|
|
180
|
+
for (const { start, end: rawEnd, days, startDays } of ranges) {
|
|
181
|
+
if (startDays !== undefined) {
|
|
182
|
+
// "F": from `start` of `startDays` days before the end day to `end` of the end day, wall times read in the zone
|
|
183
|
+
for (let k = 0; k <= startDays + 1; k++) {
|
|
184
|
+
const endDay = Date.UTC(f.year, f.month - 1, f.day + k);
|
|
185
|
+
if (!days.has(new Date(endDay).getUTCDay() + 1)) continue;
|
|
186
|
+
const d = new Date(endDay);
|
|
187
|
+
const at = (dayShift: number, minutes: number): number =>
|
|
188
|
+
zonedToUnix(timezone, d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate() - dayShift, Math.floor(minutes / 60), minutes % 60);
|
|
189
|
+
const from = at(startDays, start);
|
|
190
|
+
const to = at(0, rawEnd);
|
|
191
|
+
if (time >= from && time < to) return [from, to];
|
|
192
|
+
}
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
158
195
|
if (start === rawEnd) {
|
|
159
196
|
// 24 hours: the run belongs to the day it ends ("0000-0000": the day itself)
|
|
160
|
-
if (days.has(start === 0 || minute < start ? f.dayOfWeek : nextDay))
|
|
197
|
+
if (days.has(start === 0 || minute < start ? f.dayOfWeek : nextDay)) {
|
|
198
|
+
const from = time - ((minute - start + MINUTES_PER_DAY) % MINUTES_PER_DAY) * MS_PER_MINUTE - f.second * 1000 - (time % 1000);
|
|
199
|
+
return [from, from + MS_PER_DAY];
|
|
200
|
+
}
|
|
161
201
|
continue;
|
|
162
202
|
}
|
|
163
203
|
// a run that ends at 0000 ends at 24:00 of the day it starts
|
|
164
204
|
const end = rawEnd === 0 ? MINUTES_PER_DAY : rawEnd;
|
|
165
205
|
const overnight = start > end;
|
|
166
206
|
if (!change) {
|
|
207
|
+
// the wall-clock minute of the bar: the run is `start` to `end` minutes of the same offset
|
|
208
|
+
const midnight = time - minute * MS_PER_MINUTE - f.second * 1000 - (time % 1000);
|
|
167
209
|
if (!overnight) {
|
|
168
|
-
if (minute >= start && minute < end && days.has(f.dayOfWeek)) return
|
|
210
|
+
if (minute >= start && minute < end && days.has(f.dayOfWeek)) return [midnight + start * MS_PER_MINUTE, midnight + end * MS_PER_MINUTE];
|
|
169
211
|
} else {
|
|
170
212
|
// the part from `start` belongs to the next day
|
|
171
|
-
if (minute >= start && days.has(nextDay)) return
|
|
172
|
-
if (minute < end && days.has(f.dayOfWeek)) return
|
|
213
|
+
if (minute >= start && days.has(nextDay)) return [midnight + start * MS_PER_MINUTE, midnight + (end + MINUTES_PER_DAY) * MS_PER_MINUTE];
|
|
214
|
+
if (minute < end && days.has(f.dayOfWeek)) return [midnight + (start - MINUTES_PER_DAY) * MS_PER_MINUTE, midnight + end * MS_PER_MINUTE];
|
|
173
215
|
}
|
|
174
216
|
continue;
|
|
175
217
|
}
|
|
@@ -182,14 +224,14 @@ export function inSession(time: number, session: string, timezone: string, barMs
|
|
|
182
224
|
const from = day * MS_PER_DAY + (overnight ? start - MINUTES_PER_DAY : start) * MS_PER_MINUTE;
|
|
183
225
|
const to = day * MS_PER_DAY + end * MS_PER_MINUTE;
|
|
184
226
|
if (to === changeWall) {
|
|
185
|
-
if (time >= from - change.after && time < to - change.after) return
|
|
227
|
+
if (time >= from - change.after && time < to - change.after) return [from - change.after, to - change.after];
|
|
186
228
|
continue;
|
|
187
229
|
}
|
|
188
|
-
if (time >= place(from) && time < place(to)) return
|
|
230
|
+
if (time >= place(from) && time < place(to)) return [place(from), place(to)];
|
|
189
231
|
// clocks going back: the second pass of the repeated hour
|
|
190
232
|
const repeated = change.at + change.after;
|
|
191
233
|
if (change.after < change.before && to < changeWall && to > repeated) {
|
|
192
|
-
if (time >= Math.max(from, repeated) - change.after && time < to - change.after) return
|
|
234
|
+
if (time >= Math.max(from, repeated) - change.after && time < to - change.after) return [place(from), to - change.after];
|
|
193
235
|
}
|
|
194
236
|
}
|
|
195
237
|
// clocks going back, the day before: a run with an end on the wall time of the change is also true one change
|
|
@@ -201,9 +243,9 @@ export function inSession(time: number, session: string, timezone: string, barMs
|
|
|
201
243
|
const from = day * MS_PER_DAY + (overnight ? start - MINUTES_PER_DAY : start) * MS_PER_MINUTE - change.before;
|
|
202
244
|
const to = day * MS_PER_DAY + end * MS_PER_MINUTE - change.before;
|
|
203
245
|
const shift = change.before - change.after;
|
|
204
|
-
if (time >= from + shift && time < to + shift && time >= to + barMs) return
|
|
246
|
+
if (time >= from + shift && time < to + shift && time >= to + barMs) return [from + shift, to + shift];
|
|
205
247
|
}
|
|
206
248
|
}
|
|
207
249
|
}
|
|
208
|
-
return
|
|
250
|
+
return null;
|
|
209
251
|
}
|
package/src/time/timezone.ts
CHANGED
|
@@ -177,6 +177,11 @@ export function civilToUnix(year: number, month: number, day: number, hour = 0,
|
|
|
177
177
|
return days * MS_PER_DAY + Date.UTC(1970, 0, 1, hour, minute, second);
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
/**
|
|
181
|
+
* UNIX time (ms) of a wall-clock date in `timezone`. A wall time skipped by a clock change is read with the offset
|
|
182
|
+
* before the change (02:30 on a 02:00 -> 03:00 night is 03:30 after it); a wall time that occurs twice gives the
|
|
183
|
+
* later instant, or the earlier one with `earlier`.
|
|
184
|
+
*/
|
|
180
185
|
export function zonedToUnix(
|
|
181
186
|
timezone: string,
|
|
182
187
|
year: number,
|
|
@@ -184,7 +189,8 @@ export function zonedToUnix(
|
|
|
184
189
|
day: number,
|
|
185
190
|
hour: number = 0,
|
|
186
191
|
minute: number = 0,
|
|
187
|
-
second: number = 0
|
|
192
|
+
second: number = 0,
|
|
193
|
+
earlier: boolean = false
|
|
188
194
|
): number {
|
|
189
195
|
const zone = resolveZone(timezone);
|
|
190
196
|
const local = civilToUnix(year, month, day, hour, minute, second);
|
|
@@ -195,6 +201,7 @@ export function zonedToUnix(
|
|
|
195
201
|
const before = offsetAt(zone, local - MS_PER_DAY);
|
|
196
202
|
const after = offsetAt(zone, local + MS_PER_DAY);
|
|
197
203
|
const candidates = [local - before, local - after].filter((t) => local - offsetAt(zone, t) === t);
|
|
198
|
-
|
|
204
|
+
// a wall time that occurs twice: the later instant, or the earlier one with `earlier`
|
|
205
|
+
if (candidates.length) return earlier ? Math.min(...candidates) : Math.max(...candidates);
|
|
199
206
|
return local - before;
|
|
200
207
|
}
|
package/src/types/metadata.ts
CHANGED
|
@@ -86,7 +86,7 @@ export interface FillOptions {
|
|
|
86
86
|
/**
|
|
87
87
|
* Input parameter type
|
|
88
88
|
*/
|
|
89
|
-
export type InputType = 'int' | 'float' | 'bool' | 'string' | 'source' | 'color' | 'timeframe' | 'session' | 'time';
|
|
89
|
+
export type InputType = 'int' | 'float' | 'bool' | 'string' | 'source' | 'color' | 'timeframe' | 'session' | 'time' | 'symbol';
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* Input parameter metadata
|