From ff840ca1406f9a8552d6849dbe298da5ef2ed341 Mon Sep 17 00:00:00 2001 From: yuxuanhui Date: Wed, 9 Sep 2026 15:12:45 +0800 Subject: [PATCH] fix: correct WorldQuant daily alpha date filters --- backend/app/worldquant.py | 8 ++++++-- backend/tests/browser_server.py | 8 +++++--- backend/tests/test_alpha_management.py | 24 +++++++++++++++++++----- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/backend/app/worldquant.py b/backend/app/worldquant.py index 8bc17cf..0aa1bc5 100644 --- a/backend/app/worldquant.py +++ b/backend/app/worldquant.py @@ -350,9 +350,13 @@ class WqClient: # Daily intervals are [UTC midnight, next midnight). Submitted dates # use the actual submission time, independently of creation or stage. field = "dateCreated" if submission == "UNSUBMITTED" else "dateSubmitted" - params.update({f"{field}>=": date_from, f"{field}<": date_to, "order": field}) + # BRAIN treats the query separator '=' as part of its comparison + # syntax: a '>=' key adds an extra '=' and makes the date invalid. + # Both bounds are inclusive upstream; exclude the next UTC midnight. + upper = (datetime.fromisoformat(date_to) - timedelta(microseconds=1)).isoformat() + params.update({f"{field}>": date_from, f"{field}<": upper, "order": field}) if field == "dateCreated": - params["dateCreated<"] = min(before, date_to) + params["dateCreated<"] = min(before, upper) return await self.get("/users/self/alphas", params) async def alpha(self, alpha_id): diff --git a/backend/tests/browser_server.py b/backend/tests/browser_server.py index dfdb80e..b2f13af 100644 --- a/backend/tests/browser_server.py +++ b/backend/tests/browser_server.py @@ -163,7 +163,9 @@ def create_test_app(): r for r in records if (r["status"] == "UNSUBMITTED") == unsubmitted and r["hidden"] == hidden ] for field in ("dateCreated", "dateSubmitted"): - for suffix in (">=", "<"): + if field + ">=" in request.url.params: + return httpx.Response(400, json=["Expected ISO 8601 datetime with timezone"]) + for suffix in (">", "<"): boundary = request.url.params.get(field + suffix) if boundary: bound = datetime.fromisoformat(boundary).replace(tzinfo=timezone.utc) @@ -173,8 +175,8 @@ def create_test_app(): if r.get(field) and ( datetime.fromisoformat(r[field]) >= bound - if suffix == ">=" - else datetime.fromisoformat(r[field]) < bound + if suffix == ">" + else datetime.fromisoformat(r[field]) <= bound ) ] offset, limit = ( diff --git a/backend/tests/test_alpha_management.py b/backend/tests/test_alpha_management.py index e1705b5..0947fc5 100644 --- a/backend/tests/test_alpha_management.py +++ b/backend/tests/test_alpha_management.py @@ -318,12 +318,25 @@ async def test_scoped_date_query_parameters_and_no_platform_check(settings): def handler(request): requests.append(request) - return httpx.Response(200, json={"results": []}) + # BRAIN parses the extra '=' as part of the datetime, returning HTTP 400. + if any(key.endswith(">=") for key in request.url.params): + return httpx.Response(400, json=["Expected ISO 8601 datetime with timezone"]) + field = "dateCreated" if "status" in request.url.params else "dateSubmitted" + lower = datetime.fromisoformat(request.url.params[field + ">"]) + upper = datetime.fromisoformat(request.url.params[field + "<"]) + records = [ + {"id": "midnight", field: "2025-01-01T00:00:00+00:00"}, + {"id": "last", field: "2025-01-01T23:59:59.999999+00:00"}, + {"id": "next", field: "2025-01-02T00:00:00+00:00"}, + ] + return httpx.Response(200, json={"results": [ + row for row in records if lower <= datetime.fromisoformat(row[field]) <= upper + ]}) client = WqClient(settings, transport=httpx.MockTransport(handler)) client.credentials, client.authenticated = ("test@example.com", "test"), True for submission in ("UNSUBMITTED", "SUBMITTED"): - await client.alphas( + result = await client.alphas( submission, True, 100, @@ -331,10 +344,11 @@ async def test_scoped_date_query_parameters_and_no_platform_check(settings): date_from="2025-01-01T00:00:00+00:00", date_to="2025-01-02T00:00:00+00:00", ) + assert [row["id"] for row in result["results"]] == ["midnight", "last"] first, second = [dict(r.url.params) for r in requests] - assert first["dateCreated>="] == "2025-01-01T00:00:00+00:00" - assert first["dateCreated<"] == "2025-01-02T00:00:00+00:00" - assert second["dateSubmitted>="] == first["dateCreated>="] + assert first["dateCreated>"] == "2025-01-01T00:00:00+00:00" + assert first["dateCreated<"] == "2025-01-01T23:59:59.999999+00:00" + assert second["dateSubmitted>"] == first["dateCreated>"] assert second["dateSubmitted<"] == first["dateCreated<"] assert "status!" in second and second["hidden"] == "true" assert all(r.method == "GET" and r.url.path == "/users/self/alphas" for r in requests)