---
phase: 02-exness-daily-tick-cache-seeder-service-with-registry-integration
plan: 03
type: execute
wave: 2
depends_on: ["02-01"]
files_modified:
  - scripts/systemd/opendeviationbar-exness-seeder.service
  - scripts/systemd/opendeviationbar-exness-seeder.timer
  - deploy/monit/opendeviationbar.conf
  - scripts/CLAUDE.md
  - scripts/systemd/CLAUDE.md
  - deploy/CLAUDE.md
  - CLAUDE.md
autonomous: true
requirements:
  - EXNS-SVC
  - EXNS-MON
  - EXNS-DOC
must_haves:
  truths:
    - "systemd service and timer files exist with correct configuration"
    - "Monit config has exness-seeder-state monitor with 2h threshold"
    - "All 4 CLAUDE.md files updated with Exness seeder documentation"
  artifacts:
    - path: "scripts/systemd/opendeviationbar-exness-seeder.service"
      provides: "systemd oneshot service for Exness seeder"
      contains: "Type=oneshot"
    - path: "scripts/systemd/opendeviationbar-exness-seeder.timer"
      provides: "systemd 1h timer for Exness seeder"
      contains: "OnUnitActiveSec=3600"
    - path: "deploy/monit/opendeviationbar.conf"
      provides: "Updated monit config with exness-seeder-state monitor"
      contains: "exness-seeder-state"
  key_links:
    - from: "scripts/systemd/opendeviationbar-exness-seeder.timer"
      to: "scripts/systemd/opendeviationbar-exness-seeder.service"
      via: "systemd timer triggers service"
      pattern: "OnUnitActiveSec=3600"
    - from: "deploy/monit/opendeviationbar.conf"
      to: "/var/tmp/opendeviationbar-exness-seeder-state.txt"
      via: "check file timestamp freshness"
      pattern: "exness-seeder-state"
---

<objective>
Create systemd service + timer, add monit monitor, and update all CLAUDE.md documentation files for the Exness seeder.

Purpose: Deployment infrastructure for the seeder to run as a 1h timer on bigblack, with monit freshness monitoring and complete documentation in the CLAUDE.md hub-and-spoke network.

Output: 2 systemd unit files, updated monit config (11 monitors), updated 4 CLAUDE.md files.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/02-exness-daily-tick-cache-seeder-service-with-registry-integration/02-CONTEXT.md
@.planning/phases/02-exness-daily-tick-cache-seeder-service-with-registry-integration/02-RESEARCH.md

<interfaces>
<!-- Existing systemd and monit patterns to follow -->

From scripts/systemd/opendeviationbar-seeder.service (template):

```ini
[Unit]
Description=OpenDeviationBar Parquet Tick Cache Seeder
After=network-online.target
Wants=network-online.target
Conflicts=opendeviationbar-seeder.service

[Service]
Type=oneshot
WorkingDirectory=/home/tca/opendeviationbar-py
EnvironmentFile=/home/tca/opendeviationbar-py/deploy/opendeviationbar.env
EnvironmentFile=-/home/tca/opendeviationbar-py/deploy/opendeviationbar.local.env
Environment=TZ=UTC
Environment=LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libmimalloc.so.2
Environment=MIMALLOC_PURGE_DELAY=1000
ExecStart=/home/tca/opendeviationbar-py/.venv/bin/python3 /home/tca/opendeviationbar-py/scripts/tick_cache_seeder.py
Nice=10
MemoryHigh=7G
MemoryMax=10G
MemorySwapMax=0
TimeoutStartSec=3600
StandardOutput=journal
StandardError=journal
SyslogIdentifier=opendeviationbar-seeder
```

From scripts/systemd/opendeviationbar-seeder.timer (template):

```ini
[Unit]
Description=OpenDeviationBar Parquet Tick Cache Seeder Timer (5min)

[Timer]
OnBootSec=120
OnUnitActiveSec=300
AccuracySec=30

[Install]
WantedBy=timers.target
```

From deploy/monit/opendeviationbar.conf (seeder-state monitor pattern):

```
check file seeder-state
    with path /var/tmp/opendeviationbar-seeder-state.txt
    if timestamp > 15 minutes then exec "/usr/local/bin/obdpy-monit-alert.sh"
        as uid "tca" and gid "tca"
    if match "failed=[1-9]" then exec "/usr/local/bin/obdpy-monit-alert.sh"
        as uid "tca" and gid "tca"
```

</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create systemd service + timer and monit monitor</name>
  <files>scripts/systemd/opendeviationbar-exness-seeder.service, scripts/systemd/opendeviationbar-exness-seeder.timer, deploy/monit/opendeviationbar.conf</files>
  <read_first>
    - scripts/systemd/opendeviationbar-seeder.service (Binance seeder service — template to adapt)
    - scripts/systemd/opendeviationbar-seeder.timer (Binance seeder timer — template to adapt)
    - deploy/monit/opendeviationbar.conf (current monit config — add new monitor)
    - scripts/systemd/CLAUDE.md (service table and conventions)
    - deploy/CLAUDE.md (monit monitor table)
    - .planning/phases/02-exness-daily-tick-cache-seeder-service-with-registry-integration/02-CONTEXT.md (locked decisions for intervals, memory limits)
  </read_first>
  <action>
    **Create `scripts/systemd/opendeviationbar-exness-seeder.service`:**

    ```ini
    # Exness forex tick cache seeder (Phase 02)
    # Downloads daily ZIP archives from ticks.ex2archive.com for all registry-enabled
    # Exness forex symbols and writes raw bid/ask quotes to local Parquet tick cache.
    # Triggered by opendeviationbar-exness-seeder.timer (1h interval).
    [Unit]
    Description=OpenDeviationBar Exness Forex Tick Cache Seeder
    After=network-online.target
    Wants=network-online.target

    [Service]
    Type=oneshot
    WorkingDirectory=/home/tca/opendeviationbar-py
    EnvironmentFile=/home/tca/opendeviationbar-py/deploy/opendeviationbar.env
    EnvironmentFile=-/home/tca/opendeviationbar-py/deploy/opendeviationbar.local.env
    Environment=TZ=UTC
    Environment=LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libmimalloc.so.2
    Environment=MIMALLOC_PURGE_DELAY=1000
    ExecStart=/home/tca/opendeviationbar-py/.venv/bin/python3 /home/tca/opendeviationbar-py/scripts/exness_tick_cache_seeder.py

    # Resource limits — lower than Binance seeder (forex data is ~10x smaller)
    Nice=10
    MemoryHigh=2G
    MemoryMax=4G
    MemorySwapMax=0
    TimeoutStartSec=3600

    # Logging
    StandardOutput=journal
    StandardError=journal
    SyslogIdentifier=opendeviationbar-exness-seeder
    ```

    **IMPORTANT:** Do NOT add `Conflicts=opendeviationbar-exness-seeder.service` — a service conflicting with itself will refuse to start. The timer's `AccuracySec=60` naturally prevents overlapping oneshot runs (systemd will not start a new instance of a oneshot while one is already running).

    **Create `scripts/systemd/opendeviationbar-exness-seeder.timer`:**

    ```ini
    # Exness forex tick cache seeder timer — runs every 1 hour
    # Forex data volume is ~10x smaller than crypto; 1h interval is sufficient.
    # State file freshness checked by monit: /var/tmp/opendeviationbar-exness-seeder-state.txt
    [Unit]
    Description=OpenDeviationBar Exness Forex Tick Cache Seeder Timer (1h)

    [Timer]
    OnBootSec=300
    OnUnitActiveSec=3600
    AccuracySec=60

    [Install]
    WantedBy=timers.target
    ```

    **Update `deploy/monit/opendeviationbar.conf`:**

    Add a new monitor block AFTER the existing `seeder-state` monitor (around line 63). Insert:

    ```
    # --- Exness Seeder state freshness (Phase 02) ---
    # Checks /var/tmp/opendeviationbar-exness-seeder-state.txt is fresh (updated every 1h by timer).
    # Stale > 2h (2x interval) means the seeder timer stopped firing or a run is hanging.
    # Content check: alert if failed=[1-9] (any symbol failed to seed this pass).
    check file exness-seeder-state
        with path /var/tmp/opendeviationbar-exness-seeder-state.txt
        if timestamp > 120 minutes then exec "/usr/local/bin/obdpy-monit-alert.sh"
            as uid "tca" and gid "tca"
        if match "failed=[1-9]" then exec "/usr/local/bin/obdpy-monit-alert.sh"
            as uid "tca" and gid "tca"
    ```

    Note: 120 minutes = 2 hours = 2x the 1h timer interval.

  </action>
  <verify>
    <automated>cd /Users/terryli/eon/opendeviationbar-py && python3 -c "
# Verify service file
svc = open('scripts/systemd/opendeviationbar-exness-seeder.service').read()
assert 'Type=oneshot' in svc
assert 'exness_tick_cache_seeder.py' in svc
assert 'MemoryHigh=2G' in svc
assert 'MemoryMax=4G' in svc
assert 'LD_PRELOAD' in svc
assert 'TZ=UTC' in svc
# MUST NOT have self-referential Conflicts=
assert 'Conflicts=opendeviationbar-exness-seeder.service' not in svc, 'Self-referential Conflicts= will prevent service from starting'

# Verify timer file

tmr = open('scripts/systemd/opendeviationbar-exness-seeder.timer').read()
assert 'OnBootSec=300' in tmr
assert 'OnUnitActiveSec=3600' in tmr
assert 'WantedBy=timers.target' in tmr

# Verify monit config

monit = open('deploy/monit/opendeviationbar.conf').read()
assert 'exness-seeder-state' in monit
assert '/var/tmp/opendeviationbar-exness-seeder-state.txt' in monit
assert '120 minutes' in monit

print('systemd + monit files validated OK')
"</automated>
</verify>
<acceptance_criteria> - `scripts/systemd/opendeviationbar-exness-seeder.service` contains: Type=oneshot, MemoryHigh=2G, MemoryMax=4G, TZ=UTC, LD_PRELOAD, exness_tick_cache_seeder.py — NO self-referential Conflicts= line - `scripts/systemd/opendeviationbar-exness-seeder.timer` contains: OnBootSec=300, OnUnitActiveSec=3600, WantedBy=timers.target - `deploy/monit/opendeviationbar.conf` contains `check file exness-seeder-state` with path `/var/tmp/opendeviationbar-exness-seeder-state.txt` and `timestamp > 120 minutes` - Monit config contains `failed=[1-9]` pattern match for exness-seeder-state
</acceptance_criteria>
<done>systemd oneshot + 1h timer created (no self-referential Conflicts=), monit monitor added with 2h freshness threshold</done>
</task>

<task type="auto">
  <name>Task 2: Update CLAUDE.md hub-and-spoke network with Exness seeder docs</name>
  <files>scripts/CLAUDE.md, scripts/systemd/CLAUDE.md, deploy/CLAUDE.md, CLAUDE.md</files>
  <read_first>
    - scripts/CLAUDE.md (find "Parquet Tick Cache Seeder" section to add parallel Exness section)
    - scripts/systemd/CLAUDE.md (find Services and Timers tables to add new entries)
    - deploy/CLAUDE.md (find Monit Monitors table to update count and add row)
    - CLAUDE.md (find Quick Reference table and Architecture section)
    - .planning/phases/02-exness-daily-tick-cache-seeder-service-with-registry-integration/02-CONTEXT.md (what to document)
  </read_first>
  <action>
    **1. Update `scripts/CLAUDE.md`:**

    After the "Parquet Tick Cache Seeder (Issue #270)" section (which ends before "## Stathera"), add a new section:

    ```markdown
    ## Exness Forex Tick Cache Seeder (Phase 02)

    Downloads daily ZIP archives from `ticks.ex2archive.com` for all registry-enabled Exness forex symbols and writes raw bid/ask quotes to the local Parquet tick cache (`~/.cache/opendeviationbar/ticks/EXNESS_RAW_SPREAD_{SYMBOL}/`).

    ### Service Design

    - **Type**: systemd oneshot + 1h timer (`opendeviationbar-exness-seeder.timer`)
    - **Entry point**: `scripts/exness_tick_cache_seeder.py`
    - **Symbols**: All enabled Exness symbols (`get_symbol_entries()` filtered by `exchange="exness"`)
    - **Start date**: `OPENDEVIATIONBAR_EXNESS_SEEDER_HISTORICAL_START=2022-01-01`
    - **Concurrency**: `OPENDEVIATIONBAR_EXNESS_SEEDER_MAX_CONCURRENT_DOWNLOADS=4`
    - **Data format**: 3-column Parquet (timestamp ms, bid f64, ask f64) — quotes, not trades

    ### Key Differences from Binance Seeder

    | Aspect | Binance Seeder | Exness Seeder |
    | --- | --- | --- |
    | Timer interval | 5 min | 1 hour |
    | Workers | 12 | 4 |
    | Memory (High/Max) | 7G/10G | 2G/4G |
    | Data type | aggTrades (7 cols) | Quotes (3 cols: timestamp, bid, ask) |
    | CSV header | No | Yes (5 columns) |
    | Rate limiting | Binance weight limits | None (zero rate limits) |
    | Batch rotation | Yes (6 symbols/run) | No (all 10 symbols/run) |
    | REST fallback | Yes (recent days) | No (no equivalent API) |

    ### Instruments (10 Forex Pairs)

    EURUSD, GBPUSD, USDJPY, AUDUSD, USDCAD, NZDUSD, EURGBP, EURJPY, GBPJPY, XAUUSD (gold).

    All registered in `symbols.toml` with `exchange="exness"`, `market="raw_spread"`, `asset_class="forex"`.
    ```

    **2. Update `scripts/systemd/CLAUDE.md`:**

    Add to the Services table (after the seeder row):

    | `opendeviationbar-exness-seeder` | oneshot (timer) | Exness forex Parquet tick cache seeder | 2G / 4G |

    Add to the Timers table:

    | `opendeviationbar-exness-seeder.timer` | Every 1 hour | exness-seeder |

    Update the service count in the header: "Services (5 units)" -> "Services (6 units)" and "Timers (2)" -> "Timers (3)".

    **3. Update `deploy/CLAUDE.md`:**

    In the Monit Monitors table, add a new row:

    | exness-seeder-state | file | `/var/tmp/opendeviationbar-exness-seeder-state.txt` mtime <2h | 3 cycles |

    Update the table header: "Monit Monitors (10 total)" -> "Monit Monitors (11 total)".

    **4. Update root `CLAUDE.md`:**

    In the Quick Reference table, add a new row after the "Operations & scripts" row:

    | Exness forex seeder | `scripts/exness_tick_cache_seeder.py` | [scripts/CLAUDE.md](/scripts/CLAUDE.md#exness-forex-tick-cache-seeder-phase-02) |

    In the Architecture section, under the `scripts/` line in the directory tree, add:
    ```
    ├── scripts/                   Pueue jobs, validation, cache population, Exness seeder
    ```

    In the Monitoring & Gap Prevention section (the L1-L8 table), this phase does NOT add new layers, so no changes needed there.

  </action>
  <verify>
    <automated>cd /Users/terryli/eon/opendeviationbar-py && python3 -c "
# Verify scripts/CLAUDE.md has Exness section
s = open('scripts/CLAUDE.md').read()
assert 'Exness Forex Tick Cache Seeder' in s, 'Missing Exness section in scripts/CLAUDE.md'
assert 'exness_tick_cache_seeder.py' in s

# Verify scripts/systemd/CLAUDE.md has new entries

sys_md = open('scripts/systemd/CLAUDE.md').read()
assert 'opendeviationbar-exness-seeder' in sys_md, 'Missing Exness seeder in systemd CLAUDE.md'
assert '6 units' in sys_md or 'Services (6' in sys_md, 'Service count not updated'

# Verify deploy/CLAUDE.md has new monitor

deploy_md = open('deploy/CLAUDE.md').read()
assert 'exness-seeder-state' in deploy_md, 'Missing Exness monitor in deploy CLAUDE.md'
assert '11 total' in deploy_md or '11)' in deploy_md, 'Monitor count not updated'

# Verify root CLAUDE.md has Exness reference

root_md = open('CLAUDE.md').read()
assert 'exness_tick_cache_seeder' in root_md or 'Exness forex seeder' in root_md, 'Missing Exness in root CLAUDE.md'

print('All 4 CLAUDE.md files validated OK')
"</automated>
</verify>
<acceptance_criteria> - `scripts/CLAUDE.md` contains "Exness Forex Tick Cache Seeder" section with service design table - `scripts/systemd/CLAUDE.md` Services table has `opendeviationbar-exness-seeder` row with `2G / 4G`, Timers table has `Every 1 hour` row, counts updated to 6 services and 3 timers - `deploy/CLAUDE.md` Monit Monitors table has `exness-seeder-state` row with `mtime <2h`, count updated to 11 - `CLAUDE.md` Quick Reference table has Exness forex seeder row
</acceptance_criteria>
<done>All 4 CLAUDE.md files updated with Exness seeder documentation, service/timer tables accurate, monitor count correct</done>
</task>

</tasks>

<verification>
```bash
# Verify all files exist
cd /Users/terryli/eon/opendeviationbar-py
ls -la scripts/systemd/opendeviationbar-exness-seeder.{service,timer}
grep -c "exness-seeder-state" deploy/monit/opendeviationbar.conf

# Count monit monitors (should be 11)

grep -c "^check " deploy/monit/opendeviationbar.conf

```
</verification>

<success_criteria>
- systemd service + timer files created with correct config values (no self-referential Conflicts=)
- Monit config extended with exness-seeder-state monitor (2h threshold, failed pattern)
- All 4 CLAUDE.md files updated with Exness seeder documentation
- Total monit monitors = 11
</success_criteria>

<output>
After completion, create `.planning/phases/02-exness-daily-tick-cache-seeder-service-with-registry-integration/02-03-SUMMARY.md`
</output>
```
