package com.activequant.backtesting; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import javax.swing.JButton; import javax.swing.JFrame; import org.apache.log4j.Logger; import com.activequant.aqviz.GlobalVizEvents; import com.activequant.backtesting.reporting.PNLMonitor; import com.activequant.domainmodel.AlgoConfig; import com.activequant.domainmodel.ETransportType; import com.activequant.domainmodel.backtesting.BacktestConfiguration; import com.activequant.domainmodel.backtesting.SimulationReport; import com.activequant.domainmodel.streaming.MarketDataEvent; import com.activequant.domainmodel.streaming.ReferenceDataEvent; import com.activequant.domainmodel.streaming.StreamEvent; import com.activequant.domainmodel.streaming.StreamEventIterator; import com.activequant.domainmodel.streaming.TradingDataEvent; import com.activequant.domainmodel.trade.event.OrderEvent; import com.activequant.interfaces.archive.IArchiveFactory; import com.activequant.interfaces.dao.IDaoFactory; import com.activequant.interfaces.trading.IExchange; import com.activequant.interfaces.trading.ITradingSystem; import com.activequant.interfaces.transport.ITransportFactory; import com.activequant.interfaces.utils.IEventListener; import com.activequant.trading.TradingSystemEnvironment; import com.activequant.trading.virtual.VirtualExchange; import com.activequant.utils.TimeMeasurement; /** * * The visual backtester is a non-parallel backtester. If you want to speed up backtesting, for example by testing day-by-day or week-by-week in * parallel, use the ParallelizedBacktester. * * Setting the interactive flag to false will result in no backtest interface. * * @author GhostRider * */ public class VisualBacktester extends AbstractBacktester { private IExchange exchange; private ITransportFactory transportFactory; @SuppressWarnings("rawtypes") private StreamEventIterator[] streamIters; private ITradingSystem[] tradingSystems; private int tickPlayAmount = 0; private FastStreamer fs; long eventCount = 0; private JFrame jframe; private boolean runFlag = true; private boolean runUntilOrderEvent = false; private PNLMonitor pnlMonitor; private boolean interactive = true; private boolean sysExit = true; private Logger log = Logger.getLogger(VisualBacktester.class); private TradingSystemEnvironment env; public TradingSystemEnvironment getEnv() { return env; } public void setEnv(TradingSystemEnvironment env) { this.env = env; } public VisualBacktester(IArchiveFactory factory, ITransportFactory transportFactory, IDaoFactory daoFactory, IExchange exchange, ITradingSystem[] tradingSystems, StreamEventIterator[] streamIters, BacktestConfiguration bc) throws Exception { this(factory, transportFactory, daoFactory, exchange, tradingSystems, streamIters, bc, true); } /** * * @param factory * @param transportFactory * @param daoFactory * @param exchange * @param tradingSystems * @param streamIters * @param bc * @param interactive set it to false to have no user interface. Use start to trigger the backtest. * @throws Exception */ @SuppressWarnings("rawtypes") public VisualBacktester(IArchiveFactory factory, ITransportFactory transportFactory, IDaoFactory daoFactory, IExchange exchange, ITradingSystem[] tradingSystems, StreamEventIterator[] streamIters, BacktestConfiguration bc, boolean interactive) throws Exception { super(bc); // this.exchange = exchange; this.streamIters = streamIters; this.transportFactory = transportFactory; this.tradingSystems = tradingSystems; this.interactive = interactive; // add the order event listener if (exchange instanceof VirtualExchange) { ((VirtualExchange) exchange).getGlobalOrderEvent().addEventListener(oelistener); ((VirtualExchange) exchange).getGlobalOrderEvent().addEventListener(new IEventListener<OrderEvent>() { @Override public void eventFired(OrderEvent event) { if (runUntilOrderEvent) tickPlayAmount = 0; } }); } pnlMonitor = new PNLMonitor(transportFactory); super.setPnlMonitor(pnlMonitor); // construct the trading system environment. env = new TradingSystemEnvironment(); env.setArchiveFactory(factory); env.setDaoFactory(daoFactory); env.setExchange(exchange); env.setTransportFactory(transportFactory); for (ITradingSystem s : tradingSystems) { s.environment(env); } for (ITradingSystem s : tradingSystems) { s.initialize(); } fs = new FastStreamer(streamIters); TimeMeasurement.start("BACKTEST"); log.info("Starting replay."); for (ITradingSystem s : tradingSystems) { s.start(); } if (interactive) { jframe = new JFrame(); jframe.setTitle("Market replay control tool"); jframe.getContentPane().setLayout(new GridLayout(1, 6)); JButton play = new JButton("Play"); jframe.getContentPane().add(play); play.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { tickPlayAmount = Integer.MAX_VALUE; runUntilOrderEvent = false; } }); JButton pause = new JButton("Pause"); pause.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { tickPlayAmount = 0; runUntilOrderEvent = false; } }); jframe.getContentPane().add(pause); JButton step = new JButton("1 step"); step.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { tickPlayAmount = 1; runUntilOrderEvent = false; } }); jframe.getContentPane().add(step); JButton step50 = new JButton("50 steps"); step50.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { tickPlayAmount = 50; runUntilOrderEvent = false; } }); jframe.getContentPane().add(step50); JButton runUntilExecutionButton = new JButton("Run to next execution"); runUntilExecutionButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { tickPlayAmount = Integer.MAX_VALUE; runUntilOrderEvent = true; } }); jframe.getContentPane().add(runUntilExecutionButton); JButton stop = new JButton("Exit"); stop.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { stop(); } catch (Exception ex) { ex.printStackTrace(); } } }); jframe.getContentPane().add(stop); jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jframe.setSize(600, 75); jframe.setVisible(true); jframe.toFront(); // also show the live chart. pnlMonitor.showLiveChart(); // } } public SimulationReport stop() throws Exception { List<AlgoConfig> algoConfigs = new ArrayList<AlgoConfig>(); for (ITradingSystem s : tradingSystems) { s.stop(); algoConfigs.add(s.getAlgoConfig()); } super.setAlgoConfigs(algoConfigs.toArray(new AlgoConfig[] {})); TimeMeasurement.stop("BACKTEST"); long difference = TimeMeasurement.getRuntime("BACKTEST"); log.info("Replayed " + eventCount + " events in " + difference + "ms. That's " + (eventCount / (double) difference) + " events/ms"); runFlag = false; // generate the report. SimulationReport sr = super.generateReport(env.getWorkingDirectory()); // GlobalVizEvents.getInstance().getEvent().fire("EXIT"); if(sysExit)System.exit(0); return sr; } public void execute() throws Exception { while (runFlag) { // iterate over all data and feed it into the event bus. while (fs.moreDataInPipe() && (tickPlayAmount > 0 || !interactive)) { // StreamEvent se = fs.getOneFromPipes(); ETransportType transportType = se.getEventType(); // only time events are sent to the generic transport layer. if (transportType.equals(ETransportType.TIME)) { transportFactory.getPublisher(transportType.toString()).send(se); } // everything's a time event, so i also have to catch the // fine rest. if (transportType.equals(ETransportType.MARKET_DATA)) { exchange.processStreamEvent(se); MarketDataEvent mde = (MarketDataEvent) se; transportFactory.getPublisher(transportType, mde.getMdiId()).send(se); // send it also into our VIRTEX vortex } else if (transportType.equals(ETransportType.REF_DATA)) { ReferenceDataEvent rde = (ReferenceDataEvent) se; transportFactory.getPublisher(transportType, rde.getInstrument()).send(se); } else if (transportType.equals(ETransportType.TRAD_DATA)) { exchange.processStreamEvent(se); TradingDataEvent tde = (TradingDataEvent) se; transportFactory.getPublisher(transportType, tde.getTradInstId()).send(se); // send everything also to virtex exchange layer. } else { // push it out over its ID. transportFactory.getPublisher(se.getId()).send(se); } // eventCount++; tickPlayAmount--; } // checking every 100ms if we are to replay more. This delay does // not impact the backtesting performance. Thread.sleep(50); // check if there would still be more. if (!fs.moreDataInPipe()) runFlag = false; } } public boolean isSysExit() { return sysExit; } public void setSysExit(boolean sysExit) { this.sysExit = sysExit; } }