@tradejs/infra 2.0.13 → 2.0.15

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/timescale.js CHANGED
@@ -34,6 +34,10 @@ __export(timescale_exports, {
34
34
  getDerivativesRangeForSymbols: () => getDerivativesRangeForSymbols,
35
35
  getDerivativesSummary: () => getDerivativesSummary,
36
36
  getDerivativesWindow: () => getDerivativesWindow,
37
+ getHyperliquidWhaleCoverageSeriesRows: () => getHyperliquidWhaleCoverageSeriesRows,
38
+ getHyperliquidWhaleFlowAggregate: () => getHyperliquidWhaleFlowAggregate,
39
+ getHyperliquidWhaleFlowSeriesRows: () => getHyperliquidWhaleFlowSeriesRows,
40
+ getHyperliquidWhaleWalletCoverage: () => getHyperliquidWhaleWalletCoverage,
37
41
  getLatestMarketBreadth: () => getLatestMarketBreadth,
38
42
  getLatestMarketCmcExchangeLiquidityContext: () => getLatestMarketCmcExchangeLiquidityContext,
39
43
  getLatestMarketCmcFearGreedContext: () => getLatestMarketCmcFearGreedContext,
@@ -51,10 +55,17 @@ __export(timescale_exports, {
51
55
  getMarketTradeFlowCoverage: () => getMarketTradeFlowCoverage,
52
56
  getSpreadRangeForSymbols: () => getSpreadRangeForSymbols,
53
57
  getSpreadSummary: () => getSpreadSummary,
58
+ hasHyperliquidWhaleBackfillCoverage: () => hasHyperliquidWhaleBackfillCoverage,
59
+ rebuildHyperliquidWhaleCoverageRows: () => rebuildHyperliquidWhaleCoverageRows,
60
+ rebuildHyperliquidWhaleFlowRows: () => rebuildHyperliquidWhaleFlowRows,
54
61
  toRows: () => toRows,
55
62
  upsertCandles: () => upsertCandles,
56
63
  upsertDerivatives: () => upsertDerivatives,
57
64
  upsertDerivativesBackfillCoverage: () => upsertDerivativesBackfillCoverage,
65
+ upsertHyperliquidWhaleCoverageRows: () => upsertHyperliquidWhaleCoverageRows,
66
+ upsertHyperliquidWhaleFlowRows: () => upsertHyperliquidWhaleFlowRows,
67
+ upsertHyperliquidWhaleTradeEvents: () => upsertHyperliquidWhaleTradeEvents,
68
+ upsertHyperliquidWhaleWalletCoverage: () => upsertHyperliquidWhaleWalletCoverage,
58
69
  upsertMarketBreadthRows: () => upsertMarketBreadthRows,
59
70
  upsertMarketCmcExchangeLiquidityContextRows: () => upsertMarketCmcExchangeLiquidityContextRows,
60
71
  upsertMarketCmcFearGreedContextRows: () => upsertMarketCmcFearGreedContextRows,
@@ -68,6 +79,7 @@ __export(timescale_exports, {
68
79
  });
69
80
  module.exports = __toCommonJS(timescale_exports);
70
81
  var import_pg = require("pg");
82
+ var import_types = require("@tradejs/types");
71
83
  var getPool = () => {
72
84
  if (!global.__pgPool__) {
73
85
  const host = process.env.PG_HOST || "127.0.0.1";
@@ -96,10 +108,12 @@ var candlesSchemaReady = false;
96
108
  var derivativesSchemaReady = false;
97
109
  var spreadSchemaReady = false;
98
110
  var binanceMarketSchemaReady = false;
111
+ var hyperliquidWhaleSchemaReady = false;
99
112
  var candlesSchemaReadyPromise = null;
100
113
  var derivativesSchemaReadyPromise = null;
101
114
  var spreadSchemaReadyPromise = null;
102
115
  var binanceMarketSchemaReadyPromise = null;
116
+ var hyperliquidWhaleSchemaReadyPromise = null;
103
117
  var closeTimescalePool = async () => {
104
118
  const pool = global.__pgPool__;
105
119
  if (!pool) {
@@ -110,16 +124,19 @@ var closeTimescalePool = async () => {
110
124
  derivativesSchemaReady = false;
111
125
  spreadSchemaReady = false;
112
126
  binanceMarketSchemaReady = false;
127
+ hyperliquidWhaleSchemaReady = false;
113
128
  candlesSchemaReadyPromise = null;
114
129
  derivativesSchemaReadyPromise = null;
115
130
  spreadSchemaReadyPromise = null;
116
131
  binanceMarketSchemaReadyPromise = null;
132
+ hyperliquidWhaleSchemaReadyPromise = null;
117
133
  await pool.end();
118
134
  };
119
135
  var CANDLES_SCHEMA_LOCK_KEY = 61e4;
120
136
  var DERIVATIVES_SCHEMA_LOCK_KEY = 610001;
121
137
  var SPREAD_SCHEMA_LOCK_KEY = 610002;
122
138
  var BINANCE_MARKET_SCHEMA_LOCK_KEY = 610003;
139
+ var HYPERLIQUID_WHALE_SCHEMA_LOCK_KEY = 610004;
123
140
  var PG_SAFE_MAX_BIND_PARAMS = 3e4;
124
141
  var normalizeCandleProvider = (provider) => String(provider || "").trim().toLowerCase();
125
142
  var normalizeCandleSymbol = (symbol) => String(symbol || "").trim().toUpperCase();
@@ -697,6 +714,234 @@ var ensureBinanceMarketSchema = async () => {
697
714
  });
698
715
  await binanceMarketSchemaReadyPromise;
699
716
  };
717
+ var ensureHyperliquidWhaleSchema = async () => {
718
+ if (hyperliquidWhaleSchemaReady) return;
719
+ if (hyperliquidWhaleSchemaReadyPromise) {
720
+ await hyperliquidWhaleSchemaReadyPromise;
721
+ return;
722
+ }
723
+ hyperliquidWhaleSchemaReadyPromise = withSchemaLock(
724
+ HYPERLIQUID_WHALE_SCHEMA_LOCK_KEY,
725
+ async () => {
726
+ if (hyperliquidWhaleSchemaReady) return;
727
+ const pool = getPool();
728
+ await pool.query("CREATE EXTENSION IF NOT EXISTS timescaledb");
729
+ await pool.query(`
730
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_trade_events (
731
+ symbol text NOT NULL,
732
+ ts timestamptz NOT NULL,
733
+ tid text NOT NULL,
734
+ price double precision NOT NULL,
735
+ size double precision NOT NULL,
736
+ notional_usd double precision NOT NULL,
737
+ buyer_address text,
738
+ seller_address text,
739
+ buyer_tracked boolean NOT NULL,
740
+ seller_tracked boolean NOT NULL,
741
+ buyer_start_position double precision,
742
+ buyer_end_position double precision,
743
+ buyer_position_action text,
744
+ buyer_closed_pnl double precision,
745
+ buyer_liquidation boolean,
746
+ seller_start_position double precision,
747
+ seller_end_position double precision,
748
+ seller_position_action text,
749
+ seller_closed_pnl double precision,
750
+ seller_liquidation boolean,
751
+ universe_fingerprint text NOT NULL,
752
+ whale_registry_fingerprint text NOT NULL,
753
+ source text,
754
+ ingested_at timestamptz NOT NULL DEFAULT now(),
755
+ PRIMARY KEY (
756
+ universe_fingerprint,
757
+ whale_registry_fingerprint,
758
+ symbol,
759
+ ts,
760
+ tid
761
+ )
762
+ )
763
+ `);
764
+ await pool.query(`
765
+ ALTER TABLE hyperliquid_whale_trade_events
766
+ ADD COLUMN IF NOT EXISTS buyer_start_position double precision,
767
+ ADD COLUMN IF NOT EXISTS buyer_end_position double precision,
768
+ ADD COLUMN IF NOT EXISTS buyer_position_action text,
769
+ ADD COLUMN IF NOT EXISTS buyer_closed_pnl double precision,
770
+ ADD COLUMN IF NOT EXISTS buyer_liquidation boolean,
771
+ ADD COLUMN IF NOT EXISTS seller_start_position double precision,
772
+ ADD COLUMN IF NOT EXISTS seller_end_position double precision,
773
+ ADD COLUMN IF NOT EXISTS seller_position_action text,
774
+ ADD COLUMN IF NOT EXISTS seller_closed_pnl double precision,
775
+ ADD COLUMN IF NOT EXISTS seller_liquidation boolean
776
+ `);
777
+ await pool.query(`
778
+ SELECT create_hypertable(
779
+ 'hyperliquid_whale_trade_events',
780
+ 'ts',
781
+ if_not_exists => TRUE,
782
+ chunk_time_interval => interval '1 day'
783
+ )
784
+ `);
785
+ await pool.query(`
786
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_events_lookup_idx
787
+ ON hyperliquid_whale_trade_events (
788
+ universe_fingerprint,
789
+ whale_registry_fingerprint,
790
+ symbol,
791
+ ts DESC
792
+ )
793
+ `);
794
+ await pool.query(`
795
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_flow (
796
+ symbol text NOT NULL,
797
+ interval text NOT NULL,
798
+ ts timestamptz NOT NULL,
799
+ trades integer NOT NULL,
800
+ whale_sides integer NOT NULL,
801
+ unique_whales integer NOT NULL,
802
+ whale_addresses text[] NOT NULL DEFAULT '{}',
803
+ buy_notional_usd double precision NOT NULL,
804
+ sell_notional_usd double precision NOT NULL,
805
+ net_notional_usd double precision NOT NULL,
806
+ buy_share_pct double precision,
807
+ position_aware_whale_sides integer NOT NULL DEFAULT 0,
808
+ long_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
809
+ short_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
810
+ long_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
811
+ short_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
812
+ long_entry_notional_usd double precision NOT NULL DEFAULT 0,
813
+ short_entry_notional_usd double precision NOT NULL DEFAULT 0,
814
+ long_exit_notional_usd double precision NOT NULL DEFAULT 0,
815
+ short_exit_notional_usd double precision NOT NULL DEFAULT 0,
816
+ entry_net_notional_usd double precision NOT NULL DEFAULT 0,
817
+ entry_long_share_pct double precision,
818
+ universe_fingerprint text NOT NULL,
819
+ whale_registry_fingerprint text NOT NULL,
820
+ source text,
821
+ ingested_at timestamptz NOT NULL DEFAULT now(),
822
+ PRIMARY KEY (
823
+ universe_fingerprint,
824
+ whale_registry_fingerprint,
825
+ symbol,
826
+ interval,
827
+ ts
828
+ )
829
+ )
830
+ `);
831
+ await pool.query(`
832
+ ALTER TABLE hyperliquid_whale_flow
833
+ ADD COLUMN IF NOT EXISTS position_aware_whale_sides integer NOT NULL DEFAULT 0,
834
+ ADD COLUMN IF NOT EXISTS long_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
835
+ ADD COLUMN IF NOT EXISTS short_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
836
+ ADD COLUMN IF NOT EXISTS long_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
837
+ ADD COLUMN IF NOT EXISTS short_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
838
+ ADD COLUMN IF NOT EXISTS long_entry_notional_usd double precision NOT NULL DEFAULT 0,
839
+ ADD COLUMN IF NOT EXISTS short_entry_notional_usd double precision NOT NULL DEFAULT 0,
840
+ ADD COLUMN IF NOT EXISTS long_exit_notional_usd double precision NOT NULL DEFAULT 0,
841
+ ADD COLUMN IF NOT EXISTS short_exit_notional_usd double precision NOT NULL DEFAULT 0,
842
+ ADD COLUMN IF NOT EXISTS entry_net_notional_usd double precision NOT NULL DEFAULT 0,
843
+ ADD COLUMN IF NOT EXISTS entry_long_share_pct double precision
844
+ `);
845
+ await pool.query(`
846
+ SELECT create_hypertable(
847
+ 'hyperliquid_whale_flow',
848
+ 'ts',
849
+ if_not_exists => TRUE,
850
+ chunk_time_interval => interval '7 days'
851
+ )
852
+ `);
853
+ await pool.query(`
854
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_flow_lookup_idx
855
+ ON hyperliquid_whale_flow (
856
+ universe_fingerprint,
857
+ whale_registry_fingerprint,
858
+ symbol,
859
+ interval,
860
+ ts DESC
861
+ )
862
+ `);
863
+ await pool.query(`
864
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_wallet_coverage (
865
+ universe_fingerprint text NOT NULL,
866
+ whale_registry_fingerprint text NOT NULL,
867
+ address text NOT NULL,
868
+ requested_from_ts timestamptz NOT NULL,
869
+ requested_to_ts timestamptz NOT NULL,
870
+ covered_from_ts timestamptz,
871
+ covered_to_ts timestamptz,
872
+ status text NOT NULL CHECK (status IN ('complete', 'truncated', 'failed')),
873
+ fills_count integer NOT NULL DEFAULT 0,
874
+ error text,
875
+ data_model_version integer NOT NULL DEFAULT 2,
876
+ checked_at timestamptz NOT NULL DEFAULT now(),
877
+ PRIMARY KEY (
878
+ universe_fingerprint,
879
+ whale_registry_fingerprint,
880
+ address,
881
+ requested_from_ts,
882
+ requested_to_ts
883
+ )
884
+ )
885
+ `);
886
+ await pool.query(`
887
+ ALTER TABLE hyperliquid_whale_wallet_coverage
888
+ ADD COLUMN IF NOT EXISTS data_model_version integer NOT NULL DEFAULT 2
889
+ `);
890
+ await pool.query(`
891
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_wallet_coverage_lookup_idx
892
+ ON hyperliquid_whale_wallet_coverage (
893
+ universe_fingerprint,
894
+ whale_registry_fingerprint,
895
+ address,
896
+ requested_from_ts,
897
+ requested_to_ts
898
+ )
899
+ `);
900
+ await pool.query(`
901
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_coverage_1m (
902
+ ts timestamptz NOT NULL,
903
+ covered_whales integer NOT NULL,
904
+ expected_whales integer NOT NULL,
905
+ coverage_pct double precision NOT NULL,
906
+ universe_fingerprint text NOT NULL,
907
+ whale_registry_fingerprint text NOT NULL,
908
+ source text,
909
+ data_model_version integer NOT NULL DEFAULT 2,
910
+ ingested_at timestamptz NOT NULL DEFAULT now(),
911
+ PRIMARY KEY (
912
+ universe_fingerprint,
913
+ whale_registry_fingerprint,
914
+ ts
915
+ )
916
+ )
917
+ `);
918
+ await pool.query(`
919
+ ALTER TABLE hyperliquid_whale_coverage_1m
920
+ ADD COLUMN IF NOT EXISTS data_model_version integer NOT NULL DEFAULT 2
921
+ `);
922
+ await pool.query(`
923
+ SELECT create_hypertable(
924
+ 'hyperliquid_whale_coverage_1m',
925
+ 'ts',
926
+ if_not_exists => TRUE,
927
+ chunk_time_interval => interval '7 days'
928
+ )
929
+ `);
930
+ await pool.query(`
931
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_coverage_1m_lookup_idx
932
+ ON hyperliquid_whale_coverage_1m (
933
+ universe_fingerprint,
934
+ whale_registry_fingerprint,
935
+ ts DESC
936
+ )
937
+ `);
938
+ hyperliquidWhaleSchemaReady = true;
939
+ }
940
+ ).finally(() => {
941
+ hyperliquidWhaleSchemaReadyPromise = null;
942
+ });
943
+ await hyperliquidWhaleSchemaReadyPromise;
944
+ };
700
945
  async function upsertDerivatives(rows) {
701
946
  if (!rows.length) return;
702
947
  await ensureDerivativesSchema();
@@ -1275,6 +1520,491 @@ async function upsertMarketTradeFlowRows(rows) {
1275
1520
  flat
1276
1521
  );
1277
1522
  }
1523
+ async function upsertHyperliquidWhaleTradeEvents(rows) {
1524
+ if (!rows.length) return;
1525
+ await ensureHyperliquidWhaleSchema();
1526
+ const cols = [
1527
+ "symbol",
1528
+ "ts",
1529
+ "tid",
1530
+ "price",
1531
+ "size",
1532
+ "notional_usd",
1533
+ "buyer_address",
1534
+ "seller_address",
1535
+ "buyer_tracked",
1536
+ "seller_tracked",
1537
+ "buyer_start_position",
1538
+ "buyer_end_position",
1539
+ "buyer_position_action",
1540
+ "buyer_closed_pnl",
1541
+ "buyer_liquidation",
1542
+ "seller_start_position",
1543
+ "seller_end_position",
1544
+ "seller_position_action",
1545
+ "seller_closed_pnl",
1546
+ "seller_liquidation",
1547
+ "universe_fingerprint",
1548
+ "whale_registry_fingerprint",
1549
+ "source"
1550
+ ];
1551
+ const maxRows = getSafeBulkInsertRows(cols.length);
1552
+ if (rows.length > maxRows) {
1553
+ for (let index = 0; index < rows.length; index += maxRows) {
1554
+ await upsertHyperliquidWhaleTradeEvents(
1555
+ rows.slice(index, index + maxRows)
1556
+ );
1557
+ }
1558
+ return;
1559
+ }
1560
+ const valuesSql = rows.map(
1561
+ (_, rowIndex) => `(${cols.map((__, colIndex) => `$${rowIndex * cols.length + colIndex + 1}`).join(",")})`
1562
+ ).join(",");
1563
+ const flat = rows.flatMap((row) => [
1564
+ row.symbol,
1565
+ row.ts,
1566
+ row.tid,
1567
+ row.price,
1568
+ row.size,
1569
+ row.notionalUsd,
1570
+ row.buyerAddress ?? null,
1571
+ row.sellerAddress ?? null,
1572
+ row.buyerTracked,
1573
+ row.sellerTracked,
1574
+ row.buyerStartPosition ?? null,
1575
+ row.buyerEndPosition ?? null,
1576
+ row.buyerPositionAction ?? null,
1577
+ row.buyerClosedPnl ?? null,
1578
+ row.buyerLiquidation ?? null,
1579
+ row.sellerStartPosition ?? null,
1580
+ row.sellerEndPosition ?? null,
1581
+ row.sellerPositionAction ?? null,
1582
+ row.sellerClosedPnl ?? null,
1583
+ row.sellerLiquidation ?? null,
1584
+ row.universeFingerprint,
1585
+ row.whaleRegistryFingerprint,
1586
+ row.source ?? null
1587
+ ]);
1588
+ await getPool().query(
1589
+ `
1590
+ INSERT INTO hyperliquid_whale_trade_events (${cols.join(",")})
1591
+ VALUES ${valuesSql}
1592
+ ON CONFLICT (
1593
+ universe_fingerprint,
1594
+ whale_registry_fingerprint,
1595
+ symbol,
1596
+ ts,
1597
+ tid
1598
+ ) DO UPDATE SET
1599
+ buyer_address = COALESCE(
1600
+ hyperliquid_whale_trade_events.buyer_address,
1601
+ EXCLUDED.buyer_address
1602
+ ),
1603
+ seller_address = COALESCE(
1604
+ hyperliquid_whale_trade_events.seller_address,
1605
+ EXCLUDED.seller_address
1606
+ ),
1607
+ buyer_tracked = hyperliquid_whale_trade_events.buyer_tracked OR EXCLUDED.buyer_tracked,
1608
+ seller_tracked = hyperliquid_whale_trade_events.seller_tracked OR EXCLUDED.seller_tracked,
1609
+ buyer_start_position = COALESCE(
1610
+ hyperliquid_whale_trade_events.buyer_start_position,
1611
+ EXCLUDED.buyer_start_position
1612
+ ),
1613
+ buyer_end_position = COALESCE(
1614
+ hyperliquid_whale_trade_events.buyer_end_position,
1615
+ EXCLUDED.buyer_end_position
1616
+ ),
1617
+ buyer_position_action = COALESCE(
1618
+ hyperliquid_whale_trade_events.buyer_position_action,
1619
+ EXCLUDED.buyer_position_action
1620
+ ),
1621
+ buyer_closed_pnl = COALESCE(
1622
+ hyperliquid_whale_trade_events.buyer_closed_pnl,
1623
+ EXCLUDED.buyer_closed_pnl
1624
+ ),
1625
+ buyer_liquidation = COALESCE(
1626
+ hyperliquid_whale_trade_events.buyer_liquidation,
1627
+ EXCLUDED.buyer_liquidation
1628
+ ),
1629
+ seller_start_position = COALESCE(
1630
+ hyperliquid_whale_trade_events.seller_start_position,
1631
+ EXCLUDED.seller_start_position
1632
+ ),
1633
+ seller_end_position = COALESCE(
1634
+ hyperliquid_whale_trade_events.seller_end_position,
1635
+ EXCLUDED.seller_end_position
1636
+ ),
1637
+ seller_position_action = COALESCE(
1638
+ hyperliquid_whale_trade_events.seller_position_action,
1639
+ EXCLUDED.seller_position_action
1640
+ ),
1641
+ seller_closed_pnl = COALESCE(
1642
+ hyperliquid_whale_trade_events.seller_closed_pnl,
1643
+ EXCLUDED.seller_closed_pnl
1644
+ ),
1645
+ seller_liquidation = COALESCE(
1646
+ hyperliquid_whale_trade_events.seller_liquidation,
1647
+ EXCLUDED.seller_liquidation
1648
+ ),
1649
+ source = EXCLUDED.source,
1650
+ ingested_at = now()
1651
+ `,
1652
+ flat
1653
+ );
1654
+ }
1655
+ async function upsertHyperliquidWhaleFlowRows(rows) {
1656
+ if (!rows.length) return;
1657
+ await ensureHyperliquidWhaleSchema();
1658
+ const cols = [
1659
+ "symbol",
1660
+ "interval",
1661
+ "ts",
1662
+ "trades",
1663
+ "whale_sides",
1664
+ "unique_whales",
1665
+ "whale_addresses",
1666
+ "buy_notional_usd",
1667
+ "sell_notional_usd",
1668
+ "net_notional_usd",
1669
+ "buy_share_pct",
1670
+ "position_aware_whale_sides",
1671
+ "long_entry_whale_addresses",
1672
+ "short_entry_whale_addresses",
1673
+ "long_exit_whale_addresses",
1674
+ "short_exit_whale_addresses",
1675
+ "long_entry_notional_usd",
1676
+ "short_entry_notional_usd",
1677
+ "long_exit_notional_usd",
1678
+ "short_exit_notional_usd",
1679
+ "entry_net_notional_usd",
1680
+ "entry_long_share_pct",
1681
+ "universe_fingerprint",
1682
+ "whale_registry_fingerprint",
1683
+ "source"
1684
+ ];
1685
+ const maxRows = getSafeBulkInsertRows(cols.length);
1686
+ if (rows.length > maxRows) {
1687
+ for (let index = 0; index < rows.length; index += maxRows) {
1688
+ await upsertHyperliquidWhaleFlowRows(rows.slice(index, index + maxRows));
1689
+ }
1690
+ return;
1691
+ }
1692
+ const valuesSql = rows.map(
1693
+ (_, rowIndex) => `(${cols.map((__, colIndex) => `$${rowIndex * cols.length + colIndex + 1}`).join(",")})`
1694
+ ).join(",");
1695
+ const flat = rows.flatMap((row) => [
1696
+ row.symbol,
1697
+ row.interval,
1698
+ row.ts,
1699
+ row.trades,
1700
+ row.whaleSides,
1701
+ row.uniqueWhales,
1702
+ row.whaleAddresses ?? [],
1703
+ row.buyNotionalUsd,
1704
+ row.sellNotionalUsd,
1705
+ row.netNotionalUsd,
1706
+ row.buySharePct ?? null,
1707
+ row.positionAwareWhaleSides,
1708
+ row.longEntryWhaleAddresses ?? [],
1709
+ row.shortEntryWhaleAddresses ?? [],
1710
+ row.longExitWhaleAddresses ?? [],
1711
+ row.shortExitWhaleAddresses ?? [],
1712
+ row.longEntryNotionalUsd,
1713
+ row.shortEntryNotionalUsd,
1714
+ row.longExitNotionalUsd,
1715
+ row.shortExitNotionalUsd,
1716
+ row.entryNetNotionalUsd,
1717
+ row.entryLongSharePct ?? null,
1718
+ row.universeFingerprint,
1719
+ row.whaleRegistryFingerprint,
1720
+ row.source ?? null
1721
+ ]);
1722
+ await getPool().query(
1723
+ `
1724
+ INSERT INTO hyperliquid_whale_flow (${cols.join(",")})
1725
+ VALUES ${valuesSql}
1726
+ ON CONFLICT (
1727
+ universe_fingerprint,
1728
+ whale_registry_fingerprint,
1729
+ symbol,
1730
+ interval,
1731
+ ts
1732
+ ) DO UPDATE SET
1733
+ trades = EXCLUDED.trades,
1734
+ whale_sides = EXCLUDED.whale_sides,
1735
+ unique_whales = EXCLUDED.unique_whales,
1736
+ whale_addresses = EXCLUDED.whale_addresses,
1737
+ buy_notional_usd = EXCLUDED.buy_notional_usd,
1738
+ sell_notional_usd = EXCLUDED.sell_notional_usd,
1739
+ net_notional_usd = EXCLUDED.net_notional_usd,
1740
+ buy_share_pct = EXCLUDED.buy_share_pct,
1741
+ position_aware_whale_sides = EXCLUDED.position_aware_whale_sides,
1742
+ long_entry_whale_addresses = EXCLUDED.long_entry_whale_addresses,
1743
+ short_entry_whale_addresses = EXCLUDED.short_entry_whale_addresses,
1744
+ long_exit_whale_addresses = EXCLUDED.long_exit_whale_addresses,
1745
+ short_exit_whale_addresses = EXCLUDED.short_exit_whale_addresses,
1746
+ long_entry_notional_usd = EXCLUDED.long_entry_notional_usd,
1747
+ short_entry_notional_usd = EXCLUDED.short_entry_notional_usd,
1748
+ long_exit_notional_usd = EXCLUDED.long_exit_notional_usd,
1749
+ short_exit_notional_usd = EXCLUDED.short_exit_notional_usd,
1750
+ entry_net_notional_usd = EXCLUDED.entry_net_notional_usd,
1751
+ entry_long_share_pct = EXCLUDED.entry_long_share_pct,
1752
+ source = EXCLUDED.source,
1753
+ ingested_at = now()
1754
+ `,
1755
+ flat
1756
+ );
1757
+ }
1758
+ async function rebuildHyperliquidWhaleFlowRows(params) {
1759
+ await ensureHyperliquidWhaleSchema();
1760
+ const client = await getPool().connect();
1761
+ try {
1762
+ await client.query("BEGIN");
1763
+ const result = await client.query(
1764
+ `
1765
+ WITH source_events AS (
1766
+ SELECT *
1767
+ FROM hyperliquid_whale_trade_events
1768
+ WHERE universe_fingerprint = $1
1769
+ AND whale_registry_fingerprint = $2
1770
+ AND ts >= to_timestamp($3/1000.0)
1771
+ AND ts < to_timestamp($4/1000.0)
1772
+ ), metrics AS (
1773
+ SELECT
1774
+ symbol,
1775
+ date_trunc('minute', ts) AS bucket_ts,
1776
+ COUNT(*)::int AS trades,
1777
+ SUM(buyer_tracked::int + seller_tracked::int)::int AS whale_sides,
1778
+ SUM(CASE WHEN buyer_tracked THEN notional_usd ELSE 0 END) AS buy_notional_usd,
1779
+ SUM(CASE WHEN seller_tracked THEN notional_usd ELSE 0 END) AS sell_notional_usd
1780
+ FROM source_events
1781
+ GROUP BY symbol, date_trunc('minute', ts)
1782
+ ), position_legs AS (
1783
+ SELECT
1784
+ symbol,
1785
+ ts,
1786
+ price,
1787
+ buyer_address AS whale_address,
1788
+ buyer_start_position AS start_position,
1789
+ buyer_end_position AS end_position
1790
+ FROM source_events
1791
+ WHERE buyer_tracked
1792
+ AND buyer_address IS NOT NULL
1793
+ AND buyer_start_position IS NOT NULL
1794
+ AND buyer_end_position IS NOT NULL
1795
+ UNION ALL
1796
+ SELECT
1797
+ symbol,
1798
+ ts,
1799
+ price,
1800
+ seller_address AS whale_address,
1801
+ seller_start_position AS start_position,
1802
+ seller_end_position AS end_position
1803
+ FROM source_events
1804
+ WHERE seller_tracked
1805
+ AND seller_address IS NOT NULL
1806
+ AND seller_start_position IS NOT NULL
1807
+ AND seller_end_position IS NOT NULL
1808
+ ), classified_legs AS (
1809
+ SELECT
1810
+ *,
1811
+ GREATEST(
1812
+ GREATEST(end_position, 0) - GREATEST(start_position, 0),
1813
+ 0
1814
+ ) AS long_entry_size,
1815
+ GREATEST(
1816
+ GREATEST(-end_position, 0) - GREATEST(-start_position, 0),
1817
+ 0
1818
+ ) AS short_entry_size,
1819
+ GREATEST(
1820
+ GREATEST(start_position, 0) - GREATEST(end_position, 0),
1821
+ 0
1822
+ ) AS long_exit_size,
1823
+ GREATEST(
1824
+ GREATEST(-start_position, 0) - GREATEST(-end_position, 0),
1825
+ 0
1826
+ ) AS short_exit_size
1827
+ FROM position_legs
1828
+ ), position_metrics AS (
1829
+ SELECT
1830
+ symbol,
1831
+ date_trunc('minute', ts) AS bucket_ts,
1832
+ COUNT(*)::int AS position_aware_whale_sides,
1833
+ SUM(long_entry_size * price) AS long_entry_notional_usd,
1834
+ SUM(short_entry_size * price) AS short_entry_notional_usd,
1835
+ SUM(long_exit_size * price) AS long_exit_notional_usd,
1836
+ SUM(short_exit_size * price) AS short_exit_notional_usd,
1837
+ COALESCE(
1838
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
1839
+ FILTER (WHERE long_entry_size > 0),
1840
+ '{}'
1841
+ ) AS long_entry_whale_addresses,
1842
+ COALESCE(
1843
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
1844
+ FILTER (WHERE short_entry_size > 0),
1845
+ '{}'
1846
+ ) AS short_entry_whale_addresses,
1847
+ COALESCE(
1848
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
1849
+ FILTER (WHERE long_exit_size > 0),
1850
+ '{}'
1851
+ ) AS long_exit_whale_addresses,
1852
+ COALESCE(
1853
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
1854
+ FILTER (WHERE short_exit_size > 0),
1855
+ '{}'
1856
+ ) AS short_exit_whale_addresses
1857
+ FROM classified_legs
1858
+ GROUP BY symbol, date_trunc('minute', ts)
1859
+ ), addresses AS (
1860
+ SELECT
1861
+ symbol,
1862
+ date_trunc('minute', ts) AS bucket_ts,
1863
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address) AS whale_addresses
1864
+ FROM source_events
1865
+ CROSS JOIN LATERAL UNNEST(ARRAY[
1866
+ CASE WHEN buyer_tracked THEN buyer_address END,
1867
+ CASE WHEN seller_tracked THEN seller_address END
1868
+ ]) AS expanded(whale_address)
1869
+ WHERE whale_address IS NOT NULL
1870
+ GROUP BY symbol, date_trunc('minute', ts)
1871
+ )
1872
+ INSERT INTO hyperliquid_whale_flow (
1873
+ symbol,
1874
+ interval,
1875
+ ts,
1876
+ trades,
1877
+ whale_sides,
1878
+ unique_whales,
1879
+ whale_addresses,
1880
+ buy_notional_usd,
1881
+ sell_notional_usd,
1882
+ net_notional_usd,
1883
+ buy_share_pct,
1884
+ position_aware_whale_sides,
1885
+ long_entry_whale_addresses,
1886
+ short_entry_whale_addresses,
1887
+ long_exit_whale_addresses,
1888
+ short_exit_whale_addresses,
1889
+ long_entry_notional_usd,
1890
+ short_entry_notional_usd,
1891
+ long_exit_notional_usd,
1892
+ short_exit_notional_usd,
1893
+ entry_net_notional_usd,
1894
+ entry_long_share_pct,
1895
+ universe_fingerprint,
1896
+ whale_registry_fingerprint,
1897
+ source
1898
+ )
1899
+ SELECT
1900
+ metrics.symbol,
1901
+ '1m',
1902
+ metrics.bucket_ts,
1903
+ metrics.trades,
1904
+ metrics.whale_sides,
1905
+ COALESCE(CARDINALITY(addresses.whale_addresses), 0),
1906
+ COALESCE(addresses.whale_addresses, '{}'),
1907
+ metrics.buy_notional_usd,
1908
+ metrics.sell_notional_usd,
1909
+ metrics.buy_notional_usd - metrics.sell_notional_usd,
1910
+ CASE
1911
+ WHEN metrics.buy_notional_usd + metrics.sell_notional_usd > 0
1912
+ THEN metrics.buy_notional_usd /
1913
+ (metrics.buy_notional_usd + metrics.sell_notional_usd)
1914
+ ELSE NULL
1915
+ END,
1916
+ COALESCE(position_metrics.position_aware_whale_sides, 0),
1917
+ COALESCE(position_metrics.long_entry_whale_addresses, '{}'),
1918
+ COALESCE(position_metrics.short_entry_whale_addresses, '{}'),
1919
+ COALESCE(position_metrics.long_exit_whale_addresses, '{}'),
1920
+ COALESCE(position_metrics.short_exit_whale_addresses, '{}'),
1921
+ COALESCE(position_metrics.long_entry_notional_usd, 0),
1922
+ COALESCE(position_metrics.short_entry_notional_usd, 0),
1923
+ COALESCE(position_metrics.long_exit_notional_usd, 0),
1924
+ COALESCE(position_metrics.short_exit_notional_usd, 0),
1925
+ COALESCE(position_metrics.long_entry_notional_usd, 0) -
1926
+ COALESCE(position_metrics.short_entry_notional_usd, 0),
1927
+ CASE
1928
+ WHEN COALESCE(position_metrics.long_entry_notional_usd, 0) +
1929
+ COALESCE(position_metrics.short_entry_notional_usd, 0) > 0
1930
+ THEN COALESCE(position_metrics.long_entry_notional_usd, 0) /
1931
+ (
1932
+ COALESCE(position_metrics.long_entry_notional_usd, 0) +
1933
+ COALESCE(position_metrics.short_entry_notional_usd, 0)
1934
+ )
1935
+ ELSE NULL
1936
+ END,
1937
+ $1,
1938
+ $2,
1939
+ CASE
1940
+ WHEN COALESCE(position_metrics.position_aware_whale_sides, 0) > 0
1941
+ THEN 'hyperliquid_user_fills'
1942
+ ELSE 'hyperliquid_trades'
1943
+ END
1944
+ FROM metrics
1945
+ LEFT JOIN addresses USING (symbol, bucket_ts)
1946
+ LEFT JOIN position_metrics USING (symbol, bucket_ts)
1947
+ ON CONFLICT (
1948
+ universe_fingerprint,
1949
+ whale_registry_fingerprint,
1950
+ symbol,
1951
+ interval,
1952
+ ts
1953
+ ) DO UPDATE SET
1954
+ trades = EXCLUDED.trades,
1955
+ whale_sides = EXCLUDED.whale_sides,
1956
+ unique_whales = EXCLUDED.unique_whales,
1957
+ whale_addresses = EXCLUDED.whale_addresses,
1958
+ buy_notional_usd = EXCLUDED.buy_notional_usd,
1959
+ sell_notional_usd = EXCLUDED.sell_notional_usd,
1960
+ net_notional_usd = EXCLUDED.net_notional_usd,
1961
+ buy_share_pct = EXCLUDED.buy_share_pct,
1962
+ position_aware_whale_sides = EXCLUDED.position_aware_whale_sides,
1963
+ long_entry_whale_addresses = EXCLUDED.long_entry_whale_addresses,
1964
+ short_entry_whale_addresses = EXCLUDED.short_entry_whale_addresses,
1965
+ long_exit_whale_addresses = EXCLUDED.long_exit_whale_addresses,
1966
+ short_exit_whale_addresses = EXCLUDED.short_exit_whale_addresses,
1967
+ long_entry_notional_usd = EXCLUDED.long_entry_notional_usd,
1968
+ short_entry_notional_usd = EXCLUDED.short_entry_notional_usd,
1969
+ long_exit_notional_usd = EXCLUDED.long_exit_notional_usd,
1970
+ short_exit_notional_usd = EXCLUDED.short_exit_notional_usd,
1971
+ entry_net_notional_usd = EXCLUDED.entry_net_notional_usd,
1972
+ entry_long_share_pct = EXCLUDED.entry_long_share_pct,
1973
+ source = EXCLUDED.source,
1974
+ ingested_at = now()
1975
+ RETURNING 1
1976
+ `,
1977
+ [
1978
+ params.universeFingerprint,
1979
+ params.whaleRegistryFingerprint,
1980
+ params.fromMs,
1981
+ params.toMs
1982
+ ]
1983
+ );
1984
+ if (params.deleteEventsBeforeMs != null) {
1985
+ await client.query(
1986
+ `
1987
+ DELETE FROM hyperliquid_whale_trade_events
1988
+ WHERE universe_fingerprint = $1
1989
+ AND whale_registry_fingerprint = $2
1990
+ AND ts < to_timestamp($3/1000.0)
1991
+ `,
1992
+ [
1993
+ params.universeFingerprint,
1994
+ params.whaleRegistryFingerprint,
1995
+ params.deleteEventsBeforeMs
1996
+ ]
1997
+ );
1998
+ }
1999
+ await client.query("COMMIT");
2000
+ return result.rowCount ?? 0;
2001
+ } catch (error) {
2002
+ await client.query("ROLLBACK");
2003
+ throw error;
2004
+ } finally {
2005
+ client.release();
2006
+ }
2007
+ }
1278
2008
  async function upsertMarketBreadthRows(rows) {
1279
2009
  if (!rows.length) return;
1280
2010
  await ensureBinanceMarketSchema();
@@ -1798,6 +2528,523 @@ async function getLatestMarketTradeFlow(params) {
1798
2528
  stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs
1799
2529
  };
1800
2530
  }
2531
+ var HYPERLIQUID_CONTEXT_INTERVAL_MS = {
2532
+ "1m": 6e4,
2533
+ "5m": 5 * 6e4,
2534
+ "15m": 15 * 6e4,
2535
+ "1h": 60 * 6e4
2536
+ };
2537
+ async function getHyperliquidWhaleWalletCoverage(params) {
2538
+ await ensureHyperliquidWhaleSchema();
2539
+ const result = await getPool().query(
2540
+ `
2541
+ SELECT
2542
+ status,
2543
+ covered_from_ts,
2544
+ covered_to_ts,
2545
+ fills_count,
2546
+ error,
2547
+ checked_at
2548
+ FROM hyperliquid_whale_wallet_coverage
2549
+ WHERE universe_fingerprint = $1
2550
+ AND whale_registry_fingerprint = $2
2551
+ AND address = $3
2552
+ AND data_model_version = $6
2553
+ AND (
2554
+ (
2555
+ covered_from_ts <= to_timestamp($4/1000.0)
2556
+ AND covered_to_ts >= to_timestamp($5/1000.0)
2557
+ )
2558
+ OR (
2559
+ requested_from_ts = to_timestamp($4/1000.0)
2560
+ AND requested_to_ts = to_timestamp($5/1000.0)
2561
+ )
2562
+ )
2563
+ ORDER BY checked_at DESC
2564
+ LIMIT 1
2565
+ `,
2566
+ [
2567
+ params.universeFingerprint,
2568
+ params.whaleRegistryFingerprint,
2569
+ params.address.toLowerCase(),
2570
+ params.fromMs,
2571
+ params.toMs,
2572
+ import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2573
+ ]
2574
+ );
2575
+ const row = result.rows[0];
2576
+ if (!row) return null;
2577
+ return {
2578
+ status: row.covered_from_ts != null && row.covered_to_ts != null && new Date(row.covered_from_ts).getTime() <= params.fromMs && new Date(row.covered_to_ts).getTime() >= params.toMs ? "complete" : String(row.status),
2579
+ coveredFromMs: row.covered_from_ts == null ? null : new Date(row.covered_from_ts).getTime(),
2580
+ coveredToMs: row.covered_to_ts == null ? null : new Date(row.covered_to_ts).getTime(),
2581
+ fillsCount: Number(row.fills_count) || 0,
2582
+ error: row.error == null ? null : String(row.error),
2583
+ checkedAt: new Date(row.checked_at)
2584
+ };
2585
+ }
2586
+ async function upsertHyperliquidWhaleWalletCoverage(params) {
2587
+ await ensureHyperliquidWhaleSchema();
2588
+ await getPool().query(
2589
+ `
2590
+ INSERT INTO hyperliquid_whale_wallet_coverage (
2591
+ universe_fingerprint,
2592
+ whale_registry_fingerprint,
2593
+ address,
2594
+ requested_from_ts,
2595
+ requested_to_ts,
2596
+ covered_from_ts,
2597
+ covered_to_ts,
2598
+ status,
2599
+ fills_count,
2600
+ error,
2601
+ data_model_version
2602
+ ) VALUES (
2603
+ $1,
2604
+ $2,
2605
+ $3,
2606
+ to_timestamp($4/1000.0),
2607
+ to_timestamp($5/1000.0),
2608
+ CASE WHEN $6::double precision IS NULL THEN NULL ELSE to_timestamp($6/1000.0) END,
2609
+ CASE WHEN $7::double precision IS NULL THEN NULL ELSE to_timestamp($7/1000.0) END,
2610
+ $8,
2611
+ $9,
2612
+ $10,
2613
+ $11
2614
+ )
2615
+ ON CONFLICT (
2616
+ universe_fingerprint,
2617
+ whale_registry_fingerprint,
2618
+ address,
2619
+ requested_from_ts,
2620
+ requested_to_ts
2621
+ ) DO UPDATE SET
2622
+ covered_from_ts = EXCLUDED.covered_from_ts,
2623
+ covered_to_ts = EXCLUDED.covered_to_ts,
2624
+ status = EXCLUDED.status,
2625
+ fills_count = EXCLUDED.fills_count,
2626
+ error = EXCLUDED.error,
2627
+ data_model_version = EXCLUDED.data_model_version,
2628
+ checked_at = now()
2629
+ `,
2630
+ [
2631
+ params.universeFingerprint,
2632
+ params.whaleRegistryFingerprint,
2633
+ params.address.toLowerCase(),
2634
+ params.fromMs,
2635
+ params.toMs,
2636
+ params.coveredFromMs,
2637
+ params.coveredToMs,
2638
+ params.status,
2639
+ params.fillsCount,
2640
+ params.error ?? null,
2641
+ import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2642
+ ]
2643
+ );
2644
+ }
2645
+ async function rebuildHyperliquidWhaleCoverageRows(params) {
2646
+ await ensureHyperliquidWhaleSchema();
2647
+ if (params.toMs <= params.fromMs) return 0;
2648
+ const result = await getPool().query(
2649
+ `
2650
+ WITH buckets AS (
2651
+ SELECT generate_series(
2652
+ to_timestamp($3/1000.0),
2653
+ to_timestamp($4/1000.0) - interval '1 minute',
2654
+ interval '1 minute'
2655
+ ) AS ts
2656
+ ), coverage AS (
2657
+ SELECT
2658
+ buckets.ts,
2659
+ COUNT(DISTINCT wallets.address)::int AS covered_whales
2660
+ FROM buckets
2661
+ LEFT JOIN hyperliquid_whale_wallet_coverage wallets
2662
+ ON wallets.universe_fingerprint = $1
2663
+ AND wallets.whale_registry_fingerprint = $2
2664
+ AND wallets.data_model_version = $6
2665
+ AND wallets.status IN ('complete', 'truncated')
2666
+ AND wallets.covered_from_ts <= buckets.ts
2667
+ AND wallets.covered_to_ts >= buckets.ts + interval '1 minute'
2668
+ GROUP BY buckets.ts
2669
+ )
2670
+ INSERT INTO hyperliquid_whale_coverage_1m (
2671
+ ts,
2672
+ covered_whales,
2673
+ expected_whales,
2674
+ coverage_pct,
2675
+ universe_fingerprint,
2676
+ whale_registry_fingerprint,
2677
+ source
2678
+ ,data_model_version
2679
+ )
2680
+ SELECT
2681
+ ts,
2682
+ covered_whales,
2683
+ $5,
2684
+ CASE WHEN $5 > 0 THEN covered_whales::double precision / $5 ELSE 0 END,
2685
+ $1,
2686
+ $2,
2687
+ 'hyperliquid_user_fills',
2688
+ $6
2689
+ FROM coverage
2690
+ ON CONFLICT (
2691
+ universe_fingerprint,
2692
+ whale_registry_fingerprint,
2693
+ ts
2694
+ ) DO UPDATE SET
2695
+ covered_whales = EXCLUDED.covered_whales,
2696
+ expected_whales = EXCLUDED.expected_whales,
2697
+ coverage_pct = EXCLUDED.coverage_pct,
2698
+ source = EXCLUDED.source,
2699
+ data_model_version = EXCLUDED.data_model_version,
2700
+ ingested_at = now()
2701
+ RETURNING 1
2702
+ `,
2703
+ [
2704
+ params.universeFingerprint,
2705
+ params.whaleRegistryFingerprint,
2706
+ params.fromMs,
2707
+ params.toMs,
2708
+ params.expectedWhales,
2709
+ import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2710
+ ]
2711
+ );
2712
+ return result.rowCount ?? 0;
2713
+ }
2714
+ async function upsertHyperliquidWhaleCoverageRows(rows) {
2715
+ if (!rows.length) return;
2716
+ await ensureHyperliquidWhaleSchema();
2717
+ const cols = [
2718
+ "ts",
2719
+ "covered_whales",
2720
+ "expected_whales",
2721
+ "coverage_pct",
2722
+ "universe_fingerprint",
2723
+ "whale_registry_fingerprint",
2724
+ "source",
2725
+ "data_model_version"
2726
+ ];
2727
+ const maxRows = Math.floor(PG_SAFE_MAX_BIND_PARAMS / cols.length);
2728
+ if (rows.length > maxRows) {
2729
+ for (let index = 0; index < rows.length; index += maxRows) {
2730
+ await upsertHyperliquidWhaleCoverageRows(
2731
+ rows.slice(index, index + maxRows)
2732
+ );
2733
+ }
2734
+ return;
2735
+ }
2736
+ const values = [];
2737
+ const tuples = rows.map((row, rowIndex) => {
2738
+ const offset = rowIndex * cols.length;
2739
+ values.push(
2740
+ row.ts,
2741
+ row.coveredWhales,
2742
+ row.expectedWhales,
2743
+ row.coveragePct,
2744
+ row.universeFingerprint,
2745
+ row.whaleRegistryFingerprint,
2746
+ row.source ?? null,
2747
+ row.dataModelVersion ?? import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2748
+ );
2749
+ return `(${cols.map((_, colIndex) => `$${offset + colIndex + 1}`).join(",")})`;
2750
+ });
2751
+ await getPool().query(
2752
+ `
2753
+ INSERT INTO hyperliquid_whale_coverage_1m (${cols.join(",")})
2754
+ VALUES ${tuples.join(",")}
2755
+ ON CONFLICT (
2756
+ universe_fingerprint,
2757
+ whale_registry_fingerprint,
2758
+ ts
2759
+ ) DO UPDATE SET
2760
+ covered_whales = EXCLUDED.covered_whales,
2761
+ expected_whales = EXCLUDED.expected_whales,
2762
+ coverage_pct = EXCLUDED.coverage_pct,
2763
+ source = EXCLUDED.source,
2764
+ data_model_version = EXCLUDED.data_model_version,
2765
+ ingested_at = now()
2766
+ `,
2767
+ values
2768
+ );
2769
+ }
2770
+ async function getHyperliquidWhaleCoverageSeriesRows(params) {
2771
+ await ensureHyperliquidWhaleSchema();
2772
+ const result = await getPool().query(
2773
+ `
2774
+ SELECT
2775
+ ts,
2776
+ covered_whales,
2777
+ expected_whales,
2778
+ coverage_pct
2779
+ FROM hyperliquid_whale_coverage_1m
2780
+ WHERE universe_fingerprint = $1
2781
+ AND whale_registry_fingerprint = $2
2782
+ AND data_model_version = $3
2783
+ AND ts >= to_timestamp($4/1000.0)
2784
+ AND ts < to_timestamp($5/1000.0)
2785
+ ORDER BY ts
2786
+ `,
2787
+ [
2788
+ params.universeFingerprint,
2789
+ params.whaleRegistryFingerprint,
2790
+ import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION,
2791
+ params.fromMs,
2792
+ params.toMs
2793
+ ]
2794
+ );
2795
+ return result.rows.map((row) => ({
2796
+ ts: new Date(row.ts),
2797
+ coveredWhales: Number(row.covered_whales) || 0,
2798
+ expectedWhales: Number(row.expected_whales) || 0,
2799
+ coveragePct: Number(row.coverage_pct) || 0
2800
+ }));
2801
+ }
2802
+ async function getHyperliquidWhaleFlowSeriesRows(params) {
2803
+ await ensureHyperliquidWhaleSchema();
2804
+ const result = await getPool().query(
2805
+ `
2806
+ SELECT
2807
+ ts,
2808
+ trades,
2809
+ whale_sides,
2810
+ whale_addresses,
2811
+ buy_notional_usd,
2812
+ sell_notional_usd,
2813
+ position_aware_whale_sides,
2814
+ long_entry_whale_addresses,
2815
+ short_entry_whale_addresses,
2816
+ long_exit_whale_addresses,
2817
+ short_exit_whale_addresses,
2818
+ long_entry_notional_usd,
2819
+ short_entry_notional_usd,
2820
+ long_exit_notional_usd,
2821
+ short_exit_notional_usd
2822
+ FROM hyperliquid_whale_flow
2823
+ WHERE symbol = $1
2824
+ AND interval = '1m'
2825
+ AND universe_fingerprint = $2
2826
+ AND whale_registry_fingerprint = $3
2827
+ AND ts >= to_timestamp($4/1000.0)
2828
+ AND ts < to_timestamp($5/1000.0)
2829
+ ORDER BY ts
2830
+ `,
2831
+ [
2832
+ params.symbol,
2833
+ params.universeFingerprint,
2834
+ params.whaleRegistryFingerprint,
2835
+ params.fromMs,
2836
+ params.toMs
2837
+ ]
2838
+ );
2839
+ return result.rows.map((row) => ({
2840
+ ts: new Date(row.ts),
2841
+ trades: Number(row.trades) || 0,
2842
+ whaleSides: Number(row.whale_sides) || 0,
2843
+ whaleAddresses: Array.isArray(row.whale_addresses) ? row.whale_addresses.map(String) : [],
2844
+ buyNotionalUsd: Number(row.buy_notional_usd) || 0,
2845
+ sellNotionalUsd: Number(row.sell_notional_usd) || 0,
2846
+ positionAwareWhaleSides: Number(row.position_aware_whale_sides) || 0,
2847
+ longEntryWhaleAddresses: Array.isArray(row.long_entry_whale_addresses) ? row.long_entry_whale_addresses.map(String) : [],
2848
+ shortEntryWhaleAddresses: Array.isArray(row.short_entry_whale_addresses) ? row.short_entry_whale_addresses.map(String) : [],
2849
+ longExitWhaleAddresses: Array.isArray(row.long_exit_whale_addresses) ? row.long_exit_whale_addresses.map(String) : [],
2850
+ shortExitWhaleAddresses: Array.isArray(row.short_exit_whale_addresses) ? row.short_exit_whale_addresses.map(String) : [],
2851
+ longEntryNotionalUsd: Number(row.long_entry_notional_usd) || 0,
2852
+ shortEntryNotionalUsd: Number(row.short_entry_notional_usd) || 0,
2853
+ longExitNotionalUsd: Number(row.long_exit_notional_usd) || 0,
2854
+ shortExitNotionalUsd: Number(row.short_exit_notional_usd) || 0
2855
+ }));
2856
+ }
2857
+ async function getHyperliquidWhaleFlowAggregate(params) {
2858
+ await ensureHyperliquidWhaleSchema();
2859
+ const intervalMs = HYPERLIQUID_CONTEXT_INTERVAL_MS[params.interval];
2860
+ const expectedBuckets = Math.ceil(intervalMs / 6e4);
2861
+ const res = await getPool().query(
2862
+ `
2863
+ WITH coverage_rows AS (
2864
+ SELECT *
2865
+ FROM hyperliquid_whale_coverage_1m
2866
+ WHERE universe_fingerprint = $2
2867
+ AND whale_registry_fingerprint = $3
2868
+ AND data_model_version = $6
2869
+ AND ts >= to_timestamp(
2870
+ ($4::double precision - $5::double precision) / 1000.0
2871
+ )
2872
+ AND ts < to_timestamp($4/1000.0)
2873
+ ), coverage_summary AS (
2874
+ SELECT
2875
+ COUNT(*)::int AS coverage_buckets,
2876
+ MAX(ts) AS coverage_as_of_ts,
2877
+ MIN(covered_whales)::int AS covered_whales,
2878
+ MAX(expected_whales)::int AS expected_whales,
2879
+ MIN(coverage_pct) AS coverage_pct
2880
+ FROM coverage_rows
2881
+ ), window_rows AS (
2882
+ SELECT *
2883
+ FROM hyperliquid_whale_flow
2884
+ WHERE symbol = $1
2885
+ AND interval = '1m'
2886
+ AND universe_fingerprint = $2
2887
+ AND whale_registry_fingerprint = $3
2888
+ AND ts >= to_timestamp(
2889
+ ($4::double precision - $5::double precision) / 1000.0
2890
+ )
2891
+ AND ts < to_timestamp($4/1000.0)
2892
+ ), unique_addresses AS (
2893
+ SELECT COUNT(DISTINCT address)::int AS unique_whales
2894
+ FROM window_rows
2895
+ CROSS JOIN LATERAL UNNEST(whale_addresses) AS expanded(address)
2896
+ ), directional_counts AS (
2897
+ SELECT
2898
+ (
2899
+ SELECT COUNT(DISTINCT address)::int
2900
+ FROM window_rows
2901
+ CROSS JOIN LATERAL UNNEST(long_entry_whale_addresses) AS expanded(address)
2902
+ ) AS long_entry_whales,
2903
+ (
2904
+ SELECT COUNT(DISTINCT address)::int
2905
+ FROM window_rows
2906
+ CROSS JOIN LATERAL UNNEST(short_entry_whale_addresses) AS expanded(address)
2907
+ ) AS short_entry_whales,
2908
+ (
2909
+ SELECT COUNT(DISTINCT address)::int
2910
+ FROM window_rows
2911
+ CROSS JOIN LATERAL UNNEST(long_exit_whale_addresses) AS expanded(address)
2912
+ ) AS long_exit_whales,
2913
+ (
2914
+ SELECT COUNT(DISTINCT address)::int
2915
+ FROM window_rows
2916
+ CROSS JOIN LATERAL UNNEST(short_exit_whale_addresses) AS expanded(address)
2917
+ ) AS short_exit_whales
2918
+ )
2919
+ SELECT
2920
+ $1::text AS symbol,
2921
+ coverage_summary.coverage_as_of_ts AS as_of_ts,
2922
+ coverage_summary.coverage_buckets,
2923
+ coverage_summary.covered_whales,
2924
+ coverage_summary.expected_whales,
2925
+ coverage_summary.coverage_pct,
2926
+ COALESCE((SELECT SUM(trades) FROM window_rows), 0)::int AS trades,
2927
+ COALESCE((SELECT SUM(whale_sides) FROM window_rows), 0)::int AS whale_sides,
2928
+ COALESCE((SELECT unique_whales FROM unique_addresses), 0)::int AS unique_whales,
2929
+ COALESCE((SELECT SUM(buy_notional_usd) FROM window_rows), 0) AS buy_notional_usd,
2930
+ COALESCE((SELECT SUM(sell_notional_usd) FROM window_rows), 0) AS sell_notional_usd,
2931
+ COALESCE((SELECT SUM(net_notional_usd) FROM window_rows), 0) AS net_notional_usd,
2932
+ CASE
2933
+ WHEN COALESCE((SELECT SUM(buy_notional_usd + sell_notional_usd) FROM window_rows), 0) > 0
2934
+ THEN (SELECT SUM(buy_notional_usd) FROM window_rows) /
2935
+ (SELECT SUM(buy_notional_usd + sell_notional_usd) FROM window_rows)
2936
+ ELSE NULL
2937
+ END AS buy_share_pct,
2938
+ COALESCE((SELECT SUM(position_aware_whale_sides) FROM window_rows), 0)::int
2939
+ AS position_aware_whale_sides,
2940
+ CASE
2941
+ WHEN COALESCE((SELECT SUM(whale_sides) FROM window_rows), 0) > 0
2942
+ THEN COALESCE((SELECT SUM(position_aware_whale_sides) FROM window_rows), 0)::double precision /
2943
+ (SELECT SUM(whale_sides) FROM window_rows)
2944
+ ELSE 0
2945
+ END AS position_aware_pct,
2946
+ COALESCE((SELECT long_entry_whales FROM directional_counts), 0)::int AS long_entry_whales,
2947
+ COALESCE((SELECT short_entry_whales FROM directional_counts), 0)::int AS short_entry_whales,
2948
+ COALESCE((SELECT long_exit_whales FROM directional_counts), 0)::int AS long_exit_whales,
2949
+ COALESCE((SELECT short_exit_whales FROM directional_counts), 0)::int AS short_exit_whales,
2950
+ COALESCE((SELECT SUM(long_entry_notional_usd) FROM window_rows), 0)
2951
+ AS long_entry_notional_usd,
2952
+ COALESCE((SELECT SUM(short_entry_notional_usd) FROM window_rows), 0)
2953
+ AS short_entry_notional_usd,
2954
+ COALESCE((SELECT SUM(long_exit_notional_usd) FROM window_rows), 0)
2955
+ AS long_exit_notional_usd,
2956
+ COALESCE((SELECT SUM(short_exit_notional_usd) FROM window_rows), 0)
2957
+ AS short_exit_notional_usd,
2958
+ COALESCE((SELECT SUM(entry_net_notional_usd) FROM window_rows), 0)
2959
+ AS entry_net_notional_usd,
2960
+ CASE
2961
+ WHEN COALESCE((SELECT SUM(long_entry_notional_usd + short_entry_notional_usd) FROM window_rows), 0) > 0
2962
+ THEN (SELECT SUM(long_entry_notional_usd) FROM window_rows) /
2963
+ (SELECT SUM(long_entry_notional_usd + short_entry_notional_usd) FROM window_rows)
2964
+ ELSE NULL
2965
+ END AS entry_long_share_pct,
2966
+ (SELECT MAX(source) FROM window_rows) AS source
2967
+ FROM coverage_summary
2968
+ `,
2969
+ [
2970
+ params.symbol,
2971
+ params.universeFingerprint,
2972
+ params.whaleRegistryFingerprint,
2973
+ params.decisionTimeMs,
2974
+ intervalMs,
2975
+ import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2976
+ ]
2977
+ );
2978
+ const row = res.rows[0];
2979
+ if (!row?.as_of_ts || Number(row.coverage_buckets) !== expectedBuckets || Number(row.covered_whales) <= 0) {
2980
+ return null;
2981
+ }
2982
+ const asOfTs = new Date(row.as_of_ts);
2983
+ const ageMs = params.decisionTimeMs - (asOfTs.getTime() + 6e4);
2984
+ return {
2985
+ symbol: params.symbol,
2986
+ interval: params.interval,
2987
+ asOfTs,
2988
+ windowEndTs: new Date(params.decisionTimeMs),
2989
+ trades: Number(row.trades) || 0,
2990
+ whaleSides: Number(row.whale_sides) || 0,
2991
+ uniqueWhales: Number(row.unique_whales) || 0,
2992
+ coveredWhales: Number(row.covered_whales) || 0,
2993
+ expectedWhales: Number(row.expected_whales) || 0,
2994
+ coveragePct: Number(row.coverage_pct) || 0,
2995
+ buyNotionalUsd: Number(row.buy_notional_usd) || 0,
2996
+ sellNotionalUsd: Number(row.sell_notional_usd) || 0,
2997
+ netNotionalUsd: Number(row.net_notional_usd) || 0,
2998
+ buySharePct: row.buy_share_pct == null ? null : Number(row.buy_share_pct) || 0,
2999
+ positionAwareWhaleSides: Number(row.position_aware_whale_sides) || 0,
3000
+ positionAwarePct: Number(row.position_aware_pct) || 0,
3001
+ longEntryWhales: Number(row.long_entry_whales) || 0,
3002
+ shortEntryWhales: Number(row.short_entry_whales) || 0,
3003
+ longExitWhales: Number(row.long_exit_whales) || 0,
3004
+ shortExitWhales: Number(row.short_exit_whales) || 0,
3005
+ longEntryNotionalUsd: Number(row.long_entry_notional_usd) || 0,
3006
+ shortEntryNotionalUsd: Number(row.short_entry_notional_usd) || 0,
3007
+ longExitNotionalUsd: Number(row.long_exit_notional_usd) || 0,
3008
+ shortExitNotionalUsd: Number(row.short_exit_notional_usd) || 0,
3009
+ entryNetNotionalUsd: Number(row.entry_net_notional_usd) || 0,
3010
+ entryLongSharePct: row.entry_long_share_pct == null ? null : Number(row.entry_long_share_pct) || 0,
3011
+ universeFingerprint: params.universeFingerprint,
3012
+ whaleRegistryFingerprint: params.whaleRegistryFingerprint,
3013
+ source: row.source == null ? null : String(row.source),
3014
+ ageMs,
3015
+ stale: ageMs < 0 || (params.maxAgeMs != null && Number.isFinite(params.maxAgeMs) ? ageMs > params.maxAgeMs : false)
3016
+ };
3017
+ }
3018
+ async function hasHyperliquidWhaleBackfillCoverage(params) {
3019
+ await ensureHyperliquidWhaleSchema();
3020
+ const expectedBuckets = Math.max(
3021
+ 0,
3022
+ Math.ceil((params.toMs - params.fromMs) / 6e4)
3023
+ );
3024
+ const result = await getPool().query(
3025
+ `
3026
+ SELECT
3027
+ COUNT(*)::int AS buckets,
3028
+ COUNT(*) FILTER (
3029
+ WHERE covered_whales = expected_whales
3030
+ )::int AS complete_buckets
3031
+ FROM hyperliquid_whale_coverage_1m
3032
+ WHERE universe_fingerprint = $1
3033
+ AND whale_registry_fingerprint = $2
3034
+ AND data_model_version = $5
3035
+ AND ts >= to_timestamp($3/1000.0)
3036
+ AND ts < to_timestamp($4/1000.0)
3037
+ `,
3038
+ [
3039
+ params.universeFingerprint,
3040
+ params.whaleRegistryFingerprint,
3041
+ params.fromMs,
3042
+ params.toMs,
3043
+ import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
3044
+ ]
3045
+ );
3046
+ return Number(result.rows[0]?.buckets) === expectedBuckets && Number(result.rows[0]?.complete_buckets) === expectedBuckets;
3047
+ }
1801
3048
  async function getLatestMarketBreadth(params) {
1802
3049
  await ensureBinanceMarketSchema();
1803
3050
  const pool = getPool();
@@ -2703,6 +3950,10 @@ async function findContinuityGap(provider, symbol, interval) {
2703
3950
  getDerivativesRangeForSymbols,
2704
3951
  getDerivativesSummary,
2705
3952
  getDerivativesWindow,
3953
+ getHyperliquidWhaleCoverageSeriesRows,
3954
+ getHyperliquidWhaleFlowAggregate,
3955
+ getHyperliquidWhaleFlowSeriesRows,
3956
+ getHyperliquidWhaleWalletCoverage,
2706
3957
  getLatestMarketBreadth,
2707
3958
  getLatestMarketCmcExchangeLiquidityContext,
2708
3959
  getLatestMarketCmcFearGreedContext,
@@ -2720,10 +3971,17 @@ async function findContinuityGap(provider, symbol, interval) {
2720
3971
  getMarketTradeFlowCoverage,
2721
3972
  getSpreadRangeForSymbols,
2722
3973
  getSpreadSummary,
3974
+ hasHyperliquidWhaleBackfillCoverage,
3975
+ rebuildHyperliquidWhaleCoverageRows,
3976
+ rebuildHyperliquidWhaleFlowRows,
2723
3977
  toRows,
2724
3978
  upsertCandles,
2725
3979
  upsertDerivatives,
2726
3980
  upsertDerivativesBackfillCoverage,
3981
+ upsertHyperliquidWhaleCoverageRows,
3982
+ upsertHyperliquidWhaleFlowRows,
3983
+ upsertHyperliquidWhaleTradeEvents,
3984
+ upsertHyperliquidWhaleWalletCoverage,
2727
3985
  upsertMarketBreadthRows,
2728
3986
  upsertMarketCmcExchangeLiquidityContextRows,
2729
3987
  upsertMarketCmcFearGreedContextRows,