Modeling a Time-Variant Random Variable by Using Drift!¶
[29]:
import pandas_datareader.data as web
from datetime import datetime
import matplotlib.pyplot as plt
import numpy as np
start = datetime(2016, 9, 1)
end = datetime(2021, 8, 31)
ticker = 'AAPL'
stock = web.DataReader(ticker, 'stooq', start, end)
prices = stock.reset_index().sort_values('Date',ascending=True)['Close'].to_numpy()
[30]:
prices
[30]:
array([ 25.027, 25.257, 25.249, ..., 148.6 , 153.12 , 151.83 ])
[31]:
plt.plot(prices)
plt.show()
![_images/particle_drift_3_0.png](_images/particle_drift_3_0.png)
[5]:
logs = np.log(prices)
[6]:
plt.plot(logs)
[6]:
[<matplotlib.lines.Line2D at 0x24398172b50>]
![_images/particle_drift_5_1.png](_images/particle_drift_5_1.png)
[7]:
first_diff = np.diff(logs)
print(first_diff.mean())
print(first_diff.std())
plt.plot(first_diff)
plt.show()
0.001434213390458498
0.01905342412763582
![_images/particle_drift_6_1.png](_images/particle_drift_6_1.png)
[9]:
avg_return = first_diff.mean()
avg_logs = np.array([logs[0]+avg_return*i for i in range(len(logs))])
plt.plot(logs)
plt.plot(avg_logs-.15)
plt.show()
![_images/particle_drift_7_0.png](_images/particle_drift_7_0.png)
[10]:
plt.hist(first_diff, bins=25)
plt.title('Daily Log Returns')
plt.show()
![_images/particle_drift_8_0.png](_images/particle_drift_8_0.png)
[48]:
avg_return
[48]:
0.001434213390458498
[12]:
l = len(prices)
total_return = np.log(prices[-1])-np.log(prices[0])
avg_return = total_return/l
start_exp = np.log(prices[0])
plt.plot(prices)
plt.plot(np.exp(avg_logs-.15))
plt.show()
![_images/particle_drift_10_0.png](_images/particle_drift_10_0.png)
[50]:
1 - np.exp(1) / np.exp(1+first_diff.mean())
[50]:
0.001433185397946235
[51]:
# An addition of .001 to the exponent translates almost exactly to a .1% increase in value
first_diff.mean()
[51]:
0.001434213390458498
So let’s use this to create a stock that oscillates around a mean that grows exponentially. We’ll model this by making the stock price an exponential function with a parameter in the exponent. Every day this parameter grows by .001; however the actual value of the stock is a random variable that we will model similarly to the mean but by bootstrapping.
[34]:
from scipy.stats import norm
# Particle filter
pf_index = np.linspace(2.5, 5.5, 301)
pf = np.full(301, 1./301.) # A completely naive prior
e_values = [(pf_index * pf).sum()]
log_means = [logs[0]]
mean_gain = first_diff.mean()
log_values = [logs[0]]
sigma = first_diff.std()
ps = [norm.pdf(0, loc=0, scale=5*sigma)]
for count in range(1,1000):
accept=False
log_means.append(log_means[-1] + mean_gain)
# Go through MCMC accept/reject loop
while not accept:
new_log_value = log_values[-1] + np.random.choice(first_diff)
new_p = norm.pdf(new_log_value, loc=log_means[-1], scale=5*sigma)
if new_p > ps[-1]:
accept = True
else:
p = new_p / ps[-1]
accept = np.random.choice([True, False], p=[p, 1-p])
log_values.append(new_log_value)
old_p = new_p
# Applying the likelihood function
if count % 1 == 0:
L = []
for i in range(len(pf_index)):
arr = norm.pdf(np.array(log_values[-1:]), loc=pf_index[i], scale=2*sigma)
L.append(arr.prod())
L = np.array(L)
pf = (5 + L) * pf
# Here we add the drift, which for simplicity we call a random variable X
# If you look carefully, you'll see that it's convolution
new_pf = pf.copy()
for i in range(pf_index.shape[0]):
# i is our index
t = pf_index[i]
t_minus_tau = t - pf_index
P_X = norm.pdf(t_minus_tau, loc=0, scale=mean_gain*np.sqrt(20))
new_pf[i] = (pf * P_X).sum()
pf = new_pf / new_pf.sum()
# And then we normalize the weights so they sum to 1
new_e_value = (pf*pf_index).sum()
e_values.append(new_e_value)
if count % 100 == 0:
plt.plot(pf_index, pf)
plt.show()
![_images/particle_drift_14_0.png](_images/particle_drift_14_0.png)
![_images/particle_drift_14_1.png](_images/particle_drift_14_1.png)
![_images/particle_drift_14_2.png](_images/particle_drift_14_2.png)
![_images/particle_drift_14_3.png](_images/particle_drift_14_3.png)
![_images/particle_drift_14_4.png](_images/particle_drift_14_4.png)
![_images/particle_drift_14_5.png](_images/particle_drift_14_5.png)
![_images/particle_drift_14_6.png](_images/particle_drift_14_6.png)
![_images/particle_drift_14_7.png](_images/particle_drift_14_7.png)
![_images/particle_drift_14_8.png](_images/particle_drift_14_8.png)
[36]:
plt.figure(figsize=(16,6))
plt.plot(log_means)
plt.plot(log_values)
plt.plot(range(0,1000),e_values)
plt.plot(range(0,1000),e_values+6*sigma)
plt.plot(range(0,1000),e_values-6*sigma)
plt.legend(['Mean','Log Values','Expected Log Values'])
plt.show()
![_images/particle_drift_15_0.png](_images/particle_drift_15_0.png)
[103]:
plt.figure(figsize=(16,6))
plt.plot(np.exp(log_means))
plt.plot(np.exp(log_values))
plt.plot(range(0,1000,20),np.exp(e_values))
plt.plot(range(0,1000,20),np.exp(e_values+9*sigma))
plt.plot(range(0,1000,20),np.exp(e_values-9*sigma))
[103]:
[<matplotlib.lines.Line2D at 0x20b0eb1edf0>]
![_images/particle_drift_16_1.png](_images/particle_drift_16_1.png)
[23]:
from scipy.stats import norm
pf_index = np.linspace(2.5, 5.5, 301)
pf = np.full(301, 1./301.)
e_values = [(pf_index * pf).sum()]
for count in range(len(logs)):
# Applying the likelihood function
if count % 20 == 0:
L = []
for i in range(len(pf_index)):
arr = norm.pdf(np.array(logs[count-20:count]), loc=pf_index[i], scale=sigma)
L.append(arr.prod())
L = np.array(L)
pf = (5 + L) * pf
# Here we add the drift, which for simplicity we call a random variable X
# If you look carefully, you'll see that it's convolution
new_pf = pf.copy()
for i in range(pf_index.shape[0]):
# i is our index
t = pf_index[i]
X = t - pf_index
P_X = norm.pdf(X, loc=mean_gain, scale=sigma*np.sqrt(20))
new_pf[i] = (pf * P_X).sum()
pf = new_pf / new_pf.sum()
# And then we normalize the weights so they sum to 1
new_e_value = (pf*pf_index).sum()
e_values.append(new_e_value)
if count % 100 == 0:
plt.plot(pf_index, pf)
plt.show()
![_images/particle_drift_17_0.png](_images/particle_drift_17_0.png)
![_images/particle_drift_17_1.png](_images/particle_drift_17_1.png)
![_images/particle_drift_17_2.png](_images/particle_drift_17_2.png)
![_images/particle_drift_17_3.png](_images/particle_drift_17_3.png)
![_images/particle_drift_17_4.png](_images/particle_drift_17_4.png)
![_images/particle_drift_17_5.png](_images/particle_drift_17_5.png)
![_images/particle_drift_17_6.png](_images/particle_drift_17_6.png)
![_images/particle_drift_17_7.png](_images/particle_drift_17_7.png)
![_images/particle_drift_17_8.png](_images/particle_drift_17_8.png)
![_images/particle_drift_17_9.png](_images/particle_drift_17_9.png)
![_images/particle_drift_17_10.png](_images/particle_drift_17_10.png)
![_images/particle_drift_17_11.png](_images/particle_drift_17_11.png)
![_images/particle_drift_17_12.png](_images/particle_drift_17_12.png)
[26]:
plt.figure(figsize=(16,6))
plt.plot(logs)
plt.plot(range(0,len(logs)+20,20), e_values)
plt.plot(range(0,len(logs)+20,20), e_values+6*sigma)
plt.plot(range(0,len(logs)+20,20), e_values-6*sigma)
[26]:
[<matplotlib.lines.Line2D at 0x243980836d0>]
![_images/particle_drift_18_1.png](_images/particle_drift_18_1.png)
[28]:
plt.figure(figsize=(16,6))
plt.plot(prices)
plt.plot(range(0,len(logs)+20,20), np.exp(e_values))
plt.plot(range(0,len(logs)+20,20), np.exp(e_values+6*sigma))
plt.plot(range(0,len(logs)+20,20), np.exp(e_values-6*sigma))
[28]:
[<matplotlib.lines.Line2D at 0x243a019d760>]
![_images/particle_drift_19_1.png](_images/particle_drift_19_1.png)
[ ]: