def table_1(n_states=3, quadratic=False): f, log_Rw, z0, z1, Rf, Rm, SMB, HML = preprocess_data(n_states) g = log_Rw # Set g to be log return on wealth result_min = solve(f, g, z0, z1, ξ=100., quadratic=quadratic, tol=1e-9, max_iter=1000) # Table 1: transition matrix and stationary probability print('Table 1: Empirical and distorted transition probabilities') print('-------------------------------------------------------') print(' empirical min divergence') print('-------------------------------------------------------') print('Transition Matrix:') for state in np.arange(1, n_states + 1): print( f" {np.round(result_min['P'][state-1],2)} {np.round(result_min['P_tilde'][state-1],2)}" ) print('') print('Stationary Probability:') print( f" {np.round(result_min['π'],2)} {np.round(result_min['π_tilde'],2)}" ) print('-------------------------------------------------------')
def figure_1(): # Figure 1: bounds on expected log market return # Below we load presolved ξs but users can use find_ξ to resolve them. f, log_Rw, z0, z1, Rf, Rm, SMB, HML = preprocess_data(n_states=3) g = log_Rw result_min = solve(f, g, z0, z1, ξ=10., quadratic=False, tol=1e-9, max_iter=1000) ξs_lower = [100, 0.29255, 0.20511, 0.16625, 0.14302, 0.12713, 0.11539] ξs_upper = [100, 0.29898, 0.21142, 0.17248, 0.14918, 0.13323, 0.12141] result_lower_list = [] result_upper_list = [] for i in range(0, 7): temp = solve(f, g, z0, z1, ξ=ξs_lower[i], quadratic=False, tol=1e-9, max_iter=1000) result_lower_list.append(temp) temp = solve(f, -g, z0, z1, ξ=ξs_upper[i], quadratic=False, tol=1e-9, max_iter=1000) result_upper_list.append(temp) def f1(percent): box_chart(result_min, result_lower_list[int(percent / 5)], result_upper_list[int(percent / 5)]) res = interact(f1, percent=widgets.IntSlider(min=0, max=30, step=5, value=20)) return res
def table_5(n_states=3): # Table 5: Comparison of transition matrices f, log_Rw, z0, z1, Rf, Rm, SMB, HML = preprocess_data(n_states) g = log_Rw # Set g to be log return on wealth result_re = solve(f, g, z0, z1, ξ=10., quadratic=False, tol=1e-9, max_iter=1000) result_qd = solve(f, g, z0, z1, ξ=10., quadratic=True, tol=1e-9, max_iter=1000) print('Table 5: Transition probabilities and stationary probabilities') print('-------------------------------------------------------') print(' relative entropy quadratic divergence') print('-------------------------------------------------------') print('Transition Matrix:') for state in np.arange(1, n_states + 1): print( f" {np.round(result_re['P'][state-1],2)} {np.round(result_qd['P_tilde'][state-1],2)}" ) print('') print('Stationary Probability:') print( f" {np.round(result_re['π'],2)} {np.round(result_qd['π_tilde'],2)}" ) print('-------------------------------------------------------')
def objective_vs_ξ(n_states): """ An illustration of how the optimized μ and ϵ change with ξ. """ f, log_Rw, z0, z1, Rf, Rm, SMB, HML = preprocess_data(n_states) ξ_grid = np.arange(.01, 1.01, .005) results_lower = [None] * len(ξ_grid) for i in range(len(ξ_grid)): ξ = ξ_grid[i] temp = solve(f=f, g=log_Rw, z0=z0, z1=z1, ξ=ξ, quadratic=False, tol=1e-9, max_iter=1000) results_lower[i] = temp μs_lower = np.array([result['μ'] for result in results_lower]) ϵs_lower = np.array([result['ϵ'] for result in results_lower]) fig = make_subplots(rows=1, cols=2) fig.add_trace(go.Scatter(x=ξ_grid, y=μs_lower, name='μ', line=dict(color='blue')), row=1, col=1) fig.add_trace(go.Scatter(x=ξ_grid, y=ϵs_lower, name='ϵ', line=dict(color='green')), row=1, col=2) fig.update_layout(height=400, width=1000, title_text="Minimized μ (left) and ϵ (right)", showlegend=False) fig.update_xaxes(rangemode="tozero", title_text='ξ') fig.update_yaxes(rangemode="tozero") fig['layout']['xaxis' + str(int(1))].update(range=(0., 1.)) fig['layout']['xaxis' + str(int(2))].update(range=(0., 1.)) fig.show()
def entropy_moment_bounds(n_states): f, log_Rw, z0, z1, Rf, Rm, SMB, HML = preprocess_data(n_states) ξ_grid = np.arange(.01, 1.01, .01) results_lower = [None] * len(ξ_grid) results_upper = [None] * len(ξ_grid) for i in range(len(ξ_grid)): ξ = ξ_grid[i] temp = solve(f=f, g=log_Rw, z0=z0, z1=z1, ξ=ξ, quadratic=False, tol=1e-9, max_iter=1000) results_lower[i] = temp temp = solve(f=f, g=-log_Rw, z0=z0, z1=z1, ξ=ξ, quadratic=False, tol=1e-9, max_iter=1000) results_upper[i] = temp REs_lower = np.array([result['RE'] for result in results_lower]) moment_bounds_cond_lower = np.array( [result['moment_bound_cond'] for result in results_lower]) moment_bounds_cond_upper = np.array( [-result['moment_bound_cond'] for result in results_upper]) moment_bounds_lower = np.array( [result['moment_bound'] for result in results_lower]) moment_bounds_upper = np.array( [-result['moment_bound'] for result in results_upper]) moment_cond = np.array( [result['moment_empirical_cond'] for result in results_lower]) moment = np.array([result['moment_empirical'] for result in results_lower]) # Plots for RE and E[Mg(X)] fig = make_subplots(rows=1, cols=2) fig.add_trace(go.Scatter(x=ξ_grid, y=np.ones_like(ξ_grid) * REs_lower[-1] * 1.2, name='1.2x min RE', line=dict(color='black', dash='dash')), row=1, col=1) fig.add_trace(go.Scatter(x=ξ_grid, y=REs_lower, name='lower bound', line=dict(color='blue')), row=1, col=1) fig.add_trace(go.Scatter(x=ξ_grid, y=moment_bounds_lower, name='lower bound', line=dict(color='green')), row=1, col=2) fig.add_trace(go.Scatter(x=ξ_grid, y=moment_bounds_upper, name='upper bound', line=dict(color='red')), row=1, col=2) fig.add_trace(go.Scatter(x=ξ_grid, y=moment, name='E[g(X)]', line=dict(dash='dash', color='orange')), row=1, col=2) fig.add_trace(go.Scatter(x=ξ_grid, y=moment_bounds_cond_lower[:, 0], name='lower bound', visible=False, line=dict(color='green')), row=1, col=2) fig.add_trace(go.Scatter(x=ξ_grid, y=moment_bounds_cond_upper[:, 0], name='upper bound', visible=False, line=dict(color='red')), row=1, col=2) fig.add_trace(go.Scatter(x=ξ_grid, y=moment_cond[:, 0], name='E[g(X)|1]', visible=False, line=dict(dash='dash', color='orange')), row=1, col=2) fig.add_trace(go.Scatter(x=ξ_grid, y=moment_bounds_cond_lower[:, 1], name='lower bound', visible=False, line=dict(color='green')), row=1, col=2) fig.add_trace(go.Scatter(x=ξ_grid, y=moment_bounds_cond_upper[:, 1], name='upper bound', visible=False, line=dict(color='red')), row=1, col=2) fig.add_trace(go.Scatter(x=ξ_grid, y=moment_cond[:, 1], name='E[g(X)|2]', visible=False, line=dict(dash='dash', color='orange')), row=1, col=2) fig.add_trace(go.Scatter(x=ξ_grid, y=moment_bounds_cond_lower[:, 2], name='lower bound', visible=False, line=dict(color='green')), row=1, col=2) fig.add_trace(go.Scatter(x=ξ_grid, y=moment_bounds_cond_upper[:, 2], name='upper bound', visible=False, line=dict(color='red')), row=1, col=2) fig.add_trace(go.Scatter(x=ξ_grid, y=moment_cond[:, 2], name='E[g(X)|3]', visible=False, line=dict(dash='dash', color='orange')), row=1, col=2) fig.update_layout( height=400, width=1000, title_text="Relative entropy (left) and moment bounds (right)", showlegend=False) fig.update_xaxes(rangemode="tozero", title_text='ξ') fig.update_yaxes(rangemode="tozero") fig['layout']['xaxis' + str(int(1))].update(range=(0., 1.)) fig['layout']['yaxis' + str(int(1))].update(range=(0., 0.06)) fig['layout']['xaxis' + str(int(2))].update(range=(0., 1.)) fig['layout']['yaxis' + str(int(2))].update(range=(-0.01, 0.04)) # Add button fig.update_layout(updatemenus=[ dict( type="buttons", direction="up", active=0, x=1.2, y=0.9, buttons=list([ dict(label="Unconditional", method="update", args=[{ "visible": [True] * 5 + [False] * 15 }]), dict(label="State 1", method="update", args=[{ "visible": [True] * 2 + [False] * 3 + [True] * 3 + [False] * 6 }]), dict(label="State 2", method="update", args=[{ "visible": [True] * 2 + [False] * 6 + [True] * 3 + [False] * 3 }]), dict(label="State 3", method="update", args=[{ "visible": [True] * 2 + [False] * 9 + [True] * 3 }]), ]), ) ]) fig.show()
def table_3(): # Table 3: Expected log market return bounds f, log_Rw, z0, z1, Rf, Rm, SMB, HML = preprocess_data(n_states=3) g = log_Rw # Set g to be log return on wealth # 1) Relative entropy specification result_min_RE = solve(f, g, z0, z1, ξ=100., quadratic=False, tol=1e-9, max_iter=1000) ξ_20_lower_RE = find_ξ(solver_args=(f, g, z0, z1, False, 1e-9, 1000), min_div=result_min_RE['RE'], pct=0.2, initial_guess=1., interval=(0, 10.), tol=1e-5, max_iter=100) result_lower_RE = solve(f, g, z0, z1, ξ=ξ_20_lower_RE, quadratic=False, tol=1e-9, max_iter=1000) ξ_20_upper_RE = find_ξ(solver_args=(f, -g, z0, z1, False, 1e-9, 1000), min_div=result_min_RE['RE'], pct=0.2, initial_guess=1., interval=(0, 10.), tol=1e-5, max_iter=100) result_upper_RE = solve(f, -g, z0, z1, ξ=ξ_20_upper_RE, quadratic=False, tol=1e-9, max_iter=1000) # 2) Quadratic specification result_min_QD = solve(f, g, z0, z1, ξ=10., quadratic=True, tol=1e-9, max_iter=1000) ξ_20_lower_QD = find_ξ(solver_args=(f, g, z0, z1, True, 1e-9, 1000), min_div=result_min_QD['QD'], pct=0.2, initial_guess=1., interval=(0, 10.), tol=1e-4, max_iter=100) result_lower_QD = solve(f, g, z0, z1, ξ=ξ_20_lower_QD, quadratic=True, tol=1e-9, max_iter=1000) ξ_20_upper_QD = find_ξ(solver_args=(f, -g, z0, z1, True, 1e-9, 1000), min_div=result_min_QD['QD'], pct=0.2, initial_guess=1., interval=(0, 10.), tol=1e-4, max_iter=100) result_upper_QD = solve(f, -g, z0, z1, ξ=ξ_20_upper_QD, quadratic=True, tol=1e-9, max_iter=1000) print('Table 3: Expected log market return bounds') print( '-----------------------------------------------------------------------' ) print( 'conditioning empirical relative entropy quadratic divergence' ) print(' (lower, upper) (lower,upper)') print( '-----------------------------------------------------------------------' ) print('low D/P %s (%s,%s) (%s,%s) ' \ % (np.round(result_min_RE['moment_empirical_cond'][0]*400,2), np.round(result_lower_RE['moment_bound_cond'][0]*400,2), np.round(-result_upper_RE['moment_bound_cond'][0]*400,2), np.round(result_lower_QD['moment_bound_cond'][0]*400,2), np.round(-result_upper_QD['moment_bound_cond'][0]*400,2))) print('mid D/P %s (%s,%s) (%s,%s) ' \ % (np.round(result_min_RE['moment_empirical_cond'][1]*400,2), np.round(result_lower_RE['moment_bound_cond'][1]*400,2), np.round(-result_upper_RE['moment_bound_cond'][1]*400,2), np.round(result_lower_QD['moment_bound_cond'][1]*400,2), np.round(-result_upper_QD['moment_bound_cond'][1]*400,2))) print('high D/P %s (%s,%s) (%s,%s) ' \ % (np.round(result_min_RE['moment_empirical_cond'][2]*400,2), np.round(result_lower_RE['moment_bound_cond'][2]*400,2), np.round(-result_upper_RE['moment_bound_cond'][2]*400,2), np.round(result_lower_QD['moment_bound_cond'][2]*400,2), np.round(-result_upper_QD['moment_bound_cond'][2]*400,2))) print('unconditional %s (%s,%s) (%s,%s) ' \ % (np.round(result_min_RE['moment_empirical']*400,2), np.round(result_lower_RE['moment_bound']*400,2), np.round(-result_upper_RE['moment_bound']*400,2), np.round(result_lower_QD['moment_bound']*400,2), np.round(-result_upper_QD['moment_bound']*400,2))) print( '-----------------------------------------------------------------------' ) print('Note 1: here we use n_states = 3.') print( 'Note 2: the numbers in the parentheses impose a divergence constraint' ) print(' that is 20 percent higher than the minimum.')
def table_2(): # Table 2: bounds on log expected return and generalized volatility f, log_Rw, z0, z1, Rf, Rm, SMB, HML = preprocess_data(n_states=3) # 1) Calculate bounds on expected return # Minimum divergence case result_min = solve(f, Rm, z0, z1, ξ=100., quadratic=False, tol=1e-9, max_iter=1000) # 20% higher divergence case, lower bound problem ξ_20_lower = find_ξ(solver_args=(f, Rm, z0, z1, False, 1e-9, 1000), min_div=result_min['RE'], pct=0.2, initial_guess=1., interval=(0, 100.), tol=1e-5, max_iter=100) result_lower = solve(f=f, g=Rm, z0=z0, z1=z1, ξ=ξ_20_lower, quadratic=False, tol=1e-9, max_iter=1000) # 20% higher divergence case, upper bound problem ξ_20_upper = find_ξ(solver_args=(f, -Rm, z0, z1, False, 1e-9, 1000), min_div=result_min['RE'], pct=0.2, initial_guess=1., interval=(0, 100.), tol=1e-5, max_iter=100) result_upper = solve(f=f, g=-Rm, z0=z0, z1=z1, ξ=ξ_20_upper, quadratic=False, tol=1e-9, max_iter=1000) # 2) Calculate bounds on generalized volatility # Below we load presolved ζs but users can plot the objective function over ζ to find the optimal ζs. g1 = Rm g2 = log_Rw solver_args = (f, z0, z1, False, 1e-9, 1000) vol_min, vol_cond_min, vol_empirical, vol_cond_empirical\ = bound_ratio(find_ξ_args=(solver_args, 0., None, None, None, None), g1=g1, g2=g2, ζ=1., lower=True, result_type=2) vol_lower, vol_cond_lower, _, _\ = bound_ratio(find_ξ_args=(solver_args, 0.2, 1., (0., 10.), 1e-5, 100), g1=g1, g2=g2, ζ=1.008, lower=True, result_type=2) vol_upper, vol_cond_upper, _, _\ = bound_ratio(find_ξ_args=(solver_args, 0.2, 1., (0., 10.), 1e-5, 100), g1=g1, g2=g2, ζ=1.009, lower=False, result_type=2) print('Table 2: Log expected market return and generalized volatility') print( '-------------------------------------------------------------------------------' ) print( 'conditioning logE logE logE - Elog logE - Elog' ) print( ' empirical imputed empirical imputed' ) print( ' (lower, upper) (lower,upper)' ) print( '-------------------------------------------------------------------------------' ) print('low D/P %s %s %s %s' \ % (np.round(np.log(result_min['moment_empirical_cond'][0])*400,2), np.round(np.log(result_min['moment_bound_cond'][0])*400,2), np.round(vol_cond_empirical[0]*400,2), np.round(vol_cond_min[0]*400,2))) print(' (%s,%s) (%s,%s)' \ % (np.round(np.log(result_lower['moment_bound_cond'][0])*400,2), np.round(np.log(-result_upper['moment_bound_cond'][0])*400,2), np.round(vol_cond_lower[0]*400,2),np.round(vol_cond_upper[0]*400,2))) print('mid D/P %s %s %s %s' \ % (np.round(np.log(result_min['moment_empirical_cond'][1])*400,2), np.round(np.log(result_min['moment_bound_cond'][1])*400,2), np.round(vol_cond_empirical[1]*400,2),np.round(vol_cond_min[1]*400,2))) print(' (%s,%s) (%s,%s)' \ % (np.round(np.log(result_lower['moment_bound_cond'][1])*400,2), np.round(np.log(-result_upper['moment_bound_cond'][1])*400,2), np.round(vol_cond_lower[1]*400,2),np.round(vol_cond_upper[1]*400,2))) print('high D/P %s %s %s %s' \ % (np.round(np.log(result_min['moment_empirical_cond'][2])*400,2), np.round(np.log(result_min['moment_bound_cond'][2])*400,2), np.round(vol_cond_empirical[2]*400,2),np.round(vol_cond_min[2]*400,2))) print(' (%s,%s) (%s,%s)' \ % (np.round(np.log(result_lower['moment_bound_cond'][2])*400,2), np.round(np.log(-result_upper['moment_bound_cond'][2])*400,2), np.round(vol_cond_lower[2]*400,2),np.round(vol_cond_upper[2]*400,2))) print('unconditional %s %s %s %s' \ % (np.round(np.log(result_min['moment_empirical'])*400,2), np.round(np.log(result_min['moment_bound'])*400,2), np.round(vol_empirical*400,2),np.round(vol_min*400,2))) print(' (%s,%s) (%s,%s)' \ % (np.round(np.log(result_lower['moment_bound'])*400,2), np.round(np.log(-result_upper['moment_bound'])*400,2), np.round(vol_lower*400,2),np.round(vol_upper*400,2))) print( '-------------------------------------------------------------------------------' ) print( 'Note 1: here we use n_states = 3 and a relative entropy divergence.') print( 'Note 2: the numbers in the parentheses impose a divergence constraint' ) print(' that is 20 percent higher than the minimum.')
def bound_ratio(find_ξ_args, g1, g2, ζ, lower=True, result_type=0): """ This function computes the bound on ratios of two gs at a given ζ. Parameters ---------- find_ξ_args : tuple Arguments (except for g in solver_args and min_div) to be passed into find_ξ, including (solver_args, pct, initial_guess, interval, tol, max_iter). If pct == 0., then the function will use ξ = 100. g1 : (n,) ndarray The g1 in g = g1 - ζ*g2. g2 : (n,) ndarray The g2 in g = g1 - ζ*g2. ζ : float The ζ in g = g1 - ζ*g2. lower : bool If True, it will compute the lower bound of the ratio. If False, it will compute the upper bound of the ratio. result_type : int If 0, the function will compute the log difference of the two moments. If 1, the function will compute the ratio of the two moments. If 2, the function will subtract the log of the first moment by the second moment. Returns ------- ratio : float Unconditional ratio of two moments. ratio_cond : (n_states,) ndarray Conditional ratios of two moments. ratio_empirical : float Empirical unconditional ratio of two moments. ratio_cond_empirical : (n_states,) ndarray Empirical conditional ratios of two moments. """ if lower: g = g1 - ζ * g2 else: g = -(g1 - ζ * g2) # f, z0, z1, quadratic, tol, max_iter f = find_ξ_args[0][0] z0 = find_ξ_args[0][1] z1 = find_ξ_args[0][2] n_states = z1.shape[1] quadratic = find_ξ_args[0][3] solver_tol = find_ξ_args[0][4] solver_max_iter = find_ξ_args[0][5] solver_args = (f, g, z0, z1, quadratic, solver_tol, solver_max_iter) result = solve(f, g, z0, z1, 100., quadratic, solver_tol, solver_max_iter) if find_ξ_args[1] != 0: if quadratic: min_div = result['QD'] else: min_div = result['RE'] ξ = find_ξ(solver_args, min_div, find_ξ_args[1], find_ξ_args[2], find_ξ_args[3], find_ξ_args[4], find_ξ_args[5]) result = solve(f, g, z0, z1, ξ, quadratic, solver_tol, solver_max_iter) # Calculate ratio, empirical # Term 1 moment_cond_g1 = np.zeros(n_states) for state in range(n_states): moment_cond_g1[state] = np.mean(g1[z0[:, state]]) moment_g1 = moment_cond_g1 @ result['π'] # Term 2 moment_cond_g2 = np.zeros(n_states) for state in range(n_states): moment_cond_g2[state] = np.mean(g2[z0[:, state]]) moment_g2 = moment_cond_g2 @ result['π'] # Calculate ratio, distorted # Term 1 moment_bound_cond_g1 = np.zeros(n_states) for state in range(n_states): moment_bound_cond_g1[state] = np.mean(result['N'][z0[:, state]] * g1[z0[:, state]]) moment_bound_g1 = moment_bound_cond_g1 @ result['π_tilde'] # Term 2 moment_bound_cond_g2 = np.zeros(n_states) for state in range(n_states): moment_bound_cond_g2[state] = np.mean(result['N'][z0[:, state]] * g2[z0[:, state]]) moment_bound_g2 = moment_bound_cond_g2 @ result['π_tilde'] # Combine term 1 and term 2 if result_type == 0: ratio_empirical = np.log(moment_g1) - np.log(moment_g2) ratio_empirical_cond = np.log(moment_cond_g1) - np.log(moment_cond_g2) ratio_bound = np.log(moment_bound_g1) - np.log(moment_bound_g2) ratio_bound_cond = np.log(moment_bound_cond_g1) - np.log( moment_bound_cond_g2) elif result_type == 1: ratio_empirical = moment_g1 / moment_g2 ratio_empirical_cond = moment_cond_g1 / moment_cond_g2 ratio_bound = moment_bound_g1 / moment_bound_g2 ratio_bound_cond = moment_bound_cond_g1 / moment_bound_cond_g2 elif result_type == 2: ratio_empirical = np.log(moment_g1) - moment_g2 ratio_empirical_cond = np.log(moment_cond_g1) - moment_cond_g2 ratio_bound = np.log(moment_bound_g1) - moment_bound_g2 ratio_bound_cond = np.log(moment_bound_cond_g1) - moment_bound_cond_g2 else: raise ValueError('Invalid result_typle. It should be 0, 1 or 2.') return ratio_bound, ratio_bound_cond, ratio_empirical, ratio_empirical_cond