@backtest-kit/pinets 0.0.6 → 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 +54 -3
- package/build/index.cjs +45 -78
- package/build/index.mjs +45 -78
- package/package.json +3 -3
- package/types.d.ts +6 -6
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
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.
|
|
4
6
|
|
|
5
|
-

|
|
6
8
|
|
|
7
9
|
[](https://deepwiki.com/tripolskypetr/backtest-kit)
|
|
8
10
|
[](https://npmjs.org/package/@backtest-kit/pinets)
|
|
@@ -29,7 +31,11 @@ Port your TradingView strategies to backtest-kit with zero rewrite. Powered by [
|
|
|
29
31
|
| Function | Description |
|
|
30
32
|
|----------|-------------|
|
|
31
33
|
| **`getSignal()`** | Run Pine Script and get structured `ISignalDto` (position, TP/SL, estimated time) |
|
|
32
|
-
| **`run()`** | Run Pine Script
|
|
34
|
+
| **`run()`** | Run Pine Script and return raw plot data |
|
|
35
|
+
| **`extract()`** | Extract values from plots with custom mapping |
|
|
36
|
+
| **`dumpPlotData()`** | Dump plot data to markdown files for debugging |
|
|
37
|
+
| **`usePine()`** | Register custom Pine constructor |
|
|
38
|
+
| **`setLogger()`** | Configure custom logger |
|
|
33
39
|
| **`File.fromPath()`** | Load Pine Script from `.pine` file |
|
|
34
40
|
| **`Code.fromString()`** | Use inline Pine Script code |
|
|
35
41
|
|
|
@@ -141,7 +147,7 @@ const plots = await run(source, {
|
|
|
141
147
|
limit: 200,
|
|
142
148
|
});
|
|
143
149
|
|
|
144
|
-
const data = extract(plots, {
|
|
150
|
+
const data = await extract(plots, {
|
|
145
151
|
// Simple: plot name -> number
|
|
146
152
|
rsi: 'RSI',
|
|
147
153
|
macd: 'MACD',
|
|
@@ -160,6 +166,51 @@ const data = extract(plots, {
|
|
|
160
166
|
// data = { rsi: 55.2, macd: 12.5, prevRsi: 52.1, trendStrength: 'strong' }
|
|
161
167
|
```
|
|
162
168
|
|
|
169
|
+
### Debug with Plot Dump
|
|
170
|
+
|
|
171
|
+
Dump plot data to markdown files for analysis and debugging:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { File, run, dumpPlotData } from '@backtest-kit/pinets';
|
|
175
|
+
|
|
176
|
+
const source = File.fromPath('strategy.pine');
|
|
177
|
+
|
|
178
|
+
const plots = await run(source, {
|
|
179
|
+
symbol: 'BTCUSDT',
|
|
180
|
+
timeframe: '1h',
|
|
181
|
+
limit: 100,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Dump plots to ./dump/ta directory
|
|
185
|
+
await dumpPlotData('signal-001', plots, 'ema-cross', './dump/ta');
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Custom Pine Constructor
|
|
189
|
+
|
|
190
|
+
Register a custom Pine constructor for advanced configurations:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { usePine } from '@backtest-kit/pinets';
|
|
194
|
+
import { Pine } from 'pinets';
|
|
195
|
+
|
|
196
|
+
// Use custom Pine instance
|
|
197
|
+
usePine(Pine);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Custom Logger
|
|
201
|
+
|
|
202
|
+
Configure logging for debugging:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import { setLogger } from '@backtest-kit/pinets';
|
|
206
|
+
|
|
207
|
+
setLogger({
|
|
208
|
+
log: (method, data) => console.log(`[${method}]`, data),
|
|
209
|
+
info: (method, data) => console.info(`[${method}]`, data),
|
|
210
|
+
error: (method, data) => console.error(`[${method}]`, data),
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
163
214
|
## 📜 Pine Script Conventions
|
|
164
215
|
|
|
165
216
|
For `getSignal()` to work, your Pine Script must include these plots:
|
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;
|
|
466
|
+
function getPlotName(config) {
|
|
467
|
+
return typeof config === "string" ? config : config.plot;
|
|
477
468
|
}
|
|
478
|
-
function
|
|
479
|
-
|
|
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;
|
|
501
|
-
}
|
|
502
|
-
function isRowWarmedUp(row, keys) {
|
|
503
|
-
for (const key of keys) {
|
|
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,
|
|
@@ -690,26 +650,32 @@ function setLogger(logger) {
|
|
|
690
650
|
pine.loggerService.setLogger(logger);
|
|
691
651
|
}
|
|
692
652
|
|
|
693
|
-
function toSignalDto(id, data) {
|
|
653
|
+
function toSignalDto(id, data, priceOpen = data.priceOpen) {
|
|
694
654
|
if (data.position === 1) {
|
|
695
|
-
|
|
655
|
+
const result = {
|
|
696
656
|
id: String(id),
|
|
697
657
|
position: "long",
|
|
698
|
-
priceOpen: data.priceOpen,
|
|
699
658
|
priceTakeProfit: data.priceTakeProfit,
|
|
700
659
|
priceStopLoss: data.priceStopLoss,
|
|
701
660
|
minuteEstimatedTime: data.minuteEstimatedTime,
|
|
702
661
|
};
|
|
662
|
+
if (priceOpen) {
|
|
663
|
+
Object.assign(result, { priceOpen });
|
|
664
|
+
}
|
|
665
|
+
return result;
|
|
703
666
|
}
|
|
704
667
|
if (data.position === -1) {
|
|
705
|
-
|
|
668
|
+
const result = {
|
|
706
669
|
id: String(id),
|
|
707
670
|
position: "short",
|
|
708
|
-
priceOpen: data.priceOpen,
|
|
709
671
|
priceTakeProfit: data.priceTakeProfit,
|
|
710
672
|
priceStopLoss: data.priceStopLoss,
|
|
711
673
|
minuteEstimatedTime: data.minuteEstimatedTime,
|
|
712
674
|
};
|
|
675
|
+
if (priceOpen) {
|
|
676
|
+
Object.assign(result, { priceOpen });
|
|
677
|
+
}
|
|
678
|
+
return result;
|
|
713
679
|
}
|
|
714
680
|
return null;
|
|
715
681
|
}
|
|
@@ -750,13 +716,14 @@ async function getSignal(source, { symbol, timeframe, limit }) {
|
|
|
750
716
|
}
|
|
751
717
|
|
|
752
718
|
const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
|
|
753
|
-
async function dumpPlotData(signalId, plots, taName, outputDir = "./dump/ta") {
|
|
719
|
+
async function dumpPlotData(signalId, plots, mapping, taName, outputDir = "./dump/ta") {
|
|
754
720
|
pine.loggerService.log(DUMP_SIGNAL_METHOD_NAME, {
|
|
755
721
|
signalId,
|
|
756
722
|
plotCount: Object.keys(plots).length,
|
|
723
|
+
mapping,
|
|
757
724
|
outputDir,
|
|
758
725
|
});
|
|
759
|
-
return await pine.pineMarkdownService.dump(signalId, plots, taName, outputDir);
|
|
726
|
+
return await pine.pineMarkdownService.dump(signalId, plots, mapping, taName, outputDir);
|
|
760
727
|
}
|
|
761
728
|
|
|
762
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;
|
|
463
|
+
function getPlotName(config) {
|
|
464
|
+
return typeof config === "string" ? config : config.plot;
|
|
474
465
|
}
|
|
475
|
-
function
|
|
476
|
-
|
|
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;
|
|
498
|
-
}
|
|
499
|
-
function isRowWarmedUp(row, keys) {
|
|
500
|
-
for (const key of keys) {
|
|
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,
|
|
@@ -687,26 +647,32 @@ function setLogger(logger) {
|
|
|
687
647
|
pine.loggerService.setLogger(logger);
|
|
688
648
|
}
|
|
689
649
|
|
|
690
|
-
function toSignalDto(id, data) {
|
|
650
|
+
function toSignalDto(id, data, priceOpen = data.priceOpen) {
|
|
691
651
|
if (data.position === 1) {
|
|
692
|
-
|
|
652
|
+
const result = {
|
|
693
653
|
id: String(id),
|
|
694
654
|
position: "long",
|
|
695
|
-
priceOpen: data.priceOpen,
|
|
696
655
|
priceTakeProfit: data.priceTakeProfit,
|
|
697
656
|
priceStopLoss: data.priceStopLoss,
|
|
698
657
|
minuteEstimatedTime: data.minuteEstimatedTime,
|
|
699
658
|
};
|
|
659
|
+
if (priceOpen) {
|
|
660
|
+
Object.assign(result, { priceOpen });
|
|
661
|
+
}
|
|
662
|
+
return result;
|
|
700
663
|
}
|
|
701
664
|
if (data.position === -1) {
|
|
702
|
-
|
|
665
|
+
const result = {
|
|
703
666
|
id: String(id),
|
|
704
667
|
position: "short",
|
|
705
|
-
priceOpen: data.priceOpen,
|
|
706
668
|
priceTakeProfit: data.priceTakeProfit,
|
|
707
669
|
priceStopLoss: data.priceStopLoss,
|
|
708
670
|
minuteEstimatedTime: data.minuteEstimatedTime,
|
|
709
671
|
};
|
|
672
|
+
if (priceOpen) {
|
|
673
|
+
Object.assign(result, { priceOpen });
|
|
674
|
+
}
|
|
675
|
+
return result;
|
|
710
676
|
}
|
|
711
677
|
return null;
|
|
712
678
|
}
|
|
@@ -747,13 +713,14 @@ async function getSignal(source, { symbol, timeframe, limit }) {
|
|
|
747
713
|
}
|
|
748
714
|
|
|
749
715
|
const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
|
|
750
|
-
async function dumpPlotData(signalId, plots, taName, outputDir = "./dump/ta") {
|
|
716
|
+
async function dumpPlotData(signalId, plots, mapping, taName, outputDir = "./dump/ta") {
|
|
751
717
|
pine.loggerService.log(DUMP_SIGNAL_METHOD_NAME, {
|
|
752
718
|
signalId,
|
|
753
719
|
plotCount: Object.keys(plots).length,
|
|
720
|
+
mapping,
|
|
754
721
|
outputDir,
|
|
755
722
|
});
|
|
756
|
-
return await pine.pineMarkdownService.dump(signalId, plots, taName, outputDir);
|
|
723
|
+
return await pine.pineMarkdownService.dump(signalId, plots, mapping, taName, outputDir);
|
|
757
724
|
}
|
|
758
725
|
|
|
759
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
|
+
"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",
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"worker-testbed": "1.0.12"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
|
-
"backtest-kit": "^
|
|
76
|
-
"pinets": "^0.8.
|
|
75
|
+
"backtest-kit": "^3.0.5",
|
|
76
|
+
"pinets": "^0.8.6",
|
|
77
77
|
"typescript": "^5.0.0"
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
package/types.d.ts
CHANGED
|
@@ -84,12 +84,12 @@ 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 {
|
|
91
91
|
position: number;
|
|
92
|
-
priceOpen
|
|
92
|
+
priceOpen?: number;
|
|
93
93
|
priceTakeProfit: number;
|
|
94
94
|
priceStopLoss: number;
|
|
95
95
|
minuteEstimatedTime: number;
|
|
@@ -97,7 +97,7 @@ interface SignalData {
|
|
|
97
97
|
interface Signal extends ISignalDto {
|
|
98
98
|
id: string;
|
|
99
99
|
}
|
|
100
|
-
declare function toSignalDto(id: ResultId$1, data: SignalData): Signal | null;
|
|
100
|
+
declare function toSignalDto(id: ResultId$1, data: SignalData, priceOpen?: number | null | undefined): Signal | null;
|
|
101
101
|
|
|
102
102
|
interface CandleModel {
|
|
103
103
|
openTime: number;
|
|
@@ -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: {
|