US examples
AAPL Bollinger rebound
On this page5
Buying the dip is a classic contrarian strategy; this page tests it quantitatively on Apple (AAPL) with Bollinger Bands. It suits patient investors who want a single-stock, mean-reversion trade that waits for statistically extreme drops.
Bollinger Bands compute a moving average and standard deviation over a rolling window of d days, and draw an upper and a lower band n standard deviations from the mean. Most price action stays inside the bands, so a move beyond them signals a possible mean reversion.
- A larger
dgives a smoother price envelope that filters out short-term noise. - A larger
ngives more extreme, and rarer, signals. Following the 68-95-99.7 rule,nis usually between 1 and 3. Atn = 2, roughly 95% of prices fall inside the bands under a normal distribution.
A close below the lower band may be an oversold signal. The hypothesis is that the price then reverts and bounces back toward the middle band.
Strategy logic
- Bollinger Bands use a 40-day moving average and 2.5 standard deviations.
- Buy AAPL when the closing price falls below the lower band.
- Sell when the price recovers above the middle band.
- Track each trade's best and worst moves (MAE/MFE) over 40 days after entry.
Code
from finlab import data
from finlab.backtest import sim
from finlab.markets.us import USMarket
data.set_market('us')
close = data.get('price:adj_close')
close = close[close.index.dayofweek < 5] # remove weekends
upperband, middleband, lowerband = data.indicator(
'BBANDS', adjust_price=True, resample='D', nbdevup=2.5, nbdevdn=2.5, timeperiod=40
)
entries = lowerband > close
exits = close > middleband
position = entries.hold_until(exits)['AAPL']
report = sim(
position, market=USMarket(), fee_ratio=0.001, tax_ratio=0, mae_mfe_window=40
)
report.display()Read the results
report.display() draws the cumulative return chart. Two more views help with a contrarian strategy.
MAE (Maximum Adverse Excursion) and MFE (Maximum Favorable Excursion) measure the worst drawdown and the best run-up during each trade's holding period, so you can see the strategy's risk and reward trade by trade:
report.display_mae_mfe_analysis(violinmode='overlay').show()The trade records list every trade:
trade_record = report.trades
trade_recordHow to adjust it
adjust_price=True: computes the bands from adjusted prices, the same scale asprice:adj_close. The default (False) uses unadjusted prices, so after AAPL's stock splits the bands would sit far above the adjusted close.nbdevup=2.5/nbdevdn=2.5: 2.5 standard deviations for the upper and lower bands, which makes entry signals relatively rare and extreme.timeperiod=40: a 40-day window for the moving average and standard deviation, balancing smoothness with responsiveness.mae_mfe_window=40: tracks MAE/MFE over 40 days after each entry, a detailed picture of per-trade risk.fee_ratio=0.001: a 0.1% commission, a reasonable estimate for US brokerage fees.tax_ratio=0: US stock trades carry no transaction tax.
Add a stop-loss and a take-profit
Catching a falling knife is risky: whether or not you catch it, be ready to let go quickly. In extreme sell-offs this contrarian approach can achieve a high win rate with short holding periods, but explicit exit rules can significantly improve risk management:
- If the position is profitable during the first 10 holding days, the dip was likely caught near the bottom, and mean reversion is playing out as expected.
- If it is underwater during the first 10 days, the bottom may not have been reached yet, and further downside is possible.
- If the loss exceeds 15%, it may be a rare, out-of-sample crash, and the position should be cut immediately.
This version adds a 10% take-profit (take_profit=0.1, sell at a 10% gain from the entry price) and a 15% stop-loss (stop_loss=-0.15, sell at a 15% loss). Change the hold_until line and run the MAE/MFE chart with the report; everything else stays the same:
position = entries.hold_until(exits, take_profit=0.1, stop_loss=-0.15)['AAPL']
report = sim(
position, market=USMarket(), fee_ratio=0.001, tax_ratio=0, mae_mfe_window=40
)
report.display_mae_mfe_analysis(violinmode='overlay').show()
report.display()The stops reduce tail risk, at the cost of possibly capping some winning trades. The MAE/MFE analysis is especially useful for calibrating these stop-loss and take-profit levels from historical trades.
Related pages
- Technical indicators
data.indicator()and the indicators it can compute. - Analysis modules MAE/MFE and other ways to study trades.
- Technical indicator selection The momentum opposite: hold the highest-RSI stocks.
- Risk management Stop-loss and take-profit across a portfolio.
Was this page helpful?