Skip to content

Commit c227f9c

Browse files
committed
update markov_asset
1 parent a137f5d commit c227f9c

File tree

1 file changed

+91
-75
lines changed

1 file changed

+91
-75
lines changed

lectures/markov_asset.md

Lines changed: 91 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ Let's start with some imports:
7575
import matplotlib.pyplot as plt
7676
import numpy as np
7777
import 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
9194
Let'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

117120
More 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

146149
We give examples of how the stochastic discount factor has been modeled below.
147150

148-
### Asset Pricing and Covariances
151+
### Asset pricing and covariances
149152

150153
Recall 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

175178
We 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

179182
Aside 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

191194
Below we'll discuss the implication of this equation.
192195

193-
## Prices in the Risk-Neutral Case
196+
## Prices in the risk-neutral case
194197

195198
What 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

204207
We'll focus on how an asset price depends on a dividend process.
205208

206-
### Example 1: Constant Dividends
209+
### Example 1: constant dividends
207210

208211
The 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.
234237
Indeed, simple algebra shows that setting $p_t = \bar p$ for all $t$
235238
satisfies 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

239242
Consider a growing, non-random dividend process $d_{t+1} = g d_t$
240243
where $0 < g \beta < 1$.
@@ -267,7 +270,7 @@ $$
267270
This 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

272275
Next, 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
314317
n = 7
315318
mc = qe.tauchen(n, 0.96, 0.25)
316319
sim_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
322325
series = [x_series, g_series, d_series, np.log(d_series)]
323326
labels = ['$X_t$', '$g_t$', '$d_t$', r'$\log \, d_t$']
@@ -330,7 +333,7 @@ plt.tight_layout()
330333
plt.show()
331334
```
332335

333-
#### Pricing Formula
336+
#### Pricing formula
334337

335338
To 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

401404
Here's the code, including a test of the spectral radius condition
402405

403-
```{code-cell} python3
406+
```{code-cell} ipython3
404407
n = 25 # Size of state space
405408
β = 0.9
406409
mc = 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
410413
warning_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
416419
fig, ax = plt.subplots()
417420
ax.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}
539542
We will define a function tree_price to compute $v$ given parameters stored in
540543
the 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
584592
def 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):
614622
Here's a plot of $v$ as a function of the state for several values of $\gamma$,
615623
with 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()
620628
states = ap.mc.state_values
621629
622630
fig, ax = plt.subplots()
623631
624632
for γ 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
629637
ax.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
707715
The above is implemented in the function consol_price.
708716
709-
```{code-cell} python3
717+
```{code-cell} ipython3
710718
def 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
813820
We can find the solution with the following function call_option
814821
815-
```{code-cell} python3
822+
```{code-cell} ipython3
816823
def 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
861877
Here'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
866882
strike_price = 40
867883

0 commit comments

Comments
 (0)