
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:
- Allineamento Medie: Il prezzo è sopra la SMA 50 > SMA 150 > SMA 200.
- Trend a Lungo Termine: La SMA 200 è in ascesa da almeno 1 mese (20 barre).
- Forza del Prezzo: Il prezzo attuale è almeno il 30% sopra i minimi a 52 settimane (evita il “bottom fishing”).
- Vicinanza ai Massimi: Il prezzo è entro il 25% dai massimi a 52 settimane (il titolo sta “bussando” ai massimi, non è in ipervenduto profondo).
- 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:
-
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.
-
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")