Skip to content

Commit

Permalink
Merge pull request #74 from AsyncAlgoTrading/coinbase
Browse files Browse the repository at this point in the history
starting work on coinbase
  • Loading branch information
timkpaine committed Aug 11, 2020
2 parents b307fdb + bb87f2e commit f3773aa
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 46 deletions.
9 changes: 3 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@ run: ## Clean and make target, run target
iex: ## Clean and make target, run target
$(PYTHON) -m aat ./config/iex.cfg

iexintraday: ## Clean and make target, run target
$(PYTHON) -m aat ./config/iex_intraday.cfg

iexlive: ## Clean and make target, run target
$(PYTHON) -m aat ./config/iex_live.cfg

ib: ## Clean and make target, run target
$(PYTHON) -m aat ./config/ib.cfg

coinbasesandbox: ## Clean and make target, run target
$(PYTHON) -m aat ./config/coinbase_sandbox.cfg

runcpp: build ## Clean and make target, run target
AAT_USE_CPP=1 $(PYTHON) -m aat $(CONFIG)

Expand Down
2 changes: 1 addition & 1 deletion aat/exchange/base/market_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ async def instruments(self):
'''get list of available instruments'''
return []

def subscribe(self, instrument):
async def subscribe(self, instrument):
'''subscribe to market data for a given instrument'''

async def tick(self):
Expand Down
2 changes: 1 addition & 1 deletion aat/exchange/base/order_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class _OrderEntry(metaclass=ABCMeta):
'''internal only class to represent the rest-sink
side of a data source'''

def accounts(self) -> List:
async def accounts(self) -> List:
'''get accounts from source'''
return []

Expand Down
34 changes: 0 additions & 34 deletions aat/exchange/crypto/coinbase.py

This file was deleted.

1 change: 1 addition & 0 deletions aat/exchange/crypto/coinbase/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .coinbase import CoinbaseProExchange # noqa: F401
117 changes: 117 additions & 0 deletions aat/exchange/crypto/coinbase/coinbase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from cbpro import PublicClient, AuthenticatedClient, WebsocketClient # type: ignore

from aat.core import ExchangeType, Order
from aat.config import TradingType, OrderType, OrderFlag, InstrumentType
from aat.exchange import Exchange
from .instruments import _get_instruments


class CoinbaseProExchange(Exchange):
'''Coinbase Pro Exchange'''

def __init__(self,
trading_type,
verbose,
api_key='',
api_secret='',
api_passphrase='',
**kwargs):
self._trading_type = trading_type
self._verbose = verbose

if trading_type == TradingType.BACKTEST:
raise NotImplementedError()

if self._trading_type == TradingType.SANDBOX:
super().__init__(ExchangeType('coinbaseprosandbox'))
else:
super().__init__(ExchangeType('coinbasepro'))

auth = api_key and api_secret and api_passphrase
self._public_client = PublicClient()

if trading_type == TradingType.SANDBOX:
self._auth_client = AuthenticatedClient(api_key, api_secret, api_passphrase, api_url="https://api-public.sandbox.pro.coinbase.com") if auth else None
else:
self._auth_client = AuthenticatedClient(api_key, api_secret, api_passphrase) if auth else None

# TODO
self._subscriptions = []
self._ws_client = WebsocketClient(url="wss://ws-feed.pro.coinbase.com", products="BTC-USD")

# *************** #
# General methods #
# *************** #
async def connect(self):
'''connect to exchange. should be asynchronous.'''

# instantiate instruments
_get_instruments(self._public_client, self.exchange())

async def lookup(self, instrument):
'''lookup an instrument on the exchange'''
# TODO
raise NotImplementedError()

# ******************* #
# Market Data Methods #
# ******************* #
async def tick(self):
'''return data from exchange'''

async def subscribe(self, instrument):
self._subscriptions.append(instrument)

# ******************* #
# Order Entry Methods #
# ******************* #
async def accounts(self):
'''get accounts from source'''
# TODO
raise NotImplementedError()

async def newOrder(self, order):
'''submit a new order to the exchange. should set the given order's `id` field to exchange-assigned id'''
if not self._auth_client:
raise NotImplementedError()

if order.instrument.type != InstrumentType.PAIR:
raise NotImplementedError()

if order.type == OrderType.LIMIT:
time_in_force = 'GTC'
if order.flag == OrderFlag.FILL_OR_KILL:
time_in_force = 'FOK'
elif order.flag == OrderFlag.IMMEDIATE_OR_CANCEL:
time_in_force = 'IOC'

ret = self._auth_client.place_limit_order(product_id='{}-{}'.format(order.instrument.leg1.name, order.instrument.leg2.name),
side=order.side.value.lower(),
price=order.price,
size=order.volume,
time_in_force=time_in_force)

elif order.type == OrderType.MARKET:
ret = self._auth_client.place_limit_order(product_id='{}-{}'.format(order.instrument.leg1.name, order.instrument.leg2.name),
side=order.side.value.lower(),
funds=order.price * order.volume)

elif order.type == OrderType.STOP:
# TODO
raise NotImplementedError()
# self._auth_client.place_stop_order(product_id='BTC-USD',
# side='buy',
# price='200.00',
# size='0.01')

# Set ID
order.id = ret['id']
return order

async def cancelOrder(self, order: Order):
'''cancel a previously submitted order to the exchange.'''
self._auth_client.cancel_order(order.id)
return order


Exchange.registerExchange('coinbase', CoinbaseProExchange)
29 changes: 29 additions & 0 deletions aat/exchange/crypto/coinbase/instruments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from functools import lru_cache
from aat import Instrument, InstrumentType, Side


@lru_cache(None)
def _get_currency(symbol):
return Instrument(name=symbol, type=InstrumentType.CURRENCY)


@lru_cache(None)
def _get_instruments(public_client, exchange):
ret = []

products = public_client.get_products()

for product in products:
first = product['base_currency']
second = product['quote_currency']

ret.append(
Instrument(name='{}/{}'.format(first, second),
type=InstrumentType.PAIR,
exchange=exchange,
leg1=_get_currency(first),
leg2=_get_currency(second),
leg1_side=Side.BUY,
leg2_side=Side.SELL)
)
return ret
2 changes: 1 addition & 1 deletion aat/exchange/exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async def connect(self):
For OrderEntry-only, can just return None
'''

def lookup(self, instrument):
async def lookup(self, instrument):
'''lookup an instrument on the exchange'''
return []

Expand Down
4 changes: 2 additions & 2 deletions aat/exchange/public/ib/ib.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def orderStatus(self, orderId, status, filled, remaining, avgFillPrice, permId,
class InteractiveBrokersExchange(Exchange):
'''Interactive Brokers Exchange'''

def __init__(self, trading_type=None, verbose=False, **kwargs):
def __init__(self, trading_type, verbose, **kwargs):
self._trading_type = trading_type
self._verbose = verbose

Expand Down Expand Up @@ -155,7 +155,7 @@ async def tick(self):
# ******************* #
# Order Entry Methods #
# ******************* #
def accounts(self):
async def accounts(self):
'''get accounts from source'''
return []

Expand Down
2 changes: 1 addition & 1 deletion aat/exchange/public/iex.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ async def instruments(self):
instruments.append(inst)
return instruments

def subscribe(self, instrument):
async def subscribe(self, instrument):
self._subscriptions.append(instrument)

async def tick(self):
Expand Down
4 changes: 4 additions & 0 deletions aat/strategy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,10 @@ def exchanges(self, instrument_type=None):
'''Return list of all available exchanges'''
return list(set(__ for _ in Instrument._instrumentdb.instruments(type=instrument_type) for __ in _.exchanges))

def accounts(self, type=None, exchange=None):
'''Return list of all accounts'''
raise NotImplementedError()

def subscribe(self, instrument=None):
'''Subscribe to market data for the given instrument'''
return self._manager.subscribe(instrument=instrument, strategy=self)
Expand Down
19 changes: 19 additions & 0 deletions aat/strategy/sample/readonly.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from aat import Strategy, Event
from pprint import pprint


class ReadOnlyStrategy(Strategy):
def __init__(self, *args, **kwargs) -> None:
super(ReadOnlyStrategy, self).__init__(*args, **kwargs)

async def onStart(self, event: Event) -> None:
pprint(self.instruments())

async def onTrade(self, event: Event) -> None:
pprint(event)

async def onOrder(self, event):
pprint(event)

async def onExit(self, event: Event) -> None:
print('Finishing...')
11 changes: 11 additions & 0 deletions config/coinbase_sandbox.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[general]
verbose=0
trading_type=sandbox

[exchange]
exchanges=
aat.exchange.crypto.coinbase:CoinbaseProExchange

[strategy]
strategies =
aat.strategy.sample.readonly:ReadOnlyStrategy

0 comments on commit f3773aa

Please sign in to comment.