jspurefix 5.6.2 → 5.8.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/DEMO_PORT_PLAN.md +1 -1
- package/README.md +263 -585
- package/dist/buffer/ascii/ascii-parser.d.ts +2 -1
- package/dist/buffer/ascii/ascii-parser.js +5 -3
- package/dist/buffer/ascii/ascii-parser.js.map +1 -1
- package/dist/buffer/tag/tags.d.ts +2 -1
- package/dist/buffer/tag/tags.js +8 -4
- package/dist/buffer/tag/tags.js.map +1 -1
- package/dist/transport/ascii/ascii-session.d.ts +1 -0
- package/dist/transport/ascii/ascii-session.js +51 -10
- package/dist/transport/ascii/ascii-session.js.map +1 -1
- package/dist/transport/http/http-acceptor.js +2 -2
- package/dist/transport/http/http-acceptor.js.map +1 -1
- package/dist/transport/session/fix-session.d.ts +1 -0
- package/dist/transport/session/fix-session.js +7 -0
- package/dist/transport/session/fix-session.js.map +1 -1
- package/jsfix.test_client.txt +75 -71
- package/jsfix.test_server.txt +72 -68
- package/package.json +10 -12
- package/src/buffer/ascii/ascii-parser.ts +3 -2
- package/src/buffer/tag/tags.ts +8 -4
- package/src/transport/ascii/ascii-session.ts +52 -10
- package/src/transport/http/http-acceptor.ts +2 -2
- package/src/transport/session/fix-session.ts +14 -0
- package/src/util/unzip.js +54 -184
package/README.md
CHANGED
|
@@ -3,66 +3,67 @@
|
|
|
3
3
|
[](https://github.com/TimelordUK/jspurefix/actions/workflows/ci.yml)
|
|
4
4
|
[](https://standardjs.com)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
6
|
+
A fast, fully native TypeScript [FIX protocol](https://www.fixtrading.org/) engine for Node.js. Built around a data-dictionary driven parser, with first-class support for sessions over TCP/TLS, persistent message stores, sequence recovery, FIXML over HTTP, and generated typed interfaces for any FIX dialect.
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
- [Features](#features)
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Examples](#examples)
|
|
13
|
+
- [Quickstart](#quickstart)
|
|
14
|
+
- [Session Configuration](#session-configuration)
|
|
15
|
+
- [TLS](#tls)
|
|
16
|
+
- [Body length padding](#body-length-padding)
|
|
17
|
+
- [Persistence & Recovery](#persistence--recovery)
|
|
18
|
+
- [Choosing a store](#choosing-a-store)
|
|
19
|
+
- [`ResetSeqNumFlag` semantics](#resetseqnumflag-semantics)
|
|
20
|
+
- [Resending messages](#resending-messages)
|
|
21
|
+
- [Working with Messages](#working-with-messages)
|
|
22
|
+
- [FIXML over HTTP](#fixml-over-http)
|
|
23
|
+
- [Data Dictionaries](#data-dictionaries)
|
|
24
|
+
- [`jsfix` CLI — log parsing & stats](#jsfix-cli--log-parsing--stats)
|
|
25
|
+
- [Performance](#performance)
|
|
26
|
+
- [Developing on jspurefix](#developing-on-jspurefix)
|
|
27
|
+
- [C# Port](#c-port)
|
|
28
|
+
- [License](#license)
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- 100% native TypeScript — no native bindings, runs anywhere Node.js does
|
|
33
|
+
- ASCII (tag=value) and FIXML message encodings
|
|
34
|
+
- Repeating groups, components, nested structures and raw data fields
|
|
35
|
+
- Dictionary-driven: load QuickFIX XML or FIX repository definitions, compile typed interfaces
|
|
36
|
+
- Full session lifecycle: logon, heartbeats, test requests, resend requests, logout
|
|
37
|
+
- TLS-encrypted sessions over TCP
|
|
38
|
+
- Pluggable persistent message store (in-memory or file) with sequence recovery
|
|
39
|
+
- HTTP initiator/acceptor for FIXML
|
|
40
|
+
- Command-line tool for parsing FIX logs into tokens, JSON, or structure dumps
|
|
41
|
+
- Sample applications: trade capture, market data, FIXML OMS, recovering skeleton
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
36
44
|
|
|
37
45
|
```shell
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
npm install jspurefix
|
|
47
|
+
cd node_modules/jspurefix && npm run unzip-repo
|
|
40
48
|
```
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
`unzip-repo` extracts the bundled FIX dictionaries. The `postinstall` hook will normally do this for you, but the command is exposed in case you need to re-run it.
|
|
43
51
|
|
|
44
|
-
|
|
45
|
-
https://github.com/TimelordUK/jspf-demo
|
|
46
|
-
```
|
|
52
|
+
A standalone demo project lives at [TimelordUK/jspf-demo](https://github.com/TimelordUK/jspf-demo) — the fastest way to see a working initiator/acceptor.
|
|
47
53
|
|
|
48
|
-
|
|
54
|
+
## Examples
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
| description | link |
|
|
57
|
+
|---|---|
|
|
58
|
+
| a good start point showing FIX 4 and 5 market data request/snapshot and custom generated types. | [jspf-md-demo](https://github.com/TimelordUK/jspf-md-demo) |
|
|
59
|
+
| simple example with a custom dictionary that targets cserver | [jspf-cserver](https://github.com/TimelordUK/jspf-cserver) |
|
|
60
|
+
| example trade capture applicaton | [jspf-demo](https://github.com/TimelordUK/jspf-demo) |
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
npm run generate
|
|
54
|
-
```
|
|
62
|
+
## Quickstart
|
|
55
63
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```shell
|
|
59
|
-
https://github.com/TimelordUK/jspf-md-demo
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
import types for usage with a client
|
|
64
|
+
Import the session types you need from `jspurefix` and the typed FIX messages for your dialect:
|
|
63
65
|
|
|
64
66
|
```typescript
|
|
65
|
-
|
|
66
67
|
import {
|
|
67
68
|
AsciiSession,
|
|
68
69
|
MsgView,
|
|
@@ -70,102 +71,94 @@ import {
|
|
|
70
71
|
IJsFixLogger,
|
|
71
72
|
Dictionary,
|
|
72
73
|
MsgType,
|
|
73
|
-
IJsFixConfig,
|
|
74
74
|
initiator,
|
|
75
75
|
acceptor,
|
|
76
76
|
makeConfig
|
|
77
77
|
} from 'jspurefix'
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
import {
|
|
80
80
|
ITradeCaptureReport,
|
|
81
81
|
ITradeCaptureReportRequest,
|
|
82
82
|
ITradeCaptureReportRequestAck
|
|
83
83
|
} from 'jspurefix/dist/types/FIX4.4/repo'
|
|
84
|
-
|
|
85
84
|
```
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
### getting started
|
|
86
|
+
A minimal session subclasses `AsciiSession` and implements two callbacks: `onReady` (connection up, logon confirmed) and `onApplicationMsg` (a non-session message arrived).
|
|
90
87
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
```shell
|
|
102
|
-
npm install
|
|
103
|
-
npm run unzip-repo
|
|
104
|
-
./node_modules/.bin/tsc --version
|
|
105
|
-
./node_modules/.bin/tsc
|
|
106
|
-
npm run tcp-tc
|
|
107
|
-
```
|
|
88
|
+
```typescript
|
|
89
|
+
class TradeCaptureClient extends AsciiSession {
|
|
90
|
+
constructor (public readonly config: IJsFixConfig) {
|
|
91
|
+
super(config)
|
|
92
|
+
this.logReceivedMsgs = true
|
|
93
|
+
this.fixLog = config.logFactory.plain(`jsfix.${config.description.application.name}.txt`)
|
|
94
|
+
this.logger = config.logFactory.logger(`${this.me}:TradeCaptureClient`)
|
|
95
|
+
}
|
|
108
96
|
|
|
109
|
-
|
|
97
|
+
protected onReady (view: MsgView): void {
|
|
98
|
+
const tcr: ITradeCaptureReportRequest =
|
|
99
|
+
TradeFactory.tradeCaptureReportRequest('all-trades', new Date())
|
|
100
|
+
this.send(MsgType.TradeCaptureReportRequest, tcr)
|
|
101
|
+
}
|
|
110
102
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
103
|
+
protected onApplicationMsg (msgType: string, view: MsgView): void {
|
|
104
|
+
switch (msgType) {
|
|
105
|
+
case MsgType.TradeCaptureReport: {
|
|
106
|
+
const tc: ITradeCaptureReport = view.toObject()
|
|
107
|
+
this.logger.info(`tc ${tc.TradeReportID} ${tc.Instrument.Symbol} ${tc.LastQty} @ ${tc.LastPx}`)
|
|
108
|
+
break
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
115
113
|
```
|
|
116
114
|
|
|
117
|
-
|
|
115
|
+
The full sample lives at `src/sample/tcp/trade-capture/` and runs both sides over a local socket:
|
|
118
116
|
|
|
119
117
|
```shell
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
npm run unzip-repo
|
|
124
|
-
node_modules/.bin/tsc
|
|
125
|
-
npm run tcp-tc
|
|
118
|
+
npm run tcp-tc # full trade-capture client/server demo
|
|
119
|
+
npm run tcp-sk # bare skeleton: connect, log on, idle
|
|
120
|
+
npm run http-oms # FIXML order/exec-report over HTTP
|
|
126
121
|
```
|
|
127
122
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
The code provided in src/sample/tcp/trade_capture is a good place to start in building an application with this library. In this case both client and server are run together communicating over a socket. In reality a client is more likely connecting to an external acceptor such as CME, ICE.
|
|
123
|
+
Each demo terminates after about a minute, or with Ctrl-C.
|
|
131
124
|
|
|
132
|
-
|
|
125
|
+
## Session Configuration
|
|
133
126
|
|
|
134
|
-
|
|
127
|
+
A session is described by a JSON file (or any object matching `ISessionDescription`). Example: `data/session/test-initiator.json`.
|
|
135
128
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"application": {
|
|
132
|
+
"type": "initiator",
|
|
133
|
+
"name": "test_client",
|
|
134
|
+
"reconnectSeconds": 10,
|
|
135
|
+
"tcp": { "host": "localhost", "port": 2344 },
|
|
136
|
+
"protocol": "ascii",
|
|
137
|
+
"dictionary": "repo44"
|
|
138
|
+
},
|
|
139
|
+
"Username": "js-client",
|
|
140
|
+
"Password": "pwd-client",
|
|
141
|
+
"EncryptMethod": 0,
|
|
142
|
+
"ResetSeqNumFlag": true,
|
|
143
|
+
"HeartBtInt": 30,
|
|
144
|
+
"SenderCompId": "init-comp",
|
|
145
|
+
"TargetCompID": "accept-comp",
|
|
146
|
+
"TargetSubID": "fix",
|
|
147
|
+
"BeginString": "FIX.4.4"
|
|
148
|
+
}
|
|
153
149
|
```
|
|
154
150
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
see example data\session\test-initiator-tls.json
|
|
151
|
+
### TLS
|
|
158
152
|
|
|
159
|
-
|
|
153
|
+
Add a `tls` block under `application.tcp`. The `ca` field is only needed for self-signed certificates; commercial vendors will supply this for you. `script/getKey.ps1` will generate a self-signed CA + client/server pair (requires `openssl` on the path).
|
|
160
154
|
|
|
161
155
|
```json
|
|
162
156
|
{
|
|
163
157
|
"application": {
|
|
164
|
-
"reconnectSeconds": 10,
|
|
165
158
|
"type": "initiator",
|
|
166
159
|
"name": "test_client",
|
|
167
160
|
"tcp": {
|
|
168
|
-
"host"
|
|
161
|
+
"host": "localhost",
|
|
169
162
|
"port": 2344,
|
|
170
163
|
"tls": {
|
|
171
164
|
"timeout": 10000,
|
|
@@ -173,45 +166,66 @@ to run the provided example tls trade capture a script such as script\getKey.ps1
|
|
|
173
166
|
"enableTrace": true,
|
|
174
167
|
"key": "data/session/certs/client/client.key",
|
|
175
168
|
"cert": "data/session/certs/client/client.crt",
|
|
176
|
-
"ca": [
|
|
177
|
-
"data/session/certs/ca/ca.crt"
|
|
178
|
-
]
|
|
169
|
+
"ca": ["data/session/certs/ca/ca.crt"]
|
|
179
170
|
}
|
|
180
171
|
},
|
|
181
172
|
"protocol": "ascii",
|
|
182
173
|
"dictionary": "repo44"
|
|
183
174
|
},
|
|
184
|
-
"
|
|
185
|
-
"Password": "pwd-tls-client",
|
|
186
|
-
"EncryptMethod": 0,
|
|
187
|
-
"ResetSeqNumFlag": true,
|
|
188
|
-
"LastSentSeqNum": 10,
|
|
189
|
-
"LastReceivedSeqNum": 11,
|
|
190
|
-
"HeartBtInt": 30,
|
|
191
|
-
"SenderCompId": "init-tls-comp",
|
|
192
|
-
"TargetCompID": "accept-tls-comp",
|
|
193
|
-
"TargetSubID": "fix",
|
|
194
|
-
"BeginString": "FIX4.4",
|
|
195
|
-
"BodyLengthChars": 6
|
|
175
|
+
"BeginString": "FIX4.4"
|
|
196
176
|
}
|
|
197
177
|
```
|
|
198
178
|
|
|
199
|
-
|
|
179
|
+
See `data/session/test-initiator-tls.json` for the complete file.
|
|
200
180
|
|
|
201
|
-
|
|
181
|
+
### Body length padding
|
|
202
182
|
|
|
203
|
-
|
|
183
|
+
`BodyLengthChars` controls how the tag-9 body-length field is zero-padded. Defaults to `7`; set to a smaller value (e.g. `6`) when interoperating with a counterparty that requires it.
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{ "BodyLengthChars": 6 }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Persistence & Recovery
|
|
190
|
+
|
|
191
|
+
By default, every session uses an in-memory message store — sequence numbers and any stored messages are lost when the process exits. For production use you'll typically want either persisted sequences or a full file-backed store with replay.
|
|
192
|
+
|
|
193
|
+
### Choosing a store
|
|
194
|
+
|
|
195
|
+
Add a `store` block to the session description. Omit it to keep the in-memory default.
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"store": { "type": "memory" }
|
|
200
|
+
}
|
|
201
|
+
```
|
|
204
202
|
|
|
205
203
|
```json
|
|
206
204
|
{
|
|
207
|
-
"
|
|
205
|
+
"store": {
|
|
206
|
+
"type": "file",
|
|
207
|
+
"directory": "/var/fix/sessions"
|
|
208
|
+
}
|
|
208
209
|
}
|
|
209
210
|
```
|
|
210
211
|
|
|
211
|
-
|
|
212
|
+
The file store writes QuickFIX-compatible files into the directory:
|
|
213
|
+
|
|
214
|
+
| File | Contents |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| `<session>.seqnums` | Current sender / target sequence numbers |
|
|
217
|
+
| `<session>.session` | Session creation timestamp |
|
|
218
|
+
| `<session>.header` | Index lines `seqnum,offset,length` into `.body` |
|
|
219
|
+
| `<session>.body` | Concatenated raw FIX messages for resend |
|
|
220
|
+
|
|
221
|
+
Session names are derived from `BeginString-SenderCompID-TargetCompID`.
|
|
212
222
|
|
|
213
|
-
|
|
214
|
-
|
|
223
|
+
You can also pass a custom factory programmatically via `IJsFixConfig.sessionStoreFactory` — useful for testing or for plugging in an alternative backend (Redis, S3, etc.).
|
|
224
|
+
|
|
225
|
+
### `ResetSeqNumFlag` semantics
|
|
226
|
+
|
|
227
|
+
- `"ResetSeqNumFlag": true` — every logon resets sender/target sequences back to 1. The engine clears the persisted store before sending the Logon, so the message correctly carries `MsgSeqNum=1` even after a reconnect with recovered state. (See [issue #140](https://github.com/TimelordUK/jspurefix/issues/140).)
|
|
228
|
+
- `"ResetSeqNumFlag": false` — sequences are preserved across logons. To seed initial sequences for a brand-new session (no persisted store), set `LastSentSeqNum` and `LastReceivedSeqNum`:
|
|
215
229
|
|
|
216
230
|
```json
|
|
217
231
|
{
|
|
@@ -221,21 +235,20 @@ then the previously used sequence numbers can be set as follows:
|
|
|
221
235
|
}
|
|
222
236
|
```
|
|
223
237
|
|
|
224
|
-
|
|
238
|
+
With a file store configured, `LastSentSeqNum` / `LastReceivedSeqNum` are only consulted the first time a session is started; subsequent runs read from the persisted `.seqnums` file.
|
|
225
239
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
240
|
+
### Resending messages
|
|
241
|
+
|
|
242
|
+
When a counterparty asks for missed messages, the engine needs access to the originals. The file store keeps every encoded message and the bundled `AsciiSession.onResendRequest()` will replay from it automatically. If you're using the in-memory store and want resend support, override `onResendRequest()` with your own retrieval logic and set the duplicate flag on each replayed message:
|
|
229
243
|
|
|
230
244
|
```typescript
|
|
231
245
|
{
|
|
232
|
-
...messageBodyData,
|
|
233
|
-
|
|
246
|
+
...messageBodyData,
|
|
247
|
+
StandardHeader: { PossDupFlag: true, MsgSeqNum: sequenceNumber }
|
|
234
248
|
}
|
|
235
|
-
|
|
236
249
|
```
|
|
237
250
|
|
|
238
|
-
|
|
251
|
+
Example payload:
|
|
239
252
|
|
|
240
253
|
```json
|
|
241
254
|
{
|
|
@@ -252,229 +265,75 @@ Additionally, make sure to include the original message sequence and the duplica
|
|
|
252
265
|
}
|
|
253
266
|
```
|
|
254
267
|
|
|
255
|
-
##
|
|
256
|
-
|
|
257
|
-
there is a comprehensive suite of tests which can be run
|
|
258
|
-
|
|
259
|
-
```shell
|
|
260
|
-
npm t
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
```bash
|
|
264
|
-
PASS src/test/elastic-buffer.test.ts
|
|
265
|
-
RUNS src/test/session.test.ts
|
|
266
|
-
PASS src/test/encode-proxy.test.tsst.ts
|
|
267
|
-
PASS src/test/execution-report.test.ts
|
|
268
|
-
PASS src/test/view-decode.test.ts
|
|
269
|
-
PASS src/test/ascii-encoder.test.ts
|
|
270
|
-
PASS src/test/ascii-parser.test.ts
|
|
271
|
-
PASS src/test/includes.test.ts
|
|
272
|
-
PASS src/test/fixml-alloc-parse.test.ts (9.433s)
|
|
273
|
-
PASS src/test/repo-full-fixml-msg.test.ts (6.025s)
|
|
274
|
-
PASS src/test/fixml-mkt-data-settle-parse.test.ts (6.021s)
|
|
275
|
-
PASS src/test/qf-full-msg.test.ts
|
|
276
|
-
PASS src/test/logon.test.ts
|
|
277
|
-
PASS src/test/fixml-mkt-data-fut-parse.test.ts (7.761s)
|
|
278
|
-
PASS src/test/time-formatter.test.ts
|
|
279
|
-
PASS src/test/ascii-segment.test.ts
|
|
280
|
-
PASS src/test/session-state.test.ts
|
|
281
|
-
PASS src/test/fixml-tc-bi-lateral-parse.test.ts (7.534s)
|
|
282
|
-
PASS src/test/ascii-tag-pos.test.ts
|
|
283
|
-
PASS src/test/fix-log-replay.test.ts
|
|
284
|
-
PASS src/test/repo-full-ascii-msg.test.ts
|
|
285
|
-
PASS src/test/session.test.ts (52.637s)
|
|
286
|
-
|
|
287
|
-
Test Suites: 21 passed, 21 total
|
|
288
|
-
Tests: 204 passed, 204 total
|
|
289
|
-
Snapshots: 0 total
|
|
290
|
-
Time: 54.606s, estimated 65s
|
|
291
|
-
Ran all test suites.
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
## Dictionary Definitions
|
|
295
|
-
|
|
296
|
-
base definitions on existing template e.g. quickfix format FIX44.xml
|
|
297
|
-
create an alias in data/dictionary.json
|
|
298
|
-
compile interfaces
|
|
299
|
-
|
|
300
|
-
```shell
|
|
301
|
-
npm run cmd -- --dict=repo42 --compile
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
use the alias in a session file e.g. data/session/test-httpInitiator.json
|
|
305
|
-
|
|
306
|
-
## sample trade-capture-client.ts
|
|
307
|
-
|
|
308
|
-
the method onApplicationMsg is called when a message is received. In this case the client has inherited from AsciiSession which carries out the session management.
|
|
309
|
-
|
|
310
|
-
```typescript
|
|
311
|
-
constructor (public readonly config: IJsFixConfig) {
|
|
312
|
-
super(config)
|
|
313
|
-
this.logReceivedMsgs = true
|
|
314
|
-
this.reports = new Dictionary<ITradeCaptureReport>()
|
|
315
|
-
this.fixLog = config.logFactory.plain(`jsfix.${config.description.application.name}.txt`)
|
|
316
|
-
this.logger = config.logFactory.logger(`${this.me}:TradeCaptureClient`)
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
protected onApplicationMsg (msgType: string, view: MsgView): void {
|
|
320
|
-
this.logger.info(`${view.toJson()}`)
|
|
321
|
-
switch (msgType) {
|
|
322
|
-
case MsgType.TradeCaptureReport: {
|
|
323
|
-
// create an object and cast to the interface
|
|
324
|
-
const tc: ITradeCaptureReport = view.toObject()
|
|
325
|
-
this.reports.addUpdate(tc.TradeReportID, tc)
|
|
326
|
-
this.logger.info(`[reports: ${this.reports.count()}] received tc ExecID = ${tc.ExecID} TradeReportID = ${tc.TradeReportID} Symbol = ${tc.Instrument.Symbol} ${tc.LastQty} @ ${tc.LastPx}`)
|
|
327
|
-
break
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
case MsgType.TradeCaptureReportRequestAck: {
|
|
331
|
-
const tc: ITradeCaptureReportRequestAck = view.toObject()
|
|
332
|
-
this.logger.info(`received tcr ack ${tc.TradeRequestID} ${tc.TradeRequestStatus}`)
|
|
333
|
-
break
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
the client onReady method is called when a connection is made and logon established and confirmed. In this case, client sends a trade capture request to the server.
|
|
340
|
-
|
|
341
|
-
```typescript
|
|
342
|
-
protected onReady (view: MsgView): void {
|
|
343
|
-
this.logger.info('ready')
|
|
344
|
-
const tcr: ITradeCaptureReportRequest = TradeFactory.tradeCaptureReportRequest('all-trades', new Date())
|
|
345
|
-
// send request to server
|
|
346
|
-
this.send(MsgType.TradeCaptureReportRequest, tcr)
|
|
347
|
-
const logoutSeconds = 32
|
|
348
|
-
this.logger.info(`will logout after ${logoutSeconds}`)
|
|
349
|
-
setTimeout(() => {
|
|
350
|
-
this.done()
|
|
351
|
-
}, logoutSeconds * 1000)
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
## working with Views
|
|
357
|
-
|
|
358
|
-
see src/test/view-decode.test.ts
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
note that a view can only be used within a callback context unless it is cloned. Once returned, the memory is re-used for next message. It is intended to convert to an object or parsed into an application specific message.
|
|
268
|
+
## Working with Messages
|
|
362
269
|
|
|
363
|
-
|
|
270
|
+
A `MsgView` is a zero-copy view over the parse buffer. The view is only valid inside the callback that received it — clone it (`view.clone()`) if you need to hold onto it past the current tick. Most code converts the view to a typed object via `toObject()`.
|
|
364
271
|
|
|
365
272
|
```typescript
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
const mmEntryExpireTimeAsString: string = mmEntryView.getString('ExpireTime')
|
|
370
|
-
expect(mmEntryExpireTimeAsString).toEqual('20180608-20:53:14.000')
|
|
371
|
-
expect(mmEntryView.getString(126)).toEqual('20180608-20:53:14.000')
|
|
273
|
+
import { ITradeCaptureReport } from 'jspurefix/dist/types/FIX4.4/repo'
|
|
274
|
+
const tc: ITradeCaptureReport = view.toObject()
|
|
372
275
|
```
|
|
373
276
|
|
|
374
|
-
|
|
277
|
+
Read a single tag by name or number:
|
|
375
278
|
|
|
376
279
|
```typescript
|
|
377
|
-
const erView: MsgView = views[0]
|
|
378
280
|
expect(erView.getString(35)).toEqual('8')
|
|
379
281
|
expect(erView.getString('MsgType')).toEqual('8')
|
|
380
|
-
expect(erView.getString(8)).toEqual('FIX4.4')
|
|
381
282
|
expect(erView.getTyped(9)).toEqual(6542)
|
|
382
283
|
expect(erView.getTyped('TotNumReports')).toEqual(19404)
|
|
383
|
-
expect(erView.getTyped('StrikePrice')).toEqual(52639)
|
|
384
284
|
```
|
|
385
285
|
|
|
386
|
-
|
|
286
|
+
Read several tags in one call:
|
|
387
287
|
|
|
388
288
|
```typescript
|
|
389
|
-
|
|
390
|
-
expect(erView.getStrings('PartyID')).toEqual(['magna.', 'iaculis', 'vitae,'])
|
|
289
|
+
const [a, b, c, d] = view.getTypedTags([8, 9, 35, 49])
|
|
391
290
|
```
|
|
392
291
|
|
|
393
|
-
|
|
292
|
+
Read all instances of a repeated tag:
|
|
394
293
|
|
|
395
294
|
```typescript
|
|
396
|
-
|
|
397
|
-
expect(a).toEqual('FIX4.4')
|
|
398
|
-
expect(b).toEqual(2955)
|
|
399
|
-
expect(c).toEqual('W')
|
|
400
|
-
expect(d).toEqual('sender-10')
|
|
295
|
+
expect(erView.getStrings('PartyID')).toEqual(['magna.', 'iaculis', 'vitae,'])
|
|
401
296
|
```
|
|
402
297
|
|
|
403
|
-
|
|
298
|
+
Drill into a repeating group or component:
|
|
404
299
|
|
|
405
300
|
```typescript
|
|
406
|
-
|
|
407
|
-
|
|
301
|
+
const noMDEntriesView: MsgView = view.getView('NoMDEntries')
|
|
302
|
+
const firstEntry: MsgView = noMDEntriesView.getGroupInstance(1)
|
|
303
|
+
const expireTime: string = firstEntry.getString('ExpireTime')
|
|
408
304
|
|
|
409
|
-
const
|
|
305
|
+
const instrument: IInstrument = view.getView('Instrument').toObject()
|
|
410
306
|
```
|
|
411
307
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
get first in group fetched from object where group is array
|
|
308
|
+
Convert nested structures in one call:
|
|
415
309
|
|
|
416
310
|
```typescript
|
|
417
|
-
|
|
418
|
-
import { IUnderlyingInstrument } from '../types/FIX4.4/quickfix/set/underlying_instrument'
|
|
419
|
-
const erView: MsgView = views[0]
|
|
420
|
-
const undInstrmtGrpView: MsgView = erView.getView('UndInstrmtGrp')
|
|
421
|
-
const undInstrmtGrpViewAsObject: IUndInstrmtGrp = undInstrmtGrpView.toObject()
|
|
422
|
-
expect(undInstrmtGrpViewAsObject.NoUnderlyings.length).toEqual(2)
|
|
423
|
-
const underlying0: IUnderlyingInstrument = undInstrmtGrpViewAsObject.NoUnderlyings[0].UnderlyingInstrument
|
|
424
|
-
expect(underlying0.UnderlyingSymbol).toEqual('massa.')
|
|
311
|
+
const legGrp: IInstrumentLeg[] = view.getView('InstrmtLegGrp.NoLegs').toObject()
|
|
425
312
|
```
|
|
426
313
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
```typescript
|
|
430
|
-
const legGrpView = view.getView('InstrmtLegGrp.NoLegs')
|
|
431
|
-
expect(legGrpView).toBeTruthy()
|
|
432
|
-
const legGrp: IInstrumentLeg[] = legGrpView.toObject()
|
|
433
|
-
expect(legGrp).toBeTruthy()
|
|
434
|
-
expect(Array.isArray(legGrp))
|
|
435
|
-
expect(legGrp.length).toEqual(2)
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
get a tokenised view of tags in view
|
|
314
|
+
Dump a tokenised view of every tag in a message:
|
|
439
315
|
|
|
440
316
|
```typescript
|
|
441
317
|
console.log(view.toString())
|
|
442
318
|
```
|
|
443
319
|
|
|
444
|
-
|
|
320
|
+
See `src/test/ascii/view-decode.test.ts` for many more examples.
|
|
445
321
|
|
|
446
|
-
|
|
447
|
-
const instrumentView: MsgView = view.getView('Instrument')
|
|
448
|
-
const instrumentObject: IInstrument = view.getView('Instrument').toObject()
|
|
449
|
-
```
|
|
322
|
+
## FIXML over HTTP
|
|
450
323
|
|
|
451
|
-
|
|
324
|
+
ASCII and FIXML sessions share the same `AsciiSession`-style application API — the framing is the only thing that changes. A small HTTP OMS demo lives at `src/sample/http/oms/`.
|
|
452
325
|
|
|
453
|
-
|
|
326
|
+
Build an order:
|
|
454
327
|
|
|
455
328
|
```typescript
|
|
456
|
-
protected onReady (view: MsgView): void {
|
|
457
|
-
this.logger.info('onReady')
|
|
458
|
-
const logoutSeconds = this.logoutSeconds
|
|
459
|
-
const req = this.factory.createOrder('IBM', Side.Buy, 10000, 100.12)
|
|
460
|
-
this.send('NewOrderSingle', req)
|
|
461
|
-
this.logger.info(`will logout after ${logoutSeconds}`)
|
|
462
|
-
setTimeout(() => {
|
|
463
|
-
this.done()
|
|
464
|
-
}, 11 * 1000)
|
|
465
|
-
}
|
|
466
|
-
|
|
467
329
|
public createOrder (symbol: string, side: Side, qty: number, price: number): INewOrderSingle {
|
|
468
|
-
const id: number = this.id++
|
|
469
330
|
return {
|
|
470
|
-
ClOrdID: `Cli${id}`,
|
|
331
|
+
ClOrdID: `Cli${this.id++}`,
|
|
471
332
|
Account: this.account,
|
|
472
333
|
Side: side,
|
|
473
334
|
Price: price,
|
|
474
335
|
OrdType: OrdType.Limit,
|
|
475
|
-
OrderQtyData: {
|
|
476
|
-
OrderQty: qty
|
|
477
|
-
} as IOrderQtyData,
|
|
336
|
+
OrderQtyData: { OrderQty: qty } as IOrderQtyData,
|
|
478
337
|
Instrument: {
|
|
479
338
|
Symbol: symbol,
|
|
480
339
|
SecurityID: '459200101',
|
|
@@ -485,335 +344,154 @@ public createOrder (symbol: string, side: Side, qty: number, price: number): INe
|
|
|
485
344
|
}
|
|
486
345
|
```
|
|
487
346
|
|
|
488
|
-
|
|
347
|
+
That renders to:
|
|
489
348
|
|
|
490
349
|
```xml
|
|
491
350
|
<FIXML>
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
351
|
+
<Order ID="Cli1" Acct="TradersRUs" Side="1" Typ="2" Px="100.12" TmInForce="1">
|
|
352
|
+
<Hdr SID="accept-comp" TID="init-comp" SSub="user123" TSub="INC"/>
|
|
353
|
+
<Instrmt Sym="IBM" ID="459200101" Src="4"/>
|
|
354
|
+
<OrdQty Qty="10000"/>
|
|
355
|
+
</Order>
|
|
497
356
|
</FIXML>
|
|
498
357
|
```
|
|
499
358
|
|
|
500
|
-
|
|
359
|
+
The server receives the order and produces an execution report:
|
|
501
360
|
|
|
502
361
|
```typescript
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
const order: INewOrderSingle = view.toObject()
|
|
509
|
-
this.logger.info(`received order id ${order.ClOrdID}`)
|
|
510
|
-
const fill: IExecutionReport = this.factory.fillOrder(order)
|
|
511
|
-
this.send('ExecutionReport', fill)
|
|
512
|
-
}
|
|
362
|
+
protected onApplicationMsg (msgType: string, view: MsgView): void {
|
|
363
|
+
if (msgType === 'Order') {
|
|
364
|
+
const order: INewOrderSingle = view.toObject()
|
|
365
|
+
const fill: IExecutionReport = this.factory.fillOrder(order)
|
|
366
|
+
this.send('ExecutionReport', fill)
|
|
513
367
|
}
|
|
514
368
|
}
|
|
515
|
-
|
|
516
|
-
public fillOrder (order: INewOrderSingle): IExecutionReport {
|
|
517
|
-
const id: number = this.execId++
|
|
518
|
-
return {
|
|
519
|
-
ClOrdID: order.ClOrdID,
|
|
520
|
-
OrdType: order.OrdType,
|
|
521
|
-
TransactTime: new Date(),
|
|
522
|
-
AvgPx: order.Price,
|
|
523
|
-
LeavesQty: 0,
|
|
524
|
-
LastPx: order.Price,
|
|
525
|
-
ExecType: ExecType.OrderStatus,
|
|
526
|
-
OrdStatus: OrdStatus.Filled,
|
|
527
|
-
ExecID: `exec${id}`,
|
|
528
|
-
Side: order.Side,
|
|
529
|
-
Price: order.Price,
|
|
530
|
-
OrderQtyData: {
|
|
531
|
-
OrderQty: order.OrderQtyData.OrderQty
|
|
532
|
-
} as IOrderQtyData,
|
|
533
|
-
Instrument: {
|
|
534
|
-
Symbol: order.Instrument.Symbol,
|
|
535
|
-
SecurityID: order.Instrument.SecurityID,
|
|
536
|
-
SecurityIDSource: SecurityIDSource.IsinNumber
|
|
537
|
-
} as IInstrument
|
|
538
|
-
} as IExecutionReport
|
|
539
|
-
}
|
|
540
369
|
```
|
|
541
370
|
|
|
542
|
-
|
|
371
|
+
Reply on the wire:
|
|
543
372
|
|
|
544
373
|
```xml
|
|
545
374
|
<FIXML>
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
375
|
+
<ExecRpt ID="Cli1" ExecID="exec1" ExecTyp="I" Stat="2" Side="1" Typ="2"
|
|
376
|
+
Px="100.12" LastPx="100.12" LeavesQty="0" AvgPx="100.12"
|
|
377
|
+
TxnTm="2018-10-07T12:16:12.584">
|
|
378
|
+
<Hdr SID="accept-comp" TID="init-comp" TSub="fix"/>
|
|
379
|
+
<Instrmt Sym="IBM" ID="459200101" Src="4"/>
|
|
380
|
+
<OrdQty Qty="10000"/>
|
|
381
|
+
</ExecRpt>
|
|
551
382
|
</FIXML>
|
|
552
383
|
```
|
|
553
384
|
|
|
554
|
-
##
|
|
385
|
+
## Data Dictionaries
|
|
555
386
|
|
|
556
|
-
|
|
387
|
+
jspurefix ships definitions for FIX 4.0–4.4 and FIX 5.0 SP0/SP1/SP2 in both QuickFIX XML and FIX-repository formats, under bundled aliases such as `repo44`, `qf44`, `qf50sp2`. The alias map lives at `data/dictionary.json`.
|
|
557
388
|
|
|
558
|
-
|
|
389
|
+
To add a custom dialect:
|
|
559
390
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
### performance on Windows Intel Core I7-4770 @ 3.5 GHz
|
|
564
|
-
```shell
|
|
565
|
-
[8]: repeats = 250000, fields = 58, length = 604 chars, elapsed ms 3658, 14.632 micros per msg
|
|
566
|
-
[8]: iterations = 80000, fields = 646, length = 6572 chars, elapsed ms 16499, 206.23749999999998 micros per msg
|
|
567
|
-
```
|
|
568
|
-
### performance on Windows 12th Gen Intel(R) Core(TM) i7-12700H 2.30 GHz
|
|
569
|
-
```shell
|
|
570
|
-
[8]: iterations = 80000, fields = 646, length = 6572 chars, elapsed ms 7476, 93.45 micros per msg
|
|
571
|
-
```
|
|
391
|
+
1. Drop your QuickFIX-style XML into `data/`.
|
|
392
|
+
2. Add an alias to `data/dictionary.json`.
|
|
393
|
+
3. Generate typed interfaces under `src/types`:
|
|
572
394
|
|
|
573
|
-
|
|
395
|
+
```shell
|
|
396
|
+
npm run cmd -- --dict=repo42 --compile
|
|
397
|
+
```
|
|
574
398
|
|
|
575
|
-
|
|
576
|
-
npm run repo44-bench-sd
|
|
577
|
-
```
|
|
399
|
+
4. Reference the alias from your session description (`"dictionary": "repo42"`).
|
|
578
400
|
|
|
579
|
-
|
|
580
|
-
```shell
|
|
581
|
-
[d]: repeats = 150000, fields = 223, length = 2233 chars, elapsed ms 7962, 53.080000000000005 micros per msg
|
|
582
|
-
d]: iterations = 150000, fields = 229, length = 2466 chars, elapsed ms 8672, 57.81333333333333 micros per msg
|
|
583
|
-
```
|
|
584
|
-
### performance on Windows 12th Gen Intel(R) Core(TM) i7-12700H 2.30 GHz
|
|
585
|
-
```
|
|
586
|
-
[d]: iterations = 150000, fields = 229, length = 2466 chars, elapsed ms 4628, 30.85333333333333 micros per msg
|
|
587
|
-
```
|
|
401
|
+
See [jspf-md-demo](https://github.com/TimelordUK/jspf-md-demo) for a worked example.
|
|
588
402
|
|
|
589
|
-
|
|
403
|
+
## `jsfix` CLI — log parsing & stats
|
|
404
|
+
|
|
405
|
+
The `jsfix-cmd` tool parses any FIX log given an appropriate dictionary.
|
|
406
|
+
|
|
407
|
+
Token dump for a specific message type:
|
|
590
408
|
|
|
591
409
|
```shell
|
|
592
|
-
npm run repo44
|
|
593
|
-
```
|
|
594
|
-
### performance on Windows Intel Core I7-4770 @ 3.5 GHz
|
|
595
|
-
```shell
|
|
596
|
-
[AE]: repeats = 30000, fields = 613, length = 5818 chars, elapsed ms 5206, 173.53333333333333 micros per msg
|
|
597
|
-
[AE]: iterations = 30000, fields = 578, length = 5741 chars, elapsed ms 5245, 174.83333333333334 micros per msg```
|
|
410
|
+
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --tokens
|
|
598
411
|
```
|
|
599
412
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
[
|
|
413
|
+
```
|
|
414
|
+
[0] 8 (BeginString) = FIX4.4, [1] 9 (BodyLength) = 0000135
|
|
415
|
+
[2] 35 (MsgType) = AD[TradeCaptureReportRequest], [3] 49 (SenderCompID) = init-comp
|
|
416
|
+
[4] 56 (TargetCompID) = accept-comp, [5] 34 (MsgSeqNum) = 2
|
|
417
|
+
...
|
|
603
418
|
```
|
|
604
419
|
|
|
605
|
-
|
|
420
|
+
Per-type message counts for the file:
|
|
606
421
|
|
|
607
422
|
```shell
|
|
608
|
-
npm run
|
|
423
|
+
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --stats
|
|
609
424
|
```
|
|
610
425
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
[0]: iterations = 250000, fields = 10, length = 131 chars, elapsed ms 950, 3.8 micros per msg
|
|
426
|
+
```json
|
|
427
|
+
{ "0": 1, "5": 2, "A": 2, "AD": 1, "AQ": 2, "AE": 5 }
|
|
614
428
|
```
|
|
615
|
-
### performance on Windows 12th Gen Intel(R) Core(TM) i7-12700H 2.30 GHz
|
|
616
|
-
```shell
|
|
617
|
-
[0]: iterations = 250000, fields = 10, length = 131 chars, elapsed ms 468, 1.8719999999999999 micros per msg
|
|
618
|
-
````
|
|
619
429
|
|
|
620
|
-
|
|
430
|
+
Convert to typed objects:
|
|
621
431
|
|
|
622
432
|
```shell
|
|
623
|
-
npm run
|
|
433
|
+
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --objects
|
|
624
434
|
```
|
|
625
435
|
|
|
626
|
-
|
|
436
|
+
Show the parser's view of nested structures within a message:
|
|
437
|
+
|
|
627
438
|
```shell
|
|
628
|
-
|
|
439
|
+
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --structures
|
|
629
440
|
```
|
|
630
|
-
|
|
441
|
+
|
|
442
|
+
Repeat-parse for benchmarking (`--repeats=N`):
|
|
443
|
+
|
|
631
444
|
```shell
|
|
632
|
-
|
|
445
|
+
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --stats --repeats=20
|
|
633
446
|
```
|
|
634
447
|
|
|
635
|
-
##
|
|
448
|
+
## Performance
|
|
636
449
|
|
|
637
|
-
|
|
450
|
+
Numbers below are illustrative — generated messages, single-threaded, parser-only (no I/O). Run them yourself with the corresponding `npm run` script.
|
|
638
451
|
|
|
639
|
-
|
|
452
|
+
| Benchmark | Script | Fields/msg | Length (chars) | I7-4770 @ 3.5 GHz | i7-12700H @ 2.3 GHz | Ryzen 9 7950X @ 4.5 GHz |
|
|
453
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
454
|
+
| Heartbeat | `npm run qf-bench-hb` | 10 | 131 | 3.8 µs/msg | 1.9 µs/msg | 1.6 µs/msg |
|
|
455
|
+
| Logon | `npm run qf-bench-lo` | 22 | 214 | 5.9 µs/msg | 2.8 µs/msg | 2.3 µs/msg |
|
|
456
|
+
| Execution Report (large) | `npm run repo44-bench-er` | 646 | 6 571 | 206.2 µs/msg | 93.5 µs/msg | 72.9 µs/msg |
|
|
457
|
+
| Security Definition † | `npm run repo44-bench-sd` | 52 | 557 | — | — | 5.6 µs/msg |
|
|
458
|
+
| Trade Capture † | `npm run repo44-bench-tc` | 112 | 1 137 | — | — | 9.3 µs/msg |
|
|
640
459
|
|
|
641
|
-
|
|
642
|
-
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --tokens
|
|
643
|
-
```
|
|
460
|
+
† The SD and TC fixtures have shrunk since the older measurements were taken (SD was 229 fields / 2 466 chars, TC was 578 fields / 5 741 chars), so the prior numbers aren't comparable with the current fixture and have been dropped from the row. Re-running them on the older hardware would produce a clean third column. Ryzen measurements taken on Node 24 LTS, WSL2.
|
|
644
461
|
|
|
645
|
-
|
|
646
|
-
[0] 8 (BeginString) = FIX4.4, [1] 9 (BodyLength) = 0000135
|
|
647
|
-
[2] 35 (MsgType) = AD[TradeCaptureReportRequest], [3] 49 (SenderCompID) = init-comp
|
|
648
|
-
[4] 56 (TargetCompID) = accept-comp, [5] 34 (MsgSeqNum) = 2
|
|
649
|
-
[6] 57 (TargetSubID) = fix, [7] 52 (SendingTime) = 20180923-16:07:04.763
|
|
650
|
-
[8] 568 (TradeRequestID) = all-trades, [9] 569 (TradeRequestType) = 0[AllTrades]
|
|
651
|
-
[10] 263 (SubscriptionRequestType) = 1[SnapshotAndUpdates], [11] 580 (NoDates) = 1
|
|
652
|
-
[12] 75 (TradeDate) = 20180923, [13] 10 (CheckSum) = 250
|
|
653
|
-
```
|
|
462
|
+
## Developing on jspurefix
|
|
654
463
|
|
|
655
|
-
|
|
464
|
+
Clone and build:
|
|
656
465
|
|
|
657
466
|
```shell
|
|
658
|
-
|
|
467
|
+
git clone https://github.com/TimelordUK/jspurefix.git
|
|
468
|
+
cd jspurefix
|
|
469
|
+
npm install # postinstall unpacks the FIX dictionaries
|
|
470
|
+
npm run build
|
|
659
471
|
```
|
|
660
472
|
|
|
661
|
-
|
|
662
|
-
messages 13 elapsed ms 8
|
|
663
|
-
{
|
|
664
|
-
"0": 1,
|
|
665
|
-
"5": 2,
|
|
666
|
-
"A": 2,
|
|
667
|
-
"AD": 1,
|
|
668
|
-
"AQ": 2,
|
|
669
|
-
"AE": 5
|
|
670
|
-
}
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
## benchmark parsing repeated reads of file
|
|
473
|
+
Run the test suite (Jest, single worker, with coverage):
|
|
674
474
|
|
|
675
|
-
```
|
|
676
|
-
npm
|
|
475
|
+
```shell
|
|
476
|
+
npm test
|
|
677
477
|
```
|
|
678
478
|
|
|
679
|
-
|
|
680
|
-
messages 13 elapsed ms 0
|
|
681
|
-
{
|
|
682
|
-
"0": 1,
|
|
683
|
-
"5": 2,
|
|
684
|
-
"A": 2,
|
|
685
|
-
"AD": 1,
|
|
686
|
-
"AQ": 2,
|
|
687
|
-
"AE": 5
|
|
688
|
-
}
|
|
689
|
-
```
|
|
479
|
+
The full suite currently runs 535 tests across 43 suites and takes ~70 s on a modern laptop. `script/build.sh` (unix) and `script\build.cmd` (windows) wrap install + build + test if you want a one-shot bootstrap.
|
|
690
480
|
|
|
691
|
-
|
|
481
|
+
Try a sample end-to-end:
|
|
692
482
|
|
|
693
|
-
```
|
|
694
|
-
npm run
|
|
695
|
-
```
|
|
696
|
-
|
|
697
|
-
```json
|
|
698
|
-
{
|
|
699
|
-
"StandardHeader": {
|
|
700
|
-
"BeginString": "FIX4.4",
|
|
701
|
-
"BodyLength": 135,
|
|
702
|
-
"MsgType": "AD",
|
|
703
|
-
"SenderCompID": "init-comp",
|
|
704
|
-
"TargetCompID": "accept-comp",
|
|
705
|
-
"MsgSeqNum": 2,
|
|
706
|
-
"TargetSubID": "fix",
|
|
707
|
-
"SendingTime": "2018-09-23T16:07:04.763Z"
|
|
708
|
-
},
|
|
709
|
-
"TradeRequestID": "all-trades",
|
|
710
|
-
"TradeRequestType": 0,
|
|
711
|
-
"SubscriptionRequestType": "1",
|
|
712
|
-
"TrdCapDtGrp": [
|
|
713
|
-
{
|
|
714
|
-
"TradeDate": "2018-09-22T23:00:00.000Z"
|
|
715
|
-
}
|
|
716
|
-
],
|
|
717
|
-
"StandardTrailer": {
|
|
718
|
-
"CheckSum": "250"
|
|
719
|
-
}
|
|
720
|
-
}
|
|
483
|
+
```shell
|
|
484
|
+
npm run tcp-tc # trade-capture client + server
|
|
721
485
|
```
|
|
722
486
|
|
|
723
|
-
|
|
487
|
+
## C# Port
|
|
724
488
|
|
|
725
|
-
|
|
726
|
-
{
|
|
727
|
-
"StandardHeader": {
|
|
728
|
-
"BeginString": "FIX4.4",
|
|
729
|
-
"BodyLength": 213,
|
|
730
|
-
"MsgType": "AE",
|
|
731
|
-
"SenderCompID": "accept-comp",
|
|
732
|
-
"TargetCompID": "init-comp",
|
|
733
|
-
"MsgSeqNum": 4,
|
|
734
|
-
"TargetSubID": "fix",
|
|
735
|
-
"SendingTime": "2018-09-23T16:07:04.986Z"
|
|
736
|
-
},
|
|
737
|
-
"TradeReportID": "100001",
|
|
738
|
-
"TradeReportTransType": 0,
|
|
739
|
-
"TradeReportType": 0,
|
|
740
|
-
"TrdType": 0,
|
|
741
|
-
"ExecID": "600001",
|
|
742
|
-
"OrdStatus": "2",
|
|
743
|
-
"PreviouslyReported": false,
|
|
744
|
-
"Instrument": {
|
|
745
|
-
"Symbol": "Gold",
|
|
746
|
-
"SecurityID": "Gold.INC"
|
|
747
|
-
},
|
|
748
|
-
"LastQty": 107,
|
|
749
|
-
"LastPx": 45.38,
|
|
750
|
-
"TradeDate": "2018-09-22T23:00:00.000Z",
|
|
751
|
-
"TransactTime": "2018-09-23T16:07:04.776Z",
|
|
752
|
-
"StandardTrailer": {
|
|
753
|
-
"CheckSum": "54"
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
```
|
|
489
|
+
This engine has been ported to C# as [cspurefix](https://github.com/TimelordUK/cspurefix), which is kept in lockstep with this codebase. If you're on .NET:
|
|
757
490
|
|
|
758
|
-
|
|
491
|
+
- **Source**: [TimelordUK/cspurefix](https://github.com/TimelordUK/cspurefix)
|
|
492
|
+
- **Demo**: [TimelordUK/purefix-standalone-demo](https://github.com/TimelordUK/purefix-standalone-demo)
|
|
493
|
+
- **NuGet**: [PureFix.Types.Core](https://www.nuget.org/packages/PureFix.Types.Core/)
|
|
759
494
|
|
|
760
|
-
|
|
761
|
-
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --structures
|
|
762
|
-
```
|
|
495
|
+
## License
|
|
763
496
|
|
|
764
|
-
|
|
765
|
-
[
|
|
766
|
-
{
|
|
767
|
-
"name": "StandardHeader",
|
|
768
|
-
"depth": 2,
|
|
769
|
-
"startTag": 8,
|
|
770
|
-
"startPosition": 0,
|
|
771
|
-
"endTag": 52,
|
|
772
|
-
"endPosition": 7,
|
|
773
|
-
"delimiterTag": 0,
|
|
774
|
-
"delimiterPositions": []
|
|
775
|
-
},
|
|
776
|
-
{
|
|
777
|
-
"name": "TrdCapDtGrp",
|
|
778
|
-
"depth": 1,
|
|
779
|
-
"startTag": 580,
|
|
780
|
-
"startPosition": 11,
|
|
781
|
-
"endTag": 75,
|
|
782
|
-
"endPosition": 12,
|
|
783
|
-
"delimiterTag": 75,
|
|
784
|
-
"delimiterPositions": [
|
|
785
|
-
12
|
|
786
|
-
]
|
|
787
|
-
},
|
|
788
|
-
{
|
|
789
|
-
"name": "StandardTrailer",
|
|
790
|
-
"depth": 1,
|
|
791
|
-
"startTag": 10,
|
|
792
|
-
"startPosition": 13,
|
|
793
|
-
"endTag": 10,
|
|
794
|
-
"endPosition": 13,
|
|
795
|
-
"delimiterTag": 0,
|
|
796
|
-
"delimiterPositions": []
|
|
797
|
-
},
|
|
798
|
-
{
|
|
799
|
-
"name": "TradeCaptureReportRequest",
|
|
800
|
-
"depth": 1,
|
|
801
|
-
"startTag": 8,
|
|
802
|
-
"startPosition": 0,
|
|
803
|
-
"endTag": 10,
|
|
804
|
-
"endPosition": 13,
|
|
805
|
-
"delimiterTag": 0,
|
|
806
|
-
"delimiterPositions": []
|
|
807
|
-
},
|
|
808
|
-
{
|
|
809
|
-
"name": "StandardTrailer",
|
|
810
|
-
"depth": 0,
|
|
811
|
-
"startTag": 10,
|
|
812
|
-
"startPosition": 14,
|
|
813
|
-
"endTag": 10,
|
|
814
|
-
"endPosition": 13,
|
|
815
|
-
"delimiterTag": 0,
|
|
816
|
-
"delimiterPositions": []
|
|
817
|
-
}
|
|
818
|
-
]
|
|
819
|
-
```
|
|
497
|
+
MIT. See [LICENSE](./LICENSE).
|