Transclude of Trend_Template_Minervini.py

Descrizione e Logica

Questa strategia codifica le regole fondamentali definite dal trader campione Mark Minervini nel suo libro “Trade Like a Stock Market Wizard”. L’obiettivo non è catturare ogni movimento, ma filtrare il mercato per identificare solo i titoli che si trovano in una fase di Trend Primario Rialzista (Stage 2) confermato.

La logica impone che un trade Long venga aperto solo se tutte le seguenti condizioni sono soddisfatte simultaneamente:

  1. Allineamento Medie: Il prezzo è sopra la SMA 50 > SMA 150 > SMA 200.
  2. Trend a Lungo Termine: La SMA 200 è in ascesa da almeno 1 mese (20 barre).
  3. Forza del Prezzo: Il prezzo attuale è almeno il 30% sopra i minimi a 52 settimane (evita il “bottom fishing”).
  4. Vicinanza ai Massimi: Il prezzo è entro il 25% dai massimi a 52 settimane (il titolo sta “bussando” ai massimi, non è in ipervenduto profondo).
  5. Forza Relativa (RS): Poiché FIRE analizza un asset alla volta, l’RS Ranking (0-99) è simulato tramite un filtro RSI > 60, che garantisce un momentum interno forte.

Logica di Uscita: La strategia chiude la posizione quando il titolo perde il momentum a medio termine, definito come una chiusura del prezzo al di sotto della SMA 50.

Come si Utilizza

Questa è una strategia a bassa frequenza e alta qualità.

  • Target: Ideale per azioni “Growth”, Crypto in Bull Run o ETF settoriali forti.
  • Timeframe: Progettata per il grafico Giornaliero (1D).
  • Aspettativa: Potrebbero passare mesi senza un trade. Quando entra, significa che le probabilità statistiche di un trend sostenuto sono massime. Se il backtest genera 0 trade in un mercato laterale/ribassista (es. 2022), la strategia sta funzionando correttamente (preservazione capitale).

Parametri di Input

Medie Mobili (Trend Definition)

  • MA 50 Length: Media mobile a breve termine (default: 50). Funge anche da Trailing Stop.
  • MA 150 Length: Media mobile a medio termine (default: 150).
  • MA 200 Length: Media mobile a lungo termine (default: 200). Deve essere la “base” del trend.

Filtri Strutturali (Minervini Criteria)

  • MA 200 Rising (Days): Numero minimo di giorni in cui la SMA 200 deve essere stata crescente (default: 20).
  • Min % above 52w Low: Quanto il prezzo deve essersi staccato dai minimi annuali (default: 1.30 = 30%).
  • Min % of 52w High: Quanto il prezzo deve essere vicino ai massimi annuali. Un valore di 0.75 significa che il prezzo deve essere > 75% del massimo assoluto (default: 0.75).
  • Min RSI (RS Proxy): Valore minimo dell’RSI per confermare la forza (default: 60).

Interpretazione Grafica (Overlay)

La strategia disegna sul grafico gli elementi chiave per l’analisi visiva del trend:

  1. Linee Indicatori:

    • 🟩 Linea Verde (SMA 50): Il “guard-rail” del trend. Se il prezzo rompe questa, si esce.
    • 🟧 Linea Arancione (SMA 150): Supporto istituzionale intermedio.
    • 🟥 Linea Rossa (SMA 200): La base del trend a lungo termine.
    • Linea Grigia Tratteggiata: Il livello del massimo a 52 settimane.
  2. Segnali Operativi:

    • 🔼 Triangolo Lime (ENTRY): “Stage 2 Breakout”. Tutte le condizioni sono allineate.
    • 🟠 Cerchio Arancione (EXIT): “Lost SMA 50”. Il momentum si è interrotto.

# VERSION: v1.1 (Fix-API-Compat) - fire/strategies/standard_library/Trend_Template_Minervini.py
 
# RESP: Implementazione del "Trend Template" (Minervini) per identificare Stage 2.
 
# DEPS: pandas, numpy, fire.strategies.base_strategy
 
# CHANGELOG v1.1:
 
#   - FIX: Sostituiti metodi alias 'buy' e 'close' con le API native del framework
 
#     'enter_long' e 'close_long' per compatibilità con la versione corrente di BaseStrategy.
 
  
 
import pandas as pd
 
import numpy as np
 
from fire.strategies.base_strategy import BaseStrategy
 
  
 
class Trend_Template_Minervini(BaseStrategy):
 
    name = "Strategy: Trend Template (Minervini)"
 
    description = (
 
        "Strategia Trend Following basata sui criteri di Mark Minervini (Stage 2). "
 
        "Filtra titoli con forte momentum, medie mobili allineate e vicini ai massimi a 52 settimane. "
 
        "Uscita: Chiusura sotto la SMA 50."
 
    )
 
  
 
    def __init__(self):
 
        super().__init__()
 
        # === Medie Mobili ===
 
        self.add_parameter("ma_50", 50, int, label="MA 50 Length")
 
        self.add_parameter("ma_150", 150, int, label="MA 150 Length (30w)")
 
        self.add_parameter("ma_200", 200, int, label="MA 200 Length (40w)")
 
        # === Criteri di Trend ===
 
        self.add_parameter("ma_200_rising_days", 20, int, label="MA 200 Rising (Days)")
 
        self.add_parameter("min_above_low", 1.30, float, step=0.05, label="Min % above 52w Low")
 
        self.add_parameter("within_below_high", 0.75, float, step=0.05, label="Min % of 52w High")
 
        # === Relative Strength Proxy ===
 
        self.add_parameter("rsi_min", 60, int, label="Min RSI (RS Proxy)")
 
  
 
    def init(self):
 
        """Calcolo vettoriale degli indicatori."""
 
        close = self.data['Close']
 
        low = self.data['Low']
 
        high = self.data['High']
 
        # 1. Medie Mobili
 
        self.sma50 = close.rolling(window=self.params['ma_50']).mean()
 
        self.sma150 = close.rolling(window=self.params['ma_150']).mean()
 
        self.sma200 = close.rolling(window=self.params['ma_200']).mean()
 
        # 2. 52-Week High / Low
 
        self.low_52w = low.rolling(window=252).min()
 
        self.high_52w = high.rolling(window=252).max()
 
        # 3. RSI
 
        delta = close.diff()
 
        gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
 
        loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
 
        rs = gain / loss
 
        self.rsi = 100 - (100 / (1 + rs))
 
  
 
        # --- Visualizzazione ---
 
        self.plot(self.sma50, name="SMA 50", color="green", width=2)
 
        self.plot(self.sma150, name="SMA 150", color="orange", width=1)
 
        self.plot(self.sma200, name="SMA 200", color="red", width=2)
 
        self.plot(self.high_52w, name="52w High", color="gray", style="dashed", overlay=True)
 
  
 
    def next(self):
 
        curr = self.i
 
        # Verifica disponibilità dati (almeno 252 barre + buffer)
 
        if curr < 260: return
 
  
 
        # Dati attuali
 
        price = self.data['Close'].iloc[curr]
 
        sma50 = self.sma50.iloc[curr]
 
        sma150 = self.sma150.iloc[curr]
 
        sma200 = self.sma200.iloc[curr]
 
        low_52 = self.low_52w.iloc[curr]
 
        high_52 = self.high_52w.iloc[curr]
 
        rsi = self.rsi.iloc[curr]
 
  
 
        lookback = self.params['ma_200_rising_days']
 
        sma200_prev = self.sma200.iloc[curr - lookback]
 
  
 
        # === CONDIZIONI MINERVINI ===
 
        cond_alignment = (price > sma50) and (sma50 > sma150) and (sma150 > sma200)
 
        cond_200_rising = sma200 > sma200_prev
 
        cond_price_support = price > sma200 and price > sma50
 
        cond_above_low = price >= (low_52 * self.params['min_above_low'])
 
        cond_near_high = price >= (high_52 * self.params['within_below_high'])
 
        cond_rs = rsi >= self.params['rsi_min']
 
  
 
        # --- GESTIONE POSIZIONE ---
 
  
 
        # ENTRY LONG
 
        if not self.position: # position è False/0 se flat
 
            if (cond_alignment and cond_200_rising and cond_price_support and
 
                cond_above_low and cond_near_high and cond_rs):
 
                # --- FIX: Uso API nativa ---
 
                self.enter_long(comment="Stage 2 Breakout")
 
                # ---------------------------
 
                self.plot_shape("triangle_up", "lime", "below", "Entry")
 
  
 
        # EXIT LONG
 
        elif self.position: # position è True/1 se long
 
            if price < sma50:
 
                # --- FIX: Uso API nativa ---
 
                self.close_long(comment="Lost SMA 50")
 
                # ---------------------------
 
                self.plot_shape("circle", "orange", "above", "Exit")