diff --git a/octobot_trading/exchanges/traders/trader.py b/octobot_trading/exchanges/traders/trader.py index 46e350974..91fdc8470 100644 --- a/octobot_trading/exchanges/traders/trader.py +++ b/octobot_trading/exchanges/traders/trader.py @@ -730,11 +730,15 @@ async def set_position_mode(self, symbol, position_mode): is_hedge=position_mode is enums.PositionMode.HEDGE ) - def _has_open_position(self, symbol): + def _has_open_position(self, symbol) -> bool: """ Checks if open position exists for :symbol: :param symbol: the position symbol :return: True if open position for :symbol: exists """ - return len(self.exchange_manager.exchange_personal_data.positions_manager.get_symbol_positions( - symbol=symbol)) != 0 + for position in self.exchange_manager.exchange_personal_data.positions_manager.get_symbol_positions( + symbol=symbol + ): + if position.size: + return True + return False diff --git a/octobot_trading/personal_data/positions/position.pxd b/octobot_trading/personal_data/positions/position.pxd index a738887af..d5a4b3620 100644 --- a/octobot_trading/personal_data/positions/position.pxd +++ b/octobot_trading/personal_data/positions/position.pxd @@ -41,7 +41,7 @@ cdef class Position(util.Initializable): cdef public object exit_price cdef public object mark_price cdef public object liquidation_price - cdef public object quantity + cdef public object single_contract_value cdef public object size cdef public object already_reduced_size cdef public object value @@ -65,7 +65,7 @@ cdef class Position(util.Initializable): object entry_price, object mark_price, object liquidation_price, - object quantity, + object single_contract_value, object size, object value, object initial_margin, diff --git a/octobot_trading/personal_data/positions/position.py b/octobot_trading/personal_data/positions/position.py index 26659e965..8fab8f961 100644 --- a/octobot_trading/personal_data/positions/position.py +++ b/octobot_trading/personal_data/positions/position.py @@ -61,7 +61,7 @@ def __init__(self, trader, symbol_contract): self.fee_to_close = constants.ZERO # Size - self.quantity = constants.ZERO + self.single_contract_value = constants.ONE self.size = constants.ZERO self.already_reduced_size = constants.ZERO self.value = constants.ZERO @@ -120,7 +120,7 @@ def _should_change(self, original_value, new_value): def _update(self, position_id, symbol, currency, market, timestamp, entry_price, mark_price, liquidation_price, - quantity, size, value, initial_margin, + single_contract_value, size, value, initial_margin, unrealized_pnl, realised_pnl, fee_to_close, status=None): changed: bool = False @@ -141,8 +141,8 @@ def _update(self, position_id, symbol, currency, market, timestamp, self.creation_time = self.exchange_manager.exchange.get_uniformized_timestamp(timestamp) self.timestamp = self.creation_time - if self._should_change(self.quantity, quantity): - self.quantity = quantity + if self._should_change(self.single_contract_value, single_contract_value): + self.single_contract_value = single_contract_value changed = True if self._should_change(self.size, size): @@ -186,7 +186,6 @@ def _update(self, position_id, symbol, currency, market, timestamp, if self._should_change(self.status.value, status): self.status = enums.PositionStatus(status) - self._update_quantity_or_size_if_necessary() # update side after quantity as it relies on self.quantity self._update_side(not entry_price) self._update_prices_if_necessary(mark_price) @@ -408,7 +407,6 @@ def _update_size(self, size_update, realised_pnl_update=constants.ZERO, size_update, is_closing=self._is_update_closing(size_update), update_price=self.mark_price, trigger_source=trigger_source) self._check_and_update_size(size_update) - self._update_quantity() self._update_side(True) if self.exchange_manager.is_simulated: margin_update = self._update_initial_margin() @@ -463,19 +461,10 @@ def _check_and_update_size(self, size_update): self.size += size_update def _update_quantity_or_size_if_necessary(self): - """ - Set quantity from size if quantity is not set and size is set or update size - """ - if self.quantity == constants.ZERO and self.size != constants.ZERO: - self._update_quantity() - elif self.size == constants.ZERO and self.quantity != constants.ZERO: - self.size = self.quantity * self.symbol_contract.current_leverage + raise NotImplementedError("_update_quantity_or_size_if_necessary not implemented") def _update_quantity(self): - """ - Update position quantity from position quantity - """ - self.quantity = self.size / self.symbol_contract.current_leverage + raise NotImplementedError("_update_quantity not implemented") def update_value(self): raise NotImplementedError("update_value not implemented") @@ -647,7 +636,7 @@ def is_short(self): return self.side is enums.PositionSide.SHORT def is_idle(self): - return self.quantity == constants.ZERO + return self.size == constants.ZERO def get_quantity_to_close(self): """ @@ -713,7 +702,7 @@ def update_from_raw(self, raw_position): mark_price=raw_position.get(enums.ExchangeConstantsPositionColumns.MARK_PRICE.value, constants.ZERO), liquidation_price=raw_position.get(enums.ExchangeConstantsPositionColumns.LIQUIDATION_PRICE.value, constants.ZERO), - quantity=raw_position.get(enums.ExchangeConstantsPositionColumns.QUANTITY.value, constants.ZERO), + single_contract_value=raw_position.get(enums.ExchangeConstantsPositionColumns.QUANTITY.value, constants.ONE), size=raw_position.get(enums.ExchangeConstantsPositionColumns.SIZE.value, constants.ZERO), value=raw_position.get(enums.ExchangeConstantsPositionColumns.NOTIONAL.value, constants.ZERO), initial_margin=raw_position.get(enums.ExchangeConstantsPositionColumns.INITIAL_MARGIN.value, @@ -734,7 +723,7 @@ def to_dict(self): enums.ExchangeConstantsPositionColumns.STATUS.value: self.status.value, enums.ExchangeConstantsPositionColumns.TIMESTAMP.value: self.timestamp, enums.ExchangeConstantsPositionColumns.SIDE.value: self.side.value, - enums.ExchangeConstantsPositionColumns.QUANTITY.value: self.quantity, + enums.ExchangeConstantsPositionColumns.QUANTITY.value: self.single_contract_value, enums.ExchangeConstantsPositionColumns.SIZE.value: self.size, enums.ExchangeConstantsPositionColumns.NOTIONAL.value: self.value, enums.ExchangeConstantsPositionColumns.INITIAL_MARGIN.value: self.initial_margin, @@ -778,11 +767,11 @@ def _update_side(self, reset_entry_price): """ if self.symbol_contract.is_one_way_position_mode() or self.side is enums.PositionSide.UNKNOWN: changed_side = False - if self.quantity > constants.ZERO: + if self.size > constants.ZERO: if self.side is not enums.PositionSide.LONG: self.side = enums.PositionSide.LONG changed_side = True - elif self.quantity < constants.ZERO: + elif self.size < constants.ZERO: if self.side is not enums.PositionSide.SHORT: self.side = enums.PositionSide.SHORT changed_side = True @@ -822,7 +811,7 @@ async def reset(self): self.entry_price = constants.ZERO self.exit_price = constants.ZERO self.mark_price = constants.ZERO - self.quantity = constants.ZERO + self.single_contract_value = constants.ONE self.size = constants.ZERO self.already_reduced_size = constants.ZERO self.value = constants.ZERO @@ -858,7 +847,7 @@ def restore(self, other_position): self.mark_price = other_position.mark_price self.liquidation_price = other_position.liquidation_price self.fee_to_close = other_position.fee_to_close - self.quantity = other_position.quantity + self.single_contract_value = other_position.single_contract_value self.size = other_position.size self.already_reduced_size = other_position.already_reduced_size self.value = other_position.value diff --git a/octobot_trading/personal_data/positions/types/inverse_position.py b/octobot_trading/personal_data/positions/types/inverse_position.py index 4affc621c..b0b67874e 100644 --- a/octobot_trading/personal_data/positions/types/inverse_position.py +++ b/octobot_trading/personal_data/positions/types/inverse_position.py @@ -26,7 +26,7 @@ def update_value(self): Notional value = CONTRACT_QUANTITY / MARK_PRICE """ try: - self.value = self.size / self.mark_price + self.value = self.size * self.single_contract_value / self.mark_price except (decimal.DivisionByZero, decimal.InvalidOperation): self.value = constants.ZERO diff --git a/octobot_trading/personal_data/positions/types/linear_position.py b/octobot_trading/personal_data/positions/types/linear_position.py index a770f98fd..29c54a16a 100644 --- a/octobot_trading/personal_data/positions/types/linear_position.py +++ b/octobot_trading/personal_data/positions/types/linear_position.py @@ -25,7 +25,7 @@ def update_value(self): """ Notional value = CONTRACT_QUANTITY * MARK_PRICE """ - self.value = self.size * self.mark_price + self.value = self.size * self.single_contract_value * self.mark_price def get_unrealized_pnl(self, price): """ diff --git a/tests/exchanges/traders/test_trader.py b/tests/exchanges/traders/test_trader.py index de1242292..85f83d3d3 100644 --- a/tests/exchanges/traders/test_trader.py +++ b/tests/exchanges/traders/test_trader.py @@ -1047,9 +1047,11 @@ async def test_set_position_mode(future_trader_simulator_with_default_linear): position_inst = LinearPosition(trader_inst, contract) await position_inst.initialize() - position_inst.update_from_raw( + position_inst.update_from_raw( { - ExchangeConstantsPositionColumns.SYMBOL.value: DEFAULT_FUTURE_SYMBOL + ExchangeConstantsPositionColumns.SYMBOL.value: DEFAULT_FUTURE_SYMBOL, + ExchangeConstantsPositionColumns.SIZE.value: decimal.Decimal("1"), + } ) exchange_manager_inst.exchange_personal_data.positions_manager.upsert_position_instance(position_inst) @@ -1071,7 +1073,8 @@ async def test__has_open_position(future_trader_simulator_with_default_linear): await position_inst.initialize() position_inst.update_from_raw( { - ExchangeConstantsPositionColumns.SYMBOL.value: DEFAULT_FUTURE_SYMBOL + ExchangeConstantsPositionColumns.SYMBOL.value: DEFAULT_FUTURE_SYMBOL, + ExchangeConstantsPositionColumns.SIZE.value: decimal.Decimal("1"), } ) exchange_manager_inst.exchange_personal_data.positions_manager.upsert_position_instance(position_inst) diff --git a/tests/personal_data/orders/test_order_util.py b/tests/personal_data/orders/test_order_util.py index 222f72abe..a1424bbd5 100644 --- a/tests/personal_data/orders/test_order_util.py +++ b/tests/personal_data/orders/test_order_util.py @@ -446,7 +446,7 @@ async def test_ensure_orders_relevancy_with_positions(future_trader_simulator_wi position.side = "other_other_side" trader_mock.cancel_order.assert_not_called() # with a non-0 quantity position - position.quantity = decimal.Decimal("2") + position.size = decimal.Decimal("2") # with position parameter async with personal_data.ensure_orders_relevancy(position=position): # changing side diff --git a/tests/personal_data/positions/test_position.py b/tests/personal_data/positions/test_position.py index 54c82a545..6f8d0d7d7 100644 --- a/tests/personal_data/positions/test_position.py +++ b/tests/personal_data/positions/test_position.py @@ -155,24 +155,24 @@ async def test_update_entry_price_when_switching_side_on_one_way(btc_usdt_future async def test_update_update_quantity(btc_usdt_future_trader_simulator_with_default_linear): config, exchange_manager_inst, trader_inst, default_contract, position_inst = btc_usdt_future_trader_simulator_with_default_linear - assert position_inst.quantity == constants.ZERO + assert position_inst.size == constants.ZERO quantity = decimal_random_quantity(1) await position_inst.update(update_size=quantity) - assert position_inst.quantity == quantity + assert position_inst.size == quantity async def test_invalid_update(btc_usdt_future_trader_simulator_with_default_linear): config, exchange_manager_inst, trader_inst, default_contract, position_inst = btc_usdt_future_trader_simulator_with_default_linear portfolio = exchange_manager_inst.exchange_personal_data.portfolio_manager.portfolio.get_currency_portfolio("BTC") - assert position_inst.quantity == constants.ZERO + assert position_inst.size == constants.ZERO async def _ensure_no_position_change(mark_price, update_size): with pytest.raises(errors.PortfolioNegativeValueError): await position_inst.update(mark_price=mark_price, update_size=update_size) # Did not affect position or portfolio data - assert position_inst.quantity == position_inst.entry_price == position_inst.mark_price == position_inst.size == \ + assert position_inst.size == position_inst.entry_price == position_inst.mark_price == position_inst.size == \ constants.ZERO assert portfolio.available == decimal.Decimal('10') assert portfolio.total == decimal.Decimal('10') diff --git a/tests/personal_data/positions/test_position_factory.py b/tests/personal_data/positions/test_position_factory.py index a1374c448..6ddf8e780 100644 --- a/tests/personal_data/positions/test_position_factory.py +++ b/tests/personal_data/positions/test_position_factory.py @@ -41,7 +41,7 @@ async def test_create_position_instance_from_raw(future_trader_simulator_with_de get_default_future_inverse_contract()) inverse_position_open = personal_data.create_position_instance_from_raw(trader_inst, { enums.ExchangeConstantsPositionColumns.SYMBOL.value: DEFAULT_FUTURE_SYMBOL, - enums.ExchangeConstantsPositionColumns.QUANTITY.value: position_quantity + enums.ExchangeConstantsPositionColumns.SIZE.value: position_quantity }) assert position.symbol == DEFAULT_FUTURE_SYMBOL assert position.market == "USDT" @@ -50,7 +50,7 @@ async def test_create_position_instance_from_raw(future_trader_simulator_with_de assert isinstance(position, personal_data.LinearPosition) assert inverse_position_open.status == enums.PositionStatus.OPEN - assert inverse_position_open.quantity == position_quantity + assert inverse_position_open.size == position_quantity assert isinstance(inverse_position_open, personal_data.InversePosition) diff --git a/tests/personal_data/trades/test_trade_factory.py b/tests/personal_data/trades/test_trade_factory.py index 6ffd4a8ac..4157d568d 100644 --- a/tests/personal_data/trades/test_trade_factory.py +++ b/tests/personal_data/trades/test_trade_factory.py @@ -191,11 +191,11 @@ async def test_create_trade_from_order(self): assert trade.origin_order_id == '362550114' assert trade.trade_type == TraderOrderType.SELL_MARKET assert trade.symbol == 'UNI/USDT' - assert trade.total_cost == ZERO + assert trade.total_cost == decimal.Decimal("202338000") assert trade.executed_quantity == decimal.Decimal("44964.0") assert trade.origin_quantity == decimal.Decimal("44964.0") - assert trade.origin_price == ZERO - assert trade.executed_price == ZERO + assert trade.origin_price == decimal.Decimal("4500") + assert trade.executed_price == decimal.Decimal("4500") assert trade.status == OrderStatus.FILLED assert trade.executed_time == 1637579281.377 assert trade.is_closing_order is True