"""Standby 1h long-only engine for the volatility-breakout family.

Motivation (2026-07-12 long-side study, see docs/strategy-iterations.md):
long gross price edge at 5m is ~zero-to-positive but is ground away by fee
drag from high signal density.  The timeframe ablation showed 1h as the sweet
spot: 3-6x fewer trades cuts the fee tax proportionally and three of four
historical bull windows turn positive (+2.85% / +0.02% / +2.01%), while 4h
degrades the signal itself.

This variant changes exactly two things versus production: timeframe 1h and
long-only.  Every filter, exit and risk control stays identical so any future
forward result attributes cleanly.  It must NOT be deployed to bot1; the plan
is a separate dry-run bot once BTC reclaims its daily 200DMA (until then the
long gate keeps it idle by construction).

Funding ceiling for new longs (crowded-longs filter): live/dry-run only, like
the regime.json gate — backtests already charge historical funding, so the
filter is inert there by runmode check and the four bull-window validation
numbers stay valid.

Deployment note: config ``timeframe`` silently overrides the class attribute
(see the V3/V4 lesson) — the standby bot needs its own config with
``"timeframe": "1h"`` or an explicit ``--timeframe 1h``.
"""

import logging
import sys
from datetime import datetime
from pathlib import Path

VOLATILITY_STRATEGY_DIR = Path(__file__).resolve().parents[1] / "volatility_breakout"
if str(VOLATILITY_STRATEGY_DIR) not in sys.path:
    sys.path.insert(0, str(VOLATILITY_STRATEGY_DIR))

from v5_d48 import VolatilityBreakout  # noqa: E402

logger = logging.getLogger(__name__)


class LongBreakout1H(VolatilityBreakout):
    timeframe = "1h"
    can_short = False

    # 210 根 1h ≈ 9 天预热，覆盖 EMA200 与 Donchian48。
    startup_candle_count = 210

    # 单期（8h）资金费率上限。中性水平 ≈ 0.01%，2021 极端拥挤多头 ≈ 0.064%
    # （年化 70%+，归因一节的结构税来源）。0.05%/8h ≈ 年化 55%，只拦极端拥挤段。
    max_long_funding_rate = 0.0005

    def version(self) -> str:
        return "research-long-1h-standby"

    def confirm_trade_entry(
        self,
        pair: str,
        order_type: str,
        amount: float,
        rate: float,
        time_in_force: str,
        current_time: datetime,
        entry_tag: str | None,
        side: str,
        **kwargs,
    ) -> bool:
        if not super().confirm_trade_entry(
            pair=pair,
            order_type=order_type,
            amount=amount,
            rate=rate,
            time_in_force=time_in_force,
            current_time=current_time,
            entry_tag=entry_tag,
            side=side,
            **kwargs,
        ):
            return False

        # 回测已按历史 funding 计费，过滤器只在 live/dry-run 生效；
        # 取不到费率时与 regime 闸同哲学：fail-closed，宁可错过。
        if self.config["runmode"].value not in ("live", "dry_run"):
            return True
        try:
            funding = self.dp._exchange._api.fetch_funding_rate(pair)["fundingRate"]
        except Exception as e:
            logger.error("读取 %s 资金费率失败(%s)，fail-closed 拦下开多", pair, e)
            return False
        if funding is None:
            logger.warning("%s 资金费率为空，fail-closed 拦下开多", pair)
            return False
        if funding > self.max_long_funding_rate:
            logger.info(
                "%s 资金费率 %.4f%%/8h 超上限 %.4f%%，拦下开多（多头拥挤）",
                pair, funding * 100, self.max_long_funding_rate * 100,
            )
            return False
        return True
