Enviar arquivos para "/"
parent
e7c36f5018
commit
74e77f522d
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,112 @@
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from collections import defaultdict
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
|
||||||
|
# === GET SYMBOLS FROM EXCHANGES ===
|
||||||
|
|
||||||
|
def get_binance_symbols():
|
||||||
|
url = "https://fapi.binance.com/fapi/v1/exchangeInfo"
|
||||||
|
data = requests.get(url).json()
|
||||||
|
return [s["symbol"].replace("USDT", "/USDT") for s in data["symbols"] if s["quoteAsset"] == "USDT"]
|
||||||
|
|
||||||
|
def get_bybit_symbols():
|
||||||
|
url = "https://api.bybit.com/v5/market/instruments-info"
|
||||||
|
params = {"category": "linear"}
|
||||||
|
r = requests.get(url, params=params)
|
||||||
|
data = r.json()
|
||||||
|
return [
|
||||||
|
s["symbol"].replace("USDT", "/USDT")
|
||||||
|
for s in data.get("result", {}).get("list", [])
|
||||||
|
if "USDT" in s["symbol"]
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_okx_symbols():
|
||||||
|
url = "https://www.okx.com/api/v5/public/instruments"
|
||||||
|
params = {"instType": "SWAP"}
|
||||||
|
data = requests.get(url, params=params).json()
|
||||||
|
return [s["instId"].replace("-USDT-SWAP", "/USDT") for s in data.get("data", []) if s["instId"].endswith("USDT-SWAP")]
|
||||||
|
|
||||||
|
# === LOAD SYMBOLS ===
|
||||||
|
|
||||||
|
bybit_symbols = get_bybit_symbols()
|
||||||
|
binance_symbols = get_binance_symbols()
|
||||||
|
okx_symbols = get_okx_symbols()
|
||||||
|
|
||||||
|
symbols = sorted(list(set(bybit_symbols + binance_symbols + okx_symbols)))
|
||||||
|
|
||||||
|
# === FUNDING FUNCTIONS ===
|
||||||
|
|
||||||
|
def normalize_symbol(symbol):
|
||||||
|
return symbol.replace("/", "")
|
||||||
|
|
||||||
|
def get_binance_funding(symbol):
|
||||||
|
try:
|
||||||
|
url = "https://fapi.binance.com/fapi/v1/premiumIndex"
|
||||||
|
params = {"symbol": normalize_symbol(symbol)}
|
||||||
|
data = requests.get(url, params=params, timeout=3).json()
|
||||||
|
return "binance", symbol, float(data.get("lastFundingRate", 0))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Binance] {symbol} funding error: {e}")
|
||||||
|
return "binance", symbol, 0.0
|
||||||
|
|
||||||
|
def get_bybit_funding(symbol):
|
||||||
|
try:
|
||||||
|
url = "https://api.bybit.com/v5/market/tickers"
|
||||||
|
params = {"category": "linear"}
|
||||||
|
data = requests.get(url, params=params, timeout=3).json()
|
||||||
|
for item in data.get("result", {}).get("list", []):
|
||||||
|
if item.get("symbol") == normalize_symbol(symbol):
|
||||||
|
return "bybit", symbol, float(item.get("fundingRate", 0))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Bybit] {symbol} funding error: {e}")
|
||||||
|
return "bybit", symbol, 0.0
|
||||||
|
|
||||||
|
def get_okx_funding(symbol):
|
||||||
|
try:
|
||||||
|
inst_id = symbol.replace("/", "-") + "-SWAP"
|
||||||
|
url = "https://www.okx.com/api/v5/public/funding-rate"
|
||||||
|
params = {"instId": inst_id}
|
||||||
|
data = requests.get(url, params=params, timeout=3).json()
|
||||||
|
return "okx", symbol, float(data.get("data", [{}])[0].get("fundingRate", 0))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[OKX] {symbol} funding error: {e}")
|
||||||
|
return "okx", symbol, 0.0
|
||||||
|
|
||||||
|
def get_hyperliquid_funding(symbol):
|
||||||
|
try:
|
||||||
|
symbol_usd = symbol.replace("USDT", "USD").replace("/", "")
|
||||||
|
url = "https://api.hyperliquid.xyz/info"
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
payload = {"type": "metaAndAssetCtxs"}
|
||||||
|
response = requests.post(url, headers=headers, json=payload)
|
||||||
|
data = response.json()
|
||||||
|
assets = data[0]["universe"]
|
||||||
|
asset_data = data[1]
|
||||||
|
for i in range(len(assets)):
|
||||||
|
name = assets[i].get("name", "").upper()
|
||||||
|
if name == symbol_usd.replace("USD", "").upper():
|
||||||
|
return "hyperliquid", symbol, float(asset_data[i].get("funding", 0))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Hyperliquid Funding Error] {symbol}: {e}")
|
||||||
|
return "hyperliquid", symbol, 0.0
|
||||||
|
|
||||||
|
funding_funcs = [
|
||||||
|
get_binance_funding,
|
||||||
|
get_bybit_funding,
|
||||||
|
get_okx_funding,
|
||||||
|
get_hyperliquid_funding # ✅ incluir aqui
|
||||||
|
]
|
||||||
|
funding_data = defaultdict(dict)
|
||||||
|
|
||||||
|
with ThreadPoolExecutor(max_workers=20) as executor:
|
||||||
|
tasks = [executor.submit(func, symbol) for symbol in symbols for func in funding_funcs]
|
||||||
|
for future in as_completed(tasks):
|
||||||
|
exchange, symbol, rate = future.result()
|
||||||
|
funding_data[symbol][exchange] = rate
|
||||||
|
|
||||||
|
# Save to file
|
||||||
|
with open("funding_cache.json", "w") as f:
|
||||||
|
json.dump(funding_data, f, indent=2)
|
||||||
|
|
||||||
|
print("[✓] funding_cache.json atualizado com sucesso")
|
|
@ -0,0 +1,13 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def log_opportunity(opp: dict):
|
||||||
|
timestamp = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
message = (
|
||||||
|
f"[{timestamp}] {opp['symbol']} | BUY on {opp['buy_exchange']} @ {opp['buy_price']} "
|
||||||
|
f"-> SELL on {opp['sell_exchange']} @ {opp['sell_price']} | "
|
||||||
|
f"Net Spread: {opp['spread_percent']:.2f}% | Size: {opp['size']:.4f} "
|
||||||
|
f"| Funding BUY: {opp['buy_funding']:.5f}, SELL: {opp['sell_funding']:.5f}"
|
||||||
|
)
|
||||||
|
print(message)
|
||||||
|
#with open("opportunities.txt", "a") as f:
|
||||||
|
#f.write(message + "\n")
|
|
@ -0,0 +1,27 @@
|
||||||
|
import asyncio
|
||||||
|
from exchanges.binance import BinancePerpStream
|
||||||
|
from exchanges.okx import OKXPerpStream
|
||||||
|
from exchanges.bybit import BybitPerpStream
|
||||||
|
from exchanges.hyperliquid import HyperliquidPerpStream
|
||||||
|
from logger import log_opportunity
|
||||||
|
from spread_calculator import check_arbitrage_opportunities
|
||||||
|
|
||||||
|
latest_ticks = {}
|
||||||
|
|
||||||
|
async def consumer(exchange, stream):
|
||||||
|
async for tick in stream():
|
||||||
|
latest_ticks[(tick['exchange'], tick['symbol'])] = tick
|
||||||
|
opportunities = check_arbitrage_opportunities(tick, latest_ticks)
|
||||||
|
for opp in opportunities:
|
||||||
|
log_opportunity(opp)
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tasks = [
|
||||||
|
consumer('binance', BinancePerpStream(["USDT"]).stream),
|
||||||
|
consumer('okx', OKXPerpStream(["USDT"]).stream),
|
||||||
|
consumer('bybit', BybitPerpStream(["USDT"]).stream)
|
||||||
|
]
|
||||||
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
|
@ -0,0 +1,63 @@
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
from exchanges.binance import BinancePerpStream
|
||||||
|
from exchanges.okx import OKXPerpStream
|
||||||
|
from exchanges.bybit import BybitPerpStream
|
||||||
|
|
||||||
|
FEE = 0.0005 # taker fee
|
||||||
|
FUNDING_WEIGHT = 1 # pondera funding rate (ex: por 8h)
|
||||||
|
MIN_SPREAD = 0.4 # mínimo spread líquido para registrar
|
||||||
|
|
||||||
|
# Lista de exchanges que você quer considerar na arbitragem
|
||||||
|
ENABLED_EXCHANGES = {"bybit", "okx", "binance"}
|
||||||
|
|
||||||
|
# Carrega funding de arquivo externo salvo periodicamente
|
||||||
|
with open("funding_cache.json", "r") as f:
|
||||||
|
funding_cache = json.load(f)
|
||||||
|
|
||||||
|
def check_arbitrage_opportunities(new_tick, all_ticks):
|
||||||
|
opportunities = []
|
||||||
|
symbol = new_tick['symbol']
|
||||||
|
|
||||||
|
if new_tick['exchange'].lower() not in ENABLED_EXCHANGES:
|
||||||
|
return opportunities
|
||||||
|
|
||||||
|
for (ex_name, ex_symbol), tick in all_ticks.items():
|
||||||
|
if ex_symbol != symbol or ex_name == new_tick['exchange']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for buyer, seller in [(new_tick, tick), (tick, new_tick)]:
|
||||||
|
if buyer['exchange'].lower() not in ENABLED_EXCHANGES or seller['exchange'].lower() not in ENABLED_EXCHANGES:
|
||||||
|
continue
|
||||||
|
|
||||||
|
buy_price = buyer['ask'] * (1 + FEE)
|
||||||
|
sell_price = seller['bid'] * (1 - FEE)
|
||||||
|
|
||||||
|
if sell_price <= buy_price:
|
||||||
|
continue
|
||||||
|
|
||||||
|
spread = sell_price - buy_price
|
||||||
|
spread_percent = (spread / buy_price) * 100
|
||||||
|
size = min(buyer['ask_size'], seller['bid_size'])
|
||||||
|
|
||||||
|
buy_funding = funding_cache.get(symbol, {}).get(buyer['exchange'], 0.0) * 100
|
||||||
|
sell_funding = funding_cache.get(symbol, {}).get(seller['exchange'], 0.0) * 100
|
||||||
|
funding_carry = sell_funding - buy_funding
|
||||||
|
net_spread_percent = spread_percent + FUNDING_WEIGHT * funding_carry
|
||||||
|
|
||||||
|
if net_spread_percent > MIN_SPREAD:
|
||||||
|
opportunities.append({
|
||||||
|
'symbol': symbol,
|
||||||
|
'buy_exchange': buyer['exchange'],
|
||||||
|
'buy_price': buyer['ask'],
|
||||||
|
'sell_exchange': seller['exchange'],
|
||||||
|
'sell_price': seller['bid'],
|
||||||
|
'spread_percent': net_spread_percent,
|
||||||
|
'raw_spread_percent': spread_percent,
|
||||||
|
'buy_funding': buy_funding,
|
||||||
|
'sell_funding': sell_funding,
|
||||||
|
'size': size
|
||||||
|
})
|
||||||
|
|
||||||
|
return opportunities
|
Loading…
Reference in New Issue