The below is not so much as a test but rather a sample code how to deal with a FIX 4.4 message – for those who need to quick start.
The code is self-explanatory – that is if you read the FIX Protocol documentation.
package test.fix;
import static org.junit.Assert.*;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.junit.Test;
import quickfix.ConfigError;
import quickfix.DataDictionary;
import quickfix.Field;
import quickfix.FieldConvertError;
import quickfix.FieldMap;
import quickfix.FieldNotFound;
import quickfix.FieldType;
import quickfix.Group;
import quickfix.InvalidMessage;
import quickfix.Message;
import quickfix.MessageUtils;
import quickfix.Message.Header;
import quickfix.field.LastQty;
import quickfix.field.MsgType;
import quickfix.field.PossDupFlag;
import quickfix.field.PossResend;
import quickfix.field.SecurityID;
import quickfix.field.TargetCompID;
public class TestFix1 {
// http://www.quickfixj.org/downloads/
private final char ONE = '\001';
@Test
public void test() throws ConfigError, InvalidMessage, FieldNotFound, IOException {
// 8=FIX.4.4^9=297^35=AE^49=AIM5^56=TILQ^17=905881^22=1^31=275.121547^32=18100^38=4979700^48=SMF6^55=SMF6^60=20151210-18:33:49.000^64=20151210^75=20151210^150=F^487=0^570=N^571=905881^552=1^54=2^37=905881^11=TMC-298.1561^1=GTAACORE^136=3^137=-135.75^138=USD^139=3^891=0^137=-1.81^138=USD^139=4^891=0^137=-497.75^138=USD^139=7^891=0^10=124^
// 8=FIX.4.4^9=412^35=AE^34=3611^49=TILQ^52=20151210-18:35:59^56=GCS2^6=275.121547^14=18100^17=905881^22=1^31=275.121547^32=18100^38=4979700^39=1^48=SMF6^55=SMF6^60=20151210-18:33:49.000^64=20151210^75=20151210^150=F^151=4961600^487=0^570=N^571=TMC-298.1568^552=1^54=2^37=TMC-298.1561^11=TMCBBG-TICKET-2015-12-10-905881^1=GTAACORE^136=3^137=-135.75^138=USD^139=3^891=0^137=-1.81^138=USD^139=4^891=0^137=-497.75^138=USD^139=7^891=0^10=017^
String messageStringHR="8=FIX.4.4^9=195^35=AE^49=AIM5^56=TILQ^17=740223^22=8^31=99.5^32=492200000^"
+ "38=489739000^48=US912828L997^55=T^60=20151030-17:42:59.000^64=20151102^75=20151030^150=F^"
+ "487=0^570=N^571=740223^552=1^54=1^37=740223^11=TMC-270.2^1=NT7H^10=126^";
messageStringHR="8=FIX.4.4^9=297^35=AE^49=AIM5^56=TILQ^17=905881^22=1^31=275.121547^32=18100^38=4979700^"
+ "48=SMF6^55=SMF6^60=20151210-18:33:49.000^64=20151210^75=20151210^150=F^487=0^570=N^571=905881^552=1^"
+ "54=2^37=905881^11=TMC-298.1561^1=GTAACORE^136=3^137=-135.75^138=USD^139=3^891=0^137=-1.81^138=USD^"
+ "139=4^891=0^137=-497.75^138=USD^139=7^891=0^10=124^";
String messageString=messageStringHR.replace('^', ONE);
DataDictionary dataDict = DataDictionaryUtils.loadDataDictionary("TIL_CORE_FIX44.xml");
FixMessagePrinter fixMessagePrinter = new FixMessagePrinter();
Message message = DataDictionaryUtils.parseRawFixMsg(dataDict, messageString, false);
System.out.printf("Raw String:%n%n%s%n%n", messageString);
System.out.printf("FixMsgObj:%n%n%s%n", fixMessagePrinter.print(message, dataDict, 4));
System.out.println("LastQty="+message.getDecimal(LastQty.FIELD));
FieldMapReader r = new FieldMapReader(message);
System.out.println("TargetCompID="+r.getDecimal(TargetCompID.FIELD));
}
}
// Helper classes
class FixMessagePrinter {
private int m_indent = 4;
private static final String CR = System.getProperty("line.separator");
/**
* Default constructor
*/
public FixMessagePrinter() { }
/**
* @return the indent
*/
public int getIndent() {
return m_indent;
}
/**
* @param indent
* the indent to set
*/
public void setIndent(int indent) {
m_indent = indent;
}
/**
*
* @param message
* @return
*/
public String print(Message message) {
return print(message, null, m_indent);
}
/**
*
* @param message
* @param dataDictionary
* @param indent space indentation for each level
* @return
*/
public String print(Message message, DataDictionary dataDictionary, int indent)
{
StringBuffer writer = new StringBuffer();
String levelSpacing = getIndentSpacing(indent);
printFields(writer, "", levelSpacing, "header", message.getHeader(), dataDictionary);
printFields(writer, "", levelSpacing, "body", message, dataDictionary);
printFields(writer, "", levelSpacing, "trailer", message.getTrailer(), dataDictionary);
return writer.toString();
}
private String getIndentSpacing(int indent)
{
StringBuffer sb = new StringBuffer(indent);
for (int i=0; i < indent; i++) {
sb.append(" ");
}
return sb.toString();
}
private void printFields(StringBuffer writer, String currentSpacing, String levelSpacing, String section, FieldMap fieldMap, DataDictionary dataDictionary)
{
Iterator<Field<?>> fieldItr = fieldMap.iterator();
while (fieldItr.hasNext()) {
Field<?> field = fieldItr.next();
if (isGroupCountField(dataDictionary, field)) {
// skip group count field
continue;
}
formatOneField(writer, currentSpacing, field.getTag(), field.getObject().toString(), dataDictionary);
}
// group
// noGroup(tagID) = groupCount
//
// field 1 = a
// field 2 = b
// field 1 = d
// field 2 = e
Iterator<Integer> groupKeyItr = fieldMap.groupKeyIterator();
while (groupKeyItr.hasNext()) {
int groupTagID = ((Integer) groupKeyItr.next()).intValue();
int groupCount = fieldMap.getGroupCount(groupTagID);
writer.append(CR);
formatOneField(writer, currentSpacing, groupTagID, Integer.toString(groupCount), dataDictionary);
// group elements
List<Group> groups = fieldMap.getGroups(groupTagID);
Iterator<Group> groupItr = groups.iterator();
final String nextLevelSpacing = currentSpacing + levelSpacing;
while (groupItr.hasNext()) {
Group group = (Group) groupItr.next();
writer.append(CR);
printFields(writer, nextLevelSpacing, levelSpacing, "group-" + groupTagID, group, dataDictionary);
}
}
}
private boolean isGroupCountField(DataDictionary dd, Field<?> field) {
if (dd == null) {
return false;
}
FieldType fieldTypeEnum = dd.getFieldTypeEnum(field.getTag());
return fieldTypeEnum != null && fieldTypeEnum == FieldType.NumInGroup;
}
/**
* @param writer
* @param field
* @param dataDictionary
*/
private void formatOneField(StringBuffer writer, String currentSpacing, int tagID, String tagValue, DataDictionary dataDictionary) {
// tagName(tagID) = "abc def"
// tagName(tagID) = enumName(1)
writer.append(currentSpacing);
String tagName = null;
String enumName = null;
if (dataDictionary != null) {
tagName = dataDictionary.getFieldName(tagID);
enumName = dataDictionary.getValueName(tagID, tagValue);
}
if (tagName == null) {
/// tagID =
writer.append(tagID);
} else {
// tagName(tagID) =
writer.append(tagName).append("(").append(tagID).append(")");
}
writer.append(" = ");
if (enumName == null) {
// "value"
writer.append("\"").append(tagValue).append("\"");
} else {
// enumName(value)
writer.append(enumName).append("(").append(tagValue).append(")");
}
writer.append(CR);
}
}
/**
* QuickFixJ getter wrapper to get default value instead of FieldNotFound
* Exception
*
*/
class FieldMapReader {
private FieldMap m_fieldMap = null;
private String m_defaultString = null;
private BigDecimal m_defaultDecimal = null;
private BigInteger m_defaultBigInt = null;
private Integer m_defaultInteger = null;
private double m_defaultDouble = 0;
private int m_defaultInt = 0;
private char m_defaultChar = '\u0000';
private boolean m_defaultBoolean = false;
private Date m_defaultDate = null;
private DateTimeFormatter m_localDateFormat = DateTimeFormat.forPattern("yyyyMMdd");
private DateTimeFormatter m_localTimeFormat = DateTimeFormat.forPattern("HH:mm:ss");
private DateTimeFormatter m_localTimeMillisFormat = DateTimeFormat.forPattern("HH:mm:ss.SSS");
private DateTimeFormatter m_localTimestampFormat = DateTimeFormat.forPattern("yyyyMMdd-HH:mm:ss");
private DateTimeFormatter m_localTimestampMillisFormat = DateTimeFormat.forPattern("yyyyMMdd-HH:mm:ss.SSS");
private Header m_header = null;
private FieldMapReader m_headerReader = null;
public FieldMapReader() {
}
public FieldMapReader(FieldMap fieldMap) {
setFieldMap(fieldMap);
}
/**
* @return the fieldMap
*/
public FieldMap getFieldMap() {
return m_fieldMap;
}
/**
* @param fieldMap
* the fieldMap to set
*/
public void setFieldMap(FieldMap fieldMap) {
m_fieldMap = fieldMap;
if (fieldMap instanceof Message) {
m_header = ((Message) fieldMap).getHeader();
m_headerReader = new FieldMapReader(m_header);
}
}
/**
* @return the headerReader
*/
public FieldMapReader getHeaderReader() {
return m_headerReader;
}
/**
*
* @param mustExist
* @return header msgType
*/
public String getMsgType(boolean mustExist) {
return getMsgType(getHeaderReader(), mustExist);
}
/**
*
* @param headerReader
* @param mustExist
* @return
*/
public static String getMsgType(FieldMapReader headerReader, boolean mustExist) {
if (headerReader == null) {
throw new RuntimeException("headerReader is null");
} else {
return headerReader.getString(MsgType.FIELD, mustExist);
}
}
/**
* @return the defaultBigInt
*/
public BigInteger getDefaultBigInt() {
return m_defaultBigInt;
}
/**
* @param defaultBigInt
* the defaultBigInt to set
*/
public void setDefaultBigInt(BigInteger defaultBigInt) {
m_defaultBigInt = defaultBigInt;
}
/**
* @return the defaultInteger
*/
public Integer getDefaultInteger() {
return m_defaultInteger;
}
/**
* @param defaultInteger
* the defaultInteger to set
*/
public void setDefaultInteger(Integer defaultInteger) {
m_defaultInteger = defaultInteger;
}
/**
* @return the defaultInt
*/
public int getDefaultInt() {
return m_defaultInt;
}
/**
* @param defaultInt
* the defaultInt to set
*/
public void setDefaultInt(int defaultInt) {
m_defaultInt = defaultInt;
}
/**
* @return the defaultString
*/
public String getDefaultString() {
return m_defaultString;
}
/**
* @param defaultString
* the defaultString to set
*/
public void setDefaultString(String defaultString) {
m_defaultString = defaultString;
}
/**
* @return the defaultDecimal
*/
public BigDecimal getDefaultDecimal() {
return m_defaultDecimal;
}
/**
* @param defaultDecimal
* the defaultDecimal to set
*/
public void setDefaultDecimal(BigDecimal defaultDecimal) {
m_defaultDecimal = defaultDecimal;
}
/**
* @return the defaultDouble
*/
public double getDefaultDouble() {
return m_defaultDouble;
}
/**
* @param defaultDouble
* the defaultDouble to set
*/
public void setDefaultDouble(double defaultDouble) {
m_defaultDouble = defaultDouble;
}
/**
* @return the defaultChar
*/
public char getDefaultChar() {
return m_defaultChar;
}
/**
* @param defaultChar
* the defaultChar to set
*/
public void setDefaultChar(char defaultChar) {
m_defaultChar = defaultChar;
}
/**
* @return the defaultBoolean
*/
public boolean isDefaultBoolean() {
return m_defaultBoolean;
}
/**
* @param defaultBoolean
* the defaultBoolean to set
*/
public void setDefaultBoolean(boolean defaultBoolean) {
m_defaultBoolean = defaultBoolean;
}
/**
* @return
* @see quickfix.FieldMap#isEmpty()
*/
public boolean isEmpty() {
return m_fieldMap.isEmpty();
}
/**
* @param field
* @return
* @throws FieldNotFound
* @see quickfix.FieldMap#getString(int)
*/
public String getString(int field) {
return getString(field, m_defaultString);
}
/**
* @param field
* @return
* @throws FieldNotFound
* @see quickfix.FieldMap#getString(int)
*/
public String getString(int field, String defaultValue) {
try {
return m_fieldMap.getString(field);
} catch (FieldNotFound e) {
return defaultValue;
}
}
/**
* @param field
* @param mustExist
* true – field must exist, false – optional field
* @return
*/
public String getString(int field, boolean mustExist) {
try {
return m_fieldMap.getString(field);
} catch (FieldNotFound e) {
if (mustExist) {
throw new RuntimeException(e);
} else {
return m_defaultString;
}
}
}
/**
* @param field
* @return
* @throws FieldNotFound
* @see quickfix.FieldMap#getBoolean(int)
*/
public Boolean getBoolean(int field) {
return getBoolean(field, m_defaultBoolean);
}
/**
* @param field
* @param defaultValue
* @return
* @see quickfix.FieldMap#getBoolean(int)
*/
public Boolean getBoolean(int field, Boolean defaultValue) {
try {
return m_fieldMap.getBoolean(field);
} catch (FieldNotFound e) {
return defaultValue;
}
}
/**
* @param field
* @return
* @throws FieldNotFound
* @see quickfix.FieldMap#getChar(int)
*/
public char getChar(int field) {
try {
return m_fieldMap.getChar(field);
} catch (FieldNotFound e) {
return m_defaultChar;
}
}
/**
* @param field
* @param mustExist
* true – field must exist, false – optional field
* @return
*/
public char getChar(int field, boolean mustExist) {
try {
return m_fieldMap.getChar(field);
} catch (FieldNotFound e) {
if (mustExist) {
throw new RuntimeException(e);
} else {
return m_defaultChar;
}
}
}
/**
* @param field
* @return
* @throws FieldNotFound
* @see quickfix.FieldMap#getDouble(int)
*/
public double getDouble(int field) {
try {
return m_fieldMap.getDouble(field);
} catch (FieldNotFound e) {
return m_defaultDouble;
}
}
/**
* @param field
* @return
* @throws FieldNotFound
* @see quickfix.FieldMap#getInt(int)
*/
public int getInt(int field) {
try {
return m_fieldMap.getInt(field);
} catch (FieldNotFound e) {
return m_defaultInt;
}
}
/**
* @param field
* @return
* @throws FieldNotFound
* @see quickfix.FieldMap#getDecimal(int)
*/
public BigDecimal getDecimal(int field) {
return getDecimal(field, false);
}
/**
* @param field
* @return
* @throws FieldNotFound
* @see quickfix.FieldMap#getDecimal(int)
*/
public BigDecimal getDecimal(int field, boolean mustExist) {
return getDecimal(field, mustExist, m_defaultDecimal);
}
/**
*
* @param field
* @param mustExist
* @param defaultValue
* @return
*/
public BigDecimal getDecimal(int field, boolean mustExist, BigDecimal defaultValue) {
try {
return m_fieldMap.getDecimal(field);
} catch (FieldNotFound e) {
if (mustExist) {
throw new RuntimeException(e);
} else {
return defaultValue;
}
}
}
/**
* @param field
* @return
* @throws FieldNotFound
*/
public Integer getInteger(int field) {
return getInteger(field, false);
}
/**
* @param field
* @param mustExist
* true – field must exist, false – optional field
* @return
*/
public Integer getInteger(int field, boolean mustExist) {
return getInteger(field, mustExist, m_defaultInteger);
}
/**
* @param field
* @param mustExist
* true – field must exist, false – optional field
* @param defaultValue
* @return
*/
public Integer getInteger(int field, boolean mustExist, Integer defaultValue) {
String value = null;
try {
value = m_fieldMap.getString(field);
return new Integer(value);
} catch (FieldNotFound e) {
if (mustExist) {
throw new RuntimeException(e);
} else {
return defaultValue;
}
}
}
/**
* @param field
* @param mustExist
* true – field must exist, false – optional field
* @param defaultValue
* @return
*/
public Long getLong(int field, boolean mustExist, Long defaultValue) {
String value = null;
try {
value = m_fieldMap.getString(field);
return new Long(value);
} catch (FieldNotFound e) {
if (mustExist) {
throw new RuntimeException(e);
} else {
return defaultValue;
}
}
}
/**
* @param field
* @return
* @throws FieldNotFound
*/
public BigInteger getBigInt(int field) {
return getBigInt(field, false);
}
/**
* @param field
* @param mustExist
* true – field must exist, false – optional field
* @return
*/
public BigInteger getBigInt(int field, boolean mustExist) {
String value = null;
try {
value = m_fieldMap.getString(field);
return convertToBigInteger(value);
} catch (FieldNotFound e) {
if (mustExist) {
throw new RuntimeException(e);
} else {
return m_defaultBigInt;
}
} catch (FieldConvertError e) {
throw newBadFieldException(field, value, e);
}
}
/**
* @param field
* @return
* @throws FieldNotFound
* @see quickfix.FieldMap#getUtcTimeStamp(int)
*/
public Date getUtcTimeStamp(int field) {
try {
return m_fieldMap.getUtcTimeStamp(field);
} catch (FieldNotFound e) {
return m_defaultDate;
}
}
/**
* @param field
* @return
* @throws FieldNotFound
* @see quickfix.FieldMap#getUtcTimeOnly(int)
*/
public Date getUtcTimeOnly(int field) {
try {
return m_fieldMap.getUtcTimeOnly(field);
} catch (FieldNotFound e) {
return m_defaultDate;
}
}
/**
* @param field
* @return
* @throws FieldNotFound
* @see quickfix.FieldMap#getUtcDateOnly(int)
*/
public Date getUtcDateOnly(int field) {
try {
return m_fieldMap.getUtcDateOnly(field);
} catch (FieldNotFound e) {
return m_defaultDate;
}
}
/**
* @param field
* @return
* @see quickfix.FieldMap#isSetField(int)
*/
public boolean isSetField(int field) {
return m_fieldMap.isSetField(field);
}
/**
* @param field
* @return
* @see quickfix.FieldMap#getGroups(int)
*/
// public List<Group> getGroups(int field)
// {
// return m_fieldMap.getGroups(field);
// }
/**
*
* @param field
* @return group as {@link FieldMapReader}
*/
public List<FieldMapReader> getGroups(int field) {
List<FieldMapReader> list = new ArrayList<FieldMapReader>();
List<Group> l = m_fieldMap.getGroups(field);
for (Group group : l) {
list.add(new FieldMapReader(group));
}
return list;
}
/**
*
* @param field
* @return
*/
public Date getLocalDateOnly(int field) {
// always pick the second formatter
return getDateField(field, 0, m_localDateFormat, m_localDateFormat);
}
/**
*
* @param field
* @return
*/
public Date getLocalTimeOnly(int field) {
// HH:mm:ss (8) vs HH:mm:ss.SSS
return getDateField(field, 8, m_localTimeFormat, m_localTimeMillisFormat);
}
/**
*
* @param field
* @return
*/
public Date getLocalTimeStamp(int field) {
// yyyyMMdd-HH:mm:ss (17) vs yyyyMMdd-HH:mm:ss.SSS
return getDateField(field, 17, m_localTimestampFormat, m_localTimestampMillisFormat);
}
/**
*
* @param field
* @param valueLen1
* value length for choosing formatter1
* @param formatter1
* @param formatter2
* for value length != valueLen1
* @return date based on supplied formatter
*/
private Date getDateField(int field, int strLen1, DateTimeFormatter formatter1, DateTimeFormatter formatter2) {
String value = null;
try {
value = m_fieldMap.getString(field);
DateTimeFormatter formatter = value.length() == strLen1 ? formatter1 : formatter2;
return formatter.parseDateTime(value).toDate();
} catch (FieldNotFound e) {
return m_defaultDate;
} catch (Exception e) {
throw newBadFieldException(field, value, e);
}
}
private RuntimeException newBadFieldException(int field, String value, Exception e)
{
String message = "invalid field value = [" +
value == null ? "" : value +
"] for tag = [" + field + "]";
return new RuntimeException(message, e);
}
/**
* Convert a String to a BigInteger.
* @param value the String to convert
* @return the converted integer
* @throws FieldConvertError raised if the String does not represent a
* valid integer.
* @see java.lang.Integer#parseInt(String)
*/
private static BigInteger convertToBigInteger(String value) throws FieldConvertError {
try {
for (int i = 0; i < value.length(); i++) {
if (!Character.isDigit(value.charAt(i)) && !(i == 0 && value.charAt(i) == '-')) {
throw new FieldConvertError("invalid integral value: " + value);
}
}
return new BigInteger(value);
} catch (NumberFormatException e) {
throw new FieldConvertError("invalid integral value: " + value + ": " + e);
}
}
/**
*
* @return is either PossDupFlag (tag 43) is set or PossResend (tag 97) is
* set
*/
public boolean isPossDupOrPossResend() {
return getHeaderReader().getBoolean(PossDupFlag.FIELD, false)
|| getHeaderReader().getBoolean(PossResend.FIELD, false);
}
/**
* return tagID if possDup flag is set or possResend flag is set in the
* header
*
* @return null if none of the flag is set to true
*/
public Integer getPossDupOrPossResendTagIfSet() {
return getPossDupOrPossResendTagIfSet(getHeaderReader());
}
/**
* return tagID if possDup flag is set or possResend flag is set in the
* headerReader
*
* @param headerReader
* @return
*/
public static Integer getPossDupOrPossResendTagIfSet(FieldMapReader headerReader) {
if (headerReader == null) {
return null;
} else if (headerReader.getBoolean(PossDupFlag.FIELD, false)) {
return PossDupFlag.FIELD;
} else if (headerReader.getBoolean(PossResend.FIELD, false)) {
return PossResend.FIELD;
} else {
return null;
}
}
}
final class DataDictionaryUtils {
private DataDictionaryUtils() { }
/**
* load the data dictionary from the resource on the classpath
* @param resourceName
* @return
* @throws ConfigError
* @throws IOException
*/
public static DataDictionary loadDataDictionary(String resourceName) throws ConfigError, IOException
{
InputStream ddStream = getResourceInputStream(resourceName);
if (ddStream == null) {
throw new RuntimeException("Unable to load data dictionary from resource = [" + resourceName + "]");
}
DataDictionary dataDictionary = loadDataDictionary(ddStream);
return dataDictionary;
}
private static InputStream getResourceInputStream(String resourceName) throws IOException {
Path path = FileSystems.getDefault().getPath(".", resourceName);
Files.newInputStream(path, StandardOpenOption.READ);
return null;
}
/**
* Load the data dictionary from the input stream
*
* @param inputStream
* @return
* @throws ConfigError
*/
public static DataDictionary loadDataDictionary(InputStream inputStream) throws ConfigError {
DataDictionary dataDictionary = new DataDictionary(inputStream);
// remember to set setCheckUnorderedGroupFields to false or else
// message generated from quickfix (which is out of order inside group)
// will not
// be parsed back by quick fix
dataDictionary.setCheckUnorderedGroupFields(false);
return dataDictionary;
}
/**
* parse raw FIX message and return the QuickFix Message Object Once you get
* the quickFix message object, you can use the following codes to print the
* FIX message:
*
* <pre>
* <code>
* m_fixMessagePrinter.print(msgObj, m_dataDictionary, indentation);
* </code>
* </pre>
*
* @param dataDictionary
* see {@link #loadDataDictionary(String)} or
* {@link #loadDataDictionary(InputStream)}
* @param rawMessage
* raw FIX message string in SOH format
* @param doValidation
* true – validate FIX message according to dataDictionary; false
* – no validation
* @return quickFix message object
* @throws InvalidMessage
*/
public static Message parseRawFixMsg(DataDictionary dataDictionary, String rawMessage, boolean doValidation)
throws InvalidMessage {
if (dataDictionary == null || rawMessage == null) {
return null;
}
Message msgObj = new Message();
msgObj.fromString(rawMessage, dataDictionary, doValidation);
return msgObj;
}
}