try:
    import v20
except ImportError:
    raise Exception("Please use 'pip install v20' to trade using Oanda v20 API.")
import sys
import time
import datetime
import traceback
import numpy as np
import pandas as pd
from autotrader import utilities, AutoData
from autotrader.brokers.broker import AbstractBroker
from autotrader.brokers.broker_utils import BrokerUtils
from autotrader.brokers.trading import Order, IsolatedPosition, Position, Trade


class Broker(AbstractBroker):
    def __init__(self, oanda_config: dict, utils: BrokerUtils = None):
        """Create v20 context."""
        self.API = oanda_config["API"]
        self.ACCESS_TOKEN = oanda_config["ACCESS_TOKEN"]
        self.port = oanda_config["PORT"]
        self.ACCOUNT_ID = oanda_config["ACCOUNT_ID"]
        self.api = v20.Context(
            hostname=self.API, token=self.ACCESS_TOKEN, port=self.port
        )

        self.STREAM_API = "stream-fxpractice.oanda.com"
        self.stream = v20.Context(
            hostname=self.STREAM_API, token=self.ACCESS_TOKEN, port=self.port
        )
        self.open_positions = {}

        # Assign broker utilities
        if utils is None:
            utils = BrokerUtils()
        self._utils = utils

        # Create AutoData instance
        data_config = utilities.get_data_config(feed="oanda")
        self.autodata = AutoData(data_config)

    def __repr__(self):
        return "AutoTrader-Oanda Broker Interface"

    def __str__(self):
        return "AutoTrader-Oanda Broker Interface"

    def get_NAV(self) -> float:
        """Returns Net Asset Value of account."""
        self._check_connection()
        response = self.api.account.get(accountID=self.ACCOUNT_ID)
        return response.body["account"].NAV

    def get_balance(self) -> float:
        """Returns account balance."""
        self._check_connection()
        response = self.api.account.get(accountID=self.ACCOUNT_ID)
        return response.body["account"].balance

    def place_order(self, order: Order, **kwargs):
        """Submits order to broker."""
        self._check_connection()

        # Call order to set order time
        order()

        # Submit order
        if order.order_type == "market":
            response = self._place_market_order(order)
        elif order.order_type == "stop-limit":
            response = self._place_stop_limit_order(order)
        elif order.order_type == "limit":
            response = self._place_limit_order(order)
        elif order.order_type == "close":
            response = self._close_position(order.instrument)
        elif order.order_type == "modify":
            response = self._modify_trade(order)
        else:
            print("Order type not recognised.")

        # Check response
        # output = self._check_response(response)

        return response

    def get_orders(self, instrument=None, **kwargs) -> dict:
        """Get all pending orders in the account."""
        self._check_connection()
        response = self.api.order.list_pending(
            accountID=self.ACCOUNT_ID, instrument=instrument
        )
        oanda_pending_orders = response.body["orders"]
        orders = {}

        for order in oanda_pending_orders:
            if order.type != "TAKE_PROFIT" and order.type != "STOP_LOSS":
                new_order = {}
                new_order["id"] = order.id
                new_order["status"] = "open"
                new_order["order_type"] = order.type
                new_order["order_stop_price"] = order.price
                new_order["order_limit_price"] = order.price
                new_order["direction"] = np.sign(order.units)
                new_order["order_time"] = order.createTime
                new_order["instrument"] = order.instrument
                new_order["size"] = abs(order.units)
                new_order["order_price"] = order.price

                # Check for take profit
                if order.takeProfitOnFill is not None:
                    new_order["take_profit"] = order.takeProfitOnFill.price

                # Check for stop loss
                if order.stopLossOnFill is not None:
                    new_order["stop_loss"] = order.stopLossOnFill.price
                    new_order["stop_type"] = "limit"

                if instrument is not None and order.instrument == instrument:
                    orders[order.id] = Order._from_dict(new_order)
                elif instrument is None:
                    orders[order.id] = Order._from_dict(new_order)

        return orders

    def cancel_order(self, order_id: int, **kwargs) -> None:
        """Cancels pending order by ID."""
        self._check_connection()
        self.api.order.cancel(accountID=self.ACCOUNT_ID, orderSpecifier=str(order_id))

    def get_trades(self, instrument=None, **kwargs) -> list:
        """Returns the trades (fills) made by the account."""
        self._check_connection()
        response = self.api.trade.list_open(
            accountID=self.ACCOUNT_ID, state="CLOSED", instrument=instrument
        )
        oanda_trades = response.body["trades"]

        trades = {}
        for trade in oanda_trades:
            trade_dict = self._oanda_trade_to_dict(trade)

            native_trade = Trade(
                instrument=trade_dict["instrument"],
                order_price=None,
                order_time=None,
                order_type=None,
                size=float(trade_dict["size"]),
                fill_time=trade_dict["time_filled"],
                fill_price=float(trade_dict["fill_price"]),
                fill_direction=trade_dict["direction"],
                fee=trade_dict["fees"],
                id=trade_dict["id"],
            )

            trades[trade.id] = native_trade

        return trades

    def get_isolated_positions(self, instrument: str = None, **kwargs):
        """Returns isolated positions for the specified instrument.

        Parameters
        ----------
        instrument : str, optional
            The instrument to fetch trades under. The default is None.
        """
        self._check_connection()
        response = self.api.trade.list_open(accountID=self.ACCOUNT_ID)
        oanda_open_trades = response.body["trades"]

        open_trades = {}
        for trade in oanda_open_trades:
            new_trade = self._oanda_trade_to_dict(trade)

            # Filter by instrument
            if instrument is not None and trade.instrument in instrument:
                open_trades[trade.id] = IsolatedPosition(**new_trade)
            elif instrument is None:
                open_trades[trade.id] = IsolatedPosition(**new_trade)

        return open_trades

    @staticmethod
    def _oanda_trade_to_dict(trade) -> dict:
        """Converts an Oanda Trade object to a dictionary."""
        new_trade = {}
        related_orders = []
        new_trade["instrument"] = trade.instrument
        new_trade["time_filled"] = trade.openTime
        new_trade["fill_price"] = trade.price
        new_trade["size"] = abs(trade.currentUnits)
        new_trade["id"] = trade.id
        new_trade["direction"] = np.sign(trade.currentUnits)
        new_trade["margin_required"] = trade.marginUsed
        new_trade["unrealised_PL"] = trade.unrealizedPL
        new_trade["fees"] = trade.financing
        new_trade["status"] = trade.state.lower()

        # Check for take profit
        if trade.takeProfitOrder is not None:
            new_trade["take_profit"] = trade.takeProfitOrder.price
            related_orders.append(trade.takeProfitOrder.id)

        # Check for stop loss
        if trade.stopLossOrder is not None:
            new_trade["stop_loss"] = trade.stopLossOrder.price
            new_trade["stop_type"] = "limit"
            related_orders.append(trade.stopLossOrder.id)

        if related_orders is not None:
            new_trade["related_orders"] = related_orders

        return new_trade

    def get_trade_details(self, trade_ID: int):
        """Returns the details of the trade specified by trade_ID.
        WARNING: THIS METHOD HAS BEEN MADE REDUNDANT BY 'get_trades' METHOD.
        """
        raise DeprecationWarning(
            "This method is deprecated, and will "
            + "be removed in a future release. Please use the "
            + "get_trades method instead."
        )
        response = self.api.trade.list(accountID=self.ACCOUNT_ID, ids=int(trade_ID))
        trade = response.body["trades"][0]

        details = {
            "direction": int(np.sign(trade.currentUnits)),
            "order_time": datetime.datetime.strptime(
                trade.openTime[:-4], "%Y-%m-%dT%H:%M:%S.%f"
            ),
            "instrument": trade.instrument,
            "size": trade.currentUnits,
            "order_price": trade.price,
            "order_ID": trade.id,
            "time_filled": trade.openTime,
            "entry_price": trade.price,
            "unrealised_PL": trade.unrealizedPL,
            "margin_required": trade.marginUsed,
        }

        # Get associated trades
        related = []
        try:
            details["take_profit"] = trade.takeProfitOrder.price
            related.append(trade.takeProfitOrder.id)
        except:
            pass

        try:
            details["stop_loss"] = trade.stopLossOrder.price
            related.append(trade.stopLossOrder.id)
        except:
            pass
        details["related_orders"] = related

        # TODO - veryify functionality of below...
        return Trade(trade)

    def get_positions(self, instrument: str = None, **kwargs) -> dict:
        """Gets the current positions open on the account."""
        self._check_connection()
        response = self.api.position.list_open(accountID=self.ACCOUNT_ID)
        oanda_open_positions = response.body["positions"]
        open_positions = {}
        for position in oanda_open_positions:
            pos = {
                "instrument": position.instrument,
                "long_units": position.long.units,
                "long_PL": position.long.unrealizedPL,
                "long_margin": None,
                "short_units": position.short.units,
                "short_PL": position.short.unrealizedPL,
                "short_margin": None,
                "total_margin": position.marginUsed,
            }

            # fetch trade ID'strade_IDs
            trade_IDs = []
            if abs(pos["long_units"]) > 0:
                for ID in position.long.tradeIDs:
                    trade_IDs.append(ID)
            if abs(pos["short_units"]) > 0:
                for ID in position.short.tradeIDs:
                    trade_IDs.append(ID)

            pos["trade_IDs"] = trade_IDs

            if instrument is not None and position.instrument == instrument:
                open_positions[position.instrument] = Position(**pos)
            elif instrument is None:
                open_positions[position.instrument] = Position(**pos)

        return open_positions

    def get_position(self, instrument: str) -> Position:
        """Gets position from Oanda."""
        self._check_connection()
        response = self.api.position.get(
            instrument=instrument, accountID=self.ACCOUNT_ID
        )
        # TODO - convert to position
        return response.body["position"]

    def get_summary(self):
        """Returns account summary."""
        self._check_connection()
        # response = self.api.account.get(accountID=self.ACCOUNT_ID)
        response = self.api.account.summary(accountID=self.ACCOUNT_ID)
        return response

    def get_data(self, pair: str, period: int, interval: str) -> pd.DataFrame:
        self._check_connection()
        response = self.api.instrument.candles(
            pair, granularity=interval, count=period, dailyAlignment=0
        )
        data = self._utils.response_to_df(response)
        return data

    def check_trade_size(self, instrument: str, units: float) -> float:
        """Checks the requested trade size against the minimum trade size
        allowed for the currency pair.
        """
        response = self.api.account.instruments(
            accountID=self.ACCOUNT_ID, instruments=instrument
        )
        # minimum_units = response.body['instruments'][0].minimumTradeSize
        trade_unit_precision = response.body["instruments"][0].tradeUnitsPrecision
        return round(units, trade_unit_precision)

    def update_data(
        self, instrument: str, granularity: str, data: pd.DataFrame
    ) -> pd.DataFrame:
        """Attempts to construct the latest candle when there is a delay in the
        api feed.
        """
        self._check_connection()
        granularity_details = self.deconstruct_granularity(granularity)
        secs = granularity_details["seconds"]
        mins = granularity_details["minutes"]
        hrs = granularity_details["hours"]
        days = granularity_details["days"]

        small_granularity = self.get_reduced_granularity(granularity_details, 25)

        # Get data equivalent of last candle's granularity
        time_now = datetime.datetime.now()
        start_time = time_now - datetime.timedelta(
            seconds=secs, minutes=mins, hours=hrs, days=days
        )
        latest_data = self.get_historical_data(
            instrument, small_granularity, start_time.timestamp(), time_now.timestamp()
        )

        # Get latest price data
        latest_close = latest_data.Close.values[0]

        open_price = data.Close.values[-1]
        close_price = latest_close
        high_price = max(latest_data.High.values)
        low_price = min(latest_data.Low.values)
        last_time = data.index[-1]
        stripped_time = datetime.datetime.strptime(
            last_time.strftime("%Y-%m-%d %H:%M:%S%z"), "%Y-%m-%d %H:%M:%S%z"
        )
        new_time = stripped_time + datetime.timedelta(
            seconds=secs, minutes=mins, hours=hrs, days=days
        )

        new_candle = pd.DataFrame(
            {
                "Open": open_price,
                "High": high_price,
                "Low": low_price,
                "Close": close_price,
            },
            index=[new_time],
        )

        new_data = pd.concat([data, new_candle])

        return new_data

    def get_historical_data(self, instrument, interval, from_time, to_time):

        self._check_connection()

        response = self.api.instrument.candles(
            instrument, granularity=interval, fromTime=from_time, toTime=to_time
        )

        data = self._utils.response_to_df(response)

        return data

    def deconstruct_granularity(self, granularity: str):
        """Returns a dict with the seconds, minutes, hours and days
        corresponding to a granularity.
        """

        # Get letter to determine timeframe (eg. M)
        letter = granularity[0]

        # Get timeframe multiple (eg. 15)
        if len(granularity) > 1:
            number = float(granularity[1:])
        else:
            number = 1

        if letter == "S":
            seconds = number
            minutes = 0
            hours = 0
            days = 0

        elif letter == "M":
            seconds = 0
            minutes = number
            hours = 0
            days = 0

        elif letter == "H":
            seconds = 0
            minutes = 0
            hours = number
            days = 0

        else:
            seconds = 0
            minutes = 0
            hours = 0
            days = number

        granularity_details = {
            "seconds": seconds,
            "minutes": minutes,
            "hours": hours,
            "days": days,
        }

        return granularity_details

    def get_reduced_granularity(self, granularity_details, fraction):
        """Returns a candlestick granularity as a fraction of given granularity"""
        secs = granularity_details["seconds"]
        mins = granularity_details["minutes"]
        hrs = granularity_details["hours"]
        days = granularity_details["days"]

        total_seconds = secs + 60 * mins + 60 * 60 * hrs + 60 * 60 * 24 * days

        fractional_seconds = total_seconds / fraction

        seconds = fractional_seconds
        minutes = fractional_seconds / 60
        hours = fractional_seconds / (60 * 60)
        days = fractional_seconds / (60 * 60 * 24)

        if days > 1:
            letter = "D"
            number = 1
            reduced_granularity = letter

        elif hours > 1:
            base = 2
            letter = "H"
            number = base * round(hours / base)
            if number > 12:
                number = 12
            reduced_granularity = letter + str(number)

        elif minutes > 1:
            base = 15
            letter = "M"
            number = base * round(minutes / base)
            if number > 30:
                number = 30
            reduced_granularity = letter + str(number)

        else:
            base = 15
            letter = "S"
            number = base * round(seconds / base)
            if number > 30:
                number = 30
            reduced_granularity = letter + str(number)

        if reduced_granularity[1:] == "0":
            reduced_granularity = reduced_granularity[0] + "1"

        return reduced_granularity

    def get_pip_location(self, instrument: str):
        """Returns the pip location of the requested instrument."""
        response = self.api.account.instruments(self.ACCOUNT_ID, instruments=instrument)
        return response.body["instruments"][0].pipLocation

    def _check_connection(self) -> None:
        """Connects to Oanda v20 REST API. An initial call is performed to check
        for a timeout error.
        """
        # TODO - improve this - currently doubles the poll rate
        for atempt in range(10):
            try:
                # Attempt basic task to check connection
                self.api.account.get(accountID=self.ACCOUNT_ID)

            except BaseException:
                # Error has occurred
                ex_type, ex_value, ex_traceback = sys.exc_info()

                # Extract unformatter stack traces as tuples
                trace_back = traceback.extract_tb(ex_traceback)

                # Format stacktrace
                stack_trace = list()

                for trace in trace_back:
                    trade_string = "File : %s , Line : %d, " % (
                        trace[0],
                        trace[1],
                    ) + "Func.Name : %s, Message : %s" % (trace[2], trace[3])
                    stack_trace.append(trade_string)

                print("\nWARNING FROM OANDA API: The following exception was caught.")
                print(
                    "Time: {}".format(
                        datetime.datetime.now().strftime("%b %d %H:%M:%S")
                    )
                )
                print("Exception type : %s " % ex_type.__name__)
                print("Exception message : %s" % ex_value)
                print("Stack trace : %s" % stack_trace)
                print("  Attempting to reconnect to Oanda v20 API.")

                time.sleep(3)
                api = v20.Context(
                    hostname=self.API, token=self.ACCESS_TOKEN, port=self.port
                )
                self.api = api

            else:
                break

        else:
            print("FATAL: All attempts to connect to Oanda API have failed.")

    def _place_market_order(self, order: Order):
        """Places market order."""
        self._check_connection()
        stop_loss_order = self._get_stop_loss_order(order)
        take_profit_details = self._get_take_profit_details(order)

        # Check position size
        size = self.check_trade_size(order.instrument, order.size)

        response = self.api.order.market(
            accountID=self.ACCOUNT_ID,
            instrument=order.instrument,
            units=order.direction * size,
            takeProfitOnFill=take_profit_details,
            **stop_loss_order
        )

        return response

    def _place_stop_limit_order(self, order):
        """Places MarketIfTouchedOrder with Oanda.
        https://developer.oanda.com/rest-live-v20/order-df/
        """
        # TODO - this submits market if touched, options below
        ordertype = "MARKET_IF_TOUCHED"  # 'MARKET_IF_TOUCHED' # 'STOP', 'LIMIT'
        self._check_connection()

        stop_loss_order = self._get_stop_loss_order(order)
        take_profit_details = self._get_take_profit_details(order)

        # Check and correct order stop price
        price = self._check_precision(order.instrument, order.order_stop_price)
        trigger_condition = order.trigger_price
        size = self.check_trade_size(order.instrument, order.size)

        # Need to test cases when no stop/take is provided (as None type)
        response = self.api.order.market_if_touched(
            accountID=self.ACCOUNT_ID,
            instrument=order.instrument,
            units=order.direction * size,
            price=str(price),
            type=ordertype,
            takeProfitOnFill=take_profit_details,
            triggerCondition=trigger_condition,
            **stop_loss_order
        )
        return response

    def _place_stop_order(self, order: Order):
        """Places a stop order."""
        # TODO - implement this method
        self._check_connection()

        stop_loss_order = self._get_stop_loss_order(order)
        take_profit_details = self._get_take_profit_details(order)

        # Check and correct order stop price
        price = self._check_precision(order.instrument, order.order_stop_price)
        price_bound = self._check_precision(order.instrument, order.order_stop_price)

        trigger_condition = order.trigger_price
        size = self.check_trade_size(order.instrument, order.size)

        response = self.api.order.stop(
            accountID=self.ACCOUNT_ID,
            instrument=order.instrument,
            units=order.direction * size,
            price=str(price),
            priceBound=str(price_bound),
            triggerCondition=trigger_condition,
            takeProfitOnFill=take_profit_details,
            **stop_loss_order
        )
        return response

    def _place_limit_order(self, order: Order):
        """PLaces a limit order."""
        self._check_connection()

        stop_loss_order = self._get_stop_loss_order(order)
        take_profit_details = self._get_take_profit_details(order)

        # Check and correct order stop price
        price = self._check_precision(order.instrument, order.order_limit_price)

        trigger_condition = order.trigger_price
        size = self.check_trade_size(order.instrument, order.size)

        response = self.api.order.limit(
            accountID=self.ACCOUNT_ID,
            instrument=order.instrument,
            units=order.direction * size,
            price=str(price),
            takeProfitOnFill=take_profit_details,
            triggerCondition=trigger_condition,
            **stop_loss_order
        )
        return response

    def _modify_trade(self, order):
        """Modifies the take profit and/or stop loss of an existing trade.

        Parameters
        ----------
        order : TYPE
            DESCRIPTION.
        """
        # Get ID of trade to modify
        modify_trade_id = order.related_orders
        trade = self.api.trade.get(
            accountID=self.ACCOUNT_ID, tradeSpecifier=modify_trade_id
        ).body["trade"]

        if order.take_profit is not None:
            # Modify trade take-profit
            tpID = trade.takeProfitOrder.id

            # Cancel existing TP
            self.api.order.cancel(self.ACCOUNT_ID, tpID)

            # Create new TP
            tp_price = self._check_precision(order.instrument, order.take_profit)
            new_tp_order = self.api.order.TakeProfitOrder(
                tradeID=str(modify_trade_id), price=str(tp_price)
            )
            response = self.api.order.create(
                accountID=self.ACCOUNT_ID, order=new_tp_order
            )
            self._check_response(response)

        if order.stop_loss is not None:
            # Modify trade stop-loss
            slID = trade.stopLossOrder.id

            # Cancel existing SL
            self.api.order.cancel(self.ACCOUNT_ID, slID)

            # Create new SL
            sl_price = self._check_precision(order.instrument, order.stop_loss)
            new_sl_order = self.api.order.StopLossOrder(
                tradeID=str(modify_trade_id), price=str(sl_price)
            )
            response = self.api.order.create(
                accountID=self.ACCOUNT_ID, order=new_sl_order
            )
            self._check_response(response)

    def _get_stop_loss_order(self, order: Order) -> dict:
        """Constructs stop loss order dictionary."""
        self._check_connection()
        if order.stop_type is not None:
            price = self._check_precision(order.instrument, order.stop_loss)

            if order.stop_type == "trailing":
                # Trailing stop loss order
                SL_type = "trailingStopLossOnFill"

                # Calculate stop loss distance
                if order.stop_distance is None:
                    # Calculate stop distance from stop loss price provided
                    if order._working_price is not None:
                        working_price = order._working_price
                    else:
                        if order.order_type == "market":
                            # Get current market price
                            last = self._get_price(order.instrument)
                            working_price = (
                                last.closeoutBid
                                if order.direction < 0
                                else last.closeoutAsk
                            )
                        elif order.order_type in ["limit", "stop-limit"]:
                            working_price = order.order_limit_price
                    distance = abs(working_price - order.stop_loss)

                else:
                    # Calculate distance using provided pip distance
                    pip_value = 10 ** self.get_pip_location(order.instrument)
                    distance = abs(order.stop_distance * pip_value)

                # Construct stop loss order details
                distance = self._check_precision(order.instrument, distance)
                SL_details = {"distance": str(distance), "type": "TRAILING_STOP_LOSS"}
            else:
                SL_type = "stopLossOnFill"
                SL_details = {"price": str(price)}

            stop_loss_order = {SL_type: SL_details}

        else:
            stop_loss_order = {}

        return stop_loss_order

    def _get_take_profit_details(self, order: Order) -> dict:
        """Constructs take profit details dictionary."""
        self._check_connection()
        if order.take_profit is not None:
            price = self._check_precision(order.instrument, order.take_profit)
            take_profit_details = {"price": str(price)}
        else:
            take_profit_details = None

        return take_profit_details

    def _check_response(self, response):
        """Checks API response (currently only for placing orders)."""
        if response.status != 201:
            message = response.body["errorMessage"]
        else:
            message = "Success."

        output = {"Status": response.status, "Message": message}
        # TODO - print errors
        return output

    def _close_position(self, instrument, long_units=None, short_units=None, **kwargs):
        """Closes all open positions on an instrument."""
        self._check_connection()
        # Check if the position is long or short
        # Temp code to close all positions
        # Close all long units
        response = self.api.position.close(
            accountID=self.ACCOUNT_ID, instrument=instrument, longUnits="ALL"
        )

        # Close all short units
        response = self.api.position.close(
            accountID=self.ACCOUNT_ID, instrument=instrument, shortUnits="ALL"
        )

        # TODO - the code below makes no sense currently; specifically,
        # position.long.Units ????

        # open_position = self.get_open_positions(instrument)

        # if len(open_position) > 0:
        #     position = open_position['position']

        #     if long_units is None:
        #         long_units  = position.long.units
        #     if short_units is None:
        #         short_units = position.short.units

        #     if long_units > 0:
        #         response = self.api.position.close(accountID=self.ACCOUNT_ID,
        #                                            instrument=instrument,
        #                                            longUnits="ALL")

        #     elif short_units > 0:
        #         response = self.api.position.close(accountID=self.ACCOUNT_ID,
        #                                            instrument=instrument,
        #                                            shortUnits="ALL")

        #     else:
        #         print("There is no current position with {} to close.".format(instrument))
        #         response = None
        # else:
        #     response = None

        return response

    def _get_precision(self, instrument: str):
        """Returns the allowable precision for a given pair."""
        self._check_connection()
        response = self.api.account.instruments(
            accountID=self.ACCOUNT_ID, instruments=instrument
        )
        precision = response.body["instruments"][0].displayPrecision
        return precision

    def _check_precision(self, instrument, price):
        """Modify a price based on required ordering precision for pair."""
        N = self._get_precision(instrument)
        corrected_price = round(price, N)
        return corrected_price

    def _get_order_book(self, instrument: str):
        """Returns the order book of the instrument specified."""
        orderbook = self.autodata.L2(instrument=instrument)
        return orderbook

    def _get_position_book(self, instrument: str):
        """Returns the position book of the instrument specified."""
        response = self.api.instrument.position_book(instrument)
        return response.body["positionBook"]

    def _get_price(self, instrument: str):
        """Returns the current price of the instrument."""
        response = self.api.pricing.get(
            accountID=self.ACCOUNT_ID, instruments=instrument
        )
        return response.body["prices"][0]
