How to write a profitable strategy for algotrading Bitcoin
In this tutorial, I'm going to write a strategy for trading bitcoin with Jesse. The point of this tutorial is to get you started on using Jesse so you can write your own strategies. I'll also teach you a few tricks that I've picked up over the years that help me write profitable strategies. This is probably going to be a series of articles.
The point of this tutorial is not to give you an awesome strategy; it is to get you started on writing one. In part one, I'll cover everything from:
- How to use technical analysis
- How to enter trades
- How to exit trades
- How to manage risk
First, make sure you have Jesse installed. If you don't, go ahead and do it. Next, make sure you imported candles. Let's do that now since it takes a few minutes to finish, which is fine because we'll be writing the strategy:
jesse import-candles Bitfinex BTC-USD 2016-01-01
Create the strategy
Let's name this strategy SampleTrendFollowing
. cd
into your Jesse project and run:
jesse make-strategy SampleTrendFollowing
Now open SampleTrendFollowing
located at strategies/SampleTrendFollowing/__init__.py
. This is the code generated by Jesse:
from jesse.strategies import Strategy
import jesse.indicators as ta
from jesse import utils
class SampleTrendFollowing(Strategy):
def should_long(self) -> bool:
return False
def should_short(self) -> bool:
return False
def should_cancel(self) -> bool:
return False
def go_long(self):
pass
def go_short(self):
pass
Using technical analysis
We'll be using EMA indicator for detecting the direction of the market trend. Let's use two EMA lines with periods of 50 for slower EMA, and 21 for faster EMA.
@property
def long_ema(self):
return ta.ema(self.candles, 50)
@property
def short_ema(self):
return ta.ema(self.candles, 21)
Notice that I defined them as class properties the @property
keyword. This allows me to use them as self.long
instead of self.long()
which is a bit easier in my opinion.
Let's also define the ATR indicator which is my favorite tool for setting the stop-loss price:
@property
def atr(self):
return ta.atr(self.candles)
Entry rules
Our entry rules are simple: go long when the fast EMA line breaks above the slow EMA, and vice versa for short trades.
def should_long(self) -> bool:
return self.short_ema > self.long_ema
def should_short(self) -> bool:
return self.short_ema < self.long_ema
Managing Risk
One critical part of every strategy is position sizing. A simple compounding position sizing will get you a long way. For example, let's risk 3% of our total capital per trade.
We also need to specify our entry price. To keep it simple, let's open our positions using a market order.
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
Notice that I used ATR
indicator for both my stop-loss and take-profit targets.
While it is a generally good practice to exit trend following strategies dynamically, I however set exit points at the moment of opening positions. I did it for the sake of keeping this tutorial simple.
Routes
Now we need to tell Jesse to trade SampleTrendFollowing
strategy when we execute the backtest command. We also need to choose a timeframe, and a symbol to trade. I chose 6h
timeframe and BTC-USD
as my symbol. Your routes.py
file should look like this:
# trading routes
routes = [
('Bitfinex', 'BTC-USD', '6h', 'SampleTrendFollowing'),
]
Running backtest
Let's see how it performs:
jesse backtest 2019-01-01 2020-01-01
And here are the results:
CANDLES |
----------------------+--------------------------
period | 365 days (12.17 months)
starting-ending date | 2019-01-01 => 2020-01-01
exchange | symbol | timeframe | strategy | DNA
------------+----------+-------------+----------------------+-------
Bitfinex | BTC-USD | 6h | SampleTrendFollowing |
Executing simulation... [####################################] 100%
Executed backtest simulation in: 22.61 seconds
METRICS |
---------------------------------+-------------------------------------
Total Closed Trades | 24
Total Net Profit | 3991.39 (39.91%)
Starting => Finishing Balance | 10000 => 14063.47
Total Open Trades | 1
Open PL | 97.15
Total Paid Fees | 498.22
Max Drawdown | -15.9%
Sharpe Ratio | 1.18
Annual Return | 26.46%
Expectancy | 166.31 (1.66%)
Avg Win | Avg Loss | 558.76 | 383.12
Ratio Avg Win / Avg Loss | 1.46
Percent Profitable | 58%
Longs | Shorts | 67% | 33%
Avg Holding Time | 2.0 weeks, 12.0 hours, 53.0 minutes
Winning Trades Avg Holding Time | 1.0 week, 4.0 days, 6.0 hours
Losing Trades Avg Holding Time | 2.0 weeks, 5.0 days, 2.0 hours
Look at that, it's actually profitable! Well, at least in 2019 it was. Is that enough backtest for a trend-following strategy trading on 6h
timeframe? It's your call, but I would go further back if I have the data. In this case, I went back to 2017 until this year's May 3th:
CANDLES |
----------------------+--------------------------
period | 1218 days (3.34 years)
starting-ending date | 2017-01-01 => 2020-05-03
exchange | symbol | timeframe | strategy | DNA
------------+----------+-------------+----------------------+-------
Bitfinex | BTC-USD | 6h | SampleTrendFollowing |
Executing simulation... [####################################] 100%
Executed backtest simulation in: 92.07 seconds
METRICS |
---------------------------------+------------------------------------
Total Closed Trades | 120
Total Net Profit | 11596.71 (115.97%)
Starting => Finishing Balance | 10000 => 21878.82
Total Open Trades | 1
Open PL | 323.36
Total Paid Fees | 2315.78
Max Drawdown | -34.07%
Sharpe Ratio | 0.78
Annual Return | 17.57%
Expectancy | 96.64 (0.97%)
Avg Win | Avg Loss | 647.43 | 418.62
Ratio Avg Win / Avg Loss | 1.55
Percent Profitable | 48%
Longs | Shorts | 66% | 34%
Avg Holding Time | 1.0 week, 2.0 days, 23.0 hours
Winning Trades Avg Holding Time | 1.0 week, 21.0 hours, 58.0 minutes
Losing Trades Avg Holding Time | 1.0 week, 4.0 days, 22.0 hours
Conclusion
The point of this tutorial was to get you started on writing strategies with Jesse. I will write more articles like this, exploring more features of Jesse such as using multiple timeframes, filters, events, and more.
If you are on Medium, please clap for the Medium version of this article.
❤️ 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 🙏