Os dealers de opções fazem hedge das opções que vendem. Quando o índice se move, o delta deles muda pelo gamma, e eles operam o ativo-objeto para ficar neutros. A gamma exposure soma isso em todos os contratos: quantos dólares de S&P 500 os dealers precisam comprar ou vender a cada movimento de 1% do índice.
O que importa é o sinal. Quando os dealers estão comprados em gamma, vendem nas altas e compram nas quedas, o que amortece o mercado. Quando estão vendidos em gamma, compram nas altas e vendem nas quedas, o que amplia os movimentos. O GEX ganhou popularidade com a ascensão das opções de vencimento no mesmo dia: em julho de 2026, um recorde de dois terços do volume de opções do SPX venceu no mesmo dia em que foi negociado, então esse fluxo de hedge hoje é uma força real sobre o índice.
Passo a passo, a página constrói:
Os números abaixo vêm da cadeia de 18 de setembro de 2026, 13h10 no horário de Nova York, um vencimento mensal. Só são necessários numpy, pandas e matplotlib. Leia as limitações antes de citar qualquer número daqui. O lado do dealer é uma premissa, não um dado.
O vídeo percorre cada passo em um dashboard ao vivo e depois roda o motor de trading em uma conta de paper trading da Interactive Brokers: regime, decisão, ordem, execução e resultado.
Publico os modelos enquanto os construo, as mudanças que faço neles e os resultados que não funcionaram. Mais curtos que estes textos, e são muitos mais.
Siga-me no XAs seções abaixo calculam a gamma exposure passo a passo, com as fórmulas, os gráficos e o código. Dois repositórios as acompanham: o motor de trading, com o regime, a decisão e a execução na Interactive Brokers, e os gráficos e modelos, incluindo a análise do vencimento no mesmo dia.
Bot de trading GEX no GitHub Gráficos e modelos de GEX no GitHub
Tudo desta página rodando ao mesmo tempo: o tape do SPX, a profundidade por strike com gamma e GEX de calls e puts, o net gamma ao longo do spot com o flip e as duas walls, e o P&L de uma estratégia com sua matriz de correlação. Um modo de sensibilidade move o spot, a volatilidade implícita, o tempo até o fechamento e o open interest do mesmo dia, e mostra como o flip e as walls se deslocam. Roda sobre uma cadeia da Cboe armazenada, com um tape simulado.
Gamma é a velocidade com que o delta de uma opção muda quando o índice se move. Black-Scholes o dá em forma fechada:
Γ = φ(d1) / (S σ √T) com d1 = [ ln(S/K) + (r + σ²/2) T ] / (σ √T)Multiplique pelo open interest e por 100 ações por contrato e você obtém quanto o delta do livro dos dealers muda por ponto. Multiplique por S² × 1% e isso vira dólares de índice a negociar por movimento de 1%. Calls entram positivas e puts negativas, pela premissa padrão de que os dealers estão comprados nas calls e vendidos nas puts que os clientes negociam:
GEX = Γ × OI × 100 × S² × 0,01 × (+1 para calls, −1 para puts)gex.py
R = 0.04 # flat rate for Black-Scholes
MULTIPLIER = 100 # shares per SPX contract
MIN_T = 4 / (365 * 24) # floor of four hours, so a contract expiring today stays finite
def bs_gamma(S, K, T, sigma):
"""Black-Scholes gamma, vectorised over K, T and sigma."""
T = np.maximum(np.asarray(T, float), MIN_T)
sigma = np.maximum(np.asarray(sigma, float), 1e-4)
d1 = (np.log(S / np.asarray(K, float)) + (R + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
return np.exp(-0.5 * d1 ** 2) / (np.sqrt(2 * np.pi) * S * sigma * np.sqrt(T))
def gex(S, K, T, sigma, oi, is_call):
"""Dollars dealers trade per 1% move: calls positive, puts negative (dealers long calls, short puts)."""
sign = np.where(is_call, 1.0, -1.0)
return sign * bs_gamma(S, K, T, sigma) * oi * MULTIPLIER * S ** 2 * 0.01Um contrato real, a maior call na call wall: a call de strike 8.100 com vencimento em 2026-12-31, open interest de 41.378, volatilidade implícita de 12,1%, com o spot em 7.637,76.
0,000632 × 41.378 × 100 × 7.637,76² × 0,01 = US$ 1,53 biSó essa linha da cadeia significa que os dealers negociam cerca de $1,5 bilhão de S&P a cada 1% de movimento do índice.
A Cboe publica a cadeia completa de SPX em um único arquivo JSON gratuito, com todos os strikes e vencimentos, open interest e volatilidade implícita, com atraso de cerca de 15 minutos. Os nomes dos contratos seguem o formato OCC, então uma expressão regular recupera vencimento, lado e strike. Contratos sem open interest, sem volatilidade ou com strikes a mais de 50% do spot são descartados.
fetch_chain.py
CHAIN = "https://cdn.cboe.com/api/global/delayed_quotes/options/_SPX.json"
OCC = re.compile(r"^(?P<root>[A-Z]+)(?P<ymd>\d{6})(?P<side>[CP])(?P<strike>\d{8})$")
def fetch_chain():
raw = json.loads(urllib.request.urlopen(
urllib.request.Request(CHAIN, headers={"User-Agent": "gex-research"}), timeout=90).read())
rows = []
for o in raw["data"]["options"]:
m = OCC.match(o["option"])
if m:
rows.append(dict(expiry=date(2000 + int(m["ymd"][:2]), int(m["ymd"][2:4]), int(m["ymd"][4:])),
is_call=m["side"] == "C", strike=int(m["strike"]) / 1000,
oi=float(o["open_interest"]), iv=float(o["iv"])))
df = pd.DataFrame(rows)
spot = float(raw["data"]["current_price"])
df["T"] = [(e - date.today()).days / 365 for e in df.expiry]
keep = (df["T"] >= 0) & (df.oi > 0) & (df.iv > 0.01) & (np.abs(np.log(df.strike / spot)) < 0.5)
return df[keep].reset_index(drop=True), spot, raw.get("timestamp", datetime.now().isoformat(timespec="minutes"))Em 18 de setembro de 2026, às 13:10, isso retorna 19.414 contratos em 56 vencimentos, com o SPX a 7.637,76.
O gamma líquido é a soma do GEX de todos os contratos:
Net GEX(S) = Σi sinali × Γi(S) × OIi × 100 × S² × 0.01net_gamma.py
def net_gamma(df, S):
x = gex(S, df.strike, df["T"], df.iv, df.oi, df.is_call)
return x.sum(), x| Livro | GEX |
|---|---|
| Todas as calls | +242.3 |
| Todas as puts | −233.0 |
| Gamma líquido | +9.4 |
Dois livros de cerca de 240 bilhões cada se anulam quase exatamente, e o que sobra, +9,4 bilhões, é positivo: os dealers estão comprados em gamma neste spot. Por menor que pareça perto de cada lado, é o sinal desse resíduo que decide se o hedge amortece ou amplifica o próximo movimento.
Agrupe os mesmos números por strike. Acima do spot, o strike com o maior GEX positivo é a call wall. Abaixo do spot, o strike com o GEX mais negativo é a put wall. Eles marcam onde está concentrado o maior volume de hedge dos dealers.
walls.py
def walls(df, per_contract, spot):
by_strike = pd.Series(per_contract).groupby(df.strike).sum()
above, below = by_strike[by_strike.index > spot], by_strike[by_strike.index < spot]
return by_strike, float(above.idxmax()), float(below.idxmin())
Abaixo do spot o livro é quase todo vermelho: o lado das puts domina. Acima, as calls assumem. Aqui a call wall fica em 8.100 e a put wall em 7.500.
O gamma líquido só vale no preço de hoje. Reprecifique cada contrato como se o índice estivesse em outro nível, some de novo e repita em uma grade de níveis: isso desenha o gamma líquido ao longo do spot. Onde ele cruza o zero está o gamma flip:
S* tal que Net GEX(S*) = 0gamma_flip.py
def profile(df, spot, lo=0.90, hi=1.10, n=161):
"""Net gamma at every hypothetical spot: re-price every contract, add again."""
grid = np.linspace(spot * lo, spot * hi, n)
total = np.array([net_gamma(df, S)[0] for S in grid])
return grid, total
def flip_level(grid, total, spot):
"""Zero crossing of the profile closest to spot, by linear interpolation."""
best = None
for i in range(len(grid) - 1):
a, b = total[i], total[i + 1]
if (a < 0 <= b) or (a > 0 >= b):
x = grid[i] + (grid[i + 1] - grid[i]) * (-a) / (b - a)
if best is None or abs(x - spot) < abs(best - spot):
best = x
return best
Aqui o flip fica em 7.631, e o SPX, a 7.638, está só 7 pontos acima. Logo acima do flip, os dealers estão comprados em gamma, mas por pouco: uma queda de 0,1% os deixaria vendidos, e o mesmo hedge que hoje amortece os movimentos passaria a amplificá-los.
A maioria das regras de GEX na internet trata o flip e as walls como suporte e resistência. Testadas dia a dia, elas não se sustentaram. Uma regra se sustentou, e ela vem da pesquisa: Baltussen, Da, Lammers e Martens, Hedging demand and market intraday momentum, Journal of Financial Economics, 2021. A última meia hora de pregão continua o movimento do dia, e no S&P 500 só quando os dealers estão vendidos em gamma. Barbon e Buraschi, Gamma Fragility (2021), mostram que os movimentos tendem a se reverter quando os dealers estão comprados em gamma.
| Gamma dos dealers no fechamento anterior | Mercado em alta | Mercado em queda |
|---|---|---|
| Vendido em gamma | compra | venda |
| Comprado em gamma | venda | compra |
Sempre do mesmo lado do hedge dos dealers. Dia estável significa sem trade.
| Teste | Sharpe |
|---|---|
| Regra acima | 1.37 |
| Sempre a favor do dia, ignorando o gamma | −2.00 |
| Mesma regra no QQQ | 0.88 |
O gamma faz todo o trabalho: o mesmo trade sem ele perde dinheiro. Mas a vantagem é pequena, cerca de 3 pontos-base por dia antes de custos, que somem com 3 pontos-base de custo, e só 26 de 723 pregões abriram com gamma vendido. Para o regime, o motor usa a série diária gratuita da SqueezeMetrics, a mesma em que a pesquisa se baseou, e imprime o número da Cboe acima como verificação cruzada, já que os dois podem divergir no sinal. O código do motor, com a execução via Interactive Brokers, está no repositório.
Inferir o gamma dos dealers a partir de gamma × open interest é discutível, e o ponto fraco é o sinal do dealer. O open interest conta quantos contratos estão abertos, não quem está de cada lado. Todo contrato tem um comprador e um vendedor, e a fórmula simplesmente supõe que os dealers estão do lado oposto dos clientes, que por convenção vendem calls (covered calls, collars) e compram puts (proteção). Essa suposição é o +1 para calls, −1 para puts na fórmula:
GEXi = sinal do dealeri × Γi × OIi × 100 × S² × 0.01 com sinal do dealer = +1 (call), −1 (put)Quando os clientes compram calls, como em um rali especulativo ou em opções de vencimento no dia, ou vendem puts para gerar renda, os dealers ficam do lado oposto e o verdadeiro sinal daquele contrato se inverte. Nada no open interest mostra isso. Os trades do próprio dia também ficam de fora, já que o open interest só é atualizado de um dia para o outro.
Por isso o dashboard e os passos 1 a 5 usam essa estimativa para explicar a mecânica: ela é simples e pode ser vista strike a strike. Nosso motor de trading não a usa. Ele nunca calcula o GEX a partir da cadeia. Ele lê um único número publicado por dia, o gamma líquido de SPX da SqueezeMetrics no fechamento anterior, e guarda só o sinal:
regimet = sinal( GEX da SqueezeMetrics no fechamento do dia t−1 ) rt = MES às 15:30 / MES às 09:30 − 1 posiçãot = +sinal(rt) se vendido em gamma (seguir) −sinal(rt) se comprado em gamma (contrariar) contratos = floor( patrimônio / (5 × preço do MES) )Para ser claro, o próprio white paper da SqueezeMetrics parte das mesmas premissas sobre os dealers, então o motor não escapa do debate. Três coisas mudam. É uma série pública, fixada antes da abertura do pregão, então não há look ahead. Ele usa só o sinal, nunca níveis como o flip ou as walls, que é onde um sinal errado em alguns strikes grandes causa mais estrago. E a regra passou por backtest nessa mesma série, então qualquer erro que a premissa carregue já está dentro do resultado acima, em vez de escondido dele. Nosso número da Cboe aparece ao lado só como verificação cruzada; no dia do vídeo, os dois divergiram no sinal.
Todos os números até aqui congelam o relógio. As opções de vencimento no dia fazem o oposto: um contrato do dia perde horas, e seu gamma, que só existe em torno do strike, fica mais estreito e mais alto à medida que o fechamento se aproxima. Este passo mantém fixos o spot, as volatilidades implícitas e todos os outros vencimentos, envelhece só o livro do dia de 6,5 horas a 0,1 hora até a expiração e reprecifica a cadeia inteira em cada nível de SPX a cada vez.
Daqui em diante, os números vêm de uma segunda rodada, em 1º de outubro de 2026, 13:38 em Nova York, com um vencimento do dia ativo: 20.034 contratos na cadeia, 363 deles vencendo naquele dia, com 402.723 contratos de open interest, SPX a 7.662. Cotações do dia sem volatilidade utilizável tomam emprestada a volatilidade do vencimento seguinte no mesmo strike. Código e gráficos estão no repositório de gráficos e modelos.
dealer_gamma_aged.py
def gamma(S, K, T, sigma):
"""Black-Scholes gamma (rate set to zero, it barely moves gamma over hours)."""
srt = np.maximum(sigma, 1e-4) * np.sqrt(np.maximum(T, 0.05 / YEAR_H))
d1 = np.log(S / K) / srt + 0.5 * srt
return np.exp(-0.5 * d1 ** 2) / (np.sqrt(2 * np.pi) * S * srt)
def dealer_gex(df, iv, S, hours, sign=None):
"""$ dealers trade per 1% SPX move, per contract. Default sign: dealers long calls, short puts.
Same day contracts get `hours` to expiration, every other expiry keeps its days."""
T = np.where(df.same_day, hours / YEAR_H, df.days / 365)
sign = np.where(df.is_call, 1.0, -1.0) if sign is None else sign
return sign * gamma(S, df.strike.to_numpy(), T, iv) * df.oi.to_numpy() * MULTIPLIER * S * S * 0.01
def profile(df, iv, grid, hours, sign=None):
"""Net dealer gamma at every hypothetical SPX level: re-price the whole book, add it up."""
return np.array([dealer_gex(df, iv, S, hours, sign).sum() for S in grid])
def flip(grid, total, spot):
"""Zero crossing of the profile closest to spot."""
best = None
for i in range(len(grid) - 1):
a, b = total[i], total[i + 1]
if (a < 0 <= b) or (a > 0 >= b):
x = grid[i] + (grid[i + 1] - grid[i]) * (-a) / (b - a)
best = x if best is None or abs(x - spot) < abs(best - spot) else best
return best
| Horas até o vencimento | Gamma flip | Gamma dos dealers no spot |
|---|---|---|
| 6.5 | 7,680 | −23.2 |
| 3 | 7,680 | −24.2 |
| 1 | 7,682 | −24.2 |
| 0.25 | 7,684 | −24.0 |
| 0.1 | 7,686 | −23.7 |
Neste dia o flip se desloca apenas de 7.680 para 7.686, e o gamma no spot quase não se mexe, porque a maior parte do book está em outros vencimentos. Onde o vencimento do mesmo dia aparece é na forma: nos últimos minutos a curva fica serrilhada, e cada oscilação é um strike do mesmo dia cujo gamma encolheu até virar um pico de poucos pontos de largura.
Separar por strike apenas o vencimento do mesmo dia mostra onde está o seu gamma. Com horas pela frente, ele se espalha por mais de cem pontos de strikes. Perto do fechamento, só os strikes a poucos pontos do spot mantêm algum: o gamma de uma opção no spot some quando o spot fica a mais de cerca de um desvio padrão do movimento restante do seu strike, e com seis minutos restantes isso dá um punhado de pontos.
same_day_by_strike.py
def same_day_by_strike(df, iv, spot, hours):
sd = df.same_day.to_numpy()
x = pd.Series(dealer_gex(df, iv, spot, hours)[sd]).groupby(df.strike[sd].to_numpy()).sum()
return x / 1e9
Pela convenção dos dealers, as puts do mesmo dia em torno do spot dominam, e por isso a maioria das barras aponta para baixo.
O GEX é cotado por movimento de 1%: gamma vezes S² vezes 1%, uma linha reta traçada a partir do gamma. Semanas antes do vencimento essa linha funciona. Perto do vencimento, ela quebra. No maior strike do mesmo dia, 7.650 com 6.177 contratos, o número de 1% sobe de US$ 3,4 bilhões com 6,5 horas restantes para US$ 27,4 bilhões com 0,1 hora restante. Nenhum hedge desse tamanho pode existir: o strike inteiro, todos os contratos com delta cheio, equivale a 6.177 × 100 × 7.650 = US$ 4,73 bilhões de SPX.
O número exato é a variação do delta para um movimento realista. Para um movimento de 0,25% a partir do strike, mais ou menos o que o SPX faz numa última hora:
hedge = Σ OI × 100 × K × [ N(d1 em K(1 + 0,25%)) − N(d1 em K) ]hedge_for_move.py
MOVE = 0.0025 # a 0.25% move, about what SPX does in a last hour
def hedge_for_move(df, iv, K, hours, move=MOVE):
"""$ of SPX dealers must trade at strike K if SPX moves `move` off it: the exact change in delta.
It can never pass the strike's whole delta, contracts x 100 x K."""
m = (df.same_day & (df.strike == K)).to_numpy()
srt = iv[m] * np.sqrt(hours / YEAR_H)
d_on, d_off = 0.5 * srt, np.log(1 + move) / srt + 0.5 * srt
return float(((norm.cdf(d_off) - norm.cdf(d_on)) * df.oi.to_numpy()[m] * MULTIPLIER * K).sum())
def linear_one_pct(df, iv, K, hours):
"""The usual GEX figure at the strike: gamma x S squared x 1%. Fine far from expiry, it explodes close to it."""
m = (df.same_day & (df.strike == K)).to_numpy()
return float((gamma(K, K, hours / YEAR_H, iv[m]) * df.oi.to_numpy()[m] * MULTIPLIER * K * K * 0.01).sum())
| Medida | A 6,5 horas | A 0,1 hora |
|---|---|---|
| Número usual, gamma × movimento de 1% | 3.4 | 27.4 |
| Hedge exato para um movimento de 0,25% | 0.82 | 2.36 |
| Strike inteiro, contratos × 100 × strike | 4.73 | 4.73 |
O hedge real sobe conforme o fechamento se aproxima e depois se estabiliza, porque um movimento de 0,25% pode no máximo levar uma opção no dinheiro do mesmo dia de delta 0,5 para perto de 1. Perto do vencimento, leia qualquer número de GEX por 1% como um limite superior que o hedge real nunca alcança.
A SqueezeMetrics publica sua própria estimativa diária de GEX desde 2011, de graça. Cada ponto abaixo é um dia: o GEX daquele dia contra o movimento do SPX no dia seguinte, de fechamento a fechamento. A estimativa deles tem método e escala próprios, então os números não são comparáveis com os da cadeia acima.
gex_next_day.py
SQUEEZE = "https://squeezemetrics.com/monitor/static/DIX.csv"
def squeeze_history():
"""SqueezeMetrics GEX (their own positioning estimate, their own scale) and the next close to close SPX move."""
raw = urllib.request.urlopen(urllib.request.Request(SQUEEZE, headers={"User-Agent": "gex-research"}),
timeout=60).read().decode()
sq = pd.read_csv(io.StringIO(raw), parse_dates=["date"])
sq["next"] = sq.price.pct_change().shift(-1) * 100
sq["gex_bn"] = sq.gex / 1e9
return sq.dropna(subset=["next"]).reset_index(drop=True)
def spreads(sq):
short, long_ = sq[sq.gex < 0], sq[sq.gex > 0]
calm = short[~short.date.dt.year.isin([2020, 2022])]
return dict(days=len(sq), short_days=len(short), short=float(short.next.std()), long=float(long_.next.std()),
short_without_2020_2022=float(calm.next.std()))
| Dias | Contagem | Movimento do dia seguinte |
|---|---|---|
| GEX negativo (dealers vendidos em gamma) | 347 | ±2.25% |
| GEX positivo (dealers comprados em gamma) | 3,529 | ±0.89% |
| GEX negativo, sem 2020 e 2022 | ±1.77% |
Nos dias de GEX negativo, o dia seguinte se moveu cerca de 2,5 vezes mais. Parte disso vem de anos voláteis: sem 2020 e 2022 a dispersão é de ±1,77%, ainda cerca do dobro dos dias de GEX positivo. GEX e volatilidade andam juntos, e este gráfico mostra a associação sem separar os dois.
Todo número de GEX nesta página assume que os dealers estão comprados em calls e vendidos em puts. O book do mesmo dia é onde essa suposição é mais fraca: o fluxo de zero dia corre nos dois sentidos, e muitos traders vendem puts do mesmo dia. Trocando o sinal apenas do book do mesmo dia, a 6,5 horas do vencimento, com todo o resto inalterado:
sign_scenarios.py
def sign_scenarios(df, iv, spot, hours):
"""Dealer gamma at spot under three views of who holds the same day book."""
std = np.where(df.is_call, 1.0, -1.0)
sd = df.same_day.to_numpy()
netted = np.where(sd, 0.0, std) # same day flow two way, nets to zero
puts_sold = np.where(sd & ~df.is_call.to_numpy(), 1.0, std) # customers sold the same day puts
return {name: float(dealer_gex(df, iv, spot, hours, s).sum() / 1e9) for name, s in
(("convention", None), ("same_day_nets_to_zero", netted), ("same_day_puts_sold", puts_sold))}| Quem detém o book do mesmo dia | Gamma dos dealers |
|---|---|
| Convenção: dealers comprados em calls, vendidos em puts | −23.2 |
| Fluxo do mesmo dia nos dois sentidos, líquido zero | −14.3 |
| Clientes venderam as puts do mesmo dia | +31.0 |
Mesma cadeia, mesmos preços, e o gamma dos dealers no spot cai em qualquer ponto entre −23,2 e +31,0 bilhões, dependendo de quem detém o book do mesmo dia. Leia o sinal de qualquer número de GEX, este incluído, como uma estimativa.