Skip to content

Commit

Permalink
Merge pull request #2 from ahrenstein/GeminiSupport
Browse files Browse the repository at this point in the history
Adding Gemini support
  • Loading branch information
ahrenstein authored May 7, 2021
2 parents 028e54b + 6e63814 commit cadce1c
Show file tree
Hide file tree
Showing 12 changed files with 604 additions and 173 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@ Crypto Dip Buying Bot: Changelog
================================
A list of all the changes made to this repo, and the bot it contains

Version 0.3.0
-------------

1. Shifted TODO versions around to reflect new goals.
2. **BREAKING CHANGE** Added Gemini Support
1. The database name the bot uses is now exchange-currency-"bot", so a new DB will be created when
using the new version of the bot.
2. Additionally, you have the option to give the bot a custom name so you can run more than one bot against the same
exchange/currency pair.
3. Fixed a bug where price data would not continue gathering if the bot was not funded.
4. Super basic exception catching around DB functions.
5. `test-compose.yml` for local debugging/testing is now separate from the production example `docker-compose-yml`
6. Added the ability to override the hourly cycle to a different cycle specified in minutes.
7. Fixed a mistake in the default starting config

Version 0.2.0
-------------

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ RUN poetry install --no-dev
ENV PYTHONUNBUFFERED=0

# Run the bot
CMD ["python", "-u", "/app/bankless_bot.py", "-c", "/config/config.json"]
CMD ["python", "-u", "/app/cryptodip_bot.py", "-c", "/config/config.json"]
31 changes: 23 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
Crypto Dip Buying Bot
=====================
This bot is designed to buy cryptocurrency on Coinbase Pro using a USD prefunded portfolio whenever it detects a significant dip in price.
This bot is designed to buy cryptocurrency on Coinbase Pro or Gemini using a USD prefunded portfolio whenever it detects a significant dip in price.

PRE-RELEASE WARNING
-------------------
I tested this code in a sandbox for a few days, before releasing it. I am running this bot against my live Coinbase Pro account as well.
This is still a very new bot with limited testing so **USE THIS BOT AT YOUR OWN RISK!**
USE AT YOUR OWN RISK
--------------------
I run this bot full time against my own personal Coinbase Pro and Gemini accounts, however I make no warranties that
the bot will function. It could crash and miss a dip, or it could detect and buy a dip before the floor. So far
it has done well for me, but your mileage may vary.
As with any open source code: **USE THIS BOT AT YOUR OWN RISK!**

Dip Detection
-------------
The bot runs in hourly cycles. Each cycle the bot will check the price of the specified cryptocurrency.
The bot checks the price in a configurable cycle. Each cycle the bot will check the price of the specified cryptocurrency.
It will then compare the average price of the previous 7 days worth of price history to the configured dip percentage.
If the current price is the configured percentage lower than the price average it will buy the cryptocurrency in the
specified amount of USD.
Expand All @@ -20,11 +22,16 @@ To run the bot you will need Docker and docker-compose installed on your compute

docker-compose up -d

Choosing An Exchange
--------------------
If you specify Gemini credentials at all in the `config.json` file then the bot will use Gemini even if Coinbase Pro
credentials are also specified.

Config File
-----------
You will need the following:

1. Coinbase Pro credentials tied to the portfolio you want to run the bot against
1. Coinbase Pro or Gemini credentials tied to the portfolio you want to run the bot against
2. Dip logic parameters:
1. The cryptocurrency you want to transact in. (It must support being paired against USD in Coinbase Pro)
2. The buy amount you want in $USD.
Expand All @@ -35,9 +42,11 @@ The following sections are optional.
1. Time variables in the bot config
1. Period of days to average (Default: 7)
2. Cool down period before buying again (Default: 7)
3. Check cycle frequency in minutes (Default: 60)
2. AWS credentials:
1. AWS API keys
2. SNS topic ARN (us-east-1 only for now)
3. Optionally you can override the bot name

These settings should be in a configuration file named `config.json` and placed in `./config`.
Additionally, you can override the volume mount to a new path if you prefer.
Expand All @@ -50,13 +59,19 @@ The file should look like this:
"buy_amount": 75.00,
"dip_percentage": 10,
"average_period_days": 3,
"cool_down_period_days": 5
"cool_down_period_days": 5,
"cycle_time_minutes": 15,
"name": "Test-Bot"
},
"coinbase": {
"api_key": "YOUR_API_KEY",
"api_secret": "YOUR_API_SECRET",
"passphrase": "YOUR_API_PASSPHRASE"
},
"gemini": {
"api_key": "YOUR_API_KEY",
"api_secret": "YOUR_API_SECRET",
},
"aws": {
"access_key": "YOUR_API_KEY",
"secret_access_key": "YOUR_API_SECRET",
Expand Down
227 changes: 226 additions & 1 deletion SourceCode/bot_internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,61 @@
# See LICENSE
#


from itertools import count
import json
import datetime
import time
import boto3
import coinbase_pro
import gemini_exchange
import mongo


def read_bot_config(config_file: str) -> [str, float, int, int, int, bool, bool, int, str]:
"""Open a JSON file and get the bot configuration
Args:
config_file: Path to the JSON file containing credentials and config options
Returns:
crypto_currency: The cryptocurrency that will be monitored
buy_amount: The price in $USD that will be purchased when a dip is detected
dip_percentage: The percentage of the average price drop that means a dip occurred
average_period_days: The time period in days to average across
cool_down_period_days: The time period in days that you will wait before transacting
aws_loaded: A bool to determine if AWS configuration options exist
using_gemini: A bool to determine if the bot should use Gemini
cycle_time_minutes: The cycle interval in minutes
bot-name: The name of the bot
"""
with open(config_file) as creds_file:
data = json.load(creds_file)
crypto_currency = data['bot']['currency']
buy_amount = data['bot']['buy_amount']
dip_percentage = data['bot']['dip_percentage']
aws_loaded = bool('aws' in data)
using_gemini = bool('gemini' in data)
if 'average_period_days' in data['bot']:
average_period_days = data['bot']['average_period_days']
else:
average_period_days = 7
if 'cool_down_period_days' in data['bot']:
cool_down_period_days = data['bot']['cool_down_period_days']
else:
cool_down_period_days = 7
if 'cycle_time_minutes' in data['bot']:
cycle_time_minutes = data['bot']['cycle_time_minutes']
else:
cycle_time_minutes = 60
if 'name' in data['bot']:
bot_name = data['bot']['name']
else:
if gemini_exchange:
bot_name = "Gemini-" + crypto_currency + "-bot"
else:
bot_name = "CoinbasePro-" + crypto_currency + "-bot"
return crypto_currency, buy_amount, dip_percentage,\
average_period_days, cool_down_period_days,\
aws_loaded, using_gemini, cycle_time_minutes, bot_name


def get_aws_creds_from_file(config_file: str) -> [str, str, str]:
Expand Down Expand Up @@ -80,3 +132,176 @@ def dip_percent_value(price: float, percent: float) -> float:
"""
dip_price = price * (1 - percent / 100)
return round(dip_price, 2)


def gemini_exchange_cycle(config_file: str, debug_mode: bool) -> None:
"""Perform bot cycles using Gemini as the exchange
Args:
config_file: Path to the JSON file containing credentials
debug_mode: Are we running in debugging mode?
"""
# Load the configuration file
config_params = read_bot_config(config_file)
if config_params[5]:
aws_config = get_aws_creds_from_file(config_file)
message = "%s has been started" % config_params[8]
post_to_sns(aws_config[0], aws_config[1], aws_config[2], message, message)
# Set API URLs
if debug_mode:
gemini_exchange_api_url = "https://api.sandbox.gemini.com"
mongo_db_connection = "mongodb://bots:buythedip@bots-db:27017/"
else:
gemini_exchange_api_url = "https://api.gemini.com"
mongo_db_connection = "mongodb://bots:buythedip@bots-db:27017/"
print("LOG: Starting bot...\nLOG: Monitoring %s on Gemini to buy $%s worth"
" when a %s%% dip occurs." % (config_params[0], config_params[1], config_params[2]))
print("LOG: Dips are checked against a %s day price"
" average with a %s day cool down period" % (config_params[3], config_params[4]))
for cycle in count():
now = datetime.datetime.now().strftime("%m/%d/%Y-%H:%M:%S")
print("LOG: Cycle %s: %s" % (cycle, now))
coin_current_price = gemini_exchange.get_coin_price(
gemini_exchange_api_url, config_params[0])
if coin_current_price == -1:
message = "ERROR: Coin price invalid. This could be an API issue. Ending cycle"
print(message)
subject = "Gemini-%s-Coin price invalid" % config_params[0]
if config_params[5]:
post_to_sns(aws_config[0], aws_config[1], aws_config[2],
subject, message)
time.sleep(config_params[7] * 60)
continue
# Add the current price to the price database
mongo.add_price(config_params[8], mongo_db_connection, coin_current_price)
# Verify that there is enough money to transact, otherwise don't bother
if not gemini_exchange.verify_balance(gemini_exchange_api_url,
config_file, config_params[1]):
message = "LOG: Not enough account balance" \
" to buy $%s worth of %s" % (config_params[1], config_params[0])
subject = "%s Funding Issue" % config_params[8]
if config_params[5]:
post_to_sns(aws_config[0], aws_config[1], aws_config[2],
subject, message)
print("LOG: %s" % message)
# Sleep for the specified cycle interval then end the cycle
time.sleep(config_params[7] * 60)
continue
# Check if the a week has passed since the last dip buy
clear_to_proceed = mongo.check_last_buy_date(config_params[8], mongo_db_connection,
config_params[4])
if clear_to_proceed is True:
print("LOG: Last buy date outside cool down period. Checking if a dip is occurring.")
average_price = mongo.average_pricing(config_params[8], mongo_db_connection,
config_params[3])
dip_price = dip_percent_value(average_price, config_params[2])
print("LOG: A %s%% dip at the average price of %s would be %s"
% (config_params[2], average_price, dip_price))
if coin_current_price <= dip_price:
print("LOG: The current price of %s is <= %s. We are in a dip!"
% (coin_current_price, dip_price))
did_buy = gemini_exchange.buy_currency(gemini_exchange_api_url,
config_file,
config_params[0], config_params[1])
message = "Buy success status is %s for %s worth of %s" \
% (did_buy, config_params[1], config_params[0])
subject = "%s Buy Status Alert" % config_params[8]
mongo.set_last_buy_date(config_params[8], mongo_db_connection)
print("LOG: %s" % message)
if config_params[5]:
post_to_sns(aws_config[0], aws_config[1], aws_config[2],
subject, message)
else:
print("LOG: The current price of %s is > %s. We are not in a dip!"
% (coin_current_price, dip_price))
else:
print("LOG: Last buy date inside cool down period. No buys will be attempted.")

# Run a price history cleanup daily otherwise sleep the interval
if (cycle * config_params[7]) % 1440 == 0:
print("LOG: Cleaning up price history older than 30 days.")
mongo.cleanup_old_records(config_params[8], mongo_db_connection)
else:
# Sleep for the specified cycle interval
time.sleep(config_params[7] * 60)


def coinbase_pro_cycle(config_file: str, debug_mode: bool) -> None:
"""Perform bot cycles using Coinbase Pro as the exchange
Args:
config_file: Path to the JSON file containing credentials
debug_mode: Are we running in debugging mode?
"""
# Load the configuration file
config_params = read_bot_config(config_file)
if config_params[5]:
aws_config = get_aws_creds_from_file(config_file)
message = "%s has been started" % config_params[8]
post_to_sns(aws_config[0], aws_config[1], aws_config[2], message, message)
# Set API URLs
if debug_mode:
coinbase_pro_api_url = "https://api-public.sandbox.pro.coinbase.com/"
mongo_db_connection = "mongodb://bots:buythedip@bots-db:27017/"
else:
coinbase_pro_api_url = "https://api.pro.coinbase.com/"
mongo_db_connection = "mongodb://bots:buythedip@bots-db:27017/"
print("LOG: Starting bot...\nLOG: Monitoring %s on Coinbase Pro to buy $%s worth"
" when a %s%% dip occurs." % (config_params[0], config_params[1], config_params[2]))
print("LOG: Dips are checked against a %s day price"
" average with a %s day cool down period" % (config_params[3], config_params[4]))
for cycle in count():
now = datetime.datetime.now().strftime("%m/%d/%Y-%H:%M:%S")
print("LOG: Cycle %s: %s" % (cycle, now))
coin_current_price = coinbase_pro.get_coin_price\
(coinbase_pro_api_url, config_file, config_params[0])
# Add the current price to the price database
mongo.add_price(config_params[8], mongo_db_connection, coin_current_price)
# Verify that there is enough money to transact, otherwise don't bother
if not coinbase_pro.verify_balance(coinbase_pro_api_url, config_file, config_params[1]):
message = "LOG: Not enough account balance" \
" to buy $%s worth of %s" % (config_params[1], config_params[0])
subject = "%s Funding Issue" % config_params[8]
if config_params[5]:
post_to_sns(aws_config[0], aws_config[1], aws_config[2],
subject, message)
print("LOG: %s" % message)
# Sleep for the specified cycle interval then end the cycle
time.sleep(config_params[7] * 60)
continue
# Check if the a week has passed since the last dip buy
clear_to_proceed = mongo.check_last_buy_date(config_params[8], mongo_db_connection,
config_params[4])
if clear_to_proceed is True:
print("LOG: Last buy date outside cool down period. Checking if a dip is occurring.")
average_price = mongo.average_pricing(config_params[8], mongo_db_connection,
config_params[3])
dip_price = dip_percent_value(average_price, config_params[2])
print("LOG: A %s%% dip at the average price of %s would be %s"
%(config_params[2], average_price, dip_price))
if coin_current_price <= dip_price:
print("LOG: The current price of %s is <= %s. We are in a dip!"
% (coin_current_price, dip_price))
did_buy = coinbase_pro.buy_currency(coinbase_pro_api_url,
config_file, config_params[0], config_params[1])
message = "Buy success status is %s for %s worth of %s"\
% (did_buy, config_params[1], config_params[0])
subject = "%s Buy Status Alert" % config_params[8]
mongo.set_last_buy_date(config_params[8], mongo_db_connection)
print("LOG: %s" % message)
if config_params[5]:
post_to_sns(aws_config[0], aws_config[1], aws_config[2],
subject, message)
else:
print("LOG: The current price of %s is > %s. We are not in a dip!"
% (coin_current_price, dip_price))
else:
print("LOG: Last buy date inside cool down period. No buys will be attempted.")

# Run a price history cleanup daily otherwise sleep the interval
if (cycle * config_params[7]) % 1440 == 0:
print("LOG: Cleaning up price history older than 30 days.")
mongo.cleanup_old_records(config_params[8], mongo_db_connection)
else:
# Sleep for the specified cycle interval
time.sleep(config_params[7] * 60)
2 changes: 1 addition & 1 deletion SourceCode/coinbase_pro.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def verify_balance(api_url: str, config_file: str, buy_amount: float) -> bool:
if float(account['balance']) >= buy_amount:
return True
except Exception as err:
print("ERROR: Unable to current balance!")
print("ERROR: Unable to get current balance!")
print(err)
return False
# Return false by default
Expand Down
Loading

0 comments on commit cadce1c

Please sign in to comment.