@tradejs/cli 2.0.13 → 2.0.15
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 +1543 -531
- package/dist/lib/aiTrainResearch.js +18 -3
- package/dist/lib/hyperliquidRateLimiter.js +75 -0
- package/dist/lib/hyperliquidWhaleBackfill.js +435 -0
- package/dist/lib/hyperliquidWhaleData.js +307 -0
- package/dist/lib/hyperliquidWhaleSelection.js +116 -0
- package/dist/lib/hyperliquidWhaleStream.js +326 -0
- package/dist/lib/marketContextPrepare.js +436 -2
- package/dist/lib/runtimeLineage.js +60 -4
- package/dist/scripts/aiTrain.js +18 -3
- package/dist/scripts/backtest.js +438 -0
- package/dist/scripts/hyperliquidWhaleBackfill.js +476 -0
- package/dist/scripts/hyperliquidWhaleIngest.js +333 -0
- package/dist/scripts/hyperliquidWhalesUpdate.js +369 -0
- package/dist/scripts/replay.js +500 -11
- package/dist/scripts/replayRunner.js +500 -11
- package/dist/scripts/runtimeParity.js +433 -0
- package/dist/scripts/signals.js +500 -13
- package/dist/scripts/signalsDaemon.js +500 -13
- package/package.json +12 -9
package/dist/scripts/replay.js
CHANGED
|
@@ -5180,6 +5180,412 @@ var backfillCoinMarketCapContextForBacktest = backfillCoinMarketCapContext;
|
|
|
5180
5180
|
var backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
|
|
5181
5181
|
var backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
|
|
5182
5182
|
|
|
5183
|
+
// src/lib/hyperliquidWhaleBackfill.ts
|
|
5184
|
+
var import_timescale4 = require("@tradejs/infra/timescale");
|
|
5185
|
+
var import_strategies2 = require("@tradejs/node/strategies");
|
|
5186
|
+
|
|
5187
|
+
// src/lib/hyperliquidWhaleData.ts
|
|
5188
|
+
var HYPERLIQUID_WHALE_BUCKET_MS = 6e4;
|
|
5189
|
+
var finiteNumber = (value) => {
|
|
5190
|
+
const numeric = typeof value === "number" ? value : Number(value);
|
|
5191
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
5192
|
+
};
|
|
5193
|
+
var POSITION_EPSILON = 1e-12;
|
|
5194
|
+
var normalizePosition = (value) => Math.abs(value) <= POSITION_EPSILON ? 0 : value;
|
|
5195
|
+
var classifyHyperliquidPositionChange = (params) => {
|
|
5196
|
+
const startValue = finiteNumber(params.startPosition);
|
|
5197
|
+
const size = finiteNumber(params.size);
|
|
5198
|
+
const side = String(params.side ?? "").toUpperCase();
|
|
5199
|
+
if (startValue == null || size == null || size <= 0) return null;
|
|
5200
|
+
if (side !== "B" && side !== "A") return null;
|
|
5201
|
+
const startPosition = normalizePosition(startValue);
|
|
5202
|
+
const endPosition = normalizePosition(
|
|
5203
|
+
startPosition + (side === "B" ? size : -size)
|
|
5204
|
+
);
|
|
5205
|
+
const startLong = Math.max(startPosition, 0);
|
|
5206
|
+
const endLong = Math.max(endPosition, 0);
|
|
5207
|
+
const startShort = Math.max(-startPosition, 0);
|
|
5208
|
+
const endShort = Math.max(-endPosition, 0);
|
|
5209
|
+
const longEntrySize = Math.max(0, endLong - startLong);
|
|
5210
|
+
const shortEntrySize = Math.max(0, endShort - startShort);
|
|
5211
|
+
const longExitSize = Math.max(0, startLong - endLong);
|
|
5212
|
+
const shortExitSize = Math.max(0, startShort - endShort);
|
|
5213
|
+
const flipped = startPosition * endPosition < 0;
|
|
5214
|
+
const opened = startPosition === 0;
|
|
5215
|
+
const closed = endPosition === 0;
|
|
5216
|
+
const increased = Math.sign(startPosition) === Math.sign(endPosition) && Math.abs(endPosition) > Math.abs(startPosition);
|
|
5217
|
+
return {
|
|
5218
|
+
startPosition,
|
|
5219
|
+
endPosition,
|
|
5220
|
+
action: flipped ? "flip" : opened ? "open" : closed ? "close" : increased ? "increase" : "reduce",
|
|
5221
|
+
longEntrySize,
|
|
5222
|
+
shortEntrySize,
|
|
5223
|
+
longExitSize,
|
|
5224
|
+
shortExitSize
|
|
5225
|
+
};
|
|
5226
|
+
};
|
|
5227
|
+
var normalizeAddress = (value) => typeof value === "string" && /^0x[0-9a-fA-F]{40}$/.test(value) ? value.toLowerCase() : null;
|
|
5228
|
+
var normalizeTradeFields = (value) => {
|
|
5229
|
+
const symbol = typeof value.coin === "string" ? value.coin.trim() : "";
|
|
5230
|
+
const price = finiteNumber(value.px);
|
|
5231
|
+
const size = finiteNumber(value.sz);
|
|
5232
|
+
const timestamp = finiteNumber(value.time);
|
|
5233
|
+
const tid = typeof value.tid === "string" || typeof value.tid === "number" ? String(value.tid) : "";
|
|
5234
|
+
if (!symbol || price == null || size == null || timestamp == null || !tid || price <= 0 || size <= 0) {
|
|
5235
|
+
return null;
|
|
5236
|
+
}
|
|
5237
|
+
return {
|
|
5238
|
+
symbol,
|
|
5239
|
+
price,
|
|
5240
|
+
size,
|
|
5241
|
+
timestamp,
|
|
5242
|
+
tid,
|
|
5243
|
+
notionalUsd: price * size
|
|
5244
|
+
};
|
|
5245
|
+
};
|
|
5246
|
+
var normalizeHyperliquidUserFill = (params) => {
|
|
5247
|
+
const normalized = normalizeTradeFields(params.fill);
|
|
5248
|
+
if (!normalized || !params.trackedSymbols.has(normalized.symbol)) return null;
|
|
5249
|
+
const address = normalizeAddress(params.address);
|
|
5250
|
+
if (!address) return null;
|
|
5251
|
+
const side = String(params.fill.side ?? "").toUpperCase();
|
|
5252
|
+
if (side !== "B" && side !== "A") return null;
|
|
5253
|
+
const positionChange = classifyHyperliquidPositionChange({
|
|
5254
|
+
startPosition: params.fill.startPosition,
|
|
5255
|
+
side,
|
|
5256
|
+
size: normalized.size
|
|
5257
|
+
});
|
|
5258
|
+
const closedPnl = finiteNumber(params.fill.closedPnl);
|
|
5259
|
+
const liquidation = params.fill.liquidation != null && typeof params.fill.liquidation === "object";
|
|
5260
|
+
const positionFields = positionChange ? side === "B" ? {
|
|
5261
|
+
buyerStartPosition: positionChange.startPosition,
|
|
5262
|
+
buyerEndPosition: positionChange.endPosition,
|
|
5263
|
+
buyerPositionAction: positionChange.action,
|
|
5264
|
+
buyerClosedPnl: closedPnl,
|
|
5265
|
+
buyerLiquidation: liquidation
|
|
5266
|
+
} : {
|
|
5267
|
+
sellerStartPosition: positionChange.startPosition,
|
|
5268
|
+
sellerEndPosition: positionChange.endPosition,
|
|
5269
|
+
sellerPositionAction: positionChange.action,
|
|
5270
|
+
sellerClosedPnl: closedPnl,
|
|
5271
|
+
sellerLiquidation: liquidation
|
|
5272
|
+
} : {};
|
|
5273
|
+
return {
|
|
5274
|
+
symbol: normalized.symbol,
|
|
5275
|
+
ts: new Date(normalized.timestamp),
|
|
5276
|
+
tid: normalized.tid,
|
|
5277
|
+
price: normalized.price,
|
|
5278
|
+
size: normalized.size,
|
|
5279
|
+
notionalUsd: normalized.notionalUsd,
|
|
5280
|
+
buyerAddress: side === "B" ? address : null,
|
|
5281
|
+
sellerAddress: side === "A" ? address : null,
|
|
5282
|
+
buyerTracked: side === "B",
|
|
5283
|
+
sellerTracked: side === "A",
|
|
5284
|
+
...positionFields,
|
|
5285
|
+
...params.identity,
|
|
5286
|
+
source: "hyperliquid_user_fills"
|
|
5287
|
+
};
|
|
5288
|
+
};
|
|
5289
|
+
|
|
5290
|
+
// src/lib/hyperliquidRateLimiter.ts
|
|
5291
|
+
var DEFAULT_WEIGHT_PER_MINUTE = 1e3;
|
|
5292
|
+
var WINDOW_MS = 6e4;
|
|
5293
|
+
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
5294
|
+
var HyperliquidInfoRateLimiter = class {
|
|
5295
|
+
constructor(capacity = DEFAULT_WEIGHT_PER_MINUTE, now = Date.now, wait = sleep2) {
|
|
5296
|
+
this.capacity = capacity;
|
|
5297
|
+
this.now = now;
|
|
5298
|
+
this.wait = wait;
|
|
5299
|
+
this.reservations = [];
|
|
5300
|
+
this.queue = Promise.resolve();
|
|
5301
|
+
}
|
|
5302
|
+
reserve(weight) {
|
|
5303
|
+
const normalizedWeight = Math.max(1, Math.ceil(weight));
|
|
5304
|
+
const pending = this.queue.then(
|
|
5305
|
+
() => this.reserveInternal(normalizedWeight)
|
|
5306
|
+
);
|
|
5307
|
+
this.queue = pending.catch(() => void 0);
|
|
5308
|
+
return pending;
|
|
5309
|
+
}
|
|
5310
|
+
async reserveInternal(weight) {
|
|
5311
|
+
if (weight > this.capacity) {
|
|
5312
|
+
throw new Error(
|
|
5313
|
+
`Hyperliquid request weight ${weight} exceeds limiter capacity ${this.capacity}`
|
|
5314
|
+
);
|
|
5315
|
+
}
|
|
5316
|
+
for (; ; ) {
|
|
5317
|
+
const nowMs = this.now();
|
|
5318
|
+
this.reservations = this.reservations.filter(
|
|
5319
|
+
(reservation) => reservation.atMs > nowMs - WINDOW_MS
|
|
5320
|
+
);
|
|
5321
|
+
const used = this.reservations.reduce(
|
|
5322
|
+
(sum, reservation) => sum + reservation.weight,
|
|
5323
|
+
0
|
|
5324
|
+
);
|
|
5325
|
+
if (used + weight <= this.capacity) {
|
|
5326
|
+
this.reservations.push({ atMs: nowMs, weight });
|
|
5327
|
+
return;
|
|
5328
|
+
}
|
|
5329
|
+
const oldest = this.reservations[0];
|
|
5330
|
+
await this.wait(Math.max(1, oldest.atMs + WINDOW_MS - nowMs));
|
|
5331
|
+
}
|
|
5332
|
+
}
|
|
5333
|
+
};
|
|
5334
|
+
var hyperliquidInfoResponseWeight = (rows) => Math.max(0, Math.ceil(Math.max(0, rows) / 20));
|
|
5335
|
+
|
|
5336
|
+
// src/lib/hyperliquidWhaleBackfill.ts
|
|
5337
|
+
var DEFAULT_BASE_URL = "https://api.hyperliquid.xyz";
|
|
5338
|
+
var DEFAULT_CONCURRENCY = 2;
|
|
5339
|
+
var DEFAULT_RATE_LIMIT_WEIGHT = 1e3;
|
|
5340
|
+
var MAX_PAGE_SIZE = 2e3;
|
|
5341
|
+
var MAX_PAGES_PER_WHALE = 5;
|
|
5342
|
+
var REQUEST_WEIGHT = 20;
|
|
5343
|
+
var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
5344
|
+
var positiveInt = (value, fallback) => {
|
|
5345
|
+
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
5346
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
5347
|
+
};
|
|
5348
|
+
var minuteCeil = (value) => Math.ceil(value / HYPERLIQUID_WHALE_BUCKET_MS) * HYPERLIQUID_WHALE_BUCKET_MS;
|
|
5349
|
+
var nextMinute = (value) => (Math.floor(value / HYPERLIQUID_WHALE_BUCKET_MS) + 1) * HYPERLIQUID_WHALE_BUCKET_MS;
|
|
5350
|
+
var fetchHyperliquidUserFillsByTime = async (params) => {
|
|
5351
|
+
const fetchImpl = params.fetchImpl ?? fetch;
|
|
5352
|
+
const limiter = params.rateLimiter ?? new HyperliquidInfoRateLimiter(
|
|
5353
|
+
positiveInt(
|
|
5354
|
+
process.env.HYPERLIQUID_WHALE_RATE_LIMIT_WEIGHT,
|
|
5355
|
+
DEFAULT_RATE_LIMIT_WEIGHT
|
|
5356
|
+
),
|
|
5357
|
+
Date.now,
|
|
5358
|
+
params.wait ?? sleep3
|
|
5359
|
+
);
|
|
5360
|
+
const fills = [];
|
|
5361
|
+
let cursor = params.startTime;
|
|
5362
|
+
for (let page = 0; page < (params.maxPages ?? MAX_PAGES_PER_WHALE) && cursor <= params.endTime; page += 1) {
|
|
5363
|
+
await limiter.reserve(REQUEST_WEIGHT);
|
|
5364
|
+
const response = await fetchImpl(
|
|
5365
|
+
`${process.env.HYPERLIQUID_API_BASE_URL?.trim() || DEFAULT_BASE_URL}/info`,
|
|
5366
|
+
{
|
|
5367
|
+
method: "POST",
|
|
5368
|
+
headers: { "content-type": "application/json" },
|
|
5369
|
+
body: JSON.stringify({
|
|
5370
|
+
type: "userFillsByTime",
|
|
5371
|
+
user: params.address,
|
|
5372
|
+
startTime: cursor,
|
|
5373
|
+
endTime: params.endTime,
|
|
5374
|
+
aggregateByTime: true
|
|
5375
|
+
})
|
|
5376
|
+
}
|
|
5377
|
+
);
|
|
5378
|
+
if (!response.ok) {
|
|
5379
|
+
throw new Error(
|
|
5380
|
+
`Hyperliquid userFillsByTime ${response.status}: ${await response.text()}`
|
|
5381
|
+
);
|
|
5382
|
+
}
|
|
5383
|
+
const raw = await response.json();
|
|
5384
|
+
const pageRows = Array.isArray(raw) ? raw : [];
|
|
5385
|
+
const responseWeight = hyperliquidInfoResponseWeight(pageRows.length);
|
|
5386
|
+
if (responseWeight > 0) await limiter.reserve(responseWeight);
|
|
5387
|
+
fills.push(...pageRows);
|
|
5388
|
+
if (pageRows.length < MAX_PAGE_SIZE) {
|
|
5389
|
+
return { fills, truncated: false, coveredFromMs: params.startTime };
|
|
5390
|
+
}
|
|
5391
|
+
const lastTime = Math.max(
|
|
5392
|
+
...pageRows.map((row) => Number(row.time)).filter(Number.isFinite)
|
|
5393
|
+
);
|
|
5394
|
+
if (!Number.isFinite(lastTime) || lastTime < cursor) {
|
|
5395
|
+
throw new Error(
|
|
5396
|
+
`Hyperliquid fills pagination stalled for ${params.address}`
|
|
5397
|
+
);
|
|
5398
|
+
}
|
|
5399
|
+
cursor = lastTime + 1;
|
|
5400
|
+
}
|
|
5401
|
+
if (cursor > params.endTime) {
|
|
5402
|
+
return { fills, truncated: false, coveredFromMs: params.startTime };
|
|
5403
|
+
}
|
|
5404
|
+
const firstAvailableTime = Math.min(
|
|
5405
|
+
...fills.map((fill) => Number(fill.time)).filter(Number.isFinite)
|
|
5406
|
+
);
|
|
5407
|
+
return {
|
|
5408
|
+
fills,
|
|
5409
|
+
truncated: true,
|
|
5410
|
+
coveredFromMs: Math.min(params.endTime + 1, nextMinute(firstAvailableTime))
|
|
5411
|
+
};
|
|
5412
|
+
};
|
|
5413
|
+
var runConcurrent = async (params) => {
|
|
5414
|
+
let nextIndex = 0;
|
|
5415
|
+
await Promise.all(
|
|
5416
|
+
Array.from(
|
|
5417
|
+
{ length: Math.min(params.concurrency, params.items.length) },
|
|
5418
|
+
async () => {
|
|
5419
|
+
for (; ; ) {
|
|
5420
|
+
const index = nextIndex;
|
|
5421
|
+
nextIndex += 1;
|
|
5422
|
+
if (index >= params.items.length) return;
|
|
5423
|
+
await params.worker(params.items[index], index);
|
|
5424
|
+
}
|
|
5425
|
+
}
|
|
5426
|
+
)
|
|
5427
|
+
);
|
|
5428
|
+
};
|
|
5429
|
+
var backfillHyperliquidWhaleContext = async (params) => {
|
|
5430
|
+
const universe = (0, import_strategies2.getHyperliquidPerpUniverseSnapshot)();
|
|
5431
|
+
const whales = (0, import_strategies2.getHyperliquidWhaleRegistrySnapshot)();
|
|
5432
|
+
const trackedSymbols = new Set(universe.symbols);
|
|
5433
|
+
const identity = {
|
|
5434
|
+
universeFingerprint: universe.fingerprint,
|
|
5435
|
+
whaleRegistryFingerprint: whales.fingerprint
|
|
5436
|
+
};
|
|
5437
|
+
const fromMs = Math.floor(params.startMs / HYPERLIQUID_WHALE_BUCKET_MS) * HYPERLIQUID_WHALE_BUCKET_MS;
|
|
5438
|
+
const toMs = minuteCeil(params.endMs);
|
|
5439
|
+
const cached = await (0, import_timescale4.hasHyperliquidWhaleBackfillCoverage)({
|
|
5440
|
+
fromMs,
|
|
5441
|
+
toMs,
|
|
5442
|
+
...identity
|
|
5443
|
+
});
|
|
5444
|
+
const emptyResult = () => ({
|
|
5445
|
+
cached,
|
|
5446
|
+
cacheOnly: params.cacheOnly,
|
|
5447
|
+
fills: 0,
|
|
5448
|
+
events: 0,
|
|
5449
|
+
buckets: 0,
|
|
5450
|
+
coverageBuckets: 0,
|
|
5451
|
+
complete: cached,
|
|
5452
|
+
walletsProcessed: 0,
|
|
5453
|
+
walletsCached: 0,
|
|
5454
|
+
completeWallets: 0,
|
|
5455
|
+
truncatedWallets: 0,
|
|
5456
|
+
failedWallets: 0,
|
|
5457
|
+
fromMs,
|
|
5458
|
+
toMs
|
|
5459
|
+
});
|
|
5460
|
+
if (cached || params.cacheOnly) return emptyResult();
|
|
5461
|
+
const limiter = new HyperliquidInfoRateLimiter(
|
|
5462
|
+
positiveInt(
|
|
5463
|
+
process.env.HYPERLIQUID_WHALE_RATE_LIMIT_WEIGHT,
|
|
5464
|
+
DEFAULT_RATE_LIMIT_WEIGHT
|
|
5465
|
+
),
|
|
5466
|
+
Date.now,
|
|
5467
|
+
params.wait ?? sleep3
|
|
5468
|
+
);
|
|
5469
|
+
const concurrency = Math.min(
|
|
5470
|
+
4,
|
|
5471
|
+
positiveInt(
|
|
5472
|
+
params.concurrency ?? process.env.HYPERLIQUID_WHALE_CONCURRENCY,
|
|
5473
|
+
DEFAULT_CONCURRENCY
|
|
5474
|
+
)
|
|
5475
|
+
);
|
|
5476
|
+
let fills = 0;
|
|
5477
|
+
let events = 0;
|
|
5478
|
+
let walletsProcessed = 0;
|
|
5479
|
+
let walletsCached = 0;
|
|
5480
|
+
let completeWallets = 0;
|
|
5481
|
+
let truncatedWallets = 0;
|
|
5482
|
+
let failedWallets = 0;
|
|
5483
|
+
const failures = [];
|
|
5484
|
+
await runConcurrent({
|
|
5485
|
+
items: whales.addresses,
|
|
5486
|
+
concurrency,
|
|
5487
|
+
worker: async (address) => {
|
|
5488
|
+
const previous = await (0, import_timescale4.getHyperliquidWhaleWalletCoverage)({
|
|
5489
|
+
address,
|
|
5490
|
+
fromMs,
|
|
5491
|
+
toMs,
|
|
5492
|
+
...identity
|
|
5493
|
+
});
|
|
5494
|
+
if (previous && previous.status !== "failed") {
|
|
5495
|
+
walletsCached += 1;
|
|
5496
|
+
if (previous.status === "complete") completeWallets += 1;
|
|
5497
|
+
else truncatedWallets += 1;
|
|
5498
|
+
} else {
|
|
5499
|
+
try {
|
|
5500
|
+
const result2 = await fetchHyperliquidUserFillsByTime({
|
|
5501
|
+
address,
|
|
5502
|
+
startTime: fromMs,
|
|
5503
|
+
endTime: toMs - 1,
|
|
5504
|
+
fetchImpl: params.fetchImpl,
|
|
5505
|
+
rateLimiter: limiter
|
|
5506
|
+
});
|
|
5507
|
+
const addressEvents = result2.fills.map(
|
|
5508
|
+
(fill) => normalizeHyperliquidUserFill({
|
|
5509
|
+
fill,
|
|
5510
|
+
address,
|
|
5511
|
+
trackedSymbols,
|
|
5512
|
+
identity
|
|
5513
|
+
})
|
|
5514
|
+
).filter((row) => row != null);
|
|
5515
|
+
await (0, import_timescale4.upsertHyperliquidWhaleTradeEvents)(addressEvents);
|
|
5516
|
+
await (0, import_timescale4.upsertHyperliquidWhaleWalletCoverage)({
|
|
5517
|
+
address,
|
|
5518
|
+
fromMs,
|
|
5519
|
+
toMs,
|
|
5520
|
+
coveredFromMs: result2.coveredFromMs,
|
|
5521
|
+
coveredToMs: toMs,
|
|
5522
|
+
status: result2.truncated ? "truncated" : "complete",
|
|
5523
|
+
fillsCount: result2.fills.length,
|
|
5524
|
+
...identity
|
|
5525
|
+
});
|
|
5526
|
+
fills += result2.fills.length;
|
|
5527
|
+
events += addressEvents.length;
|
|
5528
|
+
if (result2.truncated) truncatedWallets += 1;
|
|
5529
|
+
else completeWallets += 1;
|
|
5530
|
+
} catch (error) {
|
|
5531
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
5532
|
+
failures.push(normalizedError);
|
|
5533
|
+
failedWallets += 1;
|
|
5534
|
+
await (0, import_timescale4.upsertHyperliquidWhaleWalletCoverage)({
|
|
5535
|
+
address,
|
|
5536
|
+
fromMs,
|
|
5537
|
+
toMs,
|
|
5538
|
+
coveredFromMs: null,
|
|
5539
|
+
coveredToMs: null,
|
|
5540
|
+
status: "failed",
|
|
5541
|
+
fillsCount: 0,
|
|
5542
|
+
error: normalizedError.message,
|
|
5543
|
+
...identity
|
|
5544
|
+
});
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
walletsProcessed += 1;
|
|
5548
|
+
params.log?.(
|
|
5549
|
+
`Hyperliquid whales ${walletsProcessed}/${whales.addresses.length}: complete=${completeWallets}, truncated=${truncatedWallets}, failed=${failedWallets}, fills=${fills}, events=${events}`
|
|
5550
|
+
);
|
|
5551
|
+
}
|
|
5552
|
+
});
|
|
5553
|
+
const buckets = await (0, import_timescale4.rebuildHyperliquidWhaleFlowRows)({
|
|
5554
|
+
fromMs,
|
|
5555
|
+
toMs,
|
|
5556
|
+
...identity
|
|
5557
|
+
});
|
|
5558
|
+
const coverageBuckets = await (0, import_timescale4.rebuildHyperliquidWhaleCoverageRows)({
|
|
5559
|
+
fromMs,
|
|
5560
|
+
toMs,
|
|
5561
|
+
expectedWhales: whales.addresses.length,
|
|
5562
|
+
...identity
|
|
5563
|
+
});
|
|
5564
|
+
const result = {
|
|
5565
|
+
cached: false,
|
|
5566
|
+
cacheOnly: false,
|
|
5567
|
+
fills,
|
|
5568
|
+
events,
|
|
5569
|
+
buckets,
|
|
5570
|
+
coverageBuckets,
|
|
5571
|
+
complete: truncatedWallets === 0 && failedWallets === 0,
|
|
5572
|
+
walletsProcessed,
|
|
5573
|
+
walletsCached,
|
|
5574
|
+
completeWallets,
|
|
5575
|
+
truncatedWallets,
|
|
5576
|
+
failedWallets,
|
|
5577
|
+
fromMs,
|
|
5578
|
+
toMs
|
|
5579
|
+
};
|
|
5580
|
+
if (params.strict !== false && failures.length) {
|
|
5581
|
+
throw new AggregateError(
|
|
5582
|
+
failures,
|
|
5583
|
+
`Hyperliquid whale backfill failed for ${failures.length}/${whales.addresses.length} wallets`
|
|
5584
|
+
);
|
|
5585
|
+
}
|
|
5586
|
+
return result;
|
|
5587
|
+
};
|
|
5588
|
+
|
|
5183
5589
|
// src/lib/marketContextPrepare.ts
|
|
5184
5590
|
var shouldPrepareDerivativesContextForRun = (params) => {
|
|
5185
5591
|
if (params.universe === "tradfi") return false;
|
|
@@ -5230,6 +5636,20 @@ var shouldPrepareCoinMarketCapContextForRun = (params) => {
|
|
|
5230
5636
|
cacheOnly: params.cacheOnly
|
|
5231
5637
|
});
|
|
5232
5638
|
};
|
|
5639
|
+
var shouldPrepareHyperliquidWhaleContextForRun = (params) => {
|
|
5640
|
+
const strategyRequiresContext = params.strategyNames?.includes(
|
|
5641
|
+
"HyperliquidConsensus"
|
|
5642
|
+
);
|
|
5643
|
+
if (params.universe === "tradfi" || params.mode === "signals" || params.cacheOnly || !params.aiEnabled && !params.mlEnabled && !strategyRequiresContext) {
|
|
5644
|
+
return false;
|
|
5645
|
+
}
|
|
5646
|
+
const configured = String(
|
|
5647
|
+
process.env.HYPERLIQUID_WHALE_BACKFILL_ENABLED ?? ""
|
|
5648
|
+
).trim().toLowerCase();
|
|
5649
|
+
if (["0", "false", "no", "off"].includes(configured)) return false;
|
|
5650
|
+
if (strategyRequiresContext) return true;
|
|
5651
|
+
return ["1", "true", "yes", "on"].includes(configured);
|
|
5652
|
+
};
|
|
5233
5653
|
var resolveCoinMarketCapBackfillForMode = (mode) => mode === "backtest" ? backfillCoinMarketCapContextForBacktest : mode === "signals" ? backfillCoinMarketCapContextForSignals : backfillCoinMarketCapContextForReplay;
|
|
5234
5654
|
var buildCoinMarketCapBackfillParams = (params) => ({
|
|
5235
5655
|
userName: params.userName,
|
|
@@ -5266,6 +5686,18 @@ var prepareMarketContextForRun = async (params) => {
|
|
|
5266
5686
|
)
|
|
5267
5687
|
);
|
|
5268
5688
|
}
|
|
5689
|
+
if (shouldPrepareHyperliquidWhaleContextForRun(params)) {
|
|
5690
|
+
await timeOperation3(
|
|
5691
|
+
"hyperliquid whale context backfill",
|
|
5692
|
+
() => backfillHyperliquidWhaleContext({
|
|
5693
|
+
startMs: params.preloadStartMs ?? params.startMs,
|
|
5694
|
+
endMs: params.endMs,
|
|
5695
|
+
cacheOnly: params.cacheOnly,
|
|
5696
|
+
strict: false,
|
|
5697
|
+
log
|
|
5698
|
+
})
|
|
5699
|
+
);
|
|
5700
|
+
}
|
|
5269
5701
|
if (shouldPrepareBinanceMarketContextForRun(params)) {
|
|
5270
5702
|
await timeOperation3("binance market context backfill", () => {
|
|
5271
5703
|
const backfill = params.mode === "backtest" ? backfillBinanceMarketContextForBacktest : params.mode === "signals" ? backfillBinanceMarketContextForSignals : backfillBinanceMarketContextForReplay;
|
|
@@ -5554,13 +5986,13 @@ var compactReplaySignal = (signal) => {
|
|
|
5554
5986
|
var import_chalk9 = __toESM(require("chalk"));
|
|
5555
5987
|
var import_progress3 = __toESM(require("progress"));
|
|
5556
5988
|
var import_backtest = require("@tradejs/core/backtest");
|
|
5557
|
-
var
|
|
5989
|
+
var import_strategies4 = require("@tradejs/core/strategies");
|
|
5558
5990
|
var import_time3 = require("@tradejs/core/time");
|
|
5559
5991
|
var import_logger = require("@tradejs/infra/logger");
|
|
5560
5992
|
var import_redis3 = require("@tradejs/infra/redis");
|
|
5561
5993
|
var import_connectors6 = require("@tradejs/node/connectors");
|
|
5562
5994
|
var import_cli4 = require("@tradejs/node/cli");
|
|
5563
|
-
var
|
|
5995
|
+
var import_strategies5 = require("@tradejs/node/strategies");
|
|
5564
5996
|
|
|
5565
5997
|
// src/lib/replay/portfolioReplayConnector.ts
|
|
5566
5998
|
var import_constants3 = require("@tradejs/core/constants");
|
|
@@ -6015,6 +6447,8 @@ var import_node_child_process = require("child_process");
|
|
|
6015
6447
|
var import_node_crypto = require("crypto");
|
|
6016
6448
|
var import_promises = __toESM(require("fs/promises"));
|
|
6017
6449
|
var import_node_path = __toESM(require("path"));
|
|
6450
|
+
var import_types2 = require("@tradejs/types");
|
|
6451
|
+
var import_strategies3 = require("@tradejs/node/strategies");
|
|
6018
6452
|
var RUNTIME_CONTEXT_ENV_KEYS = [
|
|
6019
6453
|
"AI_MODE",
|
|
6020
6454
|
"MIN_AI_QUALITY",
|
|
@@ -6032,7 +6466,12 @@ var RUNTIME_CONTEXT_ENV_KEYS = [
|
|
|
6032
6466
|
"COINMARKETCAP_CONTEXT_HISTORICAL_ACCESS_MONTHS",
|
|
6033
6467
|
"COINMARKETCAP_CONTEXT_BACKFILL_ENABLED",
|
|
6034
6468
|
"COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
|
|
6035
|
-
"COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS"
|
|
6469
|
+
"COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS",
|
|
6470
|
+
"HYPERLIQUID_WHALE_CONTEXT_ENABLED",
|
|
6471
|
+
"HYPERLIQUID_WHALE_BACKFILL_ENABLED",
|
|
6472
|
+
"HYPERLIQUID_WHALE_MIN_COVERAGE_PCT",
|
|
6473
|
+
"HYPERLIQUID_WHALE_CONCURRENCY",
|
|
6474
|
+
"HYPERLIQUID_WHALE_RATE_LIMIT_WEIGHT"
|
|
6036
6475
|
];
|
|
6037
6476
|
var RUNTIME_CONTEXT_DATA_MODEL = {
|
|
6038
6477
|
derivativesSourceIntervals: "15m",
|
|
@@ -6042,7 +6481,11 @@ var RUNTIME_CONTEXT_DATA_MODEL = {
|
|
|
6042
6481
|
coinMarketCapAvailabilityModel: "closed-interval-all-daily",
|
|
6043
6482
|
coinMarketCapDataModelVersion: 3,
|
|
6044
6483
|
binanceBreadthUniverseModel: "fixed-versioned-top5-top10-top30-top50-top100",
|
|
6045
|
-
binanceBreadthDataModelVersion: 2
|
|
6484
|
+
binanceBreadthDataModelVersion: 2,
|
|
6485
|
+
hyperliquidWhaleCanonicalInterval: "1m",
|
|
6486
|
+
hyperliquidWhaleGateMinNotionalUsd: 5e4,
|
|
6487
|
+
hyperliquidWhaleMinCoveragePct: 0.8,
|
|
6488
|
+
hyperliquidWhaleDataModelVersion: import_types2.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
6046
6489
|
};
|
|
6047
6490
|
var normalizeForStableJson = (value) => {
|
|
6048
6491
|
if (Array.isArray(value)) {
|
|
@@ -6055,10 +6498,24 @@ var normalizeForStableJson = (value) => {
|
|
|
6055
6498
|
}
|
|
6056
6499
|
return value;
|
|
6057
6500
|
};
|
|
6501
|
+
var resolveRuntimeMaxLossValue = (config) => {
|
|
6502
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
6503
|
+
return null;
|
|
6504
|
+
}
|
|
6505
|
+
const strategyConfig = config.strategyConfig;
|
|
6506
|
+
if (!strategyConfig || typeof strategyConfig !== "object" || Array.isArray(strategyConfig)) {
|
|
6507
|
+
return null;
|
|
6508
|
+
}
|
|
6509
|
+
const maxLossValue = Number(
|
|
6510
|
+
strategyConfig.MAX_LOSS_VALUE
|
|
6511
|
+
);
|
|
6512
|
+
return Number.isFinite(maxLossValue) ? maxLossValue : null;
|
|
6513
|
+
};
|
|
6058
6514
|
var fingerprintRuntimeValue = (value) => (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
|
|
6059
6515
|
var gitLineageCache = /* @__PURE__ */ new Map();
|
|
6060
6516
|
var gateFingerprintCache = /* @__PURE__ */ new Map();
|
|
6061
6517
|
var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
|
|
6518
|
+
var snapshotFingerprintCache = /* @__PURE__ */ new Map();
|
|
6062
6519
|
var resolveGitLineage = (projectRoot) => {
|
|
6063
6520
|
const cached = gitLineageCache.get(projectRoot);
|
|
6064
6521
|
if (cached) {
|
|
@@ -6132,6 +6589,24 @@ var resolveBinanceBreadthFingerprint = async (projectRoot) => {
|
|
|
6132
6589
|
binanceBreadthFingerprintCache.set(projectRoot, result);
|
|
6133
6590
|
return result;
|
|
6134
6591
|
};
|
|
6592
|
+
var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
|
|
6593
|
+
const cacheKey = `${projectRoot}:${relativePath}`;
|
|
6594
|
+
if (snapshotFingerprintCache.has(cacheKey)) {
|
|
6595
|
+
return snapshotFingerprintCache.get(cacheKey) ?? null;
|
|
6596
|
+
}
|
|
6597
|
+
const content = await readOptionalFile(import_node_path.default.join(projectRoot, relativePath));
|
|
6598
|
+
let result = null;
|
|
6599
|
+
if (content) {
|
|
6600
|
+
try {
|
|
6601
|
+
const parsed = JSON.parse(content.toString("utf8"));
|
|
6602
|
+
result = typeof parsed.fingerprint === "string" && parsed.fingerprint.trim() ? parsed.fingerprint.trim() : null;
|
|
6603
|
+
} catch {
|
|
6604
|
+
result = null;
|
|
6605
|
+
}
|
|
6606
|
+
}
|
|
6607
|
+
snapshotFingerprintCache.set(cacheKey, result);
|
|
6608
|
+
return result;
|
|
6609
|
+
};
|
|
6135
6610
|
var resolveGateFingerprint = async ({
|
|
6136
6611
|
projectRoot,
|
|
6137
6612
|
strategyName,
|
|
@@ -6147,7 +6622,8 @@ var resolveGateFingerprint = async ({
|
|
|
6147
6622
|
`packages/strategies/src/${strategyName}/guardrails.ts`,
|
|
6148
6623
|
`packages/strategies/src/${strategyName}/pockets.ts`,
|
|
6149
6624
|
`packages/strategies/src/${strategyName}/config.ts`,
|
|
6150
|
-
"packages/node/src/ai.ts"
|
|
6625
|
+
"packages/node/src/ai.ts",
|
|
6626
|
+
"packages/core/src/utils/strategyHelpers/signalBuilders.ts"
|
|
6151
6627
|
];
|
|
6152
6628
|
const optionalEntries = await Promise.all(
|
|
6153
6629
|
relativeCandidates.map(async (relativePath) => {
|
|
@@ -6185,12 +6661,22 @@ var buildRuntimeLineage = async ({
|
|
|
6185
6661
|
env = process.env
|
|
6186
6662
|
}) => {
|
|
6187
6663
|
const git = resolveGitLineage(projectRoot);
|
|
6664
|
+
const hyperliquidPerpUniverseFingerprint = typeof import_strategies3.getHyperliquidPerpUniverseSnapshot === "function" ? (0, import_strategies3.getHyperliquidPerpUniverseSnapshot)().fingerprint : await resolveSnapshotFingerprint(
|
|
6665
|
+
projectRoot,
|
|
6666
|
+
"packages/node/src/config/hyperliquidPerpUniverse.json"
|
|
6667
|
+
);
|
|
6668
|
+
const hyperliquidWhaleRegistryFingerprint = typeof import_strategies3.getHyperliquidWhaleRegistrySnapshot === "function" ? (0, import_strategies3.getHyperliquidWhaleRegistrySnapshot)().fingerprint : await resolveSnapshotFingerprint(
|
|
6669
|
+
projectRoot,
|
|
6670
|
+
"packages/node/src/config/hyperliquidWhales.json"
|
|
6671
|
+
);
|
|
6188
6672
|
const context = {
|
|
6189
6673
|
...Object.fromEntries(
|
|
6190
6674
|
RUNTIME_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
|
|
6191
6675
|
),
|
|
6192
6676
|
...RUNTIME_CONTEXT_DATA_MODEL,
|
|
6193
6677
|
binanceBreadthUniverseFingerprint: await resolveBinanceBreadthFingerprint(projectRoot),
|
|
6678
|
+
hyperliquidPerpUniverseFingerprint,
|
|
6679
|
+
hyperliquidWhaleRegistryFingerprint,
|
|
6194
6680
|
...runContext
|
|
6195
6681
|
};
|
|
6196
6682
|
return {
|
|
@@ -6202,7 +6688,8 @@ var buildRuntimeLineage = async ({
|
|
|
6202
6688
|
gitSha: git.gitSha
|
|
6203
6689
|
}),
|
|
6204
6690
|
configFingerprint: fingerprintRuntimeValue(config),
|
|
6205
|
-
contextFingerprint: fingerprintRuntimeValue(context)
|
|
6691
|
+
contextFingerprint: fingerprintRuntimeValue(context),
|
|
6692
|
+
maxLossValue: resolveRuntimeMaxLossValue(config)
|
|
6206
6693
|
};
|
|
6207
6694
|
};
|
|
6208
6695
|
var runtimeLineageKey = (lineage) => [
|
|
@@ -6219,7 +6706,7 @@ var runtimeLineagesMatch = (left, right) => left != null && right != null && run
|
|
|
6219
6706
|
var loadRuntimeStrategies = async (runtimeStrategies) => {
|
|
6220
6707
|
const strategies = await Promise.all(
|
|
6221
6708
|
runtimeStrategies.map(async ({ strategyName, strategyConfig }) => {
|
|
6222
|
-
const strategyCreator = await (0,
|
|
6709
|
+
const strategyCreator = await (0, import_strategies5.getStrategyCreator)(
|
|
6223
6710
|
strategyName,
|
|
6224
6711
|
replayProjectRoot
|
|
6225
6712
|
);
|
|
@@ -6565,7 +7052,7 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6565
7052
|
);
|
|
6566
7053
|
if (result && typeof result !== "string") {
|
|
6567
7054
|
result.runtimeLineage = strategyRuntime.runtimeLineage;
|
|
6568
|
-
await (0,
|
|
7055
|
+
await (0, import_strategies5.enrichSignalWithBinanceMarketContext)({
|
|
6569
7056
|
signal: result,
|
|
6570
7057
|
env: "PARITY"
|
|
6571
7058
|
});
|
|
@@ -6589,8 +7076,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6589
7076
|
}
|
|
6590
7077
|
} finally {
|
|
6591
7078
|
for (const keyPrefix of sharedReplayKeyPrefixes) {
|
|
6592
|
-
(0,
|
|
6593
|
-
(0,
|
|
7079
|
+
(0, import_strategies4.releaseStrategyIndicatorsReplayCache)(keyPrefix);
|
|
7080
|
+
(0, import_strategies4.releaseStrategyReplayCache)(keyPrefix);
|
|
6594
7081
|
}
|
|
6595
7082
|
}
|
|
6596
7083
|
const artifacts = replayConnector.getReplayArtifacts();
|
|
@@ -9626,6 +10113,7 @@ var prepareReplayBinanceMarketContext = async (preparedRun) => {
|
|
|
9626
10113
|
cacheOnly: Boolean(replayFlags.cacheOnly),
|
|
9627
10114
|
aiEnabled: preparedRun.aiEnabled,
|
|
9628
10115
|
mlEnabled: preparedRun.mlEnabled,
|
|
10116
|
+
strategyNames: preparedRun.strategyNames,
|
|
9629
10117
|
log: (message) => console.log(import_chalk12.default.gray(message))
|
|
9630
10118
|
});
|
|
9631
10119
|
};
|
|
@@ -9763,7 +10251,8 @@ var replayBacktest = async () => {
|
|
|
9763
10251
|
),
|
|
9764
10252
|
mlEnabled: replayStrategies.some(
|
|
9765
10253
|
({ strategyConfig }) => Boolean(strategyConfig.ML_ENABLED)
|
|
9766
|
-
)
|
|
10254
|
+
),
|
|
10255
|
+
strategyNames: replayStrategies.map(({ strategyName }) => strategyName)
|
|
9767
10256
|
});
|
|
9768
10257
|
console.log(import_chalk12.default.yellow(`tickers: ${preparedRun.tickers.length}`));
|
|
9769
10258
|
console.log(
|