Skip to content

[Position] fix position quantity size issues #797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions octobot_trading/exchanges/traders/trader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions octobot_trading/personal_data/positions/position.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
37 changes: 13 additions & 24 deletions octobot_trading/personal_data/positions/position.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
9 changes: 6 additions & 3 deletions tests/exchanges/traders/test_trader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tests/personal_data/orders/test_order_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions tests/personal_data/positions/test_position.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
4 changes: 2 additions & 2 deletions tests/personal_data/positions/test_position_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)


Expand Down
6 changes: 3 additions & 3 deletions tests/personal_data/trades/test_trade_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down