""" MemeHunterFuturesV7_Quantum.py - Estrategia Cuantitativa de Frontera (PB-74). Integra los avances teóricos y empíricos del proyecto: 1. Motor Alfa Asimétrico Short-Only en 4H (desinflación macro y liquidación post-pump). 2. Filtro de Memoria Estacionaria FFD (Diferenciación Fraccionaria d=0.4) para confirmar tendencia sin pérdida de memoria. 3. Dimensionamiento Continuo de Apuestas (Bet Sizing de Marcos López de Prado / Kelly Sigmoidal) en leverage(): m = 2 * Z[ (p - 0.5) / sqrt(p * (1 - p)) ] - 1 -> Escala continua [1.0x - 2.0x]. 4. Hardening Institucional de Producción: - Kill-Switch de equidad total (Drawdown >= 25%). - Guardián de Funding Bleed (< -0.05% por 8h). - Salidas Técnicas como Airbag ante quiebre de estructura y Trailing Stop de Alta Fidelidad (12% / 4%). """ import math from datetime import datetime from typing import Optional import logging from pathlib import Path try: from freqtrade.strategy import IStrategy from freqtrade.persistence import Trade except ImportError: class IStrategy: pass class Trade: pass try: import pandas_ta as ta except ImportError: ta = None from pandas import DataFrame import numpy as np import scipy.stats as ss LOG = logging.getLogger(__name__) def calculate_ffd_approx(close_series: np.ndarray, d: float = 0.40, lags: int = 30) -> np.ndarray: """Aproximación vectorizada de FFD para generación de indicadores en tiempo real.""" weights = [1.0] for k in range(1, lags): w_k = -weights[-1] / k * (d - k + 1) weights.append(w_k) weights = np.array(weights[::-1]) n = len(close_series) ffd = np.full(n, np.nan) for i in range(lags - 1, n): ffd[i] = np.dot(weights, close_series[i - lags + 1 : i + 1]) return ffd def continuous_leverage_calc(conviction_score: float, min_lev: float = 1.0, max_lev: float = 2.0) -> float: """ Traduce la convicción estocástica [0.0, 1.0] a apalancamiento continuo mediante la fórmula sigmoidal normal de López de Prado. """ # Mapear score de 0.0-1.0 a probabilidad p en [0.50, 0.95] p = 0.50 + 0.45 * min(1.0, max(0.0, conviction_score)) if p <= 0.51: return min_lev denom = math.sqrt(p * (1.0 - p)) z = (p - 0.5) / denom m = 2.0 * float(ss.norm.cdf(z)) - 1.0 # m in [0, 1] lev = min_lev + m * (max_lev - min_lev) return round(float(lev), 2) class MemeHunterFuturesV7_Quantum(IStrategy): INTERFACE_VERSION = 3 timeframe = '4h' can_short = True minimal_roi = { "0": 0.602, "1704": 0.141, "2255": 0.047, "7483": 0 } stoploss = -0.10 trailing_stop = True trailing_stop_positive = 0.040 trailing_stop_positive_offset = 0.120 trailing_only_offset_is_reached = True process_only_new_candles = True startup_candle_count: int = 200 order_types = { 'entry': 'limit', 'exit': 'market', # Salidas forzadas como market para evitar no-fills en colapsos 'stoploss': 'market', 'stoploss_on_exchange': False, 'stoploss_on_exchange_interval': 60 } order_time_in_force = { 'entry': 'GTC', 'exit': 'GTC' } def leverage(self, pair: str, current_time: datetime, current_rate: float, proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, **kwargs) -> float: """ Dimensionamiento continuo de apalancamiento (Bet Sizing Institucional). Calcula el apalancamiento exacto [1.0x - 2.0x] según el score de convicción del setup. """ # Ponderación continua según confluencia de factores if entry_tag == 'short_trapped_auction': score = 0.90 # Confluencia máxima: trampa de compradores + absorción en techo elif entry_tag == 'short_structure_shift': score = 0.70 # Confluencia alta: quiebre de estructura post-pump elif entry_tag == 'short_macro_breakdown': score = 0.55 # Confluencia base: desinflación macro bajo EMA200 else: score = 0.50 lev = continuous_leverage_calc(score, min_lev=1.0, max_lev=2.0) return min(lev, max_leverage) def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # 1. Medias Exponenciales Macro dataframe['ema_50'] = ta.ema(dataframe['close'], length=50) dataframe['ema_200'] = ta.ema(dataframe['close'], length=200) dataframe['dist_ema_50'] = (dataframe['close'] - dataframe['ema_50']) / dataframe['ema_50'] # 2. Osciladores de Momentum dataframe['rsi'] = ta.rsi(dataframe['close'], length=14) macd = ta.macd(dataframe['close'], fast=12, slow=26, signal=9) dataframe['macd'] = macd['MACD_12_26_9'] dataframe['macdsignal'] = macd['MACDs_12_26_9'] dataframe['macdhist'] = macd['MACDh_12_26_9'] dataframe['atr'] = ta.atr(dataframe['high'], dataframe['low'], dataframe['close'], length=14) # 3. Diferenciación Fraccionaria FFD (d=0.40) para memoria estacionaria close_arr = dataframe['close'].values dataframe['ffd_close'] = calculate_ffd_approx(close_arr, d=0.40, lags=30) dataframe['ffd_momentum'] = dataframe['ffd_close'].diff() # 4. Microestructura: Detección de Participantes Atrapados (Trapped Longs) upper_wick = dataframe['high'] - dataframe[['close', 'open']].max(axis=1) candle_body = (dataframe['close'] - dataframe['open']).abs() candle_range = dataframe['high'] - dataframe['low'] vol_sma20 = ta.sma(dataframe['volume'], length=20) vol_ratio = dataframe['volume'] / vol_sma20.replace(0, np.nan) is_wick_dominant = (upper_wick >= candle_body * 1.8) & (upper_wick >= candle_range * 0.45) is_volume_spike = vol_ratio >= 1.4 is_closing_weak = dataframe['close'] <= (dataframe['low'] + candle_range * 0.35) dataframe['trapped_longs_signal'] = (is_wick_dominant & is_volume_spike & is_closing_weak).astype(int) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[:, 'enter_long'] = 0 dataframe.loc[:, 'enter_short'] = 0 # Setup 1: Absorción de Techo y Liquidación de Longs Atrapados short_trapped_conditions = ( (dataframe['trapped_longs_signal'].rolling(window=3).max() == 1) & (dataframe['close'] < dataframe['low'].shift(1)) & (dataframe['macdhist'] < dataframe['macdhist'].shift(1)) & (dataframe['volume'] > 0) ) dataframe.loc[short_trapped_conditions, 'enter_short'] = 1 dataframe.loc[short_trapped_conditions, 'enter_tag'] = 'short_trapped_auction' # Setup 2: Transición de Estructura Post-Pump (Agotamiento de Impulso) short_structure_shift = ( (dataframe['dist_ema_50'].rolling(window=3).max() >= 0.12) & (dataframe['close'] < dataframe['low'].shift(1)) & (dataframe['rsi'] < 68) & (dataframe['rsi'].shift(1) >= 65) & (dataframe['macdhist'] < dataframe['macdhist'].shift(1)) & (dataframe['volume'] > 0) ) dataframe.loc[short_structure_shift, 'enter_short'] = 1 dataframe.loc[short_structure_shift, 'enter_tag'] = 'short_structure_shift' # Setup 3: Desinflación Macro Confirmada por FFD short_macro_conditions = ( (dataframe['close'] < dataframe['ema_200']) & (dataframe['macd'] < dataframe['macdsignal']) & (dataframe['macd'] < 0) & (dataframe['rsi'] <= 44) & (dataframe['rsi'] >= 22) & (dataframe['ffd_momentum'] <= 0) & # Confirmación de memoria estacionaria FFD (dataframe['volume'] > 0) ) dataframe.loc[short_macro_conditions, 'enter_short'] = 1 dataframe.loc[short_macro_conditions, 'enter_tag'] = 'short_macro_breakdown' return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[:, 'exit_long'] = 0 dataframe.loc[:, 'exit_short'] = 0 # Salidas Técnicas como amortiguador (Airbag ante invalidación estructural) exit_short = ( (dataframe['close'] > dataframe['ema_200']) | ((dataframe['macd'] > dataframe['macdsignal']) & (dataframe['rsi'] > 58)) ) dataframe.loc[exit_short, 'exit_short'] = 1 return dataframe