jspurefix 5.6.1 → 5.8.0
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 +256 -587
- package/dist/runtime/session-launcher.d.ts +2 -0
- package/dist/runtime/session-launcher.js +14 -0
- package/dist/runtime/session-launcher.js.map +1 -1
- package/dist/transport/ascii/ascii-session.d.ts +1 -0
- package/dist/transport/ascii/ascii-session.js +19 -0
- 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 +2 -4
- package/src/runtime/session-launcher.ts +18 -0
- package/src/transport/ascii/ascii-session.ts +20 -0
- 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,58 @@
|
|
|
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
|
+
- [Quickstart](#quickstart)
|
|
13
|
+
- [Session Configuration](#session-configuration)
|
|
14
|
+
- [TLS](#tls)
|
|
15
|
+
- [Body length padding](#body-length-padding)
|
|
16
|
+
- [Persistence & Recovery](#persistence--recovery)
|
|
17
|
+
- [Choosing a store](#choosing-a-store)
|
|
18
|
+
- [`ResetSeqNumFlag` semantics](#resetseqnumflag-semantics)
|
|
19
|
+
- [Resending messages](#resending-messages)
|
|
20
|
+
- [Working with Messages](#working-with-messages)
|
|
21
|
+
- [FIXML over HTTP](#fixml-over-http)
|
|
22
|
+
- [Data Dictionaries](#data-dictionaries)
|
|
23
|
+
- [`jsfix` CLI — log parsing & stats](#jsfix-cli--log-parsing--stats)
|
|
24
|
+
- [Performance](#performance)
|
|
25
|
+
- [Developing on jspurefix](#developing-on-jspurefix)
|
|
26
|
+
- [C# Port](#c-port)
|
|
27
|
+
- [License](#license)
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- 100% native TypeScript — no native bindings, runs anywhere Node.js does
|
|
32
|
+
- ASCII (tag=value) and FIXML message encodings
|
|
33
|
+
- Repeating groups, components, nested structures and raw data fields
|
|
34
|
+
- Dictionary-driven: load QuickFIX XML or FIX repository definitions, compile typed interfaces
|
|
35
|
+
- Full session lifecycle: logon, heartbeats, test requests, resend requests, logout
|
|
36
|
+
- TLS-encrypted sessions over TCP
|
|
37
|
+
- Pluggable persistent message store (in-memory or file) with sequence recovery
|
|
38
|
+
- HTTP initiator/acceptor for FIXML
|
|
39
|
+
- Command-line tool for parsing FIX logs into tokens, JSON, or structure dumps
|
|
40
|
+
- Sample applications: trade capture, market data, FIXML OMS, recovering skeleton
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
36
43
|
|
|
37
44
|
```shell
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
npm install jspurefix
|
|
46
|
+
cd node_modules/jspurefix && npm run unzip-repo
|
|
40
47
|
```
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
```shell
|
|
45
|
-
https://github.com/TimelordUK/jspf-demo
|
|
46
|
-
```
|
|
49
|
+
`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.
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
A standalone demo project lives at [TimelordUK/jspf-demo](https://github.com/TimelordUK/jspf-demo) — the fastest way to see a working initiator/acceptor.
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```shell
|
|
53
|
-
npm run generate
|
|
54
|
-
```
|
|
53
|
+
## Quickstart
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```shell
|
|
59
|
-
https://github.com/TimelordUK/jspf-md-demo
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
import types for usage with a client
|
|
55
|
+
Import the session types you need from `jspurefix` and the typed FIX messages for your dialect:
|
|
63
56
|
|
|
64
57
|
```typescript
|
|
65
|
-
|
|
66
58
|
import {
|
|
67
59
|
AsciiSession,
|
|
68
60
|
MsgView,
|
|
@@ -70,102 +62,94 @@ import {
|
|
|
70
62
|
IJsFixLogger,
|
|
71
63
|
Dictionary,
|
|
72
64
|
MsgType,
|
|
73
|
-
IJsFixConfig,
|
|
74
65
|
initiator,
|
|
75
66
|
acceptor,
|
|
76
67
|
makeConfig
|
|
77
68
|
} from 'jspurefix'
|
|
78
|
-
|
|
69
|
+
|
|
79
70
|
import {
|
|
80
71
|
ITradeCaptureReport,
|
|
81
72
|
ITradeCaptureReportRequest,
|
|
82
73
|
ITradeCaptureReportRequestAck
|
|
83
74
|
} from 'jspurefix/dist/types/FIX4.4/repo'
|
|
84
|
-
|
|
85
75
|
```
|
|
86
76
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
### getting started
|
|
90
|
-
|
|
91
|
-
clone from git
|
|
92
|
-
|
|
93
|
-
unix
|
|
94
|
-
|
|
95
|
-
```shell
|
|
96
|
-
git clone https://github.com/TimelordUK/jspurefix.git
|
|
97
|
-
cd jspurefix
|
|
98
|
-
script/build.sh
|
|
99
|
-
```
|
|
77
|
+
A minimal session subclasses `AsciiSession` and implements two callbacks: `onReady` (connection up, logon confirmed) and `onApplicationMsg` (a non-session message arrived).
|
|
100
78
|
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
79
|
+
```typescript
|
|
80
|
+
class TradeCaptureClient extends AsciiSession {
|
|
81
|
+
constructor (public readonly config: IJsFixConfig) {
|
|
82
|
+
super(config)
|
|
83
|
+
this.logReceivedMsgs = true
|
|
84
|
+
this.fixLog = config.logFactory.plain(`jsfix.${config.description.application.name}.txt`)
|
|
85
|
+
this.logger = config.logFactory.logger(`${this.me}:TradeCaptureClient`)
|
|
86
|
+
}
|
|
108
87
|
|
|
109
|
-
|
|
88
|
+
protected onReady (view: MsgView): void {
|
|
89
|
+
const tcr: ITradeCaptureReportRequest =
|
|
90
|
+
TradeFactory.tradeCaptureReportRequest('all-trades', new Date())
|
|
91
|
+
this.send(MsgType.TradeCaptureReportRequest, tcr)
|
|
92
|
+
}
|
|
110
93
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
94
|
+
protected onApplicationMsg (msgType: string, view: MsgView): void {
|
|
95
|
+
switch (msgType) {
|
|
96
|
+
case MsgType.TradeCaptureReport: {
|
|
97
|
+
const tc: ITradeCaptureReport = view.toObject()
|
|
98
|
+
this.logger.info(`tc ${tc.TradeReportID} ${tc.Instrument.Symbol} ${tc.LastQty} @ ${tc.LastPx}`)
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
115
104
|
```
|
|
116
105
|
|
|
117
|
-
|
|
106
|
+
The full sample lives at `src/sample/tcp/trade-capture/` and runs both sides over a local socket:
|
|
118
107
|
|
|
119
108
|
```shell
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
npm run unzip-repo
|
|
124
|
-
node_modules/.bin/tsc
|
|
125
|
-
npm run tcp-tc
|
|
109
|
+
npm run tcp-tc # full trade-capture client/server demo
|
|
110
|
+
npm run tcp-sk # bare skeleton: connect, log on, idle
|
|
111
|
+
npm run http-oms # FIXML order/exec-report over HTTP
|
|
126
112
|
```
|
|
127
113
|
|
|
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.
|
|
131
|
-
|
|
132
|
-
There is also a skeleton application which shows all application code stripped away to just enough to create a connection, login and see the session.
|
|
114
|
+
Each demo terminates after about a minute, or with Ctrl-C.
|
|
133
115
|
|
|
134
|
-
|
|
116
|
+
## Session Configuration
|
|
135
117
|
|
|
136
|
-
|
|
137
|
-
used to parse messages.
|
|
118
|
+
A session is described by a JSON file (or any object matching `ISessionDescription`). Example: `data/session/test-initiator.json`.
|
|
138
119
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"application": {
|
|
123
|
+
"type": "initiator",
|
|
124
|
+
"name": "test_client",
|
|
125
|
+
"reconnectSeconds": 10,
|
|
126
|
+
"tcp": { "host": "localhost", "port": 2344 },
|
|
127
|
+
"protocol": "ascii",
|
|
128
|
+
"dictionary": "repo44"
|
|
129
|
+
},
|
|
130
|
+
"Username": "js-client",
|
|
131
|
+
"Password": "pwd-client",
|
|
132
|
+
"EncryptMethod": 0,
|
|
133
|
+
"ResetSeqNumFlag": true,
|
|
134
|
+
"HeartBtInt": 30,
|
|
135
|
+
"SenderCompId": "init-comp",
|
|
136
|
+
"TargetCompID": "accept-comp",
|
|
137
|
+
"TargetSubID": "fix",
|
|
138
|
+
"BeginString": "FIX.4.4"
|
|
139
|
+
}
|
|
153
140
|
```
|
|
154
141
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
see example data\session\test-initiator-tls.json
|
|
142
|
+
### TLS
|
|
158
143
|
|
|
159
|
-
|
|
144
|
+
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
145
|
|
|
161
146
|
```json
|
|
162
147
|
{
|
|
163
148
|
"application": {
|
|
164
|
-
"reconnectSeconds": 10,
|
|
165
149
|
"type": "initiator",
|
|
166
150
|
"name": "test_client",
|
|
167
151
|
"tcp": {
|
|
168
|
-
"host"
|
|
152
|
+
"host": "localhost",
|
|
169
153
|
"port": 2344,
|
|
170
154
|
"tls": {
|
|
171
155
|
"timeout": 10000,
|
|
@@ -173,45 +157,66 @@ to run the provided example tls trade capture a script such as script\getKey.ps1
|
|
|
173
157
|
"enableTrace": true,
|
|
174
158
|
"key": "data/session/certs/client/client.key",
|
|
175
159
|
"cert": "data/session/certs/client/client.crt",
|
|
176
|
-
"ca": [
|
|
177
|
-
"data/session/certs/ca/ca.crt"
|
|
178
|
-
]
|
|
160
|
+
"ca": ["data/session/certs/ca/ca.crt"]
|
|
179
161
|
}
|
|
180
162
|
},
|
|
181
163
|
"protocol": "ascii",
|
|
182
164
|
"dictionary": "repo44"
|
|
183
165
|
},
|
|
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
|
|
166
|
+
"BeginString": "FIX4.4"
|
|
196
167
|
}
|
|
197
168
|
```
|
|
198
169
|
|
|
199
|
-
|
|
170
|
+
See `data/session/test-initiator-tls.json` for the complete file.
|
|
171
|
+
|
|
172
|
+
### Body length padding
|
|
173
|
+
|
|
174
|
+
`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.
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{ "BodyLengthChars": 6 }
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Persistence & Recovery
|
|
200
181
|
|
|
201
|
-
|
|
182
|
+
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.
|
|
202
183
|
|
|
203
|
-
|
|
184
|
+
### Choosing a store
|
|
185
|
+
|
|
186
|
+
Add a `store` block to the session description. Omit it to keep the in-memory default.
|
|
204
187
|
|
|
205
188
|
```json
|
|
206
189
|
{
|
|
207
|
-
"
|
|
190
|
+
"store": { "type": "memory" }
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"store": {
|
|
197
|
+
"type": "file",
|
|
198
|
+
"directory": "/var/fix/sessions"
|
|
199
|
+
}
|
|
208
200
|
}
|
|
209
201
|
```
|
|
210
202
|
|
|
211
|
-
|
|
203
|
+
The file store writes QuickFIX-compatible files into the directory:
|
|
212
204
|
|
|
213
|
-
|
|
214
|
-
|
|
205
|
+
| File | Contents |
|
|
206
|
+
| --- | --- |
|
|
207
|
+
| `<session>.seqnums` | Current sender / target sequence numbers |
|
|
208
|
+
| `<session>.session` | Session creation timestamp |
|
|
209
|
+
| `<session>.header` | Index lines `seqnum,offset,length` into `.body` |
|
|
210
|
+
| `<session>.body` | Concatenated raw FIX messages for resend |
|
|
211
|
+
|
|
212
|
+
Session names are derived from `BeginString-SenderCompID-TargetCompID`.
|
|
213
|
+
|
|
214
|
+
You can also pass a custom factory programmatically via `IJsFixConfig.sessionStoreFactory` — useful for testing or for plugging in an alternative backend (Redis, S3, etc.).
|
|
215
|
+
|
|
216
|
+
### `ResetSeqNumFlag` semantics
|
|
217
|
+
|
|
218
|
+
- `"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).)
|
|
219
|
+
- `"ResetSeqNumFlag": false` — sequences are preserved across logons. To seed initial sequences for a brand-new session (no persisted store), set `LastSentSeqNum` and `LastReceivedSeqNum`:
|
|
215
220
|
|
|
216
221
|
```json
|
|
217
222
|
{
|
|
@@ -221,21 +226,20 @@ then the previously used sequence numbers can be set as follows:
|
|
|
221
226
|
}
|
|
222
227
|
```
|
|
223
228
|
|
|
224
|
-
|
|
229
|
+
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
230
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
231
|
+
### Resending messages
|
|
232
|
+
|
|
233
|
+
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
234
|
|
|
230
235
|
```typescript
|
|
231
236
|
{
|
|
232
|
-
...messageBodyData,
|
|
233
|
-
|
|
237
|
+
...messageBodyData,
|
|
238
|
+
StandardHeader: { PossDupFlag: true, MsgSeqNum: sequenceNumber }
|
|
234
239
|
}
|
|
235
|
-
|
|
236
240
|
```
|
|
237
241
|
|
|
238
|
-
|
|
242
|
+
Example payload:
|
|
239
243
|
|
|
240
244
|
```json
|
|
241
245
|
{
|
|
@@ -252,229 +256,75 @@ Additionally, make sure to include the original message sequence and the duplica
|
|
|
252
256
|
}
|
|
253
257
|
```
|
|
254
258
|
|
|
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
|
-
|
|
259
|
+
## Working with Messages
|
|
360
260
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
fetch a group view
|
|
261
|
+
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
262
|
|
|
365
263
|
```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')
|
|
264
|
+
import { ITradeCaptureReport } from 'jspurefix/dist/types/FIX4.4/repo'
|
|
265
|
+
const tc: ITradeCaptureReport = view.toObject()
|
|
372
266
|
```
|
|
373
267
|
|
|
374
|
-
|
|
268
|
+
Read a single tag by name or number:
|
|
375
269
|
|
|
376
270
|
```typescript
|
|
377
|
-
const erView: MsgView = views[0]
|
|
378
271
|
expect(erView.getString(35)).toEqual('8')
|
|
379
272
|
expect(erView.getString('MsgType')).toEqual('8')
|
|
380
|
-
expect(erView.getString(8)).toEqual('FIX4.4')
|
|
381
273
|
expect(erView.getTyped(9)).toEqual(6542)
|
|
382
274
|
expect(erView.getTyped('TotNumReports')).toEqual(19404)
|
|
383
|
-
expect(erView.getTyped('StrikePrice')).toEqual(52639)
|
|
384
275
|
```
|
|
385
276
|
|
|
386
|
-
|
|
277
|
+
Read several tags in one call:
|
|
387
278
|
|
|
388
279
|
```typescript
|
|
389
|
-
|
|
390
|
-
expect(erView.getStrings('PartyID')).toEqual(['magna.', 'iaculis', 'vitae,'])
|
|
280
|
+
const [a, b, c, d] = view.getTypedTags([8, 9, 35, 49])
|
|
391
281
|
```
|
|
392
282
|
|
|
393
|
-
|
|
283
|
+
Read all instances of a repeated tag:
|
|
394
284
|
|
|
395
285
|
```typescript
|
|
396
|
-
|
|
397
|
-
expect(a).toEqual('FIX4.4')
|
|
398
|
-
expect(b).toEqual(2955)
|
|
399
|
-
expect(c).toEqual('W')
|
|
400
|
-
expect(d).toEqual('sender-10')
|
|
286
|
+
expect(erView.getStrings('PartyID')).toEqual(['magna.', 'iaculis', 'vitae,'])
|
|
401
287
|
```
|
|
402
288
|
|
|
403
|
-
|
|
289
|
+
Drill into a repeating group or component:
|
|
404
290
|
|
|
405
291
|
```typescript
|
|
406
|
-
|
|
407
|
-
|
|
292
|
+
const noMDEntriesView: MsgView = view.getView('NoMDEntries')
|
|
293
|
+
const firstEntry: MsgView = noMDEntriesView.getGroupInstance(1)
|
|
294
|
+
const expireTime: string = firstEntry.getString('ExpireTime')
|
|
408
295
|
|
|
409
|
-
const
|
|
296
|
+
const instrument: IInstrument = view.getView('Instrument').toObject()
|
|
410
297
|
```
|
|
411
298
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
get first in group fetched from object where group is array
|
|
299
|
+
Convert nested structures in one call:
|
|
415
300
|
|
|
416
301
|
```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.')
|
|
302
|
+
const legGrp: IInstrumentLeg[] = view.getView('InstrmtLegGrp.NoLegs').toObject()
|
|
425
303
|
```
|
|
426
304
|
|
|
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
|
|
305
|
+
Dump a tokenised view of every tag in a message:
|
|
439
306
|
|
|
440
307
|
```typescript
|
|
441
308
|
console.log(view.toString())
|
|
442
309
|
```
|
|
443
310
|
|
|
444
|
-
|
|
311
|
+
See `src/test/ascii/view-decode.test.ts` for many more examples.
|
|
445
312
|
|
|
446
|
-
|
|
447
|
-
const instrumentView: MsgView = view.getView('Instrument')
|
|
448
|
-
const instrumentObject: IInstrument = view.getView('Instrument').toObject()
|
|
449
|
-
```
|
|
313
|
+
## FIXML over HTTP
|
|
450
314
|
|
|
451
|
-
|
|
315
|
+
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
316
|
|
|
453
|
-
|
|
317
|
+
Build an order:
|
|
454
318
|
|
|
455
319
|
```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
320
|
public createOrder (symbol: string, side: Side, qty: number, price: number): INewOrderSingle {
|
|
468
|
-
const id: number = this.id++
|
|
469
321
|
return {
|
|
470
|
-
ClOrdID: `Cli${id}`,
|
|
322
|
+
ClOrdID: `Cli${this.id++}`,
|
|
471
323
|
Account: this.account,
|
|
472
324
|
Side: side,
|
|
473
325
|
Price: price,
|
|
474
326
|
OrdType: OrdType.Limit,
|
|
475
|
-
OrderQtyData: {
|
|
476
|
-
OrderQty: qty
|
|
477
|
-
} as IOrderQtyData,
|
|
327
|
+
OrderQtyData: { OrderQty: qty } as IOrderQtyData,
|
|
478
328
|
Instrument: {
|
|
479
329
|
Symbol: symbol,
|
|
480
330
|
SecurityID: '459200101',
|
|
@@ -485,335 +335,154 @@ public createOrder (symbol: string, side: Side, qty: number, price: number): INe
|
|
|
485
335
|
}
|
|
486
336
|
```
|
|
487
337
|
|
|
488
|
-
|
|
338
|
+
That renders to:
|
|
489
339
|
|
|
490
340
|
```xml
|
|
491
341
|
<FIXML>
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
342
|
+
<Order ID="Cli1" Acct="TradersRUs" Side="1" Typ="2" Px="100.12" TmInForce="1">
|
|
343
|
+
<Hdr SID="accept-comp" TID="init-comp" SSub="user123" TSub="INC"/>
|
|
344
|
+
<Instrmt Sym="IBM" ID="459200101" Src="4"/>
|
|
345
|
+
<OrdQty Qty="10000"/>
|
|
346
|
+
</Order>
|
|
497
347
|
</FIXML>
|
|
498
348
|
```
|
|
499
349
|
|
|
500
|
-
|
|
350
|
+
The server receives the order and produces an execution report:
|
|
501
351
|
|
|
502
352
|
```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
|
-
}
|
|
353
|
+
protected onApplicationMsg (msgType: string, view: MsgView): void {
|
|
354
|
+
if (msgType === 'Order') {
|
|
355
|
+
const order: INewOrderSingle = view.toObject()
|
|
356
|
+
const fill: IExecutionReport = this.factory.fillOrder(order)
|
|
357
|
+
this.send('ExecutionReport', fill)
|
|
513
358
|
}
|
|
514
359
|
}
|
|
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
360
|
```
|
|
541
361
|
|
|
542
|
-
|
|
362
|
+
Reply on the wire:
|
|
543
363
|
|
|
544
364
|
```xml
|
|
545
365
|
<FIXML>
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
366
|
+
<ExecRpt ID="Cli1" ExecID="exec1" ExecTyp="I" Stat="2" Side="1" Typ="2"
|
|
367
|
+
Px="100.12" LastPx="100.12" LeavesQty="0" AvgPx="100.12"
|
|
368
|
+
TxnTm="2018-10-07T12:16:12.584">
|
|
369
|
+
<Hdr SID="accept-comp" TID="init-comp" TSub="fix"/>
|
|
370
|
+
<Instrmt Sym="IBM" ID="459200101" Src="4"/>
|
|
371
|
+
<OrdQty Qty="10000"/>
|
|
372
|
+
</ExecRpt>
|
|
551
373
|
</FIXML>
|
|
552
374
|
```
|
|
553
375
|
|
|
554
|
-
##
|
|
376
|
+
## Data Dictionaries
|
|
555
377
|
|
|
556
|
-
|
|
378
|
+
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
379
|
|
|
558
|
-
|
|
380
|
+
To add a custom dialect:
|
|
559
381
|
|
|
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
|
-
```
|
|
382
|
+
1. Drop your QuickFIX-style XML into `data/`.
|
|
383
|
+
2. Add an alias to `data/dictionary.json`.
|
|
384
|
+
3. Generate typed interfaces under `src/types`:
|
|
572
385
|
|
|
573
|
-
|
|
386
|
+
```shell
|
|
387
|
+
npm run cmd -- --dict=repo42 --compile
|
|
388
|
+
```
|
|
574
389
|
|
|
575
|
-
|
|
576
|
-
npm run repo44-bench-sd
|
|
577
|
-
```
|
|
390
|
+
4. Reference the alias from your session description (`"dictionary": "repo42"`).
|
|
578
391
|
|
|
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
|
-
```
|
|
392
|
+
See [jspf-md-demo](https://github.com/TimelordUK/jspf-md-demo) for a worked example.
|
|
588
393
|
|
|
589
|
-
|
|
394
|
+
## `jsfix` CLI — log parsing & stats
|
|
395
|
+
|
|
396
|
+
The `jsfix-cmd` tool parses any FIX log given an appropriate dictionary.
|
|
397
|
+
|
|
398
|
+
Token dump for a specific message type:
|
|
590
399
|
|
|
591
400
|
```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```
|
|
401
|
+
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --tokens
|
|
598
402
|
```
|
|
599
403
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
[
|
|
404
|
+
```
|
|
405
|
+
[0] 8 (BeginString) = FIX4.4, [1] 9 (BodyLength) = 0000135
|
|
406
|
+
[2] 35 (MsgType) = AD[TradeCaptureReportRequest], [3] 49 (SenderCompID) = init-comp
|
|
407
|
+
[4] 56 (TargetCompID) = accept-comp, [5] 34 (MsgSeqNum) = 2
|
|
408
|
+
...
|
|
603
409
|
```
|
|
604
410
|
|
|
605
|
-
|
|
411
|
+
Per-type message counts for the file:
|
|
606
412
|
|
|
607
413
|
```shell
|
|
608
|
-
npm run
|
|
414
|
+
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --stats
|
|
609
415
|
```
|
|
610
416
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
[0]: iterations = 250000, fields = 10, length = 131 chars, elapsed ms 950, 3.8 micros per msg
|
|
417
|
+
```json
|
|
418
|
+
{ "0": 1, "5": 2, "A": 2, "AD": 1, "AQ": 2, "AE": 5 }
|
|
614
419
|
```
|
|
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
420
|
|
|
620
|
-
|
|
421
|
+
Convert to typed objects:
|
|
621
422
|
|
|
622
423
|
```shell
|
|
623
|
-
npm run
|
|
424
|
+
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --objects
|
|
624
425
|
```
|
|
625
426
|
|
|
626
|
-
|
|
427
|
+
Show the parser's view of nested structures within a message:
|
|
428
|
+
|
|
627
429
|
```shell
|
|
628
|
-
|
|
430
|
+
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --structures
|
|
629
431
|
```
|
|
630
|
-
|
|
432
|
+
|
|
433
|
+
Repeat-parse for benchmarking (`--repeats=N`):
|
|
434
|
+
|
|
631
435
|
```shell
|
|
632
|
-
|
|
436
|
+
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --stats --repeats=20
|
|
633
437
|
```
|
|
634
438
|
|
|
635
|
-
##
|
|
439
|
+
## Performance
|
|
636
440
|
|
|
637
|
-
|
|
441
|
+
Numbers below are illustrative — generated messages, single-threaded, parser-only (no I/O). Run them yourself with the corresponding `npm run` script.
|
|
638
442
|
|
|
639
|
-
|
|
443
|
+
| Benchmark | Script | Fields/msg | Length (chars) | I7-4770 @ 3.5 GHz | i7-12700H @ 2.3 GHz | Ryzen 9 7950X @ 4.5 GHz |
|
|
444
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
445
|
+
| Heartbeat | `npm run qf-bench-hb` | 10 | 131 | 3.8 µs/msg | 1.9 µs/msg | 1.7 µs/msg |
|
|
446
|
+
| Logon | `npm run qf-bench-lo` | 22 | 214 | 5.9 µs/msg | 2.8 µs/msg | 2.5 µs/msg |
|
|
447
|
+
| Execution Report (large) | `npm run repo44-bench-er` | 646 | 6 571 | 206.2 µs/msg | 93.5 µs/msg | 72.9 µs/msg |
|
|
448
|
+
| Security Definition † | `npm run repo44-bench-sd` | 52 | 557 | — | — | 5.6 µs/msg |
|
|
449
|
+
| Trade Capture † | `npm run repo44-bench-tc` | 112 | 1 137 | — | — | 9.3 µs/msg |
|
|
640
450
|
|
|
641
|
-
|
|
642
|
-
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --tokens
|
|
643
|
-
```
|
|
451
|
+
† 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
452
|
|
|
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
|
-
```
|
|
453
|
+
## Developing on jspurefix
|
|
654
454
|
|
|
655
|
-
|
|
455
|
+
Clone and build:
|
|
656
456
|
|
|
657
457
|
```shell
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
}
|
|
458
|
+
git clone https://github.com/TimelordUK/jspurefix.git
|
|
459
|
+
cd jspurefix
|
|
460
|
+
npm install # postinstall unpacks the FIX dictionaries
|
|
461
|
+
npm run build
|
|
671
462
|
```
|
|
672
463
|
|
|
673
|
-
|
|
464
|
+
Run the test suite (Jest, single worker, with coverage):
|
|
674
465
|
|
|
675
|
-
```
|
|
676
|
-
npm
|
|
677
|
-
```
|
|
678
|
-
|
|
679
|
-
```json
|
|
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
|
-
}
|
|
466
|
+
```shell
|
|
467
|
+
npm test
|
|
689
468
|
```
|
|
690
469
|
|
|
691
|
-
|
|
470
|
+
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.
|
|
692
471
|
|
|
693
|
-
|
|
694
|
-
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --objects
|
|
695
|
-
```
|
|
472
|
+
Try a sample end-to-end:
|
|
696
473
|
|
|
697
|
-
```
|
|
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
|
-
}
|
|
474
|
+
```shell
|
|
475
|
+
npm run tcp-tc # trade-capture client + server
|
|
721
476
|
```
|
|
722
477
|
|
|
723
|
-
|
|
478
|
+
## C# Port
|
|
724
479
|
|
|
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
|
-
```
|
|
480
|
+
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
481
|
|
|
758
|
-
|
|
482
|
+
- **Source**: [TimelordUK/cspurefix](https://github.com/TimelordUK/cspurefix)
|
|
483
|
+
- **Demo**: [TimelordUK/purefix-standalone-demo](https://github.com/TimelordUK/purefix-standalone-demo)
|
|
484
|
+
- **NuGet**: [PureFix.Types.Core](https://www.nuget.org/packages/PureFix.Types.Core/)
|
|
759
485
|
|
|
760
|
-
|
|
761
|
-
npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --structures
|
|
762
|
-
```
|
|
486
|
+
## License
|
|
763
487
|
|
|
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
|
-
```
|
|
488
|
+
MIT. See [LICENSE](./LICENSE).
|