@tradejs/cli 1.0.8 → 1.0.9

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.
@@ -474,6 +474,80 @@ var backfillDerivativesContext = async (params, enabled) => {
474
474
  };
475
475
  var backfillDerivativesContextForSignals = async (params) => backfillDerivativesContext(params, isSignalsDerivativesContextEnabled());
476
476
 
477
+ // src/lib/runtimeSignalsStorage.ts
478
+ var DAY_MS2 = 864e5;
479
+ var RUNTIME_STORAGE_DAY_OFFSET_MS = 5 * 60 * 60 * 1e3;
480
+ var toIsoDayKey = (timestamp) => new Date(timestamp).toISOString().slice(0, 10);
481
+ var toRuntimeStorageDayTimestamp = (timestamp) => Math.floor((timestamp + RUNTIME_STORAGE_DAY_OFFSET_MS) / DAY_MS2) * DAY_MS2;
482
+ var getRuntimeStorageDayKey = (timestamp) => toIsoDayKey(toRuntimeStorageDayTimestamp(timestamp));
483
+ var toRuntimeSignalBucketRef = (signal) => ({
484
+ signalId: signal.signalId,
485
+ symbol: signal.symbol,
486
+ strategy: signal.strategy,
487
+ timestamp: signal.timestamp
488
+ });
489
+ var normalizeRuntimeSignalSkipReason = (reason, fallbackSource) => {
490
+ let source = fallbackSource;
491
+ let normalizedReason = reason;
492
+ if (reason.startsWith("AI_QUALITY_BELOW_MIN")) {
493
+ source = "AI";
494
+ normalizedReason = "MIN_AI_QUALITY";
495
+ } else if (reason === "AI_QUALITY_UNAVAILABLE") {
496
+ source = "AI";
497
+ normalizedReason = "QUALITY_UNAVAILABLE";
498
+ } else if (reason === "ML_RESULT_UNAVAILABLE") {
499
+ source = "ML";
500
+ normalizedReason = "RESULT_UNAVAILABLE";
501
+ } else if (reason.startsWith("ML_THRESHOLD_NOT_MET")) {
502
+ source = "ML";
503
+ normalizedReason = "ML_THRESHOLD";
504
+ } else if (reason.startsWith("HOOK_BEFORE_ENTRY_GATE:")) {
505
+ source = "hook";
506
+ normalizedReason = `BEFORE_ENTRY_GATE:${reason.slice(
507
+ "HOOK_BEFORE_ENTRY_GATE:".length
508
+ )}`;
509
+ } else if (reason === "HOOK_BEFORE_ENTRY_GATE") {
510
+ source = "hook";
511
+ normalizedReason = "BEFORE_ENTRY_GATE";
512
+ } else if (reason === "MAKE_ORDERS_DISABLED" || reason === "ENTRY_POLICY_BLOCKED") {
513
+ source = "policy";
514
+ }
515
+ return {
516
+ source: `skip from ${source}`,
517
+ reason: normalizedReason
518
+ };
519
+ };
520
+ var buildStatsReasonField = (source, reason) => `reason:${source}:${reason}`;
521
+ var buildRuntimeSignalStatsIncrements = (evaluation) => {
522
+ const increments = {
523
+ evaluated: 1
524
+ };
525
+ if (evaluation.status === "signal") {
526
+ increments.signals = 1;
527
+ }
528
+ let skipReason = null;
529
+ let fallbackSource = "core";
530
+ if (evaluation.status === "skip") {
531
+ skipReason = evaluation.reason || "NO_SIGNAL";
532
+ fallbackSource = "core";
533
+ } else if (evaluation.status === "signal" && (evaluation.orderStatus === "skipped" || evaluation.orderStatus === "canceled")) {
534
+ skipReason = evaluation.orderSkipReason || evaluation.reason || `orderStatus=${evaluation.orderStatus}`;
535
+ fallbackSource = "runtime";
536
+ } else if (evaluation.status === "error") {
537
+ skipReason = evaluation.reason || "EVALUATION_ERROR";
538
+ fallbackSource = "runtime";
539
+ }
540
+ if (skipReason) {
541
+ const normalized = normalizeRuntimeSignalSkipReason(
542
+ skipReason,
543
+ fallbackSource
544
+ );
545
+ increments[buildStatsReasonField(normalized.source, normalized.reason)] = 1;
546
+ }
547
+ return increments;
548
+ };
549
+ var shouldStoreDetailedRuntimeSignalEvaluation = (evaluation) => evaluation.status !== "skip";
550
+
477
551
  // src/scripts/signals.ts
478
552
  import_args.default.option(["t", "tickers"], "Selected tickers");
479
553
  import_args.default.option(["e", "exclude"], "Exclude tickers from tests");
@@ -589,40 +663,13 @@ var createStrategySkipStats = (runtimeStrategies) => new Map(
589
663
  }
590
664
  ])
591
665
  );
592
- var normalizeSkipStatsReason = (reason, fallbackSource) => {
593
- let source = fallbackSource;
594
- let normalizedReason = reason;
595
- if (reason.startsWith("AI_QUALITY_BELOW_MIN")) {
596
- source = "AI";
597
- normalizedReason = "MIN_AI_QUALITY";
598
- } else if (reason === "AI_QUALITY_UNAVAILABLE") {
599
- source = "AI";
600
- normalizedReason = "QUALITY_UNAVAILABLE";
601
- } else if (reason === "ML_RESULT_UNAVAILABLE") {
602
- source = "ML";
603
- normalizedReason = "RESULT_UNAVAILABLE";
604
- } else if (reason.startsWith("ML_THRESHOLD_NOT_MET")) {
605
- source = "ML";
606
- normalizedReason = "ML_THRESHOLD";
607
- } else if (reason.startsWith("HOOK_BEFORE_ENTRY_GATE:")) {
608
- source = "hook";
609
- normalizedReason = `BEFORE_ENTRY_GATE:${reason.slice(
610
- "HOOK_BEFORE_ENTRY_GATE:".length
611
- )}`;
612
- } else if (reason === "HOOK_BEFORE_ENTRY_GATE") {
613
- source = "hook";
614
- normalizedReason = "BEFORE_ENTRY_GATE";
615
- } else if (reason === "MAKE_ORDERS_DISABLED" || reason === "ENTRY_POLICY_BLOCKED") {
616
- source = "policy";
617
- }
618
- return `skip from ${source} / ${normalizedReason}`;
619
- };
620
666
  var recordStrategyReason = (strategyStats, strategyName, reason, fallbackSource = "core") => {
621
667
  const stats = strategyStats.get(strategyName);
622
668
  if (!stats) {
623
669
  return;
624
670
  }
625
- const normalizedReason = normalizeSkipStatsReason(reason, fallbackSource);
671
+ const normalized = normalizeRuntimeSignalSkipReason(reason, fallbackSource);
672
+ const normalizedReason = `${normalized.source} / ${normalized.reason}`;
626
673
  stats.reasons.set(
627
674
  normalizedReason,
628
675
  (stats.reasons.get(normalizedReason) ?? 0) + 1
@@ -633,28 +680,34 @@ var buildRuntimeSignalEvaluationId = ({
633
680
  symbol,
634
681
  timestamp
635
682
  }) => `${strategyName}:${symbol}:${timestamp}`;
636
- var hasMeaningfulEvaluationReason = (value) => {
637
- const reason = typeof value === "string" ? value.trim() : "";
638
- return reason.length > 0 && reason !== "NO_SIGNAL";
639
- };
640
- var resolveRuntimeSignalEvaluationTtl = (evaluation) => {
641
- if (evaluation.status === "signal" || evaluation.status === "error") {
642
- return import_constants2.TTL_1M;
643
- }
644
- if (hasMeaningfulEvaluationReason(evaluation.reason) || hasMeaningfulEvaluationReason(evaluation.orderSkipReason)) {
645
- return import_constants2.TTL_1M;
646
- }
647
- return import_constants2.TTL_3D;
648
- };
683
+ var getTelegramDeliverableSignals = (signals2) => signals2.filter(
684
+ (signal) => signal.orderStatus !== "skipped" && signal.orderStatus !== "canceled"
685
+ );
649
686
  var saveRuntimeSignalEvaluation = async (evaluation) => {
650
- await (0, import_redis.setData)(
651
- import_redis.redisKeys.runtimeSignalEvaluation(
687
+ const dayKey = getRuntimeStorageDayKey(evaluation.timestamp);
688
+ if (shouldStoreDetailedRuntimeSignalEvaluation(evaluation)) {
689
+ await (0, import_redis.setHashJsonField)(
690
+ import_redis.redisKeys.runtimeSignalEvaluationBucket(
691
+ evaluation.userName,
692
+ dayKey,
693
+ evaluation.strategy
694
+ ),
695
+ evaluation.evaluationId,
696
+ evaluation,
697
+ {
698
+ expire: import_constants2.TTL_10D
699
+ }
700
+ );
701
+ }
702
+ await (0, import_redis.incrHashFields)(
703
+ import_redis.redisKeys.runtimeSignalEvaluationStatsBucket(
652
704
  evaluation.userName,
653
- evaluation.evaluationId
705
+ dayKey,
706
+ evaluation.strategy
654
707
  ),
655
- evaluation,
708
+ buildRuntimeSignalStatsIncrements(evaluation),
656
709
  {
657
- expire: resolveRuntimeSignalEvaluationTtl(evaluation)
710
+ expire: import_constants2.TTL_10D
658
711
  }
659
712
  );
660
713
  };
@@ -711,6 +764,8 @@ var findSignals = async (symbol, connector, btcBinanceData, btcCoinbaseData, run
711
764
  interval
712
765
  })
713
766
  ]);
767
+ cachedData.pop();
768
+ btcCachedData.pop();
714
769
  const lastCandle = cachedData.pop();
715
770
  const btcLastCandle = btcCachedData.pop();
716
771
  if (!lastCandle || !btcLastCandle) {
@@ -772,17 +827,19 @@ var findSignals = async (symbol, connector, btcBinanceData, btcCoinbaseData, run
772
827
  );
773
828
  }
774
829
  strategySignals.push(signal);
775
- await (0, import_redis.setData)(import_redis.redisKeys.signal(symbol, signal.signalId), signal, {
776
- expire: import_constants2.TTL_1D
777
- });
778
830
  await (0, import_redis.setData)(import_redis.redisKeys.storeSignal(symbol, signal.signalId), signal, {
779
- expire: import_constants2.TTL_3M
831
+ expire: import_constants2.TTL_10D
780
832
  });
781
- await (0, import_redis.setData)(
782
- import_redis.redisKeys.runtimeSignal(flags.user, signal.signalId),
783
- signal,
833
+ await (0, import_redis.setHashJsonField)(
834
+ import_redis.redisKeys.runtimeSignalBucket(
835
+ flags.user,
836
+ getRuntimeStorageDayKey(signal.timestamp),
837
+ signal.strategy
838
+ ),
839
+ signal.signalId,
840
+ toRuntimeSignalBucketRef(signal),
784
841
  {
785
- expire: import_constants2.TTL_1M
842
+ expire: import_constants2.TTL_10D
786
843
  }
787
844
  );
788
845
  await saveRuntimeSignalEvaluation({
@@ -1008,11 +1065,12 @@ var signals = async () => {
1008
1065
  symbol: import_chalk2.default.gray(symbol)
1009
1066
  });
1010
1067
  });
1011
- if (!flags.skipScreenshots) {
1012
- await (0, import_cli.makeScreenshots)(signals2, "15", flags.user);
1013
- }
1014
1068
  if (flags.notify) {
1015
- await (0, import_cli.sendToTG)(signals2, "15", flags.user);
1069
+ const telegramSignals = getTelegramDeliverableSignals(signals2);
1070
+ if (!flags.skipScreenshots && telegramSignals.length > 0) {
1071
+ await (0, import_cli.makeScreenshots)(telegramSignals, "15", flags.user);
1072
+ }
1073
+ await (0, import_cli.sendToTG)(telegramSignals, "15", flags.user);
1016
1074
  }
1017
1075
  import_logger.logger.info(
1018
1076
  JSON.stringify(
@@ -37,7 +37,7 @@ var import_config = require("dotenv/config");
37
37
  var import_args = __toESM(require("args"));
38
38
  var import_constants = require("@tradejs/core/constants");
39
39
  var import_logger = require("@tradejs/infra/logger");
40
- var import_redis = require("@tradejs/infra/redis");
40
+ var import_redis2 = require("@tradejs/infra/redis");
41
41
  var import_connectors = require("@tradejs/node/connectors");
42
42
 
43
43
  // src/lib/telegramReports.ts
@@ -100,6 +100,124 @@ ${chunk}`, {
100
100
  return chunks.length;
101
101
  };
102
102
 
103
+ // src/lib/runtimeSignalsLoader.ts
104
+ var import_redis = require("@tradejs/infra/redis");
105
+
106
+ // src/lib/runtimeSignalsStorage.ts
107
+ var DAY_MS = 864e5;
108
+ var RUNTIME_STORAGE_DAY_OFFSET_MS = 5 * 60 * 60 * 1e3;
109
+ var toIsoDayKey = (timestamp) => new Date(timestamp).toISOString().slice(0, 10);
110
+ var toRuntimeStorageDayTimestamp = (timestamp) => Math.floor((timestamp + RUNTIME_STORAGE_DAY_OFFSET_MS) / DAY_MS) * DAY_MS;
111
+ var getRuntimeStorageDayKeys = (startTime, endTime) => {
112
+ if (!Number.isFinite(startTime) || !Number.isFinite(endTime)) {
113
+ return [];
114
+ }
115
+ const start = Math.min(startTime, endTime);
116
+ const endExclusive = Math.max(startTime, endTime);
117
+ const normalizedEnd = Math.max(start, endExclusive - 1);
118
+ const keys = [];
119
+ const startDay = toRuntimeStorageDayTimestamp(start);
120
+ const endDay = toRuntimeStorageDayTimestamp(normalizedEnd);
121
+ for (let current = startDay; current <= endDay; current += DAY_MS) {
122
+ keys.push(toIsoDayKey(current));
123
+ }
124
+ return keys;
125
+ };
126
+ var isRuntimeSignalBucketRef = (value) => {
127
+ if (!value || typeof value !== "object") {
128
+ return false;
129
+ }
130
+ const record = value;
131
+ return typeof record.signalId === "string" && typeof record.symbol === "string" && typeof record.strategy === "string" && typeof record.timestamp === "number";
132
+ };
133
+ var toCount = (value) => {
134
+ const parsed = Number.parseInt(String(value ?? "0"), 10);
135
+ return Number.isFinite(parsed) ? parsed : 0;
136
+ };
137
+ var parseRuntimeSignalStatsBucket = (value) => {
138
+ const reasonGroups = /* @__PURE__ */ new Map();
139
+ for (const [field, rawCount] of Object.entries(value)) {
140
+ if (!field.startsWith("reason:")) {
141
+ continue;
142
+ }
143
+ const [, source, ...reasonParts] = field.split(":");
144
+ const reason = reasonParts.join(":");
145
+ if (!source || !reason) {
146
+ continue;
147
+ }
148
+ const reasons = reasonGroups.get(source) ?? /* @__PURE__ */ new Map();
149
+ reasons.set(reason, toCount(rawCount));
150
+ reasonGroups.set(source, reasons);
151
+ }
152
+ return {
153
+ evaluated: toCount(value.evaluated),
154
+ signals: toCount(value.signals),
155
+ reasonGroups
156
+ };
157
+ };
158
+
159
+ // src/lib/runtimeSignalsLoader.ts
160
+ var isSignalRecord = (value) => {
161
+ if (!value || typeof value !== "object") {
162
+ return false;
163
+ }
164
+ const record = value;
165
+ return typeof record.signalId === "string" && typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.timestamp === "number";
166
+ };
167
+ var sortByTimestamp = (records) => [...records].sort((left, right) => left.timestamp - right.timestamp);
168
+ var loadRuntimeSignals = async (userName) => {
169
+ const bucketKeys = await (0, import_redis.getKeys)(import_redis.redisKeys.runtimeSignalBuckets(userName));
170
+ const refs = (await Promise.all(bucketKeys.map((key) => (0, import_redis.getHashJsonValues)(key)))).flat().filter(isRuntimeSignalBucketRef);
171
+ const seen = /* @__PURE__ */ new Set();
172
+ const payloads = await Promise.all(
173
+ refs.map(async (ref) => {
174
+ const dedupeKey = `${ref.symbol}:${ref.signalId}`;
175
+ if (seen.has(dedupeKey)) {
176
+ return null;
177
+ }
178
+ seen.add(dedupeKey);
179
+ const signal = await (0, import_redis.getData)(
180
+ import_redis.redisKeys.storeSignal(ref.symbol, ref.signalId),
181
+ null
182
+ );
183
+ return isSignalRecord(signal) ? signal : null;
184
+ })
185
+ );
186
+ return sortByTimestamp(payloads.filter(isSignalRecord));
187
+ };
188
+ var parseBucketKey = (prefix, key) => {
189
+ if (!key.startsWith(prefix)) {
190
+ return null;
191
+ }
192
+ const suffix = key.slice(prefix.length);
193
+ const firstColon = suffix.indexOf(":");
194
+ if (firstColon <= 0 || firstColon >= suffix.length - 1) {
195
+ return null;
196
+ }
197
+ return {
198
+ dayKey: suffix.slice(0, firstColon),
199
+ strategy: suffix.slice(firstColon + 1)
200
+ };
201
+ };
202
+ var loadRuntimeSignalEvaluationStatsBuckets = async (userName) => {
203
+ const prefix = import_redis.redisKeys.runtimeSignalEvaluationStatsBuckets(userName);
204
+ const keys = await (0, import_redis.getKeys)(prefix);
205
+ const entries = await Promise.all(
206
+ keys.map(async (key) => {
207
+ const parsedKey = parseBucketKey(prefix, key);
208
+ if (!parsedKey) {
209
+ return null;
210
+ }
211
+ return {
212
+ key,
213
+ ...parsedKey,
214
+ stats: parseRuntimeSignalStatsBucket(await (0, import_redis.getHashData)(key))
215
+ };
216
+ })
217
+ );
218
+ return entries.filter(Boolean);
219
+ };
220
+
103
221
  // src/scripts/signalsSummary.ts
104
222
  import_args.default.option(["u", "user"], "Use user config", "root");
105
223
  import_args.default.option(
@@ -139,51 +257,6 @@ var normalizeStatus = (value) => {
139
257
  }
140
258
  return "unknown";
141
259
  };
142
- var normalizeSkipStatsReason = (reason, fallbackSource) => {
143
- let source = fallbackSource;
144
- let normalizedReason = reason;
145
- if (reason.startsWith("AI_QUALITY_BELOW_MIN")) {
146
- source = "AI";
147
- normalizedReason = "MIN_AI_QUALITY";
148
- } else if (reason === "AI_QUALITY_UNAVAILABLE") {
149
- source = "AI";
150
- normalizedReason = "QUALITY_UNAVAILABLE";
151
- } else if (reason === "ML_RESULT_UNAVAILABLE") {
152
- source = "ML";
153
- normalizedReason = "RESULT_UNAVAILABLE";
154
- } else if (reason.startsWith("ML_THRESHOLD_NOT_MET")) {
155
- source = "ML";
156
- normalizedReason = "ML_THRESHOLD";
157
- } else if (reason.startsWith("HOOK_BEFORE_ENTRY_GATE:")) {
158
- source = "hook";
159
- normalizedReason = `BEFORE_ENTRY_GATE:${reason.slice(
160
- "HOOK_BEFORE_ENTRY_GATE:".length
161
- )}`;
162
- } else if (reason === "HOOK_BEFORE_ENTRY_GATE") {
163
- source = "hook";
164
- normalizedReason = "BEFORE_ENTRY_GATE";
165
- } else if (reason === "MAKE_ORDERS_DISABLED" || reason === "ENTRY_POLICY_BLOCKED") {
166
- source = "policy";
167
- }
168
- return {
169
- source: `skip from ${source}`,
170
- reason: normalizedReason
171
- };
172
- };
173
- var isSignalRecord = (value) => {
174
- if (!value || typeof value !== "object") {
175
- return false;
176
- }
177
- const record = value;
178
- return typeof record.signalId === "string" && typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.timestamp === "number";
179
- };
180
- var isRuntimeSignalEvaluationRecord = (value) => {
181
- if (!value || typeof value !== "object") {
182
- return false;
183
- }
184
- const record = value;
185
- return typeof record.evaluationId === "string" && typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.timestamp === "number" && (record.status === "signal" || record.status === "skip" || record.status === "error");
186
- };
187
260
  var isRuntimeTradeRecord = (value) => {
188
261
  if (!value || typeof value !== "object") {
189
262
  return false;
@@ -203,16 +276,6 @@ var resolveSummaryConnectorName = async (value) => {
203
276
  );
204
277
  return import_connectors.DEFAULT_CONNECTOR_NAME;
205
278
  };
206
- var loadRuntimeSignals = async (userName) => {
207
- const keys = await (0, import_redis.getKeys)(import_redis.redisKeys.runtimeSignals(userName));
208
- const signals = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
209
- return signals.filter(isSignalRecord).sort((left, right) => left.timestamp - right.timestamp);
210
- };
211
- var loadRuntimeSignalEvaluations = async (userName) => {
212
- const keys = await (0, import_redis.getKeys)(import_redis.redisKeys.runtimeSignalEvaluations(userName));
213
- const evaluations = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
214
- return evaluations.filter(isRuntimeSignalEvaluationRecord).sort((left, right) => left.timestamp - right.timestamp);
215
- };
216
279
  var resolveStrategyNameByConfigKey = (userName, key) => {
217
280
  const parts = key.split(":");
218
281
  if (parts.length !== 5) {
@@ -225,14 +288,30 @@ var resolveStrategyNameByConfigKey = (userName, key) => {
225
288
  return strategyName;
226
289
  };
227
290
  var loadRuntimeStrategyNames = async (userName) => {
228
- const keys = await (0, import_redis.getKeys)(`${import_redis.redisKeys.strategies(userName)}:`);
291
+ const keys = await (0, import_redis2.getKeys)(`${import_redis2.redisKeys.strategies(userName)}:`);
229
292
  return keys.filter((key) => key.endsWith(":config")).map((key) => resolveStrategyNameByConfigKey(userName, key)).filter((value) => Boolean(value)).sort((left, right) => left.localeCompare(right));
230
293
  };
231
294
  var loadRuntimeTrades = async (userName) => {
232
- const keys = await (0, import_redis.getKeys)(import_redis.redisKeys.runtimeTrades(userName));
233
- const trades = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
295
+ const keys = await (0, import_redis2.getKeys)(import_redis2.redisKeys.runtimeTrades(userName));
296
+ const trades = await Promise.all(keys.map((key) => (0, import_redis2.getData)(key, null)));
234
297
  return trades.filter(isRuntimeTradeRecord).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
235
298
  };
299
+ var createRuntimeSignalStats = () => ({
300
+ evaluated: 0,
301
+ signals: 0,
302
+ reasonGroups: /* @__PURE__ */ new Map()
303
+ });
304
+ var mergeRuntimeSignalStats = (target, source) => {
305
+ target.evaluated += source.evaluated;
306
+ target.signals += source.signals;
307
+ for (const [group, reasons] of source.reasonGroups.entries()) {
308
+ const targetReasons = target.reasonGroups.get(group) ?? /* @__PURE__ */ new Map();
309
+ for (const [reason, count] of reasons.entries()) {
310
+ targetReasons.set(reason, (targetReasons.get(reason) ?? 0) + count);
311
+ }
312
+ target.reasonGroups.set(group, targetReasons);
313
+ }
314
+ };
236
315
  var loadClosedPnlRows = async ({
237
316
  connector,
238
317
  startTime,
@@ -286,8 +365,8 @@ var syncRuntimeTrades = async ({
286
365
  const symbols = [...new Set(trades.map((trade) => trade.symbol))];
287
366
  await Promise.all(
288
367
  symbols.map(async (symbol) => {
289
- const activeRef = await (0, import_redis.getData)(
290
- import_redis.redisKeys.runtimeActiveTrade(userName, symbol),
368
+ const activeRef = await (0, import_redis2.getData)(
369
+ import_redis2.redisKeys.runtimeActiveTrade(userName, symbol),
291
370
  null
292
371
  );
293
372
  activeOrderIdBySymbol.set(
@@ -324,8 +403,8 @@ var syncRuntimeTrades = async ({
324
403
  currentPnl: openPosition.unrealizedPnl,
325
404
  lastSyncedAt: endTime
326
405
  };
327
- await (0, import_redis.setData)(
328
- import_redis.redisKeys.runtimeTrade(userName, trade.orderId),
406
+ await (0, import_redis2.setData)(
407
+ import_redis2.redisKeys.runtimeTrade(userName, trade.orderId),
329
408
  nextTrade2,
330
409
  {
331
410
  expire: 0
@@ -346,10 +425,10 @@ var syncRuntimeTrades = async ({
346
425
  lastSyncedAt: endTime
347
426
  };
348
427
  await Promise.all([
349
- (0, import_redis.setData)(import_redis.redisKeys.runtimeTrade(userName, trade.orderId), nextTrade, {
428
+ (0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, trade.orderId), nextTrade, {
350
429
  expire: import_constants.TTL_1M
351
430
  }),
352
- ...isCurrentActiveTrade ? [(0, import_redis.delKey)(import_redis.redisKeys.runtimeActiveTrade(userName, trade.symbol))] : []
431
+ ...isCurrentActiveTrade ? [(0, import_redis2.delKey)(import_redis2.redisKeys.runtimeActiveTrade(userName, trade.symbol))] : []
353
432
  ]);
354
433
  syncedTrades.push(nextTrade);
355
434
  }
@@ -361,7 +440,7 @@ var buildSummaryMessage = ({
361
440
  endTime,
362
441
  configuredStrategyNames,
363
442
  signals,
364
- signalEvaluations,
443
+ evaluationStatsByStrategy,
365
444
  trades
366
445
  }) => {
367
446
  const lines = [];
@@ -374,7 +453,6 @@ var buildSummaryMessage = ({
374
453
  ];
375
454
  const strategyNames = /* @__PURE__ */ new Set();
376
455
  const signalStats = /* @__PURE__ */ new Map();
377
- const evaluationStats = /* @__PURE__ */ new Map();
378
456
  const tradeStats = /* @__PURE__ */ new Map();
379
457
  for (const strategyName of configuredStrategyNames) {
380
458
  if (strategyName) {
@@ -388,38 +466,6 @@ var buildSummaryMessage = ({
388
466
  stats.set(status, (stats.get(status) ?? 0) + 1);
389
467
  signalStats.set(signal.strategy, stats);
390
468
  }
391
- for (const evaluation of signalEvaluations) {
392
- strategyNames.add(evaluation.strategy);
393
- const stats = evaluationStats.get(evaluation.strategy) ?? {
394
- evaluated: 0,
395
- signals: 0,
396
- reasonGroups: /* @__PURE__ */ new Map()
397
- };
398
- stats.evaluated += 1;
399
- if (evaluation.status === "signal") {
400
- stats.signals += 1;
401
- }
402
- const orderStatus = normalizeStatus(evaluation.orderStatus);
403
- let skipReason = null;
404
- let fallbackSource = "core";
405
- if (evaluation.status === "skip") {
406
- skipReason = evaluation.reason || "NO_SIGNAL";
407
- fallbackSource = "core";
408
- } else if (evaluation.status === "signal" && (orderStatus === "skipped" || orderStatus === "canceled")) {
409
- skipReason = evaluation.orderSkipReason || evaluation.reason || `orderStatus=${orderStatus}`;
410
- fallbackSource = "runtime";
411
- } else if (evaluation.status === "error") {
412
- skipReason = evaluation.reason || "EVALUATION_ERROR";
413
- fallbackSource = "runtime";
414
- }
415
- if (skipReason) {
416
- const normalized = normalizeSkipStatsReason(skipReason, fallbackSource);
417
- const reasons = stats.reasonGroups.get(normalized.source) ?? /* @__PURE__ */ new Map();
418
- reasons.set(normalized.reason, (reasons.get(normalized.reason) ?? 0) + 1);
419
- stats.reasonGroups.set(normalized.source, reasons);
420
- }
421
- evaluationStats.set(evaluation.strategy, stats);
422
- }
423
469
  for (const trade of trades) {
424
470
  strategyNames.add(trade.strategy);
425
471
  const stats = tradeStats.get(trade.strategy) ?? {
@@ -494,7 +540,7 @@ var buildSummaryMessage = ({
494
540
  return lines.join("\n");
495
541
  }
496
542
  const appendEvaluationDetails = (strategyName) => {
497
- const evaluation = evaluationStats.get(strategyName);
543
+ const evaluation = evaluationStatsByStrategy.get(strategyName);
498
544
  if (!evaluation) {
499
545
  return;
500
546
  }
@@ -524,7 +570,7 @@ var buildSummaryMessage = ({
524
570
  for (const strategyName of sortedStrategies) {
525
571
  const stats = signalStats.get(strategyName);
526
572
  if (!stats || stats.size === 0) {
527
- const evaluation = evaluationStats.get(strategyName);
573
+ const evaluation = evaluationStatsByStrategy.get(strategyName);
528
574
  lines.push("");
529
575
  lines.push(
530
576
  evaluation?.evaluated ? `<b>${escapeHtml(strategyName)}</b>
@@ -593,10 +639,10 @@ var signalsSummary = async () => {
593
639
  const connector = await connectorFactory({
594
640
  userName: flags.user
595
641
  });
596
- const [configuredStrategyNames, signals, signalEvaluations, trades] = await Promise.all([
642
+ const [configuredStrategyNames, signals, evaluationStatsBuckets, trades] = await Promise.all([
597
643
  loadRuntimeStrategyNames(flags.user),
598
644
  loadRuntimeSignals(flags.user),
599
- loadRuntimeSignalEvaluations(flags.user),
645
+ loadRuntimeSignalEvaluationStatsBuckets(flags.user),
600
646
  loadRuntimeTrades(flags.user)
601
647
  ]);
602
648
  const syncedTrades = await syncRuntimeTrades({
@@ -607,16 +653,23 @@ var signalsSummary = async () => {
607
653
  endTime
608
654
  });
609
655
  const windowSignals = signals.filter(
610
- (signal) => signal.timestamp >= startTime && signal.timestamp <= endTime
611
- );
612
- const windowSignalEvaluations = signalEvaluations.filter(
613
- (evaluation) => evaluation.timestamp >= startTime && evaluation.timestamp <= endTime
656
+ (signal) => signal.timestamp >= startTime && signal.timestamp < endTime
614
657
  );
658
+ const windowDayKeys = new Set(getRuntimeStorageDayKeys(startTime, endTime));
659
+ const windowEvaluationStats = /* @__PURE__ */ new Map();
660
+ for (const entry of evaluationStatsBuckets) {
661
+ if (!windowDayKeys.has(entry.dayKey)) {
662
+ continue;
663
+ }
664
+ const stats = windowEvaluationStats.get(entry.strategy) ?? createRuntimeSignalStats();
665
+ mergeRuntimeSignalStats(stats, entry.stats);
666
+ windowEvaluationStats.set(entry.strategy, stats);
667
+ }
615
668
  const signalIds = new Set(
616
669
  signals.map((signal) => signal.signalId).filter(Boolean)
617
670
  );
618
671
  const windowTrades = syncedTrades.filter(
619
- (trade) => trade.entryTimestamp >= startTime && trade.entryTimestamp <= endTime && trade.signalId != null && signalIds.has(trade.signalId)
672
+ (trade) => trade.entryTimestamp >= startTime && trade.entryTimestamp < endTime && trade.signalId != null && signalIds.has(trade.signalId)
620
673
  );
621
674
  const message = buildSummaryMessage({
622
675
  hours,
@@ -624,14 +677,18 @@ var signalsSummary = async () => {
624
677
  endTime,
625
678
  configuredStrategyNames,
626
679
  signals: windowSignals,
627
- signalEvaluations: windowSignalEvaluations,
680
+ evaluationStatsByStrategy: windowEvaluationStats,
628
681
  trades: windowTrades
629
682
  });
683
+ const windowEvaluationsCount = [...windowEvaluationStats.values()].reduce(
684
+ (sum, stats) => sum + stats.evaluated,
685
+ 0
686
+ );
630
687
  import_logger.logger.info(
631
688
  "signals summary window=%sh signals=%s evaluations=%s trades=%s connector=%s user=%s",
632
689
  hours,
633
690
  windowSignals.length,
634
- windowSignalEvaluations.length,
691
+ windowEvaluationsCount,
635
692
  windowTrades.length,
636
693
  connectorName,
637
694
  flags.user
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Official CLI for the TradeJS open-source framework: infra setup, backtests, signals, bots, and ML workflows.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -29,14 +29,14 @@
29
29
  "tradejs": "dist/cli.js"
30
30
  },
31
31
  "dependencies": {
32
- "@tradejs/base": "^1.0.8",
33
- "@tradejs/connectors": "^1.0.8",
34
- "@tradejs/core": "^1.0.8",
35
- "@tradejs/indicators": "^1.0.8",
36
- "@tradejs/infra": "^1.0.8",
37
- "@tradejs/node": "^1.0.8",
38
- "@tradejs/strategies": "^1.0.8",
39
- "@tradejs/types": "^1.0.8",
32
+ "@tradejs/base": "^1.0.9",
33
+ "@tradejs/connectors": "^1.0.9",
34
+ "@tradejs/core": "^1.0.9",
35
+ "@tradejs/indicators": "^1.0.9",
36
+ "@tradejs/infra": "^1.0.9",
37
+ "@tradejs/node": "^1.0.9",
38
+ "@tradejs/strategies": "^1.0.9",
39
+ "@tradejs/types": "^1.0.9",
40
40
  "args": "^5.0.3",
41
41
  "bcryptjs": "^2.4.3",
42
42
  "chalk": "4.1.2",