# source: https://raw.githubusercontent.com/ShoVang/TradingBots/bc33c736061c02d48ffeac3c1cb0e247d878a086/cBot/user_data/strategies/dual_timeframe_strategy.py
from freqtrade.strategy import IStrategy, IntParameter
from pandas import DataFrame
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from functools import reduce
from typing import Optional

class Github_ShoVang_TradingBots__dual_timeframe_strategy__20250621_051441(IStrategy):
    # Common strategy parameters
    INTERFACE_VERSION = 3
    
    # Max open trades - only one trade at a time
    max_open_trades = 1
    
    # Minimal ROI designed for the strategy             ADJUST THIS 
    minimal_roi = { # 1 ATR
    }

    # Optimal stoploss designed for the strategy        ADJUST THIS 
    stoploss = -0.05  # 5% stop loss

    # Trailing stoploss
    trailing_stop = True
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.02
    trailing_only_offset_is_reached = True

    # Position sizing
    position_size = 0.1  # 10% of account per trade

    # Loss tracking
    consecutive_losses = 0
    last_trade_time = None
    trading_paused = False
    pause_until = None

    def __init__(self, config: dict) -> None:
        super().__init__(config)
        self.loss_counter = 0

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # MACD          I dont think ill need macds
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']
        dataframe['macdhist'] = macd['macdhist']

        # RSI
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)

        # EMAs
        dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
        dataframe['ema200'] = ta.EMA(dataframe, timeperiod=200)

        # ATR
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)

        return dataframe

    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                          proposed_stake: float, min_stake: float, max_stake: float,
                          leverage: float, entry_tag: Optional[str], side: str,
                          **kwargs) -> float:
        return self.position_size * self.wallets.get_total_stake_amount()

    def custom_stoploss(self, pair: str, trade, current_time, current_rate, current_profit, **kwargs):
        # Fallback ATR in case metadata is missing
        atr = trade.open_trade_metadata.get('atr_at_entry', 0.01)

        # Calculate stoploss as distance from entry
        stop_loss_price = trade.open_rate - atr
        stop_loss_relative = (current_rate - stop_loss_price) / current_rate

        # If profit > 1.5x ATR, tighten stop to lock in profit
        if current_profit > (1.5 * (atr / trade.open_rate)):
            return 0.01  # Tight trailing stop

        return max(0.01, stop_loss_relative)  # Prevent SL from being too tight


    def open_trade(self, trade, **kwargs):
        # Capture ATR at trade entry
        trade.update_open_trade_metadata({'atr_at_entry': self.dataframe['atr'].iloc[-1]})
        return None

    def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, 
                           time_in_force: str, current_time: datetime, entry_tag: Optional[str], 
                           side: str, **kwargs) -> bool:
        """
        Called before a trade entry is executed. Used to prevent trades during pause periods.
        """
        # Check if we're in a trading pause
        if self.trading_paused:
            if current_time >= self.pause_until:
                self.trading_paused = False
                self.consecutive_losses = 0
                print(f"Trading resumed after pause period")
            else:
                print(f"Trade entry blocked - trading paused until {self.pause_until}")
                return False  # Block the trade entry
        
        return True  # Allow the trade entry

class MomentumStrategy(Github_ShoVang_TradingBots__dual_timeframe_strategy__20250621_051441):
    """
    Strategy for timeframes <= 30 minutes using MACD and EMA200
    """
    timeframe = '30m'  # Default timeframe changed from '5m' to '30m'

    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        # Check if we're in a trading pause
        if self.trading_paused:
            # Use current time from dataframe instead of datetime.now()
            current_time = pd.to_datetime(dataframe['date'].iloc[-1]) if len(dataframe) > 0 else datetime.now()
            if current_time >= self.pause_until:
                self.trading_paused = False
                self.consecutive_losses = 0
            else:
                dataframe['buy'] = 0
                return dataframe

        # MACD cross conditions - MACD line (faster) crosses above signal line (slower)
        macd_cross = (
            (dataframe['macd'] > dataframe['macdsignal']) &
            (dataframe['macd'].shift(1) <= dataframe['macdsignal'].shift(1))
        )
        
        # MACD crosses above zero line (momentum turning positive)
        macd_above_zero = (
            (dataframe['macd'] > 0) &
            (dataframe['macd'].shift(1) <= 0)
        )
        
        # Price above 200 EMA for longs
        above_ema200 = dataframe['close'] > dataframe['ema200']
        
        # Combine conditions: MACD cross above zero line AND price above 200 EMA
        buy_condition = macd_above_zero & above_ema200
        
        conditions.append(buy_condition)

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions),
                'buy'] = 1

        return dataframe

    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        # MACD cross down - MACD line crosses below signal line
        macd_cross_down = (
            (dataframe['macd'] < dataframe['macdsignal']) &
            (dataframe['macd'].shift(1) >= dataframe['macdsignal'].shift(1))
        )
        
        # Price below 200 EMA
        below_ema200 = dataframe['close'] < dataframe['ema200']
        
        # Sell on MACD cross down OR price below 200 EMA
        conditions.append(macd_cross_down | below_ema200)

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions),
                'sell'] = 1

        return dataframe

    def custom_stoploss(self, pair: str, trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float:
        """
        Custom stoploss logic:
        - Use ATR-based static stoploss until price moves 2x ATR above entry
        - Once price is 2x ATR above entry, activate trailing stop
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if dataframe is None or len(dataframe) == 0:
            return self.stoploss  # fallback to default

        # Find the candle at or just before the trade open time
        entry_time = trade.open_date_utc
        entry_idx = dataframe[dataframe['date'] <= entry_time].index.max()
        if pd.isna(entry_idx):
            return self.stoploss

        entry_atr = dataframe.loc[entry_idx, 'atr']
        entry_price = trade.open_rate
        atr_multiple = 2.0
        trigger_price = entry_price + atr_multiple * entry_atr

        # If price has not reached trigger, use static ATR stop
        if current_rate < trigger_price:
            return (entry_price - entry_atr) / current_rate - 1
        # If price has reached trigger, use trailing stop (e.g. 1x ATR below high)
        else:
            # Find the highest price since entry
            high_since_entry = dataframe.loc[entry_idx:, 'high'].max()
            trailing_sl = high_since_entry - entry_atr
            # If current price is above trailing stop, set stoploss to trailing
            if current_rate > trailing_sl:
                return (trailing_sl / current_rate) - 1
            else:
                # If price is below trailing stop, fallback to default stoploss
                return self.stoploss

    def open_trade(self, trade, **kwargs):
        """
        Capture ATR at trade entry for stoploss calculation
        """
        # Get the current dataframe for this pair
        dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
        
        # Capture ATR at the time of entry (last row)
        atr_at_entry = dataframe['atr'].iloc[-1] if len(dataframe) > 0 else 0.01
        
        # Store ATR in trade metadata
        trade.update_open_trade_metadata({'atr_at_entry': atr_at_entry})
        return None

    def confirm_trade_exit(self, pair: str, trade, order_type: str, amount: float, rate: float, 
                          time_in_force: str, sell_reason: str, current_time: datetime, **kwargs) -> bool:
        """
        Called before a trade exit is executed. Used for loss tracking and trading pause logic.
        """
        # Calculate profit before exit
        current_profit = trade.calc_profit_ratio(rate)
        
        # Update consecutive losses counter
        if current_profit < 0:
            self.consecutive_losses += 1
            self.loss_counter += 1
        else:
            self.consecutive_losses = 0
            self.loss_counter = 0

        # Check for trading pause conditions - pause after 3 consecutive losses
        if self.consecutive_losses >= 3:
            self.trading_paused = True
            self.pause_until = current_time + timedelta(days=2)
            print(f"Trading paused for 2 days after {self.consecutive_losses} consecutive losses")

        return True

    def custom_exit(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float,
                   current_profit: float, **kwargs):
        # This method is now only for custom exit logic, not loss tracking
        return None

class DipBuyStrategy(Github_ShoVang_TradingBots__dual_timeframe_strategy__20250621_051441):
    """
    Strategy for timeframes => 30 minutes using RSI and EMA touches
    """
    timeframe = '30m'  # Base timeframe

    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        # Check if we're in a trading pause
        if self.trading_paused:
            if datetime.now() >= self.pause_until:
                self.trading_paused = False
                self.consecutive_losses = 0
            else:
                dataframe['buy'] = 0
                return dataframe

        # RSI conditions
        rsi_oversold = dataframe['rsi'] < 30
        
        # EMA touch conditions
        ema_touch = (
            (dataframe['close'] > dataframe['ema50']) &
            (dataframe['close'] > dataframe['ema200'])
        )
        
        conditions.append((rsi_oversold | ema_touch))

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions),
                'buy'] = 1

        return dataframe

    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        # RSI overbought
        rsi_overbought = dataframe['rsi'] > 70
        
        # EMA touch conditions
        ema_touch_down = (
            (dataframe['close'] < dataframe['ema50']) |
            (dataframe['close'] < dataframe['ema200'])
        )
        
        conditions.append(rsi_overbought | ema_touch_down)

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions),
                'sell'] = 1

        return dataframe

    def custom_exit(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float,
                   current_profit: float, **kwargs):
        # Update consecutive losses counter
        if current_profit < 0:
            self.consecutive_losses += 1
            self.loss_counter += 1
        else:
            self.consecutive_losses = 0
            self.loss_counter = 0

        # Check for trading pause conditions
        if self.consecutive_losses >= 3:
            self.trading_paused = True
            self.pause_until = current_time + timedelta(days=3)

        return None 