diff --git a/Trading/Exchange/binance/binance_exchange.py b/Trading/Exchange/binance/binance_exchange.py index 980e35d35..a30b6671f 100644 --- a/Trading/Exchange/binance/binance_exchange.py +++ b/Trading/Exchange/binance/binance_exchange.py @@ -17,17 +17,32 @@ import octobot_trading.exchanges as exchanges -class Binance(exchanges.SpotCCXTExchange): +class Binance(exchanges.SpotCCXTExchange, exchanges.FutureCCXTExchange): DESCRIPTION = "" BUY_STR = "BUY" SELL_STR = "SELL" ACCOUNTS = { - trading_enums.AccountTypes.CASH: 'cash' + trading_enums.AccountTypes.CASH: 'cash', + trading_enums.AccountTypes.FUTURE: 'future' } + FUNDING_WITH_MARK_PRICE = True + + BINANCE_FUTURE_QUANTITY = "positionAmt" + BINANCE_FUTURE_UNREALIZED_PNL = "unRealizedProfit" + BINANCE_FUTURE_LIQUIDATION_PRICE = "liquidationPrice" + BINANCE_FUTURE_VALUE = "liquidationPrice" + + BINANCE_MARGIN_TYPE_ISOLATED = "ISOLATED" + BINANCE_MARGIN_TYPE_CROSSED = "CROSSED" + + BINANCE_FUNDING_RATE = "lastFundingRate" + BINANCE_NEXT_FUNDING_TIME = "nextFundingTime" + BINANCE_TIME = "time" BINANCE_MARK_PRICE = "markPrice" + BINANCE_ENTRY_PRICE = "entryPrice" @classmethod def get_name(cls): @@ -98,3 +113,120 @@ def _fill_order_missing_data(self, order, trades): if not order[trading_enums.ExchangeConstantsOrderColumns.FEE.value] and order_id in trades: order[trading_enums.ExchangeConstantsOrderColumns.FEE.value] = \ trades[order_id][trading_enums.ExchangeConstantsOrderColumns.FEE.value] + + async def get_open_positions(self) -> dict: + return [ + self.parse_position(position) + for position in await self.connector.client.fapiPrivate_get_positionrisk() + ] + + async def get_mark_price(self, symbol: str) -> dict: + return (await self.get_mark_price_and_funding(symbol))[0] + + async def get_funding_rate(self, symbol: str): + return (await self.get_mark_price_and_funding(symbol))[1] + + async def get_mark_price_and_funding(self, symbol: str) -> tuple: + return self._parse_mark_price_and_funding_dict(await self.connector.client.fapiPublic_get_premiumindex( + {"symbol": self.get_exchange_pair(symbol)})) + + async def get_funding_rate_history(self, symbol: str, limit: int = 1) -> list: + return [ + self.parse_funding(funding_rate_dict) + for funding_rate_dict in (await self.connector.client.fapiPublic_get_funding_rate( + {"symbol": self.get_exchange_pair(symbol), + "limit": limit})) + ] + + async def set_symbol_leverage(self, symbol: str, leverage: int): + await self.connector.client.fapiPrivate_post_leverage( + {"symbol": self.get_exchange_pair(symbol), + "leverage": leverage}) + + async def set_symbol_margin_type(self, symbol: str, isolated: bool): + await self.connector.client.fapiPrivate_post_marginType( + {"symbol": self.get_exchange_pair(symbol), + "marginType": self.BINANCE_MARGIN_TYPE_ISOLATED if isolated else self.BINANCE_MARGIN_TYPE_CROSSED}) + + def parse_position(self, position_dict): + try: + position_dict.update({ + trading_enums.ExchangeConstantsPositionColumns.SYMBOL.value: + self.get_pair_from_exchange( + position_dict[trading_enums.ExchangeConstantsPositionColumns.SYMBOL.value]), + trading_enums.ExchangeConstantsPositionColumns.ID.value: + self.connector.client.safe_string(position_dict, + trading_enums.ExchangeConstantsPositionColumns.ID.value, + position_dict[ + trading_enums.ExchangeConstantsPositionColumns.SYMBOL.value]), + trading_enums.ExchangeConstantsPositionColumns.QUANTITY.value: + self.connector.client.safe_float(position_dict, self.BINANCE_FUTURE_QUANTITY, 0), + trading_enums.ExchangeConstantsPositionColumns.VALUE.value: + self.calculate_position_value( + self.connector.client.safe_float( + position_dict, trading_enums.ExchangeConstantsPositionColumns.QUANTITY.value, 0), + self.connector.client.safe_float( + position_dict, trading_enums.ExchangeConstantsPositionColumns.MARK_PRICE.value, 1)), + trading_enums.ExchangeConstantsPositionColumns.MARGIN.value: + # TODO + self.connector.client.safe_float(position_dict, + trading_enums.ExchangeConstantsPositionColumns.MARGIN.value, + 0), + trading_enums.ExchangeConstantsPositionColumns.UNREALISED_PNL.value: + self.connector.client.safe_float(position_dict, self.BINANCE_FUTURE_UNREALIZED_PNL, 0), + trading_enums.ExchangeConstantsPositionColumns.REALISED_PNL.value: + # TODO + self.connector.client.safe_float(position_dict, + trading_enums.ExchangeConstantsPositionColumns.REALISED_PNL.value, + 0), + trading_enums.ExchangeConstantsPositionColumns.LIQUIDATION_PRICE.value: + self.connector.client.safe_float(position_dict, self.BINANCE_FUTURE_LIQUIDATION_PRICE, 0), + trading_enums.ExchangeConstantsPositionColumns.MARK_PRICE.value: + self.connector.client.safe_float(position_dict, self.BINANCE_MARK_PRICE, 0), + trading_enums.ExchangeConstantsPositionColumns.ENTRY_PRICE.value: + self.connector.client.safe_float(position_dict, self.BINANCE_ENTRY_PRICE, 0), + trading_enums.ExchangeConstantsPositionColumns.TIMESTAMP.value: + # TODO + self.connector.client.safe_float(position_dict, + trading_enums.ExchangeConstantsPositionColumns.TIMESTAMP.value, + self.connector.get_exchange_current_time()), + trading_enums.ExchangeConstantsPositionColumns.STATUS.value: + self.parse_position_status(self.connector.client.safe_string(position_dict, + trading_enums.ExchangeConstantsPositionColumns.STATUS.value, + default_value=trading_enums.PositionStatus.OPEN.value)), + trading_enums.ExchangeConstantsPositionColumns.SIDE.value: + self.parse_position_side(self.connector.client.safe_string(position_dict, + trading_enums.ExchangeConstantsPositionColumns.SIDE.value, + default_value=trading_enums.PositionSide.UNKNOWN.value)), + }) + except KeyError as e: + self.logger.error(f"Fail to parse position dict ({e})") + return position_dict + + def parse_funding(self, funding_dict, from_ticker=False): + try: + funding_dict = { + trading_enums.ExchangeConstantsFundingColumns.LAST_FUNDING_TIME.value: + self.parse_timestamp(funding_dict, self.BINANCE_TIME), + trading_enums.ExchangeConstantsFundingColumns.FUNDING_RATE.value: + self.connector.client.safe_float(funding_dict, self.BINANCE_FUNDING_RATE), + trading_enums.ExchangeConstantsFundingColumns.NEXT_FUNDING_TIME.value: + self.parse_timestamp(funding_dict, self.BINANCE_NEXT_FUNDING_TIME) + } + except KeyError as e: + self.logger.error(f"Fail to parse funding dict ({e})") + return funding_dict + + def parse_mark_price(self, mark_price_dict, from_ticker=False): + try: + mark_price_dict = { + trading_enums.ExchangeConstantsMarkPriceColumns.MARK_PRICE.value: + self.connector.client.safe_float(mark_price_dict, self.BINANCE_MARK_PRICE, 0) + } + except KeyError as e: + self.logger.error(f"Fail to parse mark_price dict ({e})") + return mark_price_dict + + def _parse_mark_price_and_funding_dict(self, mark_price_and_funding_dict): + return self.parse_mark_price(mark_price_and_funding_dict), \ + self.parse_funding(mark_price_and_funding_dict) diff --git a/Trading/Exchange/binance/resources/binance.md b/Trading/Exchange/binance/resources/binance.md index b13d19c60..696a5a8f2 100644 --- a/Trading/Exchange/binance/resources/binance.md +++ b/Trading/Exchange/binance/resources/binance.md @@ -1 +1 @@ -Binance is a SpotExchange adaptation for Binance exchange using the REST API. +Binance is a SpotExchange and FutureExchange adaptation for Binance exchange using the REST API.