Arbitrage.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. import asyncio
  2. import requests
  3. from datetime import datetime
  4. from telegram import Bot
  5. import platform
  6. # Telegram bot configuration (replace with your own bot token and chat ID)
  7. TELEGRAM_BOT_TOKEN = "YourBotToken" # Replace with your Telegram bot token
  8. TELEGRAM_CHAT_ID = "YourID" # Replace with your Telegram chat ID
  9. # API endpoints
  10. NOBITEX_API = "https://apiv2.nobitex.ir/v3/orderbook/USDTIRT"
  11. BITPIN_API = "https://api.bitpin.ir/v4/mth/matches/USDT_IRT/"
  12. # Minimum volume for Nobitex sell orders (in USDT)
  13. MIN_SELL_VOLUME = 500.0
  14. MIN_PROFIT_IRR = 5000.0
  15. def log_error(message):
  16. """Log errors with timestamp."""
  17. print(f"Error at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}: {message}")
  18. async def send_telegram_message(message):
  19. """Send Telegram message with error handling."""
  20. try:
  21. bot = Bot(token=TELEGRAM_BOT_TOKEN)
  22. await bot.send_message(chat_id=TELEGRAM_CHAT_ID, text=message)
  23. print("Sent message:", message)
  24. except Exception as e:
  25. log_error(f"Failed to send Telegram message: {e}")
  26. def fetch_nobitex_data():
  27. """Fetch Nobitex orderbook data with error handling."""
  28. try:
  29. response = requests.get(NOBITEX_API, timeout=10)
  30. response.raise_for_status()
  31. data = response.json()
  32. if data.get("status") == "ok":
  33. print("Nobitex API read successfully at", response.headers.get('Date', datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
  34. return data.get("asks", []), data.get("bids", [])
  35. else:
  36. log_error(f"Nobitex API error: {data.get('status')}")
  37. return [], []
  38. except (requests.RequestException, ValueError) as e:
  39. log_error(f"Error fetching Nobitex data: {e}")
  40. return [], []
  41. def fetch_bitpin_data():
  42. """Fetch Bitpin trade data with error handling."""
  43. try:
  44. response = requests.get(BITPIN_API, timeout=10)
  45. response.raise_for_status()
  46. data = response.json()
  47. print("Bitpin API read successfully at", response.headers.get('Date', datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
  48. return data
  49. except (requests.RequestException, ValueError) as e:
  50. log_error(f"Error fetching Bitpin data: {e}")
  51. return []
  52. def calculate_weighted_average(orders, target_volume, start_index=0):
  53. """Calculate weighted average price for orders starting from start_index until target_volume is reached."""
  54. try:
  55. total_volume = 0.0
  56. weighted_price_sum = 0.0
  57. used_orders = []
  58. for i in range(start_index, len(orders)):
  59. price, volume = orders[i]
  60. volume_to_use = min(volume, target_volume - total_volume)
  61. weighted_price_sum += price * volume_to_use
  62. total_volume += volume_to_use
  63. used_orders.append((price, volume_to_use))
  64. if total_volume >= target_volume:
  65. break
  66. if total_volume < target_volume:
  67. print(f"Debug: Insufficient volume, got {total_volume:.2f}, needed {target_volume:.2f}")
  68. return None, 0.0
  69. avg_price = weighted_price_sum / total_volume
  70. 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}")
  71. return avg_price, total_volume
  72. except (ZeroDivisionError, TypeError) as e:
  73. log_error(f"Error in calculate_weighted_average: {e}")
  74. return None, 0.0
  75. async def check_arbitrage():
  76. """Check for arbitrage opportunities with error handling."""
  77. try:
  78. # Fetch data from both APIs
  79. nobitex_asks, nobitex_bids = fetch_nobitex_data()
  80. bitpin_trades = fetch_bitpin_data()
  81. if not nobitex_asks or not nobitex_bids or not bitpin_trades:
  82. print("No data to compare")
  83. return
  84. # Convert Bitpin prices to IRR (toman to rial) and sort by price
  85. bitpin_buys = []
  86. bitpin_sells = []
  87. try:
  88. for trade in bitpin_trades:
  89. price = float(trade["price"]) * 10
  90. volume = float(trade["base_amount"])
  91. if trade["side"] == "buy":
  92. bitpin_buys.append((price, volume))
  93. elif trade["side"] == "sell":
  94. bitpin_sells.append((price, volume))
  95. bitpin_buys = sorted(bitpin_buys, key=lambda x: x[0], reverse=True)
  96. bitpin_sells = sorted(bitpin_sells, key=lambda x: x[0])
  97. except (KeyError, ValueError, TypeError) as e:
  98. log_error(f"Error parsing Bitpin trades: {e}")
  99. return
  100. # Sort Nobitex asks by price ascending and bids by price descending
  101. try:
  102. nobitex_asks = sorted(
  103. [(float(ask[0]), float(ask[1])) for ask in nobitex_asks],
  104. key=lambda x: x[0]
  105. )
  106. nobitex_bids = sorted(
  107. [(float(bid[0]), float(bid[1])) for bid in nobitex_bids],
  108. key=lambda x: x[0], reverse=True
  109. )
  110. except (ValueError, TypeError) as e:
  111. log_error(f"Error parsing Nobitex asks/bids: {e}")
  112. return
  113. # Track best arbitrage opportunity
  114. best_ask_sell_diff = float('-inf')
  115. best_bid_buy_diff = float('-inf')
  116. best_ask_sell_details = None
  117. best_bid_buy_details = None
  118. # Process first valid Nobitex ask
  119. if nobitex_asks:
  120. try:
  121. ask_price, ask_volume = nobitex_asks[0]
  122. orig_ask_price, orig_ask_volume = ask_price, ask_volume
  123. print(f"Debug: First Nobitex ask - Price: {ask_price:.1f}, Volume: {ask_volume:.2f}")
  124. # Enforce minimum sell volume
  125. if ask_volume < MIN_SELL_VOLUME:
  126. print(f"Debug: Aggregating Nobitex asks to reach {MIN_SELL_VOLUME} USDT")
  127. avg_price, total_volume = calculate_weighted_average(nobitex_asks, MIN_SELL_VOLUME)
  128. if avg_price is None or total_volume < MIN_SELL_VOLUME:
  129. print(f"Debug: Failed to aggregate Nobitex asks to {MIN_SELL_VOLUME} USDT")
  130. else:
  131. ask_price, ask_volume = avg_price, total_volume
  132. print(f"Debug: Aggregated Nobitex ask - Price: {ask_price:.1f}, Volume: {ask_volume:.2f}")
  133. # Compare with Bitpin sells
  134. for i, (sell_price, sell_volume) in enumerate(bitpin_sells):
  135. try:
  136. print(f"Debug: Comparing with Bitpin sell - Price: {sell_price:.1f}, Volume: {sell_volume:.2f}")
  137. volume_diff_ratio = abs(ask_volume - sell_volume) / max(ask_volume, sell_volume)
  138. if volume_diff_ratio > 0.05:
  139. print(f"Debug: Volume difference > 5% ({volume_diff_ratio:.2%}), aggregating Bitpin sells")
  140. avg_sell_price, total_sell_volume = calculate_weighted_average(bitpin_sells, ask_volume, i)
  141. if avg_sell_price is None or total_sell_volume < MIN_SELL_VOLUME:
  142. print(f"Debug: Failed to aggregate Bitpin sells to {ask_volume:.2f} USDT")
  143. continue
  144. price_diff = avg_sell_price - ask_price
  145. compare_volume = total_sell_volume
  146. compare_price = avg_sell_price
  147. print(f"Debug: Bitpin sell aggregated - Price: {avg_sell_price:.1f}, Volume: {total_sell_volume:.2f}")
  148. else:
  149. price_diff = sell_price - ask_price
  150. compare_volume = sell_volume
  151. compare_price = sell_price
  152. print(f"Debug: Using single Bitpin sell - Price: {sell_price:.1f}, Volume: {sell_volume:.2f}")
  153. if price_diff > best_ask_sell_diff:
  154. best_ask_sell_diff = price_diff
  155. best_ask_sell_details = (ask_price, ask_volume, compare_price, compare_volume, orig_ask_price, orig_ask_volume, sell_price, sell_volume)
  156. print(f"Debug: New best ask-sell diff: {price_diff:.1f}")
  157. except (ZeroDivisionError, TypeError) as e:
  158. log_error(f"Error comparing Bitpin sell: {e}")
  159. continue
  160. else:
  161. # Use first ask directly if volume is sufficient
  162. print(f"Debug: First Nobitex ask volume sufficient, comparing with Bitpin sells")
  163. for i, (sell_price, sell_volume) in enumerate(bitpin_sells):
  164. try:
  165. print(f"Debug: Comparing with Bitpin sell - Price: {sell_price:.1f}, Volume: {sell_volume:.2f}")
  166. volume_diff_ratio = abs(ask_volume - sell_volume) / max(ask_volume, sell_volume)
  167. if volume_diff_ratio > 0.05:
  168. print(f"Debug: Volume difference > 5% ({volume_diff_ratio:.2%}), aggregating Bitpin sells")
  169. avg_sell_price, total_sell_volume = calculate_weighted_average(bitpin_sells, ask_volume, i)
  170. if avg_sell_price is None or total_sell_volume < MIN_SELL_VOLUME:
  171. print(f"Debug: Failed to aggregate Bitpin sells to {ask_volume:.2f} USDT")
  172. continue
  173. price_diff = avg_sell_price - ask_price
  174. compare_volume = total_sell_volume
  175. compare_price = avg_sell_price
  176. print(f"Debug: Bitpin sell aggregated - Price: {avg_sell_price:.1f}, Volume: {total_sell_volume:.2f}")
  177. else:
  178. price_diff = sell_price - ask_price
  179. compare_volume = sell_volume
  180. compare_price = sell_price
  181. print(f"Debug: Using single Bitpin sell - Price: {sell_price:.1f}, Volume: {sell_volume:.2f}")
  182. if price_diff > best_ask_sell_diff:
  183. best_ask_sell_diff = price_diff
  184. best_ask_sell_details = (ask_price, ask_volume, compare_price, compare_volume, orig_ask_price, orig_ask_volume, sell_price, sell_volume)
  185. print(f"Debug: New best ask-sell diff: {price_diff:.1f}")
  186. except (ZeroDivisionError, TypeError) as e:
  187. log_error(f"Error comparing Bitpin sell: {e}")
  188. continue
  189. except (IndexError, TypeError) as e:
  190. log_error(f"Error processing Nobitex ask: {e}")
  191. # Process first valid Nobitex bid
  192. if nobitex_bids:
  193. try:
  194. bid_price, bid_volume = nobitex_bids[0]
  195. orig_bid_price, orig_bid_volume = bid_price, bid_volume
  196. print(f"Debug: First Nobitex bid - Price: {bid_price:.1f}, Volume: {bid_volume:.2f}")
  197. for i, (buy_price, buy_volume) in enumerate(bitpin_buys):
  198. try:
  199. print(f"Debug: Comparing with Bitpin buy - Price: {buy_price:.1f}, Volume: {buy_volume:.2f}")
  200. volume_diff_ratio = abs(bid_volume - buy_volume) / max(bid_volume, buy_volume)
  201. if volume_diff_ratio > 0.05:
  202. print(f"Debug: Volume difference > 5% ({volume_diff_ratio:.2%}), aggregating Bitpin buys")
  203. avg_buy_price, total_buy_volume = calculate_weighted_average(bitpin_buys, bid_volume, i)
  204. if avg_buy_price is None:
  205. print(f"Debug: Failed to aggregate Bitpin buys to {bid_volume:.2f} USDT")
  206. continue
  207. price_diff = bid_price - avg_buy_price
  208. compare_volume = total_buy_volume
  209. compare_price = avg_buy_price
  210. print(f"Debug: Bitpin buy aggregated - Price: {avg_buy_price:.1f}, Volume: {total_buy_volume:.2f}")
  211. else:
  212. price_diff = bid_price - buy_price
  213. compare_volume = buy_volume
  214. compare_price = buy_price
  215. print(f"Debug: Using single Bitpin buy - Price: {buy_price:.1f}, Volume: {buy_volume:.2f}")
  216. if price_diff > best_bid_buy_diff:
  217. best_bid_buy_diff = price_diff
  218. best_bid_buy_details = (bid_price, bid_volume, compare_price, compare_volume, orig_bid_price, orig_bid_volume, buy_price, buy_volume)
  219. print(f"Debug: New best bid-buy diff: {price_diff:.1f}")
  220. except (ZeroDivisionError, TypeError) as e:
  221. log_error(f"Error comparing Bitpin buy: {e}")
  222. continue
  223. except (IndexError, TypeError) as e:
  224. log_error(f"Error processing Nobitex bid: {e}")
  225. # Send single Telegram message for the best opportunity
  226. try:
  227. best_message = None
  228. if best_ask_sell_diff > best_bid_buy_diff and best_ask_sell_diff > MIN_PROFIT_IRR and best_ask_sell_details:
  229. ask_price, ask_volume, compare_price, compare_volume, orig_ask_price, orig_ask_volume, sell_price, sell_volume = best_ask_sell_details
  230. best_message = (
  231. f"✅ موقعیت آربیتراژ یافت شد 📈\n"
  232. f"💸 خرید از نوبیتکس: {int(orig_ask_price)} ریال با حجم : {orig_ask_volume:.2f} دلار\n"
  233. f"💰 فروش در بیت پین: {int(sell_price)} ریال با حجم: {sell_volume:.2f} دلار\n"
  234. f"📊 قیمت میانگین خرید: {int(ask_price)} ریال\n"
  235. f"📊 قیمت میانگین فروش: {int(compare_price)} ریال\n"
  236. f"📏 حجم قیمت میانگین: {compare_volume:.2f} دلار\n"
  237. f"📈 تفاوت قیمت: {int(best_ask_sell_diff)} ریال"
  238. )
  239. elif best_bid_buy_diff > MIN_PROFIT_IRR and best_bid_buy_details:
  240. bid_price, bid_volume, compare_price, compare_volume, orig_bid_price, orig_bid_volume, buy_price, buy_volume = best_bid_buy_details
  241. best_message = (
  242. f"✅ موقعیت آربیتراژ یافت شد 📈\n"
  243. f"💸 خرید از بیت پین: {int(orig_bid_price)} ریال با حجم : {orig_bid_volume:.2f} دلار\n"
  244. f"💰 فروش در نوبیتکس: {int(buy_price)} ریال با حجم: {buy_volume:.2f} دلار\n"
  245. f"📊 قیمت میانگین خرید: {int(bid_price)} ریال\n"
  246. f"📊 قیمت میانگین فروش: {int(compare_price)} ریال\n"
  247. f"📏 حجم قیمت میانگین: {compare_volume:.2f} دلار\n"
  248. f"📈 تفاوت قیمت: {int(best_bid_buy_diff)} ریال"
  249. )
  250. if best_message:
  251. await send_telegram_message(best_message)
  252. else:
  253. print("No arbitrage opportunity found with price difference > " + str(MIN_PROFIT_IRR) + " IRR")
  254. except Exception as e:
  255. log_error(f"Error preparing Telegram message: {e}")
  256. # Print maximum price differences
  257. try:
  258. if best_ask_sell_details:
  259. print(
  260. f"Max Nobitex Ask - Bitpin Sell Difference: {best_ask_sell_diff:.1f} IRR\n"
  261. f"Nobitex Sell: {best_ask_sell_details[0]:.1f} IRR, Volume: {best_ask_sell_details[1]:.2f}\n"
  262. f"Bitpin Sell: {best_ask_sell_details[2]:.1f} IRR, Volume: {best_ask_sell_details[3]:.2f}\n"
  263. f"Original Nobitex Sell: {best_ask_sell_details[4]:.1f} IRR, Volume: {best_ask_sell_details[5]:.2f}"
  264. )
  265. else:
  266. print("No valid Nobitex Ask - Bitpin Sell comparisons")
  267. except Exception as e:
  268. log_error(f"Error printing ask-sell differences: {e}")
  269. try:
  270. if best_bid_buy_details:
  271. print(
  272. f"Max Nobitex Bid - Bitpin Buy Difference: {best_bid_buy_diff:.1f} IRR\n"
  273. f"Nobitex Buy: {best_bid_buy_details[0]:.1f} IRR, Volume: {best_bid_buy_details[1]:.2f}\n"
  274. f"Bitpin Buy: {best_bid_buy_details[2]:.1f} IRR, Volume: {best_bid_buy_details[3]:.2f}\n"
  275. f"Original Nobitex Buy: {best_bid_buy_details[4]:.1f} IRR, Volume: {best_bid_buy_details[5]:.2f}"
  276. )
  277. else:
  278. print("No valid Nobitex Bid - Bitpin Buy comparisons")
  279. except Exception as e:
  280. log_error(f"Error printing bid-buy differences: {e}")
  281. except Exception as e:
  282. log_error(f"Error in check_arbitrage: {e}")
  283. async def main():
  284. FPS = 1 / 30 # Check every 60 seconds
  285. while True:
  286. try:
  287. await check_arbitrage()
  288. await asyncio.sleep(1.0 / FPS)
  289. except Exception as e:
  290. log_error(f"Error in main loop: {e}")
  291. await asyncio.sleep(1.0 / FPS) # Continue after error
  292. if platform.system() == "Emscripten":
  293. asyncio.ensure_future(main())
  294. else:
  295. if __name__ == "__main__":
  296. asyncio.run(main())