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))