From 15c4ca076833b56fe5db2add8da5b158f989fab2 Mon Sep 17 00:00:00 2001 From: cwasicki <126617870+cwasicki@users.noreply.github.com> Date: Mon, 12 Jan 2026 17:46:37 +0100 Subject: [PATCH 1/8] Add is_valid check to delivery time filter class If both are set, start timestamps have to be before end timestamps. Signed-off-by: cwasicki <126617870+cwasicki@users.noreply.github.com> --- .../client/electricity_trading/_types.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/frequenz/client/electricity_trading/_types.py b/src/frequenz/client/electricity_trading/_types.py index af97e25..f4ba895 100644 --- a/src/frequenz/client/electricity_trading/_types.py +++ b/src/frequenz/client/electricity_trading/_types.py @@ -1577,6 +1577,25 @@ def to_pb(self) -> electricity_trading_pb2.DeliveryTimeFilter: ), ) + @property + def is_valid(self) -> bool: + """Check if the DeliveryTimeFilter is valid. + + Verifies that the start and end dates in the time interval are logical, if both are set. + + Returns: + True if the filter is valid, False otherwise. + """ + if self.time_interval is None: + return True + + start = self.time_interval.start_time + end = self.time_interval.end_time + if start is None or end is None: + return True + + return start < end + @dataclass(frozen=True) class GridpoolOrderFilter: From 8afcc93d7eb5bff09463a9b8a12cc90b1a6c919e Mon Sep 17 00:00:00 2001 From: cwasicki <126617870+cwasicki@users.noreply.github.com> Date: Mon, 12 Jan 2026 17:51:26 +0100 Subject: [PATCH 2/8] Update delivery time filter check for gridpool streams Uses the new `is_valid` check that now comes with the delivery time filter. This also drops the check that delivery start time of the filter has to be in the future, which is not desired for the gridpool data streaming endpoints. Signed-off-by: cwasicki <126617870+cwasicki@users.noreply.github.com> --- src/frequenz/client/electricity_trading/_client.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/frequenz/client/electricity_trading/_client.py b/src/frequenz/client/electricity_trading/_client.py index 80743cb..8392c48 100644 --- a/src/frequenz/client/electricity_trading/_client.py +++ b/src/frequenz/client/electricity_trading/_client.py @@ -300,8 +300,10 @@ def gridpool_orders_stream( Raises: grpc.RpcError: If an error occurs while streaming the orders. + ValueError: If an invalid delivery_time_filter is provided. """ - self.validate_params(delivery_time_filter=delivery_time_filter) + if delivery_time_filter and not delivery_time_filter.is_valid: + raise ValueError("Invalid delivery_time_filter provided.") gridpool_order_filter = GridpoolOrderFilter( order_states=order_states, @@ -366,8 +368,10 @@ def gridpool_trades_stream( Raises: grpc.RpcError: If an error occurs while streaming gridpool trades. + ValueError: If an invalid delivery_time_filter is provided. """ - self.validate_params(delivery_time_filter=delivery_time_filter) + if delivery_time_filter and not delivery_time_filter.is_valid: + raise ValueError("Invalid delivery_time_filter provided.") gridpool_trade_filter = GridpoolTradeFilter( trade_states=trade_states, From b5fa0fb99f24aae39f42886c4486d94e8822bf40 Mon Sep 17 00:00:00 2001 From: cwasicki <126617870+cwasicki@users.noreply.github.com> Date: Mon, 5 Jan 2026 22:29:37 +0100 Subject: [PATCH 3/8] CLI: Rename execution range args for list public orders and trades Renaming time range CLI arguments to use from/to instead of start/end to clearly distinguish from delivery start. Signed-off-by: cwasicki <126617870+cwasicki@users.noreply.github.com> --- .../electricity_trading/cli/__main__.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/frequenz/client/electricity_trading/cli/__main__.py b/src/frequenz/client/electricity_trading/cli/__main__.py index 1052a22..b863a00 100644 --- a/src/frequenz/client/electricity_trading/cli/__main__.py +++ b/src/frequenz/client/electricity_trading/cli/__main__.py @@ -51,15 +51,15 @@ def cli() -> None: @click.option("--url", required=True, type=str) @click.option("--auth_key", required=True, type=str) @click.option("--delivery-start", default=None, type=iso) -@click.option("--start", default=None, type=iso) -@click.option("--end", default=None, type=iso) +@click.option("--execution-from", default=None, type=iso) +@click.option("--execution-to", default=None, type=iso) @click.option("--sign_secret", default=None, type=str) def receive_public_trades( # pylint: disable=too-many-arguments url: str, auth_key: str, *, - start: datetime, - end: datetime, + execution_from: datetime, + execution_to: datetime, delivery_start: datetime, sign_secret: str | None = None, ) -> None: @@ -69,8 +69,8 @@ def receive_public_trades( # pylint: disable=too-many-arguments url=url, auth_key=auth_key, delivery_start=delivery_start, - start=start, - end=end, + start=execution_from, + end=execution_to, sign_secret=sign_secret, ) ) @@ -80,15 +80,15 @@ def receive_public_trades( # pylint: disable=too-many-arguments @click.option("--url", required=True, type=str) @click.option("--auth_key", required=True, type=str) @click.option("--delivery-start", default=None, type=iso) -@click.option("--start", default=None, type=iso) -@click.option("--end", default=None, type=iso) +@click.option("--execution-from", default=None, type=iso) +@click.option("--execution-to", default=None, type=iso) @click.option("--sign_secret", default=None, type=str) def receive_public_orders( # pylint: disable=too-many-arguments url: str, auth_key: str, *, - start: datetime, - end: datetime, + execution_from: datetime, + execution_to: datetime, delivery_start: datetime, sign_secret: str | None = None, ) -> None: @@ -98,8 +98,8 @@ def receive_public_orders( # pylint: disable=too-many-arguments url=url, auth_key=auth_key, delivery_start=delivery_start, - start=start, - end=end, + start=execution_from, + end=execution_to, sign_secret=sign_secret, ) ) From 8408b940ff26baac9b8cae66e15453680dbd4e39 Mon Sep 17 00:00:00 2001 From: cwasicki <126617870+cwasicki@users.noreply.github.com> Date: Mon, 12 Jan 2026 17:57:36 +0100 Subject: [PATCH 4/8] CLI: Print gridpool ID for gridpool trades and orders Adds the gridpool ID as a column to the CSV output which is useful when outputs from multiple gridpools are merged. Signed-off-by: cwasicki <126617870+cwasicki@users.noreply.github.com> --- .../electricity_trading/cli/etrading.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/frequenz/client/electricity_trading/cli/etrading.py b/src/frequenz/client/electricity_trading/cli/etrading.py index 6b0f770..7957105 100644 --- a/src/frequenz/client/electricity_trading/cli/etrading.py +++ b/src/frequenz/client/electricity_trading/cli/etrading.py @@ -162,7 +162,7 @@ async def list_gridpool_trades( lst = client.list_gridpool_trades(gid, delivery_time_filter=delivery_time_filter) async for trade in lst: - print_trade(trade) + print_trade(trade, gid) if delivery_start and delivery_start <= datetime.now(timezone.utc): return @@ -171,7 +171,7 @@ async def list_gridpool_trades( gid, delivery_time_filter=delivery_time_filter ).new_receiver() async for trade in stream: - print_trade(trade) + print_trade(trade, gid) async def list_gridpool_orders( @@ -216,7 +216,7 @@ async def list_gridpool_orders( lst = client.list_gridpool_orders(gid, delivery_time_filter=delivery_time_filter) async for order in reverse_iterator(lst): - print_order(order) + print_order(order, gid) if delivery_start and delivery_start <= datetime.now(timezone.utc): return @@ -225,7 +225,7 @@ async def list_gridpool_orders( gid, delivery_time_filter=delivery_time_filter ).new_receiver() async for order in stream: - print_order(order) + print_order(order, gid) # pylint: disable=too-many-arguments @@ -287,7 +287,7 @@ async def create_order( tag=tag, ) - print_order(order) + print_order(order, gid) async def cancel_order( @@ -406,12 +406,13 @@ def print_trade_header() -> None: "quantity_mw," "currency," "price," - "state " + "state," + "gridpool_id" ) print(header) -def print_trade(trade: Trade) -> None: +def print_trade(trade: Trade, gid: int) -> None: """Print trade details to stdout in CSV format.""" values = ( trade.id, @@ -426,6 +427,7 @@ def print_trade(trade: Trade) -> None: trade.price.currency, trade.price.amount, trade.state, + gid, ) print(",".join(v.name if isinstance(v, Enum) else str(v) for v in values)) @@ -447,12 +449,13 @@ def print_order_header() -> None: "currency," "price," "state," - "tag" + "tag," + "gridpool_id" ) print(header) -def print_order(order: OrderDetail) -> None: +def print_order(order: OrderDetail, gid: int) -> None: """ Print order details to stdout in CSV format. @@ -469,6 +472,7 @@ def print_order(order: OrderDetail) -> None: Args: order: OrderDetail object + gid: Gridpool ID """ values = [ order.order_id, @@ -486,6 +490,7 @@ def print_order(order: OrderDetail) -> None: order.order.price.amount, order.state_detail.state, order.order.tag, + gid, ] print(",".join(v.name if isinstance(v, Enum) else str(v) for v in values)) From 98c373184dcaccbfbebdef1a81cc1453799c92d2 Mon Sep 17 00:00:00 2001 From: cwasicki <126617870+cwasicki@users.noreply.github.com> Date: Mon, 12 Jan 2026 18:21:26 +0100 Subject: [PATCH 5/8] CLI: Support delivery time filter on gridpool trade stream Replaces the delivery period filter to support full delivery period start intervals. Using to/from arguments to avoid ambiguity with delivery start. Signed-off-by: cwasicki <126617870+cwasicki@users.noreply.github.com> --- .../electricity_trading/cli/__main__.py | 10 +++-- .../electricity_trading/cli/etrading.py | 38 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/frequenz/client/electricity_trading/cli/__main__.py b/src/frequenz/client/electricity_trading/cli/__main__.py index b863a00..f1a7d26 100644 --- a/src/frequenz/client/electricity_trading/cli/__main__.py +++ b/src/frequenz/client/electricity_trading/cli/__main__.py @@ -105,18 +105,21 @@ def receive_public_orders( # pylint: disable=too-many-arguments ) +# pylint: disable=too-many-arguments @cli.command() @click.option("--url", required=True, type=str) @click.option("--auth_key", required=True, type=str) @click.option("--gid", required=True, type=int) -@click.option("--start", default=None, type=iso) +@click.option("--delivery-from", default=None, type=iso) +@click.option("--delivery-to", default=None, type=iso) @click.option("--sign_secret", default=None, type=str) def receive_gridpool_trades( url: str, auth_key: str, gid: int, *, - start: datetime, + delivery_from: datetime | None, + delivery_to: datetime | None, sign_secret: str | None = None, ) -> None: """List and/or stream gridpool trades.""" @@ -125,7 +128,8 @@ def receive_gridpool_trades( url=url, auth_key=auth_key, gid=gid, - delivery_start=start, + delivery_from=delivery_from, + delivery_to=delivery_to, sign_secret=sign_secret, ) ) diff --git a/src/frequenz/client/electricity_trading/cli/etrading.py b/src/frequenz/client/electricity_trading/cli/etrading.py index 7957105..12ac383 100644 --- a/src/frequenz/client/electricity_trading/cli/etrading.py +++ b/src/frequenz/client/electricity_trading/cli/etrading.py @@ -125,51 +125,47 @@ async def receive_public_orders( # pylint: disable=too-many-arguments print_public_order(order) +# pylint: disable=too-many-arguments async def list_gridpool_trades( url: str, auth_key: str, gid: int, *, - delivery_start: datetime, + delivery_from: datetime | None, + delivery_to: datetime | None, sign_secret: str | None = None, ) -> None: """List gridpool trades and stream new gridpool trades. - Optionally a delivery_start can be provided to filter the trades by delivery period. + Optionally trades can be filtered by delivery period. Args: url: URL of the trading API. auth_key: API key. gid: Gridpool ID. - delivery_start: Start of the delivery period or None. + delivery_from: Start timestamp (inclusive) to filter delivery start times or None. + delivery_to: End timestamp (exclusive) to filter delivery start times or None. sign_secret: The cryptographic secret to use for HMAC generation. """ client = Client(server_url=url, auth_key=auth_key, sign_secret=sign_secret) print_trade_header() - delivery_time_filter = None - # If delivery period is selected, list historical trades also - if delivery_start is not None: - check_delivery_start(delivery_start) - delivery_time_filter = DeliveryTimeFilter( - time_interval=Interval( - start_time=delivery_start, - end_time=delivery_start + timedelta(minutes=15), - ), - duration_filters=[], - ) - lst = client.list_gridpool_trades(gid, delivery_time_filter=delivery_time_filter) + delivery_time_filter = DeliveryTimeFilter(Interval(delivery_from, delivery_to)) + lst = client.list_gridpool_trades( + gid, + delivery_time_filter=delivery_time_filter, + ) + + # Initialize the stream before printing to minimize the gap between the two + stream = client.gridpool_trades_stream( + gid, + delivery_time_filter=delivery_time_filter, + ).new_receiver() async for trade in lst: print_trade(trade, gid) - if delivery_start and delivery_start <= datetime.now(timezone.utc): - return - - stream = client.gridpool_trades_stream( - gid, delivery_time_filter=delivery_time_filter - ).new_receiver() async for trade in stream: print_trade(trade, gid) From cdf4013ead89083068ec5e33faf2b56fd4e7c929 Mon Sep 17 00:00:00 2001 From: cwasicki <126617870+cwasicki@users.noreply.github.com> Date: Mon, 12 Jan 2026 18:36:45 +0100 Subject: [PATCH 6/8] CLI: Support delivery time filter on gridpool order stream Replaces the delivery period filter to support full delivery period start intervals. Using to/from arguments to avoid ambiguity with delivery start. Signed-off-by: cwasicki <126617870+cwasicki@users.noreply.github.com> --- .../electricity_trading/cli/__main__.py | 10 +++-- .../electricity_trading/cli/etrading.py | 42 ++++++++----------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/frequenz/client/electricity_trading/cli/__main__.py b/src/frequenz/client/electricity_trading/cli/__main__.py index f1a7d26..89c68e8 100644 --- a/src/frequenz/client/electricity_trading/cli/__main__.py +++ b/src/frequenz/client/electricity_trading/cli/__main__.py @@ -135,17 +135,20 @@ def receive_gridpool_trades( ) +# pylint: disable=too-many-arguments @cli.command() @click.option("--url", required=True, type=str) @click.option("--auth_key", required=True, type=str) -@click.option("--start", default=None, type=iso) +@click.option("--delivery-from", default=None, type=iso) +@click.option("--delivery-to", default=None, type=iso) @click.option("--gid", required=True, type=int) @click.option("--sign_secret", default=None, type=str) def receive_gridpool_orders( url: str, auth_key: str, *, - start: datetime, + delivery_from: datetime | None, + delivery_to: datetime | None, gid: int, sign_secret: str | None = None, ) -> None: @@ -154,7 +157,8 @@ def receive_gridpool_orders( run_list_gridpool_orders( url=url, auth_key=auth_key, - delivery_start=start, + delivery_from=delivery_from, + delivery_to=delivery_to, gid=gid, sign_secret=sign_secret, ) diff --git a/src/frequenz/client/electricity_trading/cli/etrading.py b/src/frequenz/client/electricity_trading/cli/etrading.py index 12ac383..9193484 100644 --- a/src/frequenz/client/electricity_trading/cli/etrading.py +++ b/src/frequenz/client/electricity_trading/cli/etrading.py @@ -4,7 +4,7 @@ """CLI tool to interact with the trading API.""" from collections import deque -from datetime import datetime, timedelta, timezone +from datetime import datetime, timedelta from decimal import Decimal from enum import Enum from typing import AsyncIterator @@ -170,19 +170,19 @@ async def list_gridpool_trades( print_trade(trade, gid) +# pylint: disable=too-many-arguments async def list_gridpool_orders( url: str, auth_key: str, *, - delivery_start: datetime, + delivery_from: datetime | None, + delivery_to: datetime | None, gid: int, sign_secret: str | None = None, ) -> None: """List orders and stream new gridpool orders. - If delivery_start is provided, list historical orders and stream new orders - for the 15 minute delivery period starting at delivery_start. - If no delivery_start is provided, stream new orders for any delivery period. + Optionally orders can be filtered by delivery period. Note that retrieved sort order for listed orders (starting from the newest) is reversed in chunks trying to bring more recent orders to the bottom. @@ -190,7 +190,8 @@ async def list_gridpool_orders( Args: url: URL of the trading API. auth_key: API key. - delivery_start: Start of the delivery period or None. + delivery_from: Start timestamp (inclusive) to filter delivery start times or None. + delivery_to: End timestamp (exclusive) to filter delivery start times or None. gid: Gridpool ID. sign_secret: The cryptographic secret to use for HMAC generation. """ @@ -198,28 +199,21 @@ async def list_gridpool_orders( print_order_header() - delivery_time_filter = None - # If delivery period is selected, list historical orders also - if delivery_start is not None: - check_delivery_start(delivery_start) - delivery_time_filter = DeliveryTimeFilter( - time_interval=Interval( - start_time=delivery_start, - end_time=delivery_start + timedelta(minutes=15), - ), - duration_filters=[], - ) - lst = client.list_gridpool_orders(gid, delivery_time_filter=delivery_time_filter) - - async for order in reverse_iterator(lst): - print_order(order, gid) + delivery_time_filter = DeliveryTimeFilter(Interval(delivery_from, delivery_to)) - if delivery_start and delivery_start <= datetime.now(timezone.utc): - return + lst = client.list_gridpool_orders( + gid, + delivery_time_filter=delivery_time_filter, + ) stream = client.gridpool_orders_stream( - gid, delivery_time_filter=delivery_time_filter + gid, + delivery_time_filter=delivery_time_filter, ).new_receiver() + + async for order in reverse_iterator(lst): + print_order(order, gid) + async for order in stream: print_order(order, gid) From 40646c73f071ffdb6b774574899dea01b1f71b91 Mon Sep 17 00:00:00 2001 From: cwasicki <126617870+cwasicki@users.noreply.github.com> Date: Mon, 12 Jan 2026 18:49:13 +0100 Subject: [PATCH 7/8] Update release notes Signed-off-by: cwasicki <126617870+cwasicki@users.noreply.github.com> --- RELEASE_NOTES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7526088..10b9670 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,11 +6,12 @@ ## Upgrading - +* Update delivery period time filter validation to remove inappropriate start time check. ## New Features * Add check to validate order details. +* CLI: Support delivery time filter in gridpool streams. ## Bug Fixes From 1192df5ec7fe6a52be2725a42378f408a5b56ecf Mon Sep 17 00:00:00 2001 From: cwasicki <126617870+cwasicki@users.noreply.github.com> Date: Fri, 16 Jan 2026 12:41:09 +0100 Subject: [PATCH 8/8] Add help text to delivery time filters on gridpool data endpoints Signed-off-by: cwasicki <126617870+cwasicki@users.noreply.github.com> --- .../electricity_trading/cli/__main__.py | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/frequenz/client/electricity_trading/cli/__main__.py b/src/frequenz/client/electricity_trading/cli/__main__.py index 89c68e8..9c48eff 100644 --- a/src/frequenz/client/electricity_trading/cli/__main__.py +++ b/src/frequenz/client/electricity_trading/cli/__main__.py @@ -110,8 +110,18 @@ def receive_public_orders( # pylint: disable=too-many-arguments @click.option("--url", required=True, type=str) @click.option("--auth_key", required=True, type=str) @click.option("--gid", required=True, type=int) -@click.option("--delivery-from", default=None, type=iso) -@click.option("--delivery-to", default=None, type=iso) +@click.option( + "--delivery-from", + default=None, + type=iso, + help="Start timestamp (inclusive) to filter delivery start times.", +) +@click.option( + "--delivery-to", + default=None, + type=iso, + help="End timestamp (exclusive) to filter delivery start times.", +) @click.option("--sign_secret", default=None, type=str) def receive_gridpool_trades( url: str, @@ -139,8 +149,18 @@ def receive_gridpool_trades( @cli.command() @click.option("--url", required=True, type=str) @click.option("--auth_key", required=True, type=str) -@click.option("--delivery-from", default=None, type=iso) -@click.option("--delivery-to", default=None, type=iso) +@click.option( + "--delivery-from", + default=None, + type=iso, + help="Start timestamp (inclusive) to filter delivery start times.", +) +@click.option( + "--delivery-to", + default=None, + type=iso, + help="End timestamp (exclusive) to filter delivery start times.", +) @click.option("--gid", required=True, type=int) @click.option("--sign_secret", default=None, type=str) def receive_gridpool_orders(