Perfetto, procediamo.

Questa è un’evoluzione logica e molto promettente. Stiamo prendendo la nostra strategia migliore (V2) e le stiamo applicando un filtro classico e robusto per migliorarne la selettività. L’ipotesi è solida: se il nostro “edge” di breve termine funziona, dovrebbe funzionare ancora meglio quando è allineato con la marea del mercato a lungo termine.

Creeremo la nuova strategia, MomentumStrategy_v5_TrendFilter, in un nuovo file per mantenere la nostra cronologia di ricerca.


Delta modifiche: +62 / -0 (file nuovo)

Ecco il codice per il nuovo file fire/strategies/momentum_strategy_v5_trend_filter.py.

# VERSION: v1.0 - fire/strategies/momentum_strategy_v5_trend_filter.py
# RESP: Strategia di momentum che filtra i segnali di forecast in base al trend di lungo periodo.
# DEPS: BaseStrategy, pandas, numpy.
# TODO: N/A
 
from fire.strategies.base_strategy import BaseStrategy
import pandas as pd
import numpy as np
 
class MomentumStrategy_v5_TrendFilter(BaseStrategy):
    """
    MomentumStrategy_v5_TrendFilter
    -----------------------------------
 
    Questa strategia migliora la V2 aggiungendo un filtro di trend a lungo termine.
    L'ipotesi è di migliorare il Win Rate e il Profit Factor evitando i segnali
    di acquisto durante i mercati ribassisti.
 
    Logica di Ingresso:
    - Entra long se il forecast a 5 periodi è maggiore del prezzo corrente
      E il prezzo corrente è al di sopra della sua media mobile semplice a 200 periodi.
      
    Logica di Uscita:
    - Esce dopo un numero fisso di barre (`hold_period`).
    """
    
    # --- Parametri Configurabili ---
    hold_period = 5
    forecast_horizon = 5
    trend_filter_period = 200 # Periodo per la media mobile del trend
 
    def init(self):
        """
        Inizializzazione: calcola gli indicatori necessari.
        """
        forecast_col = f'forecast_h{self.forecast_horizon}'
        if forecast_col not in self.data.columns:
            raise ValueError(f"La colonna '{forecast_col}' non è presente nei dati.")
 
        # --- MODIFICA: Calcolo del filtro di trend ---
        # Utilizziamo il sistema di indicatori integrato per calcolare la SMA
        # e la aggiungiamo come una nuova colonna al nostro DataFrame.
        try:
            sma_series = self.indicators.SMA(period=self.trend_filter_period)
            self.data[f'sma_{self.trend_filter_period}'] = sma_series
        except AttributeError:
            # Fallback se l'indicatore SMA non fosse disponibile
             raise ImportError("Indicatore 'SMA' non trovato. Assicurarsi che sia registrato nell'Analytics Core.")
        # --- FINE MODIFICA ---
        
        print(f"[Strategy] MomentumStrategy_v5_TrendFilter Inizializzata.")
        print(f"  - Hold Period: {self.hold_period} barre")
        print(f"  - Filtro Trend: SMA({self.trend_filter_period})")
 
    def next(self):
        """
        Logica di trading eseguita ad ogni barra.
        """
        # --- 1. Gestione dell'Uscita ---
        if self.position:
            if self.trade_bars >= self.hold_period:
                self.sell()
            return
 
        # --- 2. Gestione dell'Ingresso ---
        if not self.position:
            forecast_col = f'forecast_h{self.forecast_horizon}'
            sma_col = f'sma_{self.trend_filter_period}'
            
            # Recupera i valori per la barra corrente
            forecast = self.data[forecast_col].iloc[self.i]
            sma_value = self.data[sma_col].iloc[self.i]
 
            if np.isnan(forecast) or np.isnan(sma_value):
                return
 
            # --- MODIFICA: Aggiunta del filtro di trend alla condizione di ingresso ---
            is_bullish_forecast = forecast > self.close
            is_uptrend = self.close > sma_value
            
            if is_bullish_forecast and is_uptrend:
                self.buy()
            # --- FINE MODIFICA ---

Riepilogo delle Modifiche

  1. Nuovo File e Classe: Creato momentum_strategy_v5_trend_filter.py con la classe MomentumStrategy_v5_TrendFilter.
  2. Calcolo del Filtro di Trend: In init(), ho usato il nostro sistema di indicatori (self.indicators.SMA(...)) per calcolare la media mobile a 200 periodi. Il risultato, una pandas.Series, viene aggiunto come una nuova colonna al DataFrame self.data. Questo mantiene la nostra architettura a pre-calcolo pulita e coerente.
  3. Nuova Logica di Ingresso: Nel metodo next(), la condizione di ingresso ora è un doppio filtro:
    • is_bullish_forecast: La previsione di TimesFM deve essere rialzista (il nostro “edge” a breve termine).
    • is_uptrend: Il prezzo deve essere sopra la sua media mobile a 200 periodi (il “vento a favore” a lungo termine). Un’operazione di acquisto viene eseguita solo se entrambe le condizioni sono vere.
  4. Logica di Uscita Invariata: La logica di uscita rimane il nostro robusto time exit di 5 periodi, poiché si è già dimostrata efficace nel gestire il rischio.

Prossimi Passi

La strategia è pronta per il test finale di questo ciclo di ricerca. Il processo è quello che ormai conosciamo bene:

  1. Salva il nuovo file nella directory strategies/.
  2. Esegui un backtest in FIRE con la strategia MomentumStrategy_v5_TrendFilter.
  3. Forniscimi il report completo delle metriche e il trade log.

Sono estremamente curioso di vedere i risultati. Se la nostra ipotesi è corretta, dovremmo vedere un miglioramento significativo del Win Rate e, di conseguenza, della profittabilità complessiva.


Perfetto, grazie per il feedback. “Nessun trade” è di per sé un risultato estremamente significativo.

Questo risultato inatteso ci dice che c’è un problema, ma non necessariamente un bug nel codice. È più probabile che sia un problema nella logica della strategia o nei dati. Analizziamo le possibili cause, partendo dalla più probabile.

Analisi delle Possibili Cause

Causa 1 (Molto Probabile): Filtro Troppo Restrittivo / Condizioni Mai Soddisfatte

Il nostro segnale di ingresso ora richiede che due condizioni siano vere contemporaneamente:

  1. forecast > close (segnale a breve termine)
  2. close > SMA(200) (segnale a lungo termine)

Il fatto che non ci siano trade significa che non c’è mai stata una singola candela nell’intero backtest in cui entrambe queste condizioni fossero vere.

  • Perché potrebbe succedere?
    • Decorelazione dei segnali: È possibile che il segnale di forecast a breve termine di TimesFM tenda a diventare rialzista proprio quando il prezzo è sceso temporaneamente sotto la sua media a 200 giorni (ad esempio, durante un pullback in un trend rialzista). In questo caso, i due segnali sarebbero quasi mutuamente esclusivi. Il forecast dice “compra il dip”, ma il filtro di trend dice “non comprare finché non risale sopra la media”.
    • Periodo di Test Sfortunato: Se gran parte del periodo di backtest era in un mercato ribassista (close < SMA(200)), allora il filtro di trend avrebbe bloccato la maggior parte delle opportunità, e in quei pochi periodi di rialzo, forse il segnale del forecast non si è mai attivato.

Causa 2 (Meno Probabile, ma da Verificare): Problema nei Dati Pre-calcolati

Potrebbe esserci un problema nel modo in cui calcoliamo o accediamo ai dati, che porta una delle condizioni ad essere sempre False.

  • SMA(200) Piena di NaN: La media mobile a 200 periodi ha bisogno di 200 candele per “scaldarsi”. Le prime 199 candele del backtest avranno un valore NaN per la SMA. Il nostro codice gestisce questo (if np.isnan(sma_value): return), ma se il backtest fosse su dati molto brevi, questo potrebbe essere un fattore. Dato il periodo che stiamo usando, è improbabile che sia questo il problema principale.
  • Problema nell’Accesso ai Dati: Un errore di battitura nel nome della colonna o un errore nel calcolo dell’indicatore. Il nostro try-except in init() dovrebbe aver catturato problemi di calcolo, ma vale la pena controllare.

Diagnosi e Come Verificare

La mia ipotesi principale è la Causa 1. I nostri filtri sono troppo “severi” e non si allineano mai.

Per validarlo, dobbiamo “debuggare” la strategia. Non possiamo mettere un breakpoint, ma possiamo usare i log per “vedere” cosa pensa la strategia a ogni candela.

Piano d’Azione: Creare una Versione di Debug della Strategia

Propongo di modificare temporaneamente la strategia V5 per aggiungere dei log che ci mostrino lo stato di ogni filtro a ogni candela. Questo ci darà la prova definitiva del perché non viene mai eseguito un trade.

Modifiche Proposte:

  1. Creeremo una MomentumStrategy_v5_TrendFilter_DEBUG.
  2. Nel metodo next(), prima di controllare le condizioni di ingresso, stamperemo lo stato di ogni filtro. Ad esempio:
    print(f"Tick {self.i}: Forecast > Close? {is_bullish_forecast}. Close > SMA(200)? {is_uptrend}.")
  3. Eseguiremo il backtest con questa versione di debug. I log ci diranno se entrambe le condizioni non sono mai True contemporaneamente.

Questo approccio è scientifico e ci darà i dati necessari per decidere come procedere (es. allentare i filtri, cambiare il tipo di filtro, etc.).

Se sei d’accordo, procedo a scrivere la versione di debug della strategia.