jspurefix 5.6.2 → 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/README.md CHANGED
@@ -3,66 +3,58 @@
3
3
  [![CI](https://github.com/TimelordUK/jspurefix/actions/workflows/ci.yml/badge.svg)](https://github.com/TimelordUK/jspurefix/actions/workflows/ci.yml)
4
4
  [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
5
5
 
6
- ## C# Port Available
7
-
8
- This TypeScript FIX engine has been ported to C#:
9
-
10
- - **Codebase**: [cspurefix](https://github.com/TimelordUK/cspurefix)
11
- - **Demo**: [purefix-standalone-demo](https://github.com/TimelordUK/purefix-standalone-demo)
12
- - **NuGet**: [PureFix.Types.Core](https://www.nuget.org/packages/PureFix.Types.Core/)
13
-
14
- ---
15
-
16
- 1. fast 100% native clean fix engine for Node JS
17
- 1. supports tls encrypted sessions over tcp
18
- 1. represent data dictionary as quickfix or repo notation
19
- 1. compile interface types against definitions
20
- 1. ascii / fixml supported
21
- 1. parses repeat groups, components and raw data fields
22
- 1. views make convinient ways of accessing data
23
- 1. complete trade capture sample to get started
24
- 1. a skeleton example shows connection, login and session only
25
- 1. parse fix logs into human readable format - JSON, tokens
26
- 1. socket session management for login, heartbeat etc
27
- 1. implement httpInitiator or acceptor
28
-
29
- ## Typescript FIX Engine for Node JS
30
-
31
- This fix engine provides a fast easy API to parse or send finacial protocol FIX messages. It is implemented entirely in typescript and runs in Node JS. Messages of any complexity can be handled providing they are backed by a suitable data dictionary. All structures within a message will be resolved for easy access - groups of components containing groups etc.
32
-
33
- ## Installing
34
-
35
- compiled typescript is now included. Install the package from npm:
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
- npm install jspurefix
39
- cd node_modules/jspurefix && npm run unzip-repo
45
+ npm install jspurefix
46
+ cd node_modules/jspurefix && npm run unzip-repo
40
47
  ```
41
48
 
42
- see our [demo application](https://github.com/TimelordUK/jspf-demo/)
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
- if you wish to generate custom messages this is easily doing using quick fix XML format - see [quickfix demo](https://github.com/TimelordUK/jspf-md-demo)
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
- edit the dictionary file data/generate and run the generator.
51
-
52
- ```shell
53
- npm run generate
54
- ```
53
+ ## Quickstart
55
54
 
56
- check the messages generated in src\types
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
- // types for the specific FIX dialect
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
- see example trade-capture-client - use
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
- ```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
- ```
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
- windows
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
- ```shell
112
- git clone https://github.com/TimelordUK/jspurefix.git
113
- cd jspurefix
114
- script\build.cmd
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
- or
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
- git clone https://github.com/TimelordUK/jspurefix.git
121
- cd jspurefix
122
- npm install
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
- ## Run Sample
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
- Both examples will automatically close down after ~1 minute or can be terminated CTRL-C
116
+ ## Session Configuration
135
117
 
136
- ensure the repo dictionary has been unpacked above. This provides the data dictionary
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
- run examples such as the trade capture, simple skeleton or http.
140
-
141
- ```shell
142
- npm run tcp-tc
143
- ```
144
-
145
- ```shell
146
- npm run tcp-sk
147
- ```
148
-
149
- included is an example fixml over http application where an order is submitted and execution report returned.
150
-
151
- ```shell
152
- npm run http-oms
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
- ## tls SSL encryption
156
-
157
- see example data\session\test-initiator-tls.json
142
+ ### TLS
158
143
 
159
- to run the provided example tls trade capture a script such as script\getKey.ps1 can be run to generate self certified certificates. This is a powershell script that requires openssl in the tunnel e.g. /mingw64/bin/openssl. the ca field below is only required when using self certified and will not be needed for a third party vendor where certificates are provided. Set enableTrace flag whilst diagnosing the session.
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" : "localhost",
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
- "Username": "js-tls-client",
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
- ## configure the body length field padding width
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
- see example initiator file data\session\test-initiator-tls.json.
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
- i.e. include field BodyLengthChars which defaults to 7 characters if omitted.
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
- "BodsyLengthChars": 6
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
- ## launching without resetting message sequences
203
+ The file store writes QuickFIX-compatible files into the directory:
212
204
 
213
- If the message sequences are persisted over multiple sessions and are not reset on logon (ie. `"ResetSeqNumFlag": false,`),
214
- then the previously used sequence numbers can be set as follows:
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
- ## resending messages
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
- By default, the library will not resend past messages as this requires persisting messages which depending on the volume,
227
- may also require a database. If you want to support resending you must override `AsciiSession.onResendRequest()` with a resending logic.
228
- Additionally, make sure to include the original message sequence and the duplicate message flag in the FIX object:
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
- StandardHeader: { PossDupFlag: true, MsgSeqNum: sequenceNumber},
237
+ ...messageBodyData,
238
+ StandardHeader: { PossDupFlag: true, MsgSeqNum: sequenceNumber }
234
239
  }
235
-
236
240
  ```
237
241
 
238
- ### Example
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
- ## Unit Tests
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
- 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.
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
- const noMDEntriesView: MsgView = view.getView('NoMDEntries')
367
- const mmEntryView: MsgView = noMDEntriesView.getGroupInstance(1)
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
- fetch single tags
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
- fetch repeated tag
277
+ Read several tags in one call:
387
278
 
388
279
  ```typescript
389
- const erView: MsgView = views[0]
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
- fetch a set of tags
283
+ Read all instances of a repeated tag:
394
284
 
395
285
  ```typescript
396
- const [a, b, c, d] = view.getTypedTags([8, 9, 35, 49])
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
- convert view into an object which can be used alongside an interface for intellisense.
289
+ Drill into a repeating group or component:
404
290
 
405
291
  ```typescript
406
- import { ITradeCaptureReport } from '../../../types/FIX4.4/repo/trade_capture_report'
407
- import { ITradeCaptureReportRequest } from '../../../types/FIX4.4/repo/trade_capture_report_request'
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 tc: ITradeCaptureReport = view.toObject()
296
+ const instrument: IInstrument = view.getView('Instrument').toObject()
410
297
  ```
411
298
 
412
- from data/examples/FIX.4.4/quickfix/execution-report
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
- import { IUndInstrmtGrp } from '../types/FIX4.4/quickfix/set/und_instrmt_grp'
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
- fetch nested structure in one call
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
- get a component from parent - this is very low cost
311
+ See `src/test/ascii/view-decode.test.ts` for many more examples.
445
312
 
446
- ```typescript
447
- const instrumentView: MsgView = view.getView('Instrument')
448
- const instrumentObject: IInstrument = view.getView('Instrument').toObject()
449
- ```
313
+ ## FIXML over HTTP
450
314
 
451
- ## FIXML
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
- Please see sample code src/sample/http for an example of how fixml can be used. Note regardless of using AsciiChars or Fixml application code looks very similar for example the client in this case.
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
- this renders to this message sent over http
338
+ That renders to:
489
339
 
490
340
  ```xml
491
341
  <FIXML>
492
- <Order ID="Cli1" Acct="TradersRUs" Side="1" Typ="2" Px="100.12" TmInForce="1">
493
- <Hdr SID="accept-comp" TID="init-comp" SSub="user123" TSub="INC"/>
494
- <Instrmt Sym="IBM" ID="459200101" Src="4"/>
495
- <OrdQty Qty="10000"/>
496
- </Order>
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
- the server receives this message and sends back an execution report :-
350
+ The server receives the order and produces an execution report:
501
351
 
502
352
  ```typescript
503
- protected onApplicationMsg (msgType: string, view: MsgView): void {
504
- // dispatch messages
505
- this.logger.info(view.toJson())
506
- switch (msgType) {
507
- case 'Order': {
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
- the fixml is sent back to the client :-
362
+ Reply on the wire:
543
363
 
544
364
  ```xml
545
365
  <FIXML>
546
- <ExecRpt ID="Cli1" ExecID="exec1" ExecTyp="I" Stat="2" Side="1" Typ="2" Px="100.12" LastPx="100.12" LeavesQty="0" AvgPx="100.12" TxnTm="2018-10-07T12:16:12.584">
547
- <Hdr SID="accept-comp" TID="init-comp" TSub="fix"/>
548
- <Instrmt Sym="IBM" ID="459200101" Src="4"/>
549
- <OrdQty Qty="10000"/>
550
- </ExecRpt>
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
- ## Performance
376
+ ## Data Dictionaries
555
377
 
556
- These messages have been randomly generated with command line tool. They are syntactically valid.
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
- ### data/examples/FIX.4.4/repo/execution-report/fix.txt
380
+ To add a custom dialect:
559
381
 
560
- ```shell
561
- npm run repo44-bench-er
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
- ### data/examples/FIX.4.4/repo/security-definition/fix.txt
386
+ ```shell
387
+ npm run cmd -- --dict=repo42 --compile
388
+ ```
574
389
 
575
- ```shell
576
- npm run repo44-bench-sd
577
- ```
390
+ 4. Reference the alias from your session description (`"dictionary": "repo42"`).
578
391
 
579
- ### performance on Windows Intel Core I7-4770 @ 3.5 GHz
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
- ### data/examples/FIX.4.4/repo/trade-capture/fix.txt
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-bench-tc
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
- ### performance on Windows 12th Gen Intel(R) Core(TM) i7-12700H 2.30 GHz
601
- ```shell
602
- [AE]: iterations = 30000, fields = 578, length = 5741 chars, elapsed ms 2725, 90.83333333333333 micros per msg```
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
- ### data/examples/FIX.4.4/quickfix/heartbeat/fix.txt
411
+ Per-type message counts for the file:
606
412
 
607
413
  ```shell
608
- npm run qf-bench-hb
414
+ npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --stats
609
415
  ```
610
416
 
611
- ### performance on Windows Intel Core I7-4770 @ 3.5 GHz
612
- ```shell
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
- ### data/examples/FIX.4.4/quickfix/logon/fix.txt
421
+ Convert to typed objects:
621
422
 
622
423
  ```shell
623
- npm run qf-bench-lo
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
- ### performance on Windows Intel Core I7-4770 @ 3.5 GHz
427
+ Show the parser's view of nested structures within a message:
428
+
627
429
  ```shell
628
- [A]: iterations = 250000, fields = 22, length = 214 chars, elapsed ms 1466, 5.864 micros per msg
430
+ npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --structures
629
431
  ```
630
- ### performance on Windows 12th Gen Intel(R) Core(TM) i7-12700H 2.30 GHz
432
+
433
+ Repeat-parse for benchmarking (`--repeats=N`):
434
+
631
435
  ```shell
632
- [A]: iterations = 250000, fields = 22, length = 214 chars, elapsed ms 693, 2.7720000000000002 micros per msg
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
- ## Log parsing
439
+ ## Performance
636
440
 
637
- the command line tool jsfix can be used to parse any fix log providing an appropriate dictionary is provided.
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
- ## parsing fields
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
- ```shell
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
- ```shell
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
- ## stats on entire file
455
+ Clone and build:
656
456
 
657
457
  ```shell
658
- npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --stats
659
- ```
660
-
661
- ```json
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
- ## benchmark parsing repeated reads of file
464
+ Run the test suite (Jest, single worker, with coverage):
674
465
 
675
- ```cmd
676
- npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --stats --repeats=20
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
- ## parse message type in a file
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
- ```cmd
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
- ```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
- }
474
+ ```shell
475
+ npm run tcp-tc # trade-capture client + server
721
476
  ```
722
477
 
723
- as above where --type=AE --objects
478
+ ## C# Port
724
479
 
725
- ```json
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
- ## show all structures within a message
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
- ```shell
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
- ```json
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).