jspurefix 5.11.2 → 5.11.4

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 (104) hide show
  1. package/.gitattributes +4 -0
  2. package/dist/buffer/index.d.ts +6 -0
  3. package/dist/buffer/index.js +6 -0
  4. package/dist/buffer/index.js.map +1 -1
  5. package/dist/collections/index.d.ts +1 -0
  6. package/dist/collections/index.js +16 -0
  7. package/dist/collections/index.js.map +1 -1
  8. package/dist/config/winston-logger.js +2 -0
  9. package/dist/config/winston-logger.js.map +1 -1
  10. package/dist/dictionary/contained/index.d.ts +3 -0
  11. package/dist/dictionary/contained/index.js +3 -0
  12. package/dist/dictionary/contained/index.js.map +1 -1
  13. package/dist/dictionary/index.d.ts +5 -0
  14. package/dist/dictionary/index.js +5 -0
  15. package/dist/dictionary/index.js.map +1 -1
  16. package/dist/generator/applicability.d.ts +3 -0
  17. package/dist/generator/applicability.js +48 -0
  18. package/dist/generator/applicability.js.map +1 -0
  19. package/dist/generator/case-builder.d.ts +34 -0
  20. package/dist/generator/case-builder.js +111 -0
  21. package/dist/generator/case-builder.js.map +1 -0
  22. package/dist/generator/corpus-store.d.ts +43 -0
  23. package/dist/generator/corpus-store.js +115 -0
  24. package/dist/generator/corpus-store.js.map +1 -0
  25. package/dist/generator/index.d.ts +12 -0
  26. package/dist/generator/index.js +29 -0
  27. package/dist/generator/index.js.map +1 -0
  28. package/dist/generator/instrument-universe.d.ts +54 -0
  29. package/dist/generator/instrument-universe.js +286 -0
  30. package/dist/generator/instrument-universe.js.map +1 -0
  31. package/dist/generator/layout.d.ts +46 -0
  32. package/dist/generator/layout.js +208 -0
  33. package/dist/generator/layout.js.map +1 -0
  34. package/dist/generator/market-context.d.ts +64 -0
  35. package/dist/generator/market-context.js +119 -0
  36. package/dist/generator/market-context.js.map +1 -0
  37. package/dist/generator/outcome.d.ts +16 -0
  38. package/dist/generator/outcome.js +71 -0
  39. package/dist/generator/outcome.js.map +1 -0
  40. package/dist/generator/realistic-generator.d.ts +53 -0
  41. package/dist/generator/realistic-generator.js +275 -0
  42. package/dist/generator/realistic-generator.js.map +1 -0
  43. package/dist/generator/rng.d.ts +12 -0
  44. package/dist/generator/rng.js +60 -0
  45. package/dist/generator/rng.js.map +1 -0
  46. package/dist/generator/scatter.d.ts +50 -0
  47. package/dist/generator/scatter.js +285 -0
  48. package/dist/generator/scatter.js.map +1 -0
  49. package/dist/generator/scenarios.d.ts +13 -0
  50. package/dist/generator/scenarios.js +139 -0
  51. package/dist/generator/scenarios.js.map +1 -0
  52. package/dist/generator/value-conventions.d.ts +15 -0
  53. package/dist/generator/value-conventions.js +217 -0
  54. package/dist/generator/value-conventions.js.map +1 -0
  55. package/dist/index.d.ts +6 -0
  56. package/dist/index.js +4 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/jsfix-cmd.d.ts +5 -0
  59. package/dist/jsfix-cmd.js +161 -0
  60. package/dist/jsfix-cmd.js.map +1 -1
  61. package/dist/store/fix-msg-ascii-store-resend.js +2 -2
  62. package/dist/store/fix-msg-ascii-store-resend.js.map +1 -1
  63. package/dist/store/fix-msg-store-record.d.ts +1 -1
  64. package/dist/store/fix-msg-store-record.js.map +1 -1
  65. package/dist/store/index.d.ts +1 -0
  66. package/dist/store/index.js +1 -0
  67. package/dist/store/index.js.map +1 -1
  68. package/dist/transport/index.d.ts +7 -0
  69. package/dist/transport/index.js +7 -0
  70. package/dist/transport/index.js.map +1 -1
  71. package/dist/transport/session/index.d.ts +1 -0
  72. package/dist/transport/session/index.js +1 -0
  73. package/dist/transport/session/index.js.map +1 -1
  74. package/docs/generated-cases.md +237 -0
  75. package/docs/instrumentation.md +14 -0
  76. package/docs/scattered-components.md +60 -5
  77. package/jsfix.test_client.txt +62 -62
  78. package/jsfix.test_server.txt +72 -72
  79. package/package.json +5 -2
  80. package/src/buffer/index.ts +6 -0
  81. package/src/collections/index.ts +1 -1
  82. package/src/config/winston-logger.ts +8 -0
  83. package/src/dictionary/contained/index.ts +3 -0
  84. package/src/dictionary/index.ts +5 -0
  85. package/src/generator/applicability.ts +87 -0
  86. package/src/generator/case-builder.ts +167 -0
  87. package/src/generator/corpus-store.ts +166 -0
  88. package/src/generator/index.ts +12 -0
  89. package/src/generator/instrument-universe.ts +347 -0
  90. package/src/generator/layout.ts +279 -0
  91. package/src/generator/market-context.ts +207 -0
  92. package/src/generator/outcome.ts +102 -0
  93. package/src/generator/realistic-generator.ts +404 -0
  94. package/src/generator/rng.ts +92 -0
  95. package/src/generator/scatter.ts +432 -0
  96. package/src/generator/scenarios.ts +167 -0
  97. package/src/generator/value-conventions.ts +295 -0
  98. package/src/index.ts +12 -0
  99. package/src/jsfix-cmd.ts +204 -0
  100. package/src/store/fix-msg-ascii-store-resend.ts +2 -1
  101. package/src/store/fix-msg-store-record.ts +4 -1
  102. package/src/store/index.ts +1 -0
  103. package/src/transport/index.ts +10 -0
  104. package/src/transport/session/index.ts +1 -0
@@ -0,0 +1,166 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+ import { IScatterPlan } from './scatter'
4
+ import { CaseOutcome } from './outcome'
5
+ import { IGeneratedCase } from './case-builder'
6
+
7
+ /**
8
+ * The corpus: edge cases kept as bytes, not as recipes.
9
+ *
10
+ * The generator is how a case is *found*; it is not what a case *is*. A seed reproduces
11
+ * a message only while the generator is unchanged - adjust one probability in the value
12
+ * conventions and the random stream reshuffles, so every seed produces a different
13
+ * message and every recorded expectation quietly stops describing what it was written
14
+ * for. A corpus that has to be regenerated to be read is not a corpus.
15
+ *
16
+ * So an entry is the two encodings, frozen, plus the expectation someone looked at and
17
+ * agreed with. Adding one is: generate until the tool proposes something interesting,
18
+ * read it, commit it. From then on it tests the same bytes forever, and it keeps
19
+ * testing them if the generator is deleted.
20
+ *
21
+ * Layout, one directory per case:
22
+ *
23
+ * data/corpus/<name>/case.json metadata, plan and expected outcome
24
+ * data/corpus/<name>/canonical.txt the contiguous encoding - the oracle
25
+ * data/corpus/<name>/scattered.txt the legal re-ordering under test
26
+ */
27
+
28
+ export interface ICorpusEntry {
29
+ readonly name: string
30
+ /** what this case is here to demonstrate, in a sentence */
31
+ readonly note: string
32
+ /** dictionary the case was built against, e.g. `repo44` */
33
+ readonly dictionary: string
34
+ /** session description used to encode and parse, relative to `data/` */
35
+ readonly session: string
36
+ readonly delimiter: string
37
+ /** what the engine does with it today, agreed by whoever added the case */
38
+ readonly expected: CaseOutcome
39
+ /**
40
+ * when `expected` is not `round-trips`, the leaf paths involved. Recorded so a change
41
+ * in *how* a case fails is a test failure too, not just a change in whether it does.
42
+ */
43
+ readonly missing?: string[]
44
+ readonly added?: string[]
45
+ readonly changed?: string[]
46
+ /** how the case was originally found - provenance, not a way to rebuild it */
47
+ readonly origin?: {
48
+ readonly scenario?: string
49
+ readonly seed?: number
50
+ readonly depth?: number
51
+ readonly fragments?: number
52
+ readonly asOf?: string
53
+ }
54
+ readonly plan: IScatterPlan
55
+ }
56
+
57
+ export interface ILoadedCase {
58
+ readonly entry: ICorpusEntry
59
+ readonly canonical: string
60
+ readonly scattered: string
61
+ readonly directory: string
62
+ }
63
+
64
+ const CASE_FILE = 'case.json'
65
+ const CANONICAL_FILE = 'canonical.txt'
66
+ const SCATTERED_FILE = 'scattered.txt'
67
+
68
+ export class CorpusStore {
69
+ constructor (public readonly root: string) {
70
+ }
71
+
72
+ public names (): string[] {
73
+ if (!fs.existsSync(this.root)) return []
74
+ return fs.readdirSync(this.root)
75
+ .filter(name => fs.existsSync(path.join(this.root, name, CASE_FILE)))
76
+ .sort()
77
+ }
78
+
79
+ public load (name: string): ILoadedCase {
80
+ const directory = path.join(this.root, name)
81
+ const entry = JSON.parse(fs.readFileSync(path.join(directory, CASE_FILE), 'utf8')) as ICorpusEntry
82
+ return {
83
+ entry,
84
+ canonical: CorpusStore.readWire(path.join(directory, CANONICAL_FILE)),
85
+ scattered: CorpusStore.readWire(path.join(directory, SCATTERED_FILE)),
86
+ directory
87
+ }
88
+ }
89
+
90
+ public loadAll (): ILoadedCase[] {
91
+ return this.names().map(name => this.load(name))
92
+ }
93
+
94
+ /**
95
+ * A case file is a fixture a person is expected to read, so it is written with the
96
+ * commentary a person needs: what the case demonstrates and what the engine does with
97
+ * it, above the wire. Everything from the first `8=` is the message.
98
+ */
99
+ public save (entry: ICorpusEntry, canonical: string, scattered: string): string {
100
+ const directory = path.join(this.root, entry.name)
101
+ fs.mkdirSync(directory, { recursive: true })
102
+ fs.writeFileSync(path.join(directory, CASE_FILE), `${JSON.stringify(entry, null, 2)}\n`, 'utf8')
103
+ fs.writeFileSync(
104
+ path.join(directory, CANONICAL_FILE),
105
+ CorpusStore.wireFile(entry, 'canonical - dictionary order, contiguous, the oracle', canonical),
106
+ 'utf8')
107
+ fs.writeFileSync(
108
+ path.join(directory, SCATTERED_FILE),
109
+ CorpusStore.wireFile(entry, 'scattered - the same bytes, legally re-ordered', scattered),
110
+ 'utf8')
111
+ return directory
112
+ }
113
+
114
+ public static fromCase (
115
+ name: string,
116
+ note: string,
117
+ dictionary: string,
118
+ session: string,
119
+ delimiter: string,
120
+ generated: IGeneratedCase,
121
+ expected: CaseOutcome,
122
+ detail: { missing: string[], added: string[], changed: string[] },
123
+ asOf?: Date): ICorpusEntry {
124
+ return {
125
+ name,
126
+ note,
127
+ dictionary,
128
+ session,
129
+ delimiter,
130
+ expected,
131
+ missing: detail.missing.length > 0 ? detail.missing : undefined,
132
+ added: detail.added.length > 0 ? detail.added : undefined,
133
+ changed: detail.changed.length > 0 ? detail.changed : undefined,
134
+ origin: {
135
+ scenario: generated.name,
136
+ seed: generated.seed,
137
+ depth: generated.requestedDepth,
138
+ asOf: asOf ? asOf.toISOString() : undefined
139
+ },
140
+ plan: generated.plan
141
+ }
142
+ }
143
+
144
+ private static wireFile (entry: ICorpusEntry, what: string, wire: string): string {
145
+ const lines: string[] = []
146
+ lines.push(`# ${entry.name} - ${what}`)
147
+ lines.push(`# ${entry.note}`)
148
+ lines.push(`# dictionary ${entry.dictionary}, expected ${entry.expected}`)
149
+ for (const site of entry.plan.sites) {
150
+ lines.push(`# scattered: ${site.component} at structure depth ${site.structureDepth}, ${site.fragments} runs`)
151
+ }
152
+ lines.push(wire)
153
+ return `${lines.join('\n')}\n`
154
+ }
155
+
156
+ /**
157
+ * the message, ignoring the commentary above it
158
+ */
159
+ private static readWire (file: string): string {
160
+ const text = fs.readFileSync(file, 'utf8')
161
+ for (const line of text.split(/\r?\n/)) {
162
+ if (line.startsWith('8=')) return line
163
+ }
164
+ throw new Error(`${file} contains no message - expected a line beginning 8=`)
165
+ }
166
+ }
@@ -0,0 +1,12 @@
1
+ export * from './rng'
2
+ export * from './instrument-universe'
3
+ export * from './market-context'
4
+ export * from './value-conventions'
5
+ export * from './realistic-generator'
6
+ export * from './applicability'
7
+ export * from './scenarios'
8
+ export * from './layout'
9
+ export * from './scatter'
10
+ export * from './outcome'
11
+ export * from './case-builder'
12
+ export * from './corpus-store'
@@ -0,0 +1,347 @@
1
+ import { Rng } from './rng'
2
+
3
+ /**
4
+ * A small book of instruments that read like instruments.
5
+ *
6
+ * The point is not coverage - it is that a generated message can be scanned by eye and
7
+ * recognised. `55=BRN` with `541=20251231` and `44=71.35` is a Brent future at a
8
+ * plausible price; `55=Lorem` with `44=-4534.7` is noise that happens to encode.
9
+ *
10
+ * Prices carry a tick and a lot size so quantities and prices agree with the contract
11
+ * they claim to be, and a spread carries its legs so a multileg message can be built
12
+ * with parent and legs that actually correspond.
13
+ */
14
+
15
+ export enum AssetClass {
16
+ Equity = 'equity',
17
+ Future = 'future',
18
+ Option = 'option',
19
+ Spread = 'spread',
20
+ Fx = 'fx',
21
+ Bond = 'bond'
22
+ }
23
+
24
+ export interface ILegSpec {
25
+ readonly symbol: string
26
+ readonly securityId: string
27
+ readonly securityIdSource: string
28
+ readonly securityType: string
29
+ readonly maturityMonthYear?: string
30
+ readonly side: string
31
+ readonly ratio: number
32
+ readonly refPx: number
33
+ }
34
+
35
+ export interface IInstrumentSpec {
36
+ readonly name: string
37
+ readonly assetClass: AssetClass
38
+ readonly symbol: string
39
+ readonly securityId: string
40
+ /** 1 CUSIP, 2 SEDOL, 4 ISIN, 8 exchange symbol, 9 CTA */
41
+ readonly securityIdSource: string
42
+ readonly securityExchange: string
43
+ readonly securityType: string
44
+ readonly cfiCode: string
45
+ readonly currency: string
46
+ readonly refPx: number
47
+ readonly tick: number
48
+ readonly lotSize: number
49
+ readonly contractMultiplier?: number
50
+ readonly maturityMonthYear?: string
51
+ readonly maturityDate?: string
52
+ readonly strikePrice?: number
53
+ readonly putOrCall?: number
54
+ readonly product?: number
55
+ readonly legs?: readonly ILegSpec[]
56
+ }
57
+
58
+ const equities: readonly IInstrumentSpec[] = [
59
+ {
60
+ name: 'Apple Inc',
61
+ assetClass: AssetClass.Equity,
62
+ symbol: 'AAPL',
63
+ securityId: 'US0378331005',
64
+ securityIdSource: '4',
65
+ securityExchange: 'XNAS',
66
+ securityType: 'CS',
67
+ cfiCode: 'ESVUFR',
68
+ currency: 'USD',
69
+ refPx: 214.5,
70
+ tick: 0.01,
71
+ lotSize: 100,
72
+ product: 5
73
+ },
74
+ {
75
+ name: 'Vodafone Group',
76
+ assetClass: AssetClass.Equity,
77
+ symbol: 'VOD.L',
78
+ securityId: 'GB00BH4HKS39',
79
+ securityIdSource: '4',
80
+ securityExchange: 'XLON',
81
+ securityType: 'CS',
82
+ cfiCode: 'ESVUFR',
83
+ currency: 'GBP',
84
+ refPx: 0.762,
85
+ tick: 0.0001,
86
+ lotSize: 1000,
87
+ product: 5
88
+ },
89
+ {
90
+ name: 'SAP SE',
91
+ assetClass: AssetClass.Equity,
92
+ symbol: 'SAP',
93
+ securityId: 'DE0007164600',
94
+ securityIdSource: '4',
95
+ securityExchange: 'XETR',
96
+ securityType: 'CS',
97
+ cfiCode: 'ESVUFR',
98
+ currency: 'EUR',
99
+ refPx: 196.3,
100
+ tick: 0.01,
101
+ lotSize: 100,
102
+ product: 5
103
+ }
104
+ ]
105
+
106
+ const futures: readonly IInstrumentSpec[] = [
107
+ {
108
+ name: 'ICE Brent Crude Jan26',
109
+ assetClass: AssetClass.Future,
110
+ symbol: 'BRN',
111
+ securityId: 'BRN FMF0026!',
112
+ securityIdSource: '8',
113
+ securityExchange: 'IFEU',
114
+ securityType: 'FUT',
115
+ cfiCode: 'FCEPSX',
116
+ currency: 'USD',
117
+ refPx: 71.35,
118
+ tick: 0.01,
119
+ lotSize: 1,
120
+ contractMultiplier: 1000,
121
+ maturityMonthYear: '202601',
122
+ maturityDate: '20251130',
123
+ product: 2
124
+ },
125
+ {
126
+ name: 'ICE Low Sulphur Gasoil Feb26',
127
+ assetClass: AssetClass.Future,
128
+ symbol: 'GAS',
129
+ securityId: 'GAS FMG0026!',
130
+ securityIdSource: '8',
131
+ securityExchange: 'IFEU',
132
+ securityType: 'FUT',
133
+ cfiCode: 'FCEPSX',
134
+ currency: 'USD',
135
+ refPx: 668.25,
136
+ tick: 0.25,
137
+ lotSize: 1,
138
+ contractMultiplier: 100,
139
+ maturityMonthYear: '202602',
140
+ maturityDate: '20260112',
141
+ product: 2
142
+ },
143
+ {
144
+ name: 'CME E-mini S and P 500 Dec25',
145
+ assetClass: AssetClass.Future,
146
+ symbol: 'ES',
147
+ securityId: 'ESZ5',
148
+ securityIdSource: '8',
149
+ securityExchange: 'XCME',
150
+ securityType: 'FUT',
151
+ cfiCode: 'FFICSX',
152
+ currency: 'USD',
153
+ refPx: 6042.25,
154
+ tick: 0.25,
155
+ lotSize: 1,
156
+ contractMultiplier: 50,
157
+ maturityMonthYear: '202512',
158
+ maturityDate: '20251219',
159
+ product: 5
160
+ }
161
+ ]
162
+
163
+ const options: readonly IInstrumentSpec[] = [
164
+ {
165
+ name: 'CME E-mini Dec25 6000 Call',
166
+ assetClass: AssetClass.Option,
167
+ symbol: 'ES',
168
+ securityId: 'ESZ5 C6000',
169
+ securityIdSource: '8',
170
+ securityExchange: 'XCME',
171
+ securityType: 'OPT',
172
+ cfiCode: 'OCEFCS',
173
+ currency: 'USD',
174
+ refPx: 118.75,
175
+ tick: 0.25,
176
+ lotSize: 1,
177
+ contractMultiplier: 50,
178
+ maturityMonthYear: '202512',
179
+ maturityDate: '20251219',
180
+ strikePrice: 6000,
181
+ putOrCall: 1,
182
+ product: 5
183
+ }
184
+ ]
185
+
186
+ const fx: readonly IInstrumentSpec[] = [
187
+ {
188
+ name: 'EUR/USD spot',
189
+ assetClass: AssetClass.Fx,
190
+ symbol: 'EUR/USD',
191
+ securityId: 'EURUSD',
192
+ securityIdSource: '8',
193
+ securityExchange: 'XOFF',
194
+ securityType: 'FXSPOT',
195
+ cfiCode: 'MRCXXX',
196
+ currency: 'EUR',
197
+ refPx: 1.0842,
198
+ tick: 0.00005,
199
+ lotSize: 1000000,
200
+ product: 4
201
+ }
202
+ ]
203
+
204
+ const bonds: readonly IInstrumentSpec[] = [
205
+ {
206
+ name: 'US Treasury 4.25 Nov 2034',
207
+ assetClass: AssetClass.Bond,
208
+ symbol: 'T 4.25 11/34',
209
+ securityId: 'US91282CLW12',
210
+ securityIdSource: '4',
211
+ securityExchange: 'XOFF',
212
+ securityType: 'TBOND',
213
+ cfiCode: 'DBFUFR',
214
+ currency: 'USD',
215
+ refPx: 99.421875,
216
+ tick: 0.015625,
217
+ lotSize: 1000,
218
+ maturityDate: '20341115',
219
+ product: 6
220
+ }
221
+ ]
222
+
223
+ /**
224
+ * The shape the whole scattered component exercise is really about. ICE publishes
225
+ * calendar and crack spreads as single tradeable instruments whose fills report a parent
226
+ * and one leg per contract, and a trade capture for one of these is where deep nesting
227
+ * shows up in real life.
228
+ */
229
+ const spreads: readonly IInstrumentSpec[] = [
230
+ {
231
+ name: 'ICE Brent Jan26/Feb26 calendar spread',
232
+ assetClass: AssetClass.Spread,
233
+ symbol: 'BRN',
234
+ securityId: 'BRN JAN26-FEB26',
235
+ securityIdSource: '8',
236
+ securityExchange: 'IFEU',
237
+ securityType: 'MLEG',
238
+ cfiCode: 'FMEPSX',
239
+ currency: 'USD',
240
+ refPx: 0.34,
241
+ tick: 0.01,
242
+ lotSize: 1,
243
+ contractMultiplier: 1000,
244
+ maturityMonthYear: '202601',
245
+ product: 2,
246
+ legs: [
247
+ {
248
+ symbol: 'BRN',
249
+ securityId: 'BRN FMF0026!',
250
+ securityIdSource: '8',
251
+ securityType: 'FUT',
252
+ maturityMonthYear: '202601',
253
+ side: '1',
254
+ ratio: 1,
255
+ refPx: 71.35
256
+ },
257
+ {
258
+ symbol: 'BRN',
259
+ securityId: 'BRN FMG0026!',
260
+ securityIdSource: '8',
261
+ securityType: 'FUT',
262
+ maturityMonthYear: '202602',
263
+ side: '2',
264
+ ratio: 1,
265
+ refPx: 71.01
266
+ }
267
+ ]
268
+ },
269
+ {
270
+ name: 'ICE Brent/Gasoil crack spread',
271
+ assetClass: AssetClass.Spread,
272
+ symbol: 'BRNGAS',
273
+ securityId: 'BRN-GAS CRACK',
274
+ securityIdSource: '8',
275
+ securityExchange: 'IFEU',
276
+ securityType: 'MLEG',
277
+ cfiCode: 'FMEPSX',
278
+ currency: 'USD',
279
+ refPx: 12.68,
280
+ tick: 0.01,
281
+ lotSize: 1,
282
+ contractMultiplier: 1000,
283
+ maturityMonthYear: '202602',
284
+ product: 2,
285
+ legs: [
286
+ {
287
+ symbol: 'GAS',
288
+ securityId: 'GAS FMG0026!',
289
+ securityIdSource: '8',
290
+ securityType: 'FUT',
291
+ maturityMonthYear: '202602',
292
+ side: '1',
293
+ ratio: 1,
294
+ refPx: 668.25
295
+ },
296
+ {
297
+ symbol: 'BRN',
298
+ securityId: 'BRN FMG0026!',
299
+ securityIdSource: '8',
300
+ securityType: 'FUT',
301
+ maturityMonthYear: '202602',
302
+ side: '2',
303
+ ratio: 1,
304
+ refPx: 71.2
305
+ }
306
+ ]
307
+ }
308
+ ]
309
+
310
+ function byClass (assetClass: AssetClass): readonly IInstrumentSpec[] {
311
+ switch (assetClass) {
312
+ case AssetClass.Equity: return equities
313
+ case AssetClass.Future: return futures
314
+ case AssetClass.Option: return options
315
+ case AssetClass.Fx: return fx
316
+ case AssetClass.Bond: return bonds
317
+ case AssetClass.Spread: return spreads
318
+ default: return equities
319
+ }
320
+ }
321
+
322
+ const all: readonly IInstrumentSpec[] = [
323
+ ...equities, ...futures, ...options, ...fx, ...bonds, ...spreads
324
+ ]
325
+
326
+ export const InstrumentUniverse = {
327
+ equities,
328
+ futures,
329
+ options,
330
+ fx,
331
+ bonds,
332
+ spreads,
333
+ all,
334
+ byClass,
335
+
336
+ byName (name: string): IInstrumentSpec | undefined {
337
+ const lower = name.toLowerCase()
338
+ return all.find(i =>
339
+ i.symbol.toLowerCase() === lower ||
340
+ i.securityId.toLowerCase() === lower ||
341
+ i.name.toLowerCase() === lower)
342
+ },
343
+
344
+ choose (rng: Rng, assetClass?: AssetClass): IInstrumentSpec {
345
+ return rng.pick(assetClass ? byClass(assetClass) : all)
346
+ }
347
+ }