diff --git a/CLAUDE.md b/CLAUDE.md index 43cb07b..4561c90 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,7 +39,7 @@ Python 3.12; a `venv/` exists in-repo. Dependencies in `requirements.txt` (numpy ## Configuration & credentials -Precedence for every credential/deployment setting: **env var → config.json → refuse to run** (no hardcoded fallbacks). A gitignored repo-root `.env` is loaded at `app_config` import via `os.environ.setdefault`. `config.json` (gitignored; schema in `config.json.example`) holds secrets plus deployment settings: `data_providers.active` (the live-feed switch), `data_providers.streaming` (optional tick-stream provider for the level-breach monitor), `scanning.pairs`/`timeframes`, per-pair `blocked_hours_utc`, `log_level`. Key env vars: `TELEGRAM_TOKEN`, `TELEGRAM_CHAT_ID`, `TELEGRAM_CHANNEL_ID`, `TWELVE_DATA_API_KEY`, `TIINGO_API_KEY`, `OANDA_ACCOUNT_ID`/`OANDA_API_TOKEN`/`OANDA_BASE_URL`, `DEEPSEEK_API_KEY` (news LLM; the `deepseek` config.json block holds model/enabled/timeout/daily cap). The `agent_panel` config.json block (all-defaults = feature dark) controls the advisory LLM agent panel: `enabled`, per-surface `telegram`/`dashboard` toggles, `min_quality`, `max_panels_per_day`, `reuse_ttl_seconds`, plus the technical-payload enrichment knobs `candles_enabled`/`candle_bars_primary`/`candle_bars_confirm`. The dark-by-default `market_outlook` block controls the hourly advisory market brief: `enabled`, `telegram`, `max_per_day`, `candles_bars`; `MARKET_OUTLOOK_ENABLED` overrides its enabled setting. The dark-by-default `violent_watch` block controls the display-only violent-move watch: `enabled`, `alert_line`, `headsup`, `max_headsup_per_day`, `headsup_min_level` (`"watch"`/`"hot"`); `VIOLENT_WATCH_ENABLED` overrides its enabled setting. +Precedence for every credential/deployment setting: **env var → config.json → refuse to run** (no hardcoded fallbacks). A gitignored repo-root `.env` is loaded at `app_config` import via `os.environ.setdefault`. `config.json` (gitignored; schema in `config.json.example`) holds secrets plus deployment settings: `data_providers.active` (the live-feed switch), `data_providers.streaming` (optional tick-stream provider for the level-breach monitor), `scanning.pairs`/`timeframes`, per-pair `blocked_hours_utc`, `log_level`. Key env vars: `TELEGRAM_TOKEN`, `TELEGRAM_CHAT_ID`, `TELEGRAM_CHANNEL_ID`, `TWELVE_DATA_API_KEY`, `TIINGO_API_KEY`, `OANDA_API_TOKEN`/`OANDA_BASE_URL` (the data feed needs no account id; a legacy `oanda.account_id` key is tolerated and ignored), `DEEPSEEK_API_KEY` (news LLM; the `deepseek` config.json block holds model/enabled/timeout/daily cap). The `agent_panel` config.json block (all-defaults = feature dark) controls the advisory LLM agent panel: `enabled`, per-surface `telegram`/`dashboard` toggles, `min_quality`, `max_panels_per_day`, `reuse_ttl_seconds`, plus the technical-payload enrichment knobs `candles_enabled`/`candle_bars_primary`/`candle_bars_confirm`. The dark-by-default `market_outlook` block controls the hourly advisory market brief: `enabled`, `telegram`, `max_per_day`, `candles_bars`; `MARKET_OUTLOOK_ENABLED` overrides its enabled setting. The dark-by-default `violent_watch` block controls the display-only violent-move watch: `enabled`, `alert_line`, `headsup`, `max_headsup_per_day`, `headsup_min_level` (`"watch"`/`"hot"`); `VIOLENT_WATCH_ENABLED` overrides its enabled setting. ## Architecture diff --git a/app_config.py b/app_config.py index 240d03a..1d191d8 100644 --- a/app_config.py +++ b/app_config.py @@ -89,6 +89,10 @@ _KNOWN_TOP = {"telegram", "data_providers", "scanning", "pairs", "log_level", "oanda", "deepseek", "agent_panel", "market_outlook", "violent_watch", "signal_log"} _KNOWN_OANDA = { + # "account_id" is TOLERATED but ignored: it belonged to the auto- + # execution daemon removed 2026-09-03 (last commit 31002cb). The candle + # provider needs only api_token + base_url; older config.json files + # still carry the key and must keep loading. "account_id", "api_token", "base_url", } _KNOWN_DEEPSEEK = { @@ -458,12 +462,6 @@ def provider_api_key( # ── Oanda resolution helpers (env → config.json → default) ─────────── -def oanda_account_id(app: Optional[AppConfig] = None) -> str: - app = app or get_app_config() - return os.environ.get("OANDA_ACCOUNT_ID") or str( - app.oanda_settings.get("account_id", "")) - - def oanda_api_token(app: Optional[AppConfig] = None) -> str: app = app or get_app_config() return os.environ.get("OANDA_API_TOKEN") or str( diff --git a/config.json.example b/config.json.example index f574be3..1b7fff5 100644 --- a/config.json.example +++ b/config.json.example @@ -26,7 +26,6 @@ } }, "oanda": { - "account_id": "REPLACE_WITH_ACCOUNT_ID", "api_token": "REPLACE_WITH_API_TOKEN", "base_url": "https://api-fxpractice.oanda.com" }, diff --git a/test_all.py b/test_all.py index 43fc3b4..c9fdf8f 100644 --- a/test_all.py +++ b/test_all.py @@ -10491,6 +10491,21 @@ def _(): os.remove(path) +@test("app_config: legacy oanda.account_id is tolerated and ignored (auto-execution removed 2026-09-03)") +def _(): + import copy + import app_config as _ac + payload = copy.deepcopy(_FULL_CFG) + payload.setdefault("oanda", {})["account_id"] = "101-004-LEGACY-001" + payload["oanda"].setdefault("api_token", "tok") + app = load_app_config(_write_json(payload)) # must NOT raise + assert app.oanda_settings.get("api_token") == payload["oanda"]["api_token"] + assert not hasattr(_ac, "oanda_account_id"), "dead accessor must stay deleted" + assert "account_id" not in open("config.json.example").read(), \ + "the example must not advertise a key nothing reads" + assert _ac.oanda_api_token(app) == payload["oanda"]["api_token"] + + # ══════════════════════════════════════════════════════════════════════ # SUMMARY # ══════════════════════════════════════════════════════════════════════