Spec for a new /api/chart/underlying/{idUnderlyings} endpoint that
renders a single price-history line for one underlying, independent
of any certificate context (no strike/barriers/legend).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.4 KiB
Design: grafico standalone per singolo sottostante (senza certificato)
Data: 2026-07-21 Stato: approvato, in attesa di implementazione
Obiettivo
Aggiungere un endpoint che generi un grafico semplificato mostrando solo l'andamento storico prezzo di un sottostante, identificato da IDUnderlyings — senza passare da un ISIN certificato e senza alcun riferimento a strike/barriere/trigger (che esistono solo nel contesto di un certificato).
Caso d'uso: preview rapida dell'andamento di un sottostante indipendente da qualunque certificato collegato.
Architettura
Stessa pipeline di /api/chart/{isin} e /api/chart/v2/{isin}, ma senza contesto certificato:
GET /api/chart/underlying/{idUnderlyings}?width=&height=&format=
→ ChartController.GenerateChartUnderlying(idUnderlyings, width, height, format)
→ IUnderlyingChartDataService.GetChartDataAsync(idUnderlyings)
→ SP cedlab_Chart_UnderlyingOnly @IDUnderlyings
→ SkiaChartRendererV2 (nuovo metodo per serie singola)
→ PNG/JPEG bytes → risposta inline, oppure wrap in PDF (riuso WrapPngInPdf esistente)
Componenti nuovi
1. Modello — Models/UnderlyingChartModels.cs
public class UnderlyingChartData
{
public int IDUnderlyings { get; set; }
public string Nome { get; set; } = string.Empty; // ticker/nome per il titolo
public List<UnderlyingChartPoint> Points { get; set; } = new();
}
public class UnderlyingChartPoint
{
public DateTime Date { get; set; }
public decimal Px { get; set; }
}
2. Servizio dati — Services/Implementations/UnderlyingChartDataService.cs + IUnderlyingChartDataService
- Chiama la SP
cedlab_Chart_UnderlyingOnly @IDUnderlyings. - Popola
UnderlyingChartDatacon i punti prezzo grezzi (non normalizzati a %, a differenza di v2 che divide per Strike/Nominal — qui non c'è contesto certificato per farlo). - Registrato in
Program.cscomeAddScoped<IUnderlyingChartDataService, UnderlyingChartDataService>().
3. Stored Procedure — docs/sql/cedlab_Chart_UnderlyingOnly.sql
CREATE OR ALTER PROCEDURE [dbo].[cedlab_Chart_UnderlyingOnly]
@IDUnderlyings INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @AdjustedPrices BIT;
SELECT @AdjustedPrices = ISNULL(AdjustedPrices, 0)
FROM dbo.Underlyings
WHERE IDUnderlyings = @IDUnderlyings
AND deleted = 0
AND sospeso = 0;
IF @AdjustedPrices IS NULL RETURN; -- non trovato / cancellato / sospeso
;WITH Ranked AS
(
SELECT
p.Px_date,
CASE WHEN @AdjustedPrices = 0 THEN p.Px_close ELSE p.Px_closeadj END AS Px,
ROW_NUMBER() OVER (ORDER BY p.Px_date DESC) AS rn
FROM dbo.Prices p
WHERE p.UnderlyingsID = @IDUnderlyings
AND (p.Px_low IS NOT NULL OR p.Px_high IS NOT NULL OR
p.Px_open IS NOT NULL OR p.Px_close IS NOT NULL)
)
SELECT
/* TODO: verificare colonna nome/ticker corretta in dbo.Underlyings prima di eseguire */
u.Ticker AS Nome,
r.Px_date,
r.Px
FROM Ranked r
CROSS JOIN (SELECT TOP 1 * FROM dbo.Underlyings WHERE IDUnderlyings = @IDUnderlyings) u
WHERE r.rn <= 350
ORDER BY r.Px_date ASC;
END
GO
Nota: colonna
u.Tickerè placeholder — il nome effettivo della colonna indbo.Underlyings(Ticker / TickerBB / Nome / Description...) va confermato prima di applicare lo script al DB, come già fatto percedlab_Chart_AllSeriesV2.sql.
4. Renderer — nuovo metodo in SkiaChartRendererV2
- Firma indicativa:
RenderSingleSeriesToPng(UnderlyingChartData data, int width, int height, bool jpeg). - Riusa griglia, assi X con intervalli mensili adattivi (12m/6m/3m/1m) e stile generale di V2.
- Una sola linea (colore nero o
AccentBlue, spessore 2px). - Titolo in alto =
data.Nome. - Nessuna legenda (una sola serie non la richiede).
- Nessuna barriera/strike/trigger disegnati (non esiste contesto certificato).
5. Controller — nuova route in ChartController.cs esistente
[HttpGet("underlying/{idUnderlyings}")]
public async Task<IActionResult> GenerateChartUnderlying(
int idUnderlyings,
[FromQuery] int width = 1100,
[FromQuery] int height = 700,
[FromQuery] string format = "png")
idUnderlyings <= 0→400 BadRequest.- Nessun punto prezzo trovato →
404 NotFoundcon messaggio esplicativo. - Formati supportati:
png(default),jpg/jpeg,pdf(viaWrapPngInPdfesistente, riuso invariato). - Non supportato:
jpgEnce?save=true— dipendono dall'alias-certificato (rpt_CertificatesChartsAlias) che non esiste per un sottostante puro senza contesto certificato. - Eccezioni →
500loggato, stesso pattern try/catch di v1/v2.
Error handling
| Caso | Risposta |
|---|---|
idUnderlyings <= 0 |
400 |
Sottostante non trovato / deleted=1 / sospeso=1 |
404 |
| Nessun prezzo disponibile | 404 |
| Eccezione runtime | 500 (loggato) |
Cache
Nessuna cache introdotta per questo endpoint — coerente con /api/chart/{isin} e /api/chart/v2/{isin}, che non cachano l'immagine (solo il PDF del report principale usa cache). Fuori scope.
Fuori scope
- Parametro
?save=true(nessun path di salvataggio dedicato per sottostanti puri). - Formato
jpgEnc(nessun alias-certificato applicabile). - Barriere/strike/trigger/scenari — richiedono contesto certificato, non disponibile con solo
IDUnderlyings. - Normalizzazione a percentuale (richiede uno strike/nominal di riferimento, assente qui).