Signed-off-by: TIANHE <TIANHE@GMAIL.COM>
This commit is contained in:
TIANHE
2025-12-29 04:45:59 +08:00
parent d5e987b8ee
commit 278b88ec71
26 changed files with 3220 additions and 317 deletions
+191 -2
View File
@@ -60,13 +60,174 @@ class StrategyService:
获取交易所交易对列表 (无需API Key)
"""
try:
import ccxt
exchange_id = exchange_config.get('exchange_id', '')
proxies = exchange_config.get('proxies')
if not exchange_id:
return {'success': False, 'message': '请选择交易所', 'symbols': []}
# For these exchanges, prefer direct REST (no ccxt), aligned with local live-trading design.
ex = str(exchange_id or "").strip().lower()
if ex in ("bybit", "coinbaseexchange", "coinbase_exchange", "kraken", "kucoin", "gate", "bitfinex"):
import requests
def _req_json(url: str) -> Any:
r = requests.get(url, timeout=15, proxies=proxies)
r.raise_for_status()
return r.json()
symbols: List[str] = []
market_type = str(exchange_config.get("market_type") or exchange_config.get("defaultType") or "spot").strip().lower()
if market_type in ("futures", "future", "perp", "perpetual"):
market_type = "swap"
if ex == "bybit":
base = str(exchange_config.get("base_url") or exchange_config.get("baseUrl") or "https://api.bybit.com").rstrip("/")
cat = "spot" if market_type == "spot" else "linear"
j = _req_json(f"{base}/v5/market/instruments-info?category={cat}")
lst = (((j.get("result") or {}).get("list")) if isinstance(j, dict) else None) or []
if isinstance(lst, list):
for it in lst:
if not isinstance(it, dict):
continue
sym = str(it.get("symbol") or "")
status = str(it.get("status") or "").lower()
if not sym or (status and status not in ("trading", "tradable", "online")):
continue
if sym.endswith("USDT") and len(sym) > 4:
symbols.append(f"{sym[:-4]}/USDT")
symbols = sorted(list(set(symbols)))
return {'success': True, 'message': f'获取成功,共 {len(symbols)} 个交易对', 'symbols': symbols}
if ex in ("coinbaseexchange", "coinbase_exchange"):
base = str(exchange_config.get("base_url") or exchange_config.get("baseUrl") or "https://api.exchange.coinbase.com").rstrip("/")
j = _req_json(f"{base}/products")
if isinstance(j, list):
for it in j:
if not isinstance(it, dict):
continue
if str(it.get("status") or "").lower() not in ("online", ""):
continue
base_ccy = str(it.get("base_currency") or "").upper()
quote_ccy = str(it.get("quote_currency") or "").upper()
if quote_ccy == "USDT" and base_ccy:
symbols.append(f"{base_ccy}/USDT")
symbols = sorted(list(set(symbols)))
return {'success': True, 'message': f'获取成功,共 {len(symbols)} 个交易对', 'symbols': symbols}
if ex == "kraken":
if market_type == "spot":
j = _req_json("https://api.kraken.com/0/public/AssetPairs")
res = (j.get("result") if isinstance(j, dict) else None) or {}
if isinstance(res, dict):
for _k, v in res.items():
if not isinstance(v, dict):
continue
wsname = str(v.get("wsname") or "")
if not wsname or "/" not in wsname:
continue
base_ccy, quote_ccy = wsname.split("/", 1)
if str(quote_ccy).upper() == "USDT":
symbols.append(f"{str(base_ccy).upper()}/USDT")
else:
base = str(exchange_config.get("futures_base_url") or exchange_config.get("futuresBaseUrl") or "https://futures.kraken.com").rstrip("/")
j = _req_json(f"{base}/derivatives/api/v3/instruments")
instruments = j.get("instruments") if isinstance(j, dict) else None
if isinstance(instruments, list):
for it in instruments:
if not isinstance(it, dict):
continue
sym = str(it.get("symbol") or "")
typ = str(it.get("type") or "").lower()
if sym and ("perpetual" in typ or typ.startswith("pf") or sym.startswith("PF_")):
symbols.append(sym)
symbols = sorted(list(set(symbols)))
return {'success': True, 'message': f'获取成功,共 {len(symbols)} 个交易对', 'symbols': symbols}
if ex == "kucoin":
if market_type == "spot":
base = str(exchange_config.get("base_url") or exchange_config.get("baseUrl") or "https://api.kucoin.com").rstrip("/")
j = _req_json(f"{base}/api/v1/symbols")
data = (j.get("data") if isinstance(j, dict) else None) or []
if isinstance(data, list):
for it in data:
if not isinstance(it, dict):
continue
if not bool(it.get("enableTrading", True)):
continue
if str(it.get("quoteCurrency") or "").upper() != "USDT":
continue
b = str(it.get("baseCurrency") or "").upper()
if b:
symbols.append(f"{b}/USDT")
else:
base = str(exchange_config.get("futures_base_url") or exchange_config.get("futuresBaseUrl") or "https://api-futures.kucoin.com").rstrip("/")
j = _req_json(f"{base}/api/v1/contracts/active")
data = (j.get("data") if isinstance(j, dict) else None) or []
if isinstance(data, list):
for it in data:
if not isinstance(it, dict):
continue
sym = str(it.get("symbol") or "")
if not sym or not sym.upper().endswith("USDTM"):
continue
base_ccy = sym[:-5].upper()
if base_ccy == "XBT":
base_ccy = "BTC"
if base_ccy:
symbols.append(f"{base_ccy}/USDT")
symbols = sorted(list(set(symbols)))
return {'success': True, 'message': f'获取成功,共 {len(symbols)} 个交易对', 'symbols': symbols}
if ex == "gate":
base = str(exchange_config.get("base_url") or exchange_config.get("baseUrl") or "https://api.gateio.ws").rstrip("/")
if market_type == "spot":
j = _req_json(f"{base}/api/v4/spot/currency_pairs")
if isinstance(j, list):
for it in j:
if not isinstance(it, dict):
continue
if str(it.get("trade_status") or "").lower() not in ("tradable", "trading", ""):
continue
base_ccy = str(it.get("base") or "").upper()
quote_ccy = str(it.get("quote") or "").upper()
if quote_ccy == "USDT" and base_ccy:
symbols.append(f"{base_ccy}/USDT")
else:
j = _req_json(f"{base}/api/v4/futures/usdt/contracts")
if isinstance(j, list):
for it in j:
if not isinstance(it, dict):
continue
name = str(it.get("name") or it.get("contract") or "")
if name and name.upper().endswith("_USDT"):
symbols.append(name.replace("_", "/"))
symbols = sorted(list(set(symbols)))
return {'success': True, 'message': f'获取成功,共 {len(symbols)} 个交易对', 'symbols': symbols}
if ex == "bitfinex":
j = _req_json("https://api-pub.bitfinex.com/v2/conf/pub:list:pair:exchange") if market_type == "spot" else _req_json(
"https://api-pub.bitfinex.com/v2/conf/pub:list:pair:futures"
)
pairs = []
if isinstance(j, list) and j and isinstance(j[0], list):
pairs = j[0]
for p in pairs:
s = str(p or "").upper()
if not s:
continue
if market_type != "spot":
symbols.append(s)
continue
# Focus USDT (Bitfinex uses UST)
if s.endswith("UST") and len(s) > 3:
symbols.append(f"{s[:-3]}/USDT")
elif s.endswith("USDT") and len(s) > 4:
symbols.append(f"{s[:-4]}/USDT")
symbols = sorted(list(set(symbols)))
return {'success': True, 'message': f'获取成功,共 {len(symbols)} 个交易对', 'symbols': symbols}
return {'success': True, 'message': '获取成功', 'symbols': symbols}
import ccxt
# 创建交易所实例 (public only)
exchange_class = getattr(ccxt, exchange_id, None)
@@ -112,6 +273,14 @@ class StrategyService:
from app.services.live_trading.binance_spot import BinanceSpotClient
from app.services.live_trading.okx import OkxClient
from app.services.live_trading.bitget import BitgetMixClient
from app.services.live_trading.bybit import BybitClient
from app.services.live_trading.coinbase_exchange import CoinbaseExchangeClient
from app.services.live_trading.kraken import KrakenClient
from app.services.live_trading.kraken_futures import KrakenFuturesClient
from app.services.live_trading.kucoin import KucoinSpotClient
from app.services.live_trading.kucoin import KucoinFuturesClient
from app.services.live_trading.gate import GateSpotClient, GateUsdtFuturesClient
from app.services.live_trading.bitfinex import BitfinexClient, BitfinexDerivativesClient
resolved = resolve_exchange_config(exchange_config or {})
safe_cfg = safe_exchange_config_for_log(resolved)
@@ -160,6 +329,26 @@ class StrategyService:
elif isinstance(client, BitgetMixClient):
product_type = str(resolved.get("product_type") or resolved.get("productType") or "USDT-FUTURES")
priv_data = client.get_accounts(product_type=product_type)
elif isinstance(client, BybitClient):
priv_data = client.get_wallet_balance()
elif isinstance(client, CoinbaseExchangeClient):
priv_data = client.get_accounts()
elif isinstance(client, KrakenClient):
priv_data = client.get_balance()
elif isinstance(client, KrakenFuturesClient):
priv_data = client.get_accounts()
elif isinstance(client, KucoinSpotClient):
priv_data = client.get_accounts()
elif isinstance(client, KucoinFuturesClient):
priv_data = client.get_accounts()
elif isinstance(client, GateSpotClient):
priv_data = client.get_accounts()
elif isinstance(client, GateUsdtFuturesClient):
priv_data = client.get_accounts()
elif isinstance(client, BitfinexClient):
priv_data = client.get_wallets()
elif isinstance(client, BitfinexDerivativesClient):
priv_data = client.get_wallets()
except Exception as e:
msg = str(e)
# Add actionable hints for the most common Binance auth error.