Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
brndnmtthws committed Dec 1, 2017
1 parent 8a844f2 commit 90b945c
Show file tree
Hide file tree
Showing 11 changed files with 368 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,5 @@ ENV/

# mypy
.mypy_cache/

coinmarketcap_cache.sqlite
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM python:3

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY optimal-buy-gdax.py .

ENTRYPOINT [ "python", "./optimal-buy-gdax.py" ]
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,79 @@
# optimal-buy-gdax
Scheduled buying of BTC, ETH, and LTC from GDAX optimally!

![crypto](crypto.gif)

# What is this?

This is a Python script you can use to automatically buy Bitcoin, Ethereum,
and Litecoin using the GDAX API. It buys these 3 currencies, weighted by market
cap (as reported by [coinmarketcap.com](https://coinmarketcap.com/)), using a form of [dollar cost averaging](https://www.bogleheads.org/wiki/Dollar_cost_averaging). according
to the following logic:

1. Check current balances of USD, BTC, ETH, and LTC
1. If the USD balance is above $100, buy BTC, ETH, and LTC weighted by market cap
1. Place 5 discounted limit orders at the current price minus 0.55% up to 0.55%,
each order with 1/5th of the remaining amount to buy for each coin
1. If the USD account balance is below $100, withdraw coins to desired addresses

You can also use the same script to schedule deposits from your bank account
periodically, such as when you're paid.

Ideally, this script would help to make sure that when we dip—

![dip](buy-the-dip.gif)

**we buy**.

# USE AT OWN RISK

Duh. Not my fault if you lose everything.

# How do I use it?

1. Set up a GDAX account, and link your bank account
1. Create the necessary API credentials for GDAX, with permissions to
manage funds, withdraw without 2FA, and trade
1. Determine the payment_method_id value by using the [GDAX API](https://docs.gdax.com/#payment-methods) (you can use your browser's developer toolbar)
1. Get a machine somewhere (GCE, EC2, Digital Ocean) with Docker and systemd
1. Copy systemd files over:

$ sudo cp optimal-buy-gdax-*.{service,timer} /etc/systemd/system
1. Edit [`/etc/systemd/system/optimal-buy-gdax-buy.service`](optimal-buy-gdax-buy.service),
[`/etc/systemd/system/optimal-buy-gdax-buy.timer`](optimal-buy-gdax-buy.timer),
[`/etc/systemd/system/optimal-buy-gdax-deposit.service`](optimal-buy-gdax-deposit.service), and
[`/etc/systemd/system/optimal-buy-gdax-deposit.timer`](optimal-buy-gdax-deposit.timer) to your liking. Make sure you:

* Change the BTC, ETH, and LTC deposit addresses
* Put the correct API keys in
* Check the deposit amount
* Check the timer dates

1. Enable the systemd units:

$ sudo systemctl enable optimal-buy-gdax-buy.service
$ sudo systemctl enable optimal-buy-gdax-buy.timer
$ sudo systemctl enable optimal-buy-gdax-deposit.service
$ sudo systemctl enable optimal-buy-gdax-deposit.timer

1. Start the systemd timers:
$ sudo systemctl start optimal-buy-gdax-buy.timer
$ sudo systemctl start optimal-buy-gdax-deposit.timer

1. Enjoy!

# Caveats/limitations

* Currently it only supports USD
* Some values are hardcoded, but if you want to change that feel free to send a
PR!
* If you try to trade manually or using some other bot at the same time,
you're probably going to have a bad time
* You might have a few dollars (<$100) sitting in your account at all times, even when all orders have been filled

# Tipjar

If you got some value out of this, please send some kudos my way:

* BTC: 37N55ywGVJZsKCsfCmkfDvwukN9GJi93vK
* LTC: 328FYhK7ioQphpsHsQFyDb55yhkEkG6mgf
Binary file added buy-the-dip.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added crypto.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions optimal-buy-gdax-buy.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[Unit]
Description=optimal-buy-gdax-buy
Wants=optimal-buy-gdax-buy.timer
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker stop optimal-buy-gdax
ExecStartPre=-/usr/bin/docker rm optimal-buy-gdax
ExecStartPre=/usr/bin/docker pull brndnmtthws/optimal-buy-gdax
ExecStart=/usr/bin/docker run --name optimal-buy-gdax \
brndnmtthws/optimal-buy-gdax \
--key myapikey \
--b64secret mysecret \
--passphrase mypassphrase \
--mode buy \
--btc-addr 37N55ywGVJZsKCsfCmkfDvwukN9GJi93vK \
--eth-addr 0xb794F5abcde39494cE839613fffBA74279579268 \
--ltc-addr 328FYhK7ioQphpsHsQFyDb55yhkEkG6mgf

[Install]
WantedBy=optimal-buy-gdax-buy.target
9 changes: 9 additions & 0 deletions optimal-buy-gdax-buy.timer
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Unit]
Description=GDAX buy order timer

[Timer]
OnCalendar=*-*-* 00:28:00
Unit=optimal-buy-gdax-buy.service

[Install]
WantedBy=multi-user.target
22 changes: 22 additions & 0 deletions optimal-buy-gdax-deposit.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[Unit]
Description=optimal-buy-gdax-deposit
Wants=optimal-buy-gdax-deposit.timer
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker stop optimal-buy-gdax
ExecStartPre=-/usr/bin/docker rm optimal-buy-gdax
ExecStartPre=/usr/bin/docker pull brndnmtthws/optimal-buy-gdax
ExecStart=/usr/bin/docker run --name optimal-buy-gdax \
brndnmtthws/optimal-buy-gdax \
--key myapikey \
--b64secret mysecret \
--passphrase mypassphrase \
--mode deposit \
--amount 1000 \
--payment-method-id e49c8d15-547b-464e-ac3d-4b9d20b360ec

[Install]
WantedBy=optimal-buy-gdax-deposit.target
9 changes: 9 additions & 0 deletions optimal-buy-gdax-deposit.timer
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Unit]
Description=GDAX deposit timer

[Timer]
OnCalendar=Mon *-*-* 00:00:00
Unit=optimal-buy-gdax-deposit.service

[Install]
WantedBy=multi-user.target
213 changes: 213 additions & 0 deletions optimal-buy-gdax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
#!/usr/bin/env python3

import gdax
import argparse
import sys
from coinmarketcap import Market

parser = argparse.ArgumentParser(description='Buy coins!')
parser.add_argument('--mode',
help='mode (deposit or buy)', required=True)
parser.add_argument('--amount', type=float, help='amount to deposit')
parser.add_argument('--key', help='API key', required=True)
parser.add_argument('--b64secret', help='API secret', required=True)
parser.add_argument('--passphrase', help='API passphrase', required=True)
parser.add_argument('--api-url', help='API URL',
default='https://api.gdax.com')
parser.add_argument('--payment-method-id',
help='Payment method ID for USD deposit')
parser.add_argument('--btc-addr', help='BTC withdrawal address')
parser.add_argument('--eth-addr', help='ETH withdrawal address')
parser.add_argument('--ltc-addr', help='LTC withdrawal address')

args = parser.parse_args()

coins = {
'BTC': 'Bitcoin',
'ETH': 'Ethereum',
'LTC': 'Litecoin',
}

client = gdax.AuthenticatedClient(args.key, args.b64secret, args.passphrase,
args.api_url)


def get_weights():
coinmarketcap = Market()

market_cap = {}
for c in coins:
ticker = coinmarketcap.ticker(currency=coins[c])
market_cap[c] = float(ticker[0]['market_cap_usd'])

total_market_cap = sum(market_cap.values())

weights = {}
for c in coins:
weights[c] = market_cap[c] / total_market_cap
print('Coin weights:')
for w in weights:
print(' {0}: {1:.4f}'.format(w, weights[w]))
print()
return weights


def deposit():
if args.amount is None:
print('Please specify deposit amount with `--amount`')
sys.exit(1)
if args.payment_method_id is None:
print('Please provide a bank ID with `--payment-method-id`')
sys.exit(1)
print('Performing deposit, amount={} USD'.format(args.amount))
result = client.deposit(payment_method_id=args.payment_method_id,
amount=args.amount,
currency='USD')
print(result)


def get_balance_for(accounts, coin):
for a in accounts:
if a['currency'] == coin:
return float(a['balance'])
return 0


def get_prices():
prices = {}
for c in coins:
ticker = client.get_product_ticker(product_id=c + '-USD')
prices[c] = float(ticker['price'])
return prices


def get_usd_balances(accounts, prices):
balances = {}
for a in accounts:
if a['currency'] == 'USD':
balances['USD'] = float(a['balance'])
elif a['currency'] in coins:
balances[a['currency']] = \
float(a['balance']) * prices[a['currency']]
for c in coins:
if c not in balances:
balances[c] = 0
return balances


def get_account(accounts, currency):
for a in accounts:
if a['currency'] == currency:
return a


def place_buy_orders(balance_difference_usd, coin, price):
if balance_difference_usd <= 0.1:
print('Difference for {} is <= 0.1, skipping'.format(coin))
return

# Set 5 buy orders, in 1% discount increments, starting from 5.5% off
remaining_usd = balance_difference_usd
discount = 0.945
amount = remaining_usd / 5.0
for i in range(0, 5):
discount = discount + 0.01
discounted_price = price * discount
size = amount / (discounted_price)
order = client.buy(
price='{0:.2f}'.format(discounted_price),
size='{0:.8f}'.format(size),
type='limit',
product_id=coin + '-USD',
)
print('order={}'.format(order))
if remaining_usd <= 0.01:
break



def start_buy_orders(accounts, prices, usd_balances):
weights = get_weights()

# Determine amount of each coin, in USD, to buy
usd_balance_sum = sum(usd_balances.values())
print('usd_balance_sum={}'.format(usd_balance_sum))

target_amount_usd = {}
for c in coins:
target_amount_usd[c] = usd_balance_sum * weights[c]
print('target_amount_usd={}'.format(target_amount_usd))

balance_differences_usd = {}
for c in coins:
balance_differences_usd[c] = target_amount_usd[c] - usd_balances[c]
print('balance_differences_usd={}'.format(balance_differences_usd))

for c in coins:
place_buy_orders(balance_differences_usd[c], c, prices[c])


def withdraw(accounts):
# Check that we've got addresses
if args.btc_addr is None:
print('No BTC withdraw address specified with `--btc-addr`')
if args.eth_addr is None:
print('No ETH withdraw address specified with `--eth-addr`')
if args.ltc_addr is None:
print('No LTC withdraw address specified with `--ltc-addr`')

# BTC
btc_account = get_account(accounts, 'BTC')
transaction = client.crypto_withdraw(
amount=btc_account['balance'],
currency='BTC',
crypto_address=args.btc_addr
)
print('transaction={}'.format(transaction))

# ETH
eth_account = get_account(accounts, 'ETH')
transaction = client.crypto_withdraw(
amount=eth_account['balance'],
currency='ETH',
crypto_address=args.eth_addr
)
print('transaction={}'.format(transaction))

# LTC
ltc_account = get_account(accounts, 'LTC')
transaction = client.crypto_withdraw(
amount=ltc_account['balance'],
currency='LTC',
crypto_address=args.ltc_addr
)
print('transaction={}'.format(transaction))


def buy():
print('Starting buy and (maybe) withdrawal')
print('First, cancelling orders')
for c in coins:
client.cancel_all(product=c + '-USD')
# Check if there's any USD available to execute a buy
accounts = client.get_accounts()
prices = get_prices()
print('accounts={}'.format(accounts))
print('prices={}'.format(prices))

usd_balances = get_usd_balances(accounts, prices)
print('usd_balances={}'.format(usd_balances))

if usd_balances['USD'] > 100:
print('USD balance above $100, buying more')
start_buy_orders(accounts, prices, usd_balances)
else:
print('Only {} USD balance remaining, withdrawing'
' coins without buying'.format(usd_balances['USD']))
withdraw(accounts)


if args.mode == 'deposit':
deposit()
elif args.mode == 'buy':
buy()
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
gdax
requests
coinmarketcap

0 comments on commit 90b945c

Please sign in to comment.