When Price is Going Down, then trade for Sell, Like we do on Margin Trading, Sell Before Buy: import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from binance import Client
import datetime
import time

# ----------------------------- Configuration -----------------------------

# Binance API credentials (Public API does not require API keys for data retrieval)
# If you plan to extend this script for live trading, provide your API_KEY and API_SECRET
API_KEY = ''
API_SECRET = ''

# Initialize Binance Client
client = Client(API_KEY, API_SECRET)

# Trading Parameters
SYMBOL = 'BTCUSDT'            # Trading pair
INTERVAL = Client.KLINE_INTERVAL_1HOUR  # Timeframe for candlesticks (1 hour)
START_DATE = '2020-01-01'     # Start date for historical data
END_DATE = '2023-10-01'       # End date for historical data

# Moving Averages Periods
SMA_SHORT_PERIOD = 50    # Short-term SMA (e.g., 50 hours)
SMA_LONG_PERIOD = 200    # Long-term SMA (e.g., 200 hours)

# RSI Parameters
RSI_PERIOD = 14          # RSI calculation period

# Position Parameters
INITIAL_CAPITAL = 10000.0  # Starting with $10,000
TRADING_FEE = 0.00075      # 0.075% trading fee per transaction

# ----------------------------- Data Collection -----------------------------

def get_historical_data(symbol, interval, start, end):
    """
    Fetches historical klines (candlestick data) from Binance.
    Handles Binance's limitation on the maximum number of data points per request.
    """
    klines = []
    start_ts = int(pd.to_datetime(start).timestamp() * 1000)
    end_ts = int(pd.to_datetime(end).timestamp() * 1000)
    limit = 1000  # Maximum limit per Binance API request

    print("Fetching historical data from Binance...")

    while True:
        temp_klines = client.get_klines(symbol=symbol, interval=interval, startTime=start_ts, endTime=end_ts, limit=limit)
        if not temp_klines:
            break
        klines.extend(temp_klines)
        last_ts = temp_klines[-1][0]
        if last_ts >= end_ts:
            break
        start_ts = last_ts + 1
        # To respect API rate limits
        time.sleep(0.5)

    # Convert to DataFrame
    data = pd.DataFrame(klines, columns=[
        'Open Time', 'Open', 'High', 'Low', 'Close', 'Volume',
        'Close Time', 'Quote Asset Volume', 'Number of Trades',
        'Taker Buy Base Asset Volume', 'Taker Buy Quote Asset Volume', 'Ignore'
    ])

    # Convert timestamp to datetime
    data['Open Time'] = pd.to_datetime(data['Open Time'], unit='ms')
    data['Close Time'] = pd.to_datetime(data['Close Time'], unit='ms')

    # Convert numerical columns to float
    numerical_cols = ['Open', 'High', 'Low', 'Close', 'Volume']
    data[numerical_cols] = data[numerical_cols].astype(float)

    # Set Open Time as index
    data.set_index('Open Time', inplace=True)

    return data

# Fetch data
data = get_historical_data(SYMBOL, INTERVAL, START_DATE, END_DATE)
print(f"Data fetched: {data.shape[0]} rows.")

# ----------------------------- Indicator Calculations -----------------------------

# Calculate Moving Averages
data['SMA_short'] = data['Close'].rolling(window=SMA_SHORT_PERIOD).mean()
data['SMA_long'] = data['Close'].rolling(window=SMA_LONG_PERIOD).mean()

# Calculate RSI
def calculate_RSI(series, period=14):
    delta = series.diff()
    gain = delta.where(delta > 0, 0.0)
    loss = -delta.where(delta < 0, 0.0)

    # Calculate the exponential moving averages
    avg_gain = gain.rolling(window=period, min_periods=period).mean()
    avg_loss = loss.rolling(window=period, min_periods=period).mean()

    # Initialize RSI series with NaN
    rsi = pd.Series(np.nan, index=series.index)

    # Calculate RSI using the exponential moving average
    for i in range(period, len(series)):
        if avg_loss.iloc[i] == 0:
            rs = np.inf
            rsi.iloc[i] = 100
        else:
            rs = avg_gain.iloc[i] / avg_loss.iloc[i]
            rsi.iloc[i] = 100 - (100 / (1 + rs))
    return rsi

data['RSI'] = calculate_RSI(data['Close'], RSI_PERIOD)

# ----------------------------- Strategy Implementation -----------------------------

# Drop rows with NaN values (due to SMA and RSI calculations)
data.dropna(inplace=True)

# Generate Signals
# Buy Signal (1): SMA_short crosses above SMA_long AND RSI <= 50
# Sell Signal (-1): SMA_short crosses below SMA_long
data['Signal'] = 0

# Conditions for bullish crossover
data['Bullish_Crossover'] = (data['SMA_short'] > data['SMA_long']) & (data['SMA_short'].shift(1) <= data['SMA_long'].shift(1)) & (data['RSI'] <= 50)
# Conditions for bearish crossover
data['Bearish_Crossover'] = (data['SMA_short'] < data['SMA_long']) & (data['SMA_short'].shift(1) >= data['SMA_long'].shift(1))

# Assign signals
data.loc[data['Bullish_Crossover'], 'Signal'] = 1
data.loc[data['Bearish_Crossover'], 'Signal'] = -1

# Identify Points where Signal changes
data['Position'] = data['Signal'].diff()

# ----------------------------- Backtesting -----------------------------

# Initialize portfolio DataFrame
portfolio = pd.DataFrame(index=data.index)
portfolio['Close'] = data['Close']
portfolio['Signal'] = data['Signal']
portfolio['Position'] = data['Position']

# Initialize portfolio columns
portfolio['Holdings'] = 0.0      # Value of BTC held
portfolio['Cash'] = INITIAL_CAPITAL
portfolio['Total'] = INITIAL_CAPITAL
portfolio['BTC_Held'] = 0.0

for i in range(1, len(portfolio)):
    # Carry forward previous holdings and cash
    portfolio.at[portfolio.index[i], 'BTC_Held'] = portfolio.at[portfolio.index[i-1], 'BTC_Held']
    portfolio.at[portfolio.index[i], 'Cash'] = portfolio.at[portfolio.index[i-1], 'Cash']
    
    # Buy Signal
    if portfolio.at[portfolio.index[i], 'Position'] == 1:  # Buy Signal
        # Ensure RSI <= 50 (already handled in signal generation)
        buy_price = portfolio.at[portfolio.index[i], 'Close']
        btc_to_buy = (portfolio.at[portfolio.index[i], 'Cash'] * (1 - TRADING_FEE)) / buy_price
        portfolio.at[portfolio.index[i], 'BTC_Held'] += btc_to_buy
        portfolio.at[portfolio.index[i], 'Cash'] -= btc_to_buy * buy_price * (1 + TRADING_FEE)
        print(f"Buy on {portfolio.index[i]} at price {buy_price:.2f} USDT, BTC Bought: {btc_to_buy:.6f}")
        
    # Sell Signal
    elif portfolio.at[portfolio.index[i], 'Position'] == -1:  # Sell Signal
        # Sell all BTC held
        sell_price = portfolio.at[portfolio.index[i], 'Close']
        btc_to_sell = portfolio.at[portfolio.index[i], 'BTC_Held']
        portfolio.at[portfolio.index[i], 'Cash'] += btc_to_sell * sell_price * (1 - TRADING_FEE)
        portfolio.at[portfolio.index[i], 'BTC_Held'] = 0.0
        print(f"Sell on {portfolio.index[i]} at price {sell_price:.2f} USDT, BTC Sold: {btc_to_sell:.6f}")
        
    # Update total portfolio value
    portfolio.at[portfolio.index[i], 'Total'] = (
        portfolio.at[portfolio.index[i], 'Cash'] +
        portfolio.at[portfolio.index[i], 'BTC_Held'] * portfolio.at[portfolio.index[i], 'Close']
    )

# Forward Fill any remaining data
portfolio['Total'].ffill(inplace=True)

# ----------------------------- Performance Evaluation -----------------------------

# Calculate returns
portfolio['Returns'] = portfolio['Total'].pct_change()

# Calculate cumulative returns
portfolio['Cumulative Returns'] = (1 + portfolio['Returns']).cumprod()

# Calculate Buy and Hold strategy for comparison
initial_price = data['Close'].iloc[0]
portfolio['Buy_Hold'] = INITIAL_CAPITAL * (data['Close'] / initial_price)

# ----------------------------- Visualization -----------------------------

plt.figure(figsize=(16, 18))

# Plot Price and Moving Averages with Signals
plt.subplot(4, 1, 1)
plt.plot(data['Close'], label='BTC/USDT Price', color='blue', alpha=0.5)
plt.plot(data['SMA_short'], label=f'SMA {SMA_SHORT_PERIOD} Hrs', color='green', alpha=0.9)
plt.plot(data['SMA_long'], label=f'SMA {SMA_LONG_PERIOD} Hrs', color='red', alpha=0.9)

# Plot Buy Signals
buy_signals = data[data['Signal'] == 1]
plt.scatter(buy_signals.index, data['Close'][buy_signals.index], marker='^', color='g', label='Buy Signal', s=100)

# Plot Sell Signals
sell_signals = data[data['Signal'] == -1]
plt.scatter(sell_signals.index, data['Close'][sell_signals.index], marker='v', color='r', label='Sell Signal', s=100)

plt.title('BTC/USDT Price with Moving Average Crossover Signals and RSI Condition (1-Hour Timeframe)')
plt.xlabel('Date')
plt.ylabel('Price (USDT)')
plt.legend()
plt.grid(True)

# Plot RSI
plt.subplot(4, 1, 2)
plt.plot(data['RSI'], label='RSI', color='m')
plt.axhline(50, color='gray', linestyle='--', label='RSI 50')
plt.title('Relative Strength Index (RSI)')
plt.xlabel('Date')
plt.ylabel('RSI Value')
plt.legend()
plt.grid(True)

# Plot Portfolio Value Over Time
plt.subplot(4, 1, 3)
plt.plot(portfolio['Total'], label='Moving Average Strategy', color='purple')
plt.plot(portfolio['Buy_Hold'], label='Buy & Hold', color='orange')
plt.title('Portfolio Value Over Time')
plt.xlabel('Date')
plt.ylabel('Portfolio Value (USDT)')
plt.legend()
plt.grid(True)

# Plot Cumulative Returns
plt.subplot(4, 1, 4)
plt.plot(portfolio['Cumulative Returns'], label='Strategy Cumulative Returns', color='purple')
plt.plot((portfolio['Buy_Hold'] / INITIAL_CAPITAL), label='Buy & Hold Cumulative Returns', color='orange')
plt.title('Cumulative Returns')
plt.xlabel('Date')
plt.ylabel('Cumulative Returns')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.show()

# ----------------------------- Performance Metrics -----------------------------

final_portfolio_value = portfolio['Total'].iloc[-1]
total_return = (final_portfolio_value / INITIAL_CAPITAL - 1) * 100

# Buy and Hold metrics
final_buy_hold = portfolio['Buy_Hold'].iloc[-1]
buy_hold_return = (final_buy_hold / INITIAL_CAPITAL - 1) * 100

# Calculate number of trades
num_buys = len(portfolio[portfolio['Position'] == 1])
num_sells = len(portfolio[portfolio['Position'] == -1])

# Print summary
print("\n--- Strategy Performance ---")
print(f"Initial Capital: ${INITIAL_CAPITAL:,.2f}")
print(f"Final Portfolio Value (Strategy): ${final_portfolio_value:,.2f}")
print(f"Total Return (Strategy): {total_return:.2f}%")
print(f"Final Portfolio Value (Buy & Hold): ${final_buy_hold:,.2f}")
print(f"Total Return (Buy & Hold): {buy_hold_return:.2f}%")
print(f"Number of Buy Signals Executed: {num_buys}")
print(f"Number of Sell Signals Executed: {num_sells}")

# Optional: Save the portfolio to a CSV file
# portfolio.to_csv('moving_average_crossover_portfolio_1H_with_RSI.csv')