@backtest-kit/pinets 0.0.3 → 0.0.5

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
@@ -501,7 +501,7 @@ function extractRowAtIndex(plots, keys, index) {
501
501
  }
502
502
  function isRowWarmedUp(row, keys) {
503
503
  for (const key of keys) {
504
- if (!row[key]) {
504
+ if (isUnsafe(row[key])) {
505
505
  return false;
506
506
  }
507
507
  }
@@ -536,12 +536,12 @@ class PineMarkdownService {
536
536
  });
537
537
  const keys = Object.keys(plots);
538
538
  if (keys.length === 0) {
539
- return;
539
+ return [];
540
540
  }
541
541
  const firstPlot = plots[keys[0]];
542
542
  const dataLength = firstPlot?.data?.length ?? 0;
543
543
  if (dataLength === 0) {
544
- return;
544
+ return [];
545
545
  }
546
546
  const rows = [];
547
547
  let warmupComplete = false;
@@ -655,7 +655,7 @@ function usePine(ctor) {
655
655
  pine.pineConnectionService.usePine(ctor);
656
656
  }
657
657
 
658
- const METHOD_NAME_RUN$1 = "run.run";
658
+ const METHOD_NAME_RUN$2 = "run.run";
659
659
  const GET_SOURCE_FN$1 = async (source) => {
660
660
  if (File.isFile(source)) {
661
661
  const code = await pine.pineCacheService.readFile(source.path, source.baseDir);
@@ -666,16 +666,23 @@ const GET_SOURCE_FN$1 = async (source) => {
666
666
  }
667
667
  throw new Error("Source must be a File or Code instance");
668
668
  };
669
- async function run(source, { symbol, timeframe, mapping, limit }) {
670
- pine.loggerService.info(METHOD_NAME_RUN$1, {
669
+ async function run(source, { symbol, timeframe, limit }) {
670
+ pine.loggerService.info(METHOD_NAME_RUN$2, {
671
671
  source,
672
672
  symbol,
673
673
  timeframe,
674
- mapping,
675
674
  limit,
676
675
  });
677
676
  const script = await GET_SOURCE_FN$1(source);
678
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
+ });
679
686
  return pine.pineDataService.extract(plots, mapping);
680
687
  }
681
688
 
@@ -683,28 +690,6 @@ function setLogger(logger) {
683
690
  pine.loggerService.setLogger(logger);
684
691
  }
685
692
 
686
- const METHOD_NAME_RUN = "strategy.getSignal";
687
- const DEFAULT_ESTIMATED_TIME = 240;
688
- const GET_SOURCE_FN = async (source) => {
689
- if (File.isFile(source)) {
690
- const code = await pine.pineCacheService.readFile(source.path, source.baseDir);
691
- return Code.fromString(code);
692
- }
693
- if (Code.isCode(source)) {
694
- return source;
695
- }
696
- throw new Error("Source must be a File or Code instance");
697
- };
698
- const SIGNAL_SCHEMA = {
699
- position: "Signal",
700
- priceOpen: "Close",
701
- priceTakeProfit: "TakeProfit",
702
- priceStopLoss: "StopLoss",
703
- minuteEstimatedTime: {
704
- plot: "EstimatedTime",
705
- transform: (v) => v || DEFAULT_ESTIMATED_TIME,
706
- },
707
- };
708
693
  function toSignalDto(data) {
709
694
  if (data.position === 1) {
710
695
  return {
@@ -728,6 +713,29 @@ function toSignalDto(data) {
728
713
  }
729
714
  return null;
730
715
  }
716
+
717
+ const METHOD_NAME_RUN = "strategy.getSignal";
718
+ const DEFAULT_ESTIMATED_TIME = 240;
719
+ const GET_SOURCE_FN = async (source) => {
720
+ if (File.isFile(source)) {
721
+ const code = await pine.pineCacheService.readFile(source.path, source.baseDir);
722
+ return Code.fromString(code);
723
+ }
724
+ if (Code.isCode(source)) {
725
+ return source;
726
+ }
727
+ throw new Error("Source must be a File or Code instance");
728
+ };
729
+ const SIGNAL_SCHEMA = {
730
+ position: "Signal",
731
+ priceOpen: "Close",
732
+ priceTakeProfit: "TakeProfit",
733
+ priceStopLoss: "StopLoss",
734
+ minuteEstimatedTime: {
735
+ plot: "EstimatedTime",
736
+ transform: (v) => v || DEFAULT_ESTIMATED_TIME,
737
+ },
738
+ };
731
739
  async function getSignal(source, { symbol, timeframe, limit }) {
732
740
  pine.loggerService.info(METHOD_NAME_RUN, {
733
741
  source,
@@ -741,7 +749,7 @@ async function getSignal(source, { symbol, timeframe, limit }) {
741
749
  }
742
750
 
743
751
  const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
744
- async function dumpPineData(signalId, plots, taName, outputDir = "./dump/ta") {
752
+ async function dumpPlotData(signalId, plots, taName, outputDir = "./dump/ta") {
745
753
  pine.loggerService.log(DUMP_SIGNAL_METHOD_NAME, {
746
754
  signalId,
747
755
  plotCount: Object.keys(plots).length,
@@ -753,9 +761,11 @@ async function dumpPineData(signalId, plots, taName, outputDir = "./dump/ta") {
753
761
  exports.AXIS_SYMBOL = AXIS_SYMBOL;
754
762
  exports.Code = Code;
755
763
  exports.File = File;
756
- exports.dumpPineData = dumpPineData;
764
+ exports.dumpPlotData = dumpPlotData;
765
+ exports.extract = extract;
757
766
  exports.getSignal = getSignal;
758
767
  exports.lib = pine;
759
768
  exports.run = run;
760
769
  exports.setLogger = setLogger;
770
+ exports.toSignalDto = toSignalDto;
761
771
  exports.usePine = usePine;
package/build/index.mjs CHANGED
@@ -498,7 +498,7 @@ function extractRowAtIndex(plots, keys, index) {
498
498
  }
499
499
  function isRowWarmedUp(row, keys) {
500
500
  for (const key of keys) {
501
- if (!row[key]) {
501
+ if (isUnsafe(row[key])) {
502
502
  return false;
503
503
  }
504
504
  }
@@ -533,12 +533,12 @@ class PineMarkdownService {
533
533
  });
534
534
  const keys = Object.keys(plots);
535
535
  if (keys.length === 0) {
536
- return;
536
+ return [];
537
537
  }
538
538
  const firstPlot = plots[keys[0]];
539
539
  const dataLength = firstPlot?.data?.length ?? 0;
540
540
  if (dataLength === 0) {
541
- return;
541
+ return [];
542
542
  }
543
543
  const rows = [];
544
544
  let warmupComplete = false;
@@ -652,7 +652,7 @@ function usePine(ctor) {
652
652
  pine.pineConnectionService.usePine(ctor);
653
653
  }
654
654
 
655
- const METHOD_NAME_RUN$1 = "run.run";
655
+ const METHOD_NAME_RUN$2 = "run.run";
656
656
  const GET_SOURCE_FN$1 = async (source) => {
657
657
  if (File.isFile(source)) {
658
658
  const code = await pine.pineCacheService.readFile(source.path, source.baseDir);
@@ -663,16 +663,23 @@ const GET_SOURCE_FN$1 = async (source) => {
663
663
  }
664
664
  throw new Error("Source must be a File or Code instance");
665
665
  };
666
- async function run(source, { symbol, timeframe, mapping, limit }) {
667
- pine.loggerService.info(METHOD_NAME_RUN$1, {
666
+ async function run(source, { symbol, timeframe, limit }) {
667
+ pine.loggerService.info(METHOD_NAME_RUN$2, {
668
668
  source,
669
669
  symbol,
670
670
  timeframe,
671
- mapping,
672
671
  limit,
673
672
  });
674
673
  const script = await GET_SOURCE_FN$1(source);
675
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
+ });
676
683
  return pine.pineDataService.extract(plots, mapping);
677
684
  }
678
685
 
@@ -680,28 +687,6 @@ function setLogger(logger) {
680
687
  pine.loggerService.setLogger(logger);
681
688
  }
682
689
 
683
- const METHOD_NAME_RUN = "strategy.getSignal";
684
- const DEFAULT_ESTIMATED_TIME = 240;
685
- const GET_SOURCE_FN = async (source) => {
686
- if (File.isFile(source)) {
687
- const code = await pine.pineCacheService.readFile(source.path, source.baseDir);
688
- return Code.fromString(code);
689
- }
690
- if (Code.isCode(source)) {
691
- return source;
692
- }
693
- throw new Error("Source must be a File or Code instance");
694
- };
695
- const SIGNAL_SCHEMA = {
696
- position: "Signal",
697
- priceOpen: "Close",
698
- priceTakeProfit: "TakeProfit",
699
- priceStopLoss: "StopLoss",
700
- minuteEstimatedTime: {
701
- plot: "EstimatedTime",
702
- transform: (v) => v || DEFAULT_ESTIMATED_TIME,
703
- },
704
- };
705
690
  function toSignalDto(data) {
706
691
  if (data.position === 1) {
707
692
  return {
@@ -725,6 +710,29 @@ function toSignalDto(data) {
725
710
  }
726
711
  return null;
727
712
  }
713
+
714
+ const METHOD_NAME_RUN = "strategy.getSignal";
715
+ const DEFAULT_ESTIMATED_TIME = 240;
716
+ const GET_SOURCE_FN = async (source) => {
717
+ if (File.isFile(source)) {
718
+ const code = await pine.pineCacheService.readFile(source.path, source.baseDir);
719
+ return Code.fromString(code);
720
+ }
721
+ if (Code.isCode(source)) {
722
+ return source;
723
+ }
724
+ throw new Error("Source must be a File or Code instance");
725
+ };
726
+ const SIGNAL_SCHEMA = {
727
+ position: "Signal",
728
+ priceOpen: "Close",
729
+ priceTakeProfit: "TakeProfit",
730
+ priceStopLoss: "StopLoss",
731
+ minuteEstimatedTime: {
732
+ plot: "EstimatedTime",
733
+ transform: (v) => v || DEFAULT_ESTIMATED_TIME,
734
+ },
735
+ };
728
736
  async function getSignal(source, { symbol, timeframe, limit }) {
729
737
  pine.loggerService.info(METHOD_NAME_RUN, {
730
738
  source,
@@ -738,7 +746,7 @@ async function getSignal(source, { symbol, timeframe, limit }) {
738
746
  }
739
747
 
740
748
  const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
741
- async function dumpPineData(signalId, plots, taName, outputDir = "./dump/ta") {
749
+ async function dumpPlotData(signalId, plots, taName, outputDir = "./dump/ta") {
742
750
  pine.loggerService.log(DUMP_SIGNAL_METHOD_NAME, {
743
751
  signalId,
744
752
  plotCount: Object.keys(plots).length,
@@ -747,4 +755,4 @@ async function dumpPineData(signalId, plots, taName, outputDir = "./dump/ta") {
747
755
  return await pine.pineMarkdownService.dump(signalId, plots, taName, outputDir);
748
756
  }
749
757
 
750
- export { AXIS_SYMBOL, Code, File, dumpPineData, getSignal, pine as lib, run, setLogger, usePine };
758
+ 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": "0.0.3",
3
+ "version": "0.0.5",
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,19 @@ 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>;
88
+
89
+ interface SignalData {
90
+ position: number;
91
+ priceOpen: number;
92
+ priceTakeProfit: number;
93
+ priceStopLoss: number;
94
+ minuteEstimatedTime: number;
95
+ }
96
+ interface Signal extends ISignalDto {
97
+ id: string;
98
+ }
99
+ declare function toSignalDto(data: SignalData): Signal | null;
87
100
 
88
101
  interface CandleModel {
89
102
  openTime: number;
@@ -171,4 +184,4 @@ declare const pine: {
171
184
  loggerService: LoggerService;
172
185
  };
173
186
 
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 };
187
+ 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, toSignalDto, usePine };