import numpy as np from scipy.stats import qmc, norm import plotly.graph_objects as go import streamlit as st # 設置 Streamlit 頁面的基本配置 st.set_page_config(page_title="European option", # 設定網頁標題 layout="wide", # 設定頁面布局為寬屏模式 initial_sidebar_state="expanded") # 初始時側邊欄狀態為展開 st.title("Geometric Brownian motion 模型下,利用模擬方法得到歐式選擇權 (European option) 評價") with st.sidebar.form(key="my_form"): option_type = st.radio("選擇權型態", ("Call", "Put"), horizontal=True) S0 = st.slider("選擇期初股價 S0", min_value=50, max_value=300, value=100) K = st.slider("選擇履約價格 K", min_value=50, max_value=300, value=100) T = st.slider("選擇到期時間 T (年)", min_value=0.1, max_value=2.0, value=1.0, step=0.1) r = st.slider("選擇無風險利率 r", min_value=0.01, max_value=0.10, value=0.03, step=0.01) sigma = st.slider("選擇波動率 σ", min_value=0.1, max_value=0.5, value=0.2, step=0.05) n_simulations = st.slider("選擇模擬次數", min_value=1000, max_value=20000, value=10000, step=1000) n_experiments = st.slider("選擇實驗重複次數", min_value=100, max_value=10000, value=1000, step=100) seed = st.text_input(label="亂數種子", value="123457") submit_button = st.form_submit_button(label="Submit") #%% # 計算歐式選擇權價格並進行折現 def European_option_price(S0, K, r, T, sigma, option_type="Call"): # Black-Scholes 公式 d1 = (np.log(S0/K)+(r+0.5*sigma**2)*T)/(sigma*np.sqrt(T)) d2 = d1-sigma*np.sqrt(T) # 真實的 call option 價格 if option_type == "Call": true_option_price = S0*norm.cdf(d1)-K*np.exp(-r*T)*norm.cdf(d2) # 真實的 put option 價格 if option_type == "Put": true_option_price = K*np.exp(-r*T)*norm.cdf(-d2)-S0*norm.cdf(-d1) return true_option_price def European_option_price_simulation(S0, K, r, T, sigma, z, option_type="Call"): ST = S0*np.exp((r-0.5*sigma**2)*T+sigma*np.sqrt(T)*z) if option_type == "Call": payoff = np.maximum(ST-K, 0) if option_type == "Put": payoff = np.maximum(K-ST, 0) option_price = np.exp(-r*T)*np.mean(payoff) return option_price option_prices_pseudo, option_prices_quasi, option_prices_numpy = [], [], [] if submit_button: bar = st.progress(20, "Start calculation") # 顯示進度條 np.random.seed(int(seed)) sobol_engine = qmc.Sobol(d=1, scramble=True, seed=int(seed)) for _ in range(n_experiments): # 使用 pseudo-random numbers u_pseudo_uniform = np.random.uniform(0, 1, n_simulations) z_pseudo_random = norm.ppf(u_pseudo_uniform) option_price_pseudo = European_option_price_simulation(S0, K, r, T, sigma, z_pseudo_random, option_type=option_type) option_prices_pseudo.append(option_price_pseudo) # 使用 quasi-random numbers u_quasi_random = sobol_engine.random(n=n_simulations).flatten() z_quasi_random = norm.ppf(u_quasi_random) option_price_quasi = European_option_price_simulation(S0, K, r, T, sigma, z_quasi_random, option_type=option_type) option_prices_quasi.append(option_price_quasi) # 使用 Numpy random z_numpy_random = np.random.normal(0, 1, n_simulations) option_price_numpy = European_option_price_simulation(S0, K, r, T, sigma, z_numpy_random, option_type=option_type) option_prices_numpy.append(option_price_numpy) true_option_price = European_option_price(S0, K, r, T, sigma, option_type=option_type) bar = bar.progress(80, "計算中") #%% from plotly.subplots import make_subplots st.subheader(option_type+" option 價格分布比較") # 創建包含三個子圖的圖表 fig = make_subplots(rows=3, cols=1, subplot_titles=("pseudo-random numbers (np.random.uniform)", "quasi-random numbers (qmc.Sobol)", "pseudo-random numbers (np.random.normal)")) nbinsx = 30 fig.add_trace( go.Histogram(x=option_prices_pseudo, nbinsx=nbinsx, histnorm="probability density", name="pseudo-random numbers (np.random.uniform)", marker=dict(color="rgba(0,0,255,0.15)", line=dict(width=1, color="black")), opacity=1.0), row=1, col=1) fig.add_trace( go.Histogram(x=option_prices_quasi, nbinsx=nbinsx, histnorm="probability density", name="quasi-random numbers (qmc.Sobol)", marker=dict(color="rgba(255,0,0,0.15)", line=dict(width=1, color="black")), opacity=1.0), row=2, col=1) fig.add_trace( go.Histogram(x=option_prices_numpy, nbinsx=nbinsx, histnorm="probability density", name="pseudo-random numbers (np.random.normal)", marker=dict(color="rgba(0,255,0,0.15)", line=dict(width=1, color="black")), opacity=1.0), row=3, col=1) fig.add_vline(x=true_option_price, line_width=2, line_dash="dash", line_color="blue") fig.update_layout( title="不同方法計算的 "+option_type+" option 價格分布", height=900) fig.update_xaxes(title=option_type+" option 價格", row=1, col=1) fig.update_xaxes(title=option_type+" option 價格", row=2, col=1) fig.update_xaxes(title=option_type+" option 價格", row=3, col=1) fig.update_yaxes(title="density", row=1, col=1) fig.update_yaxes(title="density", row=2, col=1) fig.update_yaxes(title="density", row=3, col=1) st.plotly_chart(fig) bar = bar.progress(100, "OK") # 打開 "命令提示字元" 或 "終端機",開始執行程式 (或進入 Command Prompt 視窗): # (a) Anaconda => Anaconda Prompt (or Anaconda Powershell Prompt) # (b) WinPython => WinPython Command Prompt.exe # Streamlit run "c:\work\subject\112\simulation\tronclass\(2024.03.21) pseudo-random numbers vs quasi-random numbers-european_option\03-pseudo-random numbers vs quasi-random numbers-european_option-streamlit2.py" # Google Chrome => http://localhost:8501/ # Google Chrome 不關掉,程式修改存檔後,可直接執行 Google Chrome output 畫面右上角的 Return。 # 指定 localhost port => streamlit run app.py --server.port 8502 # 需要終止該應用程式只需在終端中按 Ctrl + C。 # 路徑和檔名不能有中文。 # Mac 不能使用 MacOS 內建的 Safari 瀏覽器,改用 Google Chrome 就沒問題了。