CMS e piattaforme 13 min di lettura

Estrazione di quotazioni di borsa: API, fonti, codice e archiviazione dei dati

Dove trovare le quotazioni di borsa: API di mercato, portali finanziari, esempi di codice in cinque linguaggi e archiviazione dello storico.

TW
Team Web-Scraping.it
Raccolta dati per le esigenze del business
Pubblicato il 18 marzo 2025

Questo è il materiale gemello dell’articolo «Scraping dei tassi di cambio». Molto di quell’articolo si trasferisce qui quasi senza modifiche — in particolare la disciplina dei tipi di dato (niente float per i prezzi) e le pratiche di caching. Ma le quotazioni azionarie hanno le loro specificità: ticker e piazze di borsa, sessioni di negoziazione e giorni festivi, operazioni societarie (split e dividendi) e — la grande differenza rispetto ai tassi delle banche centrali — la licenza sui dati. Le informazioni di borsa in tempo reale sono regolate dalle borse e dalle autorità di vigilanza e, a differenza del cambio ufficiale liberamente pubblicato, non sempre si possono ottenere gratis, tanto meno ridistribuire.


1. Che cos’è una «quotazione» e quali dati esistono

A seconda del compito, per «quotazione» si intendono cose diverse, ed è la prima cosa da definire:

  • Last / prezzo corrente — il prezzo dell’ultima transazione. Quello che mostra il widget «prezzo dell’azione adesso».
  • Barra OHLCV (candela) — Open, High, Low, Close e Volume di un intervallo (minuto, ora, giorno). La base di grafici e backtest.
  • Bid/Ask (book di negoziazione) — i migliori prezzi in acquisto e in vendita. Servono per il trading; di solito sono i dati più «costosi» e più vincolati da licenza.
  • EOD (end-of-day) — i prezzi di chiusura della giornata. Economici o gratuiti; vanno bene per l’analisi e per il monitoraggio di portafoglio.
  • Adjusted close — la chiusura rettificata per split e dividendi, perché lo storico resti continuo.

E il bivio chiave sulla «freschezza»: tempo reale → ritardo di 15–20 minuti → end-of-day. Più i dati sono freschi e granulari, più severa è la licenza e più alto il prezzo.


2. Le borse come fonte primaria

La fonte primaria delle quotazioni sono le borse stesse: NYSE e NASDAQ negli Stati Uniti, le piazze di Euronext, la Borsa di Londra, la Deutsche Börse e, in Italia, Borsa Italiana (Euronext Milan). Ma, a differenza delle banche centrali, le borse non regalano quasi mai un’API pubblica: i loro dati in tempo reale sono un prodotto su licenza distribuito tramite vendor autorizzati, e ciò che viene pubblicato gratis su siti e portali arriva di solito con un ritardo di 15–20 minuti. Per lo sviluppatore, la via pratica sono le API della sezione successiva.

Borsa Che cosa quota Accesso ai dati
NYSE / NASDAQ (USA) azioni, ETF tempo reale su licenza tramite vendor; ritardo di ~15 min sui portali gratuiti
Borsa Italiana — Euronext Milan azioni italiane, indice FTSE MIB quotazioni in ritardo sul sito; tempo reale tramite i servizi market data di Euronext (licenza)
Euronext (altre piazze), LSE, Deutsche Börse azioni europee stesso schema: vendor e licenze
SIX (Zurigo), BME (Madrid) altri mercati europei sito con ritardo; dati completi tramite vendor

Un comodo ponte verso l’articolo sulle valute: molte delle API della sezione successiva (Finnhub, Twelve Data, Alpha Vantage) non coprono solo le azioni, ma anche le coppie di valute e le cripto. In altre parole, una sola integrazione risolve insieme le quotazioni azionarie e il cambio di mercato — di mercato, appunto, che non è il tasso ufficiale di riferimento della banca centrale.

Una particolarità pratica di queste fonti: ogni provider ha il proprio dialetto di JSON. Alpha Vantage numera le chiavi («1. open», «4. close»), Yahoo Finance annida i valori in array paralleli dentro indicators, Twelve Data restituisce i prezzi come stringhe. La morale è sempre la stessa: mappa i campi per nome e valida la risposta, invece di fidarti di un ordine fisso (lo vedrai negli esempi).


3. API internazionali

Qui non esiste una fonte libera «alla banca centrale»: tutte richiedono una chiave e applicano limiti, e il tempo reale è quasi sempre a pagamento.

Servizio Copertura Chiave Limite gratuito Note
Finnhub azioni USA e internazionali, FX, cripto necessaria ~60 richieste/min c’è il WebSocket; storico limitato nel piano gratuito
Twelve Data azioni, FX, cripto necessaria ~800 richieste/giorno OHLC, indicatori, REST pulito
Alpha Vantage 200.000+ ticker, 20+ borse necessaria 25 richieste/giorno (5/min) EOD e indicatori; tempo reale USA a pagamento
yfinance (non ufficiale, Yahoo) molto ampia non serve nessun limite esplicito, ma è scraping per prototipi e formazione, non per la produzione
EODHD 150.000+ ticker globali necessaria di prova forte nel download massivo dello storico
Financial Modeling Prep prezzi + fondamentali necessaria con limite bilanci, multipli
Tiingo EOD + fondamentali USA necessaria con limite end-of-day puliti
Marketstack / Polygon.io globale / tempo reale USA necessaria di prova / di fatto a pagamento Polygon: bassa latenza, tick

Un dettaglio recente importante: IEX Cloud ha chiuso il 31 agosto 2024. Se qualche guida lo consiglia ancora, è un’informazione superata; i sostituti più vicini sono Alpha Vantage e Financial Modeling Prep.

I piani gratuiti vanno benissimo per un prototipo, ma sbattono presto contro i limiti (le 25 richieste al giorno di Alpha Vantage sono, letteralmente, un paio di decine di ticker al giorno). Per questo, con un piano gratuito la logica si costruisce sempre attorno alla cache e all’archiviazione locale: si scarica lo storico una volta e poi si aggiornano solo i punti recenti.


4. Perché le quotazioni azionarie sono più difficili dei tassi di cambio

Alcune differenze che mandano in crisi uno scraper ingenuo:

  • Il ticker non è unico. Uno stesso simbolo può essere negoziato su più borse (Eni è quotata a Milano come ENI e, tramite ADR, anche a New York). Per questo la chiave dello strumento è la coppia «borsa + ticker», e ancora più affidabili sono gli identificatori internazionali ISIN o FIGI.
  • Sessioni di negoziazione e giorni festivi. Ogni borsa ha il suo orario, il suo fuso orario e le sue sessioni pre e post negoziazione. «L’ultimo prezzo» nel weekend è il prezzo di venerdì.
  • Operazioni societarie. Uno split 1:10 «affonda» il prezzo di 10 volte — ma non è un crollo del mercato, bensì un ricalcolo tecnico. È l’analogo del campo nominale dell’articolo sulle valute: ignoralo e otterrai una falsa anomalia. Dividendi e split si correggono con l’adjusted close.
  • La valuta dello strumento. Il prezzo del titolo è espresso nella valuta della sua borsa (EUR, USD, GBP…), e per un portafoglio in una sola valuta bisogna convertirlo — è qui che entrano in gioco i tassi di cambio dell’articolo gemello.
  • Il volume (volume). È un numero intero, ma enorme: milioni e miliardi di titoli. Serve un tipo intero a 64 bit.

5. Cinque soluzioni in linguaggi diversi

Gli esempi passano per quattro provider e formati diversi (Alpha Vantage compare due volte, in due tagli: istantanea e candele giornaliere). In tutti, l’accento è sulle due cose dell’articolo sulle valute: il prezzo si salva in un tipo decimale (non float) e il volume in un intero a 64 bit.

5.1. PHP — Alpha Vantage (GLOBAL_QUOTE, prezzi come stringhe)

php
<?php
declare(strict_types=1);

/**
 * Ultima quotazione di un'azione via Alpha Vantage (GLOBAL_QUOTE).
 * Piano gratuito: 25 richieste/giorno — la cache è obbligatoria.
 */
function lastPrice(string $ticker): ?string
{
    $url = sprintf(
        'https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=%s&apikey=%s',
        urlencode($ticker),
        getenv('ALPHAVANTAGE_KEY')
    );

    $raw = file_get_contents($url);
    if ($raw === false) {
        throw new RuntimeException('Impossibile ottenere i dati da Alpha Vantage');
    }
    $json = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);

    // Le chiavi arrivano numerate: "01. symbol", "05. price"...
    $quote = $json['Global Quote'] ?? [];
    if (!$quote) {
        return null; // ticker sconosciuto o limite giornaliero esaurito
    }

    // Il prezzo arriva come stringa: lo conserviamo com'è, senza passare per float
    return $quote['05. price'] ?? null;
}

echo 'IBM: '  . (lastPrice('IBM')  ?? 'nessun dato') . " USD\n";
echo 'AAPL: ' . (lastPrice('AAPL') ?? 'nessun dato') . " USD\n";

L’aspetto istruttivo qui: Alpha Vantage restituisce i prezzi come stringhe JSON, quindi la precisione decimale sopravvive a json_decode senza sforzo — basta non convertirla in float. Per l’aritmetica, come nell’articolo sulle valute, BCMath o una libreria Money. Nota anche che un ticker inesistente o un limite esaurito non arrivano come errore HTTP: la risposta è vuota o contiene una nota di avviso, e quel caso va gestito in modo esplicito invece di fidarsi di un formato fisso.

5.2. Python — Finnhub (quotazione corrente, Decimal)

python
import os
import requests
from decimal import Decimal

FINNHUB_TOKEN = os.environ["FINNHUB_TOKEN"]  # chiave gratuita, ~60 richieste/min


def finnhub_quote(symbol: str) -> dict[str, Decimal]:
    """Quotazione corrente di un simbolo (nel piano gratuito, mercato USA)."""
    resp = requests.get(
        "https://finnhub.io/api/v1/quote",
        params={"symbol": symbol, "token": FINNHUB_TOKEN},
        timeout=10,
    )
    resp.raise_for_status()
    d = resp.json()
    # Decimal(str(...)) fissa esattamente il valore arrivato nel JSON
    return {
        "current":    Decimal(str(d["c"])),   # prezzo corrente
        "open":       Decimal(str(d["o"])),
        "high":       Decimal(str(d["h"])),
        "low":        Decimal(str(d["l"])),
        "prev_close": Decimal(str(d["pc"])),
    }


if __name__ == "__main__":
    q = finnhub_quote("AAPL")
    print(f"AAPL: {q['current']} USD (apertura {q['open']}, massimo {q['high']})")

5.3. JavaScript / Node.js — Twelve Data (OHLC, volume)

javascript
// Gratis: ~800 richieste al giorno. La chiave è obbligatoria.
const API_KEY = process.env.TWELVE_DATA_KEY;

async function twelveQuote(symbol) {
  const url = new URL("https://api.twelvedata.com/quote");
  url.searchParams.set("symbol", symbol);
  url.searchParams.set("apikey", API_KEY);

  const res = await fetch(url);
  const d = await res.json();
  if (d.status === "error") throw new Error(d.message);

  return {
    symbol: d.symbol,
    open: d.open,        // stringhe: non le convertiamo in Number senza necessità
    high: d.high,
    low: d.low,
    close: d.close,
    volume: d.volume,    // il volume è un intero grande: stringa/BigInt
    exchange: d.exchange,
  };
}

twelveQuote("MSFT").then((q) =>
  console.log(`${q.symbol} (${q.exchange}): close ${q.close}, vol ${q.volume}`)
);

In JavaScript non esiste un tipo decimale e Number è un double. Per questo i prezzi restano stringhe; per l’aritmetica sui prezzi si usano decimal.js/big.js, e per il volume il BigInt nativo.

5.4. Go — Alpha Vantage (candele giornaliere OHLCV)

go
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "sort"
    "time"
)

// Alpha Vantage: gratis, 25 richieste/giorno, 5/min.
type avDaily struct {
    Series map[string]struct {
        Open   string `json:"1. open"`
        High   string `json:"2. high"`
        Low    string `json:"3. low"`
        Close  string `json:"4. close"`
        Volume string `json:"5. volume"`
    } `json:"Time Series (Daily)"`
}

func dailyBars(symbol string) (avDaily, error) {
    key := os.Getenv("ALPHAVANTAGE_KEY")
    url := fmt.Sprintf(
        "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=%s&apikey=%s",
        symbol, key,
    )
    client := &http.Client{Timeout: 15 * time.Second}
    resp, err := client.Get(url)
    if err != nil {
        return avDaily{}, err
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    var out avDaily
    if err := json.Unmarshal(body, &out); err != nil {
        return avDaily{}, err
    }
    return out, nil
}

func main() {
    bars, err := dailyBars("IBM")
    if err != nil {
        panic(err)
    }
    // individuiamo la candela più recente per data
    dates := make([]string, 0, len(bars.Series))
    for d := range bars.Series {
        dates = append(dates, d)
    }
    sort.Strings(dates)
    last := dates[len(dates)-1]
    b := bars.Series[last]
    // i prezzi restano stringhe; per i calcoli, shopspring/decimal
    fmt.Printf("IBM %s: O=%s H=%s L=%s C=%s V=%s\n",
        last, b.Open, b.High, b.Low, b.Close, b.Volume)
}

La comodità è che Alpha Vantage consegna i prezzi come stringhe: la precisione decimale si conserva «di fabbrica»; basta non convertirle in float64.

5.5. C# / .NET — Yahoo Finance (endpoint non ufficiale, decimal)

c#
using System.Text.Json;

// ⚠️ Endpoint non ufficiale di Yahoo Finance: lo stesso usato dalla libreria yfinance.
// Va bene per prototipi e formazione, ma senza garanzie e NON per la produzione.
public static class YahooChart
{
    private static readonly HttpClient Http = new();

    public static async Task<(string Date, decimal Close)> LastDailyCloseAsync(string symbol)
    {
        var url = $"https://query1.finance.yahoo.com/v8/finance/chart/{symbol}?interval=1d&range=5d";

        var req = new HttpRequestMessage(HttpMethod.Get, url);
        req.Headers.UserAgent.ParseAdd("Mozilla/5.0"); // Yahoo richiede uno User-Agent

        var resp = await Http.SendAsync(req);
        resp.EnsureSuccessStatusCode();
        using var doc = JsonDocument.Parse(await resp.Content.ReadAsStreamAsync());

        var result = doc.RootElement.GetProperty("chart").GetProperty("result")[0];
        var timestamps = result.GetProperty("timestamp");
        var closes = result.GetProperty("indicators")
            .GetProperty("quote")[0].GetProperty("close");

        var i = timestamps.GetArrayLength() - 1;
        var unix = timestamps[i].GetInt64();
        var close = closes[i].GetDecimal();    // decimal: corretto per un prezzo
        var date = DateTimeOffset.FromUnixTimeSeconds(unix).ToString("yyyy-MM-dd");

        return (date, close);
    }
}

class Program
{
    static async Task Main()
    {
        var (date, close) = await YahooChart.LastDailyCloseAsync("AAPL");
        Console.WriteLine($"AAPL close {date}: {close} USD");
    }
}

Questo esempio mostra con onestà la via «da scraping» attraverso Yahoo: non serve una chiave e i dati sono vicini al tempo reale, ma l’endpoint non è ufficiale e può cambiare senza preavviso — non ci si può costruire sopra la produzione. I simboli seguono la convenzione di Yahoo: i titoli di Borsa Italiana portano il suffisso .MI (ENI.MI, ISP.MI).


6. In quale tipo di dato archiviare le quotazioni

La regola di base è esattamente la stessa dell’articolo sui tassi di cambio: prezzi e grandezze monetarie solo in un tipo decimale; niente float/double. La virgola mobile binaria accumula errori di arrotondamento e, nei backtest su periodi lunghi, questo produce scostamenti impossibili da riconciliare.

Campo Tipo Perché
Prezzo (open/high/low/close, last) NUMERIC(18,6) / Decimal / decimal precisione senza perdite
Volume (volume) BIGINT / int64 miliardi di titoli non entrano in un int normale
Adjusted close NUMERIC(18,6) conservarlo accanto al prezzo «grezzo», non al suo posto
Momento della barra TIMESTAMPTZ sempre con il fuso orario della borsa
Valuta dello strumento CHAR(3) (ISO 4217) per convertire il portafoglio

Esempio di tabella per le candele (OHLCV):

sql
CREATE TABLE quotes (
    id           BIGSERIAL PRIMARY KEY,
    source       VARCHAR(16)   NOT NULL,   -- 'FINNHUB', 'AV', 'TWELVE', 'YAHOO'
    exchange     VARCHAR(16)   NOT NULL,   -- borsa: 'NASDAQ', 'BIT'...
    ticker       VARCHAR(20)   NOT NULL,   -- AAPL, ENI...
    isin         CHAR(12),                 -- identificatore affidabile dello strumento
    ccy          CHAR(3)       NOT NULL,   -- valuta del prezzo: USD, EUR...
    ts           TIMESTAMPTZ   NOT NULL,   -- momento della barra/quotazione
    interval     VARCHAR(8)    NOT NULL DEFAULT '1d', -- 1m | 1h | 1d
    open         NUMERIC(18,6) NOT NULL,
    high         NUMERIC(18,6) NOT NULL,
    low          NUMERIC(18,6) NOT NULL,
    close        NUMERIC(18,6) NOT NULL,
    adj_close    NUMERIC(18,6),            -- rettificato per split/dividendi
    volume       BIGINT        NOT NULL DEFAULT 0,
    fetched_at   TIMESTAMPTZ   NOT NULL DEFAULT now(),
    UNIQUE (source, exchange, ticker, interval, ts)
);

Conviene archiviare a parte le operazioni societarie (gli split con il loro coefficiente e i dividendi con la loro data), perché quando compaiono bisogna ricalcolare retroattivamente lo storico di adj_close.


7. Consigli pratici

  • Distingui tempo reale, ritardo ed EOD. Per la maggior parte dei compiti (portafoglio, analisi, dashboard) bastano i dati in ritardo o end-of-day: costano meno e hanno licenze più semplici.
  • Rispetta le licenze. È la grande differenza rispetto ai tassi delle banche centrali. I dati di borsa in tempo reale sono regolamentati (le borse, la FINRA, la SEC), e perfino mostrarli di nuovo ai tuoi utenti può richiedere un accordo. Per questo, ad esempio, il tempo reale USA di Alpha Vantage è a pagamento. Prima di pubblicare dati, controlla le condizioni della fonte.
  • Costruisci tutto attorno alla cache. Con un limite di 25 richieste al giorno non c’è alternativa: scarica lo storico una volta e, da lì in poi, solo aggiornamenti incrementali; il resto lo servi dalla tua base.
  • Tratta gli split come il «nominale». Un salto del prezzo di N volte è quasi sempre un’operazione societaria, non un movimento del mercato. Il semplice controllo «variazione rispetto al giorno precedente superiore a X%» intercetta sia gli split sia gli errori di estrazione.
  • Identifica lo strumento con borsa + ticker (meglio ancora con ISIN/FIGI). Uno stesso ticker vive su più piazze.
  • Ricorda sessioni e giorni festivi. Una risposta vuota nel weekend è normale; prendi l’ultima data disponibile nella risposta, non quella richiesta.
  • Non costruire la produzione sullo scraping di Yahoo. Per un prototipo è perfetto; per un servizio che deve vivere anni, usa un’API con garanzie.

8. Dove si applica

  • Tracker di portafoglio — valore corrente degli asset, P&L, conversione nella valuta base.
  • Trading algoritmico e bot — segnali ed esecuzione (qui servono già il tempo reale e il book).
  • Dashboard e BI — visualizzazione del mercato, tagli settoriali.
  • Screener e backtesting — filtro dei titoli e verifica delle strategie sullo storico (OHLCV + adjusted).
  • Robo-advisor e fintech — raccomandazioni e gestione automatizzata.
  • Contabilità e valutazione — rivalutazione degli investimenti al prezzo di mercato a una data.
  • Alert — notifiche quando il prezzo raggiunge un livello definito.

In sintesi

L’estrazione di quotazioni di borsa somiglia tecnicamente a quella dei tassi di cambio: gli stessi tipi decimali, la stessa cache, la stessa cautela con i salti «tecnici» (solo che qui il ruolo del nominale lo giocano gli split). Ma si aggiungono tre cose: il ticker è legato a una borsa (e, meglio, a un ISIN/FIGI), lo storico richiede rettifiche per le operazioni societarie e — la cosa più importante nella pratica — i dati sono sotto licenza: gratis ci sono quasi solo il ritardo e l’end-of-day, mentre il tempo reale costa e arriva vincolato a condizioni. Per iniziare bastano i piani gratuiti di Finnhub, Twelve Data o Alpha Vantage, che coprono anche le coppie di valute e si ricollegano così al tema dell’articolo gemello sui tassi di cambio. Somma la disciplina dei tipi, la cache e il rispetto delle licenze, e avrai quotazioni su cui appoggiare i tuoi calcoli.