@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.
- 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
|
@@ -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
|
|
1651
|
+
var import_strategies5 = require("@tradejs/core/strategies");
|
|
1652
1652
|
var import_constants3 = require("@tradejs/core/constants");
|
|
1653
|
-
var
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
6188
|
+
await (0, import_timescale5.upsertCandles)(
|
|
5702
6189
|
rows.flatMap(
|
|
5703
|
-
({ symbol, candle }) => (0,
|
|
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:
|
|
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,
|
|
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.
|
|
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.
|
|
33
|
-
"@tradejs/connectors": "^2.0.
|
|
34
|
-
"@tradejs/core": "^2.0.
|
|
35
|
-
"@tradejs/indicators": "^2.0.
|
|
36
|
-
"@tradejs/infra": "^2.0.
|
|
37
|
-
"@tradejs/node": "^2.0.
|
|
38
|
-
"@tradejs/strategies": "^2.0.
|
|
39
|
-
"@tradejs/types": "^2.0.
|
|
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",
|