# source: https://raw.githubusercontent.com/IvPalmer/Master-Trader/c7955a15d4bfe82e567bc2bd315a372000a442fd/ft_userdata/strategy_lab/exporter.py
# directory_url: https://github.com/IvPalmer/Master-Trader/blob/main/ft_userdata/strategy_lab/
# User: IvPalmer
# Repository: Master-Trader
# --------------------"""
Freqtrade strategy exporter.

Generates deployable .py strategy files from winning signal combos.
"""

import json
import re
from pathlib import Path

from strategy_lab.signals import EXIT_PROFILES


def export_strategy(combo, rank: int, output_dir: Path) -> Path:
    """Export a signal combo as a Freqtrade strategy .py file."""
    class_name = f"LabStrategy_{rank:03d}"
    exit_params = EXIT_PROFILES[combo.exit_profile]

    # Parse entry description to determine which indicators/signals are needed
    entry_desc = combo.entry_desc
    gate_desc = combo.gate_desc

    indicators = _detect_indicators(entry_desc)
    btc_indicators = _detect_btc_indicators(gate_desc)
    entry_conditions = _build_entry_conditions(entry_desc)
    gate_conditions = _build_gate_conditions(gate_desc)

    roi_str = json.dumps(exit_params["minimal_roi"], indent=8)
    # Convert to Python dict format
    roi_str = roi_str.replace('"', "'")

    strategy_code = f'''import logging
from freqtrade.strategy import IStrategy, informative
from pandas import DataFrame
import numpy as np
import pandas as pd

logger = logging.getLogger(__name__)


class Github_IvPalmer_Master_Trader__exporter__20260923_012354(IStrategy):
    """
    Auto-generated by Strategy Lab.
    Combo: {combo.label}
    """
    INTERFACE_VERSION: int = 3

    minimal_roi = {roi_str}

    stoploss = {exit_params["stoploss"]}
    trailing_stop = True
    trailing_stop_positive = {exit_params["trailing_stop_positive"]}
    trailing_stop_positive_offset = {exit_params["trailing_stop_positive_offset"]}
    trailing_only_offset_is_reached = True

    timeframe = '1h'
    process_only_new_candles = True
    use_exit_signal = False
    exit_profit_only = {exit_params["exit_profit_only"]}
    exit_profit_offset = {exit_params["exit_profit_offset"]}
    startup_candle_count = 200

    @informative('1h', 'BTC/{{stake}}')
    def populate_indicators_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
{_generate_btc_indicators(btc_indicators)}
        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
{_generate_pair_indicators(indicators)}
{_generate_btc_gate_columns(gate_desc)}
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
{entry_conditions}
{gate_conditions}
                (dataframe['volume'] > 0)
            ),
            'enter_long'] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Exits handled by ROI, stoploss, and trailing stop
        return dataframe

{_generate_helpers(indicators)}
'''

    output_path = output_dir / f"Github_IvPalmer_Master_Trader__exporter__20260923_012354.py"
    with open(output_path, "w") as f:
        f.write(strategy_code)

    return output_path


def _detect_indicators(entry_desc: str) -> set:
    """Detect which indicators are needed from the entry description."""
    indicators = set()
    if "st(" in entry_desc or "st_all(" in entry_desc:
        indicators.add("supertrend")
        # Extract params
        for match in re.finditer(r'st(?:_all)?\(([^)]+)\)', entry_desc):
            indicators.add(f"supertrend_params:{match.group(1)}")
    if "ema(" in entry_desc:
        indicators.add("ema")
        match = re.search(r'ema\((\d+),(\d+)\)', entry_desc)
        if match:
            indicators.add(f"ema_params:{match.group(1)},{match.group(2)}")
    if "rsi(" in entry_desc:
        indicators.add("rsi")
    if "macd" in entry_desc:
        indicators.add("macd")
    if "bb(" in entry_desc:
        indicators.add("bollinger")
        match = re.search(r'bb\((\d+),([\d.]+)\)', entry_desc)
        if match:
            indicators.add(f"bb_params:{match.group(1)},{match.group(2)}")
    if "vol(" in entry_desc:
        indicators.add("volume_sma")
    if "adx(" in entry_desc:
        indicators.add("adx")
    if "stoch(" in entry_desc:
        indicators.add("stochastic")
    return indicators


def _detect_btc_indicators(gate_desc: str) -> set:
    indicators = set()
    if "sma50" in gate_desc:
        indicators.add("sma50")
    if "sma200" in gate_desc:
        indicators.add("sma200")
    if "rsi" in gate_desc:
        indicators.add("rsi")
    if "nc" in gate_desc:
        indicators.add("close")  # Need close for crash detection
    return indicators


def _generate_btc_indicators(indicators: set) -> str:
    lines = []
    if "sma50" in indicators:
        lines.append("        dataframe['sma50'] = dataframe['close'].rolling(50).mean()")
    if "sma200" in indicators:
        lines.append("        dataframe['sma200'] = dataframe['close'].rolling(200).mean()")
    if "rsi" in indicators:
        lines.append("        dataframe['rsi'] = _rsi(dataframe['close'], 14)")
    if not lines:
        lines.append("        pass")
    return "\n".join(lines)


def _generate_pair_indicators(indicators: set) -> str:
    lines = []

    for ind in indicators:
        if ind.startswith("supertrend_params:"):
            params = ind.split(":")[1]
            if "+" in params:
                # Multiple supertrend bands: st_all(4,8+1,8)
                for pair_str in params.split("+"):
                    m, p = pair_str.strip().split(",")
                    lines.append(f"        dataframe = self._supertrend(dataframe, {m}, {p})")
            else:
                parts = params.split(",")
                if len(parts) == 2:
                    lines.append(f"        dataframe = self._supertrend(dataframe, {parts[0]}, {parts[1]})")

    for ind in indicators:
        if ind.startswith("ema_params:"):
            fast, slow = ind.split(":")[1].split(",")
            lines.append(f"        dataframe['ema_{fast}'] = dataframe['close'].ewm(span={fast}, adjust=False).mean()")
            lines.append(f"        dataframe['ema_{slow}'] = dataframe['close'].ewm(span={slow}, adjust=False).mean()")

    if "rsi" in indicators:
        lines.append("        dataframe['rsi'] = _rsi(dataframe['close'], 14)")

    if "macd" in indicators:
        lines.append("        ema12 = dataframe['close'].ewm(span=12, adjust=False).mean()")
        lines.append("        ema26 = dataframe['close'].ewm(span=26, adjust=False).mean()")
        lines.append("        dataframe['macd'] = ema12 - ema26")
        lines.append("        dataframe['macdsignal'] = dataframe['macd'].ewm(span=9, adjust=False).mean()")

    for ind in indicators:
        if ind.startswith("bb_params:"):
            period, std = ind.split(":")[1].split(",")
            lines.append(f"        bb_sma = dataframe['close'].rolling({period}).mean()")
            lines.append(f"        bb_std = dataframe['close'].rolling({period}).std()")
            lines.append(f"        dataframe['bb_lower'] = bb_sma - {std} * bb_std")

    if "volume_sma" in indicators:
        lines.append("        dataframe['vol_sma20'] = dataframe['volume'].rolling(20).mean()")

    if "adx" in indicators:
        lines.append("        dataframe['adx'] = _adx(dataframe, 14)")

    if "stochastic" in indicators:
        lines.append("        low_min = dataframe['low'].rolling(14).min()")
        lines.append("        high_max = dataframe['high'].rolling(14).max()")
        lines.append("        dataframe['stoch_k'] = 100 * (dataframe['close'] - low_min) / (high_max - low_min + 1e-10)")

    if not lines:
        lines.append("        pass")
    return "\n".join(lines)


def _generate_btc_gate_columns(gate_desc: str) -> str:
    lines = []
    conditions = []

    if "sma50" in gate_desc:
        conditions.append("(dataframe['btc_usdt_close_1h'] > dataframe['btc_usdt_sma50_1h'])")
    if "sma200" in gate_desc:
        conditions.append("(dataframe['btc_usdt_close_1h'] > dataframe['btc_usdt_sma200_1h'])")
    if "rsi35" in gate_desc:
        conditions.append("(dataframe['btc_usdt_rsi_1h'] > 35)")
    if "nc24" in gate_desc:
        conditions.append("(dataframe['btc_usdt_close_1h'] >= dataframe['btc_usdt_close_1h'].shift(24) * 0.97)")

    if conditions:
        cond_str = " & ".join(conditions)
        lines.append(f"        dataframe['btc_gate'] = ({cond_str}).astype(int)")
    else:
        lines.append("        dataframe['btc_gate'] = 1")

    return "\n".join(lines)


def _build_entry_conditions(entry_desc: str) -> str:
    """Build Freqtrade entry conditions from signal description."""
    conditions = []

    # Supertrend
    for match in re.finditer(r'st\((\d+),(\d+)\)', entry_desc):
        m, p = match.groups()
        conditions.append(f"                (dataframe['st_{m}_{p}'] == 'up') &")

    # EMA crossover
    match = re.search(r'ema\((\d+),(\d+)\)', entry_desc)
    if match:
        fast, slow = match.groups()
        conditions.append(f"                (dataframe['ema_{fast}'] > dataframe['ema_{slow}']) &")
        conditions.append(f"                (dataframe['ema_{fast}'].shift(1) <= dataframe['ema_{slow}'].shift(1)) &")

    # MACD
    if "macd" in entry_desc and "ema" not in entry_desc:
        conditions.append("                (dataframe['macd'] > dataframe['macdsignal']) &")
        conditions.append("                (dataframe['macd'].shift(1) <= dataframe['macdsignal'].shift(1)) &")

    # Bollinger bounce
    match = re.search(r'bb\((\d+),([\d.]+)\)', entry_desc)
    if match:
        conditions.append("                (dataframe['close'] > dataframe['bb_lower']) &")
        conditions.append("                (dataframe['close'].shift(1) <= dataframe['bb_lower'].shift(1)) &")

    # RSI range
    match = re.search(r'rsi\((\d+),(\d+)\)', entry_desc)
    if match:
        low, high = match.groups()
        conditions.append(f"                (dataframe['rsi'] >= {low}) &")
        conditions.append(f"                (dataframe['rsi'] <= {high}) &")

    # Volume spike
    match = re.search(r'vol\(([\d.]+)\)', entry_desc)
    if match:
        mult = match.group(1)
        conditions.append(f"                (dataframe['volume'] > {mult} * dataframe['vol_sma20']) &")

    # ADX trending
    match = re.search(r'adx\((\d+)\)', entry_desc)
    if match:
        threshold = match.group(1)
        conditions.append(f"                (dataframe['adx'] > {threshold}) &")

    # Stochastic
    match = re.search(r'stoch\((\d+)\)', entry_desc)
    if match:
        threshold = match.group(1)
        conditions.append(f"                (dataframe['stoch_k'] > {threshold}) &")
        conditions.append(f"                (dataframe['stoch_k'].shift(1) <= {threshold}) &")

    return "\n".join(conditions)


def _build_gate_conditions(gate_desc: str) -> str:
    return "                (dataframe['btc_gate'] == 1) &\n"


def _generate_helpers(indicators: set) -> str:
    """Generate helper functions needed by the strategy."""
    parts = []

    if "supertrend" in indicators:
        parts.append('''
    def _supertrend(self, df, multiplier, period):
        import talib.abstract as ta
        high = df['high'].values
        low = df['low'].values
        close = df['close'].values
        length = len(df)
        tr = pd.concat([df['high']-df['low'], (df['high']-df['close'].shift(1)).abs(), (df['low']-df['close'].shift(1)).abs()], axis=1).max(axis=1)
        atr = tr.rolling(period).mean().values
        basic_ub = (high + low) / 2 + multiplier * atr
        basic_lb = (high + low) / 2 - multiplier * atr
        final_ub = np.zeros(length)
        final_lb = np.zeros(length)
        for i in range(period, length):
            final_ub[i] = basic_ub[i] if basic_ub[i] < final_ub[i-1] or close[i-1] > final_ub[i-1] else final_ub[i-1]
            final_lb[i] = basic_lb[i] if basic_lb[i] > final_lb[i-1] or close[i-1] < final_lb[i-1] else final_lb[i-1]
        st = np.zeros(length)
        for i in range(period, length):
            if st[i-1] == final_ub[i-1]:
                st[i] = final_ub[i] if close[i] <= final_ub[i] else final_lb[i]
            elif st[i-1] == final_lb[i-1]:
                st[i] = final_lb[i] if close[i] >= final_lb[i] else final_ub[i]
        col = f"st_{multiplier}_{period}"
        df[col] = np.where(st > 0, np.where(close < st, 'down', 'up'), 'none')
        return df''')

    # Always include _rsi and _adx as module-level functions since they may be needed
    parts.append('''

def _rsi(close, period=14):
    delta = close.diff()
    gain = delta.clip(lower=0)
    loss = (-delta).clip(lower=0)
    avg_gain = gain.ewm(alpha=1/period, min_periods=period).mean()
    avg_loss = loss.ewm(alpha=1/period, min_periods=period).mean()
    rs = avg_gain / (avg_loss + 1e-10)
    return 100 - (100 / (1 + rs))


def _adx(df, period=14):
    high, low, close = df['high'], df['low'], df['close']
    plus_dm = high.diff()
    minus_dm = -low.diff()
    plus_dm = plus_dm.where((plus_dm > minus_dm) & (plus_dm > 0), 0.0)
    minus_dm = minus_dm.where((minus_dm > plus_dm) & (minus_dm > 0), 0.0)
    tr = pd.concat([high-low, (high-close.shift(1)).abs(), (low-close.shift(1)).abs()], axis=1).max(axis=1)
    atr = tr.rolling(period).mean()
    plus_di = 100 * (plus_dm.ewm(alpha=1/period, min_periods=period).mean() / (atr + 1e-10))
    minus_di = 100 * (minus_dm.ewm(alpha=1/period, min_periods=period).mean() / (atr + 1e-10))
    dx = 100 * (plus_di - minus_di).abs() / (plus_di + minus_di + 1e-10)
    return dx.ewm(alpha=1/period, min_periods=period).mean()''')

    return "\n".join(parts)

    # Legacy function name kept for compatibility
    _generate_supertrend_helper = _generate_helpers
    _dummy = '''
    def _supertrend(self, df, multiplier, period):
        import talib.abstract as ta
        high = df['high'].values
        low = df['low'].values
        close = df['close'].values
        length = len(df)
        tr = pd.concat([df['high']-df['low'], (df['high']-df['close'].shift(1)).abs(), (df['low']-df['close'].shift(1)).abs()], axis=1).max(axis=1)
        atr = tr.rolling(period).mean().values
        basic_ub = (high + low) / 2 + multiplier * atr
        basic_lb = (high + low) / 2 - multiplier * atr
        final_ub = np.zeros(length)
        final_lb = np.zeros(length)
        for i in range(period, length):
            final_ub[i] = basic_ub[i] if basic_ub[i] < final_ub[i-1] or close[i-1] > final_ub[i-1] else final_ub[i-1]
            final_lb[i] = basic_lb[i] if basic_lb[i] > final_lb[i-1] or close[i-1] < final_lb[i-1] else final_lb[i-1]
        st = np.zeros(length)
        for i in range(period, length):
            if st[i-1] == final_ub[i-1]:
                st[i] = final_ub[i] if close[i] <= final_ub[i] else final_lb[i]
            elif st[i-1] == final_lb[i-1]:
                st[i] = final_lb[i] if close[i] >= final_lb[i] else final_ub[i]
        col = f"st_{multiplier}_{period}"
        df[col] = np.where(st > 0, np.where(close < st, 'down', 'up'), 'none')
        return df


def _rsi(close, period=14):
    delta = close.diff()
    gain = delta.clip(lower=0)
    loss = (-delta).clip(lower=0)
    avg_gain = gain.ewm(alpha=1/period, min_periods=period).mean()
    avg_loss = loss.ewm(alpha=1/period, min_periods=period).mean()
    rs = avg_gain / (avg_loss + 1e-10)
    return 100 - (100 / (1 + rs))


def _adx(df, period=14):
    high, low, close = df['high'], df['low'], df['close']
    plus_dm = high.diff()
    minus_dm = -low.diff()
    plus_dm = plus_dm.where((plus_dm > minus_dm) & (plus_dm > 0), 0.0)
    minus_dm = minus_dm.where((minus_dm > plus_dm) & (minus_dm > 0), 0.0)
    tr = pd.concat([high-low, (high-close.shift(1)).abs(), (low-close.shift(1)).abs()], axis=1).max(axis=1)
    atr = tr.rolling(period).mean()
    plus_di = 100 * (plus_dm.ewm(alpha=1/period, min_periods=period).mean() / (atr + 1e-10))
    minus_di = 100 * (minus_dm.ewm(alpha=1/period, min_periods=period).mean() / (atr + 1e-10))
    dx = 100 * (plus_di - minus_di).abs() / (plus_di + minus_di + 1e-10)
    return dx.ewm(alpha=1/period, min_periods=period).mean()
'''
