Descrizione e Logica

Questa non è una strategia di trading classica (che cerca entrate e uscite ottimizzate per il P&L), ma un Algoritmo di Screening. Il suo scopo è scansionare un vasto universo di titoli (es. NASDAQ Composite, Russell 3000) e restituire una “Shortlist” di candidati di alta qualità istituzionale.

Il filtro applica una logica “AND” rigorosa su 9 criteri simultanei. Un titolo passa il filtro (Segnale BUY/OK) solo se:

  1. Prezzo Minimo: Il prezzo è superiore a $5 (Esclusione Penny Stocks).
  2. Trend Primario: Le medie mobili sono perfettamente allineate: SMA 50 > SMA 150 > SMA 200.
  3. Forza del Prezzo: Il prezzo è superiore alla SMA 150 con un margine di sicurezza (buffer).
  4. Liquidità (Volume): Il volume medio a 20 giorni è > 500.000 azioni.
  5. Liquidità (Capitale): Il volume medio scambiato in dollari (Dollar Volume) è > $100 Milioni al giorno. Questo garantisce che il titolo sia tradabile da fondi istituzionali.
  6. Volatilità (ADR): L’Average Daily Range è almeno del 2.75%. Il titolo deve muoversi abbastanza da generare profitto (“Mover”).
  7. Dati Fondamentali (Predisposizione): Il codice include la logica per filtrare per Market Cap (> $20M) e Paese (Exclude China), attualmente impostati come passanti in attesa dell’integrazione dei dati fondamentali nel DataManager.

Come si Utilizza

Questo script è progettato per essere eseguito principalmente nel modulo SCANNER (Tab Research) o nell’ARENA.

  • Nello Scanner: Selezionare questa strategia e una lista di ticker. I risultati mostreranno solo i titoli che oggi soddisfano tutti i criteri. È il punto di partenza per la sessione di trading giornaliera.
  • Nel Backtest: Se eseguita come backtest, la strategia aprirà una posizione “Long” quando i criteri sono soddisfatti e la chiuderà quando anche solo uno di essi viene meno. Questo è utile per vedere storicamente quando un titolo è stato considerato “Prime”.

Parametri di Input

Filtri Prezzo e Trend

  • Min Price ($): Soglia minima di prezzo (default: 5.0).
  • SMA 50 / 150 / 200 Length: Periodi per la definizione della struttura del trend.
  • Price vs SMA150 (> %): Buffer percentuale richiesto sopra la media a 150 giorni (default: 0.10%). Serve a evitare falsi positivi quando il prezzo “balla” esattamente sulla media.

Filtri Liquidità e Volatilità

  • Min Avg Vol (Shares): Numero minimo di azioni scambiate mediamente (default: 500k).
  • Min Avg ): Valore monetario minimo scambiato giornalmente in Milioni di Dollari (default: 100M). Cruciale per identificare l’interesse istituzionale.
  • Min ADR %: Volatilità media giornaliera minima richiesta (default: 2.75%).

Filtri Fondamentali (Placeholder)

  • Min Mkt Cap (M$): Capitalizzazione minima (attualmente logica pass-through).

Interpretazione Grafica (Overlay)

Quando applicata su un grafico, la strategia visualizza:

  1. Linee Trend:

    • 🔵 SMA 50 (Breve termine istituzionale).
    • 🟠 SMA 150 (Medio termine).
    • 🔴 SMA 200 (Lungo termine).
  2. Marker di Validazione:

    • 🔵 Pallino Ciano (Sotto la barra): Indica che in quel giorno il titolo soddisfa TUTTI i 9 criteri.
    • Se il pallino scompare, significa che il titolo ha perso una caratteristica chiave (es. il volume è sceso o il trend si è rotto).

# VERSION: v1.1 (Refined-Criteria) - fire/strategies/standard_library/Fundamental_Trend_Filter.py
 
# RESP: Filtro composito Tecnico/Fondamentale per selezione titoli leader.
 
# DEPS: pandas, numpy, fire.strategies.base_strategy
 
# CHANGELOG v1.1:
 
#   - CONFIG: Aggiornato default ADR a 2.75%.
 
#   - LOGIC: Introdotto buffer percentuale per la condizione "Price vs 150 SMA" (default 0.10%).
 
  
 
import pandas as pd
 
import numpy as np
 
from fire.strategies.base_strategy import BaseStrategy
 
  
 
class Fundamental_Trend_Filter(BaseStrategy):
 
    name = "Screener: Liquid Trend & Momentum"
 
    description = (
 
        "Filtro avanzato per selezione titoli. Criteri: "
 
        "Last > $5, Mkt Cap > $20M, 50>150>200 SMA, "
 
        "ADR > 2.75%, Vol > 500K, $Vol > 100M, Price > 150 SMA (+0.10%)."
 
    )
 
  
 
    def __init__(self):
 
        super().__init__()
 
        # === 1. Prezzo Minimo ===
 
        self.add_parameter("min_price", 5.0, float, label="Min Price ($)")
 
        # === 2. Market Cap (Placeholder per futura integrazione dati) ===
 
        self.add_parameter("min_market_cap_m", 20, int, label="Min Mkt Cap (M$)")
 
        # === 3/4/9. Medie Mobili ===
 
        self.add_parameter("sma_50_len", 50, int, label="SMA 50 Length")
 
        self.add_parameter("sma_150_len", 150, int, label="SMA 150 Length")
 
        self.add_parameter("sma_200_len", 200, int, label="SMA 200 Length")
 
        # Buffer specifico per la SMA 150 (0.10%)
 
        self.add_parameter("price_above_sma150_pct", 0.10, float, step=0.01, label="Price vs SMA150 (> %)")
 
        # === 5. ADR (Average Daily Range) ===
 
        self.add_parameter("adr_len", 20, int, label="ADR Length")
 
        # Aggiornato default a 2.75%
 
        self.add_parameter("min_adr_pct", 2.75, float, step=0.1, label="Min ADR %")
 
        # === 7. Volume Medio (Shares) ===
 
        self.add_parameter("vol_len", 20, int, label="Avg Vol Length")
 
        self.add_parameter("min_vol_shares", 500000, int, label="Min Avg Vol (Shares)")
 
        # === 8. Dollar Volume (Liquidity) ===
 
        self.add_parameter("min_dollar_vol_m", 100, int, label="Min Avg $ Vol (M$)")
 
  
 
    def init(self):
 
        """Calcolo Indicatori."""
 
        close = self.data['Close']
 
        high = self.data['High']
 
        low = self.data['Low']
 
        volume = self.data['Volume']
 
        # --- Medie Mobili ---
 
        self.sma50 = close.rolling(window=self.params['sma_50_len']).mean()
 
        self.sma150 = close.rolling(window=self.params['sma_150_len']).mean()
 
        self.sma200 = close.rolling(window=self.params['sma_200_len']).mean()
 
        # --- Volume ---
 
        self.avg_vol = volume.rolling(window=self.params['vol_len']).mean()
 
        # --- Dollar Volume (Close * Volume) ---
 
        daily_dollar_vol = close * volume
 
        self.avg_dollar_vol = daily_dollar_vol.rolling(window=self.params['vol_len']).mean()
 
        # --- ADR (Average Daily Range %) ---
 
        # Formula: ((High - Low) / Low) * 100
 
        daily_range_pct = ((high - low) / low) * 100
 
        self.adr = daily_range_pct.rolling(window=self.params['adr_len']).mean()
 
  
 
        # --- Visualizzazione ---
 
        self.plot(self.sma50, "SMA 50", color="blue", width=2)
 
        self.plot(self.sma150, "SMA 150", color="orange")
 
        self.plot(self.sma200, "SMA 200", color="red")
 
  
 
    def next(self):
 
        # Indici
 
        curr = self.i
 
        # Serve storico sufficiente per la SMA 200
 
        if curr < 205: return
 
        # Valori Attuali
 
        price = self.data['Close'].iloc[curr]
 
        sma50 = self.sma50.iloc[curr]
 
        sma150 = self.sma150.iloc[curr]
 
        sma200 = self.sma200.iloc[curr]
 
        avg_vol = self.avg_vol.iloc[curr]
 
        avg_dol_vol = self.avg_dollar_vol.iloc[curr]
 
        adr = self.adr.iloc[curr]
 
        # === APPLICAZIONE FILTRI ===
 
        # 1. Price > $5
 
        cond_price = price > self.params['min_price']
 
        # 2. Market Cap (Placeholder)
 
        cond_mkt_cap = True
 
        # 3. 50 > 150
 
        cond_ma_3 = sma50 > sma150
 
        # 4. 50 > 200
 
        cond_ma_4 = sma50 > sma200
 
        # 9. Price > 150 SMA + Buffer (0.10%)
 
        # Esempio: se SMA150 è 100, Prezzo deve essere > 100.10
 
        buffer_mult = 1 + (self.params['price_above_sma150_pct'] / 100.0)
 
        cond_ma_9 = price > (sma150 * buffer_mult)
 
        # 6. Exclude Country (Placeholder)
 
        cond_country = True
 
        # 7. Avg Vol > 500K
 
        cond_vol_shares = avg_vol > self.params['min_vol_shares']
 
        # 8. Avg $ Vol > 100M
 
        min_dol_vol = self.params['min_dollar_vol_m'] * 1_000_000
 
        cond_vol_dollars = avg_dol_vol > min_dol_vol
 
        # 5. ADR (2.75%)
 
        cond_adr = adr >= self.params['min_adr_pct']
 
        # === VALUTAZIONE FINALE ===
 
        is_valid = (
 
            cond_price and
 
            cond_mkt_cap and
 
            cond_ma_3 and
 
            cond_ma_4 and
 
            cond_ma_9 and
 
            cond_country and
 
            cond_vol_shares and
 
            cond_vol_dollars and
 
            cond_adr
 
        )
 
        # Se siamo nello Scanner o nel Backtest
 
        if is_valid:
 
            info = f"ADR:{adr:.2f}% $Vol:{avg_dol_vol/1e6:.1f}M"
 
            if self.position == 0:
 
                self.enter_long(comment=f"SCREENER OK | {info}")
 
            self.plot_shape("circle", "cyan", "below", "OK", size="small")
 
        else:
 
            if self.position > 0:
 
                self.close_long(comment="Filter Lost")