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, self.fee_rate)
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, self.fee_rate)
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 | BTC-USD | 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', 'BTC-USD', 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', 'BTC-USD', '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', 'BTC-USD', '6h', 'SampleTrendFollowing'),
]
# in case your strategy required extra candles, timeframes, ...
extra_candles = [
]
The error told me that I'm missing ('Bitfinex', 'BTC-USD', '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', 'BTC-USD', '6h', 'SampleTrendFollowing'),
]
# in case your strategy required extra candles, timeframes, ...
extra_candles = [
('Bitfinex', 'BTC-USD', '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 | BTC-USD | 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.
❤️ Like Jesse?
If you like this project, please consider supporting me for free by using my referral links.
It's a win-win, you get free discounts and bonuses, and it helps me to develop more features and content:
Thank you 🙏