aaa
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
build/
|
||||||
|
dist/
|
||||||
@@ -1,316 +0,0 @@
|
|||||||
# -*- 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.abstract.mt5 import ImplicitAPI
|
|
||||||
import asyncio
|
|
||||||
import hashlib
|
|
||||||
from ccxt.base.types import Any, Balances, DepositAddress, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, OrderBooks, Trade, TradingFees, Transaction
|
|
||||||
from typing import List
|
|
||||||
from ccxt.base.errors import ExchangeError
|
|
||||||
from ccxt.base.errors import AuthenticationError
|
|
||||||
from ccxt.base.errors import ArgumentsRequired
|
|
||||||
from ccxt.base.errors import InsufficientFunds
|
|
||||||
from ccxt.base.errors import InvalidOrder
|
|
||||||
from ccxt.base.errors import OrderNotFound
|
|
||||||
from ccxt.base.errors import DDoSProtection
|
|
||||||
from ccxt.base.errors import RateLimitExceeded
|
|
||||||
from ccxt.base.errors import ExchangeNotAvailable
|
|
||||||
from ccxt.base.errors import InvalidNonce
|
|
||||||
from ccxt.base.decimal_to_precision import TICK_SIZE
|
|
||||||
from ccxt.base.precise import Precise
|
|
||||||
|
|
||||||
|
|
||||||
class mt5(Exchange, ImplicitAPI):
|
|
||||||
def describe(self) -> Any:
|
|
||||||
return self.deep_extend(super(mt5, self).describe(), {
|
|
||||||
'id': 'mt5',
|
|
||||||
'name': 'MT5',
|
|
||||||
'countries': ['US'],
|
|
||||||
'version': 'v2025.02.05-05.23',
|
|
||||||
'rateLimit': 1000,
|
|
||||||
'hostname': '43.167.188.220:5000',
|
|
||||||
'pro': True,
|
|
||||||
'options': {
|
|
||||||
'host': '18.163.85.196',
|
|
||||||
'port': 443,
|
|
||||||
},
|
|
||||||
'has': {
|
|
||||||
'CORS': True,
|
|
||||||
'spot': True,
|
|
||||||
'margin': True,
|
|
||||||
'swap': True,
|
|
||||||
'future': True,
|
|
||||||
'option': True,
|
|
||||||
'borrowCrossMargin': True,
|
|
||||||
'cancelAllOrders': True,
|
|
||||||
'cancelAllOrdersAfter': True,
|
|
||||||
'cancelOrder': True,
|
|
||||||
'cancelOrders': True,
|
|
||||||
'cancelOrdersForSymbols': True,
|
|
||||||
'closeAllPositions': False,
|
|
||||||
'closePosition': False,
|
|
||||||
'createConvertTrade': True,
|
|
||||||
'createMarketBuyOrderWithCost': True,
|
|
||||||
'createMarketSellOrderWithCost': True,
|
|
||||||
'createOrder': True,
|
|
||||||
'createOrders': True,
|
|
||||||
'createOrderWithTakeProfitAndStopLoss': True,
|
|
||||||
'createPostOnlyOrder': True,
|
|
||||||
'createReduceOnlyOrder': True,
|
|
||||||
'createStopLimitOrder': True,
|
|
||||||
'createStopLossOrder': True,
|
|
||||||
'createStopMarketOrder': True,
|
|
||||||
'createStopOrder': True,
|
|
||||||
'createTakeProfitOrder': True,
|
|
||||||
'createTrailingAmountOrder': True,
|
|
||||||
'createTriggerOrder': True,
|
|
||||||
'editOrder': True,
|
|
||||||
'editOrders': True,
|
|
||||||
'fetchAllGreeks': True,
|
|
||||||
'fetchBalance': True,
|
|
||||||
'fetchBidsAsks': 'emulated',
|
|
||||||
'fetchBorrowInterest': False, # temporarily disabled, doesn't work
|
|
||||||
'fetchBorrowRateHistories': False,
|
|
||||||
'fetchBorrowRateHistory': False,
|
|
||||||
'fetchCanceledAndClosedOrders': True,
|
|
||||||
'fetchCanceledOrders': True,
|
|
||||||
'fetchClosedOrder': True,
|
|
||||||
'fetchClosedOrders': True,
|
|
||||||
'fetchConvertCurrencies': True,
|
|
||||||
'fetchConvertQuote': True,
|
|
||||||
'fetchConvertTrade': True,
|
|
||||||
'fetchConvertTradeHistory': True,
|
|
||||||
'fetchCrossBorrowRate': True,
|
|
||||||
'fetchCrossBorrowRates': False,
|
|
||||||
'fetchCurrencies': True,
|
|
||||||
'fetchDeposit': False,
|
|
||||||
'fetchDepositAddress': True,
|
|
||||||
'fetchDepositAddresses': False,
|
|
||||||
'fetchDepositAddressesByNetwork': True,
|
|
||||||
'fetchDeposits': True,
|
|
||||||
'fetchDepositWithdrawFee': 'emulated',
|
|
||||||
'fetchDepositWithdrawFees': True,
|
|
||||||
'fetchFundingHistory': True,
|
|
||||||
'fetchFundingRate': 'emulated', # emulated in exchange
|
|
||||||
'fetchFundingRateHistory': True,
|
|
||||||
'fetchFundingRates': True,
|
|
||||||
'fetchGreeks': True,
|
|
||||||
'fetchIndexOHLCV': True,
|
|
||||||
'fetchIsolatedBorrowRate': False,
|
|
||||||
'fetchIsolatedBorrowRates': False,
|
|
||||||
'fetchLedger': True,
|
|
||||||
'fetchLeverage': True,
|
|
||||||
'fetchLeverageTiers': True,
|
|
||||||
'fetchLongShortRatio': False,
|
|
||||||
'fetchLongShortRatioHistory': True,
|
|
||||||
'fetchMarginAdjustmentHistory': False,
|
|
||||||
'fetchMarketLeverageTiers': True,
|
|
||||||
'fetchMarkets': True,
|
|
||||||
'fetchMarkOHLCV': True,
|
|
||||||
'fetchMyLiquidations': True,
|
|
||||||
'fetchMySettlementHistory': True,
|
|
||||||
'fetchMyTrades': True,
|
|
||||||
'fetchOHLCV': True,
|
|
||||||
'fetchOpenInterest': True,
|
|
||||||
'fetchOpenInterestHistory': True,
|
|
||||||
'fetchOpenOrder': True,
|
|
||||||
'fetchOpenOrders': True,
|
|
||||||
'fetchOption': True,
|
|
||||||
'fetchOptionChain': True,
|
|
||||||
'fetchOrder': True,
|
|
||||||
'fetchOrderBook': True,
|
|
||||||
'fetchOrders': False,
|
|
||||||
'fetchOrderTrades': True,
|
|
||||||
'fetchPosition': True,
|
|
||||||
'fetchPositionHistory': 'emulated',
|
|
||||||
'fetchPositions': True,
|
|
||||||
'fetchPositionsHistory': True,
|
|
||||||
'fetchPremiumIndexOHLCV': True,
|
|
||||||
'fetchSettlementHistory': True,
|
|
||||||
'fetchTicker': True,
|
|
||||||
'fetchTickers': True,
|
|
||||||
'fetchTime': True,
|
|
||||||
'fetchTrades': True,
|
|
||||||
'fetchTradingFee': True,
|
|
||||||
'fetchTradingFees': True,
|
|
||||||
'fetchTransactions': False,
|
|
||||||
'fetchTransfers': True,
|
|
||||||
'fetchUnderlyingAssets': False,
|
|
||||||
'fetchVolatilityHistory': True,
|
|
||||||
'fetchWithdrawals': True,
|
|
||||||
'repayCrossMargin': True,
|
|
||||||
'sandbox': True,
|
|
||||||
'setLeverage': True,
|
|
||||||
'setMarginMode': True,
|
|
||||||
'setPositionMode': True,
|
|
||||||
'transfer': True,
|
|
||||||
'withdraw': True,
|
|
||||||
},
|
|
||||||
'timeframes': {
|
|
||||||
'1m': 1,
|
|
||||||
'5m': 5,
|
|
||||||
'15m': 15,
|
|
||||||
'30m': 30,
|
|
||||||
'1h': 60,
|
|
||||||
'4h': 240,
|
|
||||||
'1d': 1440,
|
|
||||||
'1w': 10080,
|
|
||||||
'1M': 43200,
|
|
||||||
},
|
|
||||||
'urls': {
|
|
||||||
'logo': '',
|
|
||||||
'api': {
|
|
||||||
'public': 'http://{hostname}',
|
|
||||||
'private': 'http://{hostname}',
|
|
||||||
},
|
|
||||||
'www': 'http://{hostname}',
|
|
||||||
'doc': ['http://{hostname}/index.html'],
|
|
||||||
},
|
|
||||||
'api': {
|
|
||||||
'public': {
|
|
||||||
'get': {
|
|
||||||
'Ping': 1
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'private': {
|
|
||||||
'get': {
|
|
||||||
'Connect': 10,
|
|
||||||
'CheckConnect': 1,
|
|
||||||
'Disconnect': 1,
|
|
||||||
'Symbols': 1,
|
|
||||||
'ServerTimezone':1,
|
|
||||||
'AccountSummary': 1,
|
|
||||||
'AccountDetails': 1,
|
|
||||||
'SymbolList': 1,
|
|
||||||
'GetQuote': 1,
|
|
||||||
'GetQuoteMany': 1,
|
|
||||||
'MarketWatchMany': 1,
|
|
||||||
'OpenedOrders': 1,
|
|
||||||
'ClosedOrders': 1,
|
|
||||||
'OpenedOrder': 1,
|
|
||||||
'OrderHistory': 1,
|
|
||||||
'PriceHistory': 1,
|
|
||||||
'OrderSend': 1,
|
|
||||||
'OrderModify': 1,
|
|
||||||
'OrderClose': 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'wsEndpoint': {
|
|
||||||
'order': "OnOrderUpdate",
|
|
||||||
'quote': "OnQuote",
|
|
||||||
'orderbook': "OnOrderBook",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'requiredCredentials': {
|
|
||||||
'apiKey': True,
|
|
||||||
'secret': True,
|
|
||||||
'hostname': True,
|
|
||||||
},
|
|
||||||
'commonCurrencies': {},
|
|
||||||
'exceptions': {
|
|
||||||
'exact': {
|
|
||||||
'Invalid token': AuthenticationError,
|
|
||||||
'Connection failed': ExchangeError,
|
|
||||||
'Invalid symbol': ExchangeError,
|
|
||||||
'Invalid order': InvalidOrder,
|
|
||||||
'Order not found': OrderNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
async def get_token(self):
|
|
||||||
"""获取或刷新 token - 异步版本"""
|
|
||||||
if self.token and self.token_checked:
|
|
||||||
try:
|
|
||||||
await self.check_connect()
|
|
||||||
return self.token
|
|
||||||
except Exception:
|
|
||||||
# Token 无效,重新连接
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 重新连接获取 token
|
|
||||||
return await self.connect()
|
|
||||||
|
|
||||||
async def connect(self):
|
|
||||||
"""连接到 MT5 账户并获取 token - 异步版本"""
|
|
||||||
request = {
|
|
||||||
'user': self.apiKey,
|
|
||||||
'password': self.secret,
|
|
||||||
'host': self.options['host'],
|
|
||||||
'port': self.options['port'],
|
|
||||||
'connectTimeoutSeconds': 30,
|
|
||||||
}
|
|
||||||
|
|
||||||
print(f"🔧 Debug: Connect request params: {request}")
|
|
||||||
|
|
||||||
response = await self.private_get_connect(request)
|
|
||||||
|
|
||||||
print(f"🔧 Debug: Connect response: {response}")
|
|
||||||
|
|
||||||
self.token = response
|
|
||||||
self.token_checked = True
|
|
||||||
return self.token
|
|
||||||
|
|
||||||
async def check_connect(self):
|
|
||||||
"""检查连接状态 - 异步版本"""
|
|
||||||
request = {
|
|
||||||
'id': await self.get_token(),
|
|
||||||
}
|
|
||||||
return await self.private_get_check_connect(request)
|
|
||||||
|
|
||||||
|
|
||||||
async def fetch_markets(self, params={}):
|
|
||||||
"""获取交易对列表"""
|
|
||||||
|
|
||||||
if not self.token:
|
|
||||||
await self.get_token()
|
|
||||||
request = {
|
|
||||||
'id': self.token,
|
|
||||||
}
|
|
||||||
response = await self.private_get_symbols(self.deep_extend(request, params))
|
|
||||||
|
|
||||||
markets = []
|
|
||||||
if isinstance(response, dict):
|
|
||||||
for symbol, info in response.items():
|
|
||||||
market = self.parse_market(info)
|
|
||||||
if market:
|
|
||||||
markets.append(market)
|
|
||||||
|
|
||||||
return markets
|
|
||||||
|
|
||||||
def parse_market(self, info):
|
|
||||||
"""解析市场信息 - 根据 SymbolInfo 结构修正"""
|
|
||||||
symbol = info.get('currency', '').upper()
|
|
||||||
if not symbol:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': symbol,
|
|
||||||
'symbol': symbol,
|
|
||||||
'base': symbol[:3] if len(symbol) >= 6 else symbol,
|
|
||||||
'quote': symbol[3:] if len(symbol) >= 6 else 'USD',
|
|
||||||
'active': True,
|
|
||||||
'precision': {
|
|
||||||
'price': info.get('digits', 5),
|
|
||||||
'amount': 2,
|
|
||||||
},
|
|
||||||
'limits': {
|
|
||||||
'amount': {
|
|
||||||
'min': 0.01,
|
|
||||||
'max': None,
|
|
||||||
},
|
|
||||||
'price': {
|
|
||||||
'min': None,
|
|
||||||
'max': None,
|
|
||||||
},
|
|
||||||
'cost': {
|
|
||||||
'min': None,
|
|
||||||
'max': None,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'info': info,
|
|
||||||
}
|
|
||||||
@@ -1,312 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from ccxt.async_support.mt5 import mt5 as mt5Parent
|
|
||||||
from ccxt.base.errors import ExchangeError, ArgumentsRequired
|
|
||||||
from ccxt.async_support.base.ws.client import Client
|
|
||||||
import asyncio
|
|
||||||
from typing import Optional, Dict, Any, List
|
|
||||||
|
|
||||||
|
|
||||||
class mt5(mt5Parent):
|
|
||||||
def describe(self):
|
|
||||||
return self.deep_extend(super(mt5, self).describe(), {
|
|
||||||
'platinum': True,
|
|
||||||
'hostname': '43.167.188.220:5000',
|
|
||||||
'has': {
|
|
||||||
# 专业版特有功能
|
|
||||||
'watchPosition': True,
|
|
||||||
'watchOrder': True,
|
|
||||||
'watchLeverage': True,
|
|
||||||
'watchMargin': True,
|
|
||||||
'advancedAPI': True,
|
|
||||||
'batchOrders': True,
|
|
||||||
'modifyOrder': True,
|
|
||||||
'closePosition': True,
|
|
||||||
'setLeverage': True,
|
|
||||||
'setMargin': True,
|
|
||||||
},
|
|
||||||
'urls': {
|
|
||||||
'api': {
|
|
||||||
'ws': 'ws://{hostname}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'options': {
|
|
||||||
'defaultType': 'spot',
|
|
||||||
'watchOrder': {
|
|
||||||
'symbol': None,
|
|
||||||
'orderId': None,
|
|
||||||
},
|
|
||||||
'watchPosition': {
|
|
||||||
'symbol': None,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 专业版特有方法
|
|
||||||
async def watch_order(self, params={}):
|
|
||||||
"""
|
|
||||||
监听特定订单的更新(专业版特有)
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not self.token:
|
|
||||||
await self.get_token()
|
|
||||||
|
|
||||||
try:
|
|
||||||
await self.load_markets()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"错误: {e}")
|
|
||||||
|
|
||||||
# await self.fetch_markets()
|
|
||||||
|
|
||||||
# return
|
|
||||||
request: dict = {
|
|
||||||
'id': self.token,
|
|
||||||
}
|
|
||||||
endpoint = self.api['wsEndpoint']['order']
|
|
||||||
url = self.implode_hostname(self.urls['api']['ws']) + '/' + endpoint
|
|
||||||
url += '?' + self.urlencode(request)
|
|
||||||
return await self.watch(url,'order_position')
|
|
||||||
|
|
||||||
def handle_message(self, client: Client, message):
|
|
||||||
# print(message)
|
|
||||||
errorCode = self.safe_string(message, 'errorCode')
|
|
||||||
if errorCode is not None:
|
|
||||||
self.handle_error_message(client, message)
|
|
||||||
return
|
|
||||||
|
|
||||||
# 处理 MT5 特定的消息
|
|
||||||
message_type = self.safe_string(message, 'type')
|
|
||||||
|
|
||||||
|
|
||||||
if message_type == 'OpenedOrders':
|
|
||||||
print("111111111111111")
|
|
||||||
self.handle_opened_orders(client, message)
|
|
||||||
elif message_type == self.api['wsEndpoint']['orderbook']:
|
|
||||||
self.handle_orderbook(client, message)
|
|
||||||
elif message_type == self.api['wsEndpoint']['quote']:
|
|
||||||
self.handle_ticker(client, message)
|
|
||||||
|
|
||||||
def handle_opened_orders(self, client, message):
|
|
||||||
"""
|
|
||||||
处理 OpenedOrders 消息
|
|
||||||
"""
|
|
||||||
orders_data = self.safe_value(message, 'data', [])
|
|
||||||
message_id = self.safe_string(message, 'id')
|
|
||||||
timestamp = self.safe_integer(message, 'timestampUTC')
|
|
||||||
parsed_orders = []
|
|
||||||
for order_data in orders_data:
|
|
||||||
# 解析每个订单
|
|
||||||
order = self.parse_ws_order(order_data)
|
|
||||||
parsed_orders.append(order)
|
|
||||||
|
|
||||||
# 分发到不同的消息流
|
|
||||||
order_id = order['id']
|
|
||||||
symbol = order['symbol']
|
|
||||||
|
|
||||||
# 更新特定订单
|
|
||||||
client.resolve(order, 'order:' + order_id)
|
|
||||||
|
|
||||||
# 更新符号特定的订单列表
|
|
||||||
client.resolve([order], 'orders:' + symbol)
|
|
||||||
|
|
||||||
print('-------------parsed_orders:',parsed_orders)
|
|
||||||
# 更新所有订单列表
|
|
||||||
client.resolve(parsed_orders, 'orders')
|
|
||||||
|
|
||||||
# 如果有请求ID,也解析到该请求
|
|
||||||
if message_id:
|
|
||||||
client.resolve(parsed_orders, 'request:' + message_id)
|
|
||||||
|
|
||||||
def handle_orderbook(self, client, message):
|
|
||||||
"""处理深度数据"""
|
|
||||||
orderbook = self.parse_ws_order_book(message)
|
|
||||||
symbol = orderbook['symbol']
|
|
||||||
message_hash = 'orderbook:' + symbol
|
|
||||||
client.resolve(orderbook, message_hash)
|
|
||||||
|
|
||||||
def parse_ws_order(self, order, market=None):
|
|
||||||
"""
|
|
||||||
解析 MT5 WebSocket 订单数据为 CCXT 标准格式
|
|
||||||
"""
|
|
||||||
# 提取订单基本信息
|
|
||||||
ticket = self.safe_integer(order, 'ticket')
|
|
||||||
symbol = self.safe_string(order, 'symbol')
|
|
||||||
order_type = self.safe_string(order, 'orderType')
|
|
||||||
deal_type = self.safe_string(order, 'dealType')
|
|
||||||
state = self.safe_string(order, 'state')
|
|
||||||
lots = self.safe_number(order, 'lots')
|
|
||||||
contract_size = self.safe_number(order, 'contractSize', 1.0)
|
|
||||||
open_price = self.safe_number(order, 'openPrice')
|
|
||||||
close_price = self.safe_number(order, 'closePrice')
|
|
||||||
profit = self.safe_number(order, 'profit')
|
|
||||||
print("ggeggegge========")
|
|
||||||
# 获取市场信息
|
|
||||||
market = self.market(symbol) if market is None else market
|
|
||||||
|
|
||||||
# 解析时间戳
|
|
||||||
open_time_str = self.safe_string(order, 'openTime')
|
|
||||||
open_timestamp = self.safe_integer(order, 'openTimestampUTC')
|
|
||||||
|
|
||||||
# 如果 openTimestampUTC 不存在,尝试解析 openTime 字符串
|
|
||||||
if open_timestamp is None and open_time_str:
|
|
||||||
try:
|
|
||||||
# 解析格式: "2025-11-15T04:06:06.994"
|
|
||||||
open_timestamp = self.parse8601(open_time_str)
|
|
||||||
except:
|
|
||||||
open_timestamp = None
|
|
||||||
|
|
||||||
# 解析订单状态
|
|
||||||
status = self.parse_order_status(state)
|
|
||||||
|
|
||||||
# 解析订单方向
|
|
||||||
side = self.parse_order_side(order_type, deal_type)
|
|
||||||
|
|
||||||
# 解析订单类型
|
|
||||||
order_type_parsed = self.parse_order_type(order_type)
|
|
||||||
|
|
||||||
# 计算数量 (lots * contractSize)
|
|
||||||
amount = lots * contract_size if (lots is not None and contract_size is not None) else None
|
|
||||||
|
|
||||||
# 解析成交数量
|
|
||||||
volume = self.safe_integer(order, 'volume', 0)
|
|
||||||
close_volume = self.safe_integer(order, 'closeVolume', 0)
|
|
||||||
|
|
||||||
# 对于已成交订单,filled 应该是 volume
|
|
||||||
filled = volume
|
|
||||||
remaining = 0 # MT5 中订单要么完全成交,要么没有
|
|
||||||
|
|
||||||
# 解析止盈止损
|
|
||||||
stop_loss = self.safe_number(order, 'stopLoss')
|
|
||||||
take_profit = self.safe_number(order, 'takeProfit')
|
|
||||||
|
|
||||||
# 构建标准订单对象
|
|
||||||
result = {
|
|
||||||
'id': str(ticket),
|
|
||||||
'clientOrderId': None,
|
|
||||||
'datetime': self.iso8601(open_timestamp) if open_timestamp else None,
|
|
||||||
'timestamp': open_timestamp,
|
|
||||||
'lastTradeTimestamp': None,
|
|
||||||
'status': status,
|
|
||||||
'symbol': market['symbol'] if market else symbol,
|
|
||||||
'type': order_type_parsed,
|
|
||||||
'side': side,
|
|
||||||
'price': open_price,
|
|
||||||
'amount': amount,
|
|
||||||
'filled': filled,
|
|
||||||
'remaining': remaining,
|
|
||||||
'cost': None, # 可以计算: amount * price
|
|
||||||
'average': None,
|
|
||||||
'fee': {
|
|
||||||
'currency': market['quote'] if market else 'USD',
|
|
||||||
'cost': self.safe_number(order, 'fee', 0),
|
|
||||||
},
|
|
||||||
'trades': None,
|
|
||||||
'info': order,
|
|
||||||
}
|
|
||||||
|
|
||||||
# 计算成本
|
|
||||||
if amount is not None and open_price is not None:
|
|
||||||
result['cost'] = amount * open_price
|
|
||||||
|
|
||||||
# 添加 MT5 特定字段
|
|
||||||
result['stopLoss'] = stop_loss
|
|
||||||
result['takeProfit'] = take_profit
|
|
||||||
result['profit'] = profit
|
|
||||||
result['commission'] = self.safe_number(order, 'commission', 0)
|
|
||||||
result['swap'] = self.safe_number(order, 'swap', 0)
|
|
||||||
result['comment'] = self.safe_string(order, 'comment', '')
|
|
||||||
print("-----------result:",result)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def parse_order_status(self, status):
|
|
||||||
"""
|
|
||||||
解析 MT5 订单状态
|
|
||||||
"""
|
|
||||||
statuses = {
|
|
||||||
'Filled': 'closed', # 已成交
|
|
||||||
'PartialFilled': 'open', # 部分成交
|
|
||||||
'Pending': 'open', # 挂单中
|
|
||||||
'Cancelled': 'canceled', # 已取消
|
|
||||||
'Rejected': 'rejected', # 已拒绝
|
|
||||||
'Expired': 'expired', # 已过期
|
|
||||||
# 根据您的数据添加更多状态映射
|
|
||||||
'Active': 'open',
|
|
||||||
'Closed': 'closed',
|
|
||||||
}
|
|
||||||
return self.safe_string(statuses, status, status.lower() if status else 'unknown')
|
|
||||||
|
|
||||||
def parse_order_side(self, order_type, deal_type):
|
|
||||||
"""
|
|
||||||
解析订单方向
|
|
||||||
"""
|
|
||||||
# 优先使用 deal_type,因为它更准确
|
|
||||||
if deal_type:
|
|
||||||
if 'Buy' in deal_type:
|
|
||||||
return 'buy'
|
|
||||||
elif 'Sell' in deal_type:
|
|
||||||
return 'sell'
|
|
||||||
|
|
||||||
# 其次使用 order_type
|
|
||||||
if order_type:
|
|
||||||
if order_type == 'Buy':
|
|
||||||
return 'buy'
|
|
||||||
elif order_type == 'Sell':
|
|
||||||
return 'sell'
|
|
||||||
elif 'Buy' in order_type:
|
|
||||||
return 'buy'
|
|
||||||
elif 'Sell' in order_type:
|
|
||||||
return 'sell'
|
|
||||||
|
|
||||||
return 'unknown'
|
|
||||||
|
|
||||||
def parse_order_type(self, order_type):
|
|
||||||
"""
|
|
||||||
解析订单类型
|
|
||||||
"""
|
|
||||||
if not order_type:
|
|
||||||
return 'market' # 默认类型
|
|
||||||
|
|
||||||
order_type_lower = order_type.lower()
|
|
||||||
|
|
||||||
if 'limit' in order_type_lower:
|
|
||||||
return 'limit'
|
|
||||||
elif 'stop' in order_type_lower:
|
|
||||||
return 'stop'
|
|
||||||
elif 'market' in order_type_lower:
|
|
||||||
return 'market'
|
|
||||||
else:
|
|
||||||
# 根据 dealType 判断
|
|
||||||
return 'market' # 默认为市价单
|
|
||||||
|
|
||||||
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
||||||
"""签名请求 URL 构建"""
|
|
||||||
endpoint = '/' + self.implode_params(path, params)
|
|
||||||
url = self.implode_hostname(self.urls['api'][api]) + endpoint
|
|
||||||
headers = headers if (headers is not None) else {}
|
|
||||||
|
|
||||||
# 对于 GET 请求,将参数添加到查询字符串
|
|
||||||
if method == 'GET' and params:
|
|
||||||
# 特殊处理数组参数
|
|
||||||
query_params = {}
|
|
||||||
for key, value in params.items():
|
|
||||||
if isinstance(value, list):
|
|
||||||
# 对于数组参数,可能需要特殊编码
|
|
||||||
query_params[key] = ','.join(value)
|
|
||||||
else:
|
|
||||||
query_params[key] = value
|
|
||||||
|
|
||||||
url += '?' + self.urlencode(query_params)
|
|
||||||
elif method == 'GET' and params:
|
|
||||||
url += '?' + self.urlencode(params)
|
|
||||||
|
|
||||||
# print(f"🔧 Debug: Final URL: {url}")
|
|
||||||
|
|
||||||
return {
|
|
||||||
'url': url,
|
|
||||||
'method': method,
|
|
||||||
'body': body,
|
|
||||||
'headers': headers
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user