diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..ca5f8a2 --- /dev/null +++ b/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.gitignore b/.gitignore index faa46f2..8951330 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,4 @@ com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties +/bin/ diff --git a/.project b/.project new file mode 100644 index 0000000..d69bdc2 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + trading-backtest + + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..e889521 --- /dev/null +++ b/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir= +eclipse.preferences.version=1 diff --git a/build.gradle b/build.gradle index af28a9d..3cf58f8 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,8 @@ dependencies { compile 'org.slf4j:slf4j-api:1.7.+' compile 'ch.qos.logback:logback-classic:1.+' + + compile group: 'com.yahoofinance-api', name: 'YahooFinanceAPI', version: '3.13.0' testCompile group: 'junit', name: 'junit', version: '4.11' } diff --git a/src/main/java/org/lst/trading/lib/util/HistoricalPriceService.java b/src/main/java/org/lst/trading/lib/util/HistoricalPriceService.java index d6312d5..be405e8 100644 --- a/src/main/java/org/lst/trading/lib/util/HistoricalPriceService.java +++ b/src/main/java/org/lst/trading/lib/util/HistoricalPriceService.java @@ -1,8 +1,7 @@ package org.lst.trading.lib.util; import org.lst.trading.lib.series.DoubleSeries; -import rx.Observable; public interface HistoricalPriceService { - Observable getHistoricalAdjustedPrices(String symbol); + DoubleSeries getHistoricalAdjustedPrices(String symbol); } diff --git a/src/main/java/org/lst/trading/lib/util/yahoo/YahooFinance.java b/src/main/java/org/lst/trading/lib/util/yahoo/YahooFinance.java deleted file mode 100755 index df61977..0000000 --- a/src/main/java/org/lst/trading/lib/util/yahoo/YahooFinance.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.lst.trading.lib.util.yahoo; - -import org.lst.trading.lib.csv.CsvReader; -import org.lst.trading.lib.series.DoubleSeries; -import org.lst.trading.lib.util.HistoricalPriceService; -import org.lst.trading.lib.util.Http; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import rx.Observable; - -import java.time.Instant; -import java.time.LocalDate; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.util.stream.Stream; - -import static java.lang.String.format; -import static org.lst.trading.lib.csv.CsvReader.ParseFunction.doubleColumn; -import static org.lst.trading.lib.csv.CsvReader.ParseFunction.ofColumn; - -public class YahooFinance implements HistoricalPriceService { - public static final String SEP = ","; - public static final CsvReader.ParseFunction DATE_COLUMN = ofColumn("Date").map(s -> LocalDate.from(DateTimeFormatter.ISO_DATE.parse(s)).atStartOfDay(ZoneOffset.UTC.normalized()).toInstant()); - public static final CsvReader.ParseFunction CLOSE_COLUMN = doubleColumn("Close"); - public static final CsvReader.ParseFunction HIGH_COLUMN = doubleColumn("High"); - public static final CsvReader.ParseFunction LOW_COLUMN = doubleColumn("Low"); - public static final CsvReader.ParseFunction OPEN_COLUMN = doubleColumn("Open"); - public static final CsvReader.ParseFunction ADJ_COLUMN = doubleColumn("Adj Close"); - public static final CsvReader.ParseFunction VOLUME_COLUMN = doubleColumn("Volume"); - public static final OffsetDateTime DEFAULT_FROM = OffsetDateTime.of(2010, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); - - private static final Logger log = LoggerFactory.getLogger(YahooFinance.class); - - @Override public Observable getHistoricalAdjustedPrices(String symbol) { - return getHistoricalAdjustedPrices(symbol, DEFAULT_FROM.toInstant()); - } - - public Observable getHistoricalAdjustedPrices(String symbol, Instant from) { - return getHistoricalAdjustedPrices(symbol, from, Instant.now()); - } - - public Observable getHistoricalAdjustedPrices(String symbol, Instant from, Instant to) { - return getHistoricalPricesCsv(symbol, from, to).map(csv -> csvToDoubleSeries(csv, symbol)); - } - - private static Observable getHistoricalPricesCsv(String symbol, Instant from, Instant to) { - return Http.get(createHistoricalPricesUrl(symbol, from, to)) - .flatMap(Http.asString()); - } - - private static DoubleSeries csvToDoubleSeries(String csv, String symbol) { - Stream lines = Stream.of(csv.split("\n")); - DoubleSeries prices = CsvReader.parse(lines, SEP, DATE_COLUMN, ADJ_COLUMN); - prices.setName(symbol); - prices = prices.toAscending(); - return prices; - } - - private static String createHistoricalPricesUrl(String symbol, Instant from, Instant to) { - return format("http://ichart.yahoo.com/table.csv?s=%s&%s&%s&g=d&ignore=.csv", symbol, toYahooQueryDate(from, "abc"), toYahooQueryDate(to, "def")); - } - - private static String toYahooQueryDate(Instant instant, String names) { - OffsetDateTime time = instant.atOffset(ZoneOffset.UTC); - String[] strings = names.split(""); - return format("%s=%d&%s=%d&%s=%d", strings[0], time.getMonthValue() - 1, strings[1], time.getDayOfMonth(), strings[2], time.getYear()); - } -} diff --git a/src/main/java/org/lst/trading/lib/util/yahoo/YahooFinanceService.java b/src/main/java/org/lst/trading/lib/util/yahoo/YahooFinanceService.java new file mode 100755 index 0000000..e111092 --- /dev/null +++ b/src/main/java/org/lst/trading/lib/util/yahoo/YahooFinanceService.java @@ -0,0 +1,68 @@ +package org.lst.trading.lib.util.yahoo; + +import java.io.FileNotFoundException; +import java.math.BigDecimal; +import java.net.SocketTimeoutException; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import org.lst.trading.lib.series.DoubleSeries; +import org.lst.trading.lib.series.TimeSeries.Entry; +import org.lst.trading.lib.util.HistoricalPriceService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import yahoofinance.Stock; +import yahoofinance.YahooFinance; +import yahoofinance.histquotes.HistoricalQuote; +import yahoofinance.histquotes.Interval; + +public class YahooFinanceService implements HistoricalPriceService { + + private static final Logger log = LoggerFactory.getLogger(YahooFinanceService.class); + + private static final int DAYS_OF_HISTORY = 2520; + + @Override + public DoubleSeries getHistoricalAdjustedPrices(String symbol) { + return getHistory(symbol, DAYS_OF_HISTORY); + } + + public DoubleSeries getHistory(String symbol, int daysBack) { + DoubleSeries doubleSeries = new DoubleSeries(symbol); + + try { + Calendar from = Calendar.getInstance(); + from.add(Calendar.DAY_OF_MONTH, -daysBack); + + Stock stock = YahooFinance.get(symbol); + //[n] is the most current, [0] is the last in history + List historicalQuotes = stock.getHistory(from, Interval.DAILY); + + for (HistoricalQuote historicalQuote : historicalQuotes) { + BigDecimal open = historicalQuote.getOpen(); + BigDecimal high = historicalQuote.getHigh(); + BigDecimal low = historicalQuote.getLow(); + BigDecimal close = historicalQuote.getClose(); + BigDecimal adjClose = historicalQuote.getAdjClose(); + Date time = historicalQuote.getDate().getTime(); + + if (open == null || high == null || low == null || close == null || adjClose == null) { + log.error(symbol + " " + time + " has missing data: " + open + ", " + high + ", " + low + ", " + close + ", " + adjClose); + continue; + } + + doubleSeries.add(new Entry(adjClose.doubleValue(), time.toInstant())); + } + } catch (FileNotFoundException e) { + log.error("no such symbol: " + symbol); + } catch (SocketTimeoutException e) { + log.error("network error: " + symbol); + } catch (Exception e) { + e.printStackTrace(); + } + + return doubleSeries; + } +} diff --git a/src/main/java/org/lst/trading/main/BacktestMain.java b/src/main/java/org/lst/trading/main/BacktestMain.java index d4c124d..0b424c4 100644 --- a/src/main/java/org/lst/trading/main/BacktestMain.java +++ b/src/main/java/org/lst/trading/main/BacktestMain.java @@ -1,19 +1,19 @@ package org.lst.trading.main; +import static java.lang.String.format; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Locale; + import org.lst.trading.lib.backtest.Backtest; import org.lst.trading.lib.model.ClosedOrder; import org.lst.trading.lib.model.TradingStrategy; import org.lst.trading.lib.series.MultipleDoubleSeries; import org.lst.trading.lib.util.Util; -import org.lst.trading.lib.util.yahoo.YahooFinance; +import org.lst.trading.lib.util.yahoo.YahooFinanceService; import org.lst.trading.main.strategy.kalman.CointegrationTradingStrategy; -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.Locale; - -import static java.lang.String.format; - public class BacktestMain { public static void main(String[] args) throws URISyntaxException, IOException { String x = "GLD"; @@ -23,8 +23,8 @@ public static void main(String[] args) throws URISyntaxException, IOException { TradingStrategy strategy = new CointegrationTradingStrategy(x, y); // download historical prices - YahooFinance finance = new YahooFinance(); - MultipleDoubleSeries priceSeries = new MultipleDoubleSeries(finance.getHistoricalAdjustedPrices(x).toBlocking().first(), finance.getHistoricalAdjustedPrices(y).toBlocking().first()); + YahooFinanceService finance = new YahooFinanceService(); + MultipleDoubleSeries priceSeries = new MultipleDoubleSeries(finance.getHistoricalAdjustedPrices(x), finance.getHistoricalAdjustedPrices(y)); // initialize the backtesting engine int deposit = 15000;