Hi Eugene,
I've been testing the volume checking code each morning and recording the
results. You can see there was a slight issue when the GE contract
temporarily jumped to 201403, before returning to 201312 (highlighted in
red):
https://docs.google.com/spreadsheet/ccc?key=0AhnMZSbcxSoHdEhpX0c1bXhZLXZESDNjd1hKdUROTUE&usp=drive_web#gid=0
I think it would be slightly safer to check 4 months for each strategy, and
7 for the grains (ZC, ZS, ZW) as Alex suggested. I made this change locally
by adding a parameter to the setStrategy method, as below, in each base
strategy:
....
int contractMonths = 7; // contract months to check for volume
....
setStrategy(contract, tradingSchedule, multiplier, commission,
bidAskSpread, contractMonths);
... and I'm using that int in the Strategy and TradeAssistant classes
(attached). I'll continue monitoring the volume checks to see if there's
anything else that could upset the data.
regards,
Michael
--
You received this message because you are subscribed to the Google Groups
"JBookTrader" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/jbooktrader.
For more options, visit https://groups.google.com/groups/opt_out.
package com.jbooktrader.strategy.base;
import com.ib.client.*;
import com.jbooktrader.platform.commission.*;
import com.jbooktrader.platform.model.*;
import com.jbooktrader.platform.optimizer.*;
import com.jbooktrader.platform.schedule.*;
import com.jbooktrader.platform.strategy.*;
import com.jbooktrader.platform.util.contract.*;
/**
* @author Eugene Kononov
*/
public abstract class StrategyES extends Strategy {
/*
* MARGIN REQUIREMENTS for ES: GLOBEX as of 13-July-2009
* Source: http://www.interactivebrokers.com/en/p.php?f=margin&ib_entity=llc
*
* Initial Intra-day: $2,500
* Intra-day Maintenance: $2,250
* Initial Overnight: $5,625
* Overnight Maintenance: $4,500
*/
protected StrategyES(StrategyParams optimizationParams) throws JBookTraderException {
super(optimizationParams);
// Specify the contract to trade
Contract contract = ContractFactory.makeFutureContract("ES", "GLOBEX");
// Define trading schedule
TradingSchedule tradingSchedule = new TradingSchedule("10:05", "15:25", "America/New_York");
int multiplier = 50;// contract multiplier
double bidAskSpread = 0.25; // prevalent spread between best bid and best ask
int contractMonths = 4; // contract months to check for volume
Commission commission = CommissionFactory.getBundledNorthAmericaFutureCommission();
setStrategy(contract, tradingSchedule, multiplier, commission, bidAskSpread, contractMonths);
}
}
package com.jbooktrader.platform.trader;
import com.ib.client.*;
import com.jbooktrader.platform.indicator.*;
import com.jbooktrader.platform.marketbook.*;
import com.jbooktrader.platform.model.*;
import com.jbooktrader.platform.position.*;
import com.jbooktrader.platform.preferences.*;
import com.jbooktrader.platform.report.*;
import com.jbooktrader.platform.startup.*;
import com.jbooktrader.platform.strategy.*;
import com.jbooktrader.platform.util.ui.*;
import javax.swing.*;
import java.text.*;
import java.util.*;
import java.util.concurrent.*;
import static com.jbooktrader.platform.preferences.JBTPreferences.*;
/**
* @author Eugene Kononov
*/
public class TraderAssistant {
private static int contractMonths = 4;
private final Map<Integer, Strategy> strategies;
private final Map<Integer, OpenOrder> openOrders;
private final Map<String, Integer> tickers;
private final Map<Integer, Integer> volumes;
private final Map<Integer, String> expirations;
private final Map<Integer, String> subscribedTickers;
private final Map<Integer, MarketBook> marketBooks;
private final EventReport eventReport;
private final Trader trader;
private final Dispatcher dispatcher;
private final String faSubAccount;
private final long maxDisconnectionTimeSeconds;
private EClientSocket socket;
private int nextStrategyID, tickerId, orderID, serverVersion;
private String accountCode;// used to determine if TWS is running against real or paper trading account
private boolean isOrderExecutionPending;
private boolean isMarketDataActive;
private long disconnectionTime;
private final BlockingQueue<String> queue;
public TraderAssistant(Trader trader) {
this.trader = trader;
dispatcher = Dispatcher.getInstance();
eventReport = dispatcher.getEventReport();
strategies = new HashMap<>();
openOrders = new HashMap<>();
tickers = new HashMap<>();
volumes = new HashMap<>();
expirations = new HashMap<>();
marketBooks = new HashMap<>();
subscribedTickers = new HashMap<>();
faSubAccount = PreferencesHolder.getInstance().get(SubAccount);
maxDisconnectionTimeSeconds = Long.parseLong(PreferencesHolder.getInstance().get(MaxDisconnectionPeriod));
queue = new ArrayBlockingQueue<>(1);
}
public Map<Integer, OpenOrder> getOpenOrders() {
return openOrders;
}
public Collection<Strategy> getAllStrategies() {
return strategies.values();
}
public MarketBook getMarketBook(int tickerId) {
return marketBooks.get(tickerId);
}
public Map<Integer, MarketBook> getAllMarketBooks() {
return marketBooks;
}
public Strategy getStrategy(String name) {
for (Map.Entry<Integer, Strategy> mapEntry : strategies.entrySet()) {
Strategy strategy = mapEntry.getValue();
if (strategy.getName().equals(name)) {
return strategy;
}
}
return null;
}
public void connect() throws JBookTraderException {
if (socket == null || !socket.isConnected()) {
eventReport.report(JBookTrader.APP_NAME, "Connecting to TWS");
socket = new EClientSocket(trader);
PreferencesHolder prefs = PreferencesHolder.getInstance();
String host = prefs.get(Host);
int port = prefs.getInt(Port);
int clientID = prefs.getInt(ClientID);
socket.eConnect(host, port, clientID);
if (!socket.isConnected()) {
throw new JBookTraderException("Could not connect to TWS. See report for details.");
}
// IB Log levels: 1=SYSTEM 2=ERROR 3=WARNING 4=INFORMATION 5=DETAIL
socket.setServerLogLevel(3);
socket.reqNewsBulletins(true);
serverVersion = socket.serverVersion();
eventReport.report(JBookTrader.APP_NAME, "Connected to TWS");
checkAccountType();
}
}
public int getServerVersion() {
return serverVersion;
}
public void disconnect() {
if (socket != null && socket.isConnected()) {
socket.cancelNewsBulletins();
socket.eDisconnect();
}
}
/**
* While TWS was disconnected from the IB server, some order executions may have occured.
* To detect executions, request them explicitly after the reconnection.
*/
public void requestExecutions() {
try {
for (OpenOrder openOrder : openOrders.values()) {
openOrder.reset();
eventReport.report(openOrder.getStrategy().getName(), "Requesting executions for open order " + openOrder.getId());
socket.reqExecutions(openOrder.getId(), new ExecutionFilter());
}
} catch (Throwable t) {
// Do not allow exceptions come back to the socket -- it will cause disconnects
eventReport.report(t);
}
}
public String makeInstrument(Contract contract) {
String instrument = contract.m_symbol;
if (contract.m_currency != null) {
instrument += "-" + contract.m_currency;
}
if (contract.m_exchange != null) {
instrument += "-" + contract.m_exchange;
}
if (contract.m_secType != null) {
instrument += "-" + contract.m_secType;
}
return instrument;
}
public synchronized MarketBook createMarketBook(Strategy strategy) {
Contract contract = strategy.getContract();
String instrument = makeInstrument(contract);
Integer ticker = tickers.get(instrument);
MarketBook marketBook;
if (ticker == null) {
marketBook = new MarketBook(instrument, strategy.getTradingSchedule().getTimeZone());
tickerId++;
tickers.put(instrument, tickerId);
marketBooks.put(tickerId, marketBook);
} else {
marketBook = marketBooks.get(ticker);
}
return marketBook;
}
public void volumeResponse(int id, int volume) throws InterruptedException {
if (queue.peek() == null && expirations.containsKey(id)) {
if (!volumes.containsKey(id)) {
volumes.put(id, volume);
}
if (volumes.size() == TraderAssistant.contractMonths) {
queue.put("done");
}
}
}
public void setMostLiquidContract(Contract contract, int contractMonths) throws InterruptedException {
if (!contract.m_secType.equalsIgnoreCase("FUT")) {
return;
}
queue.clear();
expirations.clear();
volumes.clear();
TraderAssistant.contractMonths = contractMonths;
Calendar calendar = Calendar.getInstance();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMM");
int lastTickerId = tickerId + TraderAssistant.contractMonths;
while (tickerId < lastTickerId) {
tickerId++;
contract.m_expiry = dateFormat.format(calendar.getTime());
expirations.put(tickerId, contract.m_expiry);
socket.reqMktData(tickerId, contract, "", true);
calendar.add(Calendar.MONTH, 1);
}
queue.take();
String mostLiquidExpiration = null;
int maxVolume = 0;
boolean isValidContract = false;
for (Map.Entry<Integer, Integer> entry : volumes.entrySet()) {
Integer ticker = entry.getKey();
Integer volume = entry.getValue();
if (volume != -1) {
isValidContract = true;
}
if (volume > maxVolume) {
mostLiquidExpiration = expirations.get(ticker);
maxVolume = volume;
}
}
if (!isValidContract) {
String msg = "Contract " + contract.m_symbol + " for exchange " + contract.m_exchange + " does not exist.";
msg += " Make sure that the ticker symbol and the exchange are specified correctly.";
eventReport.report(JBookTrader.APP_NAME, msg);
throw new RuntimeException(msg);
}
if (mostLiquidExpiration == null) {
String msg = "Unable to determine the most liquid " + contract.m_symbol + " contract because no trading volume was reported. Please try again when trading resumes.";
eventReport.report(JBookTrader.APP_NAME, msg);
throw new RuntimeException(msg);
}
contract.m_expiry = mostLiquidExpiration;
eventReport.report(JBookTrader.APP_NAME, "The most liquid " + contract.m_symbol + " contract was determined as " + mostLiquidExpiration + ". Volume: " + maxVolume + ".");
}
public synchronized void requestMarketData(Strategy strategy) throws InterruptedException {
Contract contract = strategy.getContract();
String instrument = makeInstrument(contract);
Integer ticker = tickers.get(instrument);
if (!subscribedTickers.containsKey(ticker)) {
setMostLiquidContract(strategy.getContract(), strategy.getContractMonths());
subscribedTickers.put(ticker, strategy.getContract().m_expiry);
socket.reqContractDetails(ticker, strategy.getContract());
eventReport.report(JBookTrader.APP_NAME, "Requested contract details for instrument " + instrument);
socket.reqMktDepth(ticker, contract, 10);
eventReport.report(JBookTrader.APP_NAME, "Requested book data for instrument " + instrument);
socket.reqMktData(ticker, contract, "", false);
eventReport.report(JBookTrader.APP_NAME, "Requested market data for instrument " + instrument);
} else {
strategy.getContract().m_expiry = subscribedTickers.get(ticker);
}
Dispatcher.getInstance().fireModelChanged(ModelListener.Event.ExpirationUpdate, strategy);
}
public synchronized void addStrategy(Strategy strategy) throws InterruptedException {
try {
Mode mode = dispatcher.getMode();
if (mode == Mode.ForwardTest || mode == Mode.Trade) {
requestMarketData(strategy);
}
strategy.setIndicatorManager(new IndicatorManager());
strategy.setIndicators();
nextStrategyID++;
strategies.put(nextStrategyID, strategy);
if (mode == Mode.ForwardTest || mode == Mode.Trade) {
String msg = "Strategy started. Trading schedule: " + strategy.getTradingSchedule();
eventReport.report(strategy.getName(), msg);
StrategyRunner.getInstance().addListener(strategy);
}
} catch (Exception e) {
MessageDialog.showMessage(e.getMessage());
}
}
public synchronized void removeAllStrategies() {
strategies.clear();
openOrders.clear();
tickers.clear();
subscribedTickers.clear();
marketBooks.clear();
}
public void setAccountCode(String accountCode) {
this.accountCode = accountCode;
}
public void resetOrderExecutionPending() {
isOrderExecutionPending = false;
}
public void setIsMarketDataActive(boolean isMarketDataActive) throws JBookTraderException {
this.isMarketDataActive = isMarketDataActive;
if (!isMarketDataActive) {
disconnectionTime = dispatcher.getNTPClock().getTime();
}
if (isMarketDataActive) {
long reconnectionTime = dispatcher.getNTPClock().getTime();
long elapsedDisconnectionTimeSeconds = (reconnectionTime - disconnectionTime) / 1000;
if (disconnectionTime != 0 && elapsedDisconnectionTimeSeconds > maxDisconnectionTimeSeconds) {
dispatcher.setMode(Mode.ForceClose);
}
disconnectionTime = 0;
}
}
public boolean getIsMarketDataActive() {
return isMarketDataActive;
}
private synchronized void placeOrder(Contract contract, Order order, Strategy strategy) {
try {
if (isOrderExecutionPending) {
return;
}
long remainingTime = strategy.getTradingSchedule().getRemainingTime(strategy.getMarketBook().getSnapshot().getTime());
long remainingMinutes = remainingTime / (1000 * 60);
if (strategy.getPositionManager().getTargetPosition() != 0 && remainingMinutes < 15) {
return;
}
isOrderExecutionPending = true;
orderID++;
Mode mode = dispatcher.getMode();
if (mode == Mode.Trade || mode == Mode.ForwardTest || mode == Mode.ForceClose) {
String msg = "Placing order " + orderID;
eventReport.report(strategy.getName(), msg);
}
openOrders.put(orderID, new OpenOrder(orderID, order, strategy));
double midPrice = strategy.getMarketBook().getSnapshot().getPrice();
double bidAskSpread = strategy.getBidAskSpread();
double expectedFillPrice = order.m_action.equalsIgnoreCase("BUY") ? (midPrice + bidAskSpread / 2) : (midPrice - bidAskSpread / 2);
strategy.getPositionManager().setExpectedFillPrice(expectedFillPrice);
if (mode == Mode.Trade || mode == Mode.ForceClose) {
socket.placeOrder(orderID, contract, order);
} else {
Execution execution = new Execution();
execution.m_shares = order.m_totalQuantity;
execution.m_price = expectedFillPrice;
execution.m_orderId = orderID;
trader.execDetails(0, contract, execution);
}
} catch (Throwable t) {
// Do not allow exceptions come back to the socket -- it will cause disconnects
eventReport.report(t);
}
}
public void placeMarketOrder(Contract contract, int quantity, String action, Strategy strategy) {
Order order = new Order();
order.m_overridePercentageConstraints = true;
order.m_action = action;
order.m_totalQuantity = quantity;
order.m_orderType = "MKT";
order.m_account = faSubAccount;
placeOrder(contract, order, strategy);
}
public void setOrderID(int orderID) {
this.orderID = orderID;
}
private void checkAccountType() throws JBookTraderException {
socket.reqAccountUpdates(true, faSubAccount);
try {
synchronized (trader) {
while (accountCode == null) {
trader.wait();
}
}
} catch (InterruptedException ie) {
throw new JBookTraderException(ie);
}
socket.reqAccountUpdates(false, faSubAccount);
boolean isRealTrading = !accountCode.startsWith("D") && Dispatcher.getInstance().getMode() == Mode.Trade;
if (isRealTrading) {
String lineSep = System.getProperty("line.separator");
String warning = "Connected to a real (not simulated) IB account. ";
warning += "Running " + JBookTrader.APP_NAME + " in trading mode against a real" + lineSep;
warning += "account may cause significant losses in your account. ";
warning += "Are you sure you want to proceed?";
int response = JOptionPane.showConfirmDialog(null, warning, JBookTrader.APP_NAME, JOptionPane.YES_NO_OPTION);
if (response == JOptionPane.NO_OPTION) {
disconnect();
}
}
}
}
package com.jbooktrader.platform.strategy;
import com.ib.client.*;
import com.jbooktrader.platform.commission.*;
import com.jbooktrader.platform.indicator.*;
import com.jbooktrader.platform.marketbook.*;
import com.jbooktrader.platform.model.*;
import com.jbooktrader.platform.model.ModelListener.*;
import com.jbooktrader.platform.optimizer.*;
import com.jbooktrader.platform.performance.*;
import com.jbooktrader.platform.portfolio.manager.*;
import com.jbooktrader.platform.position.*;
import com.jbooktrader.platform.report.*;
import com.jbooktrader.platform.schedule.*;
/**
* Base class for all classes that implement trading strategies.
*
* @author Eugene Kononov
*/
public abstract class Strategy implements Comparable<Strategy> {
private final StrategyParams params;
private final EventReport eventReport;
private final Dispatcher dispatcher;
private final String name;
private final PortfolioManager portfolioManager;
private MarketBook marketBook;
private Contract contract;
private TradingSchedule tradingSchedule;
private PositionManager positionManager;
private PerformanceManager performanceManager;
private StrategyReportManager strategyReportManager;
private IndicatorManager indicatorManager;
private double bidAskSpread;
private int contractMonths;
private boolean isDisabled;
protected Strategy(StrategyParams params) {
this.params = params;
if (params.size() == 0) {
setParams();
}
name = getClass().getSimpleName();
dispatcher = Dispatcher.getInstance();
eventReport = dispatcher.getEventReport();
portfolioManager = dispatcher.getPortfolioManager();
}
/**
* Framework calls this method when a new snapshot of the limit order book is taken.
*/
public abstract void onBookSnapshot();
/**
* Framework calls this method to set strategy parameter ranges and values.
*/
protected abstract void setParams();
/**
* Framework calls this method to instantiate indicators.
*/
public abstract void setIndicators();
protected void goLong() {
int targetPosition = getPositionManager().getTargetPosition();
if (targetPosition <= 0) {
int size = portfolioManager.getSize(this);
if (size != 0) {
positionManager.setTargetPosition(size);
}
}
}
protected void goShort() {
int targetPosition = getPositionManager().getTargetPosition();
if (targetPosition >= 0) {
int size = portfolioManager.getSize(this);
if (size != 0) {
positionManager.setTargetPosition(-size);
}
}
}
public void goFlat() {
positionManager.setTargetPosition(0);
}
public double getBidAskSpread() {
return bidAskSpread;
}
public void closePosition() {
goFlat();
if (positionManager.getCurrentPosition() != 0) {
Mode mode = dispatcher.getMode();
if (mode == Mode.ForwardTest || mode == Mode.Trade) {
String msg = "End of trading interval. Closing current position.";
eventReport.report(name, msg);
}
positionManager.trade();
}
}
public StrategyParams getParams() {
return params;
}
protected int getParam(String name) {
return params.get(name).getValue();
}
protected void addParam(String name, int min, int max, int step, int value) {
params.add(name, min, max, step, value);
}
public PositionManager getPositionManager() {
return positionManager;
}
public PerformanceManager getPerformanceManager() {
return performanceManager;
}
public StrategyReportManager getStrategyReportManager() {
return strategyReportManager;
}
public IndicatorManager getIndicatorManager() {
return indicatorManager;
}
public void setIndicatorManager(IndicatorManager indicatorManager) {
this.indicatorManager = indicatorManager;
indicatorManager.setMarketBook(marketBook);
}
public TradingSchedule getTradingSchedule() {
return tradingSchedule;
}
protected Indicator addIndicator(Indicator indicator) {
return indicatorManager.addIndicator(indicator);
}
protected void setStrategy(Contract contract, TradingSchedule tradingSchedule, int multiplier, Commission commission, double bidAskSpread, int contractMonths) {
this.contract = contract;
contract.m_multiplier = String.valueOf(multiplier);
this.tradingSchedule = tradingSchedule;
performanceManager = new PerformanceManager(this, multiplier, commission);
positionManager = new PositionManager(this);
strategyReportManager = new StrategyReportManager(this);
marketBook = dispatcher.getTrader().getAssistant().createMarketBook(this);
this.bidAskSpread = bidAskSpread;
this.contractMonths = contractMonths;
}
public MarketBook getMarketBook() {
return marketBook;
}
public void setMarketBook(MarketBook marketBook) {
this.marketBook = marketBook;
}
public Contract getContract() {
return contract;
}
public int getContractMonths() {
return this.contractMonths;
}
public void disable() {
isDisabled = true;
}
public String getSymbol() {
String symbol = contract.m_symbol;
if (contract.m_currency != null) {
symbol += "." + contract.m_currency;
}
return symbol;
}
public String getName() {
return name;
}
public void processInstant(boolean isInSchedule) {
if (isInSchedule) {
if (dispatcher.getMode() == Mode.ForceClose) {
closePosition();
} else if (indicatorManager.hasValidIndicators()) {
onBookSnapshot();
}
if (!isDisabled) {
positionManager.trade();
}
} else {
closePosition(); // force flat position
}
}
public void process() {
if (!marketBook.isEmpty()) {
indicatorManager.updateIndicators();
MarketSnapshot marketSnapshot = marketBook.getSnapshot();
long instant = marketSnapshot.getTime();
processInstant(tradingSchedule.contains(instant));
performanceManager.updatePositionValue(marketSnapshot.getPrice(), positionManager.getCurrentPosition());
dispatcher.fireModelChanged(Event.StrategyUpdate, this);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(" ").append(name).append(" [");
sb.append(contract.m_symbol).append("-");
sb.append(contract.m_secType).append("-");
sb.append(contract.m_exchange).append("]");
return sb.toString();
}
public int compareTo(Strategy other) {
return name.compareTo(other.name);
}
}