# source: https://raw.githubusercontent.com/shaojintian/crypto_backtesting/75ba72665f1a6afaa985e9e0e853330d6d2bd848/freq_trade/user_data/strategies/vol_mean_reversion_strategy.py
import numpy as np
import pandas as pd
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter,Trade
from pandas import DataFrame
import talib.abstract as ta
from technical import qtpylib

import numpy as np
import pandas as pd
from datetime import datetime, timedelta, timezone
from pandas import DataFrame
from typing import Optional, Union

class Github_shaojintian_crypto_backtesting__vol_mean_reversion_strategy__20250211_105003(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = "3m"
    
    # Minimal ROI
    minimal_roi = {
        "0": 0.02,  # 2% profit
        "30": 0.01,
        "60": 0.005
    }

    # Parameters
    bb_window = IntParameter(10, 50, default=20, space='buy')
    bb_std = DecimalParameter(1.5, 3.0, default=2.0, decimals=1, space='buy')
    vol_window = IntParameter(10, 50, default=20, space='buy')
    
    # Stoploss
    stoploss = -0.01  # 2% stoploss
    
    # trailing stoploss
    trailing_stop: bool = True
    trailing_stop_positive: float = 0.01
    trailing_stop_positive_offset: float = 0.0
    trailing_only_offset_is_reached = False
    use_custom_stoploss: bool = True

    can_short = True

    def custom_stoploss(
        self,
        pair: str,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        after_fill: bool,
        **kwargs,
    ) -> float | None:
        """
        Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
        e.g. returning -0.05 would create a stoploss 5% below current_rate.
        The custom stoploss can never be below self.stoploss, which serves as a hard maximum loss.

        For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/

        When not implemented by a strategy, returns the initial stoploss value.
        Only called when use_custom_stoploss is set to True.

        :param pair: Pair that's currently analyzed
        :param trade: trade object.
        :param current_time: datetime object, containing the current datetime
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
        :param current_profit: Current profit (as ratio), calculated based on current_rate.
        :param after_fill: True if the stoploss is called after the order was filled.
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
        :return float: New stoploss value, relative to the current_rate
        """
        return self.stoploss
    
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Volatility
        dataframe['volatility'] = dataframe['close'].rolling(window=self.vol_window.value).std()
        dataframe['volatility_mean'] = dataframe['volatility'].rolling(window=self.vol_window.value).mean()
        
        # Bollinger Bands
        bollinger = qtpylib.bollinger_bands(
            dataframe['close'], 
            window=self.bb_window.value,
            stds=self.bb_std.value
        )
        dataframe['bb_upperband'] = bollinger['upper']
        dataframe['bb_middleband'] = bollinger['mid']
        dataframe['bb_lowerband'] = bollinger['lower']
        
        # Volume Filters
        dataframe['volume_mean'] = dataframe['volume'].rolling(window=self.vol_window.value).mean()
        
        return dataframe
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                # Price below lower band
                (dataframe['close'] < dataframe['bb_lowerband']) &
                # High volatility
                (dataframe['volatility'] > dataframe['volatility_mean']) &
                # Good volume
                (dataframe['volume'] > dataframe['volume_mean'])
            ),
            'enter_short'
        ] = 1
        
        return dataframe
    
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                # Price above middle band
                (dataframe['close'] > dataframe['bb_middleband']) |
                # Volatility back to normal
                (dataframe['volatility'] < dataframe['volatility_mean'])
            ),
            'exit_short'
        ] = 1
        
        return dataframe