Skip to content

Commit b1657bf

Browse files
committed
[Orders] fix partially fill channel notification
1 parent 6b28636 commit b1657bf

File tree

8 files changed

+47
-20
lines changed

8 files changed

+47
-20
lines changed

octobot_trading/exchanges/connectors/ccxt/ccxt_connector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -949,7 +949,7 @@ def get_ccxt_order_type(self, order_type: enums.TraderOrderType):
949949
return enums.TradeOrderType.MARKET.value
950950
raise RuntimeError(f"Unknown order type: {order_type}")
951951

952-
def get_trade_fee(self, symbol: str, order_type: enums.TraderOrderType, quantity, price, taker_or_maker):
952+
def get_trade_fee(self, symbol: str, order_type: enums.TraderOrderType, quantity, price, taker_or_maker) -> dict:
953953
fees = self.calculate_fees(symbol, order_type, quantity, price, taker_or_maker)
954954
fees[enums.FeePropertyColumns.IS_FROM_EXCHANGE.value] = False
955955
fees[enums.FeePropertyColumns.COST.value] = decimal.Decimal(

octobot_trading/exchanges/types/rest_exchange.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ async def cancel_order(
808808
) -> enums.OrderStatus:
809809
return await self.connector.cancel_order(exchange_order_id, symbol, order_type, **kwargs)
810810

811-
def get_trade_fee(self, symbol: str, order_type: enums.TraderOrderType, quantity, price, taker_or_maker):
811+
def get_trade_fee(self, symbol: str, order_type: enums.TraderOrderType, quantity, price, taker_or_maker) -> dict:
812812
return self.connector.get_trade_fee(symbol, order_type, quantity, price, taker_or_maker)
813813

814814
def get_fees(self, symbol):

octobot_trading/personal_data/orders/order.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -931,8 +931,9 @@ def update_from_raw(self, raw_order):
931931
)
932932
filled_price = decimal.Decimal(str(price))
933933
# set average price with real average price if available, use filled_price otherwise
934-
average_price = decimal.Decimal(str(raw_order.get(enums.ExchangeConstantsOrderColumns.AVERAGE.value, 0.0)
935-
or filled_price))
934+
average_price = decimal.Decimal(str(
935+
raw_order.get(enums.ExchangeConstantsOrderColumns.AVERAGE.value, 0.0) or filled_price
936+
))
936937

937938
return self.update(
938939
symbol=str(raw_order.get(enums.ExchangeConstantsOrderColumns.SYMBOL.value, None)),
@@ -1187,12 +1188,13 @@ def to_string(self):
11871188
)
11881189
trailing_profile = f"Trailing profile : {self.trailing_profile} | " if self.trailing_profile else ""
11891190
cancel_policy = f"Cancel policy : {self.cancel_policy} | " if self.cancel_policy else ""
1191+
filled_quantity = f" ({self.filled_quantity} Filled)" if self.filled_quantity else ""
11901192
return (
11911193
f"{inactive}{self.symbol} | "
11921194
f"{chained_order}"
11931195
f"{self.order_type.name if self.order_type is not None else 'Unknown'} | "
11941196
f"Price : {str(self.origin_price)} | "
1195-
f"Quantity : {str(self.origin_quantity)}{' (Reduce only)' if self.reduce_only else ''} | "
1197+
f"Quantity : {str(self.origin_quantity)}{filled_quantity}{' (Reduce only)' if self.reduce_only else ''} | "
11961198
f"State : {self.state.state.value if self.state is not None else 'Unknown'} | "
11971199
f"{trailing_profile}"
11981200
f"{cancel_policy}"

octobot_trading/personal_data/orders/order_state.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def get_logger(self):
7676
"""
7777
:return: the order logger
7878
"""
79-
return logging.get_logger(self.order.get_logger_name() if self.order is not None else
79+
return logging.get_logger(f"[{self.__class__.__name__}] {self.order.get_logger_name()}" if self.order is not None else
8080
f"{self.__class__.__name__}_without_order")
8181

8282
def log_event_message(self, state_message, error=None):

octobot_trading/personal_data/orders/states/fill_order_state.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,13 @@ async def on_refresh_successful(self):
7474
can also be still pending
7575
or be fully filled
7676
"""
77-
if self.order.status is enums.OrderStatus.PARTIALLY_FILLED:
78-
# TODO manage partially filled
79-
await self.update()
77+
self.get_logger().info(f"on_refresh_successful [{self.order.status}] for {self.order}")
78+
if self.order.is_partially_filled():
79+
self.get_logger().info(f"Partially filled order: {str(self.order)}")
80+
# notify order partially filled
81+
await self.order.exchange_manager.exchange_personal_data.handle_order_update_notification(
82+
self.order, enums.OrderUpdateType.STATE_CHANGE
83+
)
8084
elif self.order.status in constants.FILL_ORDER_STATUS_SCOPE:
8185
self.state = enums.OrderStates.FILLED
8286
await self.update()

octobot_trading/personal_data/orders/states/open_order_state.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import octobot_trading.enums as enums
1919
import octobot_trading.personal_data.orders.order_state as order_state
2020
import octobot_trading.personal_data.orders.states.order_state_factory as order_state_factory
21+
import octobot_trading.personal_data.portfolios.portfolio_util as portfolio_util
2122

2223

2324
class OpenOrderState(order_state.OrderState):
@@ -55,11 +56,16 @@ async def initialize_impl(self, forced=False) -> None:
5556
# update the availability of the currency in the portfolio if order is not
5657
# from exchange initialization (otherwise it's already taken into account in portfolio)
5758
portfolio_manager = self.order.exchange_manager.exchange_personal_data.portfolio_manager
58-
before_order_details = str(portfolio_manager.portfolio)
59+
before_order_details = portfolio_util.filter_empty_values(
60+
portfolio_util.portfolio_to_float(portfolio_manager.portfolio.portfolio)
61+
)
5962
portfolio_manager.refresh_portfolio_available_from_order(self.order, True)
63+
after_order_details = portfolio_util.filter_empty_values(
64+
portfolio_util.portfolio_to_float(portfolio_manager.portfolio.portfolio)
65+
)
6066
self.get_logger().debug(
61-
f"Updated portfolio available after new open order. "
62-
f"Before order: {before_order_details}. After order: {portfolio_manager.portfolio}"
67+
f"Updated [{self.order.exchange_manager.exchange_name}] portfolio available after new open order. "
68+
f"Before order: {before_order_details}. After order: {after_order_details}"
6369
)
6470

6571
return await super().initialize_impl()

octobot_trading/personal_data/portfolios/history/historical_asset_value.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def __contains__(self, item):
3333

3434
def __repr__(self):
3535
return f"{self.__class__.__name__} " \
36-
f"[timestamp: {HistoricalAssetValue}, _value_by_currency{self._value_by_currency}]"
36+
f"[timestamp: {self._timestamp}, _value_by_currency{self._value_by_currency}]"
3737

3838
def get(self, currency):
3939
return self._value_by_currency[currency]

tests/exchanges/test_exchange_builder.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,17 @@ async def test_build_trading_modes_if_required(exchange_manager):
3939

4040
# with trader simulator: will attempt to create a trading mode and fail (because of the None arg)
4141
builder.is_simulated()
42-
trading_mode_class = mock.Mock(
43-
get_is_using_trading_mode_on_exchange=mock.Mock(return_value=True),
44-
get_supported_exchange_types=mock.Mock(return_value=[]),
45-
return_value=mock.Mock(
46-
initialize=mock.AsyncMock(),
47-
),
48-
)
4942
with mock.patch.object(
5043
commons_authentication.Authenticator, "has_open_source_package", mock.Mock(return_value=False)
5144
) as has_open_source_package_mock:
45+
# get_is_using_trading_mode_on_exchange returns True
46+
trading_mode_class = mock.Mock(
47+
get_is_using_trading_mode_on_exchange=mock.Mock(return_value=True),
48+
get_supported_exchange_types=mock.Mock(return_value=[]),
49+
return_value=mock.Mock(
50+
initialize=mock.AsyncMock(),
51+
),
52+
)
5253
with pytest.raises(AttributeError):
5354
await builder._build_trading_modes_if_required(None, tentacles_setup_config)
5455
has_open_source_package_mock.assert_not_called()
@@ -57,7 +58,21 @@ async def test_build_trading_modes_if_required(exchange_manager):
5758
trading_mode_class.get_supported_exchange_types.assert_called_once()
5859
trading_mode_class.return_value.initialize.assert_awaited_once()
5960
has_open_source_package_mock.assert_called_once()
61+
trading_mode_class.reset_mock()
62+
trading_mode_class.get_is_using_trading_mode_on_exchange.reset_mock()
63+
trading_mode_class.get_supported_exchange_types.reset_mock()
64+
trading_mode_class.return_value.initialize.reset_mock()
65+
66+
# get_is_using_trading_mode_on_exchange returns False
67+
trading_mode_class.get_is_using_trading_mode_on_exchange.return_value = False
68+
await builder._build_trading_modes_if_required(trading_mode_class, tentacles_setup_config)
69+
trading_mode_class.get_is_using_trading_mode_on_exchange.assert_called_once_with(builder.exchange_name, tentacles_setup_config)
70+
trading_mode_class.get_supported_exchange_types.assert_not_called()
71+
trading_mode_class.return_value.initialize.assert_not_called()
72+
has_open_source_package_mock.assert_called_once()
73+
6074
# raised by default has_open_source_package (which should be overriden)
75+
trading_mode_class.get_is_using_trading_mode_on_exchange.return_value = True
6176
with pytest.raises(NotImplementedError):
6277
await builder._build_trading_modes_if_required(trading_mode_class, tentacles_setup_config)
6378

0 commit comments

Comments
 (0)