HOW TO GET FEE FROM BINANCE ADN BITSTAMP DYNAMICALLY import websockets
import datetime
import pandas as pd
import json
import asyncio
import aiohttp
import pulp

# Constants
BINANCE_WS_URL = "wss://stream.binance.com:9443/ws"
BITSTAMP_API_URL = "https://www.bitstamp.net/api/v2/ticker/"
CRYPTO_PAIRS = ["BTCUSDT"]  # Binance uses uppercase pairs with 'USDT'
BITSTAMP_PAIRS = ["btcusd"]  # Bitstamp uses lowercase
KLINE_INTERVAL = "1m"  # Binance supports intervals like '1m', '5m', etc.
BITSTAMP_FETCH_INTERVAL = 5  # in seconds

class DataCollector:
    def __init__(self, binance_pairs, binance_interval, bitstamp_pairs, bitstamp_interval):
        self.binance_pairs = [pair.lower() for pair in binance_pairs]
        self.binance_interval = binance_interval
        self.bitstamp_pairs = bitstamp_pairs
        self.bitstamp_interval = bitstamp_interval

        self.columns = ["timestamp", "pair", "exchange", "open", "high", "low", "close", "volume"]
        self.data = pd.DataFrame(columns=self.columns)

        # Fees (example values; adjust according to actual fees)
        self.fee_binance = 0.001    # 0.1%
        self.fee_bitstamp = 0.0025  # 0.25%

    async def subscribe_binance(self, ws, pair):
        subscribe_message = {
            "method": "SUBSCRIBE",
            "params": [
                f"{pair}@kline_{self.binance_interval}"
            ],
            "id": 1
        }
        print(f"Subscribing to Binance: {pair}@kline_{self.binance_interval}")
        await ws.send(json.dumps(subscribe_message))

    async def collect_binance(self):
        async with websockets.connect(BINANCE_WS_URL) as ws:
            # Subscribe to all Binance pairs
            for pair in self.binance_pairs:
                await self.subscribe_binance(ws, pair)
                print(f"Subscribed to Binance {pair}")

            while True:
                try:
                    message = await ws.recv()
                    data = json.loads(message)
                    # print("Binance message received:", data)
                    if 'k' in data:
                        kline = data['k']
                        if kline['x']:  # Kline closed
                            timestamp = datetime.datetime.fromtimestamp(kline['t'] / 1000)
                            pair = data['s']
                            open_price = float(kline['o'])
                            high = float(kline['h'])
                            low = float(kline['l'])
                            close = float(kline['c'])
                            volume = float(kline['v'])

                            new_row = {
                                "timestamp": timestamp,
                                "pair": pair,
                                "exchange": "binance",
                                "open": open_price,
                                "high": high,
                                "low": low,
                                "close": close,
                                "volume": volume
                            }

                            # Replace append with concat
                            new_row_df = pd.DataFrame([new_row])
                            self.data = pd.concat([self.data, new_row_df], ignore_index=True)
                            print(f"Collected Binance Data: {new_row}")

                except Exception as e:
                    print(f"Binance WebSocket error: {e}")
                    await asyncio.sleep(1)  # Wait before reconnecting

    async def fetch_bitstamp_ticker(self, session, pair):
        url = BITSTAMP_API_URL + pair
        try:
            async with session.get(url) as response:
                if response.status == 200:
                    ticker = await response.json()
                    timestamp = datetime.datetime.fromtimestamp(int(ticker['timestamp']))
                    new_row = {
                        "timestamp": timestamp,
                        "pair": pair.upper(),
                        "exchange": "bitstamp",
                        "open": float(ticker['open']),
                        "high": float(ticker['high']),
                        "low": float(ticker['low']),
                        "close": float(ticker['last']),
                        "volume": float(ticker['volume'])
                    }
                    # Replace append with concat
                    new_row_df = pd.DataFrame([new_row])
                    self.data = pd.concat([self.data, new_row_df], ignore_index=True)
                    print(f"Collected Bitstamp Data: {new_row}")
                else:
                    print(f"Failed to fetch Bitstamp ticker for {pair.upper()}")
        except Exception as e:
            print(f"Error fetching Bitstamp ticker for {pair.upper()}: {e}")

    async def collect_bitstamp(self):
        while True:
            async with aiohttp.ClientSession() as session:
                tasks = [self.fetch_bitstamp_ticker(session, pair) for pair in self.bitstamp_pairs]
                await asyncio.gather(*tasks)
            await asyncio.sleep(self.bitstamp_interval)

    def detect_arbitrage(self):
        """
        Detects arbitrage opportunities between Binance and Bitstamp.
        Buys on Binance (lower price) and sells on Bitstamp (higher price).
        Considers trading fees.
        """
        # Fetch the latest data
        latest_binance = self.data[self.data['exchange'] == 'binance'].tail(1)
        latest_bitstamp = self.data[self.data['exchange'] == 'bitstamp'].tail(1)

        if latest_binance.empty or latest_bitstamp.empty:
            print("Insufficient data for arbitrage detection.")
            return

        # Extract prices
        buy_price = latest_binance['close'].values[0] * (1 + self.fee_binance)  # Effective buy price on Binance
        sell_price = latest_bitstamp['close'].values[0] * (1 - self.fee_bitstamp)  # Effective sell price on Bitstamp

        print(f"Effective Buy Price (Binance): ${buy_price:.2f}")
        print(f"Effective Sell Price (Bitstamp): ${sell_price:.2f}")

        # Check if arbitrage is possible
        if sell_price > buy_price:
            # Set up Linear Programming problem
            profit_model = pulp.LpProblem("Arbitrage_Profit", pulp.LpMaximize)

            # Variable: Quantity to trade
            quantity = pulp.LpVariable('Quantity', lowBound=0, cat='Continuous')

            # Objective: Maximize profit
            profit = (sell_price - buy_price) * quantity
            profit_model += profit, "Total_Profit"

            # Constraints:
            # Assume there's a maximum quantity you can trade based on the available volumes
            max_quantity_binance = float(latest_binance['volume'].values[0])
            max_quantity_bitstamp = float(latest_bitstamp['volume'].values[0])
            profit_model += quantity <= max_quantity_binance, "Max_Binance_Volume"
            profit_model += quantity <= max_quantity_bitstamp, "Max_Bitstamp_Volume"

            # Solve the problem
            profit_model.solve()

            if pulp.LpStatus[profit_model.status] == 'Optimal' and pulp.value(profit_model.objective) > 0:
                optimal_quantity = quantity.varValue
                total_profit = pulp.value(profit_model.objective)
                print("Arbitrage Opportunity Detected!")
                print(f"Buy {optimal_quantity:.6f} BTC on Binance at ${buy_price:.2f}")
                print(f"Sell {optimal_quantity:.6f} BTC on Bitstamp at ${sell_price:.2f}")
                print(f"Expected Profit: ${total_profit:.2f}")
            else:
                print("No profitable arbitrage opportunity at the moment.")
        else:
            print("No arbitrage opportunity: Sell price <= Buy price.")

    async def collect_all(self):
        # Run collectors and perform arbitrage detection periodically
        collector_binance = self.collect_binance()
        collector_bitstamp = self.collect_bitstamp()

        # Run collectors concurrently
        collectors = asyncio.gather(collector_binance, collector_bitstamp)

        # Additionally, run arbitrage detection at regular intervals
        async def arbitrage_loop():
            while True:
                await asyncio.sleep(5)  # Check every 5 seconds
                self.detect_arbitrage()

        arbitrage = asyncio.create_task(arbitrage_loop())

        await collectors

# Initialize DataCollector with fees
data_collector = DataCollector(
    binance_pairs=CRYPTO_PAIRS,
    binance_interval=KLINE_INTERVAL,
    bitstamp_pairs=BITSTAMP_PAIRS,
    bitstamp_interval=BITSTAMP_FETCH_INTERVAL
)

# Run the data collector and arbitrage detector
asyncio.run(data_collector.collect_all())