Option Topic 10: Snowball Options (2) (Pricing Application + Main Parameter Analysis)

Table of Contents

1. Pricing application

1.1 Pricing comparison

1.2 Simulated valuation

2. Analysis of main parameters

2.1 Number of simulations

2.2 Remaining term

2.3 Volatility


Disclaimer: This article was independently completed by the author with reference to relevant materials and his own practice and thinking. He does not guarantee the accuracy, completeness or reliability of the full text. At the same time, the data mentioned in the article are only used as examples and do not constitute recommendations; all opinions in the article do not constitute any investment advice. Readers are requested to read this statement carefully. If readers read this article, they are aware of this statement by default.

In Options Topic 9, the pricing ideas and corresponding code implementation of snowball options are shared. This issue mainly shares the application of pricing and the impact of main parameters on snowball options.

1. Pricing Application

1.1 Pricing Comparison

In option topic 9, the pricing model of snowballs in two states is introduced: knock-in has occurred in the past and knock-in has not occurred. Let’s discuss these two modes now. Assume that Snowball has been running for 4 months (84 days), and the corresponding valuations of the two valuation models are made at different prices (the price range is set to 0.73 to 1.05). For convenience, the code of option topic 9 is packaged and placed in the py of Pricing_model (the corresponding folder is named snow_ball).

from snow_ball.Pricing_model import *
import matplotlib.pyplot as plt

# Snowball elements
r = 0.025
vol = 0.22
residual_day = 168
knock_out = 1.03 # Knock out price
knock_in = 0.75 # Knock in price
coupon = 0.2 # Annualized coupon
lock_time = 3 # Lock period, unit is month
T = 1 # term, converted into years
times = 5000

# Obtain the valuation corresponding to different prices of the target under the two valuation modes
St_list = [0.73 + 0.01 * x for x in range(33)]
in_nav_list = []
out_nav_list = []
for St in St_list:
    print(St)
    price_path = get_s_path(St, r, vol, residual_day, times)
    in_nav = get_snowball_nav('in', coupon, T, r,residual_day, knock_in, knock_out, lock_time, price_path)
    out_nav = get_snowball_nav('out', coupon, T, r,residual_day, knock_in, knock_out, lock_time, price_path)
    in_nav_list.append(in_nav)
    out_nav_list.append(out_nav)
# Drawing

plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
plt.plot(St_list, in_nav_list, label='Knock-in occurred')
plt.plot(St_list, out_nav_list, label='No typing has occurred')
plt.title('Snowball valuation model comparison (variable is the underlying price)')
plt.legend()
plt.show()

The corresponding result is:

As can be seen from the above figure: between the knock-in line (75%) and the knock-out line (103%), the pricing valuation in the case of knock-in will be much lower than the valuation in the case of no knock-in. For snowballs that have been knocked in, there is a possibility of profit only when the underlying price is close to the knock-out line, so its valuation is basically close to the underlying price. For snowballs that have never had a knock-in, as the underlying price rises, the probability of a knock-in will become lower and lower, so the valuation will show a non-linear growth. When approaching the knock-out line, the probability of triggering a knock-out will If the value is increased, the probability of obtaining the full coupon will decrease, so its valuation growth will slow down, but the overall expected return will be higher than the corresponding coupon (there is still the possibility of obtaining the full coupon).

Next, assume that the underlying price is 0.9, the snowball runs for 200 days, and the remaining period is used as the independent variable to compare the difference between the two valuations.

As can be seen from the above figure: as the remaining term approaches 0, the pricing valuation in the case of knock-in will be much lower than the valuation in the case of no knock-in. For a snowball that has been knocked in, assuming that the underlying price has been at 0.9, as the expiration date approaches, the probability of being knocked out will become lower and lower, so its valuation is close to 0.9; for a snowball that has not been knocked in, Snowball, assuming that the underlying price has been at 0.9, as the maturity date approaches, there is a high probability that it can obtain the full coupon, so the valuation is close to 1.2.

To sum up, the pricing without knock-in will be higher than the pricing without knock-in. There are two main reasons: 1. After knock-in occurs, it is not possible to obtain the full coupon (no knock-in and knock-out) ;2. After a knock-in occurs, it means that the index has dropped by 25%, and the probability of a knock-out in the future is very low. During the application process, we must first determine the current status of the snowball, and then use different valuation models.

1.2 Simulated Valuation

Here, CSI 500 is used as the target, and the data from January 18, 2018 to January 18, 2019 is selected to demonstrate simulated valuation. It should be noted that since this article adopts a relatively simple valuation process (especially the processing of dates), there will be certain errors in the valuation. This method is more suitable for understanding the valuation logic and doing backtesting.

First, use akshare to obtain the closing price data of CSI 500, normalize it, and generate the corresponding remaining date sequence.

import akshare as ak
import pandas as pd

# Get data and normalize the net value
code = 'sh000905'
st, et = '2018-01-18', '2019-01-18'
data = ak.stock_zh_index_daily_em(symbol=code).sort_values(by=['date'], ascending=True)
index_df = data.loc[(data['date'] >= st) & amp; (data['date'] <= et)]
gy_nav = round(index_df['close'] / index_df['close'].values[0], 3)
new_index = pd.DataFrame({'date': index_df['date'], 'net worth': gy_nav})
# Add remaining period
new_index['remaining period'] = [len(new_index) - x for x in range(len(new_index))]

The corresponding result is:

Next, determine the corresponding status of the valuation node, that is, whether a knock-in occurs. It should be noted that once a knock-in occurs on a node, the subsequent status will be a knock-in; at the same time, it is also necessary to determine whether a knock-in occurs during the entire process. (Judging from the actual data, there is no knock-out situation, and the actual date can be used as a judgment. The corresponding program can be used here to refine the implementation, and no further reproduction will be done here).

# Determine the status of the valuation node
def get_status_list(status_list):
    # Once a knock-in occurs, fill all subsequent states with knock-in
    new_list = status_list.copy()
    end = len(status_list)
    for x in range(0, end):
        num = status_list[x]
        if num == 'out':
            pass
        else:
            a = x
            break
    start = a
    new_list[start:end] = ['in' for y in range(0, end - start)]
    return new_list


# Generate status sequence
status_list = ['out' if x >= 0.75 else 'in' for x in new_index['Net worth']]
# Judge the status sequence and add it to the net worth table
new_index['status'] = get_status_list(status_list)

The corresponding result is:

Considering that the current valuation time interval is only 244 trading days, the parameters of the get_one_nav function are adjusted. The main thing is to adjust the 252 parameter to 244, and adjust the 21 days of each month to 20. The main adjustments here are as follows:

def get_one_nav(status, coupon, T, r, residual_day, one_path, knock_in, knock_out, lock_time):
    '''
    Get the corresponding net value of Snowball’s single simulation path
    :param status: str, the past status of the snowball, 'out' means that no knock-in has occurred in the past, 'in' means that a knock-in has occurred in the past
    :param coupon: int, the annualized coupon corresponding to Snowball, for example, 0.2
    :param T: int, the period corresponding to the snowball (converted into adults), for example, 1
    :param r: int, risk-free interest rate, for example 0.025
    :param residual_day: int, the number of remaining days, for example 252
    :param one_path: matrix, single corresponding simulation path
    :param knock_in: int, the knockout price corresponding to the snowball
    :param knock_out: int, the knock-in price corresponding to the demand
    :return: int, estimated net worth of Snowball
    '''
    year_day = 244 # Number of trading days in a year
    month_day = round(year_day/12,0) # Number of trading days in a month
    
    all_time = T * year_day # period, converted into days

    survival_day = all_time - residual_day # Survival period
    # The serial number corresponding to the trading day when the price is lower than the knock-in price
    knock_in_day = np.where(one_path <= knock_in)
    # The serial number corresponding to the trading day when the price is higher than the knock-out price
    knock_out_day = np.where(one_path >= knock_out)
    # The above serial number of each trading day needs to be added with the snowball’s existing period
    new_out_day = knock_out_day[0] + survival_day
    # Determine whether the above date is an observation day. There are 252 trading days in a year, so one month is set to 12
    obs_may_day = new_out_day[new_out_day % month_day == 0]
    # Due to the lock-in period, the observation days in the first three months need to be eliminated
    obs_day = obs_may_day[obs_may_day /month_day > lock_time]

    # No typing occurred in the past
    if status == 'out':
        # Case 1: Knockout occurred in the future
        if len(obs_day) > 0:
            t = obs_day[0]
            re = coupon * (t / year_day) * np.exp(-r * t / year_day)
            nav = 1 * (1 + re) # 1 represents the net value at the beginning of the period
        else:
            # Case 2: No knock-in or knock-out occurs in the future
            if len(knock_in_day[0]) == 0:
                re = coupon * np.exp(-r * T)
                nav = 1 + re
            # Case 3: No knock-out is issued in the future, but a knock-in occurs
            else:
                # The net value at the end of the period is greater than or equal to 1
                if one_path[-1] >= 1:
                    re = 0
                    nav = 1 * (1 + re)
                # The net value at the end of the period is less than 1
                elif one_path[-1] < 1:
                    re = (one_path[-1] - 1) * np.exp(-r * T)
                    nav = 1 * (1 + re)

    elif status == 'in':
        # Case 1: Knockout occurred in the future
        if len(obs_day) > 0:
            t = obs_day[0]
            re = coupon * (t / year_day) * np.exp(-r * t /year_day)
            nav = 1 * (1 + re) # 1 represents the net value at the beginning of the period
        else:
            # Case 2: No knockout has occurred in the future
            # The net value at the end of the period is greater than or equal to 1
            if one_path[-1] >= 1:
                re = 0
                nav = 1 * (1 + re)
            # The net value at the end of the period is less than 1
            elif one_path[-1] < 1:
                re = (one_path[-1] - 1) * np.exp(-r * T)
                nav = 1 * (1 + re)
    else:
        nav = 0
    new_value = nav
    return new_value

Then, introduce the valuation function introduced in Snowball Valuation (1) (after adjusting the trading day parameters), and obtain the corresponding simulated valuation results through a loop. Here, the corresponding variables are mainly the underlying price, the number of remaining days, and the current corresponding status.

# Calculate simulated valuation
from snow_ball.Pricing_model import *


def get_one_day_nav(status, residual_day, St):
    # Calculate the simulation valuation corresponding to the snowball based on the parameters
    r = 0.025
    vol = 0.22
    times = 5000
    price_path = get_s_path(St, r, vol, residual_day, times)
    # Snowball elements
    knock_out = 1.03 # Knock out price
    knock_in = 0.75 # Knock in price
    coupon = 0.2 # Annualized coupon
    lock_time = 3 # Lock period, unit is month
    T = 1 # term, converted into years
    nav = get_snowball_nav(status, coupon, T, r, residual_day,
                           knock_in, knock_out, lock_time, price_path)
    return nav


nav_list = []
for num in range(0, len(new_index)):
    st = new_index['Net worth'].values[num]
    residual_day = new_index['remaining period'].values[num]
    print(residual_day)
    status = new_index['status'].values[num]
    one_nav = get_one_day_nav(status, residual_day, st)
    nav_list.append(one_nav)

new_index['Simulated valuation'] = nav_list

The corresponding result is:

Visualize the results and get the following results:

import matplotlib.pyplot as plt
date = pd.to_datetime(new_index['date'])
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
plt.plot(date, new_index['Net Value'], label='Underlying Net Value Trend')
plt.plot(date, new_index['Simulated Valuation'], label='Simulated Valuation')
plt.title('Simulation Valuation Case')
plt.legend()
plt.show()

It can be seen from the above figure that when the snowball has not been knocked in, its valuation is much higher than the net value of the underlying. This is because in this case, the possibility of knocking out without knocking in is greater, so the expected return will be higher than The target itself; when a knock-in occurs, the valuations of the two are basically consistent. This is because the probability of another knock-out in the future is extremely low. When the snowball expires, the two converge to the same value.

2. Main parameter analysis

There are many parameters that affect valuation. Here are three main parameters. At the same time, in order to better explain the sensitivity of the parameters, the snowball estimation in the unkeyed state is used in the following articles (the snowball estimation in the keyed-in state is less sensitive to each parameter). At the same time, since the price positions of different targets are affected by parameters differently, the target prices are set to 1 and 0.8 as the control group.

2.1 simulation times

With the number of simulations as the only variable, the underlying prices are set to 1 and 0.8 respectively; the other parameters are fixed.

from snow_ball.Pricing_model import *
import matplotlib.pyplot as plt

# Snowball elements
r = 0.025
vol = 0.22
residual_day = 252
knock_out = 1.03 # Knock out price
knock_in = 0.75 # Knock in price
coupon = 0.2 # Annualized coupon
lock_time = 3 # Lock period, unit is month
T = 1 # term, converted into years
# times = 5000

time_list = [1000 * x for x in range(20)]
nav_list = []
nav_list1 = []

for times in time_list:
    print(times)
    price_path = get_s_path(0.8, r, vol, residual_day, times)
    one_nav = get_snowball_nav('out', coupon, T, r, residual_day, knock_in, knock_out, lock_time, price_path)
    price_path1 = get_s_path(1, r, vol, residual_day, times)
    one_nav1 = get_snowball_nav('out', coupon, T, r, residual_day, knock_in, knock_out, lock_time, price_path1)
    nav_list.append(one_nav)
    nav_list1.append(one_nav1)
# Drawing
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
zt = plt.figure()
p1 = zt.add_subplot(2, 1, 1)
p1.plot(time_list, nav_list, label='subject price 0.8')

plt.legend(loc='best')
plt.title('The impact of the number of simulations on snowball valuation')

p2 = zt.add_subplot(2, 1, 2)
p2.plot(time_list, nav_list1, label='subject price 1.0')

plt.legend(loc='best')
plt.show()

It can be seen from the above results that when the number reaches 5,000 times, the valuation impact is basically a difference in the thousandths. If you are pursuing higher accuracy, you can increase the number to 100,000 times; if you are just doing some understanding calculations or a large amount of backtest data, then 5,000 or 10,000 times is a better choice.

2.2 Remaining Term

With the remaining term as the only variable, the underlying price is set to 1 and 0.8 respectively; the other parameters are fixed.

from snow_ball.Pricing_model import *
import matplotlib.pyplot as plt

# Snowball elements
r = 0.025
vol = 0.22
knock_out = 1.03 # Knock out price
knock_in = 0.75 # Knock in price
coupon = 0.2 # Annualized coupon
lock_time = 3 # Lock period, unit is month
T = 1 # term, converted into years
times = 5000

day_list = [252 - x for x in range(252)]
nav_list = []
nav_list1 = []


for day in day_list:
    print(day)
    price_path = get_s_path(0.8, r, vol, day, times)
    one_nav = get_snowball_nav('out', coupon, T, r, day, knock_in, knock_out, lock_time, price_path)
    price_path1 = get_s_path(1, r, vol, day, times)
    one_nav1 = get_snowball_nav('out', coupon, T, r, day, knock_in, knock_out, lock_time, price_path1)
    nav_list.append(one_nav)
    nav_list1.append(one_nav1)
# Drawing
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
plt.plot(day_list, nav_list, label='subject price 0.8')
plt.plot(day_list, nav_list1, label='subject price 1.0')

plt.title('The impact of remaining term on snowball valuation')
plt.legend()
plt.show()

As can be seen from the above figure, if there is no knock-in, the closer to expiration, the higher the valuation of Snowball will be. At the same time, the valuation will accelerate the process of rising as it approaches expiration (ordinary options still exist) This characteristic), the final valuation of the two converged at 1.2 (both received full coupons). Compared with the snowball with an underlying price of 0.8, the trend of the snowball with an underlying price of 1 will be relatively smoother. This is because there is basically no knock-in at this position, and the probability of knock-out is very high. Therefore, as the As the duration increases, its valuation increases. For the snowball located at 0.8, as the maturity date approaches, its probability of obtaining full coupons is higher than in the previous period, so there is an accelerated upward trend in valuation.

2.3 Volatility

With volatility as the only variable, the underlying price is set to 1 and 0.8 respectively; the other parameters are fixed.

from snow_ball.Pricing_model import *
import matplotlib.pyplot as plt

# Snowball elements
r = 0.025
#vol=0.22
residual_day = 252
knock_out = 1.03 # Knock out price
knock_in = 0.75 # Knock in price
coupon = 0.2 # Annualized coupon
lock_time = 3 # Lock period, unit is month
T = 1 # term, converted into years
times = 5000

vol_list = [0.1 + x*0.01 for x in range(40)]
nav_list = []
nav_list1 = []

for vol in vol_list:
    print(vol)
    price_path = get_s_path(0.8, r, vol, residual_day, times)
    one_nav = get_snowball_nav('out', coupon, T, r, residual_day, knock_in, knock_out, lock_time, price_path)
    price_path1 = get_s_path(1, r, vol, residual_day, times)
    one_nav1 = get_snowball_nav('out', coupon, T, r, residual_day, knock_in, knock_out, lock_time, price_path1)
    nav_list.append(one_nav)
    nav_list1.append(one_nav1)
# Drawing
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
zt = plt.figure()
p1 = zt.add_subplot(2, 1, 1)
p1.plot(vol_list, nav_list, label='subject price 0.8')

plt.legend(loc='best')
plt.title('The impact of volatility on snowball valuation')

p2 = zt.add_subplot(2, 1, 2)
p2.plot(vol_list, nav_list1, label='subject price 1.0')

plt.legend(loc='best')
plt.show()

Volatility is the core element of Snowball, and it also affects the level of coupons. Since Snowball’s investors are more likely to expect small fluctuations in the future target, that is, the volatility will be within a small range; therefore, when the volatility level is higher, the corresponding Snowball’s overall valuation will be lower, because in this state, the full value of the stock will be obtained. The possibility of a coupon is very low, and the corresponding coupon will be higher (can be regarded as compensation). The main methods for predicting volatility levels include GARCH models and stochastic volatility models, but the overall prediction is relatively difficult. The author also did some research on the way to calculate the volatility, but finally found that the difference in volatility under the multi-mode is not big, and it is basically within a range of plus or minus 2% during the same period. Therefore, for the value of volatility, the author tends to choose some simpler methods, such as moving smoothing or average.

This ends this issue. If you have any questions, please feel free to ask.

Disclaimer: This article was independently completed by the author with reference to relevant materials and his own practice and thinking. He does not guarantee the accuracy, completeness or reliability of the full text. At the same time, the data mentioned in the article are only used as examples and do not constitute recommendations; all opinions in the article do not constitute any investment advice. Readers are requested to read this statement carefully. If readers read this article, they are aware of this statement by default.

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. Python entry skill treeHomepageOverview 345,175 people are learning the system