oakscriptjs 0.9.6 → 0.10.1

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 (140) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/array/index.d.ts +9 -23
  3. package/dist/cjs/array/index.d.ts.map +1 -1
  4. package/dist/cjs/array/index.js +4 -0
  5. package/dist/cjs/array/index.js.map +2 -2
  6. package/dist/cjs/drawing/bar-time.d.ts +24 -0
  7. package/dist/cjs/drawing/bar-time.d.ts.map +1 -0
  8. package/dist/cjs/drawing/bar-time.js +47 -0
  9. package/dist/cjs/drawing/bar-time.js.map +7 -0
  10. package/dist/cjs/drawing/output.d.ts +32 -0
  11. package/dist/cjs/drawing/output.d.ts.map +1 -0
  12. package/dist/cjs/drawing/output.js +184 -0
  13. package/dist/cjs/drawing/output.js.map +7 -0
  14. package/dist/cjs/drawing/registry.d.ts +5 -4
  15. package/dist/cjs/drawing/registry.d.ts.map +1 -1
  16. package/dist/cjs/drawing/registry.js +11 -2
  17. package/dist/cjs/drawing/registry.js.map +2 -2
  18. package/dist/cjs/index.d.ts +7 -3
  19. package/dist/cjs/index.d.ts.map +1 -1
  20. package/dist/cjs/index.js +11 -2
  21. package/dist/cjs/index.js.map +2 -2
  22. package/dist/cjs/polyline/index.d.ts +3 -5
  23. package/dist/cjs/polyline/index.d.ts.map +1 -1
  24. package/dist/cjs/polyline/index.js +0 -5
  25. package/dist/cjs/polyline/index.js.map +2 -2
  26. package/dist/cjs/runtime/index.d.ts +2 -1
  27. package/dist/cjs/runtime/index.d.ts.map +1 -1
  28. package/dist/cjs/runtime/index.js +2 -0
  29. package/dist/cjs/runtime/index.js.map +2 -2
  30. package/dist/cjs/runtime/input-active.d.ts +27 -0
  31. package/dist/cjs/runtime/input-active.d.ts.map +1 -0
  32. package/dist/cjs/runtime/input-active.js +44 -0
  33. package/dist/cjs/runtime/input-active.js.map +7 -0
  34. package/dist/cjs/runtime/types.d.ts +51 -2
  35. package/dist/cjs/runtime/types.d.ts.map +1 -1
  36. package/dist/cjs/runtime/types.js.map +1 -1
  37. package/dist/cjs/script/index.d.ts +104 -8
  38. package/dist/cjs/script/index.d.ts.map +1 -1
  39. package/dist/cjs/script/index.js +163 -28
  40. package/dist/cjs/script/index.js.map +3 -3
  41. package/dist/cjs/str/index.d.ts +9 -3
  42. package/dist/cjs/str/index.d.ts.map +1 -1
  43. package/dist/cjs/str/index.js +11 -6
  44. package/dist/cjs/str/index.js.map +2 -2
  45. package/dist/cjs/str/numberformat.d.ts +6 -0
  46. package/dist/cjs/str/numberformat.d.ts.map +1 -1
  47. package/dist/cjs/str/numberformat.js +9 -1
  48. package/dist/cjs/str/numberformat.js.map +2 -2
  49. package/dist/cjs/table/index.d.ts +87 -0
  50. package/dist/cjs/table/index.d.ts.map +1 -0
  51. package/dist/cjs/table/index.js +150 -0
  52. package/dist/cjs/table/index.js.map +7 -0
  53. package/dist/cjs/text/index.d.ts +17 -2
  54. package/dist/cjs/text/index.d.ts.map +1 -1
  55. package/dist/cjs/text/index.js +15 -1
  56. package/dist/cjs/text/index.js.map +2 -2
  57. package/dist/cjs/types/index.d.ts +30 -1
  58. package/dist/cjs/types/index.d.ts.map +1 -1
  59. package/dist/cjs/types/index.js +12 -0
  60. package/dist/cjs/types/index.js.map +2 -2
  61. package/dist/cjs/types/metadata.d.ts +258 -26
  62. package/dist/cjs/types/metadata.d.ts.map +1 -1
  63. package/dist/cjs/types/metadata.js.map +1 -1
  64. package/dist/esm/array/index.d.ts +9 -23
  65. package/dist/esm/array/index.d.ts.map +1 -1
  66. package/dist/esm/array/index.js +10 -22
  67. package/dist/esm/array/index.js.map +1 -1
  68. package/dist/esm/drawing/bar-time.d.ts +24 -0
  69. package/dist/esm/drawing/bar-time.d.ts.map +1 -0
  70. package/dist/esm/drawing/bar-time.js +44 -0
  71. package/dist/esm/drawing/bar-time.js.map +1 -0
  72. package/dist/esm/drawing/output.d.ts +32 -0
  73. package/dist/esm/drawing/output.d.ts.map +1 -0
  74. package/dist/esm/drawing/output.js +190 -0
  75. package/dist/esm/drawing/output.js.map +1 -0
  76. package/dist/esm/drawing/registry.d.ts +5 -4
  77. package/dist/esm/drawing/registry.d.ts.map +1 -1
  78. package/dist/esm/drawing/registry.js +12 -2
  79. package/dist/esm/drawing/registry.js.map +1 -1
  80. package/dist/esm/index.d.ts +7 -3
  81. package/dist/esm/index.d.ts.map +1 -1
  82. package/dist/esm/index.js +8 -3
  83. package/dist/esm/index.js.map +1 -1
  84. package/dist/esm/polyline/index.d.ts +3 -5
  85. package/dist/esm/polyline/index.d.ts.map +1 -1
  86. package/dist/esm/polyline/index.js +3 -9
  87. package/dist/esm/polyline/index.js.map +1 -1
  88. package/dist/esm/runtime/index.d.ts +2 -1
  89. package/dist/esm/runtime/index.d.ts.map +1 -1
  90. package/dist/esm/runtime/index.js +1 -0
  91. package/dist/esm/runtime/index.js.map +1 -1
  92. package/dist/esm/runtime/input-active.d.ts +27 -0
  93. package/dist/esm/runtime/input-active.d.ts.map +1 -0
  94. package/dist/esm/runtime/input-active.js +54 -0
  95. package/dist/esm/runtime/input-active.js.map +1 -0
  96. package/dist/esm/runtime/types.d.ts +51 -2
  97. package/dist/esm/runtime/types.d.ts.map +1 -1
  98. package/dist/esm/script/index.d.ts +104 -8
  99. package/dist/esm/script/index.d.ts.map +1 -1
  100. package/dist/esm/script/index.js +194 -24
  101. package/dist/esm/script/index.js.map +1 -1
  102. package/dist/esm/str/index.d.ts +9 -3
  103. package/dist/esm/str/index.d.ts.map +1 -1
  104. package/dist/esm/str/index.js +25 -10
  105. package/dist/esm/str/index.js.map +1 -1
  106. package/dist/esm/str/numberformat.d.ts +6 -0
  107. package/dist/esm/str/numberformat.d.ts.map +1 -1
  108. package/dist/esm/str/numberformat.js +12 -0
  109. package/dist/esm/str/numberformat.js.map +1 -1
  110. package/dist/esm/table/index.d.ts +87 -0
  111. package/dist/esm/table/index.d.ts.map +1 -0
  112. package/dist/esm/table/index.js +169 -0
  113. package/dist/esm/table/index.js.map +1 -0
  114. package/dist/esm/text/index.d.ts +17 -2
  115. package/dist/esm/text/index.d.ts.map +1 -1
  116. package/dist/esm/text/index.js +17 -2
  117. package/dist/esm/text/index.js.map +1 -1
  118. package/dist/esm/types/index.d.ts +30 -1
  119. package/dist/esm/types/index.d.ts.map +1 -1
  120. package/dist/esm/types/index.js +14 -0
  121. package/dist/esm/types/index.js.map +1 -1
  122. package/dist/esm/types/metadata.d.ts +258 -26
  123. package/dist/esm/types/metadata.d.ts.map +1 -1
  124. package/package.json +1 -1
  125. package/src/array/index.ts +12 -23
  126. package/src/drawing/bar-time.ts +42 -0
  127. package/src/drawing/output.ts +214 -0
  128. package/src/drawing/registry.ts +16 -6
  129. package/src/index.ts +11 -3
  130. package/src/polyline/index.ts +3 -12
  131. package/src/runtime/index.ts +3 -0
  132. package/src/runtime/input-active.ts +50 -0
  133. package/src/runtime/types.ts +48 -2
  134. package/src/script/index.ts +301 -30
  135. package/src/str/index.ts +23 -10
  136. package/src/str/numberformat.ts +13 -0
  137. package/src/table/index.ts +221 -0
  138. package/src/text/index.ts +18 -2
  139. package/src/types/index.ts +32 -1
  140. package/src/types/metadata.ts +325 -26
@@ -37,21 +37,39 @@ 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,
72
+ InputCondition,
55
73
  PlotConfig,
56
74
  ShapeConfig,
57
75
  } from '../runtime/types.js';
@@ -65,17 +83,21 @@ import * as labelCore from '../label/index.js';
65
83
  import * as boxCore from '../box/index.js';
66
84
  import * as linefillCore from '../linefill/index.js';
67
85
  import * as polylineCore from '../polyline/index.js';
86
+ import * as tableCore from '../table/index.js';
68
87
  import * as chartPointCore from '../chartpoint/index.js';
69
88
  import * as textCore from '../text/index.js';
70
89
  import * as mathCore from '../math/index.js';
71
90
  import { resetDrawings, setDrawingLimits } from '../drawing/registry.js';
91
+ import { drawingOutputs } from '../drawing/output.js';
72
92
  import * as timeframeCore from '../timeframe/index.js';
73
93
  import * as timeCore from '../time/index.js';
74
94
  import * as strCore from '../str/index.js';
75
95
  import { formatMessage } from '../str/messageformat.js';
96
+ import { tickScale } from '../str/numberformat.js';
76
97
  import { TradingCalendar, type SessionSpec } from '../session/calendar.js';
77
98
  import { SessionBars, sessionFlags, type SessionFlags } from '../session/bars.js';
78
99
  import { afterCompletion, heikinAshi, mapToChart, periodsOf, resample } from '../security/resample.js';
100
+ import { position } from '../types/index.js';
79
101
  import type { ChartPoint } from '../types/index.js';
80
102
  import {
81
103
  STRATEGY_CONSTANTS,
@@ -110,11 +132,17 @@ export interface ScriptRunResult {
110
132
  shapeConfig: ShapeConfig[];
111
133
  arrowConfig: ArrowConfig[];
112
134
  barColorConfig: BarColorConfig[];
135
+ /** plotcandle() / plotbar() declarations */
136
+ candleConfig: CandleConfig[];
113
137
  defaultInputs: Record<string, unknown>;
114
138
  alertConfig: AlertConditionConfig[];
115
139
  /** Strategy properties (declared values, else the PineScript defaults) when the script declares strategy(). */
116
140
  strategyConfig?: StrategyProperties;
117
- /** The renderable output (plots keyed by plotConfig ids, plot-pair fills). */
141
+ /**
142
+ * The renderable output: plots keyed by plotConfig ids, plot-pair fills, markers, arrows, background and bar
143
+ * colours, candles and bars, and the drawings (labels, lines, boxes, linefills, polylines, tables) alive after the
144
+ * last bar.
145
+ */
118
146
  result: IndicatorResult & { alerts?: AlertState[] };
119
147
  }
120
148
 
@@ -159,8 +187,11 @@ interface Collector {
159
187
  arrowConfig: ArrowConfig[];
160
188
  arrows: ArrowData[];
161
189
  barColorConfig: BarColorConfig[];
162
- bgcolors: BarColorData[];
163
- barcolors: BarColorData[];
190
+ bgColors: BgColorData[];
191
+ barColors: BarColorData[];
192
+ candleConfig: CandleConfig[];
193
+ plotCandles: Record<string, PlotCandleData[]>;
194
+ plotBars: Record<string, PlotBarData[]>;
164
195
  alertConfig: AlertConditionConfig[];
165
196
  alerts: AlertState[];
166
197
  strategy?: StrategyRun;
@@ -210,8 +241,11 @@ function freshCollector(inputValues: Record<string, unknown>): Collector {
210
241
  arrowConfig: [],
211
242
  arrows: [],
212
243
  barColorConfig: [],
213
- bgcolors: [],
214
- barcolors: [],
244
+ bgColors: [],
245
+ barColors: [],
246
+ candleConfig: [],
247
+ plotCandles: {},
248
+ plotBars: {},
215
249
  alertConfig: [],
216
250
  alerts: [],
217
251
  };
@@ -284,6 +318,7 @@ export function executeScript(
284
318
  shapeConfig: c.shapeConfig,
285
319
  arrowConfig: c.arrowConfig,
286
320
  barColorConfig: c.barColorConfig,
321
+ candleConfig: c.candleConfig,
287
322
  defaultInputs: c.defaultInputs,
288
323
  alertConfig: c.alertConfig,
289
324
  strategyConfig: c.strategy?.properties,
@@ -293,8 +328,11 @@ export function executeScript(
293
328
  fills: c.fills.length ? c.fills : undefined,
294
329
  markers: c.markers.length ? c.markers : undefined,
295
330
  arrows: c.arrows.length ? c.arrows : undefined,
296
- bgcolors: c.bgcolors.length ? c.bgcolors : undefined,
297
- barcolors: c.barcolors.length ? c.barcolors : undefined,
331
+ bgColors: c.bgColors.length ? c.bgColors : undefined,
332
+ barColors: c.barColors.length ? c.barColors : undefined,
333
+ plotCandles: Object.keys(c.plotCandles).length ? c.plotCandles : undefined,
334
+ plotBars: Object.keys(c.plotBars).length ? c.plotBars : undefined,
335
+ ...drawingOutputs(ctxBars.bars, chartCtx.timeUnit),
298
336
  alerts: c.alerts.length ? c.alerts : undefined,
299
337
  },
300
338
  };
@@ -344,13 +382,19 @@ function declare(c: Collector, kind: 'indicator' | 'strategy'): void {
344
382
  c.declaration = kind;
345
383
  }
346
384
 
347
- /** Options of every `input.*` kind (PineScript `group`, `inline`, `tooltip`, `confirm`, `display`). */
385
+ /** Options of every `input.*` kind (PineScript `group`, `inline`, `tooltip`, `confirm`, `display`, `active`). */
348
386
  export interface InputOptions {
349
387
  group?: string;
350
388
  inline?: string;
351
389
  tooltip?: string;
352
390
  confirm?: boolean;
353
391
  display?: InputConfig['display'];
392
+ /**
393
+ * PineScript `active`: false greys the input out in the settings (it keeps its value). A boolean as in
394
+ * PineScript (`active: showMa`), or a condition on input ids that a host can apply without running the script
395
+ * (`active: 'ma_1'`, `active: { input: 'type', ne: 'None' }`)
396
+ */
397
+ active?: boolean | InputCondition;
354
398
  }
355
399
 
356
400
  export interface NumericInputOptions extends InputOptions {
@@ -371,6 +415,7 @@ function common(opts: InputOptions): Partial<InputConfig> {
371
415
  if (opts.tooltip !== undefined) out.tooltip = opts.tooltip;
372
416
  if (opts.confirm !== undefined) out.confirm = opts.confirm;
373
417
  if (opts.display !== undefined) out.display = opts.display;
418
+ if (opts.active !== undefined) out.active = opts.active;
374
419
  return out;
375
420
  }
376
421
 
@@ -779,8 +824,46 @@ export interface PlotCharOptions extends Omit<PlotShapeOptions, 'style'> {
779
824
  char?: string;
780
825
  }
781
826
 
827
+ /** PineScript shape.* style -> marker shape. */
828
+ const MARKER_SHAPES: Record<ShapeStyle, MarkerShape> = {
829
+ xcross: 'xcross',
830
+ cross: 'cross',
831
+ triangleup: 'triangleUp',
832
+ triangledown: 'triangleDown',
833
+ flag: 'flag',
834
+ circle: 'circle',
835
+ arrowup: 'arrowUp',
836
+ arrowdown: 'arrowDown',
837
+ labelup: 'labelUp',
838
+ labeldown: 'labelDown',
839
+ square: 'square',
840
+ diamond: 'diamond',
841
+ };
842
+
843
+ /**
844
+ * Marker position of a PineScript location. With location.absolute the shape sits on the price: a label pointing
845
+ * down above it, a label pointing up below it, the other shapes (and the characters) centred on it.
846
+ */
847
+ function markerPosition(location: MarkerLocation, shape: ShapeStyle | undefined): MarkerPosition {
848
+ switch (location) {
849
+ case 'abovebar':
850
+ return 'aboveBar';
851
+ case 'belowbar':
852
+ return 'belowBar';
853
+ case 'top':
854
+ return 'top';
855
+ case 'bottom':
856
+ return 'bottom';
857
+ case 'absolute':
858
+ return shape === 'labeldown' ? 'atPriceTop' : shape === 'labelup' ? 'atPriceBottom' : 'atPriceMiddle';
859
+ }
860
+ }
861
+
862
+ /** Default colour of plotshape / plotchar markers. */
863
+ const MARKER_COLOR = '#2962FF';
864
+
782
865
  /** Shared emitter for plotshape()/plotchar(). Emits one marker per bar where
783
- * the condition is truthy (not na, not 0). */
866
+ * the condition is truthy (not na, not 0). A plotchar marker has the shape 'circle' and its character in `char`. */
784
867
  function emitMarkers(
785
868
  kind: 'shape' | 'char',
786
869
  condition: Series,
@@ -806,6 +889,19 @@ function emitMarkers(
806
889
  offset: options.offset,
807
890
  forceOverlay: options.force_overlay,
808
891
  });
892
+ const shapeStyle = kind === 'shape' ? (style as ShapeStyle) : undefined;
893
+ const template: Omit<MarkerData, 'time'> = {
894
+ position: markerPosition(location, shapeStyle),
895
+ shape: shapeStyle ? MARKER_SHAPES[shapeStyle] : 'circle',
896
+ color: options.color ?? MARKER_COLOR,
897
+ id,
898
+ };
899
+ if (char !== undefined) template.char = char;
900
+ if (options.text !== undefined) template.text = options.text;
901
+ if (options.textcolor !== undefined) template.textColor = options.textcolor;
902
+ if (options.size !== undefined) template.size = options.size;
903
+ if (options.tooltip !== undefined) template.tooltip = options.tooltip;
904
+ if (options.force_overlay) template.forceOverlay = true;
809
905
  const values = condition.toArray();
810
906
  const b = bars();
811
907
  const off = options.offset ?? 0;
@@ -814,19 +910,8 @@ function emitMarkers(
814
910
  if (v === undefined || Number.isNaN(v) || v === 0) continue;
815
911
  const j = i + off;
816
912
  if (j < 0 || j >= b.length) continue;
817
- c.markers.push({
818
- time: b[j]!.time,
819
- id,
820
- location,
821
- style,
822
- char,
823
- color: options.color,
824
- text: options.text,
825
- textcolor: options.textcolor,
826
- size: options.size,
827
- tooltip: options.tooltip,
828
- price: location === 'absolute' ? v : undefined,
829
- });
913
+ const time = b[j]!.time;
914
+ c.markers.push(location === 'absolute' ? { time, ...template, price: v } : { time, ...template });
830
915
  }
831
916
  }
832
917
 
@@ -906,13 +991,14 @@ function emitBarColors(
906
991
  c.barColorConfig.push({ id, kind, title: options.title, offset: options.offset, forceOverlay: options.force_overlay });
907
992
  const b = bars();
908
993
  const off = options.offset ?? 0;
909
- const target = kind === 'bgcolor' ? c.bgcolors : c.barcolors;
994
+ const target: BgColorData[] = kind === 'bgcolor' ? c.bgColors : c.barColors;
995
+ const forceOverlay = kind === 'bgcolor' && options.force_overlay === true;
910
996
  for (let i = 0; i < b.length; i++) {
911
997
  const col = typeof colors === 'string' ? colors : colors[i];
912
998
  if (!col) continue;
913
999
  const j = i + off;
914
1000
  if (j < 0 || j >= b.length) continue;
915
- target.push({ time: b[j]!.time, color: col });
1001
+ target.push(forceOverlay ? { time: b[j]!.time, color: col, forceOverlay } : { time: b[j]!.time, color: col });
916
1002
  }
917
1003
  }
918
1004
 
@@ -935,6 +1021,129 @@ export function barcolor(
935
1021
  emitBarColors('barcolor', colors, options);
936
1022
  }
937
1023
 
1024
+ /** A colour argument of plotcandle() / plotbar(): a colour, or per-bar colours (null / undefined entries are na). */
1025
+ export type ScriptCandleColor = string | Array<string | null | undefined>;
1026
+
1027
+ export interface PlotCandleOptions {
1028
+ /** Body colour: a colour or a per-bar array (color.when); an na entry gives a transparent body */
1029
+ color?: ScriptCandleColor;
1030
+ /** Wick colour: a colour or a per-bar array */
1031
+ wickcolor?: ScriptCandleColor;
1032
+ /** Border colour: a colour or a per-bar array */
1033
+ bordercolor?: ScriptCandleColor;
1034
+ display?: CandleConfig['display'];
1035
+ /** Draw on the main chart pane even when the script is not an overlay (PineScript `force_overlay`); default false. */
1036
+ force_overlay?: boolean;
1037
+ }
1038
+
1039
+ export interface PlotBarOptions {
1040
+ /** Bar colour: a colour or a per-bar array (color.when); an na entry gives a transparent bar */
1041
+ color?: ScriptCandleColor;
1042
+ display?: CandleConfig['display'];
1043
+ /** Draw on the main chart pane even when the script is not an overlay (PineScript `force_overlay`); default false. */
1044
+ force_overlay?: boolean;
1045
+ }
1046
+
1047
+ /** Colour of bar i: undefined when the argument is not given, 'transparent' for an na entry. */
1048
+ function candleColor(col: ScriptCandleColor | undefined, i: number): string | undefined {
1049
+ if (col === undefined) return undefined;
1050
+ if (typeof col === 'string') return col;
1051
+ return col[i] ?? 'transparent';
1052
+ }
1053
+
1054
+ interface OhlcRow {
1055
+ i: number;
1056
+ open: number;
1057
+ high: number;
1058
+ low: number;
1059
+ close: number;
1060
+ }
1061
+
1062
+ /** The bars where open, high, low and close are all not na (PineScript draws no candle / bar otherwise). */
1063
+ function ohlcRows(o: Series, h: Series, l: Series, cl: Series): OhlcRow[] {
1064
+ const [ov, hv, lv, cv] = [o.toArray(), h.toArray(), l.toArray(), cl.toArray()];
1065
+ const rows: OhlcRow[] = [];
1066
+ for (let i = 0; i < bars().length; i++) {
1067
+ const [open, high, low, close] = [ov[i]!, hv[i]!, lv[i]!, cv[i]!];
1068
+ if (Number.isFinite(open) && Number.isFinite(high) && Number.isFinite(low) && Number.isFinite(close)) {
1069
+ rows.push({ i, open, high, low, close });
1070
+ }
1071
+ }
1072
+ return rows;
1073
+ }
1074
+
1075
+ const staticColor = (col: ScriptCandleColor | undefined): string | undefined => (typeof col === 'string' ? col : undefined);
1076
+
1077
+ /**
1078
+ * PineScript `plotcandle(open, high, low, close, title?, { color, wickcolor, bordercolor, display, force_overlay })`:
1079
+ * candles in `result.plotCandles[id]`, the declaration in `candleConfig`. A bar with an na value has no candle.
1080
+ */
1081
+ export function plotcandle(
1082
+ open: Series,
1083
+ high: Series,
1084
+ low: Series,
1085
+ close: Series,
1086
+ title?: string,
1087
+ options: PlotCandleOptions = {}
1088
+ ): void {
1089
+ const c = collector();
1090
+ const id = `plotcandle${c.candleConfig.filter((x) => x.kind === 'candle').length}`;
1091
+ c.candleConfig.push({
1092
+ id,
1093
+ kind: 'candle',
1094
+ title,
1095
+ color: staticColor(options.color),
1096
+ wickColor: staticColor(options.wickcolor),
1097
+ borderColor: staticColor(options.bordercolor),
1098
+ display: options.display,
1099
+ forceOverlay: options.force_overlay,
1100
+ });
1101
+ const b = bars();
1102
+ c.plotCandles[id] = ohlcRows(open, high, low, close).map(({ i, ...ohlc }) => {
1103
+ const candle: PlotCandleData = { time: b[i]!.time, ...ohlc };
1104
+ const color = candleColor(options.color, i);
1105
+ const wickColor = candleColor(options.wickcolor, i);
1106
+ const borderColor = candleColor(options.bordercolor, i);
1107
+ if (color !== undefined) candle.color = color;
1108
+ if (wickColor !== undefined) candle.wickColor = wickColor;
1109
+ if (borderColor !== undefined) candle.borderColor = borderColor;
1110
+ if (options.force_overlay) candle.forceOverlay = true;
1111
+ return candle;
1112
+ });
1113
+ }
1114
+
1115
+ /**
1116
+ * PineScript `plotbar(open, high, low, close, title?, { color, display, force_overlay })`: OHLC bars in
1117
+ * `result.plotBars[id]`, the declaration in `candleConfig`. A bar with an na value is not drawn.
1118
+ */
1119
+ export function plotbar(
1120
+ open: Series,
1121
+ high: Series,
1122
+ low: Series,
1123
+ close: Series,
1124
+ title?: string,
1125
+ options: PlotBarOptions = {}
1126
+ ): void {
1127
+ const c = collector();
1128
+ const id = `plotbar${c.candleConfig.filter((x) => x.kind === 'bar').length}`;
1129
+ c.candleConfig.push({
1130
+ id,
1131
+ kind: 'bar',
1132
+ title,
1133
+ color: staticColor(options.color),
1134
+ display: options.display,
1135
+ forceOverlay: options.force_overlay,
1136
+ });
1137
+ const b = bars();
1138
+ c.plotBars[id] = ohlcRows(open, high, low, close).map(({ i, ...ohlc }) => {
1139
+ const bar: PlotBarData = { time: b[i]!.time, ...ohlc };
1140
+ const color = candleColor(options.color, i);
1141
+ if (color !== undefined) bar.color = color;
1142
+ if (options.force_overlay) bar.forceOverlay = true;
1143
+ return bar;
1144
+ });
1145
+ }
1146
+
938
1147
  // ── Per-bar execution (eachBar) ──────────────────────────────────────────────
939
1148
 
940
1149
  /**
@@ -1322,8 +1531,18 @@ export interface ChartContext {
1322
1531
  tickerid?: string;
1323
1532
  /** Unit of `Bar.time`: 's' (default, as lightweight-charts) or 'ms'. PineScript times are in ms. */
1324
1533
  timeUnit?: 's' | 'ms';
1325
- /** Minimum price move of the symbol (PineScript `syminfo.mintick`), e.g. 0.01; used by `math.round_to_mintick` */
1534
+ /**
1535
+ * Minimum price move of the symbol (PineScript `syminfo.mintick`), e.g. 0.01; used by `math.round_to_mintick` and
1536
+ * `format.mintick`. Default: `minmove / pricescale` when both are given
1537
+ */
1326
1538
  mintick?: number;
1539
+ /**
1540
+ * PineScript `syminfo.minmove` and `syminfo.pricescale` (mintick = minmove / pricescale). Default: from `mintick`,
1541
+ * with pricescale 10^(its decimals) (0.25 is 25 / 100). Pass them for a symbol priced in fractions (1 / 32):
1542
+ * `format.mintick` shows floor(log10(pricescale)) decimals
1543
+ */
1544
+ minmove?: number;
1545
+ pricescale?: number;
1327
1546
  /** Currency value of one point of price move for one contract (PineScript `syminfo.pointvalue`); 1 for stocks */
1328
1547
  pointvalue?: number;
1329
1548
  /** Smallest tradable quantity (PineScript `syminfo.mincontract`); 1 for whole shares */
@@ -1411,19 +1630,37 @@ function symbolProperty(key: 'mintick' | 'pointvalue' | 'mincontract'): number {
1411
1630
  collector();
1412
1631
  const value = chartCtx[key];
1413
1632
  if (value === undefined) {
1633
+ if (key === 'mintick' && chartCtx.minmove !== undefined && chartCtx.pricescale !== undefined) {
1634
+ return chartCtx.minmove / chartCtx.pricescale;
1635
+ }
1414
1636
  throw new Error(`syminfo.${key} is not known: pass it as executeScript(body, bars, inputs, { ${key} }).`);
1415
1637
  }
1416
1638
  return value;
1417
1639
  }
1418
1640
 
1641
+ /** `[minmove, pricescale]` of the chart symbol: from the chart context, else from its mintick. */
1642
+ function symbolScale(): [number, number] {
1643
+ collector();
1644
+ const { minmove, pricescale } = chartCtx;
1645
+ if (minmove !== undefined && pricescale !== undefined) return [minmove, pricescale];
1646
+ const [move, scale] = tickScale(symbolProperty('mintick'), pricescale);
1647
+ return [minmove ?? move, scale];
1648
+ }
1649
+
1419
1650
  /**
1420
1651
  * PineScript `syminfo.timezone`, `syminfo.tickerid`, `syminfo.session` (session type), `syminfo.mintick`,
1421
- * `syminfo.pointvalue` and `syminfo.mincontract`, from the chart context.
1652
+ * `syminfo.minmove`, `syminfo.pricescale`, `syminfo.pointvalue` and `syminfo.mincontract`, from the chart context.
1422
1653
  */
1423
1654
  export const syminfo = {
1424
1655
  get mintick(): number {
1425
1656
  return symbolProperty('mintick');
1426
1657
  },
1658
+ get minmove(): number {
1659
+ return symbolScale()[0];
1660
+ },
1661
+ get pricescale(): number {
1662
+ return symbolScale()[1];
1663
+ },
1427
1664
  get pointvalue(): number {
1428
1665
  return symbolProperty('pointvalue');
1429
1666
  },
@@ -1509,9 +1746,11 @@ export function inSession(time: number, session: string, timezone?: string): boo
1509
1746
  /** PineScript `str.*`; `format_time` and the dates of `format` use the exchange time zone by default. */
1510
1747
  export const str = {
1511
1748
  ...strCore,
1512
- // format.mintick rounds to the chart symbol's tick (syminfo.mintick)
1513
- tostring: ((value: unknown, format?: string, mintick?: number) =>
1514
- strCore.tostring(value as never, format, format === 'mintick' ? (mintick ?? syminfo.mintick) : mintick)) as typeof strCore.tostring,
1749
+ // format.mintick uses the chart symbol's tick and price scale (syminfo.mintick, syminfo.pricescale)
1750
+ tostring: ((value: unknown, format?: string, mintick?: number, pricescale?: number) =>
1751
+ format === 'mintick' && mintick === undefined
1752
+ ? strCore.tostring(value as never, format, syminfo.mintick, pricescale ?? syminfo.pricescale)
1753
+ : strCore.tostring(value as never, format, mintick, pricescale)) as typeof strCore.tostring,
1515
1754
  format_time(time: number, format: string = "yyyy-MM-dd'T'HH:mm:ssZ", timezone?: string): string {
1516
1755
  return strCore.format_time(time, format, timezone ?? exchangeTimezone());
1517
1756
  },
@@ -1831,7 +2070,20 @@ export const polyline = {
1831
2070
  },
1832
2071
  };
1833
2072
 
1834
- /** PineScript `text.format_*` constants. */
2073
+ /**
2074
+ * PineScript `table.*`; `table.all` lists the live tables in creation order. The live tables go to `result.tables`.
2075
+ */
2076
+ export const table = {
2077
+ ...tableCore,
2078
+ get all() {
2079
+ return tableCore.all();
2080
+ },
2081
+ };
2082
+
2083
+ /** PineScript `position.*` constants (table positions). */
2084
+ export { position };
2085
+
2086
+ /** PineScript `text.format_*`, `text.align_*` and `text.wrap_*` constants. */
1835
2087
  export const text = { ...textCore };
1836
2088
 
1837
2089
  /**
@@ -1902,21 +2154,40 @@ export const math = {
1902
2154
  }) as typeof mathCore.round_to_mintick,
1903
2155
  };
1904
2156
  export * as compare from '../compare/index.js';
2157
+ export { isInputActive } from '../runtime/input-active.js';
1905
2158
  export type {
1906
2159
  Bar,
1907
2160
  IndicatorResult,
1908
2161
  TimeValue,
1909
2162
  InputConfig,
2163
+ InputCondition,
1910
2164
  PlotConfig,
1911
2165
  HLineConfig,
1912
2166
  FillConfig,
1913
2167
  ShapeConfig,
1914
2168
  ArrowConfig,
1915
2169
  BarColorConfig,
2170
+ CandleConfig,
1916
2171
  MarkerData,
2172
+ MarkerPosition,
2173
+ MarkerShape,
1917
2174
  ArrowData,
1918
2175
  BarColorData,
2176
+ BgColorData,
2177
+ PlotCandleData,
2178
+ PlotBarData,
2179
+ LabelData,
2180
+ LabelStyle,
2181
+ LineDrawingData,
2182
+ BoxData,
2183
+ LinefillData,
2184
+ PolylineData,
2185
+ TableData,
2186
+ TableCellData,
2187
+ TableMergeData,
2188
+ TablePosition,
1919
2189
  MarkerLocation,
1920
2190
  MarkerSize,
2191
+ PineSize,
1921
2192
  ShapeStyle,
1922
2193
  };
package/src/str/index.ts CHANGED
@@ -9,10 +9,9 @@
9
9
  * @version 6
10
10
  */
11
11
 
12
- import { formatNumber } from './numberformat.js';
12
+ import { formatNumber, tickScale } from './numberformat.js';
13
13
  import { formatMessage } from './messageformat.js';
14
14
  import { formatDate } from './dateformat.js';
15
- import { round_to_mintick } from '../math/index.js';
16
15
  import type { int, bool, float, simple_int, simple_string } from '../types/index.js';
17
16
 
18
17
  /**
@@ -40,6 +39,8 @@ export function length(str: simple_string): int {
40
39
  * `format.price` / `format.inherit`
41
40
  * @param mintick - Tick size for `format.mintick` (PineScript `syminfo.mintick`, implicit there). Not part of the
42
41
  * PineScript signature: the core function cannot see the chart, so pass it, as for `math.round_to_mintick`
42
+ * @param pricescale - Price scale of the symbol for `format.mintick` (PineScript `syminfo.pricescale`), needed for a
43
+ * symbol priced in fractions (32 for a 1/32 tick). Default: 10^(decimals of `mintick`)
43
44
  * @returns String representation of the value
44
45
  *
45
46
  * @remarks
@@ -53,8 +54,12 @@ export function length(str: simple_string): int {
53
54
  * - `format.percent` shows 2 decimals and "%" without multiplying; `format.volume` uses K, M, B, T
54
55
  * - `format.price` and `format.inherit` give "price1" / "inherit1" in PineScript: they are patterns
55
56
  * without digit characters, and oakscriptjs does the same
56
- * - `format.mintick` rounds to the tick (`math.round_to_mintick`) and shows the tick's decimals ("#.##" for 0.01);
57
- * it throws without `mintick`
57
+ * - `format.mintick` shows floor(log10(pricescale)) decimals, zeros included ("1.00", "0.40" for tick 0.01), and
58
+ * rounds in two steps, with mintick = minmove / pricescale: the double quotient `|x| / (1 / pricescale)` half away
59
+ * from zero (140.355 / 0.01 = 14035.499999999998 gives "140.35", where `math.round_to_mintick` gives 140.36),
60
+ * then `floor(units / minmove + 0.5)` ticks (tick 0.02: 0.005 gives "0.02", -0.01 gives "0.00"). With a
61
+ * pricescale that is no power of 10 the tick value is then rounded half even to those decimals (1/32: 25.25 gives
62
+ * "25.2", -0.02 gives "-0.0"). It throws without `mintick`
58
63
  * - na gives "NaN" ("NaN%" with `format.percent`); an infinity gives "NaN" too ("NaN%" for `format.percent`,
59
64
  * "NaNT" for `format.volume`), except `format.mintick`, which gives "Infinity" / "-Infinity"
60
65
  *
@@ -66,7 +71,7 @@ export function length(str: simple_string): int {
66
71
  * str.tostring(12345.678, format.volume) // "12.346K"
67
72
  * ```
68
73
  */
69
- export function tostring(value: any, format?: simple_string, mintick?: float): string {
74
+ export function tostring(value: any, format?: simple_string, mintick?: float, pricescale?: float): string {
70
75
  if (typeof value !== 'number') return String(value);
71
76
  if (Number.isNaN(value)) return format === 'percent' ? 'NaN%' : 'NaN';
72
77
  if (!Number.isFinite(value)) {
@@ -94,11 +99,19 @@ export function tostring(value: any, format?: simple_string, mintick?: float): s
94
99
  if (mintick === undefined) {
95
100
  throw new Error('str.tostring(x, format.mintick) needs the tick size: pass it as str.tostring(x, format.mintick, syminfo.mintick)');
96
101
  }
97
- // mask: one '#' per decimal of the tick ("#.##" for 0.01, "#" for 1)
98
- const m = /^\d+(?:\.(\d+))?(?:e-(\d+))?$/.exec(String(mintick));
99
- const decimals = m ? (m[1]?.length ?? 0) + Number(m[2] ?? 0) : 0;
100
- const mask = decimals ? '#.' + '#'.repeat(decimals) : '#';
101
- return formatNumber(round_to_mintick(value, mintick), mask, 'halfUpShortest');
102
+ const [minmove, scale] = tickScale(mintick, pricescale);
103
+ const decimals = Math.floor(Math.log10(scale) + 1e-9);
104
+ const mask = decimals > 0 ? '0.' + '0'.repeat(decimals) : '0';
105
+ if (!(minmove >= 1)) return formatNumber(value, mask, 'halfUpShortest');
106
+ // 1. the double quotient |x| / (1 / pricescale), half away from zero (not math.round_to_mintick's exact decimal)
107
+ const scaled = Math.abs(value) / (1 / scale);
108
+ let units = Math.trunc(scaled);
109
+ if (scaled - units >= 0.5) units++;
110
+ // 2. to the tick, a half going up (minmove 2: -0.01 gives "0.00", 0.01 gives "0.02")
111
+ const ticks = Math.floor((value < 0 ? -units : units) / minmove + 0.5);
112
+ // 3. the tick value at the scale's decimals; a negative one keeps its sign when it prints as zero ("-0.0")
113
+ const text = formatNumber((Math.abs(ticks) * minmove) / scale, mask, 'halfEvenExact');
114
+ return ticks < 0 ? '-' + text : text;
102
115
  }
103
116
  default:
104
117
  return formatNumber(value, format, 'halfUpShortest');
@@ -230,3 +230,16 @@ function affixes(p: Pattern, negative: boolean, body: string): string {
230
230
  if (p.negPrefix === null) return '-' + p.prefix + body + p.suffix;
231
231
  return p.negPrefix + body + p.negSuffix!;
232
232
  }
233
+
234
+ /**
235
+ * `[minmove, pricescale]` of a tick (PineScript `syminfo.mintick = syminfo.minmove / syminfo.pricescale`).
236
+ * Without `pricescale` it is 10^(decimals of the tick): 0.02 is 2 / 100, 0.25 is 25 / 100. A symbol priced in
237
+ * fractions needs its own (1/32 is 1 / 32).
238
+ */
239
+ export function tickScale(mintick: number, pricescale?: number): [number, number] {
240
+ if (pricescale === undefined) {
241
+ const m = /^\d+(?:\.(\d+))?(?:e-(\d+))?$/.exec(String(mintick));
242
+ pricescale = Number(`1e${m ? (m[1]?.length ?? 0) + Number(m[2] ?? 0) : 0}`);
243
+ }
244
+ return [Math.round(mintick * pricescale), pricescale];
245
+ }