Beispiel #1
0
    def marketSell(self, market: str='', base_quantity: float=0) -> list:
        """Executes a market sell providing a crypto amount"""

        # validates the market is syntactically correct
        if not self._isMarketValid(market):
            raise ValueError('Binance market is invalid.')

        if not isinstance(base_quantity, int) and not isinstance(base_quantity, float):
            raise TypeError('The crypto amount is not numeric.')

        try:
            df_filters = self.getMarketInfoFilters(market)
            step_size = float(df_filters.loc[df_filters['filterType'] == 'LOT_SIZE']['stepSize'])
            precision = int(round(-math.log(step_size, 10), 0))

            # remove fees
            base_quantity = base_quantity - (base_quantity * self.getTradeFee(market))

            # execute market sell
            stepper = 10.0 ** precision
            truncated = math.trunc(stepper * base_quantity) / stepper
            Logger.info('Order quantity after rounding and fees: ' + str(truncated))
            return self.client.order_market_sell(symbol=market, quantity=truncated)
        except Exception as err:
            ts = datetime.now().strftime("%d-%m-%Y %H:%M:%S")
            Logger.error(ts + ' Binance ' + ' marketSell ' + str(err))
            return []
Beispiel #2
0
    def __init__(self, api_key: str='', api_secret: str='', api_url: str='https://api.binance.com') -> None:
        """Binance API object model
    
        Parameters
        ----------
        api_key : str
            Your Binance account portfolio API key
        api_secret : str
            Your Binance account portfolio API secret
        """
    
        # options
        self.debug = False
        self.die_on_api_error = False

        valid_urls = [
            'https://api.binance.com/',
            'https://api.binance.us/',
            'https://testnet.binance.vision/api/'
        ]

        if len(api_url) > 1 and api_url[-1] != '/':
            api_url = api_url + '/'

        # validate Binance API
        if api_url not in valid_urls:
            raise ValueError('Binance API URL is invalid')

        # validates the api key is syntactically correct
        p = re.compile(r"^[A-z0-9]{64,64}$")
        if not p.match(api_key):
            self.handle_init_error('Binance API key is invalid')
 
        # validates the api secret is syntactically correct
        p = re.compile(r"^[A-z0-9]{64,64}$")
        if not p.match(api_secret):
            self.handle_init_error('Binance API secret is invalid')

        self.mode = 'live' # TODO: check if this needs to be set here
        self.api_url = api_url
        self.api_key = api_key
        self.api_secret = api_secret
        self.order_attempts = 0

        for i in range(10):
            try:
                sys.tracebacklimit = 0
                if 'api.binance.us' in api_url:
                    self.client = Client(self.api_key, self.api_secret, { 'verify': False, 'timeout': 20 }, tld='us')
                else:
                    self.client = Client(self.api_key, self.api_secret, { 'verify': False, 'timeout': 20 })
                break
            except Exception as e:
                if i == 9:
                    raise SystemExit("Can not create instance of AuthAPI client.")
                Logger.error('Exception: ' + str(e)) 
                Logger.error('Error on creating instance of AuthAPI Client. Trying again... Attempt: ' + str(i))
                sleep(0.1)

        sys.tracebacklimit = 1
Beispiel #3
0
    def authAPI(self,
                method: str,
                uri: str,
                payload: str = '') -> pd.DataFrame:
        if not isinstance(method, str):
            raise TypeError('Method is not a string.')

        if not method in ['DELETE', 'GET', 'POST']:
            raise TypeError('Method not DELETE, GET or POST.')

        if not isinstance(uri, str):
            raise TypeError('URI is not a string.')

        try:
            if method == 'DELETE':
                resp = requests.delete(self._api_url + uri, auth=self)
            elif method == 'GET':
                resp = requests.get(self._api_url + uri, auth=self)
            elif method == 'POST':
                resp = requests.post(self._api_url + uri,
                                     json=payload,
                                     auth=self)

            if resp.status_code != 200:
                if self.die_on_api_error or resp.status_code == 401:
                    # disable traceback
                    sys.tracebacklimit = 0

                    raise Exception(method.upper() + ' (' +
                                    '{}'.format(resp.status_code) + ') ' +
                                    self._api_url + uri + ' - ' +
                                    '{}'.format(resp.json()['message']))
                else:
                    Logger.error('error: ' + method.upper() + ' (' +
                                 '{}'.format(resp.status_code) + ') ' +
                                 self._api_url + uri + ' - ' +
                                 '{}'.format(resp.json()['message']))
                    return pd.DataFrame()

            resp.raise_for_status()

            if isinstance(resp.json(), list):
                df = pd.DataFrame.from_dict(resp.json())
                return df
            else:
                df = pd.DataFrame(resp.json(), index=[0])
                return df

        except requests.ConnectionError as err:
            return self.handle_api_error(err, 'ConnectionError')

        except requests.exceptions.HTTPError as err:
            return self.handle_api_error(err, 'HTTPError')

        except requests.Timeout as err:
            return self.handle_api_error(err, 'Timeout')

        except json.decoder.JSONDecodeError as err:
            return self.handle_api_error(err, 'JSONDecodeError')
Beispiel #4
0
    def handle_init_error(self, err: str) -> None:
        """Handle initialisation error"""

        if self.debug:
            raise TypeError(err)
        elif self.die_on_api_error:
            raise SystemExit(err)
        else:
            Logger.error(f"Initialization Error: {err}")
Beispiel #5
0
    def authAPI(self, method: str, uri: str, payload: str = "") -> dict:
        """Initiates a REST API call"""

        if not isinstance(method, str):
            raise TypeError("Method is not a string.")

        if not method in ["GET", "POST"]:
            raise TypeError("Method not GET or POST.")

        if not isinstance(uri, str):
            raise TypeError("URI is not a string.")

        try:
            if method == "GET":
                resp = requests.get(self._api_url + uri)
            elif method == "POST":
                resp = requests.post(self._api_url + uri, json=payload)

            trycnt, maxretry = (1, 5)
            while trycnt <= maxretry:
                if resp.status_code != 200:
                    resp_message = resp.json()["message"]
                    message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message}"
                    if self.die_on_api_error:
                        raise Exception(message)
                    else:
                        Logger.error(
                            f"Coinbase Pro API request error - retry attempt {trycnt}: {message}"
                        )
                    time.sleep(15)
                    trycnt += 1
                else:
                    break

                if method == "GET":
                    resp = requests.get(self._api_url + uri)
                elif method == "POST":
                    resp = requests.post(self._api_url + uri, json=payload)

            resp.raise_for_status()
            return resp.json()

        except requests.ConnectionError as err:
            Logger.error("requests.ConnectionError")  # remove this later
            return self.handle_api_error(err, "ConnectionError")

        except requests.exceptions.HTTPError as err:
            Logger.error("requests.exceptions.HTTPError")  # remove this later
            return self.handle_api_error(err, "HTTPError")

        except requests.Timeout as err:
            Logger.error("requests.Timeout")  # remove this later
            return self.handle_api_error(err, "Timeout")

        except json.decoder.JSONDecodeError as err:
            Logger.error("json.decoder.JSONDecodeError")  # remove this later
            return self.handle_api_error(err, "JSONDecodeError")
Beispiel #6
0
    def getTime(self) -> datetime:
        """Retrieves the exchange time"""

        try:
            # GET /api/v3/time
            resp = self.authAPI("GET", "/api/v3/time")
            return self.convert_time(int(resp["serverTime"])) - timedelta(hours=1)
        except Exception as e:
            Logger.error(f"Error: {e}")
            return None
Beispiel #7
0
    def marketBuy(
        self, market: str = "", quote_quantity: float = 0, test: bool = False
    ) -> list:
        """Executes a market buy providing a funding amount"""

        # validates the market is syntactically correct
        if not self._isMarketValid(market):
            raise ValueError("Binance market is invalid.")

        # validates quote_quantity is either an integer or float
        if not isinstance(quote_quantity, int) and not isinstance(
            quote_quantity, float
        ):
            raise TypeError("The funding amount is not numeric.")

        try:
            current_price = self.getTicker(market)[1]

            base_quantity = np.divide(quote_quantity, current_price)

            df_filters = self.getMarketInfoFilters(market)
            step_size = float(
                df_filters.loc[df_filters["filterType"] == "LOT_SIZE"]["stepSize"]
            )
            precision = int(round(-math.log(step_size, 10), 0))

            # remove fees
            base_quantity = base_quantity - (base_quantity * self.getTradeFee(market))

            # execute market buy
            stepper = 10.0 ** precision
            truncated = math.trunc(stepper * base_quantity) / stepper

            order = {
                "symbol": market,
                "side": "BUY",
                "type": "MARKET",
                "quantity": truncated,
                "recvWindow": self.recv_window,
            }

            # Logger.debug(order)

            # POST /api/v3/order/test
            if test is True:
                resp = self.authAPI("POST", "/api/v3/order/test", order)
            else:
                resp = self.authAPI("POST", "/api/v3/order", order)

            return resp
        except Exception as err:
            ts = datetime.now().strftime("%d-%m-%Y %H:%M:%S")
            Logger.error(f"{ts} Binance  marketBuy {str(err)}")
            return []
Beispiel #8
0
    def getTakerFee(self, market: str='') -> float:
        if market == '':
            return DEFAULT_TAKER_FEE_RATE
        else:
            fees = self.getFees(market)

        if len(fees) == 0 or 'taker_fee_rate' not in fees:
            Logger.error(f"error: 'taker_fee_rate' not in fees (using {DEFAULT_TAKER_FEE_RATE} as a fallback)")
            return DEFAULT_TAKER_FEE_RATE

        return float(fees['taker_fee_rate'].to_string(index=False).strip())
Beispiel #9
0
 def __init__(self) -> None:
     for i in range(10):
         try:
             self.client = Client()
             break
         except Exception as e:
             if i == 9:
                 raise SystemExit("Can not create instance of AuthAPI client.")
             Logger.error('Exception: ' + str(e)) 
             Logger.error('Error on creating instance of AuthAPI Client. Trying again... Attempt: ' + str(i))
             sleep(0.1)
Beispiel #10
0
    def on_error(self, e, data=None):
        Logger.error(e)
        Logger.error("{} - data: {}".format(e, data))

        self.stop = True
        try:
            self.ws = None
            self.tickers = None
            self.candles = None
            self.start_time = None
            self.time_elapsed = 0
        except:
            pass
Beispiel #11
0
    def getTime(self) -> datetime:
        """Retrieves the exchange time"""

        try:
            resp = self.authAPI("GET", "time")
            if "epoch" in resp:
                epoch = int(resp["epoch"])
                return datetime.fromtimestamp(epoch)
            else:
                Logger.error(resp)
                return None
        except Exception as e:
            Logger.error(f"Error: {e}")
            return None
Beispiel #12
0
    def handle_api_error(self, err: str, reason: str) -> dict:
        """Handler for API errors"""

        if self.debug:
            if self.die_on_api_error:
                raise SystemExit(err)
            else:
                Logger.error(err)
                return {}
        else:
            if self.die_on_api_error:
                raise SystemExit(f"{reason}: {self._api_url}")
            else:
                Logger.info(f"{reason}: {self._api_url}")
                return {}
Beispiel #13
0
    def getTakerFee(self, market: str = "") -> float:
        """Retrieves the taker fee"""

        if len(market) != None:
            fees = self.getFees(market)
        else:
            fees = self.getFees()

        if len(fees) == 0 or "taker_fee_rate" not in fees:
            Logger.error(
                f"error: 'taker_fee_rate' not in fees (using {DEFAULT_TAKER_FEE_RATE} as a fallback)"
            )
            return DEFAULT_TAKER_FEE_RATE

        return float(fees["taker_fee_rate"].to_string(index=False).strip())
Beispiel #14
0
    def handle_api_error(self, err: str, reason: str) -> pd.DataFrame:
        """Handle API errors"""

        if self.debug:
            if self.die_on_api_error:
                raise SystemExit(err)
            else:
                Logger.error(err)
                return pd.DataFrame()
        else:
            if self.die_on_api_error:
                raise SystemExit(f"{reason}: {self._api_url}")
            else:
                Logger.info(f"{reason}: {self._api_url}")
                return pd.DataFrame()
Beispiel #15
0
    def authAPI(self, method: str, uri: str, payload: str = '') -> dict:
        if not isinstance(method, str):
            raise TypeError('Method is not a string.')

        if not method in ['GET', 'POST']:
            raise TypeError('Method not GET or POST.')

        if not isinstance(uri, str):
            raise TypeError('URI is not a string.')

        try:
            if method == 'GET':
                resp = requests.get(self._api_url + uri)
            elif method == 'POST':
                resp = requests.post(self._api_url + uri, json=payload)

            if resp.status_code != 200:
                resp_message = resp.json()['message']
                message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message}"
                if self.die_on_api_error:
                    raise Exception(message)
                else:
                    Logger.error(f"Error: {message}")
                    return {}

            resp.raise_for_status()
            return resp.json()

        except requests.ConnectionError as err:
            return self.handle_api_error(err, "ConnectionError")

        except requests.exceptions.HTTPError as err:
            return self.handle_api_error(err, "HTTPError")

        except requests.Timeout as err:
            return self.handle_api_error(err, "Timeout")

        except json.decoder.JSONDecodeError as err:
            return self.handle_api_error(err, "JSONDecodeError")
Beispiel #16
0
    def authAPI(self, method: str, uri: str, payload: str = {}) -> dict:
        """Initiates a REST API call to exchange"""

        if not isinstance(method, str):
            raise TypeError("Method is not a string.")

        if not method in ["GET", "POST"]:
            raise TypeError("Method not GET or POST.")

        if not isinstance(uri, str):
            raise TypeError("URI is not a string.")

        try:
            resp = requests.get(f"{self._api_url}{uri}", params=payload)

            if resp.status_code != 200:
                resp_message = resp.json()["msg"]
                message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message}"
                if self.die_on_api_error:
                    raise Exception(message)
                else:
                    Logger.error(f"Error: {message}")
                    return {}

            resp.raise_for_status()
            return resp.json()

        except requests.ConnectionError as err:
            return self.handle_api_error(err, "ConnectionError")

        except requests.exceptions.HTTPError as err:
            return self.handle_api_error(err, "HTTPError")

        except requests.Timeout as err:
            return self.handle_api_error(err, "Timeout")

        except json.decoder.JSONDecodeError as err:
            return self.handle_api_error(err, "JSONDecodeError")
Beispiel #17
0
    def getTime(self) -> datetime:
        """Retrieves the exchange time"""

        try:
            resp = self.authAPI("GET", "time")
            if "epoch" in resp:
                epoch = int(resp["epoch"])
                return datetime.fromtimestamp(epoch)
            else:
                Logger.error(
                    "resp does not contain the epoch key for some reason!"
                )  # remove this later
                Logger.error(resp)
                return None
        except Exception as e:
            Logger.error(f"Error: {e}")
            return None
Beispiel #18
0
    def authAPI(self,
                method: str,
                uri: str,
                payload: str = "") -> pd.DataFrame:
        """Initiates a REST API call"""

        if not isinstance(method, str):
            raise TypeError("Method is not a string.")

        if not method in ["DELETE", "GET", "POST"]:
            raise TypeError("Method not DELETE, GET or POST.")

        if not isinstance(uri, str):
            raise TypeError("URI is not a string.")

        try:
            if method == "DELETE":
                resp = requests.delete(self._api_url + uri, auth=self)
            elif method == "GET":
                resp = requests.get(self._api_url + uri, auth=self)
            elif method == "POST":
                resp = requests.post(self._api_url + uri,
                                     json=payload,
                                     auth=self)

            if "msg" in resp.json():
                resp_message = resp.json()["msg"]
            elif "message" in resp.json():
                resp_message = resp.json()["message"]
            else:
                resp_message = ""

            if resp.status_code == 401 and (resp_message
                                            == "request timestamp expired"):
                message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message} (hint: check your system time is using NTP)"
                Logger.error(f"Error: {message}")
                return {}
            elif resp.status_code != 200:
                if self.die_on_api_error or resp.status_code == 401:
                    # disable traceback
                    sys.tracebacklimit = 0

                    raise Exception(
                        f"{method.upper()} ({resp.status_code}) {self._api_url}{uri} - {resp_message}"
                    )
                else:
                    Logger.error(
                        f"error: {method.upper()} ({resp.status_code}) {self._api_url}{uri} - {resp_message}"
                    )
                    return pd.DataFrame()

            resp.raise_for_status()

            if isinstance(resp.json(), list):
                df = pd.DataFrame.from_dict(resp.json())
                return df
            else:
                df = pd.DataFrame(resp.json(), index=[0])
                return df

        except requests.ConnectionError as err:
            return self.handle_api_error(err, "ConnectionError")

        except requests.exceptions.HTTPError as err:
            return self.handle_api_error(err, "HTTPError")

        except requests.Timeout as err:
            return self.handle_api_error(err, "Timeout")

        except json.decoder.JSONDecodeError as err:
            return self.handle_api_error(err, "JSONDecodeError")
Beispiel #19
0
    def authAPI(self, method: str, uri: str, payload: str = {}) -> dict:
        """Initiates a REST API call to the exchange"""

        if not isinstance(method, str):
            raise TypeError("Method is not a string.")

        if not method in ["GET", "POST"]:
            raise TypeError("Method not GET or POST.")

        if not isinstance(uri, str):
            raise TypeError("URI is not a string.")

        signed_uri = [
            "/api/v3/account",
            "/api/v3/allOrders",
            "/api/v3/order",
            "/api/v3/order/test",
            "/sapi/v1/asset/tradeFee",
        ]

        query_string = urlencode(payload, True)
        if uri in signed_uri and query_string:
            query_string = "{}&timestamp={}".format(query_string, self.getTimestamp())
        elif uri in signed_uri:
            query_string = "timestamp={}".format(self.getTimestamp())

        if uri in signed_uri:
            url = (
                self._api_url
                + uri
                + "?"
                + query_string
                + "&signature="
                + self.createHash(query_string)
            )
        else:
            url = self._api_url + uri + "?" + query_string

        params = {"url": url, "params": {}}

        try:
            resp = self._dispatch_request(method)(**params)

            if "msg" in resp.json():
                resp_message = resp.json()["msg"]
            elif "message" in resp.json():
                resp_message = resp.json()["message"]
            else:
                resp_message = ""

            if resp.status_code == 400 and (
                resp_message
                == "Timestamp for this request is outside of the recvWindow."
            ):
                message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message} (hint: increase recvWindow with --recvWindow <5000-60000>)"
                Logger.error(f"Error: {message}")
                return {}
            elif resp.status_code == 429 and (
                resp_message.startswith("Too much request weight used")
            ):
                message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message} (sleeping for 5 seconds to prevent being banned)"
                Logger.error(f"Error: {message}")
                time.sleep(5)
                return {}
            elif resp.status_code != 200:
                message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message}"
                if self.die_on_api_error:
                    raise Exception(message)
                else:
                    Logger.error(f"Error: {message}")
                    return {}

            resp.raise_for_status()
            return resp.json()

        except requests.ConnectionError as err:
            return self.handle_api_error(err, "ConnectionError")

        except requests.exceptions.HTTPError as err:
            return self.handle_api_error(err, "HTTPError")

        except requests.Timeout as err:
            return self.handle_api_error(err, "Timeout")

        except json.decoder.JSONDecodeError as err:
            return self.handle_api_error(err, "JSONDecodeError")
Beispiel #20
0
def executeJob(sc=None,
               app: PyCryptoBot = None,
               state: AppState = None,
               trading_data=pd.DataFrame()):
    """Trading bot job which runs at a scheduled interval"""

    global technical_analysis

    # connectivity check (only when running live)
    if app.isLive() and app.getTime() is None:
        Logger.warning(
            'Your connection to the exchange has gone down, will retry in 1 minute!'
        )

        # poll every 5 minute
        list(map(s.cancel, s.queue))
        s.enter(300, 1, executeJob, (sc, app, state))
        return

    # increment state.iterations
    state.iterations = state.iterations + 1

    if not app.isSimulation():
        # retrieve the app.getMarket() data
        trading_data = app.getHistoricalData(app.getMarket(),
                                             app.getGranularity())

    else:
        if len(trading_data) == 0:
            return None

    # analyse the market data
    if app.isSimulation() and len(trading_data.columns) > 8:
        df = trading_data
        # if smartswitch the get the market data using new granularity
        if app.sim_smartswitch:
            df_last = app.getInterval(df, state.iterations)
            if len(df_last.index.format()) > 0:

                current_df_index = str(df_last.index.format()[0])
                current_sim_date = f'{current_df_index} 00:00:00' if len(
                    current_df_index) == 10 else current_df_index
                dt = current_sim_date.split(' ')
                date = dt[0].split('-')
                time = dt[1].split(':')
                startDate = datetime(int(date[0]), int(date[1]), int(date[2]),
                                     int(time[0]), int(time[1]), int(time[2]))
                trading_data = app.getHistoricalData(
                    app.getMarket(), app.getGranularity(),
                    startDate.isoformat(timespec='milliseconds'),
                    datetime.now().isoformat(timespec='milliseconds'))
                trading_dataCopy = trading_data.copy()
                technical_analysis = TechnicalAnalysis(trading_dataCopy)
                technical_analysis.addAll()
                df = technical_analysis.getDataFrame()
                state.iterations = 1
            app.sim_smartswitch = False

    else:
        trading_dataCopy = trading_data.copy()
        technical_analysis = TechnicalAnalysis(trading_dataCopy)
        technical_analysis.addAll()
        df = technical_analysis.getDataFrame()

    if app.isSimulation():
        df_last = app.getInterval(df, state.iterations)
    else:
        df_last = app.getInterval(df)

    if len(df_last.index.format()) > 0:
        current_df_index = str(df_last.index.format()[0])
    else:
        current_df_index = state.last_df_index

    formatted_current_df_index = f'{current_df_index} 00:00:00' if len(
        current_df_index) == 10 else current_df_index

    current_sim_date = formatted_current_df_index

    # use actual sim mode date to check smartchswitch
    if app.getSmartSwitch() == 1 and app.getGranularity(
    ) == 3600 and app.is1hEMA1226Bull(
            current_sim_date) is True and app.is6hEMA1226Bull(
                current_sim_date) is True:
        Logger.info(
            '*** smart switch from granularity 3600 (1 hour) to 900 (15 min) ***'
        )

        if app.isSimulation():
            app.sim_smartswitch = True

        app.notifyTelegram(
            app.getMarket() +
            " smart switch from granularity 3600 (1 hour) to 900 (15 min)")

        app.setGranularity(900)
        list(map(s.cancel, s.queue))
        s.enter(5, 1, executeJob, (sc, app, state))

    # use actual sim mode date to check smartchswitch
    if app.getSmartSwitch() == 1 and app.getGranularity(
    ) == 900 and app.is1hEMA1226Bull(
            current_sim_date) is False and app.is6hEMA1226Bull(
                current_sim_date) is False:
        Logger.info(
            "*** smart switch from granularity 900 (15 min) to 3600 (1 hour) ***"
        )

        if app.isSimulation():
            app.sim_smartswitch = True

        app.notifyTelegram(
            app.getMarket() +
            " smart switch from granularity 900 (15 min) to 3600 (1 hour)")

        app.setGranularity(3600)
        list(map(s.cancel, s.queue))
        s.enter(5, 1, executeJob, (sc, app, state))

    if app.getExchange() == 'binance' and app.getGranularity() == 86400:
        if len(df) < 250:
            # data frame should have 250 rows, if not retry
            Logger.error('error: data frame length is < 250 (' + str(len(df)) +
                         ')')
            list(map(s.cancel, s.queue))
            s.enter(300, 1, executeJob, (sc, app, state))
    else:
        if len(df) < 300:
            if not app.isSimulation():
                # data frame should have 300 rows, if not retry
                Logger.error('error: data frame length is < 300 (' +
                             str(len(df)) + ')')
                list(map(s.cancel, s.queue))
                s.enter(300, 1, executeJob, (sc, app, state))

    if len(df_last) > 0:
        now = datetime.today().strftime('%Y-%m-%d %H:%M:%S')

        # last_action polling if live
        if app.isLive():
            last_action_current = state.last_action
            state.pollLastAction()
            if last_action_current != state.last_action:
                Logger.info(
                    f'last_action change detected from {last_action_current} to {state.last_action}'
                )
                app.notifyTelegram(
                    f"{app.getMarket} last_action change detected from {last_action_current} to {state.last_action}"
                )

        if not app.isSimulation():
            ticker = app.getTicker(app.getMarket())
            now = ticker[0]
            price = ticker[1]
            if price < df_last['low'].values[0] or price == 0:
                price = float(df_last['close'].values[0])
        else:
            price = float(df_last['close'].values[0])

        if price < 0.0001:
            raise Exception(
                app.getMarket() +
                ' is unsuitable for trading, quote price is less than 0.0001!')

        # technical indicators
        ema12gtema26 = bool(df_last['ema12gtema26'].values[0])
        ema12gtema26co = bool(df_last['ema12gtema26co'].values[0])
        goldencross = bool(df_last['goldencross'].values[0])
        macdgtsignal = bool(df_last['macdgtsignal'].values[0])
        macdgtsignalco = bool(df_last['macdgtsignalco'].values[0])
        ema12ltema26 = bool(df_last['ema12ltema26'].values[0])
        ema12ltema26co = bool(df_last['ema12ltema26co'].values[0])
        macdltsignal = bool(df_last['macdltsignal'].values[0])
        macdltsignalco = bool(df_last['macdltsignalco'].values[0])
        obv = float(df_last['obv'].values[0])
        obv_pc = float(df_last['obv_pc'].values[0])
        elder_ray_buy = bool(df_last['eri_buy'].values[0])
        elder_ray_sell = bool(df_last['eri_sell'].values[0])

        # if simulation, set goldencross based on actual sim date
        if app.isSimulation():
            goldencross = app.is1hSMA50200Bull(current_sim_date)

        # if simulation interations < 200 set goldencross to true
        #if app.isSimulation() and state.iterations < 200:
        #    goldencross = True

        # candlestick detection
        hammer = bool(df_last['hammer'].values[0])
        inverted_hammer = bool(df_last['inverted_hammer'].values[0])
        hanging_man = bool(df_last['hanging_man'].values[0])
        shooting_star = bool(df_last['shooting_star'].values[0])
        three_white_soldiers = bool(df_last['three_white_soldiers'].values[0])
        three_black_crows = bool(df_last['three_black_crows'].values[0])
        morning_star = bool(df_last['morning_star'].values[0])
        evening_star = bool(df_last['evening_star'].values[0])
        three_line_strike = bool(df_last['three_line_strike'].values[0])
        abandoned_baby = bool(df_last['abandoned_baby'].values[0])
        morning_doji_star = bool(df_last['morning_doji_star'].values[0])
        evening_doji_star = bool(df_last['evening_doji_star'].values[0])
        two_black_gapping = bool(df_last['two_black_gapping'].values[0])

        strategy = Strategy(app, state, df, state.iterations)
        state.action = strategy.getAction()

        immediate_action = False
        margin, profit, sell_fee = 0, 0, 0

        if state.last_buy_size > 0 and state.last_buy_price > 0 and price > 0 and state.last_action == 'BUY':
            # update last buy high
            if price > state.last_buy_high:
                state.last_buy_high = price

            if state.last_buy_high > 0:
                change_pcnt_high = ((price / state.last_buy_high) - 1) * 100
            else:
                change_pcnt_high = 0

            # buy and sell calculations
            state.last_buy_fee = round(state.last_buy_size * app.getTakerFee(),
                                       8)
            state.last_buy_filled = round(
                ((state.last_buy_size - state.last_buy_fee) /
                 state.last_buy_price), 8)

            # if not a simulation, sync with exchange orders
            if not app.isSimulation():
                exchange_last_buy = app.getLastBuy()
                if exchange_last_buy is not None:
                    if state.last_buy_size != exchange_last_buy['size']:
                        state.last_buy_size = exchange_last_buy['size']
                    if state.last_buy_filled != exchange_last_buy['filled']:
                        state.last_buy_filled = exchange_last_buy['filled']
                    if state.last_buy_price != exchange_last_buy['price']:
                        state.last_buy_price = exchange_last_buy['price']

                    if app.getExchange() == 'coinbasepro':
                        if state.last_buy_fee != exchange_last_buy['fee']:
                            state.last_buy_fee = exchange_last_buy['fee']

            margin, profit, sell_fee = calculate_margin(
                buy_size=state.last_buy_size,
                buy_filled=state.last_buy_filled,
                buy_price=state.last_buy_price,
                buy_fee=state.last_buy_fee,
                sell_percent=app.getSellPercent(),
                sell_price=price,
                sell_taker_fee=app.getTakerFee())

            # handle immedate sell actions
            if strategy.isSellTrigger(price,
                                      technical_analysis.getTradeExit(price),
                                      margin, change_pcnt_high, obv_pc,
                                      macdltsignal):
                state.action = 'SELL'
                state.last_action = 'BUY'
                immediate_action = True

            # handle overriding wait actions (do not sell if sell at loss disabled!)
            if strategy.isWaitTrigger(margin):
                state.action = 'WAIT'
                state.last_action = 'BUY'
                immediate_action = False

        bullbeartext = ''
        if app.disableBullOnly() is True or (df_last['sma50'].values[0]
                                             == df_last['sma200'].values[0]):
            bullbeartext = ''
        elif goldencross is True:
            bullbeartext = ' (BULL)'
        elif goldencross is False:
            bullbeartext = ' (BEAR)'

        # polling is every 5 minutes (even for hourly intervals), but only process once per interval
        if (immediate_action is True
                or state.last_df_index != current_df_index):
            precision = 4

            if (price < 0.01):
                precision = 8

            # Since precision does not change after this point, it is safe to prepare a tailored `truncate()` that would
            # work with this precision. It should save a couple of `precision` uses, one for each `truncate()` call.
            truncate = functools.partial(_truncate, n=precision)

            price_text = 'Close: ' + truncate(price)
            ema_text = ''
            if app.disableBuyEMA() is False:
                ema_text = app.compare(df_last['ema12'].values[0],
                                       df_last['ema26'].values[0], 'EMA12/26',
                                       precision)

            macd_text = ''
            if app.disableBuyMACD() is False:
                macd_text = app.compare(df_last['macd'].values[0],
                                        df_last['signal'].values[0], 'MACD',
                                        precision)

            obv_text = ''
            if app.disableBuyOBV() is False:
                obv_text = 'OBV: ' + truncate(
                    df_last['obv'].values[0]) + ' (' + str(
                        truncate(df_last['obv_pc'].values[0])) + '%)'

            state.eri_text = ''
            if app.disableBuyElderRay() is False:
                if elder_ray_buy is True:
                    state.eri_text = 'ERI: buy | '
                elif elder_ray_sell is True:
                    state.eri_text = 'ERI: sell | '
                else:
                    state.eri_text = 'ERI: | '

            if hammer is True:
                log_text = '* Candlestick Detected: Hammer ("Weak - Reversal - Bullish Signal - Up")'
                Logger.info(log_text)

            if shooting_star is True:
                log_text = '* Candlestick Detected: Shooting Star ("Weak - Reversal - Bearish Pattern - Down")'
                Logger.info(log_text)

            if hanging_man is True:
                log_text = '* Candlestick Detected: Hanging Man ("Weak - Continuation - Bearish Pattern - Down")'
                Logger.info(log_text)

            if inverted_hammer is True:
                log_text = '* Candlestick Detected: Inverted Hammer ("Weak - Continuation - Bullish Pattern - Up")'
                Logger.info(log_text)

            if three_white_soldiers is True:
                log_text = '*** Candlestick Detected: Three White Soldiers ("Strong - Reversal - Bullish Pattern - Up")'
                Logger.info(log_text)

                app.notifyTelegram(app.getMarket() + ' (' +
                                   app.printGranularity() + ') ' + log_text)

            if three_black_crows is True:
                log_text = '* Candlestick Detected: Three Black Crows ("Strong - Reversal - Bearish Pattern - Down")'
                Logger.info(log_text)

                app.notifyTelegram(app.getMarket() + ' (' +
                                   app.printGranularity() + ') ' + log_text)

            if morning_star is True:
                log_text = '*** Candlestick Detected: Morning Star ("Strong - Reversal - Bullish Pattern - Up")'
                Logger.info(log_text)

                app.notifyTelegram(app.getMarket() + ' (' +
                                   app.printGranularity() + ') ' + log_text)

            if evening_star is True:
                log_text = '*** Candlestick Detected: Evening Star ("Strong - Reversal - Bearish Pattern - Down")'
                Logger.info(log_text)

                app.notifyTelegram(app.getMarket() + ' (' +
                                   app.printGranularity() + ') ' + log_text)

            if three_line_strike is True:
                log_text = '** Candlestick Detected: Three Line Strike ("Reliable - Reversal - Bullish Pattern - Up")'
                Logger.info(log_text)

                app.notifyTelegram(app.getMarket() + ' (' +
                                   app.printGranularity() + ') ' + log_text)

            if abandoned_baby is True:
                log_text = '** Candlestick Detected: Abandoned Baby ("Reliable - Reversal - Bullish Pattern - Up")'
                Logger.info(log_text)

                app.notifyTelegram(app.getMarket() + ' (' +
                                   app.printGranularity() + ') ' + log_text)

            if morning_doji_star is True:
                log_text = '** Candlestick Detected: Morning Doji Star ("Reliable - Reversal - Bullish Pattern - Up")'
                Logger.info(log_text)

                app.notifyTelegram(app.getMarket() + ' (' +
                                   app.printGranularity() + ') ' + log_text)

            if evening_doji_star is True:
                log_text = '** Candlestick Detected: Evening Doji Star ("Reliable - Reversal - Bearish Pattern - Down")'
                Logger.info(log_text)

                app.notifyTelegram(app.getMarket() + ' (' +
                                   app.printGranularity() + ') ' + log_text)

            if two_black_gapping is True:
                log_text = '*** Candlestick Detected: Two Black Gapping ("Reliable - Reversal - Bearish Pattern - Down")'
                Logger.info(log_text)

                app.notifyTelegram(app.getMarket() + ' (' +
                                   app.printGranularity() + ') ' + log_text)

            ema_co_prefix = ''
            ema_co_suffix = ''
            if app.disableBuyEMA() is False:
                if ema12gtema26co is True:
                    ema_co_prefix = '*^ '
                    ema_co_suffix = ' ^*'
                elif ema12ltema26co is True:
                    ema_co_prefix = '*v '
                    ema_co_suffix = ' v*'
                elif ema12gtema26 is True:
                    ema_co_prefix = '^ '
                    ema_co_suffix = ' ^'
                elif ema12ltema26 is True:
                    ema_co_prefix = 'v '
                    ema_co_suffix = ' v'

            macd_co_prefix = ''
            macd_co_suffix = ''
            if app.disableBuyMACD() is False:
                if macdgtsignalco is True:
                    macd_co_prefix = '*^ '
                    macd_co_suffix = ' ^*'
                elif macdltsignalco is True:
                    macd_co_prefix = '*v '
                    macd_co_suffix = ' v*'
                elif macdgtsignal is True:
                    macd_co_prefix = '^ '
                    macd_co_suffix = ' ^'
                elif macdltsignal is True:
                    macd_co_prefix = 'v '
                    macd_co_suffix = ' v'

            obv_prefix = ''
            obv_suffix = ''
            if app.disableBuyOBV() is False:
                if float(obv_pc) > 0:
                    obv_prefix = '^ '
                    obv_suffix = ' ^ | '
                elif float(obv_pc) < 0:
                    obv_prefix = 'v '
                    obv_suffix = ' v | '

            if not app.isVerbose():
                if state.last_action != '':
                    output_text = formatted_current_df_index + ' | ' + app.getMarket() + bullbeartext + ' | ' + \
                                  app.printGranularity() + ' | ' + price_text + ' | ' + ema_co_prefix + \
                                  ema_text + ema_co_suffix + ' | ' + macd_co_prefix + macd_text + macd_co_suffix + \
                                  obv_prefix + obv_text + obv_suffix + state.eri_text + ' | ' + state.action + \
                                  ' | Last Action: ' + state.last_action
                else:
                    output_text = formatted_current_df_index + ' | ' + app.getMarket() + bullbeartext + ' | ' + \
                                  app.printGranularity() + ' | ' + price_text + ' | ' + ema_co_prefix + \
                                  ema_text + ema_co_suffix + ' | ' + macd_co_prefix + macd_text + macd_co_suffix + \
                                  obv_prefix + obv_text + obv_suffix + state.eri_text + ' | ' + state.action + ' '

                if state.last_action == 'BUY':
                    if state.last_buy_size > 0:
                        margin_text = truncate(margin) + '%'
                    else:
                        margin_text = '0%'

                    output_text += ' | ' + margin_text + ' (delta: ' + str(
                        round(price - state.last_buy_price, precision)) + ')'

                Logger.info(output_text)

                # Seasonal Autoregressive Integrated Moving Average (ARIMA) model (ML prediction for 3 intervals from now)
                if not app.isSimulation():
                    try:
                        prediction = technical_analysis.seasonalARIMAModelPrediction(
                            int(app.getGranularity() / 60) *
                            3)  # 3 intervals from now
                        Logger.info(
                            f'Seasonal ARIMA model predicts the closing price will be {str(round(prediction[1], 2))} at {prediction[0]} (delta: {round(prediction[1] - price, 2)})'
                        )
                    except:
                        pass

                if state.last_action == 'BUY':
                    # display support, resistance and fibonacci levels
                    Logger.info(
                        technical_analysis.
                        printSupportResistanceFibonacciLevels(price))

            else:
                Logger.debug('-- Iteration: ' + str(state.iterations) + ' --' +
                             bullbeartext)

                if state.last_action == 'BUY':
                    if state.last_buy_size > 0:
                        margin_text = truncate(margin) + '%'
                    else:
                        margin_text = '0%'

                    Logger.debug('-- Margin: ' + margin_text + ' --')

                Logger.debug('price: ' + truncate(price))
                Logger.debug('ema12: ' +
                             truncate(float(df_last['ema12'].values[0])))
                Logger.debug('ema26: ' +
                             truncate(float(df_last['ema26'].values[0])))
                Logger.debug('ema12gtema26co: ' + str(ema12gtema26co))
                Logger.debug('ema12gtema26: ' + str(ema12gtema26))
                Logger.debug('ema12ltema26co: ' + str(ema12ltema26co))
                Logger.debug('ema12ltema26: ' + str(ema12ltema26))
                Logger.debug('sma50: ' +
                             truncate(float(df_last['sma50'].values[0])))
                Logger.debug('sma200: ' +
                             truncate(float(df_last['sma200'].values[0])))
                Logger.debug('macd: ' +
                             truncate(float(df_last['macd'].values[0])))
                Logger.debug('signal: ' +
                             truncate(float(df_last['signal'].values[0])))
                Logger.debug('macdgtsignal: ' + str(macdgtsignal))
                Logger.debug('macdltsignal: ' + str(macdltsignal))
                Logger.debug('obv: ' + str(obv))
                Logger.debug('obv_pc: ' + str(obv_pc))
                Logger.debug('action: ' + state.action)

                # informational output on the most recent entry
                Logger.info('')
                Logger.info(
                    '================================================================================'
                )
                txt = '        Iteration : ' + str(
                    state.iterations) + bullbeartext
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                txt = '        Timestamp : ' + str(df_last.index.format()[0])
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                Logger.info(
                    '--------------------------------------------------------------------------------'
                )
                txt = '            Close : ' + truncate(price)
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                txt = '            EMA12 : ' + truncate(
                    float(df_last['ema12'].values[0]))
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                txt = '            EMA26 : ' + truncate(
                    float(df_last['ema26'].values[0]))
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                txt = '   Crossing Above : ' + str(ema12gtema26co)
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                txt = '  Currently Above : ' + str(ema12gtema26)
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                txt = '   Crossing Below : ' + str(ema12ltema26co)
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                txt = '  Currently Below : ' + str(ema12ltema26)
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')

                if (ema12gtema26 is True and ema12gtema26co is True):
                    txt = '        Condition : EMA12 is currently crossing above EMA26'
                elif (ema12gtema26 is True and ema12gtema26co is False):
                    txt = '        Condition : EMA12 is currently above EMA26 and has crossed over'
                elif (ema12ltema26 is True and ema12ltema26co is True):
                    txt = '        Condition : EMA12 is currently crossing below EMA26'
                elif (ema12ltema26 is True and ema12ltema26co is False):
                    txt = '        Condition : EMA12 is currently below EMA26 and has crossed over'
                else:
                    txt = '        Condition : -'
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')

                txt = '            SMA20 : ' + truncate(
                    float(df_last['sma20'].values[0]))
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                txt = '           SMA200 : ' + truncate(
                    float(df_last['sma200'].values[0]))
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')

                Logger.info(
                    '--------------------------------------------------------------------------------'
                )
                txt = '             MACD : ' + truncate(
                    float(df_last['macd'].values[0]))
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                txt = '           Signal : ' + truncate(
                    float(df_last['signal'].values[0]))
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                txt = '  Currently Above : ' + str(macdgtsignal)
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                txt = '  Currently Below : ' + str(macdltsignal)
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')

                if (macdgtsignal is True and macdgtsignalco is True):
                    txt = '        Condition : MACD is currently crossing above Signal'
                elif (macdgtsignal is True and macdgtsignalco is False):
                    txt = '        Condition : MACD is currently above Signal and has crossed over'
                elif (macdltsignal is True and macdltsignalco is True):
                    txt = '        Condition : MACD is currently crossing below Signal'
                elif (macdltsignal is True and macdltsignalco is False):
                    txt = '        Condition : MACD is currently below Signal and has crossed over'
                else:
                    txt = '        Condition : -'
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')

                Logger.info(
                    '--------------------------------------------------------------------------------'
                )
                txt = '           Action : ' + state.action
                Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                Logger.info(
                    '================================================================================'
                )
                if state.last_action == 'BUY':
                    txt = '           Margin : ' + margin_text
                    Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ')
                    Logger.info(
                        '================================================================================'
                    )

            # if a buy signal
            if state.action == 'BUY':
                state.last_buy_price = price
                state.last_buy_high = state.last_buy_price

                # if live
                if app.isLive():
                    app.notifyTelegram(app.getMarket() + ' (' +
                                       app.printGranularity() + ') BUY at ' +
                                       price_text)

                    if not app.isVerbose():
                        Logger.info(formatted_current_df_index + ' | ' +
                                    app.getMarket() + ' | ' +
                                    app.printGranularity() + ' | ' +
                                    price_text + ' | BUY')
                    else:
                        Logger.info(
                            '--------------------------------------------------------------------------------'
                        )
                        Logger.info(
                            '|                      *** Executing LIVE Buy Order ***                        |'
                        )
                        Logger.info(
                            '--------------------------------------------------------------------------------'
                        )

                    # display balances
                    Logger.info(app.getBaseCurrency() +
                                ' balance before order: ' +
                                str(account.getBalance(app.getBaseCurrency())))
                    Logger.info(
                        app.getQuoteCurrency() + ' balance before order: ' +
                        str(account.getBalance(app.getQuoteCurrency())))

                    # execute a live market buy
                    state.last_buy_size = float(
                        account.getBalance(app.getQuoteCurrency()))
                    if app.getBuyMaxSize(
                    ) and state.last_buy_size > app.getBuyMaxSize():
                        state.last_buy_size = app.getBuyMaxSize()

                    resp = app.marketBuy(app.getMarket(), state.last_buy_size,
                                         app.getBuyPercent())
                    Logger.debug(resp)

                    # display balances
                    Logger.info(app.getBaseCurrency() +
                                ' balance after order: ' +
                                str(account.getBalance(app.getBaseCurrency())))
                    Logger.info(
                        app.getQuoteCurrency() + ' balance after order: ' +
                        str(account.getBalance(app.getQuoteCurrency())))
                # if not live
                else:
                    app.notifyTelegram(app.getMarket() + ' (' +
                                       app.printGranularity() +
                                       ') TEST BUY at ' + price_text)
                    # TODO: Improve simulator calculations by including calculations for buy and sell limit configurations.
                    if state.last_buy_size == 0 and state.last_buy_filled == 0:
                        state.last_buy_size = 1000
                        state.first_buy_size = 1000

                    state.buy_count = state.buy_count + 1
                    state.buy_sum = state.buy_sum + state.last_buy_size

                    if not app.isVerbose():
                        Logger.info(formatted_current_df_index + ' | ' +
                                    app.getMarket() + ' | ' +
                                    app.printGranularity() + ' | ' +
                                    price_text + ' | BUY')

                        bands = technical_analysis.getFibonacciRetracementLevels(
                            float(price))
                        Logger.info(' Fibonacci Retracement Levels:' +
                                    str(bands))
                        technical_analysis.printSupportResistanceLevel(
                            float(price))

                        if len(bands) >= 1 and len(bands) <= 2:
                            if len(bands) == 1:
                                first_key = list(bands.keys())[0]
                                if first_key == 'ratio1':
                                    state.fib_low = 0
                                    state.fib_high = bands[first_key]
                                if first_key == 'ratio1_618':
                                    state.fib_low = bands[first_key]
                                    state.fib_high = bands[first_key] * 2
                                else:
                                    state.fib_low = bands[first_key]

                            elif len(bands) == 2:
                                first_key = list(bands.keys())[0]
                                second_key = list(bands.keys())[1]
                                state.fib_low = bands[first_key]
                                state.fib_high = bands[second_key]

                    else:
                        Logger.info(
                            '--------------------------------------------------------------------------------'
                        )
                        Logger.info(
                            '|                      *** Executing TEST Buy Order ***                        |'
                        )
                        Logger.info(
                            '--------------------------------------------------------------------------------'
                        )

                if app.shouldSaveGraphs():
                    tradinggraphs = TradingGraphs(technical_analysis)
                    ts = datetime.now().timestamp()
                    filename = app.getMarket() + '_' + app.printGranularity(
                    ) + '_buy_' + str(ts) + '.png'
                    tradinggraphs.renderEMAandMACD(len(trading_data),
                                                   'graphs/' + filename, True)

            # if a sell signal
            elif state.action == 'SELL':
                # if live
                if app.isLive():
                    app.notifyTelegram(
                        app.getMarket() + ' (' + app.printGranularity() +
                        ') SELL at ' + price_text + ' (margin: ' +
                        margin_text + ', (delta: ' +
                        str(round(price - state.last_buy_price, precision)) +
                        ')')

                    if not app.isVerbose():
                        Logger.info(formatted_current_df_index + ' | ' +
                                    app.getMarket() + ' | ' +
                                    app.printGranularity() + ' | ' +
                                    price_text + ' | SELL')

                        bands = technical_analysis.getFibonacciRetracementLevels(
                            float(price))
                        Logger.info(' Fibonacci Retracement Levels:' +
                                    str(bands))

                        if len(bands) >= 1 and len(bands) <= 2:
                            if len(bands) == 1:
                                first_key = list(bands.keys())[0]
                                if first_key == 'ratio1':
                                    state.fib_low = 0
                                    state.fib_high = bands[first_key]
                                if first_key == 'ratio1_618':
                                    state.fib_low = bands[first_key]
                                    state.fib_high = bands[first_key] * 2
                                else:
                                    state.fib_low = bands[first_key]

                            elif len(bands) == 2:
                                first_key = list(bands.keys())[0]
                                second_key = list(bands.keys())[1]
                                state.fib_low = bands[first_key]
                                state.fib_high = bands[second_key]

                    else:
                        Logger.info(
                            '--------------------------------------------------------------------------------'
                        )
                        Logger.info(
                            '|                      *** Executing LIVE Sell Order ***                        |'
                        )
                        Logger.info(
                            '--------------------------------------------------------------------------------'
                        )

                    # display balances
                    Logger.info(app.getBaseCurrency() +
                                ' balance before order: ' +
                                str(account.getBalance(app.getBaseCurrency())))
                    Logger.info(
                        app.getQuoteCurrency() + ' balance before order: ' +
                        str(account.getBalance(app.getQuoteCurrency())))

                    # execute a live market sell
                    resp = app.marketSell(
                        app.getMarket(),
                        float(account.getBalance(app.getBaseCurrency())),
                        app.getSellPercent())
                    Logger.debug(resp)

                    # display balances
                    Logger.info(app.getBaseCurrency() +
                                ' balance after order: ' +
                                str(account.getBalance(app.getBaseCurrency())))
                    Logger.info(
                        app.getQuoteCurrency() + ' balance after order: ' +
                        str(account.getBalance(app.getQuoteCurrency())))

                # if not live
                else:
                    margin, profit, sell_fee = calculate_margin(
                        buy_size=state.last_buy_size,
                        buy_filled=state.last_buy_filled,
                        buy_price=state.last_buy_price,
                        buy_fee=state.last_buy_fee,
                        sell_percent=app.getSellPercent(),
                        sell_price=price,
                        sell_taker_fee=app.getTakerFee())

                    if state.last_buy_size > 0:
                        margin_text = truncate(margin) + '%'
                    else:
                        margin_text = '0%'
                    app.notifyTelegram(
                        app.getMarket() + ' (' + app.printGranularity() +
                        ') TEST SELL at ' + price_text + ' (margin: ' +
                        margin_text + ', (delta: ' +
                        str(round(price - state.last_buy_price, precision)) +
                        ')')

                    # Preserve next buy values for simulator
                    state.sell_count = state.sell_count + 1
                    buy_size = ((app.getSellPercent() / 100) *
                                ((price / state.last_buy_price) *
                                 (state.last_buy_size - state.last_buy_fee)))
                    state.last_buy_size = buy_size - sell_fee
                    state.sell_sum = state.sell_sum + state.last_buy_size

                    if not app.isVerbose():
                        if price > 0:
                            margin_text = truncate(margin) + '%'
                        else:
                            margin_text = '0%'

                        Logger.info(formatted_current_df_index + ' | ' +
                                    app.getMarket() + ' | ' +
                                    app.printGranularity() + ' | SELL | ' +
                                    str(price) + ' | BUY | ' +
                                    str(state.last_buy_price) + ' | DIFF | ' +
                                    str(price - state.last_buy_price) +
                                    ' | DIFF | ' + str(profit) +
                                    ' | MARGIN NO FEES | ' + margin_text +
                                    ' | MARGIN FEES | ' +
                                    str(round(sell_fee, precision)))

                    else:
                        Logger.info(
                            '--------------------------------------------------------------------------------'
                        )
                        Logger.info(
                            '|                      *** Executing TEST Sell Order ***                        |'
                        )
                        Logger.info(
                            '--------------------------------------------------------------------------------'
                        )

                if app.shouldSaveGraphs():
                    tradinggraphs = TradingGraphs(technical_analysis)
                    ts = datetime.now().timestamp()
                    filename = app.getMarket() + '_' + app.printGranularity(
                    ) + '_sell_' + str(ts) + '.png'
                    tradinggraphs.renderEMAandMACD(len(trading_data),
                                                   'graphs/' + filename, True)

            # last significant action
            if state.action in ['BUY', 'SELL']:
                state.last_action = state.action

            state.last_df_index = str(df_last.index.format()[0])

            if not app.isLive() and state.iterations == len(df):
                Logger.info("\nSimulation Summary: ")

                if state.buy_count > state.sell_count and app.allowSellAtLoss(
                ):
                    # Calculate last sell size
                    state.last_buy_size = ((app.getSellPercent() / 100) * (
                        (price / state.last_buy_price) *
                        (state.last_buy_size - state.last_buy_fee)))
                    # Reduce sell fee from last sell size
                    state.last_buy_size = state.last_buy_size - state.last_buy_price * app.getTakerFee(
                    )
                    state.sell_sum = state.sell_sum + state.last_buy_size
                    state.sell_count = state.sell_count + 1

                elif state.buy_count > state.sell_count and not app.allowSellAtLoss(
                ):
                    Logger.info("\n")
                    Logger.info(
                        '        Note : "sell at loss" is disabled and you have an open trade, if the margin'
                    )
                    Logger.info(
                        '               result below is negative it will assume you sold at the end of the'
                    )
                    Logger.info(
                        '               simulation which may not be ideal. Try setting --sellatloss 1'
                    )

                Logger.info("\n")
                Logger.info('   Buy Count : ' + str(state.buy_count))
                Logger.info('  Sell Count : ' + str(state.sell_count))
                Logger.info('   First Buy : ' + str(state.first_buy_size))
                Logger.info('   Last Sell : ' + str(state.last_buy_size))

                app.notifyTelegram(
                    f"Simulation Summary\n   Buy Count: {state.buy_count}\n   Sell Count: {state.sell_count}\n   First Buy: {state.first_buy_size}\n   Last Sell: {state.last_buy_size}\n"
                )

                if state.sell_count > 0:
                    Logger.info("\n")
                    Logger.info('      Margin : ' + _truncate((
                        ((state.last_buy_size - state.first_buy_size) /
                         state.first_buy_size) * 100), 4) + '%')
                    Logger.info("\n")
                    Logger.info(
                        '  ** non-live simulation, assuming highest fees')
                    app.notifyTelegram(
                        f"      Margin: {_truncate((((state.last_buy_size - state.first_buy_size) / state.first_buy_size) * 100), 4)}%\n  ** non-live simulation, assuming highest fees\n"
                    )

        else:
            if state.last_buy_size > 0 and state.last_buy_price > 0 and price > 0 and state.last_action == 'BUY':
                # show profit and margin if already bought
                Logger.info(now + ' | ' + app.getMarket() + bullbeartext +
                            ' | ' + app.printGranularity() +
                            ' | Current Price: ' + str(price) + ' | Margin: ' +
                            str(margin) + ' | Profit: ' + str(profit))
            else:
                Logger.info(now + ' | ' + app.getMarket() + bullbeartext +
                            ' | ' + app.printGranularity() +
                            ' | Current Price: ' + str(price))

            # decrement ignored iteration
            state.iterations = state.iterations - 1

        # if live
        if not app.disableTracker() and app.isLive():
            # update order tracker csv
            if app.getExchange() == 'binance':
                account.saveTrackerCSV(app.getMarket())
            elif app.getExchange() == 'coinbasepro':
                account.saveTrackerCSV()

        if app.isSimulation():
            if state.iterations < 300:
                if app.simuluationSpeed() in ['fast', 'fast-sample']:
                    # fast processing
                    list(map(s.cancel, s.queue))
                    s.enter(0, 1, executeJob, (sc, app, state, df))
                else:
                    # slow processing
                    list(map(s.cancel, s.queue))
                    s.enter(1, 1, executeJob, (sc, app, state, df))

        else:
            # poll every 1 minute
            list(map(s.cancel, s.queue))
            s.enter(60, 1, executeJob, (sc, app, state))
Beispiel #21
0
    def authAPI(self,
                method: str,
                uri: str,
                payload: str = "") -> pd.DataFrame:
        """Initiates a REST API call"""

        if not isinstance(method, str):
            raise TypeError("Method is not a string.")

        if not method in ["DELETE", "GET", "POST"]:
            raise TypeError("Method not DELETE, GET or POST.")

        if not isinstance(uri, str):
            raise TypeError("URI is not a string.")

        try:
            if method == "DELETE":
                resp = requests.delete(self._api_url + uri, auth=self)
            elif method == "GET":
                # resp = requests.request('GET', self._api_url + uri, headers=headers)
                resp = requests.get(self._api_url + uri, auth=self)
            elif method == "POST":
                resp = requests.post(self._api_url + uri,
                                     json=payload,
                                     auth=self)

            # Logger.debug(resp.json())
            if resp.status_code != 200:
                if self.die_on_api_error or resp.status_code == 401:
                    # disable traceback
                    sys.tracebacklimit = 0

                    raise Exception(method.upper() + " (" +
                                    "{}".format(resp.status_code) + ") " +
                                    self._api_url + uri + " - " +
                                    "{}".format(resp.json()["msg"]))
                else:
                    Logger.error("error: " + method.upper() + " (" +
                                 "{}".format(resp.status_code) + ") " +
                                 self._api_url + uri + " - " +
                                 "{}".format(resp.json()["msg"]))
                    return pd.DataFrame()

            resp.raise_for_status()

            mjson = resp.json()
            if isinstance(mjson, list):
                df = pd.DataFrame.from_dict(mjson)

            if "data" in mjson:
                mjson = mjson["data"]
            if "items" in mjson:
                if isinstance(mjson["items"], list):
                    df = pd.DataFrame.from_dict(mjson["items"])
                else:
                    df = pd.DataFrame(mjson["items"], index=[0])
            elif "data" in mjson:
                if isinstance(mjson, list):
                    df = pd.DataFrame.from_dict(mjson)
                else:
                    df = pd.DataFrame(mjson, index=[0])
            else:
                if isinstance(mjson, list):
                    df = pd.DataFrame.from_dict(mjson)
                else:
                    df = pd.DataFrame(mjson, index=[0])

            return df

        except requests.ConnectionError as err:
            return self.handle_api_error(err, "ConnectionError")

        except requests.exceptions.HTTPError as err:
            return self.handle_api_error(err, "HTTPError")

        except requests.Timeout as err:
            return self.handle_api_error(err, "Timeout")

        except json.decoder.JSONDecodeError as err:
            return self.handle_api_error(err, "JSONDecodeError")

        except Exception as err:
            return self.handle_api_error(err, "Exception")