@yuants/app-virtual-exchange 0.11.1 → 0.11.3

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 (58) hide show
  1. package/dist/position.js +7 -15
  2. package/dist/position.js.map +1 -1
  3. package/dist/quote/implementations/v1.js +7 -2
  4. package/dist/quote/implementations/v1.js.map +1 -1
  5. package/dist/quote/scheduler.js +244 -0
  6. package/dist/quote/scheduler.js.map +1 -0
  7. package/dist/quote/service.js +4 -67
  8. package/dist/quote/service.js.map +1 -1
  9. package/dist/quote/state.js +1 -0
  10. package/dist/quote/state.js.map +1 -1
  11. package/lib/position.d.ts.map +1 -1
  12. package/lib/position.js +7 -15
  13. package/lib/position.js.map +1 -1
  14. package/lib/quote/implementations/v1.d.ts.map +1 -1
  15. package/lib/quote/implementations/v1.js +7 -2
  16. package/lib/quote/implementations/v1.js.map +1 -1
  17. package/lib/quote/scheduler.d.ts +21 -0
  18. package/lib/quote/scheduler.d.ts.map +1 -0
  19. package/lib/quote/scheduler.js +248 -0
  20. package/lib/quote/scheduler.js.map +1 -0
  21. package/lib/quote/service.js +7 -70
  22. package/lib/quote/service.js.map +1 -1
  23. package/lib/quote/state.d.ts +1 -0
  24. package/lib/quote/state.d.ts.map +1 -1
  25. package/lib/quote/state.js +2 -1
  26. package/lib/quote/state.js.map +1 -1
  27. package/package.json +2 -2
  28. package/temp/package-deps.json +9 -13
  29. package/dist/quote/upstream/executor.js +0 -98
  30. package/dist/quote/upstream/executor.js.map +0 -1
  31. package/dist/quote/upstream/index.js +0 -2
  32. package/dist/quote/upstream/index.js.map +0 -1
  33. package/dist/quote/upstream/prefix-matcher.js +0 -7
  34. package/dist/quote/upstream/prefix-matcher.js.map +0 -1
  35. package/dist/quote/upstream/registry.js +0 -116
  36. package/dist/quote/upstream/registry.js.map +0 -1
  37. package/dist/quote/upstream/router.js +0 -119
  38. package/dist/quote/upstream/router.js.map +0 -1
  39. package/lib/quote/upstream/executor.d.ts +0 -8
  40. package/lib/quote/upstream/executor.d.ts.map +0 -1
  41. package/lib/quote/upstream/executor.js +0 -102
  42. package/lib/quote/upstream/executor.js.map +0 -1
  43. package/lib/quote/upstream/index.d.ts +0 -3
  44. package/lib/quote/upstream/index.d.ts.map +0 -1
  45. package/lib/quote/upstream/index.js +0 -6
  46. package/lib/quote/upstream/index.js.map +0 -1
  47. package/lib/quote/upstream/prefix-matcher.d.ts +0 -8
  48. package/lib/quote/upstream/prefix-matcher.d.ts.map +0 -1
  49. package/lib/quote/upstream/prefix-matcher.js +0 -11
  50. package/lib/quote/upstream/prefix-matcher.js.map +0 -1
  51. package/lib/quote/upstream/registry.d.ts +0 -18
  52. package/lib/quote/upstream/registry.d.ts.map +0 -1
  53. package/lib/quote/upstream/registry.js +0 -120
  54. package/lib/quote/upstream/registry.js.map +0 -1
  55. package/lib/quote/upstream/router.d.ts +0 -27
  56. package/lib/quote/upstream/router.d.ts.map +0 -1
  57. package/lib/quote/upstream/router.js +0 -124
  58. package/lib/quote/upstream/router.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/quote/service.ts"],"names":[],"mappings":";;AAAA,+CAA4C;AAC5C,yCAA2C;AAC3C,+BAAiD;AACjD,mCAA2C;AAE3C,yCAAyD;AAEzD,MAAM,QAAQ,GAAG,mBAAQ,CAAC,WAAW,EAAE,CAAC;AAExC,MAAM,UAAU,GAAG,IAAA,wBAAgB,GAAE,CAAC;AACtC,MAAM,qBAAqB,GAAG,IAAA,sCAA2B,EAAC,QAAQ,CAAC,CAAC;AAgBpE,MAAM,gBAAgB,GAAG,CAAC,MAAgB,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3E,MAAM,eAAe,GAAG,CAAC,MAAmB,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAE7E,MAAM,sBAAsB,GAAG,CAC7B,UAAuB,EACvB,WAAqB,EACrB,MAAmB,EACnB,UAAkB,EACe,EAAE;IACnC,MAAM,UAAU,GAAoB,EAAE,CAAC;IACvC,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE;QACpC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE;YAC1B,MAAM,KAAK,GAAG,UAAU,CAAC,aAAa,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;YAC1D,IAAI,KAAK,KAAK,SAAS,EAAE;gBACvB,UAAU,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;gBACvC,SAAS;aACV;YACD,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,UAAU,EAAE;gBACzB,UAAU,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;aACxC;SACF;KACF;IACD,OAAO,EAAE,UAAU,EAAE,CAAC;AACxB,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,IAAI,cAAO,EAAc,CAAC;AAC/C,MAAM,gBAAgB,GAA2D;IAC/E,YAAY,EAAE,CAAC;IACf,aAAa,EAAE,CAAC;IAChB,eAAe,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,cAAc,GAAG,GAA4B,EAAE;IACnD,MAAM,OAAO,GAAG,gBAAgB,CAAC,YAAY,GAAG,gBAAgB,CAAC,aAAa,CAAC;IAC/E,MAAM,SAAS,GAAG,gBAAgB,CAAC,aAAa,GAAG,gBAAgB,CAAC,eAAe,CAAC;IACpF,uBACE,OAAO;QACP,SAAS,IACN,gBAAgB,EACnB;AACJ,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CAAC,IAAgB,EAAE,EAAE;IAC7C,gBAAgB,CAAC,YAAY,EAAE,CAAC;IAChC,gBAAgB,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC/C,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,KAAc,EAAuC,EAAE;IAC7E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE;QAC/C,MAAM,IAAI,GAAG,MAAM,IAAI,KAAK,CAAC,CAAC,CAAE,KAAa,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAC/D,MAAM,OAAO,GAAG,SAAS,IAAI,KAAK,CAAC,CAAC,CAAE,KAAa,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,OAAO;YACL,IAAI,EAAE,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;YACjD,OAAO,EAAE,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;SAC3D,CAAC;KACH;IACD,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,KAAK,EAAE,IAAgB,EAAE,EAAE;IACnD,MAAM,EAAE,UAAU,EAAE,GAAG,sBAAsB,CAAC,UAAU,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC1G,MAAM,qBAAqB,CAAC,0BAA0B,CAAC;QACrD,UAAU;QACV,WAAW,EAAE,UAAU;QACvB,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,YAAY;KACT,IAAI,CACH,IAAA,gBAAS,EAAC,CAAC,IAAI,EAAE,EAAE,CACjB,IAAA,YAAK,EAAC,KAAK,IAAI,EAAE;IACf,gBAAgB,CAAC,aAAa,EAAE,CAAC;IACjC,gBAAgB,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE9C,IAAI;QACF,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;KAC/B;IAAC,OAAO,KAAK,EAAE;QACd,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QACtC,gBAAgB,CAAC,UAAU,mBAAK,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,IAAK,OAAO,CAAE,CAAC;QAC7D,OAAO,CAAC,IAAI,CACV,IAAA,kBAAU,EAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EACtB,mCAAmC,EACnC,eAAe,IAAI,CAAC,WAAW,CAAC,MAAM,WAAW,IAAI,CAAC,MAAM,CAAC,MAAM,eAAe,IAAI,CAAC,UAAU,EAAE,EACnG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CACxB,CAAC;KACH;YAAS;QACR,gBAAgB,CAAC,eAAe,EAAE,CAAC;QACnC,gBAAgB,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;KACjD;AACH,CAAC,CAAC,CACH,CACF;KACA,SAAS,EAAE,CAAC;AAEf,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAqB,kBAAkB,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IACvF,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;AAC7C,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAyB,oBAAoB,EAAE,EAAE,EAAE,KAAK,IAAI,EAAE;IAC1F,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,YAAY,EAAE,EAAE,EAAE,CAAC;AAC9E,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,MAAM,CAAC,cAAc,CAI5B,iBAAiB,EACjB;IACE,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,CAAC,aAAa,EAAE,QAAQ,EAAE,YAAY,CAAC;IACjD,UAAU,EAAE;QACV,WAAW,EAAE;YACX,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;SAC1B;QACD,MAAM,EAAE;YACN,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;SAC1B;QACD,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;KAC/B;CACF,EACD,KAAK,EAAE,GAAG,EAAE,EAAE;IACZ,MAAM,WAAW,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC;IAE/B,0EAA0E;IAC1E,iDAAiD;IACjD,MAAM,EAAE,UAAU,EAAE,GAAG,sBAAsB,CAAC,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAC3F,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE;QACzB,iBAAiB,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;KACxD;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC1D,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;AACnD,CAAC,CACF,CAAC;AAEF,QAAQ,CAAC,MAAM,CAAC,cAAc,CAA8B,4BAA4B,EAAE,EAAE,EAAE,KAAK,IAAI,EAAE;IACvG,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC;AACrE,CAAC,CAAC,CAAC","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
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/quote/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Terminal } from '@yuants/protocol';\nimport { IQuoteField } from '@yuants/exchange';\nimport { quoteState } from './state';\nimport { IQuoteKey, IQuoteRequire, IQuoteState, IQuoteUpdateAction } from './types';\nimport { markDirty } from './scheduler';\n\nconst terminal = Terminal.fromNodeEnv();\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\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 const { needUpdate } = analyzeRequestedQuotes(quoteState, product_ids, fields, updated_at);\n for (const { product_id, field } of needUpdate) {\n markDirty(product_id, field as any as IQuoteField);\n }\n\n const data = quoteState.filterValues(product_ids, fields);\n return { res: { code: 0, message: 'OK', data } };\n },\n);\n"]}
@@ -1,2 +1,3 @@
1
1
  export declare const createQuoteState: () => import("./types").IQuoteState;
2
+ export declare const quoteState: import("./types").IQuoteState;
2
3
  //# sourceMappingURL=state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/quote/state.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,gBAAgB,qCAAqB,CAAC"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/quote/state.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,gBAAgB,qCAAqB,CAAC;AAEnD,eAAO,MAAM,UAAU,+BAAqB,CAAC"}
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createQuoteState = void 0;
3
+ exports.quoteState = exports.createQuoteState = void 0;
4
4
  const implementations_1 = require("./implementations");
5
5
  // 使用 v1 作为生产版本
6
6
  exports.createQuoteState = implementations_1.implementations.v1;
7
+ exports.quoteState = (0, exports.createQuoteState)();
7
8
  //# sourceMappingURL=state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/quote/state.ts"],"names":[],"mappings":";;;AAAA,uDAAoD;AAEpD,eAAe;AACF,QAAA,gBAAgB,GAAG,iCAAe,CAAC,EAAE,CAAC","sourcesContent":["import { implementations } from './implementations';\n\n// 使用 v1 作为生产版本\nexport const createQuoteState = implementations.v1;\n"]}
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/quote/state.ts"],"names":[],"mappings":";;;AAAA,uDAAoD;AAEpD,eAAe;AACF,QAAA,gBAAgB,GAAG,iCAAe,CAAC,EAAE,CAAC;AAEtC,QAAA,UAAU,GAAG,IAAA,wBAAgB,GAAE,CAAC","sourcesContent":["import { implementations } from './implementations';\n\n// 使用 v1 作为生产版本\nexport const createQuoteState = implementations.v1;\n\nexport const quoteState = createQuoteState();\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuants/app-virtual-exchange",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "main": "lib/index.js",
5
5
  "files": [
6
6
  "dist",
@@ -16,7 +16,7 @@
16
16
  "@yuants/data-quote": "0.4.0",
17
17
  "@yuants/secret": "0.4.1",
18
18
  "@yuants/sql": "0.9.31",
19
- "@yuants/exchange": "0.7.1",
19
+ "@yuants/exchange": "0.8.0",
20
20
  "@yuants/cache": "0.3.4",
21
21
  "rxjs": "~7.5.6",
22
22
  "ajv": "~8.12.0"
@@ -1,17 +1,17 @@
1
1
  {
2
- "apps/virtual-exchange/CHANGELOG.json": "e6d256897299fcf53e06a8fafdfc2858e7cf8a2c",
3
- "apps/virtual-exchange/CHANGELOG.md": "28bc28afb1ba1b3637b25ddece4fea9067e6aa60",
2
+ "apps/virtual-exchange/CHANGELOG.json": "eb778f19dfe0922fc3da2e47ead69f07a7f746b7",
3
+ "apps/virtual-exchange/CHANGELOG.md": "0d6c6cabadf6db4b26c5fda4274d65e27d0dc5c9",
4
4
  "apps/virtual-exchange/api-extractor.json": "62f4fd324425b9a235f0c117975967aab09ced0c",
5
5
  "apps/virtual-exchange/config/jest.config.json": "4bb17bde3ee911163a3edb36a6eb71491d80b1bd",
6
6
  "apps/virtual-exchange/config/rig.json": "f6c7b5537dc77a3170ba9f008bae3b6c3ee11956",
7
7
  "apps/virtual-exchange/config/typescript.json": "854907e8a821f2050f6533368db160c649c25348",
8
8
  "apps/virtual-exchange/etc/app-virtual-exchange.api.md": "6cb40ec1fa2d40a31a7b0dd3f02b8b24a4d7c4de",
9
- "apps/virtual-exchange/package.json": "1261fe420dfbc5b030709e7670eeab63d83363d3",
9
+ "apps/virtual-exchange/package.json": "81b2d1c43e23a1ff7bd869e526d3b4fa34296d34",
10
10
  "apps/virtual-exchange/src/credential.ts": "7ff9cfe06e46005b2a69e60b5596eb1f59d42bba",
11
11
  "apps/virtual-exchange/src/general.ts": "b3d0cd8c57975b9711008beaa05ad7f6812bd57e",
12
12
  "apps/virtual-exchange/src/index.ts": "67e1963facf0ee2aedd871496361cff90bf49356",
13
13
  "apps/virtual-exchange/src/legacy-services.ts": "a9f6b6c61b7a0efc909a443e6fde88c2766562bf",
14
- "apps/virtual-exchange/src/position.ts": "0a0eeac4356bdad82f0e6b58d51d3fbad6c0b9ff",
14
+ "apps/virtual-exchange/src/position.ts": "df2e7fc833bad109c4939df9535577995179120f",
15
15
  "apps/virtual-exchange/src/product-collector.ts": "15ba0a692d694d20b607eaad0287a864577ef30c",
16
16
  "apps/virtual-exchange/src/quote/DESIGN.md": "4b952e24f77a7429fd82cdf29155a725bbebe583",
17
17
  "apps/virtual-exchange/src/quote/QUOTE_STATE_PERFORMANCE_REPORT.md": "b90b7b77a70bb5f4b503d03ccba8f1d07edf7d37",
@@ -26,18 +26,14 @@
26
26
  "apps/virtual-exchange/src/quote/implementations/README.md": "da3afe60c9bc16576cc226d63c233bb2d2fd7c38",
27
27
  "apps/virtual-exchange/src/quote/implementations/index.ts": "99d8c99b24e5e7f14293f489916c83a0bac192d7",
28
28
  "apps/virtual-exchange/src/quote/implementations/v0.ts": "cbc6aa7b7356b18d83a219241e9070e2aeed1365",
29
- "apps/virtual-exchange/src/quote/implementations/v1.ts": "f8c5bd941659c4a8b6dff0541f44133f123fc78a",
29
+ "apps/virtual-exchange/src/quote/implementations/v1.ts": "4ae86c1d5d8dc39c1c1fc695d6990d859e2c2137",
30
30
  "apps/virtual-exchange/src/quote/implementations/v2.ts": "8723cede6cee6092ec0b8ce9b273ee92f47790cf",
31
31
  "apps/virtual-exchange/src/quote/implementations/v3.ts": "f5e4f31c5592d84206d0a27ee51107ba9a91a53d",
32
- "apps/virtual-exchange/src/quote/service.ts": "f74bb5f0fb776839d84e55709f3971301b3fdeb3",
32
+ "apps/virtual-exchange/src/quote/scheduler.ts": "d92a25ed63098a5536b7957f4e98bd1c009843c8",
33
+ "apps/virtual-exchange/src/quote/service.ts": "dd32a7af1aa58b388bcfadfe81b605b646a9b0eb",
33
34
  "apps/virtual-exchange/src/quote/state.benchmark.ts": "075d3c5bab0ae6f7b61accfe977f7d35233b06ad",
34
- "apps/virtual-exchange/src/quote/state.ts": "a13be6e84d30b54de16f90e1cb3feeeab75957df",
35
+ "apps/virtual-exchange/src/quote/state.ts": "8690d156db5850cde46c366a884d6fc707dc3b7c",
35
36
  "apps/virtual-exchange/src/quote/types.ts": "4894fd3df8b5bb92e7aaacda9136fa3e70d45835",
36
- "apps/virtual-exchange/src/quote/upstream/executor.ts": "f7acd3ced6114f1a20ad72c746a9dfbf67b495ec",
37
- "apps/virtual-exchange/src/quote/upstream/index.ts": "10439c0fa997c61fcca1763aae40fddba3daa3da",
38
- "apps/virtual-exchange/src/quote/upstream/prefix-matcher.ts": "079066de8c1d5d91b63ffc2bf67c2163e7b8a540",
39
- "apps/virtual-exchange/src/quote/upstream/registry.ts": "c4ce97bd63963063a690422c5edc41fe988b94a6",
40
- "apps/virtual-exchange/src/quote/upstream/router.ts": "d34853abadaaea73246ec735c8e9b5f2789311c3",
41
37
  "apps/virtual-exchange/tsconfig.json": "22f94ca28b507f8ddcc21b9053158eefd3f726a9",
42
38
  "apps/virtual-exchange/.rush/temp/shrinkwrap-deps.json": "2c8344167a574161e8a89138bc1eb686bf6339de",
43
39
  "libraries/protocol/temp/package-deps.json": "c94931cb0eab7180d90a06d740c1a97c0ac0c6b6",
@@ -48,7 +44,7 @@
48
44
  "libraries/data-quote/temp/package-deps.json": "c2f009f818a503342bef588da21c9abd52beea00",
49
45
  "libraries/secret/temp/package-deps.json": "360293dbcad7870a579c7c043d7f8f87a1b68c48",
50
46
  "libraries/sql/temp/package-deps.json": "041074e0102f9a01820c0fddcf82b9eaee226c79",
51
- "libraries/exchange/temp/package-deps.json": "8e1be9c0b37755b28288f53265acf9e27f99c943",
47
+ "libraries/exchange/temp/package-deps.json": "98f6e1318166102a07cf64b69399b35cb4575222",
52
48
  "libraries/cache/temp/package-deps.json": "e1f94620bc6245add32a4f9d379ed2901129d818",
53
49
  "tools/toolkit/temp/package-deps.json": "23e053490eb8feade23e4d45de4e54883e322711"
54
50
  }
@@ -1,98 +0,0 @@
1
- import { newError } from '@yuants/utils';
2
- import { filter, firstValueFrom, map } from 'rxjs';
3
- const createConcurrencyLimiter = (concurrency) => {
4
- const queue = [];
5
- let active = 0;
6
- const next = () => {
7
- if (active >= concurrency)
8
- return;
9
- const task = queue.shift();
10
- if (!task)
11
- return;
12
- active++;
13
- task();
14
- };
15
- return async (fn) => await new Promise((resolve, reject) => {
16
- queue.push(async () => {
17
- try {
18
- resolve(await fn());
19
- }
20
- catch (e) {
21
- reject(e);
22
- }
23
- finally {
24
- active--;
25
- next();
26
- }
27
- });
28
- next();
29
- });
30
- };
31
- const pickInstance = (params) => {
32
- var _a;
33
- const { group_id, instances, mapGroupIdToRoundRobinIndex } = params;
34
- if (instances.length === 0)
35
- throw newError('VEX_QUOTE_PROVIDER_INSTANCE_EMPTY', { group_id });
36
- const nextIndex = ((_a = mapGroupIdToRoundRobinIndex.get(group_id)) !== null && _a !== void 0 ? _a : 0) % instances.length;
37
- mapGroupIdToRoundRobinIndex.set(group_id, nextIndex + 1);
38
- return instances[nextIndex];
39
- };
40
- const requestGetQuotes = async (params) => {
41
- const { terminal, instance, req } = params;
42
- const res = await firstValueFrom(terminal.client
43
- .request('GetQuotes', instance.terminal_id, req, instance.service_id)
44
- .pipe(map((msg) => msg.res), filter((v) => v !== undefined)));
45
- if (res.code !== 0)
46
- throw newError('VEX_QUOTE_PROVIDER_ERROR', { instance, res });
47
- if (res.data === undefined)
48
- throw newError('VEX_QUOTE_PROVIDER_DATA_MISSING', { instance, res });
49
- return res.data;
50
- };
51
- const runWithProviderGroupConcurrencyLimit1 = async (params) => {
52
- var _a;
53
- const { group_id, mapGroupIdToTailPromise, fn } = params;
54
- const prev = (_a = mapGroupIdToTailPromise.get(group_id)) !== null && _a !== void 0 ? _a : Promise.resolve();
55
- let resolveCurrent = () => { };
56
- const current = new Promise((resolve) => {
57
- resolveCurrent = resolve;
58
- });
59
- mapGroupIdToTailPromise.set(group_id, prev.then(() => current));
60
- await prev;
61
- try {
62
- return await fn();
63
- }
64
- finally {
65
- resolveCurrent();
66
- }
67
- };
68
- export const createGetQuotesExecutor = (terminal) => {
69
- const mapGroupIdToRoundRobinIndex = new Map();
70
- const mapGroupIdToTailPromise = new Map();
71
- const limitGetQuotes = createConcurrencyLimiter(32);
72
- const mapKeyToInFlightGetQuotesPromise = new Map();
73
- const requestGetQuotesInFlight = (key, planned) => {
74
- const existing = mapKeyToInFlightGetQuotesPromise.get(key);
75
- if (existing)
76
- return existing;
77
- const promise = limitGetQuotes(() => runWithProviderGroupConcurrencyLimit1({
78
- group_id: planned.group_id,
79
- mapGroupIdToTailPromise,
80
- fn: async () => {
81
- const instance = pickInstance({
82
- group_id: planned.group_id,
83
- instances: planned.instances,
84
- mapGroupIdToRoundRobinIndex,
85
- });
86
- return await requestGetQuotes({ terminal, instance, req: planned.req });
87
- },
88
- })).finally(() => {
89
- mapKeyToInFlightGetQuotesPromise.delete(key);
90
- });
91
- mapKeyToInFlightGetQuotesPromise.set(key, promise);
92
- return promise;
93
- };
94
- return {
95
- execute: async (requests) => await Promise.all(requests.map(async ({ key, planned }) => await requestGetQuotesInFlight(key, planned))),
96
- };
97
- };
98
- //# sourceMappingURL=executor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../src/quote/upstream/executor.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n IQuoteUpdateAction as IExchangeQuoteUpdateAction,\n IQuoteServiceRequestByVEX,\n} from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { newError } from '@yuants/utils';\nimport { filter, firstValueFrom, map } from 'rxjs';\nimport { IQuoteProviderInstance } from '../types';\nimport { IPlannedRequestWithKey } from './router';\n\ntype IPlannedRequest = IPlannedRequestWithKey['planned'];\n\nconst createConcurrencyLimiter = (concurrency: number) => {\n const queue: Array<() => void> = [];\n let active = 0;\n const next = () => {\n if (active >= concurrency) return;\n const task = queue.shift();\n if (!task) return;\n active++;\n task();\n };\n return async <T>(fn: () => Promise<T>): Promise<T> =>\n await new Promise<T>((resolve, reject) => {\n queue.push(async () => {\n try {\n resolve(await fn());\n } catch (e) {\n reject(e);\n } finally {\n active--;\n next();\n }\n });\n next();\n });\n};\n\nconst pickInstance = (params: {\n group_id: string;\n instances: IQuoteProviderInstance[];\n mapGroupIdToRoundRobinIndex: Map<string, number>;\n}): IQuoteProviderInstance => {\n const { group_id, instances, mapGroupIdToRoundRobinIndex } = params;\n if (instances.length === 0) throw newError('VEX_QUOTE_PROVIDER_INSTANCE_EMPTY', { group_id });\n const nextIndex = (mapGroupIdToRoundRobinIndex.get(group_id) ?? 0) % instances.length;\n mapGroupIdToRoundRobinIndex.set(group_id, nextIndex + 1);\n return instances[nextIndex];\n};\n\nconst requestGetQuotes = async (params: {\n terminal: Terminal;\n instance: IQuoteProviderInstance;\n req: IQuoteServiceRequestByVEX;\n}): Promise<IExchangeQuoteUpdateAction> => {\n const { terminal, instance, req } = params;\n const res = await firstValueFrom(\n terminal.client\n .request<IQuoteServiceRequestByVEX, IExchangeQuoteUpdateAction>(\n 'GetQuotes',\n instance.terminal_id,\n req,\n instance.service_id,\n )\n .pipe(\n map((msg) => msg.res),\n filter((v): v is Exclude<typeof v, undefined> => v !== undefined),\n ),\n );\n if (res.code !== 0) throw newError('VEX_QUOTE_PROVIDER_ERROR', { instance, res });\n if (res.data === undefined) throw newError('VEX_QUOTE_PROVIDER_DATA_MISSING', { instance, res });\n return res.data as any;\n};\n\nconst runWithProviderGroupConcurrencyLimit1 = async <T>(params: {\n group_id: string;\n mapGroupIdToTailPromise: Map<string, Promise<void>>;\n fn: () => Promise<T>;\n}): Promise<T> => {\n const { group_id, mapGroupIdToTailPromise, fn } = params;\n const prev = mapGroupIdToTailPromise.get(group_id) ?? Promise.resolve();\n let resolveCurrent: () => void = () => {};\n const current = new Promise<void>((resolve) => {\n resolveCurrent = resolve;\n });\n mapGroupIdToTailPromise.set(\n group_id,\n prev.then(() => current),\n );\n await prev;\n try {\n return await fn();\n } finally {\n resolveCurrent();\n }\n};\n\nexport interface IGetQuotesExecutor {\n execute: (requests: IPlannedRequestWithKey[]) => Promise<IExchangeQuoteUpdateAction[]>;\n}\n\nexport const createGetQuotesExecutor = (terminal: Terminal): IGetQuotesExecutor => {\n const mapGroupIdToRoundRobinIndex = new Map<string, number>();\n const mapGroupIdToTailPromise = new Map<string, Promise<void>>();\n const limitGetQuotes = createConcurrencyLimiter(32);\n\n const mapKeyToInFlightGetQuotesPromise = new Map<string, Promise<IExchangeQuoteUpdateAction>>();\n const requestGetQuotesInFlight = (key: string, planned: IPlannedRequest) => {\n const existing = mapKeyToInFlightGetQuotesPromise.get(key);\n if (existing) return existing;\n const promise = limitGetQuotes(() =>\n runWithProviderGroupConcurrencyLimit1({\n group_id: planned.group_id,\n mapGroupIdToTailPromise,\n fn: async () => {\n const instance = pickInstance({\n group_id: planned.group_id,\n instances: planned.instances,\n mapGroupIdToRoundRobinIndex,\n });\n return await requestGetQuotes({ terminal, instance, req: planned.req });\n },\n }),\n ).finally(() => {\n mapKeyToInFlightGetQuotesPromise.delete(key);\n });\n mapKeyToInFlightGetQuotesPromise.set(key, promise);\n return promise;\n };\n\n return {\n execute: async (requests) =>\n await Promise.all(\n requests.map(async ({ key, planned }) => await requestGetQuotesInFlight(key, planned)),\n ),\n };\n};\n"]}
@@ -1,2 +0,0 @@
1
- export { createQuoteProviderRegistry } from './registry';
2
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/quote/upstream/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export { createQuoteProviderRegistry } from './registry';\nexport type { IQuoteProviderRegistry } from './registry';\n"]}
@@ -1,7 +0,0 @@
1
- export const createSortedPrefixMatcher = (entries) => {
2
- const sorted = [...entries].sort((a, b) => b.prefix.length - a.prefix.length);
3
- return {
4
- match: (value) => sorted.filter((x) => value.startsWith(x.prefix)).map((x) => x.value),
5
- };
6
- };
7
- //# sourceMappingURL=prefix-matcher.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"prefix-matcher.js","sourceRoot":"","sources":["../../../src/quote/upstream/prefix-matcher.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACvC,OAA4C,EACzB,EAAE;IACrB,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9E,OAAO;QACL,KAAK,EAAE,CAAC,KAAa,EAAO,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;KACpG,CAAC;AACJ,CAAC,CAAC","sourcesContent":["export interface IPrefixMatcher<T> {\n match: (value: string) => T[];\n}\n\nexport const createSortedPrefixMatcher = <T>(\n entries: Array<{ prefix: string; value: T }>,\n): IPrefixMatcher<T> => {\n const sorted = [...entries].sort((a, b) => b.prefix.length - a.prefix.length);\n return {\n match: (value: string): T[] => sorted.filter((x) => value.startsWith(x.prefix)).map((x) => x.value),\n };\n};\n"]}
@@ -1,116 +0,0 @@
1
- import { parseQuoteServiceMetadataFromSchema } from '@yuants/exchange';
2
- import { encodePath, formatTime, listWatch, newError } from '@yuants/utils';
3
- import { EMPTY, map, of, tap } from 'rxjs';
4
- import { createGetQuotesExecutor } from './executor';
5
- import { buildProviderIndices, createQuoteRouter, } from './router';
6
- const normalizeMetadataForGroup = (metadata) => {
7
- const fields = [...metadata.fields].sort();
8
- return Object.assign(Object.assign({}, metadata), { fields });
9
- };
10
- export const createQuoteProviderRegistry = (terminal) => {
11
- const router = createQuoteRouter();
12
- const executor = createGetQuotesExecutor(terminal);
13
- const mapGroupIdToGroup = new Map();
14
- let version = 0;
15
- let cachedIndices;
16
- const quoteServiceInfos$ = terminal.terminalInfos$.pipe(map((infos) => infos.flatMap((info) => {
17
- var _a;
18
- return Object.values((_a = info.serviceInfo) !== null && _a !== void 0 ? _a : {})
19
- .filter((serviceInfo) => serviceInfo.method === 'GetQuotes')
20
- .map((serviceInfo) => ({ terminal_id: info.terminal_id, serviceInfo }));
21
- })));
22
- quoteServiceInfos$
23
- .pipe(listWatch((v) => v.serviceInfo.service_id, (v) => {
24
- var _a, _b;
25
- console.info(formatTime(Date.now()), `[VEX][Quote]DiscoveringGetQuotesProvider...`, `from terminal ${v.terminal_id}`, `service ${v.serviceInfo.service_id}`, `schema: ${JSON.stringify(v.serviceInfo.schema)}`);
26
- try {
27
- const metadata = normalizeMetadataForGroup(parseQuoteServiceMetadataFromSchema(v.serviceInfo.schema));
28
- const group_id = encodePath(metadata.product_id_prefix, metadata.fields.join(','), (_a = metadata.max_products_per_request) !== null && _a !== void 0 ? _a : '');
29
- const provider = {
30
- terminal_id: v.terminal_id,
31
- service_id: v.serviceInfo.service_id || v.serviceInfo.method,
32
- };
33
- const group = (_b = mapGroupIdToGroup.get(group_id)) !== null && _b !== void 0 ? _b : (() => {
34
- const next = {
35
- group_id,
36
- meta: metadata,
37
- mapTerminalIdToInstance: new Map(),
38
- };
39
- mapGroupIdToGroup.set(group_id, next);
40
- return next;
41
- })();
42
- group.mapTerminalIdToInstance.set(provider.terminal_id, provider);
43
- version++;
44
- return of(void 0).pipe(tap({
45
- unsubscribe: () => {
46
- var _a, _b;
47
- (_a = mapGroupIdToGroup.get(group_id)) === null || _a === void 0 ? void 0 : _a.mapTerminalIdToInstance.delete(v.terminal_id);
48
- if (((_b = mapGroupIdToGroup.get(group_id)) === null || _b === void 0 ? void 0 : _b.mapTerminalIdToInstance.size) === 0) {
49
- mapGroupIdToGroup.delete(group_id);
50
- }
51
- version++;
52
- },
53
- }));
54
- }
55
- catch (_c) {
56
- console.info(formatTime(Date.now()), `[VEX][Quote]IgnoredGetQuotesProvider`, `Ignored GetQuotes provider from terminal ${v.terminal_id}`, `service ${v.serviceInfo.service_id} due to invalid schema.`);
57
- return EMPTY;
58
- }
59
- }))
60
- .subscribe();
61
- const snapshot = () => {
62
- const groups = Array.from(mapGroupIdToGroup.values());
63
- if (!cachedIndices || cachedIndices.version !== version) {
64
- cachedIndices = { version, indices: buildProviderIndices(groups) };
65
- }
66
- return { groups, indices: cachedIndices.indices };
67
- };
68
- const planOrThrow = (misses, updated_at) => {
69
- const { groups, indices } = snapshot();
70
- if (groups.length === 0)
71
- throw newError('VEX_QUOTE_PROVIDER_NOT_FOUND', { method: 'GetQuotes' });
72
- return router.planOrThrow(misses, indices, updated_at);
73
- };
74
- const execute = async (requests) => (await executor.execute(requests));
75
- const fillQuoteStateFromUpstream = async (params) => {
76
- const { quoteState, cacheMissed, updated_at } = params;
77
- if (cacheMissed.length === 0)
78
- return;
79
- const { groups } = snapshot();
80
- console.info(formatTime(Date.now()), `[VEX][Quote]UpstreamProviderDiscovery`, ` Discovered ${groups.length} GetQuotes provider groups from terminal infos.`, JSON.stringify(groups));
81
- const plan = planOrThrow(cacheMissed, updated_at);
82
- const mapGroupIdToProductIds = new Map();
83
- for (const { planned } of plan.requests) {
84
- let productIds = mapGroupIdToProductIds.get(planned.group_id);
85
- if (!productIds) {
86
- productIds = new Set();
87
- mapGroupIdToProductIds.set(planned.group_id, productIds);
88
- }
89
- for (const product_id of planned.req.product_ids)
90
- productIds.add(product_id);
91
- }
92
- console.info(formatTime(Date.now()), `[VEX][Quote]RouteDispatched`, ` Routed ${cacheMissed.length} missed quotes to ${mapGroupIdToProductIds.size} provider groups.`, JSON.stringify({
93
- productsByGroupId: [...mapGroupIdToProductIds.entries()].map(([group_id, productIds]) => ({
94
- group_id,
95
- product_ids: [...productIds],
96
- })),
97
- default_action: plan.defaultAction,
98
- }));
99
- if (Object.keys(plan.defaultAction).length > 0) {
100
- quoteState.update(plan.defaultAction);
101
- }
102
- console.info(formatTime(Date.now()), `[VEX][Quote]RequestPlanned`, `Planned ${plan.requests.length} upstream GetQuotes requests.`, JSON.stringify(plan.requests.map(({ key, planned }) => ({
103
- key,
104
- group_id: planned.group_id,
105
- product_ids: planned.req.product_ids,
106
- fields: planned.req.fields,
107
- }))));
108
- const actions = await execute(plan.requests);
109
- console.debug(formatTime(Date.now()), `[VEX][Quote]RequestReceived`, `Received ${actions.length} upstream GetQuotes responses.`, JSON.stringify(actions));
110
- for (const action of actions) {
111
- quoteState.update(action);
112
- }
113
- };
114
- return { snapshot, planOrThrow, execute, fillQuoteStateFromUpstream };
115
- };
116
- //# sourceMappingURL=registry.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/quote/upstream/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { IQuoteServiceMetadata, parseQuoteServiceMetadataFromSchema } from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { encodePath, formatTime, listWatch, newError } from '@yuants/utils';\nimport { EMPTY, map, of, tap } from 'rxjs';\nimport {\n IQuoteKey,\n IQuoteProviderGroup,\n IQuoteProviderInstance,\n IQuoteRequire,\n IQuoteState,\n IQuoteUpdateAction,\n} from '../types';\nimport { createGetQuotesExecutor } from './executor';\nimport {\n buildProviderIndices,\n createQuoteRouter,\n IProviderIndices,\n IPlannedRequestWithKey,\n QuoteUpstreamPlan,\n} from './router';\n\nexport interface IQuoteProviderRegistry {\n snapshot: () => { groups: IQuoteProviderGroup[]; indices: IProviderIndices };\n planOrThrow: (misses: IQuoteRequire[], updated_at: number) => QuoteUpstreamPlan;\n execute: (requests: IPlannedRequestWithKey[]) => Promise<IQuoteUpdateAction[]>;\n fillQuoteStateFromUpstream: (params: {\n quoteState: IQuoteState;\n cacheMissed: IQuoteRequire[];\n updated_at: number;\n }) => Promise<void>;\n}\n\nconst normalizeMetadataForGroup = (metadata: IQuoteServiceMetadata): IQuoteServiceMetadata => {\n const fields = [...(metadata.fields as unknown as IQuoteKey[])].sort();\n return { ...metadata, fields };\n};\n\nexport const createQuoteProviderRegistry = (terminal: Terminal): IQuoteProviderRegistry => {\n const router = createQuoteRouter();\n const executor = createGetQuotesExecutor(terminal);\n\n const mapGroupIdToGroup = new Map<string, IQuoteProviderGroup>();\n let version = 0;\n let cachedIndices: { version: number; indices: IProviderIndices } | undefined;\n\n const quoteServiceInfos$ = terminal.terminalInfos$.pipe(\n map((infos) =>\n infos.flatMap((info) =>\n Object.values(info.serviceInfo ?? {})\n .filter((serviceInfo) => serviceInfo.method === 'GetQuotes')\n .map((serviceInfo) => ({ terminal_id: info.terminal_id, serviceInfo })),\n ),\n ),\n );\n\n quoteServiceInfos$\n .pipe(\n listWatch(\n (v) => v.serviceInfo.service_id,\n (v) => {\n console.info(\n formatTime(Date.now()),\n `[VEX][Quote]DiscoveringGetQuotesProvider...`,\n `from terminal ${v.terminal_id}`,\n `service ${v.serviceInfo.service_id}`,\n `schema: ${JSON.stringify(v.serviceInfo.schema)}`,\n );\n try {\n const metadata = normalizeMetadataForGroup(\n parseQuoteServiceMetadataFromSchema(v.serviceInfo.schema),\n );\n const group_id = encodePath(\n metadata.product_id_prefix,\n (metadata.fields as any[]).join(','),\n metadata.max_products_per_request ?? '',\n );\n const provider: IQuoteProviderInstance = {\n terminal_id: v.terminal_id,\n service_id: v.serviceInfo.service_id || v.serviceInfo.method,\n };\n const group =\n mapGroupIdToGroup.get(group_id) ??\n (() => {\n const next: IQuoteProviderGroup = {\n group_id,\n meta: metadata,\n mapTerminalIdToInstance: new Map<string, IQuoteProviderInstance>(),\n };\n mapGroupIdToGroup.set(group_id, next);\n return next;\n })();\n group.mapTerminalIdToInstance.set(provider.terminal_id, provider);\n version++;\n return of(void 0).pipe(\n tap({\n unsubscribe: () => {\n mapGroupIdToGroup.get(group_id)?.mapTerminalIdToInstance.delete(v.terminal_id);\n if (mapGroupIdToGroup.get(group_id)?.mapTerminalIdToInstance.size === 0) {\n mapGroupIdToGroup.delete(group_id);\n }\n version++;\n },\n }),\n );\n } catch {\n console.info(\n formatTime(Date.now()),\n `[VEX][Quote]IgnoredGetQuotesProvider`,\n `Ignored GetQuotes provider from terminal ${v.terminal_id}`,\n `service ${v.serviceInfo.service_id} due to invalid schema.`,\n );\n return EMPTY;\n }\n },\n ),\n )\n .subscribe();\n\n const snapshot = () => {\n const groups = Array.from(mapGroupIdToGroup.values());\n if (!cachedIndices || cachedIndices.version !== version) {\n cachedIndices = { version, indices: buildProviderIndices(groups) };\n }\n return { groups, indices: cachedIndices.indices };\n };\n\n const planOrThrow = (misses: IQuoteRequire[], updated_at: number): QuoteUpstreamPlan => {\n const { groups, indices } = snapshot();\n if (groups.length === 0) throw newError('VEX_QUOTE_PROVIDER_NOT_FOUND', { method: 'GetQuotes' });\n return router.planOrThrow(misses, indices, updated_at);\n };\n\n const execute = async (requests: IPlannedRequestWithKey[]): Promise<IQuoteUpdateAction[]> =>\n (await executor.execute(requests)) as any;\n\n const fillQuoteStateFromUpstream: IQuoteProviderRegistry['fillQuoteStateFromUpstream'] = async (params) => {\n const { quoteState, cacheMissed, updated_at } = params;\n if (cacheMissed.length === 0) return;\n\n const { groups } = snapshot();\n console.info(\n formatTime(Date.now()),\n `[VEX][Quote]UpstreamProviderDiscovery`,\n ` Discovered ${groups.length} GetQuotes provider groups from terminal infos.`,\n JSON.stringify(groups),\n );\n\n const plan = planOrThrow(cacheMissed, updated_at);\n\n const mapGroupIdToProductIds = new Map<string, Set<string>>();\n for (const { planned } of plan.requests) {\n let productIds = mapGroupIdToProductIds.get(planned.group_id);\n if (!productIds) {\n productIds = new Set<string>();\n mapGroupIdToProductIds.set(planned.group_id, productIds);\n }\n for (const product_id of planned.req.product_ids) productIds.add(product_id);\n }\n console.info(\n formatTime(Date.now()),\n `[VEX][Quote]RouteDispatched`,\n ` Routed ${cacheMissed.length} missed quotes to ${mapGroupIdToProductIds.size} provider groups.`,\n JSON.stringify({\n productsByGroupId: [...mapGroupIdToProductIds.entries()].map(([group_id, productIds]) => ({\n group_id,\n product_ids: [...productIds],\n })),\n default_action: plan.defaultAction,\n }),\n );\n\n if (Object.keys(plan.defaultAction).length > 0) {\n quoteState.update(plan.defaultAction);\n }\n\n console.info(\n formatTime(Date.now()),\n `[VEX][Quote]RequestPlanned`,\n `Planned ${plan.requests.length} upstream GetQuotes requests.`,\n JSON.stringify(\n plan.requests.map(({ key, planned }) => ({\n key,\n group_id: planned.group_id,\n product_ids: planned.req.product_ids,\n fields: planned.req.fields,\n })),\n ),\n );\n\n const actions = await execute(plan.requests);\n console.debug(\n formatTime(Date.now()),\n `[VEX][Quote]RequestReceived`,\n `Received ${actions.length} upstream GetQuotes responses.`,\n JSON.stringify(actions),\n );\n\n for (const action of actions) {\n quoteState.update(action);\n }\n };\n\n return { snapshot, planOrThrow, execute, fillQuoteStateFromUpstream };\n};\n"]}
@@ -1,119 +0,0 @@
1
- import { TextEncoder } from 'util';
2
- import { encodePath, fnv1a64Hex, newError } from '@yuants/utils';
3
- import { createSortedPrefixMatcher } from './prefix-matcher';
4
- const SEP_BYTE = new Uint8Array([0xff]);
5
- const encodeStrings = (parts) => {
6
- const buffers = [];
7
- for (const part of parts) {
8
- buffers.push(new TextEncoder().encode(part));
9
- buffers.push(SEP_BYTE);
10
- }
11
- const totalLength = buffers.reduce((sum, b) => sum + b.length, 0);
12
- const result = new Uint8Array(totalLength);
13
- let offset = 0;
14
- for (const b of buffers) {
15
- result.set(b, offset);
16
- offset += b.length;
17
- }
18
- return result;
19
- };
20
- const fnv1a64HexFromStrings = (parts) => fnv1a64Hex(encodeStrings(parts));
21
- export const buildProviderIndices = (groups) => {
22
- const mapGroupIdToGroup = new Map(groups.map((x) => [x.group_id, x]));
23
- const prefixMatcher = createSortedPrefixMatcher(groups.map((group) => ({ prefix: group.meta.product_id_prefix, value: group.group_id })));
24
- const mapFieldToGroupIds = new Map();
25
- for (const group of groups) {
26
- for (const field of group.meta.fields) {
27
- let groupIds = mapFieldToGroupIds.get(field);
28
- if (!groupIds) {
29
- groupIds = new Set();
30
- mapFieldToGroupIds.set(field, groupIds);
31
- }
32
- groupIds.add(group.group_id);
33
- }
34
- }
35
- return { mapGroupIdToGroup, prefixMatcher, mapFieldToGroupIds };
36
- };
37
- const createRequestKey = (group_id, batchProductIds) => encodePath(group_id, fnv1a64HexFromStrings(batchProductIds));
38
- const planRequests = (params) => {
39
- var _a;
40
- const { productsByGroupId, mapGroupIdToGroup } = params;
41
- const plannedRequests = [];
42
- for (const [group_id, productIdSet] of productsByGroupId) {
43
- const group = mapGroupIdToGroup.get(group_id);
44
- if (!group)
45
- continue;
46
- const sortedProductIds = [...productIdSet].sort();
47
- const max = (_a = group.meta.max_products_per_request) !== null && _a !== void 0 ? _a : sortedProductIds.length;
48
- for (let i = 0; i < sortedProductIds.length; i += max) {
49
- const batchProductIds = sortedProductIds.slice(i, i + max);
50
- const key = createRequestKey(group_id, batchProductIds);
51
- plannedRequests.push({
52
- key,
53
- planned: {
54
- group_id,
55
- instances: Array.from(group.mapTerminalIdToInstance.values()),
56
- req: { product_ids: batchProductIds, fields: group.meta.fields },
57
- },
58
- });
59
- }
60
- }
61
- return plannedRequests;
62
- };
63
- export const createQuoteRouter = () => ({
64
- planOrThrow: (misses, indices, updated_at) => {
65
- const { prefixMatcher, mapFieldToGroupIds, mapGroupIdToGroup } = indices;
66
- const productsByGroupId = new Map();
67
- const defaultAction = {};
68
- const unroutableProducts = new Set();
69
- const mapProductIdToGroupIds = new Map();
70
- for (const miss of misses) {
71
- const { product_id, field } = miss;
72
- let productGroupIds = mapProductIdToGroupIds.get(product_id);
73
- if (!productGroupIds) {
74
- productGroupIds = prefixMatcher.match(product_id);
75
- mapProductIdToGroupIds.set(product_id, productGroupIds);
76
- }
77
- if (productGroupIds.length === 0) {
78
- unroutableProducts.add(product_id);
79
- continue;
80
- }
81
- const fieldGroupIds = mapFieldToGroupIds.get(field);
82
- if (!fieldGroupIds) {
83
- if (!defaultAction[product_id])
84
- defaultAction[product_id] = {};
85
- defaultAction[product_id][field] = ['', updated_at];
86
- continue;
87
- }
88
- let matched = false;
89
- for (const group_id of productGroupIds) {
90
- if (!fieldGroupIds.has(group_id))
91
- continue;
92
- matched = true;
93
- let productIds = productsByGroupId.get(group_id);
94
- if (!productIds) {
95
- productIds = new Set();
96
- productsByGroupId.set(group_id, productIds);
97
- }
98
- productIds.add(product_id);
99
- }
100
- if (!matched) {
101
- if (!defaultAction[product_id])
102
- defaultAction[product_id] = {};
103
- defaultAction[product_id][field] = ['', updated_at];
104
- }
105
- }
106
- if (unroutableProducts.size !== 0) {
107
- throw newError('VEX_QUOTE_PRODUCT_UNROUTABLE', {
108
- updated_at,
109
- unroutable_products: [...unroutableProducts].slice(0, 200),
110
- unroutable_products_total: unroutableProducts.size,
111
- });
112
- }
113
- return {
114
- defaultAction,
115
- requests: planRequests({ productsByGroupId, mapGroupIdToGroup }),
116
- };
117
- },
118
- });
119
- //# sourceMappingURL=router.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"router.js","sourceRoot":"","sources":["../../../src/quote/upstream/router.ts"],"names":[],"mappings":"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","sourcesContent":["import { TextEncoder } from 'util';\n\nimport { IQuoteServiceRequestByVEX } from '@yuants/exchange';\nimport { encodePath, fnv1a64Hex, newError } from '@yuants/utils';\nimport { createSortedPrefixMatcher, IPrefixMatcher } from './prefix-matcher';\nimport {\n IQuoteKey,\n IQuoteProviderGroup,\n IQuoteProviderInstance,\n IQuoteRequire,\n IQuoteUpdateAction,\n} from '../types';\n\nconst SEP_BYTE = new Uint8Array([0xff]);\n\nconst encodeStrings = (parts: string[]): Uint8Array => {\n const buffers: Uint8Array[] = [];\n for (const part of parts) {\n buffers.push(new TextEncoder().encode(part));\n buffers.push(SEP_BYTE);\n }\n const totalLength = buffers.reduce((sum, b) => sum + b.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const b of buffers) {\n result.set(b, offset);\n offset += b.length;\n }\n return result;\n};\n\nconst fnv1a64HexFromStrings = (parts: string[]): string => fnv1a64Hex(encodeStrings(parts));\n\nexport type IPlannedRequest = {\n group_id: string;\n instances: IQuoteProviderInstance[];\n req: IQuoteServiceRequestByVEX;\n};\n\nexport type IPlannedRequestWithKey = { key: string; planned: IPlannedRequest };\n\nexport type QuoteUpstreamPlan = {\n defaultAction: IQuoteUpdateAction;\n requests: IPlannedRequestWithKey[];\n};\n\nexport type IProviderIndices = {\n mapGroupIdToGroup: Map<string, IQuoteProviderGroup>;\n prefixMatcher: IPrefixMatcher<string>;\n mapFieldToGroupIds: Map<IQuoteKey, Set<string>>;\n};\n\nexport const buildProviderIndices = (groups: IQuoteProviderGroup[]): IProviderIndices => {\n const mapGroupIdToGroup = new Map(groups.map((x) => [x.group_id, x] as const));\n const prefixMatcher = createSortedPrefixMatcher(\n groups.map((group) => ({ prefix: group.meta.product_id_prefix, value: group.group_id })),\n );\n const mapFieldToGroupIds = new Map<IQuoteKey, Set<string>>();\n for (const group of groups) {\n for (const field of group.meta.fields as IQuoteKey[]) {\n let groupIds = mapFieldToGroupIds.get(field);\n if (!groupIds) {\n groupIds = new Set<string>();\n mapFieldToGroupIds.set(field, groupIds);\n }\n groupIds.add(group.group_id);\n }\n }\n return { mapGroupIdToGroup, prefixMatcher, mapFieldToGroupIds };\n};\n\nconst createRequestKey = (group_id: string, batchProductIds: string[]) =>\n encodePath(group_id, fnv1a64HexFromStrings(batchProductIds));\n\nconst planRequests = (params: {\n productsByGroupId: Map<string, Set<string>>;\n mapGroupIdToGroup: Map<string, IQuoteProviderGroup>;\n}): IPlannedRequestWithKey[] => {\n const { productsByGroupId, mapGroupIdToGroup } = params;\n const plannedRequests: IPlannedRequestWithKey[] = [];\n for (const [group_id, productIdSet] of productsByGroupId) {\n const group = mapGroupIdToGroup.get(group_id);\n if (!group) continue;\n const sortedProductIds = [...productIdSet].sort();\n const max = group.meta.max_products_per_request ?? sortedProductIds.length;\n for (let i = 0; i < sortedProductIds.length; i += max) {\n const batchProductIds = sortedProductIds.slice(i, i + max);\n const key = createRequestKey(group_id, batchProductIds);\n plannedRequests.push({\n key,\n planned: {\n group_id,\n instances: Array.from(group.mapTerminalIdToInstance.values()),\n req: { product_ids: batchProductIds, fields: group.meta.fields as any },\n },\n });\n }\n }\n return plannedRequests;\n};\n\nexport interface IQuoteRouter {\n planOrThrow: (misses: IQuoteRequire[], indices: IProviderIndices, updated_at: number) => QuoteUpstreamPlan;\n}\n\nexport const createQuoteRouter = (): IQuoteRouter => ({\n planOrThrow: (misses, indices, updated_at) => {\n const { prefixMatcher, mapFieldToGroupIds, mapGroupIdToGroup } = indices;\n const productsByGroupId = new Map<string, Set<string>>();\n const defaultAction: IQuoteUpdateAction = {};\n const unroutableProducts = new Set<string>();\n\n const mapProductIdToGroupIds = new Map<string, string[]>();\n\n for (const miss of misses) {\n const { product_id, field } = miss;\n\n let productGroupIds = mapProductIdToGroupIds.get(product_id);\n if (!productGroupIds) {\n productGroupIds = prefixMatcher.match(product_id);\n mapProductIdToGroupIds.set(product_id, productGroupIds);\n }\n if (productGroupIds.length === 0) {\n unroutableProducts.add(product_id);\n continue;\n }\n\n const fieldGroupIds = mapFieldToGroupIds.get(field);\n if (!fieldGroupIds) {\n if (!defaultAction[product_id]) defaultAction[product_id] = {};\n defaultAction[product_id]![field] = ['', updated_at];\n continue;\n }\n\n let matched = false;\n for (const group_id of productGroupIds) {\n if (!fieldGroupIds.has(group_id)) continue;\n matched = true;\n let productIds = productsByGroupId.get(group_id);\n if (!productIds) {\n productIds = new Set<string>();\n productsByGroupId.set(group_id, productIds);\n }\n productIds.add(product_id);\n }\n\n if (!matched) {\n if (!defaultAction[product_id]) defaultAction[product_id] = {};\n defaultAction[product_id]![field] = ['', updated_at];\n }\n }\n\n if (unroutableProducts.size !== 0) {\n throw newError('VEX_QUOTE_PRODUCT_UNROUTABLE', {\n updated_at,\n unroutable_products: [...unroutableProducts].slice(0, 200),\n unroutable_products_total: unroutableProducts.size,\n });\n }\n\n return {\n defaultAction,\n requests: planRequests({ productsByGroupId, mapGroupIdToGroup }),\n };\n },\n});\n"]}
@@ -1,8 +0,0 @@
1
- import { IQuoteUpdateAction as IExchangeQuoteUpdateAction } from '@yuants/exchange';
2
- import { Terminal } from '@yuants/protocol';
3
- import { IPlannedRequestWithKey } from './router';
4
- export interface IGetQuotesExecutor {
5
- execute: (requests: IPlannedRequestWithKey[]) => Promise<IExchangeQuoteUpdateAction[]>;
6
- }
7
- export declare const createGetQuotesExecutor: (terminal: Terminal) => IGetQuotesExecutor;
8
- //# sourceMappingURL=executor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/quote/upstream/executor.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,IAAI,0BAA0B,EAEjD,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAI5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAyFlD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,QAAQ,EAAE,sBAAsB,EAAE,KAAK,OAAO,CAAC,0BAA0B,EAAE,CAAC,CAAC;CACxF;AAED,eAAO,MAAM,uBAAuB,aAAc,QAAQ,KAAG,kBAmC5D,CAAC"}