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.