Daily Stock Signals from Fast and Slow Simple Moving Averages
Summary
This daily stock strategy compares a fast simple moving average of closing prices with a slower one. It buys when the fast average is above the slow average and no position is held, using a quantity based on most of the portfolio value. It sells the entire position when the fast average falls below the slow average. The example uses one stock, configurable averaging periods, historical price bars, and a backtest against a broad-market benchmark over a specified date range.
The rule is a basic trend-following signal, and the document reports no backtest results or evidence of profitability. It checks whether the averages are ordered at each iteration, rather than verifying that a fresh crossover occurred, so it may enter after the crossing day. The position sizing can concentrate nearly all portfolio value in one stock, while the example does not explain transaction costs, slippage, stop rules, or other risk controls. A benchmark is specified for comparison, but no comparison outcome is provided.
Key ideas
- The strategy buys when the fast closing-price SMA is above the slow SMA and no position exists.
- It exits the entire stock position when the fast SMA is below the slow SMA.
- Position size is calculated from nearly all portfolio value and the latest price.
- The example specifies a historical backtest period and a market benchmark but gives no results.
- The rules omit explicit crossover detection, transaction-cost treatment, and broader risk controls.
Tags
Full text
# sma_crossover.py
```py
"""
SMA Crossover
Asset class: Stocks
Data source: Yahoo Finance (free)
Description: Classic moving average crossover strategy. Buys when fast SMA
crosses above slow SMA, sells when it crosses below.
"""
from datetime import datetime
from lumibot.strategies import Strategy
from lumibot.backtesting import YahooDataBacktesting
class SmaCrossover(Strategy):
parameters = {
"symbol": "AAPL",
"fast_period": 10,
"slow_period": 30,
}
def initialize(self):
self.sleeptime = "1D"
def on_trading_iteration(self):
symbol = self.parameters["symbol"]
fast = self.parameters["fast_period"]
slow = self.parameters["slow_period"]
bars = self.get_historical_prices(symbol, slow + 5)
if bars is None:
return
df = bars.df
sma_fast = df["close"].rolling(fast).mean().iloc[-1]
sma_slow = df["close"].rolling(slow).mean().iloc[-1]
has_position = self.get_position(symbol) is not None
if sma_fast > sma_slow and not has_position:
price = self.get_last_price(symbol)
quantity = int(self.portfolio_value * 0.95 // price)
if quantity > 0:
order = self.create_order(symbol, quantity, "buy")
self.submit_order(order)
self.log_message(f"BUY {quantity} {symbol} (fast SMA {sma_fast:.2f} > slow SMA {sma_slow:.2f})")
elif sma_fast < sma_slow and has_position:
self.sell_all()
self.log_message(f"SELL ALL {symbol} (fast SMA {sma_fast:.2f} < slow SMA {sma_slow:.2f})")
if __name__ == "__main__":
SmaCrossover.backtest(
YahooDataBacktesting,
datetime(2020, 1, 1),
datetime(2024, 1, 1),
benchmark_asset="SPY",
)
```Shown in full with attribution under the source's licence. Licence: MIT
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.