# source: https://raw.githubusercontent.com/vaskosmihaylov/nfi-custom-strategies/6b37792ca9c552129d6c8f0c0f4d278737262e1b/user_data/strategies/Frankie/FrankenStrat_Shorts.py
"""
Github_vaskosmihaylov_nfi_custom_strategies__FrankenStrat_Shorts__20260211_192706 Strategy

A shorts-only variant of FrankenStrat, designed to profit in bear markets
and during overbought conditions.

Strategy Concept:
Multi-signal short entry strategy combining SMAOffset, TEMA, ClucMay, MACD,
and SSL channel signals - all inverted from the long version.

Entry Conditions (7 signals):
1. SMAOffset Short: Price above EMA with moderate EWO
2. TEMA Short: Price above TEMA with moderate EWO
3. ClucMay Bear Guard: Downtrend with price above upper BB
4. ClucMay No Guard: Deep above upper BB with extreme overbought RSI
5. MACD Bear Guard: Downtrend with bullish MACD crossover above upper BB
6. MACD No Guard: Bullish MACD crossover above upper BB
7. SSL Short: Price above SMA5 with bearish SSL and RSI divergence

Exit Conditions:
- Signal: Price below BB middleband or below EMA
- ROI: Same as longs (2.9% tiered)
- Custom Stoploss: 4-hour timeout + emergency -20% backstop

Key Differences from Long Strategy:
- INTERFACE_VERSION 3 with enter_short/exit_short
- 3x leverage via leverage() callback
- Max 4 short positions via confirm_trade_entry()
- Emergency backstop at -20%

Author: Derived from FrankenStrat
Version: 1.0.0
"""

import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy as np
import talib.abstract as ta
from freqtrade.persistence import Trade
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
from datetime import datetime, timedelta
from freqtrade.strategy import merge_informative_pair, DecimalParameter, IntParameter

from functools import reduce


def SSLChannels(dataframe, length=7):
  df = dataframe.copy()
  df["ATR"] = ta.ATR(df, timeperiod=14)
  df["smaHigh"] = df["high"].rolling(length).mean() + df["ATR"]
  df["smaLow"] = df["low"].rolling(length).mean() - df["ATR"]
  df["hlv"] = np.where(df["close"] > df["smaHigh"], 1, np.where(df["close"] < df["smaLow"], -1, np.nan))
  df["hlv"] = df["hlv"].ffill()
  df["sslDown"] = np.where(df["hlv"] < 0, df["smaHigh"], df["smaLow"])
  df["sslUp"] = np.where(df["hlv"] < 0, df["smaLow"], df["smaHigh"])
  return df["sslDown"], df["sslUp"]


def ewo(dataframe, ema_length=5, ema2_length=35):
  df = dataframe.copy()
  ema1 = ta.EMA(df, timeperiod=ema_length)
  ema2 = ta.EMA(df, timeperiod=ema2_length)
  emadif = (ema1 - ema2) / df["close"] * 100
  return emadif


class Github_vaskosmihaylov_nfi_custom_strategies__FrankenStrat_Shorts__20260211_192706(IStrategy):
  INTERFACE_VERSION = 3
  can_short = True

  minimal_roi = {
    "0": 0.029,
    "10": 0.021,
    "30": 0.01,
    "40": 0.005,
  }

  # SMAOffset - same values as longs, logic inverted in populate_entry_trend
  base_nb_candles_buy = IntParameter(5, 80, default=12, space="buy", optimize=True)
  base_nb_candles_sell = IntParameter(5, 80, default=31, space="sell", optimize=True)
  low_offset = DecimalParameter(0.9, 0.99, default=0.975, space="buy", optimize=True)
  high_offset = DecimalParameter(0.95, 1.1, default=1.0, space="sell", optimize=True)

  # TEMA
  tema_low_offset = DecimalParameter(0.9, 0.99, default=0.95, space="buy", optimize=True)

  # Protection
  fast_ewo = 50
  slow_ewo = 200

  stoploss = -0.99  # effectively disabled, managed by custom_stoploss

  timeframe = "5m"
  inf_1h = "1h"

  # Exit signal
  use_exit_signal = True
  exit_profit_only = False
  exit_profit_offset = 0.001
  ignore_roi_if_entry_signal = False

  # Trailing stoploss
  trailing_stop = False
  trailing_only_offset_is_reached = False
  trailing_stop_positive = 0.01
  trailing_stop_positive_offset = 0.025

  # Custom stoploss
  use_custom_stoploss = True

  # Run "populate_indicators()" only for new candle.
  process_only_new_candles = False

  # Number of candles the strategy requires before producing valid signals
  startup_candle_count: int = 200

  # Max short positions
  max_short_trades = 4

  # Optional order type mapping.
  order_types = {
    "entry": "limit",
    "exit": "limit",
    "stoploss": "market",
    "stoploss_on_exchange": False,
  }

  def leverage(self, pair: str, current_time: datetime, current_rate: float,
               proposed_leverage: float, max_leverage: float, entry_tag: str,
               side: str, **kwargs) -> float:
    return 3.0

  def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
                          time_in_force: str, current_time: datetime, entry_tag: str,
                          side: str, **kwargs) -> bool:
    # Block all long entries
    if side == "long":
      return False

    # Count open shorts
    short_count = sum(1 for trade in Trade.get_trades_proxy(is_open=True) if trade.is_short)

    # Enforce max shorts limit
    if short_count >= self.max_short_trades:
      return False

    return True

  def custom_stoploss(self, pair: str, trade: "Trade", current_time: datetime,
                      current_rate: float, current_profit: float, **kwargs) -> float:
    # Emergency backstop - prevent liquidation at 3x leverage
    if current_profit <= -0.20:
      return -0.21

    # Manage losing trades and open room for better ones.
    if (current_profit < 0) & (current_time - timedelta(minutes=240) > trade.open_date_utc):
      return 0.01
    return 0.99

  def informative_pairs(self):
    pairs = self.dp.current_whitelist()
    informative_pairs = [(pair, "1h") for pair in pairs]
    return informative_pairs

  def informative_1h_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    assert self.dp, "DataProvider is required for multiple timeframes."
    # Get the informative pair
    informative_1h = self.dp.get_pair_dataframe(pair=metadata["pair"], timeframe=self.inf_1h)
    # EMA
    informative_1h["ema_50"] = ta.EMA(informative_1h, timeperiod=50)
    informative_1h["ema_200"] = ta.EMA(informative_1h, timeperiod=200)
    # RSI
    informative_1h["rsi"] = ta.RSI(informative_1h, timeperiod=14)

    # SSL Channels
    ssl_down_1h, ssl_up_1h = SSLChannels(informative_1h, 20)
    informative_1h["ssl_down"] = ssl_down_1h
    informative_1h["ssl_up"] = ssl_up_1h

    return informative_1h

  def normal_tf_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    for length in set(list(self.base_nb_candles_buy.range) + list(self.base_nb_candles_sell.range)):
      dataframe[f"ema_{length}"] = ta.EMA(dataframe, timeperiod=length)

    dataframe["ewo"] = ewo(dataframe, self.fast_ewo, self.slow_ewo)

    # RSI
    dataframe["rsi_fast"] = ta.RSI(dataframe, timeperiod=4)
    dataframe["rsi_slow"] = ta.RSI(dataframe, timeperiod=20)
    dataframe["hma_50"] = qtpylib.hull_moving_average(dataframe["close"], window=50)

    # TEMA
    dataframe["tema"] = ta.TEMA(dataframe, length=14)

    # strategy ClucMay72018
    bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
    dataframe["bb_lowerband"] = bollinger["lower"]
    dataframe["bb_middleband"] = bollinger["mid"]
    dataframe["bb_upperband"] = bollinger["upper"]
    dataframe["ema_slow"] = ta.EMA(dataframe, timeperiod=50)
    dataframe["volume_mean_slow"] = dataframe["volume"].rolling(window=30).mean()

    # EMA
    dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200)

    dataframe["ema_26"] = ta.EMA(dataframe, timeperiod=26)
    dataframe["ema_12"] = ta.EMA(dataframe, timeperiod=12)

    # SMA
    dataframe["sma_5"] = ta.EMA(dataframe, timeperiod=5)

    # RSI
    dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)

    return dataframe

  def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    # The indicators for the 1h informative timeframe
    informative_1h = self.informative_1h_indicators(dataframe, metadata)
    dataframe = merge_informative_pair(dataframe, informative_1h, self.timeframe, self.inf_1h, ffill=True)

    # The indicators for the normal (5m) timeframe
    dataframe = self.normal_tf_indicators(dataframe, metadata)

    return dataframe

  def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    dataframe.loc[
      (
        # Signal 1: SMAOffset Short - price above EMA (overbought)
        (dataframe["close"] > (dataframe[f"ema_{self.base_nb_candles_buy.value}"] * (2 - self.low_offset.value)))
        & (dataframe["ewo"] < 1)
        & (dataframe["volume"] > 0)
      )
      | (
        # Signal 2: TEMA Short - price above TEMA (overbought)
        (dataframe["close"] > (dataframe["tema"] * (2 - self.tema_low_offset.value)))
        & (dataframe["ewo"] < 1)
        & (dataframe["volume"] > 0)
      )
      | (
        # Signal 3: ClucMay Short with bear guard (inverted bull guard)
        (dataframe["close"] < dataframe["ema_200"])
        & (dataframe["close"] < dataframe["ema_200_1h"])
        & (dataframe["close"] > dataframe["ema_slow"])
        & (dataframe["close"] > 1.01 * dataframe["bb_upperband"])
        & (dataframe["volume_mean_slow"] > dataframe["volume_mean_slow"].shift(30) * 0.4)
        & (dataframe["volume"] > 0)
      )
      | (
        # Signal 4: ClucMay Short without guard (inverted bear no guard)
        (dataframe["close"] > dataframe["ema_slow"])
        & (dataframe["close"] > 1.025 * dataframe["bb_upperband"])
        & (dataframe["volume"] < (dataframe["volume"].shift() * 4))
        & (dataframe["rsi_1h"] > 85)
        & (dataframe["volume_mean_slow"] > dataframe["volume_mean_slow"].shift(30) * 0.4)
        & (dataframe["volume"] > 0)
      )
      | (
        # Signal 5: MACD Short with bear guard (inverted MACD bull guard)
        (dataframe["close"] < dataframe["ema_200"])
        & (dataframe["close"] < dataframe["ema_200_1h"])
        & (dataframe["ema_12"] > dataframe["ema_26"])
        & ((dataframe["ema_12"] - dataframe["ema_26"]) > (dataframe["open"] * 0.02))
        & ((dataframe["ema_12"].shift() - dataframe["ema_26"].shift()) > (dataframe["open"] / 100))
        & (dataframe["volume"] < (dataframe["volume"].shift() * 4))
        & (dataframe["close"] > dataframe["bb_upperband"])
        & (dataframe["volume_mean_slow"] > dataframe["volume_mean_slow"].shift(30) * 0.4)
        & (dataframe["volume"] > 0)
      )
      | (
        # Signal 6: MACD Short without guard (inverted MACD bear no guard)
        (dataframe["ema_12"] > dataframe["ema_26"])
        & ((dataframe["ema_12"] - dataframe["ema_26"]) > (dataframe["open"] * 0.03))
        & ((dataframe["ema_12"].shift() - dataframe["ema_26"].shift()) > (dataframe["open"] / 100))
        & (dataframe["volume"] < (dataframe["volume"].shift() * 4))
        & (dataframe["close"] > dataframe["bb_upperband"])
        & (dataframe["volume"] > 0)
      )
      | (
        # Signal 7: SSL Short - price above SMA5 with bearish SSL
        (dataframe["close"] > dataframe["sma_5"])
        & (dataframe["ssl_down_1h"] > dataframe["ssl_up_1h"])
        & (dataframe["ema_slow"] < dataframe["ema_200"])
        & (dataframe["ema_50_1h"] < dataframe["ema_200_1h"])
        & (dataframe["rsi"] > dataframe["rsi_1h"] + 43.276)
        & (dataframe["volume"] > 0)
      ),
      "enter_short",
    ] = 1

    return dataframe

  def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    conditions = []

    conditions.append(
      (
        # Price below BB middleband (inverted from above)
        (dataframe["close"] < dataframe["bb_middleband"] * 0.99)
        & (dataframe["volume"] > 0)
      )
    )

    conditions.append(
      (
        (dataframe["close"] < (dataframe[f"ema_{self.base_nb_candles_sell.value}"] * (2 - self.high_offset.value)))
        & (dataframe["ewo"] > -20)
        & (dataframe["volume"] > 0)
      )
    )
    if conditions:
      dataframe.loc[
        reduce(lambda x, y: x | y, conditions),
        "exit_short",
      ] = 1

    return dataframe
