FIRE DD-CHARTING 3.0

1. Panoramica e Filosofia: L’Architettura Ibrida

Il sottosistema di Charting è un sistema ibrido progettato per sfruttare il meglio di due librerie specializzate:

  1. Lightweight Charts™: Un motore ad alte prestazioni, ottimizzato per grafici finanziari basati sul tempo (Candele, Barre, Linee, etc.).

  2. Plotly: Un motore di visualizzazione scientifica estremamente flessibile, utilizzato per grafici complessi o non basati sul tempo (Renko, Kagi, PnF, Heatmaps, etc.).

Per gestire queste due tecnologie, la nostra architettura si basa su due pattern distinti, ognuno ottimizzato per la libreria che serve.


2. Le Due Architetture di Charting

2.1. Architettura #1: Plotter/Renderer (per Lightweight Charts)

Questo pattern è progettato per la natura imperativa di Lightweight Charts e si basa su una rigida separazione delle responsabilità.

  • I Plotter: Trasformano i dati grezzi. Es. HeikinAshiPlotter.

  • I Renderer: Invocano i comandi di disegno JavaScript. Es. CandlestickRenderer.

  • Il Widget (LightweightChartWidget): Esegue i comandi.

(Questa sezione rimane concettualmente invariata rispetto alla versione 2.0)

2.2. Architettura #2: Worker (per Grafici Plotly)

Questo pattern è progettato per la natura dichiarativa di Plotly, che si aspetta un singolo oggetto di configurazione per l’intero grafico.

  • I Worker (KagiWorker, LineBreakWorker, etc.): Sono classi QRunnable eseguite in un thread separato. La loro unica responsabilità è prendere i dati grezzi e produrre una stringa JSON che descrive l’intera figura Plotly. Incapsulano sia la logica di calcolo che quella di layout.

  • Il Widget (QCPlotlyChartWidget): È un visualizzatore reattivo. Riceve la stringa JSON dal worker e la passa a Plotly.js per creare un’istanza dinamica e ridimensionabile del grafico.

  • L’Orchestratore (BacktestTabWidget): Ha la responsabilità di avviare il worker corretto in base alla selezione dell’utente.


3. Flussi dei Dati a Confronto

3.1. Flusso dei Dati (Lightweight Charts)

(Questa sezione rimane invariata rispetto alla versione 2.0)

3.2. Flusso dei Dati (Plotly - es. Kagi)

  1. Evento UI: L’utente seleziona “Kagi” dalla toolbar.

  2. MainWindow (on_chart_plotter_changed): Chiama charting_panel.switch_chart_engine(“Kagi”), che mostra il QCPlotlyChartWidget.

  3. BacktestTabWidget (_redraw_chart_with_current_data): Rileva che “Kagi” è un tipo di grafico Plotly.

  4. Dispatcher (_start_advanced_chart_worker): Crea un’istanza del KagiWorker e lo avvia in un thread separato.

  5. KagiWorker (run): Esegue i calcoli, crea l’oggetto go.Figure e lo serializza in una stringa JSON.

  6. Emissione Segnale: Il worker emette il segnale finished con il payload {‘figure_json’: ’…’}.

  7. Propagazione: Il BacktestTabWidget riceve il segnale e ri-emette il JSON tramite il suo segnale advanced_chart_data_updated.

  8. Rendering Finale: La MainWindow riceve questo segnale e chiama lo slot plot(figure_json) del QCPlotlyChartWidget, che infine renderizza il grafico.


4. Guide Pratiche

4.1. Come Aggiungere un Grafico Lightweight Charts

(Questa sezione rimane invariata)

4.2. Come Aggiungere un Grafico Plotly (es. Kagi)

  1. Crea il POC: Sviluppa uno script poc_[nome]_generator.py per validare la logica di calcolo e visualizzazione, producendo un file HTML.

  2. Crea il Worker: Crea un nuovo file fire/workers/charting/[nome]_worker.py. Sposta la logica dal POC, assicurandoti che il worker produca una stringa JSON e la emetta nel segnale finished.

  3. Registra nella UI:

    • Aggiungi il nome del grafico in fire/ui_components/chrome_manager.py.

    • Aggiungi il nome del grafico alla lista PLOTLY_CHART_TYPES in fire/app_state.py.

  4. Aggiungi la Logica di Dispatching:

    • Importa il nuovo worker in fire/synapses/backtest/backtest_tab_widget.py.

    • Aggiungi un blocco elif plotter_name == “[Nome Grafico]”: nel metodo _start_advanced_chart_worker per istanziare e avviare il nuovo worker.


5. Regole d’Oro e Best Practice (NUOVA SEZIONE)

5.1. Il Contratto sui Nomi delle Colonne (Lightweight Charts)

  • REGOLA: Tutti i Renderer di Lightweight Charts (es. CandlestickRenderer, BarRenderer) si aspettano di ricevere un DataFrame con colonne in formato TitleCase (es. Open, High, Low, Close, time).

  • OBBLIGO: È responsabilità di ogni Plotter (CandlestickPlotter, HeikinAshiPlotter, etc.) garantire che il DataFrame restituito dal suo metodo plot() sia sempre in questo formato, indipendentemente dal formato dei dati che riceve in input. Questo previene bug di “contaminazione” dello stato.

5.2. La Reattività di Plotly: JSON, non HTML

  • REGOLA: Per garantire che i grafici Plotly siano reattivi e si adattino al ridimensionamento della finestra, il pattern corretto è creare un’istanza JavaScript dinamica.

  • OBBLIGO: I worker Plotly devono produrre una stringa JSON (json.dumps(fig, cls=PlotlyJSONEncoder)). Il QCPlotlyChartWidget si occuperà di ricevere questo JSON e di passarlo a Plotly.newPlot() con l’opzione {responsive: true}. L’approccio basato su fig.to_html() è deprecato perché genera grafici statici.

5.3. Attenzione al Bug di go.Candlestick con Assi Non-Temporali

  • REGOLA: L’oggetto go.Candlestick di Plotly contiene un’interazione anomala che può causare un “fallimento silente” del rendering quando viene utilizzato con un asse X non temporale in un ambiente browser embedded come QWebEngineView.

  • SINTOMO: Il grafico appare vuoto e l’ispezione del DOM rivela che il container ha un’altezza di 0 pixel, nonostante il layout specifichi un’altezza corretta.

  • OBBLIGO: Per qualsiasi grafico che utilizza go.Candlestick con un asse X non-temporale (es. Line Break), è obbligatorio seguire la soluzione standard definita nell’ADR dedicato per prevenire questo bug. La soluzione consiste nel fornire a Plotly un asse X puramente numerico e monotono.

  • APPROFONDIMENTO: Per la diagnosi completa, le cause e l’implementazione di riferimento, consultare il seguente ADR: ADR-0007: Risoluzione del Bug di Rendering a 0px per Grafici Plotly basati su go.Candlestick.