Skip to content

Options & Greeks

ThetaDataDx provides two complementary approaches to options analytics:

  1. Server-computed Greeks - query ThetaData's servers for pre-calculated Greeks via the standard endpoints
  2. Local Greeks calculator - compute all 23 Black-Scholes Greeks offline with no server call and no subscription required

Option Chain Workflow

Step 1: Discover Expirations

rust
let exps = client.option_list_expirations("SPY").await?;
for exp in &exps {
    println!("Expiration: {}", exp);  // "20240419", "20240517", ...
}
python
exps = client.option_list_expirations("SPY")
print(exps[:10])  # ["20240419", "20240517", ...]

Step 2: Get Strikes

rust
let strikes = client.option_list_strikes("SPY", "20240419").await?;
println!("{} strikes available", strikes.len());
// v3 returns strikes as dollar values
python
strikes = client.option_list_strikes("SPY", "20240419")
print(f"{len(strikes)} strikes for 2024-04-19")

Step 3: Fetch Chain Data

Use wildcards to fetch the entire chain in a single call -- no looping required:

rust
// One call: all strikes, both calls and puts
let chain = client.option_snapshot_quote(
    "SPY", "20240419", "*", "both"
).await?;

for q in &chain {
    if q.has_contract_id() {
        println!("{} {} {:.2}: bid={:.2} ask={:.2}",
            q.expiration, if q.is_call() { "C" } else { "P" },
            q.strike, q.bid, q.ask);
    }
}
python
# One call: all strikes, both calls and puts
chain = client.option_snapshot_quote("SPY", "20240419", "*", "both")

for q in chain:
    if "expiration" in q:
        print(f"{q['expiration']} {q['right']} {q['strike']:.2f}: "
              f"bid={q['bid']:.2f} ask={q['ask']:.2f}")

Step 4: Server-Computed Greeks

rust
// All Greeks snapshot for a contract
let greeks = client.option_snapshot_greeks_all(
    "SPY", "20240419", "500", "C"
).await?;

// Historical EOD Greeks over a date range
let greeks_eod = client.option_history_greeks_eod(
    "SPY", "20240419", "500", "C", "20240101", "20240301"
).await?;

// Greeks on each individual trade
let trade_greeks = client.option_history_trade_greeks_all(
    "SPY", "20240419", "500", "C", "20240315"
).await?;
python
# All Greeks snapshot for a contract
greeks = client.option_snapshot_greeks_all("SPY", "20240419", "500", "C")

# Historical EOD Greeks
greeks_eod = client.option_history_greeks_eod("SPY", "20240419", "500", "C",
                                               "20240101", "20240301")

# Greeks on each individual trade
trade_greeks = client.option_history_trade_greeks_all("SPY", "20240419", "500", "C",
                                                       "20240315")

Full Chain Scan with DataFrames (Python)

python
from thetadatadx import Credentials, Config, ThetaDataDxClient

creds = Credentials.from_file("creds.txt")
client = ThetaDataDxClient(creds, Config.production())

# Get nearest expiration
exps = client.option_list_expirations("SPY")
exp = exps[0]

# Fetch EOD for all calls in one request using wildcard strike
chain = client.option_history_eod("SPY", exp, "*", "C",
                                   "20240301", "20240301")

# Each row has contract ID fields (expiration, strike, right)
for row in chain[:5]:
    print(f"strike={row.strike:.2f} close={row.close:.2f} vol={row.volume}")

Wildcard Queries with Contract Identification

When you pass "0" for expiration or strike, the server returns data across all matching contracts. Each tick includes contract identification fields (expiration, strike, right) so you can distinguish which contract each tick belongs to.

strike_range only narrows one of these wildcard bulk queries. It does not expand a pinned strike into neighboring strikes. In other words:

  • strike="500" + strike_range=5 still targets the single 500 strike contract
  • strike="0" + strike_range=5 returns a spot-relative range of strikes around ATM

If you are comparing against the v3 REST API, the same wildcard behavior is expressed with strike=* / expiration=* instead of "0".

WARNING

The right parameter does not accept "0" as a wildcard. Use "C" / "call" for calls, "P" / "put" for puts, or "both" / "*" for both (case-insensitive — e.g. "CALL", "Put", "c" all work). Only expiration and strike accept "0" as a wildcard.

rust
// Wildcard: all SPY call contracts on a given date
let trades = client.option_history_trade("SPY", "0", "0", "C", "20240315").await?;
for t in &trades {
    if t.has_contract_id() {
        println!("{} {} strike={:.2} price={:.4}",
            t.expiration,
            if t.is_call() { "C" } else { "P" },
            t.strike,
            t.price);
    }
}
python
# Wildcard: all SPY call contracts on a given date
trades = client.option_history_trade("SPY", "0", "0", "C", "20240315")
for t in trades:
    # `expiration` / `strike` / `right` are only populated on option tick
    # types — stock tick rows leave them at the pyclass default.
    if t.expiration:
        print(f"{t.expiration} {t.right} strike={t.strike:.2f} price={t.price:.4f}")

The 4 contract ID fields and helper methods (is_call(), is_put(), has_contract_id()) are available on all 10 option tick types.

Right Field

In Go and Python, the right field is a human-readable string: "C" (call), "P" (put), or "" (not set). In Rust and C FFI, right is an i32 (67=Call, 80=Put); use is_call()/is_put() helpers.


Local Greeks Calculator

Compute Greeks locally without any server call using the built-in Black-Scholes calculator. Works offline, no ThetaData subscription needed.

All 23 Greeks at Once

The most common usage: compute IV from the market price, then derive all 23 Greeks in a single call.

rust
use thetadatadx::all_greeks;

let result = all_greeks(
    450.0,            // spot price
    455.0,            // strike price
    0.05,             // risk-free rate
    0.015,            // dividend yield
    30.0 / 365.0,     // time to expiration (years)
    8.50,             // market option price
    "C",              // right ("C"/"P" or "call"/"put", case-insensitive)
);

println!("Implied Volatility: {:.4}", result.iv);
println!("Delta:    {:.4}", result.delta);
println!("Gamma:    {:.6}", result.gamma);
println!("Theta:    {:.4} (daily)", result.theta);
println!("Vega:     {:.4}", result.vega);
println!("Rho:      {:.4}", result.rho);
println!("Vanna:    {:.6}", result.vanna);
println!("Charm:    {:.6}", result.charm);
println!("Vomma:    {:.6}", result.vomma);
println!("Speed:    {:.8}", result.speed);
println!("Zomma:    {:.8}", result.zomma);
println!("Color:    {:.8}", result.color);
println!("Ultima:   {:.6}", result.ultima);
python
from thetadatadx import all_greeks

g = all_greeks(
    spot=450.0, strike=455.0, rate=0.05,
    div_yield=0.015, tte=30/365, option_price=8.50, right="C"
)

print(f"IV:    {g['iv']:.4f}")
print(f"Delta: {g['delta']:.4f}")
print(f"Gamma: {g['gamma']:.6f}")
print(f"Theta: {g['theta']:.4f}")
print(f"Vega:  {g['vega']:.4f}")
print(f"Rho:   {g['rho']:.4f}")

TIP

The result contains 22 keys: value, delta, gamma, theta, vega, rho, iv, iv_error, vanna, charm, vomma, veta, speed, zomma, color, ultima, d1, d2, dual_delta, dual_gamma, epsilon, lambda.

Implied Volatility Only

rust
use thetadatadx::implied_volatility;

let (iv, error) = implied_volatility(
    450.0,        // spot
    455.0,        // strike
    0.05,         // rate
    0.015,        // dividend yield
    30.0 / 365.0, // time to expiry
    8.50,         // market price
    "C",          // right ("C"/"P" or "call"/"put", case-insensitive)
);
println!("IV: {:.4}, Error: {:.6}", iv, error);
python
from thetadatadx import implied_volatility

iv, err = implied_volatility(450.0, 455.0, 0.05, 0.015, 30/365, 8.50, "C")
print(f"IV: {iv:.4f}, Error: {err:.6f}")

The solver uses bisection with up to 128 iterations. The error return is the relative difference (theoretical - market) / market.

Individual Greeks (Rust)

For targeted computation when you only need one or two values:

rust
use tdbe::greeks;

let s = 450.0;  // spot
let x = 455.0;  // strike
let v = 0.20;   // volatility (sigma)
let r = 0.05;   // rate
let q = 0.015;  // dividend yield
let t = 30.0 / 365.0;  // time (years)

// First order
let delta = greeks::delta(s, x, v, r, q, t, true);
let theta = greeks::theta(s, x, v, r, q, t, true);  // daily (divided by 365)
let vega  = greeks::vega(s, x, v, r, q, t);
let rho   = greeks::rho(s, x, v, r, q, t, true);

// Second order
let gamma = greeks::gamma(s, x, v, r, q, t);
let vanna = greeks::vanna(s, x, v, r, q, t);
let charm = greeks::charm(s, x, v, r, q, t, true);
let vomma = greeks::vomma(s, x, v, r, q, t);

// Third order
let speed  = greeks::speed(s, x, v, r, q, t);
let zomma  = greeks::zomma(s, x, v, r, q, t);
let color  = greeks::color(s, x, v, r, q, t);
let ultima = greeks::ultima(s, x, v, r, q, t);  // clamped to [-100, 100]

Greeks Reference

First Order

GreekDescriptionNotes
deltadV/dSCall: 0 to 1, Put: -1 to 0
thetaTime decay per dayDivided by 365
vegaSensitivity to volatilitySame for calls and puts
rhoSensitivity to interest rate
epsilonSensitivity to dividend yield
lambdaLeverage ratio (delta * S / V)

Second Order

GreekDescription
gammad2V/dS2 (same for calls and puts)
vannad2V/dSdv
charmd2V/dSdt (delta decay)
vommad2V/dv2 (vol-of-vol)
vetad2V/dvdt

Third Order

GreekDescription
speedd3V/dS3
zommad3V/dS2dv
colord3V/dS2dt
ultimad3V/dv3 (clamped to [-100, 100])

Auxiliary

GreekDescription
d1Black-Scholes d1 intermediate
d2Black-Scholes d2 intermediate
dual_deltadV/dK (sensitivity to strike)
dual_gammad2V/dK2

Edge Cases

ConditionBehavior
t = 0 (at expiry)Returns 0.0 for most Greeks; value() returns intrinsic value
v = 0 (zero vol)Returns 0.0 for most Greeks; value() returns intrinsic value
Deep ITM/OTMIV solver may return high error; check iv_error field
ultima overflowClamped to [-100, 100] range

Released under the Apache-2.0 License.