Initial work on bittrex implementation

This commit is contained in:
fredfortier
2017-08-27 23:26:48 -04:00
parent c40fd98022
commit 1cfcb1d96e
12 changed files with 536 additions and 38 deletions
+5 -5
View File
@@ -201,8 +201,8 @@ def ipython_only(option):
)
@click.option(
'-n',
'--algo-name',
help='A label assigned to the algorithm for tracking purposes.',
'--algo-namespace',
help='A label assigned to the algorithm for tracking purposes. '
)
@click.option(
'-c',
@@ -381,14 +381,14 @@ def ingest(bundle, compile_locally, assets_version, show_progress):
'--before',
type=Timestamp(),
help='Clear all data before TIMESTAMP.'
' This may not be passed with -k / --keep-last',
' This may not be passed with -k / --keep-last',
)
@click.option(
'-a',
'--after',
type=Timestamp(),
help='Clear all data after TIMESTAMP'
' This may not be passed with -k / --keep-last',
' This may not be passed with -k / --keep-last',
)
@click.option(
'-k',
@@ -396,7 +396,7 @@ def ingest(bundle, compile_locally, assets_version, show_progress):
type=int,
metavar='N',
help='Clear all but the last N downloads.'
' This may not be passed with -e / --before or -a / --after',
' This may not be passed with -e / --before or -a / --after',
)
def clean(bundle, before, after, keep_last):
"""Clean up data downloaded with the ingest command.
+3 -23
View File
@@ -41,7 +41,6 @@ class Bitfinex(Exchange):
self.url = BITFINEX_URL
self.key = key
self.secret = secret
self.id = 'b'
self.name = 'bitfinex'
self.assets = {}
self.load_assets()
@@ -220,26 +219,6 @@ class Bitfinex(Exchange):
portfolio.portfolio_value = \
portfolio.positions_value + portfolio.cash
@property
def portfolio(self):
"""
Return the Portfolio
:return:
"""
# if self._portfolio is None:
# portfolio = ExchangePortfolio(
# start_date=pd.Timestamp.utcnow()
# )
# self.store.portfolio = portfolio
# self.update_portfolio()
#
# portfolio.starting_cash = portfolio.cash
# else:
# portfolio = self.store.portfolio
return self._portfolio
@property
def account(self):
account = Account()
@@ -273,8 +252,9 @@ class Bitfinex(Exchange):
# TODO: research the time skew conditions
return pd.Timedelta('0s')
def subscribe_to_market_data(self, symbol):
pass
def get_account(self):
# TODO: fetch account data and keep in cache
return None
def get_candles(self, data_frequency, assets, bar_count=None):
"""
+107
View File
@@ -0,0 +1,107 @@
from logbook import Logger
from six.moves import urllib
import json
import pandas as pd
from catalyst.exchange.exchange import Exchange
from catalyst.exchange.bittrex.bittrex_api import Bittrex_api
log = Logger('Bittrex')
class Bittrex(Exchange):
def __init__(self, key, secret, base_currency, portfolio=None):
self.api = Bittrex_api(key=key, secret=secret)
self.name = 'bittrex'
self.assets = dict()
self.load_assets()
@property
def account(self):
pass
@property
def portfolio(self):
pass
@property
def positions(self):
pass
@property
def time_skew(self):
pass
def sanitize_curency_symbol(self, exchange_symbol):
"""
Helper method used to build the universal pair.
Include any symbol mapping here if appropriate.
:param exchange_symbol:
:return universal_symbol:
"""
return exchange_symbol.lower()
def fetch_symbol_map(self):
"""
Since Bittrex gives us a complete dictionary of symbols,
we can build the symbol map ad-hoc as opposed to maintaining
a static file. We must be careful with mapping any unconventional
symbol name as appropriate.
:return symbol_map:
"""
symbol_map = dict()
markets = self.api.getmarkets()
for market in markets:
exchange_symbol = market['MarketName']
symbol = '{market}_{base}'.format(
market=self.sanitize_curency_symbol(market['MarketCurrency']),
base=self.sanitize_curency_symbol(market['BaseCurrency'])
)
symbol_map[exchange_symbol] = dict(
symbol=symbol,
start_date=pd.to_datetime(market['Created'], utc=True)
)
return symbol_map
def update_portfolio(self):
pass
def order(self):
log.info('creating order')
pass
def get_open_orders(self, asset):
pass
def open_orders(self):
log.info('retrieving open orders')
pass
def get_order(self):
log.info('retrieving order')
pass
def cancel_order(self):
log.info('cancel order')
pass
def get_candles(self):
log.info('retrieving candles')
url = 'https://bittrex.com/Api/v2.0/pub/market/GetTicks?marketName=BTC-NEO&tickInterval=day&_=1499127220008'
with urllib.request.urlopen(url) as url:
data = json.loads(url.read().decode())
result = data['result']
pass
def tickers(self):
log.info('retrieving tickers')
pass
def get_account(self):
log.info('retrieving account data')
pass
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env python
import json
import time
import hmac
import hashlib
from six.moves import urllib
# Workaround for backwards compatibility
# https://stackoverflow.com/questions/3745771/urllib-request-in-python-2-7
urlopen = urllib.request.urlopen
class Bittrex_api(object):
def __init__(self, key, secret):
self.key = key
self.secret = secret
self.public = ['getmarkets', 'getcurrencies', 'getticker',
'getmarketsummaries', 'getmarketsummary',
'getorderbook', 'getmarkethistory']
self.market = ['buylimit', 'buymarket', 'selllimit', 'sellmarket',
'cancel', 'getopenorders']
self.account = ['getbalances', 'getbalance', 'getdepositaddress',
'withdraw', 'getorder', 'getorderhistory',
'getwithdrawalhistory', 'getdeposithistory']
def query(self, method, values={}):
if method in self.public:
url = 'https://bittrex.com/api/v1.1/public/'
elif method in self.market:
url = 'https://bittrex.com/api/v1.1/market/'
elif method in self.account:
url = 'https://bittrex.com/api/v1.1/account/'
else:
return 'Something went wrong, sorry.'
url += method + '?' + urllib.parse.urlencode(values)
if method not in self.public:
url += '&apikey=' + self.key
url += '&nonce=' + str(int(time.time()))
signature = hmac.new(self.secret, url, hashlib.sha512).hexdigest()
headers = {'apisign': signature}
else:
headers = {}
req = urllib.request.Request(url, headers=headers)
response = json.loads(urlopen(req).read())
if response["result"]:
return response["result"]
else:
return response["message"]
def getmarkets(self):
return self.query('getmarkets')
def getcurrencies(self):
return self.query('getcurrencies')
def getticker(self, market):
return self.query('getticker', {'market': market})
def getmarketsummaries(self):
return self.query('getmarketsummaries')
def getmarketsummary(self, market):
return self.query('getmarketsummary', {'market': market})
def getorderbook(self, market, type, depth=20):
return self.query('getorderbook',
{'market': market, 'type': type, 'depth': depth})
def getmarkethistory(self, market, count=20):
return self.query('getmarkethistory',
{'market': market, 'count': count})
def buylimit(self, market, quantity, rate):
return self.query('buylimit', {'market': market, 'quantity': quantity,
'rate': rate})
def buymarket(self, market, quantity):
return self.query('buymarket',
{'market': market, 'quantity': quantity})
def selllimit(self, market, quantity, rate):
return self.query('selllimit', {'market': market, 'quantity': quantity,
'rate': rate})
def sellmarket(self, market, quantity):
return self.query('sellmarket',
{'market': market, 'quantity': quantity})
def cancel(self, uuid):
return self.query('cancel', {'uuid': uuid})
def getopenorders(self, market):
return self.query('getopenorders', {'market': market})
def getbalances(self):
return self.query('getbalances')
def getbalance(self, currency):
return self.query('getbalance', {'currency': currency})
def getdepositaddress(self, currency):
return self.query('getdepositaddress', {'currency': currency})
def withdraw(self, currency, quantity, address):
return self.query('withdraw',
{'currency': currency, 'quantity': quantity,
'address': address})
def getorder(self, uuid):
return self.query('getorder', {'uuid': uuid})
def getorderhistory(self, market, count):
return self.query('getorderhistory',
{'market': market, 'count': count})
def getwithdrawalhistory(self, currency, count):
return self.query('getwithdrawalhistory',
{'currency': currency, 'count': count})
def getdeposithistory(self, currency, count):
return self.query('getdeposithistory',
{'currency': currency, 'count': count})
+27 -8
View File
@@ -17,6 +17,7 @@ from catalyst.errors import (
from catalyst.finance.order import ORDER_STATUS
from catalyst.finance.transaction import Transaction
from catalyst.exchange.exchange_utils import get_exchange_symbols
from catalyst.exchange.exchange_portfolio import ExchangePortfolio
log = Logger('Exchange')
@@ -32,10 +33,6 @@ class Exchange:
self.minute_writer = None
self.minute_reader = None
@abstractmethod
def subscribe_to_market_data(self, symbol):
pass
@abstractproperty
def positions(self):
pass
@@ -44,9 +41,20 @@ class Exchange:
def update_portfolio(self):
pass
@abstractproperty
@property
def portfolio(self):
pass
"""
Return the Portfolio
:return:
"""
if self._portfolio is None:
self._portfolio = ExchangePortfolio(
start_date=pd.Timestamp.utcnow()
)
self.update_portfolio()
return self._portfolio
@abstractproperty
def account(self):
@@ -106,6 +114,9 @@ class Exchange:
return asset
def fetch_symbol_map(self):
return get_exchange_symbols(self.name)
def load_assets(self):
"""
Populate the 'assets' attribute with a dictionary of Assets.
@@ -124,7 +135,7 @@ class Exchange:
via its api.
"""
symbol_map = get_exchange_symbols(self.name)
symbol_map = self.fetch_symbol_map()
for exchange_symbol in symbol_map:
asset = symbol_map[exchange_symbol]
symbol = asset['symbol']
@@ -486,4 +497,12 @@ class Exchange:
:param assets:
:return:
"""
return
pass
@abc.abstractmethod
def get_account(self):
"""
Retrieve the account parameters.
:return:
"""
pass
+4 -1
View File
@@ -9,7 +9,9 @@ Logbook==0.12.5
# Scientific Libraries
pytz==2016.4
numpy==1.11.1
# FF: Upgraded numpy because of errors with version 1.11
numpy==1.13.1
# for pandas-datareader
requests-file==1.4.1
@@ -77,3 +79,4 @@ lru-dict==1.1.4
empyrical==0.2.1
tables==3.3.0
+2 -1
View File
@@ -314,6 +314,7 @@ setup(
'Topic :: System :: Distributed Computing',
],
install_requires=install_requires(conda_format=conda_build),
extras_require=extras_requires(conda_format=conda_build),
extras_require=extras_requires(conda_format=conda_build,
install_requires=['six']),
**conditional_arguments
)
View File
+34
View File
@@ -0,0 +1,34 @@
import unittest
from abc import ABCMeta, abstractmethod
class BaseExchangeTestCase():
__metaclass__ = ABCMeta
@abstractmethod
def test_order(self):
pass
@abstractmethod
def test_open_orders(self):
pass
@abstractmethod
def test_get_order(self):
pass
@abstractmethod
def test_cancel_order(self):
pass
@abstractmethod
def test_get_candles(self):
pass
@abstractmethod
def test_tickers(self):
pass
@abstractmethod
def get_account(self):
pass
+126
View File
@@ -0,0 +1,126 @@
from catalyst.exchange.bitfinex.bitfinex import Bitfinex
from .base import BaseExchangeTestCase
from logbook import Logger
import pandas as pd
from catalyst.finance.execution import (MarketOrder,
LimitOrder,
StopOrder,
StopLimitOrder)
from catalyst.exchange.exchange_utils import get_exchange_auth
log = Logger('test_bitfinex')
class BitfinexTestCase(BaseExchangeTestCase):
@classmethod
def setup(self):
print ('creating bitfinex object')
auth = get_exchange_auth('bitfinex')
self.exchange = Bitfinex(
key=auth['key'],
secret=auth['secret'],
base_currency='usd'
)
def test_order(self):
log.info('creating order')
pass
def test_open_orders(self):
log.info('retrieving open orders')
pass
def test_get_order(self):
log.info('retrieving order')
pass
def test_cancel_order(self):
log.info('cancel order')
pass
def test_get_candles(self):
log.info('retrieving candles')
pass
def test_tickers(self):
log.info('retrieving tickers')
pass
def get_account(self):
log.info('retrieving account data')
pass
# def test_order(self):
# log.info('ordering from bitfinex')
# bitfinex = Bitfinex()
# order_id = bitfinex.order(
# asset=bitfinex.get_asset('eth_usd'),
# style=LimitOrder(limit_price=200),
# limit_price=200,
# amount=0.5,
# stop_price=None
# )
# log.info('order created {}'.format(order_id))
# pass
#
# def test_portfolio(self):
# log.info('fetching portfolio data')
# pass
#
# def test_account(self):
# log.info('fetching account data')
# pass
#
# def test_time_skew(self):
# log.info('time skew not implemented')
# pass
#
# def test_get_open_orders(self):
# log.info('fetching open orders')
# bitfinex = Bitfinex()
# order_id = bitfinex.get_open_orders()
# log.info('open orders: {}'.format(order_id))
# pass
#
# def test_get_order(self):
# log.info('querying orders from bitfinex')
# bitfinex = Bitfinex()
# response = bitfinex.get_order(order_id=3361248395)
# log.info('the order: {}'.format(response))
# pass
#
# def test_cancel_order(self):
# log.info('canceling order from bitfinex')
# bitfinex = Bitfinex()
# response = bitfinex.cancel_order(order_id=3330847408)
# log.info('canceled order: {}'.format(response))
# pass
#
# def test_get_spot_value(self):
# log.info('spot value not implemented')
# bitfinex = Bitfinex()
# assets = [
# bitfinex.get_asset('eth_usd'),
# bitfinex.get_asset('etc_usd'),
# bitfinex.get_asset('eos_usd'),
# ]
# # assets = bitfinex.get_asset('eth_usd')
# value = bitfinex.get_spot_value(
# assets=assets,
# field='close',
# data_frequency='minute'
# )
# pass
#
# def test_tickers(self):
# log.info('fetching ticker from bitfinex')
# bitfinex = Bitfinex()
# current_date = pd.Timestamp.utcnow()
# assets = [
# bitfinex.get_asset('eth_usd'),
# bitfinex.get_asset('etc_usd'),
# bitfinex.get_asset('eos_usd'),
# ]
# tickers = bitfinex.tickers(date=current_date, assets=assets)
# log.info('got tickers {}'.format(tickers))
# pass
+51
View File
@@ -0,0 +1,51 @@
from catalyst.exchange.bittrex.bittrex import Bittrex
from .base import BaseExchangeTestCase
from logbook import Logger
import pandas as pd
from catalyst.finance.execution import (MarketOrder,
LimitOrder,
StopOrder,
StopLimitOrder)
from catalyst.exchange.exchange_utils import get_exchange_auth
log = Logger('test_bittrex')
class BittrexTestCase(BaseExchangeTestCase):
@classmethod
def setup(self):
print ('creating bittrex object')
auth = get_exchange_auth('bittrex')
self.exchange = Bittrex(
key=auth['key'],
secret=auth['secret'],
base_currency='usd'
)
def test_order(self):
log.info('creating order')
pass
def test_open_orders(self):
log.info('retrieving open orders')
pass
def test_get_order(self):
log.info('retrieving order')
pass
def test_cancel_order(self):
log.info('cancel order')
pass
def test_get_candles(self):
log.info('retrieving candles')
pass
def test_tickers(self):
log.info('retrieving tickers')
pass
def get_account(self):
log.info('retrieving account data')
pass
+50
View File
@@ -0,0 +1,50 @@
from unittest import TestCase
from logbook import Logger
from mock import patch, sentinel
from catalyst.exchange.exchange_clock import ExchangeClock
from catalyst.utils.calendars.trading_calendar import days_at_time
from datetime import time
from collections import defaultdict
from catalyst.utils.calendars import get_calendar
import pandas as pd
log = Logger('ExchangeClockTestCase')
class ExchangeClockTestCase(TestCase):
@classmethod
def setUpClass(cls):
cls.open_calendar = get_calendar("OPEN")
cls.sessions = pd.Timestamp.utcnow()
def setUp(self):
self.internal_clock = None
self.events = defaultdict(list)
def advance_clock(self, x):
"""Mock function for sleep. Advances the internal clock by 1 min"""
# The internal clock advance time must be 1 minute to match
# MinutesSimulationClock's update frequency
self.internal_clock += pd.Timedelta('1 min')
def get_clock(self, arg, *args, **kwargs):
"""Mock function for pandas.to_datetime which is used to query the
current time in RealtimeClock"""
assert arg == "now"
return self.internal_clock
def test_clock(self):
with patch('catalyst.exchange.exchange_clock.pd.to_datetime') as to_dt, \
patch('catalyst.exchange.exchange_clock.sleep') as sleep:
clock = ExchangeClock(sessions=self.sessions)
to_dt.side_effect = self.get_clock
sleep.side_effect = self.advance_clock
start_time = pd.Timestamp.utcnow()
self.internal_clock = start_time
events = list(clock)
# Event 0 is SESSION_START which always happens at 00:00.
ts, event_type = events[1]
pass