1
This commit is contained in:
@@ -1,8 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
|
||||||
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
|
||||||
|
|
||||||
from ccxt.async_support.base.exchange import Exchange
|
from ccxt.async_support.base.exchange import Exchange
|
||||||
from ccxt.abstract.mt5 import ImplicitAPI
|
from ccxt.abstract.mt5 import ImplicitAPI
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -57,8 +54,7 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
'fetchOrder': True,
|
'fetchOrder': True,
|
||||||
'fetchOrderBook': True,
|
'fetchOrderBook': True,
|
||||||
'fetchTicker': True,
|
'fetchTicker': True,
|
||||||
'fetchTickers': True, # 添加这个字段
|
'fetchTickers': True,
|
||||||
'fetchTickers': True,
|
|
||||||
},
|
},
|
||||||
'timeframes': {
|
'timeframes': {
|
||||||
'1m': 1,
|
'1m': 1,
|
||||||
@@ -166,6 +162,19 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
}
|
}
|
||||||
return await self.private_get_checkconnect(request)
|
return await self.private_get_checkconnect(request)
|
||||||
|
|
||||||
|
async def disconnect(self):
|
||||||
|
"""断开连接"""
|
||||||
|
if hasattr(self, 'token') and self.token:
|
||||||
|
request = {
|
||||||
|
'id': self.token,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
await self.private_get_disconnect(request)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
self.token = None
|
||||||
|
|
||||||
async def fetch_markets(self, params={}):
|
async def fetch_markets(self, params={}):
|
||||||
"""获取交易对列表 - 异步修复版本"""
|
"""获取交易对列表 - 异步修复版本"""
|
||||||
if not hasattr(self, 'token') or not self.token:
|
if not hasattr(self, 'token') or not self.token:
|
||||||
@@ -225,7 +234,12 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
|
|
||||||
# 确保符号格式正确 (如 EURUSD)
|
# 确保符号格式正确 (如 EURUSD)
|
||||||
if len(symbol) < 6:
|
if len(symbol) < 6:
|
||||||
return None
|
# 处理较短的符号
|
||||||
|
base = symbol
|
||||||
|
quote = 'USD' # 默认报价货币
|
||||||
|
else:
|
||||||
|
base = symbol[:3]
|
||||||
|
quote = symbol[3:]
|
||||||
|
|
||||||
base = symbol[:3]
|
base = symbol[:3]
|
||||||
quote = symbol[3:]
|
quote = symbol[3:]
|
||||||
@@ -278,6 +292,21 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
print(f"解析市场信息失败: {e}, info: {info}")
|
print(f"解析市场信息失败: {e}, info: {info}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
async def server_timezone(self):
|
||||||
|
"""获得mt5服务器时区"""
|
||||||
|
if hasattr(self, 'timezone'):
|
||||||
|
return self.timezone
|
||||||
|
else:
|
||||||
|
if not hasattr(self, 'token') or not self.token:
|
||||||
|
await self.get_token()
|
||||||
|
|
||||||
|
request = {
|
||||||
|
'id': self.token,
|
||||||
|
}
|
||||||
|
response = await self.private_get_servertimezone(request)
|
||||||
|
self.timezone = int(float(response))
|
||||||
|
return self.timezone
|
||||||
|
|
||||||
async def fetch_balance(self, params={}):
|
async def fetch_balance(self, params={}):
|
||||||
"""获取账户余额"""
|
"""获取账户余额"""
|
||||||
if not hasattr(self, 'token') or not self.token:
|
if not hasattr(self, 'token') or not self.token:
|
||||||
@@ -302,16 +331,49 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
margin = self.safe_number(response, 'margin', 0.0)
|
margin = self.safe_number(response, 'margin', 0.0)
|
||||||
free_margin = self.safe_number(response, 'freeMargin', 0.0)
|
free_margin = self.safe_number(response, 'freeMargin', 0.0)
|
||||||
equity = self.safe_number(response, 'equity', 0.0)
|
equity = self.safe_number(response, 'equity', 0.0)
|
||||||
|
profit = self.safe_number(response, 'profit', 0.0)
|
||||||
|
|
||||||
result[currency] = {
|
result[currency] = {
|
||||||
'free': free_margin,
|
'free': free_margin,
|
||||||
'used': margin,
|
'used': margin,
|
||||||
'total': balance,
|
'total': balance,
|
||||||
'equity': equity,
|
'equity': equity,
|
||||||
|
'profit': profit,
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.safe_balance(result)
|
return self.safe_balance(result)
|
||||||
|
|
||||||
|
async def fetch_account_details(self, params={}):
|
||||||
|
"""获取账户信息"""
|
||||||
|
if not hasattr(self, 'token') or not self.token:
|
||||||
|
await self.get_token()
|
||||||
|
|
||||||
|
request = {
|
||||||
|
'id': self.token,
|
||||||
|
}
|
||||||
|
response = await self.private_get_accountdetails(self.extend(request, params))
|
||||||
|
|
||||||
|
return self.parse_account(response)
|
||||||
|
|
||||||
|
def parse_account(self, response):
|
||||||
|
"""解析账户信息"""
|
||||||
|
return {
|
||||||
|
'serverName': self.safe_string(response, 'serverName'),
|
||||||
|
'user': self.safe_string(response, 'user'),
|
||||||
|
'host': self.safe_string(response, 'host'),
|
||||||
|
'port': self.safe_integer(response, 'port'),
|
||||||
|
'serverTime': self.safe_string(response, 'serverTime'),
|
||||||
|
'serverTimeZone': self.safe_integer(response, 'serverTimeZone'),
|
||||||
|
'company': self.safe_string(response, 'company'),
|
||||||
|
'currency': self.safe_string(response, 'currency', 'UST'),
|
||||||
|
'accountName': self.safe_string(response, 'accountName'),
|
||||||
|
'group': self.safe_string(response, 'group'),
|
||||||
|
'accountType': self.safe_string(response, 'accountType'),
|
||||||
|
'accountLeverage': self.safe_integer(response, 'accountLeverage'),
|
||||||
|
'accountMethod': self.safe_string(response, 'accountMethod'),
|
||||||
|
'isInvestor': self.safe_value(response, 'isInvestor', False),
|
||||||
|
}
|
||||||
|
|
||||||
async def fetch_ticker(self, symbol, params={}):
|
async def fetch_ticker(self, symbol, params={}):
|
||||||
"""获取行情数据"""
|
"""获取行情数据"""
|
||||||
await self.load_markets()
|
await self.load_markets()
|
||||||
@@ -531,6 +593,9 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
'cost': fee_cost,
|
'cost': fee_cost,
|
||||||
'currency': None,
|
'currency': None,
|
||||||
}
|
}
|
||||||
|
# 确保所有必需字段都有值
|
||||||
|
if timestamp is None:
|
||||||
|
timestamp = self.milliseconds()
|
||||||
|
|
||||||
return self.safe_order({
|
return self.safe_order({
|
||||||
'id': id,
|
'id': id,
|
||||||
@@ -556,13 +621,21 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
'cost': cost,
|
'cost': cost,
|
||||||
'trades': None,
|
'trades': None,
|
||||||
'fee': fee,
|
'fee': fee,
|
||||||
# 'info': order,
|
'info': order,
|
||||||
'average': None,
|
'average': None,
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(f"解析订单失败: {e}, order: {order}")
|
print(f"解析订单失败: {e}, order: {order}")
|
||||||
raise e
|
return self.safe_order({
|
||||||
|
'id': self.safe_string(order, 'ticket'),
|
||||||
|
'symbol': symbol,
|
||||||
|
'status': 'unknown',
|
||||||
|
'side': 'unknown',
|
||||||
|
'type': 'unknown',
|
||||||
|
'timestamp': self.milliseconds(),
|
||||||
|
'info': order,
|
||||||
|
})
|
||||||
|
|
||||||
def parse_order_status(self, status):
|
def parse_order_status(self, status):
|
||||||
statuses = {
|
statuses = {
|
||||||
@@ -739,14 +812,15 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url) as response:
|
async with session.get(url, timeout=aiohttp.ClientTimeout(total=30)) as response:
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
content = await response.text()
|
content = await response.text()
|
||||||
# 直接返回内容,让 parse_json 处理
|
|
||||||
return self.parse_json(content)
|
return self.parse_json(content)
|
||||||
else:
|
else:
|
||||||
error_text = await response.text()
|
error_text = await response.text()
|
||||||
raise ExchangeError(f"HTTP {response.status}: {error_text}")
|
raise ExchangeError(f"HTTP {response.status}: {error_text}")
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
raise ExchangeError("请求超时")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ExchangeError(f"请求失败: {e}")
|
raise ExchangeError(f"请求失败: {e}")
|
||||||
|
|
||||||
|
|||||||
73
ccxt/mt5.py
73
ccxt/mt5.py
@@ -1,8 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
|
||||||
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
|
||||||
|
|
||||||
from ccxt.base.exchange import Exchange
|
from ccxt.base.exchange import Exchange
|
||||||
from ccxt.abstract.mt5 import ImplicitAPI
|
from ccxt.abstract.mt5 import ImplicitAPI
|
||||||
from ccxt.base.types import Any, Balances, BorrowInterest, Conversion, CrossBorrowRate, Currencies, Currency, DepositAddress, FundingHistory, Greeks, Int, LedgerEntry, Leverage, LeverageTier, LeverageTiers, Liquidation, LongShortRatio, Market, Num, Option, OptionChain, Order, OrderBook, OrderRequest, CancellationRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, TradingFees, Transaction, MarketInterface, TransferEntry
|
from ccxt.base.types import Any, Balances, BorrowInterest, Conversion, CrossBorrowRate, Currencies, Currency, DepositAddress, FundingHistory, Greeks, Int, LedgerEntry, Leverage, LeverageTier, LeverageTiers, Liquidation, LongShortRatio, Market, Num, Option, OptionChain, Order, OrderBook, OrderRequest, CancellationRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, TradingFees, Transaction, MarketInterface, TransferEntry
|
||||||
@@ -79,7 +76,7 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
'urls': {
|
'urls': {
|
||||||
'logo': '',
|
'logo': '',
|
||||||
'api': {
|
'api': {
|
||||||
'public': 'http://43.167.188.220:5000', # 直接使用具体地址,不使用 {hostname}
|
'public': 'http://43.167.188.220:5000',
|
||||||
'private': 'http://43.167.188.220:5000',
|
'private': 'http://43.167.188.220:5000',
|
||||||
},
|
},
|
||||||
'www': 'http://43.167.188.220:5000',
|
'www': 'http://43.167.188.220:5000',
|
||||||
@@ -112,7 +109,10 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
'OrderSend': 1,
|
'OrderSend': 1,
|
||||||
'OrderModify': 1,
|
'OrderModify': 1,
|
||||||
'OrderClose': 1,
|
'OrderClose': 1,
|
||||||
'SubscribeOhlc': 1, # 添加订阅端点
|
'SubscribeOhlc': 1,
|
||||||
|
'UnsubscribeOhlc': 1,
|
||||||
|
'Subscribe': 1,
|
||||||
|
'UnSubscribe': 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -157,10 +157,10 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
def connect(self):
|
def connect(self):
|
||||||
"""连接到 MT5 账户并获取 token"""
|
"""连接到 MT5 账户并获取 token"""
|
||||||
params = {
|
params = {
|
||||||
'user': self.apiKey, # apiKey 作为 user
|
'user': self.apiKey,
|
||||||
'password': self.secret, # secret 作为 password
|
'password': self.secret,
|
||||||
'host': self.options['host'], # 账号所分配在的MT5服务器IP
|
'host': self.options['host'],
|
||||||
'port': self.options['port'], # 账号所分配在的MT5服务器端口
|
'port': self.options['port'],
|
||||||
'connectTimeoutSeconds': self.options['connectTimeoutSeconds'],
|
'connectTimeoutSeconds': self.options['connectTimeoutSeconds'],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +225,6 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
if market and market.get('symbol'):
|
if market and market.get('symbol'):
|
||||||
markets.append(market)
|
markets.append(market)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 跳过解析失败的市场,继续处理其他市场
|
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(f"跳过交易对 {symbol}: {e}")
|
print(f"跳过交易对 {symbol}: {e}")
|
||||||
continue
|
continue
|
||||||
@@ -252,7 +251,6 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
def parse_market(self, info):
|
def parse_market(self, info):
|
||||||
"""解析市场信息 - 更健壮的版本"""
|
"""解析市场信息 - 更健壮的版本"""
|
||||||
try:
|
try:
|
||||||
# 安全获取 symbol
|
|
||||||
if not isinstance(info, dict):
|
if not isinstance(info, dict):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -262,21 +260,15 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
|
|
||||||
symbol = symbol.upper().strip()
|
symbol = symbol.upper().strip()
|
||||||
|
|
||||||
# 确保符号格式正确 (如 EURUSD, BTCUSD)
|
# 处理符号格式
|
||||||
if len(symbol) < 6:
|
if len(symbol) < 6:
|
||||||
# 处理较短的符号(如黄金 XAUUSD 是6位,但可能有其他情况)
|
|
||||||
# 对于无法确定格式的符号,直接使用原始符号
|
|
||||||
base = symbol
|
base = symbol
|
||||||
quote = 'USD' # 默认报价货币
|
quote = 'USD'
|
||||||
else:
|
else:
|
||||||
# 假设标准格式是3位基础货币+3位报价货币
|
|
||||||
base = symbol[:3]
|
base = symbol[:3]
|
||||||
quote = symbol[3:]
|
quote = symbol[3:]
|
||||||
|
|
||||||
# 安全处理精度
|
|
||||||
digits = self.safe_integer(info, 'digits', 5)
|
digits = self.safe_integer(info, 'digits', 5)
|
||||||
|
|
||||||
# 确保 digits 是整数
|
|
||||||
if digits is not None:
|
if digits is not None:
|
||||||
try:
|
try:
|
||||||
digits = int(digits)
|
digits = int(digits)
|
||||||
@@ -374,9 +366,7 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
'id': self.token,
|
'id': self.token,
|
||||||
}
|
}
|
||||||
|
|
||||||
# 如果指定了特定的交易对
|
|
||||||
if symbols is not None:
|
if symbols is not None:
|
||||||
# 将符号列表转换为 MT5 格式(如 ['EUR/USD', 'GBP/USD'] -> ['EURUSD', 'GBPUSD'])
|
|
||||||
mt5_symbols = []
|
mt5_symbols = []
|
||||||
for symbol in symbols:
|
for symbol in symbols:
|
||||||
market = self.market(symbol)
|
market = self.market(symbol)
|
||||||
@@ -387,7 +377,6 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
response = self.private_get_getquotemany(self.extend(request, params))
|
response = self.private_get_getquotemany(self.extend(request, params))
|
||||||
return self.parse_tickers(response, symbols)
|
return self.parse_tickers(response, symbols)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 如果批量获取失败,回退到逐个获取
|
|
||||||
if symbols is not None:
|
if symbols is not None:
|
||||||
return self.fetch_tickers_fallback(symbols, params)
|
return self.fetch_tickers_fallback(symbols, params)
|
||||||
else:
|
else:
|
||||||
@@ -411,7 +400,6 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
tickers = {}
|
tickers = {}
|
||||||
|
|
||||||
if isinstance(response, list):
|
if isinstance(response, list):
|
||||||
# 如果响应是数组
|
|
||||||
for ticker_data in response:
|
for ticker_data in response:
|
||||||
try:
|
try:
|
||||||
ticker = self.parse_ticker(ticker_data)
|
ticker = self.parse_ticker(ticker_data)
|
||||||
@@ -422,7 +410,6 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
print(f"解析行情数据失败: {e}")
|
print(f"解析行情数据失败: {e}")
|
||||||
continue
|
continue
|
||||||
elif isinstance(response, dict):
|
elif isinstance(response, dict):
|
||||||
# 如果响应是字典
|
|
||||||
for symbol_key, ticker_data in response.items():
|
for symbol_key, ticker_data in response.items():
|
||||||
try:
|
try:
|
||||||
ticker = self.parse_ticker(ticker_data)
|
ticker = self.parse_ticker(ticker_data)
|
||||||
@@ -433,7 +420,6 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
print(f"解析行情数据失败 {symbol_key}: {e}")
|
print(f"解析行情数据失败 {symbol_key}: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 如果指定了特定的交易对,确保返回的顺序一致
|
|
||||||
if symbols is not None:
|
if symbols is not None:
|
||||||
ordered_tickers = {}
|
ordered_tickers = {}
|
||||||
for symbol in symbols:
|
for symbol in symbols:
|
||||||
@@ -453,8 +439,6 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
}
|
}
|
||||||
response = self.private_get_getquote(self.extend(request, params))
|
response = self.private_get_getquote(self.extend(request, params))
|
||||||
|
|
||||||
# MT5 的 GetQuote 返回的是最新报价,不是完整的订单簿
|
|
||||||
# 这里模拟一个简单的订单簿
|
|
||||||
bid = self.safe_number(response, 'bid')
|
bid = self.safe_number(response, 'bid')
|
||||||
ask = self.safe_number(response, 'ask')
|
ask = self.safe_number(response, 'ask')
|
||||||
|
|
||||||
@@ -485,7 +469,7 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
'datetime': None,
|
'datetime': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
currency = 'USDT' # 强制设定为 USDT
|
currency = 'USDT'
|
||||||
balance = self.safe_number(response, 'balance', 0.0)
|
balance = self.safe_number(response, 'balance', 0.0)
|
||||||
margin = self.safe_number(response, 'margin', 0.0)
|
margin = self.safe_number(response, 'margin', 0.0)
|
||||||
free_margin = self.safe_number(response, 'freeMargin', 0.0)
|
free_margin = self.safe_number(response, 'freeMargin', 0.0)
|
||||||
@@ -530,7 +514,7 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
|
def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
|
||||||
"""获取未平仓订单 - 修复版本"""
|
"""获取未平仓订单"""
|
||||||
self.load_token()
|
self.load_token()
|
||||||
request = {
|
request = {
|
||||||
'id': self.token,
|
'id': self.token,
|
||||||
@@ -538,7 +522,6 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
|
|
||||||
response = self.private_get_openedorders(self.extend(request, params))
|
response = self.private_get_openedorders(self.extend(request, params))
|
||||||
|
|
||||||
# 如果指定了特定交易对,进行过滤
|
|
||||||
if symbol is not None:
|
if symbol is not None:
|
||||||
market = self.market(symbol)
|
market = self.market(symbol)
|
||||||
filtered_orders = []
|
filtered_orders = []
|
||||||
@@ -550,15 +533,14 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
return self.parse_orders(response, None, since, limit)
|
return self.parse_orders(response, None, since, limit)
|
||||||
|
|
||||||
def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
|
def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
|
||||||
"""获取已平仓订单 - 修复版本"""
|
"""获取已平仓订单"""
|
||||||
self.load_token()
|
self.load_token()
|
||||||
request = {
|
request = {
|
||||||
'id': self.token,
|
'id': self.token,
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.private_get_closedorders(self.extend(request, params))
|
response = self.private_get_closedorders(self.extend(request, params))
|
||||||
# print(response)
|
|
||||||
# 如果指定了特定交易对,进行过滤
|
|
||||||
if symbol is not None:
|
if symbol is not None:
|
||||||
market = self.market(symbol)
|
market = self.market(symbol)
|
||||||
filtered_orders = []
|
filtered_orders = []
|
||||||
@@ -570,25 +552,20 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
return self.parse_orders(response, None, since, limit)
|
return self.parse_orders(response, None, since, limit)
|
||||||
|
|
||||||
def parse_order(self, order, market=None):
|
def parse_order(self, order, market=None):
|
||||||
"""解析订单信息 - 修复市场符号问题"""
|
"""解析订单信息"""
|
||||||
try:
|
try:
|
||||||
id = self.safe_string(order, 'ticket')
|
id = self.safe_string(order, 'ticket')
|
||||||
market_id = self.safe_string(order, 'symbol')
|
market_id = self.safe_string(order, 'symbol')
|
||||||
|
|
||||||
# 安全地解析市场符号
|
|
||||||
symbol = None
|
symbol = None
|
||||||
if market is not None:
|
if market is not None:
|
||||||
symbol = market['symbol']
|
symbol = market['symbol']
|
||||||
elif market_id is not None:
|
elif market_id is not None:
|
||||||
# 修复:提供更多参数来正确解析符号
|
|
||||||
# 假设 MT5 的符号格式是 BASEQUOTE(如 EURUSD, BTCUSD)
|
|
||||||
if len(market_id) >= 6:
|
if len(market_id) >= 6:
|
||||||
# 尝试解析为 3+3 格式(如 EURUSD, GBPUSD)
|
|
||||||
base = market_id[:3]
|
base = market_id[:3]
|
||||||
quote = market_id[3:]
|
quote = market_id[3:]
|
||||||
symbol = base + '/' + quote
|
symbol = base + '/' + quote
|
||||||
else:
|
else:
|
||||||
# 如果无法解析,使用原始 market_id
|
|
||||||
symbol = market_id
|
symbol = market_id
|
||||||
|
|
||||||
timestamp = self.parse8601(self.safe_string(order, 'openTime'))
|
timestamp = self.parse8601(self.safe_string(order, 'openTime'))
|
||||||
@@ -634,7 +611,7 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
'price': price,
|
'price': price,
|
||||||
'stopLossPrice': self.safe_number(order, 'stopLoss'),
|
'stopLossPrice': self.safe_number(order, 'stopLoss'),
|
||||||
'takeProfitPrice': self.safe_number(order, 'takeProfit'),
|
'takeProfitPrice': self.safe_number(order, 'takeProfit'),
|
||||||
'reduceOnly':None,
|
'reduceOnly': None,
|
||||||
'triggerPrice': None,
|
'triggerPrice': None,
|
||||||
'amount': amount,
|
'amount': amount,
|
||||||
'filled': filled,
|
'filled': filled,
|
||||||
@@ -686,7 +663,6 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
|
|
||||||
def parse_position(self, order_data, market: Market = None):
|
def parse_position(self, order_data, market: Market = None):
|
||||||
"""从订单数据解析持仓"""
|
"""从订单数据解析持仓"""
|
||||||
# 只有状态为 Filled 的订单才是持仓
|
|
||||||
state = self.safe_string(order_data, 'state')
|
state = self.safe_string(order_data, 'state')
|
||||||
if state != 'Filled':
|
if state != 'Filled':
|
||||||
return None
|
return None
|
||||||
@@ -702,17 +678,14 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
if open_timestamp_utc:
|
if open_timestamp_utc:
|
||||||
timestamp = open_timestamp_utc
|
timestamp = open_timestamp_utc
|
||||||
|
|
||||||
# 确定持仓方向
|
|
||||||
order_type = self.safe_string(order_data, 'orderType')
|
order_type = self.safe_string(order_data, 'orderType')
|
||||||
side = 'long' if order_type == 'Buy' else 'short'
|
side = 'long' if order_type == 'Buy' else 'short'
|
||||||
|
|
||||||
# 计算持仓价值
|
|
||||||
contracts = self.safe_number(order_data, 'lots', 0)
|
contracts = self.safe_number(order_data, 'lots', 0)
|
||||||
entry_price = self.safe_number(order_data, 'openPrice', 0)
|
entry_price = self.safe_number(order_data, 'openPrice', 0)
|
||||||
current_price = self.safe_number(order_data, 'closePrice', entry_price)
|
current_price = self.safe_number(order_data, 'closePrice', entry_price)
|
||||||
notional = contracts * entry_price if contracts and entry_price else None
|
notional = contracts * entry_price if contracts and entry_price else None
|
||||||
|
|
||||||
# 计算盈亏百分比
|
|
||||||
percentage = None
|
percentage = None
|
||||||
if entry_price and current_price and entry_price != 0:
|
if entry_price and current_price and entry_price != 0:
|
||||||
if side == 'long':
|
if side == 'long':
|
||||||
@@ -729,17 +702,17 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
'contracts': contracts,
|
'contracts': contracts,
|
||||||
'contractSize': self.safe_number(order_data, 'contractSize', 1.0),
|
'contractSize': self.safe_number(order_data, 'contractSize', 1.0),
|
||||||
'entryPrice': entry_price,
|
'entryPrice': entry_price,
|
||||||
'markPrice': current_price, # 使用当前价格作为标记价格
|
'markPrice': current_price,
|
||||||
'notional': notional,
|
'notional': notional,
|
||||||
'leverage': 1, # MT5 可能需要从账户信息获取
|
'leverage': 1,
|
||||||
'unrealizedPnl': self.safe_number(order_data, 'profit', 0),
|
'unrealizedPnl': self.safe_number(order_data, 'profit', 0),
|
||||||
'realizedPnl': 0, # 对于持仓,已实现盈亏为0
|
'realizedPnl': 0,
|
||||||
'liquidationPrice': None, # MT5 可能不提供
|
'liquidationPrice': None,
|
||||||
'marginMode': 'cross',
|
'marginMode': 'cross',
|
||||||
'percentage': percentage,
|
'percentage': percentage,
|
||||||
'marginRatio': None,
|
'marginRatio': None,
|
||||||
'collateral': None,
|
'collateral': None,
|
||||||
'initialMargin': None, # 可能需要计算
|
'initialMargin': None,
|
||||||
'initialMarginPercentage': None,
|
'initialMarginPercentage': None,
|
||||||
'maintenanceMargin': None,
|
'maintenanceMargin': None,
|
||||||
'maintenanceMarginPercentage': None,
|
'maintenanceMarginPercentage': None,
|
||||||
@@ -757,7 +730,6 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
'volume': amount,
|
'volume': amount,
|
||||||
}
|
}
|
||||||
|
|
||||||
# 映射订单类型
|
|
||||||
operation_map = {
|
operation_map = {
|
||||||
'market': {
|
'market': {
|
||||||
'buy': 'Buy',
|
'buy': 'Buy',
|
||||||
@@ -781,7 +753,6 @@ class mt5(Exchange, ImplicitAPI):
|
|||||||
if type in ['limit', 'stop'] and price is not None:
|
if type in ['limit', 'stop'] and price is not None:
|
||||||
request['price'] = price
|
request['price'] = price
|
||||||
|
|
||||||
# 处理止损止盈
|
|
||||||
stop_loss = self.safe_number(params, 'stopLoss')
|
stop_loss = self.safe_number(params, 'stopLoss')
|
||||||
take_profit = self.safe_number(params, 'takeProfit')
|
take_profit = self.safe_number(params, 'takeProfit')
|
||||||
if stop_loss is not None:
|
if stop_loss is not None:
|
||||||
|
|||||||
304
ccxt/pro/mt5.py
304
ccxt/pro/mt5.py
@@ -41,6 +41,14 @@ class mt5(mt5Parent):
|
|||||||
"""监听所有数据(订单、持仓、钱包)"""
|
"""监听所有数据(订单、持仓、钱包)"""
|
||||||
if not hasattr(self, 'token') or not self.token:
|
if not hasattr(self, 'token') or not self.token:
|
||||||
await self.get_token()
|
await self.get_token()
|
||||||
|
|
||||||
|
# 检查连接状态
|
||||||
|
try:
|
||||||
|
await self.check_connect()
|
||||||
|
except Exception as e:
|
||||||
|
if self.verbose:
|
||||||
|
print(f"连接检查失败,重新连接: {e}")
|
||||||
|
await self.get_token()
|
||||||
|
|
||||||
url = self.urls['api']['ws'] + '/OnOrderUpdate?id=' + self.token
|
url = self.urls['api']['ws'] + '/OnOrderUpdate?id=' + self.token
|
||||||
message_hash = 'all_data'
|
message_hash = 'all_data'
|
||||||
@@ -153,40 +161,6 @@ class mt5(mt5Parent):
|
|||||||
print(f"❌ 行情监听失败: {e}")
|
print(f"❌ 行情监听失败: {e}")
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
async def close_ticker(self, symbol: str, params={}):
|
|
||||||
"""取消行情订阅"""
|
|
||||||
await self.load_markets()
|
|
||||||
market = self.market(symbol)
|
|
||||||
|
|
||||||
if not hasattr(self, 'token') or not self.token:
|
|
||||||
await self.get_token()
|
|
||||||
|
|
||||||
# 取消订阅参数
|
|
||||||
request = {
|
|
||||||
'id': self.token,
|
|
||||||
'symbol': market['id'],
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 发送取消订阅请求
|
|
||||||
result = await self.private_get('UnSubscribe', self.extend(request, params))
|
|
||||||
|
|
||||||
# 更新订阅状态
|
|
||||||
if hasattr(self, 'ticker_subscriptions'):
|
|
||||||
subscribe_hash = 'ticker:' + market['id']
|
|
||||||
if subscribe_hash in self.ticker_subscriptions:
|
|
||||||
self.ticker_subscriptions.remove(subscribe_hash)
|
|
||||||
|
|
||||||
if self.verbose:
|
|
||||||
print(f"✅ 取消行情订阅成功: {symbol}")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
if self.verbose:
|
|
||||||
print(f"❌ 取消行情订阅失败: {e}")
|
|
||||||
raise e
|
|
||||||
|
|
||||||
async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Optional[int] = None, limit: Optional[int] = None, params={}):
|
async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Optional[int] = None, limit: Optional[int] = None, params={}):
|
||||||
await self.load_markets()
|
await self.load_markets()
|
||||||
market = self.market(symbol)
|
market = self.market(symbol)
|
||||||
@@ -238,6 +212,40 @@ class mt5(mt5Parent):
|
|||||||
print(f"❌ K线监听失败: {e}")
|
print(f"❌ K线监听失败: {e}")
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
async def close_ticker(self, symbol: str, params={}):
|
||||||
|
"""取消行情订阅"""
|
||||||
|
await self.load_markets()
|
||||||
|
market = self.market(symbol)
|
||||||
|
|
||||||
|
if not hasattr(self, 'token') or not self.token:
|
||||||
|
await self.get_token()
|
||||||
|
|
||||||
|
# 取消订阅参数
|
||||||
|
request = {
|
||||||
|
'id': self.token,
|
||||||
|
'symbol': market['id'],
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 发送取消订阅请求
|
||||||
|
result = await self.private_get('UnSubscribe', self.extend(request, params))
|
||||||
|
|
||||||
|
# 更新订阅状态
|
||||||
|
if hasattr(self, 'ticker_subscriptions'):
|
||||||
|
subscribe_hash = 'ticker:' + market['id']
|
||||||
|
if subscribe_hash in self.ticker_subscriptions:
|
||||||
|
self.ticker_subscriptions.remove(subscribe_hash)
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print(f"✅ 取消行情订阅成功: {symbol}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if self.verbose:
|
||||||
|
print(f"❌ 取消行情订阅失败: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
async def close_ohlcv(self, symbol: Optional[str] = None, timeframe: Optional[str] = None, params={}):
|
async def close_ohlcv(self, symbol: Optional[str] = None, timeframe: Optional[str] = None, params={}):
|
||||||
"""取消K线订阅"""
|
"""取消K线订阅"""
|
||||||
if not hasattr(self, 'token') or not self.token:
|
if not hasattr(self, 'token') or not self.token:
|
||||||
@@ -291,29 +299,37 @@ class mt5(mt5Parent):
|
|||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(f"❌ 取消K线订阅失败: {e}")
|
print(f"❌ 取消K线订阅失败: {e}")
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
async def send_ticker_subscription(self, request, params={}):
|
async def close(self):
|
||||||
"""发送行情数据订阅请求"""
|
"""关闭所有连接和清理缓存"""
|
||||||
try:
|
try:
|
||||||
# 使用 private_get 发送订阅
|
# 取消所有订阅
|
||||||
response = await self.private_get('Subscribe', self.extend(request, params))
|
if hasattr(self, 'ohlcv_subscriptions') and self.ohlcv_subscriptions:
|
||||||
|
await self.close_ohlcv()
|
||||||
|
|
||||||
|
if hasattr(self, 'ticker_subscriptions') and self.ticker_subscriptions:
|
||||||
|
# 取消所有ticker订阅
|
||||||
|
for subscribe_hash in list(self.ticker_subscriptions):
|
||||||
|
symbol = subscribe_hash.replace('ticker:', '')
|
||||||
|
try:
|
||||||
|
await self.close_ticker(symbol)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 清理缓存
|
||||||
|
self.orders_list = []
|
||||||
|
self.positions_list = []
|
||||||
|
self.ohlcv_cache = {}
|
||||||
|
self.ticker_cache = {}
|
||||||
|
|
||||||
|
# 断开连接
|
||||||
|
await self.disconnect()
|
||||||
|
|
||||||
if self.verbose:
|
|
||||||
print(f"✅ 行情订阅响应: {response}")
|
|
||||||
|
|
||||||
# 检查订阅是否成功
|
|
||||||
if response in [True, 'OK', 'SUCCESS']:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
raise ExchangeError(f"行情订阅失败: {response}")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(f"private_get 失败,尝试直接HTTP请求: {e}")
|
print(f"关闭连接时出错: {e}")
|
||||||
|
|
||||||
# 直接HTTP请求
|
await super().close()
|
||||||
return await self.send_direct_subscription('Subscribe', request, params)
|
|
||||||
|
|
||||||
|
|
||||||
def handle_message(self, client: Client, message):
|
def handle_message(self, client: Client, message):
|
||||||
"""处理WebSocket消息 - 根据类型分发"""
|
"""处理WebSocket消息 - 根据类型分发"""
|
||||||
@@ -584,64 +600,75 @@ class mt5(mt5Parent):
|
|||||||
if not order_data:
|
if not order_data:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
symbol = self.safe_string(order_data, 'symbol')
|
try:
|
||||||
if symbol and len(symbol) >= 6:
|
symbol = self.safe_string(order_data, 'symbol')
|
||||||
base = symbol[:3]
|
if symbol and len(symbol) >= 6:
|
||||||
quote = symbol[3:]
|
base = symbol[:3]
|
||||||
symbol = base + '/' + quote
|
quote = symbol[3:]
|
||||||
|
symbol = base + '/' + quote
|
||||||
# 确定订单状态
|
|
||||||
state = self.safe_string(order_data, 'state')
|
# 确定订单状态
|
||||||
status = self.parse_order_status(state)
|
state = self.safe_string(order_data, 'state')
|
||||||
|
status = self.parse_order_status(state)
|
||||||
# 确定订单是否已关闭
|
|
||||||
close_time = self.safe_string(order_data, 'closeTime')
|
# 确定订单是否已关闭
|
||||||
is_closed = close_time and close_time != "0001-01-01T00:00:00"
|
close_time = self.safe_string(order_data, 'closeTime')
|
||||||
|
is_closed = close_time and close_time != "0001-01-01T00:00:00"
|
||||||
timestamp = self.parse8601(self.safe_string(order_data, 'openTime'))
|
|
||||||
last_trade_timestamp = None
|
timestamp = self.parse8601(self.safe_string(order_data, 'openTime'))
|
||||||
if is_closed:
|
if timestamp is None:
|
||||||
last_trade_timestamp = self.parse8601(close_time)
|
timestamp = self.milliseconds()
|
||||||
|
|
||||||
amount = self.safe_number(order_data, 'lots')
|
last_trade_timestamp = None
|
||||||
filled = self.safe_number(order_data, 'closeLots')
|
if is_closed:
|
||||||
remaining = amount - filled if amount is not None and filled is not None else None
|
last_trade_timestamp = self.parse8601(close_time)
|
||||||
|
|
||||||
return {
|
amount = self.safe_number(order_data, 'lots', 0)
|
||||||
'id': self.safe_string(order_data, 'ticket'),
|
filled = self.safe_number(order_data, 'closeLots', 0)
|
||||||
'clientOrderId': None,
|
remaining = max(amount - filled, 0) if amount is not None and filled is not None else None
|
||||||
'datetime': self.iso8601(timestamp),
|
|
||||||
'timestamp': timestamp,
|
return {
|
||||||
'lastTradeTimestamp': last_trade_timestamp,
|
'id': self.safe_string(order_data, 'ticket'),
|
||||||
'lastUpdateTimestamp': last_trade_timestamp,
|
'clientOrderId': None,
|
||||||
'status': status,
|
'datetime': self.iso8601(timestamp),
|
||||||
'symbol': symbol,
|
'timestamp': timestamp,
|
||||||
'type': self.parse_order_type(self.safe_string(order_data, 'orderType')),
|
'lastTradeTimestamp': last_trade_timestamp,
|
||||||
'timeInForce': None,
|
'lastUpdateTimestamp': last_trade_timestamp,
|
||||||
'postOnly': None,
|
'status': status,
|
||||||
'side': self.parse_order_side(self.safe_string(order_data, 'orderType')),
|
'symbol': symbol,
|
||||||
'price': self.safe_number(order_data, 'openPrice'),
|
'type': self.parse_order_type(self.safe_string(order_data, 'orderType')),
|
||||||
'stopLossPrice': self.safe_number(order_data, 'stopLoss'),
|
'timeInForce': None,
|
||||||
'takeProfitPrice': self.safe_number(order_data, 'takeProfit'),
|
'postOnly': None,
|
||||||
'reduceOnly':None,
|
'side': self.parse_order_side(self.safe_string(order_data, 'orderType')),
|
||||||
'triggerPrice': None,
|
'price': self.safe_number(order_data, 'openPrice'),
|
||||||
'amount': amount,
|
'stopLossPrice': self.safe_number(order_data, 'stopLoss'),
|
||||||
'filled': filled,
|
'takeProfitPrice': self.safe_number(order_data, 'takeProfit'),
|
||||||
'remaining': remaining,
|
'reduceOnly': None,
|
||||||
'cost': None,
|
'triggerPrice': None,
|
||||||
'trades': None,
|
'amount': amount,
|
||||||
'fee': {
|
'filled': filled,
|
||||||
'cost': self.safe_number(order_data, 'commission', 0) + self.safe_number(order_data, 'fee', 0),
|
'remaining': remaining,
|
||||||
'currency': None,
|
'cost': None,
|
||||||
},
|
'trades': None,
|
||||||
'average': None,
|
'fee': {
|
||||||
'info': order_data,
|
'cost': self.safe_number(order_data, 'commission', 0) + self.safe_number(order_data, 'fee', 0),
|
||||||
}
|
'currency': None,
|
||||||
|
},
|
||||||
|
'average': None,
|
||||||
|
'info': order_data,
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
if self.verbose:
|
||||||
|
print(f"解析订单数据失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def parse_ws_positions_from_orders(self, orders_data):
|
def parse_ws_positions_from_orders(self, orders_data):
|
||||||
"""从订单数据解析持仓信息"""
|
"""从订单数据解析持仓信息"""
|
||||||
positions = []
|
positions = []
|
||||||
|
|
||||||
|
if not orders_data:
|
||||||
|
return positions
|
||||||
|
|
||||||
if not isinstance(orders_data, list):
|
if not isinstance(orders_data, list):
|
||||||
orders_data = [orders_data]
|
orders_data = [orders_data]
|
||||||
|
|
||||||
@@ -706,6 +733,10 @@ class mt5(mt5Parent):
|
|||||||
"""解析WebSocket K线数据"""
|
"""解析WebSocket K线数据"""
|
||||||
timestamp = self.parse8601(self.safe_string(data, 'time'))
|
timestamp = self.parse8601(self.safe_string(data, 'time'))
|
||||||
|
|
||||||
|
# 确保时间戳是毫秒
|
||||||
|
if timestamp and timestamp < 1000000000000:
|
||||||
|
timestamp *= 1000
|
||||||
|
|
||||||
return [
|
return [
|
||||||
timestamp, # 时间戳
|
timestamp, # 时间戳
|
||||||
self.safe_number(data, 'open'), # 开盘价
|
self.safe_number(data, 'open'), # 开盘价
|
||||||
@@ -797,6 +828,57 @@ class mt5(mt5Parent):
|
|||||||
# 方法2:直接HTTP请求
|
# 方法2:直接HTTP请求
|
||||||
return await self.send_direct_subscription('SubscribeOhlc', request, params)
|
return await self.send_direct_subscription('SubscribeOhlc', request, params)
|
||||||
|
|
||||||
|
async def send_ticker_subscription(self, request, params={}):
|
||||||
|
"""发送行情数据订阅请求"""
|
||||||
|
try:
|
||||||
|
# 使用 private_get 发送订阅
|
||||||
|
response = await self.private_get('Subscribe', self.extend(request, params))
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print(f"✅ 行情订阅响应: {response}")
|
||||||
|
|
||||||
|
# 检查订阅是否成功
|
||||||
|
if response in [True, 'OK', 'SUCCESS']:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
raise ExchangeError(f"行情订阅失败: {response}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if self.verbose:
|
||||||
|
print(f"private_get 失败,尝试直接HTTP请求: {e}")
|
||||||
|
|
||||||
|
# 直接HTTP请求
|
||||||
|
return await self.send_direct_subscription('Subscribe', request, params)
|
||||||
|
|
||||||
|
async def send_direct_subscription(self, endpoint, request, params={}):
|
||||||
|
"""直接发送订阅HTTP请求"""
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
# 构建完整的URL
|
||||||
|
base_url = self.urls['api']['private']
|
||||||
|
query_params = self.extend(request, params)
|
||||||
|
query_string = urllib.parse.urlencode(query_params)
|
||||||
|
|
||||||
|
url = f"{base_url}/{endpoint}?{query_string}"
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print(f"🔧 直接订阅URL: {url}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(url) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
result = await response.text()
|
||||||
|
if self.verbose:
|
||||||
|
print(f"✅ 直接订阅成功: {result}")
|
||||||
|
return self.parse_json(result)
|
||||||
|
else:
|
||||||
|
error_text = await response.text()
|
||||||
|
raise ExchangeError(f"订阅请求失败 {response.status}: {error_text}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise ExchangeError(f"订阅请求错误: {e}")
|
||||||
|
|
||||||
def handle_error_message(self, client: Client, message):
|
def handle_error_message(self, client: Client, message):
|
||||||
"""处理错误消息"""
|
"""处理错误消息"""
|
||||||
error_code = self.safe_string(message, 'errorCode')
|
error_code = self.safe_string(message, 'errorCode')
|
||||||
@@ -820,7 +902,15 @@ class mt5(mt5Parent):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(f"拒绝K线消息哈希失败 {ohlcv_hash}: {e}")
|
print(f"拒绝K线消息哈希失败 {ohlcv_hash}: {e}")
|
||||||
|
# 拒绝所有ticker消息哈希
|
||||||
|
if hasattr(client, 'futures') and client.futures:
|
||||||
|
ticker_hashes = [hash for hash in client.futures.keys() if isinstance(hash, str) and hash.startswith('ticker:')]
|
||||||
|
for ticker_hash in ticker_hashes:
|
||||||
|
try:
|
||||||
|
client.reject(error, ticker_hash)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(f"错误处理失败: {e}")
|
print(f"错误处理失败: {e}")
|
||||||
@@ -38,19 +38,20 @@ def quick_sync_test():
|
|||||||
logger.info(f" ✅ 交易对数量: {len(markets)}")
|
logger.info(f" ✅ 交易对数量: {len(markets)}")
|
||||||
|
|
||||||
# # 测试余额
|
# # 测试余额
|
||||||
# logger.info("2. 测试余额...")
|
logger.info("2. 测试余额...")
|
||||||
# balance = exchange.fetch_balance()
|
balance = exchange.fetch_balance()
|
||||||
# logger.info(f" ✅ 余额: {balance['total'].get('USDT', 'N/A')}")
|
logger.info(f" ✅ 余额: {balance['total'].get('USDT', 'N/A')}")
|
||||||
|
|
||||||
# 测试行情
|
# 测试行情
|
||||||
# logger.info("3. 测试行情...")
|
logger.info("3. 测试行情...")
|
||||||
# ticker = exchange.fetch_ticker('EUR/USD')
|
ticker = exchange.fetch_ticker('BTC/USD')
|
||||||
# logger.info(f" ✅ EUR/USD: {ticker['bid']} / {ticker['ask']}")
|
logger.info(f" ✅ BTC/USD: {ticker['bid']} / {ticker['ask']}")
|
||||||
|
|
||||||
# 测试订单
|
# 测试订单
|
||||||
logger.info("3. 测试订单...")
|
logger.info("3. 测试订单...")
|
||||||
ticker = exchange.fetch_closed_orders('EUR/USD')
|
ticker = exchange.fetch_closed_orders('BTC/USD',limit=1)
|
||||||
logger.info(f" ✅ EUR/USD: {ticker['bid']} / {ticker['ask']}")
|
# logger.info(f" ✅ BTC/USD: {ticker['bid']} / {ticker['ask']}")
|
||||||
|
logger.info(f" ✅ 订单数量: {len(ticker)}")
|
||||||
|
|
||||||
|
|
||||||
logger.info("🎉 快速同步测试完成!")
|
logger.info("🎉 快速同步测试完成!")
|
||||||
@@ -116,21 +117,21 @@ async def main():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|
||||||
exchange = mt5_sync(CONFIG)
|
# exchange = mt5_sync(CONFIG)
|
||||||
|
|
||||||
# 测试连接
|
# 测试连接
|
||||||
logger.info("1. 测试连接...")
|
# logger.info("1. 测试连接...")
|
||||||
token = exchange.get_token()
|
# token = exchange.get_token()
|
||||||
logger.info(f" ✅ Token: {token}")
|
# logger.info(f" ✅ Token: {token}")
|
||||||
|
|
||||||
# # 测试交易对
|
# # # 测试交易对
|
||||||
logger.info("4. 测试交易对...")
|
# logger.info("4. 测试交易对...")
|
||||||
markets = exchange.fetch_markets()
|
# markets = exchange.fetch_markets()
|
||||||
logger.info(f" ✅ 交易对数量: {len(markets)}")
|
# logger.info(f" ✅ 交易对数量: {len(markets)}")
|
||||||
|
|
||||||
|
|
||||||
logger.info("3. 测试订单...")
|
# logger.info("3. 测试订单...")
|
||||||
ticker = exchange.fetch_closed_orders('BTC/USD')
|
# ticker = exchange.fetch_closed_orders('BTC/USD')
|
||||||
logger.info(f"{ticker}")
|
# logger.info(f"{ticker}")
|
||||||
Reference in New Issue
Block a user