
Trusted by millions. Actually enjoyed by them too.
Morning Brew makes business news something you’ll actually look forward to — which is why over 4 million people read it every day.
Sure, the Brew’s take on the news is witty and sharp. But the games? Addictive. You might come for the crosswords and quizzes, but you’ll leave knowing the stories shaping your career and life.
Try Morning Brew’s newsletter for free — and join millions who keep up with the news because they want to, not because they have to.
Elite Quant Plan – 20% OFF (This Week Only)
You get immediate access to:
Full code from every article (including today’s HMM notebook)
Private GitHub repos & templates
All premium deep dives (3–5 per month)
2 × 1-on-1 calls with me
One custom bot built/fixed for you
→ Start your free trial now 👇
(Doors close in 7 days or when the post goes out of the spotlight — whichever comes first.)
See you on the inside.
👉 Upgrade Now →
🔔 Limited-Time Holiday Deal: 20% Off Our Complete 2026 Playbook! 🔔
Level up before the year ends!
AlgoEdge Insights: 30+ Python-Powered Trading Strategies – The Complete 2026 Playbook
30+ battle-tested algorithmic trading strategies from the AlgoEdge Insights newsletter – fully coded in Python, backtested, and ready to deploy. Your full arsenal for dominating 2026 markets.
Special Promo: Use code JANUARY2026 for 20% off
Valid only until January 25, 2026 — act fast!
👇 Buy Now & Save 👇
Instant access to every strategy we've shared, plus exclusive extras.
— AlgoEdge Insights Team
Premium Members – Your Full Notebook Is Ready
The complete Google Colab notebook from today’s article (with live data, full Hidden Markov Model, interactive charts, statistics, and one-click CSV export) is waiting for you.
Preview of what you’ll get:

Inside:
Importance of Stop-Losses
Theory and Application of Bayesian Optimization
Walk-forward Optimization
Volatility-Based Dynamic Stop-Losses
Python Implementation
Other Stop-Losses Techniques
Beautiful interactive Plotly charts
Regime duration & performance tables
Ready-to-use CSV export
Bonus: works on Bitcoin, SPX, or any ticker with one line change
Free readers – you already got the full breakdown and visuals in the article. Paid members – you get the actual tool.
Not upgraded yet? Fix that in 10 seconds here👇
Google Collab Notebook With Full Code Is Available In the End Of The Article Behind The Paywall 👇 (For Paid Subs Only)
Entering a trade at the right time is important. But exiting when things go wrong is even more important. This is where stop-losses are useful.
A stop-loss defines the maximum loss you can accept. Without stop-losses, you need larger gains later to recover.
This process can help in managing emotions and lowers stress. More so when you need to set dynamic stop-losses as market moves.
We will implement dynamic, volatility-based stop-losses. We will also optimize the parameters using Bayesian and walk-forward techniques.
The method adapts to various market scenarios. You can find an end-to-end example in a Google Colab Notebook.

This article is structured as follows:
Importance of Stop-Losses
Theory and Application of Bayesian Optimization
Walk-forward Optimization
Volatility-Based Dynamic Stop-Losses
Python Implementation
Other Stop-Losses Techniques
Dalio: “Stocks Only Look Strong in Dollar Terms.” Here’s a Globally Priced Alternative for Diversification.
Ray Dalio recently reported that much of the S&P 500’s 2025 gains came not from real growth, but from the dollar quietly losing value. Reportedly down 10% last year!
He’s not alone. Several BlackRock, Fidelity, and Bloomberg analysts say to expect further dollar decline in 2026.
So, even when your U.S. assets look “up,” your purchasing power may actually be down.
Which is why many investors are adding globally priced, scarce assets to their portfolios—like art.
Art is traded on a global stage, making it largely resistant to currency swings.
Now, Masterworks is opening access to invest in artworks featuring legends like Banksy, Basquiat, and Picasso as a low-correlation asset class with attractive appreciation historically (1995-2025).*
Masterworks’ 26 sales have yielded annualized net returns like 14.6%, 17.6%, and 17.8%.
They handle the sourcing, storage, and sale. You just click to invest.
Special offer for my subscribers:
*Based on Masterworks data. Investing involves risk. Past performance is not indicative of future returns. Important Reg A disclosures: masterworks.com/cd.
1. Importance of Stop-Losses
Stop-losses protect your trades from large losses. They are very important in risk management in volatile markets.
For example, when the oil price crashed, many oil companies saw share prices drop.
Whiting Petroleum even filed for bankruptcy (Forbes). Proper stop-loss usage could have reduced the impact by selling earlier (Bloomberg).
2. Volatility-Based Stop Losses
Recent studies show that volatility-based stop losses can help manage risk. They can also boost portfolio performance.
Traditional stop-loss rules might fail in certain random walk environments and in trending markets, volatility-based stop-losses work better (ar5iv).
These stop-losses can scale with volatility to balance risk and return. However, their success depends on parameter calibration (SpringerLink).
Average True Range (ATR)
A common way to set a volatility-based stop-loss is the Average True Range. ATR measures market volatility over a chosen window (often 14 periods).
ATR is calculated using the following formula:

Where:
n is the number of periods.
TRi is the True Range for each period.
True Range is the maximum of:
Current high — current low.
Absolute value of (current high — previous close).
Absolute value of (current low — previous close).
A trader might then set a stop-loss at:

For example, if the ATR is $1.50 and the multiplier is 2, the stop-loss is $3 from the close, up and down.
During high-volatility events, a multiplier would be higher to avoid quick stop-outs. The multiplier is dependent on trader’s risk tolerance.
3. Bayesian Optimization
Bayesian optimization finds optimal parameters for functions that are costly to evaluate.
In trading, it helps tune algorithmic strategies. It uses a probabilistic model (often a Gaussian process) to approximate the objective function.
It balances exploration (trying new parameters) and exploitation (improving known good ones).
Process
1. Define the objective function f(x), which is the function we want to optimize. In trading, this could be strategy returns.
2. Choose a Surrogate Model: Often a Gaussian process. It has a mean function μ(x) and a covariance function k(x,x′).
3. Acquisition Function: Guides the next point to sample. Common ones are Expected Improvement (EI) and Upper Confidence Bound (UCB).

x+ is the current best observation.
4. Iterate: Evaluate the objective function at the chosen point, update the surrogate model with the new data, and repeat until convergence.
Benefits
Efficiency: Fewer evaluations than grid or random search.
Adaptability: Handles complex, noisy, and expensive functions.
Flexibility: You can add prior knowledge to speed up the process.
If You Could Be Earlier Than 85% of the Market?
Most read the move after it runs. The top 250K start before the bell.
Elite Trade Club turns noise into a five-minute plan—what’s moving, why it matters, and the stocks to watch now. Miss it and you chase.
Catch it and you decide.
By joining, you’ll receive Elite Trade Club emails and select partner insights. See Privacy Policy.
4. Walk-Forward Optimization
Walk-forward optimization evaluates and refines a trading strategy over multiple rolling windows. This prevents overfitting to past data.
It suits dynamic stop-loss settings because it adapts to new conditions. You split data into training and testing segments.
You optimize parameters on the training set, then apply them on the next test set. You repeat until you cover all periods.
Process
1. Divide Data into Segments: Split the historical data into training and testing segments. The data is divided into overlapping windows to ensure each segment has both training and testing periods.

2. Optimize on Training Set: Optimize the strategy parameters on the training set to maximize the objective function, such as returns.
3. Test on Testing Set: Apply the optimized parameters to the testing set to evaluate performance. Record the results and move the window forward.
4. Iterate: Repeat the process for the entire dataset. Move the training and testing windows forward by a fixed step each time.
Benefits
Reduces Overfitting: Uses out-of-sample validation.
Adaptation: Parameters update as market behavior shifts.
Improves Robustness: Ensures consistency over different segments.
5. Dynamic Stop-Losses Methodology
We set stop-losses using technical indicators and walk-forward optimization.
First, we calculate indicators like ATR, EMAs, MACD, and RSI for entry signals.
Then we apply a stop-loss that is a multiple of the ATR. We optimize that multiplier (and other inputs) using Bayesian optimization. The aim is maximizing cumulative returns in backtests.
We then use walk-forward optimization to confirm if the chosen parameters hold up over varying market phases. Finally, we enforce a minimum holding period to reduce quick flips.
6. Python Implementation
We start with a simple script for ATR-based stop-losses. Then we optimize parameters.
After that, we compare the optimized approach to a baseline with no stop-losses.
Everything is done in Python. End-to-end Google colab notebook provided below for easy execution.
6.1 Simple ATR-Based Stop Losses
The script below (i) fetches data, (ii) computes the ATR, and (iii) sets dynamic stop-losses. The stop losses adjust based on market volatility.
While this implementation provides a dynamic approach, it is not optimized for the best parameters. Optimization will be addressed next.
import warnings
import yfinance as yf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
warnings.filterwarnings('ignore')
def fetch_data(symbol, start, end):
data = yf.download(symbol, start=start, end=end)
if data.empty:
raise ValueError("No data fetched.")
return data
def calculate_atr(data, atr_window):
data['TR'] = np.maximum((data['High'] - data['Low']),
np.maximum(abs(data['High'] - data['Close'].shift(1)),
abs(data['Low'] - data['Close'].shift(1))))
data['ATR'] = data['TR'].rolling(window=atr_window).mean()
return data
def calculate_stop_loss(data, atr_multiplier):
data['Buy_Stop_Loss'] = data['Close'] - (data['ATR'] * atr_multiplier)
data['Sell_Stop_Loss'] = data['Close'] + (data['ATR'] * atr_multiplier)
return data
def plot_atr_boundaries(data, symbol, atr_multiplier):
plt.figure(figsize=(14, 7))
plt.plot(data['Close'], label='Close Price', color='blue')
plt.plot(data['Buy_Stop_Loss'], label=f'Buy Stop Loss (ATR x {atr_multiplier})', linestyle='--', color='red')
plt.plot(data['Sell_Stop_Loss'], label=f'Sell Stop Loss (ATR x {atr_multiplier})', linestyle='--', color='green')
last_close = data['Close'].iloc[-1]
last_buy_sl = data['Buy_Stop_Loss'].iloc[-1]
last_sell_sl = data['Sell_Stop_Loss'].iloc[-1]
plt.text(data.index[-1], last_close, f'Last Close: {last_close:.2f}', color='blue', ha='right')
plt.text(data.index[-1], last_buy_sl, f'Buy SL: {last_buy_sl:.2f}', color='red', ha='right')
plt.text(data.index[-1], last_sell_sl, f'Sell SL: {last_sell_sl:.2f}', color='green', ha='right')
plt.title(f'{symbol} ATR Boundaries')
plt.xlabel('Date')
plt.ylabel('Price')
plt.legend()
plt.show()
# Execution
symbol = 'ASML'
start_date = '2022-01-01'
end_date = '2025-01-01'
atr_multiplier = 3
atr_window = 50
data = fetch_data(symbol, start_date, end_date)
if not data.empty:
data = calculate_atr(data, atr_window)
data = calculate_stop_loss(data, atr_multiplier)
plot_atr_boundaries(data, symbol, atr_multiplier)
Figure. 1: ASML price with ATR-based stop-losses. The blue line is the close, and the dashed lines are buy/sell stop-losses.
6.2 Optimized Approach
Now, we use bayesian optimization to refine the ATR-based stop-losses. It then applies walk-forward optimization to confirm reliability.
6.2.1 Set up Environment
We install and import libraries. BayesianOptimization is used for optimizing the parameters and yfinance for fetching free stock price data.
!pip install bayesian-optimization
import warnings
import yfinance as yf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from bayes_opt import BayesianOptimization
warnings.filterwarnings('ignore')def fetch_data(symbol, start, end):
data = yf.download(symbol, start=start, end=end)
if data.empty:
raise ValueError("No data fetched.")
return data6.2.2 Signal Generaion
We generate trading signals using various technical indicators.
These indicators include ATR for volatility measurement, EMAs for trend identification, MACD for momentum, and RSI for overbought/oversold conditions.
# Function to compute Relative Strength Index
def compute_RSI(series, period=14):
delta = series.diff(1) # Calculate the difference between consecutive closing prices
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean() # Calculate average gains
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean() # Calculate average losses
RS = gain / loss # Relative Strength
RSI = 100 - (100 / (1 + RS)) # Compute RSI
return RSI
# Function to calculate technical indicators
def calculate_indicators(data):
data['TR'] = np.maximum((data['High'] - data['Low']),
np.maximum(abs(data['High'] - data['Close'].shift(1)),
abs(data['Low'] - data['Close'].shift(1))))
data['ATR'] = data['TR'].rolling(window=14).mean()
data['EMA12'] = data['Close'].ewm(span=12, adjust=False).mean()
data['EMA26'] = data['Close'].ewm(span=26, adjust=False).mean()
data['MACD'] = data['EMA12'] - data['EMA26']
data['Signal'] = data['MACD'].ewm(span=9, adjust=False).mean()
data['RSI'] = compute_RSI(data['Close'])
return data6.2.3 Dynamic Stop-Losses
The function below calculates dynamic stop-loss levels based on the ATR and a multiplier which will be determined from the optimization process.
def calculate_stop_loss(data, atr_multiplier):
data['Buy_Stop_Loss'] = data['Close'] - (data['ATR'] * atr_multiplier) # Calculate buy stop loss level
data['Sell_Stop_Loss'] = data['Close'] + (data['ATR'] * atr_multiplier) # Calculate sell stop loss level
return data # Return the updated data with stop loss levels6.2.4 Optimizing Stop-Losses Parameters
The backtesting strategy evaluates the performance of dynamic stop-losses.
Bayesian Optimization is then used to find the optimal ATR multiplier and window size.
Back Testing Strategy Logic
ATR Multiplier Adjustment: Ensures the ATR multiplier is at least the minimum specified value.
Stop Loss Calculation: Uses the ATR and adjusted multiplier to set dynamic buy and sell stop loss levels.
Signal Generation: Generates buy and sell signals based on stop loss levels, MACD, and RSI.
Enforcing Minimum Holding Period: Ensures buy and sell signals respect a minimum holding period to prevent frequent trading.
Return Calculation: Calculates daily and strategy returns based on the signals and computes the cumulative returns.
Final Return: Returns the final cumulative return of the strategy, which serves as the objective for optimization.
def backtest_strategy(atr_multiplier, atr_window, data, min_atr_multiplier, min_holding_period):
atr_multiplier = max(atr_multiplier, min_atr_multiplier) # Ensure ATR multiplier is at least the minimum
data['ATR'] = data['TR'].rolling(window=int(atr_window)).mean() # Calculate ATR with specified window
data = calculate_stop_loss(data.copy(), atr_multiplier) # Calculate stop loss levels
# Generate buy signals
buy_signals = ((data['Close'] > data['Buy_Stop_Loss']) & (data['MACD'] > data['Signal']) & (data['RSI'] < 70)).shift(1).fillna(False)
# Generate sell signals
sell_signals = ((data['Close'] < data['Sell_Stop_Loss']) & (data['MACD'] < data['Signal']) & (data['RSI'] > 30)).shift(1).fillna(False)
buy_signals = enforce_min_holding_period(buy_signals, min_holding_period) # Enforce minimum holding period for buy signals
sell_signals = enforce_min_holding_period(sell_signals, min_holding_period) # Enforce minimum holding period for sell signals
returns = data['Close'].pct_change().fillna(0) # Calculate daily returns
strategy_returns = returns * buy_signals.astype(int) - returns * sell_signals.astype(int) # Calculate strategy returns
cumulative_returns = (1 + strategy_returns).cumprod() # Calculate cumulative returns
return cumulative_returns.iloc[-1] # Return the final cumulative returnFinding Optimal Parameters
Objective Function Definition: Defines an objective function that returns the final cumulative return of the backtested strategy.
Parameter Bounds: Sets bounds for the ATR multiplier and window (e.g. ATR multiplier: min value to 3, ATR window: 5 to 60 periods).
Bayesian Optimization Setup: Initializes a Bayesian Optimizer with the objective function and parameter bounds.
Optimization Execution: Runs the optimization process, evaluating the objective function with different parameter values.
Optimal Parameters: Returns the ATR multiplier and window that resulted in the highest cumulative return during backtesting.
def optimize_parameters(data, min_atr_multiplier, min_holding_period):
def objective(atr_multiplier, atr_window):
return backtest_strategy(atr_multiplier, atr_window, data, min_atr_multiplier, min_holding_period) # Objective function for optimization
optimizer = BayesianOptimization(
f=objective,
pbounds={"atr_multiplier": (min_atr_multiplier, 3), "atr_window": (5, 60)}, # Parameter bounds for optimization, i.e between min atr and 3
random_state=42
)
optimizer.maximize(init_points=10, n_iter=100) # Run optimization: start with 10 random evaluations, then perform 100 additional iterations
return optimizer.max['params'] # Return the best parameters6.2.5 Walk-Forward Optimization
Walk-forward optimization splits the data into rolling windows. Each window has a training segment and a test segment.
We optimize parameters on training, then apply them to the next test. We repeat until we cover all data windows.
Retrieving the Best Parameters
Parameter Optimization: In each training period, Bayesian Optimization is used to find the optimal ATR multiplier and window.
Application to Testing Period: The optimized parameters are applied to the subsequent testing period to evaluate the strategy’s performance.
Evaluation and Recording: The strategy’s performance, including cumulative returns, is recorded for each testing period.
Compilation of Results: After all segments have been processed, the results are compiled into a DataFrame. This DataFrame includes the final returns and other performance metrics for each testing period.
For comparison, we also compute the ‘BuyOrSell-and-Hold-Until-Close’ returns.
This strategy calculates returns based on holding a position after receiving a buy signal until a sell signal is generated.
#The enforce_min_holding_period function ensures that trading signals respect a minimum holding period to prevent frequent trades.
def enforce_min_holding_period(signals, min_holding_period):
hold_signals = signals.copy() # Make a copy of the signals
for i in range(1, len(signals)):
if signals.iloc[i]: # If there is a signal at position i
hold_signals.iloc[i+1:i+min_holding_period] = False # Enforce holding period by setting following signals to False
return hold_signals # Return the modified signals
# The walk_forward_optimization function performs walk-forward optimization, training and testing the strategy over different periods to evaluate its performance.
def walk_forward_optimization(data, training_window=200, testing_window=50, min_atr_multiplier=1, min_holding_period=5):
total_length = len(data) # Total length of the data
n_splits = (total_length - training_window) // testing_window # Calculate the number of splits for walk-forward optimization
results = [] # List to store the results
for i in range(n_splits):
train_start = i * testing_window # Start index of the training data
train_end = train_start + training_window # End index of the training data
test_start = train_end # Start index of the testing data
test_end = test_start + testing_window # End index of the testing data
if test_end > total_length: # If test end exceeds data length, exit the loop
print(f"Test end {test_end} exceeds data length {total_length}")
break
train_data = data.iloc[train_start:train_end] # Extract training data
test_data = data.iloc[test_start:test_end] # Extract testing data
print(f"Training data range: {train_start} to {train_end}")
print(f"Testing data range: {test_start} to {test_end}")
optimized_params = optimize_parameters(train_data, min_atr_multiplier, min_holding_period) # Optimize parameters on training data
optimized_multiplier = optimized_params['atr_multiplier'] # Get the optimized ATR multiplier
optimized_window = optimized_params['atr_window'] # Get the optimized ATR window
test_data['ATR'] = test_data['TR'].rolling(window=int(optimized_window)).mean() # Calculate ATR on test data
test_data = calculate_stop_loss(test_data, optimized_multiplier) # Calculate stop loss levels on test data
# Generate buy signals on test data
test_data['Buy_Signal'] = ((test_data['Close'] > test_data['Buy_Stop_Loss']) & (test_data['MACD'] > test_data['Signal']) & (test_data['RSI'] < 70)).shift(1).fillna(False)
# Generate sell signals on test data
test_data['Sell_Signal'] = ((test_data['Close'] < test_data['Sell_Stop_Loss']) & (test_data['MACD'] < test_data['Signal']) & (test_data['RSI'] > 30)).shift(1).fillna(False)
# Enforce minimum holding period on buy and sell signals
test_data['Buy_Signal'] = enforce_min_holding_period(test_data['Buy_Signal'], min_holding_period)
test_data['Sell_Signal'] = enforce_min_holding_period(test_data['Sell_Signal'], min_holding_period)
test_data['Returns'] = test_data['Close'].pct_change().fillna(0) # Calculate daily returns
test_data['Strategy_Returns'] = test_data['Returns'] * test_data['Buy_Signal'].astype(int) - test_data['Returns'] * test_data['Sell_Signal'].astype(int) # Calculate strategy returns
test_data['Cumulative_Strategy_Returns'] = (1 + test_data['Strategy_Returns']).cumprod() # Calculate cumulative returns
final_return = test_data['Cumulative_Strategy_Returns'].iloc[-1] # Get the final cumulative return
# Calculate additional performance metrics
sharpe_ratio = test_data['Strategy_Returns'].mean() / test_data['Strategy_Returns'].std() * np.sqrt(252)
downside_std = test_data['Strategy_Returns'][test_data['Strategy_Returns'] < 0].std()
sortino_ratio = test_data['Strategy_Returns'].mean() / downside_std * np.sqrt(252) if downside_std != 0 else np.nan
max_drawdown = (test_data['Cumulative_Strategy_Returns'].cummax() - test_data['Cumulative_Strategy_Returns']).max()
calmar_ratio = test_data['Strategy_Returns'].mean() * 252 / max_drawdown if max_drawdown != 0 else np.nan
results.append({
'Final_Return': final_return,
'Sharpe_Ratio': sharpe_ratio,
'Sortino_Ratio': sortino_ratio,
'Max_Drawdown': max_drawdown,
'Calmar_Ratio': calmar_ratio
})
return pd.DataFrame(results) # Return the results as a DataFrame6.2.6 Execute Functions and Plot Results
Finally, we execute the entire chain of functions, optimize the parameters, and plot the results to visualize the performance of the dynamic stop-loss strategy.
# Function to plot the final results
def plot_results(data, symbol):
plt.figure(figsize=(14, 7))
plt.plot(data['Close'], label='Close Price', color='blue')
plt.plot(data['Buy_Stop_Loss'], label='Buy Stop Loss', linestyle='--', color='red')
plt.plot(data['Sell_Stop_Loss'], label='Sell Stop Loss', linestyle='--', color='green')
# Adding annotations for the last values without arrows
plt.text(data.index[-1], data['Close'].iloc[-1], f'Last Close: {data["Close"].iloc[-1]:.2f}', color='blue', ha='right')
plt.text(data.index[-1], data['Buy_Stop_Loss'].iloc[-1], f'Buy SL: {data["Buy_Stop_Loss"].iloc[-1]:.2f}', color='red', ha='right')
plt.text(data.index[-1], data['Sell_Stop_Loss'].iloc[-1], f'Sell SL: {data["Sell_Stop_Loss"].iloc[-1]:.2f}', color='green', ha='right')
plt.title(f'{symbol} Stop Loss Levels based on Optimized ATR')
plt.xlabel('Date')
plt.ylabel('Price')
plt.legend()
plt.show()
plt.figure(figsize=(14, 7))
plt.plot(data['Cumulative_Strategy_Returns'], label='Strategy Returns', color='purple')
plt.plot(data['Cumulative_Hold_Returns'], label='BuyOrSell-and-Hold-Until-Close Returns', color='orange')
plt.title(f'{symbol} Cumulative Strategy vs BuyOrSell-and-Hold-Until-Close Returns')
plt.xlabel('Date')
plt.ylabel('Cumulative Returns')
plt.legend()
plt.show()
# Execution
symbol = 'ASML' # Define stock symbol
data = fetch_data(symbol, '2022-01-01', '2025-01-01') # Fetch data for the specified date range
min_atr_multiplier = 2 # Define minimum ATR multiplier
min_holding_period = 1 # Define minimum holding period
# Execute the entire chain of functions
if not data.empty:
data = calculate_indicators(data)
results = walk_forward_optimization(data, min_atr_multiplier=min_atr_multiplier, min_holding_period=min_holding_period)
print(results)
optimized_params = optimize_parameters(data, min_atr_multiplier=min_atr_multiplier, min_holding_period=min_holding_period)
optimized_multiplier = optimized_params['atr_multiplier']
optimized_window = optimized_params['atr_window']
print(f'Optimized ATR Multiplier: {optimized_multiplier}')
print(f'Optimized ATR Window: {optimized_window}')
data['ATR'] = data['TR'].rolling(window=int(optimized_window)).mean()
data = calculate_stop_loss(data, optimized_multiplier)
data['Buy_Signal'] = ((data['Close'] > data['Buy_Stop_Loss']) & (data['MACD'] > data['Signal']) & (data['RSI'] < 70)).shift(1).fillna(False)
data['Sell_Signal'] = ((data['Close'] < data['Sell_Stop_Loss']) & (data['MACD'] < data['Signal']) & (data['RSI'] > 30)).shift(1).fillna(False)
data['Buy_Signal'] = enforce_min_holding_period(data['Buy_Signal'], min_holding_period)
data['Sell_Signal'] = enforce_min_holding_period(data['Sell_Signal'], min_holding_period)
data['Returns'] = data['Close'].pct_change().fillna(0)
data['Strategy_Returns'] = data['Returns'] * data['Buy_Signal'].astype(int) - data['Returns'] * data['Sell_Signal'].astype(int)
data['Cumulative_Strategy_Returns'] = (1 + data['Strategy_Returns']).cumprod()
# Adjusted BuyOrSell-and-Hold-Until-Close Strategy: Hold after Buy Signal until Sell Signal
data['Hold_Signal'] = data['Buy_Signal'].astype(int) - data['Sell_Signal'].astype(int).cumsum().clip(lower=0).astype(bool)
data['Hold_Returns'] = data['Returns'] * data['Hold_Signal'].astype(int)
data['Cumulative_Hold_Returns'] = (1 + data['Hold_Returns']).cumprod()
plot_results(data, symbol)Optimized ATR Multiplier: 3.0
Optimized ATR Window: 50.76339365041722

Figure 2. Cumulative profit for the optimized dynamic stop-loss strategy vs a simpler approach.
Concluding Thoughts
This methodology is algorithmic and avoids some emotional trading errors. It does not capture factors like investor sentiment or herd behavior.
Such factors can shift prices in ways that technical indicators miss.
Traders must keep an eye on actual market conditions. Stop-losses are needed for preserving capital.
Exiting a losing trade in time can matter more than finding the perfect entry.
In many cases, avoiding big drawdowns is a better outcome than trying to recoup large losses later.
Subscribe to our premium content to read the rest.
Become a paying subscriber to get access to this post and other subscriber-only content.
Upgrade






