feat(run): auto-save corpus samples + annotated FIRE screenshots
- samples/: full frame saved every time accepted colour changes (enough diversity for the labelled corpus, no constant-N-seconds flood). - logs/fires/: annotated frame saved on every BUY/SELL trigger, attached to the Discord/Telegram Alert so the push includes a visual. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -377,6 +377,12 @@ def run_live(cfg, duration_s=None, capture_stub: bool = False) -> None:
|
||||
start = time.monotonic()
|
||||
heartbeat_due = start + cfg.heartbeat_min * 60
|
||||
levels_extractor = None
|
||||
last_saved_color: str | None = None
|
||||
samples_dir = Path("samples")
|
||||
samples_dir.mkdir(exist_ok=True)
|
||||
fires_dir = Path("logs") / "fires"
|
||||
fires_dir.mkdir(parents=True, exist_ok=True)
|
||||
import cv2 # type: ignore[import-untyped]
|
||||
|
||||
try:
|
||||
while duration_s is None or (time.monotonic() - start) < duration_s:
|
||||
@@ -400,11 +406,31 @@ def run_live(cfg, duration_s=None, capture_stub: bool = False) -> None:
|
||||
"ts": now, "event": "tick",
|
||||
"color": res.color, "state": tr.next.value, "reason": tr.reason,
|
||||
})
|
||||
# corpus: save full frame on each new distinct color for later labeling
|
||||
if res.color != last_saved_color:
|
||||
ts_str = datetime.fromtimestamp(now).strftime("%Y%m%d_%H%M%S")
|
||||
sample_path = samples_dir / f"{ts_str}_{res.color}.png"
|
||||
try:
|
||||
cv2.imwrite(str(sample_path), frame)
|
||||
except Exception as exc:
|
||||
audit.log({"ts": now, "event": "sample_save_failed", "error": str(exc)})
|
||||
last_saved_color = res.color
|
||||
# FIRE: annotate frame + save, attach to alert
|
||||
if tr.trigger and not tr.locked:
|
||||
ts_str = datetime.fromtimestamp(now).strftime("%Y%m%d_%H%M%S")
|
||||
fire_path = fires_dir / f"{ts_str}_{tr.trigger}.png"
|
||||
try:
|
||||
annotated = frame.copy()
|
||||
x, y, w, h = cfg.dot_roi.x, cfg.dot_roi.y, cfg.dot_roi.w, cfg.dot_roi.h
|
||||
cv2.rectangle(annotated, (x, y), (x + w, y + h), (0, 255, 255), 2)
|
||||
cv2.imwrite(str(fire_path), annotated)
|
||||
except Exception:
|
||||
fire_path = None
|
||||
notifier.send(Alert(
|
||||
kind="trigger",
|
||||
title=f"{tr.trigger} signal",
|
||||
body=f"@ {now}",
|
||||
body=f"@ {datetime.fromtimestamp(now).isoformat(timespec='seconds')}",
|
||||
image_path=fire_path,
|
||||
direction=tr.trigger,
|
||||
))
|
||||
levels_extractor = LevelsExtractor(cfg, tr.trigger, now)
|
||||
|
||||
Reference in New Issue
Block a user