"""Four upstream workarounds, applied identically to both runs.

None of them edits LLM-Auto-Trader; they are monkeypatches in the launcher, so
`git status` in the tool's checkout stays clean. They exist because the tool
as shipped (2026-07-21, ccxt 4.5.81, python 3.12) cannot place an order.

1. `logging.handlers` — `src/utils/logger.py` calls
   `logging.handlers.RotatingFileHandler` without importing the submodule.
   Crashes at startup unless something else imported it first.

2. `fetch_open_interest -> None` — ccxt returns `openInterestValue: None` for
   binance perpetuals; `src/data/market.py:225` passes it through with
   `.get("openInterestValue", 0)` (the key exists, so the default never
   applies) and `src/llm/context_builder.py:469` formats it with `:,.0f`.
   Every cycle then dies before the LLM is called.

3. order-book depth in USDT — `src/bot.py:690` sums `bid_depth + ask_depth`,
   which `src/data/market.py:168` computed in BASE units (BTC), and hands it
   to `RiskManager.validate_action(orderbook_depth_usdt=...)`. ~20 BTC of
   depth is read as "$20 of depth", the slippage-aware shrink concludes any
   order is enormous, and size_pct collapses to ~0.001% — below one lot, so
   the executor answers "Position size too small after rounding" forever.
   We multiply the depth by the top-of-book price, which is what the
   parameter name asks for.

Apply before importing `src.bot`.

`LAT_FAST_CYCLE=<seconds>` is not a workaround but an observation aid: the
scheduler's floor (`min_interval` 300s) and its idle interval
(`default_no_position` 1800s) are hardcoded dataclass defaults, so with no
position open the bot consults the model once every 30 minutes. Set it to
watch several loops in one sitting. It changes cadence only — nothing about
how an order is priced, sized or filled.
"""
import logging.handlers        # noqa: F401  (1)
import os


def tune(settings) -> None:
    """Two settings with no env knob. Both dataclasses are frozen and their
    defaults are baked into __init__, so patching the class does nothing —
    the loaded instance has to be written through object.__setattr__."""
    rr = float(os.environ.get("LAT_MIN_RR", "0"))
    if rr:
        # The shipped floor is 1.5 and the model aims at exactly 1.5, so it can
        # land a hair under and `rr < 1.5` (src/risk/stop_loss.py:94) rejects
        # the entry. Where the floor sits is the user's call.
        object.__setattr__(settings.risk, "min_reward_risk_ratio", rr)
    fast = float(os.environ.get("LAT_FAST_CYCLE", "0"))
    if fast:
        for k in ("min_interval", "base_interval", "default_no_position"):
            object.__setattr__(settings.llm, k, fast)
    return settings


def apply() -> None:
    from src.data.market import MarketDataCollector

    async def _no_oi(self, symbol):                       # (2)
        return None

    MarketDataCollector.fetch_open_interest = _no_oi

    _ob = MarketDataCollector.fetch_orderbook_summary

    async def _depth_in_usdt(self, symbol, depth=10):     # (3)
        s = await _ob(self, symbol, depth)
        if s is None:
            return None
        px = (s.top_bid + s.top_ask) / 2 or 1.0
        s.bid_depth *= px
        s.ask_depth *= px
        return s

    MarketDataCollector.fetch_orderbook_summary = _depth_in_usdt
