# source: https://raw.githubusercontent.com/DOUGLASGUEDESATRIA/OSIRIS_TRADE/fe3c09567f3798d1cad6d7a306fa4f6131d91e17/user_data/strategies/OsirisDNA.py
"""
Github_DOUGLASGUEDESATRIA_OSIRIS_TRADE__OsirisDNA__20260329_060843 v4 — Session Flow with Flat SL
==========================================
v3 confirmed: the session direction signal WORKS (53.5% WR confirmed)
but graduated/trailing SL was killing good trades (79/106 exits were
premature trailing_stop_loss).

v4 FIX: FLAT stoploss — no trailing, no graduation. Let the session
develop its full move without premature tightening.

KEY INSIGHT from hyperopt:
  - TP exits: 13 trades, 100% WR, +1.84% avg ← signal is profitable
  - trailing_stop_loss: 79 trades, 10% WR ← SL management destroying edge

v4 APPROACH:
  - Flat SL (no custom_stoploss — just use strategy stoploss)
  - TP via custom_exit
  - Timeout via custom_exit
  - EOD close
  - ATR-multiple SL/TP option via stoploss parameter

MATH (at 1:1 R:R with 53.5% WR):
  EV = 0.535 * TP - 0.465 * SL = 0.07 * TP per trade
  At TP=SL=1.0%, 2x leverage: 0.14% per trade per day
  200 trading days: 28% annual
"""

import logging
from datetime import datetime
from pandas import DataFrame
import numpy as np

from freqtrade.persistence import Trade
from freqtrade.strategy import (
    IStrategy,
    DecimalParameter,
    IntParameter,
)
import talib.abstract as ta

logger = logging.getLogger(__name__)


class Github_DOUGLASGUEDESATRIA_OSIRIS_TRADE__OsirisDNA__20260329_060843(IStrategy):
    INTERFACE_VERSION = 3
    can_short = True

    timeframe = '15m'
    startup_candle_count = 200
    process_only_new_candles = True

    # FLAT stoploss — no custom_stoploss, no trailing
    stoploss = -0.03  # 3% account max (with 2x leverage = 1.5% price move)
    use_custom_stoploss = False
    trailing_stop = False
    minimal_roi = {"0": 100}

    # === SESSION FLOW params ===
    london_thresh = DecimalParameter(0.05, 1.00, default=0.10, decimals=2,
                                      space='buy', optimize=True, load=False)
    ny_entry_hour = IntParameter(12, 14, default=14, space='buy', optimize=True)

    # TP as percentage (applies before leverage)
    sess_tp_pct = DecimalParameter(0.30, 3.00, default=1.51, decimals=2,
                                    space='sell', optimize=True)

    # Max hold in 15m candles (48 = 12h)
    sess_max_hold = IntParameter(8, 48, default=32, space='sell', optimize=True)

    # Leverage
    sess_leverage = IntParameter(2, 5, default=2, space='buy', optimize=True)

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['hour'] = dataframe['date'].dt.hour
        dataframe['minute'] = dataframe['date'].dt.minute
        dataframe['weekday'] = dataframe['date'].dt.weekday

        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
        dataframe['atr_pct'] = dataframe['atr'] / dataframe['close'] * 100
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)

        # EMA200 for trend context
        dataframe['ema200'] = ta.EMA(dataframe, timeperiod=200)

        # London session return tracking
        hours = dataframe['hour'].values
        minutes = dataframe['minute'].values
        closes = dataframe['close'].values
        opens = dataframe['open'].values

        london_rets = np.zeros(len(dataframe))
        london_base = np.nan

        for i in range(len(dataframe)):
            h, m = int(hours[i]), int(minutes[i])
            if h == 8 and m == 0:
                london_base = opens[i]
            if not np.isnan(london_base) and london_base > 0:
                london_rets[i] = (closes[i] - london_base) / london_base * 100
            if h == 22 and m == 0:
                london_base = np.nan

        dataframe['london_ret'] = london_rets

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[:, 'enter_long'] = 0
        dataframe.loc[:, 'enter_short'] = 0
        dataframe.loc[:, 'enter_tag'] = ''

        entry_h = self.ny_entry_hour.value
        thresh = self.london_thresh.value

        weekday = dataframe['weekday'] <= 4
        ny_candle = (dataframe['hour'] == entry_h) & (dataframe['minute'] == 0)

        london_up = dataframe['london_ret'] > thresh
        london_dn = dataframe['london_ret'] < -thresh

        # Session continuation: LONG ONLY
        # DNA evidence: longs +29.18% over 2.2y, shorts -33.68%
        # BTC has natural long bias in session flow
        dataframe.loc[weekday & ny_candle & london_up, 'enter_long'] = 1
        dataframe.loc[weekday & ny_candle & london_up, 'enter_tag'] = 'sess_long'

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[:, 'exit_long'] = 0
        dataframe.loc[:, 'exit_short'] = 0
        return dataframe

    def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
                    current_rate: float, current_profit: float,
                    **kwargs) -> str | bool:

        tp = self.sess_tp_pct.value / 100
        hours_held = (current_time - trade.open_date_utc).total_seconds() / 3600

        # Take profit
        if current_profit >= tp:
            return 'tp'

        # Cut losers: if still losing >2% after 3h, session thesis failed
        if hours_held >= 3.0 and current_profit < -0.02:
            return 'cut_loss_3h'

        # Reduce further: if losing >1% after 5h, cut
        if hours_held >= 5.0 and current_profit < -0.01:
            return 'cut_loss_5h'

        # Max hold timeout
        if hours_held >= 8.0:
            return 'timeout_profit' if current_profit > 0 else 'timeout_loss'

        # EOD close
        if current_time.hour >= 21:
            if current_profit > 0.001:
                return 'eod_profit'
            return 'eod_close'

        return False

    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:
        return min(float(self.sess_leverage.value), max_leverage)
