oakscriptjs 0.9.6 → 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 (104) 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 +6 -3
  19. package/dist/cjs/index.d.ts.map +1 -1
  20. package/dist/cjs/index.js +9 -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/types.d.ts +22 -0
  27. package/dist/cjs/runtime/types.d.ts.map +1 -1
  28. package/dist/cjs/runtime/types.js.map +1 -1
  29. package/dist/cjs/script/index.d.ts +82 -5
  30. package/dist/cjs/script/index.d.ts.map +1 -1
  31. package/dist/cjs/script/index.js +141 -26
  32. package/dist/cjs/script/index.js.map +3 -3
  33. package/dist/cjs/table/index.d.ts +87 -0
  34. package/dist/cjs/table/index.d.ts.map +1 -0
  35. package/dist/cjs/table/index.js +150 -0
  36. package/dist/cjs/table/index.js.map +7 -0
  37. package/dist/cjs/text/index.d.ts +17 -2
  38. package/dist/cjs/text/index.d.ts.map +1 -1
  39. package/dist/cjs/text/index.js +15 -1
  40. package/dist/cjs/text/index.js.map +2 -2
  41. package/dist/cjs/types/index.d.ts +30 -1
  42. package/dist/cjs/types/index.d.ts.map +1 -1
  43. package/dist/cjs/types/index.js +12 -0
  44. package/dist/cjs/types/index.js.map +2 -2
  45. package/dist/cjs/types/metadata.d.ts +258 -26
  46. package/dist/cjs/types/metadata.d.ts.map +1 -1
  47. package/dist/cjs/types/metadata.js.map +1 -1
  48. package/dist/esm/array/index.d.ts +9 -23
  49. package/dist/esm/array/index.d.ts.map +1 -1
  50. package/dist/esm/array/index.js +10 -22
  51. package/dist/esm/array/index.js.map +1 -1
  52. package/dist/esm/drawing/bar-time.d.ts +24 -0
  53. package/dist/esm/drawing/bar-time.d.ts.map +1 -0
  54. package/dist/esm/drawing/bar-time.js +44 -0
  55. package/dist/esm/drawing/bar-time.js.map +1 -0
  56. package/dist/esm/drawing/output.d.ts +32 -0
  57. package/dist/esm/drawing/output.d.ts.map +1 -0
  58. package/dist/esm/drawing/output.js +190 -0
  59. package/dist/esm/drawing/output.js.map +1 -0
  60. package/dist/esm/drawing/registry.d.ts +5 -4
  61. package/dist/esm/drawing/registry.d.ts.map +1 -1
  62. package/dist/esm/drawing/registry.js +12 -2
  63. package/dist/esm/drawing/registry.js.map +1 -1
  64. package/dist/esm/index.d.ts +6 -3
  65. package/dist/esm/index.d.ts.map +1 -1
  66. package/dist/esm/index.js +7 -3
  67. package/dist/esm/index.js.map +1 -1
  68. package/dist/esm/polyline/index.d.ts +3 -5
  69. package/dist/esm/polyline/index.d.ts.map +1 -1
  70. package/dist/esm/polyline/index.js +3 -9
  71. package/dist/esm/polyline/index.js.map +1 -1
  72. package/dist/esm/runtime/types.d.ts +22 -0
  73. package/dist/esm/runtime/types.d.ts.map +1 -1
  74. package/dist/esm/script/index.d.ts +82 -5
  75. package/dist/esm/script/index.d.ts.map +1 -1
  76. package/dist/esm/script/index.js +167 -21
  77. package/dist/esm/script/index.js.map +1 -1
  78. package/dist/esm/table/index.d.ts +87 -0
  79. package/dist/esm/table/index.d.ts.map +1 -0
  80. package/dist/esm/table/index.js +169 -0
  81. package/dist/esm/table/index.js.map +1 -0
  82. package/dist/esm/text/index.d.ts +17 -2
  83. package/dist/esm/text/index.d.ts.map +1 -1
  84. package/dist/esm/text/index.js +17 -2
  85. package/dist/esm/text/index.js.map +1 -1
  86. package/dist/esm/types/index.d.ts +30 -1
  87. package/dist/esm/types/index.d.ts.map +1 -1
  88. package/dist/esm/types/index.js +14 -0
  89. package/dist/esm/types/index.js.map +1 -1
  90. package/dist/esm/types/metadata.d.ts +258 -26
  91. package/dist/esm/types/metadata.d.ts.map +1 -1
  92. package/package.json +1 -1
  93. package/src/array/index.ts +12 -23
  94. package/src/drawing/bar-time.ts +42 -0
  95. package/src/drawing/output.ts +214 -0
  96. package/src/drawing/registry.ts +16 -6
  97. package/src/index.ts +9 -3
  98. package/src/polyline/index.ts +3 -12
  99. package/src/runtime/types.ts +23 -0
  100. package/src/script/index.ts +254 -24
  101. package/src/table/index.ts +221 -0
  102. package/src/text/index.ts +18 -2
  103. package/src/types/index.ts +32 -1
  104. package/src/types/metadata.ts +325 -26
@@ -37,18 +37,35 @@ 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,
@@ -65,10 +82,12 @@ import * as labelCore from '../label/index.js';
65
82
  import * as boxCore from '../box/index.js';
66
83
  import * as linefillCore from '../linefill/index.js';
67
84
  import * as polylineCore from '../polyline/index.js';
85
+ import * as tableCore from '../table/index.js';
68
86
  import * as chartPointCore from '../chartpoint/index.js';
69
87
  import * as textCore from '../text/index.js';
70
88
  import * as mathCore from '../math/index.js';
71
89
  import { resetDrawings, setDrawingLimits } from '../drawing/registry.js';
90
+ import { drawingOutputs } from '../drawing/output.js';
72
91
  import * as timeframeCore from '../timeframe/index.js';
73
92
  import * as timeCore from '../time/index.js';
74
93
  import * as strCore from '../str/index.js';
@@ -76,6 +95,7 @@ import { formatMessage } from '../str/messageformat.js';
76
95
  import { TradingCalendar, type SessionSpec } from '../session/calendar.js';
77
96
  import { SessionBars, sessionFlags, type SessionFlags } from '../session/bars.js';
78
97
  import { afterCompletion, heikinAshi, mapToChart, periodsOf, resample } from '../security/resample.js';
98
+ import { position } from '../types/index.js';
79
99
  import type { ChartPoint } from '../types/index.js';
80
100
  import {
81
101
  STRATEGY_CONSTANTS,
@@ -110,11 +130,17 @@ export interface ScriptRunResult {
110
130
  shapeConfig: ShapeConfig[];
111
131
  arrowConfig: ArrowConfig[];
112
132
  barColorConfig: BarColorConfig[];
133
+ /** plotcandle() / plotbar() declarations */
134
+ candleConfig: CandleConfig[];
113
135
  defaultInputs: Record<string, unknown>;
114
136
  alertConfig: AlertConditionConfig[];
115
137
  /** Strategy properties (declared values, else the PineScript defaults) when the script declares strategy(). */
116
138
  strategyConfig?: StrategyProperties;
117
- /** The renderable output (plots keyed by plotConfig ids, plot-pair fills). */
139
+ /**
140
+ * The renderable output: plots keyed by plotConfig ids, plot-pair fills, markers, arrows, background and bar
141
+ * colours, candles and bars, and the drawings (labels, lines, boxes, linefills, polylines, tables) alive after the
142
+ * last bar.
143
+ */
118
144
  result: IndicatorResult & { alerts?: AlertState[] };
119
145
  }
120
146
 
@@ -159,8 +185,11 @@ interface Collector {
159
185
  arrowConfig: ArrowConfig[];
160
186
  arrows: ArrowData[];
161
187
  barColorConfig: BarColorConfig[];
162
- bgcolors: BarColorData[];
163
- barcolors: BarColorData[];
188
+ bgColors: BgColorData[];
189
+ barColors: BarColorData[];
190
+ candleConfig: CandleConfig[];
191
+ plotCandles: Record<string, PlotCandleData[]>;
192
+ plotBars: Record<string, PlotBarData[]>;
164
193
  alertConfig: AlertConditionConfig[];
165
194
  alerts: AlertState[];
166
195
  strategy?: StrategyRun;
@@ -210,8 +239,11 @@ function freshCollector(inputValues: Record<string, unknown>): Collector {
210
239
  arrowConfig: [],
211
240
  arrows: [],
212
241
  barColorConfig: [],
213
- bgcolors: [],
214
- barcolors: [],
242
+ bgColors: [],
243
+ barColors: [],
244
+ candleConfig: [],
245
+ plotCandles: {},
246
+ plotBars: {},
215
247
  alertConfig: [],
216
248
  alerts: [],
217
249
  };
@@ -284,6 +316,7 @@ export function executeScript(
284
316
  shapeConfig: c.shapeConfig,
285
317
  arrowConfig: c.arrowConfig,
286
318
  barColorConfig: c.barColorConfig,
319
+ candleConfig: c.candleConfig,
287
320
  defaultInputs: c.defaultInputs,
288
321
  alertConfig: c.alertConfig,
289
322
  strategyConfig: c.strategy?.properties,
@@ -293,8 +326,11 @@ export function executeScript(
293
326
  fills: c.fills.length ? c.fills : undefined,
294
327
  markers: c.markers.length ? c.markers : undefined,
295
328
  arrows: c.arrows.length ? c.arrows : undefined,
296
- bgcolors: c.bgcolors.length ? c.bgcolors : undefined,
297
- barcolors: c.barcolors.length ? c.barcolors : undefined,
329
+ bgColors: c.bgColors.length ? c.bgColors : undefined,
330
+ barColors: c.barColors.length ? c.barColors : undefined,
331
+ plotCandles: Object.keys(c.plotCandles).length ? c.plotCandles : undefined,
332
+ plotBars: Object.keys(c.plotBars).length ? c.plotBars : undefined,
333
+ ...drawingOutputs(ctxBars.bars, chartCtx.timeUnit),
298
334
  alerts: c.alerts.length ? c.alerts : undefined,
299
335
  },
300
336
  };
@@ -779,8 +815,46 @@ export interface PlotCharOptions extends Omit<PlotShapeOptions, 'style'> {
779
815
  char?: string;
780
816
  }
781
817
 
818
+ /** PineScript shape.* style -> marker shape. */
819
+ const MARKER_SHAPES: Record<ShapeStyle, MarkerShape> = {
820
+ xcross: 'xcross',
821
+ cross: 'cross',
822
+ triangleup: 'triangleUp',
823
+ triangledown: 'triangleDown',
824
+ flag: 'flag',
825
+ circle: 'circle',
826
+ arrowup: 'arrowUp',
827
+ arrowdown: 'arrowDown',
828
+ labelup: 'labelUp',
829
+ labeldown: 'labelDown',
830
+ square: 'square',
831
+ diamond: 'diamond',
832
+ };
833
+
834
+ /**
835
+ * Marker position of a PineScript location. With location.absolute the shape sits on the price: a label pointing
836
+ * down above it, a label pointing up below it, the other shapes (and the characters) centred on it.
837
+ */
838
+ function markerPosition(location: MarkerLocation, shape: ShapeStyle | undefined): MarkerPosition {
839
+ switch (location) {
840
+ case 'abovebar':
841
+ return 'aboveBar';
842
+ case 'belowbar':
843
+ return 'belowBar';
844
+ case 'top':
845
+ return 'top';
846
+ case 'bottom':
847
+ return 'bottom';
848
+ case 'absolute':
849
+ return shape === 'labeldown' ? 'atPriceTop' : shape === 'labelup' ? 'atPriceBottom' : 'atPriceMiddle';
850
+ }
851
+ }
852
+
853
+ /** Default colour of plotshape / plotchar markers. */
854
+ const MARKER_COLOR = '#2962FF';
855
+
782
856
  /** Shared emitter for plotshape()/plotchar(). Emits one marker per bar where
783
- * the condition is truthy (not na, not 0). */
857
+ * the condition is truthy (not na, not 0). A plotchar marker has the shape 'circle' and its character in `char`. */
784
858
  function emitMarkers(
785
859
  kind: 'shape' | 'char',
786
860
  condition: Series,
@@ -806,6 +880,19 @@ function emitMarkers(
806
880
  offset: options.offset,
807
881
  forceOverlay: options.force_overlay,
808
882
  });
883
+ const shapeStyle = kind === 'shape' ? (style as ShapeStyle) : undefined;
884
+ const template: Omit<MarkerData, 'time'> = {
885
+ position: markerPosition(location, shapeStyle),
886
+ shape: shapeStyle ? MARKER_SHAPES[shapeStyle] : 'circle',
887
+ color: options.color ?? MARKER_COLOR,
888
+ id,
889
+ };
890
+ if (char !== undefined) template.char = char;
891
+ if (options.text !== undefined) template.text = options.text;
892
+ if (options.textcolor !== undefined) template.textColor = options.textcolor;
893
+ if (options.size !== undefined) template.size = options.size;
894
+ if (options.tooltip !== undefined) template.tooltip = options.tooltip;
895
+ if (options.force_overlay) template.forceOverlay = true;
809
896
  const values = condition.toArray();
810
897
  const b = bars();
811
898
  const off = options.offset ?? 0;
@@ -814,19 +901,8 @@ function emitMarkers(
814
901
  if (v === undefined || Number.isNaN(v) || v === 0) continue;
815
902
  const j = i + off;
816
903
  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
- });
904
+ const time = b[j]!.time;
905
+ c.markers.push(location === 'absolute' ? { time, ...template, price: v } : { time, ...template });
830
906
  }
831
907
  }
832
908
 
@@ -906,13 +982,14 @@ function emitBarColors(
906
982
  c.barColorConfig.push({ id, kind, title: options.title, offset: options.offset, forceOverlay: options.force_overlay });
907
983
  const b = bars();
908
984
  const off = options.offset ?? 0;
909
- const target = kind === 'bgcolor' ? c.bgcolors : c.barcolors;
985
+ const target: BgColorData[] = kind === 'bgcolor' ? c.bgColors : c.barColors;
986
+ const forceOverlay = kind === 'bgcolor' && options.force_overlay === true;
910
987
  for (let i = 0; i < b.length; i++) {
911
988
  const col = typeof colors === 'string' ? colors : colors[i];
912
989
  if (!col) continue;
913
990
  const j = i + off;
914
991
  if (j < 0 || j >= b.length) continue;
915
- target.push({ time: b[j]!.time, color: col });
992
+ target.push(forceOverlay ? { time: b[j]!.time, color: col, forceOverlay } : { time: b[j]!.time, color: col });
916
993
  }
917
994
  }
918
995
 
@@ -935,6 +1012,129 @@ export function barcolor(
935
1012
  emitBarColors('barcolor', colors, options);
936
1013
  }
937
1014
 
1015
+ /** A colour argument of plotcandle() / plotbar(): a colour, or per-bar colours (null / undefined entries are na). */
1016
+ export type ScriptCandleColor = string | Array<string | null | undefined>;
1017
+
1018
+ export interface PlotCandleOptions {
1019
+ /** Body colour: a colour or a per-bar array (color.when); an na entry gives a transparent body */
1020
+ color?: ScriptCandleColor;
1021
+ /** Wick colour: a colour or a per-bar array */
1022
+ wickcolor?: ScriptCandleColor;
1023
+ /** Border colour: a colour or a per-bar array */
1024
+ bordercolor?: ScriptCandleColor;
1025
+ display?: CandleConfig['display'];
1026
+ /** Draw on the main chart pane even when the script is not an overlay (PineScript `force_overlay`); default false. */
1027
+ force_overlay?: boolean;
1028
+ }
1029
+
1030
+ export interface PlotBarOptions {
1031
+ /** Bar colour: a colour or a per-bar array (color.when); an na entry gives a transparent bar */
1032
+ color?: ScriptCandleColor;
1033
+ display?: CandleConfig['display'];
1034
+ /** Draw on the main chart pane even when the script is not an overlay (PineScript `force_overlay`); default false. */
1035
+ force_overlay?: boolean;
1036
+ }
1037
+
1038
+ /** Colour of bar i: undefined when the argument is not given, 'transparent' for an na entry. */
1039
+ function candleColor(col: ScriptCandleColor | undefined, i: number): string | undefined {
1040
+ if (col === undefined) return undefined;
1041
+ if (typeof col === 'string') return col;
1042
+ return col[i] ?? 'transparent';
1043
+ }
1044
+
1045
+ interface OhlcRow {
1046
+ i: number;
1047
+ open: number;
1048
+ high: number;
1049
+ low: number;
1050
+ close: number;
1051
+ }
1052
+
1053
+ /** The bars where open, high, low and close are all not na (PineScript draws no candle / bar otherwise). */
1054
+ function ohlcRows(o: Series, h: Series, l: Series, cl: Series): OhlcRow[] {
1055
+ const [ov, hv, lv, cv] = [o.toArray(), h.toArray(), l.toArray(), cl.toArray()];
1056
+ const rows: OhlcRow[] = [];
1057
+ for (let i = 0; i < bars().length; i++) {
1058
+ const [open, high, low, close] = [ov[i]!, hv[i]!, lv[i]!, cv[i]!];
1059
+ if (Number.isFinite(open) && Number.isFinite(high) && Number.isFinite(low) && Number.isFinite(close)) {
1060
+ rows.push({ i, open, high, low, close });
1061
+ }
1062
+ }
1063
+ return rows;
1064
+ }
1065
+
1066
+ const staticColor = (col: ScriptCandleColor | undefined): string | undefined => (typeof col === 'string' ? col : undefined);
1067
+
1068
+ /**
1069
+ * PineScript `plotcandle(open, high, low, close, title?, { color, wickcolor, bordercolor, display, force_overlay })`:
1070
+ * candles in `result.plotCandles[id]`, the declaration in `candleConfig`. A bar with an na value has no candle.
1071
+ */
1072
+ export function plotcandle(
1073
+ open: Series,
1074
+ high: Series,
1075
+ low: Series,
1076
+ close: Series,
1077
+ title?: string,
1078
+ options: PlotCandleOptions = {}
1079
+ ): void {
1080
+ const c = collector();
1081
+ const id = `plotcandle${c.candleConfig.filter((x) => x.kind === 'candle').length}`;
1082
+ c.candleConfig.push({
1083
+ id,
1084
+ kind: 'candle',
1085
+ title,
1086
+ color: staticColor(options.color),
1087
+ wickColor: staticColor(options.wickcolor),
1088
+ borderColor: staticColor(options.bordercolor),
1089
+ display: options.display,
1090
+ forceOverlay: options.force_overlay,
1091
+ });
1092
+ const b = bars();
1093
+ c.plotCandles[id] = ohlcRows(open, high, low, close).map(({ i, ...ohlc }) => {
1094
+ const candle: PlotCandleData = { time: b[i]!.time, ...ohlc };
1095
+ const color = candleColor(options.color, i);
1096
+ const wickColor = candleColor(options.wickcolor, i);
1097
+ const borderColor = candleColor(options.bordercolor, i);
1098
+ if (color !== undefined) candle.color = color;
1099
+ if (wickColor !== undefined) candle.wickColor = wickColor;
1100
+ if (borderColor !== undefined) candle.borderColor = borderColor;
1101
+ if (options.force_overlay) candle.forceOverlay = true;
1102
+ return candle;
1103
+ });
1104
+ }
1105
+
1106
+ /**
1107
+ * PineScript `plotbar(open, high, low, close, title?, { color, display, force_overlay })`: OHLC bars in
1108
+ * `result.plotBars[id]`, the declaration in `candleConfig`. A bar with an na value is not drawn.
1109
+ */
1110
+ export function plotbar(
1111
+ open: Series,
1112
+ high: Series,
1113
+ low: Series,
1114
+ close: Series,
1115
+ title?: string,
1116
+ options: PlotBarOptions = {}
1117
+ ): void {
1118
+ const c = collector();
1119
+ const id = `plotbar${c.candleConfig.filter((x) => x.kind === 'bar').length}`;
1120
+ c.candleConfig.push({
1121
+ id,
1122
+ kind: 'bar',
1123
+ title,
1124
+ color: staticColor(options.color),
1125
+ display: options.display,
1126
+ forceOverlay: options.force_overlay,
1127
+ });
1128
+ const b = bars();
1129
+ c.plotBars[id] = ohlcRows(open, high, low, close).map(({ i, ...ohlc }) => {
1130
+ const bar: PlotBarData = { time: b[i]!.time, ...ohlc };
1131
+ const color = candleColor(options.color, i);
1132
+ if (color !== undefined) bar.color = color;
1133
+ if (options.force_overlay) bar.forceOverlay = true;
1134
+ return bar;
1135
+ });
1136
+ }
1137
+
938
1138
  // ── Per-bar execution (eachBar) ──────────────────────────────────────────────
939
1139
 
940
1140
  /**
@@ -1831,7 +2031,20 @@ export const polyline = {
1831
2031
  },
1832
2032
  };
1833
2033
 
1834
- /** PineScript `text.format_*` constants. */
2034
+ /**
2035
+ * PineScript `table.*`; `table.all` lists the live tables in creation order. The live tables go to `result.tables`.
2036
+ */
2037
+ export const table = {
2038
+ ...tableCore,
2039
+ get all() {
2040
+ return tableCore.all();
2041
+ },
2042
+ };
2043
+
2044
+ /** PineScript `position.*` constants (table positions). */
2045
+ export { position };
2046
+
2047
+ /** PineScript `text.format_*`, `text.align_*` and `text.wrap_*` constants. */
1835
2048
  export const text = { ...textCore };
1836
2049
 
1837
2050
  /**
@@ -1913,10 +2126,27 @@ export type {
1913
2126
  ShapeConfig,
1914
2127
  ArrowConfig,
1915
2128
  BarColorConfig,
2129
+ CandleConfig,
1916
2130
  MarkerData,
2131
+ MarkerPosition,
2132
+ MarkerShape,
1917
2133
  ArrowData,
1918
2134
  BarColorData,
2135
+ BgColorData,
2136
+ PlotCandleData,
2137
+ PlotBarData,
2138
+ LabelData,
2139
+ LabelStyle,
2140
+ LineDrawingData,
2141
+ BoxData,
2142
+ LinefillData,
2143
+ PolylineData,
2144
+ TableData,
2145
+ TableCellData,
2146
+ TableMergeData,
2147
+ TablePosition,
1919
2148
  MarkerLocation,
1920
2149
  MarkerSize,
2150
+ PineSize,
1921
2151
  ShapeStyle,
1922
2152
  };
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Table namespace
3
+ * Functions for creating and filling table drawing objects.
4
+ *
5
+ * @remarks
6
+ * A table is drawn at a fixed position of the pane (`position.*`), not at a bar. Tables have no getters in
7
+ * PineScript; the script API returns the live tables in `result.tables`.
8
+ *
9
+ * PineScript rules:
10
+ * - `table.cell` defines a cell: the arguments it does not give get their default, a previous definition of the
11
+ * cell is replaced
12
+ * - `table.cell_set_*` changes one property of a cell (a cell that was not defined is created)
13
+ * - a column or row outside the table is a runtime error
14
+ * - a deleted table is `na`: its functions do nothing
15
+ *
16
+ * @version 6
17
+ */
18
+
19
+ import { all as allDrawings, isDeleted, register, remove } from '../drawing/registry.js';
20
+ import type { Table, TableCell, color } from '../types/index.js';
21
+
22
+ type TablePosition = Table['position'];
23
+ type HAlign = 'left' | 'center' | 'right';
24
+ type VAlign = 'top' | 'center' | 'bottom';
25
+ type FontFamily = 'default' | 'monospace';
26
+
27
+ /**
28
+ * Creates a new table (PineScript `table.new`).
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const t = table.new(position.top_right, 2, 3, '#131722', '#787B86', 1);
33
+ * table.cell(t, 0, 0, 'RSI', 0, 0, '#FFFFFF');
34
+ * ```
35
+ */
36
+ export function new_table(
37
+ position: TablePosition,
38
+ columns: number,
39
+ rows: number,
40
+ bgcolor?: color,
41
+ frame_color?: color,
42
+ frame_width?: number,
43
+ border_color?: color,
44
+ border_width?: number,
45
+ force_overlay?: boolean
46
+ ): Table {
47
+ return register('table', {
48
+ position,
49
+ columns,
50
+ rows,
51
+ bgcolor,
52
+ frame_color,
53
+ frame_width,
54
+ border_color,
55
+ border_width,
56
+ force_overlay: force_overlay ?? false,
57
+ cells: new Map(),
58
+ merges: [],
59
+ });
60
+ }
61
+
62
+ function checkCell(caller: string, id: Table, column: number, row: number): void {
63
+ if (!(column >= 0 && column < id.columns && row >= 0 && row < id.rows)) {
64
+ throw new RangeError(
65
+ `${caller}: column ${column}, row ${row} is outside the table (${id.columns} columns, ${id.rows} rows).`
66
+ );
67
+ }
68
+ }
69
+
70
+ const key = (column: number, row: number): string => `${column},${row}`;
71
+
72
+ /** The cell at column / row, created when it was not defined. */
73
+ function cellOf(caller: string, id: Table, column: number, row: number): TableCell {
74
+ checkCell(caller, id, column, row);
75
+ const cells = (id.cells ??= new Map());
76
+ let cell = cells.get(key(column, row));
77
+ if (!cell) cells.set(key(column, row), (cell = { text: '' }));
78
+ return cell;
79
+ }
80
+
81
+ /**
82
+ * Defines a cell (PineScript `table.cell`): its text and properties. Omitted properties get their default.
83
+ */
84
+ export function cell(
85
+ table_id: Table,
86
+ column: number,
87
+ row: number,
88
+ text?: string,
89
+ width?: number,
90
+ height?: number,
91
+ text_color?: color,
92
+ text_halign?: HAlign,
93
+ text_valign?: VAlign,
94
+ text_size?: string | number,
95
+ bgcolor?: color,
96
+ tooltip?: string,
97
+ text_font_family?: FontFamily,
98
+ text_formatting?: number
99
+ ): void {
100
+ if (isDeleted(table_id)) return;
101
+ checkCell('table.cell()', table_id, column, row);
102
+ (table_id.cells ??= new Map()).set(key(column, row), {
103
+ text: text ?? '',
104
+ width,
105
+ height,
106
+ text_color,
107
+ text_halign,
108
+ text_valign,
109
+ text_size,
110
+ bgcolor,
111
+ tooltip,
112
+ text_font_family,
113
+ text_formatting,
114
+ });
115
+ }
116
+
117
+ function setCell<K extends keyof TableCell>(name: string, field: K) {
118
+ return (table_id: Table, column: number, row: number, value: TableCell[K]): void => {
119
+ if (isDeleted(table_id)) return;
120
+ cellOf(`table.${name}()`, table_id, column, row)[field] = value;
121
+ };
122
+ }
123
+
124
+ /** Sets the text of a cell. */
125
+ export const cell_set_text = setCell('cell_set_text', 'text');
126
+ /** Sets the width of a cell (% of the chart width; 0 fits the text). */
127
+ export const cell_set_width = setCell('cell_set_width', 'width');
128
+ /** Sets the height of a cell (% of the chart height; 0 fits the text). */
129
+ export const cell_set_height = setCell('cell_set_height', 'height');
130
+ /** Sets the text colour of a cell. */
131
+ export const cell_set_text_color = setCell('cell_set_text_color', 'text_color');
132
+ /** Sets the horizontal text alignment of a cell. */
133
+ export const cell_set_text_halign = setCell('cell_set_text_halign', 'text_halign');
134
+ /** Sets the vertical text alignment of a cell. */
135
+ export const cell_set_text_valign = setCell('cell_set_text_valign', 'text_valign');
136
+ /** Sets the text size of a cell. */
137
+ export const cell_set_text_size = setCell('cell_set_text_size', 'text_size');
138
+ /** Sets the background colour of a cell. */
139
+ export const cell_set_bgcolor = setCell('cell_set_bgcolor', 'bgcolor');
140
+ /** Sets the tooltip of a cell. */
141
+ export const cell_set_tooltip = setCell('cell_set_tooltip', 'tooltip');
142
+ /** Sets the font family of a cell. */
143
+ export const cell_set_text_font_family = setCell('cell_set_text_font_family', 'text_font_family');
144
+ /** Sets the text formatting of a cell (`text.format_*` flags). */
145
+ export const cell_set_text_formatting = setCell('cell_set_text_formatting', 'text_formatting');
146
+
147
+ /**
148
+ * Merges the cells from start_column / start_row to end_column / end_row (PineScript `table.merge_cells`). The
149
+ * merged cell shows the content of the start cell.
150
+ */
151
+ export function merge_cells(
152
+ table_id: Table,
153
+ start_column: number,
154
+ start_row: number,
155
+ end_column: number,
156
+ end_row: number
157
+ ): void {
158
+ if (isDeleted(table_id)) return;
159
+ checkCell('table.merge_cells()', table_id, start_column, start_row);
160
+ checkCell('table.merge_cells()', table_id, end_column, end_row);
161
+ if (end_column < start_column || end_row < start_row) {
162
+ throw new RangeError('table.merge_cells(): the end cell is before the start cell.');
163
+ }
164
+ (table_id.merges ??= []).push({ start_column, start_row, end_column, end_row });
165
+ }
166
+
167
+ /**
168
+ * Removes the cells from start_column / start_row to end_column / end_row (PineScript `table.clear`; default end:
169
+ * the start cell), and the merges that start in that range.
170
+ */
171
+ export function clear(
172
+ table_id: Table,
173
+ start_column: number,
174
+ start_row: number,
175
+ end_column: number = start_column,
176
+ end_row: number = start_row
177
+ ): void {
178
+ if (isDeleted(table_id)) return;
179
+ checkCell('table.clear()', table_id, start_column, start_row);
180
+ checkCell('table.clear()', table_id, end_column, end_row);
181
+ const inside = (column: number, row: number): boolean =>
182
+ column >= start_column && column <= end_column && row >= start_row && row <= end_row;
183
+ for (let column = start_column; column <= end_column; column++) {
184
+ for (let row = start_row; row <= end_row; row++) table_id.cells?.delete(key(column, row));
185
+ }
186
+ if (table_id.merges) table_id.merges = table_id.merges.filter((m) => !inside(m.start_column, m.start_row));
187
+ }
188
+
189
+ function setTable<K extends keyof Table>(field: K) {
190
+ return (table_id: Table, value: Table[K]): void => {
191
+ if (isDeleted(table_id)) return;
192
+ table_id[field] = value;
193
+ };
194
+ }
195
+
196
+ /** Moves the table to another position (`position.*`). */
197
+ export const set_position = setTable('position');
198
+ /** Sets the background colour of the table. */
199
+ export const set_bgcolor = setTable('bgcolor');
200
+ /** Sets the colour of the outer frame. */
201
+ export const set_frame_color = setTable('frame_color');
202
+ /** Sets the width of the outer frame. */
203
+ export const set_frame_width = setTable('frame_width');
204
+ /** Sets the colour of the cell borders. */
205
+ export const set_border_color = setTable('border_color');
206
+ /** Sets the width of the cell borders. */
207
+ export const set_border_width = setTable('border_width');
208
+
209
+ /** Deletes the table (PineScript `table.delete`). */
210
+ export function delete_table(table_id: Table): void {
211
+ remove('table', table_id);
212
+ }
213
+
214
+ /** All live tables, in creation order (PineScript `table.all`). A new array. */
215
+ export function all(): Table[] {
216
+ return allDrawings('table');
217
+ }
218
+
219
+ // Alias to match PineScript API
220
+ export { new_table as new };
221
+ export { delete_table as delete };
package/src/text/index.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Text namespace
3
- * PineScript `text.format_*` constants for `label.set_text_formatting` and `box.set_text_formatting`.
3
+ * PineScript `text.format_*` constants for `label.set_text_formatting` and `box.set_text_formatting`, and the
4
+ * `text.align_*` / `text.wrap_*` constants.
4
5
  *
5
- * The values are flags that add up, as in PineScript (`text.format_bold + text.format_italic`).
6
+ * The format values are flags that add up, as in PineScript (`text.format_bold + text.format_italic`).
6
7
  * They are the values PineScript uses for drawn labels and boxes.
7
8
  */
8
9
 
@@ -12,3 +13,18 @@ export const format_none = 0;
12
13
  export const format_bold = 1;
13
14
  /** Italic text. */
14
15
  export const format_italic = 2;
16
+
17
+ /** Text aligned to the left (`table.cell`, `box.new`, `label.new` textalign). */
18
+ export const align_left = 'left' as const;
19
+ /** Text centred horizontally or vertically. */
20
+ export const align_center = 'center' as const;
21
+ /** Text aligned to the right. */
22
+ export const align_right = 'right' as const;
23
+ /** Text aligned to the top (`table.cell`, `box.new` text_valign). */
24
+ export const align_top = 'top' as const;
25
+ /** Text aligned to the bottom. */
26
+ export const align_bottom = 'bottom' as const;
27
+ /** Text wrapped to the box width (`box.new` text_wrap). */
28
+ export const wrap_auto = 'auto' as const;
29
+ /** Text not wrapped. */
30
+ export const wrap_none = 'none' as const;