Skip to content
Finance Lessons

Deep Learning for Market Data

Embeddings for Categorical & Alternative Data

Turning tickers, sectors, regimes, calendars and news into dense learned vectors — why entity embeddings crush one-hot encodings, the dimension rules of thumb, sharing a representation across a pooled cross-section, and the leakage traps embeddings make especially easy to fall into.

14 min Updated Jun 19, 2026

So far this course has fed your networks numbers — returns, volumes, volatilities, the nicely-behaved continuous stuff a matrix multiply is happy to chew on. But real market data is riddled with labels: the ticker JPM, the sector “Financials”, the country “US”, the exchange “NYSE”, the day-of-week “Monday”, the regime tag “risk-off”. And then there’s the truly unstructured swamp — news headlines, 10-K filings, earnings-call transcripts.

A neural net does not know what “Financials” means. It multiplies and adds. It cannot multiply the string "JPM". So before any of your hard-won RNN / TCN / transformer machinery can touch a category, you have to turn that label into a vector. The lazy way is one-hot encoding. The good way — the way that lets a bank learn from what other banks did — is an embedding. This lesson is about doing it well, and about the leakage traps that embeddings make extraordinarily easy to fall into.

One-hot encoding is a waste

You one-hot encode a universe of 3,000 tickers and feed the result straight into the first dense layer. What is the single biggest structural problem with this representation (before we even talk about parameters)?

Analogy. One-hot encoding is like giving every employee in a 3,000-person company a badge that is a single light in a 3,000-light panel — yours is light #1,742, lit; everyone else’s is dark. The badge tells you who someone is and absolutely nothing about what they’re like. Two analysts on the same desk are as “far apart” as the CEO and the night janitor.

Definition. One-hot encoding maps a category cc from a vocabulary of cardinality KK to the indicator vector ec{0,1}Ke_c \in \{0,1\}^K — a KK-long vector that is 11 in position cc and 00 everywhere else. All distinct one-hot vectors are mutually orthogonal: eiej=0e_i \cdot e_j = 0 for iji \neq j, and eiej=2\lVert e_i - e_j \rVert = \sqrt{2} for every pair. Constant distance, zero similarity structure.

Worked example. Take a toy universe of four tickers and encode them:

TickerSectorOne-hot vector
JPMFinancials[1, 0, 0, 0]
BACFinancials[0, 1, 0, 0]
XOMEnergy[0, 0, 1, 0]
AAPLTechnology[0, 0, 0, 1]

Distance between the two banks, eJPMeBAC=(10)2+(01)2=21.41\lVert e_{\text{JPM}} - e_{\text{BAC}} \rVert = \sqrt{(1-0)^2 + (0-1)^2} = \sqrt{2} \approx 1.41. Distance between a bank and the oil major, eJPMeXOM=21.41\lVert e_{\text{JPM}} - e_{\text{XOM}} \rVert = \sqrt{2} \approx 1.41. Identical. The encoding insists JPM and BAC are no more similar than JPM and XOM.

Now scale up. With K=3,000K = 3{,}000 tickers, each input is a 3,000-long sparse vector. A first dense layer with h=64h = 64 hidden units needs K×h=3,000×64=192,000K \times h = 3{,}000 \times 64 = 192{,}000 weights just to read the ticker — and most of them see a single non-zero example at a time, so they update glacially.

Warning:

The curse, again

Remember lesson 1’s complaint that deep nets starve on markets’ tiny effective sample size? One-hot encoding pours gasoline on that fire. A 3,000-wide sparse input multiplies your parameter count without adding information, and each ticker’s column only gets a gradient on the rows where that exact ticker appears. High cardinality + low effective-N is precisely the regime where one-hot encoding overfits fastest.

When to use it

One-hot is fine — even preferable for its simplicity — when cardinality is low (say K10K \le 10: day-of-week, a handful of regimes) and you have plenty of data per category. At that scale the parameter cost is trivial and the lack of learned similarity barely matters. It’s only at high cardinality that it becomes a liability.

Fill in the structural facts about one-hot vectors.

Pick the right option for each blank, then check.

Any two distinct one-hot vectors are , which means the model receives information about category similarity, and a first dense layer's parameter count grows in the cardinality.

Entity embeddings

An entity embedding layer maps a ticker id to a dense vector. Where do the values in that vector come from?

Analogy. This is word2vec for stocks. Word2vec learned that “king − man + woman ≈ queen” not because anyone told it, but because words that appear in similar contexts ended up with similar vectors. An entity embedding does the same with tickers: two banks that respond alike to rate moves get nudged toward each other in the embedding space, because sharing a representation lowers the loss. You never hand-label “these are both banks” — the geometry falls out of the training signal.

Definition. An embedding maps each category id c{1,,K}c \in \{1, \dots, K\} to a dense vector vcRm\mathbf{v}_c \in \mathbb{R}^m via a lookup into an embedding matrix ERK×mE \in \mathbb{R}^{K \times m}, where row cc is vc\mathbf{v}_c. Equivalently it’s a dense layer with no bias and no activation applied to the one-hot vector: vc=Eec\mathbf{v}_c = E^{\top} e_c. The matrix EE is trained jointly with the task. The embedding dimension mm (with mKm \ll K) is a hyperparameter you choose.

Dimension rule of thumb. Two popular heuristics:

mmin ⁣(50,K2)ormK0.25.m \approx \min\!\left(50, \left\lceil \tfrac{K}{2} \right\rceil\right) \qquad \text{or} \qquad m \approx K^{0.25}.

The first (the fast.ai rule) caps the size for huge vocabularies; the second grows very slowly. Both encode the same instinct: more categories deserve a richer space, but with sharply diminishing returns.

Worked example. With K=3,000K = 3{,}000 tickers, the min-rule gives mmin(50,3000/2)=min(50,1500)=50m \approx \min(50, \lceil 3000/2 \rceil) = \min(50, 1500) = 50. The fourth-root rule gives m30000.257.48m \approx 3000^{0.25} \approx 7.4 \to 8. People usually land somewhere in between; say m=32m = 32. Parameter count of the embedding: K×m=3,000×32=96,000K \times m = 3{,}000 \times 32 = 96{,}000 — versus the 192,000192{,}000 the one-hot dense layer needed above for h=64h=64, and crucially every row now accumulates shared structure rather than memorising one ticker in isolation.

PropertyOne-hot encodingEntity embedding (m=32m=32)
Vector lengthK=3,000K = 3{,}000m=32m = 32
Storage / params (read)K×h=192,000K \times h = 192{,}000K×m=96,000K \times m = 96{,}000
Encodes similarity?No — all pairs equidistantYes — learned geometry
Generalises across cats?NoYes (banks share structure)
Sparse vs denseSparse, mostly zerosDense, all dimensions used
Interpretable axes?Trivially (one per cat)Not directly (latent factors)

Pick a term, then click its definition.

When to use it

Reach for an entity embedding when a categorical is high-cardinality and you have a large pooled cross-section so many categories can pool statistical strength (more on this below). It shines exactly where one-hot drowns. But it is not free: you’ve added K×mK \times m parameters that can memorise, and a learned space that — like every learned thing here — can leak the future if you’re careless.

Embedding alternative & text data

You want to feed yesterday's news flow into a return model. Which approach turns a headline into something the network can actually consume, without you hand-coding features?

Analogy. A pretrained text embedding is a translator you hired who already speaks the language fluently. You don’t teach them English from scratch on your 4,000 trading days of headlines — you hand them the sentence and they hand you back a compact “meaning vector”. Your downstream model only has to learn what that meaning implies for returns, not what English is.

Definition & taxonomy. “Embedding alternative data” really means producing a dense vector representation of unstructured or semi-structured inputs:

Alt-data inputRepresentationTypical dimension
News headline / filingPretrained sentence/document embedding384–1536
Sentiment of textScalar (or small vector) sentiment score1–3
Document topic mixTopic-model vector (e.g. LDA proportions)20–100
Continuous alt-signalStandardised scalar, optionally binned + embedded1–8
Categorical alt-tagEntity embedding (same machinery as tickers)K0.25K^{0.25}-ish

The point is uniform: by the time it reaches the network, everything — ticker, sector, news, sentiment — is a dense vector, and the downstream layers don’t care which column came from a lookup table and which came from a language model.

Worked example (out-of-vocabulary). Your training universe has K=3,000K = 3{,}000 tickers. In live trading a freshly-IPO’d name appears that the embedding table has never seen. With a raw lookup, there is no row for it — the model crashes or silently maps to garbage. The fix is to reserve an extra row at index 00 for an <UNK> (“unknown”) token: build ER(K+1)×mE \in \mathbb{R}^{(K+1) \times m}, route every unseen id to row 00, and train that row by randomly mapping some rare tickers to <UNK> during training so it learns a sensible “generic new name” vector. Same trick handles out-of-vocabulary words in text encoders.

Info:

Cardinality discipline

Before embedding any categorical, count distinct values as they exist at decision time and decide an OOV policy. A “country” field with 40 values is cheap; a “news source URL” field with 80,000 values is a memorisation trap unless you bucket the long tail into <UNK>. Cardinality is a design decision, not a given.

When to use it

Pretrained text embeddings earn their keep when the signal lives in the language (tone of a filing, novelty of a headline) and your own dataset is far too small to learn English from scratch — which is essentially always in finance. Skip the heavy encoder when a one-line sentiment score already captures everything you’ll act on; a 1,536-dim vector feeding a model with 3,000 training days is a fast track to overfitting.

Sort each market input by the representation that fits it best.

Place each item in the right group.

  • Exchange / venue code
  • Full text of an earnings-call transcript
  • A news headline string
  • GICS sector label
  • Ticker id from a 3,000-name universe
  • 10-K risk-factors section

Leakage traps embeddings make easy

You learn a ticker embedding on your ENTIRE dataset (2010–2025), then run a purged, walk-forward backtest on top of those frozen vectors and report a beautiful Sharpe. Why is the result still contaminated?

This is the section that matters. Embeddings are a learned representation, and anything learned can memorise the future. Every leakage lesson from Machine Learning for Alpha — preprocessing-on-full-data, survivorship, look-ahead — has an embedding-shaped version that is easier to commit and harder to spot.

The five traps.

#TrapWhat leaks
aEmbedding fit on the full sampleTest-period co-occurrences bleed into the vectors (preprocessing-on-full-data)
bSurvivorship in the entity setVocabulary built from names that survived to today silently drops the dead
cLook-ahead labels (regime / reclassification)A tag assigned with hindsight encodes the outcome it should predict
dA category that encodes the futuree.g. a was_acquired flag is a label masquerading as a feature
eTarget leakage via memorisationOn tiny data the embedding learns an id→outcome map instead of structure

Worked example (regime look-ahead). Suppose you tag each day with a regime label and embed it. You define “risk-off” by checking whether the next 20 days had a drawdown exceeding 10%:

regimet={risk-offif min1k20rt+kcum<0.10risk-onotherwise.\text{regime}_t = \begin{cases} \text{risk-off} & \text{if } \min_{1 \le k \le 20}\, r_{t+k}^{\text{cum}} < -0.10 \\ \text{risk-on} & \text{otherwise.} \end{cases}

That label at time tt is a function of returns from t+1t{+}1 to t+20t{+}20. Embed it, feed it to a model predicting rt+1r_{t+1}, and your “feature” already contains the answer — the embedding for “risk-off” will look spectacularly predictive, and on live data, where you can’t see the next 20 days, it evaporates. The fix: define the regime using only information available at or before tt (a trailing volatility threshold, a moving-average crossover), and assign it causally.

Warning:

Embeddings launder leakage — audit every category like a hostile witness

The danger is that an embedding is opaque, so leakage hides inside a dense vector instead of a named column you’d flag in review. Three habits: (1) Fit the embedding inside the training fold only — refit per walk-forward window, never on the pooled full sample; treat it exactly like a fitted scaler. (2) Build the vocabulary point-in-time — the set of tickers/sectors must be the universe as known on the decision date, including names later delisted, or you’ve re-imported survivorship through the back door. (3) Interrogate every categorical: “could a human assign this label without knowing the future?” If a regime tag, sector reclassification, or was_acquired flag needs hindsight, it is a target, not a feature, no matter how dense the vector that hides it.

Which of these embedding setups leak future information? Select every one that does.

When the leak is subtle

The nastiest case is (e), memorisation on tiny data. With K=3,000K = 3{,}000 tickers, m=32m = 32, and only ~1,000 days of pooled history, the embedding can quietly learn an id→average-return lookup — a fancy target-encoding fit on the training labels. It validates poorly out-of-sample and you’ll blame “regime change”. The tell: in-sample fit improves sharply with mm while out-of-sample stalls or degrades. Shrink mm, add regularisation/dropout on the embedding, or question whether you have the data to embed this category at all.

A teammate refits ticker embeddings on the full 15-year history “for stability”, then runs purged walk-forward CV on top. Why is the purging useless here, in the vocabulary of Machine Learning for Alpha?

Answer. Purged CV protects against temporal leakage between the train and test labels during model fitting — it removes overlapping windows and embargoes near the split. But the embedding was fit before the CV even started, on data that includes the test period. That is preprocessing-on-full-data leakage: the features handed to the CV already encode test-period structure, so purging the model fit changes nothing. The cure is identical to the one for scalers and PCA in that course — fit the transform inside the training fold only, then apply the frozen-from-train transform to the held-out data. An embedding is just a fitted transform with parameters; treat it like one.

When embeddings earn their keep

You have a categorical with 12 distinct values, 800 training rows, and you're choosing between an entity embedding and a gradient-boosted tree with native categorical handling. What's the sober call?

Analogy. Embeddings are a team sport. Their whole advantage is letting categories pool strength — one bank learning from a thousand bank-days. If you’ve got two banks and a long weekend of data, there’s no team to pool; you’re just memorising. Embeddings reward a crowded stadium, not an empty one.

The decision, made precise. Favour an embedding when both hold: (1) cardinality is high (KK in the hundreds-to-thousands), so one-hot is wasteful and similarity structure is worth learning; and (2) you have a large pooled cross-section — many entities ×\times many periods — so the embedding has enough examples per latent factor to learn structure rather than ids. Avoid it when data is tiny (it memorises), when cardinality is low (an ordinal or target encoding, or just a one-hot, is simpler), or when a tree will do.

SituationCardinalityEffective dataBest tool
3,000 tickers, 15y daily, pooled panelHighLargeEntity embedding
Day-of-week featureLow (7)AnyOne-hot / cyclical encoding
12 sectors, 800 rowsLow–medSmallTarget/ordinal encoding or a tree
Mixed tabular features, modest dataMixedModestGradient-boosted trees
News text, signal in languagen/aSmall own-dataPretrained text embedding
Info:

Foreshadowing lesson 6

Notice how often the honest answer above is “use a tree”. Gradient-boosted trees handle categoricals natively (no one-hot, no embedding matrix), need no GPU, and resist overfitting on the small, noisy, low-signal panels that define finance. The next lesson makes the uncomfortable case directly: on most tabular market problems, trees usually win — and embeddings are the exception you reach for, not the default.

When to use it — the trade-off

The embedding trade is expressive power and shared structure bought with parameters, opacity, and a fresh surface for leakage. You take the trade when high cardinality and a deep pooled cross-section make the shared structure real and learnable. You decline it when the data can’t fund the parameters, or when a tree gives you 95% of the benefit with 5% of the foot-guns.

Summarise the embedding decision rule.

Pick the right option for each blank, then check.

Entity embeddings pay off when cardinality is and the pooled cross-section is ; with little data the embedding tends to , and for plain tabular panels a gradient-boosted is often the simpler winner.

Recap

Big picture

Embeddings for categorical & alt-data

  • Embeddings
    • One-hot
      • Orthogonal → no similarity
      • K-wide, params blow up
      • OK only when K is small
    • Entity embedding
      • Lookup table E (K×m), learned
      • m ≈ min(50, ⌈K/2⌉) or K^0.25
      • word2vec for stocks: banks cluster
    • Alt & text data
      • Pretrained sentence/doc vectors
      • Sentiment / topic vectors
      • OOV → <UNK> row
    • Leakage traps
      • Fit on full sample
      • Survivorship vocabulary
      • Look-ahead regime labels
      • Category encodes the future
      • Memorisation on tiny data
    • When to embed
      • High cardinality + big panel
      • Not tiny data, not low K
      • Else: trees usually win
From labels to dense learned vectors — and the leakage traps along the way.

Embeddings check

Question 1 of 50 correct

Why does one-hot encoding fail to express that two banks are similar?

Check your answer to continue.

Mark lesson as complete