feat(binance_client, position_manager): 优化价格获取逻辑与异常处理
在 `binance_client` 中引入 K线和最优挂单的 WebSocket 流,优先从缓存中获取价格数据,减少对 REST API 的依赖。同时,更新了价格获取逻辑,确保在未能获取价格时提供详细的错误信息。增强了异常处理,确保在请求超时或失败时记录相关日志,提升系统的稳定性和可追溯性。
This commit is contained in:
parent
dfbdfee596
commit
30f4a22fb4
|
|
@ -554,7 +554,7 @@ class BinanceClient:
|
|||
async def get_klines(self, symbol: str, interval: str = '5m', limit: int = 2) -> List[List]:
|
||||
"""
|
||||
获取K线数据(合约市场)
|
||||
优先从 Redis 缓存读取,如果缓存不可用或过期则使用 REST API
|
||||
优先级:WS 缓存 > Redis 缓存 > REST API
|
||||
|
||||
Args:
|
||||
symbol: 交易对
|
||||
|
|
@ -564,15 +564,30 @@ class BinanceClient:
|
|||
Returns:
|
||||
K线数据列表
|
||||
"""
|
||||
# 先查 Redis 缓存
|
||||
# 1. 优先从 WS 缓存读取(实时更新,无 REST 请求)
|
||||
try:
|
||||
from .kline_stream import get_klines_from_cache, get_kline_stream_instance, is_kline_cache_fresh
|
||||
stream = get_kline_stream_instance()
|
||||
if stream:
|
||||
# 确保订阅该流(首次请求时自动订阅)
|
||||
await stream.subscribe(symbol, interval, limit=max(limit, 50))
|
||||
if is_kline_cache_fresh(symbol, interval, max_age_sec=300.0):
|
||||
ws_cached = get_klines_from_cache(symbol, interval, limit)
|
||||
if ws_cached and len(ws_cached) >= limit:
|
||||
logger.debug(f"从 WS 缓存获取 {symbol} K线数据: {interval} x{limit}")
|
||||
return ws_cached
|
||||
except Exception as e:
|
||||
logger.debug(f"读取 K线 WS 缓存失败: {e}")
|
||||
|
||||
# 2. 查 Redis 缓存
|
||||
cache_key = f"klines:{symbol}:{interval}:{limit}"
|
||||
cached = await self.redis_cache.get(cache_key)
|
||||
if cached:
|
||||
logger.debug(f"从缓存获取 {symbol} K线数据: {interval} x{limit}")
|
||||
logger.debug(f"从 Redis 缓存获取 {symbol} K线数据: {interval} x{limit}")
|
||||
return cached
|
||||
|
||||
# 3. REST API(兜底)
|
||||
try:
|
||||
# 缓存未命中,调用 API
|
||||
klines = await self._rate_limited_request(
|
||||
f'klines_{symbol}_{interval}',
|
||||
self.client.futures_klines(symbol=symbol, interval=interval, limit=limit)
|
||||
|
|
@ -580,22 +595,11 @@ class BinanceClient:
|
|||
|
||||
# 写入 Redis 缓存(根据 interval 动态设置 TTL)
|
||||
if klines:
|
||||
# TTL 设置:1m=10秒, 5m=30秒, 15m=1分钟, 1h=5分钟, 4h=15分钟, 1d=1小时
|
||||
ttl_map = {
|
||||
'1m': 10,
|
||||
'3m': 20,
|
||||
'5m': 30,
|
||||
'15m': 60,
|
||||
'30m': 120,
|
||||
'1h': 300,
|
||||
'2h': 600,
|
||||
'4h': 900,
|
||||
'6h': 1200,
|
||||
'8h': 1800,
|
||||
'12h': 2400,
|
||||
'1d': 3600
|
||||
'1m': 10, '3m': 20, '5m': 30, '15m': 60, '30m': 120,
|
||||
'1h': 300, '2h': 600, '4h': 900, '6h': 1200, '8h': 1800, '12h': 2400, '1d': 3600
|
||||
}
|
||||
ttl = ttl_map.get(interval, 300) # 默认 5 分钟
|
||||
ttl = ttl_map.get(interval, 300)
|
||||
await self.redis_cache.set(cache_key, klines, ttl=ttl)
|
||||
logger.debug(f"已缓存 {symbol} K线数据: {interval} x{limit} (TTL: {ttl}秒)")
|
||||
|
||||
|
|
@ -1352,13 +1356,39 @@ class BinanceClient:
|
|||
# 获取交易对精度信息
|
||||
symbol_info = await self.get_symbol_info(symbol)
|
||||
|
||||
# 获取当前价格以计算名义价值
|
||||
# 获取当前价格以计算名义价值(优先用 bookTicker 估算执行价,提升准确性)
|
||||
if price is None:
|
||||
ticker = await self.get_ticker_24h(symbol)
|
||||
if not ticker:
|
||||
logger.error(f"无法获取 {symbol} 的价格信息")
|
||||
return None
|
||||
current_price = ticker['price']
|
||||
# 优先用最优挂单估算(买单用 askPrice,卖单用 bidPrice)
|
||||
try:
|
||||
from .book_ticker_stream import get_book_ticker
|
||||
book = get_book_ticker(symbol)
|
||||
if book:
|
||||
if side == "BUY":
|
||||
estimated_price = float(book.get("askPrice", 0))
|
||||
else:
|
||||
estimated_price = float(book.get("bidPrice", 0))
|
||||
if estimated_price > 0:
|
||||
current_price = estimated_price
|
||||
logger.debug(f"{symbol} 使用 bookTicker 估算价格: {current_price:.4f} ({side})")
|
||||
else:
|
||||
ticker = await self.get_ticker_24h(symbol)
|
||||
if not ticker:
|
||||
logger.error(f"无法获取 {symbol} 的价格信息")
|
||||
return None
|
||||
current_price = ticker['price']
|
||||
else:
|
||||
ticker = await self.get_ticker_24h(symbol)
|
||||
if not ticker:
|
||||
logger.error(f"无法获取 {symbol} 的价格信息")
|
||||
return None
|
||||
current_price = ticker['price']
|
||||
except Exception as e:
|
||||
logger.debug(f"使用 bookTicker 估算价格失败,回退到 ticker: {e}")
|
||||
ticker = await self.get_ticker_24h(symbol)
|
||||
if not ticker:
|
||||
logger.error(f"无法获取 {symbol} 的价格信息")
|
||||
return None
|
||||
current_price = ticker['price']
|
||||
else:
|
||||
current_price = price
|
||||
|
||||
|
|
@ -1716,10 +1746,19 @@ class BinanceClient:
|
|||
# =========================
|
||||
|
||||
async def futures_create_algo_order(self, params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
symbol = params.get('symbol', 'UNKNOWN')
|
||||
algo_timeout = getattr(config, 'ALGO_ORDER_TIMEOUT_SEC', 30) # 条件单接口易超时,单独加长
|
||||
try:
|
||||
# python-binance 内部会自动补 timestamp / signature
|
||||
res = await self.client._request_futures_api("post", "algoOrder", True, data=params)
|
||||
# python-binance 内部会自动补 timestamp / signature;用较长超时避免 TimeoutError 空消息
|
||||
res = await asyncio.wait_for(
|
||||
self.client._request_futures_api("post", "algoOrder", True, data=params),
|
||||
timeout=algo_timeout,
|
||||
)
|
||||
return res if isinstance(res, dict) else None
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"{symbol} ❌ 创建 Algo 条件单失败: 请求超时({algo_timeout}秒)")
|
||||
logger.error(f" 参数: {params}")
|
||||
return None
|
||||
except BinanceAPIException as e:
|
||||
error_code = e.code if hasattr(e, 'code') else None
|
||||
error_msg = str(e)
|
||||
|
|
@ -1749,8 +1788,8 @@ class BinanceClient:
|
|||
|
||||
return None
|
||||
except Exception as e:
|
||||
symbol = params.get('symbol', 'UNKNOWN')
|
||||
logger.error(f"{symbol} ❌ 创建 Algo 条件单失败: {type(e).__name__}: {e}")
|
||||
err_msg = getattr(e, "message", str(e)) or repr(e)
|
||||
logger.error(f"{symbol} ❌ 创建 Algo 条件单失败: {type(e).__name__}: {err_msg}")
|
||||
logger.error(f" 参数: {params}")
|
||||
import traceback
|
||||
logger.debug(f" 堆栈跟踪: {traceback.format_exc()}")
|
||||
|
|
@ -2007,7 +2046,7 @@ class BinanceClient:
|
|||
logger.error(f"{symbol} ❌ 挂保护单失败({trigger_type}): {error_msg}")
|
||||
logger.error(f" 错误代码: {error_code}")
|
||||
logger.error(f" 触发价格: {stop_price:.8f} (格式化后: {stop_price_str})")
|
||||
logger.error(f" 当前价格: {cp if cp else 'N/A'}")
|
||||
logger.error(f" 当前价格: {cp if cp else '无(已尝试 WS/REST)'}")
|
||||
logger.error(f" 持仓方向: {pd}")
|
||||
logger.error(f" 平仓方向: {close_side}")
|
||||
logger.error(f" 工作类型: {working_type}")
|
||||
|
|
|
|||
181
trading_system/book_ticker_stream.py
Normal file
181
trading_system/book_ticker_stream.py
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
"""
|
||||
最优挂单 WebSocket 流:订阅 !bookTicker,维护全市场最优买/卖价缓存。
|
||||
用于滑点估算、入场价格优化,提升交易执行效果。
|
||||
文档:更新速度 5s,推送所有交易对的最优挂单(最高买单、最低卖单)。
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import Dict, Optional, Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 最优挂单缓存:symbol -> { bidPrice, bidQty, askPrice, askQty, time }
|
||||
_book_ticker_cache: Dict[str, Dict[str, Any]] = {}
|
||||
_book_ticker_updated_at: float = 0.0
|
||||
|
||||
|
||||
def get_book_ticker_cache() -> Dict[str, Dict[str, Any]]:
|
||||
"""返回当前最优挂单缓存。"""
|
||||
return dict(_book_ticker_cache)
|
||||
|
||||
|
||||
def get_book_ticker(symbol: str) -> Optional[Dict[str, Any]]:
|
||||
"""获取指定交易对的最优挂单;无缓存时返回 None。"""
|
||||
return _book_ticker_cache.get(symbol.upper())
|
||||
|
||||
|
||||
def is_book_ticker_cache_fresh(max_age_sec: float = 10.0) -> bool:
|
||||
"""缓存是否在 max_age_sec 秒内更新过且非空。"""
|
||||
if not _book_ticker_cache:
|
||||
return False
|
||||
return (time.monotonic() - _book_ticker_updated_at) <= max_age_sec
|
||||
|
||||
|
||||
def estimate_slippage(symbol: str, side: str, quantity: float) -> Optional[float]:
|
||||
"""
|
||||
估算滑点(USDT):基于最优挂单估算执行价格与标记价格的偏差。
|
||||
|
||||
Args:
|
||||
symbol: 交易对
|
||||
side: BUY/SELL
|
||||
quantity: 下单数量
|
||||
|
||||
Returns:
|
||||
估算滑点(USDT),None 表示无法估算
|
||||
"""
|
||||
ticker = get_book_ticker(symbol)
|
||||
if not ticker:
|
||||
return None
|
||||
try:
|
||||
bid_price = float(ticker.get("bidPrice", 0))
|
||||
ask_price = float(ticker.get("askPrice", 0))
|
||||
bid_qty = float(ticker.get("bidQty", 0))
|
||||
ask_qty = float(ticker.get("askQty", 0))
|
||||
if bid_price <= 0 or ask_price <= 0:
|
||||
return None
|
||||
mid_price = (bid_price + ask_price) / 2
|
||||
if side == "BUY":
|
||||
# 买单:用 askPrice(卖一)估算,滑点 = (askPrice - midPrice) * quantity
|
||||
if ask_qty >= quantity:
|
||||
slippage = (ask_price - mid_price) * quantity
|
||||
else:
|
||||
# 数量超过卖一,需要吃多档,粗略估算
|
||||
slippage = (ask_price - mid_price) * quantity * 1.2
|
||||
else: # SELL
|
||||
# 卖单:用 bidPrice(买一)估算,滑点 = (midPrice - bidPrice) * quantity
|
||||
if bid_qty >= quantity:
|
||||
slippage = (mid_price - bid_price) * quantity
|
||||
else:
|
||||
slippage = (mid_price - bid_price) * quantity * 1.2
|
||||
return slippage
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
class BookTickerStream:
|
||||
"""订阅合约 !bookTicker,持续更新 _book_ticker_cache。无需 listenKey,公开行情。"""
|
||||
|
||||
def __init__(self, testnet: bool = False):
|
||||
self.testnet = testnet
|
||||
self._ws = None
|
||||
self._task: Optional[asyncio.Task] = None
|
||||
self._running = False
|
||||
|
||||
def _ws_url(self) -> str:
|
||||
if self.testnet:
|
||||
return "wss://stream.binancefuture.com/ws/!bookTicker"
|
||||
return "wss://fstream.binance.com/ws/!bookTicker"
|
||||
|
||||
async def start(self) -> bool:
|
||||
if self._running:
|
||||
return True
|
||||
self._running = True
|
||||
self._task = asyncio.create_task(self._run_ws())
|
||||
logger.info("BookTickerStream: 已启动(!bookTicker),用于滑点估算")
|
||||
return True
|
||||
|
||||
async def stop(self):
|
||||
self._running = False
|
||||
if self._task:
|
||||
self._task.cancel()
|
||||
try:
|
||||
await self._task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
self._task = None
|
||||
if self._ws:
|
||||
try:
|
||||
await self._ws.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._ws = None
|
||||
logger.info("BookTickerStream: 已停止")
|
||||
|
||||
async def _run_ws(self):
|
||||
import aiohttp
|
||||
while self._running:
|
||||
url = self._ws_url()
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.ws_connect(
|
||||
url, heartbeat=50, timeout=aiohttp.ClientTimeout(total=15)
|
||||
) as ws:
|
||||
self._ws = ws
|
||||
logger.info("BookTickerStream: WS 已连接")
|
||||
async for msg in ws:
|
||||
if not self._running:
|
||||
break
|
||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||
self._handle_message(msg.data)
|
||||
elif msg.type in (aiohttp.WSMsgType.ERROR, aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSE):
|
||||
break
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
except Exception as e:
|
||||
err_msg = getattr(e, "message", str(e)) or repr(e)
|
||||
err_type = type(e).__name__
|
||||
logger.warning(
|
||||
"BookTickerStream: WS 异常 %s: %s,10s 后重连",
|
||||
err_type, err_msg,
|
||||
exc_info=logger.isEnabledFor(logging.DEBUG),
|
||||
)
|
||||
await asyncio.sleep(10)
|
||||
self._ws = None
|
||||
if not self._running:
|
||||
break
|
||||
|
||||
def _handle_message(self, raw: str):
|
||||
global _book_ticker_cache, _book_ticker_updated_at
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except Exception:
|
||||
return
|
||||
# 可能是单条对象或组合流格式
|
||||
if isinstance(data, dict) and "stream" in data:
|
||||
ticker_data = data.get("data", {})
|
||||
else:
|
||||
ticker_data = data
|
||||
|
||||
if not isinstance(ticker_data, dict) or ticker_data.get("e") != "bookTicker":
|
||||
return
|
||||
|
||||
s = (ticker_data.get("s") or "").strip().upper()
|
||||
if not s or not s.endswith("USDT"):
|
||||
return
|
||||
|
||||
try:
|
||||
_book_ticker_cache[s] = {
|
||||
"symbol": s,
|
||||
"bidPrice": float(ticker_data.get("b", 0)),
|
||||
"bidQty": float(ticker_data.get("B", 0)),
|
||||
"askPrice": float(ticker_data.get("a", 0)),
|
||||
"askQty": float(ticker_data.get("A", 0)),
|
||||
"time": int(ticker_data.get("T", 0)),
|
||||
}
|
||||
except (TypeError, ValueError):
|
||||
return
|
||||
|
||||
_book_ticker_updated_at = time.monotonic()
|
||||
logger.debug(f"BookTickerStream: 已更新 {s} bid={_book_ticker_cache[s]['bidPrice']:.4f} ask={_book_ticker_cache[s]['askPrice']:.4f}")
|
||||
|
|
@ -405,6 +405,8 @@ CONNECTION_RETRIES = int(os.getenv('CONNECTION_RETRIES', '3')) # 连接重试
|
|||
# 仅用于 get_open_positions / get_recent_trades 等只读接口的单次等待时间,不影响下单/止损止盈的快速失败
|
||||
# 调大此值会延长单次请求最大等待时间,在同步/查询持仓时可能阻塞事件循环,影响实时性;保持 60 秒,通过增加重试+退避应对偶发超时
|
||||
READ_ONLY_REQUEST_TIMEOUT = int(os.getenv('READ_ONLY_REQUEST_TIMEOUT', '60'))
|
||||
# 创建 Algo 条件单(止损/止盈)单次请求超时(秒),网络不稳时可适当调大
|
||||
ALGO_ORDER_TIMEOUT_SEC = int(os.getenv('ALGO_ORDER_TIMEOUT_SEC', '30'))
|
||||
# 获取持仓时过滤掉名义价值低于此值的仓位(USDT),与币安仪表板不一致时可调低或设为 0
|
||||
POSITION_MIN_NOTIONAL_USDT = float(os.getenv('POSITION_MIN_NOTIONAL_USDT', '1.0'))
|
||||
|
||||
|
|
|
|||
256
trading_system/kline_stream.py
Normal file
256
trading_system/kline_stream.py
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
"""
|
||||
K线 WebSocket 流:订阅 <symbol>@kline_<interval>,维护K线缓存。
|
||||
供 get_klines 优先使用,替代 REST 拉取,减少超时、实时更新技术指标。
|
||||
文档:推送间隔 250ms,仅推送最新一根K线的更新;x=false 表示K线未完结,x=true 表示已完结。
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import Dict, List, Optional, Any, Tuple
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# K线缓存:{ (symbol, interval): [kline1, kline2, ...] },最多保留 limit 根
|
||||
_kline_cache: Dict[Tuple[str, str], List[List]] = {}
|
||||
_kline_cache_updated_at: Dict[Tuple[str, str], float] = {}
|
||||
_kline_cache_limit: Dict[Tuple[str, str], int] = {} # 每个 (symbol, interval) 的 limit
|
||||
|
||||
|
||||
def get_klines_from_cache(symbol: str, interval: str, limit: int = 50) -> Optional[List[List]]:
|
||||
"""从缓存返回K线数据(与 REST get_klines 格式兼容)。未订阅或数据不足时返回 None。"""
|
||||
key = (symbol.upper(), interval.lower())
|
||||
cached = _kline_cache.get(key)
|
||||
if not cached or len(cached) < limit:
|
||||
return None
|
||||
# 返回最后 limit 根
|
||||
return cached[-limit:]
|
||||
|
||||
|
||||
def is_kline_cache_fresh(symbol: str, interval: str, max_age_sec: float = 300.0) -> bool:
|
||||
"""缓存是否在 max_age_sec 秒内更新过。"""
|
||||
key = (symbol.upper(), interval.lower())
|
||||
updated = _kline_cache_updated_at.get(key, 0)
|
||||
if updated == 0:
|
||||
return False
|
||||
return (time.monotonic() - updated) <= max_age_sec
|
||||
|
||||
|
||||
class KlineStream:
|
||||
"""订阅合约 K线流,持续更新 _kline_cache。支持动态订阅/取消订阅。"""
|
||||
|
||||
def __init__(self, testnet: bool = False):
|
||||
self.testnet = testnet
|
||||
self._ws = None
|
||||
self._task: Optional[asyncio.Task] = None
|
||||
self._running = False
|
||||
self._subscribed: Dict[Tuple[str, str], bool] = {} # (symbol, interval) -> 是否已订阅
|
||||
self._subscription_lock = asyncio.Lock()
|
||||
|
||||
def _ws_base_url(self) -> str:
|
||||
if self.testnet:
|
||||
return "wss://stream.binancefuture.com"
|
||||
return "wss://fstream.binance.com"
|
||||
|
||||
async def start(self) -> bool:
|
||||
global _kline_stream_instance
|
||||
if self._running:
|
||||
return True
|
||||
self._running = True
|
||||
_kline_stream_instance = self
|
||||
self._task = asyncio.create_task(self._run_ws())
|
||||
logger.info("KlineStream: 已启动(支持动态订阅)")
|
||||
return True
|
||||
|
||||
async def stop(self):
|
||||
global _kline_stream_instance
|
||||
self._running = False
|
||||
_kline_stream_instance = None
|
||||
if self._task:
|
||||
self._task.cancel()
|
||||
try:
|
||||
await self._task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
self._task = None
|
||||
if self._ws:
|
||||
try:
|
||||
await self._ws.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._ws = None
|
||||
logger.info("KlineStream: 已停止")
|
||||
|
||||
async def subscribe(self, symbol: str, interval: str, limit: int = 50) -> bool:
|
||||
"""订阅指定 symbol 和 interval 的K线流(若 WS 未连接则等待连接后订阅)。"""
|
||||
symbol = symbol.upper()
|
||||
interval = interval.lower()
|
||||
key = (symbol, interval)
|
||||
async with self._subscription_lock:
|
||||
if self._subscribed.get(key):
|
||||
return True
|
||||
if not self._running:
|
||||
return False
|
||||
# 等待 WS 连接(最多等待 5 秒)
|
||||
for _ in range(50):
|
||||
if self._ws:
|
||||
break
|
||||
await asyncio.sleep(0.1)
|
||||
if not self._ws:
|
||||
return False
|
||||
stream_name = f"{symbol.lower()}@kline_{interval}"
|
||||
try:
|
||||
await self._ws.send_json({
|
||||
"method": "SUBSCRIBE",
|
||||
"params": [stream_name],
|
||||
"id": int(time.time() * 1000) % 1000000,
|
||||
})
|
||||
self._subscribed[key] = True
|
||||
_kline_cache_limit[key] = limit
|
||||
logger.debug(f"KlineStream: 已订阅 {symbol} {interval}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"KlineStream: 订阅 {symbol} {interval} 失败: {e}")
|
||||
return False
|
||||
|
||||
async def _run_ws(self):
|
||||
import aiohttp
|
||||
# 使用组合流,支持动态订阅
|
||||
url = f"{self._ws_base_url()}/stream"
|
||||
while self._running:
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.ws_connect(
|
||||
url, heartbeat=50, timeout=aiohttp.ClientTimeout(total=15)
|
||||
) as ws:
|
||||
self._ws = ws
|
||||
logger.info("KlineStream: WS 已连接(组合流,支持动态订阅)")
|
||||
async for msg in ws:
|
||||
if not self._running:
|
||||
break
|
||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||
raw = msg.data
|
||||
# 处理订阅响应({"result": null, "id": ...})或K线数据
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
if isinstance(data, dict) and "result" in data:
|
||||
# 订阅响应,忽略
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
self._handle_message(raw)
|
||||
elif msg.type in (aiohttp.WSMsgType.ERROR, aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSE):
|
||||
break
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
except Exception as e:
|
||||
err_msg = getattr(e, "message", str(e)) or repr(e)
|
||||
err_type = type(e).__name__
|
||||
logger.warning(
|
||||
"KlineStream: WS 异常 %s: %s,10s 后重连",
|
||||
err_type, err_msg,
|
||||
exc_info=logger.isEnabledFor(logging.DEBUG),
|
||||
)
|
||||
await asyncio.sleep(10)
|
||||
self._ws = None
|
||||
# 重连时清空订阅状态,需要重新订阅
|
||||
async with self._subscription_lock:
|
||||
self._subscribed.clear()
|
||||
if not self._running:
|
||||
break
|
||||
|
||||
def _handle_message(self, raw: str):
|
||||
global _kline_cache, _kline_cache_updated_at
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except Exception:
|
||||
return
|
||||
# 组合流格式:{ "stream": "btcusdt@kline_1h", "data": {...} }
|
||||
if isinstance(data, dict) and "stream" in data:
|
||||
stream = data.get("stream", "")
|
||||
kline_data = data.get("data", {})
|
||||
else:
|
||||
kline_data = data
|
||||
stream = ""
|
||||
|
||||
if not isinstance(kline_data, dict) or kline_data.get("e") != "kline":
|
||||
return
|
||||
|
||||
k = kline_data.get("k")
|
||||
if not isinstance(k, dict):
|
||||
return
|
||||
|
||||
s = (k.get("s") or "").strip().upper()
|
||||
i = (k.get("i") or "").strip().lower()
|
||||
if not s or not i:
|
||||
return
|
||||
|
||||
key = (s, i)
|
||||
if key not in self._subscribed:
|
||||
return
|
||||
|
||||
# 转换为 REST 格式:[open_time, open, high, low, close, volume, close_time, quote_volume, trades, ...]
|
||||
try:
|
||||
t = int(k.get("t", 0))
|
||||
o = float(k.get("o", 0))
|
||||
h = float(k.get("h", 0))
|
||||
l = float(k.get("l", 0))
|
||||
c = float(k.get("c", 0))
|
||||
v = float(k.get("v", 0))
|
||||
T = int(k.get("T", 0))
|
||||
q = float(k.get("q", 0))
|
||||
n = int(k.get("n", 0))
|
||||
x = k.get("x", False) # 是否完结
|
||||
except (TypeError, ValueError):
|
||||
return
|
||||
|
||||
kline_rest_format = [
|
||||
t, # open_time
|
||||
str(o), # open
|
||||
str(h), # high
|
||||
str(l), # low
|
||||
str(c), # close
|
||||
str(v), # volume
|
||||
T, # close_time
|
||||
str(q), # quote_volume
|
||||
n, # trades
|
||||
"0", # taker_buy_base_volume
|
||||
"0", # taker_buy_quote_volume
|
||||
"0", # ignore
|
||||
]
|
||||
|
||||
# 更新缓存:若 x=true(完结),追加新K线;若 x=false(未完结),更新最后一根
|
||||
if key not in _kline_cache:
|
||||
_kline_cache[key] = []
|
||||
cache_list = _kline_cache[key]
|
||||
|
||||
if x:
|
||||
# K线完结:追加新K线
|
||||
cache_list.append(kline_rest_format)
|
||||
limit = _kline_cache_limit.get(key, 50)
|
||||
if len(cache_list) > limit * 2:
|
||||
cache_list[:] = cache_list[-limit:]
|
||||
else:
|
||||
# K线未完结:更新最后一根(或追加第一根)
|
||||
if cache_list:
|
||||
cache_list[-1] = kline_rest_format
|
||||
else:
|
||||
cache_list.append(kline_rest_format)
|
||||
|
||||
_kline_cache_updated_at[key] = time.monotonic()
|
||||
logger.debug(f"KlineStream: 已更新 {s} {i} (完结={x}),缓存 {len(cache_list)} 根")
|
||||
|
||||
|
||||
# 全局 KlineStream 实例
|
||||
_kline_stream_instance: Optional["KlineStream"] = None
|
||||
|
||||
|
||||
def get_kline_stream_instance() -> Optional["KlineStream"]:
|
||||
"""返回当前运行的 KlineStream 实例(未启动时为 None)。"""
|
||||
return _kline_stream_instance
|
||||
|
||||
|
||||
def set_kline_stream_instance(instance: Optional["KlineStream"]):
|
||||
"""设置全局 KlineStream 实例(由 main 调用)。"""
|
||||
global _kline_stream_instance
|
||||
_kline_stream_instance = instance
|
||||
|
|
@ -237,6 +237,8 @@ async def main():
|
|||
client = None
|
||||
user_data_stream = None
|
||||
ticker_24h_stream = None
|
||||
kline_stream = None
|
||||
book_ticker_stream = None
|
||||
try:
|
||||
# 1. 初始化币安客户端
|
||||
logger.info("初始化币安客户端...")
|
||||
|
|
@ -338,6 +340,34 @@ async def main():
|
|||
logger.debug(f"启动 24h ticker WS 失败(将使用 REST): {e}")
|
||||
ticker_24h_stream = None
|
||||
|
||||
# 3.2 启动 K线 WS 流(技术指标实时更新,减少 REST 超时)
|
||||
kline_stream = None
|
||||
try:
|
||||
from .kline_stream import KlineStream
|
||||
use_testnet = getattr(config, "USE_TESTNET", False)
|
||||
kline_stream = KlineStream(testnet=use_testnet)
|
||||
if await kline_stream.start():
|
||||
logger.info("✓ K线 WS 已启动(技术指标将优先使用 WS 缓存,减少超时)")
|
||||
else:
|
||||
kline_stream = None
|
||||
except Exception as e:
|
||||
logger.debug(f"启动 K线 WS 失败(将使用 REST): {e}")
|
||||
kline_stream = None
|
||||
|
||||
# 3.3 启动最优挂单 WS 流(滑点估算,优化入场价格)
|
||||
book_ticker_stream = None
|
||||
try:
|
||||
from .book_ticker_stream import BookTickerStream
|
||||
use_testnet = getattr(config, "USE_TESTNET", False)
|
||||
book_ticker_stream = BookTickerStream(testnet=use_testnet)
|
||||
if await book_ticker_stream.start():
|
||||
logger.info("✓ 最优挂单 WS 已启动(用于滑点估算与价格优化)")
|
||||
else:
|
||||
book_ticker_stream = None
|
||||
except Exception as e:
|
||||
logger.debug(f"启动最优挂单 WS 失败: {e}")
|
||||
book_ticker_stream = None
|
||||
|
||||
# 4. 初始化各个模块
|
||||
logger.info("初始化交易模块...")
|
||||
scanner = MarketScanner(client)
|
||||
|
|
@ -431,6 +461,18 @@ async def main():
|
|||
logger.info("Ticker24h Stream 已停止")
|
||||
except Exception as e:
|
||||
logger.debug(f"停止 Ticker24h Stream 时异常: {e}")
|
||||
try:
|
||||
if kline_stream is not None:
|
||||
await kline_stream.stop()
|
||||
logger.info("Kline Stream 已停止")
|
||||
except Exception as e:
|
||||
logger.debug(f"停止 Kline Stream 时异常: {e}")
|
||||
try:
|
||||
if book_ticker_stream is not None:
|
||||
await book_ticker_stream.stop()
|
||||
logger.info("BookTicker Stream 已停止")
|
||||
except Exception as e:
|
||||
logger.debug(f"停止 BookTicker Stream 时异常: {e}")
|
||||
if client:
|
||||
await client.disconnect()
|
||||
logger.info("程序已退出")
|
||||
|
|
|
|||
|
|
@ -1568,35 +1568,58 @@ class PositionManager:
|
|||
except Exception as e:
|
||||
logger.debug(f"{symbol} 取消旧保护单时出错(可忽略): {e}")
|
||||
|
||||
# 获取当前价格(如果未提供,优先使用标记价格 MARK_PRICE,因为止损单使用 MARK_PRICE)
|
||||
# 获取当前价格(如果未提供):优先 WS 缓存(bookTicker/ticker24h)→ 持仓 markPrice → REST ticker
|
||||
if current_price is None:
|
||||
try:
|
||||
# 优先获取标记价格(MARK_PRICE),因为止损单使用 MARK_PRICE 作为触发基准
|
||||
positions = await self._get_open_positions()
|
||||
position = next((p for p in positions if p['symbol'] == symbol), None)
|
||||
if position:
|
||||
mark_price = position.get('markPrice')
|
||||
if mark_price and float(mark_price) > 0:
|
||||
current_price = float(mark_price)
|
||||
logger.debug(f"{symbol} 从持仓获取标记价格: {current_price}")
|
||||
else:
|
||||
# 如果没有标记价格,使用 ticker 价格
|
||||
# 1) 优先 WS:最优挂单中点价或 24h ticker 缓存(避免 REST 超时)
|
||||
try:
|
||||
try:
|
||||
from .book_ticker_stream import get_book_ticker
|
||||
except ImportError:
|
||||
from book_ticker_stream import get_book_ticker
|
||||
book = get_book_ticker(symbol)
|
||||
if book and float(book.get("bidPrice", 0)) > 0 and float(book.get("askPrice", 0)) > 0:
|
||||
mid = (float(book["bidPrice"]) + float(book["askPrice"])) / 2
|
||||
current_price = mid
|
||||
logger.debug(f"{symbol} 从 bookTicker WS 获取当前价格: {current_price}")
|
||||
except Exception:
|
||||
pass
|
||||
if current_price is None:
|
||||
try:
|
||||
try:
|
||||
from .ticker_24h_stream import get_tickers_24h_cache, is_ticker_24h_cache_fresh
|
||||
except ImportError:
|
||||
from ticker_24h_stream import get_tickers_24h_cache, is_ticker_24h_cache_fresh
|
||||
if is_ticker_24h_cache_fresh(max_age_sec=120):
|
||||
tickers = get_tickers_24h_cache()
|
||||
t = tickers.get(symbol)
|
||||
if t and t.get("price"):
|
||||
current_price = float(t["price"])
|
||||
logger.debug(f"{symbol} 从 ticker24h WS 获取当前价格: {current_price}")
|
||||
except Exception:
|
||||
pass
|
||||
# 2) 持仓标记价格(MARK_PRICE,与止损单触发基准一致)
|
||||
if current_price is None:
|
||||
positions = await self._get_open_positions()
|
||||
position = next((p for p in positions if p['symbol'] == symbol), None)
|
||||
if position:
|
||||
mark_price = position.get('markPrice')
|
||||
if mark_price and float(mark_price) > 0:
|
||||
current_price = float(mark_price)
|
||||
logger.debug(f"{symbol} 从持仓获取标记价格: {current_price}")
|
||||
if current_price is None:
|
||||
ticker = await self.client.get_ticker_24h(symbol)
|
||||
if ticker:
|
||||
current_price = ticker.get('price')
|
||||
logger.debug(f"{symbol} 从ticker获取当前价格: {current_price}")
|
||||
else:
|
||||
# 如果没有持仓,使用 ticker 价格
|
||||
ticker = await self.client.get_ticker_24h(symbol)
|
||||
if ticker:
|
||||
current_price = ticker.get('price')
|
||||
logger.debug(f"{symbol} 从ticker获取当前价格: {current_price}")
|
||||
current_price = ticker.get('price') and float(ticker['price']) or None
|
||||
if current_price:
|
||||
logger.debug(f"{symbol} 从 ticker REST 获取当前价格: {current_price}")
|
||||
except Exception as e:
|
||||
logger.warning(f"{symbol} 获取当前价格失败: {e}")
|
||||
err_msg = getattr(e, "message", str(e)) or repr(e)
|
||||
logger.warning(f"{symbol} 获取当前价格失败: {type(e).__name__}: {err_msg}")
|
||||
|
||||
# 如果仍然没有当前价格,记录警告
|
||||
if current_price is None:
|
||||
logger.warning(f"{symbol} ⚠️ 无法获取当前价格,止损单可能无法正确验证触发条件")
|
||||
logger.warning(f"{symbol} ⚠️ 无法获取当前价格(已尝试 WS bookTicker/ticker24h、持仓、REST),止损单可能无法正确验证触发条件")
|
||||
|
||||
# 在挂止损单前,检查当前价格是否已经触发止损(避免 -2021 错误)
|
||||
if current_price and stop_loss:
|
||||
|
|
@ -1705,7 +1728,7 @@ class PositionManager:
|
|||
else:
|
||||
logger.error(f"{symbol} ❌ 止损单挂单失败!将依赖WebSocket监控,但可能无法及时止损")
|
||||
logger.error(f" 止损价格: {stop_loss:.8f}")
|
||||
logger.error(f" 当前价格: {current_price if current_price else 'N/A'}")
|
||||
logger.error(f" 当前价格: {current_price if current_price else '无(已尝试 WS bookTicker/ticker24h、持仓、REST)'}")
|
||||
logger.error(f" 持仓方向: {side}")
|
||||
logger.error(f" ⚠️ 警告: 没有交易所级别的止损保护,如果系统崩溃或网络中断,可能无法及时止损!")
|
||||
logger.error(f" 💡 建议: 检查止损价格计算是否正确,或手动在币安界面设置止损")
|
||||
|
|
|
|||
338
ws交易接口.txt
Normal file
338
ws交易接口.txt
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
下单 (TRADE)
|
||||
接口描述
|
||||
下单
|
||||
|
||||
方式
|
||||
order.place
|
||||
|
||||
请求
|
||||
order.place
|
||||
|
||||
{
|
||||
"id": "3f7df6e3-2df4-44b9-9919-d2f38f90a99a",
|
||||
"method": "order.place",
|
||||
"params": {
|
||||
"apiKey": "HMOchcfii9ZRZnhjp2XjGXhsOBd6msAhKz9joQaWwZ7arcJTlD2hGPHQj1lGdTjR",
|
||||
"positionSide": "BOTH",
|
||||
"price": 43187.00,
|
||||
"quantity": 0.1,
|
||||
"side": "BUY",
|
||||
"symbol": "BTCUSDT",
|
||||
"timeInForce": "GTC",
|
||||
"timestamp": 1702555533821,
|
||||
"type": "LIMIT",
|
||||
"signature": "0f04368b2d22aafd0ggc8809ea34297eff602272917b5f01267db4efbc1c9422"
|
||||
}
|
||||
}
|
||||
|
||||
请求权重
|
||||
0
|
||||
|
||||
请求参数
|
||||
名称 类型 是否必需 描述
|
||||
symbol STRING YES 交易对
|
||||
side ENUM YES 买卖方向 SELL, BUY
|
||||
positionSide ENUM NO 持仓方向,单向持仓模式下非必填,默认且仅可填BOTH;在双向持仓模式下必填,且仅可选择 LONG 或 SHORT
|
||||
type ENUM YES 订单类型 LIMIT, MARKET, STOP, TAKE_PROFIT, STOP_MARKET, TAKE_PROFIT_MARKET, TRAILING_STOP_MARKET
|
||||
reduceOnly STRING NO true, false; 非双开模式下默认false;双开模式下不接受此参数; 使用closePosition不支持此参数。
|
||||
quantity DECIMAL NO 下单数量,使用closePosition不支持此参数。
|
||||
price DECIMAL NO 委托价格
|
||||
newClientOrderId STRING NO 用户自定义的订单号,不可以重复出现在挂单中。如空缺系统会自动赋值。必须满足正则规则 ^[\.A-Z\:/a-z0-9_-]{1,36}$
|
||||
stopPrice DECIMAL NO 触发价, 仅 STOP, STOP_MARKET, TAKE_PROFIT, TAKE_PROFIT_MARKET 需要此参数
|
||||
closePosition STRING NO true, false;触发后全部平仓,仅支持STOP_MARKET和TAKE_PROFIT_MARKET;不与quantity合用;自带只平仓效果,不与reduceOnly 合用
|
||||
activationPrice DECIMAL NO 追踪止损激活价格,仅TRAILING_STOP_MARKET 需要此参数, 默认为下单当前市场价格(支持不同workingType)
|
||||
callbackRate DECIMAL NO 追踪止损回调比例,可取值范围[0.1, 10],其中 1代表1% ,仅TRAILING_STOP_MARKET 需要此参数
|
||||
timeInForce ENUM NO 有效方法
|
||||
workingType ENUM NO stopPrice 触发类型: MARK_PRICE(标记价格), CONTRACT_PRICE(合约最新价). 默认 CONTRACT_PRICE
|
||||
priceProtect STRING NO 条件单触发保护:"TRUE","FALSE", 默认"FALSE". 仅 STOP, STOP_MARKET, TAKE_PROFIT, TAKE_PROFIT_MARKET 需要此参数
|
||||
newOrderRespType ENUM NO "ACK", "RESULT", 默认 "ACK"
|
||||
priceMatch ENUM NO OPPONENT/ OPPONENT_5/ OPPONENT_10/ OPPONENT_20/QUEUE/ QUEUE_5/ QUEUE_10/ QUEUE_20;不能与price同时传
|
||||
selfTradePreventionMode ENUM NO NONE / EXPIRE_TAKER/ EXPIRE_MAKER/ EXPIRE_BOTH; 默认NONE
|
||||
goodTillDate LONG NO TIF为GTD时订单的自动取消时间, 当timeInforce为GTD时必传;传入的时间戳仅保留秒级精度,毫秒级部分会被自动忽略,时间戳需大于当前时间+600s且小于253402300799000
|
||||
recvWindow LONG NO
|
||||
timestamp LONG YES
|
||||
根据 order type的不同,某些参数强制要求,具体如下:
|
||||
|
||||
Type 强制要求的参数
|
||||
LIMIT timeInForce, quantity, price或priceMatch
|
||||
MARKET quantity
|
||||
STOP, TAKE_PROFIT quantity, stopPrice
|
||||
STOP_MARKET, TAKE_PROFIT_MARKET stopPrice, price或priceMatch
|
||||
TRAILING_STOP_MARKET callbackRate
|
||||
条件单的触发必须:
|
||||
|
||||
如果订单参数priceProtect为true:
|
||||
达到触发价时,MARK_PRICE(标记价格)与CONTRACT_PRICE(合约最新价)之间的价差不能超过改symbol触发保护阈值
|
||||
触发保护阈值请参考接口GET /fapi/v1/exchangeInfo 返回内容相应symbol中"triggerProtect"字段
|
||||
STOP, STOP_MARKET 止损单:
|
||||
买入: 最新合约价格/标记价格高于等于触发价stopPrice
|
||||
卖出: 最新合约价格/标记价格低于等于触发价stopPrice
|
||||
TAKE_PROFIT, TAKE_PROFIT_MARKET 止盈单:
|
||||
买入: 最新合约价格/标记价格低于等于触发价stopPrice
|
||||
卖出: 最新合约价格/标记价格高于等于触发价stopPrice
|
||||
TRAILING_STOP_MARKET 跟踪止损单:
|
||||
买入: 当合约价格/标记价格区间最低价格低于激活价格activationPrice,且最新合约价格/标记价高于等于最低价设定回调幅度。
|
||||
卖出: 当合约价格/标记价格区间最高价格高于激活价格activationPrice,且最新合约价格/标记价低于等于最高价设定回调幅度。
|
||||
TRAILING_STOP_MARKET 跟踪止损单如果遇到报错 {"code": -2021, "msg": "Order would immediately trigger."}
|
||||
表示订单不满足以下条件:
|
||||
|
||||
买入: 指定的activationPrice 必须小于 latest price
|
||||
卖出: 指定的activationPrice 必须大于 latest price
|
||||
newOrderRespType 如果传 RESULT:
|
||||
|
||||
MARKET 订单将直接返回成交结果;
|
||||
配合使用特殊 timeInForce 的 LIMIT 订单将直接返回成交或过期拒绝结果。
|
||||
STOP_MARKET, TAKE_PROFIT_MARKET 配合 closePosition=true:
|
||||
|
||||
条件单触发依照上述条件单触发逻辑
|
||||
条件触发后,平掉当时持有所有多头仓位(若为卖单)或当时持有所有空头仓位(若为买单)
|
||||
不支持 quantity 参数
|
||||
自带只平仓属性,不支持reduceOnly参数
|
||||
双开模式下,LONG方向上不支持BUY; SHORT 方向上不支持SELL
|
||||
响应示例
|
||||
{
|
||||
"id": "3f7df6e3-2df4-44b9-9919-d2f38f90a99a",
|
||||
"status": 200,
|
||||
"result": {
|
||||
"orderId": 325078477,
|
||||
"symbol": "BTCUSDT",
|
||||
"status": "NEW",
|
||||
"clientOrderId": "iCXL1BywlBaf2sesNUrVl3",
|
||||
"price": "43187.00",
|
||||
"avgPrice": "0.00",
|
||||
"origQty": "0.100",
|
||||
"executedQty": "0.000",
|
||||
"cumQty": "0.000",
|
||||
"cumQuote": "0.00000",
|
||||
"timeInForce": "GTC",
|
||||
"type": "LIMIT",
|
||||
"reduceOnly": false,
|
||||
"closePosition": false,
|
||||
"side": "BUY",
|
||||
"positionSide": "BOTH",
|
||||
"stopPrice": "0.00",
|
||||
"workingType": "CONTRACT_PRICE",
|
||||
"priceProtect": false,
|
||||
"origType": "LIMIT",
|
||||
"priceMatch": "NONE",
|
||||
"selfTradePreventionMode": "NONE",
|
||||
"goodTillDate": 0,
|
||||
"updateTime": 1702555534435
|
||||
},
|
||||
"rateLimits": [
|
||||
{
|
||||
"rateLimitType": "ORDERS",
|
||||
"interval": "SECOND",
|
||||
"intervalNum": 10,
|
||||
"limit": 300,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"rateLimitType": "ORDERS",
|
||||
"interval": "MINUTE",
|
||||
"intervalNum": 1,
|
||||
"limit": 1200,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"rateLimitType": "REQUEST_WEIGHT",
|
||||
"interval": "MINUTE",
|
||||
"intervalNum": 1,
|
||||
"limit": 2400,
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
查询订单
|
||||
查询订单 (USER_DATA)
|
||||
接口描述
|
||||
查询订单状态
|
||||
|
||||
请注意,如果订单满足如下条件,不会被查询到:
|
||||
订单的最终状态为 CANCELED 或者 EXPIRED 并且 订单没有任何的成交记录 并且 订单生成时间 + 3天 < 当前时间
|
||||
订单创建时间 + 90天 < 当前时间
|
||||
方式
|
||||
order.status
|
||||
|
||||
请求
|
||||
{
|
||||
"id": "0ce5d070-a5e5-4ff2-b57f-1556741a4204",
|
||||
"method": "order.status",
|
||||
"params": {
|
||||
"apiKey": "HMOchcfii9ZRZnhjp2XjGXhsOBd6msAhKz9joQaWwZ7arcJTlD2hGPHQj1lGdTjR",
|
||||
"orderId": 328999071,
|
||||
"symbol": "BTCUSDT",
|
||||
"timestamp": 1703441060152,
|
||||
"signature": "ba48184fc38a71d03d2b5435bd67c1206e3191e989fe99bda1bc643a880dfdbf"
|
||||
}
|
||||
}
|
||||
|
||||
请求权重
|
||||
1
|
||||
|
||||
请求参数
|
||||
名称 类型 是否必需 描述
|
||||
symbol STRING YES 交易对
|
||||
orderId LONG NO 系统订单号
|
||||
origClientOrderId STRING NO 用户自定义的订单号
|
||||
recvWindow LONG NO
|
||||
timestamp LONG YES
|
||||
注意:
|
||||
|
||||
至少需要发送 orderId 与 origClientOrderId中的一个
|
||||
orderId在symbol维度是自增的
|
||||
响应示例
|
||||
{
|
||||
"avgPrice": "0.00000", // 平均成交价
|
||||
"clientOrderId": "abc", // 用户自定义的订单号
|
||||
"cumQuote": "0", // 成交金额
|
||||
"executedQty": "0", // 成交量
|
||||
"orderId": 1573346959, // 系统订单号
|
||||
"origQty": "0.40", // 原始委托数量
|
||||
"origType": "TRAILING_STOP_MARKET", // 触发前订单类型
|
||||
"price": "0", // 委托价格
|
||||
"reduceOnly": false, // 是否仅减仓
|
||||
"side": "BUY", // 买卖方向
|
||||
"positionSide": "SHORT", // 持仓方向
|
||||
"status": "NEW", // 订单状态
|
||||
"stopPrice": "9300", // 触发价,对`TRAILING_STOP_MARKET`无效
|
||||
"closePosition": false, // 是否条件全平仓
|
||||
"symbol": "BTCUSDT", // 交易对
|
||||
"time": 1579276756075, // 订单时间
|
||||
"timeInForce": "GTC", // 有效方法
|
||||
"type": "TRAILING_STOP_MARKET", // 订单类型
|
||||
"activatePrice": "9020", // 跟踪止损激活价格, 仅`TRAILING_STOP_MARKET` 订单返回此字段
|
||||
"priceRate": "0.3", // 跟踪止损回调比例, 仅`TRAILING_STOP_MARKET` 订单返回此字段
|
||||
"updateTime": 1579276756075, // 更新时间
|
||||
"workingType": "CONTRACT_PRICE", // 条件价格触发类型
|
||||
"priceProtect": false // 是否开启条件单触发保护
|
||||
}
|
||||
|
||||
|
||||
条件单下单 (TRADE)
|
||||
接口描述
|
||||
条件单下单
|
||||
|
||||
方式
|
||||
algoOrder.place
|
||||
|
||||
请求
|
||||
{
|
||||
"id": "7731f6b5-8d5e-419c-a424-016b0a5fe8d7",
|
||||
"method": "algoOrder.place",
|
||||
"params": {
|
||||
"algoType": "CONDITIONAL",
|
||||
"apiKey": "autoApiKey7mM4kPWaRuTUypdTEZKG8U8tDjO64xdBJBrmE1nXU2XSwdxGPyXcYx",
|
||||
"newOrderRespType": "RESULT",
|
||||
"positionSide": "SHORT",
|
||||
"price": "160000",
|
||||
"quantity": "1",
|
||||
"recvWindow": "99999999",
|
||||
"side": "SELL",
|
||||
"symbol": "BTCUSDT",
|
||||
"timeInForce": "GTC",
|
||||
"timestamp": 1762506268690,
|
||||
"triggerprice": 120000,
|
||||
"type": "TAKE_PROFIT",
|
||||
"signature": "ec6e529c69fd8193b19484907bc713114eae06259fcab9728dafd5910f9cac5a"
|
||||
}
|
||||
}
|
||||
|
||||
请求权重
|
||||
0
|
||||
|
||||
请求参数
|
||||
名称 类型 是否必需 描述
|
||||
algoType ENUM YES 仅支持 CONDITIONAL
|
||||
symbol STRING YES 交易对
|
||||
side ENUM YES 买卖方向 SELL, BUY
|
||||
positionSide ENUM NO 持仓方向,单向持仓模式下非必填,默认且仅可填BOTH;在双向持仓模式下必填,且仅可选择 LONG 或 SHORT
|
||||
type ENUM YES 条件订单类型 STOP, TAKE_PROFIT, STOP_MARKET, TAKE_PROFIT_MARKET, TRAILING_STOP_MARKET
|
||||
timeInForce ENUM NO IOC or GTC or FOK, 默认 GTC
|
||||
quantity DECIMAL NO 下单数量,使用closePosition不支持此参数。
|
||||
price DECIMAL NO 委托价格
|
||||
triggerPrice DECIMAL NO 触发价
|
||||
workingType ENUM NO 触发类型: MARK_PRICE(标记价格), CONTRACT_PRICE(合约最新价). 默认 CONTRACT_PRICE
|
||||
priceMatch ENUM NO OPPONENT/ OPPONENT_5/ OPPONENT_10/ OPPONENT_20/QUEUE/ QUEUE_5/ QUEUE_10/ QUEUE_20;不能与price同时传
|
||||
closePosition STRING NO true, false;触发后全部平仓,仅支持STOP_MARKET和TAKE_PROFIT_MARKET;不与quantity合用;自带只平仓效果,不与reduceOnly 合用
|
||||
priceProtect STRING NO 条件单触发保护:"TRUE","FALSE", 默认"FALSE".
|
||||
reduceOnly STRING NO true, false; 非双开模式下默认false;双开模式下不接受此参数; 使用closePosition不支持此参数。
|
||||
activatePrice DECIMAL NO 追踪止损激活价格,仅TRAILING_STOP_MARKET 需要此参数, 默认为下单当前市场价格(支持不同workingType)
|
||||
callbackRate DECIMAL NO 追踪止损回调比例,可取值范围[0.1, 10],其中 1代表1% ,仅TRAILING_STOP_MARKET 需要此参数
|
||||
clientAlgoId STRING NO 用户自定义的条件订单号,不可以重复出现在挂单中。如空缺系统会自动赋值。必须满足正则规则 ^[\.A-Z\:/a-z0-9_-]{1,36}$
|
||||
newOrderRespType ENUM NO "ACK", "RESULT", 默认 "ACK"
|
||||
selfTradePreventionMode ENUM NO EXPIRE_TAKER/ EXPIRE_MAKER/ EXPIRE_BOTH; 默认NONE
|
||||
goodTillDate LONG NO TIF为GTD时订单的自动取消时间, 当timeInforce为GTD时必传;传入的时间戳仅保留秒级精度,毫秒级部分会被自动忽略,时间戳需大于当前时间+600s且小于253402300799000
|
||||
recvWindow LONG NO
|
||||
timestamp LONG YES
|
||||
条件单的触发必须:
|
||||
|
||||
如果订单参数priceProtect为true:
|
||||
达到触发价时,MARK_PRICE(标记价格)与CONTRACT_PRICE(合约最新价)之间的价差不能超过改symbol触发保护阈值
|
||||
触发保护阈值请参考接口GET /fapi/v1/exchangeInfo 返回内容相应symbol中"triggerProtect"字段
|
||||
STOP, STOP_MARKET 止损单:
|
||||
买入: 最新合约价格/标记价格高于等于触发价stopPrice
|
||||
卖出: 最新合约价格/标记价格低于等于触发价stopPrice
|
||||
TAKE_PROFIT, TAKE_PROFIT_MARKET 止盈单:
|
||||
买入: 最新合约价格/标记价格低于等于触发价stopPrice
|
||||
卖出: 最新合约价格/标记价格高于等于触发价stopPrice
|
||||
TRAILING_STOP_MARKET 跟踪止损单:
|
||||
买入: 当合约价格/标记价格区间最低价格低于激活价格activatePrice,且最新合约价格/标记价高于等于最低价设定回调幅度。
|
||||
卖出: 当合约价格/标记价格区间最高价格高于激活价格activatePrice,且最新合约价格/标记价低于等于最高价设定回调幅度。
|
||||
TRAILING_STOP_MARKET 跟踪止损单如果遇到报错 {"code": -2021, "msg": "Order would immediately trigger."}
|
||||
表示订单不满足以下条件:
|
||||
|
||||
买入: 指定的activatePrice 必须小于 latest price
|
||||
卖出: 指定的activatePrice 必须大于 latest price
|
||||
STOP_MARKET, TAKE_PROFIT_MARKET 配合 closePosition=true:
|
||||
|
||||
条件单触发依照上述条件单触发逻辑
|
||||
条件触发后,平掉当时持有所有多头仓位(若为卖单)或当时持有所有空头仓位(若为买单)
|
||||
不支持 quantity 参数
|
||||
自带只平仓属性,不支持reduceOnly参数
|
||||
双开模式下,LONG方向上不支持BUY; SHORT 方向上不支持SELL
|
||||
selfTradePreventionMode 仅在 timeInForce为IOC或GTC或GTD时生效.
|
||||
|
||||
响应示例
|
||||
{
|
||||
"id": "06c9dbd8-ccbf-4ecf-a29c-fe31495ac73f",
|
||||
"status": 200,
|
||||
"result": {
|
||||
"algoId": 3000000000003505,
|
||||
"clientAlgoId": "0Xkl1p621E4EryvufmYre1",
|
||||
"algoType": "CONDITIONAL",
|
||||
"orderType": "TAKE_PROFIT",
|
||||
"symbol": "BTCUSDT",
|
||||
"side": "SELL",
|
||||
"positionSide": "SHORT",
|
||||
"timeInForce": "GTC",
|
||||
"quantity": "1.000",
|
||||
"algoStatus": "NEW",
|
||||
"triggerPrice": "120000.00",
|
||||
"price": "160000.00",
|
||||
"icebergQuantity": null,
|
||||
"selfTradePreventionMode": "EXPIRE_MAKER",
|
||||
"workingType": "CONTRACT_PRICE",
|
||||
"priceMatch": "NONE",
|
||||
"closePosition": false,
|
||||
"priceProtect": false,
|
||||
"reduceOnly": false,
|
||||
"createTime": 1762507264142,
|
||||
"updateTime": 1762507264143,
|
||||
"triggerTime": 0,
|
||||
"goodTillDate": 0
|
||||
},
|
||||
"rateLimits": [
|
||||
{
|
||||
"rateLimitType": "REQUEST_WEIGHT",
|
||||
"interval": "MINUTE",
|
||||
"intervalNum": 1,
|
||||
"limit": 2400,
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
318
ws行情推送.txt
Normal file
318
ws行情推送.txt
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
全市场最优挂单信息
|
||||
数据流描述
|
||||
所有交易对交易对最优挂单信息
|
||||
|
||||
Stream Name
|
||||
!bookTicker
|
||||
|
||||
注意: 响应消息不包含RPI订单,其不可见。
|
||||
|
||||
更新速度
|
||||
5s
|
||||
|
||||
响应示例
|
||||
{
|
||||
"e":"bookTicker", // 事件类型
|
||||
"u":400900217, // 更新ID
|
||||
"E": 1568014460893, // 事件推送时间
|
||||
"T": 1568014460891, // 撮合时间
|
||||
"s":"BNBUSDT", // 交易对
|
||||
"b":"25.35190000", // 买单最优挂单价格
|
||||
"B":"31.21000000", // 买单最优挂单数量
|
||||
"a":"25.36520000", // 卖单最优挂单价格
|
||||
"A":"40.66000000" // 卖单最优挂单数量
|
||||
}
|
||||
|
||||
|
||||
市场数据连接
|
||||
本篇所列出的所有wss接口需用下列方式连接:
|
||||
|
||||
Base Url:wss://fstream.binance.com
|
||||
订阅单一stream格式为 /ws/<streamName>
|
||||
组合streams的URL格式为 /stream?streams=/<streamName1>/<streamName2>/<streamName3>
|
||||
连接样例:
|
||||
wss://fstream.binance.com/ws/bnbusdt@aggTrade
|
||||
wss://fstream.binance.com/stream?streams=bnbusdt@aggTrade/btcusdt@markPrice
|
||||
订阅组合streams时,事件payload会以这样的格式封装 {"stream":"<streamName<","data":<rawPayload>}
|
||||
|
||||
stream名称中所有交易对均为小写。
|
||||
|
||||
每个链接有效期不超过24小时,请妥善处理断线重连。
|
||||
|
||||
服务端每3分钟会发送ping帧,客户端应当在10分钟内回复pong帧,否则服务端会主动断开链接。允许客户端发送不成对的pong帧(即客户端可以以高于15分钟每次的频率发送pong帧保持链接)。
|
||||
|
||||
Websocket服务器每秒最多接受10个订阅消息。
|
||||
|
||||
如果用户发送的消息超过限制,连接会被断开连接。反复被断开连接的IP有可能被服务器屏蔽。
|
||||
|
||||
单个连接最多可以订阅 1024 个Streams。
|
||||
|
||||
|
||||
|
||||
实时订阅/取消数据流
|
||||
以下数据可以通过websocket发送以实现订阅或取消订阅数据流。示例如下。
|
||||
响应内容中的id是无符号整数,作为往来信息的唯一标识。
|
||||
订阅一个信息流
|
||||
请求
|
||||
|
||||
{
|
||||
"method": "SUBSCRIBE",
|
||||
"params":
|
||||
[
|
||||
"btcusdt@aggTrade",
|
||||
"btcusdt@depth"
|
||||
],
|
||||
"id": 1
|
||||
}
|
||||
|
||||
响应
|
||||
|
||||
{
|
||||
"result": null,
|
||||
"id": 1
|
||||
}
|
||||
|
||||
取消订阅一个信息流
|
||||
请求
|
||||
|
||||
{
|
||||
"method": "UNSUBSCRIBE",
|
||||
"params":
|
||||
[
|
||||
"btcusdt@depth"
|
||||
],
|
||||
"id": 312
|
||||
}
|
||||
|
||||
响应
|
||||
|
||||
{
|
||||
"result": null,
|
||||
"id": 312
|
||||
}
|
||||
|
||||
已订阅信息流
|
||||
请求
|
||||
|
||||
{
|
||||
"method": "LIST_SUBSCRIPTIONS",
|
||||
"id": 3
|
||||
}
|
||||
|
||||
响应
|
||||
|
||||
{
|
||||
"result": [
|
||||
"btcusdt@aggTrade"
|
||||
],
|
||||
"id": 3
|
||||
}
|
||||
|
||||
设定属性
|
||||
当前,唯一可以设置的属性是设置是否启用combined("组合")信息流。
|
||||
当使用/ws/("原始信息流")进行连接时,combined属性设置为false,而使用 /stream/进行连接时则将属性设置为true。
|
||||
|
||||
请求
|
||||
|
||||
{
|
||||
"method": "SET_PROPERTY",
|
||||
"params":
|
||||
[
|
||||
"combined",
|
||||
true
|
||||
],
|
||||
"id": 5
|
||||
}
|
||||
|
||||
响应
|
||||
|
||||
{
|
||||
"result": null
|
||||
"id": 5
|
||||
}
|
||||
|
||||
检索属性
|
||||
请求
|
||||
|
||||
{
|
||||
"method": "GET_PROPERTY",
|
||||
"params":
|
||||
[
|
||||
"combined"
|
||||
],
|
||||
"id": 2
|
||||
}
|
||||
|
||||
响应
|
||||
|
||||
{
|
||||
"result": true, // Indicates that combined is set to true.
|
||||
"id": 2
|
||||
}
|
||||
|
||||
错误信息
|
||||
错误信息 描述
|
||||
{"code": 0, "msg": "Unknown property"} SET_PROPERTY 或 GET_PROPERTY中应用的参数无效
|
||||
{"code": 1, "msg": "Invalid value type: expected Boolean"} 仅接受true或false
|
||||
{"code": 2, "msg": "Invalid request: property name must be a string"} 提供的属性名无效
|
||||
{"code": 2, "msg": "Invalid request: request ID must be an unsigned integer"} 参数id未提供或id值是无效类型
|
||||
{"code": 2, "msg": "Invalid request: unknown variant %s, expected one of SUBSCRIBE, UNSUBSCRIBE, LIST_SUBSCRIPTIONS, SET_PROPERTY, GET_PROPERTY at line 1 column 28"} 错字提醒,或提供的值不是预期类型
|
||||
{"code": 2, "msg": "Invalid request: too many parameters"} 数据中提供了不必要参数
|
||||
{"code": 2, "msg": "Invalid request: property name must be a string"} 未提供属性名
|
||||
{"code": 2, "msg": "Invalid request: missing field method at line 1 column 73"} 数据未提供method
|
||||
{"code":3,"msg":"Invalid JSON: expected value at line %s column %s"} JSON 语法有误.
|
||||
|
||||
|
||||
|
||||
按Symbol的完整Ticker
|
||||
数据流描述
|
||||
按Symbol刷新的24小时完整ticker信息
|
||||
|
||||
Stream Name
|
||||
<symbol>@ticker
|
||||
|
||||
更新速度
|
||||
2000ms
|
||||
|
||||
响应示例
|
||||
{
|
||||
"e": "24hrTicker", // 事件类型
|
||||
"E": 123456789, // 事件时间
|
||||
"s": "BNBUSDT", // 交易对
|
||||
"p": "0.0015", // 24小时价格变化
|
||||
"P": "250.00", // 24小时价格变化(百分比)
|
||||
"w": "0.0018", // 平均价格
|
||||
"c": "0.0025", // 最新成交价格
|
||||
"Q": "10", // 最新成交价格上的成交量
|
||||
"o": "0.0010", // 24小时内第一比成交的价格
|
||||
"h": "0.0025", // 24小时内最高成交价
|
||||
"l": "0.0010", // 24小时内最低成交价
|
||||
"v": "10000", // 24小时内成交量
|
||||
"q": "18", // 24小时内成交额
|
||||
"O": 0, // 统计开始时间
|
||||
"C": 86400000, // 统计关闭时间
|
||||
"F": 0, // 24小时内第一笔成交交易ID
|
||||
"L": 18150, // 24小时内最后一笔成交交易ID
|
||||
"n": 18151 // 24小时内成交数
|
||||
}
|
||||
|
||||
|
||||
K线
|
||||
Stream Description
|
||||
K线stream逐秒推送所请求的K线种类(最新一根K线)的更新。推送间隔250毫秒(如有刷新)
|
||||
|
||||
订阅 Kline 需要提供间隔参数,最短为分钟线,最长为月线。支持以下间隔:
|
||||
|
||||
m -> 分钟; h -> 小时; d -> 天; w -> 周; M -> 月
|
||||
|
||||
1m
|
||||
3m
|
||||
5m
|
||||
15m
|
||||
30m
|
||||
1h
|
||||
2h
|
||||
4h
|
||||
6h
|
||||
8h
|
||||
12h
|
||||
1d
|
||||
3d
|
||||
1w
|
||||
1M
|
||||
Stream Name
|
||||
<symbol>@kline_<interval>
|
||||
|
||||
Update Speed
|
||||
250ms
|
||||
|
||||
Response Example
|
||||
{
|
||||
"e": "kline", // 事件类型
|
||||
"E": 123456789, // 事件时间
|
||||
"s": "BNBUSDT", // 交易对
|
||||
"k": {
|
||||
"t": 123400000, // 这根K线的起始时间
|
||||
"T": 123460000, // 这根K线的结束时间
|
||||
"s": "BNBUSDT", // 交易对
|
||||
"i": "1m", // K线间隔
|
||||
"f": 100, // 这根K线期间第一笔成交ID
|
||||
"L": 200, // 这根K线期间末一笔成交ID
|
||||
"o": "0.0010", // 这根K线期间第一笔成交价
|
||||
"c": "0.0020", // 这根K线期间末一笔成交价
|
||||
"h": "0.0025", // 这根K线期间最高成交价
|
||||
"l": "0.0015", // 这根K线期间最低成交价
|
||||
"v": "1000", // 这根K线期间成交量
|
||||
"n": 100, // 这根K线期间成交笔数
|
||||
"x": false, // 这根K线是否完结(是否已经开始下一根K线)
|
||||
"q": "1.0000", // 这根K线期间成交额
|
||||
"V": "500", // 主动买入的成交量
|
||||
"Q": "0.500", // 主动买入的成交额
|
||||
"B": "123456" // 忽略此参数
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
连续合约K线
|
||||
数据流描述
|
||||
K线stream逐秒推送所请求的K线种类(最新一根K线)的更新。
|
||||
|
||||
合约类型:
|
||||
|
||||
perpetual 永续合约
|
||||
current_quarter 当季交割合约
|
||||
next_quarter 次季交割合约
|
||||
tradifi_perpetual 传统金融合约
|
||||
订阅Kline需要提供间隔参数,最短为分钟线,最长为月线。支持以下间隔:
|
||||
|
||||
s -> 秒; m -> 分钟; h -> 小时; d -> 天; w -> 周; M -> 月
|
||||
|
||||
1s
|
||||
1m
|
||||
3m
|
||||
5m
|
||||
15m
|
||||
30m
|
||||
1h
|
||||
2h
|
||||
4h
|
||||
6h
|
||||
8h
|
||||
12h
|
||||
1d
|
||||
3d
|
||||
1w
|
||||
1M
|
||||
Stream Name
|
||||
<pair>_<contractType>@continuousKline_<interval>
|
||||
|
||||
更新速度
|
||||
250ms
|
||||
|
||||
响应示例
|
||||
{
|
||||
"e":"continuous_kline", // 事件类型
|
||||
"E":1607443058651, // 事件时间
|
||||
"ps":"BTCUSDT", // 标的交易对
|
||||
"ct":"PERPETUAL", // 合约类型
|
||||
"k":{
|
||||
"t":1607443020000, // 这根K线的起始时间
|
||||
"T":1607443079999, // 这根K线的结束时间
|
||||
"i":"1m", // K线间隔
|
||||
"f":116467658886, // 这根K线期间第一笔更新ID
|
||||
"L":116468012423, // 这根K线期间末一笔更新ID
|
||||
"o":"18787.00", // 这根K线期间第一笔成交价
|
||||
"c":"18804.04", // 这根K线期间末一笔成交价
|
||||
"h":"18804.04", // 这根K线期间最高成交价
|
||||
"l":"18786.54", // 这根K线期间最低成交价
|
||||
"v":"197.664", // 这根K线期间成交量
|
||||
"n":543, // 这根K线期间成交笔数
|
||||
"x":false, // 这根K线是否完结(是否已经开始下一根K线)
|
||||
"q":"3715253.19494", // 这根K线期间成交额
|
||||
"V":"184.769", // 主动买入的成交量
|
||||
"Q":"3472925.84746", // 主动买入的成交额
|
||||
"B":"0" // 忽略此参数
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
206
行情ws接口.txt
Normal file
206
行情ws接口.txt
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
深度信息
|
||||
接口描述
|
||||
获取有限档订单薄信息
|
||||
|
||||
方式
|
||||
depth
|
||||
|
||||
注意: 响应消息不包含RPI订单,其不可见。
|
||||
|
||||
请求
|
||||
{
|
||||
"id": "51e2affb-0aba-4821-ba75-f2625006eb43",
|
||||
"method": "depth",
|
||||
"params": {
|
||||
"symbol": "BTCUSDT"
|
||||
}
|
||||
}
|
||||
|
||||
请求权重
|
||||
limit 权重
|
||||
5, 10, 20, 50 2
|
||||
100 5
|
||||
500 10
|
||||
1000 20
|
||||
请求参数
|
||||
名称 类型 是否必需 描述
|
||||
symbol STRING YES 交易对
|
||||
limit INT NO 默认 500; 可选值:[5, 10, 20, 50, 100, 500, 1000]
|
||||
响应示例
|
||||
{
|
||||
"id": "51e2affb-0aba-4821-ba75-f2625006eb43",
|
||||
"status": 200,
|
||||
"result": {
|
||||
"lastUpdateId": 1027024,
|
||||
"E": 1589436922972, // 消息时间
|
||||
"T": 1589436922959, // 撮合引擎时间
|
||||
"bids": [ // 买单
|
||||
[
|
||||
"4.00000000", // 价格
|
||||
"431.00000000" // 数量
|
||||
]
|
||||
],
|
||||
"asks": [ // 卖单
|
||||
[
|
||||
"4.00000200", // 价格
|
||||
"12.00000000" // 数量
|
||||
]
|
||||
]
|
||||
},
|
||||
"rateLimits": [
|
||||
{
|
||||
"rateLimitType": "REQUEST_WEIGHT",
|
||||
"interval": "MINUTE",
|
||||
"intervalNum": 1,
|
||||
"limit": 2400,
|
||||
"count": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
最新价格
|
||||
接口描述
|
||||
返回最近价格
|
||||
|
||||
方式
|
||||
ticker.price
|
||||
|
||||
请求
|
||||
{
|
||||
"id": "9d32157c-a556-4d27-9866-66760a174b57",
|
||||
"method": "ticker.price",
|
||||
"params": {
|
||||
"symbol": "BTCUSDT"
|
||||
}
|
||||
}
|
||||
|
||||
请求权重
|
||||
单交易对1,无交易对2
|
||||
|
||||
请求参数
|
||||
名称 类型 是否必需 描述
|
||||
symbol STRING NO 交易对
|
||||
不发送交易对参数,则会返回所有交易对信息
|
||||
响应示例
|
||||
{
|
||||
"id": "9d32157c-a556-4d27-9866-66760a174b57",
|
||||
"status": 200,
|
||||
"result": {
|
||||
"symbol": "BTCUSDT",
|
||||
"price": "6000.01",
|
||||
"time": 1589437530011
|
||||
},
|
||||
"rateLimits": [
|
||||
{
|
||||
"rateLimitType": "REQUEST_WEIGHT",
|
||||
"interval": "MINUTE",
|
||||
"intervalNum": 1,
|
||||
"limit": 2400,
|
||||
"count": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
或(当不发送symbol)
|
||||
|
||||
{
|
||||
"id": "9d32157c-a556-4d27-9866-66760a174b57",
|
||||
"status": 200,
|
||||
"result": [
|
||||
{
|
||||
"symbol": "BTCUSDT",
|
||||
"price": "6000.01",
|
||||
"time": 1589437530011
|
||||
}
|
||||
],
|
||||
"rateLimits": [
|
||||
{
|
||||
"rateLimitType": "REQUEST_WEIGHT",
|
||||
"interval": "MINUTE",
|
||||
"intervalNum": 1,
|
||||
"limit": 2400,
|
||||
"count": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
当前最优挂单
|
||||
接口描述
|
||||
返回当前最优的挂单(最高买单,最低卖单)
|
||||
|
||||
方式
|
||||
ticker.book
|
||||
|
||||
注意: 响应消息不包含RPI订单,其不可见。
|
||||
|
||||
请求
|
||||
{
|
||||
"id": "9d32157c-a556-4d27-9866-66760a174b57",
|
||||
"method": "ticker.book",
|
||||
"params": {
|
||||
"symbol": "BTCUSDT"
|
||||
}
|
||||
}
|
||||
|
||||
请求权重
|
||||
单交易对2,无交易对5
|
||||
|
||||
请求参数
|
||||
名称 类型 是否必需 描述
|
||||
symbol STRING NO 交易对
|
||||
不发送交易对参数,则会返回所有交易对信息
|
||||
该接口返回头中的X-MBX-USED-WEIGHT-1M参数不准确,可以忽略
|
||||
响应示例
|
||||
{
|
||||
"id": "9d32157c-a556-4d27-9866-66760a174b57",
|
||||
"status": 200,
|
||||
"result": {
|
||||
"lastUpdateId": 1027024,
|
||||
"symbol": "BTCUSDT", // 交易对
|
||||
"bidPrice": "4.00000000", //最优买单价
|
||||
"bidQty": "431.00000000", //挂单量
|
||||
"askPrice": "4.00000200", //最优卖单价
|
||||
"askQty": "9.00000000", //挂单量
|
||||
"time": 1589437530011 // 撮合引擎时间
|
||||
},
|
||||
"rateLimits": [
|
||||
{
|
||||
"rateLimitType": "REQUEST_WEIGHT",
|
||||
"interval": "MINUTE",
|
||||
"intervalNum": 1,
|
||||
"limit": 2400,
|
||||
"count": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
或(当不发送symbol)
|
||||
|
||||
{
|
||||
"id": "9d32157c-a556-4d27-9866-66760a174b57",
|
||||
"status": 200,
|
||||
"result": [
|
||||
{
|
||||
"lastUpdateId": 1027024,
|
||||
"symbol": "BTCUSDT", // 交易对
|
||||
"bidPrice": "4.00000000", //最优买单价
|
||||
"bidQty": "431.00000000", //挂单量
|
||||
"askPrice": "4.00000200", //最优卖单价
|
||||
"askQty": "9.00000000", //挂单量
|
||||
"time": 1589437530011 // 撮合引擎时间
|
||||
}
|
||||
]
|
||||
"rateLimits": [
|
||||
{
|
||||
"rateLimitType": "REQUEST_WEIGHT",
|
||||
"interval": "MINUTE",
|
||||
"intervalNum": 1,
|
||||
"limit": 2400,
|
||||
"count": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
243
行情接口REST.txt
Normal file
243
行情接口REST.txt
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
获取服务器时间
|
||||
接口描述
|
||||
获取服务器时间
|
||||
|
||||
HTTP请求
|
||||
GET /fapi/v1/time
|
||||
|
||||
请求权重
|
||||
1
|
||||
|
||||
请求参数
|
||||
NONE
|
||||
|
||||
响应示例
|
||||
{
|
||||
"serverTime": 1499827319559 // 当前的系统时间
|
||||
}
|
||||
|
||||
|
||||
|
||||
获取交易规则和交易对
|
||||
接口描述
|
||||
获取交易规则和交易对
|
||||
|
||||
HTTP请求
|
||||
GET /fapi/v1/exchangeInfo
|
||||
|
||||
请求权重
|
||||
1
|
||||
|
||||
请求参数
|
||||
NONE
|
||||
|
||||
响应示例
|
||||
{
|
||||
"exchangeFilters": [],
|
||||
"rateLimits": [ // API访问的限制
|
||||
{
|
||||
"interval": "MINUTE", // 按照分钟计算
|
||||
"intervalNum": 1, // 按照1分钟计算
|
||||
"limit": 2400, // 上限次数
|
||||
"rateLimitType": "REQUEST_WEIGHT" // 按照访问权重来计算
|
||||
},
|
||||
{
|
||||
"interval": "MINUTE",
|
||||
"intervalNum": 1,
|
||||
"limit": 1200,
|
||||
"rateLimitType": "ORDERS" // 按照订单数量来计算
|
||||
}
|
||||
],
|
||||
"serverTime": 1565613908500, // 请忽略。如果需要获取当前系统时间,请查询接口 “GET /fapi/v1/time”
|
||||
"assets": [ // 资产信息
|
||||
{
|
||||
"asset": "BTC",
|
||||
"marginAvailable": true, // 是否可用作保证金
|
||||
"autoAssetExchange": "-0.10" // 保证金资产自动兑换阈值
|
||||
},
|
||||
{
|
||||
"asset": "USDT",
|
||||
"marginAvailable": true, // 是否可用作保证金
|
||||
"autoAssetExchange": "0" // 保证金资产自动兑换阈值
|
||||
},
|
||||
{
|
||||
"asset": "BNB",
|
||||
"marginAvailable": false, // 是否可用作保证金
|
||||
"autoAssetExchange": null // 保证金资产自动兑换阈值
|
||||
}
|
||||
],
|
||||
"symbols": [ // 交易对信息
|
||||
{
|
||||
"symbol": "BLZUSDT", // 交易对
|
||||
"pair": "BLZUSDT", // 标的交易对
|
||||
"contractType": "PERPETUAL", // 合约类型
|
||||
"deliveryDate": 4133404800000, // 交割日期
|
||||
"onboardDate": 1598252400000, // 上线日期
|
||||
"status": "TRADING", // 交易对状态
|
||||
"maintMarginPercent": "2.5000", // 请忽略
|
||||
"requiredMarginPercent": "5.0000", // 请忽略
|
||||
"baseAsset": "BLZ", // 标的资产
|
||||
"quoteAsset": "USDT", // 报价资产
|
||||
"marginAsset": "USDT", // 保证金资产
|
||||
"pricePrecision": 5, // 价格小数点位数(仅作为系统精度使用,注意同tickSize 区分)
|
||||
"quantityPrecision": 0, // 数量小数点位数(仅作为系统精度使用,注意同stepSize 区分)
|
||||
"baseAssetPrecision": 8, // 标的资产精度
|
||||
"quotePrecision": 8, // 报价资产精度
|
||||
"underlyingType": "COIN",
|
||||
"underlyingSubType": ["STORAGE"],
|
||||
"settlePlan": 0,
|
||||
"triggerProtect": "0.15", // 开启"priceProtect"的条件订单的触发阈值
|
||||
"filters": [
|
||||
{
|
||||
"filterType": "PRICE_FILTER", // 价格限制
|
||||
"maxPrice": "300", // 价格上限, 最大价格
|
||||
"minPrice": "0.0001", // 价格下限, 最小价格
|
||||
"tickSize": "0.0001" // 订单最小价格间隔
|
||||
},
|
||||
{
|
||||
"filterType": "LOT_SIZE", // 数量限制
|
||||
"maxQty": "10000000", // 数量上限, 最大数量
|
||||
"minQty": "1", // 数量下限, 最小数量
|
||||
"stepSize": "1" // 订单最小数量间隔
|
||||
},
|
||||
{
|
||||
"filterType": "MARKET_LOT_SIZE", // 市价订单数量限制
|
||||
"maxQty": "590119", // 数量上限, 最大数量
|
||||
"minQty": "1", // 数量下限, 最小数量
|
||||
"stepSize": "1" // 允许的步进值
|
||||
},
|
||||
{
|
||||
"filterType": "MAX_NUM_ORDERS", // 最多订单数限制
|
||||
"limit": 200
|
||||
},
|
||||
{
|
||||
"filterType": "MIN_NOTIONAL", // 最小名义价值
|
||||
"notional": "5.0",
|
||||
},
|
||||
{
|
||||
"filterType": "PERCENT_PRICE", // 价格比限制
|
||||
"multiplierUp": "1.1500", // 价格上限百分比
|
||||
"multiplierDown": "0.8500", // 价格下限百分比
|
||||
"multiplierDecimal": "4"
|
||||
}
|
||||
],
|
||||
"OrderType": [ // 订单类型
|
||||
"LIMIT", // 限价单
|
||||
"MARKET", // 市价单
|
||||
"STOP", // 止损单
|
||||
"STOP_MARKET", // 止损市价单
|
||||
"TAKE_PROFIT", // 止盈单
|
||||
"TAKE_PROFIT_MARKET", // 止盈暑市价单
|
||||
"TRAILING_STOP_MARKET" // 跟踪止损市价单
|
||||
],
|
||||
"timeInForce": [ // 有效方式
|
||||
"GTC", // 成交为止, 一直有效
|
||||
"IOC", // 无法立即成交(吃单)的部分就撤销
|
||||
"FOK", // 无法全部立即成交就撤销
|
||||
"GTX" // 无法成为挂单方就撤销
|
||||
],
|
||||
"liquidationFee": "0.010000", // 强平费率
|
||||
"marketTakeBound": "0.30", // 市价吃单(相对于标记价格)允许可造成的最大价格偏离比例
|
||||
}
|
||||
],
|
||||
"timezone": "UTC" // 服务器所用的时间区域
|
||||
}
|
||||
|
||||
24hr价格变动情况
|
||||
接口描述
|
||||
请注意,不携带symbol参数会返回全部交易对数据,不仅数据庞大,而且权重极高
|
||||
|
||||
HTTP请求
|
||||
GET /fapi/v1/ticker/24hr
|
||||
|
||||
请求权重
|
||||
带symbol为1, 不带为40
|
||||
|
||||
请求参数
|
||||
名称 类型 是否必需 描述
|
||||
symbol STRING NO 交易对
|
||||
不发送交易对参数,则会返回所有交易对信息
|
||||
响应示例
|
||||
{
|
||||
"symbol": "BTCUSDT",
|
||||
"priceChange": "-94.99999800", //24小时价格变动
|
||||
"priceChangePercent": "-95.960", //24小时价格变动百分比
|
||||
"weightedAvgPrice": "0.29628482", //加权平均价
|
||||
"lastPrice": "4.00000200", //最近一次成交价
|
||||
"lastQty": "200.00000000", //最近一次成交额
|
||||
"openPrice": "99.00000000", //24小时内第一次成交的价格
|
||||
"highPrice": "100.00000000", //24小时最高价
|
||||
"lowPrice": "0.10000000", //24小时最低价
|
||||
"volume": "8913.30000000", //24小时成交量
|
||||
"quoteVolume": "15.30000000", //24小时成交额
|
||||
"openTime": 1499783499040, //24小时内,第一笔交易的发生时间
|
||||
"closeTime": 1499869899040, //24小时内,最后一笔交易的发生时间
|
||||
"firstId": 28385, // 首笔成交id
|
||||
"lastId": 28460, // 末笔成交id
|
||||
"count": 76 // 成交笔数
|
||||
}
|
||||
|
||||
或(当不发送交易对信息)
|
||||
|
||||
[
|
||||
{
|
||||
"symbol": "BTCUSDT",
|
||||
"priceChange": "-94.99999800", //24小时价格变动
|
||||
"priceChangePercent": "-95.960", //24小时价格变动百分比
|
||||
"weightedAvgPrice": "0.29628482", //加权平均价
|
||||
"lastPrice": "4.00000200", //最近一次成交价
|
||||
"lastQty": "200.00000000", //最近一次成交额
|
||||
"openPrice": "99.00000000", //24小时内第一次成交的价格
|
||||
"highPrice": "100.00000000", //24小时最高价
|
||||
"lowPrice": "0.10000000", //24小时最低价
|
||||
"volume": "8913.30000000", //24小时成交量
|
||||
"quoteVolume": "15.30000000", //24小时成交额
|
||||
"openTime": 1499783499040, //24小时内,第一笔交易的发生时间
|
||||
"closeTime": 1499869899040, //24小时内,最后一笔交易的发生时间
|
||||
"firstId": 28385, // 首笔成交id
|
||||
"lastId": 28460, // 末笔成交id
|
||||
"count": 76 // 成交笔数
|
||||
}
|
||||
]
|
||||
|
||||
当前最优挂单
|
||||
接口描述
|
||||
返回当前最优的挂单(最高买单,最低卖单)
|
||||
|
||||
HTTP请求
|
||||
GET /fapi/v1/ticker/bookTicker
|
||||
|
||||
注意: 响应消息不包含RPI订单,其不可见。
|
||||
|
||||
请求权重
|
||||
单交易对2,无交易对5
|
||||
|
||||
请求参数
|
||||
名称 类型 是否必需 描述
|
||||
symbol STRING NO 交易对
|
||||
不发送交易对参数,则会返回所有交易对信息
|
||||
该接口返回头中的X-MBX-USED-WEIGHT-1M参数不准确,可以忽略
|
||||
响应示例
|
||||
{
|
||||
"symbol": "BTCUSDT", // 交易对
|
||||
"bidPrice": "4.00000000", //最优买单价
|
||||
"bidQty": "431.00000000", //挂单量
|
||||
"askPrice": "4.00000200", //最优卖单价
|
||||
"askQty": "9.00000000", //挂单量
|
||||
"time": 1589437530011 // 撮合引擎时间
|
||||
}
|
||||
|
||||
或(当不发送symbol)
|
||||
|
||||
[
|
||||
{
|
||||
"symbol": "BTCUSDT", // 交易对
|
||||
"bidPrice": "4.00000000", //最优买单价
|
||||
"bidQty": "431.00000000", //挂单量
|
||||
"askPrice": "4.00000200", //最优卖单价
|
||||
"askQty": "9.00000000", //挂单量
|
||||
"time": 1589437530011 // 撮合引擎时间
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user