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.


Leave a Reply

Your email address will not be published. Required fields are marked *