-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathexchanges.py
More file actions
88 lines (68 loc) · 2.63 KB
/
Copy pathexchanges.py
File metadata and controls
88 lines (68 loc) · 2.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
"""Single source of truth for ccxt exchange clients.
bot_logic.py and signal_handler.py used to build their own clients with
different settings. The dashboard read futures positions while the webhook
placed spot orders on Binance, because only bot_logic set defaultType.
Everything now shares the clients built here.
"""
import ccxt
import config
from log_setup import get_logger
logger = get_logger("exchanges")
SUPPORTED = ("bybit", "binance")
def _build_bybit():
client = ccxt.bybit(
{
"apiKey": config.BYBIT_API_KEY,
"secret": config.BYBIT_API_SECRET,
"enableRateLimit": True,
}
)
# Timestamp synchronization, otherwise Bybit rejects requests as invalid.
client.options["recvWindow"] = 5000
client.options["adjustForTimeDifference"] = True
return client
def _build_binance():
client = ccxt.binance(
{
"apiKey": config.BINANCE_API_KEY,
"secret": config.BINANCE_API_SECRET,
"enableRateLimit": True,
# Must match what the dashboard reads back, or orders land on spot
# while positions are fetched from futures.
"options": {"defaultType": "future"},
}
)
client.options["warnOnFetchOpenOrdersWithoutSymbol"] = False
return client
_BUILDERS = {
"bybit": (_build_bybit, lambda: (config.BYBIT_API_KEY, config.BYBIT_API_SECRET)),
"binance": (_build_binance, lambda: (config.BINANCE_API_KEY, config.BINANCE_API_SECRET)),
}
def _load():
loaded = {}
for name in SUPPORTED:
if name not in config.ENABLED_EXCHANGES:
logger.info(f"⏭ {name} not listed in EXCHANGES, skipping.")
continue
builder, credentials = _BUILDERS[name]
key, secret = credentials()
if not key or not secret:
# Building a client anyway would let orders be sent with blank
# credentials and fail at the exchange instead of here.
logger.warning(f"⏭ {name} has no API credentials configured, skipping.")
continue
logger.info(f"🔄 Setting up {name} API...")
try:
loaded[name] = builder()
logger.info(f"✅ {name} successfully initialized!")
except Exception as e:
logger.error(f"❌ Error initializing {name}: {e}")
if loaded:
logger.info(f"🎉 Loaded exchanges: {list(loaded.keys())}")
else:
logger.error("❌ No exchanges loaded! Double-check API keys and config.")
return loaded
exchanges = _load()
def get(name):
"""Return a configured client, or None if that exchange is unavailable."""
return exchanges.get(name)