plz fix these 2 files so they work together, and the GUI of main.py loads, i'm tired make it work already, its so simple erroes, keypair, and pubkey and idiotic issues solve them and output working main.py:
# main.py

import sys
import threading
import queue
import time
import termios
import tty
import select
import os
import Keypair
import json
from collections import deque
from rich.console import Console
from rich.layout import Layout
from rich.panel import Panel
from rich.text import Text
from rich.live import Live
from rich.align import Align
from rich.table import Table
from rich import box

from trading_agent import TradingAgent, Config  # Import the TradingAgent and Config classes

# Initialize OpenAI client using environment variable
import openai
openai.api_key = os.environ.get('OPENAI_API_KEY', 'YOUR_OPENAI_API_KEY')

console = Console()

# Queues for thread-safe communication
log_queue = queue.Queue()
chat_queue = queue.Queue()

# Define the terminal settings for non-blocking input
class NonBlockingInput:
    def __init__(self):
        self.fd = sys.stdin.fileno()
        self.original_attrs = None
        tty.setcbreak(self.fd)

    def get_key(self):
        dr, dw, de = select.select([sys.stdin], [], [], 0)
        if dr:
            return sys.stdin.read(1)
        return None

    def restore(self):
        termios.tcsetattr(self.fd, termios.TCSADRAIN, self.original_attrs)

class TerminalTradingAgentUI:
    def __init__(self):
        # Configuration Fields with defaults
        self.config_fields = {
            "llm_api_key": os.environ.get('OPENAI_API_KEY', 'YOUR_OPENAI_API_KEY'),
            "llm_model": "gpt-4",
            "api_key": "your_api_key",
            "api_secret": "your_api_secret",
            "coin_list": 'SOL/USDT,SRM/USDT,RAY/USDT,KIN/USDT,FIDA/USDT',
            "interval": "5m",
            "base_trade_amount": "10",
            "stop_loss_percent": "2",
            "take_profit_percent": "5",
            "position_check_interval": "60",
        }
        self.should_exit = False

        # Assign log_queue and chat_queue to instance variables
        self.log_queue = log_queue
        self.chat_queue = chat_queue

        # Initialize UI State
        self.focus_order = [
            "chat_input",
            "llm_api_key",
            "api_key",
            "api_secret",
            "coin_list",
            "interval",
            "base_trade_amount",
            "stop_loss_percent",
            "take_profit_percent",
            "position_check_interval",
            "apply_button",
            "start_agent_button",
            "stop_agent_button",
            "agent_list"
        ]
        self.current_focus_index = 0  # Start with chat_input
        self.in_edit_mode = False
        self.edit_field = None
        self.input_buffer = ""
        self.chat_input = ""

        # Popup State
        self.show_popup = False
        self.popup_type = None  # 'edit', 'confirm', 'input'
        self.popup_field = None
        self.edit_buffer = ""

        # Chat and Logs
        self.chat_history = deque(maxlen=100)
        self.logs = deque(maxlen=100)

        # Initialize NonBlockingInput
        self.nbi = NonBlockingInput()

        # Initialize agents list
        self.trading_agents = []
        self.agent_focus_index = 0

        # Start log and chat handlers
        self.log_thread = threading.Thread(target=self.handle_logs, daemon=True)
        self.log_thread.start()
        self.chat_thread = threading.Thread(target=self.handle_chat, daemon=True)
        self.chat_thread.start()

        # Ensure at least one agent (agent0) is initialized
        self.initialize_default_agent()

        # Initialize layout
        self.layout = self.create_layout()

        # Start background threads
        self.input_thread = threading.Thread(target=self.handle_inputs, daemon=True)
        self.input_thread.start()
        self.dashboard_update_thread = threading.Thread(target=self.update_dashboard_loop, daemon=True)
        self.dashboard_update_thread.start()

    
    def initialize_default_agent(self):
        # Create a default agent (agent0) and set it to running using a real wallet
        keypair_path = os.path.join(os.getcwd(), 'keys', 'wallet1.json')  # Update the path as needed
        agent_config = Config()
        agent_config.api_key = self.config_fields["api_key"]
        agent_config.api_secret = self.config_fields["api_secret"]
        agent_config.llm_api_key = self.config_fields["llm_api_key"]
        agent_config.llm_model = self.config_fields["llm_model"]
        agent_config.coin_list = self.config_fields["coin_list"].split(',')
        agent_config.interval = self.config_fields["interval"]
        agent_config.interval_seconds = self.get_interval_seconds(agent_config.interval)
        agent_config.base_trade_amount = float(self.config_fields["base_trade_amount"])
        agent_config.stop_loss_percent = float(self.config_fields["stop_loss_percent"])
        agent_config.take_profit_percent = float(self.config_fields["take_profit_percent"])
        agent_config.position_check_interval = int(self.config_fields["position_check_interval"])

        default_agent = TradingAgent(agent_config, log_queue=self.log_queue, chat_queue=self.chat_queue)
        default_agent.initialize_wallet(keypair_path)
        default_agent.start()
        self.trading_agents.append(default_agent)
        self.agent_focus_index = 0
        self.log_queue.put(("log", "Default agent started with real wallet."))

    def render_wallet_panel(self):
        if self.trading_agents:
            current_agent = self.trading_agents[self.agent_focus_index]
            wallet_info = current_agent.wallet
            wallet_address = wallet_info.get('address', 'N/A')
            pnl = wallet_info.get('pnl', 0.0)
            balance = wallet_info.get('balance', {})
            wallet_content = (
                f"Agent ID: {self.agent_focus_index}\n"
                f"Wallet Address: {wallet_address}\n"
                f"PnL: {pnl}\n\nBalances:\n"
            )
            for coin, amount in balance.items():
                wallet_content += f"  {coin}: {amount:.4f}\n"

            wallet_panel = Panel(
                Align.left(wallet_content),
                title="Wallet",
                border_style="bold magenta",
                box=box.ROUNDED
            )
        else:
            wallet_panel = Panel(
                Align.center("No agents running."),
                title="Wallet",
                border_style="bold magenta",
                box=box.ROUNDED
            )
        self.layout["right_pane"]["wallet"].update(wallet_panel)

    def initialize_wallet(self, keypair_path):
            """
            Initialize the wallet by loading the keypair and fetching balances.
            """
            try:
                # Load the keypair from the specified file
                self.keypair = self.load_keypair_from_file(keypair_path)

                # Set wallet attributes
                self.wallet['address'] = str(self.keypair.public_key)
                self.wallet['pnl'] = 0.0
                self.wallet['balance'] = self.fetch_wallet_balances()
            except Exception as e:
                self.log(f"Error initializing wallet: {e}", "error")
                raise     

    def load_keypair_from_file(self, filepath):
            """
            Load a Solana keypair from a JSON file.
            The JSON file should contain an array of numbers representing the secret key.
            """
            try:
                with open(filepath, 'r') as f:
                    keypair_data = json.load(f)
                secret_key = bytes(keypair_data)
                keypair = keypair.from_secret_key(secret_key)
                self.log(f"Successfully loaded keypair from {filepath}", "info")
                return keypair
            except Exception as e:
                self.log(f"Failed to load keypair from {filepath}: {e}", "error")
                raise

    def fetch_wallet_balances(self):
            """
            Fetch the wallet balances from the Solana blockchain using the real wallet address.
            """
            balances = {}
            try:
                if not self.keypair:
                    raise Exception("Keypair not loaded. Cannot fetch balances.")

                address = self.wallet['address']
                # Fetch account balance in lamports
                response = self.client.get_balance(address)
                if 'result' in response and response['result']:
                    balances['SOL'] = response['result']['value'] / 1e9  # Convert lamports to SOL
                else:
                    balances['SOL'] = 0.0

                # For demonstration, fetch other token balances if applicable
                # This requires additional implementation based on the tokens you hold
                # Example (pseudo-code):
                # balances['USDT'] = self.get_token_balance(address, 'USDT')
                # balances['SRM'] = self.get_token_balance(address, 'SRM')
                # Add similar calls for other tokens

                self.log(f"Fetched wallet balances: {balances}", "info")
            except Exception as e:
                self.log(f"Error fetching wallet balances: {e}", "error")
                if self.log_queue:
                    self.log_queue.put(("error", f"Error fetching wallet balances: {str(e)}"))
                else:
                    print(f"Error fetching wallet balances: {str(e)}")
            return balances
    def get_token_balance(self, address, token_symbol):
            # Implement token balance fetching logic here
            pass


    def mask_api_key(self, key):
        if key and len(key) >= 12:
            return f"{key[:4]}****{key[-4:]}"
        elif key and len(key) > 8:
            return f"{key[:4]}{'*' * (len(key)-8)}{key[-4:]}"
        elif key:
            return "*" * len(key)
        return key

    def create_layout(self):
        layout = Layout()

        # Define the main areas
        layout.split(
            Layout(name="header", size=3),
            Layout(name="body", ratio=1),
            Layout(name="footer", size=3)
        )

        # Header
        header_text = Text("Terminal Trading Agent Dashboard", style="bold white on blue")
        header_panel = Panel(Align.center(header_text), style="bold blue", box=box.ROUNDED)
        layout["header"].update(header_panel)

        # Body split into sidebar, main, and right pane
        layout["body"].split_row(
            Layout(name="sidebar", size=40),
            Layout(name="main", ratio=1),
            Layout(name="right_pane", size=40)
        )

        # Initialize empty layouts for main areas to avoid KeyError
        layout["sidebar"].update(Panel("Loading...", title="Configuration"))
        layout["main"].split(
            Layout(name="dashboard", ratio=3),
            Layout(name="chat_area", ratio=1)
        )
        layout["main"]["dashboard"].update(Panel("Loading...", title="Agent Dashboard"))
        layout["main"]["chat_area"].update(Panel("Loading...", title="Chat"))
        layout["right_pane"].split(
            Layout(name="logs", ratio=1),
            Layout(name="wallet", ratio=1)
        )
        layout["right_pane"]["logs"].update(Panel("Loading...", title="Logs"))
        layout["right_pane"]["wallet"].update(Panel("Loading...", title="Wallet"))

        # Chat Input Area at the bottom
        layout["footer"].update(self.render_chat_input_panel())

        return layout

    def render_config_panel(self):
        config_table = Table.grid(expand=True, padding=(1, 2))
        config_table.add_column(justify="left", ratio=2)
        config_table.add_column(justify="left", ratio=3)

        for field, value in self.config_fields.items():
            if field.endswith("key") or field.endswith("secret"):
                display_value = self.mask_api_key(value) if value and value != "Not Set" else value
            else:
                display_value = str(value)

            if self.show_popup and self.popup_type == 'edit' and self.popup_field == field:
                display_value = self.edit_buffer + "|"

            if self.focus_order[self.current_focus_index] == field:
                config_table.add_row(
                    f"[bold cyan]{field.replace('_', ' ').upper()}:[/bold cyan]",
                    f"[underline bold yellow]{display_value}[/underline bold yellow]"
                )
            else:
                config_table.add_row(
                    f"[bold cyan]{field.replace('_', ' ').upper()}:[/bold cyan]",
                    display_value
                )

        # Add Apply and Control buttons at the bottom of the sidebar
        apply_button_label = "[bold magenta]<Apply Settings>"
        if self.focus_order[self.current_focus_index] == "apply_button":
            apply_button_label = "[underline bold yellow]<Apply Settings>[/underline bold yellow]"

        start_agent_button_label = "[bold green]<Start Agent>"
        if self.focus_order[self.current_focus_index] == "start_agent_button":
            start_agent_button_label = "[underline bold yellow]<Start Agent>[/underline bold yellow]"

        stop_agent_button_label = "[bold red]<Stop Agent>"
        if self.focus_order[self.current_focus_index] == "stop_agent_button":
            stop_agent_button_label = "[underline bold yellow]<Stop Agent>[/underline bold yellow]"

        config_table.add_row("", "")
        config_table.add_row(apply_button_label, "")
        config_table.add_row(start_agent_button_label, "")
        config_table.add_row(stop_agent_button_label, "")

        config_panel = Panel(config_table, title="Configuration", border_style="bold green", box=box.ROUNDED)

        return config_panel

    def render_chat_area_panel(self):
        chat_content = "\n".join([msg for msg in self.chat_history])
        chat_panel = Panel(Align.left(chat_content), title="Chat", border_style="bold yellow", box=box.ROUNDED)
        return chat_panel

    def render_dashboard_panel(self):
        dashboard_table = Table(expand=True, box=box.SIMPLE_HEAVY)
        dashboard_table.add_column("ID", justify="center")
        dashboard_table.add_column("Coin Pair", justify="center")
        dashboard_table.add_column("Status", justify="center")
        dashboard_table.add_column("Timer", justify="center")
        dashboard_table.add_column("Last Operation", justify="center")
        dashboard_table.add_column("Interval", justify="center")
        dashboard_table.add_column("Control", justify="center")

        rotating_frames = ['-', '\\', '|', '/']

        if self.trading_agents:
            for idx, agent in enumerate(self.trading_agents):
                # Status with rotating animation
                if agent.running:
                    frame = rotating_frames[int(time.time() * 2) % len(rotating_frames)]
                    status = f"[bold green]{frame} Running[/bold green]"
                else:
                    status = f"[cyan]Idle[/cyan]"

                # Timer display
                time_remaining = max(0, agent.next_action_time - time.time()) if agent.running else 0
                timer_display = time.strftime('%H:%M:%S', time.gmtime(time_remaining))

                # Last operation
                last_op = agent.last_operation

                # Coin pair
                coin_pair = ", ".join(agent.config.coin_list)

                # Interval
                interval = f"{agent.config.interval_seconds} sec"

                # Control buttons
                if self.focus_order[self.current_focus_index] == "agent_list" and self.agent_focus_index == idx:
                    control = "[underline yellow]<P>/<S>[/underline yellow]"
                    agent_id_style = "[underline bold yellow]"
                else:
                    control = "<P>/<S>"
                    agent_id_style = ""
                control += " (Play/Pause/Stop)"

                dashboard_table.add_row(
                    f"{agent_id_style}{idx}{'' if agent_id_style == '' else '[/underline bold yellow]'}",
                    coin_pair,
                    status,
                    timer_display,
                    last_op,
                    interval,
                    control
                )
        else:
            dashboard_table.add_row("No agents running.", "", "", "", "", "", "")

        dashboard_panel = Panel(Align.center(dashboard_table), title="Agent Dashboard", border_style="bold green", box=box.ROUNDED)
        return dashboard_panel

    def render_logs_panel(self):
        logs_content = "\n".join([msg for msg in list(self.logs)[-20:]])
        logs_panel = Panel(Align.left(logs_content), title="Logs", border_style="bold blue", box=box.ROUNDED)
        return logs_panel

    

    def render_chat_input_panel(self):
        cursor = "|" if int(time.time() * 2) % 2 == 0 else " "  # Simple blinking cursor
        display_input = self.chat_input + cursor if self.focus_order[self.current_focus_index] == "chat_input" else self.chat_input
        chat_input_table = Table.grid(padding=(1,2))
        chat_input_table.add_column(justify="left", ratio=1)
        chat_input_table.add_column(justify="left", ratio=4)
        chat_input_table.add_row(
            Text(">> ", style="bold green"),
            Text(display_input, style="bold green")
        )
        chat_input_panel = Panel(chat_input_table, border_style="bold magenta", box=box.ROUNDED)
        return chat_input_panel

    def handle_logs(self):
        while not self.should_exit:
            try:
                msg_type, message = self.log_queue.get(timeout=1)
                if msg_type == "log":
                    self.logs.append(f"{message}")
                elif msg_type == "error":
                    self.logs.append(f"[bold red]{message}[/bold red]")
                elif msg_type == "warning":
                    self.logs.append(f"[bold yellow]{message}[/bold yellow]")
                self.update_logs_panel()
            except queue.Empty:
                pass

    def handle_chat(self):
        while not self.should_exit:
            try:
                msg_type, message = self.chat_queue.get(timeout=1)
                if msg_type == "assistant":
                    self.chat_history.append(f"Assistant: {message}")
                elif msg_type == "agent":
                    self.chat_history.append(f"Agent: {message}")
                elif msg_type == "user":
                    self.chat_history.append(f"User: {message}")
                self.update_chat_area_panel()
            except queue.Empty:
                pass

    def handle_inputs(self):
        while not self.should_exit:
            key = self.nbi.get_key()
            if key:
                if key == '\x03':  # Ctrl-C
                    self.should_exit = True
                    continue
                if self.show_popup:
                    if key == '\n':
                        # Handle popup submission
                        self.config_fields[self.popup_field] = self.edit_buffer
                        self.edit_buffer = ''
                        self.show_popup = False
                        self.update_config_panel()
                    elif key == '\x1b':
                        # Handle popup cancellation
                        self.edit_buffer = ''
                        self.show_popup = False
                        self.update_config_panel()
                    elif key == '\x7f':  # Backspace
                        self.edit_buffer = self.edit_buffer[:-1]
                        self.update_config_panel()
                    else:
                        self.edit_buffer += key
                        self.update_config_panel()
                    continue

                elif self.focus_order[self.current_focus_index] == "chat_input":
                    if key == '\n':
                        self.process_command(self.chat_input)
                        self.chat_input = ""
                        self.update_chat_input_panel()
                    elif key == '\t' or key == '\x0f':  # Tab key
                        self.current_focus_index = (self.current_focus_index + 1) % len(self.focus_order)
                        self.update_focus()
                    elif key == '\x7f':
                        self.chat_input = self.chat_input[:-1]
                        self.update_chat_input_panel()
                    else:
                        self.chat_input += key
                        self.update_chat_input_panel()
                else:
                    if key == '\t' or key == '\x0f':  # Tab key
                        self.current_focus_index = (self.current_focus_index + 1) % len(self.focus_order)
                        self.update_focus()
                    elif key == '\n':
                        focus_field = self.focus_order[self.current_focus_index]
                        if focus_field in self.config_fields:
                            self.popup_type = 'edit'
                            self.popup_field = focus_field
                            self.edit_buffer = ''
                            self.show_popup = True
                            self.update_config_panel()
                        elif focus_field == 'apply_button':
                            self.apply_settings()
                        elif focus_field == 'start_agent_button':
                            self.start_new_agent()
                        elif focus_field == 'stop_agent_button':
                            self.stop_current_agent()
                        elif focus_field == 'agent_list':
                            pass  # For now do nothing

                    elif key == 'p':
                        # Pause or play the selected agent
                        if self.focus_order[self.current_focus_index] == 'agent_list':
                            agent = self.trading_agents[self.agent_focus_index]
                            if agent.running:
                                agent.stop()
                                self.log_queue.put(("log", f"Agent {self.agent_focus_index} paused."))
                            else:
                                agent.start()
                                self.log_queue.put(("log", f"Agent {self.agent_focus_index} resumed."))
                    elif key == 's':
                        # Stop the selected agent
                        if self.focus_order[self.current_focus_index] == 'agent_list':
                            agent = self.trading_agents[self.agent_focus_index]
                            agent.stop()
                            self.trading_agents.pop(self.agent_focus_index)
                            self.log_queue.put(("log", f"Agent {self.agent_focus_index} stopped and removed."))
                            if self.agent_focus_index >= len(self.trading_agents):
                                self.agent_focus_index = len(self.trading_agents) - 1
                    elif key == '\x1b':
                        pass  # Handle ESC if necessary
                    elif key == '\x1b[A':
                        # Up arrow
                        if self.focus_order[self.current_focus_index] == 'agent_list':
                            if self.agent_focus_index > 0:
                                self.agent_focus_index -= 1
                                self.update_dashboard_panel()
                    elif key == '\x1b[B':
                        # Down arrow
                        if self.focus_order[self.current_focus_index] == 'agent_list':
                            if self.agent_focus_index < len(self.trading_agents) -1:
                                self.agent_focus_index +=1
                                self.update_dashboard_panel()
                    else:
                        pass  # Ignore other keys for now
            else:
                time.sleep(0.1)

    def process_command(self, command):
        if command.startswith('/'):
            parts = command[1:].split(' ', 1)
            if len(parts) >= 2 and parts[0].isdigit():
                agent_index = int(parts[0])
                if agent_index < len(self.trading_agents):
                    agent_command = parts[1].strip()
                    self.chat_queue.put(("user", f"(To Agent {agent_index}): {agent_command}"))
                    self.trading_agents[agent_index].receive_command(agent_command)
                else:
                    self.chat_history.append(f"Agent {agent_index} does not exist.")
                    self.update_chat_area_panel()
            else:
                self.chat_history.append("Invalid command. Usage: /<agent_index> <message>")
                self.update_chat_area_panel()
        else:
            # General chat command to default agent
            if self.trading_agents:
                default_agent = self.trading_agents[0]
                self.chat_queue.put(("user", command))
                default_agent.receive_command(command)
            else:
                self.chat_history.append("No agents running to receive the command.")
                self.update_chat_area_panel()

    def update_logs_panel(self):
        self.layout["right_pane"]["logs"].update(self.render_logs_panel())

    def update_chat_area_panel(self):
        self.layout["main"]["chat_area"].update(self.render_chat_area_panel())

    def update_dashboard_panel(self):
        self.layout["main"]["dashboard"].update(self.render_dashboard_panel())
        self.layout["right_pane"]["wallet"].update(self.render_wallet_panel())

    def update_config_panel(self):
        self.layout["sidebar"].update(self.render_config_panel())

    def update_chat_input_panel(self):
        self.layout["footer"].update(self.render_chat_input_panel())

    def update_dashboard_loop(self):
        while not self.should_exit:
            self.update_dashboard_panel()
            self.update_chat_input_panel()
            time.sleep(0.5)

    def update_focus(self):
        # Update the UI to reflect the current focus
        self.update_config_panel()
        self.update_chat_input_panel()
        self.update_dashboard_panel()

    def apply_settings(self):
        self.log_queue.put(("log", "Settings applied."))
        # Apply settings to all agents (if needed)
        for agent in self.trading_agents:
            agent.config.api_key = self.config_fields["api_key"]
            agent.config.api_secret = self.config_fields["api_secret"]
            agent.config.llm_api_key = self.config_fields["llm_api_key"]
            agent.config.llm_model = self.config_fields["llm_model"]
            agent.config.coin_list = self.config_fields["coin_list"].split(',')
            agent.config.interval = self.config_fields["interval"]
            agent.config.interval_seconds = self.get_interval_seconds(agent.config.interval)
            agent.config.base_trade_amount = float(self.config_fields["base_trade_amount"])
            agent.config.stop_loss_percent = float(self.config_fields["stop_loss_percent"])
            agent.config.take_profit_percent = float(self.config_fields["take_profit_percent"])
            agent.config.position_check_interval = int(self.config_fields["position_check_interval"])

    def start_new_agent(self):
        agent_config = Config()
        agent_config.api_key = self.config_fields["api_key"]
        agent_config.api_secret = self.config_fields["api_secret"]
        agent_config.llm_api_key = self.config_fields["llm_api_key"]
        agent_config.llm_model = self.config_fields["llm_model"]
        agent_config.coin_list = self.config_fields["coin_list"].split(',')
        agent_config.interval = self.config_fields["interval"]
        agent_config.interval_seconds = self.get_interval_seconds(agent_config.interval)
        agent_config.base_trade_amount = float(self.config_fields["base_trade_amount"])
        agent_config.stop_loss_percent = float(self.config_fields["stop_loss_percent"])
        agent_config.take_profit_percent = float(self.config_fields["take_profit_percent"])
        agent_config.position_check_interval = int(self.config_fields["position_check_interval"])

        new_agent = TradingAgent(agent_config, log_queue=self.log_queue, chat_queue=self.chat_queue)
        new_agent.start()
        self.trading_agents.append(new_agent)
        self.agent_focus_index = len(self.trading_agents) - 1
        self.log_queue.put(("log", f"Started new agent with ID {self.agent_focus_index}."))

    def stop_current_agent(self):
        if self.trading_agents:
            agent = self.trading_agents.pop(self.agent_focus_index)
            agent.stop()
            self.log_queue.put(("log", f"Stopped agent with ID {self.agent_focus_index}."))
            if self.agent_focus_index >= len(self.trading_agents):
                self.agent_focus_index = len(self.trading_agents) - 1

    def get_interval_seconds(self, interval_str):
        units = {'m': 60, 'h': 3600, 'd': 86400}
        try:
            return int(interval_str[:-1]) * units[interval_str[-1]]
        except:
            return 300  # Default to 5 minutes

    def run(self):
        with Live(self.layout, refresh_per_second=10, screen=True) as live:
            while not self.should_exit:
                try:
                    live.update(self.layout)
                    time.sleep(0.1)
                except KeyboardInterrupt:
                    self.should_exit = True
        self.nbi.restore()
        console.print("[bold red]Application has exited.[/bold red]")

if __name__ == "__main__":
    try:
        agent_ui = TerminalTradingAgentUI() 
        agent_ui.run()
    except KeyboardInterrupt:
        sys.exit(0)

trading_agents.py

# trading_agent.py

import time
import threading
import requests
import json
import random
import numpy as np
import pandas as pd
import ta
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler
import openai
from solana.rpc.api import Client  # For interacting with Solana RPC                                                                                                                                
from solana.publickey import PublicKey  # For PublicKey functionality
from solana.rpc.api import Client

class Config:
    def __init__(self):
        self.api_key = 'your_api_key'
        self.api_secret = 'your_api_secret'
        self.llm_api_key = 'your_openai_api_key'
        self.llm_model = 'gpt-4'
        self.coin_list = ['SOL/USDT']
        self.interval = '5m'  # Timeframe for market data
        self.interval_seconds = 300  # Corresponds to '5m'
        self.base_trade_amount = 10  # Base amount for trades
        # Additional configurations
        self.stop_loss_percent = 2  # Stop-loss percentage
        self.take_profit_percent = 5  # Take-profit percentage
        self.position_check_interval = 60  # Seconds between position checks

class TradingAgent:
    def __init__(self, config, log_queue=None, chat_queue=None):
        self.config = config
        self.log_queue = log_queue
        self.chat_queue = chat_queue
        self.running = False
        self.next_action_time = time.time()
        self.last_operation = "Initialized"
        self.wallet = {}
        self.positions = {}
        self.scaler = StandardScaler()
        self.model = self.initialize_ml_model()
        self.initialize_wallet()
        self.client = Client("https://api.mainnet-beta.solana.com")
        openai.api_key = self.config.llm_api_key
        # Lock for thread safety
        self.lock = threading.Lock()

    def initialize_wallet(self):
        # Generate a Solana wallet or retrieve existing wallet data
        self.wallet['address'] = self.generate_wallet_address()
        self.wallet['pnl'] = 0.0
        self.wallet['balance'] = self.fetch_wallet_balances()

    def generate_wallet_address(self):
        # Generate a random public key for demonstration
        dummy_pubkey = PublicKey.random()
        return str(dummy_pubkey)

    def fetch_wallet_balances(self):
        # Fetch balances from Solana blockchain using the wallet address
        balances = {}
        try:
            address = self.wallet['address']
            # Fetch account balance
            response = self.client.get_balance(address)
            if 'result' in response and response['result']:
                balances['SOL'] = response['result']['value'] / 1e9  # Convert lamports to SOL
            else:
                balances['SOL'] = 0.0
            # For simplicity, simulate other token balances
            balances['USDT'] = random.uniform(500, 1000)
            balances['SRM'] = random.uniform(50, 200)
            balances['RAY'] = random.uniform(50, 200)
            balances['KIN'] = random.uniform(1000, 5000)
            balances['FIDA'] = random.uniform(50, 200)
        except Exception as e:
            if self.log_queue:
                self.log_queue.put(("error", f"Error fetching wallet balances: {str(e)}"))
            else:
                print(f"Error fetching wallet balances: {str(e)}")
        return balances

    def initialize_ml_model(self):
        # Initialize a pre-trained machine learning model (e.g., Random Forest)
        model = RandomForestRegressor(n_estimators=100, random_state=42)
        return model

    def get_market_data(self, coin_pair):
        self.log(f"Fetching market data for {coin_pair}...", "info")
        try:
            base_coin, quote_coin = coin_pair.split('/')
            days = '7'  # Fetch past 7 days data
            base_coin_id = self.get_coingecko_id(base_coin)
            params = {
                'vs_currency': quote_coin.lower(),
                'days': days,
                'interval': 'hourly'
            }
            url = f"https://api.coingecko.com/api/v3/coins/{base_coin_id}/market_chart"
            response = requests.get(url, params=params)
            data = response.json()
            if 'prices' in data:
                prices = data['prices']
                df = pd.DataFrame(prices, columns=['timestamp', 'price'])
                df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
                # Generate OHLCV data
                df.set_index('timestamp', inplace=True)
                df['open'] = df['price']
                df['high'] = df['price']
                df['low'] = df['price']
                df['close'] = df['price']
                df['volume'] = 0  # Volume data is not available
                df = df[['open', 'high', 'low', 'close', 'volume']]
                self.log(f"Market data for {coin_pair} fetched successfully.", "info")
                return df
            else:
                raise Exception("No price data found")
        except Exception as e:
            self.log(f"Failed to fetch market data for {coin_pair}: {e}", "error")
            return None

    def get_coingecko_id(self, coin_symbol):
        mapping = {
            'BTC': 'bitcoin',
            'ETH': 'ethereum',
            'SOL': 'solana',
            'USDT': 'tether',
            'USDC': 'usd-coin',
            'SRM': 'serum',
            'RAY': 'raydium',
            'KIN': 'kin',
            'FIDA': 'bonfida',
            # Add other coin mappings as needed
        }
        return mapping.get(coin_symbol.upper(), coin_symbol.lower())

    def preprocess_data(self, market_data):
        self.log("Preprocessing market data...", "info")
        try:
            # Feature engineering using technical indicators
            # Ensure market_data is not empty and has required columns
            if market_data.empty or not all(col in market_data.columns for col in ['open', 'high', 'low', 'close', 'volume']):
                self.log("Market data is empty or missing required columns", "error")
                return None, None
            market_data['EMA_20'] = ta.trend.EMAIndicator(close=market_data['close'], window=20).ema_indicator()
            market_data['SMA_50'] = ta.trend.SMAIndicator(close=market_data['close'], window=50).sma_indicator()
            market_data['ROC'] = ta.momentum.ROCIndicator(close=market_data['close'], window=12).roc()
            market_data['RSI'] = ta.momentum.RSIIndicator(close=market_data['close'], window=14).rsi()
            macd = ta.trend.MACD(close=market_data['close'])
            market_data['MACD'] = macd.macd()
            market_data['MACD_signal'] = macd.macd_signal()
            market_data['MACD_diff'] = macd.macd_diff()
            bollinger = ta.volatility.BollingerBands(close=market_data['close'], window=20)
            market_data['BB_high'] = bollinger.bollinger_hband()
            market_data['BB_low'] = bollinger.bollinger_lband()
            # Drop NaN values
            market_data.dropna(inplace=True)
            # Prepare features and target
            features = market_data[['EMA_20', 'SMA_50', 'ROC', 'RSI', 'MACD', 'MACD_signal', 'MACD_diff', 'BB_high', 'BB_low']]
            scaled_features = self.scaler.fit_transform(features)
            target = market_data['close'].values
            self.log("Market data preprocessed successfully.", "info")
            return scaled_features, target
        except Exception as e:
            self.log(f"Error in data preprocessing: {e}", "error")
            return None, None

    def train_ml_model(self, X_train, y_train):
        self.log("Training machine learning model...", "info")
        try:
            self.model.fit(X_train, y_train)
            self.log("Machine learning model trained.", "info")
        except Exception as e:
            self.log(f"Error training ML model: {e}", "error")

    def predict_price_movement(self, X_test):
        self.log("Predicting price movement...", "info")
        try:
            prediction = self.model.predict(X_test)
            self.log(f"Prediction complete: {prediction[0]}", "info")
            return prediction[0]
        except Exception as e:
            self.log(f"Error in prediction: {e}", "error")
            return None

    def analyze_market(self, market_data, coin_pair):
        if market_data is None or market_data.empty:
            self.log(f"No market data to analyze for {coin_pair}.", "error")
            return None

        self.log(f"Analyzing market for {coin_pair}...", "info")
        try:
            scaled_features, target = self.preprocess_data(market_data)
            if scaled_features is None or target is None or len(target) < 2:
                self.log(f"Not enough data to train model for {coin_pair}", "warning")
                return None
            X_train = scaled_features[:-1]
            y_train = target[:-1]
            X_test = scaled_features[-1].reshape(1, -1)
            current_price = target[-1]
            self.train_ml_model(X_train, y_train)
            predicted_price = self.predict_price_movement(X_test)
            if predicted_price is None:
                return None
            self.log(f"Predicted price for {coin_pair}: {predicted_price}, Current price: {current_price}", "info")

            # Dynamic position sizing using Kelly Criterion
            kelly_fraction = self.calculate_kelly_fraction(current_price, predicted_price)
            trade_amount = self.config.base_trade_amount * kelly_fraction
            trade_amount = max(trade_amount, 0)  # Ensure non-negative trade amount
            if trade_amount <= 0:
                action = 'hold'
            elif predicted_price > current_price:
                action = 'buy'
            else:
                action = 'sell'

            # Prepare prompt for LLM
            prompt = (
                f"As an expert crypto trading assistant, analyze the following market data and provide a trading decision.\n"
                f"Coin Pair: {coin_pair}\n"
                f"Current Price: {current_price}\n"
                f"Predicted Price: {predicted_price}\n"
                f"Kelly Criterion Fraction: {kelly_fraction}\n"
                f"Available Strategies: Aggressive Buy, Defensive Sell, Hold\n"
                f"Constraints:\n"
                f"- Only recommend 'buy' if predicted price is significantly higher than current price.\n"
                f"- Only recommend 'sell' if predicted price is significantly lower than current price.\n"
                f"- Consider the Kelly Criterion Fraction for position sizing.\n"
                f"Provide your decision in the following JSON format:\n"
                f"{{'action': 'buy'/'sell'/'hold', 'reason': 'Your reason here', 'confidence': 'High'/'Medium'/'Low'}}"
            )

            # Use LLM for decision refinement
            try:
                self.log("Sending prompt to LLM...", "info")
                # Build the message for OpenAI
                messages = [
                    {"role": "system", "content": "You are a Solana crypto trading expert with exceptional wisdom in profit making, trading indicators, and market trends."},
                    {"role": "user", "content": prompt},
                ]
                response = openai.ChatCompletion.create(
                    model=self.config.llm_model,
                    messages=messages,
                )
                llm_response = response['choices'][0]['message']['content']
                self.log(f"LLM Response for {coin_pair}: {llm_response}", "info")
                decision = self.parse_llm_response(llm_response, trade_amount)
                if decision is not None:
                    self.last_operation = f"Analyzed market for {coin_pair}, decision: {decision['action']}"
                    # Send decision to chat
                    self.send_chat_message(f"Decision for {coin_pair}: {decision}")
                return decision
            except Exception as e:
                self.log(f"LLM decision-making failed for {coin_pair}: {e}", "error")
                self.last_operation = f"LLM error for {coin_pair}"
                return None
        except Exception as e:
            self.log(f"Error in market analysis for {coin_pair}: {e}", "error")
            self.last_operation = f"Market analysis error for {coin_pair}"
            return None

    def calculate_kelly_fraction(self, current_price, predicted_price):
        self.log("Calculating Kelly Criterion fraction...", "info")
        try:
            expected_return = (predicted_price - current_price) / current_price
            historical_volatility = np.std([current_price, predicted_price])
            if historical_volatility == 0:
                return 0
            fraction = expected_return / (historical_volatility ** 2)
            fraction = min(max(fraction, 0), 1)  # Ensure fraction is between 0 and 1
            self.log(f"Calculated Kelly fraction: {fraction}", "info")
            return fraction
        except Exception as e:
            self.log(f"Error in Kelly Criterion calculation: {e}", "error")
            return 0

    def parse_llm_response(self, response_text, trade_amount):
        self.log("Parsing LLM response...", "info")
        try:
            # Use regex to extract JSON from the response if needed
            import re
            match = re.search(r'\{.*\}', response_text.replace("'", '"'), re.DOTALL)
            if match:
                response_json = json.loads(match.group(0))
            else:
                response_json = json.loads(response_text.strip().replace("'", '"'))

            action = response_json.get('action', 'hold')
            reason = response_json.get('reason', '')
            confidence = response_json.get('confidence', 'Medium')
            decision = {
                'action': action.lower(),
                'reason': reason,
                'confidence': confidence,
                'amount': trade_amount
            }
            self.log(f"Decision parsed: {decision}", "info")
            return decision
        except json.JSONDecodeError as e:
            self.log(f"Failed to parse LLM response: {e}", "error")
            return None

    def execute_trade(self, decision, coin_pair):
        self.log(f"Executing trade for {coin_pair}...", "info")
        if decision:
            try:
                symbol = coin_pair
                amount = decision['amount']
                if decision['action'] == 'buy':
                    # Simulate buy order
                    buy_price = self.get_current_price(coin_pair)
                    if buy_price is None:
                        self.log(f"Failed to get current price for {coin_pair}", "error")
                        return
                    self.positions[symbol] = {
                        'amount': amount,
                        'entry_price': buy_price,
                        'stop_loss_price': buy_price * (1 - self.config.stop_loss_percent / 100),
                        'take_profit_price': buy_price * (1 + self.config.take_profit_percent / 100)
                    }
                    # Update wallet balance
                    self.update_wallet_balance(symbol, amount, buy_price, decision['action'])
                    self.log(f"Executed BUY order for {symbol}, amount: {amount} at price {buy_price}", "info")
                    self.last_operation = f"Executed BUY order for {symbol}"
                    self.send_chat_message(f"Executed BUY order for {symbol}, amount: {amount} at price {buy_price}")
                elif decision['action'] == 'sell' and symbol in self.positions:
                    # Simulate sell order
                    sell_price = self.get_current_price(coin_pair)
                    if sell_price is None:
                        self.log(f"Failed to get current price for {coin_pair}", "error")
                        return
                    position = self.positions[symbol]
                    amount = position['amount']
                    pnl = (sell_price - position['entry_price']) * amount
                    self.wallet['pnl'] += pnl
                    # Remove position
                    del self.positions[symbol]
                    # Update wallet balance
                    self.update_wallet_balance(symbol, amount, sell_price, decision['action'])
                    self.log(f"Executed SELL order for {symbol}, amount: {amount} at price {sell_price}, PnL: {pnl}", "info")
                    self.last_operation = f"Executed SELL order for {symbol}"
                    self.send_chat_message(f"Executed SELL order for {symbol}, amount: {amount} at price {sell_price}, PnL: {pnl}")
                else:
                    self.log(f"No action taken for {symbol}", "info")
                    self.last_operation = f"No action for {symbol}"
            except Exception as e:
                self.log(f"Trade execution failed for {symbol}: {e}", "error")
                self.last_operation = f"Trade execution failed for {symbol}"

    def get_current_price(self, coin_pair):
        try:
            base_coin, quote_coin = coin_pair.split('/')
            base_coin_id = self.get_coingecko_id(base_coin)
            url = f"https://api.coingecko.com/api/v3/simple/price"
            params = {
                'ids': base_coin_id,
                'vs_currencies': quote_coin.lower()
            }
            response = requests.get(url, params=params)
            data = response.json()
            if base_coin_id in data and quote_coin.lower() in data[base_coin_id]:
                price = data[base_coin_id][quote_coin.lower()]
                return price
            else:
                raise Exception("Price data not available")
        except Exception as e:
            self.log(f"Error fetching current price for {coin_pair}: {e}", "error")
            return None

    def update_wallet_balance(self, symbol, amount, price, action):
        base_coin, quote_coin = symbol.split('/')
        if action == 'buy':
            # Deduct quote_currency
            cost = amount * price
            self.wallet['balance'][quote_coin] = self.wallet['balance'].get(quote_coin, 0) - cost
            # Add base_currency
            self.wallet['balance'][base_coin] = self.wallet['balance'].get(base_coin, 0) + amount
        elif action == 'sell':
            # Add quote_currency
            revenue = amount * price
            self.wallet['balance'][quote_coin] = self.wallet['balance'].get(quote_coin, 0) + revenue
            # Deduct base_currency
            self.wallet['balance'][base_coin] = self.wallet['balance'].get(base_coin, 0) - amount
        self.log(f"Updated wallet balance after {action} {amount} {base_coin}", "info")

    def check_positions(self):
        # Implement stop loss and take profit logic
        current_prices = {}
        for coin_pair in self.positions.keys():
            current_prices[coin_pair] = self.get_current_price(coin_pair)

        for symbol, position in list(self.positions.items()):
            current_price = current_prices.get(symbol)
            if current_price is None:
                continue
            if current_price <= position['stop_loss_price']:
                # Stop loss triggered
                amount = position['amount']
                pnl = (current_price - position['entry_price']) * amount
                self.wallet['pnl'] += pnl
                self.update_wallet_balance(symbol, amount, current_price, 'sell')
                self.send_chat_message(f"Stop loss triggered for {symbol}. Sold {amount} at {current_price}")
                self.log(f"Stop loss triggered for {symbol}. Sold {amount} at {current_price}", "info")
                del self.positions[symbol]
            elif current_price >= position['take_profit_price']:
                # Take profit triggered
                amount = position['amount']
                pnl = (current_price - position['entry_price']) * amount
                self.wallet['pnl'] += pnl
                self.update_wallet_balance(symbol, amount, current_price, 'sell')
                self.send_chat_message(f"Take profit triggered for {symbol}. Sold {amount} at {current_price}")
                self.log(f"Take profit triggered for {symbol}. Sold {amount} at {current_price}", "info")
                del self.positions[symbol]

    def run(self):
        self.running = True
        while self.running:
            try:
                current_time = time.time()
                if current_time >= self.next_action_time:
                    self.last_operation = 'Fetching market data...'
                    self.log("Agent fetching market data...", "info")
                    for coin_pair in self.config.coin_list:
                        market_data = self.get_market_data(coin_pair)
                        if market_data is not None:
                            self.last_operation = f'Analyzing market data for {coin_pair}...'
                            self.log(f"Analyzing market data for {coin_pair}...", "info")
                            decision = self.analyze_market(market_data, coin_pair)
                            if decision:
                                self.last_operation = f'Placing order for {coin_pair}...'
                                self.log(f"Placing order for {coin_pair}...", "info")
                                self.execute_trade(decision, coin_pair)
                            else:
                                self.last_operation = f'No action for {coin_pair}'
                                self.log(f"No action for {coin_pair}", "info")
                        else:
                            self.last_operation = f'Market data fetch failed for {coin_pair}'
                            self.log(f"Market data fetch failed for {coin_pair}", "error")
                    self.next_action_time = current_time + self.config.interval_seconds
                else:
                    self.check_positions()
                    time.sleep(1)
            except Exception as e:
                with self.lock:
                    self.last_operation = f"Error: {str(e)}"
                    self.log(f"Agent encountered an error: {str(e)}", "error")
                    # Sleep before retrying
                    time.sleep(5)
                    continue  # Keep the agent running even after errors

    def receive_command(self, command):
        # Process the command sent to the agent
        if command.lower() == 'what is your status':
            status_info = f"Status: {'Running' if self.running else 'Idle'}\n"
            status_info += f"Wallet Address: {self.wallet.get('address', 'N/A')}\n"
            status_info += f"PnL: {self.wallet.get('pnl', 0.0):.2f}\n"
            status_info += f"Coins: {', '.join(self.config.coin_list)}\n"
            status_info += f"Positions: {self.positions}\n"
            status_info += f"Last Operation: {self.last_operation}"
            self.chat_queue.put(("agent", status_info))
        else:
            self.chat_queue.put(("agent", f"Agent received: {command}"))

    def start(self):
        self.thread = threading.Thread(target=self.run, daemon=True)
        self.thread.start()
        self.running = True

    def stop(self):
        self.running = False
        if self.thread.is_alive():
            self.thread.join()

    def log(self, message, level='info'):
        # Send to log_queue if available
        timestamp = time.strftime("[%H:%M:%S]")
        full_message = f"{timestamp} {message}"
        if self.log_queue:
            self.log_queue.put((level, full_message))
        else:
            print(full_message)

    def send_chat_message(self, message):
        if self.chat_queue:
            self.chat_queue.put(("agent", message))
        else:
            print(f"Agent Message: {message}")