Parameter Optimization
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:
– Simple access to stock data
– Declarative formulation of trading strategies
– Evaluation of trading strategies
– Optimization of trading strategies
– Support of portfolio of multiple stocks / trading strategies
In this document we describe the functionality which helps us to optimize the parameters for trading strategies.
Setup
First you need to install the java libraries:
%classpath config resolver maven-public http://pschatzmann.ch: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
Added new repo: maven-public
Added jars: [httpclient-4.5.3.jar, jetty-util-9.2.20.v20161216.jar, sojo-1.0.5.jar, httpcore-4.4.6.jar, xercesImpl-2.11.0.jar, httpmime-4.5.2.jar, xml-apis-1.4.01.jar, commons-codec-1.10.jar, sevenzipjbinding-9.20-2.00beta.jar, serializer-2.7.2.jar, websocket-client-9.2.20.v20161216.jar, timeseries-forecast-1.1.1.jar, sac-1.3.jar, jetty-io-9.2.20.v20161216.jar, combinatoradix-0.8.2.jar, commons-jcs-core-2.0.jar, websocket-common-9.2.20.v20161216.jar, jfreechart-1.5.0.jar, ta4j-core-0.10.jar, commons-cli-1.3.1.jar, investor-0.9-SNAPSHOT.jar, commons-io-2.5.jar, cssparser-0.9.21.jar, htmlunit-2.24.jar, jackson-annotations-2.9.4.jar, jackson-databind-2.9.4.jar, commons-attributes-api-2.2.jar, YahooFinanceAPI-3.12.3.jar, log4j-1.2.17.jar, jcl-over-slf4j-1.7.25.jar, htmlunit-core-js-2.23.jar, jackson-core-2.9.4.jar, hamcrest-core-1.1.jar, commons-lang3-3.5.jar, sevenzipjbinding-all-platforms-9.20-2.00beta.jar, slf4j-api-1.7.25.jar, slf4j-log4j12-1.7.25.jar, ant-1.5.jar, junit-4.10.jar, websocket-api-9.2.20.v20161216.jar, xalan-2.7.2.jar, qdox-1.5.jar, neko-htmlunit-2.24.jar]
Added jars: [jcs-1.3.jar, commons-logging-1.1.jar, common-0.0.1-SNAPSHOT.jar, servlet-api-2.3.jar, logkit-1.0.1.jar, avalon-framework-4.1.3.jar, concurrent-1.3.4.jar, in-memory-stardb-0.0.1-SNAPSHOT.jar, jupyter-jdk-extensions-0.0.1-SNAPSHOT.jar]
Imports
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.integration.ChartData.FieldName._;
import ch.pschatzmann.stocks.strategy.OptimizedStrategy.Schedule._;
// java
import java.lang._;
import java.util.ArrayList
// ta4j
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._;
/// jupyter custom displayer
import ch.pschatzmann.display.Displayers
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.integration.ChartData.FieldName._
import ch.pschatzmann.stocks.strategy.OptimizedStrategy.Schedule._
import java.lang._
impo...
Logging / Caching
We are using JCS for caching the stock data. We deactivate this functionality in order to avoid the errors and warnings if the system is not set up properly.
The framework is using log4j. For this demonstration session we deactivate the information messages and therfore define the log level for this session to display only Errors:
Displayers.setup("ERROR")
Context.setCachingActive(false);
Context.isCachingActive();
false
Trading Strategy Parameter Optimization
Each trading strategy can have some build-in parameters. The Optimizer is used to optimize
the parameter values of the trading strategy to give the best indicated KPI value. In the example below
we maximize the AbsoluteReturn.
We currently provide the following implementations for Optimizers:
– BinarySearchOptimizer
– PermutatedBinarySearchOptimizer
– SequenceOptimizer
– BruteForceOptimizer
– SimmulatedAnnealingOptimizer
– GeneticOptimizer
The BinarySearchOptimizer is using on the parameter sequence which is defined in the trading strategy to
optimize the target. In order to avoid local minima – in the case if parameters are dependent on each other – we have implemented the PermutatedBinarySearchOptimizer which is using the result of the best sequence combination.
val target = KPI.AbsoluteReturn;
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 trader = new PaperTrader(account);
var optimizer = new BinarySearchOptimizer(new Fitness(trader), KPI.AbsoluteReturn);
var result = optimizer.optimize(new RSI2Strategy(stockdata),account.getDateRange());
// print one parameter
println("Absolute Return: "+result.result().getValue(target));
// print all parameters
Displayers.display(result.getMap());
Absolute Return: 91268.0
Key | Value |
---|---|
PurchasedValue | 112923 |
SharpeRatio | 1 |
RealizedGains | 12973 |
UnrealizedGains | 78345 |
LongSMAPeriod | 250 |
Cash | 12 |
MaxDrawDownLowValue | 106823 |
NumberOfTrades | 5 |
NumberOfTradedStocks | 1 |
EntryLimit | 3 |
RSIPeriod | 2 |
NumberOfCashTransfers | 1 |
AbsoluteReturnAvaragePerDay | 119 |
NumberOfSells | 2 |
MaxDrawDownHighValue | 125054 |
ShortSMAPeriod | 9 |
ReturnPercentAnualized | 30 |
AbsoluteReturnStdDev | 1383 |
ExitLimit | 94 |
AbsoluteReturn | 91268 |
NumberOfBuys | 3 |
ReturnPercent | 91 |
TotalFees | 50 |
MaxDrawDownNumberOfDays | 410 |
ActualValue | 191268 |
MaxDrawDownPercent | 18231 |
ReturnPurcentStdDev | 0 |
Avoid Look Ahead Bias: OptimizedStrategy
The example above has a big problem: It suffers from look ahead bias!. This is bias created by the use of information or data that would not have been known or available during the period being analyzed. This will lead to unrealistic results.
Therefore we need to make sure that when we create the optimization, we only use the data backward in time.
We have created the OptimizedStrategy class to help us with this.
Here is the example that only uses the historic data up to the “2014-12-31” to optimize the strategy parameters:
var periods = Context.getDateRanges("2014-01-01","2015-01-01");
var account = new Account("Simulation","USD", 100000.00, periods.get(0).getStart(), 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 optimizer = new BinarySearchOptimizer(new SimulatedFitness(account), KPI.AbsoluteReturn);
var optimizedStrategy = new OptimizedStrategy(strategy, optimizer, periods.get(0));
var state = new Fitness(trader).getFitness(optimizedStrategy, periods.get(1));
"Return: " + state.result().getValue(KPI.AbsoluteReturn);
Return: 78946.0
We also support a more dynamic approach where we run the optimization while we execute the strategy.
In the following example we run the optimization every month on past data:
import ch.pschatzmann.stocks.strategy.OptimizedStrategy.Schedule._;
var periods = Context.getDateRanges("2014-01-01","2015-01-01");
var account = new Account("Simulation","USD", 100000.00, periods.get(0).getStart(), 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 optimizer = new BinarySearchOptimizer(new SimulatedFitness(account),KPI.AbsoluteReturn);
var optimizedStrategy = new OptimizedStrategy(strategy, optimizer, MONTH);
var state = new Fitness(trader).getFitness(optimizedStrategy, periods.get(1));
"Return: " + state.result().getValue(KPI.AbsoluteReturn);
Return: 54414.0
Performance of Optimizers
Finally we compare the result of the different Optimizers. First we run them with the standard settings:
def evaluateNoOptimization() = {
val rec = new java.util.TreeMap[String,Any]();
val start = System.currentTimeMillis()
account.reset();
val strategy = new RSI2Strategy(stockdata);
var state = new SimulatedFitness(account).getFitness(strategy,account.getDateRange());
val end = System.currentTimeMillis()
rec.put("Strategy", "Not optimized");
rec.put(target.name(), state.result.getValue(target));
rec.put("Runtime (sec)", (end - start) / 1000.0);
result.add(rec);
}
def evaluateOptimizer(optimizer : IOptimizer, name:String) = {
println("processing "+name)
val rec = new java.util.TreeMap[String,Any]()
val start = System.currentTimeMillis()
account.reset();
var resultOfOptimization = optimizer.optimize(new RSI2Strategy(stockdata),account.getDateRange());
val end = System.currentTimeMillis()
rec.put("Strategy", name);
rec.put(target.name(), resultOfOptimization.result().getValue(target));
rec.put("Runtime (sec)", (end - start) / 1000.0);
result.add(rec);
}
val result = new ArrayList[java.util.Map[String,_]]();
val target = KPI.AbsoluteReturn;
val stockdata = 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 fitness = new SimulatedFitness(account);
evaluateNoOptimization();
evaluateOptimizer(new BinarySearchOptimizer(fitness, target),"BinarySearchOptimizer");
evaluateOptimizer(new SequenceOptimizer(fitness, target),"SequenceOptimizer");
evaluateOptimizer(new SimulatedAnnealingOptimizer(fitness, target),"SimmulatedAnnealingOptimizer");
evaluateOptimizer(new GeneticOptimizer(fitness, target),"GeneticOptimizer");
// this is very slow
//evaluateOptimizer(new PermutatedBinarySearchOptimizer(fitness,target),"PermutatedBinarySearchOptimizer");
Displayers.display(result);
processing BinarySearchOptimizer
processing SequenceOptimizer
processing SimmulatedAnnealingOptimizer
processing GeneticOptimizer
AbsoluteReturn | Runtime (sec) | Strategy |
---|---|---|
56741 | 0.274 | Not optimized |
91268 | 5.593 | BinarySearchOptimizer |
84676 | 39.602 | SequenceOptimizer |
94325 | 32.512 | SimmulatedAnnealingOptimizer |
96840 | 86.355 | GeneticOptimizer |
We can also influence the performance of the optimizers by setting their specific parameters:
result.clear()
var optimizer = new SimulatedAnnealingOptimizer(fitness,target);
optimizer.setCount(20)
evaluateOptimizer(optimizer,"SimulatedAnnealingOptimizer")
Displayers.display(result)
processing SimulatedAnnealingOptimizer
AbsoluteReturn | Runtime (sec) | Strategy |
---|---|---|
92251 | 7.08 | SimulatedAnnealingOptimizer |
result.clear()
var optimizer = new GeneticOptimizer(fitness, target);
optimizer.setGenerations(20)
evaluateOptimizer(optimizer,"GeneticOptimizer")
Displayers.display(result)
processing GeneticOptimizer
AbsoluteReturn | Runtime (sec) | Strategy |
---|---|---|
113990 | 168.922 | GeneticOptimizer |
The BinarySearchOptimizer gives the quickest results.
The other optimizers are slow but potentilly give better results.
0 Comments