package org.marketcetera.strategy;
import static org.junit.Assert.*;
import static org.marketcetera.module.TestMessages.FLOW_REQUESTER_PROVIDER;
import static org.marketcetera.strategy.Status.FAILED;
import static org.marketcetera.strategy.Status.RUNNING;
import static org.marketcetera.strategy.Status.STOPPED;
import java.beans.ExceptionListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.management.ManagementFactory;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicLong;
import java.util.jar.Manifest;
import javax.management.JMX;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.apache.commons.lang.SerializationUtils;
import org.apache.commons.lang.SystemUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.marketcetera.client.*;
import org.marketcetera.client.brokers.BrokerStatus;
import org.marketcetera.client.brokers.BrokersStatus;
import org.marketcetera.client.users.UserInfo;
import org.marketcetera.core.BigDecimalUtils;
import org.marketcetera.core.LoggerConfiguration;
import org.marketcetera.core.notifications.ServerStatusListener;
import org.marketcetera.core.position.PositionKey;
import org.marketcetera.event.*;
import org.marketcetera.marketdata.DateUtils;
import org.marketcetera.marketdata.MarketDataFeedTestBase;
import org.marketcetera.marketdata.TestMessages;
import org.marketcetera.marketdata.bogus.BogusFeedModuleFactory;
import org.marketcetera.module.*;
import org.marketcetera.quickfix.FIXVersion;
import org.marketcetera.strategy.StrategyModule.ClientFactory;
import org.marketcetera.trade.*;
import org.marketcetera.trade.Currency;
import org.marketcetera.util.log.I18NMessage;
import quickfix.Message;
import quickfix.field.OrdStatus;
import quickfix.field.Side;
import quickfix.field.TransactTime;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
/* $License$ */
/**
* Base class for <code>Strategy</code> tests.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyTestBase.java 16888 2014-04-22 18:32:36Z colin $
* @since 1.0.0
*/
public class StrategyTestBase
extends ModuleTestBase
implements Messages
{
public static final File SAMPLE_STRATEGY_DIR = new File("src" + File.separator + "test" + File.separator + "sample_data",
"inputs");
/**
* Tuple which describes the location and name of a strategy.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyTestBase.java 16888 2014-04-22 18:32:36Z colin $
* @since 1.0.0
*/
public static class StrategyCoordinates
{
private final File file;
private final String name;
public static StrategyCoordinates get(File inFile,
String inName)
{
return new StrategyCoordinates(inFile,
inName);
}
private StrategyCoordinates(File inFile,
String inName)
{
file = inFile;
name = inName;
}
/**
* Get the file value.
*
* @return a <code>File</code> value
*/
public final File getFile()
{
return file;
}
/**
* Get the name value.
*
* @return a <code>String</code> value
*/
public final String getName()
{
return name;
}
}
/**
* A {@link DataReceiver} implementation that stores the data it receives.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyTestBase.java 16888 2014-04-22 18:32:36Z colin $
* @since 1.0.0
*/
public static class MockRecorderModule
extends Module
implements DataReceiver, DataEmitter
{
/**
* indicates if the module should emit execution reports when it receives order objects
*/
public static boolean shouldSendExecutionReports = true;
public static boolean shouldFullyFillOrders = true;
public static boolean shouldIgnoreLogMessages = true;
public static int ordersReceived = 0;
/**
* Create a new MockRecorderModule instance.
*
* @param inURN
*/
protected MockRecorderModule(ModuleURN inURN)
{
super(inURN,
false);
}
/* (non-Javadoc)
* @see org.marketcetera.module.Module#preStart()
*/
@Override
protected void preStart()
throws ModuleException
{
}
/* (non-Javadoc)
* @see org.marketcetera.module.Module#preStop()
*/
@Override
protected void preStop()
throws ModuleException
{
}
/* (non-Javadoc)
* @see org.marketcetera.module.DataReceiver#receiveData(org.marketcetera.module.DataFlowID, java.lang.Object)
*/
@Override
public void receiveData(DataFlowID inFlowID,
Object inData)
throws UnsupportedDataTypeException, StopDataFlowException
{
if(inData instanceof LogEvent) {
if(shouldIgnoreLogMessages) {
return;
}
}
synchronized(data) {
data.add(new DataReceived(inFlowID,
inData));
}
if(inData instanceof OrderSingle) {
if(shouldSendExecutionReports) {
OrderSingle order = (OrderSingle)inData;
try {
List<ExecutionReport> executionReports = generateExecutionReports(order);
synchronized(subscribers) {
for(ExecutionReport executionReport : executionReports) {
for(DataEmitterSupport subscriber : subscribers.values()) {
subscriber.send(executionReport);
}
}
}
} catch (Exception e) {
e.printStackTrace();
throw new StopDataFlowException(e,
null);
}
}
ordersReceived += 1;
}
}
/* (non-Javadoc)
* @see org.marketcetera.module.DataEmitter#cancel(org.marketcetera.module.RequestID)
*/
@Override
public void cancel(DataFlowID inFlowID,
RequestID inRequestID)
{
synchronized(subscribers) {
subscribers.remove(inRequestID);
}
}
/* (non-Javadoc)
* @see org.marketcetera.module.DataEmitter#requestData(org.marketcetera.module.DataRequest, org.marketcetera.module.DataEmitterSupport)
*/
@Override
public void requestData(DataRequest inRequest,
DataEmitterSupport inRequester)
throws RequestDataException
{
synchronized(subscribers) {
subscribers.put(inRequester.getRequestID(),
inRequester);
}
}
/**
* collection of subscribers interested in data emitter by this module
*/
private final Map<RequestID,DataEmitterSupport> subscribers = new HashMap<RequestID,DataEmitterSupport>();
/**
* Resets the collection of data received.
*/
public void resetDataReceived()
{
synchronized(data) {
data.clear();
}
}
/**
* Returns a copy of the list of the received data.
*
* @return a <code>list<DataReceived></code> value
*/
public List<DataReceived> getDataReceived()
{
synchronized(data) {
return new ArrayList<DataReceived>(data);
}
}
/**
* collection of data received by this module
*/
private final List<DataReceived> data = new ArrayList<DataReceived>();
/**
* The {@link ModuleFactory} implementation for {@link MockRecorderModule}.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyTestBase.java 16888 2014-04-22 18:32:36Z colin $
* @since 1.0.0
*/
public static class Factory
extends ModuleFactory
{
/**
* used to generate unique identifiers for the instance counters
*/
private static final AtomicLong instanceCounter = new AtomicLong();
/**
* provider URN for {@link StrategyDataEmissionModule}
*/
public static final ModuleURN PROVIDER_URN = new ModuleURN("metc:receiver:system");
public static final Map<ModuleURN,MockRecorderModule> recorders = new HashMap<ModuleURN,MockRecorderModule>();
/**
* Create a new Factory instance.
*/
public Factory()
{
super(PROVIDER_URN,
FLOW_REQUESTER_PROVIDER,
true,
false);
}
/* (non-Javadoc)
* @see org.marketcetera.module.ModuleFactory#create(java.lang.Object[])
*/
@Override
public Module create(Object... inParameters)
throws ModuleCreationException
{
MockRecorderModule module = new MockRecorderModule(new ModuleURN(PROVIDER_URN,
"mockRecorderModule" + instanceCounter.incrementAndGet()));
recorders.put(module.getURN(),
module);
return module;
}
}
/**
* Stores the data received by {@link MockRecorderModule}.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyTestBase.java 16888 2014-04-22 18:32:36Z colin $
* @since 1.0.0
*/
public static class DataReceived
{
/**
* the data flow ID of the data received
*/
private final DataFlowID dataFlowID;
/**
* the actual data received
*/
private final Object data;
/**
* Create a new DataReceived instance.
*
* @param inDataFlowID a <code>DataFlowID</code> value
* @param inData an <code>Object</code> value
*/
private DataReceived(DataFlowID inDataFlowID,
Object inData)
{
dataFlowID = inDataFlowID;
data = inData;
}
/**
* Get the dataFlowID value.
*
* @return a <code>DataFlowID</code> value
*/
public DataFlowID getDataFlowID()
{
return dataFlowID;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return data == null ? "null data" : data.toString();
}
/**
* Get the data value.
*
* @return an <code>Object</code> value
*/
public Object getData()
{
return data;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((data == null) ? 0 : data.hashCode());
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DataReceived other = (DataReceived) obj;
if (data == null) {
if (other.data != null)
return false;
} else if (!data.equals(other.data))
return false;
return true;
}
}
}
/**
* A {@link DataEmitter} implementation that emits each type of data a {@link RunningStrategy} can receive.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyTestBase.java 16888 2014-04-22 18:32:36Z colin $
* @since 1.0.0
*/
public static class StrategyDataEmissionModule
extends Module
implements DataEmitter
{
/**
* data to transmit
*/
private static final List<Object> dataToSend = new ArrayList<Object>();
/**
* Gets the data that will be tramsitted.
*
* @return a <code>List<Object></code> value
*/
public static List<Object> getDataToSend()
{
synchronized(dataToSend) {
return dataToSend;
}
}
/**
* Rests the data to be transmitted to its default setting.
*
* @throws Exception if an error occurs
*/
public static void setDataToSendToDefaults()
throws Exception
{
synchronized(dataToSend) {
dataToSend.clear();
dataToSend.add(EventTestBase.generateEquityTradeEvent(System.nanoTime(),
System.currentTimeMillis(),
new Equity("GOOG"),
"Exchange",
new BigDecimal("100"),
new BigDecimal("10000")));
dataToSend.add(EventTestBase.generateEquityBidEvent(System.nanoTime(),
System.currentTimeMillis(),
new Equity("GOOG"),
"Exchange",
new BigDecimal("200"),
new BigDecimal("20000")));
dataToSend.add(EventTestBase.generateEquityAskEvent(System.nanoTime(),
System.currentTimeMillis(),
new Equity("GOOG"),
"Exchange",
new BigDecimal("200"),
new BigDecimal("20000")));
dataToSend.add(EventTestBase.generateDividendEvent());
Message orderCancelReject = FIXVersion.FIX44.getMessageFactory().newOrderCancelReject();
OrderCancelReject cancel = org.marketcetera.trade.Factory.getInstance().createOrderCancelReject(orderCancelReject,
null,
Originator.Server,
null,
null);
dataToSend.add(cancel);
Message executionReport = FIXVersion.FIX44.getMessageFactory().newExecutionReport("orderid",
"clOrderID",
"execID",
OrdStatus.FILLED,
Side.BUY,
new BigDecimal(100),
new BigDecimal(200),
new BigDecimal(300),
new BigDecimal(400),
new BigDecimal(500),
new BigDecimal(600),
new Equity("Symbol"),
"account",
"text");
dataToSend.add(org.marketcetera.trade.Factory.getInstance().createExecutionReport(executionReport,
new BrokerID("some-broker"),
Originator.Server,
null,
null));
// send an object that doesn't fit one of the categories
dataToSend.add(new Date());
}
}
/**
* Create a new MockRecorderModule instance.
*
* @param inURN
*/
protected StrategyDataEmissionModule(ModuleURN inURN)
{
super(inURN,
false);
}
/* (non-Javadoc)
* @see org.marketcetera.module.Module#preStart()
*/
@Override
protected void preStart()
throws ModuleException
{
}
/* (non-Javadoc)
* @see org.marketcetera.module.Module#preStop()
*/
@Override
protected void preStop()
throws ModuleException
{
}
/* (non-Javadoc)
* @see org.marketcetera.module.DataEmitter#cancel(org.marketcetera.module.RequestID)
*/
@Override
public void cancel(DataFlowID inFlowID, RequestID inRequestID)
{
// nothing to do here
}
/* (non-Javadoc)
* @see org.marketcetera.module.DataEmitter#requestData(org.marketcetera.module.DataRequest, org.marketcetera.module.DataEmitterSupport)
*/
@Override
public void requestData(DataRequest inRequest,
DataEmitterSupport inSupport)
throws UnsupportedRequestParameterType, IllegalRequestParameterValue
{
try {
sendDataTypes(inSupport);
} catch (Exception e) {
e.printStackTrace();
throw new IllegalRequestParameterValue(null,
e);
}
}
/**
* Sends each type of data a {@link RunningStrategy} must be able to respond to.
*
* <p>When a new call-back is added to {@link RunningStrategy}, this method should
* be expanded to send that data.
*
* @param inSupport a <code>DataEmitterSupport</code> value to which to send the data
* @throws Exception if an error occurs
*/
private void sendDataTypes(DataEmitterSupport inSupport)
throws Exception
{
synchronized(dataToSend) {
for(Object o : dataToSend) {
inSupport.send(o);
}
}
}
/**
* The {@link ModuleFactory} implementation for {@link StrategyDataEmissionModule}.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyTestBase.java 16888 2014-04-22 18:32:36Z colin $
* @since 1.0.0
*/
public static class Factory
extends ModuleFactory
{
/**
* used to generate unique identifiers for the instance counters
*/
private static final AtomicLong instanceCounter = new AtomicLong();
/**
* provider URN for {@link StrategyDataEmissionModule}
*/
public static final ModuleURN PROVIDER_URN = new ModuleURN("metc:emitter:system");
/**
* Create a new Factory instance.
*/
public Factory()
{
super(PROVIDER_URN,
FLOW_REQUESTER_PROVIDER,
true,
false);
}
/* (non-Javadoc)
* @see org.marketcetera.module.ModuleFactory#create(java.lang.Object[])
*/
@Override
public Module create(Object... inParameters)
throws ModuleCreationException
{
return new StrategyDataEmissionModule(new ModuleURN(PROVIDER_URN,
"strategyDataEmissionModule" + instanceCounter.incrementAndGet()));
}
}
}
public static class MockClient
implements Client
{
public static class MockClientFactory
implements org.marketcetera.client.ClientFactory
{
/* (non-Javadoc)
* @see org.marketcetera.client.ClientFactory#getClient(org.marketcetera.client.ClientParameters)
*/
@Override
public Client getClient(ClientParameters inClientParameters)
throws ClientInitException, ConnectionException
{
return new MockClient();
}
}
/**
* indicates whether calls to {@link #getBrokersStatus()} should fail automatically
*/
public static boolean getBrokersFails = false;
/**
* indicates whether calls to {@link #getEquityPositionAsOf(Date, Equity)} should fail automatically
*/
public static boolean getPositionFails = false;
/**
* Broker status listeners
*/
private final Deque<BrokerStatusListener> mBrokerStatusListeners=
new LinkedList<BrokerStatusListener>();
/**
* indicates whether calls to {@link #addBrokerStatusListener(BrokerStatusListener)} should fail automatically
*/
public static boolean addBrokerStatusListenerFails = false;
/* (non-Javadoc)
* @see org.marketcetera.client.Client#addExceptionListener(java.beans.ExceptionListener)
*/
@Override
public void addExceptionListener(ExceptionListener inArg0)
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#addReportListener(org.marketcetera.client.ReportListener)
*/
@Override
public void addReportListener(ReportListener inArg0)
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#addBrokerStatusListener(org.marketcetera.client.BrokerStatusListener)
*/
@Override
public void addBrokerStatusListener (BrokerStatusListener listener)
{
if (addBrokerStatusListenerFails) {
throw new RuntimeException("This exception is expected");
}
synchronized (mBrokerStatusListeners) {
mBrokerStatusListeners.addFirst(listener);
}
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#addServerStatusListener(org.marketcetera.client.ServerStatusListener)
*/
@Override
public void addServerStatusListener(ServerStatusListener inArg0)
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#close()
*/
@Override
public void close()
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getBrokersStatus()
*/
@Override
public BrokersStatus getBrokersStatus()
throws ConnectionException
{
if(getBrokersFails) {
throw new NullPointerException("This exception is expected");
}
return brokers;
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getUserInfo(UserID, boolean)
*/
@Override
public UserInfo getUserInfo(UserID id, boolean useCache)
throws ConnectionException
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getLastConnectTime()
*/
@Override
public Date getLastConnectTime()
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getParameters()
*/
@Override
public ClientParameters getParameters()
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getEquityPositionAsOf(java.util.Date, org.marketcetera.trade.Equity)
*/
@Override
public BigDecimal getEquityPositionAsOf(Date inDate,
Equity inEquity)
throws ConnectionException
{
if(getPositionFails) {
throw new NullPointerException("This exception is expected");
}
Position position = positions.get(inEquity);
if(position == null) {
return null;
}
return position.getPositionAt(inDate);
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getAllEquityPositionsAsOf(java.util.Date)
*/
@Override
public Map<PositionKey<Equity>,BigDecimal> getAllEquityPositionsAsOf(Date inDate)
throws ConnectionException
{
if(getPositionFails) {
throw new NullPointerException("This exception is expected");
}
Map<PositionKey<Equity>,BigDecimal> result = new LinkedHashMap<PositionKey<Equity>,BigDecimal>();
for(Map.Entry<Instrument,Position> entry : positions.entrySet()) {
if(entry.getKey() instanceof Equity) {
final Equity equity = (Equity)entry.getKey();
BigDecimal value = getEquityPositionAsOf(inDate,
equity);
if(value != null) {
PositionKey<Equity> key = new PositionKey<Equity>() {
@Override
public String getAccount()
{
return null;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return getInstrument().getSymbol();
}
@Override
public Equity getInstrument()
{
return equity;
}
@Override
public String getTraderId()
{
return null;
}
};
result.put(key,
value);
}
}
}
return result;
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getAllOptionPositionsAsOf(java.util.Date)
*/
@Override
public Map<PositionKey<Option>, BigDecimal> getAllOptionPositionsAsOf(Date inDate)
throws ConnectionException
{
if(getPositionFails) {
throw new NullPointerException("This exception is expected");
}
Map<PositionKey<Option>,BigDecimal> result = new LinkedHashMap<PositionKey<Option>,BigDecimal>();
for(Map.Entry<Instrument,Position> entry : positions.entrySet()) {
if(entry.getKey() instanceof Option) {
final Option option = (Option)entry.getKey();
BigDecimal value = getOptionPositionAsOf(inDate,
option);
if(value != null) {
PositionKey<Option> key = new PositionKey<Option>() {
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return getInstrument().getSymbol();
}
@Override
public String getAccount()
{
return null;
}
@Override
public Option getInstrument()
{
return option;
}
@Override
public String getTraderId()
{
return null;
}
};
result.put(key,
value);
}
}
}
return result;
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getOptionPositionAsOf(java.util.Date, org.marketcetera.trade.Option)
*/
@Override
public BigDecimal getOptionPositionAsOf(Date inDate,
Option inOption)
throws ConnectionException
{
if(getPositionFails) {
throw new NullPointerException("This exception is expected");
}
Position position = positions.get(inOption);
if(position == null) {
return null;
}
return position.getPositionAt(inDate);
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getOptionPositionsAsOf(java.util.Date, java.lang.String[])
*/
@Override
public Map<PositionKey<Option>,BigDecimal> getOptionPositionsAsOf(Date inDate,
String... inRootSymbols)
throws ConnectionException
{
if(getPositionFails) {
throw new NullPointerException("This exception is expected");
}
Set<String> rootSymbols = new HashSet<String>(Arrays.asList(inRootSymbols));
Map<PositionKey<Option>,BigDecimal> allOptionPositions = getAllOptionPositionsAsOf(inDate);
Map<PositionKey<Option>,BigDecimal> result = new LinkedHashMap<PositionKey<Option>,BigDecimal>();
for(Map.Entry<PositionKey<Option>,BigDecimal> position : allOptionPositions.entrySet()) {
if(rootSymbols.contains(position.getKey().getInstrument().getSymbol())) {
result.put(position.getKey(),
position.getValue());
}
}
return result;
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getAllFuturePositionsAsOf(java.util.Date)
*/
@Override
public Map<PositionKey<Future>, BigDecimal> getAllFuturePositionsAsOf(Date inDate)
throws ConnectionException
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getFuturePositionAsOf(java.util.Date, org.marketcetera.trade.Future)
*/
@Override
public BigDecimal getFuturePositionAsOf(Date inDate,
Future inEquity)
throws ConnectionException
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getOptionRoots(java.lang.String)
*/
@Override
public Collection<String> getOptionRoots(String inUnderlying)
throws ConnectionException
{
if(getPositionFails) {
throw new NullPointerException("This exception is expected");
}
return roots.get(inUnderlying);
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getUnderlying(java.lang.String)
*/
@Override
public String getUnderlying(String inOptionRoot)
throws ConnectionException
{
if(getPositionFails) {
throw new NullPointerException("This exception is expected");
}
return underlyings.get(inOptionRoot);
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getReportsSince(java.util.Date)
*/
@Override
public ReportBase[] getReportsSince(Date inDate)
throws ConnectionException
{
if(getReportsSinceThrows != null) {
throw getReportsSinceThrows;
}
List<ReportBase> reportsToReturn = new ArrayList<ReportBase>();
for(ReportBase report : reports) {
if(report.getSendingTime().compareTo(inDate) != -1) {
reportsToReturn.add(report);
}
}
return reportsToReturn.toArray(new ReportBase[reportsToReturn.size()]);
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#reconnect()
*/
@Override
public void reconnect()
throws ConnectionException
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#reconnect(org.marketcetera.client.ClientParameters)
*/
@Override
public void reconnect(ClientParameters inArg0)
throws ConnectionException
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#removeExceptionListener(java.beans.ExceptionListener)
*/
@Override
public void removeExceptionListener(ExceptionListener inArg0)
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#removeReportListener(org.marketcetera.client.ReportListener)
*/
@Override
public void removeReportListener(ReportListener inArg0)
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#removeBrokerStatusListener(org.marketcetera.client.BrokerStatusListener)
*/
@Override
public void removeBrokerStatusListener (BrokerStatusListener listener)
{
synchronized (mBrokerStatusListeners) {
mBrokerStatusListeners.removeFirstOccurrence(listener);
}
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#removeServerStatusListener(org.marketcetera.client.ServerStatusListener)
*/
@Override
public void removeServerStatusListener(ServerStatusListener inArg0)
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#sendOrder(org.marketcetera.trade.OrderSingle)
*/
@Override
public void sendOrder(OrderSingle inArg0)
throws ConnectionException, OrderValidationException
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#sendOrder(org.marketcetera.trade.OrderReplace)
*/
@Override
public void sendOrder(OrderReplace inArg0)
throws ConnectionException, OrderValidationException
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#sendOrder(org.marketcetera.trade.OrderCancel)
*/
@Override
public void sendOrder(OrderCancel inArg0)
throws ConnectionException, OrderValidationException
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#sendOrderRaw(org.marketcetera.trade.FIXOrder)
*/
@Override
public void sendOrderRaw(FIXOrder inArg0)
throws ConnectionException, OrderValidationException
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#isCredentialsMatch(String, char[])
*/
@Override
public boolean isCredentialsMatch(String inUsername, char[] inPassword)
{
throw new UnsupportedOperationException();
}
@Override
public boolean isServerAlive()
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getUserData()
*/
@Override
public Properties getUserData()
throws ConnectionException
{
return userdata;
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#setUserData(java.util.Properties)
*/
@Override
public void setUserData(Properties inProperties)
throws ConnectionException
{
userdata = inProperties;
}
public Properties userdata;
/**
* reports used to feed report-related calls
*/
private final Set<ReportBase> reports = new TreeSet<ReportBase>(ReportSendingTimeComparator.INSTANCE);
/**
* if non-null, will be thrown during {@link #getReportsSince(Date)}.
*/
private volatile ConnectionException getReportsSinceThrows;
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getCurrencyPositionAsOf(java.util.Date, org.marketcetera.trade.Currency)
*/
@Override
public BigDecimal getCurrencyPositionAsOf(Date inDate,
Currency inCurrency)
throws ConnectionException
{
if(getPositionFails) {
throw new NullPointerException("This exception is expected");
}
Position position = positions.get(inCurrency);
if(position == null) {
return null;
}
return position.getPositionAt(inDate);
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getAllCurrencyPositionsAsOf(java.util.Date)
*/
@Override
public Map<PositionKey<Currency>,BigDecimal> getAllCurrencyPositionsAsOf(Date inDate)
throws ConnectionException
{
if(getPositionFails) {
throw new NullPointerException("This exception is expected");
}
Map<PositionKey<Currency>,BigDecimal> result = new LinkedHashMap<PositionKey<Currency>,BigDecimal>();
for(Map.Entry<Instrument,Position> entry : positions.entrySet()) {
if(entry.getKey() instanceof Currency) {
final Currency currency = (Currency)entry.getKey();
BigDecimal value = getCurrencyPositionAsOf(inDate,
currency);
if(value != null) {
PositionKey<Currency> key = new PositionKey<Currency>() {
@Override
public String getAccount()
{
return null;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return getInstrument().getSymbol();
}
@Override
public Currency getInstrument()
{
return currency;
}
@Override
public String getTraderId()
{
return null;
}
};
result.put(key,
value);
}
}
}
return result;
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#deleteReport(org.marketcetera.trade.ExecutionReportImpl)
*/
@Override
public void deleteReport(ExecutionReportImpl inReport)
throws ConnectionException
{
throw new UnsupportedOperationException();
}
/**
* Sends the given <code>BrokerStatus</code> to registered broker status listeners.
*
* @param inBrokerStatus a <code>BrokerStatus</code> value
*/
public void sendToListeners(BrokerStatus inBrokerStatus)
{
for(BrokerStatusListener brokerStatusListener : mBrokerStatusListeners) {
brokerStatusListener.receiveBrokerStatus(inBrokerStatus);
}
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#resolveSymbol(java.lang.String)
*/
@Override
public Instrument resolveSymbol(String inSymbol)
throws ConnectionException
{
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#getOpenOrders()
*/
@Override
public List<ReportBaseImpl> getOpenOrders()
throws ConnectionException
{
return Collections.emptyList();
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#findRootOrderIdFor(org.marketcetera.trade.OrderID)
*/
@Override
public OrderID findRootOrderIdFor(OrderID inOrderID)
{
throw new UnsupportedOperationException(); // TODO
}
/* (non-Javadoc)
* @see org.marketcetera.client.Client#addReport(org.marketcetera.trade.FIXMessageWrapper, org.marketcetera.trade.BrokerID, org.marketcetera.trade.Hierarchy)
*/
@Override
public void addReport(FIXMessageWrapper inReport,
BrokerID inBrokerID,
Hierarchy inHierarchy)
throws ConnectionException
{
throw new UnsupportedOperationException(); // TODO
}
}
/**
* Compares the sending times of two <code>ReportBase</code> values.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyTestBase.java 16888 2014-04-22 18:32:36Z colin $
* @since 2.1.4
*/
private enum ReportSendingTimeComparator
implements Comparator<ReportBase>
{
INSTANCE;
/* (non-Javadoc)
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
@Override
public int compare(ReportBase inO1,
ReportBase inO2)
{
return inO1.getSendingTime().compareTo(inO2.getSendingTime());
}
}
/**
* Generates a random set of broker status objects.
*
* @return a <code>BrokerStatus</code> value
*/
public static final BrokersStatus generateBrokersStatus()
{
List<BrokerStatus> brokers = new ArrayList<BrokerStatus>();
for(int counter=0;counter<9;counter++) {
brokers.add(new BrokerStatus("Broker-" + System.nanoTime(),
new BrokerID("broker-" + ++counter),
random.nextBoolean()));
}
// make sure at least one broker is logged on
brokers.add(new BrokerStatus("Broker-" + System.nanoTime(),
new BrokerID("broker-10"),
true));
return new BrokersStatus(brokers);
}
/**
* A period of time during which a value is in effect.
*
* <p>This class can be used to track a value which changes over time.
* A series of <code>Interval<T></code> objects can represent
* a value that changes over time by sorting them by the interval
* date. To determine the value of a function represented by a series
* of intervals, find the intersection of the desired date (D) and the interval
* where: D > interval1.getDate() && D < interval2.getDate().
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyTestBase.java 16888 2014-04-22 18:32:36Z colin $
* @since 1.0.0
*/
public static class Interval<T>
implements Comparable<Interval<T>>
{
/**
* the date at which this interval takes effect
*/
private final Date date;
/**
* value for this interval
*/
private final T value;
/**
* Create a new Interval instance.
*
* @param inDate a <code>Date</code> value
* @param inValue a <code>T</code> value
*/
public Interval(Date inDate,
T inValue)
{
assert(inDate != null);
date = inDate;
value = inValue;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((date == null) ? 0 : date.hashCode());
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Interval<?> other = (Interval<?>) obj;
if (date == null) {
if (other.date != null)
return false;
} else if (!date.equals(other.date))
return false;
return true;
}
/**
* Get the date at which this interval takes effect.
*
* @return a <code>Date</code> value
*/
public final Date getDate()
{
return date;
}
/**
* Get the interval value.
*
* @return a <code>T</code> value
*/
public final T getValue()
{
return value;
}
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(Interval<T> inOther)
{
return getDate().compareTo(inOther.getDate());
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return String.format("[%s:%s]",
getDate(),
getValue());
}
}
/**
* A set of intervals representing the change of the position of a security over time.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyTestBase.java 16888 2014-04-22 18:32:36Z colin $
* @since 1.0.0
*/
public static class Position
{
/**
* the set of intervals that define the position change points
*/
private final SortedSet<Interval<BigDecimal>> position = new TreeSet<Interval<BigDecimal>>();
/**
* the instrument for which this position is defined
*/
private final Instrument instrument;
/**
* Create a new Position instance.
*
* <p>The initial position is randomly generated.
*
* @param inInstrument an <code>Instrument</code> value
*/
public Position(Instrument inInstrument)
{
this(inInstrument,
generateRandomPosition());
}
/**
* Create a new Position instance.
*
* @param inInstrument an <code>Instrument</code> value
* @param inStartingPosition a <code>List<Interval<BigDecimal>></code> value as the initial position
*/
public Position(Instrument inInstrument,
List<Interval<BigDecimal>> inStartingPosition)
{
assert(inInstrument != null);
assert(inStartingPosition != null);
instrument = inInstrument;
position.addAll(inStartingPosition);
}
/**
* Adds a data-point to the position.
*
* <p>If the given <code>Date</code> is already present in the position,
* the position will be updated with the new quantity.
*
* @param inDate a <code>Date</code> value
* @param inQuantity a <code>BigDecimal</code> value
*/
public void add(Date inDate,
BigDecimal inQuantity)
{
position.add(new Interval<BigDecimal>(inDate,
inQuantity));
}
/**
* Gets an immutable view of the position.
*
* @return a <code>List<Interval<BigDecimal>></code> value
*/
public List<Interval<BigDecimal>> getPositionView()
{
return Collections.unmodifiableList(new ArrayList<Interval<BigDecimal>>(position));
}
/**
* Gets the position at the given date.
*
* @param inDate a <code>Date</code> value
* @return a <code>BigDecimal</code> value containing the position at the given date
*/
public BigDecimal getPositionAt(Date inDate)
{
Date dataPoint = new Date(inDate.getTime() + 1);
Interval<BigDecimal> point = new Interval<BigDecimal>(dataPoint,
BigDecimal.ZERO);
// if there are no intervals or the asked-for date precedes our first data-point,
// then the position is 0
if(position.isEmpty() ||
position.first().compareTo(point) > 0) {
return BigDecimal.ZERO;
}
SortedSet<Interval<BigDecimal>> earlierIntervals = position.headSet(point);
if(earlierIntervals.isEmpty()) {
// the point asked for is later than all our intervals, return the tail of the master set
return new BigDecimal(position.last().getValue().toString());
} else {
// the point asked for falls somewhere within the intervals, return the last value of the tail set
return new BigDecimal(earlierIntervals.last().getValue().toString());
}
}
/**
* The instrument for this position.
*
* @return an <code>Instrument</code> value
*/
public Instrument getInstrument()
{
return instrument;
}
/**
* Generates a random position.
*
* <p>The position returned is a series of <code>Interval<BigDecimal></code> values
* arranged in chronologically increasing order. The interval values are randomly
* distributed between [-10000,10000). The position will begin at a randomly determined point
* 1-52 weeks before the current time. The minimum granularity of a position change is one
* minute, the maximum is 5 days.
*
* @return a <code>List<Interval<BigDecimal>></code> value
*/
public static final List<Interval<BigDecimal>> generateRandomPosition()
{
final BigDecimal MINUS_ONE = new BigDecimal("-1");
long currentMillis = System.currentTimeMillis();
// start the position 1-52 wks in the past
int seedWeek = random.nextInt(52)+1;
long difference = (long)seedWeek * 1000 * 60 * 60 * 24 * 7;
long seedMillis = currentMillis - difference;
List<Interval<BigDecimal>> position = new ArrayList<Interval<BigDecimal>>();
while(seedMillis < currentMillis) {
position.add(new Interval<BigDecimal>(new Date(seedMillis),
BigDecimalUtils.multiply(BigDecimalUtils.multiply(new BigDecimal(10000),
random.nextDouble()).setScale(0,
RoundingMode.HALF_UP),
(random.nextBoolean() ? MINUS_ONE : BigDecimal.ONE))));
// minimum granularity for a change in position is 1 min, maximum is 5 days (this is entirely arbitrary)
seedMillis += (random.nextInt(1 * 60 * 24 * 5) + 1) * 1000 * 60;
}
return position;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
StringBuffer output = new StringBuffer();
output.append("Position for ").append(getInstrument()).append(SystemUtils.LINE_SEPARATOR);
for(Interval<BigDecimal> interval : position) {
output.append(interval).append(",");
}
return output.toString();
}
}
/**
* Generates positions for the given symbols.
*
* @param inInstruments a <code>List<Instrument></code> value containing the instruments for which to generate positions
* @return a <code>Map<Instrument,Position></code> value containing the generated positions
*/
public static final Map<Instrument,Position> generatePositions(List<Instrument> inInstruments)
{
Map<Instrument,Position> positions = new HashMap<Instrument,Position>();
for(Instrument instrument : inInstruments) {
positions.put(instrument,
new Position(instrument));
}
return positions;
}
/**
* Verifies that the event created contains the expected information.
*
* @param inActualEvent a <code>LogEvent</code> value containing the event to verify
* @param inExpectedLevel a <code>Priority</code> value containing the expected priority
* @param inException a <code>Throwable</code> value containing the expected exception or <code>null</code> for none
* @param inExpectedMessage an <code>I18NMessage</code> value containing the expected message
* @param inExpectedParameters a <code>Serializable[]</code> value containing the expected parameters
*/
public static void verifyEvent(LogEvent inActualEvent,
LogEventLevel inExpectedLevel,
Throwable inException,
I18NMessage inExpectedMessage,
Serializable...inExpectedParameters)
throws Exception
{
assertEquals(inExpectedLevel,
inActualEvent.getLevel());
assertEquals(inException,
inActualEvent.getException());
String messageText = inExpectedMessage.getMessageProvider().getText(inExpectedMessage,
(Object[])inExpectedParameters);
assertEquals(messageText,
inActualEvent.getMessage());
// serialize event
LogEvent serializedEvent = (LogEvent)
SerializationUtils.deserialize
(SerializationUtils.serialize(inActualEvent));
assertEquals(inExpectedLevel,
serializedEvent.getLevel());
if(inException == null) {
assertNull(serializedEvent.getException());
} else {
assertEquals(inException.getMessage(),
serializedEvent.getException().getMessage());
}
assertEquals(messageText,
serializedEvent.getMessage());
}
/**
* Run at the beginning of execution of all tests.
*/
@BeforeClass
public static void once()
throws Exception
{
LoggerConfiguration.logSetup();
try {
ClientManager.setClientFactory(new MockClient.MockClientFactory());
ClientManager.init(null);
} catch (ClientInitException ignored) {}
client = (MockClient)ClientManager.getInstance();
System.setProperty(org.marketcetera.strategy.Strategy.CLASSPATH_PROPERTYNAME,
StrategyTestBase.SAMPLE_STRATEGY_DIR.getCanonicalPath());
List<Instrument> testInstruments = new ArrayList<Instrument>();
testInstruments.add(new Equity("METC"));
testInstruments.add(new Equity("GOOG"));
testInstruments.add(new Equity("YHOO"));
testInstruments.add(new Equity("ORCL"));
testInstruments.add(new Equity("AAPL"));
testInstruments.add(new Equity("JAVA"));
testInstruments.add(new Equity("MSFT"));
testInstruments.add(new Option("METC1",
DateUtils.dateToString(new Date(),
DateUtils.DAYS),
EventTestBase.generateDecimalValue(),
OptionType.Call));
testInstruments.add(new Option("METC2",
DateUtils.dateToString(new Date(),
DateUtils.DAYS),
EventTestBase.generateDecimalValue(),
OptionType.Put));
testInstruments.add(new Option("METC3",
DateUtils.dateToString(new Date(),
DateUtils.DAYS),
EventTestBase.generateDecimalValue(),
OptionType.Call));
testInstruments.add(new Option("METC4",
DateUtils.dateToString(new Date(),
DateUtils.DAYS),
EventTestBase.generateDecimalValue(),
OptionType.Put));
testInstruments.add(new Currency("USD/GBP"));
testInstruments.add(new Currency("USD/JPY"));
testInstruments.add(new Currency("USD/INR"));
roots.putAll("METC",
Arrays.asList(new String[] { "METC1", "METC2", "METC3", "METC4" } ));
underlyings.put("METC1",
"METC");
underlyings.put("METC2",
"METC");
underlyings.put("METC3",
"METC");
underlyings.put("METC4",
"METC");
testInstruments.add(new Option("MSFT1",
DateUtils.dateToString(new Date(),
DateUtils.DAYS),
EventTestBase.generateDecimalValue(),
OptionType.Call));
testInstruments.add(new Option("MSFT2",
DateUtils.dateToString(new Date(),
DateUtils.DAYS),
EventTestBase.generateDecimalValue(),
OptionType.Put));
testInstruments.add(new Option("MSFT3",
DateUtils.dateToString(new Date(),
DateUtils.DAYS),
EventTestBase.generateDecimalValue(),
OptionType.Call));
testInstruments.add(new Option("MSFT4",
DateUtils.dateToString(new Date(),
DateUtils.DAYS),
EventTestBase.generateDecimalValue(),
OptionType.Put));
roots.putAll("MSFT",
Arrays.asList(new String[] { "MSFT1", "MSFT2", "MSFT3", "MSFT4" } ));
underlyings.put("MSFT1",
"MSFT");
underlyings.put("MSFT2",
"MSFT");
underlyings.put("MSFT3",
"MSFT");
underlyings.put("MSFT4",
"MSFT");
positions.putAll(generatePositions(testInstruments));
}
/**
* Run before each test.
*
* @throws Exception if an error occurs
*/
@Before
public void setup()
throws Exception
{
StringBuilder classpath = new StringBuilder();
for(String path : getClassPath()) {
classpath.append(path).append(File.pathSeparator);
}
System.setProperty(JavaCompilerExecutionEngine.CLASSPATH_KEY,
classpath.toString());
brokers = generateBrokersStatus();
MockClient.getBrokersFails = false;
MockClient.getPositionFails = false;
MockClient.addBrokerStatusListenerFails = false;
executionReportMultiplicity = 1;
MockRecorderModule.shouldSendExecutionReports = true;
MockRecorderModule.shouldFullyFillOrders = true;
MockRecorderModule.shouldIgnoreLogMessages = true;
MockRecorderModule.ordersReceived = 0;
getClientFails = false;
final MockClient testClient = new MockClient();
StrategyModule.clientFactory = new ClientFactory() {
@Override
public Client getClient()
throws ClientInitException
{
if(getClientFails) {
throw new ClientInitException(TestMessages.EXPECTED_EXCEPTION);
}
return testClient;
}
};
moduleManager = new ModuleManager();
moduleManager.init();
outputURN = moduleManager.createModule(MockRecorderModule.Factory.PROVIDER_URN);
moduleManager.start(outputURN);
moduleManager.start(bogusDataFeedURN);
factory = new StrategyModuleFactory();
runningModules.clear();
runningModules.add(outputURN);
runningModules.add(bogusDataFeedURN);
setPropertiesToNull();
tradeEvent = EventTestBase.generateEquityTradeEvent(System.nanoTime(),
System.currentTimeMillis(),
new Equity("METC"),
"Q",
new BigDecimal("1000.25"),
new BigDecimal("1000"));
askEvent = EventTestBase.generateEquityAskEvent(System.nanoTime(),
System.currentTimeMillis(),
new Equity("METC"),
"Q",
new BigDecimal("100.00"),
new BigDecimal("10000"));
StrategyDataEmissionModule.setDataToSendToDefaults();
}
/**
* Run after each test.
*
* @throws Exception if an error occurs
*/
@After
public void cleanup()
throws Exception
{
cancelDataFlows(null);
for(ModuleURN strategy : runningModules) {
try {
moduleManager.stop(strategy);
} catch (Exception e) {
// ignore failures, just press ahead
}
}
try {
moduleManager.stop(outputURN);
} catch (ModuleException ignore) {
// ignore failures, just press ahead
}
moduleManager.deleteModule(outputURN);
moduleManager.stop();
}
/**
* Cancels all active data flows.
* @param inStrategyURN a <code>ModuleURN</code> containing a strategy URN for which to cancel flows
* or null to cancel all flows
*/
protected final void cancelDataFlows(ModuleURN inStrategyURN)
{
synchronized(dataFlowsByStrategy) {
Collection<List<DataFlowID>> flowsToCancel;
if(inStrategyURN == null) {
flowsToCancel = dataFlowsByStrategy.values();
} else {
List<DataFlowID> singleList = dataFlowsByStrategy.get(inStrategyURN);
if(singleList == null) {
return;
}
flowsToCancel = new ArrayList<List<DataFlowID>>();
flowsToCancel.add(singleList);
}
for(List<DataFlowID> flows : flowsToCancel) {
for(DataFlowID dataFlow : flows) {
try {
moduleManager.cancel(dataFlow);
} catch (Exception e) {
// ignore all exceptions and keep canceling
}
}
}
dataFlowsByStrategy.clear();
}
}
/**
* Starts the given strategy and hooks it up to the mock ORS client.
*
* @param inStrategyURN a <code>ModuleURN</code> value
* @throws Exception if an error occurs
*/
protected final void startStrategy(ModuleURN inStrategyURN)
throws Exception
{
moduleManager.start(inStrategyURN);
setupMockORSConnection(inStrategyURN);
verifyStrategyReady(inStrategyURN);
}
/**
* Stops the given strategy and cancels all active data flows.
*
* @param inStrategyURN a <code>
* @throws Exception
*/
protected final void stopStrategy(ModuleURN inStrategyURN)
throws Exception
{
cancelDataFlows(null);
moduleManager.stop(inStrategyURN);
verifyStrategyStopped(inStrategyURN);
}
/**
* Sets up a connection to the testing ORSClient for execution reports.
*
* <p>The data flow established will be automatically stopped by invocations of
* {@link #cancelDataFlows(ModuleURN)}.
*
* @param inStrategyURN a <code>ModuleURN</code> connecting the module to which to plumb the ORSClient output
* @return a <code>DataFlowID</code> representing the data flow
* @throws Exception if an error occurs
*/
protected final DataFlowID setupMockORSConnection(ModuleURN inStrategyURN)
throws Exception
{
DataFlowID flowID = moduleManager.createDataFlow(new DataRequest[] { new DataRequest(outputURN),
new DataRequest(inStrategyURN) },
false);
synchronized(dataFlowsByStrategy) {
List<DataFlowID> flows = dataFlowsByStrategy.get(inStrategyURN);
if(flows == null) {
flows = new ArrayList<DataFlowID>();
dataFlowsByStrategy.put(inStrategyURN,
flows);
}
flows.add(flowID);
}
return flowID;
}
/**
* Generates an <code>ExecutionReport</code> from the given <code>OrderSingle</code>.
*
* @param inOrder an <code>OrderSingle</code> value
* @return an <code>ExecutionReport</code> value
* @throws Exception if an error exists
*/
protected static List<ExecutionReport> generateExecutionReports(OrderSingle inOrder)
throws Exception
{
List<ExecutionReport> reports = new ArrayList<ExecutionReport>();
for(Message rawExeReport : generateFixExecutionReports(inOrder)) {
reports.add(org.marketcetera.trade.Factory.getInstance().createExecutionReport(rawExeReport,
inOrder.getBrokerID(),
Originator.Broker,
null,
null));
}
return reports;
}
/**
* Generates FIX <code>Message</code> objects that contain execution reports for partial and/or
* complete fills of the given order.
*
* <p>The number of objects returned can be adjusted by changing the value of {@link #executionReportMultiplicity}.
* Whether or not the list partially or fully fills the given order can be adjusted by changing the
* value of {@link MockRecorderModule#shouldFullyFillOrders}.
*
* @param inOrder an <code>OrderSingle</code> value
* @return a <code>List<Message></code> value
* @throws Exception if an error occurs
*/
protected static List<Message> generateFixExecutionReports(OrderSingle inOrder)
throws Exception
{
int multiplicity = executionReportMultiplicity;
List<Message> reports = new ArrayList<Message>();
if(inOrder.getQuantity() != null) {
BigDecimal totalQuantity = new BigDecimal(inOrder.getQuantity().toString());
BigDecimal lastQuantity = BigDecimal.ZERO;
for(int iteration=0;iteration<multiplicity-1;iteration++) {
BigDecimal thisQuantity = totalQuantity.subtract(totalQuantity.divide(new BigDecimal(Integer.toString(multiplicity))));
totalQuantity = totalQuantity.subtract(thisQuantity);
Message rawExeReport = generateFixExecutionReport(inOrder,
OrdStatus.PARTIALLY_FILLED,
thisQuantity,
lastQuantity,
FIXVersion.FIX44);
reports.add(rawExeReport);
lastQuantity = thisQuantity;
}
Message rawExeReport = generateFixExecutionReport(inOrder,
MockRecorderModule.shouldFullyFillOrders ? OrdStatus.FILLED : OrdStatus.PARTIALLY_FILLED,
totalQuantity,
lastQuantity,
FIXVersion.FIX44);
reports.add(rawExeReport);
}
return reports;
}
/**
* Generates a FIX <code>Message</code> containing an execution report of the given
* status for the given order.
*
* <p><em>Warning</em> - most of the attributes of the FIX message returned are arbitrary and possibly
* incorrect. It is the caller's responsibility to review and modify the returned value
* for the intended purpose.
*
* @param inOrder an <code>OrderSingle</code> value
* @param inOrderStatus a <code>char</code> value corresponding to an {@link OrdStatus} value
* @param inQuantity a <code>BigDecimal</code> value
* @param inLastQuantity a <code>BigDecimal</code> value
* @return a <code>Message</code> value
* @throws Exception if an error occurs
*/
protected static Message generateFixExecutionReport(OrderSingle inOrder,
char inOrderStatus,
BigDecimal inQuantity,
BigDecimal inLastQuantity,
FIXVersion inFIXVersion)
throws Exception
{
Message exeReport = inFIXVersion.getMessageFactory().newExecutionReport(inOrder.getOrderID().toString(),
inOrder.getOrderID().toString(),
"execID",
inOrderStatus,
Side.BUY,
inQuantity,
inOrder.getPrice(),
inLastQuantity,
inOrder.getPrice(),
inOrder.getQuantity(),
inOrder.getPrice(),
inOrder.getInstrument(),
inOrder.getAccount(),
inOrder.getText());
exeReport.setField(new TransactTime(extractTransactTimeFromRunningStrategy()));
return exeReport;
}
/**
* Creates an <code>OrderSingle</code> value with the given <code>OrderID</code>.
*
* <p>If a null <code>OrderID</code> is given, a new <code>OrderID</code> is assigned to
* the <code>OrderSingle</code>.
*
* @param inOrderID an <code>OrderID</code> value or <code>null</code>
* @return an <code>OrderSingle</code> value
*/
protected static OrderSingle createOrderWithID(OrderID inOrderID)
{
OrderSingle order = Factory.getInstance().createOrderSingle();
order.setOrderType(OrderType.Limit);
order.setPrice(new BigDecimal("100.23"));
order.setQuantity(new BigDecimal("10000"));
order.setSide(org.marketcetera.trade.Side.Buy);
order.setInstrument(new Equity("METC"));
if(inOrderID != null) {
order.setOrderID(inOrderID);
}
return order;
}
/**
* Extracts the date used to generate an order from a running strategy, if applicable.
*
* @return a <code>Date</code> value used to generate the most recent order in a running strategy or the current time if none exists
*/
protected static Date extractTransactTimeFromRunningStrategy()
{
String transactTimeString = AbstractRunningStrategy.getProperty("transactTime");
Date transactTime = new Date();
if(transactTimeString != null) {
transactTime = new Date(Long.parseLong(transactTimeString));
}
return transactTime;
}
/**
* Verifies that a strategy module can start and stop with the given parameters.
*
* @param inParameters an <code>Object...</code> value containing the parameters to pass to the module creation command
* @throws Exception if an error occurs
*/
protected void verifyStrategyStartsAndStops(Object...inParameters)
throws Exception
{
ModuleURN urn = createStrategy(inParameters);
moduleManager.stop(urn);
assertFalse(moduleManager.getModuleInfo(urn).getState().isStarted());
moduleManager.deleteModule(urn);
}
/**
* Waits until the given strategy has either started or erred out.
*
* @param inStrategyURN a <code>ModuleURN</code> value
* @throws Exception if an error occurs
*/
protected void verifyStrategyReady(final ModuleURN inStrategyURN)
throws Exception
{
MarketDataFeedTestBase.wait(new Callable<Boolean>() {
@Override
public Boolean call()
throws Exception
{
Status status = getStatus(inStrategyURN);
return status.equals(RUNNING) || status.equals(FAILED);
}
});
}
/**
* Waits until the given strategy has stopped, either with or without error.
*
* @param inStrategyURN a <code>ModuleURN</code> value
* @throws Exception if an error occurs
*/
protected void verifyStrategyStopped(final ModuleURN inStrategyURN)
throws Exception
{
MarketDataFeedTestBase.wait(new Callable<Boolean>() {
@Override
public Boolean call()
throws Exception
{
Status status = getStatus(inStrategyURN);
return status.equals(STOPPED) || status.equals(FAILED);
}
});
}
/**
* Verifies that the given strategy is at the given status.
*
* @param inStrategy a <code>ModuleURN</code> value
* @param inStatus a <code>Status</code> value
* @throws Exception if an error occurs
*/
protected void verifyStrategyStatus(ModuleURN inStrategy,
Status inStatus)
throws Exception
{
assertEquals(inStatus,
getStatus(inStrategy));
}
/**
* Returns the status of the given strategy.
*
* @param inStrategy a <code>ModuleURN</code> value
* @return a <code>Status</code> value
* @throws Exception if an error occurs
*/
protected Status getStatus(ModuleURN inStrategy)
throws Exception
{
return Status.valueOf(getMXProxy(inStrategy).getStatus());
}
/**
* Asserts that the values in the common strategy storage area for some well-known testing keys are null.
*/
protected void verifyNullProperties()
{
verifyPropertyNull("onAsk");
verifyPropertyNull("onBid");
verifyPropertyNull("onCancel");
verifyPropertyNull("onDividend");
verifyPropertyNull("onExecutionReport");
verifyPropertyNull("onOther");
verifyPropertyNull("onTrade");
}
/**
* Asserts that the values in the common strategy storage area for some well-known testing keys are not null.
* @throws Exception if an error occurs
*/
protected void verifyNonNullProperties()
throws Exception
{
verifyPropertyNonNull("onAsk");
verifyPropertyNonNull("onBid");
verifyPropertyNonNull("onCancel");
verifyPropertyNonNull("onDividend");
verifyPropertyNonNull("onExecutionReport");
verifyPropertyNonNull("onOther");
verifyPropertyNonNull("onTrade");
}
/**
* Sets the values in the common strategy storage area for some well-known testing keys to null.
*/
protected void setPropertiesToNull()
{
Properties properties = AbstractRunningStrategy.getProperties();
properties.clear();
verifyNullProperties();
}
/**
* Verifies the given property is non-null.
*
* @param inKey a <code>String</code> value
* @return a <code>String</code> value or null
* @throws Exception if an error occurs
*/
protected String verifyPropertyNonNull(final String inKey)
throws Exception
{
MarketDataFeedTestBase.wait(new Callable<Boolean>() {
@Override
public Boolean call()
throws Exception
{
return AbstractRunningStrategy.getProperty(inKey) != null;
}
});
return AbstractRunningStrategy.getProperty(inKey);
}
/**
* Verifies the given property is null.
*
* @param inKey a <code>String</code> value
*/
protected void verifyPropertyNull(String inKey)
{
Properties properties = AbstractRunningStrategy.getProperties();
assertNull(inKey + " is supposed to be null",
properties.getProperty(inKey));
}
/**
* Creates a strategy with the given parameters.
*
* <p>The strategy is guaranteed to be running at the successful exit of this method. Strategies created by this method
* are tracked and shut down, if necessary, at the end of the test.
*
* @param inParameters an <code>Object...</code> value containing the parameters to pass to the module creation command
* @return a <code>ModuleURN</code> value containing the URN of the strategy
* @throws Exception if an error occurs
*/
protected ModuleURN createStrategy(Object...inParameters)
throws Exception
{
verifyNullProperties();
LinkedList<Object> actualParameters = new LinkedList<Object>(Arrays.asList(inParameters));
if(inParameters.length <= 6) {
actualParameters.addFirst(null);
}
ModuleURN strategyURN = createModule(StrategyModuleFactory.PROVIDER_URN,
actualParameters.toArray());
theStrategy = strategyURN;
verifyStrategyReady(strategyURN);
return strategyURN;
}
/**
* Creates and starts a module with the given URN and the given parameters.
*
* <p>The module is guaranteed to be running at the successful exit of this method. Modules created by this method
* are tracked and shut down, if necessary, at the end of the test.
*
* @param inProvider a <code>ModuleURN</code> value
* @param inParameters an <code>Object...</code> value containing the parameters to pass to the module creation command
* @return a <code>ModuleURN</code> value containing the URN of the strategy
* @throws Exception if an error occurs
*/
protected ModuleURN createModule(ModuleURN inProvider,
Object...inParameters)
throws Exception
{
ModuleURN urn = moduleManager.createModule(inProvider,
inParameters);
assertFalse(moduleManager.getModuleInfo(urn).getState().isStarted());
moduleManager.start(urn);
assertTrue(moduleManager.getModuleInfo(urn).getState().isStarted());
runningModules.add(urn);
return urn;
}
/**
* Returns an <code>MXBean</code> interface to the given strategy.
*
* @param inModuleURN a <code>ModuleURN</code> value containing a strategy
* @return a <code>StrategyMXBean</code> value
* @throws Exception if an error occurs
*/
protected StrategyMXBean getMXProxy(ModuleURN inModuleURN)
throws Exception
{
ObjectName objectName = inModuleURN.toObjectName();
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
return JMX.newMXBeanProxy(server,
objectName,
StrategyMXBean.class,
true);
}
/**
* Gets a handle to the given strategy;
*
* @param inStrategyURN a <code>ModuleURN</code> value
*
* <p>Note that this method will <em>fail</em> if the given strategy is not running
*
* @return a <code>StrategyImpl</code> value
*/
protected final StrategyImpl getRunningStrategy(ModuleURN inStrategyURN)
{
Set<StrategyImpl> runningStrategies = StrategyImpl.getRunningStrategies();
for(StrategyImpl runningStrategy : runningStrategies) {
if(runningStrategy.getDefaultNamespace().equals(inStrategyURN.instanceName())) {
return runningStrategy;
}
}
fail(inStrategyURN + " not currently running");
return null;
}
/**
* Constructs a classpath to use for Java compilation.
*
* <p>This method will make a best-effort to create the classpath,
* ignoring errors that occur during the collection. This method
* is not expected to throw exceptions, muddling on instead.
*
* @return a <code>Set<String></code> value
*/
private Set<String> getClassPath()
{
// get the classloader that was used to load this class
ClassLoader classLoader = getClass().getClassLoader();
// this collection will hold all the paths we find, duplicates discarded, in the order they appear
Set<String> paths = new LinkedHashSet<String>();
// //Collect all URLs from the URL Class Loaders.
// do {
// if(classLoader instanceof URLClassLoader) {
// URLClassLoader urlClassLoader = (URLClassLoader)classLoader;
// for(URL url : urlClassLoader.getURLs()) {
// try {
// paths.add(url.toURI().getPath());
// } catch (URISyntaxException ignore) {
// }
// }
// }
// // traverse the classloader tree upwards until no more remain
// } while((classLoader = classLoader.getParent()) != null);
// // reset the classloader to the current
// classLoader = getClass().getClassLoader();
//iterate through the manifests of all the jars to find the
// values of their Class-Path attribute value and add them to the
// set.
try {
Enumeration<URL> resourceEnumeration = classLoader.getResources("META-INF/MANIFEST.MF");
while(resourceEnumeration.hasMoreElements()) {
URL resourceURL = resourceEnumeration.nextElement();
InputStream is = null;
try {
// open the resource
is = resourceURL.openStream();
Manifest manifest = new Manifest(is);
String theClasspath = manifest.getMainAttributes().getValue("Class-Path");
if(theClasspath != null &&
!theClasspath.trim().isEmpty()) {
//manifest classpath is space separated URLs
for(String path : theClasspath.split(" ")) {
try {
URL pathURL = new URL(path);
paths.add(pathURL.toURI().getPath());
} catch (MalformedURLException ignore) {
} catch (URISyntaxException ignore) {
}
}
}
} catch (IOException ignore) {
} finally {
if(is != null) {
try {
is.close();
} catch (IOException ignore) {
}
}
}
}
} catch (IOException ignore) {
}
return paths;
}
/**
* random number generator for public use
*/
public static final Random random = new Random(System.nanoTime());
/**
* indicates if the getClient call in the StrategyModule should fail
*/
protected static boolean getClientFails;
/**
* global singleton module manager
*/
protected ModuleManager moduleManager;
/**
* the factory to use to create the market data provider modules
*/
protected ModuleFactory factory;
/**
* test destination of output
*/
protected ModuleURN outputURN;
/**
* list of strategies started during test
*/
protected final List<ModuleURN> runningModules = new ArrayList<ModuleURN>();
/**
* data flows by the strategy that caused their creation
*/
private final Map<ModuleURN,List<DataFlowID>> dataFlowsByStrategy = new HashMap<ModuleURN,List<DataFlowID>>();
/**
* URN for market data provider
*/
protected final ModuleURN bogusDataFeedURN = BogusFeedModuleFactory.INSTANCE_URN;
/**
* trade event with generic information
*/
protected TradeEvent tradeEvent;
/**
* ask event with generic information
*/
protected AskEvent askEvent;
/**
* can be used to track a central strategy
*/
protected ModuleURN theStrategy;
/**
* positions for a set of symbols
*/
protected final static Map<Instrument,Position> positions = new LinkedHashMap<Instrument,Position>();
/**
* list of option roots for a given single underlying symbol
*/
protected final static Multimap<String,String> roots = LinkedHashMultimap.create();
/**
* the underlying symbol for each root
*/
protected final static Map<String,String> underlyings = new LinkedHashMap<String,String>();
/**
* a set of test brokers
*/
protected static BrokersStatus brokers;
/**
* determines how many execution reports should be produced for each order received
*/
protected static int executionReportMultiplicity = 1;
/**
* test client used to simulate connections to the server
*/
protected static MockClient client;
}