def get_price(self, num_dt: int, dx: float, num_dx: int) -> float: dt = self.expiry / num_dt x_pts = 2 * num_dx + 1 x_limit = dx * num_dx res = np.empty([num_dt + 1, x_pts]) prices = np.linspace(-x_limit, x_limit, x_pts) + self.spot_price res[-1, :] = [max(self.payoff(self.expiry, p), 0.) for p in prices] for i in range(num_dt - 1, -1, -1): t = i * dt knots, coeffs, order = splrep(prices, res[i + 1, :], k=3) spline_func = BSpline(knots, coeffs, order) disc = np.exp(self.ir(t) - self.ir(t + dt)) for j in range(x_pts): m, v = get_future_price_mean_var(prices[j], t, dt, self.ir, self.dispersion) stdev = np.sqrt(v) norm_dist = norm(loc=m, scale=stdev) sample_points = 201 integr_func = lambda x: max(spline_func(x), 0. ) * norm_dist.pdf(x) low, high = (m - 4 * stdev, m + 4 * stdev) disc_exp_payoff = disc * trapz( np.vectorize(integr_func)(np.linspace( low, high, sample_points)), dx=(high - low) / (sample_points - 1)) / (norm_dist.cdf(high) - norm_dist.cdf(low)) res[i, j] = max(self.payoff(t, prices[j]), disc_exp_payoff) return res[0, num_dx]
def get_fqi_price(self, num_dt: int, num_paths: int, feature_funcs: Sequence[Callable[[int, np.ndarray], float]], batch_size: int, model_prob_draws: int) -> float: features = len(feature_funcs) a_mat = np.zeros((features, features)) b_vec = np.zeros(features) params = np.zeros(features) paths = self.get_all_paths(num_paths, num_dt + 1) dt = self.expiry / num_dt for path_num, path in enumerate(paths): for step in range(num_dt): t = step * dt disc = np.exp(self.ir(t) - self.ir(t + dt)) phi_s = np.array( [f(step, path[:(step + 1)]) for f in feature_funcs]) if model_prob_draws > 1: m, v = get_future_price_mean_var(path[step], t, dt, self.lognormal, self.ir, self.isig) norm_draws = np.random.normal(m, np.sqrt(v), model_prob_draws) local_paths = [ np.append(paths[:(step + 1)], nd) for nd in (np.exp(norm_draws) if self.lognormal else norm_draws) ] else: local_paths = [path[:(step + 2)]] all_max_val = np.zeros(len(local_paths)) for i, local_path in enumerate(local_paths): next_payoff = self.payoff(t + dt, local_path) if step == num_dt - 1: next_phi = np.zeros(features) else: next_phi = np.array( [f(step + 1, local_path) for f in feature_funcs]) all_max_val[i] = max(next_payoff, params.dot(next_phi)) max_val = np.mean(all_max_val) a_mat += np.outer(phi_s, phi_s) b_vec += phi_s * disc * max_val if (path_num + 1) % batch_size == 0: params = np.linalg.inv(a_mat).dot(b_vec) # print(params) a_mat = np.zeros((features, features)) b_vec = np.zeros(features) return self.get_price_from_paths_and_params(paths, params, num_dt, feature_funcs)
def get_all_paths(self, num_paths: int, num_dt: int) -> np.ndarray: dt = self.expiry / num_dt paths = np.empty([num_paths, num_dt + 1]) paths[:, 0] = self.spot_price for i in range(num_paths): price = self.spot_price for t in range(num_dt): m, v = get_future_price_mean_var(price, t, dt, self.ir, self.dispersion) price = np.random.normal(m, np.sqrt(v)) paths[i, t + 1] = price return paths
def get_price( self, num_dt: int, num_paths: int, feature_funcs: Sequence[Callable[[float, np.ndarray], float]]) -> float: dt = self.expiry / num_dt paths = np.empty([num_paths, num_dt + 1]) paths[:, 0] = self.spot_price for i in range(num_paths): price = self.spot_price for t in range(num_dt): m, v = get_future_price_mean_var(price, t, dt, self.ir, self.dispersion) price = np.random.normal(m, np.sqrt(v)) paths[i, t + 1] = price cashflow = np.array([ max(self.payoff(self.expiry, paths[i, :]), 0.) for i in range(num_paths) ]) for t in range(num_dt - 1, 0, -1): """ For each time slice t Step 1: collect X as features of (t, [S_0,.., S_t]) for those paths for which payoff(t, [S_0, ...., S_t]) > 0, and corresponding Y as the time-t discounted future actual cash flow on those paths. Step 2: Do the (X,Y) regression. Denote Y^ as regression-prediction. Compare Y^ versus payoff(t, [S_0, ..., S_t]). If payoff is higher, set cashflow at time t on that path to be the payoff, else set cashflow at time t on that path to be the time-t discounted future actual cash flow on that path. """ disc = np.exp(self.ir(t) - self.ir(t + dt)) cashflow = cashflow * disc payoff = np.array( [self.payoff(t, paths[i, :(t + 1)]) for i in range(num_paths)]) indices = [i for i in range(num_paths) if payoff[i] > 0] if len(indices) > 0: x_vals = np.array( [[f(t, paths[i, :(t + 1)]) for f in feature_funcs] for i in indices]) y_vals = np.array([cashflow[i] for i in indices]) estimate = x_vals.dot( np.linalg.lstsq(x_vals, y_vals, rcond=None)[0]) # plt.scatter([paths[i, t] for i in indices], y_vals, c='r') # plt.scatter([paths[i, t] for i in indices], estimate, c='b') # plt.show() for i, ind in enumerate(indices): if payoff[ind] > estimate[i]: cashflow[ind] = payoff[ind] return max(self.payoff(0, np.array([self.spot_price])), np.average(cashflow * np.exp(-self.ir(dt))))
def state_reward_gen(self, state: StateType, action: ActionType, num_dt: int) -> Tuple[StateType, float]: ind, price_arr = state delta_t = self.expiry / num_dt t = ind * delta_t reward = (np.exp(-self.ir(t)) * self.payoff(t, price_arr)) if\ (action and ind <= num_dt) else 0. m, v = get_future_price_mean_var(price_arr[-1], t, delta_t, self.ir, self.dispersion) next_price = np.random.normal(m, np.sqrt(v)) price1 = np.append(price_arr, next_price) next_ind = (num_dt if action else ind) + 1 return (next_ind, price1), reward
def get_all_paths(self, spot_pct_noise: float, num_paths: int, num_dt: int) -> np.ndarray: dt = self.expiry / num_dt paths = np.empty([num_paths, num_dt + 1]) spot = self.spot_price for i in range(num_paths): start = max(0.001, np.random.normal(spot, spot * spot_pct_noise)) paths[i, 0] = start for t in range(num_dt): m, v = get_future_price_mean_var(paths[i, t], t, dt, self.lognormal, self.ir, self.isig) norm_draw = np.random.normal(m, np.sqrt(v)) paths[i, t + 1] = np.exp(norm_draw) if self.lognormal else norm_draw return paths
def get_price(self, num_dt: int, num_dx: int, center: float, width: float) -> float: """ :param num_dt: represents number of discrete time steps :param num_dx: represents number of discrete state-space steps (on each side of the center) :param center: represents the center of the state space grid. For the case of lognormal == True, it should be Mean[log(x_{expiry}]. For the case of lognormal == False, it should be Mean[x_{expiry}]. :param width: represents the width of the state space grid. For the case of lognormal == True, it should be a multiple of Stdev[log(x_{expiry})]. For the case of lognormal == True, it should be a multiple of Stdev[log(x_{expiry})]. :return: the price of the American option (this is the discounted expected payoff at time 0 at current stock price. """ dt = self.expiry / num_dt x_pts = 2 * num_dx + 1 lsp = np.linspace(center - width, center + width, x_pts) prices = np.exp(lsp) if self.lognormal else lsp res = np.empty([num_dt, x_pts]) res[-1, :] = [max(self.payoff(self.expiry, p), 0.) for p in prices] sample_points = 201 for i in range(num_dt - 2, -1, -1): t = (i + 1) * dt knots, coeffs, order = splrep(prices, res[i + 1, :], k=3) spline_func = BSpline(knots, coeffs, order) disc = np.exp(self.ir(t) - self.ir(t + dt)) for j in range(x_pts): m, v = get_future_price_mean_var(prices[j], t, dt, self.lognormal, self.ir, self.isig) stdev = np.sqrt(v) norm_dist = norm(loc=m, scale=stdev) # noinspection PyShadowingNames def integr_func(x: float, spline_func=spline_func, norm_dist=norm_dist) -> float: val = np.exp(x) if self.lognormal else x return max(spline_func(val), 0.) * norm_dist.pdf(x) low, high = (m - 4 * stdev, m + 4 * stdev) disc_exp_payoff = disc * trapz( np.vectorize(integr_func)(np.linspace( low, high, sample_points)), dx=(high - low) / (sample_points - 1)) / (norm_dist.cdf(high) - norm_dist.cdf(low)) res[i, j] = max(self.payoff(t, prices[j]), disc_exp_payoff) knots, coeffs, order = splrep(prices, res[0, :], k=3) spline_func = BSpline(knots, coeffs, order) disc = np.exp(-self.ir(dt)) m, v = get_future_price_mean_var(self.spot_price, 0., dt, self.lognormal, self.ir, self.isig) stdev = np.sqrt(v) norm_dist = norm(loc=m, scale=stdev) # noinspection PyShadowingNames def integr_func0(x: float, spline_func=spline_func, norm_dist=norm_dist) -> float: val = np.exp(x) if self.lognormal else x return max(spline_func(val), 0.) * norm_dist.pdf(x) low, high = (m - 4 * stdev, m + 4 * stdev) disc_exp_payoff = disc * trapz( np.vectorize(integr_func0)(np.linspace(low, high, sample_points)), dx=(high - low) / (sample_points - 1)) / (norm_dist.cdf(high) - norm_dist.cdf(low)) return max(self.payoff(0., self.spot_price), disc_exp_payoff)
from examples.american_pricing.bs_pricing import EuropeanBSPricing ebsp = EuropeanBSPricing(is_call=False, spot_price=spot_price_val, strike=strike_val, expiry=expiry_val, r=rr, sigma=sigma_val) print(ebsp.option_price) # noinspection PyShadowingNames ir_func = lambda t, rr=rr: rr * t # noinspection PyShadowingNames isig_func = lambda t, sigma_val=sigma_val: sigma_val * sigma_val * t gp = GridPricing( spot_price=spot_price_val, payoff=payoff_func, expiry=expiry_val, lognormal=lognormal_val, ir=ir_func, isig=isig_func, ) num_dt_val = 10 num_dx_val = 100 expiry_mean, expiry_var = get_future_price_mean_var( spot_price_val, 0., expiry_val, lognormal_val, ir_func, isig_func) print( gp.get_price(num_dt=num_dt_val, num_dx=num_dx_val, center=expiry_mean, width=np.sqrt(expiry_var) * 4.))
def get_vanilla_american_price( is_call: bool, spot_price: float, strike: float, expiry: float, lognormal: bool, r: float, sigma: float, num_dt: int, num_paths: int, num_laguerre: int, params_bag: Mapping[str, Any]) -> Mapping[str, float]: opt_payoff = lambda _, x, is_call=is_call, strike=strike:\ max(x - strike, 0.) if is_call else max(strike - x, 0.) # noinspection PyShadowingNames ir_func = lambda t, r=r: r * t isig_func = lambda t, sigma=sigma: sigma * sigma * t num_dx = 200 expiry_mean, expiry_var = get_future_price_mean_var( spot_price, 0., expiry, lognormal, ir_func, isig_func) grid_price = GridPricing(spot_price=spot_price, payoff=opt_payoff, expiry=expiry, lognormal=lognormal, ir=ir_func, isig=isig_func).get_price( num_dt=num_dt, num_dx=num_dx, center=expiry_mean, width=np.sqrt(expiry_var) * 4) gp = AmericanPricing( spot_price=spot_price, payoff=(lambda t, x, opt_payoff=opt_payoff: opt_payoff(t, x[-1])), expiry=expiry, lognormal=lognormal, ir=ir_func, isig=isig_func) ident = np.eye(num_laguerre) # noinspection PyShadowingNames def laguerre_feature_func(x: float, i: int, ident=ident, strike=strike) -> float: # noinspection PyTypeChecker xp = x / strike return np.exp(-xp / 2) * lagval(xp, ident[i]) ls_price = gp.get_ls_price( num_dt=num_dt, num_paths=num_paths, feature_funcs=[lambda _, x: 1.] + [(lambda _, x, i=i: laguerre_feature_func(x[-1], i)) for i in range(num_laguerre)]) # noinspection PyShadowingNames def rl_feature_func(ind: int, x: float, a: bool, i: int, num_laguerre: int = num_laguerre, num_dt: int = num_dt, expiry: float = expiry) -> float: dt = expiry / num_dt t = ind * dt if i < num_laguerre + 4: if ind < num_dt and not a: if i == 0: ret = 1. elif i < num_laguerre + 1: ret = laguerre_feature_func(x, i - 1) elif i == num_laguerre + 1: if t >= expiry_val: ret = 0. else: ret = np.sin(-t * np.pi / (2. * expiry) + np.pi / 2.) elif i == num_laguerre + 2: if t >= expiry_val: ret = -LARGENUM else: ret = np.log(expiry - t) else: if t >= expiry_val: ret = 1. else: rat = t / expiry ret = rat * rat else: ret = 0. else: if ind <= num_dt and a: ret = np.exp(-r * (ind * dt)) * opt_payoff(ind * dt, x) else: ret = 0. return ret rl_price = gp.get_rl_fa_price( num_dt=num_dt, method=params_bag["method"], exploring_start=params_bag["exploring_start"], algorithm=params_bag["algorithm"], softmax=params_bag["softmax"], epsilon=params_bag["epsilon"], epsilon_half_life=params_bag["epsilon_half_life"], lambd=params_bag["lambda"], num_paths=num_paths, batch_size=params_bag["batch_size"], feature_funcs=[ (lambda x, i=i: rl_feature_func(x[0][0], x[0][1][-1], x[1], i)) for i in range(num_laguerre + 5) ], neurons=params_bag["neurons"], learning_rate=params_bag["learning_rate"], learning_rate_decay=params_bag["learning_rate_decay"], adam=params_bag["adam"], offline=params_bag["offline"]) return {"Grid": grid_price, "LS": ls_price, "RL": rl_price}
def get_price( self, num_dt: int, num_dx: int, center: float, width: float ) -> float: """ :param num_dt: represents number of discrete time steps :param num_dx: represents number of discrete state-space steps (on each side of the center) :param center: represents the center of the state space grid. For the case of lognormal == True, it should be Mean[log(x_{expiry}]. For the case of lognormal == False, it should be Mean[x_{expiry}]. :param width: represents the width of the state space grid. For the case of lognormal == True, it should be a multiple of Stdev[log(x_{expiry})]. :return: the price of the American option (this is the discounted expected payoff at time 0 at current stock price. """ dt = self.expiry / num_dt x_pts = 2 * num_dx + 1 lsp = np.linspace(center - width, center + width, x_pts) prices = np.exp(lsp) if self.lognormal else lsp res = np.empty([num_dt, x_pts]) res[-1, :] = [max(self.payoff(self.expiry, p), 0.) for p in prices] sample_points = 201 final = [(p, max(self.payoff(self.expiry, p), 0.)) for p in prices] ex_boundary = [max(p for p, e in final if e > 0)] for i in range(num_dt - 2, -1, -1): t = (i + 1) * dt knots, coeffs, order = splrep(prices, res[i + 1, :], k=3) spline_func = BSpline(knots, coeffs, order) disc = np.exp(self.ir(t) - self.ir(t + dt)) stprcs = [] cp = [] ep = [] for j in range(x_pts): m, v = get_future_price_mean_var( prices[j], t, dt, self.lognormal, self.ir, self.isig ) stdev = np.sqrt(v) norm_dist = norm(loc=m, scale=stdev) # noinspection PyShadowingNames def integr_func( x: float, spline_func=spline_func, norm_dist=norm_dist ) -> float: val = np.exp(x) if self.lognormal else x return max(spline_func(val), 0.) * norm_dist.pdf(x) low, high = (m - 4 * stdev, m + 4 * stdev) disc_exp_payoff = disc * trapz( np.vectorize(integr_func)(np.linspace(low, high, sample_points)), dx=(high - low) / (sample_points - 1) ) / (norm_dist.cdf(high) - norm_dist.cdf(low)) if prices[j] < 100: stprcs.append(prices[j]) cp.append(disc_exp_payoff) ep.append(max(self.payoff(t, prices[j]), 0.)) res[i, j] = max(self.payoff(t, prices[j]), disc_exp_payoff) ex_boundary.append(max(p for p, c, e in zip(stprcs, cp, ep) if e > c)) # if i == int(num_dt / 10) or i == num_dt - int(num_dt / 10) \ # or i == int(num_dt / 2): # # print(list(zip(stprcs, cp, ep))) # plt.title("Grid Time = %.3f" % t) # plt.plot(stprcs, cp, 'r', stprcs, ep, 'b') # plt.show() # plt.plot([t * dt for t in range(1, num_dt + 1)], ex_boundary[::-1]) # plt.title("Grid Boundary") # plt.savefig(str(Path.home()) + "/Downloads/GridBoundary.png") knots, coeffs, order = splrep(prices, res[0, :], k=3) spline_func = BSpline(knots, coeffs, order) disc = np.exp(-self.ir(dt)) m, v = get_future_price_mean_var( self.spot_price, 0., dt, self.lognormal, self.ir, self.isig ) stdev = np.sqrt(v) norm_dist = norm(loc=m, scale=stdev) # noinspection PyShadowingNames def integr_func0( x: float, spline_func=spline_func, norm_dist=norm_dist ) -> float: val = np.exp(x) if self.lognormal else x return max(spline_func(val), 0.) * norm_dist.pdf(x) low, high = (m - 4 * stdev, m + 4 * stdev) disc_exp_payoff = disc * trapz( np.vectorize(integr_func0)(np.linspace(low, high, sample_points)), dx=(high - low) / (sample_points - 1) ) / (norm_dist.cdf(high) - norm_dist.cdf(low)) return max(self.payoff(0., self.spot_price), disc_exp_payoff)