Idea Generation
Another, perhaps more standard, way to try and come up with new strategies before implementing them as a system is to manipulate data in TimeArrays
. Due to the way that the BackTester
tries to mimic a true trading situation, it sacrifices a bit of outright speed. This may be fine in most cases, but for true big data it is often faster to work with a TimeArray
. Most of this functionality is present in the brilliant TimeSeries and MarketTechnicals packages.
We here discuss the slow fast again from this point of view.
Slow Fast with TimeArrays
As usual, we define a Broker
, and proceed with acquiring the historical data with bars
.
using MarketTechnicals
broker = AlpacaBroker(ENV["ALPACA_KEY_ID"], ENV["ALPACA_SECRET"])
start_day = DateTime("2015-01-01T00:00:00")
stop_day = DateTime("2020-01-01T00:00:00")
full_bars = bars(broker, Stock("AAPL"), start_day, stop_day, timeframe=Day(1))
df = rename(merge(full_bars[:c], full_bars[:o]), [:AAPL_Close, :AAPL_Open])
1028×2 TimeArray{Float64, 2, DateTime, Matrix{Float64}} 2015-12-01T06:00:00 to 2019-12-31T06:00:00
│ │ AAPL_Close │ AAPL_Open │
├─────────────────────┼────────────┼───────────┤
│ 2015-12-01T06:00:00 │ 117.34 │ 118.75 │
│ 2015-12-02T06:00:00 │ 116.28 │ 117.05 │
│ 2015-12-03T06:00:00 │ 115.2 │ 116.55 │
│ 2015-12-04T06:00:00 │ 119.03 │ 115.29 │
│ 2015-12-07T06:00:00 │ 118.28 │ 118.98 │
│ 2015-12-08T06:00:00 │ 118.23 │ 117.52 │
│ 2015-12-09T06:00:00 │ 115.62 │ 117.64 │
│ 2015-12-10T06:00:00 │ 116.17 │ 116.04 │
│ 2015-12-11T06:00:00 │ 113.18 │ 115.0 │
⋮
│ 2019-12-19T06:00:00 │ 280.02 │ 279.5 │
│ 2019-12-20T06:00:00 │ 279.44 │ 282.23 │
│ 2019-12-23T06:00:00 │ 284.0 │ 280.53 │
│ 2019-12-24T06:00:00 │ 284.27 │ 284.69 │
│ 2019-12-26T06:00:00 │ 289.91 │ 284.82 │
│ 2019-12-27T06:00:00 │ 289.8 │ 291.12 │
│ 2019-12-30T06:00:00 │ 291.52 │ 289.46 │
│ 2019-12-31T06:00:00 │ 293.65 │ 289.93 │
Next, we calculate the two moving averages that we will use in our strategy:
sma_ta = merge(sma(df, 20), sma(df, 120))
909×4 TimeArray{Float64, 2, DateTime, Matrix{Float64}} 2016-05-23T06:00:00 to 2019-12-31T06:00:00
│ │ AAPL_Close_sma_20 │ AAPL_Open_sma_20 │
├─────────────────────┼───────────────────┼──────────────────┤
│ 2016-05-23T06:00:00 │ 94.3535 │ 94.5498 │
│ 2016-05-24T06:00:00 │ 94.031 │ 94.2148 │
│ 2016-05-25T06:00:00 │ 94.121 │ 94.3428 │
│ 2016-05-26T06:00:00 │ 94.4 │ 94.4472 │
│ 2016-05-27T06:00:00 │ 94.7305 │ 94.7198 │
│ 2016-05-31T06:00:00 │ 95.0415 │ 95.0015 │
│ 2016-06-01T06:00:00 │ 95.2055 │ 95.2425 │
│ 2016-06-02T06:00:00 │ 95.382 │ 95.3625 │
│ 2016-06-03T06:00:00 │ 95.616 │ 95.552 │
⋮
│ 2019-12-19T06:00:00 │ 269.1995 │ 268.4275 │
│ 2019-12-20T06:00:00 │ 270.071 │ 269.3545 │
│ 2019-12-23T06:00:00 │ 271.182 │ 270.2515 │
│ 2019-12-24T06:00:00 │ 272.077 │ 271.3505 │
│ 2019-12-26T06:00:00 │ 273.358 │ 272.2445 │
│ 2019-12-27T06:00:00 │ 274.456 │ 273.5215 │
│ 2019-12-30T06:00:00 │ 275.6695 │ 274.6645 │
│ 2019-12-31T06:00:00 │ 277.144 │ 275.7975 │
│ │ AAPL_Close_sma_120 │ AAPL_Open_sma_120 │
├─────────────────────┼────────────────────┼───────────────────┤
│ 2016-05-23T06:00:00 │ 102.29 │ 102.3774 │
│ 2016-05-24T06:00:00 │ 102.128 │ 102.1979 │
│ 2016-05-25T06:00:00 │ 101.9892 │ 102.0438 │
│ 2016-05-26T06:00:00 │ 101.8659 │ 101.9032 │
│ 2016-05-27T06:00:00 │ 101.7102 │ 101.7711 │
│ 2016-05-31T06:00:00 │ 101.5568 │ 101.6096 │
│ 2016-06-01T06:00:00 │ 101.392 │ 101.4555 │
│ 2016-06-02T06:00:00 │ 101.2428 │ 101.2885 │
│ 2016-06-03T06:00:00 │ 101.0908 │ 101.1364 │
⋮
│ 2019-12-19T06:00:00 │ 230.249 │ 229.9667 │
│ 2019-12-20T06:00:00 │ 230.8883 │ 230.6402 │
│ 2019-12-23T06:00:00 │ 231.5515 │ 231.284 │
│ 2019-12-24T06:00:00 │ 232.2185 │ 231.9618 │
│ 2019-12-26T06:00:00 │ 232.9676 │ 232.6619 │
│ 2019-12-27T06:00:00 │ 233.7056 │ 233.4279 │
│ 2019-12-30T06:00:00 │ 234.4414 │ 234.158 │
│ 2019-12-31T06:00:00 │ 235.2072 │ 234.8798 │
To find crossovers we do:
diffs = rename(sma_ta[:AAPL_Close_sma_20] .- sma_ta[:AAPL_Close_sma_120], :diff)
signal = TimeArray(timestamp(diffs), zeros(length(diffs)), [:signal])
diffs = merge(diffs, rename(sign.(diffs), :sign), rename(sign.(lag(diffs,1)), :lagged_sign), signal)
diffs = map(diffs) do timestamp, vals
if vals[2] != vals[3]
if vals[1] < 0
vals[4] = 1
elseif vals[1] > 0
vals[4] = -1
end
end
return timestamp, vals
end
diffs[diffs[:signal] .!= 0]
7×4 TimeArray{Float64, 2, DateTime, Matrix{Float64}} 2016-08-03T06:00:00 to 2019-03-26T05:00:00
│ │ diff │ sign │ lagged_sign │ signal │
├─────────────────────┼─────────┼───────┼─────────────┼────────┤
│ 2016-08-03T06:00:00 │ 0.0346 │ 1.0 │ -1.0 │ -1.0 │
│ 2018-02-21T06:00:00 │ -0.3429 │ -1.0 │ 1.0 │ 1.0 │
│ 2018-02-27T06:00:00 │ 0.2592 │ 1.0 │ -1.0 │ -1.0 │
│ 2018-04-13T06:00:00 │ -0.2369 │ -1.0 │ 1.0 │ 1.0 │
│ 2018-05-07T06:00:00 │ 0.0309 │ 1.0 │ -1.0 │ -1.0 │
│ 2018-11-19T06:00:00 │ -0.3642 │ -1.0 │ 1.0 │ 1.0 │
│ 2019-03-26T05:00:00 │ 0.0712 │ 1.0 │ -1.0 │ -1.0 │
Then we fill in our positions and cash balance, and calculate the total position value:
signal = lag(diffs[:signal], 1) # because we buy at open next period
position = rename(cumsum(signal), :position)
position_value = rename(df[:AAPL_Close] .* position, :position_value)
cash = rename(cumsum(signal .* -1 .* df[:AAPL_Open]), :cash)
total = rename(cash .+ position_value, :total)
df = merge(df, position_value, cash, total)
plot([df[:AAPL_Close] df[:total]])
We find similar horrible results as in slow fast before.