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')
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
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
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)