can we improve factor calculation logic without help of any api in the following scripte :

import tkinter as tk
from tkinter import ttk, messagebox
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import yfinance as yf
import threading
import queue
import numpy as np
from datetime import datetime, timedelta
import pandas as pd  # Ensure pandas is imported
import logging  # For enhanced logging

# Configure logging
logging.basicConfig(
    filename='bitcoin_trend_predictor.log',
    level=logging.INFO,
    format='%(asctime)s:%(levelname)s:%(message)s'
)

# Updated List of factors (including new factors)
FACTORS_LIST = [
    "Supply and Demand",
    "Market Sentiment",
    "Institutional Adoption",
    "Economic Conditions",
    "Technological Evolution",
    "Regulation",
    "Competition",
    "Financial Integration",
    "Geopolitical Factors",
    "Special Events",
    "Volatility Index",         # New Factor
    "Price Momentum",           # New Factor
    "Volume Momentum"           # New Factor
]

# Updated weights for each factor (including new factors)
WEIGHTS = {
    "Supply and Demand": 1.0,
    "Market Sentiment": 1.5,
    "Institutional Adoption": 1.0,
    "Economic Conditions": 1.0,
    "Technological Evolution": 1.2,
    "Regulation": 1.0,
    "Competition": 1.2,
    "Financial Integration": 1.0,
    "Geopolitical Factors": 0.8,
    "Special Events": 0.7,
    "Volatility Index": 1.0,          # New Weight
    "Price Momentum": 1.0,            # New Weight
    "Volume Momentum": 1.0            # New Weight
}

# Base price for normalization
BASE_PRICE = 50000

# Create a thread-safe Queue for communication
result_queue = queue.Queue()

def min_max_normalize_manual(factors, feature_range=(-1, 1)):
    """
    Manually performs min-max normalization on the factor scores.

    Args:
        factors (dict): Dictionary of factor scores.
        feature_range (tuple): Desired range of transformed data.

    Returns:
        dict: Normalized factor scores.
    """
    min_val = min(factors.values())
    max_val = max(factors.values())
    range_val = max_val - min_val
    if range_val == 0:
        return {factor: 0.0 for factor in factors}
    scale_min, scale_max = feature_range
    normalized_factors = {
        factor: ((value - min_val) / range_val) * (scale_max - scale_min) + scale_min
        for factor, value in factors.items()
    }
    return normalized_factors

def calculate_rsi(series, period=14):
    """
    Calculates the Relative Strength Index (RSI) for a given price series.

    Args:
        series (pd.Series): Price series.
        period (int): RSI calculation period.

    Returns:
        float: Latest RSI value or 50.0 if insufficient data.
    """
    delta = series.diff()
    gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
    loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
    rs = gain / loss
    rsi = 100 - (100 / (1 + rs))
    return rsi.iloc[-1] if not rsi.empty and not pd.isna(rsi.iloc[-1]) else 50.0  # Neutral RSI if insufficient data

def calculate_momentum(series, period=10):
    """
    Calculates the momentum of a price series.

    Args:
        series (pd.Series): Price series.
        period (int): Number of periods over which to calculate momentum.

    Returns:
        float: Momentum value or 0.0 if insufficient data.
    """
    if len(series) < period:
        return 0.0
    try:
        momentum = series.iloc[-1] - series.iloc[-period]
        return momentum / series.iloc[-period] if series.iloc[-period] != 0 else 0.0
    except IndexError:
        logging.warning("Insufficient data for momentum calculation.")
        return 0.0

def get_bitcoin_data():
    """
    Uses yfinance to retrieve current and historical Bitcoin data.

    Returns:
        dict: Contains calculated factor scores.
    """
    try:
        btc = yf.Ticker("BTC-USD")
        eth = yf.Ticker("ETH-USD")
        ltc = yf.Ticker("LTC-USD")
        xrp = yf.Ticker("XRP-USD")
        ada = yf.Ticker("ADA-USD")
        doge = yf.Ticker("DOGE-USD")
        sp500 = yf.Ticker("^GSPC")
        vix = yf.Ticker("^VIX")

        # Define the period for historical data (180 days for better coverage)
        end_date = datetime.today()
        start_date = end_date - timedelta(days=180)

        # Retrieve historical data
        btc_hist = btc.history(start=start_date, end=end_date)
        eth_hist = eth.history(start=start_date, end=end_date)
        ltc_hist = ltc.history(start=start_date, end=end_date)
        xrp_hist = xrp.history(start=start_date, end=end_date)
        ada_hist = ada.history(start=start_date, end=end_date)
        doge_hist = doge.history(start=start_date, end=end_date)
        sp500_hist = sp500.history(start=start_date, end=end_date)
        vix_hist = vix.history(start=start_date, end=end_date)

        # Current price
        if not btc_hist.empty:
            current_price = btc_hist['Close'][-1]
        else:
            current_price = BASE_PRICE
            logging.warning("Bitcoin historical data is empty. Using BASE_PRICE.")

        # Last 30 days history to calculate trends
        btc_recent_hist = btc_hist.tail(30)
        if not btc_recent_hist.empty:
            # Supply and Demand using 30-day Moving Average
            moving_average = btc_recent_hist['Close'].rolling(window=30).mean()
            if not moving_average.empty and not pd.isna(moving_average.iloc[-1]):
                price_trend = (current_price - moving_average.iloc[-1]) / moving_average.iloc[-1]
            else:
                price_trend = 0.0
                logging.warning("Insufficient data for Supply and Demand calculation.")
        else:
            price_trend = 0.0
            logging.warning("Bitcoin recent historical data is empty.")

        # Market Sentiment using RSI
        sentiment_score = calculate_rsi(btc_recent_hist['Close'])
        # Normalize RSI to [-1, 1]
        sentiment_score = (sentiment_score - 50) / 50

        # Institutional Adoption: Use volumes
        if not btc_recent_hist.empty:
            avg_volume = btc_recent_hist['Volume'].mean()
            current_volume = btc_recent_hist['Volume'][-1]
            adoption_score = (current_volume / avg_volume) - 1 if avg_volume != 0 else 0.0
        else:
            adoption_score = 0.0
            logging.warning("Bitcoin recent historical data is empty for Institutional Adoption.")

        # Economic Conditions: Use VIX as a proxy
        if not vix_hist.empty:
            avg_vix = vix_hist['Close'].mean()
            current_vix = vix_hist['Close'][-1]
            economic_conditions = (avg_vix - current_vix) / avg_vix if avg_vix != 0 else 0.0
        else:
            economic_conditions = 0.0
            logging.warning("VIX historical data is empty.")

        # Technological Evolution using EMA of volatility and mean close
        if not btc_recent_hist.empty:
            btc_pct_change = btc_recent_hist['Close'].pct_change()
            volatility_ema = btc_pct_change.rolling(window=30).std().ewm(span=30, adjust=False).mean()
            mean_close_ema = btc_recent_hist['Close'].rolling(window=30).mean().ewm(span=30, adjust=False).mean()
            if not volatility_ema.empty and not pd.isna(volatility_ema.iloc[-1]):
                tech_evolution = (1 / volatility_ema.iloc[-1]) * mean_close_ema.iloc[-1] if volatility_ema.iloc[-1] != 0 else 0.0
            else:
                tech_evolution = 0.0
                logging.warning("Insufficient data for Technological Evolution calculation.")
        else:
            tech_evolution = 0.0
            logging.warning("Bitcoin recent historical data is empty for Technological Evolution.")

        # Regulation using extended historical volatility
        extended_start_date = start_date - timedelta(days=90)  # 6 months for extended analysis
        hist_extended = btc.history(start=extended_start_date, end=start_date)
        if not hist_extended.empty:
            extended_pct_change = hist_extended['Close'].pct_change().rolling(window=30).std()
            mean_volatility = extended_pct_change.mean()
            current_volatility_series = btc_hist['Close'].pct_change().rolling(window=30).std()
            current_volatility = current_volatility_series.iloc[-1] if not current_volatility_series.empty else 0.0
            regulation_score = (current_volatility - mean_volatility) / mean_volatility if mean_volatility != 0 else 0.0
        else:
            regulation_score = 0.0
            logging.warning("Extended historical data for Regulation is empty.")

        # Competition: Including additional competitors with weighted average return
        competitors = {
            "ETH-USD": eth_hist,
            "LTC-USD": ltc_hist,
            "XRP-USD": xrp_hist,
            "ADA-USD": ada_hist,
            "DOGE-USD": doge_hist
        }
        market_caps = {
            "ETH-USD": 200000000000,  # Example values; replace with actual if available
            "LTC-USD": 50000000000,
            "XRP-USD": 40000000000,
            "ADA-USD": 45000000000,
            "DOGE-USD": 60000000000
        }
        total_market_cap = sum(market_caps.values())
        btc_start = btc_hist['Close'].iloc[0] if not btc_hist['Close'].empty else 0.0
        btc_end = btc_hist['Close'].iloc[-1] if not btc_hist['Close'].empty else 0.0
        btc_return = (btc_end / btc_start) - 1 if btc_start != 0 else 0.0

        total_weighted_return = 0.0
        valid_competitors = 0
        for ticker, hist in competitors.items():
            if not hist.empty and not hist['Close'].empty:
                comp_start = hist['Close'].iloc[0]
                comp_end = hist['Close'].iloc[-1]
                if comp_start != 0:
                    comp_return = (comp_end / comp_start) - 1
                    weight = market_caps.get(ticker, 0) / total_market_cap
                    total_weighted_return += comp_return * weight
                    valid_competitors += 1
        competition_score = btc_return - total_weighted_return if valid_competitors > 0 else 0.0

        # Financial Integration: Rolling correlation with the S&P 500
        if not sp500_hist.empty and not btc_hist.empty:
            btc_returns = btc_hist['Close'].pct_change().dropna()
            sp500_returns = sp500_hist['Close'].pct_change().dropna()
            btc_returns_aligned, sp500_returns_aligned = btc_returns.align(sp500_returns, join='inner')
            if len(btc_returns_aligned) >= 60:
                window_size = 30
                rolling_corr = btc_returns_aligned.rolling(window=window_size).corr(sp500_returns_aligned)
                recent_corr = rolling_corr.dropna().iloc[-1] if not rolling_corr.dropna().empty else np.nan
                financial_integration = (recent_corr + 1) / 2 if pd.notnull(recent_corr) else 0.5
            else:
                financial_integration = 0.5
                logging.warning("Not enough data points for Financial Integration calculation.")
        else:
            financial_integration = 0.5
            logging.warning("S&P 500 or Bitcoin historical data is empty for Financial Integration.")

        # Geopolitical Factors: Weighted combination
        geopolitical = economic_conditions * 0.6 + regulation_score * 0.4
        geopolitical = max(min(geopolitical, 1.0), -1.0)

        # Special Events: Categorized severity
        if not btc_recent_hist.empty:
            daily_changes = btc_recent_hist['Close'].pct_change().abs()
            dynamic_threshold = daily_changes.mean() + 2 * daily_changes.std()
            mild_events = (daily_changes > dynamic_threshold) & (daily_changes <= dynamic_threshold * 1.5)
            severe_events = daily_changes > (dynamic_threshold * 1.5)
            total_days = len(btc_recent_hist)
            special_events = (mild_events.sum() * 0.5 + severe_events.sum() * 1.0) / total_days if total_days > 0 else 0.0
        else:
            special_events = 0.0
            logging.warning("Bitcoin recent historical data is empty for Special Events.")

        # Additional Factors
        if not btc_hist.empty:
            btc_pct_change_long = btc_hist['Close'].pct_change().rolling(window=30).std()
            historical_pct_change = btc_hist['Close'].pct_change().rolling(window=180).std()
            if not btc_pct_change_long.empty and not historical_pct_change.empty:
                volatility_index = (btc_pct_change_long.iloc[-1] - historical_pct_change.mean()) / historical_pct_change.mean() if historical_pct_change.mean() != 0 else 0.0
            else:
                volatility_index = 0.0
                logging.warning("Insufficient data for Volatility Index calculation.")
        else:
            volatility_index = 0.0
            logging.warning("Bitcoin historical data is empty for Volatility Index.")

        price_momentum = calculate_momentum(btc_recent_hist['Close'], period=10)

        if not btc_recent_hist.empty:
            avg_volume = btc_recent_hist['Volume'].mean()
            current_volume = btc_recent_hist['Volume'][-1]
            volume_momentum = (current_volume - avg_volume) / avg_volume if avg_volume != 0 else 0.0
        else:
            volume_momentum = 0.0
            logging.warning("Bitcoin recent historical data is empty for Volume Momentum.")

        # Compile the scores
        factors = {
            "Supply and Demand": price_trend,
            "Market Sentiment": sentiment_score,
            "Institutional Adoption": adoption_score,
            "Economic Conditions": economic_conditions,
            "Technological Evolution": tech_evolution,
            "Regulation": regulation_score,
            "Competition": competition_score,
            "Financial Integration": financial_integration,
            "Geopolitical Factors": geopolitical,
            "Special Events": special_events,
            "Volatility Index": volatility_index,
            "Price Momentum": price_momentum,
            "Volume Momentum": volume_momentum
        }

        # Normalize factor scores using Min-Max Scaling to [-1, 1]
        standardized_factors = min_max_normalize_manual(factors, feature_range=(-1, 1))

        return standardized_factors

    except Exception as e:
        logging.error(f"Error retrieving data via yfinance: {e}")
        return {factor: 0.0 for factor in FACTORS_LIST}

def evaluate_factors():
    """
    Evaluates all factors by retrieving their respective scores via yfinance.

    Returns:
        dict: Dictionary containing factor names and their corresponding scores.
    """
    factors = get_bitcoin_data()
    return factors

def calculate_index(factors):
    """
    Calculates the global index based on factor scores and their weights.

    Args:
        factors (dict): Dictionary of factor scores.

    Returns:
        float: Calculated index.
    """
    total_weight = sum(WEIGHTS.get(factor, 1) for factor in factors)
    total_score = sum(score * WEIGHTS.get(factor, 1) for factor, score in factors.items())
    # Normalize the index to be within the range [-1, 1]
    normalized_index = total_score / total_weight if total_weight != 0 else 0.0
    return normalized_index

def plot_factors(factors):
    """
    Displays a horizontal bar chart of factor scores.

    Args:
        factors (dict): Dictionary of factor scores.
    """
    try:
        # Clear the previous plot
        for widget in frame_plot.winfo_children():
            widget.destroy()

        factor_names = list(factors.keys())
        scores = list(factors.values())

        fig, ax = plt.subplots(figsize=(6, 8))  # Adjusted size for more factors
        # Enhanced color scheme for better accessibility
        colors = ['#2ecc71' if x >= 0 else '#e74c3c' for x in scores]
        bars = ax.barh(factor_names, scores, color=colors)
        ax.axvline(0, color='black')
        ax.set_xlabel('Standardized Score')
        ax.set_title('Factor Evaluation')

        # Dynamically adjust label positions based on score magnitude
        x_limits = ax.get_xlim()
        for bar in bars:
            width = bar.get_width()
            if width >= 0:
                label_x = width + (x_limits[1] * 0.01)  # 1% padding
                alignment = 'left'
            else:
                label_x = width - (x_limits[1] * 0.01)  # 1% padding
                alignment = 'right'
            ax.text(label_x, 
                    bar.get_y() + bar.get_height() / 2, 
                    f"{width:.2f}", 
                    va='center',
                    ha=alignment,
                    fontsize=8,
                    color='black')

        plt.tight_layout()
        canvas = FigureCanvasTkAgg(fig, master=frame_plot)
        canvas.draw()
        canvas.get_tk_widget().pack()
    except Exception as e:
        logging.error(f"Error plotting factors: {e}")
        messagebox.showerror("Plotting Error", f"An error occurred while plotting factors: {e}")

def update_results():
    """
    Retrieves factor scores, calculates the index, and queues the results for GUI update.
    """
    try:
        factors = evaluate_factors()
        index = calculate_index(factors)
        # Queue the results for the main thread to process
        result_queue.put((factors, index))
    except Exception as e:
        logging.error(f"Error updating results: {e}")
        result_queue.put(("error", str(e)))

def process_queue():
    """
    Processes items in the result_queue and updates the GUI accordingly.
    This function is scheduled to run periodically using Tkinter's after method.
    """
    try:
        while not result_queue.empty():
            result = result_queue.get_nowait()
            if result[0] == "error":
                # Handle the error
                messagebox.showerror("Error", f"An error occurred while updating results: {result[1]}")
                label_result.config(text="Predictive Index: Error", fg="red")
            else:
                factors, index = result
                # Display factor scores
                for factor, score in factors.items():
                    tree.set(factor, "Score", f"{score:.4f}")  # Increased precision

                # Display the index
                if index > 0.5:
                    trend = "Strong Uptrend Predicted 📈"
                    color = "#27ae60"  # Dark green
                elif 0 < index <= 0.5:
                    trend = "Moderate Uptrend Predicted 📈"
                    color = "#2ecc71"  # Light green
                elif -0.5 <= index < 0:
                    trend = "Moderate Downtrend Predicted 📉"
                    color = "#e67e22"  # Orange
                elif index < -0.5:
                    trend = "Strong Downtrend Predicted 📉"
                    color = "#c0392b"  # Dark red
                else:
                    trend = "Stable ➖"
                    color = "#95a5a6"  # Gray

                label_result.config(text=f"Predictive Index: {trend} (Score: {index:.2f})", fg=color)

                # Display the chart
                plot_factors(factors)
    except Exception as e:
        logging.error(f"Error processing queue: {e}")
    finally:
        # Schedule the next queue check
        root.after(100, process_queue)

def update_results_threaded():
    """
    Starts the update_results function in a separate thread to keep the GUI responsive.
    """
    # Disable the update button to prevent multiple threads
    button_update.config(state='disabled')
    def target():
        try:
            update_results()
        finally:
            # Re-enable the update button after processing
            button_update.config(state='normal')
    thread = threading.Thread(target=target)
    thread.start()

# Configure the main window
root = tk.Tk()
root.title("Bitcoin Price Analysis")

# Configure layout
root.geometry("800x1000")  # Increased size for better visibility and additional factors
root.resizable(False, False)

# Title
title = ttk.Label(root, text="Bitcoin Trend Predictor", font=("Helvetica", 18, "bold"))
title.pack(pady=10)

# Factors table
columns = ("Factor", "Score")
tree = ttk.Treeview(root, columns=columns, show='headings', height=13)  # Increased height for new factors
tree.heading("Factor", text="Factor")
tree.heading("Score", text="Score")
tree.column("Factor", width=300, anchor='center')  # Increased width for better readability
tree.column("Score", width=100, anchor='center')

# Insert factors with `iid` corresponding to the factor name
for factor in FACTORS_LIST:
    tree.insert('', tk.END, iid=factor, values=(factor, "0.0000"))

tree.pack(pady=10)

# Index result (using tkinter.Label)
label_result = tk.Label(root, text="Predictive Index: ", font=("Helvetica", 16, "bold"))
label_result.pack(pady=10)

# Update button
button_update = ttk.Button(root, text="Evaluate", command=update_results_threaded)
button_update.pack(pady=10)

# Frame for the chart
frame_plot = ttk.Frame(root)
frame_plot.pack(pady=10, fill='both', expand=True)

# Initial call to process_queue to start periodic checking
root.after(100, process_queue)

# Run the main loop
root.mainloop()