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.
Files changed (256) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/array/index.d.ts +93 -50
  3. package/dist/cjs/array/index.d.ts.map +1 -1
  4. package/dist/cjs/array/index.js +239 -78
  5. package/dist/cjs/array/index.js.map +3 -3
  6. package/dist/cjs/callsite/index.d.ts +81 -2
  7. package/dist/cjs/callsite/index.d.ts.map +1 -1
  8. package/dist/cjs/callsite/index.js +62 -1
  9. package/dist/cjs/callsite/index.js.map +2 -2
  10. package/dist/cjs/color/index.d.ts +7 -3
  11. package/dist/cjs/color/index.d.ts.map +1 -1
  12. package/dist/cjs/color/index.js +6 -1
  13. package/dist/cjs/color/index.js.map +2 -2
  14. package/dist/cjs/drawing/bar-time.d.ts +24 -0
  15. package/dist/cjs/drawing/bar-time.d.ts.map +1 -0
  16. package/dist/cjs/drawing/bar-time.js +47 -0
  17. package/dist/cjs/drawing/bar-time.js.map +7 -0
  18. package/dist/cjs/drawing/output.d.ts +32 -0
  19. package/dist/cjs/drawing/output.d.ts.map +1 -0
  20. package/dist/cjs/drawing/output.js +184 -0
  21. package/dist/cjs/drawing/output.js.map +7 -0
  22. package/dist/cjs/drawing/registry.d.ts +5 -4
  23. package/dist/cjs/drawing/registry.d.ts.map +1 -1
  24. package/dist/cjs/drawing/registry.js +11 -2
  25. package/dist/cjs/drawing/registry.js.map +2 -2
  26. package/dist/cjs/index.d.ts +6 -3
  27. package/dist/cjs/index.d.ts.map +1 -1
  28. package/dist/cjs/index.js +9 -2
  29. package/dist/cjs/index.js.map +2 -2
  30. package/dist/cjs/label/index.d.ts.map +1 -1
  31. package/dist/cjs/label/index.js +2 -1
  32. package/dist/cjs/label/index.js.map +2 -2
  33. package/dist/cjs/linefill/index.d.ts.map +1 -1
  34. package/dist/cjs/linefill/index.js +1 -0
  35. package/dist/cjs/linefill/index.js.map +2 -2
  36. package/dist/cjs/math/index.d.ts +76 -21
  37. package/dist/cjs/math/index.d.ts.map +1 -1
  38. package/dist/cjs/math/index.js +155 -41
  39. package/dist/cjs/math/index.js.map +3 -3
  40. package/dist/cjs/matrix/index.d.ts.map +1 -1
  41. package/dist/cjs/matrix/index.js +42 -36
  42. package/dist/cjs/matrix/index.js.map +2 -2
  43. package/dist/cjs/polyline/index.d.ts +3 -5
  44. package/dist/cjs/polyline/index.d.ts.map +1 -1
  45. package/dist/cjs/polyline/index.js +0 -5
  46. package/dist/cjs/polyline/index.js.map +2 -2
  47. package/dist/cjs/runtime/series.d.ts +9 -0
  48. package/dist/cjs/runtime/series.d.ts.map +1 -1
  49. package/dist/cjs/runtime/series.js +16 -2
  50. package/dist/cjs/runtime/series.js.map +2 -2
  51. package/dist/cjs/runtime/types.d.ts +22 -0
  52. package/dist/cjs/runtime/types.d.ts.map +1 -1
  53. package/dist/cjs/runtime/types.js.map +1 -1
  54. package/dist/cjs/script/index.d.ts +97 -36
  55. package/dist/cjs/script/index.d.ts.map +1 -1
  56. package/dist/cjs/script/index.js +173 -26
  57. package/dist/cjs/script/index.js.map +3 -3
  58. package/dist/cjs/session/calendar.d.ts.map +1 -1
  59. package/dist/cjs/session/calendar.js +15 -2
  60. package/dist/cjs/session/calendar.js.map +2 -2
  61. package/dist/cjs/str/index.d.ts +50 -38
  62. package/dist/cjs/str/index.d.ts.map +1 -1
  63. package/dist/cjs/str/index.js +43 -15
  64. package/dist/cjs/str/index.js.map +2 -2
  65. package/dist/cjs/str/messageformat.d.ts +2 -1
  66. package/dist/cjs/str/messageformat.d.ts.map +1 -1
  67. package/dist/cjs/str/messageformat.js +4 -1
  68. package/dist/cjs/str/messageformat.js.map +2 -2
  69. package/dist/cjs/str/numberformat.d.ts +4 -0
  70. package/dist/cjs/str/numberformat.d.ts.map +1 -1
  71. package/dist/cjs/str/numberformat.js +26 -4
  72. package/dist/cjs/str/numberformat.js.map +3 -3
  73. package/dist/cjs/ta/index.d.ts +86 -43
  74. package/dist/cjs/ta/index.d.ts.map +1 -1
  75. package/dist/cjs/ta/index.js +173 -267
  76. package/dist/cjs/ta/index.js.map +3 -3
  77. package/dist/cjs/ta/running-sum.d.ts +54 -2
  78. package/dist/cjs/ta/running-sum.d.ts.map +1 -1
  79. package/dist/cjs/ta/running-sum.js +132 -52
  80. package/dist/cjs/ta/running-sum.js.map +2 -2
  81. package/dist/cjs/ta-series.d.ts +4 -4
  82. package/dist/cjs/ta-series.d.ts.map +1 -1
  83. package/dist/cjs/ta-series.js +4 -3
  84. package/dist/cjs/ta-series.js.map +2 -2
  85. package/dist/cjs/table/index.d.ts +87 -0
  86. package/dist/cjs/table/index.d.ts.map +1 -0
  87. package/dist/cjs/table/index.js +150 -0
  88. package/dist/cjs/table/index.js.map +7 -0
  89. package/dist/cjs/text/index.d.ts +17 -2
  90. package/dist/cjs/text/index.d.ts.map +1 -1
  91. package/dist/cjs/text/index.js +15 -1
  92. package/dist/cjs/text/index.js.map +2 -2
  93. package/dist/cjs/time/datestring.d.ts +1 -0
  94. package/dist/cjs/time/datestring.d.ts.map +1 -1
  95. package/dist/cjs/time/datestring.js +7 -0
  96. package/dist/cjs/time/datestring.js.map +2 -2
  97. package/dist/cjs/time/index.d.ts.map +1 -1
  98. package/dist/cjs/time/index.js +2 -1
  99. package/dist/cjs/time/index.js.map +2 -2
  100. package/dist/cjs/time/session.d.ts +28 -9
  101. package/dist/cjs/time/session.d.ts.map +1 -1
  102. package/dist/cjs/time/session.js +74 -28
  103. package/dist/cjs/time/session.js.map +2 -2
  104. package/dist/cjs/time/timezone.d.ts +5 -0
  105. package/dist/cjs/time/timezone.d.ts.map +1 -1
  106. package/dist/cjs/time/timezone.js +15 -1
  107. package/dist/cjs/time/timezone.js.map +2 -2
  108. package/dist/cjs/types/index.d.ts +30 -1
  109. package/dist/cjs/types/index.d.ts.map +1 -1
  110. package/dist/cjs/types/index.js +12 -0
  111. package/dist/cjs/types/index.js.map +2 -2
  112. package/dist/cjs/types/metadata.d.ts +258 -26
  113. package/dist/cjs/types/metadata.d.ts.map +1 -1
  114. package/dist/cjs/types/metadata.js.map +1 -1
  115. package/dist/esm/array/index.d.ts +93 -50
  116. package/dist/esm/array/index.d.ts.map +1 -1
  117. package/dist/esm/array/index.js +383 -133
  118. package/dist/esm/array/index.js.map +1 -1
  119. package/dist/esm/callsite/index.d.ts +81 -2
  120. package/dist/esm/callsite/index.d.ts.map +1 -1
  121. package/dist/esm/callsite/index.js +145 -3
  122. package/dist/esm/callsite/index.js.map +1 -1
  123. package/dist/esm/color/index.d.ts +7 -3
  124. package/dist/esm/color/index.d.ts.map +1 -1
  125. package/dist/esm/color/index.js +15 -3
  126. package/dist/esm/color/index.js.map +1 -1
  127. package/dist/esm/drawing/bar-time.d.ts +24 -0
  128. package/dist/esm/drawing/bar-time.d.ts.map +1 -0
  129. package/dist/esm/drawing/bar-time.js +44 -0
  130. package/dist/esm/drawing/bar-time.js.map +1 -0
  131. package/dist/esm/drawing/output.d.ts +32 -0
  132. package/dist/esm/drawing/output.d.ts.map +1 -0
  133. package/dist/esm/drawing/output.js +190 -0
  134. package/dist/esm/drawing/output.js.map +1 -0
  135. package/dist/esm/drawing/registry.d.ts +5 -4
  136. package/dist/esm/drawing/registry.d.ts.map +1 -1
  137. package/dist/esm/drawing/registry.js +12 -2
  138. package/dist/esm/drawing/registry.js.map +1 -1
  139. package/dist/esm/index.d.ts +6 -3
  140. package/dist/esm/index.d.ts.map +1 -1
  141. package/dist/esm/index.js +7 -3
  142. package/dist/esm/index.js.map +1 -1
  143. package/dist/esm/label/index.d.ts.map +1 -1
  144. package/dist/esm/label/index.js +2 -1
  145. package/dist/esm/label/index.js.map +1 -1
  146. package/dist/esm/linefill/index.d.ts.map +1 -1
  147. package/dist/esm/linefill/index.js +3 -0
  148. package/dist/esm/linefill/index.js.map +1 -1
  149. package/dist/esm/math/index.d.ts +76 -21
  150. package/dist/esm/math/index.d.ts.map +1 -1
  151. package/dist/esm/math/index.js +227 -61
  152. package/dist/esm/math/index.js.map +1 -1
  153. package/dist/esm/matrix/index.d.ts.map +1 -1
  154. package/dist/esm/matrix/index.js +57 -43
  155. package/dist/esm/matrix/index.js.map +1 -1
  156. package/dist/esm/polyline/index.d.ts +3 -5
  157. package/dist/esm/polyline/index.d.ts.map +1 -1
  158. package/dist/esm/polyline/index.js +3 -9
  159. package/dist/esm/polyline/index.js.map +1 -1
  160. package/dist/esm/runtime/series.d.ts +9 -0
  161. package/dist/esm/runtime/series.d.ts.map +1 -1
  162. package/dist/esm/runtime/series.js +18 -1
  163. package/dist/esm/runtime/series.js.map +1 -1
  164. package/dist/esm/runtime/types.d.ts +22 -0
  165. package/dist/esm/runtime/types.d.ts.map +1 -1
  166. package/dist/esm/script/index.d.ts +97 -36
  167. package/dist/esm/script/index.d.ts.map +1 -1
  168. package/dist/esm/script/index.js +211 -22
  169. package/dist/esm/script/index.js.map +1 -1
  170. package/dist/esm/session/calendar.d.ts.map +1 -1
  171. package/dist/esm/session/calendar.js +22 -2
  172. package/dist/esm/session/calendar.js.map +1 -1
  173. package/dist/esm/str/index.d.ts +50 -38
  174. package/dist/esm/str/index.d.ts.map +1 -1
  175. package/dist/esm/str/index.js +103 -52
  176. package/dist/esm/str/index.js.map +1 -1
  177. package/dist/esm/str/messageformat.d.ts +2 -1
  178. package/dist/esm/str/messageformat.d.ts.map +1 -1
  179. package/dist/esm/str/messageformat.js +7 -2
  180. package/dist/esm/str/messageformat.js.map +1 -1
  181. package/dist/esm/str/numberformat.d.ts +4 -0
  182. package/dist/esm/str/numberformat.d.ts.map +1 -1
  183. package/dist/esm/str/numberformat.js +38 -4
  184. package/dist/esm/str/numberformat.js.map +1 -1
  185. package/dist/esm/ta/index.d.ts +86 -43
  186. package/dist/esm/ta/index.d.ts.map +1 -1
  187. package/dist/esm/ta/index.js +296 -348
  188. package/dist/esm/ta/index.js.map +1 -1
  189. package/dist/esm/ta/running-sum.d.ts +54 -2
  190. package/dist/esm/ta/running-sum.d.ts.map +1 -1
  191. package/dist/esm/ta/running-sum.js +166 -71
  192. package/dist/esm/ta/running-sum.js.map +1 -1
  193. package/dist/esm/ta-series.d.ts +4 -4
  194. package/dist/esm/ta-series.d.ts.map +1 -1
  195. package/dist/esm/ta-series.js +5 -4
  196. package/dist/esm/ta-series.js.map +1 -1
  197. package/dist/esm/table/index.d.ts +87 -0
  198. package/dist/esm/table/index.d.ts.map +1 -0
  199. package/dist/esm/table/index.js +169 -0
  200. package/dist/esm/table/index.js.map +1 -0
  201. package/dist/esm/text/index.d.ts +17 -2
  202. package/dist/esm/text/index.d.ts.map +1 -1
  203. package/dist/esm/text/index.js +17 -2
  204. package/dist/esm/text/index.js.map +1 -1
  205. package/dist/esm/time/datestring.d.ts +1 -0
  206. package/dist/esm/time/datestring.d.ts.map +1 -1
  207. package/dist/esm/time/datestring.js +12 -0
  208. package/dist/esm/time/datestring.js.map +1 -1
  209. package/dist/esm/time/index.d.ts.map +1 -1
  210. package/dist/esm/time/index.js +3 -1
  211. package/dist/esm/time/index.js.map +1 -1
  212. package/dist/esm/time/session.d.ts +28 -9
  213. package/dist/esm/time/session.d.ts.map +1 -1
  214. package/dist/esm/time/session.js +113 -42
  215. package/dist/esm/time/session.js.map +1 -1
  216. package/dist/esm/time/timezone.d.ts +5 -0
  217. package/dist/esm/time/timezone.d.ts.map +1 -1
  218. package/dist/esm/time/timezone.js +21 -1
  219. package/dist/esm/time/timezone.js.map +1 -1
  220. package/dist/esm/types/index.d.ts +30 -1
  221. package/dist/esm/types/index.d.ts.map +1 -1
  222. package/dist/esm/types/index.js +14 -0
  223. package/dist/esm/types/index.js.map +1 -1
  224. package/dist/esm/types/metadata.d.ts +258 -26
  225. package/dist/esm/types/metadata.d.ts.map +1 -1
  226. package/package.json +1 -1
  227. package/src/array/index.ts +364 -150
  228. package/src/callsite/index.ts +147 -3
  229. package/src/color/index.ts +15 -5
  230. package/src/drawing/bar-time.ts +42 -0
  231. package/src/drawing/output.ts +214 -0
  232. package/src/drawing/registry.ts +16 -6
  233. package/src/index.ts +9 -3
  234. package/src/label/index.ts +2 -1
  235. package/src/linefill/index.ts +2 -0
  236. package/src/math/index.ts +256 -74
  237. package/src/matrix/index.ts +52 -52
  238. package/src/polyline/index.ts +3 -12
  239. package/src/runtime/series.ts +22 -1
  240. package/src/runtime/types.ts +23 -0
  241. package/src/script/index.ts +307 -25
  242. package/src/session/calendar.ts +22 -2
  243. package/src/str/index.ts +95 -51
  244. package/src/str/messageformat.ts +7 -2
  245. package/src/str/numberformat.ts +39 -4
  246. package/src/ta/index.ts +306 -386
  247. package/src/ta/running-sum.ts +187 -74
  248. package/src/ta-series.ts +8 -7
  249. package/src/table/index.ts +221 -0
  250. package/src/text/index.ts +18 -2
  251. package/src/time/datestring.ts +10 -0
  252. package/src/time/index.ts +3 -1
  253. package/src/time/session.ts +97 -47
  254. package/src/time/timezone.ts +21 -1
  255. package/src/types/index.ts +32 -1
  256. package/src/types/metadata.ts +325 -26
@@ -1,78 +1,128 @@
1
1
  /**
2
- * Session strings ("0930-1600", "0930-1600:23456", "1000-1100,1400-1500:23456", "24x7").
2
+ * PineScript session strings ("0930-1600", "0930-1600:23456", "1000-1100,1400-1500:23456", "0930-1600|1000-1300:7",
3
+ * "24x7").
3
4
  *
4
- * Format `<periods>[:<days>]`:
5
- * - a period is "HHmm-HHmm"; several periods are separated by commas
6
- * - days are digits 1 (Sunday) to 7 (Saturday). Without days, a single period covers every
7
- * day, and several periods cover Monday to Friday ("23456"), as in PineScript
8
- * - an end before the start is an overnight period that ends on the next day, and it
9
- * belongs to the day it ends: "1700-1700:23456" starts Sunday 17:00 and covers Monday
10
- * - "0000-0000" and "24x7" are the 24-hour session of every day
5
+ * Grammar (measured on PineScript): `range[,range...][:days]`, several sections joined by "|".
6
+ * - a range is exactly "HHMM-HHMM"; hours and minutes are minute counts taken modulo one day ("0930-2500" runs
7
+ * 09:30 to 01:00, "0960-1600" 10:00 to 16:00); a start of 24:00 or later must open a run of at most a day
8
+ * - days are digits 1 (Sunday) to 7 (Saturday), the day each run ends on
9
+ * - a lone range without days runs every day; otherwise a section without days is the default section, which runs
10
+ * on the weekdays (2-6) no other section names, and a later section naming a day replaces the earlier ones on that
11
+ * day ("1100-1400|1200-1300:7": 11:00-14:00 Monday to Friday, 12:00-13:00 Saturday)
12
+ * - a trailing ",", ":" or "|" ends its list ("1100-1400," is "1100-1400" Monday to Friday)
13
+ * - an end before the start is an overnight run that belongs to the day it ends; equal ends are 24 hours;
14
+ * "0000-0000" and "24x7" are the whole day, every day
15
+ * - a string that is empty or does not start with a digit ("regular", "extended") is the symbol's own session
16
+ * - a malformed string ("1100 -1400", "0930-16", ":8") is an error, as the PineScript runtime error
11
17
  */
12
18
 
13
19
  import { zonedFields } from './timezone.js';
14
20
 
15
- interface Period {
16
- /** Minutes after midnight, 0-1439 */
21
+ const MINUTES_PER_DAY = 1440;
22
+ const WEEKDAYS = [2, 3, 4, 5, 6];
23
+ const ALL_DAYS = [1, 2, 3, 4, 5, 6, 7];
24
+
25
+ /** A run of a session: minutes after midnight (0-1439) and the days it ends on (1 = Sunday ... 7 = Saturday). */
26
+ export interface SessionRange {
17
27
  start: number;
18
28
  end: number;
29
+ days: Set<number>;
19
30
  }
20
31
 
21
- interface ParsedSession {
22
- periods: Period[];
23
- /** Included days, indexed 1 (Sunday) to 7 (Saturday) */
24
- days: boolean[];
32
+ function invalid(session: string): SyntaxError {
33
+ return new SyntaxError(`Invalid session "${session}": expected "HHMM-HHMM[,HHMM-HHMM...][:days][|...]"`);
25
34
  }
26
35
 
27
- const PERIOD = /^(\d{2})(\d{2})-(\d{2})(\d{2})$/;
28
-
29
- const sessions = new Map<string, ParsedSession>();
30
-
31
- function parseSession(session: string): ParsedSession {
32
- let parsed = sessions.get(session);
33
- if (parsed) return parsed;
34
-
35
- const invalid = () => new SyntaxError(`Invalid session "${session}": expected "HHmm-HHmm[,HHmm-HHmm...][:days]"`);
36
- const text = session.trim() === '24x7' ? '0000-0000' : session.replace(/\s+/g, '');
37
- const [periodText = '', dayText, extra] = text.split(':');
38
- if (extra !== undefined || periodText === '') throw invalid();
39
-
40
- const periods = periodText.split(',').map((p) => {
41
- const m = PERIOD.exec(p);
42
- if (!m) throw invalid();
43
- const [h1, m1, h2, m2] = [m[1], m[2], m[3], m[4]].map(Number) as [number, number, number, number];
44
- if (h1 > 23 || h2 > 23 || m1 > 59 || m2 > 59) throw invalid();
45
- return { start: h1 * 60 + m1, end: h2 * 60 + m2 };
36
+ function parseRanges(text: string, session: string): Array<[number, number]> {
37
+ if (!text) return [];
38
+ const entries = text.split(',');
39
+ if (entries.length > 1 && entries[entries.length - 1] === '') entries.pop();
40
+ return entries.map((entry) => {
41
+ if (!/^\d{4}-\d{4}$/.test(entry)) throw invalid(session);
42
+ let start = Number(entry.slice(0, 2)) * 60 + Number(entry.slice(2, 4));
43
+ let end = Number(entry.slice(5, 7)) * 60 + Number(entry.slice(7, 9));
44
+ if (start >= MINUTES_PER_DAY) {
45
+ const endOfRun = end || MINUTES_PER_DAY;
46
+ if (!(start - MINUTES_PER_DAY < endOfRun && endOfRun <= start && start < 2 * MINUTES_PER_DAY)) throw invalid(session);
47
+ }
48
+ start %= MINUTES_PER_DAY;
49
+ end %= MINUTES_PER_DAY;
50
+ return [start, end];
46
51
  });
52
+ }
47
53
 
48
- const dayDigits = dayText ?? (periods.length > 1 ? '23456' : '1234567');
49
- if (!/^[1-7]+$/.test(dayDigits)) throw invalid();
50
- const days = new Array<boolean>(8).fill(false);
51
- for (const d of dayDigits) days[Number(d)] = true;
54
+ function parseDays(text: string, session: string): Set<number> {
55
+ if (!/^[1-7]+$/.test(text)) throw invalid(session);
56
+ return new Set([...text].map(Number));
57
+ }
58
+
59
+ const parsed = new Map<string, SessionRange[] | null>();
52
60
 
53
- parsed = { periods, days };
54
- sessions.set(session, parsed);
55
- return parsed;
61
+ /**
62
+ * Runs of a PineScript session string, or null for the symbol's own session (empty or not starting with a digit).
63
+ * @throws SyntaxError when the string is malformed (a PineScript runtime error)
64
+ */
65
+ export function parsePineSession(session: string): SessionRange[] | null {
66
+ const cached = parsed.get(session);
67
+ if (cached !== undefined) return cached;
68
+ const spec = session.trim();
69
+ let out: SessionRange[] | null;
70
+ if (!spec || !/^\d/.test(spec)) {
71
+ out = null;
72
+ } else if (spec === '24x7') {
73
+ out = [{ start: 0, end: 0, days: new Set(ALL_DAYS) }];
74
+ } else if (!/[:,|]/.test(spec)) {
75
+ out = parseRanges(spec, session).map(([start, end]) => ({ start, end, days: new Set(ALL_DAYS) }));
76
+ } else {
77
+ const sections = spec.split('|');
78
+ if (sections.length > 1 && sections[sections.length - 1] === '') sections.pop();
79
+ const items = sections.map((section) => {
80
+ const parts = section.split(':');
81
+ if (parts.length > 2) throw invalid(session);
82
+ const ranges = parseRanges(parts[0]!, session);
83
+ const days = parts.length === 1 || parts[1] === '' ? null : parseDays(parts[1]!, session);
84
+ return { ranges, days };
85
+ });
86
+ if (items.filter((it) => it.days === null).length > 1) throw invalid(session);
87
+ // a later section naming a day replaces the earlier ones on that day; the default section takes the weekdays
88
+ // no other section names
89
+ const own: Set<number>[] = items.map(() => new Set<number>());
90
+ const claimed = new Set<number>();
91
+ for (let i = items.length - 1; i >= 0; i--) {
92
+ const days = items[i]!.days;
93
+ if (days === null) continue;
94
+ for (const d of days) if (!claimed.has(d)) own[i]!.add(d);
95
+ for (const d of days) claimed.add(d);
96
+ }
97
+ items.forEach((it, i) => {
98
+ if (it.days === null) for (const d of WEEKDAYS) if (!claimed.has(d)) own[i]!.add(d);
99
+ });
100
+ out = items.flatMap((it, i) => (own[i]!.size ? it.ranges.map(([start, end]) => ({ start, end, days: own[i]! })) : []));
101
+ }
102
+ parsed.set(session, out);
103
+ return out;
56
104
  }
57
105
 
58
106
  /**
59
107
  * True when `time` falls inside `session`, with the session hours read in `timezone`.
60
- * A period includes its start minute and excludes its end minute.
108
+ * A run includes its start minute and excludes its end minute.
109
+ * @throws Error for the symbol's own session ("regular", "extended", ""), which this function does not know: use
110
+ * `time(timeframe, session)` in a script with the chart session
61
111
  */
62
112
  export function inSession(time: number, session: string, timezone: string): boolean {
63
113
  if (Number.isNaN(time)) return false;
64
- const { periods, days } = parseSession(session);
114
+ const ranges = parsePineSession(session);
115
+ if (ranges === null) throw new Error(`Session "${session}" is the symbol session: it needs the chart session (time(timeframe, session) in a script)`);
65
116
  const f = zonedFields(time, timezone);
66
117
  const minute = f.hour * 60 + f.minute;
67
118
  const nextDay = (f.dayOfWeek % 7) + 1;
68
-
69
- for (const { start, end } of periods) {
119
+ for (const { start, end, days } of ranges) {
70
120
  if (start < end || (start === 0 && end === 0)) {
71
- if (minute >= start && (minute < end || end === 0) && days[f.dayOfWeek]) return true;
121
+ if (minute >= start && (minute < end || end === 0) && days.has(f.dayOfWeek)) return true;
72
122
  } else {
73
123
  // Overnight (24 hours when start === end): the part from `start` belongs to the next day.
74
- if (minute >= start && days[nextDay]) return true;
75
- if (minute < end && days[f.dayOfWeek]) return true;
124
+ if (minute >= start && days.has(nextDay)) return true;
125
+ if (minute < end && days.has(f.dayOfWeek)) return true;
76
126
  }
77
127
  }
78
128
  return false;
@@ -157,6 +157,26 @@ export function zonedFields(time: number, timezone: string): ZonedFields {
157
157
  * resolve to the later instant, and wall-clock times that do not exist (start of
158
158
  * daylight saving time) are shifted forward by the length of the gap.
159
159
  */
160
+ /**
161
+ * UNIX time (ms) of a UTC wall-clock date: Gregorian calendar from 1582-10-15, Julian calendar before it (as
162
+ * PineScript: 1582-10-05 is 1582-10-15). Out-of-range fields roll over like `Date.UTC`.
163
+ */
164
+ export function civilToUnix(year: number, month: number, day: number, hour = 0, minute = 0, second = 0): number {
165
+ const gregorian = Date.UTC(year, month - 1, day, hour, minute, second);
166
+ // normalized year / month / day of the Gregorian reading decides the calendar
167
+ const d = new Date(Date.UTC(year, month - 1, 1));
168
+ const y = d.getUTCFullYear();
169
+ const m = d.getUTCMonth() + 1;
170
+ if (y > 1582 || (y === 1582 && (m > 10 || (m === 10 && day >= 15)))) return gregorian;
171
+ // Julian day number of y-m-1, then the day offset and the time of day
172
+ const a = Math.floor((14 - m) / 12);
173
+ const yy = y + 4800 - a;
174
+ const mm = m + 12 * a - 3;
175
+ const jdn = 1 + Math.floor((153 * mm + 2) / 5) + 365 * yy + Math.floor(yy / 4) - 32083;
176
+ const days = jdn - 2440588 + (day - 1);
177
+ return days * MS_PER_DAY + Date.UTC(1970, 0, 1, hour, minute, second);
178
+ }
179
+
160
180
  export function zonedToUnix(
161
181
  timezone: string,
162
182
  year: number,
@@ -167,7 +187,7 @@ export function zonedToUnix(
167
187
  second: number = 0
168
188
  ): number {
169
189
  const zone = resolveZone(timezone);
170
- const local = Date.UTC(year, month - 1, day, hour, minute, second);
190
+ const local = civilToUnix(year, month, day, hour, minute, second);
171
191
  if (zone.kind === 'fixed') return local - zone.offsetMs;
172
192
 
173
193
  // At most one offset transition happens within a day of `local`: the offsets
@@ -217,8 +217,22 @@ export interface Table {
217
217
  border_width?: number;
218
218
  /** Table background color */
219
219
  bgcolor?: color;
220
- /** Cell data (internal storage) */
220
+ /** Display on the main chart pane (PineScript force_overlay) */
221
+ force_overlay?: boolean;
222
+ /** Cells by "column,row" */
221
223
  cells?: Map<string, TableCell>;
224
+ /** Merged cell ranges (PineScript table.merge_cells) */
225
+ merges?: TableMerge[];
226
+ }
227
+
228
+ /**
229
+ * Merged cell range of a table
230
+ */
231
+ export interface TableMerge {
232
+ start_column: number;
233
+ start_row: number;
234
+ end_column: number;
235
+ end_row: number;
222
236
  }
223
237
 
224
238
  /**
@@ -245,6 +259,8 @@ export interface TableCell {
245
259
  tooltip?: string;
246
260
  /** Text font family */
247
261
  text_font_family?: 'default' | 'monospace';
262
+ /** Text formatting flags: 0 none, 1 bold, 2 italic, 3 bold + italic (PineScript text.format_*) */
263
+ text_formatting?: number;
248
264
  }
249
265
 
250
266
  // Series types for drawing objects
@@ -349,6 +365,21 @@ export const xloc = {
349
365
  bar_time: 'bar_time' as const,
350
366
  };
351
367
 
368
+ /**
369
+ * Table position constants (PineScript `position.*`), for `table.new` and `table.set_position`
370
+ */
371
+ export const position = {
372
+ top_left: 'top_left' as const,
373
+ top_center: 'top_center' as const,
374
+ top_right: 'top_right' as const,
375
+ middle_left: 'middle_left' as const,
376
+ middle_center: 'middle_center' as const,
377
+ middle_right: 'middle_right' as const,
378
+ bottom_left: 'bottom_left' as const,
379
+ bottom_center: 'bottom_center' as const,
380
+ bottom_right: 'bottom_right' as const,
381
+ };
382
+
352
383
  /**
353
384
  * ChartPoint interface - Represents a point on the chart
354
385
  * @remarks
@@ -223,14 +223,19 @@ export interface FillGradient {
223
223
  }
224
224
 
225
225
  /**
226
- * Marker location on the chart (PineScript plotshape/plotchar locations)
226
+ * Marker location on the chart (PineScript plotshape/plotchar `location.*`)
227
227
  */
228
228
  export type MarkerLocation = 'abovebar' | 'belowbar' | 'top' | 'bottom' | 'absolute';
229
229
 
230
+ /**
231
+ * PineScript size constant (`size.auto`, `size.tiny`, ...)
232
+ */
233
+ export type PineSize = 'auto' | 'tiny' | 'small' | 'normal' | 'large' | 'huge';
234
+
230
235
  /**
231
236
  * Marker size (PineScript size.* constants)
232
237
  */
233
- export type MarkerSize = 'auto' | 'tiny' | 'small' | 'normal' | 'large' | 'huge';
238
+ export type MarkerSize = PineSize;
234
239
 
235
240
  /**
236
241
  * Shape style (PineScript shape.* constants)
@@ -250,31 +255,76 @@ export type ShapeStyle =
250
255
  | 'diamond';
251
256
 
252
257
  /**
253
- * One marker emitted by plotshape()/plotchar() on a bar where the condition held
258
+ * Position of a marker:
259
+ * - aboveBar / belowBar / inBar: next to the bar (PineScript location.abovebar / location.belowbar)
260
+ * - atPriceTop / atPriceBottom / atPriceMiddle: at `price` (PineScript location.absolute), with the meaning of the
261
+ * lightweight-charts price positions: atPriceTop has the shape above the price, its bottom (the tip of a label) on
262
+ * the price (shape.labeldown); atPriceBottom has the shape below the price, its top on the price (shape.labelup);
263
+ * atPriceMiddle centres the shape on the price (other shapes)
264
+ * - top / bottom: at the top / bottom edge of the pane of the indicator (PineScript location.top / location.bottom),
265
+ * the price pane for an overlay indicator or with forceOverlay
266
+ */
267
+ export type MarkerPosition =
268
+ | 'aboveBar'
269
+ | 'belowBar'
270
+ | 'inBar'
271
+ | 'atPriceTop'
272
+ | 'atPriceBottom'
273
+ | 'atPriceMiddle'
274
+ | 'top'
275
+ | 'bottom';
276
+
277
+ /**
278
+ * Marker shape (PineScript shape.* in the lightweight-charts spelling)
279
+ */
280
+ export type MarkerShape =
281
+ | 'arrowUp'
282
+ | 'arrowDown'
283
+ | 'circle'
284
+ | 'square'
285
+ | 'labelUp'
286
+ | 'labelDown'
287
+ | 'triangleUp'
288
+ | 'triangleDown'
289
+ | 'cross'
290
+ | 'xcross'
291
+ | 'diamond'
292
+ | 'flag';
293
+
294
+ /**
295
+ * One marker: a plotshape()/plotchar() marker on a bar where the condition held, or a marker of a built-in indicator
254
296
  */
255
297
  export interface MarkerData {
256
298
  /** Time of the bar the marker is displayed on (offset already applied) */
257
- time: any;
258
- /** Id of the plotshape/plotchar declaration that produced it */
259
- id: string;
260
- /** Marker location relative to the bar */
261
- location: MarkerLocation;
262
- /** Shape style, or 'char' for plotchar markers */
263
- style: ShapeStyle | 'char';
264
- /** Character (plotchar only) */
265
- char?: string;
266
- /** Marker color */
267
- color?: string;
268
- /** Text shown near the marker */
299
+ time: number;
300
+ /** Where the marker is drawn */
301
+ position: MarkerPosition;
302
+ /** Price of the atPrice* positions (required with them) */
303
+ price?: number;
304
+ /** Shape. A plotchar marker also has `char`: the character is drawn in place of the shape */
305
+ shape: MarkerShape;
306
+ /**
307
+ * Shape colour (the character colour of a plotchar marker). A fully transparent colour ('transparent',
308
+ * '#rrggbb00', 'rgba(r, g, b, 0)') draws no shape, as PineScript `color = na`; the text is still drawn
309
+ */
310
+ color: string;
311
+ /** Text; a line feed starts a new line */
269
312
  text?: string;
270
- /** Text color */
271
- textcolor?: string;
272
- /** Marker size */
273
- size?: MarkerSize;
274
- /** Hover tooltip (extension over PineScript plotshape, used by label.new ports) */
313
+ /**
314
+ * Text colour (PineScript textcolor). When omitted: white inside label shapes (labelUp / labelDown), the shape
315
+ * colour for the other shapes
316
+ */
317
+ textColor?: string;
318
+ /** Size multiplier (1 = default size) or a PineScript size */
319
+ size?: number | PineSize;
320
+ /** PineScript force_overlay = true: the marker of a non-overlay indicator is drawn on the price pane */
321
+ forceOverlay?: boolean;
322
+ /** Id of the plotshape/plotchar declaration that produced it (script markers) */
323
+ id?: string;
324
+ /** plotchar character, drawn in place of the shape */
325
+ char?: string;
326
+ /** Hover tooltip */
275
327
  tooltip?: string;
276
- /** Price for location 'absolute' (the series value on that bar) */
277
- price?: number;
278
328
  }
279
329
 
280
330
  /**
@@ -292,15 +342,248 @@ export interface ArrowData {
292
342
  }
293
343
 
294
344
  /**
295
- * Per-bar color for bgcolor()/barcolor() output
345
+ * Per-bar candle color (barcolor() output)
296
346
  */
297
347
  export interface BarColorData {
298
348
  /** Time of the colored bar (offset already applied) */
299
- time: any;
349
+ time: number;
300
350
  /** Color */
301
351
  color: string;
302
352
  }
303
353
 
354
+ /**
355
+ * Per-bar background color (bgcolor() output)
356
+ */
357
+ export interface BgColorData {
358
+ /** Time of the colored bar (offset already applied) */
359
+ time: number;
360
+ /** Color (with its transparency) */
361
+ color: string;
362
+ /** PineScript bgcolor(..., force_overlay = true): drawn on the price pane even for a non-overlay indicator */
363
+ forceOverlay?: boolean;
364
+ }
365
+
366
+ /**
367
+ * One candle of plotcandle() (or of a built-in indicator that draws candles)
368
+ */
369
+ export interface PlotCandleData {
370
+ time: number;
371
+ open: number;
372
+ high: number;
373
+ low: number;
374
+ close: number;
375
+ /** Body colour */
376
+ color?: string;
377
+ borderColor?: string;
378
+ wickColor?: string;
379
+ /** PineScript force_overlay = true: the candles of a non-overlay indicator are drawn on the price pane */
380
+ forceOverlay?: boolean;
381
+ }
382
+
383
+ /**
384
+ * One bar of plotbar(): an OHLC bar (a vertical line from low to high, the open tick on the left, the close tick on
385
+ * the right)
386
+ */
387
+ export interface PlotBarData {
388
+ time: number;
389
+ open: number;
390
+ high: number;
391
+ low: number;
392
+ close: number;
393
+ color?: string;
394
+ /** PineScript force_overlay = true: the bars of a non-overlay indicator are drawn on the price pane */
395
+ forceOverlay?: boolean;
396
+ }
397
+
398
+ /** PineScript label style (`label.style_*` without the prefix) */
399
+ export type LabelStyle =
400
+ | 'none'
401
+ | 'xcross'
402
+ | 'cross'
403
+ | 'triangleup'
404
+ | 'triangledown'
405
+ | 'flag'
406
+ | 'circle'
407
+ | 'arrowup'
408
+ | 'arrowdown'
409
+ | 'label_up'
410
+ | 'label_down'
411
+ | 'label_left'
412
+ | 'label_right'
413
+ | 'label_lower_left'
414
+ | 'label_lower_right'
415
+ | 'label_upper_left'
416
+ | 'label_upper_right'
417
+ | 'label_center'
418
+ | 'square'
419
+ | 'diamond'
420
+ | 'text_outline';
421
+
422
+ // Drawing outputs (labels, lines, boxes, linefills, polylines, tables). A field the script did not set is omitted and
423
+ // the renderer uses the PineScript default; a colour set to na is 'transparent'. A point on a bar after the last bar
424
+ // (PineScript bar_index + k) has the time of that future bar (see barTime). The same applies to markers and plots.
425
+
426
+ /**
427
+ * A label (PineScript label.new)
428
+ */
429
+ export interface LabelData {
430
+ time: number;
431
+ /** Price; not used with yloc 'abovebar' / 'belowbar' */
432
+ price: number;
433
+ text: string;
434
+ /** Background, border and arrow colour */
435
+ color?: string;
436
+ textColor?: string;
437
+ /** 'none': text only, no background (PineScript label.style_none) */
438
+ style?: LabelStyle;
439
+ size?: PineSize | number;
440
+ /**
441
+ * PineScript yloc: 'price' (default) places the label at `price`; 'abovebar' / 'belowbar' place it above the bar
442
+ * high / below the bar low and ignore `price` (the renderer reads the bar at `time`).
443
+ */
444
+ yloc?: 'price' | 'abovebar' | 'belowbar';
445
+ textAlign?: 'left' | 'center' | 'right';
446
+ tooltip?: string;
447
+ fontFamily?: 'default' | 'monospace';
448
+ /** PineScript text.format_* flags: 0 none, 1 bold, 2 italic, 3 bold + italic */
449
+ textFormatting?: number;
450
+ /** PineScript force_overlay = true: the label of a non-overlay indicator is drawn on the price pane */
451
+ forceOverlay?: boolean;
452
+ }
453
+
454
+ /**
455
+ * A line (PineScript line.new)
456
+ */
457
+ export interface LineDrawingData {
458
+ time1: number;
459
+ price1: number;
460
+ time2: number;
461
+ price2: number;
462
+ color?: string;
463
+ width?: number;
464
+ style?: 'solid' | 'dashed' | 'dotted' | 'arrow_left' | 'arrow_right' | 'arrow_both';
465
+ extend?: 'none' | 'left' | 'right' | 'both';
466
+ /** PineScript force_overlay = true: the line of a non-overlay indicator is drawn on the price pane */
467
+ forceOverlay?: boolean;
468
+ }
469
+
470
+ /**
471
+ * A rectangle (PineScript box.new): time1 / price1 are the left / top, time2 / price2 the right / bottom
472
+ */
473
+ export interface BoxData {
474
+ time1: number;
475
+ price1: number;
476
+ time2: number;
477
+ price2: number;
478
+ bgColor?: string;
479
+ borderColor?: string;
480
+ borderWidth?: number;
481
+ borderStyle?: 'solid' | 'dashed' | 'dotted';
482
+ text?: string;
483
+ textColor?: string;
484
+ textSize?: PineSize | number;
485
+ textHAlign?: 'left' | 'center' | 'right';
486
+ textVAlign?: 'top' | 'center' | 'bottom';
487
+ textWrap?: 'none' | 'auto';
488
+ fontFamily?: 'default' | 'monospace';
489
+ /** PineScript text.format_* flags: 0 none, 1 bold, 2 italic, 3 bold + italic */
490
+ textFormatting?: number;
491
+ /** PineScript box extend: the box continues to the left / right edge of the chart */
492
+ extend?: 'none' | 'left' | 'right' | 'both';
493
+ /** PineScript force_overlay = true: the box of a non-overlay indicator is drawn on the price pane */
494
+ forceOverlay?: boolean;
495
+ }
496
+
497
+ /**
498
+ * The area between two lines (PineScript linefill.new), with the two lines as drawn (their extend included)
499
+ */
500
+ export interface LinefillData {
501
+ line1: LineDrawingData;
502
+ line2: LineDrawingData;
503
+ color?: string;
504
+ }
505
+
506
+ /**
507
+ * A polyline (PineScript polyline.new)
508
+ */
509
+ export interface PolylineData {
510
+ points: Array<{ time: number; price: number }>;
511
+ /** Curved segments between the points */
512
+ curved?: boolean;
513
+ /** The last point is connected to the first */
514
+ closed?: boolean;
515
+ lineColor?: string;
516
+ /** Fill of the area inside the polyline */
517
+ fillColor?: string;
518
+ lineStyle?: 'solid' | 'dotted' | 'dashed' | 'arrow_left' | 'arrow_right' | 'arrow_both';
519
+ lineWidth?: number;
520
+ /** PineScript force_overlay = true: the polyline of a non-overlay indicator is drawn on the price pane */
521
+ forceOverlay?: boolean;
522
+ }
523
+
524
+ /** Table position (PineScript position.*) */
525
+ export type TablePosition =
526
+ | 'top_left'
527
+ | 'top_center'
528
+ | 'top_right'
529
+ | 'middle_left'
530
+ | 'middle_center'
531
+ | 'middle_right'
532
+ | 'bottom_left'
533
+ | 'bottom_center'
534
+ | 'bottom_right';
535
+
536
+ /**
537
+ * A table cell (PineScript table.cell)
538
+ */
539
+ export interface TableCellData {
540
+ row: number;
541
+ column: number;
542
+ text: string;
543
+ bgColor?: string;
544
+ textColor?: string;
545
+ textSize?: PineSize | number;
546
+ /** Width in % of the chart width; 0 fits the text */
547
+ width?: number;
548
+ /** Height in % of the chart height; 0 fits the text */
549
+ height?: number;
550
+ textHAlign?: 'left' | 'center' | 'right';
551
+ textVAlign?: 'top' | 'center' | 'bottom';
552
+ tooltip?: string;
553
+ fontFamily?: 'default' | 'monospace';
554
+ /** PineScript text.format_* flags: 0 none, 1 bold, 2 italic, 3 bold + italic */
555
+ textFormatting?: number;
556
+ }
557
+
558
+ /**
559
+ * Merged cells of a table (PineScript table.merge_cells): the cell at the start column / row covers the range and
560
+ * gives its content
561
+ */
562
+ export interface TableMergeData {
563
+ startColumn: number;
564
+ startRow: number;
565
+ endColumn: number;
566
+ endRow: number;
567
+ }
568
+
569
+ /**
570
+ * A table (PineScript table.new)
571
+ */
572
+ export interface TableData {
573
+ position: TablePosition;
574
+ columns: number;
575
+ rows: number;
576
+ cells: TableCellData[];
577
+ merges?: TableMergeData[];
578
+ bgColor?: string;
579
+ frameColor?: string;
580
+ frameWidth?: number;
581
+ borderColor?: string;
582
+ borderWidth?: number;
583
+ /** PineScript force_overlay = true: the table of a non-overlay indicator is drawn on the price pane */
584
+ forceOverlay?: boolean;
585
+ }
586
+
304
587
  /**
305
588
  * Complete indicator calculation result
306
589
  */
@@ -318,9 +601,25 @@ export interface IndicatorResult {
318
601
  /** Arrows from plotarrow() */
319
602
  arrows?: ArrowData[];
320
603
  /** Background colors from bgcolor() */
321
- bgcolors?: BarColorData[];
604
+ bgColors?: BgColorData[];
322
605
  /** Bar colors from barcolor() */
323
- barcolors?: BarColorData[];
606
+ barColors?: BarColorData[];
607
+ /** Candles from plotcandle(), keyed by plotcandle id */
608
+ plotCandles?: Record<string, PlotCandleData[]>;
609
+ /** OHLC bars from plotbar(), keyed by plotbar id */
610
+ plotBars?: Record<string, PlotBarData[]>;
611
+ /** Labels alive after the last bar */
612
+ labels?: LabelData[];
613
+ /** Lines alive after the last bar */
614
+ lines?: LineDrawingData[];
615
+ /** Boxes alive after the last bar */
616
+ boxes?: BoxData[];
617
+ /** Linefills alive after the last bar */
618
+ linefills?: LinefillData[];
619
+ /** Polylines alive after the last bar */
620
+ polylines?: PolylineData[];
621
+ /** Tables alive after the last bar */
622
+ tables?: TableData[];
324
623
  }
325
624
 
326
625
  /**