Beispiel #1
0
    settings = get_settings()

    Messenger = Messenger(secrets, settings)
    Trader = Trader(secrets, settings)

    Trader.initialise()

    while True:
        try:
            Trader.analyse_pauses()
            Trader.analyse_buys()
            Trader.analyse_sells()
            time.sleep(10)

        except SSLError as exception:
            Messenger.print_error("SSL")
            logger.exception(exception)
            time.sleep(10)
        except ConnectionError as exception:
            Messenger.print_error("connection")
            logger.exception(exception)
            time.sleep(10)
        except json.decoder.JSONDecodeError as exception:
            Messenger.print_error("JSONDecode")
            logger.exception(exception)
            time.sleep(10)
        except TypeError as exception:
            Messenger.print_error("typeError")
            logger.exception(exception)
            time.sleep(10)
        except KeyError as exception:
Beispiel #2
0
class Trader(object):
    """
    Used for handling all trade functionality
    """
    def __init__(self, secrets, settings):
        self.trade_params = settings["tradeParameters"]
        self.pause_params = settings["pauseParameters"]

        self.Bittrex = Bittrex(secrets)
        self.Messenger = Messenger(secrets, settings)
        self.Database = Database()

    def initialise(self):
        """
        Fetch the initial coin pairs to track and to print the header line
        """
        try:
            if len(self.Database.app_data["coinPairs"]) < 1:
                self.Database.store_coin_pairs(self.get_markets("BTC"))
            self.Messenger.print_header(
                len(self.Database.app_data["coinPairs"]))
        except ConnectionError as exception:
            self.Messenger.print_error("connection", [], True)
            logger.exception(exception)
            exit()

    def analyse_pauses(self):
        """
        Checks all the paused buy and sell pairs and the balance notification timer and reactivate the necessary ones
        """
        if self.Database.check_resume(self.pause_params["buy"]["pauseTime"],
                                      "buy"):
            self.Database.store_coin_pairs(self.get_markets("BTC"))
            self.Messenger.print_resume_pause(
                len(self.Database.app_data["coinPairs"]), "buy")
        if "sell" in self.pause_params and self.Database.check_resume(
                self.pause_params["sell"]["pauseTime"], "sell"):
            self.Messenger.print_resume_pause(
                self.Database.app_data["pausedTrackedCoinPairs"], "sell")
            self.Database.resume_sells()
        if "balance" in self.pause_params and self.Database.check_resume(
                self.pause_params["balance"]["pauseTime"], "balance"):
            current_balance = self.Messenger.send_balance_slack(
                self.get_non_zero_balances(),
                self.Database.get_previous_total_balance())
            self.Database.reset_balance_notifier(current_balance)

    def analyse_buys(self):
        """
        Analyse all the un-paused coin pairs for buy signals and apply buys
        """
        trade_len = len(self.Database.trades["trackedCoinPairs"])
        pause_trade_len = len(self.Database.app_data["pausedTrackedCoinPairs"])
        if (trade_len < 1 or pause_trade_len == trade_len
            ) and trade_len < self.trade_params["buy"]["maxOpenTrades"]:
            for coin_pair in self.Database.app_data["coinPairs"]:
                self.buy_strategy(coin_pair)

    def analyse_sells(self):
        """
        Analyse all the un-paused tracked coin pairs for sell signals and apply sells
        """
        for coin_pair in self.Database.trades["trackedCoinPairs"]:
            if coin_pair not in self.Database.app_data[
                    "pausedTrackedCoinPairs"]:
                self.sell_strategy(coin_pair)

    def buy_strategy(self, coin_pair):
        """
        Applies the buy checks on the coin pair and handles the results appropriately

        :param coin_pair: Coin pair market to check (ex: BTC-ETH, BTC-FCT)
        :type coin_pair: str
        """
        if (len(self.Database.trades["trackedCoinPairs"]) >=
                self.trade_params["buy"]["maxOpenTrades"]
                or coin_pair in self.Database.trades["trackedCoinPairs"]):
            return
        rsi = self.calculate_RSI(coin_pair=coin_pair,
                                 period=14,
                                 unit=self.trade_params["tickerInterval"])
        day_volume = self.get_current_24hr_volume(coin_pair)
        current_buy_price = self.get_current_price(coin_pair, "ask")

        if rsi is None:
            return

        if self.check_buy_parameters(rsi, day_volume, current_buy_price):
            buy_stats = {"rsi": rsi, "24HrVolume": day_volume}
            self.buy(coin_pair, self.trade_params["buy"]["btcAmount"],
                     current_buy_price, buy_stats)
        elif "buy" in self.pause_params and rsi >= self.pause_params["buy"][
                "rsiThreshold"] > 0:
            self.Messenger.print_pause(coin_pair, [rsi, day_volume],
                                       self.pause_params["buy"]["pauseTime"],
                                       "buy")
            self.Database.pause_buy(coin_pair)
        else:
            self.Messenger.print_no_buy(coin_pair, rsi, day_volume,
                                        current_buy_price)

    def sell_strategy(self, coin_pair):
        """
        Applies the sell checks on the coin pair and handles the results appropriately

        :param coin_pair: Coin pair market to check (ex: BTC-ETH, BTC-FCT)
        :type coin_pair: str
        """
        if (coin_pair in self.Database.app_data["pausedTrackedCoinPairs"]
                or coin_pair not in self.Database.trades["trackedCoinPairs"]):
            return
        rsi = self.calculate_RSI(coin_pair=coin_pair,
                                 period=14,
                                 unit=self.trade_params["tickerInterval"])
        current_sell_price = self.get_current_price(coin_pair, "bid")
        profit_margin = self.Database.get_profit_margin(
            coin_pair, current_sell_price)

        if rsi is None:
            return

        if self.check_sell_parameters(rsi, profit_margin):
            sell_stats = {"rsi": rsi, "profitMargin": profit_margin}
            self.sell(coin_pair, current_sell_price, sell_stats)
        elif "sell" in self.pause_params and profit_margin <= self.pause_params[
                "sell"]["profitMarginThreshold"] < 0:
            self.Messenger.print_pause(coin_pair, [profit_margin, rsi],
                                       self.pause_params["sell"]["pauseTime"],
                                       "sell")
            self.Database.pause_sell(coin_pair)
        else:
            self.Messenger.print_no_sell(coin_pair, rsi, profit_margin,
                                         current_sell_price)

    def check_buy_parameters(self, rsi, day_volume, current_buy_price):
        """
        Used to check if the buy conditions have been met

        :param rsi: The coin pair's current RSI
        :type rsi: float
        :param day_volume: The coin pair's current 24 hour volume
        :type day_volume: float
        :param current_buy_price: The coin pair's current price
        :type current_buy_price: float

        :return: Boolean indicating if the buy conditions have been met
        :rtype : bool
        """
        rsi_check = rsi <= self.trade_params["buy"]["rsiThreshold"]
        day_volume_check = day_volume >= self.trade_params["buy"][
            "24HourVolumeThreshold"]
        current_buy_price_check = current_buy_price >= self.trade_params[
            "buy"]["minimumUnitPrice"]

        return rsi_check and day_volume_check and current_buy_price_check

    def check_sell_parameters(self, rsi, profit_margin):
        """
        Used to check if the sell conditions have been met

        :param rsi: The coin pair's current RSI
        :type rsi: float
        :param profit_margin: The coin pair's current profit margin
        :type profit_margin: float

        :return: Boolean indicating if the sell conditions have been met
        :rtype : bool
        """
        rsi_check = rsi >= self.trade_params["sell"]["rsiThreshold"]
        lower_profit_check = profit_margin >= self.trade_params["sell"][
            "minProfitMarginThreshold"]
        upper_profit_check = profit_margin >= self.trade_params["sell"][
            "profitMarginThreshold"]
        loss_check = ("lossMarginThreshold" in self.trade_params["sell"]
                      and 0 > self.trade_params["sell"]["lossMarginThreshold"]
                      >= profit_margin)

        return (rsi_check and lower_profit_check) or upper_profit_check or (
            rsi_check and loss_check)

    def buy(self, coin_pair, btc_quantity, price, stats, trade_time_limit=2):
        """
        Used to place a buy order to Bittrex. Wait until the order is completed.
        If the order is not filled within trade_time_limit minutes cancel it.

        :param coin_pair: String literal for the market (ex: BTC-LTC)
        :type coin_pair: str
        :param btc_quantity: The amount of BTC to buy with
        :type btc_quantity: float
        :param price: The price at which to buy
        :type price: float
        :param stats: The buy stats object
        :type stats: dict
        :param trade_time_limit: The time in minutes to wait fot the order before cancelling it
        :type trade_time_limit: float
        """
        buy_quantity = round(btc_quantity / price, 8)
        buy_data = self.Bittrex.buy_limit(coin_pair, buy_quantity, price)
        if not buy_data["success"]:
            error_str = self.Messenger.print_error(
                "buy", [coin_pair, buy_data["message"]])
            logger.error(error_str)
            return
        self.Database.store_initial_buy(coin_pair, buy_data["result"]["uuid"])

        buy_order_data = self.get_order(buy_data["result"]["uuid"],
                                        trade_time_limit * 60)
        self.Database.store_buy(buy_order_data["result"], stats)

        self.Messenger.print_buy(coin_pair, price, stats["rsi"],
                                 stats["24HrVolume"])
        self.Messenger.send_buy_slack(coin_pair, stats["rsi"],
                                      stats["24HrVolume"])
        self.Messenger.send_buy_gmail(buy_order_data["result"], stats)
        self.Messenger.play_sw_imperial_march()

    def sell(self, coin_pair, price, stats, trade_time_limit=2):
        """
        Used to place a sell order to Bittrex. Wait until the order is completed.
        If the order is not filled within trade_time_limit minutes cancel it.

        :param coin_pair: String literal for the market (ex: BTC-LTC)
        :type coin_pair: str
        :param price: The price at which to buy
        :type price: float
        :param stats: The buy stats object
        :type stats: dict
        :param trade_time_limit: The time in minutes to wait fot the order before cancelling it
        :type trade_time_limit: float
        """
        trade = self.Database.get_open_trade(coin_pair)
        sell_data = self.Bittrex.sell_limit(coin_pair, trade["quantity"],
                                            price)
        if not sell_data["success"]:
            error_str = self.Messenger.print_error(
                "sell", [coin_pair, sell_data["message"]])
            logger.error(error_str)
            return

        sell_order_data = self.get_order(sell_data["result"]["uuid"],
                                         trade_time_limit * 60)
        # TODO: Handle partial/incomplete sales.
        self.Database.store_sell(sell_order_data["result"], stats)

        self.Messenger.print_sell(coin_pair, price, stats["rsi"],
                                  stats["profitMargin"])
        self.Messenger.send_sell_slack(coin_pair, stats["rsi"],
                                       stats["profitMargin"])
        self.Messenger.send_sell_gmail(sell_order_data["result"], stats)
        self.Messenger.play_sw_theme()

    def get_markets(self, main_market_filter=None):
        """
        Gets all the Bittrex markets and filters them based on the main market filter

        :param main_market_filter: Main market to filter on (ex: BTC, ETH, USDT)
        :type main_market_filter: str

        :return: All Bittrex markets (with filter applied, if any)
        :rtype : list
        """
        markets = self.Bittrex.get_markets()
        if not markets["success"]:
            error_str = self.Messenger.print_error("market", [], True)
            logger.error(error_str)
            exit()

        markets = markets["result"]
        if main_market_filter is not None:
            market_check = main_market_filter + "-"
            markets = py_.filter_(
                markets, lambda market: market_check in market["MarketName"])
        markets = py_.map_(markets, lambda market: market["MarketName"])
        return markets

    def get_current_price(self, coin_pair, price_type):
        """
        Gets current market price for a coin pair

        :param coin_pair: Coin pair market to check (ex: BTC-ETH, BTC-FCT)
        :type coin_pair: str
        :param price_type: The type of price to get (one of: 'ask', 'bid')
        :type price_type: str

        :return: Coin pair's current market price
        :rtype : float
        """
        coin_summary = self.Bittrex.get_market_summary(coin_pair)
        if not coin_summary["success"]:
            error_str = self.Messenger.print_error("coinMarket", [coin_pair])
            logger.error(error_str)
            return None
        if price_type == "ask":
            return coin_summary["result"][0]["Ask"]
        if price_type == "bid":
            return coin_summary["result"][0]["Bid"]
        return coin_summary["result"][0]["Last"]

    def get_current_24hr_volume(self, coin_pair):
        """
        Gets current 24 hour market volume for a coin pair

        :param coin_pair: Coin pair market to check (ex: BTC-ETH, BTC-FCT)
        :type coin_pair: str

        :return: Coin pair's current 24 hour market volume
        :rtype : float
        """
        coin_summary = self.Bittrex.get_market_summary(coin_pair)
        if not coin_summary["success"]:
            error_str = self.Messenger.print_error("coinMarket", [coin_pair])
            logger.error(error_str)
            return None
        return coin_summary["result"][0]["BaseVolume"]

    def get_closing_prices(self, coin_pair, period, unit):
        """
        Returns closing prices within a specified time frame for a coin pair

        :param coin_pair: String literal for the market (ex: BTC-LTC)
        :type coin_pair: str
        :param period: Number of periods to query
        :type period: int
        :param unit: Ticker interval (one of: 'oneMin', 'fiveMin', 'thirtyMin', 'hour', 'week', 'day', and 'month')
        :type unit: str

        :return: Array of closing prices
        :rtype : list
        """
        historical_data = self.Bittrex.get_historical_data(
            coin_pair, period, unit)
        closing_prices = []
        for i in historical_data:
            closing_prices.append(i["C"])
        return closing_prices

    def get_order(self, order_uuid, trade_time_limit):
        """
        Used to get an order from Bittrex by it's UUID.
        First wait until the order is completed before retrieving it.
        If the order is not completed within trade_time_limit seconds, cancel it.

        :param order_uuid: The order's UUID
        :type order_uuid: str
        :param trade_time_limit: The time in seconds to wait fot the order before cancelling it
        :type trade_time_limit: float

        :return: Order object
        :rtype : dict
        """
        start_time = time.time()
        order_data = self.Bittrex.get_order(order_uuid)
        while time.time() - start_time <= trade_time_limit and order_data[
                "result"]["IsOpen"]:
            time.sleep(10)
            order_data = self.Bittrex.get_order(order_uuid)

        if order_data["result"]["IsOpen"]:
            error_str = self.Messenger.print_error("order", [
                order_uuid, trade_time_limit, order_data["result"]["Exchange"]
            ])
            logger.error(error_str)
            if order_data["result"]["Type"] == "LIMIT_BUY":
                self.Bittrex.cancel(order_uuid)

        return order_data

    def calculate_RSI(self, coin_pair, period, unit):
        """
        Calculates the Relative Strength Index for a coin_pair
        If the returned value is above 75, it's overbought (SELL IT!)
        If the returned value is below 25, it's oversold (BUY IT!)

        :param coin_pair: String literal for the market (ex: BTC-LTC)
        :type coin_pair: str
        :param period: Number of periods to query
        :type period: int
        :param unit: Ticker interval (one of: 'oneMin', 'fiveMin', 'thirtyMin', 'hour', 'week', 'day', and 'month')
        :type unit: str

        :return: RSI
        :rtype : float
        """
        closing_prices = self.get_closing_prices(coin_pair, period * 3, unit)
        count = 0
        change = []
        # Calculating price changes
        for i in closing_prices:
            if count != 0:
                change.append(i - closing_prices[count - 1])
            count += 1
            if count == 15:
                break
        # Calculating gains and losses
        advances = []
        declines = []
        for i in change:
            if i > 0:
                advances.append(i)
            if i < 0:
                declines.append(abs(i))
        average_gain = (sum(advances) / 14)
        average_loss = (sum(declines) / 14)
        new_avg_gain = average_gain
        new_avg_loss = average_loss
        for _ in closing_prices:
            if 14 < count < len(closing_prices):
                close = closing_prices[count]
                new_change = close - closing_prices[count - 1]
                add_loss = 0
                add_gain = 0
                if new_change > 0:
                    add_gain = new_change
                if new_change < 0:
                    add_loss = abs(new_change)
                new_avg_gain = (new_avg_gain * 13 + add_gain) / 14
                new_avg_loss = (new_avg_loss * 13 + add_loss) / 14
                count += 1

        if new_avg_loss == 0:
            return None

        rs = new_avg_gain / new_avg_loss
        new_rs = 100 - 100 / (1 + rs)
        return new_rs

    def get_non_zero_balances(self):
        """
        Gets all non-zero user coin balances in the correct format
        """
        balances_data = self.Bittrex.get_balances()
        if not balances_data["success"]:
            error_str = self.Messenger.print_error("balance")
            logger.error(error_str)
            return
        non_zero_balances = py_.filter_(
            balances_data["result"],
            lambda balance_item: balance_item["Balance"] > 0)
        return py_.map_(non_zero_balances,
                        lambda balance: self.create_balance_object(balance))

    def create_balance_object(self, balance_item):
        """
        Creates a new balance object containing only the relevant values and the BTC value of the coin's balance

        :param balance_item: The Bittrex user balance object for a coin
        :type balance_item: dict
        """
        btc_price = 1
        is_tracked = False
        if balance_item["Currency"] != "BTC":
            coin_pair = "BTC-" + balance_item["Currency"]
            is_tracked = coin_pair in self.Database.trades["trackedCoinPairs"]
            btc_price = self.get_current_price(coin_pair, "bid")

        return py_.assign(
            py_.pick(balance_item, "Currency", "Balance"), {
                "BtcValue": round(btc_price * balance_item["Balance"], 8),
                "IsTracked": is_tracked
            })