Skip to content

Commit 5cfdf0a

Browse files
lee101claude
andcommitted
collect_5min_bars: isolate per-symbol failures so one bad CSV can't stall the rest
A corrupt last line in XRPUSDT.csv (truncated "2026" string) and an empty UNIUSDT.csv had been raising in append_bars on every cycle, with the existing cycle-level try/except catching the exception AFTER the for-loop had already moved on, so all symbols *after* the bad CSV in the rotation were silently skipped. 11 of 17 binance symbols had been stale by 27-34 days as a result. Move the per-symbol fetch + append into its own try/except inside the loop, pull the cycle into a testable run_collection_cycle helper, and also wrap the daily backfill scheduler so a backfill exception cannot suppress the next cycle either. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent f586cbd commit 5cfdf0a

2 files changed

Lines changed: 90 additions & 16 deletions

File tree

scripts/collect_5min_bars.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,27 @@ def daily_backfill(symbols: list, out_root: Path):
7979
logger.warning(f"backfill failed: {e}")
8080

8181

82+
def run_collection_cycle(symbols: list[str], out_root: Path, last_backfill):
83+
for sym in symbols:
84+
try:
85+
bars = fetch_recent_5m(sym, limit=3)
86+
path = out_root / f"{sym}.csv"
87+
n = append_bars(path, bars)
88+
if n > 0:
89+
logger.info(f"{sym}: +{n} bars")
90+
except Exception as e:
91+
logger.error(f"{sym} fetch/append failed: {e}")
92+
93+
try:
94+
now = datetime.now(timezone.utc)
95+
if last_backfill is None or (now - last_backfill).total_seconds() > 86400:
96+
daily_backfill(symbols, out_root)
97+
last_backfill = now
98+
except Exception as e:
99+
logger.error(f"backfill scheduler error: {e}")
100+
return last_backfill
101+
102+
82103
def main():
83104
p = argparse.ArgumentParser()
84105
p.add_argument("--symbols", nargs="+", default=["DOGEUSDT"])
@@ -92,22 +113,7 @@ def main():
92113
last_backfill = None
93114

94115
while True:
95-
try:
96-
for sym in args.symbols:
97-
bars = fetch_recent_5m(sym, limit=3)
98-
path = args.out_root / f"{sym}.csv"
99-
n = append_bars(path, bars)
100-
if n > 0:
101-
logger.info(f"{sym}: +{n} bars")
102-
103-
now = datetime.now(timezone.utc)
104-
if last_backfill is None or (now - last_backfill).total_seconds() > 86400:
105-
daily_backfill(args.symbols, args.out_root)
106-
last_backfill = now
107-
108-
except Exception as e:
109-
logger.error(f"cycle error: {e}")
110-
116+
last_backfill = run_collection_cycle(args.symbols, args.out_root, last_backfill)
111117
time.sleep(args.interval_seconds)
112118

113119

tests/test_collect_5min_bars.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from __future__ import annotations
2+
3+
from datetime import datetime, timezone
4+
from pathlib import Path
5+
6+
import pandas as pd
7+
8+
from scripts import collect_5min_bars
9+
10+
11+
def test_collection_cycle_continues_after_symbol_failure(
12+
monkeypatch,
13+
tmp_path: Path,
14+
) -> None:
15+
fetched: list[str] = []
16+
appended: list[str] = []
17+
18+
def fake_fetch(symbol: str, *, limit: int) -> pd.DataFrame:
19+
fetched.append(symbol)
20+
if symbol == "BADUSDT":
21+
raise RuntimeError("temporary fetch failure")
22+
return pd.DataFrame([{"timestamp": pd.Timestamp("2026-01-01T00:00:00Z")}])
23+
24+
def fake_append(path: Path, new: pd.DataFrame) -> int:
25+
appended.append(path.stem)
26+
assert not new.empty
27+
return len(new)
28+
29+
monkeypatch.setattr(collect_5min_bars, "fetch_recent_5m", fake_fetch)
30+
monkeypatch.setattr(collect_5min_bars, "append_bars", fake_append)
31+
monkeypatch.setattr(collect_5min_bars, "daily_backfill", lambda symbols, out_root: None)
32+
33+
last_backfill = collect_5min_bars.run_collection_cycle(
34+
["BADUSDT", "GOODUSDT"],
35+
tmp_path,
36+
None,
37+
)
38+
39+
assert fetched == ["BADUSDT", "GOODUSDT"]
40+
assert appended == ["GOODUSDT"]
41+
assert isinstance(last_backfill, datetime)
42+
43+
44+
def test_collection_cycle_preserves_backfill_time_after_scheduler_failure(
45+
monkeypatch,
46+
tmp_path: Path,
47+
) -> None:
48+
previous_backfill = datetime(2026, 1, 1, tzinfo=timezone.utc)
49+
50+
monkeypatch.setattr(
51+
collect_5min_bars,
52+
"fetch_recent_5m",
53+
lambda symbol, *, limit: pd.DataFrame(),
54+
)
55+
monkeypatch.setattr(collect_5min_bars, "append_bars", lambda path, new: 0)
56+
57+
def fail_backfill(symbols: list[str], out_root: Path) -> None:
58+
raise RuntimeError("backfill scheduler failure")
59+
60+
monkeypatch.setattr(collect_5min_bars, "daily_backfill", fail_backfill)
61+
62+
last_backfill = collect_5min_bars.run_collection_cycle(
63+
["DOGEUSDT"],
64+
tmp_path,
65+
previous_backfill,
66+
)
67+
68+
assert last_backfill == previous_backfill

0 commit comments

Comments
 (0)