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