# source: https://raw.githubusercontent.com/Meiyerlove/lhjy/c2933ef7b5dac1bf9f3150b8f312c5164ec6232d/freqtrade_bot/user_data/strategies/GridStrategy.py
import talib.abstract as ta
from pandas import DataFrame
from freqtrade.strategy import IStrategy, DecimalParameter, IntParameter
from freqtrade.persistence import Trade
import logging

logger = logging.getLogger(__name__)


class Github_Meiyerlove_lhjy__GridStrategy__20260429_064223(IStrategy):
    """
    专业自适应网格策略

    核心改进（相比交易所内置网格）：
      1. ATR 动态间距：波动大 → 格子自动拉宽，避免过早加仓；波动小 → 格子收窄，提高资金效率
      2. ADX 趋势过滤：ADX > 阈值说明是趋势市，不开新仓；只在震荡市运行
      3. 递增仓位：第1次加仓1倍，第2次1.5倍，第3次2倍 → 越跌买越多，更快摊低均价
      4. ATR 动态止盈：回升 N×ATR 就出场，不用死板的固定百分比
      5. EMA200 趋势方向：只在大方向向上时做多，避免逆势抄底
    """

    INTERFACE_VERSION = 3
    timeframe = "1h"
    can_short = False

    position_adjustment_enable = True
    max_entry_position_adjustment = 4  # 最多加仓4次（共5档）

    # ROI 设宽，主要由 custom_exit 控制精确出场
    minimal_roi = {
        "0": 0.10,
        "2880": 0.02,  # 持仓 120 天还没涨，降到 2% 接受出场
        "5760": 0.005,
    }

    # 止损设宽：覆盖5档×ATR的加仓空间
    stoploss = -0.25
    trailing_stop = False
    startup_candle_count = 50

    # ── Hyperopt 参数 ────────────────────────────────────────────────
    # 网格间距倍数（间距 = atr_mult × ATR）
    atr_mult = DecimalParameter(0.5, 2.5, default=1.2, decimals=1, space="buy")

    # ADX 阈值：低于此值认为是震荡市，才允许入场
    adx_max = IntParameter(15, 40, default=28, space="buy")

    # 布林带宽度上限：太宽说明正在剧烈波动，不入场
    bb_width_max = DecimalParameter(0.04, 0.20, default=0.10, decimals=2, space="buy")

    # 止盈触发：均价回升 tp_atr_mult × ATR 就出场
    tp_atr_mult = DecimalParameter(0.5, 2.0, default=1.0, decimals=1, space="sell")

    # ATR 缓存：populate_indicators 中更新，custom_exit/adjust_entry 中读取
    _atr_cache: dict = {}

    # ── 指标计算 ─────────────────────────────────────────────────────
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["atr"]    = ta.ATR(dataframe, timeperiod=14)
        dataframe["adx"]    = ta.ADX(dataframe, timeperiod=14)
        dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)
        dataframe["rsi"]    = ta.RSI(dataframe, timeperiod=14)

        upper, mid, lower = ta.BBANDS(dataframe["close"], timeperiod=20,
                                       nbdevup=2.0, nbdevdn=2.0)
        dataframe["bb_upper"] = upper
        dataframe["bb_mid"]   = mid
        dataframe["bb_lower"] = lower
        # BB宽度归一化（(上轨-下轨)/中轨），代表相对波动幅度
        dataframe["bb_width"] = (upper - lower) / mid

        # 缓存最新 ATR，供 custom_exit 和 adjust_trade_entry 使用
        if len(dataframe) > 0:
            self._atr_cache[metadata["pair"]] = float(dataframe["atr"].iloc[-1])

        return dataframe

    # ── 入场信号 ─────────────────────────────────────────────────────
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                # 大方向：价格在 EMA200 之上（长期上升趋势）
                (dataframe["close"] > dataframe["ema200"]) &
                # 趋势强度低：ADX 小 → 震荡市，适合网格
                (dataframe["adx"] < self.adx_max.value) &
                # 波动幅度不过大：BB 宽度在合理范围内
                (dataframe["bb_width"] < self.bb_width_max.value) &
                # 入场位置：价格不高于 BB 中轨太多（不在高位追入）
                (dataframe["close"] <= dataframe["bb_mid"] * 1.01) &
                # 成交量有效
                (dataframe["volume"] > 0)
            ),
            "enter_long",
        ] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # 不设退出信号，全部由 custom_exit + minimal_roi 控制
        return dataframe

    # ── 动态止盈 ─────────────────────────────────────────────────────
    FEE_RATE = 0.001  # OKX 现货挂单费率 0.10%

    def custom_exit(self, pair, trade, current_time, current_rate,
                    current_profit, **kwargs):
        """
        均价回升 tp_atr_mult × ATR 时止盈出场。
        最小止盈随 DCA 档数动态提高，确保每笔交易扣完手续费后都真正盈利。
        """
        atr = self._atr_cache.get(pair)
        if atr is None or trade.open_rate == 0:
            return None

        filled = trade.nr_of_successful_entries
        # DCA 每加一档多一笔入场手续费；最小止盈 = 累计入场费 + 0.2% 净利缓冲
        fee_buffer = self.FEE_RATE * filled + 0.002
        atr_target  = (self.tp_atr_mult.value * atr) / trade.open_rate
        profit_target = max(atr_target, fee_buffer)

        if current_profit >= profit_target:
            filled = trade.nr_of_successful_entries
            return f"grid_tp_lvl{filled}_{current_profit:.3f}"

        return None

    # ── 网格加仓逻辑 ─────────────────────────────────────────────────
    def adjust_trade_entry(
        self,
        trade: Trade,
        current_time,
        current_rate: float,
        current_profit: float,
        min_stake,
        max_stake: float,
        current_entry_rate: float,
        current_exit_rate: float,
        current_entry_profit: float,
        current_exit_profit: float,
        **kwargs,
    ):
        """
        自适应网格加仓：
        - 间距 = atr_mult × ATR（随市场波动自动调整）
        - 仓位递增：填入第N档时，加仓 = 初始仓 × (1 + (N-1)×0.5)
          档位  加仓倍数  资金比例
            1    1.0×    第1次同等金额
            2    1.5×    价格再跌一格
            3    2.0×    ...
            4    2.5×    第4次加仓
        """
        filled = trade.nr_of_successful_entries
        if filled >= (self.max_entry_position_adjustment + 1):
            return None

        # 动态间距：ATR占当前价的比例
        atr = self._atr_cache.get(trade.pair)
        if atr:
            grid_step = (self.atr_mult.value * atr) / current_rate
            grid_step = max(grid_step, 0.01)   # 最小1%
            grid_step = min(grid_step, 0.08)   # 最大8%（防极端行情）
        else:
            grid_step = 0.025  # 无ATR时降级到2.5%固定间距

        # 达到触发阈值才加仓
        threshold = -(grid_step * filled)
        if current_profit > threshold:
            return None

        # 递增仓位：第1档加1倍，第2档加1.5倍，依此类推
        multiplier = 1.0 + max(filled - 1, 0) * 0.5
        stake = trade.stake_amount * multiplier
        stake = max(stake, min_stake or 0)
        stake = min(stake, max_stake)

        logger.info(
            f"[Grid] {trade.pair} 第{filled}次加仓 | "
            f"间距={grid_step:.2%} 阈值={threshold:.2%} 当前={current_profit:.2%} | "
            f"加仓={stake:.2f} USDT ({multiplier}x)"
        )
        return stake
