I have a code to the strategy: Complete Strategy Logic: Implements rules:
Buy when MA_1 crossing MA_20
Set SL as 0.5xATR(5day)
Move SL next day (if not out) to the middle of 1st day bar (Open-Close)
Exit on stop loss
Mirror rules for short position
Money management:
1pip=1$, 10k initial capital
Markets:
EUR/USD  ; could you check the code pasted below if it has properly implementing rules? and what is wrong with if low_p <= stop_loss: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().     ; import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# Download EUR/USD daily data (2020-01-01 to 2024-12-31)
df = yf.download('EURUSD=X', start='2020-01-01', end='2024-12-31', interval='1d')

# Filter out weekends if any (usually yfinance excludes weekends)
df = df[df.index.dayofweek < 5]

# Calculate Moving Averages
# MA1: previous day's close (1-day MA shifted by 1 to avoid lookahead)
df['MA1'] = df['Close'].shift(1)
df['MA20'] = df['Close'].rolling(window=20).mean().shift(1)

# Calculate ATR(5)
high_low = df['High'] - df['Low']
high_close = np.abs(df['High'] - df['Close'].shift(1))
low_close = np.abs(df['Low'] - df['Close'].shift(1))
tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
df['ATR5'] = tr.rolling(window=5).mean()

# Initialize columns for signals, positions, trades, equity
df['Signal'] = 0
df['Position'] = 0
df['EntryPrice'] = np.nan
df['SL'] = np.nan
df['TradePnl'] = 0.0
df['Equity'] = 0.0

# Strategy parameters
capital = 10000  # Starting capital in USD
pip_value = 1    # $1 per pip
pip_size = 0.0001  # EUR/USD pip size

equity = capital
position = 0  # 1 = long, -1 = short, 0 = flat
entry_price = 0
stop_loss = 0
trade_open = False
entry_index = None

def half_open_close_bar(open_p, close_p):
    return abs(open_p - close_p) / 2

# Main loop over data starting from index 2 (to access i-2 safely)
for i in range(2, len(df)):
    today = df.index[i]
    yesterday = df.index[i-1]

    # Get moving averages by position (avoid .at to prevent indexing errors)
    prev2_ma1 = df['MA1'].iloc[i-2]
    prev2_ma20 = df['MA20'].iloc[i-2]
    prev_ma1 = df['MA1'].iloc[i-1]
    prev_ma20 = df['MA20'].iloc[i-1]

    # Check for NaN in MAs or ATR
    if pd.isna(prev2_ma1) or pd.isna(prev2_ma20) or pd.isna(prev_ma1) or pd.isna(prev_ma20) or pd.isna(df['ATR5'].iloc[i-1]):
        df.at[today, 'Equity'] = equity
        continue

    atr5 = df['ATR5'].iloc[i-1]

    # Determine trend
    uptrend = prev_ma1 > prev_ma20
    downtrend = prev_ma1 < prev_ma20

    # Detect crossovers on yesterday's MAs
    crossover_long = (prev2_ma1 <= prev2_ma20) and (prev_ma1 > prev_ma20) and uptrend
    crossover_short = (prev2_ma1 >= prev2_ma20) and (prev_ma1 < prev_ma20) and downtrend

    open_p = df['Open'].iloc[i]
    high_p = df['High'].iloc[i]
    low_p = df['Low'].iloc[i]
    close_p = df['Close'].iloc[i]

    # Manage existing position
    if position != 0:
        if position == 1:  # Long position
            # Check stop loss hit during the day
            if low_p <= stop_loss:
                # Stop loss triggered, exit at stop loss price
                trade_pnl = (stop_loss - entry_price) / pip_size * pip_value
                equity += trade_pnl
                df.at[today, 'TradePnl'] = trade_pnl
                df.at[today, 'Equity'] = equity
                position = 0
                trade_open = False
                df.at[today, 'Position'] = 0
                continue
            else:
                # If close higher than entry, move SL to half open-close bar of entry day
                if close_p > entry_price:
                    half_bar = entry_price + half_open_close_bar(df['Open'].loc[entry_index], df['Close'].loc[entry_index])
                    if half_bar > stop_loss:
                        stop_loss = half_bar

            # Exit rule: if low touches half open-close previous day
            half_bar_prev = df['Open'].iloc[i-1] + half_open_close_bar(df['Open'].iloc[i-1], df['Close'].iloc[i-1])
            if low_p <= half_bar_prev:
                # Exit long at close price
                trade_pnl = (close_p - entry_price) / pip_size * pip_value
                equity += trade_pnl
                df.at[today, 'TradePnl'] = trade_pnl
                df.at[today, 'Equity'] = equity
                position = 0
                trade_open = False
                df.at[today, 'Position'] = 0
                continue

            # Update unrealized PnL
            unrealized_pnl = (close_p - entry_price) / pip_size * pip_value
            df.at[today, 'Equity'] = equity + unrealized_pnl
            df.at[today, 'Position'] = position

        else:  # Short position
            # Check stop loss hit during the day
            if high_p >= stop_loss:
                # Stop loss triggered, exit at stop loss price
                trade_pnl = (entry_price - stop_loss) / pip_size * pip_value
                equity += trade_pnl
                df.at[today, 'TradePnl'] = trade_pnl
                df.at[today, 'Equity'] = equity
                position = 0
                trade_open = False
                df.at[today, 'Position'] = 0
                continue
            else:
                # If close lower than entry, move SL to half open-close bar of entry day
                if close_p < entry_price:
                    half_bar = entry_price - half_open_close_bar(df['Open'].loc[entry_index], df['Close'].loc[entry_index])
                    if half_bar < stop_loss:
                        stop_loss = half_bar

            # Exit rule: if high touches half open-close previous day
            half_bar_prev = df['Open'].iloc[i-1] - half_open_close_bar(df['Open'].iloc[i-1], df['Close'].iloc[i-1])
            if high_p >= half_bar_prev:
                # Exit short at close price
                trade_pnl = (entry_price - close_p) / pip_size * pip_value
                equity += trade_pnl
                df.at[today, 'TradePnl'] = trade_pnl
                df.at[today, 'Equity'] = equity
                position = 0
                trade_open = False
                df.at[today, 'Position'] = 0
                continue

            # Update unrealized PnL
            unrealized_pnl = (entry_price - close_p) / pip_size * pip_value
            df.at[today, 'Equity'] = equity + unrealized_pnl
            df.at[today, 'Position'] = position

    # No open position: check entry signals
    if position == 0:
        if crossover_long:
            # Enter long at today's open
            entry_price = open_p
            stop_loss = entry_price - 0.5 * atr5
            position = 1
            trade_open = True
            entry_index = today
            df.at[today, 'Signal'] = 1
            df.at[today, 'EntryPrice'] = entry_price
            df.at[today, 'SL'] = stop_loss
            df.at[today, 'Position'] = position
            df.at[today, 'Equity'] = equity
        elif crossover_short:
            # Enter short at today's open
            entry_price = open_p
            stop_loss = entry_price + 0.5 * atr5
            position = -1
            trade_open = True
            entry_index = today
            df.at[today, 'Signal'] = -1
            df.at[today, 'EntryPrice'] = entry_price
            df.at[today, 'SL'] = stop_loss
            df.at[today, 'Position'] = position
            df.at[today, 'Equity'] = equity
        else:
            df.at[today, 'Position'] = 0
            df.at[today, 'Equity'] = equity

# Fill forward equity for days without trades
df['Equity'] = df['Equity'].fillna(method='ffill').fillna(capital)

# Calculate performance metrics
total_trades = df['TradePnl'].astype(bool).sum()
winning_trades = (df['TradePnl'] > 0).sum()
losing_trades = (df['TradePnl'] < 0).sum()
win_ratio = winning_trades / total_trades if total_trades > 0 else 0
profit_factor = (df.loc[df['TradePnl'] > 0, 'TradePnl'].sum() / 
                 abs(df.loc[df['TradePnl'] < 0, 'TradePnl'].sum())) if losing_trades > 0 else np.nan
total_profit = df['TradePnl'].sum()
profit_percent = (equity - capital) / capital * 100

# Calculate CAGR
years = (df.index[-1] - df.index[0]).days / 365.25
cagr = ((equity / capital) ** (1 / years) - 1) * 100

# Calculate max drawdown
rolling_max = df['Equity'].cummax()
drawdown = df['Equity'] - rolling_max
max_dd = drawdown.min()

# Trades per month
months = years * 12
trades_per_month = total_trades / months if months > 0 else 0

# Print results
print(f"Total trades: {total_trades}")
print(f"Winning trades: {winning_trades}")
print(f"Losing trades: {losing_trades}")
print(f"Win ratio: {win_ratio:.2f}")
print(f"Profit factor: {profit_factor:.2f}")
print(f"Total profit ($): {total_profit:.2f}")
print(f"Profit %: {profit_percent:.2f}%")
print(f"CAGR: {cagr:.2f}%")
print(f"Max Drawdown ($): {max_dd:.2f}")
print(f"Trades per month: {trades_per_month:.2f}")

# Plot equity curve
plt.figure(figsize=(12,6))
plt.plot(df.index, df['Equity'], label='Equity Curve')
plt.title('Equity Curve of Momentum MA Crossover Strategy on EUR/USD')
plt.xlabel('Date')
plt.ylabel('Equity ($)')
plt.legend()
plt.grid()
plt.show()