Skip to content

Commit cace663

Browse files
authored
Support orders without price or quantity (#103)
This add support for cancelled orders which do not have the price or quantity field set. Since the order field is still valid for cancelled orders and price and quantity fields are not designed as optional, we set the fields to `NaN`. In addition to that the headers of the CLI tool are fixed.
2 parents cfcfb51 + d28274c commit cace663

File tree

4 files changed

+79
-30
lines changed

4 files changed

+79
-30
lines changed

RELEASE_NOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@
1616

1717
## Bug Fixes
1818

19+
* Deal correctly with cancelled orders with no price or quantity
20+
1921
<!-- Here goes notable bug fixes that are worth a special mention or explanation -->

src/frequenz/client/electricity_trading/_types.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,8 +1010,16 @@ def from_pb(cls, order: electricity_trading_pb2.Order) -> Self:
10101010
delivery_period=DeliveryPeriod.from_pb(order.delivery_period),
10111011
type=OrderType.from_pb(order.type),
10121012
side=MarketSide.from_pb(order.side),
1013-
price=Price.from_pb(order.price),
1014-
quantity=Power.from_pb(order.quantity),
1013+
price=(
1014+
Price.from_pb(order.price)
1015+
if order.HasField("price")
1016+
else Price(Decimal("NaN"), Currency.UNSPECIFIED)
1017+
),
1018+
quantity=(
1019+
Power.from_pb(order.quantity)
1020+
if order.HasField("quantity")
1021+
else Power(Decimal("NaN"))
1022+
),
10151023
stop_price=(
10161024
Price.from_pb(order.stop_price)
10171025
if order.HasField("stop_price")
@@ -1308,8 +1316,11 @@ def from_pb(cls, order_detail: electricity_trading_pb2.OrderDetail) -> Self:
13081316
13091317
Returns:
13101318
OrderDetail object corresponding to the protobuf message.
1319+
1320+
Raises:
1321+
ValueError: If the order price and quantity are not specified for a non-canceled order.
13111322
"""
1312-
return cls(
1323+
od = cls(
13131324
order_id=order_detail.order_id,
13141325
order=Order.from_pb(order_detail.order),
13151326
state_detail=StateDetail.from_pb(order_detail.state_detail),
@@ -1321,6 +1332,19 @@ def from_pb(cls, order_detail: electricity_trading_pb2.OrderDetail) -> Self:
13211332
),
13221333
)
13231334

1335+
# Only cancelled orders are allowed to have missing price or quantity
1336+
missing_price_or_quantity = (
1337+
od.order.price.amount.is_nan()
1338+
or od.order.price.currency == Currency.UNSPECIFIED
1339+
or od.order.quantity.mw.is_nan()
1340+
)
1341+
if missing_price_or_quantity and od.state_detail.state != OrderState.CANCELED:
1342+
raise ValueError(
1343+
f"Price and quantity must be specified for a non-canceled order (`{od}`)."
1344+
)
1345+
1346+
return od
1347+
13241348
def to_pb(self) -> electricity_trading_pb2.OrderDetail:
13251349
"""Convert an OrderDetail object to protobuf OrderDetail.
13261350

src/frequenz/client/electricity_trading/cli/etrading.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ async def list_orders(
9797
"""
9898
client = Client(server_url=url, auth_key=key)
9999

100-
# print_header()
100+
print_order_header()
101101

102102
delivery_period = None
103103
# If delivery period is selected, list historical orders also
@@ -200,18 +200,18 @@ async def cancel_order(
200200
def print_trade_header() -> None:
201201
"""Print trade header in CSV format."""
202202
header = (
203-
"public_trade_id, "
204-
"execution_time, "
205-
"delivery_period_start, "
206-
"delivery_period_duration, "
207-
"buy_delivery_area_code, "
208-
"sell_delivery_area_code, "
209-
"buy_delivery_area_code_type, "
210-
"sell_delivery_area_code_type"
211-
"quantity_mw, "
212-
"price, "
213-
"currency, "
214-
"state, "
203+
"public_trade_id,"
204+
"execution_time,"
205+
"delivery_period_start,"
206+
"delivery_period_duration,"
207+
"buy_delivery_area_code,"
208+
"sell_delivery_area_code,"
209+
"buy_delivery_area_code_type,"
210+
"sell_delivery_area_code_type,"
211+
"quantity_mw,"
212+
"currency,"
213+
"price,"
214+
"state "
215215
)
216216
print(header)
217217

@@ -238,20 +238,20 @@ def print_trade(trade: PublicTrade) -> None:
238238
def print_order_header() -> None:
239239
"""Print order header in CSV format."""
240240
header = (
241-
"order_id, "
242-
"create_time, "
243-
"modification_time, "
244-
"delivery_period_start, "
245-
"delivery_period_duration"
246-
"delivery_area_code, "
247-
"delivery_area_code_type, "
248-
"order_type, "
249-
"quantity_mw, "
250-
"open_quantity_mw, "
251-
"side, "
252-
"currency, "
253-
"price, "
254-
"state, "
241+
"order_id,"
242+
"create_time,"
243+
"modification_time,"
244+
"delivery_period_start,"
245+
"delivery_period_duration,"
246+
"delivery_area_code,"
247+
"delivery_area_code_type,"
248+
"order_type,"
249+
"quantity_mw,"
250+
"open_quantity_mw,"
251+
"side,"
252+
"currency,"
253+
"price,"
254+
"state"
255255
)
256256
print(header)
257257

tests/test_types.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,29 @@ def test_order_detail_from_pb() -> None:
498498
)
499499

500500

501+
def test_order_detail_from_pb_missing_fields() -> None:
502+
"""Test the client order detail type conversion from protobuf with missing fields."""
503+
# Missing price
504+
od_pb1 = electricity_trading_pb2.OrderDetail()
505+
od_pb1.CopyFrom(ORDER_DETAIL_PB)
506+
od_pb1.order.ClearField("price")
507+
# Not allowed for active orders
508+
with pytest.raises(ValueError):
509+
OrderDetail.from_pb(od_pb1)
510+
# But allowed for canceled orders
511+
od_pb1.state_detail.state = electricity_trading_pb2.OrderState.ORDER_STATE_CANCELED
512+
OrderDetail.from_pb(od_pb1)
513+
514+
# Missing quantity (same logic as above)
515+
od_pb2 = electricity_trading_pb2.OrderDetail()
516+
od_pb2.CopyFrom(ORDER_DETAIL_PB)
517+
od_pb2.order.ClearField("quantity")
518+
with pytest.raises(ValueError):
519+
OrderDetail.from_pb(od_pb2)
520+
od_pb2.state_detail.state = electricity_trading_pb2.OrderState.ORDER_STATE_CANCELED
521+
OrderDetail.from_pb(od_pb2)
522+
523+
501524
def test_order_detail_no_timezone_error() -> None:
502525
"""Test that an order detail with inputs with no timezone raises a ValueError."""
503526
with pytest.raises(ValueError):

0 commit comments

Comments
 (0)