Skip to content
Merged
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
33 changes: 22 additions & 11 deletions Trading/Mode/index_trading_mode/index_trading.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ async def _buy_coin(self, symbol, ideal_amount) -> list:

class IndexTradingModeProducer(trading_modes.AbstractTradingModeProducer):
REFRESH_INTERVAL = "refresh_interval"
CANCEL_OPEN_ORDERS = "cancel_open_orders"
REBALANCE_TRIGGER_MIN_PERCENT = "rebalance_trigger_min_percent"
SELL_UNINDEXED_TRADED_COINS = "sell_unindexed_traded_coins"
INDEX_CONTENT = "index_content"
Expand Down Expand Up @@ -323,6 +324,8 @@ async def ensure_index(self):
f"{len(self.trading_mode.indexed_coins)} coins: {self.trading_mode.indexed_coins} with reference market: "
f"{self.exchange_manager.exchange_personal_data.portfolio_manager.reference_market}"
)
if self.trading_mode.cancel_open_orders:
await self.cancel_traded_pairs_open_orders_if_any()
is_rebalance_required, rebalance_details = self._get_rebalance_details()
if is_rebalance_required:
await self._trigger_rebalance(rebalance_details)
Expand Down Expand Up @@ -502,6 +505,18 @@ def get_channels_registration(self):
)
return topics

async def cancel_traded_pairs_open_orders_if_any(self):
if symbol_open_orders := [
order
for order in self.exchange_manager.exchange_personal_data.orders_manager.get_open_orders()
if order.symbol in self.exchange_manager.exchange_config.traded_symbol_pairs
]:
self.logger.info(
f"Cancelling {len(symbol_open_orders)} open orders"
)
for order in symbol_open_orders:
await self.trading_mode.cancel_order(order)


class IndexTradingMode(trading_modes.AbstractTradingMode):
MODE_PRODUCER_CLASSES = [IndexTradingModeProducer]
Expand All @@ -515,6 +530,7 @@ def __init__(self, config, exchange_manager):
self.rebalance_trigger_min_ratio = decimal.Decimal("0.05") # 5%
self.ratio_per_asset = {}
self.sell_unindexed_traded_coins = True
self.cancel_open_orders = True
self.total_ratio_per_asset = trading_constants.ZERO
self.indexed_coins = []

Expand All @@ -538,6 +554,12 @@ def init_user_inputs(self, inputs: dict) -> None:
title="Rebalance cap: maximum allowed percent holding of a coin beyond initial ratios before "
"triggering a rebalance.",
))) / trading_constants.ONE_HUNDRED
self.cancel_open_orders = float(self.UI.user_input(
IndexTradingModeProducer.CANCEL_OPEN_ORDERS, commons_enums.UserInputTypes.BOOLEAN,
self.cancel_open_orders, inputs,
title="Cancel open orders: When enabled, open orders of the index trading pairs will be canceled to free "
"funds and invest in the index content.",
))
self.sell_unindexed_traded_coins = trading_config.get(
IndexTradingModeProducer.SELL_UNINDEXED_TRADED_COINS,
self.sell_unindexed_traded_coins
Expand Down Expand Up @@ -689,17 +711,6 @@ def get_current_state(self) -> tuple:
async def single_exchange_process_optimize_initial_portfolio(
self, sellable_assets: list, target_asset: str, tickers: dict
) -> list:
symbol_open_orders = [
order
for order in self.exchange_manager.exchange_personal_data.orders_manager.get_open_orders()
if order.symbol in self.exchange_manager.exchange_config.traded_symbol_pairs
]
self.logger.info(
f"Optimizing portfolio: cancelling {len(symbol_open_orders)} open orders, selling {sellable_assets} "
f"to buy {target_asset}"
)
for order in symbol_open_orders:
await self.cancel_order(order)
Comment on lines -692 to -702
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 still do that for "entire portfolio" bots?

return await trading_modes.convert_assets_to_target_asset(
self, sellable_assets, target_asset, tickers
)
35 changes: 30 additions & 5 deletions Trading/Mode/index_trading_mode/tests/test_index_trading_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,7 @@ async def test_single_exchange_process_optimize_initial_portfolio(tools):

orders = await mode.single_exchange_process_optimize_initial_portfolio(["BTC", "ETH"], "USDT", {})
convert_assets_to_target_asset_mock.assert_called_once_with(mode, ["BTC", "ETH"], "USDT", {})
assert cancel_order_mock.call_count == 2
assert cancel_order_mock.mock_calls[0].args[0] is open_order_1
assert cancel_order_mock.mock_calls[1].args[0] is open_order_2
cancel_order_mock.assert_not_called()
assert orders == ["order_1"]
convert_assets_to_target_asset_mock.reset_mock()

Expand Down Expand Up @@ -344,7 +342,7 @@ async def test_ohlcv_callback(tools):
mode, producer, consumer, trader = await _init_mode(tools, _get_config(tools, update))
current_time = time.time()
with mock.patch.object(producer, "ensure_index", mock.AsyncMock()) as ensure_index_mock, \
mock.patch.object(producer, "_notify_if_missing_too_many_coins", mock.Mock()) \
mock.patch.object(producer, "_notify_if_missing_too_many_coins", mock.Mock()) \
as _notify_if_missing_too_many_coins_mock:
with mock.patch.object(
trader.exchange_manager.exchange, "get_exchange_current_time", mock.Mock(return_value=current_time)
Expand Down Expand Up @@ -410,7 +408,9 @@ async def test_ensure_index(tools):
mode, producer, consumer, trader = await _init_mode(tools, _get_config(tools, update))
with mock.patch.object(
producer, "_wait_for_symbol_prices_and_profitability_init", mock.AsyncMock()
) as _wait_for_symbol_prices_and_profitability_init_mock:
) as _wait_for_symbol_prices_and_profitability_init_mock, \
mock.patch.object(producer, "cancel_traded_pairs_open_orders_if_any", mock.AsyncMock()) \
as _cancel_traded_pairs_open_orders_if_any:
with mock.patch.object(producer, "_trigger_rebalance", mock.AsyncMock()) as _trigger_rebalance_mock:
with mock.patch.object(
producer, "_get_rebalance_details", mock.Mock(return_value=(False, {}))
Expand All @@ -419,23 +419,48 @@ async def test_ensure_index(tools):
assert producer.last_activity == octobot_trading.modes.TradingModeActivity(
index_trading.IndexActivity.REBALANCING_SKIPPED
)
_cancel_traded_pairs_open_orders_if_any.assert_called_once()
_cancel_traded_pairs_open_orders_if_any.reset_mock()
_wait_for_symbol_prices_and_profitability_init_mock.assert_called_once()
_wait_for_symbol_prices_and_profitability_init_mock.reset_mock()
_get_rebalance_details_mock.assert_called_once()
_trigger_rebalance_mock.assert_not_called()
with mock.patch.object(
producer, "_get_rebalance_details", mock.Mock(return_value=(True, {"plop": 1}))
) as _get_rebalance_details_mock:
producer.trading_mode.cancel_open_orders = False
await producer.ensure_index()
assert producer.last_activity == octobot_trading.modes.TradingModeActivity(
index_trading.IndexActivity.REBALANCING_DONE, {"plop": 1}
)
_wait_for_symbol_prices_and_profitability_init_mock.assert_called_once()
_wait_for_symbol_prices_and_profitability_init_mock.reset_mock()
_get_rebalance_details_mock.assert_called_once()
_cancel_traded_pairs_open_orders_if_any.assert_not_called()
_trigger_rebalance_mock.assert_called_once_with({"plop": 1})


async def test_cancel_traded_pairs_open_orders_if_any(tools):
update = {}
mode, producer, consumer, trader = await _init_mode(tools, _get_config(tools, update))
orders = [
mock.Mock(symbol="BTC/USDT"),
mock.Mock(symbol="BTC/USDT"),
mock.Mock(symbol="ETH/USDT"),
mock.Mock(symbol="DOGE/USDT"),
]
with mock.patch.object(
trader.exchange_manager.exchange_personal_data.orders_manager, "get_open_orders", mock.Mock(return_value=orders)
) as get_open_orders_mock, \
mock.patch.object(mode, "cancel_order", mock.AsyncMock()) \
as cancel_order_mock:
await producer.cancel_traded_pairs_open_orders_if_any()
get_open_orders_mock.assert_called_once()
assert cancel_order_mock.call_count == 2
assert cancel_order_mock.mock_calls[0].args[0] is orders[0]
assert cancel_order_mock.mock_calls[1].args[0] is orders[1]


async def test_trigger_rebalance(tools):
update = {}
mode, producer, consumer, trader = await _init_mode(tools, _get_config(tools, update))
Expand Down