@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 +48 -1
- package/build/index.cjs +51 -4
- package/build/index.mjs +51 -5
- package/package.json +4 -4
- package/types.d.ts +8 -1
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$
|
|
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$
|
|
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
|
|
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(
|
|
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$
|
|
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$
|
|
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
|
|
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(
|
|
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.
|
|
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.
|
|
72
|
+
"backtest-kit": "5.11.0",
|
|
73
73
|
"worker-testbed": "1.0.12"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
|
76
|
-
"backtest-kit": "^5.
|
|
77
|
-
"pinets": "^0.9.
|
|
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 };
|