1
$\begingroup$

I've been pursuing the holy grail of trading, short term FX trading, using machine learning. I've experimented with a ton of strategies but mainly those revolving around holding each trade for a fixed duration, however, I think a better strategy can be pursued by training on data where the y-value represents the actual return given some set of rules. For example, if a stop loss were used, the gain-loss value could be calculated for each training example given the stop loss rules and those values could be used to train the model. Then when the model is deployed, the same rules would be used.

I wanted to test out how stop loss methods would perform if the entry points were picked at random to determine if they create any kind of headwind or tailwind in normal market conditions. Here's the function that I'm using to do that:

def stop_loss_random_test(prices, side="long", stop_type="trailing", stop_loss=0.0005, num=1000, forward=1000):
    
    np.random.seed(0)
    prices_indices = np.arange(prices.shape[0])
    random_indices = np.random.choice(prices_indices[:-forward], num, replace=False)
    forward_range = np.arange(forward)

    multiplier = 1 if side == "long" else -1
    entry_price = None
    max_gain = None
    gains = []
    durations = []
    
    for i in random_indices:

        entry_price = prices[i]
        max_gain = 0
        
        for p in forward_range:
            gain = prices[i+p] / entry_price - 1
            gain *= multiplier
            max_gain = max(gain, max_gain)
            
            if stop_type == "trailing":
                close_position = gain < max_gain - stop_loss
            elif stop_type == "race":
                # position is closed if stop_loss amount is lost OR gained
                close_position = abs(gain) > stop_loss
            else: raise Exception("Invalid stop type")
            
            if close_position or p == forward - 1:
                gains.append(gain)
                durations.append(p)
                entry_price = None
                trailing_stop = None
                break
    
    
    gains = np.array(gains)
    
    print("Stop loss results:", side, stop_type)
    print("Mean", gains.mean())
    print("Std", gains.std())
    print("Min", gains.min()) # only using close - loss can exceed stop amount
    print("Max", gains.max())
    print("Count", gains.shape[0])
    print("Average holding time:", np.array(durations).mean())
 

Note that when the stop_type parameter is set to race, the stop loss value remains the same throughout the trade and the trade is exited when the amount of the stop loss, e.g., 0.05% (using percentages instead of pips for easier calculation) is exceeded as a gain OR loss, i.e., the stop-loss amount is also the take-profit amount. This yields the following results using 2019 1-minute EURUSD prices:

Stop loss results: long trailing
Mean -6.676326476038819e-05
Std 0.0005539449863005583
Min -0.0019789614410355982
Max 0.003713987268880059
Count 1000
Average holding time: 101.665

Stop loss results: short trailing
Mean -1.4801988162509883e-05
Std 0.0006089588441524955
Min -0.0018916471674601532
Max 0.006041128848146338
Count 1000
Average holding time: 106.612

Stop loss results: long race
Mean -2.622936566547718e-05
Std 0.0005885432783222677
Min -0.0019789614410355982
Max 0.0018916471674601532
Count 1000
Average holding time: 104.207

Stop loss results: short race
Mean 2.622936566547718e-05
Std 0.0005885432783222677
Min -0.0018916471674601532
Max 0.0019789614410355982
Count 1000
Average holding time: 104.207

What's interesting to me about this is that the trailing stop loses regardless of whether long or short positions are entered. Granted, these are very small numbers, but the average of around -0.004% is greater than the usual market spread for the EURUSD using a broker such as Dukascopy (not including commissions).

I would not have expected this and would probably more have expected the opposite (i.e., that on short time frames, momentum would allow the trailing stop method to result in small gains on average). Using the race option fixes the imbalance, but I'm not sure how practical it is for actual trading.

I understand the value of trailing stops and I also understand how small these numbers would appear to anyone trading on a longer time frame, but I'm wondering if there's a way to still use stops but to turn that small loss into a small gain with each trade.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.