Introduction
Last year I was busy to build my own project which 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
At the end it should be possible to easily formulate and evaluate stock strategy and to evaluate the impact of changes on assumptions.
In this document we demonstrates the basic functionality using Scala: We are using JupyterLab with the BeakerX Scala Kernel. The Jupyter source code can be found on github. And finally here is the link to the Javadoc.
Setup
We need to add the java libraries:
%classpath config resolver maven-public http://software.pschatzmann.ch/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
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.util.stream.Collectors;
import java.util._;
import java.lang._;
import java.util.function.Consumer;
// 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.util.stream...
We register the automatic displayers for charts. We are also not interested in information messages from the log, so we define a higher log level:
Displayers.setup("WARN")
true
Basic Data Structures: Universe, StockID, StockData
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.
– QuandlWIKIUniverse
– QuandlSixUniverse
– QuandlEuronextUniverse
– MarketUniverse
– JsonUniverse
– ListUniverse
var universe = new ListUniverse(Arrays.asList(new StockID("AAPL","NASDAQ")));
var values = universe.list();
Displayers.display(values)
ticker | exchange |
---|---|
AAPL | NASDAQ |
var universe = new QuandlSixUniverse();
var values = Context.head(universe.list(),10);
Displayers.display(values)
ticker | exchange |
---|---|
ARP290071876CHF | SIX |
AN8068571086CHF | SIX |
AT0000652011CHF | SIX |
AT0000741053CHF | SIX |
AT0000606306CHF | SIX |
AT0000676903CHF | SIX |
AT0000743059CHF | SIX |
AT0000644505CHF | SIX |
AT0000720008CHF | SIX |
AT0000697750CHF | SIX |
Just as a side note: The API provides java collections. It is possible to convert them to a Scala type – e.q a Seq.
var universe = new IEXUniverse();
var values = Context.head(universe.list(),10);
Displayers.display(values)
ticker | exchange |
---|---|
A | |
AA | |
AABA | |
AAC | |
AADR | |
AAL | |
AAMC | |
AAME | |
AAN | |
AAOI |
import scala.collection.JavaConverters;
JavaConverters.asScalaBufferConverter(values).asScala.toSeq
[[:A, :AA, :AABA, :AAC, :AADR, :AAL, :AAMC, :AAME, :AAN, :AAOI]]
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
– YahooReader
– QuandlWIKIReader
– QuandlSixReader
– MarketArchiveHttpReader
– AlphaVantageReader
– IEXReader
Here is the example how to retrieve the stock history:
var stockdata = new StockData(new StockID("AAPL", "NASDAQ"), new IEXReader());
Displayers.display(Context.tail(stockdata.getHistory(),10));
index | date | low | high | open | closing | adjustmentFactor | volume |
---|---|---|---|---|---|---|---|
1248 | 2018-05-14 | 187.86 | 189.01 | 188.15 | 20778772 | ||
1249 | 2018-05-15 | 185.1 | 186.78 | 186.44 | 23695159 | ||
1250 | 2018-05-16 | 186 | 186.07 | 188.18 | 19183064 | ||
1251 | 2018-05-17 | 186.36 | 188 | 186.99 | 17294029 | ||
1252 | 2018-05-18 | 186.13 | 187.19 | 186.31 | 18297728 | ||
1253 | 2018-05-21 | 186.9106 | 188 | 187.63 | 18400787 | ||
1254 | 2018-05-22 | 186.78 | 188.375 | 187.16 | 15240704 | ||
1255 | 2018-05-23 | 185.76 | 186.35 | 188.36 | 20058415 | ||
1256 | 2018-05-24 | 186.21 | 188.77 | 188.15 | 23233975 | ||
1257 | 2018-05-25 | 187.65 | 188.23 | 188.58 | 17460963 |
Charts
Unfortunatly the BeakerX charting currently does not work in Jupyterlab. Therfore we use JFeeChart http://www.jfree.org/jfreechart/.
Now, we can display a stock chart in just 1 line:
var aapl = new StockData(new StockID("AAPL", "NASDAQ"), new MarketArchiveHttpReader());
new TimeSeriesChart().add(aapl)
Technical Analysis with TA4J
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:IStockData = new StockData(new StockID("AAPL", "NASDAQ"), new MarketArchiveHttpReader());
// translate to Ta4j TimeSeries
var series = new Ta4jTimeSeries(stockData, new DateRange(Context.date("2017-01-01"),new Date()));
// Closing Prices
var closePrice:Indicator[Decimal] = new ClosePriceIndicator(series);
// Getting the simple moving average (SMA) of the close price over the last 5 ticks
var shortSma:Indicator[Decimal] = new SMAIndicator(closePrice, 5);
// Getting a longer SMA (e.g. over the 30 last ticks)
var longSma:Indicator[Decimal] = new SMAIndicator(closePrice, 30);
// create chart
var chart = new TimeSeriesChart()
chart.add(shortSma,"shortSma")
chart.add(closePrice,"close")
chart.add(longSma,"longSma")
chart
Trading with TA4J
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%20started.
The example has been converted to Scala:
var stockData = new StockData(new StockID("AAPL", "NASDAQ"), new MarketArchiveHttpReader());
var series = new Ta4jTimeSeries(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, Decimal.valueOf("800")));
// 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, Decimal.valueOf("3")))
.or(new StopGainRule(closePrice, Decimal.valueOf("2")));
var strategy = new BaseStrategy(buyingRule, sellingRule);
// Running our juicy trading strategy...
var manager = new TimeSeriesManager(series);
var tradingRecord = manager.run(strategy);
println("Number of trades for our strategy: " + tradingRecord.getTradeCount());
// Getting the cash flow of the resulting trades
var cashFlow = new CashFlow(series, tradingRecord);
// Getting the profitable trades ratio
var profitTradesRatio = new AverageProfitableTradesCriterion();
println("Profitable trades ratio: " + profitTradesRatio.calculate(series, tradingRecord));
// Getting the reward-risk ratio
var rewardRiskRatio = new RewardRiskRatioCriterion();
println("Reward-risk ratio: " + rewardRiskRatio.calculate(series, tradingRecord));
// Total profit of our strategy
// vs total profit of a buy-and-hold strategy
var vsBuyAndHold = new VersusBuyAndHoldCriterion(new TotalProfitCriterion());
println("Our profit vs buy-and-hold profit: " + vsBuyAndHold.calculate(series, tradingRecord));
"--END--"
Number of trades for our strategy: 218
Profitable trades ratio: 0.536697247706422
Reward-risk ratio: 1.8774625448568232
Our profit vs buy-and-hold profit: 0.0019066141135999938
--END--
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.
Accounts and Paper Trader
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
Displayers.display(account.getTransactions());
active | stockID | date | quantity | requestedPrice | filledPrice | fees | comment | id | impactOnCash | buyOrSell | requestedPriceType | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
true |
|
2015-01-01 | 0 | 0 | 0 | 0 | 7217808a-02f5-4204-8c33-0ffb74e1e423 | 100000 | NA | CashTransfer | |||||||
true |
|
2015-01-05 | 100 | 0 | 102.5099 | 10 | d8590f3b-7f8c-4795-8eaa-a01e73ce0f91 | -10260.9933 | Buy | Market | |||||||
true |
|
2015-10-05 | -90 | 0 | 105.3364 | 10 | fd1bd3ec-b5fb-4a86-8521-84e9c20d9d2a | 9470.2776 | Sell | Market |
Trading Strategies
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, BuyAndHoldStrategy]
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.
Displayers.display(account.getTransactions().collect(Collectors.toList()))
active | stockID | date | quantity | requestedPrice | filledPrice | fees | comment | id | impactOnCash | buyOrSell | requestedPriceType | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
true |
|
2015-01-01 | 0 | 0 | 0 | 0 | 7217808a-02f5-4204-8c33-0ffb74e1e423 | 100000 | NA | CashTransfer | |||||||
true |
|
2015-01-05 | 100 | 0 | 102.5099 | 10 | d8590f3b-7f8c-4795-8eaa-a01e73ce0f91 | -10260.9933 | Buy | Market | |||||||
true |
|
2015-10-05 | -90 | 0 | 105.3364 | 10 | fd1bd3ec-b5fb-4a86-8521-84e9c20d9d2a | 9470.2776 | Sell | Market |
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
Displayers.display(state.result().getParameters())
Return: 56741.0
Key | Value |
---|---|
ReturnPercentAnualized | 19 |
NumberOfTrades | 5 |
SharpeRatio | 1 |
MaxDrawDownPercent | 33517 |
PurchasedValue | 92884 |
ReturnPurcentStdDev | 0 |
RealizedGains | -7066 |
NumberOfBuys | 3 |
TotalFees | 50 |
AbsoluteReturn | 56741 |
MaxDrawDownLowValue | 88757 |
MaxDrawDownNumberOfDays | 458 |
AbsoluteReturnAvaragePerDay | 74 |
ActualValue | 156741 |
MaxDrawDownHighValue | 122274 |
AbsoluteReturnStdDev | 1235 |
UnrealizedGains | 63858 |
ReturnPercent | 57 |
NumberOfTradedStocks | 1 |
Cash | 97 |
NumberOfCashTransfers | 1 |
NumberOfSells | 2 |
Displayers.display(account.getTransactions())
active | stockID | date | quantity | requestedPrice | filledPrice | fees | comment | id | impactOnCash | buyOrSell | requestedPriceType | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
true |
|
2015-01-01 | 0 | 0 | 0 | 0 | 1af70412-cea9-4c63-a3ad-5e6ba047f31b | 100000 | NA | CashTransfer | |||||||
true |
|
2015-01-12 | 966 | 0 | 103.4187 | 10 | 1e16cf8a-81fb-4115-8bff-6e65abc38749 | -99912.458 | Buy | Market | |||||||
true |
|
2015-08-17 | -966 | 0 | 112.3154 | 10 | 5bf75205-f175-4760-9ec9-6a8402bd5219 | 108486.6756 | Sell | Market | |||||||
true |
|
2016-04-15 | 1020 | 0 | 106.3323 | 10 | c4543d5d-108e-4383-8088-d723dab52bfd | -108468.9442 | Buy | Market | |||||||
true |
|
2016-05-10 | -1020 | 0 | 90.979 | 10 | 3fbc5afb-00f2-49ca-bce4-364a77cbe0b1 | 92788.5762 | Sell | Market | |||||||
true |
|
2016-08-11 | 878 | 0 | 105.6793 | 10 | 58710958-a9ac-49eb-861f-9ec8b66d5988 | -92796.3972 | Buy | Market |
// create chart for total values
var chart = new TimeSeriesChart();
chart.add(account.getTotalValueHistory(), "Total Value")
chart.add(account.getCashHistoryForAllDates(), "Cash")
chart.add(account.getActualValueHistory(), "Actual Value")
chart.addLabels(account.getTransactions())
chart
In order to get a better understanding of the development of the values over time we can
chart the Acocunt information.
Displayers.display(account.getTransactions())
active | stockID | date | quantity | requestedPrice | filledPrice | fees | comment | id | impactOnCash | buyOrSell | requestedPriceType | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
true |
|
2015-01-01 | 0 | 0 | 0 | 0 | 1af70412-cea9-4c63-a3ad-5e6ba047f31b | 100000 | NA | CashTransfer | |||||||
true |
|
2015-01-12 | 966 | 0 | 103.4187 | 10 | 1e16cf8a-81fb-4115-8bff-6e65abc38749 | -99912.458 | Buy | Market | |||||||
true |
|
2015-08-17 | -966 | 0 | 112.3154 | 10 | 5bf75205-f175-4760-9ec9-6a8402bd5219 | 108486.6756 | Sell | Market | |||||||
true |
|
2016-04-15 | 1020 | 0 | 106.3323 | 10 | c4543d5d-108e-4383-8088-d723dab52bfd | -108468.9442 | Buy | Market | |||||||
true |
|
2016-05-10 | -1020 | 0 | 90.979 | 10 | 3fbc5afb-00f2-49ca-bce4-364a77cbe0b1 | 92788.5762 | Sell | Market | |||||||
true |
|
2016-08-11 | 878 | 0 | 105.6793 | 10 | 58710958-a9ac-49eb-861f-9ec8b66d5988 | -92796.3972 | Buy | Market |
Trading Strategies Description
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)
}
Displayers.display(list)
Description | Name |
---|---|
Developed by Donald Lambert, the Commodity Channel Index (CCI) is a momentum oscillator that can be used to identify a new trend or warn of extreme conditions. This strategy uses weekly CCI to dictate the trading bias when it surges above +100 or plunges below -100, which are key levels noted by Lambert. Once the trading bias is set, daily CCI is used to generate trading signals when it reaches its extremes. Details can be found in stockcharts |
CCICorrectionStrategy |
This strategy compares the current price to the global extrema over a week. We ar going long if the close price goes below the minimum price. We are going short if the close price goes above the maximum price. | GlobalExtremaStrategy |
Many trading strategies are based on a process, not a single signal. This process often involves a series of steps that ultimately lead to a signal. Typically, chartists first establish a trading bias or long-term perspective. Second, chartists wait for pullbacks or bounces that will improve the risk-reward ratio. Third, chartists look for a reversal that indicates a subsequent upturn or downturn in price. The strategy put forth here uses moving average to define the trend, the Stochastic Oscillator to identify corrections within that trend and the MACD-Histogram to signal short-term reversals. It is a complete strategy based on a three step process.
http://stockcharts.com/school/doku.php?id=chart_school:trading_strategies:moving_momentum |
MovingMomentumStrategy |
Developed by Larry Connors, the 2-period RSI strategy is a mean-reversion trading strategy designed to buy or sell securities after a corrective period. The strategy is rather simple. Connors suggests looking for buying opportunities when 2-period RSI moves below 10, which is considered deeply oversold. Conversely, traders can look for short-selling opportunities when 2-period RSI moves above 90. This is a rather aggressive short-term strategy designed to participate in an ongoing trend. It is not designed to identify major tops or bottoms. Before looking at the details, note that this article is designed to educate chartists on possible strategies. We are not presenting a stand-alone trading strategy that can be used right out of the box. Instead, this article is meant to enhance strategy development and refinement. See http://stockcharts.com/school/doku.php?id=chart_school:trading_strategies:rsi2. |
RSI2Strategy |
We buy the title when the account is opened and hold the stock. This strategy can be used as a baseline to compare the other strategies. | BuyAndHoldStrategy |
Comparing Trading Strategies
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)));
Displayers.display(result)
PurchasedValue | SharpeRatio | RealizedGains | UnrealizedGains | LongSMAPeriod | Cash | MaxDrawDownLowValue | NumberOfTrades | NumberOfTradedStocks | EntryLimit | RSIPeriod | NumberOfCashTransfers | AbsoluteReturnAvaragePerDay | NumberOfSells | MaxDrawDownHighValue | ShortSMAPeriod | ReturnPercentAnualized | AbsoluteReturnStdDev | ExitLimit | AbsoluteReturn | NumberOfBuys | ReturnPercent | TotalFees | MaxDrawDownNumberOfDays | ActualValue | MaxDrawDownPercent | ReturnPurcentStdDev | Strategy |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
92884 | 1 | -7066 | 63858 | 200 | 97 | 88757 | 5 | 1 | 5 | 2 | 1 | 74 | 2 | 122274 | 5 | 19 | 1235 | 95 | 56741 | 3 | 57 | 50 | 458 | 156741 | 33517 | 0 | RSI2Strategy |
99990 | 1 | 0 | 72368 | 14 | 85003 | 1 | 1 | 1 | 94 | 0 | 122201 | 24 | 1615 | 72358 | 1 | 72 | 10 | 426 | 172358 | 37198 | 0 | BuyAndHoldStrategy | |||||
79404 | 1 | -20566 | 48741 | 47 | 74745 | 3 | 1 | 1 | 37 | 1 | 102875 | 9 | 1106 | 28145 | 2 | 28 | 30 | 487 | 128145 | 28130 | 0 | CCICorrectionStrategy | |||||
139839 | 1 | 40279 | 0 | 139839 | 94371 | 44 | 1 | 1 | 52 | 22 | 112952 | 13 | 1031 | 39839 | 22 | 40 | 440 | 318 | 139839 | 18582 | 0 | GlobalExtremaStrategy | |||||
105885 | 0 | 5925 | 0 | 105885 | 83121 | 4 | 1 | 1 | 8 | 2 | 106331 | 2 | 740 | 5885 | 2 | 6 | 40 | 331 | 105885 | 23210 | 0 | MovingMomentumStrategy |
Custom Trading Strategies
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, Decimal.valueOf(1.004));
val buyingRule = new UnderIndicatorRule(closePrices, downWeek);
// Going short if the close price goes above the max price
val upWeek = new MultiplierIndicator(weekMaxPrice, Decimal.valueOf(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));
Displayers.display(state.getMap());
Key | Value |
---|---|
AbsoluteReturnStdDev | 1031 |
PurchasedValue | 139839 |
SharpeRatio | 1 |
RealizedGains | 40279 |
AbsoluteReturn | 39839 |
UnrealizedGains | 0 |
NumberOfBuys | 22 |
Cash | 139839 |
ReturnPercent | 40 |
MaxDrawDownLowValue | 94371 |
TotalFees | 440 |
MaxDrawDownNumberOfDays | 318 |
NumberOfTrades | 44 |
NumberOfTradedStocks | 1 |
NumberOfCashTransfers | 1 |
ActualValue | 139839 |
MaxDrawDownPercent | 18582 |
ReturnPurcentStdDev | 0 |
AbsoluteReturnAvaragePerDay | 52 |
NumberOfSells | 22 |
MaxDrawDownHighValue | 112952 |
ReturnPercentAnualized | 13 |
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 Ta4jTimeSeries(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, Decimal.valueOf(1.004));
val buyingRule = new UnderIndicatorRule(closePrices, downWeek);
// Going short if the close price goes above the max price
val upWeek = new MultiplierIndicator(weekMaxPrice, Decimal.valueOf(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 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));
Displayers.display(state.getMap());
Key | Value |
---|---|
AbsoluteReturnStdDev | 1031 |
PurchasedValue | 139839 |
SharpeRatio | 1 |
RealizedGains | 40279 |
AbsoluteReturn | 39839 |
UnrealizedGains | 0 |
NumberOfBuys | 22 |
Cash | 139839 |
ReturnPercent | 40 |
MaxDrawDownLowValue | 94371 |
TotalFees | 440 |
MaxDrawDownNumberOfDays | 318 |
NumberOfTrades | 44 |
NumberOfTradedStocks | 1 |
NumberOfCashTransfers | 1 |
ActualValue | 139839 |
MaxDrawDownPercent | 18582 |
ReturnPurcentStdDev | 0 |
AbsoluteReturnAvaragePerDay | 52 |
NumberOfSells | 22 |
MaxDrawDownHighValue | 112952 |
ReturnPercentAnualized | 13 |
1 Comment
Daniel Ferreira Castro · 13. February 2022 at 18:14
Hi, I am very happy for your effort on this library but your Maven Repo is working oddly. Lots of Error 504 when I try to add it as a source for my maven project. Also when I go to Nexus I get the same problem fetching the resources.