@tradejs/cli 1.0.6 → 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 +10049 -4860
- package/dist/lib/aiTrainMetrics.js +23 -0
- package/dist/lib/candlesProviderMigration.js +266 -0
- package/dist/lib/cliArgs.js +40 -0
- package/dist/lib/derivativesContextBackfill.js +487 -0
- package/dist/lib/researchAgent.js +195 -0
- package/dist/lib/runtimeParity.js +234 -0
- package/dist/lib/runtimeSignalsLoader.js +153 -0
- package/dist/lib/runtimeSignalsStorage.js +162 -0
- package/dist/lib/telegramReports.js +91 -0
- package/dist/lib/timeWindow.js +101 -0
- package/dist/scripts/agentRun.js +909 -0
- package/dist/scripts/aiTrain.js +113 -31
- package/dist/scripts/backtest.js +794 -93
- package/dist/scripts/candlesMigrateProvider.js +5302 -0
- package/dist/scripts/continuity.js +1 -1
- package/dist/scripts/migration.js +1 -1
- package/dist/scripts/researchAuto.js +847 -0
- package/dist/scripts/results.js +46 -22
- package/dist/scripts/runtimeParity.js +1688 -0
- package/dist/scripts/signals.js +774 -63
- package/dist/scripts/signalsSummary.js +712 -0
- package/dist/scripts/user-add.js +0 -9
- package/package.json +16 -12
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/lib/runtimeParity.ts
|
|
21
|
+
var runtimeParity_exports = {};
|
|
22
|
+
__export(runtimeParity_exports, {
|
|
23
|
+
compareTradeParityEntries: () => compareTradeParityEntries,
|
|
24
|
+
dedupeRuntimeParityEntries: () => dedupeRuntimeParityEntries,
|
|
25
|
+
extractBacktestEntryParityEntries: () => extractBacktestEntryParityEntries,
|
|
26
|
+
extractRuntimeParityEntries: () => extractRuntimeParityEntries,
|
|
27
|
+
findRuntimeDuplicateGroups: () => findRuntimeDuplicateGroups,
|
|
28
|
+
summarizeMatchedParity: () => summarizeMatchedParity
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(runtimeParity_exports);
|
|
31
|
+
var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
32
|
+
var toGroupKey = (entry) => `${entry.strategy}::${entry.symbol}::${entry.direction}`;
|
|
33
|
+
var toRuntimeDuplicateKey = (entry) => `${toGroupKey(entry)}::${entry.timestamp}`;
|
|
34
|
+
var sortTradeParityEntries = (left, right) => left.timestamp - right.timestamp || left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || left.direction.localeCompare(right.direction) || left.id.localeCompare(right.id);
|
|
35
|
+
var buildPriceDeltaPct = (leftPrice, rightPrice) => {
|
|
36
|
+
if (leftPrice == null || rightPrice == null || !Number.isFinite(leftPrice) || !Number.isFinite(rightPrice) || leftPrice === 0) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return Math.abs((rightPrice - leftPrice) / leftPrice * 100);
|
|
40
|
+
};
|
|
41
|
+
var extractBacktestEntryParityEntries = (orderLog) => {
|
|
42
|
+
if (!Array.isArray(orderLog)) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
return orderLog.filter(
|
|
46
|
+
(item) => (item.type === "OPEN_LONG" || item.type === "OPEN_SHORT") && item.signal && typeof item.signal.strategy === "string" && typeof item.signal.symbol === "string" && (item.signal.direction === "LONG" || item.signal.direction === "SHORT")
|
|
47
|
+
).map((item, index) => {
|
|
48
|
+
const signal = item.signal;
|
|
49
|
+
const timestamp = typeof signal.timestamp === "number" && Number.isFinite(signal.timestamp) ? signal.timestamp : item.timestamp;
|
|
50
|
+
return {
|
|
51
|
+
id: typeof signal.signalId === "string" && signal.signalId.trim() ? signal.signalId : `backtest-${index}-${timestamp}`,
|
|
52
|
+
source: "backtest",
|
|
53
|
+
strategy: signal.strategy,
|
|
54
|
+
symbol: signal.symbol,
|
|
55
|
+
direction: signal.direction,
|
|
56
|
+
timestamp,
|
|
57
|
+
price: toFiniteNumberOrNull(item.price),
|
|
58
|
+
signalId: typeof signal.signalId === "string" ? signal.signalId : void 0
|
|
59
|
+
};
|
|
60
|
+
}).sort((left, right) => left.timestamp - right.timestamp);
|
|
61
|
+
};
|
|
62
|
+
var extractRuntimeParityEntries = (trades) => trades.filter(
|
|
63
|
+
(trade) => typeof trade.strategy === "string" && typeof trade.symbol === "string" && (trade.direction === "LONG" || trade.direction === "SHORT") && typeof trade.entryTimestamp === "number" && Number.isFinite(trade.entryTimestamp)
|
|
64
|
+
).map((trade) => ({
|
|
65
|
+
id: trade.orderId,
|
|
66
|
+
source: "runtime",
|
|
67
|
+
strategy: trade.strategy,
|
|
68
|
+
symbol: trade.symbol,
|
|
69
|
+
direction: trade.direction,
|
|
70
|
+
timestamp: trade.entryTimestamp,
|
|
71
|
+
price: toFiniteNumberOrNull(trade.entryPrice),
|
|
72
|
+
orderId: trade.orderId,
|
|
73
|
+
signalId: typeof trade.signalId === "string" ? trade.signalId : void 0
|
|
74
|
+
})).sort((left, right) => left.timestamp - right.timestamp);
|
|
75
|
+
var findRuntimeDuplicateGroups = (entries) => {
|
|
76
|
+
const groups = /* @__PURE__ */ new Map();
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
if (entry.source !== "runtime") {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const key = toRuntimeDuplicateKey(entry);
|
|
82
|
+
const bucket = groups.get(key) ?? [];
|
|
83
|
+
bucket.push(entry);
|
|
84
|
+
groups.set(key, bucket);
|
|
85
|
+
}
|
|
86
|
+
return [...groups.entries()].filter(([, group]) => group.length > 1).map(([key, group]) => {
|
|
87
|
+
const sortedGroup = [...group].sort(sortTradeParityEntries);
|
|
88
|
+
const first = sortedGroup[0];
|
|
89
|
+
return {
|
|
90
|
+
key,
|
|
91
|
+
strategy: first.strategy,
|
|
92
|
+
symbol: first.symbol,
|
|
93
|
+
direction: first.direction,
|
|
94
|
+
timestamp: first.timestamp,
|
|
95
|
+
entries: sortedGroup
|
|
96
|
+
};
|
|
97
|
+
}).sort(
|
|
98
|
+
(left, right) => left.timestamp - right.timestamp || left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || left.direction.localeCompare(right.direction)
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
var dedupeRuntimeParityEntries = (entries) => {
|
|
102
|
+
const duplicateGroups = findRuntimeDuplicateGroups(entries);
|
|
103
|
+
const duplicateEntriesSet = /* @__PURE__ */ new Set();
|
|
104
|
+
for (const group of duplicateGroups) {
|
|
105
|
+
for (const duplicate of group.entries.slice(1)) {
|
|
106
|
+
duplicateEntriesSet.add(duplicate);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const dedupedEntries = entries.filter((entry) => !duplicateEntriesSet.has(entry)).sort(sortTradeParityEntries);
|
|
110
|
+
const duplicateEntries = duplicateGroups.flatMap((group) => group.entries.slice(1)).sort(sortTradeParityEntries);
|
|
111
|
+
return {
|
|
112
|
+
entries: dedupedEntries,
|
|
113
|
+
duplicateGroups,
|
|
114
|
+
duplicateEntries
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
var compareTradeParityEntries = ({
|
|
118
|
+
runtimeEntries,
|
|
119
|
+
backtestEntries,
|
|
120
|
+
toleranceMs
|
|
121
|
+
}) => {
|
|
122
|
+
const matched = [];
|
|
123
|
+
const runtimeOnly = [];
|
|
124
|
+
const backtestOnly = [];
|
|
125
|
+
const groupedRuntime = /* @__PURE__ */ new Map();
|
|
126
|
+
const groupedBacktest = /* @__PURE__ */ new Map();
|
|
127
|
+
for (const entry of runtimeEntries) {
|
|
128
|
+
const key = toGroupKey(entry);
|
|
129
|
+
const bucket = groupedRuntime.get(key) ?? [];
|
|
130
|
+
bucket.push(entry);
|
|
131
|
+
groupedRuntime.set(key, bucket);
|
|
132
|
+
}
|
|
133
|
+
for (const entry of backtestEntries) {
|
|
134
|
+
const key = toGroupKey(entry);
|
|
135
|
+
const bucket = groupedBacktest.get(key) ?? [];
|
|
136
|
+
bucket.push(entry);
|
|
137
|
+
groupedBacktest.set(key, bucket);
|
|
138
|
+
}
|
|
139
|
+
const groupKeys = /* @__PURE__ */ new Set([
|
|
140
|
+
...groupedRuntime.keys(),
|
|
141
|
+
...groupedBacktest.keys()
|
|
142
|
+
]);
|
|
143
|
+
for (const key of groupKeys) {
|
|
144
|
+
const runtimeGroup = [...groupedRuntime.get(key) ?? []].sort(
|
|
145
|
+
(left, right) => left.timestamp - right.timestamp
|
|
146
|
+
);
|
|
147
|
+
const backtestGroup = [...groupedBacktest.get(key) ?? []].sort(
|
|
148
|
+
(left, right) => left.timestamp - right.timestamp
|
|
149
|
+
);
|
|
150
|
+
const unmatchedBacktest = backtestGroup.map((entry) => ({
|
|
151
|
+
entry,
|
|
152
|
+
used: false
|
|
153
|
+
}));
|
|
154
|
+
for (const runtimeEntry of runtimeGroup) {
|
|
155
|
+
let bestIndex = -1;
|
|
156
|
+
let bestDiff = Number.POSITIVE_INFINITY;
|
|
157
|
+
for (let index = 0; index < unmatchedBacktest.length; index += 1) {
|
|
158
|
+
const candidate = unmatchedBacktest[index];
|
|
159
|
+
if (candidate.used) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const diff = Math.abs(
|
|
163
|
+
candidate.entry.timestamp - runtimeEntry.timestamp
|
|
164
|
+
);
|
|
165
|
+
if (diff > toleranceMs || diff >= bestDiff) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
bestIndex = index;
|
|
169
|
+
bestDiff = diff;
|
|
170
|
+
}
|
|
171
|
+
if (bestIndex < 0) {
|
|
172
|
+
runtimeOnly.push(runtimeEntry);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
unmatchedBacktest[bestIndex].used = true;
|
|
176
|
+
const backtestEntry = unmatchedBacktest[bestIndex].entry;
|
|
177
|
+
matched.push({
|
|
178
|
+
runtime: runtimeEntry,
|
|
179
|
+
backtest: backtestEntry,
|
|
180
|
+
timestampDiffMs: bestDiff,
|
|
181
|
+
priceDeltaPct: buildPriceDeltaPct(
|
|
182
|
+
runtimeEntry.price,
|
|
183
|
+
backtestEntry.price
|
|
184
|
+
)
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
for (const candidate of unmatchedBacktest) {
|
|
188
|
+
if (!candidate.used) {
|
|
189
|
+
backtestOnly.push(candidate.entry);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
matched.sort(
|
|
194
|
+
(left, right) => left.runtime.timestamp - right.runtime.timestamp
|
|
195
|
+
);
|
|
196
|
+
runtimeOnly.sort((left, right) => left.timestamp - right.timestamp);
|
|
197
|
+
backtestOnly.sort((left, right) => left.timestamp - right.timestamp);
|
|
198
|
+
return {
|
|
199
|
+
matched,
|
|
200
|
+
runtimeOnly,
|
|
201
|
+
backtestOnly
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
var summarizeMatchedParity = (matched) => {
|
|
205
|
+
if (!matched.length) {
|
|
206
|
+
return {
|
|
207
|
+
avgPriceDeltaPct: null,
|
|
208
|
+
maxPriceDeltaPct: null,
|
|
209
|
+
avgTimestampDiffMs: null,
|
|
210
|
+
maxTimestampDiffMs: null
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const priceDeltas = matched.map((item) => item.priceDeltaPct).filter(
|
|
214
|
+
(value) => value != null && Number.isFinite(value)
|
|
215
|
+
);
|
|
216
|
+
const timestampDiffs = matched.map((item) => item.timestampDiffMs).filter((value) => Number.isFinite(value));
|
|
217
|
+
const average = (values) => values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
|
|
218
|
+
const maximum = (values) => values.length ? Math.max(...values) : null;
|
|
219
|
+
return {
|
|
220
|
+
avgPriceDeltaPct: average(priceDeltas),
|
|
221
|
+
maxPriceDeltaPct: maximum(priceDeltas),
|
|
222
|
+
avgTimestampDiffMs: average(timestampDiffs),
|
|
223
|
+
maxTimestampDiffMs: maximum(timestampDiffs)
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
227
|
+
0 && (module.exports = {
|
|
228
|
+
compareTradeParityEntries,
|
|
229
|
+
dedupeRuntimeParityEntries,
|
|
230
|
+
extractBacktestEntryParityEntries,
|
|
231
|
+
extractRuntimeParityEntries,
|
|
232
|
+
findRuntimeDuplicateGroups,
|
|
233
|
+
summarizeMatchedParity
|
|
234
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/lib/runtimeSignalsLoader.ts
|
|
21
|
+
var runtimeSignalsLoader_exports = {};
|
|
22
|
+
__export(runtimeSignalsLoader_exports, {
|
|
23
|
+
isRuntimeSignalEvaluationRecord: () => isRuntimeSignalEvaluationRecord,
|
|
24
|
+
isSignalRecord: () => isSignalRecord,
|
|
25
|
+
loadRuntimeSignalEvaluationStatsBuckets: () => loadRuntimeSignalEvaluationStatsBuckets,
|
|
26
|
+
loadRuntimeSignalEvaluations: () => loadRuntimeSignalEvaluations,
|
|
27
|
+
loadRuntimeSignals: () => loadRuntimeSignals
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(runtimeSignalsLoader_exports);
|
|
30
|
+
var import_redis = require("@tradejs/infra/redis");
|
|
31
|
+
|
|
32
|
+
// src/lib/runtimeSignalsStorage.ts
|
|
33
|
+
var RUNTIME_STORAGE_DAY_OFFSET_MS = 5 * 60 * 60 * 1e3;
|
|
34
|
+
var isRuntimeSignalBucketRef = (value) => {
|
|
35
|
+
if (!value || typeof value !== "object") {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const record = value;
|
|
39
|
+
return typeof record.signalId === "string" && typeof record.symbol === "string" && typeof record.strategy === "string" && typeof record.timestamp === "number";
|
|
40
|
+
};
|
|
41
|
+
var toCount = (value) => {
|
|
42
|
+
const parsed = Number.parseInt(String(value ?? "0"), 10);
|
|
43
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
44
|
+
};
|
|
45
|
+
var parseRuntimeSignalStatsBucket = (value) => {
|
|
46
|
+
const reasonGroups = /* @__PURE__ */ new Map();
|
|
47
|
+
for (const [field, rawCount] of Object.entries(value)) {
|
|
48
|
+
if (!field.startsWith("reason:")) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const [, source, ...reasonParts] = field.split(":");
|
|
52
|
+
const reason = reasonParts.join(":");
|
|
53
|
+
if (!source || !reason) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const reasons = reasonGroups.get(source) ?? /* @__PURE__ */ new Map();
|
|
57
|
+
reasons.set(reason, toCount(rawCount));
|
|
58
|
+
reasonGroups.set(source, reasons);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
evaluated: toCount(value.evaluated),
|
|
62
|
+
signals: toCount(value.signals),
|
|
63
|
+
reasonGroups
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/lib/runtimeSignalsLoader.ts
|
|
68
|
+
var isSignalRecord = (value) => {
|
|
69
|
+
if (!value || typeof value !== "object") {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const record = value;
|
|
73
|
+
return typeof record.signalId === "string" && typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.timestamp === "number";
|
|
74
|
+
};
|
|
75
|
+
var isRuntimeSignalEvaluationRecord = (value) => {
|
|
76
|
+
if (!value || typeof value !== "object") {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const record = value;
|
|
80
|
+
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");
|
|
81
|
+
};
|
|
82
|
+
var sortByTimestamp = (records) => [...records].sort((left, right) => left.timestamp - right.timestamp);
|
|
83
|
+
var loadRuntimeSignals = async (userName) => {
|
|
84
|
+
const bucketKeys = await (0, import_redis.getKeys)(import_redis.redisKeys.runtimeSignalBuckets(userName));
|
|
85
|
+
const refs = (await Promise.all(bucketKeys.map((key) => (0, import_redis.getHashJsonValues)(key)))).flat().filter(isRuntimeSignalBucketRef);
|
|
86
|
+
const seen = /* @__PURE__ */ new Set();
|
|
87
|
+
const payloads = await Promise.all(
|
|
88
|
+
refs.map(async (ref) => {
|
|
89
|
+
const dedupeKey = `${ref.symbol}:${ref.signalId}`;
|
|
90
|
+
if (seen.has(dedupeKey)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
seen.add(dedupeKey);
|
|
94
|
+
const signal = await (0, import_redis.getData)(
|
|
95
|
+
import_redis.redisKeys.storeSignal(ref.symbol, ref.signalId),
|
|
96
|
+
null
|
|
97
|
+
);
|
|
98
|
+
return isSignalRecord(signal) ? signal : null;
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
return sortByTimestamp(payloads.filter(isSignalRecord));
|
|
102
|
+
};
|
|
103
|
+
var loadRuntimeSignalEvaluations = async (userName) => {
|
|
104
|
+
const bucketKeys = await (0, import_redis.getKeys)(
|
|
105
|
+
import_redis.redisKeys.runtimeSignalEvaluationBuckets(userName)
|
|
106
|
+
);
|
|
107
|
+
const evaluations = (await Promise.all(bucketKeys.map((key) => (0, import_redis.getHashJsonValues)(key)))).flat().filter(isRuntimeSignalEvaluationRecord);
|
|
108
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
109
|
+
for (const evaluation of evaluations) {
|
|
110
|
+
deduped.set(evaluation.evaluationId, evaluation);
|
|
111
|
+
}
|
|
112
|
+
return sortByTimestamp([...deduped.values()]);
|
|
113
|
+
};
|
|
114
|
+
var parseBucketKey = (prefix, key) => {
|
|
115
|
+
if (!key.startsWith(prefix)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const suffix = key.slice(prefix.length);
|
|
119
|
+
const firstColon = suffix.indexOf(":");
|
|
120
|
+
if (firstColon <= 0 || firstColon >= suffix.length - 1) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
dayKey: suffix.slice(0, firstColon),
|
|
125
|
+
strategy: suffix.slice(firstColon + 1)
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
var loadRuntimeSignalEvaluationStatsBuckets = async (userName) => {
|
|
129
|
+
const prefix = import_redis.redisKeys.runtimeSignalEvaluationStatsBuckets(userName);
|
|
130
|
+
const keys = await (0, import_redis.getKeys)(prefix);
|
|
131
|
+
const entries = await Promise.all(
|
|
132
|
+
keys.map(async (key) => {
|
|
133
|
+
const parsedKey = parseBucketKey(prefix, key);
|
|
134
|
+
if (!parsedKey) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
key,
|
|
139
|
+
...parsedKey,
|
|
140
|
+
stats: parseRuntimeSignalStatsBucket(await (0, import_redis.getHashData)(key))
|
|
141
|
+
};
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
return entries.filter(Boolean);
|
|
145
|
+
};
|
|
146
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
147
|
+
0 && (module.exports = {
|
|
148
|
+
isRuntimeSignalEvaluationRecord,
|
|
149
|
+
isSignalRecord,
|
|
150
|
+
loadRuntimeSignalEvaluationStatsBuckets,
|
|
151
|
+
loadRuntimeSignalEvaluations,
|
|
152
|
+
loadRuntimeSignals
|
|
153
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/lib/runtimeSignalsStorage.ts
|
|
21
|
+
var runtimeSignalsStorage_exports = {};
|
|
22
|
+
__export(runtimeSignalsStorage_exports, {
|
|
23
|
+
buildRuntimeSignalStatsIncrements: () => buildRuntimeSignalStatsIncrements,
|
|
24
|
+
getRuntimeStorageDayKey: () => getRuntimeStorageDayKey,
|
|
25
|
+
getRuntimeStorageDayKeys: () => getRuntimeStorageDayKeys,
|
|
26
|
+
isRuntimeSignalBucketRef: () => isRuntimeSignalBucketRef,
|
|
27
|
+
normalizeRuntimeSignalSkipReason: () => normalizeRuntimeSignalSkipReason,
|
|
28
|
+
parseRuntimeSignalStatsBucket: () => parseRuntimeSignalStatsBucket,
|
|
29
|
+
shouldStoreDetailedRuntimeSignalEvaluation: () => shouldStoreDetailedRuntimeSignalEvaluation,
|
|
30
|
+
toRuntimeSignalBucketRef: () => toRuntimeSignalBucketRef
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(runtimeSignalsStorage_exports);
|
|
33
|
+
var DAY_MS = 864e5;
|
|
34
|
+
var RUNTIME_STORAGE_DAY_OFFSET_MS = 5 * 60 * 60 * 1e3;
|
|
35
|
+
var toIsoDayKey = (timestamp) => new Date(timestamp).toISOString().slice(0, 10);
|
|
36
|
+
var toRuntimeStorageDayTimestamp = (timestamp) => Math.floor((timestamp + RUNTIME_STORAGE_DAY_OFFSET_MS) / DAY_MS) * DAY_MS;
|
|
37
|
+
var getRuntimeStorageDayKey = (timestamp) => toIsoDayKey(toRuntimeStorageDayTimestamp(timestamp));
|
|
38
|
+
var getRuntimeStorageDayKeys = (startTime, endTime) => {
|
|
39
|
+
if (!Number.isFinite(startTime) || !Number.isFinite(endTime)) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
const start = Math.min(startTime, endTime);
|
|
43
|
+
const endExclusive = Math.max(startTime, endTime);
|
|
44
|
+
const normalizedEnd = Math.max(start, endExclusive - 1);
|
|
45
|
+
const keys = [];
|
|
46
|
+
const startDay = toRuntimeStorageDayTimestamp(start);
|
|
47
|
+
const endDay = toRuntimeStorageDayTimestamp(normalizedEnd);
|
|
48
|
+
for (let current = startDay; current <= endDay; current += DAY_MS) {
|
|
49
|
+
keys.push(toIsoDayKey(current));
|
|
50
|
+
}
|
|
51
|
+
return keys;
|
|
52
|
+
};
|
|
53
|
+
var toRuntimeSignalBucketRef = (signal) => ({
|
|
54
|
+
signalId: signal.signalId,
|
|
55
|
+
symbol: signal.symbol,
|
|
56
|
+
strategy: signal.strategy,
|
|
57
|
+
timestamp: signal.timestamp
|
|
58
|
+
});
|
|
59
|
+
var isRuntimeSignalBucketRef = (value) => {
|
|
60
|
+
if (!value || typeof value !== "object") {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const record = value;
|
|
64
|
+
return typeof record.signalId === "string" && typeof record.symbol === "string" && typeof record.strategy === "string" && typeof record.timestamp === "number";
|
|
65
|
+
};
|
|
66
|
+
var normalizeRuntimeSignalSkipReason = (reason, fallbackSource) => {
|
|
67
|
+
let source = fallbackSource;
|
|
68
|
+
let normalizedReason = reason;
|
|
69
|
+
if (reason.startsWith("AI_QUALITY_BELOW_MIN")) {
|
|
70
|
+
source = "AI";
|
|
71
|
+
normalizedReason = "MIN_AI_QUALITY";
|
|
72
|
+
} else if (reason === "AI_QUALITY_UNAVAILABLE") {
|
|
73
|
+
source = "AI";
|
|
74
|
+
normalizedReason = "QUALITY_UNAVAILABLE";
|
|
75
|
+
} else if (reason === "ML_RESULT_UNAVAILABLE") {
|
|
76
|
+
source = "ML";
|
|
77
|
+
normalizedReason = "RESULT_UNAVAILABLE";
|
|
78
|
+
} else if (reason.startsWith("ML_THRESHOLD_NOT_MET")) {
|
|
79
|
+
source = "ML";
|
|
80
|
+
normalizedReason = "ML_THRESHOLD";
|
|
81
|
+
} else if (reason.startsWith("HOOK_BEFORE_ENTRY_GATE:")) {
|
|
82
|
+
source = "hook";
|
|
83
|
+
normalizedReason = `BEFORE_ENTRY_GATE:${reason.slice(
|
|
84
|
+
"HOOK_BEFORE_ENTRY_GATE:".length
|
|
85
|
+
)}`;
|
|
86
|
+
} else if (reason === "HOOK_BEFORE_ENTRY_GATE") {
|
|
87
|
+
source = "hook";
|
|
88
|
+
normalizedReason = "BEFORE_ENTRY_GATE";
|
|
89
|
+
} else if (reason === "MAKE_ORDERS_DISABLED" || reason === "ENTRY_POLICY_BLOCKED") {
|
|
90
|
+
source = "policy";
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
source: `skip from ${source}`,
|
|
94
|
+
reason: normalizedReason
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
var buildStatsReasonField = (source, reason) => `reason:${source}:${reason}`;
|
|
98
|
+
var buildRuntimeSignalStatsIncrements = (evaluation) => {
|
|
99
|
+
const increments = {
|
|
100
|
+
evaluated: 1
|
|
101
|
+
};
|
|
102
|
+
if (evaluation.status === "signal") {
|
|
103
|
+
increments.signals = 1;
|
|
104
|
+
}
|
|
105
|
+
let skipReason = null;
|
|
106
|
+
let fallbackSource = "core";
|
|
107
|
+
if (evaluation.status === "skip") {
|
|
108
|
+
skipReason = evaluation.reason || "NO_SIGNAL";
|
|
109
|
+
fallbackSource = "core";
|
|
110
|
+
} else if (evaluation.status === "signal" && (evaluation.orderStatus === "skipped" || evaluation.orderStatus === "canceled")) {
|
|
111
|
+
skipReason = evaluation.orderSkipReason || evaluation.reason || `orderStatus=${evaluation.orderStatus}`;
|
|
112
|
+
fallbackSource = "runtime";
|
|
113
|
+
} else if (evaluation.status === "error") {
|
|
114
|
+
skipReason = evaluation.reason || "EVALUATION_ERROR";
|
|
115
|
+
fallbackSource = "runtime";
|
|
116
|
+
}
|
|
117
|
+
if (skipReason) {
|
|
118
|
+
const normalized = normalizeRuntimeSignalSkipReason(
|
|
119
|
+
skipReason,
|
|
120
|
+
fallbackSource
|
|
121
|
+
);
|
|
122
|
+
increments[buildStatsReasonField(normalized.source, normalized.reason)] = 1;
|
|
123
|
+
}
|
|
124
|
+
return increments;
|
|
125
|
+
};
|
|
126
|
+
var shouldStoreDetailedRuntimeSignalEvaluation = (evaluation) => evaluation.status !== "skip";
|
|
127
|
+
var toCount = (value) => {
|
|
128
|
+
const parsed = Number.parseInt(String(value ?? "0"), 10);
|
|
129
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
130
|
+
};
|
|
131
|
+
var parseRuntimeSignalStatsBucket = (value) => {
|
|
132
|
+
const reasonGroups = /* @__PURE__ */ new Map();
|
|
133
|
+
for (const [field, rawCount] of Object.entries(value)) {
|
|
134
|
+
if (!field.startsWith("reason:")) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const [, source, ...reasonParts] = field.split(":");
|
|
138
|
+
const reason = reasonParts.join(":");
|
|
139
|
+
if (!source || !reason) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const reasons = reasonGroups.get(source) ?? /* @__PURE__ */ new Map();
|
|
143
|
+
reasons.set(reason, toCount(rawCount));
|
|
144
|
+
reasonGroups.set(source, reasons);
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
evaluated: toCount(value.evaluated),
|
|
148
|
+
signals: toCount(value.signals),
|
|
149
|
+
reasonGroups
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
153
|
+
0 && (module.exports = {
|
|
154
|
+
buildRuntimeSignalStatsIncrements,
|
|
155
|
+
getRuntimeStorageDayKey,
|
|
156
|
+
getRuntimeStorageDayKeys,
|
|
157
|
+
isRuntimeSignalBucketRef,
|
|
158
|
+
normalizeRuntimeSignalSkipReason,
|
|
159
|
+
parseRuntimeSignalStatsBucket,
|
|
160
|
+
shouldStoreDetailedRuntimeSignalEvaluation,
|
|
161
|
+
toRuntimeSignalBucketRef
|
|
162
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/lib/telegramReports.ts
|
|
21
|
+
var telegramReports_exports = {};
|
|
22
|
+
__export(telegramReports_exports, {
|
|
23
|
+
TELEGRAM_REPORT_CHUNK_LIMIT: () => TELEGRAM_REPORT_CHUNK_LIMIT,
|
|
24
|
+
sendTelegramReport: () => sendTelegramReport,
|
|
25
|
+
splitTelegramReport: () => splitTelegramReport
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(telegramReports_exports);
|
|
28
|
+
var import_cli = require("@tradejs/node/cli");
|
|
29
|
+
var TELEGRAM_REPORT_CHUNK_LIMIT = 3900;
|
|
30
|
+
var buildPartHeader = (index, total) => `\u{1F4E9} <b>Part ${index}/${total}</b>`;
|
|
31
|
+
var splitLongLine = (line, limit) => {
|
|
32
|
+
const chunks = [];
|
|
33
|
+
for (let index = 0; index < line.length; index += limit) {
|
|
34
|
+
chunks.push(line.slice(index, index + limit));
|
|
35
|
+
}
|
|
36
|
+
return chunks;
|
|
37
|
+
};
|
|
38
|
+
var splitTelegramReport = (message, limit = TELEGRAM_REPORT_CHUNK_LIMIT) => {
|
|
39
|
+
const normalizedLimit = Math.max(100, Math.floor(limit));
|
|
40
|
+
if (message.length <= normalizedLimit) {
|
|
41
|
+
return [message];
|
|
42
|
+
}
|
|
43
|
+
const chunks = [];
|
|
44
|
+
let current = "";
|
|
45
|
+
const flush = () => {
|
|
46
|
+
if (!current) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
chunks.push(current);
|
|
50
|
+
current = "";
|
|
51
|
+
};
|
|
52
|
+
for (const line of message.split("\n")) {
|
|
53
|
+
const next = current ? `${current}
|
|
54
|
+
${line}` : line;
|
|
55
|
+
if (next.length <= normalizedLimit) {
|
|
56
|
+
current = next;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
flush();
|
|
60
|
+
if (line.length <= normalizedLimit) {
|
|
61
|
+
current = line;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
chunks.push(...splitLongLine(line, normalizedLimit));
|
|
65
|
+
}
|
|
66
|
+
flush();
|
|
67
|
+
return chunks;
|
|
68
|
+
};
|
|
69
|
+
var sendTelegramReport = async (message, options = {}) => {
|
|
70
|
+
const chunks = splitTelegramReport(message);
|
|
71
|
+
if (chunks.length === 1) {
|
|
72
|
+
await (0, import_cli.sendTextToTG)(chunks[0], options);
|
|
73
|
+
return chunks.length;
|
|
74
|
+
}
|
|
75
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
76
|
+
const header = buildPartHeader(index + 1, chunks.length);
|
|
77
|
+
await (0, import_cli.sendTextToTG)(`${header}
|
|
78
|
+
|
|
79
|
+
${chunk}`, {
|
|
80
|
+
userName: options.userName,
|
|
81
|
+
markup: index === chunks.length - 1 ? options.markup : void 0
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return chunks.length;
|
|
85
|
+
};
|
|
86
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
87
|
+
0 && (module.exports = {
|
|
88
|
+
TELEGRAM_REPORT_CHUNK_LIMIT,
|
|
89
|
+
sendTelegramReport,
|
|
90
|
+
splitTelegramReport
|
|
91
|
+
});
|