{
  "_note": "config.json for screener/trade1 -- the bot CORE (scanner, stream, orders, sizing, EOD liquidation) with NO entry or exit strategy in it. All trade decisions come from the rule modules named under 'rules' (breakout_rules.py, reversal_rules.py, exit_rules.py); each module's own settings live in the section named after it. Shares the SAME Alpaca paper account as screener/trade -- never run both bots at the same time.",
  "mode": {
    "execution_mode": "paper",
    "_execution_mode_options": [
      "simulation",
      "paper",
      "live"
    ],
    "_note": "paper = real orders to the Alpaca paper account in .env (the SAME account screener/trade uses). simulation = no orders sent. live = real money."
  },
  "schedule": {
    "timezone": "America/New_York",
    "market_open_time": "09:30:00",
    "market_close_time": "16:00:00",
    "premarket_scan_time": "09:28:00",
    "no_new_entries_after": "15:15:00",
    "force_liquidate_time": "15:55:00",
    "poll_interval_seconds": 5,
    "eod_liquidation_max_wait_seconds": 120,
    "_note": "premarket_scan_time / no_new_entries_after / force_liquidate_time / poll_interval_seconds / eod_liquidation_max_wait_seconds drive monitor.py's daily lifecycle."
  },
  "universe": {
    "price_min": 5.0,
    "price_max": 15.0,
    "exclude_symbols": [],
    "include_only_symbols": [],
    "min_avg_daily_volume": 500000,
    "exclude_etfs_and_funds": true,
    "exclude_leveraged_inverse": true,
    "max_name_syllables": 5,
    "_note": "Same universe filters as the breakout_bot design doc calls for (Part II section 2), which are themselves the same filters sip_bot already uses -- price $5-$15, 500k avg daily volume floor, OTC/ETF/leveraged-inverse exclusion, max-5-syllable names. No min_daily_atr_pct floor here (unlike sip_bot's config) -- the breakout scanner is deliberately a pure continuous score with no hard reject gates beyond universe membership, per the design doc's philosophy that candidate_score answers 'which stocks deserve attention' not 'which stock will go up'."
  },
  "scanner": {
    "enabled": true,
    "top_candidate_count": 30,
    "rescan_interval_minutes": 30,
    "true_premarket_high": true,
    "_note_true_premarket_high": "[2026-09-24] BUG found: after the open, scanner.scan()'s 6h lookback includes regular-session bars, so rescans/restarts saved the high-so-far-today as premarket_high (58 of 68 rescanned symbols on 9/24). true: the saved premarket_high level uses premarket bars only (scoring unchanged). Recommended ON.",
    "first_rescan_after_open_minutes": 10,
    "rescan_interval_first_hour_minutes": 0,
    "_note_early_rescan": "[2026-09-24] DNA started moving after the open and was only picked up by the 9:58 rescan (+9% already). first_rescan_after_open_minutes (e.g. 10 = 9:40) forces one extra early rescan; rescan_interval_first_hour_minutes (e.g. 15) shortens the interval during the first hour. 0 = off.",
    "_note_rescan": "[2026-09-23] User request: refresh the top-30 list every 30 min during the session (background thread in monitor.py; stops at no_new_entries_after; 0 = off). Dropped symbols are unsubscribed unless a position is open; resistance levels stay from the first scan a symbol appeared in. Rescans write data/candidates/{date}_scanner_HHMM.json; the opening scan file is unchanged (simulate.py replays only that one).",
    "weights": {
      "relative_volume": 30,
      "premarket_volume": 20,
      "gap_or_price_movement": 20,
      "premarket_price_strength": 15,
      "distance_to_resistance": 15
    },
    "rvol_full_credit_multiple": 3.0,
    "premarket_volume_full_credit": 100000,
    "gap_full_credit_pct": 10.0,
    "price_strength_full_credit_pct": 5.0,
    "resistance_full_credit_distance_pct": 8.0,
    "_note": "Weights are the design doc's own explicit split (Part II section 3: 30/20/20/15/15). The four *_full_credit* thresholds are first-cut placeholders carried over from screener/premarket's breakout_scanner.py shadow-run (2026-09-16) -- tune once there are a few days of this project's own scanner output to compare against real outcomes. gap_or_price_movement and premarket_price_strength are scored SYMMETRICALLY (clamped to [-1, 1], not [0, 1]) so an actively declining stock scores worse than a flat one, not the same -- carried over from the same 2026-09-16 bugfix."
  },
  "streaming": {
    "feed": "sip",
    "reconnect_backoff_seconds": [
      2,
      5,
      10,
      30,
      60
    ],
    "max_reconnect_attempts": 20,
    "stale_data_seconds": 120,
    "sub_minute_bucket_seconds": 30,
    "sub_minute_buffer_minutes": 15,
    "_note": "Same shape and same values as sip_bot's streaming config -- stream.py here is a near-verbatim copy of screener/premarket's (it's generic buffering/reconnect logic with no strategy-specific code in it).",
    "benchmark_symbols": [],
    "_note_benchmark_symbols": "Symbols streamed as 1-min bars only (e.g. [\"SPY\",\"IWM\"]), never traded; rule modules read them through view.benchmarks."
  },
  "trading": {
    "max_positions": 5,
    "allow_multiple_entries_same_symbol": true,
    "same_symbol_reentry_cooldown_minutes": 0,
    "account_risk_pct_per_trade": 1.0,
    "max_position_notional_pct_of_equity": 19.0,
    "min_shares": 1,
    "_note": "max_positions raised 3->6 on 2026-09-18 per user request (originally started smaller than sip_bot's 5 since this was a brand-new, unvalidated engine -- now past that). Position size is risk-based: risk account_risk_pct_per_trade% of equity on the distance between entry and smart_engine's own Stage 3 stop (not a separate risk_manager.py -- that stop is already computed at entry time, reused directly instead of recomputing a second one)."
  },
  "data_storage": {
    "candidates_dir": "data/candidates",
    "positions_dir": "state",
    "trades_dir": "data/trades",
    "decisions_dir": "data/decisions",
    "state_dir": "state",
    "retention_days": 30
  },
  "logging": {
    "log_dir": "logs",
    "level": "INFO",
    "filename_prefix": "trade_bot",
    "retention_days": 30
  },
  "rules": {
    "entry_modules": [
      "breakout_rules",
      "reversal_rules"
    ],
    "exit_module": "exit_rules",
    "hot_reload": false,
    "_note": "monitor.py imports these files and asks them for every trade decision. entry_modules are asked in order each poll; the first that says BUY wins. hot_reload=true re-imports a rule file when it changes on disk (its per-symbol state is kept), so rules can be edited mid-day without a restart (a restart liquidates everything). If the exit module fails to load or raises for a position, the core falls back to selling at that position's entry stop -- a safety net only."
  },
  "breakout_rules": {
    "enabled": true
  },
  "reversal_rules": {
    "enabled": true
  },
  "exit_rules": {
    "enabled": true
  },
  "simulator": {
    "cache_dir": "/var/www/screener/trade/data/simulations/cache",
    "_note": "simulate.py: saved trade-by-trade data, one folder per day (shared with screener/trade -- 21 days, 8/27-9/25). Missing symbols are downloaded from Alpaca on first use."
  }
}
