@yuants/app-virtual-exchange 0.11.7 → 0.12.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/dist/series-data/fifo-queue.js +25 -0
- package/dist/series-data/fifo-queue.js.map +1 -0
- package/dist/series-data/index.js +2 -0
- package/dist/series-data/index.js.map +1 -0
- package/dist/series-data/scheduler.js +568 -0
- package/dist/series-data/scheduler.js.map +1 -0
- package/lib/series-data/fifo-queue.d.ts +8 -0
- package/lib/series-data/fifo-queue.d.ts.map +1 -0
- package/lib/series-data/fifo-queue.js +29 -0
- package/lib/series-data/fifo-queue.js.map +1 -0
- package/lib/series-data/index.d.ts +2 -0
- package/lib/series-data/index.d.ts.map +1 -0
- package/lib/series-data/index.js +4 -0
- package/lib/series-data/index.js.map +1 -0
- package/lib/series-data/scheduler.d.ts +2 -0
- package/lib/series-data/scheduler.d.ts.map +1 -0
- package/lib/series-data/scheduler.js +570 -0
- package/lib/series-data/scheduler.js.map +1 -0
- package/package.json +3 -1
- package/temp/package-deps.json +10 -4
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var _a, _b;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const data_interest_rate_1 = require("@yuants/data-interest-rate");
|
|
5
|
+
const data_ohlc_1 = require("@yuants/data-ohlc");
|
|
6
|
+
const exchange_1 = require("@yuants/exchange");
|
|
7
|
+
const protocol_1 = require("@yuants/protocol");
|
|
8
|
+
const sql_1 = require("@yuants/sql");
|
|
9
|
+
const utils_1 = require("@yuants/utils");
|
|
10
|
+
const rxjs_1 = require("rxjs");
|
|
11
|
+
const fifo_queue_1 = require("./fifo-queue");
|
|
12
|
+
const terminal = protocol_1.Terminal.fromNodeEnv();
|
|
13
|
+
const isEnabled = process.env.VEX_SERIES_DATA_ENABLED === '1';
|
|
14
|
+
const onlyProductIdPrefix = ((_a = process.env.VEX_SERIES_DATA_ONLY_PRODUCT_ID_PREFIX) !== null && _a !== void 0 ? _a : '').trim();
|
|
15
|
+
const CONFIG = {
|
|
16
|
+
tickIntervalMs: 1000,
|
|
17
|
+
scanFullLoopIntervalMs: 5 * 60000,
|
|
18
|
+
/**
|
|
19
|
+
* Maximum number of inflight series data ingest requests.
|
|
20
|
+
*/
|
|
21
|
+
maxInflight: 20,
|
|
22
|
+
/**
|
|
23
|
+
* Only run tail jobs when the global head backlog is below this threshold.
|
|
24
|
+
*/
|
|
25
|
+
tailOnlyWhenGlobalHeadBelow: 20,
|
|
26
|
+
defaultInterestRateHeadLagMs: 8 * 60 * 60000,
|
|
27
|
+
defaultForwardSeedWindowMs: 24 * 60 * 60000,
|
|
28
|
+
maxBackoffMs: 5 * 60000,
|
|
29
|
+
backoffStepMs: 5000,
|
|
30
|
+
};
|
|
31
|
+
const capabilities = [];
|
|
32
|
+
const mapCapKeyToCapability = new Map();
|
|
33
|
+
const mapCapKeyToState = new Map();
|
|
34
|
+
const mapSeriesKeyToState = new Map();
|
|
35
|
+
let inflight = 0;
|
|
36
|
+
let scanIndex = 0;
|
|
37
|
+
let capRunIndex = 0;
|
|
38
|
+
const LOG_QUEUE_INTERVAL_MS = Number((_b = process.env.VEX_SERIES_DATA_LOG_QUEUE_INTERVAL_MS) !== null && _b !== void 0 ? _b : '10000');
|
|
39
|
+
const getOrCreateCapState = (capKey) => {
|
|
40
|
+
const existing = mapCapKeyToState.get(capKey);
|
|
41
|
+
if (existing)
|
|
42
|
+
return existing;
|
|
43
|
+
const next = {
|
|
44
|
+
capKey,
|
|
45
|
+
headQueue: (0, fifo_queue_1.createFifoQueue)(),
|
|
46
|
+
tailQueue: (0, fifo_queue_1.createFifoQueue)(),
|
|
47
|
+
pendingHead: new Set(),
|
|
48
|
+
pendingTail: new Set(),
|
|
49
|
+
inflight: false,
|
|
50
|
+
nextEligibleAt: 0,
|
|
51
|
+
backoffMs: 0,
|
|
52
|
+
};
|
|
53
|
+
mapCapKeyToState.set(capKey, next);
|
|
54
|
+
return next;
|
|
55
|
+
};
|
|
56
|
+
const computeSeriesId = (seriesType, product_id, duration) => {
|
|
57
|
+
if (seriesType === 'ohlc')
|
|
58
|
+
return (0, data_ohlc_1.encodeOHLCSeriesId)(product_id, duration !== null && duration !== void 0 ? duration : '');
|
|
59
|
+
return (0, data_interest_rate_1.encodeInterestRateSeriesId)(product_id);
|
|
60
|
+
};
|
|
61
|
+
const getOrCreateSeriesState = (params) => {
|
|
62
|
+
const { capKey, seriesType, product_id, duration, direction } = params;
|
|
63
|
+
const table_name = seriesType === 'ohlc' ? 'ohlc_v2' : 'interest_rate';
|
|
64
|
+
const series_id = computeSeriesId(seriesType, product_id, duration);
|
|
65
|
+
const seriesKey = (0, utils_1.encodePath)(table_name, direction, product_id, duration !== null && duration !== void 0 ? duration : '');
|
|
66
|
+
const existing = mapSeriesKeyToState.get(seriesKey);
|
|
67
|
+
if (existing) {
|
|
68
|
+
existing.capKey = capKey;
|
|
69
|
+
return existing;
|
|
70
|
+
}
|
|
71
|
+
const next = {
|
|
72
|
+
capKey,
|
|
73
|
+
seriesKey,
|
|
74
|
+
seriesType,
|
|
75
|
+
table_name,
|
|
76
|
+
series_id,
|
|
77
|
+
product_id,
|
|
78
|
+
duration,
|
|
79
|
+
direction,
|
|
80
|
+
ranges: [],
|
|
81
|
+
};
|
|
82
|
+
mapSeriesKeyToState.set(seriesKey, next);
|
|
83
|
+
return next;
|
|
84
|
+
};
|
|
85
|
+
const computeHeadLagMs = (series) => {
|
|
86
|
+
var _a;
|
|
87
|
+
if (series.seriesType === 'interest_rate')
|
|
88
|
+
return CONFIG.defaultInterestRateHeadLagMs;
|
|
89
|
+
const offset = (0, utils_1.convertDurationToOffset)((_a = series.duration) !== null && _a !== void 0 ? _a : '');
|
|
90
|
+
if (!isFinite(offset) || offset <= 0)
|
|
91
|
+
return 60000;
|
|
92
|
+
return Math.max(60000, offset);
|
|
93
|
+
};
|
|
94
|
+
const computeOverlapMs = (series) => {
|
|
95
|
+
var _a;
|
|
96
|
+
const maxOverlapMs = 60 * 60000;
|
|
97
|
+
let overlapMs = 60000;
|
|
98
|
+
if (series.seriesType === 'ohlc') {
|
|
99
|
+
const offset = (0, utils_1.convertDurationToOffset)((_a = series.duration) !== null && _a !== void 0 ? _a : '');
|
|
100
|
+
if (isFinite(offset) && offset > 0)
|
|
101
|
+
overlapMs = offset;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
overlapMs = 60 * 60000;
|
|
105
|
+
}
|
|
106
|
+
if (series.last_window_ms && isFinite(series.last_window_ms) && series.last_window_ms > 0) {
|
|
107
|
+
overlapMs = Math.min(overlapMs, Math.max(1, series.last_window_ms - 1));
|
|
108
|
+
}
|
|
109
|
+
return Math.max(1, Math.min(maxOverlapMs, overlapMs));
|
|
110
|
+
};
|
|
111
|
+
const applyOverlapToRequestTime = (series, baseTime) => {
|
|
112
|
+
const hasAnyRange = series.union_start_ms !== undefined || series.union_end_ms !== undefined || series.ranges.length > 0;
|
|
113
|
+
if (!hasAnyRange)
|
|
114
|
+
return baseTime;
|
|
115
|
+
const overlapMs = computeOverlapMs(series);
|
|
116
|
+
if (series.direction === 'backward')
|
|
117
|
+
return Math.min(baseTime + overlapMs, Date.now());
|
|
118
|
+
return Math.max(0, baseTime - overlapMs);
|
|
119
|
+
};
|
|
120
|
+
const computeBackoffMs = (current) => {
|
|
121
|
+
const next = current <= 0 ? CONFIG.backoffStepMs : Math.min(CONFIG.maxBackoffMs, current + CONFIG.backoffStepMs);
|
|
122
|
+
return next;
|
|
123
|
+
};
|
|
124
|
+
const enqueueCapJob = (capKey, kind, seriesKey) => {
|
|
125
|
+
const capState = getOrCreateCapState(capKey);
|
|
126
|
+
const pendingSet = kind === 'head' ? capState.pendingHead : capState.pendingTail;
|
|
127
|
+
const queue = kind === 'head' ? capState.headQueue : capState.tailQueue;
|
|
128
|
+
if (pendingSet.has(seriesKey))
|
|
129
|
+
return;
|
|
130
|
+
pendingSet.add(seriesKey);
|
|
131
|
+
queue.enqueue({ kind, seriesKey });
|
|
132
|
+
};
|
|
133
|
+
const scheduleSeries = (series) => {
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
const headLagMs = computeHeadLagMs(series);
|
|
136
|
+
const needHead = series.union_end_ms === undefined || now - series.union_end_ms > headLagMs;
|
|
137
|
+
if (needHead) {
|
|
138
|
+
enqueueCapJob(series.capKey, 'head', series.seriesKey);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (series.union_start_ms === undefined)
|
|
142
|
+
return;
|
|
143
|
+
enqueueCapJob(series.capKey, 'tail', series.seriesKey);
|
|
144
|
+
};
|
|
145
|
+
const mergeRangesAndGetUnion = async (series_id, table_name) => {
|
|
146
|
+
const rows = await (0, sql_1.requestSQL)(terminal, `
|
|
147
|
+
WITH locked AS (
|
|
148
|
+
SELECT series_id, table_name, start_time, end_time
|
|
149
|
+
FROM series_data_range
|
|
150
|
+
WHERE series_id = ${(0, sql_1.escapeSQL)(series_id)} AND table_name = ${(0, sql_1.escapeSQL)(table_name)}
|
|
151
|
+
ORDER BY start_time ASC, end_time ASC
|
|
152
|
+
FOR UPDATE
|
|
153
|
+
),
|
|
154
|
+
ordered AS (
|
|
155
|
+
SELECT
|
|
156
|
+
start_time,
|
|
157
|
+
end_time,
|
|
158
|
+
max(end_time) OVER (
|
|
159
|
+
ORDER BY start_time ASC, end_time ASC
|
|
160
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
|
161
|
+
) AS running_end
|
|
162
|
+
FROM locked
|
|
163
|
+
),
|
|
164
|
+
marks AS (
|
|
165
|
+
SELECT
|
|
166
|
+
start_time,
|
|
167
|
+
end_time,
|
|
168
|
+
running_end,
|
|
169
|
+
CASE
|
|
170
|
+
WHEN start_time >= COALESCE(
|
|
171
|
+
lag(running_end) OVER (ORDER BY start_time ASC, end_time ASC),
|
|
172
|
+
'-infinity'::timestamptz
|
|
173
|
+
) THEN 1
|
|
174
|
+
ELSE 0
|
|
175
|
+
END AS is_new_group
|
|
176
|
+
FROM ordered
|
|
177
|
+
),
|
|
178
|
+
groups AS (
|
|
179
|
+
SELECT
|
|
180
|
+
start_time,
|
|
181
|
+
end_time,
|
|
182
|
+
sum(is_new_group) OVER (
|
|
183
|
+
ORDER BY start_time ASC, end_time ASC
|
|
184
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
|
185
|
+
) AS grp
|
|
186
|
+
FROM marks
|
|
187
|
+
),
|
|
188
|
+
merged AS (
|
|
189
|
+
SELECT min(start_time) AS start_time, max(end_time) AS end_time
|
|
190
|
+
FROM groups
|
|
191
|
+
GROUP BY grp
|
|
192
|
+
),
|
|
193
|
+
to_delete AS (
|
|
194
|
+
SELECT l.series_id, l.table_name, l.start_time, l.end_time
|
|
195
|
+
FROM locked l
|
|
196
|
+
WHERE NOT EXISTS (
|
|
197
|
+
SELECT 1
|
|
198
|
+
FROM merged m
|
|
199
|
+
WHERE m.start_time = l.start_time AND m.end_time = l.end_time
|
|
200
|
+
)
|
|
201
|
+
),
|
|
202
|
+
deleted AS (
|
|
203
|
+
DELETE FROM series_data_range t
|
|
204
|
+
USING to_delete d
|
|
205
|
+
WHERE
|
|
206
|
+
t.series_id = d.series_id
|
|
207
|
+
AND t.table_name = d.table_name
|
|
208
|
+
AND t.start_time = d.start_time
|
|
209
|
+
AND t.end_time = d.end_time
|
|
210
|
+
RETURNING 1
|
|
211
|
+
),
|
|
212
|
+
to_insert AS (
|
|
213
|
+
SELECT m.start_time, m.end_time
|
|
214
|
+
FROM merged m
|
|
215
|
+
WHERE NOT EXISTS (
|
|
216
|
+
SELECT 1
|
|
217
|
+
FROM locked l
|
|
218
|
+
WHERE l.start_time = m.start_time AND l.end_time = m.end_time
|
|
219
|
+
)
|
|
220
|
+
),
|
|
221
|
+
inserted AS (
|
|
222
|
+
INSERT INTO series_data_range (series_id, table_name, start_time, end_time)
|
|
223
|
+
SELECT ${(0, sql_1.escapeSQL)(series_id)}, ${(0, sql_1.escapeSQL)(table_name)}, start_time, end_time
|
|
224
|
+
FROM to_insert
|
|
225
|
+
ON CONFLICT DO NOTHING
|
|
226
|
+
RETURNING 1
|
|
227
|
+
)
|
|
228
|
+
SELECT start_time, end_time
|
|
229
|
+
FROM series_data_range
|
|
230
|
+
WHERE series_id = ${(0, sql_1.escapeSQL)(series_id)} AND table_name = ${(0, sql_1.escapeSQL)(table_name)}
|
|
231
|
+
ORDER BY start_time ASC, end_time ASC;
|
|
232
|
+
`);
|
|
233
|
+
if (rows.length === 0)
|
|
234
|
+
return { segments: [] };
|
|
235
|
+
const segments = [];
|
|
236
|
+
let startMs = Number.POSITIVE_INFINITY;
|
|
237
|
+
let endMs = Number.NEGATIVE_INFINITY;
|
|
238
|
+
for (const r of rows) {
|
|
239
|
+
const s = Date.parse(r.start_time);
|
|
240
|
+
const e = Date.parse(r.end_time);
|
|
241
|
+
if (!isNaN(s) && !isNaN(e) && e >= s) {
|
|
242
|
+
segments.push({ startMs: s, endMs: e });
|
|
243
|
+
startMs = Math.min(startMs, s);
|
|
244
|
+
endMs = Math.max(endMs, e);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (!isFinite(startMs) || !isFinite(endMs))
|
|
248
|
+
return { segments };
|
|
249
|
+
return { segments, union: { startMs, endMs } };
|
|
250
|
+
};
|
|
251
|
+
const findNearestGap = (segments) => {
|
|
252
|
+
for (let i = segments.length - 2; i >= 0; i--) {
|
|
253
|
+
const left = segments[i];
|
|
254
|
+
const right = segments[i + 1];
|
|
255
|
+
if (left.endMs < right.startMs)
|
|
256
|
+
return { left, right };
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
const computeTailTime = (series, now) => {
|
|
260
|
+
var _a, _b, _c, _d, _e;
|
|
261
|
+
const gap = findNearestGap(series.ranges);
|
|
262
|
+
if (gap) {
|
|
263
|
+
return series.direction === 'backward' ? gap.right.startMs : gap.left.endMs;
|
|
264
|
+
}
|
|
265
|
+
const first = series.ranges[0];
|
|
266
|
+
const mostRecent = series.ranges[series.ranges.length - 1];
|
|
267
|
+
if (series.direction === 'backward') {
|
|
268
|
+
return (_c = (_b = (_a = series.union_start_ms) !== null && _a !== void 0 ? _a : first === null || first === void 0 ? void 0 : first.startMs) !== null && _b !== void 0 ? _b : mostRecent === null || mostRecent === void 0 ? void 0 : mostRecent.startMs) !== null && _c !== void 0 ? _c : now;
|
|
269
|
+
}
|
|
270
|
+
const windowMs = (_d = series.last_window_ms) !== null && _d !== void 0 ? _d : CONFIG.defaultForwardSeedWindowMs;
|
|
271
|
+
const start = (_e = series.union_start_ms) !== null && _e !== void 0 ? _e : Math.max(0, now - windowMs);
|
|
272
|
+
return Math.max(0, start - windowMs);
|
|
273
|
+
};
|
|
274
|
+
const computeRequestTime = (series, kind) => {
|
|
275
|
+
var _a;
|
|
276
|
+
const now = Date.now();
|
|
277
|
+
if (series.direction === 'backward') {
|
|
278
|
+
if (kind === 'head')
|
|
279
|
+
return applyOverlapToRequestTime(series, now);
|
|
280
|
+
return applyOverlapToRequestTime(series, computeTailTime(series, now));
|
|
281
|
+
}
|
|
282
|
+
const windowMs = (_a = series.last_window_ms) !== null && _a !== void 0 ? _a : CONFIG.defaultForwardSeedWindowMs;
|
|
283
|
+
if (kind === 'head') {
|
|
284
|
+
if (series.union_end_ms !== undefined)
|
|
285
|
+
return applyOverlapToRequestTime(series, series.union_end_ms);
|
|
286
|
+
return applyOverlapToRequestTime(series, Math.max(0, now - windowMs));
|
|
287
|
+
}
|
|
288
|
+
return applyOverlapToRequestTime(series, computeTailTime(series, now));
|
|
289
|
+
};
|
|
290
|
+
const requestIngest = async (series, time) => {
|
|
291
|
+
var _a;
|
|
292
|
+
if (series.seriesType === 'ohlc') {
|
|
293
|
+
const req = {
|
|
294
|
+
product_id: series.product_id,
|
|
295
|
+
duration: (_a = series.duration) !== null && _a !== void 0 ? _a : '',
|
|
296
|
+
direction: series.direction,
|
|
297
|
+
time,
|
|
298
|
+
};
|
|
299
|
+
return terminal.client.requestForResponseData('IngestOHLC', req);
|
|
300
|
+
}
|
|
301
|
+
const req = {
|
|
302
|
+
product_id: series.product_id,
|
|
303
|
+
direction: series.direction,
|
|
304
|
+
time,
|
|
305
|
+
};
|
|
306
|
+
return terminal.client.requestForResponseData('IngestInterestRate', req);
|
|
307
|
+
};
|
|
308
|
+
const executeJob = async (capState, job) => {
|
|
309
|
+
const series = mapSeriesKeyToState.get(job.seriesKey);
|
|
310
|
+
if (!series)
|
|
311
|
+
return;
|
|
312
|
+
if (job.kind === 'head')
|
|
313
|
+
capState.pendingHead.delete(job.seriesKey);
|
|
314
|
+
else
|
|
315
|
+
capState.pendingTail.delete(job.seriesKey);
|
|
316
|
+
try {
|
|
317
|
+
// make sure each request overlaps existing ranges
|
|
318
|
+
const time = computeRequestTime(series, job.kind);
|
|
319
|
+
const result = await requestIngest(series, time);
|
|
320
|
+
capState.backoffMs = 0;
|
|
321
|
+
capState.nextEligibleAt = 0;
|
|
322
|
+
if (!result.range) {
|
|
323
|
+
capState.backoffMs = computeBackoffMs(capState.backoffMs);
|
|
324
|
+
capState.nextEligibleAt = Date.now() + capState.backoffMs;
|
|
325
|
+
scheduleSeries(series);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const startMs = Date.parse(result.range.start_time);
|
|
329
|
+
const endMs = Date.parse(result.range.end_time);
|
|
330
|
+
if (!isNaN(startMs) && !isNaN(endMs) && endMs > startMs) {
|
|
331
|
+
series.last_window_ms = endMs - startMs;
|
|
332
|
+
}
|
|
333
|
+
const merged = await mergeRangesAndGetUnion(series.series_id, series.table_name);
|
|
334
|
+
series.ranges = merged.segments;
|
|
335
|
+
if (merged.union) {
|
|
336
|
+
series.union_start_ms = merged.union.startMs;
|
|
337
|
+
series.union_end_ms = merged.union.endMs;
|
|
338
|
+
}
|
|
339
|
+
scheduleSeries(series);
|
|
340
|
+
}
|
|
341
|
+
catch (e) {
|
|
342
|
+
capState.backoffMs = computeBackoffMs(capState.backoffMs);
|
|
343
|
+
capState.nextEligibleAt = Date.now() + capState.backoffMs;
|
|
344
|
+
console.warn((0, utils_1.formatTime)(Date.now()), '[VEX][SeriesData]CapFailed', `cap=${capState.capKey}`, `backoff_ms=${capState.backoffMs}`, `${e}`);
|
|
345
|
+
scheduleSeries(series);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
const computeGlobalHeadBacklog = () => {
|
|
349
|
+
let total = 0;
|
|
350
|
+
for (const s of mapCapKeyToState.values()) {
|
|
351
|
+
total += s.headQueue.size();
|
|
352
|
+
}
|
|
353
|
+
return total;
|
|
354
|
+
};
|
|
355
|
+
const pickNextRunnableCap = (now) => {
|
|
356
|
+
if (capabilities.length === 0)
|
|
357
|
+
return;
|
|
358
|
+
const n = capabilities.length;
|
|
359
|
+
for (let i = 0; i < n; i++) {
|
|
360
|
+
// Round-robin pick
|
|
361
|
+
const cap = capabilities[capRunIndex++ % n];
|
|
362
|
+
const capState = getOrCreateCapState(cap.capKey);
|
|
363
|
+
if (capState.inflight)
|
|
364
|
+
continue;
|
|
365
|
+
if (capState.nextEligibleAt > now)
|
|
366
|
+
continue;
|
|
367
|
+
if (capState.headQueue.size() === 0 && capState.tailQueue.size() === 0)
|
|
368
|
+
continue;
|
|
369
|
+
return capState;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
const dequeueFromCap = (capState, canRunTail) => {
|
|
373
|
+
const head = capState.headQueue.dequeue();
|
|
374
|
+
if (head)
|
|
375
|
+
return { job: head, kind: 'head' };
|
|
376
|
+
if (!canRunTail)
|
|
377
|
+
return;
|
|
378
|
+
const tail = capState.tailQueue.dequeue();
|
|
379
|
+
if (!tail)
|
|
380
|
+
return;
|
|
381
|
+
return { job: tail, kind: 'tail' };
|
|
382
|
+
};
|
|
383
|
+
const scanProductsOnce = async () => {
|
|
384
|
+
var _a;
|
|
385
|
+
if (capabilities.length === 0)
|
|
386
|
+
return;
|
|
387
|
+
const now = Date.now();
|
|
388
|
+
const cap = capabilities[scanIndex++ % capabilities.length];
|
|
389
|
+
if (cap.nextScanAt > now)
|
|
390
|
+
return;
|
|
391
|
+
const where = [
|
|
392
|
+
`product_id LIKE ${(0, sql_1.escapeSQL)(`${cap.product_id_prefix}%`)}`,
|
|
393
|
+
onlyProductIdPrefix ? `product_id LIKE ${(0, sql_1.escapeSQL)(`${onlyProductIdPrefix}%`)}` : '',
|
|
394
|
+
cap.seriesType === 'interest_rate' ? `COALESCE(no_interest_rate, false) = false` : '',
|
|
395
|
+
]
|
|
396
|
+
.filter(Boolean)
|
|
397
|
+
.join(' AND ');
|
|
398
|
+
const rows = await (0, sql_1.requestSQL)(terminal, `
|
|
399
|
+
SELECT product_id
|
|
400
|
+
FROM product
|
|
401
|
+
WHERE ${where}
|
|
402
|
+
ORDER BY product_id ASC
|
|
403
|
+
`);
|
|
404
|
+
cap.nextScanAt = now + CONFIG.scanFullLoopIntervalMs;
|
|
405
|
+
for (const row of rows) {
|
|
406
|
+
if (cap.seriesType === 'ohlc') {
|
|
407
|
+
for (const duration of (_a = cap.duration_list) !== null && _a !== void 0 ? _a : []) {
|
|
408
|
+
const series = getOrCreateSeriesState({
|
|
409
|
+
capKey: cap.capKey,
|
|
410
|
+
seriesType: 'ohlc',
|
|
411
|
+
product_id: row.product_id,
|
|
412
|
+
duration,
|
|
413
|
+
direction: cap.direction,
|
|
414
|
+
});
|
|
415
|
+
scheduleSeries(series);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
const series = getOrCreateSeriesState({
|
|
420
|
+
capKey: cap.capKey,
|
|
421
|
+
seriesType: 'interest_rate',
|
|
422
|
+
product_id: row.product_id,
|
|
423
|
+
direction: cap.direction,
|
|
424
|
+
});
|
|
425
|
+
scheduleSeries(series);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
const tick = async () => {
|
|
430
|
+
if (!isEnabled)
|
|
431
|
+
return;
|
|
432
|
+
try {
|
|
433
|
+
await scanProductsOnce();
|
|
434
|
+
}
|
|
435
|
+
catch (e) {
|
|
436
|
+
console.error((0, utils_1.formatTime)(Date.now()), '[VEX][SeriesData]ScanFailed', `${e}`);
|
|
437
|
+
}
|
|
438
|
+
const now = Date.now();
|
|
439
|
+
while (inflight < CONFIG.maxInflight) {
|
|
440
|
+
const capState = pickNextRunnableCap(now);
|
|
441
|
+
if (!capState)
|
|
442
|
+
return;
|
|
443
|
+
const globalHeadBacklog = computeGlobalHeadBacklog();
|
|
444
|
+
const canRunTail = globalHeadBacklog < CONFIG.tailOnlyWhenGlobalHeadBelow && capState.headQueue.size() === 0;
|
|
445
|
+
const next = dequeueFromCap(capState, canRunTail);
|
|
446
|
+
if (!next)
|
|
447
|
+
return;
|
|
448
|
+
capState.inflight = true;
|
|
449
|
+
inflight++;
|
|
450
|
+
executeJob(capState, next.job).finally(() => {
|
|
451
|
+
capState.inflight = false;
|
|
452
|
+
inflight--;
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
if (!isEnabled) {
|
|
457
|
+
console.info((0, utils_1.formatTime)(Date.now()), '[VEX][SeriesData]Disabled', 'VEX_SERIES_DATA_ENABLED!=1');
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
terminal.server.provideService('VEX/SeriesData/Peek', {}, async () => {
|
|
461
|
+
const now = Date.now();
|
|
462
|
+
const caps = capabilities.map((cap) => {
|
|
463
|
+
const s = getOrCreateCapState(cap.capKey);
|
|
464
|
+
return {
|
|
465
|
+
capKey: cap.capKey,
|
|
466
|
+
method: cap.method,
|
|
467
|
+
product_id_prefix: cap.product_id_prefix,
|
|
468
|
+
direction: cap.direction,
|
|
469
|
+
head_queue_size: s.headQueue.size(),
|
|
470
|
+
tail_queue_size: s.tailQueue.size(),
|
|
471
|
+
inflight: s.inflight,
|
|
472
|
+
backoff_ms: s.backoffMs,
|
|
473
|
+
nextEligibleAt: s.nextEligibleAt > now ? (0, utils_1.formatTime)(s.nextEligibleAt) : undefined,
|
|
474
|
+
};
|
|
475
|
+
});
|
|
476
|
+
return {
|
|
477
|
+
res: {
|
|
478
|
+
code: 0,
|
|
479
|
+
message: 'OK',
|
|
480
|
+
data: {
|
|
481
|
+
enabled: true,
|
|
482
|
+
only_product_id_prefix: onlyProductIdPrefix || undefined,
|
|
483
|
+
inflight,
|
|
484
|
+
cap_count: capabilities.length,
|
|
485
|
+
global_head_backlog: computeGlobalHeadBacklog(),
|
|
486
|
+
series_count: mapSeriesKeyToState.size,
|
|
487
|
+
caps: caps.slice(0, 50),
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
};
|
|
491
|
+
});
|
|
492
|
+
// Setup trace log
|
|
493
|
+
(0, rxjs_1.timer)(0, LOG_QUEUE_INTERVAL_MS).subscribe(() => {
|
|
494
|
+
const now = Date.now();
|
|
495
|
+
const caps = capabilities
|
|
496
|
+
.map((cap) => {
|
|
497
|
+
const s = getOrCreateCapState(cap.capKey);
|
|
498
|
+
return {
|
|
499
|
+
capKey: cap.capKey,
|
|
500
|
+
head: s.headQueue.size(),
|
|
501
|
+
tail: s.tailQueue.size(),
|
|
502
|
+
inflight: s.inflight,
|
|
503
|
+
backoffMs: s.backoffMs,
|
|
504
|
+
nextEligibleAt: s.nextEligibleAt,
|
|
505
|
+
};
|
|
506
|
+
})
|
|
507
|
+
.filter((x) => x.inflight || x.head > 0 || x.tail > 0 || x.nextEligibleAt > now)
|
|
508
|
+
.slice(0, 20)
|
|
509
|
+
.map((x) => `cap=${x.capKey} head=${x.head} tail=${x.tail} inflight=${x.inflight ? 1 : 0} backoff_ms=${x.backoffMs}${x.nextEligibleAt > now ? ` next=${(0, utils_1.formatTime)(x.nextEligibleAt)}` : ''}`);
|
|
510
|
+
console.info((0, utils_1.formatTime)(now), '[VEX][SeriesData]Queues', `inflight=${inflight}`, `cap_count=${capabilities.length}`, `global_head_backlog=${computeGlobalHeadBacklog()}`, `series_count=${mapSeriesKeyToState.size}`, caps.length ? `caps=[${caps.join(' | ')}]` : 'caps=[]');
|
|
511
|
+
});
|
|
512
|
+
terminal.terminalInfos$
|
|
513
|
+
.pipe((0, rxjs_1.mergeMap)((terminalInfos) => (0, rxjs_1.from)(terminalInfos).pipe((0, rxjs_1.mergeMap)((terminalInfo) => (0, rxjs_1.from)(Object.values(terminalInfo.serviceInfo || {})).pipe((0, rxjs_1.filter)((serviceInfo) => serviceInfo.method === 'IngestOHLC' || serviceInfo.method === 'IngestInterestRate'), (0, rxjs_1.map)((serviceInfo) => {
|
|
514
|
+
try {
|
|
515
|
+
if (serviceInfo.method === 'IngestOHLC') {
|
|
516
|
+
const meta = (0, exchange_1.parseOHLCServiceMetadataFromSchema)(serviceInfo.schema);
|
|
517
|
+
const capKey = (0, utils_1.encodePath)('IngestOHLC', meta.product_id_prefix, meta.direction);
|
|
518
|
+
const existing = mapCapKeyToCapability.get(capKey);
|
|
519
|
+
const duration_list = [...new Set(meta.duration_list)].sort();
|
|
520
|
+
if (existing) {
|
|
521
|
+
existing.duration_list = duration_list;
|
|
522
|
+
return existing;
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
capKey,
|
|
526
|
+
method: 'IngestOHLC',
|
|
527
|
+
seriesType: 'ohlc',
|
|
528
|
+
product_id_prefix: meta.product_id_prefix,
|
|
529
|
+
direction: meta.direction,
|
|
530
|
+
duration_list,
|
|
531
|
+
nextScanAt: 0,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
const meta = (0, exchange_1.parseInterestRateServiceMetadataFromSchema)(serviceInfo.schema);
|
|
535
|
+
const capKey = (0, utils_1.encodePath)('IngestInterestRate', meta.product_id_prefix, meta.direction);
|
|
536
|
+
const existing = mapCapKeyToCapability.get(capKey);
|
|
537
|
+
if (existing)
|
|
538
|
+
return existing;
|
|
539
|
+
return {
|
|
540
|
+
capKey,
|
|
541
|
+
method: 'IngestInterestRate',
|
|
542
|
+
seriesType: 'interest_rate',
|
|
543
|
+
product_id_prefix: meta.product_id_prefix,
|
|
544
|
+
direction: meta.direction,
|
|
545
|
+
nextScanAt: 0,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
catch (_a) {
|
|
549
|
+
console.info((0, utils_1.formatTime)(Date.now()), '[VEX][SeriesData]ParseServiceMetadataFailed', `method=${serviceInfo.method}`, `terminal_id=${terminalInfo.terminal_id}`);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
}), (0, rxjs_1.filter)((x) => !!x))), (0, rxjs_1.toArray)(), (0, rxjs_1.tap)((nextCaps) => {
|
|
553
|
+
const nextMap = new Map();
|
|
554
|
+
for (const cap of nextCaps) {
|
|
555
|
+
nextMap.set(cap.capKey, cap);
|
|
556
|
+
}
|
|
557
|
+
mapCapKeyToCapability.clear();
|
|
558
|
+
nextMap.forEach((v, k) => mapCapKeyToCapability.set(k, v));
|
|
559
|
+
capabilities.length = 0;
|
|
560
|
+
nextMap.forEach((v) => capabilities.push(v));
|
|
561
|
+
}))))
|
|
562
|
+
.subscribe();
|
|
563
|
+
(0, rxjs_1.defer)(() => (0, rxjs_1.from)(tick()))
|
|
564
|
+
.pipe((0, rxjs_1.catchError)((e) => {
|
|
565
|
+
console.error((0, utils_1.formatTime)(Date.now()), '[VEX][SeriesData]TickError', `${e}`);
|
|
566
|
+
return rxjs_1.EMPTY;
|
|
567
|
+
}), (0, rxjs_1.repeat)({ delay: CONFIG.tickIntervalMs }))
|
|
568
|
+
.subscribe();
|
|
569
|
+
}
|
|
570
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/series-data/scheduler.ts"],"names":[],"mappings":"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","sourcesContent":["import { encodeInterestRateSeriesId } from '@yuants/data-interest-rate';\nimport { encodeOHLCSeriesId } from '@yuants/data-ohlc';\nimport {\n IIngestInterestRateRequest,\n IIngestOHLCRequest,\n ISeriesIngestResult,\n parseInterestRateServiceMetadataFromSchema,\n parseOHLCServiceMetadataFromSchema,\n SeriesFetchDirection,\n} from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\nimport { convertDurationToOffset, encodePath, formatTime } from '@yuants/utils';\nimport { catchError, defer, EMPTY, filter, from, map, mergeMap, repeat, tap, timer, toArray } from 'rxjs';\nimport { createFifoQueue } from './fifo-queue';\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst isEnabled = process.env.VEX_SERIES_DATA_ENABLED === '1';\nconst onlyProductIdPrefix = (process.env.VEX_SERIES_DATA_ONLY_PRODUCT_ID_PREFIX ?? '').trim();\n\nconst CONFIG = {\n tickIntervalMs: 1_000,\n scanFullLoopIntervalMs: 5 * 60_000,\n /**\n * Maximum number of inflight series data ingest requests.\n */\n maxInflight: 20,\n /**\n * Only run tail jobs when the global head backlog is below this threshold.\n */\n tailOnlyWhenGlobalHeadBelow: 20,\n defaultInterestRateHeadLagMs: 8 * 60 * 60_000,\n defaultForwardSeedWindowMs: 24 * 60 * 60_000,\n maxBackoffMs: 5 * 60_000,\n backoffStepMs: 5_000,\n} as const;\n\n// Per-capability bucket scheduler.\n//\n// Service Discovery -> Scan Products -> Schedule Series -> Tick -> Execute Job -> Merge Range -> Repeat\n//\n// 1) Service Discovery:\n// Subscribe `terminal.terminalInfos$`, find services with method `IngestOHLC` / `IngestInterestRate`,\n// parse schema -> capability metadata (product_id_prefix, direction, duration_list).\n//\n// 2) Capability buckets (capKey):\n// capKey = encodePath(method, product_id_prefix, direction)\n// - each cap has its own headQueue/tailQueue and runs serially (cap inflight=1)\n// - tick chooses a cap in round-robin; cap runs head first\n// - tail runs only when global head backlog is low (avoid hurting freshness)\n// - errors/backoff are isolated per-cap (a noisy cap won't block others)\n//\n// 3) Scan Products:\n// For each capability, scan `product` table by `product_id_prefix` (full scan) and expand product -> series:\n// - OHLC: product x duration_list\n// - InterestRate: product\n//\n// 4) Progress source of truth:\n// We do NOT depend on wrote_count/inserted_count to decide progress.\n// After each successful ingest, we merge `series_data_range` and recompute:\n// - segments[]: merged ranges (used for gap detection)\n// - union: { min(start), max(end) } (used for head/tail decisions; union does NOT mean continuous coverage)\n//\n// 5) Gap semantics:\n// - merge only on strict overlap: prev.end_time > next.start_time\n// - touch (prev.end_time == next.start_time): NOT merged, and NOT a gap\n// - gap only when prev.end_time < next.start_time\n// Tail will prioritize filling the nearest-now gap (by range boundaries).\n//\n// 6) Each request must overlap:\n// Because merge is strict-overlap only, a \"touching\" page would keep accumulating fragments.\n// Therefore we deliberately shift request `time` into the already-covered area by `overlapMs`:\n// - backward: time += overlapMs (clamp to <= now)\n// - forward: time -= overlapMs (clamp to >= 0)\n// Duplicate writes are OK (unique key / ON CONFLICT absorbs them).\n//\n// 7) Retry/backoff:\n// When request fails or returns no range, we apply backoff and reschedule,\n// otherwise the queue may be consumed to empty and appear \"stuck\".\n//\n// 8) Routing / load balancing:\n// Scheduler does not pin a request to a specific instance; it uses `terminal.client.requestForResponseData`\n// and relies on Terminal's schema-based service selection + load balancing.\n\ntype ISeriesType = 'ohlc' | 'interest_rate';\ntype IJobKind = 'head' | 'tail';\n\ntype IJob = {\n kind: IJobKind;\n seriesKey: string;\n};\n\ntype ISeriesState = {\n capKey: string;\n seriesKey: string;\n seriesType: ISeriesType;\n table_name: string;\n series_id: string;\n product_id: string;\n duration?: string;\n direction: SeriesFetchDirection;\n ranges: { startMs: number; endMs: number }[];\n union_start_ms?: number;\n union_end_ms?: number;\n last_window_ms?: number;\n};\n\ntype ICapability = {\n capKey: string;\n method: 'IngestOHLC' | 'IngestInterestRate';\n seriesType: ISeriesType;\n product_id_prefix: string;\n direction: SeriesFetchDirection;\n duration_list?: string[];\n nextScanAt: number;\n};\n\ntype ICapabilityState = {\n capKey: string;\n headQueue: ReturnType<typeof createFifoQueue<IJob>>;\n tailQueue: ReturnType<typeof createFifoQueue<IJob>>;\n pendingHead: Set<string>;\n pendingTail: Set<string>;\n inflight: boolean;\n nextEligibleAt: number;\n backoffMs: number;\n};\n\nconst capabilities: ICapability[] = [];\nconst mapCapKeyToCapability = new Map<string, ICapability>();\nconst mapCapKeyToState = new Map<string, ICapabilityState>();\n\nconst mapSeriesKeyToState = new Map<string, ISeriesState>();\n\nlet inflight = 0;\nlet scanIndex = 0;\nlet capRunIndex = 0;\nconst LOG_QUEUE_INTERVAL_MS = Number(process.env.VEX_SERIES_DATA_LOG_QUEUE_INTERVAL_MS ?? '10000');\n\nconst getOrCreateCapState = (capKey: string): ICapabilityState => {\n const existing = mapCapKeyToState.get(capKey);\n if (existing) return existing;\n const next: ICapabilityState = {\n capKey,\n headQueue: createFifoQueue<IJob>(),\n tailQueue: createFifoQueue<IJob>(),\n pendingHead: new Set<string>(),\n pendingTail: new Set<string>(),\n inflight: false,\n nextEligibleAt: 0,\n backoffMs: 0,\n };\n mapCapKeyToState.set(capKey, next);\n return next;\n};\n\nconst computeSeriesId = (seriesType: ISeriesType, product_id: string, duration?: string): string => {\n if (seriesType === 'ohlc') return encodeOHLCSeriesId(product_id, duration ?? '');\n return encodeInterestRateSeriesId(product_id);\n};\n\nconst getOrCreateSeriesState = (params: {\n capKey: string;\n seriesType: ISeriesType;\n product_id: string;\n duration?: string;\n direction: SeriesFetchDirection;\n}): ISeriesState => {\n const { capKey, seriesType, product_id, duration, direction } = params;\n const table_name = seriesType === 'ohlc' ? 'ohlc_v2' : 'interest_rate';\n const series_id = computeSeriesId(seriesType, product_id, duration);\n const seriesKey = encodePath(table_name, direction, product_id, duration ?? '');\n\n const existing = mapSeriesKeyToState.get(seriesKey);\n if (existing) {\n existing.capKey = capKey;\n return existing;\n }\n\n const next: ISeriesState = {\n capKey,\n seriesKey,\n seriesType,\n table_name,\n series_id,\n product_id,\n duration,\n direction,\n ranges: [],\n };\n mapSeriesKeyToState.set(seriesKey, next);\n return next;\n};\n\nconst computeHeadLagMs = (series: ISeriesState): number => {\n if (series.seriesType === 'interest_rate') return CONFIG.defaultInterestRateHeadLagMs;\n const offset = convertDurationToOffset(series.duration ?? '');\n if (!isFinite(offset) || offset <= 0) return 60_000;\n return Math.max(60_000, offset);\n};\n\nconst computeOverlapMs = (series: ISeriesState): number => {\n const maxOverlapMs = 60 * 60_000;\n let overlapMs = 60_000;\n if (series.seriesType === 'ohlc') {\n const offset = convertDurationToOffset(series.duration ?? '');\n if (isFinite(offset) && offset > 0) overlapMs = offset;\n } else {\n overlapMs = 60 * 60_000;\n }\n if (series.last_window_ms && isFinite(series.last_window_ms) && series.last_window_ms > 0) {\n overlapMs = Math.min(overlapMs, Math.max(1, series.last_window_ms - 1));\n }\n return Math.max(1, Math.min(maxOverlapMs, overlapMs));\n};\n\nconst applyOverlapToRequestTime = (series: ISeriesState, baseTime: number): number => {\n const hasAnyRange =\n series.union_start_ms !== undefined || series.union_end_ms !== undefined || series.ranges.length > 0;\n if (!hasAnyRange) return baseTime;\n const overlapMs = computeOverlapMs(series);\n if (series.direction === 'backward') return Math.min(baseTime + overlapMs, Date.now());\n return Math.max(0, baseTime - overlapMs);\n};\n\nconst computeBackoffMs = (current: number): number => {\n const next =\n current <= 0 ? CONFIG.backoffStepMs : Math.min(CONFIG.maxBackoffMs, current + CONFIG.backoffStepMs);\n return next;\n};\n\nconst enqueueCapJob = (capKey: string, kind: IJobKind, seriesKey: string) => {\n const capState = getOrCreateCapState(capKey);\n const pendingSet = kind === 'head' ? capState.pendingHead : capState.pendingTail;\n const queue = kind === 'head' ? capState.headQueue : capState.tailQueue;\n if (pendingSet.has(seriesKey)) return;\n pendingSet.add(seriesKey);\n queue.enqueue({ kind, seriesKey });\n};\n\nconst scheduleSeries = (series: ISeriesState) => {\n const now = Date.now();\n\n const headLagMs = computeHeadLagMs(series);\n const needHead = series.union_end_ms === undefined || now - series.union_end_ms > headLagMs;\n if (needHead) {\n enqueueCapJob(series.capKey, 'head', series.seriesKey);\n return;\n }\n\n if (series.union_start_ms === undefined) return;\n enqueueCapJob(series.capKey, 'tail', series.seriesKey);\n};\n\nconst mergeRangesAndGetUnion = async (\n series_id: string,\n table_name: string,\n): Promise<{\n segments: { startMs: number; endMs: number }[];\n union?: { startMs: number; endMs: number };\n}> => {\n const rows = await requestSQL<{ start_time: string; end_time: string }[]>(\n terminal,\n `\n WITH locked AS (\n SELECT series_id, table_name, start_time, end_time\n FROM series_data_range\n WHERE series_id = ${escapeSQL(series_id)} AND table_name = ${escapeSQL(table_name)}\n ORDER BY start_time ASC, end_time ASC\n FOR UPDATE\n ),\n ordered AS (\n SELECT\n start_time,\n end_time,\n max(end_time) OVER (\n ORDER BY start_time ASC, end_time ASC\n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n ) AS running_end\n FROM locked\n ),\n marks AS (\n SELECT\n start_time,\n end_time,\n running_end,\n CASE\n WHEN start_time >= COALESCE(\n lag(running_end) OVER (ORDER BY start_time ASC, end_time ASC),\n '-infinity'::timestamptz\n ) THEN 1\n ELSE 0\n END AS is_new_group\n FROM ordered\n ),\n groups AS (\n SELECT\n start_time,\n end_time,\n sum(is_new_group) OVER (\n ORDER BY start_time ASC, end_time ASC\n ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n ) AS grp\n FROM marks\n ),\n merged AS (\n SELECT min(start_time) AS start_time, max(end_time) AS end_time\n FROM groups\n GROUP BY grp\n ),\n to_delete AS (\n SELECT l.series_id, l.table_name, l.start_time, l.end_time\n FROM locked l\n WHERE NOT EXISTS (\n SELECT 1\n FROM merged m\n WHERE m.start_time = l.start_time AND m.end_time = l.end_time\n )\n ),\n deleted AS (\n DELETE FROM series_data_range t\n USING to_delete d\n WHERE\n t.series_id = d.series_id\n AND t.table_name = d.table_name\n AND t.start_time = d.start_time\n AND t.end_time = d.end_time\n RETURNING 1\n ),\n to_insert AS (\n SELECT m.start_time, m.end_time\n FROM merged m\n WHERE NOT EXISTS (\n SELECT 1\n FROM locked l\n WHERE l.start_time = m.start_time AND l.end_time = m.end_time\n )\n ),\n inserted AS (\n INSERT INTO series_data_range (series_id, table_name, start_time, end_time)\n SELECT ${escapeSQL(series_id)}, ${escapeSQL(table_name)}, start_time, end_time\n FROM to_insert\n ON CONFLICT DO NOTHING\n RETURNING 1\n )\n SELECT start_time, end_time\n FROM series_data_range\n WHERE series_id = ${escapeSQL(series_id)} AND table_name = ${escapeSQL(table_name)}\n ORDER BY start_time ASC, end_time ASC;\n `,\n );\n\n if (rows.length === 0) return { segments: [] };\n const segments: { startMs: number; endMs: number }[] = [];\n let startMs = Number.POSITIVE_INFINITY;\n let endMs = Number.NEGATIVE_INFINITY;\n for (const r of rows) {\n const s = Date.parse(r.start_time);\n const e = Date.parse(r.end_time);\n if (!isNaN(s) && !isNaN(e) && e >= s) {\n segments.push({ startMs: s, endMs: e });\n startMs = Math.min(startMs, s);\n endMs = Math.max(endMs, e);\n }\n }\n if (!isFinite(startMs) || !isFinite(endMs)) return { segments };\n return { segments, union: { startMs, endMs } };\n};\n\nconst findNearestGap = (\n segments: { startMs: number; endMs: number }[],\n): { left: { startMs: number; endMs: number }; right: { startMs: number; endMs: number } } | undefined => {\n for (let i = segments.length - 2; i >= 0; i--) {\n const left = segments[i];\n const right = segments[i + 1];\n if (left.endMs < right.startMs) return { left, right };\n }\n};\n\nconst computeTailTime = (series: ISeriesState, now: number): number => {\n const gap = findNearestGap(series.ranges);\n if (gap) {\n return series.direction === 'backward' ? gap.right.startMs : gap.left.endMs;\n }\n\n const first = series.ranges[0];\n const mostRecent = series.ranges[series.ranges.length - 1];\n if (series.direction === 'backward') {\n return series.union_start_ms ?? first?.startMs ?? mostRecent?.startMs ?? now;\n }\n\n const windowMs = series.last_window_ms ?? CONFIG.defaultForwardSeedWindowMs;\n const start = series.union_start_ms ?? Math.max(0, now - windowMs);\n return Math.max(0, start - windowMs);\n};\n\nconst computeRequestTime = (series: ISeriesState, kind: IJobKind): number => {\n const now = Date.now();\n if (series.direction === 'backward') {\n if (kind === 'head') return applyOverlapToRequestTime(series, now);\n return applyOverlapToRequestTime(series, computeTailTime(series, now));\n }\n\n const windowMs = series.last_window_ms ?? CONFIG.defaultForwardSeedWindowMs;\n if (kind === 'head') {\n if (series.union_end_ms !== undefined) return applyOverlapToRequestTime(series, series.union_end_ms);\n return applyOverlapToRequestTime(series, Math.max(0, now - windowMs));\n }\n return applyOverlapToRequestTime(series, computeTailTime(series, now));\n};\n\nconst requestIngest = async (series: ISeriesState, time: number): Promise<ISeriesIngestResult> => {\n if (series.seriesType === 'ohlc') {\n const req: IIngestOHLCRequest = {\n product_id: series.product_id,\n duration: series.duration ?? '',\n direction: series.direction,\n time,\n };\n return terminal.client.requestForResponseData<IIngestOHLCRequest, ISeriesIngestResult>('IngestOHLC', req);\n }\n\n const req: IIngestInterestRateRequest = {\n product_id: series.product_id,\n direction: series.direction,\n time,\n };\n return terminal.client.requestForResponseData<IIngestInterestRateRequest, ISeriesIngestResult>(\n 'IngestInterestRate',\n req,\n );\n};\n\nconst executeJob = async (capState: ICapabilityState, job: IJob) => {\n const series = mapSeriesKeyToState.get(job.seriesKey);\n if (!series) return;\n\n if (job.kind === 'head') capState.pendingHead.delete(job.seriesKey);\n else capState.pendingTail.delete(job.seriesKey);\n\n try {\n // make sure each request overlaps existing ranges\n const time = computeRequestTime(series, job.kind);\n const result = await requestIngest(series, time);\n\n capState.backoffMs = 0;\n capState.nextEligibleAt = 0;\n\n if (!result.range) {\n capState.backoffMs = computeBackoffMs(capState.backoffMs);\n capState.nextEligibleAt = Date.now() + capState.backoffMs;\n scheduleSeries(series);\n return;\n }\n\n const startMs = Date.parse(result.range.start_time);\n const endMs = Date.parse(result.range.end_time);\n if (!isNaN(startMs) && !isNaN(endMs) && endMs > startMs) {\n series.last_window_ms = endMs - startMs;\n }\n\n const merged = await mergeRangesAndGetUnion(series.series_id, series.table_name);\n series.ranges = merged.segments;\n if (merged.union) {\n series.union_start_ms = merged.union.startMs;\n series.union_end_ms = merged.union.endMs;\n }\n\n scheduleSeries(series);\n } catch (e) {\n capState.backoffMs = computeBackoffMs(capState.backoffMs);\n capState.nextEligibleAt = Date.now() + capState.backoffMs;\n console.warn(\n formatTime(Date.now()),\n '[VEX][SeriesData]CapFailed',\n `cap=${capState.capKey}`,\n `backoff_ms=${capState.backoffMs}`,\n `${e}`,\n );\n scheduleSeries(series);\n }\n};\n\nconst computeGlobalHeadBacklog = (): number => {\n let total = 0;\n for (const s of mapCapKeyToState.values()) {\n total += s.headQueue.size();\n }\n return total;\n};\n\nconst pickNextRunnableCap = (now: number): ICapabilityState | undefined => {\n if (capabilities.length === 0) return;\n const n = capabilities.length;\n for (let i = 0; i < n; i++) {\n // Round-robin pick\n const cap = capabilities[capRunIndex++ % n];\n const capState = getOrCreateCapState(cap.capKey);\n if (capState.inflight) continue;\n if (capState.nextEligibleAt > now) continue;\n if (capState.headQueue.size() === 0 && capState.tailQueue.size() === 0) continue;\n return capState;\n }\n};\n\nconst dequeueFromCap = (\n capState: ICapabilityState,\n canRunTail: boolean,\n): { job: IJob; kind: IJobKind } | undefined => {\n const head = capState.headQueue.dequeue();\n if (head) return { job: head, kind: 'head' };\n if (!canRunTail) return;\n const tail = capState.tailQueue.dequeue();\n if (!tail) return;\n return { job: tail, kind: 'tail' };\n};\n\nconst scanProductsOnce = async () => {\n if (capabilities.length === 0) return;\n\n const now = Date.now();\n const cap = capabilities[scanIndex++ % capabilities.length];\n if (cap.nextScanAt > now) return;\n\n const where = [\n `product_id LIKE ${escapeSQL(`${cap.product_id_prefix}%`)}`,\n onlyProductIdPrefix ? `product_id LIKE ${escapeSQL(`${onlyProductIdPrefix}%`)}` : '',\n cap.seriesType === 'interest_rate' ? `COALESCE(no_interest_rate, false) = false` : '',\n ]\n .filter(Boolean)\n .join(' AND ');\n\n const rows = await requestSQL<{ product_id: string }[]>(\n terminal,\n `\n SELECT product_id\n FROM product\n WHERE ${where}\n ORDER BY product_id ASC\n `,\n );\n cap.nextScanAt = now + CONFIG.scanFullLoopIntervalMs;\n\n for (const row of rows) {\n if (cap.seriesType === 'ohlc') {\n for (const duration of cap.duration_list ?? []) {\n const series = getOrCreateSeriesState({\n capKey: cap.capKey,\n seriesType: 'ohlc',\n product_id: row.product_id,\n duration,\n direction: cap.direction,\n });\n scheduleSeries(series);\n }\n } else {\n const series = getOrCreateSeriesState({\n capKey: cap.capKey,\n seriesType: 'interest_rate',\n product_id: row.product_id,\n direction: cap.direction,\n });\n scheduleSeries(series);\n }\n }\n};\n\nconst tick = async () => {\n if (!isEnabled) return;\n\n try {\n await scanProductsOnce();\n } catch (e) {\n console.error(formatTime(Date.now()), '[VEX][SeriesData]ScanFailed', `${e}`);\n }\n\n const now = Date.now();\n while (inflight < CONFIG.maxInflight) {\n const capState = pickNextRunnableCap(now);\n if (!capState) return;\n\n const globalHeadBacklog = computeGlobalHeadBacklog();\n const canRunTail =\n globalHeadBacklog < CONFIG.tailOnlyWhenGlobalHeadBelow && capState.headQueue.size() === 0;\n const next = dequeueFromCap(capState, canRunTail);\n if (!next) return;\n\n capState.inflight = true;\n inflight++;\n executeJob(capState, next.job).finally(() => {\n capState.inflight = false;\n inflight--;\n });\n }\n};\n\nif (!isEnabled) {\n console.info(formatTime(Date.now()), '[VEX][SeriesData]Disabled', 'VEX_SERIES_DATA_ENABLED!=1');\n} else {\n terminal.server.provideService('VEX/SeriesData/Peek', {}, async () => {\n const now = Date.now();\n const caps = capabilities.map((cap) => {\n const s = getOrCreateCapState(cap.capKey);\n return {\n capKey: cap.capKey,\n method: cap.method,\n product_id_prefix: cap.product_id_prefix,\n direction: cap.direction,\n head_queue_size: s.headQueue.size(),\n tail_queue_size: s.tailQueue.size(),\n inflight: s.inflight,\n backoff_ms: s.backoffMs,\n nextEligibleAt: s.nextEligibleAt > now ? formatTime(s.nextEligibleAt) : undefined,\n };\n });\n return {\n res: {\n code: 0,\n message: 'OK',\n data: {\n enabled: true,\n only_product_id_prefix: onlyProductIdPrefix || undefined,\n inflight,\n cap_count: capabilities.length,\n global_head_backlog: computeGlobalHeadBacklog(),\n series_count: mapSeriesKeyToState.size,\n caps: caps.slice(0, 50),\n },\n },\n };\n });\n\n // Setup trace log\n timer(0, LOG_QUEUE_INTERVAL_MS).subscribe(() => {\n const now = Date.now();\n const caps = capabilities\n .map((cap) => {\n const s = getOrCreateCapState(cap.capKey);\n return {\n capKey: cap.capKey,\n head: s.headQueue.size(),\n tail: s.tailQueue.size(),\n inflight: s.inflight,\n backoffMs: s.backoffMs,\n nextEligibleAt: s.nextEligibleAt,\n };\n })\n .filter((x) => x.inflight || x.head > 0 || x.tail > 0 || x.nextEligibleAt > now)\n .slice(0, 20)\n .map(\n (x) =>\n `cap=${x.capKey} head=${x.head} tail=${x.tail} inflight=${x.inflight ? 1 : 0} backoff_ms=${\n x.backoffMs\n }${x.nextEligibleAt > now ? ` next=${formatTime(x.nextEligibleAt)}` : ''}`,\n );\n\n console.info(\n formatTime(now),\n '[VEX][SeriesData]Queues',\n `inflight=${inflight}`,\n `cap_count=${capabilities.length}`,\n `global_head_backlog=${computeGlobalHeadBacklog()}`,\n `series_count=${mapSeriesKeyToState.size}`,\n caps.length ? `caps=[${caps.join(' | ')}]` : 'caps=[]',\n );\n });\n\n terminal.terminalInfos$\n .pipe(\n mergeMap((terminalInfos) =>\n from(terminalInfos).pipe(\n mergeMap((terminalInfo) =>\n from(Object.values(terminalInfo.serviceInfo || {})).pipe(\n filter(\n (serviceInfo) =>\n serviceInfo.method === 'IngestOHLC' || serviceInfo.method === 'IngestInterestRate',\n ),\n map((serviceInfo): ICapability | undefined => {\n try {\n if (serviceInfo.method === 'IngestOHLC') {\n const meta = parseOHLCServiceMetadataFromSchema(serviceInfo.schema);\n const capKey = encodePath('IngestOHLC', meta.product_id_prefix, meta.direction);\n const existing = mapCapKeyToCapability.get(capKey);\n const duration_list = [...new Set(meta.duration_list)].sort();\n if (existing) {\n existing.duration_list = duration_list;\n return existing;\n }\n return {\n capKey,\n method: 'IngestOHLC',\n seriesType: 'ohlc',\n product_id_prefix: meta.product_id_prefix,\n direction: meta.direction,\n duration_list,\n nextScanAt: 0,\n };\n }\n\n const meta = parseInterestRateServiceMetadataFromSchema(serviceInfo.schema);\n const capKey = encodePath('IngestInterestRate', meta.product_id_prefix, meta.direction);\n const existing = mapCapKeyToCapability.get(capKey);\n if (existing) return existing;\n return {\n capKey,\n method: 'IngestInterestRate',\n seriesType: 'interest_rate',\n product_id_prefix: meta.product_id_prefix,\n direction: meta.direction,\n nextScanAt: 0,\n };\n } catch {\n console.info(\n formatTime(Date.now()),\n '[VEX][SeriesData]ParseServiceMetadataFailed',\n `method=${serviceInfo.method}`,\n `terminal_id=${terminalInfo.terminal_id}`,\n );\n return;\n }\n }),\n filter((x): x is Exclude<typeof x, undefined> => !!x),\n ),\n ),\n toArray(),\n tap((nextCaps) => {\n const nextMap = new Map<string, ICapability>();\n for (const cap of nextCaps) {\n nextMap.set(cap.capKey, cap);\n }\n mapCapKeyToCapability.clear();\n nextMap.forEach((v, k) => mapCapKeyToCapability.set(k, v));\n capabilities.length = 0;\n nextMap.forEach((v) => capabilities.push(v));\n }),\n ),\n ),\n )\n .subscribe();\n\n defer(() => from(tick()))\n .pipe(\n catchError((e) => {\n console.error(formatTime(Date.now()), '[VEX][SeriesData]TickError', `${e}`);\n return EMPTY;\n }),\n repeat({ delay: CONFIG.tickIntervalMs }),\n )\n .subscribe();\n}\n"]}
|