Separate model inputs, terminal payoffs, backward induction, and verification. In this exercise, a 100-step Cox–Ross–Rubinstein tree prices an American put at about for , , , , and . At each node, compare the value of exercising immediately with the value of continuing.
Map a node to the two entries it needs
Set , , , , and . Order each vector from zero up moves to all up moves. From node , the down successor is entry and the up successor is entry of the next vector. The discounted continuation value is . The American put uses , with the positive part of at maturity. The continuation value is nonnegative under the assumed probabilities.
Think of the rollback array as belonging to a single time level. At maturity it has 101 entries indexed by the number of up moves. One rollback creates 100 parent values. The first parent uses entries zero and one, the next uses one and two, and so on. At an American node, compare the discounted continuation with intrinsic value calculated using the parent stock price. Doing that comparison at a child's spot tests the wrong exercise decision. As a quick test, the American price should be at least its immediate exercise value and at least the European value computed on the same grid.
Check early exercise against the European value
Here years and . The tree produces an American value of about . The European version is cheaper, with a continuous-model benchmark of . The current exercise value is , so the initial node chooses continuation. For a local early-exercise check, inspect a low spot near maturity. There the immediate receipt of cash can be worth more than waiting a step for a nearly certain payoff.
Implement the 100-step rollback
import numpy as np
def put_tree(spot, strike, maturity, rate, vol, steps=100, *, american=True):
dt = maturity / steps
up = np.exp(vol * np.sqrt(dt))
down = 1 / up
gross = np.exp(rate * dt)
prob_up = (gross - down) / (up - down)
if not 0 < prob_up < 1:
raise ValueError("No-arbitrage condition failed for this time step.")
def spot_layer(level):
ups = np.arange(level + 1)
return spot * up**ups * down**(level - ups)
values = np.maximum(strike - spot_layer(steps), 0.0)
for level in range(steps - 1, -1, -1):
continuation = ((1 - prob_up) * values[:-1] + prob_up * values[1:]) / gross
values = np.maximum(strike - spot_layer(level), continuation) if american else continuation
return float(values[0])
print(put_tree(95, 100, 1, 0.03, 0.25))
print(put_tree(95, 100, 1, 0.03, 0.25, american=False))The options pricer gives you a continuous-model European benchmark. Compare it with your European tree on the same inputs.