mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-03 03:08:56 +08:00
BLD: all exchange operations now implemented an unit tested with CCXT
This commit is contained in:
@@ -77,8 +77,12 @@ class CCXT(Exchange):
|
||||
def time_skew(self):
|
||||
return None
|
||||
|
||||
def get_symbol(self, asset):
|
||||
parts = asset.symbol.split('_')
|
||||
def get_symbol(self, asset_or_symbol):
|
||||
symbol = asset_or_symbol if isinstance(
|
||||
asset_or_symbol, string_types
|
||||
) else asset_or_symbol.symbol
|
||||
|
||||
parts = symbol.split('_')
|
||||
return '{}/{}'.format(parts[0].upper(), parts[1].upper())
|
||||
|
||||
def get_catalyst_symbol(self, market_or_symbol):
|
||||
@@ -230,15 +234,24 @@ class CCXT(Exchange):
|
||||
|
||||
def _create_order(self, order_status):
|
||||
"""
|
||||
Create a Catalyst order object from a Bitfinex order dictionary
|
||||
:param order_status:
|
||||
:return: Order
|
||||
Create a Catalyst order object from a CCXT order dictionary
|
||||
|
||||
Parameters
|
||||
----------
|
||||
order_status: dict[str, Object]
|
||||
The order dict from the CCXT api.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Order
|
||||
The Catalyst order object
|
||||
|
||||
"""
|
||||
if order_status['status'] == 'canceled':
|
||||
status = ORDER_STATUS.CANCELLED
|
||||
|
||||
elif order_status['status'] == 'closed' and order_status['filled'] > 0:
|
||||
log.info('found executed order {}'.format(order_status))
|
||||
log.debug('found executed order {}'.format(order_status))
|
||||
status = ORDER_STATUS.FILLED
|
||||
|
||||
elif order_status['status'] == 'open':
|
||||
@@ -247,30 +260,27 @@ class CCXT(Exchange):
|
||||
else:
|
||||
raise ValueError('invalid state for order')
|
||||
|
||||
amount = float(order_status['amount'])
|
||||
filled = float(order_status['filled'])
|
||||
amount = order_status['amount']
|
||||
filled = order_status['filled']
|
||||
|
||||
if order_status['side'] == 'sell':
|
||||
amount = -amount
|
||||
filled = -filled
|
||||
|
||||
price = float(order_status['price'])
|
||||
price = order_status['price']
|
||||
order_type = order_status['type']
|
||||
|
||||
stop_price = None
|
||||
limit_price = None
|
||||
|
||||
# TODO: is this comprehensive enough?
|
||||
if order_type.endswith('limit'):
|
||||
limit_price = price
|
||||
elif order_type.endswith('stop'):
|
||||
stop_price = price
|
||||
limit_price = price if order_type == 'limit' else None
|
||||
stop_price = None # TODO: add support
|
||||
|
||||
executed_price = order_status['cost'] / order_status['amount']
|
||||
commission = order_status['fee']
|
||||
date = from_ms_timestamp(order_status['timestamp'])
|
||||
|
||||
# order_id = str(order_status['info']['clientOrderId'])
|
||||
order_id = order_status['id']
|
||||
symbol = order_status['info']['symbol']
|
||||
|
||||
order = Order(
|
||||
dt=date,
|
||||
asset=self.assets[symbol],
|
||||
@@ -278,7 +288,7 @@ class CCXT(Exchange):
|
||||
stop=stop_price,
|
||||
limit=limit_price,
|
||||
filled=filled,
|
||||
id=str(order_status['id']),
|
||||
id=order_id,
|
||||
commission=commission
|
||||
)
|
||||
order.status = status
|
||||
@@ -319,7 +329,8 @@ class CCXT(Exchange):
|
||||
if 'info' not in result:
|
||||
raise ValueError('cannot use order without info attribute')
|
||||
|
||||
order_id = str(result['info']['clientOrderId'])
|
||||
# order_id = str(result['info']['clientOrderId'])
|
||||
order_id = result['id']
|
||||
order = Order(
|
||||
dt=from_ms_timestamp(result['info']['transactTime']),
|
||||
asset=asset,
|
||||
@@ -350,11 +361,53 @@ class CCXT(Exchange):
|
||||
|
||||
return orders
|
||||
|
||||
def get_order(self, order_id):
|
||||
return None
|
||||
def _get_asset_from_order(self, order_id):
|
||||
open_orders = self.portfolio.open_orders
|
||||
order = next(
|
||||
(order for order in open_orders if order.id == order_id),
|
||||
None
|
||||
) # type: Order
|
||||
return order.asset if order is not None else None
|
||||
|
||||
def cancel_order(self, order_param):
|
||||
return None
|
||||
def get_order(self, order_id, asset_or_symbol=None):
|
||||
if asset_or_symbol is None and self.portfolio is not None:
|
||||
asset_or_symbol = self._get_asset_from_order(order_id)
|
||||
|
||||
if asset_or_symbol is None:
|
||||
log.debug(
|
||||
'order not found in memory, the request might fail '
|
||||
'on some exchanges.'
|
||||
)
|
||||
try:
|
||||
symbol = self.get_symbol(asset_or_symbol) \
|
||||
if asset_or_symbol is not None else None
|
||||
order_status = self.api.fetch_order(id=order_id, symbol=symbol)
|
||||
order, _ = self._create_order(order_status)
|
||||
|
||||
except Exception as e:
|
||||
raise ExchangeRequestError(error=e)
|
||||
|
||||
return order
|
||||
|
||||
def cancel_order(self, order_param, asset_or_symbol=None):
|
||||
order_id = order_param.id \
|
||||
if isinstance(order_param, Order) else order_param
|
||||
|
||||
if asset_or_symbol is None and self.portfolio is not None:
|
||||
asset_or_symbol = self._get_asset_from_order(order_id)
|
||||
|
||||
if asset_or_symbol is None:
|
||||
log.debug(
|
||||
'order not found in memory, cancelling order might fail '
|
||||
'on some exchanges.'
|
||||
)
|
||||
try:
|
||||
symbol = self.get_symbol(asset_or_symbol) \
|
||||
if asset_or_symbol is not None else None
|
||||
self.api.cancel_order(id=order_id, symbol=symbol)
|
||||
|
||||
except Exception as e:
|
||||
raise ExchangeRequestError(error=e)
|
||||
|
||||
def tickers(self, assets):
|
||||
"""
|
||||
|
||||
@@ -289,9 +289,11 @@ class Exchange:
|
||||
log.debug('found open order: {}'.format(order_id))
|
||||
|
||||
order, executed_price = self.get_order(order_id)
|
||||
log.debug('got updated order {} {}'.format(
|
||||
order, executed_price))
|
||||
|
||||
log.debug(
|
||||
'got updated order {} {}'.format(
|
||||
order, executed_price
|
||||
)
|
||||
)
|
||||
if order.status == ORDER_STATUS.FILLED:
|
||||
transaction = Transaction(
|
||||
asset=order.asset,
|
||||
@@ -822,7 +824,7 @@ class Exchange:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_order(self, order_id):
|
||||
def get_order(self, order_id, symbol_or_asset=None):
|
||||
"""Lookup an order based on the order id returned from one of the
|
||||
order functions.
|
||||
|
||||
@@ -830,6 +832,8 @@ class Exchange:
|
||||
----------
|
||||
order_id : str
|
||||
The unique identifier for the order.
|
||||
symbol_or_asset: str|TradingPair
|
||||
The catalyst symbol, some exchanges need this
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -841,13 +845,15 @@ class Exchange:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def cancel_order(self, order_param):
|
||||
def cancel_order(self, order_param, symbol_or_asset=None):
|
||||
"""Cancel an open order.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
order_param : str or Order
|
||||
The order_id or order object to cancel.
|
||||
symbol_or_asset: str|TradingPair
|
||||
The catalyst symbol, some exchanges need this
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ from logbook import Logger
|
||||
|
||||
from catalyst.constants import LOG_LEVEL
|
||||
from catalyst.protocol import Portfolio, Positions, Position
|
||||
from catalyst.utils.deprecate import deprecated
|
||||
|
||||
log = Logger('ExchangePortfolio', level=LOG_LEVEL)
|
||||
|
||||
@@ -11,7 +10,8 @@ log = Logger('ExchangePortfolio', level=LOG_LEVEL)
|
||||
class ExchangePortfolio(Portfolio):
|
||||
"""
|
||||
Since the goal is to support multiple exchanges, it makes sense to
|
||||
include additional stats in the portfolio object.
|
||||
include additional stats in the portfolio object. This fills the role
|
||||
of Blotter and Portfolio in live mode.
|
||||
|
||||
Instead of relying on the performance tracker, each exchange portfolio
|
||||
tracks its own holding. This offers a separation between tracking an
|
||||
@@ -89,32 +89,6 @@ class ExchangePortfolio(Portfolio):
|
||||
|
||||
log.debug('updated portfolio with executed order')
|
||||
|
||||
@deprecated
|
||||
def execute_transaction(self, transaction):
|
||||
# TODO: almost duplicate of execute_order. Not sure why Poloniex needs this.
|
||||
log.debug('executing transaction {}'.format(transaction.order_id))
|
||||
|
||||
order_position = self.positions[transaction.asset] \
|
||||
if transaction.asset in self.positions else None
|
||||
|
||||
if order_position is None:
|
||||
raise ValueError(
|
||||
'Trying to execute transaction for a position not held: %s' % transaction.order_id
|
||||
)
|
||||
|
||||
self.capital_used += transaction.amount * transaction.price
|
||||
|
||||
if transaction.amount > 0:
|
||||
if order_position.cost_basis > 0:
|
||||
order_position.cost_basis = np.average(
|
||||
[order_position.cost_basis, transaction.price],
|
||||
weights=[order_position.amount, transaction.amount]
|
||||
)
|
||||
else:
|
||||
order_position.cost_basis = transaction.price
|
||||
|
||||
log.debug('updated portfolio with executed order')
|
||||
|
||||
def remove_order(self, order):
|
||||
"""
|
||||
Removing an open order.
|
||||
|
||||
@@ -45,14 +45,14 @@ class TestCCXT(BaseExchangeTestCase):
|
||||
|
||||
def test_get_order(self):
|
||||
log.info('retrieving order')
|
||||
order = self.exchange.get_order(
|
||||
u'2c584020-9caf-4af5-bde0-332c0bba17e2')
|
||||
order = self.exchange.get_order('2631386', 'neo_eth')
|
||||
# order = self.exchange.get_order('2631386')
|
||||
assert isinstance(order, Order)
|
||||
pass
|
||||
|
||||
def test_cancel_order(self, ):
|
||||
log.info('cancel order')
|
||||
self.exchange.cancel_order(u'dc7bcca2-5219-4145-8848-8a593d2a72f9')
|
||||
self.exchange.cancel_order('2631386', 'neo_eth')
|
||||
pass
|
||||
|
||||
def test_get_candles(self):
|
||||
|
||||
Reference in New Issue
Block a user