import os
import threading
import django
# Set the environment variable to point to your project’s settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'binancebot.settings')

# Initialize Django
django.setup()

from concurrent.futures import ProcessPoolExecutor, as_completed
import ccxt
import pandas as pd
import numpy as np
import talib as ta
from django.utils import timezone
from .models import Signal
import logging
from multiprocessing import Process, Event
from loguru import logger

# Configure logging
logging.basicConfig(level=logging.DEBUG)
#logger.add("trading_bot.log", rotation="1 MB", compression="zip", retention="10 days")

class SignalGenerator:
    def __init__(self, symbol='BTC/USDT', timeframe='1m'):
        self.symbol = symbol
        self.timeframe = timeframe
        self.exchange = ccxt.binance({
            'options': {'defaultType': 'future'}  # Configure for futures markets
        })
        logging.debug(f"Initialized SignalGenerator with symbol: {symbol} and timeframe: {timeframe}")

    def fetch_market_data(self):
        try:
            # Fetch only the latest bar to reduce data fetched
            latest_bar = self.exchange.fetch_ohlcv(self.symbol, self.timeframe, limit=1)
            if latest_bar:
                latest_bar = latest_bar[0]
                df = pd.DataFrame([latest_bar], columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
                df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
                return df
            else:
                logging.error("No market data fetched for {self.symbol}.")
                return pd.DataFrame()
        except Exception as e:
            logging.error(f"Error fetching market datafor {self.symbol}: {e}")
            return pd.DataFrame()

    def generate_signals(self, df):
        try:
            # Example: simple moving average crossover
            df['SMA10'] = ta.SMA(df['close'], timeperiod=10)
            df['SMA20'] = ta.SMA(df['close'], timeperiod=20)
            df['Signal'] = np.where(df['SMA10'] > df['SMA20'], 'BUY', 'SELL')
            return df
        except Exception as e:
            logging.error(f"Error generating signals for {self.symbol}: {e}")
            return df

    def save_signals(self, df):
        try:
            last_signal = df.iloc[-1]
            # Save all generated signals to the database
            Signal.objects.update_or_create(
                timestamp=timezone.make_aware(last_signal['timestamp']),
                symbol=self.symbol,
                defaults={
                    'price': last_signal['close'],
                    'signal_type': last_signal['Signal'],
                }
            )
            logging.info(f"Signal saved for {self.symbol}: {last_signal['Signal']}")
        except Exception as e:
            logging.error(f"Error saving signals for {self.symbol} to database: {e}")

    def run(self, stop_event):
        while not stop_event.is_set():
            df = self.fetch_market_data()
            if not df.empty:
                df = self.generate_signals(df)
                self.save_signals(df)
            else:
                logging.error("No data fetched, cannot proceed with signal generation.")
            stop_event.wait(1)  # Check the event every second

def worker(symbol, stop_event):
    signal_generator = SignalGenerator(symbol=symbol)
    signal_generator.run(stop_event)

def main():
    stop_event = Event()
    processes = []

    def signal_handler(sig, frame):
        logging.info("KeyboardInterrupt (SIGINT) received. Requesting shutdown.")
        stop_event.set()  
# Fetch all market symbols from Binance Futures
    try:
        exchange = ccxt.binance({'options': {'defaultType': 'future'}})
        markets = exchange.load_markets()

# Filter for USDT pairs
        usdt_pairs = [symbol for symbol in markets if symbol.endswith('/USDT')]
    
        for symbol in usdt_pairs:
            p = Process(target=worker, args=(symbol, stop_event))
            processes.append(p)
            p.start()

        # Wait for the processes to complete
        for p in processes:
            p.join()
# Iterate over each USDT pair
    #with ProcessPoolExecutor() as executor:
         # Make the executor ignore SIGINT in workers and let the main process handle it.
        
       # future_to_symbol = {executor.submit(process_symbol, symbol): symbol for symbol in usdt_pairs}
        #try:
            #for future in as_completed(future_to_symbol):
               # symbol = future_to_symbol[future]
                #try:
                    #future.result()
                #except Exception as e:
                    #logging.error(f"Error processing symbol {symbol}: {e}")
        #for symbol in usdt_pairs:
            #try:
                #logging.info(f"Processing symbol: {symbol}")
                #signal_generator = SignalGenerator(symbol=symbol)
                #signal_generator.run()
            #except Exception as e:
                #logging.error(f"Error processing symbol {symbol}: {e}")
    except KeyboardInterrupt:
        logging.info("KeyboardInterrupt caught. Setting stop event and terminating all processes.")
        stop_event.set()
        for p in processes:
            p.join()
        logging.info("All processes have been terminated gracefully.")
        


if __name__ == "__main__":
    main()

The above code uses cpu to generate signals concurrently. I don't want that. Wat other option can we use to make it run concurrently without using too much cpu