package com.activequant.backtesting; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.apache.log4j.Logger; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.activequant.domainmodel.backtesting.BacktestConfiguration; import com.activequant.domainmodel.backtesting.SimulationReport; import com.activequant.domainmodel.backtesting.TimeSetup; import com.activequant.domainmodel.streaming.StreamEventIterator; import com.activequant.domainmodel.streaming.TimeStreamEvent; import com.activequant.interfaces.archive.IArchiveFactory; import com.activequant.interfaces.backtesting.IStreamFactory; import com.activequant.interfaces.backtesting.ITimeRangeSplitter; import com.activequant.interfaces.dao.IDaoFactory; import com.activequant.interfaces.trading.ITradingSystem; import com.activequant.interfaces.transport.ITransportFactory; import com.activequant.trading.virtual.VirtualExchange; import com.activequant.transport.memory.InMemoryTransportFactory; import com.activequant.utils.Date8Time6Parser; /** * The parallelized backtester splits a time frame into smaller chunks and tests * these in parallel. Best suited for trading systems that liquidate at end of * day or end of week or end of month as no knowledge about what the different * chunks is exchanged between chunks. * * * Creates a thread pool and hands the chunks to the thread pool for * backtesting. * * It uses an ITimeRangeSplitter to obtain a list of backtest chunks. * * The individual outputs of these chunks are merged into a bigger report. * * Spawns non-interactive visual backtesters. * * Generates reports per backtest slice and stores these into a separate folder. * Finally aggregates the separate reports. * * @author GhostRider * */ public class ParallelizedBacktester extends AbstractBacktester { private ExecutorService threadPool; private Logger log = Logger.getLogger(ParallelizedBacktester.class); /** * * * * @param factory * @param transportFactory * @param daoFactory * @param exchange * @param tradingSystems * @param streamIters * @param bc * @throws Exception */ @SuppressWarnings("rawtypes") public ParallelizedBacktester(String aqAppContextSpringFile, BacktestConfiguration outerBacktestConfig, ITimeRangeSplitter timeRangeSplitter) throws Exception { super(outerBacktestConfig); // create a thread pool with nCores-1 threads. int usedCores = 1; // Runtime.getRuntime().availableProcessors() - 1; log.info("Instantiating a parallelized backtester with " + usedCores + " threads. "); threadPool = Executors.newFixedThreadPool(usedCores); Date8Time6Parser dtp = new Date8Time6Parser(); List<TimeSetup> chunks = timeRangeSplitter.split(dtp.fromLong(outerBacktestConfig.getDate8Time6Start()), dtp.fromLong(outerBacktestConfig.getDate8Time6End())); // Set<Future<SimulationReport>> futures = new HashSet<Future<SimulationReport>>(); for (int i = 0; i < chunks.size(); i++) { TimeSetup chunk = chunks.get(i); log.info("Obtained a chunk: " + chunk.toString()); BacktestConfiguration localConfig = btConfig.clone(); localConfig.setTimeSetup(chunk); // BacktestJob job = new BacktestJob(aqAppContextSpringFile, localConfig); Future<SimulationReport> f = threadPool.submit(job); futures.add(f); } // // List<SimulationReport> simReports = new ArrayList<SimulationReport>(); for(Future<SimulationReport> f : futures) simReports.add(f.get()); // threadPool.shutdown(); log.info("All jobs done. Merging minor simulation reports into major report."); // need to merge PNL curves and transactions ... // more advanced statistics can be generated through the // transaction-list-2-report generator // } class BacktestJob implements Callable<SimulationReport> { private BacktestConfiguration btConfig; private String aqAppContextSpringFile; BacktestJob(String aqAppContextSpringFile, BacktestConfiguration btConfig) { this.btConfig = btConfig; this.aqAppContextSpringFile = aqAppContextSpringFile; } @Override public SimulationReport call() { log.info("Simulating " + btConfig.getTimeSetup() + " with algo config: " + btConfig.getAlgoConfig()); SimulationReport sr = new SimulationReport (); try { // ApplicationContext appContext = new ClassPathXmlApplicationContext(aqAppContextSpringFile); IDaoFactory idf = (IDaoFactory) appContext.getBean("ibatisDao"); IArchiveFactory archiveFactory = (IArchiveFactory) appContext.getBean("archiveFactory"); // // // initialize transport layer and VirtEX ITransportFactory transport = new InMemoryTransportFactory(); VirtualExchange virtEx = new VirtualExchange(transport); // // ITradingSystem tradSys = (ITradingSystem) appContext.getBean("tradingSystem"); IStreamFactory streamFactory= (IStreamFactory) appContext.getBean("streamFactory"); StreamEventIterator<? extends TimeStreamEvent>[] streamIters = streamFactory.getStreamIterators(btConfig); // // initialize the backtester VisualBacktester bt = new VisualBacktester(archiveFactory, transport, idf, virtEx, new ITradingSystem[]{tradSys}, streamIters, btConfig, false); // set the backtest config, for later reporting. // // // ok, now that we have all initialized ... execute the // backtest. bt.setSysExit(false); bt.execute(); sr = bt.stop(); sr.setSimulationStatus("SUCCESS"); } catch (Exception ex) { log.warn("Exception while running backtest job", ex); sr.setSimulationStatus("Error while running backtest: " + ex); } return sr; } } public static void main(String[] args) throws Exception { BacktestConfiguration bt = new BacktestConfiguration(); bt.setMdis(new String[]{"CSV.SOY"}); bt.setTdis(new String[]{"CSV.SOY"}); bt.setDate8Time6Start(20120101000000L); bt.setDate8Time6End(20120501000000L); new ParallelizedBacktester("backtest1.xml", bt, new TimeRangeSplitterWeekly()); } }