Slow Fast Moving Average

using Trading
using Trading.Strategies
using Trading.Basic
using Trading.Indicators
using Trading.Portfolio

struct SlowFast <: System end
Overseer.requested_components(::SlowFast) = (SMA{50, Close}, SMA{200, Close})

function Overseer.update(s::SlowFast, t::Trader, asset_ledgers)
    for asset_ledger in asset_ledgers
        asset = asset_ledger.asset
        for e in new_entities(asset_ledger, s)
            prev_e = prev(e, 1)

            if prev_e === nothing
                continue
            end

            sma_50  = e[SMA{50, Close}].sma
            sma_200 = e[SMA{200, Close}].sma

            prev_sma_50 = prev_e[SMA{50, Close}].sma
            prev_sma_200 = prev_e[SMA{200, Close}].sma

            if sma_50 > sma_200 && prev_sma_50 < prev_sma_200
                Entity(t, Sale(asset, 1.0))
            elseif sma_50 < sma_200 && prev_sma_50 > prev_sma_200
                Entity(t, Purchase(asset, 1.0))
            end
        end
    end
end

The Inf values for the quantity of stocks to trade in the Sale and Purchase constructors signifies that we want to buy as many stocks as our cash balance allows for.

broker = HistoricalBroker(AlpacaBroker(ENV["ALPACA_KEY_ID"], ENV["ALPACA_SECRET"]))

strategy = Strategy(:slowfast, [SlowFast()], assets=[Stock("MSFT"), Stock("AAPL")])

trader = BackTester(broker, start = DateTime("2015-01-01T00:00:00"),
                            stop = DateTime("2020-01-01T00:00:00"),
                            dt = Day(1),
                            strategies = [strategy],
                            cash = 1000,
                            only_day=false)
start(trader)
Trader

Main task:    Task (done) @0x00007fcb5cd2d2d0
Trading task: Task (done) @0x00007fcb5cd2ce20
Data tasks:   Dict{Trading.AssetType.T, Task}(Trading.AssetType.Stock => Task (done) @0x00007fcb5cd2d140)

Portfolio -- positions: -451.34999999999997, cash: 1209.7231333333334, tot: 758.3731333333335

Current positions:
┌────────┬──────────┬─────────┐
│ Ticker │ Quantity │   Value │
├────────┼──────────┼─────────┤
│   MSFT │     -1.0 │  -157.7 │
│   AAPL │     -1.0 │ -293.65 │
└────────┴──────────┴─────────┘

Strategies:

Trades:
┌─────────────────────┬────────┬──────┬──────────┬───────────┬───────────┐
│                Time │ Ticker │ Side │ Quantity │ Avg Price │ Tot Price │
├─────────────────────┼────────┼──────┼──────────┼───────────┼───────────┤
│ 2019-04-10T06:00:00 │   AAPL │ sell │      1.0 │    198.68 │    198.68 │
│ 2019-03-06T06:00:00 │   MSFT │ sell │      1.0 │    111.87 │    111.87 │
│ 2018-12-19T06:00:00 │   MSFT │  buy │      1.0 │    103.65 │    103.65 │
│ 2018-11-29T06:00:00 │   AAPL │  buy │      1.0 │    182.66 │    182.66 │
│ 2018-05-10T06:00:00 │   AAPL │ sell │      1.0 │    187.74 │    187.74 │
│ 2018-05-01T06:00:00 │   AAPL │  buy │      1.0 │    166.41 │    166.41 │
│ 2016-08-12T06:00:00 │   AAPL │ sell │      1.0 │    107.78 │    107.78 │
│ 2016-07-30T06:00:00 │   MSFT │ sell │      1.0 │   56.3733 │   56.3733 │
└─────────────────────┴────────┴──────┴──────────┴───────────┴───────────┘

After having executed the strategy, we can see some quick overview from the output, but by converting it to a TimeArray we can more easily analyse how the strategy performed

using Plots

ta = TimeArray(trader)

plot(ta[:portfolio_value])

We see that in this case the strategy didn't work particularly well. In fact it seems that inverting it, we might get a better result. We can simply redefine our update function as follows:

function Overseer.update(s::SlowFast, t::Trader, asset_ledgers)
    for asset_ledger in asset_ledgers
        asset = asset_ledger.asset
        for e in new_entities(asset_ledger, s)
            prev_e = prev(e, 1)

            if prev_e === nothing
                continue
            end

            sma_50  = e[SMA{50, Close}].sma
            sma_200 = e[SMA{200, Close}].sma

            prev_sma_50 = prev_e[SMA{50, Close}].sma
            prev_sma_200 = prev_e[SMA{200, Close}].sma

            if sma_50 > sma_200 && prev_sma_50 < prev_sma_200
                Entity(t, Purchase(asset, Inf))
            elseif sma_50 < sma_200 && prev_sma_50 > prev_sma_200
                Entity(t, Sale(asset, Inf))
            end
        end
    end
end

We have basically swapped the Purchase and Sale components. To execute this updated version we call reset! and start again.

reset!(trader)
start(trader)
Trader

Main task:    Task (done) @0x00007fcb53085aa0
Trading task: Task (done) @0x00007fcb53085140
Data tasks:   Dict{Trading.AssetType.T, Task}(Trading.AssetType.Stock => Task (done) @0x00007fcb53085910)

Portfolio -- positions: NaN, cash: NaN, tot: NaN

Current positions:
┌────────┬──────────┬───────┐
│ Ticker │ Quantity │ Value │
├────────┼──────────┼───────┤
│   MSFT │      NaN │   NaN │
│   AAPL │      NaN │   NaN │
└────────┴──────────┴───────┘

Strategies:

Trades:
┌─────────────────────┬────────┬──────┬──────────┬───────────┬───────────┐
│                Time │ Ticker │ Side │ Quantity │ Avg Price │ Tot Price │
├─────────────────────┼────────┼──────┼──────────┼───────────┼───────────┤
│ 2019-04-10T06:00:00 │   AAPL │  buy │      Inf │    198.68 │       Inf │
│ 2019-03-06T06:00:00 │   MSFT │  buy │      Inf │    111.87 │       Inf │
│ 2018-12-19T06:00:00 │   MSFT │ sell │      Inf │    103.65 │       Inf │
│ 2018-11-29T06:00:00 │   AAPL │ sell │      Inf │    182.66 │       Inf │
│ 2018-05-10T06:00:00 │   AAPL │  buy │      Inf │    187.74 │       Inf │
│ 2018-05-01T06:00:00 │   AAPL │ sell │      Inf │    166.41 │       Inf │
│ 2016-08-12T06:00:00 │   AAPL │  buy │      Inf │    107.78 │       Inf │
│ 2016-07-30T06:00:00 │   MSFT │  buy │      Inf │   56.3733 │       Inf │
└─────────────────────┴────────┴──────┴──────────┴───────────┴───────────┘

and plot the results again, this time taking the relative performances of the portfolio vs the two stocks:

ta = Trading.relative(TimeArray(trader))

portfolio_val = ta[:portfolio_value]
aapl_closes = ta[:AAPL_Close]
msft_closes = ta[:MSFT_Close]

p = plot(merge(portfolio_val, aapl_closes, msft_closes))