How to Read a Futures Symbol (ESZ6, CLF27)
Read any futures symbol on sight: root, month code, year digit. Worked examples for ESZ6, CLF27 and ZCK6, the full month table, and two traps to avoid.
A futures symbol packs three things into a handful of characters: a product root, a one-letter month code, and the expiry year. ESZ6 reads as ES (the E-mini S&P 500), Z (December) and 6 (2026), so it names the December 2026 E-mini contract. Learn the twelve month letters and the rest of the board opens up, including CLF27 (January 2027 crude oil) and ZCK6 (May 2026 corn).
The three parts of a futures symbol
The root comes first. One to three characters, naming the product and nothing about the delivery date. ES is the E-mini S&P 500, NQ the E-mini Nasdaq 100, CL WTI crude oil, GC gold, ZC corn, ZN the 10-year Treasury note, ZQ 30-day fed funds. Roots can carry a digit, as 6E (euro FX) does, and that digit is usually the first thing to break a parser written for letters only. Some platforms print a leading slash on the root to mark the instrument as a future.
The month code comes next: exactly one letter, from a fixed alphabet of twelve. The letters are arbitrary. They are not month initials, and no rule generates them, which is why this is the one table worth bookmarking.
The year closes the symbol, written with one digit or two. Z6 and Z26 name the same December 2026 delivery. Which one you meet is a property of the vendor, not of the contract.
Read in that order, ZNU6 is the September 2026 10-year Treasury note future and GCM7 is June 2027 gold.
Futures month codes: the full F to Z table
FJanuaryGFebruaryHMarchJAprilKMayMJuneNJulyQAugustUSeptemberVOctoberXNovemberZDecember
Four of those letters do most of the work in financial futures. H, M, U and Z, the March, June, September and December cycle, carry the listings for equity index, short-rate and currency contracts. Physical markets use more of the alphabet on their own rhythms: corn lists H, K, N, U and Z, crude oil lists all twelve.
That quarterly rhythm shows up outside futures. Listed equity options expire on the third Friday of the month, the same anchor date the index futures settle against. The panel below totals option volume across four liquid underlyings over the first half of 2026, counting only expirations more than 120 days out, bucketed by the calendar month of expiry with the futures month code attached.
The exact SQL behind every number
SELECT
concat(
arrayElement(['F','G','H','J','K','M','N','Q','U','V','X','Z'], toMonth(expiration_date)),
' (',
arrayElement(['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'], toMonth(expiration_date)),
')'
) AS code_bucket,
round(toFloat64(sum(volume)) / 1e6, 2) AS contracts_millions,
countDistinct(expiration_date) AS expiry_dates
FROM global_markets.options_greeks
WHERE underlying_symbol IN ('SPY', 'QQQ', 'AAPL', 'NVDA')
AND date >= '2026-01-02'
AND date < '2026-07-01'
AND days_to_expiry > 120
AND volume > 0
AND iv_converged = 1
GROUP BY toMonth(expiration_date)
ORDER BY contracts_millions DESCThe busiest bucket is Z (Dec), at 13.5 million contracts across 4 separate expiry dates. The thinnest is G (Feb) at 0.05 million. Long-dated listings cluster on a few months, and the letters naming those months are the ones you read most often. Those same quarterly dates are when index futures, index options and single-stock options all expire in one session, which is triple witching.
Trap 1: one year digit or two
ESZ6 and ESZ26 are the same contract, written by two vendors with different habits. The trouble starts once history arrives. A single digit only identifies a year inside one decade, so ESZ6 is December 2026 or December 2016, and a symbol file spanning both holds both. Nothing inside the string settles it. The convention comes from outside: the vendor's documentation, or the range of dates the file covers.
Two rules keep this from becoming a data bug. Resolve the year to four digits at parse time, and keep the raw string beside it so you can always show what the vendor sent. Then pick an explicit pivot, a rule mapping one digit into a ten-year window around the date you are working with, and write the pivot into the code rather than carrying it in your head.
Several delivery years also run at once, which is the other half of the problem.
The exact SQL behind every number
SELECT
toYear(expiration_date) AS expiry_year,
countDistinct(expiration_date) AS listed_expiry_dates,
toString(min(expiration_date)) AS first_expiry,
toString(max(expiration_date)) AS last_expiry
FROM global_markets.options_greeks
WHERE underlying_symbol = 'SPY'
AND date >= '2026-07-01'
AND date < '2026-08-01'
AND volume > 0
AND iv_converged = 1
GROUP BY toYear(expiration_date)
ORDER BY expiry_yearThrough July 2026 the SPY option calendar carried 44 distinct expiry dates inside 2026, and listings ran out to 2028, where 3 dates were open. Futures curves in rates and energy stretch further still. One digit carries all of that.
Trap 2: a continuous symbol is not a contract
ESZ6 is a contract. ES1!, ESc1 and ES=F are not. Those are continuous, or front-month, series: a construction pointing at whichever real contract is currently nearest, switching to the next one as each expires. A chart of a single contract stops when the contract does, and the continuous series is the workaround. You cannot trade one. You trade the specific contract it happens to reference today.
Two consequences follow. A price history under a continuous symbol is stitched from several contracts, and the join is a modelling choice: no adjustment, ratio adjustment and difference adjustment produce three different histories from the same trades. Stitching one properly is its own problem, and this post does not solve it. The second: a continuous symbol's identity changes while the string stays the same, so a series stored under ES1! alone no longer says which contract each print came from.
The switch itself is a roll. Activity drains out of the expiring contract over a few days and the next contract in the cycle takes over. The panel below follows two expiry dates in SPY options through one such window: the June 19 2026 quarterly and the September 18 2026 quarterly standing behind it.
The exact SQL behind every number
SELECT
toString(date) AS session_date,
round(toFloat64(sumIf(volume, expiration_date = '2026-06-19')) / 1e6, 2) AS jun_19_expiry_millions,
round(toFloat64(sumIf(volume, expiration_date = '2026-09-18')) / 1e6, 2) AS sep_18_expiry_millions
FROM global_markets.options_greeks
WHERE underlying_symbol = 'SPY'
AND date >= '2026-05-15'
AND date <= '2026-07-10'
AND volume > 0
AND iv_converged = 1
GROUP BY date
ORDER BY dateThe window opens on 2026-05-15 and closes on 2026-07-10, several weeks past the June date. On that closing session the June 19 column reads 0 million contracts, its expiry behind it, while the September 18 expiry carries 0.05 million and goes on trading. Nothing about the September symbol changed over those weeks. What changed is the contract a front-month series would have been pointing at.
The matched pair: futures and options symbols
Options symbols solve the same problem with the opposite design. The OSI format spells the date out in full, six digits of year, month and day, then a C or a P and a padded strike. Nothing is ambiguous and nothing is short. Futures kept the older convention: one letter for the month, one digit for the year, a symbol you can say out loud. If you work across both, read how to read an options symbol next, since the two formats sit side by side in most market data files.
Parse a list of futures symbols in Python
No package and no network connection are needed here; the standard library covers it. The block below parses a hardcoded list into root, month and year, prints each expiry, then sorts the contracts into calendar order.
python3 <<'PY'
import re
from collections import namedtuple
MONTHS = {
'F': ('January', 1), 'G': ('February', 2), 'H': ('March', 3),
'J': ('April', 4), 'K': ('May', 5), 'M': ('June', 6),
'N': ('July', 7), 'Q': ('August', 8), 'U': ('September', 9),
'V': ('October', 10), 'X': ('November', 11), 'Z': ('December', 12),
}
SYMBOL = re.compile(
r'^(?P<root>[A-Z0-9]{1,3}?)(?P<code>[FGHJKMNQUVXZ])(?P<year>[0-9]{1,2})$'
)
Contract = namedtuple('Contract', 'symbol root month month_num year')
# One year digit is ambiguous. Resolve it inside a ten-year window around pivot.
def resolve_year(digits, pivot):
if len(digits) == 2:
return 2000 + int(digits)
base = (pivot // 10) * 10 + int(digits)
for year in (base, base + 10, base - 10):
if pivot - 4 <= year <= pivot + 5:
return year
return base
def parse(symbol, pivot=2026):
match = SYMBOL.match(symbol.strip().upper())
if not match:
raise ValueError('not a futures contract symbol: ' + symbol)
name, number = MONTHS[match.group('code')]
return Contract(symbol, match.group('root'), name, number,
resolve_year(match.group('year'), pivot))
SYMBOLS = ['ESZ6', 'CLF27', 'ZCK6', 'GCM7', 'NQH6', 'ZNU6']
contracts = [parse(s) for s in SYMBOLS]
for c in contracts:
print(f'{c.symbol:<6} root={c.root:<3} expires {c.month} {c.year}')
print()
print('calendar order')
for c in sorted(contracts, key=lambda c: (c.year, c.month_num)):
print(f' {c.year}-{c.month_num:02d} {c.symbol}')
PY
ESZ6 root=ES expires December 2026
CLF27 root=CL expires January 2027
ZCK6 root=ZC expires May 2026
GCM7 root=GC expires June 2027
NQH6 root=NQ expires March 2026
ZNU6 root=ZN expires September 2026
calendar order
2026-03 NQH6
2026-05 ZCK6
2026-09 ZNU6
2026-12 ESZ6
2027-01 CLF27
2027-06 GCM7
The pattern is non-greedy on the root, which is what lets ZNU6 resolve. N is a valid month code, so the first pass reads the root as Z, fails on the leftover U6, then backtracks to root ZN, month U, year 6. Sorting on the pair of year and month number rather than on the string is the other half: alphabetically ESZ6 comes before NQH6, while in calendar order March 2026 comes first.
FAQ
What do the letters in a futures symbol mean?
The single letter before the year digits is the delivery month: F January, G February, H March, J April, K May, M June, N July, Q August, U September, V October, X November, Z December. Everything before that letter is the product root, and the digits after it are the year.
What does ESZ6 mean?
ESZ6 is the E-mini S&P 500 futures contract for December 2026. ES is the root, Z is the December month code, and 6 is the year digit. Some vendors write the identical contract as ESZ26.
Why do futures use month codes instead of month numbers?
The letter convention predates electronic trading and stayed in place once exchanges, clearing firms and data vendors had all built around it. One character also keeps the whole symbol short, which mattered on ticker tape and still matters in fixed-width data files.
What is the difference between ES and ES1!?
ES with a month and year attached, such as ESZ6, is one specific contract with one expiry. ES1! and similar forms are continuous front-month series: a construction that follows whichever contract is nearest and rolls to the next one as each expires. Only the specific contract is tradeable.
Where else do futures month codes appear?
Positioning reports and rate calendars use the same shorthand. The weekly positioning data covered in the COT report release schedule aggregates every contract month under one product, and short-rate contracts such as ZQ are read as market-implied probabilities in how markets price Fed rate odds.
Data notes and method
The three panels count listed US equity option contracts with a converged greeks solution and non-zero volume for the session, grouped by underlying symbol rather than by contract code. The month-code column is a label applied to the calendar month each contract expires in. Equity options are not futures. The panels are here to show the shape of the quarterly calendar the two share, and every window is pinned to fixed 2026 dates so the figures stay put under the prose.
Every panel above carries the SQL that produced it, one expander away. The same questions can be asked in plain English on the Strasmore terminal.