Coinbase Pro is a pretty easy to use crypto exchange. Although it doesn’t allow trading of the entire crypto universe, it does allow users to deposit and trade with fiat currencies. One feature that’s missing, but pretty easy to implement, is automatic deposits.
This tutorial will walk you through using Python to initiate deposits on a periodic basis. I know what you're thinking, "Making automatic deposits does not constitute having a trading bot". Future tutorials will build on this one to complete out all of the bot features. Stay tuned!
Want to skip to the end? Here's the repo with this tutorial's code.
Technologies used
- Python - Programming Language
- Logger - Python package for logging events
- AWS S3 - Storage for logging events
- Heroku - Deployment
- Coinbase Pro - Crypto Exchange
Contents
- Coinbase Pro Set Up
- Heroku Set Up
- Connecting to Coinbase Pro
- Making Deposits
- App Logging (optional)
- Environment Variables in Heroku
- Extra notes
Coinbase Pro Set Up
To get started, make sure you have a production account with Coinbase Pro and an account on the Coinbase Pro Sandbox (this is used for testing). For both accounts, create API credentials
- Profile in upper right corner -> API -> New API Key
- Permissions - Later on, I'll show you how to place trades, so for now you can select all three (view, transfer, trade)
- Passphrase - leave as default
- IP Whitelist - leave as default
- Two Factor Authentication Code - depends on how you set up your account
When you're done, you should have the following for both your production and sandbox accounts:
- Passphrase
- Secret
- Key
Flask App
First create a folder for this project and then initiate and activate a virtual environment
mkdir crypto_trading_bot
cd crypto_trading_bot
virtualenv env
source env/bin/activate
Let's go ahead and install Flask and create the requirements file.
Note: Make sure your virtual environment is activated. If not, all of the packages installed on your computer will be added to the requirements file.
pip install Flask
pip freeze > requirements.txt
Next, create a file with the following
Filename: app.py
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/", methods = ['GET'] )
def func():
return jsonify({"message": "Hello World"})
if __name__ == '__main__':
app.run(debug=True)
This code create a basic Flask app with a single endpoint. Let's test it out. In your terminal write
python app.py
. This runs the Flask app on your local computer. In your terminal, you should see a line that says something like "Running on http://###.#.#.#:###/". If you go to this URL in your browser, you should see "message: Hello World"
Alright, now that the app is set up, let's deploy it to Heroku.
Heroku Set Up
Usually, tutorials walk you through the entire process before showing you how to deploy it. I'm going to go through the deployment step now because Heroku allows for automatic deployments when code is pushed to a branch. Also, from this point on, we'll need to utilize secure information like secrets and passphrases. Using Heroku's environment configuration is way easier than adding all of the keys to my computer's bash_profile, and doing so makes sure you don't mix keys in different projects.
If you haven't already, create an account on Heroku. Then, install the Heroku CLI to your computer and go through the set up process. Afterwords, create an app and head over to the Deploy page to connect it to the repository where you're pushing your code.
- Deployment Method - Github (feel free to use Heroku Git if you want)
- Automatic deploys - Select the branch you want to automatically deploy and then click "Enable Automatic Deploys"
Before we can check out our app, we have to serve it. We'll do so using Gunicorn. Run the following in your terminal (make sure your virtualenv is still running).
pip install gunicorn
pip freeze > requirements.txt
Then, add the following file.
Filename: Procfile
(warning, there's no extension to this file name)
web: gunicorn app:app
Now commit your changes, wait about a minute, and then head over to your site! You can find your domain on the "Settings" page in the "Domain and certificates" section.
Now that that's set up, let's connect the app to Coinbase Pro.
Connecting to Coinbase Pro
Let's create an environment file so that we can deploy the app locally for testing.
Filename: .env
CB_KEY=ENTER_YOUR_SANDBOX_KEY_HERE
CB_PASSPHRASE=ENTER_YOUR_SANDBOX_PASSPHRASE_HERE
CB_SECRET=ENTER_YOUR_SANDBOX_SECRET_HERE
CB_URL=http://api-public.sandbox.pro.coinbase.com
If you're uploading your code to a public repo, make sure to add .env to your .gitignore file.
Next, let's install the Coinbase Pro Python package.
pip install cbpro
pip freeze > requirements.txt
Because I already know that I'll be adding multiple functions (modules) that will need to connect to my Coinbase Pro account (making deposits, placing trades, viewing balances, etc.), I'm going to create a decorator function that will pass my Coinbase authenticated client to any necessary functions. Don't worry, I actually JUST learned about decorator functions. So if this part doesn't make sense to you, don't worry. Decorators don't make sense to most of us.
Filename: cbpro_client.py
import cbpro
from config import CB_CREDENTIALS
def get_client(credentials):
"""Returns the cbpro AuthenticatedClient using the credentials from the parameters dict"""
cbpro_client = cbpro.AuthenticatedClient(credentials['KEY'], credentials['SECRET'], credentials['PASSPHRASE'], api_url=credentials['URL'])
return cbpro_client
def cbpro_client(func):
def function_wrapper(*args, **kwargs):
cbpro_client = get_client(CB_CREDENTIALS)
resp = func(cbpro_client = cbpro_client, *args, **kwargs)
return resp
return function_wrapper
In the same folder, create a config file that will read your credentials from the .env file (when using Heroku locally) or your bash_profile (when using python app.py).
Filename: config.py
# Coinbase Credentials
CB_CREDENTIALS = {
'PASSPHRASE': os.environ['CB_PASSPHRASE'],
'SECRET': os.environ['CB_SECRET'],
'KEY': os.environ['CB_KEY'],
'URL': os.environ['CB_URL']
}
Next up, we're making a deposit!
Making Deposits
Alright, this is the moment we've all been waiting for.
Filename: deposit_funds.py
from cbpro_client import cbpro_client
@cbpro_client
def get_deposit_account(cbpro_client):
""" Gets ID of account's ACH bank account (assumes there's only one)
Params:
- None
Return:
- account: dict
{
{
"allow_buy": bool,
"allow_deposit": bool,
"allow_sell": bool,
"allow_withdraw": bool,
"cdv_status": str,
"created_at": time,
"currency": str,
"id": str,
"limits": dict
"name": str,
"primary_buy": bool,
"primary_sell": bool,
"resource": str,
"resource_path": str,
"type": str,
"updated_at": time,
"verification_method": str,
"verified": bool
}
}
"""
bank_accounts = cbpro_client.get_payment_methods()
for account in bank_accounts:
# This assumes that there is only one ACH bank account connected
if account['type'] == 'ach_bank_account':
return account
@cbpro_client
def deposit_funds(cbpro_client, deposit_amount = 10): # Default deposit amount is $10
""" Makes deposit into USD Wallet
Params:
- deposit_amonut: int (default 10)
Return:
- deposit response
{
'id' : str,
'amount' : str,
'currency' : 'USD',
'payout_at' : str (datetime)
}
"""
deposit_account_id = get_deposit_account()['id']
resp = cbpro_client.deposit(deposit_amount, 'USD', deposit_account_id)
return resp
Let's unpack. The first function retrieves the bank account id for the account used to make the deposit. This function assumes that there's only one ACH account. So, if you have multiple ach accounts connected to your Coinbase account, feel free to customize how the account is selected. The second function uses the returned bank account to make a deposit.
*Random: I'm still trying to figure out my docstring style. If anyone has any tips or suggestions, feel free to share!
Now, let's add this function to our app so that we can test it out! In your app.py file, add the following to the top
from deposit_funds import deposit_funds
And right above "if name == 'main':", add a new endpoint
@app.route("/deposit", methods = ['GET'])
def deposit_function():
resp = deposit_funds()
return jsonify(resp)
Normally, this is where I would say, "To test it out, run the following in your terminal and then head to the deposit endpoint (http://0.0.0.0:####/deposit)"
heroku local
However, this would produce an error because Coinbase doesn't let you deposit funds via API in sandbox mode. So unfortunately, this won't work. However, when you switch over to your real Coinbase credentials, it will be good to go! If you don't believe me (it's okay, we just met), then import the get_deposit_account function instead and use that function for the deposit endpoint. After running your app locally, you should see a JSON object of your deposit account in the browser. Don't forget to switch back to the deposit_funds function.
Now I don't know about you, but I think it would be pretty nice to see the output of each step within the code so I can make sure everything is up and running correctly. Next, we'll add some logging.
Logging
*Feel free to skip this step if logging isn't your flavor
Logging is an AWESOME tool to help you see what's going on in your code (no more print statements!). Because I want to be extra fancy, we'll publish log files to AWS S3 buckets so that we can view them easily.
Here's a tutorial on creating an IAM user on AWS. Also, here's how you create a bucket on AWS S3. Create a bucket and write down the name (e.g. crypto_bot_logs)
Let's create our logger.
First, install boto3, a package to access AWS resources via Python
pip install boto3
pip freeze > requirements.txt
Second, add your IAM credentials to your .env file
Filename: .env
AWS_ACCESS_KEY_ID=ENTER_YOUR_KEY_HERE
AWS_SECRET_ACCESS_KEY=ENTER_YOUR_SECRET_HERE
Thirdly, grab your credentials in your config file by adding the following to the end
Filename: config.py
# AWS Credentials
AWS_ACCESS_KEY_ID = os.environ['AWS_ACCESS_KEY_ID']
AWS_SECRET_ACCESS_KEY = os.environ['AWS_SECRET_ACCESS_KEY']
Fourthly (is fourthly even a word?), create your logger file
Filename: logger.py
import boto3
from datetime import datetime
import logging
from config import AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
s3_client = boto3.client('s3',
aws_access_key_id = AWS_ACCESS_KEY_ID,
aws_secret_access_key = AWS_SECRET_ACCESS_KEY,
region_name = 'us-east-1')
def create_logger(logger_name):
logger = logging.getLogger(logger_name)
logger.setLevel(logging.DEBUG)
# create file handler which logs even debug messages
fh = logging.FileHandler('{}_{}.log'.format(logger_name, datetime.now().isoformat().split('.')[0]))
fh.setLevel(logging.DEBUG)
# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
# add the handlers to the logger
logger.addHandler(fh)
return logger
def upload_log(logger):
file_name = logger.handlers[0].baseFilename
directory = datetime.now().date().isoformat()
key = "{}/{}".format(directory, file_name.split('/')[-1])
bucket_name = ENTER_YOUR_BUCKET_FILENAME_HERE
s3_client.upload_file(Filename = file_name, Bucket = bucket_name, Key = key)
def logger(func):
def function_wrapper(*args, **kwargs):
function_name = func.__name__
logger = create_logger(function_name)
logger.info("Now running - {}".format(function_name))
resp = func(logger = logger, *args, **kwargs)
upload_log(logger)
return resp
return function_wrapper
Here's what each function does:
- create_logger - creates a logger with a FileHandler
- upload_log - Uploads the recently created log file to the designated file
- logger - This is a decorator function that creates a logger, passes it to the function, and then uploads the log file when the function finishes
Now, we can add some logging statements to our functions. Here's what I added, but feel free to customize based on what you want to see. Here's an awesome tutorial on logging.
Here's how my deposit_funds function looks now. Don't forget to import logger at the top
Filename: deposit_funds.py
@cbpro_client
@logger
def deposit_funds(cbpro_client, logger, deposit_amount = 10): # Default deposit amount is $10
""" Makes deposit into USD Wallet
Params:
- deposit_amonut: int (default 10)
Return:
- deposit response
{
'id' : str,
'amount' : str,
'currency' : 'USD',
'payout_at' : str (datetime)
}
"""
logger.info("Getting account ID")
deposit_account_id = get_deposit_account()['id']
logger.info("Account ID: {}".format(deposit_account_id))
resp = cbpro_client.deposit(deposit_amount, 'USD', deposit_account_id)
if 'message' in resp.keys():
logger.warning("In sandbox mode, unable to make deposit")
else:
logger.info("Deposit Response: {}".format(resp))
return resp
If you run your app now (heroku local) and head to the '/deposits' endpoint, you'll see the same error you saw in the previous section. However, if you head to your S3 bucket, open the folder with today's date, and open the file named "deposit_funds_[todays_date].log", you should see something like this.
Yay! Now we can see where in the function the error is coming from. When you deploy the app to Heroku and use your production credentials, the log message will show you the response for making the deposit.
That's (almost) it! Commit your changes and push to your repo so it can make its way to Heroku.
Environment Variables in Heroku
Remember how we created a .env file so that we could read environment variables when using heroku local? Well, now that it's game time, it's time to add our real credentials to our Heroku app. On your app's Settings page in Heroku, click on "Reveal Config Vars" and add the following key value pairs.
- key: CB_KEY value: YOUR_PRODUCTION_CB_KEY
- key: CB_PASSPHRASE value: YOUR_PRODUCTION_PASSPHRASE
- key: CB_SECRET value: YOUR_PRODUCTION_SECRET
- key: CB_URL value: https://api.pro.coinbase.com
- key: AWS_ACCESS_KEY_ID value: YOUR_AWS_ACCESS_KEY
- key: AWS_SECRET_ACCESS_KEY value: YOUR_AWS_SECRET_KEY
- key: TZ value: Your Timezone (e.g. America/New_York) (optional)
Now, when your app runs, it will use these credentials.
Schedule
If you've made it this far, congrats! You now have an app that makes a deposit of $10 whenever you go to http://your_domain_url.com/deposit. But what if you want the app to do it automatically? Next, I'll show you how to make some slight changes so that the deposit_funds function runs on a schedule.
Alright, one more package to install. In your terminal run the following:
pip install flask_script
pip freeze > requirements.txt
Then create the following file.
Filename: manage.py
from flask_script import Manager
from app import app
from deposit_funds import deposit_funds
manager = Manager(app)
@manager.command
def make_deposit():
deposit_funds()
This file creates a command that we can call from Heroku.
Then from the Resources tab on your Heroku dashboard, search for Heroku Scheduler and add it to your app. Note: Heroku might ask you to add payment information to your account. Click on "Heroku Scheduler" and then "Create job". I want my function to run every Thursday. Here's how my job looks
Run Command: if [ "$(date +%u)" = 4 ]; then python manage.py make_deposit; fi
Click "Save Job" to lock it in. You're done! You now have an app that will make automatic deposits to your Coinbase Pro account and log activities to a S3 bucket!
Extra notes
Another alternative is to create an AWS Lambda function and that gets called via AWS Cloudwatch. If you're interested in seeing how to do it this way, let me know!
Top comments (2)
Thanks for this great tutorial. It's been a massive help. I do have a question though - where does the
os
come from in the config.py file? I don't see it defined anywhere...Solved this problem by importing
os
. But now I have a different error:File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/os.py", line 679, in getitem
raise KeyError(key) from None
KeyError: 'CB_PASSPHRASE'
Any ideas on what I'm missing? I've followed the tutorial to the letter :S