How to Optimize Stop Loss Placement Using MAE Data from myfxbook

August 10, 2020

We will answer the Question:

How far did the trade move away from breakeven (in the negative direction) before either getting stopped out or back on to take profit?

This can help us answer if we should set a maximum stop loss level. If the trade moves in x pips against us, then we know it has a high probability of being a loser and we should just get out early.

The free online tool at https://www.myfxbook.com collects the data, so we can export it and analyze it here. We can export to csv, remove the last "open trades" block of data, and save the file so we get a clean import into a pandas dataframe.

Note this type of analysis probably works best on an automated system that sets a well defined stop loss level.

What would happen to performance if we used a maximum stop loss on the system?

  • tradeoff: How does the capture of pips change when some actual wins would have been losses, and some larger losses would have been cut short, by using a maximum stop loss level?

Script inputs

  • df is a pandas dataframe read in after creating a csv export from myfxbook data
  • pipLossToStudy is the target threshold under investigation, set to final number after a few iterations of study
  • pipLossListToStudy is a list of maximum stop loss thresholds that are simulated to gather data on 'what if' scenarios
In [1]:
# define all imports needed
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style='darkgrid')
import warnings
warnings.filterwarnings("ignore")
In [2]:
# define all of the functions we will use for this analysis
def calculateStats(df, pipLossListToStudy):
    """
    Iterate through pipLossListToStudy to generate hypothetical results of different maximum stop loss levels
    # list of hypothetical wins
    # list of hypothetical losses
    # total aggregate after each number
    """

    verbose=False
    totalpipsbefore = df['Pips'].sum()
    pipswonbefore = df[df['Pips'] > 0]['Pips'].sum()
    pipslostbefore = df[df['Pips'] < 0]['Pips'].sum()
    numtrades = len(df)

    if verbose:
        print('Number of trades: ', numtrades)
        print('Total pips before: ', totalpipsbefore)
        print('Pips won before: ', pipswonbefore)
        print('Pips lost before: ', pipslostbefore, '\n')

    numwins=len(df[df['Pips'] > 0])
    numlosses = len(df[df['Pips'] < 0])
    simpips = []
    simpipswonlist = []
    simpipslostlist = []
    for pipLossToStudy in pipLossListToStudy:

        deadwins = len(df[df['Pips'] > 0][df['Min(pips)'] <= pipLossToStudy])
        deadwinspips = df[df['Pips'] > 0][df['Min(pips)'] <= pipLossToStudy]['Pips'].sum()
        cutlosses = len(df[df['Pips'] < 0][df['Min(pips)'] <= pipLossToStudy])
        cutlossespips = df[df['Pips'] < 0][df['Min(pips)'] <= pipLossToStudy]['Pips'].sum()
        simwins2losses=deadwins*pipLossToStudy
        simlossescutshort=cutlosses*pipLossToStudy
        simpipswon=pipswonbefore-deadwinspips
        simpipslost=pipslostbefore-cutlossespips+simwins2losses+simlossescutshort
        simtotalpips=simpipswon+simpipslost
        simpips.append(simtotalpips)
        simpipswonlist.append(simpipswon)
        simpipslostlist.append(simpipslost)

        if verbose:
            print('\nTEST PIP LOSS STOP: ', pipLossToStudy)
            print('Number of wins that would have been losses: ', deadwins, 'or ', round(deadwins/numwins,3)*100, '% of total wins')
            print('Number of pips won that would have been losses: ', deadwinspips)
            print('Number of losses that would have cut short: ', cutlosses, 'or ', round(cutlosses/numlosses,3)*100,'% of total losses')
            print('Number of pips lost that would have been cut short: ', cutlossespips)
            print('Wins would have been losses in pips: ', simwins2losses)
            print('Losses would have reduced to this in pips: ', simlossescutshort)
            print('Sim total pips won: ', simpipswon)
            print('Sim total pips lost: ', simpipslost)
            print('Total pips after making the change: ', simtotalpips)
    return simpips, simpipswonlist, simpipslostlist

def plotMAE(df, pipLossToStudy):
    """
    Generate a basic plot of MAE results split by wins and losses - similar to that on myfxbook MAE page
    """
    # generate the standard MAE chart in pips
    fig = plt.figure(figsize=(14, 6))
    ax3 = fig.add_subplot(1,1,1)
    ax3.scatter(x=df[df['Pips']>0]['Pips'], y=df[df['Pips']>0]['Min(pips)'],
                color='green', label='Winning Trades')
    ax3.scatter(x=df[df['Pips']<0]['Pips'], y=df[df['Pips']<0]['Min(pips)'],
                color='red', label='Losing Trades')
    ax3.set_xlabel('Pips')
    ax3.set_ylabel('Min(pips) MAE')
    ax3.axhline(pipLossToStudy,
                color='blue', label='Proposed Max Stop', linestyle='--')
    ax3.set_title('MAE vs. Trade Outcome (pips)')
    ax3.legend()
    fig.show()

    # generate separate histograms of wins and losses
    fig = plt.figure(figsize=(14, 6))

    ax2 = fig.add_subplot(1, 2, 1)
    ax2.hist(x = df[df['Pips'] < 0]['Min(pips)'], bins=25, color='red')
    ax2.set_title('MAE Distribution for Losing Trades')
    ax2.set_xlabel('Min(pips) - MAE')
    ax2.set_ylabel('Number of Trades')
    ax2.axvline(pipLossToStudy,
                color='blue', label='Proposed Max Stop', linestyle='--')
    ax2.legend()

    ax1 = fig.add_subplot(1, 2, 2)
    ax1.hist(df[df['Pips'] > 0]['Min(pips)'], bins=25,
             color='green')
    ax1.set_title('MAE Distribution for Winning Trades')
    ax1.set_xlabel('Min(pips) - MAE')
    ax1.set_ylabel('Number of Trades')
    ax1.axvline(pipLossToStudy,
                color='blue', label='Proposed Max Stop', linestyle='--')
    ax1.legend()

    fig.show()
    return

def plotMFE(df, pipProfitToStudy):
    """
    Generate a basic plot of MAE results split by wins and losses - similar to that on myfxbook MAE page
    """
    # generate the standard MAE chart in pips
    fig = plt.figure(figsize=(14, 6))
    ax3 = fig.add_subplot(1,1,1)
    ax3.scatter(x=df[df['Pips']>0]['Pips'], y=df[df['Pips']>0]['Max(pips)'],
                color='green', label='Winning Trades')
    ax3.scatter(x=df[df['Pips']<0]['Pips'], y=df[df['Pips']<0]['Max(pips)'],
                color='red', label='Losing Trades')
    ax3.set_xlabel('Pips')
    ax3.set_ylabel('Max(pips) MFE')
    ax3.axhline(pipProfitToStudy,
                color='blue', label='Proposed Max Stop', linestyle='--')
    ax3.set_title('MFE vs. Trade Outcome (pips)')
    ax3.legend()
    fig.show()

    fig.show()
    return

def plotPipChanges(pipLossListToStudy, pipLossToStudy, simpips, simpipswonlist, simpipslostlist):
    """
    Generate a plot of aggregate total system results, win results, and loss results, after simulating
    what would happen at each value in pipLossListToStudy
    """
    fig = plt.figure(figsize=(14, 6))

    ax3 = fig.add_subplot(1,3,1)
    ax3.plot(pipLossListToStudy, simpips)
    ax3.set_title('Total Pips vs. Stop Loss (MAE) Levels')
    ax3.set_xlabel('Proposed Min(pips) - MAE')
    ax3.set_ylabel('Total Pips Gained/Lost')
    ax3.axvline(pipLossToStudy,
                color='blue', label='Proposed Max Stop', linestyle='--')

    ax1 = fig.add_subplot(1,3,2)
    ax1.plot(pipLossListToStudy, simpipswonlist, color='green')
    ax1.set_title('Total Pips Won vs. Stop Loss (MAE) Levels')
    ax1.set_xlabel('Proposed Min(pips) - MAE')
    ax1.set_ylabel('Total Pips Gained/Lost')
    ax1.axvline(pipLossToStudy,
                color='blue', label='Proposed Max Stop', linestyle='--')

    ax2 = fig.add_subplot(1,3,3)
    ax2.plot(pipLossListToStudy, simpipslostlist, color='red')
    ax2.set_title('Total Pips Lost vs. Stop Loss (MAE) Levels')
    ax2.set_xlabel('Proposed Min(pips) - MAE')
    ax2.set_ylabel('Total Pips Gained/Lost')
    ax2.axvline(pipLossToStudy,
                color='blue', label='Proposed Max Stop', linestyle='--')

    fig.show()
    return

def plotByDate(df, pipLossToStudy):
    """
    Plot trade results across time, colored by whether they would have been stopped, 
    or not, at the target pipLossToStudy
    """
    fig = plt.figure(figsize=(14, 6))
    ax = fig.add_subplot(1, 1, 1)
    ax.scatter(pd.to_datetime(df[df['Min(pips)'] < pipLossToStudy]['Open Date']),
               df[df['Min(pips)'] < pipLossToStudy]['Pips'], color='orange', label='Trades Stopped Early')
    ax.scatter(pd.to_datetime(df[df['Min(pips)'] > pipLossToStudy]['Open Date']),
               df[df['Min(pips)'] > pipLossToStudy]['Pips'], color='blue', label='Trades Not Changed')
    ax.set_title('Date of Trades - When did trades that would have been losses happen?\nIs there a particular market environment that causes this phenomena for this system?')
    ax.set_xlabel('Open Date of Trade')
    ax.set_ylabel('Trade Result in Pips')
    ax.axhline(pipLossToStudy,
               color='red', label='Proposed Max Stop', linestyle='--')
    ax.axhline(0, color='black', label='0 Pips')
    ax.legend()
    fig.show()
    return

My System 8C Analysis

Below we will generate some charts of our system, using the functions defined above:

  • First, plot the MAE vs Trade Outcome. Essentially this same chart can be found in the myfxbook analysis tools.
  • Next, we will break down the distribution of winning and losing trades.
  • Then, we simulate what would happen over a range of maximum stop loss levels. We will plot the aggregate number of pips (simulated result of wins + losses), how many pips we would have won, and how many pips we would have lost.
  • Finally, we simply plot the result of trades as a function of time. We color those trades that would have been expected by a maximum stop loss.
In [4]:
# after exporting & creating a csv file from myfxbook, read it in and perform the analysis
df = pd.read_csv('../strategies/Test_8C_BabyBounce/myfxbook-export-7.20.20.csv')

# what is the target pipLossToStudy? this plots the 'breakpoint line' in the charts below
pipLossToStudy = -75

# what list of pips do you want to study? this will generate the simulation visualization charts
pipLossListToStudy = [-15, -20, -25, -30, -35, -40, -45, -50, -55, -60, -65, -70, -75, -80, -85, -90, -95, -100, -105,
                     -110, -115, -120, -125, -130, -135, -140]

# finally, run the functions we've created
simpips, simpipswonlist, simpipslostlist = calculateStats(df, pipLossListToStudy)
plotMAE(df, pipLossToStudy)
plotPipChanges(pipLossListToStudy, pipLossToStudy, simpips, simpipswonlist, simpipslostlist)
plotByDate(df, pipLossToStudy)

What if we set stop loss at a maximum number?

  1. System 8C Results
    • stop loss at 75 pips would historically have best improved performance
    • out of 2309 trades, it would have made 15 wins into losses and cut short 139 losses
    • would have improved total losses in pips from -7178 to -5534
    • What trades are happening that set a stop loss larger than 75? Does that mean this system shouldn't be trade in high volatility?

Now 'zoom in' around the 75 pip stop loss level

In [5]:
df = pd.read_csv('../strategies/Test_8C_BabyBounce/myfxbook-export-7.20.20.csv')
pipLossToStudy = -75
pipLossListToStudy = [-70, -71, -72, -73, -74, -75, -76, -77, -78, -79, -80]
simpips, simpipswonlist, simpipslostlist = calculateStats(df, pipLossListToStudy)
plotMAE(df, pipLossToStudy)
plotPipChanges(pipLossListToStudy, pipLossToStudy, simpips, simpipswonlist, simpipslostlist)
plotByDate(df, pipLossToStudy)

Improvements

This analysis has been performed on a test system that executes over the aggregate of 28 major currency pairs. This analysis could be fine-tuned and made more useful by selecting specific currency pairs that work well on this system, and implementing specific stop loss levels for each of the pairs. The only purpose of trading the system on all 28 major fx pairs is to collect statistics for analysis.