mt5添加获取持仓方法

This commit is contained in:
macbook_max
2025-11-29 20:49:42 +08:00
parent dd4d01c58a
commit b298c01743
3 changed files with 256 additions and 121 deletions

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
build/ build/
dist/ dist/
ccxt/__pycache__/ ccxt/__pycache__/
__pycache__/ __pycache__/
.vscode/settings.json

View File

@@ -52,6 +52,7 @@ class mt5(Exchange, ImplicitAPI):
'fetchOHLCV': True, 'fetchOHLCV': True,
'fetchOpenOrders': True, 'fetchOpenOrders': True,
'fetchOrder': True, 'fetchOrder': True,
'fetchPositions': True,
'fetchOrderBook': True, 'fetchOrderBook': True,
'fetchTicker': True, 'fetchTicker': True,
'fetchTickers': True, 'fetchTickers': True,
@@ -561,6 +562,132 @@ class mt5(Exchange, ImplicitAPI):
return tickers return tickers
async def fetch_positions(self, symbol=None, since=None, limit=None, params={}):
"""异步获取持仓信息"""
if not hasattr(self, 'token') or not self.token:
await self.get_token()
request = {
'id': self.token,
}
response = await self.private_get_openedorders(self.extend(request, params))
# 使用基类的 parse_positions 方法,让 parse_position 自己判断是否为有效持仓
result = self.parse_positions(response, [symbol] if symbol else None, params)
# 过滤掉 None 值(无效持仓)
result = [position for position in result if position is not None]
# 应用限制
if limit is not None:
result = result[:limit]
return result
def parse_position(self, position, market: Market = None):
"""解析持仓信息 - 根据真实数据调整"""
# 获取市场信息
market_id = self.safe_string(position, 'symbol')
symbol = self.safe_symbol(market_id, market, '/')
# 检查是否为有效持仓
state = self.safe_string(position, 'state')
lots = self.safe_number(position, 'lots', 0)
close_lots = self.safe_number(position, 'closeLots', 0)
# 只有状态为已成交且有未平仓数量的才是有效持仓
# 根据你的业务逻辑调整这个判断条件
if state != 'Filled' or lots <= close_lots:
return None
# 解析时间戳
timestamp = self.parse8601(self.safe_string(position, 'openTime'))
open_timestamp_utc = self.safe_integer(position, 'openTimestampUTC')
if open_timestamp_utc:
timestamp = open_timestamp_utc
# 确定持仓方向
order_type = self.safe_string(position, 'orderType')
side = 'long' if order_type == 'Buy' else 'short'
# 获取持仓数量 (未平仓数量)
contracts = lots - close_lots
# 获取价格信息
entry_price = self.safe_number(position, 'openPrice', 0)
current_price = self.safe_number(position, 'closePrice', entry_price)
mark_price = current_price
# 计算持仓价值
contract_size = self.safe_number(position, 'contractSize', 1.0)
notional = contracts * entry_price * contract_size if contracts and entry_price and contract_size else None
# 计算盈亏 - 使用 profit 字段
unrealized_pnl = self.safe_number(position, 'profit', 0)
# 计算保证金信息
initial_margin = None
initial_margin_percentage = None
# 如果有持仓价值,计算保证金
if notional is not None and notional != 0:
leverage = self.safe_number(position, 'leverage', 100)
initial_margin = notional / leverage
initial_margin_percentage = 1 / leverage
# 计算强平价格 (简化计算)
liquidation_price = None
if entry_price is not None:
if side == 'long':
liquidation_price = entry_price * 0.95 # 假设 5% 强平线
else:
liquidation_price = entry_price * 1.05 # 假设 5% 强平线
# 计算百分比盈亏
percentage = None
if entry_price is not None and mark_price is not None and entry_price != 0:
if side == 'long':
percentage = (mark_price - entry_price) / entry_price
else:
percentage = (entry_price - mark_price) / entry_price
# 获取止损止盈价格
stop_loss_price = self.safe_number(position, 'stopLoss')
take_profit_price = self.safe_number(position, 'takeProfit')
# 返回标准化的持仓信息
return {
'info': position,
'id': self.safe_string(position, 'ticket'),
'symbol': symbol,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp) if timestamp else None,
'lastUpdateTimestamp': timestamp,
'initialMargin': initial_margin,
'initialMarginPercentage': initial_margin_percentage,
'maintenanceMargin': None,
'maintenanceMarginPercentage': None,
'entryPrice': entry_price,
'notional': notional,
'leverage': self.safe_number(position, 'leverage', 100),
'unrealizedPnl': unrealized_pnl,
'realizedPnl': self.safe_number(position, 'realizedPnl', 0),
'contracts': contracts,
'contractSize': contract_size,
'marginRatio': None,
'liquidationPrice': liquidation_price,
'markPrice': mark_price,
'lastPrice': mark_price,
'collateral': initial_margin,
'marginMode': 'cross',
'side': side,
'percentage': percentage,
'stopLossPrice': stop_loss_price,
'takeProfitPrice': take_profit_price,
'hedged': None,
}
async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}): async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
"""异步获取未平仓订单 - 修复版本""" """异步获取未平仓订单 - 修复版本"""
if not hasattr(self, 'token') or not self.token: if not hasattr(self, 'token') or not self.token:
@@ -731,68 +858,6 @@ class mt5(Exchange, ImplicitAPI):
} }
return self.safe_string(types, type, type) 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={}): async def create_order(self, symbol, type, side, amount, price=None, params={}):
"""创建订单""" """创建订单"""
await self.load_markets() await self.load_markets()

View File

@@ -58,6 +58,7 @@ class mt5(Exchange, ImplicitAPI):
'fetchOHLCV': True, 'fetchOHLCV': True,
'fetchOpenOrders': True, 'fetchOpenOrders': True,
'fetchOrder': True, 'fetchOrder': True,
'fetchPositions': True,
'fetchOrderBook': True, 'fetchOrderBook': True,
'fetchTicker': True, 'fetchTicker': True,
'fetchTickers': True, 'fetchTickers': True,
@@ -582,6 +583,132 @@ class mt5(Exchange, ImplicitAPI):
'isInvestor': self.safe_value(response, 'isInvestor', False), 'isInvestor': self.safe_value(response, 'isInvestor', False),
} }
def fetch_positions(self, symbol=None, since=None, limit=None, params={}):
"""异步获取持仓信息"""
if not hasattr(self, 'token') or not self.token:
self.get_token()
request = {
'id': self.token,
}
response = self.private_get_openedorders(self.extend(request, params))
# 使用基类的 parse_positions 方法,让 parse_position 自己判断是否为有效持仓
result = self.parse_positions(response, [symbol] if symbol else None, params)
# 过滤掉 None 值(无效持仓)
result = [position for position in result if position is not None]
# 应用限制
if limit is not None:
result = result[:limit]
return result
def parse_position(self, position, market: Market = None):
"""解析持仓信息 - 根据真实数据调整"""
# 获取市场信息
market_id = self.safe_string(position, 'symbol')
symbol = self.safe_symbol(market_id, market, '/')
# 检查是否为有效持仓
state = self.safe_string(position, 'state')
lots = self.safe_number(position, 'lots', 0)
close_lots = self.safe_number(position, 'closeLots', 0)
# 只有状态为已成交且有未平仓数量的才是有效持仓
# 根据你的业务逻辑调整这个判断条件
if state != 'Filled' or lots <= close_lots:
return None
# 解析时间戳
timestamp = self.parse8601(self.safe_string(position, 'openTime'))
open_timestamp_utc = self.safe_integer(position, 'openTimestampUTC')
if open_timestamp_utc:
timestamp = open_timestamp_utc
# 确定持仓方向
order_type = self.safe_string(position, 'orderType')
side = 'long' if order_type == 'Buy' else 'short'
# 获取持仓数量 (未平仓数量)
contracts = lots - close_lots
# 获取价格信息
entry_price = self.safe_number(position, 'openPrice', 0)
current_price = self.safe_number(position, 'closePrice', entry_price)
mark_price = current_price
# 计算持仓价值
contract_size = self.safe_number(position, 'contractSize', 1.0)
notional = contracts * entry_price * contract_size if contracts and entry_price and contract_size else None
# 计算盈亏 - 使用 profit 字段
unrealized_pnl = self.safe_number(position, 'profit', 0)
# 计算保证金信息
initial_margin = None
initial_margin_percentage = None
# 如果有持仓价值,计算保证金
if notional is not None and notional != 0:
leverage = self.safe_number(position, 'leverage', 100)
initial_margin = notional / leverage
initial_margin_percentage = 1 / leverage
# 计算强平价格 (简化计算)
liquidation_price = None
if entry_price is not None:
if side == 'long':
liquidation_price = entry_price * 0.95 # 假设 5% 强平线
else:
liquidation_price = entry_price * 1.05 # 假设 5% 强平线
# 计算百分比盈亏
percentage = None
if entry_price is not None and mark_price is not None and entry_price != 0:
if side == 'long':
percentage = (mark_price - entry_price) / entry_price
else:
percentage = (entry_price - mark_price) / entry_price
# 获取止损止盈价格
stop_loss_price = self.safe_number(position, 'stopLoss')
take_profit_price = self.safe_number(position, 'takeProfit')
# 返回标准化的持仓信息
return {
'info': position,
'id': self.safe_string(position, 'ticket'),
'symbol': symbol,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp) if timestamp else None,
'lastUpdateTimestamp': timestamp,
'initialMargin': initial_margin,
'initialMarginPercentage': initial_margin_percentage,
'maintenanceMargin': None,
'maintenanceMarginPercentage': None,
'entryPrice': entry_price,
'notional': notional,
'leverage': self.safe_number(position, 'leverage', 100),
'unrealizedPnl': unrealized_pnl,
'realizedPnl': self.safe_number(position, 'realizedPnl', 0),
'contracts': contracts,
'contractSize': contract_size,
'marginRatio': None,
'liquidationPrice': liquidation_price,
'markPrice': mark_price,
'lastPrice': mark_price,
'collateral': initial_margin,
'marginMode': 'cross',
'side': side,
'percentage': percentage,
'stopLossPrice': stop_loss_price,
'takeProfitPrice': take_profit_price,
'hedged': None,
}
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()
@@ -730,64 +857,6 @@ class mt5(Exchange, ImplicitAPI):
} }
return self.safe_string(types, type, type) return self.safe_string(types, type, type)
def parse_position(self, order_data, market: Market = None):
"""从订单数据解析持仓"""
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,
'unrealizedPnl': self.safe_number(order_data, 'profit', 0),
'realizedPnl': 0,
'liquidationPrice': None,
'marginMode': 'cross',
'percentage': percentage,
'marginRatio': None,
'collateral': None,
'initialMargin': None,
'initialMarginPercentage': None,
'maintenanceMargin': None,
'maintenanceMarginPercentage': None,
'info': order_data,
}
def create_order(self, symbol, type, side, amount, price=None, params={}): def create_order(self, symbol, type, side, amount, price=None, params={}):
"""创建订单""" """创建订单"""
self.load_token() self.load_token()