Beispiel #1
0
    def test_european_derivative_price_delta_mc_close_to_black_scholes(self):
        current_price = tf.constant(100.0, dtype=tf.float32)
        r = 0.05
        vol = tf.constant([[0.2]], dtype=tf.float32)
        strike = 100.0
        maturity = 0.1
        dt = 0.01

        bs_call_price = util.black_scholes_call_price(current_price, r, vol,
                                                      strike, maturity)
        bs_delta = monte_carlo_manager.sensitivity_autodiff(
            bs_call_price, current_price)

        num_samples = int(1e3)
        initial_states = tf.ones([num_samples, 1]) * current_price

        def _dynamics_op(log_s, t, dt):
            return dynamics.gbm_log_euler_step_nd(log_s, r, vol, t, dt, key=1)

        def _payoff_fn(log_s):
            return tf.exp(-r * maturity) * payoffs.call_payoff(
                tf.exp(log_s), strike)

        (_, _, outcomes) = monte_carlo_manager.non_callable_price_mc(
            tf.log(initial_states), _dynamics_op, _payoff_fn, maturity,
            num_samples, dt)

        mc_deltas = monte_carlo_manager.sensitivity_autodiff(
            outcomes, initial_states)

        mean_mc_deltas = tf.reduce_mean(mc_deltas)
        mc_deltas_std = tf.sqrt(
            tf.reduce_mean(mc_deltas**2) - (mean_mc_deltas**2))

        with self.test_session() as session:
            bs_delta_eval = session.run(bs_delta)
            mean_mc_deltas_eval, mc_deltas_std_eval = session.run(
                (mean_mc_deltas, mc_deltas_std))

        self.assertLessEqual(
            mean_mc_deltas_eval,
            bs_delta_eval + 3.0 * mc_deltas_std_eval / np.sqrt(num_samples))
        self.assertGreaterEqual(
            mean_mc_deltas_eval,
            bs_delta_eval - 3.0 * mc_deltas_std_eval / np.sqrt(num_samples))
def main(_):
    num_dims = FLAGS.num_dims
    num_batches = FLAGS.num_batches
    hist_drift = FLAGS.drift
    hist_vol = FLAGS.volatility
    hist_cor = FLAGS.correlation
    hist_cor_matrix = (hist_cor * np.ones(
        (num_dims, num_dims)) + (1.0 - hist_cor) * np.eye(num_dims))

    hist_price = FLAGS.initial_price * np.ones(num_dims)
    hist_vol_matrix = hist_vol * np.real(scipy.linalg.sqrtm(hist_cor_matrix))

    hist_dt = FLAGS.delta_t_historical
    sim_dt = FLAGS.delta_t_monte_carlo

    strike = FLAGS.strike
    maturity = FLAGS.maturity

    # Placeholders for tensorflow-based simulator's arguments.
    sim_price = tf.placeholder(shape=[num_dims], dtype=tf.float32)
    sim_drift = tf.placeholder(shape=(), dtype=tf.float32)
    sim_vol_matrix = tf.constant(hist_vol_matrix, dtype=tf.float32)
    sim_maturity = tf.placeholder(shape=(), dtype=tf.float32)

    # Transition operation between t and t + dt with price in log scale.
    def _dynamics_op(log_s, t, dt):
        return gbm_log_euler_step_nd(log_s, sim_drift, sim_vol_matrix, t, dt)

    # Terminal payoff function (with price in log scale).
    def _payoff_fn(log_s):
        return basket_call_payoff(tf.exp(log_s), strike)

    # Call's price and delta estimates (sensitivity to current underlying price).
    # Monte Carlo estimation under the risk neutral probability is used.
    # The reason why we employ the risk neutral probability is that the position
    # is hedged each day depending on the value of the underlying.
    # See http://www.cmap.polytechnique.fr/~touzi/Poly-MAP552.pdf for a complete
    # explanation.
    price_estimate, _, _ = monte_carlo_manager.non_callable_price_mc(
        initial_state=tf.log(sim_price),
        dynamics_op=_dynamics_op,
        payoff_fn=_payoff_fn,
        maturity=sim_maturity,
        num_samples=FLAGS.num_batch_samples,
        dt=sim_dt)
    delta_estimate = monte_carlo_manager.sensitivity_autodiff(
        price_estimate, sim_price)

    # Start the hedging experiment.
    session = tf.Session()

    hist_price_profile = []
    cash_profile = []
    underlying_profile = []
    wall_times = []

    t = 0
    cash_owned = 0.0
    underlying_owned = np.zeros(num_dims)
    while t <= maturity:
        # Each day, a new stock price is observed.

        cash_eval = 0.0
        delta_eval = 0.0
        for _ in range(num_batches):
            if t == 0.0:
                # The first day a derivative price is computed to decide how mush cash
                # is initially needed to replicate the derivative's payoff at maturity.
                cash_eval_batch = controllers.price_derivative(
                    price_estimate,
                    session,
                    params={
                        sim_drift: 0.0,
                        sim_price: hist_price,
                        sim_maturity: maturity - t
                    })

            # Each day the delta of the derivative is computed to decide how many
            # shares of the underlying should be owned to replicate the derivative's
            # payoff at maturity.
            start_time = time.time()
            delta_eval_batch = controllers.hedge_derivative(delta_estimate,
                                                            session,
                                                            params={
                                                                sim_drift:
                                                                0.0,
                                                                sim_price:
                                                                hist_price,
                                                                sim_maturity:
                                                                maturity - t
                                                            })
            wall_times.append(time.time() - start_time)
            delta_eval += delta_eval_batch / num_batches
            cash_eval += cash_eval_batch / num_batches

        if t == 0.0:
            logging.info("Initial price estimate: %.2f", cash_eval)

        # Self-financing portfolio dynamics, held cash is used to buy the underlying
        # or increases when the underlying is sold.
        if t == 0.0:
            cash_owned = cash_eval - np.sum(delta_eval * hist_price)
            underlying_owned = delta_eval
        else:
            cash_owned -= np.sum((delta_eval - underlying_owned) * hist_price)
            underlying_owned = delta_eval
        logging.info("Cash at t=%.2f: %.2f", t, cash_owned)
        logging.info("Mean delta at t=%.2f: %.4f", t, np.mean(delta_eval))
        logging.info("Mean underlying at t=%.2f: %.2f ", t,
                     np.mean(hist_price))

        hist_price_profile.append(hist_price)
        cash_profile.append(cash_owned)
        underlying_profile.append(underlying_owned)

        # Simulation of price movements under the historical probability (i.e. what
        # is actually happening in the stock market).
        hist_price = hist_log_euler_step(hist_price, hist_drift,
                                         hist_vol_matrix, hist_dt)

        t += hist_dt

    session.close()

    # At maturity, the value of the replicating portfolio should be exactly
    # the opposite of the payoff of the option being sold.
    # The reason why the match is not exact here is two-fold: we only hedge once
    # a day and we use noisy Monte Carlo estimates to do so.
    underlying_owned_value = np.sum(underlying_owned * hist_price)
    profit = np.sum(underlying_owned * hist_price) + cash_owned
    loss = (np.mean(hist_price) - strike) * (np.mean(hist_price) > strike)

    logging.info("Cash owned at maturity %.3f.", cash_owned)
    logging.info("Value of underlying owned at maturity %.3f.",
                 underlying_owned_value)
    logging.info("Profit (value held) = %.3f.", profit)
    logging.info("Loss (payoff sold, 0 if price is below strike) = %.3f.",
                 loss)
    logging.info("PnL (should be close to 0) = %.3f.", profit - loss)