How to use multiple timeframes in your algotrading strategy

In this tutorial I am going to take a look at an example of how you can use multiple timeframes in your strategies. In the previous article, we learned how to import candles, write a simple profitable strategy, define routes, and to execute the backtest. If you haven't read my previous article yet, please do so before continuing.

I'll be continuing where I left off in the previous article. This is the complete strategy I ended up with:

from jesse.strategies import Strategy
import jesse.indicators as ta
from jesse import utils


class SampleTrendFollowing(Strategy):
    def should_long(self) -> bool:
        return self.short_ema > self.long_ema

    def should_short(self) -> bool:
        return self.short_ema < self.long_ema

    def should_cancel(self) -> bool:
        return True

    def go_long(self):
        entry = self.price
        stop = entry - 3*self.atr
        qty = utils.risk_to_qty(self.capital, 3, entry, stop)
        profit_target = entry + 5*self.atr

        self.buy = qty, entry
        self.stop_loss = qty, stop
        self.take_profit = qty, profit_target

    def go_short(self):
        entry = self.price
        stop = entry + 3 * self.atr
        qty = utils.risk_to_qty(self.capital, 3, entry, stop)
        profit_target = entry - 5 * self.atr

        self.sell = qty, entry
        self.stop_loss = qty, stop
        self.take_profit = qty, profit_target

    @property
    def long_ema(self):
        return ta.ema(self.candles, 50)

    @property
    def short_ema(self):
        return ta.ema(self.candles, 21)

    @property
    def atr(self):
        return ta.atr(self.candles)

And this is the result of backtesting it since 2019-01-01 to 2020-05-01 with 4h timeframe:

 CANDLES              |
----------------------+--------------------------
 period               |    486 days (1.33 years)
 starting-ending date | 2019-01-01 => 2020-05-01


 exchange   | symbol   | timeframe   | strategy             | DNA
------------+----------+-------------+----------------------+-------
 Bitfinex   | BTCUSD   | 4h          | SampleTrendFollowing |


Executing simulation...  [####################################]  100%
Executed backtest simulation in:  32.53 seconds


 METRICS                         |
---------------------------------+------------------------------------
 Total Closed Trades             |                                 62
 Total Net Profit                |                   3143.55 (31.44%)
 Starting => Finishing Balance   |                  10000 => 13009.82
 Total Open Trades               |                                  1
 Open PL                         |                            -114.95
 Total Paid Fees                 |                            1512.06
 Max Drawdown                    |                            -18.86%
 Sharpe Ratio                    |                               0.62
 Annual Return                   |                             14.59%
 Expectancy                      |                       50.7 (0.51%)
 Avg Win | Avg Loss              |                    521.54 | 363.06
 Ratio Avg Win / Avg Loss        |                               1.44
 Percent Profitable              |                                47%
 Longs | Shorts                  |                          56% | 44%
 Avg Holding Time                | 1.0 week, 17.0 hours, 42.0 minutes
 Winning Trades Avg Holding Time | 1.0 week, 12.0 hours, 19.0 minutes
 Losing Trades Avg Holding Time  | 1.0 week, 22.0 hours, 26.0 minutes

Defining multiple timeframes in your strategy

In this article I am going to use multiple timeframes to see if I can improve the results. One trick that experienced traders use in their manual trading is to look at the trend at the bigger timeframe (or as they call it the anchor timeframe). This simple trick often increases your win-rate for the cost of reducing the number of entry signals.

You might be asking how do I know which timeframe is the anchor timeframe? The common formula is either 4 or 6 times of your trading timeframe. For example in my case the anchor timeframe for 4h is:

6 * 4h = 24h ("1D" in jesse's terms)

Jesse offers a utility helper that calculates this for you, and that is going to be what I'll use.

Let's add a new property method that returns the big trend. I'll return 1 for an uptrend, and -1 for downtrend:

@property
def anchor_trend(self):
    # use self.get_candles() to get the candles for the anchor timeframe
    anchor_candles = self.get_candles(
        self.exchange, self.symbol, utils.anchor_timeframe(self.timeframe)
    )

    ema = ta.ema(anchor_candles, 100)

    if self.price > ema:
        return 1
    else:
        return -1

As you can see, this time I used self.get_candles instead of self.candles. I also used a few other builtin properties instead of using hard-coded strings:

# instead of 
anchor_candles = self.get_candles(
    'Bitfinex', 'BTCUSD', utils.anchor_timeframe('4h')
)

# I wrote
anchor_candles = self.get_candles(
    self.exchange, self.symbol, utils.anchor_timeframe(self.timeframe)
)

This way, when I change my routes to try out other exchanges, symbols, and timeframes, I don't have to change my strategy's code.

Now I update my entry rules to include anchor_trend:

def should_long(self) -> bool:
    return self.short_ema > self.long_ema and self.anchor_trend == 1

def should_short(self) -> bool:
    return self.short_ema < self.long_ema and self.anchor_trend == -1

Modifying your routes

Now I execute the backtest again to see how this change would affect the backtest result but I get this error:

Uncaught Exception: RouteNotFound: Bellow route is required but missing in your routes:
('Bitfinex', 'BTCUSD', '1D')

The error is clear. If I'm using candles for another timeframe, I should add it to my routes.py file too. My trading route must stay the same because I'm trading a single position, so any other timeframe that I use is considered as an extra candle.

This is what my routes.py looks like now:

# trading routes
routes = [
    ('Bitfinex', 'BTCUSD', '6h', 'SampleTrendFollowing'),
]

# in case your strategy required extra candles, timeframes, ...
extra_candles = [

]

The error told me that I'm missing ('Bitfinex', 'BTCUSD', '1D') in my routes; so let's add it to the extra_candles list. This is how my routes.py should become:

# trading routes
routes = [
    ('Bitfinex', 'BTCUSD', '6h', 'SampleTrendFollowing'),
]

# in case your strategy required extra candles, timeframes, ...
extra_candles = [
    ('Bitfinex', 'BTCUSD', '1D'),
]

This time the backtest goes smoothly. Here are the results:

 CANDLES              |
----------------------+--------------------------
 period               |    486 days (1.33 years)
 starting-ending date | 2019-01-01 => 2020-05-01


 exchange   | symbol   | timeframe   | strategy             | DNA
------------+----------+-------------+----------------------+-------
 Bitfinex   | BTCUSD   | 4h          | SampleTrendFollowing |


Executing simulation...  [####################################]  100%
Executed backtest simulation in:  34.42 seconds


 METRICS                         |
---------------------------------+-----------------------------------
 Total Closed Trades             |                                56
 Total Net Profit                |                  4842.12 (48.42%)
 Starting => Finishing Balance   |                 10000 => 14685.89
 Total Open Trades               |                                 1
 Open PL                         |                           -129.79
 Total Paid Fees                 |                           1447.79
 Max Drawdown                    |                           -21.85%
 Sharpe Ratio                    |                              0.86
 Annual Return                   |                             22.0%
 Expectancy                      |                     86.47 (0.86%)
 Avg Win | Avg Loss              |                   546.57 | 373.63
 Ratio Avg Win / Avg Loss        |                              1.46
 Percent Profitable              |                               50%
 Longs | Shorts                  |                         61% | 39%
 Avg Holding Time                |      1.0 week, 1.0 day, 4.0 hours
 Winning Trades Avg Holding Time | 5.0 days, 23.0 hours, 9.0 minutes
 Losing Trades Avg Holding Time  |     1.0 week, 3.0 days, 9.0 hours

Here are a few notable changes in the backtest result after I used the anchor timeframe to detect the bigger trend of the market:

  • The Total Net Profit is increased from 31.44% to 48.42% which means, more money!
  • The Total Closed Trades is decreased which means I get fewer entry signals, which is expected since I added another entry condition to my entry rules.
  • The Sharpe ratio is increased from 0.62 to 0.86.
  • Win-rate is increased from 47% to 50%.

The look-ahead bias

The look-ahead bias is a serious issue when using multiple timeframes in algo strategies. In short, it means using data from the future.

Some of you might be familiar with it and be wondering if the look-ahead bias has a hand in this result boost that we just observed. Rest assured, it doesn't. The Jesse framework takes care of the look-ahead bias behind the scenes.

Conclusion

We just saw a noticeable boost in my strategy's metrics as the result of using the anchor timeframe for determining the bigger trend of the market.

Of course you won't get the same boost every time you use multiple timeframes in your strategies. But in my opinion, it makes sense to at least try it.

This idea can be extended. For example if you're trading Ether, you might want to use Bitcoin's anchor trend as it is obvious Bitcoin is the king of the cryptocurrency markets and has a huge impact on the price of other coins.

© 2020 jesse-ai.com. All rights reserved.