Files
ForexBot2/CLAUDE.md
T
Harwinder Sekhon b936d443b8 Cleanup: remove dead code, stale docs, and all non-main branches
- Delete back-compat shims data_provider.py / oanda_provider.py;
  test_all.py now imports providers.* directly (ForexDataProvider name
  retired; the two shim-only tests removed, 268 -> 266 tests)
- Delete backtest/research/calendar_store.py (no importer, no entry
  point); config.py audit comment reworded
- Delete executed plan doc (2026-07-21 centralized-config) and the two
  unbuildable guide PDFs; HTML guides remain the source of truth
- OANDA_MIGRATION_ROADMAP.md status header corrected: phases 1-2
  shipped 2026-07-28, feed is live on Oanda; phases 3-6 still unbuilt
- Branches deleted locally + on both remotes: oanda_demo (merged),
  web-dashboard, eurusd-modular, reversal-exit-alert (owner-approved
  permanent deletion); July stash dropped

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zR58p8ACW1oJ5QXVUZ5es
2026-08-03 11:08:48 -04:00

10 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

Signal-intelligence platform for XAU/USD (gold — the only supported pair). It fetches OHLCV from the active data provider (Oanda v20 since 2026-07-28; Twelve Data and Tiingo remain registered fallbacks), scores 9 technical indicators per timeframe with regime-adaptive weights, checks multi-timeframe agreement and news risk, grades the signal (A+…F), and generates a full trade setup (entry, SL, TP1/TP2, trailing plan). Delivery: Telegram bot, CLI, and an Oanda practice-account auto-execution daemon. A realism-focused backtester replays history through the exact same signal code.

Every numeric threshold in config.py and pair_signals/xauusd.py was backtested or is load-bearing for a backtested result. Validation evidence lives in the factory/module docstrings and in ~/.claude/projects/.../memory/.

Commands

python test_all.py                      # QA suite: 266 tests, 18 modules, no network (mocked)
python main.py --pair XAUUSD --timeframe H1 --detail   # CLI signal report (--json for full dump)
python telegram_bot.py                  # the bot (refuses to boot without credentials)
python oanda_daemon.py                  # auto-execution daemon on Oanda practice account

# Backtests
python backtest/gpu_backtest.py --pair XAUUSD --days 365
python backtest/gpu_backtest.py --pair XAUUSD --start 2025-01-01 --end 2025-06-01 \
    --primary-tf 15min --confirm-tfs 1h,4h
# key flags: --spread --quality A+,A,B,C --strategies --workers --max-hold-bars
#            --warmup-bars --cooldown-bars --no-cache --export-csv out.csv --detail

test_all.py is NOT pytest — it uses a custom @test("name") decorator that runs each test immediately at import; don't grep for def test_ or try pytest test_all.py. Run the whole file; it prints per-module results and exits non-zero on failure. There is no way to run a single test short of commenting modules out. Tests must stay green; every new gate/knob needs tests. When patching weekend logic in tests, patch providers.base._is_forex_weekend.

Python 3.12; a venv/ exists in-repo. Dependencies in requirements.txt (numpy, pandas, requests, twelvedata, python-telegram-bot ≥20, websockets).

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), 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.

Architecture

End-to-end pipeline (every scan)

providers/<active>.fetch_ohlcv()       per TF: throttle, fetch, drop forming candle, validate/clean, cache
  → engine.TechnicalEngine.analyze()   per TF: 9 scored indicators + ATR context → regime → composite score
  → signal_aggregator.analyze_pair()   live entry point (weekend/day filters, fetch, staleness)
      └─ analyze_from_dataframes()     replayable core: MTF agreement → news → bonuses/penalties →
                                       final signal + quality grade → gates → trade setup → reasons
  → SignalReport                       consumed by telegram_bot.py, main.py, oanda_daemon.py, backtester

Signal currency everywhere is SignalStrength (−2..+2). DataFrames are UTC tz-aware, oldest-first, capitalized OHLCV columns. Everything runs on closed bars only — the forming candle is dropped, and session/hour filters evaluate bar-CLOSE time (bar label + candle duration) identically in live and backtest.

Layers

  • providers/ — one class per provider subclassing BaseDataProvider (providers/base.py), which owns everything the pipeline depends on: TTL+boundary cache, forming-candle drop, freshness check, validate/clean, paginated fetch_range (feeds the backtester from any provider), optional tick streaming. Subclasses implement only _fetch_raw + symbol/interval maps. Registry + make_data_provider in providers/__init__.py; config.json data_providers.active selects the live feed. A test enforces that subclasses don't override shared base methods. Tiingo is candles-only — do not re-add spread display from its free FX feed (two-tier indicative quotes, removed 2026-07-23).
  • engine.py — TechnicalEngine: 9 scored indicators (ema_trend, rsi, macd, bollinger, adx, roc, keltner, divergence, candlestick) + ATR as context. Regime classification (trending_up/down, trending = mixed/direction-UNKNOWN, transitioning, ranging) drives per-indicator weight multipliers and regime-aware RSI reinterpretation. Compute order matters (ATR first; MACD always computed even when not a pillar — divergence needs the histogram). Private series ride on the result as _underscore attrs; scorers/formatters skip anything _-prefixed or weight-0.
  • signal_aggregator.py — MTF agreement, diminishing-decay penalty system, exhaustion hard block, quality grading, then an ordered chain of post-grade gates (session, XAU SELL quality/vol/hour, regime blocks, M5 noise gates), then three setup generators: XAU ATR (A+/A), generic swing-based, plus the M5 pullback (XAU_PB) and chart-pattern (XAU_CP, chart_patterns.py) entry paths. TradeSetup.strategy selects trailing params; trail params are stamped on the setup from the producing config so per-TF overrides reach formatters.
  • pair_signals/ — per-pair config factories. Always use the factory (produce_signal / make_config), never bare PlatformConfig() — the factory applies the config.json overlay first, then the validated per-TF deltas (M5 hour-9 block, M15 trail arm 0.70, M15 SELL-hour block, etc.; evidence in the docstring). A test enforces factory ≡ inline-config equivalence. Confirmation map: M5→[M15,H1], M15→[M5,H1], H1→[M15,H4], H4→[H1,D1].
  • news_engine.py — Forex Factory calendar (MyFXBook RSS fallback) + RSS keyword sentiment with gold-inverse keywords. Class-level caches (fresh engine per scan). By XAU-tuned default, event risk and sentiment warn but do not gate signals (event_risk_caps_signals=False, sentiment_affects_signals=False).
  • telegram_bot.py — owner-locked bot + subscriber channel; candle-boundary auto-scan loops (M5/M15/H1/H4, boundary+10 s), alert quality threshold + fingerprint dedup (committed only after a successful send), WebSocket level-breach monitor (auto-disabled when the active provider has supports_streaming=False — Oanda does not stream). Rotating log: logs/telegram.log, 3-h rotation × 240 backups.
  • oanda_daemon.py / oanda_executor.py — practice-account auto-execution. Book-parity rules (see daemon docstring): no broker-side TP (trailing model — TP1/TP2 advisory), 5-bar per-TF cooldown, trail ratchets at bar close exactly once per bar. Accepted divergence: one position total.
  • backtest/gpu_backtest.py — bar-by-bar replay through the real analyze_from_dataframes (news off). Realism model (do not regress): next-bar-open entries re-anchored to the raw price path, round-trip --spread charged per trade, trade resolution on RAW bars (cleaning applies only to signal-side data), same-bar SL+TP tie → SL first, gap fills at bar open, trail ratchet at bar close only. Indicators are precomputed once per TF (CuPy GPU or NumPy fallback — check the Backend: log line); replay fans out over worker processes and the 5-bar cooldown is applied in an ordered pass afterwards, so parallel and sequential books are identical. Parquet cache in backtest/cache/ keyed by provider/symbol/TF/window. backtest/*.csv are gitignored pinned trade books from past campaigns — used for manual before/after comparison. backtest/instructions_for_backtest.txt lists what every backtest report must record.

Rules for making changes (read before editing signal logic)

  1. Never change a tuned value without re-validation. Acceptance rule: positive total R in BOTH train and validation halves of a ≥12-month window, PF ≥ 1.3, at realistic cost (XAU $0.30 round trip), trailing exits, next-open fills. Marginal edges must additionally pass the 2024-06→2025-03 out-of-sample window — within-year splits alone have approved configs that later failed OOS.
  2. Per-timeframe overrides live in the pair factory, never in global defaults — the same knob has opposite effects on different TFs (proven repeatedly: tp_floor_rr, hour-9 block, trail params). Document validation evidence in the factory docstring.
  3. Pair isolation is a hard invariant. XAU gates in the aggregator are display-name-gated so a future pair port cannot inherit gold-calibrated logic by accident.
  4. Live == backtest parity. If you add a live gate, add it to the replay path in the same commit; all time filters evaluate bar-close time in both paths.
  5. ROC stays weighted even though it's correlated with MACD/Keltner/EMA — zeroing it backtested 13R worse; composite thresholds are calibrated with it.
  6. Backtester realism knobs are results-critical (raw-path resolution, spread charging, next-open entry, SL-first ties, close-only trail). Touching them invalidates comparability with all pinned books.
  7. The forming candle must stay dropped — live signals repaint otherwise.
  8. Don't add config fields nothing reads; don't disable indicators in production paths (disabled_indicators is for experiments only).
  9. EUR/USD was removed 2026-07-16 after a no-edge verdict (cost-to-volatility; last commit with EUR support: 7d3fd95). Do not re-add without new data. BTC/ETH work lives on unmerged branches only.

Current state notes (2026-07-30)

  • Oanda v20 is the live feed (data_providers.active: "oanda"), but the pinned backtest books are still Twelve-Data-pinned and the backtest cost assumption is $0.30 while measured real spreads are ~$0.65–0.72 — re-pin books on Oanda bars before treating feed-switch comparisons as valid (see Oanda/TD divergence study in memory).
  • Human-facing docs in docs/ (user_guide.html, beginner_guide.html + PDF builds) must be kept in sync when signal behavior changes.