@backtest-kit/pinets 3.0.0 → 3.0.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.
- package/README.md +2 -0
- package/build/index.cjs +34 -73
- package/build/index.mjs +34 -73
- package/package.json +1 -1
- package/types.d.ts +4 -4
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<img src="https://github.com/tripolskypetr/backtest-kit/raw/refs/heads/master/assets/heraldry.svg" height="45px" align="right">
|
|
2
|
+
|
|
1
3
|
# 📜 @backtest-kit/pinets
|
|
2
4
|
|
|
3
5
|
> Run TradingView Pine Script strategies in Node.js self hosted enviroment. Execute your existing Pine Script indicators and generate trading signals - pure technical analysis with 1:1 syntax compatibility.
|
package/build/index.cjs
CHANGED
|
@@ -463,120 +463,80 @@ const GET_EXECUTION_CONTEXT_FN = () => {
|
|
|
463
463
|
when: "",
|
|
464
464
|
};
|
|
465
465
|
};
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
if (value === null)
|
|
469
|
-
return true;
|
|
470
|
-
if (typeof value !== "number")
|
|
471
|
-
return true;
|
|
472
|
-
if (isNaN(value))
|
|
473
|
-
return true;
|
|
474
|
-
if (!isFinite(value))
|
|
475
|
-
return true;
|
|
476
|
-
return false;
|
|
477
|
-
}
|
|
478
|
-
function extractRowAtIndex(plots, keys, index) {
|
|
479
|
-
let time = null;
|
|
480
|
-
for (const key of keys) {
|
|
481
|
-
const plotData = plots[key]?.data;
|
|
482
|
-
if (plotData && plotData[index]) {
|
|
483
|
-
time = plotData[index].time;
|
|
484
|
-
break;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
if (time === null)
|
|
488
|
-
return null;
|
|
489
|
-
const row = { time };
|
|
490
|
-
for (const key of keys) {
|
|
491
|
-
const plotData = plots[key]?.data;
|
|
492
|
-
if (plotData && plotData[index]) {
|
|
493
|
-
const value = plotData[index].value;
|
|
494
|
-
row[key] = isUnsafe(value) ? null : value;
|
|
495
|
-
}
|
|
496
|
-
else {
|
|
497
|
-
row[key] = null;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
return row;
|
|
466
|
+
function getPlotName(config) {
|
|
467
|
+
return typeof config === "string" ? config : config.plot;
|
|
501
468
|
}
|
|
502
|
-
function
|
|
503
|
-
|
|
504
|
-
if (isUnsafe(row[key])) {
|
|
505
|
-
return false;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
return true;
|
|
469
|
+
function isSafe(value) {
|
|
470
|
+
return typeof value === "number" && !isNaN(value) && isFinite(value);
|
|
509
471
|
}
|
|
472
|
+
const DEFAULT_FORMAT = (v) => v !== null ? Number(v).toFixed(4) : "N/A";
|
|
510
473
|
function generateMarkdownTable(rows, keys, signalId) {
|
|
511
|
-
let markdown = "";
|
|
512
474
|
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN();
|
|
513
|
-
markdown
|
|
475
|
+
let markdown = `# PineScript Technical Analysis Dump\n\n`;
|
|
514
476
|
markdown += `**Signal ID**: ${String(signalId)}\n`;
|
|
515
477
|
if (createdAt) {
|
|
516
478
|
markdown += `**Current datetime**: ${String(createdAt)}\n`;
|
|
517
479
|
}
|
|
518
480
|
markdown += "\n";
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
markdown += header;
|
|
522
|
-
markdown += separator;
|
|
481
|
+
markdown += `| ${keys.join(" | ")} | timestamp |\n`;
|
|
482
|
+
markdown += `| --- | ${keys.map(() => "---").join(" | ")} |\n`;
|
|
523
483
|
for (const row of rows) {
|
|
524
484
|
const timestamp = new Date(row.time).toISOString();
|
|
525
485
|
const cells = keys.map((key) => DEFAULT_FORMAT(row[key]));
|
|
526
|
-
markdown += `| ${
|
|
486
|
+
markdown += `| ${cells.join(" | ")} | ${timestamp} |\n`;
|
|
527
487
|
}
|
|
528
488
|
return markdown;
|
|
529
489
|
}
|
|
530
490
|
class PineMarkdownService {
|
|
531
491
|
constructor() {
|
|
532
492
|
this.loggerService = inject(TYPES.loggerService);
|
|
533
|
-
this.getData = (plots) => {
|
|
534
|
-
this.loggerService.log("pineMarkdownService
|
|
493
|
+
this.getData = (plots, mapping) => {
|
|
494
|
+
this.loggerService.log("pineMarkdownService getData", {
|
|
535
495
|
plotCount: Object.keys(plots).length,
|
|
536
496
|
});
|
|
537
|
-
const
|
|
538
|
-
if (
|
|
497
|
+
const entries = Object.entries(mapping);
|
|
498
|
+
if (entries.length === 0) {
|
|
539
499
|
return [];
|
|
540
500
|
}
|
|
541
|
-
const
|
|
542
|
-
const dataLength =
|
|
501
|
+
const plotNames = entries.map(([key, config]) => ({ key, plotName: getPlotName(config) }));
|
|
502
|
+
const dataLength = Math.max(...plotNames.map(({ plotName }) => plots[plotName]?.data?.length ?? 0));
|
|
543
503
|
if (dataLength === 0) {
|
|
544
504
|
return [];
|
|
545
505
|
}
|
|
546
506
|
const rows = [];
|
|
547
|
-
let warmupComplete = false;
|
|
548
507
|
for (let i = 0; i < dataLength; i++) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
if (
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
continue;
|
|
508
|
+
let time = null;
|
|
509
|
+
const row = { time: 0 };
|
|
510
|
+
for (const { key, plotName } of plotNames) {
|
|
511
|
+
const point = plots[plotName]?.data?.[i];
|
|
512
|
+
if (time === null && point) {
|
|
513
|
+
time = point.time;
|
|
558
514
|
}
|
|
515
|
+
row[key] = isSafe(point?.value) ? point.value : null;
|
|
516
|
+
}
|
|
517
|
+
if (time !== null) {
|
|
518
|
+
row.time = time;
|
|
519
|
+
rows.push(row);
|
|
559
520
|
}
|
|
560
|
-
rows.push(row);
|
|
561
521
|
}
|
|
562
522
|
return rows.slice(-TABLE_ROWS_LIMIT);
|
|
563
523
|
};
|
|
564
|
-
this.getReport = (signalId, plots) => {
|
|
524
|
+
this.getReport = (signalId, plots, mapping) => {
|
|
565
525
|
this.loggerService.log("pineMarkdownService getReport", {
|
|
566
526
|
signalId,
|
|
567
527
|
plotCount: Object.keys(plots).length,
|
|
568
528
|
});
|
|
569
|
-
const
|
|
570
|
-
const
|
|
529
|
+
const keys = Object.keys(mapping);
|
|
530
|
+
const rows = this.getData(plots, mapping);
|
|
571
531
|
return generateMarkdownTable(rows, keys, signalId);
|
|
572
532
|
};
|
|
573
|
-
this.dump = async (signalId, plots, taName, outputDir = `./dump/ta/${taName}`) => {
|
|
533
|
+
this.dump = async (signalId, plots, mapping, taName, outputDir = `./dump/ta/${taName}`) => {
|
|
574
534
|
this.loggerService.log("pineMarkdownService dumpSignal", {
|
|
575
535
|
signalId,
|
|
576
536
|
plotCount: Object.keys(plots).length,
|
|
577
537
|
outputDir,
|
|
578
538
|
});
|
|
579
|
-
const content = this.getReport(signalId, plots);
|
|
539
|
+
const content = this.getReport(signalId, plots, mapping);
|
|
580
540
|
const { exchangeName, frameName, strategyName } = GET_METHOD_CONTEXT_FN();
|
|
581
541
|
await backtestKit.Markdown.writeData(taName, content, {
|
|
582
542
|
path: outputDir,
|
|
@@ -756,13 +716,14 @@ async function getSignal(source, { symbol, timeframe, limit }) {
|
|
|
756
716
|
}
|
|
757
717
|
|
|
758
718
|
const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
|
|
759
|
-
async function dumpPlotData(signalId, plots, taName, outputDir = "./dump/ta") {
|
|
719
|
+
async function dumpPlotData(signalId, plots, mapping, taName, outputDir = "./dump/ta") {
|
|
760
720
|
pine.loggerService.log(DUMP_SIGNAL_METHOD_NAME, {
|
|
761
721
|
signalId,
|
|
762
722
|
plotCount: Object.keys(plots).length,
|
|
723
|
+
mapping,
|
|
763
724
|
outputDir,
|
|
764
725
|
});
|
|
765
|
-
return await pine.pineMarkdownService.dump(signalId, plots, taName, outputDir);
|
|
726
|
+
return await pine.pineMarkdownService.dump(signalId, plots, mapping, taName, outputDir);
|
|
766
727
|
}
|
|
767
728
|
|
|
768
729
|
exports.AXIS_SYMBOL = AXIS_SYMBOL;
|
package/build/index.mjs
CHANGED
|
@@ -460,120 +460,80 @@ const GET_EXECUTION_CONTEXT_FN = () => {
|
|
|
460
460
|
when: "",
|
|
461
461
|
};
|
|
462
462
|
};
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
if (value === null)
|
|
466
|
-
return true;
|
|
467
|
-
if (typeof value !== "number")
|
|
468
|
-
return true;
|
|
469
|
-
if (isNaN(value))
|
|
470
|
-
return true;
|
|
471
|
-
if (!isFinite(value))
|
|
472
|
-
return true;
|
|
473
|
-
return false;
|
|
474
|
-
}
|
|
475
|
-
function extractRowAtIndex(plots, keys, index) {
|
|
476
|
-
let time = null;
|
|
477
|
-
for (const key of keys) {
|
|
478
|
-
const plotData = plots[key]?.data;
|
|
479
|
-
if (plotData && plotData[index]) {
|
|
480
|
-
time = plotData[index].time;
|
|
481
|
-
break;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
if (time === null)
|
|
485
|
-
return null;
|
|
486
|
-
const row = { time };
|
|
487
|
-
for (const key of keys) {
|
|
488
|
-
const plotData = plots[key]?.data;
|
|
489
|
-
if (plotData && plotData[index]) {
|
|
490
|
-
const value = plotData[index].value;
|
|
491
|
-
row[key] = isUnsafe(value) ? null : value;
|
|
492
|
-
}
|
|
493
|
-
else {
|
|
494
|
-
row[key] = null;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
return row;
|
|
463
|
+
function getPlotName(config) {
|
|
464
|
+
return typeof config === "string" ? config : config.plot;
|
|
498
465
|
}
|
|
499
|
-
function
|
|
500
|
-
|
|
501
|
-
if (isUnsafe(row[key])) {
|
|
502
|
-
return false;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
return true;
|
|
466
|
+
function isSafe(value) {
|
|
467
|
+
return typeof value === "number" && !isNaN(value) && isFinite(value);
|
|
506
468
|
}
|
|
469
|
+
const DEFAULT_FORMAT = (v) => v !== null ? Number(v).toFixed(4) : "N/A";
|
|
507
470
|
function generateMarkdownTable(rows, keys, signalId) {
|
|
508
|
-
let markdown = "";
|
|
509
471
|
const { when: createdAt } = GET_EXECUTION_CONTEXT_FN();
|
|
510
|
-
markdown
|
|
472
|
+
let markdown = `# PineScript Technical Analysis Dump\n\n`;
|
|
511
473
|
markdown += `**Signal ID**: ${String(signalId)}\n`;
|
|
512
474
|
if (createdAt) {
|
|
513
475
|
markdown += `**Current datetime**: ${String(createdAt)}\n`;
|
|
514
476
|
}
|
|
515
477
|
markdown += "\n";
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
markdown += header;
|
|
519
|
-
markdown += separator;
|
|
478
|
+
markdown += `| ${keys.join(" | ")} | timestamp |\n`;
|
|
479
|
+
markdown += `| --- | ${keys.map(() => "---").join(" | ")} |\n`;
|
|
520
480
|
for (const row of rows) {
|
|
521
481
|
const timestamp = new Date(row.time).toISOString();
|
|
522
482
|
const cells = keys.map((key) => DEFAULT_FORMAT(row[key]));
|
|
523
|
-
markdown += `| ${
|
|
483
|
+
markdown += `| ${cells.join(" | ")} | ${timestamp} |\n`;
|
|
524
484
|
}
|
|
525
485
|
return markdown;
|
|
526
486
|
}
|
|
527
487
|
class PineMarkdownService {
|
|
528
488
|
constructor() {
|
|
529
489
|
this.loggerService = inject(TYPES.loggerService);
|
|
530
|
-
this.getData = (plots) => {
|
|
531
|
-
this.loggerService.log("pineMarkdownService
|
|
490
|
+
this.getData = (plots, mapping) => {
|
|
491
|
+
this.loggerService.log("pineMarkdownService getData", {
|
|
532
492
|
plotCount: Object.keys(plots).length,
|
|
533
493
|
});
|
|
534
|
-
const
|
|
535
|
-
if (
|
|
494
|
+
const entries = Object.entries(mapping);
|
|
495
|
+
if (entries.length === 0) {
|
|
536
496
|
return [];
|
|
537
497
|
}
|
|
538
|
-
const
|
|
539
|
-
const dataLength =
|
|
498
|
+
const plotNames = entries.map(([key, config]) => ({ key, plotName: getPlotName(config) }));
|
|
499
|
+
const dataLength = Math.max(...plotNames.map(({ plotName }) => plots[plotName]?.data?.length ?? 0));
|
|
540
500
|
if (dataLength === 0) {
|
|
541
501
|
return [];
|
|
542
502
|
}
|
|
543
503
|
const rows = [];
|
|
544
|
-
let warmupComplete = false;
|
|
545
504
|
for (let i = 0; i < dataLength; i++) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
if (
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
continue;
|
|
505
|
+
let time = null;
|
|
506
|
+
const row = { time: 0 };
|
|
507
|
+
for (const { key, plotName } of plotNames) {
|
|
508
|
+
const point = plots[plotName]?.data?.[i];
|
|
509
|
+
if (time === null && point) {
|
|
510
|
+
time = point.time;
|
|
555
511
|
}
|
|
512
|
+
row[key] = isSafe(point?.value) ? point.value : null;
|
|
513
|
+
}
|
|
514
|
+
if (time !== null) {
|
|
515
|
+
row.time = time;
|
|
516
|
+
rows.push(row);
|
|
556
517
|
}
|
|
557
|
-
rows.push(row);
|
|
558
518
|
}
|
|
559
519
|
return rows.slice(-TABLE_ROWS_LIMIT);
|
|
560
520
|
};
|
|
561
|
-
this.getReport = (signalId, plots) => {
|
|
521
|
+
this.getReport = (signalId, plots, mapping) => {
|
|
562
522
|
this.loggerService.log("pineMarkdownService getReport", {
|
|
563
523
|
signalId,
|
|
564
524
|
plotCount: Object.keys(plots).length,
|
|
565
525
|
});
|
|
566
|
-
const
|
|
567
|
-
const
|
|
526
|
+
const keys = Object.keys(mapping);
|
|
527
|
+
const rows = this.getData(plots, mapping);
|
|
568
528
|
return generateMarkdownTable(rows, keys, signalId);
|
|
569
529
|
};
|
|
570
|
-
this.dump = async (signalId, plots, taName, outputDir = `./dump/ta/${taName}`) => {
|
|
530
|
+
this.dump = async (signalId, plots, mapping, taName, outputDir = `./dump/ta/${taName}`) => {
|
|
571
531
|
this.loggerService.log("pineMarkdownService dumpSignal", {
|
|
572
532
|
signalId,
|
|
573
533
|
plotCount: Object.keys(plots).length,
|
|
574
534
|
outputDir,
|
|
575
535
|
});
|
|
576
|
-
const content = this.getReport(signalId, plots);
|
|
536
|
+
const content = this.getReport(signalId, plots, mapping);
|
|
577
537
|
const { exchangeName, frameName, strategyName } = GET_METHOD_CONTEXT_FN();
|
|
578
538
|
await Markdown.writeData(taName, content, {
|
|
579
539
|
path: outputDir,
|
|
@@ -753,13 +713,14 @@ async function getSignal(source, { symbol, timeframe, limit }) {
|
|
|
753
713
|
}
|
|
754
714
|
|
|
755
715
|
const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
|
|
756
|
-
async function dumpPlotData(signalId, plots, taName, outputDir = "./dump/ta") {
|
|
716
|
+
async function dumpPlotData(signalId, plots, mapping, taName, outputDir = "./dump/ta") {
|
|
757
717
|
pine.loggerService.log(DUMP_SIGNAL_METHOD_NAME, {
|
|
758
718
|
signalId,
|
|
759
719
|
plotCount: Object.keys(plots).length,
|
|
720
|
+
mapping,
|
|
760
721
|
outputDir,
|
|
761
722
|
});
|
|
762
|
-
return await pine.pineMarkdownService.dump(signalId, plots, taName, outputDir);
|
|
723
|
+
return await pine.pineMarkdownService.dump(signalId, plots, mapping, taName, outputDir);
|
|
763
724
|
}
|
|
764
725
|
|
|
765
726
|
export { AXIS_SYMBOL, Code, File, dumpPlotData, extract, getSignal, pine as lib, run, setLogger, toSignalDto, usePine };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtest-kit/pinets",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
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",
|
package/types.d.ts
CHANGED
|
@@ -84,7 +84,7 @@ interface IParams {
|
|
|
84
84
|
declare function getSignal(source: File | Code, { symbol, timeframe, limit }: IParams): Promise<ISignalDto | null>;
|
|
85
85
|
|
|
86
86
|
type ResultId$2 = string | number;
|
|
87
|
-
declare function dumpPlotData(signalId: ResultId$2, plots: PlotModel, taName: string, outputDir?: string): Promise<void>;
|
|
87
|
+
declare function dumpPlotData<M extends PlotMapping>(signalId: ResultId$2, plots: PlotModel, mapping: M, taName: string, outputDir?: string): Promise<void>;
|
|
88
88
|
|
|
89
89
|
type ResultId$1 = string | number;
|
|
90
90
|
interface SignalData {
|
|
@@ -169,9 +169,9 @@ interface IPlotRow {
|
|
|
169
169
|
}
|
|
170
170
|
declare class PineMarkdownService {
|
|
171
171
|
private readonly loggerService;
|
|
172
|
-
getData: (plots: PlotModel) => IPlotRow[];
|
|
173
|
-
getReport: (signalId: ResultId, plots: PlotModel) => string;
|
|
174
|
-
dump: (signalId: ResultId, plots: PlotModel, taName: string, outputDir?: string) => Promise<void>;
|
|
172
|
+
getData: <M extends PlotMapping>(plots: PlotModel, mapping: M) => IPlotRow[];
|
|
173
|
+
getReport: <M extends PlotMapping>(signalId: ResultId, plots: PlotModel, mapping: M) => string;
|
|
174
|
+
dump: <M extends PlotMapping>(signalId: ResultId, plots: PlotModel, mapping: M, taName: string, outputDir?: string) => Promise<void>;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
declare const pine: {
|