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) 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) 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.