# source: https://raw.githubusercontent.com/h5un/freqtrade-strategies/ff7e2afe6a186f30bfee36ae87428fe8e11a8346/user_data/strategies/grid_strategy.py
# user_data/strategies/grid_strategy.py
from freqtrade.strategy import IStrategy, DecimalParameter
from pandas import DataFrame


class Github_h5un_freqtrade_strategies__grid_strategy__20260112_111946(IStrategy):
    """
    以「上次成交價為中心」的網格策略
    - 跌超 x% -> 每次加倉 +0.1 NAV
    - 漲超 y% -> 每次減倉 -0.1 NAV (或加空/回補)
    - 倉位上限 ±0.9 NAV
    - 使用 position adjustment 逐步加減碼
    """

    INTERFACE_VERSION = 3
    can_short: bool = True
    position_adjustment_enable = True
    timeframe = "1h"
    process_only_new_candles = True
    startup_candle_count = 20

    minimal_roi = {"0": 1}
    stoploss = -1
    trailing_stop = False

    x = DecimalParameter(0.001, 0.02, default=0.005, decimals=4,
                         space="buy", optimize=True, load=True)
    y = DecimalParameter(0.001, 0.02, default=0.005, decimals=4,
                         space="sell", optimize=True, load=True)

    step: float = 0.10   # 每次調整 = 10% 的基準名義額
    max_pos: float = 0.90

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 單一變數：初始為 None，等 populate_indicators() 時再設
        self.last_trade_price: float | None = None

    def populate_indicators(self, df: DataFrame, metadata: dict) -> DataFrame:
        # ✅ 初始化：只在第一次還沒設定時，把第一根 K 的 close 當作 last_trade_price
        if self.last_trade_price is None and len(df) > 0:
            self.last_trade_price = float(df["close"].iloc[0])

        df["prev_close"] = df["close"].shift(1)
        df["chg"] = (df["close"] - df["prev_close"]) / df["prev_close"]
        return df

    def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        df["enter_long"] = 0
        df["enter_short"] = 0
        # 跌超 x% → 開多（第一筆由這裡觸發）
        df.loc[(df["chg"] <= -self.x.value) & (df["volume"] > 0), "enter_long"] = 1
        # 漲超 y% → 開空
        df.loc[(df["chg"] >=  self.y.value) & (df["volume"] > 0), "enter_short"] = 1
        return df

    def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        df["exit_long"] = 0
        df["exit_short"] = 0
        return df

    def adjust_trade_position(self, trade, current_time, current_rate, current_profit, **kwargs):
        # 1) 確保每筆交易開始時，有中心
        if self.last_trade_price is None:
            self.last_trade_price = current_rate  # 或 trade.open_rate

        last_trade_price = self.last_trade_price
        change_pct = (current_rate - last_trade_price) / last_trade_price

        # 名義額基準（建議用名義額，不用保證金）
        entry_price = trade.open_rate or current_rate
        base_notional = max(abs(trade.amount) * entry_price, 1e-9)

        current_amount = abs(getattr(trade, "amount", 0.0))
        current_notional = current_amount * current_rate
        pos_ratio = current_notional / base_notional

        one_step_notional = self.step * base_notional
        one_step_qty = one_step_notional / current_rate

        eps = 1e-9
        close_tolerance = 1.05
        band = 0.003  # 放寬一點 0.3%

        delta_qty = 0.0

        if not trade.is_short:
            if change_pct <= -self.x.value:
                if pos_ratio > self.max_pos - self.step + eps:
                    delta_qty = -current_amount                # 平倉
                    self.last_trade_price = None               # 2) 平倉後清空
                    return float(delta_qty)
                delta_qty = +abs(one_step_qty)
                self.last_trade_price = current_rate          # 3) 加倉/減倉後更新中心

            elif change_pct >= self.y.value and current_amount > 0:
                if current_notional <= one_step_notional * close_tolerance:
                    delta_qty = -current_amount                # 平倉
                    self.last_trade_price = None
                    return float(delta_qty)
                delta_qty = -abs(one_step_qty)
                self.last_trade_price = current_rate

            # 回中心就平（先拿掉 need profit）
            if abs(change_pct) < band and current_amount > 0:
                delta_qty = -current_amount
                self.last_trade_price = None
                return float(delta_qty)

        else:
            if change_pct >= self.y.value:
                if pos_ratio > self.max_pos - self.step + eps:
                    delta_qty = +current_amount                # 平空
                    self.last_trade_price = None
                    return float(delta_qty)
                delta_qty = -abs(one_step_qty)
                self.last_trade_price = current_rate

            elif change_pct <= -self.x.value and current_amount > 0:
                if current_notional <= one_step_notional * close_tolerance:
                    delta_qty = +current_amount                # 平空
                    self.last_trade_price = None
                    return float(delta_qty)
                delta_qty = +abs(one_step_qty)
                self.last_trade_price = current_rate

            if abs(change_pct) < band and current_amount > 0:
                delta_qty = +current_amount
                self.last_trade_price = None
                return float(delta_qty)

        return float(delta_qty)

    def on_trade_close(self, trade, order, **kwargs):
        # 保險：成交全平時再清一次，讓下一筆能重新設中心
        self.last_trade_price = None
