How to work with repeating groups in FixEdge
Preface
FIXEdge offers solution for Business Rules purposed to provide routing, transferring, and data manipulations for FIX messages that go through the Business Layer of FIXEdge. See BL Scripting with JavaScript for more information.
Changing field values in repeating groups using JavaScript
This example describes how to change the Party Role (452) 's value from '1' (Executing Firm) to '17' (Contra Firm) in Execution Report (35=8).
Rule
The rule on the call of the JavaScript. Condition section sets up the condition for filtering messages by field MsgType (35) = 8.
<Rule Description="Save Parties for every incoming Execution Report (35=8)"> <Source Name=".*" /> <Condition> <MsgType> <Val>8</Val> </MsgType> </Condition> <Action> <Script Language="JavaScript" FileName ="updatePartyRole.js"/> <!-- Do other necessary transformations and actions--> <!-- ... --> </Action> </Rule>
See Rule element for more information
JavaScript
Further, use JavaScript "updatePartyRole.js" on repeating group transformation:
parties = getGroup(453); groupSize = getNumField(453); if ( (groupSize > 0) && isGroupValid(parties) ) // check if message is valid { for (i=0; i < groupSize; ++i) // Repeat for all members in the repeating group { if (getNumField(parties, i, 452) == 17) // If PartyRole in the first group equals '17' change it to '1' { setNumField(parties, i, 452, 1) // Notice, that numbering starts from zero. } } }
Storing repeating groups in Database
This example describes how to write the values from the Parties repeating group in Trade Capture Report (35=AE) to specific DB columns.
History Configuration
It is necessary to create the History definition in the Business Layer Configuration file and to register all the fields that are in the database table and in the order in which they are registered in the database.
<History Name="Parties" StorageType="ODBC" MaxNumberOfRecords="15000" TableName="Parties" ColumnSize="256" ConnectionString="DSN=TradeCap;UID= admin;Pwd=temp_pass;"> <KeyField ColumnName="TradeReportID" ColumnSize="128">571</KeyField> <KeyField ColumnName="PartyID" ColumnSize="256">448</KeyField> <Field ColumnName="PartyIDSource" ColumnSize="256">447</Field> <Field ColumnName="PartyRole" ColumnSize="256">452</Field> </History>
See Histories for more information
Rule
The rule on the call of the JavaScript
<Rule Description="Save Parties for every incoming AE message"> <Source Name=".*" /> <Condition> <EqualField Field="35" Value="AE" /> </Condition> <Action> <Script Language="JavaScript" FileName="TrdCapt.js" /> </Action> </Rule>
See Rule element for more information
JavaScript
JavaScript "TrdCapt.js" for storing in a DB
tags = new Array(447, 452); //In this array, we assign non-key tag numbers, below which the loop will go through this array with getStringField. partyKey = new Array(); //An array in which the key fields 571 and 448 will be stored partyKey.push(getStringField(571)); //We write down the first part of the key. In this example, this is TradeReportID hndl = getGroup(453); groupSize = getNumField(453); if (null != groupSize and isGroupValid(hndl)) { for (i = 0; i < groupSize; ++i) { partyData = new Array(); // An array for data to be stored in db. the values of the 447 and 452 tags entry = i; partyKey.push(getStringField(hndl, i, 448)); //Record the second part of the key, PartyID if (null == getRecordFromHistory("Parties", partyKey)) //Check if there is already such an entry in the database { for (j = 0; j < tags.length; ++j) // go through all fields to be stored. { // val = getStringField(hndl, i, tags[j]); if (undefined == val) val = ""; // missing tag = "", In the database for non-key fields it is desirable to enable write NULL partyData.push(val); } saveToHistory("Parties", partyKey, partyData, ""); // Save records to the database } else { //If such a record already exists in the database, then we write a notification and do nothing. print("Do nothing. Party duplicate has found in DB for " + partyKey); } } }
Messages with the same TradeReportID (571) and PartyID (448) tags are considered as duplicates and are filtered.
For changing this behavior, KeyFields in History configuration should be modified/extended
Removing block from a repeating groups
For removing repeating group block we recommend the following steps:
- Clear all fields from a group entry.
- Copy all fields from the last group entry.
- Resize group, i.e. set a new group size value to a specific tag.
Please note that this approach has certain limitations:
- Validation for repeating groups checks should be enabled. Messages with undefined tags and unexpected structure should be rejected or skipped.
- All possible fields should be handled in JavaScript. Unexpected tags should be restricted in fix dictionaries. Otherwise, JavaScript would make a mess with tags.
BL_Config.xml
The rule on the call of the JavaScript for removing a block from a repeating group.
<?xml version="1.0" encoding="UTF-8"?> <FIXEdge> <BusinessLayer> <Rule> <Source> <FixSession SenderCompID=".*" TargetCompID="FIXEDGE"/> </Source> <Action> <Script Language="JavaScript" FileName="FIXEdge1/conf/removePartyRole17.js"/> <Send Name="RECIPIENT" /> </Action> </Rule> <DefaultRule> <Action> <DoNothing/> </Action> </DefaultRule> </BusinessLayer> </FIXEdge>
JavaScript
Further, use JavaScript "removePartyRole17.js" for removing a block from a repeating group:
partiesGroupTags = new Array( 448, 447, 452, 2376 ); partiesNoCount = getNumField(453); partiesGrp = getGroup(453); partiesNewSize = partiesNoCount; print("[DEBUG] Initial message has " + partiesNoCount.toString() + " parties"); // for debugging if (0 < partiesNoCount && isGroupValid(partiesGrp)) { var i = 0; while (i < partiesNoCount && partiesNewSize > 0) { var partyRole = getNumField(partiesGrp, i, 452); if (partyRole == 17) { // Copy the last block to this position field by field if it exist for (var n=0; n < partiesGroupTags.length; ++n) { if( i == partiesNewSize-1) // is this is the last block { removeField(partiesGrp, i, partiesGroupTags[n]); } else { // partiesNewSize-1 - reference to the last block. lastBlockField = getStringField(partiesGrp, partiesNewSize-1, partiesGroupTags[n]) currentBlockField = getStringField(partiesGrp, i, partiesGroupTags[n]) print("[DEBUG] Current value of Field " + partiesGroupTags[n].toString() + " :" + currentBlockField + " Last Field: " + lastBlockField ); // for debugging if ( lastBlockField != null && currentBlockField != null) // swap values with last group and clean it. { swapFields(partiesGrp, partiesNewSize-1, partiesGroupTags[n], partiesGrp, i, partiesGroupTags[n]); removeField(partiesGrp, partiesNewSize-1, partiesGroupTags[n]); } else if ( lastBlockField != null) { setStringField(partiesGrp, i, partiesGroupTags[n], lastBlockField ); // assumed only field that can be converted to string can be absent } else // currentBlockField != null and should be removed. { removeField(partiesGrp, i, partiesGroupTags[n]); } } } --partiesNewSize; print("[DEBUG] Removed party block #" + i.toString() + " New parties size = " + partiesNewSize.toString() ); // for debugging // Do not increment i because copied block could have partyRole = 17 and should be rechecked. } else { ++i; } } if (partiesNewSize > 0) { setNumField(453, partiesNewSize); } else // remove parties if there are no entries; { removeField(453); } print("[DEBUG] New parties size = " + partiesNewSize.toString()); // for debugging }
Example of transformation
Sent message: 8=FIX.4.4|9=230|35=D|49=FIXEdge|56=FIXCLIENT|34=2|52=20180409-09:43:06.927|11=E2017000000000000000001|453=3|448=TestValue1|447=D|452=11|448=TestValue2|447=D|452=122|448=XYZT|447=D|452=17|1=EPAM-TEST|55=USD/CNH|54=1|15=USD|58=Remove block #3 of 3|10=045| Processed message: 8=FIX.4.4|9=208|35=D|49=FIXCLIENT|56=FIXEdge|34=2|52=20180409-09:43:06.930|11=E2017000000000000000001|453=2|448=TestValue1|447=D|452=11|448=TestValue2|447=D|452=122|1=EPAM-TEST|55=USD/CNH|54=1|15=USD|58=Remove block #3 of 3|10=140|