Files
ccxt_with_mt5/ccxt/pro/derive.py
lz_db 0fab423a18 add
2025-11-16 12:31:03 +08:00

705 lines
28 KiB
Python

# -*- 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
import ccxt.async_support
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById
from ccxt.base.types import Any, Bool, Int, Order, OrderBook, Str, Ticker, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import UnsubscribeError
class derive(ccxt.async_support.derive):
def describe(self) -> Any:
return self.deep_extend(super(derive, self).describe(), {
'has': {
'ws': False,
'watchBalance': False,
'watchMyTrades': True,
'watchOHLCV': False,
'watchOrderBook': True,
'watchOrders': True,
'watchTicker': True,
'watchTickers': False,
'watchBidsAsks': False,
'watchTrades': True,
'watchTradesForSymbols': False,
'watchPositions': False,
},
'urls': {
'api': {
'ws': 'wss://api.lyra.finance/ws',
},
'test': {
'ws': 'wss://api-demo.lyra.finance/ws',
},
},
'options': {
'tradesLimit': 1000,
'ordersLimit': 1000,
'requestId': {},
},
'streaming': {
'keepAlive': 9000,
},
'exceptions': {
'ws': {
'exact': {},
},
},
})
def request_id(self, url):
options = self.safe_value(self.options, 'requestId', {})
previousValue = self.safe_integer(options, url, 0)
newValue = self.sum(previousValue, 1)
self.options['requestId'][url] = newValue
return newValue
async def watch_public(self, messageHash, message, subscription):
url = self.urls['api']['ws']
requestId = self.request_id(url)
request = self.extend(message, {
'id': requestId,
})
subscription = self.extend(subscription, {
'id': requestId,
'method': 'subscribe',
})
return await self.watch(url, messageHash, request, messageHash, subscription)
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
https://docs.derive.xyz/reference/orderbook-instrument_name-group-depth
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
:param str symbol: unified symbol of the market to fetch the order book for
:param int [limit]: the maximum amount of order book entries to return.
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
"""
await self.load_markets()
if limit is None:
limit = 10
market = self.market(symbol)
topic = 'orderbook.' + market['id'] + '.10.' + self.number_to_string(limit)
request: dict = {
'method': 'subscribe',
'params': {
'channels': [
topic,
],
},
}
subscription: dict = {
'name': topic,
'symbol': symbol,
'limit': limit,
'params': params,
}
orderbook = await self.watch_public(topic, request, subscription)
return orderbook.limit()
def handle_order_book(self, client: Client, message):
#
# {
# method: 'subscription',
# params: {
# channel: 'orderbook.BTC-PERP.10.1',
# data: {
# timestamp: 1738331231506,
# instrument_name: 'BTC-PERP',
# publish_id: 628419,
# bids: [['104669', '40']],
# asks: [['104736', '40']]
# }
# }
# }
#
params = self.safe_dict(message, 'params')
data = self.safe_dict(params, 'data')
marketId = self.safe_string(data, 'instrument_name')
market = self.safe_market(marketId)
symbol = market['symbol']
topic = self.safe_string(params, 'channel')
if not (symbol in self.orderbooks):
defaultLimit = self.safe_integer(self.options, 'watchOrderBookLimit', 1000)
subscription = client.subscriptions[topic]
limit = self.safe_integer(subscription, 'limit', defaultLimit)
self.orderbooks[symbol] = self.order_book({}, limit)
orderbook = self.orderbooks[symbol]
timestamp = self.safe_integer(data, 'timestamp')
snapshot = self.parse_order_book(data, symbol, timestamp, 'bids', 'asks')
orderbook.reset(snapshot)
client.resolve(orderbook, topic)
async def watch_ticker(self, symbol: str, params={}) -> Ticker:
"""
https://docs.derive.xyz/reference/ticker-instrument_name-interval
watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
:param str symbol: unified symbol of the market to fetch the ticker for
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
await self.load_markets()
market = self.market(symbol)
topic = 'ticker.' + market['id'] + '.100'
request: dict = {
'method': 'subscribe',
'params': {
'channels': [
topic,
],
},
}
subscription: dict = {
'name': topic,
'symbol': symbol,
'params': params,
}
return await self.watch_public(topic, request, subscription)
def handle_ticker(self, client: Client, message):
#
# {
# method: 'subscription',
# params: {
# channel: 'ticker.BTC-PERP.100',
# data: {
# timestamp: 1738485104439,
# instrument_ticker: {
# instrument_type: 'perp',
# instrument_name: 'BTC-PERP',
# scheduled_activation: 1701840228,
# scheduled_deactivation: '9223372036854775807',
# is_active: True,
# tick_size: '0.1',
# minimum_amount: '0.01',
# maximum_amount: '10000',
# amount_step: '0.001',
# mark_price_fee_rate_cap: '0',
# maker_fee_rate: '0.0001',
# taker_fee_rate: '0.0003',
# base_fee: '0.1',
# base_currency: 'BTC',
# quote_currency: 'USD',
# option_details: null,
# perp_details: {
# index: 'BTC-USD',
# max_rate_per_hour: '0.004',
# min_rate_per_hour: '-0.004',
# static_interest_rate: '0.0000125',
# aggregate_funding: '10581.779418721074588722',
# funding_rate: '0.000024792239208858'
# },
# erc20_details: null,
# base_asset_address: '0xDBa83C0C654DB1cd914FA2710bA743e925B53086',
# base_asset_sub_id: '0',
# pro_rata_fraction: '0',
# fifo_min_allocation: '0',
# pro_rata_amount_step: '0.1',
# best_ask_amount: '0.131',
# best_ask_price: '99898.6',
# best_bid_amount: '0.056',
# best_bid_price: '99889.1',
# five_percent_bid_depth: '11.817',
# five_percent_ask_depth: '9.116',
# option_pricing: null,
# index_price: '99883.8',
# mark_price: '99897.52408421244763303548098',
# stats: {
# contract_volume: '92.395',
# num_trades: '2924',
# open_interest: '33.743468027373780786',
# high: '102320.4',
# low: '99064.3',
# percent_change: '-0.021356',
# usd_change: '-2178'
# },
# timestamp: 1738485165881,
# min_price: '97939.1',
# max_price: '101895.2'
# }
# }
# }
# }
#
params = self.safe_dict(message, 'params')
rawData = self.safe_dict(params, 'data')
data = self.safe_dict(rawData, 'instrument_ticker')
topic = self.safe_value(params, 'channel')
ticker = self.parse_ticker(data)
self.tickers[ticker['symbol']] = ticker
client.resolve(ticker, topic)
return message
async def un_watch_order_book(self, symbol: str, params={}) -> Any:
"""
unsubscribe from the orderbook channel
:param str symbol: unified symbol of the market to fetch the order book for
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.limit]: orderbook limit, default is None
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
"""
await self.load_markets()
limit = self.safe_integer(params, 'limit')
if limit is None:
limit = 10
market = self.market(symbol)
topic = 'orderbook.' + market['id'] + '.10.' + self.number_to_string(limit)
messageHash = 'unwatch' + topic
request: dict = {
'method': 'unsubscribe',
'params': {
'channels': [
topic,
],
},
}
subscription: dict = {
'name': topic,
}
return await self.un_watch_public(messageHash, request, subscription)
async def un_watch_trades(self, symbol: str, params={}) -> Any:
"""
unsubscribe from the trades channel
:param str symbol: unified symbol of the market to unwatch the trades for
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns any: status of the unwatch request
"""
await self.load_markets()
market = self.market(symbol)
topic = 'trades.' + market['id']
messageHah = 'unwatch' + topic
request: dict = {
'method': 'unsubscribe',
'params': {
'channels': [
topic,
],
},
}
subscription: dict = {
'name': topic,
}
return await self.un_watch_public(messageHah, request, subscription)
async def un_watch_public(self, messageHash, message, subscription):
url = self.urls['api']['ws']
requestId = self.request_id(url)
request = self.extend(message, {
'id': requestId,
})
subscription = self.extend(subscription, {
'id': requestId,
'method': 'unsubscribe',
})
return await self.watch(url, messageHash, request, messageHash, subscription)
def handle_order_book_un_subscription(self, client: Client, topic):
parsedTopic = topic.split('.')
marketId = self.safe_string(parsedTopic, 1)
market = self.safe_market(marketId)
symbol = market['symbol']
if symbol in self.orderbooks:
del self.orderbooks[symbol]
if topic in client.subscriptions:
del client.subscriptions[topic]
error = UnsubscribeError(self.id + ' orderbook ' + symbol)
client.reject(error, topic)
client.resolve(error, 'unwatch' + topic)
def handle_trades_un_subscription(self, client: Client, topic):
parsedTopic = topic.split('.')
marketId = self.safe_string(parsedTopic, 1)
market = self.safe_market(marketId)
symbol = market['symbol']
if symbol in self.orderbooks:
del self.trades[symbol]
if topic in client.subscriptions:
del client.subscriptions[topic]
error = UnsubscribeError(self.id + ' trades ' + symbol)
client.reject(error, topic)
client.resolve(error, 'unwatch' + topic)
def handle_un_subscribe(self, client: Client, message):
#
# {
# id: 1,
# result: {
# status: {'orderbook.BTC-PERP.10.10': 'ok'},
# remaining_subscriptions: []
# }
# }
#
result = self.safe_dict(message, 'result')
status = self.safe_dict(result, 'status')
if status is not None:
topics = list(status.keys())
for i in range(0, len(topics)):
topic = topics[i]
if topic.find('orderbook') >= 0:
self.handle_order_book_un_subscription(client, topic)
elif topic.find('trades') >= 0:
self.handle_trades_un_subscription(client, topic)
return message
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
watches information on multiple trades made in a market
https://docs.derive.xyz/reference/trades-instrument_name
:param str symbol: unified market symbol of the market trades were made in
:param int [since]: the earliest time in ms to fetch trades for
:param int [limit]: the maximum number of trade structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
"""
await self.load_markets()
market = self.market(symbol)
topic = 'trades.' + market['id']
request: dict = {
'method': 'subscribe',
'params': {
'channels': [
topic,
],
},
}
subscription: dict = {
'name': topic,
'symbol': symbol,
'params': params,
}
trades = await self.watch_public(topic, request, subscription)
if self.newUpdates:
limit = trades.getLimit(market['symbol'], limit)
return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
def handle_trade(self, client: Client, message):
#
#
params = self.safe_dict(message, 'params')
data = self.safe_dict(params, 'data')
topic = self.safe_value(params, 'channel')
parsedTopic = topic.split('.')
marketId = self.safe_string(parsedTopic, 1)
market = self.safe_market(marketId)
symbol = market['symbol']
tradesArray = self.safe_value(self.trades, symbol)
if tradesArray is None:
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
tradesArray = ArrayCache(limit)
for i in range(0, len(data)):
trade = self.parse_trade(data[i])
tradesArray.append(trade)
self.trades[symbol] = tradesArray
client.resolve(tradesArray, topic)
async def authenticate(self, params={}):
self.check_required_credentials()
url = self.urls['api']['ws']
client = self.client(url)
messageHash = 'authenticated'
future = client.reusableFuture(messageHash)
authenticated = self.safe_value(client.subscriptions, messageHash)
if authenticated is None:
requestId = self.request_id(url)
now = str(self.milliseconds())
signature = self.signMessage(now, self.privateKey)
deriveWalletAddress = self.safe_string(self.options, 'deriveWalletAddress')
request: dict = {
'id': requestId,
'method': 'public/login',
'params': {
'wallet': deriveWalletAddress,
'timestamp': now,
'signature': signature,
},
}
# subscription: Dict = {
# 'name': topic,
# 'symbol': symbol,
# 'params': params,
# }
message = self.extend(request, params)
self.watch(url, messageHash, message, messageHash, message)
return await future
async def watch_private(self, messageHash, message, subscription):
await self.authenticate()
url = self.urls['api']['ws']
requestId = self.request_id(url)
request = self.extend(message, {
'id': requestId,
})
subscription = self.extend(subscription, {
'id': requestId,
'method': 'subscribe',
})
return await self.watch(url, messageHash, request, messageHash, subscription)
async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
https://docs.derive.xyz/reference/subaccount_id-orders
watches information on multiple orders made by the user
:param str symbol: unified market symbol of the market orders were made in
:param int [since]: the earliest time in ms to fetch orders for
:param int [limit]: the maximum number of order structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.subaccount_id]: *required* the subaccount id
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
subaccountId = None
subaccountId, params = self.handleDeriveSubaccountId('watchOrders', params)
topic = self.number_to_string(subaccountId) + '.orders'
messageHash = topic
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
messageHash += ':' + symbol
request: dict = {
'method': 'subscribe',
'params': {
'channels': [
topic,
],
},
}
subscription: dict = {
'name': topic,
'params': params,
}
message = self.extend(request, params)
orders = await self.watch_private(messageHash, message, subscription)
if self.newUpdates:
limit = orders.getLimit(symbol, limit)
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
def handle_order(self, client: Client, message):
#
# {
# method: 'subscription',
# params: {
# channel: '130837.orders',
# data: [
# {
# subaccount_id: 130837,
# order_id: '1f44c564-5658-4b69-b8c4-4019924207d5',
# instrument_name: 'BTC-PERP',
# direction: 'buy',
# label: 'test1234',
# quote_id: null,
# creation_timestamp: 1738578974146,
# last_update_timestamp: 1738578974146,
# limit_price: '10000',
# amount: '0.01',
# filled_amount: '0',
# average_price: '0',
# order_fee: '0',
# order_type: 'limit',
# time_in_force: 'post_only',
# order_status: 'untriggered',
# max_fee: '219',
# signature_expiry_sec: 1746354973,
# nonce: 1738578973570,
# signer: '0x30CB7B06AdD6749BbE146A6827502B8f2a79269A',
# signature: '0xc6927095f74a0d3b1aeef8c0579d120056530479f806e9d2e6616df742a8934c69046361beae833b32b25c0145e318438d7d1624bb835add956f63aa37192f571c',
# cancel_reason: '',
# mmp: False,
# is_transfer: False,
# replaced_order_id: null,
# trigger_type: 'stoploss',
# trigger_price_type: 'mark',
# trigger_price: '102800',
# trigger_reject_message: null
# }
# ]
# }
# }
#
params = self.safe_dict(message, 'params')
topic = self.safe_string(params, 'channel')
rawOrders = self.safe_list(params, 'data')
for i in range(0, len(rawOrders)):
data = rawOrders[i]
parsed = self.parse_order(data)
symbol = self.safe_string(parsed, 'symbol')
orderId = self.safe_string(parsed, 'id')
if symbol is not None:
if self.orders is None:
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
self.orders = ArrayCacheBySymbolById(limit)
cachedOrders = self.orders
orders = self.safe_value(cachedOrders.hashmap, symbol, {})
order = self.safe_value(orders, orderId)
if order is not None:
fee = self.safe_value(order, 'fee')
if fee is not None:
parsed['fee'] = fee
fees = self.safe_value(order, 'fees')
if fees is not None:
parsed['fees'] = fees
parsed['trades'] = self.safe_value(order, 'trades')
parsed['timestamp'] = self.safe_integer(order, 'timestamp')
parsed['datetime'] = self.safe_string(order, 'datetime')
cachedOrders.append(parsed)
messageHashSymbol = topic + ':' + symbol
client.resolve(self.orders, messageHashSymbol)
client.resolve(self.orders, topic)
async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
https://docs.derive.xyz/reference/subaccount_id-trades
watches information on multiple trades made by the user
:param str symbol: unified market symbol of the market orders were made in
:param int [since]: the earliest time in ms to fetch orders for
:param int [limit]: the maximum number of order structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.subaccount_id]: *required* the subaccount id
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
"""
await self.load_markets()
subaccountId = None
subaccountId, params = self.handleDeriveSubaccountId('watchMyTrades', params)
topic = self.number_to_string(subaccountId) + '.trades'
messageHash = topic
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
messageHash += ':' + symbol
request: dict = {
'method': 'subscribe',
'params': {
'channels': [
topic,
],
},
}
subscription: dict = {
'name': topic,
'params': params,
}
message = self.extend(request, params)
trades = await self.watch_private(messageHash, message, subscription)
if self.newUpdates:
limit = trades.getLimit(symbol, limit)
return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
def handle_my_trade(self, client: Client, message):
#
#
myTrades = self.myTrades
if myTrades is None:
limit = self.safe_integer(self.options, 'tradesLimit', 1000)
myTrades = ArrayCacheBySymbolById(limit)
params = self.safe_dict(message, 'params')
topic = self.safe_string(params, 'channel')
rawTrades = self.safe_list(params, 'data')
for i in range(0, len(rawTrades)):
trade = self.parse_trade(message)
myTrades.append(trade)
client.resolve(myTrades, topic)
messageHash = topic + trade['symbol']
client.resolve(myTrades, messageHash)
def handle_error_message(self, client: Client, message) -> Bool:
#
# {
# id: '690c6276-0fc6-4121-aafa-f28bf5adedcb',
# error: {code: -32600, message: 'Invalid Request'}
# }
#
if not ('error' in message):
return False
errorMessage = self.safe_dict(message, 'error')
errorCode = self.safe_string(errorMessage, 'code')
try:
if errorCode is not None:
feedback = self.id + ' ' + self.json(message)
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
raise ExchangeError(feedback)
return False
except Exception as error:
if isinstance(error, AuthenticationError):
messageHash = 'authenticated'
client.reject(error, messageHash)
if messageHash in client.subscriptions:
del client.subscriptions[messageHash]
else:
client.reject(error)
return True
def handle_message(self, client: Client, message):
if self.handle_error_message(client, message):
return
methods: dict = {
'orderbook': self.handle_order_book,
'ticker': self.handle_ticker,
'trades': self.handle_trade,
'orders': self.handle_order,
'mytrades': self.handle_my_trade,
}
event = None
params = self.safe_dict(message, 'params')
if params is not None:
channel = self.safe_string(params, 'channel')
if channel is not None:
parsedChannel = channel.split('.')
if (channel.find('orders') >= 0) or channel.find('trades') > 0:
event = self.safe_string(parsedChannel, 1)
# {subaccounr_id}.trades
if event == 'trades':
event = 'mytrades'
else:
event = self.safe_string(parsedChannel, 0)
method = self.safe_value(methods, event)
if method is not None:
method(client, message)
return
if 'id' in message:
id = self.safe_string(message, 'id')
subscriptionsById = self.index_by(client.subscriptions, 'id')
subscription = self.safe_value(subscriptionsById, id, {})
if 'method' in subscription:
if subscription['method'] == 'public/login':
self.handle_auth(client, message)
elif subscription['method'] == 'unsubscribe':
self.handle_un_subscribe(client, message)
# could handleSubscribe
def handle_auth(self, client: Client, message):
#
# {
# id: 1,
# result: [130837]
# }
#
messageHash = 'authenticated'
ids = self.safe_list(message, 'result')
if len(ids) > 0:
# client.resolve(message, messageHash)
future = self.safe_value(client.futures, 'authenticated')
future.resolve(True)
else:
error = AuthenticationError(self.json(message))
client.reject(error, messageHash)
# allows further authentication attempts
if messageHash in client.subscriptions:
del client.subscriptions['authenticated']