# source: https://raw.githubusercontent.com/siroosab/freqtrade-to-forex/8cee294cdf5a0e58a7338b61fa86fa4db1bdc4f8/freqtrade/forex/strategies/Crude_Oil_Scalp.py
# directory_url: https://github.com/siroosab/freqtrade-to-forex/blob/stable/freqtrade/forex/strategies/
# User: siroosab
# Repository: freqtrade-to-forex
# --------------------# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
# © nicks1008
"""Crude oil scalp strategy translated from the CRUDE OIL BUY/SELL Pine indicator."""

import math
from copy import deepcopy

import pandas as pd
from pandas import DataFrame, Series

from freqtrade.strategy import IntParameter, IStrategy


def _wilder_average(values: Series, period: int) -> Series:
    """Calculate Wilder's RMA with the SMA seed used by Pine Script."""
    result = [float("nan")] * len(values)
    seed: list[float] = []
    average: float | None = None

    for index, value in enumerate(values.to_numpy(dtype=float)):
        if not math.isfinite(value):
            seed.clear()
            average = None
            continue
        if average is None:
            seed.append(float(value))
            if len(seed) == period:
                average = sum(seed) / period
                result[index] = average
        else:
            average += (float(value) - average) / period
            result[index] = average

    return Series(result, index=values.index, dtype=float)


class Github_siroosab_freqtrade_to_forex__Crude_Oil_Scalp__20261011_155146(IStrategy):
    """Trade the indicator's BUY/SELL labels and close on its RSI reversal alerts."""

    INTERFACE_VERSION = 3
    can_short = True
    timeframe = "3m"
    startup_candle_count = 202
    process_only_new_candles = True
    use_exit_signal = True
    minimal_roi: dict[str, float] = {}
    stoploss = -0.99

    rsi_length = IntParameter(2, 100, default=14, space="buy")
    rsi_high = IntParameter(60, 95, default=80, space="sell")
    rsi_low = IntParameter(5, 40, default=20, space="sell")
    sma_length = IntParameter(5, 200, default=70, space="buy")
    hyperopt_search_bounds = {
        "rsi_length": (2, 200),
        "rsi_high": (51, 99),
        "rsi_low": (1, 49),
        "sma_length": (3, 500),
    }

    def __init__(self, config: dict | None = None) -> None:
        super().__init__(config or {})
        for parameter_name in ("rsi_length", "rsi_high", "rsi_low", "sma_length"):
            setattr(self, parameter_name, deepcopy(getattr(type(self), parameter_name)))

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        close = pd.to_numeric(dataframe["close"], errors="coerce")
        sma_length = int(self.sma_length.value)
        dataframe["sma"] = close.rolling(sma_length, min_periods=sma_length).mean()

        change = close.diff()
        average_gain = _wilder_average(change.clip(lower=0.0), int(self.rsi_length.value))
        average_loss = _wilder_average((-change.clip(upper=0.0)), int(self.rsi_length.value))
        relative_strength = average_gain / average_loss
        dataframe["rsi"] = 100.0 - (100.0 / (1.0 + relative_strength))
        dataframe.loc[(average_gain == 0.0) & (average_loss == 0.0), "rsi"] = 50.0
        dataframe.loc[(average_loss == 0.0) & (average_gain > 0.0), "rsi"] = 100.0

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        open_price = pd.to_numeric(dataframe["open"], errors="coerce")
        high = pd.to_numeric(dataframe["high"], errors="coerce")
        low = pd.to_numeric(dataframe["low"], errors="coerce")
        close = pd.to_numeric(dataframe["close"], errors="coerce")
        sma = pd.to_numeric(dataframe["sma"], errors="coerce")

        prior_close = close.shift(1)
        close_crossed_above_sma = (
            (close.shift(2) <= sma.shift(1))
            & (prior_close > sma)
        )
        prior_low = low.shift(1)
        low_crossed_below_sma = (
            (low.shift(2) >= sma.shift(1))
            & (prior_low < sma)
        )

        dataframe["enter_long"] = (
            close_crossed_above_sma
            & (open_price.shift(1) < prior_close)
            & (high > high.shift(1))
            & (close > open_price)
        ).fillna(False)
        dataframe["enter_short"] = (
            low_crossed_below_sma
            & (close.shift(1) < open_price.shift(1))
            & (low < prior_low)
            & (close < open_price)
        ).fillna(False)
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        rsi = pd.to_numeric(dataframe["rsi"], errors="coerce")
        open_price = pd.to_numeric(dataframe["open"], errors="coerce")
        high_level = int(self.rsi_high.value)
        low_level = int(self.rsi_low.value)

        dataframe["exit_long"] = (
            (rsi.shift(1) >= high_level) & (rsi < high_level)
        ).fillna(False)
        dataframe["exit_short"] = (
            (rsi.shift(1) <= low_level)
            & (rsi > low_level)
            & (open_price < pd.to_numeric(dataframe["close"], errors="coerce"))
        ).fillna(False)
        return dataframe
