Transclude of Minervini_Toolkit.py

Descrizione e Logica

Questa strategia non esegue operazioni di compravendita automatica, ma agisce come una Dashboard Visiva Avanzata sovrapposta al grafico dei prezzi. È progettata per permettere al trader di valutare a colpo d’occhio la “salute” tecnica di un titolo secondo i rigidi criteri dei Campioni di Borsa (SEPA method).

Il toolkit combina tre strumenti analitici distinti in un unico overlay:

  1. Trend Template (Il Semaforo Verde): Verifica se il titolo si trova in una “Fase 2” (Trend Primario Rialzista). Le condizioni verificate includono l’allineamento delle medie mobili (Prezzo > 50 > 150 > 200), la pendenza della media a 200 giorni, la posizione rispetto ai massimi/minimi a 52 settimane e la forza relativa (proxy RSI).
  2. Extended Detector (Il Semaforo Rosso): Identifica quando il prezzo si è allontanato troppo velocemente dalla sua media mobile a breve termine (EMA 10), una condizione statistica che spesso precede un ritracciamento o una pausa (“Mean Reversion”).
  3. Pivot Points (Struttura del Prezzo): Identifica e scrive sul grafico i prezzi esatti dei massimi e minimi di swing rilevanti, aiutando a tracciare basi, resistenze e supporti.

Come si Utilizza

Questo strumento è fondamentale nella fase di Selezione e Screening (Watchlist Review) o nell’Analisi Discrezionale (Arena/Backtest Lab).

  • Analisi del Trend: Se lo sfondo del grafico non è colorato di Verde, il titolo non soddisfa i requisiti minimi per un investimento trend-following di qualità istituzionale.
  • Timing di Ingresso: Evitare di acquistare quando lo sfondo è Rosso (Extended). Anche se il trend è forte, il rischio di acquistare un massimo locale è elevato. Attendere che il prezzo rientri verso la EMA 10 o la SMA 50.
  • Analisi Strutturale: Utilizzare i numeri dei Pivot Points per identificare pattern grafici come Cup with Handle, VCP o doppi minimi.

Parametri di Input

Configurazione Medie Mobili

  • Show Moving Averages: Attiva/Disattiva le linee delle medie.
  • SMA 50 / 150 / 200 Length: Periodi per le medie a medio e lungo termine.

Trend Template (Sfondo Verde)

  • Show Trend Template: Attiva la colorazione verde dello sfondo se le condizioni sono soddisfatte.
  • Min RSI (RS Proxy): Valore minimo di RSI (default 70) usato come proxy per la Forza Relativa (RS Ranking) per confermare la leadership del titolo.

Extended Detector (Sfondo Rosso)

  • Show Extended: Attiva la colorazione rossa dello sfondo per eccessi di prezzo.
  • Extended ATR Mult: La distanza dalla EMA 10, misurata in multipli dell’ATR, necessaria per considerare il prezzo “esteso” (default: 2.1).

Pivot Points

  • Show Pivot Points: Attiva la visualizzazione numerica dei massimi/minimi.
  • Pivot Lookback: Numero di barre a sinistra e destra per confermare un pivot (default: 9).

Interpretazione Grafica (Overlay)

Il grafico si arricchisce di diversi layer informativi:

  1. Colorazione Sfondo (Zone):

    • 🟩 Sfondo Verde Trasparente: Il titolo è in Stage 2 confermato. È un candidato all’acquisto (“Green Light”).
    • 🟥 Sfondo Rosso Trasparente: Il titolo è Esteso (Overextended). Pericolo di pullback (“Red Light”).
    • Sfondo Neutro: Il titolo è in fase di consolidamento, accumulazione o trend ribassista (Stage 1, 3 o 4).
  2. Linee Indicatori:

    • 🟠 Linea Arancione: EMA 10 (Trend brevissimo termine / Power Trend).
    • 🔵 Linea Blu: SMA 50 (Supporto Istituzionale).
    • 🟢 Linea Lime: SMA 150.
    • 🔴 Linea Rossa: SMA 200 (Trend Primario).
  3. Etichette Testuali:

    • Numeri bianchi sopra/sotto le candele indicano i prezzi esatti dei Pivot Highs e Pivot Lows confermati.

# VERSION: v1.0 - fire/strategies/standard_library/Minervini_Toolkit.py
 
# RESP: Implementazione Visuale del "Mark Minervini Toolkit" (Trend Template, Pivots, MAs).
 
# LOGICA: Identifica Stage 2 (Trend) e Punti di Svolta (Pivots).
 
# DEPS: pandas, numpy, fire.strategies.base_strategy
 
# ARCH_ROLE: feature (strategy/indicator)
 
# LAYER: fire-app
 
  
 
import pandas as pd
 
import numpy as np
 
from fire.strategies.base_strategy import BaseStrategy
 
  
 
class Minervini_Toolkit(BaseStrategy):
 
    name = "Toolkit: Mark Minervini (Visual)"
 
    description = (
 
        "Suite completa di analisi visiva basata su Mark Minervini. "
 
        "Include: Medie Mobili Chiave, Trend Template (Sfondo Verde), "
 
        "Extended Detector (Sfondo Rosso) e Pivot Points (High/Low)."
 
    )
 
  
 
    def __init__(self):
 
        super().__init__()
 
        # --- INPUT MEDIE MOBILI ---
 
        self.add_parameter("show_ma", True, bool, label="Show Moving Averages")
 
        self.add_parameter("ma50_len", 50, int, label="SMA 50 Length")
 
        self.add_parameter("ma150_len", 150, int, label="SMA 150 Length")
 
        self.add_parameter("ma200_len", 200, int, label="SMA 200 Length")
 
  
 
        # --- INPUT TREND TEMPLATE ---
 
        self.add_parameter("show_template", True, bool, label="Show Trend Template (Bg)")
 
        self.add_parameter("rs_proxy_rsi", 70, int, label="Min RSI (RS Proxy)")
 
  
 
        # --- INPUT EXTENDED DETECTOR ---
 
        self.add_parameter("show_extended", True, bool, label="Show Extended (Bg)")
 
        self.add_parameter("ext_threshold", 2.1, float, label="Extended ATR Mult")
 
  
 
        # --- INPUT PIVOTS ---
 
        self.add_parameter("show_pivots", True, bool, label="Show Pivot Points")
 
        self.add_parameter("pivot_len", 9, int, label="Pivot Lookback")
 
  
 
    def init(self):
 
        """Calcolo Vettoriale Completo."""
 
        close = self.data['Close']
 
        high = self.data['High']
 
        low = self.data['Low']
 
        # 1. Medie Mobili
 
        self.ema10 = close.ewm(span=10, adjust=False).mean()
 
        self.sma50 = close.rolling(window=self.params['ma50_len']).mean()
 
        self.sma150 = close.rolling(window=self.params['ma150_len']).mean()
 
        self.sma200 = close.rolling(window=self.params['ma200_len']).mean()
 
  
 
        # 2. Plotting Medie
 
        if self.params['show_ma']:
 
            self.plot(self.ema10, "EMA 10", color="orange", width=1)
 
            self.plot(self.sma50, "SMA 50", color="#2C8AD8", width=2)   # Blue
 
            self.plot(self.sma150, "SMA 150", color="#00E676", width=2) # Lime
 
            self.plot(self.sma200, "SMA 200", color="#FF5252", width=2) # Red
 
  
 
        # 3. Calcolo Trend Template (Minervini Criteria)
 
        # Condition 1: Price > 150 > 200
 
        c1 = (close > self.sma150) & (self.sma150 > self.sma200)
 
        # Condition 2: 200 SMA Rising (almeno da 20 barre)
 
        sma200_prev = self.sma200.shift(20)
 
        c2 = self.sma200 > sma200_prev
 
        # Condition 3: Price > 50 SMA
 
        c3 = close > self.sma50
 
        # Condition 4: 52-Week High/Low
 
        high_52 = high.rolling(window=250).max()
 
        low_52 = low.rolling(window=250).min()
 
        # Price > 30% above 52w low
 
        c4 = close >= (low_52 * 1.30)
 
        # Price within 25% of 52w high (Price > 75% of High)
 
        c5 = close >= (high_52 * 0.75)
 
        # RS Proxy (RSI)
 
        delta = close.diff()
 
        gain = (delta.where(delta > 0, 0)).rolling(14).mean()
 
        loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
 
        rs = gain / loss
 
        rsi = 100 - (100 / (1 + rs))
 
        c6 = rsi >= self.params['rs_proxy_rsi']
 
  
 
        self.trend_template_active = c1 & c2 & c3 & c4 & c5 & c6
 
  
 
        # 4. Calcolo Extended Detector
 
        # Distanza High da EMA10 > N * ATR
 
        prev_c = close.shift(1)
 
        tr = pd.concat([high - low, (high - prev_c).abs(), (low - prev_c).abs()], axis=1).max(axis=1)
 
        atr = tr.rolling(14).mean()
 
        dist = high - self.ema10
 
        self.is_extended = dist > (atr * self.params['ext_threshold'])
 
  
 
        # 5. Calcolo Pivot Points (Swing Highs / Lows)
 
        # Un massimo è un pivot se è il più alto di N barre prima e N barre dopo.
 
        # In backtest (vettoriale) possiamo guardare "avanti" usando shift negativo.
 
        p_len = self.params['pivot_len']
 
        # Rotoliamo su una finestra di (p_len * 2 + 1) centrata
 
        # Nota: rolling(center=True) guarda avanti.
 
        window = p_len * 2 + 1
 
        local_max = high.rolling(window=window, center=True).max()
 
        local_min = low.rolling(window=window, center=True).min()
 
        # Identifichiamo dove High == LocalMax (Pivot High)
 
        self.pivot_highs = (high == local_max) & (high > 0)
 
        # Identifichiamo dove Low == LocalMin (Pivot Low)
 
        self.pivot_lows = (low == local_min) & (low > 0)
 
  
 
    def next(self):
 
        # 1. Gestione Background (Trend Template & Extended)
 
        if self.params['show_extended'] and self.is_extended.iloc[self.i]:
 
            # Rosso Trasparente (Extended - Pericolo)
 
            self.bgcolor("rgba(255, 82, 82, 0.2)")
 
        elif self.params['show_template'] and self.trend_template_active.iloc[self.i]:
 
            # Verde Trasparente (Trend Template - Safe Zone)
 
            self.bgcolor("rgba(0, 230, 118, 0.15)")
 
  
 
        # 2. Gestione Pivot Points (Visualizzazione Ritardata)
 
        # Poiché un pivot è confermato solo dopo N barre, dobbiamo "disegnare nel passato"?
 
        # In LWC non possiamo aggiungere shape nel passato facilmente barra per barra in real-time mode,
 
        # ma in Backtest Mode (dove abbiamo tutto il dataframe) FIRE gestisce i plot_shape.
 
        # Tuttavia, per simulare il comportamento "live", disegniamo il pivot
 
        # quando viene "confermato" (cioè N barre dopo il picco).
 
        if self.params['show_pivots']:
 
            p_len = self.params['pivot_len']
 
            # Guardiamo indietro di p_len barre per vedere se LÌ c'era un pivot
 
            # Se siamo alla barra 100, e p_len è 9, controlliamo la barra 91.
 
            check_idx = self.i - p_len
 
            if check_idx >= 0:
 
                # Controlliamo se nel dataframe, all'indice check_idx, c'è un pivot
 
                if self.pivot_highs.iloc[check_idx]:
 
                    price = self.data['High'].iloc[check_idx]
 
                    # Disegniamo sulla barra corrente (o proviamo a posizionarlo nel passato se il motore lo supporta?)
 
                    # BaseStrategy.plot_shape usa self.i (corrente).
 
                    # Trick: Inseriamo il testo con il prezzo.
 
                    self.plot_shape(
 
                        style="none", # Solo testo, niente forma geometrica (o arrow_down piccola)
 
                        color="white", # O textcolor specifico
 
                        location="above",
 
                        text=f"{price:.2f}",
 
                        size="small"
 
                        # Nota: Questo apparirà alla barra corrente (N giorni dopo il pivot).
 
                        # È il comportamento corretto per un indicatore non-repainting in real-time.
 
                        # Se volessimo l'effetto "repainting" (disegno sul picco esatto),
 
                        # servirebbe un supporto engine avanzato.
 
                    )
 
                if self.pivot_lows.iloc[check_idx]:
 
                    price = self.data['Low'].iloc[check_idx]
 
                    self.plot_shape(
 
                        style="none",
 
                        color="white",
 
                        location="below",
 
                        text=f"{price:.2f}",
 
                        size="small"
 
                    )