Arbitrage.py 17 KB

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