---
phase: 02-exness-daily-tick-cache-seeder-service-with-registry-integration
verified: 2026-03-23T20:15:00Z
status: gaps_found
score: 9/10 must-haves verified
gaps:
  - truth: "systemd service and timer files exist with correct configuration"
    status: partial
    reason: "Service file contains self-referential Conflicts=opendeviationbar-exness-seeder.service (line 9). The plan explicitly states 'IMPORTANT: Do NOT add Conflicts=opendeviationbar-exness-seeder.service — a service conflicting with itself will refuse to start.' This line will prevent the service from starting on bigblack."
    artifacts:
      - path: "scripts/systemd/opendeviationbar-exness-seeder.service"
        issue: "Line 9: Conflicts=opendeviationbar-exness-seeder.service — self-referential Conflicts= prevents the service from starting"
    missing:
      - "Remove line 9 (Conflicts=opendeviationbar-exness-seeder.service) from scripts/systemd/opendeviationbar-exness-seeder.service"
---

# Phase 02: Exness Daily Tick Cache Seeder Verification Report

**Phase Goal:** Build an Exness forex tick cache seeder that mirrors the Binance seeder (scripts/tick_cache_seeder.py) architecture. Downloads daily ZIPs from ticks.ex2archive.com and writes daily Parquet files to ~/.cache/opendeviationbar/ticks/. Wires Exness forex symbols into the unified symbols.toml registry as SSoT. Deploys as a systemd oneshot + 1h timer with Telegram status reporting and monit monitoring.
**Verified:** 2026-03-23T20:15:00Z
**Status:** gaps_found
**Re-verification:** No — initial verification

---

## Goal Achievement

### Observable Truths

| #   | Truth                                                                                             | Status   | Evidence                                                                                                                                                                    |
| --- | ------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1   | 10 Exness forex symbols exist in symbols.toml with correct schema fields                          | VERIFIED | `tomllib.load()` returns 10 symbols with exchange=exness, asset_class=forex, market=raw_spread, correct thresholds                                                          |
| 2   | ExnessSeederConfig loads from environment variables with correct defaults                         | VERIFIED | `ExnessSeederConfig()` returns historical_start=2022-01-01, max_concurrent_downloads=4, download_timeout_s=60, state_file=/var/tmp/opendeviationbar-exness-seeder-state.txt |
| 3   | get_symbol_entries() returns SymbolEntry objects for all 10 Exness symbols with exchange='exness' | VERIFIED | `get_symbol_entries()` returns 10 entries all with exchange=exness, enabled=True                                                                                            |
| 4   | Seeder downloads Exness daily ZIPs and writes 3-column Parquet files                              | VERIFIED | scripts/exness_tick_cache_seeder.py (442 lines):\_download_exness_day + \_atomic_write_parquet + 3-column schema (timestamp, bid, ask)                                      |
| 5   | Weekend 404s are silently skipped without error logging                                           | VERIFIED | test_weekend_404_returns_none passes; 404 returns None (not raises); logged at DEBUG                                                                                        |
| 6   | Already-cached days are skipped (idempotent)                                                      | VERIFIED | test_idempotent_skip passes; \_get_parquet_path().exists() check before download                                                                                            |
| 7   | State file written on completion for monit freshness check                                        | VERIFIED | State written in main() (initial + final) and incrementally in \_seed_symbol(); test_state_file_written passes                                                              |
| 8   | Telegram reports sent: silent on success, persistent on failure                                   | VERIFIED | send_telegram(disable_notification=True) for success, disable_notification=False for failure; no ephemeral_ttl param                                                        |
| 9   | Symbols read from registry filtered by exchange=exness, not hardcoded                             | VERIFIED | `get_symbol_entries()` filtered by `entry.exchange == "exness" and entry.enabled`                                                                                           |
| 10  | systemd service and timer files exist with correct configuration                                  | PARTIAL  | Timer is correct. Service has self-referential Conflicts= (line 9) that will prevent service from starting on bigblack                                                      |

**Score:** 9/10 truths verified

---

### Required Artifacts

| Artifact                                                 | Expected                                              | Status   | Details                                                                                                                                                                                                                                                                                          |
| -------------------------------------------------------- | ----------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `python/opendeviationbar/data/symbols.toml`              | 10 Exness forex symbol registry entries               | VERIFIED | 10 entries present, all with exchange=exness, market=raw_spread, asset_class=forex; XAUUSD=[200,500,1000]; others=[50,100,200]                                                                                                                                                                   |
| `python/opendeviationbar/config/exness_seeder.py`        | ExnessSeederConfig pydantic-settings class            | VERIFIED | File exists; class ExnessSeederConfig(BaseSettings) with env*prefix=OPENDEVIATIONBAR_EXNESS_SEEDER*; 4 fields with correct defaults                                                                                                                                                              |
| `python/opendeviationbar/config/__init__.py`             | Lazy import of ExnessSeederConfig                     | VERIFIED | **getattr** pattern re-exports ExnessSeederConfig                                                                                                                                                                                                                                                |
| `scripts/exness_tick_cache_seeder.py`                    | Exness forex tick cache seeder script (200+ lines)    | VERIFIED | 442 lines; \_exness_url, \_download_exness_day,\_download_day,\_seed_symbol, main                                                                                                                                                                                                                |
| `tests/test_exness_seeder.py`                            | 8+ unit tests for seeder                              | VERIFIED | 9 tests, all passing: test_exness_url_construction, test_exness_url_no_leading_zeros_dropped, test_download_parse_csv, test_weekend_404_returns_none, test_registry_filter_exness_only, test_parquet_write_3_columns, test_idempotent_skip, test_state_file_written, test_registry_entries_count |
| `scripts/systemd/opendeviationbar-exness-seeder.service` | systemd oneshot service for Exness seeder             | STUB     | Exists with correct content (Type=oneshot, MemoryHigh=2G, MemoryMax=4G, TZ=UTC, LD_PRELOAD) BUT line 9 has self-referential Conflicts= that will prevent service from starting                                                                                                                   |
| `scripts/systemd/opendeviationbar-exness-seeder.timer`   | systemd 1h timer for Exness seeder                    | VERIFIED | OnBootSec=300, OnUnitActiveSec=3600, WantedBy=timers.target                                                                                                                                                                                                                                      |
| `deploy/monit/opendeviationbar.conf`                     | Updated monit config with exness-seeder-state monitor | VERIFIED | exness-seeder-state block present with path /var/tmp/opendeviationbar-exness-seeder-state.txt, 120 minutes threshold, failed=[1-9] pattern; 11 total check blocks                                                                                                                                |

---

### Key Link Verification

| From                                                 | To                                                     | Via                                                | Status | Details                                                                                                                  |
| ---------------------------------------------------- | ------------------------------------------------------ | -------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------ |
| scripts/exness_tick_cache_seeder.py                  | python/opendeviationbar/symbol_registry.py             | get_symbol_entries() filtered by exchange='exness' | WIRED  | Line 332-336: `entries = get_symbol_entries()` then filter `entry.exchange == "exness" and entry.enabled`                |
| scripts/exness_tick_cache_seeder.py                  | python/opendeviationbar/config/exness_seeder.py        | ExnessSeederConfig() instantiation                 | WIRED  | Line 323: import; line 328: `cfg = ExnessSeederConfig()`                                                                 |
| scripts/exness_tick_cache_seeder.py                  | python/opendeviationbar/storage/parquet_io.py          | \_atomic_write_parquet() for daily Parquet write   | WIRED  | Line 209: import; line 269: `_atomic_write_parquet(df, parquet_path)`                                                    |
| scripts/exness_tick_cache_seeder.py                  | ticks.ex2archive.com                                   | requests.get() HTTP download                       | WIRED  | Line 58: `_EXNESS_BASE_URL = "https://ticks.ex2archive.com/ticks"`; requests.get() in \_download_exness_day              |
| scripts/systemd/opendeviationbar-exness-seeder.timer | scripts/systemd/opendeviationbar-exness-seeder.service | systemd timer triggers service                     | WIRED  | OnUnitActiveSec=3600 present; timer triggers service by name convention                                                  |
| deploy/monit/opendeviationbar.conf                   | /var/tmp/opendeviationbar-exness-seeder-state.txt      | check file timestamp freshness                     | WIRED  | `check file exness-seeder-state with path /var/tmp/opendeviationbar-exness-seeder-state.txt` + `timestamp > 120 minutes` |
| python/opendeviationbar/config/exness_seeder.py      | pydantic_settings.BaseSettings                         | class inheritance                                  | WIRED  | `class ExnessSeederConfig(BaseSettings)` with `env_prefix="OPENDEVIATIONBAR_EXNESS_SEEDER_"`                             |
| python/opendeviationbar/data/symbols.toml            | python/opendeviationbar/symbol_registry.py             | tomllib.load in \_load_registry()                  | WIRED  | Registry loads TOML; `get_symbol_entries()` returns 10 Exness entries with exchange='exness'                             |

---

### Data-Flow Trace (Level 4)

Not applicable — phase produces a seeder script (data ingestion tool) and configuration artifacts, not UI components or display pipelines. Key data flows verified via unit tests (9/9 passing).

---

### Behavioral Spot-Checks

| Behavior                                          | Command                                                                                                                                                                                                          | Result              | Status |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ------ |
| symbols.toml parses and returns 10 Exness symbols | `python3 -c "import tomllib; data=tomllib.load(open('python/opendeviationbar/data/symbols.toml','rb')); exness={k:v for k,v in data['symbols'].items() if v.get('exchange')=='exness'}; assert len(exness)==10"` | 10 symbols returned | PASS   |
| ExnessSeederConfig loads with correct defaults    | `python3 -c "from opendeviationbar.config.exness_seeder import ExnessSeederConfig; cfg=ExnessSeederConfig(); assert cfg.historical_start=='2022-01-01'"`                                                         | Defaults correct    | PASS   |
| get_symbol_entries() returns Exness symbols       | `python3 -c "from opendeviationbar.symbol_registry import get_symbol_entries; exness=[k for k,v in get_symbol_entries().items() if v.exchange=='exness']; assert len(exness)==10"`                               | 10 symbols returned | PASS   |
| All unit tests pass                               | `uv run --python 3.13 pytest tests/test_exness_seeder.py -v`                                                                                                                                                     | 9 passed in 0.91s   | PASS   |
| Service file has self-referential Conflicts=      | `grep 'Conflicts=opendeviationbar-exness-seeder.service' scripts/systemd/opendeviationbar-exness-seeder.service`                                                                                                 | Found on line 9     | FAIL   |
| Timer file correct                                | `grep 'OnUnitActiveSec=3600' scripts/systemd/opendeviationbar-exness-seeder.timer`                                                                                                                               | Present             | PASS   |
| Monit has 11 check blocks                         | `grep -c '^check ' deploy/monit/opendeviationbar.conf`                                                                                                                                                           | 11                  | PASS   |

---

### Requirements Coverage

All 10 requirement IDs are defined in ROADMAP.md Phase 2. No separate REQUIREMENTS.md exists for this project — requirements are tracked inline in the ROADMAP and PLAN frontmatter.

| Requirement | Source Plan | Description                                        | Status    | Evidence                                                                                                                  |
| ----------- | ----------- | -------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------- |
| EXNS-REG    | 02-01       | 10 Exness forex symbols registered in symbols.toml | SATISFIED | 10 symbols with exchange=exness, asset_class=forex, market=raw_spread, correct thresholds                                 |
| EXNS-CFG    | 02-01       | ExnessSeederConfig with env-var overrides          | SATISFIED | ExnessSeederConfig(BaseSettings) with OPENDEVIATIONBAR*EXNESS_SEEDER* prefix and 4 fields                                 |
| EXNS-DL     | 02-02       | Download Exness daily ZIPs via HTTP                | SATISFIED | requests.get() in \_download_exness_day(); ticks.ex2archive.com URL pattern correct                                       |
| EXNS-PARSE  | 02-02       | Parse Exness CSV to 3-column Polars DataFrame      | SATISFIED | has_header=True, Z-suffix stripping, epoch-ms timestamps, bid/ask f64; test_download_parse_csv passes                     |
| EXNS-WRITE  | 02-02       | Write 3-column Parquet atomically                  | SATISFIED | \_atomic_write_parquet() used directly; test_parquet_write_3_columns passes                                               |
| EXNS-IDEM   | 02-02       | Idempotent — skip already-cached days              | SATISFIED | \_get_parquet_path().exists() check; test_idempotent_skip passes                                                          |
| EXNS-TEL    | 02-02       | Telegram: silent on success, persistent on failure | SATISFIED | send_telegram(disable_notification=True) success; disable_notification=False failure; no ephemeral_ttl                    |
| EXNS-SVC    | 02-03       | systemd oneshot + 1h timer                         | BLOCKED   | Timer correct. Service has self-referential Conflicts= (line 9) — prevents service from starting on bigblack              |
| EXNS-MON    | 02-03       | Monit freshness monitor at 2h threshold            | SATISFIED | exness-seeder-state monitor with 120 minutes threshold and failed=[1-9] pattern; 11 total monitors                        |
| EXNS-DOC    | 02-03       | 4 CLAUDE.md files updated with Exness seeder docs  | SATISFIED | scripts/CLAUDE.md, scripts/systemd/CLAUDE.md (6 services/3 timers), deploy/CLAUDE.md (11 monitors), CLAUDE.md all updated |

---

### Anti-Patterns Found

| File                                                     | Line    | Pattern                                                                                   | Severity | Impact                                                                                                                                                                                                                                                                                                |
| -------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `scripts/systemd/opendeviationbar-exness-seeder.service` | 9       | `Conflicts=opendeviationbar-exness-seeder.service`                                        | Blocker  | Self-referential Conflicts= in [Unit] section. systemd interprets this as "conflict with myself" — the service will refuse to start. Plan 02-03 explicitly warned: "IMPORTANT: Do NOT add Conflicts=opendeviationbar-exness-seeder.service — a service conflicting with itself will refuse to start." |
| `scripts/exness_tick_cache_seeder.py`                    | 196-204 | `_seed_symbol` has `state_path` and `run_start_iso` params beyond the plan's 5-param spec | Warning  | Benign implementation refinement — state writes happen incrementally after each symbol. Tests pass. Does not affect goal achievement.                                                                                                                                                                 |

---

### Human Verification Required

None. All automated checks are conclusive. The self-referential `Conflicts=` is a deterministic failure that does not need human observation.

---

### Gaps Summary

**1 blocker preventing full deployment readiness:**

The systemd service file `scripts/systemd/opendeviationbar-exness-seeder.service` contains a self-referential `Conflicts=opendeviationbar-exness-seeder.service` on line 9. This is a deployment blocker: when deployed to bigblack and `systemctl --user start opendeviationbar-exness-seeder.timer` is run, systemd will refuse to start the service because it conflicts with itself. The plan (02-03-PLAN.md) explicitly prohibited this exact line with a bold "IMPORTANT" warning.

**Fix:** Remove line 9 from `scripts/systemd/opendeviationbar-exness-seeder.service`:

```
Conflicts=opendeviationbar-exness-seeder.service
```

All other phase deliverables are correct and working:

- 10 Exness symbols correctly registered in symbols.toml (verified via tomllib + symbol_registry)
- ExnessSeederConfig loads correctly with all 4 env-configurable fields
- Seeder script is complete (442 lines), passes all 9 unit tests
- Timer file is correct (OnBootSec=300, OnUnitActiveSec=3600)
- Monit has 11 monitors including the new exness-seeder-state block
- All 4 CLAUDE.md files updated with accurate documentation

The ROADMAP still shows Plan 02-02 as unchecked (`[ ] 02-02-PLAN.md`) despite the plan being executed and the summary being present. This is a documentation inconsistency in ROADMAP.md that should be addressed (checkbox should be `[x]`).

---

_Verified: 2026-03-23T20:15:00Z_
_Verifier: Claude (gsd-verifier)_
