791 lines
27 KiB
Python
791 lines
27 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from ccxt.base.exchange import Exchange
|
|
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 typing import List
|
|
from typing import List, Optional # 添加 Optional 导入
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import AuthenticationError
|
|
from ccxt.base.errors import PermissionDenied
|
|
from ccxt.base.errors import AccountSuspended
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import BadSymbol
|
|
from ccxt.base.errors import NoChange
|
|
from ccxt.base.errors import MarginModeAlreadySet
|
|
from ccxt.base.errors import ManualInteractionNeeded
|
|
from ccxt.base.errors import InsufficientFunds
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.errors import OrderNotFound
|
|
from ccxt.base.errors import NotSupported
|
|
from ccxt.base.errors import RateLimitExceeded
|
|
from ccxt.base.errors import InvalidNonce
|
|
from ccxt.base.errors import RequestTimeout
|
|
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': '10.203.0.6:5000',
|
|
'pro': True,
|
|
'options': {
|
|
'host': '18.163.85.196',
|
|
'port': 443,
|
|
'connectTimeoutSeconds': 30,
|
|
},
|
|
'has': {
|
|
'CORS': True,
|
|
'spot': True,
|
|
'margin': True,
|
|
'swap': False,
|
|
'future': False,
|
|
'option': False,
|
|
'cancelOrder': True,
|
|
'createOrder': True,
|
|
'fetchBalance': True,
|
|
'fetchClosedOrders': True,
|
|
'fetchMarkets': True,
|
|
'fetchMyTrades': True,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenOrders': True,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchTicker': True,
|
|
'fetchTickers': 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,
|
|
'SymbolParamsMany': 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,
|
|
'SubscribeOhlc': 1,
|
|
'UnsubscribeOhlc': 1,
|
|
'Subscribe': 1,
|
|
'UnSubscribe': 1,
|
|
},
|
|
},
|
|
},
|
|
'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,
|
|
},
|
|
},
|
|
})
|
|
|
|
def ping_server(self):
|
|
"""测试连接"""
|
|
response = self.public_get_ping()
|
|
if response == 'OK':
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def get_token(self):
|
|
"""获取或刷新 token"""
|
|
if hasattr(self, 'token') and self.token:
|
|
try:
|
|
self.check_connect()
|
|
return self.token
|
|
except Exception:
|
|
# Token 无效,重新连接
|
|
pass
|
|
|
|
# 重新连接获取 token
|
|
return self.connect()
|
|
|
|
def connect(self):
|
|
"""连接到 MT5 账户并获取 token"""
|
|
params = {
|
|
'user': self.apiKey,
|
|
'password': self.secret,
|
|
'host': self.options['host'],
|
|
'port': self.options['port'],
|
|
'connectTimeoutSeconds': self.options['connectTimeoutSeconds'],
|
|
}
|
|
|
|
response = self.private_get_connect(params)
|
|
|
|
self.token = response
|
|
return self.token
|
|
|
|
def check_connect(self):
|
|
"""检查连接状态"""
|
|
params = {
|
|
'id': self.get_token(),
|
|
}
|
|
return self.private_get_checkconnect(params)
|
|
|
|
def disconnect(self):
|
|
"""断开连接"""
|
|
if hasattr(self, 'token') and self.token:
|
|
params = {
|
|
'id': self.token,
|
|
}
|
|
try:
|
|
self.private_get_disconnect(params)
|
|
except Exception:
|
|
pass
|
|
finally:
|
|
self.token = None
|
|
|
|
def load_token(self):
|
|
"""确保 token 已加载"""
|
|
if not hasattr(self, 'token') or not self.token:
|
|
self.get_token()
|
|
|
|
def server_timezone(self):
|
|
"""获得mt5服务器时区"""
|
|
if hasattr(self, 'timezone'):
|
|
return self.timezone
|
|
else:
|
|
self.load_token()
|
|
request = {
|
|
'id': self.token,
|
|
}
|
|
response = self.private_get_servertimezone(request)
|
|
self.timezone = int(float(response))
|
|
return self.timezone
|
|
|
|
def fetch_markets(self, params={}):
|
|
"""获取交易对列表 - 修复版本"""
|
|
self.load_token()
|
|
request = {
|
|
'id': self.token,
|
|
}
|
|
|
|
try:
|
|
response = self.private_get_symbols(self.extend(request, params))
|
|
|
|
markets = []
|
|
if isinstance(response, dict):
|
|
for symbol, info in response.items():
|
|
try:
|
|
market = self.parse_market(info)
|
|
if market and market.get('symbol'):
|
|
markets.append(market)
|
|
except Exception as e:
|
|
if self.verbose:
|
|
print(f"跳过交易对 {symbol}: {e}")
|
|
continue
|
|
|
|
# 设置市场数据
|
|
if markets:
|
|
self.markets = {}
|
|
self.symbols = []
|
|
for market in markets:
|
|
id = market['id']
|
|
symbol = market['symbol']
|
|
self.markets[id] = market
|
|
self.markets[symbol] = market
|
|
self.symbols.append(symbol)
|
|
|
|
self.symbols = sorted(self.symbols)
|
|
self.ids = sorted(self.markets.keys())
|
|
|
|
return markets
|
|
|
|
except Exception as e:
|
|
raise ExchangeError(f"获取市场数据失败: {e}")
|
|
|
|
def parse_market(self, info):
|
|
"""解析市场信息 - 更健壮的版本"""
|
|
try:
|
|
if not isinstance(info, dict):
|
|
return None
|
|
|
|
symbol = self.safe_string(info, 'currency', '')
|
|
if not symbol:
|
|
return None
|
|
|
|
symbol = symbol.upper().strip()
|
|
|
|
# 处理符号格式
|
|
if len(symbol) < 6:
|
|
base = symbol
|
|
quote = 'USD'
|
|
else:
|
|
base = symbol[:3]
|
|
quote = symbol[3:]
|
|
|
|
digits = self.safe_integer(info, 'digits', 5)
|
|
if digits is not None:
|
|
try:
|
|
digits = int(digits)
|
|
except (ValueError, TypeError):
|
|
digits = 5
|
|
|
|
market_id = symbol
|
|
|
|
return {
|
|
'id': market_id,
|
|
'symbol': base + '/' + quote,
|
|
'base': base,
|
|
'quote': quote,
|
|
'baseId': base,
|
|
'quoteId': quote,
|
|
'active': True,
|
|
'type': 'spot',
|
|
'spot': True,
|
|
'margin': True,
|
|
'precision': {
|
|
'price': digits,
|
|
'amount': 2,
|
|
},
|
|
'limits': {
|
|
'amount': {
|
|
'min': self.safe_number(info, 'minVolume', 0.01),
|
|
'max': self.safe_number(info, 'maxVolume'),
|
|
},
|
|
'price': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'cost': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'info': info,
|
|
}
|
|
except Exception as e:
|
|
if self.verbose:
|
|
print(f"解析市场信息失败: {e}, info: {info}")
|
|
return None
|
|
|
|
def fetch_ticker(self, symbol, params={}):
|
|
"""获取行情数据"""
|
|
self.load_token()
|
|
market = self.market(symbol)
|
|
request = {
|
|
'id': self.token,
|
|
'symbol': market['id'],
|
|
}
|
|
|
|
response = self.private_get_getquote(self.extend(request, params))
|
|
return self.parse_ticker(response, market)
|
|
|
|
def parse_ticker(self, ticker, market=None):
|
|
"""解析行情数据"""
|
|
symbol = market['symbol'] if market else None
|
|
timestamp = None
|
|
if ticker.get('time'):
|
|
try:
|
|
timestamp = self.parse8601(ticker.get('time'))
|
|
except:
|
|
timestamp = None
|
|
|
|
return {
|
|
'symbol': symbol,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp) if timestamp else None,
|
|
'high': None,
|
|
'low': None,
|
|
'bid': self.safe_number(ticker, 'bid'),
|
|
'bidVolume': None,
|
|
'ask': self.safe_number(ticker, 'ask'),
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': None,
|
|
'close': None,
|
|
'last': self.safe_number(ticker, 'last'),
|
|
'previousClose': None,
|
|
'change': None,
|
|
'percentage': None,
|
|
'average': None,
|
|
'baseVolume': self.safe_number(ticker, 'volume'),
|
|
'quoteVolume': None,
|
|
'info': ticker,
|
|
}
|
|
|
|
def fetch_tickers(self, symbols: Optional[List[str]] = None, params={}):
|
|
"""获取多个交易对的行情数据"""
|
|
self.load_token()
|
|
|
|
request = {
|
|
'id': self.token,
|
|
}
|
|
|
|
if symbols is not None:
|
|
mt5_symbols = []
|
|
for symbol in symbols:
|
|
market = self.market(symbol)
|
|
mt5_symbols.append(market['id'])
|
|
request['symbols'] = mt5_symbols
|
|
|
|
try:
|
|
response = self.private_get_getquotemany(self.extend(request, params))
|
|
return self.parse_tickers(response, symbols)
|
|
except Exception as e:
|
|
if symbols is not None:
|
|
return self.fetch_tickers_fallback(symbols, params)
|
|
else:
|
|
raise ExchangeError(f"获取批量行情失败: {e}")
|
|
|
|
def fetch_tickers_fallback(self, symbols, params={}):
|
|
"""回退方法:逐个获取交易对行情"""
|
|
tickers = {}
|
|
for symbol in symbols:
|
|
try:
|
|
ticker = self.fetch_ticker(symbol, params)
|
|
tickers[symbol] = ticker
|
|
except Exception as e:
|
|
if self.verbose:
|
|
print(f"获取 {symbol} 行情失败: {e}")
|
|
continue
|
|
return tickers
|
|
|
|
def parse_tickers(self, response, symbols=None):
|
|
"""解析批量行情数据"""
|
|
tickers = {}
|
|
|
|
if isinstance(response, list):
|
|
for ticker_data in response:
|
|
try:
|
|
ticker = self.parse_ticker(ticker_data)
|
|
if ticker and ticker.get('symbol'):
|
|
tickers[ticker['symbol']] = ticker
|
|
except Exception as e:
|
|
if self.verbose:
|
|
print(f"解析行情数据失败: {e}")
|
|
continue
|
|
elif isinstance(response, dict):
|
|
for symbol_key, ticker_data in response.items():
|
|
try:
|
|
ticker = self.parse_ticker(ticker_data)
|
|
if ticker and ticker.get('symbol'):
|
|
tickers[ticker['symbol']] = ticker
|
|
except Exception as e:
|
|
if self.verbose:
|
|
print(f"解析行情数据失败 {symbol_key}: {e}")
|
|
continue
|
|
|
|
if symbols is not None:
|
|
ordered_tickers = {}
|
|
for symbol in symbols:
|
|
if symbol in tickers:
|
|
ordered_tickers[symbol] = tickers[symbol]
|
|
return ordered_tickers
|
|
|
|
return tickers
|
|
|
|
def fetch_order_book(self, symbol, limit=None, params={}):
|
|
"""获取订单簿"""
|
|
self.load_token()
|
|
market = self.market(symbol)
|
|
request = {
|
|
'id': self.token,
|
|
'symbol': market['id'],
|
|
}
|
|
response = self.private_get_getquote(self.extend(request, params))
|
|
|
|
bid = self.safe_number(response, 'bid')
|
|
ask = self.safe_number(response, 'ask')
|
|
|
|
return {
|
|
'symbol': symbol,
|
|
'bids': [[bid, 1]] if bid else [],
|
|
'asks': [[ask, 1]] if ask else [],
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'nonce': None,
|
|
}
|
|
|
|
def fetch_balance(self, params={}):
|
|
"""获取账户余额"""
|
|
self.load_token()
|
|
request = {
|
|
'id': self.token,
|
|
}
|
|
response = self.private_get_accountsummary(self.extend(request, params))
|
|
|
|
return self.parse_balance(response)
|
|
|
|
def parse_balance(self, response):
|
|
"""解析余额信息"""
|
|
result = {
|
|
'info': response,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
}
|
|
|
|
currency = 'USDT'
|
|
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)
|
|
|
|
def fetch_account_details(self, params={}):
|
|
"""获取账户信息"""
|
|
self.load_token()
|
|
request = {
|
|
'id': self.token,
|
|
}
|
|
response = 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),
|
|
}
|
|
|
|
def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
|
|
"""获取未平仓订单"""
|
|
self.load_token()
|
|
request = {
|
|
'id': self.token,
|
|
}
|
|
|
|
response = self.private_get_openedorders(self.extend(request, params))
|
|
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
filtered_orders = []
|
|
for order in response:
|
|
if isinstance(order, dict) and order.get('symbol') == market['id']:
|
|
filtered_orders.append(order)
|
|
return self.parse_orders(filtered_orders, market, since, limit)
|
|
else:
|
|
return self.parse_orders(response, None, since, limit)
|
|
|
|
def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
|
|
"""获取已平仓订单"""
|
|
self.load_token()
|
|
request = {
|
|
'id': self.token,
|
|
}
|
|
|
|
response = self.private_get_closedorders(self.extend(request, params))
|
|
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
filtered_orders = []
|
|
for order in response:
|
|
if isinstance(order, dict) and order.get('symbol') == market['id']:
|
|
filtered_orders.append(order)
|
|
return self.parse_orders(filtered_orders, market, since, limit)
|
|
else:
|
|
return self.parse_orders(response, None, since, limit)
|
|
|
|
def parse_order(self, order, market=None):
|
|
"""解析订单信息"""
|
|
try:
|
|
id = self.safe_string(order, 'ticket')
|
|
market_id = self.safe_string(order, 'symbol')
|
|
|
|
symbol = None
|
|
if market is not None:
|
|
symbol = market['symbol']
|
|
elif market_id is not None:
|
|
if len(market_id) >= 6:
|
|
base = market_id[:3]
|
|
quote = market_id[3:]
|
|
symbol = base + '/' + quote
|
|
else:
|
|
symbol = market_id
|
|
|
|
timestamp = self.parse8601(self.safe_string(order, 'openTime'))
|
|
last_trade_timestamp = self.parse8601(self.safe_string(order, 'closeTime'))
|
|
|
|
status = self.parse_order_status(self.safe_string(order, 'state'))
|
|
side = self.parse_order_side(self.safe_string(order, 'orderType'))
|
|
type = self.parse_order_type(self.safe_string(order, 'orderType'))
|
|
|
|
price = self.safe_number(order, 'openPrice')
|
|
amount = self.safe_number(order, 'lots')
|
|
filled = self.safe_number(order, 'closeLots', 0)
|
|
|
|
remaining = None
|
|
if amount is not None and filled is not None:
|
|
remaining = amount - filled
|
|
|
|
cost = None
|
|
if price is not None and filled is not None:
|
|
cost = price * filled
|
|
|
|
fee = None
|
|
fee_cost = self.safe_number(order, 'commission', 0)
|
|
if fee_cost != 0:
|
|
fee = {
|
|
'cost': fee_cost,
|
|
'currency': None,
|
|
}
|
|
|
|
return self.safe_order({
|
|
'id': id,
|
|
'clientOrderId': None,
|
|
'datetime': self.iso8601(timestamp),
|
|
'timestamp': timestamp,
|
|
'lastTradeTimestamp': last_trade_timestamp,
|
|
'lastUpdateTimestamp': last_trade_timestamp,
|
|
'status': status,
|
|
'symbol': symbol,
|
|
'type': type,
|
|
'timeInForce': None,
|
|
'postOnly': None,
|
|
'side': side,
|
|
'price': price,
|
|
'stopLossPrice': self.safe_number(order, 'stopLoss'),
|
|
'takeProfitPrice': self.safe_number(order, 'takeProfit'),
|
|
'reduceOnly': None,
|
|
'triggerPrice': None,
|
|
'amount': amount,
|
|
'filled': filled,
|
|
'remaining': remaining,
|
|
'cost': cost,
|
|
'trades': None,
|
|
'fee': fee,
|
|
'info': order,
|
|
'average': None,
|
|
})
|
|
except Exception as e:
|
|
if self.verbose:
|
|
print(f"解析订单失败: {e}, order: {order}")
|
|
raise e
|
|
|
|
def parse_order_status(self, status):
|
|
statuses = {
|
|
'Started': 'open',
|
|
'Placed': 'open',
|
|
'Cancelled': 'canceled',
|
|
'Partial': 'open',
|
|
'Filled': 'closed',
|
|
'Rejected': 'rejected',
|
|
'Expired': 'expired',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_order_side(self, side):
|
|
sides = {
|
|
'Buy': 'buy',
|
|
'Sell': 'sell',
|
|
'BuyLimit': 'buy',
|
|
'SellLimit': 'sell',
|
|
'BuyStop': 'buy',
|
|
'SellStop': 'sell',
|
|
}
|
|
return self.safe_string(sides, side, side)
|
|
|
|
def parse_order_type(self, type):
|
|
types = {
|
|
'Buy': 'market',
|
|
'Sell': 'market',
|
|
'BuyLimit': 'limit',
|
|
'SellLimit': 'limit',
|
|
'BuyStop': 'stop',
|
|
'SellStop': 'stop',
|
|
}
|
|
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={}):
|
|
"""创建订单"""
|
|
self.load_token()
|
|
market = self.market(symbol)
|
|
|
|
request = {
|
|
'id': self.token,
|
|
'symbol': market['id'],
|
|
'volume': amount,
|
|
}
|
|
|
|
operation_map = {
|
|
'market': {
|
|
'buy': 'Buy',
|
|
'sell': 'Sell',
|
|
},
|
|
'limit': {
|
|
'buy': 'BuyLimit',
|
|
'sell': 'SellLimit',
|
|
},
|
|
'stop': {
|
|
'buy': 'BuyStop',
|
|
'sell': 'SellStop',
|
|
},
|
|
}
|
|
|
|
if type in operation_map and side in operation_map[type]:
|
|
request['operation'] = operation_map[type][side]
|
|
else:
|
|
raise InvalidOrder(self.id + ' createOrder does not support order type ' + type + ' and side ' + side)
|
|
|
|
if type in ['limit', 'stop'] and price is not None:
|
|
request['price'] = price
|
|
|
|
stop_loss = self.safe_number(params, 'stopLoss')
|
|
take_profit = self.safe_number(params, 'takeProfit')
|
|
if stop_loss is not None:
|
|
request['stoploss'] = stop_loss
|
|
if take_profit is not None:
|
|
request['takeprofit'] = take_profit
|
|
|
|
response = self.private_get_ordersend(self.extend(request, params))
|
|
return self.parse_order(response, market)
|
|
|
|
def cancel_order(self, id, symbol=None, params={}):
|
|
"""取消订单"""
|
|
self.load_token()
|
|
request = {
|
|
'id': self.token,
|
|
'ticket': int(id),
|
|
}
|
|
response = self.private_get_orderclose(self.extend(request, params))
|
|
return self.parse_order(response)
|
|
|
|
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
"""签名请求"""
|
|
base_url = self.implode_hostname(self.urls['api'][api])
|
|
url = base_url + '/' + path
|
|
query = self.omit(params, self.extract_params(path))
|
|
|
|
if method == 'GET' and query:
|
|
url += '?' + self.urlencode(query)
|
|
|
|
return {
|
|
'url': url,
|
|
'method': method,
|
|
'body': body,
|
|
'headers': headers
|
|
} |