docs: reflect Telegram /pause/resume, operating hours, phase-skip backstop, validate-calibration

README gets: operating-hours config + CLI override flags, Telegram command table
with /pause /resume [force] semantics, validate-calibration usage + exit codes,
new audit event reference, phase-skip backstop note, and test count bump.

CLAUDE.md quick reference now lists the new subcommand, CLI flags, and
Telegram commands so future sessions pick them up without re-reading main.py.

TODOS.md marks the 2026-04-17 hang fix, canary drift notification,
phase-skip backstop, operating-hours window, and validate-calibration as
done with commit pointers; adds exchange-calendar holidays as known gap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 12:09:44 +03:00
parent 40cc67b4c6
commit 37f0b14468
3 changed files with 108 additions and 6 deletions

View File

@@ -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_<color>.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) |