@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,487 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/lib/derivativesContextBackfill.ts
|
|
31
|
+
var derivativesContextBackfill_exports = {};
|
|
32
|
+
__export(derivativesContextBackfill_exports, {
|
|
33
|
+
backfillDerivativesContextForBacktest: () => backfillDerivativesContextForBacktest,
|
|
34
|
+
backfillDerivativesContextForSignals: () => backfillDerivativesContextForSignals,
|
|
35
|
+
isBacktestDerivativesContextEnabled: () => isBacktestDerivativesContextEnabled,
|
|
36
|
+
isSignalsDerivativesContextEnabled: () => isSignalsDerivativesContextEnabled,
|
|
37
|
+
resolveDerivativesContextBackfillSymbols: () => resolveDerivativesContextBackfillSymbols,
|
|
38
|
+
resolveDerivativesContextBackfillWindow: () => resolveDerivativesContextBackfillWindow,
|
|
39
|
+
resolveDerivativesContextIntervals: () => resolveDerivativesContextIntervals,
|
|
40
|
+
resolveDerivativesContextLookbackMs: () => resolveDerivativesContextLookbackMs,
|
|
41
|
+
shouldBackfillDerivativesContextForBacktest: () => shouldBackfillDerivativesContextForBacktest,
|
|
42
|
+
shouldBackfillDerivativesContextForSignals: () => shouldBackfillDerivativesContextForSignals
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(derivativesContextBackfill_exports);
|
|
45
|
+
var import_chalk = __toESM(require("chalk"));
|
|
46
|
+
var import_progress = __toESM(require("progress"));
|
|
47
|
+
var import_async = require("@tradejs/core/async");
|
|
48
|
+
var import_constants = require("@tradejs/core/constants");
|
|
49
|
+
var import_indicators = require("@tradejs/core/indicators");
|
|
50
|
+
var import_timescale = require("@tradejs/infra/timescale");
|
|
51
|
+
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
52
|
+
var HOUR_MS = 60 * 60 * 1e3;
|
|
53
|
+
var DAY_MS = 24 * HOUR_MS;
|
|
54
|
+
var DEFAULT_LOOKBACK_HOURS = 48;
|
|
55
|
+
var DEFAULT_INTERVALS = ["15m", "1h"];
|
|
56
|
+
var coinalyzeIntervalMap = {
|
|
57
|
+
"15m": "15min",
|
|
58
|
+
"1h": "1hour"
|
|
59
|
+
};
|
|
60
|
+
var lastRequestTs = 0;
|
|
61
|
+
var asInt = (value, fallback) => {
|
|
62
|
+
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
63
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
64
|
+
};
|
|
65
|
+
var parseList = (value) => String(value ?? "").split(",").map((item) => item.trim().toUpperCase()).filter(Boolean);
|
|
66
|
+
var normalizeSymbols = (symbols) => [
|
|
67
|
+
...new Set(
|
|
68
|
+
symbols.map(
|
|
69
|
+
(symbol) => String(symbol || "").trim().toUpperCase()
|
|
70
|
+
).filter(Boolean)
|
|
71
|
+
)
|
|
72
|
+
];
|
|
73
|
+
var resolveDerivativesContextBackfillSymbols = (_requestedSymbols = []) => [...import_constants.DERIVATIVES_CONTEXT_REFERENCE_SYMBOLS];
|
|
74
|
+
var chunkArray = (items, size) => {
|
|
75
|
+
const chunks = [];
|
|
76
|
+
for (let i = 0; i < items.length; i += size) {
|
|
77
|
+
chunks.push(items.slice(i, i + size));
|
|
78
|
+
}
|
|
79
|
+
return chunks;
|
|
80
|
+
};
|
|
81
|
+
var isBacktestDerivativesContextEnabled = () => {
|
|
82
|
+
return isDerivativesContextBackfillEnabled("BACKTEST");
|
|
83
|
+
};
|
|
84
|
+
var isSignalsDerivativesContextEnabled = () => isDerivativesContextBackfillEnabled("CRON");
|
|
85
|
+
var isDerivativesContextBackfillEnabled = (env) => {
|
|
86
|
+
const normalized = String(process.env.DERIVATIVES_CONTEXT_ENABLED ?? "").trim().toLowerCase();
|
|
87
|
+
if (!normalized) return false;
|
|
88
|
+
if (["1", "true", "yes", "on"].includes(normalized)) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
if (normalized === "backtest") {
|
|
92
|
+
return env === "BACKTEST";
|
|
93
|
+
}
|
|
94
|
+
if (normalized === "live") {
|
|
95
|
+
return env !== "BACKTEST";
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
};
|
|
99
|
+
var shouldBackfillDerivativesContextForBacktest = (params) => !params.cacheOnly && (params.aiEnabled || params.mlEnabled) && isBacktestDerivativesContextEnabled();
|
|
100
|
+
var shouldBackfillDerivativesContextForSignals = (params) => !params.cacheOnly && isSignalsDerivativesContextEnabled();
|
|
101
|
+
var resolveDerivativesContextIntervals = () => {
|
|
102
|
+
const intervals = (0, import_indicators.normalizeDerivativesIntervals)(
|
|
103
|
+
process.env.DERIVATIVES_CONTEXT_INTERVALS
|
|
104
|
+
);
|
|
105
|
+
return intervals.length ? intervals : DEFAULT_INTERVALS;
|
|
106
|
+
};
|
|
107
|
+
var resolveDerivativesContextLookbackMs = () => {
|
|
108
|
+
const hours = Number(process.env.DERIVATIVES_CONTEXT_LOOKBACK_HOURS);
|
|
109
|
+
const normalizedHours = Number.isFinite(hours) && hours > 0 ? hours : DEFAULT_LOOKBACK_HOURS;
|
|
110
|
+
return normalizedHours * HOUR_MS;
|
|
111
|
+
};
|
|
112
|
+
var resolveDerivativesContextBackfillWindow = (params) => {
|
|
113
|
+
const { startMs, endMs, preloadStartMs, nowMs = Date.now() } = params;
|
|
114
|
+
const safeEndMs = Math.min(endMs, nowMs);
|
|
115
|
+
const safeStartMs = Math.max(0, Math.min(startMs, safeEndMs));
|
|
116
|
+
const lookbackFromMs = safeStartMs - resolveDerivativesContextLookbackMs();
|
|
117
|
+
const requestedFromMs = preloadStartMs != null ? Math.max(Number(preloadStartMs), lookbackFromMs) : lookbackFromMs;
|
|
118
|
+
const fromMs = Math.max(
|
|
119
|
+
0,
|
|
120
|
+
Math.min(
|
|
121
|
+
Number.isFinite(requestedFromMs) ? requestedFromMs : safeStartMs,
|
|
122
|
+
safeStartMs
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
return {
|
|
126
|
+
fromMs: Math.trunc(fromMs),
|
|
127
|
+
toMs: Math.trunc(safeEndMs),
|
|
128
|
+
testStartMs: Math.trunc(safeStartMs)
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
var getCoinalyzeApiKey = async (userName) => {
|
|
132
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
133
|
+
return settings.COINALYZE_API_KEY.trim() || String(process.env.COINALYZE_API_KEY ?? "").trim();
|
|
134
|
+
};
|
|
135
|
+
var getCoinalyzeBaseUrl = () => process.env.COINALYZE_BASE_URL?.trim() || "https://api.coinalyze.net/v1";
|
|
136
|
+
var getRequestDelayMs = () => Math.max(
|
|
137
|
+
100,
|
|
138
|
+
asInt(
|
|
139
|
+
process.env.DERIVATIVES_CONTEXT_BACKFILL_REQUEST_DELAY_MS ?? process.env.COINALYZE_MIN_REQUEST_DELAY_MS,
|
|
140
|
+
200
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
var getRequestTimeoutMs = () => Math.max(5e3, asInt(process.env.COINALYZE_REQUEST_TIMEOUT_MS, 45e3));
|
|
144
|
+
var fetchJsonWithRateLimit = async (url, apiKey) => {
|
|
145
|
+
const requestDelayMs = getRequestDelayMs();
|
|
146
|
+
const requestTimeoutMs = getRequestTimeoutMs();
|
|
147
|
+
const maxRetries = asInt(process.env.COINALYZE_MAX_RETRIES, 4);
|
|
148
|
+
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
149
|
+
const waitMs = Math.max(0, lastRequestTs + requestDelayMs - Date.now());
|
|
150
|
+
if (waitMs > 0) {
|
|
151
|
+
await (0, import_async.delay)(waitMs);
|
|
152
|
+
}
|
|
153
|
+
lastRequestTs = Date.now();
|
|
154
|
+
const controller = new AbortController();
|
|
155
|
+
const timer = setTimeout(() => controller.abort(), requestTimeoutMs);
|
|
156
|
+
let response;
|
|
157
|
+
try {
|
|
158
|
+
response = await fetch(url, {
|
|
159
|
+
headers: {
|
|
160
|
+
api_key: apiKey,
|
|
161
|
+
"x-api-key": apiKey,
|
|
162
|
+
Authorization: `Bearer ${apiKey}`
|
|
163
|
+
},
|
|
164
|
+
signal: controller.signal
|
|
165
|
+
});
|
|
166
|
+
} catch (error) {
|
|
167
|
+
clearTimeout(timer);
|
|
168
|
+
const abortError = error instanceof Error && (error.name === "AbortError" || String(error.message).toLowerCase().includes("aborted"));
|
|
169
|
+
if (attempt < maxRetries && abortError) {
|
|
170
|
+
await (0, import_async.delay)(Math.min(12e3, 800 * 2 ** attempt));
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
throw error;
|
|
174
|
+
} finally {
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
}
|
|
177
|
+
if (response.ok) {
|
|
178
|
+
return response.json();
|
|
179
|
+
}
|
|
180
|
+
const text = await response.text();
|
|
181
|
+
const retryAfterRaw = Number(response.headers.get("retry-after") ?? "");
|
|
182
|
+
const retryAfterMs = Number.isFinite(retryAfterRaw) && retryAfterRaw > 0 ? retryAfterRaw * 1e3 : null;
|
|
183
|
+
const transient = response.status === 429 || response.status >= 500;
|
|
184
|
+
if (attempt < maxRetries && transient) {
|
|
185
|
+
await (0, import_async.delay)(retryAfterMs ?? Math.min(12e3, 800 * 2 ** attempt));
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
throw new Error(`Coinalyze ${response.status}: ${text}`);
|
|
189
|
+
}
|
|
190
|
+
throw new Error("Coinalyze request failed after retries");
|
|
191
|
+
};
|
|
192
|
+
var fetchCoinalyzeMarkets = async (apiKey) => {
|
|
193
|
+
const raw = await fetchJsonWithRateLimit(
|
|
194
|
+
`${getCoinalyzeBaseUrl()}/future-markets`,
|
|
195
|
+
apiKey
|
|
196
|
+
);
|
|
197
|
+
return Array.isArray(raw) ? raw : [];
|
|
198
|
+
};
|
|
199
|
+
var selectBestMarket = (candidates, exchangePriority) => {
|
|
200
|
+
const scored = candidates.map((item) => {
|
|
201
|
+
let score = 0;
|
|
202
|
+
if (item.is_perpetual) score += 50;
|
|
203
|
+
if (String(item.quote_asset || "").toUpperCase() === "USDT") score += 25;
|
|
204
|
+
if (item.symbol.includes("_PERP")) score += 10;
|
|
205
|
+
const exchange = String(item.exchange || "").toUpperCase();
|
|
206
|
+
const idx = exchangePriority.indexOf(exchange);
|
|
207
|
+
if (idx >= 0) {
|
|
208
|
+
score += (exchangePriority.length - idx) * 10;
|
|
209
|
+
}
|
|
210
|
+
return { item, score };
|
|
211
|
+
});
|
|
212
|
+
scored.sort((a, b) => {
|
|
213
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
214
|
+
return a.item.symbol.localeCompare(b.item.symbol);
|
|
215
|
+
});
|
|
216
|
+
return scored[0]?.item ?? null;
|
|
217
|
+
};
|
|
218
|
+
var buildMatches = (tickers, markets, exchangePriority) => {
|
|
219
|
+
const marketByTicker = /* @__PURE__ */ new Map();
|
|
220
|
+
for (const market of markets) {
|
|
221
|
+
const key = String(market.symbol_on_exchange || "").trim().toUpperCase();
|
|
222
|
+
if (!key) continue;
|
|
223
|
+
const list = marketByTicker.get(key) ?? [];
|
|
224
|
+
list.push(market);
|
|
225
|
+
marketByTicker.set(key, list);
|
|
226
|
+
}
|
|
227
|
+
const matches = [];
|
|
228
|
+
const unmatched = [];
|
|
229
|
+
for (const ticker of tickers) {
|
|
230
|
+
const candidates = marketByTicker.get(ticker) ?? [];
|
|
231
|
+
const selected = selectBestMarket(candidates, exchangePriority);
|
|
232
|
+
if (!selected) {
|
|
233
|
+
unmatched.push(ticker);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
matches.push({
|
|
237
|
+
symbol: ticker,
|
|
238
|
+
marketSymbol: selected.symbol,
|
|
239
|
+
exchange: selected.exchange
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return { matches, unmatched };
|
|
243
|
+
};
|
|
244
|
+
var normalizeMetricPoint = (metric, point) => {
|
|
245
|
+
if (metric === "oi") {
|
|
246
|
+
return {
|
|
247
|
+
...point,
|
|
248
|
+
open_interest: point.open_interest ?? point.openInterest ?? point.oi ?? point.c
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
if (metric === "funding") {
|
|
252
|
+
return {
|
|
253
|
+
...point,
|
|
254
|
+
funding_rate: point.funding_rate ?? point.fundingRate ?? point.rate ?? point.c
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
...point,
|
|
259
|
+
liq_long: point.liq_long ?? point.long_liq ?? point.long ?? point.l,
|
|
260
|
+
liq_short: point.liq_short ?? point.short_liq ?? point.short ?? point.s
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
var toSeriesMap = (raw, metric) => {
|
|
264
|
+
const out = /* @__PURE__ */ new Map();
|
|
265
|
+
if (!Array.isArray(raw)) return out;
|
|
266
|
+
for (const item of raw) {
|
|
267
|
+
const symbol = String(item.symbol || "").trim().toUpperCase();
|
|
268
|
+
const history = Array.isArray(item.history) ? item.history : [];
|
|
269
|
+
if (!symbol) continue;
|
|
270
|
+
out.set(
|
|
271
|
+
symbol,
|
|
272
|
+
history.filter((point) => point && typeof point === "object").map((point) => normalizeMetricPoint(metric, point))
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
return out;
|
|
276
|
+
};
|
|
277
|
+
var fetchMetricBatch = async (params) => {
|
|
278
|
+
const { endpoint, metric, marketSymbols, apiKey, interval, fromMs, toMs } = params;
|
|
279
|
+
const url = new URL(`${getCoinalyzeBaseUrl()}${endpoint}`);
|
|
280
|
+
url.searchParams.set("symbols", marketSymbols.join(","));
|
|
281
|
+
url.searchParams.set("interval", coinalyzeIntervalMap[interval] || interval);
|
|
282
|
+
url.searchParams.set("from", String(Math.floor(fromMs / 1e3)));
|
|
283
|
+
url.searchParams.set("to", String(Math.floor(toMs / 1e3)));
|
|
284
|
+
const raw = await fetchJsonWithRateLimit(url.toString(), apiKey);
|
|
285
|
+
return toSeriesMap(raw, metric);
|
|
286
|
+
};
|
|
287
|
+
var skippedBackfillResult = () => ({
|
|
288
|
+
skipped: true,
|
|
289
|
+
rows: 0,
|
|
290
|
+
matchedSymbols: 0,
|
|
291
|
+
unmatchedSymbols: 0,
|
|
292
|
+
failedWindows: 0,
|
|
293
|
+
skippedWindows: 0
|
|
294
|
+
});
|
|
295
|
+
var backfillDerivativesContext = async (params, enabled) => {
|
|
296
|
+
const { userName, startMs, endMs } = params;
|
|
297
|
+
const requestedSymbols = normalizeSymbols(params.symbols);
|
|
298
|
+
const symbols = resolveDerivativesContextBackfillSymbols(requestedSymbols);
|
|
299
|
+
if (!enabled || !requestedSymbols.length || !symbols.length) {
|
|
300
|
+
return skippedBackfillResult();
|
|
301
|
+
}
|
|
302
|
+
const intervals = resolveDerivativesContextIntervals();
|
|
303
|
+
if (!intervals.length) {
|
|
304
|
+
return skippedBackfillResult();
|
|
305
|
+
}
|
|
306
|
+
const apiKey = await getCoinalyzeApiKey(userName);
|
|
307
|
+
if (!apiKey) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
`Missing COINALYZE_API_KEY for derivatives context backfill (user=${userName})`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
const {
|
|
313
|
+
fromMs,
|
|
314
|
+
toMs: safeEndMs,
|
|
315
|
+
testStartMs: safeStartMs
|
|
316
|
+
} = resolveDerivativesContextBackfillWindow({
|
|
317
|
+
startMs,
|
|
318
|
+
endMs,
|
|
319
|
+
preloadStartMs: params.preloadStartMs
|
|
320
|
+
});
|
|
321
|
+
if (safeEndMs <= fromMs) {
|
|
322
|
+
return skippedBackfillResult();
|
|
323
|
+
}
|
|
324
|
+
const exchangePriority = parseList(
|
|
325
|
+
process.env.COINALYZE_EXCHANGE_PRIORITY || process.env.DERIVATIVES_CONTEXT_EXCHANGE_PRIORITY || "A,6,0"
|
|
326
|
+
);
|
|
327
|
+
const markets = await fetchCoinalyzeMarkets(apiKey);
|
|
328
|
+
if (!markets.length) {
|
|
329
|
+
throw new Error("No markets returned by Coinalyze /future-markets");
|
|
330
|
+
}
|
|
331
|
+
const { matches, unmatched } = buildMatches(
|
|
332
|
+
symbols,
|
|
333
|
+
markets,
|
|
334
|
+
exchangePriority
|
|
335
|
+
);
|
|
336
|
+
if (!matches.length) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
"No matched derivatives reference symbols between BTC/ETH and Coinalyze"
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
await (0, import_timescale.waitForDbReady)();
|
|
342
|
+
const batchDays = asInt(
|
|
343
|
+
process.env.DERIVATIVES_CONTEXT_BACKFILL_BATCH_DAYS,
|
|
344
|
+
30
|
|
345
|
+
);
|
|
346
|
+
const symbolBatchSize = asInt(
|
|
347
|
+
process.env.DERIVATIVES_CONTEXT_BACKFILL_SYMBOL_BATCH_SIZE,
|
|
348
|
+
8
|
|
349
|
+
);
|
|
350
|
+
const symbolBatches = chunkArray(matches, symbolBatchSize);
|
|
351
|
+
const windowsPerPair = Math.ceil((safeEndMs - fromMs) / (batchDays * DAY_MS));
|
|
352
|
+
const totalWindows = symbolBatches.length * intervals.length * windowsPerPair;
|
|
353
|
+
const bar = new import_progress.default(
|
|
354
|
+
":current/:total [:bar][:percent] :eta(s) :batch rows=:rows fail=:fail skip=:skip",
|
|
355
|
+
{
|
|
356
|
+
total: Math.max(1, totalWindows),
|
|
357
|
+
width: 24
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
const oiPath = process.env.COINALYZE_OI_PATH?.trim() || "/open-interest-history";
|
|
361
|
+
const fundingPath = process.env.COINALYZE_FUNDING_PATH?.trim() || "/funding-rate-history";
|
|
362
|
+
const liqPath = process.env.COINALYZE_LIQ_PATH?.trim() || "/liquidation-history";
|
|
363
|
+
let totalRows = 0;
|
|
364
|
+
let failedWindows = 0;
|
|
365
|
+
let skippedWindows = 0;
|
|
366
|
+
console.log(
|
|
367
|
+
import_chalk.default.cyan(
|
|
368
|
+
`derivatives context backfill: referenceSymbols=${matches.length}, requestedSymbols=${requestedSymbols.length}, unmatched=${unmatched.length}, intervals=${intervals.join(",")}, window=${new Date(fromMs).toISOString()}..${new Date(safeEndMs).toISOString()}, testStart=${new Date(safeStartMs).toISOString()}`
|
|
369
|
+
)
|
|
370
|
+
);
|
|
371
|
+
for (const interval of intervals) {
|
|
372
|
+
const edgesBySymbol = await (0, import_timescale.getDerivativesDataEdgesForSymbols)(
|
|
373
|
+
matches.map((item) => item.symbol),
|
|
374
|
+
interval
|
|
375
|
+
);
|
|
376
|
+
for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
|
|
377
|
+
const batch = symbolBatches[batchIdx];
|
|
378
|
+
let cursor = fromMs;
|
|
379
|
+
while (cursor < safeEndMs) {
|
|
380
|
+
const toMs = Math.min(safeEndMs, cursor + batchDays * DAY_MS);
|
|
381
|
+
const missingBatch = batch.filter((item) => {
|
|
382
|
+
const edges = edgesBySymbol.get(item.symbol.toUpperCase());
|
|
383
|
+
return edges?.min == null || edges?.max == null || edges.min > cursor || edges.max < toMs;
|
|
384
|
+
});
|
|
385
|
+
try {
|
|
386
|
+
if (!missingBatch.length) {
|
|
387
|
+
skippedWindows += 1;
|
|
388
|
+
} else {
|
|
389
|
+
const marketSymbols = missingBatch.map((item) => item.marketSymbol);
|
|
390
|
+
const oiMap = await fetchMetricBatch({
|
|
391
|
+
endpoint: oiPath,
|
|
392
|
+
metric: "oi",
|
|
393
|
+
marketSymbols,
|
|
394
|
+
apiKey,
|
|
395
|
+
interval,
|
|
396
|
+
fromMs: cursor,
|
|
397
|
+
toMs
|
|
398
|
+
});
|
|
399
|
+
const fundingMap = await fetchMetricBatch({
|
|
400
|
+
endpoint: fundingPath,
|
|
401
|
+
metric: "funding",
|
|
402
|
+
marketSymbols,
|
|
403
|
+
apiKey,
|
|
404
|
+
interval,
|
|
405
|
+
fromMs: cursor,
|
|
406
|
+
toMs
|
|
407
|
+
});
|
|
408
|
+
const liqMap = await fetchMetricBatch({
|
|
409
|
+
endpoint: liqPath,
|
|
410
|
+
metric: "liq",
|
|
411
|
+
marketSymbols,
|
|
412
|
+
apiKey,
|
|
413
|
+
interval,
|
|
414
|
+
fromMs: cursor,
|
|
415
|
+
toMs
|
|
416
|
+
});
|
|
417
|
+
const rows = missingBatch.flatMap((item) => {
|
|
418
|
+
const marketSymbol = item.marketSymbol.toUpperCase();
|
|
419
|
+
const points = (0, import_indicators.mergeCoinalyzeMetrics)({
|
|
420
|
+
symbol: item.symbol,
|
|
421
|
+
oiRaw: oiMap.get(marketSymbol) ?? [],
|
|
422
|
+
fundingRaw: fundingMap.get(marketSymbol) ?? [],
|
|
423
|
+
liqRaw: liqMap.get(marketSymbol) ?? []
|
|
424
|
+
});
|
|
425
|
+
return (0, import_indicators.coinalyzePointsToRows)(points, interval, "coinalyze");
|
|
426
|
+
});
|
|
427
|
+
if (rows.length) {
|
|
428
|
+
await (0, import_timescale.upsertDerivatives)(rows);
|
|
429
|
+
totalRows += rows.length;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
} catch (error) {
|
|
433
|
+
failedWindows += 1;
|
|
434
|
+
console.error(
|
|
435
|
+
import_chalk.default.red(
|
|
436
|
+
`derivatives context backfill window failed batch=${batchIdx + 1}/${symbolBatches.length} interval=${interval} ${new Date(cursor).toISOString()}..${new Date(toMs).toISOString()}: ${error}`
|
|
437
|
+
)
|
|
438
|
+
);
|
|
439
|
+
} finally {
|
|
440
|
+
bar.tick(1, {
|
|
441
|
+
batch: import_chalk.default.gray(
|
|
442
|
+
`${batchIdx + 1}/${symbolBatches.length} ${interval}`
|
|
443
|
+
),
|
|
444
|
+
rows: totalRows,
|
|
445
|
+
fail: failedWindows,
|
|
446
|
+
skip: skippedWindows
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
cursor = toMs + 1;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
console.log("");
|
|
454
|
+
console.log(
|
|
455
|
+
import_chalk.default.green(
|
|
456
|
+
`derivatives context backfill done: rows=${totalRows}, failed_windows=${failedWindows}, skipped_windows=${skippedWindows}`
|
|
457
|
+
)
|
|
458
|
+
);
|
|
459
|
+
if (failedWindows > 0) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
`Derivatives context backfill failed for ${failedWindows} window(s)`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
skipped: false,
|
|
466
|
+
rows: totalRows,
|
|
467
|
+
matchedSymbols: matches.length,
|
|
468
|
+
unmatchedSymbols: unmatched.length,
|
|
469
|
+
failedWindows,
|
|
470
|
+
skippedWindows
|
|
471
|
+
};
|
|
472
|
+
};
|
|
473
|
+
var backfillDerivativesContextForBacktest = async (params) => backfillDerivativesContext(params, isBacktestDerivativesContextEnabled());
|
|
474
|
+
var backfillDerivativesContextForSignals = async (params) => backfillDerivativesContext(params, isSignalsDerivativesContextEnabled());
|
|
475
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
476
|
+
0 && (module.exports = {
|
|
477
|
+
backfillDerivativesContextForBacktest,
|
|
478
|
+
backfillDerivativesContextForSignals,
|
|
479
|
+
isBacktestDerivativesContextEnabled,
|
|
480
|
+
isSignalsDerivativesContextEnabled,
|
|
481
|
+
resolveDerivativesContextBackfillSymbols,
|
|
482
|
+
resolveDerivativesContextBackfillWindow,
|
|
483
|
+
resolveDerivativesContextIntervals,
|
|
484
|
+
resolveDerivativesContextLookbackMs,
|
|
485
|
+
shouldBackfillDerivativesContextForBacktest,
|
|
486
|
+
shouldBackfillDerivativesContextForSignals
|
|
487
|
+
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/lib/researchAgent.ts
|
|
31
|
+
var researchAgent_exports = {};
|
|
32
|
+
__export(researchAgent_exports, {
|
|
33
|
+
RESEARCH_AGENT_BRANCH_PREFIX: () => RESEARCH_AGENT_BRANCH_PREFIX,
|
|
34
|
+
RESEARCH_AGENT_MODEL: () => RESEARCH_AGENT_MODEL,
|
|
35
|
+
RESEARCH_AGENT_REASONING_EFFORT: () => RESEARCH_AGENT_REASONING_EFFORT,
|
|
36
|
+
assertDiffAllowed: () => assertDiffAllowed,
|
|
37
|
+
buildResearchAgentBranchName: () => buildResearchAgentBranchName,
|
|
38
|
+
buildResearchAgentCommitMessage: () => buildResearchAgentCommitMessage,
|
|
39
|
+
buildResearchAgentPrBody: () => buildResearchAgentPrBody,
|
|
40
|
+
buildResearchAgentPrTitle: () => buildResearchAgentPrTitle,
|
|
41
|
+
getResearchAgentAllowedPathPrefixes: () => getResearchAgentAllowedPathPrefixes,
|
|
42
|
+
getResearchAgentNotePath: () => getResearchAgentNotePath,
|
|
43
|
+
normalizeDiffOutput: () => normalizeDiffOutput,
|
|
44
|
+
parseChangedFilesFromDiff: () => parseChangedFilesFromDiff,
|
|
45
|
+
parseGithubRepositoryFromRemote: () => parseGithubRepositoryFromRemote,
|
|
46
|
+
toUpperSnakeCase: () => toUpperSnakeCase
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(researchAgent_exports);
|
|
49
|
+
var import_node_path = __toESM(require("path"));
|
|
50
|
+
var RESEARCH_AGENT_MODEL = "openai/gpt-5.4";
|
|
51
|
+
var RESEARCH_AGENT_REASONING_EFFORT = "medium";
|
|
52
|
+
var RESEARCH_AGENT_BRANCH_PREFIX = "codex/research";
|
|
53
|
+
var toUpperSnakeCase = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
54
|
+
var RESEARCH_NOTE_FILE_OVERRIDES = {
|
|
55
|
+
TrendLine: "AI_TRENDLINE_REPLAY_NOTES.md",
|
|
56
|
+
ReverseTrendLine: "AI_REVERSE_TRENDLINE_REPLAY_NOTES.md",
|
|
57
|
+
AdaptiveMomentumRibbon: "AI_ADAPTIVE_MOMENTUM_RIBBON_REPLAY_NOTES.md",
|
|
58
|
+
VolumeDivergence: "AI_VOLUME_DIVERGENCE_REPLAY_NOTES.md"
|
|
59
|
+
};
|
|
60
|
+
var buildResearchAgentBranchName = (strategy, runId) => {
|
|
61
|
+
const normalizedStrategy = strategy.trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
62
|
+
const normalizedRunId = runId.trim().replace(/[^a-zA-Z0-9-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
63
|
+
return `${RESEARCH_AGENT_BRANCH_PREFIX}/${normalizedStrategy}-${normalizedRunId}`;
|
|
64
|
+
};
|
|
65
|
+
var buildResearchAgentCommitMessage = (strategy, runId) => `Research agent: ${strategy} follow-up for ${runId}`;
|
|
66
|
+
var buildResearchAgentPrTitle = (strategy, runId) => `Research agent: ${strategy} follow-up for ${runId}`;
|
|
67
|
+
var getResearchAgentNotePath = (strategy) => import_node_path.default.join(
|
|
68
|
+
"notes",
|
|
69
|
+
RESEARCH_NOTE_FILE_OVERRIDES[strategy] || `AI_${toUpperSnakeCase(strategy)}_REPLAY_NOTES.md`
|
|
70
|
+
);
|
|
71
|
+
var getResearchAgentAllowedPathPrefixes = (strategy) => [
|
|
72
|
+
`packages/strategies/src/${strategy}/`,
|
|
73
|
+
getResearchAgentNotePath(strategy)
|
|
74
|
+
];
|
|
75
|
+
var parseGithubRepositoryFromRemote = (value) => {
|
|
76
|
+
const trimmed = value.trim();
|
|
77
|
+
const sshMatch = trimmed.match(/github\.com:([^/]+\/[^/.]+)(?:\.git)?$/i);
|
|
78
|
+
if (sshMatch?.[1]) {
|
|
79
|
+
return sshMatch[1];
|
|
80
|
+
}
|
|
81
|
+
const httpsMatch = trimmed.match(/github\.com\/([^/]+\/[^/.]+)(?:\.git)?$/i);
|
|
82
|
+
if (httpsMatch?.[1]) {
|
|
83
|
+
return httpsMatch[1];
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
};
|
|
87
|
+
var buildResearchAgentPrBody = (params) => {
|
|
88
|
+
const validationSummary = Object.entries(params.validation || {}).map(([key, value]) => `- \`${key}\`: ${value}`).join("\n");
|
|
89
|
+
const aiRun = params.aiTrainLocal?.run;
|
|
90
|
+
const aiOutcome = params.aiTrainLocal?.outcome;
|
|
91
|
+
const lines = [
|
|
92
|
+
"## Why",
|
|
93
|
+
`Automated research follow-up for \`${params.strategy}\` from run \`${params.runId}\`.`,
|
|
94
|
+
`Research config: \`${params.config}\`, connector \`${params.connector}\`, timeframe \`${params.timeframe}\`, window \`${params.days}d\`, recent rows \`${params.recent}\`.`
|
|
95
|
+
];
|
|
96
|
+
if (params.summary) {
|
|
97
|
+
lines.push(params.summary);
|
|
98
|
+
}
|
|
99
|
+
if (aiRun || aiOutcome) {
|
|
100
|
+
lines.push("");
|
|
101
|
+
lines.push("### AI train snapshot");
|
|
102
|
+
if (aiRun) {
|
|
103
|
+
lines.push(
|
|
104
|
+
`- Rows: \`${aiRun.totalRows ?? 0}\`, approved: \`${aiRun.approvedRows ?? 0}\`, minQuality: \`${aiRun.minQuality ?? "n/a"}\``
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (aiOutcome) {
|
|
108
|
+
lines.push(
|
|
109
|
+
`- approvalRate=\`${aiOutcome.approvalRate ?? 0}\`, precisionApproved=\`${aiOutcome.precisionApproved ?? 0}\`, recallWinners=\`${aiOutcome.recallWinners ?? 0}\``
|
|
110
|
+
);
|
|
111
|
+
lines.push(
|
|
112
|
+
`- avgProfitApproved=\`${aiOutcome.avgProfitApproved ?? 0}\`, avgProfitApprovedPerMonth=\`${aiOutcome.avgProfitApprovedPerMonth ?? 0}\`, expectancyDelta=\`${aiOutcome.expectancyDelta ?? 0}\``
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
lines.push("");
|
|
117
|
+
lines.push("## What changed");
|
|
118
|
+
for (const file of params.changedFiles) {
|
|
119
|
+
lines.push(`- \`${file}\``);
|
|
120
|
+
}
|
|
121
|
+
lines.push("");
|
|
122
|
+
lines.push("## Validation");
|
|
123
|
+
lines.push(validationSummary || "- Validation did not run");
|
|
124
|
+
lines.push("");
|
|
125
|
+
lines.push("## Notes");
|
|
126
|
+
lines.push("- Created automatically by the TradeJS research agent.");
|
|
127
|
+
lines.push("- Base branch: `stable`.");
|
|
128
|
+
return lines.join("\n");
|
|
129
|
+
};
|
|
130
|
+
var normalizeDiffOutput = (value) => {
|
|
131
|
+
const trimmed = value.trim();
|
|
132
|
+
if (!trimmed) {
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
135
|
+
const fenced = trimmed.match(/```(?:diff)?\s*([\s\S]*?)```/i);
|
|
136
|
+
const content = fenced?.[1]?.trim() || trimmed;
|
|
137
|
+
return content.trim();
|
|
138
|
+
};
|
|
139
|
+
var parseChangedFilesFromDiff = (diffText) => {
|
|
140
|
+
const files = /* @__PURE__ */ new Set();
|
|
141
|
+
const lines = diffText.split("\n");
|
|
142
|
+
for (const line of lines) {
|
|
143
|
+
if (!line.startsWith("+++ ")) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const filePath = line.slice(4).trim();
|
|
147
|
+
if (!filePath || filePath === "/dev/null") {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const normalized = filePath.startsWith("b/") ? filePath.slice(2) : filePath;
|
|
151
|
+
if (normalized) {
|
|
152
|
+
files.add(normalized);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return [...files];
|
|
156
|
+
};
|
|
157
|
+
var assertDiffAllowed = (diffText, allowedPrefixes) => {
|
|
158
|
+
if (!diffText || diffText === "NO_CHANGES") {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
if (/\n\+\+\+\s+\/dev\/null\b/.test(`
|
|
162
|
+
${diffText}`)) {
|
|
163
|
+
throw new Error("Research agent diff must not delete files");
|
|
164
|
+
}
|
|
165
|
+
const changedFiles = parseChangedFilesFromDiff(diffText);
|
|
166
|
+
if (!changedFiles.length) {
|
|
167
|
+
throw new Error("Research agent diff did not contain any changed files");
|
|
168
|
+
}
|
|
169
|
+
for (const filePath of changedFiles) {
|
|
170
|
+
const allowed = allowedPrefixes.some(
|
|
171
|
+
(prefix) => prefix.endsWith("/") ? filePath.startsWith(prefix) : filePath === prefix || filePath.startsWith(`${prefix}/`)
|
|
172
|
+
);
|
|
173
|
+
if (!allowed) {
|
|
174
|
+
throw new Error(`Research agent changed disallowed path: ${filePath}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return changedFiles;
|
|
178
|
+
};
|
|
179
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
180
|
+
0 && (module.exports = {
|
|
181
|
+
RESEARCH_AGENT_BRANCH_PREFIX,
|
|
182
|
+
RESEARCH_AGENT_MODEL,
|
|
183
|
+
RESEARCH_AGENT_REASONING_EFFORT,
|
|
184
|
+
assertDiffAllowed,
|
|
185
|
+
buildResearchAgentBranchName,
|
|
186
|
+
buildResearchAgentCommitMessage,
|
|
187
|
+
buildResearchAgentPrBody,
|
|
188
|
+
buildResearchAgentPrTitle,
|
|
189
|
+
getResearchAgentAllowedPathPrefixes,
|
|
190
|
+
getResearchAgentNotePath,
|
|
191
|
+
normalizeDiffOutput,
|
|
192
|
+
parseChangedFilesFromDiff,
|
|
193
|
+
parseGithubRepositoryFromRemote,
|
|
194
|
+
toUpperSnakeCase
|
|
195
|
+
});
|