From 9207197a5686d801a611fceeb64445056c6703e2 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Wed, 15 Apr 2026 22:03:36 +0000 Subject: [PATCH] initial: scaffold atm trading monitor (Faza 1) Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 64 ++++ conftest.py | 5 + ...aude-master-design-20260415-atm-trading.md | 149 ++++++++ docs/happy-swinging-mccarthy.md | 43 +++ docs/image.png | Bin 0 -> 163674 bytes docs/partitioned-honking-unicorn.md | 258 ++++++++++++++ docs/swirling-drifting-starfish.md | 74 ++++ pyproject.toml | 37 ++ src/atm/__init__.py | 1 + src/atm/audit.py | 54 +++ src/atm/config.py | 170 +++++++++ src/atm/notifier/__init__.py | 18 + src/atm/notifier/discord.py | 42 +++ src/atm/notifier/fanout.py | 128 +++++++ src/atm/notifier/telegram.py | 55 +++ src/atm/state_machine.py | 224 ++++++++++++ src/atm/vision.py | 162 +++++++++ tests/__init__.py | 0 tests/test_audit.py | 107 ++++++ tests/test_notifier.py | 221 ++++++++++++ tests/test_state_machine.py | 327 ++++++++++++++++++ 21 files changed, 2139 insertions(+) create mode 100644 .gitignore create mode 100644 conftest.py create mode 100644 docs/claude-master-design-20260415-atm-trading.md create mode 100644 docs/happy-swinging-mccarthy.md create mode 100644 docs/image.png create mode 100644 docs/partitioned-honking-unicorn.md create mode 100644 docs/swirling-drifting-starfish.md create mode 100644 pyproject.toml create mode 100644 src/atm/__init__.py create mode 100644 src/atm/audit.py create mode 100644 src/atm/config.py create mode 100644 src/atm/notifier/__init__.py create mode 100644 src/atm/notifier/discord.py create mode 100644 src/atm/notifier/fanout.py create mode 100644 src/atm/notifier/telegram.py create mode 100644 src/atm/state_machine.py create mode 100644 src/atm/vision.py create mode 100644 tests/__init__.py create mode 100644 tests/test_audit.py create mode 100644 tests/test_notifier.py create mode 100644 tests/test_state_machine.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b410ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,64 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +*.egg +MANIFEST +.pytest_cache/ +.coverage +.coverage.* +htmlcov/ +.tox/ +.nox/ +.hypothesis/ +.mypy_cache/ +.ruff_cache/ +.pyright/ + +# Virtualenv +.venv/ +venv/ +env/ +ENV/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +.DS_Store + +# ATM runtime artefacts +logs/*.jsonl +logs/dead_letter.jsonl +samples/*.png +samples/*.jpg +samples/labels.json +trades.jsonl + +# configs: keep template + current marker, not generated calibration +configs/*.toml +!configs/example.toml + +# Secrets +config.toml +.env +*.secret + +# Images in root (e.g. reference screenshots) +/image.png diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..2f72d59 --- /dev/null +++ b/conftest.py @@ -0,0 +1,5 @@ +"""Root conftest — add src/ to sys.path so 'import atm' works without install.""" +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent / "src")) diff --git a/docs/claude-master-design-20260415-atm-trading.md b/docs/claude-master-design-20260415-atm-trading.md new file mode 100644 index 0000000..6906a43 --- /dev/null +++ b/docs/claude-master-design-20260415-atm-trading.md @@ -0,0 +1,149 @@ +# Design: ATM — Automated Trading Monitor (M2D Strategy) + +Generated by /office-hours on 2026-04-15 +Branch: master +Repo: /workspace/atm (greenfield) +Status: APPROVED +Mode: Builder (personal live-trading tool, high-stakes) + +## Problem Statement + +User trades the M2D strategy on DIA (TradeStation chart with custom indicator) with execution on TradeLocker US30 CFD (prop firm account). Same strategy also applies to GLD → XAUUSD. Bridging signal source (TradeStation Windows app) with execution (TradeLocker web) currently requires user to watch both screens for 4 hours per evening. Goal: bot detects the trigger signal automatically and notifies user via Telegram/Discord with chart screenshot + SL/TP levels so user can execute the trade in TradeLocker. + +## Strategy M2D — Full Spec + +**Setup:** TradeStation, 3-minute chart, DIA (or GLD) symbol, custom indicator "M2D MAPS" that renders a horizontal strip of colored dots below the price panel. Dots are indexed by time, y-position is fixed. + +### BUY sequence (sequential in time, rightmost N dots): +1. **Turquoise dot** — 15-minute buy trigger +2. **Dark green dot** — 3-minute sell +3. **Light green dot** — 3-minute buy → **TRIGGER** + +At trigger: +- Execute BUY on TradeLocker, instrument US30 CFD +- Stop Loss 0.6% +- Volume 0.1 lots maximum +- TP1, TP2, SL are drawn automatically as horizontal lines on the TradeStation chart after entry +- User manual lifecycle: at TP1 close half, move SL to ~breakeven; at TP2 close remaining half + +### SELL sequence (mirror): +1. **Yellow dot** — 15-minute sell (red 15min candle) +2. **Dark red dot** — 3-minute buy +3. **Light red dot** — 3-minute sell → **TRIGGER** + +Same size (0.1 lots), same SL %, same TP management. + +### Instrument mapping (intentional asymmetry): +- DIA chart (TradeStation) ↔ US30 CFD (TradeLocker) +- GLD chart (TradeStation) ↔ XAUUSD CFD (TradeLocker) + +### Trading window: +- NY open first 2 hours + NY close last 2 hours +- RO summer time: 16:30-18:30 and 21:00-23:00 +- Typical frequency: 1 trade per evening + +## Constraints + +- **Prop firm account on TradeLocker.** Faza 2 (auto-execution) requires reading prop TOS first — many prop firms prohibit automation or detect robotic timing patterns. +- No API on TradeLocker. No signal export on TradeStation for compiled custom indicator. +- Bot runs on the same Windows machine as TradeStation. Cross-machine (RDP/VNC) screenshot adds latency and fragility. + +## Premises (agreed) + +1. Screenshot + visual detection is the only viable bridge. +2. Notification-first (Faza 1) is the right sequencing. Zero-click MVP removes all financial bug risk. +3. M2D MAPS dot strip has stable y-position on fixed TradeStation layout → ROI color sampling is the right detection method. +4. DIA→US30 price divergence is acceptable risk (user's judgment, has been trading this pairing live). +5. Bot runs on the same Windows machine as TradeStation. + +## Recommended Approach — B: Structured Service with Dry-Run and Audit Log + +Python package on Windows, structured for clean extension to Faza 2. + +### Components: +- **Detector core:** `mss` screenshot of TradeStation window (located by title via `pygetwindow`) → crop M2D MAPS ROI → scan rightmost N dot positions → classify each by closest-color match with tolerance → feed into state machine that tracks 3-dot sequences (turquoise→dark-green→light-green = BUY trigger; yellow→dark-red→light-red = SELL trigger). +- **Level extractor:** after trigger, scan chart region for horizontal colored lines (SL/TP1/TP2). Convert pixel y to price via calibration of y-axis scale. +- **Calibration tool (Tkinter):** interactive — user clicks on each dot color sample, captures RGB + tolerance, clicks on ROI corners, captures y-axis price references. Writes to `config.toml`. +- **Dry-run mode:** runs detector against a folder of saved screenshots (recorded during normal operation). Shows what notification WOULD have been sent for each. Used to validate new color thresholds or strategy tweaks without live risk. +- **Notifier abstraction:** interface with Discord webhook and Telegram bot implementations. Sends: annotated screenshot + decoded SL/TP1/TP2 prices + signal type (BUY/SELL) + timestamp. +- **Audit log (JSONL):** every detection cycle — timestamp, detected dots, classification, decision, notification sent y/n. Replayable, debuggable. +- **Scheduler:** Windows Task Scheduler entry, auto-start/stop at 16:30 / 18:30 / 21:00 / 23:00 local time (summer/winter offset aware). + +### Structure: +``` +atm/ +├── pyproject.toml +├── config.toml # populated by calibration tool +├── src/atm/ +│ ├── detector.py # screenshot + color classification + state machine +│ ├── levels.py # SL/TP1/TP2 pixel-to-price extraction +│ ├── notifier/ +│ │ ├── __init__.py # abstract Notifier +│ │ ├── discord.py +│ │ └── telegram.py +│ ├── audit.py # JSONL logger +│ ├── calibrate.py # Tkinter UI +│ ├── dryrun.py # replay on saved screenshots +│ └── main.py # orchestration + scheduler hooks +├── samples/ # saved screenshots for dry-run corpus +└── logs/ # JSONL audit +``` + +### Detection algorithm (core loop): +1. Every 1 second during trading window: + - Locate TradeStation window + - If not foreground or minimized, log + skip + - Screenshot M2D MAPS ROI (fixed offsets from window bounds) + - For rightmost N=5 dot positions, sample center pixel, classify to nearest labeled color within tolerance + - Update rolling window of last 10 dots with their timestamps + - Evaluate state machine: did the last 3 classified dots (within a bounded time window) complete a BUY or SELL sequence? + - If trigger fired AND not already fired for this bar: extract SL/TP1/TP2 levels, send notification, log, mark fired. + +### Anti-duplicate logic: +- Each trigger dot is keyed by (x-pixel position at capture, color). Once fired, stored in "recently fired" set with 10-minute TTL. Prevents re-fire if same dot persists across cycles. + +### Sanity guards: +- If classification confidence (color distance) low for 3+ cycles in a row → push "bot lost sight" alert to user. Layout may have changed. +- If TradeStation window not found for 60 seconds → push "bot cannot find chart" alert. + +## Open Questions (non-blocking) + +- Exact color tolerance values — determined during calibration session, not a design question. +- GLD/XAUUSD: same M2D indicator on GLD chart? Assume yes, confirm during calibration. +- Multi-symbol monitoring — single window switched manually, or two TradeStation windows side by side? Defer; v1 = single chart at a time, user switches manually. + +## Success Criteria (Faza 1) + +- Over 20 live trading sessions, bot detects ≥95% of signals user also spotted manually. +- Zero false-positive notifications during the bot's first 5 sessions (tune tolerances aggressively). +- Notification delivered within 3 seconds of trigger dot appearing. +- Audit log lets user reproduce "why was no notification sent" for any missed signal. + +## Distribution Plan + +Personal tool, single user. No distribution channel needed — runs locally on user's Windows box. Git repo at `/workspace/atm`. `pyproject.toml` + `pip install -e .` for local dev. No CI/CD; user's own `scheduled task` starts/stops it. + +## Risk Flag — Faza 2 (deferred) + +Before extending to auto-execution in TradeLocker: +1. Read prop firm TOS (search for "EA", "automation", "bot", "copy trading", "external signal"). If prohibited, **Faza 2 is off the table** — tool stays notification-only. +2. If permitted, implement via Playwright browser automation against TradeLocker web UI. +3. Add human-like click timing randomization (100-400ms jitter) to avoid robotic detection. +4. Dry-run mode then becomes: "click coordinates resolved, action NOT sent" — user reviews the intended click before enabling live. + +## Next Steps (concrete) + +1. Init `/workspace/atm` as Python project. `pyproject.toml`, basic structure. +2. Build calibration tool first. Without calibrated config, nothing works. +3. Record 20-30 sample screenshots across several trading sessions (can start this today — doesn't need any code yet; just `mss` screenshot on a 5-second timer dumping to disk). +4. Build detector + state machine. Validate against recorded screenshots in dry-run mode. +5. Wire Discord webhook first (simpler than Telegram bot). Test end-to-end on live session. +6. Add audit log. +7. Schedule Windows task for trading hours. + +## What I noticed about how you think + +- You explicitly asked for dry-run before writing a line of code. "Să verific dacă vrea să apese corect, fără să apese efectiv." That's not a common instinct for someone building their own tool; it's the instinct of someone who has already had something break expensively. +- You phased the project yourself — "faza 2 după ce mă conving că merge." That's the right ordering and you arrived at it unprompted. +- When I challenged the API premise, you answered with specifics: the indicator is custom, the account doesn't support API. You knew the constraint, not guessed it. +- You flagged the prop account almost casually at the end. A lot of builders would have skipped that detail. It turned out to be the most important constraint in the entire design. diff --git a/docs/happy-swinging-mccarthy.md b/docs/happy-swinging-mccarthy.md new file mode 100644 index 0000000..1d31520 --- /dev/null +++ b/docs/happy-swinging-mccarthy.md @@ -0,0 +1,43 @@ +# Plan: ATM Eng Review — Findings Applied + +## Context + +User ran `/plan-eng-review` on `partitioned-honking-unicorn.md` (ATM trading monitor, Faza 1). Eng review complete. All 4 decisions resolved, obvious fixes applied, plan file updated in place. + +## Where the changes live + +The reviewed plan (with all eng-review edits) is at: +**`/home/claude/.claude/plans/partitioned-honking-unicorn.md`** + +Test plan artifact at: +**`~/.gstack/projects/romfast-workspace/claude-master-eng-review-test-plan-20260415-212932.md`** + +## What changed in the reviewed plan + +### 4 decisions (AskUserQuestion) +1. **Bar flicker** → debounce depth=1 (configurable); screenshot in alert = visual check. +2. **Phase A entry price** → dropped; Phase A is direction + screenshot only; user puts manual 0.6% SL in TradeLocker; Phase B sends real levels from chart. +3. **Notifier blocking** → fire-and-forget worker threads per backend, bounded queue, retry + dead-letter. +4. **Alert SPoF** → Discord + Telegram parallel from day 1. + +### Obvious fixes (stated, applied) +- Exhaustive state transition table (default-noise rule, SELL mirror explicit, phase-skip handling). +- Python 3.11+ pin → drop `tomli`, use stdlib `tomllib`. +- Windows symlink replaced by `configs/current.txt` marker file. +- New `vision.py` shared module (ROI/hash/interp/Hough). +- `@dataclass Config` with load-time validation. +- DPI check added to calibrate + README note. + +### Test coverage +Expanded from state-machine-only to: every module + 1 E2E replay harness. Acceptance gate unchanged (precision=100%, recall≥95% on labeled corpus). + +## Verification (post-implementation) + +Run the full verification checklist from `partitioned-honking-unicorn.md` (sections 1-9). Specifically: +- `pytest tests/` — all new unit tests + E2E replay pass. +- `atm dryrun ./samples` hits acceptance gate. +- Live 2-session test: both Discord and Telegram fire; kill one mid-session and confirm the other still delivers + dead-letter file gets the failed alert. + +## Status + +**CEO + ENG CLEARED.** No further reviews required before implementation. Design + DX reviews properly skipped (no UI scope; personal single-user tool). Run `/ship` after implementation. diff --git a/docs/image.png b/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..be291ad9280a7d9132c5df48c1b5259836741d1a GIT binary patch literal 163674 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 jo=LwYYW;tc5KRp{Z+6blGnw;eL{3vx_d$iq tolerance → `UNKNOWN`, state unchanged. + +--- + +## Levels Extraction (two-phase, simplified) + +**Phase A — at trigger (immediate alert to Discord + Telegram):** +- No entry-price compute. No spec-math SL/TP. User places a manual 0.6% SL in TradeLocker at entry; actual TP1/TP2/SL come in Phase B from the chart. +- Notification: `🟢 BUY signal DIA→US30 | 22:47:03` + annotated screenshot (detected dot highlighted). + +**Phase B — after user trades (chart-scan confirmation):** +- After Phase A fires, detector keeps watching the chart ROI for horizontal colored lines (red=SL, green=TP1/TP2). +- When lines appear (user has entered trade in TradeLocker and TradeStation drew them) → scan y-pixels via Hough + color mask, convert via y-axis calibration → send second alert to both channels: `✅ Levels: SL=484.35 | TP1=485.20 | TP2=485.88`. +- If chart-line scan times out (no lines in 10 min) → silent (user didn't trade). +- If only 2 lines detected (user didn't set TP2 or line not rendered yet) → partial-result alert. +- Phase B overlap with next signal: guarded by per-direction lockout + Phase-B completion flag; a new FIRE cannot issue until prior Phase B closes (timeout or success). + +--- + +## Dedup / Lockout + +- Time-based lockout: after any FIRE, block re-fire for 4 minutes (one 3-min bar + 1 min safety). +- Tracked per-direction: BUY lockout doesn't block SELL. +- Stored as single timestamp per direction (not pixel-keyed). + +--- + +## Observability + +- **Heartbeat:** every 30 min to a separate Discord thread (not main alerts channel): `🟢 22:00 alive | 0 triggers | confidence avg 0.85 | chart OK`. Silence >35 min = watchdog concern (user notices). +- **Layout canary:** every 60 cycles (5 min), hash a stable reference region (axis labels, chart border). Stored baseline in config. On significant divergence (>threshold) → `⚠️ Layout changed — auto-paused, recalibrate` to alerts channel. Bot pauses detection until operator acknowledges (touch a pause-file or restart). +- **Low-confidence alert:** 3+ consecutive cycles with confidence below threshold → `⚠️ Bot lost sight` (already in original plan). +- **Window-lost alert:** TradeStation window not found for 60s → `⚠️ Cannot find chart`. +- **Audit JSONL:** per-cycle, daily rotation (`logs/YYYY-MM-DD.jsonl`), fields: `{ts, window_found, roi_ok, rightmost_dot_color, confidence, state, transition, trigger, notified, reason}`. + +--- + +## Files to Create + +- `/workspace/atm/pyproject.toml` — Python 3.11+ required. Deps: `mss`, `opencv-python`, `numpy`, `requests`, `pygetwindow`, `pywin32` (DPI + window capture), `rich` (CLI), `pillow` (screenshot annotation). **No `tomli` — use stdlib `tomllib`.** +- `/workspace/atm/config.toml` — populated by calibration tool (ROI coords, per-color RGB + tolerance, `debounce_depth`, y-axis scale, canary-region baseline hash, Discord webhook URL, Telegram bot token + chat_id) +- `/workspace/atm/src/atm/config.py` — **[ENG-REVIEW]** `@dataclass Config` with `Config.load(path)` that validates on load (RGB tuples, positive tolerances, both notifier credentials present, y-axis 2-point pair). Fail fast at startup. +- `/workspace/atm/src/atm/vision.py` — **[ENG-REVIEW]** shared primitives: ROI crop, perceptual hash, pixel-to-price linear interp, Hough line detection with color mask. Used by detector/canary/levels to avoid drift. +- `/workspace/atm/src/atm/detector.py` — screenshot loop, rightmost-dot scan, color classification, rolling window, debounce +- `/workspace/atm/src/atm/state_machine.py` — explicit phased state machine (spec above), exhaustive transition table +- `/workspace/atm/src/atm/levels.py` — Phase B chart-scan only (Phase A entry-price compute removed after ENG-REVIEW) +- `/workspace/atm/src/atm/canary.py` — layout fingerprint hash + drift check + auto-pause +- `/workspace/atm/src/atm/notifier/__init__.py` — abstract `Notifier` protocol: `send_alert()`, `send_heartbeat()`, `send_levels_confirm()` +- `/workspace/atm/src/atm/notifier/fanout.py` — **[ENG-REVIEW]** `FanoutNotifier` wraps N backends, each with its own worker thread + bounded queue (size 50, drop-oldest on overflow) + retry with exponential backoff + dead-letter file on total failure. Main loop never blocks. +- `/workspace/atm/src/atm/notifier/discord.py` — webhook POST, annotated screenshot upload (multipart) +- `/workspace/atm/src/atm/notifier/telegram.py` — **[ENG-REVIEW]** built in parallel with Discord (no longer deferred); bot API, photo upload +- `/workspace/atm/src/atm/audit.py` — JSONL logger with daily local-midnight rotation, line-buffered write for crash safety +- `/workspace/atm/src/atm/calibrate.py` — Tkinter: window pick → DPI check → ROI corners → per-color sample → y-axis scale → canary region → save versioned config +- `/workspace/atm/src/atm/labeler.py` — **[EXPANSION]** Tkinter label UI → `labels.json` +- `/workspace/atm/src/atm/dryrun.py` — replay with precision/recall/confusion matrix when labels present +- `/workspace/atm/src/atm/journal.py` — **[EXPANSION]** `atm journal` CLI → `trades.jsonl` +- `/workspace/atm/src/atm/report.py` — **[EXPANSION]** weekly aggregation +- `/workspace/atm/src/atm/main.py` — CLI: `atm calibrate`, `atm label `, `atm dryrun `, `atm run [--duration Xh]`, `atm journal`, `atm report [--week YYYY-WW]` +- `/workspace/atm/tests/` — **[ENG-REVIEW]** unit + E2E per test plan at `~/.gstack/projects/romfast-workspace/claude-master-eng-review-test-plan-20260415-212932.md` +- `/workspace/atm/samples/`, `/workspace/atm/logs/` +- `/workspace/atm/configs/` — versioned config archive. **[ENG-REVIEW]** No symlink (Windows admin-required); use `configs/current.txt` marker file storing the active filename. `Config.load()` reads the marker. +- `/workspace/atm/docs/phase2-prop-firm-audit.md` — structured TOS checklist +- `/workspace/atm/README.md` — setup, calibration workflow, per-session operating checklist, DPI/multi-monitor notes + +--- + +## Build Order + +1. **`pyproject.toml` + package scaffold** — Python 3.11+, `pip install -e .`, `atm --help` works. +2. **Standalone screenshot-dump script** — `mss` timer dumps to `samples/` every 5s during trading sessions. Build corpus in parallel. +3. **`config.py` + `vision.py`** — Config dataclass with validation; shared vision primitives. Ship with unit tests for config load + pixel-to-price interp. +4. **`calibrate.py`** — versioned config in `configs/YYYY-MM-DD-HHMM.toml`; `configs/current.txt` marker file points at active. DPI check + canary region capture. +5. **`labeler.py`** — once ~30 samples exist, tag them. `labels.json` is ground truth. +6. **`state_machine.py`** + **unit tests** (clean BUY, clean SELL, cooling, opposite-rearm, lockout per-direction, noise, phase-skip, all state×color pairs via parameterized test). +7. **`detector.py`** + **unit tests** (empty/background ROI, rightmost-cluster, rolling window FIFO, debounce depth=1, classification edges including UNKNOWN). +8. **`canary.py`** + **unit tests** (drift threshold, pause-file gating). +9. **`levels.py`** (Phase B only) + **unit tests** (Hough line detection with color mask, 2 vs 3 lines, 10-min timeout, pixel-to-price roundtrip). +10. **`notifier/fanout.py` + `discord.py` + `telegram.py`** + **unit tests** (queue overflow drop-oldest, 429 backoff, dead-letter on total failure, fanout: one backend down still delivers). Both channels built in parallel — fire together from day 1. +11. **`audit.py`** + **unit tests** (daily rotation at local midnight, line-buffered flush crash safety). +12. **`dryrun.py`** — replay on `samples/` against `labels.json`. **Acceptance gate before live: precision = 100%, recall ≥ 95%.** +13. **E2E replay test** — feed `samples/` through detector → state_machine → notifier-mock → in-memory audit; assert labels match FIREs. +14. **`journal.py`**, **`report.py`**, **`main.py`** (unified CLI). +15. **Windows Task Scheduler setup** — 16:30→18:30, 21:00→23:00. `atm run --duration 2h`. Manual DST check twice yearly. +16. **`docs/phase2-prop-firm-audit.md`** — TOS checklist template. + +--- + +## Existing Utilities to Reuse + +Greenfield Python project. No internal utilities. External libs: `mss` (screenshot), `pygetwindow` (window locate), `opencv-python` (line detection in Phase B), `numpy` (color math), `requests` (Discord webhook), `tomli` (config parsing), `pillow` (annotated screenshots). + +--- + +## Verification + +End-to-end, in build order: + +1. **State machine unit tests:** `pytest tests/test_state_machine.py` — all scenarios (clean BUY, clean SELL, cooling, rearm, lockout, noise) pass. +2. **Calibration:** `atm calibrate` → step through → `config.toml` populated with plausible RGBs for described colors + y-axis scale sane + canary region picked. +3. **Labeled corpus:** ≥30 screenshots in `samples/`, `atm label ./samples` tags each. +4. **Dry-run with metrics:** `atm dryrun ./samples` → precision + recall + confusion matrix printed. **Acceptance gate:** precision = 100%, recall ≥ 95%. If not met → tune tolerances, re-run. +5. **Live test notification-only (2 sessions):** `atm run`. Verify: + - Discord + Telegram notifications within 5s of trigger, both channels receive. + - Phase A message: direction + timestamp + annotated screenshot. + - Phase B levels-alert fires once TradeStation draws SL/TP lines; correct SL/TP1/TP2 prices. + - Heartbeat messages every 30 min in thread. + - Audit JSONL complete, state transitions visible. + - Kill one notifier (e.g. wrong token) → other still delivers, dead-letter file for failed one. +6. **Canary test:** manually move TradeStation window during session → layout-changed alert within 5 min. Move back → restart bot → resumes. +7. **Scheduler test:** Windows Task Scheduler starts bot at 16:30, stops at 18:30 cleanly, log rotates at midnight. +8. **Journal test:** after real trade, `atm journal` → prompt flow complete → `trades.jsonl` entry present. +9. **Report test:** after 1 week of live use, `atm report --week 2026-16` → precision per color, slippage distribution, P&L summary. + +--- + +## Risk Register + +- **Prop firm TOS (Faza 2 blocker):** read TOS using `docs/phase2-prop-firm-audit.md` checklist before any auto-execution work. If EA/automation prohibited → Faza 2 dead, stay on Faza 1 permanently. +- **TradeStation layout change:** canary catches it within 5 min → auto-pause. Recalibrate. Losing a session to a layout change is acceptable cost. +- **Calibration drift over time:** versioned configs in `configs/` let you roll back to last-known-good if new calibration misfires. +- **DIA↔US30 price divergence:** accepted (user's judgment). Phase 1 journal captures slippage per signal, feeding Faza 2 go/no-go. +- **Screen sharing / RDP during trading:** overlay can break classification. Low prob, documented in README as operator hygiene. +- **Windows Task Scheduler DST transitions:** twice per year, schedule may misfire. Manual check first week of each DST change. + +--- + +## Out of Scope (Faza 1) + +- Any automated click in TradeLocker (Faza 2 work) +- Multi-symbol concurrent monitoring (single chart at a time; user switches manually between DIA and GLD) +- Backtesting on historical data (strategy already manually validated) +- Web UI / dashboard (headless + Discord/Telegram only) +- Ack feedback loop (react-on-notification labeling) — deferred to TODOS.md as `P2-ack-loop`: shipping baseline first, adding feedback once detection quality verified +- Telegram notifier — built only after Discord is stable 5+ sessions + +--- + +## Accepted Expansions (CEO review, SELECTIVE mode) + +1. ✅ **Labeled sample corpus + dry-run metrics** — `labeler.py`, `labels.json`, automated precision/recall in dryrun. Makes acceptance criteria ("false-positives = 0, false-negatives ≤ 5%") machine-checkable. +2. ✅ **Level-extractor fallback (spec-math)** — Phase A always uses spec-math; Phase B validates against chart. Redundancy on fragile piece. +3. ✅ **Layout canary + auto-pause** — `canary.py` hashes stable UI region, auto-pauses on drift. Catches silent classification-with-wrong-positions failure mode. +4. ✅ **Trade journal CLI** — `atm journal` + `trades.jsonl` + weekly report. Data for Faza 2 go/no-go decision. +5. ✅ **Prop-firm TOS audit checklist** — `docs/phase2-prop-firm-audit.md`. Structured Faza 2 evaluation framework shipped now. + +## Deferred to TODOS.md + +- **Ack feedback loop** — Discord reaction emojis feeding precision tuning. High value, operationally heavier (bot vs webhook). Add after Faza 1 baseline stable. + +--- + +## GSTACK REVIEW REPORT + +| Review | Trigger | Why | Runs | Status | Findings | +|--------|---------|-----|------|--------|----------| +| CEO Review | `/plan-ceo-review` | Scope & strategy | 1 | CLEAR (SELECTIVE EXPANSION) | 6 proposals, 5 accepted, 1 deferred; 2 arch corrections | +| Codex Review | `/codex review` | Independent 2nd opinion | 0 | — | — | +| Eng Review | `/plan-eng-review` | Architecture & tests (required) | 1 | CLEAR (FULL_REVIEW) | 9 issues found, 0 critical gaps; 4 decisions made, 0 unresolved | +| Design Review | `/plan-design-review` | UI/UX gaps | 0 | — | SKIPPED (no UI scope — CLI + Discord/Telegram) | +| DX Review | `/plan-devex-review` | Developer experience gaps | 0 | — | SKIPPED (personal tool, single user) | + +**UNRESOLVED:** 0 + +**ENG REVIEW DECISIONS:** +1. **Bar flicker** → debounce depth=1 (configurable), rely on screenshot-in-notification for visual verification. +2. **Phase A entry price** → dropped. User places manual 0.6% SL in TradeLocker at entry. Phase A = direction + screenshot only. Phase B = real SL/TP1/TP2 from chart. +3. **Notifier blocking** → fire-and-forget worker threads per backend, bounded queue (size 50, drop-oldest), retry w/ backoff, dead-letter on total failure. +4. **Alert SPoF** → Discord + Telegram built in parallel from day 1, both fire together. + +**ENG REVIEW OBVIOUS FIXES (stated, no decision):** +- Exhaustive state transition table (all state×color pairs, default-noise rule, SELL mirror explicit). +- Python 3.11+ pin, drop `tomli` dep, use stdlib `tomllib`. +- Windows symlink → `configs/current.txt` marker file. +- Shared `vision.py` module (ROI, hash, interp, Hough). +- `@dataclass Config` with fail-fast load-time validation. +- DPI check + multi-monitor note in calibrate + README. + +**ENG REVIEW TEST SCOPE (accepted: FULL):** unit tests for every module (state_machine, detector, levels Phase B, canary, audit, notifier fanout/retry, calibrate roundtrip, config validate) + 1 E2E replay harness asserting labeled-corpus precision/recall. Test plan artifact: `~/.gstack/projects/romfast-workspace/claude-master-eng-review-test-plan-20260415-212932.md`. + +**VERDICT:** CEO + ENG CLEARED — ready to implement. Run `/ship` after implementation. No further reviews required before build. diff --git a/docs/swirling-drifting-starfish.md b/docs/swirling-drifting-starfish.md new file mode 100644 index 0000000..08b5643 --- /dev/null +++ b/docs/swirling-drifting-starfish.md @@ -0,0 +1,74 @@ +# Plan: ATM — Automated Trading Monitor (M2D, Faza 1) + +## Context + +User tranzacționează manual strategia M2D pe DIA (TradeStation) cu execuție pe TradeLocker US30 CFD (cont prop firm). Aceeași strategie merge și pe GLD → XAUUSD. 4 ore/seară trebuie să urmărească 2 ecrane. Obiectiv Faza 1: bot detectează automat trigger-ul și trimite notificare Telegram/Discord cu screenshot + nivele SL/TP1/TP2, user execută manual în TradeLocker. Faza 2 (auto-execution) deferată până prop firm TOS verificat + Faza 1 dovedită. + +Design doc complet salvat la `~/.gstack/projects/romfast-workspace/claude-master-design-20260415-atm-trading.md` (include strategia M2D cu toate detaliile). + +## Approach: B — Structured Python service + dry-run + audit log + +Rulează pe aceeași mașină Windows cu TradeStation. ROI color sampling pe strip-ul M2D MAPS, state machine pentru secvența 3-dot, notifier abstraction (Discord/Telegram), calibration Tkinter, dry-run pe screenshot-uri salvate. + +## Files to Create + +- `/workspace/atm/pyproject.toml` — packaging, deps: `mss`, `opencv-python`, `numpy`, `requests`, `pygetwindow`, `tomli` +- `/workspace/atm/config.toml` — populat de calibration tool (ROI coords, culori referință + toleranțe, y-axis scale) +- `/workspace/atm/src/atm/detector.py` — screenshot loop + color classification + state machine 3-dot +- `/workspace/atm/src/atm/levels.py` — extragere SL/TP1/TP2 din liniile orizontale (pixel y → preț) +- `/workspace/atm/src/atm/notifier/__init__.py` — interface `Notifier.send(signal, screenshot, levels)` +- `/workspace/atm/src/atm/notifier/discord.py` — webhook POST +- `/workspace/atm/src/atm/notifier/telegram.py` — bot API +- `/workspace/atm/src/atm/audit.py` — JSONL logger, fiecare ciclu +- `/workspace/atm/src/atm/calibrate.py` — Tkinter UI: click pe dot → capture RGB + tolerance; click pe colț ROI → salvează; click pe 2 puncte pe axa Y cu prețurile → calibrare scale +- `/workspace/atm/src/atm/dryrun.py` — replay detector pe folder de screenshot-uri +- `/workspace/atm/src/atm/main.py` — orchestration, CLI (`atm run`, `atm calibrate`, `atm dryrun `) +- `/workspace/atm/samples/` — director screenshot-uri pentru dry-run corpus +- `/workspace/atm/logs/` — director JSONL audit +- `/workspace/atm/README.md` — setup + calibration workflow + +## Build Order + +1. **`pyproject.toml` + scaffold package** — `pip install -e .`, `atm --help` funcționează. +2. **Script standalone de capture samples** (înainte de orice logică) — rulezi în timpul următoarelor sesiuni trading, dump screenshot la 5s interval în `samples/`. Ai corpus pentru dry-run. +3. **`calibrate.py`** — fără config calibrat, nimic nu merge. Tkinter cu: pas 1 (select TradeStation window by title), pas 2 (click pe colțuri ROI M2D MAPS), pas 3 (click pe fiecare culoare: turquoise, verde închis, verde deschis, galben, roșu închis, roșu deschis + gri neutru; capturează RGB + rază de toleranță implicită 20), pas 4 (2 click-uri pe axa Y + valori preț introduse → scale factor pixel→preț). Salvează `config.toml`. +4. **`detector.py`** — loop 1s: locate window, screenshot ROI, sample rightmost 5 dots pe pozițiile calibrate, clasifică fiecare la cea mai apropiată culoare (Euclidean in RGB cu toleranță). Rolling window ultimele 10 clasificări + timestamp. State machine: ultimele 3 non-gri consecutive = secvență BUY sau SELL? Fire o dată pe trigger (dedup set cu TTL 10min). +5. **`levels.py`** — după trigger, scan chart region pentru liniile orizontale roșii (SL) și verzi (TP1/TP2). Extrage y-pixel al fiecărei linii, convertește la preț folosind scale-ul calibrat. +6. **`notifier/discord.py`** — POST multipart cu screenshot adnotat + mesaj formatat: `🟢 BUY DIA→US30 | SL: 484.35 | TP1: 485.20 | TP2: 485.90 | 22:47:03`. +7. **`dryrun.py`** — iterează `samples/`, rulează detector, printează ce AR fi trimis. Validare logică detecție înainte de live. +8. **`audit.py`** — wrap detector loop, scrie JSONL: `{ts, window_found, roi_ok, dots:[...], classification:[...], trigger:null|"BUY"|"SELL", notified:true|false, reason}`. +9. **`main.py`** — CLI unificat. `atm calibrate`, `atm dryrun ./samples`, `atm run` (loop live cu audit). +10. **Windows Task Scheduler** — 2 task-uri: start 16:30 (stop 18:30), start 21:00 (stop 23:00). `atm run --duration 2h`. +11. **`notifier/telegram.py`** — opțional după ce Discord e stabil. + +## Existing Utilities to Reuse + +N/A — greenfield project. No internal utilities to reuse. + +## Verification + +End-to-end, în ordinea din build: + +1. **Calibration workflow:** `atm calibrate` → urmezi pașii → rezultă `config.toml` complet. Verifică manual că RGB-urile sunt plauzibile pentru culorile descrise. +2. **Dry-run corpus:** ai ≥20 screenshot-uri din sesiuni reale în `samples/`. Rulezi `atm dryrun ./samples` → output per screenshot: clasificare + decizie trigger. Manual verifici că cazurile unde ai văzut tu semnal reali → trigger; cazurile neutre → no-trigger. False-positives = 0 țintă, false-negatives ≤ 5%. +3. **Live test notification-only (2 sesiuni):** `atm run` în fereastra trading. Verifici: + - Notificările Discord apar în 3s de când vezi trigger-ul pe chart. + - Screenshot atașat e clar, lizibil. + - SL/TP1/TP2 extrase sunt la ≤$0.05 de nivelele reale pe chart. + - Audit log (`logs/YYYY-MM-DD.jsonl`) conține fiecare ciclu; poți reproduce un missed signal. +4. **Sanity alerts:** mută/redimensionează fereastra TradeStation → bot detectează "window lost" în 60s → notificare. Restabilește fereastra → bot reia. +5. **Scheduler validation:** Windows Task Scheduler pornește `atm run` la 16:30, se oprește curat la 18:30, audit log salvează fără corupere. + +## Risk Register + +- **Prop firm TOS (Faza 2 blocker, NU Faza 1):** înainte de orice extensie spre auto-execution în TradeLocker, citești TOS-ul prop-ului, cauți "EA / automation / bot / copy trading / external signals". Dacă e interzis, Faza 2 e moartă și rămâi permanent pe Faza 1. +- **Indicator layout change:** dacă TradeStation update schimbă render-ul M2D MAPS → re-calibration. Audit log va arăta degradare graduală a confidence-ului → alert activ via "bot lost sight". +- **Price divergence DIA↔US30:** trigger-ul se dă pe DIA; poate fi o secundă unde US30 deja a mișcat diferit. Risc acceptabil (judgment user), dar monitorizat în Faza 2 prin slippage analysis. +- **Screenshot pe ecran sharing / AnyDesk / RDP:** dacă cineva se conectează remote la Windows-ul tău în timpul trading, screenshot-urile pot cuprinde overlay-uri nepotrivite. Mic, dar notabil. + +## Out of Scope (Faza 1) + +- Orice click automat în TradeLocker +- Multi-symbol concurrent monitoring (single chart la un moment dat) +- Backtesting pe date istorice (strategia e deja validată manual) +- UI / dashboard web — totul rulează headless cu notificări externe diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f267c7d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,37 @@ +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "atm" +version = "0.1.0" +description = "M2D Trading Monitor — TradeStation chart watcher, Discord/Telegram notifier" +requires-python = ">=3.11" +dependencies = [ + "numpy>=1.26", + "opencv-python>=4.9", + "pillow>=10.0", + "requests>=2.31", + "rich>=13.0", +] + +[project.optional-dependencies] +windows = [ + "mss>=9.0", + "pygetwindow>=0.0.9", + "pywin32>=306", +] +dev = [ + "pytest>=8.0", + "pytest-cov>=5.0", +] + +[project.scripts] +atm = "atm.main:main" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +pythonpath = ["src"] diff --git a/src/atm/__init__.py b/src/atm/__init__.py new file mode 100644 index 0000000..3dc1f76 --- /dev/null +++ b/src/atm/__init__.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/src/atm/audit.py b/src/atm/audit.py new file mode 100644 index 0000000..7a9e1a8 --- /dev/null +++ b/src/atm/audit.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import json +from datetime import datetime, date +from pathlib import Path +from typing import Callable, IO + + +class AuditLog: + def __init__( + self, + base_dir: Path, + clock: Callable[[], datetime] | None = None, + ) -> None: + self._base_dir = Path(base_dir) + self._clock: Callable[[], datetime] = clock or datetime.now + self._current_date: date | None = None + self._fh: IO[str] | None = None + + def log(self, event: dict) -> None: + now = self._clock() + today = now.date() + + if today != self._current_date: + self._open(today) + + if "ts" not in event: + event = {**event, "ts": now.isoformat()} + + assert self._fh is not None + self._fh.write(json.dumps(event, separators=(",", ":")) + "\n") + + def close(self) -> None: + if self._fh is not None: + try: + self._fh.close() + except Exception: + pass + finally: + self._fh = None + + @property + def current_path(self) -> Path: + if self._current_date is None: + today = self._clock().date() + return self._base_dir / f"{today}.jsonl" + return self._base_dir / f"{self._current_date}.jsonl" + + def _open(self, today: date) -> None: + self.close() + self._base_dir.mkdir(parents=True, exist_ok=True) + path = self._base_dir / f"{today}.jsonl" + self._fh = open(path, "a", buffering=1, encoding="utf-8") + self._current_date = today diff --git a/src/atm/config.py b/src/atm/config.py new file mode 100644 index 0000000..bc40c19 --- /dev/null +++ b/src/atm/config.py @@ -0,0 +1,170 @@ +"""Config dataclass with load-time validation. Fail fast.""" +from __future__ import annotations + +import tomllib +from dataclasses import dataclass +from pathlib import Path +from typing import Literal + +DotColor = Literal[ + "turquoise", "yellow", + "dark_green", "dark_red", + "light_green", "light_red", + "gray", "background", +] + +VALID_COLORS: tuple[str, ...] = ( + "turquoise", "yellow", "dark_green", "dark_red", + "light_green", "light_red", "gray", "background", +) + + +@dataclass(frozen=True) +class ROI: + x: int + y: int + w: int + h: int + + def __post_init__(self) -> None: + if self.w <= 0 or self.h <= 0: + raise ValueError(f"ROI w/h must be positive: {self}") + if self.x < 0 or self.y < 0: + raise ValueError(f"ROI x/y must be non-negative: {self}") + + +@dataclass(frozen=True) +class ColorSpec: + rgb: tuple[int, int, int] + tolerance: float + + def __post_init__(self) -> None: + if len(self.rgb) != 3 or any(not (0 <= c <= 255) for c in self.rgb): + raise ValueError(f"rgb must be 3 ints 0-255: {self.rgb}") + if self.tolerance <= 0: + raise ValueError(f"tolerance must be positive: {self.tolerance}") + + +@dataclass(frozen=True) +class YAxisCalib: + """Two reference points (pixel_y, price) for linear interp.""" + p1_y: int + p1_price: float + p2_y: int + p2_price: float + + def __post_init__(self) -> None: + if self.p1_y == self.p2_y: + raise ValueError("y-axis calibration points must differ in y") + + +@dataclass(frozen=True) +class CanaryRegion: + roi: ROI + baseline_phash: str + drift_threshold: int = 8 + + +@dataclass(frozen=True) +class DiscordCfg: + webhook_url: str + + def __post_init__(self) -> None: + if not self.webhook_url.startswith("http"): + raise ValueError("discord webhook_url required") + + +@dataclass(frozen=True) +class TelegramCfg: + bot_token: str + chat_id: str + + def __post_init__(self) -> None: + if not self.bot_token or not self.chat_id: + raise ValueError("telegram bot_token + chat_id required") + + +@dataclass(frozen=True) +class Config: + window_title: str + dot_roi: ROI + chart_roi: ROI + colors: dict[str, ColorSpec] + y_axis: YAxisCalib + canary: CanaryRegion + discord: DiscordCfg + telegram: TelegramCfg + debounce_depth: int = 1 + loop_interval_s: float = 5.0 + heartbeat_min: int = 30 + lockout_s: int = 240 + low_conf_threshold: float = 0.2 + low_conf_run: int = 3 + phaseb_timeout_s: int = 600 + dead_letter_path: str = "logs/dead_letter.jsonl" + config_version: str = "unknown" + + def __post_init__(self) -> None: + required = {"turquoise", "yellow", "dark_green", "dark_red", + "light_green", "light_red", "gray"} + missing = required - set(self.colors.keys()) + if missing: + raise ValueError(f"config.colors missing: {missing}") + if self.debounce_depth < 1: + raise ValueError("debounce_depth >= 1") + if self.loop_interval_s <= 0: + raise ValueError("loop_interval_s > 0") + + @classmethod + def load(cls, path: str | Path) -> "Config": + p = Path(path) + data = tomllib.loads(p.read_text(encoding="utf-8")) + return cls._from_dict(data, version=p.stem) + + @classmethod + def load_current(cls, configs_dir: str | Path) -> "Config": + """Resolve configs/current.txt → active config file.""" + d = Path(configs_dir) + marker = d / "current.txt" + if not marker.exists(): + raise FileNotFoundError(f"marker not found: {marker}") + name = marker.read_text(encoding="utf-8").strip() + return cls.load(d / name) + + @classmethod + def _from_dict(cls, data: dict, version: str = "unknown") -> "Config": + roi = ROI(**data["dot_roi"]) + chart = ROI(**data["chart_roi"]) + colors = {k: ColorSpec(rgb=tuple(v["rgb"]), tolerance=float(v["tolerance"])) + for k, v in data["colors"].items()} + y = YAxisCalib(**data["y_axis"]) + canary = CanaryRegion( + roi=ROI(**data["canary"]["roi"]), + baseline_phash=data["canary"]["baseline_phash"], + drift_threshold=int(data["canary"].get("drift_threshold", 8)), + ) + discord = DiscordCfg(webhook_url=data["discord"]["webhook_url"]) + telegram = TelegramCfg( + bot_token=data["telegram"]["bot_token"], + chat_id=str(data["telegram"]["chat_id"]), + ) + opts = data.get("options", {}) + return cls( + window_title=data["window_title"], + dot_roi=roi, + chart_roi=chart, + colors=colors, + y_axis=y, + canary=canary, + discord=discord, + telegram=telegram, + debounce_depth=int(opts.get("debounce_depth", 1)), + loop_interval_s=float(opts.get("loop_interval_s", 5.0)), + heartbeat_min=int(opts.get("heartbeat_min", 30)), + lockout_s=int(opts.get("lockout_s", 240)), + low_conf_threshold=float(opts.get("low_conf_threshold", 0.2)), + low_conf_run=int(opts.get("low_conf_run", 3)), + phaseb_timeout_s=int(opts.get("phaseb_timeout_s", 600)), + dead_letter_path=opts.get("dead_letter_path", "logs/dead_letter.jsonl"), + config_version=version, + ) diff --git a/src/atm/notifier/__init__.py b/src/atm/notifier/__init__.py new file mode 100644 index 0000000..d556859 --- /dev/null +++ b/src/atm/notifier/__init__.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass +from pathlib import Path +from typing import Protocol + + +@dataclass +class Alert: + kind: str # "trigger" | "heartbeat" | "levels" | "warn" + title: str + body: str + image_path: Path | None = None # annotated screenshot + direction: str | None = None # "BUY"/"SELL" when kind=trigger + + +class Notifier(Protocol): + name: str + + def send(self, alert: Alert) -> None: ... # raises on failure diff --git a/src/atm/notifier/discord.py b/src/atm/notifier/discord.py new file mode 100644 index 0000000..fc1526b --- /dev/null +++ b/src/atm/notifier/discord.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import requests + +from . import Alert + + +class DiscordNotifier: + name = "discord" + + def __init__(self, webhook_url: str, session: Any = None) -> None: + self._url = webhook_url + self._session = session or requests.Session() + + def send(self, alert: Alert) -> None: + content = f"**{alert.title}**\n{alert.body}" + + if alert.image_path and Path(alert.image_path).exists(): + with open(alert.image_path, "rb") as fh: + resp = self._session.post( + self._url, + data={"content": content}, + files={"file": fh}, + timeout=10, + ) + else: + resp = self._session.post( + self._url, + json={"content": content}, + timeout=10, + ) + + if resp.status_code == 429: + raise RuntimeError(f"Discord rate-limited (429): {resp.text}") + if resp.status_code >= 500: + raise RuntimeError(f"Discord server error ({resp.status_code}): {resp.text}") + # 200 / 204 are OK; other 4xx are treated as errors too + if not (resp.status_code in (200, 204)): + raise RuntimeError(f"Discord unexpected status ({resp.status_code}): {resp.text}") diff --git a/src/atm/notifier/fanout.py b/src/atm/notifier/fanout.py new file mode 100644 index 0000000..db8c113 --- /dev/null +++ b/src/atm/notifier/fanout.py @@ -0,0 +1,128 @@ +from __future__ import annotations + +import json +import queue +import threading +import time +from copy import copy +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +from . import Alert, Notifier + +_SENTINEL = object() + + +@dataclass +class _Stats: + sent: int = 0 + failed: int = 0 + dropped: int = 0 + retries: int = 0 + + +class FanoutNotifier: + def __init__( + self, + backends: list[Notifier], + dead_letter_path: Path, + queue_size: int = 50, + max_retries: int = 3, + backoff_base: float = 0.5, + ) -> None: + self._backends = backends + self._dead_letter_path = Path(dead_letter_path) + self._queue_size = queue_size + self._max_retries = max_retries + self._backoff_base = backoff_base + self._dl_lock = threading.Lock() + + self._queues: dict[str, queue.Queue[Any]] = {} + self._stats: dict[str, _Stats] = {} + self._workers: list[threading.Thread] = [] + + for backend in backends: + q: queue.Queue[Any] = queue.Queue(maxsize=queue_size) + self._queues[backend.name] = q + self._stats[backend.name] = _Stats() + t = threading.Thread( + target=self._worker, + args=(backend, q), + daemon=True, + name=f"fanout-{backend.name}", + ) + t.start() + self._workers.append(t) + + def send(self, alert: Alert) -> None: + alert_copy = copy(alert) + for backend in self._backends: + q = self._queues[backend.name] + stats = self._stats[backend.name] + if q.full(): + try: + q.get_nowait() + stats.dropped += 1 + except queue.Empty: + pass + q.put(alert_copy) + + def stop(self, timeout: float = 5.0) -> None: + for q in self._queues.values(): + q.put(_SENTINEL) + for w in self._workers: + w.join(timeout=timeout) + + def stats(self) -> dict[str, dict[str, int]]: + return { + name: { + "sent": s.sent, + "failed": s.failed, + "dropped": s.dropped, + "retries": s.retries, + } + for name, s in self._stats.items() + } + + def _worker(self, backend: Notifier, q: queue.Queue[Any]) -> None: + stats = self._stats[backend.name] + while True: + item = q.get() + if item is _SENTINEL: + break + self._dispatch(backend, item, stats) + + def _dispatch(self, backend: Notifier, alert: Alert, stats: _Stats) -> None: + last_exc: Exception | None = None + for attempt in range(self._max_retries + 1): + if attempt > 0: + delay = self._backoff_base * (2 ** (attempt - 1)) + time.sleep(delay) + stats.retries += 1 + try: + backend.send(alert) + stats.sent += 1 + return + except Exception as exc: + last_exc = exc + + # Exhausted all retries + stats.failed += 1 + self._write_dead_letter(backend.name, alert, last_exc) + + def _write_dead_letter( + self, backend_name: str, alert: Alert, exc: Exception | None + ) -> None: + record = { + "backend": backend_name, + "alert_title": alert.title, + "alert_kind": alert.kind, + "error_str": str(exc) if exc else "", + "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + } + line = json.dumps(record) + "\n" + with self._dl_lock: + self._dead_letter_path.parent.mkdir(parents=True, exist_ok=True) + with open(self._dead_letter_path, "a", encoding="utf-8") as fh: + fh.write(line) diff --git a/src/atm/notifier/telegram.py b/src/atm/notifier/telegram.py new file mode 100644 index 0000000..fe76d26 --- /dev/null +++ b/src/atm/notifier/telegram.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import requests + +from . import Alert + +_BASE = "https://api.telegram.org/bot{token}/{method}" + + +class TelegramNotifier: + name = "telegram" + + def __init__(self, bot_token: str, chat_id: str, session: Any = None) -> None: + self._token = bot_token + self._chat_id = chat_id + self._session = session or requests.Session() + + def _url(self, method: str) -> str: + return _BASE.format(token=self._token, method=method) + + def send(self, alert: Alert) -> None: + text = f"*{alert.title}*\n{alert.body}" + + if alert.image_path and Path(alert.image_path).exists(): + with open(alert.image_path, "rb") as fh: + resp = self._session.post( + self._url("sendPhoto"), + data={ + "chat_id": self._chat_id, + "caption": text, + "parse_mode": "Markdown", + }, + files={"photo": fh}, + timeout=10, + ) + else: + resp = self._session.post( + self._url("sendMessage"), + json={ + "chat_id": self._chat_id, + "text": text, + "parse_mode": "Markdown", + }, + timeout=10, + ) + + if resp.status_code == 429: + raise RuntimeError(f"Telegram rate-limited (429): {resp.text}") + if resp.status_code >= 500: + raise RuntimeError(f"Telegram server error ({resp.status_code}): {resp.text}") + if resp.status_code != 200: + raise RuntimeError(f"Telegram unexpected status ({resp.status_code}): {resp.text}") diff --git a/src/atm/state_machine.py b/src/atm/state_machine.py new file mode 100644 index 0000000..031c5e3 --- /dev/null +++ b/src/atm/state_machine.py @@ -0,0 +1,224 @@ +"""ATM trading state machine — pure stdlib, no cv2/numpy.""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Literal + + +class State(Enum): + IDLE = "IDLE" + ARMED_BUY = "ARMED_BUY" + PRIMED_BUY = "PRIMED_BUY" + ARMED_SELL = "ARMED_SELL" + PRIMED_SELL = "PRIMED_SELL" + + +DotColor = Literal[ + "turquoise", "yellow", "dark_green", "dark_red", "light_green", "light_red", "gray" +] + + +@dataclass(frozen=True) +class Transition: + prev: State + next: State + event: str + reason: str + trigger: Literal["BUY", "SELL"] | None = None + locked: bool = False + arm_ts: float | None = None + prime_ts: float | None = None + fire_ts: float | None = None + + +class StateMachine: + """Finite state machine for ATM dot-color trading signals. + + BUY side and SELL side are mirrors of each other: + turquoise ↔ yellow + dark_green ↔ dark_red + light_green ↔ light_red + + Lockout: after a fire event, same-direction fires within lockout_s + seconds produce locked=True transitions (caller suppresses notify). + Opposite direction is unaffected. + """ + + def __init__(self, lockout_s: int = 240) -> None: + self._state = State.IDLE + self._lockout_s = lockout_s + # arm_ts / prime_ts are reset per-arm / per-prime cycle + self._arm_ts: float | None = None + self._prime_ts: float | None = None + # last fire timestamps per direction + self._last_fire: dict[str, float] = {} + + @property + def state(self) -> State: + return self._state + + # ------------------------------------------------------------------ + # Core feed + # ------------------------------------------------------------------ + + def feed(self, color: DotColor, ts: float) -> Transition: # noqa: C901 + prev = self._state + + match self._state: + case State.IDLE: + t = self._from_idle(color, ts) + case State.ARMED_BUY: + t = self._from_armed(color, ts, direction="BUY") + case State.ARMED_SELL: + t = self._from_armed(color, ts, direction="SELL") + case State.PRIMED_BUY: + t = self._from_primed(color, ts, direction="BUY") + case State.PRIMED_SELL: + t = self._from_primed(color, ts, direction="SELL") + case _: # pragma: no cover + t = self._noise(color, ts) + + assert t.prev == prev + self._state = t.next + return t + + # ------------------------------------------------------------------ + # State handlers + # ------------------------------------------------------------------ + + def _from_idle(self, color: DotColor, ts: float) -> Transition: + prev = State.IDLE + match color: + case "turquoise": + self._arm_ts = ts + self._prime_ts = None + return Transition(prev, State.ARMED_BUY, color, "arm", arm_ts=ts) + case "yellow": + self._arm_ts = ts + self._prime_ts = None + return Transition(prev, State.ARMED_SELL, color, "arm", arm_ts=ts) + case "light_green" | "light_red": + return Transition(prev, State.IDLE, color, "phase_skip") + case _: + # dark_green, dark_red, gray → noise + return Transition(prev, State.IDLE, color, "noise") + + def _from_armed( + self, color: DotColor, ts: float, direction: Literal["BUY", "SELL"] + ) -> Transition: + """Handle events from ARMED_BUY or ARMED_SELL.""" + prev = State.ARMED_BUY if direction == "BUY" else State.ARMED_SELL + # Map color names to logical roles for the given direction + arm_color = "turquoise" if direction == "BUY" else "yellow" + opposite_arm = "yellow" if direction == "BUY" else "turquoise" + prime_color = "dark_green" if direction == "BUY" else "dark_red" + skip_correct = "light_green" if direction == "BUY" else "light_red" + skip_wrong = "light_red" if direction == "BUY" else "light_green" + primed_state = State.PRIMED_BUY if direction == "BUY" else State.PRIMED_SELL + opposite_state = State.ARMED_SELL if direction == "BUY" else State.ARMED_BUY + + match color: + case c if c == "gray": + return Transition(prev, prev, color, "persist", arm_ts=self._arm_ts) + case c if c == arm_color: + # refresh: update arm_ts + self._arm_ts = ts + return Transition(prev, prev, color, "refresh", arm_ts=ts) + case c if c == prime_color: + self._prime_ts = ts + return Transition( + prev, primed_state, color, "prime", + arm_ts=self._arm_ts, prime_ts=ts, + ) + case c if c == opposite_arm: + self._arm_ts = ts + self._prime_ts = None + return Transition( + prev, opposite_state, color, "opposite_rearm", arm_ts=ts + ) + case c if c == skip_correct: + # phase_skip — no fire + self._arm_ts = None + self._prime_ts = None + return Transition(prev, State.IDLE, color, "phase_skip") + case c if c == skip_wrong: + self._arm_ts = None + self._prime_ts = None + return Transition(prev, State.IDLE, color, "phase_skip") + case _: + # dark_red (for BUY) or dark_green (for SELL) → ignore + return Transition(prev, prev, color, "ignore", arm_ts=self._arm_ts) + + def _from_primed( + self, color: DotColor, ts: float, direction: Literal["BUY", "SELL"] + ) -> Transition: + """Handle events from PRIMED_BUY or PRIMED_SELL.""" + prev = State.PRIMED_BUY if direction == "BUY" else State.PRIMED_SELL + accumulate_color = "dark_green" if direction == "BUY" else "dark_red" + ignore_color = "dark_red" if direction == "BUY" else "dark_green" + fire_color = "light_green" if direction == "BUY" else "light_red" + skip_color = "light_red" if direction == "BUY" else "light_green" + arm_color = "turquoise" if direction == "BUY" else "yellow" + opposite_arm = "yellow" if direction == "BUY" else "turquoise" + armed_self = State.ARMED_BUY if direction == "BUY" else State.ARMED_SELL + armed_opp = State.ARMED_SELL if direction == "BUY" else State.ARMED_BUY + + match color: + case c if c == accumulate_color: + return Transition( + prev, prev, color, "accumulate", + arm_ts=self._arm_ts, prime_ts=self._prime_ts, + ) + case c if c == ignore_color: + return Transition( + prev, prev, color, "ignore", + arm_ts=self._arm_ts, prime_ts=self._prime_ts, + ) + case c if c == fire_color: + locked = self._is_locked(direction, ts) + fire_ts = ts + self._last_fire[direction] = ts + self._arm_ts = None + self._prime_ts = None + return Transition( + prev, State.IDLE, color, "fire", + trigger=direction, + locked=locked, + fire_ts=fire_ts, + ) + case c if c == skip_color: + self._arm_ts = None + self._prime_ts = None + return Transition(prev, State.IDLE, color, "phase_skip") + case c if c == "gray": + self._arm_ts = None + self._prime_ts = None + return Transition(prev, State.IDLE, color, "cooled") + case c if c == arm_color: + self._arm_ts = ts + self._prime_ts = None + return Transition(prev, armed_self, color, "rearm", arm_ts=ts) + case c if c == opposite_arm: + self._arm_ts = ts + self._prime_ts = None + return Transition(prev, armed_opp, color, "opposite_rearm", arm_ts=ts) + case _: + return Transition( + prev, prev, color, "noise", + arm_ts=self._arm_ts, prime_ts=self._prime_ts, + ) + + # ------------------------------------------------------------------ + # Helpers + # ------------------------------------------------------------------ + + def _noise(self, color: DotColor, ts: float) -> Transition: # pragma: no cover + return Transition(self._state, self._state, color, "noise") + + def _is_locked(self, direction: str, ts: float) -> bool: + last = self._last_fire.get(direction) + if last is None: + return False + return (ts - last) < self._lockout_s diff --git a/src/atm/vision.py b/src/atm/vision.py new file mode 100644 index 0000000..1a40f0c --- /dev/null +++ b/src/atm/vision.py @@ -0,0 +1,162 @@ +"""Shared vision primitives: ROI crop, perceptual hash, pixel↔price interp, Hough lines.""" +from __future__ import annotations + +from dataclasses import dataclass + +import cv2 +import numpy as np + +from .config import ROI, YAxisCalib + + +def crop_roi(img: np.ndarray, roi: ROI) -> np.ndarray: + """Crop RGB/BGR image by ROI. Raises on out-of-bounds.""" + h, w = img.shape[:2] + if roi.x + roi.w > w or roi.y + roi.h > h: + raise ValueError(f"ROI {roi} exceeds image {w}x{h}") + return img[roi.y:roi.y + roi.h, roi.x:roi.x + roi.w].copy() + + +def phash(img: np.ndarray, size: int = 16) -> str: + """Perceptual hash via DCT. Returns hex string. Image may be RGB/BGR/gray.""" + if img.ndim == 3: + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + else: + gray = img + small = cv2.resize(gray, (32, 32), interpolation=cv2.INTER_AREA).astype(np.float32) + dct = cv2.dct(small) + block = dct[:size, :size] + med = np.median(block[1:, 1:]) # skip DC term + bits = (block > med).flatten() + # pack into hex + out = 0 + for b in bits: + out = (out << 1) | int(b) + return f"{out:0{len(bits) // 4}x}" + + +def hamming_hex(a: str, b: str) -> int: + if len(a) != len(b): + raise ValueError("hash length mismatch") + return bin(int(a, 16) ^ int(b, 16)).count("1") + + +def pixel_y_to_price(y: int, calib: YAxisCalib) -> float: + """Linear interp from screen-y to price. y grows downward; prices usually invert.""" + dy = calib.p2_y - calib.p1_y + dp = calib.p2_price - calib.p1_price + return calib.p1_price + (y - calib.p1_y) * (dp / dy) + + +def price_to_pixel_y(price: float, calib: YAxisCalib) -> int: + dy = calib.p2_y - calib.p1_y + dp = calib.p2_price - calib.p1_price + return int(round(calib.p1_y + (price - calib.p1_price) * (dy / dp))) + + +@dataclass(frozen=True) +class ColorMatch: + name: str + distance: float + confidence: float # 1 - d_nearest / d_second + + +def classify_pixel( + rgb: tuple[int, int, int], + palette: dict[str, tuple[tuple[int, int, int], float]], +) -> ColorMatch: + """Nearest-color in RGB Euclidean. palette: name → (rgb, tolerance). + + Returns UNKNOWN if nearest exceeds its tolerance. + """ + best_name = "UNKNOWN" + best_d = float("inf") + second_d = float("inf") + best_tol = 0.0 + p = np.array(rgb, dtype=np.float32) + for name, (ref, tol) in palette.items(): + d = float(np.linalg.norm(p - np.array(ref, dtype=np.float32))) + if d < best_d: + second_d = best_d + best_d = d + best_name = name + best_tol = tol + elif d < second_d: + second_d = d + if best_d > best_tol: + return ColorMatch(name="UNKNOWN", distance=best_d, confidence=0.0) + conf = 1.0 - (best_d / second_d) if second_d > 0 else 1.0 + return ColorMatch(name=best_name, distance=best_d, confidence=max(0.0, conf)) + + +def find_rightmost_dot( + roi_img: np.ndarray, + bg_rgb: tuple[int, int, int], + bg_tol: float = 15.0, + min_cluster_px: int = 3, +) -> tuple[int, int] | None: + """Scan ROI right→left; return (x, y) of rightmost non-background cluster center. + + roi_img is BGR (OpenCV convention). bg_rgb is RGB. + """ + bgr_bg = np.array([bg_rgb[2], bg_rgb[1], bg_rgb[0]], dtype=np.float32) + diff = np.linalg.norm(roi_img.astype(np.float32) - bgr_bg, axis=2) + mask = diff > bg_tol # True = non-background + _h, w = mask.shape + for x in range(w - 1, -1, -1): + col = mask[:, x] + if col.sum() >= min_cluster_px: + ys = np.where(col)[0] + return (x, int(ys.mean())) + return None + + +def pixel_rgb(roi_img: np.ndarray, x: int, y: int, box: int = 3) -> tuple[int, int, int]: + """Sample mean RGB of a (2*box+1)² patch around (x,y). Input BGR → returns RGB.""" + h, w = roi_img.shape[:2] + x0, x1 = max(0, x - box), min(w, x + box + 1) + y0, y1 = max(0, y - box), min(h, y + box + 1) + patch = roi_img[y0:y1, x0:x1] + mean = patch.reshape(-1, 3).mean(axis=0) + # BGR → RGB + return (int(mean[2]), int(mean[1]), int(mean[0])) + + +def detect_color_lines( + chart_img: np.ndarray, + color_rgb: tuple[int, int, int], + tol: float = 30.0, + min_line_length_frac: float = 0.3, +) -> list[int]: + """Find horizontal lines of given color. Returns list of y-coords (sorted). + + chart_img is BGR. Color mask + Hough for near-horizontal lines. + """ + _h, w = chart_img.shape[:2] + bgr_target = np.array([color_rgb[2], color_rgb[1], color_rgb[0]], dtype=np.float32) + diff = np.linalg.norm(chart_img.astype(np.float32) - bgr_target, axis=2) + mask = (diff < tol).astype(np.uint8) * 255 + if mask.sum() == 0: + return [] + min_len = max(10, int(w * min_line_length_frac)) + lines = cv2.HoughLinesP( + mask, + rho=1, theta=np.pi / 180, + threshold=min_len, + minLineLength=min_len, + maxLineGap=int(w * 0.05), + ) + if lines is None: + return [] + ys: list[int] = [] + for _x1, y1, _x2, y2 in lines[:, 0]: + if abs(y2 - y1) <= 2: # near-horizontal + ys.append(int((y1 + y2) / 2)) + # merge near-duplicates within 3 px + ys.sort() + merged: list[int] = [] + for y in ys: + if merged and abs(y - merged[-1]) <= 3: + continue + merged.append(y) + return merged diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_audit.py b/tests/test_audit.py new file mode 100644 index 0000000..465eca8 --- /dev/null +++ b/tests/test_audit.py @@ -0,0 +1,107 @@ +"""Tests for AuditLog.""" +from __future__ import annotations + +import json +import os +from datetime import datetime +from pathlib import Path + +import pytest + +from atm.audit import AuditLog + + +def _dt(s: str) -> datetime: + return datetime.fromisoformat(s) + + +def test_writes_jsonl(tmp_path: Path) -> None: + clock_dt = _dt("2026-04-15T10:00:00") + log = AuditLog(tmp_path / "logs", clock=lambda: clock_dt) + + events = [ + {"msg": "first", "ts": "2026-04-15T10:00:00"}, + {"msg": "second", "ts": "2026-04-15T10:01:00"}, + {"msg": "third", "ts": "2026-04-15T10:02:00"}, + ] + for e in events: + log.log(e) + log.close() + + lines = (tmp_path / "logs" / "2026-04-15.jsonl").read_text().splitlines() + assert len(lines) == 3 + for original, line in zip(events, lines): + parsed = json.loads(line) + assert parsed["msg"] == original["msg"] + assert parsed["ts"] == original["ts"] + + +def test_daily_rotation(tmp_path: Path) -> None: + base = tmp_path / "logs" + times = [ + _dt("2026-04-15T23:59:59"), # just before midnight + _dt("2026-04-16T00:00:01"), # just after midnight + ] + idx = 0 + + def clock() -> datetime: + return times[min(idx, len(times) - 1)] + + log = AuditLog(base, clock=clock) + + log.log({"msg": "before"}) + idx = 1 + log.log({"msg": "after"}) + log.close() + + file_15 = base / "2026-04-15.jsonl" + file_16 = base / "2026-04-16.jsonl" + assert file_15.exists(), "File for Apr 15 should exist" + assert file_16.exists(), "File for Apr 16 should exist" + + lines_15 = [json.loads(l) for l in file_15.read_text().splitlines()] + lines_16 = [json.loads(l) for l in file_16.read_text().splitlines()] + assert lines_15[0]["msg"] == "before" + assert lines_16[0]["msg"] == "after" + + +def test_line_buffered(tmp_path: Path) -> None: + base = tmp_path / "logs" + clock_dt = _dt("2026-04-15T12:00:00") + log = AuditLog(base, clock=lambda: clock_dt) + log.log({"msg": "hello", "ts": "2026-04-15T12:00:00"}) + # Do NOT call close() — file should already have content due to line buffering + path = base / "2026-04-15.jsonl" + assert os.stat(path).st_size > 0 + log.close() + + +def test_adds_ts_when_missing(tmp_path: Path) -> None: + clock_dt = _dt("2026-04-15T09:30:00") + log = AuditLog(tmp_path / "logs", clock=lambda: clock_dt) + log.log({"msg": "hi"}) + log.close() + + lines = (tmp_path / "logs" / "2026-04-15.jsonl").read_text().splitlines() + parsed = json.loads(lines[0]) + assert "ts" in parsed + assert parsed["ts"] == "2026-04-15T09:30:00" + + +def test_preserves_existing_ts(tmp_path: Path) -> None: + clock_dt = _dt("2026-04-15T09:30:00") + log = AuditLog(tmp_path / "logs", clock=lambda: clock_dt) + log.log({"ts": "2026-01-01T00:00:00", "msg": "hi"}) + log.close() + + lines = (tmp_path / "logs" / "2026-04-15.jsonl").read_text().splitlines() + parsed = json.loads(lines[0]) + assert parsed["ts"] == "2026-01-01T00:00:00" + + +def test_close_idempotent(tmp_path: Path) -> None: + clock_dt = _dt("2026-04-15T10:00:00") + log = AuditLog(tmp_path / "logs", clock=lambda: clock_dt) + log.log({"msg": "x", "ts": "2026-04-15T10:00:00"}) + log.close() + log.close() # should not raise diff --git a/tests/test_notifier.py b/tests/test_notifier.py new file mode 100644 index 0000000..1cba552 --- /dev/null +++ b/tests/test_notifier.py @@ -0,0 +1,221 @@ +"""Tests for notifier module: FanoutNotifier, DiscordNotifier, TelegramNotifier.""" +from __future__ import annotations + +import json +import time +from pathlib import Path + +import pytest + +from atm.notifier import Alert +from atm.notifier.fanout import FanoutNotifier + + +# --------------------------------------------------------------------------- +# Fake backends +# --------------------------------------------------------------------------- + +class FakeBackend: + """Configurable fake backend for testing.""" + + def __init__( + self, + name: str = "fake", + always_fail: bool = False, + fail_first_n: int = 0, + sleep_s: float = 0.0, + ) -> None: + self.name = name + self._always_fail = always_fail + self._fail_first_n = fail_first_n + self._sleep_s = sleep_s + self._call_count = 0 + + def send(self, alert: Alert) -> None: + self._call_count += 1 + if self._sleep_s: + time.sleep(self._sleep_s) + if self._always_fail: + raise RuntimeError(f"{self.name}: simulated failure") + if self._call_count <= self._fail_first_n: + raise RuntimeError(f"{self.name}: simulated failure #{self._call_count}") + + +def _alert(title: str = "test", kind: str = "trigger") -> Alert: + return Alert(kind=kind, title=title, body="body text") + + +# --------------------------------------------------------------------------- +# FanoutNotifier tests +# --------------------------------------------------------------------------- + +def test_fanout_both_delivered(tmp_path: Path) -> None: + dl = tmp_path / "dead.jsonl" + b1 = FakeBackend("b1") + b2 = FakeBackend("b2") + fan = FanoutNotifier([b1, b2], dl, backoff_base=0.01) + for i in range(3): + fan.send(_alert(f"alert-{i}")) + fan.stop(timeout=5.0) + + s = fan.stats() + assert s["b1"]["sent"] == 3 + assert s["b2"]["sent"] == 3 + assert s["b1"]["failed"] == 0 + assert s["b2"]["failed"] == 0 + + +def test_one_backend_down_other_delivers(tmp_path: Path) -> None: + dl = tmp_path / "dead.jsonl" + ok_backend = FakeBackend("ok") + bad_backend = FakeBackend("bad", always_fail=True) + fan = FanoutNotifier( + [ok_backend, bad_backend], dl, max_retries=1, backoff_base=0.01 + ) + for i in range(2): + fan.send(_alert(f"a{i}")) + fan.stop(timeout=5.0) + + s = fan.stats() + assert s["ok"]["sent"] == 2 + assert s["bad"]["failed"] == 2 + + # dead letter file should have entries for the bad backend + assert dl.exists() + lines = [json.loads(l) for l in dl.read_text().splitlines()] + assert all(e["backend"] == "bad" for e in lines) + assert len(lines) == 2 + + +def test_dead_letter_on_exhausted_retries(tmp_path: Path) -> None: + dl = tmp_path / "dead.jsonl" + bad = FakeBackend("bad", always_fail=True) + fan = FanoutNotifier([bad], dl, max_retries=3, backoff_base=0.01) + fan.send(_alert("my-alert")) + fan.stop(timeout=5.0) + + s = fan.stats() + assert s["bad"]["failed"] == 1 + # retries = max_retries (3 extra attempts after first) + assert s["bad"]["retries"] == 3 + + assert dl.exists() + lines = [json.loads(l) for l in dl.read_text().splitlines()] + assert len(lines) == 1 + entry = lines[0] + assert entry["backend"] == "bad" + assert entry["alert_title"] == "my-alert" + assert "error_str" in entry + assert "timestamp" in entry + + +def test_queue_drop_oldest(tmp_path: Path) -> None: + dl = tmp_path / "dead.jsonl" + # slow backend: each send takes 0.5s so queue fills fast + slow = FakeBackend("slow", sleep_s=0.5) + fan = FanoutNotifier([slow], dl, queue_size=2, backoff_base=0.01) + + # Pump 10 alerts rapidly; worker can't keep up + for i in range(10): + fan.send(_alert(f"a{i}")) + + fan.stop(timeout=10.0) + s = fan.stats() + assert s["slow"]["dropped"] > 0 + assert s["slow"]["sent"] <= 2 + 1 # queue_size + possibly 1 in-flight + + +def test_retry_backoff_recovers(tmp_path: Path) -> None: + dl = tmp_path / "dead.jsonl" + # Fails only the very first call, succeeds after + b = FakeBackend("b", fail_first_n=1) + fan = FanoutNotifier([b], dl, max_retries=3, backoff_base=0.01) + fan.send(_alert("recover")) + fan.stop(timeout=5.0) + + s = fan.stats() + assert s["b"]["sent"] == 1 + assert s["b"]["retries"] == 1 + assert s["b"]["failed"] == 0 + assert not dl.exists() + + +def test_stop_drains(tmp_path: Path) -> None: + dl = tmp_path / "dead.jsonl" + b = FakeBackend("b") + fan = FanoutNotifier([b], dl, backoff_base=0.01) + for i in range(5): + fan.send(_alert(f"a{i}")) + fan.stop(timeout=5.0) + # All items should have been processed before stop returned + assert fan.stats()["b"]["sent"] == 5 + + +# --------------------------------------------------------------------------- +# DiscordNotifier unit tests (no real HTTP) +# --------------------------------------------------------------------------- + +class _MockResponse: + def __init__(self, status_code: int, text: str = "") -> None: + self.status_code = status_code + self.text = text + + +class _MockSession: + def __init__(self, status_code: int = 204) -> None: + self.status_code = status_code + self.calls: list[dict] = [] + + def post(self, url: str, **kwargs): + self.calls.append({"url": url, **kwargs}) + return _MockResponse(self.status_code) + + +def test_discord_send_ok() -> None: + from atm.notifier.discord import DiscordNotifier + session = _MockSession(204) + n = DiscordNotifier("https://discord.example/hook", session=session) + n.send(_alert("Hello")) + assert len(session.calls) == 1 + assert "**Hello**" in session.calls[0]["json"]["content"] + + +def test_discord_429_raises() -> None: + from atm.notifier.discord import DiscordNotifier + n = DiscordNotifier("https://discord.example/hook", session=_MockSession(429)) + with pytest.raises(RuntimeError, match="429"): + n.send(_alert("x")) + + +def test_discord_5xx_raises() -> None: + from atm.notifier.discord import DiscordNotifier + n = DiscordNotifier("https://discord.example/hook", session=_MockSession(500)) + with pytest.raises(RuntimeError, match="500"): + n.send(_alert("x")) + + +# --------------------------------------------------------------------------- +# TelegramNotifier unit tests (no real HTTP) +# --------------------------------------------------------------------------- + +def test_telegram_send_ok() -> None: + from atm.notifier.telegram import TelegramNotifier + session = _MockSession(200) + n = TelegramNotifier("token", "chat123", session=session) + n.send(_alert("Hi")) + assert len(session.calls) == 1 + assert "*Hi*" in session.calls[0]["json"]["text"] + + +def test_telegram_429_raises() -> None: + from atm.notifier.telegram import TelegramNotifier + n = TelegramNotifier("token", "chat123", session=_MockSession(429)) + with pytest.raises(RuntimeError, match="429"): + n.send(_alert("x")) + + +def test_telegram_5xx_raises() -> None: + from atm.notifier.telegram import TelegramNotifier + n = TelegramNotifier("token", "chat123", session=_MockSession(500)) + with pytest.raises(RuntimeError, match="500"): + n.send(_alert("x")) diff --git a/tests/test_state_machine.py b/tests/test_state_machine.py new file mode 100644 index 0000000..513c1d5 --- /dev/null +++ b/tests/test_state_machine.py @@ -0,0 +1,327 @@ +"""Tests for atm.state_machine.""" + +from __future__ import annotations + +import pytest + +from atm.state_machine import DotColor, State, StateMachine, Transition + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _drive(sm: StateMachine, events: list[tuple[DotColor, float]]) -> list[Transition]: + return [sm.feed(color, ts) for color, ts in events] + + +def _buy_sequence_to_primed(sm: StateMachine, start_ts: float = 1.0) -> None: + """Drive sm from IDLE → PRIMED_BUY (does not fire).""" + sm.feed("turquoise", start_ts) # → ARMED_BUY + sm.feed("dark_green", start_ts + 1) # → PRIMED_BUY + + +def _sell_sequence_to_primed(sm: StateMachine, start_ts: float = 1.0) -> None: + """Drive sm from IDLE → PRIMED_SELL (does not fire).""" + sm.feed("yellow", start_ts) # → ARMED_SELL + sm.feed("dark_red", start_ts + 1) # → PRIMED_SELL + + +# --------------------------------------------------------------------------- +# 1. clean_buy +# --------------------------------------------------------------------------- + + +def test_clean_buy() -> None: + sm = StateMachine() + + t1 = sm.feed("turquoise", 1.0) + assert t1.prev == State.IDLE + assert t1.next == State.ARMED_BUY + assert t1.reason == "arm" + assert sm.state == State.ARMED_BUY + + t2 = sm.feed("gray", 2.0) + assert t2.prev == State.ARMED_BUY + assert t2.next == State.ARMED_BUY + assert t2.reason == "persist" + assert sm.state == State.ARMED_BUY + + t3 = sm.feed("dark_green", 3.0) + assert t3.prev == State.ARMED_BUY + assert t3.next == State.PRIMED_BUY + assert t3.reason == "prime" + assert sm.state == State.PRIMED_BUY + + t4 = sm.feed("light_green", 4.0) + assert t4.prev == State.PRIMED_BUY + assert t4.next == State.IDLE + assert t4.reason == "fire" + assert t4.trigger == "BUY" + assert t4.locked is False + assert sm.state == State.IDLE + + +# --------------------------------------------------------------------------- +# 2. clean_sell (mirror of clean_buy) +# --------------------------------------------------------------------------- + + +def test_clean_sell() -> None: + sm = StateMachine() + + t1 = sm.feed("yellow", 1.0) + assert t1.prev == State.IDLE + assert t1.next == State.ARMED_SELL + assert t1.reason == "arm" + + t2 = sm.feed("gray", 2.0) + assert t2.prev == State.ARMED_SELL + assert t2.next == State.ARMED_SELL + assert t2.reason == "persist" + + t3 = sm.feed("dark_red", 3.0) + assert t3.prev == State.ARMED_SELL + assert t3.next == State.PRIMED_SELL + assert t3.reason == "prime" + + t4 = sm.feed("light_red", 4.0) + assert t4.prev == State.PRIMED_SELL + assert t4.next == State.IDLE + assert t4.reason == "fire" + assert t4.trigger == "SELL" + assert t4.locked is False + + +# --------------------------------------------------------------------------- +# 3. cooled +# --------------------------------------------------------------------------- + + +def test_cooled() -> None: + sm = StateMachine() + _buy_sequence_to_primed(sm, start_ts=1.0) + assert sm.state == State.PRIMED_BUY + + t = sm.feed("gray", 10.0) + assert t.prev == State.PRIMED_BUY + assert t.next == State.IDLE + assert t.reason == "cooled" + assert t.trigger is None + + +# --------------------------------------------------------------------------- +# 4. opposite_rearm from ARMED_BUY +# --------------------------------------------------------------------------- + + +def test_opposite_rearm_from_armed_buy() -> None: + sm = StateMachine() + sm.feed("turquoise", 1.0) # → ARMED_BUY + assert sm.state == State.ARMED_BUY + + t = sm.feed("yellow", 2.0) + assert t.prev == State.ARMED_BUY + assert t.next == State.ARMED_SELL + assert t.reason == "opposite_rearm" + assert sm.state == State.ARMED_SELL + + +# --------------------------------------------------------------------------- +# 5. opposite_rearm from PRIMED_BUY +# --------------------------------------------------------------------------- + + +def test_opposite_rearm_from_primed_buy() -> None: + sm = StateMachine() + _buy_sequence_to_primed(sm, start_ts=1.0) + assert sm.state == State.PRIMED_BUY + + t = sm.feed("yellow", 5.0) + assert t.prev == State.PRIMED_BUY + assert t.next == State.ARMED_SELL + assert t.reason == "opposite_rearm" + assert sm.state == State.ARMED_SELL + + +# --------------------------------------------------------------------------- +# 6. lockout_same_direction +# --------------------------------------------------------------------------- + + +def test_lockout_same_direction() -> None: + sm = StateMachine(lockout_s=240) + + # First fire at t=100 + _buy_sequence_to_primed(sm, start_ts=90.0) + t_fire1 = sm.feed("light_green", 100.0) + assert t_fire1.trigger == "BUY" + assert t_fire1.locked is False + + # Re-prime + _buy_sequence_to_primed(sm, start_ts=110.0) + + # Second fire at t=200 — inside lockout window (200 - 100 = 100 < 240) + t_fire2 = sm.feed("light_green", 200.0) + assert t_fire2.trigger == "BUY" + assert t_fire2.locked is True + assert t_fire2.next == State.IDLE + + # Re-prime again + _buy_sequence_to_primed(sm, start_ts=300.0) + + # Third fire at t=341 — outside lockout (341 - 200 = 141 < 240, still locked) + # Actually 341 - 100 would be outside but last fire is at t=200: + # 341 - 200 = 141 < 240 → still locked + # We need t > 200 + 240 = 440 + _buy_sequence_to_primed(sm, start_ts=430.0) + t_fire3 = sm.feed("light_green", 441.0) + assert t_fire3.trigger == "BUY" + assert t_fire3.locked is False + + +def test_lockout_same_direction_boundary() -> None: + """Spec requirement: fire BUY @ t=100; fire again @ t=341 → locked=False (241 >= 240).""" + sm = StateMachine(lockout_s=240) + + _buy_sequence_to_primed(sm, start_ts=90.0) + t_fire1 = sm.feed("light_green", 100.0) + assert t_fire1.locked is False + + # Re-prime and fire just inside window: 339-100=239 < 240 → locked + _buy_sequence_to_primed(sm, start_ts=110.0) + t_locked = sm.feed("light_green", 339.0) + assert t_locked.locked is True + + # last_fire is now 339. Re-prime and fire just outside: 580-339=241 >= 240 → unlocked + _buy_sequence_to_primed(sm, start_ts=340.0) + t_free = sm.feed("light_green", 580.0) + assert t_free.locked is False + + +# --------------------------------------------------------------------------- +# 7. lockout_does_not_block_opposite +# --------------------------------------------------------------------------- + + +def test_lockout_does_not_block_opposite() -> None: + sm = StateMachine(lockout_s=240) + + # Fire BUY at t=100 + _buy_sequence_to_primed(sm, start_ts=90.0) + sm.feed("light_green", 100.0) + + # Drive SELL sequence — opposite direction must not be locked + _sell_sequence_to_primed(sm, start_ts=110.0) + t_sell = sm.feed("light_red", 200.0) + assert t_sell.trigger == "SELL" + assert t_sell.locked is False + + +# --------------------------------------------------------------------------- +# 8. phase_skip from ARMED_BUY (light_green without priming) +# --------------------------------------------------------------------------- + + +def test_phase_skip_armed_buy() -> None: + sm = StateMachine() + sm.feed("turquoise", 1.0) # → ARMED_BUY + assert sm.state == State.ARMED_BUY + + t = sm.feed("light_green", 2.0) + assert t.prev == State.ARMED_BUY + assert t.next == State.IDLE + assert t.reason == "phase_skip" + assert t.trigger is None + + +# --------------------------------------------------------------------------- +# 9. noise_from_idle +# --------------------------------------------------------------------------- + + +def test_noise_from_idle() -> None: + sm = StateMachine() + t = sm.feed("dark_green", 1.0) + assert t.prev == State.IDLE + assert t.next == State.IDLE + assert t.reason == "noise" + assert t.trigger is None + assert sm.state == State.IDLE + + +# --------------------------------------------------------------------------- +# 10. refresh_arm_ts +# --------------------------------------------------------------------------- + + +def test_refresh_arm_ts() -> None: + sm = StateMachine() + sm.feed("turquoise", 1.0) # arm at t=1 + + t1 = sm.feed("turquoise", 5.0) # refresh at t=5 + assert t1.prev == State.ARMED_BUY + assert t1.next == State.ARMED_BUY + assert t1.reason == "refresh" + assert t1.arm_ts == 5.0 + + t2 = sm.feed("turquoise", 9.0) # refresh again at t=9 + assert t2.arm_ts == 9.0 + + +# --------------------------------------------------------------------------- +# 11. exhaustive — parameterize over every (state, color) pair +# --------------------------------------------------------------------------- + +ALL_STATES = list(State) +ALL_COLORS: list[DotColor] = [ + "turquoise", "yellow", "dark_green", "dark_red", + "light_green", "light_red", "gray", +] + +FIRE_DIRECTIONS: dict[str, str] = { + State.PRIMED_BUY.value: "BUY", + State.PRIMED_SELL.value: "SELL", +} + +VALID_STATES = set(State) + + +def _sm_in_state(target: State) -> StateMachine: + """Return a fresh StateMachine already in the given state.""" + sm = StateMachine() + match target: + case State.IDLE: + pass + case State.ARMED_BUY: + sm.feed("turquoise", 1.0) + case State.ARMED_SELL: + sm.feed("yellow", 1.0) + case State.PRIMED_BUY: + sm.feed("turquoise", 1.0) + sm.feed("dark_green", 2.0) + case State.PRIMED_SELL: + sm.feed("yellow", 1.0) + sm.feed("dark_red", 2.0) + assert sm.state == target, f"Setup failed: wanted {target}, got {sm.state}" + return sm + + +@pytest.mark.parametrize("state", ALL_STATES) +@pytest.mark.parametrize("color", ALL_COLORS) +def test_exhaustive(state: State, color: DotColor) -> None: + sm = _sm_in_state(state) + t = sm.feed(color, 10.0) + + # (a) resulting state is valid + assert t.next in VALID_STATES, f"Invalid next state: {t.next}" + + # (b) reason is non-empty + assert t.reason, f"Empty reason for ({state}, {color})" + + # (c) if fire, trigger matches direction + if t.reason == "fire": + expected_dir = FIRE_DIRECTIONS.get(state.value) + assert t.trigger == expected_dir, ( + f"Wrong trigger for fire from {state}: got {t.trigger}, expected {expected_dir}" + )