diff --git a/CLAUDE.md b/CLAUDE.md index 6455193..ae8a8e3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,14 +6,31 @@ Personal Faza-1 tool for the M2D strategy. Python 3.11+. ```bash pip install -e ".[windows]" # Windows: live capture -pip install -e . # Linux/macOS: dev/dryrun only +pip install -e ".[dev]" # Linux/macOS: dev + tests (WSL: create venv first) atm calibrate # Tk wizard atm debug --delay 5 # one-shot capture + detect -atm run --start-at 16:30 --stop-at 23:00 # live session +atm validate-calibration samples/calibration_labels.json # offline color gate +atm run --start-at 16:30 --stop-at 23:00 # live session +atm run --tz America/New_York --oh-start 09:30 --oh-stop 16:00 # NYSE window override atm dryrun samples # corpus gate -pytest # run tests +pytest -q # 184 tests ``` +## Telegram commands (live) + +`/ss` `/status` `/pause` `/resume` `/resume force` `/3` (interval min) `/stop` + +- `/resume` clears only user pause; Canary drift requires `/resume force`. +- Drift-pause now emits a single Telegram alert (was silent pre-refactor — root cause of the 2026-04-17 hang). + +## Operating-hours config + +`[options.operating_hours]` in TOML: `enabled`, `timezone` (NYSE local, e.g. `America/New_York`), `weekdays`, `start_hhmm`, `stop_hhmm`. Timezone validated at load; `_tz_cache` reused per tick. Boundary crossings log `market_open` / `market_closed` and notify once. Startup in-window is silent. + +## Phase-skip backstop + +`[options.alerts] fire_on_phase_skip = true` (default) — ARMED→light_* direct (dark_* missed) still emits a `⚠️ PHASE SKIP` alert using FSM lockout to suppress spam. + ## Skill routing When the user's request matches an available skill, ALWAYS invoke it using the Skill diff --git a/README.md b/README.md index 2190508..7a899ee 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ atm/ │ ├── journal.py # trade entries │ ├── report.py # weekly R-multiple PnL │ └── main.py # unified CLI -├── tests/ # 105 pytest cases +├── tests/ # 184 pytest cases └── TODOS.md # P1/P2/P3 backlog, Faza 2 items ``` @@ -135,13 +135,74 @@ Startup sequence: 7. At `--stop-at` (or `--duration`): **"ATM stopped" ping**, then exit. Per-cycle behaviour: -- Canary drift → auto-pause (logs `paused`, skips detection). Clear by running `atm run` again with the pause-file removed. +- Canary drift → auto-pause + **single-shot Telegram alert** (`⚠️ Canary drift=N — monitorizare pauzată`). Clear via `/resume force` in Telegram, or restart with pause-file removed. - Detector reports UNKNOWN → stays in current state (logged as `noise`). - Colour change → full frame saved to `samples/YYYYMMDD_HHMMSS_.png` (for corpus). - FIRE (BUY/SELL, not locked) → annotated PNG saved to `logs/fires/`, attached to the alert, `LevelsExtractor` armed. +- **Phase skip backstop** (`fire_on_phase_skip=true` default) → ARMED → light_red/light_green direct (dark_red/dark_green missed) still emits `⚠️ PHASE SKIP` alert with screenshot. FSM lockout suppresses spam. - Phase-B complete → "Levels SL=… TP1=… TP2=…" push. - Heartbeat every `heartbeat_min` minutes. +### Operating hours window + +Configure via `[options.operating_hours]` in TOML (source of truth: NYSE local time, timezone-aware so DST is handled automatically): + +```toml +[options.operating_hours] +enabled = true +timezone = "America/New_York" # fail-fast validated at config load +weekdays = ["MON", "TUE", "WED", "THU", "FRI"] +start_hhmm = "09:30" # NYSE open +stop_hhmm = "16:00" # NYSE close +``` + +Out-of-window ticks are skipped (logged only on transition). On boundary crossings the bot emits `market_open` / `market_closed` Telegram status messages exactly once per transition. **Startup in-window does not emit a spurious `market_open` alert.** + +CLI overrides (beat TOML): + +``` +atm run --tz America/New_York --weekdays MON,TUE,WED,THU,FRI --oh-start 09:30 --oh-stop 16:00 +``` + +> `--oh-start / --oh-stop` are **different** from `--start-at / --stop-at`. The `--start-at / --stop-at` pair controls wall-clock session bounds (when the process starts and quits); `--oh-*` controls the NYSE trading window inside the session (what hours detection actually runs). They compose. + +### Telegram commands + +Send to the bot chat: + +| Command | Effect | +|---|---| +| `/ss` or `/screenshot` | Take and send a screenshot now | +| `/status` | State + pause reason + window open/closed | +| `/pause` | Suspend detection (heartbeats continue) | +| `/resume` | Clear user pause only. If Canary is drift-paused it **stays paused** — use `/resume force` | +| `/resume force` | Also clear Canary drift-pause (use after recalibration) | +| `/3` or `/interval 3` | Set auto-screenshot interval to 3 min | +| `/stop` | Stop the scheduler | + +Only `allowed_chat_ids` are accepted. After 3 consecutive `401`s the poller enters degraded mode. + +### Calibration validation (offline gate) + +Validate that the current calibration classifies known-labeled frames correctly **without waiting for a live session**: + +```bash +atm validate-calibration samples/calibration_labels.json +``` + +Input JSON: +```json +[ + {"path": "logs/fires/20260417_201500_arm_sell.png", "expected": "yellow", "note": "first arm"}, + {"path": "logs/fires/20260417_205302_ss.png", "expected": "dark_red"}, + {"path": "logs/fires/20260417_210441_ss.png", "expected": "light_red"} +] +``` + +Output: per-sample PASS/FAIL with detected color + top-3 candidates by RGB distance + suggestion pixels for misclassifications. + +Exit code: `0` if 100% PASS, `1` on any FAIL, `2` on malformed/missing input. Suitable for CI or a pre-`atm run` sanity check. + Keep PowerShell minimized during the session so it doesn't cover TradeStation. --- @@ -209,9 +270,27 @@ atm calibrate [--screenshot PATH] [--delay SEC] # Tk wizard atm debug [--delay SEC] # one-shot capture + detect atm label SAMPLES_DIR # Tk labeling atm dryrun SAMPLES_DIR # corpus gate +atm validate-calibration LABEL_FILE.json # offline color-classification gate atm run [--duration H] [--start-at HH:MM] [--stop-at HH:MM] [--startup-delay SEC] [--capture-stub] + [--tz TZNAME] [--weekdays MON,TUE,...] [--oh-start HH:MM] [--oh-stop HH:MM] atm journal [--file PATH] # interactive trade entry atm report [--week YYYY-WW] [--file PATH] # weekly summary ``` -Exit code: `atm dryrun` exits 0 if gate passes, 1 otherwise. Other commands follow standard convention. +Exit codes: +- `atm dryrun` — 0 pass, 1 fail. +- `atm validate-calibration` — 0 all PASS, 1 any FAIL, 2 bad input. +- Others: standard convention. + +## Audit log events + +Events written to `logs/YYYY-MM-DD.jsonl`. Added by the lifecycle+canary work: + +| Event | Payload | When | +|---|---|---| +| `canary_drift_paused` | `distance` | First drift tick after clean; emits Telegram alert | +| `user_paused` | — | `/pause` received | +| `user_resumed` | `was_drift`, `was_user`, `force` | `/resume` or `/resume force` | +| `market_open` / `market_closed` | `reason` | Operating-hours window boundary (once per transition; **not** at startup) | +| `phase_skip_fire` | `direction` | Backstop alert when ARMED→light_* direct | +| `command_error` | `action`, `error` | Dispatch exception (isolated from detection loop) | diff --git a/TODOS.md b/TODOS.md index c58a8bb..a142cf0 100644 --- a/TODOS.md +++ b/TODOS.md @@ -60,6 +60,12 @@ Price overlay (from Telegram commands feature) uses `y_axis` linear interpolatio ## Quality debt - [x] **Integration test for run_live loop**: lifecycle async test added in `tests/test_main.py` (IDLE→ARMED→PRIMED auto-poll→FIRE auto-stop). +- [x] **Detection-loop hang on canary pause** (2026-04-17 incident): `_drain_cmd_queue` now runs unconditionally; helpers extracted to module scope for testability (commit `c5024ce`). +- [x] **Silent canary drift-pause**: single-shot Telegram alert on `not_paused → paused` (commit `9cf49ca`). +- [x] **Phase-skip backstop**: `fire_on_phase_skip` (default on) emits alert when ARMED→light_* direct (commit `8b53b8d`). +- [x] **Operating hours window**: NYSE-timezone-aware gate with `/pause` `/resume` `/resume force` control (commits `54f5575`, `2386577`). +- [x] **Offline calibration gate**: `atm validate-calibration` replays labeled frames through detector (commit `8bae507`). - [ ] **Coverage report**: run `pytest --cov=atm --cov-report=term-missing`, aim for ≥ 85% per module. - [ ] **Typing strictness**: run `pyright src/` with strict mode, fix reported issues. - [ ] **Perf baseline**: profile one detection cycle on a representative frame; ensure < 100ms so 5s loop has ample headroom. +- [ ] **Exchange calendar holidays**: operating_hours doesn't know about NYSE closures (MLK, Thanksgiving, Good Friday). User `/pause`s manually for now.