@@ -929,21 +929,169 @@ Now let's plot the reservation wage as a function of volatility:
929929fig, ax = plt.subplots()
930930
931931ax.plot(σ_vals, res_wages_volatility, linewidth=2)
932- ax.set_xlabel('volatility ($\sigma$)', fontsize=12)
932+ ax.set_xlabel(r 'volatility ($\sigma$)', fontsize=12)
933933ax.set_ylabel('reservation wage', fontsize=12)
934- ax.set_title('Reservation wage increases with volatility')
935- ax.grid(True, alpha=0.3)
936-
937934plt.show()
938935```
939936
940937As expected, the reservation wage is increasing in $\sigma$.
941938
942- This confirms that workers prefer more volatile wage distributions, all else equal,
943- because they can capitalize on high offers while rejecting low ones.
939+ ### Lifetime Value and Volatility
940+
941+ We've seen that the reservation wage increases with volatility. Now let's verify that
942+ the lifetime value at the optimal policy also increases with volatility.
943+
944+ The intuition is that higher volatility provides more upside potential while the
945+ worker can protect against downside risk by rejecting low offers. This option value
946+ translates into higher expected lifetime utility.
947+
948+ To demonstrate this, we'll:
949+ 1 . Compute the reservation wage for each volatility level
950+ 2 . Simulate the worker's job search process following the optimal policy
951+ 3 . Calculate the expected discounted lifetime income
952+
953+ The simulation works as follows: starting unemployed, the worker draws wage offers
954+ and accepts the first offer that exceeds their reservation wage. We then compute
955+ the present value of this income stream.
956+
957+ ``` {code-cell} ipython3
958+ @jax.jit
959+ def simulate_lifetime_value(key, model, w_bar, max_search_periods=1000):
960+ """
961+ Simulate one realization of the job search and compute lifetime value.
962+
963+ Parameters:
964+ -----------
965+ key : jax.random.PRNGKey
966+ Random key for JAX
967+ model : McCallModelContinuous
968+ The model containing parameters
969+ w_bar : float
970+ The reservation wage
971+ max_search_periods : int
972+ Maximum number of search periods before forcing acceptance
973+
974+ Returns:
975+ --------
976+ lifetime_value : float
977+ Discounted sum of lifetime income
978+ """
979+ c, β, σ, μ, w_draws = model
980+
981+ def search_step(state):
982+ t, key, accepted, wage = state
983+ key, subkey = jax.random.split(key)
984+ # Draw wage offer
985+ s = jax.random.normal(subkey)
986+ w = jnp.exp(μ + σ * s)
987+ # Check if we accept
988+ accept_now = w >= w_bar
989+ # Update state: if we accept now, store the wage
990+ wage = jnp.where(accept_now, w, wage)
991+ accepted = jnp.logical_or(accepted, accept_now)
992+ t = t + 1
993+ return t, key, accepted, wage
994+
995+ def search_cond(state):
996+ t, _, accepted, _ = state
997+ # Continue searching if not accepted and haven't hit max periods
998+ return jnp.logical_and(jnp.logical_not(accepted), t < max_search_periods)
999+
1000+ # Initial state: period 0, not accepted, wage 0
1001+ initial_state = (0, key, False, 0.0)
1002+ t_final, _, _, final_wage = jax.lax.while_loop(search_cond, search_step, initial_state)
1003+
1004+ # Compute lifetime value
1005+ # During unemployment (periods 0 to t_final-1): receive c each period
1006+ # After employment (period t_final onwards): receive final_wage forever
1007+ unemployment_value = c * (1 - β**t_final) / (1 - β)
1008+ employment_value = (β**t_final) * final_wage / (1 - β)
1009+ lifetime_value = unemployment_value + employment_value
1010+
1011+ return lifetime_value
1012+
1013+
1014+ @jax.jit
1015+ def compute_mean_lifetime_value(model, w_bar, num_reps=10000, seed=1234):
1016+ """
1017+ Compute mean lifetime value across many simulations.
1018+
1019+ Parameters:
1020+ -----------
1021+ model : McCallModelContinuous
1022+ The model containing parameters
1023+ w_bar : float
1024+ The reservation wage
1025+ num_reps : int
1026+ Number of simulation replications
1027+ seed : int
1028+ Random seed
1029+
1030+ Returns:
1031+ --------
1032+ mean_value : float
1033+ Average lifetime value across all replications
1034+ """
1035+ key = jax.random.PRNGKey(seed)
1036+ keys = jax.random.split(key, num_reps)
1037+
1038+ # Vectorize the simulation across all replications
1039+ simulate_fn = jax.vmap(simulate_lifetime_value, in_axes=(0, None, None))
1040+ lifetime_values = simulate_fn(keys, model, w_bar)
1041+
1042+ return jnp.mean(lifetime_values)
1043+ ```
1044+
1045+ Now let's compute both the reservation wage and the expected lifetime value
1046+ for each volatility level:
1047+
1048+ ``` {code-cell} ipython3
1049+ # Use the same volatility range and mean wage
1050+ σ_vals = jnp.linspace(0.1, 1.0, 25)
1051+ mean_wage = 20.0
1052+
1053+ # Storage for results
1054+ res_wages_vol = []
1055+ lifetime_values_vol = []
1056+
1057+ for σ in σ_vals:
1058+ μ = compute_μ_for_mean(σ, mean_wage)
1059+ model = create_mccall_continuous(σ=float(σ), μ=float(μ))
1060+
1061+ # Compute reservation wage
1062+ w_bar = compute_reservation_wage_continuous(model)
1063+ res_wages_vol.append(w_bar)
1064+
1065+ # Compute expected lifetime value
1066+ lv = compute_mean_lifetime_value(model, w_bar)
1067+ lifetime_values_vol.append(lv)
1068+
1069+ res_wages_vol = jnp.array(res_wages_vol)
1070+ lifetime_values_vol = jnp.array(lifetime_values_vol)
1071+ ```
1072+
1073+ Let's visualize the expected lifetime value as a function of volatility:
1074+
1075+ ``` {code-cell} ipython3
1076+ fig, ax = plt.subplots()
1077+
1078+ ax.plot(σ_vals, lifetime_values_vol, linewidth=2, color='green')
1079+ ax.set_xlabel(r'volatility ($\sigma$)', fontsize=12)
1080+ ax.set_ylabel('expected lifetime value', fontsize=12)
1081+ plt.show()
1082+ ```
1083+
1084+ The plot confirms that despite workers setting higher reservation wages when facing
1085+ more volatile wage offers (as shown above), they achieve higher expected lifetime
1086+ values due to the option value of search.
1087+
1088+ This demonstrates a key insight from the McCall model: volatility in wage offers
1089+ benefits workers who can optimally time their job acceptance decision.
1090+
9441091
9451092## Exercises
9461093
1094+
9471095``` {exercise}
9481096:label: mm_ex1
9491097
@@ -958,6 +1106,8 @@ given the parameters, and then simulate to see how long it takes to accept.
9581106Repeat a large number of times and take the average.
9591107
9601108Plot mean unemployment duration as a function of $c$ in `c_vals`.
1109+
1110+ Try to explain what you see.
9611111```
9621112
9631113``` {solution-start} mm_ex1
0 commit comments