---
phase: 02-exness-daily-tick-cache-seeder-service-with-registry-integration
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - python/opendeviationbar/data/symbols.toml
  - python/opendeviationbar/config/exness_seeder.py
  - python/opendeviationbar/config/__init__.py
autonomous: true
requirements:
  - EXNS-REG
  - EXNS-CFG
must_haves:
  truths:
    - "10 Exness forex symbols exist in symbols.toml with correct schema fields"
    - "ExnessSeederConfig loads from environment variables with correct defaults"
    - "get_symbol_entries() returns SymbolEntry objects for all 10 Exness symbols with exchange='exness'"
  artifacts:
    - path: "python/opendeviationbar/data/symbols.toml"
      provides: "10 Exness forex symbol registry entries"
      contains: "symbols.EURUSD"
    - path: "python/opendeviationbar/config/exness_seeder.py"
      provides: "ExnessSeederConfig pydantic-settings class"
      exports: ["ExnessSeederConfig"]
  key_links:
    - from: "python/opendeviationbar/config/exness_seeder.py"
      to: "pydantic_settings.BaseSettings"
      via: "class inheritance"
      pattern: "class ExnessSeederConfig.*BaseSettings"
    - from: "python/opendeviationbar/data/symbols.toml"
      to: "python/opendeviationbar/symbol_registry.py"
      via: "tomllib.load in _load_registry()"
      pattern: "exchange.*exness"
---

<objective>
Add 10 Exness forex symbols to the unified symbol registry and create the ExnessSeederConfig configuration class.

Purpose: The registry entries are the SSoT that the seeder script reads from. The config class provides environment-variable-driven configuration. Both are prerequisites for the seeder script (Plan 02) and deployment (Plan 03).

Output: Updated symbols.toml with 10 Exness entries, new ExnessSeederConfig class.
</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/STATE.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>
<!-- Key types and contracts the executor needs. -->

From python/opendeviationbar/symbol_registry.py:

```python
@dataclass(frozen=True)
class SymbolEntry:
    symbol: str
    enabled: bool
    asset_class: str        # "crypto", "forex", "equities"
    exchange: str           # "binance", "exness"
    market: str             # "spot", "futures-um", "raw_spread"
    listing_date: date
    effective_start: date | None
    ...
    thresholds: tuple[int, ...]
    min_threshold: int
    keywords: tuple[str, ...]
    ...

def get_symbol_entries() -> MappingProxyType[str, SymbolEntry]: ...
def get_registered_symbols(*, asset_class=None, tier=None, enabled_only=True) -> tuple[str, ...]: ...
```

From python/opendeviationbar/config/seeder.py (pattern to follow):

```python
class SeederConfig(BaseSettings):
    model_config = SettingsConfigDict(
        env_prefix="OPENDEVIATIONBAR_SEEDER_",
        case_sensitive=False,
    )
    historical_start: str = "2018-01-01"
    max_concurrent_downloads: int = 12
    vision_timeout_s: int = 120
    head_timeout_s: int = 10
    state_file: str = "/var/tmp/opendeviationbar-seeder-state.txt"
    markets: str = "spot"
    rest_fallback_enabled: bool = True
    rest_fallback_max_age_days: int = 2
    batch_size: int = 6
    last_seeded_state_file: str = "/var/tmp/opendeviationbar-seeder-last-seeded.json"
```

Existing symbols.toml entry pattern (BTCUSDT):

```toml
[symbols.BTCUSDT]
enabled = true
asset_class = "crypto"
exchange = "binance"
market = "spot"
tier = 1
listing_date = 2017-08-17
effective_start = 2018-01-16
reason = "..."
first_clean_date = 2018-01-16
data_anomalies = [...]
processing_notes = "..."
min_threshold = 100
thresholds = [100, 250, 500, 750]
kintsugi_priority = 10
keywords = [...]
reference = "..."
```

</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add 10 Exness forex symbols to symbols.toml registry</name>
  <files>python/opendeviationbar/data/symbols.toml</files>
  <read_first>
    - python/opendeviationbar/data/symbols.toml (current registry — see existing entry schema)
    - python/opendeviationbar/symbol_registry.py (SymbolEntry dataclass fields — lines 177-240)
    - .planning/phases/02-exness-daily-tick-cache-seeder-service-with-registry-integration/02-CONTEXT.md (locked decisions)
    - .planning/phases/02-exness-daily-tick-cache-seeder-service-with-registry-integration/02-RESEARCH.md (XAUUSD thresholds, entry template)
  </read_first>
  <action>
    Update the header comment (lines 1-16) to mention Exness forex alongside Binance crypto in the keywords line. Add this text to the keywords comment: "exness, forex" (already present — verify).

    After the last Binance crypto entry (end of file), add a new section header and 10 Exness forex symbol entries. The exact entries:

    ```
    # =============================================================================
    # Forex Symbols (Exness Raw Spread)
    # =============================================================================

    [symbols.EURUSD]
    enabled = true
    asset_class = "forex"
    exchange = "exness"
    market = "raw_spread"
    tier = 1
    listing_date = 2022-01-01
    effective_start = 2022-01-03
    min_threshold = 50
    thresholds = [50, 100, 200]
    keywords = ["euro", "dollar", "eurusd", "forex", "major"]
    processing_notes = "Exness Raw_Spread. Tick data = bid/ask quotes, not trades. ~60K ticks/day."

    [symbols.GBPUSD]
    enabled = true
    asset_class = "forex"
    exchange = "exness"
    market = "raw_spread"
    tier = 1
    listing_date = 2022-01-01
    effective_start = 2022-01-03
    min_threshold = 50
    thresholds = [50, 100, 200]
    keywords = ["pound", "dollar", "gbpusd", "forex", "major"]
    processing_notes = "Exness Raw_Spread. Tick data = bid/ask quotes. ~55K ticks/day."

    [symbols.USDJPY]
    enabled = true
    asset_class = "forex"
    exchange = "exness"
    market = "raw_spread"
    tier = 1
    listing_date = 2022-01-01
    effective_start = 2022-01-03
    min_threshold = 50
    thresholds = [50, 100, 200]
    keywords = ["dollar", "yen", "usdjpy", "forex", "major"]
    processing_notes = "Exness Raw_Spread. Tick data = bid/ask quotes. ~50K ticks/day."

    [symbols.AUDUSD]
    enabled = true
    asset_class = "forex"
    exchange = "exness"
    market = "raw_spread"
    tier = 2
    listing_date = 2022-01-01
    effective_start = 2022-01-03
    min_threshold = 50
    thresholds = [50, 100, 200]
    keywords = ["aussie", "dollar", "audusd", "forex", "major"]
    processing_notes = "Exness Raw_Spread. Tick data = bid/ask quotes. ~40K ticks/day."

    [symbols.USDCAD]
    enabled = true
    asset_class = "forex"
    exchange = "exness"
    market = "raw_spread"
    tier = 2
    listing_date = 2022-01-01
    effective_start = 2022-01-03
    min_threshold = 50
    thresholds = [50, 100, 200]
    keywords = ["dollar", "loonie", "usdcad", "forex", "major"]
    processing_notes = "Exness Raw_Spread. Tick data = bid/ask quotes. ~35K ticks/day."

    [symbols.NZDUSD]
    enabled = true
    asset_class = "forex"
    exchange = "exness"
    market = "raw_spread"
    tier = 2
    listing_date = 2022-01-01
    effective_start = 2022-01-03
    min_threshold = 50
    thresholds = [50, 100, 200]
    keywords = ["kiwi", "dollar", "nzdusd", "forex", "major"]
    processing_notes = "Exness Raw_Spread. Tick data = bid/ask quotes. ~30K ticks/day."

    [symbols.EURGBP]
    enabled = true
    asset_class = "forex"
    exchange = "exness"
    market = "raw_spread"
    tier = 2
    listing_date = 2022-01-01
    effective_start = 2022-01-03
    min_threshold = 50
    thresholds = [50, 100, 200]
    keywords = ["euro", "pound", "eurgbp", "forex", "cross"]
    processing_notes = "Exness Raw_Spread. Tick data = bid/ask quotes. ~35K ticks/day."

    [symbols.EURJPY]
    enabled = true
    asset_class = "forex"
    exchange = "exness"
    market = "raw_spread"
    tier = 2
    listing_date = 2022-01-01
    effective_start = 2022-01-03
    min_threshold = 50
    thresholds = [50, 100, 200]
    keywords = ["euro", "yen", "eurjpy", "forex", "cross"]
    processing_notes = "Exness Raw_Spread. Tick data = bid/ask quotes. ~40K ticks/day."

    [symbols.GBPJPY]
    enabled = true
    asset_class = "forex"
    exchange = "exness"
    market = "raw_spread"
    tier = 2
    listing_date = 2022-01-01
    effective_start = 2022-01-03
    min_threshold = 50
    thresholds = [50, 100, 200]
    keywords = ["pound", "yen", "gbpjpy", "forex", "cross"]
    processing_notes = "Exness Raw_Spread. Tick data = bid/ask quotes. ~45K ticks/day."

    [symbols.XAUUSD]
    enabled = true
    asset_class = "forex"
    exchange = "exness"
    market = "raw_spread"
    tier = 1
    listing_date = 2022-01-01
    effective_start = 2022-01-03
    min_threshold = 200
    thresholds = [200, 500, 1000]
    keywords = ["gold", "xauusd", "forex", "commodity", "precious metal"]
    processing_notes = "Exness Raw_Spread. Gold quotes ~500K ticks/day. Higher thresholds than forex due to wider spreads and larger price moves."
    ```

    Key decisions:
    - effective_start = 2022-01-03 (Monday, first trading day of 2022)
    - EURUSD/GBPUSD/USDJPY/XAUUSD = tier 1, rest = tier 2
    - XAUUSD thresholds = [200, 500, 1000] per research recommendation (wider spreads, more volatile)
    - All other forex = [50, 100, 200] per CONTEXT.md locked decision for EURUSD
    - No `kintsugi_priority` field (kintsugi integration is deferred)
    - No `data_anomalies` or `known_gaps` (clean data assumed for Exness)

  </action>
  <verify>
    <automated>cd /Users/terryli/eon/opendeviationbar-py && python3 -c "
import tomllib
with open('python/opendeviationbar/data/symbols.toml', 'rb') as f:
    data = tomllib.load(f)
exness = {k: v for k, v in data['symbols'].items() if v.get('exchange') == 'exness'}
assert len(exness) == 10, f'Expected 10 Exness symbols, got {len(exness)}'
for sym in ['EURUSD','GBPUSD','USDJPY','AUDUSD','USDCAD','NZDUSD','EURGBP','EURJPY','GBPJPY','XAUUSD']:
    e = exness[sym]
    assert e['asset_class'] == 'forex', f'{sym} wrong asset_class'
    assert e['exchange'] == 'exness', f'{sym} wrong exchange'
    assert e['market'] == 'raw_spread', f'{sym} wrong market'
    assert e['enabled'] == True, f'{sym} not enabled'
assert exness['EURUSD']['thresholds'] == [50, 100, 200]
assert exness['XAUUSD']['thresholds'] == [200, 500, 1000]
assert exness['XAUUSD']['min_threshold'] == 200
print('All 10 Exness symbols validated OK')
"</automated>
  </verify>
  <acceptance_criteria>
    - symbols.toml contains `[symbols.EURUSD]` through `[symbols.XAUUSD]` (10 entries)
    - Each entry has fields: enabled, asset_class, exchange, market, tier, listing_date, effective_start, min_threshold, thresholds, keywords, processing_notes
    - All 10 have `exchange = "exness"` and `market = "raw_spread"` and `asset_class = "forex"`
    - EURUSD thresholds = [50, 100, 200], XAUUSD thresholds = [200, 500, 1000]
    - Header comment contains "exness" and "forex" in keywords line
    - `tomllib.load()` succeeds without errors (valid TOML)
  </acceptance_criteria>
  <done>10 Exness forex symbols registered in symbols.toml with correct schema, parseable by tomllib, filterable by exchange="exness"</done>
</task>

<task type="auto">
  <name>Task 2: Create ExnessSeederConfig pydantic-settings class</name>
  <files>python/opendeviationbar/config/exness_seeder.py, python/opendeviationbar/config/__init__.py</files>
  <read_first>
    - python/opendeviationbar/config/seeder.py (SeederConfig pattern to mirror)
    - python/opendeviationbar/config/__init__.py (re-export pattern)
    - python/opendeviationbar/config/CLAUDE.md (config subsystem conventions)
    - .planning/phases/02-exness-daily-tick-cache-seeder-service-with-registry-integration/02-CONTEXT.md (env var names, defaults)
  </read_first>
  <action>
    Create `python/opendeviationbar/config/exness_seeder.py` with:

    ```python
    """Exness forex tick cache seeder configuration.

    Seeds bigblack's local tick cache from Exness daily ZIP archives so that
    future forex bar construction has all quotes pre-cached locally.

    Environment variable prefix: OPENDEVIATIONBAR_EXNESS_SEEDER_
    """

    from __future__ import annotations

    from pydantic_settings import BaseSettings, SettingsConfigDict


    class ExnessSeederConfig(BaseSettings):
        """Exness tick cache seeder configuration.

        Environment Variables
        ---------------------
        OPENDEVIATIONBAR_EXNESS_SEEDER_HISTORICAL_START : str
            Earliest date to seed (YYYY-MM-DD). Per-symbol effective_start in
            symbols.toml overrides this when it is later (default: "2022-01-01").
        OPENDEVIATIONBAR_EXNESS_SEEDER_MAX_CONCURRENT_DOWNLOADS : int
            Concurrent ZIP downloads per symbol (default: 4).
        OPENDEVIATIONBAR_EXNESS_SEEDER_DOWNLOAD_TIMEOUT_S : int
            HTTP timeout for ZIP downloads in seconds (default: 60).
        OPENDEVIATIONBAR_EXNESS_SEEDER_STATE_FILE : str
            Path to state file written on completion for monit freshness check.
            Default: "/var/tmp/opendeviationbar-exness-seeder-state.txt"
        """

        model_config = SettingsConfigDict(
            env_prefix="OPENDEVIATIONBAR_EXNESS_SEEDER_",
            case_sensitive=False,
        )

        historical_start: str = "2022-01-01"
        max_concurrent_downloads: int = 4
        download_timeout_s: int = 60
        state_file: str = "/var/tmp/opendeviationbar-exness-seeder-state.txt"
    ```

    Then update `python/opendeviationbar/config/__init__.py` to add `ExnessSeederConfig` to the re-exports. Find the existing imports/exports pattern and add:
    - Import: `from .exness_seeder import ExnessSeederConfig`
    - Add `ExnessSeederConfig` to `__all__` list if one exists

  </action>
  <verify>
    <automated>cd /Users/terryli/eon/opendeviationbar-py && python3 -c "
from opendeviationbar.config.exness_seeder import ExnessSeederConfig
cfg = ExnessSeederConfig()
assert cfg.historical_start == '2022-01-01'
assert cfg.max_concurrent_downloads == 4
assert cfg.download_timeout_s == 60
assert cfg.state_file == '/var/tmp/opendeviationbar-exness-seeder-state.txt'
print('ExnessSeederConfig validated OK')
"</automated>
  </verify>
  <acceptance_criteria>
    - File `python/opendeviationbar/config/exness_seeder.py` exists
    - Contains `class ExnessSeederConfig(BaseSettings)`
    - `env_prefix="OPENDEVIATIONBAR_EXNESS_SEEDER_"` in model_config
    - Fields: historical_start (str, "2022-01-01"), max_concurrent_downloads (int, 4), download_timeout_s (int, 60), state_file (str, "/var/tmp/opendeviationbar-exness-seeder-state.txt")
    - `from opendeviationbar.config.exness_seeder import ExnessSeederConfig` succeeds
  </acceptance_criteria>
  <done>ExnessSeederConfig class created with all 4 configurable fields, importable from config package</done>
</task>

</tasks>

<verification>
```bash
# Verify registry loads all Exness symbols
cd /Users/terryli/eon/opendeviationbar-py && python3 -c "
from opendeviationbar.symbol_registry import get_symbol_entries
entries = get_symbol_entries()
exness = {k: v for k, v in entries.items() if v.exchange == 'exness'}
assert len(exness) == 10, f'Expected 10, got {len(exness)}'
print(f'Registry: {len(exness)} Exness symbols loaded')
for name, entry in sorted(exness.items()):
    print(f'  {name}: tier={entry.tier}, thresholds={entry.thresholds}')
"

# Verify config

python3 -c "
from opendeviationbar.config.exness_seeder import ExnessSeederConfig
cfg = ExnessSeederConfig()
print(f'Config: start={cfg.historical_start}, workers={cfg.max_concurrent_downloads}, timeout={cfg.download_timeout_s}s')
"

```
</verification>

<success_criteria>
- 10 Exness forex symbols in symbols.toml, all with exchange="exness", market="raw_spread", asset_class="forex"
- ExnessSeederConfig loadable via pydantic-settings with correct defaults
- Both are importable from their respective modules without errors
</success_criteria>

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