@tradejs/infra 2.0.18 → 2.0.20
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/ai.js +39 -74
- package/dist/ai.mjs +37 -74
- package/dist/chunk-2CZREG43.mjs +112 -0
- package/dist/chunk-DFMKDB2R.mjs +1285 -0
- package/dist/chunk-I2J6YDBD.mjs +910 -0
- package/dist/chunk-NWXFWTWU.mjs +1114 -0
- package/dist/chunk-SZQB7ER5.mjs +492 -0
- package/dist/chunk-YVIHTUV5.mjs +286 -0
- package/dist/coreResearch.d.mts +20 -0
- package/dist/coreResearch.d.ts +20 -0
- package/dist/coreResearch.js +89 -0
- package/dist/coreResearch.mjs +48 -0
- package/dist/internal-2coHaaos.d.mts +26 -0
- package/dist/internal-2coHaaos.d.ts +26 -0
- package/dist/ml.mjs +3 -3
- package/dist/runtimeDeployments.d.mts +10 -0
- package/dist/runtimeDeployments.d.ts +10 -0
- package/dist/runtimeDeployments.js +447 -0
- package/dist/runtimeDeployments.mjs +81 -0
- package/dist/runtimeStrategyConfigs.d.mts +28 -0
- package/dist/runtimeStrategyConfigs.d.ts +28 -0
- package/dist/runtimeStrategyConfigs.js +425 -0
- package/dist/runtimeStrategyConfigs.mjs +89 -0
- package/dist/strategyReleaseEvidence.d.mts +12 -0
- package/dist/strategyReleaseEvidence.d.ts +12 -0
- package/dist/strategyReleaseEvidence.js +120 -0
- package/dist/strategyReleaseEvidence.mjs +90 -0
- package/dist/timescale/candles.d.mts +38 -0
- package/dist/timescale/candles.d.ts +38 -0
- package/dist/timescale/candles.js +408 -0
- package/dist/timescale/candles.mjs +21 -0
- package/dist/timescale/client.d.mts +4 -0
- package/dist/timescale/client.d.ts +4 -0
- package/dist/timescale/client.js +109 -0
- package/dist/timescale/client.mjs +12 -0
- package/dist/timescale/derivatives.d.mts +90 -0
- package/dist/timescale/derivatives.d.ts +90 -0
- package/dist/timescale/derivatives.js +1270 -0
- package/dist/timescale/derivatives.mjs +26 -0
- package/dist/timescale/hyperliquidWhales.d.mts +149 -0
- package/dist/timescale/hyperliquidWhales.d.ts +149 -0
- package/dist/timescale/hyperliquidWhales.js +1893 -0
- package/dist/timescale/hyperliquidWhales.mjs +30 -0
- package/dist/timescale/marketContext.d.mts +188 -0
- package/dist/timescale/marketContext.d.ts +188 -0
- package/dist/timescale/marketContext.js +2091 -0
- package/dist/timescale/marketContext.mjs +60 -0
- package/dist/timescale/spread.d.mts +11 -0
- package/dist/timescale/spread.d.ts +11 -0
- package/dist/timescale/spread.js +215 -0
- package/dist/timescale/spread.mjs +11 -0
- package/dist/timescale.d.mts +9 -476
- package/dist/timescale.d.ts +9 -476
- package/dist/timescale.js +2121 -2112
- package/dist/timescale.mjs +73 -4070
- package/dist/tradingAccounts.d.mts +2 -8
- package/dist/tradingAccounts.d.ts +2 -8
- package/dist/tradingAccounts.js +0 -71
- package/dist/tradingAccounts.mjs +0 -65
- package/dist/values-BrvcmnfM.d.mts +6 -0
- package/dist/values-BrvcmnfM.d.ts +6 -0
- package/package.json +53 -2
package/dist/timescale.js
CHANGED
|
@@ -84,25 +84,21 @@ __export(timescale_exports, {
|
|
|
84
84
|
waitForDbReady: () => waitForDbReady
|
|
85
85
|
});
|
|
86
86
|
module.exports = __toCommonJS(timescale_exports);
|
|
87
|
+
|
|
88
|
+
// src/timescale/pool.ts
|
|
87
89
|
var import_pg = require("pg");
|
|
88
|
-
var import_types = require("@tradejs/types");
|
|
89
90
|
var getPool = () => {
|
|
90
91
|
if (!global.__pgPool__) {
|
|
91
|
-
const host = process.env.PG_HOST || "127.0.0.1";
|
|
92
|
-
const port = Number(process.env.PG_PORT ?? 5432);
|
|
93
|
-
const user = process.env.PG_USER || "app";
|
|
94
|
-
const password = String(process.env.PG_PASSWORD ?? "app");
|
|
95
|
-
const database = process.env.PG_DATABASE || process.env.PG_DB || "app";
|
|
96
92
|
const max = Number(process.env.PG_POOL_MAX ?? 10);
|
|
97
93
|
const connectionTimeoutMillis = Number(
|
|
98
94
|
process.env.PG_CONNECTION_TIMEOUT_MS ?? 3e4
|
|
99
95
|
);
|
|
100
96
|
global.__pgPool__ = new import_pg.Pool({
|
|
101
|
-
host,
|
|
102
|
-
port,
|
|
103
|
-
user,
|
|
104
|
-
password,
|
|
105
|
-
database,
|
|
97
|
+
host: process.env.PG_HOST || "127.0.0.1",
|
|
98
|
+
port: Number(process.env.PG_PORT ?? 5432),
|
|
99
|
+
user: process.env.PG_USER || "app",
|
|
100
|
+
password: String(process.env.PG_PASSWORD ?? "app"),
|
|
101
|
+
database: process.env.PG_DATABASE || process.env.PG_DB || "app",
|
|
106
102
|
max: Number.isFinite(max) && max > 0 ? Math.floor(max) : 10,
|
|
107
103
|
idleTimeoutMillis: 3e4,
|
|
108
104
|
connectionTimeoutMillis: Number.isFinite(connectionTimeoutMillis) && connectionTimeoutMillis > 0 ? Math.floor(connectionTimeoutMillis) : 3e4
|
|
@@ -110,63 +106,29 @@ var getPool = () => {
|
|
|
110
106
|
}
|
|
111
107
|
return global.__pgPool__;
|
|
112
108
|
};
|
|
113
|
-
var
|
|
114
|
-
var derivativesSchemaReady = false;
|
|
115
|
-
var spreadSchemaReady = false;
|
|
116
|
-
var binanceMarketSchemaReady = false;
|
|
117
|
-
var hyperliquidWhaleSchemaReady = false;
|
|
118
|
-
var candlesSchemaReadyPromise = null;
|
|
119
|
-
var derivativesSchemaReadyPromise = null;
|
|
120
|
-
var spreadSchemaReadyPromise = null;
|
|
121
|
-
var binanceMarketSchemaReadyPromise = null;
|
|
122
|
-
var hyperliquidWhaleSchemaReadyPromise = null;
|
|
123
|
-
var marketContextSchemaMode = "ensure";
|
|
124
|
-
var verifiedMarketContextSchemas = /* @__PURE__ */ new Set();
|
|
125
|
-
var configureTimescaleMarketContextSchemaMode = (mode) => {
|
|
126
|
-
marketContextSchemaMode = mode;
|
|
127
|
-
verifiedMarketContextSchemas.clear();
|
|
128
|
-
};
|
|
129
|
-
var closeTimescalePool = async () => {
|
|
109
|
+
var closePool = async () => {
|
|
130
110
|
const pool = global.__pgPool__;
|
|
131
|
-
if (!pool)
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
111
|
+
if (!pool) return;
|
|
134
112
|
global.__pgPool__ = void 0;
|
|
135
|
-
candlesSchemaReady = false;
|
|
136
|
-
derivativesSchemaReady = false;
|
|
137
|
-
spreadSchemaReady = false;
|
|
138
|
-
binanceMarketSchemaReady = false;
|
|
139
|
-
hyperliquidWhaleSchemaReady = false;
|
|
140
|
-
candlesSchemaReadyPromise = null;
|
|
141
|
-
derivativesSchemaReadyPromise = null;
|
|
142
|
-
spreadSchemaReadyPromise = null;
|
|
143
|
-
binanceMarketSchemaReadyPromise = null;
|
|
144
|
-
hyperliquidWhaleSchemaReadyPromise = null;
|
|
145
|
-
verifiedMarketContextSchemas.clear();
|
|
146
113
|
await pool.end();
|
|
147
114
|
};
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
var
|
|
151
|
-
var BINANCE_MARKET_SCHEMA_LOCK_KEY = 610003;
|
|
152
|
-
var HYPERLIQUID_WHALE_SCHEMA_LOCK_KEY = 610004;
|
|
153
|
-
var PG_SAFE_MAX_BIND_PARAMS = 3e4;
|
|
154
|
-
var resolveMarketContextQueryTimeoutMs = (override) => {
|
|
115
|
+
|
|
116
|
+
// src/timescale/query.ts
|
|
117
|
+
var resolveQueryTimeoutMs = (override) => {
|
|
155
118
|
if (Number.isFinite(override) && Number(override) > 0) {
|
|
156
119
|
return Math.floor(Number(override));
|
|
157
120
|
}
|
|
158
121
|
const configured = Number(process.env.MARKET_CONTEXT_SQL_TIMEOUT_MS);
|
|
159
122
|
return Number.isFinite(configured) && configured > 0 ? Math.floor(configured) : 3e4;
|
|
160
123
|
};
|
|
161
|
-
var
|
|
124
|
+
var createQueryError = (name, message) => {
|
|
162
125
|
const error = new Error(message);
|
|
163
126
|
error.name = name;
|
|
164
127
|
return error;
|
|
165
128
|
};
|
|
166
129
|
var queryMarketContext = async (text, values, options = {}) => {
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
const timeoutMs = resolveMarketContextQueryTimeoutMs(options.timeoutMs);
|
|
130
|
+
const client = await getPool().connect();
|
|
131
|
+
const timeoutMs = resolveQueryTimeoutMs(options.timeoutMs);
|
|
170
132
|
let released = false;
|
|
171
133
|
let rejectCancellation;
|
|
172
134
|
const release = (error) => {
|
|
@@ -182,14 +144,11 @@ var queryMarketContext = async (text, values, options = {}) => {
|
|
|
182
144
|
rejectCancellation?.(error);
|
|
183
145
|
};
|
|
184
146
|
const onAbort = () => cancel(
|
|
185
|
-
|
|
186
|
-
"AbortError",
|
|
187
|
-
"Timescale market-context query aborted"
|
|
188
|
-
)
|
|
147
|
+
createQueryError("AbortError", "Timescale market-context query aborted")
|
|
189
148
|
);
|
|
190
149
|
const timer = setTimeout(
|
|
191
150
|
() => cancel(
|
|
192
|
-
|
|
151
|
+
createQueryError(
|
|
193
152
|
"TimescaleQueryTimeoutError",
|
|
194
153
|
`Timescale market-context query exceeded ${timeoutMs}ms`
|
|
195
154
|
)
|
|
@@ -200,24 +159,67 @@ var queryMarketContext = async (text, values, options = {}) => {
|
|
|
200
159
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
201
160
|
try {
|
|
202
161
|
if (options.signal?.aborted) {
|
|
203
|
-
const error =
|
|
162
|
+
const error = createQueryError(
|
|
204
163
|
"AbortError",
|
|
205
164
|
"Timescale market-context query aborted"
|
|
206
165
|
);
|
|
207
166
|
release(error);
|
|
208
167
|
throw error;
|
|
209
168
|
}
|
|
210
|
-
|
|
211
|
-
return await Promise.race([query, cancellation]);
|
|
169
|
+
return await Promise.race([client.query(text, values), cancellation]);
|
|
212
170
|
} finally {
|
|
213
171
|
clearTimeout(timer);
|
|
214
172
|
options.signal?.removeEventListener("abort", onAbort);
|
|
215
173
|
release();
|
|
216
174
|
}
|
|
217
175
|
};
|
|
176
|
+
|
|
177
|
+
// src/timescale/values.ts
|
|
178
|
+
var PG_SAFE_MAX_BIND_PARAMS = 3e4;
|
|
218
179
|
var normalizeCandleProvider = (provider) => String(provider || "").trim().toLowerCase();
|
|
219
180
|
var normalizeCandleSymbol = (symbol) => String(symbol || "").trim().toUpperCase();
|
|
220
181
|
var getSafeBulkInsertRows = (columnsCount) => Math.max(1, Math.floor(PG_SAFE_MAX_BIND_PARAMS / columnsCount));
|
|
182
|
+
var toMarketFeatureAge = (rowTs, atMs) => {
|
|
183
|
+
const ageMs = atMs - rowTs.getTime();
|
|
184
|
+
return Number.isFinite(ageMs) ? ageMs : null;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// src/timescale/internal.ts
|
|
188
|
+
var candlesSchemaReady = false;
|
|
189
|
+
var derivativesSchemaReady = false;
|
|
190
|
+
var spreadSchemaReady = false;
|
|
191
|
+
var binanceMarketSchemaReady = false;
|
|
192
|
+
var hyperliquidWhaleSchemaReady = false;
|
|
193
|
+
var candlesSchemaReadyPromise = null;
|
|
194
|
+
var derivativesSchemaReadyPromise = null;
|
|
195
|
+
var spreadSchemaReadyPromise = null;
|
|
196
|
+
var binanceMarketSchemaReadyPromise = null;
|
|
197
|
+
var hyperliquidWhaleSchemaReadyPromise = null;
|
|
198
|
+
var marketContextSchemaMode = "ensure";
|
|
199
|
+
var verifiedMarketContextSchemas = /* @__PURE__ */ new Set();
|
|
200
|
+
var configureTimescaleMarketContextSchemaMode = (mode) => {
|
|
201
|
+
marketContextSchemaMode = mode;
|
|
202
|
+
verifiedMarketContextSchemas.clear();
|
|
203
|
+
};
|
|
204
|
+
var closeTimescalePool = async () => {
|
|
205
|
+
candlesSchemaReady = false;
|
|
206
|
+
derivativesSchemaReady = false;
|
|
207
|
+
spreadSchemaReady = false;
|
|
208
|
+
binanceMarketSchemaReady = false;
|
|
209
|
+
hyperliquidWhaleSchemaReady = false;
|
|
210
|
+
candlesSchemaReadyPromise = null;
|
|
211
|
+
derivativesSchemaReadyPromise = null;
|
|
212
|
+
spreadSchemaReadyPromise = null;
|
|
213
|
+
binanceMarketSchemaReadyPromise = null;
|
|
214
|
+
hyperliquidWhaleSchemaReadyPromise = null;
|
|
215
|
+
verifiedMarketContextSchemas.clear();
|
|
216
|
+
await closePool();
|
|
217
|
+
};
|
|
218
|
+
var CANDLES_SCHEMA_LOCK_KEY = 61e4;
|
|
219
|
+
var DERIVATIVES_SCHEMA_LOCK_KEY = 610001;
|
|
220
|
+
var SPREAD_SCHEMA_LOCK_KEY = 610002;
|
|
221
|
+
var BINANCE_MARKET_SCHEMA_LOCK_KEY = 610003;
|
|
222
|
+
var HYPERLIQUID_WHALE_SCHEMA_LOCK_KEY = 610004;
|
|
221
223
|
var withSchemaLock = async (lockKey, work) => {
|
|
222
224
|
const pool = getPool();
|
|
223
225
|
await pool.query("SELECT pg_advisory_lock($1)", [lockKey]);
|
|
@@ -283,103 +285,6 @@ var ensureCandlesSchema = async () => {
|
|
|
283
285
|
});
|
|
284
286
|
await candlesSchemaReadyPromise;
|
|
285
287
|
};
|
|
286
|
-
var toRows = (provider, symbol, interval, data) => {
|
|
287
|
-
const normalizedProvider = normalizeCandleProvider(provider);
|
|
288
|
-
if (!normalizedProvider) {
|
|
289
|
-
throw new Error("Candle provider is required");
|
|
290
|
-
}
|
|
291
|
-
const normalizedSymbol = normalizeCandleSymbol(symbol);
|
|
292
|
-
return data.map((i) => ({
|
|
293
|
-
provider: normalizedProvider,
|
|
294
|
-
symbol: normalizedSymbol,
|
|
295
|
-
interval,
|
|
296
|
-
ts: new Date(i.timestamp),
|
|
297
|
-
// ms -> Date
|
|
298
|
-
open: i.open,
|
|
299
|
-
high: i.high,
|
|
300
|
-
low: i.low,
|
|
301
|
-
close: i.close,
|
|
302
|
-
volume: i.volume ?? null,
|
|
303
|
-
turnover: i.turnover ?? null,
|
|
304
|
-
takerBuyBaseVolume: i.takerBuyBaseVolume ?? null,
|
|
305
|
-
takerBuyQuoteVolume: i.takerBuyQuoteVolume ?? null,
|
|
306
|
-
takerSellBaseVolume: i.takerSellBaseVolume ?? null,
|
|
307
|
-
takerSellQuoteVolume: i.takerSellQuoteVolume ?? null
|
|
308
|
-
}));
|
|
309
|
-
};
|
|
310
|
-
async function upsertCandles(rows) {
|
|
311
|
-
if (!rows.length) return;
|
|
312
|
-
await ensureCandlesSchema();
|
|
313
|
-
const pool = getPool();
|
|
314
|
-
const cols = [
|
|
315
|
-
"provider",
|
|
316
|
-
"symbol",
|
|
317
|
-
"interval",
|
|
318
|
-
"ts",
|
|
319
|
-
"open",
|
|
320
|
-
"high",
|
|
321
|
-
"low",
|
|
322
|
-
"close",
|
|
323
|
-
"volume",
|
|
324
|
-
"turnover",
|
|
325
|
-
"taker_buy_base_volume",
|
|
326
|
-
"taker_buy_quote_volume",
|
|
327
|
-
"taker_sell_base_volume",
|
|
328
|
-
"taker_sell_quote_volume"
|
|
329
|
-
];
|
|
330
|
-
const maxRows = Math.floor(65535 / cols.length);
|
|
331
|
-
if (rows.length > maxRows) {
|
|
332
|
-
for (let i = 0; i < rows.length; i += maxRows) {
|
|
333
|
-
await upsertCandles(rows.slice(i, i + maxRows));
|
|
334
|
-
}
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
const valuesSql = rows.map(
|
|
338
|
-
(_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
|
|
339
|
-
).join(",");
|
|
340
|
-
const flat = rows.flatMap((r) => [
|
|
341
|
-
normalizeCandleProvider(r.provider),
|
|
342
|
-
normalizeCandleSymbol(r.symbol),
|
|
343
|
-
r.interval,
|
|
344
|
-
r.ts,
|
|
345
|
-
r.open,
|
|
346
|
-
r.high,
|
|
347
|
-
r.low,
|
|
348
|
-
r.close,
|
|
349
|
-
r.volume ?? null,
|
|
350
|
-
r.turnover ?? null,
|
|
351
|
-
r.takerBuyBaseVolume ?? null,
|
|
352
|
-
r.takerBuyQuoteVolume ?? null,
|
|
353
|
-
r.takerSellBaseVolume ?? null,
|
|
354
|
-
r.takerSellQuoteVolume ?? null
|
|
355
|
-
]);
|
|
356
|
-
const sql = `
|
|
357
|
-
INSERT INTO candles (${cols.join(",")})
|
|
358
|
-
VALUES ${valuesSql}
|
|
359
|
-
ON CONFLICT (provider, symbol, interval, ts) DO UPDATE SET
|
|
360
|
-
open = EXCLUDED.open,
|
|
361
|
-
high = EXCLUDED.high,
|
|
362
|
-
low = EXCLUDED.low,
|
|
363
|
-
close = EXCLUDED.close,
|
|
364
|
-
volume = COALESCE(EXCLUDED.volume, candles.volume),
|
|
365
|
-
turnover = COALESCE(EXCLUDED.turnover, candles.turnover),
|
|
366
|
-
taker_buy_base_volume = COALESCE(EXCLUDED.taker_buy_base_volume, candles.taker_buy_base_volume),
|
|
367
|
-
taker_buy_quote_volume = COALESCE(EXCLUDED.taker_buy_quote_volume, candles.taker_buy_quote_volume),
|
|
368
|
-
taker_sell_base_volume = COALESCE(EXCLUDED.taker_sell_base_volume, candles.taker_sell_base_volume),
|
|
369
|
-
taker_sell_quote_volume = COALESCE(EXCLUDED.taker_sell_quote_volume, candles.taker_sell_quote_volume)
|
|
370
|
-
`;
|
|
371
|
-
const client = await pool.connect();
|
|
372
|
-
try {
|
|
373
|
-
await client.query("BEGIN");
|
|
374
|
-
await client.query(sql, flat);
|
|
375
|
-
await client.query("COMMIT");
|
|
376
|
-
} catch (e) {
|
|
377
|
-
await client.query("ROLLBACK");
|
|
378
|
-
throw e;
|
|
379
|
-
} finally {
|
|
380
|
-
client.release();
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
288
|
var ensureDerivativesSchema = async () => {
|
|
384
289
|
if (derivativesSchemaReady) return;
|
|
385
290
|
if (derivativesSchemaReadyPromise) {
|
|
@@ -1071,44 +976,315 @@ var ensureMarketContextSchemas = async (sources) => {
|
|
|
1071
976
|
await ensureMarketContextSchema(source);
|
|
1072
977
|
}
|
|
1073
978
|
};
|
|
1074
|
-
|
|
979
|
+
|
|
980
|
+
// src/timescale/candles.ts
|
|
981
|
+
var toRows = (provider, symbol, interval, data) => {
|
|
982
|
+
const normalizedProvider = normalizeCandleProvider(provider);
|
|
983
|
+
if (!normalizedProvider) {
|
|
984
|
+
throw new Error("Candle provider is required");
|
|
985
|
+
}
|
|
986
|
+
const normalizedSymbol = normalizeCandleSymbol(symbol);
|
|
987
|
+
return data.map((i) => ({
|
|
988
|
+
provider: normalizedProvider,
|
|
989
|
+
symbol: normalizedSymbol,
|
|
990
|
+
interval,
|
|
991
|
+
ts: new Date(i.timestamp),
|
|
992
|
+
// ms -> Date
|
|
993
|
+
open: i.open,
|
|
994
|
+
high: i.high,
|
|
995
|
+
low: i.low,
|
|
996
|
+
close: i.close,
|
|
997
|
+
volume: i.volume ?? null,
|
|
998
|
+
turnover: i.turnover ?? null,
|
|
999
|
+
takerBuyBaseVolume: i.takerBuyBaseVolume ?? null,
|
|
1000
|
+
takerBuyQuoteVolume: i.takerBuyQuoteVolume ?? null,
|
|
1001
|
+
takerSellBaseVolume: i.takerSellBaseVolume ?? null,
|
|
1002
|
+
takerSellQuoteVolume: i.takerSellQuoteVolume ?? null
|
|
1003
|
+
}));
|
|
1004
|
+
};
|
|
1005
|
+
async function upsertCandles(rows) {
|
|
1075
1006
|
if (!rows.length) return;
|
|
1076
|
-
await
|
|
1007
|
+
await ensureCandlesSchema();
|
|
1077
1008
|
const pool = getPool();
|
|
1078
1009
|
const cols = [
|
|
1010
|
+
"provider",
|
|
1079
1011
|
"symbol",
|
|
1080
1012
|
"interval",
|
|
1081
1013
|
"ts",
|
|
1082
|
-
"
|
|
1083
|
-
"
|
|
1084
|
-
"
|
|
1085
|
-
"
|
|
1086
|
-
"
|
|
1087
|
-
"
|
|
1014
|
+
"open",
|
|
1015
|
+
"high",
|
|
1016
|
+
"low",
|
|
1017
|
+
"close",
|
|
1018
|
+
"volume",
|
|
1019
|
+
"turnover",
|
|
1020
|
+
"taker_buy_base_volume",
|
|
1021
|
+
"taker_buy_quote_volume",
|
|
1022
|
+
"taker_sell_base_volume",
|
|
1023
|
+
"taker_sell_quote_volume"
|
|
1088
1024
|
];
|
|
1089
1025
|
const maxRows = Math.floor(65535 / cols.length);
|
|
1090
1026
|
if (rows.length > maxRows) {
|
|
1091
1027
|
for (let i = 0; i < rows.length; i += maxRows) {
|
|
1092
|
-
await
|
|
1028
|
+
await upsertCandles(rows.slice(i, i + maxRows));
|
|
1093
1029
|
}
|
|
1094
1030
|
return;
|
|
1095
1031
|
}
|
|
1096
1032
|
const valuesSql = rows.map(
|
|
1097
1033
|
(_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
|
|
1098
1034
|
).join(",");
|
|
1099
|
-
const flat = rows.flatMap((
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1035
|
+
const flat = rows.flatMap((r) => [
|
|
1036
|
+
normalizeCandleProvider(r.provider),
|
|
1037
|
+
normalizeCandleSymbol(r.symbol),
|
|
1038
|
+
r.interval,
|
|
1039
|
+
r.ts,
|
|
1040
|
+
r.open,
|
|
1041
|
+
r.high,
|
|
1042
|
+
r.low,
|
|
1043
|
+
r.close,
|
|
1044
|
+
r.volume ?? null,
|
|
1045
|
+
r.turnover ?? null,
|
|
1046
|
+
r.takerBuyBaseVolume ?? null,
|
|
1047
|
+
r.takerBuyQuoteVolume ?? null,
|
|
1048
|
+
r.takerSellBaseVolume ?? null,
|
|
1049
|
+
r.takerSellQuoteVolume ?? null
|
|
1109
1050
|
]);
|
|
1110
1051
|
const sql = `
|
|
1111
|
-
INSERT INTO
|
|
1052
|
+
INSERT INTO candles (${cols.join(",")})
|
|
1053
|
+
VALUES ${valuesSql}
|
|
1054
|
+
ON CONFLICT (provider, symbol, interval, ts) DO UPDATE SET
|
|
1055
|
+
open = EXCLUDED.open,
|
|
1056
|
+
high = EXCLUDED.high,
|
|
1057
|
+
low = EXCLUDED.low,
|
|
1058
|
+
close = EXCLUDED.close,
|
|
1059
|
+
volume = COALESCE(EXCLUDED.volume, candles.volume),
|
|
1060
|
+
turnover = COALESCE(EXCLUDED.turnover, candles.turnover),
|
|
1061
|
+
taker_buy_base_volume = COALESCE(EXCLUDED.taker_buy_base_volume, candles.taker_buy_base_volume),
|
|
1062
|
+
taker_buy_quote_volume = COALESCE(EXCLUDED.taker_buy_quote_volume, candles.taker_buy_quote_volume),
|
|
1063
|
+
taker_sell_base_volume = COALESCE(EXCLUDED.taker_sell_base_volume, candles.taker_sell_base_volume),
|
|
1064
|
+
taker_sell_quote_volume = COALESCE(EXCLUDED.taker_sell_quote_volume, candles.taker_sell_quote_volume)
|
|
1065
|
+
`;
|
|
1066
|
+
const client = await pool.connect();
|
|
1067
|
+
try {
|
|
1068
|
+
await client.query("BEGIN");
|
|
1069
|
+
await client.query(sql, flat);
|
|
1070
|
+
await client.query("COMMIT");
|
|
1071
|
+
} catch (e) {
|
|
1072
|
+
await client.query("ROLLBACK");
|
|
1073
|
+
throw e;
|
|
1074
|
+
} finally {
|
|
1075
|
+
client.release();
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
async function getCandlesRange(provider, symbol, interval, startMs, endMs) {
|
|
1079
|
+
await ensureCandlesSchema();
|
|
1080
|
+
const pool = getPool();
|
|
1081
|
+
const normalizedProvider = normalizeCandleProvider(provider);
|
|
1082
|
+
const normalizedSymbol = normalizeCandleSymbol(symbol);
|
|
1083
|
+
const sql = `
|
|
1084
|
+
SELECT symbol, interval, ts,
|
|
1085
|
+
open, high, low, close, volume, turnover,
|
|
1086
|
+
taker_buy_base_volume AS "takerBuyBaseVolume",
|
|
1087
|
+
taker_buy_quote_volume AS "takerBuyQuoteVolume",
|
|
1088
|
+
taker_sell_base_volume AS "takerSellBaseVolume",
|
|
1089
|
+
taker_sell_quote_volume AS "takerSellQuoteVolume"
|
|
1090
|
+
FROM candles
|
|
1091
|
+
WHERE provider = $1 AND symbol = $2 AND interval = $3
|
|
1092
|
+
AND ts >= to_timestamp($4/1000.0)
|
|
1093
|
+
AND ts <= to_timestamp($5/1000.0)
|
|
1094
|
+
ORDER BY ts ASC
|
|
1095
|
+
`;
|
|
1096
|
+
const res = await pool.query(sql, [
|
|
1097
|
+
normalizedProvider,
|
|
1098
|
+
normalizedSymbol,
|
|
1099
|
+
interval,
|
|
1100
|
+
startMs,
|
|
1101
|
+
endMs
|
|
1102
|
+
]);
|
|
1103
|
+
return res.rows;
|
|
1104
|
+
}
|
|
1105
|
+
async function getDataEdges(provider, symbol, interval) {
|
|
1106
|
+
await ensureCandlesSchema();
|
|
1107
|
+
const pool = getPool();
|
|
1108
|
+
const normalizedProvider = normalizeCandleProvider(provider);
|
|
1109
|
+
const normalizedSymbol = normalizeCandleSymbol(symbol);
|
|
1110
|
+
const sqlMin = `
|
|
1111
|
+
SELECT extract(epoch from ts)*1000 AS ms
|
|
1112
|
+
FROM candles
|
|
1113
|
+
WHERE provider=$1 AND symbol=$2 AND interval=$3
|
|
1114
|
+
ORDER BY ts ASC
|
|
1115
|
+
LIMIT 1
|
|
1116
|
+
`;
|
|
1117
|
+
const sqlMax = `
|
|
1118
|
+
SELECT extract(epoch from ts)*1000 AS ms
|
|
1119
|
+
FROM candles
|
|
1120
|
+
WHERE provider=$1 AND symbol=$2 AND interval=$3
|
|
1121
|
+
ORDER BY ts DESC
|
|
1122
|
+
LIMIT 1
|
|
1123
|
+
`;
|
|
1124
|
+
const [minQ, maxQ] = await Promise.all([
|
|
1125
|
+
pool.query(sqlMin, [normalizedProvider, normalizedSymbol, interval]),
|
|
1126
|
+
pool.query(sqlMax, [normalizedProvider, normalizedSymbol, interval])
|
|
1127
|
+
]);
|
|
1128
|
+
const minRaw = minQ.rows[0]?.ms;
|
|
1129
|
+
const maxRaw = maxQ.rows[0]?.ms;
|
|
1130
|
+
const min = Number.isFinite(Number(minRaw)) ? Number(minRaw) : void 0;
|
|
1131
|
+
const max = Number.isFinite(Number(maxRaw)) ? Number(maxRaw) : void 0;
|
|
1132
|
+
return { min, max };
|
|
1133
|
+
}
|
|
1134
|
+
async function getDataEdgesForSymbols(provider, symbols, interval) {
|
|
1135
|
+
const normalizedSymbols = [
|
|
1136
|
+
...new Set(symbols.map(normalizeCandleSymbol).filter(Boolean))
|
|
1137
|
+
];
|
|
1138
|
+
const result = /* @__PURE__ */ new Map();
|
|
1139
|
+
for (const symbol of normalizedSymbols) {
|
|
1140
|
+
result.set(symbol, {});
|
|
1141
|
+
}
|
|
1142
|
+
if (!normalizedSymbols.length) {
|
|
1143
|
+
return result;
|
|
1144
|
+
}
|
|
1145
|
+
await ensureCandlesSchema();
|
|
1146
|
+
const pool = getPool();
|
|
1147
|
+
const normalizedProvider = normalizeCandleProvider(provider);
|
|
1148
|
+
const sql = `
|
|
1149
|
+
WITH requested(symbol) AS (
|
|
1150
|
+
SELECT unnest($2::text[])
|
|
1151
|
+
)
|
|
1152
|
+
SELECT
|
|
1153
|
+
r.symbol,
|
|
1154
|
+
(
|
|
1155
|
+
SELECT extract(epoch from c.ts)*1000
|
|
1156
|
+
FROM candles c
|
|
1157
|
+
WHERE c.provider = $1 AND c.symbol = r.symbol AND c.interval = $3
|
|
1158
|
+
ORDER BY c.ts ASC
|
|
1159
|
+
LIMIT 1
|
|
1160
|
+
) AS min_ms,
|
|
1161
|
+
(
|
|
1162
|
+
SELECT extract(epoch from c.ts)*1000
|
|
1163
|
+
FROM candles c
|
|
1164
|
+
WHERE c.provider = $1 AND c.symbol = r.symbol AND c.interval = $3
|
|
1165
|
+
ORDER BY c.ts DESC
|
|
1166
|
+
LIMIT 1
|
|
1167
|
+
) AS max_ms
|
|
1168
|
+
FROM requested r
|
|
1169
|
+
`;
|
|
1170
|
+
const response = await pool.query(sql, [
|
|
1171
|
+
normalizedProvider,
|
|
1172
|
+
normalizedSymbols,
|
|
1173
|
+
interval
|
|
1174
|
+
]);
|
|
1175
|
+
for (const row of response.rows) {
|
|
1176
|
+
const symbol = normalizeCandleSymbol(String(row.symbol || ""));
|
|
1177
|
+
if (!symbol) continue;
|
|
1178
|
+
const min = row.min_ms == null ? NaN : Number(row.min_ms);
|
|
1179
|
+
const max = row.max_ms == null ? NaN : Number(row.max_ms);
|
|
1180
|
+
result.set(symbol, {
|
|
1181
|
+
...Number.isFinite(min) ? { min } : {},
|
|
1182
|
+
...Number.isFinite(max) ? { max } : {}
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
return result;
|
|
1186
|
+
}
|
|
1187
|
+
async function waitForDbReady(attempts = 20, delayMs = 1e3) {
|
|
1188
|
+
const pool = getPool();
|
|
1189
|
+
let lastError;
|
|
1190
|
+
for (let i = 0; i < attempts; i++) {
|
|
1191
|
+
try {
|
|
1192
|
+
await pool.query("SELECT 1");
|
|
1193
|
+
return;
|
|
1194
|
+
} catch (e) {
|
|
1195
|
+
lastError = e;
|
|
1196
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
throw lastError;
|
|
1200
|
+
}
|
|
1201
|
+
async function deleteCandles(provider, symbol, interval) {
|
|
1202
|
+
const pool = getPool();
|
|
1203
|
+
const normalizedProvider = normalizeCandleProvider(provider);
|
|
1204
|
+
const normalizedSymbol = normalizeCandleSymbol(symbol);
|
|
1205
|
+
const sql = `
|
|
1206
|
+
DELETE FROM candles
|
|
1207
|
+
WHERE provider = $1 AND symbol = $2 AND interval = $3
|
|
1208
|
+
`;
|
|
1209
|
+
await pool.query(sql, [normalizedProvider, normalizedSymbol, interval]);
|
|
1210
|
+
}
|
|
1211
|
+
async function findContinuityGap(provider, symbol, interval) {
|
|
1212
|
+
const pool = getPool();
|
|
1213
|
+
const normalizedProvider = normalizeCandleProvider(provider);
|
|
1214
|
+
const normalizedSymbol = normalizeCandleSymbol(symbol);
|
|
1215
|
+
const expectedSeconds = interval * 60;
|
|
1216
|
+
const sql = `
|
|
1217
|
+
WITH ordered AS (
|
|
1218
|
+
SELECT
|
|
1219
|
+
ts,
|
|
1220
|
+
LAG(ts) OVER (ORDER BY ts) AS prev_ts
|
|
1221
|
+
FROM candles
|
|
1222
|
+
WHERE provider = $1 AND symbol = $2 AND interval = $3
|
|
1223
|
+
)
|
|
1224
|
+
SELECT
|
|
1225
|
+
ts,
|
|
1226
|
+
prev_ts,
|
|
1227
|
+
EXTRACT(EPOCH FROM (ts - prev_ts))::int AS diff_seconds
|
|
1228
|
+
FROM ordered
|
|
1229
|
+
WHERE prev_ts IS NOT NULL
|
|
1230
|
+
AND EXTRACT(EPOCH FROM (ts - prev_ts))::int <> $4
|
|
1231
|
+
ORDER BY ts ASC
|
|
1232
|
+
LIMIT 1
|
|
1233
|
+
`;
|
|
1234
|
+
const res = await pool.query(sql, [
|
|
1235
|
+
normalizedProvider,
|
|
1236
|
+
normalizedSymbol,
|
|
1237
|
+
interval,
|
|
1238
|
+
expectedSeconds
|
|
1239
|
+
]);
|
|
1240
|
+
const row = res.rows[0];
|
|
1241
|
+
if (!row) return null;
|
|
1242
|
+
return {
|
|
1243
|
+
ts: new Date(row.ts).getTime(),
|
|
1244
|
+
prevTs: new Date(row.prev_ts).getTime(),
|
|
1245
|
+
diffSeconds: row.diff_seconds
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// src/timescale/derivatives.ts
|
|
1250
|
+
async function upsertDerivatives(rows) {
|
|
1251
|
+
if (!rows.length) return;
|
|
1252
|
+
await ensureDerivativesSchema();
|
|
1253
|
+
const pool = getPool();
|
|
1254
|
+
const cols = [
|
|
1255
|
+
"symbol",
|
|
1256
|
+
"interval",
|
|
1257
|
+
"ts",
|
|
1258
|
+
"open_interest",
|
|
1259
|
+
"funding_rate",
|
|
1260
|
+
"liq_long",
|
|
1261
|
+
"liq_short",
|
|
1262
|
+
"liq_total",
|
|
1263
|
+
"source"
|
|
1264
|
+
];
|
|
1265
|
+
const maxRows = Math.floor(65535 / cols.length);
|
|
1266
|
+
if (rows.length > maxRows) {
|
|
1267
|
+
for (let i = 0; i < rows.length; i += maxRows) {
|
|
1268
|
+
await upsertDerivatives(rows.slice(i, i + maxRows));
|
|
1269
|
+
}
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
const valuesSql = rows.map(
|
|
1273
|
+
(_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
|
|
1274
|
+
).join(",");
|
|
1275
|
+
const flat = rows.flatMap((row) => [
|
|
1276
|
+
row.symbol,
|
|
1277
|
+
row.interval,
|
|
1278
|
+
row.ts,
|
|
1279
|
+
row.openInterest ?? null,
|
|
1280
|
+
row.fundingRate ?? null,
|
|
1281
|
+
row.liqLong ?? null,
|
|
1282
|
+
row.liqShort ?? null,
|
|
1283
|
+
row.liqTotal ?? null,
|
|
1284
|
+
row.source ?? null
|
|
1285
|
+
]);
|
|
1286
|
+
const sql = `
|
|
1287
|
+
INSERT INTO derivatives_market (${cols.join(",")})
|
|
1112
1288
|
VALUES ${valuesSql}
|
|
1113
1289
|
ON CONFLICT (symbol, interval, ts) DO UPDATE SET
|
|
1114
1290
|
open_interest = COALESCE(EXCLUDED.open_interest, derivatives_market.open_interest),
|
|
@@ -1542,6 +1718,8 @@ async function getDerivativesSummary(hours = 24, limit = 500, symbols) {
|
|
|
1542
1718
|
items
|
|
1543
1719
|
};
|
|
1544
1720
|
}
|
|
1721
|
+
|
|
1722
|
+
// src/timescale/spread.ts
|
|
1545
1723
|
async function upsertSpreadRows(rows) {
|
|
1546
1724
|
if (!rows.length) return;
|
|
1547
1725
|
await ensureSpreadSchema();
|
|
@@ -1586,23 +1764,81 @@ async function upsertSpreadRows(rows) {
|
|
|
1586
1764
|
`;
|
|
1587
1765
|
await pool.query(sql, flat);
|
|
1588
1766
|
}
|
|
1589
|
-
async function
|
|
1590
|
-
if (!
|
|
1591
|
-
|
|
1767
|
+
async function getSpreadRangeForSymbols(symbols, interval, startMs, endMs) {
|
|
1768
|
+
if (!symbols.length) {
|
|
1769
|
+
return [];
|
|
1770
|
+
}
|
|
1771
|
+
await ensureSpreadSchema();
|
|
1592
1772
|
const pool = getPool();
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1773
|
+
const sql = `
|
|
1774
|
+
SELECT symbol, interval, ts, binance_price, coinbase_price, spread
|
|
1775
|
+
FROM market_spread
|
|
1776
|
+
WHERE symbol = ANY($1)
|
|
1777
|
+
AND interval = $2
|
|
1778
|
+
AND ts >= to_timestamp($3/1000.0)
|
|
1779
|
+
AND ts <= to_timestamp($4/1000.0)
|
|
1780
|
+
ORDER BY symbol ASC, ts ASC
|
|
1781
|
+
`;
|
|
1782
|
+
const res = await pool.query(sql, [symbols, interval, startMs, endMs]);
|
|
1783
|
+
return res.rows;
|
|
1784
|
+
}
|
|
1785
|
+
async function getSpreadSummary(hours = 24, limit = 500) {
|
|
1786
|
+
await ensureSpreadSchema();
|
|
1787
|
+
const pool = getPool();
|
|
1788
|
+
const cappedHours = Math.max(1, Math.min(24 * 30, hours));
|
|
1789
|
+
const cappedLimit = Math.max(50, Math.min(5e3, limit));
|
|
1790
|
+
const rowsQ = await pool.query(
|
|
1791
|
+
`
|
|
1792
|
+
SELECT symbol, interval, ts, binance_price, coinbase_price, spread
|
|
1793
|
+
FROM market_spread
|
|
1794
|
+
WHERE ts >= now() - ($1 || ' hours')::interval
|
|
1795
|
+
ORDER BY ts DESC
|
|
1796
|
+
LIMIT $2
|
|
1797
|
+
`,
|
|
1798
|
+
[String(cappedHours), cappedLimit]
|
|
1799
|
+
);
|
|
1800
|
+
const aggQ = await pool.query(
|
|
1801
|
+
`
|
|
1802
|
+
SELECT
|
|
1803
|
+
symbol,
|
|
1804
|
+
interval,
|
|
1805
|
+
COUNT(*)::int AS points,
|
|
1806
|
+
MAX(ts) AS last_ts,
|
|
1807
|
+
AVG(spread) AS avg_spread,
|
|
1808
|
+
STDDEV_POP(spread) AS std_spread
|
|
1809
|
+
FROM market_spread
|
|
1810
|
+
WHERE ts >= now() - ($1 || ' hours')::interval
|
|
1811
|
+
GROUP BY symbol, interval
|
|
1812
|
+
ORDER BY points DESC, symbol ASC
|
|
1813
|
+
LIMIT 500
|
|
1814
|
+
`,
|
|
1815
|
+
[String(cappedHours)]
|
|
1816
|
+
);
|
|
1817
|
+
return {
|
|
1818
|
+
rows: rowsQ.rows,
|
|
1819
|
+
aggregates: aggQ.rows,
|
|
1820
|
+
hours: cappedHours
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// src/timescale/marketContext.ts
|
|
1825
|
+
async function upsertMarketTradeFlowRows(rows) {
|
|
1826
|
+
if (!rows.length) return;
|
|
1827
|
+
await ensureBinanceMarketSchema();
|
|
1828
|
+
const pool = getPool();
|
|
1829
|
+
const cols = [
|
|
1830
|
+
"symbol",
|
|
1831
|
+
"interval",
|
|
1832
|
+
"ts",
|
|
1833
|
+
"trades",
|
|
1834
|
+
"buy_base_volume",
|
|
1835
|
+
"sell_base_volume",
|
|
1836
|
+
"buy_quote_volume",
|
|
1837
|
+
"sell_quote_volume",
|
|
1838
|
+
"net_base_delta",
|
|
1839
|
+
"net_quote_delta",
|
|
1840
|
+
"buy_pressure_pct",
|
|
1841
|
+
"source"
|
|
1606
1842
|
];
|
|
1607
1843
|
const maxRows = getSafeBulkInsertRows(cols.length);
|
|
1608
1844
|
if (rows.length > maxRows) {
|
|
@@ -1647,530 +1883,273 @@ async function upsertMarketTradeFlowRows(rows) {
|
|
|
1647
1883
|
flat
|
|
1648
1884
|
);
|
|
1649
1885
|
}
|
|
1650
|
-
async function
|
|
1886
|
+
async function upsertMarketBreadthRows(rows) {
|
|
1651
1887
|
if (!rows.length) return;
|
|
1652
|
-
await
|
|
1888
|
+
await ensureBinanceMarketSchema();
|
|
1889
|
+
const pool = getPool();
|
|
1653
1890
|
const cols = [
|
|
1654
|
-
"
|
|
1891
|
+
"universe",
|
|
1892
|
+
"interval",
|
|
1655
1893
|
"ts",
|
|
1656
|
-
"
|
|
1657
|
-
"
|
|
1658
|
-
"
|
|
1659
|
-
"
|
|
1660
|
-
"
|
|
1661
|
-
"
|
|
1662
|
-
"
|
|
1663
|
-
"
|
|
1664
|
-
"
|
|
1665
|
-
"
|
|
1666
|
-
"
|
|
1667
|
-
"
|
|
1668
|
-
"
|
|
1669
|
-
"
|
|
1670
|
-
"
|
|
1671
|
-
"
|
|
1672
|
-
"
|
|
1673
|
-
"
|
|
1674
|
-
"
|
|
1675
|
-
"
|
|
1894
|
+
"symbols_count",
|
|
1895
|
+
"advancers",
|
|
1896
|
+
"decliners",
|
|
1897
|
+
"unchanged",
|
|
1898
|
+
"advance_decline_ratio",
|
|
1899
|
+
"pct_above_ma20",
|
|
1900
|
+
"pct_above_ma50",
|
|
1901
|
+
"equal_weighted_return",
|
|
1902
|
+
"volume_weighted_return",
|
|
1903
|
+
"dispersion",
|
|
1904
|
+
"btc_return_1h",
|
|
1905
|
+
"btc_return_4h",
|
|
1906
|
+
"btc_return_24h",
|
|
1907
|
+
"alt_basket_return_1h",
|
|
1908
|
+
"alt_basket_return_4h",
|
|
1909
|
+
"alt_basket_return_24h",
|
|
1910
|
+
"btc_vs_alt_return_1h",
|
|
1911
|
+
"btc_vs_alt_return_4h",
|
|
1912
|
+
"btc_vs_alt_return_24h",
|
|
1913
|
+
"btc_turnover_share_1h",
|
|
1914
|
+
"btc_turnover_share_24h",
|
|
1915
|
+
"btc_turnover_share_change_24h",
|
|
1916
|
+
"alt_vol_to_btc_vol_24h",
|
|
1917
|
+
"alt_dispersion_24h",
|
|
1918
|
+
"btc_alt_regime",
|
|
1676
1919
|
"source"
|
|
1677
1920
|
];
|
|
1678
1921
|
const maxRows = getSafeBulkInsertRows(cols.length);
|
|
1679
1922
|
if (rows.length > maxRows) {
|
|
1680
|
-
for (let
|
|
1681
|
-
await
|
|
1682
|
-
rows.slice(index, index + maxRows)
|
|
1683
|
-
);
|
|
1923
|
+
for (let i = 0; i < rows.length; i += maxRows) {
|
|
1924
|
+
await upsertMarketBreadthRows(rows.slice(i, i + maxRows));
|
|
1684
1925
|
}
|
|
1685
1926
|
return;
|
|
1686
1927
|
}
|
|
1687
1928
|
const valuesSql = rows.map(
|
|
1688
|
-
(_,
|
|
1929
|
+
(_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
|
|
1689
1930
|
).join(",");
|
|
1690
1931
|
const flat = rows.flatMap((row) => [
|
|
1691
|
-
row.
|
|
1932
|
+
row.universe,
|
|
1933
|
+
row.interval,
|
|
1692
1934
|
row.ts,
|
|
1693
|
-
row.
|
|
1694
|
-
row.
|
|
1695
|
-
row.
|
|
1696
|
-
row.
|
|
1697
|
-
row.
|
|
1698
|
-
row.
|
|
1699
|
-
row.
|
|
1700
|
-
row.
|
|
1701
|
-
row.
|
|
1702
|
-
row.
|
|
1703
|
-
row.
|
|
1704
|
-
row.
|
|
1705
|
-
row.
|
|
1706
|
-
row.
|
|
1707
|
-
row.
|
|
1708
|
-
row.
|
|
1709
|
-
row.
|
|
1710
|
-
row.
|
|
1711
|
-
row.
|
|
1712
|
-
row.
|
|
1935
|
+
row.symbolsCount,
|
|
1936
|
+
row.advancers,
|
|
1937
|
+
row.decliners,
|
|
1938
|
+
row.unchanged,
|
|
1939
|
+
row.advanceDeclineRatio ?? null,
|
|
1940
|
+
row.pctAboveMa20 ?? null,
|
|
1941
|
+
row.pctAboveMa50 ?? null,
|
|
1942
|
+
row.equalWeightedReturn ?? null,
|
|
1943
|
+
row.volumeWeightedReturn ?? null,
|
|
1944
|
+
row.dispersion ?? null,
|
|
1945
|
+
row.btcReturn1h ?? null,
|
|
1946
|
+
row.btcReturn4h ?? null,
|
|
1947
|
+
row.btcReturn24h ?? null,
|
|
1948
|
+
row.altBasketReturn1h ?? null,
|
|
1949
|
+
row.altBasketReturn4h ?? null,
|
|
1950
|
+
row.altBasketReturn24h ?? null,
|
|
1951
|
+
row.btcVsAltReturn1h ?? null,
|
|
1952
|
+
row.btcVsAltReturn4h ?? null,
|
|
1953
|
+
row.btcVsAltReturn24h ?? null,
|
|
1954
|
+
row.btcTurnoverShare1h ?? null,
|
|
1955
|
+
row.btcTurnoverShare24h ?? null,
|
|
1956
|
+
row.btcTurnoverShareChange24h ?? null,
|
|
1957
|
+
row.altVolToBtcVol24h ?? null,
|
|
1958
|
+
row.altDispersion24h ?? null,
|
|
1959
|
+
row.btcAltRegime ?? null,
|
|
1713
1960
|
row.source ?? null
|
|
1714
1961
|
]);
|
|
1715
|
-
await
|
|
1962
|
+
await pool.query(
|
|
1716
1963
|
`
|
|
1717
|
-
INSERT INTO
|
|
1964
|
+
INSERT INTO market_breadth (${cols.join(",")})
|
|
1718
1965
|
VALUES ${valuesSql}
|
|
1719
|
-
ON CONFLICT (
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
),
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
),
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
),
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
),
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
hyperliquid_whale_trade_events.seller_liquidation,
|
|
1774
|
-
EXCLUDED.seller_liquidation
|
|
1775
|
-
),
|
|
1776
|
-
source = EXCLUDED.source,
|
|
1777
|
-
ingested_at = now()
|
|
1778
|
-
`,
|
|
1779
|
-
flat
|
|
1780
|
-
);
|
|
1781
|
-
}
|
|
1782
|
-
async function upsertHyperliquidWhaleFlowRows(rows) {
|
|
1783
|
-
if (!rows.length) return;
|
|
1784
|
-
await ensureHyperliquidWhaleSchema();
|
|
1785
|
-
const cols = [
|
|
1786
|
-
"symbol",
|
|
1787
|
-
"interval",
|
|
1788
|
-
"ts",
|
|
1789
|
-
"trades",
|
|
1790
|
-
"whale_sides",
|
|
1791
|
-
"unique_whales",
|
|
1792
|
-
"whale_addresses",
|
|
1793
|
-
"buy_notional_usd",
|
|
1794
|
-
"sell_notional_usd",
|
|
1795
|
-
"net_notional_usd",
|
|
1796
|
-
"buy_share_pct",
|
|
1797
|
-
"position_aware_whale_sides",
|
|
1798
|
-
"long_entry_whale_addresses",
|
|
1799
|
-
"short_entry_whale_addresses",
|
|
1800
|
-
"long_exit_whale_addresses",
|
|
1801
|
-
"short_exit_whale_addresses",
|
|
1802
|
-
"long_entry_notional_usd",
|
|
1803
|
-
"short_entry_notional_usd",
|
|
1804
|
-
"long_exit_notional_usd",
|
|
1805
|
-
"short_exit_notional_usd",
|
|
1806
|
-
"entry_net_notional_usd",
|
|
1807
|
-
"entry_long_share_pct",
|
|
1808
|
-
"universe_fingerprint",
|
|
1809
|
-
"whale_registry_fingerprint",
|
|
1810
|
-
"source"
|
|
1966
|
+
ON CONFLICT (universe, interval, ts) DO UPDATE SET
|
|
1967
|
+
symbols_count = EXCLUDED.symbols_count,
|
|
1968
|
+
advancers = EXCLUDED.advancers,
|
|
1969
|
+
decliners = EXCLUDED.decliners,
|
|
1970
|
+
unchanged = EXCLUDED.unchanged,
|
|
1971
|
+
advance_decline_ratio = COALESCE(EXCLUDED.advance_decline_ratio, market_breadth.advance_decline_ratio),
|
|
1972
|
+
pct_above_ma20 = COALESCE(EXCLUDED.pct_above_ma20, market_breadth.pct_above_ma20),
|
|
1973
|
+
pct_above_ma50 = COALESCE(EXCLUDED.pct_above_ma50, market_breadth.pct_above_ma50),
|
|
1974
|
+
equal_weighted_return = COALESCE(EXCLUDED.equal_weighted_return, market_breadth.equal_weighted_return),
|
|
1975
|
+
volume_weighted_return = COALESCE(EXCLUDED.volume_weighted_return, market_breadth.volume_weighted_return),
|
|
1976
|
+
dispersion = COALESCE(EXCLUDED.dispersion, market_breadth.dispersion),
|
|
1977
|
+
btc_return_1h = COALESCE(EXCLUDED.btc_return_1h, market_breadth.btc_return_1h),
|
|
1978
|
+
btc_return_4h = COALESCE(EXCLUDED.btc_return_4h, market_breadth.btc_return_4h),
|
|
1979
|
+
btc_return_24h = COALESCE(EXCLUDED.btc_return_24h, market_breadth.btc_return_24h),
|
|
1980
|
+
alt_basket_return_1h = COALESCE(EXCLUDED.alt_basket_return_1h, market_breadth.alt_basket_return_1h),
|
|
1981
|
+
alt_basket_return_4h = COALESCE(EXCLUDED.alt_basket_return_4h, market_breadth.alt_basket_return_4h),
|
|
1982
|
+
alt_basket_return_24h = COALESCE(EXCLUDED.alt_basket_return_24h, market_breadth.alt_basket_return_24h),
|
|
1983
|
+
btc_vs_alt_return_1h = COALESCE(EXCLUDED.btc_vs_alt_return_1h, market_breadth.btc_vs_alt_return_1h),
|
|
1984
|
+
btc_vs_alt_return_4h = COALESCE(EXCLUDED.btc_vs_alt_return_4h, market_breadth.btc_vs_alt_return_4h),
|
|
1985
|
+
btc_vs_alt_return_24h = COALESCE(EXCLUDED.btc_vs_alt_return_24h, market_breadth.btc_vs_alt_return_24h),
|
|
1986
|
+
btc_turnover_share_1h = COALESCE(EXCLUDED.btc_turnover_share_1h, market_breadth.btc_turnover_share_1h),
|
|
1987
|
+
btc_turnover_share_24h = COALESCE(EXCLUDED.btc_turnover_share_24h, market_breadth.btc_turnover_share_24h),
|
|
1988
|
+
btc_turnover_share_change_24h = COALESCE(EXCLUDED.btc_turnover_share_change_24h, market_breadth.btc_turnover_share_change_24h),
|
|
1989
|
+
alt_vol_to_btc_vol_24h = COALESCE(EXCLUDED.alt_vol_to_btc_vol_24h, market_breadth.alt_vol_to_btc_vol_24h),
|
|
1990
|
+
alt_dispersion_24h = COALESCE(EXCLUDED.alt_dispersion_24h, market_breadth.alt_dispersion_24h),
|
|
1991
|
+
btc_alt_regime = COALESCE(EXCLUDED.btc_alt_regime, market_breadth.btc_alt_regime),
|
|
1992
|
+
source = COALESCE(EXCLUDED.source, market_breadth.source),
|
|
1993
|
+
ingested_at = now()
|
|
1994
|
+
`,
|
|
1995
|
+
flat
|
|
1996
|
+
);
|
|
1997
|
+
}
|
|
1998
|
+
async function upsertMarketGlobalContextRows(rows) {
|
|
1999
|
+
if (!rows.length) return;
|
|
2000
|
+
await ensureBinanceMarketSchema();
|
|
2001
|
+
const pool = getPool();
|
|
2002
|
+
const cols = [
|
|
2003
|
+
"source",
|
|
2004
|
+
"ts",
|
|
2005
|
+
"updated_at_ts",
|
|
2006
|
+
"active_cryptocurrencies",
|
|
2007
|
+
"active_exchanges",
|
|
2008
|
+
"active_market_pairs",
|
|
2009
|
+
"markets",
|
|
2010
|
+
"total_market_cap_usd",
|
|
2011
|
+
"total_volume_usd",
|
|
2012
|
+
"total_volume_reported_usd",
|
|
2013
|
+
"btc_dominance_pct",
|
|
2014
|
+
"eth_dominance_pct",
|
|
2015
|
+
"alt_market_cap_usd",
|
|
2016
|
+
"alt_volume_usd",
|
|
2017
|
+
"alt_volume_reported_usd",
|
|
2018
|
+
"btc_to_alt_market_cap_ratio",
|
|
2019
|
+
"market_cap_change_pct_24h_usd"
|
|
1811
2020
|
];
|
|
1812
2021
|
const maxRows = getSafeBulkInsertRows(cols.length);
|
|
1813
2022
|
if (rows.length > maxRows) {
|
|
1814
|
-
for (let
|
|
1815
|
-
await
|
|
2023
|
+
for (let i = 0; i < rows.length; i += maxRows) {
|
|
2024
|
+
await upsertMarketGlobalContextRows(rows.slice(i, i + maxRows));
|
|
1816
2025
|
}
|
|
1817
2026
|
return;
|
|
1818
2027
|
}
|
|
1819
2028
|
const valuesSql = rows.map(
|
|
1820
|
-
(_,
|
|
2029
|
+
(_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
|
|
1821
2030
|
).join(",");
|
|
1822
2031
|
const flat = rows.flatMap((row) => [
|
|
1823
|
-
row.
|
|
2032
|
+
row.source,
|
|
2033
|
+
row.ts,
|
|
2034
|
+
row.updatedAt ?? null,
|
|
2035
|
+
row.activeCryptocurrencies ?? null,
|
|
2036
|
+
row.activeExchanges ?? null,
|
|
2037
|
+
row.activeMarketPairs ?? null,
|
|
2038
|
+
row.markets ?? null,
|
|
2039
|
+
row.totalMarketCapUsd ?? null,
|
|
2040
|
+
row.totalVolumeUsd ?? null,
|
|
2041
|
+
row.totalVolumeReportedUsd ?? null,
|
|
2042
|
+
row.btcDominancePct ?? null,
|
|
2043
|
+
row.ethDominancePct ?? null,
|
|
2044
|
+
row.altMarketCapUsd ?? null,
|
|
2045
|
+
row.altVolumeUsd ?? null,
|
|
2046
|
+
row.altVolumeReportedUsd ?? null,
|
|
2047
|
+
row.btcToAltMarketCapRatio ?? null,
|
|
2048
|
+
row.marketCapChangePct24hUsd ?? null
|
|
2049
|
+
]);
|
|
2050
|
+
await pool.query(
|
|
2051
|
+
`
|
|
2052
|
+
INSERT INTO market_global_context (${cols.join(",")})
|
|
2053
|
+
VALUES ${valuesSql}
|
|
2054
|
+
ON CONFLICT (source, ts) DO UPDATE SET
|
|
2055
|
+
updated_at_ts = COALESCE(EXCLUDED.updated_at_ts, market_global_context.updated_at_ts),
|
|
2056
|
+
active_cryptocurrencies = COALESCE(EXCLUDED.active_cryptocurrencies, market_global_context.active_cryptocurrencies),
|
|
2057
|
+
active_exchanges = COALESCE(EXCLUDED.active_exchanges, market_global_context.active_exchanges),
|
|
2058
|
+
active_market_pairs = COALESCE(EXCLUDED.active_market_pairs, market_global_context.active_market_pairs),
|
|
2059
|
+
markets = COALESCE(EXCLUDED.markets, market_global_context.markets),
|
|
2060
|
+
total_market_cap_usd = COALESCE(EXCLUDED.total_market_cap_usd, market_global_context.total_market_cap_usd),
|
|
2061
|
+
total_volume_usd = COALESCE(EXCLUDED.total_volume_usd, market_global_context.total_volume_usd),
|
|
2062
|
+
total_volume_reported_usd = COALESCE(EXCLUDED.total_volume_reported_usd, market_global_context.total_volume_reported_usd),
|
|
2063
|
+
btc_dominance_pct = COALESCE(EXCLUDED.btc_dominance_pct, market_global_context.btc_dominance_pct),
|
|
2064
|
+
eth_dominance_pct = COALESCE(EXCLUDED.eth_dominance_pct, market_global_context.eth_dominance_pct),
|
|
2065
|
+
alt_market_cap_usd = COALESCE(EXCLUDED.alt_market_cap_usd, market_global_context.alt_market_cap_usd),
|
|
2066
|
+
alt_volume_usd = COALESCE(EXCLUDED.alt_volume_usd, market_global_context.alt_volume_usd),
|
|
2067
|
+
alt_volume_reported_usd = COALESCE(EXCLUDED.alt_volume_reported_usd, market_global_context.alt_volume_reported_usd),
|
|
2068
|
+
btc_to_alt_market_cap_ratio = COALESCE(EXCLUDED.btc_to_alt_market_cap_ratio, market_global_context.btc_to_alt_market_cap_ratio),
|
|
2069
|
+
market_cap_change_pct_24h_usd = COALESCE(EXCLUDED.market_cap_change_pct_24h_usd, market_global_context.market_cap_change_pct_24h_usd),
|
|
2070
|
+
ingested_at = now()
|
|
2071
|
+
`,
|
|
2072
|
+
flat
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
2075
|
+
async function upsertMarketReferenceAssetContextRows(rows) {
|
|
2076
|
+
if (!rows.length) return;
|
|
2077
|
+
await ensureBinanceMarketSchema();
|
|
2078
|
+
const pool = getPool();
|
|
2079
|
+
const cols = [
|
|
2080
|
+
"source",
|
|
2081
|
+
"symbol",
|
|
2082
|
+
"cmc_id",
|
|
2083
|
+
"interval",
|
|
2084
|
+
"ts",
|
|
2085
|
+
"open_usd",
|
|
2086
|
+
"high_usd",
|
|
2087
|
+
"low_usd",
|
|
2088
|
+
"close_usd",
|
|
2089
|
+
"volume_usd",
|
|
2090
|
+
"market_cap_usd"
|
|
2091
|
+
];
|
|
2092
|
+
const maxRows = getSafeBulkInsertRows(cols.length);
|
|
2093
|
+
if (rows.length > maxRows) {
|
|
2094
|
+
for (let i = 0; i < rows.length; i += maxRows) {
|
|
2095
|
+
await upsertMarketReferenceAssetContextRows(rows.slice(i, i + maxRows));
|
|
2096
|
+
}
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
const valuesSql = rows.map(
|
|
2100
|
+
(_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
|
|
2101
|
+
).join(",");
|
|
2102
|
+
const flat = rows.flatMap((row) => [
|
|
2103
|
+
row.source,
|
|
2104
|
+
row.symbol.trim().toUpperCase(),
|
|
2105
|
+
Math.trunc(row.cmcId),
|
|
1824
2106
|
row.interval,
|
|
1825
2107
|
row.ts,
|
|
1826
|
-
row.
|
|
1827
|
-
row.
|
|
1828
|
-
row.
|
|
1829
|
-
row.
|
|
1830
|
-
row.
|
|
1831
|
-
row.
|
|
1832
|
-
row.netNotionalUsd,
|
|
1833
|
-
row.buySharePct ?? null,
|
|
1834
|
-
row.positionAwareWhaleSides,
|
|
1835
|
-
row.longEntryWhaleAddresses ?? [],
|
|
1836
|
-
row.shortEntryWhaleAddresses ?? [],
|
|
1837
|
-
row.longExitWhaleAddresses ?? [],
|
|
1838
|
-
row.shortExitWhaleAddresses ?? [],
|
|
1839
|
-
row.longEntryNotionalUsd,
|
|
1840
|
-
row.shortEntryNotionalUsd,
|
|
1841
|
-
row.longExitNotionalUsd,
|
|
1842
|
-
row.shortExitNotionalUsd,
|
|
1843
|
-
row.entryNetNotionalUsd,
|
|
1844
|
-
row.entryLongSharePct ?? null,
|
|
1845
|
-
row.universeFingerprint,
|
|
1846
|
-
row.whaleRegistryFingerprint,
|
|
1847
|
-
row.source ?? null
|
|
2108
|
+
row.openUsd ?? null,
|
|
2109
|
+
row.highUsd ?? null,
|
|
2110
|
+
row.lowUsd ?? null,
|
|
2111
|
+
row.closeUsd ?? null,
|
|
2112
|
+
row.volumeUsd ?? null,
|
|
2113
|
+
row.marketCapUsd ?? null
|
|
1848
2114
|
]);
|
|
1849
|
-
await
|
|
2115
|
+
await pool.query(
|
|
1850
2116
|
`
|
|
1851
|
-
INSERT INTO
|
|
2117
|
+
INSERT INTO market_reference_asset_context (${cols.join(",")})
|
|
1852
2118
|
VALUES ${valuesSql}
|
|
1853
|
-
ON CONFLICT (
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
whale_sides = EXCLUDED.whale_sides,
|
|
1862
|
-
unique_whales = EXCLUDED.unique_whales,
|
|
1863
|
-
whale_addresses = EXCLUDED.whale_addresses,
|
|
1864
|
-
buy_notional_usd = EXCLUDED.buy_notional_usd,
|
|
1865
|
-
sell_notional_usd = EXCLUDED.sell_notional_usd,
|
|
1866
|
-
net_notional_usd = EXCLUDED.net_notional_usd,
|
|
1867
|
-
buy_share_pct = EXCLUDED.buy_share_pct,
|
|
1868
|
-
position_aware_whale_sides = EXCLUDED.position_aware_whale_sides,
|
|
1869
|
-
long_entry_whale_addresses = EXCLUDED.long_entry_whale_addresses,
|
|
1870
|
-
short_entry_whale_addresses = EXCLUDED.short_entry_whale_addresses,
|
|
1871
|
-
long_exit_whale_addresses = EXCLUDED.long_exit_whale_addresses,
|
|
1872
|
-
short_exit_whale_addresses = EXCLUDED.short_exit_whale_addresses,
|
|
1873
|
-
long_entry_notional_usd = EXCLUDED.long_entry_notional_usd,
|
|
1874
|
-
short_entry_notional_usd = EXCLUDED.short_entry_notional_usd,
|
|
1875
|
-
long_exit_notional_usd = EXCLUDED.long_exit_notional_usd,
|
|
1876
|
-
short_exit_notional_usd = EXCLUDED.short_exit_notional_usd,
|
|
1877
|
-
entry_net_notional_usd = EXCLUDED.entry_net_notional_usd,
|
|
1878
|
-
entry_long_share_pct = EXCLUDED.entry_long_share_pct,
|
|
1879
|
-
source = EXCLUDED.source,
|
|
2119
|
+
ON CONFLICT (source, symbol, interval, ts) DO UPDATE SET
|
|
2120
|
+
cmc_id = EXCLUDED.cmc_id,
|
|
2121
|
+
open_usd = COALESCE(EXCLUDED.open_usd, market_reference_asset_context.open_usd),
|
|
2122
|
+
high_usd = COALESCE(EXCLUDED.high_usd, market_reference_asset_context.high_usd),
|
|
2123
|
+
low_usd = COALESCE(EXCLUDED.low_usd, market_reference_asset_context.low_usd),
|
|
2124
|
+
close_usd = COALESCE(EXCLUDED.close_usd, market_reference_asset_context.close_usd),
|
|
2125
|
+
volume_usd = COALESCE(EXCLUDED.volume_usd, market_reference_asset_context.volume_usd),
|
|
2126
|
+
market_cap_usd = COALESCE(EXCLUDED.market_cap_usd, market_reference_asset_context.market_cap_usd),
|
|
1880
2127
|
ingested_at = now()
|
|
1881
2128
|
`,
|
|
1882
2129
|
flat
|
|
1883
2130
|
);
|
|
1884
2131
|
}
|
|
1885
|
-
async function
|
|
1886
|
-
await ensureHyperliquidWhaleSchema();
|
|
1887
|
-
const client = await getPool().connect();
|
|
1888
|
-
try {
|
|
1889
|
-
await client.query("BEGIN");
|
|
1890
|
-
const result = await client.query(
|
|
1891
|
-
`
|
|
1892
|
-
WITH source_events AS (
|
|
1893
|
-
SELECT *
|
|
1894
|
-
FROM hyperliquid_whale_trade_events
|
|
1895
|
-
WHERE universe_fingerprint = $1
|
|
1896
|
-
AND whale_registry_fingerprint = $2
|
|
1897
|
-
AND ts >= to_timestamp($3/1000.0)
|
|
1898
|
-
AND ts < to_timestamp($4/1000.0)
|
|
1899
|
-
), metrics AS (
|
|
1900
|
-
SELECT
|
|
1901
|
-
symbol,
|
|
1902
|
-
date_trunc('minute', ts) AS bucket_ts,
|
|
1903
|
-
COUNT(*)::int AS trades,
|
|
1904
|
-
SUM(buyer_tracked::int + seller_tracked::int)::int AS whale_sides,
|
|
1905
|
-
SUM(CASE WHEN buyer_tracked THEN notional_usd ELSE 0 END) AS buy_notional_usd,
|
|
1906
|
-
SUM(CASE WHEN seller_tracked THEN notional_usd ELSE 0 END) AS sell_notional_usd
|
|
1907
|
-
FROM source_events
|
|
1908
|
-
GROUP BY symbol, date_trunc('minute', ts)
|
|
1909
|
-
), position_legs AS (
|
|
1910
|
-
SELECT
|
|
1911
|
-
symbol,
|
|
1912
|
-
ts,
|
|
1913
|
-
price,
|
|
1914
|
-
buyer_address AS whale_address,
|
|
1915
|
-
buyer_start_position AS start_position,
|
|
1916
|
-
buyer_end_position AS end_position
|
|
1917
|
-
FROM source_events
|
|
1918
|
-
WHERE buyer_tracked
|
|
1919
|
-
AND buyer_address IS NOT NULL
|
|
1920
|
-
AND buyer_start_position IS NOT NULL
|
|
1921
|
-
AND buyer_end_position IS NOT NULL
|
|
1922
|
-
UNION ALL
|
|
1923
|
-
SELECT
|
|
1924
|
-
symbol,
|
|
1925
|
-
ts,
|
|
1926
|
-
price,
|
|
1927
|
-
seller_address AS whale_address,
|
|
1928
|
-
seller_start_position AS start_position,
|
|
1929
|
-
seller_end_position AS end_position
|
|
1930
|
-
FROM source_events
|
|
1931
|
-
WHERE seller_tracked
|
|
1932
|
-
AND seller_address IS NOT NULL
|
|
1933
|
-
AND seller_start_position IS NOT NULL
|
|
1934
|
-
AND seller_end_position IS NOT NULL
|
|
1935
|
-
), classified_legs AS (
|
|
1936
|
-
SELECT
|
|
1937
|
-
*,
|
|
1938
|
-
GREATEST(
|
|
1939
|
-
GREATEST(end_position, 0) - GREATEST(start_position, 0),
|
|
1940
|
-
0
|
|
1941
|
-
) AS long_entry_size,
|
|
1942
|
-
GREATEST(
|
|
1943
|
-
GREATEST(-end_position, 0) - GREATEST(-start_position, 0),
|
|
1944
|
-
0
|
|
1945
|
-
) AS short_entry_size,
|
|
1946
|
-
GREATEST(
|
|
1947
|
-
GREATEST(start_position, 0) - GREATEST(end_position, 0),
|
|
1948
|
-
0
|
|
1949
|
-
) AS long_exit_size,
|
|
1950
|
-
GREATEST(
|
|
1951
|
-
GREATEST(-start_position, 0) - GREATEST(-end_position, 0),
|
|
1952
|
-
0
|
|
1953
|
-
) AS short_exit_size
|
|
1954
|
-
FROM position_legs
|
|
1955
|
-
), position_metrics AS (
|
|
1956
|
-
SELECT
|
|
1957
|
-
symbol,
|
|
1958
|
-
date_trunc('minute', ts) AS bucket_ts,
|
|
1959
|
-
COUNT(*)::int AS position_aware_whale_sides,
|
|
1960
|
-
SUM(long_entry_size * price) AS long_entry_notional_usd,
|
|
1961
|
-
SUM(short_entry_size * price) AS short_entry_notional_usd,
|
|
1962
|
-
SUM(long_exit_size * price) AS long_exit_notional_usd,
|
|
1963
|
-
SUM(short_exit_size * price) AS short_exit_notional_usd,
|
|
1964
|
-
COALESCE(
|
|
1965
|
-
ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
|
|
1966
|
-
FILTER (WHERE long_entry_size > 0),
|
|
1967
|
-
'{}'
|
|
1968
|
-
) AS long_entry_whale_addresses,
|
|
1969
|
-
COALESCE(
|
|
1970
|
-
ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
|
|
1971
|
-
FILTER (WHERE short_entry_size > 0),
|
|
1972
|
-
'{}'
|
|
1973
|
-
) AS short_entry_whale_addresses,
|
|
1974
|
-
COALESCE(
|
|
1975
|
-
ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
|
|
1976
|
-
FILTER (WHERE long_exit_size > 0),
|
|
1977
|
-
'{}'
|
|
1978
|
-
) AS long_exit_whale_addresses,
|
|
1979
|
-
COALESCE(
|
|
1980
|
-
ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
|
|
1981
|
-
FILTER (WHERE short_exit_size > 0),
|
|
1982
|
-
'{}'
|
|
1983
|
-
) AS short_exit_whale_addresses
|
|
1984
|
-
FROM classified_legs
|
|
1985
|
-
GROUP BY symbol, date_trunc('minute', ts)
|
|
1986
|
-
), addresses AS (
|
|
1987
|
-
SELECT
|
|
1988
|
-
symbol,
|
|
1989
|
-
date_trunc('minute', ts) AS bucket_ts,
|
|
1990
|
-
ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address) AS whale_addresses
|
|
1991
|
-
FROM source_events
|
|
1992
|
-
CROSS JOIN LATERAL UNNEST(ARRAY[
|
|
1993
|
-
CASE WHEN buyer_tracked THEN buyer_address END,
|
|
1994
|
-
CASE WHEN seller_tracked THEN seller_address END
|
|
1995
|
-
]) AS expanded(whale_address)
|
|
1996
|
-
WHERE whale_address IS NOT NULL
|
|
1997
|
-
GROUP BY symbol, date_trunc('minute', ts)
|
|
1998
|
-
)
|
|
1999
|
-
INSERT INTO hyperliquid_whale_flow (
|
|
2000
|
-
symbol,
|
|
2001
|
-
interval,
|
|
2002
|
-
ts,
|
|
2003
|
-
trades,
|
|
2004
|
-
whale_sides,
|
|
2005
|
-
unique_whales,
|
|
2006
|
-
whale_addresses,
|
|
2007
|
-
buy_notional_usd,
|
|
2008
|
-
sell_notional_usd,
|
|
2009
|
-
net_notional_usd,
|
|
2010
|
-
buy_share_pct,
|
|
2011
|
-
position_aware_whale_sides,
|
|
2012
|
-
long_entry_whale_addresses,
|
|
2013
|
-
short_entry_whale_addresses,
|
|
2014
|
-
long_exit_whale_addresses,
|
|
2015
|
-
short_exit_whale_addresses,
|
|
2016
|
-
long_entry_notional_usd,
|
|
2017
|
-
short_entry_notional_usd,
|
|
2018
|
-
long_exit_notional_usd,
|
|
2019
|
-
short_exit_notional_usd,
|
|
2020
|
-
entry_net_notional_usd,
|
|
2021
|
-
entry_long_share_pct,
|
|
2022
|
-
universe_fingerprint,
|
|
2023
|
-
whale_registry_fingerprint,
|
|
2024
|
-
source
|
|
2025
|
-
)
|
|
2026
|
-
SELECT
|
|
2027
|
-
metrics.symbol,
|
|
2028
|
-
'1m',
|
|
2029
|
-
metrics.bucket_ts,
|
|
2030
|
-
metrics.trades,
|
|
2031
|
-
metrics.whale_sides,
|
|
2032
|
-
COALESCE(CARDINALITY(addresses.whale_addresses), 0),
|
|
2033
|
-
COALESCE(addresses.whale_addresses, '{}'),
|
|
2034
|
-
metrics.buy_notional_usd,
|
|
2035
|
-
metrics.sell_notional_usd,
|
|
2036
|
-
metrics.buy_notional_usd - metrics.sell_notional_usd,
|
|
2037
|
-
CASE
|
|
2038
|
-
WHEN metrics.buy_notional_usd + metrics.sell_notional_usd > 0
|
|
2039
|
-
THEN metrics.buy_notional_usd /
|
|
2040
|
-
(metrics.buy_notional_usd + metrics.sell_notional_usd)
|
|
2041
|
-
ELSE NULL
|
|
2042
|
-
END,
|
|
2043
|
-
COALESCE(position_metrics.position_aware_whale_sides, 0),
|
|
2044
|
-
COALESCE(position_metrics.long_entry_whale_addresses, '{}'),
|
|
2045
|
-
COALESCE(position_metrics.short_entry_whale_addresses, '{}'),
|
|
2046
|
-
COALESCE(position_metrics.long_exit_whale_addresses, '{}'),
|
|
2047
|
-
COALESCE(position_metrics.short_exit_whale_addresses, '{}'),
|
|
2048
|
-
COALESCE(position_metrics.long_entry_notional_usd, 0),
|
|
2049
|
-
COALESCE(position_metrics.short_entry_notional_usd, 0),
|
|
2050
|
-
COALESCE(position_metrics.long_exit_notional_usd, 0),
|
|
2051
|
-
COALESCE(position_metrics.short_exit_notional_usd, 0),
|
|
2052
|
-
COALESCE(position_metrics.long_entry_notional_usd, 0) -
|
|
2053
|
-
COALESCE(position_metrics.short_entry_notional_usd, 0),
|
|
2054
|
-
CASE
|
|
2055
|
-
WHEN COALESCE(position_metrics.long_entry_notional_usd, 0) +
|
|
2056
|
-
COALESCE(position_metrics.short_entry_notional_usd, 0) > 0
|
|
2057
|
-
THEN COALESCE(position_metrics.long_entry_notional_usd, 0) /
|
|
2058
|
-
(
|
|
2059
|
-
COALESCE(position_metrics.long_entry_notional_usd, 0) +
|
|
2060
|
-
COALESCE(position_metrics.short_entry_notional_usd, 0)
|
|
2061
|
-
)
|
|
2062
|
-
ELSE NULL
|
|
2063
|
-
END,
|
|
2064
|
-
$1,
|
|
2065
|
-
$2,
|
|
2066
|
-
CASE
|
|
2067
|
-
WHEN COALESCE(position_metrics.position_aware_whale_sides, 0) > 0
|
|
2068
|
-
THEN 'hyperliquid_user_fills'
|
|
2069
|
-
ELSE 'hyperliquid_trades'
|
|
2070
|
-
END
|
|
2071
|
-
FROM metrics
|
|
2072
|
-
LEFT JOIN addresses USING (symbol, bucket_ts)
|
|
2073
|
-
LEFT JOIN position_metrics USING (symbol, bucket_ts)
|
|
2074
|
-
ON CONFLICT (
|
|
2075
|
-
universe_fingerprint,
|
|
2076
|
-
whale_registry_fingerprint,
|
|
2077
|
-
symbol,
|
|
2078
|
-
interval,
|
|
2079
|
-
ts
|
|
2080
|
-
) DO UPDATE SET
|
|
2081
|
-
trades = EXCLUDED.trades,
|
|
2082
|
-
whale_sides = EXCLUDED.whale_sides,
|
|
2083
|
-
unique_whales = EXCLUDED.unique_whales,
|
|
2084
|
-
whale_addresses = EXCLUDED.whale_addresses,
|
|
2085
|
-
buy_notional_usd = EXCLUDED.buy_notional_usd,
|
|
2086
|
-
sell_notional_usd = EXCLUDED.sell_notional_usd,
|
|
2087
|
-
net_notional_usd = EXCLUDED.net_notional_usd,
|
|
2088
|
-
buy_share_pct = EXCLUDED.buy_share_pct,
|
|
2089
|
-
position_aware_whale_sides = EXCLUDED.position_aware_whale_sides,
|
|
2090
|
-
long_entry_whale_addresses = EXCLUDED.long_entry_whale_addresses,
|
|
2091
|
-
short_entry_whale_addresses = EXCLUDED.short_entry_whale_addresses,
|
|
2092
|
-
long_exit_whale_addresses = EXCLUDED.long_exit_whale_addresses,
|
|
2093
|
-
short_exit_whale_addresses = EXCLUDED.short_exit_whale_addresses,
|
|
2094
|
-
long_entry_notional_usd = EXCLUDED.long_entry_notional_usd,
|
|
2095
|
-
short_entry_notional_usd = EXCLUDED.short_entry_notional_usd,
|
|
2096
|
-
long_exit_notional_usd = EXCLUDED.long_exit_notional_usd,
|
|
2097
|
-
short_exit_notional_usd = EXCLUDED.short_exit_notional_usd,
|
|
2098
|
-
entry_net_notional_usd = EXCLUDED.entry_net_notional_usd,
|
|
2099
|
-
entry_long_share_pct = EXCLUDED.entry_long_share_pct,
|
|
2100
|
-
source = EXCLUDED.source,
|
|
2101
|
-
ingested_at = now()
|
|
2102
|
-
RETURNING 1
|
|
2103
|
-
`,
|
|
2104
|
-
[
|
|
2105
|
-
params.universeFingerprint,
|
|
2106
|
-
params.whaleRegistryFingerprint,
|
|
2107
|
-
params.fromMs,
|
|
2108
|
-
params.toMs
|
|
2109
|
-
]
|
|
2110
|
-
);
|
|
2111
|
-
if (params.deleteEventsBeforeMs != null) {
|
|
2112
|
-
await client.query(
|
|
2113
|
-
`
|
|
2114
|
-
DELETE FROM hyperliquid_whale_trade_events
|
|
2115
|
-
WHERE universe_fingerprint = $1
|
|
2116
|
-
AND whale_registry_fingerprint = $2
|
|
2117
|
-
AND ts < to_timestamp($3/1000.0)
|
|
2118
|
-
`,
|
|
2119
|
-
[
|
|
2120
|
-
params.universeFingerprint,
|
|
2121
|
-
params.whaleRegistryFingerprint,
|
|
2122
|
-
params.deleteEventsBeforeMs
|
|
2123
|
-
]
|
|
2124
|
-
);
|
|
2125
|
-
}
|
|
2126
|
-
await client.query("COMMIT");
|
|
2127
|
-
return result.rowCount ?? 0;
|
|
2128
|
-
} catch (error) {
|
|
2129
|
-
await client.query("ROLLBACK");
|
|
2130
|
-
throw error;
|
|
2131
|
-
} finally {
|
|
2132
|
-
client.release();
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
async function upsertMarketBreadthRows(rows) {
|
|
2132
|
+
async function upsertMarketCmcExchangeLiquidityContextRows(rows) {
|
|
2136
2133
|
if (!rows.length) return;
|
|
2137
2134
|
await ensureBinanceMarketSchema();
|
|
2138
2135
|
const pool = getPool();
|
|
2139
2136
|
const cols = [
|
|
2140
|
-
"
|
|
2137
|
+
"source",
|
|
2141
2138
|
"interval",
|
|
2142
2139
|
"ts",
|
|
2143
|
-
"
|
|
2144
|
-
"
|
|
2145
|
-
"
|
|
2146
|
-
"
|
|
2147
|
-
"
|
|
2148
|
-
"
|
|
2149
|
-
"pct_above_ma50",
|
|
2150
|
-
"equal_weighted_return",
|
|
2151
|
-
"volume_weighted_return",
|
|
2152
|
-
"dispersion",
|
|
2153
|
-
"btc_return_1h",
|
|
2154
|
-
"btc_return_4h",
|
|
2155
|
-
"btc_return_24h",
|
|
2156
|
-
"alt_basket_return_1h",
|
|
2157
|
-
"alt_basket_return_4h",
|
|
2158
|
-
"alt_basket_return_24h",
|
|
2159
|
-
"btc_vs_alt_return_1h",
|
|
2160
|
-
"btc_vs_alt_return_4h",
|
|
2161
|
-
"btc_vs_alt_return_24h",
|
|
2162
|
-
"btc_turnover_share_1h",
|
|
2163
|
-
"btc_turnover_share_24h",
|
|
2164
|
-
"btc_turnover_share_change_24h",
|
|
2165
|
-
"alt_vol_to_btc_vol_24h",
|
|
2166
|
-
"alt_dispersion_24h",
|
|
2167
|
-
"btc_alt_regime",
|
|
2168
|
-
"source"
|
|
2140
|
+
"exchanges_count",
|
|
2141
|
+
"total_volume_usd",
|
|
2142
|
+
"binance_volume_usd",
|
|
2143
|
+
"binance_volume_share",
|
|
2144
|
+
"top_exchange_volume_share",
|
|
2145
|
+
"liquidity_regime"
|
|
2169
2146
|
];
|
|
2170
2147
|
const maxRows = getSafeBulkInsertRows(cols.length);
|
|
2171
2148
|
if (rows.length > maxRows) {
|
|
2172
2149
|
for (let i = 0; i < rows.length; i += maxRows) {
|
|
2173
|
-
await
|
|
2150
|
+
await upsertMarketCmcExchangeLiquidityContextRows(
|
|
2151
|
+
rows.slice(i, i + maxRows)
|
|
2152
|
+
);
|
|
2174
2153
|
}
|
|
2175
2154
|
return;
|
|
2176
2155
|
}
|
|
@@ -2178,235 +2157,7 @@ async function upsertMarketBreadthRows(rows) {
|
|
|
2178
2157
|
(_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
|
|
2179
2158
|
).join(",");
|
|
2180
2159
|
const flat = rows.flatMap((row) => [
|
|
2181
|
-
row.
|
|
2182
|
-
row.interval,
|
|
2183
|
-
row.ts,
|
|
2184
|
-
row.symbolsCount,
|
|
2185
|
-
row.advancers,
|
|
2186
|
-
row.decliners,
|
|
2187
|
-
row.unchanged,
|
|
2188
|
-
row.advanceDeclineRatio ?? null,
|
|
2189
|
-
row.pctAboveMa20 ?? null,
|
|
2190
|
-
row.pctAboveMa50 ?? null,
|
|
2191
|
-
row.equalWeightedReturn ?? null,
|
|
2192
|
-
row.volumeWeightedReturn ?? null,
|
|
2193
|
-
row.dispersion ?? null,
|
|
2194
|
-
row.btcReturn1h ?? null,
|
|
2195
|
-
row.btcReturn4h ?? null,
|
|
2196
|
-
row.btcReturn24h ?? null,
|
|
2197
|
-
row.altBasketReturn1h ?? null,
|
|
2198
|
-
row.altBasketReturn4h ?? null,
|
|
2199
|
-
row.altBasketReturn24h ?? null,
|
|
2200
|
-
row.btcVsAltReturn1h ?? null,
|
|
2201
|
-
row.btcVsAltReturn4h ?? null,
|
|
2202
|
-
row.btcVsAltReturn24h ?? null,
|
|
2203
|
-
row.btcTurnoverShare1h ?? null,
|
|
2204
|
-
row.btcTurnoverShare24h ?? null,
|
|
2205
|
-
row.btcTurnoverShareChange24h ?? null,
|
|
2206
|
-
row.altVolToBtcVol24h ?? null,
|
|
2207
|
-
row.altDispersion24h ?? null,
|
|
2208
|
-
row.btcAltRegime ?? null,
|
|
2209
|
-
row.source ?? null
|
|
2210
|
-
]);
|
|
2211
|
-
await pool.query(
|
|
2212
|
-
`
|
|
2213
|
-
INSERT INTO market_breadth (${cols.join(",")})
|
|
2214
|
-
VALUES ${valuesSql}
|
|
2215
|
-
ON CONFLICT (universe, interval, ts) DO UPDATE SET
|
|
2216
|
-
symbols_count = EXCLUDED.symbols_count,
|
|
2217
|
-
advancers = EXCLUDED.advancers,
|
|
2218
|
-
decliners = EXCLUDED.decliners,
|
|
2219
|
-
unchanged = EXCLUDED.unchanged,
|
|
2220
|
-
advance_decline_ratio = COALESCE(EXCLUDED.advance_decline_ratio, market_breadth.advance_decline_ratio),
|
|
2221
|
-
pct_above_ma20 = COALESCE(EXCLUDED.pct_above_ma20, market_breadth.pct_above_ma20),
|
|
2222
|
-
pct_above_ma50 = COALESCE(EXCLUDED.pct_above_ma50, market_breadth.pct_above_ma50),
|
|
2223
|
-
equal_weighted_return = COALESCE(EXCLUDED.equal_weighted_return, market_breadth.equal_weighted_return),
|
|
2224
|
-
volume_weighted_return = COALESCE(EXCLUDED.volume_weighted_return, market_breadth.volume_weighted_return),
|
|
2225
|
-
dispersion = COALESCE(EXCLUDED.dispersion, market_breadth.dispersion),
|
|
2226
|
-
btc_return_1h = COALESCE(EXCLUDED.btc_return_1h, market_breadth.btc_return_1h),
|
|
2227
|
-
btc_return_4h = COALESCE(EXCLUDED.btc_return_4h, market_breadth.btc_return_4h),
|
|
2228
|
-
btc_return_24h = COALESCE(EXCLUDED.btc_return_24h, market_breadth.btc_return_24h),
|
|
2229
|
-
alt_basket_return_1h = COALESCE(EXCLUDED.alt_basket_return_1h, market_breadth.alt_basket_return_1h),
|
|
2230
|
-
alt_basket_return_4h = COALESCE(EXCLUDED.alt_basket_return_4h, market_breadth.alt_basket_return_4h),
|
|
2231
|
-
alt_basket_return_24h = COALESCE(EXCLUDED.alt_basket_return_24h, market_breadth.alt_basket_return_24h),
|
|
2232
|
-
btc_vs_alt_return_1h = COALESCE(EXCLUDED.btc_vs_alt_return_1h, market_breadth.btc_vs_alt_return_1h),
|
|
2233
|
-
btc_vs_alt_return_4h = COALESCE(EXCLUDED.btc_vs_alt_return_4h, market_breadth.btc_vs_alt_return_4h),
|
|
2234
|
-
btc_vs_alt_return_24h = COALESCE(EXCLUDED.btc_vs_alt_return_24h, market_breadth.btc_vs_alt_return_24h),
|
|
2235
|
-
btc_turnover_share_1h = COALESCE(EXCLUDED.btc_turnover_share_1h, market_breadth.btc_turnover_share_1h),
|
|
2236
|
-
btc_turnover_share_24h = COALESCE(EXCLUDED.btc_turnover_share_24h, market_breadth.btc_turnover_share_24h),
|
|
2237
|
-
btc_turnover_share_change_24h = COALESCE(EXCLUDED.btc_turnover_share_change_24h, market_breadth.btc_turnover_share_change_24h),
|
|
2238
|
-
alt_vol_to_btc_vol_24h = COALESCE(EXCLUDED.alt_vol_to_btc_vol_24h, market_breadth.alt_vol_to_btc_vol_24h),
|
|
2239
|
-
alt_dispersion_24h = COALESCE(EXCLUDED.alt_dispersion_24h, market_breadth.alt_dispersion_24h),
|
|
2240
|
-
btc_alt_regime = COALESCE(EXCLUDED.btc_alt_regime, market_breadth.btc_alt_regime),
|
|
2241
|
-
source = COALESCE(EXCLUDED.source, market_breadth.source),
|
|
2242
|
-
ingested_at = now()
|
|
2243
|
-
`,
|
|
2244
|
-
flat
|
|
2245
|
-
);
|
|
2246
|
-
}
|
|
2247
|
-
async function upsertMarketGlobalContextRows(rows) {
|
|
2248
|
-
if (!rows.length) return;
|
|
2249
|
-
await ensureBinanceMarketSchema();
|
|
2250
|
-
const pool = getPool();
|
|
2251
|
-
const cols = [
|
|
2252
|
-
"source",
|
|
2253
|
-
"ts",
|
|
2254
|
-
"updated_at_ts",
|
|
2255
|
-
"active_cryptocurrencies",
|
|
2256
|
-
"active_exchanges",
|
|
2257
|
-
"active_market_pairs",
|
|
2258
|
-
"markets",
|
|
2259
|
-
"total_market_cap_usd",
|
|
2260
|
-
"total_volume_usd",
|
|
2261
|
-
"total_volume_reported_usd",
|
|
2262
|
-
"btc_dominance_pct",
|
|
2263
|
-
"eth_dominance_pct",
|
|
2264
|
-
"alt_market_cap_usd",
|
|
2265
|
-
"alt_volume_usd",
|
|
2266
|
-
"alt_volume_reported_usd",
|
|
2267
|
-
"btc_to_alt_market_cap_ratio",
|
|
2268
|
-
"market_cap_change_pct_24h_usd"
|
|
2269
|
-
];
|
|
2270
|
-
const maxRows = getSafeBulkInsertRows(cols.length);
|
|
2271
|
-
if (rows.length > maxRows) {
|
|
2272
|
-
for (let i = 0; i < rows.length; i += maxRows) {
|
|
2273
|
-
await upsertMarketGlobalContextRows(rows.slice(i, i + maxRows));
|
|
2274
|
-
}
|
|
2275
|
-
return;
|
|
2276
|
-
}
|
|
2277
|
-
const valuesSql = rows.map(
|
|
2278
|
-
(_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
|
|
2279
|
-
).join(",");
|
|
2280
|
-
const flat = rows.flatMap((row) => [
|
|
2281
|
-
row.source,
|
|
2282
|
-
row.ts,
|
|
2283
|
-
row.updatedAt ?? null,
|
|
2284
|
-
row.activeCryptocurrencies ?? null,
|
|
2285
|
-
row.activeExchanges ?? null,
|
|
2286
|
-
row.activeMarketPairs ?? null,
|
|
2287
|
-
row.markets ?? null,
|
|
2288
|
-
row.totalMarketCapUsd ?? null,
|
|
2289
|
-
row.totalVolumeUsd ?? null,
|
|
2290
|
-
row.totalVolumeReportedUsd ?? null,
|
|
2291
|
-
row.btcDominancePct ?? null,
|
|
2292
|
-
row.ethDominancePct ?? null,
|
|
2293
|
-
row.altMarketCapUsd ?? null,
|
|
2294
|
-
row.altVolumeUsd ?? null,
|
|
2295
|
-
row.altVolumeReportedUsd ?? null,
|
|
2296
|
-
row.btcToAltMarketCapRatio ?? null,
|
|
2297
|
-
row.marketCapChangePct24hUsd ?? null
|
|
2298
|
-
]);
|
|
2299
|
-
await pool.query(
|
|
2300
|
-
`
|
|
2301
|
-
INSERT INTO market_global_context (${cols.join(",")})
|
|
2302
|
-
VALUES ${valuesSql}
|
|
2303
|
-
ON CONFLICT (source, ts) DO UPDATE SET
|
|
2304
|
-
updated_at_ts = COALESCE(EXCLUDED.updated_at_ts, market_global_context.updated_at_ts),
|
|
2305
|
-
active_cryptocurrencies = COALESCE(EXCLUDED.active_cryptocurrencies, market_global_context.active_cryptocurrencies),
|
|
2306
|
-
active_exchanges = COALESCE(EXCLUDED.active_exchanges, market_global_context.active_exchanges),
|
|
2307
|
-
active_market_pairs = COALESCE(EXCLUDED.active_market_pairs, market_global_context.active_market_pairs),
|
|
2308
|
-
markets = COALESCE(EXCLUDED.markets, market_global_context.markets),
|
|
2309
|
-
total_market_cap_usd = COALESCE(EXCLUDED.total_market_cap_usd, market_global_context.total_market_cap_usd),
|
|
2310
|
-
total_volume_usd = COALESCE(EXCLUDED.total_volume_usd, market_global_context.total_volume_usd),
|
|
2311
|
-
total_volume_reported_usd = COALESCE(EXCLUDED.total_volume_reported_usd, market_global_context.total_volume_reported_usd),
|
|
2312
|
-
btc_dominance_pct = COALESCE(EXCLUDED.btc_dominance_pct, market_global_context.btc_dominance_pct),
|
|
2313
|
-
eth_dominance_pct = COALESCE(EXCLUDED.eth_dominance_pct, market_global_context.eth_dominance_pct),
|
|
2314
|
-
alt_market_cap_usd = COALESCE(EXCLUDED.alt_market_cap_usd, market_global_context.alt_market_cap_usd),
|
|
2315
|
-
alt_volume_usd = COALESCE(EXCLUDED.alt_volume_usd, market_global_context.alt_volume_usd),
|
|
2316
|
-
alt_volume_reported_usd = COALESCE(EXCLUDED.alt_volume_reported_usd, market_global_context.alt_volume_reported_usd),
|
|
2317
|
-
btc_to_alt_market_cap_ratio = COALESCE(EXCLUDED.btc_to_alt_market_cap_ratio, market_global_context.btc_to_alt_market_cap_ratio),
|
|
2318
|
-
market_cap_change_pct_24h_usd = COALESCE(EXCLUDED.market_cap_change_pct_24h_usd, market_global_context.market_cap_change_pct_24h_usd),
|
|
2319
|
-
ingested_at = now()
|
|
2320
|
-
`,
|
|
2321
|
-
flat
|
|
2322
|
-
);
|
|
2323
|
-
}
|
|
2324
|
-
async function upsertMarketReferenceAssetContextRows(rows) {
|
|
2325
|
-
if (!rows.length) return;
|
|
2326
|
-
await ensureBinanceMarketSchema();
|
|
2327
|
-
const pool = getPool();
|
|
2328
|
-
const cols = [
|
|
2329
|
-
"source",
|
|
2330
|
-
"symbol",
|
|
2331
|
-
"cmc_id",
|
|
2332
|
-
"interval",
|
|
2333
|
-
"ts",
|
|
2334
|
-
"open_usd",
|
|
2335
|
-
"high_usd",
|
|
2336
|
-
"low_usd",
|
|
2337
|
-
"close_usd",
|
|
2338
|
-
"volume_usd",
|
|
2339
|
-
"market_cap_usd"
|
|
2340
|
-
];
|
|
2341
|
-
const maxRows = getSafeBulkInsertRows(cols.length);
|
|
2342
|
-
if (rows.length > maxRows) {
|
|
2343
|
-
for (let i = 0; i < rows.length; i += maxRows) {
|
|
2344
|
-
await upsertMarketReferenceAssetContextRows(rows.slice(i, i + maxRows));
|
|
2345
|
-
}
|
|
2346
|
-
return;
|
|
2347
|
-
}
|
|
2348
|
-
const valuesSql = rows.map(
|
|
2349
|
-
(_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
|
|
2350
|
-
).join(",");
|
|
2351
|
-
const flat = rows.flatMap((row) => [
|
|
2352
|
-
row.source,
|
|
2353
|
-
row.symbol.trim().toUpperCase(),
|
|
2354
|
-
Math.trunc(row.cmcId),
|
|
2355
|
-
row.interval,
|
|
2356
|
-
row.ts,
|
|
2357
|
-
row.openUsd ?? null,
|
|
2358
|
-
row.highUsd ?? null,
|
|
2359
|
-
row.lowUsd ?? null,
|
|
2360
|
-
row.closeUsd ?? null,
|
|
2361
|
-
row.volumeUsd ?? null,
|
|
2362
|
-
row.marketCapUsd ?? null
|
|
2363
|
-
]);
|
|
2364
|
-
await pool.query(
|
|
2365
|
-
`
|
|
2366
|
-
INSERT INTO market_reference_asset_context (${cols.join(",")})
|
|
2367
|
-
VALUES ${valuesSql}
|
|
2368
|
-
ON CONFLICT (source, symbol, interval, ts) DO UPDATE SET
|
|
2369
|
-
cmc_id = EXCLUDED.cmc_id,
|
|
2370
|
-
open_usd = COALESCE(EXCLUDED.open_usd, market_reference_asset_context.open_usd),
|
|
2371
|
-
high_usd = COALESCE(EXCLUDED.high_usd, market_reference_asset_context.high_usd),
|
|
2372
|
-
low_usd = COALESCE(EXCLUDED.low_usd, market_reference_asset_context.low_usd),
|
|
2373
|
-
close_usd = COALESCE(EXCLUDED.close_usd, market_reference_asset_context.close_usd),
|
|
2374
|
-
volume_usd = COALESCE(EXCLUDED.volume_usd, market_reference_asset_context.volume_usd),
|
|
2375
|
-
market_cap_usd = COALESCE(EXCLUDED.market_cap_usd, market_reference_asset_context.market_cap_usd),
|
|
2376
|
-
ingested_at = now()
|
|
2377
|
-
`,
|
|
2378
|
-
flat
|
|
2379
|
-
);
|
|
2380
|
-
}
|
|
2381
|
-
async function upsertMarketCmcExchangeLiquidityContextRows(rows) {
|
|
2382
|
-
if (!rows.length) return;
|
|
2383
|
-
await ensureBinanceMarketSchema();
|
|
2384
|
-
const pool = getPool();
|
|
2385
|
-
const cols = [
|
|
2386
|
-
"source",
|
|
2387
|
-
"interval",
|
|
2388
|
-
"ts",
|
|
2389
|
-
"exchanges_count",
|
|
2390
|
-
"total_volume_usd",
|
|
2391
|
-
"binance_volume_usd",
|
|
2392
|
-
"binance_volume_share",
|
|
2393
|
-
"top_exchange_volume_share",
|
|
2394
|
-
"liquidity_regime"
|
|
2395
|
-
];
|
|
2396
|
-
const maxRows = getSafeBulkInsertRows(cols.length);
|
|
2397
|
-
if (rows.length > maxRows) {
|
|
2398
|
-
for (let i = 0; i < rows.length; i += maxRows) {
|
|
2399
|
-
await upsertMarketCmcExchangeLiquidityContextRows(
|
|
2400
|
-
rows.slice(i, i + maxRows)
|
|
2401
|
-
);
|
|
2402
|
-
}
|
|
2403
|
-
return;
|
|
2404
|
-
}
|
|
2405
|
-
const valuesSql = rows.map(
|
|
2406
|
-
(_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
|
|
2407
|
-
).join(",");
|
|
2408
|
-
const flat = rows.flatMap((row) => [
|
|
2409
|
-
row.source,
|
|
2160
|
+
row.source,
|
|
2410
2161
|
row.interval,
|
|
2411
2162
|
row.ts,
|
|
2412
2163
|
Math.trunc(row.exchangesCount),
|
|
@@ -2615,10 +2366,6 @@ async function upsertMarketContextBackfillCoverage(rows) {
|
|
|
2615
2366
|
flat
|
|
2616
2367
|
);
|
|
2617
2368
|
}
|
|
2618
|
-
var toMarketFeatureAge = (rowTs, atMs) => {
|
|
2619
|
-
const ageMs = atMs - rowTs.getTime();
|
|
2620
|
-
return Number.isFinite(ageMs) ? ageMs : null;
|
|
2621
|
-
};
|
|
2622
2369
|
async function getLatestMarketTradeFlow(params) {
|
|
2623
2370
|
await prepareMarketContextSchemaForRead("binance");
|
|
2624
2371
|
const res = await queryMarketContext(
|
|
@@ -2655,695 +2402,388 @@ async function getLatestMarketTradeFlow(params) {
|
|
|
2655
2402
|
stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs
|
|
2656
2403
|
};
|
|
2657
2404
|
}
|
|
2658
|
-
|
|
2659
|
-
"
|
|
2660
|
-
|
|
2661
|
-
"15m": 15 * 6e4,
|
|
2662
|
-
"1h": 60 * 6e4
|
|
2663
|
-
};
|
|
2664
|
-
async function getHyperliquidWhaleWalletCoverage(params) {
|
|
2665
|
-
await ensureHyperliquidWhaleSchema();
|
|
2666
|
-
const result = await getPool().query(
|
|
2405
|
+
async function getLatestMarketBreadth(params) {
|
|
2406
|
+
await prepareMarketContextSchemaForRead("binance");
|
|
2407
|
+
const res = await queryMarketContext(
|
|
2667
2408
|
`
|
|
2668
2409
|
SELECT
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2410
|
+
universe,
|
|
2411
|
+
interval,
|
|
2412
|
+
ts,
|
|
2413
|
+
symbols_count::int AS "symbolsCount",
|
|
2414
|
+
advancers::int AS advancers,
|
|
2415
|
+
decliners::int AS decliners,
|
|
2416
|
+
unchanged::int AS unchanged,
|
|
2417
|
+
advance_decline_ratio AS "advanceDeclineRatio",
|
|
2418
|
+
pct_above_ma20 AS "pctAboveMa20",
|
|
2419
|
+
pct_above_ma50 AS "pctAboveMa50",
|
|
2420
|
+
equal_weighted_return AS "equalWeightedReturn",
|
|
2421
|
+
volume_weighted_return AS "volumeWeightedReturn",
|
|
2422
|
+
dispersion,
|
|
2423
|
+
btc_return_1h AS "btcReturn1h",
|
|
2424
|
+
btc_return_4h AS "btcReturn4h",
|
|
2425
|
+
btc_return_24h AS "btcReturn24h",
|
|
2426
|
+
alt_basket_return_1h AS "altBasketReturn1h",
|
|
2427
|
+
alt_basket_return_4h AS "altBasketReturn4h",
|
|
2428
|
+
alt_basket_return_24h AS "altBasketReturn24h",
|
|
2429
|
+
btc_vs_alt_return_1h AS "btcVsAltReturn1h",
|
|
2430
|
+
btc_vs_alt_return_4h AS "btcVsAltReturn4h",
|
|
2431
|
+
btc_vs_alt_return_24h AS "btcVsAltReturn24h",
|
|
2432
|
+
btc_turnover_share_1h AS "btcTurnoverShare1h",
|
|
2433
|
+
btc_turnover_share_24h AS "btcTurnoverShare24h",
|
|
2434
|
+
btc_turnover_share_change_24h AS "btcTurnoverShareChange24h",
|
|
2435
|
+
alt_vol_to_btc_vol_24h AS "altVolToBtcVol24h",
|
|
2436
|
+
alt_dispersion_24h AS "altDispersion24h",
|
|
2437
|
+
btc_alt_regime AS "btcAltRegime",
|
|
2438
|
+
source
|
|
2439
|
+
FROM market_breadth
|
|
2440
|
+
WHERE universe = $1
|
|
2441
|
+
AND interval = $2
|
|
2442
|
+
AND ts <= to_timestamp($3/1000.0)
|
|
2443
|
+
ORDER BY ts DESC
|
|
2691
2444
|
LIMIT 1
|
|
2692
2445
|
`,
|
|
2693
|
-
[
|
|
2694
|
-
|
|
2695
|
-
params.whaleRegistryFingerprint,
|
|
2696
|
-
params.address.toLowerCase(),
|
|
2697
|
-
params.fromMs,
|
|
2698
|
-
params.toMs,
|
|
2699
|
-
import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
2700
|
-
]
|
|
2446
|
+
[params.universe, params.interval, params.atMs],
|
|
2447
|
+
params
|
|
2701
2448
|
);
|
|
2702
|
-
const row =
|
|
2449
|
+
const row = res.rows[0];
|
|
2703
2450
|
if (!row) return null;
|
|
2451
|
+
const ageMs = toMarketFeatureAge(row.ts, params.atMs);
|
|
2704
2452
|
return {
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
fillsCount: Number(row.fills_count) || 0,
|
|
2709
|
-
error: row.error == null ? null : String(row.error),
|
|
2710
|
-
checkedAt: new Date(row.checked_at)
|
|
2453
|
+
...row,
|
|
2454
|
+
ageMs,
|
|
2455
|
+
stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs
|
|
2711
2456
|
};
|
|
2712
2457
|
}
|
|
2713
|
-
async function
|
|
2714
|
-
await
|
|
2715
|
-
|
|
2458
|
+
async function getLatestMarketGlobalContext(params) {
|
|
2459
|
+
await prepareMarketContextSchemaForRead("coinmarketcap");
|
|
2460
|
+
const source = params.source ?? "coinmarketcap_global";
|
|
2461
|
+
const res = await queryMarketContext(
|
|
2716
2462
|
`
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
$
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
universe_fingerprint,
|
|
2744
|
-
whale_registry_fingerprint,
|
|
2745
|
-
address,
|
|
2746
|
-
requested_from_ts,
|
|
2747
|
-
requested_to_ts
|
|
2748
|
-
) DO UPDATE SET
|
|
2749
|
-
covered_from_ts = EXCLUDED.covered_from_ts,
|
|
2750
|
-
covered_to_ts = EXCLUDED.covered_to_ts,
|
|
2751
|
-
status = EXCLUDED.status,
|
|
2752
|
-
fills_count = EXCLUDED.fills_count,
|
|
2753
|
-
error = EXCLUDED.error,
|
|
2754
|
-
data_model_version = EXCLUDED.data_model_version,
|
|
2755
|
-
checked_at = now()
|
|
2463
|
+
SELECT
|
|
2464
|
+
source,
|
|
2465
|
+
ts,
|
|
2466
|
+
updated_at_ts AS "updatedAt",
|
|
2467
|
+
active_cryptocurrencies::int AS "activeCryptocurrencies",
|
|
2468
|
+
active_exchanges::int AS "activeExchanges",
|
|
2469
|
+
active_market_pairs::int AS "activeMarketPairs",
|
|
2470
|
+
markets::int AS markets,
|
|
2471
|
+
total_market_cap_usd AS "totalMarketCapUsd",
|
|
2472
|
+
total_volume_usd AS "totalVolumeUsd",
|
|
2473
|
+
total_volume_reported_usd AS "totalVolumeReportedUsd",
|
|
2474
|
+
btc_dominance_pct AS "btcDominancePct",
|
|
2475
|
+
eth_dominance_pct AS "ethDominancePct",
|
|
2476
|
+
alt_market_cap_usd AS "altMarketCapUsd",
|
|
2477
|
+
alt_volume_usd AS "altVolumeUsd",
|
|
2478
|
+
alt_volume_reported_usd AS "altVolumeReportedUsd",
|
|
2479
|
+
btc_to_alt_market_cap_ratio AS "btcToAltMarketCapRatio",
|
|
2480
|
+
market_cap_change_pct_24h_usd AS "marketCapChangePct24hUsd"
|
|
2481
|
+
FROM market_global_context
|
|
2482
|
+
WHERE source = $1
|
|
2483
|
+
AND ts + CASE
|
|
2484
|
+
WHEN source = 'coinmarketcap_global' THEN interval '1 day'
|
|
2485
|
+
ELSE interval '0 seconds'
|
|
2486
|
+
END <= to_timestamp($2/1000.0)
|
|
2487
|
+
ORDER BY ts DESC
|
|
2488
|
+
LIMIT 1
|
|
2756
2489
|
`,
|
|
2757
|
-
[
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2490
|
+
[source, params.atMs],
|
|
2491
|
+
params
|
|
2492
|
+
);
|
|
2493
|
+
const row = res.rows[0];
|
|
2494
|
+
if (!row) return null;
|
|
2495
|
+
const previousRes = await queryMarketContext(
|
|
2496
|
+
`
|
|
2497
|
+
SELECT
|
|
2498
|
+
btc_dominance_pct AS "btcDominancePct",
|
|
2499
|
+
eth_dominance_pct AS "ethDominancePct",
|
|
2500
|
+
alt_market_cap_usd AS "altMarketCapUsd",
|
|
2501
|
+
alt_volume_usd AS "altVolumeUsd"
|
|
2502
|
+
FROM market_global_context
|
|
2503
|
+
WHERE source = $1
|
|
2504
|
+
AND ts <= $2::timestamptz - interval '24 hours'
|
|
2505
|
+
ORDER BY ts DESC
|
|
2506
|
+
LIMIT 1
|
|
2507
|
+
`,
|
|
2508
|
+
[source, row.ts],
|
|
2509
|
+
params
|
|
2770
2510
|
);
|
|
2511
|
+
const previousDominance = previousRes.rows[0]?.btcDominancePct == null ? null : Number(previousRes.rows[0].btcDominancePct);
|
|
2512
|
+
const previousEthDominance = previousRes.rows[0]?.ethDominancePct == null ? null : Number(previousRes.rows[0].ethDominancePct);
|
|
2513
|
+
const previousAltMarketCap = previousRes.rows[0]?.altMarketCapUsd == null ? null : Number(previousRes.rows[0].altMarketCapUsd);
|
|
2514
|
+
const previousAltVolume = previousRes.rows[0]?.altVolumeUsd == null ? null : Number(previousRes.rows[0].altVolumeUsd);
|
|
2515
|
+
const currentDominance = row.btcDominancePct == null ? null : Number(row.btcDominancePct);
|
|
2516
|
+
const currentEthDominance = row.ethDominancePct == null ? null : Number(row.ethDominancePct);
|
|
2517
|
+
const currentAltMarketCap = row.altMarketCapUsd == null ? null : Number(row.altMarketCapUsd);
|
|
2518
|
+
const currentAltVolume = row.altVolumeUsd == null ? null : Number(row.altVolumeUsd);
|
|
2519
|
+
const ageMs = toMarketFeatureAge(row.ts, params.atMs);
|
|
2520
|
+
return {
|
|
2521
|
+
...row,
|
|
2522
|
+
ageMs,
|
|
2523
|
+
stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs,
|
|
2524
|
+
btcDominanceChange24hPct: currentDominance != null && previousDominance != null ? currentDominance - previousDominance : null,
|
|
2525
|
+
ethDominanceChange24hPct: currentEthDominance != null && previousEthDominance != null ? currentEthDominance - previousEthDominance : null,
|
|
2526
|
+
altMarketCapChange24hPct: currentAltMarketCap != null && previousAltMarketCap != null && previousAltMarketCap > 0 ? (currentAltMarketCap - previousAltMarketCap) / previousAltMarketCap : null,
|
|
2527
|
+
altVolumeChange24hPct: currentAltVolume != null && previousAltVolume != null && previousAltVolume > 0 ? (currentAltVolume - previousAltVolume) / previousAltVolume : null
|
|
2528
|
+
};
|
|
2771
2529
|
}
|
|
2772
|
-
async function
|
|
2773
|
-
await
|
|
2774
|
-
|
|
2775
|
-
const
|
|
2776
|
-
const defaultChunkMinutes = 7 * 24 * 60;
|
|
2777
|
-
const chunkMinutes = Number.isFinite(params.chunkMinutes) && Number(params.chunkMinutes) > 0 ? Math.floor(Number(params.chunkMinutes)) : defaultChunkMinutes;
|
|
2778
|
-
const chunkMs = chunkMinutes * minuteMs;
|
|
2779
|
-
const totalBuckets = Math.ceil((params.toMs - params.fromMs) / minuteMs);
|
|
2780
|
-
const totalChunks = Math.ceil((params.toMs - params.fromMs) / chunkMs);
|
|
2781
|
-
let completedBuckets = 0;
|
|
2782
|
-
let rows = 0;
|
|
2783
|
-
for (let chunkIndex = 0, chunkFromMs = params.fromMs; chunkFromMs < params.toMs; chunkIndex += 1, chunkFromMs += chunkMs) {
|
|
2784
|
-
const chunkToMs = Math.min(params.toMs, chunkFromMs + chunkMs);
|
|
2785
|
-
const result = await getPool().query(
|
|
2786
|
-
`
|
|
2787
|
-
WITH normalized_ranges AS (
|
|
2788
|
-
SELECT
|
|
2789
|
-
address,
|
|
2790
|
-
GREATEST(
|
|
2791
|
-
to_timestamp($3/1000.0),
|
|
2792
|
-
date_trunc('minute', covered_from_ts) +
|
|
2793
|
-
CASE
|
|
2794
|
-
WHEN covered_from_ts = date_trunc('minute', covered_from_ts)
|
|
2795
|
-
THEN interval '0 minutes'
|
|
2796
|
-
ELSE interval '1 minute'
|
|
2797
|
-
END
|
|
2798
|
-
) AS range_start,
|
|
2799
|
-
LEAST(
|
|
2800
|
-
to_timestamp($4/1000.0),
|
|
2801
|
-
date_trunc('minute', covered_to_ts)
|
|
2802
|
-
) AS range_end
|
|
2803
|
-
FROM hyperliquid_whale_wallet_coverage
|
|
2804
|
-
WHERE universe_fingerprint = $1
|
|
2805
|
-
AND whale_registry_fingerprint = $2
|
|
2806
|
-
AND data_model_version = $6
|
|
2807
|
-
AND status IN ('complete', 'truncated')
|
|
2808
|
-
AND covered_from_ts < to_timestamp($4/1000.0)
|
|
2809
|
-
AND covered_to_ts > to_timestamp($3/1000.0)
|
|
2810
|
-
), eligible_ranges AS (
|
|
2811
|
-
SELECT *
|
|
2812
|
-
FROM normalized_ranges
|
|
2813
|
-
WHERE range_start < range_end
|
|
2814
|
-
), ordered_ranges AS (
|
|
2815
|
-
SELECT
|
|
2816
|
-
*,
|
|
2817
|
-
MAX(range_end) OVER (
|
|
2818
|
-
PARTITION BY address
|
|
2819
|
-
ORDER BY range_start, range_end
|
|
2820
|
-
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
|
|
2821
|
-
) AS previous_max_end
|
|
2822
|
-
FROM eligible_ranges
|
|
2823
|
-
), marked_ranges AS (
|
|
2824
|
-
SELECT
|
|
2825
|
-
*,
|
|
2826
|
-
SUM(
|
|
2827
|
-
CASE
|
|
2828
|
-
WHEN previous_max_end IS NULL OR range_start > previous_max_end
|
|
2829
|
-
THEN 1
|
|
2830
|
-
ELSE 0
|
|
2831
|
-
END
|
|
2832
|
-
) OVER (
|
|
2833
|
-
PARTITION BY address
|
|
2834
|
-
ORDER BY range_start, range_end
|
|
2835
|
-
) AS range_group
|
|
2836
|
-
FROM ordered_ranges
|
|
2837
|
-
), merged_ranges AS (
|
|
2838
|
-
SELECT
|
|
2839
|
-
address,
|
|
2840
|
-
MIN(range_start) AS range_start,
|
|
2841
|
-
MAX(range_end) AS range_end
|
|
2842
|
-
FROM marked_ranges
|
|
2843
|
-
GROUP BY address, range_group
|
|
2844
|
-
), deltas AS (
|
|
2845
|
-
SELECT range_start AS ts, 1 AS delta
|
|
2846
|
-
FROM merged_ranges
|
|
2847
|
-
UNION ALL
|
|
2848
|
-
SELECT range_end AS ts, -1 AS delta
|
|
2849
|
-
FROM merged_ranges
|
|
2850
|
-
), bucket_deltas AS (
|
|
2851
|
-
SELECT ts, SUM(delta)::int AS delta
|
|
2852
|
-
FROM deltas
|
|
2853
|
-
GROUP BY ts
|
|
2854
|
-
), buckets AS (
|
|
2855
|
-
SELECT generate_series(
|
|
2856
|
-
to_timestamp($3/1000.0),
|
|
2857
|
-
to_timestamp($4/1000.0) - interval '1 minute',
|
|
2858
|
-
interval '1 minute'
|
|
2859
|
-
) AS ts
|
|
2860
|
-
), coverage AS (
|
|
2861
|
-
SELECT
|
|
2862
|
-
buckets.ts,
|
|
2863
|
-
SUM(COALESCE(bucket_deltas.delta, 0)) OVER (
|
|
2864
|
-
ORDER BY buckets.ts
|
|
2865
|
-
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
|
2866
|
-
)::int AS covered_whales
|
|
2867
|
-
FROM buckets
|
|
2868
|
-
LEFT JOIN bucket_deltas USING (ts)
|
|
2869
|
-
)
|
|
2870
|
-
INSERT INTO hyperliquid_whale_coverage_1m (
|
|
2871
|
-
ts,
|
|
2872
|
-
covered_whales,
|
|
2873
|
-
expected_whales,
|
|
2874
|
-
coverage_pct,
|
|
2875
|
-
universe_fingerprint,
|
|
2876
|
-
whale_registry_fingerprint,
|
|
2877
|
-
source,
|
|
2878
|
-
data_model_version
|
|
2879
|
-
)
|
|
2880
|
-
SELECT
|
|
2881
|
-
ts,
|
|
2882
|
-
covered_whales,
|
|
2883
|
-
$5,
|
|
2884
|
-
CASE WHEN $5 > 0 THEN covered_whales::double precision / $5 ELSE 0 END,
|
|
2885
|
-
$1,
|
|
2886
|
-
$2,
|
|
2887
|
-
'hyperliquid_user_fills',
|
|
2888
|
-
$6
|
|
2889
|
-
FROM coverage
|
|
2890
|
-
ON CONFLICT (
|
|
2891
|
-
universe_fingerprint,
|
|
2892
|
-
whale_registry_fingerprint,
|
|
2893
|
-
ts
|
|
2894
|
-
) DO UPDATE SET
|
|
2895
|
-
covered_whales = EXCLUDED.covered_whales,
|
|
2896
|
-
expected_whales = EXCLUDED.expected_whales,
|
|
2897
|
-
coverage_pct = EXCLUDED.coverage_pct,
|
|
2898
|
-
source = EXCLUDED.source,
|
|
2899
|
-
data_model_version = EXCLUDED.data_model_version,
|
|
2900
|
-
ingested_at = now()
|
|
2901
|
-
`,
|
|
2902
|
-
[
|
|
2903
|
-
params.universeFingerprint,
|
|
2904
|
-
params.whaleRegistryFingerprint,
|
|
2905
|
-
chunkFromMs,
|
|
2906
|
-
chunkToMs,
|
|
2907
|
-
params.expectedWhales,
|
|
2908
|
-
import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
2909
|
-
]
|
|
2910
|
-
);
|
|
2911
|
-
const chunkBuckets = Math.ceil((chunkToMs - chunkFromMs) / minuteMs);
|
|
2912
|
-
completedBuckets = Math.min(totalBuckets, completedBuckets + chunkBuckets);
|
|
2913
|
-
rows += result.rowCount ?? 0;
|
|
2914
|
-
params.onProgress?.({
|
|
2915
|
-
chunkIndex: chunkIndex + 1,
|
|
2916
|
-
totalChunks,
|
|
2917
|
-
completedBuckets,
|
|
2918
|
-
totalBuckets,
|
|
2919
|
-
rows
|
|
2920
|
-
});
|
|
2921
|
-
}
|
|
2922
|
-
return rows;
|
|
2923
|
-
}
|
|
2924
|
-
async function upsertHyperliquidWhaleCoverageRows(rows) {
|
|
2925
|
-
if (!rows.length) return;
|
|
2926
|
-
await ensureHyperliquidWhaleSchema();
|
|
2927
|
-
const cols = [
|
|
2928
|
-
"ts",
|
|
2929
|
-
"covered_whales",
|
|
2930
|
-
"expected_whales",
|
|
2931
|
-
"coverage_pct",
|
|
2932
|
-
"universe_fingerprint",
|
|
2933
|
-
"whale_registry_fingerprint",
|
|
2934
|
-
"source",
|
|
2935
|
-
"data_model_version"
|
|
2936
|
-
];
|
|
2937
|
-
const maxRows = Math.floor(PG_SAFE_MAX_BIND_PARAMS / cols.length);
|
|
2938
|
-
if (rows.length > maxRows) {
|
|
2939
|
-
for (let index = 0; index < rows.length; index += maxRows) {
|
|
2940
|
-
await upsertHyperliquidWhaleCoverageRows(
|
|
2941
|
-
rows.slice(index, index + maxRows)
|
|
2942
|
-
);
|
|
2943
|
-
}
|
|
2944
|
-
return;
|
|
2945
|
-
}
|
|
2946
|
-
const values = [];
|
|
2947
|
-
const tuples = rows.map((row, rowIndex) => {
|
|
2948
|
-
const offset = rowIndex * cols.length;
|
|
2949
|
-
values.push(
|
|
2950
|
-
row.ts,
|
|
2951
|
-
row.coveredWhales,
|
|
2952
|
-
row.expectedWhales,
|
|
2953
|
-
row.coveragePct,
|
|
2954
|
-
row.universeFingerprint,
|
|
2955
|
-
row.whaleRegistryFingerprint,
|
|
2956
|
-
row.source ?? null,
|
|
2957
|
-
row.dataModelVersion ?? import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
2958
|
-
);
|
|
2959
|
-
return `(${cols.map((_, colIndex) => `$${offset + colIndex + 1}`).join(",")})`;
|
|
2960
|
-
});
|
|
2961
|
-
await getPool().query(
|
|
2530
|
+
async function getMarketGlobalContextCoverage(params) {
|
|
2531
|
+
await ensureBinanceMarketSchema();
|
|
2532
|
+
const pool = getPool();
|
|
2533
|
+
const res = await pool.query(
|
|
2962
2534
|
`
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
expected_whales = EXCLUDED.expected_whales,
|
|
2972
|
-
coverage_pct = EXCLUDED.coverage_pct,
|
|
2973
|
-
source = EXCLUDED.source,
|
|
2974
|
-
data_model_version = EXCLUDED.data_model_version,
|
|
2975
|
-
ingested_at = now()
|
|
2535
|
+
SELECT
|
|
2536
|
+
extract(epoch from MIN(ts))*1000 AS first_ms,
|
|
2537
|
+
extract(epoch from MAX(ts))*1000 AS last_ms,
|
|
2538
|
+
COUNT(*)::int AS rows
|
|
2539
|
+
FROM market_global_context
|
|
2540
|
+
WHERE source = $1
|
|
2541
|
+
AND ts >= to_timestamp($2/1000.0)
|
|
2542
|
+
AND ts <= to_timestamp($3/1000.0)
|
|
2976
2543
|
`,
|
|
2977
|
-
|
|
2544
|
+
[params.source, params.startMs, params.endMs]
|
|
2978
2545
|
);
|
|
2546
|
+
const row = res.rows[0];
|
|
2547
|
+
const rows = Number(row?.rows ?? 0);
|
|
2548
|
+
const firstMs = Number(row?.first_ms);
|
|
2549
|
+
const lastMs = Number(row?.last_ms);
|
|
2550
|
+
if (!rows || !Number.isFinite(firstMs) || !Number.isFinite(lastMs)) {
|
|
2551
|
+
return null;
|
|
2552
|
+
}
|
|
2553
|
+
return { firstMs, lastMs, rows };
|
|
2979
2554
|
}
|
|
2980
|
-
async function
|
|
2981
|
-
|
|
2982
|
-
|
|
2555
|
+
async function getMarketReferenceAssetContextCoverage(params) {
|
|
2556
|
+
const symbols = [
|
|
2557
|
+
...new Set(
|
|
2558
|
+
params.symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean)
|
|
2559
|
+
)
|
|
2560
|
+
];
|
|
2561
|
+
const coverage = /* @__PURE__ */ new Map();
|
|
2562
|
+
if (!symbols.length) return coverage;
|
|
2563
|
+
await ensureBinanceMarketSchema();
|
|
2564
|
+
const pool = getPool();
|
|
2565
|
+
const res = await pool.query(
|
|
2983
2566
|
`
|
|
2984
2567
|
SELECT
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
FROM
|
|
2990
|
-
WHERE
|
|
2991
|
-
AND
|
|
2992
|
-
AND
|
|
2568
|
+
symbol,
|
|
2569
|
+
extract(epoch from MIN(ts))*1000 AS first_ms,
|
|
2570
|
+
extract(epoch from MAX(ts))*1000 AS last_ms,
|
|
2571
|
+
COUNT(*)::int AS rows
|
|
2572
|
+
FROM market_reference_asset_context
|
|
2573
|
+
WHERE source = $1
|
|
2574
|
+
AND symbol = ANY($2)
|
|
2575
|
+
AND interval = $3
|
|
2993
2576
|
AND ts >= to_timestamp($4/1000.0)
|
|
2994
|
-
AND ts
|
|
2995
|
-
|
|
2577
|
+
AND ts <= to_timestamp($5/1000.0)
|
|
2578
|
+
GROUP BY symbol
|
|
2996
2579
|
`,
|
|
2997
|
-
[
|
|
2998
|
-
params.universeFingerprint,
|
|
2999
|
-
params.whaleRegistryFingerprint,
|
|
3000
|
-
import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION,
|
|
3001
|
-
params.fromMs,
|
|
3002
|
-
params.toMs
|
|
3003
|
-
],
|
|
3004
|
-
params
|
|
2580
|
+
[params.source, symbols, params.interval, params.startMs, params.endMs]
|
|
3005
2581
|
);
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
2582
|
+
for (const row of res.rows) {
|
|
2583
|
+
const firstMs = Number(row.first_ms);
|
|
2584
|
+
const lastMs = Number(row.last_ms);
|
|
2585
|
+
const rows = Number(row.rows);
|
|
2586
|
+
if (Number.isFinite(firstMs) && Number.isFinite(lastMs) && rows > 0) {
|
|
2587
|
+
coverage.set(row.symbol.toUpperCase(), { firstMs, lastMs, rows });
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
return coverage;
|
|
3012
2591
|
}
|
|
3013
|
-
async function
|
|
3014
|
-
|
|
3015
|
-
const
|
|
2592
|
+
async function getLatestMarketReferenceAssetContexts(params) {
|
|
2593
|
+
const source = params.source ?? "coinmarketcap_reference_asset";
|
|
2594
|
+
const interval = params.interval ?? "1d";
|
|
2595
|
+
const symbols = [
|
|
2596
|
+
...new Set(
|
|
2597
|
+
params.symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean)
|
|
2598
|
+
)
|
|
2599
|
+
];
|
|
2600
|
+
const rows = /* @__PURE__ */ new Map();
|
|
2601
|
+
if (!symbols.length) return rows;
|
|
2602
|
+
await prepareMarketContextSchemaForRead("coinmarketcap");
|
|
2603
|
+
const res = await queryMarketContext(
|
|
3016
2604
|
`
|
|
3017
|
-
SELECT
|
|
2605
|
+
SELECT DISTINCT ON (symbol)
|
|
2606
|
+
source,
|
|
2607
|
+
symbol,
|
|
2608
|
+
cmc_id AS "cmcId",
|
|
2609
|
+
interval,
|
|
3018
2610
|
ts,
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
AND interval = '1m'
|
|
3036
|
-
AND universe_fingerprint = $2
|
|
3037
|
-
AND whale_registry_fingerprint = $3
|
|
3038
|
-
AND ts >= to_timestamp($4/1000.0)
|
|
3039
|
-
AND ts < to_timestamp($5/1000.0)
|
|
3040
|
-
ORDER BY ts
|
|
2611
|
+
open_usd AS "openUsd",
|
|
2612
|
+
high_usd AS "highUsd",
|
|
2613
|
+
low_usd AS "lowUsd",
|
|
2614
|
+
close_usd AS "closeUsd",
|
|
2615
|
+
volume_usd AS "volumeUsd",
|
|
2616
|
+
market_cap_usd AS "marketCapUsd"
|
|
2617
|
+
FROM market_reference_asset_context
|
|
2618
|
+
WHERE source = $1
|
|
2619
|
+
AND symbol = ANY($2)
|
|
2620
|
+
AND interval = $3
|
|
2621
|
+
AND ts + CASE interval
|
|
2622
|
+
WHEN '1d' THEN interval '1 day'
|
|
2623
|
+
WHEN '1h' THEN interval '1 hour'
|
|
2624
|
+
ELSE interval '0 seconds'
|
|
2625
|
+
END <= to_timestamp($4/1000.0)
|
|
2626
|
+
ORDER BY symbol ASC, ts DESC
|
|
3041
2627
|
`,
|
|
3042
|
-
[
|
|
3043
|
-
params.symbol,
|
|
3044
|
-
params.universeFingerprint,
|
|
3045
|
-
params.whaleRegistryFingerprint,
|
|
3046
|
-
params.fromMs,
|
|
3047
|
-
params.toMs
|
|
3048
|
-
],
|
|
2628
|
+
[source, symbols, interval, params.atMs],
|
|
3049
2629
|
params
|
|
3050
2630
|
);
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
shortEntryWhaleAddresses: Array.isArray(row.short_entry_whale_addresses) ? row.short_entry_whale_addresses.map(String) : [],
|
|
3061
|
-
longExitWhaleAddresses: Array.isArray(row.long_exit_whale_addresses) ? row.long_exit_whale_addresses.map(String) : [],
|
|
3062
|
-
shortExitWhaleAddresses: Array.isArray(row.short_exit_whale_addresses) ? row.short_exit_whale_addresses.map(String) : [],
|
|
3063
|
-
longEntryNotionalUsd: Number(row.long_entry_notional_usd) || 0,
|
|
3064
|
-
shortEntryNotionalUsd: Number(row.short_entry_notional_usd) || 0,
|
|
3065
|
-
longExitNotionalUsd: Number(row.long_exit_notional_usd) || 0,
|
|
3066
|
-
shortExitNotionalUsd: Number(row.short_exit_notional_usd) || 0
|
|
3067
|
-
}));
|
|
2631
|
+
for (const row of res.rows) {
|
|
2632
|
+
const ageMs = toMarketFeatureAge(row.ts, params.atMs);
|
|
2633
|
+
rows.set(row.symbol.toUpperCase(), {
|
|
2634
|
+
...row,
|
|
2635
|
+
ageMs,
|
|
2636
|
+
stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
return rows;
|
|
3068
2640
|
}
|
|
3069
|
-
async function
|
|
3070
|
-
await prepareMarketContextSchemaForRead("
|
|
3071
|
-
const
|
|
3072
|
-
const
|
|
2641
|
+
async function getLatestMarketCmcExchangeLiquidityContext(params) {
|
|
2642
|
+
await prepareMarketContextSchemaForRead("coinmarketcap");
|
|
2643
|
+
const source = params.source ?? "coinmarketcap_exchange_liquidity";
|
|
2644
|
+
const interval = params.interval ?? "1d";
|
|
3073
2645
|
const res = await queryMarketContext(
|
|
3074
2646
|
`
|
|
3075
|
-
WITH coverage_rows AS (
|
|
3076
|
-
SELECT *
|
|
3077
|
-
FROM hyperliquid_whale_coverage_1m
|
|
3078
|
-
WHERE universe_fingerprint = $2
|
|
3079
|
-
AND whale_registry_fingerprint = $3
|
|
3080
|
-
AND data_model_version = $6
|
|
3081
|
-
AND ts >= to_timestamp(
|
|
3082
|
-
($4::double precision - $5::double precision) / 1000.0
|
|
3083
|
-
)
|
|
3084
|
-
AND ts < to_timestamp($4/1000.0)
|
|
3085
|
-
), coverage_summary AS (
|
|
3086
|
-
SELECT
|
|
3087
|
-
COUNT(*)::int AS coverage_buckets,
|
|
3088
|
-
MAX(ts) AS coverage_as_of_ts,
|
|
3089
|
-
MIN(covered_whales)::int AS covered_whales,
|
|
3090
|
-
MAX(expected_whales)::int AS expected_whales,
|
|
3091
|
-
MIN(coverage_pct) AS coverage_pct
|
|
3092
|
-
FROM coverage_rows
|
|
3093
|
-
), window_rows AS (
|
|
3094
|
-
SELECT *
|
|
3095
|
-
FROM hyperliquid_whale_flow
|
|
3096
|
-
WHERE symbol = $1
|
|
3097
|
-
AND interval = '1m'
|
|
3098
|
-
AND universe_fingerprint = $2
|
|
3099
|
-
AND whale_registry_fingerprint = $3
|
|
3100
|
-
AND ts >= to_timestamp(
|
|
3101
|
-
($4::double precision - $5::double precision) / 1000.0
|
|
3102
|
-
)
|
|
3103
|
-
AND ts < to_timestamp($4/1000.0)
|
|
3104
|
-
), unique_addresses AS (
|
|
3105
|
-
SELECT COUNT(DISTINCT address)::int AS unique_whales
|
|
3106
|
-
FROM window_rows
|
|
3107
|
-
CROSS JOIN LATERAL UNNEST(whale_addresses) AS expanded(address)
|
|
3108
|
-
), directional_counts AS (
|
|
3109
|
-
SELECT
|
|
3110
|
-
(
|
|
3111
|
-
SELECT COUNT(DISTINCT address)::int
|
|
3112
|
-
FROM window_rows
|
|
3113
|
-
CROSS JOIN LATERAL UNNEST(long_entry_whale_addresses) AS expanded(address)
|
|
3114
|
-
) AS long_entry_whales,
|
|
3115
|
-
(
|
|
3116
|
-
SELECT COUNT(DISTINCT address)::int
|
|
3117
|
-
FROM window_rows
|
|
3118
|
-
CROSS JOIN LATERAL UNNEST(short_entry_whale_addresses) AS expanded(address)
|
|
3119
|
-
) AS short_entry_whales,
|
|
3120
|
-
(
|
|
3121
|
-
SELECT COUNT(DISTINCT address)::int
|
|
3122
|
-
FROM window_rows
|
|
3123
|
-
CROSS JOIN LATERAL UNNEST(long_exit_whale_addresses) AS expanded(address)
|
|
3124
|
-
) AS long_exit_whales,
|
|
3125
|
-
(
|
|
3126
|
-
SELECT COUNT(DISTINCT address)::int
|
|
3127
|
-
FROM window_rows
|
|
3128
|
-
CROSS JOIN LATERAL UNNEST(short_exit_whale_addresses) AS expanded(address)
|
|
3129
|
-
) AS short_exit_whales
|
|
3130
|
-
)
|
|
3131
2647
|
SELECT
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
CASE
|
|
3145
|
-
WHEN
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
AS position_aware_whale_sides,
|
|
3152
|
-
CASE
|
|
3153
|
-
WHEN COALESCE((SELECT SUM(whale_sides) FROM window_rows), 0) > 0
|
|
3154
|
-
THEN COALESCE((SELECT SUM(position_aware_whale_sides) FROM window_rows), 0)::double precision /
|
|
3155
|
-
(SELECT SUM(whale_sides) FROM window_rows)
|
|
3156
|
-
ELSE 0
|
|
3157
|
-
END AS position_aware_pct,
|
|
3158
|
-
COALESCE((SELECT long_entry_whales FROM directional_counts), 0)::int AS long_entry_whales,
|
|
3159
|
-
COALESCE((SELECT short_entry_whales FROM directional_counts), 0)::int AS short_entry_whales,
|
|
3160
|
-
COALESCE((SELECT long_exit_whales FROM directional_counts), 0)::int AS long_exit_whales,
|
|
3161
|
-
COALESCE((SELECT short_exit_whales FROM directional_counts), 0)::int AS short_exit_whales,
|
|
3162
|
-
COALESCE((SELECT SUM(long_entry_notional_usd) FROM window_rows), 0)
|
|
3163
|
-
AS long_entry_notional_usd,
|
|
3164
|
-
COALESCE((SELECT SUM(short_entry_notional_usd) FROM window_rows), 0)
|
|
3165
|
-
AS short_entry_notional_usd,
|
|
3166
|
-
COALESCE((SELECT SUM(long_exit_notional_usd) FROM window_rows), 0)
|
|
3167
|
-
AS long_exit_notional_usd,
|
|
3168
|
-
COALESCE((SELECT SUM(short_exit_notional_usd) FROM window_rows), 0)
|
|
3169
|
-
AS short_exit_notional_usd,
|
|
3170
|
-
COALESCE((SELECT SUM(entry_net_notional_usd) FROM window_rows), 0)
|
|
3171
|
-
AS entry_net_notional_usd,
|
|
3172
|
-
CASE
|
|
3173
|
-
WHEN COALESCE((SELECT SUM(long_entry_notional_usd + short_entry_notional_usd) FROM window_rows), 0) > 0
|
|
3174
|
-
THEN (SELECT SUM(long_entry_notional_usd) FROM window_rows) /
|
|
3175
|
-
(SELECT SUM(long_entry_notional_usd + short_entry_notional_usd) FROM window_rows)
|
|
3176
|
-
ELSE NULL
|
|
3177
|
-
END AS entry_long_share_pct,
|
|
3178
|
-
(SELECT MAX(source) FROM window_rows) AS source
|
|
3179
|
-
FROM coverage_summary
|
|
2648
|
+
source,
|
|
2649
|
+
interval,
|
|
2650
|
+
ts,
|
|
2651
|
+
exchanges_count::int AS "exchangesCount",
|
|
2652
|
+
total_volume_usd AS "totalVolumeUsd",
|
|
2653
|
+
binance_volume_usd AS "binanceVolumeUsd",
|
|
2654
|
+
binance_volume_share AS "binanceVolumeShare",
|
|
2655
|
+
top_exchange_volume_share AS "topExchangeVolumeShare",
|
|
2656
|
+
liquidity_regime AS "liquidityRegime"
|
|
2657
|
+
FROM market_cmc_exchange_liquidity_context
|
|
2658
|
+
WHERE source = $1
|
|
2659
|
+
AND interval = $2
|
|
2660
|
+
AND ts + CASE interval
|
|
2661
|
+
WHEN '1d' THEN interval '1 day'
|
|
2662
|
+
WHEN '1h' THEN interval '1 hour'
|
|
2663
|
+
ELSE interval '0 seconds'
|
|
2664
|
+
END <= to_timestamp($3/1000.0)
|
|
2665
|
+
ORDER BY ts DESC
|
|
2666
|
+
LIMIT 1
|
|
3180
2667
|
`,
|
|
3181
|
-
[
|
|
3182
|
-
params.symbol,
|
|
3183
|
-
params.universeFingerprint,
|
|
3184
|
-
params.whaleRegistryFingerprint,
|
|
3185
|
-
params.decisionTimeMs,
|
|
3186
|
-
intervalMs,
|
|
3187
|
-
import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
3188
|
-
],
|
|
2668
|
+
[source, interval, params.atMs],
|
|
3189
2669
|
params
|
|
3190
2670
|
);
|
|
3191
2671
|
const row = res.rows[0];
|
|
3192
|
-
if (!row
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
2672
|
+
if (!row) return null;
|
|
2673
|
+
const previousRes = await queryMarketContext(
|
|
2674
|
+
`
|
|
2675
|
+
SELECT total_volume_usd AS "totalVolumeUsd"
|
|
2676
|
+
FROM market_cmc_exchange_liquidity_context
|
|
2677
|
+
WHERE source = $1
|
|
2678
|
+
AND interval = $2
|
|
2679
|
+
AND ts <= $3::timestamptz - interval '24 hours'
|
|
2680
|
+
ORDER BY ts DESC
|
|
2681
|
+
LIMIT 1
|
|
2682
|
+
`,
|
|
2683
|
+
[source, interval, row.ts],
|
|
2684
|
+
params
|
|
2685
|
+
);
|
|
2686
|
+
const currentTotal = row.totalVolumeUsd == null ? null : Number(row.totalVolumeUsd);
|
|
2687
|
+
const previousTotal = previousRes.rows[0]?.totalVolumeUsd == null ? null : Number(previousRes.rows[0].totalVolumeUsd);
|
|
2688
|
+
const ageMs = toMarketFeatureAge(row.ts, params.atMs);
|
|
3197
2689
|
return {
|
|
3198
|
-
|
|
3199
|
-
interval: params.interval,
|
|
3200
|
-
asOfTs,
|
|
3201
|
-
windowEndTs: new Date(params.decisionTimeMs),
|
|
3202
|
-
trades: Number(row.trades) || 0,
|
|
3203
|
-
whaleSides: Number(row.whale_sides) || 0,
|
|
3204
|
-
uniqueWhales: Number(row.unique_whales) || 0,
|
|
3205
|
-
coveredWhales: Number(row.covered_whales) || 0,
|
|
3206
|
-
expectedWhales: Number(row.expected_whales) || 0,
|
|
3207
|
-
coveragePct: Number(row.coverage_pct) || 0,
|
|
3208
|
-
buyNotionalUsd: Number(row.buy_notional_usd) || 0,
|
|
3209
|
-
sellNotionalUsd: Number(row.sell_notional_usd) || 0,
|
|
3210
|
-
netNotionalUsd: Number(row.net_notional_usd) || 0,
|
|
3211
|
-
buySharePct: row.buy_share_pct == null ? null : Number(row.buy_share_pct) || 0,
|
|
3212
|
-
positionAwareWhaleSides: Number(row.position_aware_whale_sides) || 0,
|
|
3213
|
-
positionAwarePct: Number(row.position_aware_pct) || 0,
|
|
3214
|
-
longEntryWhales: Number(row.long_entry_whales) || 0,
|
|
3215
|
-
shortEntryWhales: Number(row.short_entry_whales) || 0,
|
|
3216
|
-
longExitWhales: Number(row.long_exit_whales) || 0,
|
|
3217
|
-
shortExitWhales: Number(row.short_exit_whales) || 0,
|
|
3218
|
-
longEntryNotionalUsd: Number(row.long_entry_notional_usd) || 0,
|
|
3219
|
-
shortEntryNotionalUsd: Number(row.short_entry_notional_usd) || 0,
|
|
3220
|
-
longExitNotionalUsd: Number(row.long_exit_notional_usd) || 0,
|
|
3221
|
-
shortExitNotionalUsd: Number(row.short_exit_notional_usd) || 0,
|
|
3222
|
-
entryNetNotionalUsd: Number(row.entry_net_notional_usd) || 0,
|
|
3223
|
-
entryLongSharePct: row.entry_long_share_pct == null ? null : Number(row.entry_long_share_pct) || 0,
|
|
3224
|
-
universeFingerprint: params.universeFingerprint,
|
|
3225
|
-
whaleRegistryFingerprint: params.whaleRegistryFingerprint,
|
|
3226
|
-
source: row.source == null ? null : String(row.source),
|
|
2690
|
+
...row,
|
|
3227
2691
|
ageMs,
|
|
3228
|
-
stale: ageMs
|
|
2692
|
+
stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs,
|
|
2693
|
+
totalVolumeChange24hPct: currentTotal != null && previousTotal != null && previousTotal > 0 ? (currentTotal - previousTotal) / previousTotal : null
|
|
3229
2694
|
};
|
|
3230
2695
|
}
|
|
3231
|
-
async function
|
|
3232
|
-
|
|
3233
|
-
const
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
FROM hyperliquid_whale_coverage_1m
|
|
3245
|
-
WHERE universe_fingerprint = $1
|
|
3246
|
-
AND whale_registry_fingerprint = $2
|
|
3247
|
-
AND data_model_version = $5
|
|
3248
|
-
AND ts >= to_timestamp($3/1000.0)
|
|
3249
|
-
AND ts < to_timestamp($4/1000.0)
|
|
3250
|
-
`,
|
|
3251
|
-
[
|
|
3252
|
-
params.universeFingerprint,
|
|
3253
|
-
params.whaleRegistryFingerprint,
|
|
3254
|
-
params.fromMs,
|
|
3255
|
-
params.toMs,
|
|
3256
|
-
import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
3257
|
-
]
|
|
3258
|
-
);
|
|
3259
|
-
return Number(result.rows[0]?.buckets) === expectedBuckets && Number(result.rows[0]?.complete_buckets) === expectedBuckets;
|
|
3260
|
-
}
|
|
3261
|
-
async function getLatestMarketBreadth(params) {
|
|
3262
|
-
await prepareMarketContextSchemaForRead("binance");
|
|
2696
|
+
async function getLatestMarketCmcIndexContexts(params) {
|
|
2697
|
+
const source = params.source ?? "coinmarketcap_index";
|
|
2698
|
+
const interval = params.interval ?? "1d";
|
|
2699
|
+
const indexSlugs = [
|
|
2700
|
+
...new Set(
|
|
2701
|
+
params.indexSlugs.map((slug) => slug.trim().toLowerCase()).filter(
|
|
2702
|
+
(slug) => ["cmc100", "cmc20"].includes(slug)
|
|
2703
|
+
)
|
|
2704
|
+
)
|
|
2705
|
+
];
|
|
2706
|
+
const rows = /* @__PURE__ */ new Map();
|
|
2707
|
+
if (!indexSlugs.length) return rows;
|
|
2708
|
+
await prepareMarketContextSchemaForRead("coinmarketcap");
|
|
3263
2709
|
const res = await queryMarketContext(
|
|
3264
2710
|
`
|
|
3265
|
-
SELECT
|
|
3266
|
-
|
|
2711
|
+
SELECT DISTINCT ON (index_slug)
|
|
2712
|
+
source,
|
|
2713
|
+
index_slug AS "indexSlug",
|
|
3267
2714
|
interval,
|
|
3268
2715
|
ts,
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
alt_basket_return_24h AS "altBasketReturn24h",
|
|
3285
|
-
btc_vs_alt_return_1h AS "btcVsAltReturn1h",
|
|
3286
|
-
btc_vs_alt_return_4h AS "btcVsAltReturn4h",
|
|
3287
|
-
btc_vs_alt_return_24h AS "btcVsAltReturn24h",
|
|
3288
|
-
btc_turnover_share_1h AS "btcTurnoverShare1h",
|
|
3289
|
-
btc_turnover_share_24h AS "btcTurnoverShare24h",
|
|
3290
|
-
btc_turnover_share_change_24h AS "btcTurnoverShareChange24h",
|
|
3291
|
-
alt_vol_to_btc_vol_24h AS "altVolToBtcVol24h",
|
|
3292
|
-
alt_dispersion_24h AS "altDispersion24h",
|
|
3293
|
-
btc_alt_regime AS "btcAltRegime",
|
|
3294
|
-
source
|
|
3295
|
-
FROM market_breadth
|
|
3296
|
-
WHERE universe = $1
|
|
3297
|
-
AND interval = $2
|
|
3298
|
-
AND ts <= to_timestamp($3/1000.0)
|
|
3299
|
-
ORDER BY ts DESC
|
|
3300
|
-
LIMIT 1
|
|
2716
|
+
value,
|
|
2717
|
+
constituents_count::int AS "constituentsCount",
|
|
2718
|
+
top_constituent_symbol AS "topConstituentSymbol",
|
|
2719
|
+
top_constituent_weight_pct AS "topConstituentWeightPct",
|
|
2720
|
+
constituents
|
|
2721
|
+
FROM market_cmc_index_context
|
|
2722
|
+
WHERE source = $1
|
|
2723
|
+
AND index_slug = ANY($2)
|
|
2724
|
+
AND interval = $3
|
|
2725
|
+
AND ts + CASE interval
|
|
2726
|
+
WHEN '1d' THEN interval '1 day'
|
|
2727
|
+
WHEN '1h' THEN interval '1 hour'
|
|
2728
|
+
ELSE interval '0 seconds'
|
|
2729
|
+
END <= to_timestamp($4/1000.0)
|
|
2730
|
+
ORDER BY index_slug ASC, ts DESC
|
|
3301
2731
|
`,
|
|
3302
|
-
[
|
|
2732
|
+
[source, indexSlugs, interval, params.atMs],
|
|
3303
2733
|
params
|
|
3304
2734
|
);
|
|
3305
|
-
const row
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
2735
|
+
for (const row of res.rows) {
|
|
2736
|
+
const previousRes = await queryMarketContext(
|
|
2737
|
+
`
|
|
2738
|
+
SELECT value
|
|
2739
|
+
FROM market_cmc_index_context
|
|
2740
|
+
WHERE source = $1
|
|
2741
|
+
AND index_slug = $2
|
|
2742
|
+
AND interval = $3
|
|
2743
|
+
AND ts <= $4::timestamptz - interval '24 hours'
|
|
2744
|
+
ORDER BY ts DESC
|
|
2745
|
+
LIMIT 1
|
|
2746
|
+
`,
|
|
2747
|
+
[source, row.indexSlug, interval, row.ts],
|
|
2748
|
+
params
|
|
2749
|
+
);
|
|
2750
|
+
const currentValue = row.value == null ? null : Number(row.value);
|
|
2751
|
+
const previousValue = previousRes.rows[0]?.value == null ? null : Number(previousRes.rows[0].value);
|
|
2752
|
+
const ageMs = toMarketFeatureAge(row.ts, params.atMs);
|
|
2753
|
+
rows.set(row.indexSlug, {
|
|
2754
|
+
...row,
|
|
2755
|
+
ageMs,
|
|
2756
|
+
stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs,
|
|
2757
|
+
valueChange24hPct: currentValue != null && previousValue != null && previousValue > 0 ? (currentValue - previousValue) / previousValue : null
|
|
2758
|
+
});
|
|
2759
|
+
}
|
|
2760
|
+
return rows;
|
|
3313
2761
|
}
|
|
3314
|
-
async function
|
|
2762
|
+
async function getLatestMarketCmcFearGreedContext(params) {
|
|
3315
2763
|
await prepareMarketContextSchemaForRead("coinmarketcap");
|
|
3316
|
-
const source = params.source ?? "
|
|
2764
|
+
const source = params.source ?? "coinmarketcap_fear_greed";
|
|
2765
|
+
const interval = params.interval ?? "1d";
|
|
3317
2766
|
const res = await queryMarketContext(
|
|
3318
2767
|
`
|
|
3319
2768
|
SELECT
|
|
3320
2769
|
source,
|
|
2770
|
+
interval,
|
|
3321
2771
|
ts,
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
markets::int AS markets,
|
|
3327
|
-
total_market_cap_usd AS "totalMarketCapUsd",
|
|
3328
|
-
total_volume_usd AS "totalVolumeUsd",
|
|
3329
|
-
total_volume_reported_usd AS "totalVolumeReportedUsd",
|
|
3330
|
-
btc_dominance_pct AS "btcDominancePct",
|
|
3331
|
-
eth_dominance_pct AS "ethDominancePct",
|
|
3332
|
-
alt_market_cap_usd AS "altMarketCapUsd",
|
|
3333
|
-
alt_volume_usd AS "altVolumeUsd",
|
|
3334
|
-
alt_volume_reported_usd AS "altVolumeReportedUsd",
|
|
3335
|
-
btc_to_alt_market_cap_ratio AS "btcToAltMarketCapRatio",
|
|
3336
|
-
market_cap_change_pct_24h_usd AS "marketCapChangePct24hUsd"
|
|
3337
|
-
FROM market_global_context
|
|
2772
|
+
value::int AS value,
|
|
2773
|
+
classification,
|
|
2774
|
+
sentiment_regime AS "sentimentRegime"
|
|
2775
|
+
FROM market_cmc_fear_greed_context
|
|
3338
2776
|
WHERE source = $1
|
|
3339
|
-
AND
|
|
3340
|
-
|
|
2777
|
+
AND interval = $2
|
|
2778
|
+
AND ts + CASE interval
|
|
2779
|
+
WHEN '1d' THEN interval '1 day'
|
|
2780
|
+
WHEN '1h' THEN interval '1 hour'
|
|
3341
2781
|
ELSE interval '0 seconds'
|
|
3342
|
-
END <= to_timestamp($
|
|
2782
|
+
END <= to_timestamp($3/1000.0)
|
|
3343
2783
|
ORDER BY ts DESC
|
|
3344
2784
|
LIMIT 1
|
|
3345
2785
|
`,
|
|
3346
|
-
[source, params.atMs],
|
|
2786
|
+
[source, interval, params.atMs],
|
|
3347
2787
|
params
|
|
3348
2788
|
);
|
|
3349
2789
|
const row = res.rows[0];
|
|
@@ -3351,39 +2791,43 @@ async function getLatestMarketGlobalContext(params) {
|
|
|
3351
2791
|
const previousRes = await queryMarketContext(
|
|
3352
2792
|
`
|
|
3353
2793
|
SELECT
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
alt_volume_usd AS "altVolumeUsd"
|
|
3358
|
-
FROM market_global_context
|
|
2794
|
+
value::int AS value,
|
|
2795
|
+
'24h' AS bucket
|
|
2796
|
+
FROM market_cmc_fear_greed_context
|
|
3359
2797
|
WHERE source = $1
|
|
3360
|
-
AND
|
|
2798
|
+
AND interval = $2
|
|
2799
|
+
AND ts <= $3::timestamptz - interval '24 hours'
|
|
3361
2800
|
ORDER BY ts DESC
|
|
3362
2801
|
LIMIT 1
|
|
3363
2802
|
`,
|
|
3364
|
-
[source, row.ts],
|
|
2803
|
+
[source, interval, row.ts],
|
|
3365
2804
|
params
|
|
3366
2805
|
);
|
|
3367
|
-
const
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
2806
|
+
const previous7dRes = await queryMarketContext(
|
|
2807
|
+
`
|
|
2808
|
+
SELECT value::int AS value
|
|
2809
|
+
FROM market_cmc_fear_greed_context
|
|
2810
|
+
WHERE source = $1
|
|
2811
|
+
AND interval = $2
|
|
2812
|
+
AND ts <= $3::timestamptz - interval '7 days'
|
|
2813
|
+
ORDER BY ts DESC
|
|
2814
|
+
LIMIT 1
|
|
2815
|
+
`,
|
|
2816
|
+
[source, interval, row.ts],
|
|
2817
|
+
params
|
|
2818
|
+
);
|
|
2819
|
+
const previousValue = previousRes.rows[0]?.value == null ? null : Number(previousRes.rows[0].value);
|
|
2820
|
+
const previous7dValue = previous7dRes.rows[0]?.value == null ? null : Number(previous7dRes.rows[0].value);
|
|
3375
2821
|
const ageMs = toMarketFeatureAge(row.ts, params.atMs);
|
|
3376
2822
|
return {
|
|
3377
2823
|
...row,
|
|
3378
2824
|
ageMs,
|
|
3379
2825
|
stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs,
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
altMarketCapChange24hPct: currentAltMarketCap != null && previousAltMarketCap != null && previousAltMarketCap > 0 ? (currentAltMarketCap - previousAltMarketCap) / previousAltMarketCap : null,
|
|
3383
|
-
altVolumeChange24hPct: currentAltVolume != null && previousAltVolume != null && previousAltVolume > 0 ? (currentAltVolume - previousAltVolume) / previousAltVolume : null
|
|
2826
|
+
valueChange24h: previousValue == null ? null : row.value - previousValue,
|
|
2827
|
+
valueChange7d: previous7dValue == null ? null : row.value - previous7dValue
|
|
3384
2828
|
};
|
|
3385
2829
|
}
|
|
3386
|
-
async function
|
|
2830
|
+
async function getMarketCmcFearGreedContextCoverage(params) {
|
|
3387
2831
|
await ensureBinanceMarketSchema();
|
|
3388
2832
|
const pool = getPool();
|
|
3389
2833
|
const res = await pool.query(
|
|
@@ -3392,166 +2836,48 @@ async function getMarketGlobalContextCoverage(params) {
|
|
|
3392
2836
|
extract(epoch from MIN(ts))*1000 AS first_ms,
|
|
3393
2837
|
extract(epoch from MAX(ts))*1000 AS last_ms,
|
|
3394
2838
|
COUNT(*)::int AS rows
|
|
3395
|
-
FROM
|
|
2839
|
+
FROM market_cmc_fear_greed_context
|
|
3396
2840
|
WHERE source = $1
|
|
3397
|
-
AND
|
|
3398
|
-
AND ts
|
|
2841
|
+
AND interval = $2
|
|
2842
|
+
AND ts >= to_timestamp($3/1000.0)
|
|
2843
|
+
AND ts <= to_timestamp($4/1000.0)
|
|
3399
2844
|
`,
|
|
3400
|
-
[params.source, params.startMs, params.endMs]
|
|
2845
|
+
[params.source, params.interval, params.startMs, params.endMs]
|
|
3401
2846
|
);
|
|
3402
|
-
const
|
|
3403
|
-
const
|
|
3404
|
-
const
|
|
3405
|
-
const lastMs = Number(row?.last_ms);
|
|
2847
|
+
const rows = Number(res.rows[0]?.rows ?? 0);
|
|
2848
|
+
const firstMs = Number(res.rows[0]?.first_ms);
|
|
2849
|
+
const lastMs = Number(res.rows[0]?.last_ms);
|
|
3406
2850
|
if (!rows || !Number.isFinite(firstMs) || !Number.isFinite(lastMs)) {
|
|
3407
2851
|
return null;
|
|
3408
2852
|
}
|
|
3409
2853
|
return { firstMs, lastMs, rows };
|
|
3410
2854
|
}
|
|
3411
|
-
async function
|
|
3412
|
-
const symbols = [
|
|
3413
|
-
...new Set(
|
|
3414
|
-
params.symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean)
|
|
3415
|
-
)
|
|
3416
|
-
];
|
|
3417
|
-
const coverage = /* @__PURE__ */ new Map();
|
|
3418
|
-
if (!symbols.length) return coverage;
|
|
2855
|
+
async function getMarketCmcExchangeLiquidityContextCoverage(params) {
|
|
3419
2856
|
await ensureBinanceMarketSchema();
|
|
3420
2857
|
const pool = getPool();
|
|
3421
2858
|
const res = await pool.query(
|
|
3422
2859
|
`
|
|
3423
2860
|
SELECT
|
|
3424
|
-
symbol,
|
|
3425
2861
|
extract(epoch from MIN(ts))*1000 AS first_ms,
|
|
3426
2862
|
extract(epoch from MAX(ts))*1000 AS last_ms,
|
|
3427
2863
|
COUNT(*)::int AS rows
|
|
3428
|
-
FROM
|
|
2864
|
+
FROM market_cmc_exchange_liquidity_context
|
|
3429
2865
|
WHERE source = $1
|
|
3430
|
-
AND
|
|
3431
|
-
AND
|
|
3432
|
-
AND ts
|
|
3433
|
-
AND ts <= to_timestamp($5/1000.0)
|
|
3434
|
-
GROUP BY symbol
|
|
2866
|
+
AND interval = $2
|
|
2867
|
+
AND ts >= to_timestamp($3/1000.0)
|
|
2868
|
+
AND ts <= to_timestamp($4/1000.0)
|
|
3435
2869
|
`,
|
|
3436
|
-
[params.source,
|
|
2870
|
+
[params.source, params.interval, params.startMs, params.endMs]
|
|
3437
2871
|
);
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
coverage.set(row.symbol.toUpperCase(), { firstMs, lastMs, rows });
|
|
3444
|
-
}
|
|
2872
|
+
const rows = Number(res.rows[0]?.rows ?? 0);
|
|
2873
|
+
const firstMs = Number(res.rows[0]?.first_ms);
|
|
2874
|
+
const lastMs = Number(res.rows[0]?.last_ms);
|
|
2875
|
+
if (!rows || !Number.isFinite(firstMs) || !Number.isFinite(lastMs)) {
|
|
2876
|
+
return null;
|
|
3445
2877
|
}
|
|
3446
|
-
return
|
|
2878
|
+
return { firstMs, lastMs, rows };
|
|
3447
2879
|
}
|
|
3448
|
-
async function
|
|
3449
|
-
const source = params.source ?? "coinmarketcap_reference_asset";
|
|
3450
|
-
const interval = params.interval ?? "1d";
|
|
3451
|
-
const symbols = [
|
|
3452
|
-
...new Set(
|
|
3453
|
-
params.symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean)
|
|
3454
|
-
)
|
|
3455
|
-
];
|
|
3456
|
-
const rows = /* @__PURE__ */ new Map();
|
|
3457
|
-
if (!symbols.length) return rows;
|
|
3458
|
-
await prepareMarketContextSchemaForRead("coinmarketcap");
|
|
3459
|
-
const res = await queryMarketContext(
|
|
3460
|
-
`
|
|
3461
|
-
SELECT DISTINCT ON (symbol)
|
|
3462
|
-
source,
|
|
3463
|
-
symbol,
|
|
3464
|
-
cmc_id AS "cmcId",
|
|
3465
|
-
interval,
|
|
3466
|
-
ts,
|
|
3467
|
-
open_usd AS "openUsd",
|
|
3468
|
-
high_usd AS "highUsd",
|
|
3469
|
-
low_usd AS "lowUsd",
|
|
3470
|
-
close_usd AS "closeUsd",
|
|
3471
|
-
volume_usd AS "volumeUsd",
|
|
3472
|
-
market_cap_usd AS "marketCapUsd"
|
|
3473
|
-
FROM market_reference_asset_context
|
|
3474
|
-
WHERE source = $1
|
|
3475
|
-
AND symbol = ANY($2)
|
|
3476
|
-
AND interval = $3
|
|
3477
|
-
AND ts + CASE interval
|
|
3478
|
-
WHEN '1d' THEN interval '1 day'
|
|
3479
|
-
WHEN '1h' THEN interval '1 hour'
|
|
3480
|
-
ELSE interval '0 seconds'
|
|
3481
|
-
END <= to_timestamp($4/1000.0)
|
|
3482
|
-
ORDER BY symbol ASC, ts DESC
|
|
3483
|
-
`,
|
|
3484
|
-
[source, symbols, interval, params.atMs],
|
|
3485
|
-
params
|
|
3486
|
-
);
|
|
3487
|
-
for (const row of res.rows) {
|
|
3488
|
-
const ageMs = toMarketFeatureAge(row.ts, params.atMs);
|
|
3489
|
-
rows.set(row.symbol.toUpperCase(), {
|
|
3490
|
-
...row,
|
|
3491
|
-
ageMs,
|
|
3492
|
-
stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs
|
|
3493
|
-
});
|
|
3494
|
-
}
|
|
3495
|
-
return rows;
|
|
3496
|
-
}
|
|
3497
|
-
async function getLatestMarketCmcExchangeLiquidityContext(params) {
|
|
3498
|
-
await prepareMarketContextSchemaForRead("coinmarketcap");
|
|
3499
|
-
const source = params.source ?? "coinmarketcap_exchange_liquidity";
|
|
3500
|
-
const interval = params.interval ?? "1d";
|
|
3501
|
-
const res = await queryMarketContext(
|
|
3502
|
-
`
|
|
3503
|
-
SELECT
|
|
3504
|
-
source,
|
|
3505
|
-
interval,
|
|
3506
|
-
ts,
|
|
3507
|
-
exchanges_count::int AS "exchangesCount",
|
|
3508
|
-
total_volume_usd AS "totalVolumeUsd",
|
|
3509
|
-
binance_volume_usd AS "binanceVolumeUsd",
|
|
3510
|
-
binance_volume_share AS "binanceVolumeShare",
|
|
3511
|
-
top_exchange_volume_share AS "topExchangeVolumeShare",
|
|
3512
|
-
liquidity_regime AS "liquidityRegime"
|
|
3513
|
-
FROM market_cmc_exchange_liquidity_context
|
|
3514
|
-
WHERE source = $1
|
|
3515
|
-
AND interval = $2
|
|
3516
|
-
AND ts + CASE interval
|
|
3517
|
-
WHEN '1d' THEN interval '1 day'
|
|
3518
|
-
WHEN '1h' THEN interval '1 hour'
|
|
3519
|
-
ELSE interval '0 seconds'
|
|
3520
|
-
END <= to_timestamp($3/1000.0)
|
|
3521
|
-
ORDER BY ts DESC
|
|
3522
|
-
LIMIT 1
|
|
3523
|
-
`,
|
|
3524
|
-
[source, interval, params.atMs],
|
|
3525
|
-
params
|
|
3526
|
-
);
|
|
3527
|
-
const row = res.rows[0];
|
|
3528
|
-
if (!row) return null;
|
|
3529
|
-
const previousRes = await queryMarketContext(
|
|
3530
|
-
`
|
|
3531
|
-
SELECT total_volume_usd AS "totalVolumeUsd"
|
|
3532
|
-
FROM market_cmc_exchange_liquidity_context
|
|
3533
|
-
WHERE source = $1
|
|
3534
|
-
AND interval = $2
|
|
3535
|
-
AND ts <= $3::timestamptz - interval '24 hours'
|
|
3536
|
-
ORDER BY ts DESC
|
|
3537
|
-
LIMIT 1
|
|
3538
|
-
`,
|
|
3539
|
-
[source, interval, row.ts],
|
|
3540
|
-
params
|
|
3541
|
-
);
|
|
3542
|
-
const currentTotal = row.totalVolumeUsd == null ? null : Number(row.totalVolumeUsd);
|
|
3543
|
-
const previousTotal = previousRes.rows[0]?.totalVolumeUsd == null ? null : Number(previousRes.rows[0].totalVolumeUsd);
|
|
3544
|
-
const ageMs = toMarketFeatureAge(row.ts, params.atMs);
|
|
3545
|
-
return {
|
|
3546
|
-
...row,
|
|
3547
|
-
ageMs,
|
|
3548
|
-
stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs,
|
|
3549
|
-
totalVolumeChange24hPct: currentTotal != null && previousTotal != null && previousTotal > 0 ? (currentTotal - previousTotal) / previousTotal : null
|
|
3550
|
-
};
|
|
3551
|
-
}
|
|
3552
|
-
async function getLatestMarketCmcIndexContexts(params) {
|
|
3553
|
-
const source = params.source ?? "coinmarketcap_index";
|
|
3554
|
-
const interval = params.interval ?? "1d";
|
|
2880
|
+
async function getMarketCmcIndexContextCoverage(params) {
|
|
3555
2881
|
const indexSlugs = [
|
|
3556
2882
|
...new Set(
|
|
3557
2883
|
params.indexSlugs.map((slug) => slug.trim().toLowerCase()).filter(
|
|
@@ -3559,233 +2885,51 @@ async function getLatestMarketCmcIndexContexts(params) {
|
|
|
3559
2885
|
)
|
|
3560
2886
|
)
|
|
3561
2887
|
];
|
|
3562
|
-
const
|
|
3563
|
-
if (!indexSlugs.length) return
|
|
3564
|
-
await
|
|
3565
|
-
const
|
|
2888
|
+
const coverage = /* @__PURE__ */ new Map();
|
|
2889
|
+
if (!indexSlugs.length) return coverage;
|
|
2890
|
+
await ensureBinanceMarketSchema();
|
|
2891
|
+
const pool = getPool();
|
|
2892
|
+
const res = await pool.query(
|
|
3566
2893
|
`
|
|
3567
|
-
SELECT
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
value,
|
|
3573
|
-
constituents_count::int AS "constituentsCount",
|
|
3574
|
-
top_constituent_symbol AS "topConstituentSymbol",
|
|
3575
|
-
top_constituent_weight_pct AS "topConstituentWeightPct",
|
|
3576
|
-
constituents
|
|
2894
|
+
SELECT
|
|
2895
|
+
index_slug,
|
|
2896
|
+
extract(epoch from MIN(ts))*1000 AS first_ms,
|
|
2897
|
+
extract(epoch from MAX(ts))*1000 AS last_ms,
|
|
2898
|
+
COUNT(*)::int AS rows
|
|
3577
2899
|
FROM market_cmc_index_context
|
|
3578
2900
|
WHERE source = $1
|
|
3579
2901
|
AND index_slug = ANY($2)
|
|
3580
2902
|
AND interval = $3
|
|
3581
|
-
AND ts
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
ELSE interval '0 seconds'
|
|
3585
|
-
END <= to_timestamp($4/1000.0)
|
|
3586
|
-
ORDER BY index_slug ASC, ts DESC
|
|
2903
|
+
AND ts >= to_timestamp($4/1000.0)
|
|
2904
|
+
AND ts <= to_timestamp($5/1000.0)
|
|
2905
|
+
GROUP BY index_slug
|
|
3587
2906
|
`,
|
|
3588
|
-
[source, indexSlugs, interval, params.
|
|
3589
|
-
params
|
|
2907
|
+
[params.source, indexSlugs, params.interval, params.startMs, params.endMs]
|
|
3590
2908
|
);
|
|
3591
2909
|
for (const row of res.rows) {
|
|
3592
|
-
const
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
AND ts <= $4::timestamptz - interval '24 hours'
|
|
3600
|
-
ORDER BY ts DESC
|
|
3601
|
-
LIMIT 1
|
|
3602
|
-
`,
|
|
3603
|
-
[source, row.indexSlug, interval, row.ts],
|
|
3604
|
-
params
|
|
3605
|
-
);
|
|
3606
|
-
const currentValue = row.value == null ? null : Number(row.value);
|
|
3607
|
-
const previousValue = previousRes.rows[0]?.value == null ? null : Number(previousRes.rows[0].value);
|
|
3608
|
-
const ageMs = toMarketFeatureAge(row.ts, params.atMs);
|
|
3609
|
-
rows.set(row.indexSlug, {
|
|
3610
|
-
...row,
|
|
3611
|
-
ageMs,
|
|
3612
|
-
stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs,
|
|
3613
|
-
valueChange24hPct: currentValue != null && previousValue != null && previousValue > 0 ? (currentValue - previousValue) / previousValue : null
|
|
3614
|
-
});
|
|
2910
|
+
const indexSlug = row.index_slug;
|
|
2911
|
+
const firstMs = Number(row.first_ms);
|
|
2912
|
+
const lastMs = Number(row.last_ms);
|
|
2913
|
+
const rows = Number(row.rows);
|
|
2914
|
+
if (Number.isFinite(firstMs) && Number.isFinite(lastMs) && rows > 0) {
|
|
2915
|
+
coverage.set(indexSlug, { firstMs, lastMs, rows });
|
|
2916
|
+
}
|
|
3615
2917
|
}
|
|
3616
|
-
return
|
|
3617
|
-
}
|
|
3618
|
-
async function getLatestMarketCmcFearGreedContext(params) {
|
|
3619
|
-
await prepareMarketContextSchemaForRead("coinmarketcap");
|
|
3620
|
-
const source = params.source ?? "coinmarketcap_fear_greed";
|
|
3621
|
-
const interval = params.interval ?? "1d";
|
|
3622
|
-
const res = await queryMarketContext(
|
|
3623
|
-
`
|
|
3624
|
-
SELECT
|
|
3625
|
-
source,
|
|
3626
|
-
interval,
|
|
3627
|
-
ts,
|
|
3628
|
-
value::int AS value,
|
|
3629
|
-
classification,
|
|
3630
|
-
sentiment_regime AS "sentimentRegime"
|
|
3631
|
-
FROM market_cmc_fear_greed_context
|
|
3632
|
-
WHERE source = $1
|
|
3633
|
-
AND interval = $2
|
|
3634
|
-
AND ts + CASE interval
|
|
3635
|
-
WHEN '1d' THEN interval '1 day'
|
|
3636
|
-
WHEN '1h' THEN interval '1 hour'
|
|
3637
|
-
ELSE interval '0 seconds'
|
|
3638
|
-
END <= to_timestamp($3/1000.0)
|
|
3639
|
-
ORDER BY ts DESC
|
|
3640
|
-
LIMIT 1
|
|
3641
|
-
`,
|
|
3642
|
-
[source, interval, params.atMs],
|
|
3643
|
-
params
|
|
3644
|
-
);
|
|
3645
|
-
const row = res.rows[0];
|
|
3646
|
-
if (!row) return null;
|
|
3647
|
-
const previousRes = await queryMarketContext(
|
|
3648
|
-
`
|
|
3649
|
-
SELECT
|
|
3650
|
-
value::int AS value,
|
|
3651
|
-
'24h' AS bucket
|
|
3652
|
-
FROM market_cmc_fear_greed_context
|
|
3653
|
-
WHERE source = $1
|
|
3654
|
-
AND interval = $2
|
|
3655
|
-
AND ts <= $3::timestamptz - interval '24 hours'
|
|
3656
|
-
ORDER BY ts DESC
|
|
3657
|
-
LIMIT 1
|
|
3658
|
-
`,
|
|
3659
|
-
[source, interval, row.ts],
|
|
3660
|
-
params
|
|
3661
|
-
);
|
|
3662
|
-
const previous7dRes = await queryMarketContext(
|
|
3663
|
-
`
|
|
3664
|
-
SELECT value::int AS value
|
|
3665
|
-
FROM market_cmc_fear_greed_context
|
|
3666
|
-
WHERE source = $1
|
|
3667
|
-
AND interval = $2
|
|
3668
|
-
AND ts <= $3::timestamptz - interval '7 days'
|
|
3669
|
-
ORDER BY ts DESC
|
|
3670
|
-
LIMIT 1
|
|
3671
|
-
`,
|
|
3672
|
-
[source, interval, row.ts],
|
|
3673
|
-
params
|
|
3674
|
-
);
|
|
3675
|
-
const previousValue = previousRes.rows[0]?.value == null ? null : Number(previousRes.rows[0].value);
|
|
3676
|
-
const previous7dValue = previous7dRes.rows[0]?.value == null ? null : Number(previous7dRes.rows[0].value);
|
|
3677
|
-
const ageMs = toMarketFeatureAge(row.ts, params.atMs);
|
|
3678
|
-
return {
|
|
3679
|
-
...row,
|
|
3680
|
-
ageMs,
|
|
3681
|
-
stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs,
|
|
3682
|
-
valueChange24h: previousValue == null ? null : row.value - previousValue,
|
|
3683
|
-
valueChange7d: previous7dValue == null ? null : row.value - previous7dValue
|
|
3684
|
-
};
|
|
2918
|
+
return coverage;
|
|
3685
2919
|
}
|
|
3686
|
-
async function
|
|
2920
|
+
async function getMarketTradeFlowCoverage(params) {
|
|
2921
|
+
const symbols = [
|
|
2922
|
+
...new Set(params.symbols.map((item) => item.toUpperCase()))
|
|
2923
|
+
];
|
|
2924
|
+
if (!symbols.length) return /* @__PURE__ */ new Map();
|
|
3687
2925
|
await ensureBinanceMarketSchema();
|
|
3688
2926
|
const pool = getPool();
|
|
3689
2927
|
const res = await pool.query(
|
|
3690
2928
|
`
|
|
3691
2929
|
SELECT
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
FROM market_cmc_fear_greed_context
|
|
3696
|
-
WHERE source = $1
|
|
3697
|
-
AND interval = $2
|
|
3698
|
-
AND ts >= to_timestamp($3/1000.0)
|
|
3699
|
-
AND ts <= to_timestamp($4/1000.0)
|
|
3700
|
-
`,
|
|
3701
|
-
[params.source, params.interval, params.startMs, params.endMs]
|
|
3702
|
-
);
|
|
3703
|
-
const rows = Number(res.rows[0]?.rows ?? 0);
|
|
3704
|
-
const firstMs = Number(res.rows[0]?.first_ms);
|
|
3705
|
-
const lastMs = Number(res.rows[0]?.last_ms);
|
|
3706
|
-
if (!rows || !Number.isFinite(firstMs) || !Number.isFinite(lastMs)) {
|
|
3707
|
-
return null;
|
|
3708
|
-
}
|
|
3709
|
-
return { firstMs, lastMs, rows };
|
|
3710
|
-
}
|
|
3711
|
-
async function getMarketCmcExchangeLiquidityContextCoverage(params) {
|
|
3712
|
-
await ensureBinanceMarketSchema();
|
|
3713
|
-
const pool = getPool();
|
|
3714
|
-
const res = await pool.query(
|
|
3715
|
-
`
|
|
3716
|
-
SELECT
|
|
3717
|
-
extract(epoch from MIN(ts))*1000 AS first_ms,
|
|
3718
|
-
extract(epoch from MAX(ts))*1000 AS last_ms,
|
|
3719
|
-
COUNT(*)::int AS rows
|
|
3720
|
-
FROM market_cmc_exchange_liquidity_context
|
|
3721
|
-
WHERE source = $1
|
|
3722
|
-
AND interval = $2
|
|
3723
|
-
AND ts >= to_timestamp($3/1000.0)
|
|
3724
|
-
AND ts <= to_timestamp($4/1000.0)
|
|
3725
|
-
`,
|
|
3726
|
-
[params.source, params.interval, params.startMs, params.endMs]
|
|
3727
|
-
);
|
|
3728
|
-
const rows = Number(res.rows[0]?.rows ?? 0);
|
|
3729
|
-
const firstMs = Number(res.rows[0]?.first_ms);
|
|
3730
|
-
const lastMs = Number(res.rows[0]?.last_ms);
|
|
3731
|
-
if (!rows || !Number.isFinite(firstMs) || !Number.isFinite(lastMs)) {
|
|
3732
|
-
return null;
|
|
3733
|
-
}
|
|
3734
|
-
return { firstMs, lastMs, rows };
|
|
3735
|
-
}
|
|
3736
|
-
async function getMarketCmcIndexContextCoverage(params) {
|
|
3737
|
-
const indexSlugs = [
|
|
3738
|
-
...new Set(
|
|
3739
|
-
params.indexSlugs.map((slug) => slug.trim().toLowerCase()).filter(
|
|
3740
|
-
(slug) => ["cmc100", "cmc20"].includes(slug)
|
|
3741
|
-
)
|
|
3742
|
-
)
|
|
3743
|
-
];
|
|
3744
|
-
const coverage = /* @__PURE__ */ new Map();
|
|
3745
|
-
if (!indexSlugs.length) return coverage;
|
|
3746
|
-
await ensureBinanceMarketSchema();
|
|
3747
|
-
const pool = getPool();
|
|
3748
|
-
const res = await pool.query(
|
|
3749
|
-
`
|
|
3750
|
-
SELECT
|
|
3751
|
-
index_slug,
|
|
3752
|
-
extract(epoch from MIN(ts))*1000 AS first_ms,
|
|
3753
|
-
extract(epoch from MAX(ts))*1000 AS last_ms,
|
|
3754
|
-
COUNT(*)::int AS rows
|
|
3755
|
-
FROM market_cmc_index_context
|
|
3756
|
-
WHERE source = $1
|
|
3757
|
-
AND index_slug = ANY($2)
|
|
3758
|
-
AND interval = $3
|
|
3759
|
-
AND ts >= to_timestamp($4/1000.0)
|
|
3760
|
-
AND ts <= to_timestamp($5/1000.0)
|
|
3761
|
-
GROUP BY index_slug
|
|
3762
|
-
`,
|
|
3763
|
-
[params.source, indexSlugs, params.interval, params.startMs, params.endMs]
|
|
3764
|
-
);
|
|
3765
|
-
for (const row of res.rows) {
|
|
3766
|
-
const indexSlug = row.index_slug;
|
|
3767
|
-
const firstMs = Number(row.first_ms);
|
|
3768
|
-
const lastMs = Number(row.last_ms);
|
|
3769
|
-
const rows = Number(row.rows);
|
|
3770
|
-
if (Number.isFinite(firstMs) && Number.isFinite(lastMs) && rows > 0) {
|
|
3771
|
-
coverage.set(indexSlug, { firstMs, lastMs, rows });
|
|
3772
|
-
}
|
|
3773
|
-
}
|
|
3774
|
-
return coverage;
|
|
3775
|
-
}
|
|
3776
|
-
async function getMarketTradeFlowCoverage(params) {
|
|
3777
|
-
const symbols = [
|
|
3778
|
-
...new Set(params.symbols.map((item) => item.toUpperCase()))
|
|
3779
|
-
];
|
|
3780
|
-
if (!symbols.length) return /* @__PURE__ */ new Map();
|
|
3781
|
-
await ensureBinanceMarketSchema();
|
|
3782
|
-
const pool = getPool();
|
|
3783
|
-
const res = await pool.query(
|
|
3784
|
-
`
|
|
3785
|
-
SELECT
|
|
3786
|
-
symbol,
|
|
3787
|
-
MIN(ts) AS first_ts,
|
|
3788
|
-
MAX(ts) AS last_ts,
|
|
2930
|
+
symbol,
|
|
2931
|
+
MIN(ts) AS first_ts,
|
|
2932
|
+
MAX(ts) AS last_ts,
|
|
3789
2933
|
COUNT(*)::int AS rows
|
|
3790
2934
|
FROM market_trade_flow
|
|
3791
2935
|
WHERE symbol = ANY($1)
|
|
@@ -3926,232 +3070,1097 @@ async function getMarketBreadthCoverage(params) {
|
|
|
3926
3070
|
btcAltMetricsRows: Number(row.btc_alt_metrics_rows) || 0
|
|
3927
3071
|
};
|
|
3928
3072
|
}
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3073
|
+
|
|
3074
|
+
// src/timescale/hyperliquidWhales.ts
|
|
3075
|
+
var import_types = require("@tradejs/types");
|
|
3076
|
+
var HYPERLIQUID_CONTEXT_INTERVAL_MS = {
|
|
3077
|
+
"1m": 6e4,
|
|
3078
|
+
"5m": 5 * 6e4,
|
|
3079
|
+
"15m": 15 * 6e4,
|
|
3080
|
+
"1h": 60 * 6e4
|
|
3081
|
+
};
|
|
3082
|
+
async function upsertHyperliquidWhaleTradeEvents(rows) {
|
|
3083
|
+
if (!rows.length) return;
|
|
3084
|
+
await ensureHyperliquidWhaleSchema();
|
|
3085
|
+
const cols = [
|
|
3086
|
+
"symbol",
|
|
3087
|
+
"ts",
|
|
3088
|
+
"tid",
|
|
3089
|
+
"price",
|
|
3090
|
+
"size",
|
|
3091
|
+
"notional_usd",
|
|
3092
|
+
"buyer_address",
|
|
3093
|
+
"seller_address",
|
|
3094
|
+
"buyer_tracked",
|
|
3095
|
+
"seller_tracked",
|
|
3096
|
+
"buyer_start_position",
|
|
3097
|
+
"buyer_end_position",
|
|
3098
|
+
"buyer_position_action",
|
|
3099
|
+
"buyer_closed_pnl",
|
|
3100
|
+
"buyer_liquidation",
|
|
3101
|
+
"seller_start_position",
|
|
3102
|
+
"seller_end_position",
|
|
3103
|
+
"seller_position_action",
|
|
3104
|
+
"seller_closed_pnl",
|
|
3105
|
+
"seller_liquidation",
|
|
3106
|
+
"universe_fingerprint",
|
|
3107
|
+
"whale_registry_fingerprint",
|
|
3108
|
+
"source"
|
|
3109
|
+
];
|
|
3110
|
+
const maxRows = getSafeBulkInsertRows(cols.length);
|
|
3111
|
+
if (rows.length > maxRows) {
|
|
3112
|
+
for (let index = 0; index < rows.length; index += maxRows) {
|
|
3113
|
+
await upsertHyperliquidWhaleTradeEvents(
|
|
3114
|
+
rows.slice(index, index + maxRows)
|
|
3115
|
+
);
|
|
3116
|
+
}
|
|
3117
|
+
return;
|
|
3932
3118
|
}
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
)
|
|
3962
|
-
const aggQ = await pool.query(
|
|
3119
|
+
const valuesSql = rows.map(
|
|
3120
|
+
(_, rowIndex) => `(${cols.map((__, colIndex) => `$${rowIndex * cols.length + colIndex + 1}`).join(",")})`
|
|
3121
|
+
).join(",");
|
|
3122
|
+
const flat = rows.flatMap((row) => [
|
|
3123
|
+
row.symbol,
|
|
3124
|
+
row.ts,
|
|
3125
|
+
row.tid,
|
|
3126
|
+
row.price,
|
|
3127
|
+
row.size,
|
|
3128
|
+
row.notionalUsd,
|
|
3129
|
+
row.buyerAddress ?? null,
|
|
3130
|
+
row.sellerAddress ?? null,
|
|
3131
|
+
row.buyerTracked,
|
|
3132
|
+
row.sellerTracked,
|
|
3133
|
+
row.buyerStartPosition ?? null,
|
|
3134
|
+
row.buyerEndPosition ?? null,
|
|
3135
|
+
row.buyerPositionAction ?? null,
|
|
3136
|
+
row.buyerClosedPnl ?? null,
|
|
3137
|
+
row.buyerLiquidation ?? null,
|
|
3138
|
+
row.sellerStartPosition ?? null,
|
|
3139
|
+
row.sellerEndPosition ?? null,
|
|
3140
|
+
row.sellerPositionAction ?? null,
|
|
3141
|
+
row.sellerClosedPnl ?? null,
|
|
3142
|
+
row.sellerLiquidation ?? null,
|
|
3143
|
+
row.universeFingerprint,
|
|
3144
|
+
row.whaleRegistryFingerprint,
|
|
3145
|
+
row.source ?? null
|
|
3146
|
+
]);
|
|
3147
|
+
await getPool().query(
|
|
3963
3148
|
`
|
|
3964
|
-
|
|
3149
|
+
INSERT INTO hyperliquid_whale_trade_events (${cols.join(",")})
|
|
3150
|
+
VALUES ${valuesSql}
|
|
3151
|
+
ON CONFLICT (
|
|
3152
|
+
universe_fingerprint,
|
|
3153
|
+
whale_registry_fingerprint,
|
|
3965
3154
|
symbol,
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
const sqlMax = `
|
|
4025
|
-
SELECT extract(epoch from ts)*1000 AS ms
|
|
4026
|
-
FROM candles
|
|
4027
|
-
WHERE provider=$1 AND symbol=$2 AND interval=$3
|
|
4028
|
-
ORDER BY ts DESC
|
|
4029
|
-
LIMIT 1
|
|
4030
|
-
`;
|
|
4031
|
-
const [minQ, maxQ] = await Promise.all([
|
|
4032
|
-
pool.query(sqlMin, [normalizedProvider, normalizedSymbol, interval]),
|
|
4033
|
-
pool.query(sqlMax, [normalizedProvider, normalizedSymbol, interval])
|
|
4034
|
-
]);
|
|
4035
|
-
const minRaw = minQ.rows[0]?.ms;
|
|
4036
|
-
const maxRaw = maxQ.rows[0]?.ms;
|
|
4037
|
-
const min = Number.isFinite(Number(minRaw)) ? Number(minRaw) : void 0;
|
|
4038
|
-
const max = Number.isFinite(Number(maxRaw)) ? Number(maxRaw) : void 0;
|
|
4039
|
-
return { min, max };
|
|
3155
|
+
ts,
|
|
3156
|
+
tid
|
|
3157
|
+
) DO UPDATE SET
|
|
3158
|
+
buyer_address = COALESCE(
|
|
3159
|
+
hyperliquid_whale_trade_events.buyer_address,
|
|
3160
|
+
EXCLUDED.buyer_address
|
|
3161
|
+
),
|
|
3162
|
+
seller_address = COALESCE(
|
|
3163
|
+
hyperliquid_whale_trade_events.seller_address,
|
|
3164
|
+
EXCLUDED.seller_address
|
|
3165
|
+
),
|
|
3166
|
+
buyer_tracked = hyperliquid_whale_trade_events.buyer_tracked OR EXCLUDED.buyer_tracked,
|
|
3167
|
+
seller_tracked = hyperliquid_whale_trade_events.seller_tracked OR EXCLUDED.seller_tracked,
|
|
3168
|
+
buyer_start_position = COALESCE(
|
|
3169
|
+
hyperliquid_whale_trade_events.buyer_start_position,
|
|
3170
|
+
EXCLUDED.buyer_start_position
|
|
3171
|
+
),
|
|
3172
|
+
buyer_end_position = COALESCE(
|
|
3173
|
+
hyperliquid_whale_trade_events.buyer_end_position,
|
|
3174
|
+
EXCLUDED.buyer_end_position
|
|
3175
|
+
),
|
|
3176
|
+
buyer_position_action = COALESCE(
|
|
3177
|
+
hyperliquid_whale_trade_events.buyer_position_action,
|
|
3178
|
+
EXCLUDED.buyer_position_action
|
|
3179
|
+
),
|
|
3180
|
+
buyer_closed_pnl = COALESCE(
|
|
3181
|
+
hyperliquid_whale_trade_events.buyer_closed_pnl,
|
|
3182
|
+
EXCLUDED.buyer_closed_pnl
|
|
3183
|
+
),
|
|
3184
|
+
buyer_liquidation = COALESCE(
|
|
3185
|
+
hyperliquid_whale_trade_events.buyer_liquidation,
|
|
3186
|
+
EXCLUDED.buyer_liquidation
|
|
3187
|
+
),
|
|
3188
|
+
seller_start_position = COALESCE(
|
|
3189
|
+
hyperliquid_whale_trade_events.seller_start_position,
|
|
3190
|
+
EXCLUDED.seller_start_position
|
|
3191
|
+
),
|
|
3192
|
+
seller_end_position = COALESCE(
|
|
3193
|
+
hyperliquid_whale_trade_events.seller_end_position,
|
|
3194
|
+
EXCLUDED.seller_end_position
|
|
3195
|
+
),
|
|
3196
|
+
seller_position_action = COALESCE(
|
|
3197
|
+
hyperliquid_whale_trade_events.seller_position_action,
|
|
3198
|
+
EXCLUDED.seller_position_action
|
|
3199
|
+
),
|
|
3200
|
+
seller_closed_pnl = COALESCE(
|
|
3201
|
+
hyperliquid_whale_trade_events.seller_closed_pnl,
|
|
3202
|
+
EXCLUDED.seller_closed_pnl
|
|
3203
|
+
),
|
|
3204
|
+
seller_liquidation = COALESCE(
|
|
3205
|
+
hyperliquid_whale_trade_events.seller_liquidation,
|
|
3206
|
+
EXCLUDED.seller_liquidation
|
|
3207
|
+
),
|
|
3208
|
+
source = EXCLUDED.source,
|
|
3209
|
+
ingested_at = now()
|
|
3210
|
+
`,
|
|
3211
|
+
flat
|
|
3212
|
+
);
|
|
4040
3213
|
}
|
|
4041
|
-
async function
|
|
4042
|
-
|
|
4043
|
-
|
|
3214
|
+
async function upsertHyperliquidWhaleFlowRows(rows) {
|
|
3215
|
+
if (!rows.length) return;
|
|
3216
|
+
await ensureHyperliquidWhaleSchema();
|
|
3217
|
+
const cols = [
|
|
3218
|
+
"symbol",
|
|
3219
|
+
"interval",
|
|
3220
|
+
"ts",
|
|
3221
|
+
"trades",
|
|
3222
|
+
"whale_sides",
|
|
3223
|
+
"unique_whales",
|
|
3224
|
+
"whale_addresses",
|
|
3225
|
+
"buy_notional_usd",
|
|
3226
|
+
"sell_notional_usd",
|
|
3227
|
+
"net_notional_usd",
|
|
3228
|
+
"buy_share_pct",
|
|
3229
|
+
"position_aware_whale_sides",
|
|
3230
|
+
"long_entry_whale_addresses",
|
|
3231
|
+
"short_entry_whale_addresses",
|
|
3232
|
+
"long_exit_whale_addresses",
|
|
3233
|
+
"short_exit_whale_addresses",
|
|
3234
|
+
"long_entry_notional_usd",
|
|
3235
|
+
"short_entry_notional_usd",
|
|
3236
|
+
"long_exit_notional_usd",
|
|
3237
|
+
"short_exit_notional_usd",
|
|
3238
|
+
"entry_net_notional_usd",
|
|
3239
|
+
"entry_long_share_pct",
|
|
3240
|
+
"universe_fingerprint",
|
|
3241
|
+
"whale_registry_fingerprint",
|
|
3242
|
+
"source"
|
|
4044
3243
|
];
|
|
4045
|
-
const
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
return
|
|
3244
|
+
const maxRows = getSafeBulkInsertRows(cols.length);
|
|
3245
|
+
if (rows.length > maxRows) {
|
|
3246
|
+
for (let index = 0; index < rows.length; index += maxRows) {
|
|
3247
|
+
await upsertHyperliquidWhaleFlowRows(rows.slice(index, index + maxRows));
|
|
3248
|
+
}
|
|
3249
|
+
return;
|
|
4051
3250
|
}
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
const
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
3251
|
+
const valuesSql = rows.map(
|
|
3252
|
+
(_, rowIndex) => `(${cols.map((__, colIndex) => `$${rowIndex * cols.length + colIndex + 1}`).join(",")})`
|
|
3253
|
+
).join(",");
|
|
3254
|
+
const flat = rows.flatMap((row) => [
|
|
3255
|
+
row.symbol,
|
|
3256
|
+
row.interval,
|
|
3257
|
+
row.ts,
|
|
3258
|
+
row.trades,
|
|
3259
|
+
row.whaleSides,
|
|
3260
|
+
row.uniqueWhales,
|
|
3261
|
+
row.whaleAddresses ?? [],
|
|
3262
|
+
row.buyNotionalUsd,
|
|
3263
|
+
row.sellNotionalUsd,
|
|
3264
|
+
row.netNotionalUsd,
|
|
3265
|
+
row.buySharePct ?? null,
|
|
3266
|
+
row.positionAwareWhaleSides,
|
|
3267
|
+
row.longEntryWhaleAddresses ?? [],
|
|
3268
|
+
row.shortEntryWhaleAddresses ?? [],
|
|
3269
|
+
row.longExitWhaleAddresses ?? [],
|
|
3270
|
+
row.shortExitWhaleAddresses ?? [],
|
|
3271
|
+
row.longEntryNotionalUsd,
|
|
3272
|
+
row.shortEntryNotionalUsd,
|
|
3273
|
+
row.longExitNotionalUsd,
|
|
3274
|
+
row.shortExitNotionalUsd,
|
|
3275
|
+
row.entryNetNotionalUsd,
|
|
3276
|
+
row.entryLongSharePct ?? null,
|
|
3277
|
+
row.universeFingerprint,
|
|
3278
|
+
row.whaleRegistryFingerprint,
|
|
3279
|
+
row.source ?? null
|
|
4081
3280
|
]);
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
3281
|
+
await getPool().query(
|
|
3282
|
+
`
|
|
3283
|
+
INSERT INTO hyperliquid_whale_flow (${cols.join(",")})
|
|
3284
|
+
VALUES ${valuesSql}
|
|
3285
|
+
ON CONFLICT (
|
|
3286
|
+
universe_fingerprint,
|
|
3287
|
+
whale_registry_fingerprint,
|
|
3288
|
+
symbol,
|
|
3289
|
+
interval,
|
|
3290
|
+
ts
|
|
3291
|
+
) DO UPDATE SET
|
|
3292
|
+
trades = EXCLUDED.trades,
|
|
3293
|
+
whale_sides = EXCLUDED.whale_sides,
|
|
3294
|
+
unique_whales = EXCLUDED.unique_whales,
|
|
3295
|
+
whale_addresses = EXCLUDED.whale_addresses,
|
|
3296
|
+
buy_notional_usd = EXCLUDED.buy_notional_usd,
|
|
3297
|
+
sell_notional_usd = EXCLUDED.sell_notional_usd,
|
|
3298
|
+
net_notional_usd = EXCLUDED.net_notional_usd,
|
|
3299
|
+
buy_share_pct = EXCLUDED.buy_share_pct,
|
|
3300
|
+
position_aware_whale_sides = EXCLUDED.position_aware_whale_sides,
|
|
3301
|
+
long_entry_whale_addresses = EXCLUDED.long_entry_whale_addresses,
|
|
3302
|
+
short_entry_whale_addresses = EXCLUDED.short_entry_whale_addresses,
|
|
3303
|
+
long_exit_whale_addresses = EXCLUDED.long_exit_whale_addresses,
|
|
3304
|
+
short_exit_whale_addresses = EXCLUDED.short_exit_whale_addresses,
|
|
3305
|
+
long_entry_notional_usd = EXCLUDED.long_entry_notional_usd,
|
|
3306
|
+
short_entry_notional_usd = EXCLUDED.short_entry_notional_usd,
|
|
3307
|
+
long_exit_notional_usd = EXCLUDED.long_exit_notional_usd,
|
|
3308
|
+
short_exit_notional_usd = EXCLUDED.short_exit_notional_usd,
|
|
3309
|
+
entry_net_notional_usd = EXCLUDED.entry_net_notional_usd,
|
|
3310
|
+
entry_long_share_pct = EXCLUDED.entry_long_share_pct,
|
|
3311
|
+
source = EXCLUDED.source,
|
|
3312
|
+
ingested_at = now()
|
|
3313
|
+
`,
|
|
3314
|
+
flat
|
|
3315
|
+
);
|
|
3316
|
+
}
|
|
3317
|
+
async function rebuildHyperliquidWhaleFlowRows(params) {
|
|
3318
|
+
await ensureHyperliquidWhaleSchema();
|
|
3319
|
+
const client = await getPool().connect();
|
|
3320
|
+
try {
|
|
3321
|
+
await client.query("BEGIN");
|
|
3322
|
+
const result = await client.query(
|
|
3323
|
+
`
|
|
3324
|
+
WITH source_events AS (
|
|
3325
|
+
SELECT *
|
|
3326
|
+
FROM hyperliquid_whale_trade_events
|
|
3327
|
+
WHERE universe_fingerprint = $1
|
|
3328
|
+
AND whale_registry_fingerprint = $2
|
|
3329
|
+
AND ts >= to_timestamp($3/1000.0)
|
|
3330
|
+
AND ts < to_timestamp($4/1000.0)
|
|
3331
|
+
), metrics AS (
|
|
3332
|
+
SELECT
|
|
3333
|
+
symbol,
|
|
3334
|
+
date_trunc('minute', ts) AS bucket_ts,
|
|
3335
|
+
COUNT(*)::int AS trades,
|
|
3336
|
+
SUM(buyer_tracked::int + seller_tracked::int)::int AS whale_sides,
|
|
3337
|
+
SUM(CASE WHEN buyer_tracked THEN notional_usd ELSE 0 END) AS buy_notional_usd,
|
|
3338
|
+
SUM(CASE WHEN seller_tracked THEN notional_usd ELSE 0 END) AS sell_notional_usd
|
|
3339
|
+
FROM source_events
|
|
3340
|
+
GROUP BY symbol, date_trunc('minute', ts)
|
|
3341
|
+
), position_legs AS (
|
|
3342
|
+
SELECT
|
|
3343
|
+
symbol,
|
|
3344
|
+
ts,
|
|
3345
|
+
price,
|
|
3346
|
+
buyer_address AS whale_address,
|
|
3347
|
+
buyer_start_position AS start_position,
|
|
3348
|
+
buyer_end_position AS end_position
|
|
3349
|
+
FROM source_events
|
|
3350
|
+
WHERE buyer_tracked
|
|
3351
|
+
AND buyer_address IS NOT NULL
|
|
3352
|
+
AND buyer_start_position IS NOT NULL
|
|
3353
|
+
AND buyer_end_position IS NOT NULL
|
|
3354
|
+
UNION ALL
|
|
3355
|
+
SELECT
|
|
3356
|
+
symbol,
|
|
3357
|
+
ts,
|
|
3358
|
+
price,
|
|
3359
|
+
seller_address AS whale_address,
|
|
3360
|
+
seller_start_position AS start_position,
|
|
3361
|
+
seller_end_position AS end_position
|
|
3362
|
+
FROM source_events
|
|
3363
|
+
WHERE seller_tracked
|
|
3364
|
+
AND seller_address IS NOT NULL
|
|
3365
|
+
AND seller_start_position IS NOT NULL
|
|
3366
|
+
AND seller_end_position IS NOT NULL
|
|
3367
|
+
), classified_legs AS (
|
|
3368
|
+
SELECT
|
|
3369
|
+
*,
|
|
3370
|
+
GREATEST(
|
|
3371
|
+
GREATEST(end_position, 0) - GREATEST(start_position, 0),
|
|
3372
|
+
0
|
|
3373
|
+
) AS long_entry_size,
|
|
3374
|
+
GREATEST(
|
|
3375
|
+
GREATEST(-end_position, 0) - GREATEST(-start_position, 0),
|
|
3376
|
+
0
|
|
3377
|
+
) AS short_entry_size,
|
|
3378
|
+
GREATEST(
|
|
3379
|
+
GREATEST(start_position, 0) - GREATEST(end_position, 0),
|
|
3380
|
+
0
|
|
3381
|
+
) AS long_exit_size,
|
|
3382
|
+
GREATEST(
|
|
3383
|
+
GREATEST(-start_position, 0) - GREATEST(-end_position, 0),
|
|
3384
|
+
0
|
|
3385
|
+
) AS short_exit_size
|
|
3386
|
+
FROM position_legs
|
|
3387
|
+
), position_metrics AS (
|
|
3388
|
+
SELECT
|
|
3389
|
+
symbol,
|
|
3390
|
+
date_trunc('minute', ts) AS bucket_ts,
|
|
3391
|
+
COUNT(*)::int AS position_aware_whale_sides,
|
|
3392
|
+
SUM(long_entry_size * price) AS long_entry_notional_usd,
|
|
3393
|
+
SUM(short_entry_size * price) AS short_entry_notional_usd,
|
|
3394
|
+
SUM(long_exit_size * price) AS long_exit_notional_usd,
|
|
3395
|
+
SUM(short_exit_size * price) AS short_exit_notional_usd,
|
|
3396
|
+
COALESCE(
|
|
3397
|
+
ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
|
|
3398
|
+
FILTER (WHERE long_entry_size > 0),
|
|
3399
|
+
'{}'
|
|
3400
|
+
) AS long_entry_whale_addresses,
|
|
3401
|
+
COALESCE(
|
|
3402
|
+
ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
|
|
3403
|
+
FILTER (WHERE short_entry_size > 0),
|
|
3404
|
+
'{}'
|
|
3405
|
+
) AS short_entry_whale_addresses,
|
|
3406
|
+
COALESCE(
|
|
3407
|
+
ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
|
|
3408
|
+
FILTER (WHERE long_exit_size > 0),
|
|
3409
|
+
'{}'
|
|
3410
|
+
) AS long_exit_whale_addresses,
|
|
3411
|
+
COALESCE(
|
|
3412
|
+
ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
|
|
3413
|
+
FILTER (WHERE short_exit_size > 0),
|
|
3414
|
+
'{}'
|
|
3415
|
+
) AS short_exit_whale_addresses
|
|
3416
|
+
FROM classified_legs
|
|
3417
|
+
GROUP BY symbol, date_trunc('minute', ts)
|
|
3418
|
+
), addresses AS (
|
|
3419
|
+
SELECT
|
|
3420
|
+
symbol,
|
|
3421
|
+
date_trunc('minute', ts) AS bucket_ts,
|
|
3422
|
+
ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address) AS whale_addresses
|
|
3423
|
+
FROM source_events
|
|
3424
|
+
CROSS JOIN LATERAL UNNEST(ARRAY[
|
|
3425
|
+
CASE WHEN buyer_tracked THEN buyer_address END,
|
|
3426
|
+
CASE WHEN seller_tracked THEN seller_address END
|
|
3427
|
+
]) AS expanded(whale_address)
|
|
3428
|
+
WHERE whale_address IS NOT NULL
|
|
3429
|
+
GROUP BY symbol, date_trunc('minute', ts)
|
|
3430
|
+
)
|
|
3431
|
+
INSERT INTO hyperliquid_whale_flow (
|
|
3432
|
+
symbol,
|
|
3433
|
+
interval,
|
|
3434
|
+
ts,
|
|
3435
|
+
trades,
|
|
3436
|
+
whale_sides,
|
|
3437
|
+
unique_whales,
|
|
3438
|
+
whale_addresses,
|
|
3439
|
+
buy_notional_usd,
|
|
3440
|
+
sell_notional_usd,
|
|
3441
|
+
net_notional_usd,
|
|
3442
|
+
buy_share_pct,
|
|
3443
|
+
position_aware_whale_sides,
|
|
3444
|
+
long_entry_whale_addresses,
|
|
3445
|
+
short_entry_whale_addresses,
|
|
3446
|
+
long_exit_whale_addresses,
|
|
3447
|
+
short_exit_whale_addresses,
|
|
3448
|
+
long_entry_notional_usd,
|
|
3449
|
+
short_entry_notional_usd,
|
|
3450
|
+
long_exit_notional_usd,
|
|
3451
|
+
short_exit_notional_usd,
|
|
3452
|
+
entry_net_notional_usd,
|
|
3453
|
+
entry_long_share_pct,
|
|
3454
|
+
universe_fingerprint,
|
|
3455
|
+
whale_registry_fingerprint,
|
|
3456
|
+
source
|
|
3457
|
+
)
|
|
3458
|
+
SELECT
|
|
3459
|
+
metrics.symbol,
|
|
3460
|
+
'1m',
|
|
3461
|
+
metrics.bucket_ts,
|
|
3462
|
+
metrics.trades,
|
|
3463
|
+
metrics.whale_sides,
|
|
3464
|
+
COALESCE(CARDINALITY(addresses.whale_addresses), 0),
|
|
3465
|
+
COALESCE(addresses.whale_addresses, '{}'),
|
|
3466
|
+
metrics.buy_notional_usd,
|
|
3467
|
+
metrics.sell_notional_usd,
|
|
3468
|
+
metrics.buy_notional_usd - metrics.sell_notional_usd,
|
|
3469
|
+
CASE
|
|
3470
|
+
WHEN metrics.buy_notional_usd + metrics.sell_notional_usd > 0
|
|
3471
|
+
THEN metrics.buy_notional_usd /
|
|
3472
|
+
(metrics.buy_notional_usd + metrics.sell_notional_usd)
|
|
3473
|
+
ELSE NULL
|
|
3474
|
+
END,
|
|
3475
|
+
COALESCE(position_metrics.position_aware_whale_sides, 0),
|
|
3476
|
+
COALESCE(position_metrics.long_entry_whale_addresses, '{}'),
|
|
3477
|
+
COALESCE(position_metrics.short_entry_whale_addresses, '{}'),
|
|
3478
|
+
COALESCE(position_metrics.long_exit_whale_addresses, '{}'),
|
|
3479
|
+
COALESCE(position_metrics.short_exit_whale_addresses, '{}'),
|
|
3480
|
+
COALESCE(position_metrics.long_entry_notional_usd, 0),
|
|
3481
|
+
COALESCE(position_metrics.short_entry_notional_usd, 0),
|
|
3482
|
+
COALESCE(position_metrics.long_exit_notional_usd, 0),
|
|
3483
|
+
COALESCE(position_metrics.short_exit_notional_usd, 0),
|
|
3484
|
+
COALESCE(position_metrics.long_entry_notional_usd, 0) -
|
|
3485
|
+
COALESCE(position_metrics.short_entry_notional_usd, 0),
|
|
3486
|
+
CASE
|
|
3487
|
+
WHEN COALESCE(position_metrics.long_entry_notional_usd, 0) +
|
|
3488
|
+
COALESCE(position_metrics.short_entry_notional_usd, 0) > 0
|
|
3489
|
+
THEN COALESCE(position_metrics.long_entry_notional_usd, 0) /
|
|
3490
|
+
(
|
|
3491
|
+
COALESCE(position_metrics.long_entry_notional_usd, 0) +
|
|
3492
|
+
COALESCE(position_metrics.short_entry_notional_usd, 0)
|
|
3493
|
+
)
|
|
3494
|
+
ELSE NULL
|
|
3495
|
+
END,
|
|
3496
|
+
$1,
|
|
3497
|
+
$2,
|
|
3498
|
+
CASE
|
|
3499
|
+
WHEN COALESCE(position_metrics.position_aware_whale_sides, 0) > 0
|
|
3500
|
+
THEN 'hyperliquid_user_fills'
|
|
3501
|
+
ELSE 'hyperliquid_trades'
|
|
3502
|
+
END
|
|
3503
|
+
FROM metrics
|
|
3504
|
+
LEFT JOIN addresses USING (symbol, bucket_ts)
|
|
3505
|
+
LEFT JOIN position_metrics USING (symbol, bucket_ts)
|
|
3506
|
+
ON CONFLICT (
|
|
3507
|
+
universe_fingerprint,
|
|
3508
|
+
whale_registry_fingerprint,
|
|
3509
|
+
symbol,
|
|
3510
|
+
interval,
|
|
3511
|
+
ts
|
|
3512
|
+
) DO UPDATE SET
|
|
3513
|
+
trades = EXCLUDED.trades,
|
|
3514
|
+
whale_sides = EXCLUDED.whale_sides,
|
|
3515
|
+
unique_whales = EXCLUDED.unique_whales,
|
|
3516
|
+
whale_addresses = EXCLUDED.whale_addresses,
|
|
3517
|
+
buy_notional_usd = EXCLUDED.buy_notional_usd,
|
|
3518
|
+
sell_notional_usd = EXCLUDED.sell_notional_usd,
|
|
3519
|
+
net_notional_usd = EXCLUDED.net_notional_usd,
|
|
3520
|
+
buy_share_pct = EXCLUDED.buy_share_pct,
|
|
3521
|
+
position_aware_whale_sides = EXCLUDED.position_aware_whale_sides,
|
|
3522
|
+
long_entry_whale_addresses = EXCLUDED.long_entry_whale_addresses,
|
|
3523
|
+
short_entry_whale_addresses = EXCLUDED.short_entry_whale_addresses,
|
|
3524
|
+
long_exit_whale_addresses = EXCLUDED.long_exit_whale_addresses,
|
|
3525
|
+
short_exit_whale_addresses = EXCLUDED.short_exit_whale_addresses,
|
|
3526
|
+
long_entry_notional_usd = EXCLUDED.long_entry_notional_usd,
|
|
3527
|
+
short_entry_notional_usd = EXCLUDED.short_entry_notional_usd,
|
|
3528
|
+
long_exit_notional_usd = EXCLUDED.long_exit_notional_usd,
|
|
3529
|
+
short_exit_notional_usd = EXCLUDED.short_exit_notional_usd,
|
|
3530
|
+
entry_net_notional_usd = EXCLUDED.entry_net_notional_usd,
|
|
3531
|
+
entry_long_share_pct = EXCLUDED.entry_long_share_pct,
|
|
3532
|
+
source = EXCLUDED.source,
|
|
3533
|
+
ingested_at = now()
|
|
3534
|
+
RETURNING 1
|
|
3535
|
+
`,
|
|
3536
|
+
[
|
|
3537
|
+
params.universeFingerprint,
|
|
3538
|
+
params.whaleRegistryFingerprint,
|
|
3539
|
+
params.fromMs,
|
|
3540
|
+
params.toMs
|
|
3541
|
+
]
|
|
3542
|
+
);
|
|
3543
|
+
if (params.deleteEventsBeforeMs != null) {
|
|
3544
|
+
await client.query(
|
|
3545
|
+
`
|
|
3546
|
+
DELETE FROM hyperliquid_whale_trade_events
|
|
3547
|
+
WHERE universe_fingerprint = $1
|
|
3548
|
+
AND whale_registry_fingerprint = $2
|
|
3549
|
+
AND ts < to_timestamp($3/1000.0)
|
|
3550
|
+
`,
|
|
3551
|
+
[
|
|
3552
|
+
params.universeFingerprint,
|
|
3553
|
+
params.whaleRegistryFingerprint,
|
|
3554
|
+
params.deleteEventsBeforeMs
|
|
3555
|
+
]
|
|
3556
|
+
);
|
|
3557
|
+
}
|
|
3558
|
+
await client.query("COMMIT");
|
|
3559
|
+
return result.rowCount ?? 0;
|
|
3560
|
+
} catch (error) {
|
|
3561
|
+
await client.query("ROLLBACK");
|
|
3562
|
+
throw error;
|
|
3563
|
+
} finally {
|
|
3564
|
+
client.release();
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
async function getHyperliquidWhaleWalletCoverage(params) {
|
|
3568
|
+
await ensureHyperliquidWhaleSchema();
|
|
3569
|
+
const result = await getPool().query(
|
|
3570
|
+
`
|
|
3571
|
+
SELECT
|
|
3572
|
+
status,
|
|
3573
|
+
covered_from_ts,
|
|
3574
|
+
covered_to_ts,
|
|
3575
|
+
fills_count,
|
|
3576
|
+
error,
|
|
3577
|
+
checked_at
|
|
3578
|
+
FROM hyperliquid_whale_wallet_coverage
|
|
3579
|
+
WHERE universe_fingerprint = $1
|
|
3580
|
+
AND whale_registry_fingerprint = $2
|
|
3581
|
+
AND address = $3
|
|
3582
|
+
AND data_model_version = $6
|
|
3583
|
+
AND (
|
|
3584
|
+
(
|
|
3585
|
+
covered_from_ts <= to_timestamp($4/1000.0)
|
|
3586
|
+
AND covered_to_ts >= to_timestamp($5/1000.0)
|
|
3587
|
+
)
|
|
3588
|
+
OR (
|
|
3589
|
+
requested_from_ts = to_timestamp($4/1000.0)
|
|
3590
|
+
AND requested_to_ts = to_timestamp($5/1000.0)
|
|
3591
|
+
)
|
|
3592
|
+
)
|
|
3593
|
+
ORDER BY checked_at DESC
|
|
3594
|
+
LIMIT 1
|
|
3595
|
+
`,
|
|
3596
|
+
[
|
|
3597
|
+
params.universeFingerprint,
|
|
3598
|
+
params.whaleRegistryFingerprint,
|
|
3599
|
+
params.address.toLowerCase(),
|
|
3600
|
+
params.fromMs,
|
|
3601
|
+
params.toMs,
|
|
3602
|
+
import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
3603
|
+
]
|
|
3604
|
+
);
|
|
3605
|
+
const row = result.rows[0];
|
|
3606
|
+
if (!row) return null;
|
|
3607
|
+
return {
|
|
3608
|
+
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),
|
|
3609
|
+
coveredFromMs: row.covered_from_ts == null ? null : new Date(row.covered_from_ts).getTime(),
|
|
3610
|
+
coveredToMs: row.covered_to_ts == null ? null : new Date(row.covered_to_ts).getTime(),
|
|
3611
|
+
fillsCount: Number(row.fills_count) || 0,
|
|
3612
|
+
error: row.error == null ? null : String(row.error),
|
|
3613
|
+
checkedAt: new Date(row.checked_at)
|
|
3614
|
+
};
|
|
3615
|
+
}
|
|
3616
|
+
async function upsertHyperliquidWhaleWalletCoverage(params) {
|
|
3617
|
+
await ensureHyperliquidWhaleSchema();
|
|
3618
|
+
await getPool().query(
|
|
3619
|
+
`
|
|
3620
|
+
INSERT INTO hyperliquid_whale_wallet_coverage (
|
|
3621
|
+
universe_fingerprint,
|
|
3622
|
+
whale_registry_fingerprint,
|
|
3623
|
+
address,
|
|
3624
|
+
requested_from_ts,
|
|
3625
|
+
requested_to_ts,
|
|
3626
|
+
covered_from_ts,
|
|
3627
|
+
covered_to_ts,
|
|
3628
|
+
status,
|
|
3629
|
+
fills_count,
|
|
3630
|
+
error,
|
|
3631
|
+
data_model_version
|
|
3632
|
+
) VALUES (
|
|
3633
|
+
$1,
|
|
3634
|
+
$2,
|
|
3635
|
+
$3,
|
|
3636
|
+
to_timestamp($4/1000.0),
|
|
3637
|
+
to_timestamp($5/1000.0),
|
|
3638
|
+
CASE WHEN $6::double precision IS NULL THEN NULL ELSE to_timestamp($6/1000.0) END,
|
|
3639
|
+
CASE WHEN $7::double precision IS NULL THEN NULL ELSE to_timestamp($7/1000.0) END,
|
|
3640
|
+
$8,
|
|
3641
|
+
$9,
|
|
3642
|
+
$10,
|
|
3643
|
+
$11
|
|
3644
|
+
)
|
|
3645
|
+
ON CONFLICT (
|
|
3646
|
+
universe_fingerprint,
|
|
3647
|
+
whale_registry_fingerprint,
|
|
3648
|
+
address,
|
|
3649
|
+
requested_from_ts,
|
|
3650
|
+
requested_to_ts
|
|
3651
|
+
) DO UPDATE SET
|
|
3652
|
+
covered_from_ts = EXCLUDED.covered_from_ts,
|
|
3653
|
+
covered_to_ts = EXCLUDED.covered_to_ts,
|
|
3654
|
+
status = EXCLUDED.status,
|
|
3655
|
+
fills_count = EXCLUDED.fills_count,
|
|
3656
|
+
error = EXCLUDED.error,
|
|
3657
|
+
data_model_version = EXCLUDED.data_model_version,
|
|
3658
|
+
checked_at = now()
|
|
3659
|
+
`,
|
|
3660
|
+
[
|
|
3661
|
+
params.universeFingerprint,
|
|
3662
|
+
params.whaleRegistryFingerprint,
|
|
3663
|
+
params.address.toLowerCase(),
|
|
3664
|
+
params.fromMs,
|
|
3665
|
+
params.toMs,
|
|
3666
|
+
params.coveredFromMs,
|
|
3667
|
+
params.coveredToMs,
|
|
3668
|
+
params.status,
|
|
3669
|
+
params.fillsCount,
|
|
3670
|
+
params.error ?? null,
|
|
3671
|
+
import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
3672
|
+
]
|
|
3673
|
+
);
|
|
3674
|
+
}
|
|
3675
|
+
async function rebuildHyperliquidWhaleCoverageRows(params) {
|
|
3676
|
+
await ensureHyperliquidWhaleSchema();
|
|
3677
|
+
if (params.toMs <= params.fromMs) return 0;
|
|
3678
|
+
const minuteMs = 6e4;
|
|
3679
|
+
const defaultChunkMinutes = 7 * 24 * 60;
|
|
3680
|
+
const chunkMinutes = Number.isFinite(params.chunkMinutes) && Number(params.chunkMinutes) > 0 ? Math.floor(Number(params.chunkMinutes)) : defaultChunkMinutes;
|
|
3681
|
+
const chunkMs = chunkMinutes * minuteMs;
|
|
3682
|
+
const totalBuckets = Math.ceil((params.toMs - params.fromMs) / minuteMs);
|
|
3683
|
+
const totalChunks = Math.ceil((params.toMs - params.fromMs) / chunkMs);
|
|
3684
|
+
let completedBuckets = 0;
|
|
3685
|
+
let rows = 0;
|
|
3686
|
+
for (let chunkIndex = 0, chunkFromMs = params.fromMs; chunkFromMs < params.toMs; chunkIndex += 1, chunkFromMs += chunkMs) {
|
|
3687
|
+
const chunkToMs = Math.min(params.toMs, chunkFromMs + chunkMs);
|
|
3688
|
+
const result = await getPool().query(
|
|
3689
|
+
`
|
|
3690
|
+
WITH normalized_ranges AS (
|
|
3691
|
+
SELECT
|
|
3692
|
+
address,
|
|
3693
|
+
GREATEST(
|
|
3694
|
+
to_timestamp($3/1000.0),
|
|
3695
|
+
date_trunc('minute', covered_from_ts) +
|
|
3696
|
+
CASE
|
|
3697
|
+
WHEN covered_from_ts = date_trunc('minute', covered_from_ts)
|
|
3698
|
+
THEN interval '0 minutes'
|
|
3699
|
+
ELSE interval '1 minute'
|
|
3700
|
+
END
|
|
3701
|
+
) AS range_start,
|
|
3702
|
+
LEAST(
|
|
3703
|
+
to_timestamp($4/1000.0),
|
|
3704
|
+
date_trunc('minute', covered_to_ts)
|
|
3705
|
+
) AS range_end
|
|
3706
|
+
FROM hyperliquid_whale_wallet_coverage
|
|
3707
|
+
WHERE universe_fingerprint = $1
|
|
3708
|
+
AND whale_registry_fingerprint = $2
|
|
3709
|
+
AND data_model_version = $6
|
|
3710
|
+
AND status IN ('complete', 'truncated')
|
|
3711
|
+
AND covered_from_ts < to_timestamp($4/1000.0)
|
|
3712
|
+
AND covered_to_ts > to_timestamp($3/1000.0)
|
|
3713
|
+
), eligible_ranges AS (
|
|
3714
|
+
SELECT *
|
|
3715
|
+
FROM normalized_ranges
|
|
3716
|
+
WHERE range_start < range_end
|
|
3717
|
+
), ordered_ranges AS (
|
|
3718
|
+
SELECT
|
|
3719
|
+
*,
|
|
3720
|
+
MAX(range_end) OVER (
|
|
3721
|
+
PARTITION BY address
|
|
3722
|
+
ORDER BY range_start, range_end
|
|
3723
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
|
|
3724
|
+
) AS previous_max_end
|
|
3725
|
+
FROM eligible_ranges
|
|
3726
|
+
), marked_ranges AS (
|
|
3727
|
+
SELECT
|
|
3728
|
+
*,
|
|
3729
|
+
SUM(
|
|
3730
|
+
CASE
|
|
3731
|
+
WHEN previous_max_end IS NULL OR range_start > previous_max_end
|
|
3732
|
+
THEN 1
|
|
3733
|
+
ELSE 0
|
|
3734
|
+
END
|
|
3735
|
+
) OVER (
|
|
3736
|
+
PARTITION BY address
|
|
3737
|
+
ORDER BY range_start, range_end
|
|
3738
|
+
) AS range_group
|
|
3739
|
+
FROM ordered_ranges
|
|
3740
|
+
), merged_ranges AS (
|
|
3741
|
+
SELECT
|
|
3742
|
+
address,
|
|
3743
|
+
MIN(range_start) AS range_start,
|
|
3744
|
+
MAX(range_end) AS range_end
|
|
3745
|
+
FROM marked_ranges
|
|
3746
|
+
GROUP BY address, range_group
|
|
3747
|
+
), deltas AS (
|
|
3748
|
+
SELECT range_start AS ts, 1 AS delta
|
|
3749
|
+
FROM merged_ranges
|
|
3750
|
+
UNION ALL
|
|
3751
|
+
SELECT range_end AS ts, -1 AS delta
|
|
3752
|
+
FROM merged_ranges
|
|
3753
|
+
), bucket_deltas AS (
|
|
3754
|
+
SELECT ts, SUM(delta)::int AS delta
|
|
3755
|
+
FROM deltas
|
|
3756
|
+
GROUP BY ts
|
|
3757
|
+
), buckets AS (
|
|
3758
|
+
SELECT generate_series(
|
|
3759
|
+
to_timestamp($3/1000.0),
|
|
3760
|
+
to_timestamp($4/1000.0) - interval '1 minute',
|
|
3761
|
+
interval '1 minute'
|
|
3762
|
+
) AS ts
|
|
3763
|
+
), coverage AS (
|
|
3764
|
+
SELECT
|
|
3765
|
+
buckets.ts,
|
|
3766
|
+
SUM(COALESCE(bucket_deltas.delta, 0)) OVER (
|
|
3767
|
+
ORDER BY buckets.ts
|
|
3768
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
|
3769
|
+
)::int AS covered_whales
|
|
3770
|
+
FROM buckets
|
|
3771
|
+
LEFT JOIN bucket_deltas USING (ts)
|
|
3772
|
+
)
|
|
3773
|
+
INSERT INTO hyperliquid_whale_coverage_1m (
|
|
3774
|
+
ts,
|
|
3775
|
+
covered_whales,
|
|
3776
|
+
expected_whales,
|
|
3777
|
+
coverage_pct,
|
|
3778
|
+
universe_fingerprint,
|
|
3779
|
+
whale_registry_fingerprint,
|
|
3780
|
+
source,
|
|
3781
|
+
data_model_version
|
|
3782
|
+
)
|
|
3783
|
+
SELECT
|
|
3784
|
+
ts,
|
|
3785
|
+
covered_whales,
|
|
3786
|
+
$5,
|
|
3787
|
+
CASE WHEN $5 > 0 THEN covered_whales::double precision / $5 ELSE 0 END,
|
|
3788
|
+
$1,
|
|
3789
|
+
$2,
|
|
3790
|
+
'hyperliquid_user_fills',
|
|
3791
|
+
$6
|
|
3792
|
+
FROM coverage
|
|
3793
|
+
ON CONFLICT (
|
|
3794
|
+
universe_fingerprint,
|
|
3795
|
+
whale_registry_fingerprint,
|
|
3796
|
+
ts
|
|
3797
|
+
) DO UPDATE SET
|
|
3798
|
+
covered_whales = EXCLUDED.covered_whales,
|
|
3799
|
+
expected_whales = EXCLUDED.expected_whales,
|
|
3800
|
+
coverage_pct = EXCLUDED.coverage_pct,
|
|
3801
|
+
source = EXCLUDED.source,
|
|
3802
|
+
data_model_version = EXCLUDED.data_model_version,
|
|
3803
|
+
ingested_at = now()
|
|
3804
|
+
`,
|
|
3805
|
+
[
|
|
3806
|
+
params.universeFingerprint,
|
|
3807
|
+
params.whaleRegistryFingerprint,
|
|
3808
|
+
chunkFromMs,
|
|
3809
|
+
chunkToMs,
|
|
3810
|
+
params.expectedWhales,
|
|
3811
|
+
import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
3812
|
+
]
|
|
3813
|
+
);
|
|
3814
|
+
const chunkBuckets = Math.ceil((chunkToMs - chunkFromMs) / minuteMs);
|
|
3815
|
+
completedBuckets = Math.min(totalBuckets, completedBuckets + chunkBuckets);
|
|
3816
|
+
rows += result.rowCount ?? 0;
|
|
3817
|
+
params.onProgress?.({
|
|
3818
|
+
chunkIndex: chunkIndex + 1,
|
|
3819
|
+
totalChunks,
|
|
3820
|
+
completedBuckets,
|
|
3821
|
+
totalBuckets,
|
|
3822
|
+
rows
|
|
4090
3823
|
});
|
|
4091
3824
|
}
|
|
4092
|
-
return
|
|
3825
|
+
return rows;
|
|
4093
3826
|
}
|
|
4094
|
-
async function
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
3827
|
+
async function upsertHyperliquidWhaleCoverageRows(rows) {
|
|
3828
|
+
if (!rows.length) return;
|
|
3829
|
+
await ensureHyperliquidWhaleSchema();
|
|
3830
|
+
const cols = [
|
|
3831
|
+
"ts",
|
|
3832
|
+
"covered_whales",
|
|
3833
|
+
"expected_whales",
|
|
3834
|
+
"coverage_pct",
|
|
3835
|
+
"universe_fingerprint",
|
|
3836
|
+
"whale_registry_fingerprint",
|
|
3837
|
+
"source",
|
|
3838
|
+
"data_model_version"
|
|
3839
|
+
];
|
|
3840
|
+
const maxRows = getSafeBulkInsertRows(cols.length);
|
|
3841
|
+
if (rows.length > maxRows) {
|
|
3842
|
+
for (let index = 0; index < rows.length; index += maxRows) {
|
|
3843
|
+
await upsertHyperliquidWhaleCoverageRows(
|
|
3844
|
+
rows.slice(index, index + maxRows)
|
|
3845
|
+
);
|
|
4104
3846
|
}
|
|
3847
|
+
return;
|
|
4105
3848
|
}
|
|
4106
|
-
|
|
3849
|
+
const values = [];
|
|
3850
|
+
const tuples = rows.map((row, rowIndex) => {
|
|
3851
|
+
const offset = rowIndex * cols.length;
|
|
3852
|
+
values.push(
|
|
3853
|
+
row.ts,
|
|
3854
|
+
row.coveredWhales,
|
|
3855
|
+
row.expectedWhales,
|
|
3856
|
+
row.coveragePct,
|
|
3857
|
+
row.universeFingerprint,
|
|
3858
|
+
row.whaleRegistryFingerprint,
|
|
3859
|
+
row.source ?? null,
|
|
3860
|
+
row.dataModelVersion ?? import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
3861
|
+
);
|
|
3862
|
+
return `(${cols.map((_, colIndex) => `$${offset + colIndex + 1}`).join(",")})`;
|
|
3863
|
+
});
|
|
3864
|
+
await getPool().query(
|
|
3865
|
+
`
|
|
3866
|
+
INSERT INTO hyperliquid_whale_coverage_1m (${cols.join(",")})
|
|
3867
|
+
VALUES ${tuples.join(",")}
|
|
3868
|
+
ON CONFLICT (
|
|
3869
|
+
universe_fingerprint,
|
|
3870
|
+
whale_registry_fingerprint,
|
|
3871
|
+
ts
|
|
3872
|
+
) DO UPDATE SET
|
|
3873
|
+
covered_whales = EXCLUDED.covered_whales,
|
|
3874
|
+
expected_whales = EXCLUDED.expected_whales,
|
|
3875
|
+
coverage_pct = EXCLUDED.coverage_pct,
|
|
3876
|
+
source = EXCLUDED.source,
|
|
3877
|
+
data_model_version = EXCLUDED.data_model_version,
|
|
3878
|
+
ingested_at = now()
|
|
3879
|
+
`,
|
|
3880
|
+
values
|
|
3881
|
+
);
|
|
4107
3882
|
}
|
|
4108
|
-
async function
|
|
4109
|
-
|
|
4110
|
-
const
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
3883
|
+
async function getHyperliquidWhaleCoverageSeriesRows(params) {
|
|
3884
|
+
await prepareMarketContextSchemaForRead("hyperliquidWhales");
|
|
3885
|
+
const result = await queryMarketContext(
|
|
3886
|
+
`
|
|
3887
|
+
SELECT
|
|
3888
|
+
ts,
|
|
3889
|
+
covered_whales,
|
|
3890
|
+
expected_whales,
|
|
3891
|
+
coverage_pct
|
|
3892
|
+
FROM hyperliquid_whale_coverage_1m
|
|
3893
|
+
WHERE universe_fingerprint = $1
|
|
3894
|
+
AND whale_registry_fingerprint = $2
|
|
3895
|
+
AND data_model_version = $3
|
|
3896
|
+
AND ts >= to_timestamp($4/1000.0)
|
|
3897
|
+
AND ts < to_timestamp($5/1000.0)
|
|
3898
|
+
ORDER BY ts
|
|
3899
|
+
`,
|
|
3900
|
+
[
|
|
3901
|
+
params.universeFingerprint,
|
|
3902
|
+
params.whaleRegistryFingerprint,
|
|
3903
|
+
import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION,
|
|
3904
|
+
params.fromMs,
|
|
3905
|
+
params.toMs
|
|
3906
|
+
],
|
|
3907
|
+
params
|
|
3908
|
+
);
|
|
3909
|
+
return result.rows.map((row) => ({
|
|
3910
|
+
ts: new Date(row.ts),
|
|
3911
|
+
coveredWhales: Number(row.covered_whales) || 0,
|
|
3912
|
+
expectedWhales: Number(row.expected_whales) || 0,
|
|
3913
|
+
coveragePct: Number(row.coverage_pct) || 0
|
|
3914
|
+
}));
|
|
3915
|
+
}
|
|
3916
|
+
async function getHyperliquidWhaleFlowSeriesRows(params) {
|
|
3917
|
+
await prepareMarketContextSchemaForRead("hyperliquidWhales");
|
|
3918
|
+
const result = await queryMarketContext(
|
|
3919
|
+
`
|
|
3920
|
+
SELECT
|
|
3921
|
+
ts,
|
|
3922
|
+
trades,
|
|
3923
|
+
whale_sides,
|
|
3924
|
+
whale_addresses,
|
|
3925
|
+
buy_notional_usd,
|
|
3926
|
+
sell_notional_usd,
|
|
3927
|
+
position_aware_whale_sides,
|
|
3928
|
+
long_entry_whale_addresses,
|
|
3929
|
+
short_entry_whale_addresses,
|
|
3930
|
+
long_exit_whale_addresses,
|
|
3931
|
+
short_exit_whale_addresses,
|
|
3932
|
+
long_entry_notional_usd,
|
|
3933
|
+
short_entry_notional_usd,
|
|
3934
|
+
long_exit_notional_usd,
|
|
3935
|
+
short_exit_notional_usd
|
|
3936
|
+
FROM hyperliquid_whale_flow
|
|
3937
|
+
WHERE symbol = $1
|
|
3938
|
+
AND interval = '1m'
|
|
3939
|
+
AND universe_fingerprint = $2
|
|
3940
|
+
AND whale_registry_fingerprint = $3
|
|
3941
|
+
AND ts >= to_timestamp($4/1000.0)
|
|
3942
|
+
AND ts < to_timestamp($5/1000.0)
|
|
3943
|
+
ORDER BY ts
|
|
3944
|
+
`,
|
|
3945
|
+
[
|
|
3946
|
+
params.symbol,
|
|
3947
|
+
params.universeFingerprint,
|
|
3948
|
+
params.whaleRegistryFingerprint,
|
|
3949
|
+
params.fromMs,
|
|
3950
|
+
params.toMs
|
|
3951
|
+
],
|
|
3952
|
+
params
|
|
3953
|
+
);
|
|
3954
|
+
return result.rows.map((row) => ({
|
|
3955
|
+
ts: new Date(row.ts),
|
|
3956
|
+
trades: Number(row.trades) || 0,
|
|
3957
|
+
whaleSides: Number(row.whale_sides) || 0,
|
|
3958
|
+
whaleAddresses: Array.isArray(row.whale_addresses) ? row.whale_addresses.map(String) : [],
|
|
3959
|
+
buyNotionalUsd: Number(row.buy_notional_usd) || 0,
|
|
3960
|
+
sellNotionalUsd: Number(row.sell_notional_usd) || 0,
|
|
3961
|
+
positionAwareWhaleSides: Number(row.position_aware_whale_sides) || 0,
|
|
3962
|
+
longEntryWhaleAddresses: Array.isArray(row.long_entry_whale_addresses) ? row.long_entry_whale_addresses.map(String) : [],
|
|
3963
|
+
shortEntryWhaleAddresses: Array.isArray(row.short_entry_whale_addresses) ? row.short_entry_whale_addresses.map(String) : [],
|
|
3964
|
+
longExitWhaleAddresses: Array.isArray(row.long_exit_whale_addresses) ? row.long_exit_whale_addresses.map(String) : [],
|
|
3965
|
+
shortExitWhaleAddresses: Array.isArray(row.short_exit_whale_addresses) ? row.short_exit_whale_addresses.map(String) : [],
|
|
3966
|
+
longEntryNotionalUsd: Number(row.long_entry_notional_usd) || 0,
|
|
3967
|
+
shortEntryNotionalUsd: Number(row.short_entry_notional_usd) || 0,
|
|
3968
|
+
longExitNotionalUsd: Number(row.long_exit_notional_usd) || 0,
|
|
3969
|
+
shortExitNotionalUsd: Number(row.short_exit_notional_usd) || 0
|
|
3970
|
+
}));
|
|
4117
3971
|
}
|
|
4118
|
-
async function
|
|
4119
|
-
|
|
4120
|
-
const
|
|
4121
|
-
const
|
|
4122
|
-
const
|
|
4123
|
-
|
|
4124
|
-
|
|
3972
|
+
async function getHyperliquidWhaleFlowAggregate(params) {
|
|
3973
|
+
await prepareMarketContextSchemaForRead("hyperliquidWhales");
|
|
3974
|
+
const intervalMs = HYPERLIQUID_CONTEXT_INTERVAL_MS[params.interval];
|
|
3975
|
+
const expectedBuckets = Math.ceil(intervalMs / 6e4);
|
|
3976
|
+
const res = await queryMarketContext(
|
|
3977
|
+
`
|
|
3978
|
+
WITH coverage_rows AS (
|
|
3979
|
+
SELECT *
|
|
3980
|
+
FROM hyperliquid_whale_coverage_1m
|
|
3981
|
+
WHERE universe_fingerprint = $2
|
|
3982
|
+
AND whale_registry_fingerprint = $3
|
|
3983
|
+
AND data_model_version = $6
|
|
3984
|
+
AND ts >= to_timestamp(
|
|
3985
|
+
($4::double precision - $5::double precision) / 1000.0
|
|
3986
|
+
)
|
|
3987
|
+
AND ts < to_timestamp($4/1000.0)
|
|
3988
|
+
), coverage_summary AS (
|
|
3989
|
+
SELECT
|
|
3990
|
+
COUNT(*)::int AS coverage_buckets,
|
|
3991
|
+
MAX(ts) AS coverage_as_of_ts,
|
|
3992
|
+
MIN(covered_whales)::int AS covered_whales,
|
|
3993
|
+
MAX(expected_whales)::int AS expected_whales,
|
|
3994
|
+
MIN(coverage_pct) AS coverage_pct
|
|
3995
|
+
FROM coverage_rows
|
|
3996
|
+
), window_rows AS (
|
|
3997
|
+
SELECT *
|
|
3998
|
+
FROM hyperliquid_whale_flow
|
|
3999
|
+
WHERE symbol = $1
|
|
4000
|
+
AND interval = '1m'
|
|
4001
|
+
AND universe_fingerprint = $2
|
|
4002
|
+
AND whale_registry_fingerprint = $3
|
|
4003
|
+
AND ts >= to_timestamp(
|
|
4004
|
+
($4::double precision - $5::double precision) / 1000.0
|
|
4005
|
+
)
|
|
4006
|
+
AND ts < to_timestamp($4/1000.0)
|
|
4007
|
+
), unique_addresses AS (
|
|
4008
|
+
SELECT COUNT(DISTINCT address)::int AS unique_whales
|
|
4009
|
+
FROM window_rows
|
|
4010
|
+
CROSS JOIN LATERAL UNNEST(whale_addresses) AS expanded(address)
|
|
4011
|
+
), directional_counts AS (
|
|
4012
|
+
SELECT
|
|
4013
|
+
(
|
|
4014
|
+
SELECT COUNT(DISTINCT address)::int
|
|
4015
|
+
FROM window_rows
|
|
4016
|
+
CROSS JOIN LATERAL UNNEST(long_entry_whale_addresses) AS expanded(address)
|
|
4017
|
+
) AS long_entry_whales,
|
|
4018
|
+
(
|
|
4019
|
+
SELECT COUNT(DISTINCT address)::int
|
|
4020
|
+
FROM window_rows
|
|
4021
|
+
CROSS JOIN LATERAL UNNEST(short_entry_whale_addresses) AS expanded(address)
|
|
4022
|
+
) AS short_entry_whales,
|
|
4023
|
+
(
|
|
4024
|
+
SELECT COUNT(DISTINCT address)::int
|
|
4025
|
+
FROM window_rows
|
|
4026
|
+
CROSS JOIN LATERAL UNNEST(long_exit_whale_addresses) AS expanded(address)
|
|
4027
|
+
) AS long_exit_whales,
|
|
4028
|
+
(
|
|
4029
|
+
SELECT COUNT(DISTINCT address)::int
|
|
4030
|
+
FROM window_rows
|
|
4031
|
+
CROSS JOIN LATERAL UNNEST(short_exit_whale_addresses) AS expanded(address)
|
|
4032
|
+
) AS short_exit_whales
|
|
4033
|
+
)
|
|
4125
4034
|
SELECT
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4035
|
+
$1::text AS symbol,
|
|
4036
|
+
coverage_summary.coverage_as_of_ts AS as_of_ts,
|
|
4037
|
+
coverage_summary.coverage_buckets,
|
|
4038
|
+
coverage_summary.covered_whales,
|
|
4039
|
+
coverage_summary.expected_whales,
|
|
4040
|
+
coverage_summary.coverage_pct,
|
|
4041
|
+
COALESCE((SELECT SUM(trades) FROM window_rows), 0)::int AS trades,
|
|
4042
|
+
COALESCE((SELECT SUM(whale_sides) FROM window_rows), 0)::int AS whale_sides,
|
|
4043
|
+
COALESCE((SELECT unique_whales FROM unique_addresses), 0)::int AS unique_whales,
|
|
4044
|
+
COALESCE((SELECT SUM(buy_notional_usd) FROM window_rows), 0) AS buy_notional_usd,
|
|
4045
|
+
COALESCE((SELECT SUM(sell_notional_usd) FROM window_rows), 0) AS sell_notional_usd,
|
|
4046
|
+
COALESCE((SELECT SUM(net_notional_usd) FROM window_rows), 0) AS net_notional_usd,
|
|
4047
|
+
CASE
|
|
4048
|
+
WHEN COALESCE((SELECT SUM(buy_notional_usd + sell_notional_usd) FROM window_rows), 0) > 0
|
|
4049
|
+
THEN (SELECT SUM(buy_notional_usd) FROM window_rows) /
|
|
4050
|
+
(SELECT SUM(buy_notional_usd + sell_notional_usd) FROM window_rows)
|
|
4051
|
+
ELSE NULL
|
|
4052
|
+
END AS buy_share_pct,
|
|
4053
|
+
COALESCE((SELECT SUM(position_aware_whale_sides) FROM window_rows), 0)::int
|
|
4054
|
+
AS position_aware_whale_sides,
|
|
4055
|
+
CASE
|
|
4056
|
+
WHEN COALESCE((SELECT SUM(whale_sides) FROM window_rows), 0) > 0
|
|
4057
|
+
THEN COALESCE((SELECT SUM(position_aware_whale_sides) FROM window_rows), 0)::double precision /
|
|
4058
|
+
(SELECT SUM(whale_sides) FROM window_rows)
|
|
4059
|
+
ELSE 0
|
|
4060
|
+
END AS position_aware_pct,
|
|
4061
|
+
COALESCE((SELECT long_entry_whales FROM directional_counts), 0)::int AS long_entry_whales,
|
|
4062
|
+
COALESCE((SELECT short_entry_whales FROM directional_counts), 0)::int AS short_entry_whales,
|
|
4063
|
+
COALESCE((SELECT long_exit_whales FROM directional_counts), 0)::int AS long_exit_whales,
|
|
4064
|
+
COALESCE((SELECT short_exit_whales FROM directional_counts), 0)::int AS short_exit_whales,
|
|
4065
|
+
COALESCE((SELECT SUM(long_entry_notional_usd) FROM window_rows), 0)
|
|
4066
|
+
AS long_entry_notional_usd,
|
|
4067
|
+
COALESCE((SELECT SUM(short_entry_notional_usd) FROM window_rows), 0)
|
|
4068
|
+
AS short_entry_notional_usd,
|
|
4069
|
+
COALESCE((SELECT SUM(long_exit_notional_usd) FROM window_rows), 0)
|
|
4070
|
+
AS long_exit_notional_usd,
|
|
4071
|
+
COALESCE((SELECT SUM(short_exit_notional_usd) FROM window_rows), 0)
|
|
4072
|
+
AS short_exit_notional_usd,
|
|
4073
|
+
COALESCE((SELECT SUM(entry_net_notional_usd) FROM window_rows), 0)
|
|
4074
|
+
AS entry_net_notional_usd,
|
|
4075
|
+
CASE
|
|
4076
|
+
WHEN COALESCE((SELECT SUM(long_entry_notional_usd + short_entry_notional_usd) FROM window_rows), 0) > 0
|
|
4077
|
+
THEN (SELECT SUM(long_entry_notional_usd) FROM window_rows) /
|
|
4078
|
+
(SELECT SUM(long_entry_notional_usd + short_entry_notional_usd) FROM window_rows)
|
|
4079
|
+
ELSE NULL
|
|
4080
|
+
END AS entry_long_share_pct,
|
|
4081
|
+
(SELECT MAX(source) FROM window_rows) AS source
|
|
4082
|
+
FROM coverage_summary
|
|
4083
|
+
`,
|
|
4084
|
+
[
|
|
4085
|
+
params.symbol,
|
|
4086
|
+
params.universeFingerprint,
|
|
4087
|
+
params.whaleRegistryFingerprint,
|
|
4088
|
+
params.decisionTimeMs,
|
|
4089
|
+
intervalMs,
|
|
4090
|
+
import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
4091
|
+
],
|
|
4092
|
+
params
|
|
4093
|
+
);
|
|
4147
4094
|
const row = res.rows[0];
|
|
4148
|
-
if (!row)
|
|
4095
|
+
if (!row?.as_of_ts || Number(row.coverage_buckets) !== expectedBuckets || Number(row.covered_whales) <= 0) {
|
|
4096
|
+
return null;
|
|
4097
|
+
}
|
|
4098
|
+
const asOfTs = new Date(row.as_of_ts);
|
|
4099
|
+
const ageMs = params.decisionTimeMs - (asOfTs.getTime() + 6e4);
|
|
4149
4100
|
return {
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4101
|
+
symbol: params.symbol,
|
|
4102
|
+
interval: params.interval,
|
|
4103
|
+
asOfTs,
|
|
4104
|
+
windowEndTs: new Date(params.decisionTimeMs),
|
|
4105
|
+
trades: Number(row.trades) || 0,
|
|
4106
|
+
whaleSides: Number(row.whale_sides) || 0,
|
|
4107
|
+
uniqueWhales: Number(row.unique_whales) || 0,
|
|
4108
|
+
coveredWhales: Number(row.covered_whales) || 0,
|
|
4109
|
+
expectedWhales: Number(row.expected_whales) || 0,
|
|
4110
|
+
coveragePct: Number(row.coverage_pct) || 0,
|
|
4111
|
+
buyNotionalUsd: Number(row.buy_notional_usd) || 0,
|
|
4112
|
+
sellNotionalUsd: Number(row.sell_notional_usd) || 0,
|
|
4113
|
+
netNotionalUsd: Number(row.net_notional_usd) || 0,
|
|
4114
|
+
buySharePct: row.buy_share_pct == null ? null : Number(row.buy_share_pct) || 0,
|
|
4115
|
+
positionAwareWhaleSides: Number(row.position_aware_whale_sides) || 0,
|
|
4116
|
+
positionAwarePct: Number(row.position_aware_pct) || 0,
|
|
4117
|
+
longEntryWhales: Number(row.long_entry_whales) || 0,
|
|
4118
|
+
shortEntryWhales: Number(row.short_entry_whales) || 0,
|
|
4119
|
+
longExitWhales: Number(row.long_exit_whales) || 0,
|
|
4120
|
+
shortExitWhales: Number(row.short_exit_whales) || 0,
|
|
4121
|
+
longEntryNotionalUsd: Number(row.long_entry_notional_usd) || 0,
|
|
4122
|
+
shortEntryNotionalUsd: Number(row.short_entry_notional_usd) || 0,
|
|
4123
|
+
longExitNotionalUsd: Number(row.long_exit_notional_usd) || 0,
|
|
4124
|
+
shortExitNotionalUsd: Number(row.short_exit_notional_usd) || 0,
|
|
4125
|
+
entryNetNotionalUsd: Number(row.entry_net_notional_usd) || 0,
|
|
4126
|
+
entryLongSharePct: row.entry_long_share_pct == null ? null : Number(row.entry_long_share_pct) || 0,
|
|
4127
|
+
universeFingerprint: params.universeFingerprint,
|
|
4128
|
+
whaleRegistryFingerprint: params.whaleRegistryFingerprint,
|
|
4129
|
+
source: row.source == null ? null : String(row.source),
|
|
4130
|
+
ageMs,
|
|
4131
|
+
stale: ageMs < 0 || (params.maxAgeMs != null && Number.isFinite(params.maxAgeMs) ? ageMs > params.maxAgeMs : false)
|
|
4153
4132
|
};
|
|
4154
4133
|
}
|
|
4134
|
+
async function hasHyperliquidWhaleBackfillCoverage(params) {
|
|
4135
|
+
await ensureHyperliquidWhaleSchema();
|
|
4136
|
+
const expectedBuckets = Math.max(
|
|
4137
|
+
0,
|
|
4138
|
+
Math.ceil((params.toMs - params.fromMs) / 6e4)
|
|
4139
|
+
);
|
|
4140
|
+
const result = await getPool().query(
|
|
4141
|
+
`
|
|
4142
|
+
SELECT
|
|
4143
|
+
COUNT(*)::int AS buckets,
|
|
4144
|
+
COUNT(*) FILTER (
|
|
4145
|
+
WHERE covered_whales = expected_whales
|
|
4146
|
+
)::int AS complete_buckets
|
|
4147
|
+
FROM hyperliquid_whale_coverage_1m
|
|
4148
|
+
WHERE universe_fingerprint = $1
|
|
4149
|
+
AND whale_registry_fingerprint = $2
|
|
4150
|
+
AND data_model_version = $5
|
|
4151
|
+
AND ts >= to_timestamp($3/1000.0)
|
|
4152
|
+
AND ts < to_timestamp($4/1000.0)
|
|
4153
|
+
`,
|
|
4154
|
+
[
|
|
4155
|
+
params.universeFingerprint,
|
|
4156
|
+
params.whaleRegistryFingerprint,
|
|
4157
|
+
params.fromMs,
|
|
4158
|
+
params.toMs,
|
|
4159
|
+
import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
4160
|
+
]
|
|
4161
|
+
);
|
|
4162
|
+
return Number(result.rows[0]?.buckets) === expectedBuckets && Number(result.rows[0]?.complete_buckets) === expectedBuckets;
|
|
4163
|
+
}
|
|
4155
4164
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4156
4165
|
0 && (module.exports = {
|
|
4157
4166
|
applyDerivativesMetricCoverage,
|