package org.marketcetera.orderloader.fix; import org.marketcetera.util.misc.ClassVersion; import org.marketcetera.util.log.I18NBoundMessage1P; import org.marketcetera.util.log.I18NBoundMessage2P; import org.marketcetera.trade.Order; import org.marketcetera.trade.Factory; import org.marketcetera.trade.BrokerID; import org.marketcetera.trade.FIXOrder; import static org.marketcetera.orderloader.Messages.*; import org.marketcetera.orderloader.RowProcessor; import org.marketcetera.orderloader.OrderProcessor; import org.marketcetera.orderloader.OrderParsingException; import org.marketcetera.orderloader.Messages; import org.marketcetera.quickfix.FIXMessageFactory; import org.marketcetera.quickfix.FIXDataDictionary; import org.marketcetera.quickfix.FIXVersion; import org.marketcetera.quickfix.FIXFieldConverterNotAvailable; import quickfix.*; import quickfix.field.*; import java.util.Vector; import java.math.BigDecimal; /* $License$ */ /** * The processor that parses rows into FIX Messages. * * @author gmiller * @author toli * @author anshul@marketcetera.com * @version $Id: FIXProcessor.java 16795 2014-01-08 00:45:23Z colin $ * @since 1.0.0 */ @ClassVersion("$Id: FIXProcessor.java 16795 2014-01-08 00:45:23Z colin $") public class FIXProcessor extends RowProcessor { /** * Creates an instance. * * @param inProcessor the processor. * @param inBrokerID the brokerID. * @param inFIXVersion the FIX Version to use for parsing, constructing * and validating messages. * * @throws org.marketcetera.orderloader.OrderParsingException if the * supplied FIX Version cannot be supported or * if brokerID was not supplied. */ public FIXProcessor(OrderProcessor inProcessor, BrokerID inBrokerID, FIXVersion inFIXVersion) throws OrderParsingException { super(inProcessor, inBrokerID); mMsgFactory = inFIXVersion.getMessageFactory(); try { mDictionary = new FIXDataDictionary(inFIXVersion.getDataDictionaryURL()); } catch (FIXFieldConverterNotAvailable e) { throw new OrderParsingException(e, new I18NBoundMessage1P( Messages.ERROR_PROCESS_FIX_VERSION, inFIXVersion.toString())); } if(inBrokerID == null) { throw new OrderParsingException(Messages.BROKER_ID_REQUIRED); } } @Override public void setHeaders(String[] inHeaders) throws OrderParsingException { mHeaderNames = inHeaders; mHeaderFields = new Vector<Field<?>>(inHeaders.length); for(String field : inHeaders) { mHeaderFields.add(getQuickFixFieldFromName(field)); } } @Override protected Order parseOrder(String[] inRow) throws OrderParsingException { Message message = mMsgFactory.newBasicOrder(); // set defaults first b/c they may be overridden for MKT orders addDefaults(message); for(int i=0;i<mHeaderFields.size();i++) { Field<?> theField = mHeaderFields.get(i); String value = parseMessageValue(theField, mHeaderNames[i], inRow[i], message); if(value!=null) { int fieldID = theField.getField(); if(mDictionary.getDictionary().isMsgField( MsgType.ORDER_SINGLE, fieldID)) { message.setField(new StringField(fieldID, value)); } else if(mDictionary.getDictionary().isHeaderField(fieldID)) { message.getHeader().setField(new StringField(fieldID, value)); } else if(mDictionary.getDictionary().isTrailerField(fieldID)) { message.getTrailer().setField(new StringField(fieldID, value)); } else { throw new OrderParsingException(new I18NBoundMessage2P( PARSING_FIELD_NOT_IN_DICT, String.valueOf(fieldID), value)); } } } try { //Create the order to have the ClOrdID assigned. FIXOrder order = Factory.getInstance().createOrder(message, geBrokerID()); mDictionary.getDictionary().validate(order.getMessage(), true); return order; } catch (Exception e) { throw new OrderParsingException(e, new I18NBoundMessage1P( Messages.PARSED_MESSAGE_FAILED_VALIDATION, message.toString())); } } /** Translate the incoming field name from String to a FIX standard * using reflection. the quickfix.field package has all of these defined * as quickfix.field.<Name> so we just need to create a class for each * english string. * * If the field is not found, it could be a "undertermined" field in * which case we check to see if it parses out to an integer. * If it does, we store the int as the field value * Otherwise, we throw an error. * * @param inFieldName the field name. * * @return the field instance represeting that field * * @throws OrderParsingException if there were failures */ protected Field<?> getQuickFixFieldFromName(String inFieldName) throws OrderParsingException { Field<?> theField = null; try { theField = (Field<?>) Class.forName("quickfix.field." + //$NON-NLS-1$ inFieldName).newInstance(); } catch(ClassNotFoundException ex) { // check to see if this is non-predetermined value (just an int header) return CustomField.getCustomField(inFieldName); }catch(Exception ex) { throw new OrderParsingException(ex, new I18NBoundMessage1P( Messages.ERROR_PARSING_UNKNOWN, inFieldName)); } return theField; } /** * For some fields (day, side, etc) we do custom lookups since * the orders may be "DAY", MKT (for price), etc * For all others, delegate to the basic field type lookup * * @param inField the field we are converting * @param inFieldName the name of the field being processed. * @param inValue string value * @param inMessage the message to add the processed field to. * * @return Translated data * * @throws OrderParsingException if there were errors parsing the field. */ protected String parseMessageValue(Field<?> inField, String inFieldName, String inValue, Message inMessage) throws OrderParsingException { if(inField instanceof CustomField) { return ((CustomField<?>)inField).parseMessageValue(inValue).toString(); //i18n_number? BigDecimal.toString() might not give the right value } switch(inField.getField()) { case Side.FIELD: return getSide(inValue)+""; //$NON-NLS-1$ case Price.FIELD: // price must be positive but can be MKT if(MKT_PRICE.equals(inValue)) { inMessage.setField(new OrdType(OrdType.MARKET)); return null; } else { BigDecimal price = null; try { price = new BigDecimal(inValue);//i18n_currency } catch(NumberFormatException ex) { throw new OrderParsingException(ex, new I18NBoundMessage1P(PARSING_PRICE_VALID_NUM, inValue)); } if(price.compareTo(BigDecimal.ZERO) <= 0) { throw new OrderParsingException(new I18NBoundMessage1P( PARSING_PRICE_POSITIVE, price)); } // just return the original string return inValue; } case OrderQty.FIELD: // quantity must be a positive integer Integer qty= null; try { qty = Integer.parseInt(inValue);//i18n_number } catch(NumberFormatException ex) { throw new OrderParsingException(ex, new I18NBoundMessage1P(PARSING_QTY_INT, inValue)); } if(qty <=0) { throw new OrderParsingException(new I18NBoundMessage1P( PARSING_QTY_POS_INT, inValue)); } // just return the original string return inValue; case TimeInForce.FIELD: try { java.lang.reflect.Field theField = TimeInForce.class. getField(inValue); return theField.get(null).toString(); } catch (Exception ex) { throw new OrderParsingException(ex, inFieldName, inValue); } default: return inValue; } } protected static char getSide(String inValue) { if(inValue != null) { inValue = inValue.toUpperCase(); if("".equals(inValue)) { //$NON-NLS-1$ return Side.UNDISCLOSED; } if("B".equals(inValue)) { //$NON-NLS-1$ return Side.BUY; } if("S".equals(inValue)) { //$NON-NLS-1$ return Side.SELL; } if("SS".equals(inValue)) { //$NON-NLS-1$ return Side.SELL_SHORT; } if("SSE".equals(inValue)) { //$NON-NLS-1$ return Side.SELL_SHORT_EXEMPT; } } return Side.UNDISCLOSED; } protected static void addDefaults(Message message) { message.getHeader().setField(new MsgType(MsgType.ORDER_SINGLE)); message.setField(new OrdType(OrdType.LIMIT)); message.setField(new HandlInst( HandlInst.AUTOMATED_EXECUTION_ORDER_PRIVATE)); message.setField(new TransactTime()); //i18n_datetime } /** * Implementation artifact, exposed for unit testing. * * @return the fix fields parsed from the header. */ Vector<Field<?>> getHeaderFields() { return mHeaderFields; } /** * Implementation artifact, exposed for unit testing. * * @return the names of the header columns. */ String[] getHeaderNames() { return mHeaderNames; } protected static String MKT_PRICE = "MKT"; //$NON-NLS-1$ private String[] mHeaderNames; private Vector<Field<?>> mHeaderFields; private final FIXMessageFactory mMsgFactory; private final FIXDataDictionary mDictionary; }