@backtest-kit/pinets 0.0.2 → 0.0.4

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
@@ -47,22 +47,22 @@ Create a Pine Script file (`strategy.pine`):
47
47
 
48
48
  ```pine
49
49
  //@version=5
50
- indicator("Signal Strategy")
50
+ indicator("Signal Strategy 100 candles of 1H timeframe")
51
51
 
52
- // Indicators
53
- rsi = ta.rsi(close, 14)
54
- atr = ta.atr(14)
55
- ema_fast = ta.ema(close, 9)
56
- ema_slow = ta.ema(close, 21)
52
+ // Indicators - faster settings for 1H
53
+ rsi = ta.rsi(close, 10)
54
+ atr = ta.atr(10)
55
+ ema_fast = ta.ema(close, 7)
56
+ ema_slow = ta.ema(close, 16)
57
57
 
58
58
  // Conditions
59
- long_cond = ta.crossover(ema_fast, ema_slow) and rsi < 70
60
- short_cond = ta.crossunder(ema_fast, ema_slow) and rsi > 30
59
+ long_cond = ta.crossover(ema_fast, ema_slow) and rsi < 65
60
+ short_cond = ta.crossunder(ema_fast, ema_slow) and rsi > 35
61
61
 
62
- // Levels
63
- sl_long = close - atr * 2
62
+ // Levels - tighter SL, wider TP for better RR
63
+ sl_long = close - atr * 1.5
64
64
  tp_long = close + atr * 3
65
- sl_short = close + atr * 2
65
+ sl_short = close + atr * 1.5
66
66
  tp_short = close - atr * 3
67
67
 
68
68
  // Plots for extraction
@@ -70,7 +70,7 @@ plot(close, "Close")
70
70
  plot(long_cond ? 1 : short_cond ? -1 : 0, "Signal")
71
71
  plot(long_cond ? sl_long : sl_short, "StopLoss")
72
72
  plot(long_cond ? tp_long : tp_short, "TakeProfit")
73
- plot(240, "EstimatedTime") // 4 hours in minutes
73
+ plot(60, "EstimatedTime") // 1 hour in minutes
74
74
  ```
75
75
 
76
76
  Use it in your strategy:
@@ -88,7 +88,7 @@ addStrategy({
88
88
 
89
89
  return await getSignal(source, {
90
90
  symbol,
91
- timeframe: '5m',
91
+ timeframe: '1h',
92
92
  limit: 100,
93
93
  });
94
94
  }
@@ -131,28 +131,29 @@ const signal = await getSignal(source, {
131
131
  For advanced use cases, extract any Pine `plot()` with custom mapping:
132
132
 
133
133
  ```typescript
134
- import { File, run } from '@backtest-kit/pinets';
134
+ import { File, run, extract } from '@backtest-kit/pinets';
135
135
 
136
136
  const source = File.fromPath('indicators.pine');
137
137
 
138
- const data = await run(source, {
138
+ const plots = await run(source, {
139
139
  symbol: 'ETHUSDT',
140
140
  timeframe: '1h',
141
141
  limit: 200,
142
- mapping: {
143
- // Simple: plot name -> number
144
- rsi: 'RSI',
145
- macd: 'MACD',
146
-
147
- // Advanced: with transform and lookback
148
- prevRsi: {
149
- plot: 'RSI',
150
- barsBack: 1, // Previous bar value
151
- },
152
- trendStrength: {
153
- plot: 'ADX',
154
- transform: (v) => v > 25 ? 'strong' : 'weak',
155
- },
142
+ });
143
+
144
+ const data = extract(plots, {
145
+ // Simple: plot name -> number
146
+ rsi: 'RSI',
147
+ macd: 'MACD',
148
+
149
+ // Advanced: with transform and lookback
150
+ prevRsi: {
151
+ plot: 'RSI',
152
+ barsBack: 1, // Previous bar value
153
+ },
154
+ trendStrength: {
155
+ plot: 'ADX',
156
+ transform: (v) => v > 25 ? 'strong' : 'weak',
156
157
  },
157
158
  });
158
159
 
package/build/index.cjs CHANGED
@@ -442,6 +442,27 @@ class PineConnectionService {
442
442
  }
443
443
  }
444
444
 
445
+ const TABLE_ROWS_LIMIT = 48;
446
+ const GET_METHOD_CONTEXT_FN = () => {
447
+ if (backtestKit.MethodContextService.hasContext()) {
448
+ const { exchangeName, frameName, strategyName } = backtestKit.lib.methodContextService.context;
449
+ return { exchangeName, frameName, strategyName };
450
+ }
451
+ return {
452
+ strategyName: "",
453
+ exchangeName: "",
454
+ frameName: "",
455
+ };
456
+ };
457
+ const GET_EXECUTION_CONTEXT_FN = () => {
458
+ if (backtestKit.ExecutionContextService.hasContext()) {
459
+ const { when } = backtestKit.lib.executionContextService.context;
460
+ return { when: when.toISOString() };
461
+ }
462
+ return {
463
+ when: "",
464
+ };
465
+ };
445
466
  const DEFAULT_FORMAT = (v) => v !== null ? Number(v).toFixed(4) : "N/A";
446
467
  function isUnsafe(value) {
447
468
  if (value === null)
@@ -488,8 +509,13 @@ function isRowWarmedUp(row, keys) {
488
509
  }
489
510
  function generateMarkdownTable(rows, keys, signalId) {
490
511
  let markdown = "";
512
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN();
491
513
  markdown += `# PineScript Technical Analysis Dump\n\n`;
492
- markdown += `**Signal ID**: ${String(signalId)}\n\n`;
514
+ markdown += `**Signal ID**: ${String(signalId)}\n`;
515
+ if (createdAt) {
516
+ markdown += `**Current datetime**: ${String(createdAt)}\n`;
517
+ }
518
+ markdown += "\n";
493
519
  const header = `| Timestamp | ${keys.join(" | ")} |\n`;
494
520
  const separator = `| --- | ${keys.map(() => "---").join(" | ")} |\n`;
495
521
  markdown += header;
@@ -510,12 +536,12 @@ class PineMarkdownService {
510
536
  });
511
537
  const keys = Object.keys(plots);
512
538
  if (keys.length === 0) {
513
- return;
539
+ return [];
514
540
  }
515
541
  const firstPlot = plots[keys[0]];
516
542
  const dataLength = firstPlot?.data?.length ?? 0;
517
543
  if (dataLength === 0) {
518
- return;
544
+ return [];
519
545
  }
520
546
  const rows = [];
521
547
  let warmupComplete = false;
@@ -533,7 +559,7 @@ class PineMarkdownService {
533
559
  }
534
560
  rows.push(row);
535
561
  }
536
- return rows;
562
+ return rows.slice(-TABLE_ROWS_LIMIT);
537
563
  };
538
564
  this.getReport = (signalId, plots) => {
539
565
  this.loggerService.log("pineMarkdownService getReport", {
@@ -551,14 +577,15 @@ class PineMarkdownService {
551
577
  outputDir,
552
578
  });
553
579
  const content = this.getReport(signalId, plots);
580
+ const { exchangeName, frameName, strategyName } = GET_METHOD_CONTEXT_FN();
554
581
  await backtestKit.Markdown.writeData(taName, content, {
555
582
  path: outputDir,
556
583
  file: `${String(signalId)}.md`,
557
584
  symbol: "",
558
585
  signalId: String(signalId),
559
- strategyName: "",
560
- exchangeName: "",
561
- frameName: "",
586
+ strategyName,
587
+ exchangeName,
588
+ frameName,
562
589
  });
563
590
  };
564
591
  }
@@ -628,7 +655,7 @@ function usePine(ctor) {
628
655
  pine.pineConnectionService.usePine(ctor);
629
656
  }
630
657
 
631
- const METHOD_NAME_RUN$1 = "run.run";
658
+ const METHOD_NAME_RUN$2 = "run.run";
632
659
  const GET_SOURCE_FN$1 = async (source) => {
633
660
  if (File.isFile(source)) {
634
661
  const code = await pine.pineCacheService.readFile(source.path, source.baseDir);
@@ -639,16 +666,23 @@ const GET_SOURCE_FN$1 = async (source) => {
639
666
  }
640
667
  throw new Error("Source must be a File or Code instance");
641
668
  };
642
- async function run(source, { symbol, timeframe, mapping, limit }) {
643
- pine.loggerService.info(METHOD_NAME_RUN$1, {
669
+ async function run(source, { symbol, timeframe, limit }) {
670
+ pine.loggerService.info(METHOD_NAME_RUN$2, {
644
671
  source,
645
672
  symbol,
646
673
  timeframe,
647
- mapping,
648
674
  limit,
649
675
  });
650
676
  const script = await GET_SOURCE_FN$1(source);
651
677
  const { plots } = await pine.pineJobService.run(script, symbol, timeframe, limit);
678
+ return plots;
679
+ }
680
+
681
+ const METHOD_NAME_RUN$1 = "extract.extract";
682
+ async function extract(plots, mapping) {
683
+ pine.loggerService.info(METHOD_NAME_RUN$1, {
684
+ mapping,
685
+ });
652
686
  return pine.pineDataService.extract(plots, mapping);
653
687
  }
654
688
 
@@ -714,7 +748,7 @@ async function getSignal(source, { symbol, timeframe, limit }) {
714
748
  }
715
749
 
716
750
  const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
717
- async function dumpPineData(signalId, plots, taName, outputDir = "./dump/ta") {
751
+ async function dumpPlotData(signalId, plots, taName, outputDir = "./dump/ta") {
718
752
  pine.loggerService.log(DUMP_SIGNAL_METHOD_NAME, {
719
753
  signalId,
720
754
  plotCount: Object.keys(plots).length,
@@ -726,7 +760,8 @@ async function dumpPineData(signalId, plots, taName, outputDir = "./dump/ta") {
726
760
  exports.AXIS_SYMBOL = AXIS_SYMBOL;
727
761
  exports.Code = Code;
728
762
  exports.File = File;
729
- exports.dumpPineData = dumpPineData;
763
+ exports.dumpPlotData = dumpPlotData;
764
+ exports.extract = extract;
730
765
  exports.getSignal = getSignal;
731
766
  exports.lib = pine;
732
767
  exports.run = run;
package/build/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { join } from 'path';
2
- import { getDate, getRawCandles, Markdown } from 'backtest-kit';
2
+ import { getDate, getRawCandles, Markdown, MethodContextService, lib, ExecutionContextService } from 'backtest-kit';
3
3
  import { createActivator } from 'di-kit';
4
4
  import { singleshot, memoize, randomString } from 'functools-kit';
5
5
  import fs from 'fs/promises';
@@ -439,6 +439,27 @@ class PineConnectionService {
439
439
  }
440
440
  }
441
441
 
442
+ const TABLE_ROWS_LIMIT = 48;
443
+ const GET_METHOD_CONTEXT_FN = () => {
444
+ if (MethodContextService.hasContext()) {
445
+ const { exchangeName, frameName, strategyName } = lib.methodContextService.context;
446
+ return { exchangeName, frameName, strategyName };
447
+ }
448
+ return {
449
+ strategyName: "",
450
+ exchangeName: "",
451
+ frameName: "",
452
+ };
453
+ };
454
+ const GET_EXECUTION_CONTEXT_FN = () => {
455
+ if (ExecutionContextService.hasContext()) {
456
+ const { when } = lib.executionContextService.context;
457
+ return { when: when.toISOString() };
458
+ }
459
+ return {
460
+ when: "",
461
+ };
462
+ };
442
463
  const DEFAULT_FORMAT = (v) => v !== null ? Number(v).toFixed(4) : "N/A";
443
464
  function isUnsafe(value) {
444
465
  if (value === null)
@@ -485,8 +506,13 @@ function isRowWarmedUp(row, keys) {
485
506
  }
486
507
  function generateMarkdownTable(rows, keys, signalId) {
487
508
  let markdown = "";
509
+ const { when: createdAt } = GET_EXECUTION_CONTEXT_FN();
488
510
  markdown += `# PineScript Technical Analysis Dump\n\n`;
489
- markdown += `**Signal ID**: ${String(signalId)}\n\n`;
511
+ markdown += `**Signal ID**: ${String(signalId)}\n`;
512
+ if (createdAt) {
513
+ markdown += `**Current datetime**: ${String(createdAt)}\n`;
514
+ }
515
+ markdown += "\n";
490
516
  const header = `| Timestamp | ${keys.join(" | ")} |\n`;
491
517
  const separator = `| --- | ${keys.map(() => "---").join(" | ")} |\n`;
492
518
  markdown += header;
@@ -507,12 +533,12 @@ class PineMarkdownService {
507
533
  });
508
534
  const keys = Object.keys(plots);
509
535
  if (keys.length === 0) {
510
- return;
536
+ return [];
511
537
  }
512
538
  const firstPlot = plots[keys[0]];
513
539
  const dataLength = firstPlot?.data?.length ?? 0;
514
540
  if (dataLength === 0) {
515
- return;
541
+ return [];
516
542
  }
517
543
  const rows = [];
518
544
  let warmupComplete = false;
@@ -530,7 +556,7 @@ class PineMarkdownService {
530
556
  }
531
557
  rows.push(row);
532
558
  }
533
- return rows;
559
+ return rows.slice(-TABLE_ROWS_LIMIT);
534
560
  };
535
561
  this.getReport = (signalId, plots) => {
536
562
  this.loggerService.log("pineMarkdownService getReport", {
@@ -548,14 +574,15 @@ class PineMarkdownService {
548
574
  outputDir,
549
575
  });
550
576
  const content = this.getReport(signalId, plots);
577
+ const { exchangeName, frameName, strategyName } = GET_METHOD_CONTEXT_FN();
551
578
  await Markdown.writeData(taName, content, {
552
579
  path: outputDir,
553
580
  file: `${String(signalId)}.md`,
554
581
  symbol: "",
555
582
  signalId: String(signalId),
556
- strategyName: "",
557
- exchangeName: "",
558
- frameName: "",
583
+ strategyName,
584
+ exchangeName,
585
+ frameName,
559
586
  });
560
587
  };
561
588
  }
@@ -625,7 +652,7 @@ function usePine(ctor) {
625
652
  pine.pineConnectionService.usePine(ctor);
626
653
  }
627
654
 
628
- const METHOD_NAME_RUN$1 = "run.run";
655
+ const METHOD_NAME_RUN$2 = "run.run";
629
656
  const GET_SOURCE_FN$1 = async (source) => {
630
657
  if (File.isFile(source)) {
631
658
  const code = await pine.pineCacheService.readFile(source.path, source.baseDir);
@@ -636,16 +663,23 @@ const GET_SOURCE_FN$1 = async (source) => {
636
663
  }
637
664
  throw new Error("Source must be a File or Code instance");
638
665
  };
639
- async function run(source, { symbol, timeframe, mapping, limit }) {
640
- pine.loggerService.info(METHOD_NAME_RUN$1, {
666
+ async function run(source, { symbol, timeframe, limit }) {
667
+ pine.loggerService.info(METHOD_NAME_RUN$2, {
641
668
  source,
642
669
  symbol,
643
670
  timeframe,
644
- mapping,
645
671
  limit,
646
672
  });
647
673
  const script = await GET_SOURCE_FN$1(source);
648
674
  const { plots } = await pine.pineJobService.run(script, symbol, timeframe, limit);
675
+ return plots;
676
+ }
677
+
678
+ const METHOD_NAME_RUN$1 = "extract.extract";
679
+ async function extract(plots, mapping) {
680
+ pine.loggerService.info(METHOD_NAME_RUN$1, {
681
+ mapping,
682
+ });
649
683
  return pine.pineDataService.extract(plots, mapping);
650
684
  }
651
685
 
@@ -711,7 +745,7 @@ async function getSignal(source, { symbol, timeframe, limit }) {
711
745
  }
712
746
 
713
747
  const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
714
- async function dumpPineData(signalId, plots, taName, outputDir = "./dump/ta") {
748
+ async function dumpPlotData(signalId, plots, taName, outputDir = "./dump/ta") {
715
749
  pine.loggerService.log(DUMP_SIGNAL_METHOD_NAME, {
716
750
  signalId,
717
751
  plotCount: Object.keys(plots).length,
@@ -720,4 +754,4 @@ async function dumpPineData(signalId, plots, taName, outputDir = "./dump/ta") {
720
754
  return await pine.pineMarkdownService.dump(signalId, plots, taName, outputDir);
721
755
  }
722
756
 
723
- export { AXIS_SYMBOL, Code, File, dumpPineData, getSignal, pine as lib, run, setLogger, usePine };
757
+ export { AXIS_SYMBOL, Code, File, dumpPlotData, extract, getSignal, pine as lib, run, setLogger, usePine };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtest-kit/pinets",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
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
@@ -42,6 +42,13 @@ interface IPine {
42
42
 
43
43
  declare function usePine<T = TPineCtor>(ctor: T): void;
44
44
 
45
+ interface IRunParams {
46
+ symbol: string;
47
+ timeframe: CandleInterval;
48
+ limit: number;
49
+ }
50
+ declare function run(source: File | Code, { symbol, timeframe, limit }: IRunParams): Promise<PlotModel>;
51
+
45
52
  type PlotExtractConfig<T = number> = {
46
53
  plot: string;
47
54
  barsBack?: number;
@@ -58,13 +65,7 @@ declare class PineDataService {
58
65
  extract<M extends PlotMapping>(plots: PlotModel, mapping: M): ExtractedData<M>;
59
66
  }
60
67
 
61
- interface IRunParams<M extends PlotMapping> {
62
- symbol: string;
63
- timeframe: CandleInterval;
64
- limit: number;
65
- mapping: M;
66
- }
67
- declare function run<M extends PlotMapping>(source: File | Code, { symbol, timeframe, mapping, limit }: IRunParams<M>): Promise<ExtractedData<M>>;
68
+ declare function extract<M extends PlotMapping>(plots: PlotModel, mapping: M): Promise<ExtractedData<M>>;
68
69
 
69
70
  interface ILogger {
70
71
  log(topic: string, ...args: any[]): void;
@@ -83,7 +84,7 @@ interface IParams {
83
84
  declare function getSignal(source: File | Code, { symbol, timeframe, limit }: IParams): Promise<ISignalDto | null>;
84
85
 
85
86
  type ResultId$1 = string | number;
86
- declare function dumpPineData(signalId: ResultId$1, plots: PlotModel, taName: string, outputDir?: string): Promise<void>;
87
+ declare function dumpPlotData(signalId: ResultId$1, plots: PlotModel, taName: string, outputDir?: string): Promise<void>;
87
88
 
88
89
  interface CandleModel {
89
90
  openTime: number;
@@ -171,4 +172,4 @@ declare const pine: {
171
172
  loggerService: LoggerService;
172
173
  };
173
174
 
174
- export { AXIS_SYMBOL, type CandleModel, Code, File, type ILogger, type IPine, type IProvider, type PlotExtractConfig, type PlotMapping, type PlotModel, type PlotRecord, type SymbolInfoModel, type TPineCtor, dumpPineData, getSignal, pine as lib, run, setLogger, usePine };
175
+ export { AXIS_SYMBOL, type CandleModel, Code, File, type ILogger, type IPine, type IProvider, type PlotExtractConfig, type PlotMapping, type PlotModel, type PlotRecord, type SymbolInfoModel, type TPineCtor, dumpPlotData, extract, getSignal, pine as lib, run, setLogger, usePine };