@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
package/dist/scripts/signals.js
CHANGED
|
@@ -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
|
|
1653
|
+
var import_strategies5 = require("@tradejs/core/strategies");
|
|
1654
1654
|
var import_constants3 = require("@tradejs/core/constants");
|
|
1655
|
-
var
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
6190
|
+
await (0, import_timescale5.upsertCandles)(
|
|
5704
6191
|
rows.flatMap(
|
|
5705
|
-
({ symbol, candle }) => (0,
|
|
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:
|
|
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,
|
|
6638
|
+
await (0, import_strategies6.enrichSignalWithBinanceMarketContext)({
|
|
6152
6639
|
signal,
|
|
6153
6640
|
env: "CRON"
|
|
6154
6641
|
});
|