├── build.xml ├── config └── log4j.properties ├── data └── nasdaq10.dat ├── lib ├── antlr.jar ├── aopalliance.jar ├── asg.cliche-020310.jar ├── asm-attrs.jar ├── asm.jar ├── com.springsource.net.sf.cglib-2.2.0.jar ├── commons-collections-3.1.jar ├── commons-collections.jar ├── commons-dbcp-1.2.2.jar ├── commons-logging-1.1.1.jar ├── commons-pool-1.5.3.jar ├── derby.jar ├── derbyclient.jar ├── derbynet.jar ├── dom4j-1.6.1.jar ├── dom4j.jar ├── ejb3-persistence.jar ├── groovy-all-1.7.10.jar ├── hibernate-annotations.jar ├── hibernate-commons-annotations.jar ├── hibernate-core.jar ├── hibernate-entitymanager.jar ├── iText-2.1.5.jar ├── javassist-3.9.0.GA.jar ├── javassist.jar ├── jcommon-1.0.16.jar ├── jfreechart-1.0.13.jar ├── jmxri.jar ├── jmxtools.jar ├── jta-1.1.jar ├── jta.jar ├── junit.jar ├── log4j.jar ├── mail.jar ├── org.springframework.aop-3.0.5.RELEASE.jar ├── org.springframework.asm-3.0.5.RELEASE.jar ├── org.springframework.aspects-3.0.5.RELEASE.jar ├── org.springframework.beans-3.0.5.RELEASE.jar ├── org.springframework.context-3.0.5.RELEASE.jar ├── org.springframework.context.support-3.0.5.RELEASE.jar ├── org.springframework.core-3.0.5.RELEASE.jar ├── org.springframework.expression-3.0.5.RELEASE.jar ├── org.springframework.instrument-3.0.5.RELEASE.jar ├── org.springframework.instrument.tomcat-3.0.5.RELEASE.jar ├── org.springframework.jdbc-3.0.5.RELEASE.jar ├── org.springframework.jms-3.0.5.RELEASE.jar ├── org.springframework.orm-3.0.5.RELEASE.jar ├── org.springframework.oxm-3.0.5.RELEASE.jar ├── org.springframework.spring-library-3.0.5.RELEASE.libd ├── org.springframework.test-3.0.5.RELEASE.jar ├── org.springframework.transaction-3.0.5.RELEASE.jar ├── org.springframework.web-3.0.5.RELEASE.jar ├── org.springframework.web.portlet-3.0.5.RELEASE.jar ├── org.springframework.web.servlet-3.0.5.RELEASE.jar ├── org.springframework.web.struts-3.0.5.RELEASE.jar ├── persistence-api-1.0.2.jar ├── slf4j-api.jar ├── slf4j-log4j12.jar └── ta-lib-0.4.0.jar ├── openquant.bat ├── openquant.sh ├── resource ├── TestTradeSystem.xml ├── symbols-yahoo-500KVolume.xml ├── symbols-yahoo-SP500.xml ├── symbols-yahoo-highVolume.xml └── symbols-yahoo-nasdaq10.xml ├── scripts └── test.groovy ├── src └── org │ └── openquant │ ├── backtest │ ├── BackTestExecutor.java │ ├── Candle.java │ ├── CandleSeries.java │ ├── CandleSeriesTestContext.java │ ├── DateUtil.java │ ├── EquityCalculator.java │ ├── GroovyTradeSystem.java │ ├── Order.java │ ├── OrderManager.java │ ├── OrderedSignalBackTestExecutor.java │ ├── Position.java │ ├── PositionProcessor.java │ ├── QuantityCalculator.java │ ├── Series.java │ ├── SimpleEquityReport.java │ ├── command │ │ ├── OpenQuantCommand.java │ │ └── SymbolSetSeries.java │ ├── intraday │ │ ├── AbstractIntradayTest.java │ │ ├── BuyIntent.java │ │ ├── CreateChart.java │ │ ├── DipperIntradayTest.java │ │ ├── IntradayBackTestExecutor.java │ │ ├── IntradayCandle.java │ │ ├── IntradayCandleSeries.java │ │ ├── IntradayOrderManager.java │ │ ├── IntradayQuoteDatasource.java │ │ ├── IntradaySeriesDatasource.java │ │ ├── LLHIntradayTest.java │ │ ├── LowestLowIntra.java │ │ ├── MyTest.java │ │ ├── OrderCallback.java │ │ ├── OrderTimeFrame.java │ │ ├── SellIntent.java │ │ ├── TimeBasedLimitSellIntent.java │ │ ├── TimeBasedSellIntent.java │ │ └── TimeBasedStopSellIntent.java │ └── report │ │ ├── AbstractReport.java │ │ ├── CSVReport.java │ │ ├── CSVReport2.java │ │ ├── JFreeChartReport.java │ │ ├── JFreeChartReportOriginal.java │ │ ├── MultiStatJFreeChartReport.java │ │ └── TradesReport.java │ ├── data │ ├── ObjectStreamSeriesDatasource.java │ ├── SeriesCollection.java │ ├── SeriesDatasource.java │ ├── YahooSeriesDatasource.java │ └── load │ │ └── DataLoader.java │ ├── quote │ ├── QuoteDataSource.java │ ├── QuoteListener.java │ └── YahooQuoteDataSource.java │ └── util │ ├── Day.java │ └── OpenMarketUtil.java └── test └── org └── openquant └── test └── TestTradeSystem.java /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /config/log4j.properties: -------------------------------------------------------------------------------- 1 | # Set root logger level to DEBUG and its only appender to A1. 2 | log4j.rootLogger=INFO, Console, File 3 | 4 | # A1 is set to be a ConsoleAppender. 5 | log4j.appender.Console=org.apache.log4j.ConsoleAppender 6 | log4j.appender.Console.layout=org.apache.log4j.PatternLayout 7 | log4j.appender.Console.layout.ConversionPattern=%m (%F:%L)%n 8 | 9 | log4j.appender.File=org.apache.log4j.RollingFileAppender 10 | log4j.appender.File.File=logs/openquant.log 11 | log4j.appender.File.MaxFileSize=5MB 12 | log4j.appender.File.MaxBackupIndex=1 13 | log4j.appender.File.layout=org.apache.log4j.PatternLayout 14 | log4j.appender.File.layout.ConversionPattern=%m%n -------------------------------------------------------------------------------- /data/nasdaq10.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/data/nasdaq10.dat -------------------------------------------------------------------------------- /lib/antlr.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/antlr.jar -------------------------------------------------------------------------------- /lib/aopalliance.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/aopalliance.jar -------------------------------------------------------------------------------- /lib/asg.cliche-020310.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/asg.cliche-020310.jar -------------------------------------------------------------------------------- /lib/asm-attrs.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/asm-attrs.jar -------------------------------------------------------------------------------- /lib/asm.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/asm.jar -------------------------------------------------------------------------------- /lib/com.springsource.net.sf.cglib-2.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/com.springsource.net.sf.cglib-2.2.0.jar -------------------------------------------------------------------------------- /lib/commons-collections-3.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/commons-collections-3.1.jar -------------------------------------------------------------------------------- /lib/commons-collections.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/commons-collections.jar -------------------------------------------------------------------------------- /lib/commons-dbcp-1.2.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/commons-dbcp-1.2.2.jar -------------------------------------------------------------------------------- /lib/commons-logging-1.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/commons-logging-1.1.1.jar -------------------------------------------------------------------------------- /lib/commons-pool-1.5.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/commons-pool-1.5.3.jar -------------------------------------------------------------------------------- /lib/derby.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/derby.jar -------------------------------------------------------------------------------- /lib/derbyclient.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/derbyclient.jar -------------------------------------------------------------------------------- /lib/derbynet.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/derbynet.jar -------------------------------------------------------------------------------- /lib/dom4j-1.6.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/dom4j-1.6.1.jar -------------------------------------------------------------------------------- /lib/dom4j.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/dom4j.jar -------------------------------------------------------------------------------- /lib/ejb3-persistence.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/ejb3-persistence.jar -------------------------------------------------------------------------------- /lib/groovy-all-1.7.10.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/groovy-all-1.7.10.jar -------------------------------------------------------------------------------- /lib/hibernate-annotations.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/hibernate-annotations.jar -------------------------------------------------------------------------------- /lib/hibernate-commons-annotations.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/hibernate-commons-annotations.jar -------------------------------------------------------------------------------- /lib/hibernate-core.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/hibernate-core.jar -------------------------------------------------------------------------------- /lib/hibernate-entitymanager.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/hibernate-entitymanager.jar -------------------------------------------------------------------------------- /lib/iText-2.1.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/iText-2.1.5.jar -------------------------------------------------------------------------------- /lib/javassist-3.9.0.GA.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/javassist-3.9.0.GA.jar -------------------------------------------------------------------------------- /lib/javassist.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/javassist.jar -------------------------------------------------------------------------------- /lib/jcommon-1.0.16.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/jcommon-1.0.16.jar -------------------------------------------------------------------------------- /lib/jfreechart-1.0.13.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/jfreechart-1.0.13.jar -------------------------------------------------------------------------------- /lib/jmxri.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/jmxri.jar -------------------------------------------------------------------------------- /lib/jmxtools.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/jmxtools.jar -------------------------------------------------------------------------------- /lib/jta-1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/jta-1.1.jar -------------------------------------------------------------------------------- /lib/jta.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/jta.jar -------------------------------------------------------------------------------- /lib/junit.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/junit.jar -------------------------------------------------------------------------------- /lib/log4j.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/log4j.jar -------------------------------------------------------------------------------- /lib/mail.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/mail.jar -------------------------------------------------------------------------------- /lib/org.springframework.aop-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.aop-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.asm-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.asm-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.aspects-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.aspects-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.beans-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.beans-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.context-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.context-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.context.support-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.context.support-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.core-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.core-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.expression-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.expression-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.instrument-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.instrument-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.instrument.tomcat-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.instrument.tomcat-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.jdbc-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.jdbc-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.jms-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.jms-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.orm-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.orm-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.oxm-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.oxm-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.spring-library-3.0.5.RELEASE.libd: -------------------------------------------------------------------------------- 1 | Library-SymbolicName: org.springframework.spring 2 | Library-Version: 3.0.5.RELEASE 3 | Library-Name: Spring Framework 4 | Import-Bundle: 5 | org.springframework.aop;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 6 | org.springframework.asm;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 7 | org.springframework.aspects;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 8 | org.springframework.beans;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 9 | org.springframework.context;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 10 | org.springframework.context.support;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 11 | org.springframework.core;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 12 | org.springframework.expression;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 13 | org.springframework.jdbc;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 14 | org.springframework.jms;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 15 | org.springframework.orm;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 16 | org.springframework.oxm;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 17 | org.springframework.transaction;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 18 | org.springframework.web;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 19 | org.springframework.web.servlet;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 20 | org.springframework.web.portlet;version="[3.0.5.RELEASE, 3.0.5.RELEASE]", 21 | com.springsource.org.aopalliance;version="[1.0.0, 1.0.0]" 22 | -------------------------------------------------------------------------------- /lib/org.springframework.test-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.test-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.transaction-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.transaction-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.web-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.web-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.web.portlet-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.web.portlet-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.web.servlet-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.web.servlet-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/org.springframework.web.struts-3.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/org.springframework.web.struts-3.0.5.RELEASE.jar -------------------------------------------------------------------------------- /lib/persistence-api-1.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/persistence-api-1.0.2.jar -------------------------------------------------------------------------------- /lib/slf4j-api.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/slf4j-api.jar -------------------------------------------------------------------------------- /lib/slf4j-log4j12.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/slf4j-log4j12.jar -------------------------------------------------------------------------------- /lib/ta-lib-0.4.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x2q/openquant/9a5442f89a85b1c6efb7b7f0ad6fc511839c181f/lib/ta-lib-0.4.0.jar -------------------------------------------------------------------------------- /openquant.bat: -------------------------------------------------------------------------------- 1 | 2 | java -cp "./lib/*;./scripts/;./config" -Xmx2048m -Xrs org.openquant.backtest.command.OpenQuantCommand -------------------------------------------------------------------------------- /openquant.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DIRNAME=`dirname $0` 4 | 5 | cd $DIRNAME 6 | 7 | # set Java 8 | if [ "x$JAVA" = "x" ]; then 9 | if [ -x "$TS_HOME/jre/bin/java" ]; then 10 | JAVA="$TS_HOME/jre/bin/java" 11 | elif [ "x$JAVA_HOME" != "x" ]; then 12 | JAVA="$JAVA_HOME/bin/java" 13 | else 14 | JAVA="java" 15 | fi 16 | fi 17 | 18 | # Memory Options 19 | JAVA_OPTS="-Xmx512m -Xrs" 20 | 21 | java -cp "./lib/*:./scripts/:./config" $JAVA_OPTS org.openquant.backtest.command.OpenQuantCommand -------------------------------------------------------------------------------- /resource/TestTradeSystem.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /resource/symbols-yahoo-highVolume.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | XOM 10 | MSFT 11 | AAPL 12 | GE 13 | WMT 14 | PG 15 | BAC 16 | JNJ 17 | JPM 18 | WFC 19 | VALE 20 | CVX 21 | CSCO 22 | T 23 | PFE 24 | ORCL 25 | INTC 26 | C 27 | HPQ 28 | KO 29 | MRK 30 | ITUB 31 | SLB 32 | COP 33 | GS 34 | VZ 35 | DIS 36 | QCOM 37 | HD 38 | TSM 39 | CMCSA 40 | USB 41 | CVS 42 | F 43 | NOK 44 | KFT 45 | MO 46 | MS 47 | NWSA 48 | BMY 49 | EMC 50 | LOW 51 | ABX 52 | GILD 53 | DOW 54 | FCX 55 | DTV 56 | UNH 57 | TXN 58 | DELL 59 | GLW 60 | EBAY 61 | HAL 62 | XTO 63 | YHOO 64 | SCHW 65 | AMAT 66 | LVS 67 | MOT 68 | CHK 69 | ATVI 70 | AA 71 | MRVL 72 | SYMC 73 | WFT 74 | S 75 | CX 76 | FITB 77 | VLO 78 | BSX 79 | M 80 | RF 81 | DAL 82 | STX 83 | SNDK 84 | XRX 85 | MU 86 | NVDA 87 | NLY 88 | GNW 89 | Q 90 | X 91 | AUY 92 | KEY 93 | ALU 94 | MGM 95 | AMD 96 | AIG 97 | MI 98 | HBAN 99 | SIRI 100 | LSI 101 | ETFC 102 | UAUA 103 | ONNN 104 | THC 105 | BRCD 106 | CIM 107 | AMR 108 | BPOP 109 | LVLT 110 | EK 111 | AKS 112 | DRYS 113 | SNV 114 | HL 115 | FNM 116 | LCC 117 | FRE 118 | PALM 119 | DYN 120 | YRCW 121 | ABK 122 | ANX 123 | 124 | -------------------------------------------------------------------------------- /resource/symbols-yahoo-nasdaq10.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ATVI 10 | ADBE 11 | AKAM 12 | ALTR 13 | AMZN 14 | AMGN 15 | APOL 16 | AAPL 17 | AMAT 18 | ADSK 19 | 20 | 21 | -------------------------------------------------------------------------------- /scripts/test.groovy: -------------------------------------------------------------------------------- 1 | import org.openquant.backtest.Position 2 | import org.openquant.backtest.Series 3 | 4 | def Close = context.closeSeries() 5 | def EMAClose = Close.EMA(12) 6 | def RSI = Close.RSI(25) 7 | 8 | for (int bar = 25; bar < context.barsCount() - 1; bar++) { 9 | 10 | println "Date[${context.date(bar)}], Close[${Close.getAt(bar)}], " + 11 | "EMA[${EMAClose.getAt(bar)}], RSI[${RSI.getAt(bar)}]" 12 | 13 | Position pos = context.getLastOpenPosition(); 14 | 15 | if (RSI.getAt(bar - 1) > RSI.getAt(bar - 2)) { 16 | context.buyAtLimit(bar, context.close(bar), 1000, "ELL") 17 | } 18 | else{ 19 | if (context.hasOpenPositions()) { 20 | context.sellAtLimit(bar, pos, pos.getEntryPrice() * 1.05, "XLL") 21 | } 22 | 23 | if (context.hasOpenPositions()) { 24 | context.sellAtStop(bar, pos, pos.getEntryPrice() * 0.95, "XLS") 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/org/openquant/backtest/BackTestExecutor.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | /* 4 | Copyright (c) 2010, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.util.ArrayList; 23 | import java.util.Collections; 24 | import java.util.Comparator; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | import org.apache.commons.logging.Log; 30 | import org.apache.commons.logging.LogFactory; 31 | import org.openquant.backtest.report.AbstractReport; 32 | import org.openquant.backtest.report.CSVReport; 33 | import org.openquant.backtest.report.JFreeChartReport; 34 | import org.openquant.backtest.report.TradesReport; 35 | import org.openquant.data.SeriesDatasource; 36 | import org.springframework.util.StopWatch; 37 | 38 | public class BackTestExecutor { 39 | 40 | private Log log = LogFactory.getLog(BackTestExecutor.class); 41 | 42 | private SeriesDatasource data; 43 | 44 | private List symbols; 45 | 46 | private CandleSeriesTestContext test; 47 | 48 | private double capital = 100000; 49 | 50 | private double commission = 9.99; 51 | 52 | private double slippage = 0.001; 53 | 54 | private List positions = new ArrayList(); 55 | 56 | private String reportName; 57 | 58 | private int testCycles = 1; 59 | 60 | public int getTestCycles() { 61 | return testCycles; 62 | } 63 | 64 | public void setTestCycles(int testCycles) { 65 | this.testCycles = testCycles; 66 | } 67 | 68 | public BackTestExecutor(SeriesDatasource data, List symbols, 69 | CandleSeriesTestContext test, String reportName, double capital, 70 | double commission, double slippage) { 71 | super(); 72 | this.data = data; 73 | this.symbols = symbols; 74 | this.test = test; 75 | this.capital = capital; 76 | this.commission = commission; 77 | this.slippage = slippage; 78 | this.reportName = reportName; 79 | 80 | } 81 | 82 | Map seriesCache = new HashMap(); 83 | 84 | private void populateCache(){ 85 | for (String symbol : symbols) { 86 | 87 | try { 88 | seriesCache.put(symbol, data.fetchSeries(symbol) ); 89 | } catch (Exception e) { 90 | log.error(e.getMessage(), e); 91 | } 92 | 93 | } 94 | } 95 | 96 | public double run() { 97 | 98 | StopWatch watch = new StopWatch("-- DEBUGGING --"); 99 | watch.start("execute tradesystem"); 100 | 101 | Collections.shuffle(symbols); 102 | 103 | for (String symbol : symbols) { 104 | 105 | CandleSeries candleSeries; 106 | try { 107 | candleSeries = data.fetchSeries(symbol); 108 | 109 | test.reset(); 110 | test.setSeries(candleSeries); 111 | test.run(); 112 | 113 | positions.addAll(test.getOrderManager().getClosedPositions()); 114 | } catch (Exception e) { 115 | log.error(e.getMessage(), e); 116 | } 117 | 118 | } 119 | 120 | // order all of the positions by entry date 121 | Collections.sort(positions, new Comparator() { 122 | 123 | @Override 124 | public int compare(Position positionOne, Position positionTwo) { 125 | return positionOne.getEntryDate().compareTo( 126 | positionTwo.getEntryDate()); 127 | } 128 | 129 | }); 130 | 131 | int posSize = test.getOrderManager().getOpenPositions().size(); 132 | 133 | log.info("Open Position Size : " + posSize); 134 | 135 | // generate reports 136 | AbstractReport report = new JFreeChartReport(reportName + ".jpg", 137 | capital, commission, slippage, positions, test 138 | .getOrderManager().getOpenPositions()); 139 | //double endingCapital = report.getTotalCapitalAndEquity(); 140 | //log.debug(String.format("Ending capital is %12.2f", endingCapital)); 141 | report.render(); 142 | 143 | new CSVReport(capital, commission, slippage, positions, test 144 | .getOrderManager().getOpenPositions(), reportName + ".csv").render(); 145 | 146 | new TradesReport(capital, commission, slippage, positions, test 147 | .getOrderManager().getOpenPositions(), reportName + "-trades.txt").render(); 148 | 149 | 150 | watch.stop(); 151 | log.debug(String.format("Time : %s seconds", watch.getTotalTimeSeconds())); 152 | 153 | return report.getTotalCapitalAndEquity(); 154 | } 155 | 156 | 157 | } -------------------------------------------------------------------------------- /src/org/openquant/backtest/Candle.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | /* 4 | Copyright (c) 2009, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.Serializable; 23 | import java.util.Date; 24 | 25 | import javax.persistence.Entity; 26 | import javax.persistence.Id; 27 | import javax.persistence.Version; 28 | 29 | @Entity 30 | public class Candle implements Comparable, Serializable { 31 | 32 | private static final long serialVersionUID = 1L; 33 | 34 | @SuppressWarnings("unused") 35 | @Id 36 | private Integer id; 37 | 38 | @SuppressWarnings("unused") 39 | @Version 40 | private Object version; 41 | 42 | private String symbol; 43 | 44 | private Date date = new Date(); 45 | 46 | private double openPrice; 47 | 48 | private double highPrice; 49 | 50 | private double lowPrice; 51 | 52 | private double closePrice; 53 | 54 | private double volume; 55 | 56 | public Candle() { 57 | } 58 | 59 | public Candle(String symbol, Date date, double open, double high, double low, double close, double volume) { 60 | super(); 61 | this.symbol = symbol; 62 | this.date = date; 63 | this.openPrice = open; 64 | this.highPrice = high; 65 | this.lowPrice = low; 66 | this.closePrice = close; 67 | this.volume = volume; 68 | } 69 | 70 | public double getOpenPrice() { 71 | return openPrice; 72 | } 73 | 74 | public void setOpenPrice(double open) { 75 | this.openPrice = open; 76 | } 77 | 78 | public double getHighPrice() { 79 | return highPrice; 80 | } 81 | 82 | public void setHighPrice(double high) { 83 | this.highPrice = high; 84 | } 85 | 86 | public double getLowPrice() { 87 | return lowPrice; 88 | } 89 | 90 | public void setLowPrice(double low) { 91 | this.lowPrice = low; 92 | } 93 | 94 | public double getClosePrice() { 95 | return closePrice; 96 | } 97 | 98 | public void setClosePrice(double close) { 99 | this.closePrice = close; 100 | } 101 | 102 | public double getVolume() { 103 | return volume; 104 | } 105 | 106 | public void setVolume(double volume) { 107 | this.volume = volume; 108 | } 109 | 110 | public Date getDate() { 111 | return date; 112 | } 113 | 114 | public void setDate(Date time) { 115 | this.date = time; 116 | } 117 | 118 | public String getSymbol() { 119 | return symbol; 120 | } 121 | 122 | public void setSymbol(String symbol) { 123 | this.symbol = symbol; 124 | } 125 | 126 | @Override 127 | public String toString() { 128 | return String.format("%s [%.2f, %.2f, %.2f, %.2f] [%s]", symbol, openPrice, highPrice, lowPrice, closePrice, date); 129 | } 130 | 131 | @Override 132 | public int compareTo(Candle c) { 133 | return this.getDate().compareTo(c.getDate()); 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/CandleSeries.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | /* 4 | Copyright (c) 2010, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.Serializable; 23 | import java.util.ArrayList; 24 | import java.util.Date; 25 | import java.util.List; 26 | 27 | public class CandleSeries implements Serializable{ 28 | 29 | private static final long serialVersionUID = 1L; 30 | 31 | protected List candles = new ArrayList(); 32 | 33 | public List getCandles() { 34 | return candles; 35 | } 36 | 37 | public String getSymbol() { 38 | return candles.get(0).getSymbol(); 39 | } 40 | 41 | public CandleSeries(List candles) { 42 | this.candles = candles; 43 | } 44 | 45 | public int size() { 46 | return candles.size(); 47 | } 48 | 49 | public Candle get(int index) { 50 | return candles.get(index); 51 | } 52 | 53 | public Date getBeginDate(){ 54 | return candles.get(0).getDate(); 55 | } 56 | 57 | public Date getEndDate(){ 58 | return candles.get(candles.size()-1).getDate(); 59 | } 60 | 61 | public Series getOpenSeries() { 62 | 63 | double[] rArray = new double[candles.size()]; 64 | 65 | int i = 0; 66 | for (Candle candle : candles) { 67 | rArray[i++] = candle.getOpenPrice(); 68 | } 69 | 70 | return new Series(rArray); 71 | } 72 | 73 | public Series getCloseSeries() { 74 | 75 | double[] rArray = new double[candles.size()]; 76 | 77 | int i = 0; 78 | for (Candle candle : candles) { 79 | rArray[i++] = candle.getClosePrice(); 80 | } 81 | 82 | return new Series(rArray); 83 | } 84 | 85 | public Series getHighSeries() { 86 | 87 | double[] rArray = new double[candles.size()]; 88 | 89 | int i = 0; 90 | for (Candle candle : candles) { 91 | rArray[i++] = candle.getHighPrice(); 92 | } 93 | 94 | return new Series(rArray); 95 | } 96 | 97 | public Series getLowSeries() { 98 | 99 | double[] rArray = new double[candles.size()]; 100 | 101 | int i = 0; 102 | for (Candle candle : candles) { 103 | rArray[i++] = candle.getLowPrice(); 104 | } 105 | 106 | return new Series(rArray); 107 | } 108 | 109 | public Series getVolumeSeries() { 110 | 111 | double[] rArray = new double[candles.size()]; 112 | 113 | int i = 0; 114 | for (Candle candle : candles) { 115 | rArray[i++] = candle.getVolume(); 116 | } 117 | 118 | return new Series(rArray); 119 | } 120 | 121 | @Override 122 | public String toString() { 123 | 124 | StringBuffer buffer = new StringBuffer(); 125 | for (Candle candle : candles) { 126 | buffer.append(candle.toString()); 127 | buffer.append("\n"); 128 | } 129 | 130 | return buffer.toString(); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/CandleSeriesTestContext.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | /* 4 | Copyright (c) 2010, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.util.Date; 23 | import java.util.List; 24 | 25 | import org.apache.commons.logging.Log; 26 | import org.apache.commons.logging.LogFactory; 27 | import org.openquant.backtest.intraday.OrderCallback; 28 | import org.openquant.backtest.intraday.TimeBasedSellIntent; 29 | 30 | public abstract class CandleSeriesTestContext { 31 | 32 | protected Log log = LogFactory.getLog(CandleSeriesTestContext.class); 33 | 34 | private CandleSeries series; 35 | 36 | private OrderManager orderManager = new OrderManager(); 37 | 38 | public CandleSeriesTestContext() { 39 | } 40 | 41 | public abstract void run(); 42 | 43 | public List preFilterOrders(List positions){ 44 | // default behavior is to just return all positions 45 | // override to filter out opening positions. This is ideal 46 | // for categorizing priority to a large list of potential 47 | // buy orders 48 | return positions; 49 | } 50 | 51 | public void finish(final double equity){ 52 | 53 | } 54 | 55 | public void reset() { 56 | orderManager.getClosedPositions().clear(); 57 | orderManager.getOpenPositions().clear(); 58 | orderManager.setSymbol(null); 59 | } 60 | 61 | public OrderManager getOrderManager() { 62 | return orderManager; 63 | } 64 | 65 | public void setOrderManager(OrderManager orderManager) { 66 | this.orderManager = orderManager; 67 | } 68 | 69 | public CandleSeries getSeries() { 70 | return series; 71 | } 72 | 73 | public void setSeries(CandleSeries series) { 74 | this.series = series; 75 | orderManager.setSymbol(series.getSymbol()); 76 | } 77 | 78 | public int barsCount() { 79 | return series.size(); 80 | } 81 | 82 | public Series closeSeries() { 83 | return series.getCloseSeries(); 84 | } 85 | 86 | public Series openSeries() { 87 | return series.getOpenSeries(); 88 | } 89 | 90 | public Series highSeries() { 91 | return series.getHighSeries(); 92 | } 93 | 94 | public Series volumeSeries(){ 95 | return series.getVolumeSeries(); 96 | } 97 | 98 | public Series lowSeries() { 99 | return series.getLowSeries(); 100 | } 101 | 102 | public double volume(int barIndex) { 103 | return series.get(barIndex).getVolume(); 104 | } 105 | 106 | public double close(int barIndex) { 107 | return series.get(barIndex).getClosePrice(); 108 | } 109 | 110 | public double open(int barIndex) { 111 | return series.get(barIndex).getOpenPrice(); 112 | } 113 | 114 | public double high(int barIndex) { 115 | return series.get(barIndex).getHighPrice(); 116 | } 117 | 118 | public double low(int barIndex) { 119 | return series.get(barIndex).getLowPrice(); 120 | } 121 | 122 | public Date date(int barIndex) { 123 | return series.get(barIndex).getDate(); 124 | } 125 | 126 | public Position buyAtMarket(int barIndex, double marketPrice, int quantity, String comments) { 127 | return orderManager.buyAtMarket(barIndex, marketPrice, quantity, comments, series); 128 | } 129 | 130 | public Position buyAtMarket(int barIndex, double marketPrice, int quantity, String comments, double score) { 131 | Position pos = orderManager.buyAtMarket(barIndex, marketPrice, quantity, comments, series); 132 | if (pos != null){ 133 | pos.setScore(score); 134 | } 135 | return pos; 136 | } 137 | 138 | public Position buyAtLimit(int barIndex, double limitPrice, int quantity, String comments) { 139 | return orderManager.buyAtLimit(barIndex, limitPrice, quantity, comments, series); 140 | } 141 | 142 | public Position buyAtLimit(int barIndex, double limitPrice, int quantity, double score, String comments) { 143 | Position pos = orderManager.buyAtLimit(barIndex, limitPrice, quantity, comments, series); 144 | if (pos != null){ 145 | pos.setScore(score); 146 | } 147 | return pos; 148 | } 149 | 150 | public Position buyAtLimit(int barIndex, double limitPrice, String comments, QuantityCalculator calculator) { 151 | return orderManager.buyAtLimit(barIndex, limitPrice, 0, comments, series, calculator); 152 | } 153 | 154 | public Position buyAtLimit(int barIndex, double limitPrice, String comments, QuantityCalculator calculator, double score) { 155 | Position pos = orderManager.buyAtMarket(barIndex, limitPrice, 0, comments, series); 156 | if (pos != null){ 157 | pos.setScore(score); 158 | } 159 | return pos; 160 | } 161 | 162 | public void sellAtLimit(int barIndex, Position position, double limitPrice, String comments) { 163 | orderManager.sellAtLimit(barIndex, position, limitPrice, comments, series); 164 | } 165 | 166 | public void sellAtStop(int barIndex, Position position, double stopPrice, String comments) { 167 | orderManager.sellAtStop(barIndex, position, stopPrice, comments, series); 168 | } 169 | 170 | public Position sellAtClose(int barIndex, Position position, int quantity, String comments){ 171 | return orderManager.sellAtClose(barIndex, position, quantity, comments, series); 172 | } 173 | 174 | public Position getLastOpenPosition() { 175 | return orderManager.getLastOpenPosition(); 176 | } 177 | 178 | public boolean hasOpenPositions() { 179 | return orderManager.hasOpenPositions(); 180 | } 181 | 182 | public boolean containsOpenPosition(Position position){ 183 | return orderManager.containsOpenPosition(position); 184 | } 185 | 186 | public void PrintLine(final String string) { 187 | System.out.println(string); 188 | } 189 | 190 | public void incrementBar(int bar){ 191 | 192 | Candle candle = series.get(bar); 193 | orderManager.processCandle(candle); 194 | } 195 | 196 | public void timeBasedExitOnClose(int days, Position position){ 197 | orderManager.timeBasedExitOnClose(days, position, null); 198 | } 199 | 200 | public void timeBasedExitOnClose(int days, Position position, OrderCallback callback){ 201 | orderManager.timeBasedExitOnClose(days, position, callback); 202 | } 203 | 204 | public void buyAtLimit(double limitPrice, QuantityCalculator calculator, OrderCallback callback) { 205 | orderManager.buyAtLimit(limitPrice, calculator, callback); 206 | } 207 | 208 | public void sellAtLimit(double limitPrice, int quantity, Position position, OrderCallback callback){ 209 | orderManager.sellAtLimit(limitPrice, quantity, position, callback); 210 | } 211 | 212 | public void sellAtLimit(double limitPrice, Position position){ 213 | orderManager.sellAtLimit(limitPrice, position.getQuantity(), position, null); 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/DateUtil.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | /* 4 | Copyright (c) 2010, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.util.ArrayList; 23 | import java.util.Calendar; 24 | import java.util.Date; 25 | import java.util.List; 26 | 27 | public class DateUtil { 28 | 29 | static List holidayList = new ArrayList(); 30 | 31 | // holiday list 32 | static { 33 | holidayList.add(new Date("11/23/2010")); 34 | } 35 | 36 | public static void main(String args[]) { 37 | 38 | /* 39 | Date day1 = new Date("08/17/2009"); // start date 40 | Date day2 = new Date("08/27/2009"); // end date 41 | Date dayCounter = day1; 42 | int counter = 1; 43 | 44 | while (dayCounter.before(day2)) { 45 | 46 | // check for weekends and holiday list 47 | if (dayCounter.getDay() != Calendar.SATURDAY 48 | && dayCounter.getDay() != Calendar.SUNDAY 49 | && !holidayList.contains(dayCounter)) { 50 | counter++; // count weekdays 51 | } 52 | 53 | dayCounter.setDate(dayCounter.getDate() + 1); 54 | } 55 | 56 | System.out.println("Working days = " + counter); 57 | 58 | */ 59 | 60 | 61 | Date date = calculateLookbackDate(1); 62 | 63 | System.out.println(date); 64 | 65 | } 66 | 67 | public static Date calculateLookbackDate(int lookback){ 68 | 69 | // today 70 | Date dayCounter = new Date(); 71 | 72 | int validDatesCounter = lookback; 73 | while(validDatesCounter > 0){ 74 | 75 | // check for weekends and holiday list 76 | dayCounter.setDate(dayCounter.getDate() - 1); 77 | 78 | Calendar calendar = Calendar.getInstance(); 79 | calendar.setTime(dayCounter); 80 | 81 | if (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY 82 | && calendar.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY 83 | && !holidayList.contains(dayCounter)) { 84 | validDatesCounter--; 85 | 86 | } 87 | } 88 | 89 | return dayCounter; 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/EquityCalculator.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | /* 4 | Copyright (c) 2010, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import org.apache.commons.logging.Log; 23 | import org.apache.commons.logging.LogFactory; 24 | 25 | class EquityCalculator implements PositionProcessor { 26 | 27 | private Log log = LogFactory.getLog(EquityCalculator.class); 28 | 29 | private double capital; 30 | 31 | private double slippage; 32 | 33 | private double commission; 34 | 35 | public EquityCalculator(double capital, double commission, double slippage) { 36 | super(); 37 | this.capital = capital; 38 | this.slippage = slippage; 39 | this.commission = commission; 40 | this.capital = capital; 41 | } 42 | 43 | public double getCapital() { 44 | return capital; 45 | } 46 | 47 | @Override 48 | public void processClosePosition(Position position) { 49 | 50 | double profit = (position.getExitPrice() - position.getEntryPrice()) * position.getQuantity(); 51 | profit = profit - (profit*slippage) - commission; 52 | 53 | capital += profit; 54 | 55 | } 56 | 57 | @Override 58 | public void processOpenPosition(Position position) { 59 | 60 | // calculate entry value and add to capital 61 | capital += position.getEntryPrice() * position.getQuantity(); 62 | 63 | } 64 | 65 | @Override 66 | public void finish() { 67 | log.debug(String.format("Ending capital is %12.2f", capital)); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/GroovyTradeSystem.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | /* 4 | Copyright (c) 2010, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.File; 23 | import java.io.FileReader; 24 | import java.io.IOException; 25 | import java.io.Reader; 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | import javax.script.ScriptEngine; 30 | import javax.script.ScriptEngineManager; 31 | import javax.script.ScriptException; 32 | 33 | import org.openquant.data.SeriesDatasource; 34 | import org.openquant.data.YahooSeriesDatasource; 35 | 36 | public class GroovyTradeSystem extends CandleSeriesTestContext { 37 | 38 | private String scriptFile; 39 | 40 | public GroovyTradeSystem(String scriptFile) { 41 | super(); 42 | if( !scriptFile.endsWith(".groovy") ){ 43 | // automatically append the groovy ext 44 | scriptFile += ".groovy"; 45 | } 46 | this.scriptFile = scriptFile; 47 | } 48 | 49 | @Override 50 | public void run() { 51 | 52 | ScriptEngineManager factory = new ScriptEngineManager(); 53 | 54 | ScriptEngine engine = factory.getEngineByName("groovy"); 55 | 56 | 57 | try { 58 | engine.put("context", this); 59 | 60 | engine.eval( readFully ( findScript("scripts") )); 61 | 62 | } catch (ScriptException e) { 63 | log.error(e.getMessage(), e); 64 | } catch (IOException e) { 65 | log.error(e.getMessage(), e); 66 | } 67 | } 68 | 69 | public static String readFully(File file) throws IOException { 70 | return readFully(new FileReader(file)); 71 | } 72 | 73 | public static String readFully(Reader reader) throws IOException { 74 | char[] arr = new char[8 * 1024]; // 8K at a time 75 | StringBuffer buf = new StringBuffer(); 76 | int numChars; 77 | 78 | while ((numChars = reader.read(arr, 0, arr.length)) > 0) { 79 | buf.append(arr, 0, numChars); 80 | } 81 | 82 | return buf.toString(); 83 | } 84 | 85 | private File findScript(String... directories) { 86 | for (String directory : directories) { 87 | for (File pathname : new File(directory).listFiles()) { 88 | if (pathname.isFile() 89 | && pathname.toString().equals(directory + File.separator + scriptFile)) { 90 | return pathname; 91 | } 92 | } 93 | } 94 | return null; 95 | } 96 | 97 | public static void main(String... args) { 98 | // new ClassPathXmlApplicationContext("TestTradeSystem.xml"); 99 | 100 | SeriesDatasource data = new YahooSeriesDatasource("2009-01-01"); 101 | 102 | List symbols = new ArrayList(); 103 | symbols.add("GOOG"); 104 | 105 | BackTestExecutor executor = new BackTestExecutor(data, symbols, 106 | new GroovyTradeSystem("Tester.groovy"), "TesterReport", 100000, 9.99, 107 | 0.2); 108 | executor.run(); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/Order.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | import java.util.Date; 4 | 5 | public class Order { 6 | private boolean entry; 7 | 8 | private Date date; 9 | 10 | private double value; 11 | 12 | private Position parentPosition; 13 | 14 | public Order(boolean entry, Date date, double value, Position position) { 15 | super(); 16 | this.date = date; 17 | this.value = value; 18 | this.entry = entry; 19 | this.parentPosition = position; 20 | } 21 | 22 | public Date getDate() { 23 | return date; 24 | } 25 | 26 | public boolean isEntry() { 27 | return entry; 28 | } 29 | 30 | public double getValue() { 31 | return value; 32 | } 33 | 34 | public void setValue(double value) { 35 | this.value = value; 36 | } 37 | 38 | public Position getParentPosition() { 39 | return parentPosition; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | //return "Order [entry=" + entry + ", date=" + date + ", value=" 45 | // + value + "]"; 46 | 47 | return String.format( "Order[ id=%s, symbol=%s, entry=%s, date=%s, value=%.2f ]", getParentPosition().getId(), getParentPosition().getSymbol(), entry, date, value ); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/org/openquant/backtest/OrderedSignalBackTestExecutor.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | /* 4 | Copyright (c) 2010, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.util.ArrayList; 23 | import java.util.Collections; 24 | import java.util.Comparator; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | import org.apache.commons.logging.Log; 30 | import org.apache.commons.logging.LogFactory; 31 | import org.openquant.backtest.report.AbstractReport; 32 | import org.openquant.backtest.report.CSVReport; 33 | import org.openquant.backtest.report.JFreeChartReport; 34 | import org.openquant.backtest.report.TradesReport; 35 | import org.openquant.data.SeriesDatasource; 36 | import org.springframework.util.StopWatch; 37 | 38 | public class OrderedSignalBackTestExecutor { 39 | 40 | private Log log = LogFactory.getLog(OrderedSignalBackTestExecutor.class); 41 | 42 | private SeriesDatasource data; 43 | 44 | private List symbols; 45 | 46 | private CandleSeriesTestContext test; 47 | 48 | private double capital = 100000; 49 | 50 | private double commission = 9.99; 51 | 52 | private double slippage = 0.001; 53 | 54 | private List positions = new ArrayList(); 55 | 56 | private String reportName; 57 | 58 | private int testCycles = 1; 59 | 60 | public int getTestCycles() { 61 | return testCycles; 62 | } 63 | 64 | public void setTestCycles(int testCycles) { 65 | this.testCycles = testCycles; 66 | } 67 | 68 | public OrderedSignalBackTestExecutor(SeriesDatasource data, List symbols, 69 | CandleSeriesTestContext test, String reportName, double capital, 70 | double commission, double slippage) { 71 | super(); 72 | this.data = data; 73 | this.symbols = symbols; 74 | this.test = test; 75 | this.capital = capital; 76 | this.commission = commission; 77 | this.slippage = slippage; 78 | this.reportName = reportName; 79 | 80 | } 81 | 82 | Map seriesCache = new HashMap(); 83 | 84 | private void populateCache(){ 85 | for (String symbol : symbols) { 86 | 87 | try { 88 | seriesCache.put(symbol, data.fetchSeries(symbol) ); 89 | } catch (Exception e) { 90 | log.error(e.getMessage(), e); 91 | } 92 | 93 | } 94 | } 95 | 96 | public double run() { 97 | 98 | StopWatch watch = new StopWatch("-- DEBUGGING --"); 99 | watch.start("execute tradesystem"); 100 | 101 | Collections.shuffle(symbols); 102 | 103 | for (String symbol : symbols) { 104 | 105 | CandleSeries candleSeries; 106 | try { 107 | candleSeries = data.fetchSeries(symbol); 108 | 109 | test.reset(); 110 | test.setSeries(candleSeries); 111 | test.run(); 112 | 113 | positions.addAll(test.getOrderManager().getClosedPositions()); 114 | } catch (Exception e) { 115 | log.error(e.getMessage(), e); 116 | } 117 | 118 | } 119 | 120 | // order all of the positions by entry date 121 | Collections.sort(positions, new Comparator() { 122 | 123 | @Override 124 | public int compare(Position positionOne, Position positionTwo) { 125 | return positionOne.getEntryDate().compareTo( 126 | positionTwo.getEntryDate()); 127 | } 128 | 129 | }); 130 | 131 | int posSize = test.getOrderManager().getOpenPositions().size(); 132 | 133 | log.info("Open Position Size : " + posSize); 134 | 135 | // generate reports 136 | AbstractReport report = new JFreeChartReport(reportName + ".jpg", 137 | capital, commission, slippage, positions, test 138 | .getOrderManager().getOpenPositions()); 139 | //double endingCapital = report.getTotalCapitalAndEquity(); 140 | //log.debug(String.format("Ending capital is %12.2f", endingCapital)); 141 | report.render(); 142 | 143 | new CSVReport(capital, commission, slippage, positions, test 144 | .getOrderManager().getOpenPositions(), reportName + ".csv").render(); 145 | 146 | new TradesReport(capital, commission, slippage, positions, test 147 | .getOrderManager().getOpenPositions(), reportName + "-trades.txt").render(); 148 | 149 | 150 | watch.stop(); 151 | log.debug(String.format("Time : %s seconds", watch.getTotalTimeSeconds())); 152 | 153 | return report.getTotalCapitalAndEquity(); 154 | } 155 | 156 | 157 | } -------------------------------------------------------------------------------- /src/org/openquant/backtest/Position.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | /* 4 | Copyright (c) 2010, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.util.Date; 23 | 24 | public class Position { 25 | 26 | private String symbol; 27 | 28 | private Date entryDate; 29 | 30 | private Date exitDate; 31 | 32 | private double entryPrice; 33 | 34 | private double exitPrice; 35 | 36 | private int quantity; 37 | 38 | private String comments; 39 | 40 | private double score = 0.0; 41 | 42 | private boolean successfullyFilled = true; 43 | 44 | private QuantityCalculator quantityCalculator; 45 | 46 | private Order entry; 47 | 48 | private Order exit; 49 | 50 | private int id; 51 | 52 | private static int idCounter = 0; 53 | 54 | public Position() { 55 | this.id = idCounter++; 56 | } 57 | 58 | public Position(String symbol, Date entryDate, Date exitDate, 59 | double entryPrice, double exitPrice, int quantity) { 60 | super(); 61 | this.id = idCounter++; 62 | this.symbol = symbol; 63 | this.entryDate = entryDate; 64 | this.exitDate = exitDate; 65 | this.entryPrice = entryPrice; 66 | this.exitPrice = exitPrice; 67 | this.quantity = quantity; 68 | } 69 | 70 | public boolean isQuantityCalculated(){ 71 | return quantityCalculator != null; 72 | } 73 | 74 | public int getId() { 75 | return id; 76 | } 77 | 78 | public void setId(int id) { 79 | this.id = id; 80 | } 81 | 82 | public String getSymbol() { 83 | return symbol; 84 | } 85 | 86 | public void setSymbol(String symbol) { 87 | this.symbol = symbol; 88 | } 89 | 90 | public Date getEntryDate() { 91 | return entryDate; 92 | } 93 | 94 | public void setEntryDate(Date entryDate) { 95 | this.entryDate = entryDate; 96 | } 97 | 98 | public Date getExitDate() { 99 | return exitDate; 100 | } 101 | 102 | public void setExitDate(Date exitDate) { 103 | this.exitDate = exitDate; 104 | } 105 | 106 | public int getQuantity() { 107 | return quantity; 108 | } 109 | 110 | public void setQuantity(int quantity) { 111 | this.quantity = quantity; 112 | } 113 | 114 | public double getEntryPrice() { 115 | return entryPrice; 116 | } 117 | 118 | public void setEntryPrice(double entryPrice) { 119 | this.entryPrice = entryPrice; 120 | } 121 | 122 | public double getExitPrice() { 123 | return exitPrice; 124 | } 125 | 126 | public void setExitPrice(double exitPrice) { 127 | this.exitPrice = exitPrice; 128 | } 129 | 130 | public void appendComment(String comment){ 131 | this.comments += ", " + comment; 132 | } 133 | 134 | public void setComments(String comments) { 135 | this.comments = comments; 136 | } 137 | 138 | public String getComments() { 139 | return comments; 140 | } 141 | 142 | public void setScore(double score) { 143 | this.score = score; 144 | } 145 | 146 | public double getScore() { 147 | return score; 148 | } 149 | 150 | public boolean isSuccessfullyFilled() { 151 | return successfullyFilled; 152 | } 153 | 154 | public void setSuccessfullyFilled(boolean successfullyFilled) { 155 | this.successfullyFilled = successfullyFilled; 156 | } 157 | 158 | public QuantityCalculator getQuantityCalculator() { 159 | return quantityCalculator; 160 | } 161 | 162 | public void setQuantityCalculator(QuantityCalculator quantityCalculator) { 163 | this.quantityCalculator = quantityCalculator; 164 | } 165 | 166 | public Order getEntry() { 167 | return entry; 168 | } 169 | 170 | public void setEntry(Order entry) { 171 | this.entry = entry; 172 | } 173 | 174 | public Order getExit() { 175 | return exit; 176 | } 177 | 178 | public void setExit(Order exit) { 179 | this.exit = exit; 180 | } 181 | 182 | @Override 183 | public String toString() { 184 | 185 | return String.format( 186 | "{%1$s} entry[%2$td %2$tb %2$ty %2$tT] : %3$3.2f, exit[%4$td %4$tb %4$ty %4$tT]: %5$3.2f, quantity: %6$s comments: %7$s score: %8$s", symbol, 187 | entryDate, entryPrice, exitDate, exitPrice, quantity, comments, score); 188 | } 189 | 190 | 191 | } 192 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/PositionProcessor.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | public interface PositionProcessor { 23 | 24 | public void processClosePosition(final Position position); 25 | 26 | public void processOpenPosition(final Position position); 27 | 28 | public void finish(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/QuantityCalculator.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | public interface QuantityCalculator { 4 | 5 | public int execute(double totalEquity, double availableCash); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/Series.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | /* 4 | Copyright (c) 2010, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.util.Iterator; 23 | import java.util.List; 24 | 25 | import com.tictactec.ta.lib.Core; 26 | import com.tictactec.ta.lib.MInteger; 27 | import com.tictactec.ta.lib.RetCode; 28 | 29 | public class Series { 30 | 31 | private double values[]; 32 | 33 | private Core lib = new Core(); 34 | MInteger outBegIdx = new MInteger(); 35 | MInteger outNbElement = new MInteger(); 36 | 37 | public Series(){ 38 | } 39 | 40 | public Series(double[] vals) { 41 | values = vals; 42 | } 43 | 44 | public Series(List vals ){ 45 | values = convert(vals); 46 | } 47 | 48 | public void setValues(double[] values) { 49 | this.values = values; 50 | } 51 | 52 | public double[] getValues() { 53 | return values; 54 | } 55 | 56 | public double getAt(int i) { 57 | return values[i]; 58 | } 59 | 60 | public int size(){ 61 | return values.length; 62 | } 63 | 64 | public double lookBack(int i){ 65 | return values[values.length-i]; 66 | } 67 | 68 | public double getLast(){ 69 | return values[values.length-1]; 70 | } 71 | 72 | public static double[] convert(List doubles) 73 | { 74 | double[] ret = new double[doubles.size()]; 75 | Iterator iterator = doubles.iterator(); 76 | for (int i = 0; i < ret.length; i++) 77 | { 78 | ret[i] = iterator.next(); 79 | } 80 | return ret; 81 | } 82 | 83 | private double[] adjust(double[] source, int lookback) { 84 | 85 | double rArray[] = new double[source.length]; 86 | int i = 0, j = 0; 87 | for (double value : source) { 88 | if (i < lookback) { 89 | rArray[i] = 0.0; 90 | } else { 91 | rArray[i] = source[j++]; 92 | } 93 | i++; 94 | } 95 | 96 | return rArray; 97 | } 98 | 99 | public Series Min(int lookback){ 100 | 101 | double output[] = new double[values.length]; 102 | lookback = lib.minLookback(lookback); 103 | 104 | RetCode retCode = lib.min(0, values.length - 1, values, lookback, outBegIdx, outNbElement, output); 105 | 106 | return new Series(adjust(output, lookback)); 107 | } 108 | 109 | public Series EMA(int lookback) { 110 | 111 | double output[] = new double[values.length]; 112 | lookback = lib.emaLookback(lookback); 113 | RetCode retCode = lib.ema(0, values.length - 1, values, lookback, outBegIdx, outNbElement, output); 114 | 115 | return new Series(adjust(output, lookback)); 116 | } 117 | 118 | public Series SMA(int lookback){ 119 | 120 | double output[] = new double[values.length]; 121 | 122 | lookback = lib.smaLookback(lookback); 123 | RetCode retCode = lib.sma(0, values.length - 1, values, lookback, outBegIdx, outNbElement, output); 124 | 125 | return new Series(adjust(output, lookback)); 126 | } 127 | 128 | public Series WMA(int lookback){ 129 | 130 | double output[] = new double[values.length]; 131 | 132 | lookback = lib.wmaLookback(lookback); 133 | RetCode retCode = lib.wma(0, values.length - 1, values, lookback, outBegIdx, outNbElement, output); 134 | 135 | return new Series(adjust(output, lookback)); 136 | } 137 | 138 | public Series RSI(int lookback){ 139 | 140 | double output[] = new double[values.length]; 141 | 142 | lookback = lib.rsiLookback(lookback); 143 | RetCode retCode = lib.rsi(0, values.length - 1, values, lookback, outBegIdx, outNbElement, output); 144 | 145 | return new Series(adjust(output, lookback)); 146 | } 147 | 148 | public Series CMO(int lookback){ 149 | 150 | double output[] = new double[values.length]; 151 | 152 | lookback = lib.cmoLookback(lookback); 153 | RetCode retCode = lib.cmo(0, values.length - 1, values, lookback, outBegIdx, outNbElement, output); 154 | 155 | return new Series(adjust(output, lookback)); 156 | } 157 | 158 | public Series ROC(int lookback){ 159 | 160 | double output[] = new double[values.length]; 161 | 162 | lookback = lib.rocLookback(lookback); 163 | RetCode retCode = lib.roc(0, values.length - 1, values, lookback, outBegIdx, outNbElement, output); 164 | 165 | return new Series(adjust(output, lookback)); 166 | } 167 | 168 | @Override 169 | public String toString() { 170 | StringBuffer buffer = new StringBuffer(); 171 | 172 | for (double value : values) { 173 | buffer.append(value); 174 | buffer.append("\n"); 175 | } 176 | 177 | return buffer.toString(); 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/SimpleEquityReport.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest; 2 | 3 | /* 4 | Copyright (c) 2010, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.BufferedWriter; 23 | import java.io.File; 24 | import java.io.FileWriter; 25 | import java.io.IOException; 26 | import java.io.PrintWriter; 27 | import java.util.Collection; 28 | 29 | import org.apache.commons.logging.Log; 30 | import org.apache.commons.logging.LogFactory; 31 | 32 | public class SimpleEquityReport { 33 | 34 | private Log log = LogFactory.getLog(SimpleEquityReport.class); 35 | 36 | private String directoryname; 37 | 38 | private PrintWriter writer; 39 | 40 | public SimpleEquityReport(Collection positions) { 41 | initialize(); 42 | } 43 | 44 | private void initialize() { 45 | try { 46 | String filename = directoryname + "/trades.csv"; 47 | FileWriter fileWriter = new FileWriter(new File(filename)); 48 | BufferedWriter bufWriter = new BufferedWriter(fileWriter); 49 | writer = new PrintWriter(bufWriter, true); 50 | 51 | createCSVHeader(writer); 52 | 53 | } catch (IOException e) { 54 | log.error(e, e); 55 | } 56 | 57 | } 58 | 59 | private void createCSVHeader(PrintWriter writer) { 60 | writer.println(String.format("%s,%s,%s,%s,%s,%s,%s,%s,%s", "Date", "Order Side", "Symbol", "Shares", 61 | "Share Price", "Commission", "Slippage", "Balance", "Value")); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/command/OpenQuantCommand.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.command; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.util.ArrayList; 25 | import java.util.Arrays; 26 | import java.util.HashMap; 27 | import java.util.HashSet; 28 | import java.util.Map; 29 | 30 | import org.openquant.backtest.BackTestExecutor; 31 | import org.openquant.backtest.GroovyTradeSystem; 32 | import org.openquant.data.YahooSeriesDatasource; 33 | 34 | import asg.cliche.Command; 35 | import asg.cliche.Param; 36 | import asg.cliche.ShellFactory; 37 | 38 | public class OpenQuantCommand { 39 | 40 | private double capital = 100000; 41 | 42 | private double commission = 9.99; 43 | 44 | private double slippage = 0.0002; 45 | 46 | private static Map symbolMap = new HashMap(); 47 | 48 | static { 49 | // load up all persisted lists 50 | for (File pathname : new File("data").listFiles()) { 51 | if (pathname.isFile()) { 52 | StringBuffer output = new StringBuffer(pathname.getName()); 53 | // remove the extension 54 | output.replace(output.length() - 4, output.length(), ""); 55 | String name = output.toString(); 56 | symbolMap.put(name, 57 | new SymbolSetSeries(name, pathname.getName())); 58 | } 59 | } 60 | } 61 | 62 | @Command(abbrev = "cap", description = "Get starting capital") 63 | public double getCapital() { 64 | return capital; 65 | } 66 | 67 | @Command(abbrev = "scap", description = "Set starting capital") 68 | public void setCapital(double capital) { 69 | this.capital = capital; 70 | } 71 | 72 | @Command(abbrev = "slip", description = "Get percent of 1% slippage per trade") 73 | public double getSlippage() { 74 | return slippage; 75 | } 76 | 77 | @Command(abbrev = "sslip", description = "Set percent of 1% slippage per trade") 78 | public void setSlippage(double slippage) { 79 | this.slippage = slippage; 80 | } 81 | 82 | @Command(abbrev = "com", description = "Get commission per trade") 83 | public double getCommission() { 84 | return commission; 85 | } 86 | 87 | @Command(abbrev = "scom", description = "Set commission per trade") 88 | public void setCommission( 89 | @Param(name = "amount", description = "Amount of commission per trade") double amount) { 90 | this.commission = amount; 91 | } 92 | 93 | @Command(abbrev = "clist", description = "Creates a new stock list. ie. clist mylist 2005-01-01 YHOO GOOG MSFT") 94 | public void createStockList( 95 | @Param(name = "name", description = "The stock list's name") String name, 96 | @Param(name = "startDate [" + YahooSeriesDatasource.DATE_FORMAT 97 | + "]", description = "The lookback start date as " 98 | + YahooSeriesDatasource.DATE_FORMAT) String startDate, 99 | @Param(name = "stocks", description = "Spaced-separated list of stock symbols") String... stocks) { 100 | symbolMap.put(name, new SymbolSetSeries(name, new HashSet( 101 | Arrays.asList(stocks)), startDate)); 102 | } 103 | 104 | @Command(abbrev = "alist", description = "Add stocks to existing list") 105 | public void addToStockList( 106 | @Param(name = "name", description = "The stock list's name") String name, 107 | @Param(name = "stocks", description = "Spaced-separated list of stock symbols") String... stocks) { 108 | 109 | if (name == null) { 110 | System.out.println("Stocklist Name must be specified."); 111 | } 112 | symbolMap.get(name).getSymbols().addAll(Arrays.asList(stocks)); 113 | 114 | } 115 | 116 | @Command(abbrev = "dlist", description = "Delete a specified stock list") 117 | public void deleteStockList( 118 | @Param(name = "name", description = "The stock list's name") String name) { 119 | SymbolSetSeries symbolSet = symbolMap.remove(name); 120 | symbolSet.delete(); 121 | } 122 | 123 | @Command(abbrev = "ls", description = "List all stock lists") 124 | public void listAll() { 125 | 126 | for (String key : symbolMap.keySet()) { 127 | System.out.println(key + "\n"); 128 | } 129 | } 130 | 131 | @Command(abbrev = "glist", description = "Prints out a specified stock list") 132 | public String getStockList( 133 | @Param(name = "name", description = "The stock list's name") String name) { 134 | StringBuffer output = new StringBuffer(); 135 | 136 | if (name == null) { 137 | return output.append("Stocklist Name must be specified.") 138 | .toString(); 139 | } 140 | 141 | SymbolSetSeries symbolSet = symbolMap.get(name); 142 | if (symbolSet == null) { 143 | return output.append( 144 | String.format("Symbol list [%s] not found.", name)) 145 | .toString(); 146 | } 147 | 148 | output.append(symbolSet); 149 | 150 | return output.toString(); 151 | } 152 | 153 | @Command(abbrev = "test", description = "Backtest a strategy against a stock list") 154 | public void backTest( 155 | @Param(name = "stockList", description = "The stock list's name") String stockList, 156 | @Param(name = "tradeSystem", description = "The tradesystem script to test against") String tradeSystemScript) { 157 | 158 | SymbolSetSeries symbolSet = symbolMap.get(stockList); 159 | if (symbolSet == null) { 160 | System.out.println(String.format("Symbol list [%s] not found.", 161 | stockList)); 162 | return; 163 | } 164 | 165 | BackTestExecutor executor = new BackTestExecutor(symbolSet, 166 | new ArrayList(symbolSet.getSymbols()), 167 | new GroovyTradeSystem(tradeSystemScript), tradeSystemScript, 168 | capital, commission, slippage); 169 | 170 | double cap = executor.run(); 171 | System.out.println(String.format("Ending capital is $ %10.2f", cap)); 172 | } 173 | 174 | public static void main(String[] args) throws IOException { 175 | ShellFactory 176 | .createConsoleShell( 177 | "openquant", 178 | "Welcome to the OpenQuant 1.0 command console.\nType ?list for a list of commands.", 179 | new OpenQuantCommand()).commandLoop(); 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/command/SymbolSetSeries.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.command; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.File; 23 | import java.util.Set; 24 | 25 | import org.openquant.backtest.CandleSeries; 26 | import org.openquant.data.ObjectStreamSeriesDatasource; 27 | import org.openquant.data.SeriesDatasource; 28 | import org.openquant.data.YahooSeriesDatasource; 29 | 30 | class SymbolSetSeries implements SeriesDatasource{ 31 | 32 | private String name; 33 | 34 | private YahooSeriesDatasource datasource; 35 | 36 | private ObjectStreamSeriesDatasource localSeriesDatasource; 37 | 38 | private Set symbols; 39 | 40 | private String beginDate; 41 | 42 | private String endDate; 43 | 44 | public SymbolSetSeries(String name, Set symbols, String beginDate) { 45 | super(); 46 | this.name = name; 47 | this.symbols = symbols; 48 | this.beginDate = beginDate; 49 | this.setDatasource(new YahooSeriesDatasource(beginDate)); 50 | initialize(); 51 | } 52 | 53 | public SymbolSetSeries(String name, Set symbols, String beginDate, String endDate) { 54 | super(); 55 | this.name = name; 56 | this.symbols = symbols; 57 | this.beginDate = beginDate; 58 | this.setDatasource(new YahooSeriesDatasource(beginDate, endDate)); 59 | initialize(); 60 | } 61 | 62 | public SymbolSetSeries(String name, String file){ 63 | super(); 64 | this.name = name; 65 | this.localSeriesDatasource = new ObjectStreamSeriesDatasource( String.format("data/%s.dat", name)); 66 | this.symbols = localSeriesDatasource.getSymbols(); 67 | this.beginDate = localSeriesDatasource.getBeginDate().toString(); 68 | this.endDate = localSeriesDatasource.getEndDate().toString(); 69 | } 70 | 71 | public void delete(){ 72 | new File(String.format("data/%s.dat", name)).delete(); 73 | } 74 | 75 | private void initialize(){ 76 | 77 | this.localSeriesDatasource = new ObjectStreamSeriesDatasource( String.format("data/%s.dat", name)); 78 | 79 | if(!this.localSeriesDatasource.exists()){ 80 | try{ 81 | 82 | for (String symbol : symbols){ 83 | try { 84 | CandleSeries series = datasource.fetchSeries(symbol); 85 | localSeriesDatasource.insertCandleSeries(series); 86 | } catch (Exception e) { 87 | // TODO Auto-generated catch block 88 | e.printStackTrace(); 89 | } 90 | } 91 | 92 | }finally{ 93 | localSeriesDatasource.commit(); 94 | } 95 | } 96 | 97 | 98 | } 99 | 100 | public Set getSymbols() { 101 | return symbols; 102 | } 103 | 104 | public void setSymbols(Set symbols) { 105 | this.symbols = symbols; 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | StringBuffer output = new StringBuffer(); 111 | output.append(String.format("[%s, %s]",name, beginDate)); 112 | output.append("\n"); 113 | 114 | for (String symbol : symbols){ 115 | output.append(symbol + "\n"); 116 | } 117 | 118 | // remove the last newline 119 | output.replace(output.length()-1, output.length(), ""); 120 | 121 | return output.toString(); 122 | } 123 | 124 | public void setDatasource(YahooSeriesDatasource datasource) { 125 | this.datasource = datasource; 126 | } 127 | 128 | public YahooSeriesDatasource getDatasource() { 129 | return datasource; 130 | } 131 | 132 | @Override 133 | public CandleSeries fetchSeries(String symbol) throws Exception { 134 | 135 | return localSeriesDatasource.fetchSeries(symbol); 136 | 137 | } 138 | 139 | } -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/AbstractIntradayTest.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import org.openquant.backtest.Candle; 4 | import org.openquant.backtest.Position; 5 | import org.openquant.backtest.QuantityCalculator; 6 | import org.openquant.backtest.Series; 7 | 8 | 9 | public abstract class AbstractIntradayTest { 10 | 11 | private IntradayCandleSeries candleSeries; 12 | 13 | private IntradayOrderManager orderManager = new IntradayOrderManager(); 14 | 15 | public void reset() { 16 | 17 | orderManager.setSymbol(null); 18 | orderManager.reset(); 19 | } 20 | 21 | public void setSymbol(String symbol) { 22 | orderManager.setSymbol(symbol); 23 | } 24 | 25 | public void processTick(Candle tick) { 26 | orderManager.processTick(tick); 27 | } 28 | 29 | public void processClose(Candle tick) { 30 | orderManager.processClose(tick); 31 | } 32 | 33 | public void buyAtLimit(double limitPrice, int quantity, OrderCallback callback) { 34 | orderManager.buyAtLimit(limitPrice, quantity, callback); 35 | } 36 | 37 | public void buyAtLimit(double limitPrice, QuantityCalculator quantityCalc, OrderCallback callback) { 38 | orderManager.buyAtLimit(limitPrice, quantityCalc, callback); 39 | } 40 | 41 | public void sellAtLimit(double limitPrice, int quantity, Position position, OrderCallback callback) { 42 | orderManager.sellAtLimit(limitPrice, quantity, position, callback); 43 | } 44 | 45 | public void sellAtStop(double stopPrice, int quantity, Position position, OrderCallback callback) { 46 | orderManager.sellAtStop(stopPrice, quantity, position, callback); 47 | } 48 | 49 | public void timeBasedExitOnClose(int days, Position position, OrderCallback callback){ 50 | orderManager.timeBasedExitOnClose(days, position, callback); 51 | } 52 | 53 | public void setCandleSeries(IntradayCandleSeries candleSeries) { 54 | this.candleSeries = candleSeries; 55 | orderManager.setSymbol(candleSeries.getSymbol()); 56 | } 57 | 58 | public IntradayCandleSeries getCandleSeries() { 59 | return candleSeries; 60 | } 61 | 62 | public void setOrderManager(IntradayOrderManager orderManager) { 63 | this.orderManager = orderManager; 64 | } 65 | 66 | public IntradayOrderManager getOrderManager() { 67 | return orderManager; 68 | } 69 | 70 | public abstract void run(); 71 | 72 | public Series dayCloseSeries(){ 73 | return candleSeries.getDayCloseSeries(); 74 | } 75 | 76 | public Series dayOpenSeries(){ 77 | return candleSeries.getDayOpenSeries(); 78 | } 79 | 80 | public Series dayLowSeries(){ 81 | return candleSeries.getDayLowSeries(); 82 | } 83 | 84 | public Series dayHighSeries(){ 85 | return candleSeries.getDayHighSeries(); 86 | } 87 | 88 | public int barsCount() { 89 | return candleSeries.size(); 90 | } 91 | 92 | public double getClose(int index){ 93 | return candleSeries.getDayCloseSeries().getAt(index); 94 | } 95 | 96 | public boolean hasOpenPositions() { 97 | return orderManager.hasOpenPositions(); 98 | } 99 | 100 | public int getOpenPositionCount(){ 101 | return orderManager.getOpenPositionCount(); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/BuyIntent.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import org.openquant.backtest.QuantityCalculator; 4 | 5 | 6 | public class BuyIntent{ 7 | 8 | private double price; 9 | 10 | private int quantity; 11 | 12 | private OrderTimeFrame timeframe = OrderTimeFrame.END_OF_DAY; 13 | 14 | private OrderCallback callback; 15 | 16 | private QuantityCalculator quantityCalc; 17 | 18 | public BuyIntent(double price, int quantity, OrderCallback callback){ 19 | this.price = price; 20 | this.quantity = quantity; 21 | this.callback = callback; 22 | } 23 | 24 | public BuyIntent(double price, QuantityCalculator quantityCalc, OrderCallback callback){ 25 | this.price = price; 26 | this.quantityCalc = quantityCalc; 27 | this.callback = callback; 28 | } 29 | 30 | public BuyIntent(double price, int quantity, OrderTimeFrame timeframe, OrderCallback callback) { 31 | super(); 32 | this.price = price; 33 | this.quantity = quantity; 34 | this.timeframe = timeframe; 35 | this.callback = callback; 36 | } 37 | 38 | public boolean isQuantityCalculated(){ 39 | return quantityCalc != null; 40 | } 41 | public double getPrice() { 42 | return price; 43 | } 44 | 45 | public OrderCallback getCallback() { 46 | return callback; 47 | } 48 | 49 | public int getQuantity() { 50 | return quantity; 51 | } 52 | 53 | public OrderTimeFrame getTimeframe() { 54 | return timeframe; 55 | } 56 | 57 | public void setTimeframe(OrderTimeFrame timeframe) { 58 | this.timeframe = timeframe; 59 | } 60 | 61 | public QuantityCalculator getQuantityCalc() { 62 | return quantityCalc; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/CreateChart.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.Calendar; 7 | import java.util.Date; 8 | import java.util.List; 9 | 10 | import org.jfree.chart.ChartFactory; 11 | import org.jfree.chart.ChartUtilities; 12 | import org.jfree.chart.JFreeChart; 13 | import org.jfree.data.time.TimeSeriesCollection; 14 | import org.jfree.data.xy.DefaultHighLowDataset; 15 | import org.jfree.date.DayOfWeekInMonthRule; 16 | import org.openquant.backtest.Series; 17 | import org.openquant.data.SeriesDatasource; 18 | 19 | public class CreateChart extends AbstractIntradayTest { 20 | 21 | @Override 22 | public void run() { 23 | 24 | System.out.println("Beginning test..."); 25 | 26 | createChart(createDataset()); 27 | 28 | } 29 | 30 | private DefaultHighLowDataset createDataset() { 31 | 32 | int serice = getCandleSeries().getDayCount(); 33 | 34 | Date[] date = new Date[serice]; 35 | double[] high = new double[serice]; 36 | double[] low = new double[serice]; 37 | double[] open = new double[serice]; 38 | double[] close = new double[serice]; 39 | double[] volume = new double[serice]; 40 | 41 | Series highs = dayHighSeries(); 42 | Series lows = dayLowSeries(); 43 | Series closes = dayCloseSeries(); 44 | Series opens = dayOpenSeries(); 45 | 46 | IntradayCandleSeries daySeries = getCandleSeries(); 47 | 48 | for (int i = 0; i < getCandleSeries().getDayCount(); i++) { 49 | 50 | date[i] = daySeries.getDayCandles(i).get(0).getDate(); 51 | high[i] = highs.getAt(i); 52 | low[i] = lows.getAt(i); 53 | open[i] = opens.getAt(i); 54 | close[i] = closes.getAt(i); 55 | volume[i] = 0; 56 | 57 | } 58 | 59 | DefaultHighLowDataset data = new DefaultHighLowDataset("", date, high, low, open, close, volume); 60 | return data; 61 | } 62 | 63 | private void createChart(final DefaultHighLowDataset dataset) { 64 | final JFreeChart chart = ChartFactory.createCandlestickChart("Candlestick Demo", "Time", "Price", dataset, 65 | false); 66 | 67 | try { 68 | ChartUtilities.saveChartAsJPEG(new File("MSFT-Candle"+".jpg"), chart, 1000, 600); 69 | } catch (IOException e) { 70 | // TODO Auto-generated catch block 71 | e.printStackTrace(); 72 | } 73 | } 74 | 75 | void debugCandles(List cList) { 76 | 77 | for (IntradayCandle c : cList) { 78 | System.out.println(c); 79 | } 80 | 81 | } 82 | 83 | public static void main(String... args) { 84 | 85 | SeriesDatasource data = new IntradaySeriesDatasource("/home/jay/StockDatabase/Test"); 86 | List symbols = new ArrayList(); 87 | 88 | symbols.add("MSFT"); 89 | 90 | IntradayBackTestExecutor executor = new IntradayBackTestExecutor(data, symbols, new CreateChart(), "ATest"); 91 | 92 | executor.run(); 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/DipperIntradayTest.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.apache.commons.logging.Log; 7 | import org.apache.commons.logging.LogFactory; 8 | import org.openquant.backtest.Position; 9 | import org.openquant.backtest.QuantityCalculator; 10 | import org.openquant.backtest.Series; 11 | import org.openquant.backtest.intraday.IntradayCandle.CANDLETYPE; 12 | import org.openquant.data.SeriesDatasource; 13 | 14 | public class DipperIntradayTest extends AbstractIntradayTest { 15 | 16 | private static Log log = LogFactory.getLog(DipperIntradayTest.class); 17 | 18 | private static final int EXIT_TIME_DAYS = 1; 19 | 20 | private static final double MAX_POSITION_AMOUNT = 100000; 21 | 22 | @Override 23 | public void run() { 24 | 25 | Series Closes = dayCloseSeries(); 26 | 27 | IntradayCandleSeries daySeries = getCandleSeries(); 28 | 29 | for (int bar = 2; bar < daySeries.getDayCount(); bar++) { 30 | 31 | //System.out.println( String.format( "bar : %s, date : %s, low : %s, Filter.getAt(bar) : %s", dayIndex, daySeries.getDayCandles(dayIndex).get(0).getDate(), 32 | // Lows.getAt(dayIndex), Filter.getAt(dayIndex) )); 33 | 34 | final int b = bar; 35 | 36 | List dayCandles = daySeries.getDayCandles(bar); 37 | 38 | //debugCandles(dayCandles); 39 | 40 | double todaysOpen = 0; 41 | 42 | for (IntradayCandle candle : dayCandles) { 43 | 44 | if (candle.getType().equals(CANDLETYPE.OPEN)) { 45 | todaysOpen = candle.getOpenPrice(); 46 | } 47 | 48 | // tick processing for the order manager 49 | if (candle.getType().equals(CANDLETYPE.CLOSE)) { 50 | processClose(candle); 51 | } else { 52 | processTick(candle); 53 | } 54 | 55 | // do not open more than one position per stock at a time 56 | if (!hasOpenPositions()) { 57 | 58 | final double LIMITPRICE = Closes.getAt(bar-1) - ( todaysOpen * 0.08); 59 | 60 | buyAtLimit(LIMITPRICE, new QuantityCalculator() { 61 | 62 | @Override 63 | public int execute(double totalEquity, double availableCash) { 64 | int quantity = (int) (totalEquity/20 / LIMITPRICE); 65 | 66 | if (quantity * LIMITPRICE > MAX_POSITION_AMOUNT){ 67 | quantity = (int) (MAX_POSITION_AMOUNT / LIMITPRICE); 68 | } 69 | return quantity; 70 | } 71 | }, new OrderCallback() { 72 | 73 | @Override 74 | public void success(Position position) { 75 | 76 | String candleString = String.format("Candle[%s, %s, %s, %s]", dayOpenSeries().getAt(b), dayHighSeries().getAt(b), dayLowSeries().getAt(b), dayCloseSeries().getAt(b)); 77 | 78 | System.out.println("ENTER Position : " + candleString + " at limit Price: " + position.getEntryPrice()); 79 | 80 | timeBasedExitOnClose(EXIT_TIME_DAYS, position, new OrderCallback() { 81 | 82 | 83 | @Override 84 | public void success(Position position) { 85 | } 86 | 87 | @Override 88 | public void expired() { 89 | // TODO Auto-generated method stub 90 | 91 | } 92 | }); 93 | 94 | } 95 | 96 | @Override 97 | public void expired() { 98 | // TODO Auto-generated method stub 99 | 100 | } 101 | }); 102 | 103 | 104 | } 105 | } 106 | } 107 | 108 | } 109 | 110 | void debugCandles(List cList){ 111 | 112 | for(IntradayCandle c : cList){ 113 | System.out.println(c); 114 | } 115 | 116 | } 117 | 118 | public static void main(String ... args){ 119 | 120 | SeriesDatasource datasource = new IntradaySeriesDatasource("/home/jay/StockDatabase/Test"); 121 | //IntradayCandleSeries series = (IntradayCandleSeries) datasource.fetchSeries("MSFT"); 122 | 123 | List symbols = new ArrayList(); 124 | 125 | symbols.add("MSFT"); 126 | 127 | // nasdaq10 128 | /* 129 | symbols.add("ATVI"); 130 | symbols.add("ADBE"); 131 | symbols.add("AKAM"); 132 | symbols.add("ALTR"); 133 | symbols.add("AMZN"); 134 | symbols.add("AMGN"); 135 | symbols.add("APOL"); 136 | symbols.add("AAPL"); 137 | symbols.add("AMAT"); 138 | symbols.add("ADSK"); 139 | */ 140 | 141 | //IntradayBackTestExecutor executor = new IntradayBackTestExecutor("/home/jay/StockDatabase/Test", new DipperIntradayTest(), "IntradayDipper-100"); 142 | 143 | IntradayBackTestExecutor executor = new IntradayBackTestExecutor(datasource, symbols, new DipperIntradayTest(), "IntradayDipper-MSFT"); 144 | executor.run(); 145 | 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/IntradayBackTestExecutor.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.File; 23 | import java.util.ArrayList; 24 | import java.util.Collections; 25 | import java.util.Comparator; 26 | import java.util.List; 27 | 28 | import org.apache.commons.logging.Log; 29 | import org.apache.commons.logging.LogFactory; 30 | import org.openquant.backtest.Position; 31 | import org.openquant.backtest.report.AbstractReport; 32 | import org.openquant.backtest.report.CSVReport; 33 | import org.openquant.backtest.report.JFreeChartReport; 34 | import org.openquant.backtest.report.TradesReport; 35 | import org.openquant.data.SeriesDatasource; 36 | import org.springframework.util.StopWatch; 37 | 38 | public class IntradayBackTestExecutor { 39 | 40 | private Log log = LogFactory.getLog(IntradayBackTestExecutor.class); 41 | 42 | private SeriesDatasource data; 43 | 44 | private List symbols; 45 | 46 | private AbstractIntradayTest test; 47 | 48 | private double capital = 100000; 49 | 50 | private double commission = 9.99; 51 | 52 | private double slippage = 0.001; 53 | 54 | private List positions = new ArrayList(); 55 | 56 | private String reportName; 57 | 58 | public IntradayBackTestExecutor(SeriesDatasource data, List symbols, 59 | AbstractIntradayTest test, String reportName, double capital, 60 | double commission, double slippage) { 61 | super(); 62 | this.data = data; 63 | this.symbols = symbols; 64 | this.test = test; 65 | this.capital = capital; 66 | this.commission = commission; 67 | this.slippage = slippage; 68 | this.reportName = reportName; 69 | } 70 | 71 | public IntradayBackTestExecutor(SeriesDatasource data, List symbols, 72 | AbstractIntradayTest test, String reportName){ 73 | super(); 74 | this.data = data; 75 | this.symbols = symbols; 76 | this.test = test; 77 | this.reportName = reportName; 78 | } 79 | 80 | public IntradayBackTestExecutor(String symbolsDirectory, AbstractIntradayTest test, String reportName){ 81 | super(); 82 | data = new IntradaySeriesDatasource(symbolsDirectory); 83 | populateSymbolsFromDirectory(symbolsDirectory); 84 | this.test = test; 85 | this.reportName = reportName; 86 | } 87 | 88 | private void populateSymbolsFromDirectory(String directoryName){ 89 | 90 | symbols = new ArrayList(); 91 | 92 | File dir = new File(directoryName); 93 | 94 | String[] children = dir.list(); 95 | if (children == null) { 96 | // Either dir does not exist or is not a directory 97 | } else { 98 | for (int i=0; i() { 139 | 140 | @Override 141 | public int compare(Position positionOne, Position positionTwo) { 142 | return positionOne.getEntryDate().compareTo( 143 | positionTwo.getEntryDate()); 144 | } 145 | 146 | }); 147 | 148 | // generate reports 149 | 150 | AbstractReport report = new JFreeChartReport(reportName + ".jpg", 151 | capital, commission, slippage, positions, test 152 | .getOrderManager().getOpenPositions()); 153 | double endingCapital = report.getTotalCapitalAndEquity(); 154 | log.info(String.format("Ending capital is %12.2f", endingCapital)); 155 | report.render(); 156 | 157 | 158 | CSVReport report2 = new CSVReport(capital, commission, slippage, positions, test 159 | .getOrderManager().getOpenPositions(), reportName + ".csv"); 160 | report2.render(); 161 | 162 | new TradesReport(capital, commission, slippage, positions, test 163 | .getOrderManager().getOpenPositions(), reportName + "-trades.txt").render(); 164 | 165 | watch.stop(); 166 | log.debug(String.format("Time : %s seconds", watch.getTotalTimeSeconds())); 167 | 168 | return 0; 169 | } 170 | 171 | 172 | } -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/IntradayCandle.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import java.util.Date; 4 | 5 | import org.openquant.backtest.Candle; 6 | 7 | public class IntradayCandle extends Candle { 8 | 9 | private static final long serialVersionUID = 3611441350748138120L; 10 | 11 | public enum CANDLETYPE{ 12 | TICK, 13 | OPEN, 14 | HIGH, 15 | LOW, 16 | CLOSE 17 | } 18 | 19 | private CANDLETYPE type = CANDLETYPE.TICK; 20 | 21 | public IntradayCandle() { 22 | super(); 23 | } 24 | 25 | public IntradayCandle(String symbol, Date date, double open, double high, 26 | double low, double close, double volume) { 27 | super(symbol, date, open, high, low, close, volume); 28 | } 29 | 30 | public void setType(CANDLETYPE type) { 31 | this.type = type; 32 | } 33 | 34 | public CANDLETYPE getType() { 35 | return type; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/IntradayCandleSeries.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import org.openquant.backtest.Candle; 10 | import org.openquant.backtest.CandleSeries; 11 | import org.openquant.backtest.Series; 12 | import org.openquant.backtest.intraday.IntradayCandle.CANDLETYPE; 13 | import org.openquant.util.Day; 14 | 15 | public class IntradayCandleSeries extends CandleSeries { 16 | 17 | private static final long serialVersionUID = 1L; 18 | 19 | private Series dayCloseSeries; 20 | 21 | private Series dayHighSeries; 22 | 23 | private Series dayLowSeries; 24 | 25 | private Series dayOpenSeries; 26 | 27 | private List> dayCandles = new ArrayList>(); 28 | 29 | public IntradayCandleSeries(List candles) { 30 | super(candles); 31 | 32 | processCandles(); 33 | } 34 | 35 | public int getDayCount(){ 36 | return dayCandles.size(); 37 | } 38 | 39 | public List getDayCandles(int dayIndex){ 40 | return dayCandles.get(dayIndex); 41 | } 42 | 43 | private void processCandles() { 44 | 45 | List closes = new ArrayList(); 46 | List opens = new ArrayList(); 47 | 48 | List highs = new ArrayList(); 49 | List lows = new ArrayList(); 50 | 51 | IntradayCandle previous = null; 52 | 53 | List day = new ArrayList(); 54 | 55 | double dayLow = 0; 56 | double dayHigh = 0; 57 | 58 | for (Candle can : candles) { 59 | 60 | IntradayCandle candle = (IntradayCandle)can; 61 | 62 | if (previous == null) { 63 | 64 | dayLow = candle.getLowPrice(); 65 | dayHigh = candle.getHighPrice(); 66 | 67 | opens.add(candle.getOpenPrice()); 68 | previous = candle; 69 | continue; 70 | } 71 | 72 | if (Day.compare(candle.getDate(), previous.getDate()) != 0) { 73 | // another day 74 | // mark the candles as open and close candles 75 | 76 | previous.setType(CANDLETYPE.CLOSE); 77 | candle.setType(CANDLETYPE.OPEN); 78 | 79 | closes.add(previous.getClosePrice()); 80 | opens.add(candle.getOpenPrice()); 81 | highs.add(dayHigh); 82 | lows.add(dayLow); 83 | dayLow = candle.getLowPrice(); 84 | dayHigh = candle.getHighPrice(); 85 | 86 | 87 | dayCandles.add(day); 88 | day = new ArrayList(); 89 | 90 | } 91 | 92 | // check the highs/lows 93 | if (candle.getLowPrice() < dayLow){ 94 | dayLow = candle.getLowPrice(); 95 | } 96 | if (candle.getHighPrice() > dayHigh){ 97 | dayHigh = candle.getHighPrice(); 98 | } 99 | 100 | day.add(candle); 101 | 102 | previous = candle; 103 | 104 | } 105 | 106 | dayCloseSeries = new Series(closes); 107 | dayOpenSeries = new Series(opens); 108 | dayHighSeries = new Series(highs); 109 | dayLowSeries = new Series(lows); 110 | } 111 | 112 | public Series getDayHighSeries() { 113 | return dayHighSeries; 114 | } 115 | 116 | public Series getDayLowSeries() { 117 | return dayLowSeries; 118 | } 119 | 120 | public Series getDayOpenSeries() { 121 | return dayOpenSeries; 122 | } 123 | 124 | public Series getDayCloseSeries() { 125 | return dayCloseSeries; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/IntradayOrderManager.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.Iterator; 6 | import java.util.LinkedHashSet; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | import org.apache.commons.logging.Log; 11 | import org.apache.commons.logging.LogFactory; 12 | import org.openquant.backtest.Candle; 13 | import org.openquant.backtest.Position; 14 | import org.openquant.backtest.QuantityCalculator; 15 | 16 | public class IntradayOrderManager { 17 | 18 | private Log log = LogFactory.getLog(IntradayOrderManager.class); 19 | 20 | private String symbol; 21 | 22 | private Set openPositions = new LinkedHashSet(); 23 | 24 | private Set closedPositions = new LinkedHashSet(); 25 | 26 | private List buyLimitIntents = new ArrayList(); 27 | 28 | private List sellLimitIntents = new ArrayList(); 29 | 30 | private List sellStopIntents = new ArrayList(); 31 | 32 | private List timeBaseCloseIntents = new ArrayList(); 33 | 34 | public String getSymbol() { 35 | return symbol; 36 | } 37 | 38 | public void setSymbol(String symbol) { 39 | this.symbol = symbol; 40 | } 41 | 42 | public Set getOpenPositions() { 43 | return openPositions; 44 | } 45 | 46 | public Collection getClosedPositions() { 47 | return closedPositions; 48 | } 49 | 50 | public void reset(){ 51 | clearAllBuyIntents(); 52 | clearAllSellIntents(); 53 | closedPositions.clear(); 54 | openPositions.clear(); 55 | } 56 | 57 | private void clearAllBuyIntents(){ 58 | buyLimitIntents.clear(); 59 | } 60 | 61 | private void clearAllSellIntents(){ 62 | sellLimitIntents.clear(); 63 | sellStopIntents.clear(); 64 | timeBaseCloseIntents.clear(); 65 | } 66 | 67 | public void processTick(final Candle tick){ 68 | //log.info("Tick " + tick); 69 | 70 | // limit buy orders 71 | for (Iterator iter = buyLimitIntents.iterator(); iter.hasNext();) { 72 | BuyIntent intent = iter.next(); 73 | if (tick.getClosePrice() <= intent.getPrice()){ 74 | // create our order 75 | Position pos = new Position(); 76 | pos.setSymbol(getSymbol()); 77 | pos.setEntryPrice(tick.getClosePrice()); 78 | pos.setQuantity(intent.getQuantity()); 79 | pos.setEntryDate(tick.getDate()); 80 | if(intent.isQuantityCalculated()){ 81 | pos.setQuantityCalculator(intent.getQuantityCalc()); 82 | } 83 | 84 | openPosition(pos); 85 | 86 | intent.getCallback().success(pos); 87 | iter.remove(); 88 | 89 | // since this is intended to be used by a single dataset at a time, 90 | // assume that multiple intraday orders of the same stock is not allowed 91 | // and clear out all other buy intents 92 | clearAllBuyIntents(); 93 | 94 | break; 95 | } 96 | } 97 | 98 | // limit sell orders 99 | for (Iterator iter = sellLimitIntents.iterator(); iter.hasNext();) { 100 | SellIntent intent = iter.next(); 101 | 102 | if (tick.getClosePrice() >= intent.getPrice() ){ 103 | 104 | Position pos = intent.getPosition(); 105 | pos.setExitPrice(tick.getClosePrice()); 106 | pos.setQuantity(intent.getQuantity()); 107 | pos.setExitDate(tick.getDate()); 108 | 109 | pos.setComments("SELL LIMIT sell on " + tick.getDate()); 110 | 111 | closePosition(pos); 112 | intent.getCallback().success(pos); 113 | iter.remove(); 114 | 115 | clearAllSellIntents(); 116 | break; 117 | } 118 | 119 | } 120 | 121 | for (Iterator iter = sellStopIntents.iterator(); iter.hasNext();) { 122 | SellIntent intent = iter.next(); 123 | 124 | if (tick.getClosePrice() <= intent.getPrice() ){ 125 | 126 | Position pos = intent.getPosition(); 127 | pos.setExitPrice(tick.getClosePrice()); 128 | pos.setQuantity(intent.getQuantity()); 129 | pos.setExitDate(tick.getDate()); 130 | 131 | closePosition(pos); 132 | intent.getCallback().success(pos); 133 | iter.remove(); 134 | 135 | clearAllSellIntents(); 136 | break; 137 | } 138 | } 139 | 140 | } 141 | 142 | public void processClose(final Candle tick){ 143 | 144 | //log.info("CLOSE Tick " + tick); 145 | 146 | // limit buy orders 147 | for (Iterator iter = buyLimitIntents.iterator(); iter.hasNext();) { 148 | BuyIntent intent = iter.next(); 149 | 150 | if (intent.getTimeframe().equals(OrderTimeFrame.END_OF_DAY)){ 151 | intent.getCallback().expired(); 152 | iter.remove(); 153 | } 154 | } 155 | // limit sell orders 156 | for (Iterator iter = sellLimitIntents.iterator(); iter.hasNext();) { 157 | SellIntent intent = iter.next(); 158 | 159 | if (intent.getTimeframe().equals(OrderTimeFrame.END_OF_DAY)){ 160 | iter.remove(); 161 | } 162 | } 163 | 164 | // limit sell orders 165 | for (Iterator iter = sellStopIntents.iterator(); iter.hasNext();) { 166 | SellIntent intent = iter.next(); 167 | 168 | if (intent.getTimeframe().equals(OrderTimeFrame.END_OF_DAY)){ 169 | iter.remove(); 170 | } 171 | } 172 | 173 | for (Iterator iter = timeBaseCloseIntents.iterator(); iter.hasNext();) { 174 | TimeBasedSellIntent intent = iter.next(); 175 | 176 | if (intent.decrement() == 0){ 177 | Position pos = intent.getPosition(); 178 | pos.setExitPrice(tick.getClosePrice()); 179 | pos.setExitDate(tick.getDate()); 180 | 181 | pos.setComments("Time base sell on " + tick.getDate()); 182 | 183 | closePosition(pos); 184 | intent.getCallback().success(pos); 185 | iter.remove(); 186 | 187 | clearAllSellIntents(); 188 | } 189 | 190 | } 191 | 192 | } 193 | 194 | private void openPosition(Position pos) { 195 | 196 | //log.info("Opening position " + pos); 197 | 198 | pos.setSymbol(symbol); 199 | openPositions.add(pos); 200 | } 201 | 202 | private void closePosition(Position pos) { 203 | 204 | //log.info("Closing position " + pos); 205 | 206 | if (openPositions.remove(pos)) { 207 | closedPositions.add(pos); 208 | } else { 209 | log.error("Could not find open position" + pos); 210 | } 211 | } 212 | 213 | public boolean hasOpenPositions() { 214 | return !openPositions.isEmpty(); 215 | } 216 | 217 | public int getOpenPositionCount(){ 218 | return openPositions.size(); 219 | } 220 | 221 | /** 222 | * An order to a broker to buy a specified quantity of a security at or 223 | * below a specified price (called the limit price). 224 | * 225 | * Read more: 226 | * http://www.investorwords.com/648/buy_limit_order.html#ixzz17LLcHmCc 227 | * 228 | */ 229 | public void buyAtLimit(double limitPrice, int quantity, OrderCallback callback) { 230 | buyLimitIntents.add(new BuyIntent(limitPrice, quantity, callback)); 231 | } 232 | 233 | 234 | public void buyAtLimit(double limitPrice, QuantityCalculator calculator, OrderCallback callback) { 235 | buyLimitIntents.add(new BuyIntent(limitPrice, calculator, callback)); 236 | } 237 | 238 | /** 239 | * An order to a broker to sell a specified quantity of a security at or 240 | * above a specified price (called the limit price) 241 | * 242 | * Read more: 243 | * http://www.investorwords.com/4479/sell_limit_order.html#ixzz17LYIFrkY 244 | * 245 | */ 246 | public void sellAtLimit(double limitPrice, int quantity, Position position, OrderCallback callback){ 247 | sellLimitIntents.add(new SellIntent(limitPrice, quantity, position, callback)); 248 | } 249 | 250 | public void timeBasedSellAtClose(int days, Position position, OrderCallback callback){ 251 | //sellLimitIntents.add(new SellIntent(limitPrice, quantity, position, callback)); 252 | timeBaseCloseIntents.add(new TimeBasedSellIntent(days, position, callback)); 253 | } 254 | 255 | /** 256 | * A stop order for which the specified price is below the current market 257 | * price and the order is to sell. 258 | * 259 | * Read more: http://www.investorwords.com/4757/stop_loss.html#ixzz17LYl0YFK 260 | * 261 | */ 262 | public void sellAtStop(double stopPrice, int quantity, Position position, OrderCallback callback){ 263 | sellStopIntents.add(new SellIntent(stopPrice, quantity, position, callback)); 264 | } 265 | 266 | public void timeBasedExitOnClose(int days, Position position, OrderCallback callback){ 267 | timeBaseCloseIntents.add( new TimeBasedSellIntent(days, position, callback) ); 268 | } 269 | 270 | } 271 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/IntradayQuoteDatasource.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | import java.util.StringTokenizer; 12 | 13 | import org.apache.commons.logging.Log; 14 | import org.apache.commons.logging.LogFactory; 15 | import org.openquant.backtest.Candle; 16 | import org.openquant.quote.QuoteDataSource; 17 | import org.openquant.quote.YahooQuoteDataSource; 18 | 19 | public class IntradayQuoteDatasource extends QuoteDataSource { 20 | 21 | List orderedQuotes = new ArrayList(); 22 | 23 | public IntradayQuoteDatasource(String directory, Set symbols, 24 | GlobalQuoteListener globalListener) { 25 | super(symbols, globalListener); 26 | initializeSymbolIterators(directory, symbols); 27 | } 28 | 29 | private void initializeSymbolIterators(String directory, Set symbols) { 30 | 31 | for (String symbol : symbols) { 32 | 33 | try { 34 | 35 | // csv file containing data 36 | String strFile = directory + File.separator + symbol + ".txt"; 37 | 38 | // create BufferedReader to read csv file 39 | BufferedReader br = new BufferedReader(new FileReader(strFile)); 40 | String strLine = ""; 41 | StringTokenizer st = null; 42 | int lineNumber = 0, tokenNumber = 0; 43 | 44 | // read comma separated file line by line 45 | while ((strLine = br.readLine()) != null) { 46 | lineNumber++; 47 | 48 | // break comma separated line using "," 49 | st = new StringTokenizer(strLine, ","); 50 | 51 | while (st.hasMoreTokens()) { 52 | // display csv values 53 | tokenNumber++; 54 | System.out.println("Line # " + lineNumber 55 | + ", Token # " + tokenNumber + ", Token : " 56 | + st.nextToken()); 57 | } 58 | 59 | // reset token number 60 | tokenNumber = 0; 61 | 62 | } 63 | 64 | } catch (Exception e) { 65 | System.out.println("Exception while reading csv file: " + e); 66 | } 67 | 68 | } 69 | 70 | Collections.sort(orderedQuotes); 71 | 72 | } 73 | 74 | @Override 75 | public Candle retrieveQuoteCandle(String symbol) throws Exception { 76 | // TODO Auto-generated method stub 77 | return null; 78 | } 79 | 80 | public static void main(String... args) { 81 | 82 | final Log log = LogFactory.getLog(YahooQuoteDataSource.class); 83 | 84 | Set symbols = new HashSet(); 85 | symbols.add("MSFT"); 86 | symbols.add("ORCL"); 87 | 88 | QuoteDataSource quote = new IntradayQuoteDatasource( 89 | "/home/jay/StockDatabase/Test", symbols, 90 | new GlobalQuoteListener() { 91 | 92 | @Override 93 | public void onQuote(Candle quoteCandle) { 94 | log.info(String.format( 95 | "Got quote %s[%s, %s, %s, %s, %s, %s]", 96 | quoteCandle.getSymbol(), 97 | quoteCandle.getOpenPrice(), 98 | quoteCandle.getHighPrice(), 99 | quoteCandle.getLowPrice(), 100 | quoteCandle.getClosePrice(), 101 | quoteCandle.getVolume(), quoteCandle.getDate())); 102 | 103 | } 104 | 105 | @Override 106 | public void onOpenQuote(Candle quoteCandle) { 107 | log.info(String.format( 108 | "Got open quote %s[%s, %s, %s, %s, %s, %s]", 109 | quoteCandle.getSymbol(), 110 | quoteCandle.getOpenPrice(), 111 | quoteCandle.getHighPrice(), 112 | quoteCandle.getLowPrice(), 113 | quoteCandle.getClosePrice(), 114 | quoteCandle.getVolume(), quoteCandle.getDate())); 115 | 116 | } 117 | }); 118 | 119 | quote.setLoopForever(true); 120 | quote.setInterval(1); 121 | quote.initialize(); 122 | 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/IntradaySeriesDatasource.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.text.DateFormat; 7 | import java.text.SimpleDateFormat; 8 | import java.util.ArrayList; 9 | import java.util.Date; 10 | import java.util.List; 11 | import java.util.StringTokenizer; 12 | 13 | import org.openquant.backtest.Candle; 14 | import org.openquant.backtest.CandleSeries; 15 | import org.openquant.data.SeriesDatasource; 16 | 17 | public class IntradaySeriesDatasource implements SeriesDatasource { 18 | 19 | public static final String DATE_TIME_FORMAT = "MM/dd/yyyyHHmm"; 20 | 21 | private final static DateFormat sdf = new SimpleDateFormat(DATE_TIME_FORMAT); 22 | 23 | private String baseDirectory; 24 | 25 | public IntradaySeriesDatasource(String baseDirectory){ 26 | this.baseDirectory = baseDirectory; 27 | } 28 | 29 | @Override 30 | public CandleSeries fetchSeries(String symbol) throws Exception { 31 | 32 | System.out.println( "Fetching series " + symbol ); 33 | 34 | List candles = new ArrayList(); 35 | 36 | String strFile = baseDirectory + File.separator + symbol + ".txt"; 37 | 38 | // create BufferedReader to read csv file 39 | BufferedReader br = new BufferedReader(new FileReader(strFile)); 40 | String strLine = ""; 41 | StringTokenizer st = null; 42 | 43 | /* 44 | "Date","Time","Open","High","Low","Close","Volume" 45 | 12/30/2002,0931,23.85,23.89,23.81,23.88,511498 46 | */ 47 | 48 | br.readLine(); 49 | // read comma separated file line by line 50 | while ((strLine = br.readLine()) != null) { 51 | 52 | // break comma separated line using "," 53 | st = new StringTokenizer(strLine, ","); 54 | 55 | String dateString = st.nextToken(); 56 | String timeString = st.nextToken(); 57 | 58 | Date date = sdf.parse(dateString + timeString); 59 | 60 | double open = Double.parseDouble(st.nextToken()); 61 | double high = Double.parseDouble(st.nextToken()); 62 | double low = Double.parseDouble(st.nextToken()); 63 | double close = Double.parseDouble(st.nextToken()); 64 | double volume = Double.parseDouble(st.nextToken()); 65 | 66 | candles.add(new IntradayCandle(symbol, date, open, high, low, close, volume)); 67 | 68 | } 69 | 70 | //System.out.println( "finished." ); 71 | 72 | return new IntradayCandleSeries(candles); 73 | 74 | } 75 | 76 | public static void main(String ... args) throws Exception{ 77 | SeriesDatasource datasource = new IntradaySeriesDatasource("/home/jay/StockDatabase/Test"); 78 | IntradayCandleSeries series = (IntradayCandleSeries) datasource.fetchSeries("MSFT"); 79 | 80 | 81 | //System.out.print(series); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/LLHIntradayTest.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.openquant.backtest.Position; 7 | import org.openquant.backtest.QuantityCalculator; 8 | import org.openquant.backtest.Series; 9 | import org.openquant.backtest.intraday.IntradayCandle.CANDLETYPE; 10 | import org.openquant.data.SeriesDatasource; 11 | 12 | public class LLHIntradayTest extends AbstractIntradayTest { 13 | 14 | private static final int EXIT_TIME_DAYS = 12; 15 | 16 | private static final double MAX_POSITION_AMOUNT = 100000; 17 | 18 | @Override 19 | public void run() { 20 | 21 | 22 | System.out.println("Beginning test..."); 23 | 24 | int Len = 21; 25 | Series Lows = dayLowSeries(); 26 | 27 | Series Filter = Lows.Min( Len ); 28 | 29 | IntradayCandleSeries daySeries = getCandleSeries(); 30 | 31 | for (int dayIndex = Len+2; dayIndex < daySeries.getDayCount(); dayIndex++) { 32 | 33 | //System.out.println( String.format( "bar : %s, date : %s, low : %s, Filter.getAt(bar) : %s", dayIndex, daySeries.getDayCandles(dayIndex).get(0).getDate(), 34 | // Lows.getAt(dayIndex), Filter.getAt(dayIndex) )); 35 | 36 | 37 | List dayCandles = daySeries.getDayCandles(dayIndex); 38 | 39 | //debugCandles(dayCandles); 40 | 41 | for (IntradayCandle candle : dayCandles) { 42 | 43 | // tick processing for the order manager 44 | if (candle.getType().equals(CANDLETYPE.CLOSE)) { 45 | processClose(candle); 46 | } else { 47 | processTick(candle); 48 | } 49 | 50 | // do not open more than one position per stock at a time 51 | if (!hasOpenPositions()) { 52 | 53 | final double LIMITPRICE = Filter.getAt(dayIndex); 54 | 55 | buyAtLimit(LIMITPRICE, new QuantityCalculator() { 56 | 57 | @Override 58 | public int execute(double totalEquity, double availableCash) { 59 | int quantity = (int) (totalEquity/20 / LIMITPRICE); 60 | 61 | if (quantity * LIMITPRICE > MAX_POSITION_AMOUNT){ 62 | quantity = (int) (MAX_POSITION_AMOUNT / LIMITPRICE); 63 | } 64 | return quantity; 65 | } 66 | }, new OrderCallback() { 67 | 68 | @Override 69 | public void success(Position position) { 70 | 71 | sellAtLimit(position.getEntryPrice() * 1.025, position.getQuantity(), position, new OrderCallback() { 72 | 73 | @Override 74 | public void success(Position position) { 75 | //transactedToday = true; 76 | 77 | } 78 | 79 | @Override 80 | public void expired() { 81 | // TODO Auto-generated method stub 82 | 83 | } 84 | }); 85 | 86 | timeBasedExitOnClose(EXIT_TIME_DAYS, position, new OrderCallback() { 87 | 88 | @Override 89 | public void success(Position position) { 90 | } 91 | 92 | @Override 93 | public void expired() { 94 | // TODO Auto-generated method stub 95 | 96 | } 97 | }); 98 | 99 | } 100 | 101 | @Override 102 | public void expired() { 103 | // TODO Auto-generated method stub 104 | 105 | } 106 | }); 107 | 108 | 109 | } 110 | } 111 | } 112 | 113 | } 114 | 115 | void debugCandles(List cList){ 116 | 117 | for(IntradayCandle c : cList){ 118 | System.out.println(c); 119 | } 120 | 121 | } 122 | 123 | public static void main(String ... args){ 124 | 125 | SeriesDatasource datasource = new IntradaySeriesDatasource("/home/jay/StockDatabase/Test"); 126 | //IntradayCandleSeries series = (IntradayCandleSeries) datasource.fetchSeries("MSFT"); 127 | 128 | List symbols = new ArrayList(); 129 | 130 | symbols.add("MSFT"); 131 | 132 | // nasdaq10 133 | /* 134 | symbols.add("ATVI"); 135 | symbols.add("ADBE"); 136 | symbols.add("AKAM"); 137 | symbols.add("ALTR"); 138 | symbols.add("AMZN"); 139 | symbols.add("AMGN"); 140 | symbols.add("APOL"); 141 | symbols.add("AAPL"); 142 | symbols.add("AMAT"); 143 | symbols.add("ADSK"); 144 | */ 145 | 146 | //IntradayBackTestExecutor executor = new IntradayBackTestExecutor("/home/jay/StockDatabase/Test", new LLHIntradayTest(), "Intraday-100"); 147 | 148 | IntradayBackTestExecutor executor = new IntradayBackTestExecutor(datasource, symbols, new LLHIntradayTest(), "Intraday-MSFT"); 149 | executor.run(); 150 | 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/LowestLowIntra.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.apache.commons.logging.Log; 7 | import org.apache.commons.logging.LogFactory; 8 | import org.openquant.backtest.Position; 9 | import org.openquant.backtest.QuantityCalculator; 10 | import org.openquant.backtest.Series; 11 | import org.openquant.backtest.intraday.IntradayCandle.CANDLETYPE; 12 | import org.openquant.data.SeriesDatasource; 13 | 14 | public class LowestLowIntra extends AbstractIntradayTest { 15 | 16 | private Log log = LogFactory.getLog(LowestLowIntra.class); 17 | 18 | private static boolean transactedToday = false; 19 | 20 | private static final double MAX_POSITION_AMOUNT = 100000; 21 | 22 | @Override 23 | public void run() { 24 | 25 | //System.out.println("Beginning test..."); 26 | 27 | int Len = 9; 28 | Series Filter = dayLowSeries().Min(Len); 29 | //Series Highs = dayHighSeries(); 30 | //Series Closes = dayCloseSeries(); 31 | 32 | IntradayCandleSeries daySeries = getCandleSeries(); 33 | 34 | for (int dayIndex = Len; dayIndex < daySeries.getDayCount(); dayIndex++) { 35 | 36 | //System.out.println( String.format("%s Close : %.2f Filter[bar-1] : %.2f", daySeries.getDayCandles(dayIndex).get(0).getDate(), Closes.getAt(dayIndex), Filter.getAt(dayIndex - 1) * 1.21) ); 37 | 38 | List dayCandles = daySeries.getDayCandles(dayIndex); 39 | 40 | // debugCandles(dayCandles); 41 | 42 | for (IntradayCandle candle : dayCandles) { 43 | 44 | // tick processing for the order manager 45 | if (candle.getType().equals(CANDLETYPE.CLOSE)) { 46 | processClose(candle); 47 | } else { 48 | processTick(candle); 49 | } 50 | 51 | // do not open more than one position per stock at a time 52 | //if ( getOpenPositionCount() < 20 ){ 53 | if (!hasOpenPositions()) { 54 | 55 | //if (Filter.getAt(dayIndex - 1) > Filter.getAt(dayIndex - 2)) { 56 | //if (Highs.getAt(dayIndex - 1) > Filter.getAt(dayIndex - 2)) { 57 | //if (candle.getHighPrice() > Filter.getAt(dayIndex - 1)) { 58 | 59 | final double LIMITPRICE = Filter.getAt(dayIndex - 1) * 1.21; 60 | 61 | //final double LIMITPRICE = Filter.getAt(dayIndex - 1); 62 | 63 | buyAtLimit(LIMITPRICE, new QuantityCalculator() { 64 | 65 | @Override 66 | public int execute(double totalEquity, double availableCash) { 67 | int quantity = (int) (totalEquity/10 / LIMITPRICE); 68 | 69 | if (quantity * LIMITPRICE > MAX_POSITION_AMOUNT){ 70 | quantity = (int) (MAX_POSITION_AMOUNT / LIMITPRICE); 71 | } 72 | 73 | return quantity; 74 | } 75 | }, 76 | new OrderCallback() { 77 | 78 | @Override 79 | public void success(Position position) { 80 | 81 | //log.info("Entered Position : " + position); 82 | 83 | /* 84 | timeBasedSellAtStop(1, position.getEntryPrice() * 0.80, position.getQuantity(), position, 85 | new OrderCallback() { 86 | 87 | @Override 88 | public void success(Position position) { 89 | // transactedToday = true; 90 | 91 | } 92 | 93 | @Override 94 | public void expired() { 95 | // TODO Auto-generated method 96 | // stub 97 | 98 | } 99 | }); 100 | 101 | */ 102 | 103 | timeBasedExitOnClose(12, position, new OrderCallback() { 104 | 105 | @Override 106 | public void success(Position position) { 107 | //log.info("Exited Position[ time based ] : " + position); 108 | 109 | } 110 | 111 | @Override 112 | public void expired() { 113 | // TODO Auto-generated method stub 114 | 115 | } 116 | }); 117 | 118 | 119 | } 120 | 121 | @Override 122 | public void expired() { 123 | // TODO Auto-generated method stub 124 | 125 | } 126 | }); 127 | 128 | //} 129 | 130 | } 131 | } 132 | } 133 | 134 | } 135 | 136 | void debugCandles(List cList) { 137 | 138 | for (IntradayCandle c : cList) { 139 | System.out.println(c); 140 | } 141 | 142 | } 143 | 144 | public static void main(String... args) { 145 | 146 | SeriesDatasource data = new IntradaySeriesDatasource("/home/jay/StockDatabase/Test"); 147 | // IntradayCandleSeries series = (IntradayCandleSeries) 148 | // datasource.fetchSeries("MSFT"); 149 | 150 | List symbols = new ArrayList(); 151 | 152 | //symbols.add("MSFT"); 153 | 154 | // nasdaq10 155 | 156 | symbols.add("ATVI"); symbols.add("ADBE"); symbols.add("AKAM"); 157 | symbols.add("ALTR"); symbols.add("AMZN"); symbols.add("AMGN"); 158 | symbols.add("APOL"); symbols.add("AAPL"); symbols.add("AMAT"); 159 | symbols.add("ADSK"); 160 | 161 | 162 | 163 | //IntradayBackTestExecutor executor = new IntradayBackTestExecutor("/home/jay/StockDatabase/Test", new LowestLowIntra(), 164 | // "LL4-NAS-100-PosSize"); 165 | 166 | IntradayBackTestExecutor executor = new IntradayBackTestExecutor(data, symbols, new LowestLowIntra(), "LL4-Nasdaq10-PosSize"); 167 | 168 | executor.run(); 169 | 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/MyTest.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.openquant.backtest.Position; 7 | import org.openquant.backtest.Series; 8 | import org.openquant.backtest.intraday.IntradayCandle.CANDLETYPE; 9 | import org.openquant.data.SeriesDatasource; 10 | 11 | public class MyTest extends AbstractIntradayTest { 12 | 13 | static boolean transactedToday = false; 14 | 15 | @Override 16 | public void run() { 17 | 18 | System.out.println("Beginning test..."); 19 | 20 | int Len = 18; 21 | Series Close = dayCloseSeries(); 22 | Series EMAClose = Close.EMA(Len); 23 | Series DayHighs = dayHighSeries(); 24 | 25 | IntradayCandleSeries daySeries = getCandleSeries(); 26 | 27 | for (int dayIndex = Len+2; dayIndex < daySeries.getDayCount(); dayIndex++) { 28 | 29 | List dayCandles = daySeries.getDayCandles(dayIndex); 30 | 31 | //debugCandles(dayCandles); 32 | 33 | transactedToday = false; 34 | for (IntradayCandle candle : dayCandles) { 35 | 36 | // if we buy a stock, hold overnight 37 | if (transactedToday){ 38 | break; 39 | } 40 | 41 | // tick processing for the order manager 42 | if (candle.getType().equals(CANDLETYPE.CLOSE)) { 43 | processClose(candle); 44 | } else { 45 | processTick(candle); 46 | } 47 | 48 | // do not open more than one position per stock at a time 49 | if (!hasOpenPositions()) { 50 | 51 | //if (EMAClose.getAt(dayIndex - 1) > EMAClose.getAt(dayIndex - 2)) { 52 | 53 | //if(candle.getHighPrice() > EMAClose.getAt(dayIndex - 1)){ 54 | if( DayHighs.getAt(dayIndex - 1) > EMAClose.getAt(dayIndex - 2)){ 55 | 56 | double LIMITPRICE = getClose(dayIndex - 1) 57 | * (1 - 0.03 * EMAClose.getAt(dayIndex - 1) / 50); 58 | 59 | buyAtLimit(LIMITPRICE, 1000, new OrderCallback() { 60 | 61 | @Override 62 | public void success(Position position) { 63 | 64 | sellAtStop(position.getEntryPrice() * 0.99, position.getQuantity(), position, new OrderCallback() { 65 | 66 | @Override 67 | public void success(Position position) { 68 | //transactedToday = true; 69 | 70 | } 71 | 72 | @Override 73 | public void expired() { 74 | // TODO Auto-generated method stub 75 | 76 | } 77 | }); 78 | 79 | sellAtLimit(position.getEntryPrice() * 1.025, position.getQuantity(), position, new OrderCallback() { 80 | 81 | @Override 82 | public void success(Position position) { 83 | //transactedToday = true; 84 | 85 | } 86 | 87 | @Override 88 | public void expired() { 89 | // TODO Auto-generated method stub 90 | 91 | } 92 | }); 93 | 94 | transactedToday = true; 95 | 96 | } 97 | 98 | @Override 99 | public void expired() { 100 | // TODO Auto-generated method stub 101 | 102 | } 103 | }); 104 | 105 | } 106 | } 107 | } 108 | } 109 | 110 | } 111 | 112 | void debugCandles(List cList){ 113 | 114 | for(IntradayCandle c : cList){ 115 | System.out.println(c); 116 | } 117 | 118 | } 119 | 120 | public static void main(String ... args){ 121 | 122 | SeriesDatasource datasource = new IntradaySeriesDatasource("/home/jay/StockDatabase/Test"); 123 | //IntradayCandleSeries series = (IntradayCandleSeries) datasource.fetchSeries("MSFT"); 124 | 125 | List symbols = new ArrayList(); 126 | 127 | //symbols.add("AAPL"); 128 | 129 | // nasdaq10 130 | 131 | symbols.add("ATVI"); 132 | symbols.add("ADBE"); 133 | symbols.add("AKAM"); 134 | symbols.add("ALTR"); 135 | symbols.add("AMZN"); 136 | symbols.add("AMGN"); 137 | symbols.add("APOL"); 138 | symbols.add("AAPL"); 139 | symbols.add("AMAT"); 140 | symbols.add("ADSK"); 141 | 142 | 143 | /* 144 | symbols.add("AAPL"); 145 | symbols.add("APOL"); 146 | symbols.add("CERN"); 147 | symbols.add("DLTR"); 148 | symbols.add("FLEX"); 149 | symbols.add("INTU"); 150 | symbols.add("MICC"); 151 | symbols.add("NWSA"); 152 | symbols.add("SBUX"); 153 | symbols.add("VMED"); 154 | symbols.add("ADBE"); 155 | symbols.add("ATVI"); 156 | symbols.add("CHKP"); 157 | symbols.add("DTV"); 158 | symbols.add("FLIR"); 159 | symbols.add("ISRG"); 160 | symbols.add("MRVL"); 161 | symbols.add("ORCL"); 162 | symbols.add("SHLD"); 163 | symbols.add("VOD"); 164 | symbols.add("ADP"); 165 | symbols.add("BBBY"); 166 | symbols.add("CHRW"); 167 | symbols.add("EBAY"); 168 | symbols.add("FSLR"); 169 | symbols.add("JOYG"); 170 | symbols.add("MSFT"); 171 | symbols.add("ORLY"); 172 | symbols.add("SIAL"); 173 | symbols.add("VRSN"); 174 | symbols.add("ADSK"); 175 | symbols.add("BIDU"); 176 | symbols.add("CMCSA"); 177 | symbols.add("ERTS"); 178 | symbols.add("GILD"); 179 | symbols.add("KLAC"); 180 | symbols.add("MU"); 181 | symbols.add("PAYX"); 182 | symbols.add("SNDK"); 183 | symbols.add("VRTX"); 184 | symbols.add("AKAM"); 185 | symbols.add("BIIB"); 186 | symbols.add("COST"); 187 | symbols.add("ESRX"); 188 | symbols.add("GOOG"); 189 | symbols.add("LIFE"); 190 | symbols.add("MXIM"); 191 | symbols.add("PCAR"); 192 | symbols.add("SPLS"); 193 | symbols.add("WCRX"); 194 | symbols.add("ALTR"); 195 | symbols.add("BMC"); 196 | symbols.add("CSCO"); 197 | symbols.add("EXPD"); 198 | symbols.add("GRMN"); 199 | symbols.add("LINTA"); 200 | symbols.add("MYL"); 201 | symbols.add("PCLN"); 202 | symbols.add("SRCL"); 203 | symbols.add("WFMI"); 204 | symbols.add("ALXN"); 205 | symbols.add("BRCM"); 206 | symbols.add("CTRP"); 207 | symbols.add("EXPE"); 208 | symbols.add("HSIC"); 209 | symbols.add("LLTC"); 210 | symbols.add("NFLX"); 211 | symbols.add("QCOM"); 212 | symbols.add("STX"); 213 | symbols.add("WYNN"); 214 | symbols.add("AMAT"); 215 | symbols.add("CA"); 216 | symbols.add("CTSH"); 217 | symbols.add("FAST"); 218 | symbols.add("ILMN"); 219 | symbols.add("LRCX"); 220 | symbols.add("NIHD"); 221 | symbols.add("QGEN"); 222 | symbols.add("SYMC"); 223 | symbols.add("XLNX"); 224 | symbols.add("AMGN"); 225 | symbols.add("CELG"); 226 | symbols.add("CTXS"); 227 | symbols.add("FFIV"); 228 | symbols.add("INFY"); 229 | symbols.add("MAT"); 230 | symbols.add("NTAP"); 231 | symbols.add("RIMM"); 232 | symbols.add("TEVA"); 233 | symbols.add("XRAY"); 234 | symbols.add("AMZN"); 235 | symbols.add("CEPH"); 236 | symbols.add("DELL"); 237 | symbols.add("FISV"); 238 | symbols.add("INTC"); 239 | symbols.add("MCHP"); 240 | symbols.add("NVDA"); 241 | symbols.add("ROST"); 242 | symbols.add("URBN"); 243 | symbols.add("YHOO"); 244 | */ 245 | 246 | IntradayBackTestExecutor executor = new IntradayBackTestExecutor(datasource, symbols, new MyTest(), "Intraday"); 247 | executor.run(); 248 | 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/OrderCallback.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import org.openquant.backtest.Position; 4 | 5 | public interface OrderCallback{ 6 | 7 | public void success(Position position); 8 | 9 | public void expired(); 10 | 11 | } -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/OrderTimeFrame.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | public enum OrderTimeFrame { 4 | 5 | GOOD_TILL_CANCELLED, 6 | END_OF_DAY 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/SellIntent.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import org.openquant.backtest.Position; 4 | 5 | 6 | public class SellIntent { 7 | 8 | private double price; 9 | 10 | private int quantity; 11 | 12 | private Position position; 13 | 14 | private OrderTimeFrame timeframe = OrderTimeFrame.GOOD_TILL_CANCELLED; 15 | 16 | private OrderCallback callback; 17 | 18 | public SellIntent(double price, int quantity, Position position, OrderTimeFrame timeframe, OrderCallback callback) { 19 | super(); 20 | this.price = price; 21 | this.quantity = quantity; 22 | this.position = position; 23 | this.timeframe = timeframe; 24 | this.callback = callback; 25 | } 26 | 27 | public SellIntent(double price, int quantity, Position position, OrderCallback callback) { 28 | super(); 29 | this.price = price; 30 | this.quantity = quantity; 31 | this.position = position; 32 | this.callback = callback; 33 | } 34 | 35 | public double getPrice() { 36 | return price; 37 | } 38 | 39 | public void setPrice(double price) { 40 | this.price = price; 41 | } 42 | 43 | public int getQuantity() { 44 | return quantity; 45 | } 46 | 47 | public void setQuantity(int quantity) { 48 | this.quantity = quantity; 49 | } 50 | 51 | public OrderTimeFrame getTimeframe() { 52 | return timeframe; 53 | } 54 | 55 | public void setTimeframe(OrderTimeFrame timeframe) { 56 | this.timeframe = timeframe; 57 | } 58 | 59 | public Position getPosition() { 60 | return position; 61 | } 62 | 63 | public OrderCallback getCallback() { 64 | return callback; 65 | } 66 | } -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/TimeBasedLimitSellIntent.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import org.openquant.backtest.Position; 4 | 5 | public class TimeBasedLimitSellIntent extends SellIntent { 6 | 7 | private int decrementor; 8 | 9 | private int dayBarCount; 10 | 11 | public TimeBasedLimitSellIntent(int dayBarCount, double price, int quantity, Position position, OrderTimeFrame timeframe, 12 | OrderCallback callback) { 13 | super(price, quantity, position, timeframe, callback); 14 | this.dayBarCount = dayBarCount; 15 | this.decrementor = dayBarCount; 16 | } 17 | 18 | public TimeBasedLimitSellIntent(int dayBarCount, double price, int quantity, Position position, OrderCallback callback) { 19 | super(price, quantity, position, callback); 20 | this.dayBarCount = dayBarCount; 21 | this.decrementor = dayBarCount; 22 | } 23 | 24 | public boolean isActive(){ 25 | return decrementor == 0; 26 | } 27 | 28 | public int getDayBarCount() { 29 | return dayBarCount; 30 | } 31 | 32 | public int decrement(){ 33 | return decrementor--; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/TimeBasedSellIntent.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import org.openquant.backtest.Position; 4 | 5 | 6 | public class TimeBasedSellIntent { 7 | 8 | private int decrementor; 9 | 10 | private int dayBarCount; 11 | 12 | private Position position; 13 | 14 | private OrderCallback callback; 15 | 16 | public TimeBasedSellIntent(int dayBarCount, Position position, OrderCallback callback) { 17 | super(); 18 | this.dayBarCount = dayBarCount; 19 | this.decrementor = dayBarCount; 20 | this.position = position; 21 | this.callback = callback; 22 | } 23 | 24 | public int getDayBarCount() { 25 | return dayBarCount; 26 | } 27 | 28 | public Position getPosition() { 29 | return position; 30 | } 31 | 32 | public OrderCallback getCallback() { 33 | return callback; 34 | } 35 | 36 | public int decrement(){ 37 | return decrementor--; 38 | } 39 | 40 | 41 | } -------------------------------------------------------------------------------- /src/org/openquant/backtest/intraday/TimeBasedStopSellIntent.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.intraday; 2 | 3 | import org.openquant.backtest.Position; 4 | 5 | public class TimeBasedStopSellIntent extends SellIntent { 6 | 7 | private int decrementor; 8 | 9 | private int dayBarCount; 10 | 11 | public TimeBasedStopSellIntent(int dayBarCount, double price, int quantity, Position position, OrderTimeFrame timeframe, 12 | OrderCallback callback) { 13 | super(price, quantity, position, timeframe, callback); 14 | this.dayBarCount = dayBarCount; 15 | this.decrementor = dayBarCount; 16 | } 17 | 18 | public TimeBasedStopSellIntent(int dayBarCount, double price, int quantity, Position position, OrderCallback callback) { 19 | super(price, quantity, position, callback); 20 | this.dayBarCount = dayBarCount; 21 | this.decrementor = dayBarCount; 22 | } 23 | 24 | public boolean isActive(){ 25 | return decrementor == 0; 26 | } 27 | 28 | public int getDayBarCount() { 29 | return dayBarCount; 30 | } 31 | 32 | public int decrement(){ 33 | return decrementor--; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/report/AbstractReport.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.report; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.util.ArrayList; 23 | import java.util.Collection; 24 | import java.util.Collections; 25 | import java.util.Comparator; 26 | import java.util.List; 27 | 28 | import org.apache.commons.logging.Log; 29 | import org.apache.commons.logging.LogFactory; 30 | import org.openquant.backtest.Order; 31 | import org.openquant.backtest.Position; 32 | 33 | public abstract class AbstractReport { 34 | 35 | private Log log = LogFactory.getLog(AbstractReport.class); 36 | 37 | private double totalCapitalAndEquity; 38 | 39 | private double slippage; 40 | 41 | private double commission; 42 | 43 | private double availableCash; 44 | 45 | private Collection closedPositions; 46 | 47 | private Collection openPositions; 48 | 49 | private Order extractEntryOrder(Position position) { 50 | 51 | return new Order(true, position.getEntryDate(), 52 | position.getEntryPrice() * position.getQuantity(), position); 53 | } 54 | 55 | private Order extractExitOrder(Position position) { 56 | 57 | return new Order(false, position.getExitDate(), position.getExitPrice() 58 | * position.getQuantity(), position); 59 | } 60 | 61 | public double getTotalCapitalAndEquity() { 62 | return totalCapitalAndEquity; 63 | } 64 | 65 | private int compareEntries(Order one, Order two){ 66 | 67 | if ((one.isEntry() && two.isEntry()) || (!one.isEntry() && !two.isEntry()) ){ 68 | return 0; 69 | } 70 | if (one.isEntry() && !two.isEntry()){ 71 | return -1; 72 | } 73 | if ( !one.isEntry() && two.isEntry()){ 74 | return 1; 75 | } 76 | return 0; 77 | } 78 | 79 | public void process() { 80 | List allOrders = new ArrayList(); 81 | 82 | for (Position position : closedPositions) { 83 | 84 | Order exit = extractExitOrder(position); 85 | allOrders.add(exit); 86 | position.setExit(exit); 87 | 88 | Order entry = extractEntryOrder(position); 89 | allOrders.add(entry); 90 | position.setEntry(entry); 91 | 92 | } 93 | 94 | Collections.sort(allOrders, new Comparator() { 95 | 96 | /** 97 | * Compare and order by date and order entry. 98 | * 99 | * @param one 100 | * @param two 101 | * @return 102 | */ 103 | @Override 104 | public int compare(Order one, Order two) { 105 | int i = one.getDate().compareTo(two.getDate()); 106 | if (i != 0){ 107 | return i; 108 | } 109 | 110 | i = compareEntries(one, two); 111 | if (i != 0){ 112 | return i; 113 | } 114 | 115 | return 0; 116 | } 117 | 118 | }); 119 | 120 | for (Order order : allOrders) { 121 | 122 | if (order.isEntry()) { 123 | 124 | log.debug( String.format("Order[%s]", order) ); 125 | 126 | Position position = order.getParentPosition(); 127 | 128 | if(position.isQuantityCalculated()){ 129 | // calculate and set the order value based on the quantity calculator 130 | int quantity = position.getQuantityCalculator().execute(totalCapitalAndEquity, availableCash); 131 | position.setQuantity(quantity); 132 | 133 | double val = position.getEntryPrice() * position.getQuantity(); 134 | order.setValue(val); 135 | //position.getEntry().setValue(val); 136 | //position.getExit().setValue(val); 137 | } 138 | 139 | 140 | if (order.getValue() > availableCash) { 141 | log.debug("Not enough available cash to purchase stock - " 142 | + position); 143 | // remove both order sides as position cannot be taken 144 | position.setSuccessfullyFilled(false); 145 | 146 | continue; 147 | 148 | } else { 149 | 150 | double entryCost = order.getValue() 151 | + (order.getValue() * this.slippage) 152 | + this.commission; 153 | 154 | availableCash -= entryCost; 155 | 156 | log.debug(order + " Available Cash : " + availableCash); 157 | 158 | onEnterOrder(order, totalCapitalAndEquity, availableCash); 159 | } 160 | 161 | } else { 162 | Position position = order.getParentPosition(); 163 | 164 | if (position.isSuccessfullyFilled()) { 165 | 166 | if(position.isQuantityCalculated()){ 167 | double val = position.getExitPrice() * position.getQuantity(); 168 | order.setValue(val); 169 | } 170 | 171 | double profit = (position.getExitPrice() - position 172 | .getEntryPrice()) * position.getQuantity(); 173 | profit -= (profit * this.slippage); 174 | profit -= this.commission; 175 | 176 | totalCapitalAndEquity += profit; 177 | 178 | double exitCost = order.getValue() 179 | - (order.getValue() * this.slippage) 180 | - this.commission; 181 | 182 | //availableCash = totalCapitalAndEquity; 183 | availableCash += exitCost; 184 | 185 | log.debug(order + " Available Cash : " + availableCash); 186 | 187 | onExitOrder(order, totalCapitalAndEquity, availableCash); 188 | } 189 | } 190 | 191 | } 192 | 193 | /* 194 | * for (Position position : openPositions) { 195 | * processOpenPosition(position); } 196 | */ 197 | 198 | } 199 | 200 | public AbstractReport(double capital, double commission, double slippage, 201 | Collection closedPositions, 202 | Collection openPositions) { 203 | super(); 204 | this.totalCapitalAndEquity = capital; 205 | this.availableCash = capital; 206 | this.commission = commission; 207 | this.slippage = slippage; 208 | this.closedPositions = closedPositions; 209 | this.openPositions = openPositions; 210 | 211 | } 212 | 213 | public abstract void onEnterOrder(Order order, double capital, 214 | double availableCash); 215 | 216 | public abstract void onExitOrder(Order order, double capital, 217 | double availableCash); 218 | 219 | public abstract void render(); 220 | 221 | } 222 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/report/CSVReport.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.report; 2 | 3 | import java.io.FileWriter; 4 | import java.io.IOException; 5 | import java.io.PrintWriter; 6 | import java.util.Collection; 7 | 8 | import org.apache.commons.logging.Log; 9 | import org.apache.commons.logging.LogFactory; 10 | import org.openquant.backtest.Order; 11 | import org.openquant.backtest.Position; 12 | 13 | public class CSVReport extends AbstractReport { 14 | 15 | private Log log = LogFactory.getLog(CSVReport.class); 16 | 17 | private PrintWriter outputStream; 18 | 19 | public CSVReport(double capital, double commission, double slippage, Collection closedPositions, 20 | Collection openPositions, String filename) { 21 | super(capital, commission, slippage, closedPositions, openPositions); 22 | 23 | try { 24 | outputStream = new PrintWriter(new FileWriter(filename)); 25 | 26 | outputStream.println( String.format("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s", 27 | "Symbol","Entry Date", "Entry Price", "Exit Date", "Exit Price", "Shares", "Available Cash", "Equity", "% Gain", "Comments" ) ); 28 | 29 | } catch (IOException e) { 30 | // TODO Auto-generated catch block 31 | e.printStackTrace(); 32 | } 33 | 34 | process(); 35 | } 36 | 37 | @Override 38 | public void onEnterOrder(Order order, double capital, double availableCash) { 39 | /* 40 | Position position = order.getParentPosition(); 41 | outputStream.println( String.format("%1$s,%2$tb %2$td %2$ty %2$tT,%3$6.2f,%4$tb %4$td %4$ty %4$tT,%5$6.2f,%6$s,%7$6.2f,%8$6.2f,%9$s", position.getSymbol(), position.getEntryDate(), 42 | position.getEntryPrice(), position.getExitDate(), position.getExitPrice(), position.getQuantity(), availableCash, capital, position.getComments() ) ); 43 | */ 44 | //log.info( String.format("Entering %s, capital: %.2f, availableCash: %.2f", order, capital, availableCash) ); 45 | 46 | } 47 | 48 | @Override 49 | public void onExitOrder(Order order, double capital, double availableCash) { 50 | Position position = order.getParentPosition(); 51 | 52 | double diff = (position.getExitPrice() - position.getEntryPrice())/ position.getEntryPrice() * 100; 53 | 54 | outputStream.println( String.format("%1$s,%2$tb %2$td %2$ty %2$tT,%3$6.2f,%4$tb %4$td %4$ty %4$tT,%5$6.2f,%6$s,%7$6.2f,%8$6.2f,%9$6.4f,%10$s", position.getSymbol(), position.getEntryDate(), 55 | position.getEntryPrice(), position.getExitDate(), position.getExitPrice(), position.getQuantity(), availableCash, capital, diff, position.getComments() ) ); 56 | 57 | //log.info( String.format("Exiting %s, capital: %.2f, availableCash: %.2f", order, capital, availableCash) ); 58 | } 59 | 60 | @Override 61 | public void render() { 62 | if (outputStream != null) { 63 | outputStream.close(); 64 | } 65 | 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/report/CSVReport2.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.report; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.FileWriter; 23 | import java.io.IOException; 24 | import java.io.PrintWriter; 25 | 26 | import org.apache.commons.logging.Log; 27 | import org.apache.commons.logging.LogFactory; 28 | import org.openquant.backtest.Position; 29 | import org.openquant.backtest.PositionProcessor; 30 | 31 | public class CSVReport2 implements PositionProcessor { 32 | 33 | private Log log = LogFactory.getLog(CSVReport2.class); 34 | 35 | private PrintWriter outputStream; 36 | 37 | private double capital; 38 | 39 | private double slippage; 40 | 41 | private double commission; 42 | 43 | public CSVReport2(String filename, double capital, double commission, double slippage) { 44 | super(); 45 | this.capital = capital; 46 | this.slippage = slippage; 47 | this.commission = commission; 48 | 49 | try { 50 | outputStream = new PrintWriter(new FileWriter(filename)); 51 | 52 | outputStream.println( String.format("%s,%s,%s,%s,%s,%s,%s,%s,%s", 53 | "Symbol","Entry Date", "Entry Price", "Exit Date", "Exit Price", "Shares", "Profit", "Equity", "Comments" ) ); 54 | 55 | } catch (IOException e) { 56 | // TODO Auto-generated catch block 57 | e.printStackTrace(); 58 | } 59 | } 60 | 61 | 62 | @Override 63 | public void processClosePosition(Position position) { 64 | 65 | double profit = (position.getExitPrice() - position.getEntryPrice()) * position.getQuantity(); 66 | profit = profit - (profit*slippage) - commission; 67 | 68 | capital += profit; 69 | 70 | outputStream.println( String.format("%1$s,%2$tb %2$td %2$ty,%3$6.2f,%4$tb %4$td %4$ty,%5$6.2f,%6$s,%7$6.2f,%8$6.2f,%9$s", position.getSymbol(), position.getEntryDate(), 71 | position.getEntryPrice(), position.getExitDate(), position.getExitPrice(), position.getQuantity(), profit, capital, position.getComments() ) ); 72 | 73 | } 74 | 75 | @Override 76 | public void processOpenPosition(Position position) { 77 | // calculate entry value and add to capital 78 | capital += position.getEntryPrice() * position.getQuantity(); 79 | 80 | outputStream.println( String.format("%1$s,%2$tb %2$td %2$ty,%3$6.2f,%4$tb %4$td %4$ty,%5$6.2f,%6$s,%7$6.2f,%8$6.2f,%9$s", position.getSymbol(), position.getEntryDate(), 81 | position.getEntryPrice(), position.getExitDate(), position.getExitPrice(), position.getQuantity(), 0.0, capital, position.getComments() ) ); 82 | 83 | } 84 | 85 | @Override 86 | public void finish() { 87 | if (outputStream != null) { 88 | outputStream.close(); 89 | } 90 | 91 | log.debug(String.format("Ending capital is %12.2f", capital)); 92 | 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/report/JFreeChartReport.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.report; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.text.DateFormat; 25 | import java.text.SimpleDateFormat; 26 | import java.util.ArrayList; 27 | import java.util.Collection; 28 | import java.util.Random; 29 | 30 | import org.apache.commons.logging.Log; 31 | import org.apache.commons.logging.LogFactory; 32 | import org.jfree.chart.ChartFactory; 33 | import org.jfree.chart.ChartUtilities; 34 | import org.jfree.chart.JFreeChart; 35 | import org.jfree.data.time.Day; 36 | import org.jfree.data.time.TimeSeries; 37 | import org.jfree.data.time.TimeSeriesCollection; 38 | import org.openquant.backtest.Order; 39 | import org.openquant.backtest.Position; 40 | 41 | public class JFreeChartReport extends AbstractReport { 42 | 43 | private Log log = LogFactory.getLog(JFreeChartReport.class); 44 | 45 | private String filename; 46 | 47 | private TimeSeries equitySeries; 48 | 49 | private TimeSeries cashSeries; 50 | 51 | public JFreeChartReport(String filename, double capital, double commission, 52 | double slippage, Collection closedPositions, 53 | Collection openPositions) { 54 | 55 | super(capital, commission, slippage, closedPositions, openPositions); 56 | 57 | Random r = new Random(); 58 | 59 | equitySeries = new TimeSeries("Equity" + r.nextInt()); 60 | 61 | cashSeries = new TimeSeries("Cash" + r.nextInt()); 62 | 63 | this.filename = filename; 64 | 65 | process(); 66 | } 67 | 68 | @Override 69 | public void onEnterOrder(Order order, double capital, double availableCash) { 70 | cashSeries.addOrUpdate(new Day(order.getDate()), availableCash); 71 | 72 | } 73 | 74 | @Override 75 | public void onExitOrder(Order order, double capital, double availableCash) { 76 | equitySeries.addOrUpdate(new Day(order.getDate()), capital); 77 | 78 | cashSeries.addOrUpdate(new Day(order.getDate()), availableCash); 79 | 80 | } 81 | 82 | public void render() { 83 | try { 84 | // Add the series to your data set 85 | TimeSeriesCollection dataset = new TimeSeriesCollection(); 86 | dataset.addSeries(equitySeries); 87 | dataset.addSeries(cashSeries); 88 | 89 | // Generate the graph 90 | JFreeChart chart = ChartFactory.createTimeSeriesChart( 91 | "Equity Curve", "Day", "Value", dataset, false, false, 92 | false); 93 | 94 | ChartUtilities.saveChartAsJPEG(new File(filename), chart, 1000, 600); 95 | 96 | } catch (IOException e) { 97 | log.error(e, e); 98 | } 99 | 100 | } 101 | 102 | public TimeSeries getEquitySeries() { 103 | return equitySeries; 104 | } 105 | 106 | public TimeSeries getCashSeries() { 107 | return cashSeries; 108 | } 109 | 110 | public static void main(String ... args) throws Exception{ 111 | 112 | DateFormat format = new SimpleDateFormat("dd/MM/yy"); 113 | 114 | Collection closedPositions = new ArrayList(); 115 | 116 | closedPositions.add( new Position("TEST", format.parse("20/04/11"), format.parse("21/04/11"), 100, 110, 1000 ) ); 117 | closedPositions.add( new Position("TEST", format.parse("21/04/11"), format.parse("22/04/11"), 100, 110, 1000 ) ); 118 | 119 | closedPositions.add( new Position("TEST", format.parse("22/04/11"), format.parse("23/04/11"), 100, 110, 1000 ) ); 120 | closedPositions.add( new Position("TEST", format.parse("22/04/11"), format.parse("23/04/11"), 100, 110, 1000 ) ); 121 | closedPositions.add( new Position("TEST", format.parse("22/04/11"), format.parse("23/04/11"), 100, 110, 1000 ) ); 122 | 123 | closedPositions.add( new Position("TEST", format.parse("23/04/11"), format.parse("24/04/11"), 100, 110, 1000 ) ); 124 | closedPositions.add( new Position("TEST", format.parse("24/04/11"), format.parse("25/04/11"), 100, 110, 1000 ) ); 125 | closedPositions.add( new Position("TEST", format.parse("25/04/11"), format.parse("26/04/11"), 100, 110, 1000 ) ); 126 | closedPositions.add( new Position("TEST", format.parse("26/04/11"), format.parse("27/04/11"), 100, 110, 1000 ) ); 127 | 128 | Collection openPositions = new ArrayList(); 129 | 130 | new JFreeChartReport("mytest.jpg", 100000, 0, 0.0, closedPositions, openPositions).render(); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/report/JFreeChartReportOriginal.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.report; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.util.Date; 25 | 26 | import org.apache.commons.logging.Log; 27 | import org.apache.commons.logging.LogFactory; 28 | import org.jfree.chart.ChartFactory; 29 | import org.jfree.chart.ChartUtilities; 30 | import org.jfree.chart.JFreeChart; 31 | import org.jfree.data.time.Day; 32 | import org.jfree.data.time.TimeSeries; 33 | import org.jfree.data.time.TimeSeriesCollection; 34 | import org.openquant.backtest.Position; 35 | import org.openquant.backtest.PositionProcessor; 36 | 37 | public class JFreeChartReportOriginal implements PositionProcessor { 38 | 39 | private Log log = LogFactory.getLog(JFreeChartReportOriginal.class); 40 | 41 | private double capital; 42 | 43 | private double slippage; 44 | 45 | private double commission; 46 | 47 | private double availableCash; 48 | 49 | private String filename; 50 | 51 | private TimeSeries series = new TimeSeries("Equity Curve"); 52 | 53 | public JFreeChartReportOriginal(String filename, double capital, double commission, double slippage) { 54 | super(); 55 | this.capital = capital; 56 | this.slippage = slippage; 57 | this.commission = commission; 58 | this.filename = filename; 59 | 60 | } 61 | 62 | @Override 63 | public void processClosePosition(Position position) { 64 | 65 | 66 | double profit = (position.getExitPrice() - position.getEntryPrice()) * position.getQuantity(); 67 | profit = profit - (profit*slippage) - commission; 68 | 69 | capital += profit; 70 | 71 | series.addOrUpdate(new Day(position.getExitDate()), capital); 72 | 73 | } 74 | 75 | @Override 76 | public void processOpenPosition(Position position) { 77 | 78 | // calculate entry value and add to capital 79 | capital += position.getEntryPrice() * position.getQuantity(); 80 | 81 | //double profit = (position.getExitPrice() - position.getEntryPrice()) * position.getQuantity(); 82 | //profit = profit - (profit*slippage) - commission; 83 | 84 | 85 | series.addOrUpdate(new Day( new Date() ), capital); 86 | 87 | 88 | } 89 | 90 | @Override 91 | public void finish() { 92 | try { 93 | // Add the series to your data set 94 | TimeSeriesCollection dataset = new TimeSeriesCollection(series); 95 | 96 | // Generate the graph 97 | JFreeChart chart = ChartFactory.createTimeSeriesChart( 98 | "Equity Curve", "Day", "Value", dataset, false, false, 99 | false); 100 | 101 | ChartUtilities.saveChartAsJPEG(new File(filename), chart, 1000, 600); 102 | 103 | log.debug(String.format("Ending capital is %12.2f", capital)); 104 | 105 | } catch (IOException e) { 106 | log.error(e, e); 107 | } 108 | 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/report/MultiStatJFreeChartReport.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.report; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.Collection; 7 | import java.util.Collections; 8 | import java.util.Comparator; 9 | import java.util.List; 10 | 11 | import org.apache.commons.logging.Log; 12 | import org.apache.commons.logging.LogFactory; 13 | import org.jfree.chart.ChartFactory; 14 | import org.jfree.chart.ChartUtilities; 15 | import org.jfree.chart.JFreeChart; 16 | import org.jfree.data.time.TimeSeriesCollection; 17 | import org.openquant.backtest.Order; 18 | import org.openquant.backtest.Position; 19 | 20 | public class MultiStatJFreeChartReport extends AbstractReport { 21 | 22 | private Log log = LogFactory.getLog(MultiStatJFreeChartReport.class); 23 | 24 | private int iterations; 25 | 26 | private String filename; 27 | 28 | private List reports = new ArrayList(); 29 | 30 | public MultiStatJFreeChartReport(String filename, double capital, double commission, 31 | double slippage, List closedPositions, 32 | Collection openPositions, int iterations) { 33 | super(capital, commission, slippage, closedPositions, openPositions); 34 | this.iterations = iterations; 35 | this.filename = filename; 36 | 37 | for (int i = 0 ; i < iterations; i++){ 38 | 39 | ArrayList copy = new ArrayList(closedPositions); 40 | Collections.copy(copy, closedPositions); 41 | 42 | Collections.shuffle(copy); 43 | // order all of the positions by entry date 44 | Collections.sort(copy, new Comparator() { 45 | 46 | @Override 47 | public int compare(Position positionOne, Position positionTwo) { 48 | return positionOne.getEntryDate().compareTo( 49 | positionTwo.getEntryDate()); 50 | } 51 | 52 | }); 53 | 54 | System.out.println(" ===== "); 55 | for (Position pos : copy){ 56 | System.out.println(pos); 57 | } 58 | 59 | JFreeChartReport report = new JFreeChartReport(filename + ".jpg", 60 | capital, commission, slippage, copy, openPositions); 61 | 62 | report.process(); 63 | reports.add( report ); 64 | 65 | } 66 | 67 | 68 | } 69 | 70 | @Override 71 | public void onEnterOrder(Order order, double capital, double availableCash) { 72 | 73 | } 74 | 75 | @Override 76 | public void onExitOrder(Order order, double capital, double availableCash) { 77 | 78 | } 79 | 80 | @Override 81 | public void render() { 82 | try { 83 | // Add the series to your data set 84 | TimeSeriesCollection dataset = new TimeSeriesCollection(); 85 | 86 | //int i = 0; 87 | for(JFreeChartReport report : reports){ 88 | 89 | //i++; 90 | //report.getEquitySeries().setKey(i); 91 | //report.getEquitySeries().setDescription("equity" + i); 92 | //report.getCashSeries().setDescription("cash" + i); 93 | 94 | dataset.addSeries(report.getEquitySeries()); 95 | //dataset.addSeries(report.getCashSeries()); 96 | } 97 | 98 | // Generate the graph 99 | 100 | JFreeChart chart = ChartFactory.createTimeSeriesChart( 101 | "Equity Curve", "Day", "Value", dataset, false, false, 102 | false); 103 | 104 | ChartUtilities.saveChartAsJPEG(new File(filename), chart, 1000, 600); 105 | 106 | } catch (IOException e) { 107 | log.error(e, e); 108 | } 109 | 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/org/openquant/backtest/report/TradesReport.java: -------------------------------------------------------------------------------- 1 | package org.openquant.backtest.report; 2 | 3 | import java.io.FileWriter; 4 | import java.io.IOException; 5 | import java.io.PrintWriter; 6 | import java.util.Collection; 7 | 8 | import org.apache.commons.logging.Log; 9 | import org.apache.commons.logging.LogFactory; 10 | import org.openquant.backtest.Order; 11 | import org.openquant.backtest.Position; 12 | 13 | public class TradesReport extends AbstractReport { 14 | 15 | private Log log = LogFactory.getLog(TradesReport.class); 16 | 17 | private PrintWriter outputStream; 18 | 19 | public TradesReport(double capital, double commission, double slippage, Collection closedPositions, 20 | Collection openPositions, String filename) { 21 | super(capital, commission, slippage, closedPositions, openPositions); 22 | 23 | try { 24 | outputStream = new PrintWriter(new FileWriter(filename)); 25 | 26 | } catch (IOException e) { 27 | // TODO Auto-generated catch block 28 | e.printStackTrace(); 29 | } 30 | 31 | process(); 32 | } 33 | 34 | @Override 35 | public void onEnterOrder(Order order, double capital, double availableCash) { 36 | outputStream.println( String.format("Entering %s, capital: %.2f, availableCash: %.2f", order, capital, availableCash) ); 37 | 38 | } 39 | 40 | @Override 41 | public void onExitOrder(Order order, double capital, double availableCash) { 42 | outputStream.println( String.format("Exiting %s, capital: %.2f, availableCash: %.2f", order, capital, availableCash) ); 43 | } 44 | 45 | @Override 46 | public void render() { 47 | 48 | if (outputStream != null) { 49 | outputStream.close(); 50 | } 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/org/openquant/data/ObjectStreamSeriesDatasource.java: -------------------------------------------------------------------------------- 1 | package org.openquant.data; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.File; 23 | import java.io.FileInputStream; 24 | import java.io.FileOutputStream; 25 | import java.io.IOException; 26 | import java.io.ObjectInputStream; 27 | import java.io.ObjectOutputStream; 28 | import java.util.Date; 29 | import java.util.Set; 30 | 31 | import org.openquant.backtest.CandleSeries; 32 | 33 | public class ObjectStreamSeriesDatasource implements SeriesDatasource { 34 | 35 | private String filename; 36 | 37 | private SeriesCollection seriesCollection = new SeriesCollection(); 38 | 39 | public ObjectStreamSeriesDatasource(final String filename) { 40 | this.filename = filename; 41 | 42 | if(exists()){ 43 | // initialize if exists 44 | FileInputStream fis = null; 45 | ObjectInputStream in = null; 46 | try { 47 | fis = new FileInputStream(filename); 48 | in = new ObjectInputStream(fis); 49 | seriesCollection = (SeriesCollection) in.readObject(); 50 | in.close(); 51 | } catch (IOException ex) { 52 | ex.printStackTrace(); 53 | } catch (ClassNotFoundException ex) { 54 | ex.printStackTrace(); 55 | } 56 | } 57 | } 58 | 59 | public Date getBeginDate() { 60 | return seriesCollection.getBeginDate(); 61 | } 62 | 63 | public Date getEndDate() { 64 | return seriesCollection.getEndDate(); 65 | } 66 | 67 | public Set getSymbols(){ 68 | return seriesCollection.getSymbols(); 69 | } 70 | 71 | public boolean exists(){ 72 | return new File(filename).exists(); 73 | } 74 | 75 | public void insertCandleSeries(CandleSeries series) { 76 | seriesCollection.addSeries(series); 77 | } 78 | 79 | public void commit() { 80 | 81 | FileOutputStream fos = null; 82 | ObjectOutputStream out = null; 83 | try { 84 | fos = new FileOutputStream(filename); 85 | out = new ObjectOutputStream(fos); 86 | out.writeObject(seriesCollection); 87 | out.close(); 88 | } catch (IOException ex) { 89 | ex.printStackTrace(); 90 | } 91 | 92 | } 93 | 94 | @Override 95 | public CandleSeries fetchSeries(String symbol) throws Exception { 96 | return seriesCollection.getSeries(symbol); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/org/openquant/data/SeriesCollection.java: -------------------------------------------------------------------------------- 1 | package org.openquant.data; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.Serializable; 23 | import java.util.Date; 24 | import java.util.HashSet; 25 | import java.util.Set; 26 | 27 | import org.openquant.backtest.CandleSeries; 28 | 29 | public class SeriesCollection implements Serializable { 30 | 31 | private static final long serialVersionUID = 1L; 32 | 33 | private Set seriesSet = new HashSet(); 34 | 35 | public void addSeries(final CandleSeries series) { 36 | seriesSet.add(series); 37 | } 38 | 39 | public Set getSymbols(){ 40 | Set rSet = new HashSet(); 41 | for (CandleSeries series : seriesSet){ 42 | 43 | rSet.add(series.getSymbol()); 44 | } 45 | return rSet; 46 | } 47 | 48 | public Date getBeginDate(){ 49 | return seriesSet.iterator().next().getBeginDate(); 50 | } 51 | 52 | public Date getEndDate(){ 53 | return seriesSet.iterator().next().getEndDate(); 54 | } 55 | 56 | public CandleSeries getSeries(String symbol){ 57 | 58 | for (CandleSeries series : seriesSet){ 59 | if (series.getSymbol().equals(symbol)){ 60 | return series; 61 | } 62 | } 63 | return null; 64 | 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/org/openquant/data/SeriesDatasource.java: -------------------------------------------------------------------------------- 1 | package org.openquant.data; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import org.openquant.backtest.CandleSeries; 23 | 24 | public interface SeriesDatasource { 25 | 26 | public abstract CandleSeries fetchSeries(final String symbol) throws Exception; 27 | 28 | } -------------------------------------------------------------------------------- /src/org/openquant/data/YahooSeriesDatasource.java: -------------------------------------------------------------------------------- 1 | package org.openquant.data; 2 | 3 | /* 4 | Copyright (c) 2010, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.BufferedReader; 23 | import java.io.InputStreamReader; 24 | import java.net.URL; 25 | import java.text.DateFormat; 26 | import java.text.MessageFormat; 27 | import java.text.ParseException; 28 | import java.text.SimpleDateFormat; 29 | import java.util.ArrayList; 30 | import java.util.Calendar; 31 | import java.util.Date; 32 | import java.util.GregorianCalendar; 33 | import java.util.List; 34 | import java.util.StringTokenizer; 35 | import java.util.TimeZone; 36 | 37 | import org.apache.commons.logging.Log; 38 | import org.apache.commons.logging.LogFactory; 39 | import org.openquant.backtest.Candle; 40 | import org.openquant.backtest.CandleSeries; 41 | import org.openquant.backtest.DateUtil; 42 | 43 | public class YahooSeriesDatasource implements SeriesDatasource { 44 | 45 | private Log log = LogFactory.getLog(YahooSeriesDatasource.class); 46 | 47 | private final static String YAHOO_URL = "http://table.finance.yahoo.com/table.csv?s={0}&a={1}&b={2}&c={3}&d={4}&e={5}&f={6}&g={7}&ignore=.csv"; 48 | 49 | public static final String DATE_FORMAT = "yyyy-MM-dd"; 50 | 51 | private final static DateFormat sdf = new SimpleDateFormat(DATE_FORMAT); 52 | 53 | private boolean splitAdjust = true; 54 | 55 | private Date begin; 56 | 57 | private Date end; 58 | 59 | 60 | public YahooSeriesDatasource(String begin, String end) { 61 | 62 | try { 63 | this.begin = sdf.parse(begin); 64 | this.end = sdf.parse(end); 65 | } catch (ParseException e) { 66 | log.error(e.getMessage(), e); 67 | } 68 | } 69 | 70 | public YahooSeriesDatasource(String begin) { 71 | 72 | try { 73 | this.begin = sdf.parse(begin); 74 | } catch (ParseException e) { 75 | log.error(e.getMessage(), e); 76 | } 77 | } 78 | 79 | public YahooSeriesDatasource(final int lookback){ 80 | this.begin = DateUtil.calculateLookbackDate(lookback); 81 | } 82 | 83 | public boolean isSplitAdjust() { 84 | return splitAdjust; 85 | } 86 | 87 | public void setSplitAdjust(boolean splitAdjust) { 88 | this.splitAdjust = splitAdjust; 89 | } 90 | 91 | public void setBeginDate(String beginDate){ 92 | try { 93 | this.begin = sdf.parse(beginDate); 94 | } catch (ParseException e) { 95 | log.error(e.getMessage(), e); 96 | } 97 | } 98 | 99 | public void setEndDate(String endDate){ 100 | try { 101 | this.end = sdf.parse(endDate); 102 | } catch (ParseException e) { 103 | log.error(e.getMessage(), e); 104 | } 105 | } 106 | 107 | /* (non-Javadoc) 108 | * @see org.openquant.backtest.SeriesDatasource#fetchSeries(java.lang.String) 109 | */ 110 | public CandleSeries fetchSeries(final String symbol) throws Exception { 111 | 112 | Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 113 | cal.setTime(begin); 114 | String beginYear = String.valueOf(cal.get(Calendar.YEAR)); 115 | String beginMonth = String.valueOf(cal.get(Calendar.MONTH)); 116 | String beginDay = String.valueOf(cal.get(Calendar.DAY_OF_MONTH)); 117 | 118 | // if end not set, assume yesterday's date 119 | if (end == null) { 120 | // get Calendar with current date 121 | GregorianCalendar gCal = new GregorianCalendar(); 122 | 123 | // get yesterday's date 124 | gCal.add(Calendar.DATE, -1); 125 | 126 | end = gCal.getTime(); 127 | } 128 | 129 | cal.setTime(end); 130 | String day = String.valueOf(cal.get(Calendar.DAY_OF_MONTH)); 131 | String month = String.valueOf(cal.get(Calendar.MONTH)); 132 | String year = String.valueOf(cal.get(Calendar.YEAR)); 133 | 134 | String resolution = "d"; 135 | 136 | // open url 137 | String urlStr = MessageFormat.format(YAHOO_URL, symbol, beginMonth, beginDay, beginYear, month, day, year, 138 | resolution); 139 | BufferedReader reader; 140 | String line; 141 | 142 | List lineList = new ArrayList(); 143 | 144 | log.info("URL [" + urlStr + "]"); 145 | URL url = new URL(urlStr); 146 | reader = new BufferedReader(new InputStreamReader(url.openStream())); 147 | line = reader.readLine(); 148 | log.debug(line); 149 | 150 | while ((line = reader.readLine()) != null) { 151 | lineList.add(0, line); 152 | } 153 | 154 | List candles = new ArrayList(); 155 | 156 | for (String currentLine : lineList) { 157 | log.debug(currentLine); 158 | StringTokenizer str = new StringTokenizer(currentLine, ","); 159 | String datestring = str.nextToken(); 160 | 161 | double open = round(Double.parseDouble(str.nextToken()), 2); 162 | double high = Double.parseDouble(str.nextToken()); 163 | double low = Double.parseDouble(str.nextToken()); 164 | double close = Double.parseDouble(str.nextToken()); 165 | Long volume = null; 166 | double adjclose = 0; 167 | if (str.hasMoreTokens()) { 168 | String volString = str.nextToken(); 169 | volume = Long.parseLong(volString); 170 | log.trace( String.format("Parsed volume [%s] to %s", volString, volume)); 171 | 172 | if (splitAdjust) { 173 | adjclose = Double.parseDouble(str.nextToken()); 174 | } 175 | } 176 | 177 | Date date = sdf.parse(datestring); 178 | 179 | Candle candle = null; 180 | if (splitAdjust) { 181 | double adjustmentFactor = adjclose / close; 182 | 183 | candle = new Candle(symbol, date, round(open * adjustmentFactor, 2), round(high * adjustmentFactor, 2), 184 | round(low * adjustmentFactor, 2), adjclose, volume); 185 | } else { 186 | // new candle 187 | candle = new Candle(symbol, date, open, high, low, close, volume); 188 | } 189 | 190 | candles.add(candle); 191 | 192 | } 193 | 194 | return new CandleSeries(candles); 195 | } 196 | 197 | public static double round(double Rval, int Rpl) { 198 | double p = (double) Math.pow(10, Rpl); 199 | Rval = Rval * p; 200 | float tmp = Math.round(Rval); 201 | return (double) tmp / p; 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /src/org/openquant/data/load/DataLoader.java: -------------------------------------------------------------------------------- 1 | package org.openquant.data.load; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.util.Set; 23 | 24 | import javax.annotation.Resource; 25 | 26 | import org.apache.commons.logging.Log; 27 | import org.apache.commons.logging.LogFactory; 28 | import org.openquant.backtest.CandleSeries; 29 | import org.openquant.data.ObjectStreamSeriesDatasource; 30 | import org.openquant.data.YahooSeriesDatasource; 31 | import org.springframework.beans.factory.annotation.Autowired; 32 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 33 | import org.springframework.context.annotation.Bean; 34 | import org.springframework.context.annotation.Configuration; 35 | import org.springframework.context.annotation.ImportResource; 36 | 37 | /** 38 | * Utility class for importing data from yahoo into a local 39 | * ObjectStreamSeriesDatasource. During backtesting the 40 | * ObjectStreamSeriesDatasource can replace the usual YahooSeriesDatasource so 41 | * the test does not have to go out and fetch the data from the Yahoo's servers 42 | * for each stock. 43 | * 44 | * This example will autowire the contents of symbols-yahoo-nasdaq10.xml into 45 | * the symbols variable and export the resulting Collection to the file specified 46 | * via the DATAFILE static var at a specific START_DATE to todays closing date. 47 | * 48 | * @author jay 49 | * 50 | */ 51 | @Configuration 52 | @ImportResource("classpath:/symbols-yahoo-nasdaq10.xml") 53 | public class DataLoader { 54 | 55 | private static Log log = LogFactory.getLog(DataLoader.class); 56 | 57 | private static final String START_DATE = "2000-01-01"; 58 | 59 | private static final String DATAFILE = "nasdaq10.dat"; 60 | 61 | @Autowired 62 | @Resource(name = "symbols") 63 | private Set symbols; 64 | 65 | private ObjectStreamSeriesDatasource localSeriesDatasource = new ObjectStreamSeriesDatasource(DATAFILE); 66 | 67 | public class ExecuteBean { 68 | 69 | public void loadFromYahoo() { 70 | 71 | try { 72 | 73 | for (String symbol : symbols) { 74 | try { 75 | CandleSeries series = yahooSeriesDatasource().fetchSeries(symbol); 76 | localSeriesDatasource.insertCandleSeries(series); 77 | log.info("Loaded series " + symbol); 78 | } catch (Exception e) { 79 | log.error(e.getMessage(), e); 80 | } 81 | } 82 | 83 | } finally { 84 | localSeriesDatasource.commit(); 85 | } 86 | 87 | } 88 | } 89 | 90 | @Bean 91 | public YahooSeriesDatasource yahooSeriesDatasource() { 92 | return new YahooSeriesDatasource(START_DATE); 93 | } 94 | 95 | @Bean(name = "executeBean", initMethod = "loadFromYahoo") 96 | public ExecuteBean executeBean() { 97 | return new ExecuteBean(); 98 | } 99 | 100 | /** 101 | * @param args 102 | */ 103 | public static void main(String[] args) { 104 | new AnnotationConfigApplicationContext(DataLoader.class); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/org/openquant/quote/QuoteDataSource.java: -------------------------------------------------------------------------------- 1 | package org.openquant.quote; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.util.HashSet; 23 | import java.util.Set; 24 | import java.util.concurrent.ScheduledFuture; 25 | import java.util.concurrent.ScheduledThreadPoolExecutor; 26 | import java.util.concurrent.TimeUnit; 27 | 28 | import org.apache.commons.logging.Log; 29 | import org.apache.commons.logging.LogFactory; 30 | import org.openquant.backtest.Candle; 31 | 32 | public abstract class QuoteDataSource { 33 | 34 | private Log log = LogFactory.getLog(QuoteDataSource.class); 35 | 36 | private Set symbols = new HashSet(); 37 | 38 | private boolean loopForever = true; 39 | 40 | private int interval = 5; 41 | 42 | private ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(20); 43 | 44 | private ScheduledFuture scheduledFuture; 45 | 46 | public interface GlobalQuoteListener{ 47 | void onOpenQuote(Candle quote); 48 | void onQuote(Candle quote); 49 | } 50 | 51 | protected GlobalQuoteListener globalListener; 52 | 53 | public GlobalQuoteListener getGlobalListener() { 54 | return globalListener; 55 | } 56 | 57 | public void setGlobalListener(GlobalQuoteListener globalListener) { 58 | this.globalListener = globalListener; 59 | } 60 | 61 | public QuoteDataSource(Set symbols, GlobalQuoteListener globalListener){ 62 | this.symbols = symbols; 63 | this.globalListener = globalListener; 64 | } 65 | 66 | public QuoteDataSource(){ 67 | 68 | } 69 | 70 | private class QuoteRetriever implements Runnable { 71 | 72 | @Override 73 | public void run() { 74 | 75 | Set localSymbols = new HashSet(getSymbols()); 76 | 77 | log.debug(">> BEGIN retrieve"); 78 | for (String symbol : localSymbols) { 79 | try { 80 | Candle candle = retrieveQuoteCandle(symbol); 81 | if (candle != null){ 82 | onGetQuote( candle ); 83 | } 84 | } catch (Exception e) { 85 | log.error("Exception when retrieving [" + symbol + "]", e); 86 | } 87 | } 88 | log.debug(">> END retrieve"); 89 | 90 | 91 | } 92 | } 93 | 94 | private void retrieveOpenQuotes(){ 95 | 96 | Set localSymbols = new HashSet(getSymbols()); 97 | 98 | log.debug(">> BEGIN retrieve"); 99 | for (String symbol : localSymbols) { 100 | try { 101 | Candle candle = retrieveQuoteCandle(symbol); 102 | if (candle != null){ 103 | onGetOpenQuote( candle ); 104 | } 105 | } catch (Exception e) { 106 | log.error("Exception when retrieving [" + symbol + "]", e); 107 | } 108 | } 109 | log.debug(">> END retrieve"); 110 | } 111 | 112 | public boolean isLoopForever() { 113 | return loopForever; 114 | } 115 | 116 | public void setLoopForever(boolean loopForever) { 117 | this.loopForever = loopForever; 118 | } 119 | 120 | public int getInterval() { 121 | return interval; 122 | } 123 | 124 | public void setInterval(int interval) { 125 | this.interval = interval; 126 | // reinitialize the schedule 127 | if (scheduledFuture != null){ 128 | scheduledFuture.cancel(true); 129 | initialize(); 130 | } 131 | } 132 | 133 | public Set getSymbols() { 134 | return symbols; 135 | } 136 | 137 | public void setSymbols(Set symbols) { 138 | this.symbols = symbols; 139 | } 140 | 141 | public void removeSymbol(String symbol){ 142 | symbols.remove(symbol); 143 | } 144 | 145 | public void initialize() { 146 | 147 | // first get Open Quotes 148 | retrieveOpenQuotes(); 149 | 150 | if (isLoopForever()) { 151 | scheduledFuture = executor.scheduleWithFixedDelay(new QuoteRetriever(), 0, getInterval(), TimeUnit.SECONDS); 152 | 153 | } else { 154 | scheduledFuture = executor.schedule(new QuoteRetriever(), getInterval(), TimeUnit.SECONDS); 155 | } 156 | } 157 | 158 | public void finalize() { 159 | scheduledFuture.cancel(false); 160 | 161 | } 162 | 163 | 164 | private void onGetQuote(final Candle quoteCandle) { 165 | 166 | if(log.isDebugEnabled()){ 167 | log.debug(String.format("Got quote %s[%s, %s, %s, %s, %s, %s]", quoteCandle.getSymbol(), 168 | quoteCandle.getClosePrice(), quoteCandle.getOpenPrice(), quoteCandle.getHighPrice(), 169 | quoteCandle.getLowPrice(), quoteCandle.getVolume(), quoteCandle.getDate())); 170 | } 171 | 172 | this.globalListener.onQuote(quoteCandle); 173 | 174 | } 175 | 176 | private void onGetOpenQuote(final Candle quoteCandle) { 177 | 178 | if(log.isDebugEnabled()){ 179 | log.debug(String.format("Got quote %s[%s, %s, %s, %s, %s, %s]", quoteCandle.getSymbol(), 180 | quoteCandle.getClosePrice(), quoteCandle.getOpenPrice(), quoteCandle.getHighPrice(), 181 | quoteCandle.getLowPrice(), quoteCandle.getVolume(), quoteCandle.getDate())); 182 | } 183 | 184 | this.globalListener.onOpenQuote(quoteCandle); 185 | 186 | } 187 | 188 | 189 | public abstract Candle retrieveQuoteCandle(String symbol) throws Exception; 190 | 191 | } 192 | -------------------------------------------------------------------------------- /src/org/openquant/quote/QuoteListener.java: -------------------------------------------------------------------------------- 1 | package org.openquant.quote; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import org.openquant.backtest.Candle; 23 | 24 | public abstract class QuoteListener{ 25 | 26 | protected String symbol; 27 | 28 | public QuoteListener(){ 29 | } 30 | 31 | public QuoteListener(String symbol){ 32 | this.symbol = symbol; 33 | } 34 | 35 | public String getSymbol() { 36 | return symbol; 37 | } 38 | 39 | public void setSymbol(String symbol) { 40 | this.symbol = symbol; 41 | } 42 | 43 | public abstract void onGetQuote(Candle quoteCandle); 44 | } 45 | -------------------------------------------------------------------------------- /src/org/openquant/quote/YahooQuoteDataSource.java: -------------------------------------------------------------------------------- 1 | package org.openquant.quote; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.io.BufferedReader; 23 | import java.io.IOException; 24 | import java.io.InputStreamReader; 25 | import java.net.MalformedURLException; 26 | import java.net.URL; 27 | import java.text.DateFormat; 28 | import java.text.SimpleDateFormat; 29 | import java.util.Date; 30 | import java.util.HashSet; 31 | import java.util.Set; 32 | import java.util.StringTokenizer; 33 | 34 | import org.apache.commons.logging.Log; 35 | import org.apache.commons.logging.LogFactory; 36 | import org.openquant.backtest.Candle; 37 | 38 | public class YahooQuoteDataSource extends QuoteDataSource { 39 | 40 | private Log log = LogFactory.getLog(YahooQuoteDataSource.class); 41 | 42 | private static final String QUOTE_URL = "http://quote.yahoo.com/d/quotes.csv?s=%s&d=t&f=sl1d1t1c1ohgvj1pp2wern"; 43 | 44 | private final static DateFormat DATE_FORMAT = new SimpleDateFormat("M/d/yyyy hh:mmaaa"); 45 | 46 | public YahooQuoteDataSource(Set symbols, GlobalQuoteListener globalListener) { 47 | super(symbols, globalListener); 48 | } 49 | 50 | public YahooQuoteDataSource(Set symbols) { 51 | this.setSymbols(symbols); 52 | } 53 | 54 | @Override 55 | public Candle retrieveQuoteCandle(String symbol) throws Exception { 56 | String urlStr = String.format(QUOTE_URL, symbol); 57 | String line = readLine(urlStr); 58 | 59 | Candle returnCandle = null; 60 | // "MSFT",28.46,"5/7/2010","1:23pm",-0.52,28.90,28.94,27.32,94535576,249.4B,28.98,"-1.79%","19.01 - 31.58",1.93,15.02,"Microsoft Corpora" 61 | 62 | StringTokenizer str = new StringTokenizer(line, ",\""); 63 | // symbol is the next token 64 | str.nextToken(); 65 | double quote = Double.parseDouble(str.nextToken()); 66 | Date date; 67 | try { 68 | date = DATE_FORMAT.parse(str.nextToken() + " " + str.nextToken()); 69 | } catch (Exception e) { 70 | log.warn("Exception when parsing date for " + symbol + " using system timestamp"); 71 | date = new Date(); 72 | } 73 | // price change is the next token 74 | str.nextToken(); 75 | double open = Double.parseDouble(str.nextToken()); 76 | double high = Double.parseDouble(str.nextToken()); 77 | double low = Double.parseDouble(str.nextToken()); 78 | double volume = Double.parseDouble(str.nextToken()); 79 | 80 | returnCandle = new Candle(symbol, date, open, high, low, quote, volume); 81 | 82 | return returnCandle; 83 | } 84 | 85 | private String readLine(final String urlStr) { 86 | BufferedReader reader; 87 | String line = null; 88 | 89 | try { 90 | URL url = new URL(urlStr); 91 | reader = new BufferedReader(new InputStreamReader(url.openStream())); 92 | line = reader.readLine(); 93 | } catch (MalformedURLException e) { 94 | log.error(e, e); 95 | } catch (IOException e) { 96 | log.error(e, e); 97 | } 98 | 99 | return line; 100 | } 101 | 102 | public static void main(String... args) { 103 | 104 | final Log log = LogFactory.getLog(YahooQuoteDataSource.class); 105 | 106 | Set symbols = new HashSet(); 107 | symbols.add("MSFT"); 108 | symbols.add("GOOG"); 109 | 110 | QuoteDataSource quote = new YahooQuoteDataSource(symbols, new GlobalQuoteListener() { 111 | 112 | @Override 113 | public void onQuote(Candle quoteCandle) { 114 | log.info(String.format("Got quote %s[%s, %s, %s, %s, %s, %s]", quoteCandle.getSymbol(), 115 | quoteCandle.getOpenPrice(), quoteCandle.getHighPrice(), quoteCandle.getLowPrice(), 116 | quoteCandle.getClosePrice(), quoteCandle.getVolume(), quoteCandle.getDate())); 117 | 118 | } 119 | 120 | @Override 121 | public void onOpenQuote(Candle quoteCandle) { 122 | log.info(String.format("Got open quote %s[%s, %s, %s, %s, %s, %s]", quoteCandle.getSymbol(), 123 | quoteCandle.getOpenPrice(), quoteCandle.getHighPrice(), quoteCandle.getLowPrice(), 124 | quoteCandle.getClosePrice(), quoteCandle.getVolume(), quoteCandle.getDate())); 125 | 126 | } 127 | }); 128 | 129 | quote.setLoopForever(true); 130 | quote.setInterval(1); 131 | quote.initialize(); 132 | 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/org/openquant/util/OpenMarketUtil.java: -------------------------------------------------------------------------------- 1 | package org.openquant.util; 2 | 3 | /* 4 | Copyright (c) 2011, Jay Logelin 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 8 | conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the distribution. Neither the name of the JQuant nor the names of its 13 | contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 15 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 16 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 19 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | import java.util.ArrayList; 23 | import java.util.Calendar; 24 | import java.util.Collections; 25 | import java.util.Date; 26 | import java.util.List; 27 | 28 | 29 | /** 30 | * source : http://www.isthemarketopen.com/ 31 | * 32 | * @author jay 33 | * 34 | */ 35 | public class OpenMarketUtil { 36 | 37 | private static List closed = new ArrayList(); 38 | 39 | static{ 40 | closed.add(new Day(2011, Calendar.JANUARY, 17)); 41 | closed.add(new Day(2011, Calendar.FEBRUARY, 21)); 42 | closed.add(new Day(2011, Calendar.APRIL, 22)); 43 | closed.add(new Day(2011, Calendar.MAY, 30)); 44 | closed.add(new Day(2011, Calendar.JULY, 4)); 45 | closed.add(new Day(2011, Calendar.SEPTEMBER, 5)); 46 | closed.add(new Day(2011, Calendar.NOVEMBER, 24)); 47 | closed.add(new Day(2011, Calendar.DECEMBER, 26)); 48 | } 49 | 50 | public static boolean isMarketOpenToday(){ 51 | return isMarketOpen(new Day(new Date())); 52 | } 53 | 54 | public static boolean isMarketOpen(Day day){ 55 | 56 | int position = Collections.binarySearch(closed, day); 57 | 58 | return position < 0; 59 | 60 | } 61 | 62 | public static void main(String ... args){ 63 | if (OpenMarketUtil.isMarketOpenToday()){ 64 | System.out.println("Market is open"); 65 | }else{ 66 | System.out.println("Market is closed"); 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /test/org/openquant/test/TestTradeSystem.java: -------------------------------------------------------------------------------- 1 | package org.openquant.test; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.openquant.backtest.BackTestExecutor; 7 | import org.openquant.backtest.CandleSeriesTestContext; 8 | import org.openquant.backtest.Position; 9 | import org.openquant.backtest.Series; 10 | import org.openquant.data.SeriesDatasource; 11 | import org.openquant.data.YahooSeriesDatasource; 12 | 13 | public class TestTradeSystem extends CandleSeriesTestContext{ 14 | 15 | @Override 16 | public void run() { 17 | 18 | Series Close = closeSeries(); 19 | Series RSI = Close.RSI(25); 20 | 21 | for (int bar = 25; bar < barsCount() - 1; bar++) { 22 | 23 | Position pos = getLastOpenPosition(); 24 | if (hasOpenPositions()) { 25 | sellAtLimit(bar + 1, pos, pos.getEntryPrice() * 1.02, "XLL"); 26 | } 27 | 28 | if (hasOpenPositions()) { 29 | sellAtStop(bar + 1, pos, pos.getEntryPrice() * 0.99, "XLS"); 30 | } 31 | 32 | if (RSI.getAt(bar) > RSI.getAt(bar - 1)) { 33 | buyAtLimit(bar + 1, close(bar), 1000, "ELL"); 34 | } 35 | 36 | 37 | } 38 | 39 | } 40 | 41 | public static void main(String... args) { 42 | //new ClassPathXmlApplicationContext("TestTradeSystem.xml"); 43 | 44 | SeriesDatasource data = new YahooSeriesDatasource("2009-01-01"); 45 | 46 | List symbols = new ArrayList(); 47 | symbols.add("GOOG"); 48 | 49 | BackTestExecutor executor = new BackTestExecutor(data, symbols, new TestTradeSystem(), "TestReport", 100000, 9.99, 0.2); 50 | executor.run(); 51 | } 52 | 53 | } 54 | --------------------------------------------------------------------------------