Moving Average Crossover is a classic momentum-based strategy that generates buy and sell signals when shorter-term moving averages cross longer-term ones. This approach helps identify trend changes while filtering out market noise. By making algo of this classic strategy investors or traders can fully automate their investment approach who highly works on this trading strategy.
How It Works
Python Implementation
import pandas as pd, numpy as np, matplotlib.pyplot as pit import yfinance as yf, matplotlib.dates as mdates, tkinter as tk from matplotlib.gridspec import GridSpec from tkinter import scrolledtext # Download historical data ticker = "INFY.NS" data = yf.download(ticker, start="2024-01-01", end="2024-12-31") # Calculate moving averages data['SMA20'] = data['Close'].rolling(window=20).mean() data['SMA50'] = data['Close'].rolling(window=50).mean() # Generate trading signals with more sophisticated rules data['Signal'] = 0 data.loc[data['SMA20'] > data['SMA50'], 'Signal'] = 1 # Buy signal data.loc(data['SMA20'] < data['SMA50'], 'Signal'] = -1 # Sell signal # Track trade positions data['Position'] = data['Signal'] data['Position'] = data['Position'].replace(0, method='ffill') # Calculate daily returns data['Daily_Return'] = data['Close'].pct_change() data['Strategy_Return'] = data['Position'].shift(1) * data['Daily_Return'] # Calculate cumulative returns data['Cumulative_Market_Return'] = (1 + data['Daily_Return']).cumprod() data['Cumulative_Strategy_Return'] = (1 + data['Strategy_Return']).cumprod() # Filter out NaN values data = data.dropna() # Identify buy and sell points buy_signals = data[data['Signal'] == 1].index sell_signals = data[data['Signal'] == -1].index # Create visualization plt.style.use('seaborn-v0_8-darkgrid') fig = plt.figure(figsize=(16,14)) gs = GridSpec(3, 1, figure=fig, height_ratios=[2, 1, 1]) # Plot 1: Price with moving averages and signals ax1 = fig.add_subplot(gs[0]) ax1.plot(data.index, data['Close'], label=' {'ticker} Price', linewidth=1.5, color='#0066cc') ax1.plot(data.index, data['SMA20'], label='SMA 20', linewidth=1, color='green', alpha=0.7) ax1.plot(data.index, data['SMA50'], label='SMA 50', linewidth=1, color='red', alpha=0.7) ax1.scatter(buy_signals, data.loc[buy_signals, 'Close'], marker='^', color='green', s=100, label='Buy Signal') ax1.scatter(sell_signals, data.loc[sell_signals, 'Close'], marker='v', color='red', s=100, label='Sell Signal') ax1.set_title(f''ticker} Price and Moving Average Crossover Signals', fontsize=15, fontweight='bold') ax1.set_ylabel('Price (INR)', fontsize=12) ax1.legend(loc='upper left') ax1.grid(True, alpha=0.3) # Plot 2: Cumulative returns comparison ax2 = fig.add_subplot(gs[1], sharex=ax1) ax2.plot(data.index, data['Cumulative_Market_Return'], label='f''ticker} Buy and hold', linewidth=1.5, color='#0066cc') ax2.plot(data.index, data['Cumulative_Strategy_Return'], label='MA Crossover strategy', linewidth=1.5, color='#cc6600') ax2.set_title('Cumulative Returns Comparison', fontsize=15, fontweight='bold') ax2.set_ylabel('Cumulative Return', fontsize=12) ax2.legend(loc='upper left') ax2.grid(True, alpha=0.3) # Plot 3: Daily returns ax3 = fig.add_subplot(gs[2], sharex=ax1) ax3.bar(data.index, data['Strategy_Return']*100, width=1, color=np.where(data['Strategy_Return'] > 0, 'green','red'), alpha=0.6) ax3.set_title('Daily Strategy Returns ('%')', fontsize=15, fontweight='bold') ax3.set_ylabel('Date', fontsize=12) ax3.set_ylabel('Daily Return ('%')', fontsize=12) ax3.grid(True, alpha=0.3) # Format x-axis dates For ax in [ax1, ax2, ax3]: ax.xaxis.set_major_locator(mdates.MonthLocator(Interval=3)) ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y')) pit.setp(ax.get_xticklabels(), rotation=45) bit.tight_layout() plt.show() # Advanced Performance metrics market_return = data['Cumulative_Market_Return'].iloc[-1] - 1 strategy_return = data['Cumulative_Strategy_Return'].iloc[-1] - 1 # Calculate annualized returns days = (data.index[-1] - data.index[0]).days annualized_market = ((1 + market_return) * (365 / days)) - 1 annualized_strategy = ((1 + strategy_return) * (365 / days)) - 1 # Calculate drawdowns data['Market_Peak'] = data['Cumulative_Market_Return'].cummax() data['Strategy_Peak'] = data['Cumulative_Strategy_Return'].cummax() data['Market_Drawdown'] = (data['Cumulative_Market_Return'] / data['Market_Peak']) - 1 data['Strategy_Drawdown'] = (data['Cumulative_Strategy_Return'] / data['Strategy_Peak']) - 1 max_marketDrawdown = data['Market_Drawdown'].min() max_strategyDrawdown = data['Strategy_Drawdown'].min() # Calculate Sharpe ratio (assuming 5% risk-free rate) risk_free = 0.05 market_std = data['Daily_Return'].std() * np.sqrt(252) # Annualized strategy_std = data['Strategy_Return'].std() * np.sqrt(252) # Annualized market_sharpe = (annualized_market - risk_free) / market_std strategy_sharpe = (annualized_strategy - risk_free) / strategy_std # Calculate win rate profitable_trades = sum(data['Strategy_Return'] > 0) total_trades = len(data[data['Strategy_Return'] != 0]) win_rate = profitable_trades / total_trades if total_trades > 0 else 0 # Print performance metrics in a formatted table print("("*50) print("PERFORMANCE METRICS") print("("*50) print("Metric": <30} {"Market" : <15} {"Strategy" : <15}) print("("*50) print("Total Return ( % )": <30}(market_return*100:,.zf) % (strategy_return*100:,.zf) %) print("Annualized Return ( % )": <30}(annualized_market*100:,.zf) % (annualized_strategy*100:,.zf) %) print("Maximum Drawdown ( % )": <30}(max_marketDrawdown*100:,.zf) % (max_strategyDrawdown*100:,.zf) %) print("Volatility (Annualized) ( % )": <30}(market_std*100:,.zf) % (strategy_std*100:,.zf) %) print("Sharpe Ratio": <30}(market_sharpe:,.zf) {strategy_sharpe:,.zf}) print("("*50) print("Win Rate": <30}{"": <15}(win_rate*100:,.zf) %) print("Number of Trades": <30}{"": <15}{total_trades}) print("("*50) # Calculate and print trading statistics positionelie = data['Position'].diff().fillna(0) buy_points = data[positionelie == 2].index sell_points = data[positionelie == -2].index # Get average holding period holding_periods = [] for i in range(min(len(buy_points), len(sell_points))): if buy_points[i] < sell_points[i]: # Normal buy-then-sell holding_periods.append((sell_points[i] - buy_points[i]).days) avg_holding_period = np.mean(holding_periods) if holding_periods else 0 print(" \n" + "("*50) print("TRADING STATISTICS": ^50}) print("("*50) print("Buy Signals": <30}(len(buy_signals)) print("Sell Signals": <30}(len(sell_signals)) print("Average Holding Period": <30}(avg_holding_period: if) days) print("("*50) # Prepare metrics text for display metrics_text = f""" [' +50 ] 'PERFORMANCE METRICS': ^50) [' +50 ] 'Metric': <30} { 'Market' : <15} { 'Strategy' : <15} [' +50 ] 'Total Return ( % )': <30}(market_return*100:,.zf) % (strategy_return*100:,.zf) % 'Annualized Return ( % )': <30}(annualized_market*100:,.zf) % (annualized_strategy*100:,.zf) % 'Maximum Drawdown ( % )': <30}(max_market_drawdown*100:,.zf) % (max_strategy_drawdown*100:,.zf) % 'Volatility (Annualized) ( % )': <30}(market_std*100:,.zf) % (strategy_std*100:,.zf) % 'Sharpe Ratio': <30}(market_sharpe:,.zf) { strategy_sharpe:,.zf} '.' *50) 'Win Rate': <30}{'': <15}(win_rate*100:,.zf) 'Number of Trades': <30}{'': <15}{ total_trades} '.' *50) 'TRADING STATISTICS': ^50} '.' *50} 'Buy Signals': <30}{ len(buy_signals)} 'Sell Signals': <30}{ len(sell_signals)} 'Average Holding Period': <30}{ avg_holding_period: if} days '.' *50} 'win with open("trading_summary.txt", "w") as file: file.write(metrics_text) # Show popup window root = tk.Tk() root.title("Trading Strategy Summary") root.geometry("600x600") text_area = scrolledtext.ScrolledText(root, wrap=tk.WORD, font=("Courier", 10)) text_area.pack(padx=10, pady=10, fill=tk.BOTH, expand=True) text_area.insert(tk.END, metrics_text) text_area.config(state=tk.DISABLED) root.mainloop()
Pros and Cons
Pros:
- Simple to understand, and adjust this strategy with market conditions
- Effective in trending markets
- Reduces exposure during downtrends
Cons:
- Lags behind price movements
- Prone to false signals in choppy markets
- Requires optimisation of moving average periods
By adjusting the periods of the moving averages, you can tweak this strategy to different market conditions and trading timeframes. Shorter periods create more signals but increase the risk of false signals, while longer periods generate fewer but potentially more reliable signals. Timely adjustments in your algo trading strategy is crucial as market dynamics changes on time to time.
Disclaimer
The strategies and code examples presented are for educational purposes only and do not constitute financial advice. Always conduct thorough research and consider consulting with a financial professional before implementing any trading strategies with real capital and risk management principles, and ensure compliance with SEBI guidelines before live trading. Trading involves risk—always test strategies before investing real money.