# source: https://raw.githubusercontent.com/Eksz3lsi0r/DOLLAMNY/f6178a8e9385159556a6443262fc338e43d6f88c/conservative_position_sizing_strategy.py
"""
Conservative Position Sizing Strategy

This strategy implements conservative risk management with:
- Fixed USD stake amount per trade
- Maximum 1-2 simultaneous trades
- No pyramiding (no additional positions in same direction)
- Conservative stoploss and take profit levels

Risk Management Features:
- Fixed USD stake: $100 per trade (configurable)
- Max open trades: 2 (configurable)
- Position adjustment disabled to prevent pyramiding
- Conservative stoploss: -2%
- Take profit: +3%
- RSI-based entry/exit signals
"""

from pandas import DataFrame
import talib.abstract as ta
from datetime import datetime
from typing import Optional

from freqtrade.persistence import Trade
from freqtrade.strategy import (
    IStrategy,
    BooleanParameter,
    DecimalParameter,
    IntParameter,
    CategoricalParameter,
)
import freqtrade.vendor.qtpylib.indicators as qtpylib


class Github_Eksz3lsi0r_DOLLAMNY__conservative_position_sizing_strategy__20250916_061219(IStrategy):
    """
    Conservative trading strategy with strict position sizing rules.
    
    Key Features:
    - Fixed USD stake amount per trade
    - Maximum 1-2 simultaneous trades
    - No pyramiding (position adjustment disabled)
    - Conservative risk management
    """

    # Strategy interface version
    INTERFACE_VERSION = 3

    # Timeframe
    timeframe = "5m"

    # Startup candle count
    startup_candle_count: int = 30

    # Order types
    order_types = {
        "entry": "limit",
        "exit": "limit", 
        "stoploss": "market",
        "stoploss_on_exchange": False,
    }

    # Order time in force
    order_time_in_force = {"entry": "gtc", "exit": "gtc"}

    # =============================================================================
    # CONSERVATIVE RISK MANAGEMENT PARAMETERS
    # =============================================================================

    # Fixed USD stake amount per trade
    fixed_usd_stake = DecimalParameter(
        50.0, 500.0, default=100.0, decimals=2, space="buy", 
        description="Fixed USD stake amount per trade"
    )

    # Maximum number of simultaneous trades (conservative: 1-2)
    max_open_trades = IntParameter(
        1, 2, default=2, space="buy",
        description="Maximum simultaneous trades"
    )

    # Conservative stoploss
    stoploss = DecimalParameter(
        -0.05, -0.01, default=-0.02, decimals=3, space="stoploss",
        description="Conservative stoploss percentage"
    )

    # Take profit target
    take_profit = DecimalParameter(
        0.02, 0.08, default=0.03, decimals=3, space="sell",
        description="Take profit target percentage"
    )

    # RSI parameters for entry/exit signals
    buy_rsi = IntParameter(20, 35, default=30, space="buy")
    sell_rsi = IntParameter(65, 80, default=70, space="sell")
    rsi_period = IntParameter(10, 21, default=14, space="buy")

    # Additional filters
    volume_filter = BooleanParameter(default=True, space="buy")
    trend_filter = BooleanParameter(default=True, space="buy")

    # =============================================================================
    # POSITION SIZING CONFIGURATION
    # =============================================================================

    # Disable position adjustment to prevent pyramiding
    position_adjustment_enable = False
    max_entry_position_adjustment = 0

    # Use custom stake amount
    use_custom_stake_amount = True

    # =============================================================================
    # ROI CONFIGURATION (Conservative)
    # =============================================================================

    # Conservative ROI table - quick exits to lock in profits
    minimal_roi = {
        "0": 0.08,    # 8% profit target
        "15": 0.04,   # 4% after 15 minutes
        "30": 0.02,   # 2% after 30 minutes
        "60": 0.01,   # 1% after 1 hour
        "120": 0.0,   # Break even after 2 hours
    }

    # =============================================================================
    # CUSTOM STAKE AMOUNT IMPLEMENTATION
    # =============================================================================

    def custom_stake_amount(
        self,
        pair: str,
        current_time: datetime,
        current_rate: float,
        proposed_stake: float,
        min_stake: float | None,
        max_stake: float,
        leverage: float,
        entry_tag: str | None,
        side: str,
        **kwargs,
    ) -> float:
        """
        Implement fixed USD stake amount per trade.
        
        This ensures each trade uses exactly the configured USD amount,
        regardless of account balance or pair price.
        """
        # Get the fixed USD stake amount
        usd_stake = self.fixed_usd_stake.value
        
        # Convert USD stake to base currency amount
        # For USDT pairs, this is straightforward
        if pair.endswith('/USDT'):
            stake_amount = usd_stake
        else:
            # For other pairs, we need to estimate the conversion
            # This is a simplified approach - in practice you might want
            # to use a more sophisticated conversion method
            stake_amount = usd_stake / current_rate
        
        # Ensure stake amount is within exchange limits
        if min_stake and stake_amount < min_stake:
            stake_amount = min_stake
        if stake_amount > max_stake:
            stake_amount = max_stake
            
        return stake_amount

    # =============================================================================
    # TRADE VALIDATION (Prevent Pyramiding)
    # =============================================================================

    def confirm_trade_entry(
        self,
        pair: str,
        order_type: str,
        amount: float,
        rate: float,
        time_in_force: str,
        current_time: datetime,
        entry_tag: str | None,
        side: str,
        **kwargs,
    ) -> bool:
        """
        Additional validation before entering a trade.
        
        This method is called before each trade entry to ensure
        we don't exceed our conservative limits.
        """
        # Get current open trades
        open_trades = Trade.get_open_trades()
        
        # Check if we already have the maximum number of trades
        if len(open_trades) >= self.max_open_trades.value:
            self.logger.info(f"Maximum trades ({self.max_open_trades.value}) reached. Skipping {pair}")
            return False
        
        # Check if we already have a position in this pair
        for trade in open_trades:
            if trade.pair == pair:
                self.logger.info(f"Already have position in {pair}. Skipping to prevent pyramiding.")
                return False
        
        # Check if we have enough balance for the fixed stake
        required_stake = self.fixed_usd_stake.value
        available_balance = self.wallets.get_available_stake_amount()
        
        if available_balance < required_stake:
            self.logger.info(f"Insufficient balance for {pair}. Required: {required_stake}, Available: {available_balance}")
            return False
        
        return True

    # =============================================================================
    # INDICATOR CALCULATION
    # =============================================================================

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Calculate technical indicators for entry/exit signals.
        """
        # RSI
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=self.rsi_period.value)
        
        # Moving averages for trend filter
        dataframe["ema_20"] = ta.EMA(dataframe, timeperiod=20)
        dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50)
        
        # Volume filter
        dataframe["volume_sma"] = ta.SMA(dataframe["volume"], timeperiod=20)
        
        # Bollinger Bands for additional context
        bollinger = qtpylib.bollinger_bands(dataframe["close"], window=20, stds=2)
        dataframe["bb_lowerband"] = bollinger["lower"]
        dataframe["bb_middleband"] = bollinger["mid"]
        dataframe["bb_upperband"] = bollinger["upper"]
        
        # MACD for momentum
        macd = ta.MACD(dataframe)
        dataframe["macd"] = macd["macd"]
        dataframe["macdsignal"] = macd["macdsignal"]
        dataframe["macdhist"] = macd["macdhist"]
        
        return dataframe

    # =============================================================================
    # ENTRY SIGNAL LOGIC
    # =============================================================================

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Define conservative entry conditions.
        """
        conditions = []
        
        # RSI oversold condition
        conditions.append(dataframe["rsi"] < self.buy_rsi.value)
        
        # Price near lower Bollinger Band (oversold)
        conditions.append(dataframe["close"] <= dataframe["bb_lowerband"])
        
        # MACD bullish crossover
        conditions.append(dataframe["macd"] > dataframe["macdsignal"])
        
        # Trend filter: price above EMA 20
        if self.trend_filter.value:
            conditions.append(dataframe["close"] > dataframe["ema_20"])
        
        # Volume filter: above average volume
        if self.volume_filter.value:
            conditions.append(dataframe["volume"] > dataframe["volume_sma"])
        
        # RSI starting to turn up (momentum)
        conditions.append(dataframe["rsi"] > dataframe["rsi"].shift(1))
        
        # Combine all conditions
        if conditions:
            dataframe.loc[
                qtpylib.AND(*conditions),
                "enter_long",
            ] = 1
        
        return dataframe

    # =============================================================================
    # EXIT SIGNAL LOGIC
    # =============================================================================

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Define conservative exit conditions.
        """
        conditions = []
        
        # RSI overbought condition
        conditions.append(dataframe["rsi"] > self.sell_rsi.value)
        
        # Price near upper Bollinger Band (overbought)
        conditions.append(dataframe["close"] >= dataframe["bb_upperband"])
        
        # MACD bearish crossover
        conditions.append(dataframe["macd"] < dataframe["macdsignal"])
        
        # RSI starting to turn down (momentum)
        conditions.append(dataframe["rsi"] < dataframe["rsi"].shift(1))
        
        # Combine all conditions
        if conditions:
            dataframe.loc[
                qtpylib.AND(*conditions),
                "exit_long",
            ] = 1
        
        return dataframe

    # =============================================================================
    # CUSTOM EXIT LOGIC
    # =============================================================================

    def custom_exit(
        self, 
        pair: str, 
        trade: Trade, 
        current_time: datetime, 
        current_rate: float, 
        current_profit: float, 
        **kwargs
    ) -> str | None:
        """
        Custom exit logic for additional profit taking.
        """
        # Take profit at configured level
        if current_profit >= self.take_profit.value:
            return "take_profit"
        
        # Exit if RSI becomes overbought and we have some profit
        if current_profit > 0.01 and trade.calc_profit_ratio(current_rate) > 0.005:
            # Get current RSI value (simplified - in practice you'd get this from the dataframe)
            return "rsi_overbought_profit"
        
        return None

    # =============================================================================
    # PLOT CONFIGURATION
    # =============================================================================

    plot_config = {
        "main_plot": {
            "ema_20": {"color": "blue"},
            "ema_50": {"color": "orange"},
            "bb_upperband": {"color": "red"},
            "bb_lowerband": {"color": "red"},
        },
        "subplots": {
            "RSI": {
                "rsi": {"color": "purple"},
            },
            "MACD": {
                "macd": {"color": "blue"},
                "macdsignal": {"color": "red"},
                "macdhist": {"type": "bar", "plotly": {"opacity": 0.9}},
            },
        },
    }

    # =============================================================================
    # STRATEGY INFORMATION
    # =============================================================================

    def informative_pairs(self):
        """
        Define additional informative pairs if needed.
        """
        return []

    def leverage(self, pair: str, current_time: datetime, current_rate: float, 
                 proposed_leverage: float, max_leverage: float, entry_tag: str | None, 
                 side: str, **kwargs) -> float:
        """
        Conservative leverage - no leverage for spot trading.
        """
        return 1.0  # No leverage for conservative approach


# =============================================================================
# CONFIGURATION EXAMPLE
# =============================================================================

"""
Example configuration for conservative position sizing:

{
    "strategy": "Github_Eksz3lsi0r_DOLLAMNY__conservative_position_sizing_strategy__20250916_061219",
    "max_open_trades": 2,
    "stake_amount": "unlimited",  # Will be overridden by custom_stake_amount
    "stake_currency": "USDT",
    "tradable_balance_ratio": 0.95,
    "dry_run_wallet": 1000,  # $1000 starting balance
    "timeframe": "5m",
    "stoploss": -0.02,  # 2% stoploss
    "minimal_roi": {
        "0": 0.08,
        "15": 0.04,
        "30": 0.02,
        "60": 0.01,
        "120": 0.0
    },
    "position_adjustment_enable": false,
    "max_entry_position_adjustment": 0
}

Key Features:
- Fixed $100 USD stake per trade
- Maximum 2 simultaneous trades
- No pyramiding (position adjustment disabled)
- Conservative 2% stoploss
- Quick profit taking at 3-8%
- RSI-based entry/exit signals
- Volume and trend filters
"""
