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.
- package/.gitattributes +4 -0
- package/dist/buffer/index.d.ts +6 -0
- package/dist/buffer/index.js +6 -0
- package/dist/buffer/index.js.map +1 -1
- package/dist/collections/index.d.ts +1 -0
- package/dist/collections/index.js +16 -0
- package/dist/collections/index.js.map +1 -1
- package/dist/config/winston-logger.js +2 -0
- package/dist/config/winston-logger.js.map +1 -1
- package/dist/dictionary/contained/index.d.ts +3 -0
- package/dist/dictionary/contained/index.js +3 -0
- package/dist/dictionary/contained/index.js.map +1 -1
- package/dist/dictionary/index.d.ts +5 -0
- package/dist/dictionary/index.js +5 -0
- package/dist/dictionary/index.js.map +1 -1
- package/dist/generator/applicability.d.ts +3 -0
- package/dist/generator/applicability.js +48 -0
- package/dist/generator/applicability.js.map +1 -0
- package/dist/generator/case-builder.d.ts +34 -0
- package/dist/generator/case-builder.js +111 -0
- package/dist/generator/case-builder.js.map +1 -0
- package/dist/generator/corpus-store.d.ts +43 -0
- package/dist/generator/corpus-store.js +115 -0
- package/dist/generator/corpus-store.js.map +1 -0
- package/dist/generator/index.d.ts +12 -0
- package/dist/generator/index.js +29 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/instrument-universe.d.ts +54 -0
- package/dist/generator/instrument-universe.js +286 -0
- package/dist/generator/instrument-universe.js.map +1 -0
- package/dist/generator/layout.d.ts +46 -0
- package/dist/generator/layout.js +208 -0
- package/dist/generator/layout.js.map +1 -0
- package/dist/generator/market-context.d.ts +64 -0
- package/dist/generator/market-context.js +119 -0
- package/dist/generator/market-context.js.map +1 -0
- package/dist/generator/outcome.d.ts +16 -0
- package/dist/generator/outcome.js +71 -0
- package/dist/generator/outcome.js.map +1 -0
- package/dist/generator/realistic-generator.d.ts +53 -0
- package/dist/generator/realistic-generator.js +275 -0
- package/dist/generator/realistic-generator.js.map +1 -0
- package/dist/generator/rng.d.ts +12 -0
- package/dist/generator/rng.js +60 -0
- package/dist/generator/rng.js.map +1 -0
- package/dist/generator/scatter.d.ts +50 -0
- package/dist/generator/scatter.js +285 -0
- package/dist/generator/scatter.js.map +1 -0
- package/dist/generator/scenarios.d.ts +13 -0
- package/dist/generator/scenarios.js +139 -0
- package/dist/generator/scenarios.js.map +1 -0
- package/dist/generator/value-conventions.d.ts +15 -0
- package/dist/generator/value-conventions.js +217 -0
- package/dist/generator/value-conventions.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/jsfix-cmd.d.ts +5 -0
- package/dist/jsfix-cmd.js +161 -0
- package/dist/jsfix-cmd.js.map +1 -1
- package/dist/store/fix-msg-ascii-store-resend.js +2 -2
- package/dist/store/fix-msg-ascii-store-resend.js.map +1 -1
- package/dist/store/fix-msg-store-record.d.ts +1 -1
- package/dist/store/fix-msg-store-record.js.map +1 -1
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.js +1 -0
- package/dist/store/index.js.map +1 -1
- package/dist/transport/index.d.ts +7 -0
- package/dist/transport/index.js +7 -0
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/session/index.d.ts +1 -0
- package/dist/transport/session/index.js +1 -0
- package/dist/transport/session/index.js.map +1 -1
- package/docs/generated-cases.md +237 -0
- package/docs/instrumentation.md +14 -0
- package/docs/scattered-components.md +60 -5
- package/jsfix.test_client.txt +62 -62
- package/jsfix.test_server.txt +72 -72
- package/package.json +5 -2
- package/src/buffer/index.ts +6 -0
- package/src/collections/index.ts +1 -1
- package/src/config/winston-logger.ts +8 -0
- package/src/dictionary/contained/index.ts +3 -0
- package/src/dictionary/index.ts +5 -0
- package/src/generator/applicability.ts +87 -0
- package/src/generator/case-builder.ts +167 -0
- package/src/generator/corpus-store.ts +166 -0
- package/src/generator/index.ts +12 -0
- package/src/generator/instrument-universe.ts +347 -0
- package/src/generator/layout.ts +279 -0
- package/src/generator/market-context.ts +207 -0
- package/src/generator/outcome.ts +102 -0
- package/src/generator/realistic-generator.ts +404 -0
- package/src/generator/rng.ts +92 -0
- package/src/generator/scatter.ts +432 -0
- package/src/generator/scenarios.ts +167 -0
- package/src/generator/value-conventions.ts +295 -0
- package/src/index.ts +12 -0
- package/src/jsfix-cmd.ts +204 -0
- package/src/store/fix-msg-ascii-store-resend.ts +2 -1
- package/src/store/fix-msg-store-record.ts +4 -1
- package/src/store/index.ts +1 -0
- package/src/transport/index.ts +10 -0
- 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
|
+
}
|