How to use Jesse to backtest your trading strategy - Part 2.
Part 1 was all about finding strategies to profitably trade #bitcoin using #chatgpt , and it responded with a day trading breakout strategy code. In Part 2, we'll learn how to use Jesse, a Python Algo-trading framework, to backtest the strategy, and maybe even trade it live if all goes well. But before that, let's take a quick review of the breakout strategy and the code from ChatGPT.
First, we load historical Bitcoin data and calculate the 20-day high and low prices. We then define the breakout signal as the price breaking above the 20-day high or below the 20-day low.
We execute trades based on the breakout signal. If the breakout signal is triggered and there is no current position, we enter a long position and set the stop loss price. If the breakout signal is triggered and there is already a long position, we exit the trade based on the exit price and calculate the profit and loss (PNL). If there is a long position at the end of the data, we exit the trade on the closing price.
import pandas as pd
# Load historical data
df = pd.read_csv('bitcoin_data.csv')
# Calculate 20-day high and low prices
df['20_day_high'] = df['Close'].rolling(window=20).max()
df['20_day_low'] = df['Close'].rolling(window=20).min()
# Define the breakout signal
df['breakout'] = ((df['Close'] > df['20_day_high'].shift(1)) | (df['Close'] < df['20_day_low'].shift(1)))
# Set up initial trade parameters
position = 0
stop_loss = 0.05
# Loop through the data and execute trades based on the breakout signal
for i in range(1, len(df)):
? ? if df['breakout'][i] and position == 0:
? ? ? ? entry_price = df['Close'][i]
? ? ? ? stop_loss_price = entry_price - (entry_price * stop_loss)
? ? ? ? position = 1
? ? elif df['breakout'][i] and position == 1:
? ? ? ? if df['Close'][i] < stop_loss_price:
? ? ? ? ? ? exit_price = stop_loss_price
? ? ? ? else:
? ? ? ? ? ? exit_price = df['Close'][i]
? ? ? ? pnl = (exit_price - entry_price) / entry_price
? ? ? ? position = 0
? ? ? ? print(f"Trade exited at {exit_price}. PNL: {pnl}")
? ? elif position == 1 and i == len(df) - 1:
? ? ? ? exit_price = df['Close'][i]
? ? ? ? pnl = (exit_price - entry_price) / entry_price
? ? ? ? position = 0
? ? ? ? print(f"Trade exited at {exit_price}. PNL: {pnl}")
Introduction to Jesse: Jesse is a user-friendly, advanced algo trading framework written in Python that lets you define strategies for different crypto spot and derivative markets in multiple time frames.
Jesse comes with the most accurate backtesting engine, a smart ordering system that automatically handles all order types, and a library of functions to help you with risk management and signal processing.
One of my favorite features is the comprehensive set of performance metrics that Jesse offers for backtests, which allows me to evaluate and trade the most likely strategies to perform well. The flow chat diagram below explains how Jesse executes strategies.
Next Steps: Now that you are familiar with Jesse, let's dive into how to define your breakout strategy and run your backtest. To follow along, you should have basic knowledge of Linux, software development tools like Docker, and the Python programming language. If you're a trader who wants to algo trade with Jesse but doesn't have a developer background, please contact me for assistance.
Installing Jesse: Saleh Mir, the creator of Jesse, has provided the resources you require to Install Jesse like this video. Or simply follow these simple instructions. When you have successfully installed Jesse and logged in at your local I.P. you should see a User Interface similar to below.
Import Candles: To backtest you need historical data. Simply select the exchange and market symbol you want then click on start.
Let's assume that you are planning to trade Bitcoin perpetual futures on #Binance and could go long or short with 2X leverage applied. I recommend starting by defining your exit conditions.
领英推荐
Once you're in a position, your exit conditions are to either get stopped out, take a profit, or reach a maximum holding period, which, for our day trading strategy, is 24 hours. Two other exit conditions to consider are if you're in a long position and your strategy signals go short, or if you're in a short position and your strategy signals go long. You'll want to close your position and quickly enter the market in the opposite direction in case the other exit conditions described above were not triggered.
To do this in Jesse, you'll need to add the following code snippet to your update_position method in Jesse.
def update_position(self):
? ? ? ? if self.is_long and self.signal_short:
? ? ? ? ? ? self.liquidate()
? ? ? ? elif self.is_short and self.signal_long:
? ? ? ? ? ? self.liquidate()? ?
? ? ? ? else:
? ? ? ? ? ? time_barrier = (self.time - self.position.opened_at)/3600000
? ? ? ? ? ? time_barrier>=24:
? ? ? ? ? ? self.log('liqating trade, trade expired at: {}'.format(datetime.fromtimestamp(self.time/1000)))
? ? ? ? ? ? self.liquidate()?
?
Entering and Exiting trades: Have you ever wondered how to enter and exit trades in a trading strategy? Well, Jesse has got you covered with a simple two-step process. First, you need to define the direction you want to trade in with the should_long or should_short methods. Second, you need to define your entry orders using the go_long or go_short methods, which include specifying the quantity, stop loss, and take profit levels.
"""
Jesse stores candles in a numpy multi-dimensional array of
which the third and fourth columns are the high and low prices.
We take the last twenty days in a pythonic [-20] then take
the maximum or minimum of those?
"""
def signal_long(self):
? ? #get 20-day highest price?
? ? highest_price_20 = max(self.candles[:,3][-20:])
return self.price >= highest_price_20
def signal_short(self):
? ? #get 20-day lowest price?
? ? lowest_price_20 = min(self.candles[:,4][-20:])
return self.price <= lowest_price_20
def should_long(self)
return self.signal_long
def go_long(self):
stop = self.price * 0.95? # 5% stop loss level of current price
? ? profit = self.price * 1.1? # take profit of 10% increase in current price
? ? qty = size_to_qty(self.available_margin * self.leverage, self.price, fee_rate=self.fee_rate)
? ? self.buy = qty, self.price
? ? self.stop_loss = qty, stop
? ? self.take_profit = qty, profit
?def should_short(self):
? return self.signal_short
?def go_short(self):
stop = self.price * 1.05? # 5% stop loss level of current price
? ? ?profit = self.price * 0.9? # take profit of 10% decrease in current price
? ? ?qty = size_to_qty(self.available_margin * self.leverage, self.price, fee_rate=self.fee_rate)
? ? ?self.sell = qty, self.price
? ? ?self.stop_loss = qty, stop
? ? ?self.take_profit = qty, profit
To trigger trades, you need to define the signals. For example, you can define your signals by taking the highest or lowest prices of the last 20 days.
Putting it all together:
from jesse.strategies import Strategy
from jesse.utils import size_to_qty
import jesse.indicators as ta
class BreakOutStrategy(Strategy):
? ? def __init__(self):
? ? ? ? super().__init__()
? ? ? ? print('Initialized the strategy class')
? ??@property
? ? def signal_long(self):
? ? ? ? highest_price_20 = max(self.candles[:, 3][-20:])
? ? ? ? self.log('current price:{}, twenty day high:{}'.format(self.price, highest_price_20))
? ? ? ? return self.price >= highest_price_20
? ??@property
? ? def signal_short(self):
? ? ? ? lowest_price_20 = min(self.candles[:, 4][-20:])
? ? ? ? self.log('current price:{}, twenty day low:{}'.format(self.price, lowest_price_20))
? ? ? ? return self.price <= lowest_price_20
? ? def should_long(self):
? ? ? ? return self.signal_long
? ? def go_long(self):
? ? ? ? stop = self.price * 0.95? # 5% stop loss level of current price
? ? ? ? profit = self.price * 1.1? # take profit of 10% increase in current price
? ? ? ? qty = size_to_qty(self.available_margin * self.leverage, self.price, fee_rate=self.fee_rate)
? ? ? ? self.buy = qty, self.price
? ? ? ? self.stop_loss = qty, stop
? ? ? ? self.take_profit = qty, profit
? ? def should_short(self):
? ? ? ? return self.signal_short
? ? def go_short(self):
? ? ? ? stop = self.price * 1.05? # 5% stop loss level of current price
? ? ? ? profit = self.price * 0.9? # take profit of 10% decrease in current price
? ? ? ? qty = size_to_qty(self.available_margin * self.leverage, self.price, fee_rate=self.fee_rate)
? ? ? ? self.sell = qty, self.price
? ? ? ? self.stop_loss = qty, stop
? ? ? ? self.take_profit = qty, profit
? ? def before(self):
? ? ? ? pass
? ? def update_position(self):
? ? ? ? if self.is_long and self.signal_short:
? ? ? ? ? ? self.liquidate()
? ? ? ? elif self.is_short and self.signal_long:
? ? ? ? ? ? self.liquidate()
? ? ? ? else:
? ? ? ? ? ? time_barrier = (self.time - self.position.opened_at) / 3600000
? ? ? ? ? ? if time_barrier >= 24:
? ? ? ? ? ? ? ? self.log('Liquidating trade, trade expired at: {}'.format(self.time))
? ? ? ? ? ? ? ? self.liquidate()
? ? def should_cancel_entry(self):
? ? ? ? return False
Once you have defined your strategy, it's time to backtest it using Jesse's backtest tab.
Unfortunately for this strategy, no trades where executed meaning the signals generated no trades. That is a bit surprising. Perhaps we can ask ChatGPT for better strategies, redefine a breakout as above or below a moving average or a range. What about trying a lower time frame? If applying these changes then let me know how you get on.
Conclusion: