Tutorial
Trading.Strategy
— TypeStrategy(name::Symbol, systems::Vector{System}; only_day = false, assets = Asset[])
A strategy embodies a set of Systems
that will run periodically, where each of the Systems
should have a defined update(s::System, trader, asset_ledgers)
function, with asset_ledgers
being the AssetLedgers
associated with each of the assets
that the strategy should be applied on.
The last AssetLedger
in asset_ledgers
is a "combined" ledger which can store data shared between all assets
for this strategy.
only_day
: whether this strategy should only run during a trading day
There are three main parts that need to be implemented for a Strategy
to be used:
- a
System
- the
Overseer.update
function - the
Overseer.requested_components
function
This latter one will be used to determine which Indicator
systems need to be executed on the data inside each AssetLedger
in order to produce the Indicators
that are used by the Strategy
.
Strategy Definition
As an example we will implement a very simple slow/fast moving average strategy, i.e. SlowFast
. The goal is that we can later use it in our Trader
in to following way:
trader = Trader(broker; strategies = [Strategy(:slowfast, [SlowFast()], assets=[Stock("stock1")])])
We begin by defining the SlowFast
System
and the components that it requests to be present in AssetLedgers
. They will be automatically created as tick data arrives.
struct SlowFast <: System end
Overseer.requested_components(::SlowFast) = (SMA{50, Close}, SMA{200, Close})
We here request the slow and fast sma components of the closing price (SMA{200, Trading.Close}
, SMA{50, Trading.Close}
).
We then implement the following update
function that will be executed periodically:
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
Let's go through this line by line:
for asset_ledger in asset_ledgers
We loop through each of the asset ledgers that this strategy was created for (i.e. stock1
, stock2
).
for e in new_entities(asset_ledger, s)
Then, we ask for the new_entities
in the AssetLedger
that have in this case both the SMA{200, Close}
and SMA{50,Close}
components. Each of these entities will be touched once and only once.
prev_e = prev(e, 1)
Since we are looking for crossings between the two moving averages, we ask for the entity of the previous time. If there was none, i.e. e
is the very first entity, prev(e, 1)
will return nothing
and so we don't do anything.
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
We retrieve the sma's for both the current and previous entity
.
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
If the fast sma crosses above the slow sma, we assume the stock is overbought and we sell it by creating an Entity
with a Sale
component. Vice versa, If the fast sma crosses below the slow sma, we assume the stock is oversold and we buy it by creating an Entity
with a Purchase
component.
BackTesting
The framework is set up to treat backtesting and realtime trading in completely identical ways, and we can therefore backtest our strategy on some historical data.
We first define the broker from which to pull the historical data, in this case we use AlpacaBroker
with our key_id
and secret
. We then use it in the HistoricalBroker
which supplies data in the same way of a realtime broker would.
We then set up the strategy for the MSFT
and AAPL
assets, define our BackTester
with our data range and interval dt
.
When using daily data (e.g. dt=Day(1)
), it is important to specify only_day=false
, otherwise nothing will happen since our strategy will only run during trading hours, and no daily bars will have a timestamp inside those hours.
Finally we use start
to loop through all the days and execute the strategy, possible trades, and any other behavior as if it is realtime.
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 (failed) @0x00007fcb5e60b530
Trading task: Task (done) @0x00007fcb5e60b210
Data tasks: Dict{Trading.AssetType.T, Task}(Trading.AssetType.Stock => Task (done) @0x00007fcb5e60b3a0)
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 │
└─────────────────────┴────────┴──────┴──────────┴───────────┴───────────┘
To perform further analysis we can transform the trader
data into a standard TimeArray
as:
ta = TimeArray(trader)
1493×17 TimeArray{Float64, 2, DateTime, Matrix{Float64}} 2015-12-01T06:00:00 to 2020-01-01T06:00:00
│ │ MSFT_position │ portfolio_value │ AAPL_position │
├─────────────────────┼───────────────┼─────────────────┼───────────────┤
│ 2015-12-01T06:00:00 │ NaN │ 1000.0 │ NaN │
│ 2015-12-02T06:00:00 │ NaN │ 1000.0 │ NaN │
│ 2015-12-03T06:00:00 │ NaN │ 1000.0 │ NaN │
│ 2015-12-04T06:00:00 │ NaN │ 1000.0 │ NaN │
│ 2015-12-05T06:00:00 │ NaN │ 1000.0 │ NaN │
│ 2015-12-06T06:00:00 │ NaN │ 1000.0 │ NaN │
│ 2015-12-07T06:00:00 │ NaN │ 1000.0 │ NaN │
│ 2015-12-08T06:00:00 │ NaN │ 1000.0 │ NaN │
│ 2015-12-09T06:00:00 │ NaN │ 1000.0 │ NaN │
⋮
│ 2019-12-25T06:00:00 │ -1.0 │ 767.4481 │ -1.0 │
│ 2019-12-26T06:00:00 │ -1.0 │ 767.3431 │ -1.0 │
│ 2019-12-27T06:00:00 │ -1.0 │ 759.1531 │ -1.0 │
│ 2019-12-28T06:00:00 │ -1.0 │ 759.861 │ -1.0 │
│ 2019-12-29T06:00:00 │ -1.0 │ 760.5688 │ -1.0 │
│ 2019-12-30T06:00:00 │ -1.0 │ 761.2766 │ -1.0 │
│ 2019-12-31T06:00:00 │ -1.0 │ 763.0231 │ -1.0 │
│ 2020-01-01T06:00:00 │ -1.0 │ 758.3731 │ -1.0 │
│ │ MSFT_Volume │ MSFT_Close │ MSFT_Low │ MSFT_Open │
├─────────────────────┼─────────────┼────────────┼──────────┼───────────┤
│ 2015-12-01T06:00:00 │ 3.9952779e7 │ 55.22 │ 54.3 │ 54.41 │
│ 2015-12-02T06:00:00 │ 4.7274879e7 │ 55.21 │ 55.06 │ 55.32 │
│ 2015-12-03T06:00:00 │ 3.8627835e7 │ 54.2 │ 53.93 │ 55.49 │
│ 2015-12-04T06:00:00 │ 4.3963662e7 │ 55.91 │ 54.1 │ 54.12 │
│ 2015-12-05T06:00:00 │ 3.9545696e7 │ 55.8767 │ 54.4967 │ 54.6767 │
│ 2015-12-06T06:00:00 │ 3.5127731e7 │ 55.8433 │ 54.8933 │ 55.2333 │
│ 2015-12-07T06:00:00 │ 3.0709765e7 │ 55.81 │ 55.29 │ 55.79 │
│ 2015-12-08T06:00:00 │ 3.2878026e7 │ 55.79 │ 54.99 │ 55.47 │
│ 2015-12-09T06:00:00 │ 3.6353909e7 │ 54.98 │ 54.51 │ 55.37 │
⋮
│ 2019-12-25T06:00:00 │ 1.1758237e7 │ 158.025 │ 157.2575 │ 157.52 │
│ 2019-12-26T06:00:00 │ 1.452729e7 │ 158.67 │ 157.4 │ 157.56 │
│ 2019-12-27T06:00:00 │ 1.8414627e7 │ 158.96 │ 158.22 │ 159.45 │
│ 2019-12-28T06:00:00 │ 1.7728742e7 │ 158.5033 │ 157.7233 │ 159.2955 │
│ 2019-12-29T06:00:00 │ 1.7042856e7 │ 158.0467 │ 157.2267 │ 159.141 │
│ 2019-12-30T06:00:00 │ 1.6356971e7 │ 157.59 │ 156.73 │ 158.9865 │
│ 2019-12-31T06:00:00 │ 1.8393419e7 │ 157.7 │ 156.45 │ 156.77 │
│ 2020-01-01T06:00:00 │ NaN │ NaN │ NaN │ NaN │
│ │ MSFT_SMA_50_Close │ MSFT_High │ MSFT_SMA_200_Close │
├─────────────────────┼───────────────────┼───────────┼────────────────────┤
│ 2015-12-01T06:00:00 │ NaN │ 55.23 │ NaN │
│ 2015-12-02T06:00:00 │ NaN │ 55.96 │ NaN │
│ 2015-12-03T06:00:00 │ NaN │ 55.77 │ NaN │
│ 2015-12-04T06:00:00 │ NaN │ 56.23 │ NaN │
│ 2015-12-05T06:00:00 │ NaN │ 56.1433 │ NaN │
│ 2015-12-06T06:00:00 │ NaN │ 56.0567 │ NaN │
│ 2015-12-07T06:00:00 │ NaN │ 55.97 │ NaN │
│ 2015-12-08T06:00:00 │ NaN │ 56.1 │ NaN │
│ 2015-12-09T06:00:00 │ NaN │ 55.87 │ NaN │
⋮
│ 2019-12-25T06:00:00 │ 151.3205 │ 158.22 │ 141.0311 │
│ 2019-12-26T06:00:00 │ 151.6127 │ 158.73 │ 141.1634 │
│ 2019-12-27T06:00:00 │ 151.9067 │ 159.55 │ 141.2952 │
│ 2019-12-28T06:00:00 │ 152.1576 │ 159.3733 │ 141.4272 │
│ 2019-12-29T06:00:00 │ 152.3983 │ 159.1967 │ 141.56 │
│ 2019-12-30T06:00:00 │ 152.6289 │ 159.02 │ 141.6864 │
│ 2019-12-31T06:00:00 │ 152.8607 │ 157.77 │ 141.8126 │
│ 2020-01-01T06:00:00 │ NaN │ NaN │ NaN │
│ │ AAPL_Volume │ AAPL_Close │ AAPL_Low │ AAPL_Open │
├─────────────────────┼─────────────┼────────────┼──────────┼───────────┤
│ 2015-12-01T06:00:00 │ 3.4852374e7 │ 117.34 │ 116.86 │ 118.75 │
│ 2015-12-02T06:00:00 │ 3.3385643e7 │ 116.28 │ 116.08 │ 117.05 │
│ 2015-12-03T06:00:00 │ 4.1560785e7 │ 115.2 │ 114.22 │ 116.55 │
│ 2015-12-04T06:00:00 │ 5.7776977e7 │ 119.03 │ 115.11 │ 115.29 │
│ 2015-12-05T06:00:00 │ 4.9211569e7 │ 118.78 │ 116.01 │ 116.52 │
│ 2015-12-06T06:00:00 │ 4.0646162e7 │ 118.53 │ 116.91 │ 117.75 │
│ 2015-12-07T06:00:00 │ 3.2080754e7 │ 118.28 │ 117.81 │ 118.98 │
│ 2015-12-08T06:00:00 │ 3.430945e7 │ 118.23 │ 116.86 │ 117.52 │
│ 2015-12-09T06:00:00 │ 4.5852027e7 │ 115.62 │ 115.08 │ 117.64 │
⋮
│ 2019-12-25T06:00:00 │ 1.7727679e7 │ 287.09 │ 283.8098 │ 284.755 │
│ 2019-12-26T06:00:00 │ 2.3335464e7 │ 289.91 │ 284.7 │ 284.82 │
│ 2019-12-27T06:00:00 │ 3.6593433e7 │ 289.8 │ 288.12 │ 291.12 │
│ 2019-12-28T06:00:00 │ 3.654568e7 │ 290.3733 │ 287.1533 │ 290.5667 │
│ 2019-12-29T06:00:00 │ 3.6497926e7 │ 290.9467 │ 286.1867 │ 290.0133 │
│ 2019-12-30T06:00:00 │ 3.6450173e7 │ 291.52 │ 285.22 │ 289.46 │
│ 2019-12-31T06:00:00 │ 2.558906e7 │ 293.65 │ 289.52 │ 289.93 │
│ 2020-01-01T06:00:00 │ NaN │ NaN │ NaN │ NaN │
│ │ AAPL_SMA_50_Close │ AAPL_High │ AAPL_SMA_200_Close │
├─────────────────────┼───────────────────┼───────────┼────────────────────┤
│ 2015-12-01T06:00:00 │ NaN │ 118.81 │ NaN │
│ 2015-12-02T06:00:00 │ NaN │ 118.11 │ NaN │
│ 2015-12-03T06:00:00 │ NaN │ 116.79 │ NaN │
│ 2015-12-04T06:00:00 │ NaN │ 119.25 │ NaN │
│ 2015-12-05T06:00:00 │ NaN │ 119.4533 │ NaN │
│ 2015-12-06T06:00:00 │ NaN │ 119.6567 │ NaN │
│ 2015-12-07T06:00:00 │ NaN │ 119.86 │ NaN │
│ 2015-12-08T06:00:00 │ NaN │ 118.6 │ NaN │
│ 2015-12-09T06:00:00 │ NaN │ 117.69 │ NaN │
⋮
│ 2019-12-25T06:00:00 │ 268.9519 │ 287.435 │ 227.9334 │
│ 2019-12-26T06:00:00 │ 269.6053 │ 289.98 │ 228.4241 │
│ 2019-12-27T06:00:00 │ 270.2127 │ 293.97 │ 228.9102 │
│ 2019-12-28T06:00:00 │ 270.8174 │ 293.5433 │ 229.388 │
│ 2019-12-29T06:00:00 │ 271.4198 │ 293.1167 │ 229.8718 │
│ 2019-12-30T06:00:00 │ 272.0199 │ 292.69 │ 230.3586 │
│ 2019-12-31T06:00:00 │ 272.6489 │ 293.68 │ 230.8632 │
│ 2020-01-01T06:00:00 │ NaN │ NaN │ NaN │
by using Plots
we can then plot certain columns in the TimeArray
, e.g. the portfolio value:
using Plots
plot(ta[:portfolio_value])
We can see that this strategy is not particularly succesful.
See Slow Fast Strategy
for a full runnable version of this strategy.
References
Trading.relative
— Functionrelative(ta::TimeArray)
Rescales all values in the columns of ta
with the first value.
Trading.NewEntitiesIterator
— TypeNewEntitiesIterator
Iterates through all the entities in the requested components of a Strategy
that were not yet seen.