Tutorial

Trading.StrategyType
Strategy(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.

Note

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

source

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.

Note

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.relativeFunction
relative(ta::TimeArray)

Rescales all values in the columns of ta with the first value.

source