50-day SMA trend with 10% stop: backtest results
Trade the 50-day moving average: buy when price closes above it, sell when it closes below, with a 10% stop loss. We ran it on real daily data for 19 stocks. It beat simply buying and holding on 4 of 19 stocks.
Results by stock
| Stock | Period | Strategy | Buy & hold | Beat it? | Worst drop | Trades | Win rate | Caution |
|---|---|---|---|---|---|---|---|---|
| Apple (AAPL) | 2016 → 2025 | +350.6% | +1038.8% | no | -33.6% | 70 | 31.4% | includes a position still open at the end |
| Amazon (AMZN) | 2016 → 2025 | +278.0% | +613.2% | no | -50.5% | 84 | 28.6% | includes a position still open at the end; fell 51% at its worst |
| AUD/USD (AUD/USD) | 2025 → 2026 | +7.0% | +7.6% | no | -4.0% | 3 | 33.3% | only 3 closed trades; includes a position still open at the end |
| BTC/USD (BTC/USD) | 2025 → 2026 | +7.4% | -35.1% | yes | -17.6% | 9 | 11.1% | only 9 closed trades; includes a position still open at the end |
| DIA (DIA) | 2025 → 2026 | +3.1% | +11.2% | no | -4.0% | 5 | 40.0% | only 5 closed trades |
| ETH/USD (ETH/USD) | 2025 → 2026 | +20.7% | -47.8% | yes | -22.1% | 3 | 33.3% | only 3 closed trades; includes a position still open at the end |
| EUR/USD (EUR/USD) | 2025 → 2026 | -3.3% | -2.7% | no | -5.4% | 7 | 14.3% | only 7 closed trades |
| GBP/USD (GBP/USD) | 2025 → 2026 | -1.6% | -1.5% | no | -5.9% | 10 | 30.0% | |
| GLD (GLD) | 2025 → 2026 | +0.5% | +16.0% | no | -8.3% | 0 | – | only 0 closed trades; includes a position still open at the end |
| Robinhood (HOOD) | 2021 → 2025 | +321.4% | +223.5% | yes | -43.6% | 33 | 36.4% | fell 44% at its worst |
| IWM (IWM) | 2025 → 2026 | +7.5% | +17.6% | no | -4.8% | 4 | 25.0% | only 4 closed trades |
| QQQ (QQQ) | 2025 → 2026 | +3.7% | +17.5% | no | -11.6% | 12 | 25.0% | |
| SLV (SLV) | 2025 → 2026 | -12.5% | +50.6% | no | -18.4% | 9 | 33.3% | only 9 closed trades; includes a position still open at the end |
| SOL/USD (SOL/USD) | 2025 → 2026 | -22.6% | -60.5% | yes | -44.5% | 12 | 0.0% | includes a position still open at the end; fell 44% at its worst |
| SPY (SPY) | 2025 → 2026 | +3.4% | +13.8% | no | -7.6% | 10 | 40.0% | |
| Tesla (TSLA) | 2016 → 2025 | +1012.9% | +2877.2% | no | -61.9% | 75 | 26.7% | includes a position still open at the end; fell 62% at its worst |
| USD/JPY (USD/JPY) | 2025 → 2026 | -1.2% | +5.5% | no | -2.7% | 4 | 25.0% | only 4 closed trades |
| USO (USO) | 2025 → 2026 | +99.8% | +107.8% | no | -24.3% | 6 | 33.3% | only 6 closed trades; includes a position still open at the end |
| XAU/USD (XAU/USD) | 2025 → 2026 | +0.3% | +19.2% | no | -8.5% | 1 | 100.0% | only 1 closed trades; includes a position still open at the end |
Best: Tesla on +1012.9%. One set of rules, fixed in advance, run on every stock: no tuning to make the numbers look better. A strategy with few trades or a big drop deserves less trust than its return suggests.
The exact rules
Buy Apple when the close crosses above the 50 day SMA. Sell when the close crosses below the 50 day SMA. Stop loss 10%.
Paste those words into QuantProof and it runs the same test, shows every trade on the chart with entry, stop loss and take profit, and compares it with buy & hold. Change a number and run it again in seconds.
Run these rules now, freeFree TradingView indicator (Pine Script v5)
Copy this into TradingView's Pine Editor and click Add to chart. It marks the buy and sell signals and draws the levels.
//@version=5
// Indicator generated by QuantProof from your rules.
// Built for AAPL on the daily (1D) chart. Config cd8d868bfd.
// Buy when: close crosses above sma(50)
// Sell when: close crosses below sma(50)
// Stop loss: 10%
//
// Execution, identical in the QuantProof backtest, this script and the alerts:
// rules are read on the bar close, the order fills at the next bar's open
// 5 bps slippage per side and 1 bps fee per side are applied to every fill
// stop loss and take profit are measured from the filled entry price, stop first when a bar hits both
// one setup at a time: no new entry until the open one closes
// Marks where your rules say buy and sell, and adds alerts you can turn on in TradingView.
indicator("50-day SMA trend with 10% stop signals", overlay=true, max_boxes_count=300, max_lines_count=300, max_labels_count=300)
// Prices are written the way the market quotes them: a $500 coin does not have a target of 566.130924.
qpPrice(x) => str.tostring(x, x >= 1000 ? "#.##" : x >= 100 ? "#.###" : x >= 1 ? "#.#####" : "#.########")
sma50 = ta.sma(close, 50)
cross1 = ta.crossover(close, sma50)
cross2 = ta.crossunder(close, sma50)
entrySignal = cross1
exitSignal = cross2
// One setup at a time, in the states QuantProof uses:
// flat -> armed (signal on the close) -> active (filled at the next open) -> closing (exit signal) -> flat.
// While a setup is armed, active or closing, new entry signals are ignored.
var string state = "flat"
var float entryPrice = na
var string exitReason = ""
slipUp = 1 + 0.0005
slipDown = 1 - 0.0005
// 1. Orders queued on the previous close fill at this bar's open.
filledNow = state == "armed"
exitedAtOpen = state == "closing"
if filledNow
entryPrice := open * slipUp
state := "active"
if exitedAtOpen
state := "flat"
// 2. While the setup is active, the stop is checked before the target, as in the backtest.
stopLevel = entryPrice * (1 - 10.0 / 100)
targetLevel = na
stopHit = state == "active" and low <= stopLevel
targetHit = false
hitNow = stopHit or targetHit
if hitNow
exitReason := stopHit ? "stop loss" : "take profit"
state := "flat"
// 3. Rules are read on this close; the order waits for the next open.
// A setup can arm on the same bar an earlier one closed, exactly as the backtest does.
armNow = state == "flat" and entrySignal
closeNow = state == "active" and exitSignal
if armNow
state := "armed"
if closeNow
exitReason := "exit rule"
state := "closing"
exitPrice = stopHit ? math.min(open, stopLevel) * slipDown : targetHit ? math.max(open, targetLevel) * slipDown : open * slipDown
closedNow = hitNow or exitedAtOpen
if closedNow
label.new(bar_index, low, exitReason + " @ " + qpPrice(exitPrice), style=label.style_label_up, color=color.new(color.red, 25), textcolor=color.white, size=size.small)
// Levels stay drawn through the bar where the trade closes.
wasInTrade = state == "active" or state == "closing" or closedNow
levelBase = entryPrice
// The setup drawn as a position: target zone above the entry, stop zone below it, and the entry
// itself as a dashed line between them.
var box tpBox = na
var box slBox = na
var line entryLine = na
if filledNow
if not na(targetLevel)
tpBox := box.new(bar_index, math.max(targetLevel, entryPrice), bar_index + 1, math.min(targetLevel, entryPrice), border_color=color.new(color.teal, 55), bgcolor=color.new(color.teal, 88))
if not na(stopLevel)
slBox := box.new(bar_index, math.max(entryPrice, stopLevel), bar_index + 1, math.min(entryPrice, stopLevel), border_color=color.new(color.gray, 55), bgcolor=color.new(color.gray, 82))
entryLine := line.new(bar_index, entryPrice, bar_index + 1, entryPrice, color=color.new(color.silver, 10), style=line.style_dashed, width=1)
if wasInTrade
if not na(tpBox)
box.set_right(tpBox, bar_index + 1)
if not na(slBox)
box.set_right(slBox, bar_index + 1)
if not na(entryLine)
line.set_x2(entryLine, bar_index + 1)
// Position levels from your inputs
float entryLine = wasInTrade ? levelBase : na
float tpLine = na
float slLine = entryLine * (1 - 10.0 / 100)
pEntry = plot(entryLine, "Entry", color.new(color.gray, 0), 1, plot.style_linebr)
pTp = plot(tpLine, "Take profit (not set)", color.new(color.green, 0), 2, plot.style_linebr)
pSl = plot(slLine, "Stop loss 10%", color.new(color.red, 0), 2, plot.style_linebr)
fill(pEntry, pTp, color.new(color.green, 85))
fill(pEntry, pSl, color.new(color.red, 85))
if (wasInTrade) and not (wasInTrade)[1]
label.new(bar_index, high, "Entry " + qpPrice(entryLine) + "\nSL " + qpPrice(slLine) + " (-10%)", style=label.style_label_lower_left, color=color.new(color.gray, 20), textcolor=color.white, size=size.small)
plot(sma50, "SMA(50)", color.orange)
plotshape(armNow, "Setup armed (fills next open)", shape.triangleup, location.belowbar, color.new(color.teal, 40), size=size.tiny)
plotshape(filledNow, "Entry filled", shape.labelup, location.belowbar, color.new(color.teal, 0), text="BUY", textcolor=color.white, size=size.small)
plotshape(closedNow, "Exit", shape.labeldown, location.abovebar, color.new(color.red, 0), text="SELL", textcolor=color.white, size=size.small)
alertcondition(armNow, "Setup armed", "50-day SMA trend with 10% stop: {{ticker}} setup armed on the close, fills at the next open")
alertcondition(filledNow, "Entry filled", "50-day SMA trend with 10% stop: {{ticker}} entry filled at the open")
alertcondition(closedNow, "Setup closed", "50-day SMA trend with 10% stop: {{ticker}} setup closed")
Test your own version
- Different stock, crypto or forex pair? Change the ticker in the description.
- Add a stop loss or take profit and see if it helps.
- Saw a strategy on YouTube? Paste the link and get the rules, the backtest and the indicator.