# paper_trader.py

import asyncio
import logging
import json
import websockets
import base64
import time
import torch
from pathlib import Path
from typing import Dict, Set
from dataclasses import dataclass

@dataclass
class PaperPosition:
   pool_address: str
   token_mint: str
   entry_price: float
   size: float 
   entry_time: float
   take_profit: float
   stop_loss: float
   attempts: int = 0
   max_attempts: int = 3

class ParallelPaperTrader:
   def __init__(self, config_path: str):
       self.config = self._load_config(config_path)
       self.setup_gpu()
       
       self.RAYDIUM_V4 = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"
       self.WSOL_MINT = "So11111111111111111111111111111111111111112"
       
       self.paper_balance = 0.5
       self.position_size = 0.015
       self.max_positions = 5
       self.take_profit = 0.065  
       self.stop_loss = 0.0325
       self.timeout = 60
       
       self.min_volume = 5000    
       self.max_impact = 0.0005
       
       self.positions: Dict[str, PaperPosition] = {}
       self._active_tokens: Set[str] = set()
       self._position_lock = asyncio.Lock()
       
       self.total_profit = 0.0
       self.wins = 0 
       self.total_trades = 0

       self.setup_tensors()
       logger.debug("Initializing paper trader...")
       logger.info(f"Paper trader initialized with {self.paper_balance} SOL")

   def _load_config(self, path: str) -> dict:
       with open(Path(path)) as f:
           return json.load(f)

   def setup_gpu(self):
       self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
       if torch.cuda.is_available():
           torch.backends.cudnn.benchmark = True
           logger.info(f"Using GPU: {torch.cuda.get_device_name()}")

   def setup_tensors(self):
       if torch.cuda.is_available():
           self.price_tensor = torch.zeros(1000, device=self.device, dtype=torch.float32)
           self.volume_tensor = torch.zeros(1000, device=self.device, dtype=torch.float32)

   async def analyze_market(self, pool_data: Dict) -> Dict:
       try:
           with torch.cuda.stream(torch.cuda.Stream()):
               self.price_tensor = torch.roll(self.price_tensor, -1)
               self.price_tensor[-1] = pool_data['price']
               
               last_5min = self.price_tensor[-300:]
               price_changes = last_5min[1:] - last_5min[:-1]
               
               buy_pressure = torch.sum(price_changes[price_changes > 0])
               sell_pressure = torch.abs(torch.sum(price_changes[price_changes < 0]))
               
               return {
                   'buy_pressure': float(buy_pressure),
                   'sell_pressure': float(sell_pressure),
                   'pressure_ratio': float(buy_pressure / max(sell_pressure, 1e-8))
               }
       except Exception as e:
           logger.error(f"Market analysis error: {e}")
           return {}

    def decode_pool_data(self, log: str) -> Dict:
        try:
           logger.debug(f"Full log: {log}")  # Log the complete message
           
           if "Program log: " in log:
               program_log = log.split("Program log: ")[1]
               logger.debug(f"Program log: {program_log}")
               
               if "ray_log: " in program_log:
                   ray_log = program_log.split("ray_log: ")[1].strip()
                   logger.debug(f"Ray log: {ray_log}")
                   # Rest of decode logic...
               else:
                   logger.debug("No ray_log found in program log")
           else:
               logger.debug("No Program log found")
               
           return None

       except Exception as e:
           logger.error(f"Decode error: {e}")
           return None   def should_enter_trade(self, pool_data: Dict, market_analysis: Dict) -> bool:
       try:
           if len(self.positions) >= self.max_positions:
               return False
               
           if pool_data['liquidity'] < self.min_volume:
               return False
               
           impact = (self.position_size * 1e9) / pool_data['base_reserve']
           if impact > self.max_impact:
               return False

           pressure_ratio = market_analysis.get('pressure_ratio', 0)
           if pressure_ratio < 1.5:
               return False
               
           return True

       except Exception as e:
           logger.error(f"Entry validation error: {e}")
           return False

   async def enter_position(self, pool_address: str, pool_data: Dict) -> bool:
       try:
           if not self.should_enter_trade(pool_data, await self.analyze_market(pool_data)):
               return False
               
           price = pool_data['price']
           position = PaperPosition(
               pool_address=pool_address,
               token_mint=pool_data.get('token_mint', pool_address),
               entry_price=price,
               size=self.position_size,
               entry_time=time.time(),
               take_profit=price * (1 + self.take_profit),
               stop_loss=price * (1 - self.stop_loss)
           )
           
           self.positions[pool_address] = position
           self.paper_balance -= self.position_size
           
           logger.info(f"""
           🚀 Entered Paper Position:
           Pool: {pool_address[:8]}
           Price: ${price:.4f}
           Size: {self.position_size} SOL
           Take Profit: ${position.take_profit:.4f}
           Stop Loss: ${position.stop_loss:.4f}
           Balance: {self.paper_balance:.4f} SOL
           """)
           
           return True

       except Exception as e:
           logger.error(f"Entry error: {e}")
           return False

   async def exit_position(self, pool_address: str, current_price: float, reason: str):
       try:
           position = self.positions[pool_address]
           pnl = (current_price - position.entry_price) * position.size
           
           self.paper_balance += position.size + pnl
           self.total_profit += pnl
           self.total_trades += 1
           
           if pnl > 0:
               self.wins += 1
           
           logger.info(f"""
           ✨ Exited Position ({reason}):
           Pool: {pool_address[:8]}
           Exit Price: ${current_price:.4f}
           PnL: {pnl:.6f} SOL (${pnl*110:.2f})
           Duration: {time.time() - position.entry_time:.1f}s
           Win Rate: {(self.wins/self.total_trades*100):.1f}%
           Balance: {self.paper_balance:.4f} SOL
           """)
           
           del self.positions[pool_address]

       except Exception as e:
           logger.error(f"Exit error: {e}")

   async def process_pool_update(self, pool_address: str, pool_data: Dict):
       try:
           async with self._position_lock:
               current_price = pool_data['price']
               market_analysis = await self.analyze_market(pool_data)
               
               if pool_address in self.positions:
                   pos = self.positions[pool_address]
                   
                   should_exit = any([
                       current_price >= pos.take_profit,
                       current_price <= pos.stop_loss,
                       time.time() - pos.entry_time > self.timeout,
                       market_analysis.get('pressure_ratio', 0) < 0.7
                   ])
                   
                   if should_exit:
                       await self.exit_position(pool_address, current_price,
                           "take_profit" if current_price >= pos.take_profit else
                           "stop_loss" if current_price <= pos.stop_loss else
                           "timeout" if time.time() - pos.entry_time > self.timeout else
                           "momentum_shift"
                       )
                       
               elif await self.enter_position(pool_address, pool_data):
                   logger.debug("New position entered")

       except Exception as e:
           logger.error(f"Process error: {e}")

   async def start(self):
       ws_url = self.config['rpc']['helius']['endpoint'].replace('https', 'wss')
       
       logger.info(f"""
       🚀 Starting Parallel Paper Trader:
       Initial Balance: {self.paper_balance} SOL
       Position Size: {self.position_size} SOL
       Take Profit: {self.take_profit*100}%
       Stop Loss: {self.stop_loss*100}%
       Max Positions: {self.max_positions}
       Timeout: {self.timeout}s
       GPU: {torch.cuda.get_device_name() if torch.cuda.is_available() else "CPU"}
       """)
       
       while True:
           try:
               async with websockets.connect(ws_url) as ws:
                   subscribe_msg = {
                       "jsonrpc": "2.0",
                       "id": 1,
                       "method": "logsSubscribe",
                       "params": [
                           {"mentions": [self.RAYDIUM_V4]},
                           {"commitment": "processed"}
                       ]
                   }
                   
                   await ws.send(json.dumps(subscribe_msg))
                   await ws.recv()  # Handle subscription confirmation
                   logger.info("Connected to Raydium mempool")
                   
                   while True:
                       msg = await ws.recv()
                       data = json.loads(msg)
                       
                       if "params" in data and "result" in data["params"]:
                           value = data["params"]["result"]["value"]
                           if "logs" in value:
                               for log in value["logs"]:
                                   if "Program log: " in log and self.RAYDIUM_V4 in log:
                                       pool_data = self.decode_pool_data(log)
                                       if pool_data and self.WSOL_MINT in log:
                                           await self.process_pool_update(self.WSOL_MINT, pool_data)

           except websockets.exceptions.ConnectionClosed:
               logger.warning("WebSocket closed - reconnecting...")
               await asyncio.sleep(1)
           except Exception as e:
               logger.error(f"Connection error: {str(e)}")
               await asyncio.sleep(1)
if __name__ == "__main__":
   logging.basicConfig(
       level=logging.DEBUG,
       format='%(asctime)s | %(levelname)s | %(message)s',
       handlers=[
           logging.FileHandler("paper_trading.log", encoding='utf-8'),
           logging.StreamHandler()
       ]
   )
   logger = logging.getLogger(__name__)

   try:
       trader = ParallelPaperTrader("config.json")
       asyncio.run(trader.start())
   except KeyboardInterrupt:
       logger.info("Paper trading stopped")
   except Exception as e:
       logger.error(f"Fatal error: {e}")
       raise can you please please please fix the spacing in this and also debug logging everywhere