Note we are receiving increasing numbers of production style support questions normally which resolve to missing fields within the dictionary file. This is particularly problematic when these fields belong to components such as Instrument as the message cannot be parsed correctly. Please check carefully before raising such issues as they take considerable time to check and fix. In particular a good start point is to fetch tokens such as below from the fix log and see if any fields are unknown. All missing fields have to be added into the correct messages and defined properly else you will experience problems which may seem like bugs in this library. If you are working against a commercial vendor check the specification they provide carefully and ensure missing fields are all present. see the jspf-cserver example for a bespoke set of messages working with this library.
We will not provide support unless it is clear these basic checks have been done and that full examples are available to demonstrate the problem along with the corresponding dictionary file being used.
example demo applications written with this library
description | link |
---|---|
a good start point showing FIX 4 and 5 market data request/snapshot and custom generated types. | jspf-md-demo |
simple example with a custom dictionary that targets cserver | jspf-cserver |
example trade capture applicaton | jspf-demo |
What's Changed
- Circular2 by @TimelordUK in #88
Full Changelog: 3.5.0...4.0.0
refactor to remove internal circular dependencies
- PR #88
- latest packages
changes made to this release may cause some simple compile errors in your application, please see example applications and below :-
// internally Dictionary has now been removed and replaced with typescript Map<>
readonly groups: Map<string, IContainedSet>
// stores on FixDefinitions are now based on Map - you now need, has, get, set methods. size is a property
public readonly simple: Map<string, SimpleFieldDefinition> = new Map<string, SimpleFieldDefinition>()
// in msgView the getTyped no longer returns any, when using will need cast with as
public getTyped (tagOrName: number | string): (boolean | string | number | Date | Buffer | null)
// e.g.
return new FixMsgStoreRecord(v.getString(MsgTag.MsgType) ?? '', v.getTyped(MsgTag.SendingTime) as Date, v.getTyped(MsgTag.MsgSeqNum) as number, v.toObject() as ILooseObject)
// to solve internal circular references in the data dictionary, an interface IContainedSet has been introduced and ContainedFieldSet now implements the interface and remains private to the library.
// i.e. an instance of interface may fetch a contained instance of the same interface (recursive symantics vs circular dependency)
getSet: (path: string) => IContainedSet | null
example command line usage parse FIX log
fetch trade capure messages from a log and display in object notation
node node_modules/jspurefix/dist/jsfix-cmd --fix=../../jsfix.test_client.txt --delimiter="|" --session=../../data/session/test-initiator.json --type=AE --objects
AE [TradeCaptureReport] = {
"StandardHeader": {
"BeginString": "FIX4.4",
"BodyLength": 194,
"MsgType": "AE",
"SenderCompID": "accept-comp",
"TargetCompID": "init-comp",
"MsgSeqNum": 11,
"TargetSubID": "fix",
"SendingTime": "2023-11-11T14:45:19.705Z"
},
"TradeReportID": "100007",
"TradeReportTransType": 0,
"TradeReportType": 0,
"TrdType": 0,
"ExecID": "600007",
"OrdStatus": "2",
"PreviouslyReported": false,
"Instrument": {
"Symbol": "Gold",
"SecurityID": "Gold.INC"
},
"LastQty": 118,
"LastPx": 4.07,
"TradeDate": "2023-11-11T00:00:00.000Z",
"TransactTime": "2023-11-11T14:45:19.705Z",
"StandardTrailer": {
"CheckSum": "016"
}
}
see tokenised output from log
node .\node_modules\jspurefix\dist\jsfix-cmd --fix=..\..\jsfix.test_client.txt --delimiter="|" --type=AE --session=..\..\data\session\test-initiator.json --tokens
[0] 8 (BeginString) = FIX4.4, [1] 9 (BodyLength) = 0000193
[2] 35 (MsgType) = AE[TradeCaptureReport], [3] 49 (SenderCompID) = accept-comp
[4] 56 (TargetCompID) = init-comp, [5] 34 (MsgSeqNum) = 3
[6] 57 (TargetSubID) = fix, [7] 52 (SendingTime) = 20231111-14:44:59.672
[8] 571 (TradeReportID) = 100000, [9] 487 (TradeReportTransType) = 0
[10] 856 (TradeReportType) = 0[Submit], [11] 828 (TrdType) = 0[RegularTrade]
[12] 17 (ExecID) = 600000, [13] 39 (OrdStatus) = 2[Filled]
[14] 570 (PreviouslyReported) = N[NotReportedToCounterparty], [15] 55 (Symbol) = Gold
[16] 48 (SecurityID) = Gold.INC, [17] 32 (LastQty) = 171
[18] 31 (LastPx) = 6.28, [19] 75 (TradeDate) = 20231111
[20] 60 (TransactTime) = 20231111-14:44:59.672, [21] 10 (CheckSum) = 226
FIX 5 message types generated for quickfix and repo based definitions as below.
export interface ILogon {
StandardHeader: IStandardHeader// [1] BeginString.8, BodyLength.9 .. HopRefID.630
EncryptMethod: number// [2] 98 (Int)
HeartBtInt: number// [3] 108 (Int)
RawDataLength?: number// [4] 95 (Length)
RawData?: Buffer// [5] 96 (RawData)
ResetSeqNumFlag?: boolean// [6] 141 (Boolean)
NextExpectedMsgSeqNum?: number// [7] 789 (Int)
MaxMessageSize?: number// [8] 383 (Length)
NoMsgTypes?: ILogonNoMsgTypes[]// [9] RefMsgType.372, MsgDirection.385
TestMessageIndicator?: boolean// [10] 464 (Boolean)
Username?: string// [11] 553 (String)
Password?: string// [12] 554 (String)
NewPassword?: string// [13] 925 (String)
EncryptedPasswordMethod?: number// [14] 1400 (Int)
EncryptedPasswordLen?: number// [15] 1401 (Length)
EncryptedPassword?: Buffer// [16] 1402 (RawData)
EncryptedNewPasswordLen?: number// [17] 1403 (Length)
EncryptedNewPassword?: Buffer// [18] 1404 (RawData)
SessionStatus?: number// [19] 1409 (Int)
DefaultApplVerID?: string// [20] 1137 (String)
DefaultApplExtID?: number// [21] 1407 (Int)
DefaultCstmApplVerID?: string// [22] 1408 (String)
Text?: string// [23] 58 (String)
EncodedTextLen?: number// [24] 354 (Length)
EncodedText?: Buffer// [25] 355 (RawData)
StandardTrailer: IStandardTrailer// [26] SignatureLength.93, Signature.89, CheckSum.10
}
you can benchmark the library with something like
npm run qf50sp2-bench-md
> jspurefix@3.2.0 qf50sp2-bench-md
> node dist/jsfix-cmd --dict=qf50sp2 --fix=data/examples/FIX.5.0SP2/quickfix/md-data-snapshot/fix.txt --benchmark --delimiter="|" --repeats=80000
{
"content": "8=FIX.5.0SP2|9=0000430|35=W|49=init-comp|56=accept-comp|115=DTS|34=3187|57=fix|145=NCFX1_FIX50|52=20231031-20:51:03.893|122=20231030-10:18:33.507|262=EURJPY10Y=TDS|48=EURJPY10Y=TDS|22=8|167=FXFWD|762=NONE|15=EUR|20460=EUZZ02X3F8ZYPHZZZZ|268=3|269=0|278=2971|270=-3657.05|271=0|272=20231030|273=10:18:33.507|336=1|269=1|278=2972|270=-3590.35|271=0|272=20231030|273=10:18:33.507|336=1|269=H|278=2973|270=-3623.7|272=20231030|273=10:18:33.507|336=1|10=052|",
"view": "[0] 8 (BeginString) = FIX.5.0SP2[FIX.5.0SP2], [1] 9 (BodyLength) = 0000430\r\n[2] 35 (MsgType) = W[MarketDataSnapshotFullRefresh], [3] 49 (SenderCompID) = init-comp\r\n[4] 56 (TargetCompID) = accept-comp, [5] 115 (OnBehalfOfCompID) = DTS\r\n[6] 34 (MsgSeqNum) = 3187, [7] 57 (TargetSubID) = fix\r\n[8] 145 (DeliverToLocationID) = NCFX1_FIX50, [9] 52 (SendingTime) = 20231031-20:51:03.893\r\n[10] 122 (OrigSendingTime) = 20231030-10:18:33.507, [11] 262 (MDReqID) = EURJPY10Y=TDS\r\n[12] 48 (SecurityID) = EURJPY10Y=TDS, [13] 22 (SecurityIDSource) = 8[ExchangeSymbol]\r\n[14] 167 (SecurityType) = FXFWD[FxForward], [15] 762 (SecuritySubType) = NONE\r\n[16] 15 (Currency) = EUR, [17] 20460 (InstrumentCustom) = EUZZ02X3F8ZYPHZZZZ\r\n[18] 268 (NoMDEntries) = 3, [19] 269 (MDEntryType) = 0[Bid]\r\n[20] 278 (MDEntryID) = 2971, [21] 270 (MDEntryPx) = -3657.05\r\n[22] 271 (MDEntrySize) = 0, [23] 272 (MDEntryDate) = 20231030\r\n[24] 273 (MDEntryTime) = 10:18:33.507, [25] 336 (TradingSessionID) = 1[Day]\r\n[26] 269 (MDEntryType) = 1[Offer], [27] 278 (MDEntryID) = 2972\r\n[28] 270 (MDEntryPx) = -3590.35, [29] 271 (MDEntrySize) = 0\r\n[30] 272 (MDEntryDate) = 20231030, [31] 273 (MDEntryTime) = 10:18:33.507\r\n[32] 336 (TradingSessionID) = 1[Day], [33] 269 (MDEntryType) = H[MidPrice]\r\n[34] 278 (MDEntryID) = 2973, [35] 270 (MDEntryPx) = -3623.7\r\n[36] 272 (MDEntryDate) = 20231030, [37] 273 (MDEntryTime) = 10:18:33.507\r\n[38] 336 (TradingSessionID) = 1[Day], [39] 10 (CheckSum) = 052",
"msg_type": "W",
"iterations": 80000,
"elapsed_ms": 590,
"fields": 40,
"content_length": 453,
"micros_per_msg": 7.375,
"chars_per_second": 61423729,
"fields_per_second": 5423729
}
Quickfix XML Trim
produce a version of quickfix from src file only including the specified messages and supporting fields. Will also decorate fields with tag ids for easier readability. All components, fields and groups included to support just those messages.
node dist/jsfix-cmd --dict=qf44 --trim --type="0,1,2,3,4,5,AE"
<message name='Reject' msgcat='admin' msgtype='3'>
<!-- 45 SEQNUM -->
<field name='RefSeqNum' required='Y'/>
<!-- 371 INT -->
<field name='RefTagID' required='N'/>
<!-- 372 STRING -->
<field name='RefMsgType' required='N'/>
<!-- 373 INT -->
<field name='SessionRejectReason' required='N'/>
<!-- 58 STRING -->
<field name='Text' required='N'/>
<!-- 354 LENGTH -->
<field name='EncodedTextLen' required='N'/>
<!-- 355 DATA -->
<field name='EncodedText' required='N'/>
</message>
note programatically
async function getTrimDefinitions (types: string[]): Promise<FixDefinitions> {
const builder = new QuickFixXmlFileBuilder(definitions)
builder.write(types)
const d = builder.elasticBuffer.toString()
const parser = new QuickFixXmlFileParser(() => new StringDuplex(d), () => new EmptyLogger())
return await parser.parse()
}
est('check builder', async () => {
const msgTypes = ['0', '1', '2', '3', '4', '5', 'AE']
const newdDefinitions = await getTrimDefinitions(msgTypes)
expect(newdDefinitions).toBeTruthy()
msgTypes.forEach(mt => {
const m = newdDefinitions.message.get(mt)
expect(m).toBeTruthy()
})
})