#!/usr/bin/env python3
"""grid_osc_signal.py �?Grid oscillator for range-bound markets.

Strategy: In confirmed RANGING markets (ADX<22, low ATR), place grid
of limit orders at support/resistance levels. Designed for ASIA session
and low-volatility periods.
"""
from __future__ import annotations

# ── Adaptive price rounding (2-digit vs 3-digit brokers) ──────────
try:
    from src.price_digits import round_price as _round_price
except ImportError:
    def _round_price(price, **_kwargs):
        return round(float(price), 2)


import time, uuid
from typing import Any
from signal_generators.base import BaseSignalGenerator


class GridOscSignal(BaseSignalGenerator):
    """Grid oscillator �?buys at support, sells at resistance."""

    SCHEMA_VERSION = "2.1.0"

    DEFAULT_PARAMS: dict[str, Any] = {
        "range_lookback": 30,
        "range_min_atr": 0.8,    # minimum range in ATR units
        "range_max_atr": 3.0,    # maximum range in ATR units
        "adx_max": 22,            # only active below this ADX
        "atr_period": 14,
        "grid_levels": 3,         # levels on each side
        "sl_atr_mult": 1.8,
        "tp_atr_mult": 3.0,
        "min_confidence": 0.50,
        "min_bars": 60,
    }

    @staticmethod
    def _atr(bars: list[dict], period: int = 14) -> float:
        n = len(bars)
        if n < period + 1: return 0.0
        trs = [max(bars[i]["high"] - bars[i]["low"], abs(bars[i]["high"] - bars[i-1]["close"]), abs(bars[i]["low"] - bars[i-1]["close"])) for i in range(1, n)]
        return sum(trs[-period:]) / period

    def analyze(self, bars: list[dict], params: dict | None = None) -> dict[str, Any]:
        p = {**self.DEFAULT_PARAMS, **(params or {})}
        tid = f"grid_osc_{uuid.uuid4().hex[:12]}"
        if len(bars) < p["min_bars"]: return self._hold(tid, "insufficient_bars", {})

        closes = [float(b["close"]) for b in bars]
        highs  = [float(b["high"]) for b in bars]
        lows   = [float(b["low"]) for b in bars]
        cp = closes[-1]
        rlb = p["range_lookback"]

        range_high = max(highs[-rlb:])
        range_low  = min(lows[-rlb:])
        atr_v = self._atr(bars, int(p["atr_period"]))
        if atr_v <= 0: return self._hold(tid, "no_atr", {})

        range_atr = (range_high - range_low) / atr_v
        if range_atr < p["range_min_atr"] or range_atr > p["range_max_atr"]:
            return self._hold(tid, f"range_out_of_bounds({range_atr:.1f}ATR)", {"range_atr": round(range_atr, 1)})

        # Grid levels
        grid_size = (range_high - range_low) / (p["grid_levels"] + 1)
        support_levels = [range_low + grid_size * (i + 1) for i in range(p["grid_levels"])]
        resistance_levels = [range_high - grid_size * (i + 1) for i in range(p["grid_levels"])]

        # Entry: price near support �?BUY; near resistance �?SELL
        action, side = "HOLD", "NONE"
        reason = ""
        for i, lvl in enumerate(support_levels):
            if cp <= lvl * 1.001:  # within 0.1%
                action, side = "BUY", "BUY"
                reason = f"Grid support L{i+1}={lvl:.1f}"
                break
        if action == "HOLD":
            for i, lvl in enumerate(resistance_levels):
                if cp >= lvl * 0.999:
                    action, side = "SELL", "SELL"
                    reason = f"Grid resistance R{i+1}={lvl:.1f}"
                    break

        if action == "HOLD": return self._hold(tid, "no_grid_touch", {"range_high": range_high, "range_low": range_low})

        confidence = p["min_confidence"]
        entry = cp
        sl = _round_price(entry - atr_v * p["sl_atr_mult"]) if side == "BUY" else _round_price(entry + atr_v * p["sl_atr_mult"])
        tp = _round_price(entry + atr_v * p["tp_atr_mult"]) if side == "BUY" else _round_price(entry - atr_v * p["tp_atr_mult"])

        return {"schema_version": self.SCHEMA_VERSION, "trace_id": tid, "strategy_id": self.get_strategy_id(),
                "model_version": None, "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S+08:00"),
                "payload": {"action": action, "side": side, "entry": round(entry, 3), "sl": sl, "tp": tp,
                            "confidence": round(confidence, 3), "reason": reason,
                            "indicators": {"range_high": range_high, "range_low": range_low, "range_atr": round(range_atr, 1),
                                           "atr": round(atr_v, 3)}}}

    def _hold(self, tid: str, r: str, ind: dict) -> dict:
        return {"schema_version": self.SCHEMA_VERSION, "trace_id": tid, "strategy_id": self.get_strategy_id(),
                "model_version": None, "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S+08:00"),
                "payload": {"action": "HOLD", "confidence": 0.0, "reason": r, "indicators": ind}}

    def get_strategy_id(self) -> str: return "grid_osc"
    def get_params_schema(self) -> dict:
        return {"range_lookback": {"type": "int", "default": 30, "range": [15, 60]},
                "range_min_atr": {"type": "float", "default": 0.8, "range": [0.5, 2.0]},
                "range_max_atr": {"type": "float", "default": 3.0, "range": [2.0, 5.0]},
                "grid_levels": {"type": "int", "default": 3, "range": [2, 5]},
                "sl_atr_mult": {"type": "float", "default": 1.0, "range": [0.5, 2.0]},
                "tp_atr_mult": {"type": "float", "default": 1.5, "range": [1.0, 3.0]}}
