class BittrexTrader(Trader):

    simulation_buy_order_id = "1234"
    simulation_sell_order_id = "4321"

    def __init__(self,
                 market="BTC-XMR",
                 minimum_trade_size=bittrex_minimum_btc_trade_size,
                 percentage_to_allocate=1,
                 percentage_per_trade=1,
                 starting_amount=1):
        '''
        Pre: options is an instance of QuadrigaOptions and must have pair and ticker set.
        Post: self.is_test = True until authenticate() is called
        '''

        # Trader is in test mode by default.
        # minimum_trade is the minimum amount of assets that can be sold on a trade.
        Trader.__init__(self, True, minimum_trade_size)

        # Will be set to a number (order ID) when an order is placed.
        self._waiting_for_order_to_fill = None

        # Used to place orders, cancel orders, get the order book during simulation mode
        self.bittrex_api = Bittrex(BittrexSecret.api_key,
                                   BittrexSecret.api_secret)

        # In test mode: Is used to prevent the same transaction from being counted twice.
        self._last_simulation_transaction_check = 0

        # In test mode: tracks how much the trader's order has been filled.
        self._expecting_simulation_balance = 0
        self._expecting_simulation_assets = 0
        self._filled_simulation_balance = 0
        self._filled_simulation_assets = 0

        # Used when aborting to determine if any positions need to be closed.
        self._active_buy_order = False
        self._active_sell_order = False

        # These variables determine how much the trader is allowed to trade with and
        # how much it will commit per trade.
        if percentage_to_allocate > 1:
            raise Warning("percentage_to_allocate cannot be greater than 1")
        self.percentage_to_allocate = Decimal(percentage_to_allocate)
        self.percentage_per_trade = Decimal(percentage_per_trade)
        self.validate_percentage_per_trade()

        # Get the market ticker and split it up for print statements.
        self.market_ticker = market
        self.minor_currency, self.major_currency = market.split("-")

        with localcontext() as context:
            context.prec = 8

            self.balance = Decimal(starting_amount)
            self.assets = Decimal(0)

            self.fee_added = Decimal(1) + bittrex_fee
            self.fee_substracted = float(1 - bittrex_fee)

    def authenticate(self):
        self.is_test = False
        self.fetch_balance_and_assets()

    def validate_percentage_per_trade(self):
        if self.percentage_per_trade > 1:
            raise Warning("percentage_per_trade cannot be greater than 1")

    def should_default_to(self, default_position):
        '''
        True == hold/buy major currency when the market is unprofitable
        False == hold/sell to get minor currency when market is unprofitable 
        None == hold whatever the trader has
        '''
        self.default_position = default_position

    def fetch_balance_and_assets(self):
        '''
        Get the balance and assets this trader has permission to spend in the exchange.
        Pre: API Key, client, and API secret have been set.
        Post: self.balance and self.assets are set. They are a percentage of the actual balance/assets
              if percentage_to_trade is set in the constructor.
        '''

        if self.is_test:
            print(
                "Warning: fetch_balance was called when trader is in test mode."
            )
        else:
            #Fetch the two availability of the two currencies from the exchange.
            minor_currency_available = self.bittrex_api.get_balance(
                self.minor_currency)["result"]["Available"]
            if minor_currency_available == None:
                minor_currency_available = 0
            self.balance = Decimal(
                float(minor_currency_available)) * self.percentage_to_allocate
            major_currency_available = self.bittrex_api.get_balance(
                self.major_currency)["result"]["Available"]
            if major_currency_available == None:
                major_currency_available = 0
            self.assets = Decimal(
                float(major_currency_available)) * self.percentage_to_allocate
            print("Trading with: " + str(round(self.balance, 3)) +
                  self.minor_currency + " and " + str(round(self.assets, 3)) +
                  self.major_currency)

    def buy(self, market_value):
        if self.can_buy == True:

            if self._waiting_for_order_to_fill != None:
                self.was_order_filled(self._waiting_for_order_to_fill)

            # Prevent buying while a sell order is active.
            if self._waiting_for_order_to_fill == None:

                #Only trade the percentage of the balance allocated to a single trade.
                #Bittrex charges its commission on top of the amount requested. (Divide by the fee + 1)
                balance_for_this_buy = (
                    self.balance * self.percentage_per_trade) / self.fee_added
                assets_to_buy = balance_for_this_buy / market_value

                assets_to_buy = round(assets_to_buy, bittrex_precision)
                market_value = round(market_value, bittrex_precision)
                if assets_to_buy >= self.minimum_trade:

                    self._active_buy_order = True

                    if self.is_test:
                        self.simulation_buy(assets_to_buy)
                    else:
                        self.limit_buy_order(assets_to_buy, market_value)

                    print("Buying in " + self.market_ticker +
                          ". Planning to spend: " + str(self.balance) +
                          self.minor_currency)
                    self.balance = Decimal(0)
                else:
                    sys.stdout.write('b ')

            else:
                sys.stdout.write('wb ')

    def limit_buy_order(self, quantity, market_value):
        if self.bittrex_api is not None:
            result = self.bittrex_api.buy_limit(self.market_ticker, quantity,
                                                market_value)
            if result["result"] is not None:
                self._waiting_for_order_to_fill = result["result"]["uuid"]
            self._active_buy_order = True
        else:
            raise Warning("self.bittrex_api cannot be None")

    def simulation_buy(self, quantity):
        self._waiting_for_order_to_fill = BittrexTrader.simulation_buy_order_id

        # Order will not be "filled" until
        # filled_simulation_assets == expecting_simulation_assets
        self._expecting_simulation_assets = quantity * float(self.fee_added)
        self._filled_simulation_assets = 0

        # Only orders that matter are the ones that might fill us which can only
        # happen in the future.
        self._last_simulation_transaction_check = datetime.datetime.now()

        self._active_buy_order = True

    def sell(self, market_value):
        if self.can_sell:
            if self._waiting_for_order_to_fill != None:
                self.was_order_filled(self._waiting_for_order_to_fill)

            # Prevent selling while a buy order is active.
            if self._waiting_for_order_to_fill == None:

                #Always sell all assets.
                assets_to_sell = round(self.assets, bittrex_precision)
                market_value = round(market_value, bittrex_precision)
                if self.assets >= self.minimum_trade:

                    self._active_sell_order = True

                    if self.is_test:
                        self.simulation_sell(assets_to_sell, market_value)
                    else:
                        self.limit_sell_order(assets_to_sell, market_value)

                    print(
                        "Selling. Planning to get balance: " +
                        str(assets_to_sell * market_value *
                            self.fee_substracted) + self.minor_currency)
                    self.assets = Decimal(0)
                else:
                    sys.stdout.write('s ')

            else:
                sys.stdout.write('ws ')

    def limit_sell_order(self, quantity, market_value):
        if self.bittrex_api is not None:
            result = self.bittrex_api.sell_limit(self.market_ticker, quantity,
                                                 market_value)
            if result["result"] is not None:
                self._waiting_for_order_to_fill = result["result"]["uuid"]
            self._active_sell_order = True
        else:
            raise Warning("self.bittrex_api cannot be None")

    def simulation_sell(self, quantity, market_value):
        self._waiting_for_order_to_fill = BittrexTrader.simulation_sell_order_id

        # Order will not be "filled" until
        # _filled_simulation_balance == _expecting_simulation_balance
        self._expecting_simulation_balance = quantity * market_value
        self._filled_simulation_balance = 0

        # Used to track how much balance is earned from partial fills.
        self._limit_order_price = market_value

        # Only orders that matter are the ones that might fill us which can only
        # happen in the future.
        self._last_simulation_transaction_check = datetime.datetime.now()

        self._active_sell_order = True

    def was_order_filled(self, order_id):
        '''
        Post: Internal balance/assets is updated if the order was filled.
        '''

        if self.is_test:
            try:
                assert (order_id == BittrexTrader.simulation_buy_order_id
                        or order_id == BittrexTrader.simulation_sell_order_id)
            except AssertionError as e:
                e.args += ("Invalid order ID: ", order_id)
                raise

            # Simulate the trader's order being filled by watching what the market.
            # It is likely that simulation mode results in higher profits as in reality
            # other bots undercut our own trades so our orders are filled less frequently.

            history = self.bittrex_api.get_market_history(
                self.market_ticker)["result"]

            for trade in history:
                if "." in trade["TimeStamp"]:
                    timestamp = datetime.datetime.strptime(
                        trade["TimeStamp"], "%Y-%m-%dT%H:%M:%S.%f")
                else:
                    timestamp = datetime.datetime.strptime(
                        trade["TimeStamp"], "%Y-%m-%dT%H:%M:%S")
                if timestamp < self._last_simulation_transaction_check:
                    break
                else:
                    with localcontext() as context:
                        context.prec = 8

                        if order_id == BittrexTrader.simulation_buy_order_id and trade[
                                "OrderType"] == "SELL":
                            self._filled_simulation_assets += trade["Quantity"]
                            if self._filled_simulation_assets >= self._expecting_simulation_assets:
                                self.assets = self._expecting_simulation_assets * self.fee_substracted
                                self._active_buy_order = False
                                self._waiting_for_order_to_fill = None
                        elif order_id == BittrexTrader.simulation_sell_order_id and trade[
                                "OrderType"] == "BUY":
                            incoming_balance = trade[
                                "Quantity"] * self._limit_order_price
                            self._filled_simulation_balance += incoming_balance
                            if self._filled_simulation_balance >= self._expecting_simulation_balance:
                                self.balance = Decimal(
                                    self._expecting_simulation_balance *
                                    self.fee_substracted)
                                self._active_sell_order = False
                                self._waiting_for_order_to_fill = None

            # Orders up to this moment have been processed, don't process them again.
            self._last_simulation_transaction_check = datetime.datetime.now()

        else:

            # Lookup the order on the market and check its status.
            # The trader will stop if the order was cancelled as a human intervened.
            # Note: The pipeline never knows the Trader's status so the pipeline will continue
            #       to pass data to the market observer.

            json_result = self.bittrex_api.get_order(order_id)["result"]

            if json_result["CancelInitiated"] == True:
                print(
                    "The order was cancelled, likely because a human intervened."
                )
                self._waiting_for_order_to_fill = None
                self.abort()
            elif json_result["Quantity"] > json_result[
                    "Quantity"] - json_result["QuantityRemaining"] > 0:
                print(
                    "The order has been partially filled. Waiting until it is fully filled."
                )
            elif json_result["IsOpen"] == False:
                order_type = json_result["Type"]
                if order_type == "LIMIT_BUY":
                    self.assets = Decimal(json_result["Quantity"] -
                                          json_result["CommissionPaid"])
                    self._active_buy_order = False
                    self._waiting_for_order_to_fill = None
                elif order_type == "LIMIT_SELL":
                    self.balance = Decimal((json_result["price"]) *
                                           Decimal(json_result["amount"]) -
                                           json_result["CommissionPaid"])
                    self._active_sell_order = False
                    self._waiting_for_order_to_fill = None
                else:
                    print "Unexpected order type: " + order_type

    def hold(self, market_value):
        ''' Cancel any open orders and revert back to the default position depending on aggressiveness. '''

        sys.stdout.write('h ')

        if self._waiting_for_order_to_fill != None:
            self.was_order_filled(self._waiting_for_order_to_fill)

        # Keep orders open if they help reach the default position.
        if self._active_sell_order and self.default_position != DefaultPosition.SELL:
            if not self.is_test:
                self.cancel_order(self._waiting_for_order_to_fill)
            self._waiting_for_order_to_fill = None
            self._active_sell_order = False

        if self._active_buy_order and self.default_position != DefaultPosition.BUY:
            if not self.is_test:
                self.cancel_order(self._waiting_for_order_to_fill)
            self._waiting_for_order_to_fill = None
            self._active_buy_order = False

    def cancel_order(self, order_id):
        order_info = self.bittrex_api.get_order(order_id)["result"]
        if order_info is not None:
            quantity_remaining = Decimal(order_info["QuantityRemaining"])
            self.balance = (quantity_remaining * Decimal(
                order_info["Limit"])) - Decimal(order_info["CommissionPaid"])
            self.assets = Decimal(order_info["Quantity"]) - quantity_remaining
            self.bittrex_api.cancel(order_id)

    def abort(self):
        Trader.abort(self)
        self._waiting_for_order_to_fill = None
        print("BittrexTrader is shutting down.")
class TestBittrexV20PublicAPI(unittest.TestCase):
    """
    Integration tests for the Bittrex public API.
    These will fail in the absence of an internet connection or if bittrex API goes down
    """

    def setUp(self):
        self.bittrex = Bittrex(None, None, api_version=API_V2_0)

    def test_handles_none_key_or_secret(self):
        self.bittrex = Bittrex(None, None, api_version=API_V2_0)
        # could call any public method here
        actual = self.bittrex.get_markets()
        self.assertTrue(actual['success'], "failed with None key and None secret")

        self.bittrex = Bittrex("123", None, api_version=API_V2_0)
        actual = self.bittrex.get_markets()
        self.assertTrue(actual['success'], "failed with None secret")

        self.bittrex = Bittrex(None, "123", api_version=API_V2_0)
        actual = self.bittrex.get_markets()
        self.assertTrue(actual['success'], "failed with None key")

    def test_get_markets(self):
        actual = self.bittrex.get_markets()
        test_basic_response(self, actual, "get_markets")
        self.assertTrue(isinstance(actual['result'], list), "result is not a list")
        self.assertTrue(len(actual['result']) > 0, "result list is 0-length")

    def test_get_currencies(self):
        actual = self.bittrex.get_currencies()
        test_basic_response(self, actual, "get_currencies")

    def test_get_ticker(self):
        self.assertRaisesRegexp(Exception, 'method call not available', self.bittrex.get_ticker,
                                market='BTC-LTC')

    def test_get_market_summaries(self):
        actual = self.bittrex.get_market_summaries()
        test_basic_response(self, actual, "get_market_summaries")

    def test_get_market_summary(self):
        actual = self.bittrex.get_marketsummary(market='BTC-LTC')
        test_basic_response(self, actual, "get_marketsummary")

    def test_get_orderbook(self):
        actual = self.bittrex.get_orderbook('BTC-LTC')
        test_basic_response(self, actual, "get_orderbook")

    def test_get_market_history(self):
        actual = self.bittrex.get_market_history('BTC-LTC')
        test_basic_response(self, actual, "get_market_history")

    def test_list_markets_by_currency(self):
        actual = self.bittrex.list_markets_by_currency('LTC')
        self.assertListEqual(['BTC-LTC', 'ETH-LTC', 'USDT-LTC'], actual)

    def test_get_wallet_health(self):
        actual = self.bittrex.get_wallet_health()
        test_basic_response(self, actual, "get_wallet_health")
        self.assertIsInstance(actual['result'], list)

    @unittest.skip("Endpoint 404s.  Is this still a valid 2.0 API?")
    def test_get_balance_distribution(self):
        actual = self.bittrex.get_balance_distribution()
        test_basic_response(self, actual, "get_balance_distribution")
        self.assertIsInstance(actual['result'], list)

    def test_get_candles(self):
        actual = self.bittrex.get_candles('BTC-LTC', tick_interval=TICKINTERVAL_ONEMIN)
        test_basic_response(self, actual, "test_get_candles")
        self.assertIsInstance(actual['result'], list)