From 2cc2e0fc51498c6e328bcd6e7d8e007f3d60e4d4 Mon Sep 17 00:00:00 2001 From: Michael Whittle Date: Mon, 10 Apr 2023 21:28:55 +0100 Subject: [PATCH 1/6] improving buy order handling --- controllers/PyCryptoBot.py | 69 ++++++++++++++--------------- models/TradingAccount.py | 4 +- models/exchange/coinbase_pro/api.py | 56 ++++++++++------------- 3 files changed, 60 insertions(+), 69 deletions(-) diff --git a/controllers/PyCryptoBot.py b/controllers/PyCryptoBot.py index eb208f7b..b7897474 100644 --- a/controllers/PyCryptoBot.py +++ b/controllers/PyCryptoBot.py @@ -932,15 +932,19 @@ def _notify(notification: str = "", level: str = "normal") -> None: # if live if self.is_live: - ac = self.account.get_balance() - self.account.base_balance_before = 0.0 - self.account.quote_balance_before = 0.0 + self.insufficientfunds = False + try: - df_base = ac[ac["currency"] == self.base_currency]["available"] - self.account.base_balance_before = 0.0 if len(df_base) == 0 else float(df_base.values[0]) + self.account.quote_balance_before = self.account.get_balance(self.quote_currency) + self.state.last_buy_size = float(self.account.quote_balance_before) + + if self.buymaxsize and self.buylastsellsize and self.state.minimum_order_quote(quote=self.state.last_sell_size, balancechk=True): + self.state.last_buy_size = self.state.last_sell_size + elif self.buymaxsize and self.state.last_buy_size > self.buymaxsize: + self.state.last_buy_size = self.buymaxsize - df_quote = ac[ac["currency"] == self.quote_currency]["available"] - self.account.quote_balance_before = 0.0 if len(df_quote) == 0 else float(df_quote.values[0]) + if self.account.quote_balance_before <= self.state.last_buy_size: + self.insufficientfunds = True except Exception: pass @@ -949,34 +953,24 @@ def _notify(notification: str = "", level: str = "normal") -> None: if not self.is_sim or (self.is_sim and not self.simresultonly): _notify(f"*** Executing SIMULATION Buy Order at {str(self.price)} ***", "info") else: - _notify("*** Executing LIVE Buy Order ***") + _notify("*** Executing LIVE Buy Order ***", "info") # display balances - _notify(f"{self.base_currency} balance before order: {str(self.account.base_balance_before)}") - _notify(f"{self.quote_currency} balance before order: {str(self.account.quote_balance_before)}") - - # execute a live market buy - self.state.last_buy_size = float(self.account.quote_balance_before) - - if self.buymaxsize and self.buylastsellsize and self.state.minimum_order_quote(quote=self.state.last_sell_size, balancechk=True): - self.state.last_buy_size = self.state.last_sell_size - elif self.buymaxsize and self.state.last_buy_size > self.buymaxsize: - self.state.last_buy_size = self.buymaxsize + _notify(f"{self.base_currency} balance before order: {str(self.account.base_balance_before)}", "debug") + _notify(f"{self.quote_currency} balance before order: {str(self.account.quote_balance_before)}", "debug") # place the buy order + resp_error = 0 + try: - df_order = self.market_buy( + self.market_buy( self.market, self.state.last_buy_size, self.get_buy_percent(), ) - if len(df_order) == 0: - resp_error = 1 - else: - resp_error = 0 except Exception as err: - _notify(f"Trade Error: {err}", "warning") + _notify(f"Trade Error: {err}", "error") resp_error = 1 if resp_error == 0: @@ -1006,9 +1000,6 @@ def _notify(notification: str = "", level: str = "normal") -> None: self.state.trailing_buy_immediate = False self.telegram_bot.add_open_order() - _notify(f"{self.base_currency} balance after order: {str(self.account.base_balance_after)}") - _notify(f"{self.quote_currency} balance after order: {str(self.account.quote_balance_after)}") - if not self.disabletelegram: self.notify_telegram( self.market @@ -1048,13 +1039,15 @@ def _notify(notification: str = "", level: str = "normal") -> None: if not self.disabletelegramerrormsgs: self.notify_telegram(f"API Error: Unable to place buy order for {self.market}") + time.sleep(30) else: - _notify( - "Unable to place order, insufficient funds or buyminsize has not been reached. Check Logs.", - "warning", - ) + if not self.is_live: + if not self.is_sim or (self.is_sim and not self.simresultonly): + _notify(f"*** Skipping SIMULATION Buy Order at {str(self.price)} -- Insufficient Funds ***", "warning") + else: + _notify("*** Skipping LIVE Buy Order -- Insufficient Funds ***", "warning") self.state.last_api_call_datetime -= timedelta(seconds=60) @@ -1163,7 +1156,11 @@ def _notify(notification: str = "", level: str = "normal") -> None: elif self.state.action == "SELL": # if live if self.is_live: - _notify(f"{formatted_current_df_index} | {self.market} | {self.print_granularity()} | {str(self.price)} | SELL") + if not self.is_live: + if not self.is_sim or (self.is_sim and not self.simresultonly): + _notify(f"*** Executing SIMULATION Sell Order at {str(self.price)} ***", "info") + else: + _notify("*** Executing LIVE Sell Order ***", "info") # check balances before and display self.account.base_balance_before = 0 @@ -1174,22 +1171,24 @@ def _notify(notification: str = "", level: str = "normal") -> None: except Exception: pass - _notify(f"{self.base_currency} balance before order: {str(self.account.base_balance_before)}") - _notify(f"{self.quote_currency} balance before order: {str(self.account.quote_balance_before)}") + _notify(f"{self.base_currency} balance before order: {str(self.account.base_balance_before)}", "debug") + _notify(f"{self.quote_currency} balance before order: {str(self.account.quote_balance_before)}", "debug") # execute a live market sell baseamounttosell = float(self.account.base_balance_before) if self.sellfullbaseamount is True else float(self.state.last_buy_filled) self.account.base_balance_after = 0 self.account.quote_balance_after = 0 + # place the sell order + resp_error = 0 + try: self.market_sell( self.market, baseamounttosell, self.get_sell_percent(), ) - resp_error = 0 except Exception as err: _notify(f"Trade Error: {err}", "warning") resp_error = 1 diff --git a/models/TradingAccount.py b/models/TradingAccount.py index 967c6e4d..c17ac45b 100644 --- a/models/TradingAccount.py +++ b/models/TradingAccount.py @@ -42,8 +42,8 @@ def __init__(self, app=None): else: self.mode = "test" - self.quotebalance = self.get_balance(app.quote_currency) - self.basebalance = self.get_balance(app.base_currency) + self.quote_balance = self.get_balance(app.quote_currency) + self.base_balance = self.get_balance(app.base_currency) self.base_balance_before = 0.0 self.quote_balance_before = 0.0 diff --git a/models/exchange/coinbase_pro/api.py b/models/exchange/coinbase_pro/api.py index 85f93e42..9c7ea60c 100644 --- a/models/exchange/coinbase_pro/api.py +++ b/models/exchange/coinbase_pro/api.py @@ -432,33 +432,29 @@ def market_buy(self, market: str = "", quote_quantity: float = 0) -> pd.DataFram # validates quote_quantity is either an integer or float if not isinstance(quote_quantity, int) and not isinstance(quote_quantity, float): - if self.app: - RichText.notify("Please report this to Michael Whittle: " + str(quote_quantity) + " " + str(type(quote_quantity)), self.app, "critical") raise TypeError("The funding amount is not numeric.") # funding amount needs to be greater than 10 if quote_quantity < MINIMUM_TRADE_AMOUNT: if self.app: - RichText.notify(f"Trade amount is too small (>= {MINIMUM_TRADE_AMOUNT}).", self.app, "warning") + if self.app.is_live: + RichText.notify("*** Aborting LIVE Buy Order - Insufficient Funds ***", self.app, "warning") + else: + RichText.notify("*** Aborting SIMULATION Buy Order - Insufficient Funds ***", self.app, "warning") return pd.DataFrame() - # raise ValueError(f"Trade amount is too small (>= {MINIMUM_TRADE_AMOUNT}).") - - try: - order = { - "product_id": market, - "type": "market", - "side": "buy", - "funds": self.market_quote_increment(market, quote_quantity), - } - if self.app is not None and self.app.debug is True: - RichText.notify(str(order), self.app, "debug") + order = { + "product_id": market, + "type": "market", + "side": "buy", + "funds": self.market_quote_increment(market, quote_quantity), + } - # place order and return result - return self.auth_api("POST", "orders", order) + if self.app is not None and self.app.debug is True: + RichText.notify(str(order), self.app, "debug") - except Exception: - return pd.DataFrame() + # place order and return result + return self.auth_api("POST", "orders", order) def market_sell(self, market: str = "", base_quantity: float = 0) -> pd.DataFrame: """Executes a market sell providing a crypto amount""" @@ -469,22 +465,18 @@ def market_sell(self, market: str = "", base_quantity: float = 0) -> pd.DataFram if not isinstance(base_quantity, int) and not isinstance(base_quantity, float): raise TypeError("The crypto amount is not numeric.") - try: - order = { - "product_id": market, - "type": "market", - "side": "sell", - "size": self.market_base_increment(market, base_quantity), - } - - if self.app is not None and self.app.debug is True: - RichText.notify(str(order), self.app, "debug") + order = { + "product_id": market, + "type": "market", + "side": "sell", + "size": self.market_base_increment(market, base_quantity), + } - model = AuthAPI(self._api_key, self._api_secret, self._api_passphrase, self._api_url) - return model.auth_api("POST", "orders", order) + if self.app is not None and self.app.debug is True: + RichText.notify(str(order), self.app, "debug") - except Exception: - return pd.DataFrame() + model = AuthAPI(self._api_key, self._api_secret, self._api_passphrase, self._api_url) + return model.auth_api("POST", "orders", order) def limit_sell(self, market: str = "", base_quantity: float = 0, future_price: float = 0) -> pd.DataFrame: """Initiates a limit sell order""" From d1e862adfcd08498f2e9a50cbfe389adef31bd12 Mon Sep 17 00:00:00 2001 From: Michael Whittle Date: Mon, 10 Apr 2023 21:37:26 +0100 Subject: [PATCH 2/6] buy order improvements --- models/exchange/coinbase_pro/api.py | 7 +------ models/exchange/kucoin/api.py | 2 -- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/models/exchange/coinbase_pro/api.py b/models/exchange/coinbase_pro/api.py index 9c7ea60c..82f86780 100644 --- a/models/exchange/coinbase_pro/api.py +++ b/models/exchange/coinbase_pro/api.py @@ -436,12 +436,7 @@ def market_buy(self, market: str = "", quote_quantity: float = 0) -> pd.DataFram # funding amount needs to be greater than 10 if quote_quantity < MINIMUM_TRADE_AMOUNT: - if self.app: - if self.app.is_live: - RichText.notify("*** Aborting LIVE Buy Order - Insufficient Funds ***", self.app, "warning") - else: - RichText.notify("*** Aborting SIMULATION Buy Order - Insufficient Funds ***", self.app, "warning") - return pd.DataFrame() + raise ValueError(f"Trade amount is too small (>= {MINIMUM_TRADE_AMOUNT}).") order = { "product_id": market, diff --git a/models/exchange/kucoin/api.py b/models/exchange/kucoin/api.py index 70f29682..1b18961d 100644 --- a/models/exchange/kucoin/api.py +++ b/models/exchange/kucoin/api.py @@ -479,8 +479,6 @@ def market_buy(self, market: str = "", quote_quantity: float = 0) -> pd.DataFram # validates quote_quantity is either an integer or float if not isinstance(quote_quantity, int) and not isinstance(quote_quantity, float): - if self.app: - RichText.notify("Please report this to Michael Whittle: " + str(quote_quantity) + " " + str(type(quote_quantity)), self.app, "critical") raise TypeError("The funding amount is not numeric.") # funding amount needs to be greater than 10 From a712ef972446ac92b355f6263ad5eb82e3f8c5df Mon Sep 17 00:00:00 2001 From: Nicolas Foret Date: Fri, 14 Apr 2023 17:47:20 +0200 Subject: [PATCH 3/6] Update binance free pairs --- controllers/PyCryptoBot.py | 20 ++------------------ models/exchange/binance/api.py | 20 ++------------------ 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/controllers/PyCryptoBot.py b/controllers/PyCryptoBot.py index eb208f7b..4961592c 100644 --- a/controllers/PyCryptoBot.py +++ b/controllers/PyCryptoBot.py @@ -3160,25 +3160,9 @@ def get_taker_fee(self): return 0.005 # default lowest fee tier elif not self.is_live and self.exchange == Exchange.BINANCE: # https://www.binance.com/en/support/announcement/binance-launches-zero-fee-bitcoin-trading-10435147c55d4a40b64fcbf43cb46329 + # UPDATE: https://www.binance.com/en/support/announcement/updates-on-zero-fee-bitcoin-trading-busd-zero-maker-fee-promotion-be13a645cca643d28eab5b9b34f2dc36 if self.get_market() in [ - "BTCAUD", - "BTCBIDR", - "BTCBRL", - "BTCBUSD", - "BTCEUR", - "BTCGBP", - "BTCRUB", - "BTCTRY", - "BTCTUSD", - "BTCUAH", - "BTCUSDC", - "BTCUSDP", - "BTCUSDT", - "BUSDUSDT", - "PAXBUSD", - "SUSDUSDT", - "USTBUSD", - "USTUSDT", + "BTCTUSD" ]: return 0.0 # no fees for those pairs else: diff --git a/models/exchange/binance/api.py b/models/exchange/binance/api.py index 319eb8da..5cf26449 100644 --- a/models/exchange/binance/api.py +++ b/models/exchange/binance/api.py @@ -262,25 +262,9 @@ def get_fees(self, market: str = "") -> pd.DataFrame: taker_fee_rate = 0.001 # https://www.binance.com/en/support/announcement/binance-launches-zero-fee-bitcoin-trading-10435147c55d4a40b64fcbf43cb46329 + # UPDATE: https://www.binance.com/en/support/announcement/updates-on-zero-fee-bitcoin-trading-busd-zero-maker-fee-promotion-be13a645cca643d28eab5b9b34f2dc36 if market in [ - "BTCAUD", - "BTCBIDR", - "BTCBRL", - "BTCBUSD", - "BTCEUR", - "BTCGBP", - "BTCRUB", - "BTCTRY", - "BTCTUSD", - "BTCUAH", - "BTCUSDC", - "BTCUSDP", - "BTCUSDT", - "BUSDUSDT", - "PAXBUSD", - "SUSDUSDT", - "USTBUSD", - "USTUSDT", + "BTCTUSD" ]: maker_fee_rate = 0 taker_fee_rate = 0 From b5bee68f43899429c5e52ecfd006ef8911f51fe4 Mon Sep 17 00:00:00 2001 From: Michael Whittle Date: Fri, 14 Apr 2023 23:48:38 +0100 Subject: [PATCH 4/6] improving coinbase exchange code --- .vscode/launch.json | 2 +- controllers/PyCryptoBot.py | 10 +- examples/script-coinbase.py | 2 + models/AppState.py | 34 ++++++- models/Stats.py | 4 +- models/TradingAccount.py | 94 ++++++++++++++++++- models/telegram/config.py | 3 +- scanner.py | 10 ++ screener.py | 26 ++++- ...inbase.py => test_exchange_coinbasepro.py} | 0 tests/unit_tests/test_exchange_enum.py | 5 + 11 files changed, 171 insertions(+), 19 deletions(-) rename tests/unit_tests/{test_exchange_coinbase.py => test_exchange_coinbasepro.py} (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 9fc52427..a613b5c0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "pycryptobot.py", "console": "integratedTerminal", - "args": [], + "args": [ "--exchange", "coinbase", "--telegram", "0" ], } ] } \ No newline at end of file diff --git a/controllers/PyCryptoBot.py b/controllers/PyCryptoBot.py index b7897474..47e62c35 100644 --- a/controllers/PyCryptoBot.py +++ b/controllers/PyCryptoBot.py @@ -695,7 +695,7 @@ def execute_job(self): if self.state.last_buy_price != exchange_last_buy["price"]: self.state.last_buy_price = exchange_last_buy["price"] - if self.exchange == Exchange.COINBASEPRO or self.exchange == Exchange.KUCOIN: + if self.exchange == Exchange.COINBASE or self.exchange == Exchange.COINBASEPRO or self.exchange == Exchange.KUCOIN: if self.state.last_buy_fee != exchange_last_buy["fee"]: self.state.last_buy_fee = exchange_last_buy["fee"] @@ -1461,7 +1461,7 @@ def _notify(notification: str = "", level: str = "normal") -> None: # update order tracker csv if self.exchange == Exchange.BINANCE: self.account.save_tracker_csv(self.market) - elif self.exchange == Exchange.COINBASEPRO or self.exchange == Exchange.KUCOIN: + elif self.exchange == Exchange.COINBASE or self.exchange == Exchange.COINBASEPRO or self.exchange == Exchange.KUCOIN: self.account.save_tracker_csv() if self.is_sim: @@ -1526,7 +1526,7 @@ def run(self): if self.websocket and not self.is_sim: RichText.notify("Opening websocket to Coinbase", self, "normal") print("") - self.websocket_connection = CBWebSocketClient([self.market], self.granularity, app=self) + self.websocket_connection = CWebSocketClient([self.market], self.granularity, app=self) self.websocket_connection.start() elif self.exchange == Exchange.COINBASEPRO: message += "Coinbase Pro bot" @@ -1724,7 +1724,7 @@ def initialise(self, banner=True): else: end_date = self.get_date_from_iso8601_str(str(pd.Series(datetime.now()).dt.round(freq="H")[0])) - if self.exchange == Exchange.COINBASEPRO: + if self.exchange == Exchange.COINBASE or self.exchange == Exchange.COINBASEPRO: end_date -= timedelta(hours=random.randint(0, 8760 * 3)) # 3 years in hours else: end_date -= timedelta(hours=random.randint(0, 8760 * 1)) @@ -3068,7 +3068,7 @@ def get_last_buy(self) -> dict: try: if self.exchange == Exchange.COINBASE: - api = CAuthAPI(self.api_key, self.api_secret, self.api_url, app=self) + api = CBAuthAPI(self.api_key, self.api_secret, self.api_url, app=self) orders = api.get_orders(self.market, "", "done") if len(orders) == 0: diff --git a/examples/script-coinbase.py b/examples/script-coinbase.py index 8ee91ca4..175cd25f 100644 --- a/examples/script-coinbase.py +++ b/examples/script-coinbase.py @@ -40,6 +40,8 @@ # print(df) # df = model1.get_product("ADA-GBP") # print(df) +# df = model1.auth_api("GET", "api/v3/brokerage/products/ADA-GBP") +# print(float(df[["base_min_size"]].values[0])) """ COINBASE""" diff --git a/models/AppState.py b/models/AppState.py index b6a30ecb..3bfad7c1 100644 --- a/models/AppState.py +++ b/models/AppState.py @@ -114,10 +114,28 @@ def minimum_order_base(self, base: float = 0.0, balancechk: bool = False): if self.app.exchange == Exchange.BINANCE: df = self.api.get_market_info_filters(self.app.market) if len(df) > 0: + sys.tracebacklimit = 0 + raise Exception(f"Market not found! ({self.app.market})") + + base = float(base) + try: base_min = float( df[df["filterType"] == "LOT_SIZE"][["minQty"]].values[0][0] ) - base = float(base) + except Exception: + base_min = 0.0 + + elif self.app.exchange == Exchange.COINBASE: + product = self.api.auth_api("GET", f"api/v3/brokerage/products/{self.app.market}") + if len(product) == 0: + sys.tracebacklimit = 0 + raise Exception(f"Market not found! ({self.app.market})") + + base = float(base) + try: + base_min = float(product[["base_min_size"]].values[0]) + except Exception: + base_min = 0.0 elif self.app.exchange == Exchange.COINBASEPRO: product = self.api.auth_api("GET", f"products/{self.app.market}") @@ -126,8 +144,10 @@ def minimum_order_base(self, base: float = 0.0, balancechk: bool = False): raise Exception(f"Market not found! ({self.app.market})") base = float(base) - # base_min = float(product["base_min_size"]) - base_min = float(0) + try: + base_min = float(product[["base_min_size"]].values[0]) + except Exception: + base_min = 0.0 elif self.app.exchange == Exchange.KUCOIN: resp = self.api.auth_api("GET", "api/v1/symbols") @@ -137,7 +157,10 @@ def minimum_order_base(self, base: float = 0.0, balancechk: bool = False): raise Exception(f"Market not found! ({self.app.market})") base = float(base) - base_min = float(product["baseMinSize"]) + try: + base_min = float(product["baseMinSize"]) + except Exception: + base_min = 0.0 # additional check for last order type if balancechk: @@ -280,7 +303,8 @@ def get_last_order(self): # binance orders do not show fees if ( - self.app.exchange == Exchange.COINBASEPRO + self.app.exchange == Exchange.COINBASE + or self.app.exchange == Exchange.COINBASEPRO or self.app.exchange == Exchange.KUCOIN ): self.last_buy_fee = float( diff --git a/models/Stats.py b/models/Stats.py index d8567405..57729fa6 100644 --- a/models/Stats.py +++ b/models/Stats.py @@ -31,7 +31,7 @@ def get_data(self, market): time = row["created_at"].to_pydatetime() if row["action"] == "buy": - if self.app.exchange == Exchange.COINBASEPRO: + if self.app.exchange == Exchange.COINBASE or self.app.exchange == Exchange.COINBASEPRO: amount = row["filled"] * row["price"] + row["fees"] else: amount = row["size"] @@ -47,7 +47,7 @@ def get_data(self, market): else: self.order_pairs[-1]["buy"]["size"] += float(amount) else: - if self.app.exchange == Exchange.COINBASEPRO: + if self.app.exchange == Exchange.COINBASE or self.app.exchange == Exchange.COINBASEPRO: amount = (row["filled"] * row["price"]) - row["fees"] else: amount = (float(row["filled"]) * float(row["price"])) - row["fees"] diff --git a/models/TradingAccount.py b/models/TradingAccount.py index c17ac45b..679c8590 100644 --- a/models/TradingAccount.py +++ b/models/TradingAccount.py @@ -64,7 +64,11 @@ def _check_market_syntax(self, market): market : str market to check """ - if self.app.exchange == Exchange.COINBASEPRO and market != "": + if self.app.exchange == Exchange.COINBASE and market != "": + p = re.compile(r"^[0-9A-Z]{1,20}\-[1-9A-Z]{2,5}$") + if not p.match(market): + raise TypeError("Coinbase market is invalid.") + elif self.app.exchange == Exchange.COINBASEPRO and market != "": p = re.compile(r"^[0-9A-Z]{1,20}\-[1-9A-Z]{2,5}$") if not p.match(market): raise TypeError("Coinbase Pro market is invalid.") @@ -142,6 +146,28 @@ def get_orders(self, market="", action="", status="all"): else: return self.orders[self.orders["market"] == market] + if self.app.exchange == Exchange.COINBASE: + if self.mode == "live": + # if config is provided and live connect to Coinbase Pro account portfolio + model = CAuthAPI( + self.app.api_key, + self.app.api_secret, + self.app.api_url, + app=self.app + ) + # retrieve orders from live Coinbase Pro account portfolio + self.orders = model.get_orders(market, action, status) + return self.orders + else: + # return dummy orders + if market == "": + return self.orders + else: + if "market" in self.orders: + return self.orders[self.orders["market"] == market] + else: + return pd.DataFrame() + if self.app.exchange == Exchange.COINBASEPRO: if self.mode == "live": # if config is provided and live connect to Coinbase Pro account portfolio @@ -416,6 +442,67 @@ def get_balance(self, currency=""): ) ) + elif self.app.exchange == Exchange.COINBASE: + if self.mode == "live": + # if config is provided and live connect to Coinbase Pro account portfolio + model = CAuthAPI( + self.app.api_key, + self.app.api_secret, + self.app.api_url, + app=self.app + ) + trycnt, maxretry = (0, 5) + while trycnt <= maxretry: + df = model.get_accounts() + + if len(df) > 0: + # retrieve all balances, but check the resp + if currency == "" and "balance" not in df: + time.sleep(5) + trycnt += 1 + # retrieve all balances and return + elif currency == "": + return df + else: + # retrieve balance of specified currency + df_filtered = df[df["currency"] == currency]["available"] + if len(df_filtered) == 0: + # return nil balance if no positive balance was found + return 0.0 + else: + # return balance of specified currency (if positive) + if currency in ["EUR", "GBP", "USD"]: + return float( + truncate( + float( + df[df["currency"] == currency][ + "available" + ].values[0] + ), + 2, + ) + ) + else: + return float( + truncate( + float( + df[df["currency"] == currency][ + "available" + ].values[0] + ), + 4, + ) + ) + else: + time.sleep(5) + trycnt += 1 + if trycnt >= maxretry: + raise Exception( + "TradingAccount: CoinbasePro API Error while getting balance." + ) + else: + return 0.0 + elif self.app.exchange == Exchange.COINBASEPRO: if self.mode == "live": # if config is provided and live connect to Coinbase Pro account portfolio @@ -842,7 +929,10 @@ def save_tracker_csv(self, market="", save_file="tracker.csv"): self._check_market_syntax(market) if self.mode == "live": - if self.app.exchange == Exchange.COINBASEPRO: + if self.app.exchange == Exchange.COINBASE: + # retrieve orders from live Coinbase account portfolio + df = self.get_orders(market, "", "done") + elif self.app.exchange == Exchange.COINBASEPRO: # retrieve orders from live Coinbase Pro account portfolio df = self.get_orders(market, "", "done") elif self.app.exchange == Exchange.BINANCE: diff --git a/models/telegram/config.py b/models/telegram/config.py index 11d76516..78853ca8 100644 --- a/models/telegram/config.py +++ b/models/telegram/config.py @@ -102,7 +102,8 @@ def get_config_options(self, update: Update, context: CallbackContext, callback: if query.data.__contains__("edit_"): exchange = query.data.replace("edit_", "") elif ( - query.data.__contains__(Exchange.COINBASEPRO.value) + query.data.__contains__(Exchange.COINBASE.value) + or query.data.__contains__(Exchange.COINBASEPRO.value) or query.data.__contains__(Exchange.BINANCE.value) or query.data.__contains__(Exchange.KUCOIN.value) or query.data.__contains__("scanner") diff --git a/scanner.py b/scanner.py index 6eb28fd2..7109f5f5 100644 --- a/scanner.py +++ b/scanner.py @@ -7,6 +7,7 @@ from models.helper.TelegramBotHelper import TelegramBotHelper as TGBot from models.Trading import TechnicalAnalysis from models.exchange.binance import PublicAPI as BPublicAPI +from models.exchange.coinbase import AuthAPI as CBAuthAPI from models.exchange.coinbase_pro import PublicAPI as CPublicAPI from models.exchange.kucoin import PublicAPI as KPublicAPI from models.exchange.Granularity import Granularity @@ -32,6 +33,8 @@ for quote in config[ex.value]["quote_currency"]: if ex == Exchange.BINANCE: api = BPublicAPI(bot_config[ex.value]["api_url"]) + elif ex == Exchange.COINBASE: + api = CBAuthAPI(bot_config[ex.value]["api_key"], bot_config[ex.value]["api_secret"], bot_config[ex.value]["api_url"]) elif ex == Exchange.COINBASEPRO: api = CPublicAPI() elif ex == Exchange.KUCOIN: @@ -45,6 +48,11 @@ for row in resp: if row["symbol"].endswith(quote): markets.append(row) + elif ex == Exchange.COINBASE: + for market in resp: + if market.endswith(f"-{quote}"): + resp[market]["stats_24hour"]["market"] = market + markets.append(resp[market]["stats_24hour"]) elif ex == Exchange.COINBASEPRO: for market in resp: if market.endswith(f"-{quote}"): @@ -60,6 +68,8 @@ if ex == Exchange.BINANCE: df_markets = df_markets[["symbol", "lastPrice", "quoteVolume"]] + elif ex == Exchange.COINBASE: + df_markets = df_markets[["market", "last", "volume"]] elif ex == Exchange.COINBASEPRO: df_markets = df_markets[["market", "last", "volume"]] elif ex == Exchange.KUCOIN: diff --git a/screener.py b/screener.py index 0c87569a..64a631b0 100755 --- a/screener.py +++ b/screener.py @@ -12,6 +12,7 @@ from controllers.PyCryptoBot import PyCryptoBot from models.helper.TelegramBotHelper import TelegramBotHelper as TGBot from models.exchange.binance import PublicAPI as BPublicAPI +from models.exchange.coinbase import AuthAPI as CBAuthAPI from models.exchange.coinbase_pro import PublicAPI as CPublicAPI from models.exchange.kucoin import PublicAPI as KPublicAPI from models.exchange.Granularity import Granularity @@ -73,9 +74,9 @@ def load_configs(): binance_app.selection_score = exchange_config.get("selection_score", 10) binance_app.tv_screener_ratings = [rating.upper() for rating in exchange_config.get("tv_screener_ratings", ["STRONG_BUY"])] exchanges_loaded.append(binance_app) - elif ex == CryptoExchange.COINBASEPRO: + elif ex == CryptoExchange.COINBASE: coinbase_app = PyCryptoBot(exchange=ex) - coinbase_app.public_api = CPublicAPI() + coinbase_app.public_api = CBAuthAPI(bot_config[ex.value]["api_key"], bot_config[ex.value]["api_secret"], bot_config[ex.value]["api_url"]) coinbase_app.scanner_quote_currencies = exchange_config.get("quote_currency", ["USDT"]) coinbase_app.granularity = Granularity(Granularity.convert_to_enum(int(exchange_config.get("granularity", "3600")))) coinbase_app.adx_threshold = exchange_config.get("adx_threshold", 25) @@ -87,6 +88,20 @@ def load_configs(): coinbase_app.selection_score = exchange_config.get("selection_score", 10) coinbase_app.tv_screener_ratings = [rating.upper() for rating in exchange_config.get("tv_screener_ratings", ["STRONG_BUY"])] exchanges_loaded.append(coinbase_app) + elif ex == CryptoExchange.COINBASEPRO: + coinbase_pro_app = PyCryptoBot(exchange=ex) + coinbase_pro_app.public_api = CPublicAPI() + coinbase_pro_app.scanner_quote_currencies = exchange_config.get("quote_currency", ["USDT"]) + coinbase_pro_app.granularity = Granularity(Granularity.convert_to_enum(int(exchange_config.get("granularity", "3600")))) + coinbase_pro_app.adx_threshold = exchange_config.get("adx_threshold", 25) + coinbase_pro_app.volatility_threshold = exchange_config.get("volatility_threshold", 9) + coinbase_pro_app.minimum_volatility = exchange_config.get("minimum_volatility", 5) + coinbase_pro_app.minimum_volume = exchange_config.get("minimum_volume", 20000) + coinbase_pro_app.volume_threshold = exchange_config.get("volume_threshold", 20000) + coinbase_pro_app.minimum_quote_price = exchange_config.get("minimum_quote_price", 0.0000001) + coinbase_pro_app.selection_score = exchange_config.get("selection_score", 10) + coinbase_pro_app.tv_screener_ratings = [rating.upper() for rating in exchange_config.get("tv_screener_ratings", ["STRONG_BUY"])] + exchanges_loaded.append(coinbase_pro_app) elif ex == CryptoExchange.KUCOIN: kucoin_app = PyCryptoBot(exchange=ex) kucoin_app.public_api = KPublicAPI(bot_config[ex.value]["api_url"]) @@ -126,6 +141,11 @@ def get_markets(app, quote_currency): for row in resp: if row["symbol"].endswith(quote_currency): markets.append(row["symbol"]) + elif app.exchange == CryptoExchange.COINBASE: + for market in resp: + market = str(market) + if market.endswith(f"-{quote_currency}"): + markets.append(market) elif app.exchange == CryptoExchange.COINBASEPRO: for market in resp: market = str(market) @@ -254,7 +274,7 @@ def process_screener_data(app, markets, quote_currency, exchange_name): # print(f"Symbol: {ta.symbol} Score: {score}/{self.selection_score} Rating: {rating}") if (score >= app.selection_score) and (rating in app.tv_screener_ratings): relavent_ta = {} - if app.exchange == CryptoExchange.COINBASEPRO or app.exchange == CryptoExchange.KUCOIN: + if app.exchange == CryptoExchange.COINBASE or app.exchange == CryptoExchange.COINBASEPRO or app.exchange == CryptoExchange.KUCOIN: relavent_ta["market"] = re.sub(rf"(.*){quote_currency}", rf"\1-{quote_currency}", ta.symbol) # relavent_ta['market'] = re.sub(quote_currency,f"-{quote_currency}", ta.symbol) else: diff --git a/tests/unit_tests/test_exchange_coinbase.py b/tests/unit_tests/test_exchange_coinbasepro.py similarity index 100% rename from tests/unit_tests/test_exchange_coinbase.py rename to tests/unit_tests/test_exchange_coinbasepro.py diff --git a/tests/unit_tests/test_exchange_enum.py b/tests/unit_tests/test_exchange_enum.py index 3e97d101..38d548ab 100644 --- a/tests/unit_tests/test_exchange_enum.py +++ b/tests/unit_tests/test_exchange_enum.py @@ -11,6 +11,10 @@ def test_enum_value_is_correct_for_binance(): assert Exchange.BINANCE.value == "binance" +def test_enum_value_is_correct_for_coinbase(): + assert Exchange.COINBASE.value == "coinbase" + + def test_enum_value_is_correct_for_coinbasepro(): assert Exchange.COINBASEPRO.value == "coinbasepro" @@ -25,6 +29,7 @@ def test_enum_value_is_correct_for_dummy(): def test_converting_string_to_enum(): assert Exchange("binance") == Exchange.BINANCE + assert Exchange("coinbase") == Exchange.COINBASE assert Exchange("coinbasepro") == Exchange.COINBASEPRO assert Exchange("kucoin") == Exchange.KUCOIN From 74efbd4a0d0882e3e7253b8d6afcd403a76a4875 Mon Sep 17 00:00:00 2001 From: Michael Whittle Date: Sat, 15 Apr 2023 00:07:20 +0100 Subject: [PATCH 5/6] improving coinbase exchange code --- .vscode/launch.json | 2 +- controllers/PyCryptoBot.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index a613b5c0..9fc52427 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "pycryptobot.py", "console": "integratedTerminal", - "args": [ "--exchange", "coinbase", "--telegram", "0" ], + "args": [], } ] } \ No newline at end of file diff --git a/controllers/PyCryptoBot.py b/controllers/PyCryptoBot.py index 47e62c35..6bfc4540 100644 --- a/controllers/PyCryptoBot.py +++ b/controllers/PyCryptoBot.py @@ -3189,6 +3189,7 @@ def get_taker_fee(self): elif self.exchange == Exchange.COINBASE: api = CBAuthAPI(self.api_key, self.api_secret, self.api_url, app=self) self.takerfee = api.get_taker_fee() + return self.takerfee elif self.exchange == Exchange.COINBASEPRO: api = CAuthAPI(self.api_key, self.api_secret, self.api_passphrase, self.api_url, app=self) self.takerfee = api.get_taker_fee() From b95c93d3bcb8ebdd56c0bddee2f4498b211545b0 Mon Sep 17 00:00:00 2001 From: Michael Whittle Date: Sat, 15 Apr 2023 16:52:08 +0100 Subject: [PATCH 6/6] updated version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a7ddeb4..a9a73606 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Python Crypto Bot v8.0.3 (pycryptobot) +# Python Crypto Bot v8.0.5 (pycryptobot) [![Docker](https://github.com/whittlem/pycryptobot/actions/workflows/container.yml/badge.svg)](https://github.com/whittlem/pycryptobot/actions/workflows/container.yml/badge.svg) [![Tests](https://github.com/whittlem/pycryptobot/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/whittlem/pycryptobot/actions/workflows/unit-tests.yml/badge.svg)