Skip to content

Shwall/coinbasepro-python

 
 

Repository files navigation

coinbasepro-python

About

Build Status

A Python 3 Client Wrapper for the Coinbase Pro Rest API

  • Requires Python 3.6 or greater

  • Provided under MIT License by Daniel Paquin.

Benefits

  • A simple to use python wrapper for both public and authenticated endpoints.
  • In about 10 minutes, you could be programmatically trading on one of the largest Bitcoin exchanges in the world!
  • Do not worry about handling the nuances of the API with easy-to-use methods for every API endpoint.
  • Gain an advantage in the market by getting under the hood of CB Pro to learn what and who is behind every tick.

Under Development

Aside

  • NOTE: This library may be subtly broken or buggy.

  • NOTE: This library is a fork of the original. This library will resemble the original less over time as development continues. The API is not compatible with the original and will break your client interface. If you are here looking for the original GDAX project, you can find it here. I have left the the original LICENSE and contributors.txt files to credit the original author as well as the projects contributors.

License

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Getting Started

This README.md is documentation on the syntax of the python client presented in this repository.

See both Requests and Coinbase Pro API Documentation for full details.

WARNING: It's recommended that you use the websocket interface instead of polling the general interface methods. It's best to reference the Coinbase Pro API Docs to see where polling is allowed, or even encouraged, in some niche cases. Polling can result in blocking, or even banning, access to the API in most other cases.

Install

NOTE: This library conflicts with the original coinbasepro-python package. Make sure to remove it before installing this package to avoid package conflicts.

  • Install manually
git clone https://github.com/teleprint-me/coinbasepro-python.git
  • Install with pip
pip install git+git://github.com/teleprint-me/coinbasepro-python.git

Core API

cbpro.auth.Auth

cbpro.auth.Auth(key: str, secret: str, passphrase: str)

Use the Auth object to authenticate yourself with private endpoints. The Auth object is a callable object that is passed to a requests method.

Example:

import cbpro
import requests

key = 'My Key'
secret = 'My Secret'
passphrase = 'My Passphrase'

sandbox = 'https://api-public.sandbox.pro.coinbase.com'
endpoint = '/accounts'
url = sandbox + endpoint
params = None

auth = cbpro.Auth(key, secret, passphrase)

response = requests.get(
    url,
    params=params,
    auth=auth,
    timeout=30
)

json = response.json()

print(json)

cbpro.messenger.Messenger

class cbpro.messenger.Messenger(auth: cbpro.auth.Auth = None,
                                url: str = None,
                                timeout: int = None) 

The Messenger object is a requests wrapper. It handles most of the common repeated tasks for you.

The Messenger object methods will return a dict in most cases. Methods may also return a list. The Messenger.paginate method returns a generator.

The Messenger object defaults to using the Rest API URL. It is recommended to use the Sandbox Rest API URL instead for testing.

Example:

import cbpro

key = 'My Key'
secret = 'My Secret'
passphrase = 'My Passphrase'

auth = cbpro.Auth(key, secret, passphrase)

sandbox = 'https://api-public.sandbox.pro.coinbase.com'
endpoint = '/accounts'

messenger = cbpro.Messenger(auth=auth, url=sandbox)

accounts = messenger.get(endpoint)

print(accounts)

cbpro.messenger.Messenger.paginate

messenger.paginate(endpoint: str, params: dict = None) -> object

The Messenger interface provides a method for paginated endpoints. Multiple calls must be made to receive the full set of data.

Messenger.paginate returns a generator which provides a clean interface for iteration and may make multiple requests behind the scenes.

The pagination options before, after, and limit may be supplied as keyword arguments if desired and are not necessary for typical use cases.

Example:

import cbpro

key = 'My Key'
secret = 'My Secret'
passphrase = 'My Passphrase'

sandbox = 'https://api-public.sandbox.pro.coinbase.com'
endpoint = '/fills'

auth = cbpro.Auth(key, secret, passphrase)
messenger = cbpro.Messenger(auth=auth, url=sandbox)
params = {'product_id': 'BTC-USD', 'before': 2}
fills = messenger.paginate(endpoint, params)

for fill in fills:
    print(fill)

cbpro.messenger.Subscriber

A Messenger instance is passed to the PublicClient or PrivateClient objects and then shared among the related classes during instantiation of client related objects. Each instance has its own memory and shares a reference to the same Messenger instance.

Any objects that subscribe to the Subscriber interface inherit the initialization of the Subscriber.messenger property allowing them to effectively communicate via the same Auth and requests.Session objects without duplicating code.

# ...
class Time(cbpro.messenger.Subscriber):
    def get(self) -> dict:
        return self.messenger.get('/time')


class PublicClient(object):
    def __init__(self, messenger: cbpro.messenger.Messenger) -> None:
        self.products = Products(messenger)
        self.currencies = Currencies(messenger)
        self.time = Time(messenger)
# ...

Public API

cbpro.public.PublicClient

cbpro.public.PublicClient(messenger: cbpro.messenger.Messenger)

Only some endpoints in the API are available to everyone. The public endpoints can be reached using cbpro.public.PublicClient object.

Example 1:

import cbpro

messenger = cbpro.Messenger()
public = cbpro.PublicClient(messenger)

cbpro.public.public_client

cbpro.public.public_client(url: str = None)

You can use the public_client method in most cases to simplify instantiation. Example 2 is only one line of code where Example 1 is 2 lines of code. They both return an instantiated PublicClient object either way.

Example 2:

import cbpro

public = cbpro.public_client()

cbpro.public.Products

cbpro.public.Products(messenger: cbpro.messenger.Messenger)

cbpro.public.Products.list

public.products.list() -> list

Example:

products = public.products.list()
type(products)  # <class 'list'>
len(products)   # 193

cbpro.public.Products.get

public.products.get(product_id: str) -> dict

Example:

product_id = 'BTC-USD'
product = public.products.get(product_id)
type(product)  # <class 'dict'>
print(product)

cbpro.public.Products.order_book

# NOTE:
#   - Polling is discouraged for this method
#   - Use the websocket stream for polling instead
public.products.order_book(product_id: str, params: dict) -> dict

Example:

params = {'level': 1}
book = public.products.order_book(product_id, params)
type(book)  # <class 'dict'>
print(book)

cbpro.public.Products.ticker

# NOTE:
#   - Polling is discouraged for this method
#   - Use the websocket stream for polling instead
public.product.ticker(product_id: str) -> dict

cbpro.public.Products.trades

# NOTE:
#   - This request is paginated
public.products.trades(product_id: str, params: dict = None) -> object

Example:

params = {'before': 2}
trades = public.products.trades(product_id, params)

type(trades)   # <class 'generator'>
print(trades)  # <generator object Messenger.paginate at 0x7f9b0ac24cf0>

for index, trade in enumerate(trades):
    if index == 10:
        break
    print(trade)

cbpro.public.Products.history

# NOTE:
#   - Polling is discouraged for this method
#   - Use the websocket stream for polling instead
public.products.history(product_id: str, params: dict) -> list

Example:

import datetime

start = datetime.datetime(2021, 4, 1, 0, 0, 0, 0).isoformat()
end = datetime.datetime(2021, 4, 7, 0, 0, 0, 0).isoformat()
day = 86400

params = {'start': start, 'end': end, 'granularity': day}
history = public.products.history(product_id, params)

type(history)  # <class 'list'>
len(history)   # 7
print(history)

cbpro.public.Products.stats

public.products.stats(product_id: str) -> dict

cbpro.public.Currencies

cbpro.public.Currencies(messenger: cbpro.messenger.Messenger)

cbpro.public.Currencies.list

public.currencies.list() -> list

cbpro.public.Currencies.get

public.currencies.get(product_id: str) -> dict

cbpro.public.Time

cbpro.public.Time(messenger: cbpro.messenger.Messenger)

cbpro.public.Time.get

public.time.get() -> dict

cbpro.models.PublicModel

cbpro.models.PublicModel()

Use PublicModel to generate passable parameters easily.

Models will help enforce your code according to what the API expects. If a parameter is incorrect, or forgotten, then the model will raise an AssertionError.

This helps in seperating the application logic from the client interface leaving the client objects clean and tidy.

Example:

import cbpro

model = cbpro.PublicModel()

cbpro.models.ProductsModel

cbpro.models.ProductsModel()

cbpro.models.ProcuctsModel.order_book

model.products.order_book(level: int = None) -> dict

Example 1:

try:  # intentionally trigger
    params = model.products.order_book(5)
except (AssertionError,) as e:
    print('AssertionError:', e)
    # AssertionError: `level` must be one of: [1, 2, 3]

Example 2:

params = model.products.order_book(2)
print(params)  # {'level': 2}

book = public.products.order_book(product_id, params=params)
type(book)  # <class 'dict'>
print(book)

cbpro.models.ProcuctsModel.history

model.products.history(start: str = None, 
                       end: str = None, 
                       granularity: int = 86400) -> dict

Example:

import datetime

start = datetime.datetime(2021, 4, 1, 0, 0, 0, 0).isoformat()
end = datetime.datetime(2021, 4, 7, 0, 0, 0, 0).isoformat()

params = model.products.history(start, end)
print(params)
# {'start': '2021-04-01T00:00:00', 'end': '2021-04-07T00:00:00', 'granularity': 86400}

history = public.products.history(product_id, params)

type(history)  # <class 'list'>
len(history)   # 7
print(history)

cbpro.private.PrivateClient

cbpro.private.PrivateClient(messenger: cbpro.messenger.Messenger)

Not all API endpoints are available to everyone. Those requiring user authentication can be reached using the PrivateClient object. You must setup API access within your Account Settings.

NOTE: The PrivateClient object inherits from the PublicClient object.

Example 1:

import cbpro

key = 'My Key'
secret = 'My Secret'
passphrase = 'My Passphrase'
sandbox = 'https://api-public.sandbox.pro.coinbase.com'

auth = cbpro.Auth(key, secret, passphrase)
messenger = cbpro.Messenger(auth=auth, url=sandbox)
private = cbpro.PrivateClient(messenger)

cbpro.private.private_client

cbpro.private.private_client(key: str,
                             secret: str,
                             passphrase: str,
                             url: str = None) -> PrivateClient:

You can use the private_client method in most cases to simplify instantiation. Example 2 is only one line of code where Example 1 is 3 lines of code. They both return an instantiated PrivateClient object either way.

Example 2:

import cbpro

key = 'My Key'
secret = 'My Secret'
passphrase = 'My Passphrase'
sandbox = 'https://api-public.sandbox.pro.coinbase.com'

private = cbpro.private_client(key, secret, passphrase, sandbox)

cbpro.private.Accounts

cbpro.private.Accounts(messenger: cbpro.messenger.Messenger)

cbpro.private.Accounts.list

private.accounts.list() -> list

cbpro.private.Accounts.get

private.accounts.get(account_id: str) -> dict

cbpro.private.Accounts.history

# NOTE:
#   - This request is paginated
private.accounts.history(account_id: str, params: dict = None) -> list

cbpro.private.Accounts.holds

# NOTE:
#   - This request is paginated
private.accounts.holds(account_id: str, params: dict = None) -> list

cbpro.private.Orders

cbpro.private.Orders(messenger: cbpro.messenger.Messenger)

cbpro.private.Orders.post

private.orders.post(json: dict) -> dict

Example: Limit Order

limit = {
    'side': 'buy',
    'product_id': 'BTC-USD',
    'type': 'limit',
    'price': 57336.2,
    'size': 0.001
}

private.orders.post(limit)

Example: Market Order

market = {
    'side': 'buy',
    'product_id': 'BTC-USD',
    'type': 'market',
    'funds': 100.0
}

private.orders.post(market)

Example: Limit Stop Order

stop = {
    'side': 'buy',
    'product_id': 'BTC-USD',
    'type': 'limit',
    'stop': 'loss',
    'stop_price': 50000.0,
    'price': 57064.8,
    'size': 0.001
}

private.orders.post(stop)

Example: Market Stop Order

stop = {
    'side': 'buy',
    'product_id': 'BTC-USD',
    'type': 'market',
    'stop': 'loss',
    'stop_price': 45000.0,
    'funds': 100.0
}

private.orders.post(stop)

cbpro.private.Orders.cancel

private.orders.cancel(id_: str, params: dict = None) -> list

cbpro.private.Orders.cancel_client

private.orders.cancel_client(oid: str, params: dict = None) -> list

cbpro.private.Orders.cancel_all

private.orders.cancel_all(params: dict = None) -> list

cbpro.private.Orders.list

# NOTE:
#   - This request is paginated
private.orders.list(params: dict) -> list

cbpro.private.Orders.get

private.orders.get(id_: str) -> dict

cbpro.private.Orders.get_client

private.orders.get_client(oid: str) -> dict

cbpro.private.Fills

cbpro.private.Fills(messenger: cbpro.messenger.Messenger)

cbpro.private.Fills.list

# NOTE:
#   - This request is paginated
#   - You are required to provide either a `product_id` or `order_id`
private.fills.list(params: dict) -> list

Example 1:

product_id = {'product_id': 'BTC-USD'}
private.fills.list(product_id)

Example 2:

order_id = {'order_id': '0e953c31-9bce-4007-978c-302be337b566'}
private.fills.list(order_id)

cbpro.private.Limits

cbpro.private.Limits(messenger: cbpro.messenger.Messenger)

cbpro.private.Limits.get

private.limits.get() -> dict

cbpro.private.Deposits

cbpro.private.Deposits(messenger: cbpro.messenger.Messenger)

cbpro.private.Deposits.list

# NOTE:
#   - This request is paginated
private.deposits.list(params: dict = None) -> list

cbpro.private.Deposits.get

private.deposits.get(transfer_id: str) -> dict

cbpro.private.Deposits.payment

private.deposits.payment(json: dict) -> dict

cbpro.private.Deposits.coinbase

private.deposits.coinbase(json: dict) -> dict

cbpro.private.Deposits.generate

private.deposits.generate(account_id: str) -> dict

cbpro.private.Withdrawals

# NOTE:
#   - `cbpro.private.Withdrawls` inherits from `cbpro.private.Deposits`
cbpro.private.Withdrawals(messenger: cbpro.messenger.Messenger)

cbpro.private.Withdrawals.payment

# NOTE:
#   - This method is overridden
private.withdrawals.payment(json: dict) -> dict

cbpro.private.Withdrawals.coinbase

# NOTE:
#   - This method is overridden
private.withdrawals.coinbase(json: dict) -> dict

cbpro.private.Withdrawals.crypto

private.withdrawals.crypto(json: dict) -> dict

cbpro.private.Withdrawals.estimate

private.withdrawals.estimate(params: dict) -> dict

cbpro.private.Conversions

cbpro.private.Conversions(messenger: cbpro.messenger.Messenger)

cbpro.private.Conversions.create

private.conversions.create(json: dict) -> dict

cbpro.private.Payments

cbpro.private.Payments(messenger: cbpro.messenger.Messenger)

cbpro.private.Payments.list

private.payments.list() -> list

cbpro.private.Coinbase

cbpro.private.Coinbase(messenger: cbpro.messenger.Messenger)

cbpro.private.Coinbase.list

private.coinbase.list() -> list

cbpro.private.Fees

cbpro.private.Fees(messenger: cbpro.messenger.Messenger)

cbpro.private.Fees.get

private.fees.get() -> list

cbpro.private.Reports

# NOTE:
#   - This object is currently undefined
cbpro.private.Reports(messenger: cbpro.messenger.Messenger)

cbpro.private.Profiles

cbpro.private.Profiles(messenger: cbpro.messenger.Messenger)

cbpro.private.Profiles.list

private.profiles.list(params: dict = None) -> list

cbpro.private.Profiles.get

private.profiles.get(profile_id: str) -> dict

cbpro.private.Profiles.transfer

private.profiles.transfer(json: dict) -> dict

cbpro.private.Oracle

# NOTE:
#   - This object is currently undefined
cbpro.private.Oracle(messenger: cbpro.messenger.Messenger)

WebsocketClient

If you would like to receive real-time market updates, you must subscribe to the websocket feed.

Subscribe to a single product

import cbpro

# Parameters are optional
wsClient = cbpro.WebsocketClient(url="wss://ws-feed.pro.coinbase.com",
                                products="BTC-USD",
                                channels=["ticker"])
# Do other stuff...
wsClient.close()

Subscribe to multiple products

import cbpro
# Parameters are optional
wsClient = cbpro.WebsocketClient(url="wss://ws-feed.pro.coinbase.com",
                                products=["BTC-USD", "ETH-USD"],
                                channels=["ticker"])
# Do other stuff...
wsClient.close()

WebsocketClient + Mongodb

The WebsocketClient now supports data gathering via MongoDB. Given a MongoDB collection, the WebsocketClient will stream results directly into the database collection.

# import PyMongo and connect to a local, running Mongo instance
from pymongo import MongoClient
import cbpro
mongo_client = MongoClient('mongodb://localhost:27017/')

# specify the database and collection
db = mongo_client.cryptocurrency_database
BTC_collection = db.BTC_collection

# instantiate a WebsocketClient instance, with a Mongo collection as a parameter
wsClient = cbpro.WebsocketClient(url="wss://ws-feed.pro.coinbase.com", products="BTC-USD",
    mongo_collection=BTC_collection, should_print=False)
wsClient.start()

WebsocketClient Methods

The WebsocketClient subscribes in a separate thread upon initialization. There are three methods which you could overwrite (before initialization) so it can react to the data streaming in. The current client is a template used for illustration purposes only.

  • onOpen - called once, immediately before the socket connection is made, this is where you want to add initial parameters.
  • onMessage - called once for every message that arrives and accepts one argument that contains the message of dict type.
  • on_close - called once after the websocket has been closed.
  • close - call this method to close the websocket connection (do not overwrite).
import cbpro, time
class myWebsocketClient(cbpro.WebsocketClient):
    def on_open(self):
        self.url = "wss://ws-feed.pro.coinbase.com/"
        self.products = ["LTC-USD"]
        self.message_count = 0
        print("Lets count the messages!")
    def on_message(self, msg):
        self.message_count += 1
        if 'price' in msg and 'type' in msg:
            print ("Message type:", msg["type"],
                   "\t@ {:.3f}".format(float(msg["price"])))
    def on_close(self):
        print("-- Goodbye! --")

wsClient = myWebsocketClient()
wsClient.start()
print(wsClient.url, wsClient.products)
while (wsClient.message_count < 500):
    print ("\nmessage_count =", "{} \n".format(wsClient.message_count))
    time.sleep(1)
wsClient.close()

Testing

A test suite is under development. Tests for the authenticated client require a set of sandbox API credentials. To provide them, rename api_config.json.example in the tests folder to api_config.json and edit the file accordingly. To run the tests, start in the project directory and run

python -m pytest

Real-time OrderBook

The OrderBook subscribes to a websocket and keeps a real-time record of the orderbook for the product_id input. Please provide your feedback for future improvements.

import cbpro, time
order_book = cbpro.OrderBook(product_id='BTC-USD')
order_book.start()
time.sleep(10)
order_book.close()

Testing

Unit tests are under development using the pytest framework. Contributions are welcome!

To run the full test suite, in the project directory run:

python -m pytest

Change Log

1.1.2 Current PyPI release

  • Refactor project for Coinbase Pro
  • Major overhaul on how pagination is handled

1.0

  • The first release that is not backwards compatible
  • Refactored to follow PEP 8 Standards
  • Improved Documentation

0.3

  • Added crypto and LTC deposit & withdraw (undocumented).
  • Added support for Margin trading (undocumented).
  • Enhanced functionality of the WebsocketClient.
  • Soft launch of the OrderBook (undocumented).
  • Minor bug squashing & syntax improvements.

0.2.2

  • Added additional API functionality such as cancelAll() and ETH withdrawal.

0.2.1

  • Allowed WebsocketClient to operate intuitively and restructured example workflow.

0.2.0

  • Renamed project to GDAX-Python
  • Merged Websocket updates to handle errors and reconnect.

0.1.2

  • Updated JSON handling for increased compatibility among some users.
  • Added support for payment methods, reports, and Coinbase user accounts.
  • Other compatibility updates.

0.1.1b2

  • Original PyPI Release.

About

The unofficial Python client for the Coinbase Pro API

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • Python 98.3%
  • Dockerfile 1.7%