"""Donchian-channel breakout — long on a new N-day high, short/flat on a new N-day low.

Rationale: channel breakout captures regime shifts via range expansion that a moving-average
threshold smooths over. The channel is read at t-1 (prior days only) so the breakout bar never
enters its own channel; the final shift makes the decision drive the next day's return — no lookahead.
"""
from __future__ import annotations

import numpy as np

from fxhnt.domain.models import PriceSeries
from fxhnt.domain.strategies.base import register, rolling_max, rolling_min


class Breakout:
    kind = "breakout"
    rationale = "channel breakout — captures regime shifts via range expansion a moving-average threshold smooths over"

    def positions(self, prices: PriceSeries, params: dict[str, float]) -> np.ndarray:
        window = int(params.get("window", 20))
        short_value = -1.0 if params.get("long_short", 0.0) else 0.0  # default long-only
        c = prices.close
        upper = rolling_max(prices.high, window)
        lower = rolling_min(prices.low, window)
        signal = np.zeros(len(c))
        for t in range(window, len(c)):
            if c[t] > upper[t - 1]:
                signal[t] = 1.0
            elif c[t] < lower[t - 1]:
                signal[t] = short_value
            else:
                signal[t] = signal[t - 1]   # hold inside the channel (Donchian stop-and-reverse/hold)
        pos = np.zeros(len(c))
        pos[1:] = signal[:-1]               # decision at t-1 drives return at t (no lookahead)
        return pos


register(Breakout())
