This script doesn't gives outputs : import requests
import pandas as pd
import numpy as np
import time
import datetime
import matplotlib.pyplot as plt
import seaborn as sns
from ta.trend import SMAIndicator, MACD, ADXIndicator
from ta.momentum import RSIIndicator
import os

# Suppress SettingWithCopyWarning
pd.options.mode.chained_assignment = None  # default='warn'

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

# List of cryptocurrencies to trade (Binance symbols)
CRYPTO_SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'ADAUSDT']

# Timeframes (Use lowercase for Binance intervals)
PRIMARY_TIMEFRAME = '15m'   # For trade execution
SECONDARY_TIMEFRAME = '1h'  # For trend confirmation
TERTIARY_TIMEFRAME = '1w'   # For weekly trend (monthly mood)

# Technical Indicator Parameters per Timeframe
indicator_params = {
    'primary': {'MA_SHORT': 20, 'MA_LONG': 50},      # Shorter MAs for higher frequency
    'secondary': {'MA_SHORT': 20, 'MA_LONG': 50},
    'tertiary': {'MA_SHORT': 10, 'MA_LONG': 30}    # Adjusted for sufficient data points (~52 weeks/year)
}

RSI_PERIOD = 14
RSI_OVERBOUGHT = 70  # Adjusted threshold
RSI_OVERSOLD = 30    # Adjusted threshold

# MACD Parameters (consistent across timeframes)
MACD_FAST = 12
MACD_SLOW = 26
MACD_SIGNAL = 9

# ADX Parameters (to measure trend strength)
ADX_PERIOD = 14
ADX_THRESHOLD = 25  # Below this, the trend is considered weak

# Risk management parameters
POSITION_SIZE = 0.02    # 2% of total capital per trade
DAILY_PNL_LIMIT = 0.10  # 10% daily profit/loss limit

# Trading fees and slippage (optional)
TRADING_FEE_PERCENTAGE = 0.1  # 0.1% per trade round-trip
SLIPPAGE_PERCENTAGE = 0.05     # 0.05% slippage per trade

# Initial capital
INITIAL_CAPITAL = 10000  # $10,000

# Simulation period (last year)
END_TIME = int(time.time() * 1000)  # current time in milliseconds
START_TIME = END_TIME - (365 * 24 * 60 * 60 * 1000)  # one year ago

# Binance API endpoint
BINANCE_API_URL = 'https://api.binance.com/api/v3/klines'

# ------------------------ Helper Functions ------------------------

def get_historical_klines(symbol, interval, start_time, end_time):
    """
    Fetch historical candlestick data from Binance API within a specified time range.
    Handles pagination by fetching 1000 candles per request.
    """
    limit = 1000  # max limit per request
    df = pd.DataFrame()
    while True:
        params = {
            'symbol': symbol,
            'interval': interval,
            'startTime': start_time,
            'endTime': end_time,
            'limit': limit
        }
        response = requests.get(BINANCE_API_URL, params=params)
        if response.status_code != 200:
            print(f"Error fetching data for {symbol}: {response.text}")
            break
        data = response.json()
        
        if not data:
            break
        
        # Convert to DataFrame
        temp_df = pd.DataFrame(data, 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
        temp_df['Open Time'] = pd.to_datetime(temp_df['Open Time'], unit='ms')
        temp_df['Close Time'] = pd.to_datetime(temp_df['Close Time'], unit='ms')
        
        # Convert numerical columns to float
        temp_df[['Open', 'High', 'Low', 'Close', 'Volume']] = temp_df[['Open', 'High', 'Low', 'Close', 'Volume']].astype(float)
        
        # Keep relevant columns
        temp_df = temp_df[['Open Time', 'Open', 'High', 'Low', 'Close', 'Volume', 'Close Time']]
        
        # Concatenate
        df = pd.concat([df, temp_df], ignore_index=True)
        
        # Check if we've fetched all data
        last_close_time = data[-1][6]
        if last_close_time >= end_time or len(data) < limit:
            break
        
        # Increment start_time for next batch
        start_time = last_close_time + 1
        time.sleep(0.5)  # to prevent hitting rate limits
    
    # Remove duplicates in case of overlapping
    df.drop_duplicates(subset=['Close Time'], inplace=True)
    return df

def calculate_indicators(df, suffix_label):
    """
    Calculate technical indicators: SMA, RSI, MACD, ADX for a given dataframe.
    Indicators are suffixed with the given label to differentiate between timeframes.
    """
    params = indicator_params.get(suffix_label, {'MA_SHORT': 50, 'MA_LONG': 200})
    MA_SHORT_tf = params['MA_SHORT']
    MA_LONG_tf = params['MA_LONG']
    
    # SMA
    sma_short = SMAIndicator(close=df['Close'], window=MA_SHORT_tf)
    sma_long = SMAIndicator(close=df['Close'], window=MA_LONG_tf)
    df[f'SMA_Short_{suffix_label}'] = sma_short.sma_indicator()
    df[f'SMA_Long_{suffix_label}'] = sma_long.sma_indicator()
    
    # RSI
    rsi = RSIIndicator(close=df['Close'], window=RSI_PERIOD)
    df[f'RSI_{suffix_label}'] = rsi.rsi()
    
    # MACD
    macd = MACD(close=df['Close'], window_slow=MACD_SLOW, window_fast=MACD_FAST, window_sign=MACD_SIGNAL)
    df[f'MACD_{suffix_label}'] = macd.macd()
    df[f'MACD_Signal_{suffix_label}'] = macd.macd_signal()
    df[f'MACD_Hist_{suffix_label}'] = macd.macd_diff()
    
    # ADX
    adx = ADXIndicator(high=df['High'], low=df['Low'], close=df['Close'], window=ADX_PERIOD)
    df[f'ADX_{suffix_label}'] = adx.adx()
    
    df['Timeframe'] = suffix_label
    return df

def generate_signals(primary_df, secondary_df, tertiary_df, symbol):
    """
    Generate buy and sell signals based on indicators and multi-time frame validation.
    """
    buy_signals = []
    sell_signals = []

    # Rename tertiary indicators to avoid column conflicts
    tertiary_df = tertiary_df.rename(columns={
        'SMA_Short_tertiary': 'SMA_Short_tertiary',
        'SMA_Long_tertiary': 'SMA_Long_tertiary',
        'RSI_tertiary': 'RSI_tertiary',
        'MACD_Hist_tertiary': 'MACD_Hist_tertiary',
        'ADX_tertiary': 'ADX_tertiary'
    })

    # Align primary and secondary DataFrames by Close Time
    merged_df = pd.merge(primary_df, secondary_df, on='Close Time', suffixes=('_primary', '_secondary'))
    merged_df = pd.merge(merged_df, tertiary_df[['Close Time', 'SMA_Short_tertiary', 'SMA_Long_tertiary', 'RSI_tertiary', 'MACD_Hist_tertiary', 'ADX_tertiary']], on='Close Time', how='left')

    # Debugging: Ensure columns exist
    required_columns = ['SMA_Short_tertiary', 'SMA_Long_tertiary', 'ADX_tertiary']
    for col in required_columns:
        if col not in merged_df.columns:
            print(f"Warning: Column '{col}' not found in merged DataFrame for {symbol}.")

    for i in range(1, len(merged_df)):
        # Primary Timeframe Conditions
        sma_short_primary = merged_df['SMA_Short_primary'][i]
        sma_long_primary = merged_df['SMA_Long_primary'][i]
        sma_short_primary_prev = merged_df['SMA_Short_primary'][i-1]
        sma_long_primary_prev = merged_df['SMA_Long_primary'][i-1]
        
        sma_diff = sma_short_primary - sma_long_primary
        sma_diff_prev = sma_short_primary_prev - sma_long_primary_prev
        
        # Define a crossover threshold percentage
        crossover_threshold = 0.5  # 0.5%
        
        if sma_diff > (crossover_threshold / 100) * sma_long_primary:
            sma_crossover = True
        else:
            sma_crossover = False

        if sma_diff < -(crossover_threshold / 100) * sma_long_primary:
            sma_crossunder = True
        else:
            sma_crossunder = False

        # RSI Conditions
        rsi_primary = merged_df['RSI_primary'][i]
        rsi_overbought = rsi_primary > RSI_OVERBOUGHT
        rsi_oversold = rsi_primary < RSI_OVERSOLD

        # MACD Conditions
        macd_hist_primary = merged_df['MACD_Hist_primary'][i]
        macd_positive = macd_hist_primary > 0
        macd_negative = macd_hist_primary < 0

        # ADX Condition (trend strength)
        adx_primary = merged_df['ADX_primary'][i]
        strong_trend = adx_primary > ADX_THRESHOLD

        # Secondary Timeframe Trend Confirmation
        sma_short_secondary = merged_df['SMA_Short_secondary'][i]
        sma_long_secondary = merged_df['SMA_Long_secondary'][i]
        
        if sma_short_secondary > sma_long_secondary:
            secondary_trend = 'bullish'
        elif sma_short_secondary < sma_long_secondary:
            secondary_trend = 'bearish'
        else:
            secondary_trend = 'neutral'

        # Tertiary Timeframe Mood (Weekly Trend)
        sma_short_tertiary = merged_df['SMA_Short_tertiary'][i]
        sma_long_tertiary = merged_df['SMA_Long_tertiary'][i]
        adx_tertiary = merged_df['ADX_tertiary'][i]
        
        if pd.notna(sma_short_tertiary) and pd.notna(sma_long_tertiary) and pd.notna(adx_tertiary):
            if sma_short_tertiary > sma_long_tertiary and adx_tertiary > ADX_THRESHOLD:
                tertiary_trend = 'bullish'
            elif sma_short_tertiary < sma_long_tertiary and adx_tertiary > ADX_THRESHOLD:
                tertiary_trend = 'bearish'
            else:
                tertiary_trend = 'neutral'
        else:
            tertiary_trend = 'neutral'

        # Buy Signal
        if (sma_crossover and rsi_oversold and macd_positive and strong_trend 
            and secondary_trend == 'bullish' and tertiary_trend == 'bullish'):
            buy_signals.append(merged_df['Close Time'][i])
            print(f"Buy signal for {symbol} at {merged_df['Close Time'][i]}")

        # Sell Signal
        if (sma_crossunder and rsi_overbought and macd_negative and strong_trend 
            and secondary_trend == 'bearish' and tertiary_trend == 'bearish'):
            sell_signals.append(merged_df['Close Time'][i])
            print(f"Sell signal for {symbol} at {merged_df['Close Time'][i]}")

    # ------------------------ Main Strategy Class ------------------------
    
    class CryptoTradingStrategy:
        def __init__(self, symbols, initial_capital=10000):
            self.symbols = symbols
            self.initial_capital = initial_capital
            self.capital = initial_capital
            self.trade_journal = pd.DataFrame(columns=[
                'Symbol', 'Entry Time', 'Entry Price', 'Exit Time', 'Exit Price',
                'Profit/Loss (%)', 'Profit/Loss ($)'
            ])
            self.daily_pnl = {}
    
        def run_backtest(self):
            for symbol in self.symbols:
                print(f"\nProcessing {symbol}...")
                # Fetch primary timeframe data
                primary_df = get_historical_klines(symbol, PRIMARY_TIMEFRAME, START_TIME, END_TIME)
                if primary_df is None or primary_df.empty:
                    print(f"No primary timeframe data for {symbol}. Skipping.")
                    continue
                primary_df = calculate_indicators(primary_df, 'primary')
                
                # Check if enough data is present
                ma_long_primary = indicator_params['primary']['MA_LONG']
                if len(primary_df) < ma_long_primary:
                    print(f"Not enough data to calculate indicators for {symbol} on primary timeframe. Skipping.")
                    continue
    
                # Fetch secondary timeframe data
                secondary_df = get_historical_klines(symbol, SECONDARY_TIMEFRAME, START_TIME, END_TIME)
                if secondary_df is None or secondary_df.empty:
                    print(f"No secondary timeframe data for {symbol}. Skipping.")
                    continue
                secondary_df = calculate_indicators(secondary_df, 'secondary')
                
                # Check if enough data is present
                ma_long_secondary = indicator_params['secondary']['MA_LONG']
                if len(secondary_df) < ma_long_secondary:
                    print(f"Not enough data to calculate indicators for {symbol} on secondary timeframe. Skipping.")
                    continue
    
                # Fetch tertiary timeframe data
                tertiary_df = get_historical_klines(symbol, TERTIARY_TIMEFRAME, START_TIME, END_TIME)
                if tertiary_df is None or tertiary_df.empty:
                    print(f"No tertiary timeframe data for {symbol}. Skipping.")
                    continue
                tertiary_df = calculate_indicators(tertiary_df, 'tertiary')
                
                # Check if enough data is present
                ma_long_tertiary = indicator_params['tertiary']['MA_LONG']
                if len(tertiary_df) < ma_long_tertiary:
                    print(f"Not enough data to calculate indicators for {symbol} on tertiary timeframe. Skipping.")
                    continue
    
                # Generate signals with multi-time frame validation
                buy_signals, sell_signals = generate_signals(primary_df, secondary_df, tertiary_df, symbol)
                print(f"{symbol} - Buy Signals: {len(buy_signals)}, Sell Signals: {len(sell_signals)}")
                
                if len(buy_signals) == 0 and len(sell_signals) == 0:
                    print(f"No trade signals generated for {symbol}. Skipping.")
                    continue
    
                # Plot signals for visual verification
                plot_signals(symbol, primary_df, secondary_df, tertiary_df, buy_signals, sell_signals)
    
                # Simulate trading
                positions = []
                for buy_time in buy_signals:
                    # Retrieve entry price
                    entry_row = primary_df[primary_df['Close Time'] == buy_time]
                    if entry_row.empty:
                        continue
                    entry_price = entry_row['Close'].values[0]
                    entry_time = buy_time
                    positions.append({
                        'Entry Time': entry_time,
                        'Entry Price': entry_price,
                        'Exit Time': None,
                        'Exit Price': None,
                        'Profit/Loss (%)': None,
                        'Profit/Loss ($)': None
                    })
    
                for pos in positions:
                    if pos['Exit Time'] is None:
                        # Find the next sell signal after entry time
                        possible_exits = [s for s in sell_signals if s > pos['Entry Time']]
                        if not possible_exits:
                            # If no sell signal exists after entry, exit at the last available price
                            exit_time = primary_df['Close Time'].iloc[-1]
                            exit_price = primary_df['Close'].iloc[-1]
                        else:
                            exit_time = possible_exits[0]
                            exit_row = primary_df[primary_df['Close Time'] == exit_time]
                            if exit_row.empty:
                                exit_price = primary_df['Close'].iloc[-1]
                            else:
                                exit_price = exit_row['Close'].values[0]
    
                        pos['Exit Time'] = exit_time
                        pos['Exit Price'] = exit_price
                        # Calculate P/L
                        pl_percent = ((exit_price - pos['Entry Price']) / pos['Entry Price']) * 100
                        pl_amount = (pl_percent / 100) * (self.initial_capital * POSITION_SIZE)
    
                        # Adjust for trading fees and slippage
                        pl_amount -= ((TRADING_FEE_PERCENTAGE / 100) * (self.initial_capital * POSITION_SIZE) * 2)  # round-trip fees
                        pl_amount -= ((SLIPPAGE_PERCENTAGE / 100) * (self.initial_capital * POSITION_SIZE))      # slippage on entry
                        pl_amount -= ((SLIPPAGE_PERCENTAGE / 100) * (self.initial_capital * POSITION_SIZE))      # slippage on exit
    
                        pos['Profit/Loss (%)'] = pl_percent
                        pos['Profit/Loss ($)'] = pl_amount
    
                        # Update capital
                        self.capital += pl_amount
    
                        # Update daily P&L
                        day = exit_time.date()
                        if day not in self.daily_pnl:
                            self.daily_pnl[day] = 0
                        self.daily_pnl[day] += pl_amount
    
                        # Check daily P&L limit
                        if abs(self.daily_pnl[day]) >= (DAILY_PNL_LIMIT * self.initial_capital):
                            print(f"Daily P/L limit reached on {day} for capital ${self.capital:.2f}. Stopping further trades for {symbol}.")
                            break  # Stop trading for this symbol on this day
    
                        # Append to trade journal
                        self.trade_journal = self.trade_journal.append({
                            'Symbol': symbol,
                            'Entry Time': pos['Entry Time'],
                            'Entry Price': pos['Entry Price'],
                            'Exit Time': pos['Exit Time'],
                            'Exit Price': pos['Exit Price'],
                            'Profit/Loss (%)': pos['Profit/Loss (%)'],
                            'Profit/Loss ($)': pos['Profit/Loss ($)']
                        }, ignore_index=True)
    
        def generate_report(self):
            """
            Generate performance reports and graphs.
            """
            if self.trade_journal.empty:
                print("No trades to report.")
                return
    
            # Convert Entry and Exit Times to datetime
            self.trade_journal['Entry Time'] = pd.to_datetime(self.trade_journal['Entry Time'])
            self.trade_journal['Exit Time'] = pd.to_datetime(self.trade_journal['Exit Time'])
    
            # Sort trades by Exit Time
            sorted_trades = self.trade_journal.sort_values(by='Exit Time')
    
            # Calculate cumulative P/L for Equity Curve
            sorted_trades['Cumulative P/L'] = (sorted_trades['Profit/Loss ($)']).cumsum() + self.initial_capital
    
            # Plot Equity Curve
            plt.figure(figsize=(14,7))
            plt.plot(sorted_trades['Exit Time'], sorted_trades['Cumulative P/L'], label='Equity Curve', color='blue')
            plt.title('Equity Curve Over Time')
            plt.xlabel('Date')
            plt.ylabel('Capital ($)')
            plt.legend()
            plt.grid(True)
            plt.tight_layout()
            plt.savefig('equity_curve.png')
            plt.close()
            print("Equity curve saved as 'equity_curve.png'.")
    
            # Profit and Loss Distribution
            plt.figure(figsize=(10,6))
            sns.histplot(self.trade_journal['Profit/Loss (%)'], bins=50, kde=True, color='green')
            plt.title('Profit and Loss Distribution')
            plt.xlabel('Profit/Loss (%)')
            plt.ylabel('Frequency')
            plt.grid(True)
            plt.tight_layout()
            plt.savefig('pl_distribution.png')
            plt.close()
            print("P/L distribution saved as 'pl_distribution.png'.")
    
            # Daily P/L
            pnl_df = pd.DataFrame(list(self.daily_pnl.items()), columns=['Date', 'P/L ($)'])
            pnl_df = pnl_df.sort_values(by='Date')
    
            plt.figure(figsize=(14,7))
            sns.barplot(x='Date', y='P/L ($)', data=pnl_df, palette='viridis')
            plt.title('Daily Profit and Loss')
            plt.xlabel('Date')
            plt.ylabel('P/L ($)')
            plt.xticks(rotation=45)
            plt.tight_layout()
            plt.savefig('daily_pnl.png')
            plt.close()
            print("Daily P&L chart saved as 'daily_pnl.png'.")
    
            # Save Sentiment Scores
            sentiment_scores = []
            for symbol in self.symbols:
                df = get_historical_klines(symbol, TERTIARY_TIMEFRAME, START_TIME, END_TIME)
                if df is None or df.empty:
                    continue
                df = calculate_indicators(df, 'tertiary')
                last_rsi = df['RSI_tertiary'].iloc[-1]
                sentiment_scores.append({'Symbol': symbol, 'RSI': last_rsi})
    
            sentiment_df = pd.DataFrame(sentiment_scores)
            sentiment_df.to_csv('sentiment_scores.csv', index=False)
            print("Sentiment scores saved as 'sentiment_scores.csv'.")
    
            # Generate Summary Report
            self.generate_summary_report(sorted_trades)
    
        def generate_summary_report(self, sorted_trades):
            """
            Generate a textual summary of the trading performance.
            """
            print("\n--- Trading Summary Report ---")
            total_trades = len(self.trade_journal)
            profitable_trades = len(self.trade_journal[self.trade_journal['Profit/Loss ($)'] > 0])
            losing_trades = len(self.trade_journal[self.trade_journal['Profit/Loss ($)'] <= 0])
            win_rate = (profitable_trades / total_trades) * 100 if total_trades > 0 else 0
            avg_profit = self.trade_journal['Profit/Loss ($)'].mean() if total_trades > 0 else 0
            max_drawdown = ((sorted_trades['Cumulative P/L'].min() - self.initial_capital) / self.initial_capital) * 100
    
            print(f"Total Trades Executed: {total_trades}")
            print(f"Profitable Trades: {profitable_trades}")
            print(f"Losing Trades: {losing_trades}")
            print(f"Win Rate: {win_rate:.2f}%")
            print(f"Average Profit/Loss per Trade: ${avg_profit:.2f}")
            print(f"Maximum Drawdown: {max_drawdown:.2f}%")
            print("--- End of Report ---\n")
    
    # ------------------------ Execution ------------------------
    
    if __name__ == "__main__":
        strategy = CryptoTradingStrategy(CRYPTO_SYMBOLS, INITIAL_CAPITAL)
        strategy.run_backtest()
        strategy.generate_report()