├── requirements.txt ├── README.md └── optionArb.py /requirements.txt: -------------------------------------------------------------------------------- 1 | asyncio 2 | aiohttp 3 | hmac 4 | hashlib 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # binance-options-arbitrage 2 | Script for trade arbitrage opportunities between European-style options and Perpetual futures, with notifications in telegram 3 | 4 | ## To run 5 | ### 1. Install requirements: 6 | ```sh 7 | pip install -r requirements.txt 8 | ``` 9 | ### 2. Set your keys and parameters 10 | ```sh 11 | bot_token = "" # telegram bot token id 12 | chat_ids = [""] # your telegram id for messages 13 | tickers = ["BTCUSDT", "ETHUSDT", "BNBUSDT"] # assets to tarde 14 | min_pct = 1 # min percentage difference 15 | volume = 100 # your trading order size 16 | leverage = 2 # levarage for using on futures to use less capital for hedging 17 | max_hold = 60*60*24*7 # max time untill option expiration 18 | binance_api_key = "" # binance api key 19 | binance_api_secret = "" # binance secret 20 | ``` 21 | ### 3. Start app 22 | ```sh 23 | python3 optionArb.py 24 | -------------------------------------------------------------------------------- /optionArb.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aiohttp 3 | import time 4 | import hmac 5 | import hashlib 6 | from datetime import datetime 7 | from math import floor 8 | 9 | bot_token = "" # telegram bot token id 10 | chat_ids = [""] # your telegram id for messages 11 | tickers = ["BTCUSDT", "ETHUSDT", "BNBUSDT"] # assets to tarde 12 | min_pct = 1 # min percentage difference 13 | volume = 100 # your trading order size 14 | leverage = 2 # levarage for using on futures to use less capital for hedging 15 | max_hold = 60*60*24*7 # max time untill option expiration 16 | binance_api_key = "" # binance api key 17 | binance_api_secret = "" # binance secret 18 | 19 | 20 | async def send_telegram_message(message, chat_id): 21 | try: 22 | url = f"https://api.telegram.org/bot{bot_token}/sendMessage" 23 | payload = {"text": message, "chat_id": chat_id} 24 | async with aiohttp.ClientSession() as session: 25 | async with session.post(url, data=payload) as response: 26 | if response.status == 200: 27 | return True 28 | except Exception as e: 29 | print(f"[{datetime.now()}] Error: {e}") 30 | return False 31 | 32 | 33 | async def fetch_option_depth(ticker): 34 | try: 35 | async with aiohttp.ClientSession() as session: 36 | async with session.get(f'https://www.binance.com/bapi/eoptions/v1/public/eoptions/market/depth?limit=50&symbol={ticker}') as response: 37 | data = await response.json() 38 | return data["data"] 39 | except Exception as e: 40 | print(f"[{datetime.now()}] Error: {e}") 41 | return False 42 | 43 | 44 | async def fetch_depth(session, ticker): 45 | try: 46 | url = f'https://www.binance.com/bapi/eoptions/v1/public/eoptions/exchange/tGroup?contract={ticker}' 47 | async with session.get(url) as response: 48 | data = await response.json() 49 | return data["data"] 50 | except Exception as e: 51 | print(f"[{datetime.now()}] Error: {e}") 52 | return False 53 | 54 | 55 | async def fetch_prices(session): 56 | try: 57 | url = 'https://fapi.binance.com/fapi/v1/ticker/price' 58 | async with session.get(url) as response: 59 | data = await response.json() 60 | prices = {price['symbol']: float(price['price']) for price in data} 61 | return prices 62 | except Exception as e: 63 | print(f"[{datetime.now()}] Error: {e}") 64 | return False 65 | 66 | 67 | async def fetch_all_depths(tickers): 68 | try: 69 | async with aiohttp.ClientSession() as session: 70 | tasks = [] 71 | for ticker in tickers: 72 | task = asyncio.create_task(fetch_depth(session, ticker)) 73 | tasks.append(task) 74 | depths = await asyncio.gather(*tasks) 75 | return depths 76 | except Exception as e: 77 | print(f"[{datetime.now()}] Error: {e}") 78 | return False 79 | 80 | 81 | async def open_binance_positions(ticker, option_price, option_qty, futures_qty, side): 82 | try: 83 | print(ticker, option_price, option_qty, futures_qty, side) 84 | data_options = { 85 | "symbol": ticker, 86 | "price": str(option_price), 87 | "quantity": str(option_qty), 88 | "side": "BUY", 89 | "type": "LIMIT", 90 | "timestamp": str(int(time.time()) * 1000), 91 | "recvWindow": "10000000", 92 | } 93 | query_string_options = "&".join( 94 | [f"{k}={v}" for k, v in data_options.items()]) 95 | signature_options = hmac.new( 96 | binance_api_secret.encode("utf-8"), 97 | query_string_options.encode("utf-8"), 98 | hashlib.sha256 99 | ).hexdigest() 100 | headers = { 101 | "X-MBX-APIKEY": binance_api_key 102 | } 103 | data_options["signature"] = signature_options 104 | data_futures = { 105 | "symbol": ticker.split("-")[0]+"USDT", 106 | "quantity": str(futures_qty), 107 | "side": side, 108 | "type": "MARKET", 109 | "timestamp": str(int(time.time())*1000), 110 | "recvWindow": "10000000", 111 | } 112 | query_string_futures = "&".join( 113 | [f"{k}={v}" for k, v in data_futures.items()]) 114 | signature_futures = hmac.new( 115 | binance_api_secret.encode("utf-8"), 116 | query_string_futures.encode("utf-8"), 117 | hashlib.sha256 118 | ).hexdigest() 119 | data_futures["signature"] = signature_futures 120 | data_leverage = { 121 | "symbol": ticker.split("-")[0]+"USDT", 122 | "leverage": str(leverage), 123 | "timestamp": str(int(time.time())*1000), 124 | "recvWindow": "10000000", 125 | } 126 | query_string_leverage = "&".join( 127 | [f"{k}={v}" for k, v in data_leverage.items()]) 128 | signature_leverage = hmac.new( 129 | binance_api_secret.encode("utf-8"), 130 | query_string_leverage.encode("utf-8"), 131 | hashlib.sha256 132 | ).hexdigest() 133 | data_leverage["signature"] = signature_leverage 134 | async with aiohttp.ClientSession() as session: 135 | await session.post("https://fapi.binance.com/fapi/v1/leverage", headers=headers, params=data_leverage) 136 | async with session.post(f"https://fapi.binance.com/fapi/v1/order", headers=headers, params=data_futures) as response1: 137 | res1 = await response1.json() 138 | print(res1) 139 | if res1["orderId"]: 140 | async with session.post(f"https://eapi.binance.com/eapi/v1/order", headers=headers, params=data_options) as response2: 141 | res2 = await response2.json() 142 | print(res2) 143 | if res2["orderId"]: 144 | return True 145 | except Exception as e: 146 | print(f"[{datetime.now()}] Error: {e}") 147 | return False 148 | 149 | 150 | async def main(): 151 | while True: 152 | try: 153 | async with aiohttp.ClientSession() as session: 154 | depths = await fetch_all_depths(tickers) 155 | prices = await fetch_prices(session) 156 | current_time = int(time.time()) 157 | if depths and prices: 158 | for data in depths: 159 | for date_option in data: 160 | expiration_time = int( 161 | date_option["expirationTime"]/1000) 162 | if expiration_time < current_time + max_hold: 163 | for option_price in date_option["optionPriceList"]: 164 | if option_price["call"] and option_price["call"]["askPrice"] != 0 and option_price["expirationPrice"] < prices[option_price["call"]["symbol"].split("-")[0]+"USDT"]: 165 | diff = (prices[option_price["call"]["symbol"].split("-")[0]+"USDT"] - (option_price["expirationPrice"] + 166 | option_price["call"]["askPrice"])) / prices[option_price["call"]["symbol"].split("-")[0]+"USDT"] * 100 167 | if diff > min_pct: 168 | option_depth = await fetch_option_depth(option_price["call"]["symbol"]) 169 | if float(option_price["call"]["askPrice"]) == float(option_depth["asks"][0]["price"]): 170 | avb_qty = float( 171 | option_depth["asks"][0]["quote"]) 172 | if avb_qty*option_price["expirationPrice"] > volume: 173 | pos_qty = floor(volume / 174 | prices[option_price["call"]["symbol"].split( 175 | "-")[0]+"USDT"]*100)/100 176 | if pos_qty >= 0.01: 177 | pos = await open_binance_positions(option_price["call"]["symbol"], float(option_depth["asks"][0]["price"]), pos_qty, pos_qty, "SELL") 178 | for chat_id in chat_ids: 179 | await send_telegram_message(f'Ticker: {option_price["call"]["symbol"]}\nStrike : {option_price["expirationPrice"]}\nPrice : {option_price["call"]["askPrice"]}\nAvailable qty: {avb_qty}\nUnderlying price: {prices[option_price["call"]["symbol"].split("-")[0]+"USDT"]}\nDifference: {diff:.2}%', chat_id) 180 | elif option_price["put"] and option_price["put"]["askPrice"] != 0 and option_price["expirationPrice"] > prices[option_price["put"]["symbol"].split("-")[0]+"USDT"]: 181 | diff = ((option_price["expirationPrice"] - option_price["put"]["askPrice"]) - prices[option_price["put"]["symbol"].split( 182 | "-")[0]+"USDT"]) / prices[option_price["put"]["symbol"].split("-")[0]+"USDT"] * 100 183 | if diff > min_pct: 184 | option_depth = await fetch_option_depth(option_price["put"]["symbol"]) 185 | if float(option_price["put"]["askPrice"]) == float(option_depth["asks"][0]["price"]): 186 | avb_qty = float( 187 | option_depth["asks"][0]["quote"]) 188 | if avb_qty*option_price["expirationPrice"] > volume: 189 | pos_qty = floor(volume / 190 | prices[option_price["put"]["symbol"].split( 191 | "-")[0]+"USDT"]*100)/100 192 | if pos_qty >= 0.01: 193 | pos = await open_binance_positions(option_price["put"]["symbol"], float(option_depth["asks"][0]["price"]), pos_qty, pos_qty, "BUY") 194 | for chat_id in chat_ids: 195 | await send_telegram_message(f'Ticker: {option_price["put"]["symbol"]}\nStrike : {option_price["expirationPrice"]}\nPrice : {option_price["put"]["askPrice"]}\nAvailable qty: {avb_qty}\nUnderlying price: {prices[option_price["put"]["symbol"].split("-")[0]+"USDT"]}\nDifference: {diff:.2}%', chat_id) 196 | except Exception as e: 197 | print(e) 198 | continue 199 | 200 | asyncio.run(main()) 201 | --------------------------------------------------------------------------------