Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ version = "0.3.0"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Indicators = "70c4c096-89a6-5ec6-8236-da8aa3bd86fd"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
SharedArrays = "1a1011a3-84de-559e-8e89-a11a2f7dc383"
ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Temporal = "a110ec8f-48c8-5d59-8f7e-f91bc4cc0c3d"
Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ 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)
Expand All @@ -57,7 +56,7 @@ function datasource(asset::String; save_downloads::Bool=true)::TS
return X
end
end
gather!(universe, source=datasource)
universe = gather(assets, source=datasource)

# define indicators and parameter space
arg_names = [:fastlimit, :slowlimit]
Expand All @@ -82,16 +81,16 @@ 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)
bt = backtest(strat)
opt = 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)
(x, y, z) = (opt[:,i] for i in 1:3)
surface(x, y, z)
```

Expand Down
6 changes: 3 additions & 3 deletions examples/mama.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function datasource(asset::String; save_downloads::Bool=true)::TS
return X
end
end
gather!(universe, source=datasource)
universe = gather(assets, source=datasource)

# define indicators and parameter space
arg_names = [:fastlimit, :slowlimit]
Expand All @@ -38,5 +38,5 @@ rules = (longrule, shortrule, exitrule)

# run strategy
strat = Strategy(universe, indicator, rules)
backtest!(strat)
optimize!(strat, samples=10)
bt = backtest(strat)
opt = optimize(strat, samples=10)
2 changes: 1 addition & 1 deletion src/Strategems.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module Strategems

export
# universe definitions
Universe, gather!, get_overall_index,
Universe, gather!, gather, get_overall_index,
# parameter sets
ParameterSet, count_runs, generate_combinations, generate_dict,
# indicators
Expand Down
47 changes: 34 additions & 13 deletions src/compute/backtest.jl
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
using ProgressMeter

function generate_trades(strat::Strategy; verbose::Bool=true)::Dict{String,TS}
function generate_trades(strat::Strategy; arg_values::Union{Vector, Nothing},
verbose::Bool=true)::Dict{String,TS}

if isnothing(arg_values)
arg_values = strat.indicator.paramset.arg_defaults
end

all_trades = Dict{String,TS}()
verbose ? progress = Progress(length(strat.universe.assets), 1, "Generating Trades") : nothing

for asset in strat.universe.assets
verbose ? next!(progress) : nothing
trades = TS(falses(size(strat.universe.data[asset],1), length(strat.rules)),
strat.universe.data[asset].index)
local indicator_data = calculate(strat.indicator, strat.universe.data[asset])

local indicator_data = calculate(strat.indicator, strat.universe.data[asset], arg_values=arg_values)

for (i,rule) in enumerate(strat.rules);
trades[:,i] = rule.trigger.fun(indicator_data)
end
Expand All @@ -16,21 +25,33 @@ function generate_trades(strat::Strategy; verbose::Bool=true)::Dict{String,TS}
return all_trades
end

function generate_trades!(strat::Strategy; args...)::Nothing
strat.backtest.trades = generate_trades(strat; args...)
return nothing
end
# function generate_trades!(strat::Strategy; args...)::Nothing
# strat.backtest.trades = generate_trades(strat; args...)
# return nothing
# end

#TODO: generalize this logic to incorporate order types
function backtest(strat::Strategy; px_trade::Symbol=:Open, px_close::Symbol=:Settle, verbose::Bool=true)::Dict{String,TS{Float64}}
function backtest(strat::Strategy;
arg_values::Union{Vector, Nothing}=nothing,
px_trade::Symbol=:Open,
px_close::Symbol=:Settle,
verbose::Bool=true)::Dict{String,TS{Float64}}

if isnothing(arg_values)
arg_values = convert(Vector, strat.indicator.paramset.arg_defaults)
end

if isempty(strat.backtest.trades)
generate_trades!(strat, verbose=verbose)
all_trades = generate_trades(strat, arg_values=arg_values, verbose=verbose)
else
all_trades = strat.backtest.trades
end

result = Dict{String,TS}()
verbose ? progress = Progress(length(strat.universe.assets), 1, "Running Backtest") : nothing
for asset in strat.universe.assets
verbose ? next!(progress) : nothing
trades = strat.backtest.trades[asset].values
trades = all_trades[asset].values
N = size(trades, 1)
summary_ts = strat.universe.data[asset]
trade_price = summary_ts[px_trade].values
Expand Down Expand Up @@ -60,7 +81,7 @@ function backtest(strat::Strategy; px_trade::Symbol=:Open, px_close::Symbol=:Set
return result
end

function backtest!(strat::Strategy; args...)::Nothing
strat.backtest.backtest = backtest(strat; args...)
return nothing
end
# function backtest!(strat::Strategy; args...)::Nothing
# strat.backtest.backtest = backtest(strat; args...)
# return nothing
# end
24 changes: 21 additions & 3 deletions src/compute/optimize.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Random, ProgressMeter
using SharedArrays
import Base.Threads.@threads

import Base: copy

Expand All @@ -14,16 +16,32 @@ function optimize(strat::Strategy; samples::Int=0, seed::Int=0, verbose::Bool=tr
samples = count_runs(strat.indicator.paramset)
sample_index = collect(1:samples)
end
combos = generate_combinations(strat.indicator.paramset)[sample_index,:]
optimization = zeros(samples, 1)
all_combos = generate_combinations(strat.indicator.paramset)

# select a subset of all combinations based on `sample_index`
combos = all_combos[sample_index,:]

optimization = convert(SharedArray, zeros(samples, 1))
verbose ? progress = Progress(length(sample_index), 1, "Optimizing Backtest") : nothing

@threads for i in 1:length(sample_index)
verbose ? next!(progress) : nothing
# strat.indicator.paramset.arg_defaults[:] = combos[i,:]
argv = convert(Vector, combos[i, :])
bt = backtest(strat, arg_values=argv, verbose=false; args...)
optimization[i] = summary_fun(bt)
end

#=
for (i, combo) in enumerate(sample_index)
verbose ? next!(progress) : nothing
strat.indicator.paramset.arg_defaults = combos[i,:]
generate_trades!(strat, verbose=false)
# generate_trades!(strat, verbose=false)
backtest!(strat, verbose=false; args...)
optimization[i] = summary_fun(strat.backtest)
end
=#

# prevent out-of-scope alteration of strat object
strat = original
return [combos optimization]
Expand Down
20 changes: 15 additions & 5 deletions src/model/backtest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
methods for handling backtest results of strategy objects
=#

mutable struct Backtest
struct Backtest
trades::Dict{String,TS}
backtest::Dict{String,TS{Float64}}
backtest::Dict{String,TS{Float64}} # results
optimization::Matrix{Float64}
function Backtest(trades::Dict{String,TS}=Dict{String,TS}(),
backtest::Dict{String,TS{Float64}}=Dict{String,TS{Float64}}(),
optimization::Matrix{Float64}=Matrix{Float64}(undef,0,0))
function Backtest(trades::Dict{String,TS} = Dict{String,TS}(),
backtest::Dict{String,TS{Float64}} = Dict{String,TS{Float64}}(),
optimization::Matrix{Float64} = Matrix{Float64}(undef,0,0)
)
return new(trades, backtest, optimization)
end
end
Expand All @@ -22,3 +23,12 @@ function cum_pnl(results::Backtest)::Float64
end
return result
end

function cum_pnl(results::Dict{String, TS{Float64}})::Float64
result = 0.0
@inbounds for val in values(results)
pnl::Vector = val[:PNL].values[:]
result += sum(pnl)
end
return result
end
31 changes: 17 additions & 14 deletions src/model/indicator.jl
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@

import Base: show

mutable struct Indicator
struct Indicator
fun::Function
paramset::ParameterSet
data::TS
function Indicator(fun::Function, paramset::ParameterSet)
data = TS()
function Indicator(fun::Function, paramset::ParameterSet, data::TS)
return new(fun, paramset, data)
end
end

function calculate(indicator::Indicator, input::TS)::TS
return indicator.fun(input; generate_dict(indicator.paramset)...)
function Indicator(fun::Function, paramset::ParameterSet)
return Indicator(fun, paramset, TS())
end

function calculate(indicator::Indicator, input::TS; arg_values::Vector)::TS
return indicator.fun(input; generate_dict(indicator.paramset, arg_values)...)
end

# function calculate!(indicator::Indicator, input::TS)::Nothing
# indicator.data = calculate(indicator, input)
# return nothing
# end

function generate_dict(universe::Universe, indicator::Indicator)::Dict{String,Indicator}
indicators = Dict{String,Indicator}()
for asset in universe.assets
local ind = Indicator(indicator.fun, indicator.paramset)
calculate!(ind, universe.data[asset])
indicators[asset] = ind
end
return indicators
end
# function generate_dict(universe::Universe, indicator::Indicator)::Dict{String,Indicator}
# indicators = Dict{String,Indicator}()
# for asset in universe.assets
# local ind = Indicator(indicator.fun, indicator.paramset)
# calculate!(ind, universe.data[asset])
# indicators[asset] = ind
# end
# return indicators
# end

# TODO: add information about the calculation function
function show(io::IO, indicator::Indicator)
Expand Down
4 changes: 2 additions & 2 deletions src/model/paramset.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Base.show

mutable struct ParameterSet
struct ParameterSet
arg_names::Vector{Symbol}
arg_defaults::Vector
arg_ranges::Vector
Expand All @@ -26,7 +26,7 @@ function count_runs(ps::ParameterSet)::Int
return n_runs
end

function generate_dict(ps::ParameterSet; arg_values::Vector=ps.arg_defaults)::Dict{Symbol,Any}
function generate_dict(ps::ParameterSet, arg_values::Vector)::Dict{Symbol,Any}
out_dict = Dict{Symbol,Any}()
for j in 1:ps.n_args
out_dict[ps.arg_names[j]] = arg_values[j]
Expand Down
2 changes: 1 addition & 1 deletion src/model/portfolio.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
type and methods to track the evolution of a strategy's securities portfolio composition
=#

mutable struct Portfolio
struct Portfolio
quantity::Matrix{Float64}
weight::Matrix{Float64}
entry_price::Matrix{Float64}
Expand Down
2 changes: 1 addition & 1 deletion src/model/strategy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Base: show
const TABWIDTH = 4
const TAB = ' ' ^ TABWIDTH

mutable struct Strategy
struct Strategy
universe::Universe
indicator::Indicator
rules::Tuple{Vararg{Rule}}
Expand Down
33 changes: 29 additions & 4 deletions src/model/universe.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,29 @@ const SEPARATORS = ['/', '_', '.']
# return tickers
# end

mutable struct Universe
struct Universe
assets::Vector{String}
# tickers::Vector{Symbol}
data::Dict{String,TS}
from::TimeType
thru::TimeType
function Universe(assets::Vector{String}, from::TimeType=Dates.Date(0), thru::TimeType=Dates.today())
function Universe(assets::Vector{String}, data::Dict{String, TS}, from::TimeType=Dates.Date(0), thru::TimeType=Dates.today())
@assert assets == unique(assets)
# tickers = guess_tickers(assets)
return new(assets, data, from, thru)
end
end

function Universe(assets::Vector{String}, from::TimeType = Dates.Date(0), thru::TimeType=Dates.today())
@assert assets == unique(assets)
# tickers = guess_tickers(assets)

data = Dict{String,TS}()
@inbounds for asset in assets
data[asset] = TS()
end
return new(assets, data, from, thru)
end

Universe(assets, data, from, thru)
end

#TODO: ensure type compatibility across variables (specifically with regard to TimeTypes)
Expand All @@ -48,6 +56,23 @@ function gather!(universe::Universe; source::Function=Temporal.quandl, verbose::
return nothing
end

function gather(assets::Vector{String}; source::Function=Temporal.quandl, verbose::Bool=true)::Universe
t0 = Vector{Dates.Date}()
tN = Vector{Dates.Date}()
data = Dict{String,TS}()
verbose ? progress = Progress(length(assets), 1, "Gathering Universe Data") : nothing
@inbounds for asset in assets
verbose ? next!(progress) : nothing
indata = source(asset)
push!(t0, indata.index[1])
push!(tN, indata.index[end])
data[asset] = indata
end
from = minimum(t0)
thru = maximum(tN)
Universe(assets, data, from, thru)
end

#FIXME: make robust to other time types
function get_overall_index(universe::Universe)::Vector{Date}
idx = Vector{Date}()
Expand Down
Loading