@backtest-kit/pinets 5.9.0 → 5.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,7 +32,8 @@ Port your TradingView strategies to backtest-kit with zero rewrite. Powered by [
32
32
  |----------|-------------|
33
33
  | **`getSignal()`** | Run Pine Script and get structured `ISignalDto` (position, TP/SL, estimated time) |
34
34
  | **`run()`** | Run Pine Script and return raw plot data |
35
- | **`extract()`** | Extract values from plots with custom mapping |
35
+ | **`extract()`** | Extract the latest bar values from plots with custom mapping |
36
+ | **`extractRows()`** | Extract all bars as a timestamped row array with custom mapping |
36
37
  | **`dumpPlotData()`** | Dump plot data to markdown files for debugging |
37
38
  | **`usePine()`** | Register custom Pine constructor |
38
39
  | **`setLogger()`** | Configure custom logger |
@@ -166,6 +167,52 @@ const data = await extract(plots, {
166
167
  // data = { rsi: 55.2, macd: 12.5, prevRsi: 52.1, trendStrength: 'strong' }
167
168
  ```
168
169
 
170
+ ### Historical Rows Extraction
171
+
172
+ `extractRows()` returns **every bar** as a typed row with a `timestamp` field — useful for building datasets, detecting crossovers across history, or feeding data into downstream analysis.
173
+
174
+ ```typescript
175
+ import { File, run, extractRows } from '@backtest-kit/pinets';
176
+
177
+ const source = File.fromPath('indicators.pine');
178
+
179
+ const plots = await run(source, {
180
+ symbol: 'ETHUSDT',
181
+ timeframe: '1h',
182
+ limit: 200,
183
+ });
184
+
185
+ const rows = await extractRows(plots, {
186
+ // Simple: plot name -> number | null
187
+ rsi: 'RSI',
188
+ macd: 'MACD',
189
+
190
+ // Advanced: with lookback and optional transform
191
+ prevRsi: {
192
+ plot: 'RSI',
193
+ barsBack: 1,
194
+ },
195
+ trend: {
196
+ plot: 'ADX',
197
+ transform: (v) => v > 25 ? 'strong' : 'weak',
198
+ },
199
+ });
200
+
201
+ // rows[0] = { timestamp: '2024-01-01T00:00:00.000Z', rsi: 48.3, macd: -2.1, prevRsi: null, trend: 'weak' }
202
+ // rows[1] = { timestamp: '2024-01-01T01:00:00.000Z', rsi: 52.1, macd: -1.5, prevRsi: 48.3, trend: 'weak' }
203
+ // ...
204
+ ```
205
+
206
+ **Difference between `extract()` and `extractRows()`:**
207
+
208
+ | | `extract()` | `extractRows()` |
209
+ |---|---|---|
210
+ | Returns | Single object (latest bar) | Array of objects (all bars) |
211
+ | Missing value | `0` (fallback) | `null` |
212
+ | `timestamp` field | No | Yes — ISO string from the bar's time |
213
+ | `barsBack` | Looks back from the last bar | Looks back from each bar's own index |
214
+ | Use case | Signal generation at current bar | Dataset export, historical analysis |
215
+
169
216
  ### Debug with Plot Dump
170
217
 
171
218
  Dump plot data to markdown files for analysis and debugging:
package/build/index.cjs CHANGED
@@ -130,6 +130,8 @@ const INTERVAL_MINUTES = {
130
130
  "4h": 240,
131
131
  "6h": 360,
132
132
  "8h": 480,
133
+ "1d": 1440,
134
+ "1w": 10080,
133
135
  };
134
136
  const AXIS_SYMBOL = "_AXIS";
135
137
  class AxisProviderService {
@@ -361,10 +363,47 @@ const GET_VALUE_FN = (plots, name, barsBack = 0) => {
361
363
  const idx = data.length - 1 - barsBack;
362
364
  return idx >= 0 ? (data[idx]?.value ?? 0) : 0;
363
365
  };
366
+ const GET_VALUE_AT_FN = (plots, name, i, barsBack = 0) => {
367
+ const data = plots[name]?.data;
368
+ if (!data)
369
+ return null;
370
+ const idx = i - barsBack;
371
+ return idx >= 0 ? (data[idx]?.value ?? null) : null;
372
+ };
364
373
  class PineDataService {
365
374
  constructor() {
366
375
  this.loggerService = inject(TYPES.loggerService);
367
376
  }
377
+ extractRows(plots, mapping) {
378
+ this.loggerService.log("pineDataService extractRows", {
379
+ plotCount: Object.keys(plots).length,
380
+ mapping,
381
+ });
382
+ const entries = Object.entries(mapping);
383
+ const plotNames = entries.map(([, config]) => typeof config === "string" ? config : config.plot);
384
+ const dataLength = plotNames
385
+ .map((name) => plots[name]?.data?.length ?? 0)
386
+ .reduce((acm, cur) => Math.max(acm, cur), 0);
387
+ const rows = [];
388
+ for (let i = 0; i < dataLength; i++) {
389
+ const row = {};
390
+ for (const [key, config] of entries) {
391
+ if (typeof config === "string") {
392
+ row[key] = GET_VALUE_AT_FN(plots, config, i);
393
+ }
394
+ else {
395
+ const raw = GET_VALUE_AT_FN(plots, config.plot, i, config.barsBack ?? 0);
396
+ row[key] = (raw !== null && config.transform ? config.transform(raw) : raw);
397
+ }
398
+ }
399
+ const firstPlot = plots[plotNames[0]]?.data?.[i];
400
+ row.timestamp = firstPlot?.time
401
+ ? new Date(firstPlot.time).toISOString()
402
+ : "";
403
+ rows.push(row);
404
+ }
405
+ return rows;
406
+ }
368
407
  extract(plots, mapping) {
369
408
  this.loggerService.log("pineDataService extract", {
370
409
  plotCount: Object.keys(plots).length,
@@ -731,7 +770,7 @@ function useIndicator(ctor) {
731
770
  pine.indicatorConnectionService.useIndicator(ctor);
732
771
  }
733
772
 
734
- const METHOD_NAME_RUN$2 = "run.run";
773
+ const METHOD_NAME_RUN$1 = "run.run";
735
774
  const GET_SOURCE_FN$2 = async (source) => {
736
775
  if (File.isFile(source)) {
737
776
  const code = await pine.pineCacheService.readFile(source.path, source.baseDir);
@@ -770,7 +809,7 @@ const RUN_INFERENCE_FN$1 = async (script, symbol, timeframe, limit, inputs, exch
770
809
  return await inference();
771
810
  };
772
811
  async function run(source, { symbol, timeframe, limit, inputs = {} }, exchangeName, when) {
773
- pine.loggerService.info(METHOD_NAME_RUN$2, {
812
+ pine.loggerService.info(METHOD_NAME_RUN$1, {
774
813
  source,
775
814
  symbol,
776
815
  timeframe,
@@ -781,9 +820,16 @@ async function run(source, { symbol, timeframe, limit, inputs = {} }, exchangeNa
781
820
  return plots;
782
821
  }
783
822
 
784
- const METHOD_NAME_RUN$1 = "extract.extract";
823
+ const METHOD_NAME_EXTRACT = "extract.extract";
824
+ const METHOD_NAME_EXTRACT_ROWS = "extractRows.extractRows";
825
+ async function extractRows(plots, mapping) {
826
+ pine.loggerService.info(METHOD_NAME_EXTRACT_ROWS, {
827
+ mapping,
828
+ });
829
+ return pine.pineDataService.extractRows(plots, mapping);
830
+ }
785
831
  async function extract(plots, mapping) {
786
- pine.loggerService.info(METHOD_NAME_RUN$1, {
832
+ pine.loggerService.info(METHOD_NAME_EXTRACT, {
787
833
  mapping,
788
834
  });
789
835
  return pine.pineDataService.extract(plots, mapping);
@@ -934,6 +980,7 @@ exports.Code = Code;
934
980
  exports.File = File;
935
981
  exports.dumpPlotData = dumpPlotData;
936
982
  exports.extract = extract;
983
+ exports.extractRows = extractRows;
937
984
  exports.getSignal = getSignal;
938
985
  exports.lib = pine;
939
986
  exports.markdown = markdown;
package/build/index.mjs CHANGED
@@ -127,6 +127,8 @@ const INTERVAL_MINUTES = {
127
127
  "4h": 240,
128
128
  "6h": 360,
129
129
  "8h": 480,
130
+ "1d": 1440,
131
+ "1w": 10080,
130
132
  };
131
133
  const AXIS_SYMBOL = "_AXIS";
132
134
  class AxisProviderService {
@@ -358,10 +360,47 @@ const GET_VALUE_FN = (plots, name, barsBack = 0) => {
358
360
  const idx = data.length - 1 - barsBack;
359
361
  return idx >= 0 ? (data[idx]?.value ?? 0) : 0;
360
362
  };
363
+ const GET_VALUE_AT_FN = (plots, name, i, barsBack = 0) => {
364
+ const data = plots[name]?.data;
365
+ if (!data)
366
+ return null;
367
+ const idx = i - barsBack;
368
+ return idx >= 0 ? (data[idx]?.value ?? null) : null;
369
+ };
361
370
  class PineDataService {
362
371
  constructor() {
363
372
  this.loggerService = inject(TYPES.loggerService);
364
373
  }
374
+ extractRows(plots, mapping) {
375
+ this.loggerService.log("pineDataService extractRows", {
376
+ plotCount: Object.keys(plots).length,
377
+ mapping,
378
+ });
379
+ const entries = Object.entries(mapping);
380
+ const plotNames = entries.map(([, config]) => typeof config === "string" ? config : config.plot);
381
+ const dataLength = plotNames
382
+ .map((name) => plots[name]?.data?.length ?? 0)
383
+ .reduce((acm, cur) => Math.max(acm, cur), 0);
384
+ const rows = [];
385
+ for (let i = 0; i < dataLength; i++) {
386
+ const row = {};
387
+ for (const [key, config] of entries) {
388
+ if (typeof config === "string") {
389
+ row[key] = GET_VALUE_AT_FN(plots, config, i);
390
+ }
391
+ else {
392
+ const raw = GET_VALUE_AT_FN(plots, config.plot, i, config.barsBack ?? 0);
393
+ row[key] = (raw !== null && config.transform ? config.transform(raw) : raw);
394
+ }
395
+ }
396
+ const firstPlot = plots[plotNames[0]]?.data?.[i];
397
+ row.timestamp = firstPlot?.time
398
+ ? new Date(firstPlot.time).toISOString()
399
+ : "";
400
+ rows.push(row);
401
+ }
402
+ return rows;
403
+ }
365
404
  extract(plots, mapping) {
366
405
  this.loggerService.log("pineDataService extract", {
367
406
  plotCount: Object.keys(plots).length,
@@ -728,7 +767,7 @@ function useIndicator(ctor) {
728
767
  pine.indicatorConnectionService.useIndicator(ctor);
729
768
  }
730
769
 
731
- const METHOD_NAME_RUN$2 = "run.run";
770
+ const METHOD_NAME_RUN$1 = "run.run";
732
771
  const GET_SOURCE_FN$2 = async (source) => {
733
772
  if (File.isFile(source)) {
734
773
  const code = await pine.pineCacheService.readFile(source.path, source.baseDir);
@@ -767,7 +806,7 @@ const RUN_INFERENCE_FN$1 = async (script, symbol, timeframe, limit, inputs, exch
767
806
  return await inference();
768
807
  };
769
808
  async function run(source, { symbol, timeframe, limit, inputs = {} }, exchangeName, when) {
770
- pine.loggerService.info(METHOD_NAME_RUN$2, {
809
+ pine.loggerService.info(METHOD_NAME_RUN$1, {
771
810
  source,
772
811
  symbol,
773
812
  timeframe,
@@ -778,9 +817,16 @@ async function run(source, { symbol, timeframe, limit, inputs = {} }, exchangeNa
778
817
  return plots;
779
818
  }
780
819
 
781
- const METHOD_NAME_RUN$1 = "extract.extract";
820
+ const METHOD_NAME_EXTRACT = "extract.extract";
821
+ const METHOD_NAME_EXTRACT_ROWS = "extractRows.extractRows";
822
+ async function extractRows(plots, mapping) {
823
+ pine.loggerService.info(METHOD_NAME_EXTRACT_ROWS, {
824
+ mapping,
825
+ });
826
+ return pine.pineDataService.extractRows(plots, mapping);
827
+ }
782
828
  async function extract(plots, mapping) {
783
- pine.loggerService.info(METHOD_NAME_RUN$1, {
829
+ pine.loggerService.info(METHOD_NAME_EXTRACT, {
784
830
  mapping,
785
831
  });
786
832
  return pine.pineDataService.extract(plots, mapping);
@@ -926,4 +972,4 @@ async function markdown(signalId, source, { symbol, timeframe, limit, inputs = {
926
972
  return await pine.pineMarkdownService.getReport(signalId, plots, mapping, Number.POSITIVE_INFINITY);
927
973
  }
928
974
 
929
- export { AXIS_SYMBOL, Code, File, dumpPlotData, extract, getSignal, pine as lib, markdown, run, setLogger, toMarkdown, toSignalDto, useIndicator, usePine };
975
+ export { AXIS_SYMBOL, Code, File, dumpPlotData, extract, extractRows, getSignal, pine as lib, markdown, run, setLogger, toMarkdown, toSignalDto, useIndicator, usePine };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtest-kit/pinets",
3
- "version": "5.9.0",
3
+ "version": "5.11.0",
4
4
  "description": "Run TradingView Pine Script strategies in Node.js self hosted environment. Execute existing Pine Script indicators and generate trading signals with 1:1 syntax compatibility via PineTS runtime.",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
@@ -69,12 +69,12 @@
69
69
  "ts-morph": "27.0.2",
70
70
  "tslib": "2.7.0",
71
71
  "typedoc": "0.27.9",
72
- "backtest-kit": "5.9.0",
72
+ "backtest-kit": "5.11.0",
73
73
  "worker-testbed": "1.0.12"
74
74
  },
75
75
  "peerDependencies": {
76
- "backtest-kit": "^5.9.0",
77
- "pinets": "^0.9.7",
76
+ "backtest-kit": "^5.11.0",
77
+ "pinets": "^0.9.8",
78
78
  "typescript": "^5.0.0"
79
79
  },
80
80
  "dependencies": {
package/types.d.ts CHANGED
@@ -74,11 +74,18 @@ type PlotMapping = {
74
74
  type ExtractedData<M extends PlotMapping> = {
75
75
  [K in keyof M]: M[K] extends PlotExtractConfig<infer R> ? R : M[K] extends string ? number : never;
76
76
  };
77
+ type ExtractedDataRow<M extends PlotMapping> = {
78
+ [K in keyof M]: M[K] extends PlotExtractConfig<infer R> ? R | null : M[K] extends string ? number | null : never;
79
+ } & {
80
+ timestamp: string;
81
+ };
77
82
  declare class PineDataService {
78
83
  private readonly loggerService;
84
+ extractRows<M extends PlotMapping>(plots: PlotModel, mapping: M): ExtractedDataRow<M>[];
79
85
  extract<M extends PlotMapping>(plots: PlotModel, mapping: M): ExtractedData<M>;
80
86
  }
81
87
 
88
+ declare function extractRows<M extends PlotMapping>(plots: PlotModel, mapping: M): Promise<ExtractedDataRow<M>[]>;
82
89
  declare function extract<M extends PlotMapping>(plots: PlotModel, mapping: M): Promise<ExtractedData<M>>;
83
90
 
84
91
  interface ILogger {
@@ -226,4 +233,4 @@ declare const pine: {
226
233
  };
227
234
  };
228
235
 
229
- export { AXIS_SYMBOL, type CandleModel, Code, File, type IIndicator, type ILogger, type IPine, type IProvider, type PlotExtractConfig, type PlotMapping, type PlotModel, type PlotRecord, type SymbolInfoModel, type TIndicatorCtor, type TPineCtor, dumpPlotData, extract, getSignal, pine as lib, markdown, run, setLogger, toMarkdown, toSignalDto, useIndicator, usePine };
236
+ export { AXIS_SYMBOL, type CandleModel, Code, type ExtractedData, type ExtractedDataRow, File, type IIndicator, type ILogger, type IPine, type IProvider, type PlotExtractConfig, type PlotMapping, type PlotModel, type PlotRecord, type SymbolInfoModel, type TIndicatorCtor, type TPineCtor, dumpPlotData, extract, extractRows, getSignal, pine as lib, markdown, run, setLogger, toMarkdown, toSignalDto, useIndicator, usePine };