mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-12 20:41:12 +08:00
BLD: tested creating orders and viewing open orders with CCXT
This commit is contained in:
@@ -83,15 +83,15 @@ def place_orders(context, amount, buying_price, selling_price, action):
|
||||
else:
|
||||
raise ValueError('invalid order action')
|
||||
|
||||
base_currency = enter_exchange.base_currency
|
||||
base_currency_amount = enter_exchange.portfolio.cash
|
||||
quote_currency = enter_exchange.quote_currency
|
||||
quote_currency_amount = enter_exchange.portfolio.cash
|
||||
|
||||
exit_balances = exit_exchange.get_balances()
|
||||
exit_currency = context.trading_pairs[
|
||||
context.selling_exchange].market_currency
|
||||
context.selling_exchange].quote_currency
|
||||
|
||||
if exit_currency in exit_balances:
|
||||
market_currency_amount = exit_balances[exit_currency]
|
||||
quote_currency_amount = exit_balances[exit_currency]
|
||||
else:
|
||||
log.warn(
|
||||
'the selling exchange {exchange_name} does not hold '
|
||||
@@ -102,25 +102,25 @@ def place_orders(context, amount, buying_price, selling_price, action):
|
||||
)
|
||||
return
|
||||
|
||||
if base_currency_amount < (amount * entry_price):
|
||||
adj_amount = base_currency_amount / entry_price
|
||||
if quote_currency_amount < (amount * entry_price):
|
||||
adj_amount = quote_currency_amount / entry_price
|
||||
log.warn(
|
||||
'not enough {base_currency} ({base_currency_amount}) to buy '
|
||||
'not enough {quote_currency} ({quote_currency_amount}) to buy '
|
||||
'{amount}, adjusting the amount to {adj_amount}'.format(
|
||||
base_currency=base_currency,
|
||||
base_currency_amount=base_currency_amount,
|
||||
quote_currency=quote_currency,
|
||||
quote_currency_amount=quote_currency_amount,
|
||||
amount=amount,
|
||||
adj_amount=adj_amount
|
||||
)
|
||||
)
|
||||
amount = adj_amount
|
||||
|
||||
elif market_currency_amount < amount:
|
||||
elif quote_currency_amount < amount:
|
||||
log.warn(
|
||||
'not enough {currency} ({currency_amount}) to sell '
|
||||
'{amount}, aborting'.format(
|
||||
currency=exit_currency,
|
||||
currency_amount=market_currency_amount,
|
||||
currency_amount=quote_currency_amount,
|
||||
amount=amount
|
||||
)
|
||||
)
|
||||
@@ -270,6 +270,6 @@ run_algorithm(
|
||||
exchange_name='poloniex,bitfinex',
|
||||
live=True,
|
||||
algo_namespace=algo_namespace,
|
||||
base_currency='btc',
|
||||
quote_currency='btc',
|
||||
live_graph=False
|
||||
)
|
||||
|
||||
@@ -3,19 +3,32 @@ from collections import defaultdict
|
||||
|
||||
import ccxt
|
||||
import pandas as pd
|
||||
from ccxt import ExchangeNotAvailable
|
||||
from six import string_types
|
||||
|
||||
from catalyst.finance.order import Order, ORDER_STATUS
|
||||
|
||||
from catalyst.algorithm import MarketOrder
|
||||
from catalyst.assets._assets import TradingPair
|
||||
from logbook import Logger
|
||||
|
||||
from catalyst.constants import LOG_LEVEL
|
||||
from catalyst.exchange.exchange import Exchange
|
||||
from catalyst.exchange.exchange import Exchange, ExchangeLimitOrder
|
||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||
from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \
|
||||
ExchangeSymbolsNotFound
|
||||
ExchangeSymbolsNotFound, ExchangeRequestError, InvalidOrderStyle
|
||||
from catalyst.exchange.exchange_utils import mixin_market_params, \
|
||||
from_ms_timestamp
|
||||
|
||||
log = Logger('CCXT', level=LOG_LEVEL)
|
||||
|
||||
SUPPORTED_EXCHANGES = dict(
|
||||
binance=ccxt.binance,
|
||||
bitfinex=ccxt.bitfinex,
|
||||
bittrex=ccxt.bittrex,
|
||||
poloniex=ccxt.poloniex,
|
||||
)
|
||||
|
||||
|
||||
class CCXT(Exchange):
|
||||
def __init__(self, exchange_name, key, secret, base_currency,
|
||||
@@ -26,7 +39,13 @@ class CCXT(Exchange):
|
||||
)
|
||||
)
|
||||
try:
|
||||
exchange_attr = getattr(ccxt, exchange_name)
|
||||
# Making instantiation as explicit as possible for code tracking.
|
||||
if exchange_name in SUPPORTED_EXCHANGES:
|
||||
exchange_attr = SUPPORTED_EXCHANGES[exchange_name]
|
||||
|
||||
else:
|
||||
exchange_attr = getattr(ccxt, exchange_name)
|
||||
|
||||
self.api = exchange_attr({
|
||||
'apiKey': key,
|
||||
'secret': secret,
|
||||
@@ -62,8 +81,12 @@ class CCXT(Exchange):
|
||||
parts = asset.symbol.split('_')
|
||||
return '{}/{}'.format(parts[0].upper(), parts[1].upper())
|
||||
|
||||
def get_catalyst_symbol(self, market):
|
||||
parts = market['symbol'].split('/')
|
||||
def get_catalyst_symbol(self, market_or_symbol):
|
||||
symbol = market_or_symbol if isinstance(
|
||||
market_or_symbol, string_types
|
||||
) else market_or_symbol['symbol']
|
||||
|
||||
parts = symbol.split('/')
|
||||
return '{}_{}'.format(parts[0].lower(), parts[1].lower())
|
||||
|
||||
def get_timeframe(self, freq):
|
||||
@@ -191,13 +214,141 @@ class CCXT(Exchange):
|
||||
self.assets[market['id']] = trading_pair
|
||||
|
||||
def get_balances(self):
|
||||
return None
|
||||
try:
|
||||
log.debug('retrieving wallets balances')
|
||||
balances = self.api.fetch_balance()
|
||||
|
||||
balances_lower = dict()
|
||||
for key in balances:
|
||||
balances_lower[key.lower()] = balances[key]
|
||||
|
||||
except Exception as e:
|
||||
log.debug('error retrieving balances: {}', e)
|
||||
raise ExchangeRequestError(error=e)
|
||||
|
||||
return balances_lower
|
||||
|
||||
def _create_order(self, order_status):
|
||||
"""
|
||||
Create a Catalyst order object from a Bitfinex order dictionary
|
||||
:param order_status:
|
||||
:return: Order
|
||||
"""
|
||||
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))
|
||||
status = ORDER_STATUS.FILLED
|
||||
|
||||
elif order_status['status'] == 'open':
|
||||
status = ORDER_STATUS.OPEN
|
||||
|
||||
else:
|
||||
raise ValueError('invalid state for order')
|
||||
|
||||
amount = float(order_status['amount'])
|
||||
filled = float(order_status['filled'])
|
||||
|
||||
if order_status['side'] == 'sell':
|
||||
amount = -amount
|
||||
filled = -filled
|
||||
|
||||
price = float(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
|
||||
|
||||
executed_price = order_status['cost'] / order_status['amount']
|
||||
commission = order_status['fee']
|
||||
date = from_ms_timestamp(order_status['timestamp'])
|
||||
|
||||
symbol = order_status['info']['symbol']
|
||||
order = Order(
|
||||
dt=date,
|
||||
asset=self.assets[symbol],
|
||||
amount=amount,
|
||||
stop=stop_price,
|
||||
limit=limit_price,
|
||||
filled=filled,
|
||||
id=str(order_status['id']),
|
||||
commission=commission
|
||||
)
|
||||
order.status = status
|
||||
|
||||
return order, executed_price
|
||||
|
||||
def create_order(self, asset, amount, is_buy, style):
|
||||
return None
|
||||
symbol = self.get_symbol(asset)
|
||||
|
||||
if isinstance(style, ExchangeLimitOrder):
|
||||
price = style.get_limit_price(is_buy)
|
||||
order_type = 'limit'
|
||||
|
||||
elif isinstance(style, MarketOrder):
|
||||
price = None
|
||||
order_type = 'market'
|
||||
|
||||
else:
|
||||
raise InvalidOrderStyle(
|
||||
exchange=self.name,
|
||||
style=style.__class__.__name__
|
||||
)
|
||||
|
||||
side = 'buy' if amount > 0 else 'sell'
|
||||
|
||||
try:
|
||||
result = self.api.create_order(
|
||||
symbol=symbol,
|
||||
type=order_type,
|
||||
side=side,
|
||||
amount=abs(amount),
|
||||
price=price
|
||||
)
|
||||
except ExchangeNotAvailable as e:
|
||||
log.debug('unable to create order: {}'.format(e))
|
||||
raise ExchangeRequestError(error=e)
|
||||
|
||||
if 'info' not in result:
|
||||
raise ValueError('cannot use order without info attribute')
|
||||
|
||||
order_id = str(result['info']['clientOrderId'])
|
||||
order = Order(
|
||||
dt=from_ms_timestamp(result['info']['transactTime']),
|
||||
asset=asset,
|
||||
amount=amount,
|
||||
stop=style.get_stop_price(is_buy),
|
||||
limit=style.get_limit_price(is_buy),
|
||||
id=order_id
|
||||
)
|
||||
return order
|
||||
|
||||
def get_open_orders(self, asset):
|
||||
return None
|
||||
try:
|
||||
symbol = self.get_symbol(asset)
|
||||
result = self.api.fetch_open_orders(
|
||||
symbol=symbol,
|
||||
since=None,
|
||||
limit=None,
|
||||
params=dict()
|
||||
)
|
||||
except Exception as e:
|
||||
raise ExchangeRequestError(error=e)
|
||||
|
||||
orders = []
|
||||
for order_status in result:
|
||||
order, executed_price = self._create_order(order_status)
|
||||
if asset is None or asset == order.sid:
|
||||
orders.append(order)
|
||||
|
||||
return orders
|
||||
|
||||
def get_order(self, order_id):
|
||||
return None
|
||||
|
||||
@@ -8,15 +8,16 @@ import pandas as pd
|
||||
from catalyst.assets._assets import TradingPair
|
||||
from logbook import Logger
|
||||
|
||||
from catalyst.algorithm import MarketOrder
|
||||
from catalyst.constants import LOG_LEVEL
|
||||
from catalyst.data.data_portal import BASE_FIELDS
|
||||
from catalyst.exchange.bundle_utils import get_start_dt, \
|
||||
get_delta, get_periods, get_periods_range
|
||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||
from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \
|
||||
InvalidOrderStyle, BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \
|
||||
BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \
|
||||
PricingDataNotLoadedError, \
|
||||
NoDataAvailableOnExchange, ExchangeSymbolsNotFound, NoValueForField
|
||||
NoDataAvailableOnExchange, NoValueForField
|
||||
from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \
|
||||
ExchangeLimitOrder, ExchangeStopOrder
|
||||
from catalyst.exchange.exchange_portfolio import ExchangePortfolio
|
||||
@@ -34,7 +35,6 @@ class Exchange:
|
||||
def __init__(self):
|
||||
self.name = None
|
||||
self.assets = dict()
|
||||
self.local_assets = dict()
|
||||
self._portfolio = None
|
||||
self.minute_writer = None
|
||||
self.minute_reader = None
|
||||
@@ -200,13 +200,13 @@ class Exchange:
|
||||
return assets
|
||||
|
||||
def _find_asset(self, asset, symbol, data_frequency, is_local=False):
|
||||
assets = self.assets if not is_local else self.local_assets
|
||||
|
||||
assets = self.assets
|
||||
for key in assets:
|
||||
has_data = (data_frequency == 'minute'
|
||||
and assets[key].end_minute is not None) \
|
||||
or (data_frequency == 'daily'
|
||||
and assets[key].end_daily is not None)
|
||||
|
||||
if not asset and assets[key].symbol.lower() == symbol.lower() \
|
||||
and (not data_frequency or has_data):
|
||||
asset = assets[key]
|
||||
@@ -236,8 +236,7 @@ class Exchange:
|
||||
asset = self._find_asset(asset, symbol, data_frequency, True)
|
||||
|
||||
if not asset:
|
||||
all_values = list(self.assets.values()) + \
|
||||
list(self.local_assets.values())
|
||||
all_values = list(self.assets.values())
|
||||
supported_symbols = sorted([
|
||||
asset.symbol for asset in all_values
|
||||
])
|
||||
@@ -253,6 +252,7 @@ class Exchange:
|
||||
def fetch_symbol_map(self, is_local=False):
|
||||
return get_exchange_symbols(self.name, is_local)
|
||||
|
||||
@abstractmethod
|
||||
def load_assets(self, is_local=False):
|
||||
"""
|
||||
Populate the 'assets' attribute with a dictionary of Assets.
|
||||
@@ -270,66 +270,7 @@ class Exchange:
|
||||
via its api.
|
||||
|
||||
"""
|
||||
try:
|
||||
symbol_map = self.fetch_symbol_map(is_local)
|
||||
except ExchangeSymbolsNotFound:
|
||||
return None
|
||||
|
||||
for exchange_symbol in symbol_map:
|
||||
asset = symbol_map[exchange_symbol]
|
||||
|
||||
if 'start_date' in asset:
|
||||
start_date = pd.to_datetime(asset['start_date'], utc=True)
|
||||
else:
|
||||
start_date = None
|
||||
|
||||
if 'end_date' in asset:
|
||||
end_date = pd.to_datetime(asset['end_date'], utc=True)
|
||||
else:
|
||||
end_date = None
|
||||
|
||||
if 'leverage' in asset:
|
||||
leverage = asset['leverage']
|
||||
else:
|
||||
leverage = 1.0
|
||||
|
||||
if 'asset_name' in asset:
|
||||
asset_name = asset['asset_name']
|
||||
else:
|
||||
asset_name = None
|
||||
|
||||
if 'min_trade_size' in asset:
|
||||
min_trade_size = asset['min_trade_size']
|
||||
else:
|
||||
min_trade_size = 0.0000001
|
||||
|
||||
if 'end_daily' in asset and asset['end_daily'] != 'N/A':
|
||||
end_daily = pd.to_datetime(asset['end_daily'], utc=True)
|
||||
else:
|
||||
end_daily = None
|
||||
|
||||
if 'end_minute' in asset and asset['end_minute'] != 'N/A':
|
||||
end_minute = pd.to_datetime(asset['end_minute'], utc=True)
|
||||
else:
|
||||
end_minute = None
|
||||
|
||||
trading_pair = TradingPair(
|
||||
symbol=asset['symbol'],
|
||||
exchange=self.name,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
leverage=leverage,
|
||||
asset_name=asset_name,
|
||||
min_trade_size=min_trade_size,
|
||||
end_daily=end_daily,
|
||||
end_minute=end_minute,
|
||||
exchange_symbol=exchange_symbol
|
||||
)
|
||||
|
||||
if is_local:
|
||||
self.local_assets[exchange_symbol] = trading_pair
|
||||
else:
|
||||
self.assets[exchange_symbol] = trading_pair
|
||||
pass
|
||||
|
||||
def check_open_orders(self):
|
||||
"""
|
||||
@@ -694,7 +635,7 @@ class Exchange:
|
||||
log.debug('synchronizing portfolio with exchange {}'.format(self.name))
|
||||
balances = self.get_balances()
|
||||
|
||||
base_position_available = balances[self.base_currency] \
|
||||
base_position_available = balances[self.base_currency]['free'] \
|
||||
if self.base_currency in balances else None
|
||||
|
||||
if base_position_available is None:
|
||||
@@ -777,28 +718,30 @@ class Exchange:
|
||||
log.warn('skipping order amount of 0')
|
||||
return None
|
||||
|
||||
if asset.base_currency != self.base_currency.lower():
|
||||
if self.base_currency is None:
|
||||
raise ValueError('no base_currency defined for this exchange')
|
||||
|
||||
if asset.quote_currency != self.base_currency.lower():
|
||||
raise MismatchingBaseCurrencies(
|
||||
base_currency=asset.base_currency,
|
||||
base_currency=asset.quote_currency,
|
||||
algo_currency=self.base_currency
|
||||
)
|
||||
|
||||
is_buy = (amount > 0)
|
||||
|
||||
if limit_price is not None and stop_price is not None:
|
||||
style = ExchangeStopLimitOrder(limit_price, stop_price,
|
||||
exchange=self.name)
|
||||
style = ExchangeStopLimitOrder(
|
||||
limit_price, stop_price, exchange=self.name
|
||||
)
|
||||
|
||||
elif limit_price is not None:
|
||||
style = ExchangeLimitOrder(limit_price, exchange=self.name)
|
||||
|
||||
elif stop_price is not None:
|
||||
style = ExchangeStopOrder(stop_price, exchange=self.name)
|
||||
|
||||
elif style is not None:
|
||||
raise InvalidOrderStyle(exchange=self.name.title(),
|
||||
style=style.__class__.__name__)
|
||||
else:
|
||||
raise ValueError('Incomplete order data.')
|
||||
style = MarketOrder(exchange=self.name)
|
||||
|
||||
display_price = limit_price if limit_price is not None else stop_price
|
||||
log.debug(
|
||||
@@ -807,9 +750,10 @@ class Exchange:
|
||||
amount=amount,
|
||||
symbol=asset.symbol,
|
||||
type=style.__class__.__name__,
|
||||
price='{}{}'.format(display_price, asset.base_currency)
|
||||
price='{}{}'.format(display_price, asset.quote_currency)
|
||||
)
|
||||
)
|
||||
|
||||
order = self.create_order(asset, amount, is_buy, style)
|
||||
if order:
|
||||
self._portfolio.create_order(order)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from catalyst.finance.execution import LimitOrder, StopOrder, StopLimitOrder
|
||||
from catalyst.finance.execution import LimitOrder, StopOrder, StopLimitOrder, MarketOrder
|
||||
|
||||
|
||||
class ExchangeLimitOrder(LimitOrder):
|
||||
|
||||
@@ -263,7 +263,7 @@ def _run(handle_data,
|
||||
)
|
||||
|
||||
if base_currency in balances:
|
||||
base_currency_available = balances[base_currency]
|
||||
base_currency_available = balances[base_currency]['free']
|
||||
log.info(
|
||||
'base currency available in the account: {} {}'.format(
|
||||
base_currency_available, base_currency
|
||||
|
||||
@@ -21,16 +21,16 @@ class TestCCXT(BaseExchangeTestCase):
|
||||
exchange_name=exchange_name,
|
||||
key=auth['key'],
|
||||
secret=auth['secret'],
|
||||
base_currency=None,
|
||||
base_currency='eth',
|
||||
portfolio=None
|
||||
)
|
||||
|
||||
def test_order(self):
|
||||
log.info('creating order')
|
||||
asset = self.exchange.get_asset('neo_btc')
|
||||
asset = self.exchange.get_asset('neo_eth')
|
||||
order_id = self.exchange.order(
|
||||
asset=asset,
|
||||
limit_price=0.0005,
|
||||
limit_price=0.07,
|
||||
amount=1,
|
||||
)
|
||||
log.info('order created {}'.format(order_id))
|
||||
@@ -39,7 +39,7 @@ class TestCCXT(BaseExchangeTestCase):
|
||||
|
||||
def test_open_orders(self):
|
||||
log.info('retrieving open orders')
|
||||
asset = self.exchange.get_asset('neo_btc')
|
||||
asset = self.exchange.get_asset('neo_eth')
|
||||
orders = self.exchange.get_open_orders(asset)
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user