Skip to content
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

[RestExchange] add option to edit_order_by_replacing #820

Open
wants to merge 2 commits 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
54 changes: 51 additions & 3 deletions octobot_trading/exchanges/types/rest_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class RestExchange(abstract_exchange.AbstractExchange):
# should be fetched using recent trades.
REQUIRE_CLOSED_ORDERS_FROM_RECENT_TRADES = False # set True when get_closed_orders is not supported
ALLOW_TRADES_FROM_CLOSED_ORDERS = False # set True when get_my_recent_trades should use get_closed_orders
SUPPORTS_ORDER_EDITING = True # set True if the exchange supports editing orders by api
DUMP_INCOMPLETE_LAST_CANDLE = False # set True in tentacle when the exchange can return incomplete last candles
# Set True when exchange is not returning empty position details when fetching a position with a specified symbol
# Exchange will then fallback to self.get_mocked_empty_position when having get_position returning None
Expand Down Expand Up @@ -154,9 +155,15 @@ async def edit_order(self, order_id: str, order_type: enums.TraderOrderType, sym
side = None if side is None else side.value
params = {} if params is None else params
params.update(self.exchange_manager.exchange_backend.get_orders_parameters(None))
edited_order = await self._edit_order(order_id, order_type, symbol, quantity=float_quantity,
price=float_price, stop_price=float_stop_price, side=side,
current_price=float_current_price, params=params)
if self.SUPPORTS_ORDER_EDITING:
edited_order = await self._edit_order(order_id, order_type, symbol, quantity=float_quantity,
price=float_price, stop_price=float_stop_price, side=side,
current_price=float_current_price, params=params)
else:
# use edit order by replacing instead
edited_order = await self._edit_order_by_replacing(order_id, order_type, symbol, quantity=float_quantity,
price=float_price, stop_price=float_stop_price, side=side,
current_price=float_current_price)
order = await self._verify_order(edited_order, order_type, symbol, price, side)
return order
return None
Expand All @@ -168,6 +175,47 @@ async def _edit_order(self, order_id: str, order_type: enums.TraderOrderType, sy
quantity, price, stop_price, side,
current_price, params)

async def _edit_order_by_replacing(
self,
order_id: str,
order_type: enums.TraderOrderType,
symbol: str,
quantity: decimal.Decimal,
price: decimal.Decimal,
stop_price: decimal.Decimal = None,
side: enums.TradeOrderSide = None,
current_price: float = None,
):
# Can be used if api doesnt have an endpoint to edit orders
# see binance USD m as an example
order_to_cancel = (
self.exchange_manager.exchange_personal_data.orders_manager.get_order(
order_id
)
)
await self.cancel_order(order_id=order_id, symbol=symbol)
await order_to_cancel.state.wait_for_terminate(
constants.INDIVIDUAL_ORDER_SYNC_TIMEOUT
)
replaced_order = await self.create_order(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we cancel the order first to free funds ? We should also wait for the order to be completely cancel as is trader.cancel_order() I think

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would have worked with some order types on some exchanges, but I reversed it anyway.
I didnt had a chance to test this code yet tho. I will let you know once I tested it.

order_type,
symbol,
quantity,
price=stop_price or price,
stop_price=stop_price,
side=side,
current_price=current_price,
reduce_only=order_to_cancel.reduce_only,
)
if replaced_order:
return replaced_order
raise RuntimeError(
"Not able to edit a order by replacing. The new order was not created, "
f"while the original order got already cancelled. ({order_id} - "
f"{order_type} - {symbol} - quantity: {quantity} - "
f"price: {price} - {side})"
)

@contextlib.asynccontextmanager
async def _order_operation(self, order_type, symbol, quantity, price, stop_price):
try:
Expand Down
9 changes: 9 additions & 0 deletions tests/exchanges/traders/test_trader.py
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,15 @@ async def test_parse_exchange_order_to_order_instance(self):

async def test_edit_order(self):
_, exchange_manager, trader_inst = await self.init_default()
await self._test_edit_order(exchange_manager, trader_inst)

async def test_edit_order_by_replacing(self):
_, exchange_manager, trader_inst = await self.init_default()
exchange_manager.exchange.SUPPORTS_ORDER_EDITING = False
# TODO test on real exchange
await self._test_edit_order(exchange_manager, trader_inst)

async def _test_edit_order(self, exchange_manager, trader_inst):
portfolio_manager = exchange_manager.exchange_personal_data.portfolio_manager
currency, market = commons_symbols.parse_symbol(self.DEFAULT_SYMBOL).base_and_quote()
assert portfolio_manager.portfolio.portfolio[currency].available == decimal.Decimal(10)
Expand Down