# source: https://raw.githubusercontent.com/Yohalloo/freqtrade/9ac43149e8f5c35fd0deb19d8ef7270443639caa/user_data/strategies/RationalShortv2.py
# -*- coding: utf-8 -*-
"""
RationalShort - 三态理性做空策略（MathModel V2）

改动目标：
1. 用“期望 E = p * W - (1-p) * L”约束策略结构：
   - 胜率 p 可以在 40%~60% 波动；
   - 但要求平均盈利 W 至少 ~2%，平均亏损 L 控制在 ~2% 左右。
2. 尽量放大利润单、缩小亏损单：
   - 止损改为 ATR + 上限控制，避免 -4%~-5% 的大亏损；
   - exit_signal / 趋势行情允许利润拉大，再用动态移动止盈出场；
   - RegimeFlip 保护：Side/Bear 单被拉成 Bull 时尽快小亏/打平离场。
3. 保留“三态模型（Bull / Side / Bear）”的大框架结构，方便后续用 hyperopt 做参数搜索。

注意：
- 该版本适用于 freqtrade 2025.10，期货 / 合约环境（can_short = True）。
- 若与旧版本共存，请将文件名改为 `RationalShortV2.py`，类名改为 `RationalShortV2`，
  并在命令行用 `--strategy RationalShortV2` 运行。
"""

from datetime import datetime

import numpy as np
import talib.abstract as ta
from pandas import DataFrame

from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
from freqtrade.persistence import Trade


class Github_Yohalloo_freqtrade__RationalShortv2__20251122_100212(IStrategy):
    """理性做空策略 (Rational Short System) - MathModel V2"""

    # === 1. 策略基础配置 ===
    INTERFACE_VERSION = 3

    timeframe = "1h"
    can_short = True

    # 兜底硬止损（真正逻辑由 custom_stoploss / custom_exit 接管）
    stoploss = -0.15

    # ROI 完全交给 custom_exit，设置一个极大值
    minimal_roi = {"0": 100}

    # === 2. 参数区（可后续用 hyperopt 调） ===
    # ADX 阈值，区分震荡 / 趋势
    adx_threshold = IntParameter(20, 40, default=25, space="buy")

    # 不同状态下的 RSI 入场门槛
    rsi_bull = IntParameter(80, 90, default=84, space="buy")   # 牛市：极端超买
    rsi_side = IntParameter(60, 80, default=68, space="buy")   # 震荡：正常超买
    rsi_bear = IntParameter(45, 70, default=58, space="buy")   # 熊市：反弹就空

    # 布林带倍数
    bb_std = DecimalParameter(1.8, 2.6, default=2.0, space="buy")

    # 超时时间（小时）
    timeout_hours = IntParameter(6, 24, default=10, space="sell")

    # ATR 止损倍数（配合上限使用）
    atr_stop_mult = DecimalParameter(2.0, 4.0, default=3.0, space="sell")

    # === 3. 保护机制 ===
    @property
    def protections(self):
        return [
            {"method": "CooldownPeriod", "stop_duration_candles": 1},
            {
                "method": "StoplossGuard",
                "lookback_period_candles": 24,
                "trade_limit": 5,
                "stop_duration_candles": 6,
                "only_per_pair": False,
            },
        ]

    # === 4. 指标计算 ===
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # A. 布林带
        bollinger = ta.BBANDS(
            dataframe,
            timeperiod=20,
            nbdevup=float(self.bb_std.value),
            nbdevdn=float(self.bb_std.value),
        )
        dataframe["bb_upper"] = bollinger["upperband"]
        dataframe["bb_middle"] = bollinger["middleband"]
        dataframe["bb_lower"] = bollinger["lowerband"]

        # B. RSI & ADX
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
        dataframe["adx"] = ta.ADX(dataframe)

        # C. EMA200 作为大级别趋势线
        dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200)

        # D. ATR：用于动态止损
        dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)

        # E. 短期加速指标（3 根 K 的涨幅）
        dataframe["roc_3"] = ta.ROC(dataframe, timeperiod=3)

        # F. 标记市场状态 (1=Bull, 2=Side, 3=Bear)
        thr = int(self.adx_threshold.value)
        dataframe["market_state"] = 2  # 默认震荡

        dataframe.loc[
            (dataframe["close"] > dataframe["ema_200"]) & (dataframe["adx"] > thr),
            "market_state",
        ] = 1

        dataframe.loc[
            (dataframe["close"] < dataframe["ema_200"]) & (dataframe["adx"] > thr),
            "market_state",
        ] = 3

        # G. 预备列
        if "enter_tag" not in dataframe.columns:
            dataframe["enter_tag"] = ""
        if "exit_short" not in dataframe.columns:
            dataframe["exit_short"] = 0

        return dataframe

    # === 5. 开仓逻辑 (分状态路由) ===
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # 只做“突破上轨的首次穿越”：上一根在下，这一根收盘在上
        cross_upper = (
            (dataframe["close"] > dataframe["bb_upper"]) &
            (dataframe["close"].shift(1) <= dataframe["bb_upper"].shift(1))
        )

        # 基础条件：突破上轨 + 有量
        base_cond = cross_upper & (dataframe["volume"] > 0)

        # === 牛市：极端超买 + 短期爆拉，逆势短线做空（scalping） ===
        cond_bull = (
            base_cond &
            (dataframe["market_state"] == 1) &
            (dataframe["rsi"] > int(self.rsi_bull.value)) &
            (dataframe["roc_3"] > 4)
        )

        # === 震荡：正常超买，但要求价格不在 EMA200 上方太远 ===
        cond_side = (
            base_cond &
            (dataframe["market_state"] == 2) &
            (dataframe["rsi"] > int(self.rsi_side.value)) &
            (dataframe["close"] <= dataframe["ema_200"] * 1.01)
        )

        # === 熊市：略微反弹就做空 ===
        cond_bear = (
            base_cond &
            (dataframe["market_state"] == 3) &
            (dataframe["rsi"] > int(self.rsi_bear.value))
        )

        dataframe.loc[cond_bull, ["enter_short", "enter_tag"]] = (1, "bull")
        dataframe.loc[cond_side, ["enter_short", "enter_tag"]] = (1, "side")
        dataframe.loc[cond_bear, ["enter_short", "enter_tag"]] = (1, "bear")

        return dataframe

    # === 6. 平仓信号（用于配合 custom_exit） ===
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """基础 exit_short 信号：跌回布林中轨以下。"""
        if "exit_short" not in dataframe.columns:
            dataframe["exit_short"] = 0

        dataframe.loc[
            (dataframe["close"] < dataframe["bb_middle"]) & (dataframe["volume"] > 0),
            "exit_short",
        ] = 1

        return dataframe

    # === 工具函数：根据 ATR 计算当前状态下的最大允许亏损 ===
    def _calc_max_loss_pct(self, state: int, last_candle: DataFrame) -> float:
        atr = float(last_candle.get("atr", 0))
        close = float(last_candle.get("close", 0)) or 1.0
        atr_pct = atr / close if close > 0 else 0.01

        mult = float(self.atr_stop_mult.value)
        raw = atr_pct * mult

        # 不同状态给不同上限（单位：小数，0.02=2%）
        if state == 1:      # Bull：只允许小亏
            max_cap = 0.012
        elif state == 2:    # Side：中等亏损
            max_cap = 0.02
        else:               # Bear：略宽一点
            max_cap = 0.025

        # 同时保证不低于 0.006（避免极小 ATR 导致随便被打掉）
        return max(0.006, min(raw, max_cap))

    # === 7. 核心：三态风控引擎 ===
    def custom_exit(
        self,
        pair: str,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ):
        """根据市场状态决定不同的止盈 / 止损 / 超时逻辑。"""

        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        try:
            df = dataframe.loc[dataframe["date"] <= current_time]
            last_candle = df.iloc[-1] if not df.empty else dataframe.iloc[-1]
        except Exception:
            last_candle = dataframe.iloc[-1]

        state = int(last_candle.get("market_state", 2))  # 默认震荡
        exit_signal = int(last_candle.get("exit_short", 0))

        trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600
        base_timeout = int(self.timeout_hours.value)

        # 不同状态下最大持仓时间
        if state == 1:      # 牛市：短线
            max_hours = max(3, int(base_timeout * 0.6))
        elif state == 3:    # 熊市：趋势单可多拿一会儿
            max_hours = int(base_timeout * 1.5)
        else:               # 震荡：使用基础超时时间
            max_hours = base_timeout

        # 动态最大允许亏损（基于 ATR + 上限）
        max_loss_pct = self._calc_max_loss_pct(state, last_candle)

        enter_tag = getattr(trade, "enter_tag", "") or ""

        # --------------------------------------
        # 0. Regime Flip 保护：Side/Bear 单被拉成 Bull
        # --------------------------------------
        if state == 1 and enter_tag not in ("bull", ""):
            # 状态切换到 Bull，且这单不是在 Bull 下开的
            # 小亏 / 打平就走，避免被拖成大亏
            if current_profit > -0.003:  # -0.3% 以内
                return "regime_flip"
            # 更深的亏损交给后面的止损逻辑处理

        # --------------------------------------
        # 1. 牛市：短线 scalping
        # --------------------------------------
        if state == 1:
            # 小止损：基于 ATR 的动态值
            if current_profit <= -max_loss_pct:
                return "bull_stop_loss"

            # 小止盈：目标 1.5% 左右
            if current_profit >= 0.015:
                return "bull_scalp_profit"

            # 如果已经有利润，并且触发布林中轨 exit 信号，则落袋
            if exit_signal == 1 and current_profit > 0.005:
                return "exit_signal"

        # --------------------------------------
        # 2. 震荡：布林带回归
        # --------------------------------------
        elif state == 2:
            # 止损：动态 + 上限
            if current_profit <= -max_loss_pct:
                return "stop_loss"

            # exit_signal 触发且 >0.5% 就离场，保证盈利单不是太小
            if exit_signal == 1 and current_profit > 0.005:
                return "exit_signal"

            # 兜底止盈：> 3.5% 直接平仓
            if current_profit >= 0.035:
                return "side_trend_profit"

        # --------------------------------------
        # 3. 熊市：趋势行情
        # --------------------------------------
        elif state == 3:
            # 止损：动态 + 上限
            if current_profit <= -max_loss_pct:
                return "bear_stop_loss"

            # 如果已经吃到比较大的趋势 (>3%)，且出现 exit_signal，则落袋为安
            if exit_signal == 1 and current_profit > 0.025:
                return "exit_signal"

        # --------------------------------------
        # 4. 超时强制平仓
        # --------------------------------------
        if trade_duration > max_hours:
            tag = f"timeout_exit_({int(max_hours)}h)"
            # 有利润就直接平仓
            if current_profit > 0:
                return tag
            # 轻微亏损也不要再拖（>-0.5%）
            if current_profit > -0.005:
                return tag

        # 不触发任何硬退出
        return None

    # === 8. 动态移动止盈 ===
    def custom_stoploss(
        self,
        pair: str,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ) -> float:
        """根据浮盈动态上调止损价位，保护利润。

        返回值是相对于开仓价的最大允许亏损（负数）。
        这里采用分段式移动止盈：
        - 盈利 > 2%   -> 止损抬到 +0.5%
        - 盈利 > 4%   -> 锁定 2%
        - 盈利 > 7%   -> 锁定 3%
        - 盈利 > 10%  -> 锁定 5%
        其它情况返回 1，让 custom_exit / 兜底 stoploss 去处理。
        """

        # 阶段 1：赚 2% -> 锁定 0.5%
        if current_profit > 0.02:
            return 0.005

        # 阶段 2：赚 4% -> 锁定 2%
        if current_profit > 0.04:
            return 0.02

        # 阶段 3：赚 7% -> 锁定 3%
        if current_profit > 0.07:
            return 0.03

        # 阶段 4：赚 10% -> 锁定 5%
        if current_profit > 0.10:
            return 0.05

        # 其它情况交给 custom_exit / 基础 stoploss
        return 1
