
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:
- Prezzo Minimo: Il prezzo è superiore a $5 (Esclusione Penny Stocks).
- Trend Primario: Le medie mobili sono perfettamente allineate: SMA 50 > SMA 150 > SMA 200.
- Forza del Prezzo: Il prezzo è superiore alla SMA 150 con un margine di sicurezza (buffer).
- Liquidità (Volume): Il volume medio a 20 giorni è > 500.000 azioni.
- Liquidità (Capitale): Il volume medio scambiato in dollari (Dollar Volume) è > $100 Milioni al giorno. Questo garantisce che il titolo sia tradabile da fondi istituzionali.
- Volatilità (ADR): L’Average Daily Range è almeno del 2.75%. Il titolo deve muoversi abbastanza da generare profitto (“Mover”).
- 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:
-
Linee Trend:
- 🔵 SMA 50 (Breve termine istituzionale).
- 🟠 SMA 150 (Medio termine).
- 🔴 SMA 200 (Lungo termine).
-
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")