package org.marketcetera.photon.parser;
import java.math.BigDecimal;
import java.util.List;
import org.codehaus.jparsec.Parser;
import org.codehaus.jparsec.Parsers;
import org.codehaus.jparsec.Scanners;
import org.codehaus.jparsec.Terminals;
import org.codehaus.jparsec.functors.Map;
import org.codehaus.jparsec.functors.Map4;
import org.codehaus.jparsec.functors.Map5;
import org.codehaus.jparsec.pattern.CharPredicates;
import org.codehaus.jparsec.pattern.Patterns;
import org.marketcetera.photon.IBrokerIdValidator;
import org.marketcetera.trade.BrokerID;
import org.marketcetera.trade.Equity;
import org.marketcetera.trade.Factory;
import org.marketcetera.trade.Instrument;
import org.marketcetera.trade.Option;
import org.marketcetera.trade.OptionType;
import org.marketcetera.trade.OrderSingle;
import org.marketcetera.trade.OrderType;
import org.marketcetera.trade.Side;
import org.marketcetera.trade.TimeInForce;
import org.marketcetera.util.misc.ClassVersion;
/* $License$ */
/**
* Parses an order from a string.
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: OrderSingleParser.java 16604 2013-06-26 14:49:42Z colin $
* @since 2.0.0
*/
@ClassVersion("$Id: OrderSingleParser.java 16604 2013-06-26 14:49:42Z colin $")
public class OrderSingleParser {
/**
* A typical word is a sequence of non-whitespace characters.
*/
private static final Parser<String> WORD = Scanners.pattern(
Patterns.regex("\\S*"), "part").source(); //$NON-NLS-1$ //$NON-NLS-2$
private static final Parser<Side> SIDE_PARSER = Terminals.Identifier.PARSER
.map(new Map<String, Side>() {
@Override
public Side map(String from) {
if (from.equalsIgnoreCase("b")) { //$NON-NLS-1$
return Side.Buy;
} else if (from.equalsIgnoreCase("s")) { //$NON-NLS-1$
return Side.Sell;
} else if (from.equalsIgnoreCase("ss")) { //$NON-NLS-1$
return Side.SellShort;
}
throw new IllegalArgumentException(
Messages.ORDER_SINGLE_PARSER_INVALID_SIDE
.getText(from));
}
});
private static final Parser<Instrument> OPTION_PARSER = Parsers.sequence(
Scanners.pattern(Patterns.regex("\\D+"), "symbol").source(), //$NON-NLS-1$ //$NON-NLS-2$
Scanners.INTEGER, Scanners.isChar(CharPredicates.among("cCpP")) //$NON-NLS-1$
.source(), Scanners.DECIMAL,
new Map4<String, String, String, String, Instrument>() {
@Override
public Instrument map(String symbol, String expiry,
String typeString, String strikeString) {
OptionType type;
if (typeString.equalsIgnoreCase("c")) { //$NON-NLS-1$
type = OptionType.Call;
} else if (typeString.equalsIgnoreCase("p")) { //$NON-NLS-1$
type = OptionType.Put;
} else {
throw new IllegalArgumentException();
}
return new Option(symbol, expiry, new BigDecimal(
strikeString), type);
}
});
private static final Parser<Instrument> EQUITY_PARSER = WORD
.map(new Map<String, Instrument>() {
@Override
public Instrument map(String symbol) {
return new Equity(symbol);
}
});
private static final Parser<Instrument> INSTRUMENT_PARSER = Terminals.Identifier.PARSER
.map(new Map<String, Instrument>() {
@Override
public Instrument map(String from) {
return OPTION_PARSER.or(EQUITY_PARSER).parse(from);
}
});
private static final Parser<BigDecimal> BIG_DECIMAL_PARSER = Terminals.Identifier.PARSER
.map(new Map<String, BigDecimal>() {
@Override
public BigDecimal map(String from) {
try {
return new BigDecimal(from);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
Messages.ORDER_SINGLE_PARSER_NOT_A_DECIMAL
.getText(from));
}
}
});
/**
* Optional parts have an id followed by a colon, followed by a value. The
* value can be quoted if spaces are desired.
*/
private static final Parser<String> OPTIONAL_PART = Scanners.pattern(
Patterns.regex("\\S*:((\".*\")|(\\S*))"), "optional part").source(); //$NON-NLS-1$ //$NON-NLS-2$
private static final Parser<?> TOKENS = Terminals.caseInsensitive(
OPTIONAL_PART.or(WORD), new String[] {}, new String[] {})
.tokenizer();
private final Parser<OrderSingle> ORDER_PARSER = Parsers
.sequence(
SIDE_PARSER,
BIG_DECIMAL_PARSER,
INSTRUMENT_PARSER,
Terminals.Identifier.PARSER,
Terminals.Identifier.PARSER.many(),
new Map5<Side, BigDecimal, Instrument, String, List<String>, OrderSingle>() {
@Override
public OrderSingle map(Side side, BigDecimal quantity,
Instrument instrument, String price,
List<String> optionalField) {
OrderSingle order = Factory.getInstance()
.createOrderSingle();
order.setSide(side);
order.setQuantity(quantity);
if (price.equalsIgnoreCase("mkt")) { //$NON-NLS-1$
order.setOrderType(OrderType.Market);
}else if (price.equalsIgnoreCase("moc")) { //$NON-NLS-1$
order.setOrderType(OrderType.MarketOnClose);
}else {
order.setOrderType(OrderType.Limit);
try {
order.setPrice(new BigDecimal(price));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
Messages.ORDER_SINGLE_PARSER_INVALID_PRICE
.getText(price));
}
}
order.setInstrument(instrument);
for (String string : optionalField) {
applyOptionalField(string, order);
}
return order;
}
private void applyOptionalField(String string,
OrderSingle order) {
if (string.isEmpty()) {
return;
}
String[] split = string.split(":"); //$NON-NLS-1$
if (split.length == 1) {
throw new IllegalArgumentException(
Messages.ORDER_SINGLE_PARSER_NO_VALUE_FOR_OPTIONAL_FIELD
.getText(split[0]));
} else if (split.length != 2) {
throw new IllegalArgumentException(
Messages.ORDER_SINGLE_PARSER_INVALID_OPTIONAL_FIELD
.getText(string));
}
String keyword = split[0];
String value = split[1];
if (keyword.equalsIgnoreCase("acc")) { //$NON-NLS-1$
if (value.startsWith("\"")) { //$NON-NLS-1$
value = Terminals.StringLiteral.DOUBLE_QUOTE_TOKENIZER
.parse(value);
}
order.setAccount(value);
} else if (keyword.equalsIgnoreCase("b")) { //$NON-NLS-1$
if (mBrokerIdValidator != null
&& !mBrokerIdValidator.isValid(value)) {
throw new IllegalArgumentException(
Messages.ORDER_SINGLE_PARSER_INVALID_BROKER_ID
.getText(value));
} else {
order.setBrokerID(new BrokerID(value));
}
} else if (keyword.equalsIgnoreCase("tif")) { //$NON-NLS-1$
if (value.equalsIgnoreCase("day")) { //$NON-NLS-1$
order.setTimeInForce(TimeInForce.Day);
} else if (value.equalsIgnoreCase("gtc")) { //$NON-NLS-1$
order
.setTimeInForce(TimeInForce.GoodTillCancel);
} else if (value.equalsIgnoreCase("fok")) { //$NON-NLS-1$
order
.setTimeInForce(TimeInForce.FillOrKill);
} else if (value.equalsIgnoreCase("clo")) { //$NON-NLS-1$
order
.setTimeInForce(TimeInForce.AtTheClose);
} else if (value.equalsIgnoreCase("opg")) { //$NON-NLS-1$
order
.setTimeInForce(TimeInForce.AtTheOpening);
} else if (value.equalsIgnoreCase("ioc")) { //$NON-NLS-1$
order
.setTimeInForce(TimeInForce.ImmediateOrCancel);
} else {
throw new IllegalArgumentException(
Messages.ORDER_SINGLE_PARSER_INVALID_TIF
.getText(value));
}
} else {
throw new IllegalArgumentException(
Messages.ORDER_SINGLE_PARSER_INVALID_OPTIONAL_FIELD
.getText(string));
}
}
}).from(TOKENS, Scanners.WHITESPACES);
private final IBrokerIdValidator mBrokerIdValidator;
/**
* Constructor.
*
* @param brokerIdValidator
* validates broker id's on order commands, can be null
*/
public OrderSingleParser(IBrokerIdValidator brokerIdValidator) {
mBrokerIdValidator = brokerIdValidator;
}
/**
* Returns a parser that parses a string into an order.
*
* @return the parser
*/
public Parser<OrderSingle> getParser() {
return ORDER_PARSER;
}
}