How to choose pairs for a Freqtrade strategy?
This article aims to explain how to do a simple analysis of the pairs to be used for Freqtrade trading strategy.
- Transform json file to pandas dataframe
- Understand and calculate Expectancy
- Plotting rolling Expectancy
How to obtain and trasform backtest data
Freqtrade allows you to perform a backtest on the historical data of your strategy.
If - - export trades is added to the command to perform the backtest then you can also obtain a json file that saves all the buy and sell operations carried out during the backtest period.
An example of a command to perform the backtest is this:
freqtrade backtesting -c config.json --strategy Strategy -timerange=20190101- --export trades
Once the calculation is complete, useful results will be shown to understand if the strategy performed well or badly.
This data are very good, but if we want to understand well if a pair has always performed well or which pair to choose between two who have obtained the same profit, this report is not enough.
Let’s see what we can do more to better understand.
First we load the json file and turn it into a dataframe with only the information we need.
We have to define the path where the created backtest file is located.
To find the path just enter the correct folders:
- freqtrade -> user_data -> backtest_results, right click on the backtest file and select “properties”.
I am on Linux so my path will be this:
- /home/matteo/freqtrade/user_data/backtest_results
to which the file name must be added.
If you are on Windows the path structure will be different.
import pandas as pd
import json
from datetime import datetime, timedelta,date
import plotly.graph_objects as go#function
def dataframe_trade(backtest_path):
f = open(backtest_path)
data = json.load(f)
#dataframe of trades
df = pd.DataFrame.from_dict(data, orient='columns')
df = df.rename(columns={0: "Pair", 1: "Return", 2: 'Date'})
df = df.drop(df.columns[[3, 4,5,6,7,8,9]], axis=1)
df['Date'] = pd.to_datetime(df['Date'], unit='s').dt.date
df['Date'] = df['Date'].astype('category')
df['Pair'] = df['Pair'].astype('category')
df = df.set_index('Date')
return dfbacktest_path = '/home/matteo/freqtrade/user_data/backtest_results/backtest-result.json'
df = dataframe_trade(backtest_path)
Once the dataframe is obtained, the result should be something like this.
This type of dataframe allows us to calculate everything necessary to arrive at the expectancy
What is the expectancy and how is it calculated?
There is a lot of confusion about expectancy and how to calculate it, so I will explain it here.
Many traders calculate expectancy in this way:
- Expectancy = (average win * win %) + (average loss * lose %)
= average trade
Note that this is also the average trade net profit. So calculating an expectancy using this equation does not provide any additional information beyond what is already known with the average trade net profit. An alternative expectancy can be calculated as follows:
- Expectancy = (average win * win % + average loss * lose %)/
(−average loss)
This metric is useful since it is a risk‐adjusted value. It basically states for every dollar you risk, what is your expected return? So with an expectancy of 0.2, you’d expect to receive 20 cents in gains for every $1 you put at risk. This expectancy has been heavily touted by trading psychologist and educator Dr. Van Tharp.
Let’s then calculate expectancy and other useful indices from our dataframe
def performance_summury(df):
total_number_trade = len(df)
net = df['Return'].mean() * total_number_trade
positive = df['Return'][df['Return'] > 0]
n_positive = positive.count()
negative = df['Return'][df['Return'] < 0]
n_negative = negative.count()
avgwin = positive.mean()
avgloss = negative.mean()
winperc = n_positive/total_number_trade
lossperc = n_negative/total_number_trade
winloss_ratio = round(n_positive/n_negative, 2)
profit_factor = round(avgwin/abs(avgloss),2)
expectancy = round( ((avgwin * winperc) + (avgloss * lossperc))/
(-avgloss),2)
return total_number_trade, n_positive,n_negative,round(avgwin*100,1), round(avgloss*100,1),profit_factor, winloss_ratio, expectancys = (df.groupby(df['Pair']).apply(performance_summury))table = pd.DataFrame(list(s),
columns=['Total number of trades','Win trades',
'Loss trades', 'Average % win', 'Average % loss',
'Profit Factor','Win/Loss Ratio',
'Expectancy'] ,index= s.index)
table = table.sort_values(by ='Expectancy',ascending=False)
The result we will get is this:
This table allows us to have a complete report of the possible pairs to be included in the strategy. I personally evaluate all these values to select a pair, however giving greater weight to the expectancy.
How to plot rolling expectancy?
Calculating a rolling expectancy is a very useful way to analyze the historical performance of a trading system. This is because a rolling expectancy gives traders insights on the time-varying performance of a strategy. We apply the expectancy formula to a fixed time window wich we then repeat every new trade.
This is the code to do it:
def expectancy(df):
total_number_trade = len(df)
net = df['Return'].mean() * total_number_trade
positive = df['Return'][df['Return'] > 0]
n_positive = positive.count()
negative = df['Return'][df['Return'] < 0]
n_negative = negative.count()
avgwin = positive.mean()
avgloss = negative.mean()
winperc = n_positive/total_number_trade
lossperc = n_negative/total_number_trade
expectancy = round( ((avgwin * winperc) + (avgloss * lossperc))/
(-avgloss),2)
return expectancyexp = []#IT TAKES SOME TIME AND SHOWS WARNINGSfor n in range(len(df)):
exp.append(df.iloc[1:n].groupby(df.iloc[1:n]['Pair']).apply(expectancy))expectancy = pd.DataFrame(exp)
expectancy = expectancy.fillna(0)# plot
res = []
for col in expectancy.columns:
res.append(
go.Scatter(
x=expectancy.index.values.tolist(),
y=expectancy[col].values.tolist(),
name=col
)
)
fig = go.Figure(data=res)
fig.update_layout(dict(
title='Rolling Expectancy',
xaxis=dict(
title=dict(
text='Number of trades'
)
),
yaxis=dict(
title=dict(
text='Expectancy'
)
)
))fig.show()
As you can see from the graph, during the first trades, the volatility of expectancy is very high. This effect is normal because the expectancy value depends very much on the quantity of trades made, a negative or positive performarce at the beginning makes this indicator very sensitive.
But what interests us is the final part of this chart.
We are concerned that the final value obviously is greater than zero and that the trend is constant or growing.
If you want to analyze them at a time, simply double click the pair you are interested in on the right side of the graph.
Conclusion
This analysis strategy is one of the many that can be used, obviously in this article not many points have been evaluated on the robustness of this analysis and the code used to do it is not optimized at all.
If you are interested in deepening the topic or recommend some improvements, I would appreciate it very much.
If you don’t know what Freqtrade is, I highly recommend looking at this link.