@tradejs/cli 2.0.13 → 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.
@@ -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 import_strategies2 = require("@tradejs/core/strategies");
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 import_strategies3 = require("@tradejs/node/strategies");
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, import_strategies3.getStrategyCreator)(
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, import_strategies3.enrichSignalWithBinanceMarketContext)({
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, import_strategies2.releaseStrategyIndicatorsReplayCache)(keyPrefix);
6596
- (0, import_strategies2.releaseStrategyReplayCache)(keyPrefix);
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(