Skip to content

Commit

Permalink
Check usd balance before executing buy order (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
freenancial authored Aug 12, 2020
1 parent a9eb1e3 commit d7a68ae
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 74 deletions.
5 changes: 1 addition & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
"editor.insertSpaces": true,
"editor.detectIndentation": false,
"editor.rulers": [
100
88
],

"python.pythonPath": "venv/bin/python",
"python.formatting.autopep8Args": [
"--max-line-length=100"
],
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.provider": "black",
Expand Down
40 changes: 19 additions & 21 deletions bitcoin_dca/bitcoin_dca.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ def __init__(self, encryption_pass=None):
self.secrets = Secret.decryptAllSecrets(encryption_pass)
self.config = Config("config.ini")

if self.config.notificationGmailUserName:
if self.config.notification_gmail_user_name:
self.email_notification = EmailNotification(
self.config.notificationGmailUserName,
self.config.notification_gmail_user_name,
self.secrets["gmail_password"],
self.config.notificationReceiver,
self.config.notification_receiver,
)
if self.config.withdrawEveryXBuy:
if self.config.withdraw_every_x_buy:
self.address_selector = AddressSelector(
self.config.withdrawMasterPublicKey,
self.config.withdrawBeginningAddress,
self.config.withdraw_master_public_key,
self.config.withdraw_beginning_address,
)
self.db_manager = DBManager()
self.next_buy_datetime = self.calcFirstBuyTime()
Expand All @@ -56,30 +56,29 @@ def calcFirstBuyTime(self):
last_buy_datetime = DBManager.convertOrderDatetime(last_buy_order_datetime)
return max(
datetime.datetime.now(),
last_buy_datetime + datetime.timedelta(0, self.config.dcaFrequency),
last_buy_datetime + datetime.timedelta(0, self.config.dca_frequency),
)

def startDCA(self):
Logger.info("--------------------------------------------------")
Logger.info("--------------------------------------------------")
Logger.info("Bitcoin DCA started")
Logger.info("")
Logger.info("----------------------")
Logger.info("----------------------")
Logger.info("Bitcoin DCA started\n")
self.coinbase_pro.showBalance()

while True:
self.waitForNextBuyTime()

Logger.info("--------------------------------------------------")
Logger.info("----------------------")

# Skip buying bitcoin if ahr999 index is above 5.0
try:
ahr999_index_value = ahr999_index.getCurrentIndexValue()
Logger.info(f"ahr999_index: {ahr999_index_value}")
Logger.info(f"ahr999_index: {ahr999_index_value}\n")
if ahr999_index_value > 5.0:
Logger.info("ahr999_index is over 5.0")
Logger.info("Skip this round of Bitcoin purchase")
self.next_buy_datetime += datetime.timedelta(
0, self.config.dcaFrequency
0, self.config.dca_frequency
)
continue
except Exception as error: # pylint: disable=broad-except
Expand All @@ -89,7 +88,7 @@ def startDCA(self):
self.coinbase_pro = self.newCoinbaseProClient()
try:
self.coinbase_pro.showBalance()
self.coinbase_pro.buyBitcoin(self.config.dcaUsdAmount)
self.coinbase_pro.buyBitcoin(self.config.dca_usd_amount)
except Exception as error: # pylint: disable=broad-except
Logger.error(f"Buy Bitcoin failed: {str(error)}")
Logger.error("Waiting for 60 seconds to retry ...")
Expand All @@ -105,13 +104,13 @@ def startDCA(self):
except Exception as error: # pylint: disable=broad-except
Logger.error(f"Withdraw Bitcoin failed: {str(error)}")

self.next_buy_datetime += datetime.timedelta(0, self.config.dcaFrequency)
self.next_buy_datetime += datetime.timedelta(0, self.config.dca_frequency)

def timeToWithdraw(self):
return (
self.address_selector is not None
and self.coinbase_pro.getUnwithdrawnBuysCount()
>= self.config.withdrawEveryXBuy
and self.coinbase_pro.unwithdrawn_buys_count
>= self.config.withdraw_every_x_buy
)

def sendEmailNotification(self):
Expand All @@ -122,7 +121,7 @@ def sendEmailNotification(self):

def withdrawAllBitcoin(self):
self.coinbase_pro.withdrawBitcoin(
self.coinbase_pro.btcAccount().balance,
self.coinbase_pro.btc_account.balance,
self.address_selector.getWithdrawAddress(),
)
self.address_selector.incrementAddressIndex()
Expand All @@ -134,9 +133,8 @@ def waitForNextBuyTime(self):
# Wait for next buy time
Logger.info(
f"Waiting until {self.next_buy_datetime.strftime('%Y-%m-%d %H:%M:%S')} "
f"to buy ${self.config.dcaUsdAmount} Bitcoin..."
f"to buy ${self.config.dca_usd_amount} Bitcoin...\n"
)
Logger.info("")
while datetime.datetime.now() < self.next_buy_datetime:
time.sleep(1)

Expand Down
88 changes: 47 additions & 41 deletions bitcoin_dca/coinbase_pro.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,34 +50,40 @@ def getCoinbaseAccount(self, currency):
Logger.error(self.coinbase_accounts)
raise e

def coinbaseUSDCAccount(self):
@property
def coinbase_usdc_account(self):
return self.convertRawAccount(self.getCoinbaseAccount("USDC"))

def usdAccount(self):
@property
def usd_account(self):
return self.convertRawAccount(self.getAccount("USD"))

def usdcAccount(self):
@property
def usd_balance(self):
return self.usd_account.balance

@property
def usdc_account(self):
return self.convertRawAccount(self.getAccount("USDC"))

def btcAccount(self):
@property
def usdc_balance(self):
return self.usdc_account.balance

@property
def btc_account(self):
return self.convertRawAccount(self.getAccount("BTC"))

def showBalance(self):
self.refresh()
Logger.info("")
Logger.info(
"Coinbase USDC balance: ${:.2f}".format(self.coinbaseUSDCAccount().balance)
)
Logger.info(
"USDC balance: ${:.2f}".format(math.floor(self.usdc_balance() * 100) / 100)
)
Logger.info(
"USD balance: ${:.2f}".format(math.floor(self.usd_balance() * 100) / 100)
)
Logger.info("BTC balance: ₿{}".format(self.btcAccount().balance))
Logger.info("")

def getUnwithdrawnBuysCount(self):
Logger.info("Current Balance:")
Logger.info(" Coinbase: ${:.2f}".format(self.coinbase_usdc_account.balance))
Logger.info(" USDC: ${:.2f}".format(math.floor(self.usdc_balance * 100) / 100))
Logger.info(" USD: ${:.2f}".format(math.floor(self.usd_balance * 100) / 100))
Logger.info(" BTC: ₿{}\n".format(self.btc_account.balance))

@property
def unwithdrawn_buys_count(self):
return self.db_manager.getUnwithdrawnBuysCount()

def depositUSDCFromCoinbase(self, amount):
Expand All @@ -86,31 +92,36 @@ def depositUSDCFromCoinbase(self, amount):
amount = math.ceil(amount * 100) / 100
Logger.info(f"Depositing ${amount} USDC from Coinbase ...")
result = self.auth_client.coinbase_deposit(
amount, "USDC", self.coinbaseUSDCAccount().id
amount, "USDC", self.coinbase_usdc_account.id
)
Logger.info(f" {result}")
Logger.info(f" {result}\n")
time.sleep(5)

def convertUSDCToUSD(self, amount):
self.refresh()

amount = math.ceil(amount * 100) / 100
if self.usdc_balance() < amount + self.config.minUsdcBalance:
if self.usdc_balance < amount + self.config.min_usdc_balance:
self.depositUSDCFromCoinbase(
amount + self.config.minUsdcBalance - self.usdc_balance()
amount + self.config.min_usdc_balance - self.usdc_balance
)

Logger.info(f"Converting ${amount} USDC to USD ...")
result = self.auth_client.convert_stablecoin(amount, "USDC", "USD")
Logger.info(f" {result}")
Logger.info(f" {result}\n")
time.sleep(5)

def buyBitcoin(self, usd_amount):
self.refresh()

usd_amount = math.ceil(usd_amount * 100) / 100
if self.usd_balance() < usd_amount:
self.convertUSDCToUSD(usd_amount - self.usd_balance())
if self.usd_balance < usd_amount:
self.convertUSDCToUSD(usd_amount - self.usd_balance)
self.refresh()
if self.usd_balance < usd_amount:
raise Exception(
f"Insufficient fund, has ${self.usd_balance}, needs ${usd_amount}"
)

Logger.info(f"Buying ${usd_amount} Bitcoin ...")
product_id = "BTC-USD"
Expand All @@ -127,25 +138,18 @@ def buyBitcoin(self, usd_amount):
cost=round(float(order_result["specified_funds"]), 2),
size=order_result["filled_size"],
)
except Exception: # pylint: disable=broad-except
Logger.error(f"Unable to fetch or parse order_result: {order_result}")
except Exception as error: # pylint: disable=broad-except
Logger.error(f"Buy Bitcoin failed, error: {error}; order_result: {order_result}")
time.sleep(5)

def usdc_balance(self):
return self.usdcAccount().balance

def usd_balance(self):
return self.usdAccount().balance

def withdrawBitcoin(self, amount, address):
Logger.info(f"Withdrawing ₿{amount} Bitcoin to address {address} ...")
withdraw_result = self.auth_client.crypto_withdraw(amount, "BTC", address)
Logger.info(withdraw_result)
result = self.auth_client.crypto_withdraw(amount, "BTC", address)
Logger.info(f" {result}\n")
self.db_manager.updateWithdrawAddressForBuyOrders(address)
Logger.info("")

def getBitcoinWorth(self):
return self.btcAccount().balance * self.getBitcoinPrice()
return self.btc_account.balance * self.getBitcoinPrice()

def getBitcoinPrice(self):
return float(
Expand All @@ -154,13 +158,15 @@ def getBitcoinPrice(self):

@staticmethod
def printOrderResult(order_result):
Logger.info(f" Cost: \t{ round( float(order_result['specified_funds']), 2 )}")
cost = round(float(order_result["specified_funds"]), 2)
Logger.info(f" Cost: \t{ cost }")
Logger.info(f" Size: \t{ order_result['filled_size'] }")
Logger.info(
f" Price: \t{ round( float(order_result['funds']) / float(order_result['filled_size']), 2 ) }"
price = round(
float(order_result["funds"]) / float(order_result["filled_size"]), 2
)
Logger.info(f" Price: \t{ price }")
Logger.info(f" Fee: \t{ order_result['fill_fees'] }")
Logger.info(f" Date: \t{ order_result['done_at'] }")
Logger.info(f" Date: \t{ order_result['done_at'] }\n")

@staticmethod
def convertRawAccount(raw_account):
Expand Down
16 changes: 8 additions & 8 deletions bitcoin_dca/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,33 @@ def reload(self):
self.config.read(self.config_file_path)

@property
def dcaUsdAmount(self):
def dca_usd_amount(self):
return self.config["BASIC"].getint("DCA_USD_AMOUNT")

@property
def dcaFrequency(self):
def dca_frequency(self):
return self.config["BASIC"].getint("DCA_FREQUENCY")

@property
def minUsdcBalance(self):
def min_usdc_balance(self):
return self.config["BASIC"].getfloat("MIN_USDC_BALANCE")

@property
def withdrawEveryXBuy(self):
def withdraw_every_x_buy(self):
return self.config["AUTO_WITHDRAWL"].getint("WITHDRAW_EVERY_X_BUY")

@property
def withdrawMasterPublicKey(self):
def withdraw_master_public_key(self):
return self.config["AUTO_WITHDRAWL"].get("MASTER_PUBLIC_KEY")

@property
def withdrawBeginningAddress(self):
def withdraw_beginning_address(self):
return self.config["AUTO_WITHDRAWL"].get("BEGINNING_ADDRESS")

@property
def notificationGmailUserName(self):
def notification_gmail_user_name(self):
return self.config["NOTIFICATION"].get("GMAIL_USER_NAME")

@property
def notificationReceiver(self):
def notification_receiver(self):
return self.config["NOTIFICATION"].get("EMAIL_NOTICE_RECEIVER")

0 comments on commit d7a68ae

Please sign in to comment.