# source: https://raw.githubusercontent.com/realm520/FreqBot/a24897ec69f16a9a06b6cd051050fe2ec5bc2583/user_data/strategies/VolatilityIndicatorsStrategy.py
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these libs ---
import numpy as np
import pandas as pd
from typing import Optional, Dict, Any
from pandas import DataFrame
from datetime import datetime
from freqtrade.strategy import (
    IStrategy,
    Trade,
    Order,
    PairLocks,
    informative,  # @informative decorator
    # Hyperopt Parameters
    BooleanParameter,
    CategoricalParameter,
    DecimalParameter,
    IntParameter,
    RealParameter,
    # timeframe helpers
    timeframe_to_minutes,
    timeframe_to_next_date,
    timeframe_to_prev_date,
    # Strategy helper functions
    merge_informative_pair,
    stoploss_from_absolute,
    stoploss_from_open,
)
from freqtrade.persistence import Trade
from freqtrade.exchange import timeframe_to_minutes
import talib.abstract as ta
from technical.indicators import ichimoku
import freqtrade.vendor.qtpylib.indicators as qtpylib


class Github_realm520_FreqBot__VolatilityIndicatorsStrategy__20250906_062156(IStrategy):
    """
    波动率指标策略
    使用五个波动率指标捕捉交易信号：
    1. Chaikin Volatility (CHV) - 查金波动率指标
    2. Donchian Channels - 唐奇安通道
    3. Keltner Channels - 凯尔特纳通道
    4. Relative Volatility Index (RVI) - 相对波动率指数
    5. Standard Deviation - 标准差
    """
    
    # 策略参数
    INTERFACE_VERSION = 3
    can_short: bool = False  # Set to False for spot trading
    
    # 买入/卖出超参数
    buy_chv_increase = DecimalParameter(0.0, 0.5, default=0.1, space="buy", optimize=True)
    buy_rvi_threshold = IntParameter(40, 70, default=50, space="buy", optimize=True)
    sell_rvi_threshold = IntParameter(30, 60, default=50, space="sell", optimize=True)
    
    # Donchian Channel 周期
    donchian_period = IntParameter(10, 30, default=20, space="buy", optimize=True)
    
    # Keltner Channel 参数
    keltner_period = IntParameter(10, 30, default=20, space="buy", optimize=True)
    keltner_multiplier = DecimalParameter(1.0, 3.0, default=2.0, space="buy", optimize=True)
    
    # 标准差阈值
    std_threshold = DecimalParameter(0.01, 0.1, default=0.03, space="buy", optimize=True)
    
    # ROI表 - 根据持仓时间设定目标收益
    minimal_roi = {
        "0": 0.10,    # 10%
        "30": 0.05,   # 5% after 30 minutes
        "60": 0.03,   # 3% after 1 hour
        "120": 0.01,  # 1% after 2 hours
    }
    
    # 止损
    stoploss = -0.05  # -5%
    trailing_stop = True
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.02
    trailing_only_offset_is_reached = True
    
    # 时间框架
    timeframe = '15m'
    
    # 订单类型
    order_types = {
        'entry': 'limit',
        'exit': 'limit',
        'stoploss': 'market',
        'stoploss_on_exchange': True
    }
    
    # 启动蜡烛数
    startup_candle_count: int = 100
    
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        计算五个波动率指标
        """
        
        # 1. Chaikin Volatility (CHV)
        # CHV = ((EMA(High-Low, 10) - EMA(High-Low, 10)[10 bars ago]) / EMA(High-Low, 10)[10 bars ago]) * 100
        hl_diff = dataframe['high'] - dataframe['low']
        ema_hl = pd.Series(ta.EMA(hl_diff, timeperiod=10), index=dataframe.index)
        dataframe['chv'] = ((ema_hl - ema_hl.shift(10)) / ema_hl.shift(10)) * 100
        
        # 2. Donchian Channels
        period = self.donchian_period.value
        dataframe['donchian_upper'] = dataframe['high'].rolling(window=period).max()
        dataframe['donchian_lower'] = dataframe['low'].rolling(window=period).min()
        dataframe['donchian_middle'] = (dataframe['donchian_upper'] + dataframe['donchian_lower']) / 2
        
        # 3. Keltner Channels
        kc_period = self.keltner_period.value
        multiplier = self.keltner_multiplier.value
        
        # 计算典型价格和EMA
        typical_price = (dataframe['high'] + dataframe['low'] + dataframe['close']) / 3
        dataframe['keltner_middle'] = pd.Series(ta.EMA(typical_price, timeperiod=kc_period), index=dataframe.index)
        
        # 计算ATR
        dataframe['atr'] = ta.ATR(dataframe['high'], dataframe['low'], dataframe['close'], timeperiod=kc_period)
        
        # 计算上下轨
        dataframe['keltner_upper'] = dataframe['keltner_middle'] + (multiplier * dataframe['atr'])
        dataframe['keltner_lower'] = dataframe['keltner_middle'] - (multiplier * dataframe['atr'])
        
        # 4. Relative Volatility Index (RVI)
        # 计算上涨和下跌的标准差
        period_rvi = 14
        
        # 价格变化
        price_change = dataframe['close'].diff()
        
        # 分离上涨和下跌
        up_moves = price_change.copy()
        up_moves[up_moves < 0] = 0
        down_moves = -price_change.copy()
        down_moves[down_moves < 0] = 0
        
        # 计算上涨和下跌的标准差
        up_std = up_moves.rolling(window=period_rvi).std()
        down_std = down_moves.rolling(window=period_rvi).std()
        
        # 计算RVI
        rs = up_std / down_std
        dataframe['rvi'] = 100 - (100 / (1 + rs))
        
        # 5. Standard Deviation
        dataframe['std'] = dataframe['close'].rolling(window=20).std()
        dataframe['std_normalized'] = dataframe['std'] / dataframe['close']
        
        # 添加量能指标辅助判断
        dataframe['volume_ma'] = dataframe['volume'].rolling(window=20).mean()
        
        # 价格位置指标
        dataframe['price_position_donchian'] = (dataframe['close'] - dataframe['donchian_lower']) / (dataframe['donchian_upper'] - dataframe['donchian_lower'])
        dataframe['price_position_keltner'] = (dataframe['close'] - dataframe['keltner_lower']) / (dataframe['keltner_upper'] - dataframe['keltner_lower'])
        
        return dataframe
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        基于波动率指标生成买入信号
        """
        conditions = []
        
        # 买入条件1：价格突破Donchian上轨
        conditions.append(
            (dataframe['close'] > dataframe['donchian_upper'].shift(1)) &
            (dataframe['volume'] > dataframe['volume_ma'])
        )
        
        # 买入条件2：价格突破Keltner上轨且RVI上升
        conditions.append(
            (dataframe['close'] > dataframe['keltner_upper']) &
            (dataframe['rvi'] > self.buy_rvi_threshold.value) &
            (dataframe['rvi'] > dataframe['rvi'].shift(1))
        )
        
        # 买入条件3：CHV增加且标准差适中（波动率增加但不过度）
        conditions.append(
            (dataframe['chv'] > dataframe['chv'].shift(1) * (1 + self.buy_chv_increase.value)) &
            (dataframe['std_normalized'] < self.std_threshold.value * 2) &
            (dataframe['std_normalized'] > self.std_threshold.value * 0.5)
        )
        
        # 买入条件4：价格在通道下轨反弹
        conditions.append(
            (dataframe['close'] > dataframe['donchian_lower']) &
            (dataframe['close'].shift(1) <= dataframe['donchian_lower'].shift(1)) &
            (dataframe['rvi'] > 40) &
            (dataframe['volume'] > dataframe['volume_ma'] * 1.5)
        )
        
        # 合并所有买入条件
        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions),
                'enter_long'] = 1
        
        # 做空条件
        short_conditions = []
        
        # 做空条件1：价格跌破Donchian下轨
        short_conditions.append(
            (dataframe['close'] < dataframe['donchian_lower'].shift(1)) &
            (dataframe['volume'] > dataframe['volume_ma'])
        )
        
        # 做空条件2：价格跌破Keltner下轨且RVI下降
        short_conditions.append(
            (dataframe['close'] < dataframe['keltner_lower']) &
            (dataframe['rvi'] < self.sell_rvi_threshold.value) &
            (dataframe['rvi'] < dataframe['rvi'].shift(1))
        )
        
        if short_conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, short_conditions),
                'enter_short'] = 1
        
        return dataframe
    
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        基于波动率指标生成卖出信号
        """
        exit_long_conditions = []
        
        # 退出多头条件1：价格跌破Donchian中轨
        exit_long_conditions.append(
            dataframe['close'] < dataframe['donchian_middle']
        )
        
        # 退出多头条件2：RVI下降到阈值以下
        exit_long_conditions.append(
            (dataframe['rvi'] < self.sell_rvi_threshold.value) &
            (dataframe['rvi'] < dataframe['rvi'].shift(1))
        )
        
        # 退出多头条件3：价格跌破Keltner中轨且波动率增大
        exit_long_conditions.append(
            (dataframe['close'] < dataframe['keltner_middle']) &
            (dataframe['std_normalized'] > self.std_threshold.value * 2)
        )
        
        # 退出多头条件4：CHV急剧下降（波动率萎缩）
        exit_long_conditions.append(
            dataframe['chv'] < dataframe['chv'].shift(1) * 0.7
        )
        
        if exit_long_conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, exit_long_conditions),
                'exit_long'] = 1
        
        # 退出做空条件
        exit_short_conditions = []
        
        # 退出做空条件1：价格突破Donchian中轨
        exit_short_conditions.append(
            dataframe['close'] > dataframe['donchian_middle']
        )
        
        # 退出做空条件2：RVI上升到阈值以上
        exit_short_conditions.append(
            (dataframe['rvi'] > self.buy_rvi_threshold.value) &
            (dataframe['rvi'] > dataframe['rvi'].shift(1))
        )
        
        if exit_short_conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, exit_short_conditions),
                'exit_short'] = 1
        
        return dataframe
    
    def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
                    current_rate: float, current_profit: float, **kwargs) -> Optional[str]:
        """
        自定义退出逻辑
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        current_candle = dataframe.iloc[-1].squeeze()
        
        # 如果是多头仓位
        if trade.is_short is False:
            # 如果CHV急剧上升，表明市场进入高波动期，考虑获利了结
            if current_candle['chv'] > current_candle['chv'] * 2:
                if current_profit > 0.02:
                    return 'high_volatility_take_profit'
            
            # 如果价格接近Donchian上轨且RVI开始下降，考虑退出
            if current_candle['price_position_donchian'] > 0.9 and current_candle['rvi'] < 60:
                if current_profit > 0:
                    return 'donchian_upper_resistance'
        
        # 如果是空头仓位
        else:
            # 如果CHV急剧上升且价格反弹，考虑止损
            if current_candle['chv'] > current_candle['chv'] * 2:
                if current_profit < -0.01:
                    return 'high_volatility_stop_loss'
            
            # 如果价格接近Donchian下轨且RVI开始上升，考虑退出
            if current_candle['price_position_donchian'] < 0.1 and current_candle['rvi'] > 40:
                if current_profit > 0:
                    return 'donchian_lower_support'
        
        return None
    
    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                            proposed_stake: float, min_stake: Optional[float], max_stake: float,
                            leverage: float, entry_tag: Optional[str], side: str,
                            **kwargs) -> float:
        """
        根据波动率调整仓位大小
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        current_candle = dataframe.iloc[-1].squeeze()
        
        # 基础仓位
        stake = proposed_stake
        
        # 根据标准差调整仓位（波动率越大，仓位越小）
        if current_candle['std_normalized'] > self.std_threshold.value * 2:
            stake = stake * 0.5  # 高波动时减半仓位
        elif current_candle['std_normalized'] < self.std_threshold.value:
            stake = stake * 1.2  # 低波动时增加20%仓位
        
        # 根据RVI调整仓位（趋势越强，仓位越大）
        if side == "long":
            if current_candle['rvi'] > 70:
                stake = stake * 1.1  # 强势上涨趋势增加10%
            elif current_candle['rvi'] < 40:
                stake = stake * 0.8  # 弱势时减少20%
        
        # 确保仓位在允许范围内
        return min(max(stake, min_stake or 0), max_stake)


# 导入reduce函数
from functools import reduce