@@ -75,7 +75,10 @@ Let's start with some imports:
7575import matplotlib.pyplot as plt
7676import numpy as np
7777import quantecon as qe
78- from numpy.linalg import eigvals, solve
78+ import jax
79+ import jax.numpy as jnp
80+ from jax.numpy.linalg import eigvals, solve
81+ from typing import NamedTuple
7982```
8083
8184## {index}` Pricing Models <single: Pricing Models> `
@@ -91,7 +94,7 @@ Let $\{d_t\}_{t \geq 0}$ be a stream of dividends
9194Let's look at some equations that we expect to hold for prices of assets under ex-dividend contracts
9295(we will consider cum-dividend pricing in the exercises).
9396
94- ### Risk-Neutral Pricing
97+ ### Risk-neutral pricing
9598
9699``` {index} single: Pricing Models; Risk-Neutral
97100```
@@ -116,7 +119,7 @@ Here ${\mathbb E}_t [y]$ denotes the best forecast of $y$, conditioned on inform
116119
117120More precisely, ${\mathbb E}_ t [ y] $ is the mathematical expectation of $y$ conditional on information available at time $t$.
118121
119- ### Pricing with Random Discount Factor
122+ ### Pricing with random discount factor
120123
121124``` {index} single: Pricing Models; Risk Aversion
122125```
@@ -145,7 +148,7 @@ This is because such assets pay well when funds are more urgently wanted.
145148
146149We give examples of how the stochastic discount factor has been modeled below.
147150
148- ### Asset Pricing and Covariances
151+ ### Asset pricing and covariances
149152
150153Recall that, from the definition of a conditional covariance ${\rm cov}_ t (x_ {t+1}, y_ {t+1})$, we have
151154
@@ -174,7 +177,7 @@ Equation {eq}`lteeqs102` asserts that the covariance of the stochastic discount
174177
175178We give examples of some models of stochastic discount factors that have been proposed later in this lecture and also in a [ later lecture] ( https://python-advanced.quantecon.org/lucas_model.html ) .
176179
177- ### The Price-Dividend Ratio
180+ ### The price-dividend ratio
178181
179182Aside from prices, another quantity of interest is the ** price-dividend ratio** $v_t := p_t / d_t$.
180183
@@ -190,7 +193,7 @@ v_t = {\mathbb E}_t \left[ m_{t+1} \frac{d_{t+1}}{d_t} (1 + v_{t+1}) \right]
190193
191194Below we'll discuss the implication of this equation.
192195
193- ## Prices in the Risk-Neutral Case
196+ ## Prices in the risk-neutral case
194197
195198What can we say about price dynamics on the basis of the models described above?
196199
@@ -203,7 +206,7 @@ For now we'll study the risk-neutral case in which the stochastic discount fac
203206
204207We'll focus on how an asset price depends on a dividend process.
205208
206- ### Example 1: Constant Dividends
209+ ### Example 1: constant dividends
207210
208211The simplest case is risk-neutral price of a constant, non-random dividend stream $d_t = d > 0$.
209212
@@ -234,7 +237,7 @@ This is the equilibrium price in the constant dividend case.
234237Indeed, simple algebra shows that setting $p_t = \bar p$ for all $t$
235238satisfies the difference equation $p_t = \beta (d + p_ {t+1})$.
236239
237- ### Example 2: Dividends with Deterministic Growth Paths
240+ ### Example 2: dividends with deterministic growth paths
238241
239242Consider a growing, non-random dividend process $d_ {t+1} = g d_t$
240243where $0 < g \beta < 1$.
267270This is called the * Gordon formula* .
268271
269272(mass_mg)=
270- ### Example 3: Markov Growth, Risk-Neutral Pricing
273+ ### Example 3: Markov growth, risk-neutral pricing
271274
272275Next, we consider a dividend process
273276
@@ -310,14 +313,14 @@ The next figure shows a simulation, where
310313* $\{ X_t\} $ evolves as a discretized AR1 process produced using {ref}` Tauchen's method <fm_ex3> ` .
311314* $g_t = \exp(X_t)$, so that $\ln g_t = X_t$ is the growth rate.
312315
313- ``` {code-cell} ipython
316+ ``` {code-cell} ipython3
314317n = 7
315318mc = qe.tauchen(n, 0.96, 0.25)
316319sim_length = 80
317320
318- x_series = mc.simulate(sim_length, init=np .median(mc.state_values))
319- g_series = np .exp(x_series)
320- d_series = np .cumprod(g_series) # Assumes d_0 = 1
321+ x_series = mc.simulate(sim_length, init=jnp .median(mc.state_values))
322+ g_series = jnp .exp(x_series)
323+ d_series = jnp .cumprod(g_series) # Assumes d_0 = 1
321324
322325series = [x_series, g_series, d_series, np.log(d_series)]
323326labels = ['$X_t$', '$g_t$', '$d_t$', r'$\log \, d_t$']
@@ -330,7 +333,7 @@ plt.tight_layout()
330333plt.show()
331334```
332335
333- #### Pricing Formula
336+ #### Pricing formula
334337
335338To obtain asset prices in this setting, let's adapt our analysis from the case of deterministic growth.
336339
@@ -400,18 +403,18 @@ As before, we'll generate $\{X_t\}$ as a {ref}`discretized AR1 process <fm_ex3>
400403
401404Here's the code, including a test of the spectral radius condition
402405
403- ``` {code-cell} python3
406+ ``` {code-cell} ipython3
404407n = 25 # Size of state space
405408β = 0.9
406409mc = qe.tauchen(n, 0.96, 0.02)
407410
408- K = mc.P * np .exp(mc.state_values)
411+ K = mc.P * jnp .exp(mc.state_values)
409412
410413warning_message = "Spectral radius condition fails"
411- assert np .max(np .abs(eigvals(K))) < 1 / β, warning_message
414+ assert jnp .max(jnp .abs(eigvals(K))) < 1 / β, warning_message
412415
413- I = np .identity(n)
414- v = solve(I - β * K, β * K @ np .ones(n))
416+ I = jnp .identity(n)
417+ v = solve(I - β * K, β * K @ jnp .ones(n))
415418
416419fig, ax = plt.subplots()
417420ax.plot(mc.state_values, v, 'g-o', lw=2, alpha=0.7, label='$v$')
@@ -440,7 +443,7 @@ We'll price several distinct assets, including
440443* A consol (a type of bond issued by the UK government in the 19th century)
441444* Call options on a consol
442445
443- ### Pricing a Lucas Tree
446+ ### Pricing a Lucas tree
444447
445448``` {index} single: Finite Markov Asset Pricing; Lucas Tree
446449```
@@ -539,46 +542,51 @@ v = (I - \beta J)^{-1} \beta J {\mathbb 1}
539542We will define a function tree_price to compute $v$ given parameters stored in
540543the class AssetPriceModel
541544
542- ```{code-cell} python3
543- class AssetPriceModel:
545+ ```{code-cell} ipython3
546+ class AssetPriceModel(NamedTuple) :
544547 """
545548 A class that stores the primitives of the asset pricing model.
546549
547550 Parameters
548551 ----------
549- β : scalar, float
550- Discount factor
551552 mc : MarkovChain
552- Contains the transition matrix and set of state values for the state
553- process
554- γ : scalar(float)
555- Coefficient of risk aversion
553+ Contains the transition matrix and set of state values
556554 g : callable
557555 The function mapping states to growth rates
558-
556+ β : float
557+ Discount factor
558+ γ : float
559+ Coefficient of risk aversion
560+ n: int
561+ The number of states
562+ """
563+ mc: qe.MarkovChain
564+ g: callable
565+ β: float
566+ γ: float
567+ n: int
568+
569+
570+ def create_ap_model(mc=None, g=jnp.exp, β=0.96, γ=2.0):
571+ """Create an AssetPriceModel class"""
572+ if mc is None:
573+ n, ρ, σ = 25, 0.9, 0.02
574+ mc = qe.tauchen(n, ρ, σ)
575+ else:
576+ mc = mc
577+ n = mc.P.shape[0]
578+
579+ return AssetPriceModel(mc=mc, g=g, β=β, γ=γ, n=n)
580+
581+
582+ def test_stability(Q, β):
559583 """
560- def __init__(self, β=0.96, mc=None, γ=2.0, g=np.exp):
561- self.β, self.γ = β, γ
562- self.g = g
563-
564- # A default process for the Markov chain
565- if mc is None:
566- self.ρ = 0.9
567- self.σ = 0.02
568- self.mc = qe.tauchen(n, self.ρ, self.σ)
569- else:
570- self.mc = mc
571-
572- self.n = self.mc.P.shape[0]
573-
574- def test_stability(self, Q):
575- """
576- Stability test for a given matrix Q.
577- """
578- sr = np.max(np.abs(eigvals(Q)))
579- if not sr < 1 / self.β:
580- msg = f"Spectral radius condition failed with radius = {sr}"
581- raise ValueError(msg)
584+ Stability test for a given matrix Q.
585+ """
586+ sr = np.max(np.abs(eigvals(Q)))
587+ if not sr < 1 / β:
588+ msg = f"Spectral radius condition failed with radius = {sr}"
589+ raise ValueError(msg)
582590
583591
584592def tree_price(ap):
@@ -601,11 +609,11 @@ def tree_price(ap):
601609 J = P * ap.g(y)**(1 - γ)
602610
603611 # Make sure that a unique solution exists
604- ap. test_stability(J)
612+ test_stability(J, β )
605613
606614 # Compute v
607- I = np .identity(ap.n)
608- Ones = np .ones(ap.n)
615+ I = jnp .identity(ap.n)
616+ Ones = jnp .ones(ap.n)
609617 v = solve(I - β * J, β * J @ Ones)
610618
611619 return v
@@ -614,16 +622,16 @@ def tree_price(ap):
614622Here's a plot of $v$ as a function of the state for several values of $\gamma$,
615623with a positively correlated Markov process and $g(x) = \exp(x)$
616624
617- ```{code-cell} python3
625+ ```{code-cell} ipython3
618626γs = [1.2, 1.4, 1.6, 1.8, 2.0]
619- ap = AssetPriceModel ()
627+ ap = create_ap_model ()
620628states = ap.mc.state_values
621629
622630fig, ax = plt.subplots()
623631
624632for γ in γs:
625- ap.γ = γ
626- v = tree_price(ap )
633+ tem_ap = create_ap_model(mc= ap.mc, g=ap.g, β=ap.β, γ=γ)
634+ v = tree_price(tem_ap )
627635 ax.plot(states, v, lw=2, alpha=0.6, label=rf"$\gamma = {γ}$")
628636
629637ax.set_title('Price-dividend ratio as a function of the state')
@@ -706,7 +714,7 @@ p = (I - \beta M)^{-1} \beta M \zeta {\mathbb 1}
706714
707715The above is implemented in the function consol_price.
708716
709- ```{code-cell} python3
717+ ```{code-cell} ipython3
710718def consol_price(ap, ζ):
711719 """
712720 Computes price of a consol bond with payoff ζ
@@ -715,26 +723,25 @@ def consol_price(ap, ζ):
715723 ----------
716724 ap: AssetPriceModel
717725 An instance of AssetPriceModel containing primitives
718-
726+
719727 ζ : scalar(float)
720728 Coupon of the console
721729
722730 Returns
723731 -------
724732 p : array_like(float)
725733 Console bond prices
726-
727734 """
728735 # Simplify names, set up matrices
729736 β, γ, P, y = ap.β, ap.γ, ap.mc.P, ap.mc.state_values
730737 M = P * ap.g(y)**(- γ)
731738
732739 # Make sure that a unique solution exists
733- ap. test_stability(M)
740+ test_stability(M, β )
734741
735742 # Compute price
736- I = np .identity(ap.n)
737- Ones = np .ones(ap.n)
743+ I = jnp .identity(ap.n)
744+ Ones = jnp .ones(ap.n)
738745 p = solve(I - β * M, β * ζ * M @ Ones)
739746
740747 return p
@@ -812,7 +819,7 @@ Start at some initial $w$ and iterate with $T$ to convergence .
812819
813820We can find the solution with the following function call_option
814821
815- ```{code-cell} python3
822+ ```{code-cell} ipython3
816823def call_option(ap, ζ, p_s, ϵ=1e-7):
817824 """
818825 Computes price of a call option on a consol bond.
@@ -828,7 +835,7 @@ def call_option(ap, ζ, p_s, ϵ=1e-7):
828835 p_s : scalar(float)
829836 Strike price
830837
831- ϵ : scalar(float), optional(default=1e-8 )
838+ ϵ : scalar(float), optional(default=1e-7 )
832839 Tolerance for infinite horizon problem
833840
834841 Returns
@@ -842,26 +849,35 @@ def call_option(ap, ζ, p_s, ϵ=1e-7):
842849 M = P * ap.g(y)**(- γ)
843850
844851 # Make sure that a unique consol price exists
845- ap. test_stability(M)
852+ test_stability(M, β )
846853
847854 # Compute option price
848855 p = consol_price(ap, ζ)
849- w = np .zeros(ap.n)
856+ w = jnp .zeros(ap.n)
850857 error = ϵ + 1
851- while error > ϵ:
858+
859+ def step(state):
860+ w, error = state
852861 # Maximize across columns
853- w_new = np .maximum(β * M @ w, p - p_s)
862+ w_new = jnp .maximum(β * M @ w, p - p_s)
854863 # Find maximal difference of each component and update
855- error = np .amax(np .abs(w - w_new))
856- w = w_new
864+ error_new = jnp .amax(jnp .abs(w - w_new))
865+ return ( w_new, error_new)
857866
858- return w
867+ # Check whether converged
868+ def cond(state):
869+ _, error = state
870+ return error > ϵ
871+
872+ final_w, _ = jax.lax.while_loop(cond, step, (w, error))
873+
874+ return final_w
859875```
860876
861877Here's a plot of $w$ compared to the consol price when $P_S = 40$
862878
863- ```{code-cell} python3
864- ap = AssetPriceModel (β=0.9)
879+ ```{code-cell} ipython3
880+ ap = create_ap_model (β=0.9)
865881ζ = 1.0
866882strike_price = 40
867883
0 commit comments