@backtest-kit/pinets 14.1.0 → 15.0.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 +245 -245
- package/build/index.cjs +106 -85
- package/build/index.mjs +106 -85
- package/package.json +90 -90
- package/types.d.ts +6 -0
package/build/index.mjs
CHANGED
|
@@ -6,7 +6,9 @@ import { singleshot, memoize, str, randomString } from 'functools-kit';
|
|
|
6
6
|
import fs from 'fs/promises';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// Symbol.for keeps the brand check working when CJS and ESM copies of the
|
|
10
|
+
// package end up in the same process (dual-package hazard).
|
|
11
|
+
const CODE_TYPE_SYMBOL = Symbol.for("backtest-kit.pinets.code-type");
|
|
10
12
|
class Code {
|
|
11
13
|
constructor(source) {
|
|
12
14
|
this.source = source;
|
|
@@ -25,7 +27,9 @@ Code.isCode = (value) => {
|
|
|
25
27
|
value.__type__ === CODE_TYPE_SYMBOL);
|
|
26
28
|
};
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
// Symbol.for keeps the brand check working when CJS and ESM copies of the
|
|
31
|
+
// package end up in the same process (dual-package hazard).
|
|
32
|
+
const FILE_TYPE_SYMBOL = Symbol.for("backtest-kit.pinets.file-type");
|
|
29
33
|
class File {
|
|
30
34
|
constructor(path, baseDir) {
|
|
31
35
|
this.path = path;
|
|
@@ -240,6 +244,22 @@ const ExchangeContextService = scoped(class {
|
|
|
240
244
|
}
|
|
241
245
|
});
|
|
242
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Known quote assets for base/quote splitting in getSymbolInfo, longest first.
|
|
249
|
+
* Symbols with an unrecognized quote keep the whole ticker as base.
|
|
250
|
+
*/
|
|
251
|
+
const QUOTE_ASSETS = [
|
|
252
|
+
"USDT",
|
|
253
|
+
"USDC",
|
|
254
|
+
"BUSD",
|
|
255
|
+
"TUSD",
|
|
256
|
+
"FDUSD",
|
|
257
|
+
"USD",
|
|
258
|
+
"BTC",
|
|
259
|
+
"ETH",
|
|
260
|
+
"BNB",
|
|
261
|
+
"EUR",
|
|
262
|
+
];
|
|
243
263
|
const PINE_TF_MAP = {
|
|
244
264
|
"1": "1m",
|
|
245
265
|
"3": "3m",
|
|
@@ -283,6 +303,11 @@ class CandleProviderService {
|
|
|
283
303
|
.toUpperCase()
|
|
284
304
|
.replace(/^BINANCE:|^BYBIT:|^OKX:/, "");
|
|
285
305
|
const normalizedTimeframe = PINE_TF_MAP[timeframe] ?? timeframe;
|
|
306
|
+
if (!INTERVAL_MINUTES[normalizedTimeframe]) {
|
|
307
|
+
throw new Error(`CandleProvider getMarketData: unknown timeframe=${timeframe}. ` +
|
|
308
|
+
`Allowed Pine values: ${Object.keys(PINE_TF_MAP).join(", ")}; ` +
|
|
309
|
+
`allowed intervals: ${Object.keys(INTERVAL_MINUTES).join(", ")}`);
|
|
310
|
+
}
|
|
286
311
|
let clampedEDate = eDate;
|
|
287
312
|
if (ExecutionContextService.hasContext()) {
|
|
288
313
|
const whenMs = lib.executionContextService.context.when.getTime();
|
|
@@ -308,12 +333,12 @@ class CandleProviderService {
|
|
|
308
333
|
const symbol = tickerId
|
|
309
334
|
.toUpperCase()
|
|
310
335
|
.replace(/^BINANCE:|^BYBIT:|^OKX:/, "");
|
|
311
|
-
const
|
|
312
|
-
const
|
|
336
|
+
const quote = QUOTE_ASSETS.find((asset) => symbol.endsWith(asset) && symbol.length > asset.length) ?? "";
|
|
337
|
+
const base = quote ? symbol.slice(0, symbol.length - quote.length) : symbol;
|
|
313
338
|
const result = {
|
|
314
339
|
ticker: symbol,
|
|
315
340
|
tickerid: symbol,
|
|
316
|
-
description: `${base}/${quote}
|
|
341
|
+
description: quote ? `${base}/${quote}` : symbol,
|
|
317
342
|
type: "crypto",
|
|
318
343
|
basecurrency: base,
|
|
319
344
|
currency: quote || "USDT",
|
|
@@ -358,8 +383,13 @@ class PineJobService {
|
|
|
358
383
|
limit,
|
|
359
384
|
});
|
|
360
385
|
const runner = await CREATE_RUNNER_FN(this, tickerId, timeframe, limit);
|
|
361
|
-
|
|
362
|
-
|
|
386
|
+
// The Indicator class is only required to pass custom inputs. Do not load
|
|
387
|
+
// it otherwise: usePine-only setups (no pinets package, no useIndicator)
|
|
388
|
+
// must keep working for plain script runs.
|
|
389
|
+
const payload = Object.keys(inputs).length
|
|
390
|
+
? await this.indicatorConnectionService.getInstance(code.source, inputs)
|
|
391
|
+
: code.source;
|
|
392
|
+
const result = await runner.run(payload);
|
|
363
393
|
const plots = Object.entries(result.plots).reduce((acm, [key, value]) => {
|
|
364
394
|
if (key === "undefined") {
|
|
365
395
|
return acm;
|
|
@@ -374,12 +404,30 @@ class PineJobService {
|
|
|
374
404
|
}
|
|
375
405
|
}
|
|
376
406
|
|
|
377
|
-
const GET_VALUE_FN = (plots, name, barsBack = 0) => {
|
|
407
|
+
const GET_VALUE_FN = (plots, name, barsBack = 0, defaultValue) => {
|
|
378
408
|
const data = plots[name]?.data;
|
|
379
|
-
if (!data || data.length === 0)
|
|
380
|
-
|
|
409
|
+
if (!data || data.length === 0) {
|
|
410
|
+
if (defaultValue !== undefined) {
|
|
411
|
+
return defaultValue;
|
|
412
|
+
}
|
|
413
|
+
throw new Error(`pinets extract: plot "${name}" is missing or empty. ` +
|
|
414
|
+
`Available plots: ${Object.keys(plots).join(", ") || "<none>"}`);
|
|
415
|
+
}
|
|
381
416
|
const idx = data.length - 1 - barsBack;
|
|
382
|
-
|
|
417
|
+
if (idx < 0) {
|
|
418
|
+
if (defaultValue !== undefined) {
|
|
419
|
+
return defaultValue;
|
|
420
|
+
}
|
|
421
|
+
throw new Error(`pinets extract: plot "${name}" has only ${data.length} points, cannot read barsBack=${barsBack}`);
|
|
422
|
+
}
|
|
423
|
+
const point = data[idx];
|
|
424
|
+
if (point == null || point.value == null) {
|
|
425
|
+
if (defaultValue !== undefined) {
|
|
426
|
+
return defaultValue;
|
|
427
|
+
}
|
|
428
|
+
throw new Error(`pinets extract: plot "${name}" has no value at barsBack=${barsBack}`);
|
|
429
|
+
}
|
|
430
|
+
return point.value;
|
|
383
431
|
};
|
|
384
432
|
const GET_VALUE_AT_FN = (plots, name, i, barsBack = 0) => {
|
|
385
433
|
const data = plots[name]?.data;
|
|
@@ -411,13 +459,19 @@ class PineDataService {
|
|
|
411
459
|
}
|
|
412
460
|
else {
|
|
413
461
|
const raw = GET_VALUE_AT_FN(plots, config.plot, i, config.barsBack ?? 0);
|
|
414
|
-
|
|
462
|
+
const value = raw ?? config.defaultValue ?? null;
|
|
463
|
+
row[key] = (value !== null && config.transform ? config.transform(value) : value);
|
|
415
464
|
}
|
|
416
465
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
466
|
+
let timestamp = "";
|
|
467
|
+
for (const name of plotNames) {
|
|
468
|
+
const point = plots[name]?.data?.[i];
|
|
469
|
+
if (point?.time) {
|
|
470
|
+
timestamp = new Date(point.time).toISOString();
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
row.timestamp = timestamp;
|
|
421
475
|
rows.push(row);
|
|
422
476
|
}
|
|
423
477
|
return rows;
|
|
@@ -434,7 +488,7 @@ class PineDataService {
|
|
|
434
488
|
Object.assign(result, { [key]: GET_VALUE_FN(plots, config) });
|
|
435
489
|
}
|
|
436
490
|
else {
|
|
437
|
-
const value = GET_VALUE_FN(plots, config.plot, config.barsBack ?? 0);
|
|
491
|
+
const value = GET_VALUE_FN(plots, config.plot, config.barsBack ?? 0, config.defaultValue);
|
|
438
492
|
Object.assign(result, {
|
|
439
493
|
[key]: config.transform ? config.transform(value) : value,
|
|
440
494
|
});
|
|
@@ -592,6 +646,9 @@ class PineMarkdownService {
|
|
|
592
646
|
if (entries.length === 0) {
|
|
593
647
|
return [];
|
|
594
648
|
}
|
|
649
|
+
if ("time" in mapping) {
|
|
650
|
+
throw new Error('pineMarkdownService getData: mapping key "time" is reserved for the row timestamp, rename the mapping key');
|
|
651
|
+
}
|
|
595
652
|
const plotNames = entries.map(([key, config]) => ({ key, plotName: getPlotName(config) }));
|
|
596
653
|
const dataLength = Math.max(...plotNames.map(({ plotName }) => plots[plotName]?.data?.length ?? 0));
|
|
597
654
|
if (dataLength === 0) {
|
|
@@ -788,8 +845,11 @@ function useIndicator(ctor) {
|
|
|
788
845
|
pine.indicatorConnectionService.useIndicator(ctor);
|
|
789
846
|
}
|
|
790
847
|
|
|
791
|
-
|
|
792
|
-
|
|
848
|
+
/**
|
|
849
|
+
* Resolves a script source (File reference or inline Code) into Code.
|
|
850
|
+
* Shared by run(), markdown() and getSignal().
|
|
851
|
+
*/
|
|
852
|
+
const getSourceCode = async (source) => {
|
|
793
853
|
if (File.isFile(source)) {
|
|
794
854
|
const code = await pine.pineCacheService.readFile(source.path, source.baseDir);
|
|
795
855
|
return Code.fromString(code);
|
|
@@ -799,14 +859,21 @@ const GET_SOURCE_FN$2 = async (source) => {
|
|
|
799
859
|
}
|
|
800
860
|
throw new Error("Source must be a File or Code instance");
|
|
801
861
|
};
|
|
802
|
-
const
|
|
803
|
-
const VALIDATE_NO_TRADING_FN$1 = () => {
|
|
862
|
+
const VALIDATE_NO_TRADING_FN = () => {
|
|
804
863
|
if (ExecutionContextService.hasContext()) {
|
|
805
864
|
throw new Error(str.newline("Time overrides are not allowed when running scripts in a trading context.", "Please remove the 'when' parameter from the run function call."));
|
|
806
865
|
}
|
|
807
866
|
};
|
|
808
|
-
|
|
809
|
-
|
|
867
|
+
/**
|
|
868
|
+
* Runs a Pine script through PineJobService, optionally scoped to an
|
|
869
|
+
* exchange context and/or a fixed point in time (backtest-style override).
|
|
870
|
+
* Shared by run() and markdown().
|
|
871
|
+
*/
|
|
872
|
+
const runInference = async (script, symbol, timeframe, limit, inputs, exchangeName, when) => {
|
|
873
|
+
if (when) {
|
|
874
|
+
VALIDATE_NO_TRADING_FN();
|
|
875
|
+
}
|
|
876
|
+
let fn = () => pine.pineJobService.run(script, symbol, timeframe, limit, inputs);
|
|
810
877
|
if (exchangeName) {
|
|
811
878
|
fn = ExchangeContextService.runWithContext(fn, { exchangeName });
|
|
812
879
|
}
|
|
@@ -817,15 +884,10 @@ const CREATE_INFERENCE_FN$1 = (script, symbol, timeframe, limit, inputs, exchang
|
|
|
817
884
|
backtest: true,
|
|
818
885
|
});
|
|
819
886
|
}
|
|
820
|
-
return fn;
|
|
821
|
-
};
|
|
822
|
-
const RUN_INFERENCE_FN$1 = async (script, symbol, timeframe, limit, inputs, exchangeName, when) => {
|
|
823
|
-
if (when) {
|
|
824
|
-
VALIDATE_NO_TRADING_FN$1();
|
|
825
|
-
}
|
|
826
|
-
const inference = CREATE_INFERENCE_FN$1(script, symbol, timeframe, limit, inputs, exchangeName, when);
|
|
827
|
-
return await inference();
|
|
887
|
+
return await fn();
|
|
828
888
|
};
|
|
889
|
+
|
|
890
|
+
const METHOD_NAME_RUN$1 = "run.run";
|
|
829
891
|
async function run(source, { symbol, timeframe, limit, inputs = {} }, exchangeName, when) {
|
|
830
892
|
pine.loggerService.info(METHOD_NAME_RUN$1, {
|
|
831
893
|
source,
|
|
@@ -833,8 +895,8 @@ async function run(source, { symbol, timeframe, limit, inputs = {} }, exchangeNa
|
|
|
833
895
|
timeframe,
|
|
834
896
|
limit,
|
|
835
897
|
});
|
|
836
|
-
const script = await
|
|
837
|
-
const { plots } = await
|
|
898
|
+
const script = await getSourceCode(source);
|
|
899
|
+
const { plots } = await runInference(script, symbol, timeframe, limit, inputs, exchangeName, when);
|
|
838
900
|
return plots;
|
|
839
901
|
}
|
|
840
902
|
|
|
@@ -857,6 +919,7 @@ function setLogger(logger) {
|
|
|
857
919
|
pine.loggerService.setLogger(logger);
|
|
858
920
|
}
|
|
859
921
|
|
|
922
|
+
const IS_VALID_PRICE_OPEN_FN = (value) => typeof value === "number" && isFinite(value) && value > 0;
|
|
860
923
|
function toSignalDto(id, data, priceOpen = data.priceOpen) {
|
|
861
924
|
if (data.position === 1) {
|
|
862
925
|
const result = {
|
|
@@ -866,7 +929,7 @@ function toSignalDto(id, data, priceOpen = data.priceOpen) {
|
|
|
866
929
|
priceStopLoss: data.priceStopLoss,
|
|
867
930
|
minuteEstimatedTime: data.minuteEstimatedTime,
|
|
868
931
|
};
|
|
869
|
-
if (priceOpen) {
|
|
932
|
+
if (IS_VALID_PRICE_OPEN_FN(priceOpen)) {
|
|
870
933
|
Object.assign(result, { priceOpen });
|
|
871
934
|
}
|
|
872
935
|
return result;
|
|
@@ -879,7 +942,7 @@ function toSignalDto(id, data, priceOpen = data.priceOpen) {
|
|
|
879
942
|
priceStopLoss: data.priceStopLoss,
|
|
880
943
|
minuteEstimatedTime: data.minuteEstimatedTime,
|
|
881
944
|
};
|
|
882
|
-
if (priceOpen) {
|
|
945
|
+
if (IS_VALID_PRICE_OPEN_FN(priceOpen)) {
|
|
883
946
|
Object.assign(result, { priceOpen });
|
|
884
947
|
}
|
|
885
948
|
return result;
|
|
@@ -889,23 +952,18 @@ function toSignalDto(id, data, priceOpen = data.priceOpen) {
|
|
|
889
952
|
|
|
890
953
|
const METHOD_NAME_RUN = "strategy.getSignal";
|
|
891
954
|
const DEFAULT_ESTIMATED_TIME = 240;
|
|
892
|
-
const GET_SOURCE_FN$1 = async (source) => {
|
|
893
|
-
if (File.isFile(source)) {
|
|
894
|
-
const code = await pine.pineCacheService.readFile(source.path, source.baseDir);
|
|
895
|
-
return Code.fromString(code);
|
|
896
|
-
}
|
|
897
|
-
if (Code.isCode(source)) {
|
|
898
|
-
return source;
|
|
899
|
-
}
|
|
900
|
-
throw new Error("Source must be a File or Code instance");
|
|
901
|
-
};
|
|
902
955
|
const SIGNAL_SCHEMA = {
|
|
903
956
|
position: "Signal",
|
|
904
|
-
priceOpen:
|
|
957
|
+
priceOpen: {
|
|
958
|
+
plot: "Close",
|
|
959
|
+
defaultValue: 0,
|
|
960
|
+
transform: (v) => v,
|
|
961
|
+
},
|
|
905
962
|
priceTakeProfit: "TakeProfit",
|
|
906
963
|
priceStopLoss: "StopLoss",
|
|
907
964
|
minuteEstimatedTime: {
|
|
908
965
|
plot: "EstimatedTime",
|
|
966
|
+
defaultValue: 0,
|
|
909
967
|
transform: (v) => v || DEFAULT_ESTIMATED_TIME,
|
|
910
968
|
},
|
|
911
969
|
};
|
|
@@ -916,7 +974,7 @@ async function getSignal(source, { symbol, timeframe, limit, inputs = {} }) {
|
|
|
916
974
|
timeframe,
|
|
917
975
|
limit,
|
|
918
976
|
});
|
|
919
|
-
const { plots } = await pine.pineJobService.run(await
|
|
977
|
+
const { plots } = await pine.pineJobService.run(await getSourceCode(source), symbol, timeframe, limit, inputs);
|
|
920
978
|
const resultId = randomString();
|
|
921
979
|
const data = pine.pineDataService.extract(plots, SIGNAL_SCHEMA);
|
|
922
980
|
return toSignalDto(resultId, data);
|
|
@@ -935,43 +993,6 @@ async function dumpPlotData(signalId, plots, mapping, taName, outputDir = `./dum
|
|
|
935
993
|
|
|
936
994
|
const TO_MARKDOWN_METHOD_NAME = "markdown.toMarkdown";
|
|
937
995
|
const MARKDOWN_METHOD_NAME = "markdown.markdown";
|
|
938
|
-
const GET_SOURCE_FN = async (source) => {
|
|
939
|
-
if (File.isFile(source)) {
|
|
940
|
-
const code = await pine.pineCacheService.readFile(source.path, source.baseDir);
|
|
941
|
-
return Code.fromString(code);
|
|
942
|
-
}
|
|
943
|
-
if (Code.isCode(source)) {
|
|
944
|
-
return source;
|
|
945
|
-
}
|
|
946
|
-
throw new Error("Source must be a File or Code instance");
|
|
947
|
-
};
|
|
948
|
-
const BASE_RUNNER_FN = async (script, symbol, timeframe, limit, inputs) => await pine.pineJobService.run(script, symbol, timeframe, limit, inputs);
|
|
949
|
-
const VALIDATE_NO_TRADING_FN = () => {
|
|
950
|
-
if (ExecutionContextService.hasContext()) {
|
|
951
|
-
throw new Error(str.newline("Time overrides are not allowed when running scripts in a trading context.", "Please remove the 'when' parameter from the run function call."));
|
|
952
|
-
}
|
|
953
|
-
};
|
|
954
|
-
const CREATE_INFERENCE_FN = (script, symbol, timeframe, limit, inputs, exchangeName, when) => {
|
|
955
|
-
let fn = () => BASE_RUNNER_FN(script, symbol, timeframe, limit, inputs);
|
|
956
|
-
if (exchangeName) {
|
|
957
|
-
fn = ExchangeContextService.runWithContext(fn, { exchangeName });
|
|
958
|
-
}
|
|
959
|
-
if (when) {
|
|
960
|
-
fn = ExecutionContextService.runWithContext(fn, {
|
|
961
|
-
when,
|
|
962
|
-
symbol,
|
|
963
|
-
backtest: true,
|
|
964
|
-
});
|
|
965
|
-
}
|
|
966
|
-
return fn;
|
|
967
|
-
};
|
|
968
|
-
const RUN_INFERENCE_FN = async (script, symbol, timeframe, limit, inputs, exchangeName, when) => {
|
|
969
|
-
if (when) {
|
|
970
|
-
VALIDATE_NO_TRADING_FN();
|
|
971
|
-
}
|
|
972
|
-
const inference = CREATE_INFERENCE_FN(script, symbol, timeframe, limit, inputs, exchangeName, when);
|
|
973
|
-
return await inference();
|
|
974
|
-
};
|
|
975
996
|
async function toMarkdown(signalId, plots, mapping, limit = Number.POSITIVE_INFINITY) {
|
|
976
997
|
pine.loggerService.log(TO_MARKDOWN_METHOD_NAME, {
|
|
977
998
|
signalId,
|
|
@@ -989,7 +1010,7 @@ async function markdown(signalId, source, { symbol, timeframe, limit, inputs = {
|
|
|
989
1010
|
exchangeName,
|
|
990
1011
|
when,
|
|
991
1012
|
});
|
|
992
|
-
const { plots } = await
|
|
1013
|
+
const { plots } = await runInference(await getSourceCode(source), symbol, timeframe, limit, inputs, exchangeName, when);
|
|
993
1014
|
return await pine.pineMarkdownService.getReport(signalId, plots, mapping, Number.POSITIVE_INFINITY);
|
|
994
1015
|
}
|
|
995
1016
|
|
package/package.json
CHANGED
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@backtest-kit/pinets",
|
|
3
|
-
"version": "
|
|
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
|
-
"author": {
|
|
6
|
-
"name": "Petr Tripolsky",
|
|
7
|
-
"email": "tripolskypetr@gmail.com",
|
|
8
|
-
"url": "https://github.com/tripolskypetr"
|
|
9
|
-
},
|
|
10
|
-
"funding": {
|
|
11
|
-
"type": "individual",
|
|
12
|
-
"url": "http://paypal.me/tripolskypetr"
|
|
13
|
-
},
|
|
14
|
-
"license": "MIT",
|
|
15
|
-
"homepage": "https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html",
|
|
16
|
-
"keywords": [
|
|
17
|
-
"pinescript",
|
|
18
|
-
"pine-script",
|
|
19
|
-
"tradingview",
|
|
20
|
-
"pinets",
|
|
21
|
-
"technical-analysis",
|
|
22
|
-
"trading-bot",
|
|
23
|
-
"algorithmic-trading",
|
|
24
|
-
"backtest",
|
|
25
|
-
"backtesting",
|
|
26
|
-
"cryptocurrency",
|
|
27
|
-
"forex",
|
|
28
|
-
"strategy",
|
|
29
|
-
"indicators",
|
|
30
|
-
"rsi",
|
|
31
|
-
"macd",
|
|
32
|
-
"ema",
|
|
33
|
-
"bollinger-bands"
|
|
34
|
-
],
|
|
35
|
-
"files": [
|
|
36
|
-
"build",
|
|
37
|
-
"types.d.ts",
|
|
38
|
-
"README.md"
|
|
39
|
-
],
|
|
40
|
-
"repository": {
|
|
41
|
-
"type": "git",
|
|
42
|
-
"url": "https://github.com/tripolskypetr/backtest-kit",
|
|
43
|
-
"documentation": "https://github.com/tripolskypetr/backtest-kit/tree/master/docs"
|
|
44
|
-
},
|
|
45
|
-
"bugs": {
|
|
46
|
-
"url": "https://github.com/tripolskypetr/backtest-kit/issues"
|
|
47
|
-
},
|
|
48
|
-
"scripts": {
|
|
49
|
-
"build": "rollup -c"
|
|
50
|
-
},
|
|
51
|
-
"main": "build/index.cjs",
|
|
52
|
-
"module": "build/index.mjs",
|
|
53
|
-
"source": "src/index.ts",
|
|
54
|
-
"types": "./types.d.ts",
|
|
55
|
-
"exports": {
|
|
56
|
-
"require": "./build/index.cjs",
|
|
57
|
-
"types": "./types.d.ts",
|
|
58
|
-
"import": "./build/index.mjs",
|
|
59
|
-
"default": "./build/index.cjs"
|
|
60
|
-
},
|
|
61
|
-
"devDependencies": {
|
|
62
|
-
"@rollup/plugin-typescript": "11.1.6",
|
|
63
|
-
"@types/node": "22.9.0",
|
|
64
|
-
"glob": "11.0.1",
|
|
65
|
-
"rimraf": "6.0.1",
|
|
66
|
-
"rollup": "3.29.5",
|
|
67
|
-
"rollup-plugin-dts": "6.1.1",
|
|
68
|
-
"rollup-plugin-peer-deps-external": "2.2.4",
|
|
69
|
-
"ts-morph": "27.0.2",
|
|
70
|
-
"tslib": "2.7.0",
|
|
71
|
-
"typedoc": "0.27.9",
|
|
72
|
-
"pinets": "0.9.
|
|
73
|
-
"backtest-kit": "
|
|
74
|
-
"worker-testbed": "
|
|
75
|
-
},
|
|
76
|
-
"peerDependencies": {
|
|
77
|
-
"backtest-kit": "^
|
|
78
|
-
"pinets": "^0.9.
|
|
79
|
-
"typescript": "^5.0.0"
|
|
80
|
-
},
|
|
81
|
-
"dependencies": {
|
|
82
|
-
"di-kit": "^1.1.1",
|
|
83
|
-
"di-scoped": "^1.0.21",
|
|
84
|
-
"functools-kit": "^
|
|
85
|
-
"get-moment-stamp": "^2.0.0"
|
|
86
|
-
},
|
|
87
|
-
"publishConfig": {
|
|
88
|
-
"access": "public"
|
|
89
|
-
}
|
|
90
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@backtest-kit/pinets",
|
|
3
|
+
"version": "15.0.0",
|
|
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
|
+
"author": {
|
|
6
|
+
"name": "Petr Tripolsky",
|
|
7
|
+
"email": "tripolskypetr@gmail.com",
|
|
8
|
+
"url": "https://github.com/tripolskypetr"
|
|
9
|
+
},
|
|
10
|
+
"funding": {
|
|
11
|
+
"type": "individual",
|
|
12
|
+
"url": "http://paypal.me/tripolskypetr"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"homepage": "https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"pinescript",
|
|
18
|
+
"pine-script",
|
|
19
|
+
"tradingview",
|
|
20
|
+
"pinets",
|
|
21
|
+
"technical-analysis",
|
|
22
|
+
"trading-bot",
|
|
23
|
+
"algorithmic-trading",
|
|
24
|
+
"backtest",
|
|
25
|
+
"backtesting",
|
|
26
|
+
"cryptocurrency",
|
|
27
|
+
"forex",
|
|
28
|
+
"strategy",
|
|
29
|
+
"indicators",
|
|
30
|
+
"rsi",
|
|
31
|
+
"macd",
|
|
32
|
+
"ema",
|
|
33
|
+
"bollinger-bands"
|
|
34
|
+
],
|
|
35
|
+
"files": [
|
|
36
|
+
"build",
|
|
37
|
+
"types.d.ts",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/tripolskypetr/backtest-kit",
|
|
43
|
+
"documentation": "https://github.com/tripolskypetr/backtest-kit/tree/master/docs"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/tripolskypetr/backtest-kit/issues"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "rollup -c"
|
|
50
|
+
},
|
|
51
|
+
"main": "build/index.cjs",
|
|
52
|
+
"module": "build/index.mjs",
|
|
53
|
+
"source": "src/index.ts",
|
|
54
|
+
"types": "./types.d.ts",
|
|
55
|
+
"exports": {
|
|
56
|
+
"require": "./build/index.cjs",
|
|
57
|
+
"types": "./types.d.ts",
|
|
58
|
+
"import": "./build/index.mjs",
|
|
59
|
+
"default": "./build/index.cjs"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@rollup/plugin-typescript": "11.1.6",
|
|
63
|
+
"@types/node": "22.9.0",
|
|
64
|
+
"glob": "11.0.1",
|
|
65
|
+
"rimraf": "6.0.1",
|
|
66
|
+
"rollup": "3.29.5",
|
|
67
|
+
"rollup-plugin-dts": "6.1.1",
|
|
68
|
+
"rollup-plugin-peer-deps-external": "2.2.4",
|
|
69
|
+
"ts-morph": "27.0.2",
|
|
70
|
+
"tslib": "2.7.0",
|
|
71
|
+
"typedoc": "0.27.9",
|
|
72
|
+
"pinets": "0.9.27",
|
|
73
|
+
"backtest-kit": "15.0.0",
|
|
74
|
+
"worker-testbed": "3.0.0"
|
|
75
|
+
},
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"backtest-kit": "^15.0.0",
|
|
78
|
+
"pinets": "^0.9.27",
|
|
79
|
+
"typescript": "^5.0.0"
|
|
80
|
+
},
|
|
81
|
+
"dependencies": {
|
|
82
|
+
"di-kit": "^1.1.1",
|
|
83
|
+
"di-scoped": "^1.0.21",
|
|
84
|
+
"functools-kit": "^4.0.0",
|
|
85
|
+
"get-moment-stamp": "^2.0.0"
|
|
86
|
+
},
|
|
87
|
+
"publishConfig": {
|
|
88
|
+
"access": "public"
|
|
89
|
+
}
|
|
90
|
+
}
|
package/types.d.ts
CHANGED
|
@@ -66,6 +66,12 @@ declare function run(source: File | Code, { symbol, timeframe, limit, inputs }:
|
|
|
66
66
|
type PlotExtractConfig<T = number> = {
|
|
67
67
|
plot: string;
|
|
68
68
|
barsBack?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Fallback used when the plot is missing, empty or has no value at the
|
|
71
|
+
* requested position. Without it `extract` throws to surface plot-name
|
|
72
|
+
* mismatches instead of silently substituting zeros.
|
|
73
|
+
*/
|
|
74
|
+
defaultValue?: number;
|
|
69
75
|
transform?: (value: number) => T;
|
|
70
76
|
};
|
|
71
77
|
type PlotMapping = {
|