"""单市场合成日线聚合;日历显式提供,未结束周与缺数周分开。""" import csv import io import json from collections import defaultdict from datetime import date, timedelta calendar = ["2026-11-23", "2026-11-24", "2026-11-25", "2026-11-27", "2026-11-30", "2026-12-01", "2026-12-02", "2026-12-03", "2026-12-04"] as_of = "2026-12-01" daily = [("2026-11-23", 10, 12, 9, 11, 100, 1000), ("2026-11-24", 11, 13, 10, 12, 200, 2200), ("2026-11-25", 12, 14, 11, 13, 300, 3600), ("2026-11-27", 13, 15, 12, 14, 150, 1950), ("2026-11-30", 14, 16, 13, 15, 100, 1400), ("2026-12-01", 15, 17, 14, 16, 110, 1650)] def week_key(day): value = date.fromisoformat(day) return (value-timedelta(days=value.weekday())).isoformat() def aggregate(rows): if len({row[0] for row in rows}) != len(rows): raise ValueError("duplicate_daily_key") groups = defaultdict(list) for row in rows: if row[0] not in calendar or row[0] > as_of: raise ValueError("invalid_session") groups[week_key(row[0])].append(row) output = [] for week in sorted({week_key(d) for d in calendar if d <= as_of}): values = sorted(groups.get(week, [])) expected = [d for d in calendar if week_key(d) == week] missing_past = sorted(set(d for d in expected if d <= as_of)-{v[0] for v in values}) status = "missing_data" if missing_past else "open_week" if max(expected) > as_of else "complete" output.append({"week_start": week, "first_session": values[0][0] if values else None, "last_session": values[-1][0] if values else None, "open": values[0][1] if values else None, "high": max((v[2] for v in values), default=None), "low": min((v[3] for v in values), default=None), "close": values[-1][4] if values else None, "volume": sum(v[5] for v in values) if values else None, "amount": sum(v[6] for v in values) if values else None, "observed_sessions": len(values), "expected_sessions": len(expected), "status": status, "missing_past": ",".join(missing_past)}) return output weekly = aggregate(daily) assert [weekly[0][k] for k in ["open", "high", "low", "close", "volume", "amount"]] == [10, 15, 9, 14, 750, 8750] assert weekly[0]["status"] == "complete" and weekly[0]["observed_sessions"] == 4 assert weekly[1]["status"] == "open_week" and weekly[1]["volume"] == 210 assert "2026-11-26" not in calendar gap = aggregate([row for row in daily if row[0] != "2026-11-24"]) assert gap[0]["status"] == "missing_data" and gap[0]["missing_past"] == "2026-11-24" assert aggregate(list(reversed(daily))) == weekly empty_week = aggregate([r for r in daily if r[0] >= "2026-11-30"])[0] assert empty_week["status"] == "missing_data" and empty_week["close"] is None stream = io.StringIO(newline="") writer = csv.DictWriter(stream, fieldnames=list(weekly[0])) writer.writeheader(); writer.writerows(weekly) assert len(list(csv.DictReader(io.StringIO(stream.getvalue())))) == 2 print(json.dumps({"weekly": weekly, "gap_test_status": gap[0]["status"], "weekly_csv": stream.getvalue()}, sort_keys=True))