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