This commit is contained in:
lz_db
2025-11-17 17:18:51 +08:00
parent 0fab423a18
commit 42a0391eeb
4 changed files with 956 additions and 146 deletions

View File

@@ -107,6 +107,10 @@ class mt5(Exchange, ImplicitAPI):
'OrderSend': 1,
'OrderModify': 1,
'OrderClose': 1,
'SubscribeOhlc': 1, # K线订阅
'UnsubscribeOhlc': 1, # K线取消订阅
'Subscribe': 1, # 行情订阅
'UnSubscribe': 1, # 行情取消订阅
},
},
},
@@ -297,11 +301,13 @@ class mt5(Exchange, ImplicitAPI):
balance = self.safe_number(response, 'balance', 0.0)
margin = self.safe_number(response, 'margin', 0.0)
free_margin = self.safe_number(response, 'freeMargin', 0.0)
equity = self.safe_number(response, 'equity', 0.0)
result[currency] = {
'free': free_margin,
'used': margin,
'total': balance,
'equity': equity,
}
return self.safe_balance(result)
@@ -532,6 +538,7 @@ class mt5(Exchange, ImplicitAPI):
'datetime': self.iso8601(timestamp),
'timestamp': timestamp,
'lastTradeTimestamp': last_trade_timestamp,
'lastUpdateTimestamp': last_trade_timestamp,
'status': status,
'symbol': symbol,
'type': type,
@@ -539,7 +546,9 @@ class mt5(Exchange, ImplicitAPI):
'postOnly': None,
'side': side,
'price': price,
'stopPrice': None,
'stopLossPrice': self.safe_number(order, 'stopLoss'),
'takeProfitPrice': self.safe_number(order, 'takeProfit'),
'reduceOnly':None,
'triggerPrice': None,
'amount': amount,
'filled': filled,
@@ -547,7 +556,7 @@ class mt5(Exchange, ImplicitAPI):
'cost': cost,
'trades': None,
'fee': fee,
'info': order,
# 'info': order,
'average': None,
})
except Exception as e:
@@ -589,6 +598,68 @@ class mt5(Exchange, ImplicitAPI):
}
return self.safe_string(types, type, type)
def parse_position(self, order_data, market: Market = None):
"""从订单数据解析持仓"""
# 只有状态为 Filled 的订单才是持仓
state = self.safe_string(order_data, 'state')
if state != 'Filled':
return None
symbol = self.safe_string(order_data, 'symbol')
if symbol and len(symbol) >= 6:
base = symbol[:3]
quote = symbol[3:]
symbol = base + '/' + quote
timestamp = self.parse8601(self.safe_string(order_data, 'openTime'))
open_timestamp_utc = self.safe_integer(order_data, 'openTimestampUTC')
if open_timestamp_utc:
timestamp = open_timestamp_utc
# 确定持仓方向
order_type = self.safe_string(order_data, 'orderType')
side = 'long' if order_type == 'Buy' else 'short'
# 计算持仓价值
contracts = self.safe_number(order_data, 'lots', 0)
entry_price = self.safe_number(order_data, 'openPrice', 0)
current_price = self.safe_number(order_data, 'closePrice', entry_price)
notional = contracts * entry_price if contracts and entry_price else None
# 计算盈亏百分比
percentage = None
if entry_price and current_price and entry_price != 0:
if side == 'long':
percentage = (current_price - entry_price) / entry_price
else:
percentage = (entry_price - current_price) / entry_price
return {
'id': self.safe_string(order_data, 'ticket'),
'symbol': symbol,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'side': side,
'contracts': contracts,
'contractSize': self.safe_number(order_data, 'contractSize', 1.0),
'entryPrice': entry_price,
'markPrice': current_price, # 使用当前价格作为标记价格
'notional': notional,
'leverage': 1, # MT5 可能需要从账户信息获取
'unrealizedPnl': self.safe_number(order_data, 'profit', 0),
'realizedPnl': 0, # 对于持仓已实现盈亏为0
'liquidationPrice': None, # MT5 可能不提供
'marginMode': 'cross',
'percentage': percentage,
'marginRatio': None,
'collateral': None,
'initialMargin': None, # 可能需要计算
'initialMarginPercentage': None,
'maintenanceMargin': None,
'maintenanceMarginPercentage': None,
# 'info': order_data,
}
async def create_order(self, symbol, type, side, amount, price=None, params={}):
"""创建订单"""
await self.load_markets()
@@ -650,6 +721,62 @@ class mt5(Exchange, ImplicitAPI):
response = await self.private_get_orderclose(self.extend(request, params))
return self.parse_order(response)
async def private_get(self, endpoint, params={}):
"""发送私有 GET 请求"""
return await self.fetch_private(endpoint, 'GET', params)
async def fetch_private(self, path, method='GET', params={}, headers=None, body=None):
"""发送私有 API 请求"""
url = self.urls['api']['private'] + '/' + path
query = self.omit(params, self.extract_params(path))
if method == 'GET' and query:
url += '?' + self.urlencode(query)
if self.verbose:
print(f"🔧 发送请求: {url}")
import aiohttp
try:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
content = await response.text()
# 直接返回内容,让 parse_json 处理
return self.parse_json(content)
else:
error_text = await response.text()
raise ExchangeError(f"HTTP {response.status}: {error_text}")
except Exception as e:
raise ExchangeError(f"请求失败: {e}")
def parse_json(self, response):
"""解析响应,支持多种格式"""
if not response:
return response
response = response.strip()
# 处理常见的成功响应
if response in ['OK', 'SUCCESS', 'True', 'true']:
return True
# 处理常见的失败响应
if response in ['FAIL', 'ERROR', 'False', 'false']:
return False
# 尝试解析 JSON
try:
import json
return json.loads(response)
except json.JSONDecodeError:
# 不是 JSON返回原始响应
return response
except Exception as e:
if self.verbose:
print(f"响应解析失败: {e}, 响应: {response}")
return response
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
"""签名请求"""
base_url = self.urls['api'][api]