@tradejs/infra 1.0.0
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/README.md +51 -0
- package/dist/chunk-LNFUOXDW.mjs +42 -0
- package/dist/files.d.mts +9 -0
- package/dist/files.d.ts +9 -0
- package/dist/files.js +103 -0
- package/dist/files.mjs +66 -0
- package/dist/http.d.mts +8 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.js +79 -0
- package/dist/http.mjs +54 -0
- package/dist/logger.d.mts +5 -0
- package/dist/logger.d.ts +5 -0
- package/dist/logger.js +66 -0
- package/dist/logger.mjs +6 -0
- package/dist/ml.d.mts +135 -0
- package/dist/ml.d.ts +135 -0
- package/dist/ml.js +1604 -0
- package/dist/ml.mjs +1512 -0
- package/dist/redis.d.mts +50 -0
- package/dist/redis.d.ts +50 -0
- package/dist/redis.js +337 -0
- package/dist/redis.mjs +296 -0
- package/dist/timescale.d.mts +68 -0
- package/dist/timescale.d.ts +68 -0
- package/dist/timescale.js +508 -0
- package/dist/timescale.mjs +471 -0
- package/package.json +60 -0
- package/proto/ml_infer.proto +19 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
// src/timescale.ts
|
|
2
|
+
import { Pool } from "pg";
|
|
3
|
+
var getPool = () => {
|
|
4
|
+
if (!global.__pgPool__) {
|
|
5
|
+
const host = process.env.PG_HOST || "127.0.0.1";
|
|
6
|
+
const port = Number(process.env.PG_PORT ?? 5432);
|
|
7
|
+
const user = process.env.PG_USER || "app";
|
|
8
|
+
const password = String(process.env.PG_PASSWORD ?? "app");
|
|
9
|
+
const database = process.env.PG_DATABASE || process.env.PG_DB || "app";
|
|
10
|
+
global.__pgPool__ = new Pool({
|
|
11
|
+
host,
|
|
12
|
+
port,
|
|
13
|
+
user,
|
|
14
|
+
password,
|
|
15
|
+
database,
|
|
16
|
+
max: 10,
|
|
17
|
+
idleTimeoutMillis: 3e4,
|
|
18
|
+
connectionTimeoutMillis: 5e3
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return global.__pgPool__;
|
|
22
|
+
};
|
|
23
|
+
var derivativesSchemaReady = false;
|
|
24
|
+
var spreadSchemaReady = false;
|
|
25
|
+
var toRows = (symbol, interval, data) => data.map((i) => ({
|
|
26
|
+
symbol,
|
|
27
|
+
interval,
|
|
28
|
+
ts: new Date(i.timestamp),
|
|
29
|
+
// ms -> Date
|
|
30
|
+
open: i.open,
|
|
31
|
+
high: i.high,
|
|
32
|
+
low: i.low,
|
|
33
|
+
close: i.close,
|
|
34
|
+
volume: i.volume ?? null,
|
|
35
|
+
turnover: i.turnover ?? null
|
|
36
|
+
}));
|
|
37
|
+
async function upsertCandles(rows) {
|
|
38
|
+
if (!rows.length) return;
|
|
39
|
+
const pool = getPool();
|
|
40
|
+
const cols = [
|
|
41
|
+
"symbol",
|
|
42
|
+
"interval",
|
|
43
|
+
"ts",
|
|
44
|
+
"open",
|
|
45
|
+
"high",
|
|
46
|
+
"low",
|
|
47
|
+
"close",
|
|
48
|
+
"volume",
|
|
49
|
+
"turnover"
|
|
50
|
+
];
|
|
51
|
+
const maxRows = Math.floor(65535 / cols.length);
|
|
52
|
+
if (rows.length > maxRows) {
|
|
53
|
+
for (let i = 0; i < rows.length; i += maxRows) {
|
|
54
|
+
await upsertCandles(rows.slice(i, i + maxRows));
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const valuesSql = rows.map(
|
|
59
|
+
(_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
|
|
60
|
+
).join(",");
|
|
61
|
+
const flat = rows.flatMap((r) => [
|
|
62
|
+
r.symbol,
|
|
63
|
+
r.interval,
|
|
64
|
+
r.ts,
|
|
65
|
+
r.open,
|
|
66
|
+
r.high,
|
|
67
|
+
r.low,
|
|
68
|
+
r.close,
|
|
69
|
+
r.volume ?? null,
|
|
70
|
+
r.turnover ?? null
|
|
71
|
+
]);
|
|
72
|
+
const sql = `
|
|
73
|
+
INSERT INTO candles (${cols.join(",")})
|
|
74
|
+
VALUES ${valuesSql}
|
|
75
|
+
ON CONFLICT (symbol, interval, ts) DO UPDATE SET
|
|
76
|
+
open = EXCLUDED.open,
|
|
77
|
+
high = EXCLUDED.high,
|
|
78
|
+
low = EXCLUDED.low,
|
|
79
|
+
close = EXCLUDED.close,
|
|
80
|
+
volume = COALESCE(EXCLUDED.volume, candles.volume),
|
|
81
|
+
turnover = COALESCE(EXCLUDED.turnover, candles.turnover)
|
|
82
|
+
`;
|
|
83
|
+
const client = await pool.connect();
|
|
84
|
+
try {
|
|
85
|
+
await client.query("BEGIN");
|
|
86
|
+
await client.query(sql, flat);
|
|
87
|
+
await client.query("COMMIT");
|
|
88
|
+
} catch (e) {
|
|
89
|
+
await client.query("ROLLBACK");
|
|
90
|
+
throw e;
|
|
91
|
+
} finally {
|
|
92
|
+
client.release();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
var ensureDerivativesSchema = async () => {
|
|
96
|
+
if (derivativesSchemaReady) return;
|
|
97
|
+
const pool = getPool();
|
|
98
|
+
await pool.query("CREATE EXTENSION IF NOT EXISTS timescaledb");
|
|
99
|
+
await pool.query(`
|
|
100
|
+
CREATE TABLE IF NOT EXISTS derivatives_market (
|
|
101
|
+
symbol text NOT NULL,
|
|
102
|
+
interval text NOT NULL,
|
|
103
|
+
ts timestamptz NOT NULL,
|
|
104
|
+
open_interest double precision,
|
|
105
|
+
funding_rate double precision,
|
|
106
|
+
liq_long double precision,
|
|
107
|
+
liq_short double precision,
|
|
108
|
+
liq_total double precision,
|
|
109
|
+
source text,
|
|
110
|
+
ingested_at timestamptz NOT NULL DEFAULT now(),
|
|
111
|
+
PRIMARY KEY (symbol, interval, ts)
|
|
112
|
+
)
|
|
113
|
+
`);
|
|
114
|
+
await pool.query(`
|
|
115
|
+
SELECT create_hypertable(
|
|
116
|
+
'derivatives_market',
|
|
117
|
+
'ts',
|
|
118
|
+
if_not_exists => TRUE,
|
|
119
|
+
chunk_time_interval => interval '14 days'
|
|
120
|
+
)
|
|
121
|
+
`);
|
|
122
|
+
await pool.query(`
|
|
123
|
+
CREATE INDEX IF NOT EXISTS derivatives_market_symbol_tf_ts_idx
|
|
124
|
+
ON derivatives_market (symbol, interval, ts DESC)
|
|
125
|
+
`);
|
|
126
|
+
derivativesSchemaReady = true;
|
|
127
|
+
};
|
|
128
|
+
var ensureSpreadSchema = async () => {
|
|
129
|
+
if (spreadSchemaReady) return;
|
|
130
|
+
const pool = getPool();
|
|
131
|
+
await pool.query("CREATE EXTENSION IF NOT EXISTS timescaledb");
|
|
132
|
+
await pool.query(`
|
|
133
|
+
CREATE TABLE IF NOT EXISTS market_spread (
|
|
134
|
+
symbol text NOT NULL,
|
|
135
|
+
interval text NOT NULL,
|
|
136
|
+
ts timestamptz NOT NULL,
|
|
137
|
+
binance_price double precision,
|
|
138
|
+
coinbase_price double precision,
|
|
139
|
+
spread double precision,
|
|
140
|
+
source text,
|
|
141
|
+
ingested_at timestamptz NOT NULL DEFAULT now(),
|
|
142
|
+
PRIMARY KEY (symbol, interval, ts)
|
|
143
|
+
)
|
|
144
|
+
`);
|
|
145
|
+
await pool.query(`
|
|
146
|
+
SELECT create_hypertable(
|
|
147
|
+
'market_spread',
|
|
148
|
+
'ts',
|
|
149
|
+
if_not_exists => TRUE,
|
|
150
|
+
chunk_time_interval => interval '14 days'
|
|
151
|
+
)
|
|
152
|
+
`);
|
|
153
|
+
await pool.query(`
|
|
154
|
+
CREATE INDEX IF NOT EXISTS market_spread_symbol_tf_ts_idx
|
|
155
|
+
ON market_spread (symbol, interval, ts DESC)
|
|
156
|
+
`);
|
|
157
|
+
spreadSchemaReady = true;
|
|
158
|
+
};
|
|
159
|
+
async function upsertDerivatives(rows) {
|
|
160
|
+
if (!rows.length) return;
|
|
161
|
+
await ensureDerivativesSchema();
|
|
162
|
+
const pool = getPool();
|
|
163
|
+
const cols = [
|
|
164
|
+
"symbol",
|
|
165
|
+
"interval",
|
|
166
|
+
"ts",
|
|
167
|
+
"open_interest",
|
|
168
|
+
"funding_rate",
|
|
169
|
+
"liq_long",
|
|
170
|
+
"liq_short",
|
|
171
|
+
"liq_total",
|
|
172
|
+
"source"
|
|
173
|
+
];
|
|
174
|
+
const maxRows = Math.floor(65535 / cols.length);
|
|
175
|
+
if (rows.length > maxRows) {
|
|
176
|
+
for (let i = 0; i < rows.length; i += maxRows) {
|
|
177
|
+
await upsertDerivatives(rows.slice(i, i + maxRows));
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const valuesSql = rows.map(
|
|
182
|
+
(_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
|
|
183
|
+
).join(",");
|
|
184
|
+
const flat = rows.flatMap((row) => [
|
|
185
|
+
row.symbol,
|
|
186
|
+
row.interval,
|
|
187
|
+
row.ts,
|
|
188
|
+
row.openInterest ?? null,
|
|
189
|
+
row.fundingRate ?? null,
|
|
190
|
+
row.liqLong ?? null,
|
|
191
|
+
row.liqShort ?? null,
|
|
192
|
+
row.liqTotal ?? null,
|
|
193
|
+
row.source ?? null
|
|
194
|
+
]);
|
|
195
|
+
const sql = `
|
|
196
|
+
INSERT INTO derivatives_market (${cols.join(",")})
|
|
197
|
+
VALUES ${valuesSql}
|
|
198
|
+
ON CONFLICT (symbol, interval, ts) DO UPDATE SET
|
|
199
|
+
open_interest = COALESCE(EXCLUDED.open_interest, derivatives_market.open_interest),
|
|
200
|
+
funding_rate = COALESCE(EXCLUDED.funding_rate, derivatives_market.funding_rate),
|
|
201
|
+
liq_long = COALESCE(EXCLUDED.liq_long, derivatives_market.liq_long),
|
|
202
|
+
liq_short = COALESCE(EXCLUDED.liq_short, derivatives_market.liq_short),
|
|
203
|
+
liq_total = COALESCE(EXCLUDED.liq_total, derivatives_market.liq_total),
|
|
204
|
+
source = COALESCE(EXCLUDED.source, derivatives_market.source),
|
|
205
|
+
ingested_at = now()
|
|
206
|
+
`;
|
|
207
|
+
await pool.query(sql, flat);
|
|
208
|
+
}
|
|
209
|
+
async function getDerivativesRangeForSymbols(symbols, interval, startMs, endMs) {
|
|
210
|
+
if (!symbols.length)
|
|
211
|
+
return [];
|
|
212
|
+
await ensureDerivativesSchema();
|
|
213
|
+
const pool = getPool();
|
|
214
|
+
const sql = `
|
|
215
|
+
SELECT symbol, interval, ts, open_interest, funding_rate, liq_long, liq_short, liq_total
|
|
216
|
+
FROM derivatives_market
|
|
217
|
+
WHERE symbol = ANY($1)
|
|
218
|
+
AND interval = $2
|
|
219
|
+
AND ts >= to_timestamp($3/1000.0)
|
|
220
|
+
AND ts <= to_timestamp($4/1000.0)
|
|
221
|
+
ORDER BY symbol ASC, ts ASC
|
|
222
|
+
`;
|
|
223
|
+
const res = await pool.query(sql, [symbols, interval, startMs, endMs]);
|
|
224
|
+
return res.rows;
|
|
225
|
+
}
|
|
226
|
+
async function getDerivativesSummary(hours = 24, limit = 500) {
|
|
227
|
+
await ensureDerivativesSchema();
|
|
228
|
+
const pool = getPool();
|
|
229
|
+
const cappedHours = Math.max(1, Math.min(24 * 30, hours));
|
|
230
|
+
const cappedLimit = Math.max(50, Math.min(5e3, limit));
|
|
231
|
+
const rowsQ = await pool.query(
|
|
232
|
+
`
|
|
233
|
+
SELECT symbol, interval, ts, open_interest, funding_rate, liq_long, liq_short, liq_total
|
|
234
|
+
FROM derivatives_market
|
|
235
|
+
WHERE ts >= now() - ($1 || ' hours')::interval
|
|
236
|
+
ORDER BY ts DESC
|
|
237
|
+
LIMIT $2
|
|
238
|
+
`,
|
|
239
|
+
[String(cappedHours), cappedLimit]
|
|
240
|
+
);
|
|
241
|
+
const aggQ = await pool.query(
|
|
242
|
+
`
|
|
243
|
+
SELECT
|
|
244
|
+
symbol,
|
|
245
|
+
interval,
|
|
246
|
+
COUNT(*)::int AS points,
|
|
247
|
+
MAX(ts) AS last_ts,
|
|
248
|
+
AVG(open_interest) AS avg_open_interest,
|
|
249
|
+
AVG(funding_rate) AS avg_funding_rate,
|
|
250
|
+
SUM(COALESCE(liq_total, 0)) AS sum_liq_total
|
|
251
|
+
FROM derivatives_market
|
|
252
|
+
WHERE ts >= now() - ($1 || ' hours')::interval
|
|
253
|
+
GROUP BY symbol, interval
|
|
254
|
+
ORDER BY points DESC, symbol ASC
|
|
255
|
+
LIMIT 500
|
|
256
|
+
`,
|
|
257
|
+
[String(cappedHours)]
|
|
258
|
+
);
|
|
259
|
+
return {
|
|
260
|
+
rows: rowsQ.rows,
|
|
261
|
+
aggregates: aggQ.rows,
|
|
262
|
+
hours: cappedHours
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
async function upsertSpreadRows(rows) {
|
|
266
|
+
if (!rows.length) return;
|
|
267
|
+
await ensureSpreadSchema();
|
|
268
|
+
const pool = getPool();
|
|
269
|
+
const cols = [
|
|
270
|
+
"symbol",
|
|
271
|
+
"interval",
|
|
272
|
+
"ts",
|
|
273
|
+
"binance_price",
|
|
274
|
+
"coinbase_price",
|
|
275
|
+
"spread",
|
|
276
|
+
"source"
|
|
277
|
+
];
|
|
278
|
+
const maxRows = Math.floor(65535 / cols.length);
|
|
279
|
+
if (rows.length > maxRows) {
|
|
280
|
+
for (let i = 0; i < rows.length; i += maxRows) {
|
|
281
|
+
await upsertSpreadRows(rows.slice(i, i + maxRows));
|
|
282
|
+
}
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const valuesSql = rows.map(
|
|
286
|
+
(_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
|
|
287
|
+
).join(",");
|
|
288
|
+
const flat = rows.flatMap((row) => [
|
|
289
|
+
row.symbol,
|
|
290
|
+
row.interval,
|
|
291
|
+
row.ts,
|
|
292
|
+
row.binancePrice ?? null,
|
|
293
|
+
row.coinbasePrice ?? null,
|
|
294
|
+
row.spread ?? null,
|
|
295
|
+
row.source ?? null
|
|
296
|
+
]);
|
|
297
|
+
const sql = `
|
|
298
|
+
INSERT INTO market_spread (${cols.join(",")})
|
|
299
|
+
VALUES ${valuesSql}
|
|
300
|
+
ON CONFLICT (symbol, interval, ts) DO UPDATE SET
|
|
301
|
+
binance_price = COALESCE(EXCLUDED.binance_price, market_spread.binance_price),
|
|
302
|
+
coinbase_price = COALESCE(EXCLUDED.coinbase_price, market_spread.coinbase_price),
|
|
303
|
+
spread = COALESCE(EXCLUDED.spread, market_spread.spread),
|
|
304
|
+
source = COALESCE(EXCLUDED.source, market_spread.source),
|
|
305
|
+
ingested_at = now()
|
|
306
|
+
`;
|
|
307
|
+
await pool.query(sql, flat);
|
|
308
|
+
}
|
|
309
|
+
async function getSpreadRangeForSymbols(symbols, interval, startMs, endMs) {
|
|
310
|
+
if (!symbols.length) {
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
await ensureSpreadSchema();
|
|
314
|
+
const pool = getPool();
|
|
315
|
+
const sql = `
|
|
316
|
+
SELECT symbol, interval, ts, binance_price, coinbase_price, spread
|
|
317
|
+
FROM market_spread
|
|
318
|
+
WHERE symbol = ANY($1)
|
|
319
|
+
AND interval = $2
|
|
320
|
+
AND ts >= to_timestamp($3/1000.0)
|
|
321
|
+
AND ts <= to_timestamp($4/1000.0)
|
|
322
|
+
ORDER BY symbol ASC, ts ASC
|
|
323
|
+
`;
|
|
324
|
+
const res = await pool.query(sql, [symbols, interval, startMs, endMs]);
|
|
325
|
+
return res.rows;
|
|
326
|
+
}
|
|
327
|
+
async function getSpreadSummary(hours = 24, limit = 500) {
|
|
328
|
+
await ensureSpreadSchema();
|
|
329
|
+
const pool = getPool();
|
|
330
|
+
const cappedHours = Math.max(1, Math.min(24 * 30, hours));
|
|
331
|
+
const cappedLimit = Math.max(50, Math.min(5e3, limit));
|
|
332
|
+
const rowsQ = await pool.query(
|
|
333
|
+
`
|
|
334
|
+
SELECT symbol, interval, ts, binance_price, coinbase_price, spread
|
|
335
|
+
FROM market_spread
|
|
336
|
+
WHERE ts >= now() - ($1 || ' hours')::interval
|
|
337
|
+
ORDER BY ts DESC
|
|
338
|
+
LIMIT $2
|
|
339
|
+
`,
|
|
340
|
+
[String(cappedHours), cappedLimit]
|
|
341
|
+
);
|
|
342
|
+
const aggQ = await pool.query(
|
|
343
|
+
`
|
|
344
|
+
SELECT
|
|
345
|
+
symbol,
|
|
346
|
+
interval,
|
|
347
|
+
COUNT(*)::int AS points,
|
|
348
|
+
MAX(ts) AS last_ts,
|
|
349
|
+
AVG(spread) AS avg_spread,
|
|
350
|
+
STDDEV_POP(spread) AS std_spread
|
|
351
|
+
FROM market_spread
|
|
352
|
+
WHERE ts >= now() - ($1 || ' hours')::interval
|
|
353
|
+
GROUP BY symbol, interval
|
|
354
|
+
ORDER BY points DESC, symbol ASC
|
|
355
|
+
LIMIT 500
|
|
356
|
+
`,
|
|
357
|
+
[String(cappedHours)]
|
|
358
|
+
);
|
|
359
|
+
return {
|
|
360
|
+
rows: rowsQ.rows,
|
|
361
|
+
aggregates: aggQ.rows,
|
|
362
|
+
hours: cappedHours
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
async function getCandlesRange(symbol, interval, startMs, endMs) {
|
|
366
|
+
const pool = getPool();
|
|
367
|
+
const sql = `
|
|
368
|
+
SELECT symbol, interval, ts,
|
|
369
|
+
open, high, low, close, volume, turnover
|
|
370
|
+
FROM candles
|
|
371
|
+
WHERE symbol = $1 AND interval = $2
|
|
372
|
+
AND ts >= to_timestamp($3/1000.0)
|
|
373
|
+
AND ts <= to_timestamp($4/1000.0)
|
|
374
|
+
ORDER BY ts ASC
|
|
375
|
+
`;
|
|
376
|
+
const res = await pool.query(sql, [symbol, interval, startMs, endMs]);
|
|
377
|
+
return res.rows;
|
|
378
|
+
}
|
|
379
|
+
async function getDataEdges(symbol, interval) {
|
|
380
|
+
const pool = getPool();
|
|
381
|
+
const sqlMin = `
|
|
382
|
+
SELECT extract(epoch from ts)*1000 AS ms
|
|
383
|
+
FROM candles
|
|
384
|
+
WHERE symbol=$1 AND interval=$2
|
|
385
|
+
ORDER BY ts ASC
|
|
386
|
+
LIMIT 1
|
|
387
|
+
`;
|
|
388
|
+
const sqlMax = `
|
|
389
|
+
SELECT extract(epoch from ts)*1000 AS ms
|
|
390
|
+
FROM candles
|
|
391
|
+
WHERE symbol=$1 AND interval=$2
|
|
392
|
+
ORDER BY ts DESC
|
|
393
|
+
LIMIT 1
|
|
394
|
+
`;
|
|
395
|
+
const [minQ, maxQ] = await Promise.all([
|
|
396
|
+
pool.query(sqlMin, [symbol, interval]),
|
|
397
|
+
pool.query(sqlMax, [symbol, interval])
|
|
398
|
+
]);
|
|
399
|
+
const minRaw = minQ.rows[0]?.ms;
|
|
400
|
+
const maxRaw = maxQ.rows[0]?.ms;
|
|
401
|
+
const min = Number.isFinite(Number(minRaw)) ? Number(minRaw) : void 0;
|
|
402
|
+
const max = Number.isFinite(Number(maxRaw)) ? Number(maxRaw) : void 0;
|
|
403
|
+
return { min, max };
|
|
404
|
+
}
|
|
405
|
+
async function waitForDbReady(attempts = 20, delayMs = 1e3) {
|
|
406
|
+
const pool = getPool();
|
|
407
|
+
let lastError;
|
|
408
|
+
for (let i = 0; i < attempts; i++) {
|
|
409
|
+
try {
|
|
410
|
+
await pool.query("SELECT 1");
|
|
411
|
+
return;
|
|
412
|
+
} catch (e) {
|
|
413
|
+
lastError = e;
|
|
414
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
throw lastError;
|
|
418
|
+
}
|
|
419
|
+
async function deleteCandles(symbol, interval) {
|
|
420
|
+
const pool = getPool();
|
|
421
|
+
const sql = `
|
|
422
|
+
DELETE FROM candles
|
|
423
|
+
WHERE symbol = $1 AND interval = $2
|
|
424
|
+
`;
|
|
425
|
+
await pool.query(sql, [symbol, interval]);
|
|
426
|
+
}
|
|
427
|
+
async function findContinuityGap(symbol, interval) {
|
|
428
|
+
const pool = getPool();
|
|
429
|
+
const expectedSeconds = interval * 60;
|
|
430
|
+
const sql = `
|
|
431
|
+
WITH ordered AS (
|
|
432
|
+
SELECT
|
|
433
|
+
ts,
|
|
434
|
+
LAG(ts) OVER (ORDER BY ts) AS prev_ts
|
|
435
|
+
FROM candles
|
|
436
|
+
WHERE symbol = $1 AND interval = $2
|
|
437
|
+
)
|
|
438
|
+
SELECT
|
|
439
|
+
ts,
|
|
440
|
+
prev_ts,
|
|
441
|
+
EXTRACT(EPOCH FROM (ts - prev_ts))::int AS diff_seconds
|
|
442
|
+
FROM ordered
|
|
443
|
+
WHERE prev_ts IS NOT NULL
|
|
444
|
+
AND EXTRACT(EPOCH FROM (ts - prev_ts))::int <> $3
|
|
445
|
+
ORDER BY ts ASC
|
|
446
|
+
LIMIT 1
|
|
447
|
+
`;
|
|
448
|
+
const res = await pool.query(sql, [symbol, interval, expectedSeconds]);
|
|
449
|
+
const row = res.rows[0];
|
|
450
|
+
if (!row) return null;
|
|
451
|
+
return {
|
|
452
|
+
ts: new Date(row.ts).getTime(),
|
|
453
|
+
prevTs: new Date(row.prev_ts).getTime(),
|
|
454
|
+
diffSeconds: row.diff_seconds
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
export {
|
|
458
|
+
deleteCandles,
|
|
459
|
+
findContinuityGap,
|
|
460
|
+
getCandlesRange,
|
|
461
|
+
getDataEdges,
|
|
462
|
+
getDerivativesRangeForSymbols,
|
|
463
|
+
getDerivativesSummary,
|
|
464
|
+
getSpreadRangeForSymbols,
|
|
465
|
+
getSpreadSummary,
|
|
466
|
+
toRows,
|
|
467
|
+
upsertCandles,
|
|
468
|
+
upsertDerivatives,
|
|
469
|
+
upsertSpreadRows,
|
|
470
|
+
waitForDbReady
|
|
471
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tradejs/infra",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Server-only infrastructure adapters for Redis, Timescale, ML, logging, and IO.",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"proto"
|
|
8
|
+
],
|
|
9
|
+
"exports": {
|
|
10
|
+
"./files": {
|
|
11
|
+
"types": "./dist/files.d.ts",
|
|
12
|
+
"import": "./dist/files.mjs",
|
|
13
|
+
"require": "./dist/files.js"
|
|
14
|
+
},
|
|
15
|
+
"./http": {
|
|
16
|
+
"types": "./dist/http.d.ts",
|
|
17
|
+
"import": "./dist/http.mjs",
|
|
18
|
+
"require": "./dist/http.js"
|
|
19
|
+
},
|
|
20
|
+
"./logger": {
|
|
21
|
+
"types": "./dist/logger.d.ts",
|
|
22
|
+
"import": "./dist/logger.mjs",
|
|
23
|
+
"require": "./dist/logger.js"
|
|
24
|
+
},
|
|
25
|
+
"./ml": {
|
|
26
|
+
"types": "./dist/ml.d.ts",
|
|
27
|
+
"import": "./dist/ml.mjs",
|
|
28
|
+
"require": "./dist/ml.js"
|
|
29
|
+
},
|
|
30
|
+
"./redis": {
|
|
31
|
+
"types": "./dist/redis.d.ts",
|
|
32
|
+
"import": "./dist/redis.mjs",
|
|
33
|
+
"require": "./dist/redis.js"
|
|
34
|
+
},
|
|
35
|
+
"./timescale": {
|
|
36
|
+
"types": "./dist/timescale.d.ts",
|
|
37
|
+
"import": "./dist/timescale.mjs",
|
|
38
|
+
"require": "./dist/timescale.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@grpc/grpc-js": "^1.10.7",
|
|
43
|
+
"@grpc/proto-loader": "^0.7.13",
|
|
44
|
+
"@tradejs/types": "^1.0.0",
|
|
45
|
+
"chalk": "4.1.2",
|
|
46
|
+
"ioredis": "5.8.0",
|
|
47
|
+
"pg": "8.16.3",
|
|
48
|
+
"winston": "3.17.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"tsup": "^8.5.1",
|
|
52
|
+
"typescript": "^5.1"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup",
|
|
56
|
+
"typecheck": "tsc -p ./tsconfig.json --noEmit"
|
|
57
|
+
},
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"author": "aleksnick (https://github.com/aleksnick)"
|
|
60
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package ml_infer;
|
|
4
|
+
|
|
5
|
+
service MlInfer {
|
|
6
|
+
rpc Predict (PredictRequest) returns (PredictResponse) {}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
message PredictRequest {
|
|
10
|
+
string strategy = 1;
|
|
11
|
+
map<string, double> features = 2;
|
|
12
|
+
double threshold = 3;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
message PredictResponse {
|
|
16
|
+
double probability = 1;
|
|
17
|
+
double threshold = 2;
|
|
18
|
+
bool passed = 3;
|
|
19
|
+
}
|