Compare commits
2 Commits
37f0b14468
...
5b61bd7b60
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b61bd7b60 | |||
| 212f77f0ee |
448
README.md
448
README.md
@@ -1,98 +1,117 @@
|
||||
# ATM — Automated Trading Monitor
|
||||
# ATM — Monitor Automat de Trading
|
||||
|
||||
Personal Faza-1 tool for the **M2D strategy**. Watches the M2D MAPS colored-dot strip on a TradeStation chart, runs a phased state machine (ARMED→PRIMED→FIRE), pushes Discord + Telegram alerts with an annotated screenshot on BUY/SELL. You execute the trade manually in TradeLocker.
|
||||
Tool personal pentru strategia **M2D**. Urmărește banda de puncte colorate M2D MAPS de pe un chart TradeStation, rulează o mașină de stări pe faze (ARMED → PRIMED → FIRE) și trimite alerte pe Discord + Telegram cu screenshot adnotat la fiecare semnal BUY/SELL. **Execuția trade-ului o faci tu manual în TradeLocker.**
|
||||
|
||||
No auto-execution. Faza 2 (auto-execute) is blocked on prop-firm TOS audit — see `docs/phase2-prop-firm-audit.md`.
|
||||
Fără execuție automată. Faza 2 (auto-execute) e blocată de auditul TOS prop-firm — vezi `docs/phase2-prop-firm-audit.md`.
|
||||
|
||||
---
|
||||
|
||||
## Project layout
|
||||
## Cum e organizat proiectul
|
||||
|
||||
```
|
||||
atm/
|
||||
├── configs/ # calibration outputs + current.txt marker
|
||||
├── configs/ # calibrări + current.txt (marcaj care config e activ)
|
||||
├── logs/
|
||||
│ ├── YYYY-MM-DD.jsonl # per-cycle audit log, rotates at local midnight
|
||||
│ ├── dead_letter.jsonl # alerts that failed after retries
|
||||
│ ├── fires/ # annotated screenshots, one per BUY/SELL trigger
|
||||
│ └── calibrate_capture_*.png / debug_*.png # gitignored debug artifacts
|
||||
├── samples/ # full frames saved automatically on each colour change
|
||||
├── src/atm/ # package
|
||||
│ ├── config.py # frozen dataclass + TOML loader
|
||||
│ ├── vision.py # ROI crop, phash, pixel↔price, Hough, connected-components
|
||||
│ ├── state_machine.py # 5-state phased FSM, per-direction lockout
|
||||
│ ├── detector.py # capture → crop → find rightmost dot → classify → debounce
|
||||
│ ├── canary.py # layout phash drift watchdog with pause-file gating
|
||||
│ ├── levels.py # Phase-B SL/TP line extraction
|
||||
│ ├── notifier/ # FanoutNotifier + Discord webhook + Telegram bot
|
||||
│ ├── audit.py # line-buffered JSONL, daily rotation
|
||||
│ ├── calibrate.py # Tk wizard (region-select + click-sample)
|
||||
│ ├── labeler.py # Tk UI → labels.json
|
||||
│ ├── dryrun.py # replay corpus, precision/recall gate
|
||||
│ ├── journal.py # trade entries
|
||||
│ ├── report.py # weekly R-multiple PnL
|
||||
│ └── main.py # unified CLI
|
||||
├── tests/ # 184 pytest cases
|
||||
└── TODOS.md # P1/P2/P3 backlog, Faza 2 items
|
||||
│ ├── YYYY-MM-DD.jsonl # audit zilnic, se rotește la miezul nopții local
|
||||
│ ├── dead_letter.jsonl # alerte care au eșuat după retries
|
||||
│ ├── fires/ # screenshot-uri adnotate, unul per trigger BUY/SELL
|
||||
│ └── calibrate_capture_*.png / debug_*.png # artefacte debug (gitignored)
|
||||
├── samples/ # frame complet salvat automat la fiecare schimbare de culoare
|
||||
├── src/atm/ # pachetul Python
|
||||
│ ├── config.py # dataclass + loader TOML
|
||||
│ ├── vision.py # crop ROI, phash, pixel↔preț, Hough, componente conectate
|
||||
│ ├── state_machine.py # FSM 5 stări + lockout per direcție
|
||||
│ ├── detector.py # capture → crop → găsește dot-ul rightmost → clasifică → debounce
|
||||
│ ├── canary.py # watchdog layout via phash drift + flag de pauză
|
||||
│ ├── levels.py # extracție SL/TP pe Faza-B
|
||||
│ ├── notifier/ # FanoutNotifier + webhook Discord + bot Telegram
|
||||
│ ├── audit.py # JSONL line-buffered, rotație zilnică
|
||||
│ ├── calibrate.py # wizard Tk (selectează regiune + click pe culori)
|
||||
│ ├── labeler.py # UI Tk → labels.json
|
||||
│ ├── dryrun.py # replay pe corpus, gate precision/recall
|
||||
│ ├── validate.py # gate offline de clasificare a culorilor
|
||||
│ ├── journal.py # înregistrări trade-uri
|
||||
│ ├── report.py # raport săptămânal PnL în R
|
||||
│ └── main.py # CLI unificat
|
||||
├── tests/ # 184 teste pytest
|
||||
└── TODOS.md # backlog P1/P2/P3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Install
|
||||
## Instalare
|
||||
|
||||
Python 3.11+ required. Clone, then:
|
||||
Python 3.11+.
|
||||
|
||||
```bash
|
||||
pip install -e ".[windows]" # Windows: live capture + window focus
|
||||
pip install -e . # Linux / macOS: dev / dryrun only (no live)
|
||||
pip install -e ".[windows]" # Windows: capture live + focus fereastră
|
||||
pip install -e ".[dev]" # Linux/macOS/WSL: doar dev + teste (fără capture)
|
||||
atm --help
|
||||
```
|
||||
|
||||
`[windows]` pulls `mss`, `pygetwindow`, `pywin32`.
|
||||
**WSL/Linux:** recomandat să folosești un virtualenv local:
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
`[windows]` aduce `mss`, `pygetwindow`, `pywin32` (nu le pune pe WSL).
|
||||
|
||||
---
|
||||
|
||||
## Calibration
|
||||
## Calibrare
|
||||
|
||||
One-time per chart layout. Run on the machine that will do live capture.
|
||||
Se face o singură dată per layout de chart. Trebuie să ruleze pe mașina pe care face capture live (Windows, fizic — nu RDP/virtual).
|
||||
|
||||
```powershell
|
||||
atm calibrate # 3s default countdown; use --delay 10 if you want more time
|
||||
atm calibrate # countdown 3s default; pune --delay 10 dacă vrei mai mult timp
|
||||
```
|
||||
|
||||
Flow:
|
||||
1. Dialog: substring of the chart window title (e.g. `TradeStation` or `DIA`). Stored in config for later auto-focus.
|
||||
2. **"Ready?" message** → click OK → 3s countdown in terminal. Alt-tab TradeStation to the foreground and minimize anything covering it.
|
||||
3. Full-desktop screenshot is captured and shown in a scaled Tk window.
|
||||
4. **Drag a rectangle** over the chart (include the M2D MAPS strip). Enter = confirm. Esc = cancel.
|
||||
5. Step-by-step clicks on the selected region:
|
||||
- M2D MAPS strip: top-left + bottom-right corners
|
||||
- One click on each of: turquoise, yellow, dark_green, dark_red, light_green, light_red, gray dot + chart background (8 total — "Skip" if a colour isn't currently visible)
|
||||
- Chart area: top-left + bottom-right (for Phase-B line detection)
|
||||
- Two known price levels on the y-axis (pixel y → enter price)
|
||||
- Canary region: top-left + bottom-right on a stable UI element (axis label, title bar)
|
||||
6. **Save** → writes `configs/YYYY-MM-DD-HHMM.toml` + marker `configs/current.txt`. Pulls Discord/Telegram creds from env (`ATM_DISCORD_URL`, `ATM_TG_TOKEN`, `ATM_TG_CHAT`) if set; otherwise `REPLACE_ME` placeholders — edit the TOML manually.
|
||||
1. Dialog: substring din titlul ferestrei chart-ului (ex. `TradeStation` sau `DIA`). Se salvează în config pentru auto-focus ulterior.
|
||||
2. **Mesaj "Ready?"** → click OK → countdown 3s în terminal. Alt-tab pe TradeStation, minimizează tot ce-l acoperă.
|
||||
3. Se face screenshot full-desktop, apare o fereastră Tk scalată.
|
||||
4. **Trage un dreptunghi** peste chart (include și banda M2D MAPS). Enter = confirmă. Esc = anulează.
|
||||
5. Click pas cu pas pe regiunea selectată:
|
||||
- M2D MAPS strip: colț stânga-sus + colț dreapta-jos
|
||||
- Un click pe fiecare culoare: turquoise, yellow, dark_green, dark_red, light_green, light_red, gray + background (8 total — "Skip" dacă o culoare nu-i vizibilă acum)
|
||||
- Chart: colț stânga-sus + colț dreapta-jos (pentru detecția de linii în Faza-B)
|
||||
- Două prețuri cunoscute pe axa Y (pixel y → introduci prețul)
|
||||
- Canary: colț stânga-sus + colț dreapta-jos pe un element UI **stabil** (etichetă axă, bară titlu)
|
||||
6. **Save** → scrie `configs/YYYY-MM-DD-HHMM.toml` + marcaj `configs/current.txt`. Preia credențialele Discord/Telegram din env (`ATM_DISCORD_URL`, `ATM_TG_TOKEN`, `ATM_TG_CHAT`) dacă sunt setate; altfel pune `REPLACE_ME` — editezi TOML-ul manual.
|
||||
|
||||
What gets written:
|
||||
- `chart_window_region = {x, y, w, h}` — virtual-desktop absolute rectangle. Runtime capture crops the same box, so the window must stay in that position.
|
||||
- `dot_roi`, `chart_roi`, `canary.roi` — coords relative to the selected region.
|
||||
- Per-colour RGB (sampled via saturation-snap within 15px of the click, mean of 5x5 around the snapped centre).
|
||||
- `y_axis` linear-interp pair.
|
||||
- `canary.baseline_phash` of the canary ROI.
|
||||
### ⚠️ Reguli critice la calibrare (evită incidentul 2026-04-17)
|
||||
|
||||
Sampling tips:
|
||||
- Click colours that are **actually present** in the current dot history. If a colour isn't visible, skip it — `atm dryrun` will tell you if the skipped value doesn't match real dots.
|
||||
- Default tolerance is 60 for dot colours, 25 for background. Tighten via TOML after dryrun if misclassifications creep in.
|
||||
**1. Click EXCLUSIV pe dot-ul din DREAPTA al strip-ului.**
|
||||
Banda M2D MAPS e istoric: dot-ul din dreapta = activ/curent, restul sunt mai vechi. TradeStation desenează dot-ul activ mai strălucitor decât cele vechi. Detector-ul live citește MEREU dot-ul din dreapta. Dacă dai click pe unul din stânga, culoarea calibrată e mai întunecată decât realitatea → clasificare greșită live (dark_red poate ajunge citit ca light_red, de exemplu).
|
||||
|
||||
**2. Canary pe un pixel STATIC.**
|
||||
NU pune regiunea canary peste: volume bar, preț curent, ceas/timestamp. Orice se schimbă natural în acea zonă declanșează drift-pause silent → bot-ul se oprește din detecție fără alertă vizibilă (asta s-a întâmplat la 22:25 pe 17.04, drift=129). Alege: o etichetă de axă, un titlu de panel, un colț de bordură.
|
||||
|
||||
**3. Calibrează în mijlocul unei sesiuni active**, nu dimineața înainte de deschidere. Dot-urile sunt clar vizibile și reflectă exact aceleași setări de rendering ca la live.
|
||||
|
||||
### Ce scrie în TOML
|
||||
|
||||
- `chart_window_region = {x, y, w, h}` — dreptunghi absolut virtual-desktop. Capture-ul la runtime crop-ează exact aceeași cutie, deci fereastra **nu trebuie mutată** după calibrare.
|
||||
- `dot_roi`, `chart_roi`, `canary.roi` — coordonate relative la regiunea selectată.
|
||||
- RGB per culoare (eșantionat cu saturation-snap într-o rază de 15px de click, media unui box 5x5 în jurul pixelului snapped).
|
||||
- `y_axis` — pereche de interpolare liniară.
|
||||
- `canary.baseline_phash` al ROI-ului canary.
|
||||
|
||||
Tips de sampling:
|
||||
- Click pe culori **chiar vizibile acum** în istoricul dot-urilor. Dacă o culoare nu-i vizibilă, skip — `atm dryrun` îți zice dacă valoarea ratată nu se potrivește cu dot-uri reale.
|
||||
- Tolerance default: 60 pentru dot-uri, 25 pentru background. Strângi în TOML după dryrun dacă apar misclasificări.
|
||||
|
||||
---
|
||||
|
||||
## Smoke-test after calibration
|
||||
## Smoke-test după calibrare
|
||||
|
||||
```powershell
|
||||
atm debug --delay 5
|
||||
```
|
||||
|
||||
Captures one frame. Saves `logs/debug_full_<ts>.png`, `logs/debug_dot_roi_<ts>.png`, `logs/debug_annotated_<ts>.png`. Prints:
|
||||
Ia un frame. Salvează `logs/debug_full_<ts>.png`, `logs/debug_dot_roi_<ts>.png`, `logs/debug_annotated_<ts>.png`. Tipărește:
|
||||
|
||||
```
|
||||
window_found: True
|
||||
@@ -102,195 +121,248 @@ classified: gray distance=24 confidence=0.79
|
||||
accepted: True color=gray
|
||||
```
|
||||
|
||||
Open the annotated PNG: yellow rectangle = `dot_roi`, red circle = detected dot. The circle should land on the ACTUAL rightmost colored dot in the M2D MAPS strip. If not:
|
||||
- Circle mid-strip → wrong window under the capture region (bring TradeStation to front).
|
||||
- Circle on a non-dot UI element → `dot_roi` boundaries capture too much; recalibrate narrower.
|
||||
- `color=None` + `UNKNOWN` → tolerances too tight OR sampled RGBs don't match real dots; recalibrate clicking on actual dots.
|
||||
Deschizi PNG-ul adnotat: dreptunghi galben = `dot_roi`, cerc roșu = dot detectat. Cercul trebuie să pice pe **dot-ul colorat cel mai din dreapta** din banda M2D MAPS. Dacă nu:
|
||||
- Cerc la mijloc de strip → alt window e sub regiunea de capture (adu TradeStation în față).
|
||||
- Cerc pe element UI non-dot → `dot_roi` prea larg; recalibrează mai îngust.
|
||||
- `color=None` + `UNKNOWN` → tolerances prea strânse SAU RGB-urile eșantionate nu se potrivesc cu dot-urile reale; recalibrează cu click pe dot-uri reale.
|
||||
|
||||
---
|
||||
|
||||
## Live run
|
||||
## Validare offline a calibrării
|
||||
|
||||
```powershell
|
||||
# Today's session 16:30–23:00 Romania local
|
||||
atm run --start-at 16:30 --stop-at 23:00
|
||||
|
||||
# Indefinite
|
||||
atm run
|
||||
|
||||
# Fixed duration (hours)
|
||||
atm run --duration 2
|
||||
|
||||
# Linux / headless smoke (reads samples/*.png in a loop)
|
||||
atm run --capture-stub --duration 0.05
|
||||
```
|
||||
|
||||
Startup sequence:
|
||||
1. Wall-clock wait until `--start-at` (if set).
|
||||
2. `pygetwindow.activate()` on the first window matching `cfg.window_title` — brings TradeStation to the foreground automatically (restores if minimised).
|
||||
3. 5s countdown (`--startup-delay`).
|
||||
4. Capture first frame + canary check. Status (`drift=X/Y` or `capture_failed`) is included in the startup ping.
|
||||
5. **"ATM started" ping** on Discord + Telegram.
|
||||
6. Main loop: every `loop_interval_s` (default 5s) — capture → canary → detect → state machine → maybe notify → maybe Phase-B.
|
||||
7. At `--stop-at` (or `--duration`): **"ATM stopped" ping**, then exit.
|
||||
|
||||
Per-cycle behaviour:
|
||||
- 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**:
|
||||
Verifici dacă calibrarea actuală clasifică corect un set de frame-uri etichetate manual, **fără să aștepți sesiunea live**. Esențial după orice recalibrare.
|
||||
|
||||
```bash
|
||||
atm validate-calibration samples/calibration_labels.json
|
||||
```
|
||||
|
||||
Input JSON:
|
||||
Format input (`samples/calibration_labels.json`):
|
||||
```json
|
||||
[
|
||||
{"path": "logs/fires/20260417_201500_arm_sell.png", "expected": "yellow", "note": "first arm"},
|
||||
{"path": "logs/fires/20260417_201500_arm_sell.png", "expected": "yellow", "note": "primul 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.
|
||||
Output: per fiecare frame PASS/FAIL + culoarea detectată + top 3 candidați după distanță RGB + sugestii de pixel pentru misclasificări.
|
||||
|
||||
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.
|
||||
Exit code:
|
||||
- `0` — 100% PASS (poți porni live în siguranță)
|
||||
- `1` — cel puțin un FAIL
|
||||
- `2` — input invalid/lipsă
|
||||
|
||||
Keep PowerShell minimized during the session so it doesn't cover TradeStation.
|
||||
### Workflow de corectare iterativă (când apare o alertă greșită live)
|
||||
|
||||
---
|
||||
Scenariu: ai rulat o sesiune live, ai văzut pe chart o culoare pe care bot-ul n-a detectat-o (sau a detectat greșit).
|
||||
|
||||
## After the session
|
||||
1. **În timpul sesiunii**, când observi o culoare nouă pe chart, trimite `/ss` în Telegram. Asta salvează un screenshot în `logs/fires/` cu timestamp.
|
||||
2. **După sesiune**, deschizi `samples/calibration_labels.json` și adaugi o intrare nouă pentru fiecare screenshot relevant:
|
||||
```json
|
||||
{"path": "logs/fires/20260420_151234_ss.png", "expected": "dark_green", "note": "văzut live, ratat de bot"}
|
||||
```
|
||||
Câmpul `expected` = culoarea pe care TU ai văzut-o pe chart (nu ce a zis bot-ul).
|
||||
3. **Rulează validarea:**
|
||||
```bash
|
||||
atm validate-calibration samples/calibration_labels.json
|
||||
```
|
||||
4. **Interpretează rezultatul:**
|
||||
- **Toate PASS** → calibrarea ține, continui live fără modificări.
|
||||
- **Măcar un FAIL** → output-ul îți arată pixelul real (ex. `RGB(128, 0, 0)`), centrul curent din TOML (ex. `dark_red RGB(83, 0, 0)`) și distanța. Două opțiuni:
|
||||
- **Fix tactic rapid:** editezi TOML-ul direct, muți centrul culorii aproape de pixelul observat. Rulezi iar `validate-calibration`. Te oprești când e PASS.
|
||||
- **Fix complet:** la următoarea sesiune live completă, rulezi `atm calibrate` de la zero pe Windows, cu **disciplina cele 3 reguli critice de mai sus** (rightmost dot, pixel static pentru canary, în timpul unei sesiuni active).
|
||||
5. **Acumulezi mai multe samples în timp.** Obiectiv: 2-3 intrări per culoare în `calibration_labels.json`. Cu cât fișierul are mai multe etichete, cu atât calibrarea următoare e validată mai solid.
|
||||
|
||||
```powershell
|
||||
atm label samples # Tk UI — label each saved frame with true dot colour
|
||||
atm dryrun samples # replay through detector + FSM; exits 0 if precision=100%, recall>=95%
|
||||
```
|
||||
### Exemplu real — incidentul 2026-04-17
|
||||
|
||||
If the gate fails, tune per-colour `tolerance` in `configs/<current>.toml`, or recalibrate colour samples that didn't match. Re-run `atm dryrun` until it passes. Only then do you trust live signals.
|
||||
La 20:53 s-a afișat un dark_red pe chart dar bot-ul l-a citit ca light_red (alertă ratată). Root cause: calibrarea anterioară (`2026-04-16-0703.toml`) a fost făcută dând click pe dot-uri istorice (mai întunecate), nu pe dot-ul activ din dreapta.
|
||||
|
||||
Trade record-keeping:
|
||||
Fix aplicat în `2026-04-18-1220.toml`, pe bază de evidență live:
|
||||
|
||||
```powershell
|
||||
atm journal # interactive entry after a real trade
|
||||
atm report --week 2026-16 # weekly win rate + R PnL + slippage
|
||||
| Culoare | Centru vechi | Pixel live observat | Centru nou |
|
||||
|---|---|---|---|
|
||||
| dark_red | (83, 0, 0) | (128, 0, 0) | **(128, 0, 0)** |
|
||||
| light_red | (153, 0, 0) | (171, 0, 0) | **(171, 0, 0)** |
|
||||
| dark_green | (0, 77, 0) | — | **(0, 122, 0)** (ajustat proporțional: +45 pe G) |
|
||||
| light_green | (0, 153, 0) | — | **(0, 171, 0)** (ajustat proporțional: +18 pe G) |
|
||||
|
||||
yellow, turquoise, gray, background — lăsate neschimbate (nu am dovezi live care să justifice ajustarea).
|
||||
|
||||
După fix: `atm validate-calibration` → 3/3 PASS, confidence 1.00 pe ambele roșuri.
|
||||
|
||||
**Rollback** dacă ceva merge prost:
|
||||
```bash
|
||||
echo "2026-04-16-0703.toml" > configs/current.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DPI / multi-monitor notes
|
||||
## Sesiunea live
|
||||
|
||||
- Calibration region is virtual-desktop-absolute; runtime capture uses the same rectangle. **Don't move the TradeStation window** after calibrating. Canary will catch drift and pause automatically.
|
||||
- Changing DPI scaling or moving to a different monitor with different DPI → recalibrate.
|
||||
- RDP / virtual desktops: `mss` can return black frames over RDP. Run locally on the same physical machine as TradeStation.
|
||||
```powershell
|
||||
# Sesiunea de azi 16:30–23:00 România local
|
||||
atm run --start-at 16:30 --stop-at 23:00
|
||||
|
||||
# Fără limită
|
||||
atm run
|
||||
|
||||
# Durată fixă (ore)
|
||||
atm run --duration 2
|
||||
|
||||
# Linux/WSL smoke (rulează pe fișiere din samples/)
|
||||
atm run --capture-stub --duration 0.05
|
||||
```
|
||||
|
||||
Startup:
|
||||
1. Așteptare wall-clock până la `--start-at` (dacă e setat).
|
||||
2. `pygetwindow.activate()` pe prima fereastră care conține `cfg.window_title` — aduce TradeStation în față (restaurează dacă-i minimizată).
|
||||
3. Countdown 5s (`--startup-delay`).
|
||||
4. Primul frame + check canary. Status (`drift=X/Y` sau `capture_failed`) e inclus în ping-ul de start.
|
||||
5. **Ping "ATM started"** pe Discord + Telegram.
|
||||
6. Loop principal: la fiecare `loop_interval_s` (default 5s) — capture → canary → detect → FSM → poate notifică → poate Faza-B.
|
||||
7. La `--stop-at` (sau `--duration`): **ping "ATM stopped"**, apoi exit.
|
||||
|
||||
Comportament per ciclu:
|
||||
- Drift canary → auto-pause + **alertă Telegram single-shot** (`⚠️ Canary drift=N — monitorizare pauzată`). Anulezi cu `/resume force` în Telegram, sau repornești cu flag-ul de pauză șters.
|
||||
- Detector raportează UNKNOWN → rămâne în starea curentă (loghează `noise`).
|
||||
- Schimbare de culoare → frame complet salvat în `samples/YYYYMMDD_HHMMSS_<color>.png` (pentru corpus).
|
||||
- FIRE (BUY/SELL, nu locked) → PNG adnotat salvat în `logs/fires/`, atașat la alertă, `LevelsExtractor` armed.
|
||||
- **Phase-skip backstop** (`fire_on_phase_skip=true` default) → ARMED → light_red/light_green direct (dark_* ratat) emite totuși alertă `⚠️ PHASE SKIP` cu screenshot. Lockout-ul FSM previne spam.
|
||||
- Faza-B completă → push "Levels SL=… TP1=… TP2=…".
|
||||
- Heartbeat la fiecare `heartbeat_min` minute.
|
||||
|
||||
Ține PowerShell minimizat în timpul sesiunii ca să nu acopere TradeStation.
|
||||
|
||||
### Fereastra orelor de trading
|
||||
|
||||
Configurezi din TOML (sursă adevăr: NYSE local, timezone-aware — DST-ul e gestionat automat):
|
||||
|
||||
```toml
|
||||
[options.operating_hours]
|
||||
enabled = true
|
||||
timezone = "America/New_York" # validat fail-fast la load
|
||||
weekdays = ["MON", "TUE", "WED", "THU", "FRI"]
|
||||
start_hhmm = "09:30" # deschidere NYSE
|
||||
stop_hhmm = "16:00" # închidere NYSE
|
||||
```
|
||||
|
||||
Tick-urile din afara ferestrei sunt skipped (logged doar la tranziție). La traversarea boundary-ului bot-ul emite `market_open` / `market_closed` în Telegram — o singură dată per tranziție. **Pornirea în-fereastră nu emite alertă spurioasă.**
|
||||
|
||||
Override din CLI (bat TOML-ul):
|
||||
|
||||
```
|
||||
atm run --tz America/New_York --weekdays MON,TUE,WED,THU,FRI --oh-start 09:30 --oh-stop 16:00
|
||||
```
|
||||
|
||||
> `--oh-start / --oh-stop` sunt **diferite** de `--start-at / --stop-at`.
|
||||
> `--start-at / --stop-at` = wall-clock session bounds (când pornește procesul și când se oprește).
|
||||
> `--oh-start / --oh-stop` = fereastra NYSE în care detecția rulează efectiv în interiorul sesiunii.
|
||||
> Se combină.
|
||||
|
||||
### Comenzi Telegram
|
||||
|
||||
Trimiți în chat-ul bot-ului:
|
||||
|
||||
| Comandă | Efect |
|
||||
|---|---|
|
||||
| `/ss` sau `/screenshot` | Screenshot acum |
|
||||
| `/status` | Stare FSM + motiv pauză + fereastră open/closed |
|
||||
| `/pause` | Suspendă detecția (heartbeat-urile continuă) |
|
||||
| `/resume` | Elimină DOAR pauza user. Dacă Canary e drift-paused, **rămâne paused** — folosește `/resume force` |
|
||||
| `/resume force` | Elimină și drift-pause-ul canary (după recalibrare) |
|
||||
| `/3` sau `/interval 3` | Interval auto-screenshot = 3 min |
|
||||
| `/stop` | Oprește scheduler-ul de screenshot |
|
||||
|
||||
Doar `allowed_chat_ids` sunt acceptate. După 3 `401` consecutive, poller-ul intră în mod degradat.
|
||||
|
||||
---
|
||||
|
||||
## După sesiune
|
||||
|
||||
```powershell
|
||||
atm label samples # UI Tk — etichetezi fiecare frame salvat cu culoarea reală
|
||||
atm dryrun samples # replay prin detector + FSM; exit 0 dacă precision=100%, recall≥95%
|
||||
```
|
||||
|
||||
Dacă gate-ul pică, ajustezi `tolerance` per culoare în `configs/<current>.toml`, sau recalibrezi eșantioanele care n-au potrivit. Rulezi iar `atm dryrun` până trece. **Numai atunci ai încredere în semnalele live.**
|
||||
|
||||
Evidență trade-uri:
|
||||
|
||||
```powershell
|
||||
atm journal # înregistrare interactivă după un trade real
|
||||
atm report --week 2026-16 # win rate săptămânal + PnL în R + slippage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Note DPI / multi-monitor
|
||||
|
||||
- Regiunea din calibrare e absolută virtual-desktop; runtime capture folosește același dreptunghi. **Nu muta fereastra TradeStation** după calibrare. Canary prinde drift-ul și pauzează automat.
|
||||
- Schimbi DPI scaling sau muți pe un alt monitor cu DPI diferit → recalibrezi.
|
||||
- RDP / desktop virtual: `mss` poate returna frame-uri negre peste RDP. Rulează local pe aceeași mașină fizică pe care e TradeStation.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Likely cause | Fix |
|
||||
| Simptom | Cauză probabilă | Fix |
|
||||
|---|---|---|
|
||||
| `capture_failed` in startup ping | `chart_window_region` references coords off-screen (different monitor layout) | Recalibrate. |
|
||||
| Startup canary `drift=X/8` with X >> 8 | Wrong window is in the capture region | Make sure TradeStation is the window at `cfg.chart_window_region`. Relaunch. |
|
||||
| `WARN: no window contains 'xxx'` at startup | `cfg.window_title` substring matches nothing | Edit `window_title` in TOML to a substring that's unique to TradeStation. |
|
||||
| No alerts even after trigger ought to fire | Check `logs/YYYY-MM-DD.jsonl` for `event=tick` entries — are colours accepted? Is `trigger` ever set? | If always UNKNOWN → tolerances too tight. If `trigger` but `locked=true` → lockout from prior fire, normal. |
|
||||
| Discord OK, Telegram silent (or vice versa) | `logs/dead_letter.jsonl` contains failed alerts with error | Fix credentials in TOML, restart. |
|
||||
| Heartbeat shows `telegram: failed > 0` | Telegram returned `ok:false` (bot blocked, invalid chat_id, parse error) | Check `logs/dead_letter.jsonl` for the `error_str` / `description` field. Common: bot never started by user in Telegram, or wrong `chat_id` flavor (channel vs group vs DM). |
|
||||
| Debug circle on mid-strip instead of right edge | Anti-aliasing bridges dots in the mask | Already fixed via erosion+connected-components — ensure `git pull` is current. |
|
||||
| Wizard window is tiny / image not visible | Tk geometry default on Windows | Already fixed — `git pull`. Image is scaled to fit screen. |
|
||||
| `capture_failed` în ping-ul de start | `chart_window_region` referă coords off-screen (alt layout monitor) | Recalibrează. |
|
||||
| Canary la startup arată `drift=X/8` cu X ≫ 8 | Alt window e în regiunea de capture | TradeStation trebuie să fie ferestra la `cfg.chart_window_region`. Relansează. |
|
||||
| `WARN: no window contains 'xxx'` la start | `cfg.window_title` nu prinde nimic | Editează `window_title` în TOML cu un substring unic pentru TradeStation. |
|
||||
| Nu vin alerte deși ar trebui | Verifică `logs/YYYY-MM-DD.jsonl` — `event=frame` au culoare acceptată? `trigger` setat? | Dacă mereu UNKNOWN → tolerances prea strânse SAU RGB-urile calibrate nu se potrivesc. Rulează `atm validate-calibration`. Dacă `trigger` dar `locked=true` → lockout de la fire anterior, normal. |
|
||||
| Alertă pe culoare greșită (ex. dark_red → light_red) | Calibrarea a luat dot istoric, nu activ | Rulează `atm validate-calibration`. Corectezi tactic în TOML sau recalibrezi cu regula rightmost dot. |
|
||||
| Discord OK, Telegram tace (sau invers) | `logs/dead_letter.jsonl` are alertele eșuate + eroarea | Fixezi credențiale în TOML, restart. |
|
||||
| Heartbeat arată `telegram: failed > 0` | Telegram a răspuns `ok:false` | Check `logs/dead_letter.jsonl` pentru `error_str` / `description`. Comun: bot-ul nu-a fost pornit de user în Telegram, sau `chat_id` greșit (channel vs group vs DM). |
|
||||
| Bot-ul "moare" după N ore, heartbeat merge dar comenzile nu răspund | Era bug-ul de hang din 2026-04-17 — drain coadă de comenzi sărit când Canary paused | Fixat în `c5024ce`. Update git pull. |
|
||||
|
||||
---
|
||||
|
||||
## Windows Task Scheduler (production)
|
||||
## Windows Task Scheduler (producție)
|
||||
|
||||
For hands-off daily runs surviving reboots:
|
||||
Pentru rulare automată zilnică care supraviețuiește reboot-urilor:
|
||||
|
||||
1. Task Scheduler → Create Task → name `ATM M2D Monitor`
|
||||
1. Task Scheduler → Create Task → nume `ATM M2D Monitor`
|
||||
2. **General**: "Run only when user is logged on", "Run with highest privileges"
|
||||
3. **Triggers**: New → Daily, Start `16:30`
|
||||
4. **Actions**: New → Program `C:\path\to\python.exe`, Arguments `-m atm run --stop-at 23:00`, Start in `D:\PROIECTE\atm`
|
||||
5. **Conditions**: uncheck "Start only if AC power" (if laptop)
|
||||
5. **Conditions**: debifează "Start only if AC power" (dacă e laptop)
|
||||
6. **Settings**: "If task runs longer than 7 hours → stop"
|
||||
|
||||
Click-right → Run to test manually. Manual DST-change check twice a year (Mar / Oct first week).
|
||||
Click-right → Run, să testezi manual. Check DST schimbare de două ori pe an (prima săptămână din martie / octombrie).
|
||||
|
||||
---
|
||||
|
||||
## Quick command reference
|
||||
## Referință rapidă comenzi
|
||||
|
||||
```
|
||||
atm calibrate [--screenshot PATH] [--delay SEC] # Tk wizard
|
||||
atm calibrate [--screenshot PATH] [--delay SEC] # wizard Tk
|
||||
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 label SAMPLES_DIR # etichetare Tk
|
||||
atm dryrun SAMPLES_DIR # gate pe corpus
|
||||
atm validate-calibration LABEL_FILE.json # gate offline clasificare culori
|
||||
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
|
||||
atm journal [--file PATH] # înregistrare interactivă
|
||||
atm report [--week YYYY-WW] [--file PATH] # raport săptămânal
|
||||
```
|
||||
|
||||
Exit codes:
|
||||
- `atm dryrun` — 0 pass, 1 fail.
|
||||
- `atm validate-calibration` — 0 all PASS, 1 any FAIL, 2 bad input.
|
||||
- Others: standard convention.
|
||||
- `atm validate-calibration` — 0 toate PASS, 1 orice FAIL, 2 input invalid.
|
||||
- Restul: standard.
|
||||
|
||||
## Audit log events
|
||||
---
|
||||
|
||||
Events written to `logs/YYYY-MM-DD.jsonl`. Added by the lifecycle+canary work:
|
||||
## Evenimente audit
|
||||
|
||||
| Event | Payload | When |
|
||||
Scrise în `logs/YYYY-MM-DD.jsonl`. Cele adăugate recent:
|
||||
|
||||
| Event | Payload | Când |
|
||||
|---|---|---|
|
||||
| `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) |
|
||||
| `canary_drift_paused` | `distance` | Primul tick cu drift după o stare curată; emite alertă Telegram |
|
||||
| `user_paused` | — | `/pause` primit |
|
||||
| `user_resumed` | `was_drift`, `was_user`, `force` | `/resume` sau `/resume force` |
|
||||
| `market_open` / `market_closed` | `reason` | Boundary fereastră operating-hours (o dată per tranziție; **nu** la startup) |
|
||||
| `phase_skip_fire` | `direction` | Alertă backstop când ARMED→light_* direct |
|
||||
| `command_error` | `action`, `error` | Excepție la dispatch (izolată de loop-ul de detecție) |
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,43 +0,0 @@
|
||||
# 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.
|
||||
BIN
docs/image.png
BIN
docs/image.png
Binary file not shown.
|
Before Width: | Height: | Size: 160 KiB |
@@ -1,258 +0,0 @@
|
||||
# Plan: ATM — Automated Trading Monitor (M2D, Faza 1) — ENG-REVIEWED
|
||||
|
||||
**Source plan:** `/home/claude/.claude/plans/swirling-drifting-starfish.md`
|
||||
**CEO plan artifact:** `~/.gstack/projects/romfast-workspace/ceo-plans/2026-04-15-atm-trading.md`
|
||||
**Eng review mode:** FULL_REVIEW (4 decisions made, 0 unresolved)
|
||||
**Design doc:** `~/.gstack/projects/romfast-workspace/claude-master-design-20260415-atm-trading.md` (APPROVED)
|
||||
**Eng test plan:** `~/.gstack/projects/romfast-workspace/claude-master-eng-review-test-plan-20260415-212932.md`
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
User trades M2D strategy manually on DIA (TradeStation) with execution on TradeLocker US30 CFD (prop firm). Same strategy on GLD → XAUUSD. 4h/evening dual-screen monitoring. Faza 1 goal: bot auto-detects M2D trigger, sends Discord/Telegram notification with screenshot + SL/TP1/TP2 levels; user executes manually in TradeLocker. Faza 2 (auto-execution) deferred until prop firm TOS verified and Faza 1 proven over 20+ sessions.
|
||||
|
||||
**Review changed two things from the original plan:**
|
||||
1. **State machine spec corrected.** Original "last 3 consecutive non-gray dots" is wrong. Actual M2D is phased: Phase 1 arming (turquoise → gray/dark-green) → Phase 2 trigger (light-green).
|
||||
2. **Levels extraction corrected.** Original plan had levels.py extracting SL/TP at trigger. But those lines only appear on TradeStation chart *after* user enters trade in TradeLocker. Corrected to two-phase: spec-math at trigger, chart-scan after entry.
|
||||
|
||||
Plus 5 accepted expansions (labeled corpus, level fallback, layout canary, trade journal, TOS checklist).
|
||||
|
||||
---
|
||||
|
||||
## Approach: B (Structured Python service, dry-run, audit log) + CEO-reviewed additions
|
||||
|
||||
Runs on Windows machine alongside TradeStation. `mss` screenshots → ROI color-sample on M2D MAPS strip → phased state machine → Discord webhook + Telegram bot → JSONL audit + trade journal → dry-run replay against labeled corpus.
|
||||
|
||||
---
|
||||
|
||||
## State Machine Spec (corrected + exhaustive)
|
||||
|
||||
States:
|
||||
- `IDLE`
|
||||
- `ARMED_BUY` — turquoise seen
|
||||
- `PRIMED_BUY` — turquoise + at least one dark-green seen
|
||||
- `ARMED_SELL` — yellow seen
|
||||
- `PRIMED_SELL` — yellow + at least one dark-red seen
|
||||
|
||||
**Default rule:** any (state, event) pair not listed below → stay in current state, no action, log as `noise`.
|
||||
|
||||
Transitions — BUY side:
|
||||
|
||||
| From | Event | To | Action |
|
||||
|------|-------|-----|--------|
|
||||
| IDLE | turquoise | ARMED_BUY | log arm_ts |
|
||||
| IDLE | yellow | ARMED_SELL | log arm_ts (sell) |
|
||||
| IDLE | dark-green / dark-red / light-green / light-red / gray | IDLE | noise (log phase-skip if light-green/light-red) |
|
||||
| ARMED_BUY | gray | ARMED_BUY | persist |
|
||||
| ARMED_BUY | turquoise | ARMED_BUY | refresh arm_ts |
|
||||
| ARMED_BUY | dark-green | PRIMED_BUY | log prime_ts |
|
||||
| ARMED_BUY | yellow | ARMED_SELL | opposite rearm |
|
||||
| ARMED_BUY | dark-red | ARMED_BUY | ignore (minority noise) |
|
||||
| ARMED_BUY | light-green | IDLE | **skip detected** — no FIRE, log phase_skip |
|
||||
| ARMED_BUY | light-red | IDLE | skip detected, log |
|
||||
| PRIMED_BUY | dark-green | PRIMED_BUY | accumulate |
|
||||
| PRIMED_BUY | dark-red | PRIMED_BUY | ignore (minority noise) |
|
||||
| PRIMED_BUY | **light-green** | IDLE | **FIRE BUY**, lockout(BUY)=4min |
|
||||
| PRIMED_BUY | light-red | IDLE | skip detected (wrong trigger) |
|
||||
| PRIMED_BUY | gray | IDLE | **COOLED** — signal dead, log |
|
||||
| PRIMED_BUY | turquoise | ARMED_BUY | rearm fresh |
|
||||
| PRIMED_BUY | yellow | ARMED_SELL | opposite rearm |
|
||||
|
||||
SELL side mirrors exactly: swap turquoise↔yellow, dark-green↔dark-red, light-green↔light-red, BUY↔SELL.
|
||||
|
||||
Notes:
|
||||
- No time-based TTL on ARMED/PRIMED. State persists until trigger fires, cooled by gray after PRIMED, opposite-color rearm, or process restart (Windows Task Scheduler stops bot at session end → natural session-boundary reset).
|
||||
- Cooling rule: "gray after dark-green" = signal racit (user's term). Gray during ARMED_BUY (before any dark-green) is OK.
|
||||
- After FIRE: 4-minute lockout per-direction. BUY lockout doesn't block SELL and vice versa. Single timestamp per direction.
|
||||
- Opposite-color-Phase-1 triggers rearm to opposite side (captures direction flip).
|
||||
- Phase-skip (arming color → trigger color with no phase-2 step) → IDLE, no FIRE, logged. Would be legitimate only if indicator collapses phases, which it doesn't per observed behavior.
|
||||
|
||||
---
|
||||
|
||||
## Detection Details
|
||||
|
||||
- **Loop interval:** 5 seconds (36 cycles per 3-min bar; stays well inside notification-latency target).
|
||||
- **Rightmost-dot detection:** scan ROI from right edge leftward, find first non-background pixel cluster → that's the rightmost dot. Don't hardcode x-pixel positions (chart scrolls; hardcoded positions drift).
|
||||
- **Debounce:** configurable `debounce_depth` in config.toml (default `1` — single-read acceptance). Increase if future sessions show mid-bar color flicker. Screenshot-in-notification is the user's visual verification on top.
|
||||
- **Rolling window:** keep last 20 classified dots with their detection timestamps. State machine consumes the newest *accepted* (post-debounce) dot per cycle.
|
||||
- **Classification:** nearest-color match in RGB Euclidean distance, per-color tolerance from calibration. Report confidence = `1 - distance_nearest / distance_second_nearest`. Log confidence every cycle. If all distances > 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 <dir>`, `atm dryrun <dir>`, `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.
|
||||
@@ -1,74 +0,0 @@
|
||||
# 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 <dir>`)
|
||||
- `/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
|
||||
Reference in New Issue
Block a user