package org.marketcetera.strategy;
import static org.marketcetera.strategy.Messages.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.marketcetera.client.ClientInitException;
import org.marketcetera.client.ClientManager;
import org.marketcetera.client.OrderValidationException;
import org.marketcetera.client.Validations;
import org.marketcetera.client.brokers.BrokerStatus;
import org.marketcetera.client.utils.LiveOrderHistoryManager;
import org.marketcetera.core.notifications.Notification;
import org.marketcetera.core.position.PositionKey;
import org.marketcetera.event.Event;
import org.marketcetera.event.impl.LogEventBuilder;
import org.marketcetera.marketdata.MarketDataRequest;
import org.marketcetera.marketdata.MarketDataRequestBuilder;
import org.marketcetera.module.DataFlowID;
import org.marketcetera.module.DataFlowSupport;
import org.marketcetera.module.DataRequest;
import org.marketcetera.module.ModuleURN;
import org.marketcetera.trade.*;
import org.marketcetera.trade.Currency;
import org.marketcetera.util.collections.UnmodifiableDeque;
import org.marketcetera.util.log.SLF4JLoggerProxy;
import org.marketcetera.util.misc.ClassVersion;
import org.marketcetera.util.misc.NamedThreadFactory;
import quickfix.Message;
/* $License$ */
/**
* Base class for running strategies.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: AbstractRunningStrategy.java 16864 2014-03-20 19:39:48Z colin $
* @since 1.0.0
*/
@ClassVersion("$Id: AbstractRunningStrategy.java 16864 2014-03-20 19:39:48Z colin $")
public abstract class AbstractRunningStrategy
implements RunningStrategy
{
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public final String toString()
{
if(strategy == null) {
return super.toString();
}
return strategy.toString();
}
/**
* Gets the shared properties store.
*
* <p>All running strategies have access to this properties store. Changes
* made to the object returned from this method will effect the original
* object.
*
* @return a <code>Properties</code> value
*/
static final Properties getProperties()
{
return properties;
}
/**
* Sets the strategy object associated with this {@link RunningStrategy}.
*
* @param inStrategy a <code>Strategy</code> value
*/
final void setStrategy(Strategy inStrategy)
{
strategy = inStrategy;
}
/**
* Called when the <code>AbstractRunningStrategy</code> starts.
* @throws ClientInitException if an error occurs during start
*/
final void start()
throws ClientInitException
{
synchronized(AbstractRunningStrategy.class) {
if(orderHistoryManager == null) {
initializeReportHistoryManager();
}
}
// Add the strategy as a broker status listener
ClientManager.getInstance().addBrokerStatusListener(this);
}
/**
* Indicates to the <code>AbstractRunningStrategy</code> that it should stop running now.
*/
final void stop()
{
// no new callbacks will be allowed
callbackService.shutdown();
// terminate existing callbacks, best effort
callbackService.shutdownNow();
// Delete the strategy as a broker status listener
try {
ClientManager.getInstance().removeBrokerStatusListener(this);
} catch(ClientInitException e) {
throw new RuntimeException(e);
}
}
/**
* Provides a non-overridable route for {@link ExecutionReport} data to
* allow this object to process the data before handing it to the strategy.
*
* @param inExecutionReport an <code>ExecutionReport</code> value
*/
final void onExecutionReportRedirected(ExecutionReport inExecutionReport)
{
// record the execution report
orderHistoryManager.add(inExecutionReport);
// now notify the strategy
onExecutionReport(inExecutionReport);
}
/**
* Provides a non-overridable route for {@link OrderCancelReject} data to
* allow this object to process the data before handing it to the strategy.
*
* @param inCancelReject an <code>OrderCanceleject</code> value
*/
final void onCancelRejectRedirected(OrderCancelReject inCancelReject)
{
// record the order cancel reject
orderHistoryManager.add(inCancelReject);
// now notify the strategy
onCancelReject(inCancelReject);
}
/**
* Supplies a broker status to the strategy.
*
* @param inStatus The status.
*/
public void receiveBrokerStatus(BrokerStatus inStatus)
{
try {
// notify the strategy
onReceiveBrokerStatus(inStatus);
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(BROKER_STATUS_PROCESS_FAILED,
String.valueOf(strategy), String.valueOf(inStatus)).create(),
strategy);
}
}
/**
* Returns the list of open orders created during this session in the order they
* were submitted.
*
* <p>Returns all orders regardless of their state.
*
* @return a <code>Set<OrderSingle></code> value
*/
final Set<OrderSingle> getSubmittedOrders()
{
return Collections.unmodifiableSet(submittedOrders);
}
/**
* Returns the list of open order IDs created during this session in the order they
* were submitted.
*
* <p>Returns all order IDs regardless of their state.
*
* @return a <code>Set<OrderID></code> value
*/
protected final Set<OrderID> getSubmittedOrderIDs()
{
return Collections.unmodifiableSet(submittedOrderIDs);
}
/**
* Returns the list of <code>OrderID</code> values for open orders created in this
* session in the order they were submitted.
*
* <p>Returns IDs of open orders only. Orders that were canceled, replaced, filled, or
* otherwise are no longer open will not be returned. For orders submitted
* via {@link AbstractRunningStrategy#cancelReplace(OrderID, OrderSingle, boolean)},
* the ID of the {@link OrderReplace} value sent to the broker is returned, not the
* {@link OrderSingle} value used to create the <code>OrderReplace</code>.
*
* @return a <code>Set<OrderID></code> value
*/
protected final Set<OrderID> getOpenOrderIDs()
{
return orderHistoryManager.getOpenOrders().keySet();
}
/**
* Gets the collection of open orders represented by the most recent <code>ExecutionReport</code>.
*
* @return a <code>Collection<ExecutionReport></code> value
*/
protected final Collection<ExecutionReport> getOpenOrders()
{
return orderHistoryManager.getOpenOrders().values();
}
/**
* Gets the <code>OrderStatus</code> for the given <code>OrderID</code>.
*
* <p>The given <code>OrderID</code> may be any part of the order chain. For example, if an order is replaced,
* either the original <code>OrderID</code> or the current <code>OrderID</code> will return the same value,
* although only the current <code>OrderID</code> is open.
*
* @param inOrderID an <code>OrderID</code> value or <code>null</code> if the given order cannot be found
* @return an <code>OrderStatus</code> value
*/
protected final OrderStatus getOrderStatus(OrderID inOrderID)
{
ReportBase latestReport = orderHistoryManager.getLatestReportFor(inOrderID);
if(latestReport == null) {
return null;
}
return latestReport.getOrderStatus();
}
/**
* Sets the given key to the given value.
*
* <p>All running strategies have access to this properties store.
*
* @param inKey a <code>String</code> value
* @param inValue a <code>String</code> value
*/
protected static void setProperty(String inKey,
String inValue)
{
if(inKey == null) {
NULL_PROPERTY_KEY.warn(AbstractRunningStrategy.class);
return;
}
if(inValue == null) {
properties.remove(inKey);
return;
}
properties.setProperty(inKey,
inValue);
}
/**
* Gets the value associated with the given key.
*
* <p>All running strategies have access to this properties store.
*
* @param inKey a <code>String</code> value
*/
protected static String getProperty(String inKey)
{
if(inKey == null) {
NULL_PROPERTY_KEY.warn(AbstractRunningStrategy.class);
return null;
}
return properties.getProperty(inKey);
}
/**
* Gets the report history origin date to use for the order history.
*
* <p>Strategies may override this method to return a date. For performance
* reasons, it is best to use the most recent date possible. The default is
* to return the first second of the current day.
*
* <p>All strategies in the same strategy agent share the same order history manager.
* The report history origin date can be set only by the first strategy to run.
*
* @return a <code>Date</code> value
*/
protected Date getReportHistoryOriginDate()
{
Calendar dateGenerator = Calendar.getInstance();
dateGenerator.set(Calendar.HOUR_OF_DAY,
0);
dateGenerator.set(Calendar.MINUTE,
0);
dateGenerator.set(Calendar.SECOND,
0);
dateGenerator.set(Calendar.MILLISECOND,
0);
Date originDate = dateGenerator.getTime();
return originDate;
}
/**
* Gets the parameter associated with the given name.
*
* @param inName a <code>String</code> value containing the key of a parameter key/value value
* @return a <code>String</code> value or null if no parameter is associated with the given name
*/
protected final String getParameter(String inName)
{
Properties parameters = strategy.getParameters();
if(parameters == null) {
NO_PARAMETERS.warn(AbstractRunningStrategy.class,
strategy);
return null;
}
return parameters.getProperty(inName);
}
/**
* Requests market data.
*
* @param inRequest a <code>MarketDataRequest</code> value containing the request to execute
* @return an <code>int</code> value containing the handle of the request or 0 if the request failed
*/
protected final int requestMarketData(MarketDataRequest inRequest)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return 0;
}
if(inRequest == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_MARKET_DATA_REQUEST,
String.valueOf(strategy),
inRequest).create(),
strategy);
return 0;
}
try {
StrategyModule.log(LogEventBuilder.debug().withMessage(SUBMITTING_MARKET_DATA_REQUEST,
String.valueOf(strategy),
String.valueOf(inRequest)).create(),
strategy);
return strategy.getServicesProvider().requestMarketData(inRequest);
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_MARKET_DATA_REQUEST,
String.valueOf(strategy),
inRequest)
.withException(e).create(),
strategy);
return 0;
}
}
/**
* Requests market data.
*
* @param inRequest a <code>String</code> value containing the representation of a {@link MarketDataRequest} to execute
* @return an <code>int</code> value containing the handle of the request or 0 if the request failed
*/
protected final int requestMarketData(String inRequest)
{
try {
MarketDataRequest request = MarketDataRequestBuilder.newRequestFromString(inRequest);
StrategyModule.log(LogEventBuilder.debug().withMessage(SUBMITTING_MARKET_DATA_REQUEST,
String.valueOf(strategy),
request).create(),
strategy);
return strategy.getServicesProvider().requestMarketData(request);
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_MARKET_DATA_REQUEST,
String.valueOf(strategy),
inRequest)
.withException(e).create(),
strategy);
return 0;
}
}
/**
* Requests market data processed by the given complex event processor.
*
* @param inRequest a <code>MarketDataRequest</code> value containing the request to execute
* @param inStatements a <code>String[]</code> value containing the statements to pass to the
* complex event processor. The meaning of the statements varies according to the actual
* event processor that handles them.
* @param inCepSource a <code>String</code> value containing the name of the complex event processor
* to which to send the query request
* @return an <code>int</code> value containing the handle of the request or 0 if the request failed
*/
protected final int requestProcessedMarketData(MarketDataRequest inRequest,
String[] inStatements,
String inCepSource)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return 0;
}
if(inRequest == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_MARKET_DATA_REQUEST,
String.valueOf(strategy),
inRequest).create(),
strategy);
return 0;
}
if(inStatements == null ||
inStatements.length == 0 ||
inCepSource == null ||
inCepSource.isEmpty()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_CEP_REQUEST,
String.valueOf(strategy),
Arrays.toString(inStatements),
inCepSource,
strategy.getDefaultNamespace()).create(),
strategy);
return 0;
}
String namespace = strategy.getDefaultNamespace();
try {
// retrieve CEP default namespace
StrategyModule.log(LogEventBuilder.debug().withMessage(SUBMITTING_PROCESSED_MARKET_DATA_REQUEST,
String.valueOf(strategy),
String.valueOf(inRequest),
Arrays.toString(inStatements),
inCepSource,
namespace).create(),
strategy);
return strategy.getServicesProvider().requestProcessedMarketData(inRequest,
inStatements,
inCepSource,
namespace);
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(COMBINED_DATA_REQUEST_FAILED,
String.valueOf(inRequest),
Arrays.toString(inStatements),
inCepSource,
namespace)
.withException(e).create(),
strategy);
return 0;
}
}
/**
* Requests market data processed by the given complex event processor from the given source.
*
* @param inRequest a <code>String</code> value containing the representation of a {@link MarketDataRequest} to execute
* @param inStatements a <code>String[]</code> value containing the statements to pass to the
* complex event processor. The meaning of the statements varies according to the actual
* event processor that handles them.
* @param inCepSource a <code>String</code> value containing the name of the complex event processor
* to which to send the query request
* @return an <code>int</code> value containing the handle of the request or 0 if the request failed
*/
protected final int requestProcessedMarketData(String inRequest,
String[] inStatements,
String inCepSource)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return 0;
}
if(inRequest == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_MARKET_DATA_REQUEST,
String.valueOf(strategy),
inRequest).create(),
strategy);
return 0;
}
if(inStatements == null ||
inStatements.length == 0 ||
inCepSource == null ||
inCepSource.isEmpty()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_CEP_REQUEST,
String.valueOf(strategy),
Arrays.toString(inStatements),
inCepSource,
strategy.getDefaultNamespace()).create(),
strategy);
return 0;
}
String namespace = strategy.getDefaultNamespace();
try {
// retrieve CEP default namespace
StrategyModule.log(LogEventBuilder.debug().withMessage(SUBMITTING_PROCESSED_MARKET_DATA_REQUEST,
String.valueOf(strategy),
String.valueOf(inRequest),
Arrays.toString(inStatements),
inCepSource,
namespace).create(),
strategy);
return strategy.getServicesProvider().requestProcessedMarketData(MarketDataRequestBuilder.newRequestFromString(inRequest),
inStatements,
inCepSource,
namespace);
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(COMBINED_DATA_REQUEST_FAILED,
String.valueOf(inRequest),
Arrays.toString(inStatements),
inCepSource,
namespace)
.withException(e).create(),
strategy);
return 0;
}
}
/**
* Cancels the given data request.
*
* @param inRequestID an <code>int</code> value containing the identifier of the data request to cancel
*/
protected final void cancelDataRequest(int inRequestID)
{
StrategyModule.log(LogEventBuilder.debug().withMessage(CANCELING_DATA_REQUEST,
String.valueOf(strategy),
String.valueOf(inRequestID)).create(),
strategy);
strategy.getServicesProvider().cancelDataRequest(inRequestID);
}
/**
* Cancels all data requests from this strategy.
*/
protected final void cancelAllDataRequests()
{
StrategyModule.log(LogEventBuilder.debug().withMessage(CANCELING_ALL_DATA_REQUESTS,
String.valueOf(strategy)).create(),
strategy);
strategy.getServicesProvider().cancelAllDataRequests();
}
/**
* Creates a complex event processor query.
*
* @param inStatements a <code>String[]</code> value containing the statements to pass to the
* complex event processor. The meaning of the statements varies according to the actual
* event processor that handles them.
* @param inSource a <code>String</code> value containing the name of the complex event processor
* to which to send the query request
* @return an <code>int</code> value containing the identifier of this request or 0 if the request
* failed
*/
protected final int requestCEPData(String[] inStatements,
String inSource)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return 0;
}
if(inStatements == null ||
inStatements.length == 0 ||
inSource == null ||
inSource.isEmpty()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_CEP_REQUEST,
String.valueOf(strategy),
Arrays.toString(inStatements),
inSource,
strategy.getDefaultNamespace()).create(),
strategy);
return 0;
}
try {
StrategyModule.log(LogEventBuilder.debug().withMessage(SUBMITTING_CEP_REQUEST,
String.valueOf(strategy),
Arrays.toString(inStatements),
inSource,
strategy.getDefaultNamespace()).create(),
strategy);
return strategy.getServicesProvider().requestCEPData(inStatements,
inSource,
strategy.getDefaultNamespace());
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CEP_REQUEST_FAILED,
Arrays.toString(inStatements),
inSource)
.withException(e).create(),
strategy);
return 0;
}
}
/**
* Gets the <code>ReportBase</code> values representing the order history of the given <code>OrderID</code>.
*
* <p>The <code>ReportBase</code> objects returned by this call represent the history of the order represented
* by the given <code>OrderID</code>. if there is no order history for the given <code>OrderID</code>, this operation
* will return an empty collection.
*
* <p>The values returned by this operation are sorted from newest to oldest: the order's current status is represented
* by the first element in the collection.
*
* <p>The collection returned by this operation will be updated as the underlying report history changes. The collection itself
* may not be modified.
*
* <p>The contents of the returned collection are limited by the value returned by {@link #getReportHistoryOriginDate()}. The
* default value is all reports. No reports with a sending time before the origin date will be returned.
*
* @param inOrderID an <code>OrderID</code> value corresponding to an existing order, either open or closed
* @return a <code>Deque<ReportBase></code> value containing the <code>ReportBase</code> objects
*/
protected final Deque<ReportBase> getExecutionReports(OrderID inOrderID)
{
if (inOrderID == null) {
return EMPTY_REPORTS;
}
return orderHistoryManager.getReportHistoryFor(inOrderID);
}
/**
* Suggests a trade.
*
* @param inOrder an <code>OrderSingle</code> value containing the trade to suggest
* @param inScore a <code>BigDecimal</code> value containing the score of this suggestion. this value is determined by the user
* but is recommended to fit in the interval [0..1]
* @param inIdentifier a <code>String</code> value containing a user-specified string to identify the suggestion
*/
protected final void suggestTrade(OrderSingle inOrder,
BigDecimal inScore,
String inIdentifier)
{
if(inOrder == null ||
inScore == null ||
inIdentifier == null ||
inIdentifier.isEmpty()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_TRADE_SUGGESTION,
String.valueOf(strategy)).create(),
strategy);
return;
}
assert(strategy != null);
OrderSingleSuggestion suggestion = Factory.getInstance().createOrderSingleSuggestion();
suggestion.setOrder(inOrder);
suggestion.setScore(inScore);
suggestion.setIdentifier(inIdentifier);
StrategyModule.log(LogEventBuilder.debug().withMessage(SUBMITTING_TRADE_SUGGESTION,
String.valueOf(strategy),
suggestion).create(),
strategy);
strategy.getServicesProvider().sendSuggestion(suggestion);
}
/**
* Sends an order to order subscribers.
*
* <p><code>OrderSingle</code> objects passed to this method will be added to the list of submitted orders
* but other object types will not. In order to track, for example, <code>OrderReplace</code> and <code>OrderCancel</code>
* objects, they must have first been created via {@link #cancelReplace(OrderID, OrderSingle, boolean)} and
* {@link #cancelOrder(OrderID, boolean)} respectively.
*
* @param inData an <code>Object</code> value
* @return a <code>boolean</code> value indicating whether the object was successfully transmitted or not
*/
protected boolean send(Object inData)
{
if(inData == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_DATA,
String.valueOf(strategy)).create(),
strategy);
return false;
}
if(inData instanceof OrderSingle) {
OrderSingle order = (OrderSingle)inData;
if(order.getOrderID() == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_ORDER,
String.valueOf(strategy)).create(),
strategy);
return false;
}
try {
Validations.validate(order);
} catch (OrderValidationException e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(ORDER_VALIDATION_FAILED,
String.valueOf(strategy))
.withException(e).create(),
strategy);
return false;
}
StrategyModule.log(LogEventBuilder.debug().withMessage(SUBMITTING_ORDER,
String.valueOf(strategy),
order,
order.getOrderID()).create(),
strategy);
SLF4JLoggerProxy.debug(AbstractRunningStrategy.class,
"{} created {}", //$NON-NLS-1$
strategy,
order);
submittedOrders.add(order);
}
strategy.getServicesProvider().send(inData);
return true;
}
/**
* Submits a request to cancel the <code>OrderSingle</code> with the given <code>OrderID</code>.
*
* <p>The order must currently be open or this operation will fail. Note that the strategy's concept of
* open orders is based on its report history origin date as {@link #getReportHistoryOriginDate() specified}.
*
* @param inOrderID an <code>OrderID</code> value containing the ID of the open order to cancel
* @param inSendOrder a <code>boolean</code> value indicating whether the <code>OrderCancel</code> should be submitted or just returned to the caller
* If <code>false</code>, it is the caller's responsibility to submit the <code>OrderCancel</code> with {@link #send(Object)}.
* @return an <code>OrderCancel</code> value containing the cancel order or <code>null</code> if the <code>OrderCancel</code> could not be constructed
*/
protected final OrderCancel cancelOrder(OrderID inOrderID,
boolean inSendOrder)
{
if(inOrderID == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_CANCEL,
String.valueOf(strategy)).create(),
strategy);
return null;
}
ExecutionReport report = orderHistoryManager.getOpenOrders().get(inOrderID);
if(report == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_ORDERID,
String.valueOf(strategy),
String.valueOf(inOrderID)).create(),
strategy);
return null;
}
OrderCancel cancelRequest = Factory.getInstance().createOrderCancel(report);
// set the BrokerOrderID to null (EG-762) because the ER acks we get back from the ORS (server)
// do not have the BrokerOrderID set correctly. it is OK to send no BrokerOrderID, but it is
// not OK to send an invalid BrokerOrderID.
cancelRequest.setBrokerOrderID(null);
SLF4JLoggerProxy.debug(AbstractRunningStrategy.class,
"{} created {}", //$NON-NLS-1$
strategy,
cancelRequest);
if(inSendOrder) {
StrategyModule.log(LogEventBuilder.debug().withMessage(SUBMITTING_CANCEL_ORDER_REQUEST,
String.valueOf(strategy),
String.valueOf(cancelRequest)).create(),
strategy);
strategy.getServicesProvider().cancelOrder(cancelRequest);
}
return cancelRequest;
}
/**
* Submits cancel requests for all <code>OrderSingle</code> open orders owned by the strategy's owner.
*
* <p> This method will make a best-effort attempt to cancel all orders. If an
* attempt to cancel one order fails, that order will be skipped and the
* others will still be attempted in their turn.
*
* @return an <code>int</code> value containing the number of orders for which cancels were submitted
*/
protected final int cancelAllOrders()
{
StrategyModule.log(LogEventBuilder.debug().withMessage(SUBMITTING_CANCEL_ALL_ORDERS_REQUEST,
String.valueOf(strategy)).create(),
strategy);
// gets a copy of the open orders list - iterate over a copy in order to prevent concurrent update problems
Set<OrderID> openOrdersCopy = new HashSet<OrderID>(orderHistoryManager.getOpenOrders().keySet());
SLF4JLoggerProxy.debug(AbstractRunningStrategy.class,
"Found {} open orders to cancel",
openOrdersCopy);
int count = 0;
for(OrderID orderId : openOrdersCopy) {
try {
if(cancelOrder(orderId,
true) != null) {
count += 1;
}
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(ORDER_CANCEL_FAILED,
String.valueOf(strategy),
orderId)
.withException(e).create(),
strategy);
}
}
StrategyModule.log(LogEventBuilder.debug().withMessage(CANCEL_REQUEST_SUBMITTED,
String.valueOf(strategy),
count).create(),
strategy);
return count;
}
/**
* Submits a cancel-replace order for the given <code>OrderID</code> with
* the given <code>Order</code>.
*
* <p>The order must be open or this call will have no effect.
*
* <p>If <code>inSendOrder</code> is <code>false</code>, it is the caller's responsibility
* to submit the <code>OrderReplace</code>.
*
* @param inOrderID an <code>OrderID</code> value containing the order to cancel
* @param inNewOrder an <code>OrderSingle</code> value containing the order with which to replace the existing order
* @param inSendOrder a <code>boolean</code> value indicating whether the <code>OrderReplace</code> should be submitted or just returned to the caller. If <code>false</code>,
* it is the caller's responsibility to submit the <code>OrderReplace</code> with {@link #send(Object)}.
* @return an <code>OrderReplace</code> value containing the new order or <code>null</code> if the old order could not be canceled and the new one could not be sent
*/
protected final OrderReplace cancelReplace(OrderID inOrderID,
OrderSingle inNewOrder,
boolean inSendOrder)
{
if(inOrderID == null ||
inNewOrder == null ||
inNewOrder.getOrderID() == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_REPLACEMENT_ORDER,
String.valueOf(strategy)).create(),
strategy);
return null;
}
ExecutionReport executionReport = orderHistoryManager.getOpenOrders().get(inOrderID);
if(executionReport == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_ORDERID,
String.valueOf(strategy),
String.valueOf(inOrderID)).create(),
strategy);
return null;
}
OrderReplace replaceOrder = Factory.getInstance().createOrderReplace(executionReport);
// set the BrokerOrderID to null (EG-762) because the ER acks we get back from the ORS (server)
// do not have the BrokerOrderID set correctly. it is OK to send no BrokerOrderID, but it is
// not OK to send an invalid BrokerOrderID.
replaceOrder.setBrokerOrderID(null);
replaceOrder.setQuantity(inNewOrder.getQuantity());
replaceOrder.setDisplayQuantity(inNewOrder.getDisplayQuantity());
// special case: when replacing a market order, it is absolutely verbotten to specify a price
if(OrderType.Market.equals(executionReport.getOrderType())) {
replaceOrder.setPrice(null);
} else {
replaceOrder.setPrice(inNewOrder.getPrice());
}
replaceOrder.setTimeInForce(inNewOrder.getTimeInForce());
SLF4JLoggerProxy.debug(AbstractRunningStrategy.class,
"{} created {}", //$NON-NLS-1$
strategy,
replaceOrder);
if(inSendOrder) {
StrategyModule.log(LogEventBuilder.debug().withMessage(SUBMITTING_CANCEL_REPLACE_REQUEST,
String.valueOf(strategy),
String.valueOf(replaceOrder)).create(),
strategy);
strategy.getServicesProvider().cancelReplace(replaceOrder);
}
return replaceOrder;
}
/**
* Sends a FIX message.
*
* @param inMessage a <code>Message</code> value
* @param inBroker a <code>BrokerID</code> value
*/
protected final void sendMessage(Message inMessage,
BrokerID inBroker)
{
if(inMessage == null ||
inBroker == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_MESSAGE,
String.valueOf(strategy)).create(),
strategy);
return;
}
StrategyModule.log(LogEventBuilder.debug().withMessage(SUBMITTING_FIX_MESSAGE,
String.valueOf(strategy),
inMessage,
inBroker).create(),
strategy);
strategy.getServicesProvider().sendMessage(inMessage,
inBroker);
}
/**
* Sends the given event to the CEP module indicated by the provider.
*
* <p>The corresponding CEP module must already exist or the message will not be sent.
*
* @param inEvent an <code>Event</code> value containing the event to be sent
* @param inProvider a <code>String</code> value containing the name of a CEP provider
*/
protected final void sendEventToCEP(Event inEvent,
String inProvider)
{
if(inEvent == null ||
inProvider == null ||
inProvider.isEmpty()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_EVENT_TO_CEP,
String.valueOf(strategy),
inEvent,
inProvider).create(),
strategy);
return;
}
String namespace = strategy.getDefaultNamespace();
StrategyModule.log(LogEventBuilder.debug().withMessage(SUBMITTING_EVENT_TO_CEP,
String.valueOf(strategy),
inEvent,
inProvider,
namespace).create(),
strategy);
strategy.getServicesProvider().sendEvent(inEvent,
inProvider,
namespace);
}
/**
* Sends the given event to the appropriate subscribers.
*
* @param inEvent an <code>Event</code> value
*/
protected final void sendEvent(Event inEvent)
{
if(inEvent == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_EVENT,
String.valueOf(strategy)).create(),
strategy);
return;
}
strategy.getServicesProvider().sendEvent(inEvent,
null,
null);
}
/**
* Sends the given notification to the appropriate subscribers.
*
* @param inNotification a <code>Notification</code> value
*/
protected final void sendNotification(Notification inNotification)
{
if(inNotification == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_NOTIFICATION,
String.valueOf(strategy)).create(),
strategy);
return;
}
strategy.getServicesProvider().sendNotification(inNotification);
}
/**
* Requests a callback after a specified delay in milliseconds.
*
* <p>The callback will be executed as close to the specified millisecond
* as possible. There is no guarantee that the timing will be exact. If
* more than one callback is requested by the same {@link RunningStrategy}
* for the same millisecond, the requests will be processed serially in
* FIFO order. This implies that a long-running callback request may
* delay other callbacks from the same {@link RunningStrategy} unless the
* caller takes steps to mitigate the bottleneck.
*
* @param inDelay a <code>long</code> value indicating how many milliseconds
* to wait before executing the callback. A value <= 0 will be interpreted
* as a request for an immediate callback.
* @param inData an <code>Object</code> value to deliver along with the callback,
* may be null
*/
protected final void requestCallbackAfter(long inDelay,
Object inData)
{
callbackService.schedule(new Callback(this,
strategy,
inData),
inDelay,
TimeUnit.MILLISECONDS);
}
/**
* Requests a callback periodically after a specified period in milliseconds.
*
* <p>The callback will be executed as close to the specified millisecond
* as possible. There is no guarantee that the timing will be exact. If
* more than one callback is requested by the same {@link RunningStrategy}
* for the same millisecond, the requests will be processed serially in
* FIFO order. This implies that a long-running callback request may
* delay other callbacks from the same {@link RunningStrategy} unless the
* caller takes steps to mitigate the bottleneck.
* @param inDelay a <code>long</code> value indicating how many milliseconds
* to wait before executing the first callback. A value <= 0 will be interpreted
* as a request for an immediate callback.
* @param inPeriod a <code>long</code> value indicating how many milliseconds
* to wait before executing the second callback, and thereafter repeatedly
* The value must be > 0.
* @param inData an <code>Object</code> value to deliver along with the callback,
* may be null
*/
protected final void requestCallbackEvery(long inDelay, long inPeriod,
Object inData)
{
callbackService.scheduleAtFixedRate(new Callback(this,
strategy,
inData), inDelay, inPeriod, TimeUnit.MILLISECONDS);
}
/**
* Requests a callback at a specific point in time.
*
* <p>The callback will be executed as close to the specified millisecond
* as possible. There is no guarantee that the timing will be exact. If
* more than one callback is requested by the same {@link RunningStrategy}
* for the same millisecond, the requests will be processed serially in
* FIFO order. This implies that a long-running callback request may
* delay other callbacks from the same {@link RunningStrategy} unless the
* caller takes steps to mitigate the bottleneck.
*
* @param inDate a <code>Date</code> value at which to execute the callback. A date
* value earlier than the present will be interpreted as a request for an
* immediate callback.
* @param inData an <code>Object</code> value to deliver with the callback or null
*/
protected final void requestCallbackAt(Date inDate,
Object inData)
{
requestCallbackAfter(inDate.getTime() - System.currentTimeMillis(),
inData);
}
/**
* Returns the list of brokers known to the system.
*
* <p>These values can be used to create and send orders with {@link #sendMessage(Message, BrokerID)}
* or {@link #send(Object)}.
*
* @return a <code>BrokerStatus[]</code> value
*/
protected final BrokerStatus[] getBrokers()
{
try {
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return new BrokerStatus[0];
}
List<BrokerStatus> brokers = strategy.getServicesProvider().getBrokers();
StrategyModule.log(LogEventBuilder.debug().withMessage(RECEIVED_BROKERS,
String.valueOf(strategy),
String.valueOf(brokers)).create(),
strategy);
return brokers.toArray(new BrokerStatus[brokers.size()]);
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_RETRIEVE_BROKERS,
String.valueOf(strategy))
.withException(e).create(),
strategy);
return new BrokerStatus[0];
}
}
/**
* Gets the position in the given <code>Equity</code> at the given point in time.
*
* <p>Note that this method will not retrieve <code>Option</code> positions. To retrieve
* <code>Option</code> positions, use {@link #getOptionPositionAsOf(Date, String, String, BigDecimal, OptionType)}.
*
* @param inDate a <code>Date</code> value indicating the point in time for which to search
* @param inSymbol a <code>String</code> value containing the <code>Equity</code> symbol
* @return a <code>BigDecimal</code> value or <code>null</code> if no position could be found
*/
protected final BigDecimal getPositionAsOf(Date inDate,
String inSymbol)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return null;
}
if(inDate == null ||
inSymbol == null ||
inSymbol.isEmpty()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_EQUITY_POSITION_REQUEST,
String.valueOf(strategy),
inDate,
inSymbol).create(),
strategy);
return null;
}
try {
BigDecimal result = strategy.getServicesProvider().getPositionAsOf(inDate,
new Equity(inSymbol));
StrategyModule.log(LogEventBuilder.debug().withMessage(RECEIVED_POSITION,
String.valueOf(strategy),
result,
inDate,
inSymbol).create(),
strategy);
return result;
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_RETRIEVE_EQUITY_POSITION,
String.valueOf(strategy),
inSymbol,
inDate)
.withException(e).create(),
strategy);
return null;
}
}
/**
* Gets all open <code>Equity</code> positions at the given point in time.
*
* @param inDate a <code>Date</code> value indicating the point in time for which to search
* @return a <code>Map<PositionKey<Equity>,BigDecimal></code> value
*/
protected final Map<PositionKey<Equity>,BigDecimal> getAllPositionsAsOf(Date inDate)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return null;
}
if(inDate == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_POSITIONS_REQUEST,
String.valueOf(strategy)).create(),
strategy);
return null;
}
try {
Map<PositionKey<Equity>,BigDecimal> result = strategy.getServicesProvider().getAllPositionsAsOf(inDate);
StrategyModule.log(LogEventBuilder.debug().withMessage(RECEIVED_POSITIONS,
String.valueOf(strategy),
String.valueOf(result),
inDate).create(),
strategy);
return result;
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_RETRIEVE_POSITIONS,
String.valueOf(strategy),
inDate)
.withException(e).create(),
strategy);
return null;
}
}
/**
* Gets the position in the given <code>Future</code> at the given point in time.
*
* <p>Note that this method will not retrieve <code>Option</code> or <code>Equity</code> positions. To retrieve
* <code>Option</code> positions, use {@link #getOptionPositionAsOf(Date, String, String, BigDecimal, OptionType)}.
* To retrieve
* <code>Equity</code> positions, use {@link #getPositionAsOf(Date, String)}.
*
* @param inDate a <code>Date</code> value indicating the point in time for which to search
* @param inUnderlyingSymbol a <code>String</code> value containing the underlying <code>Future</code> symbol
* @param inExpirationMonth a <code>FutureExpirationMonth</code> value
* @param inExpirationYear an <code>int</code> value
* @return a <code>BigDecimal</code> value or <code>null</code> if no position could be found
*/
protected final BigDecimal getFuturePositionAsOf(Date inDate,
String inUnderlyingSymbol,
FutureExpirationMonth inExpirationMonth,
int inExpirationYear)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return null;
}
if(inDate == null ||
inUnderlyingSymbol == null ||
inUnderlyingSymbol.isEmpty()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_FUTURE_POSITION_REQUEST,
String.valueOf(strategy),
inDate,
inUnderlyingSymbol).create(),
strategy);
return null;
}
try {
BigDecimal result = strategy.getServicesProvider().getFuturePositionAsOf(inDate,
new Future(inUnderlyingSymbol,
inExpirationMonth,
inExpirationYear));
StrategyModule.log(LogEventBuilder.debug().withMessage(RECEIVED_POSITION,
String.valueOf(strategy),
result,
inDate,
inUnderlyingSymbol).create(),
strategy);
return result;
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_RETRIEVE_FUTURE_POSITION,
String.valueOf(strategy),
inUnderlyingSymbol,
inDate)
.withException(e).create(),
strategy);
return null;
}
}
/**
* Gets the position in the given <code>Currency</code> at the given point in time.
* @param inDate a <code>Date</code> value indicating the point in time for which to search
* @param inSymbol a <code>String</code> value containing the underlying <code>Currency</code> symbol
* @return a <code>BigDecimal</code> value or <code>null</code> if no position could be found
*/
protected final BigDecimal getCurrencyPositionAsOf(Date inDate,
String inSymbol)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return null;
}
if(inDate == null ||
inSymbol == null ||
inSymbol.isEmpty()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_CURRENCY_POSITION_REQUEST,
String.valueOf(strategy),
inDate,
inSymbol).create(),
strategy);
return null;
}
try {
BigDecimal result = strategy.getServicesProvider().getCurrencyPositionAsOf(inDate,
new Currency(inSymbol));
StrategyModule.log(LogEventBuilder.debug().withMessage(RECEIVED_POSITION,
String.valueOf(strategy),
result,
inDate,
inSymbol).create(),
strategy);
return result;
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_RETRIEVE_CURRENCY_POSITION,
String.valueOf(strategy),
inSymbol,
inDate)
.withException(e).create(),
strategy);
return null;
}
}
/**
* Gets all open <code>Future</code> positions at the given point in time.
*
* @param inDate a <code>Date</code> value indicating the point in time for which to search
* @return a <code>Map<PositionKey<Equity>,BigDecimal></code> value
*/
protected final Map<PositionKey<Future>,BigDecimal> getAllFuturePositionsAsOf(Date inDate)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return null;
}
if(inDate == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_POSITIONS_REQUEST,
String.valueOf(strategy)).create(),
strategy);
return null;
}
try {
Map<PositionKey<Future>,BigDecimal> result = strategy.getServicesProvider().getAllFuturePositionsAsOf(inDate);
StrategyModule.log(LogEventBuilder.debug().withMessage(RECEIVED_POSITIONS,
String.valueOf(strategy),
String.valueOf(result),
inDate).create(),
strategy);
return result;
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_RETRIEVE_POSITIONS,
String.valueOf(strategy),
inDate)
.withException(e).create(),
strategy);
return null;
}
}
/**
* Gets all open <code>Currency</code> positions at the given point in time.
*
* @param inDate a <code>Date</code> value indicating the point in time for which to search
* @return a <code>Map<PositionKey<Currency>,BigDecimal></code> value
*/
protected final Map<PositionKey<Currency>,BigDecimal> getAllCurrencyPositionsAsOf(Date inDate)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return null;
}
if(inDate == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_POSITIONS_REQUEST,
String.valueOf(strategy)).create(),
strategy);
return null;
}
try {
Map<PositionKey<Currency>,BigDecimal> result = strategy.getServicesProvider().getAllCurrencyPositionsAsOf(inDate);
StrategyModule.log(LogEventBuilder.debug().withMessage(RECEIVED_POSITIONS,
String.valueOf(strategy),
String.valueOf(result),
inDate).create(),
strategy);
return result;
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_RETRIEVE_POSITIONS,
String.valueOf(strategy),
inDate)
.withException(e).create(),
strategy);
return null;
}
}
/**
* Gets the position in the given <code>Option</code> at the given point in time.
*
* @param inDate a <code>Date</code> value indicating the point in time for which to search
* @param inOptionRoot a <code>String</code> value
* @param inExpiry a <code>String</code> value
* @param inStrikePrice a <code>BigDecimal</code> value
* @param inType an <code>OptionType</code> value
* @return a <code>BigDecimal</code> value or <code>null</code> if no position could be found
*/
protected final BigDecimal getOptionPositionAsOf(Date inDate,
String inOptionRoot,
String inExpiry,
BigDecimal inStrikePrice,
OptionType inType)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return null;
}
if(inDate == null ||
inOptionRoot == null ||
inOptionRoot.isEmpty() ||
inExpiry == null ||
inExpiry.isEmpty() ||
inStrikePrice == null ||
inType == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_OPTION_POSITION_REQUEST,
String.valueOf(strategy),
inDate,
inOptionRoot,
inExpiry,
inStrikePrice,
inType).create(),
strategy);
return null;
}
try {
Option option = new Option(inOptionRoot,
inExpiry,
inStrikePrice,
inType);
BigDecimal result = strategy.getServicesProvider().getOptionPositionAsOf(inDate,
option);
StrategyModule.log(LogEventBuilder.debug().withMessage(RECEIVED_POSITION,
String.valueOf(strategy),
result,
inDate,
option).create(),
strategy);
return result;
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_RETRIEVE_OPTION_POSITION,
String.valueOf(strategy),
inOptionRoot,
inExpiry,
inStrikePrice,
inType,
inDate)
.withException(e).create(),
strategy);
return null;
}
}
/**
* Gets all open <code>Option</code> positions at the given point in time.
*
* @param inDate a <code>Date</code> value indicating the point in time for which to search
* @return a <code>Map<PositionKey<Option>,BigDecimal></code> value
*/
protected final Map<PositionKey<Option>,BigDecimal> getAllOptionPositionsAsOf(Date inDate)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return null;
}
if(inDate == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_POSITIONS_REQUEST,
String.valueOf(strategy)).create(),
strategy);
return null;
}
try {
Map<PositionKey<Option>,BigDecimal> result = strategy.getServicesProvider().getAllOptionPositionsAsOf(inDate);
StrategyModule.log(LogEventBuilder.debug().withMessage(RECEIVED_POSITIONS,
String.valueOf(strategy),
String.valueOf(result),
inDate).create(),
strategy);
return result;
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_RETRIEVE_POSITIONS,
String.valueOf(strategy),
inDate)
.withException(e).create(),
strategy);
return null;
}
}
/**
* Gets open positions for the options specified by the given option roots at the given point in time.
*
* @param inDate a <code>Date</code> value indicating the point in time for which to search
* @param inOptionRoots a <code>String[]</code> value containing the specific option roots for which to search
* @return a <code>Map<PositionKey<Option>,BigDecimal></code> value
*/
protected final Map<PositionKey<Option>,BigDecimal> getOptionPositionsAsOf(Date inDate,
String...inOptionRoots)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return null;
}
if(inDate == null ||
inOptionRoots == null ||
inOptionRoots.length == 0) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_POSITIONS_BY_OPTION_ROOTS_REQUEST,
String.valueOf(strategy)).create(),
strategy);
return null;
}
for(String optionRoot : inOptionRoots) {
if(optionRoot == null ||
optionRoot.isEmpty()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_POSITIONS_BY_OPTION_ROOTS_REQUEST,
String.valueOf(strategy)).create(),
strategy);
return null;
}
}
try {
Map<PositionKey<Option>,BigDecimal> result = strategy.getServicesProvider().getOptionPositionsAsOf(inDate,
inOptionRoots);
StrategyModule.log(LogEventBuilder.debug().withMessage(RECEIVED_POSITIONS,
String.valueOf(strategy),
String.valueOf(result),
inDate).create(),
strategy);
return result;
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_RETRIEVE_POSITIONS_BY_OPTION_ROOTS,
String.valueOf(strategy),
Arrays.toString(inOptionRoots),
inDate)
.withException(e).create(),
strategy);
return null;
}
}
/**
* Gets the underlying symbol for the given option root, if available.
*
* @param inOptionRoot a <code>String</code> value containing an option root
* @return a <code>String</code> value containing the symbol for the underlying instrument or <code>null</code> if
* no underlying instrument could be found
*/
protected final String getUnderlying(String inOptionRoot)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return null;
}
if(inOptionRoot == null ||
inOptionRoot.isEmpty()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_UNDERLYING_REQUEST,
String.valueOf(strategy),
inOptionRoot).create(),
strategy);
return null;
}
try {
String result = strategy.getServicesProvider().getUnderlying(inOptionRoot);
StrategyModule.log(LogEventBuilder.debug().withMessage(RECEIVED_UNDERLYING,
String.valueOf(strategy),
result,
inOptionRoot).create(),
strategy);
return result;
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_RETRIEVE_UNDERLYING,
String.valueOf(strategy),
inOptionRoot)
.withException(e).create(),
strategy);
return null;
}
}
/**
* Gets the set of of known option roots for the given underlying symbol.
*
* @param inUnderlying a <code>String</code> value containing the symbol of an underlying instrument
* @return a <code>Collection<String></code> value sorted lexicographically by option root or <code>null</code>
* if no option roots could be found
*/
protected final Collection<String> getOptionRoots(String inUnderlying)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return null;
}
if(inUnderlying == null ||
inUnderlying.isEmpty()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_OPTION_ROOTS_REQUEST,
String.valueOf(strategy)).create(),
strategy);
return null;
}
try {
Collection<String> result = strategy.getServicesProvider().getOptionRoots(inUnderlying);
StrategyModule.log(LogEventBuilder.debug().withMessage(RECEIVED_OPTION_ROOTS,
String.valueOf(strategy),
String.valueOf(result),
inUnderlying).create(),
strategy);
return result;
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_RETRIEVE_OPTION_ROOTS,
String.valueOf(strategy),
inUnderlying)
.withException(e).create(),
strategy);
return null;
}
}
/**
* Initiates a data flow request.
*
* <p>See {@link DataFlowSupport#createDataFlow(DataRequest[], boolean)}.
* @param inAppendDataSink a <code>boolean</code> value indicating if the sink module should be appended to the
* data pipeline, if it's not already requested as the last module and the last module is capable of emitting data.
* @param inRequests a <code>DataRequest...</code> value containing the ordered list of requests. Each instance
* identifies a stage of the data pipeline. The data from the first stage is piped to the next.
*
* @return a <code>DataFlowID</code> value containing a unique ID identifying the data flow. The ID can be used to cancel
* the data flow request and get more details on it. Returns <code>null</code> if the request could not be created.
*/
protected final DataFlowID createDataFlow(boolean inAppendDataSink,
DataRequest... inRequests)
{
if(!canReceiveData()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_REQUEST_DATA,
String.valueOf(strategy),
strategy.getStatus()).create(),
strategy);
return null;
}
if(inRequests == null ||
inRequests.length == 0) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_DATA_REQUEST,
String.valueOf(strategy)).create(),
strategy);
return null;
}
try {
return strategy.getServicesProvider().createDataFlow(inRequests,
inAppendDataSink);
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(DATA_REQUEST_FAILED,
String.valueOf(strategy),
Arrays.toString(inRequests))
.withException(e).create(),
strategy);
return null;
}
}
/**
* Cancels a data flow identified by the supplied data flow ID.
*
* <p>See {@link DataFlowSupport#cancel(DataFlowID)}.
*
* @param inDataFlowID a <code>DataFlowID</code> value containing the request handle that was returned from
* a prior call to {@link #createDataFlow(boolean, DataRequest[])}
*/
protected final void cancelDataFlow(DataFlowID inDataFlowID)
{
if(inDataFlowID == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_DATA_REQUEST_CANCEL,
String.valueOf(strategy)).create(),
strategy);
return;
}
try {
strategy.getServicesProvider().cancelDataFlow(inDataFlowID);
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(DATA_REQUEST_CANCEL_FAILED,
String.valueOf(strategy),
inDataFlowID)
.withException(e).create(),
strategy);
}
}
/**
* Gets the {@link ModuleURN} of this strategy.
*
* @return a <code>ModuleURN</code> value
*/
protected final ModuleURN getURN()
{
return strategy.getServicesProvider().getURN();
}
/**
* Gets the user data associated with the current user.
*
* @return a <code>Properties</code> value
*/
protected final Properties getUserData()
{
try {
return strategy.getServicesProvider().getUserData();
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(FAILED_TO_RETRIEVE_USER_DATA,
String.valueOf(strategy))
.withException(e).create(),
strategy);
return null;
}
}
/**
* Sets the user data associated with the current user.
*
* @param inUserData a <code>Properties</code> value
*/
protected final void setUserData(Properties inUserData)
{
try {
strategy.getServicesProvider().setUserData(inUserData);
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(FAILED_TO_SET_USER_DATA,
String.valueOf(strategy))
.withException(e).create(),
strategy);
}
}
/**
* Emits the given debug message to the strategy log output.
*
* @param inMessage a <code>String</code> value
*/
protected void debug(String inMessage)
{
if(inMessage == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_LOG,
String.valueOf(strategy)).create(),
strategy);
return;
}
strategy.getServicesProvider().log(LogEventBuilder.debug().withMessage(MESSAGE_1P,
inMessage).create());
}
/**
* Emits the given info message to the strategy log output.
*
* @param inMessage a <code>String</code> value
*/
protected void info(String inMessage)
{
if(inMessage == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_LOG,
String.valueOf(strategy)).create(),
strategy);
return;
}
strategy.getServicesProvider().log(LogEventBuilder.info().withMessage(MESSAGE_1P,
inMessage).create());
}
/**
* Emits the given warn message to the strategy log output.
*
* @param inMessage a <code>String</code> value
*/
protected void warn(String inMessage)
{
if(inMessage == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_LOG,
String.valueOf(strategy)).create(),
strategy);
return;
}
strategy.getServicesProvider().log(LogEventBuilder.warn().withMessage(MESSAGE_1P,
inMessage).create());
}
/**
* Emits the given error message to the strategy log output.
*
* @param inMessage a <code>String</code> value
*/
protected void error(String inMessage)
{
if(inMessage == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_LOG,
String.valueOf(strategy)).create(),
strategy);
return;
}
strategy.getServicesProvider().log(LogEventBuilder.error().withMessage(MESSAGE_1P,
inMessage).create());
}
/**
* Initializes the report history manager.
*
* <p>This can be a very expensive call depending on the value returned
* by {@link #getReportHistoryOriginDate()} and the number of execution reports
* in the database.
*/
protected final void initializeReportHistoryManager()
{
synchronized(AbstractRunningStrategy.class) {
Date origin = getReportHistoryOriginDate();
Messages.USING_ORDER_HISTORY_ORIGIN.info(AbstractRunningStrategy.class,
origin);
try {
if(orderHistoryManager != null) {
orderHistoryManager.stop();
}
orderHistoryManager = new LiveOrderHistoryManager(origin);
orderHistoryManager.start();
} catch (ClientInitException e) {
throw new RuntimeException(e);
}
}
}
/**
* Indicates if incoming data can be received.
*
* @return a <code>boolean</code> value
*/
private boolean canReceiveData()
{
return strategy.getStatus().canReceiveData();
}
/**
* common properties store shared among all strategies
*/
private static final Properties properties = new Properties();
/**
* scheduler for request callbacks
*/
private final ScheduledExecutorService callbackService = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("StrategyCallback")); //$NON-NLS-1$
/**
* tracks submitted orders
*/
private final Set<OrderSingle> submittedOrders = new LinkedHashSet<OrderSingle>();
/**
* tracks submitted orderIDs
*/
private final Set<OrderID> submittedOrderIDs = new LinkedHashSet<OrderID>();
/**
* tracks orders based on execution reports
*/
private static volatile LiveOrderHistoryManager orderHistoryManager;
/**
* static strategy object of which this object is a running representation
*/
private Strategy strategy;
/**
* empty collection used to indicate the lack of reports for an order
*/
private static final Deque<ReportBase> EMPTY_REPORTS = new UnmodifiableDeque<ReportBase>(new LinkedList<ReportBase>());
/**
* Executes a callback to a specific {@link RunningStrategy}.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: AbstractRunningStrategy.java 16864 2014-03-20 19:39:48Z colin $
* @since 1.0.0
*/
@ClassVersion("$Id: AbstractRunningStrategy.java 16864 2014-03-20 19:39:48Z colin $")
private static final class Callback
implements Runnable
{
/**
* the base strategy
*/
private final Strategy strategy;
/**
* the strategy which to call
*/
private final RunningStrategy runningStrategy;
/**
* the data payload to deliver, may be null
*/
private final Object data;
/**
* Create a new Callback instance.
*
* @param inRunningStrategy a <code>RunningStrategy</code> instance
* @param inStrategy a <code>Strategy</code> value containing the base strategy
* @param inData an <code>Object</code> value to deliver to the {@link RunningStrategy}
* or null
*/
private Callback(RunningStrategy inRunningStrategy,
Strategy inStrategy,
Object inData)
{
runningStrategy = inRunningStrategy;
strategy = inStrategy;
data = inData;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run()
{
StrategyModule.log(LogEventBuilder.debug().withMessage(EXECUTING_CALLBACK,
String.valueOf(runningStrategy),
new Date()).create(),
strategy);
try {
runningStrategy.onCallback(data);
} catch (Exception e) {
if(strategy.getExecutor() != null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CALLBACK_ERROR,
String.valueOf(strategy),
strategy.getExecutor().interpretRuntimeException(e))
.withException(e).create(),
strategy);
} else {
StrategyModule.log(LogEventBuilder.warn().withMessage(CALLBACK_ERROR,
String.valueOf(strategy),
e.getLocalizedMessage())
.withException(e).create(),
strategy);
}
}
}
}
}