diff --git a/strategies/bollinger_bands_strategy.py b/strategies/bollinger_bands_strategy.py index dc985ab..265d021 100644 --- a/strategies/bollinger_bands_strategy.py +++ b/strategies/bollinger_bands_strategy.py @@ -1,6 +1,6 @@ from datetime import datetime import math -from api import Api +from api import Api, ApiException import time from talipp.indicators import BB @@ -8,7 +8,7 @@ class bollinger_bands_strategy: def __init__(self, api_client, CONFIG, logger): logger.info(" > init: bollinger_bands_strategy instance created.") - + logger.info("========================================================================") logger.info(" ") logger.info(" ⚠️ WARNING! ⚠️ ") @@ -22,16 +22,11 @@ def __init__(self, api_client, CONFIG, logger): logger.info("========================================================================") # Internal state: - self.start_time = datetime.now() - self.counter = 0 self.last_execution_time = None - self.last_order_ref=None self._values = (None, None) - self.api_client=api_client - self.logger=logger + self.api_client : Api = api_client + self.logger = logger self.initialized = False - self.sell_order_ref = None - self.buy_order_ref = None self.last_candle = None # Strategy configuration: @@ -55,20 +50,6 @@ def __init__(self, api_client, CONFIG, logger): def place_buy_order(self, api_client, logger, price): logger.info(" ⚙️ Placing BUY order...") - if not self.buy_order_ref == None: - logger.info(f" > Already placed BUY order. Nothing to do.") - return - - if self.sell_order_ref == None: - logger.debug(f" > No SELL order. Nothing to cancel.") - else: - try: - response = api_client.cancel_order(self.sell_order_ref) - logger.info(f" > [OK] Canceled SELL order: {self.sell_order_ref}") - self.sell_order_ref = None - except: - logger.exception(f" > ⚠️ [FAILED] could not cancel order: {self.sell_order_ref} ⚠️") - logger.error(f" > Exception! ") try: balance_available = int(api_client.get_balances().get(self.base_asset, 0)) logger.debug(f" > balance_available : {balance_available}") @@ -89,29 +70,13 @@ def place_buy_order(self, api_client, logger, price): price_amount=f"{int(math.floor(offered_amount / price))}" ) logger.info(f" > [OK] PLACED NEW BUY ORDER: {response.order_ref}") - self.buy_order_ref=response.order_ref except: logger.error(" > ⚠️ [FAILED] Could not place BUY order. ⚠️") logger.exception(f" > Exception! ") def place_sell_order(self, api_client, logger, price): - logger.info("Placing SELL order...") - - if not self.sell_order_ref == None: - logger.info(f" > Already placed SELL order. Nothing to do.") - return + logger.info(" ⚙️ Placing SELL order...") - if self.buy_order_ref == None: - logger.debug(f" > No BUY order placed. Nothing to cancel.") - else: - try: - logger.info(f" ⚙️ Canceling BUY order: {self.buy_order_ref}") - response = api_client.cancel_order(self.buy_order_ref) - logger.info(f" > [OK] Canceled BUY order: {self.buy_order_ref}") - self.buy_order_ref = None - except: - logger.error(f" > ⚠️ [FAILED] could not cancel order: {self.buy_order_ref} ⚠️") - logger.exception(f" > Exception! ") try: balance_available = int(api_client.get_balances().get(self.target_asset, 0)) logger.info(f" > balance_available : {balance_available}") @@ -130,14 +95,56 @@ def place_sell_order(self, api_client, logger, price): price_amount=f"{int(math.floor(order_size * price))}" ) logger.info(f" > [OK] PLACED NEW SELL ORDER: {response.order_ref}") - self.sell_order_ref=response.order_ref except: logger.error(f" > ⚠️ [FAILED] Could not place SELL order. ⚠️") logger.exception(f" > Exception! ") + def get_buy_orders(self): + own_orders = self.api_client.get_own_orders(self.market) + return own_orders.bids + + def get_sell_orders(self): + own_orders = self.api_client.get_own_orders(self.market) + return own_orders.asks + + def cancel_buy_orders(self): + self.logger.info(" > Cancel all BUY orders...") + self.cancel_orders("bid") + self.logger.info(" > [OK] Canceled all BUY orders.") + + def cancel_sell_orders(self): + self.logger.info(" > Cancel all SELL orders...") + self.cancel_orders("ask") + self.logger.info(" > [OK] Canceled all SELL orders.") + + def cancel_orders(self, side): + while True: + orders = [] + own_orders = self.api_client.get_own_orders(self.market) + if (side == "ask"): + orders = own_orders.asks + else: + orders = own_orders.bids + + if len(orders) == 0: + return + else: + self.logger.info(f" Remaining {side} orders: {len(orders)}.") + + order = orders[0] + try: + self.logger.info(f" ⚙️ Canceling order: {order.output_reference}") + self.api_client.cancel_order(order.output_reference) + self.logger.info(f" > [OK] Canceled order: {order.output_reference}") + except ApiException: + self.logger.error(f" > ⚠️ [FAILED] could not cancel order: {order.output_reference} ⚠️") + self.logger.exception(f" > Exception! ") + def process_candle(self, candle): - self.logger.info(f"--------------------------------------------------------------------------------") - self.logger.info(f" > processsing candle - timestamp: {candle.timestamp} - base_close: {candle.base_close}") + if self.initialized: + self.logger.info(f" > processsing candle - timestamp: {candle.timestamp} - base_close: {candle.base_close}") + else: + self.logger.info(f" > processsing init candle - timestamp: {candle.timestamp} - base_close: {candle.base_close}") if (not self.last_candle == None) and (self.last_candle.timestamp == candle.timestamp): self.logger.info(f" > Candle has already been processsed. Nothing to do.") @@ -152,18 +159,13 @@ def process_candle(self, candle): # Keep a small window of values to check if there is a crossover. self._values = (self._values[-1], value) - self.logger.debug(f" > self.bb.input_values: {self.bb.input_values}") - if len(self.bb) < 2 or self.bb[-1] == None or self.bb[-2] == None: - self.logger.info(f" Bollinger Bands: Initializing... ⚙️ ⏳ ") + self.logger.info(f" BOLLINGER BANDS: Initializing... ⚙️ ⏳ ") self.logger.info(f" > Upper band: Not available.") self.logger.info(f" > Lower band: Not available.") - self.logger.info(f" Orders: ") - self.logger.info(f" > On-Chain BUY order: {self.buy_order_ref} ") - self.logger.info(f" > On-Chain SELL order: {self.sell_order_ref} ") return - self.logger.info(f" Bollinger Bands: ") + self.logger.info(f" BOLLINGER BANDS: ") self.logger.info(f" > Upper band: {self.bb[-1].ub}") self.logger.info(f" > Lower band: {self.bb[-1].lb}") @@ -171,29 +173,54 @@ def process_candle(self, candle): self.logger.info(f" -> Initializaion phase. Do not place orders yet.") return + self.place_buy_order(self.api_client, self.logger, candle.base_close) + # Price moved below lower band ? if self._values[-2] >= self.bb[-2].lb and self._values[-1] < self.bb[-1].lb: self.logger.info(f" -> Price moved below the lower band -> BUY! 🛒 🛒 🛒 ") - self.place_buy_order(self.api_client, self.logger, candle.base_close) + self.cancel_sell_orders() + if len(self.get_buy_orders()) > 0: + self.logger.info(" > Already placed BUY order. Nothing to do.") + else: + self.place_buy_order(self.api_client, self.logger, candle.base_close) # Price moved above upper band ? elif self._values[-2] <= self.bb[-2].ub and self._values[-1] > self.bb[-1].ub: self.logger.info(f" -> Price moved above the upper band -> SELL! 💲 💲 💲 ") - self.place_sell_order(self.api_client, self.logger, candle.base_close) - - self.logger.info(f" Orders: ") - self.logger.info(f" > On-Chain BUY order: {self.buy_order_ref} ") - self.logger.info(f" > On-Chain SELL order: {self.sell_order_ref} ") + self.cancel_buy_orders() + if len(self.get_sell_orders()) > 0: + self.logger.info(" > Already placed SELL order. Nothing to do.") + else: + self.place_sell_order(self.api_client, self.logger, candle.base_low) + self.log_orders() + + def log_orders(self): + own_orders = self.api_client.get_own_orders(self.market) + + self.logger.info(" ON-CHAIN ORDERS:") + + if (len(own_orders.asks) + len(own_orders.bids)) == 0: + self.logger.info(f" > No orders.") + return + + for sell_order in own_orders.asks: + self.logger.info(f" > SELL: {sell_order.output_reference}") + + for buy_order in own_orders.bids: + self.logger.info(f" > BUY: {buy_order.output_reference} ") def execute(self, api_client : Api, CONFIG, logger): current_time = datetime.now() if self.last_execution_time is None: - logger.info("Executing for the first time") + logger.info("Executing for the first time -> initialize.") candles = api_client.get_price_history(self.market, resolution="1m", sort="asc", limit=self.period*5) for candle in candles[:-1]: + self.logger.info(f"--------------------------------------------------------------------------------") self.process_candle(candle) time.sleep(1) + logger.info(" > [OK] Initialized.") + logger.info("========================================================================") self.initialized = True self.last_candle=None else: @@ -204,16 +231,10 @@ def execute(self, api_client : Api, CONFIG, logger): self.last_execution_time = current_time # Update last execution time self.initialized = True - self.counter += 1 - logger.info(f" > Counter: {self.counter}") - try: get_market_price = api_client.get_market_price(self.market) candle=get_market_price[0] - logger.info(f" > Base closing price: {candle.base_close}") self.process_candle(candle) except: logger.error(f" > ⚠️ [FAILED] could not process candle ⚠️") logger.exception(f" > Exception! ") - - logger.info(f" > EXECUTION FINISHED.")