package org.marketcetera.orderloader.fix;
import org.marketcetera.util.unicode.SignatureCharset;
import static org.marketcetera.orderloader.Messages.*;
import org.marketcetera.orderloader.*;
import org.marketcetera.trade.Order;
import org.marketcetera.trade.BrokerID;
import org.marketcetera.trade.FIXOrder;
import org.marketcetera.quickfix.FIXVersion;
import org.marketcetera.core.ExpectedTestFailure;
import org.marketcetera.core.LoggerConfiguration;
import org.junit.Test;
import org.junit.BeforeClass;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import quickfix.field.*;
import quickfix.Message;
import quickfix.Field;
import java.util.List;
import java.util.Vector;
import java.util.Arrays;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/* $License$ */
/**
* Tests {@link FIXProcessor}
*
* @author Toli Kuznets
* @author anshul@marketcetera.com
* @version $Id: FIXProcessorTest.java 16154 2012-07-14 16:34:05Z colin $
* @since 1.0.0
*/
public class FIXProcessorTest {
@BeforeClass
public static void setupLogger() {
LoggerConfiguration.logSetup();
}
@Test
public void getSide()
{
assertEquals(Side.UNDISCLOSED, FIXProcessor.getSide(null));
assertEquals(Side.UNDISCLOSED, FIXProcessor.getSide(""));
assertEquals(Side.UNDISCLOSED, FIXProcessor.getSide("asdf"));
assertEquals(Side.BUY, FIXProcessor.getSide("b"));
assertEquals(Side.BUY, FIXProcessor.getSide("B"));
assertEquals(Side.SELL, FIXProcessor.getSide("S"));
assertEquals(Side.SELL, FIXProcessor.getSide("s"));
assertEquals(Side.SELL_SHORT, FIXProcessor.getSide("SS"));
assertEquals(Side.SELL_SHORT, FIXProcessor.getSide("ss"));
assertEquals(Side.SELL_SHORT_EXEMPT, FIXProcessor.getSide("SSE"));
assertEquals(Side.SELL_SHORT_EXEMPT, FIXProcessor.getSide("sse"));
}
@Test
public void addDefaults() throws Exception
{
Message msg = new Message();
FIXProcessor.addDefaults(msg);
assertEquals("msgType", MsgType.ORDER_SINGLE, msg.getHeader().getString(MsgType.FIELD));
assertEquals("orderType", OrdType.LIMIT, msg.getChar(OrdType.FIELD));
assertEquals("handlInst", HandlInst.AUTOMATED_EXECUTION_ORDER_PRIVATE, msg.getChar(HandlInst.FIELD));
}
@Test
public void basicFieldParsing() throws Exception
{
final Message msg =new Message();
FIXProcessor proc = create();
assertEquals(""+Side.UNDISCLOSED, proc.parseMessageValue(new Side(), "side", "z", msg));
assertEquals(""+Side.BUY, proc.parseMessageValue(new Side(), "side", "B", msg));
assertEquals(""+Side.SELL, proc.parseMessageValue(new Side(), "side", "S", msg));
}
@Test
public void priceParsing() throws Exception
{
final Message msg =new Message();
final FIXProcessor proc = create();
assertEquals(null, proc.parseMessageValue(new Price(), "Price", "MKT", msg));
assertEquals(OrdType.MARKET, msg.getChar(OrdType.FIELD));
assertEquals("42", proc.parseMessageValue(new Price(), "Price", "42", msg));
assertEquals("42.42", proc.parseMessageValue(new Price(), "Price", "42.42", msg));
(new ExpectedTestFailure(OrderParsingException.class,
PARSING_PRICE_POSITIVE.getText("-42")) {
protected void execute() throws Throwable
{
proc.parseMessageValue(new Price(), "price", "-42", msg);
}
}).run();
(new ExpectedTestFailure(OrderParsingException.class,
PARSING_PRICE_VALID_NUM.getText("toli")) {
protected void execute() throws Throwable
{
proc.parseMessageValue(new Price(), "price", "toli", msg);
}
}).run();
}
@Test
public void quantityParsing() throws Exception
{
final FIXProcessor proc = create();
final Message msg =new Message();
assertEquals("42", proc.parseMessageValue(new OrderQty(), "OrderQty", "42", msg));
(new ExpectedTestFailure(OrderParsingException.class,
PARSING_QTY_POS_INT.getText("-42")) {
protected void execute() throws Throwable
{
proc.parseMessageValue(new OrderQty(), "OrderQty", "-42", msg);
}
}).run();
(new ExpectedTestFailure(OrderParsingException.class,
PARSING_QTY_INT.getText("toli")) {
protected void execute() throws Throwable
{
proc.parseMessageValue(new OrderQty(), "OrderQty", "toli", msg);
}
}).run();
(new ExpectedTestFailure(OrderParsingException.class,
PARSING_QTY_INT.getText("42.2")) {
protected void execute() throws Throwable
{
proc.parseMessageValue(new OrderQty(), "OrderQty", "42.2", msg);
}
}).run();
}
@Test
public void getQuickfixFieldFromName() throws Exception
{
final FIXProcessor proc = create();
assertEquals(new Side(), proc.getQuickFixFieldFromName("Side"));
assertEquals(new OrderQty(), proc.getQuickFixFieldFromName("OrderQty"));
assertEquals(new CustomField<String>(1234, null), proc.getQuickFixFieldFromName("1234"));
(new ExpectedTestFailure(OrderParsingException.class, "ToliField") {
protected void execute() throws Throwable
{
proc.getQuickFixFieldFromName("ToliField");
}
}).run();
}
@Test
// verify that we get the right field order back when passed in
public void getFieldOrder() throws Exception
{
doVerifyFieldOrder(new Field[] {new Symbol(), new Side(), new OrderQty(), new Price(), new TimeInForce(), new Account()},
new String[] {"Symbol", "Side", "OrderQty", "Price", "TimeInForce", "Account"});
// reorder the fields
doVerifyFieldOrder(new Field[] {new OrderQty(), new Price(), new TimeInForce(), new Symbol(), new Side(), new Account()},
new String[] {"OrderQty", "Price", "TimeInForce", "Symbol", "Side", "Account"});
// custom field
doVerifyFieldOrder(new Field[] {new OrderQty(), new Price(), new TimeInForce(), new Symbol(), new Side(), new CustomField<String>(1234, null)},
new String[] {"OrderQty", "Price", "TimeInForce", "Symbol", "Side", "1234"});
// custom field in the middle
doVerifyFieldOrder(new Field[] {new OrderQty(), new CustomField<String>(1234, null), new Symbol(), new Side()},
new String[] {"OrderQty", "1234", "Symbol", "Side"});
// unknown field
(new ExpectedTestFailure(OrderParsingException.class, "ToliField") {
protected void execute() throws Throwable
{
doVerifyFieldOrder(new Field[] {new Symbol(), new Side(), new OrderQty(), new Price(), new TimeInForce(), new Account()},
new String[] {"Symbol", "Side", "ToliField", "Price", "TimeInForce", "Account"});
}
}).run();
}
private void doVerifyFieldOrder(Field<?>[] inFields, String[] inHeaders) throws Exception
{
final FIXProcessor proc = create();
proc.initialize(inHeaders);
assertEquals(new Vector<Field<?>>(Arrays.asList(inFields)), proc.getHeaderFields());
assertArrayEquals(inHeaders, proc.getHeaderNames());
}
/**
* Use a pre-specified giant order and make sure the right
* messages are sent.
*
* Tests {@link #ORDER_EXAMPLE} processing when its supplied
* in native encoding
*
* @throws Exception if there were errors
*/
@Test
public void endToEndNativeEncoding() throws Exception
{
verifyOrderExampleProcessing(new ByteArrayInputStream(
ORDER_EXAMPLE.getBytes()));
}
/**
* Tests {@link #ORDER_EXAMPLE} processing when its supplied in
* UTF8 Encoding
*
* @throws Exception if there were errors
*/
@Test
public void endToEndUTF8Encoding() throws Exception
{
verifyOrderExampleProcessing(new ByteArrayInputStream(
SignatureCharset.UTF8_UTF8.encode(ORDER_EXAMPLE)));
}
/**
* Tests {@link #ORDER_EXAMPLE} processing when its supplied in UTF32
* encoding
*
* @throws Exception if there were errors
*/
@Test
public void endToEndUTF32Encoding() throws Exception
{
verifyOrderExampleProcessing(new ByteArrayInputStream(
SignatureCharset.UTF32BE_UTF32BE.encode(ORDER_EXAMPLE)));
}
/**
* Verifies results of processing of {@link #ORDER_EXAMPLE}
*
* @param inStream the input stream to read orders from
*
* @throws Exception if there were errors.
*/
private void verifyOrderExampleProcessing(InputStream inStream)
throws Exception {
verifyOrderParsing(inStream, 2, 0, 9, 8);
}
@Test
public void commentedLines() throws Exception
{
String order =
"#Opening comment\n" +
"Symbol,Side,OrderQty,Price,TimeInForce,Account\n"+
"#IBM,B,100,12.1,DAY,123-ASDF-234\n"+
"IBM,SS,100,12.22,DAY,123-ASDF-234\n"+
"EFA,SSE,100,MKT,DAY,9182379812\n"+
"#EFA,SSE,100,MKT,FILL_OR_KILL,9182379812\n";
verifyOrderParsing(new ByteArrayInputStream(order.getBytes()), 0, 3, 2, 0);
}
@Test
public void wrongNumberOfFields() throws Exception
{
String order =
"Symbol,Side,OrderQty,Price,TimeInForce,Account\n"+
"IBM,B,100,12.1,DAY,123-ASDF-234\n"+
"IBM,SS,100,123-ASDF-234\n";
verifyOrderParsing(new ByteArrayInputStream(order.getBytes()), 0, 0, 1, 1);
}
@Test
public void endToEndCustom() throws Exception
{
String order =
"Symbol,Side,OrderQty,Price,58,Account\n"+
"IBM,B,100,12.1,DAY,123-ASDF-234\n"+
"IBM,S,100,12.22,12,123-ASDF-234\n"+
"IBM,S,100,12.22,12.45,123-ASDF-234\n";
verifyOrderParsing(new ByteArrayInputStream(order.getBytes()), 0, 0, 3, 0);
}
@Test
public void validMessage() throws Exception
{
RowProcessor processor = create();
String[] headerNames = {"Symbol", "Side", "OrderQty", "Price", "TimeInForce", "Account"};
processor.initialize(headerNames);
// manually construct message: {55=IBM, 40=2, 38=100, 21=3, 11=666, 1=123-ASDF-234, 54=5, 59=0, 44=12.22}
Message message = parseOrder(processor, true, "IBM", "SS", "100", "12.22", "DAY", "123-ASDF-234");
assertEquals("IBM",message.getString(Symbol.FIELD));
assertEquals(Side.SELL_SHORT,message.getChar(Side.FIELD));
assertEquals("100",message.getString(OrderQty.FIELD));
assertEquals("12.22",message.getString(Price.FIELD));
assertEquals(TimeInForce.DAY,message.getChar(TimeInForce.FIELD));
assertEquals("123-ASDF-234",message.getString(Account.FIELD));
}
private Message parseOrder(RowProcessor processor, boolean inVerify, String... inRow) {
processor.processOrder(1, inRow);
if (inVerify) {
assertEquals(1, processor.getNumSuccess());
assertEquals(0, processor.getNumFailed());
assertEquals(0, processor.getFailedOrders().size());
return verifySingleOrder();
} else {
return null;
}
}
private Message verifySingleOrder() {
List<Order> orders = mProcessor.getOrders();
assertEquals(1, orders.size());
FIXOrder order = (FIXOrder) orders.get(0);
assertEquals(BROKER_ID, order.getBrokerID());
assertNull(order.getSecurityType());
return order.getMessage();
}
@Test
public void nonAscii()
throws Exception
{
final String[] headerNames = { "Symbol", "Side", "OrderQty", "Price", "HandlInst", "Account" };
RowProcessor processor = create();
processor.initialize(headerNames);
String symbol = "some string";
Message message = parseOrder(processor, true, symbol, "SS", "100", "12.22", "3", "123-ASDF-234");
assertEquals(symbol,
message.getString(Symbol.FIELD));
assertEquals(Side.SELL_SHORT,
message.getChar(Side.FIELD));
assertEquals("100",
message.getString(OrderQty.FIELD));
assertEquals("12.22",
message.getString(Price.FIELD));
assertEquals("3",
message.getString(HandlInst.FIELD));
assertEquals("123-ASDF-234",
message.getString(Account.FIELD));
symbol = "some other string";
processor = create();
processor.initialize(headerNames);
message = parseOrder(processor, true, symbol, "SS", "100", "12.22", "3", "123-ASDF-234");
assertEquals(symbol,
message.getString(Symbol.FIELD));
assertEquals(Side.SELL_SHORT,
message.getChar(Side.FIELD));
assertEquals("100",
message.getString(OrderQty.FIELD));
assertEquals("12.22",
message.getString(Price.FIELD));
assertEquals("3",
message.getString(HandlInst.FIELD));
assertEquals("123-ASDF-234",
message.getString(Account.FIELD));
}
/** Verify that HandlInst, if set, overwrites the default one in the NOS message factory */
@Test
public void handlInstField() throws Exception
{
RowProcessor processor = create();
final String[] headerNames = {"Symbol", "Side", "OrderQty", "Price", "HandlInst", "Account"};
processor.initialize(headerNames);
Message message = parseOrder(processor, true, "IBM", "SS", "100", "12.22", "3", "123-ASDF-234");
assertFalse("default handlInst is same as what we set so this test is pointless",
FIXVersion.FIX42.getMessageFactory().newBasicOrder().
getString(HandlInst.FIELD).equals(""+HandlInst.MANUAL_ORDER));
assertEquals("IBM", message.getString(Symbol.FIELD) );
assertEquals(Side.SELL_SHORT, message.getChar(Side.FIELD) );
assertEquals("100", message.getString(OrderQty.FIELD) );
assertEquals("12.22", message.getString(Price.FIELD) );
assertEquals("3", message.getString(HandlInst.FIELD) );
assertEquals("123-ASDF-234", message.getString(Account.FIELD) );
}
@Test
public void withCustomField() throws Exception
{
final String[] headerNames = {"Symbol", "Side", "OrderQty", "Price", "9999", "Account"};
RowProcessor processor = create();
processor.initialize(headerNames);
parseOrder(processor, false, "IBM", "SS", "100", "12.22", "customValue", "123-ASDF-234");
assertTrue(mProcessor.getOrders().isEmpty());
assertEquals(1, processor.getNumFailed());
assertEquals(1, processor.getFailedOrders().size());
processor = create();
processor.initialize(headerNames);
// manually construct message: {55=IBM, 40=2, 38=100, 21=3, 11=666, 1=123-ASDF-234, 54=5, 59=0, 44=12.22}
Message message = parseOrder(processor, true, "IBM", "SS", "100", "12.22", "123", "123-ASDF-234");
assertEquals("IBM", message.getString(Symbol.FIELD) );
assertEquals(Side.SELL_SHORT, message.getChar(Side.FIELD) );
assertEquals("100", message.getString(OrderQty.FIELD) );
assertEquals("12.22", message.getString(Price.FIELD) );
assertEquals("123", message.getString(9999) );
assertEquals("123-ASDF-234", message.getString(Account.FIELD) );
processor = create();
processor.initialize(headerNames);
// integer, id+1
// manually construct message: {55=IBM, 40=2, 38=100, 21=3, 11=666, 1=123-ASDF-234, 54=5, 59=0, 44=12.22}
message = parseOrder(processor, true, "IBM", "SS", "100", "12.22", "12345", "123-ASDF-234");
assertEquals("IBM", message.getString(Symbol.FIELD) );
assertEquals(Side.SELL_SHORT, message.getChar(Side.FIELD) );
assertEquals("100", message.getString(OrderQty.FIELD) );
assertEquals("12.22", message.getString(Price.FIELD) );
assertEquals("12345", message.getString(9999) );
assertEquals("123-ASDF-234", message.getString(Account.FIELD) );
}
/** Test using both header and trailer and message custom fields */
@Test
public void withMixedCustomFields() throws Exception {
RowProcessor processor = create();
final String[] headerNames = {"Symbol", "Side", "OrderQty", "Price", "9999", "SenderSubID", "SignatureLength", "Signature"};
processor.initialize(headerNames);
// manually construct message: {55=IBM, Side=SS, OrderQty=100, Price=12.22, 9999=custom, SenderSubID=sub1, 93=3, 89=sig}
Message message = parseOrder(processor, true, "IBM", "SS", "100", "12.22", "1234", "sub1", "sig".length()+"", "sig");
assertEquals("IBM", message.getString(Symbol.FIELD) );
assertEquals(Side.SELL_SHORT, message.getChar(Side.FIELD) );
assertEquals("100", message.getString(OrderQty.FIELD) );
assertEquals("12.22", message.getString(Price.FIELD) );
assertEquals("1234", message.getString(9999));
assertEquals("sub1", message.getHeader().getString(SenderSubID.FIELD));
assertEquals(3, message.getTrailer().getInt(SignatureLength.FIELD));
assertEquals("sig", message.getTrailer().getString(Signature.FIELD));
}
/** Try sending a message with key not in dictionary */
@Test
public void fieldNotInDictionary() throws Exception {
RowProcessor processor = create();
final String[] headerNames = {"Symbol", "Side", "OrderQty", "Price", "7654"};
processor.initialize(headerNames);
// manually construct message: {55=IBM, Side=SS, OrderQty=100, Price=12.22, 9999=custom, SenderSubID=sub1, 93=3, 89=sig}
parseOrder(processor, false, "IBM", "SS", "100", "12.22", "1234");
assertEquals(1, processor.getNumFailed());
List<FailedOrderInfo> infoList = processor.getFailedOrders();
assertEquals(1, infoList.size());
FailedOrderInfo info = infoList.get(0);
assertTrue(info.getException().toString(), info.getException().getLocalizedMessage().endsWith(PARSING_FIELD_NOT_IN_DICT.getText("7654", "1234")));
}
@Test
public void marketPrice() throws Exception {
RowProcessor processor = create();
String[] headerNames = {"Symbol", "Side", "OrderQty", "Price", "TimeInForce"};
processor.initialize(headerNames);
// manually construct message: {55=IBM, 40=2, 38=100, 21=3, 11=666, 54=5, 59=0}
Message message = parseOrder(processor, true, "IBM", "SS", "100", FIXProcessor.MKT_PRICE, "DAY");
assertEquals("IBM",message.getString(Symbol.FIELD));
assertEquals(Side.SELL_SHORT,message.getChar(Side.FIELD));
assertEquals("100",message.getString(OrderQty.FIELD));
assertEquals(OrdType.MARKET,message.getChar(OrdType.FIELD));
assertFalse(message.isSetField(Price.FIELD));
assertEquals(TimeInForce.DAY,message.getChar(TimeInForce.FIELD));
}
private void verifyOrderParsing(InputStream inStream, int inBlanks,
int inComments, int inSuccess,
int inFailed) throws Exception {
RowProcessor processor = create();
OrderParser parser = new OrderParser(processor);
parser.parseOrders(inStream);
assertEquals(inBlanks, parser.getNumBlankLines());
assertEquals(inComments, parser.getNumComments());
assertEquals(inSuccess, processor.getNumSuccess());
assertEquals(inFailed, processor.getNumFailed());
assertEquals(inFailed + inSuccess, processor.getTotal());
assertEquals(inFailed, processor.getFailedOrders().size());
}
private FIXProcessor create() throws Exception {
mProcessor.getOrders().clear();
return new FIXProcessor(mProcessor,
BROKER_ID, FIXVersion.FIX42);
}
private final MockOrderProcessor mProcessor = new MockOrderProcessor();
/**
* Example order for testing.
*/
public static final String ORDER_EXAMPLE =
"Symbol,Side,OrderQty,Price,TimeInForce,Account\n"+
"IBM,B,100,12.1,DAY,123-ASDF-234\n"+
"IBM,SS,100,12.22,DAY,123-ASDF-234\n"+
"EFA,SSE,100,MKT,DAY,9182379812\n"+
"EFA,SSE,100,MKT,FILL_OR_KILL,9182379812\n"+
"---,SSE,100,MKT,DAY,9182379812\n"+
"EFA,---,100,MKT,DAY,9182379812\n"+
"EFA,SSE,---,MKT,DAY,9182379812\n"+
"\n"+
"EFA,SSE,100,---,DAY,9182379812\n"+
"EFA,SSE,100,MKT,---,9182379812\n"+
"EFA,SSE,100,MKT,---,---\n"+
"EFA,SSE,100,MKT,DAY,---\n"+
"EFA,SSE,100.1,MKT,DAY,9182379812\n"+
"IBM,SS,100,-12.22,DAY,123-ASDF-234\n"+
"IBM,SS,-100,12.22,DAY,123-ASDF-234\n"+
"//do nothing\n"+
"\n"+
"IBM,SS,100,12.22,DAY,123-ASDF-234\n"+
"IBM,S,100,12.22,DAY,123-ASDF-234\n";
private static final BrokerID BROKER_ID = new BrokerID("Yes");
}