@yuants/app-virtual-exchange 0.16.0 → 0.17.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.
Files changed (43) hide show
  1. package/dist/series-collector/backwards-interest-rate.js +29 -63
  2. package/dist/series-collector/backwards-interest-rate.js.map +1 -1
  3. package/dist/series-collector/discovery.js +33 -0
  4. package/dist/series-collector/discovery.js.map +1 -0
  5. package/dist/series-collector/forwards-interest-rate.js +31 -70
  6. package/dist/series-collector/forwards-interest-rate.js.map +1 -1
  7. package/dist/series-collector/index.js +1 -2
  8. package/dist/series-collector/index.js.map +1 -1
  9. package/dist/series-collector/interest-rate.js +43 -0
  10. package/dist/series-collector/interest-rate.js.map +1 -0
  11. package/dist/series-collector/patch-interest-rate.js +59 -0
  12. package/dist/series-collector/patch-interest-rate.js.map +1 -0
  13. package/dist/series-collector/sql-helpers.js +11 -0
  14. package/dist/series-collector/sql-helpers.js.map +1 -0
  15. package/lib/series-collector/backwards-interest-rate.d.ts +3 -1
  16. package/lib/series-collector/backwards-interest-rate.d.ts.map +1 -1
  17. package/lib/series-collector/backwards-interest-rate.js +31 -63
  18. package/lib/series-collector/backwards-interest-rate.js.map +1 -1
  19. package/lib/series-collector/discovery.d.ts +7 -0
  20. package/lib/series-collector/discovery.d.ts.map +1 -0
  21. package/lib/series-collector/discovery.js +37 -0
  22. package/lib/series-collector/discovery.js.map +1 -0
  23. package/lib/series-collector/forwards-interest-rate.d.ts +3 -1
  24. package/lib/series-collector/forwards-interest-rate.d.ts.map +1 -1
  25. package/lib/series-collector/forwards-interest-rate.js +33 -70
  26. package/lib/series-collector/forwards-interest-rate.js.map +1 -1
  27. package/lib/series-collector/index.d.ts +1 -2
  28. package/lib/series-collector/index.d.ts.map +1 -1
  29. package/lib/series-collector/index.js +1 -2
  30. package/lib/series-collector/index.js.map +1 -1
  31. package/lib/series-collector/interest-rate.d.ts +2 -0
  32. package/lib/series-collector/interest-rate.d.ts.map +1 -0
  33. package/lib/series-collector/interest-rate.js +45 -0
  34. package/lib/series-collector/interest-rate.js.map +1 -0
  35. package/lib/series-collector/patch-interest-rate.d.ts +4 -0
  36. package/lib/series-collector/patch-interest-rate.d.ts.map +1 -0
  37. package/lib/series-collector/patch-interest-rate.js +63 -0
  38. package/lib/series-collector/patch-interest-rate.js.map +1 -0
  39. package/lib/series-collector/sql-helpers.d.ts +11 -0
  40. package/lib/series-collector/sql-helpers.d.ts.map +1 -0
  41. package/lib/series-collector/sql-helpers.js +17 -0
  42. package/lib/series-collector/sql-helpers.js.map +1 -0
  43. package/package.json +1 -1
@@ -2,72 +2,38 @@
2
2
  // 该文件会定期扫描所有 Terminal 的 ServiceInfo,提取出所有支持 Backwards 拉取的序列。
3
3
  // 然后对每个序列,向对应的 IngestInterestRate Service 发送拉取请求,补齐历史数据。
4
4
  // 使用 Token Bucket 控制每个数据源的请求速率,避免过载。
5
- import { parseInterestRateServiceMetadataFromSchema, } from '@yuants/exchange';
6
5
  import { Terminal } from '@yuants/protocol';
7
- import { escapeSQL, requestSQL } from '@yuants/sql';
8
6
  import { decodePath, formatTime, tokenBucket } from '@yuants/utils';
9
- import { defer, repeat, retry } from 'rxjs';
7
+ import { findInterestRateStartTimeBackward } from './sql-helpers';
10
8
  const terminal = Terminal.fromNodeEnv();
11
- const listBackwardSeriesIds = async () => {
12
- const product_ids = await requestSQL(terminal, `select product_id from product`);
13
- console.time('[SeriesCollector][InterestRate][Backwards] calc');
14
- const series_ids = new Set();
15
- for (const terminalInfo of terminal.terminalInfos) {
16
- for (const serviceInfo of Object.values(terminalInfo.serviceInfo || {})) {
17
- if (serviceInfo.method !== 'IngestInterestRate')
18
- continue;
19
- try {
20
- const meta = parseInterestRateServiceMetadataFromSchema(serviceInfo.schema);
21
- if (meta.direction !== 'backward')
22
- continue;
23
- for (const { product_id } of product_ids) {
24
- if (!product_id.startsWith(meta.product_id_prefix))
25
- continue;
26
- series_ids.add(product_id);
27
- }
28
- }
29
- finally {
30
- }
31
- }
9
+ export const handleIngestInterestRateBackward = async (product_id, meta, signal) => {
10
+ var _a, _b, _c, _d;
11
+ const [datasource_id] = decodePath(product_id);
12
+ // 控制速率:每个数据源每秒钟只能请求一次
13
+ await tokenBucket(`interest_rate:backward:${datasource_id}`).acquire(1, signal);
14
+ let req;
15
+ if (meta.direction === 'backward') {
16
+ const startTime = await findInterestRateStartTimeBackward(terminal, product_id);
17
+ const start_time = startTime ? new Date(startTime).getTime() : Date.now();
18
+ req = {
19
+ product_id: product_id,
20
+ direction: 'backward',
21
+ time: start_time,
22
+ };
32
23
  }
33
- console.timeEnd('[SeriesCollector][InterestRate][Backwards] calc');
34
- return series_ids;
24
+ else {
25
+ req = {
26
+ product_id: product_id,
27
+ direction: 'forward',
28
+ time: 0,
29
+ };
30
+ }
31
+ console.info(formatTime(Date.now()), '[SeriesCollector][InterestRate][Backward]', 'Request', `product_id=${req.product_id}, direction=${req.direction}, time=${formatTime(req.time)}`);
32
+ const res = await terminal.client.requestForResponseData('IngestInterestRate', req);
33
+ terminal.metrics
34
+ .counter('series_collector_ingest_count', '')
35
+ .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate', task: 'backward' })
36
+ .inc(res.wrote_count || 0);
37
+ console.info(formatTime(Date.now()), '[SeriesCollector][InterestRate][Backward]', 'Result', `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${formatTime((_b = (_a = res.range) === null || _a === void 0 ? void 0 : _a.start_time) !== null && _b !== void 0 ? _b : NaN)}, end_time=${formatTime((_d = (_c = res.range) === null || _c === void 0 ? void 0 : _c.end_time) !== null && _d !== void 0 ? _d : NaN)}`);
35
38
  };
36
- defer(async () => {
37
- const time = Date.now();
38
- const series_ids = await listBackwardSeriesIds();
39
- console.log(`[SeriesCollector][InterestRate][Backwards] Found ${series_ids.size} series to collect backwards data for. (${formatTime(Date.now() - time)})`);
40
- await Promise.all([...series_ids].map(async (product_id) => {
41
- var _a, _b, _c, _d;
42
- try {
43
- const [datasource_id] = decodePath(product_id);
44
- // 控制速率:每个数据源每秒钟只能请求一次
45
- await tokenBucket(`interest_rate_backwards:${datasource_id}`, {
46
- refillInterval: 1000,
47
- capacity: 1,
48
- }).acquire();
49
- {
50
- const [record] = await requestSQL(terminal, `select start_time from series_data_range where series_id = ${escapeSQL(product_id)} and table_name = 'interest_rate' order by start_time limit 1`);
51
- const start_time = record ? new Date(record.start_time).getTime() : Date.now();
52
- const req = {
53
- product_id: product_id,
54
- direction: 'backward',
55
- time: start_time,
56
- };
57
- console.info(formatTime(Date.now()), 'DispatchIngestInterestRateRequest', `product_id=${product_id}, time=${formatTime(start_time)}`);
58
- const res = await terminal.client.requestForResponseData('IngestInterestRate', req);
59
- terminal.metrics
60
- .counter('series_collector_backwards_ingest_count', '')
61
- .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate' })
62
- .inc(res.wrote_count || 0);
63
- console.info(formatTime(Date.now()), 'DispatchIngestInterestRateResult', `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${formatTime((_b = (_a = res.range) === null || _a === void 0 ? void 0 : _a.start_time) !== null && _b !== void 0 ? _b : NaN)}, end_time=${formatTime((_d = (_c = res.range) === null || _c === void 0 ? void 0 : _c.end_time) !== null && _d !== void 0 ? _d : NaN)}`);
64
- }
65
- }
66
- catch (e) {
67
- console.info(formatTime(Date.now()), 'DispatchIngestInterestRateError', `series_id=${product_id}`, e);
68
- }
69
- }));
70
- })
71
- .pipe(retry({ delay: 1000 }), repeat({ delay: 1000 }))
72
- .subscribe();
73
39
  //# sourceMappingURL=backwards-interest-rate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"backwards-interest-rate.js","sourceRoot":"","sources":["../../src/series-collector/backwards-interest-rate.ts"],"names":[],"mappings":"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","sourcesContent":["// 解决 Backwards 拉取历史数据的调度器\n// 该文件会定期扫描所有 Terminal 的 ServiceInfo,提取出所有支持 Backwards 拉取的序列。\n// 然后对每个序列,向对应的 IngestInterestRate Service 发送拉取请求,补齐历史数据。\n// 使用 Token Bucket 控制每个数据源的请求速率,避免过载。\n\nimport {\n IIngestInterestRateRequest,\n ISeriesIngestResult,\n parseInterestRateServiceMetadataFromSchema,\n} from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\nimport { decodePath, formatTime, tokenBucket } from '@yuants/utils';\nimport { defer, repeat, retry } from 'rxjs';\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst listBackwardSeriesIds = async () => {\n const product_ids = await requestSQL<{ product_id: string }[]>(terminal, `select product_id from product`);\n\n console.time('[SeriesCollector][InterestRate][Backwards] calc');\n\n const series_ids = new Set<string>();\n for (const terminalInfo of terminal.terminalInfos) {\n for (const serviceInfo of Object.values(terminalInfo.serviceInfo || {})) {\n if (serviceInfo.method !== 'IngestInterestRate') continue;\n try {\n const meta = parseInterestRateServiceMetadataFromSchema(serviceInfo.schema);\n if (meta.direction !== 'backward') continue;\n\n for (const { product_id } of product_ids) {\n if (!product_id.startsWith(meta.product_id_prefix)) continue;\n series_ids.add(product_id);\n }\n } finally {\n }\n }\n }\n\n console.timeEnd('[SeriesCollector][InterestRate][Backwards] calc');\n\n return series_ids;\n};\n\ndefer(async () => {\n const time = Date.now();\n const series_ids = await listBackwardSeriesIds();\n console.log(\n `[SeriesCollector][InterestRate][Backwards] Found ${\n series_ids.size\n } series to collect backwards data for. (${formatTime(Date.now() - time)})`,\n );\n\n await Promise.all(\n [...series_ids].map(async (product_id) => {\n try {\n const [datasource_id] = decodePath(product_id);\n // 控制速率:每个数据源每秒钟只能请求一次\n await tokenBucket(`interest_rate_backwards:${datasource_id}`, {\n refillInterval: 1000,\n capacity: 1,\n }).acquire();\n {\n const [record] = await requestSQL<\n {\n start_time: string;\n }[]\n >(\n terminal,\n `select start_time from series_data_range where series_id = ${escapeSQL(\n product_id,\n )} and table_name = 'interest_rate' order by start_time limit 1`,\n );\n const start_time = record ? new Date(record.start_time).getTime() : Date.now();\n\n const req: IIngestInterestRateRequest = {\n product_id: product_id,\n direction: 'backward',\n time: start_time,\n };\n\n console.info(\n formatTime(Date.now()),\n 'DispatchIngestInterestRateRequest',\n `product_id=${product_id}, time=${formatTime(start_time)}`,\n );\n\n const res = await terminal.client.requestForResponseData<\n IIngestInterestRateRequest,\n ISeriesIngestResult\n >('IngestInterestRate', req);\n\n terminal.metrics\n .counter('series_collector_backwards_ingest_count', '')\n .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate' })\n .inc(res.wrote_count || 0);\n\n console.info(\n formatTime(Date.now()),\n 'DispatchIngestInterestRateResult',\n `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${formatTime(\n res.range?.start_time ?? NaN,\n )}, end_time=${formatTime(res.range?.end_time ?? NaN)}`,\n );\n }\n } catch (e) {\n console.info(formatTime(Date.now()), 'DispatchIngestInterestRateError', `series_id=${product_id}`, e);\n }\n }),\n );\n})\n .pipe(retry({ delay: 1000 }), repeat({ delay: 1000 }))\n .subscribe();\n"]}
1
+ {"version":3,"file":"backwards-interest-rate.js","sourceRoot":"","sources":["../../src/series-collector/backwards-interest-rate.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,6DAA6D;AAC7D,yDAAyD;AACzD,qCAAqC;AAOrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,iCAAiC,EAAE,MAAM,eAAe,CAAC;AAElE,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;AAExC,MAAM,CAAC,MAAM,gCAAgC,GAAG,KAAK,EACnD,UAAkB,EAClB,IAAkC,EAClC,MAAmB,EACnB,EAAE;;IACF,MAAM,CAAC,aAAa,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IAC/C,sBAAsB;IACtB,MAAM,WAAW,CAAC,0BAA0B,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAChF,IAAI,GAA+B,CAAC;IACpC,IAAI,IAAI,CAAC,SAAS,KAAK,UAAU,EAAE;QACjC,MAAM,SAAS,GAAG,MAAM,iCAAiC,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAChF,MAAM,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QAE1E,GAAG,GAAG;YACJ,UAAU,EAAE,UAAU;YACtB,SAAS,EAAE,UAAU;YACrB,IAAI,EAAE,UAAU;SACjB,CAAC;KACH;SAAM;QACL,GAAG,GAAG;YACJ,UAAU,EAAE,UAAU;YACtB,SAAS,EAAE,SAAS;YACpB,IAAI,EAAE,CAAC;SACR,CAAC;KACH;IAED,OAAO,CAAC,IAAI,CACV,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,2CAA2C,EAC3C,SAAS,EACT,cAAc,GAAG,CAAC,UAAU,eAAe,GAAG,CAAC,SAAS,UAAU,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CACzF,CAAC;IAEF,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,sBAAsB,CACtD,oBAAoB,EACpB,GAAG,CACJ,CAAC;IAEF,QAAQ,CAAC,OAAO;SACb,OAAO,CAAC,+BAA+B,EAAE,EAAE,CAAC;SAC5C,MAAM,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;SACtF,GAAG,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;IAE7B,OAAO,CAAC,IAAI,CACV,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,2CAA2C,EAC3C,QAAQ,EACR,aAAa,UAAU,oBAAoB,GAAG,CAAC,WAAW,gBAAgB,UAAU,CAClF,MAAA,MAAA,GAAG,CAAC,KAAK,0CAAE,UAAU,mCAAI,GAAG,CAC7B,cAAc,UAAU,CAAC,MAAA,MAAA,GAAG,CAAC,KAAK,0CAAE,QAAQ,mCAAI,GAAG,CAAC,EAAE,CACxD,CAAC;AACJ,CAAC,CAAC","sourcesContent":["// 解决 Backwards 拉取历史数据的调度器\n// 该文件会定期扫描所有 Terminal 的 ServiceInfo,提取出所有支持 Backwards 拉取的序列。\n// 然后对每个序列,向对应的 IngestInterestRate Service 发送拉取请求,补齐历史数据。\n// 使用 Token Bucket 控制每个数据源的请求速率,避免过载。\n\nimport {\n IIngestInterestRateRequest,\n IInterestRateServiceMetadata,\n ISeriesIngestResult,\n} from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { decodePath, formatTime, tokenBucket } from '@yuants/utils';\nimport { findInterestRateStartTimeBackward } from './sql-helpers';\n\nconst terminal = Terminal.fromNodeEnv();\n\nexport const handleIngestInterestRateBackward = async (\n product_id: string,\n meta: IInterestRateServiceMetadata,\n signal: AbortSignal,\n) => {\n const [datasource_id] = decodePath(product_id);\n // 控制速率:每个数据源每秒钟只能请求一次\n await tokenBucket(`interest_rate:backward:${datasource_id}`).acquire(1, signal);\n let req: IIngestInterestRateRequest;\n if (meta.direction === 'backward') {\n const startTime = await findInterestRateStartTimeBackward(terminal, product_id);\n const start_time = startTime ? new Date(startTime).getTime() : Date.now();\n\n req = {\n product_id: product_id,\n direction: 'backward',\n time: start_time,\n };\n } else {\n req = {\n product_id: product_id,\n direction: 'forward',\n time: 0,\n };\n }\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Backward]',\n 'Request',\n `product_id=${req.product_id}, direction=${req.direction}, time=${formatTime(req.time)}`,\n );\n\n const res = await terminal.client.requestForResponseData<IIngestInterestRateRequest, ISeriesIngestResult>(\n 'IngestInterestRate',\n req,\n );\n\n terminal.metrics\n .counter('series_collector_ingest_count', '')\n .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate', task: 'backward' })\n .inc(res.wrote_count || 0);\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Backward]',\n 'Result',\n `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${formatTime(\n res.range?.start_time ?? NaN,\n )}, end_time=${formatTime(res.range?.end_time ?? NaN)}`,\n );\n};\n"]}
@@ -0,0 +1,33 @@
1
+ // 发现所有支持利率的品种系列ID
2
+ import { parseInterestRateServiceMetadataFromSchema } from '@yuants/exchange';
3
+ import { Terminal } from '@yuants/protocol';
4
+ import { requestSQL } from '@yuants/sql';
5
+ const terminal = Terminal.fromNodeEnv();
6
+ /**
7
+ * 列出所有支持利率的品种系列ID以及对应的 Service Metadata
8
+ * @returns
9
+ */
10
+ export const listInterestRateSeriesIds = async () => {
11
+ const product_ids = await requestSQL(terminal,
12
+ // 必须是支持利率的品种
13
+ `select product_id from product where no_interest_rate = false`);
14
+ const series_ids = new Map();
15
+ for (const terminalInfo of terminal.terminalInfos) {
16
+ for (const serviceInfo of Object.values(terminalInfo.serviceInfo || {})) {
17
+ if (serviceInfo.method !== 'IngestInterestRate')
18
+ continue;
19
+ try {
20
+ const meta = parseInterestRateServiceMetadataFromSchema(serviceInfo.schema);
21
+ for (const { product_id } of product_ids) {
22
+ if (!product_id.startsWith(meta.product_id_prefix))
23
+ continue;
24
+ series_ids.set(product_id, meta);
25
+ }
26
+ }
27
+ finally {
28
+ }
29
+ }
30
+ }
31
+ return series_ids;
32
+ };
33
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/series-collector/discovery.ts"],"names":[],"mappings":"AAAA,kBAAkB;AAElB,OAAO,EAAgC,0CAA0C,EAAE,MAAM,kBAAkB,CAAC;AAC5G,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;AAExC;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,KAAK,IAAI,EAAE;IAClD,MAAM,WAAW,GAAG,MAAM,UAAU,CAClC,QAAQ;IACR,aAAa;IACb,+DAA+D,CAChE,CAAC;IAEF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAwC,CAAC;IACnE,KAAK,MAAM,YAAY,IAAI,QAAQ,CAAC,aAAa,EAAE;QACjD,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE;YACvE,IAAI,WAAW,CAAC,MAAM,KAAK,oBAAoB;gBAAE,SAAS;YAC1D,IAAI;gBACF,MAAM,IAAI,GAAG,0CAA0C,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;gBAE5E,KAAK,MAAM,EAAE,UAAU,EAAE,IAAI,WAAW,EAAE;oBACxC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC;wBAAE,SAAS;oBAC7D,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;iBAClC;aACF;oBAAS;aACT;SACF;KACF;IAED,OAAO,UAAU,CAAC;AACpB,CAAC,CAAC","sourcesContent":["// 发现所有支持利率的品种系列ID\n\nimport { IInterestRateServiceMetadata, parseInterestRateServiceMetadataFromSchema } from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { requestSQL } from '@yuants/sql';\n\nconst terminal = Terminal.fromNodeEnv();\n\n/**\n * 列出所有支持利率的品种系列ID以及对应的 Service Metadata\n * @returns\n */\nexport const listInterestRateSeriesIds = async () => {\n const product_ids = await requestSQL<{ product_id: string }[]>(\n terminal,\n // 必须是支持利率的品种\n `select product_id from product where no_interest_rate = false`,\n );\n\n const series_ids = new Map<string, IInterestRateServiceMetadata>();\n for (const terminalInfo of terminal.terminalInfos) {\n for (const serviceInfo of Object.values(terminalInfo.serviceInfo || {})) {\n if (serviceInfo.method !== 'IngestInterestRate') continue;\n try {\n const meta = parseInterestRateServiceMetadataFromSchema(serviceInfo.schema);\n\n for (const { product_id } of product_ids) {\n if (!product_id.startsWith(meta.product_id_prefix)) continue;\n series_ids.set(product_id, meta);\n }\n } finally {\n }\n }\n }\n\n return series_ids;\n};\n"]}
@@ -2,80 +2,41 @@
2
2
  // 该文件会定期扫描所有 Terminal 的 ServiceInfo,提取出所有支持 Forwards 拉取的序列。
3
3
  // 然后对每个序列,向对应的 IngestInterestRate Service 发送拉取请求,补齐历史数据。
4
4
  // 使用 Token Bucket 控制每个数据源的请求速率,避免过载。
5
- import { parseInterestRateServiceMetadataFromSchema, } from '@yuants/exchange';
6
5
  import { Terminal } from '@yuants/protocol';
7
- import { escapeSQL, requestSQL } from '@yuants/sql';
8
6
  import { decodePath, formatTime, tokenBucket } from '@yuants/utils';
9
- import { defer, repeat, retry } from 'rxjs';
7
+ import { findInterestRateEndTimeForward } from './sql-helpers';
10
8
  const terminal = Terminal.fromNodeEnv();
11
- const listBackwardSeriesIds = async () => {
12
- const product_ids = await requestSQL(terminal, `select product_id from product`);
13
- console.time('[SeriesCollector][InterestRate][Forwards] calc');
14
- const series_ids = new Map();
15
- for (const terminalInfo of terminal.terminalInfos) {
16
- for (const serviceInfo of Object.values(terminalInfo.serviceInfo || {})) {
17
- if (serviceInfo.method !== 'IngestInterestRate')
18
- continue;
19
- try {
20
- const meta = parseInterestRateServiceMetadataFromSchema(serviceInfo.schema);
21
- for (const { product_id } of product_ids) {
22
- if (!product_id.startsWith(meta.product_id_prefix))
23
- continue;
24
- series_ids.set(product_id, meta);
25
- }
26
- }
27
- finally {
28
- }
9
+ export const handleIngestInterestRateForward = async (product_id, meta, signal) => {
10
+ var _a, _b, _c, _d;
11
+ const [datasource_id] = decodePath(product_id);
12
+ // 控制速率:每个数据源每秒钟只能请求一次
13
+ await tokenBucket(`interest_rate_forwards:${datasource_id}`).acquire(1, signal);
14
+ {
15
+ let req;
16
+ if (meta.direction === 'forward') {
17
+ const endTime = await findInterestRateEndTimeForward(terminal, product_id);
18
+ const time = endTime ? new Date(endTime).getTime() : 0;
19
+ req = {
20
+ product_id: product_id,
21
+ direction: 'forward',
22
+ time,
23
+ };
29
24
  }
25
+ else {
26
+ // backward
27
+ req = {
28
+ product_id,
29
+ direction: 'backward',
30
+ time: Date.now(),
31
+ };
32
+ }
33
+ console.info(formatTime(Date.now()), '[SeriesCollector][InterestRate][Forward]', 'Request', `product_id=${req.product_id}, direction=${req.direction}, time=${formatTime(req.time)}`);
34
+ const res = await terminal.client.requestForResponseData('IngestInterestRate', req);
35
+ terminal.metrics
36
+ .counter('series_collector_forwards_ingest_count', '')
37
+ .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate' })
38
+ .inc(res.wrote_count || 0);
39
+ console.info(formatTime(Date.now()), '[SeriesCollector][InterestRate][Forward]', 'Result', `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${formatTime((_b = (_a = res.range) === null || _a === void 0 ? void 0 : _a.start_time) !== null && _b !== void 0 ? _b : NaN)}, end_time=${formatTime((_d = (_c = res.range) === null || _c === void 0 ? void 0 : _c.end_time) !== null && _d !== void 0 ? _d : NaN)}`);
30
40
  }
31
- console.timeEnd('[SeriesCollector][InterestRate][Forwards] calc');
32
- return series_ids;
33
41
  };
34
- defer(async () => {
35
- const series_ids = await listBackwardSeriesIds();
36
- console.log(`[SeriesCollector][InterestRate][Forwards] Found ${series_ids.size} series to collect forwards data for. `);
37
- await Promise.all([...series_ids].map(async ([product_id, meta]) => {
38
- var _a, _b, _c, _d;
39
- try {
40
- const [datasource_id] = decodePath(product_id);
41
- // 控制速率:每个数据源每秒钟只能请求一次
42
- await tokenBucket(`interest_rate_forwards:${datasource_id}`, {
43
- refillInterval: 1000,
44
- capacity: 1,
45
- }).acquire();
46
- {
47
- let req;
48
- if (meta.direction === 'forward') {
49
- const [record] = await requestSQL(terminal, `select end_time from series_data_range where series_id = ${escapeSQL(product_id)} and table_name = 'interest_rate' order by end_time desc limit 1`);
50
- const time = record ? new Date(record.end_time).getTime() : 0;
51
- req = {
52
- product_id: product_id,
53
- direction: 'forward',
54
- time,
55
- };
56
- }
57
- else {
58
- // backward
59
- req = {
60
- product_id,
61
- direction: 'backward',
62
- time: Date.now(),
63
- };
64
- }
65
- console.info(formatTime(Date.now()), 'DispatchIngestInterestRateRequestForwardTask', `product_id=${req.product_id}, direction=${req.direction}, time=${formatTime(req.time)}`);
66
- const res = await terminal.client.requestForResponseData('IngestInterestRate', req);
67
- terminal.metrics
68
- .counter('series_collector_forwards_ingest_count', '')
69
- .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate' })
70
- .inc(res.wrote_count || 0);
71
- console.info(formatTime(Date.now()), 'DispatchIngestInterestRateRequestForwardTaskResult', `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${formatTime((_b = (_a = res.range) === null || _a === void 0 ? void 0 : _a.start_time) !== null && _b !== void 0 ? _b : NaN)}, end_time=${formatTime((_d = (_c = res.range) === null || _c === void 0 ? void 0 : _c.end_time) !== null && _d !== void 0 ? _d : NaN)}`);
72
- }
73
- }
74
- catch (e) {
75
- console.info(formatTime(Date.now()), 'DispatchIngestInterestRateErrorForwards', `series_id=${product_id}`, e);
76
- }
77
- }));
78
- })
79
- .pipe(retry({ delay: 1000 }), repeat({ delay: 1000 }))
80
- .subscribe();
81
42
  //# sourceMappingURL=forwards-interest-rate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"forwards-interest-rate.js","sourceRoot":"","sources":["../../src/series-collector/forwards-interest-rate.ts"],"names":[],"mappings":"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","sourcesContent":["// 解决 Forwards 拉取历史数据的调度器\n// 该文件会定期扫描所有 Terminal 的 ServiceInfo,提取出所有支持 Forwards 拉取的序列。\n// 然后对每个序列,向对应的 IngestInterestRate Service 发送拉取请求,补齐历史数据。\n// 使用 Token Bucket 控制每个数据源的请求速率,避免过载。\n\nimport {\n IIngestInterestRateRequest,\n IInterestRateServiceMetadata,\n ISeriesIngestResult,\n parseInterestRateServiceMetadataFromSchema,\n} from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\nimport { decodePath, formatTime, tokenBucket } from '@yuants/utils';\nimport { defer, repeat, retry } from 'rxjs';\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst listBackwardSeriesIds = async () => {\n const product_ids = await requestSQL<{ product_id: string }[]>(terminal, `select product_id from product`);\n\n console.time('[SeriesCollector][InterestRate][Forwards] calc');\n\n const series_ids = new Map<string, IInterestRateServiceMetadata>();\n for (const terminalInfo of terminal.terminalInfos) {\n for (const serviceInfo of Object.values(terminalInfo.serviceInfo || {})) {\n if (serviceInfo.method !== 'IngestInterestRate') continue;\n try {\n const meta = parseInterestRateServiceMetadataFromSchema(serviceInfo.schema);\n\n for (const { product_id } of product_ids) {\n if (!product_id.startsWith(meta.product_id_prefix)) continue;\n series_ids.set(product_id, meta);\n }\n } finally {\n }\n }\n }\n\n console.timeEnd('[SeriesCollector][InterestRate][Forwards] calc');\n\n return series_ids;\n};\n\ndefer(async () => {\n const series_ids = await listBackwardSeriesIds();\n console.log(\n `[SeriesCollector][InterestRate][Forwards] Found ${series_ids.size} series to collect forwards data for. `,\n );\n\n await Promise.all(\n [...series_ids].map(async ([product_id, meta]) => {\n try {\n const [datasource_id] = decodePath(product_id);\n // 控制速率:每个数据源每秒钟只能请求一次\n await tokenBucket(`interest_rate_forwards:${datasource_id}`, {\n refillInterval: 1000,\n capacity: 1,\n }).acquire();\n {\n let req: IIngestInterestRateRequest;\n if (meta.direction === 'forward') {\n const [record] = await requestSQL<\n {\n end_time: string;\n }[]\n >(\n terminal,\n `select end_time from series_data_range where series_id = ${escapeSQL(\n product_id,\n )} and table_name = 'interest_rate' order by end_time desc limit 1`,\n );\n const time = record ? new Date(record.end_time).getTime() : 0;\n\n req = {\n product_id: product_id,\n direction: 'forward',\n time,\n };\n } else {\n // backward\n req = {\n product_id,\n direction: 'backward',\n time: Date.now(),\n };\n }\n\n console.info(\n formatTime(Date.now()),\n 'DispatchIngestInterestRateRequestForwardTask',\n `product_id=${req.product_id}, direction=${req.direction}, time=${formatTime(req.time)}`,\n );\n const res = await terminal.client.requestForResponseData<\n IIngestInterestRateRequest,\n ISeriesIngestResult\n >('IngestInterestRate', req);\n\n terminal.metrics\n .counter('series_collector_forwards_ingest_count', '')\n .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate' })\n .inc(res.wrote_count || 0);\n\n console.info(\n formatTime(Date.now()),\n 'DispatchIngestInterestRateRequestForwardTaskResult',\n `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${formatTime(\n res.range?.start_time ?? NaN,\n )}, end_time=${formatTime(res.range?.end_time ?? NaN)}`,\n );\n }\n } catch (e) {\n console.info(\n formatTime(Date.now()),\n 'DispatchIngestInterestRateErrorForwards',\n `series_id=${product_id}`,\n e,\n );\n }\n }),\n );\n})\n .pipe(retry({ delay: 1000 }), repeat({ delay: 1000 }))\n .subscribe();\n"]}
1
+ {"version":3,"file":"forwards-interest-rate.js","sourceRoot":"","sources":["../../src/series-collector/forwards-interest-rate.ts"],"names":[],"mappings":"AAAA,yBAAyB;AACzB,4DAA4D;AAC5D,yDAAyD;AACzD,qCAAqC;AAOrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,8BAA8B,EAAE,MAAM,eAAe,CAAC;AAE/D,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;AAExC,MAAM,CAAC,MAAM,+BAA+B,GAAG,KAAK,EAClD,UAAkB,EAClB,IAAkC,EAClC,MAAmB,EACnB,EAAE;;IACF,MAAM,CAAC,aAAa,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IAC/C,sBAAsB;IACtB,MAAM,WAAW,CAAC,0BAA0B,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAEhF;QACE,IAAI,GAA+B,CAAC;QACpC,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE;YAChC,MAAM,OAAO,GAAG,MAAM,8BAA8B,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAC3E,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAEvD,GAAG,GAAG;gBACJ,UAAU,EAAE,UAAU;gBACtB,SAAS,EAAE,SAAS;gBACpB,IAAI;aACL,CAAC;SACH;aAAM;YACL,WAAW;YACX,GAAG,GAAG;gBACJ,UAAU;gBACV,SAAS,EAAE,UAAU;gBACrB,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;aACjB,CAAC;SACH;QAED,OAAO,CAAC,IAAI,CACV,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,0CAA0C,EAC1C,SAAS,EACT,cAAc,GAAG,CAAC,UAAU,eAAe,GAAG,CAAC,SAAS,UAAU,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CACzF,CAAC;QACF,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,sBAAsB,CACtD,oBAAoB,EACpB,GAAG,CACJ,CAAC;QAEF,QAAQ,CAAC,OAAO;aACb,OAAO,CAAC,wCAAwC,EAAE,EAAE,CAAC;aACrD,MAAM,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;aACpE,GAAG,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;QAE7B,OAAO,CAAC,IAAI,CACV,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,0CAA0C,EAC1C,QAAQ,EACR,aAAa,UAAU,oBAAoB,GAAG,CAAC,WAAW,gBAAgB,UAAU,CAClF,MAAA,MAAA,GAAG,CAAC,KAAK,0CAAE,UAAU,mCAAI,GAAG,CAC7B,cAAc,UAAU,CAAC,MAAA,MAAA,GAAG,CAAC,KAAK,0CAAE,QAAQ,mCAAI,GAAG,CAAC,EAAE,CACxD,CAAC;KACH;AACH,CAAC,CAAC","sourcesContent":["// 解决 Forwards 拉取历史数据的调度器\n// 该文件会定期扫描所有 Terminal 的 ServiceInfo,提取出所有支持 Forwards 拉取的序列。\n// 然后对每个序列,向对应的 IngestInterestRate Service 发送拉取请求,补齐历史数据。\n// 使用 Token Bucket 控制每个数据源的请求速率,避免过载。\n\nimport {\n IIngestInterestRateRequest,\n IInterestRateServiceMetadata,\n ISeriesIngestResult,\n} from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { decodePath, formatTime, tokenBucket } from '@yuants/utils';\nimport { findInterestRateEndTimeForward } from './sql-helpers';\n\nconst terminal = Terminal.fromNodeEnv();\n\nexport const handleIngestInterestRateForward = async (\n product_id: string,\n meta: IInterestRateServiceMetadata,\n signal: AbortSignal,\n) => {\n const [datasource_id] = decodePath(product_id);\n // 控制速率:每个数据源每秒钟只能请求一次\n await tokenBucket(`interest_rate_forwards:${datasource_id}`).acquire(1, signal);\n\n {\n let req: IIngestInterestRateRequest;\n if (meta.direction === 'forward') {\n const endTime = await findInterestRateEndTimeForward(terminal, product_id);\n const time = endTime ? new Date(endTime).getTime() : 0;\n\n req = {\n product_id: product_id,\n direction: 'forward',\n time,\n };\n } else {\n // backward\n req = {\n product_id,\n direction: 'backward',\n time: Date.now(),\n };\n }\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Forward]',\n 'Request',\n `product_id=${req.product_id}, direction=${req.direction}, time=${formatTime(req.time)}`,\n );\n const res = await terminal.client.requestForResponseData<IIngestInterestRateRequest, ISeriesIngestResult>(\n 'IngestInterestRate',\n req,\n );\n\n terminal.metrics\n .counter('series_collector_forwards_ingest_count', '')\n .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate' })\n .inc(res.wrote_count || 0);\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Forward]',\n 'Result',\n `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${formatTime(\n res.range?.start_time ?? NaN,\n )}, end_time=${formatTime(res.range?.end_time ?? NaN)}`,\n );\n }\n};\n"]}
@@ -1,5 +1,4 @@
1
- import './backwards-interest-rate';
2
1
  import './backwards-ohlc';
3
- import './forwards-interest-rate';
4
2
  import './forwards-ohlc';
3
+ import './interest-rate';
5
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/series-collector/index.ts"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAC;AACnC,OAAO,kBAAkB,CAAC;AAC1B,OAAO,0BAA0B,CAAC;AAClC,OAAO,iBAAiB,CAAC","sourcesContent":["import './backwards-interest-rate';\nimport './backwards-ohlc';\nimport './forwards-interest-rate';\nimport './forwards-ohlc';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/series-collector/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,iBAAiB,CAAC;AACzB,OAAO,iBAAiB,CAAC","sourcesContent":["import './backwards-ohlc';\nimport './forwards-ohlc';\nimport './interest-rate';\n"]}
@@ -0,0 +1,43 @@
1
+ import { listWatch } from '@yuants/utils';
2
+ import { defer, map, Observable, repeat, retry } from 'rxjs';
3
+ import { handleIngestInterestRateBackward } from './backwards-interest-rate';
4
+ import { listInterestRateSeriesIds } from './discovery';
5
+ import { handleIngestInterestRateForward } from './forwards-interest-rate';
6
+ import { handleInterestRatePatch } from './patch-interest-rate';
7
+ defer(() => listInterestRateSeriesIds())
8
+ .pipe(retry({ delay: 1000 }), repeat({ delay: 60000 }), map((x) => Array.from(x.entries())), listWatch((x) => x[0], ([product_id, meta]) => new Observable((sub) => {
9
+ // 处理每个利率品种任务: (forward / backward / patch),都需要独立调度
10
+ const abortController = new AbortController();
11
+ sub.add(() => {
12
+ abortController.abort();
13
+ });
14
+ // 先处理前向任务
15
+ const forwardTask = defer(async () => {
16
+ await handleIngestInterestRateForward(product_id, meta, abortController.signal);
17
+ })
18
+ .pipe(retry(), repeat())
19
+ .subscribe();
20
+ sub.add(() => {
21
+ forwardTask.unsubscribe();
22
+ });
23
+ // 设置后向任务
24
+ const backwardTask = defer(async () => {
25
+ await handleIngestInterestRateBackward(product_id, meta, abortController.signal);
26
+ })
27
+ .pipe(retry(), repeat())
28
+ .subscribe();
29
+ sub.add(() => {
30
+ backwardTask.unsubscribe();
31
+ });
32
+ // 设置补齐任务
33
+ const patchTask = defer(async () => {
34
+ await handleInterestRatePatch(product_id, meta, abortController.signal);
35
+ })
36
+ .pipe(retry(), repeat())
37
+ .subscribe();
38
+ sub.add(() => {
39
+ patchTask.unsubscribe();
40
+ });
41
+ })))
42
+ .subscribe();
43
+ //# sourceMappingURL=interest-rate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interest-rate.js","sourceRoot":"","sources":["../../src/series-collector/interest-rate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7D,OAAO,EAAE,gCAAgC,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,+BAA+B,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,KAAK,CAAC,GAAG,EAAE,CAAC,yBAAyB,EAAE,CAAC;KACrC,IAAI,CACH,KAAK,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EACtB,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EACxB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,EACnC,SAAS,CACP,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EACX,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE,CACrB,IAAI,UAAU,CAAC,CAAC,GAAG,EAAE,EAAE;IACrB,mDAAmD;IACnD,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;IAE9C,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;QACX,eAAe,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,UAAU;IACV,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE;QACnC,MAAM,+BAA+B,CAAC,UAAU,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAClF,CAAC,CAAC;SACC,IAAI,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC;SACvB,SAAS,EAAE,CAAC;IAEf,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;QACX,WAAW,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,SAAS;IACT,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE;QACpC,MAAM,gCAAgC,CAAC,UAAU,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IACnF,CAAC,CAAC;SACC,IAAI,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC;SACvB,SAAS,EAAE,CAAC;IAEf,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;QACX,YAAY,CAAC,WAAW,EAAE,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,SAAS;IACT,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE;QACjC,MAAM,uBAAuB,CAAC,UAAU,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAC1E,CAAC,CAAC;SACC,IAAI,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC;SACvB,SAAS,EAAE,CAAC;IAEf,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;QACX,SAAS,CAAC,WAAW,EAAE,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CACL,CACF;KACA,SAAS,EAAE,CAAC","sourcesContent":["import { listWatch } from '@yuants/utils';\nimport { defer, map, Observable, repeat, retry } from 'rxjs';\nimport { handleIngestInterestRateBackward } from './backwards-interest-rate';\nimport { listInterestRateSeriesIds } from './discovery';\nimport { handleIngestInterestRateForward } from './forwards-interest-rate';\nimport { handleInterestRatePatch } from './patch-interest-rate';\n\ndefer(() => listInterestRateSeriesIds())\n .pipe(\n retry({ delay: 1000 }),\n repeat({ delay: 60000 }),\n map((x) => Array.from(x.entries())),\n listWatch(\n (x) => x[0],\n ([product_id, meta]) =>\n new Observable((sub) => {\n // 处理每个利率品种任务: (forward / backward / patch),都需要独立调度\n const abortController = new AbortController();\n\n sub.add(() => {\n abortController.abort();\n });\n\n // 先处理前向任务\n const forwardTask = defer(async () => {\n await handleIngestInterestRateForward(product_id, meta, abortController.signal);\n })\n .pipe(retry(), repeat())\n .subscribe();\n\n sub.add(() => {\n forwardTask.unsubscribe();\n });\n\n // 设置后向任务\n const backwardTask = defer(async () => {\n await handleIngestInterestRateBackward(product_id, meta, abortController.signal);\n })\n .pipe(retry(), repeat())\n .subscribe();\n\n sub.add(() => {\n backwardTask.unsubscribe();\n });\n\n // 设置补齐任务\n const patchTask = defer(async () => {\n await handleInterestRatePatch(product_id, meta, abortController.signal);\n })\n .pipe(retry(), repeat())\n .subscribe();\n\n sub.add(() => {\n patchTask.unsubscribe();\n });\n }),\n ),\n )\n .subscribe();\n"]}
@@ -0,0 +1,59 @@
1
+ import { Terminal } from '@yuants/protocol';
2
+ import { escapeSQL, requestSQL } from '@yuants/sql';
3
+ import { decodePath, formatTime, tokenBucket } from '@yuants/utils';
4
+ const terminal = Terminal.fromNodeEnv();
5
+ // Patch 任务:查找数据缺口并进行补齐
6
+ export const handleInterestRatePatch = async (product_id, meta, signal) => {
7
+ var _a, _b, _c, _d;
8
+ const [datasource_id] = decodePath(product_id);
9
+ await tokenBucket(`interest_rate:patch:${datasource_id}`).acquire(1, signal);
10
+ const [record] = await requestSQL(terminal, `
11
+ WITH reversed_ranges AS (
12
+ SELECT
13
+ start_time,
14
+ end_time,
15
+ LEAD(end_time) OVER (
16
+ PARTITION BY table_name, series_id
17
+ ORDER BY start_time DESC
18
+ ) AS next_end_time -- 注意:倒序时 LEAD 是前一个区间
19
+ FROM series_data_range
20
+ WHERE table_name = 'interest_rate'
21
+ AND series_id = ${escapeSQL(product_id)}
22
+ )
23
+ SELECT
24
+ next_end_time AS gap_start_time, -- 前一个区间的结束时间
25
+ start_time AS gap_end_time -- 当前区间的开始时间
26
+ FROM reversed_ranges
27
+ WHERE next_end_time IS NOT NULL
28
+ AND start_time > next_end_time -- 有空缺
29
+ ORDER BY start_time DESC -- 从最新开始
30
+ LIMIT 1;
31
+ `);
32
+ // no gap
33
+ if (!record)
34
+ return;
35
+ const gapStartTime = new Date(record.gap_start_time).getTime();
36
+ const gapEndTime = new Date(record.gap_end_time).getTime();
37
+ console.info(formatTime(Date.now()), '[SeriesCollector][InterestRate][Patch]', 'FindGap', `series=${product_id}, from=${formatTime(gapStartTime)}, to=${formatTime(gapEndTime)}`);
38
+ let req;
39
+ if (meta.direction === 'forward') {
40
+ // forward patch
41
+ req = {
42
+ product_id: product_id,
43
+ direction: 'forward',
44
+ time: gapStartTime,
45
+ };
46
+ }
47
+ else {
48
+ // backward patch
49
+ req = {
50
+ product_id: product_id,
51
+ direction: 'backward',
52
+ time: gapEndTime,
53
+ };
54
+ }
55
+ console.info(formatTime(Date.now()), '[SeriesCollector][InterestRate][Patch]', product_id, 'PatchRequest', `direction=${req.direction}, time=${formatTime(req.time)}`);
56
+ const res = await terminal.client.requestForResponseData('IngestInterestRate', req);
57
+ console.info(formatTime(Date.now()), '[SeriesCollector][InterestRate][Patch]', product_id, 'PatchBackwardResult', `ingested_count=${res.wrote_count}, start_time=${formatTime((_b = (_a = res.range) === null || _a === void 0 ? void 0 : _a.start_time) !== null && _b !== void 0 ? _b : NaN)}, end_time=${formatTime((_d = (_c = res.range) === null || _c === void 0 ? void 0 : _c.end_time) !== null && _d !== void 0 ? _d : NaN)}`);
58
+ };
59
+ //# sourceMappingURL=patch-interest-rate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-interest-rate.js","sourceRoot":"","sources":["../../src/series-collector/patch-interest-rate.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEpE,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;AAExC,uBAAuB;AACvB,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,EAC1C,UAAkB,EAClB,IAAkC,EAClC,MAAmB,EACnB,EAAE;;IACF,MAAM,CAAC,aAAa,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IAC/C,MAAM,WAAW,CAAC,uBAAuB,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7E,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,UAAU,CAC/B,QAAQ,EACR;;;;;;;;;;;wBAWoB,SAAS,CAAC,UAAU,CAAC;;;;;;;;;;KAUxC,CACF,CAAC;IAEF,SAAS;IACT,IAAI,CAAC,MAAM;QAAE,OAAO;IAEpB,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE,CAAC;IAC/D,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC;IAE3D,OAAO,CAAC,IAAI,CACV,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,wCAAwC,EACxC,SAAS,EACT,UAAU,UAAU,UAAU,UAAU,CAAC,YAAY,CAAC,QAAQ,UAAU,CAAC,UAAU,CAAC,EAAE,CACvF,CAAC;IAEF,IAAI,GAA+B,CAAC;IAEpC,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE;QAChC,gBAAgB;QAChB,GAAG,GAAG;YACJ,UAAU,EAAE,UAAU;YACtB,SAAS,EAAE,SAAkB;YAC7B,IAAI,EAAE,YAAY;SACnB,CAAC;KACH;SAAM;QACL,iBAAiB;QACjB,GAAG,GAAG;YACJ,UAAU,EAAE,UAAU;YACtB,SAAS,EAAE,UAAmB;YAC9B,IAAI,EAAE,UAAU;SACjB,CAAC;KACH;IAED,OAAO,CAAC,IAAI,CACV,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,wCAAwC,EACxC,UAAU,EACV,cAAc,EACd,aAAa,GAAG,CAAC,SAAS,UAAU,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAC3D,CAAC;IAEF,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,sBAAsB,CACtD,oBAAoB,EACpB,GAAG,CACJ,CAAC;IAEF,OAAO,CAAC,IAAI,CACV,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,wCAAwC,EACxC,UAAU,EACV,qBAAqB,EACrB,kBAAkB,GAAG,CAAC,WAAW,gBAAgB,UAAU,CACzD,MAAA,MAAA,GAAG,CAAC,KAAK,0CAAE,UAAU,mCAAI,GAAG,CAC7B,cAAc,UAAU,CAAC,MAAA,MAAA,GAAG,CAAC,KAAK,0CAAE,QAAQ,mCAAI,GAAG,CAAC,EAAE,CACxD,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import {\n IIngestInterestRateRequest,\n IInterestRateServiceMetadata,\n ISeriesIngestResult,\n} from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\nimport { decodePath, formatTime, tokenBucket } from '@yuants/utils';\n\nconst terminal = Terminal.fromNodeEnv();\n\n// Patch 任务:查找数据缺口并进行补齐\nexport const handleInterestRatePatch = async (\n product_id: string,\n meta: IInterestRateServiceMetadata,\n signal: AbortSignal,\n) => {\n const [datasource_id] = decodePath(product_id);\n await tokenBucket(`interest_rate:patch:${datasource_id}`).acquire(1, signal);\n const [record] = await requestSQL<{ gap_start_time: string; gap_end_time: string }[]>(\n terminal,\n `\nWITH reversed_ranges AS (\n SELECT \n start_time,\n end_time,\n LEAD(end_time) OVER (\n PARTITION BY table_name, series_id \n ORDER BY start_time DESC\n ) AS next_end_time -- 注意:倒序时 LEAD 是前一个区间\n FROM series_data_range\n WHERE table_name = 'interest_rate' \n AND series_id = ${escapeSQL(product_id)}\n)\nSELECT \n next_end_time AS gap_start_time, -- 前一个区间的结束时间\n start_time AS gap_end_time -- 当前区间的开始时间\nFROM reversed_ranges\nWHERE next_end_time IS NOT NULL \n AND start_time > next_end_time -- 有空缺\nORDER BY start_time DESC -- 从最新开始\nLIMIT 1;\n `,\n );\n\n // no gap\n if (!record) return;\n\n const gapStartTime = new Date(record.gap_start_time).getTime();\n const gapEndTime = new Date(record.gap_end_time).getTime();\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Patch]',\n 'FindGap',\n `series=${product_id}, from=${formatTime(gapStartTime)}, to=${formatTime(gapEndTime)}`,\n );\n\n let req: IIngestInterestRateRequest;\n\n if (meta.direction === 'forward') {\n // forward patch\n req = {\n product_id: product_id,\n direction: 'forward' as const,\n time: gapStartTime,\n };\n } else {\n // backward patch\n req = {\n product_id: product_id,\n direction: 'backward' as const,\n time: gapEndTime,\n };\n }\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Patch]',\n product_id,\n 'PatchRequest',\n `direction=${req.direction}, time=${formatTime(req.time)}`,\n );\n\n const res = await terminal.client.requestForResponseData<IIngestInterestRateRequest, ISeriesIngestResult>(\n 'IngestInterestRate',\n req,\n );\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Patch]',\n product_id,\n 'PatchBackwardResult',\n `ingested_count=${res.wrote_count}, start_time=${formatTime(\n res.range?.start_time ?? NaN,\n )}, end_time=${formatTime(res.range?.end_time ?? NaN)}`,\n );\n};\n"]}
@@ -0,0 +1,11 @@
1
+ import { escapeSQL, requestSQL } from '@yuants/sql';
2
+ /**
3
+ * 查找某个利率品种系列ID的最新结束时间(for forward task)
4
+ * @param terminal
5
+ * @param product_id
6
+ * @returns
7
+ */
8
+ export const findInterestRateEndTimeForward = (terminal, product_id) => requestSQL(terminal, `select end_time from series_data_range where series_id = ${escapeSQL(product_id)} and table_name = 'interest_rate' order by end_time desc limit 1`).then((records) => { var _a; return (_a = records === null || records === void 0 ? void 0 : records[0]) === null || _a === void 0 ? void 0 : _a.end_time; });
9
+ export const findInterestRateStartTimeBackward = (terminal, product_id) => requestSQL(terminal, `select start_time from series_data_range where series_id = ${escapeSQL(product_id)} and table_name = 'interest_rate' order by start_time asc limit 1`).then((records) => { var _a; return (_a = records === null || records === void 0 ? void 0 : records[0]) === null || _a === void 0 ? void 0 : _a.start_time; });
10
+ export const findOHLCEndTimeForward = (terminal, series_id) => requestSQL(terminal, `select end_time from series_data_range where series_id = ${escapeSQL(series_id)} and table_name = 'ohlc_v2' order by end_time desc limit 1`).then((records) => { var _a; return (_a = records === null || records === void 0 ? void 0 : records[0]) === null || _a === void 0 ? void 0 : _a.end_time; });
11
+ //# sourceMappingURL=sql-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-helpers.js","sourceRoot":"","sources":["../../src/series-collector/sql-helpers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,QAAkB,EAAE,UAAkB,EAAE,EAAE,CACvF,UAAU,CAKR,QAAQ,EACR,4DAA4D,SAAS,CACnE,UAAU,CACX,kEAAkE,CACpE,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,WAAC,OAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAG,CAAC,CAAC,0CAAE,QAAQ,CAAA,EAAA,CAAC,CAAC;AAE9C,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,QAAkB,EAAE,UAAkB,EAAE,EAAE,CAC1F,UAAU,CAKR,QAAQ,EACR,8DAA8D,SAAS,CACrE,UAAU,CACX,mEAAmE,CACrE,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,WAAC,OAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAG,CAAC,CAAC,0CAAE,UAAU,CAAA,EAAA,CAAC,CAAC;AAEhD,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,QAAkB,EAAE,SAAiB,EAAE,EAAE,CAC9E,UAAU,CAKR,QAAQ,EACR,4DAA4D,SAAS,CACnE,SAAS,CACV,4DAA4D,CAC9D,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,WAAC,OAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAG,CAAC,CAAC,0CAAE,QAAQ,CAAA,EAAA,CAAC,CAAC","sourcesContent":["import { Terminal } from '@yuants/protocol';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\n\n/**\n * 查找某个利率品种系列ID的最新结束时间(for forward task)\n * @param terminal\n * @param product_id\n * @returns\n */\nexport const findInterestRateEndTimeForward = (terminal: Terminal, product_id: string) =>\n requestSQL<\n {\n end_time: string;\n }[]\n >(\n terminal,\n `select end_time from series_data_range where series_id = ${escapeSQL(\n product_id,\n )} and table_name = 'interest_rate' order by end_time desc limit 1`,\n ).then((records) => records?.[0]?.end_time);\n\nexport const findInterestRateStartTimeBackward = (terminal: Terminal, product_id: string) =>\n requestSQL<\n {\n start_time: string;\n }[]\n >(\n terminal,\n `select start_time from series_data_range where series_id = ${escapeSQL(\n product_id,\n )} and table_name = 'interest_rate' order by start_time asc limit 1`,\n ).then((records) => records?.[0]?.start_time);\n\nexport const findOHLCEndTimeForward = (terminal: Terminal, series_id: string) =>\n requestSQL<\n {\n end_time: string;\n }[]\n >(\n terminal,\n `select end_time from series_data_range where series_id = ${escapeSQL(\n series_id,\n )} and table_name = 'ohlc_v2' order by end_time desc limit 1`,\n ).then((records) => records?.[0]?.end_time);\n"]}
@@ -1,2 +1,4 @@
1
- export {};
1
+ /// <reference types="node" />
2
+ import { IInterestRateServiceMetadata } from '@yuants/exchange';
3
+ export declare const handleIngestInterestRateBackward: (product_id: string, meta: IInterestRateServiceMetadata, signal: AbortSignal) => Promise<void>;
2
4
  //# sourceMappingURL=backwards-interest-rate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"backwards-interest-rate.d.ts","sourceRoot":"","sources":["../../src/series-collector/backwards-interest-rate.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"backwards-interest-rate.d.ts","sourceRoot":"","sources":["../../src/series-collector/backwards-interest-rate.ts"],"names":[],"mappings":";AAKA,OAAO,EAEL,4BAA4B,EAE7B,MAAM,kBAAkB,CAAC;AAO1B,eAAO,MAAM,gCAAgC,eAC/B,MAAM,QACZ,4BAA4B,UAC1B,WAAW,kBAgDpB,CAAC"}
@@ -4,72 +4,40 @@
4
4
  // 然后对每个序列,向对应的 IngestInterestRate Service 发送拉取请求,补齐历史数据。
5
5
  // 使用 Token Bucket 控制每个数据源的请求速率,避免过载。
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- const exchange_1 = require("@yuants/exchange");
7
+ exports.handleIngestInterestRateBackward = void 0;
8
8
  const protocol_1 = require("@yuants/protocol");
9
- const sql_1 = require("@yuants/sql");
10
9
  const utils_1 = require("@yuants/utils");
11
- const rxjs_1 = require("rxjs");
10
+ const sql_helpers_1 = require("./sql-helpers");
12
11
  const terminal = protocol_1.Terminal.fromNodeEnv();
13
- const listBackwardSeriesIds = async () => {
14
- const product_ids = await (0, sql_1.requestSQL)(terminal, `select product_id from product`);
15
- console.time('[SeriesCollector][InterestRate][Backwards] calc');
16
- const series_ids = new Set();
17
- for (const terminalInfo of terminal.terminalInfos) {
18
- for (const serviceInfo of Object.values(terminalInfo.serviceInfo || {})) {
19
- if (serviceInfo.method !== 'IngestInterestRate')
20
- continue;
21
- try {
22
- const meta = (0, exchange_1.parseInterestRateServiceMetadataFromSchema)(serviceInfo.schema);
23
- if (meta.direction !== 'backward')
24
- continue;
25
- for (const { product_id } of product_ids) {
26
- if (!product_id.startsWith(meta.product_id_prefix))
27
- continue;
28
- series_ids.add(product_id);
29
- }
30
- }
31
- finally {
32
- }
33
- }
12
+ const handleIngestInterestRateBackward = async (product_id, meta, signal) => {
13
+ var _a, _b, _c, _d;
14
+ const [datasource_id] = (0, utils_1.decodePath)(product_id);
15
+ // 控制速率:每个数据源每秒钟只能请求一次
16
+ await (0, utils_1.tokenBucket)(`interest_rate:backward:${datasource_id}`).acquire(1, signal);
17
+ let req;
18
+ if (meta.direction === 'backward') {
19
+ const startTime = await (0, sql_helpers_1.findInterestRateStartTimeBackward)(terminal, product_id);
20
+ const start_time = startTime ? new Date(startTime).getTime() : Date.now();
21
+ req = {
22
+ product_id: product_id,
23
+ direction: 'backward',
24
+ time: start_time,
25
+ };
34
26
  }
35
- console.timeEnd('[SeriesCollector][InterestRate][Backwards] calc');
36
- return series_ids;
27
+ else {
28
+ req = {
29
+ product_id: product_id,
30
+ direction: 'forward',
31
+ time: 0,
32
+ };
33
+ }
34
+ console.info((0, utils_1.formatTime)(Date.now()), '[SeriesCollector][InterestRate][Backward]', 'Request', `product_id=${req.product_id}, direction=${req.direction}, time=${(0, utils_1.formatTime)(req.time)}`);
35
+ const res = await terminal.client.requestForResponseData('IngestInterestRate', req);
36
+ terminal.metrics
37
+ .counter('series_collector_ingest_count', '')
38
+ .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate', task: 'backward' })
39
+ .inc(res.wrote_count || 0);
40
+ console.info((0, utils_1.formatTime)(Date.now()), '[SeriesCollector][InterestRate][Backward]', 'Result', `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${(0, utils_1.formatTime)((_b = (_a = res.range) === null || _a === void 0 ? void 0 : _a.start_time) !== null && _b !== void 0 ? _b : NaN)}, end_time=${(0, utils_1.formatTime)((_d = (_c = res.range) === null || _c === void 0 ? void 0 : _c.end_time) !== null && _d !== void 0 ? _d : NaN)}`);
37
41
  };
38
- (0, rxjs_1.defer)(async () => {
39
- const time = Date.now();
40
- const series_ids = await listBackwardSeriesIds();
41
- console.log(`[SeriesCollector][InterestRate][Backwards] Found ${series_ids.size} series to collect backwards data for. (${(0, utils_1.formatTime)(Date.now() - time)})`);
42
- await Promise.all([...series_ids].map(async (product_id) => {
43
- var _a, _b, _c, _d;
44
- try {
45
- const [datasource_id] = (0, utils_1.decodePath)(product_id);
46
- // 控制速率:每个数据源每秒钟只能请求一次
47
- await (0, utils_1.tokenBucket)(`interest_rate_backwards:${datasource_id}`, {
48
- refillInterval: 1000,
49
- capacity: 1,
50
- }).acquire();
51
- {
52
- const [record] = await (0, sql_1.requestSQL)(terminal, `select start_time from series_data_range where series_id = ${(0, sql_1.escapeSQL)(product_id)} and table_name = 'interest_rate' order by start_time limit 1`);
53
- const start_time = record ? new Date(record.start_time).getTime() : Date.now();
54
- const req = {
55
- product_id: product_id,
56
- direction: 'backward',
57
- time: start_time,
58
- };
59
- console.info((0, utils_1.formatTime)(Date.now()), 'DispatchIngestInterestRateRequest', `product_id=${product_id}, time=${(0, utils_1.formatTime)(start_time)}`);
60
- const res = await terminal.client.requestForResponseData('IngestInterestRate', req);
61
- terminal.metrics
62
- .counter('series_collector_backwards_ingest_count', '')
63
- .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate' })
64
- .inc(res.wrote_count || 0);
65
- console.info((0, utils_1.formatTime)(Date.now()), 'DispatchIngestInterestRateResult', `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${(0, utils_1.formatTime)((_b = (_a = res.range) === null || _a === void 0 ? void 0 : _a.start_time) !== null && _b !== void 0 ? _b : NaN)}, end_time=${(0, utils_1.formatTime)((_d = (_c = res.range) === null || _c === void 0 ? void 0 : _c.end_time) !== null && _d !== void 0 ? _d : NaN)}`);
66
- }
67
- }
68
- catch (e) {
69
- console.info((0, utils_1.formatTime)(Date.now()), 'DispatchIngestInterestRateError', `series_id=${product_id}`, e);
70
- }
71
- }));
72
- })
73
- .pipe((0, rxjs_1.retry)({ delay: 1000 }), (0, rxjs_1.repeat)({ delay: 1000 }))
74
- .subscribe();
42
+ exports.handleIngestInterestRateBackward = handleIngestInterestRateBackward;
75
43
  //# sourceMappingURL=backwards-interest-rate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"backwards-interest-rate.js","sourceRoot":"","sources":["../../src/series-collector/backwards-interest-rate.ts"],"names":[],"mappings":"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","sourcesContent":["// 解决 Backwards 拉取历史数据的调度器\n// 该文件会定期扫描所有 Terminal 的 ServiceInfo,提取出所有支持 Backwards 拉取的序列。\n// 然后对每个序列,向对应的 IngestInterestRate Service 发送拉取请求,补齐历史数据。\n// 使用 Token Bucket 控制每个数据源的请求速率,避免过载。\n\nimport {\n IIngestInterestRateRequest,\n ISeriesIngestResult,\n parseInterestRateServiceMetadataFromSchema,\n} from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\nimport { decodePath, formatTime, tokenBucket } from '@yuants/utils';\nimport { defer, repeat, retry } from 'rxjs';\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst listBackwardSeriesIds = async () => {\n const product_ids = await requestSQL<{ product_id: string }[]>(terminal, `select product_id from product`);\n\n console.time('[SeriesCollector][InterestRate][Backwards] calc');\n\n const series_ids = new Set<string>();\n for (const terminalInfo of terminal.terminalInfos) {\n for (const serviceInfo of Object.values(terminalInfo.serviceInfo || {})) {\n if (serviceInfo.method !== 'IngestInterestRate') continue;\n try {\n const meta = parseInterestRateServiceMetadataFromSchema(serviceInfo.schema);\n if (meta.direction !== 'backward') continue;\n\n for (const { product_id } of product_ids) {\n if (!product_id.startsWith(meta.product_id_prefix)) continue;\n series_ids.add(product_id);\n }\n } finally {\n }\n }\n }\n\n console.timeEnd('[SeriesCollector][InterestRate][Backwards] calc');\n\n return series_ids;\n};\n\ndefer(async () => {\n const time = Date.now();\n const series_ids = await listBackwardSeriesIds();\n console.log(\n `[SeriesCollector][InterestRate][Backwards] Found ${\n series_ids.size\n } series to collect backwards data for. (${formatTime(Date.now() - time)})`,\n );\n\n await Promise.all(\n [...series_ids].map(async (product_id) => {\n try {\n const [datasource_id] = decodePath(product_id);\n // 控制速率:每个数据源每秒钟只能请求一次\n await tokenBucket(`interest_rate_backwards:${datasource_id}`, {\n refillInterval: 1000,\n capacity: 1,\n }).acquire();\n {\n const [record] = await requestSQL<\n {\n start_time: string;\n }[]\n >(\n terminal,\n `select start_time from series_data_range where series_id = ${escapeSQL(\n product_id,\n )} and table_name = 'interest_rate' order by start_time limit 1`,\n );\n const start_time = record ? new Date(record.start_time).getTime() : Date.now();\n\n const req: IIngestInterestRateRequest = {\n product_id: product_id,\n direction: 'backward',\n time: start_time,\n };\n\n console.info(\n formatTime(Date.now()),\n 'DispatchIngestInterestRateRequest',\n `product_id=${product_id}, time=${formatTime(start_time)}`,\n );\n\n const res = await terminal.client.requestForResponseData<\n IIngestInterestRateRequest,\n ISeriesIngestResult\n >('IngestInterestRate', req);\n\n terminal.metrics\n .counter('series_collector_backwards_ingest_count', '')\n .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate' })\n .inc(res.wrote_count || 0);\n\n console.info(\n formatTime(Date.now()),\n 'DispatchIngestInterestRateResult',\n `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${formatTime(\n res.range?.start_time ?? NaN,\n )}, end_time=${formatTime(res.range?.end_time ?? NaN)}`,\n );\n }\n } catch (e) {\n console.info(formatTime(Date.now()), 'DispatchIngestInterestRateError', `series_id=${product_id}`, e);\n }\n }),\n );\n})\n .pipe(retry({ delay: 1000 }), repeat({ delay: 1000 }))\n .subscribe();\n"]}
1
+ {"version":3,"file":"backwards-interest-rate.js","sourceRoot":"","sources":["../../src/series-collector/backwards-interest-rate.ts"],"names":[],"mappings":";AAAA,0BAA0B;AAC1B,6DAA6D;AAC7D,yDAAyD;AACzD,qCAAqC;;;AAOrC,+CAA4C;AAC5C,yCAAoE;AACpE,+CAAkE;AAElE,MAAM,QAAQ,GAAG,mBAAQ,CAAC,WAAW,EAAE,CAAC;AAEjC,MAAM,gCAAgC,GAAG,KAAK,EACnD,UAAkB,EAClB,IAAkC,EAClC,MAAmB,EACnB,EAAE;;IACF,MAAM,CAAC,aAAa,CAAC,GAAG,IAAA,kBAAU,EAAC,UAAU,CAAC,CAAC;IAC/C,sBAAsB;IACtB,MAAM,IAAA,mBAAW,EAAC,0BAA0B,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAChF,IAAI,GAA+B,CAAC;IACpC,IAAI,IAAI,CAAC,SAAS,KAAK,UAAU,EAAE;QACjC,MAAM,SAAS,GAAG,MAAM,IAAA,+CAAiC,EAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAChF,MAAM,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QAE1E,GAAG,GAAG;YACJ,UAAU,EAAE,UAAU;YACtB,SAAS,EAAE,UAAU;YACrB,IAAI,EAAE,UAAU;SACjB,CAAC;KACH;SAAM;QACL,GAAG,GAAG;YACJ,UAAU,EAAE,UAAU;YACtB,SAAS,EAAE,SAAS;YACpB,IAAI,EAAE,CAAC;SACR,CAAC;KACH;IAED,OAAO,CAAC,IAAI,CACV,IAAA,kBAAU,EAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,2CAA2C,EAC3C,SAAS,EACT,cAAc,GAAG,CAAC,UAAU,eAAe,GAAG,CAAC,SAAS,UAAU,IAAA,kBAAU,EAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CACzF,CAAC;IAEF,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,sBAAsB,CACtD,oBAAoB,EACpB,GAAG,CACJ,CAAC;IAEF,QAAQ,CAAC,OAAO;SACb,OAAO,CAAC,+BAA+B,EAAE,EAAE,CAAC;SAC5C,MAAM,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;SACtF,GAAG,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;IAE7B,OAAO,CAAC,IAAI,CACV,IAAA,kBAAU,EAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,2CAA2C,EAC3C,QAAQ,EACR,aAAa,UAAU,oBAAoB,GAAG,CAAC,WAAW,gBAAgB,IAAA,kBAAU,EAClF,MAAA,MAAA,GAAG,CAAC,KAAK,0CAAE,UAAU,mCAAI,GAAG,CAC7B,cAAc,IAAA,kBAAU,EAAC,MAAA,MAAA,GAAG,CAAC,KAAK,0CAAE,QAAQ,mCAAI,GAAG,CAAC,EAAE,CACxD,CAAC;AACJ,CAAC,CAAC;AAnDW,QAAA,gCAAgC,oCAmD3C","sourcesContent":["// 解决 Backwards 拉取历史数据的调度器\n// 该文件会定期扫描所有 Terminal 的 ServiceInfo,提取出所有支持 Backwards 拉取的序列。\n// 然后对每个序列,向对应的 IngestInterestRate Service 发送拉取请求,补齐历史数据。\n// 使用 Token Bucket 控制每个数据源的请求速率,避免过载。\n\nimport {\n IIngestInterestRateRequest,\n IInterestRateServiceMetadata,\n ISeriesIngestResult,\n} from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { decodePath, formatTime, tokenBucket } from '@yuants/utils';\nimport { findInterestRateStartTimeBackward } from './sql-helpers';\n\nconst terminal = Terminal.fromNodeEnv();\n\nexport const handleIngestInterestRateBackward = async (\n product_id: string,\n meta: IInterestRateServiceMetadata,\n signal: AbortSignal,\n) => {\n const [datasource_id] = decodePath(product_id);\n // 控制速率:每个数据源每秒钟只能请求一次\n await tokenBucket(`interest_rate:backward:${datasource_id}`).acquire(1, signal);\n let req: IIngestInterestRateRequest;\n if (meta.direction === 'backward') {\n const startTime = await findInterestRateStartTimeBackward(terminal, product_id);\n const start_time = startTime ? new Date(startTime).getTime() : Date.now();\n\n req = {\n product_id: product_id,\n direction: 'backward',\n time: start_time,\n };\n } else {\n req = {\n product_id: product_id,\n direction: 'forward',\n time: 0,\n };\n }\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Backward]',\n 'Request',\n `product_id=${req.product_id}, direction=${req.direction}, time=${formatTime(req.time)}`,\n );\n\n const res = await terminal.client.requestForResponseData<IIngestInterestRateRequest, ISeriesIngestResult>(\n 'IngestInterestRate',\n req,\n );\n\n terminal.metrics\n .counter('series_collector_ingest_count', '')\n .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate', task: 'backward' })\n .inc(res.wrote_count || 0);\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Backward]',\n 'Result',\n `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${formatTime(\n res.range?.start_time ?? NaN,\n )}, end_time=${formatTime(res.range?.end_time ?? NaN)}`,\n );\n};\n"]}
@@ -0,0 +1,7 @@
1
+ import { IInterestRateServiceMetadata } from '@yuants/exchange';
2
+ /**
3
+ * 列出所有支持利率的品种系列ID以及对应的 Service Metadata
4
+ * @returns
5
+ */
6
+ export declare const listInterestRateSeriesIds: () => Promise<Map<string, IInterestRateServiceMetadata>>;
7
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/series-collector/discovery.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,4BAA4B,EAA8C,MAAM,kBAAkB,CAAC;AAM5G;;;GAGG;AACH,eAAO,MAAM,yBAAyB,0DAwBrC,CAAC"}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ // 发现所有支持利率的品种系列ID
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.listInterestRateSeriesIds = void 0;
5
+ const exchange_1 = require("@yuants/exchange");
6
+ const protocol_1 = require("@yuants/protocol");
7
+ const sql_1 = require("@yuants/sql");
8
+ const terminal = protocol_1.Terminal.fromNodeEnv();
9
+ /**
10
+ * 列出所有支持利率的品种系列ID以及对应的 Service Metadata
11
+ * @returns
12
+ */
13
+ const listInterestRateSeriesIds = async () => {
14
+ const product_ids = await (0, sql_1.requestSQL)(terminal,
15
+ // 必须是支持利率的品种
16
+ `select product_id from product where no_interest_rate = false`);
17
+ const series_ids = new Map();
18
+ for (const terminalInfo of terminal.terminalInfos) {
19
+ for (const serviceInfo of Object.values(terminalInfo.serviceInfo || {})) {
20
+ if (serviceInfo.method !== 'IngestInterestRate')
21
+ continue;
22
+ try {
23
+ const meta = (0, exchange_1.parseInterestRateServiceMetadataFromSchema)(serviceInfo.schema);
24
+ for (const { product_id } of product_ids) {
25
+ if (!product_id.startsWith(meta.product_id_prefix))
26
+ continue;
27
+ series_ids.set(product_id, meta);
28
+ }
29
+ }
30
+ finally {
31
+ }
32
+ }
33
+ }
34
+ return series_ids;
35
+ };
36
+ exports.listInterestRateSeriesIds = listInterestRateSeriesIds;
37
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/series-collector/discovery.ts"],"names":[],"mappings":";AAAA,kBAAkB;;;AAElB,+CAA4G;AAC5G,+CAA4C;AAC5C,qCAAyC;AAEzC,MAAM,QAAQ,GAAG,mBAAQ,CAAC,WAAW,EAAE,CAAC;AAExC;;;GAGG;AACI,MAAM,yBAAyB,GAAG,KAAK,IAAI,EAAE;IAClD,MAAM,WAAW,GAAG,MAAM,IAAA,gBAAU,EAClC,QAAQ;IACR,aAAa;IACb,+DAA+D,CAChE,CAAC;IAEF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAwC,CAAC;IACnE,KAAK,MAAM,YAAY,IAAI,QAAQ,CAAC,aAAa,EAAE;QACjD,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE;YACvE,IAAI,WAAW,CAAC,MAAM,KAAK,oBAAoB;gBAAE,SAAS;YAC1D,IAAI;gBACF,MAAM,IAAI,GAAG,IAAA,qDAA0C,EAAC,WAAW,CAAC,MAAM,CAAC,CAAC;gBAE5E,KAAK,MAAM,EAAE,UAAU,EAAE,IAAI,WAAW,EAAE;oBACxC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC;wBAAE,SAAS;oBAC7D,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;iBAClC;aACF;oBAAS;aACT;SACF;KACF;IAED,OAAO,UAAU,CAAC;AACpB,CAAC,CAAC;AAxBW,QAAA,yBAAyB,6BAwBpC","sourcesContent":["// 发现所有支持利率的品种系列ID\n\nimport { IInterestRateServiceMetadata, parseInterestRateServiceMetadataFromSchema } from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { requestSQL } from '@yuants/sql';\n\nconst terminal = Terminal.fromNodeEnv();\n\n/**\n * 列出所有支持利率的品种系列ID以及对应的 Service Metadata\n * @returns\n */\nexport const listInterestRateSeriesIds = async () => {\n const product_ids = await requestSQL<{ product_id: string }[]>(\n terminal,\n // 必须是支持利率的品种\n `select product_id from product where no_interest_rate = false`,\n );\n\n const series_ids = new Map<string, IInterestRateServiceMetadata>();\n for (const terminalInfo of terminal.terminalInfos) {\n for (const serviceInfo of Object.values(terminalInfo.serviceInfo || {})) {\n if (serviceInfo.method !== 'IngestInterestRate') continue;\n try {\n const meta = parseInterestRateServiceMetadataFromSchema(serviceInfo.schema);\n\n for (const { product_id } of product_ids) {\n if (!product_id.startsWith(meta.product_id_prefix)) continue;\n series_ids.set(product_id, meta);\n }\n } finally {\n }\n }\n }\n\n return series_ids;\n};\n"]}
@@ -1,2 +1,4 @@
1
- export {};
1
+ /// <reference types="node" />
2
+ import { IInterestRateServiceMetadata } from '@yuants/exchange';
3
+ export declare const handleIngestInterestRateForward: (product_id: string, meta: IInterestRateServiceMetadata, signal: AbortSignal) => Promise<void>;
2
4
  //# sourceMappingURL=forwards-interest-rate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"forwards-interest-rate.d.ts","sourceRoot":"","sources":["../../src/series-collector/forwards-interest-rate.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"forwards-interest-rate.d.ts","sourceRoot":"","sources":["../../src/series-collector/forwards-interest-rate.ts"],"names":[],"mappings":";AAKA,OAAO,EAEL,4BAA4B,EAE7B,MAAM,kBAAkB,CAAC;AAO1B,eAAO,MAAM,+BAA+B,eAC9B,MAAM,QACZ,4BAA4B,UAC1B,WAAW,kBAmDpB,CAAC"}
@@ -4,80 +4,43 @@
4
4
  // 然后对每个序列,向对应的 IngestInterestRate Service 发送拉取请求,补齐历史数据。
5
5
  // 使用 Token Bucket 控制每个数据源的请求速率,避免过载。
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- const exchange_1 = require("@yuants/exchange");
7
+ exports.handleIngestInterestRateForward = void 0;
8
8
  const protocol_1 = require("@yuants/protocol");
9
- const sql_1 = require("@yuants/sql");
10
9
  const utils_1 = require("@yuants/utils");
11
- const rxjs_1 = require("rxjs");
10
+ const sql_helpers_1 = require("./sql-helpers");
12
11
  const terminal = protocol_1.Terminal.fromNodeEnv();
13
- const listBackwardSeriesIds = async () => {
14
- const product_ids = await (0, sql_1.requestSQL)(terminal, `select product_id from product`);
15
- console.time('[SeriesCollector][InterestRate][Forwards] calc');
16
- const series_ids = new Map();
17
- for (const terminalInfo of terminal.terminalInfos) {
18
- for (const serviceInfo of Object.values(terminalInfo.serviceInfo || {})) {
19
- if (serviceInfo.method !== 'IngestInterestRate')
20
- continue;
21
- try {
22
- const meta = (0, exchange_1.parseInterestRateServiceMetadataFromSchema)(serviceInfo.schema);
23
- for (const { product_id } of product_ids) {
24
- if (!product_id.startsWith(meta.product_id_prefix))
25
- continue;
26
- series_ids.set(product_id, meta);
27
- }
28
- }
29
- finally {
30
- }
12
+ const handleIngestInterestRateForward = async (product_id, meta, signal) => {
13
+ var _a, _b, _c, _d;
14
+ const [datasource_id] = (0, utils_1.decodePath)(product_id);
15
+ // 控制速率:每个数据源每秒钟只能请求一次
16
+ await (0, utils_1.tokenBucket)(`interest_rate_forwards:${datasource_id}`).acquire(1, signal);
17
+ {
18
+ let req;
19
+ if (meta.direction === 'forward') {
20
+ const endTime = await (0, sql_helpers_1.findInterestRateEndTimeForward)(terminal, product_id);
21
+ const time = endTime ? new Date(endTime).getTime() : 0;
22
+ req = {
23
+ product_id: product_id,
24
+ direction: 'forward',
25
+ time,
26
+ };
31
27
  }
28
+ else {
29
+ // backward
30
+ req = {
31
+ product_id,
32
+ direction: 'backward',
33
+ time: Date.now(),
34
+ };
35
+ }
36
+ console.info((0, utils_1.formatTime)(Date.now()), '[SeriesCollector][InterestRate][Forward]', 'Request', `product_id=${req.product_id}, direction=${req.direction}, time=${(0, utils_1.formatTime)(req.time)}`);
37
+ const res = await terminal.client.requestForResponseData('IngestInterestRate', req);
38
+ terminal.metrics
39
+ .counter('series_collector_forwards_ingest_count', '')
40
+ .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate' })
41
+ .inc(res.wrote_count || 0);
42
+ console.info((0, utils_1.formatTime)(Date.now()), '[SeriesCollector][InterestRate][Forward]', 'Result', `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${(0, utils_1.formatTime)((_b = (_a = res.range) === null || _a === void 0 ? void 0 : _a.start_time) !== null && _b !== void 0 ? _b : NaN)}, end_time=${(0, utils_1.formatTime)((_d = (_c = res.range) === null || _c === void 0 ? void 0 : _c.end_time) !== null && _d !== void 0 ? _d : NaN)}`);
32
43
  }
33
- console.timeEnd('[SeriesCollector][InterestRate][Forwards] calc');
34
- return series_ids;
35
44
  };
36
- (0, rxjs_1.defer)(async () => {
37
- const series_ids = await listBackwardSeriesIds();
38
- console.log(`[SeriesCollector][InterestRate][Forwards] Found ${series_ids.size} series to collect forwards data for. `);
39
- await Promise.all([...series_ids].map(async ([product_id, meta]) => {
40
- var _a, _b, _c, _d;
41
- try {
42
- const [datasource_id] = (0, utils_1.decodePath)(product_id);
43
- // 控制速率:每个数据源每秒钟只能请求一次
44
- await (0, utils_1.tokenBucket)(`interest_rate_forwards:${datasource_id}`, {
45
- refillInterval: 1000,
46
- capacity: 1,
47
- }).acquire();
48
- {
49
- let req;
50
- if (meta.direction === 'forward') {
51
- const [record] = await (0, sql_1.requestSQL)(terminal, `select end_time from series_data_range where series_id = ${(0, sql_1.escapeSQL)(product_id)} and table_name = 'interest_rate' order by end_time desc limit 1`);
52
- const time = record ? new Date(record.end_time).getTime() : 0;
53
- req = {
54
- product_id: product_id,
55
- direction: 'forward',
56
- time,
57
- };
58
- }
59
- else {
60
- // backward
61
- req = {
62
- product_id,
63
- direction: 'backward',
64
- time: Date.now(),
65
- };
66
- }
67
- console.info((0, utils_1.formatTime)(Date.now()), 'DispatchIngestInterestRateRequestForwardTask', `product_id=${req.product_id}, direction=${req.direction}, time=${(0, utils_1.formatTime)(req.time)}`);
68
- const res = await terminal.client.requestForResponseData('IngestInterestRate', req);
69
- terminal.metrics
70
- .counter('series_collector_forwards_ingest_count', '')
71
- .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate' })
72
- .inc(res.wrote_count || 0);
73
- console.info((0, utils_1.formatTime)(Date.now()), 'DispatchIngestInterestRateRequestForwardTaskResult', `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${(0, utils_1.formatTime)((_b = (_a = res.range) === null || _a === void 0 ? void 0 : _a.start_time) !== null && _b !== void 0 ? _b : NaN)}, end_time=${(0, utils_1.formatTime)((_d = (_c = res.range) === null || _c === void 0 ? void 0 : _c.end_time) !== null && _d !== void 0 ? _d : NaN)}`);
74
- }
75
- }
76
- catch (e) {
77
- console.info((0, utils_1.formatTime)(Date.now()), 'DispatchIngestInterestRateErrorForwards', `series_id=${product_id}`, e);
78
- }
79
- }));
80
- })
81
- .pipe((0, rxjs_1.retry)({ delay: 1000 }), (0, rxjs_1.repeat)({ delay: 1000 }))
82
- .subscribe();
45
+ exports.handleIngestInterestRateForward = handleIngestInterestRateForward;
83
46
  //# sourceMappingURL=forwards-interest-rate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"forwards-interest-rate.js","sourceRoot":"","sources":["../../src/series-collector/forwards-interest-rate.ts"],"names":[],"mappings":"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","sourcesContent":["// 解决 Forwards 拉取历史数据的调度器\n// 该文件会定期扫描所有 Terminal 的 ServiceInfo,提取出所有支持 Forwards 拉取的序列。\n// 然后对每个序列,向对应的 IngestInterestRate Service 发送拉取请求,补齐历史数据。\n// 使用 Token Bucket 控制每个数据源的请求速率,避免过载。\n\nimport {\n IIngestInterestRateRequest,\n IInterestRateServiceMetadata,\n ISeriesIngestResult,\n parseInterestRateServiceMetadataFromSchema,\n} from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\nimport { decodePath, formatTime, tokenBucket } from '@yuants/utils';\nimport { defer, repeat, retry } from 'rxjs';\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst listBackwardSeriesIds = async () => {\n const product_ids = await requestSQL<{ product_id: string }[]>(terminal, `select product_id from product`);\n\n console.time('[SeriesCollector][InterestRate][Forwards] calc');\n\n const series_ids = new Map<string, IInterestRateServiceMetadata>();\n for (const terminalInfo of terminal.terminalInfos) {\n for (const serviceInfo of Object.values(terminalInfo.serviceInfo || {})) {\n if (serviceInfo.method !== 'IngestInterestRate') continue;\n try {\n const meta = parseInterestRateServiceMetadataFromSchema(serviceInfo.schema);\n\n for (const { product_id } of product_ids) {\n if (!product_id.startsWith(meta.product_id_prefix)) continue;\n series_ids.set(product_id, meta);\n }\n } finally {\n }\n }\n }\n\n console.timeEnd('[SeriesCollector][InterestRate][Forwards] calc');\n\n return series_ids;\n};\n\ndefer(async () => {\n const series_ids = await listBackwardSeriesIds();\n console.log(\n `[SeriesCollector][InterestRate][Forwards] Found ${series_ids.size} series to collect forwards data for. `,\n );\n\n await Promise.all(\n [...series_ids].map(async ([product_id, meta]) => {\n try {\n const [datasource_id] = decodePath(product_id);\n // 控制速率:每个数据源每秒钟只能请求一次\n await tokenBucket(`interest_rate_forwards:${datasource_id}`, {\n refillInterval: 1000,\n capacity: 1,\n }).acquire();\n {\n let req: IIngestInterestRateRequest;\n if (meta.direction === 'forward') {\n const [record] = await requestSQL<\n {\n end_time: string;\n }[]\n >(\n terminal,\n `select end_time from series_data_range where series_id = ${escapeSQL(\n product_id,\n )} and table_name = 'interest_rate' order by end_time desc limit 1`,\n );\n const time = record ? new Date(record.end_time).getTime() : 0;\n\n req = {\n product_id: product_id,\n direction: 'forward',\n time,\n };\n } else {\n // backward\n req = {\n product_id,\n direction: 'backward',\n time: Date.now(),\n };\n }\n\n console.info(\n formatTime(Date.now()),\n 'DispatchIngestInterestRateRequestForwardTask',\n `product_id=${req.product_id}, direction=${req.direction}, time=${formatTime(req.time)}`,\n );\n const res = await terminal.client.requestForResponseData<\n IIngestInterestRateRequest,\n ISeriesIngestResult\n >('IngestInterestRate', req);\n\n terminal.metrics\n .counter('series_collector_forwards_ingest_count', '')\n .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate' })\n .inc(res.wrote_count || 0);\n\n console.info(\n formatTime(Date.now()),\n 'DispatchIngestInterestRateRequestForwardTaskResult',\n `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${formatTime(\n res.range?.start_time ?? NaN,\n )}, end_time=${formatTime(res.range?.end_time ?? NaN)}`,\n );\n }\n } catch (e) {\n console.info(\n formatTime(Date.now()),\n 'DispatchIngestInterestRateErrorForwards',\n `series_id=${product_id}`,\n e,\n );\n }\n }),\n );\n})\n .pipe(retry({ delay: 1000 }), repeat({ delay: 1000 }))\n .subscribe();\n"]}
1
+ {"version":3,"file":"forwards-interest-rate.js","sourceRoot":"","sources":["../../src/series-collector/forwards-interest-rate.ts"],"names":[],"mappings":";AAAA,yBAAyB;AACzB,4DAA4D;AAC5D,yDAAyD;AACzD,qCAAqC;;;AAOrC,+CAA4C;AAC5C,yCAAoE;AACpE,+CAA+D;AAE/D,MAAM,QAAQ,GAAG,mBAAQ,CAAC,WAAW,EAAE,CAAC;AAEjC,MAAM,+BAA+B,GAAG,KAAK,EAClD,UAAkB,EAClB,IAAkC,EAClC,MAAmB,EACnB,EAAE;;IACF,MAAM,CAAC,aAAa,CAAC,GAAG,IAAA,kBAAU,EAAC,UAAU,CAAC,CAAC;IAC/C,sBAAsB;IACtB,MAAM,IAAA,mBAAW,EAAC,0BAA0B,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAEhF;QACE,IAAI,GAA+B,CAAC;QACpC,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE;YAChC,MAAM,OAAO,GAAG,MAAM,IAAA,4CAA8B,EAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAC3E,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAEvD,GAAG,GAAG;gBACJ,UAAU,EAAE,UAAU;gBACtB,SAAS,EAAE,SAAS;gBACpB,IAAI;aACL,CAAC;SACH;aAAM;YACL,WAAW;YACX,GAAG,GAAG;gBACJ,UAAU;gBACV,SAAS,EAAE,UAAU;gBACrB,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;aACjB,CAAC;SACH;QAED,OAAO,CAAC,IAAI,CACV,IAAA,kBAAU,EAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,0CAA0C,EAC1C,SAAS,EACT,cAAc,GAAG,CAAC,UAAU,eAAe,GAAG,CAAC,SAAS,UAAU,IAAA,kBAAU,EAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CACzF,CAAC;QACF,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,sBAAsB,CACtD,oBAAoB,EACpB,GAAG,CACJ,CAAC;QAEF,QAAQ,CAAC,OAAO;aACb,OAAO,CAAC,wCAAwC,EAAE,EAAE,CAAC;aACrD,MAAM,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;aACpE,GAAG,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;QAE7B,OAAO,CAAC,IAAI,CACV,IAAA,kBAAU,EAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,0CAA0C,EAC1C,QAAQ,EACR,aAAa,UAAU,oBAAoB,GAAG,CAAC,WAAW,gBAAgB,IAAA,kBAAU,EAClF,MAAA,MAAA,GAAG,CAAC,KAAK,0CAAE,UAAU,mCAAI,GAAG,CAC7B,cAAc,IAAA,kBAAU,EAAC,MAAA,MAAA,GAAG,CAAC,KAAK,0CAAE,QAAQ,mCAAI,GAAG,CAAC,EAAE,CACxD,CAAC;KACH;AACH,CAAC,CAAC;AAtDW,QAAA,+BAA+B,mCAsD1C","sourcesContent":["// 解决 Forwards 拉取历史数据的调度器\n// 该文件会定期扫描所有 Terminal 的 ServiceInfo,提取出所有支持 Forwards 拉取的序列。\n// 然后对每个序列,向对应的 IngestInterestRate Service 发送拉取请求,补齐历史数据。\n// 使用 Token Bucket 控制每个数据源的请求速率,避免过载。\n\nimport {\n IIngestInterestRateRequest,\n IInterestRateServiceMetadata,\n ISeriesIngestResult,\n} from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { decodePath, formatTime, tokenBucket } from '@yuants/utils';\nimport { findInterestRateEndTimeForward } from './sql-helpers';\n\nconst terminal = Terminal.fromNodeEnv();\n\nexport const handleIngestInterestRateForward = async (\n product_id: string,\n meta: IInterestRateServiceMetadata,\n signal: AbortSignal,\n) => {\n const [datasource_id] = decodePath(product_id);\n // 控制速率:每个数据源每秒钟只能请求一次\n await tokenBucket(`interest_rate_forwards:${datasource_id}`).acquire(1, signal);\n\n {\n let req: IIngestInterestRateRequest;\n if (meta.direction === 'forward') {\n const endTime = await findInterestRateEndTimeForward(terminal, product_id);\n const time = endTime ? new Date(endTime).getTime() : 0;\n\n req = {\n product_id: product_id,\n direction: 'forward',\n time,\n };\n } else {\n // backward\n req = {\n product_id,\n direction: 'backward',\n time: Date.now(),\n };\n }\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Forward]',\n 'Request',\n `product_id=${req.product_id}, direction=${req.direction}, time=${formatTime(req.time)}`,\n );\n const res = await terminal.client.requestForResponseData<IIngestInterestRateRequest, ISeriesIngestResult>(\n 'IngestInterestRate',\n req,\n );\n\n terminal.metrics\n .counter('series_collector_forwards_ingest_count', '')\n .labels({ terminal_id: terminal.terminal_id, type: 'interest_rate' })\n .inc(res.wrote_count || 0);\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Forward]',\n 'Result',\n `series_id=${product_id}, ingested_count=${res.wrote_count}, start_time=${formatTime(\n res.range?.start_time ?? NaN,\n )}, end_time=${formatTime(res.range?.end_time ?? NaN)}`,\n );\n }\n};\n"]}
@@ -1,5 +1,4 @@
1
- import './backwards-interest-rate';
2
1
  import './backwards-ohlc';
3
- import './forwards-interest-rate';
4
2
  import './forwards-ohlc';
3
+ import './interest-rate';
5
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/series-collector/index.ts"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAC;AACnC,OAAO,kBAAkB,CAAC;AAC1B,OAAO,0BAA0B,CAAC;AAClC,OAAO,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/series-collector/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,iBAAiB,CAAC;AACzB,OAAO,iBAAiB,CAAC"}
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- require("./backwards-interest-rate");
4
3
  require("./backwards-ohlc");
5
- require("./forwards-interest-rate");
6
4
  require("./forwards-ohlc");
5
+ require("./interest-rate");
7
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/series-collector/index.ts"],"names":[],"mappings":";;AAAA,qCAAmC;AACnC,4BAA0B;AAC1B,oCAAkC;AAClC,2BAAyB","sourcesContent":["import './backwards-interest-rate';\nimport './backwards-ohlc';\nimport './forwards-interest-rate';\nimport './forwards-ohlc';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/series-collector/index.ts"],"names":[],"mappings":";;AAAA,4BAA0B;AAC1B,2BAAyB;AACzB,2BAAyB","sourcesContent":["import './backwards-ohlc';\nimport './forwards-ohlc';\nimport './interest-rate';\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=interest-rate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interest-rate.d.ts","sourceRoot":"","sources":["../../src/series-collector/interest-rate.ts"],"names":[],"mappings":""}
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("@yuants/utils");
4
+ const rxjs_1 = require("rxjs");
5
+ const backwards_interest_rate_1 = require("./backwards-interest-rate");
6
+ const discovery_1 = require("./discovery");
7
+ const forwards_interest_rate_1 = require("./forwards-interest-rate");
8
+ const patch_interest_rate_1 = require("./patch-interest-rate");
9
+ (0, rxjs_1.defer)(() => (0, discovery_1.listInterestRateSeriesIds)())
10
+ .pipe((0, rxjs_1.retry)({ delay: 1000 }), (0, rxjs_1.repeat)({ delay: 60000 }), (0, rxjs_1.map)((x) => Array.from(x.entries())), (0, utils_1.listWatch)((x) => x[0], ([product_id, meta]) => new rxjs_1.Observable((sub) => {
11
+ // 处理每个利率品种任务: (forward / backward / patch),都需要独立调度
12
+ const abortController = new AbortController();
13
+ sub.add(() => {
14
+ abortController.abort();
15
+ });
16
+ // 先处理前向任务
17
+ const forwardTask = (0, rxjs_1.defer)(async () => {
18
+ await (0, forwards_interest_rate_1.handleIngestInterestRateForward)(product_id, meta, abortController.signal);
19
+ })
20
+ .pipe((0, rxjs_1.retry)(), (0, rxjs_1.repeat)())
21
+ .subscribe();
22
+ sub.add(() => {
23
+ forwardTask.unsubscribe();
24
+ });
25
+ // 设置后向任务
26
+ const backwardTask = (0, rxjs_1.defer)(async () => {
27
+ await (0, backwards_interest_rate_1.handleIngestInterestRateBackward)(product_id, meta, abortController.signal);
28
+ })
29
+ .pipe((0, rxjs_1.retry)(), (0, rxjs_1.repeat)())
30
+ .subscribe();
31
+ sub.add(() => {
32
+ backwardTask.unsubscribe();
33
+ });
34
+ // 设置补齐任务
35
+ const patchTask = (0, rxjs_1.defer)(async () => {
36
+ await (0, patch_interest_rate_1.handleInterestRatePatch)(product_id, meta, abortController.signal);
37
+ })
38
+ .pipe((0, rxjs_1.retry)(), (0, rxjs_1.repeat)())
39
+ .subscribe();
40
+ sub.add(() => {
41
+ patchTask.unsubscribe();
42
+ });
43
+ })))
44
+ .subscribe();
45
+ //# sourceMappingURL=interest-rate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interest-rate.js","sourceRoot":"","sources":["../../src/series-collector/interest-rate.ts"],"names":[],"mappings":";;AAAA,yCAA0C;AAC1C,+BAA6D;AAC7D,uEAA6E;AAC7E,2CAAwD;AACxD,qEAA2E;AAC3E,+DAAgE;AAEhE,IAAA,YAAK,EAAC,GAAG,EAAE,CAAC,IAAA,qCAAyB,GAAE,CAAC;KACrC,IAAI,CACH,IAAA,YAAK,EAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EACtB,IAAA,aAAM,EAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EACxB,IAAA,UAAG,EAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,EACnC,IAAA,iBAAS,EACP,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EACX,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE,CACrB,IAAI,iBAAU,CAAC,CAAC,GAAG,EAAE,EAAE;IACrB,mDAAmD;IACnD,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;IAE9C,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;QACX,eAAe,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,UAAU;IACV,MAAM,WAAW,GAAG,IAAA,YAAK,EAAC,KAAK,IAAI,EAAE;QACnC,MAAM,IAAA,wDAA+B,EAAC,UAAU,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAClF,CAAC,CAAC;SACC,IAAI,CAAC,IAAA,YAAK,GAAE,EAAE,IAAA,aAAM,GAAE,CAAC;SACvB,SAAS,EAAE,CAAC;IAEf,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;QACX,WAAW,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,SAAS;IACT,MAAM,YAAY,GAAG,IAAA,YAAK,EAAC,KAAK,IAAI,EAAE;QACpC,MAAM,IAAA,0DAAgC,EAAC,UAAU,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IACnF,CAAC,CAAC;SACC,IAAI,CAAC,IAAA,YAAK,GAAE,EAAE,IAAA,aAAM,GAAE,CAAC;SACvB,SAAS,EAAE,CAAC;IAEf,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;QACX,YAAY,CAAC,WAAW,EAAE,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,SAAS;IACT,MAAM,SAAS,GAAG,IAAA,YAAK,EAAC,KAAK,IAAI,EAAE;QACjC,MAAM,IAAA,6CAAuB,EAAC,UAAU,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAC1E,CAAC,CAAC;SACC,IAAI,CAAC,IAAA,YAAK,GAAE,EAAE,IAAA,aAAM,GAAE,CAAC;SACvB,SAAS,EAAE,CAAC;IAEf,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;QACX,SAAS,CAAC,WAAW,EAAE,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CACL,CACF;KACA,SAAS,EAAE,CAAC","sourcesContent":["import { listWatch } from '@yuants/utils';\nimport { defer, map, Observable, repeat, retry } from 'rxjs';\nimport { handleIngestInterestRateBackward } from './backwards-interest-rate';\nimport { listInterestRateSeriesIds } from './discovery';\nimport { handleIngestInterestRateForward } from './forwards-interest-rate';\nimport { handleInterestRatePatch } from './patch-interest-rate';\n\ndefer(() => listInterestRateSeriesIds())\n .pipe(\n retry({ delay: 1000 }),\n repeat({ delay: 60000 }),\n map((x) => Array.from(x.entries())),\n listWatch(\n (x) => x[0],\n ([product_id, meta]) =>\n new Observable((sub) => {\n // 处理每个利率品种任务: (forward / backward / patch),都需要独立调度\n const abortController = new AbortController();\n\n sub.add(() => {\n abortController.abort();\n });\n\n // 先处理前向任务\n const forwardTask = defer(async () => {\n await handleIngestInterestRateForward(product_id, meta, abortController.signal);\n })\n .pipe(retry(), repeat())\n .subscribe();\n\n sub.add(() => {\n forwardTask.unsubscribe();\n });\n\n // 设置后向任务\n const backwardTask = defer(async () => {\n await handleIngestInterestRateBackward(product_id, meta, abortController.signal);\n })\n .pipe(retry(), repeat())\n .subscribe();\n\n sub.add(() => {\n backwardTask.unsubscribe();\n });\n\n // 设置补齐任务\n const patchTask = defer(async () => {\n await handleInterestRatePatch(product_id, meta, abortController.signal);\n })\n .pipe(retry(), repeat())\n .subscribe();\n\n sub.add(() => {\n patchTask.unsubscribe();\n });\n }),\n ),\n )\n .subscribe();\n"]}
@@ -0,0 +1,4 @@
1
+ /// <reference types="node" />
2
+ import { IInterestRateServiceMetadata } from '@yuants/exchange';
3
+ export declare const handleInterestRatePatch: (product_id: string, meta: IInterestRateServiceMetadata, signal: AbortSignal) => Promise<void>;
4
+ //# sourceMappingURL=patch-interest-rate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-interest-rate.d.ts","sourceRoot":"","sources":["../../src/series-collector/patch-interest-rate.ts"],"names":[],"mappings":";AAAA,OAAO,EAEL,4BAA4B,EAE7B,MAAM,kBAAkB,CAAC;AAQ1B,eAAO,MAAM,uBAAuB,eACtB,MAAM,QACZ,4BAA4B,UAC1B,WAAW,kBAmFpB,CAAC"}
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleInterestRatePatch = void 0;
4
+ const protocol_1 = require("@yuants/protocol");
5
+ const sql_1 = require("@yuants/sql");
6
+ const utils_1 = require("@yuants/utils");
7
+ const terminal = protocol_1.Terminal.fromNodeEnv();
8
+ // Patch 任务:查找数据缺口并进行补齐
9
+ const handleInterestRatePatch = async (product_id, meta, signal) => {
10
+ var _a, _b, _c, _d;
11
+ const [datasource_id] = (0, utils_1.decodePath)(product_id);
12
+ await (0, utils_1.tokenBucket)(`interest_rate:patch:${datasource_id}`).acquire(1, signal);
13
+ const [record] = await (0, sql_1.requestSQL)(terminal, `
14
+ WITH reversed_ranges AS (
15
+ SELECT
16
+ start_time,
17
+ end_time,
18
+ LEAD(end_time) OVER (
19
+ PARTITION BY table_name, series_id
20
+ ORDER BY start_time DESC
21
+ ) AS next_end_time -- 注意:倒序时 LEAD 是前一个区间
22
+ FROM series_data_range
23
+ WHERE table_name = 'interest_rate'
24
+ AND series_id = ${(0, sql_1.escapeSQL)(product_id)}
25
+ )
26
+ SELECT
27
+ next_end_time AS gap_start_time, -- 前一个区间的结束时间
28
+ start_time AS gap_end_time -- 当前区间的开始时间
29
+ FROM reversed_ranges
30
+ WHERE next_end_time IS NOT NULL
31
+ AND start_time > next_end_time -- 有空缺
32
+ ORDER BY start_time DESC -- 从最新开始
33
+ LIMIT 1;
34
+ `);
35
+ // no gap
36
+ if (!record)
37
+ return;
38
+ const gapStartTime = new Date(record.gap_start_time).getTime();
39
+ const gapEndTime = new Date(record.gap_end_time).getTime();
40
+ console.info((0, utils_1.formatTime)(Date.now()), '[SeriesCollector][InterestRate][Patch]', 'FindGap', `series=${product_id}, from=${(0, utils_1.formatTime)(gapStartTime)}, to=${(0, utils_1.formatTime)(gapEndTime)}`);
41
+ let req;
42
+ if (meta.direction === 'forward') {
43
+ // forward patch
44
+ req = {
45
+ product_id: product_id,
46
+ direction: 'forward',
47
+ time: gapStartTime,
48
+ };
49
+ }
50
+ else {
51
+ // backward patch
52
+ req = {
53
+ product_id: product_id,
54
+ direction: 'backward',
55
+ time: gapEndTime,
56
+ };
57
+ }
58
+ console.info((0, utils_1.formatTime)(Date.now()), '[SeriesCollector][InterestRate][Patch]', product_id, 'PatchRequest', `direction=${req.direction}, time=${(0, utils_1.formatTime)(req.time)}`);
59
+ const res = await terminal.client.requestForResponseData('IngestInterestRate', req);
60
+ console.info((0, utils_1.formatTime)(Date.now()), '[SeriesCollector][InterestRate][Patch]', product_id, 'PatchBackwardResult', `ingested_count=${res.wrote_count}, start_time=${(0, utils_1.formatTime)((_b = (_a = res.range) === null || _a === void 0 ? void 0 : _a.start_time) !== null && _b !== void 0 ? _b : NaN)}, end_time=${(0, utils_1.formatTime)((_d = (_c = res.range) === null || _c === void 0 ? void 0 : _c.end_time) !== null && _d !== void 0 ? _d : NaN)}`);
61
+ };
62
+ exports.handleInterestRatePatch = handleInterestRatePatch;
63
+ //# sourceMappingURL=patch-interest-rate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-interest-rate.js","sourceRoot":"","sources":["../../src/series-collector/patch-interest-rate.ts"],"names":[],"mappings":";;;AAKA,+CAA4C;AAC5C,qCAAoD;AACpD,yCAAoE;AAEpE,MAAM,QAAQ,GAAG,mBAAQ,CAAC,WAAW,EAAE,CAAC;AAExC,uBAAuB;AAChB,MAAM,uBAAuB,GAAG,KAAK,EAC1C,UAAkB,EAClB,IAAkC,EAClC,MAAmB,EACnB,EAAE;;IACF,MAAM,CAAC,aAAa,CAAC,GAAG,IAAA,kBAAU,EAAC,UAAU,CAAC,CAAC;IAC/C,MAAM,IAAA,mBAAW,EAAC,uBAAuB,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7E,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,IAAA,gBAAU,EAC/B,QAAQ,EACR;;;;;;;;;;;wBAWoB,IAAA,eAAS,EAAC,UAAU,CAAC;;;;;;;;;;KAUxC,CACF,CAAC;IAEF,SAAS;IACT,IAAI,CAAC,MAAM;QAAE,OAAO;IAEpB,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE,CAAC;IAC/D,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC;IAE3D,OAAO,CAAC,IAAI,CACV,IAAA,kBAAU,EAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,wCAAwC,EACxC,SAAS,EACT,UAAU,UAAU,UAAU,IAAA,kBAAU,EAAC,YAAY,CAAC,QAAQ,IAAA,kBAAU,EAAC,UAAU,CAAC,EAAE,CACvF,CAAC;IAEF,IAAI,GAA+B,CAAC;IAEpC,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE;QAChC,gBAAgB;QAChB,GAAG,GAAG;YACJ,UAAU,EAAE,UAAU;YACtB,SAAS,EAAE,SAAkB;YAC7B,IAAI,EAAE,YAAY;SACnB,CAAC;KACH;SAAM;QACL,iBAAiB;QACjB,GAAG,GAAG;YACJ,UAAU,EAAE,UAAU;YACtB,SAAS,EAAE,UAAmB;YAC9B,IAAI,EAAE,UAAU;SACjB,CAAC;KACH;IAED,OAAO,CAAC,IAAI,CACV,IAAA,kBAAU,EAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,wCAAwC,EACxC,UAAU,EACV,cAAc,EACd,aAAa,GAAG,CAAC,SAAS,UAAU,IAAA,kBAAU,EAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAC3D,CAAC;IAEF,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,sBAAsB,CACtD,oBAAoB,EACpB,GAAG,CACJ,CAAC;IAEF,OAAO,CAAC,IAAI,CACV,IAAA,kBAAU,EAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,wCAAwC,EACxC,UAAU,EACV,qBAAqB,EACrB,kBAAkB,GAAG,CAAC,WAAW,gBAAgB,IAAA,kBAAU,EACzD,MAAA,MAAA,GAAG,CAAC,KAAK,0CAAE,UAAU,mCAAI,GAAG,CAC7B,cAAc,IAAA,kBAAU,EAAC,MAAA,MAAA,GAAG,CAAC,KAAK,0CAAE,QAAQ,mCAAI,GAAG,CAAC,EAAE,CACxD,CAAC;AACJ,CAAC,CAAC;AAtFW,QAAA,uBAAuB,2BAsFlC","sourcesContent":["import {\n IIngestInterestRateRequest,\n IInterestRateServiceMetadata,\n ISeriesIngestResult,\n} from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\nimport { decodePath, formatTime, tokenBucket } from '@yuants/utils';\n\nconst terminal = Terminal.fromNodeEnv();\n\n// Patch 任务:查找数据缺口并进行补齐\nexport const handleInterestRatePatch = async (\n product_id: string,\n meta: IInterestRateServiceMetadata,\n signal: AbortSignal,\n) => {\n const [datasource_id] = decodePath(product_id);\n await tokenBucket(`interest_rate:patch:${datasource_id}`).acquire(1, signal);\n const [record] = await requestSQL<{ gap_start_time: string; gap_end_time: string }[]>(\n terminal,\n `\nWITH reversed_ranges AS (\n SELECT \n start_time,\n end_time,\n LEAD(end_time) OVER (\n PARTITION BY table_name, series_id \n ORDER BY start_time DESC\n ) AS next_end_time -- 注意:倒序时 LEAD 是前一个区间\n FROM series_data_range\n WHERE table_name = 'interest_rate' \n AND series_id = ${escapeSQL(product_id)}\n)\nSELECT \n next_end_time AS gap_start_time, -- 前一个区间的结束时间\n start_time AS gap_end_time -- 当前区间的开始时间\nFROM reversed_ranges\nWHERE next_end_time IS NOT NULL \n AND start_time > next_end_time -- 有空缺\nORDER BY start_time DESC -- 从最新开始\nLIMIT 1;\n `,\n );\n\n // no gap\n if (!record) return;\n\n const gapStartTime = new Date(record.gap_start_time).getTime();\n const gapEndTime = new Date(record.gap_end_time).getTime();\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Patch]',\n 'FindGap',\n `series=${product_id}, from=${formatTime(gapStartTime)}, to=${formatTime(gapEndTime)}`,\n );\n\n let req: IIngestInterestRateRequest;\n\n if (meta.direction === 'forward') {\n // forward patch\n req = {\n product_id: product_id,\n direction: 'forward' as const,\n time: gapStartTime,\n };\n } else {\n // backward patch\n req = {\n product_id: product_id,\n direction: 'backward' as const,\n time: gapEndTime,\n };\n }\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Patch]',\n product_id,\n 'PatchRequest',\n `direction=${req.direction}, time=${formatTime(req.time)}`,\n );\n\n const res = await terminal.client.requestForResponseData<IIngestInterestRateRequest, ISeriesIngestResult>(\n 'IngestInterestRate',\n req,\n );\n\n console.info(\n formatTime(Date.now()),\n '[SeriesCollector][InterestRate][Patch]',\n product_id,\n 'PatchBackwardResult',\n `ingested_count=${res.wrote_count}, start_time=${formatTime(\n res.range?.start_time ?? NaN,\n )}, end_time=${formatTime(res.range?.end_time ?? NaN)}`,\n );\n};\n"]}
@@ -0,0 +1,11 @@
1
+ import { Terminal } from '@yuants/protocol';
2
+ /**
3
+ * 查找某个利率品种系列ID的最新结束时间(for forward task)
4
+ * @param terminal
5
+ * @param product_id
6
+ * @returns
7
+ */
8
+ export declare const findInterestRateEndTimeForward: (terminal: Terminal, product_id: string) => Promise<string>;
9
+ export declare const findInterestRateStartTimeBackward: (terminal: Terminal, product_id: string) => Promise<string>;
10
+ export declare const findOHLCEndTimeForward: (terminal: Terminal, series_id: string) => Promise<string>;
11
+ //# sourceMappingURL=sql-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-helpers.d.ts","sourceRoot":"","sources":["../../src/series-collector/sql-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,aAAc,QAAQ,cAAc,MAAM,oBAUxC,CAAC;AAE9C,eAAO,MAAM,iCAAiC,aAAc,QAAQ,cAAc,MAAM,oBAUzC,CAAC;AAEhD,eAAO,MAAM,sBAAsB,aAAc,QAAQ,aAAa,MAAM,oBAU/B,CAAC"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findOHLCEndTimeForward = exports.findInterestRateStartTimeBackward = exports.findInterestRateEndTimeForward = void 0;
4
+ const sql_1 = require("@yuants/sql");
5
+ /**
6
+ * 查找某个利率品种系列ID的最新结束时间(for forward task)
7
+ * @param terminal
8
+ * @param product_id
9
+ * @returns
10
+ */
11
+ const findInterestRateEndTimeForward = (terminal, product_id) => (0, sql_1.requestSQL)(terminal, `select end_time from series_data_range where series_id = ${(0, sql_1.escapeSQL)(product_id)} and table_name = 'interest_rate' order by end_time desc limit 1`).then((records) => { var _a; return (_a = records === null || records === void 0 ? void 0 : records[0]) === null || _a === void 0 ? void 0 : _a.end_time; });
12
+ exports.findInterestRateEndTimeForward = findInterestRateEndTimeForward;
13
+ const findInterestRateStartTimeBackward = (terminal, product_id) => (0, sql_1.requestSQL)(terminal, `select start_time from series_data_range where series_id = ${(0, sql_1.escapeSQL)(product_id)} and table_name = 'interest_rate' order by start_time asc limit 1`).then((records) => { var _a; return (_a = records === null || records === void 0 ? void 0 : records[0]) === null || _a === void 0 ? void 0 : _a.start_time; });
14
+ exports.findInterestRateStartTimeBackward = findInterestRateStartTimeBackward;
15
+ const findOHLCEndTimeForward = (terminal, series_id) => (0, sql_1.requestSQL)(terminal, `select end_time from series_data_range where series_id = ${(0, sql_1.escapeSQL)(series_id)} and table_name = 'ohlc_v2' order by end_time desc limit 1`).then((records) => { var _a; return (_a = records === null || records === void 0 ? void 0 : records[0]) === null || _a === void 0 ? void 0 : _a.end_time; });
16
+ exports.findOHLCEndTimeForward = findOHLCEndTimeForward;
17
+ //# sourceMappingURL=sql-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-helpers.js","sourceRoot":"","sources":["../../src/series-collector/sql-helpers.ts"],"names":[],"mappings":";;;AACA,qCAAoD;AAEpD;;;;;GAKG;AACI,MAAM,8BAA8B,GAAG,CAAC,QAAkB,EAAE,UAAkB,EAAE,EAAE,CACvF,IAAA,gBAAU,EAKR,QAAQ,EACR,4DAA4D,IAAA,eAAS,EACnE,UAAU,CACX,kEAAkE,CACpE,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,WAAC,OAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAG,CAAC,CAAC,0CAAE,QAAQ,CAAA,EAAA,CAAC,CAAC;AAVjC,QAAA,8BAA8B,kCAUG;AAEvC,MAAM,iCAAiC,GAAG,CAAC,QAAkB,EAAE,UAAkB,EAAE,EAAE,CAC1F,IAAA,gBAAU,EAKR,QAAQ,EACR,8DAA8D,IAAA,eAAS,EACrE,UAAU,CACX,mEAAmE,CACrE,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,WAAC,OAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAG,CAAC,CAAC,0CAAE,UAAU,CAAA,EAAA,CAAC,CAAC;AAVnC,QAAA,iCAAiC,qCAUE;AAEzC,MAAM,sBAAsB,GAAG,CAAC,QAAkB,EAAE,SAAiB,EAAE,EAAE,CAC9E,IAAA,gBAAU,EAKR,QAAQ,EACR,4DAA4D,IAAA,eAAS,EACnE,SAAS,CACV,4DAA4D,CAC9D,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,WAAC,OAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAG,CAAC,CAAC,0CAAE,QAAQ,CAAA,EAAA,CAAC,CAAC;AAVjC,QAAA,sBAAsB,0BAUW","sourcesContent":["import { Terminal } from '@yuants/protocol';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\n\n/**\n * 查找某个利率品种系列ID的最新结束时间(for forward task)\n * @param terminal\n * @param product_id\n * @returns\n */\nexport const findInterestRateEndTimeForward = (terminal: Terminal, product_id: string) =>\n requestSQL<\n {\n end_time: string;\n }[]\n >(\n terminal,\n `select end_time from series_data_range where series_id = ${escapeSQL(\n product_id,\n )} and table_name = 'interest_rate' order by end_time desc limit 1`,\n ).then((records) => records?.[0]?.end_time);\n\nexport const findInterestRateStartTimeBackward = (terminal: Terminal, product_id: string) =>\n requestSQL<\n {\n start_time: string;\n }[]\n >(\n terminal,\n `select start_time from series_data_range where series_id = ${escapeSQL(\n product_id,\n )} and table_name = 'interest_rate' order by start_time asc limit 1`,\n ).then((records) => records?.[0]?.start_time);\n\nexport const findOHLCEndTimeForward = (terminal: Terminal, series_id: string) =>\n requestSQL<\n {\n end_time: string;\n }[]\n >(\n terminal,\n `select end_time from series_data_range where series_id = ${escapeSQL(\n series_id,\n )} and table_name = 'ohlc_v2' order by end_time desc limit 1`,\n ).then((records) => records?.[0]?.end_time);\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuants/app-virtual-exchange",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "main": "lib/index.js",
5
5
  "files": [
6
6
  "dist",