package com.activequant.backtesting.reporting; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Iterator; import java.util.Map.Entry; import org.apache.log4j.Logger; import org.jfree.chart.ChartUtilities; import sun.reflect.generics.reflectiveObjects.NotImplementedException; import com.activequant.backtesting.OrderEventListener; import com.activequant.domainmodel.AlgoConfig; import com.activequant.domainmodel.backtesting.BacktestConfiguration; import com.activequant.domainmodel.backtesting.SimulationReport; import com.activequant.domainmodel.exceptions.InvalidDate8Time6Input; import com.activequant.interfaces.backtesting.IReportRenderer; import com.activequant.timeseries.CSVExporter; import com.activequant.timeseries.ChartUtils; import com.activequant.timeseries.TSContainer2; import com.activequant.timeseries.TSContainerMethods; import com.activequant.utils.ArrayUtils; import com.activequant.utils.CsvMapWriter; import com.activequant.utils.Date8Time6Parser; import com.activequant.utils.FileUtils; public class HTMLReportGen implements IReportRenderer { protected CSVFileFillExporter fillExporter = new CSVFileFillExporter(); private Logger log = Logger.getLogger(HTMLReportGen.class); private String templateFolder = "templates"; private String htmlTemplate = "perfreport.html"; private String cssFile = "report.css"; private String targetFolder; public HTMLReportGen(String targetFolder, String templateFolder) { this.targetFolder = targetFolder; this.templateFolder = templateFolder; } public void genReport(AlgoConfig[] algoConfigs, OrderEventListener oelistener, PNLMonitor pnlMonitor, BacktestConfiguration btConfig) { try { TSContainerMethods tcm = new TSContainerMethods(); new File(targetFolder).mkdirs(); fillExporter.export(targetFolder, oelistener.getFillEvents()); // generate PNL report TSContainer2 pnlContainer = pnlMonitor.getCumulatedTSContainer(); tcm.overwriteNull(pnlContainer); tcm.overwriteNull(pnlContainer, 0.0); FileOutputStream fout; try { fout = new FileOutputStream(targetFolder + File.separator + "pnl.csv"); CSVExporter c = new CSVExporter(fout, pnlContainer); c.write(); fout.close(); } catch (Exception e) { e.printStackTrace(); } // generate a PNL chart. ChartUtilities.saveChartAsPNG(new File(targetFolder + File.separator + "pnl.png"), pnlMonitor.getStaticChart(), 800, 600); TSContainer2 posSeries = oelistener.getPositionOverTime(); tcm.overwriteNull(posSeries, 0.0); try { fout = new FileOutputStream(targetFolder + File.separator + "positions.csv"); CSVExporter c = new CSVExporter(fout, posSeries); c.write(); fout.close(); } catch (Exception e) { e.printStackTrace(); } // generate a position chart, make sure we align it to the // pnlContainer tcm.injectTimeStamps(posSeries, pnlContainer.getTimeStamps()); ChartUtilities.saveChartAsPNG(new File(targetFolder + File.separator + "position.png"), ChartUtils.getStepChart("Position", posSeries), 800, 600); // dump all used algo configs. try { if (algoConfigs != null) { for (AlgoConfig ac : algoConfigs) { fout = new FileOutputStream(targetFolder + File.separator + "algoconfig_" + ac.getId() + "_.csv"); new CsvMapWriter().write(ac.propertyMap(), fout); fout.close(); } } } catch (Exception e) { e.printStackTrace(); } // dump the backtest config. try { if (btConfig != null) { fout = new FileOutputStream(targetFolder + File.separator + "btconfig_" + btConfig.getId() + "_.csv"); new CsvMapWriter().write(btConfig.propertyMap(), fout); fout.close(); } } catch (Exception e) { e.printStackTrace(); } // calculate some statistics. BacktestStatistics bs = new BacktestStatistics(); bs.setReportId(new SimpleDateFormat("yyyyMMdd").format(new Date())); bs.calcPNLStats(pnlContainer); bs.calcPosStats(oelistener.getPositionOverTime()); bs.populateOrderStats(oelistener); // dump the stats try { fout = new FileOutputStream(targetFolder + File.separator + "statistics.csv"); new CsvMapWriter().write(bs.getStatistics(), fout); fout.close(); } catch (Exception e) { e.printStackTrace(); } generate(algoConfigs, btConfig, bs); } catch (IOException ex) { log.warn("Error", ex); } } public void genReport(BacktestConfiguration btConfig, AlgoConfig[] algoConfigs, SimulationReport simReport) { throw new NotImplementedException(); } public void generate(AlgoConfig[] algoConfigs, BacktestConfiguration bc, BacktestStatistics bt) throws FileNotFoundException, IOException { // take the template input and generate it. // read-in the entire file. log.info("Generating report."); log.info("Reading template from " + templateFolder + File.separator + htmlTemplate); FileUtils.copy(templateFolder + File.separator + cssFile, targetFolder + File.separator + cssFile); String templateString = FileUtils .readFully(new FileInputStream(templateFolder + File.separator + htmlTemplate)); log.info("Populating generic section."); // replace the place holders. templateString = templateString.replace("{REPORTID}", bt.getReportId()); if (bc != null) { templateString = templateString.replace("{MDIS}", ArrayUtils.toString(bc.getMdis())); templateString = templateString.replace("{TDIS}", ArrayUtils.toString(bc.getTdis())); try { templateString = templateString.replace("{BACKTESTSTART}", "" + new Date8Time6Parser().fromDouble((double) bc.getDate8Time6Start()).getCalendar() .getTime()); templateString = templateString.replace("{BACKTESTEND}", "" + new Date8Time6Parser().fromDouble((double) bc.getDate8Time6End()).getCalendar().getTime()); } catch (InvalidDate8Time6Input e) { e.printStackTrace(); } templateString = templateString.replace("{REPORTRESOLUTION}", bc.getResolutionTimeFrame()); } int acMarkerStart = templateString.indexOf("<!-- AC_MARKER_START -->"); int acMarkerEnd = templateString.indexOf("<!-- AC_MARKER_END -->"); String acTemplate = templateString.substring(acMarkerStart + "<!-- AC_MARKER_START -->".length(), acMarkerEnd); if (algoConfigs != null) { StringBuffer acSection = new StringBuffer(); for (AlgoConfig ac : algoConfigs) { String acTemplateLocal = new String(acTemplate); log.info("Replacing algo config section. "); int acEntryMarkerStart = acTemplateLocal.indexOf("<!-- AC_ENTRY_START -->"); int acEntryMarkerEnd = acTemplateLocal.indexOf("<!-- AC_ENTRY_END -->"); String acEntryTemplate = acTemplateLocal.substring( acEntryMarkerStart + "<!-- AC_ENTRY_START -->".length(), acEntryMarkerEnd); StringBuffer entryRep = new StringBuffer(); // build the entry rows. Iterator<Entry<String, Object>> entryIter = ac.propertyMap().entrySet().iterator(); while (entryIter.hasNext()) { Entry<String, Object> e = entryIter.next(); String entry = acEntryTemplate.replace("{KEY}", e.getKey()); if (e.getValue() != null) entry = entry.replace("{VALUE}", e.getValue().toString()); else entry = entry.replace("{VALUE}", "-"); entryRep.append(entry).append("\n"); } acTemplateLocal = acTemplateLocal.substring(0, acEntryMarkerStart) + entryRep.toString() + acTemplateLocal.substring(acEntryMarkerEnd); // acSection.append(acTemplateLocal).append("\n"); } // replace the section in the report. templateString = templateString.substring(0, acMarkerStart) + acSection.toString() + templateString.substring(acMarkerEnd); } else { templateString = templateString.substring(0, acMarkerStart) + templateString.substring(acMarkerEnd); } log.info("Replacing leg section"); int legMarkerStart = templateString.indexOf("<!-- LEG_MARKER_START -->"); int legMarkerEnd = templateString.indexOf("<!-- LEG_MARKER_END -->"); String instrumentTemplate = templateString.substring(legMarkerStart, legMarkerEnd + "<!-- LEG_MARKER_END -->".length()); templateString = templateString.replace(instrumentTemplate, ""); int legInsertPoint = templateString.indexOf("<!-- LEG_MARKER_PLACEMENT -->"); for (String instrument : bt.getInstrumentIDs()) { log.info("Replacing for " + instrument); String t = new String(instrumentTemplate); t = t.replace("{INSTRUMENTID}", instrument); t = t.replace("{MAXPNL}", "" + bt.getStatistics().get(instrument + ".MAXPNL")); t = t.replace("{TOTALPLACED}", "" + bt.getStatistics().get(instrument + ".TOTALPLACED")); t = t.replace("{TOTALFILLS}", "" + bt.getStatistics().get(instrument + ".TOTALFILLS")); t = t.replace("{TOTALORDERUPDS}", "" + bt.getStatistics().get(instrument + ".TOTALORDERUPDS")); t = t.replace("{TOTALORDERCNCL}", "" + bt.getStatistics().get(instrument + ".TOTALORDERCNCL")); t = t.replace("{FINALPNL}", "" + bt.getStatistics().get(instrument + ".FINALPNL")); t = t.replace("{PNLPERTRADE}", "" + bt.getStatistics().get(instrument + ".PNLPERTRADE")); templateString = templateString.replace("<!-- LEG_MARKER_PLACEMENT -->", "<!-- LEG_MARKER_PLACEMENT -->\n" + t); } // replacing all null with - templateString = templateString.replace("null", "-"); log.info("All replaced."); new FileOutputStream(targetFolder + File.separator + "small_report.html").write(templateString.getBytes()); log.info("Written."); } public String getTemplateFolder() { return templateFolder; } public void setTemplateFolder(String templateFolder) { this.templateFolder = templateFolder; } }