Moving Average Crossover Strategy#
Trading Risk Warning
IMPORTANT: All examples should be tested using demo accounts only!
- Trading involves substantial risk of loss
 - These examples are for educational purposes only
 - Always test with fake money before using real funds
 
Overview#
The Moving Average Crossover is one of the most widely used trading strategies in technical analysis. It uses two moving averages of different periods to generate trading signals:
| Signal Type | Description | 
|---|---|
| Buy signal | When the faster (shorter-period) moving average crosses above the slower (longer-period) moving average | 
| Sell signal | When the faster moving average crosses below the slower moving average | 
This strategy aims to identify potential trend changes in the market.
Strategy Logic#
| Step | Description | 
|---|---|
| 1 | Calculate two Simple Moving Averages (SMA): a short-period SMA and a long-period SMA | 
| 2 | Compare current and previous values of both SMAs to detect crossovers | 
| 3 | Generate buy signals when short SMA crosses above long SMA | 
| 4 | Generate sell signals when short SMA crosses below long SMA | 
| 5 | Execute trades only during specified trading hours | 
Strategy Flow#
flowchart TD
    A[Start] --> B[Fetch Market Data]
    B --> C[Calculate Short-Period SMA]
    B --> D[Calculate Long-Period SMA]
    C --> E[Compare Current & Previous SMAs]
    D --> E
    E --> F{"Short MA Crossed<br/>Above Long MA?"}
    F -->|Yes| G[Generate Buy Signal]
    F -->|No| H{"Short MA Crossed<br/>Below Long MA?"}
    H -->|Yes| I[Generate Sell Signal]
    H -->|No| J[No Trading Signal]
    G --> K{"Within Trading<br/>Hours?"}
    I --> K
    J --> K
    K -->|Yes| L[Execute Trade]
    K -->|No| M[Skip Trade Execution]
    L --> N[Update Statistics]
    M --> N
    N --> O[Wait for Next Tick]
    O --> B
Code Implementation#
Let's break down the implementation step by step:
Step 1: Required Imports#
from __future__ import annotations
import logging
from mqpy.rates import Rates
from mqpy.tick import Tick
from mqpy.trade import Trade
# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
We import the necessary modules from MQPy:
- Rates: For accessing historical price data
- Tick: For accessing current market prices
- Trade: For executing trading operations
- logging: For keeping track of what the strategy is doing
Step 2: Define SMA Calculation Function#
def calculate_sma(prices: list[float], period: int) -> float | None:
    """Calculate Simple Moving Average."""
    if len(prices) < period:
        return None
    return sum(prices[-period:]) / period
This function calculates a Simple Moving Average (SMA) from a list of prices:
- It first checks if we have enough price data for the requested period
- If not, it returns None
- Otherwise, it calculates the average of the last period prices
Step 3: Initialize the Trading Strategy#
trade = Trade(
    expert_name="Moving Average Crossover",
    version="1.0",
    symbol="EURUSD",
    magic_number=567,
    lot=0.1,
    stop_loss=25,
    emergency_stop_loss=300,
    take_profit=25,
    emergency_take_profit=300,
    start_time="9:15",
    finishing_time="17:30",
    ending_time="17:50",
    fee=0.5,
)
Here we initialize the Trade object with our strategy parameters:
- expert_name: The name of our trading strategy
- symbol: The trading instrument (EURUSD in this case)
- magic_number: A unique identifier for this strategy's trades
- lot: The trading volume
- stop_loss/take_profit: Risk management parameters in points
- emergency_stop_loss/emergency_take_profit: Larger safety values if regular ones fail
- start_time/finishing_time/ending_time: Define the trading session hours
Step 4: Set Strategy Parameters#
# Strategy parameters
prev_tick_time = 0
short_period = 5
long_period = 20
# Variables to track previous state for crossover detection
prev_short_ma = None
prev_long_ma = None
We define the key parameters for our strategy:
- short_period: The period for the fast moving average (5 bars)
- long_period: The period for the slow moving average (20 bars)
- We also initialize variables to track the previous MA values for crossover detection
Step 5: Main Trading Loop#
try:
    while True:
        # Prepare the symbol for trading
        trade.prepare_symbol()
        # Fetch tick and rates data
        current_tick = Tick(trade.symbol)
        historical_rates = Rates(trade.symbol, long_period + 10, 0, 1)  # Get extra data for reliability
The main loop:
- Prepares the symbol for trading
- Gets the current market price via Tick
- Retrieves historical price data via Rates. We request slightly more data (long_period + 10) for reliability
Step 6: Calculate Moving Averages#
# Only process if we have a new tick
if current_tick.time_msc != prev_tick_time and len(historical_rates.close) >= long_period:
    # Calculate moving averages
    short_ma = calculate_sma(historical_rates.close, short_period)
    long_ma = calculate_sma(historical_rates.close, long_period)
For each new tick: - We check that it's different from the previous tick to avoid redundant calculations - We ensure we have enough historical data - We calculate both the short and long moving averages
Step 7: Detect Crossovers#
# Check if we have enough data for comparison
if short_ma and long_ma and prev_short_ma and prev_long_ma:
    # Detect crossover (short MA crosses above long MA)
    cross_above = prev_short_ma <= prev_long_ma and short_ma > long_ma
    # Detect crossunder (short MA crosses below long MA)
    cross_below = prev_short_ma >= prev_long_ma and short_ma < long_ma
    # Log crossover events
    if cross_above:
        logger.info(f"Bullish crossover detected: Short MA ({short_ma:.5f}) crossed above Long MA ({long_ma:.5f})")
    elif cross_below:
        logger.info(f"Bearish crossover detected: Short MA ({short_ma:.5f}) crossed below Long MA ({long_ma:.5f})")
To detect crossovers, we need both current and previous MA values:
- cross_above: Occurs when the short MA was below (or equal to) the long MA in the previous tick, but is now above it
- cross_below: Occurs when the short MA was above (or equal to) the long MA in the previous tick, but is now below it
- We log these events for monitoring the strategy
Step 8: Execute Trades#
# Execute trading positions based on signals
if trade.trading_time():  # Only trade during allowed hours
    trade.open_position(
        should_buy=cross_above,
        should_sell=cross_below,
        comment="Moving Average Crossover Strategy"
    )
When a signal is detected:
- We first check if we're within the allowed trading hours using trade.trading_time()
- If yes, we call open_position() with our buy/sell signals
- The comment parameter helps identify the strategy in the trading terminal
Step 9: Update State and Check End of Day#
# Update previous MA values for next comparison
prev_short_ma = short_ma
prev_long_ma = long_ma
# Update trading statistics periodically
trade.statistics()
prev_tick_time = current_tick.time_msc
# Check if it's the end of the trading day
if trade.days_end():
    trade.close_position("End of the trading day reached.")
    break
After processing each tick: - We update the previous MA values for the next iteration - We update trading statistics for monitoring - We update the previous tick time - We check if it's the end of the trading day, and if so, close positions and exit
Step 10: Error Handling#
except KeyboardInterrupt:
    logger.info("Strategy execution interrupted by user.")
    trade.close_position("User interrupted the strategy.")
except Exception as e:
    logger.error(f"Error in strategy execution: {e}")
finally:
    logger.info("Finishing the program.")
Proper error handling ensures:
- Clean exit when the user interrupts the program
- Logging of any errors that occur
- Proper cleanup in the finally block
Full Source Code#
You can find the complete source code for this strategy in the MQPy GitHub repository.
Backtesting and Optimization#
This strategy can be improved by:
| Improvement | Description | 
|---|---|
| Period Optimization | Finding the optimal MA periods for specific instruments | 
| Filter Addition | Adding filters to avoid false signals in ranging markets | 
| Position Sizing | Implementing dynamic position sizing based on market volatility | 
| Stop Management | Adding trailing stop-loss to secure profits as the trend develops | 
Next Steps#
Try experimenting with different:
| Experiment | Options | 
|---|---|
| MA Periods | Try pairs like 9 and 21, or 50 and 200 for different timeframes | 
| MA Types | Test Exponential, Weighted, or other MA types for potential improvements | 
| Instruments | Apply the strategy to various forex pairs, stocks, or commodities | 
| Timeframes | Scale from M1 (1-minute) to D1 (daily) charts for different trading styles |