This project provides an easy to use functionality to implement and evaluate automatic stock trading strategies. It is implemented in java and therefore can be used in any environment which builds on the JVM.
It provides the following functionality:
At the end it should be possible to easily formulate and evaluate a stock strategy and to evaluate the impact of changes.
In this document we demonstrates the basic functionality using Scala: We are using JupyterLab (http://jupyter.org) with the BeakerX (http://beakerx.com/) Scala Kernel.
We need to add the java libraries:
%classpath config resolver maven-public http://192.168.1.10:8081/repository/maven-public/
%classpath add mvn ch.pschatzmann:investor:0.9-SNAPSHOT
%classpath add mvn ch.pschatzmann:jupyter-jdk-extensions:0.0.1-SNAPSHOT
First we define all the imports which are used in this demo:
// our stock evaluation framwork
import ch.pschatzmann.dates._;
import ch.pschatzmann.stocks._;
import ch.pschatzmann.stocks.data.universe._;
import ch.pschatzmann.stocks.input._;
import ch.pschatzmann.stocks.accounting._;
import ch.pschatzmann.stocks.accounting.kpi._;
import ch.pschatzmann.stocks.execution._;
import ch.pschatzmann.stocks.execution.fees._;
import ch.pschatzmann.stocks.execution.price._;
import ch.pschatzmann.stocks.parameters._;
import ch.pschatzmann.stocks.strategy._;
import ch.pschatzmann.stocks.strategy.optimization._;
import ch.pschatzmann.stocks.strategy.allocation._;
import ch.pschatzmann.stocks.strategy.selection._;
import ch.pschatzmann.stocks.integration._;
import ch.pschatzmann.stocks.strategy.OptimizedStrategy.Schedule._;
import ch.pschatzmann.stocks.cache._
import ch.pschatzmann.stocks.ta4j.indicator._
// java
import java.util.stream.Collectors;
import java.util._;
import java.lang._;
import java.util.function.Consumer;
// scala
import scala.collection.JavaConverters;
// ta4j
import org.ta4j.core._
import org.ta4j.core.num._
import org.ta4j.core.analysis._
import org.ta4j.core.analysis.criteria._;
import org.ta4j.core.indicators._;
import org.ta4j.core.indicators.helpers._;
import org.ta4j.core.trading.rules._;
// jupyter custom displayer
import ch.pschatzmann.display.Table
import ch.pschatzmann.dates._ import ch.pschatzmann.stocks._ import ch.pschatzmann.stocks.data.universe._ import ch.pschatzmann.stocks.input._ import ch.pschatzmann.stocks.accounting._ import ch.pschatzmann.stocks.accounting.kpi._ import ch.pschatzmann.stocks.execution._ import ch.pschatzmann.stocks.execution.fees._ import ch.pschatzmann.stocks.execution.price._ import ch.pschatzmann.stocks.parameters._ import ch.pschatzmann.stocks.strategy._ import ch.pschatzmann.stocks.strategy.optimization._ import ch.pschatzmann.stocks.strategy.allocation._ import ch.pschatzmann.stocks.strategy.selection._ import ch.pschatzmann.stocks.integration._ import ch.pschatzmann.stocks.strategy.OptimizedStrategy.Schedule._ import ch.pschatzmann.stocks.cache._ import ch.pschatzmann.stocks.ta4j.indicator._ imp...
A StockID is identifiying a stock by the trading symbol and the exchange.
The Uninverse is a collection of StockIDs. We can use the universe to find stocks or to process a collection relevant stocks.
var universe = new ListUniverse("NASDAQ:AAPL, NASDAQ:AA")
universe.list()
[NASDAQ:AAPL, NASDAQ:AA]
BeakerX is displaying a list of Maps automatically as Javascript table. We can use our Table class to convert any list of objects so that we can diplay it as table. We have also implemented some short cuts on our custom classes so that we can use our class direcly instead of a list.
Table.create(universe)
var universe = new IEXUniverse().list;
Table.create(universe)
var universe = new QuandlSixUniverse().list;
Table.create(universe)
Just as a side note: The API provides java collections. It is possible to convert them to a Scala type - e.q a Seq.
import scala.collection.JavaConverters._
new QuandlSixUniverse().list.asScala.toSeq.slice(0,5)
[[AN8068571086CHF, ARP290071876CHF, AT0000606306CHF, AT0000644505CHF, AT0000652011CHF]]
The StockData is the class which provides the history of stock rates and some stock related KPIs. We need to indicate a Reader which defines the source of the data.
Currently we support
The folloiwng readers will need some license keys in the investor.properties
Here is the example how to retrieve the stock history:
var stockData = Context.getStockData("AAPL", new IEXReader());
Table.create(stockData)
We do not need to indicate reader. In this case the system uses the Default reader:
var table = Table.create(Context.getStockData("AAPL"),Context.getStockData("INTL"))
We use the BeakerX charting functionality:
Now, we can display a stock chart:
new SimpleTimePlot {
data = table.seq()
columns = Seq("AAPL.Open","INTL.Closing")
showLegend = false
}
Ta4j is an open source Java library for technical analysis. It provides the basic components for creation, evaluation and execution of trading strategies.
We can use our StockData functionality as data source for TA4J to e.g. to calculate indicators:
var stockData = Context.getStockData("AAPL", "NASDAQ");
// translate to Ta4j TimeSeries
var series = new StockTimeSeries(stockData, new DateRange(Context.date("2017-01-01"),new Date()));
// Closing Prices
var closePrice = new ClosePriceIndicator(series);
// Getting the simple moving average (SMA) of the close price over the last 5 ticks
var shortSma = new SMAIndicator(closePrice, 5);
// Getting a longer SMA (e.g. over the 30 last ticks)
var longSma = new SMAIndicator(closePrice, 30);
SMAIndicator barCount: 30
We convert the indicators to a Table. The name of the indicator is usually determined from the java class name but because we have multiple SMAIndicators we need to wrap them into a NamedIndicator in order to assign a unique name:
var indicators = Table.create(closePrice, NamedIndicator.create(shortSma,"ShortSMA"), NamedIndicator.create(longSma,"LongSMA"))
// create chart
new SimpleTimePlot {
data = indicators.seq()
columns = Seq("ShortSMA","LongSMA","ClosePriceIndicator")
showLegend = false
}
Here is the complete trading and evaluation example which we took from the TA4J documentation that can be found at https://github.com/ta4j/ta4j/wiki/Getting+started.
The example has been converted to Scala:
var result = new HashMap[String,Number]()
var stockData = new StockData(new StockID("AAPL", "NASDAQ"), new MarketArchiveHttpReader());
var series = new StockTimeSeries(stockData);
var closePrice = new ClosePriceIndicator(series);
// Getting the simple moving average (SMA) of the close price over the last 5 ticks
var shortSma = new SMAIndicator(closePrice, 5);
// Getting a longer SMA (e.g. over the 30 last ticks)
var longSma = new SMAIndicator(closePrice, 30);
// Buying rules
// We want to buy:
// - if the 5-ticks SMA crosses over 30-ticks SMA
// - or if the price goes below a defined price (e.g $800.00)
var buyingRule = new CrossedUpIndicatorRule(shortSma, longSma)
.or(new CrossedDownIndicatorRule(closePrice, Context.number(800.0)));
// Selling rules
// We want to sell:
// - if the 5-ticks SMA crosses under 30-ticks SMA
// - or if if the price looses more than 3%
// - or if the price earns more than 2%
var sellingRule = new CrossedDownIndicatorRule(shortSma, longSma)
.or(new StopLossRule(closePrice, Context.number(3.0)))
.or(new StopGainRule(closePrice, Context.number(2.0)));
var strategy = new BaseStrategy(buyingRule, sellingRule);
// Running our juicy trading strategy...
var manager = new TimeSeriesManager(series);
var tradingRecord = manager.run(strategy);
result.put("Number of trades for our strategy" , tradingRecord.getTradeCount().longValue());
// Getting the cash flow of the resulting trades
var cashFlow = new CashFlow(series, tradingRecord);
// Getting the profitable trades ratio
var profitTradesRatio = new AverageProfitableTradesCriterion();
result.put("Profitable trades ratio" , profitTradesRatio.calculate(series, tradingRecord).doubleValue());
// Getting the reward-risk ratio
var rewardRiskRatio = new RewardRiskRatioCriterion();
result.put("Reward-risk ratio" , rewardRiskRatio.calculate(series, tradingRecord).doubleValue());
// Total profit of our strategy
// vs total profit of a buy-and-hold strategy
var vsBuyAndHold = new VersusBuyAndHoldCriterion(new TotalProfitCriterion());
result.put("Our profit vs buy-and-hold profit" , vsBuyAndHold.calculate(series, tradingRecord).doubleValue());
result
So far we have seen how we can use our functionality toghether with TA4J to implement an automatic trading and evaluation platform.
In the next Chapter we demonstrate our own Trading Simulation and Optimization functionality.
The Account class is used to record and evaluate trades. We need to indicate the opening amount, the open date of the account and the Fees Model (IFeesModel). We can optionally register a generic reader or a ticker specific reader which defines from where the stock information is read.
The requested stock trades are recorded with the addTransaction() method. Positive quantities are purchased, negative quantities are sold.
The paper trader implements the basic trading (simulation) functionality. We can indicate a delay (with setDelay() and the price logic with setPrice(). In our example the trade is executed on the next day with the open rate.
With the execute() method we start the processing which is processing the open unfilled orders.
var stockdata = new StockID("AAPL", "NASDAQ");
var account = new Account("Simulation","USD", 100000.00, Context.date("2015-01-01"), new PerTradeFees(10.0));
account.putReader(new MarketArchiveHttpReader());
account.addTransaction(new Transaction(Context.date("2015-01-04"), stockdata, +100l));
account.addTransaction(new Transaction(Context.date("2015-10-04"), stockdata, -90l));
var trader = new PaperTrader(account);
// configure alternative logic
trader.setDelay(new OneDayDelay());
trader.setPrice(new OpenPrice());
trader.execute();
// display the resulting transactions
Table.create(account.getTransactions());
The heart of automatic trading are the "trading strategies". A class which implements ITradingStrategy can be used for automatic trading. A class which implements IOptimizableTradingStrategy can be used for automatic parameter optimization and automatic trading.
The framework comes with the following standard strategies:
TradingStrategyFactory.list()
[CCICorrectionStrategy, GlobalExtremaStrategy, MovingMomentumStrategy, RSI2Strategy]
The Fitness class will be used to evaluate the strategies. As a result it provides both the input and the calculated KPI ouput parameters and updates the account.
You can use the SimulatedFitness class if you want to avoid the update to the account.
Table.create(account.getTransactions())
var account = new Account("Simulation","USD", 100000.00, Context.date("2015-01-01"), new PerTradeFees(10.0));
var stockdata = new StockData(new StockID("AAPL", "NASDAQ"), new MarketArchiveHttpReader());
var strategy = new RSI2Strategy(stockdata);
var trader = new PaperTrader(account);
var state = new Fitness(trader).getFitness(strategy, account.getDateRange());
// print one parameter
println("Return: " + state.result().getValue(KPI.AbsoluteReturn));
// print all parameters
state.getMap()
Return: 87513.0
Table.create(account.getTransactions())
var totalHistory = HistoricValues.create(account.getTotalValueHistory(), "TotalValue")
var cashHistory = HistoricValues.create(account.getCashHistoryForAllDates(), "Cash")
var actualHistory = HistoricValues.create(account.getActualValueHistory(), "ActualValue")
ch.pschatzmann.stocks.integration.HistoricValues@45694867
var historyTable = Table.create(totalHistory, cashHistory, actualHistory)
new SimpleTimePlot {
data = historyTable.seq
columns = Seq("TotalValue","Cash","ActualValue")
showLegend = false
}
In order to get a better understanding of the development of the values over time we can chart the Acocunt information.
Table.create(account.getTransactions())
import scala.collection.JavaConversions._
var list = new ArrayList[HashMap[String,String]]()
for (strategy <- TradingStrategyFactory.list()) {
var map = new HashMap[String,String]();
map.put("Name", strategy)
map.put("Description",TradingStrategyFactory.getStrategyDesciption(strategy))
list.add(map)
}
Table.create(list)
Here is a small example which compares the trading strategies for Apple starting from 2015-01-01
import java.util.ArrayList
def calculateResult(account:Account, strategy : IOptimizableTradingStrategy) : java.util.Map[String,Object] = {
var state = new SimulatedFitness(account).getFitness(strategy, account.getDateRange());
var result = state.getMap();
// add strategy name to result
result.put("Strategy", strategy.getClass().getSimpleName());
return result;
}
var account = new Account("Simulation","USD", 100000.00, Context.date("2015-01-01"), new PerTradeFees(10.0));
var sd = Context.getStockData("AAPL", "NASDAQ");
var result = new ArrayList[java.util.Map[String,Object]]();
result.add(calculateResult(account, new RSI2Strategy(sd)));
result.add(calculateResult(account, new BuyAndHoldStrategy(sd)));
result.add(calculateResult(account, new CCICorrectionStrategy(sd)));
result.add(calculateResult(account, new GlobalExtremaStrategy(sd)));
result.add(calculateResult(account, new MovingMomentumStrategy(sd)));
Table.create(result)
Finally we demonstrate how you can implement your custom Strategy. The indicators and trading strategy functionality is based on TA4J https://github.com/ta4j/ta4j.
The simplest and fastest way is to implement a BaseStrategy by extending the CommonTradingStrategy:
import ch.pschatzmann.dates._;
import ch.pschatzmann.stocks._;
import ch.pschatzmann.stocks.accounting._;
import ch.pschatzmann.stocks.integration._;
import ch.pschatzmann.stocks.execution._;
import ch.pschatzmann.stocks.execution.fees._;
import ch.pschatzmann.stocks.strategy._;
import ch.pschatzmann.stocks.strategy.optimization._;
import ch.pschatzmann.stocks.input._;
import ch.pschatzmann.stocks.parameters._;
import org.ta4j.core._;
import org.ta4j.core.analysis._;
import org.ta4j.core.analysis.criteria._;
import org.ta4j.core.indicators._;
import org.ta4j.core.indicators.helpers._;
import org.ta4j.core.trading.rules._;
import ch.pschatzmann.display.Displayers
class DemoStrategy(sd : StockData) extends CommonTradingStrategy (sd){
// Define BaseStrategy
def buildStrategy(timeSeries : TimeSeries) : BaseStrategy = {
val closePrices = new ClosePriceIndicator(timeSeries);
// Getting the max price over the past week
val maxPrices = new MaxPriceIndicator(timeSeries);
val weekMaxPrice = new HighestValueIndicator(maxPrices, 7);
// Getting the min price over the past week
val minPrices = new MinPriceIndicator(timeSeries);
val weekMinPrice = new LowestValueIndicator(minPrices, 7);
// Going long if the close price goes below the min price
val downWeek = new MultiplierIndicator(weekMinPrice, 1.004);
val buyingRule = new UnderIndicatorRule(closePrices, downWeek);
// Going short if the close price goes above the max price
val upWeek = new MultiplierIndicator(weekMaxPrice, 0.996);
val sellingRule = new OverIndicatorRule(closePrices, upWeek);
return new BaseStrategy(buyingRule, sellingRule);
}
}
var apple = new StockData(new StockID("AAPL", "NASDAQ"), new MarketArchiveHttpReader());
var account = new Account("Simulation","USD", 100000.00, Context.date("2015-01-01"), new PerTradeFees(10.0));
var strategy = new DemoStrategy(apple);
var trader = new PaperTrader(account);
var state = new Fitness(trader).getFitness(strategy, account.getDateRange());
println("Return: "+state.result().getValue(KPI.AbsoluteReturn));
state.getMap();
Return: 39839.0
An alternaive approach is to implement the interface directly:
import ch.pschatzmann.dates._;
import ch.pschatzmann.stocks._;
import ch.pschatzmann.stocks.accounting._;
import ch.pschatzmann.stocks.integration._;
import ch.pschatzmann.stocks.execution._;
import ch.pschatzmann.stocks.execution.fees._;
import ch.pschatzmann.stocks.strategy._;
import ch.pschatzmann.stocks.strategy.optimization._;
import ch.pschatzmann.stocks.input._;
import ch.pschatzmann.stocks.parameters._;
import org.ta4j.core._;
import org.ta4j.core.analysis._;
import org.ta4j.core.analysis.criteria._;
import org.ta4j.core.indicators._;
import org.ta4j.core.indicators.helpers._;
import org.ta4j.core.trading.rules._;
import ch.pschatzmann.display.Displayers
/**
* Strategy implemented in Scala
*/
class DemoStrategy extends ITradingStrategy {
var state = new State();
val stockdata = new StockData(new StockID("AAPL", "NASDAQ"), new MarketArchiveHttpReader());
def getStrategy():Strategy = {
var timeSeries = new StockTimeSeries(getStockData());
val closePrices = new ClosePriceIndicator(timeSeries);
// Getting the max price over the past week
val maxPrices = new MaxPriceIndicator(timeSeries);
val weekMaxPrice = new HighestValueIndicator(maxPrices, 7);
// Getting the min price over the past week
val minPrices = new MinPriceIndicator(timeSeries);
val weekMinPrice = new LowestValueIndicator(minPrices, 7);
// Going long if the close price goes below the min price
val downWeek = new MultiplierIndicator(weekMinPrice, 1.004);
val buyingRule = new UnderIndicatorRule(closePrices, downWeek);
// Going short if the close price goes above the max price
val upWeek = new MultiplierIndicator(weekMaxPrice, 0.996);
val sellingRule = new OverIndicatorRule(closePrices, upWeek);
return new BaseStrategy(buyingRule, sellingRule);
}
def getStockData():StockData = {
return stockdata;
}
def getName():String = {
return "DemoStrategy";
}
def getDescription():String = {
return "Demo strategy implemented in scala";
}
def getParameters():State = {
return state;
}
def resetHistory() {
}
def reset() {
}
}
var account = new Account("Simulation","USD", 100000.00, Context.date("2015-01-01"), new PerTradeFees(10.0));
var strategy = new DemoStrategy();
var trader = new PaperTrader(account);
var state = new Fitness(trader).getFitness(strategy,account.getDateRange());
println("Return: "+state.result().getValue(KPI.AbsoluteReturn));
state.getMap();
Return: 39839.0