| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- import asyncio
- import requests
- from datetime import datetime
- from telegram import Bot
- import platform
- # Telegram bot configuration (replace with your own bot token and chat ID)
- TELEGRAM_BOT_TOKEN = "YourBotToken" # Replace with your Telegram bot token
- TELEGRAM_CHAT_ID = "YourID" # Replace with your Telegram chat ID
- # API endpoints
- NOBITEX_API = "https://apiv2.nobitex.ir/v3/orderbook/USDTIRT"
- BITPIN_API = "https://api.bitpin.ir/v4/mth/matches/USDT_IRT/"
- # Minimum volume for Nobitex sell orders (in USDT)
- MIN_SELL_VOLUME = 500.0
- MIN_PROFIT_IRR = 5000.0
- def log_error(message):
- """Log errors with timestamp."""
- print(f"Error at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}: {message}")
- async def send_telegram_message(message):
- """Send Telegram message with error handling."""
- try:
- bot = Bot(token=TELEGRAM_BOT_TOKEN)
- await bot.send_message(chat_id=TELEGRAM_CHAT_ID, text=message)
- print("Sent message:", message)
- except Exception as e:
- log_error(f"Failed to send Telegram message: {e}")
- def fetch_nobitex_data():
- """Fetch Nobitex orderbook data with error handling."""
- try:
- response = requests.get(NOBITEX_API, timeout=10)
- response.raise_for_status()
- data = response.json()
- if data.get("status") == "ok":
- print("Nobitex API read successfully at", response.headers.get('Date', datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
- return data.get("asks", []), data.get("bids", [])
- else:
- log_error(f"Nobitex API error: {data.get('status')}")
- return [], []
- except (requests.RequestException, ValueError) as e:
- log_error(f"Error fetching Nobitex data: {e}")
- return [], []
- def fetch_bitpin_data():
- """Fetch Bitpin trade data with error handling."""
- try:
- response = requests.get(BITPIN_API, timeout=10)
- response.raise_for_status()
- data = response.json()
- print("Bitpin API read successfully at", response.headers.get('Date', datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
- return data
- except (requests.RequestException, ValueError) as e:
- log_error(f"Error fetching Bitpin data: {e}")
- return []
- def calculate_weighted_average(orders, target_volume, start_index=0):
- """Calculate weighted average price for orders starting from start_index until target_volume is reached."""
- try:
- total_volume = 0.0
- weighted_price_sum = 0.0
- used_orders = []
- for i in range(start_index, len(orders)):
- price, volume = orders[i]
- volume_to_use = min(volume, target_volume - total_volume)
- weighted_price_sum += price * volume_to_use
- total_volume += volume_to_use
- used_orders.append((price, volume_to_use))
- if total_volume >= target_volume:
- break
- if total_volume < target_volume:
- print(f"Debug: Insufficient volume, got {total_volume:.2f}, needed {target_volume:.2f}")
- return None, 0.0
- avg_price = weighted_price_sum / total_volume
- print(f"Debug: Aggregated {len(used_orders)} orders: {[(p, v) for p, v in used_orders]}, Total Volume: {total_volume:.2f}, Avg Price: {avg_price:.1f}")
- return avg_price, total_volume
- except (ZeroDivisionError, TypeError) as e:
- log_error(f"Error in calculate_weighted_average: {e}")
- return None, 0.0
- async def check_arbitrage():
- """Check for arbitrage opportunities with error handling."""
- try:
- # Fetch data from both APIs
- nobitex_asks, nobitex_bids = fetch_nobitex_data()
- bitpin_trades = fetch_bitpin_data()
- if not nobitex_asks or not nobitex_bids or not bitpin_trades:
- print("No data to compare")
- return
- # Convert Bitpin prices to IRR (toman to rial) and sort by price
- bitpin_buys = []
- bitpin_sells = []
- try:
- for trade in bitpin_trades:
- price = float(trade["price"]) * 10
- volume = float(trade["base_amount"])
- if trade["side"] == "buy":
- bitpin_buys.append((price, volume))
- elif trade["side"] == "sell":
- bitpin_sells.append((price, volume))
- bitpin_buys = sorted(bitpin_buys, key=lambda x: x[0], reverse=True)
- bitpin_sells = sorted(bitpin_sells, key=lambda x: x[0])
- except (KeyError, ValueError, TypeError) as e:
- log_error(f"Error parsing Bitpin trades: {e}")
- return
- # Sort Nobitex asks by price ascending and bids by price descending
- try:
- nobitex_asks = sorted(
- [(float(ask[0]), float(ask[1])) for ask in nobitex_asks],
- key=lambda x: x[0]
- )
- nobitex_bids = sorted(
- [(float(bid[0]), float(bid[1])) for bid in nobitex_bids],
- key=lambda x: x[0], reverse=True
- )
- except (ValueError, TypeError) as e:
- log_error(f"Error parsing Nobitex asks/bids: {e}")
- return
- # Track best arbitrage opportunity
- best_ask_sell_diff = float('-inf')
- best_bid_buy_diff = float('-inf')
- best_ask_sell_details = None
- best_bid_buy_details = None
- # Process first valid Nobitex ask
- if nobitex_asks:
- try:
- ask_price, ask_volume = nobitex_asks[0]
- orig_ask_price, orig_ask_volume = ask_price, ask_volume
- print(f"Debug: First Nobitex ask - Price: {ask_price:.1f}, Volume: {ask_volume:.2f}")
- # Enforce minimum sell volume
- if ask_volume < MIN_SELL_VOLUME:
- print(f"Debug: Aggregating Nobitex asks to reach {MIN_SELL_VOLUME} USDT")
- avg_price, total_volume = calculate_weighted_average(nobitex_asks, MIN_SELL_VOLUME)
- if avg_price is None or total_volume < MIN_SELL_VOLUME:
- print(f"Debug: Failed to aggregate Nobitex asks to {MIN_SELL_VOLUME} USDT")
- else:
- ask_price, ask_volume = avg_price, total_volume
- print(f"Debug: Aggregated Nobitex ask - Price: {ask_price:.1f}, Volume: {ask_volume:.2f}")
- # Compare with Bitpin sells
- for i, (sell_price, sell_volume) in enumerate(bitpin_sells):
- try:
- print(f"Debug: Comparing with Bitpin sell - Price: {sell_price:.1f}, Volume: {sell_volume:.2f}")
- volume_diff_ratio = abs(ask_volume - sell_volume) / max(ask_volume, sell_volume)
- if volume_diff_ratio > 0.05:
- print(f"Debug: Volume difference > 5% ({volume_diff_ratio:.2%}), aggregating Bitpin sells")
- avg_sell_price, total_sell_volume = calculate_weighted_average(bitpin_sells, ask_volume, i)
- if avg_sell_price is None or total_sell_volume < MIN_SELL_VOLUME:
- print(f"Debug: Failed to aggregate Bitpin sells to {ask_volume:.2f} USDT")
- continue
- price_diff = avg_sell_price - ask_price
- compare_volume = total_sell_volume
- compare_price = avg_sell_price
- print(f"Debug: Bitpin sell aggregated - Price: {avg_sell_price:.1f}, Volume: {total_sell_volume:.2f}")
- else:
- price_diff = sell_price - ask_price
- compare_volume = sell_volume
- compare_price = sell_price
- print(f"Debug: Using single Bitpin sell - Price: {sell_price:.1f}, Volume: {sell_volume:.2f}")
- if price_diff > best_ask_sell_diff:
- best_ask_sell_diff = price_diff
- best_ask_sell_details = (ask_price, ask_volume, compare_price, compare_volume, orig_ask_price, orig_ask_volume, sell_price, sell_volume)
- print(f"Debug: New best ask-sell diff: {price_diff:.1f}")
- except (ZeroDivisionError, TypeError) as e:
- log_error(f"Error comparing Bitpin sell: {e}")
- continue
- else:
- # Use first ask directly if volume is sufficient
- print(f"Debug: First Nobitex ask volume sufficient, comparing with Bitpin sells")
- for i, (sell_price, sell_volume) in enumerate(bitpin_sells):
- try:
- print(f"Debug: Comparing with Bitpin sell - Price: {sell_price:.1f}, Volume: {sell_volume:.2f}")
- volume_diff_ratio = abs(ask_volume - sell_volume) / max(ask_volume, sell_volume)
- if volume_diff_ratio > 0.05:
- print(f"Debug: Volume difference > 5% ({volume_diff_ratio:.2%}), aggregating Bitpin sells")
- avg_sell_price, total_sell_volume = calculate_weighted_average(bitpin_sells, ask_volume, i)
- if avg_sell_price is None or total_sell_volume < MIN_SELL_VOLUME:
- print(f"Debug: Failed to aggregate Bitpin sells to {ask_volume:.2f} USDT")
- continue
- price_diff = avg_sell_price - ask_price
- compare_volume = total_sell_volume
- compare_price = avg_sell_price
- print(f"Debug: Bitpin sell aggregated - Price: {avg_sell_price:.1f}, Volume: {total_sell_volume:.2f}")
- else:
- price_diff = sell_price - ask_price
- compare_volume = sell_volume
- compare_price = sell_price
- print(f"Debug: Using single Bitpin sell - Price: {sell_price:.1f}, Volume: {sell_volume:.2f}")
- if price_diff > best_ask_sell_diff:
- best_ask_sell_diff = price_diff
- best_ask_sell_details = (ask_price, ask_volume, compare_price, compare_volume, orig_ask_price, orig_ask_volume, sell_price, sell_volume)
- print(f"Debug: New best ask-sell diff: {price_diff:.1f}")
- except (ZeroDivisionError, TypeError) as e:
- log_error(f"Error comparing Bitpin sell: {e}")
- continue
- except (IndexError, TypeError) as e:
- log_error(f"Error processing Nobitex ask: {e}")
- # Process first valid Nobitex bid
- if nobitex_bids:
- try:
- bid_price, bid_volume = nobitex_bids[0]
- orig_bid_price, orig_bid_volume = bid_price, bid_volume
- print(f"Debug: First Nobitex bid - Price: {bid_price:.1f}, Volume: {bid_volume:.2f}")
- for i, (buy_price, buy_volume) in enumerate(bitpin_buys):
- try:
- print(f"Debug: Comparing with Bitpin buy - Price: {buy_price:.1f}, Volume: {buy_volume:.2f}")
- volume_diff_ratio = abs(bid_volume - buy_volume) / max(bid_volume, buy_volume)
- if volume_diff_ratio > 0.05:
- print(f"Debug: Volume difference > 5% ({volume_diff_ratio:.2%}), aggregating Bitpin buys")
- avg_buy_price, total_buy_volume = calculate_weighted_average(bitpin_buys, bid_volume, i)
- if avg_buy_price is None:
- print(f"Debug: Failed to aggregate Bitpin buys to {bid_volume:.2f} USDT")
- continue
- price_diff = bid_price - avg_buy_price
- compare_volume = total_buy_volume
- compare_price = avg_buy_price
- print(f"Debug: Bitpin buy aggregated - Price: {avg_buy_price:.1f}, Volume: {total_buy_volume:.2f}")
- else:
- price_diff = bid_price - buy_price
- compare_volume = buy_volume
- compare_price = buy_price
- print(f"Debug: Using single Bitpin buy - Price: {buy_price:.1f}, Volume: {buy_volume:.2f}")
- if price_diff > best_bid_buy_diff:
- best_bid_buy_diff = price_diff
- best_bid_buy_details = (bid_price, bid_volume, compare_price, compare_volume, orig_bid_price, orig_bid_volume, buy_price, buy_volume)
- print(f"Debug: New best bid-buy diff: {price_diff:.1f}")
- except (ZeroDivisionError, TypeError) as e:
- log_error(f"Error comparing Bitpin buy: {e}")
- continue
- except (IndexError, TypeError) as e:
- log_error(f"Error processing Nobitex bid: {e}")
- # Send single Telegram message for the best opportunity
- try:
- best_message = None
- if best_ask_sell_diff > best_bid_buy_diff and best_ask_sell_diff > MIN_PROFIT_IRR and best_ask_sell_details:
- ask_price, ask_volume, compare_price, compare_volume, orig_ask_price, orig_ask_volume, sell_price, sell_volume = best_ask_sell_details
- best_message = (
- f"✅ موقعیت آربیتراژ یافت شد 📈\n"
- f"💸 خرید از نوبیتکس: {int(orig_ask_price)} ریال با حجم : {orig_ask_volume:.2f} دلار\n"
- f"💰 فروش در بیت پین: {int(sell_price)} ریال با حجم: {sell_volume:.2f} دلار\n"
- f"📊 قیمت میانگین خرید: {int(ask_price)} ریال\n"
- f"📊 قیمت میانگین فروش: {int(compare_price)} ریال\n"
- f"📏 حجم قیمت میانگین: {compare_volume:.2f} دلار\n"
- f"📈 تفاوت قیمت: {int(best_ask_sell_diff)} ریال"
- )
- elif best_bid_buy_diff > MIN_PROFIT_IRR and best_bid_buy_details:
- bid_price, bid_volume, compare_price, compare_volume, orig_bid_price, orig_bid_volume, buy_price, buy_volume = best_bid_buy_details
- best_message = (
- f"✅ موقعیت آربیتراژ یافت شد 📈\n"
- f"💸 خرید از بیت پین: {int(orig_bid_price)} ریال با حجم : {orig_bid_volume:.2f} دلار\n"
- f"💰 فروش در نوبیتکس: {int(buy_price)} ریال با حجم: {buy_volume:.2f} دلار\n"
- f"📊 قیمت میانگین خرید: {int(bid_price)} ریال\n"
- f"📊 قیمت میانگین فروش: {int(compare_price)} ریال\n"
- f"📏 حجم قیمت میانگین: {compare_volume:.2f} دلار\n"
- f"📈 تفاوت قیمت: {int(best_bid_buy_diff)} ریال"
- )
- if best_message:
- await send_telegram_message(best_message)
- else:
- print("No arbitrage opportunity found with price difference > " + str(MIN_PROFIT_IRR) + " IRR")
- except Exception as e:
- log_error(f"Error preparing Telegram message: {e}")
- # Print maximum price differences
- try:
- if best_ask_sell_details:
- print(
- f"Max Nobitex Ask - Bitpin Sell Difference: {best_ask_sell_diff:.1f} IRR\n"
- f"Nobitex Sell: {best_ask_sell_details[0]:.1f} IRR, Volume: {best_ask_sell_details[1]:.2f}\n"
- f"Bitpin Sell: {best_ask_sell_details[2]:.1f} IRR, Volume: {best_ask_sell_details[3]:.2f}\n"
- f"Original Nobitex Sell: {best_ask_sell_details[4]:.1f} IRR, Volume: {best_ask_sell_details[5]:.2f}"
- )
- else:
- print("No valid Nobitex Ask - Bitpin Sell comparisons")
- except Exception as e:
- log_error(f"Error printing ask-sell differences: {e}")
- try:
- if best_bid_buy_details:
- print(
- f"Max Nobitex Bid - Bitpin Buy Difference: {best_bid_buy_diff:.1f} IRR\n"
- f"Nobitex Buy: {best_bid_buy_details[0]:.1f} IRR, Volume: {best_bid_buy_details[1]:.2f}\n"
- f"Bitpin Buy: {best_bid_buy_details[2]:.1f} IRR, Volume: {best_bid_buy_details[3]:.2f}\n"
- f"Original Nobitex Buy: {best_bid_buy_details[4]:.1f} IRR, Volume: {best_bid_buy_details[5]:.2f}"
- )
- else:
- print("No valid Nobitex Bid - Bitpin Buy comparisons")
- except Exception as e:
- log_error(f"Error printing bid-buy differences: {e}")
- except Exception as e:
- log_error(f"Error in check_arbitrage: {e}")
- async def main():
- FPS = 1 / 30 # Check every 60 seconds
- while True:
- try:
- await check_arbitrage()
- await asyncio.sleep(1.0 / FPS)
- except Exception as e:
- log_error(f"Error in main loop: {e}")
- await asyncio.sleep(1.0 / FPS) # Continue after error
- if platform.system() == "Emscripten":
- asyncio.ensure_future(main())
- else:
- if __name__ == "__main__":
- asyncio.run(main())
|