Example #1
0
class TestBittrexMarketAPI(unittest.TestCase):
    def setUp(self):
        self.bittrex = Bittrex()

    def test_secret_and_key(self):
        try:
            os.environ['BITTREX_KEY']
            os.environ['BITTREX_SECRET']
        except KeyError:
            self.fail("Requires BITTREX_KEY and BITTREX_SECRET env variables")

    def test_buy_limit(self):
        self.assertRaises(TypeError, self.bittrex.buy_limit)
        actual = self.bittrex.buy_limit('BTC-LTC', 1, 1)
        test_response_structure(self, actual, 'buy_limit')

    def test_sell_limit(self):
        self.assertRaises(TypeError, self.bittrex.buy_limit)
        actual = self.bittrex.sell_limit('BTC-LTC', 1, 1)
        test_response_structure(self, actual, 'sell_limit')

    def test_cancel(self):
        self.assertRaises(TypeError, self.bittrex.cancel)
        # provide invalid uuid but test the json structure
        actual = self.bittrex.cancel('BTC-LTC')
        test_response_structure(self, actual, 'cancel')

    def test_open_orders(self):
        invalid_actual = self.bittrex.get_open_orders('Invalid Market')
        no_param_actual = self.bittrex.get_open_orders()
        actual = self.bittrex.get_open_orders('BTC-LTC')
        test_failed_response(self, invalid_actual, 'get_open_orders')
        test_basic_response(self, no_param_actual, 'get_open_orders')
        test_basic_response(self, actual, 'get_open_orders')
Example #2
0
class Trader(object):
    """
    Used for handling all trade functionality
    """
    def __init__(self, secrets):
        self.trade_params = secrets["tradeParameters"]
        self.pause_params = secrets["pauseParameters"]

        self.Bittrex = Bittrex(secrets)
        self.Messenger = Messenger(secrets)
        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_exception_error("connection")
            logger.exception(exception)
            exit()

    def analyse_pauses(self):
        """
        Check all the paused buy and sell pairs 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 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()

    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 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 rsi is not None and rsi <= self.pause_params["buy"][
                "rsiThreshold"]:
            self.Messenger.print_no_buy(coin_pair, rsi, day_volume,
                                        current_buy_price)
        elif rsi is not None:
            self.Messenger.print_pause(coin_pair, rsi,
                                       self.pause_params["buy"]["pauseTime"],
                                       "buy")
            self.Database.pause_buy(coin_pair)

    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 self.check_sell_parameters(rsi, profit_margin):
            sell_stats = {"rsi": rsi, "profitMargin": profit_margin}
            self.sell(coin_pair, current_sell_price, sell_stats)
        elif rsi is not None and profit_margin >= self.pause_params["sell"][
                "profitMarginThreshold"]:
            self.Messenger.print_no_sell(coin_pair, rsi, profit_margin,
                                         current_sell_price)
        elif rsi is not None:
            self.Messenger.print_pause(coin_pair, profit_margin,
                                       self.pause_params["sell"]["pauseTime"],
                                       "sell")
            self.Database.pause_sell(coin_pair)

    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
        """
        return (
            rsi is not None and rsi <= self.trade_params["buy"]["rsiThreshold"]
            and day_volume >= self.trade_params["buy"]["24HourVolumeThreshold"]
            and
            current_buy_price > self.trade_params["buy"]["minimumUnitPrice"])

    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
        """
        return ((rsi is not None
                 and rsi >= self.trade_params["sell"]["rsiThreshold"]
                 and profit_margin >
                 self.trade_params["sell"]["minProfitMarginThreshold"])
                or profit_margin >
                self.trade_params["sell"]["profitMarginThreshold"])

    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_data = self.Bittrex.buy_limit(coin_pair, btc_quantity / price,
                                          price)
        if not buy_data["success"]:
            return logger.error(
                "Failed to buy on {} market.".format(coin_pair))
        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.send_buy_email(buy_order_data["result"], stats)
        self.Messenger.print_buy(coin_pair, price, stats["rsi"],
                                 stats["24HrVolume"])
        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"]:
            return logger.error(
                "Failed to sell on {} market. Bittrex error message: {}".
                format(coin_pair, sell_data["message"]))

        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.send_sell_email(sell_order_data["result"], stats)
        self.Messenger.print_sell(coin_pair, price, stats["rsi"],
                                  stats["profitMargin"])
        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"]:
            logger.error("Failed to fetch Bittrex markets")
            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"]:
            logger.error(
                "Failed to fetch Bittrex market summary for the {} market".
                format(coin_pair))
            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"]:
            logger.error(
                "Failed to fetch Bittrex market summary for the {} market".
                format(coin_pair))
            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_order_error(
                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

        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
Example #3
0
class BittrexBuysellWorker(object):
    def __init__(self, key=None, secret=None, token=None):
        self.EXCHANGE_RATE = 0.9975  # 0.25% for each trading transaction
        self.api = Bittrex(key, secret)
        self.token = token
        try:
            m = self.api.get_market_summaries()
            #print m
            market_list = []
            for market in m.get("result"):
                market_list.append(market.get("MarketName"))
            #print marketlist
        except:
            print "Error: Cannot get market summaries"
            exit(1)
        self.market_list = market_list

    def w_get_api(self):
        """
		Get balance of a coin
		:return: api Bittrex.py
		:rtype : Bittrex class
		"""
        return self.api

    def w_get_balance(self, coin):
        """
		Get balance of a coin
		:return: current balance value
		:rtype : float
		"""
        try:
            m = self.api.get_balance(coin)
            print m
            if (m.get("success")):
                v = m.get("result").get("Balance")
                return v if v else 0
            else:
                print("Cannot get {} balance".format(coin))
                return ERROR.CMD_UNSUCCESS
        except:
            print("Error Account/Connection issue. Get {} balance failed".
                  format(coin))
            return ERROR.CONNECTION_FAIL

    def w_get_market_name(self, coin1, coin2):
        """
		Get marketname for pair of coins
		:param coin1: String literal for coin 1 (ex: USDT)
		:param coin2: String literal for coin 2 (ex: XMR)
		:return: (MarketName, "Buy"/"Sell") 
		:rtype : str
		"""
        market = coin1 + "-" + coin2
        if market in self.market_list:
            return (market, "Buy")
        else:
            market = coin2 + "-" + coin1
            if market in self.market_list:
                return (market, "Sell")
            else:
                print "Error: Invalid coin pair"
                return (None, None)

    def w_get_price(self, market, type, unit=0, depth=20):
        """
		Get price Ask Last Bid 
		:param market: String literal for coin1-coin2 (ex: USDT-XMR)
		:param type: Ask/Last/Bid
		:param unit: if unit != 0, get price with respect to order book
		:return: price
		:rtype : float
		"""
        if type is "Last" or "Bid" or "Ask":
            try:
                price = self.api.get_marketsummary(market).get(
                    "result")[0].get(type)
                if type == "Ask":
                    ordertype = "sell"
                elif type == "Bid":
                    ordertype = "buy"
                else:  #Dont need to check order book for Last
                    return price

                m = self.api.get_orderbook(market, ordertype, depth)
                #print m
                if (m.get("message") != ""):
                    print "Fail to get order book of {}: {}".format(
                        market, m.get("message"))
                    return ERROR.CMD_UNSUCCESS
                else:
                    order_price_list = m.get("result")
                    #print order_price_list
                    sum_quantity = 0
                    for o in order_price_list:
                        #print o
                        quantity = o.get("Quantity")
                        price = o.get("Rate")
                        sum_quantity += quantity
                        if (sum_quantity >= unit):
                            return price

            except:
                print("Error in get {} price".format(market))
                return ERROR.CONNECTION_FAIL
        else:
            print("Invalid type of market (Ask/Bid/Last)")
            return ERROR.PARAMETERS_INVALID
            '''To do: unit != 0'''

    def w_get_open_order(self, market=None):
        """
		Get list of uuid of open orders
		:param market: String literal for coin1-coin2 (ex: USDT-XMR)
		:return: uuid list
		:rtype : str
		"""
        return self.api.get_open_orders(market)

    def w_order_buy_sell(self,
                         coin1,
                         coin2,
                         value1,
                         price,
                         timeout,
                         cancel_on_timeout=True):
        """
		Buy/Sell from coin c1 to coin coin2 at price 
		:param coin1: String literal for coin 1 (ex: USDT)
		:param coin2: String literal for coin 2 (ex: XMR)
		:param value1: The value of coin1 which is used to buy/sell
		:param price: buy/sell price, can be Ask, Last or Bid
		:return: uuid order
		:rtype : str
		"""

        value2_before = self.w_get_balance(coin2)  #get current coin2 balance
        market, type = self.w_get_market_name(coin1, coin2)
        #print market, type
        #Buy and sell are seperated from market point of view.
        if (type == "Buy"):
            order_buy_sell = self.api.buy_limit
            quantity = value1 / price * self.EXCHANGE_RATE
            value2 = quantity * self.EXCHANGE_RATE
        elif (type) == "Sell":
            order_buy_sell = self.api.sell_limit
            quantity = value1
            value2 = quantity * price * self.EXCHANGE_RATE
        else:
            print "Buy/Sell Error: Invalid market {}-{}".format(coin1, coin2)
            return ERROR.CMD_INVALID
        print("@@@ From {} {} buy {} {} at price {}.".format(
            value1, coin1, value2, coin2, price))
        #Process buy/sell within timeout
        #Several posible cases:
        # 1. The price is too high/low, no one wants to buy/sell
        # 2. The connection drops while waiting the result
        # 3. The quantity is too low -> "INSUFFICIENT_FUNDS" response from server
        try:
            m = order_buy_sell(market, quantity, price)
            if (m.get("success") == True):
                uuid = m.get("result").get("uuid")
                process_time = time.time() + timeout
                while 1:
                    value2_after = self.w_get_balance(coin2)
                    if time.time() > process_time:
                        if cancel_on_timeout:  #Cancel order because of timeout
                            self.w_cancel_order(uuid)
                            print "Cancel order!"
                        else:
                            print "Order is still open!"
                            return uuid
                        print "{} transaction was timeout".format(type)
                        return ERROR.TIME_OUT
                    if (value2_after < 0):
                        #Error
                        print "Error: in balance code {}".format(value2_after)
                        return ERROR.CMD_UNSUCCESS
                    if (value2_after - value2_before >=
                            value2 * 0.9):  #offset 10% for safety
                        #buy success
                        return uuid
            elif m.get("message") == "INSUFFICIENT_FUNDS":
                print("INSUFFICIENT_FUNDS issue")
                print m
                return ERROR.PARAMETERS_INVALID
            else:
                print m
                return ERROR.CMD_UNSUCCESS
        except:
            print "Error buy/sell. Conection failed."
            return ERROR.CONNECTION_FAIL

    def w_cancel_order(self, uuid):
        """
		Cancel an order via uuid 
		:param uuid: order uuid
		:return: error code
		:rtype : ERROR
		"""
        try:
            #todo: need check timeout
            self.api.cancel(uuid)
            while uuid in self.api.get_open_orders():
                print "Wait for cancel order {}".format(uuid)

            return ERROR.CMD_SUCCESS
        except:
            print "Cannot cancel order {}".format(uuid)
            return ERROR.CONNECTION_FAIL

    def w_get_price_by_order_book(self, market, ordertype, value, depth=20):
        """
		Get the corresponding price for a given value -> Avoid stuck in buy/sell order
		:param market: Ex: USDT-XMR
		:param ordertype: "buy" or "sell"
		:param value: The value of coin1 which is used to buy/sell
		:return: uuid order
		:rtype : str
		"""
        return 0
class bittrex_private:
    def __init__(self):
        # self.bittrex_public = Bittrex(None, None)  # 公開情報を扱うBittrexオブジェクト
        self.bittrex_private = Bittrex(KEY, SECRET)  # 個人の情報を扱うBittrexオブジェクト

    # トレード履歴を取得
    def get_order_history(self):
        response = self.bittrex_private.get_order_history()
        pprint(response)
        return response

    # 買い注文を出す
    # marketに通貨ペア、quantityに注文する量、rateに価格を指定
    def buy_alt_coin(self, market, quantity, rate):
        response = self.bittrex_private.buy_limit(market=market,
                                                  quantity=quantity,
                                                  rate=rate)
        # 成功なら注文id、失敗ならfalseを返す
        if response['success'] is False:
            print(response)
            return False
        else:
            return response['result']['uuid']

    # 売り注文を出す
    # marketに通貨ペア、quantityに注文する量、rateに価格を指定
    def sell_alt_coin(self, market, quantity, rate):
        response = self.bittrex_private.sell_limit(market=market,
                                                   quantity=quantity,
                                                   rate=rate)
        # 成功なら注文id、失敗ならfalseを返す
        if response['success'] is False:
            print(response)
            return False
        else:
            return response['result']['uuid']

    # 注文をキャンセルする
    def order_cancel(self, uuid):
        response = self.bittrex_private.cancel(uuid=uuid)
        # 成功ならtrue、失敗ならfalseを返す
        print(response)
        return response['success']

    # 注文一覧を取得する
    def get_orders(self):
        order_list = []
        response = self.bittrex_private.get_open_orders()
        if response['success'] is False:
            print(response)
            return False
        else:
            # 注文が1件もない場合
            if len(response['result']) == 0:
                return None

            for item in response['result']:
                # 通貨の種類と量、注文IDを抜き出す
                balance = {}
                balance['market'] = item['Exchange']
                balance['quantity'] = item['Quantity']
                balance['uuid'] = item['OrderUuid']
                order_list.append(balance)

        return order_list

    # 所有している通貨をList型で返す
    def get_balances(self):
        balance_list = []
        response = self.bittrex_private.get_balances()
        if response['success'] is False:
            print(response)
            return False
        else:
            for item in response['result']:
                # 利用可能な通貨量が0の場合はスキップする
                if item['Available'] == 0:
                    continue
                # 通貨の種類と量を抜き出す
                balance = {}
                balance['currency'] = item['Currency']
                balance['available'] = item['Available']
                balance_list.append(balance)

        return balance_list
Example #5
0
class Trader(object):
    """
    Used for handling all trade functionality
    """

    def __init__(self, secrets):
        self.trade_params = secrets["tradeParameters"]
        self.pause_params = secrets["pauseParameters"]

        self.Bittrex = Bittrex(secrets)
        self.Messenger = Messenger(secrets)
        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_exception_error("connection")
            logger.exception(exception)
            exit()

    def analyse_pauses(self):
        """
        Check all the paused buy and sell pairs 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 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()

    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 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 rsi is not None and rsi <= self.pause_params["buy"]["rsiThreshold"]:
            self.Messenger.print_no_buy(coin_pair, rsi, day_volume, current_buy_price)
        elif rsi is not None:
            self.Messenger.print_pause(coin_pair, rsi, self.pause_params["buy"]["pauseTime"], "buy")
            self.Database.pause_buy(coin_pair)

    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 self.check_sell_parameters(rsi, profit_margin):
            sell_stats = {
                "rsi": rsi,
                "profitMargin": profit_margin
            }
            self.sell(coin_pair, current_sell_price, sell_stats)
        elif rsi is not None and profit_margin >= self.pause_params["sell"]["profitMarginThreshold"]:
            self.Messenger.print_no_sell(coin_pair, rsi, profit_margin, current_sell_price)
        elif rsi is not None:
            self.Messenger.print_pause(coin_pair, profit_margin, self.pause_params["sell"]["pauseTime"], "sell")
            self.Database.pause_sell(coin_pair)

    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
        """
        return (rsi is not None and rsi <= self.trade_params["buy"]["rsiThreshold"] and
                day_volume >= self.trade_params["buy"]["24HourVolumeThreshold"] and
                current_buy_price > self.trade_params["buy"]["minimumUnitPrice"])

    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
        """
        return ((rsi is not None and rsi >= self.trade_params["sell"]["rsiThreshold"] and
                 profit_margin > self.trade_params["sell"]["minProfitMarginThreshold"]) or
                profit_margin > self.trade_params["sell"]["profitMarginThreshold"])

    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_data = self.Bittrex.buy_limit(coin_pair, btc_quantity / price, price)
        if not buy_data["success"]:
            return logger.error("Failed to buy on {} market.".format(coin_pair))
        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"]:
            return logger.error(
                "Failed to sell on {} market. Bittrex error message: {}".format(coin_pair, sell_data["message"])
            )

        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"]:
            logger.error("Failed to fetch Bittrex markets")
            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"]:
            logger.error("Failed to fetch Bittrex market summary for the {} market".format(coin_pair))
            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"]:
            logger.error("Failed to fetch Bittrex market summary for the {} market".format(coin_pair))
            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_order_error(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

        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
                        float(1 - cfg.getfloat('Data', 'commission') / 100),
                        sellOrderBook[x]['Rate'])
                else:
                    text = my_bittrex.buy_limit(
                        'BTC-' + last_message['text'],
                        sellOrderBook[x]['Quantity'] *
                        float(1 - cfg.getfloat('Data', 'commission') / 100),
                        sellOrderBook[x]['Rate'])

                BTC = BTC - BTC / sellOrderBook[x]['Rate'] * float(
                    1 - cfg.getfloat('Data', 'commission') / 100)
                if BTC <= 0.00050000: break
            if BTC <= 0.00050000: break

        # проверяет исполнился ли ордер
        bot.sendMessage(chat_id=str(last_message['from']['id']),
                        text="Проверяю покупку (2 сек)...")
        time.sleep(1)
        get_open_orders = my_bittrex.get_open_orders('BTC-' +
                                                     last_message['text'])
        if len(get_open_orders['result']) == 0:
            setSell(last_message)
            break

        # Если ордер на покупку не исполнился , то отменяет все открытые новые ордера
        bot.sendMessage(chat_id=str(last_message['from']['id']),
                        text="Не удалось выкупить весь объем.\nПовторяю...")
        for y in range(0, len(get_open_orders['result'])):
            my_bittrex.cancel(get_open_orders['result'][y]['OrderUuid'])
            time.sleep(0.1)
            print(
                'Cancelled order not found...order was not placed while the program is running'
            )
        else:
            print('Cancelled order found...', ',Time = ',
                  cancelled_order['Opened'].split('T')[0],
                  cancelled_order['Opened'].split('T')[1], ',Type =',
                  cancelled_order['OrderType'], ',Exchange =',
                  cancelled_order['Exchange'], ',Quantity =',
                  cancelled_order['Quantity'])
            slave_number = 0
            for i in order_mapping:
                slave_bittrex = Bittrex(api_key=slave_api[slave_number][0],
                                        api_secret=slave_api[slave_number][1])
                slave_number += 1
                if i[cancelled_order['OrderUuid']] == 0:
                    print('Order was not executed to slave', slave_number)
                else:
                    new_slave_cancel = slave_bittrex.cancel(
                        uuid=i[cancelled_order['OrderUuid']])
                    i[cancelled_order['OrderUuid']] = 0
                    if new_slave_cancel:
                        print('Order Cancelled Successfully from Slave',
                              slave_number)
                    else:
                        print('Error in slave', slave_number,
                              new_slave_cancel['message'])
        i_open_count -= 1
        i_count = i_open_count + i_history_count
        sleep(10)