diff --git a/Trading/Exchange/binance/binance_exchange.py b/Trading/Exchange/binance/binance_exchange.py index 980e35d35..0ddb233d3 100644 --- a/Trading/Exchange/binance/binance_exchange.py +++ b/Trading/Exchange/binance/binance_exchange.py @@ -13,21 +13,42 @@ # # You should have received a copy of the GNU Lesser General Public # License along with this library. +import octobot_commons.constants as common_constants import octobot_trading.enums as trading_enums 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' } + MARK_PRICE_IN_POSITION = True + + BINANCE_SYMBOL = "symbol" + + BINANCE_FUTURE_QUANTITY = "positionAmt" + BINANCE_FUTURE_UNREALIZED_PNL = "unRealizedProfit" + BINANCE_FUTURE_LIQUIDATION_PRICE = "liquidationPrice" + BINANCE_FUTURE_VALUE = "liquidationPrice" + + BINANCE_MARGIN_TYPE = "marginType" + BINANCE_MARGIN_TYPE_ISOLATED = "ISOLATED" + BINANCE_MARGIN_TYPE_CROSSED = "CROSSED" + + BINANCE_FUNDING_RATE = "fundingRate" + BINANCE_LAST_FUNDING_TIME = "fundingTime" + BINANCE_FUNDING_DURATION = 8 * common_constants.HOURS_TO_SECONDS + + BINANCE_TIME = "time" BINANCE_MARK_PRICE = "markPrice" + BINANCE_ENTRY_PRICE = "entryPrice" @classmethod def get_name(cls): @@ -98,3 +119,116 @@ 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_funding_rate(self, symbol: str): + return (await self.get_funding_rate_history(symbol=symbol, limit=1))[-1] + + async def get_funding_rate_history(self, symbol: str, limit: int = 100) -> list: + return [ + self.parse_funding(funding_rate_dict) + for funding_rate_dict in (await self.connector.client.fapiPublic_get_fundingrate( + {self.BINANCE_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( + {self.BINANCE_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( + { + self.BINANCE_SYMBOL: self.get_exchange_pair(symbol), + self.BINANCE_MARGIN_TYPE: 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.MARGIN_TYPE.value: + position_dict.get(self.BINANCE_MARGIN_TYPE, None), + 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.get_uniform_timestamp( + 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: + last_funding_time = self.connector.get_uniform_timestamp( + self.connector.client.safe_float(funding_dict, self.BINANCE_LAST_FUNDING_TIME)) + funding_dict = { + trading_enums.ExchangeConstantsFundingColumns.LAST_FUNDING_TIME.value: last_funding_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: + last_funding_time + self.BINANCE_FUNDING_DURATION + } + 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 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.