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

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'
)

# List of factors
FACTORS_LIST = [
    "Regulation",
    "Institutional Adoption",
    "Market Sentiment",
    "Economic Conditions",
    "Technological Evolution",
    "Supply and Demand",
    "Competition",
    "Financial Integration",
    "Geopolitical Factors",
    "Special Events"
]

# Define weights for each factor
WEIGHTS = {
    "Regulation": 1.0,
    "Institutional Adoption": 1.0,
    "Market Sentiment": 1.5,
    "Economic Conditions": 1.0,
    "Technological Evolution": 1.2,  # Adjust if necessary
    "Supply and Demand": 2.0,
    "Competition": 1.2,  # Increased weight due to expanded analysis
    "Financial Integration": 1.0,
    "Geopolitical Factors": 0.8,  # Adjusted weight
    "Special Events": 0.7
}

# Base price for normalization
BASE_PRICE = 50000

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

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

    Returns:
        dict: Contains current price, market sentiment, etc.
    """
    try:
        btc = yf.Ticker("BTC-USD")
        eth = yf.Ticker("ETH-USD")
        ltc = yf.Ticker("LTC-USD")  # Added Litecoin for competition
        xrp = yf.Ticker("XRP-USD")  # Added Ripple for competition
        sp500 = yf.Ticker("^GSPC")
        vix = yf.Ticker("^VIX")
        
        # Define the period for historical data (150 days for better coverage)
        end_date = datetime.today()
        start_date = end_date - timedelta(days=150)
        
        # 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)
        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:
            mean_price = btc_recent_hist['Close'].mean()
            if mean_price != 0:
                price_trend = (current_price - mean_price) / mean_price  # Normalization
            else:
                price_trend = 0.0
                logging.warning("Mean price over recent period is zero.")
        else:
            price_trend = 0.0
            logging.warning("Bitcoin recent historical data is empty.")
    
        # Sentiment based on recent changes
        recent_changes = btc_recent_hist['Close'].pct_change().dropna()
        sentiment_score = recent_changes.mean() if not recent_changes.empty else 0.0  # Average of changes
    
        # Institutional Adoption: Use volumes
        avg_volume = btc_recent_hist['Volume'].mean() if not btc_recent_hist.empty else 0.0
        current_volume = btc_recent_hist['Volume'][-1] if not btc_recent_hist.empty else 0.0
        if avg_volume != 0:
            adoption_score = (current_volume / avg_volume) - 1  # Normalized
        else:
            adoption_score = 0.0
            logging.warning("Average volume is zero.")
    
        # Economic Conditions: Use VIX as a proxy
        if not vix_hist.empty:
            avg_vix = vix_hist['Close'].mean()
            current_vix = vix_hist['Close'][-1]
            if avg_vix != 0:
                economic_conditions = (avg_vix - current_vix) / avg_vix  # Normalized
            else:
                economic_conditions = 0.0
                logging.warning("Average VIX is zero.")
        else:
            economic_conditions = 0.0
            logging.warning("VIX historical data is empty.")
    
        # Technological Evolution: Enhanced proxy using historical commit counts (simulated)
        # Since no API is allowed, we'll simulate technological evolution with moving average of price volatility
        if not btc_recent_hist.empty:
            volatility = btc_recent_hist['Close'].std()
            mean_close = btc_recent_hist['Close'].mean()
            if mean_close != 0:
                # Simulate technological evolution as inverse of volatility trend
                tech_evolution = (1 / volatility) * mean_close  # Higher value suggests more stability, implying technological maturity
            else:
                tech_evolution = 0.0
                logging.warning("Mean close is zero for technological evolution.")
        else:
            tech_evolution = 0.0
            logging.warning("Bitcoin recent historical data is empty for technological evolution.")
    
        # Regulation: Enhanced proxy using changes in volume volatility
        if not btc_hist.empty:
            volatility = btc_hist['Close'].std()
            # Compare current volatility with a 3-month historical average
            extended_start_date = start_date - timedelta(days=90)
            hist_extended = btc.history(start=extended_start_date, end=start_date)
            if not hist_extended.empty:
                mean_volatility = hist_extended['Close'].std()
                if mean_volatility != 0:
                    regulation_score = (volatility - mean_volatility) / mean_volatility
                else:
                    regulation_score = 0.0  # Neutral if mean_volatility is zero
                    logging.warning("Mean volatility over extended period is zero.")
            else:
                regulation_score = 0.0  # Neutral default instead of simplified
                logging.warning("Extended historical data for regulation is empty.")
        else:
            regulation_score = 0.0
            logging.warning("Bitcoin historical data is empty for regulation.")
    
        # Competition: Compare performance with Ethereum, Litecoin, and Ripple
        competition_score = 0.0
        competitors = {
            "ETH-USD": eth_hist,
            "LTC-USD": ltc_hist,
            "XRP-USD": xrp_hist
        }
        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

        if btc_start != 0:
            btc_return = (btc_end / btc_start) - 1
            total_competitor_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
                        total_competitor_return += comp_return
                        valid_competitors += 1
            if valid_competitors > 0:
                average_competitor_return = total_competitor_return / valid_competitors
                competition_score = btc_return - average_competitor_return  # Positive if BTC outperforms average competitors
            else:
                competition_score = 0.0
                logging.warning("No valid competitors for competition score calculation.")
        else:
            competition_score = 0.0
            logging.warning("Bitcoin start price is zero for competition score.")
    
        # Financial Integration: Rolling correlation with the S&P 500
        financial_integration = 0.5  # Default neutral value
        if not sp500_hist.empty and not btc_hist.empty:
            # Calculate daily returns
            btc_returns = btc_hist['Close'].pct_change().dropna()
            sp500_returns = sp500_hist['Close'].pct_change().dropna()
            
            # Align the dates
            btc_returns_aligned, sp500_returns_aligned = btc_returns.align(sp500_returns, join='inner')
            
            # Check if there are enough data points
            if len(btc_returns_aligned) >= 60:  # Increased window for rolling correlation
                window_size = 30
                rolling_corr = btc_returns_aligned.rolling(window=window_size).corr(sp500_returns_aligned)
                recent_corr = rolling_corr.dropna()[-1] if not rolling_corr.dropna().empty else np.nan
                if pd.notnull(recent_corr):
                    # Normalize correlation from [-1, 1] to [0, 1]
                    financial_integration = (recent_corr + 1) / 2
                else:
                    financial_integration = 0.5  # Neutral value if corr is NaN
                    logging.warning("Recent rolling correlation is NaN.")
            else:
                financial_integration = 0.5  # Default neutral value
                logging.warning("Not enough data points for rolling correlation.")
        else:
            financial_integration = 0.5  # Default neutral value
            logging.warning("S&P 500 or Bitcoin historical data is empty for financial integration.")
    
        # Geopolitical Factors: Enhanced proxy using economic_conditions and regulation_score
        # Combining multiple existing proxies to better reflect geopolitical factors
        geopolitical = economic_conditions * 0.6 + regulation_score * 0.4  # Weighted combination
        # Ensure geopolitical factors are within [-1,1]
        geopolitical = max(min(geopolitical, 1.0), -1.0)
    
        # Special Events: Use sudden price variations
        if not btc_recent_hist.empty:
            daily_changes = btc_recent_hist['Close'].pct_change().abs()
            # Define a dynamic threshold based on historical volatility
            dynamic_threshold = daily_changes.mean() + 2 * daily_changes.std()
            events = daily_changes > dynamic_threshold
            if len(btc_recent_hist) > 0:
                special_events = events.sum() / len(btc_recent_hist)  # Normalized
            else:
                special_events = 0.0
        else:
            special_events = 0.0
            logging.warning("Bitcoin recent historical data is empty for special events.")
    
        # 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
        }
    
        # Standardize factor scores (z-score normalization)
        factor_values = np.array(list(factors.values()))
        mean_factors = factor_values.mean()
        std_factors = factor_values.std()
        if std_factors != 0:
            standardized_factors = {factor: (score - mean_factors) / std_factors for factor, score in factors.items()}
        else:
            standardized_factors = {factor: 0.0 for factor in factors}
            logging.warning("Standard deviation of factors is zero.")
    
        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_score = sum(score * WEIGHTS.get(factor, 1) for factor, score in factors.items())
    return total_score

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, 4))
        # 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("800x900")  # Increased size for better visibility
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=10)
tree.heading("Factor", text="Factor")
tree.heading("Score", text="Score")
tree.column("Factor", width=250, anchor='center')
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()