Spaces:
Running
Running
Commit ·
92ab5f4
1
Parent(s): 7d84d55
Fix daily market data freshness across weekends
Browse files
MARKET_DESK_AGENTS_ORCHESTRATOR_REPORT.md
CHANGED
|
@@ -56,7 +56,7 @@ Verdicts are:
|
|
| 56 |
- `REJECTED_BENCHMARK_UNDERPERFORMANCE`
|
| 57 |
- `DATA_BLOCKED`
|
| 58 |
|
| 59 |
-
Approval requires the configured minimum sample size, positive expectancy, sufficient edge score, acceptable risk/reward, no high overfitting risk and no measured benchmark underperformance. Stored fractional rates are normalized consistently (`0.58` is reported as `58%`). Sharpe and Sortino remain `null` when no persisted source supplies them.
|
| 60 |
|
| 61 |
## Diversification Rules
|
| 62 |
|
|
|
|
| 56 |
- `REJECTED_BENCHMARK_UNDERPERFORMANCE`
|
| 57 |
- `DATA_BLOCKED`
|
| 58 |
|
| 59 |
+
Approval requires the configured minimum sample size, positive expectancy, sufficient edge score, acceptable risk/reward, no high overfitting risk and no measured benchmark underperformance. Stored fractional rates are normalized consistently (`0.58` is reported as `58%`). Sharpe and Sortino remain `null` when no persisted source supplies them. Daily OHLCV freshness uses the end of the stored market date, preventing a Friday close from being rejected early on Monday solely because `PriceHistory.date` has no intraday timestamp.
|
| 60 |
|
| 61 |
## Diversification Rules
|
| 62 |
|
backend/app/services/market_desks.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
from dataclasses import dataclass
|
| 4 |
-
from datetime import date, datetime, timedelta
|
| 5 |
from typing import Callable, Protocol
|
| 6 |
|
| 7 |
from sqlalchemy import func, select
|
|
@@ -173,7 +173,9 @@ class BaseMarketDeskAgent:
|
|
| 173 |
def _is_stale(self, latest_date: date) -> bool:
|
| 174 |
if self.stale_after_hours <= 0:
|
| 175 |
return False
|
| 176 |
-
|
|
|
|
|
|
|
| 177 |
return latest < datetime.utcnow() - timedelta(hours=self.stale_after_hours)
|
| 178 |
|
| 179 |
def _availability_payload(self, status: str, configured_count: int, eligible_count: int) -> dict:
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
from dataclasses import dataclass
|
| 4 |
+
from datetime import date, datetime, time, timedelta
|
| 5 |
from typing import Callable, Protocol
|
| 6 |
|
| 7 |
from sqlalchemy import func, select
|
|
|
|
| 173 |
def _is_stale(self, latest_date: date) -> bool:
|
| 174 |
if self.stale_after_hours <= 0:
|
| 175 |
return False
|
| 176 |
+
# PriceHistory stores a market date, not an intraday observation time.
|
| 177 |
+
# Keep a Friday daily bar fresh through the weekend freshness budget.
|
| 178 |
+
latest = datetime.combine(latest_date, time.max)
|
| 179 |
return latest < datetime.utcnow() - timedelta(hours=self.stale_after_hours)
|
| 180 |
|
| 181 |
def _availability_payload(self, status: str, configured_count: int, eligible_count: int) -> dict:
|
backend/tests/test_market_desk_orchestrator.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
from datetime import date
|
| 2 |
|
| 3 |
from sqlalchemy import create_engine
|
| 4 |
from sqlalchemy.orm import Session
|
|
@@ -138,6 +138,19 @@ def test_unavailable_agent_remains_skipped_when_reused_after_discovery():
|
|
| 138 |
assert result["opportunities_found"] == 0
|
| 139 |
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
def test_quant_edge_rejects_candidate_when_sample_is_insufficient():
|
| 142 |
with setup_db() as db:
|
| 143 |
assessment = BlumQuantEdgeAgent(min_score=60.0, min_sample_size=20).assess(db, candidate())
|
|
|
|
| 1 |
+
from datetime import date, timedelta
|
| 2 |
|
| 3 |
from sqlalchemy import create_engine
|
| 4 |
from sqlalchemy.orm import Session
|
|
|
|
| 138 |
assert result["opportunities_found"] == 0
|
| 139 |
|
| 140 |
|
| 141 |
+
def test_daily_bar_freshness_uses_end_of_market_date():
|
| 142 |
+
with setup_db() as db:
|
| 143 |
+
asset = add_asset(db, "NVDA")
|
| 144 |
+
stored = db.query(PriceHistory).filter(PriceHistory.asset_id == asset.id).one()
|
| 145 |
+
stored.date = date.today() - timedelta(days=4)
|
| 146 |
+
db.commit()
|
| 147 |
+
|
| 148 |
+
availability = NasdaqAgent(stale_after_hours=96.0).availability(db)
|
| 149 |
+
|
| 150 |
+
assert availability["status"] == "AVAILABLE"
|
| 151 |
+
assert availability["eligible_asset_count"] == 1
|
| 152 |
+
|
| 153 |
+
|
| 154 |
def test_quant_edge_rejects_candidate_when_sample_is_insufficient():
|
| 155 |
with setup_db() as db:
|
| 156 |
assessment = BlumQuantEdgeAgent(min_score=60.0, min_sample_size=20).assess(db, candidate())
|