# source: https://raw.githubusercontent.com/pangtongs/pangtongs.github.io/31930a1aa099a0780293485478c1eca96fd50e5d/python/my-trading-bots/user_data/strategies/StablecoinScalper.py
# Github_pangtongs_pangtongs_github_io__StablecoinScalper__20250406_025733.py - Improved Version
from freqtrade.strategy import IStrategy
from pandas import DataFrame
import numpy as np
import logging
from datetime import datetime, timedelta
from freqtrade.persistence import Trade
import pandas_ta as ta

logger = logging.getLogger(__name__)

class Github_pangtongs_pangtongs_github_io__StablecoinScalper__20250406_025733(IStrategy):
    """
    Adaptive Stablecoin Scalping Strategy
    
    Analyzes recent price movements to find optimal entry and exit points.
    Uses bid-ask zone detection for stablecoin pairs.
    """
    
    # Minimal ROI - lower to match our expected small profits (0.01%)
    minimal_roi = {
        "0": 0.0001  # 0.01% profit target
    }
    
    # Settings
    timeframe = '1m'
    stoploss = -0.0005  # Tighter stop loss (0.05%)
    trailing_stop = False
    process_only_new_candles = True
    startup_candle_count = 200  # Need more data for volatility calculations
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False
    
    # Define parameters
    buy_threshold = 0.9998  # Relative value, adapts to recent price action
    sell_threshold = 1.0002  # Relative value, adapts to recent price action
    
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Adds indicators to identify optimal trading zones
        """
        # Calculate rolling statistics to identify price zones
        dataframe['price_mean'] = dataframe['close'].rolling(window=500).mean()
        
        # Calculate standard deviation to identify volatility
        dataframe['price_std'] = dataframe['close'].rolling(window=500).std()
        
        # Calculate reasonable price deviation for this pair
        dataframe['lower_band'] = dataframe['price_mean'] - (dataframe['price_std'] * 1.5)
        dataframe['upper_band'] = dataframe['price_mean'] + (dataframe['price_std'] * 1.5)
        
        # Historical price levels (lows and highs)
        dataframe['recent_min'] = dataframe['low'].rolling(window=100).min()
        dataframe['recent_max'] = dataframe['high'].rolling(window=100).max()
        
        # Weighted average price
        dataframe['vwap'] = (dataframe['close'] * dataframe['volume']).rolling(20).sum() / dataframe['volume'].rolling(20).sum()
        
        # Calculate historical volatility
        dataframe['volatility'] = dataframe['price_std'] / dataframe['price_mean']
        
        # Check if there's enough price movement for this strategy to work
        range_pct = (dataframe['recent_max'] - dataframe['recent_min']) / dataframe['price_mean']
        dataframe['enough_range'] = range_pct > 0.0002  # Need at least 0.02% range
        
        # Calculate dynamic buy/sell levels based on recent price action
        dataframe['buy_level'] = dataframe['recent_min'] + (0.1 * (dataframe['recent_max'] - dataframe['recent_min']))
        dataframe['sell_level'] = dataframe['recent_min'] + (0.9 * (dataframe['recent_max'] - dataframe['recent_min']))
        
        return dataframe
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Buy when price dips to lower range of recent price action
        """
        pair = metadata['pair']
        
        # Only apply to stablecoin pairs
        if 'USDT' in pair and ('USDC' in pair or 'BUSD' in pair or 'TUSD' in pair or 'FDUSD' in pair):
            conditions = [
                # Price is near the lower range of recent activity
                dataframe['close'] <= dataframe['buy_level'],
                # Make sure there's enough historical range for this to work
                dataframe['enough_range'],
                # Ensure volatility is reasonable (not too low or high)
                dataframe['volatility'] > 0.00005,
                dataframe['volatility'] < 0.001,
                # Volume should be reasonable
                dataframe['volume'] > 0
            ]
            dataframe['buy'] = np.where(np.logical_and.reduce(conditions), 1, 0)
        else:
            dataframe['buy'] = 0
            
        return dataframe
    
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Sell when price reaches upper range of recent price action
        """
        pair = metadata['pair']
        
        # Only apply to stablecoin pairs
        if 'USDT' in pair and ('USDC' in pair or 'BUSD' in pair or 'TUSD' in pair or 'FDUSD' in pair):
            conditions = [
                # Price is near the upper range of recent activity
                dataframe['close'] >= dataframe['sell_level'],
                # Volume should be reasonable
                dataframe['volume'] > 0
            ]
            dataframe['sell'] = np.where(np.logical_and.reduce(conditions), 1, 0)
        else:
            dataframe['sell'] = 0
            
        return dataframe
    
    def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, 
                           time_in_force: str, current_time: datetime, **kwargs) -> bool:
        """
        Additional checks before entering a trade
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if dataframe.empty:
            return False
            
        current_candle = dataframe.iloc[-1].squeeze()
        
        # Make sure there's decent volume (at least $10,000 in last candle)
        if current_candle['volume'] * current_candle['close'] < 10000:
            return False
        
        # Check if buy price offers potential profit margin
        # Need at least 0.015% between buy price and recent high
        if (current_candle['recent_max'] / rate - 1) < 0.00015:
            return False
            
        return True
    
    def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
                   current_profit: float, **kwargs):
        """
        Custom exit conditions
        """
        # Get the dataframe for this pair
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if dataframe.empty:
            return None
        
        # Shorter timeout - exit after 30 minutes
        if current_time - trade.open_date_utc > timedelta(minutes=30):
            return 'timeout_exit'
            
        # If we're in very slight profit and price is declining, take it
        if 0 < current_profit < 0.0005:
            current_candle = dataframe.iloc[-1].squeeze()
            prev_candle = dataframe.iloc[-2].squeeze()
            
            if current_candle['close'] < prev_candle['close']:
                return 'small_profit_exit'
        
        return None