A build log on QTGNN v2, what happens when the message function in a graph attention network is a variational quantum circuit instead of a matrix multiply.
Not investment advice. This is a machine learning experiment. Nothing here is a trading system, I make no performance claims, and the limitations section at the end is the most important part of the post.
A graph neural network passes messages along edges. Normally that message function is a learned linear map. I made it a 4-qubit variational quantum circuit and pointed the whole thing at a graph whose nodes are stocks and whose edges are rolling price correlations.
Most price prediction models treat each ticker in isolation: feed in AAPL's history, predict AAPL's next move. That throws away the thing every trader knows: assets move together. NVDA and META are not independent draws, and neither are JPM and GS.
That relational structure is exactly what GNNs are for. So the 10 tickers become nodes in a fully-connected directed graph, and the edge weight w_ij is the Pearson correlation over a rolling 30-day window of normalised closing prices.
The rolling window is what makes this interesting rather than decorative: the graph is dynamic. Connectivity changes at every timestep, so the model sees the market's structure shift between regimes rather than assuming one fixed correlation matrix for two years of history.
The universe is 10 S&P 500 names across 5 sectors: AAPL, MSFT, GOOGL, NVDA and META in tech, JPM and GS in finance, JNJ in health, XOM in energy, AMZN in consumer. Roughly 500 trading days of OHLCV from yfinance. Price and volume are Min-Max normalised separately per ticker, because volume's raw magnitude would otherwise dominate price entirely.
Every node carries a 31-dimensional feature vector at every timestep, assembled from four sources:
Temporal (GRU). A Gated Recurrent Unit reads the last SEQ_LEN = 10 trading days of price and volume jointly as a 2-D sequence. Its final hidden state h ∈ ℝ³² is the temporal summary.
Sentiment (FinBERT). Recent headlines per ticker are pulled from Yahoo Finance and passed through ProsusAI/finbert, a BERT fine-tuned on financial text. It emits a 3-vector of (positive, negative, neutral) probabilities, averaged across headlines.
Macro. Three series aligned to the same trading calendar, each with an actual economic reason to be there:
| Symbol | What it is | Why it belongs |
|---|---|---|
^VIX |
CBOE Volatility Index | implied volatility, the market's fear gauge |
^TNX |
10-year Treasury yield | the risk-free rate; high yields pull capital out of equities |
GLD |
SPDR Gold Shares | safe-haven demand, rises in risk-off regimes |
Sector. A 5-dimensional one-hot.
Concatenated:
z_i = [ h_GRU ‖ sentiment_i ‖ sector_i ‖ macro_t ] ∈ ℝ⁴³
32 3 5 3
Note the two different numbers. The raw node vector is 31-dimensional, because the price and volume histories enter as 10 days each. After the GRU has folded those 20 numbers into a 32-dimensional hidden state, the embedding the graph actually passes around is 43-dimensional.
Here is the actual novelty. In a standard graph attention network, the message from node j to node i is a learned linear transform of j's embedding. In QTGNN it is a circuit:
m_{j→i} = QCircuit( tanh(W·z_j)·π , θ , corr_ij )
The node embedding is squashed with tanh and scaled by π, which maps it into a rotation angle: the thing a qubit can actually consume. The correlation corr_ij is injected as its own rotation, so the edge weight is not a scalar multiplier bolted on afterwards; it participates in the state evolution.
Two ideas are stacked here.
Data re-up (N_REUP = 4). Pérez-Salinas et al. (2020) showed that repeating the data encoding interleaved with trainable layers lets a quantum model represent arbitrary Fourier series of its input, making it a universal function approximator on the device. Encode once and you are stuck with a very limited function class; encode four times and the expressivity opens up. It is the quantum analogue of depth.
A Hardware-Efficient Ansatz (N_Q_LAYERS = 3). The trainable part: alternating single-qubit RY and RZ rotations with nearest-neighbour CNOT entanglers. "Hardware-efficient" means it is built from gates real devices actually implement natively, rather than a mathematically elegant ansatz that compiles into a thousand physical operations.
Measuring ⟨Z₀⟩…⟨Z₃⟩ ∈ [−1,1] gives a 4-dimensional message vector. From there it is ordinary GAT machinery: a learnable linear layer produces attention coefficients α_ij, softmax-normalised per destination node; aggregated messages combine with the node's own embedding through a residual connection and LayerNorm. A second QGAT layer refines, and a linear readout maps each node's 4-D quantum output to one scalar, the predicted next-day normalised price delta.
The whole thing trains end-to-end. PennyLane differentiates through the circuit, so the quantum weights θ sit in the same backward pass as the GRU.
Never shuffle a financial time series. Shuffling before splitting leaks the future into the training set, and the model will happily report a beautiful validation number that means nothing. QTGNN uses walk-forward validation: the training window grows sequentially and each validation window immediately follows its training period, 5 folds over 500 days, validation fixed at 30 days.
Two learning rates, and the quantum one is 10× smaller. Adam runs with classical weights at 1e-2 and quantum circuit weights at 1e-3. This is a hedge against barren plateaus: the phenomenon where gradients in a variational circuit vanish exponentially with qubit count and depth. On a plateau, a learning rate tuned for the classical half will push the circuit parameters around on essentially no gradient signal, which is worse than not moving. A smaller step is the cheap mitigation. (The real ones, local cost functions, smart initialisation and shallower ansätze, are future work.)
Everything else:
| Parameter | Value |
|---|---|
EPOCHS /PATIENCE |
60 / 10 (early stopping) |
BATCH_SIZE |
8 graphs |
SEQ_LEN /GRU_HIDDEN |
10 days / 32 |
N_WIRES /N_Q_LAYERS /N_REUP |
4 / 3 / 4 |
CORR_WIN |
30 days |
N_FOLDS /VAL_SIZE |
5 / 30 days |
Loss is MSE on the next-day normalised price delta.
There is a second metric printed each epoch, and it is worth stopping on because I had it wrong. My report calls it an annualised Sharpe ratio, S = (μ_e/σ_e)·√252, where μ_e and σ_e are the mean and standard deviation of the per-sample prediction errors. That is not a Sharpe ratio. A Sharpe ratio is mean excess return over the standard deviation of returns; mean error over standard deviation of error is a different quantity, and it points the wrong way: a model with a large, consistent bias scores wonderfully on it, because a big μ_e over a small σ_e is exactly what a confidently wrong model produces.
So it is not a trading metric, it is closer to an inverse coefficient of variation of the residuals, and the report's thresholds ("above 1.0 is acceptable in live trading") do not transfer to it at all. Leaving it labelled Sharpe would have been the single most misleading thing in the project, which is a good argument for writing the post: I only caught it because I had to explain the formula in prose.
Everything above runs on PennyLane's default.qubit: a simulator, on my CPU. This is worth being precise about, because "quantum machine learning" invites the wrong assumption.
An N-qubit state lives in ℂ^(2^N). For 4 qubits that is a 16-dimensional complex vector, and every gate is a dense matrix multiply against it. Perfectly cheap.
That exponential is the entire story of the field. At ~50 qubits the state vector no longer fits in any machine that exists, which is simultaneously why simulation stops and why real quantum hardware is interesting. At 4 qubits I am nowhere near that line, which means I get none of the theorised advantage. What I have is a circuit that is a differentiable, entangling, non-linear function with an unusual inductive bias, that happens to be cheap enough to simulate exactly.
That is a legitimate thing to study. It is not a speedup, and anyone telling you their 4-qubit simulated model beats a classical baseline because of quantum should be asked which baseline.
The limitations are the honest half of this project:
default.qubit on CPU. Any real advantage requires QPU execution or at minimum GPU simulation via lightning.gpu.
Next steps, in the order I would do them: GPU simulation, a live news feed, 50+ tickers with sparse attention, and a real back-test with position sizing and costs, so that there is finally a number that means profit rather than error.
The interesting question in hybrid QML is not "is it faster", at this scale it obviously is not. It is where in the architecture you put the circuit. Most hybrid models bolt a variational circuit on as a classifier head, which makes it a small non-linear layer with an expensive gradient. Putting it in the message function means it runs once per edge, sees a pair of nodes and their relationship, and its output has to mean something relational. That is a much more specific role, and it makes the resulting model easier to reason about than "and then some qubits."
The rest of what I learned was not quantum at all. It was that walk-forward validation is non-negotiable, that a single learning rate across a hybrid model is a silent failure mode, and that a static feature dressed up as a dynamic one will quietly do nothing while you tune everything around it.
References: Pérez-Salinas et al., Data re-up for a universal quantum classifier (Quantum 4, 226, 2020) · Bergholm et al., PennyLane (arXiv:1811.04968) · Veličković et al., Graph Attention Networks (ICLR 2018) · Yang et al., FinBERT (arXiv:2006.08097) · Cerezo et al., Variational quantum algorithms (Nature Reviews Physics 3, 2021) · Orús et al., Quantum computing for finance (Reviews in Physics 4, 2019).
I'm a UPC student who mostly builds audio DSP and real-time systems. QTGNN was the project where I wanted to find out what a variational circuit actually feels like to train, and the answer was "slow, and unusually sensitive to your learning rate."