A single parity check handles a static, no-model claim. Pricing the option today, before you know which way the stock moves, needs a model of how it could move, and the simplest one that still nails the no-arbitrage logic is the binomial tree: over each small time step Δt, the stock can only go up by a factor u or down by a factor d. The Cox-Ross-Rubinstein (CRR) parameterization ties u and d to a volatility estimate σ:
Δt = T/n
u = e^(σ·√Δt) d = 1/u
p = (e^((r−q)·Δt) − d) / (u − d) [the RISK-NEUTRAL probability of an up-move]
Stop and notice what p is not: it is not your real-world view of how likely the stock is to rise. It is the unique probability that makes the discounted expected stock price under the tree equal to today's price, the probability an arbitrage-free world would need to have, regardless of anyone's actual beliefs. This is the same risk-neutral logic DV1.01 used for forwards, now applied one step at a time. Once you have u, d, and p, pricing is pure backward induction: compute every terminal payoff, then walk backward, at each node discounting the probability-weighted average of the two nodes it leads to:
V(node) = e^(−r·Δt) · [ p·V(up-child) + (1−p)·V(down-child) ]
Advertisement
How do you price an American option in the tree?
For an American option, at every node you additionally compare that continuation value against immediate exercise (max(S−K,0) for a call, max(K−S,0) for a put) and take the larger, the option is worth whichever is better, holding or exercising right now. This single extra comparison, repeated at every node, is the entire difference between pricing a European and an American option; there is no other machinery to learn. Worked Examples 3 and 4 walk a full tree, node by node, in both directions (European call, American put), with every intermediate value shown and Python-verified. Here is the pricer itself, in plain math-module Python, no dependency beyond the standard library, so you can run it exactly as shown:
import math
def crr_tree(S, K, r, q, sigma, T, n, call=True, american=False):
dt = T / n
u = math.exp(sigma * math.sqrt(dt))
d = 1 / u
p = (math.exp((r - q) * dt) - d) / (u - d)
disc = math.exp(-r * dt)
ST = [S * u**(n - j) * d**j for j in range(n + 1)] # j = number of down-moves
V = [max(s - K, 0.0) if call else max(K - s, 0.0) for s in ST]
for step in range(n - 1, -1, -1):
newS = [S * u**(step - j) * d**j for j in range(step + 1)]
newV = []
for j in range(step + 1):
cont = disc * (p * V[j] + (1 - p) * V[j + 1])
if american:
intrinsic = max(newS[j] - K, 0.0) if call else max(K - newS[j], 0.0)
newV.append(max(cont, intrinsic))
else:
newV.append(cont)
V = newV
return V[0]