@tradejs/node 1.0.5 → 1.0.6

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/dist/cli.mjs CHANGED
@@ -1,19 +1,18 @@
1
1
  import {
2
2
  AI_CONCURRENCY_LIMIT,
3
3
  KLINE_CONCURRENCY_LIMIT,
4
- SCREENSHOT_CONCURRENCY_LIMIT,
5
- TG_CONCURRENCY_LIMIT
4
+ SCREENSHOT_CONCURRENCY_LIMIT
6
5
  } from "./chunk-SCMBUEGK.mjs";
7
6
  import {
8
7
  require_lodash
9
8
  } from "./chunk-GKDBAF3A.mjs";
10
9
  import {
11
10
  askAI
12
- } from "./chunk-LMAKIC3C.mjs";
13
- import "./chunk-ZY6ULOWK.mjs";
11
+ } from "./chunk-72FKXJ2I.mjs";
12
+ import "./chunk-EOZSJKUM.mjs";
14
13
  import {
15
14
  getTradejsProjectCwd
16
- } from "./chunk-P2ZUWONT.mjs";
15
+ } from "./chunk-CGJ2UU6H.mjs";
17
16
  import {
18
17
  __toESM
19
18
  } from "./chunk-6DZX6EAA.mjs";
@@ -33,8 +32,8 @@ import {
33
32
  RedisWriteBlockedError,
34
33
  delKeyWithOptions,
35
34
  getKeys,
36
- getData as getData2,
37
- redisKeys as redisKeys2
35
+ getData,
36
+ redisKeys
38
37
  } from "@tradejs/infra/redis";
39
38
  import { logger as logger3 } from "@tradejs/infra/logger";
40
39
 
@@ -44,7 +43,7 @@ import path from "path";
44
43
  import puppeteer from "puppeteer";
45
44
  import { delay } from "@tradejs/core/async";
46
45
  import { logger } from "@tradejs/infra/logger";
47
- import { getData, redisKeys } from "@tradejs/infra/redis";
46
+ import { getUserSettings } from "@tradejs/infra/userSettings";
48
47
  var { APP_URL } = process.env;
49
48
  var SCREENSHOT_NAVIGATION_ATTEMPTS = 3;
50
49
  var SCREENSHOT_NAVIGATION_RETRY_DELAY_MS = 2e3;
@@ -64,13 +63,59 @@ var SCREENSHOT_VIEWPORT = {
64
63
  var getProjectRoot = (projectRoot) => path.resolve(getTradejsProjectCwd(projectRoot));
65
64
  var getScreenshotsDir = (projectRoot) => path.join(getProjectRoot(projectRoot), "data", "screenshots");
66
65
  var maskTokenInUrl = (url) => url.replace(/([?&]token=)[^&]+/i, "$1<hidden>");
66
+ var getErrorFields = (value) => {
67
+ if (!value || typeof value !== "object") {
68
+ return [];
69
+ }
70
+ const record = value;
71
+ const fields = [
72
+ ["name", record.name],
73
+ ["code", record.code],
74
+ ["errno", record.errno],
75
+ ["type", record.type],
76
+ ["syscall", record.syscall],
77
+ ["hostname", record.hostname],
78
+ ["host", record.host],
79
+ ["address", record.address],
80
+ ["port", record.port]
81
+ ];
82
+ return fields.filter(([, fieldValue]) => fieldValue != null && String(fieldValue).trim()).map(([key, fieldValue]) => `${key}=${String(fieldValue)}`);
83
+ };
84
+ var describeErrorValue = (value) => {
85
+ if (value == null) {
86
+ return "";
87
+ }
88
+ if (typeof value === "string") {
89
+ return value;
90
+ }
91
+ if (value instanceof Error) {
92
+ const message = value.message?.trim() || "";
93
+ const fields = getErrorFields(value);
94
+ return [message, ...fields].filter(Boolean).join(" ");
95
+ }
96
+ if (typeof value === "object") {
97
+ const fields = getErrorFields(value);
98
+ if (fields.length) {
99
+ return fields.join(" ");
100
+ }
101
+ try {
102
+ return JSON.stringify(value);
103
+ } catch {
104
+ return String(value);
105
+ }
106
+ }
107
+ return String(value);
108
+ };
67
109
  var getErrorMessage = (error) => {
68
110
  const maybeError = error;
69
- const message = maybeError?.message || String(error);
111
+ const message = describeErrorValue(error) || String(error);
70
112
  if (maybeError?.cause == null) {
71
113
  return message;
72
114
  }
73
- const cause = maybeError.cause instanceof Error ? maybeError.cause.message : String(maybeError.cause);
115
+ const cause = describeErrorValue(maybeError.cause);
116
+ if (!cause) {
117
+ return message;
118
+ }
74
119
  return `${message}; cause: ${cause}`;
75
120
  };
76
121
  var truncateText = (value, maxLength = SCREENSHOT_CONSOLE_TEXT_LIMIT) => value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
@@ -97,13 +142,13 @@ var getScreenshotPath = ({ symbol, signalId, interval }, projectRoot) => {
97
142
  getScreenshotFilename({ symbol, signalId, interval })
98
143
  );
99
144
  };
100
- var screenDashboard = async (signal, projectRoot) => {
145
+ var screenDashboard = async (signal, projectRoot, userName = "root") => {
101
146
  const { symbol, signalId, interval } = signal;
102
147
  const screenshotBaseUrl = getScreenshotRenderBaseUrl();
103
148
  const screenshotPath = getScreenshotPath(signal, projectRoot);
104
- const rootUser = await getData(redisKeys.user("root"), null);
105
- const token2 = rootUser && typeof rootUser === "object" ? rootUser.token : null;
106
- const tokenParam = typeof token2 === "string" && token2.length > 0 ? `&token=${encodeURIComponent(token2)}` : "";
149
+ const settings = await getUserSettings(userName);
150
+ const token = settings.token;
151
+ const tokenParam = typeof token === "string" && token.length > 0 ? `&token=${encodeURIComponent(token)}` : "";
107
152
  const dashboardUrl = `${screenshotBaseUrl}/routes/dashboard/bybit/${symbol}/${interval}/?signalId=${signalId}&autoZoom=true&screenshot=1${tokenParam}`;
108
153
  const maskedDashboardUrl = maskTokenInUrl(dashboardUrl);
109
154
  logger.info(
@@ -128,6 +173,7 @@ var screenDashboard = async (signal, projectRoot) => {
128
173
  ]
129
174
  });
130
175
  const browserProcess = browser.process();
176
+ let browserCloseRequested = false;
131
177
  try {
132
178
  if (browserProcess) {
133
179
  logger.info(
@@ -147,6 +193,9 @@ var screenDashboard = async (signal, projectRoot) => {
147
193
  await browser.version()
148
194
  );
149
195
  browser.on("disconnected", () => {
196
+ if (browserCloseRequested) {
197
+ return;
198
+ }
150
199
  logger.error(
151
200
  "screenshot browser disconnected: %s %sm captureAttempt=%d",
152
201
  symbol,
@@ -345,6 +394,7 @@ var screenDashboard = async (signal, projectRoot) => {
345
394
  await delay(SCREENSHOT_CAPTURE_RETRY_DELAY_MS);
346
395
  }
347
396
  } finally {
397
+ browserCloseRequested = true;
348
398
  await browser.close().catch(() => void 0);
349
399
  }
350
400
  }
@@ -355,14 +405,65 @@ var screenDashboard = async (signal, projectRoot) => {
355
405
  import { delay as delay2 } from "@tradejs/core/async";
356
406
  import { formatNumber } from "@tradejs/core/math";
357
407
  import { logger as logger2 } from "@tradejs/infra/logger";
408
+ import { getUserSettings as getUserSettings2 } from "@tradejs/infra/userSettings";
358
409
  var escapeHtml = (s) => {
359
410
  if (!s) return "";
360
411
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
361
412
  };
362
- var { APP_URL: APP_URL2, TG_BOT_TOKEN: token, TG_CHAT_ID: chatId } = process.env;
413
+ var { APP_URL: APP_URL2 } = process.env;
363
414
  var TG_REQUEST_ATTEMPTS = 3;
364
415
  var TG_REQUEST_RETRY_DELAY_MS = 2e3;
416
+ var getErrorFields2 = (value) => {
417
+ if (!value || typeof value !== "object") {
418
+ return [];
419
+ }
420
+ const record = value;
421
+ const fields = [
422
+ ["name", record.name],
423
+ ["code", record.code],
424
+ ["errno", record.errno],
425
+ ["type", record.type],
426
+ ["syscall", record.syscall],
427
+ ["hostname", record.hostname],
428
+ ["host", record.host],
429
+ ["address", record.address],
430
+ ["port", record.port]
431
+ ];
432
+ return fields.filter(([, fieldValue]) => fieldValue != null && String(fieldValue).trim()).map(([key, fieldValue]) => `${key}=${String(fieldValue)}`);
433
+ };
434
+ var describeErrorValue2 = (value) => {
435
+ if (value == null) {
436
+ return "";
437
+ }
438
+ if (typeof value === "string") {
439
+ return value;
440
+ }
441
+ if (value instanceof Error) {
442
+ const message = value.message?.trim() || "";
443
+ const fields = getErrorFields2(value);
444
+ return [message, ...fields].filter(Boolean).join(" ");
445
+ }
446
+ if (typeof value === "object") {
447
+ const fields = getErrorFields2(value);
448
+ if (fields.length) {
449
+ return fields.join(" ");
450
+ }
451
+ try {
452
+ return JSON.stringify(value);
453
+ } catch {
454
+ return String(value);
455
+ }
456
+ }
457
+ return String(value);
458
+ };
365
459
  var normalizeQuality = (value) => typeof value === "number" ? Math.max(1, Math.min(5, Math.round(value))) : null;
460
+ var formatOrderSkipReason = (reason) => {
461
+ if (!reason) return "";
462
+ if (reason.startsWith("AI_QUALITY_BELOW_MIN")) {
463
+ return "AI_QUALITY_BELOW_MIN";
464
+ }
465
+ return reason;
466
+ };
366
467
  var getLastNumber = (value) => {
367
468
  if (Array.isArray(value)) {
368
469
  const last = value[value.length - 1];
@@ -402,11 +503,14 @@ var getTelegramErrorReason = (data) => {
402
503
  };
403
504
  var getErrorMessage2 = (error) => {
404
505
  const maybeError = error;
405
- const message = maybeError?.message || String(error);
506
+ const message = describeErrorValue2(error) || String(error);
406
507
  if (maybeError?.cause == null) {
407
508
  return message;
408
509
  }
409
- const cause = maybeError.cause instanceof Error ? maybeError.cause.message : String(maybeError.cause);
510
+ const cause = describeErrorValue2(maybeError.cause);
511
+ if (!cause) {
512
+ return message;
513
+ }
410
514
  return `${message}; cause: ${cause}`;
411
515
  };
412
516
  var parseTelegramResponse = async (response) => {
@@ -429,7 +533,20 @@ var parseTelegramResponse = async (response) => {
429
533
  }
430
534
  }
431
535
  };
432
- var requestTelegram = async (method, init) => {
536
+ var getTelegramSettings = async (userName = "root") => {
537
+ const settings = await getUserSettings2(userName);
538
+ const token = settings.TG_BOT_TOKEN;
539
+ const chatId = settings.TG_CHAT_ID;
540
+ if (!token || !chatId) {
541
+ throw new Error(`Telegram settings are incomplete for user ${userName}`);
542
+ }
543
+ return { token, chatId };
544
+ };
545
+ var requestTelegram = async ({
546
+ method,
547
+ token,
548
+ init
549
+ }) => {
433
550
  let lastError = null;
434
551
  for (let attempt = 1; attempt <= TG_REQUEST_ATTEMPTS; attempt += 1) {
435
552
  try {
@@ -457,17 +574,23 @@ var requestTelegram = async (method, init) => {
457
574
  };
458
575
  var sendTelegramMessage = async ({
459
576
  message,
460
- markup
577
+ markup,
578
+ token,
579
+ chatId
461
580
  }) => {
462
- const data = await requestTelegram("sendMessage", {
463
- method: "POST",
464
- headers: { "Content-Type": "application/json" },
465
- body: JSON.stringify({
466
- chat_id: chatId,
467
- text: message,
468
- reply_markup: markup,
469
- parse_mode: "HTML"
470
- })
581
+ const data = await requestTelegram({
582
+ method: "sendMessage",
583
+ token,
584
+ init: {
585
+ method: "POST",
586
+ headers: { "Content-Type": "application/json" },
587
+ body: JSON.stringify({
588
+ chat_id: chatId,
589
+ text: message,
590
+ reply_markup: markup,
591
+ parse_mode: "HTML"
592
+ })
593
+ }
471
594
  });
472
595
  logger2.info(
473
596
  "tg sendMessage: %s",
@@ -526,7 +649,9 @@ var formatMessage = (signal, analysis) => {
526
649
  }
527
650
  lines.push(orderStatusText);
528
651
  if ((orderStatus === "skipped" || orderStatus === "canceled") && orderSkipReason) {
529
- lines.push(`Skip reason: <b>${escapeHtml(orderSkipReason)}</b>`);
652
+ lines.push(
653
+ `Skip reason: <b>${escapeHtml(formatOrderSkipReason(orderSkipReason))}</b>`
654
+ );
530
655
  }
531
656
  }
532
657
  if (isConfigFromBacktest) {
@@ -544,21 +669,9 @@ var formatMessage = (signal, analysis) => {
544
669
  lines.push(aiQualityLine);
545
670
  }
546
671
  lines.push("");
547
- if (touches) {
548
- lines.push(`Points: ${touches}`);
549
- }
550
- if (atrPct != null && Number.isFinite(atrPct)) {
551
- lines.push(`ATR: ${atrPct.toFixed(2)}`);
552
- }
553
- if (distance) {
554
- lines.push(`Distance: ${distance}`);
555
- }
556
672
  if (correlation) {
557
673
  lines.push(`BTC correlation: ${correlation}`);
558
674
  }
559
- if (spread != null && Number.isFinite(spread)) {
560
- lines.push(`BTC spread (CB-BN)/BN: ${spread.toFixed(6)}`);
561
- }
562
675
  const prices = formatPrices();
563
676
  if (prices) {
564
677
  lines.push("");
@@ -572,8 +685,9 @@ var formatMessage = (signal, analysis) => {
572
685
  \u0414\u0435\u0442\u0430\u043B\u0438: ${err.message || String(err)}`;
573
686
  }
574
687
  };
575
- var sendSignal = async (signal, imgInterval, analysis) => {
688
+ var sendSignal = async (signal, imgInterval, analysis, options = {}) => {
576
689
  const { symbol, signalId, interval } = signal;
690
+ const { token, chatId } = await getTelegramSettings(options.userName);
577
691
  const message = formatMessage(signal, analysis);
578
692
  const publicAppUrl = APP_URL2?.startsWith("https") ? APP_URL2 : null;
579
693
  const dashboardUrl = publicAppUrl ? `${APP_URL2}/routes/dashboard/bybit/${symbol}/${interval}/?signalId=${signalId}` : null;
@@ -599,7 +713,7 @@ var sendSignal = async (signal, imgInterval, analysis) => {
599
713
  symbol,
600
714
  error?.message || String(error)
601
715
  );
602
- await sendTelegramMessage({ message, markup });
716
+ await sendTelegramMessage({ message, markup, token, chatId });
603
717
  return;
604
718
  }
605
719
  const photoBody = new FormData();
@@ -616,9 +730,13 @@ var sendSignal = async (signal, imgInterval, analysis) => {
616
730
  }
617
731
  let data;
618
732
  try {
619
- data = await requestTelegram("sendPhoto", {
620
- method: "POST",
621
- body: photoBody
733
+ data = await requestTelegram({
734
+ method: "sendPhoto",
735
+ token,
736
+ init: {
737
+ method: "POST",
738
+ body: photoBody
739
+ }
622
740
  });
623
741
  } catch (error) {
624
742
  const reason = getErrorMessage2(error);
@@ -628,7 +746,9 @@ var sendSignal = async (signal, imgInterval, analysis) => {
628
746
 
629
747
  \u26A0\uFE0F <b>Photo delivery failed</b>
630
748
  Reason: <code>${escapeHtml(reason)}</code>`,
631
- markup
749
+ markup,
750
+ token,
751
+ chatId
632
752
  });
633
753
  return;
634
754
  }
@@ -640,7 +760,9 @@ Reason: <code>${escapeHtml(reason)}</code>`,
640
760
 
641
761
  \u26A0\uFE0F <b>Photo delivery failed</b>
642
762
  Reason: <code>${escapeHtml(reason)}</code>`,
643
- markup
763
+ markup,
764
+ token,
765
+ chatId
644
766
  });
645
767
  return;
646
768
  }
@@ -688,16 +810,21 @@ ${escapeHtml(clean)}`);
688
810
  }
689
811
  return lines.join("\n");
690
812
  };
691
- var sendSignalAnalysis = async (signal, analysis) => {
813
+ var sendSignalAnalysis = async (signal, analysis, options = {}) => {
814
+ const { token, chatId } = await getTelegramSettings(options.userName);
692
815
  const message = formatAnalysisMessage(signal, analysis);
693
- const data = await requestTelegram("sendMessage", {
694
- method: "POST",
695
- headers: { "Content-Type": "application/json" },
696
- body: JSON.stringify({
697
- chat_id: chatId,
698
- text: message,
699
- parse_mode: "HTML"
700
- })
816
+ const data = await requestTelegram({
817
+ method: "sendMessage",
818
+ token,
819
+ init: {
820
+ method: "POST",
821
+ headers: { "Content-Type": "application/json" },
822
+ body: JSON.stringify({
823
+ chat_id: chatId,
824
+ text: message,
825
+ parse_mode: "HTML"
826
+ })
827
+ }
701
828
  });
702
829
  logger2.info(
703
830
  "tg sendMessage (analysis): %s",
@@ -762,8 +889,8 @@ var cleanRedis = async (area) => {
762
889
  }
763
890
  logger3.info("");
764
891
  };
765
- var update = async (connector, interval, tickers) => {
766
- const PRELOAD_START = getTimestamp(PRELOAD_DAYS);
892
+ var update = async (connector, interval, tickers, preloadDays = PRELOAD_DAYS) => {
893
+ const PRELOAD_START = getTimestamp(preloadDays);
767
894
  const PRELOAD_END = getTimestamp();
768
895
  const bar = new ProgressBar(
769
896
  ":current/:total [:bar][:percent] :eta(s) :symbol",
@@ -774,7 +901,7 @@ var update = async (connector, interval, tickers) => {
774
901
  );
775
902
  logger3.info(
776
903
  chalk.yellow(
777
- `update: ${tickers.length} (klineConcurrency=${KLINE_CONCURRENCY_LIMIT})`
904
+ `update: ${tickers.length} (klineConcurrency=${KLINE_CONCURRENCY_LIMIT}, preloadDays=${preloadDays})`
778
905
  )
779
906
  );
780
907
  const queue = tickers.slice();
@@ -788,7 +915,8 @@ var update = async (connector, interval, tickers) => {
788
915
  start: PRELOAD_START,
789
916
  end: PRELOAD_END,
790
917
  interval,
791
- silent: true
918
+ silent: true,
919
+ warmOnly: true
792
920
  });
793
921
  } catch {
794
922
  logger3.error("Failed loading: %s", symbol);
@@ -841,7 +969,7 @@ var getTickers = async (connector, include = "", exclude = "", limit, chunk) =>
841
969
  }
842
970
  return tickers.filter((t) => !excludeTickers.includes(t));
843
971
  };
844
- var makeScreenshots = async (signals, interval) => {
972
+ var makeScreenshots = async (signals, interval, userName = "root") => {
845
973
  const projectRoot = getProjectRoot2();
846
974
  const bar = new ProgressBar(
847
975
  ":current/:total [:bar][:percent] :eta(s) :symbol",
@@ -856,7 +984,7 @@ var makeScreenshots = async (signals, interval) => {
856
984
  SCREENSHOT_CONCURRENCY_LIMIT,
857
985
  async (signal) => {
858
986
  try {
859
- await screenDashboard({ ...signal, interval }, projectRoot);
987
+ await screenDashboard({ ...signal, interval }, projectRoot, userName);
860
988
  } catch (error) {
861
989
  logger3.error(
862
990
  "Failed screenshot: %s (%s)",
@@ -870,7 +998,7 @@ var makeScreenshots = async (signals, interval) => {
870
998
  );
871
999
  logger3.info("");
872
1000
  };
873
- var sendToAI = async (signals) => {
1001
+ var sendToAI = async (signals, userName = "root") => {
874
1002
  const bar = new ProgressBar(
875
1003
  ":current/:total [:bar][:percent] :eta(s) :symbol",
876
1004
  {
@@ -881,7 +1009,7 @@ var sendToAI = async (signals) => {
881
1009
  logger3.info(chalk.yellow("AI:", signals.length));
882
1010
  await runWithConcurrency(signals, AI_CONCURRENCY_LIMIT, async (signal) => {
883
1011
  try {
884
- await askAI(signal);
1012
+ await askAI(signal, { userName });
885
1013
  } catch {
886
1014
  logger3.error("Failed ask: %s", signal.symbol);
887
1015
  } finally {
@@ -890,7 +1018,7 @@ var sendToAI = async (signals) => {
890
1018
  });
891
1019
  logger3.info("");
892
1020
  };
893
- var sendToTG = async (signals, imgInterval) => {
1021
+ var sendToTG = async (signals, imgInterval, userName = "root") => {
894
1022
  const bar = new ProgressBar(
895
1023
  ":current/:total [:bar][:percent] :eta(s) :symbol",
896
1024
  {
@@ -899,15 +1027,15 @@ var sendToTG = async (signals, imgInterval) => {
899
1027
  }
900
1028
  );
901
1029
  logger3.info(chalk.yellow("messages:", signals.length));
902
- await runWithConcurrency(signals, TG_CONCURRENCY_LIMIT, async (signal) => {
1030
+ await runWithConcurrency(signals, 1, async (signal) => {
903
1031
  try {
904
- const analysis = await getData2(
905
- redisKeys2.analysis(signal.symbol, signal.signalId),
1032
+ const analysis = await getData(
1033
+ redisKeys.analysis(signal.symbol, signal.signalId),
906
1034
  null
907
1035
  );
908
- await sendSignal(signal, imgInterval, analysis);
1036
+ await sendSignal(signal, imgInterval, analysis, { userName });
909
1037
  if (analysis && typeof analysis === "object" && Object.keys(analysis).length > 0) {
910
- await sendSignalAnalysis(signal, analysis);
1038
+ await sendSignalAnalysis(signal, analysis, { userName });
911
1039
  }
912
1040
  } catch (err) {
913
1041
  logger3.error(
@@ -258,7 +258,7 @@ var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
258
258
  cachedByCwd.set(cwd, config);
259
259
  if (!announcedConfigFile.has(configFilePath)) {
260
260
  announcedConfigFile.add(configFilePath);
261
- import_logger.logger.log("info", "Loaded TradeJS config: %s", configFilePath);
261
+ import_logger.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
262
262
  }
263
263
  return config;
264
264
  } catch (error) {
@@ -10,8 +10,8 @@ import {
10
10
  registerConnectorEntries,
11
11
  resetConnectorRegistryCache,
12
12
  resolveConnectorName
13
- } from "./chunk-CIY64D57.mjs";
14
- import "./chunk-P2ZUWONT.mjs";
13
+ } from "./chunk-XW5L327F.mjs";
14
+ import "./chunk-CGJ2UU6H.mjs";
15
15
  import "./chunk-6DZX6EAA.mjs";
16
16
  export {
17
17
  BUILTIN_CONNECTOR_NAMES,
package/dist/pine.d.mts CHANGED
@@ -22,11 +22,15 @@ interface RunPineScriptParams {
22
22
  limit?: number;
23
23
  }
24
24
  declare const getPinePlotSeries: (context: PineContextLike, plotName: string) => PinePlotPoint[];
25
- declare const getLatestPinePlotValue: (context: PineContextLike, plotName: string) => unknown;
26
- declare const asFiniteNumber: (value: unknown) => number | undefined;
27
- declare const asPineBoolean: (value: unknown) => boolean;
28
- declare const loadPineScript: (filePath: string, fallback?: string) => string;
29
- declare const createLoadPineScript: (baseDir: string) => ((fileNameOrPath: string, fallback?: string) => string);
25
+ declare const getLatestPineRawPlotValue: (context: PineContextLike, plotName: string) => unknown;
26
+ declare const getLatestPineNumberPlotValue: (context: PineContextLike, plotName: string) => number | null;
27
+ declare const getLatestPineBooleanPlotValue: (context: PineContextLike, plotName: string) => boolean;
28
+ declare const getLatestPineNumberPlotValues: <TPlotName extends string>(context: PineContextLike, plotNames: readonly TPlotName[]) => Record<TPlotName, number | null>;
29
+ declare const getLatestPineBooleanPlotValues: <TPlotName extends string>(context: PineContextLike, plotNames: readonly TPlotName[]) => Record<TPlotName, boolean>;
30
+ declare const toFiniteNumber: (value: unknown) => number | undefined;
31
+ declare const toPineBoolean: (value: unknown) => boolean;
32
+ declare const loadPineScriptFile: (filePath: string, fallback?: string) => string;
33
+ declare const createPineScriptLoader: (baseDir: string) => ((fileNameOrPath: string, fallback?: string) => string);
30
34
  declare const runPineScript: ({ candles, script, symbol, timeframe, inputs, limit, }: RunPineScriptParams) => Promise<PineContextLike>;
31
35
 
32
- export { type PineContextLike, type PinePlotPoint, type RunPineScriptParams, asFiniteNumber, asPineBoolean, createLoadPineScript, getLatestPinePlotValue, getPinePlotSeries, loadPineScript, runPineScript };
36
+ export { type PineContextLike, type PinePlotPoint, type RunPineScriptParams, createPineScriptLoader, getLatestPineBooleanPlotValue, getLatestPineBooleanPlotValues, getLatestPineNumberPlotValue, getLatestPineNumberPlotValues, getLatestPineRawPlotValue, getPinePlotSeries, loadPineScriptFile, runPineScript, toFiniteNumber, toPineBoolean };
package/dist/pine.d.ts CHANGED
@@ -22,11 +22,15 @@ interface RunPineScriptParams {
22
22
  limit?: number;
23
23
  }
24
24
  declare const getPinePlotSeries: (context: PineContextLike, plotName: string) => PinePlotPoint[];
25
- declare const getLatestPinePlotValue: (context: PineContextLike, plotName: string) => unknown;
26
- declare const asFiniteNumber: (value: unknown) => number | undefined;
27
- declare const asPineBoolean: (value: unknown) => boolean;
28
- declare const loadPineScript: (filePath: string, fallback?: string) => string;
29
- declare const createLoadPineScript: (baseDir: string) => ((fileNameOrPath: string, fallback?: string) => string);
25
+ declare const getLatestPineRawPlotValue: (context: PineContextLike, plotName: string) => unknown;
26
+ declare const getLatestPineNumberPlotValue: (context: PineContextLike, plotName: string) => number | null;
27
+ declare const getLatestPineBooleanPlotValue: (context: PineContextLike, plotName: string) => boolean;
28
+ declare const getLatestPineNumberPlotValues: <TPlotName extends string>(context: PineContextLike, plotNames: readonly TPlotName[]) => Record<TPlotName, number | null>;
29
+ declare const getLatestPineBooleanPlotValues: <TPlotName extends string>(context: PineContextLike, plotNames: readonly TPlotName[]) => Record<TPlotName, boolean>;
30
+ declare const toFiniteNumber: (value: unknown) => number | undefined;
31
+ declare const toPineBoolean: (value: unknown) => boolean;
32
+ declare const loadPineScriptFile: (filePath: string, fallback?: string) => string;
33
+ declare const createPineScriptLoader: (baseDir: string) => ((fileNameOrPath: string, fallback?: string) => string);
30
34
  declare const runPineScript: ({ candles, script, symbol, timeframe, inputs, limit, }: RunPineScriptParams) => Promise<PineContextLike>;
31
35
 
32
- export { type PineContextLike, type PinePlotPoint, type RunPineScriptParams, asFiniteNumber, asPineBoolean, createLoadPineScript, getLatestPinePlotValue, getPinePlotSeries, loadPineScript, runPineScript };
36
+ export { type PineContextLike, type PinePlotPoint, type RunPineScriptParams, createPineScriptLoader, getLatestPineBooleanPlotValue, getLatestPineBooleanPlotValues, getLatestPineNumberPlotValue, getLatestPineNumberPlotValues, getLatestPineRawPlotValue, getPinePlotSeries, loadPineScriptFile, runPineScript, toFiniteNumber, toPineBoolean };
package/dist/pine.js CHANGED
@@ -30,13 +30,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/pine.ts
31
31
  var pine_exports = {};
32
32
  __export(pine_exports, {
33
- asFiniteNumber: () => asFiniteNumber,
34
- asPineBoolean: () => asPineBoolean,
35
- createLoadPineScript: () => createLoadPineScript,
36
- getLatestPinePlotValue: () => getLatestPinePlotValue,
33
+ createPineScriptLoader: () => createPineScriptLoader,
34
+ getLatestPineBooleanPlotValue: () => getLatestPineBooleanPlotValue,
35
+ getLatestPineBooleanPlotValues: () => getLatestPineBooleanPlotValues,
36
+ getLatestPineNumberPlotValue: () => getLatestPineNumberPlotValue,
37
+ getLatestPineNumberPlotValues: () => getLatestPineNumberPlotValues,
38
+ getLatestPineRawPlotValue: () => getLatestPineRawPlotValue,
37
39
  getPinePlotSeries: () => getPinePlotSeries,
38
- loadPineScript: () => loadPineScript,
39
- runPineScript: () => runPineScript
40
+ loadPineScriptFile: () => loadPineScriptFile,
41
+ runPineScript: () => runPineScript,
42
+ toFiniteNumber: () => toFiniteNumber,
43
+ toPineBoolean: () => toPineBoolean
40
44
  });
41
45
  module.exports = __toCommonJS(pine_exports);
42
46
  var import_node_fs = __toESM(require("fs"));
@@ -47,18 +51,32 @@ var getPinePlotSeries = (context, plotName) => {
47
51
  const data = context?.plots?.[name]?.data;
48
52
  return Array.isArray(data) ? data : [];
49
53
  };
50
- var getLatestPinePlotValue = (context, plotName) => {
54
+ var getLatestPineRawPlotValue = (context, plotName) => {
51
55
  const series = getPinePlotSeries(context, plotName);
52
56
  if (!series.length) return void 0;
53
57
  return series[series.length - 1]?.value;
54
58
  };
55
- var asFiniteNumber = (value) => {
59
+ var getLatestPineNumberPlotValue = (context, plotName) => toFiniteNumber(getLatestPineRawPlotValue(context, plotName)) ?? null;
60
+ var getLatestPineBooleanPlotValue = (context, plotName) => toPineBoolean(getLatestPineRawPlotValue(context, plotName));
61
+ var getLatestPineNumberPlotValues = (context, plotNames) => Object.fromEntries(
62
+ plotNames.map((plotName) => [
63
+ plotName,
64
+ getLatestPineNumberPlotValue(context, plotName)
65
+ ])
66
+ );
67
+ var getLatestPineBooleanPlotValues = (context, plotNames) => Object.fromEntries(
68
+ plotNames.map((plotName) => [
69
+ plotName,
70
+ getLatestPineBooleanPlotValue(context, plotName)
71
+ ])
72
+ );
73
+ var toFiniteNumber = (value) => {
56
74
  if (typeof value !== "number" || !Number.isFinite(value)) {
57
75
  return void 0;
58
76
  }
59
77
  return value;
60
78
  };
61
- var asPineBoolean = (value) => {
79
+ var toPineBoolean = (value) => {
62
80
  if (typeof value === "boolean") return value;
63
81
  if (typeof value === "number") return Number.isFinite(value) && value !== 0;
64
82
  return false;
@@ -68,7 +86,7 @@ var loadPinets = () => {
68
86
  const cjsPath = resolvedPath.includes("pinets.min.browser") ? resolvedPath.replace(/pinets\.min\.browser(\.es)?\.js$/, "pinets.min.cjs") : resolvedPath;
69
87
  return require(cjsPath);
70
88
  };
71
- var loadPineScript = (filePath, fallback = "") => {
89
+ var loadPineScriptFile = (filePath, fallback = "") => {
72
90
  const resolvedPath = String(filePath || "").trim();
73
91
  if (!resolvedPath) {
74
92
  return fallback;
@@ -79,7 +97,7 @@ var loadPineScript = (filePath, fallback = "") => {
79
97
  return fallback;
80
98
  }
81
99
  };
82
- var createLoadPineScript = (baseDir) => {
100
+ var createPineScriptLoader = (baseDir) => {
83
101
  const resolvedBaseDir = import_node_path.default.resolve(baseDir);
84
102
  return (fileNameOrPath, fallback = "") => {
85
103
  const rawPath = String(fileNameOrPath || "").trim();
@@ -87,7 +105,7 @@ var createLoadPineScript = (baseDir) => {
87
105
  return fallback;
88
106
  }
89
107
  const resolvedPath = import_node_path.default.isAbsolute(rawPath) ? rawPath : import_node_path.default.resolve(resolvedBaseDir, rawPath);
90
- return loadPineScript(resolvedPath, fallback);
108
+ return loadPineScriptFile(resolvedPath, fallback);
91
109
  };
92
110
  };
93
111
  var MINUTE_MS = 6e4;
@@ -145,11 +163,15 @@ var runPineScript = async ({
145
163
  };
146
164
  // Annotate the CommonJS export names for ESM import in node:
147
165
  0 && (module.exports = {
148
- asFiniteNumber,
149
- asPineBoolean,
150
- createLoadPineScript,
151
- getLatestPinePlotValue,
166
+ createPineScriptLoader,
167
+ getLatestPineBooleanPlotValue,
168
+ getLatestPineBooleanPlotValues,
169
+ getLatestPineNumberPlotValue,
170
+ getLatestPineNumberPlotValues,
171
+ getLatestPineRawPlotValue,
152
172
  getPinePlotSeries,
153
- loadPineScript,
154
- runPineScript
173
+ loadPineScriptFile,
174
+ runPineScript,
175
+ toFiniteNumber,
176
+ toPineBoolean
155
177
  });