In the binomial tree, each node leads to two other nodes in the next time step. Similarly in a trinomial tree, each node leads to three other nodes in the next time step. Besides having up and down states, the middle node of the trinomial tree indicates no change in state. When extended over more than two time steps, the trinomial tree can be thought of as a recombining tree, where the middle nodes always retain the same values as the previous time step.
Let's consider the Boyle trinomial tree, where the tree is calibrated such that the probability of up, down, and flat movements, , , and with risk-neutral probabilities , , and are as follows:
We can see that recombines with . With calibration, the no state movement grows at a flat rate of 1 instead of at the risk-free rate. The variable is the annualized dividend yield and is the annualized volatility of the underlying stock. In general, with an increased number of nodes to process, a trinomial tree gives better accuracy than the binomial tree when fewer time steps are modeled, saving on the computation speed and resources. Refer to the following figure:
The Python implementation of the trinomial tree is given in the following TrinomialTreeOption
class, which inherits from the BinomialTreeOption
class.
The _setup_parameters_
method will implement the model parameters of the trinomial tree. The _initialize_stock_price_tree_
method will set up the trinomial tree to include the flat movement of stock prices. The _traverse_tree_
method takes into account the middle node after discounting the payoff. Save this file as TrinomialTreeOption.py
:
""" Price an option by the Boyle trinomial tree """ from BinomialTreeOption import BinomialTreeOption import math import numpy as np class TrinomialTreeOption(BinomialTreeOption): def _setup_parameters_(self): """ Required calculations for the model """ self.u = math.exp(self.sigma*math.sqrt(2.*self.dt)) self.d = 1/self.u self.m = 1 self.qu = ((math.exp((self.r-self.div) * self.dt/2.) - math.exp(-self.sigma * math.sqrt(self.dt/2.))) / (math.exp(self.sigma * math.sqrt(self.dt/2.)) - math.exp(-self.sigma * math.sqrt(self.dt/2.))))**2 self.qd = ((math.exp(self.sigma * math.sqrt(self.dt/2.)) - math.exp((self.r-self.div) * self.dt/2.)) / (math.exp(self.sigma * math.sqrt(self.dt/2.)) - math.exp(-self.sigma * math.sqrt(self.dt/2.))))**2. self.qm = 1 - self.qu - self.qd def _initialize_stock_price_tree_(self): """ Initialize a 2D tree at t=0 """ self.STs = [np.array([self.S0])] for i in range(self.N): prev_nodes = self.STs[-1] self.ST = np.concatenate( (prev_nodes*self.u, [prev_nodes[-1]*self.m, prev_nodes[-1]*self.d])) self.STs.append(self.ST) def _traverse_tree_(self, payoffs): """ Traverse the tree backwards """ for i in reversed(range(self.N)): payoffs = (payoffs[:-2] * self.qu + payoffs[1:-1] * self.qm + payoffs[2:] * self.qd) * self.df if not self.is_european: payoffs = self.__check_early_exercise__(payoffs, i) return payoffs
Using the same example of the binomial tree, we get the following result:
>>> from TrinomialTreeOption import TrinomialTreeOption >>> eu_put = TrinomialTreeOption( ... 50, 50, 0.05, 0.5, 2, >>> {"sigma": 0.3, "is_call": False}) >>> print "European put: %s" % eu_put.price() European put: 3.33090549176 >>> am_option = TrinomialTreeOption( ... 50, 50, 0.05, 0.5, 2, >>> {"sigma": 0.3, "is_call": False, "is_eu": False}) >>> print "American put: %s" % am_option.price() American put: 3.482414539021
We obtain prices of $3.33 and $3.48 for the European and American put option respectively.