@yuants/app-virtual-exchange 0.10.3 → 0.11.1

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 (39) hide show
  1. package/dist/position.js +15 -7
  2. package/dist/position.js.map +1 -1
  3. package/dist/quote/__tests__/implementations.test.js +66 -0
  4. package/dist/quote/__tests__/implementations.test.js.map +1 -1
  5. package/dist/quote/implementations/v0.js +12 -1
  6. package/dist/quote/implementations/v0.js.map +1 -1
  7. package/dist/quote/implementations/v1.js +12 -1
  8. package/dist/quote/implementations/v1.js.map +1 -1
  9. package/dist/quote/implementations/v2.js +12 -1
  10. package/dist/quote/implementations/v2.js.map +1 -1
  11. package/dist/quote/implementations/v3.js +12 -1
  12. package/dist/quote/implementations/v3.js.map +1 -1
  13. package/dist/quote/service.js +1 -1
  14. package/dist/quote/service.js.map +1 -1
  15. package/dist/quote/types.js.map +1 -1
  16. package/lib/position.d.ts.map +1 -1
  17. package/lib/position.js +15 -7
  18. package/lib/position.js.map +1 -1
  19. package/lib/quote/__tests__/implementations.test.js +66 -0
  20. package/lib/quote/__tests__/implementations.test.js.map +1 -1
  21. package/lib/quote/implementations/v0.d.ts.map +1 -1
  22. package/lib/quote/implementations/v0.js +12 -1
  23. package/lib/quote/implementations/v0.js.map +1 -1
  24. package/lib/quote/implementations/v1.d.ts.map +1 -1
  25. package/lib/quote/implementations/v1.js +12 -1
  26. package/lib/quote/implementations/v1.js.map +1 -1
  27. package/lib/quote/implementations/v2.d.ts.map +1 -1
  28. package/lib/quote/implementations/v2.js +12 -1
  29. package/lib/quote/implementations/v2.js.map +1 -1
  30. package/lib/quote/implementations/v3.d.ts.map +1 -1
  31. package/lib/quote/implementations/v3.js +12 -1
  32. package/lib/quote/implementations/v3.js.map +1 -1
  33. package/lib/quote/service.js +1 -1
  34. package/lib/quote/service.js.map +1 -1
  35. package/lib/quote/types.d.ts +8 -0
  36. package/lib/quote/types.d.ts.map +1 -1
  37. package/lib/quote/types.js.map +1 -1
  38. package/package.json +3 -3
  39. package/temp/package-deps.json +13 -13
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/quote/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Terminal } from '@yuants/protocol';\nimport { formatTime } from '@yuants/utils';\nimport { Subject, concatMap, defer } from 'rxjs';\nimport { createQuoteState } from './state';\nimport { IQuoteKey, IQuoteRequire, IQuoteState, IQuoteUpdateAction } from './types';\nimport { createQuoteProviderRegistry } from './upstream';\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst quoteState = createQuoteState();\nconst quoteProviderRegistry = createQuoteProviderRegistry(terminal);\n\ntype UpdateTask = { product_ids: string[]; fields: IQuoteKey[]; updated_at: number };\n\ntype IQuoteUpdateQueueStatus = {\n pending: number;\n in_flight: number;\n queued_total: number;\n started_total: number;\n processed_total: number;\n last_enqueued_at?: number;\n last_started_at?: number;\n last_processed_at?: number;\n last_error?: { at: number; code?: string; message?: string };\n};\n\nconst normalizeStrings = (values: string[]) => [...new Set(values)].sort();\nconst normalizeFields = (values: IQuoteKey[]) => [...new Set(values)].sort();\n\nconst analyzeRequestedQuotes = (\n quoteState: IQuoteState,\n product_ids: string[],\n fields: IQuoteKey[],\n updated_at: number,\n): { needUpdate: IQuoteRequire[] } => {\n const needUpdate: IQuoteRequire[] = [];\n for (const product_id of product_ids) {\n for (const field of fields) {\n const tuple = quoteState.getValueTuple(product_id, field);\n if (tuple === undefined) {\n needUpdate.push({ product_id, field });\n continue;\n }\n if (tuple[1] < updated_at) {\n needUpdate.push({ product_id, field });\n }\n }\n }\n return { needUpdate };\n};\n\nconst updateQueue$ = new Subject<UpdateTask>();\nconst updateQueueStats: Omit<IQuoteUpdateQueueStatus, 'pending' | 'in_flight'> = {\n queued_total: 0,\n started_total: 0,\n processed_total: 0,\n};\n\nconst getQueueStatus = (): IQuoteUpdateQueueStatus => {\n const pending = updateQueueStats.queued_total - updateQueueStats.started_total;\n const in_flight = updateQueueStats.started_total - updateQueueStats.processed_total;\n return {\n pending,\n in_flight,\n ...updateQueueStats,\n };\n};\n\nconst enqueueUpdateTask = (task: UpdateTask) => {\n updateQueueStats.queued_total++;\n updateQueueStats.last_enqueued_at = Date.now();\n updateQueue$.next(task);\n};\n\nconst summarizeError = (error: unknown): { code?: string; message?: string } => {\n if (typeof error === 'object' && error !== null) {\n const code = 'code' in error ? (error as any).code : undefined;\n const message = 'message' in error ? (error as any).message : undefined;\n return {\n code: typeof code === 'string' ? code : undefined,\n message: typeof message === 'string' ? message : undefined,\n };\n }\n return {};\n};\n\nconst processUpdateTask = async (task: UpdateTask) => {\n const { needUpdate } = analyzeRequestedQuotes(quoteState, task.product_ids, task.fields, task.updated_at);\n await quoteProviderRegistry.fillQuoteStateFromUpstream({\n quoteState,\n cacheMissed: needUpdate,\n updated_at: task.updated_at,\n });\n};\n\nupdateQueue$\n .pipe(\n concatMap((task) =>\n defer(async () => {\n updateQueueStats.started_total++;\n updateQueueStats.last_started_at = Date.now();\n\n try {\n await processUpdateTask(task);\n } catch (error) {\n const summary = summarizeError(error);\n updateQueueStats.last_error = { at: Date.now(), ...summary };\n console.info(\n formatTime(Date.now()),\n `[VEX][Quote]UpdateQueueTaskFailed`,\n `product_ids=${task.product_ids.length} fields=${task.fields.length} updated_at=${task.updated_at}`,\n JSON.stringify(summary),\n );\n } finally {\n updateQueueStats.processed_total++;\n updateQueueStats.last_processed_at = Date.now();\n }\n }),\n ),\n )\n .subscribe();\n\nterminal.server.provideService<IQuoteUpdateAction>('VEX/UpdateQuotes', {}, async (msg) => {\n quoteState.update(msg.req);\n return { res: { code: 0, message: 'OK' } };\n});\n\nterminal.server.provideService<{}, IQuoteUpdateAction>('VEX/DumpQuoteState', {}, async () => {\n return { res: { code: 0, message: 'OK', data: quoteState.dumpAsObject() } };\n});\n\nterminal.server.provideService<\n { product_ids: string[]; fields: IQuoteKey[]; updated_at: number },\n IQuoteUpdateAction\n>(\n 'VEX/QueryQuotes',\n {\n type: 'object',\n required: ['product_ids', 'fields', 'updated_at'],\n properties: {\n product_ids: {\n type: 'array',\n items: { type: 'string' },\n },\n fields: {\n type: 'array',\n items: { type: 'string' },\n },\n updated_at: { type: 'number' },\n },\n },\n async (msg) => {\n const product_ids = normalizeStrings(msg.req.product_ids);\n const fields = normalizeFields(msg.req.fields);\n const { updated_at } = msg.req;\n\n // SWR strategy: if we have stale or missing data, enqueue an update task,\n // but still return the current data immediately.\n const { needUpdate } = analyzeRequestedQuotes(quoteState, product_ids, fields, updated_at);\n if (needUpdate.length > 0) {\n enqueueUpdateTask({ product_ids, fields, updated_at });\n }\n\n const data = quoteState.filter(product_ids, fields, 0);\n return { res: { code: 0, message: 'OK', data } };\n },\n);\n\nterminal.server.provideService<{}, IQuoteUpdateQueueStatus>('VEX/QuoteUpdateQueueStatus', {}, async () => {\n return { res: { code: 0, message: 'OK', data: getQueueStatus() } };\n});\n"]}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/quote/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Terminal } from '@yuants/protocol';\nimport { formatTime } from '@yuants/utils';\nimport { Subject, concatMap, defer } from 'rxjs';\nimport { createQuoteState } from './state';\nimport { IQuoteKey, IQuoteRequire, IQuoteState, IQuoteUpdateAction } from './types';\nimport { createQuoteProviderRegistry } from './upstream';\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst quoteState = createQuoteState();\nconst quoteProviderRegistry = createQuoteProviderRegistry(terminal);\n\ntype UpdateTask = { product_ids: string[]; fields: IQuoteKey[]; updated_at: number };\n\ntype IQuoteUpdateQueueStatus = {\n pending: number;\n in_flight: number;\n queued_total: number;\n started_total: number;\n processed_total: number;\n last_enqueued_at?: number;\n last_started_at?: number;\n last_processed_at?: number;\n last_error?: { at: number; code?: string; message?: string };\n};\n\nconst normalizeStrings = (values: string[]) => [...new Set(values)].sort();\nconst normalizeFields = (values: IQuoteKey[]) => [...new Set(values)].sort();\n\nconst analyzeRequestedQuotes = (\n quoteState: IQuoteState,\n product_ids: string[],\n fields: IQuoteKey[],\n updated_at: number,\n): { needUpdate: IQuoteRequire[] } => {\n const needUpdate: IQuoteRequire[] = [];\n for (const product_id of product_ids) {\n for (const field of fields) {\n const tuple = quoteState.getValueTuple(product_id, field);\n if (tuple === undefined) {\n needUpdate.push({ product_id, field });\n continue;\n }\n if (tuple[1] < updated_at) {\n needUpdate.push({ product_id, field });\n }\n }\n }\n return { needUpdate };\n};\n\nconst updateQueue$ = new Subject<UpdateTask>();\nconst updateQueueStats: Omit<IQuoteUpdateQueueStatus, 'pending' | 'in_flight'> = {\n queued_total: 0,\n started_total: 0,\n processed_total: 0,\n};\n\nconst getQueueStatus = (): IQuoteUpdateQueueStatus => {\n const pending = updateQueueStats.queued_total - updateQueueStats.started_total;\n const in_flight = updateQueueStats.started_total - updateQueueStats.processed_total;\n return {\n pending,\n in_flight,\n ...updateQueueStats,\n };\n};\n\nconst enqueueUpdateTask = (task: UpdateTask) => {\n updateQueueStats.queued_total++;\n updateQueueStats.last_enqueued_at = Date.now();\n updateQueue$.next(task);\n};\n\nconst summarizeError = (error: unknown): { code?: string; message?: string } => {\n if (typeof error === 'object' && error !== null) {\n const code = 'code' in error ? (error as any).code : undefined;\n const message = 'message' in error ? (error as any).message : undefined;\n return {\n code: typeof code === 'string' ? code : undefined,\n message: typeof message === 'string' ? message : undefined,\n };\n }\n return {};\n};\n\nconst processUpdateTask = async (task: UpdateTask) => {\n const { needUpdate } = analyzeRequestedQuotes(quoteState, task.product_ids, task.fields, task.updated_at);\n await quoteProviderRegistry.fillQuoteStateFromUpstream({\n quoteState,\n cacheMissed: needUpdate,\n updated_at: task.updated_at,\n });\n};\n\nupdateQueue$\n .pipe(\n concatMap((task) =>\n defer(async () => {\n updateQueueStats.started_total++;\n updateQueueStats.last_started_at = Date.now();\n\n try {\n await processUpdateTask(task);\n } catch (error) {\n const summary = summarizeError(error);\n updateQueueStats.last_error = { at: Date.now(), ...summary };\n console.info(\n formatTime(Date.now()),\n `[VEX][Quote]UpdateQueueTaskFailed`,\n `product_ids=${task.product_ids.length} fields=${task.fields.length} updated_at=${task.updated_at}`,\n JSON.stringify(summary),\n );\n } finally {\n updateQueueStats.processed_total++;\n updateQueueStats.last_processed_at = Date.now();\n }\n }),\n ),\n )\n .subscribe();\n\nterminal.server.provideService<IQuoteUpdateAction>('VEX/UpdateQuotes', {}, async (msg) => {\n quoteState.update(msg.req);\n return { res: { code: 0, message: 'OK' } };\n});\n\nterminal.server.provideService<{}, IQuoteUpdateAction>('VEX/DumpQuoteState', {}, async () => {\n return { res: { code: 0, message: 'OK', data: quoteState.dumpAsObject() } };\n});\n\nterminal.server.provideService<\n { product_ids: string[]; fields: IQuoteKey[]; updated_at: number },\n Record<string, Partial<Record<IQuoteKey, string>>>\n>(\n 'VEX/QueryQuotes',\n {\n type: 'object',\n required: ['product_ids', 'fields', 'updated_at'],\n properties: {\n product_ids: {\n type: 'array',\n items: { type: 'string' },\n },\n fields: {\n type: 'array',\n items: { type: 'string' },\n },\n updated_at: { type: 'number' },\n },\n },\n async (msg) => {\n const product_ids = normalizeStrings(msg.req.product_ids);\n const fields = normalizeFields(msg.req.fields);\n const { updated_at } = msg.req;\n\n // SWR strategy: if we have stale or missing data, enqueue an update task,\n // but still return the current data immediately.\n const { needUpdate } = analyzeRequestedQuotes(quoteState, product_ids, fields, updated_at);\n if (needUpdate.length > 0) {\n enqueueUpdateTask({ product_ids, fields, updated_at });\n }\n\n const data = quoteState.filterValues(product_ids, fields);\n return { res: { code: 0, message: 'OK', data } };\n },\n);\n\nterminal.server.provideService<{}, IQuoteUpdateQueueStatus>('VEX/QuoteUpdateQueueStatus', {}, async () => {\n return { res: { code: 0, message: 'OK', data: getQueueStatus() } };\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/quote/types.ts"],"names":[],"mappings":"","sourcesContent":["import { IQuote } from '@yuants/data-quote';\nimport { IQuoteServiceMetadata } from '@yuants/exchange';\n\nexport type IQuoteKey = Exclude<keyof IQuote, 'datasource_id' | 'product_id' | 'updated_at'>;\n\n/**\n * 用于批量更新的数据结构\n * 结构为:\n * product_id -> field_name (keyof IQuote) -> [value: string, updated_at: number]\n * 这样设计的目的是为了减少更新的数据量,同时保留每个字段的更新时间\n *\n * 例如:\n * ```json\n * {\n * \"product_123\": {\n * \"last_price\": [\"100.5\", 1627890123456],\n * \"volume\": [\"1500\", 1627890123456]\n * },\n * \"product_456\": {\n * \"last_price\": [\"200.0\", 1627890123456]\n * }\n * }\n * ```\n * @public\n */\nexport type IQuoteUpdateAction = Record<\n string,\n Partial<Record<IQuoteKey, [value: string, updated_at: number]>>\n>;\nexport interface IQuoteState {\n update: (action: IQuoteUpdateAction) => void;\n dumpAsObject: () => IQuoteUpdateAction;\n getValueTuple: (product_id: string, field: IQuoteKey) => [string, number] | undefined;\n filter: (product_ids: string[], fields: IQuoteKey[], updated_at: number) => IQuoteUpdateAction;\n}\n\nexport interface IQuoteProviderInstance {\n terminal_id: string;\n service_id: string;\n}\n\n/**\n * A \"provider group\" is a capability signature of `GetQuotes`.\n * Multiple vendor terminals may provide the same capability; VEX load-balances across instances.\n */\nexport interface IQuoteProviderGroup {\n group_id: string;\n meta: IQuoteServiceMetadata;\n mapTerminalIdToInstance: Map<string, IQuoteProviderInstance>;\n}\n\nexport interface IQuoteRequire {\n product_id: string;\n field: IQuoteKey;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/quote/types.ts"],"names":[],"mappings":"","sourcesContent":["import { IQuote } from '@yuants/data-quote';\nimport { IQuoteServiceMetadata } from '@yuants/exchange';\n\nexport type IQuoteKey = Exclude<keyof IQuote, 'datasource_id' | 'product_id' | 'updated_at'>;\n\n/**\n * 用于批量更新的数据结构\n * 结构为:\n * product_id -> field_name (keyof IQuote) -> [value: string, updated_at: number]\n * 这样设计的目的是为了减少更新的数据量,同时保留每个字段的更新时间\n *\n * 例如:\n * ```json\n * {\n * \"product_123\": {\n * \"last_price\": [\"100.5\", 1627890123456],\n * \"volume\": [\"1500\", 1627890123456]\n * },\n * \"product_456\": {\n * \"last_price\": [\"200.0\", 1627890123456]\n * }\n * }\n * ```\n * @public\n */\nexport type IQuoteUpdateAction = Record<\n string,\n Partial<Record<IQuoteKey, [value: string, updated_at: number]>>\n>;\nexport interface IQuoteState {\n update: (action: IQuoteUpdateAction) => void;\n dumpAsObject: () => IQuoteUpdateAction;\n getValueTuple: (product_id: string, field: IQuoteKey) => [string, number] | undefined;\n filter: (product_ids: string[], fields: IQuoteKey[], updated_at: number) => IQuoteUpdateAction;\n /**\n * 过滤并获取指定产品和字段的值,即便对应字段不存在或未更新也会返回空字符串\n *\n * @param product_ids\n * @param fields\n * @returns\n */\n filterValues: <K extends IQuoteKey>(\n product_ids: string[],\n fields: K[],\n ) => Record<string, Record<K, string>>;\n}\n\nexport interface IQuoteProviderInstance {\n terminal_id: string;\n service_id: string;\n}\n\n/**\n * A \"provider group\" is a capability signature of `GetQuotes`.\n * Multiple vendor terminals may provide the same capability; VEX load-balances across instances.\n */\nexport interface IQuoteProviderGroup {\n group_id: string;\n meta: IQuoteServiceMetadata;\n mapTerminalIdToInstance: Map<string, IQuoteProviderInstance>;\n}\n\nexport interface IQuoteRequire {\n product_id: string;\n field: IQuoteKey;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"position.d.ts","sourceRoot":"","sources":["../src/position.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAqC5C,eAAO,MAAM,gBAAgB,cAAqB,SAAS,EAAE,KAAG,QAAQ,SAAS,EAAE,CAiFlF,CAAC;AAEF,eAAO,MAAM,cAAc,WAAkB,MAAM,EAAE,KAAG,QAAQ,MAAM,EAAE,CA4BvE,CAAC"}
1
+ {"version":3,"file":"position.d.ts","sourceRoot":"","sources":["../src/position.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAkD5C,eAAO,MAAM,gBAAgB,cAAqB,SAAS,EAAE,KAAG,QAAQ,SAAS,EAAE,CAiFlF,CAAC;AAEF,eAAO,MAAM,cAAc,WAAkB,MAAM,EAAE,KAAG,QAAQ,MAAM,EAAE,CA4BvE,CAAC"}
package/lib/position.js CHANGED
@@ -3,15 +3,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.polyfillOrders = exports.polyfillPosition = void 0;
4
4
  const cache_1 = require("@yuants/cache");
5
5
  const data_product_1 = require("@yuants/data-product");
6
+ const data_quote_1 = require("@yuants/data-quote");
6
7
  const protocol_1 = require("@yuants/protocol");
7
8
  const sql_1 = require("@yuants/sql");
8
9
  const utils_1 = require("@yuants/utils");
9
10
  const terminal = protocol_1.Terminal.fromNodeEnv();
10
11
  const productCache = (0, data_product_1.createClientProductCache)(terminal);
11
12
  const quoteCache = (0, cache_1.createCache)(async (product_id) => {
12
- const sql = `select * from quote where product_id = ${(0, sql_1.escapeSQL)(product_id)}`;
13
- const [quote] = await (0, sql_1.requestSQL)(terminal, sql);
14
- return quote;
13
+ const quoteRecord = await (0, data_quote_1.queryQuotes)(terminal, [product_id], [
14
+ 'ask_price',
15
+ 'bid_price',
16
+ 'last_price',
17
+ 'interest_rate_long',
18
+ 'interest_rate_short',
19
+ 'interest_rate_prev_settled_at',
20
+ 'interest_rate_next_settled_at',
21
+ ], Date.now());
22
+ return quoteRecord[product_id];
15
23
  }, { expire: 30000 });
16
24
  const interestRateIntervalCache = (0, cache_1.createCache)(async (product_id) => {
17
25
  const sql = `select created_at from interest_rate where series_id = ${(0, sql_1.escapeSQL)(product_id)} order by created_at desc limit 2`;
@@ -71,7 +79,7 @@ const polyfillPosition = async (positions) => {
71
79
  }
72
80
  // 利率相关信息的追加
73
81
  if (quote) {
74
- if (quote.interest_rate_next_settled_at !== null) {
82
+ if (quote.interest_rate_next_settled_at != null) {
75
83
  const nextSettledAt = new Date(quote.interest_rate_next_settled_at).getTime();
76
84
  // 优先使用行情数据中的下一个结算时间
77
85
  pos.settlement_scheduled_at = nextSettledAt;
@@ -81,7 +89,7 @@ const polyfillPosition = async (positions) => {
81
89
  pos.settlement_interval = interval;
82
90
  }
83
91
  }
84
- else if (quote.interest_rate_next_settled_at === null && interestRateInterval !== undefined) {
92
+ else if (quote.interest_rate_next_settled_at == null && interestRateInterval !== undefined) {
85
93
  // 估算下一个结算时间
86
94
  // 找到 prev + k * interval > now 的最小 k,则下一个结算时间为 prev + k * interval
87
95
  const k = Math.ceil((Date.now() - interestRateInterval.prev) / interestRateInterval.interval);
@@ -92,12 +100,12 @@ const polyfillPosition = async (positions) => {
92
100
  pos.settlement_interval = interestRateInterval.interval;
93
101
  }
94
102
  if (pos.direction === 'LONG') {
95
- if (quote.interest_rate_long !== null) {
103
+ if (quote.interest_rate_long != null) {
96
104
  pos.interest_to_settle = +quote.interest_rate_long * pos.valuation;
97
105
  }
98
106
  }
99
107
  if (pos.direction === 'SHORT') {
100
- if (quote.interest_rate_short !== null) {
108
+ if (quote.interest_rate_short != null) {
101
109
  pos.interest_to_settle = +quote.interest_rate_short * pos.valuation;
102
110
  }
103
111
  }
@@ -1 +1 @@
1
- {"version":3,"file":"position.js","sourceRoot":"","sources":["../src/position.ts"],"names":[],"mappings":"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","sourcesContent":["import { createCache } from '@yuants/cache';\nimport { IPosition } from '@yuants/data-account';\nimport { IOrder } from '@yuants/data-order';\nimport { createClientProductCache } from '@yuants/data-product';\nimport { IQuote } from '@yuants/data-quote';\nimport { Terminal } from '@yuants/protocol';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\nimport { newError } from '@yuants/utils';\n\nconst terminal = Terminal.fromNodeEnv();\nconst productCache = createClientProductCache(terminal);\nconst quoteCache = createCache<IQuote>(\n async (product_id) => {\n const sql = `select * from quote where product_id = ${escapeSQL(product_id)}`;\n const [quote] = await requestSQL<IQuote[]>(terminal, sql);\n return quote;\n },\n { expire: 30_000 },\n);\n\nconst interestRateIntervalCache = createCache(\n async (product_id: string) => {\n const sql = `select created_at from interest_rate where series_id = ${escapeSQL(\n product_id,\n )} order by created_at desc limit 2`;\n const rates = await requestSQL<{ created_at: string }[]>(terminal, sql);\n if (rates.length < 2) return undefined;\n const prev = new Date(rates[0].created_at).getTime();\n const prevOfPrev = new Date(rates[1].created_at).getTime();\n const interval = prev - prevOfPrev;\n return {\n prev,\n prevOfPrev,\n interval,\n };\n },\n { swrAfter: 60_000, expire: 3600_000 },\n);\n\nexport const polyfillPosition = async (positions: IPosition[]): Promise<IPosition[]> => {\n // TODO: 使用 batch query SQL 优化 product / quote 查询性能\n for (const pos of positions) {\n const [theProduct, quote, interestRateInterval] = await Promise.all([\n productCache.query(pos.product_id),\n quoteCache.query(pos.product_id),\n interestRateIntervalCache.query(pos.product_id),\n ]);\n\n // 估值 = value_scale * volume * closable_price\n if (theProduct) {\n if (theProduct.base_currency) {\n pos.base_currency = theProduct.base_currency;\n }\n if (theProduct.quote_currency) {\n pos.quote_currency = theProduct.quote_currency;\n }\n if (pos.size === undefined && pos.volume !== undefined && pos.direction !== undefined) {\n pos.size = (pos.direction === 'LONG' ? 1 : -1) * pos.volume * (theProduct.value_scale || 1) + '';\n }\n if (pos.free_size === undefined && pos.free_volume !== undefined && pos.direction !== undefined) {\n pos.free_size =\n (pos.direction === 'LONG' ? 1 : -1) * pos.free_volume * (theProduct.value_scale || 1) + '';\n }\n pos.valuation = Math.abs((theProduct.value_scale || 1) * pos.volume * pos.closable_price);\n }\n\n if (quote && pos.size) {\n const sizeNum = +pos.size;\n if (pos.current_price === undefined) {\n if (sizeNum > 0) {\n // 多头头寸使用买一价作为可平仓价格,如果没有买一价则使用最新价\n pos.current_price = (quote.ask_price || quote.last_price) + '';\n } else {\n // 空头头寸使用卖一价作为可平仓价格,如果没有卖一价则使用最新价\n pos.current_price = (quote.bid_price || quote.last_price) + '';\n }\n }\n\n // 计算名义价值\n if (pos.notional === undefined) {\n pos.notional = sizeNum * (+pos.current_price || 0) + '';\n }\n }\n\n // 利率相关信息的追加\n if (quote) {\n if (quote.interest_rate_next_settled_at !== null) {\n const nextSettledAt = new Date(quote.interest_rate_next_settled_at).getTime();\n // 优先使用行情数据中的下一个结算时间\n pos.settlement_scheduled_at = nextSettledAt;\n // 优先使用下一个结算时间推算结算间隔\n if (interestRateInterval !== undefined) {\n const interval = nextSettledAt - interestRateInterval.prev;\n pos.settlement_interval = interval;\n }\n } else if (quote.interest_rate_next_settled_at === null && interestRateInterval !== undefined) {\n // 估算下一个结算时间\n // 找到 prev + k * interval > now 的最小 k,则下一个结算时间为 prev + k * interval\n const k = Math.ceil((Date.now() - interestRateInterval.prev) / interestRateInterval.interval);\n pos.settlement_scheduled_at = interestRateInterval.prev + k * interestRateInterval.interval;\n }\n\n // 如果还没有结算间隔,则使用 interest rate 表的时间间隔作为结算间隔\n if (pos.settlement_interval === undefined && interestRateInterval) {\n pos.settlement_interval = interestRateInterval.interval;\n }\n\n if (pos.direction === 'LONG') {\n if (quote.interest_rate_long !== null) {\n pos.interest_to_settle = +quote.interest_rate_long * pos.valuation;\n }\n }\n if (pos.direction === 'SHORT') {\n if (quote.interest_rate_short !== null) {\n pos.interest_to_settle = +quote.interest_rate_short * pos.valuation;\n }\n }\n }\n }\n return positions;\n};\n\nexport const polyfillOrders = async (orders: IOrder[]): Promise<IOrder[]> => {\n for (const order of orders) {\n const theProduct = await productCache.query(order.product_id);\n if (theProduct) {\n if (order.size !== undefined) {\n const sizeNum = +order.size;\n const sizeStep = theProduct.volume_step * theProduct.value_scale;\n if (!(sizeStep > 0)) throw newError('INVALID_SIZE_STEP', { product: theProduct, sizeStep });\n // check size is multiple of sizeStep\n if (Math.abs(sizeNum - Math.round(sizeNum / sizeStep) * sizeStep) > 1e-16) {\n throw newError('INVALID_ORDER_SIZE_NOT_MULTIPLE_OF_SIZE_STEP', {\n order,\n sizeStep,\n sizeNum,\n product: theProduct,\n });\n }\n\n if (sizeNum >= 0) {\n order.order_direction = order.is_close ? 'CLOSE_SHORT' : 'OPEN_LONG';\n } else {\n order.order_direction = order.is_close ? 'CLOSE_LONG' : 'OPEN_SHORT';\n }\n order.volume = Math.abs(sizeNum) / theProduct.value_scale;\n }\n }\n }\n return orders;\n};\n"]}
1
+ {"version":3,"file":"position.js","sourceRoot":"","sources":["../src/position.ts"],"names":[],"mappings":"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","sourcesContent":["import { createCache } from '@yuants/cache';\nimport { IPosition } from '@yuants/data-account';\nimport { IOrder } from '@yuants/data-order';\nimport { createClientProductCache } from '@yuants/data-product';\nimport { IQuote, queryQuotes } from '@yuants/data-quote';\nimport { Terminal } from '@yuants/protocol';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\nimport { newError } from '@yuants/utils';\n\nconst terminal = Terminal.fromNodeEnv();\nconst productCache = createClientProductCache(terminal);\n\nconst quoteCache = createCache<Partial<IQuote> | undefined>(\n async (product_id) => {\n const quoteRecord = await queryQuotes(\n terminal,\n [product_id],\n [\n 'ask_price',\n 'bid_price',\n 'last_price',\n 'interest_rate_long',\n 'interest_rate_short',\n 'interest_rate_prev_settled_at',\n 'interest_rate_next_settled_at',\n ],\n Date.now(),\n );\n return quoteRecord[product_id];\n },\n { expire: 30_000 },\n);\n\nconst interestRateIntervalCache = createCache(\n async (product_id: string) => {\n const sql = `select created_at from interest_rate where series_id = ${escapeSQL(\n product_id,\n )} order by created_at desc limit 2`;\n const rates = await requestSQL<{ created_at: string }[]>(terminal, sql);\n if (rates.length < 2) return undefined;\n const prev = new Date(rates[0].created_at).getTime();\n const prevOfPrev = new Date(rates[1].created_at).getTime();\n const interval = prev - prevOfPrev;\n return {\n prev,\n prevOfPrev,\n interval,\n };\n },\n { swrAfter: 60_000, expire: 3600_000 },\n);\n\nexport const polyfillPosition = async (positions: IPosition[]): Promise<IPosition[]> => {\n // TODO: 使用 batch query SQL 优化 product / quote 查询性能\n for (const pos of positions) {\n const [theProduct, quote, interestRateInterval] = await Promise.all([\n productCache.query(pos.product_id),\n quoteCache.query(pos.product_id),\n interestRateIntervalCache.query(pos.product_id),\n ]);\n\n // 估值 = value_scale * volume * closable_price\n if (theProduct) {\n if (theProduct.base_currency) {\n pos.base_currency = theProduct.base_currency;\n }\n if (theProduct.quote_currency) {\n pos.quote_currency = theProduct.quote_currency;\n }\n if (pos.size === undefined && pos.volume !== undefined && pos.direction !== undefined) {\n pos.size = (pos.direction === 'LONG' ? 1 : -1) * pos.volume * (theProduct.value_scale || 1) + '';\n }\n if (pos.free_size === undefined && pos.free_volume !== undefined && pos.direction !== undefined) {\n pos.free_size =\n (pos.direction === 'LONG' ? 1 : -1) * pos.free_volume * (theProduct.value_scale || 1) + '';\n }\n pos.valuation = Math.abs((theProduct.value_scale || 1) * pos.volume * pos.closable_price);\n }\n\n if (quote && pos.size) {\n const sizeNum = +pos.size;\n if (pos.current_price === undefined) {\n if (sizeNum > 0) {\n // 多头头寸使用买一价作为可平仓价格,如果没有买一价则使用最新价\n pos.current_price = (quote.ask_price || quote.last_price) + '';\n } else {\n // 空头头寸使用卖一价作为可平仓价格,如果没有卖一价则使用最新价\n pos.current_price = (quote.bid_price || quote.last_price) + '';\n }\n }\n\n // 计算名义价值\n if (pos.notional === undefined) {\n pos.notional = sizeNum * (+pos.current_price || 0) + '';\n }\n }\n\n // 利率相关信息的追加\n if (quote) {\n if (quote.interest_rate_next_settled_at != null) {\n const nextSettledAt = new Date(quote.interest_rate_next_settled_at).getTime();\n // 优先使用行情数据中的下一个结算时间\n pos.settlement_scheduled_at = nextSettledAt;\n // 优先使用下一个结算时间推算结算间隔\n if (interestRateInterval !== undefined) {\n const interval = nextSettledAt - interestRateInterval.prev;\n pos.settlement_interval = interval;\n }\n } else if (quote.interest_rate_next_settled_at == null && interestRateInterval !== undefined) {\n // 估算下一个结算时间\n // 找到 prev + k * interval > now 的最小 k,则下一个结算时间为 prev + k * interval\n const k = Math.ceil((Date.now() - interestRateInterval.prev) / interestRateInterval.interval);\n pos.settlement_scheduled_at = interestRateInterval.prev + k * interestRateInterval.interval;\n }\n\n // 如果还没有结算间隔,则使用 interest rate 表的时间间隔作为结算间隔\n if (pos.settlement_interval === undefined && interestRateInterval) {\n pos.settlement_interval = interestRateInterval.interval;\n }\n\n if (pos.direction === 'LONG') {\n if (quote.interest_rate_long != null) {\n pos.interest_to_settle = +quote.interest_rate_long * pos.valuation;\n }\n }\n if (pos.direction === 'SHORT') {\n if (quote.interest_rate_short != null) {\n pos.interest_to_settle = +quote.interest_rate_short * pos.valuation;\n }\n }\n }\n }\n return positions;\n};\n\nexport const polyfillOrders = async (orders: IOrder[]): Promise<IOrder[]> => {\n for (const order of orders) {\n const theProduct = await productCache.query(order.product_id);\n if (theProduct) {\n if (order.size !== undefined) {\n const sizeNum = +order.size;\n const sizeStep = theProduct.volume_step * theProduct.value_scale;\n if (!(sizeStep > 0)) throw newError('INVALID_SIZE_STEP', { product: theProduct, sizeStep });\n // check size is multiple of sizeStep\n if (Math.abs(sizeNum - Math.round(sizeNum / sizeStep) * sizeStep) > 1e-16) {\n throw newError('INVALID_ORDER_SIZE_NOT_MULTIPLE_OF_SIZE_STEP', {\n order,\n sizeStep,\n sizeNum,\n product: theProduct,\n });\n }\n\n if (sizeNum >= 0) {\n order.order_direction = order.is_close ? 'CLOSE_SHORT' : 'OPEN_LONG';\n } else {\n order.order_direction = order.is_close ? 'CLOSE_LONG' : 'OPEN_SHORT';\n }\n order.volume = Math.abs(sizeNum) / theProduct.value_scale;\n }\n }\n }\n return orders;\n};\n"]}
@@ -214,6 +214,72 @@ describe('IQuoteState implementations', () => {
214
214
  const dumped = quoteState.dumpAsObject();
215
215
  expect(dumped).toEqual(updateAction);
216
216
  });
217
+ // 测试 11: filterValues 方法应该返回值(即使字段不存在)
218
+ it('should return values via filterValues (even if field missing)', () => {
219
+ // 设置测试数据
220
+ quoteState.update({
221
+ product_001: {
222
+ last_price: ['100.0', 1000],
223
+ ask_price: ['101.0', 2000],
224
+ // bid_price not set
225
+ },
226
+ product_002: {
227
+ last_price: ['200.0', 1500],
228
+ },
229
+ });
230
+ // 测试 1: 获取存在的字段
231
+ const result1 = quoteState.filterValues(['product_001', 'product_002'], ['last_price', 'ask_price']);
232
+ expect(result1).toEqual({
233
+ product_001: {
234
+ last_price: '100.0',
235
+ ask_price: '101.0',
236
+ },
237
+ product_002: {
238
+ last_price: '200.0',
239
+ ask_price: '',
240
+ },
241
+ });
242
+ // 测试 2: 获取不存在的产品和字段
243
+ const result2 = quoteState.filterValues(['product_001', 'non_existent'], ['last_price', 'bid_price']);
244
+ expect(result2).toEqual({
245
+ product_001: {
246
+ last_price: '100.0',
247
+ bid_price: '',
248
+ },
249
+ non_existent: {
250
+ last_price: '',
251
+ bid_price: '',
252
+ },
253
+ });
254
+ // 测试 3: 空产品列表
255
+ const result3 = quoteState.filterValues([], ['last_price']);
256
+ expect(result3).toEqual({});
257
+ // 测试 4: 空字段列表
258
+ const result4 = quoteState.filterValues(['product_001'], []);
259
+ expect(result4).toEqual({
260
+ product_001: {},
261
+ });
262
+ // 测试 5: 所有字段类型
263
+ const allFields = [
264
+ 'last_price',
265
+ 'ask_price',
266
+ 'ask_volume',
267
+ 'bid_volume',
268
+ 'bid_price',
269
+ 'interest_rate_short',
270
+ 'open_interest',
271
+ 'interest_rate_prev_settled_at',
272
+ 'interest_rate_next_settled_at',
273
+ 'interest_rate_long',
274
+ ];
275
+ const result5 = quoteState.filterValues(['product_001'], allFields);
276
+ expect(Object.keys(result5.product_001)).toEqual(allFields);
277
+ expect(result5.product_001.last_price).toBe('100.0');
278
+ expect(result5.product_001.ask_price).toBe('101.0');
279
+ // 其他字段应为空字符串
280
+ expect(result5.product_001.bid_price).toBe('');
281
+ expect(result5.product_001.ask_volume).toBe('');
282
+ });
217
283
  });
218
284
  });
219
285
  });
@@ -1 +1 @@
1
- {"version":3,"file":"implementations.test.js","sourceRoot":"","sources":["../../../src/quote/__tests__/implementations.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { implementations } from '../implementations';\nimport { IQuoteKey, IQuoteUpdateAction } from '../types';\n\n// 测试所有实现的通用测试套件\ndescribe('IQuoteState implementations', () => {\n // 测试每个实现\n Object.entries(implementations).forEach(([name, createQuoteState]) => {\n describe(`${name} implementation`, () => {\n let quoteState: ReturnType<typeof createQuoteState>;\n\n beforeEach(() => {\n quoteState = createQuoteState();\n });\n\n // 测试 1: 初始状态应该是空的\n it('should have empty initial state', () => {\n const dumped = quoteState.dumpAsObject();\n expect(dumped).toEqual({});\n });\n\n // 测试 2: 更新单个产品的单个字段\n it('should update single field for single product', () => {\n const updateAction: IQuoteUpdateAction = {\n product_001: {\n last_price: ['100.5', 1000],\n },\n };\n\n quoteState.update(updateAction);\n\n // 验证通过 getValueTuple 获取\n const tuple = quoteState.getValueTuple('product_001', 'last_price');\n expect(tuple).toEqual(['100.5', 1000]);\n\n // 验证 dumpAsObject\n const dumped = quoteState.dumpAsObject();\n expect(dumped).toEqual(updateAction);\n });\n\n // 测试 3: 更新多个产品和多个字段\n it('should update multiple fields for multiple products', () => {\n const updateAction: IQuoteUpdateAction = {\n product_001: {\n last_price: ['100.5', 1000],\n ask_price: ['101.0', 1001],\n },\n product_002: {\n bid_price: ['99.0', 1002],\n ask_volume: ['500', 1003],\n },\n };\n\n quoteState.update(updateAction);\n\n // 验证所有字段\n expect(quoteState.getValueTuple('product_001', 'last_price')).toEqual(['100.5', 1000]);\n expect(quoteState.getValueTuple('product_001', 'ask_price')).toEqual(['101.0', 1001]);\n expect(quoteState.getValueTuple('product_002', 'bid_price')).toEqual(['99.0', 1002]);\n expect(quoteState.getValueTuple('product_002', 'ask_volume')).toEqual(['500', 1003]);\n\n // 验证 dumpAsObject\n const dumped = quoteState.dumpAsObject();\n expect(dumped).toEqual(updateAction);\n });\n\n // 测试 4: 更新应该覆盖旧的时间戳,但忽略更旧的时间戳\n it('should overwrite with newer timestamp, ignore older timestamp', () => {\n // 初始更新\n quoteState.update({\n product_001: {\n last_price: ['100.0', 1000],\n },\n });\n\n // 用更新的时间戳覆盖\n quoteState.update({\n product_001: {\n last_price: ['200.0', 2000],\n },\n });\n\n expect(quoteState.getValueTuple('product_001', 'last_price')).toEqual(['200.0', 2000]);\n\n // 用更旧的时间戳尝试覆盖(应该被忽略)\n quoteState.update({\n product_001: {\n last_price: ['300.0', 1500], // 时间戳 1500 比 2000 旧\n },\n });\n\n // 应该仍然是旧的值\n expect(quoteState.getValueTuple('product_001', 'last_price')).toEqual(['200.0', 2000]);\n });\n\n // 测试 5: 获取不存在的产品或字段应该返回 undefined\n it('should return undefined for non-existent product or field', () => {\n expect(quoteState.getValueTuple('non_existent', 'last_price')).toBeUndefined();\n\n // 添加一个产品但不包含该字段\n quoteState.update({\n product_001: {\n ask_price: ['101.0', 1000],\n },\n });\n\n expect(quoteState.getValueTuple('product_001', 'last_price')).toBeUndefined();\n expect(quoteState.getValueTuple('product_002', 'last_price')).toBeUndefined();\n });\n\n // 测试 6: filter 方法应该返回符合条件的数据\n it('should filter data by product_ids, fields, and updated_at', () => {\n // 设置测试数据\n quoteState.update({\n product_001: {\n last_price: ['100.0', 1000],\n ask_price: ['101.0', 2000], // 较新的时间戳\n bid_price: ['99.0', 500], // 较旧的时间戳\n },\n product_002: {\n last_price: ['200.0', 1500],\n ask_price: ['201.0', 2500],\n },\n product_003: {\n last_price: ['300.0', 1200],\n },\n });\n\n // 测试 1: 过滤特定产品和字段,时间阈值 1000\n const result1 = quoteState.filter(['product_001', 'product_002'], ['last_price', 'ask_price'], 1000);\n\n expect(result1).toEqual({\n product_001: {\n last_price: ['100.0', 1000],\n ask_price: ['101.0', 2000],\n },\n product_002: {\n last_price: ['200.0', 1500],\n ask_price: ['201.0', 2500],\n },\n });\n\n // 测试 2: 过滤时间阈值 1500(排除较旧的数据)\n const result2 = quoteState.filter(\n ['product_001', 'product_002', 'product_003'],\n ['last_price', 'bid_price'],\n 1500,\n );\n\n expect(result2).toEqual({\n product_001: {\n // last_price 时间戳 1000 < 1500,应该被排除\n // bid_price 时间戳 500 < 1500,应该被排除\n },\n product_002: {\n last_price: ['200.0', 1500],\n },\n product_003: {\n // last_price 时间戳 1200 < 1500,应该被排除\n },\n });\n\n // 测试 3: 过滤不存在的产品应该返回空对象\n const result3 = quoteState.filter(['non_existent'], ['last_price'], 0);\n\n expect(result3).toEqual({\n non_existent: {},\n });\n });\n\n // 测试 7: 空更新不应该改变状态\n it('should handle empty update action', () => {\n // 先添加一些数据\n quoteState.update({\n product_001: {\n last_price: ['100.0', 1000],\n },\n });\n\n const beforeDump = quoteState.dumpAsObject();\n\n // 空更新\n quoteState.update({});\n\n const afterDump = quoteState.dumpAsObject();\n expect(afterDump).toEqual(beforeDump);\n });\n\n // 测试 8: 更新时只提供部分字段,不应影响其他字段\n it('should not affect other fields when updating partial fields', () => {\n // 初始设置两个字段\n quoteState.update({\n product_001: {\n last_price: ['100.0', 1000],\n ask_price: ['101.0', 1001],\n },\n });\n\n // 只更新一个字段\n quoteState.update({\n product_001: {\n last_price: ['200.0', 2000],\n },\n });\n\n // last_price 应该更新,ask_price 应该保持不变\n expect(quoteState.getValueTuple('product_001', 'last_price')).toEqual(['200.0', 2000]);\n expect(quoteState.getValueTuple('product_001', 'ask_price')).toEqual(['101.0', 1001]);\n });\n\n // 测试 9: 重复的更新应该保持幂等性\n it('should be idempotent for duplicate updates', () => {\n const updateAction: IQuoteUpdateAction = {\n product_001: {\n last_price: ['100.0', 1000],\n },\n };\n\n // 多次执行相同的更新\n quoteState.update(updateAction);\n quoteState.update(updateAction);\n quoteState.update(updateAction);\n\n // 状态应该与单次更新相同\n expect(quoteState.dumpAsObject()).toEqual(updateAction);\n });\n\n // 测试 10: 字段名称应该是 IQuoteKey 类型\n it('should handle all IQuoteKey field types', () => {\n const allFields: IQuoteKey[] = [\n 'last_price',\n 'ask_price',\n 'ask_volume',\n 'bid_volume',\n 'bid_price',\n 'interest_rate_short',\n 'open_interest',\n 'interest_rate_prev_settled_at',\n 'interest_rate_next_settled_at',\n 'interest_rate_long',\n ];\n\n const updateAction: IQuoteUpdateAction = {\n product_001: {},\n };\n\n // 为每个字段设置值\n allFields.forEach((field, index) => {\n updateAction['product_001'][field] = [`value_${index}`, 1000 + index];\n });\n\n quoteState.update(updateAction);\n\n // 验证每个字段\n allFields.forEach((field, index) => {\n expect(quoteState.getValueTuple('product_001', field)).toEqual([`value_${index}`, 1000 + index]);\n });\n\n // 验证 dumpAsObject\n const dumped = quoteState.dumpAsObject();\n expect(dumped).toEqual(updateAction);\n });\n });\n });\n});\n"]}
1
+ {"version":3,"file":"implementations.test.js","sourceRoot":"","sources":["../../../src/quote/__tests__/implementations.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { implementations } from '../implementations';\nimport { IQuoteKey, IQuoteUpdateAction } from '../types';\n\n// 测试所有实现的通用测试套件\ndescribe('IQuoteState implementations', () => {\n // 测试每个实现\n Object.entries(implementations).forEach(([name, createQuoteState]) => {\n describe(`${name} implementation`, () => {\n let quoteState: ReturnType<typeof createQuoteState>;\n\n beforeEach(() => {\n quoteState = createQuoteState();\n });\n\n // 测试 1: 初始状态应该是空的\n it('should have empty initial state', () => {\n const dumped = quoteState.dumpAsObject();\n expect(dumped).toEqual({});\n });\n\n // 测试 2: 更新单个产品的单个字段\n it('should update single field for single product', () => {\n const updateAction: IQuoteUpdateAction = {\n product_001: {\n last_price: ['100.5', 1000],\n },\n };\n\n quoteState.update(updateAction);\n\n // 验证通过 getValueTuple 获取\n const tuple = quoteState.getValueTuple('product_001', 'last_price');\n expect(tuple).toEqual(['100.5', 1000]);\n\n // 验证 dumpAsObject\n const dumped = quoteState.dumpAsObject();\n expect(dumped).toEqual(updateAction);\n });\n\n // 测试 3: 更新多个产品和多个字段\n it('should update multiple fields for multiple products', () => {\n const updateAction: IQuoteUpdateAction = {\n product_001: {\n last_price: ['100.5', 1000],\n ask_price: ['101.0', 1001],\n },\n product_002: {\n bid_price: ['99.0', 1002],\n ask_volume: ['500', 1003],\n },\n };\n\n quoteState.update(updateAction);\n\n // 验证所有字段\n expect(quoteState.getValueTuple('product_001', 'last_price')).toEqual(['100.5', 1000]);\n expect(quoteState.getValueTuple('product_001', 'ask_price')).toEqual(['101.0', 1001]);\n expect(quoteState.getValueTuple('product_002', 'bid_price')).toEqual(['99.0', 1002]);\n expect(quoteState.getValueTuple('product_002', 'ask_volume')).toEqual(['500', 1003]);\n\n // 验证 dumpAsObject\n const dumped = quoteState.dumpAsObject();\n expect(dumped).toEqual(updateAction);\n });\n\n // 测试 4: 更新应该覆盖旧的时间戳,但忽略更旧的时间戳\n it('should overwrite with newer timestamp, ignore older timestamp', () => {\n // 初始更新\n quoteState.update({\n product_001: {\n last_price: ['100.0', 1000],\n },\n });\n\n // 用更新的时间戳覆盖\n quoteState.update({\n product_001: {\n last_price: ['200.0', 2000],\n },\n });\n\n expect(quoteState.getValueTuple('product_001', 'last_price')).toEqual(['200.0', 2000]);\n\n // 用更旧的时间戳尝试覆盖(应该被忽略)\n quoteState.update({\n product_001: {\n last_price: ['300.0', 1500], // 时间戳 1500 比 2000 旧\n },\n });\n\n // 应该仍然是旧的值\n expect(quoteState.getValueTuple('product_001', 'last_price')).toEqual(['200.0', 2000]);\n });\n\n // 测试 5: 获取不存在的产品或字段应该返回 undefined\n it('should return undefined for non-existent product or field', () => {\n expect(quoteState.getValueTuple('non_existent', 'last_price')).toBeUndefined();\n\n // 添加一个产品但不包含该字段\n quoteState.update({\n product_001: {\n ask_price: ['101.0', 1000],\n },\n });\n\n expect(quoteState.getValueTuple('product_001', 'last_price')).toBeUndefined();\n expect(quoteState.getValueTuple('product_002', 'last_price')).toBeUndefined();\n });\n\n // 测试 6: filter 方法应该返回符合条件的数据\n it('should filter data by product_ids, fields, and updated_at', () => {\n // 设置测试数据\n quoteState.update({\n product_001: {\n last_price: ['100.0', 1000],\n ask_price: ['101.0', 2000], // 较新的时间戳\n bid_price: ['99.0', 500], // 较旧的时间戳\n },\n product_002: {\n last_price: ['200.0', 1500],\n ask_price: ['201.0', 2500],\n },\n product_003: {\n last_price: ['300.0', 1200],\n },\n });\n\n // 测试 1: 过滤特定产品和字段,时间阈值 1000\n const result1 = quoteState.filter(['product_001', 'product_002'], ['last_price', 'ask_price'], 1000);\n\n expect(result1).toEqual({\n product_001: {\n last_price: ['100.0', 1000],\n ask_price: ['101.0', 2000],\n },\n product_002: {\n last_price: ['200.0', 1500],\n ask_price: ['201.0', 2500],\n },\n });\n\n // 测试 2: 过滤时间阈值 1500(排除较旧的数据)\n const result2 = quoteState.filter(\n ['product_001', 'product_002', 'product_003'],\n ['last_price', 'bid_price'],\n 1500,\n );\n\n expect(result2).toEqual({\n product_001: {\n // last_price 时间戳 1000 < 1500,应该被排除\n // bid_price 时间戳 500 < 1500,应该被排除\n },\n product_002: {\n last_price: ['200.0', 1500],\n },\n product_003: {\n // last_price 时间戳 1200 < 1500,应该被排除\n },\n });\n\n // 测试 3: 过滤不存在的产品应该返回空对象\n const result3 = quoteState.filter(['non_existent'], ['last_price'], 0);\n\n expect(result3).toEqual({\n non_existent: {},\n });\n });\n\n // 测试 7: 空更新不应该改变状态\n it('should handle empty update action', () => {\n // 先添加一些数据\n quoteState.update({\n product_001: {\n last_price: ['100.0', 1000],\n },\n });\n\n const beforeDump = quoteState.dumpAsObject();\n\n // 空更新\n quoteState.update({});\n\n const afterDump = quoteState.dumpAsObject();\n expect(afterDump).toEqual(beforeDump);\n });\n\n // 测试 8: 更新时只提供部分字段,不应影响其他字段\n it('should not affect other fields when updating partial fields', () => {\n // 初始设置两个字段\n quoteState.update({\n product_001: {\n last_price: ['100.0', 1000],\n ask_price: ['101.0', 1001],\n },\n });\n\n // 只更新一个字段\n quoteState.update({\n product_001: {\n last_price: ['200.0', 2000],\n },\n });\n\n // last_price 应该更新,ask_price 应该保持不变\n expect(quoteState.getValueTuple('product_001', 'last_price')).toEqual(['200.0', 2000]);\n expect(quoteState.getValueTuple('product_001', 'ask_price')).toEqual(['101.0', 1001]);\n });\n\n // 测试 9: 重复的更新应该保持幂等性\n it('should be idempotent for duplicate updates', () => {\n const updateAction: IQuoteUpdateAction = {\n product_001: {\n last_price: ['100.0', 1000],\n },\n };\n\n // 多次执行相同的更新\n quoteState.update(updateAction);\n quoteState.update(updateAction);\n quoteState.update(updateAction);\n\n // 状态应该与单次更新相同\n expect(quoteState.dumpAsObject()).toEqual(updateAction);\n });\n\n // 测试 10: 字段名称应该是 IQuoteKey 类型\n it('should handle all IQuoteKey field types', () => {\n const allFields: IQuoteKey[] = [\n 'last_price',\n 'ask_price',\n 'ask_volume',\n 'bid_volume',\n 'bid_price',\n 'interest_rate_short',\n 'open_interest',\n 'interest_rate_prev_settled_at',\n 'interest_rate_next_settled_at',\n 'interest_rate_long',\n ];\n\n const updateAction: IQuoteUpdateAction = {\n product_001: {},\n };\n\n // 为每个字段设置值\n allFields.forEach((field, index) => {\n updateAction['product_001'][field] = [`value_${index}`, 1000 + index];\n });\n\n quoteState.update(updateAction);\n\n // 验证每个字段\n allFields.forEach((field, index) => {\n expect(quoteState.getValueTuple('product_001', field)).toEqual([`value_${index}`, 1000 + index]);\n });\n\n // 验证 dumpAsObject\n const dumped = quoteState.dumpAsObject();\n expect(dumped).toEqual(updateAction);\n });\n\n // 测试 11: filterValues 方法应该返回值(即使字段不存在)\n it('should return values via filterValues (even if field missing)', () => {\n // 设置测试数据\n quoteState.update({\n product_001: {\n last_price: ['100.0', 1000],\n ask_price: ['101.0', 2000],\n // bid_price not set\n },\n product_002: {\n last_price: ['200.0', 1500],\n },\n });\n\n // 测试 1: 获取存在的字段\n const result1 = quoteState.filterValues(['product_001', 'product_002'], ['last_price', 'ask_price']);\n expect(result1).toEqual({\n product_001: {\n last_price: '100.0',\n ask_price: '101.0',\n },\n product_002: {\n last_price: '200.0',\n ask_price: '',\n },\n });\n\n // 测试 2: 获取不存在的产品和字段\n const result2 = quoteState.filterValues(['product_001', 'non_existent'], ['last_price', 'bid_price']);\n expect(result2).toEqual({\n product_001: {\n last_price: '100.0',\n bid_price: '',\n },\n non_existent: {\n last_price: '',\n bid_price: '',\n },\n });\n\n // 测试 3: 空产品列表\n const result3 = quoteState.filterValues([], ['last_price']);\n expect(result3).toEqual({});\n\n // 测试 4: 空字段列表\n const result4 = quoteState.filterValues(['product_001'], []);\n expect(result4).toEqual({\n product_001: {},\n });\n\n // 测试 5: 所有字段类型\n const allFields: IQuoteKey[] = [\n 'last_price',\n 'ask_price',\n 'ask_volume',\n 'bid_volume',\n 'bid_price',\n 'interest_rate_short',\n 'open_interest',\n 'interest_rate_prev_settled_at',\n 'interest_rate_next_settled_at',\n 'interest_rate_long',\n ];\n const result5 = quoteState.filterValues(['product_001'], allFields);\n expect(Object.keys(result5.product_001)).toEqual(allFields);\n expect(result5.product_001.last_price).toBe('100.0');\n expect(result5.product_001.ask_price).toBe('101.0');\n // 其他字段应为空字符串\n expect(result5.product_001.bid_price).toBe('');\n expect(result5.product_001.ask_volume).toBe('');\n });\n });\n });\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"v0.d.ts","sourceRoot":"","sources":["../../../src/quote/implementations/v0.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,WAAW,EAAsB,MAAM,UAAU,CAAC;AAEtE;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,WAAW,CA+DhD"}
1
+ {"version":3,"file":"v0.d.ts","sourceRoot":"","sources":["../../../src/quote/implementations/v0.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,WAAW,EAAsB,MAAM,UAAU,CAAC;AAEtE;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,WAAW,CA8EhD"}
@@ -60,7 +60,18 @@ function createQuoteStateV0() {
60
60
  }
61
61
  return result;
62
62
  };
63
- return { update, dumpAsObject, getValueTuple, filter };
63
+ const filterValues = (product_ids, fields) => {
64
+ const result = {};
65
+ for (const product_id of product_ids) {
66
+ result[product_id] = {};
67
+ for (const field of fields) {
68
+ const tuple = getValueTuple(product_id, field);
69
+ result[product_id][field] = tuple ? tuple[0] : '';
70
+ }
71
+ }
72
+ return result;
73
+ };
74
+ return { update, dumpAsObject, getValueTuple, filter, filterValues };
64
75
  }
65
76
  exports.createQuoteStateV0 = createQuoteStateV0;
66
77
  //# sourceMappingURL=v0.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"v0.js","sourceRoot":"","sources":["../../../src/quote/implementations/v0.ts"],"names":[],"mappings":";;;AAEA;;;GAGG;AACH,SAAgB,kBAAkB;IAChC,sDAAsD;IACtD,MAAM,IAAI,GAAG,IAAI,GAAG,EAA4C,CAAC;IAEjE,MAAM,MAAM,GAAG,CAAC,MAA0B,EAAE,EAAE;QAC5C,KAAK,MAAM,UAAU,IAAI,MAAM,EAAE;YAC/B,IAAI,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACtC,IAAI,CAAC,UAAU,EAAE;gBACf,UAAU,GAAG,IAAI,GAAG,EAAE,CAAC;gBACvB,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;aAClC;YAED,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;YAClC,KAAK,MAAM,UAAU,IAAI,MAAM,EAAE;gBAC/B,MAAM,KAAK,GAAG,UAAuB,CAAC;gBACtC,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,MAAM,CAAC,KAAK,CAAE,CAAC;gBAC3C,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACvC,IAAI,CAAC,QAAQ,IAAI,UAAU,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE;oBAC1C,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;iBAC5C;aACF;SACF;IACH,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,GAAuB,EAAE;QAC5C,MAAM,MAAM,GAAuB,EAAE,CAAC;QACtC,IAAI,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE;YACtC,MAAM,WAAW,GAAiD,EAAE,CAAC;YACrE,UAAU,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;gBAClC,WAAW,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;YAC7B,CAAC,CAAC,CAAC;YACH,MAAM,CAAC,UAAU,CAAC,GAAG,WAAW,CAAC;QACnC,CAAC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,CAAC,UAAkB,EAAE,KAAgB,EAAgC,EAAE;QAC3F,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACxC,IAAI,CAAC,UAAU;YAAE,OAAO,SAAS,CAAC;QAClC,OAAO,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,WAAqB,EAAE,MAAmB,EAAE,UAAkB,EAAsB,EAAE;QACpG,MAAM,MAAM,GAAuB,EAAE,CAAC;QACtC,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE;YACpC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACxC,MAAM,WAAW,GAAiD,EAAE,CAAC;YAErE,IAAI,UAAU,EAAE;gBACd,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE;oBAC1B,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;oBACpC,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,EAAE;wBACnC,WAAW,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;qBAC5B;iBACF;aACF;YAED,MAAM,CAAC,UAAU,CAAC,GAAG,WAAW,CAAC;SAClC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;AACzD,CAAC;AA/DD,gDA+DC","sourcesContent":["import { IQuoteKey, IQuoteState, IQuoteUpdateAction } from '../types';\n\n/**\n * 创建一个简单的基于 Map 的实现,用于对比测试\n * 这个实现使用嵌套 Map 结构,作为性能对比的基准\n */\nexport function createQuoteStateV0(): IQuoteState {\n // 使用三层嵌套结构:product_id -> field -> [value, updated_at]\n const data = new Map<string, Map<IQuoteKey, [string, number]>>();\n\n const update = (action: IQuoteUpdateAction) => {\n for (const product_id in action) {\n let productMap = data.get(product_id);\n if (!productMap) {\n productMap = new Map();\n data.set(product_id, productMap);\n }\n\n const fields = action[product_id];\n for (const field_name in fields) {\n const field = field_name as IQuoteKey;\n const [value, updated_at] = fields[field]!;\n const existing = productMap.get(field);\n if (!existing || updated_at >= existing[1]) {\n productMap.set(field, [value, updated_at]);\n }\n }\n }\n };\n\n const dumpAsObject = (): IQuoteUpdateAction => {\n const result: IQuoteUpdateAction = {};\n data.forEach((productMap, product_id) => {\n const productData: Partial<Record<IQuoteKey, [string, number]>> = {};\n productMap.forEach((tuple, field) => {\n productData[field] = tuple;\n });\n result[product_id] = productData;\n });\n return result;\n };\n\n const getValueTuple = (product_id: string, field: IQuoteKey): [string, number] | undefined => {\n const productMap = data.get(product_id);\n if (!productMap) return undefined;\n return productMap.get(field);\n };\n\n const filter = (product_ids: string[], fields: IQuoteKey[], updated_at: number): IQuoteUpdateAction => {\n const result: IQuoteUpdateAction = {};\n for (const product_id of product_ids) {\n const productMap = data.get(product_id);\n const productData: Partial<Record<IQuoteKey, [string, number]>> = {};\n\n if (productMap) {\n for (const field of fields) {\n const tuple = productMap.get(field);\n if (tuple && tuple[1] >= updated_at) {\n productData[field] = tuple;\n }\n }\n }\n\n result[product_id] = productData;\n }\n return result;\n };\n\n return { update, dumpAsObject, getValueTuple, filter };\n}\n"]}
1
+ {"version":3,"file":"v0.js","sourceRoot":"","sources":["../../../src/quote/implementations/v0.ts"],"names":[],"mappings":"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","sourcesContent":["import { IQuoteKey, IQuoteState, IQuoteUpdateAction } from '../types';\n\n/**\n * 创建一个简单的基于 Map 的实现,用于对比测试\n * 这个实现使用嵌套 Map 结构,作为性能对比的基准\n */\nexport function createQuoteStateV0(): IQuoteState {\n // 使用三层嵌套结构:product_id -> field -> [value, updated_at]\n const data = new Map<string, Map<IQuoteKey, [string, number]>>();\n\n const update = (action: IQuoteUpdateAction) => {\n for (const product_id in action) {\n let productMap = data.get(product_id);\n if (!productMap) {\n productMap = new Map();\n data.set(product_id, productMap);\n }\n\n const fields = action[product_id];\n for (const field_name in fields) {\n const field = field_name as IQuoteKey;\n const [value, updated_at] = fields[field]!;\n const existing = productMap.get(field);\n if (!existing || updated_at >= existing[1]) {\n productMap.set(field, [value, updated_at]);\n }\n }\n }\n };\n\n const dumpAsObject = (): IQuoteUpdateAction => {\n const result: IQuoteUpdateAction = {};\n data.forEach((productMap, product_id) => {\n const productData: Partial<Record<IQuoteKey, [string, number]>> = {};\n productMap.forEach((tuple, field) => {\n productData[field] = tuple;\n });\n result[product_id] = productData;\n });\n return result;\n };\n\n const getValueTuple = (product_id: string, field: IQuoteKey): [string, number] | undefined => {\n const productMap = data.get(product_id);\n if (!productMap) return undefined;\n return productMap.get(field);\n };\n\n const filter = (product_ids: string[], fields: IQuoteKey[], updated_at: number): IQuoteUpdateAction => {\n const result: IQuoteUpdateAction = {};\n for (const product_id of product_ids) {\n const productMap = data.get(product_id);\n const productData: Partial<Record<IQuoteKey, [string, number]>> = {};\n\n if (productMap) {\n for (const field of fields) {\n const tuple = productMap.get(field);\n if (tuple && tuple[1] >= updated_at) {\n productData[field] = tuple;\n }\n }\n }\n\n result[product_id] = productData;\n }\n return result;\n };\n\n const filterValues = <K extends IQuoteKey>(\n product_ids: string[],\n fields: K[],\n ): Record<string, Record<K, string>> => {\n const result: Record<string, Record<K, string>> = {};\n for (const product_id of product_ids) {\n result[product_id] = {} as Record<K, string>;\n for (const field of fields) {\n const tuple = getValueTuple(product_id, field);\n result[product_id][field] = tuple ? tuple[0] : '';\n }\n }\n return result;\n };\n\n return { update, dumpAsObject, getValueTuple, filter, filterValues };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"v1.d.ts","sourceRoot":"","sources":["../../../src/quote/implementations/v1.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,WAAW,EAAsB,MAAM,UAAU,CAAC;AAoBtE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAO,WA8FrC,CAAC"}
1
+ {"version":3,"file":"v1.d.ts","sourceRoot":"","sources":["../../../src/quote/implementations/v1.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,WAAW,EAAsB,MAAM,UAAU,CAAC;AAoBtE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAO,WA6GrC,CAAC"}
@@ -111,7 +111,18 @@ const createQuoteStateV1 = () => {
111
111
  }
112
112
  return result;
113
113
  };
114
- return { update, dumpAsObject, getValueTuple, filter };
114
+ const filterValues = (product_ids, fields) => {
115
+ const result = {};
116
+ for (const product_id of product_ids) {
117
+ result[product_id] = {};
118
+ for (const field of fields) {
119
+ const tuple = getValueTuple(product_id, field);
120
+ result[product_id][field] = tuple ? tuple[0] : '';
121
+ }
122
+ }
123
+ return result;
124
+ };
125
+ return { update, dumpAsObject, getValueTuple, filter, filterValues };
115
126
  };
116
127
  exports.createQuoteStateV1 = createQuoteStateV1;
117
128
  //# sourceMappingURL=v1.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"v1.js","sourceRoot":"","sources":["../../../src/quote/implementations/v1.ts"],"names":[],"mappings":";;;AAAA,yCAAyC;AAGzC,wBAAwB;AACxB,MAAM,MAAM,GAAG,CAAC,CAAC,CAAiC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAiB,CAAC,CAAC;IAC3F,mDAAmD;IACnD,UAAU,EAAE,CAAC;IACb,SAAS,EAAE,CAAC;IACZ,UAAU,EAAE,CAAC;IACb,UAAU,EAAE,CAAC;IACb,SAAS,EAAE,CAAC;IACZ,mBAAmB,EAAE,CAAC;IACtB,aAAa,EAAE,CAAC;IAChB,6BAA6B,EAAE,CAAC;IAChC,6BAA6B,EAAE,CAAC;IAChC,kBAAkB,EAAE,CAAC;CACtB,CAAC,CAAC;AAEH,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;AAClC,MAAM,oBAAoB,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAElG;;;;GAIG;AACI,MAAM,kBAAkB,GAAG,GAAgB,EAAE;IAClD,+BAA+B;IAC/B,MAAM,IAAI,GAAwB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtD,2CAA2C;IAC3C,MAAM,cAAc,GAAG,CAAC,UAAkB,EAAE,KAAa,EAAsB,EAAE;QAC/E,IAAI,SAAS,GAAG,mBAAmB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACpD,IAAI,SAAS,KAAK,SAAS,EAAE;YAC3B,OAAO,SAAS,CAAC;SAClB;QACD,MAAM,WAAW,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;QAChD,IAAI,WAAW,KAAK,SAAS;YAAE,MAAM,IAAA,gBAAQ,EAAC,oBAAoB,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAAC;QACzG,OAAO,SAAS,GAAG,WAAW,CAAC;IACjC,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,CAAC,UAAkB,EAAE,KAAgB,EAAgC,EAAE;QAC3F,MAAM,MAAM,GAAG,cAAc,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACjD,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAW,CAAC;QACrC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC1C,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAW,CAAC;QAC9C,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IAC7B,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,CAAC,UAAkB,EAAE,KAAgB,EAAE,KAAa,EAAE,UAAkB,EAAE,EAAE;QAChG,IAAI,MAAM,GAAG,cAAc,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC/C,IAAI,MAAM,KAAK,SAAS,EAAE;YACxB,MAAM,SAAS,GAAG,mBAAmB,CAAC,IAAI,GAAG,WAAW,GAAG,CAAC,CAAC;YAC7D,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC1B,mBAAmB,CAAC,GAAG,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;YAC/C,MAAM,GAAG,SAAS,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;SAClD;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC;IAChC,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,MAA0B,EAAE,EAAE;QAC5C,KAAK,MAAM,UAAU,IAAI,MAAM,EAAE;YAC/B,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;YAClC,KAAK,MAAM,UAAU,IAAI,MAAM,EAAE;gBAC/B,MAAM,KAAK,GAAG,UAAuB,CAAC;gBACtC,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,MAAM,CAAC,KAAK,CAAE,CAAC;gBAC3C,MAAM,QAAQ,GAAG,aAAa,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;gBAClD,IAAI,QAAQ,KAAK,SAAS,IAAI,UAAU,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE;oBACvD,aAAa,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;iBACrD;aACF;SACF;IACH,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,GAAuB,EAAE;QAC5C,MAAM,MAAM,GAAuB,EAAE,CAAC;QACtC,KAAK,MAAM,UAAU,IAAI,QAAQ,EAAE;YACjC,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC;SACzB;QACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE;YACvC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAW,CAAC;YAChC,IAAI,KAAK,KAAK,SAAS;gBAAE,SAAS;YAClC,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAW,CAAC;YAEzC,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC;YACvD,MAAM,UAAU,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC;YAE1C,MAAM,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC/C,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;YAEtC,MAAM,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;SACtD;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF;;;;;;OAMG;IACH,MAAM,MAAM,GAAG,CAAC,WAAqB,EAAE,MAAmB,EAAE,UAAkB,EAAsB,EAAE;QACpG,MAAM,MAAM,GAAuB,EAAE,CAAC;QACtC,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE;YACpC,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC;YACxB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE;gBAC1B,MAAM,KAAK,GAAG,aAAa,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;gBAC/C,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,EAAE;oBACnC,MAAM,CAAC,UAAU,CAAE,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;iBACpC;aACF;SACF;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;AACzD,CAAC,CAAC;AA9FW,QAAA,kBAAkB,sBA8F7B","sourcesContent":["import { newError } from '@yuants/utils';\nimport { IQuoteKey, IQuoteState, IQuoteUpdateAction } from '../types';\n\n// TRICK: 固定字段顺序,方便计算偏移量\nconst FIELDS = ((x: { [key in IQuoteKey]: number }) => Object.keys(x).sort() as IQuoteKey[])({\n // TS TRICK: 强制运行时数组具有 IQuoteKey 的所有字段。不重不漏,味道真是好极了\n last_price: 0,\n ask_price: 0,\n ask_volume: 0,\n bid_volume: 0,\n bid_price: 0,\n interest_rate_short: 0,\n open_interest: 0,\n interest_rate_prev_settled_at: 0,\n interest_rate_next_settled_at: 0,\n interest_rate_long: 0,\n});\n\nconst FIELD_COUNT = FIELDS.length;\nconst mapFieldNameToOffset = Object.fromEntries(FIELDS.map((field, index) => [field, index * 2]));\n\n/**\n * 高效的行情状态管理器\n * 内部使用扁平化数组存储数据,避免内存碎片化和过多的 Map/对象开销\n * 提供高效的读写接口,支持按需更新和查询\n */\nexport const createQuoteStateV1 = (): IQuoteState => {\n // 内部数据结构的设计需要考虑高效的读写性能,防止内存碎片化\n const data: (string | number)[] = [];\n const products: string[] = [];\n const mapProductIdToIndex = new Map<string, number>();\n // 0~20 (10 fields * 2 (value, updated_at))\n const getFieldOffset = (product_id: string, field: string): number | undefined => {\n let baseIndex = mapProductIdToIndex.get(product_id);\n if (baseIndex === undefined) {\n return undefined;\n }\n const fieldOffset = mapFieldNameToOffset[field];\n if (fieldOffset === undefined) throw newError('INVALID_FIELD_NAME', { field, available_fields: FIELDS });\n return baseIndex + fieldOffset;\n };\n\n const getValueTuple = (product_id: string, field: IQuoteKey): [string, number] | undefined => {\n const offset = getFieldOffset(product_id, field);\n if (offset === undefined) return undefined;\n const value = data[offset] as string;\n if (value === undefined) return undefined;\n const updated_at = data[offset + 1] as number;\n return [value, updated_at];\n };\n\n const setValueTuple = (product_id: string, field: IQuoteKey, value: string, updated_at: number) => {\n let offset = getFieldOffset(product_id, field);\n if (offset === undefined) {\n const baseIndex = mapProductIdToIndex.size * FIELD_COUNT * 2;\n products.push(product_id);\n mapProductIdToIndex.set(product_id, baseIndex);\n offset = baseIndex + mapFieldNameToOffset[field];\n }\n data[offset] = value;\n data[offset + 1] = updated_at;\n };\n\n const update = (action: IQuoteUpdateAction) => {\n for (const product_id in action) {\n const fields = action[product_id];\n for (const field_name in fields) {\n const field = field_name as IQuoteKey;\n const [value, updated_at] = fields[field]!;\n const oldTuple = getValueTuple(product_id, field);\n if (oldTuple === undefined || updated_at >= oldTuple[1]) {\n setValueTuple(product_id, field, value, updated_at);\n }\n }\n }\n };\n\n const dumpAsObject = (): IQuoteUpdateAction => {\n const result: IQuoteUpdateAction = {};\n for (const product_id of products) {\n result[product_id] = {};\n }\n for (let i = 0; i < data.length; i += 2) {\n const value = data[i] as string;\n if (value === undefined) continue;\n const updated_at = data[i + 1] as number;\n\n const productIndex = Math.floor(i / (FIELD_COUNT * 2));\n const product_id = products[productIndex];\n\n const fieldIndex = (i % (FIELD_COUNT * 2)) / 2;\n const field_name = FIELDS[fieldIndex];\n\n result[product_id][field_name] = [value, updated_at];\n }\n return result;\n };\n\n /**\n * 过滤状态,返回指定 product_id 列表和字段列表中,且更新时间不早于指定时间的字段数据\n * @param product_ids - 需要过滤的 product_id 列表\n * @param fields - 需要过滤的字段列表\n * @param updated_at - 需要过滤的更新时间阈值 (仅返回更新时间不早于该值的字段)\n * @returns 过滤后的数据\n */\n const filter = (product_ids: string[], fields: IQuoteKey[], updated_at: number): IQuoteUpdateAction => {\n const result: IQuoteUpdateAction = {};\n for (const product_id of product_ids) {\n result[product_id] = {};\n for (const field of fields) {\n const tuple = getValueTuple(product_id, field);\n if (tuple && tuple[1] >= updated_at) {\n result[product_id]![field] = tuple;\n }\n }\n }\n return result;\n };\n\n return { update, dumpAsObject, getValueTuple, filter };\n};\n"]}
1
+ {"version":3,"file":"v1.js","sourceRoot":"","sources":["../../../src/quote/implementations/v1.ts"],"names":[],"mappings":"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","sourcesContent":["import { newError } from '@yuants/utils';\nimport { IQuoteKey, IQuoteState, IQuoteUpdateAction } from '../types';\n\n// TRICK: 固定字段顺序,方便计算偏移量\nconst FIELDS = ((x: { [key in IQuoteKey]: number }) => Object.keys(x).sort() as IQuoteKey[])({\n // TS TRICK: 强制运行时数组具有 IQuoteKey 的所有字段。不重不漏,味道真是好极了\n last_price: 0,\n ask_price: 0,\n ask_volume: 0,\n bid_volume: 0,\n bid_price: 0,\n interest_rate_short: 0,\n open_interest: 0,\n interest_rate_prev_settled_at: 0,\n interest_rate_next_settled_at: 0,\n interest_rate_long: 0,\n});\n\nconst FIELD_COUNT = FIELDS.length;\nconst mapFieldNameToOffset = Object.fromEntries(FIELDS.map((field, index) => [field, index * 2]));\n\n/**\n * 高效的行情状态管理器\n * 内部使用扁平化数组存储数据,避免内存碎片化和过多的 Map/对象开销\n * 提供高效的读写接口,支持按需更新和查询\n */\nexport const createQuoteStateV1 = (): IQuoteState => {\n // 内部数据结构的设计需要考虑高效的读写性能,防止内存碎片化\n const data: (string | number)[] = [];\n const products: string[] = [];\n const mapProductIdToIndex = new Map<string, number>();\n // 0~20 (10 fields * 2 (value, updated_at))\n const getFieldOffset = (product_id: string, field: string): number | undefined => {\n let baseIndex = mapProductIdToIndex.get(product_id);\n if (baseIndex === undefined) {\n return undefined;\n }\n const fieldOffset = mapFieldNameToOffset[field];\n if (fieldOffset === undefined) throw newError('INVALID_FIELD_NAME', { field, available_fields: FIELDS });\n return baseIndex + fieldOffset;\n };\n\n const getValueTuple = (product_id: string, field: IQuoteKey): [string, number] | undefined => {\n const offset = getFieldOffset(product_id, field);\n if (offset === undefined) return undefined;\n const value = data[offset] as string;\n if (value === undefined) return undefined;\n const updated_at = data[offset + 1] as number;\n return [value, updated_at];\n };\n\n const setValueTuple = (product_id: string, field: IQuoteKey, value: string, updated_at: number) => {\n let offset = getFieldOffset(product_id, field);\n if (offset === undefined) {\n const baseIndex = mapProductIdToIndex.size * FIELD_COUNT * 2;\n products.push(product_id);\n mapProductIdToIndex.set(product_id, baseIndex);\n offset = baseIndex + mapFieldNameToOffset[field];\n }\n data[offset] = value;\n data[offset + 1] = updated_at;\n };\n\n const update = (action: IQuoteUpdateAction) => {\n for (const product_id in action) {\n const fields = action[product_id];\n for (const field_name in fields) {\n const field = field_name as IQuoteKey;\n const [value, updated_at] = fields[field]!;\n const oldTuple = getValueTuple(product_id, field);\n if (oldTuple === undefined || updated_at >= oldTuple[1]) {\n setValueTuple(product_id, field, value, updated_at);\n }\n }\n }\n };\n\n const dumpAsObject = (): IQuoteUpdateAction => {\n const result: IQuoteUpdateAction = {};\n for (const product_id of products) {\n result[product_id] = {};\n }\n for (let i = 0; i < data.length; i += 2) {\n const value = data[i] as string;\n if (value === undefined) continue;\n const updated_at = data[i + 1] as number;\n\n const productIndex = Math.floor(i / (FIELD_COUNT * 2));\n const product_id = products[productIndex];\n\n const fieldIndex = (i % (FIELD_COUNT * 2)) / 2;\n const field_name = FIELDS[fieldIndex];\n\n result[product_id][field_name] = [value, updated_at];\n }\n return result;\n };\n\n /**\n * 过滤状态,返回指定 product_id 列表和字段列表中,且更新时间不早于指定时间的字段数据\n * @param product_ids - 需要过滤的 product_id 列表\n * @param fields - 需要过滤的字段列表\n * @param updated_at - 需要过滤的更新时间阈值 (仅返回更新时间不早于该值的字段)\n * @returns 过滤后的数据\n */\n const filter = (product_ids: string[], fields: IQuoteKey[], updated_at: number): IQuoteUpdateAction => {\n const result: IQuoteUpdateAction = {};\n for (const product_id of product_ids) {\n result[product_id] = {};\n for (const field of fields) {\n const tuple = getValueTuple(product_id, field);\n if (tuple && tuple[1] >= updated_at) {\n result[product_id]![field] = tuple;\n }\n }\n }\n return result;\n };\n\n const filterValues = <K extends IQuoteKey>(\n product_ids: string[],\n fields: K[],\n ): Record<string, Record<K, string>> => {\n const result: Record<string, Record<K, string>> = {};\n for (const product_id of product_ids) {\n result[product_id] = {} as Record<K, string>;\n for (const field of fields) {\n const tuple = getValueTuple(product_id, field);\n result[product_id][field] = tuple ? tuple[0] : '';\n }\n }\n return result;\n };\n\n return { update, dumpAsObject, getValueTuple, filter, filterValues };\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"v2.d.ts","sourceRoot":"","sources":["../../../src/quote/implementations/v2.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,WAAW,EAAsB,MAAM,UAAU,CAAC;AAoBtE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAO,WA2FrC,CAAC"}
1
+ {"version":3,"file":"v2.d.ts","sourceRoot":"","sources":["../../../src/quote/implementations/v2.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,WAAW,EAAsB,MAAM,UAAU,CAAC;AAoBtE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAO,WA0GrC,CAAC"}
@@ -105,7 +105,18 @@ const createQuoteStateV2 = () => {
105
105
  }
106
106
  return result;
107
107
  };
108
- return { update, dumpAsObject, getValueTuple, filter };
108
+ const filterValues = (product_ids, fields) => {
109
+ const result = {};
110
+ for (const product_id of product_ids) {
111
+ result[product_id] = {};
112
+ for (const field of fields) {
113
+ const tuple = getValueTuple(product_id, field);
114
+ result[product_id][field] = tuple ? tuple[0] : '';
115
+ }
116
+ }
117
+ return result;
118
+ };
119
+ return { update, dumpAsObject, getValueTuple, filter, filterValues };
109
120
  };
110
121
  exports.createQuoteStateV2 = createQuoteStateV2;
111
122
  //# sourceMappingURL=v2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"v2.js","sourceRoot":"","sources":["../../../src/quote/implementations/v2.ts"],"names":[],"mappings":"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","sourcesContent":["import { newError } from '@yuants/utils';\nimport { IQuoteKey, IQuoteState, IQuoteUpdateAction } from '../types';\n\n// TRICK: 固定字段顺序,方便计算偏移量\nconst FIELDS = ((x: { [key in IQuoteKey]: number }) => Object.keys(x).sort() as IQuoteKey[])({\n // TS TRICK: 强制运行时数组具有 IQuoteKey 的所有字段。不重不漏,味道真是好极了\n last_price: 0,\n ask_price: 0,\n ask_volume: 0,\n bid_volume: 0,\n bid_price: 0,\n interest_rate_short: 0,\n open_interest: 0,\n interest_rate_prev_settled_at: 0,\n interest_rate_next_settled_at: 0,\n interest_rate_long: 0,\n});\n\nconst FIELD_COUNT = FIELDS.length;\nconst mapFieldNameToOffset = Object.fromEntries(FIELDS.map((field, index) => [field, index])); // 改为单倍偏移\n\n/**\n * 高效的行情状态管理器 v2\n * 将 value 和 timestamp 拆分为两个数组,使数组元素类型一致,提高内存访问效率\n */\nexport const createQuoteStateV2 = (): IQuoteState => {\n // 内部数据结构的设计需要考虑高效的读写性能,防止内存碎片化\n const values: string[] = []; // 存储字段值\n const timestamps: number[] = []; // 存储更新时间戳\n const products: string[] = [];\n const mapProductIdToIndex = new Map<string, number>();\n // 每个产品占用 FIELD_COUNT 个位置\n const getFieldOffset = (product_id: string, field: string): number => {\n let baseIndex = mapProductIdToIndex.get(product_id);\n if (baseIndex === undefined) {\n baseIndex = mapProductIdToIndex.size * FIELD_COUNT;\n products.push(product_id);\n mapProductIdToIndex.set(product_id, baseIndex);\n }\n const fieldOffset = mapFieldNameToOffset[field];\n if (fieldOffset === undefined) throw newError('INVALID_FIELD_NAME', { field, available_fields: FIELDS });\n return baseIndex + fieldOffset;\n };\n\n const getValueTuple = (product_id: string, field: IQuoteKey): [string, number] | undefined => {\n const offset = getFieldOffset(product_id, field);\n const value = values[offset];\n if (value === undefined) return undefined;\n const updated_at = timestamps[offset];\n return [value, updated_at];\n };\n\n const setValueTuple = (product_id: string, field: IQuoteKey, value: string, updated_at: number) => {\n const offset = getFieldOffset(product_id, field);\n values[offset] = value;\n timestamps[offset] = updated_at;\n };\n\n const update = (action: IQuoteUpdateAction) => {\n for (const product_id in action) {\n const fields = action[product_id];\n for (const field_name in fields) {\n const field = field_name as IQuoteKey;\n const [value, updated_at] = fields[field]!;\n const oldTuple = getValueTuple(product_id, field);\n\n if (oldTuple === undefined || updated_at >= oldTuple[1]) {\n setValueTuple(product_id, field, value, updated_at);\n }\n }\n }\n };\n\n const dumpAsObject = (): IQuoteUpdateAction => {\n const result: IQuoteUpdateAction = {};\n for (const product_id of products) {\n result[product_id] = {};\n }\n for (let i = 0; i < values.length; i++) {\n const value = values[i];\n if (value === undefined) continue;\n const updated_at = timestamps[i];\n\n const productIndex = Math.floor(i / FIELD_COUNT);\n const product_id = products[productIndex];\n\n const fieldIndex = i % FIELD_COUNT;\n const field_name = FIELDS[fieldIndex];\n\n result[product_id]![field_name] = [value, updated_at];\n }\n return result;\n };\n\n /**\n * 过滤状态,返回指定 product_id 列表和字段列表中,且更新时间不早于指定时间的字段数据\n * @param product_ids - 需要过滤的 product_id 列表\n * @param fields - 需要过滤的字段列表\n * @param updated_at - 需要过滤的更新时间阈值 (仅返回更新时间不早于该值的字段)\n * @returns 过滤后的数据\n */\n const filter = (product_ids: string[], fields: IQuoteKey[], updated_at: number): IQuoteUpdateAction => {\n const result: IQuoteUpdateAction = {};\n for (const product_id of product_ids) {\n result[product_id] = {};\n for (const field of fields) {\n const tuple = getValueTuple(product_id, field);\n if (tuple && tuple[1] >= updated_at) {\n result[product_id]![field] = tuple;\n }\n }\n }\n return result;\n };\n\n return { update, dumpAsObject, getValueTuple, filter };\n};\n"]}
1
+ {"version":3,"file":"v2.js","sourceRoot":"","sources":["../../../src/quote/implementations/v2.ts"],"names":[],"mappings":"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","sourcesContent":["import { newError } from '@yuants/utils';\nimport { IQuoteKey, IQuoteState, IQuoteUpdateAction } from '../types';\n\n// TRICK: 固定字段顺序,方便计算偏移量\nconst FIELDS = ((x: { [key in IQuoteKey]: number }) => Object.keys(x).sort() as IQuoteKey[])({\n // TS TRICK: 强制运行时数组具有 IQuoteKey 的所有字段。不重不漏,味道真是好极了\n last_price: 0,\n ask_price: 0,\n ask_volume: 0,\n bid_volume: 0,\n bid_price: 0,\n interest_rate_short: 0,\n open_interest: 0,\n interest_rate_prev_settled_at: 0,\n interest_rate_next_settled_at: 0,\n interest_rate_long: 0,\n});\n\nconst FIELD_COUNT = FIELDS.length;\nconst mapFieldNameToOffset = Object.fromEntries(FIELDS.map((field, index) => [field, index])); // 改为单倍偏移\n\n/**\n * 高效的行情状态管理器 v2\n * 将 value 和 timestamp 拆分为两个数组,使数组元素类型一致,提高内存访问效率\n */\nexport const createQuoteStateV2 = (): IQuoteState => {\n // 内部数据结构的设计需要考虑高效的读写性能,防止内存碎片化\n const values: string[] = []; // 存储字段值\n const timestamps: number[] = []; // 存储更新时间戳\n const products: string[] = [];\n const mapProductIdToIndex = new Map<string, number>();\n // 每个产品占用 FIELD_COUNT 个位置\n const getFieldOffset = (product_id: string, field: string): number => {\n let baseIndex = mapProductIdToIndex.get(product_id);\n if (baseIndex === undefined) {\n baseIndex = mapProductIdToIndex.size * FIELD_COUNT;\n products.push(product_id);\n mapProductIdToIndex.set(product_id, baseIndex);\n }\n const fieldOffset = mapFieldNameToOffset[field];\n if (fieldOffset === undefined) throw newError('INVALID_FIELD_NAME', { field, available_fields: FIELDS });\n return baseIndex + fieldOffset;\n };\n\n const getValueTuple = (product_id: string, field: IQuoteKey): [string, number] | undefined => {\n const offset = getFieldOffset(product_id, field);\n const value = values[offset];\n if (value === undefined) return undefined;\n const updated_at = timestamps[offset];\n return [value, updated_at];\n };\n\n const setValueTuple = (product_id: string, field: IQuoteKey, value: string, updated_at: number) => {\n const offset = getFieldOffset(product_id, field);\n values[offset] = value;\n timestamps[offset] = updated_at;\n };\n\n const update = (action: IQuoteUpdateAction) => {\n for (const product_id in action) {\n const fields = action[product_id];\n for (const field_name in fields) {\n const field = field_name as IQuoteKey;\n const [value, updated_at] = fields[field]!;\n const oldTuple = getValueTuple(product_id, field);\n\n if (oldTuple === undefined || updated_at >= oldTuple[1]) {\n setValueTuple(product_id, field, value, updated_at);\n }\n }\n }\n };\n\n const dumpAsObject = (): IQuoteUpdateAction => {\n const result: IQuoteUpdateAction = {};\n for (const product_id of products) {\n result[product_id] = {};\n }\n for (let i = 0; i < values.length; i++) {\n const value = values[i];\n if (value === undefined) continue;\n const updated_at = timestamps[i];\n\n const productIndex = Math.floor(i / FIELD_COUNT);\n const product_id = products[productIndex];\n\n const fieldIndex = i % FIELD_COUNT;\n const field_name = FIELDS[fieldIndex];\n\n result[product_id]![field_name] = [value, updated_at];\n }\n return result;\n };\n\n /**\n * 过滤状态,返回指定 product_id 列表和字段列表中,且更新时间不早于指定时间的字段数据\n * @param product_ids - 需要过滤的 product_id 列表\n * @param fields - 需要过滤的字段列表\n * @param updated_at - 需要过滤的更新时间阈值 (仅返回更新时间不早于该值的字段)\n * @returns 过滤后的数据\n */\n const filter = (product_ids: string[], fields: IQuoteKey[], updated_at: number): IQuoteUpdateAction => {\n const result: IQuoteUpdateAction = {};\n for (const product_id of product_ids) {\n result[product_id] = {};\n for (const field of fields) {\n const tuple = getValueTuple(product_id, field);\n if (tuple && tuple[1] >= updated_at) {\n result[product_id]![field] = tuple;\n }\n }\n }\n return result;\n };\n\n const filterValues = <K extends IQuoteKey>(\n product_ids: string[],\n fields: K[],\n ): Record<string, Record<K, string>> => {\n const result: Record<string, Record<K, string>> = {};\n for (const product_id of product_ids) {\n result[product_id] = {} as Record<K, string>;\n for (const field of fields) {\n const tuple = getValueTuple(product_id, field);\n result[product_id][field] = tuple ? tuple[0] : '';\n }\n }\n return result;\n };\n\n return { update, dumpAsObject, getValueTuple, filter, filterValues };\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"v3.d.ts","sourceRoot":"","sources":["../../../src/quote/implementations/v3.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,WAAW,EAAsB,MAAM,UAAU,CAAC;AAoBtE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAO,WAuNrC,CAAC"}
1
+ {"version":3,"file":"v3.d.ts","sourceRoot":"","sources":["../../../src/quote/implementations/v3.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,WAAW,EAAsB,MAAM,UAAU,CAAC;AAoBtE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAO,WAsOrC,CAAC"}
@@ -209,7 +209,18 @@ const createQuoteStateV3 = () => {
209
209
  structures: products.length * 50 + stringPool.length * 16, // 粗略估计
210
210
  },
211
211
  });
212
- return { update, dumpAsObject, getValueTuple, filter };
212
+ const filterValues = (product_ids, fields) => {
213
+ const result = {};
214
+ for (const product_id of product_ids) {
215
+ result[product_id] = {};
216
+ for (const field of fields) {
217
+ const tuple = getValueTuple(product_id, field);
218
+ result[product_id][field] = tuple ? tuple[0] : '';
219
+ }
220
+ }
221
+ return result;
222
+ };
223
+ return { update, dumpAsObject, getValueTuple, filter, filterValues };
213
224
  };
214
225
  exports.createQuoteStateV3 = createQuoteStateV3;
215
226
  //# sourceMappingURL=v3.js.map