@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.
@@ -1650,9 +1650,9 @@ var import_chalk7 = __toESM(require("chalk"));
1650
1650
  var import_connectors6 = require("@tradejs/node/connectors");
1651
1651
  var import_cli2 = require("@tradejs/node/cli");
1652
1652
  var import_async3 = require("@tradejs/core/async");
1653
- var import_strategies3 = require("@tradejs/core/strategies");
1653
+ var import_strategies5 = require("@tradejs/core/strategies");
1654
1654
  var import_constants3 = require("@tradejs/core/constants");
1655
- var import_strategies4 = require("@tradejs/node/strategies");
1655
+ var import_strategies6 = require("@tradejs/node/strategies");
1656
1656
  var import_time3 = require("@tradejs/core/time");
1657
1657
  var import_logger4 = require("@tradejs/infra/logger");
1658
1658
  var import_redis5 = require("@tradejs/infra/redis");
@@ -4654,6 +4654,412 @@ var backfillCoinMarketCapContextForBacktest = backfillCoinMarketCapContext;
4654
4654
  var backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
4655
4655
  var backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
4656
4656
 
4657
+ // src/lib/hyperliquidWhaleBackfill.ts
4658
+ var import_timescale4 = require("@tradejs/infra/timescale");
4659
+ var import_strategies2 = require("@tradejs/node/strategies");
4660
+
4661
+ // src/lib/hyperliquidWhaleData.ts
4662
+ var HYPERLIQUID_WHALE_BUCKET_MS = 6e4;
4663
+ var finiteNumber = (value) => {
4664
+ const numeric = typeof value === "number" ? value : Number(value);
4665
+ return Number.isFinite(numeric) ? numeric : null;
4666
+ };
4667
+ var POSITION_EPSILON = 1e-12;
4668
+ var normalizePosition = (value) => Math.abs(value) <= POSITION_EPSILON ? 0 : value;
4669
+ var classifyHyperliquidPositionChange = (params) => {
4670
+ const startValue = finiteNumber(params.startPosition);
4671
+ const size = finiteNumber(params.size);
4672
+ const side = String(params.side ?? "").toUpperCase();
4673
+ if (startValue == null || size == null || size <= 0) return null;
4674
+ if (side !== "B" && side !== "A") return null;
4675
+ const startPosition = normalizePosition(startValue);
4676
+ const endPosition = normalizePosition(
4677
+ startPosition + (side === "B" ? size : -size)
4678
+ );
4679
+ const startLong = Math.max(startPosition, 0);
4680
+ const endLong = Math.max(endPosition, 0);
4681
+ const startShort = Math.max(-startPosition, 0);
4682
+ const endShort = Math.max(-endPosition, 0);
4683
+ const longEntrySize = Math.max(0, endLong - startLong);
4684
+ const shortEntrySize = Math.max(0, endShort - startShort);
4685
+ const longExitSize = Math.max(0, startLong - endLong);
4686
+ const shortExitSize = Math.max(0, startShort - endShort);
4687
+ const flipped = startPosition * endPosition < 0;
4688
+ const opened = startPosition === 0;
4689
+ const closed = endPosition === 0;
4690
+ const increased = Math.sign(startPosition) === Math.sign(endPosition) && Math.abs(endPosition) > Math.abs(startPosition);
4691
+ return {
4692
+ startPosition,
4693
+ endPosition,
4694
+ action: flipped ? "flip" : opened ? "open" : closed ? "close" : increased ? "increase" : "reduce",
4695
+ longEntrySize,
4696
+ shortEntrySize,
4697
+ longExitSize,
4698
+ shortExitSize
4699
+ };
4700
+ };
4701
+ var normalizeAddress = (value) => typeof value === "string" && /^0x[0-9a-fA-F]{40}$/.test(value) ? value.toLowerCase() : null;
4702
+ var normalizeTradeFields = (value) => {
4703
+ const symbol = typeof value.coin === "string" ? value.coin.trim() : "";
4704
+ const price = finiteNumber(value.px);
4705
+ const size = finiteNumber(value.sz);
4706
+ const timestamp = finiteNumber(value.time);
4707
+ const tid = typeof value.tid === "string" || typeof value.tid === "number" ? String(value.tid) : "";
4708
+ if (!symbol || price == null || size == null || timestamp == null || !tid || price <= 0 || size <= 0) {
4709
+ return null;
4710
+ }
4711
+ return {
4712
+ symbol,
4713
+ price,
4714
+ size,
4715
+ timestamp,
4716
+ tid,
4717
+ notionalUsd: price * size
4718
+ };
4719
+ };
4720
+ var normalizeHyperliquidUserFill = (params) => {
4721
+ const normalized = normalizeTradeFields(params.fill);
4722
+ if (!normalized || !params.trackedSymbols.has(normalized.symbol)) return null;
4723
+ const address = normalizeAddress(params.address);
4724
+ if (!address) return null;
4725
+ const side = String(params.fill.side ?? "").toUpperCase();
4726
+ if (side !== "B" && side !== "A") return null;
4727
+ const positionChange = classifyHyperliquidPositionChange({
4728
+ startPosition: params.fill.startPosition,
4729
+ side,
4730
+ size: normalized.size
4731
+ });
4732
+ const closedPnl = finiteNumber(params.fill.closedPnl);
4733
+ const liquidation = params.fill.liquidation != null && typeof params.fill.liquidation === "object";
4734
+ const positionFields = positionChange ? side === "B" ? {
4735
+ buyerStartPosition: positionChange.startPosition,
4736
+ buyerEndPosition: positionChange.endPosition,
4737
+ buyerPositionAction: positionChange.action,
4738
+ buyerClosedPnl: closedPnl,
4739
+ buyerLiquidation: liquidation
4740
+ } : {
4741
+ sellerStartPosition: positionChange.startPosition,
4742
+ sellerEndPosition: positionChange.endPosition,
4743
+ sellerPositionAction: positionChange.action,
4744
+ sellerClosedPnl: closedPnl,
4745
+ sellerLiquidation: liquidation
4746
+ } : {};
4747
+ return {
4748
+ symbol: normalized.symbol,
4749
+ ts: new Date(normalized.timestamp),
4750
+ tid: normalized.tid,
4751
+ price: normalized.price,
4752
+ size: normalized.size,
4753
+ notionalUsd: normalized.notionalUsd,
4754
+ buyerAddress: side === "B" ? address : null,
4755
+ sellerAddress: side === "A" ? address : null,
4756
+ buyerTracked: side === "B",
4757
+ sellerTracked: side === "A",
4758
+ ...positionFields,
4759
+ ...params.identity,
4760
+ source: "hyperliquid_user_fills"
4761
+ };
4762
+ };
4763
+
4764
+ // src/lib/hyperliquidRateLimiter.ts
4765
+ var DEFAULT_WEIGHT_PER_MINUTE = 1e3;
4766
+ var WINDOW_MS = 6e4;
4767
+ var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
4768
+ var HyperliquidInfoRateLimiter = class {
4769
+ constructor(capacity = DEFAULT_WEIGHT_PER_MINUTE, now = Date.now, wait = sleep2) {
4770
+ this.capacity = capacity;
4771
+ this.now = now;
4772
+ this.wait = wait;
4773
+ this.reservations = [];
4774
+ this.queue = Promise.resolve();
4775
+ }
4776
+ reserve(weight) {
4777
+ const normalizedWeight = Math.max(1, Math.ceil(weight));
4778
+ const pending = this.queue.then(
4779
+ () => this.reserveInternal(normalizedWeight)
4780
+ );
4781
+ this.queue = pending.catch(() => void 0);
4782
+ return pending;
4783
+ }
4784
+ async reserveInternal(weight) {
4785
+ if (weight > this.capacity) {
4786
+ throw new Error(
4787
+ `Hyperliquid request weight ${weight} exceeds limiter capacity ${this.capacity}`
4788
+ );
4789
+ }
4790
+ for (; ; ) {
4791
+ const nowMs = this.now();
4792
+ this.reservations = this.reservations.filter(
4793
+ (reservation) => reservation.atMs > nowMs - WINDOW_MS
4794
+ );
4795
+ const used = this.reservations.reduce(
4796
+ (sum, reservation) => sum + reservation.weight,
4797
+ 0
4798
+ );
4799
+ if (used + weight <= this.capacity) {
4800
+ this.reservations.push({ atMs: nowMs, weight });
4801
+ return;
4802
+ }
4803
+ const oldest = this.reservations[0];
4804
+ await this.wait(Math.max(1, oldest.atMs + WINDOW_MS - nowMs));
4805
+ }
4806
+ }
4807
+ };
4808
+ var hyperliquidInfoResponseWeight = (rows) => Math.max(0, Math.ceil(Math.max(0, rows) / 20));
4809
+
4810
+ // src/lib/hyperliquidWhaleBackfill.ts
4811
+ var DEFAULT_BASE_URL = "https://api.hyperliquid.xyz";
4812
+ var DEFAULT_CONCURRENCY = 2;
4813
+ var DEFAULT_RATE_LIMIT_WEIGHT = 1e3;
4814
+ var MAX_PAGE_SIZE = 2e3;
4815
+ var MAX_PAGES_PER_WHALE = 5;
4816
+ var REQUEST_WEIGHT = 20;
4817
+ var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
4818
+ var positiveInt = (value, fallback) => {
4819
+ const parsed = Number.parseInt(String(value ?? ""), 10);
4820
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
4821
+ };
4822
+ var minuteCeil = (value) => Math.ceil(value / HYPERLIQUID_WHALE_BUCKET_MS) * HYPERLIQUID_WHALE_BUCKET_MS;
4823
+ var nextMinute = (value) => (Math.floor(value / HYPERLIQUID_WHALE_BUCKET_MS) + 1) * HYPERLIQUID_WHALE_BUCKET_MS;
4824
+ var fetchHyperliquidUserFillsByTime = async (params) => {
4825
+ const fetchImpl = params.fetchImpl ?? fetch;
4826
+ const limiter = params.rateLimiter ?? new HyperliquidInfoRateLimiter(
4827
+ positiveInt(
4828
+ process.env.HYPERLIQUID_WHALE_RATE_LIMIT_WEIGHT,
4829
+ DEFAULT_RATE_LIMIT_WEIGHT
4830
+ ),
4831
+ Date.now,
4832
+ params.wait ?? sleep3
4833
+ );
4834
+ const fills = [];
4835
+ let cursor = params.startTime;
4836
+ for (let page = 0; page < (params.maxPages ?? MAX_PAGES_PER_WHALE) && cursor <= params.endTime; page += 1) {
4837
+ await limiter.reserve(REQUEST_WEIGHT);
4838
+ const response = await fetchImpl(
4839
+ `${process.env.HYPERLIQUID_API_BASE_URL?.trim() || DEFAULT_BASE_URL}/info`,
4840
+ {
4841
+ method: "POST",
4842
+ headers: { "content-type": "application/json" },
4843
+ body: JSON.stringify({
4844
+ type: "userFillsByTime",
4845
+ user: params.address,
4846
+ startTime: cursor,
4847
+ endTime: params.endTime,
4848
+ aggregateByTime: true
4849
+ })
4850
+ }
4851
+ );
4852
+ if (!response.ok) {
4853
+ throw new Error(
4854
+ `Hyperliquid userFillsByTime ${response.status}: ${await response.text()}`
4855
+ );
4856
+ }
4857
+ const raw = await response.json();
4858
+ const pageRows = Array.isArray(raw) ? raw : [];
4859
+ const responseWeight = hyperliquidInfoResponseWeight(pageRows.length);
4860
+ if (responseWeight > 0) await limiter.reserve(responseWeight);
4861
+ fills.push(...pageRows);
4862
+ if (pageRows.length < MAX_PAGE_SIZE) {
4863
+ return { fills, truncated: false, coveredFromMs: params.startTime };
4864
+ }
4865
+ const lastTime = Math.max(
4866
+ ...pageRows.map((row) => Number(row.time)).filter(Number.isFinite)
4867
+ );
4868
+ if (!Number.isFinite(lastTime) || lastTime < cursor) {
4869
+ throw new Error(
4870
+ `Hyperliquid fills pagination stalled for ${params.address}`
4871
+ );
4872
+ }
4873
+ cursor = lastTime + 1;
4874
+ }
4875
+ if (cursor > params.endTime) {
4876
+ return { fills, truncated: false, coveredFromMs: params.startTime };
4877
+ }
4878
+ const firstAvailableTime = Math.min(
4879
+ ...fills.map((fill) => Number(fill.time)).filter(Number.isFinite)
4880
+ );
4881
+ return {
4882
+ fills,
4883
+ truncated: true,
4884
+ coveredFromMs: Math.min(params.endTime + 1, nextMinute(firstAvailableTime))
4885
+ };
4886
+ };
4887
+ var runConcurrent = async (params) => {
4888
+ let nextIndex = 0;
4889
+ await Promise.all(
4890
+ Array.from(
4891
+ { length: Math.min(params.concurrency, params.items.length) },
4892
+ async () => {
4893
+ for (; ; ) {
4894
+ const index = nextIndex;
4895
+ nextIndex += 1;
4896
+ if (index >= params.items.length) return;
4897
+ await params.worker(params.items[index], index);
4898
+ }
4899
+ }
4900
+ )
4901
+ );
4902
+ };
4903
+ var backfillHyperliquidWhaleContext = async (params) => {
4904
+ const universe = (0, import_strategies2.getHyperliquidPerpUniverseSnapshot)();
4905
+ const whales = (0, import_strategies2.getHyperliquidWhaleRegistrySnapshot)();
4906
+ const trackedSymbols = new Set(universe.symbols);
4907
+ const identity = {
4908
+ universeFingerprint: universe.fingerprint,
4909
+ whaleRegistryFingerprint: whales.fingerprint
4910
+ };
4911
+ const fromMs = Math.floor(params.startMs / HYPERLIQUID_WHALE_BUCKET_MS) * HYPERLIQUID_WHALE_BUCKET_MS;
4912
+ const toMs = minuteCeil(params.endMs);
4913
+ const cached = await (0, import_timescale4.hasHyperliquidWhaleBackfillCoverage)({
4914
+ fromMs,
4915
+ toMs,
4916
+ ...identity
4917
+ });
4918
+ const emptyResult = () => ({
4919
+ cached,
4920
+ cacheOnly: params.cacheOnly,
4921
+ fills: 0,
4922
+ events: 0,
4923
+ buckets: 0,
4924
+ coverageBuckets: 0,
4925
+ complete: cached,
4926
+ walletsProcessed: 0,
4927
+ walletsCached: 0,
4928
+ completeWallets: 0,
4929
+ truncatedWallets: 0,
4930
+ failedWallets: 0,
4931
+ fromMs,
4932
+ toMs
4933
+ });
4934
+ if (cached || params.cacheOnly) return emptyResult();
4935
+ const limiter = new HyperliquidInfoRateLimiter(
4936
+ positiveInt(
4937
+ process.env.HYPERLIQUID_WHALE_RATE_LIMIT_WEIGHT,
4938
+ DEFAULT_RATE_LIMIT_WEIGHT
4939
+ ),
4940
+ Date.now,
4941
+ params.wait ?? sleep3
4942
+ );
4943
+ const concurrency = Math.min(
4944
+ 4,
4945
+ positiveInt(
4946
+ params.concurrency ?? process.env.HYPERLIQUID_WHALE_CONCURRENCY,
4947
+ DEFAULT_CONCURRENCY
4948
+ )
4949
+ );
4950
+ let fills = 0;
4951
+ let events = 0;
4952
+ let walletsProcessed = 0;
4953
+ let walletsCached = 0;
4954
+ let completeWallets = 0;
4955
+ let truncatedWallets = 0;
4956
+ let failedWallets = 0;
4957
+ const failures = [];
4958
+ await runConcurrent({
4959
+ items: whales.addresses,
4960
+ concurrency,
4961
+ worker: async (address) => {
4962
+ const previous = await (0, import_timescale4.getHyperliquidWhaleWalletCoverage)({
4963
+ address,
4964
+ fromMs,
4965
+ toMs,
4966
+ ...identity
4967
+ });
4968
+ if (previous && previous.status !== "failed") {
4969
+ walletsCached += 1;
4970
+ if (previous.status === "complete") completeWallets += 1;
4971
+ else truncatedWallets += 1;
4972
+ } else {
4973
+ try {
4974
+ const result2 = await fetchHyperliquidUserFillsByTime({
4975
+ address,
4976
+ startTime: fromMs,
4977
+ endTime: toMs - 1,
4978
+ fetchImpl: params.fetchImpl,
4979
+ rateLimiter: limiter
4980
+ });
4981
+ const addressEvents = result2.fills.map(
4982
+ (fill) => normalizeHyperliquidUserFill({
4983
+ fill,
4984
+ address,
4985
+ trackedSymbols,
4986
+ identity
4987
+ })
4988
+ ).filter((row) => row != null);
4989
+ await (0, import_timescale4.upsertHyperliquidWhaleTradeEvents)(addressEvents);
4990
+ await (0, import_timescale4.upsertHyperliquidWhaleWalletCoverage)({
4991
+ address,
4992
+ fromMs,
4993
+ toMs,
4994
+ coveredFromMs: result2.coveredFromMs,
4995
+ coveredToMs: toMs,
4996
+ status: result2.truncated ? "truncated" : "complete",
4997
+ fillsCount: result2.fills.length,
4998
+ ...identity
4999
+ });
5000
+ fills += result2.fills.length;
5001
+ events += addressEvents.length;
5002
+ if (result2.truncated) truncatedWallets += 1;
5003
+ else completeWallets += 1;
5004
+ } catch (error) {
5005
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
5006
+ failures.push(normalizedError);
5007
+ failedWallets += 1;
5008
+ await (0, import_timescale4.upsertHyperliquidWhaleWalletCoverage)({
5009
+ address,
5010
+ fromMs,
5011
+ toMs,
5012
+ coveredFromMs: null,
5013
+ coveredToMs: null,
5014
+ status: "failed",
5015
+ fillsCount: 0,
5016
+ error: normalizedError.message,
5017
+ ...identity
5018
+ });
5019
+ }
5020
+ }
5021
+ walletsProcessed += 1;
5022
+ params.log?.(
5023
+ `Hyperliquid whales ${walletsProcessed}/${whales.addresses.length}: complete=${completeWallets}, truncated=${truncatedWallets}, failed=${failedWallets}, fills=${fills}, events=${events}`
5024
+ );
5025
+ }
5026
+ });
5027
+ const buckets = await (0, import_timescale4.rebuildHyperliquidWhaleFlowRows)({
5028
+ fromMs,
5029
+ toMs,
5030
+ ...identity
5031
+ });
5032
+ const coverageBuckets = await (0, import_timescale4.rebuildHyperliquidWhaleCoverageRows)({
5033
+ fromMs,
5034
+ toMs,
5035
+ expectedWhales: whales.addresses.length,
5036
+ ...identity
5037
+ });
5038
+ const result = {
5039
+ cached: false,
5040
+ cacheOnly: false,
5041
+ fills,
5042
+ events,
5043
+ buckets,
5044
+ coverageBuckets,
5045
+ complete: truncatedWallets === 0 && failedWallets === 0,
5046
+ walletsProcessed,
5047
+ walletsCached,
5048
+ completeWallets,
5049
+ truncatedWallets,
5050
+ failedWallets,
5051
+ fromMs,
5052
+ toMs
5053
+ };
5054
+ if (params.strict !== false && failures.length) {
5055
+ throw new AggregateError(
5056
+ failures,
5057
+ `Hyperliquid whale backfill failed for ${failures.length}/${whales.addresses.length} wallets`
5058
+ );
5059
+ }
5060
+ return result;
5061
+ };
5062
+
4657
5063
  // src/lib/marketContextPrepare.ts
4658
5064
  var shouldPrepareDerivativesContextForRun = (params) => {
4659
5065
  if (params.universe === "tradfi") return false;
@@ -4704,6 +5110,20 @@ var shouldPrepareCoinMarketCapContextForRun = (params) => {
4704
5110
  cacheOnly: params.cacheOnly
4705
5111
  });
4706
5112
  };
5113
+ var shouldPrepareHyperliquidWhaleContextForRun = (params) => {
5114
+ const strategyRequiresContext = params.strategyNames?.includes(
5115
+ "HyperliquidConsensus"
5116
+ );
5117
+ if (params.universe === "tradfi" || params.mode === "signals" || params.cacheOnly || !params.aiEnabled && !params.mlEnabled && !strategyRequiresContext) {
5118
+ return false;
5119
+ }
5120
+ const configured = String(
5121
+ process.env.HYPERLIQUID_WHALE_BACKFILL_ENABLED ?? ""
5122
+ ).trim().toLowerCase();
5123
+ if (["0", "false", "no", "off"].includes(configured)) return false;
5124
+ if (strategyRequiresContext) return true;
5125
+ return ["1", "true", "yes", "on"].includes(configured);
5126
+ };
4707
5127
  var resolveCoinMarketCapBackfillForMode = (mode) => mode === "backtest" ? backfillCoinMarketCapContextForBacktest : mode === "signals" ? backfillCoinMarketCapContextForSignals : backfillCoinMarketCapContextForReplay;
4708
5128
  var buildCoinMarketCapBackfillParams = (params) => ({
4709
5129
  userName: params.userName,
@@ -4740,6 +5160,18 @@ var prepareMarketContextForRun = async (params) => {
4740
5160
  )
4741
5161
  );
4742
5162
  }
5163
+ if (shouldPrepareHyperliquidWhaleContextForRun(params)) {
5164
+ await timeOperation3(
5165
+ "hyperliquid whale context backfill",
5166
+ () => backfillHyperliquidWhaleContext({
5167
+ startMs: params.preloadStartMs ?? params.startMs,
5168
+ endMs: params.endMs,
5169
+ cacheOnly: params.cacheOnly,
5170
+ strict: false,
5171
+ log
5172
+ })
5173
+ );
5174
+ }
4743
5175
  if (shouldPrepareBinanceMarketContextForRun(params)) {
4744
5176
  await timeOperation3("binance market context backfill", () => {
4745
5177
  const backfill = params.mode === "backtest" ? backfillBinanceMarketContextForBacktest : params.mode === "signals" ? backfillBinanceMarketContextForSignals : backfillBinanceMarketContextForReplay;
@@ -4913,7 +5345,7 @@ var updateBtcReferenceHistory = async ({
4913
5345
  var import_logger = require("@tradejs/infra/logger");
4914
5346
  var import_redis2 = require("@tradejs/infra/redis");
4915
5347
  var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
4916
- var import_strategies2 = require("@tradejs/node/strategies");
5348
+ var import_strategies3 = require("@tradejs/node/strategies");
4917
5349
 
4918
5350
  // src/lib/runtimeRedis.ts
4919
5351
  var import_redis = require("@tradejs/infra/redis");
@@ -5013,7 +5445,7 @@ var loadRuntimeStrategies = async ({
5013
5445
  });
5014
5446
  const effectiveAccountId = resolvedAccount?.id ?? requestedAccountId;
5015
5447
  const [strategyCreator, strategyResults] = await Promise.all([
5016
- (0, import_strategies2.getStrategyCreator)(strategyName, projectRoot2),
5448
+ (0, import_strategies3.getStrategyCreator)(strategyName, projectRoot2),
5017
5449
  (0, import_redis2.getData)(import_redis2.redisKeys.strategyResults(userName, strategyName), {})
5018
5450
  ]);
5019
5451
  if (!strategyCreator) {
@@ -5114,6 +5546,8 @@ var import_node_child_process = require("child_process");
5114
5546
  var import_node_crypto = require("crypto");
5115
5547
  var import_promises = __toESM(require("fs/promises"));
5116
5548
  var import_node_path = __toESM(require("path"));
5549
+ var import_types = require("@tradejs/types");
5550
+ var import_strategies4 = require("@tradejs/node/strategies");
5117
5551
  var RUNTIME_CONTEXT_ENV_KEYS = [
5118
5552
  "AI_MODE",
5119
5553
  "MIN_AI_QUALITY",
@@ -5131,7 +5565,12 @@ var RUNTIME_CONTEXT_ENV_KEYS = [
5131
5565
  "COINMARKETCAP_CONTEXT_HISTORICAL_ACCESS_MONTHS",
5132
5566
  "COINMARKETCAP_CONTEXT_BACKFILL_ENABLED",
5133
5567
  "COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
5134
- "COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS"
5568
+ "COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS",
5569
+ "HYPERLIQUID_WHALE_CONTEXT_ENABLED",
5570
+ "HYPERLIQUID_WHALE_BACKFILL_ENABLED",
5571
+ "HYPERLIQUID_WHALE_MIN_COVERAGE_PCT",
5572
+ "HYPERLIQUID_WHALE_CONCURRENCY",
5573
+ "HYPERLIQUID_WHALE_RATE_LIMIT_WEIGHT"
5135
5574
  ];
5136
5575
  var RUNTIME_CONTEXT_DATA_MODEL = {
5137
5576
  derivativesSourceIntervals: "15m",
@@ -5141,7 +5580,11 @@ var RUNTIME_CONTEXT_DATA_MODEL = {
5141
5580
  coinMarketCapAvailabilityModel: "closed-interval-all-daily",
5142
5581
  coinMarketCapDataModelVersion: 3,
5143
5582
  binanceBreadthUniverseModel: "fixed-versioned-top5-top10-top30-top50-top100",
5144
- binanceBreadthDataModelVersion: 2
5583
+ binanceBreadthDataModelVersion: 2,
5584
+ hyperliquidWhaleCanonicalInterval: "1m",
5585
+ hyperliquidWhaleGateMinNotionalUsd: 5e4,
5586
+ hyperliquidWhaleMinCoveragePct: 0.8,
5587
+ hyperliquidWhaleDataModelVersion: import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
5145
5588
  };
5146
5589
  var normalizeForStableJson = (value) => {
5147
5590
  if (Array.isArray(value)) {
@@ -5154,10 +5597,24 @@ var normalizeForStableJson = (value) => {
5154
5597
  }
5155
5598
  return value;
5156
5599
  };
5600
+ var resolveRuntimeMaxLossValue = (config) => {
5601
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
5602
+ return null;
5603
+ }
5604
+ const strategyConfig = config.strategyConfig;
5605
+ if (!strategyConfig || typeof strategyConfig !== "object" || Array.isArray(strategyConfig)) {
5606
+ return null;
5607
+ }
5608
+ const maxLossValue = Number(
5609
+ strategyConfig.MAX_LOSS_VALUE
5610
+ );
5611
+ return Number.isFinite(maxLossValue) ? maxLossValue : null;
5612
+ };
5157
5613
  var fingerprintRuntimeValue = (value) => (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
5158
5614
  var gitLineageCache = /* @__PURE__ */ new Map();
5159
5615
  var gateFingerprintCache = /* @__PURE__ */ new Map();
5160
5616
  var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
5617
+ var snapshotFingerprintCache = /* @__PURE__ */ new Map();
5161
5618
  var resolveGitLineage = (projectRoot2) => {
5162
5619
  const cached = gitLineageCache.get(projectRoot2);
5163
5620
  if (cached) {
@@ -5231,6 +5688,24 @@ var resolveBinanceBreadthFingerprint = async (projectRoot2) => {
5231
5688
  binanceBreadthFingerprintCache.set(projectRoot2, result);
5232
5689
  return result;
5233
5690
  };
5691
+ var resolveSnapshotFingerprint = async (projectRoot2, relativePath) => {
5692
+ const cacheKey = `${projectRoot2}:${relativePath}`;
5693
+ if (snapshotFingerprintCache.has(cacheKey)) {
5694
+ return snapshotFingerprintCache.get(cacheKey) ?? null;
5695
+ }
5696
+ const content = await readOptionalFile(import_node_path.default.join(projectRoot2, relativePath));
5697
+ let result = null;
5698
+ if (content) {
5699
+ try {
5700
+ const parsed = JSON.parse(content.toString("utf8"));
5701
+ result = typeof parsed.fingerprint === "string" && parsed.fingerprint.trim() ? parsed.fingerprint.trim() : null;
5702
+ } catch {
5703
+ result = null;
5704
+ }
5705
+ }
5706
+ snapshotFingerprintCache.set(cacheKey, result);
5707
+ return result;
5708
+ };
5234
5709
  var resolveGateFingerprint = async ({
5235
5710
  projectRoot: projectRoot2,
5236
5711
  strategyName,
@@ -5246,7 +5721,8 @@ var resolveGateFingerprint = async ({
5246
5721
  `packages/strategies/src/${strategyName}/guardrails.ts`,
5247
5722
  `packages/strategies/src/${strategyName}/pockets.ts`,
5248
5723
  `packages/strategies/src/${strategyName}/config.ts`,
5249
- "packages/node/src/ai.ts"
5724
+ "packages/node/src/ai.ts",
5725
+ "packages/core/src/utils/strategyHelpers/signalBuilders.ts"
5250
5726
  ];
5251
5727
  const optionalEntries = await Promise.all(
5252
5728
  relativeCandidates.map(async (relativePath) => {
@@ -5284,12 +5760,22 @@ var buildRuntimeLineage = async ({
5284
5760
  env = process.env
5285
5761
  }) => {
5286
5762
  const git = resolveGitLineage(projectRoot2);
5763
+ const hyperliquidPerpUniverseFingerprint = typeof import_strategies4.getHyperliquidPerpUniverseSnapshot === "function" ? (0, import_strategies4.getHyperliquidPerpUniverseSnapshot)().fingerprint : await resolveSnapshotFingerprint(
5764
+ projectRoot2,
5765
+ "packages/node/src/config/hyperliquidPerpUniverse.json"
5766
+ );
5767
+ const hyperliquidWhaleRegistryFingerprint = typeof import_strategies4.getHyperliquidWhaleRegistrySnapshot === "function" ? (0, import_strategies4.getHyperliquidWhaleRegistrySnapshot)().fingerprint : await resolveSnapshotFingerprint(
5768
+ projectRoot2,
5769
+ "packages/node/src/config/hyperliquidWhales.json"
5770
+ );
5287
5771
  const context = {
5288
5772
  ...Object.fromEntries(
5289
5773
  RUNTIME_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
5290
5774
  ),
5291
5775
  ...RUNTIME_CONTEXT_DATA_MODEL,
5292
5776
  binanceBreadthUniverseFingerprint: await resolveBinanceBreadthFingerprint(projectRoot2),
5777
+ hyperliquidPerpUniverseFingerprint,
5778
+ hyperliquidWhaleRegistryFingerprint,
5293
5779
  ...runContext
5294
5780
  };
5295
5781
  return {
@@ -5301,7 +5787,8 @@ var buildRuntimeLineage = async ({
5301
5787
  gitSha: git.gitSha
5302
5788
  }),
5303
5789
  configFingerprint: fingerprintRuntimeValue(config),
5304
- contextFingerprint: fingerprintRuntimeValue(context)
5790
+ contextFingerprint: fingerprintRuntimeValue(context),
5791
+ maxLossValue: resolveRuntimeMaxLossValue(config)
5305
5792
  };
5306
5793
  };
5307
5794
  var runtimeLineageKey = (lineage) => [
@@ -5680,7 +6167,7 @@ var runSignalsDaemon = async ({
5680
6167
  // src/lib/signals/klineFeed.ts
5681
6168
  var import_connectors5 = require("@tradejs/connectors");
5682
6169
  var import_logger3 = require("@tradejs/infra/logger");
5683
- var import_timescale4 = require("@tradejs/infra/timescale");
6170
+ var import_timescale5 = require("@tradejs/infra/timescale");
5684
6171
 
5685
6172
  // src/lib/marketData/klineEvents.ts
5686
6173
  var import_ioredis = __toESM(require("ioredis"));
@@ -5700,9 +6187,9 @@ var createSignalsKlineFeed = async ({
5700
6187
  publish = publishMarketKlineEvent,
5701
6188
  persist = async (rows) => {
5702
6189
  const intervalMinutes = Number(interval2);
5703
- await (0, import_timescale4.upsertCandles)(
6190
+ await (0, import_timescale5.upsertCandles)(
5704
6191
  rows.flatMap(
5705
- ({ symbol, candle }) => (0, import_timescale4.toRows)("bybit", symbol, intervalMinutes, [candle])
6192
+ ({ symbol, candle }) => (0, import_timescale5.toRows)("bybit", symbol, intervalMinutes, [candle])
5706
6193
  )
5707
6194
  );
5708
6195
  },
@@ -5884,7 +6371,7 @@ var resolveSignalsKlineWsWaitMs = () => resolveNonNegativeInteger(process.env.SI
5884
6371
  var createDefaultSignalsLifecycle = (runtimeInterval = interval) => createSignalsStrategyLifecycle({
5885
6372
  intervalMs: Number(runtimeInterval) * 6e4,
5886
6373
  maxLiveBars: resolveSignalsDaemonMaxLiveBars(runtimeInterval),
5887
- releaseState: import_strategies3.releaseStrategyReplayCache
6374
+ releaseState: import_strategies5.releaseStrategyReplayCache
5888
6375
  });
5889
6376
  var timeOperation2 = (label, operation) => timeOperation(
5890
6377
  label,
@@ -6148,7 +6635,7 @@ var findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCo
6148
6635
  if (configId && configId !== "config" && !signal.signalId.endsWith(`:${configId}`)) {
6149
6636
  signal.signalId = `${signal.signalId}:${configId}`;
6150
6637
  }
6151
- await (0, import_strategies4.enrichSignalWithBinanceMarketContext)({
6638
+ await (0, import_strategies6.enrichSignalWithBinanceMarketContext)({
6152
6639
  signal,
6153
6640
  env: "CRON"
6154
6641
  });