@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.
- package/dist/cli.js +1405 -1258
- package/dist/lib/runtimeSignalsLoader.js +153 -0
- package/dist/lib/runtimeSignalsStorage.js +162 -0
- package/dist/scripts/agentRun.js +14 -0
- package/dist/scripts/researchAuto.js +3 -1
- package/dist/scripts/runtimeParity.js +69 -32
- package/dist/scripts/signals.js +117 -59
- package/dist/scripts/signalsSummary.js +167 -110
- package/package.json +9 -9
package/dist/scripts/signals.js
CHANGED
|
@@ -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
|
|
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
|
|
637
|
-
|
|
638
|
-
|
|
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
|
-
|
|
651
|
-
|
|
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
|
-
|
|
705
|
+
dayKey,
|
|
706
|
+
evaluation.strategy
|
|
654
707
|
),
|
|
655
|
-
evaluation,
|
|
708
|
+
buildRuntimeSignalStatsIncrements(evaluation),
|
|
656
709
|
{
|
|
657
|
-
expire:
|
|
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.
|
|
831
|
+
expire: import_constants2.TTL_10D
|
|
780
832
|
});
|
|
781
|
-
await (0, import_redis.
|
|
782
|
-
import_redis.redisKeys.
|
|
783
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
233
|
-
const trades = await Promise.all(keys.map((key) => (0,
|
|
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,
|
|
290
|
-
|
|
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,
|
|
328
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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,
|
|
642
|
+
const [configuredStrategyNames, signals, evaluationStatsBuckets, trades] = await Promise.all([
|
|
597
643
|
loadRuntimeStrategyNames(flags.user),
|
|
598
644
|
loadRuntimeSignals(flags.user),
|
|
599
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
33
|
-
"@tradejs/connectors": "^1.0.
|
|
34
|
-
"@tradejs/core": "^1.0.
|
|
35
|
-
"@tradejs/indicators": "^1.0.
|
|
36
|
-
"@tradejs/infra": "^1.0.
|
|
37
|
-
"@tradejs/node": "^1.0.
|
|
38
|
-
"@tradejs/strategies": "^1.0.
|
|
39
|
-
"@tradejs/types": "^1.0.
|
|
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",
|