Strategems is a Julia package aimed at simplifying and streamlining the process of developing, testing, and optimizing algorithmic/systematic trading strategies. This package is inspired in large part by the quantstrat1,2 package in R, adopting a similar general structure to the building blocks that make up a strategy.
Given the highly iterative nature of event-driven trading strategy development, Julia's high-performance design (particularly in the context of loops) and straightforward syntax would seem to make it a natural fit as a language for systematic strategy research and development. While this package remains early in development, with time the hope is to be able to rapidly implement a trading idea, construct a historical backtest, analyze its results, optimize over a given parameter set, and visualize all of this with great detail.
This package makes heavy use of the Temporal package's TS
time series type to facilitate the underlying computations involved in cleaning & preprocessing the data used when testing a Strategy
. Additionally, the Indicators package offers many technical analysis functions that have been written/designed with the goal of a highly generalized systematic trading strategy research engine in mind, and should thus should simplify the process of working with this data quite a bit.
The Strategems package can be installed using the standard Julia package manager functions.
# Option A:
Pkg.add("Strategems")
# Option B:
Pkg.clone("https://github.com/dysonance/Strategems.jl")
Below are the basic building blocks making up the general anatomy of a Strategy with respect to the Strategems.jl
package design and the type definitions used to facilitate the research workflow.
Universe
: encapsulation of the assets/securities the strategy is to be allowed to tradeIndicator
: calculation done on each asset in the universe whose results we think have predictive potential for future price movementParameterSet
: inputs/arguments to the indicator calculationsSignal
: boolean flag sending messages to the trading logic/rules to be interpreted and acted uponRule
: applications of trading logic derived from interpretations of prior calculations & signals at each time stepStrategy
: overarching object encapsulating and directing all of the above logic and data to power the backtesting engine
Below is a quick example demonstrating a simple use-case that one might use to get acquainted with how the package works. Note that the custom infix operators denoted by the uparrow and downarrow below are defined in this package as another way of expressing that one variable crosses over another. The intention of this infix operator definition is to hopefully make the definition of a strategy more syntactically expressive and intuitive.
The key indicator used in this strategy is John Ehlers's MESA Adaptive Moving Average (or MAMA for short). This functionality is implemented in the Indicators.jl
package described above, and outputs a Matrix
(or TS
object if one is passed as an input) of two columns, the first being the MAMA itself and the second being the FAMA, or following adaptive moving average.
This strategy simply goes long when the MAMA crosses over the FAMA, and goes short when the FAMA crosses over the MAMA. Below is an implementation that shows how to set default arguments to the Indicators.mama
function and run a simple backtest using those parameters, and also define specified ranges over which we might like to see how the strategy behaves under different parameter sets.
using Strategems, Indicators, Temporal, Dates
# define universe and gather data
assets = ["CHRIS/CME_CL1", "CHRIS/CME_RB1"]
universe = Universe(assets)
function datasource(asset::String; save_downloads::Bool=true)::TS
savedata_path = joinpath(dirname(pathof(Strategems)), "..", "data", "$asset.csv")
if isfile(savedata_path)
return Temporal.tsread(savedata_path)
else
X = quandl(asset)
if save_downloads
if !isdir(dirname(savedata_path))
mkdir(dirname(savedata_path))
end
Temporal.tswrite(X, savedata_path)
end
return X
end
end
gather!(universe, source=datasource)
# define indicators and parameter space
arg_names = [:fastlimit, :slowlimit]
arg_defaults = [0.5, 0.05]
arg_ranges = [0.01:0.01:0.99, 0.01:0.01:0.99]
paramset = ParameterSet(arg_names, arg_defaults, arg_ranges)
f(x; args...) = Indicators.mama(x; args...)
indicator = Indicator(f, paramset)
# define signals that will trigger trading decisions
# note the uparrow infix operator is defined to simplify one variable crossing over another
# (similarly for the downarrow infix operator for crossing under)
siglong = @signal MAMA ↑ FAMA
sigshort = @signal MAMA ↓ FAMA
sigexit = @signal MAMA == FAMA
# define the trading rules
longrule = @rule siglong → long 100
shortrule = @rule sigshort → short 100
exitrule = @rule sigexit → liquidate 1.0
rules = (longrule, shortrule, exitrule)
# run strategy
strat = Strategy(universe, indicator, rules)
backtest!(strat)
optimize!(strat, samples=0) # randomly sample the parameter space (0 -> use all combinations)
# cumulative pnl for each combination of the parameter space
strat.backtest.optimization
# visualizing results with the Plots.jl package
using Plots
gr()
(x, y, z) = (strat.backtest.optimization[:,i] for i in 1:3)
surface(x, y, z)
- Get a sufficiently full-featured type system established to facilitate easy construction of simple strategies
- Allow more intelligent logic for trading rules
- Adjust order sizing based on portfolio/account at time t
- Portfolio optimization logic
- Risk limits
- Stop loss rules
- Define a more diverse set of order types
- Limit orders
- Stop orders