fix: derive all aggregation timeframes from trade timestamp (#112)
Some checks failed
CI / test (pull_request) Has been cancelled
Some checks failed
CI / test (pull_request) Has been cancelled
run_all_aggregations() previously used datetime.now(UTC) for weekly through annual layers while using the trade date only for daily, causing data misalignment on backfill. Now all layers consistently use the latest trade timestamp. Also adds "Z" suffix handling for fromisoformat() compatibility and strengthens test assertions to verify L4-L2 layer values end-to-end. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -230,21 +230,44 @@ class ContextAggregator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def run_all_aggregations(self) -> None:
|
def run_all_aggregations(self) -> None:
|
||||||
"""Run all aggregations from L7 to L1 (bottom-up)."""
|
"""Run all aggregations from L7 to L1 (bottom-up).
|
||||||
|
|
||||||
|
All timeframes are derived from the latest trade timestamp so that
|
||||||
|
past data re-aggregation produces consistent results across layers.
|
||||||
|
"""
|
||||||
|
cursor = self.conn.execute("SELECT MAX(timestamp) FROM trades")
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if not row or row[0] is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
ts_raw = row[0]
|
||||||
|
if ts_raw.endswith("Z"):
|
||||||
|
ts_raw = ts_raw.replace("Z", "+00:00")
|
||||||
|
latest_ts = datetime.fromisoformat(ts_raw)
|
||||||
|
trade_date = latest_ts.date()
|
||||||
|
date_str = trade_date.isoformat()
|
||||||
|
|
||||||
|
iso_year, iso_week, _ = trade_date.isocalendar()
|
||||||
|
week_str = f"{iso_year}-W{iso_week:02d}"
|
||||||
|
month_str = f"{trade_date.year}-{trade_date.month:02d}"
|
||||||
|
quarter = (trade_date.month - 1) // 3 + 1
|
||||||
|
quarter_str = f"{trade_date.year}-Q{quarter}"
|
||||||
|
year_str = str(trade_date.year)
|
||||||
|
|
||||||
# L7 (trades) → L6 (daily)
|
# L7 (trades) → L6 (daily)
|
||||||
self.aggregate_daily_from_trades()
|
self.aggregate_daily_from_trades(date_str)
|
||||||
|
|
||||||
# L6 (daily) → L5 (weekly)
|
# L6 (daily) → L5 (weekly)
|
||||||
self.aggregate_weekly_from_daily()
|
self.aggregate_weekly_from_daily(week_str)
|
||||||
|
|
||||||
# L5 (weekly) → L4 (monthly)
|
# L5 (weekly) → L4 (monthly)
|
||||||
self.aggregate_monthly_from_weekly()
|
self.aggregate_monthly_from_weekly(month_str)
|
||||||
|
|
||||||
# L4 (monthly) → L3 (quarterly)
|
# L4 (monthly) → L3 (quarterly)
|
||||||
self.aggregate_quarterly_from_monthly()
|
self.aggregate_quarterly_from_monthly(quarter_str)
|
||||||
|
|
||||||
# L3 (quarterly) → L2 (annual)
|
# L3 (quarterly) → L2 (annual)
|
||||||
self.aggregate_annual_from_quarterly()
|
self.aggregate_annual_from_quarterly(year_str)
|
||||||
|
|
||||||
# L2 (annual) → L1 (legacy)
|
# L2 (annual) → L1 (legacy)
|
||||||
self.aggregate_legacy_from_annual()
|
self.aggregate_legacy_from_annual()
|
||||||
|
|||||||
@@ -300,9 +300,17 @@ class TestContextAggregator:
|
|||||||
# Verify data exists in each layer
|
# Verify data exists in each layer
|
||||||
store = aggregator.store
|
store = aggregator.store
|
||||||
assert store.get_context(ContextLayer.L6_DAILY, date, "total_pnl") == 1000.0
|
assert store.get_context(ContextLayer.L6_DAILY, date, "total_pnl") == 1000.0
|
||||||
current_week = datetime.now(UTC).strftime("%Y-W%V")
|
from datetime import date as date_cls
|
||||||
assert store.get_context(ContextLayer.L5_WEEKLY, current_week, "weekly_pnl") is not None
|
trade_date = date_cls.fromisoformat(date)
|
||||||
# Further layers depend on time alignment, just verify no crashes
|
iso_year, iso_week, _ = trade_date.isocalendar()
|
||||||
|
trade_week = f"{iso_year}-W{iso_week:02d}"
|
||||||
|
assert store.get_context(ContextLayer.L5_WEEKLY, trade_week, "weekly_pnl") is not None
|
||||||
|
trade_month = f"{trade_date.year}-{trade_date.month:02d}"
|
||||||
|
trade_quarter = f"{trade_date.year}-Q{(trade_date.month - 1) // 3 + 1}"
|
||||||
|
trade_year = str(trade_date.year)
|
||||||
|
assert store.get_context(ContextLayer.L4_MONTHLY, trade_month, "monthly_pnl") == 1000.0
|
||||||
|
assert store.get_context(ContextLayer.L3_QUARTERLY, trade_quarter, "quarterly_pnl") == 1000.0
|
||||||
|
assert store.get_context(ContextLayer.L2_ANNUAL, trade_year, "annual_pnl") == 1000.0
|
||||||
|
|
||||||
|
|
||||||
class TestLayerMetadata:
|
class TestLayerMetadata:
|
||||||
|
|||||||
Reference in New Issue
Block a user