daily update
This commit is contained in:
26393
to_explore/3d_array_and_volume_plot_v1.1-8BA47.ipynb
Normal file
26393
to_explore/3d_array_and_volume_plot_v1.1-8BA47.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
25282
to_explore/Discretionary_Signal_Backtesting.ipynb
Normal file
25282
to_explore/Discretionary_Signal_Backtesting.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
120688
to_explore/Walk Forward Optimization - Vectorbt/WFO.ipynb
Normal file
120688
to_explore/Walk Forward Optimization - Vectorbt/WFO.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -0,0 +1,404 @@
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import vectorbtpro as vbt
|
||||
import talib
|
||||
from numba import njit
|
||||
from pathlib import Path
|
||||
import scipy
|
||||
import itertools
|
||||
|
||||
FX_MAJOR_LIST = ['EURUSD','AUDNZD','AUDUSD','AUDJPY','EURCHF','EURGBP','EURJPY','GBPCHF','GBPJPY','GBPUSD','NZDUSD','USDCAD','USDCHF','USDJPY','CADJPY','EURAUD','CHFJPY','EURCAD','AUDCAD','AUDCHF','CADCHF','EURNZD','GBPAUD','GBPCAD','GBPNZD','NZDCAD','NZDCHF','NZDJPY']
|
||||
FX_MAJOR_LIST = sorted(FX_MAJOR_LIST)
|
||||
FX_MAJOR_PATH = 'Data/FOREX/oanda/majors_{0}/{1}.csv'
|
||||
|
||||
# Major Currency Pair Loader
|
||||
def get_fx_majors(datapath=FX_MAJOR_PATH, side='bid', start=None, end=None, fillna=True):
|
||||
if side.lower() not in ['bid', 'ask']:
|
||||
raise ValueError('Side *{0}* not recognized. Must be bid or ask'.format(side))
|
||||
print('Loading FOREX {0} major pairs.'.format(side.upper()))
|
||||
if '{0}' in datapath:
|
||||
data = vbt.CSVData.fetch([datapath.format(side.lower(), i) for i in FX_MAJOR_LIST], start=start, end=end)
|
||||
else:
|
||||
data = vbt.CSVData.fetch(['{0}/majors_{1}/{2}.csv'.format(datapath, side, i) for i in FX_MAJOR_LIST], start=start, end=end)
|
||||
|
||||
return data
|
||||
|
||||
# FOREX Position Sizing with ATR
|
||||
def get_fx_position_size(data, init_cash=10_000, risk=0.01, atr=14, sl=1.5):
|
||||
atr = vbt.talib("ATR").run(
|
||||
data.high,
|
||||
data.low,
|
||||
data.close,
|
||||
timeperiod=atr,
|
||||
skipna=True
|
||||
).real.droplevel(0, axis=1)
|
||||
pip_decimal = {i:0.01 if 'JPY' in i else 0.0001 for i in data.close.columns}
|
||||
pip_value = {i:100 if 'JPY' in i else 10_000 for i in data.close.columns}
|
||||
stop_pips = np.ceil(sl*atr/pip_decimal)
|
||||
|
||||
size = ((risk*init_cash/stop_pips)*pip_value)
|
||||
|
||||
return size
|
||||
|
||||
|
||||
# NNFX Dynamic Risk Model
|
||||
@njit
|
||||
def adjust_func_nb(c, atr, sl, tp):
|
||||
position_now = c.last_position[c.col]
|
||||
|
||||
# Check if position is open and needs to be managed
|
||||
if position_now != 0:
|
||||
# Get Current SL & TP Info
|
||||
sl_info = c.last_sl_info[c.col]
|
||||
tp_info = c.last_tp_info[c.col]
|
||||
tp_info.ladder = True
|
||||
tsl_info = c.last_tsl_info[c.col]
|
||||
|
||||
last_order = c.order_records[c.order_counts[c.col] - 1, c.col]
|
||||
|
||||
if last_order.stop_type == -1:
|
||||
# STOP TYPE == -1, User Generate Order
|
||||
# Get Current ATR Value
|
||||
catr = vbt.pf_nb.select_nb(c, atr)
|
||||
|
||||
if not vbt.pf_nb.is_stop_info_active_nb(sl_info):
|
||||
sl_info.stop = catr*vbt.pf_nb.select_nb(c, sl)
|
||||
|
||||
if not vbt.pf_nb.is_stop_info_active_nb(tp_info):
|
||||
tp_info.stop = catr*vbt.pf_nb.select_nb(c, tp)
|
||||
tp_info.exit_size = round(abs(position_now) * 0.5)
|
||||
|
||||
elif last_order.stop_type == vbt.sig_enums.StopType.TP:
|
||||
# STOP TYPE == 3, last fill was a take profit
|
||||
if not vbt.pf_nb.is_stop_info_active_nb(tsl_info):
|
||||
# Set a Trailing Stop for remaining
|
||||
tsl_info.stop = sl_info.stop
|
||||
|
||||
# Deactivate Original Stop
|
||||
sl_info.stop = np.nan
|
||||
|
||||
def get_NNFX_risk(atr_, sl_, tp_):
|
||||
args = {"adjust_func_nb":adjust_func_nb,
|
||||
"adjust_args":(vbt.Rep("atr"), vbt.Rep("sl"), vbt.Rep("tp")),
|
||||
"broadcast_named_args":dict(
|
||||
atr=atr_,
|
||||
sl=sl_,
|
||||
tp=tp_
|
||||
),
|
||||
"use_stops":True}
|
||||
|
||||
return args
|
||||
|
||||
|
||||
# Whites Reality Check for a Single Strategy
|
||||
def col_p_val_WRC(col, means, n, inds):
|
||||
samples = col.values[inds].mean(axis=0)
|
||||
return (samples > means[col.name]).sum()/n
|
||||
|
||||
def get_WRC_p_val(raw_ret, allocations, n=2000):
|
||||
# Detrending & Zero Centering
|
||||
#raw_ret = np.log(data.close/data.open)
|
||||
det_ret = raw_ret - raw_ret.mean(axis=0)
|
||||
det_strat = np.sign(allocations+allocations.shift(1))*det_ret
|
||||
|
||||
# Zero Centering
|
||||
mean_strat = det_strat.mean(axis=0)
|
||||
zero_strat = det_strat - mean_strat
|
||||
|
||||
# Sampling
|
||||
inds = np.random.randint(0, raw_ret.shape[0], size=(raw_ret.shape[0], n))
|
||||
ps = zero_strat.apply(col_p_val_WRC, axis=0, args=(mean_strat, n, inds))
|
||||
|
||||
return ps
|
||||
|
||||
# Monte Carlo Permutation Method (MCP) for Inference Testing
|
||||
def col_p_val_MCP(col, det_ret, means, inds, n):
|
||||
samples = det_ret[col.name[-1]].values[inds]
|
||||
#print(col.values[:, np.newaxis].shape)
|
||||
samples = np.nanmean(samples*col.values[:, np.newaxis], axis=0)
|
||||
return (samples > means[col.name]).sum()/n
|
||||
|
||||
def get_MCP_p_val(raw_ret, allocations, n=2000):
|
||||
# Detrending
|
||||
det_ret = raw_ret - raw_ret.mean(axis=0)
|
||||
allocations = np.sign(allocations + allocations.shift(1))
|
||||
det_strat = allocations*det_ret
|
||||
|
||||
# Zero Centering
|
||||
mean_strat = det_strat.mean(axis=0)
|
||||
|
||||
# Sampling
|
||||
inds = np.tile(np.arange(0, raw_ret.shape[0])[:, np.newaxis], (1, 2000))
|
||||
inds = np.take_along_axis(inds, np.random.randn(*inds.shape).argsort(axis=0), axis=0)
|
||||
ps = allocations.apply(col_p_val_MCP, axis=0, args=(det_ret, mean_strat, inds, n))
|
||||
|
||||
return ps
|
||||
|
||||
def _nonull_df_dict(df, times=True):
|
||||
if times:
|
||||
d = {i:df[i].dropna().to_numpy(dtype=int) for i in df.columns}
|
||||
else:
|
||||
d = {i:df[i].dropna().to_numpy() for i in df.columns}
|
||||
return d
|
||||
|
||||
def col_p_val_MCPH(col, det_ret, means, times, signals, n):
|
||||
# Get Column Specific Holding Times & Signals
|
||||
_times = times[col.name]
|
||||
_signals = signals[col.name]
|
||||
|
||||
# Create Time/Signal Perumutation Arrays
|
||||
index_arr = np.tile(np.arange(0, len(_times))[:, np.newaxis], (1, n))
|
||||
sorter = np.random.randn(*index_arr.shape)
|
||||
index_arr = np.take_along_axis(index_arr, sorter.argsort(axis=0), axis=0)
|
||||
|
||||
# Create Sampling Array
|
||||
_times_perm = _times[index_arr]
|
||||
_signals_perm = _signals[index_arr]
|
||||
_times_flat = _times_perm.flatten('F')
|
||||
_signals_flat = _signals_perm.flatten('F')
|
||||
samples = np.repeat(_signals_flat, _times_flat).reshape((len(col), n), order='F')
|
||||
samples = np.multiply(det_ret[col.name[-1]].values[np.tile(np.arange(0, col.shape[0])[:, np.newaxis], (1, n))], samples)
|
||||
|
||||
samples = np.nanmean(samples, axis=0)
|
||||
|
||||
return (samples > means[col.name]).sum()/n
|
||||
|
||||
def get_MCPH_p_val(raw_ret, allocations, n=2000):
|
||||
# Detrending
|
||||
#raw_ret = np.log(data.close/data.open)
|
||||
det_ret = raw_ret - raw_ret.mean(axis=0)
|
||||
allocations = np.sign(allocations + allocations.shift(1)).fillna(0)
|
||||
det_strat = allocations*det_ret
|
||||
|
||||
# Zero Centering
|
||||
mean_strat = det_strat.mean(axis=0)
|
||||
|
||||
# Strategy Allocation Holding Distribution and Corresponding Signals
|
||||
changes = (allocations == allocations.shift(1))
|
||||
times = changes.cumsum()-changes.cumsum().where(~changes).ffill().fillna(0).astype(int) + 1
|
||||
times = times[times - times.shift(-1, fill_value=1) >= 0]
|
||||
signals = allocations[~times.isnull()]
|
||||
|
||||
# Get Dictionary of Times/Signals
|
||||
times = _nonull_df_dict(times)
|
||||
signals = _nonull_df_dict(signals, times=False)
|
||||
|
||||
# Sampling
|
||||
ps = allocations.apply(col_p_val_MCPH, axis=0, args=(det_ret, mean_strat, times, signals, n))
|
||||
|
||||
return ps
|
||||
|
||||
# Adjusted Returns: Adjusts closes of time series to reflect trade exit prices. Used as input to WRC and MCP statistical tests
|
||||
def get_adjusted_returns(data, pf):
|
||||
# Trade Records
|
||||
records = pf.trades.records_readable[['Column', 'Exit Index', 'Avg Exit Price']]
|
||||
records.Column=records['Column'].apply(lambda x: x[-1])
|
||||
|
||||
close_adj = data.get('Close')
|
||||
for row, value in records.iterrows():
|
||||
close_adj[value['Column']][value['Exit Index']] = value['Avg Exit Price']
|
||||
|
||||
return np.log(close_adj/data.open)
|
||||
|
||||
# Optimized Split
|
||||
def get_optimized_split(tf, frac, n):
|
||||
# Parameter Estimation
|
||||
d = tf/(frac + n*(1 - frac))
|
||||
di = frac*d
|
||||
do = (1-frac)*d
|
||||
|
||||
# Mixed Integer, Linear Optimization
|
||||
c = [-(1/frac - 1), 1]
|
||||
Aeq = [[1, n]]
|
||||
Aub = [[-1, 1],
|
||||
[(1/frac - 1), -1]]
|
||||
beq = [tf]
|
||||
bub = [0, 0]
|
||||
x0_bounds = (di*0.5, di*1.5)
|
||||
x1_bounds = (do*0.5, do*1.5)
|
||||
res = scipy.optimize.linprog(
|
||||
c, A_eq=Aeq, b_eq=beq, A_ub=Aub, b_ub=bub, bounds=(x0_bounds, x1_bounds),
|
||||
integrality=[1, 1],
|
||||
method='highs',
|
||||
options={"disp": True})
|
||||
|
||||
# Solutions
|
||||
di, do = res.x
|
||||
|
||||
# Actual Fraction
|
||||
frac_a = di/(do+di)
|
||||
|
||||
return int(di), int(do), frac_a
|
||||
|
||||
def wfo_split_func(splits, bounds, index, length_IS=20, length_OOS=30):
|
||||
if len(splits) == 0:
|
||||
new_split = (slice(0, length_IS), slice(length_IS, length_OOS+length_IS))
|
||||
else:
|
||||
# Previous split, second set, right bound
|
||||
prev_end = bounds[-1][1][1]
|
||||
|
||||
# Split Calculation
|
||||
new_split = (
|
||||
slice(prev_end-length_IS, prev_end),
|
||||
slice(prev_end, prev_end + length_OOS)
|
||||
)
|
||||
if new_split[-1].stop > len(index):
|
||||
return None
|
||||
return new_split
|
||||
|
||||
def get_wfo_splitter(index, fraction, n):
|
||||
# Generates a splitter based on train/(train+test) fraction and number of folds
|
||||
d_IS, d_OOS, frac = get_optimized_split(len(index), fraction, n)
|
||||
|
||||
# Generate the Splitter
|
||||
splitter = vbt.Splitter.from_split_func(
|
||||
index,
|
||||
wfo_split_func,
|
||||
split_args=(
|
||||
vbt.Rep("splits"),
|
||||
vbt.Rep("bounds"),
|
||||
vbt.Rep("index"),
|
||||
),
|
||||
split_kwargs={
|
||||
'length_IS':d_IS,
|
||||
'length_OOS':d_OOS
|
||||
},
|
||||
set_labels=["IS", "OOS"]
|
||||
)
|
||||
|
||||
return splitter
|
||||
|
||||
# WFO Fold Analysis Splitters
|
||||
def get_wfo_splitters(index, fractions, folds):
|
||||
# Create Combinations of Folds/Fractions
|
||||
combinations = itertools.product(fractions, folds)
|
||||
|
||||
# Generate Splitters
|
||||
splitters = {}
|
||||
splitter_ranges = {}
|
||||
for comb in combinations:
|
||||
splitter = get_wfo_splitter(index, comb[0], comb[1])
|
||||
splitters.update({comb:splitter})
|
||||
splitter_ranges.update({comb:[d_IS, d_OOS, frac]})
|
||||
|
||||
return splitters, splitter_ranges
|
||||
|
||||
# NNFX WFO Trainin Performance Function
|
||||
@vbt.parameterized(merg_func='concat')
|
||||
def strat_perf(data, ind, atr, pos_size, long_signal='long', short_signal='short', metric='sharpe_ratio'):
|
||||
# Simulation
|
||||
pf = vbt.Portfolio.from_signals(
|
||||
data,
|
||||
entries=getattr(ind, long_signal),
|
||||
short_entries=getattr(ind, short_signal),
|
||||
**get_NNFX_risk(atr, 1.5, 1.0),
|
||||
size=pos_size,
|
||||
size_type='amount',
|
||||
init_cash=10_000,
|
||||
delta_format='absolute',
|
||||
price='nextopen',
|
||||
stop_entry_price='fillprice',
|
||||
leverage=np.inf,
|
||||
#fixed_fees=pos_size*data.get('Spread')
|
||||
)
|
||||
result = getattr(pf, metric)
|
||||
return result
|
||||
|
||||
# Walk Forward Optimization Portfolio Simulation
|
||||
def walk_forward_optimization(data, ind, pos_size, atr, splitter, metric='total_return', long_signal='long', short_signal='short', group=True):
|
||||
|
||||
# Calculate Performance on Training Sets
|
||||
train_perf = splitter.apply(
|
||||
strat_perf,
|
||||
vbt.Takeable(data),
|
||||
vbt.Takeable(ind),
|
||||
vbt.Takeable(atr),
|
||||
vbt.Takeable(pos_size),
|
||||
metric=metric,
|
||||
long_signal=long_signal,
|
||||
short_signal=short_signal,
|
||||
_execute_kwargs=dict(
|
||||
show_progress=False,
|
||||
#clear_cache=50,
|
||||
#collect_garbage=50
|
||||
),
|
||||
merge_func='row_stack',
|
||||
set_='IS',
|
||||
execute_kwargs=dict(show_progress=True),
|
||||
jitted=True
|
||||
)
|
||||
|
||||
# Get the Best Parameters
|
||||
exclusions = [i for i in range(len(train_perf.index.names)) if train_perf.index.names[i] not in getattr(ind, long_signal).columns.names]
|
||||
group = train_perf.groupby(['split','symbol'])
|
||||
best = group.idxmax()
|
||||
best[:] = [tuple([i[j] for j in range(len(i)) if j not in exclusions]) for i in best]
|
||||
best = best.droplevel('symbol')
|
||||
|
||||
# Generate the OOS Signals
|
||||
opt_long = []
|
||||
opt_short = []
|
||||
for i in best.index.get_level_values('split').unique():
|
||||
_opt_long = splitter['OOS'].take(getattr(ind, long_signal))[i][best[i]]
|
||||
_opt_short = splitter['OOS'].take(getattr(ind, short_signal))[i][best[i]]
|
||||
|
||||
remove_cols = [i for i in _opt_long.columns.names if i != 'symbol']
|
||||
_opt_long = _opt_long.droplevel(remove_cols, axis=1)
|
||||
_opt_short = _opt_short.droplevel(remove_cols, axis=1)
|
||||
|
||||
opt_long.append(_opt_long)
|
||||
opt_short.append(_opt_short)
|
||||
opt_long = pd.concat(opt_long)
|
||||
opt_short = pd.concat(opt_short)
|
||||
|
||||
# Run the WFO Portfolio
|
||||
group_by = len(opt_long.columns)*[0] if group else None
|
||||
pf = vbt.Portfolio.from_signals(
|
||||
data,
|
||||
entries=opt_long,
|
||||
short_entries=opt_short,
|
||||
**get_NNFX_risk(atr, 1.5, 1.0),
|
||||
size=pos_size,
|
||||
size_type='amount',
|
||||
init_cash=10_000,
|
||||
delta_format='absolute',
|
||||
price='nextopen',
|
||||
stop_entry_price='fillprice',
|
||||
leverage=np.inf,
|
||||
#fixed_fees=pos_size*data.get('Spread'),
|
||||
group_by=group_by
|
||||
)
|
||||
|
||||
return pf
|
||||
|
||||
# WFO Fold Analysis
|
||||
def wfo_fold_analysis(data, ind, pos_size, atr, splitters, metric='total_return', long_signal='long', short_signal='short'):
|
||||
# Create the Results Matrix
|
||||
keys = splitters.keys()
|
||||
fractions = list(set([i[0] for i in keys]))
|
||||
folds = list(set([i[1] for i in keys]))
|
||||
FF, NN = np.meshgrid(fractions, folds)
|
||||
RR = np.zeros_like(FF)
|
||||
|
||||
# Perform the Analysis
|
||||
for key, splitter in splitters.items():
|
||||
# Get the Key Indices
|
||||
idx = np.where((key[0] == FF) & (key[1] == NN))
|
||||
|
||||
# WFO using Splitter
|
||||
print('Performing Walk Forward for train fraction {0} and N = {1}'.format(key[0], key[1]))
|
||||
wfo = walk_forward_optimization(data, ind, pos_size, atr, splitter, metric=metric, long_signal=long_signal, short_signal=short_signal)
|
||||
|
||||
# Correlation
|
||||
rolling_returns = pd.DataFrame(wfo.cumulative_returns)
|
||||
rolling_returns = rolling_returns[rolling_returns != 1.0].dropna()
|
||||
rolling_returns['idx'] = np.arange(0, len(rolling_returns), 1)
|
||||
rolling_returns
|
||||
corr_matrix = rolling_returns.corr()
|
||||
R_sq = corr_matrix.iloc[0, 1]**2
|
||||
|
||||
# Update the Results
|
||||
print(idx[0][0], idx[1][0], R_sq)
|
||||
RR[idx] = R_sq
|
||||
|
||||
return FF, NN, RR
|
||||
@ -0,0 +1,704 @@
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import vectorbtpro as vbt
|
||||
import talib
|
||||
from numba import njit
|
||||
|
||||
|
||||
# TREND FILTER INDICATOR
|
||||
# Apply Method
|
||||
def tf_apply(close, ema_period, shift_period, smooth_period, tick_val):
|
||||
ma = vbt.nb.ma_1d_nb(close/tick_val, ema_period, wtype=2)
|
||||
rocma = ma - vbt.nb.fshift_1d_nb(ma, shift_period)
|
||||
tf = np.abs(vbt.nb.ma_1d_nb(rocma, smooth_period, wtype=2))
|
||||
return tf
|
||||
# Indicator Factor
|
||||
TrendFilter = vbt.IF(
|
||||
class_name='TrendFilter',
|
||||
short_name='tf',
|
||||
input_names=['close'],
|
||||
param_names=['ema_period', 'shift_period', 'smooth_period', 'tick_val'],
|
||||
output_names=['real']
|
||||
).with_apply_func(
|
||||
tf_apply,
|
||||
takes_1d=True,
|
||||
ema_period=200,
|
||||
shift_period=1,
|
||||
smooth_period=5,
|
||||
tick_val=1e-4
|
||||
)
|
||||
|
||||
# Adaptive Laguerre
|
||||
# Helpers
|
||||
@njit
|
||||
def alag_loop_nb(p, l, warmup):
|
||||
f = np.full(p.shape, np.nan)
|
||||
L0 = np.full(p.shape, np.nan)
|
||||
L1 = np.full(p.shape, np.nan)
|
||||
L2 = np.full(p.shape, np.nan)
|
||||
L3 = np.full(p.shape, np.nan)
|
||||
dir_ = np.full(p.shape, 1)
|
||||
d = np.full(l, np.nan)
|
||||
|
||||
for i in range(3, p.shape[0]):
|
||||
if i < warmup:
|
||||
f[i] = p[i]
|
||||
L0[i] = p[i]
|
||||
L1[i] = p[i-1]
|
||||
L2[i] = p[i-2]
|
||||
L3[i] = p[i-2]
|
||||
else:
|
||||
# Get Differences
|
||||
mi = 0
|
||||
mx = 0
|
||||
a = 0
|
||||
for j in range(l):
|
||||
d[j] = p[i-j] - f[i-j-1]
|
||||
mi = d[j] if d[j] < mi else mi
|
||||
mx = d[j] if d[j] > mx else mx
|
||||
# Min-Max Rescale
|
||||
d = (d - mi)/(mx - mi)
|
||||
a = np.nanmedian(d)
|
||||
|
||||
# Calculation
|
||||
L0[i] = a*p[i] + (1-a)*L0[i-1]
|
||||
L1[i] = -(1 - a) * L0[i] + L0[i-1] + (1 - a) * L1[i-1]
|
||||
L2[i] = -(1 - a) * L1[i] + L1[i-1] + (1 - a) * L2[i-1]
|
||||
L3[i] = -(1 - a) * L2[i] + L2[i-1] + (1 - a) * L3[i-1]
|
||||
f[i] = (L0[i] + 2*L1[i] + 2*L2[i] + L3[i])/6
|
||||
if f[i] < f[i-1]:
|
||||
dir_[i] = -1
|
||||
|
||||
return f, dir_
|
||||
|
||||
def alag_apply(high, low, timeperiod):
|
||||
# Hardcoded 3X Timeperiod Bar Warmup
|
||||
warm = 3*timeperiod
|
||||
|
||||
# Average Price
|
||||
av_price = talib.MEDPRICE(high, low)
|
||||
av_price = vbt.nb.ffill_1d_nb(av_price)
|
||||
|
||||
# Filter
|
||||
filter, direction = alag_loop_nb(av_price, timeperiod, warm)
|
||||
|
||||
# Warmup
|
||||
filter[:warm] = np.nan
|
||||
direction[:warm] = 0
|
||||
|
||||
# Trade Indicators
|
||||
conf = direction > 0
|
||||
long = vbt.nb.crossed_above_1d_nb(direction, np.full(high.shape, 0))
|
||||
short = vbt.nb.crossed_below_1d_nb(direction, np.full(high.shape, 0))
|
||||
|
||||
return filter, direction, long, short, conf
|
||||
|
||||
AdaptiveLaguerre = vbt.IF(
|
||||
class_name='AdaptiveLaguerre',
|
||||
short_name='alag',
|
||||
input_names=['high', 'low'],
|
||||
param_names=['timeperiod'],
|
||||
output_names=['filter', 'direction', 'long', 'short', 'conf']
|
||||
).with_apply_func(
|
||||
alag_apply,
|
||||
takes_1d=True,
|
||||
timeperiod=20,
|
||||
)
|
||||
|
||||
class AdaptiveLaguerre(AdaptiveLaguerre):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
l_kwargs=dict(trace_kwargs=dict(line_color='limegreen', connectgaps=False)),
|
||||
s_kwargs=dict(trace_kwargs=dict(line_color='red', connectgaps=False)),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
|
||||
filter = self.select_col_from_obj(self.filter, column)
|
||||
direction = self.select_col_from_obj(self.direction, column)
|
||||
|
||||
# Coloring
|
||||
long = pd.Series(np.nan, index=filter.index)
|
||||
short = pd.Series(np.nan, index=filter.index)
|
||||
ups = np.where(direction > 0)[0]
|
||||
downs = np.where(direction < 0)[0]
|
||||
|
||||
# Back Shifting the Start of Each Sequence (proper coloring)
|
||||
# ups
|
||||
upstarts = ups[1:] - ups[:-1]
|
||||
upstarts = np.insert(upstarts, 0, upstarts[0]) == 1
|
||||
upstarts = ups[np.where(~upstarts & np.roll(upstarts,-1))[0]] - 1
|
||||
ups = np.append(ups, upstarts)
|
||||
|
||||
# downs
|
||||
downstarts = downs[1:] - downs[:-1]
|
||||
downstarts = np.insert(downstarts, 0, downstarts[0]) == 1
|
||||
downstarts = downs[np.where(~downstarts & np.roll(downstarts,-1))[0]] - 1
|
||||
downs = np.append(downs, downstarts)
|
||||
|
||||
# Plot Lines
|
||||
long[ups] = filter.iloc[ups]
|
||||
short[downs] = filter.iloc[downs]
|
||||
|
||||
long.vbt.plot(fig=fig, **l_kwargs)
|
||||
short.vbt.plot(fig=fig, **s_kwargs)
|
||||
|
||||
return fig
|
||||
|
||||
|
||||
# AROON UP & DOWN
|
||||
# Helpers
|
||||
def aroon_apply(h, l, t):
|
||||
|
||||
u = 100*vbt.nb.rolling_argmax_1d_nb(h, t, local=True)/(t-1)
|
||||
d = 100*vbt.nb.rolling_argmin_1d_nb(l, t, local=True)/(t-1)
|
||||
|
||||
u[:t] = np.nan
|
||||
d[:t] = np.nan
|
||||
|
||||
# Trade Indicators
|
||||
conf = u > d
|
||||
long = vbt.nb.crossed_above_1d_nb(u, d)
|
||||
short = vbt.nb.crossed_below_1d_nb(u, d)
|
||||
|
||||
return u, d, long, short, conf
|
||||
|
||||
Aroon = vbt.IF(
|
||||
class_name='Aroon',
|
||||
short_name='aroon',
|
||||
input_names=['high', 'low'],
|
||||
param_names=['timeperiod'],
|
||||
output_names=['up', 'down', 'long', 'short', 'conf']
|
||||
).with_apply_func(
|
||||
aroon_apply,
|
||||
takes_1d=True,
|
||||
timeperiod=20,
|
||||
)
|
||||
|
||||
# Class
|
||||
class Aroon(Aroon):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
l_kwargs=dict(trace_kwargs=dict(line_color='limegreen', connectgaps=False)),
|
||||
s_kwargs=dict(trace_kwargs=dict(line_color='red', connectgaps=False)),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
|
||||
up = self.select_col_from_obj(self.up, column).rename('Aroon(up)')
|
||||
down = self.select_col_from_obj(self.down, column).rename('Aroon(down)')
|
||||
|
||||
up.vbt.plot(fig=fig, **l_kwargs, **layout_kwargs)
|
||||
down.vbt.plot(fig=fig, **s_kwargs, **layout_kwargs)
|
||||
|
||||
return fig
|
||||
|
||||
# Elhers Two Pole Super Smoother
|
||||
# Helpers
|
||||
@njit
|
||||
def tpss_loop(p, l, warmup):
|
||||
f = np.full(p.shape, np.nan)
|
||||
dir_ = np.full(p.shape, 1)
|
||||
|
||||
# Initialize
|
||||
f[:warmup] = p[:warmup]
|
||||
a1 = np.exp(-1.414*3.14159/l)
|
||||
b1 = 2*a1*np.cos(np.deg2rad(1.414*180/l))
|
||||
c2 = b1
|
||||
c3 = -a1*a1
|
||||
c1 = 1 - c2 - c3
|
||||
for i in range(warmup, p.shape[0]):
|
||||
f[i] = c1*p[i] + c2*f[i-1] + c3*f[i-2]
|
||||
if f[i] - f[i-1] < 0:
|
||||
dir_[i] = -1
|
||||
|
||||
return f, dir_
|
||||
|
||||
def tpss_apply(close, timeperiod):
|
||||
# Hardcoded 3X Timeperiod Bar Warmup
|
||||
warm = 3*timeperiod
|
||||
|
||||
# Average Price
|
||||
av_price = (vbt.nb.fshift_1d_nb(close) + close)/2
|
||||
av_price = vbt.nb.ffill_1d_nb(av_price)
|
||||
|
||||
# Filter
|
||||
filter, direction = tpss_loop(av_price, timeperiod, warm)
|
||||
|
||||
# Warmup
|
||||
filter[:warm] = np.nan
|
||||
direction[:warm] = 0
|
||||
|
||||
# Trade Indicators
|
||||
conf = direction > 0
|
||||
long = vbt.nb.crossed_above_1d_nb(direction, np.full(close.shape, 0))
|
||||
short = vbt.nb.crossed_below_1d_nb(direction, np.full(close.shape, 0))
|
||||
|
||||
return filter, direction, long, short, conf
|
||||
|
||||
SuperSmoother = vbt.IF(
|
||||
class_name='SuperSmoother',
|
||||
short_name='tpss',
|
||||
input_names=['close'],
|
||||
param_names=['timeperiod'],
|
||||
output_names=['filter', 'direction', 'long', 'short', 'conf'],
|
||||
attr_settings=dict(
|
||||
filter=dict(dtype=np.float_),
|
||||
direction=dict(dtype=np.float_),
|
||||
long=dict(dtype=np.bool_),
|
||||
short=dict(dtype=np.bool_),
|
||||
conf=dict(dtype=np.bool_),
|
||||
)
|
||||
).with_apply_func(
|
||||
tpss_apply,
|
||||
takes_1d=True,
|
||||
timeperiod=20
|
||||
)
|
||||
|
||||
class SuperSmoother(SuperSmoother):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
l_kwargs=dict(trace_kwargs=dict(line_color='limegreen', connectgaps=False)),
|
||||
s_kwargs=dict(trace_kwargs=dict(line_color='red', connectgaps=False)),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
|
||||
filter = self.select_col_from_obj(self.filter, column)
|
||||
direction = self.select_col_from_obj(self.direction, column)
|
||||
|
||||
# Coloring
|
||||
long = pd.Series(np.nan, index=filter.index)
|
||||
short = pd.Series(np.nan, index=filter.index)
|
||||
ups = np.where(direction > 0)[0]
|
||||
downs = np.where(direction < 0)[0]
|
||||
|
||||
# Back Shifting the Start of Each Sequence (proper coloring)
|
||||
# ups
|
||||
upstarts = ups[1:] - ups[:-1]
|
||||
upstarts = np.insert(upstarts, 0, upstarts[0]) == 1
|
||||
upstarts = ups[np.where(~upstarts & np.roll(upstarts,-1))[0]] - 1
|
||||
ups = np.append(ups, upstarts)
|
||||
|
||||
# downs
|
||||
downstarts = downs[1:] - downs[:-1]
|
||||
downstarts = np.insert(downstarts, 0, downstarts[0]) == 1
|
||||
downstarts = downs[np.where(~downstarts & np.roll(downstarts,-1))[0]] - 1
|
||||
downs = np.append(downs, downstarts)
|
||||
|
||||
# Plot Lines
|
||||
long[ups] = filter.iloc[ups]
|
||||
short[downs] = filter.iloc[downs]
|
||||
|
||||
long.vbt.plot(fig=fig, **l_kwargs)
|
||||
short.vbt.plot(fig=fig, **s_kwargs)
|
||||
|
||||
return fig
|
||||
|
||||
# Kase Permission Stochastic
|
||||
@njit
|
||||
def kase_loop_nb(TripleK, pstX, alpha):
|
||||
TripleDF = np.full(TripleK.shape[0], 0.0)
|
||||
TripleDS = np.full(TripleK.shape[0], 0.0)
|
||||
|
||||
for i in range(pstX+1, TripleK.shape[0]):
|
||||
TripleDF[i] = TripleDF[i-pstX] + alpha*(TripleK[i] - TripleDF[i-pstX])
|
||||
TripleDS[i] = (2.0*TripleDS[i-pstX] + TripleDF[i]) / 3.0
|
||||
return TripleDF, TripleDS
|
||||
|
||||
@njit
|
||||
def kase_smooth_nb(price, length):
|
||||
# Initialization
|
||||
e0 = np.full(price.shape[0], 0.0)
|
||||
e1 = np.full(price.shape[0], 0.0)
|
||||
e2 = np.full(price.shape[0], 0.0)
|
||||
e3 = np.full(price.shape[0], 0.0)
|
||||
e4 = np.full(price.shape[0], 0.0)
|
||||
alpha = 0.45*(length-1.0)/(0.45*(length-1.0)+2.0)
|
||||
|
||||
# Calculation
|
||||
for i in range(price.shape[0]):
|
||||
if i <= 2:
|
||||
e0[i] = price[i]
|
||||
e1[i] = price[i]
|
||||
e2[i] = price[i]
|
||||
e3[i] = price[i]
|
||||
e4[i] = price[i]
|
||||
else:
|
||||
e0[i] = price[i] + alpha * (e0[i-1] - price[i])
|
||||
e1[i] = (price[i] - e0[i]) * (1 - alpha) + alpha * e1[i-1]
|
||||
e2[i] = e0[i] + e1[i]
|
||||
e3[i] = e2[i] - e4[i-1] * (1-alpha)**2 + (alpha**2) * e3[i-1]
|
||||
e4[i] = e3[i] + e4[i-1]
|
||||
|
||||
return e4
|
||||
|
||||
def kase_apply(h, l, c, pstLength, pstX, pstSmooth, smoothPeriod):
|
||||
|
||||
# Variables
|
||||
lookback = pstLength*pstX
|
||||
alpha = 2.0/(1.0 + pstSmooth)
|
||||
|
||||
# Calculations
|
||||
hh = vbt.nb.rolling_max_1d_nb(h, lookback)
|
||||
ll = vbt.nb.rolling_min_1d_nb(l, lookback)
|
||||
# Triple K
|
||||
TripleK = vbt.nb.fillna_1d_nb(100*(c - ll)/(hh-ll), 0.0)
|
||||
TripleK[TripleK < 0] = 0.0
|
||||
|
||||
# Triple DF, DS
|
||||
TripleDF, TripleDS = kase_loop_nb(TripleK, pstX, alpha)
|
||||
|
||||
# SMA of DF and DS
|
||||
TripleDFs = talib.SMA(TripleDF, pstSmooth)
|
||||
TripleDSs = talib.SMA(TripleDS, pstSmooth)
|
||||
|
||||
# Kase Smoothing
|
||||
pst = kase_smooth_nb(TripleDFs, smoothPeriod)
|
||||
pss = kase_smooth_nb(TripleDSs, smoothPeriod)
|
||||
|
||||
# Signals and Confirmation
|
||||
long = vbt.nb.crossed_above_1d_nb(pst, pss)
|
||||
short = vbt.nb.crossed_above_1d_nb(pss, pst)
|
||||
conf = pst > pss
|
||||
|
||||
return pst, pss, long, short, conf
|
||||
|
||||
KasePermissionStochastic = vbt.IF(
|
||||
class_name='KasePermissionStochastic',
|
||||
short_name='KPSS',
|
||||
input_names=['high', 'low', 'close'],
|
||||
param_names=['pstLength', 'pstX', 'pstSmooth', 'smoothPeriod'],
|
||||
output_names=['pst', 'pss', 'long', 'short', 'conf'],
|
||||
attr_settings=dict(
|
||||
pst=dict(dtype=np.float_),
|
||||
pss=dict(dtype=np.float_),
|
||||
long=dict(dtype=np.bool_),
|
||||
short=dict(dtype=np.bool_),
|
||||
conf=dict(dtype=np.bool_),
|
||||
)
|
||||
).with_apply_func(
|
||||
kase_apply,
|
||||
takes_1d=True,
|
||||
pstLength=9,
|
||||
pstX=5,
|
||||
pstSmooth=3,
|
||||
smoothPeriod=10
|
||||
)
|
||||
class KasePermissionStochastic(KasePermissionStochastic):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
pst_kwargs=dict(trace_kwargs=dict(line_color='limegreen', connectgaps=False)),
|
||||
pss_kwargs=dict(trace_kwargs=dict(line_color='red', connectgaps=False)),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
pst_kwargs = pst_kwargs if pst_kwargs else {}
|
||||
pss_kwargs = pss_kwargs if pss_kwargs else {}
|
||||
|
||||
pst = self.select_col_from_obj(self.pst, column).rename('KPSS-pst')
|
||||
pss = self.select_col_from_obj(self.pss, column).rename('KPSS-pss')
|
||||
long = self.select_col_from_obj(self.long, column).rename('Long')
|
||||
short = self.select_col_from_obj(self.short, column).rename('Short')
|
||||
|
||||
pst.vbt.plot(fig=fig, **pst_kwargs, **layout_kwargs)
|
||||
pss.vbt.plot(fig=fig, **pss_kwargs, **layout_kwargs)
|
||||
long.vbt.signals.plot_as_entries(fig=fig, y=pst, **layout_kwargs)
|
||||
short.vbt.signals.plot_as_exits(fig=fig, y=pss, **layout_kwargs)
|
||||
|
||||
return fig
|
||||
|
||||
|
||||
## TREND LORD
|
||||
def trend_lord_apply(c, period):
|
||||
|
||||
# Variables
|
||||
sqrt_period = round(np.sqrt(period))
|
||||
|
||||
# Calculations
|
||||
ma = vbt.nb.ma_1d_nb(c, period, wtype=vbt.enums.WType.Weighted)
|
||||
tl = vbt.nb.ma_1d_nb(ma, sqrt_period, wtype=vbt.enums.WType.Weighted)
|
||||
|
||||
# Signals
|
||||
dir = np.sign(tl - vbt.nb.fshift_1d_nb(tl))
|
||||
long = vbt.nb.crossed_above_1d_nb(dir, np.full(c.shape[0], 0.0))
|
||||
short = vbt.nb.crossed_below_1d_nb(dir, np.full(c.shape[0], 0.0))
|
||||
conf = dir > 0
|
||||
|
||||
return tl, dir, long, short, conf
|
||||
|
||||
TrendLord = vbt.IF(
|
||||
class_name='TrendLord',
|
||||
short_name='tl',
|
||||
input_names=['close'],
|
||||
param_names=['timeperiod'],
|
||||
output_names=['tl', 'direction', 'long', 'short', 'conf']
|
||||
).with_apply_func(
|
||||
trend_lord_apply,
|
||||
takes_1d=True,
|
||||
timeperiod=20
|
||||
)
|
||||
|
||||
class TrendLord(TrendLord):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
l_kwargs=dict(trace_kwargs=dict(marker=dict(color='limegreen'))),
|
||||
s_kwargs=dict(trace_kwargs=dict(marker=dict(color='red'))),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
|
||||
tl = self.select_col_from_obj(self.tl, column)
|
||||
direction = self.select_col_from_obj(self.direction, column)
|
||||
|
||||
# Coloring
|
||||
long = pd.Series(np.nan, index=tl.index)
|
||||
short = pd.Series(np.nan, index=tl.index)
|
||||
ups = np.where(direction > 0)[0]
|
||||
downs = np.where(direction < 0)[0]
|
||||
|
||||
# Plot Lines
|
||||
long[ups] = tl.iloc[ups]
|
||||
short[downs] = tl.iloc[downs]
|
||||
|
||||
long.vbt.barplot(fig=fig, **l_kwargs, **layout_kwargs)
|
||||
short.vbt.barplot(fig=fig, **s_kwargs, **layout_kwargs)
|
||||
|
||||
return fig
|
||||
|
||||
## Cyber Cycle
|
||||
@njit
|
||||
def cyber_cycle_apply(c, a):
|
||||
|
||||
# Variables
|
||||
c = vbt.nb.ffill_1d_nb(c)
|
||||
smooth = np.full(c.shape[0], 0.0)
|
||||
cycle = np.full(c.shape[0], 0.0)
|
||||
trigger = np.full(c.shape[0], 0.0)
|
||||
|
||||
# Calculations
|
||||
for i in range(0, c.shape[0]):
|
||||
if i < 4:
|
||||
cycle[i] = (c[i] - 2*c[i-1] + c[i-2])/4
|
||||
smooth[i] = (c[i] + 2*c[i-1] + 2*c[i-2] + c[i-3])/6
|
||||
trigger[i] = cycle[i-1]
|
||||
else:
|
||||
smooth[i] = (c[i] + 2*c[i-1] + 2*c[i-2] + c[i-3])/6
|
||||
cycle[i] = ((1 - 0.5*a)**2)*(smooth[i] - 2*smooth[i-1] + smooth[i-2]) + 2*(1-a)*cycle[i-1] - ((1-a)**2)*cycle[i-2]
|
||||
trigger[i] = cycle[i-1]
|
||||
|
||||
# Remove Early Convergence Period
|
||||
cycle[:30] = 0.0
|
||||
trigger[:30] = 0.0
|
||||
|
||||
# Signals
|
||||
long = vbt.nb.crossed_above_1d_nb(cycle, trigger)
|
||||
short = vbt.nb.crossed_below_1d_nb(cycle, trigger)
|
||||
conf = cycle > trigger
|
||||
|
||||
return cycle, trigger, long, short, conf
|
||||
|
||||
CyberCycle = vbt.IF(
|
||||
class_name='CyberCycle',
|
||||
short_name='cc',
|
||||
input_names=['close'],
|
||||
param_names=['alpha'],
|
||||
output_names=['cycle', 'trigger', 'long', 'short', 'conf']
|
||||
).with_apply_func(
|
||||
cyber_cycle_apply,
|
||||
takes_1d=True,
|
||||
alpha=0.7
|
||||
)
|
||||
|
||||
class CyberCycle(CyberCycle):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
l_kwargs=dict(trace_kwargs=dict(marker=dict(color='limegreen'))),
|
||||
s_kwargs=dict(trace_kwargs=dict(marker=dict(color='red'))),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
|
||||
cycle = self.select_col_from_obj(self.cycle, column)
|
||||
trigger = self.select_col_from_obj(self.trigger, column)
|
||||
|
||||
cycle.vbt.plot(fig=fig, **l_kwargs, **layout_kwargs)
|
||||
trigger.vbt.plot(fig=fig, **s_kwargs, **layout_kwargs)
|
||||
|
||||
return fig
|
||||
|
||||
## Inverse Fisher of the Cyber Cycle
|
||||
def icycle_apply(c, a, p, s, level=0.5):
|
||||
# Calculate Cyber Cycle
|
||||
cycle, _, _, _, _ = cyber_cycle_apply(c, a)
|
||||
|
||||
# Scaling
|
||||
h = vbt.nb.rolling_max_1d_nb(cycle, p)
|
||||
l = vbt.nb.rolling_min_1d_nb(cycle, p)
|
||||
cycle = (2*5.0)*((cycle-l)/(h-l))-5.0
|
||||
|
||||
# Smoothing
|
||||
cycle = talib.EMA(cycle, s)
|
||||
|
||||
# Inverse Fisher of the Cyber Cycle
|
||||
icc = (np.exp(2*cycle)-1.0)/(np.exp(2*cycle)+1.0)
|
||||
|
||||
# Signals
|
||||
long = vbt.nb.crossed_above_1d_nb(icc, np.full(cycle.shape[0], -1*level))
|
||||
short = vbt.nb.crossed_below_1d_nb(icc, np.full(cycle.shape[0], level))
|
||||
long_conf = icc < -1*level
|
||||
short_conf = icc > level
|
||||
|
||||
return icc, long, short, long_conf, short_conf
|
||||
|
||||
iCyberCycle = vbt.IF(
|
||||
class_name='iCyberCycle',
|
||||
short_name='icc',
|
||||
input_names=['close'],
|
||||
param_names=['alpha', 'period', 'smoothing'],
|
||||
output_names=['icycle', 'long', 'short', 'long_conf', 'short_conf']
|
||||
).with_apply_func(
|
||||
icycle_apply,
|
||||
takes_1d=True,
|
||||
alpha=0.7,
|
||||
period=10,
|
||||
smoothing=9
|
||||
)
|
||||
|
||||
class iCyberCycle(iCyberCycle):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
c_kwargs=dict(trace_kwargs=dict(marker=dict(color='yellow'))),
|
||||
l_kwargs=dict(trace_kwargs=dict(marker=dict(color='limegreen'))),
|
||||
s_kwargs=dict(trace_kwargs=dict(marker=dict(color='red'))),
|
||||
fig=None,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
c_kwargs = c_kwargs if c_kwargs else {}
|
||||
|
||||
icycle = self.select_col_from_obj(self.icycle, column)
|
||||
long_level = pd.DataFrame(np.full(len(icycle), -0.5), index=icycle.index)
|
||||
short_level = pd.DataFrame(np.full(len(icycle), 0.5), index=icycle.index)
|
||||
|
||||
icycle.vbt.plot(fig=fig, **c_kwargs, **layout_kwargs)
|
||||
long_level.vbt.plot(fig=fig, **l_kwargs, **layout_kwargs)
|
||||
short_level.vbt.plot(fig=fig, **s_kwargs, **layout_kwargs)
|
||||
|
||||
|
||||
return fig
|
||||
|
||||
# Trend Trigger Factor
|
||||
def t3_ma_apply(c, l, a=0.7):
|
||||
|
||||
# Variables
|
||||
c1 = -a**3
|
||||
c2 = 3*a**2 + 3*a**3
|
||||
c3 = -6*a**2 - 3*a - 3*a**3
|
||||
c4 = 1 + 3*a + a**3 + 3*a**2
|
||||
|
||||
# Calculation
|
||||
e1 = talib.EMA(c, l)
|
||||
e2 = talib.EMA(e1, l)
|
||||
e3 = talib.EMA(e2, l)
|
||||
e4 = talib.EMA(e3, l)
|
||||
e5 = talib.EMA(e4, l)
|
||||
e6 = talib.EMA(e5, l)
|
||||
T3 = c1*e6 + c2*e5 + c3*e4 + c4*e3
|
||||
|
||||
return T3
|
||||
def ttf_apply(c, l, t3=True, t3_period=3, b=0.7):
|
||||
|
||||
# Fill Nan
|
||||
c = vbt.nb.ffill_1d_nb(c)
|
||||
|
||||
# Variables
|
||||
c_s = vbt.nb.fshift_1d_nb(c, l)
|
||||
|
||||
# Caluclations
|
||||
buyPower = vbt.nb.rolling_max_1d_nb(c, l) - vbt.nb.rolling_min_1d_nb(c_s, l)
|
||||
sellPower = vbt.nb.rolling_max_1d_nb(c_s, l) - vbt.nb.rolling_min_1d_nb(c, l)
|
||||
ttf = 100*((buyPower-sellPower)/(0.5*(buyPower+sellPower)))
|
||||
|
||||
# T3 Smoothing
|
||||
if t3:
|
||||
ttf = t3_ma_apply(ttf, t3_period, a=b)
|
||||
|
||||
# Signnals
|
||||
long = vbt.nb.crossed_above_1d_nb(ttf, np.full(c.shape[0], 0.0))
|
||||
short = vbt.nb.crossed_below_1d_nb(ttf, np.full(c.shape[0], 0.0))
|
||||
conf = ttf > 0
|
||||
|
||||
return ttf, long, short, conf
|
||||
|
||||
TrendTriggerFactor = vbt.IF(
|
||||
class_name='TrendTriggerFactor',
|
||||
short_name='ttf',
|
||||
input_names=['close'],
|
||||
param_names=['timeperiod', 't3', 't3_period', 'b'],
|
||||
output_names=['ttf', 'long', 'short', 'conf']
|
||||
).with_apply_func(
|
||||
ttf_apply,
|
||||
takes_1d=True,
|
||||
timeperiod=15,
|
||||
t3=True,
|
||||
t3_period=3,
|
||||
b=0.7
|
||||
)
|
||||
|
||||
class TrendTriggerFactor(TrendTriggerFactor):
|
||||
from itertools import groupby, accumulate
|
||||
def plot(self,
|
||||
column=None,
|
||||
l_kwargs=dict(trace_kwargs=dict(marker=dict(color='limegreen'))),
|
||||
s_kwargs=dict(trace_kwargs=dict(marker=dict(color='red'))),
|
||||
n_kwargs=dict(trace_kwargs=dict(marker=dict(color='rgba(255, 255, 0, 0.1)'))),
|
||||
fig=None,
|
||||
signal=True,
|
||||
**layout_kwargs):
|
||||
l_kwargs = l_kwargs if l_kwargs else {}
|
||||
s_kwargs = s_kwargs if s_kwargs else {}
|
||||
n_kwargs = n_kwargs if n_kwargs else {}
|
||||
|
||||
ttf = self.select_col_from_obj(self.ttf, column)
|
||||
|
||||
if not signal:
|
||||
ttf.vbt.plot(fig=fig, **n_kwargs, **layout_kwargs)
|
||||
else:
|
||||
long = pd.DataFrame(np.full(len(ttf), 100), index=ttf.index)
|
||||
short = pd.DataFrame(np.full(len(ttf), -100), index=ttf.index)
|
||||
neutral = pd.DataFrame(np.full(len(ttf), np.nan), index=ttf.index)
|
||||
|
||||
long_entry = self.select_col_from_obj(self.long, column).fillna(False)
|
||||
short_entry = self.select_col_from_obj(self.short, column).fillna(False)
|
||||
|
||||
long_idx = long_entry
|
||||
short_idx = short_entry
|
||||
pre_long_idx = long_idx.shift(-1)
|
||||
pre_short_idx = short_idx.shift(-1)
|
||||
|
||||
neutral[long_idx==True] = 100
|
||||
neutral[pre_long_idx==True] = -100
|
||||
neutral[short_idx==True] = -100
|
||||
neutral[pre_short_idx==True] = 100
|
||||
|
||||
long[~self.select_col_from_obj(self.conf, column)] = np.nan
|
||||
short[self.select_col_from_obj(self.conf, column)] = np.nan
|
||||
|
||||
long.vbt.plot(fig=fig, **l_kwargs, **layout_kwargs)
|
||||
short.vbt.plot(fig=fig, **s_kwargs, **layout_kwargs)
|
||||
neutral.vbt.plot(fig=fig, **n_kwargs, **layout_kwargs)
|
||||
|
||||
|
||||
return fig
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
66
to_explore/Walk Forward Optimization - Vectorbt/wfo.html
Normal file
66
to_explore/Walk Forward Optimization - Vectorbt/wfo.html
Normal file
File diff suppressed because one or more lines are too long
2111
to_explore/kama_from_scratch-CB378.ipynb
Normal file
2111
to_explore/kama_from_scratch-CB378.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
855
to_explore/markov_variance_switching_2_leverage.ipynb
Normal file
855
to_explore/markov_variance_switching_2_leverage.ipynb
Normal file
@ -0,0 +1,855 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Markov Variance Switching\n",
|
||||
"\n",
|
||||
"# 2: Leveraged Exchange Traded Funds\n",
|
||||
"\n",
|
||||
"Kim, C., Nelson, C., and Startz, R. (1998). Testing for mean reversion in heteroskedastic data based on Gibbs-sampling-augmented randomization. Journal of Empirical Finance, (5)2, pp.131-154.\n",
|
||||
"\n",
|
||||
"**Author:** shittles\n",
|
||||
"\n",
|
||||
"**Created:** 2024-10-17\n",
|
||||
"\n",
|
||||
"**Modified:** 2024-10-17\n",
|
||||
"\n",
|
||||
"## Sources\n",
|
||||
"- https://www.statsmodels.org/v0.11.1/examples/notebooks/generated/markov_autoregression.html\n",
|
||||
"- https://www.proshares.com/our-etfs/leveraged-and-inverse/upro\n",
|
||||
"- https://www.bogleheads.org/forum/viewtopic.php?t=272007\n",
|
||||
"- https://www.bogleheads.org/forum/viewtopic.php?t=288192\n",
|
||||
"- https://www.reddit.com/r/LETFs/comments/14lubaz/finally_an_accurate_backtesting_model/\n",
|
||||
"- https://www.reddit.com/r/mauerstrassenwetten/comments/sivtas/zahlgrafs_exzellente_abenteuer_teil_4/\n",
|
||||
"- https://code.launchpad.net/zgea\n",
|
||||
"\n",
|
||||
"## Changelog\n",
|
||||
"- Modified Markov variance switching notebook for portfolio optimisation (2h - 2024-10-17).\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import pandas as pd\n",
|
||||
"import plotly.express as px\n",
|
||||
"import plotly.graph_objects as go\n",
|
||||
"import statsmodels.api as sm\n",
|
||||
"\n",
|
||||
"from pykalman import KalmanFilter\n",
|
||||
"from sklearn.compose import make_column_transformer\n",
|
||||
"from sklearn.preprocessing import RobustScaler\n",
|
||||
"\n",
|
||||
"from vectorbtpro import *"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"vbt.settings.set_theme(\"dark\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Ingestion\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Use the fund, not the index, because its going to be part of a portfolio.\n",
|
||||
"data = vbt.YFData.pull(\n",
|
||||
" [\"SPY\", \"UPRO\"], start=\"50 years ago\", end=\"today\", timeframe=\"daily\", tz=\"UTC\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"data.stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.data[\"SPY\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.data[\"UPRO\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.plot(symbol=\"SPY\", yaxis=dict(type=\"log\")).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.plot(symbol=\"UPRO\", yaxis=dict(type=\"log\")).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Cleaning\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.features"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.get_feature(\"Dividends\").any()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.get_feature(\"Stock Splits\").any()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.get_feature(\"Capital Gains\").any()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = data.remove_features([\"Capital Gains\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# A post global financial crisis backtest probably isn't long enough.\n",
|
||||
"data = data.transform(lambda df: df.loc[\"June 25th 2009\" < df.index])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# I don't need to resample the data since it's sourced from the same exchange.\n",
|
||||
"# data = data.resample(\"daily\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Modelling\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.log_returns.vbt.plot().show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Pre-processing\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"column_transformer = make_column_transformer(\n",
|
||||
" (RobustScaler(), [\"SPY\"]),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"sr_log_returns_scaled = pd.Series(\n",
|
||||
" data=column_transformer.fit_transform(pd.DataFrame(data.log_returns[\"SPY\"])).ravel(),\n",
|
||||
" index=data.index,\n",
|
||||
" name=\"SPY\",\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sr_log_returns_scaled.vbt.plot().show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Markov Regression\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"k_regimes_kns = 3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"kns = sm.tsa.MarkovRegression(\n",
|
||||
" sr_log_returns_scaled, k_regimes=k_regimes_kns, trend=\"n\", switching_variance=True\n",
|
||||
")\n",
|
||||
"results_kns = kns.fit()\n",
|
||||
"\n",
|
||||
"results_kns.summary()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"results_kns.filtered_marginal_probabilities # using data until time t (excluding time t+1, ..., T)\n",
|
||||
"# results_kns.smoothed_marginal_probabilities # using data until time T"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_subplots(\n",
|
||||
" rows=k_regimes_kns,\n",
|
||||
" cols=1,\n",
|
||||
" y_title=\"Filtered Marginal Variance Regime Probabilities\",\n",
|
||||
" # y_title=\"Smoothed Marginal Variance Regime Probabilities\",\n",
|
||||
" shared_xaxes=True,\n",
|
||||
" subplot_titles=[\n",
|
||||
" \"Low-variance\",\n",
|
||||
" \"Medium-variance\",\n",
|
||||
" \"High-variance\",\n",
|
||||
" ], # order changes dependent on fit\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"for i in range(k_regimes_kns):\n",
|
||||
" fig = results_kns.filtered_marginal_probabilities[i].vbt.plot(\n",
|
||||
" # fig = results_kns.smoothed_marginal_probabilities[i].vbt.plot(\n",
|
||||
" add_trace_kwargs=dict(row=i + 1, col=1), fig=fig\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"fig.update_layout(showlegend=False)\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def plot_annotated_line(\n",
|
||||
" fig: go.Figure,\n",
|
||||
" x: pd.Series,\n",
|
||||
" y: pd.Series,\n",
|
||||
" classes: pd.Series,\n",
|
||||
" dict_class_colours: dict,\n",
|
||||
" dict_class_labels: dict,\n",
|
||||
") -> go.Figure:\n",
|
||||
" \"\"\"Plot a line chart where each trace is coloured based on its class.\n",
|
||||
"\n",
|
||||
" Yes, plotly really doesn't support this out of the box.\n",
|
||||
"\n",
|
||||
" Args:\n",
|
||||
" fig: Figure.\n",
|
||||
" x: Indices.\n",
|
||||
" y: Close prices.\n",
|
||||
" classes: Regimes.\n",
|
||||
" dict_class_colours: In the format {class: colour}\n",
|
||||
" dict_class_labels: In the format {class: label}\n",
|
||||
"\n",
|
||||
" Returns:\n",
|
||||
" fig: The figure.\n",
|
||||
" \"\"\"\n",
|
||||
" # Plot each segment in its corresponding color.\n",
|
||||
" for i in range(len(x) - 1):\n",
|
||||
" fig.add_trace(\n",
|
||||
" go.Scatter(\n",
|
||||
" x=x[i : i + 2],\n",
|
||||
" y=y[i : i + 2],\n",
|
||||
" mode=\"lines\",\n",
|
||||
" line=dict(color=dict_class_colours[classes[i]], width=2),\n",
|
||||
" showlegend=False, # added manually\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" # Label each regime.\n",
|
||||
" for regime, colour in dict_class_colours.items():\n",
|
||||
" fig.add_trace(\n",
|
||||
" go.Scatter(\n",
|
||||
" x=[None],\n",
|
||||
" y=[None],\n",
|
||||
" mode=\"lines\",\n",
|
||||
" line=dict(color=colour, width=2),\n",
|
||||
" name=dict_class_labels[regime],\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" return fig"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sr_variance_regime_forecasts = results_kns.filtered_marginal_probabilities.idxmax(\n",
|
||||
" axis=1\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"sr_variance_regime_predictions = results_kns.smoothed_marginal_probabilities.idxmax(\n",
|
||||
" axis=1\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sr_variance_regime_forecasts.vbt.plot().show()\n",
|
||||
"# sr_variance_regime_predictions.vbt.plot().show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# order changes dependent on fit\n",
|
||||
"dict_variance_regime_labels = {\n",
|
||||
" 0: \"Low\",\n",
|
||||
" 1: \"Medium\",\n",
|
||||
" 2: \"High\",\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"dict_variance_regime_colours = {\n",
|
||||
" 0: \"green\",\n",
|
||||
" 1: \"orange\",\n",
|
||||
" 2: \"red\",\n",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_figure()\n",
|
||||
"\n",
|
||||
"fig = plot_annotated_line(\n",
|
||||
" fig,\n",
|
||||
" data.index,\n",
|
||||
" np.log(data.data[\"SPY\"][\"Close\"]),\n",
|
||||
" sr_variance_regime_forecasts,\n",
|
||||
" # sr_variance_regime_predictions,\n",
|
||||
" dict_variance_regime_colours,\n",
|
||||
" dict_variance_regime_labels,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"fig.update_layout(\n",
|
||||
" title=\"Filtered Variance Regime Labels\",\n",
|
||||
" # title=\"Smoothed Variance Regime Labels\",\n",
|
||||
" xaxis_title=\"Date\",\n",
|
||||
" yaxis_title=\"Log Close\",\n",
|
||||
" showlegend=True,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Kalman Filter\n",
|
||||
"Experiment with smoothing the regime probabilities.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def kalman_smooth(column: np.array) -> np.array:\n",
|
||||
" \"\"\"Apply a Kalman filter to the column.\n",
|
||||
" \n",
|
||||
" The Kalman filter class cannot handle the NaNs created by aligning symbols\n",
|
||||
" indices, so only apply it to the relevent slice of the array.\n",
|
||||
" \"\"\"\n",
|
||||
" # index = column.index\n",
|
||||
" # column = column.loc[column.first_valid_index() : column.last_valid_index()]\n",
|
||||
"\n",
|
||||
" # Filter out NaNs at the start and end of the column.\n",
|
||||
" valid_mask = ~np.isnan(column)\n",
|
||||
"\n",
|
||||
" if not valid_mask.any():\n",
|
||||
" # If all values are NaN, return an array of NaNs with the same length.\n",
|
||||
" return np.full_like(column, np.nan)\n",
|
||||
"\n",
|
||||
" # Get the index of the first occurrence of the maximum value in the array.\n",
|
||||
" first_valid = valid_mask.argmax()\n",
|
||||
" # Reverse the array to find the index of the last occurence.\n",
|
||||
" last_valid = len(valid_mask) - valid_mask[::-1].argmax()\n",
|
||||
"\n",
|
||||
" column = column[first_valid:last_valid]\n",
|
||||
"\n",
|
||||
" kf = KalmanFilter(initial_state_mean=0, n_dim_obs=1)\n",
|
||||
" kf = kf.em(column, n_iter=5)\n",
|
||||
"\n",
|
||||
" smoothed_state_means, _ = kf.smooth(column)\n",
|
||||
"\n",
|
||||
" # return pd.Series(\n",
|
||||
" # data=smoothed_state_means.ravel(),\n",
|
||||
" # index=column.index,\n",
|
||||
" # name=column.name,\n",
|
||||
" # ).reindex(index)\n",
|
||||
" result = np.full(len(valid_mask), np.nan)\n",
|
||||
" result[first_valid:last_valid] = smoothed_state_means.ravel()\n",
|
||||
"\n",
|
||||
" return result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 29,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"Kalman = vbt.IF(\n",
|
||||
" class_name=\"Kalman\",\n",
|
||||
" short_name=\"kf\",\n",
|
||||
" input_names=[\"column\"],\n",
|
||||
" output_names=[\"smoothed_state_means\"],\n",
|
||||
").with_apply_func(\n",
|
||||
" kalman_smooth,\n",
|
||||
" takes_1d=True,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ssm = Kalman.run(results_kns.filtered_marginal_probabilities)\n",
|
||||
"\n",
|
||||
"ssm.kf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_subplots(\n",
|
||||
" rows=k_regimes_kns,\n",
|
||||
" cols=1,\n",
|
||||
" y_title=\"Kalman Filtered Marginal Variance Regime (Not) Probabilities\",\n",
|
||||
" shared_xaxes=True,\n",
|
||||
" subplot_titles=[\n",
|
||||
" \"Low-variance\",\n",
|
||||
" \"Medium-variance\",\n",
|
||||
" \"High-variance\",\n",
|
||||
" ], # order changes dependent on fit\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"for i in range(k_regimes_kns):\n",
|
||||
" fig = ssm.kf[i].vbt.plot(\n",
|
||||
" add_trace_kwargs=dict(row=i + 1, col=1), fig=fig\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"fig.update_layout(showlegend=False)\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 32,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sr_variance_regime_kalman_forecasts = ssm.kf.idxmax(axis=1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sr_variance_regime_kalman_forecasts.vbt.plot().show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_figure()\n",
|
||||
"\n",
|
||||
"fig = plot_annotated_line(\n",
|
||||
" fig,\n",
|
||||
" data.index,\n",
|
||||
" np.log(data.data[\"SPY\"][\"Close\"]),\n",
|
||||
" sr_variance_regime_kalman_forecasts,\n",
|
||||
" dict_variance_regime_colours,\n",
|
||||
" dict_variance_regime_labels,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"fig.update_layout(\n",
|
||||
" title=\"Kalman Filtered Variance Regime Labels\",\n",
|
||||
" xaxis_title=\"Date\",\n",
|
||||
" yaxis_title=\"Log Close\",\n",
|
||||
" showlegend=True,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Backtest\n",
|
||||
"\n",
|
||||
"### 100% SPX Allocation\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf = vbt.Portfolio.from_holding(close=data.data[\"SPY\"][\"Close\"])\n",
|
||||
"\n",
|
||||
"pf.stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.plot(yaxis=dict(type=\"log\")).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_figure()\n",
|
||||
"\n",
|
||||
"pf.drawdowns.plot(yaxis=dict(type=\"log\"), fig=fig)\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_figure()\n",
|
||||
"\n",
|
||||
"pf.plot_underwater(pct_scale=True, fig=fig)\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 100% UPRO Allocation\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf = vbt.Portfolio.from_holding(\n",
|
||||
" close=data.data[\"UPRO\"][\"Close\"], bm_close=data.data[\"SPY\"][\"Close\"]\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"pf.stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.plot(yaxis=dict(type=\"log\")).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = vbt.make_figure()\n",
|
||||
"\n",
|
||||
"pf.drawdowns.plot(yaxis=dict(type=\"log\"), fig=fig)\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Nice!\n",
|
||||
"fig = vbt.make_figure()\n",
|
||||
"\n",
|
||||
"pf.plot_underwater(pct_scale=True, fig=fig)\n",
|
||||
"\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Filtered Marginal Probability Allocation\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ws_points = data.wrapper.get_index_points(every=\"W\")\n",
|
||||
"\n",
|
||||
"ws_points"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ws_timestamps = data.wrapper.index[ws_points]\n",
|
||||
"\n",
|
||||
"ws_timestamps"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"symbol_wrapper = data.get_symbol_wrapper(freq=\"1D\") \n",
|
||||
"\n",
|
||||
"allocations = symbol_wrapper.fill()\n",
|
||||
"\n",
|
||||
"allocations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"allocations[\"UPRO\"] = results_kns.filtered_marginal_probabilities[0]\n",
|
||||
"allocations[\"SPY\"] = 1 - allocations[\"UPRO\"]\n",
|
||||
"\n",
|
||||
"allocations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf = vbt.Portfolio.from_orders(\n",
|
||||
" close=data.get(\"Close\"),\n",
|
||||
" bm_close=data.data[\"SPY\"][\"Close\"],\n",
|
||||
" size=allocations,\n",
|
||||
" size_type=\"targetpercent\",\n",
|
||||
" group_by=True, \n",
|
||||
" cash_sharing=True,\n",
|
||||
" call_seq=\"auto\" \n",
|
||||
")\n",
|
||||
"\n",
|
||||
"pf.stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# sim_alloc = pf.get_asset_value(group_by=False).vbt / pf.value\n",
|
||||
"\n",
|
||||
"# sim_alloc.vbt.plot(\n",
|
||||
"# trace_kwargs=dict(stackgroup=\"one\"),\n",
|
||||
"# use_gl=False\n",
|
||||
"# ).show()\n",
|
||||
"\n",
|
||||
"pf.plot_allocations().show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.plot(yaxis=dict(type=\"log\")).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.drawdowns.plot(yaxis=dict(type=\"log\")).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.plot_underwater(pct_scale=True).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.0"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
290
to_explore/pyquantnews/100_3DayPullbacks.ipynb
Normal file
290
to_explore/pyquantnews/100_3DayPullbacks.ipynb
Normal file
@ -0,0 +1,290 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3d7e090f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "71212d49",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code analyzes the SPY ticker data from Yahoo Finance to identify and study 3-day losing streaks. It calculates daily returns, identifies losing streaks, and tracks the days since the last streak. The code further computes future returns following these streaks to evaluate market behavior. This is useful for understanding market patterns and predicting future performance based on historical data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3e33fd4d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"import yfinance as yf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "207fb52c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define the ticker symbol and download historical data from Yahoo Finance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f6c02468",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ticker = \"SPY\"\n",
|
||||
"data = yf.download(ticker)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2564ee80",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate daily returns by finding the difference between consecutive closing prices"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "bab6fda1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data[\"return\"] = data[\"Close\"].diff()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b9b2fb9f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Identify days with negative returns to find losing days in the dataset"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "fe5059b6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data[\"down\"] = data[\"return\"] < 0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0dcd7b43",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Identify 3-day losing streaks by checking for three consecutive losing days"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e0dc811b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data[\"3_day_losing_streak\"] = (\n",
|
||||
" data[\"down\"] & data[\"down\"].shift(1) & data[\"down\"].shift(2)\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f20e4853",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Initialize a column to track the number of days since the last 3-day losing streak"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "766171ef",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data[\"days_since_last_streak\"] = np.nan"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3c417464",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Iterate over the data to calculate the days since the last 3-day losing streak"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "49416f01",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"last_streak_day = -np.inf # Initialize with a very large negative value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2f6d58c2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for i in range(len(data)):\n",
|
||||
" if data[\"3_day_losing_streak\"].iloc[i]:\n",
|
||||
" if i - last_streak_day >= 42: # Check if it's been at least 42 trading days\n",
|
||||
" data.loc[data.index[i], \"days_since_last_streak\"] = i - last_streak_day\n",
|
||||
" last_streak_day = i"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b11167c0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Filter the data to show only the occurrences that meet the criteria"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8910af75",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"result = data.dropna(subset=[\"days_since_last_streak\"]).copy()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "870198e1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate future returns following the identified streaks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0fcde2dc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"result[\"next_1_day_return\"] = data[\"Close\"].shift(-1) / data[\"Close\"] - 1\n",
|
||||
"result[\"next_5_day_return\"] = data[\"Close\"].shift(-5) / data[\"Close\"] - 1\n",
|
||||
"result[\"next_10_day_return\"] = data[\"Close\"].shift(-10) / data[\"Close\"] - 1\n",
|
||||
"result[\"next_21_day_return\"] = data[\"Close\"].shift(-21) / data[\"Close\"] - 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "10250edf",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Print the mean future returns for different time horizons"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "62d635bc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"cols = [\n",
|
||||
" \"next_1_day_return\",\n",
|
||||
" \"next_5_day_return\",\n",
|
||||
" \"next_10_day_return\",\n",
|
||||
" \"next_21_day_return\"\n",
|
||||
"]\n",
|
||||
"print(result[cols].mean())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "374238ed",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the proportion of positive returns for the different time horizons"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9cc1f42b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"result[cols].gt(0).mean().plot.bar()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7b4b3cbe",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Display the proportion of positive returns for the different time horizons"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5538d15f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"result[cols].gt(0).mean()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b190dc9f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
356
to_explore/pyquantnews/102_DeltaHedging.ipynb
Normal file
356
to_explore/pyquantnews/102_DeltaHedging.ipynb
Normal file
@ -0,0 +1,356 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "719d0221",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d1a07cd6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code prices a European call option using QuantLib. It sets up market conditions including spot price, volatility, dividend rate, and risk-free rate. The code then calculates the option price and delta using the Black-Scholes-Merton model. It also demonstrates delta hedging by adjusting the stock position in response to changes in the spot price. This is useful for option pricing and risk management in financial markets."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "d439df1a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import QuantLib as ql"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6cd4da78",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Set the evaluation date for the pricing model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "25131917",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"today = ql.Date(15, 1, 2023)\n",
|
||||
"ql.Settings.instance().evaluationDate = today"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bf75bb46",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define the option parameters including expiry date, strike price, and type"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "a50af380",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"expiry = ql.Date(15, 7, 2023)\n",
|
||||
"strike_price = 100\n",
|
||||
"option_type = ql.Option.Call"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d1ac359b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the payoff and exercise objects for the European option"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "783d1bff",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"payoff = ql.PlainVanillaPayoff(option_type, strike_price)\n",
|
||||
"exercise = ql.EuropeanExercise(expiry)\n",
|
||||
"european_option = ql.VanillaOption(payoff, exercise)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "61ee097c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define market parameters including spot price, volatility, dividend rate, and risk-free rate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "db0a54a6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"spot_price = 100\n",
|
||||
"volatility = 0.2 # 20%\n",
|
||||
"dividend_rate = 0.01 # 1%\n",
|
||||
"risk_free_rate = 0.05 # 5%"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "436e4502",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create handles for market conditions such as spot price, volatility, dividend yield, and risk-free rate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "e283fafc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))\n",
|
||||
"volatility_handle = ql.BlackVolTermStructureHandle(\n",
|
||||
" ql.BlackConstantVol(today, ql.NullCalendar(), ql.QuoteHandle(ql.SimpleQuote(volatility)), ql.Actual365Fixed())\n",
|
||||
")\n",
|
||||
"dividend_handle = ql.YieldTermStructureHandle(\n",
|
||||
" ql.FlatForward(today, ql.QuoteHandle(ql.SimpleQuote(dividend_rate)), ql.Actual365Fixed())\n",
|
||||
")\n",
|
||||
"risk_free_handle = ql.YieldTermStructureHandle(\n",
|
||||
" ql.FlatForward(today, ql.QuoteHandle(ql.SimpleQuote(risk_free_rate)), ql.Actual365Fixed())\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8604c96c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the Black-Scholes-Merton process using the defined market conditions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "a4d0ca38",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"bsm_process = ql.BlackScholesMertonProcess(spot_handle, dividend_handle, risk_free_handle, volatility_handle)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f4d1e624",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Price the option using the analytic European engine and calculate its Net Present Value (NPV)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "f619b803",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Option Price: 6.56\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"european_option.setPricingEngine(ql.AnalyticEuropeanEngine(bsm_process))\n",
|
||||
"option_price = european_option.NPV()\n",
|
||||
"print(f\"Option Price: {option_price:.2f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c61ab297",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the option delta, which measures sensitivity to changes in the spot price"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "0b38ea69",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Option Delta: 0.58\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"delta = european_option.delta()\n",
|
||||
"print(f\"Option Delta: {delta:.2f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7a406a89",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Compute the initial stock position required for delta hedging"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "ce3c6064",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Initial Stock Position: 58.08\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"stock_position = delta * spot_price\n",
|
||||
"print(f\"Initial Stock Position: {stock_position:.2f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0d5db52f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Simulate a change in the spot price and update the spot handle"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "ff0ec270",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"new_spot_price = 105\n",
|
||||
"spot_handle = ql.QuoteHandle(ql.SimpleQuote(new_spot_price))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2ef23741",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Recalculate the option price and delta with the new spot price"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "5ab13208",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"New Option Price: 6.56\n",
|
||||
"New Option Delta: 0.58\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"new_option_price = european_option.NPV()\n",
|
||||
"new_delta = european_option.delta()\n",
|
||||
"print(f\"New Option Price: {new_option_price:.2f}\")\n",
|
||||
"print(f\"New Option Delta: {new_delta:.2f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f15a5f31",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Adjust the stock position for delta hedging based on the new delta"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "976bac17",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Adjustment in Stock Position: 2.90\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"new_stock_position = new_delta * new_spot_price\n",
|
||||
"hedge_adjustment = new_stock_position - stock_position\n",
|
||||
"print(f\"Adjustment in Stock Position: {hedge_adjustment:.2f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "19006af5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "357aedc8-fef7-42d9-a5ac-0f973488a6ec",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
189
to_explore/pyquantnews/103_PoissonJumps.ipynb
Normal file
189
to_explore/pyquantnews/103_PoissonJumps.ipynb
Normal file
File diff suppressed because one or more lines are too long
308
to_explore/pyquantnews/104_TrackingError.ipynb
Normal file
308
to_explore/pyquantnews/104_TrackingError.ipynb
Normal file
File diff suppressed because one or more lines are too long
188
to_explore/pyquantnews/105_CalmarRatio.ipynb
Normal file
188
to_explore/pyquantnews/105_CalmarRatio.ipynb
Normal file
File diff suppressed because one or more lines are too long
294
to_explore/pyquantnews/106_ThetaDecay.ipynb
Normal file
294
to_explore/pyquantnews/106_ThetaDecay.ipynb
Normal file
@ -0,0 +1,294 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c1d5e21b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ed31c33c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code calculates and visualizes the theta of a European call option over time until expiration. It sets up the necessary financial instruments using QuantLib, including the option, interest rate curve, dividend yield curve, and volatility surface. The theta is calculated for each day leading up to expiration and stored in a list. Finally, the theta values are plotted against the days to expiration, providing insight into the option's time decay."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2ce21918",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import QuantLib as ql\n",
|
||||
"import matplotlib.pyplot as plt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "687a3623",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define option parameters"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4fbd7243",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"expiry_date = ql.Date(15, 6, 2023)\n",
|
||||
"strike_price = 100\n",
|
||||
"spot_price = 105\n",
|
||||
"volatility = 0.2\n",
|
||||
"risk_free_rate = 0.01\n",
|
||||
"dividend_yield = 0.02"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "49f2c17d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Set up the QuantLib calendar and day count convention"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "59c625a5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"calendar = ql.UnitedStates(ql.UnitedStates.NYSE)\n",
|
||||
"day_count = ql.Actual365Fixed()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "462a8820",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the QuantLib objects for the option"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "402532e0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"exercise = ql.EuropeanExercise(expiry_date)\n",
|
||||
"payoff = ql.PlainVanillaPayoff(ql.Option.Call, strike_price)\n",
|
||||
"option = ql.VanillaOption(payoff, exercise)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2af0a464",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the interest rate curve"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e1e92d44",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"risk_free_rate_handle = ql.YieldTermStructureHandle(\n",
|
||||
" ql.FlatForward(0, calendar, ql.QuoteHandle(ql.SimpleQuote(risk_free_rate)), day_count)\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b90496fb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the dividend yield curve"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "60284e6e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"dividend_yield_handle = ql.YieldTermStructureHandle(\n",
|
||||
" ql.FlatForward(0, calendar, ql.QuoteHandle(ql.SimpleQuote(dividend_yield)), day_count)\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5ed9af74",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the volatility surface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4f0b4be8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"volatility_handle = ql.BlackVolTermStructureHandle(\n",
|
||||
" ql.BlackConstantVol(0, calendar, ql.QuoteHandle(ql.SimpleQuote(volatility)), day_count)\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c976b34a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create the Black-Scholes process"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2a6dd5ce",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))\n",
|
||||
"bs_process = ql.BlackScholesMertonProcess(\n",
|
||||
" spot_handle, \n",
|
||||
" dividend_yield_handle, \n",
|
||||
" risk_free_rate_handle, \n",
|
||||
" volatility_handle\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a97ac96c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define the range of days to expiration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "813206a1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"days_to_expiry = range(365, 15, -1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a0944d10",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"theta_values = []"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fab6d8f2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate theta for each day"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "12acf9e4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for days in days_to_expiry:\n",
|
||||
" expiry_date = calendar.advance(ql.Date().todaysDate(), ql.Period(int(days), ql.Days))\n",
|
||||
" exercise = ql.EuropeanExercise(expiry_date)\n",
|
||||
" option = ql.VanillaOption(payoff, exercise)\n",
|
||||
" \n",
|
||||
" # Set up the pricing engine\n",
|
||||
" engine = ql.AnalyticEuropeanEngine(bs_process)\n",
|
||||
" option.setPricingEngine(engine)\n",
|
||||
" \n",
|
||||
" # Calculate theta\n",
|
||||
" theta = option.theta() / 365\n",
|
||||
" theta_values.append(theta)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b61c5992",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the theta values"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "edd4d555",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.figure(figsize=(10, 6))\n",
|
||||
"plt.plot(days_to_expiry, theta_values, label='Theta')\n",
|
||||
"plt.xlabel('Days to Expiration')\n",
|
||||
"plt.ylabel('Theta')\n",
|
||||
"plt.title('Option Theta over Time to Expiration')\n",
|
||||
"plt.gca().invert_xaxis()\n",
|
||||
"ticks = range(365, 15, -50)\n",
|
||||
"plt.xticks(ticks)\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ec3156e1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
267
to_explore/pyquantnews/107_RiskOfRuin.ipynb
Normal file
267
to_explore/pyquantnews/107_RiskOfRuin.ipynb
Normal file
@ -0,0 +1,267 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ec44d369",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8e794167",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code simulates a trading strategy to assess the risk of ruin. It defines functions to simulate individual trades, an entire trading strategy, and calculate the risk of ruin over multiple simulations. The parameters include initial capital, win probability, average win/loss amounts, and the number of trades. The output is a plot showing how the probability of a winning trade affects the risk of ruin. This can help traders understand the robustness of their trading strategy."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "23e1fe3a",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import matplotlib.pyplot as plt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8a7ec979",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Simulate a single trade with given win probability and average win/loss amounts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "23261ffb",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def simulate_trade(win_prob, avg_win, avg_loss):\n",
|
||||
" \"\"\"\n",
|
||||
" Simulate a single trade with given win probability and average win/loss amounts.\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" win_prob : float\n",
|
||||
" Probability of a winning trade\n",
|
||||
" avg_win : float\n",
|
||||
" Average amount won per winning trade\n",
|
||||
" avg_loss : float\n",
|
||||
" Average amount lost per losing trade\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" float\n",
|
||||
" The result of the trade, positive for win and negative for loss\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Determine the trade outcome based on win probability and return the result\n",
|
||||
" if np.random.rand() < win_prob:\n",
|
||||
" return avg_win\n",
|
||||
" else:\n",
|
||||
" return -avg_loss"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f6a99690",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Simulate the entire trading strategy over a given number of trades"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8bfa85ae",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def simulate_trading_strategy(initial_capital, trades, win_prob, avg_win, avg_loss):\n",
|
||||
" \"\"\"\n",
|
||||
" Simulate the entire trading strategy over a given number of trades.\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" initial_capital : float\n",
|
||||
" Starting capital for the trading strategy\n",
|
||||
" trades : int\n",
|
||||
" Number of trades to simulate\n",
|
||||
" win_prob : float\n",
|
||||
" Probability of a winning trade\n",
|
||||
" avg_win : float\n",
|
||||
" Average amount won per winning trade\n",
|
||||
" avg_loss : float\n",
|
||||
" Average amount lost per losing trade\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" list\n",
|
||||
" Capital history as a list of capital values after each trade\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Initialize capital and history list\n",
|
||||
" capital = initial_capital\n",
|
||||
" capital_history = [capital]\n",
|
||||
"\n",
|
||||
" # Simulate each trade and update capital\n",
|
||||
" for _ in range(trades):\n",
|
||||
" capital += simulate_trade(win_prob, avg_win, avg_loss)\n",
|
||||
" capital_history.append(capital)\n",
|
||||
"\n",
|
||||
" return capital_history"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "79c3ffec",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the risk of ruin over a number of trading simulations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "11a634e0",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def calculate_risk_of_ruin(initial_capital, trades, win_prob, avg_win, avg_loss, simulations=100):\n",
|
||||
" \"\"\"\n",
|
||||
" Calculate the risk of ruin over a number of trading simulations.\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" initial_capital : float\n",
|
||||
" Starting capital for the trading strategy\n",
|
||||
" trades : int\n",
|
||||
" Number of trades to simulate\n",
|
||||
" win_prob : float\n",
|
||||
" Probability of a winning trade\n",
|
||||
" avg_win : float\n",
|
||||
" Average amount won per winning trade\n",
|
||||
" avg_loss : float\n",
|
||||
" Average amount lost per losing trade\n",
|
||||
" simulations : int, optional\n",
|
||||
" Number of simulations to run (default is 100)\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" float\n",
|
||||
" Proportion of simulations where the capital went to zero or below\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Initialize ruin count\n",
|
||||
" ruin_count = 0\n",
|
||||
"\n",
|
||||
" # Run the specified number of simulations\n",
|
||||
" for _ in range(simulations):\n",
|
||||
" capital_history = simulate_trading_strategy(initial_capital, trades, win_prob, avg_win, avg_loss)\n",
|
||||
" if min(capital_history) <= 0:\n",
|
||||
" ruin_count += 1\n",
|
||||
"\n",
|
||||
" return ruin_count / simulations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "38b50779",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define initial parameters for the trading simulation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6b0a2e66",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"initial_capital = 10000\n",
|
||||
"average_win = 110\n",
|
||||
"average_loss = 100\n",
|
||||
"trades = 1000"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "93284971",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate risk of ruin for varying win probabilities"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b492dda9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"risk_of_ruins = []\n",
|
||||
"steps = range(30, 60)\n",
|
||||
"for step in steps:\n",
|
||||
" win_probability = step / 100\n",
|
||||
" risk_of_ruin = calculate_risk_of_ruin(initial_capital, trades, win_probability, average_win, average_loss)\n",
|
||||
" risk_of_ruins.append(risk_of_ruin)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d5657ff2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the risk of ruin versus probability of a winning trade"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a649045d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.figure(figsize=(10, 6))\n",
|
||||
"plt.plot(steps, risk_of_ruins, label='Risk of ruin')\n",
|
||||
"plt.xlabel('Probability of a winning trade')\n",
|
||||
"plt.ylabel('Risk of ruin')\n",
|
||||
"plt.title(\"Probability of ruin versus probability of a winning trade\")\n",
|
||||
"plt.grid(True)\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b6ef45a0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
163
to_explore/pyquantnews/109_Expectancy.ipynb
Normal file
163
to_explore/pyquantnews/109_Expectancy.ipynb
Normal file
@ -0,0 +1,163 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2b725ab1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c686b10c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code calculates the expectancy ratio of a series of trades. The expectancy ratio measures the average expected return per trade by considering the win rate, loss rate, and average profit/loss of trades. It is useful in financial trading to evaluate the performance of a trading strategy. The input is a DataFrame of trades with profit or loss values. The output is a single expectancy ratio value."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "652ddb60",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f54264f9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a function to calculate the expectancy ratio of trades."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d056583b",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def calculate_expectancy_ratio(trades):\n",
|
||||
" \"\"\"Calculate the expectancy ratio of trades.\n",
|
||||
" \n",
|
||||
" This function computes the average expected return for a series of trades \n",
|
||||
" by considering their win rate, loss rate, and average profit/loss.\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" trades : pd.DataFrame\n",
|
||||
" DataFrame containing trade information with a 'Profit' column.\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" expectancy_ratio : float\n",
|
||||
" The calculated expectancy ratio.\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" # Calculate the number of trades\n",
|
||||
" num_trades = len(trades)\n",
|
||||
" \n",
|
||||
" # Separate winning and losing trades\n",
|
||||
" winners = trades[trades['Profit'] > 0]\n",
|
||||
" losers = trades[trades['Profit'] <= 0]\n",
|
||||
" \n",
|
||||
" # Calculate win rate and loss rate\n",
|
||||
" win_rate = len(winners) / num_trades\n",
|
||||
" loss_rate = len(losers) / num_trades\n",
|
||||
" \n",
|
||||
" # Calculate average profit for winning trades and average loss for losing trades\n",
|
||||
" avg_win = winners['Profit'].mean()\n",
|
||||
" avg_loss = losers['Profit'].mean()\n",
|
||||
" \n",
|
||||
" # Compute the expectancy ratio\n",
|
||||
" expectancy_ratio = (win_rate * avg_win) + (loss_rate * avg_loss)\n",
|
||||
" \n",
|
||||
" return expectancy_ratio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2611f9e5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create a dictionary with trade data including trade numbers and corresponding profits/losses."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7c26a120",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"trade_data = {\n",
|
||||
" 'Trade': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],\n",
|
||||
" 'Profit': [100, -50, 200, -100, 300, -150, 400, -200, 500, -250]\n",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b84b5061",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Convert the trade data dictionary into a pandas DataFrame."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3f057971",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"trades = pd.DataFrame(trade_data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4ce371e5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the expectancy ratio using the defined function and print the result."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4086f225",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"expectancy_ratio = calculate_expectancy_ratio(trades)\n",
|
||||
"print(f\"Expectancy Ratio: {expectancy_ratio}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "49299264",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
246
to_explore/pyquantnews/10_SharpeRatio.ipynb
Normal file
246
to_explore/pyquantnews/10_SharpeRatio.ipynb
Normal file
@ -0,0 +1,246 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1ed30b1e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "550cc5f3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code downloads historical price data for SPY and AAPL from Yahoo Finance, calculates daily returns, and computes the Sharpe ratio for these returns. It includes a function to determine the Sharpe ratio, adjusting for a daily benchmark return. The code then plots the rolling 30-day Sharpe ratio for AAPL and visualizes the histogram of these Sharpe ratios. Additionally, it compares the rolling 30-day Sharpe ratio of AAPL against SPY and plots the histogram of the differences."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dcabe4c7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import yfinance as yf\n",
|
||||
"import numpy as np"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "46f6a31c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Download historical price data for SPY and AAPL from Yahoo Finance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2a83f550",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = yf.download([\"SPY\", \"AAPL\"], start=\"2020-01-01\", end=\"2022-07-31\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2018e200",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Extract adjusted closing prices for SPY and AAPL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c977a8bc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"closes = data['Adj Close']\n",
|
||||
"spy_returns = closes.SPY.pct_change().dropna()\n",
|
||||
"aapl_returns = closes.AAPL.pct_change().dropna()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9741f742",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a function to calculate the Sharpe ratio of a strategy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f618f290",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def sharpe_ratio(returns, adjustment_factor=0.0):\n",
|
||||
" \"\"\"\n",
|
||||
" Determines the Sharpe ratio of a strategy.\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" returns : pd.Series or np.ndarray\n",
|
||||
" Daily returns of the strategy, noncumulative.\n",
|
||||
" adjustment_factor : int, float\n",
|
||||
" Constant daily benchmark return throughout the period.\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" sharpe_ratio : float\n",
|
||||
"\n",
|
||||
" Note\n",
|
||||
" -----\n",
|
||||
" See https://en.wikipedia.org/wiki/Sharpe_ratio for more details.\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Adjust returns by subtracting the benchmark return\n",
|
||||
"\n",
|
||||
" returns_risk_adj = returns - adjustment_factor\n",
|
||||
"\n",
|
||||
" # Print the annualized standard deviation of the risk-adjusted returns\n",
|
||||
"\n",
|
||||
" print(returns_risk_adj.std() * np.sqrt(252))\n",
|
||||
"\n",
|
||||
" # Return the annualized Sharpe ratio\n",
|
||||
"\n",
|
||||
" return (\n",
|
||||
" returns_risk_adj.mean() / returns_risk_adj.std()\n",
|
||||
" ) * np.sqrt(252)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "035cea96",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the Sharpe ratio for SPY daily returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "23dfb39c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sharpe_ratio(spy_returns)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "19aebd83",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the Sharpe ratio for AAPL daily returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "12292244",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sharpe_ratio(aapl_returns)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "77252a24",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the rolling 30-day Sharpe ratio for AAPL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "75215b9c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"aapl_returns.rolling(30).apply(sharpe_ratio).plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5ac119f9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the histogram of the rolling 30-day Sharpe ratios for AAPL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "bae6269a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"aapl_returns.rolling(30).apply(sharpe_ratio).hist(bins=50)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3b70d6ff",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Compare the rolling 30-day Sharpe ratio of AAPL against SPY and plot the histogram of the differences"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0796ca2e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"(\n",
|
||||
" aapl_returns.rolling(30).apply(sharpe_ratio)\n",
|
||||
" - spy_returns.rolling(30).apply(sharpe_ratio)\n",
|
||||
").hist(bins=50)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cef5bf53",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
602
to_explore/pyquantnews/111_PrincipalComponentsRegression.ipynb
Normal file
602
to_explore/pyquantnews/111_PrincipalComponentsRegression.ipynb
Normal file
File diff suppressed because one or more lines are too long
414
to_explore/pyquantnews/112_LlamaIndexFinancialStatement.ipynb
Normal file
414
to_explore/pyquantnews/112_LlamaIndexFinancialStatement.ipynb
Normal file
@ -0,0 +1,414 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3fd3d3a8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "97330215",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.llms.openai import OpenAI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "dc086cc8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.core import (\n",
|
||||
" StorageContext,\n",
|
||||
" VectorStoreIndex,\n",
|
||||
" SimpleDirectoryReader,\n",
|
||||
" load_index_from_storage,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "93549f19",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.core.tools import QueryEngineTool, ToolMetadata\n",
|
||||
"from llama_index.core.query_engine import SubQuestionQueryEngine\n",
|
||||
"from dotenv import load_dotenv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "15d43e1f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"load_dotenv()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ca35fe7d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Configure the language model and load the document"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fd2c5bd0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"First, we configure the language model with specific parameters and load the document."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "30c58e66",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"llm = OpenAI(temperature=0, model_name=\"gpt-4o\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "d92fcf7f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Loaded NVDA 10-K with 80 pages\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"doc = SimpleDirectoryReader(input_files=[\"nvda.pdf\"]).load_data()\n",
|
||||
"print(f\"Loaded NVDA 10-K with {len(doc)} pages\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a7615156",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We set the language model to use the GPT-4 model with a temperature of 0 for deterministic responses. The model is configured to use an unlimited number of tokens. We then load the NVDA 10-K document from a PDF file and print the number of pages loaded."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d2c67a6e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Create an index to enable querying of the document"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ba9502c6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we create an index from the loaded document to facilitate efficient querying."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "22eff79d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"index = VectorStoreIndex.from_documents(doc)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "4651c1a9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"engine = index.as_query_engine(similarity_top_k=3)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "77f2ee27",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We create a VectorStoreIndex from the loaded document, which enables us to perform similarity searches. We then set up a query engine with a similarity search parameter to return the top 3 most relevant results for each query."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d36f8d48",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Query specific financial information from the document"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1fefd8e4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now, we can use the query engine to extract specific financial information from the document."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "a0ff7054",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"The revenue of NVIDIA in the last period reported was $30,040 million for the three months ended July 28, 2024, as shown on page 3 of the document.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = await engine.aquery(\"What is the revenue of NVDIA in the last period reported? Answer in millions with page reference. Include the period.\")\n",
|
||||
"print(response)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "4c70926e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"NVIDIA's fiscal period begins on the last Sunday in January.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = await engine.aquery(\"What is the beginning and end date of NVIDA's fiscal period?\")\n",
|
||||
"print(response)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "625a2cd7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We use the query engine to asynchronously ask questions about NVIDIA's financial report. The first query asks for the revenue in the last reported period, including the page reference. The second query asks for the beginning and end dates of NVIDIA's fiscal period. The responses are printed to the console."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c589df9b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Set up a tool for sub-question querying"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9b5b7372",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We will now set up a tool to handle more complex queries by breaking them down into sub-questions."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "c880739e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"query_engine_tool = [\n",
|
||||
" QueryEngineTool(\n",
|
||||
" query_engine=engine,\n",
|
||||
" metadata=ToolMetadata(name='nvda_10k', description='Provides information about NVDA financials for year 2024')\n",
|
||||
" )\n",
|
||||
"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "99229480",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"s_engine = SubQuestionQueryEngine.from_defaults(query_engine_tools=query_engine_tool)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d3f0d7e2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We create a list of QueryEngineTool objects with metadata describing the tool's function. We then initialize a SubQuestionQueryEngine with the list of tools. This engine can break down complex queries into smaller, more manageable sub-questions."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5a0e20f5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Perform complex queries on customer segments and risks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "268e67df",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Finally, we perform more complex queries on the document to extract detailed information about customer segments and business risks."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "194b22a8",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Generated 2 sub questions.\n",
|
||||
"\u001b[1;3;38;2;237;90;200m[nvda_10k] Q: What are the customer segments that grew the fastest in terms of revenue in 2024?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[nvda_10k] Q: Which geographies showed the highest growth in revenue for NVDA in 2024?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;237;90;200m[nvda_10k] A: Networking revenue grew the fastest in terms of revenue in 2024.\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[nvda_10k] A: Data Center revenue showed the highest growth in revenue for NVDA in 2024.\n",
|
||||
"\u001b[0mNetworking revenue grew the fastest in terms of revenue in 2024, while Data Center revenue showed the highest growth in revenue for NVDA in the same year.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = await s_engine.aquery(\"Compare and contrast the customer segments and geographies that grew the fastest\")\n",
|
||||
"print(response)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "56bc8833",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Generated 1 sub questions.\n",
|
||||
"\u001b[1;3;38;2;237;90;200m[nvda_10k] Q: What risks are highlighted in NVDA's 10-K document for the year 2024?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;237;90;200m[nvda_10k] A: Long manufacturing lead times, uncertain supply and component availability, failure to estimate customer demand accurately, mismatches between supply and demand, product shortages, excess inventory, and the impact of changes in product development cycles, competing technologies, business and economic conditions, government lockdowns, technology advancements, and other factors on revenue and supply levels.\n",
|
||||
"\u001b[0mThe risks highlighted in the document for NVIDIA's business include long manufacturing lead times, uncertain supply and component availability, failure to estimate customer demand accurately, mismatches between supply and demand, product shortages, excess inventory, and the impact of changes in product development cycles, competing technologies, business and economic conditions, government lockdowns, technology advancements, and other factors on revenue and supply levels.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = await s_engine.aquery(\"What risks to NVDIA's business are highlighted in the document?\")\n",
|
||||
"print(response)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "2c0c2b4e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Generated 2 sub questions.\n",
|
||||
"\u001b[1;3;38;2;237;90;200m[nvda_10k] Q: What are the key risks highlighted in the NVDA 10K document?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[nvda_10k] Q: How does NVDA plan to mitigate the risks mentioned in the 10K document?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;237;90;200m[nvda_10k] A: The key risks highlighted in the NVDA 10K document include potential challenges related to manufacturing lead times, uncertain supply and component availability, inaccurate estimation of customer demand leading to mismatches between supply and demand, product shortages or excess inventory, inability to secure sufficient commitments for capacity, impeded ability to sell products if necessary components are unavailable, extended lead times on orders, increased product costs due to securing future supply, and the impact of various factors on underestimating or overestimating customer demand.\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[nvda_10k] A: NVDA plans to mitigate the risks mentioned in the 10K document by increasing supply and capacity purchases with existing and new suppliers to support their demand projections. Additionally, they aim to accurately estimate customer demand to avoid mismatches between supply and demand, which have previously harmed their financial results. They may enter into long-term supply agreements and capacity commitments to address their business needs and secure sufficient commitments for capacity. Furthermore, they acknowledge the potential impact of factors such as changes in product development cycles, competitor actions, economic conditions, and technology advancements on their revenue and strive to manage these uncertainties effectively.\n",
|
||||
"\u001b[0mNVDIA sees the risks highlighted in the document impacting financial performance through potential challenges related to manufacturing lead times, uncertain supply and component availability, inaccurate estimation of customer demand leading to mismatches between supply and demand, product shortages or excess inventory, inability to secure sufficient commitments for capacity, impeded ability to sell products if necessary components are unavailable, extended lead times on orders, increased product costs due to securing future supply, and the impact of various factors on underestimating or overestimating customer demand. These risks could lead to financial implications such as decreased revenue, increased costs, reduced profitability, and potential negative effects on overall financial results.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = await s_engine.aquery(\"How does NVDIA see the risks highlighted in the document impacting financial performance?\")\n",
|
||||
"print(response)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b1ac62ee",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We use the sub-question query engine to ask complex questions about NVIDIA's customer segments and geographies and the business risks highlighted in the document. The engine breaks these questions into smaller sub-questions, processes them, and compiles the responses. Each response is then printed to the console."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b47d32f0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d6d8773b-4b58-43c0-829c-c56fd603703d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
459
to_explore/pyquantnews/113_VectorBTPairsTrading.ipynb
Normal file
459
to_explore/pyquantnews/113_VectorBTPairsTrading.ipynb
Normal file
@ -0,0 +1,459 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "607fd8fb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "49879d7a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"from vectorbtpro import *\n",
|
||||
"import pandas as pd\n",
|
||||
"import scipy.stats as st\n",
|
||||
"import statsmodels.tsa.stattools as ts \n",
|
||||
"import numpy as np\n",
|
||||
"import warnings\n",
|
||||
"warnings.filterwarnings(\"ignore\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a726d955",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Load and save S&P 500 tickers and their data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8eed1e5d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"First, we load the S&P 500 tickers from Wikipedia and save their historical data if it doesn't already exist. We will store the data in an HDF5 file format. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f481675b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sp500_tickers = pd.read_html('https://en.wikipedia.org/wiki/List_of_S%26P_500_companies')[0]['Symbol'].tolist()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "95b7da8c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"COINT_FILE = \"coint_pvalues.pickle\"\n",
|
||||
"POOL_FILE = \"data_pool.h5\"\n",
|
||||
"START = \"2015-01-01\"\n",
|
||||
"END = \"2023-12-31\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8be1d20a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if not vbt.file_exists(POOL_FILE):\n",
|
||||
" with vbt.ProgressBar(total=len(sp500_tickers)) as pbar:\n",
|
||||
" collected = 0\n",
|
||||
" for symbol in sp500_tickers:\n",
|
||||
" try:\n",
|
||||
" data = vbt.YFData.pull(\n",
|
||||
" symbol,\n",
|
||||
" start=START,\n",
|
||||
" end=END,\n",
|
||||
" silence_warnings=True,\n",
|
||||
" )\n",
|
||||
" data.to_hdf(POOL_FILE)\n",
|
||||
" collected += 1\n",
|
||||
" except:\n",
|
||||
" pass\n",
|
||||
" pbar.set_prefix(f\"{symbol} ({collected})\")\n",
|
||||
" pbar.update()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4f1e195c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We load the S&P 500 tickers from Wikipedia using pandas. We then check if the data file already exists. If it does not, we initialize a progress bar and start collecting historical data for each ticker using the vectorbtpro library's YFData.pull method. The collected data is then saved into an HDF5 file. If there is an error while collecting data for a ticker, we simply pass and move to the next ticker."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9870389a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Filter and select valid symbols from the data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ad74b67c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we load the saved data, filter out any symbols with missing data, and keep only valid symbols. This ensures we work with complete datasets."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4896730c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = vbt.HDFData.pull(\n",
|
||||
" POOL_FILE,\n",
|
||||
" start=START,\n",
|
||||
" end=END,\n",
|
||||
" silence_warnings=True\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4f8e9058",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = data.select_symbols([\n",
|
||||
" k\n",
|
||||
" for k, v in data.data.items()\n",
|
||||
" if not v.isnull().any().any()\n",
|
||||
"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9cac3f6d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We load the saved data from the HDF5 file. We then iterate over each symbol's data and check for missing values. If a symbol has any missing values, it is excluded. This ensures that we only work with complete datasets, which is crucial for accurate analysis."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cada47c4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Identify cointegrated pairs of stocks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6e2ea5ba",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now, we identify pairs of stocks that are cointegrated. Cointegration helps us find pairs of stocks that have a stable relationship over time, which is essential for pairs trading strategies."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "36050b45",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"@vbt.parameterized(\n",
|
||||
" merge_func=\"concat\",\n",
|
||||
" engine=\"pathos\",\n",
|
||||
" distribute=\"chunks\",\n",
|
||||
" n_chunks=\"auto\"\n",
|
||||
")\n",
|
||||
"def coint_pvalue(close, s1, s2):\n",
|
||||
" return ts.coint(np.log(close[s1]), np.log(close[s2]))[1]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5863250c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if not vbt.file_exists(COINT_FILE):\n",
|
||||
" coint_pvalues = coint_pvalue(\n",
|
||||
" data.close,\n",
|
||||
" vbt.Param(data.symbols, condition=\"s1 != s2\"),\n",
|
||||
" vbt.Param(data.symbols)\n",
|
||||
" )\n",
|
||||
" vbt.save(coint_pvalues, COINT_FILE)\n",
|
||||
"else:\n",
|
||||
" coint_pvalues = vbt.load(COINT_FILE)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "85224b4e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"coint_pvalues = coint_pvalues.sort_values()\n",
|
||||
"coint_pvalues.head(20)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3397e3ea",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We define a function to calculate the cointegration p-value between two stock price series. We use the parameterized decorator to parallelize the computation. If the cointegration p-values file does not exist, we calculate the p-values for all pairs of stocks and save the results. Otherwise, we load the saved p-values. We then sort the p-values in ascending order and display the top 20 pairs with the lowest p-values, indicating the strongest cointegration."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "019e2328",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Analyze and visualize the selected stock pair"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4e5cfedc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Finally, we choose a specific pair of stocks, analyze their price relationship, and visualize their spread and z-score. This helps us understand their mean-reverting behavior for potential trading opportunities."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b7ea967b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"S1, S2 = \"WYNN\", \"DVN\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "839b843a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.plot(column=\"Close\", symbol=[S1, S2], base=1).show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "edabec9b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"S1_log = np.log(data.get(\"Close\", S1))\n",
|
||||
"S2_log = np.log(data.get(\"Close\", S2))\n",
|
||||
"log_diff = (S2_log - S1_log).rename(\"Log diff\")\n",
|
||||
"fig = log_diff.vbt.plot()\n",
|
||||
"fig.add_hline(y=log_diff.mean(), line_color=\"yellow\", line_dash=\"dot\")\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "fc73a803",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = vbt.YFData.pull(\n",
|
||||
" [S1, S2],\n",
|
||||
" start=START,\n",
|
||||
" end=END,\n",
|
||||
" silence_warnings=True,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e1e36a43",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"UPPER = st.norm.ppf(1 - 0.05 / 2)\n",
|
||||
"LOWER = -st.norm.ppf(1 - 0.05 / 2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "40980b4a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"S1_close = data.get(\"Close\", S1)\n",
|
||||
"S2_close = data.get(\"Close\", S2)\n",
|
||||
"ols = vbt.OLS.run(S1_close, S2_close, window=vbt.Default(21))\n",
|
||||
"spread = ols.error.rename(\"Spread\")\n",
|
||||
"zscore = ols.zscore.rename(\"Z-score\")\n",
|
||||
"print(pd.concat((spread, zscore), axis=1))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "81afd692",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"upper_crossed = zscore.vbt.crossed_above(UPPER)\n",
|
||||
"lower_crossed = zscore.vbt.crossed_below(LOWER)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "eb36548f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = zscore.vbt.plot()\n",
|
||||
"fig.add_hline(y=UPPER, line_color=\"orangered\", line_dash=\"dot\")\n",
|
||||
"fig.add_hline(y=0, line_color=\"yellow\", line_dash=\"dot\")\n",
|
||||
"fig.add_hline(y=LOWER, line_color=\"limegreen\", line_dash=\"dot\")\n",
|
||||
"upper_crossed.vbt.signals.plot_as_exits(zscore, fig=fig)\n",
|
||||
"lower_crossed.vbt.signals.plot_as_entries(zscore, fig=fig)\n",
|
||||
"fig.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e650b931",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"long_entries = data.symbol_wrapper.fill(False)\n",
|
||||
"short_entries = data.symbol_wrapper.fill(False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8571c745",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"short_entries.loc[upper_crossed, S1] = True\n",
|
||||
"long_entries.loc[upper_crossed, S2] = True\n",
|
||||
"long_entries.loc[lower_crossed, S1] = True\n",
|
||||
"short_entries.loc[lower_crossed, S2] = True"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "66168c29",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf = vbt.Portfolio.from_signals(\n",
|
||||
" data,\n",
|
||||
" entries=long_entries,\n",
|
||||
" short_entries=short_entries,\n",
|
||||
" size=10,\n",
|
||||
" size_type=\"valuepercent100\",\n",
|
||||
" group_by=True,\n",
|
||||
" cash_sharing=True,\n",
|
||||
" call_seq=\"auto\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c5332cb0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pf.stats()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "de8b48d9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We select two specific stocks and plot their closing prices. We then calculate their log-price difference and plot it to analyze their mean-reverting behavior. We pull the latest data for the selected stocks and calculate the spread and z-score using a rolling window OLS regression. We identify the points where the z-score crosses above or below the thresholds and plot these signals. Finally, we define long and short entry signals and create a portfolio based on these signals to evaluate its performance."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bc7c7218",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Your next steps"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9b35e7cf-3a73-488f-bbe3-b818906bb040",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Try changing the selected stock pair to explore different cointegrated pairs. You can also adjust the z-score thresholds to see how it affects your trading signals. Experiment with different window sizes in the OLS regression to find an optimal setting for your strategy."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1af444c5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
236
to_explore/pyquantnews/114_SortinoRatio.ipynb
Normal file
236
to_explore/pyquantnews/114_SortinoRatio.ipynb
Normal file
@ -0,0 +1,236 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "40c4b691",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e8acbb3a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Load stock data and calculate daily returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ec0a3524-f34d-47c1-9d0d-4c214e667a05",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We start by loading stock data for three companies: Apple (AAPL), Microsoft (MSFT), and Google (GOOGL). We will use the adjusted closing prices from January 1, 2020, to January 1, 2022. Then, we calculate the daily returns for these stocks."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dd38b0ce",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"import yfinance as yf\n",
|
||||
"import matplotlib.pyplot as plt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "98a88cab",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"tickers = ['AAPL', 'MSFT', 'GOOGL']\n",
|
||||
"data = yf.download(tickers, start='2020-01-01', end='2022-01-01')['Adj Close']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "92f195d8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns = data.pct_change().dropna()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "29257797",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We use the yf.download method from the yfinance library to fetch adjusted closing prices for the specified tickers and date range. The pct_change method calculates the daily returns, which represent the percentage change in stock prices from one day to the next. The dropna method ensures that we remove any missing values from our data. This step is crucial for accurate calculations in subsequent steps."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9d9ce685",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Calculate portfolio returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7f4974cc-4fae-4684-ab89-5b1f170bf5e1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we will calculate the returns of a portfolio consisting of the three stocks. We assign specific weights to each stock and compute the portfolio returns based on these weights."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "915c359b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"weights = np.array([0.4, 0.4, 0.2])\n",
|
||||
"portfolio_returns = returns.dot(weights)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3bec93fa",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We create an array of weights that represent the proportion of the portfolio allocated to each stock: 40% to Apple, 40% to Microsoft, and 20% to Google. We then calculate the portfolio returns by taking the dot product of the daily returns and the weights. This gives us a time series of portfolio returns, which is the weighted sum of the individual stock returns for each day."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4901e227",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Calculate Sortino Ratio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8082f1f7-97d0-413e-8b8e-80447bd43342",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We will now calculate the Sortino Ratio for the portfolio. The Sortino Ratio measures the risk-adjusted return, focusing only on downside risk. We first determine the excess returns over a risk-free rate and then compute the downside deviation."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "bc4451fd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"risk_free_rate = 0.01\n",
|
||||
"target_return = 0\n",
|
||||
"excess_return = portfolio_returns - risk_free_rate / 252\n",
|
||||
"downside_returns = excess_return[excess_return < 0]\n",
|
||||
"downside_deviation = np.std(downside_returns)\n",
|
||||
"sortino_ratio = np.mean(excess_return) / downside_deviation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3a039805",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We set the risk-free rate at 1% and assume a target return of 0. The excess return is calculated by subtracting the daily risk-free rate from the portfolio returns. We identify the downside returns, which are the negative excess returns, and compute the standard deviation of these downside returns to get the downside deviation. Finally, we calculate the Sortino Ratio by dividing the mean excess return by the downside deviation. This ratio helps us understand the portfolio's performance relative to the downside risk."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dffa0116",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Plot portfolio returns and visualize the downside risk"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "45c15282-dddb-4f40-b31d-1101a2c8f643",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Lastly, we will plot the portfolio returns and highlight the periods where the returns are below the target return. This will help us visualize the downside risk."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "94f2cf01",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.figure(figsize=(12, 6))\n",
|
||||
"plt.plot(portfolio_returns.index, portfolio_returns, label='Portfolio Returns')\n",
|
||||
"downside_returns = portfolio_returns[portfolio_returns < target_return]\n",
|
||||
"plt.fill_between(downside_returns.index, downside_returns, alpha=0.5, color='red', label='Downside Returns')\n",
|
||||
"plt.title('Portfolio Returns with Downside Risk Highlighted')\n",
|
||||
"plt.xlabel('Date')\n",
|
||||
"plt.ylabel('Returns')\n",
|
||||
"plt.legend()\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1f318296",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We create a plot with portfolio returns on the y-axis and dates on the x-axis. We use the plot method from matplotlib to visualize the portfolio returns. We then identify the dates where the returns are below the target return and highlight these periods using the fill_between method with a red fill color. The plot includes a title and labels for the axes, and a legend to distinguish between the portfolio returns and downside returns. This visualization helps us easily identify periods of negative performance."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "32baf792",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Your next steps"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dd15bfe8-42e1-4f91-ba77-123b3dc5211b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Try changing the weights assigned to the stocks and observe how the portfolio returns and the Sortino Ratio change. You can also experiment with different time periods to see how the portfolio would have performed in various market conditions. This will give you a deeper understanding of portfolio performance and risk management."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5e461cd3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
503
to_explore/pyquantnews/115_PandasMarketDataAnalysis.ipynb
Normal file
503
to_explore/pyquantnews/115_PandasMarketDataAnalysis.ipynb
Normal file
File diff suppressed because one or more lines are too long
458
to_explore/pyquantnews/116_AutomateWithIBAPI.ipynb
Normal file
458
to_explore/pyquantnews/116_AutomateWithIBAPI.ipynb
Normal file
@ -0,0 +1,458 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fd999901",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "986105e8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import threading\n",
|
||||
"import time\n",
|
||||
"from typing import Dict, Optional"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b1b21d3d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"from ibapi.client import EClient\n",
|
||||
"from ibapi.common import BarData\n",
|
||||
"from ibapi.contract import Contract\n",
|
||||
"from ibapi.order import Order\n",
|
||||
"from ibapi.wrapper import EWrapper"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "655e0e34",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Calculate Donchian Channels for price data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "712afa36",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 2
|
||||
},
|
||||
"source": [
|
||||
"This section defines a function to calculate Donchian Channels for given price data over a specified period. It calculates the upper and lower bands and optionally the middle line."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c9b4cbb0",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 2
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def donchian_channel(df: pd.DataFrame, period: int = 30) -> pd.DataFrame:\n",
|
||||
"\n",
|
||||
" df[\"upper\"] = df[\"high\"].rolling(window=period).max()\n",
|
||||
"\n",
|
||||
" df[\"lower\"] = df[\"low\"].rolling(window=period).min()\n",
|
||||
"\n",
|
||||
" df[\"mid\"] = (df[\"upper\"] + df[\"lower\"]) / 2\n",
|
||||
"\n",
|
||||
" return df"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2467633a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This function takes a DataFrame containing price data and a period for the calculation. It computes the upper band as the highest high over the period and the lower band as the lowest low. The middle line is calculated as the average of the upper and lower bands. Finally, the function returns the DataFrame with the new columns added."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "193cbda0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Create a class to interact with Interactive Brokers API"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "59e6e825",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 2
|
||||
},
|
||||
"source": [
|
||||
"This section defines a TradingApp class that interacts with the Interactive Brokers (IB) API. This class handles connections, data retrieval, and order placement."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ef93770e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class TradingApp(EClient, EWrapper):\n",
|
||||
"\n",
|
||||
" def __init__(self) -> None:\n",
|
||||
"\n",
|
||||
" EClient.__init__(self, self)\n",
|
||||
" self.data: Dict[int, pd.DataFrame] = {}\n",
|
||||
" self.nextOrderId: Optional[int] = None\n",
|
||||
"\n",
|
||||
" def error(\n",
|
||||
" self, reqId: int, errorCode: int, errorString: str, advanced: any\n",
|
||||
" ) -> None:\n",
|
||||
"\n",
|
||||
" print(f\"Error: {reqId}, {errorCode}, {errorString}\")\n",
|
||||
"\n",
|
||||
" def nextValidId(self, orderId: int) -> None:\n",
|
||||
"\n",
|
||||
" super().nextValidId(orderId)\n",
|
||||
" self.nextOrderId = orderId\n",
|
||||
"\n",
|
||||
" def get_historical_data(self, reqId: int, contract: Contract) -> pd.DataFrame:\n",
|
||||
"\n",
|
||||
" self.data[reqId] = pd.DataFrame(columns=[\"time\", \"high\", \"low\", \"close\"])\n",
|
||||
" self.data[reqId].set_index(\"time\", inplace=True)\n",
|
||||
" self.reqHistoricalData(\n",
|
||||
" reqId=reqId,\n",
|
||||
" contract=contract,\n",
|
||||
" endDateTime=\"\",\n",
|
||||
" durationStr=\"1 D\",\n",
|
||||
" barSizeSetting=\"1 min\",\n",
|
||||
" whatToShow=\"MIDPOINT\",\n",
|
||||
" useRTH=0,\n",
|
||||
" formatDate=2,\n",
|
||||
" keepUpToDate=False,\n",
|
||||
" chartOptions=[],\n",
|
||||
" )\n",
|
||||
" time.sleep(5)\n",
|
||||
" return self.data[reqId]\n",
|
||||
"\n",
|
||||
" def historicalData(self, reqId: int, bar: BarData) -> None:\n",
|
||||
"\n",
|
||||
" df = self.data[reqId]\n",
|
||||
"\n",
|
||||
" df.loc[pd.to_datetime(bar.date, unit=\"s\"), [\"high\", \"low\", \"close\"]] = [\n",
|
||||
" bar.high,\n",
|
||||
" bar.low,\n",
|
||||
" bar.close,\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
" df = df.astype(float)\n",
|
||||
"\n",
|
||||
" self.data[reqId] = df\n",
|
||||
"\n",
|
||||
" @staticmethod\n",
|
||||
" def get_contract(symbol: str) -> Contract:\n",
|
||||
"\n",
|
||||
" contract = Contract()\n",
|
||||
" contract.symbol = symbol\n",
|
||||
" contract.secType = \"STK\"\n",
|
||||
" contract.exchange = \"SMART\"\n",
|
||||
" contract.currency = \"USD\"\n",
|
||||
" return contract\n",
|
||||
"\n",
|
||||
" def place_order(\n",
|
||||
" self, contract: Contract, action: str, order_type: str, quantity: int\n",
|
||||
" ) -> None:\n",
|
||||
"\n",
|
||||
" order = Order()\n",
|
||||
" order.action = action\n",
|
||||
" order.orderType = order_type\n",
|
||||
" order.totalQuantity = quantity\n",
|
||||
"\n",
|
||||
" self.placeOrder(self.nextOrderId, contract, order)\n",
|
||||
" self.nextOrderId += 1\n",
|
||||
" print(\"Order placed\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4ac4d04c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The TradingApp class extends EClient and EWrapper to interact with the IB API. It initializes the client and wrapper components and sets up data storage. The error method handles API errors, while nextValidId sets the next valid order ID. The get_historical_data method requests historical market data for a given contract, storing it in a DataFrame. The historicalData method processes and stores the received data. The get_contract method creates a stock contract, and the place_order method places trades using the provided contract, action, order type, and quantity."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dd46c3c3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Connect the trading app and request data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b4f05869",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This section connects the TradingApp to the IB API and requests historical data for a specified stock. It also calculates Donchian Channels for the acquired data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6916d705",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"app = TradingApp()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "46aa8c0a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"app.connect(\"127.0.0.1\", 7497, clientId=5)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e4c3e56e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"threading.Thread(target=app.run, daemon=True).start()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e7ceb258",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 2
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"while True:\n",
|
||||
" if isinstance(app.nextOrderId, int):\n",
|
||||
" print(\"connected\")\n",
|
||||
" break\n",
|
||||
" else:\n",
|
||||
" print(\"waiting for connection\")\n",
|
||||
" time.sleep(1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "57f829af",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"nvda = TradingApp.get_contract(\"NVDA\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "815602fb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = app.get_historical_data(99, nvda)\n",
|
||||
"data.tail()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "75f32b38",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 2
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"donchian = donchian_channel(data, period=30)\n",
|
||||
"donchian.tail()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "09e50c35",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The code creates an instance of the TradingApp class and connects it to the IB API using the specified IP address, port, and client ID. It then starts the app on a separate thread to allow code execution to continue. A loop checks for a successful connection by verifying the nextOrderId. Once connected, it defines a contract for the stock symbol NVDA and requests historical data for the last trading day using a specified request ID. The Donchian Channels are then calculated for the acquired data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8b65adae",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Implement trading logic based on Donchian Channels"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3061633c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This section implements trading logic based on the Donchian Channels. It checks for breakouts and places buy or sell orders accordingly."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f4d5dd6f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"period = 30"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "848bb8d0",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 2
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"while True:\n",
|
||||
"\n",
|
||||
" print(\"Getting data for contract...\")\n",
|
||||
" data = app.get_historical_data(99, nvda)\n",
|
||||
"\n",
|
||||
" if len(data) < period:\n",
|
||||
" print(f\"There are only {len(data)} bars of data, skipping...\")\n",
|
||||
" continue\n",
|
||||
"\n",
|
||||
" print(\"Computing the Donchian Channel...\")\n",
|
||||
" donchian = donchian_channel(data, period=period)\n",
|
||||
"\n",
|
||||
" last_price = data.iloc[-1].close\n",
|
||||
"\n",
|
||||
" upper, lower = donchian[[\"upper\", \"lower\"]].iloc[-1]\n",
|
||||
"\n",
|
||||
" print(\n",
|
||||
" f\"Check if last price {last_price} is outside the channels {upper} and {lower}\"\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" if last_price >= upper:\n",
|
||||
" print(\"Breakout detected, going long...\")\n",
|
||||
" app.place_order(nvda, \"BUY\", \"MKT\", 10)\n",
|
||||
"\n",
|
||||
" elif last_price <= lower:\n",
|
||||
" print(\"Breakout detected, going short...\")\n",
|
||||
" app.place_order(nvda, \"SELL\", \"MKT\", 10)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "31f5b652",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The code sets the period for the Donchian Channels to 30. It enters an infinite loop to continuously request data and check for trading opportunities. It retrieves historical data for the NVDA contract and skips further processing if there is insufficient data. It then calculates the Donchian Channels and gets the last traded price. The code compares the last price with the upper and lower channels to detect breakouts. If a breakout to the upside is detected, it places a buy market order for 10 shares. If a breakout to the downside is detected, it places a sell market order for 10 shares."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "89402165",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Disconnect the trading app"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "92b4fc23",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This section disconnects the TradingApp from the IB API once the trading logic is complete."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c771f63a",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 2
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"app.disconnect()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a4d25159",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The code disconnects the TradingApp from the IB API. This ensures that the connection is properly closed once the trading logic is complete. It is important to disconnect to avoid leaving open connections, which can lead to issues with the API or unwanted data usage."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d0733028",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Your next steps"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2221da63",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Try changing the stock symbol to a different one to see how the Donchian Channels and trading logic perform with other stocks. Experiment with different periods for the Donchian Channels to observe how the results change. Consider modifying the order quantity to suit your trading strategy better."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9961ff12",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
226
to_explore/pyquantnews/117_PortfolioVariance.ipynb
Normal file
226
to_explore/pyquantnews/117_PortfolioVariance.ipynb
Normal file
@ -0,0 +1,226 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3216dcbe",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "2348156b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import yfinance as yf\n",
|
||||
"import warnings\n",
|
||||
"warnings.filterwarnings(\"ignore\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "054e4261",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Retrieve historical stock prices for selected assets"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6dc9ad89-8631-4802-821b-399c956158bd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We start by collecting historical stock prices for a set of specified assets. We will use the `yfinance` library to download this data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "65f895d0",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[*********************100%%**********************] 5 of 5 completed\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"prices = yf.download([\"FDN\", \"FTXL\", \"FXD\", \"FXR\", \"HDEF\"], start=\"2020-01-01\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b258dbd9-5a22-46b4-ae2c-ce99a23291ec",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We use the `yfinance` library to download historical price data for five different assets, starting from January 1, 2020. This data includes all available prices up to the current date. The data is returned in a structured format, with separate columns for different types of prices, such as open, high, low, close, and adjusted close. We're interested in the adjusted close prices, which account for corporate actions like dividends and stock splits."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dbeb7368",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Calculate daily returns for each asset"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "79820113-91f4-49f1-97cf-17ebd716c7e8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we calculate the daily returns for each asset. This helps us understand how the price of each asset changes from one day to the next."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "d61f22ee",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns = (\n",
|
||||
" prices[\"Adj Close\"]\n",
|
||||
" .pct_change()\n",
|
||||
" .dropna()\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fa2b88c8-f9c7-43e3-9d5d-c36c50225c67",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We calculate the daily returns by taking the percentage change of the adjusted close prices from one day to the next. The `pct_change()` function is used to compute these changes. We drop any missing values that may arise from calculating percentage changes, particularly at the start of the dataset. The result is a new dataset where each value represents the daily return of an asset expressed as a percentage."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "63911f78",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Determine expected returns and covariance matrix"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5267166d-e2c1-45b0-bda3-6f0f340a0f2f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We now compute the expected returns for each asset and the covariance matrix of the returns. The expected return gives us an average sense of how much we can expect each asset to return. The covariance matrix helps us understand how the returns of the different assets move in relation to each other."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "754a1cb1-4498-4a8a-8a08-b6b3069c2ddd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"expected_returns = np.mean(returns, axis=0)\n",
|
||||
"cov_matrix = np.cov(returns, rowvar=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c6176ff7-22df-4e56-9c12-f9a1bbccf300",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We calculate the expected returns using the mean of the daily returns for each asset. This gives us a single number for each asset representing its average daily return over the entire period. The covariance matrix is computed using `np.cov`, which takes the returns data and calculates how the returns of different assets change together. The result is a matrix where each element represents the covariance between a pair of assets, helping us understand their return correlations."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9f2556d4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Evaluate the portfolio's risk through variance and standard deviation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b09aac17-c051-4214-a4ee-e38bbab9fc2e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Finally, we define a portfolio with equal weights for each asset and calculate its variance and standard deviation. These metrics will help us assess the risk associated with the portfolio."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "c961c89e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"weights = np.array([0.2] * 5)\n",
|
||||
"portfolio_variance = np.dot(weights.T, np.dot(cov_matrix, weights))\n",
|
||||
"portfolio_std_dev = np.sqrt(portfolio_variance) * np.sqrt(252)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5872f502-98b0-436a-bf0d-6e635bf8979d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We define equal weights of 0.2 for each asset, assuming we want to allocate the same proportion of our investment to each. The portfolio variance is calculated using a mathematical formula that combines the weights and the covariance matrix. This calculation results in a single value representing the total risk of the portfolio based on the variance of its assets. To get the annualized standard deviation, we take the square root of the portfolio variance and multiply it by the square root of 252, the typical number of trading days in a year. The standard deviation provides a measure of how much the portfolio's return might fluctuate annually."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ec9e6505",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Your next steps"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3996c26e-d565-4724-8128-d6ce7672bf74",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Try modifying the weight distribution among the assets to see how it affects the portfolio's risk. You can adjust the weights in the `weights` array, making some assets more dominant than others. Observe how changes in asset allocation impact the portfolio's variance and standard deviation, allowing you to explore different risk-return profiles. Experimenting with different weights helps in understanding the dynamics of portfolio optimization."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6928a33e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
269
to_explore/pyquantnews/11_InformationRatio.ipynb
Normal file
269
to_explore/pyquantnews/11_InformationRatio.ipynb
Normal file
@ -0,0 +1,269 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6ae95e22",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d03b11d6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code downloads historical price data for specific stocks and calculates a portfolio's performance. It uses Yahoo Finance data to get adjusted close prices for QQQ, AAPL, and AMZN. The code constructs a simple portfolio of equal shares in AAPL and AMZN, computes the portfolio's value, daily returns, and cumulative returns over time. It then compares the portfolio's cumulative returns to a benchmark (QQQ). Finally, it calculates the information ratio to evaluate the portfolio's performance relative to the benchmark."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "df51ef5b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import yfinance as yf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d1365087",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Download historical price data for QQQ, AAPL, and AMZN from Yahoo Finance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6c4473af",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = yf.download([\"QQQ\", \"AAPL\", \"AMZN\"], start=\"2020-01-01\", end=\"2022-07-31\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a22d784e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Extract adjusted close prices for the downloaded data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ab04ef0d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"closes = data['Adj Close']\n",
|
||||
"benchmark_returns = closes.QQQ.pct_change()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7788fe03",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Construct a simple portfolio with equal shares of AAPL and AMZN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3ac0ea69",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"aapl_position = closes.AAPL * 50\n",
|
||||
"amzn_position = closes.AMZN * 50"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "82e0e793",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Compute the portfolio value over time by summing the positions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3e5c9614",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"portfolio_value = aapl_position + amzn_position"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "75f062eb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the portfolio's daily profit and loss (PnL)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "98e59e8c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"portfolio_pnl = (\n",
|
||||
" (aapl_position - aapl_position.shift()) \n",
|
||||
" + (amzn_position - amzn_position.shift())\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "410af899",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Compute the portfolio's daily return by dividing PnL by the portfolio value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e17de052",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"portfolio_returns = (portfolio_pnl / portfolio_value)\n",
|
||||
"portfolio_returns.name = \"Port\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6349bff8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create cumulative returns for both the portfolio and the benchmark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ea4a9b96",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"portfolio_cumulative_returns = (portfolio_returns.fillna(0.0) + 1).cumprod()\n",
|
||||
"benchmark_cumulative_returns = (benchmark_returns.fillna(0.0) + 1).cumprod()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1382ec20",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the cumulative returns of the portfolio against the benchmark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9ba13165",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"portfolio_cumulative_returns = (portfolio_returns.fillna(0.0) + 1).cumprod()\n",
|
||||
"benchmark_cumulative_returns = (benchmark_returns.fillna(0.0) + 1).cumprod()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9e6d8de3",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pd.concat([portfolio_cumulative_returns, benchmark_cumulative_returns], axis=1).plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8454d5ed",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def information_ratio(portfolio_returns, benchmark_returns):\n",
|
||||
" \"\"\"\n",
|
||||
" Determines the information ratio of a strategy.\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" portfolio_returns : pd.Series or np.ndarray\n",
|
||||
" Daily returns of the strategy, noncumulative.\n",
|
||||
" benchmark_returns : int, float\n",
|
||||
" Daily returns of the benchmark or factor, noncumulative.\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" information_ratio : float\n",
|
||||
"\n",
|
||||
" Note\n",
|
||||
" -----\n",
|
||||
" See https://en.wikipedia.org/wiki/Information_ratio for more details.\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" # Calculate active return by subtracting benchmark returns from portfolio returns\n",
|
||||
" active_return = portfolio_returns - benchmark_returns\n",
|
||||
"\n",
|
||||
" # Calculate tracking error as the standard deviation of active returns\n",
|
||||
" tracking_error = active_return.std()\n",
|
||||
"\n",
|
||||
" # Return the information ratio, which is the mean active return divided by the tracking error\n",
|
||||
" return active_return.mean() / tracking_error"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7bd6fa3a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the information ratio of the portfolio relative to the benchmark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6960c140",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"information_ratio(portfolio_returns, benchmark_returns)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e225688c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
305
to_explore/pyquantnews/12_GARCH.ipynb
Normal file
305
to_explore/pyquantnews/12_GARCH.ipynb
Normal file
@ -0,0 +1,305 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "920cf328",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "879db885",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code downloads historical stock data, calculates returns, and fits an ARCH model to forecast volatility. It utilizes the Yahoo Finance API to obtain the adjusted closing prices of a specified stock. The ARCH model is then fitted to the calculated returns to estimate future volatility. The results are visualized and the forecasted volatility is annualized. This approach is valuable for financial modeling and risk management."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "335fbd37",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import yfinance as yf\n",
|
||||
"from arch import arch_model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2332c898",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%matplotlib inline\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"plt.rc(\"figure\", figsize=(16, 6))\n",
|
||||
"plt.rc(\"savefig\", dpi=90)\n",
|
||||
"plt.rc(\"font\", family=\"sans-serif\")\n",
|
||||
"plt.rc(\"font\", size=14)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2088696e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Download historical stock data for Apple (AAPL) from Yahoo Finance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "38de8923",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = yf.download(\"aapl\", start=\"2020-01-01\", end=\"2022-07-31\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ebf2b85c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Extract adjusted closing prices and calculate daily returns in percentage terms"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ff63f46b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"adjusted_closes = data['Adj Close']\n",
|
||||
"returns = 100 * adjusted_closes.pct_change().dropna()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "066381ec",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Initialize an ARCH model using the calculated returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "77127441",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model = arch_model(returns)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e0fbb63b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Fit the ARCH model to the returns data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8c4d2599",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"res = model.fit()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f00f8634",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Print a summary of the model fitting results"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "afe19f42",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(res.summary())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b19de7fa",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the results of the model fitting for visualization"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "820b348f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = res.plot(\"D\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "377f0e9a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Forecast variance for the next period using the fitted model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "67613456",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"forecast = res.forecast(horizon=1, reindex=False)\n",
|
||||
"variance_forecast = forecast.variance.iloc[-1][0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "18fd8b24",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the forecasted volatility and annualize it"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0f457d02",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"volatility_forecast = np.sqrt(variance_forecast)\n",
|
||||
"annualized_volatility_forecast = volatility_forecast * np.sqrt(252) / 100\n",
|
||||
"annualized_volatility_forecast"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8f619b19",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the historical annualized volatility standard deviation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3522f50d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"annualized_volatility_stdev = returns.std() * np.sqrt(252) / 100\n",
|
||||
"annualized_volatility_stdev"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b1fa7d5d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Compute the relative error between the forecasted volatility and the historical volatility standard deviation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a5e08674",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"(annualized_volatility_forecast - annualized_volatility_stdev) / annualized_volatility_stdev"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "35246630",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ca318b09",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d2cc08df",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a12d9575",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3956f9ac",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ac528c77",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5449b958",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7548750a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
250
to_explore/pyquantnews/13_SortinoRatio.ipynb
Normal file
250
to_explore/pyquantnews/13_SortinoRatio.ipynb
Normal file
@ -0,0 +1,250 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e8e4c152",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a57f8b45",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code downloads historical stock data for SPY and AAPL from Yahoo Finance and calculates daily returns. It then computes the Sortino ratio, a risk-adjusted performance metric, for these returns. The Sortino ratio focuses on downside risk, providing a better measure for evaluating strategies with asymmetric risk profiles. The code also visualizes the rolling Sortino ratio and its distribution for AAPL. This is useful for performance analysis and comparison of different investment strategies."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8a9c9a42",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import yfinance as yf\n",
|
||||
"import numpy as np"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "aac23142",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Download historical adjusted closing prices for SPY and AAPL from Yahoo Finance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e3627e86",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = yf.download([\"SPY\", \"AAPL\"], start=\"2020-01-01\", end=\"2022-07-31\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "778fee89",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Extract adjusted closing prices and calculate daily returns for SPY and AAPL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "41003991",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"closes = data['Adj Close']\n",
|
||||
"spy_returns = closes.SPY.pct_change().dropna()\n",
|
||||
"aapl_returns = closes.AAPL.pct_change().dropna()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dbb043d7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a function to calculate the Sortino ratio for a series of returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "fefe6265",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def sortino_ratio(returns, adjustment_factor=0.0):\n",
|
||||
" \"\"\"\n",
|
||||
" Determines the Sortino ratio of a strategy.\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" returns : pd.Series or np.ndarray\n",
|
||||
" Daily returns of the strategy, noncumulative.\n",
|
||||
" adjustment_factor : int, float\n",
|
||||
" Constant daily benchmark return throughout the period.\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" sortino_ratio : float\n",
|
||||
"\n",
|
||||
" Note\n",
|
||||
" -----\n",
|
||||
" See `<https://www.sunrisecapital.com/wp-content/uploads/2014/06/Futures_\n",
|
||||
" Mag_Sortino_0213.pdf>`__ for more details.\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Adjust returns by subtracting the adjustment factor and compute average annual return\n",
|
||||
" returns_risk_adj = np.asanyarray(returns - adjustment_factor)\n",
|
||||
" average_annual_return = returns_risk_adj.mean() * 252\n",
|
||||
"\n",
|
||||
" # Compute downside deviation by considering only negative deviations\n",
|
||||
" downside_diff = np.clip(returns_risk_adj, np.NINF, 0)\n",
|
||||
" np.square(downside_diff, out=downside_diff)\n",
|
||||
" annualized_downside_deviation = np.sqrt(downside_diff.mean()) * np.sqrt(252)\n",
|
||||
"\n",
|
||||
" # Calculate and return the Sortino ratio\n",
|
||||
" return average_annual_return / annualized_downside_deviation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "334d33ea",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the Sortino ratio for SPY's daily returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0c281e5e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sortino_ratio(spy_returns)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cd306508",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the Sortino ratio for AAPL's daily returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a6d8b4a1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sortino_ratio(aapl_returns)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ea3d422f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the rolling 30-day Sortino ratio for AAPL's returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b86b1ba0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"aapl_returns.rolling(30).apply(sortino_ratio).plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bf0f5718",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the histogram of the rolling 30-day Sortino ratio for AAPL's returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dcf47150",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"aapl_returns.rolling(30).apply(sortino_ratio).hist(bins=50)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8eaa8c01",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the histogram of the difference between rolling 30-day Sortino ratios of AAPL and SPY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3f3b8abb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"(\n",
|
||||
" aapl_returns.rolling(30).apply(sortino_ratio)\n",
|
||||
" - spy_returns.rolling(30).apply(sortino_ratio)\n",
|
||||
").hist(bins=50)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7ffcdc8a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the Sortino ratio for SPY's daily returns again"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "221fcbf5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sortino_ratio(spy_returns)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ac229400",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
310
to_explore/pyquantnews/14_RiskParity.ipynb
Normal file
310
to_explore/pyquantnews/14_RiskParity.ipynb
Normal file
@ -0,0 +1,310 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ffd5a549",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8ebd9176",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code constructs and optimizes a financial portfolio using historical stock data. It uses the Riskfolio library to estimate asset returns and covariance. The code further optimizes the portfolio for risk parity and visualizes the resulting asset allocation and risk contributions. Additionally, it demonstrates the impact of adding a constraint on expected returns. This is useful for portfolio management and risk assessment in finance."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "fe7c58a8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import yfinance as yf\n",
|
||||
"import riskfolio as rp\n",
|
||||
"import warnings\n",
|
||||
"warnings.filterwarnings(\"ignore\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f926ae4e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a list of stock tickers to include in the portfolio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7042f7bf",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"assets = [\"JCI\", \"TGT\", \"CMCSA\", \"CPB\", \"MO\", \"APA\", \"MMC\", \"JPM\",\n",
|
||||
" \"ZION\", \"PSA\", \"BAX\", \"BMY\", \"LUV\", \"PCAR\", \"TXT\", \"TMO\",\n",
|
||||
" \"DE\", \"MSFT\", \"HPQ\", \"SEE\", \"VZ\", \"CNP\", \"NI\", \"T\", \"BA\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "79e0a66f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Sort the list of tickers alphabetically"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "68e33540",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"assets.sort()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0e39a06b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Download historical stock data for the specified tickers from Yahoo Finance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1020999e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = yf.download(assets, start=\"2016-01-01\", end=\"2019-12-30\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "965ab385",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate daily returns based on adjusted closing prices"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6c6cf1fd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns = data['Adj Close'].pct_change().dropna()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0b4b580c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create a Portfolio object using the calculated returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1f3a849b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"port = rp.Portfolio(returns=returns)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "aefeba8e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Estimate expected returns and covariance matrix using historical data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6d5508fd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"port.assets_stats(method_mu='hist', method_cov='hist', d=0.94)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d01b6618",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Optimize the portfolio for risk parity using mean-variance optimization"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e81ffa05",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"w_rp = port.rp_optimization(\n",
|
||||
" model=\"Classic\", # use historical\n",
|
||||
" rm=\"MV\", # use mean-variance optimization\n",
|
||||
" hist=True, # use historical scenarios\n",
|
||||
" rf=0, # set risk free rate to 0\n",
|
||||
" b=None # don't use constraints\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c1831b35",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the asset allocation of the optimized portfolio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "65904137",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ax = rp.plot_pie(w=w_rp)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e25e251f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the risk contribution of each asset in the optimized portfolio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "79c79c02",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ax = rp.plot_risk_con(\n",
|
||||
" w_rp,\n",
|
||||
" cov=port.cov,\n",
|
||||
" returns=port.returns,\n",
|
||||
" rm=\"MV\",\n",
|
||||
" rf=0,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2a97ece3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Set a constraint for the minimum level of expected returns for the portfolio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "be3c355a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"port.lowerret = 0.0008"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6400a196",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Optimize the portfolio for risk parity with the added constraint on expected returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "856a87ed",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"w_rp_c = port.rp_optimization(\n",
|
||||
" model=\"Classic\", # use historical\n",
|
||||
" rm=\"MV\", # use mean-variance optimization\n",
|
||||
" hist=True, # use historical scenarios\n",
|
||||
" rf=0, # set risk free rate to 0\n",
|
||||
" b=None # don't use constraints\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0aa64fb9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the asset allocation of the constrained optimized portfolio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3f34eb3a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ax = rp.plot_pie(w=w_rp_c)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "42e39406",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the risk contribution of each asset in the constrained optimized portfolio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9dc06f47",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ax = rp.plot_risk_con(\n",
|
||||
" w_rp_c,\n",
|
||||
" cov=port.cov,\n",
|
||||
" returns=port.returns,\n",
|
||||
" rm=\"MV\",\n",
|
||||
" rf=0,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bcf00531",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
287
to_explore/pyquantnews/15_StockPriceDatabase.ipynb
Normal file
287
to_explore/pyquantnews/15_StockPriceDatabase.ipynb
Normal file
@ -0,0 +1,287 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b99b374d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "db4c147e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This notebook fetches financial data for specified stock symbols from Yahoo Finance and stores it in a SQLite database. It provides functions to download stock data over a given date range and save the data into the database. Additionally, it can save data for the latest trading session. This is useful for maintaining a local database of historical stock prices for analysis and backtesting. The code demonstrates usage with example stock symbols and queries the stored data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d3000251",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from sys import argv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "280a44ca",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import yfinance as yf\n",
|
||||
"import sqlite3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6223c51a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Establish a connection to the SQLite database"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8b6b88ae",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"con = sqlite3.connect(\"market_data.sqlite\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a7a5eed8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Fetch stock data for a given symbol and date range from Yahoo Finance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "03765086",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def get_stock_data(symbol, start, end):\n",
|
||||
" \"\"\"Fetch stock data from Yahoo Finance.\n",
|
||||
" \n",
|
||||
" Downloads stock data for a specified symbol and date range and processes it.\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" symbol : str\n",
|
||||
" The stock ticker symbol.\n",
|
||||
" start : str\n",
|
||||
" The start date (YYYY-MM-DD).\n",
|
||||
" end : str\n",
|
||||
" The end date (YYYY-MM-DD).\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" data : pd.DataFrame\n",
|
||||
" A DataFrame containing the stock data.\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" # Download the stock data from Yahoo Finance\n",
|
||||
" data = yf.download(symbol, start=start, end=end)\n",
|
||||
" \n",
|
||||
" # Reset the DataFrame index to use integer indexing\n",
|
||||
" data.reset_index(inplace=True)\n",
|
||||
"\n",
|
||||
" # Rename the columns to match database schema\n",
|
||||
" data.rename(columns={\n",
|
||||
" \"Date\": \"date\",\n",
|
||||
" \"Open\": \"open\",\n",
|
||||
" \"Low\": \"low\",\n",
|
||||
" \"Close\": \"close\",\n",
|
||||
" \"Adj Close\": \"adj_close\",\n",
|
||||
" \"Volume\": \"volume\"\n",
|
||||
" }, inplace=True)\n",
|
||||
" \n",
|
||||
" # Add a column for the stock symbol\n",
|
||||
" data['symbol'] = symbol\n",
|
||||
" \n",
|
||||
" return data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1d907550",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Save stock data for a given symbol and date range to the SQLite database"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7117438c",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def save_data_range(symbol, start, end):\n",
|
||||
" \"\"\"Save stock data to database.\n",
|
||||
" \n",
|
||||
" Fetches and saves stock data for a specified symbol and date range.\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" symbol : str\n",
|
||||
" The stock ticker symbol.\n",
|
||||
" start : str\n",
|
||||
" The start date (YYYY-MM-DD).\n",
|
||||
" end : str\n",
|
||||
" The end date (YYYY-MM-DD).\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" None\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" # Get stock data for the specified symbol and date range\n",
|
||||
" data = get_stock_data(symbol, start, end)\n",
|
||||
" \n",
|
||||
" # Save the data to the SQLite database\n",
|
||||
" data.to_sql(\n",
|
||||
" \"stock_data\", \n",
|
||||
" con, \n",
|
||||
" if_exists=\"append\", \n",
|
||||
" index=False\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1cd30bfc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Save stock data for the latest trading session to the SQLite database"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3cb23ec7",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def save_last_trading_session(symbol):\n",
|
||||
" \"\"\"Save the latest trading session data.\n",
|
||||
" \n",
|
||||
" Fetches and saves stock data for the latest trading session.\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" symbol : str\n",
|
||||
" The stock ticker symbol.\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" None\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" # Get today's date\n",
|
||||
" today = pd.Timestamp.today()\n",
|
||||
" \n",
|
||||
" # Get stock data for the latest trading session\n",
|
||||
" data = get_stock_data(symbol, today, today)\n",
|
||||
" \n",
|
||||
" # Save the data to the SQLite database\n",
|
||||
" data.to_sql(\n",
|
||||
" \"stock_data\", \n",
|
||||
" con, \n",
|
||||
" if_exists=\"append\", \n",
|
||||
" index=False\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "42704aa5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Fetch and save data for the symbol \"TLT\" from 2022-01-01 to 2022-10-21"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d84f5d39",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"save_data_range(\"TLT\", \"2022-01-01\", \"2022-10-21\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9bb24b46",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Query the SQLite database to fetch data for the symbol \"SPY\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a34f05ee",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df = pd.read_sql_query(\"SELECT * from stock_data where symbol='SPY'\", con)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "97dd546f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Display the fetched data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "481992be",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fcf58b0f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
240
to_explore/pyquantnews/16_OmegaRatio.ipynb
Normal file
240
to_explore/pyquantnews/16_OmegaRatio.ipynb
Normal file
@ -0,0 +1,240 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "79ea368d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "58225e34",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code calculates the Omega ratio for financial returns, a performance metric that captures more information about the distribution of returns than traditional metrics like the Sharpe ratio. It uses stock price data from Yahoo Finance to compute daily returns for a specific stock (AAPL). The Omega ratio is calculated by dividing the sum of positive returns above a threshold by the absolute sum of negative returns below that threshold. This metric is useful for assessing the risk and return profile of investments, especially those with non-normal return distributions. The code also visualizes the rolling Omega ratio and basic statistical properties of the returns."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3a954243",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import yfinance as yf\n",
|
||||
"import numpy as np"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a375f9fa",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Download the stock data for AAPL from Yahoo Finance for the specified date range"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e3430b66",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = yf.download(\"AAPL\", start=\"2020-01-01\", end=\"2021-12-31\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "294330b3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate daily returns from the adjusted closing prices"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4133cec7",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns = data[\"Adj Close\"].pct_change()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "68e174db",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the Omega ratio of a strategy's returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "aa288ad4",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def omega_ratio(returns, required_return=0.0):\n",
|
||||
" \"\"\"Determines the Omega ratio of a strategy.\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" returns : pd.Series or np.ndarray\n",
|
||||
" Daily returns of the strategy, noncumulative.\n",
|
||||
" required_return : float, optional\n",
|
||||
" Minimum acceptance return of the investor. Threshold over which to\n",
|
||||
" consider positive vs negative returns. It will be converted to a\n",
|
||||
" value appropriate for the period of the returns. E.g. An annual minimum\n",
|
||||
" acceptable return of 100 will translate to a minimum acceptable\n",
|
||||
" return of 0.018.\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" omega_ratio : float\n",
|
||||
"\n",
|
||||
" Note\n",
|
||||
" -----\n",
|
||||
" See https://en.wikipedia.org/wiki/Omega_ratio for more details.\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Convert the required return to a daily return threshold\n",
|
||||
"\n",
|
||||
" return_threshold = (1 + required_return) ** (1 / 252) - 1\n",
|
||||
"\n",
|
||||
" # Calculate the difference between returns and the return threshold\n",
|
||||
"\n",
|
||||
" returns_less_thresh = returns - return_threshold\n",
|
||||
"\n",
|
||||
" # Calculate the numerator as the sum of positive returns above the threshold\n",
|
||||
"\n",
|
||||
" numer = sum(returns_less_thresh[returns_less_thresh > 0.0])\n",
|
||||
"\n",
|
||||
" # Calculate the denominator as the absolute sum of negative returns below the threshold\n",
|
||||
"\n",
|
||||
" denom = -1.0 * sum(returns_less_thresh[returns_less_thresh < 0.0])\n",
|
||||
"\n",
|
||||
" # Return the Omega ratio if the denominator is positive; otherwise, return NaN\n",
|
||||
"\n",
|
||||
" if denom > 0.0:\n",
|
||||
" return numer / denom\n",
|
||||
" else:\n",
|
||||
" return np.nan"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cc63495d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the Omega ratio for the given returns and required return"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "da67b3ba",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"omega_ratio(returns, 0.07)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9e89e93b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Compute and plot the rolling 30-day Omega ratio of the returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ba112f43",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns.rolling(30).apply(omega_ratio).plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "33e329c8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot a histogram of the daily returns to visualize their distribution"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1bad9e37",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns.hist(bins=50)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cc2176f5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate and display the skewness of the returns distribution"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "85b6eba5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns.skew()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2cd3f659",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate and display the kurtosis of the returns distribution"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "025b51fc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns.kurtosis()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9f4bae70",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
388
to_explore/pyquantnews/19_VolatilitySurface.ipynb
Normal file
388
to_explore/pyquantnews/19_VolatilitySurface.ipynb
Normal file
@ -0,0 +1,388 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d75c1762",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "46b15169",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code retrieves option chains for a given ticker symbol using Yahoo Finance, then processes and visualizes the data. It fetches call and put options, calculates days to expiration, and filters based on implied volatility. The code further segments the options data by expiration date and strike price to plot implied volatility skew and term structure. Finally, it creates a 3D surface plot to visualize implied volatility across different strikes and expirations. This is useful for options analysis and trading strategies."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b020a9f9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import pandas as pd\n",
|
||||
"import yfinance as yf\n",
|
||||
"import datetime as dt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "23169cbf",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import matplotlib.pyplot as plt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "582752e3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a function to retrieve and process option chains for a given ticker symbol"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7883ca7d",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def option_chains(ticker):\n",
|
||||
" \"\"\"Retrieve and process option chains.\n",
|
||||
" \n",
|
||||
" This function fetches option chains from Yahoo Finance for a given ticker symbol, \n",
|
||||
" processes the data, and calculates days to expiration for each option.\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" ticker : str\n",
|
||||
" The ticker symbol of the asset.\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" chains : pd.DataFrame\n",
|
||||
" DataFrame containing processed option chains with days to expiration.\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" # Fetch option chains from Yahoo Finance for the given ticker\n",
|
||||
" asset = yf.Ticker(ticker)\n",
|
||||
" expirations = asset.options\n",
|
||||
" \n",
|
||||
" chains = pd.DataFrame()\n",
|
||||
" \n",
|
||||
" for expiration in expirations:\n",
|
||||
" # Retrieve option chain for a specific expiration date\n",
|
||||
" opt = asset.option_chain(expiration)\n",
|
||||
" \n",
|
||||
" calls = opt.calls\n",
|
||||
" calls['optionType'] = \"call\"\n",
|
||||
" \n",
|
||||
" puts = opt.puts\n",
|
||||
" puts['optionType'] = \"put\"\n",
|
||||
" \n",
|
||||
" # Concatenate call and put options into a single DataFrame\n",
|
||||
" chain = pd.concat([calls, puts])\n",
|
||||
" chain['expiration'] = pd.to_datetime(expiration) + pd.DateOffset(hours=23, minutes=59, seconds=59)\n",
|
||||
" \n",
|
||||
" chains = pd.concat([chains, chain])\n",
|
||||
" \n",
|
||||
" # Calculate days to expiration for each option\n",
|
||||
" chains[\"daysToExpiration\"] = (chains.expiration - dt.datetime.today()).dt.days + 1\n",
|
||||
" \n",
|
||||
" return chains"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7548148b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Retrieve the option chains for the SPY ticker symbol"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2fd4f869",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"options = option_chains(\"SPY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a6f20ede",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Filter the option chains to get only call options"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3b39a055",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"calls = options[options[\"optionType\"] == \"call\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "21838163",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Select call options that expire on 2023-01-20"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2e289397",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"calls_at_expiry = calls[calls[\"expiration\"] == \"2023-01-20 23:59:59\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ef86ea45",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Filter out call options with low implied volatility"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f4fb5275",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"filtered_calls_at_expiry = calls_at_expiry[calls_at_expiry.impliedVolatility >= 0.001]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5cdc711d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot implied volatility skew for call options expiring on 2023-01-20"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b8a97578",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"filtered_calls_at_expiry[[\"strike\", \"impliedVolatility\"]].set_index(\"strike\").plot(\n",
|
||||
" title=\"Implied Volatility Skew\", figsize=(7, 4)\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "96a47554",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Select options with a strike price of 400.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "fc10d700",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"calls_at_strike = options[options[\"strike\"] == 400.0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e7f5080e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Filter out options with low implied volatility"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "055393a8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"filtered_calls_at_strike = calls_at_strike[calls_at_strike.impliedVolatility >= 0.001]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d06e635d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot implied volatility term structure for options with a strike price of 400.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "85184a63",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"filtered_calls_at_strike[[\"expiration\", \"impliedVolatility\"]].set_index(\"expiration\").plot(\n",
|
||||
" title=\"Implied Volatility Term Structure\", figsize=(7, 4)\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "439c1746",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Pivot the DataFrame to prepare data for a 3D surface plot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f05c6eca",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"surface = (\n",
|
||||
" calls[['daysToExpiration', 'strike', 'impliedVolatility']]\n",
|
||||
" .pivot_table(values='impliedVolatility', index='strike', columns='daysToExpiration')\n",
|
||||
" .dropna()\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "35a8ce2a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create a figure object for the 3D plot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "aaece560",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = plt.figure(figsize=(10, 8))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e817706c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Add a 3D subplot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c859d74c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ax = fig.add_subplot(111, projection='3d')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "51cff93c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Prepare coordinate matrices for the 3D plot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0a3cf2cc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x, y, z = surface.columns.values, surface.index.values, surface.values\n",
|
||||
"X, Y = np.meshgrid(x, y)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f3983db1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Set axis labels and title"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d2be8ca5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ax.set_xlabel('Days to expiration')\n",
|
||||
"ax.set_ylabel('Strike price')\n",
|
||||
"ax.set_zlabel('Implied volatility')\n",
|
||||
"ax.set_title('Call implied volatility surface')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "774bbcb1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the 3D surface of implied volatility"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0d281abb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ax.plot_surface(X, Y, z)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a65a16aa",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
383
to_explore/pyquantnews/20_BetaHedge.ipynb
Normal file
383
to_explore/pyquantnews/20_BetaHedge.ipynb
Normal file
@ -0,0 +1,383 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5f2ea2cb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ab6f458d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code analyzes the relationship between Tesla (TSLA) and S&P 500 (SPY) returns using linear regression. It downloads historical price data for TSLA and SPY, calculates daily returns, and plots them. A linear regression model is then used to estimate the alpha and beta of TSLA in relation to SPY. The code also constructs and evaluates a beta-hedged portfolio combining TSLA and SPY returns."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4e952311",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"from statsmodels import regression\n",
|
||||
"import statsmodels.api as sm\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import yfinance as yf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "60b5a085",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Download historical price data for TSLA and SPY from Yahoo Finance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c84671a0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = yf.download(\"TSLA, SPY\", start=\"2014-01-01\", end=\"2015-01-01\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "81dc42f6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Extract adjusted close prices for TSLA and SPY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "bd0ff214",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"asset = data[\"Adj Close\"].TSLA\n",
|
||||
"benchmark = data[\"Adj Close\"].SPY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b77d7257",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate daily returns for TSLA and SPY and drop any missing values"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8c2ec45f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"asset_returns = asset.pct_change().dropna()\n",
|
||||
"benchmark_returns = benchmark.pct_change().dropna()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4b54ff94",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot daily returns for TSLA and SPY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9da3ec4c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"asset_returns.plot()\n",
|
||||
"benchmark_returns.plot()\n",
|
||||
"plt.ylabel(\"Daily Return\")\n",
|
||||
"plt.legend()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6864d36d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Extract daily returns values for linear regression analysis"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "23a6712a",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"X = benchmark_returns.values\n",
|
||||
"Y = asset_returns.values"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "04e42249",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def linreg(x, y):\n",
|
||||
" \"\"\"Perform linear regression on two arrays.\n",
|
||||
" \n",
|
||||
" This function adds a constant to x, performs linear regression, \n",
|
||||
" and returns the alpha and beta coefficients.\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" x : np.ndarray\n",
|
||||
" The independent variable (e.g., benchmark returns).\n",
|
||||
" y : np.ndarray\n",
|
||||
" The dependent variable (e.g., asset returns).\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" alpha : float\n",
|
||||
" The intercept of the regression line.\n",
|
||||
" beta : float\n",
|
||||
" The slope of the regression line.\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Add a column of 1s to fit alpha\n",
|
||||
" x = sm.add_constant(x)\n",
|
||||
"\n",
|
||||
" # Fit the OLS regression model to the data\n",
|
||||
" model = regression.linear_model.OLS(y, x).fit()\n",
|
||||
"\n",
|
||||
" # Remove the constant now that we're done\n",
|
||||
" x = x[:, 1]\n",
|
||||
"\n",
|
||||
" return model.params[0], model.params[1]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "789ba4a8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate alpha and beta for TSLA relative to SPY using linear regression"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ffd2463d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"alpha, beta = linreg(X, Y)\n",
|
||||
"print(f\"Alpha: {alpha}\")\n",
|
||||
"print(f\"Beta: {beta}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4a34f58e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Generate a range of X values for plotting the regression line"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "70e5fc38",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"X2 = np.linspace(X.min(), X.max(), 100)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cac07ff2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the predicted Y values (regression line) for the generated X values"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "523900e1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"Y_hat = X2 * beta + alpha"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2d92649d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the raw data points (TSLA vs. SPY daily returns)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "79d7840d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.scatter(X, Y, alpha=0.3)\n",
|
||||
"plt.xlabel(\"SPY Daily Return\")\n",
|
||||
"plt.ylabel(\"TSLA Daily Return\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "90e244dc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the regression line on the scatter plot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6ce9f245",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.plot(X2, Y_hat, 'r', alpha=0.9);"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7cf8a6a8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Construct a beta-hedged portfolio by combining TSLA and SPY returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ada8b88c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"portfolio = -1 * beta * benchmark_returns + asset_returns\n",
|
||||
"portfolio.name = \"TSLA + Hedge\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ad5ee872",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the returns of the beta-hedged portfolio, TSLA, and SPY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "163b687b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"portfolio.plot(alpha=0.9)\n",
|
||||
"benchmark_returns.plot(alpha=0.5)\n",
|
||||
"asset_returns.plot(alpha=0.5)\n",
|
||||
"plt.ylabel(\"Daily Return\")\n",
|
||||
"plt.legend();"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d8eb7cbd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Print the mean and standard deviation of the beta-hedged portfolio and TSLA returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "99d31898",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"means: \", portfolio.mean(), asset_returns.mean())\n",
|
||||
"print(\"volatilities: \", portfolio.std(), asset_returns.std())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cab17da9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Extract portfolio values for further linear regression analysis"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "81a44668",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"P = portfolio.values"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a926759c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate alpha and beta for the beta-hedged portfolio using linear regression"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "383c99d8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"alpha, beta = linreg(X, P)\n",
|
||||
"print('alpha: ' + str(alpha))\n",
|
||||
"print('beta: ' + str(beta))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3fdca098",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
697
to_explore/pyquantnews/21_PairsTrading.ipynb
Normal file
697
to_explore/pyquantnews/21_PairsTrading.ipynb
Normal file
@ -0,0 +1,697 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7dc325ea",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7fbdbb7d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code analyzes the cointegration of stock pairs using historical price data. It downloads stock prices, finds cointegrated pairs, and calculates their spread. Cointegration indicates a stable, long-term relationship between stock pairs, useful for statistical arbitrage. Visualization tools like heatmaps and rolling z-scores help to identify trading signals. The code is practical in pairs trading strategies and quantitative finance."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "66c329f2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import pandas as pd"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "46e13192",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import statsmodels.api as sm\n",
|
||||
"from statsmodels.tsa.stattools import coint\n",
|
||||
"from statsmodels.regression.rolling import RollingOLS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ace9406d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import yfinance as yf\n",
|
||||
"import seaborn\n",
|
||||
"import matplotlib.pyplot as plt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fef80814",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define the list of stock symbols to analyze"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b3656066",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"symbol_list = ['meta', 'amzn', 'aapl', 'nflx', 'goog']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "aea87a6c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Download historical adjusted closing prices for the specified symbols"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "30fcec98",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = yf.download(symbol_list, start='2014-01-01', end='2015-01-01')['Adj Close']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "060ae221",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a function to find cointegrated pairs of stocks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "56b50383",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def find_cointegrated_pairs(data):\n",
|
||||
" \"\"\"Find cointegrated stock pairs\n",
|
||||
"\n",
|
||||
" This function calculates cointegration scores and p-values for stock pairs.\n",
|
||||
"\n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" data : pd.DataFrame\n",
|
||||
" DataFrame of stock prices with stocks as columns.\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" score_matrix : np.ndarray\n",
|
||||
" Matrix of cointegration scores.\n",
|
||||
" pvalue_matrix : np.ndarray\n",
|
||||
" Matrix of p-values for cointegration tests.\n",
|
||||
" pairs : list\n",
|
||||
" List of cointegrated stock pairs.\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" # Initialize matrices for scores and p-values, and a list for pairs\n",
|
||||
" n = data.shape[1]\n",
|
||||
" score_matrix = np.zeros((n, n))\n",
|
||||
" pvalue_matrix = np.ones((n, n))\n",
|
||||
" keys = data.keys()\n",
|
||||
" pairs = []\n",
|
||||
"\n",
|
||||
" # Loop over combinations of stock pairs to test for cointegration\n",
|
||||
" for i in range(n):\n",
|
||||
" for j in range(i + 1, n):\n",
|
||||
" S1 = data[keys[i]]\n",
|
||||
" S2 = data[keys[j]]\n",
|
||||
" result = coint(S1, S2)\n",
|
||||
" score = result[0]\n",
|
||||
" pvalue = result[1]\n",
|
||||
" score_matrix[i, j] = score\n",
|
||||
" pvalue_matrix[i, j] = pvalue\n",
|
||||
" \n",
|
||||
" # Add pair to list if p-value is less than 0.05\n",
|
||||
" if pvalue < 0.05:\n",
|
||||
" pairs.append((keys[i], keys[j]))\n",
|
||||
" \n",
|
||||
" return score_matrix, pvalue_matrix, pairs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c154e226",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Find cointegrated pairs and store scores, p-values, and pairs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "efff3232",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"scores, pvalues, pairs = find_cointegrated_pairs(data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "44112349",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Visualize the p-value matrix as a heatmap"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "288ff7d1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"seaborn.heatmap(\n",
|
||||
" pvalues, \n",
|
||||
" xticklabels=symbol_list, \n",
|
||||
" yticklabels=symbol_list, \n",
|
||||
" cmap='RdYlGn_r', \n",
|
||||
" mask=(pvalues >= 0.10)\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "65dabf09",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Select two stocks, Amazon (AMZN) and Apple (AAPL), for further analysis"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2c41310c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"S1 = data.AMZN\n",
|
||||
"S2 = data.AAPL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5edd06ec",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Perform a cointegration test on the selected pair"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "83fc4b52",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"score, pvalue, _ = coint(S1, S2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1ad95d73",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Print the p-value of the cointegration test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "240c1103",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pvalue"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "51882f40",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Add a constant term to the Amazon stock prices for regression analysis"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4bf6bfd7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"S1 = sm.add_constant(S1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fa8668d3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Fit an Ordinary Least Squares (OLS) regression model using Apple as the dependent variable"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "fbd88ec3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"results = sm.OLS(S2, S1).fit()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e8d11ac1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Remove the constant term from Amazon stock prices"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6d1de3f7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"S1 = S1.AMZN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a38f4599",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Extract the regression coefficient (beta) for Amazon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "03b56755",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"b = results.params['AMZN']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0199e5b8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the spread between Apple and the beta-adjusted Amazon prices"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "67d972a7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"spread = S2 - b * S1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "da2513be",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the spread and its mean value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "29b6df5f",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"spread.plot()\n",
|
||||
"plt.axhline(spread.mean(), color='black')\n",
|
||||
"plt.legend(['Spread'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c0cc2832",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a function to calculate the z-score of a series"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "56b5ca81",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def zscore(series):\n",
|
||||
" \"\"\"Calculate z-score of a series\n",
|
||||
"\n",
|
||||
" This function returns the z-score for a given series.\n",
|
||||
"\n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" series : pd.Series\n",
|
||||
" A pandas Series for which to calculate z-score.\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" zscore : pd.Series\n",
|
||||
" Z-score of the input series.\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" return (series - series.mean()) / np.std(series)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ef4d6e5c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the z-score of the spread with mean and threshold lines"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e28d66d0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"zscore(spread).plot()\n",
|
||||
"plt.axhline(zscore(spread).mean(), color='black')\n",
|
||||
"plt.axhline(1.0, color='red', linestyle='--')\n",
|
||||
"plt.axhline(-1.0, color='green', linestyle='--')\n",
|
||||
"plt.legend(['Spread z-score', 'Mean', '+1', '-1'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "daf80128",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create a DataFrame with the signal and position size in the pair"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3e3b8c8f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"trades = pd.concat([zscore(spread), S2 - b * S1], axis=1)\n",
|
||||
"trades.columns = [\"signal\", \"position\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "82f2b445",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Add long and short positions based on z-score thresholds"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "102994fc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"trades[\"side\"] = 0.0\n",
|
||||
"trades.loc[trades.signal <= -1, \"side\"] = 1\n",
|
||||
"trades.loc[trades.signal >= 1, \"side\"] = -1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "20667ab3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate and plot cumulative returns from the trading strategy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "202ffdea",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns = trades.position.pct_change() * trades.side\n",
|
||||
"returns.cumsum().plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ade9d6ff",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Print the trades DataFrame for inspection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a176d604",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"trades"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d7180626",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the spread using a rolling OLS model with a 30-day window"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c7690187",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model = RollingOLS(endog=S1, exog=S2, window=30)\n",
|
||||
"rres = model.fit()\n",
|
||||
"spread = S2 - rres.params.AAPL * S1\n",
|
||||
"spread.name = 'spread'"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9b5f37bb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate 1-day and 30-day moving averages of the spread"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f64b6a8c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"spread_mavg1 = spread.rolling(1).mean()\n",
|
||||
"spread_mavg1.name = 'spread 1d mavg'\n",
|
||||
"spread_mavg30 = spread.rolling(30).mean()\n",
|
||||
"spread_mavg30.name = 'spread 30d mavg'"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "34db999b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the 1-day and 30-day moving averages of the spread"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "847c6226",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.plot(spread_mavg1.index, spread_mavg1.values)\n",
|
||||
"plt.plot(spread_mavg30.index, spread_mavg30.values)\n",
|
||||
"plt.legend(['1 Day Spread MAVG', '30 Day Spread MAVG'])\n",
|
||||
"plt.ylabel('Spread')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f45c9783",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the rolling 30-day standard deviation of the spread"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c4235ea2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"std_30 = spread.rolling(30).std()\n",
|
||||
"std_30.name = 'std 30d'"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "63186967",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Compute and plot the z-score of the spread using 30-day moving averages and standard deviation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e3f5ed69",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"zscore_30_1 = (spread_mavg1 - spread_mavg30) / std_30\n",
|
||||
"zscore_30_1.name = 'z-score'\n",
|
||||
"zscore_30_1.plot()\n",
|
||||
"plt.axhline(0, color='black')\n",
|
||||
"plt.axhline(1.0, color='red', linestyle='--')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2639cbd5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the scaled stock prices and the rolling z-score for comparison"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9d976a6a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.plot(S1.index, S1.values / 10)\n",
|
||||
"plt.plot(S2.index, S2.values / 10)\n",
|
||||
"plt.plot(zscore_30_1.index, zscore_30_1.values)\n",
|
||||
"plt.legend(['S1 Price / 10', 'S2 Price / 10', 'Price Spread Rolling z-Score'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3e889d9b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Update the symbol list and download new data for another analysis period"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "de1483ff",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"symbol_list = ['amzn', 'aapl']\n",
|
||||
"data = yf.download(symbol_list, start='2015-01-01', end='2016-01-01')['Adj Close']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "64d819f3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Select Amazon (AMZN) and Apple (AAPL) prices from the new data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4ae4d2ee",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"S1 = data.AMZN\n",
|
||||
"S2 = data.AAPL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d48f9db4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Perform a cointegration test on the new data and print the p-value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1f80f3d8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"score, pvalue, _ = coint(S1, S2)\n",
|
||||
"print(pvalue)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c0e7a627",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
351
to_explore/pyquantnews/22_PortfolioPCA.ipynb
Normal file
351
to_explore/pyquantnews/22_PortfolioPCA.ipynb
Normal file
@ -0,0 +1,351 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "301963b1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "667b85b6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code performs Principal Component Analysis (PCA) on a portfolio of stocks to identify principal components driving the returns. It retrieves historical stock data, calculates daily returns, and applies PCA to these returns. The explained variance and principal components are visualized, and the factor returns and exposures are computed. These statistical risk factors help in understanding how much of the portfolio's returns arise from unobservable features. This is useful for portfolio management, risk assessment, and diversification analysis."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "71d0282c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import yfinance as yf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "746b91c7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"from sklearn.decomposition import PCA\n",
|
||||
"import matplotlib.pyplot as plt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2863a52a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a list of stock symbols to retrieve historical data for"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4f98bfc4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"symbols = [\n",
|
||||
" 'IBM',\n",
|
||||
" 'MSFT',\n",
|
||||
" 'META',\n",
|
||||
" 'INTC',\n",
|
||||
" 'NEM',\n",
|
||||
" 'AU',\n",
|
||||
" 'AEM',\n",
|
||||
" 'GFI'\n",
|
||||
"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "02e922c9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Download historical adjusted close prices for the defined stock symbols within the specified date range"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a563405d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = yf.download(symbols, start=\"2020-01-01\", end=\"2022-11-30\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1f52f786",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate daily returns for the portfolio by computing percentage change and dropping NaN values"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0b26bb2f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"portfolio_returns = data['Adj Close'].pct_change().dropna()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d6a1198c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Apply Principal Component Analysis (PCA) to the portfolio returns to identify key components"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a9c1ac8b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pca = PCA(n_components=3)\n",
|
||||
"pca.fit(portfolio_returns)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7dea06ae",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Retrieve the explained variance ratio and the principal components from the PCA model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3ed0fced",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pct = pca.explained_variance_ratio_\n",
|
||||
"pca_components = pca.components_"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "91fe5bfd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the cumulative explained variance for visualization and create an array for component indices"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4b40fb20",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"cum_pct = np.cumsum(pct)\n",
|
||||
"x = np.arange(1, len(pct) + 1, 1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a58e88c7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the percentage contribution of each principal component and the cumulative contribution"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "184bb55c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.subplot(1, 2, 1)\n",
|
||||
"plt.bar(x, pct * 100, align=\"center\")\n",
|
||||
"plt.title('Contribution (%)')\n",
|
||||
"plt.xlabel('Component')\n",
|
||||
"plt.xticks(x)\n",
|
||||
"plt.xlim([0, 4])\n",
|
||||
"plt.ylim([0, 100])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9fbd347d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.subplot(1, 2, 2)\n",
|
||||
"plt.plot(x, cum_pct * 100, 'ro-')\n",
|
||||
"plt.title('Cumulative contribution (%)')\n",
|
||||
"plt.xlabel('Component')\n",
|
||||
"plt.xticks(x)\n",
|
||||
"plt.xlim([0, 4])\n",
|
||||
"plt.ylim([0, 100])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5b0a4722",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Construct statistical risk factors using the principal components and portfolio returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "027a88cf",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"X = np.asarray(portfolio_returns)\n",
|
||||
"factor_returns = X.dot(pca_components.T)\n",
|
||||
"factor_returns = pd.DataFrame(\n",
|
||||
" columns=[\"f1\", \"f2\", \"f3\"], \n",
|
||||
" index=portfolio_returns.index,\n",
|
||||
" data=factor_returns\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3ba5fc5f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Display the first few rows of the factor returns DataFrame"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3f7ee295",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"factor_returns.head()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ae7022ed",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate and display the factor exposures by transposing the principal components matrix"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "69a1609b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"factor_exposures = pd.DataFrame(\n",
|
||||
" index=[\"f1\", \"f2\", \"f3\"], \n",
|
||||
" columns=portfolio_returns.columns,\n",
|
||||
" data=pca_components\n",
|
||||
").T"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "406e4727",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"factor_exposures"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "73a266c3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Sort and plot the factor exposures for the first principal component (f1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7ff5ca2a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"factor_exposures.f1.sort_values().plot.bar()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "65c58ba2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Scatter plot to visualize factor exposures of the first two principal components (f1 and f2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6fd70496",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"labels = factor_exposures.index\n",
|
||||
"data = factor_exposures.values\n",
|
||||
"plt.scatter(data[:, 0], data[:, 1])\n",
|
||||
"plt.xlabel('factor exposure of PC1')\n",
|
||||
"plt.ylabel('factor exposure of PC2')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "789e384f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for label, x, y in zip(labels, data[:, 0], data[:, 1]):\n",
|
||||
" plt.annotate(\n",
|
||||
" label,\n",
|
||||
" xy=(x, y), \n",
|
||||
" xytext=(-20, 20),\n",
|
||||
" textcoords='offset points',\n",
|
||||
" arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0')\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "425b0e57",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
595
to_explore/pyquantnews/23_ConditionalValueAtRisk.ipynb
Normal file
595
to_explore/pyquantnews/23_ConditionalValueAtRisk.ipynb
Normal file
@ -0,0 +1,595 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "431b9977",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "937cf4f7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code calculates Value at Risk (VaR) and Conditional Value at Risk (CVaR) for a portfolio of stocks. It uses historical stock data to compute returns, then scales these returns to simulate a portfolio's performance. It defines functions to calculate VaR and CVaR, both of which are measures of potential losses in a portfolio. The code also generates visualizations of these risk metrics to aid in financial risk assessment. This is practical for portfolio management and risk analysis in finance."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5b1162c9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import pandas as pd\n",
|
||||
"import yfinance as yf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b3d76629",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import matplotlib.pyplot as plt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b8b5d355",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a list of stock tickers representing the portfolio components"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9fd6d834",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"oex = ['MMM','T','ABBV','ABT','ACN','ALL','GOOGL','GOOG','MO','AMZN','AXP','AIG','AMGN','AAPL','BAC',\n",
|
||||
" 'BRK-B','BIIB','BLK','BA','BMY','CVS','COF','CAT','CVX','CSCO','C','KO','CL','CMCSA',\n",
|
||||
" 'COP','DHR','DUK','DD','EMC','EMR','EXC','XOM','META','FDX','F','GD','GE','GM','GILD',\n",
|
||||
" 'GS','HAL','HD','HON','INTC','IBM','JPM','JNJ','KMI','LLY','LMT','LOW','MA','MCD','MDT','MRK',\n",
|
||||
" 'MET','MSFT','MS','NKE','NEE','OXY','ORCL','PYPL','PEP','PFE','PM','PG','QCOM',\n",
|
||||
" 'SLB','SPG','SO','SBUX','TGT','TXN','BK','USB','UNP','UPS','UNH','VZ','V','WMT',\n",
|
||||
" 'WBA','DIS','WFC']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2e00c960",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Count the number of stocks in the portfolio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c5583926",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"num_stocks = len(oex)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "37a412ad",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Download historical stock data for the defined period"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c794c59a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = yf.download(oex, start='2014-01-01', end='2016-04-04')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "13db52ed",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate daily returns and de-mean the returns by subtracting the mean"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c61098bd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns = data['Adj Close'].pct_change()\n",
|
||||
"returns = returns - returns.mean(skipna=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ec932e56",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the de-meaned returns for visualization"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f2d5ad90",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns.plot(legend=None)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "38d7b1de",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a function to scale weights so their absolute values sum to one"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "31ffb424",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def scale(x):\n",
|
||||
" return x / np.sum(np.abs(x))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "87766d95",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Generate random weights for the portfolio and scale them"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0884a511",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"weights = scale(np.random.random(num_stocks))\n",
|
||||
"plt.bar(np.arange(num_stocks), weights)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1f97cb07",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a function to calculate Value at Risk (VaR)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5bccf1c9",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def value_at_risk(value_invested, returns, weights, alpha=0.95, lookback_days=500):\n",
|
||||
" \"\"\"Calculates Value at Risk (VaR) for a portfolio\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" value_invested : float\n",
|
||||
" Total value of the portfolio\n",
|
||||
" returns : pd.DataFrame\n",
|
||||
" Historical returns of the portfolio components\n",
|
||||
" weights : np.ndarray\n",
|
||||
" Weights of each asset in the portfolio\n",
|
||||
" alpha : float, optional\n",
|
||||
" Confidence level for VaR, by default 0.95\n",
|
||||
" lookback_days : int, optional\n",
|
||||
" Number of days to look back for historical data, by default 500\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" float\n",
|
||||
" The Value at Risk (VaR) at the specified confidence level\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" # Fill missing values in returns with zero and calculate portfolio returns\n",
|
||||
"\n",
|
||||
" returns = returns.fillna(0.0)\n",
|
||||
" portfolio_returns = returns.iloc[-lookback_days:].dot(weights)\n",
|
||||
"\n",
|
||||
" # Calculate the VaR as the percentile of portfolio returns\n",
|
||||
"\n",
|
||||
" return np.percentile(portfolio_returns, 100 * (1 - alpha)) * value_invested"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "700ce7bd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define the total value invested in the portfolio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b087d682",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"value_invested = 1000000"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "926f99ac",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate Value at Risk (VaR) using the defined function"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b0d5a7f9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"value_at_risk(value_invested, returns, weights, alpha=0.95, lookback_days=520)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c6bc2883",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define parameters for lookback days and confidence level"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5e9379f3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"lookback_days = 500\n",
|
||||
"alpha = 0.95"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "260bc52d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate portfolio returns using historical data and weights"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2396b2f1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"portfolio_returns = returns.fillna(0.0).iloc[-lookback_days:].dot(weights)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "79001b24",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate VaR and express it as a return rather than absolute loss"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "64973e13",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"portfolio_VaR = value_at_risk(value_invested, returns, weights, alpha=0.95)\n",
|
||||
"portfolio_VaR_return = portfolio_VaR / value_invested"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "43704fbe",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot histogram of portfolio returns and mark the VaR on the plot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "bd62e764",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.hist(portfolio_returns, bins=30)\n",
|
||||
"plt.axvline(portfolio_VaR_return, color='red', linestyle='solid')\n",
|
||||
"plt.legend(['VaR', 'Returns'])\n",
|
||||
"plt.title('Historical VaR')\n",
|
||||
"plt.xlabel('Return')\n",
|
||||
"plt.ylabel('Observation Frequency')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "376798ce",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define the number of iterations for VaR calculation and initialize an array"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c6eecfc0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"N = 1000\n",
|
||||
"VaRs = np.zeros((N, 1))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9d5cb851",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Iterate to calculate VaR over different lookback windows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "58bf2dfe",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for i in range(N):\n",
|
||||
" VaRs[i] = value_at_risk(value_invested, returns, weights, lookback_days=i + 1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "aadb983c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the VaR values over different lookback windows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "58715c8f",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.plot(VaRs)\n",
|
||||
"plt.xlabel('Lookback Window')\n",
|
||||
"plt.ylabel('VaR')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9d78afc3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a function to calculate Conditional Value at Risk (CVaR)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c9debae5",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def cvar(value_invested, returns, weights, alpha=0.95, lookback_days=500):\n",
|
||||
" \"\"\"Calculates Conditional Value at Risk (CVaR) for a portfolio\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" value_invested : float\n",
|
||||
" Total value of the portfolio\n",
|
||||
" returns : pd.DataFrame\n",
|
||||
" Historical returns of the portfolio components\n",
|
||||
" weights : np.ndarray\n",
|
||||
" Weights of each asset in the portfolio\n",
|
||||
" alpha : float, optional\n",
|
||||
" Confidence level for CVaR, by default 0.95\n",
|
||||
" lookback_days : int, optional\n",
|
||||
" Number of days to look back for historical data, by default 500\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" float\n",
|
||||
" The Conditional Value at Risk (CVaR) at the specified confidence level\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" # Calculate VaR and portfolio returns for the specified lookback period\n",
|
||||
"\n",
|
||||
" var = value_at_risk(value_invested, returns, weights, alpha, lookback_days=lookback_days)\n",
|
||||
" \n",
|
||||
" returns = returns.fillna(0.0)\n",
|
||||
" portfolio_returns = returns.iloc[-lookback_days:].dot(weights)\n",
|
||||
" var_pct_loss = var / value_invested\n",
|
||||
" \n",
|
||||
" # Calculate the mean of returns below the VaR threshold\n",
|
||||
"\n",
|
||||
" return np.nanmean(portfolio_returns[portfolio_returns < var_pct_loss]) * value_invested"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3362b55d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate CVaR using the defined function"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f8273f2f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"cvar(value_invested, returns, weights, lookback_days=500)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "64439069",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate VaR again for consistency"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ff33b141",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"value_at_risk(value_invested, returns, weights, lookback_days=500)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "db4a0e67",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate portfolio returns using historical data and weights"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2dc1b37c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"lookback_days = 500"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a2a13483",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"portfolio_returns = returns.fillna(0.0).iloc[-lookback_days:].dot(weights)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0ac89dd2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate VaR and CVaR and express them as returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "36b35c9e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"portfolio_VaR = value_at_risk(value_invested, returns, weights)\n",
|
||||
"portfolio_VaR_return = portfolio_VaR / value_invested"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8efa2126",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"portfolio_CVaR = cvar(value_invested, returns, weights)\n",
|
||||
"portfolio_CVaR_return = portfolio_CVaR / value_invested"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "40bead66",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot histogram of portfolio returns, marking VaR and CVaR on the plot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b4bc480d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.hist(portfolio_returns[portfolio_returns > portfolio_VaR_return], bins=20)\n",
|
||||
"plt.hist(portfolio_returns[portfolio_returns < portfolio_VaR_return], bins=10)\n",
|
||||
"plt.axvline(portfolio_VaR_return, color='red', linestyle='solid')\n",
|
||||
"plt.axvline(portfolio_CVaR_return, color='red', linestyle='dashed')\n",
|
||||
"plt.legend(['VaR', 'CVaR', 'Returns', 'Returns < VaR'])\n",
|
||||
"plt.title('Historical VaR and CVaR')\n",
|
||||
"plt.xlabel('Return')\n",
|
||||
"plt.ylabel('Observation Frequency')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4b6c6fbc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
290
to_explore/pyquantnews/24_FactorAnalysis.ipynb
Normal file
290
to_explore/pyquantnews/24_FactorAnalysis.ipynb
Normal file
@ -0,0 +1,290 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8bea070e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "59b6b6e4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code performs a multifactor analysis on monthly stock returns, applying the Fama-French three-factor model for financial analysis. It fetches historical factor data, calculates active returns of selected stocks, and estimates their sensitivities to the Fama-French factors. The code also performs rolling regression to analyze the stability of factor exposures over time. Lastly, it calculates and prints the marginal contributions to risk from each factor."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "84290a88",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import pandas as pd"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2ec2de24",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas_datareader as pdr\n",
|
||||
"import yfinance as yf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "06d39571",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import statsmodels.api as sm\n",
|
||||
"from statsmodels import regression\n",
|
||||
"from statsmodels.regression.rolling import RollingOLS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bc2a7be0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Fetch Fama-French factors data starting from 2000-01-01 and select the SMB and HML factors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5aff0b52",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"factors = pdr.get_data_famafrench(\n",
|
||||
" 'F-F_Research_Data_Factors',\n",
|
||||
" start='2000-01-01'\n",
|
||||
")[0][1:]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "65938e3e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"SMB = factors.SMB\n",
|
||||
"HML = factors.HML"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8528b4ce",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Download monthly adjusted close prices for specified stocks starting from 2000-01-01"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8518efcf",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = yf.download(\n",
|
||||
" ['SPY', 'MSFT', 'AAPL', 'INTC'], \n",
|
||||
" start=\"2000-01-01\", \n",
|
||||
" interval=\"1mo\"\n",
|
||||
")['Adj Close']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "439951e5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the monthly returns and convert them to period-based returns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9b300366",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"monthly_returns = data.pct_change().to_period(\"M\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "246d309b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Extract the benchmark returns (SPY) and calculate active returns against the benchmark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f71342e4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"bench = monthly_returns.pop(\"SPY\")\n",
|
||||
"R = monthly_returns.mean(axis=1)\n",
|
||||
"active = R - bench"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ff54a6cb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create a DataFrame with active returns and Fama-French factors SMB and HML"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d8ed7a12",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df = pd.DataFrame({\n",
|
||||
" 'R': active,\n",
|
||||
" 'F1': SMB,\n",
|
||||
" 'F2': HML,\n",
|
||||
"}).dropna()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3201423a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Perform Ordinary Least Squares (OLS) regression to estimate sensitivities to the factors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d5e1b15c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"b1, b2 = regression.linear_model.OLS(\n",
|
||||
" df.R, \n",
|
||||
" df[['F1', 'F2']]\n",
|
||||
").fit().params"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f621be1f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(f'Sensitivities of active returns to factors:\\nSMB: {b1}\\nHML: {b2}')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2eab25c3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Perform rolling OLS regression to estimate how factor sensitivities change over time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c2489be4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"exog_vars = [\"SMB\", \"HML\"]\n",
|
||||
"exog = sm.add_constant(factors[exog_vars])\n",
|
||||
"rols = RollingOLS(df.R, exog, window=12)\n",
|
||||
"rres = rols.fit()\n",
|
||||
"fig = rres.plot_recursive_coefficient(variables=exog_vars)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f4a1b059",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate covariance between factors and marginal contributions to active risk (MCAR) for each factor"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ad0355a8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"F1 = df.F1\n",
|
||||
"F2 = df.F2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6b0126e7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"cov = np.cov(F1, F2)\n",
|
||||
"ar_squared = (active.std())**2\n",
|
||||
"mcar1 = (b1 * (b2 * cov[0,1] + b1 * cov[0,0])) / ar_squared\n",
|
||||
"mcar2 = (b2 * (b1 * cov[0,1] + b2 * cov[1,1])) / ar_squared\n",
|
||||
"print(f'SMB risk contribution: {mcar1}')\n",
|
||||
"print(f'HML risk contribution: {mcar2}')\n",
|
||||
"print(f'Unexplained risk contribution: {1 - (mcar1 + mcar2)}')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "96df69b9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
188
to_explore/pyquantnews/25_KalmanFilter.ipynb
Normal file
188
to_explore/pyquantnews/25_KalmanFilter.ipynb
Normal file
@ -0,0 +1,188 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "120db895",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "98c6ae8b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code uses the Kalman Filter to smooth financial time series data and compare it to a simple moving average. It loads historical stock prices for a specified date range and computes the Kalman Filter estimate of the average price. It then plots the Kalman Filter estimate alongside the actual prices and a 30-day moving average. This is useful for financial analysis and modeling to reduce noise and detect underlying trends in stock prices."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2d864e60",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import matplotlib.pyplot as plt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d7c52f92",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pykalman import KalmanFilter\n",
|
||||
"from openbb_terminal.sdk import openbb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5fa2307c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Load historical stock prices for LMT between specified start and end dates"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "216b4c1a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = openbb.stocks.load(\"LMT\", start_date=\"2013-01-01\", end_date=\"2015-01-01\")\n",
|
||||
"prices = data[\"Adj Close\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f8486b4c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Initialize the Kalman Filter with specified parameters"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a262938f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"kf = KalmanFilter(\n",
|
||||
" transition_matrices = [1],\n",
|
||||
" observation_matrices = [1],\n",
|
||||
" initial_state_mean = 0,\n",
|
||||
" initial_state_covariance = 1,\n",
|
||||
" observation_covariance=1,\n",
|
||||
" transition_covariance=0.01\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "84830040",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Apply Kalman Filter to the price data to estimate the average. The filter smooths the data to reduce noise and detect trends. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "733d4019",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"state_means, _ = kf.filter(prices.values)\n",
|
||||
"state_means = pd.Series(state_means.flatten(), index=prices.index)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b3bc4dde",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Compute a 30-day rolling mean of the price data for comparison"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b96677e2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"mean30 = prices.rolling(window=30).mean()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7cf2307d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the Kalman Filter estimate, actual prices, and 30-day moving average to visualize data smoothing and trend detection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "42c88d04",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.plot(state_means)\n",
|
||||
"plt.plot(prices)\n",
|
||||
"plt.plot(mean30)\n",
|
||||
"plt.title('Kalman filter estimate of average')\n",
|
||||
"plt.legend(['Kalman', 'Price', '30-day MA'])\n",
|
||||
"plt.xlabel('Day')\n",
|
||||
"plt.ylabel('Price')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "726482c3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the last 200 days of Kalman Filter estimate, actual prices, and 30-day moving average for a more detailed view"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "71660a32",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.plot(state_means[-200:])\n",
|
||||
"plt.plot(prices[-200:])\n",
|
||||
"plt.plot(mean30[-200:])\n",
|
||||
"plt.title('Kalman filter estimate of average')\n",
|
||||
"plt.legend(['Kalman', 'Price', '30-day MA'])\n",
|
||||
"plt.xlabel('Day')\n",
|
||||
"plt.ylabel('Price')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fa717712",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
220
to_explore/pyquantnews/26_HurstExponent.ipynb
Normal file
220
to_explore/pyquantnews/26_HurstExponent.ipynb
Normal file
@ -0,0 +1,220 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cf1415df",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b9fd5ee6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This notebook analyzes the Hurst exponent of the S&P 500 index to measure market trends and randomness. It loads historical S&P 500 data using the OpenBB SDK and calculates the Hurst exponent for various time lags. The Hurst exponent helps in understanding the nature of time series, whether it is mean-reverting, trending, or a random walk. This information is valuable for financial analysts and quant traders for making informed decisions. Additionally, it plots rolling volatility to observe changes in market volatility over time."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d89701d0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c475ecc1",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 2
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from openbb_terminal.sdk import openbb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "883862a7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Load historical S&P 500 data from 2000 to 2019 using the OpenBB SDK and select the adjusted close prices"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "00be2ba2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df = openbb.stocks.load(\"^GSPC\", start_date=\"2000-01-01\", end_date=\"2019-12-31\")[\"Adj Close\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8dd5f150",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the S&P 500 adjusted close prices to visualize the historical data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ab7ba936",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df.plot(title=\"S&P 500\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9a9e6e7d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def get_hurst_exponent(ts, max_lag=20):\n",
|
||||
" \"\"\"Calculate the Hurst exponent of a time series\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" ts : np.ndarray\n",
|
||||
" Time series data\n",
|
||||
" max_lag : int, optional\n",
|
||||
" Maximum lag to consider, by default 20\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" float\n",
|
||||
" Estimated Hurst exponent\n",
|
||||
" \n",
|
||||
" Notes\n",
|
||||
" -----\n",
|
||||
" The Hurst exponent is used to determine the \n",
|
||||
" long-term memory of time series data.\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Define the range of lags to be used in the calculation\n",
|
||||
" lags = range(2, max_lag)\n",
|
||||
"\n",
|
||||
" # Calculate the standard deviation of differences for each lag\n",
|
||||
" tau = [np.std(np.subtract(ts[lag:], ts[:-lag])) for lag in lags]\n",
|
||||
"\n",
|
||||
" # Perform a linear fit to estimate the Hurst exponent\n",
|
||||
" return np.polyfit(np.log(lags), np.log(tau), 1)[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fc132efc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate and print the Hurst exponent for various lags using the full dataset"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7a1872d6",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 2
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for lag in [20, 100, 250, 500, 1000]:\n",
|
||||
" hurst_exp = get_hurst_exponent(df.values, lag)\n",
|
||||
" print(f\"{lag} lags: {hurst_exp:.4f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d51dc23f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Select a shorter series from 2005 to 2007 and calculate the Hurst exponent for various lags"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3cd6e2df",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 2
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"shorter_series = df.loc[\"2005\":\"2007\"].values\n",
|
||||
"for lag in [20, 100, 250, 500]:\n",
|
||||
" hurst_exp = get_hurst_exponent(shorter_series, lag)\n",
|
||||
" print(f\"{lag} lags: {hurst_exp:.4f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0a623fcf",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate rolling volatility using a 30-day window and plot the results to observe changes over time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "100403dc",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 2
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"rv = df.rolling(30).apply(np.std)\n",
|
||||
"rv.plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a021555b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate and print the Hurst exponent for various lags using the rolling volatility data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2175669f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for lag in [20, 100, 250, 500, 1000]:\n",
|
||||
" hurst_exp = get_hurst_exponent(rv.dropna().values, lag)\n",
|
||||
" print(f\"{lag} lags: {hurst_exp:.4f}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dfb39650",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
194
to_explore/pyquantnews/27_Decomposition.ipynb
Normal file
194
to_explore/pyquantnews/27_Decomposition.ipynb
Normal file
@ -0,0 +1,194 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f265ef79",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d7770571",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code retrieves and analyzes unemployment data, focusing on trends and seasonality. It uses the OpenBB SDK to fetch unemployment data from 2010 to 2019. The data is then processed to calculate rolling statistics and visualized. Seasonal decomposition and STL decomposition are applied to understand the seasonal and trend components. Additionally, the Hodrick-Prescott filter is used to separate the cyclical and trend components of the data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "60656fb6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1436ce1d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from statsmodels.tsa.seasonal import seasonal_decompose, STL\n",
|
||||
"from statsmodels.tsa.filters.hp_filter import hpfilter\n",
|
||||
"from openbb_terminal.sdk import openbb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "566d605d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Retrieve unemployment data from OpenBB SDK for the period starting 2010"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "df30c6a4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df = openbb.economy.unemp(2010)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e7a42626",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Set the index to the 'date' column, filter up to 2019-12-31, and sort by date"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cbcf68cf",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df = df.set_index(\"date\")[:\"2019-12-31\"].sort_index()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9b4b91cd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate rolling mean and standard deviation with a 12-month window"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e28f9215",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df[\"rolling_mean\"] = df[\"unemp\"].rolling(window=12).mean()\n",
|
||||
"df[\"rolling_std\"] = df[\"unemp\"].rolling(window=12).std()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e5863458",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the unemployment rate with rolling mean and standard deviation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c10e8025",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df.plot(title=\"Unemployment rate\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "17630ce4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Perform seasonal decomposition of the unemployment data using an additive model and plot results"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "52f48f1d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"decomposition_results = seasonal_decompose(\n",
|
||||
" df[\"unemp\"], \n",
|
||||
" model=\"additive\"\n",
|
||||
").plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "47f1d516",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Apply STL decomposition to the unemployment data and plot the results"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a75ed137",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"stl_decomposition = STL(df[[\"unemp\"]]).fit()\n",
|
||||
"stl_decomposition.plot().suptitle(\"STL Decomposition\");"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b61552be",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Apply Hodrick-Prescott filter to decompose the unemployment data into cycle and trend components and plot results"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "36c7a50e",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 2
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"hp_df = df[[\"unemp\"]].copy()\n",
|
||||
"hp_df[\"cycle\"], hp_df[\"trend\"] = hpfilter(hp_df[\"unemp\"], 129600)\n",
|
||||
"hp_df.plot(subplots=True, title=\"Hodrick-Prescott filter\");"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c508c532",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
339
to_explore/pyquantnews/28_FlowEffectsBacktrader.ipynb
Normal file
339
to_explore/pyquantnews/28_FlowEffectsBacktrader.ipynb
Normal file
@ -0,0 +1,339 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "04574bc2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8c027594",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This notebook demonstrates backtesting a trading strategy using Backtrader and OpenBB SDK for data acquisition. It outlines creating a trading strategy and running backtests to simulate performance over historical data. The code includes setting up a backtest, downloading data, defining strategy logic, and assessing results with performance metrics. This is useful for evaluating trading strategies' robustness and optimizing them for better risk-adjusted returns."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3bee1431",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import datetime as dt\n",
|
||||
"import pandas as pd\n",
|
||||
"from openbb_terminal.sdk import openbb\n",
|
||||
"import quantstats as qs\n",
|
||||
"import backtrader as bt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c0e61059",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a function to fetch stock data from OpenBB SDK and convert it for Backtrader use"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "132f6e66",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def openbb_data_to_bt_data(symbol, start_date, end_date):\n",
|
||||
" \"\"\"Fetch and convert stock data for Backtrader\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" symbol : str\n",
|
||||
" Stock symbol to fetch\n",
|
||||
" start_date : str\n",
|
||||
" Start date for data in 'YYYY-MM-DD' format\n",
|
||||
" end_date : str\n",
|
||||
" End date for data in 'YYYY-MM-DD' format\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" bt.feeds.YahooFinanceCSVData\n",
|
||||
" Formatted data for Backtrader\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" # Fetch stock data from OpenBB SDK\n",
|
||||
" df = openbb.stocks.load(symbol, start_date=start_date, end_date=end_date)\n",
|
||||
"\n",
|
||||
" # Save data to a CSV file\n",
|
||||
" fn = f\"{symbol.lower()}.csv\"\n",
|
||||
" df.to_csv(fn)\n",
|
||||
" \n",
|
||||
" # Return data formatted for Backtrader\n",
|
||||
" return bt.feeds.YahooFinanceCSVData(\n",
|
||||
" dataname=fn,\n",
|
||||
" fromdate=dt.datetime.strptime(start_date, '%Y-%m-%d'),\n",
|
||||
" todate=dt.datetime.strptime(end_date, '%Y-%m-%d')\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6c3c0564",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Determine the last day of a given month"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cc17ebfc",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def last_day_of_month(any_day):\n",
|
||||
" \"\"\"Calculate the last day of the month\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" any_day : datetime.date\n",
|
||||
" Any date within the target month\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" int\n",
|
||||
" Last day of the month\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" # Move to the next month and then back to the last day of the current month\n",
|
||||
" next_month = any_day.replace(day=28) + dt.timedelta(days=4)\n",
|
||||
" return (next_month - dt.timedelta(days=next_month.day)).day"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "64b04705",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a trading strategy to execute monthly flows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0729cd4a",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class MonthlyFlows(bt.Strategy):\n",
|
||||
" \"\"\"Strategy to trade based on monthly flows\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" end_of_month : int, optional\n",
|
||||
" Day of the month to start buying (default is 23)\n",
|
||||
" start_of_month : int, optional\n",
|
||||
" Day of the month to start selling (default is 7)\n",
|
||||
" \n",
|
||||
" Attributes\n",
|
||||
" ----------\n",
|
||||
" order : bt.Order or None\n",
|
||||
" Current order in the market\n",
|
||||
" dataclose : bt.LineSeries\n",
|
||||
" Closing prices of the data feed\n",
|
||||
" \"\"\"\n",
|
||||
" \n",
|
||||
" params = (\n",
|
||||
" (\"end_of_month\", 23),\n",
|
||||
" (\"start_of_month\", 7),\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" def __init__(self):\n",
|
||||
" self.order = None\n",
|
||||
" self.dataclose = self.datas[0].close\n",
|
||||
" \n",
|
||||
" def notify_order(self, order):\n",
|
||||
" \"\"\"Handle order notifications\"\"\"\n",
|
||||
" \n",
|
||||
" # No more orders\n",
|
||||
" self.order = None \n",
|
||||
" \n",
|
||||
" def next(self):\n",
|
||||
" \"\"\"Execute strategy logic for each step in the backtest\"\"\"\n",
|
||||
" \n",
|
||||
" # Get today's date, day of month, and last day of current month\n",
|
||||
" dt_ = self.datas[0].datetime.date(0)\n",
|
||||
" dom = dt_.day\n",
|
||||
" ldm = last_day_of_month(dt_)\n",
|
||||
" \n",
|
||||
" # If an order is pending, exit\n",
|
||||
" if self.order:\n",
|
||||
" return\n",
|
||||
" \n",
|
||||
" # Check if we are in the market\n",
|
||||
" if not self.position:\n",
|
||||
" \n",
|
||||
" # We're in the first week of the month, sell\n",
|
||||
" if dom <= self.params.start_of_month:\n",
|
||||
" self.order = self.order_target_percent(target=-1)\n",
|
||||
" print(f\"Created SELL of {self.order.size} at {self.data_close[0]} on day {dom}\")\n",
|
||||
" \n",
|
||||
" # We're in the last week of the month, buy\n",
|
||||
" if dom >= self.params.end_of_month:\n",
|
||||
" self.order = self.order_target_percent(target=1)\n",
|
||||
" print(f\"Created BUY of {self.order.size} {self.data_close[0]} on day {dom}\")\n",
|
||||
" \n",
|
||||
" # We are not in the market\n",
|
||||
" else:\n",
|
||||
" \n",
|
||||
" # If we're long\n",
|
||||
" if self.position.size > 0:\n",
|
||||
" if not self.params.end_of_month <= dom <= ldm:\n",
|
||||
" print(f\"Created CLOSE of {self.position.size} at {self.data_close[0]} on day {dom}\")\n",
|
||||
" self.order = self.order_target_percent(target=0.0)\n",
|
||||
" \n",
|
||||
" # If we're short\n",
|
||||
" if self.position.size < 0:\n",
|
||||
" if not 1 <= dom <= self.params.start_of_month:\n",
|
||||
" print(f\"Created CLOSE of {self.position.size} at {self.data_close[0]} on day {dom}\")\n",
|
||||
" self.order = self.order_target_percent(target=0.0)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e8fbbc83",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Run the strategy using Backtrader"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b47c7a71",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = openbb_data_to_bt_data(\"TLT\", start_date=\"2002-01-01\", end_date=\"2022-06-30\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d53cbfee",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"cerebro = bt.Cerebro(stdstats=False)\n",
|
||||
"cerebro.adddata(data)\n",
|
||||
"cerebro.broker.setcash(1000.0)\n",
|
||||
"cerebro.addstrategy(MonthlyFlows)\n",
|
||||
"cerebro.addobserver(bt.observers.Value)\n",
|
||||
"cerebro.addanalyzer(bt.analyzers.Returns, _name=\"returns\")\n",
|
||||
"cerebro.addanalyzer(bt.analyzers.TimeReturn, _name=\"time_return\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "94ea3665",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"backtest_result = cerebro.run()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "257cc57c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Convert backtest results into a pandas DataFrame"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "27a1ec64",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns_dict = backtest_result[0].analyzers.time_return.get_analysis()\n",
|
||||
"returns_df = (\n",
|
||||
" pd.DataFrame(\n",
|
||||
" list(returns_dict.items()),\n",
|
||||
" columns=[\"date\", \"return\"]\n",
|
||||
" )\n",
|
||||
" .set_index(\"date\")\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7b6e5887",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Fetch benchmark data for comparison"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "86c540be",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"bench = openbb.stocks.load(\"TLT\", start_date=\"2002-01-01\", end_date=\"2022-06-30\")[\"Adj Close\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3c39d605",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Assess results using QuantStats metrics"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2dec6aa2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"qs.reports.metrics(\n",
|
||||
" returns_df,\n",
|
||||
" benchmark=bench,\n",
|
||||
" mode=\"full\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5b590090",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
539
to_explore/pyquantnews/2_RealizedVolatilityModels.ipynb
Normal file
539
to_explore/pyquantnews/2_RealizedVolatilityModels.ipynb
Normal file
@ -0,0 +1,539 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c599b573",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "88633890",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code calculates various methods of historical volatility from stock price data. It downloads historical prices for Apple Inc. (AAPL) and computes returns. The code then defines functions to compute volatility using different models: Parkinson, Garman-Klass, Hodges-Tompkins, Rogers-Satchell, Yang-Zhang, and standard deviation. Each model is applied to the data, and the results are plotted for visualization. This is useful for risk management, option pricing, and financial analysis."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d1d67415",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import math"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a2a29969",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import yfinance as yf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8da6c98e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Download historical stock data for Apple Inc. (AAPL) from Yahoo Finance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4f2be4ee",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = yf.download(\"AAPL\", start=\"2017-01-01\", end=\"2022-06-30\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7b8097d1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Display the downloaded stock data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9c28f735",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3677bc76",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate daily returns from adjusted closing prices"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0713eb09",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"returns = (data[\"Adj Close\"] / data[\"Adj Close\"].shift(1)) - 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "03de5a56",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the adjusted closing prices over time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "615a1ff9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data[\"Adj Close\"].plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "297e9737",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def parkinson(price_data, window=30, trading_periods=252, clean=True):\n",
|
||||
" \"\"\"Calculate volatility using the Parkinson model\n",
|
||||
"\n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" price_data : DataFrame\n",
|
||||
" Historical stock price data\n",
|
||||
" window : int, optional\n",
|
||||
" Rolling window size in days (default is 30)\n",
|
||||
" trading_periods : int, optional\n",
|
||||
" Number of trading periods per year (default is 252)\n",
|
||||
" clean : bool, optional\n",
|
||||
" Whether to drop NaN values in the result (default is True)\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" Series\n",
|
||||
" Parkinson volatility\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Calculate the Parkinson volatility estimate using high and low prices\n",
|
||||
" rs = (1.0 / (4.0 * math.log(2.0))) * (\n",
|
||||
" (price_data[\"High\"] / price_data[\"Low\"]).apply(np.log)\n",
|
||||
" ) ** 2.0\n",
|
||||
"\n",
|
||||
" # Define a function to apply the rolling mean and scale by trading periods\n",
|
||||
" def f(v):\n",
|
||||
" return (trading_periods * v.mean()) ** 0.5\n",
|
||||
"\n",
|
||||
" # Apply the rolling window calculation to the Parkinson estimate\n",
|
||||
" result = rs.rolling(window=window, center=False).apply(func=f)\n",
|
||||
"\n",
|
||||
" # Return the cleaned result or the full result based on the 'clean' parameter\n",
|
||||
" if clean:\n",
|
||||
" return result.dropna()\n",
|
||||
" else:\n",
|
||||
" return result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "60f29474",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot Parkinson volatility over time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "31a319a3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"parkinson(data).plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4ac1610e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def garman_klass(price_data, window=30, trading_periods=252, clean=True):\n",
|
||||
" \"\"\"Calculate volatility using the Garman-Klass model\n",
|
||||
"\n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" price_data : DataFrame\n",
|
||||
" Historical stock price data\n",
|
||||
" window : int, optional\n",
|
||||
" Rolling window size in days (default is 30)\n",
|
||||
" trading_periods : int, optional\n",
|
||||
" Number of trading periods per year (default is 252)\n",
|
||||
" clean : bool, optional\n",
|
||||
" Whether to drop NaN values in the result (default is True)\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" Series\n",
|
||||
" Garman-Klass volatility\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Calculate log returns of high/low and close/open prices\n",
|
||||
" log_hl = (price_data[\"High\"] / price_data[\"Low\"]).apply(np.log)\n",
|
||||
" log_co = (price_data[\"Close\"] / price_data[\"Open\"]).apply(np.log)\n",
|
||||
"\n",
|
||||
" # Compute the Garman-Klass volatility estimate\n",
|
||||
" rs = 0.5 * log_hl**2 - (2 * math.log(2) - 1) * log_co**2\n",
|
||||
"\n",
|
||||
" # Define a function to apply the rolling mean and scale by trading periods\n",
|
||||
" def f(v):\n",
|
||||
" return (trading_periods * v.mean()) ** 0.5\n",
|
||||
"\n",
|
||||
" # Apply the rolling window calculation to the Garman-Klass estimate\n",
|
||||
" result = rs.rolling(window=window, center=False).apply(func=f)\n",
|
||||
"\n",
|
||||
" # Return the cleaned result or the full result based on the 'clean' parameter\n",
|
||||
" if clean:\n",
|
||||
" return result.dropna()\n",
|
||||
" else:\n",
|
||||
" return result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "01168fcc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot Garman-Klass volatility over time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "88a8d0d0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"garman_klass(data).plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2011bdea",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def hodges_tompkins(price_data, window=30, trading_periods=252, clean=True):\n",
|
||||
" \"\"\"Calculate volatility using the Hodges-Tompkins model\n",
|
||||
"\n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" price_data : DataFrame\n",
|
||||
" Historical stock price data\n",
|
||||
" window : int, optional\n",
|
||||
" Rolling window size in days (default is 30)\n",
|
||||
" trading_periods : int, optional\n",
|
||||
" Number of trading periods per year (default is 252)\n",
|
||||
" clean : bool, optional\n",
|
||||
" Whether to drop NaN values in the result (default is True)\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" Series\n",
|
||||
" Hodges-Tompkins volatility\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Calculate log returns of closing prices\n",
|
||||
" log_return = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n",
|
||||
"\n",
|
||||
" # Compute the rolling standard deviation and scale by trading periods\n",
|
||||
" vol = log_return.rolling(window=window, center=False).std() * math.sqrt(\n",
|
||||
" trading_periods\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" # Calculate adjustment factors based on window size and sample size\n",
|
||||
" h = window\n",
|
||||
" n = (log_return.count() - h) + 1\n",
|
||||
"\n",
|
||||
" adj_factor = 1.0 / (1.0 - (h / n) + ((h**2 - 1) / (3 * n**2)))\n",
|
||||
"\n",
|
||||
" # Apply the adjustment factor to the volatility estimate\n",
|
||||
" result = vol * adj_factor\n",
|
||||
"\n",
|
||||
" # Return the cleaned result or the full result based on the 'clean' parameter\n",
|
||||
" if clean:\n",
|
||||
" return result.dropna()\n",
|
||||
" else:\n",
|
||||
" return result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c4349cdc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot Hodges-Tompkins volatility over time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b4824347",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"hodges_tompkins(data).plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5c6305c6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def rogers_satchell(price_data, window=30, trading_periods=252, clean=True):\n",
|
||||
" \"\"\"Calculate volatility using the Rogers-Satchell model\n",
|
||||
"\n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" price_data : DataFrame\n",
|
||||
" Historical stock price data\n",
|
||||
" window : int, optional\n",
|
||||
" Rolling window size in days (default is 30)\n",
|
||||
" trading_periods : int, optional\n",
|
||||
" Number of trading periods per year (default is 252)\n",
|
||||
" clean : bool, optional\n",
|
||||
" Whether to drop NaN values in the result (default is True)\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" Series\n",
|
||||
" Rogers-Satchell volatility\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Calculate log returns of high/open, low/open, and close/open prices\n",
|
||||
" log_ho = (price_data[\"High\"] / price_data[\"Open\"]).apply(np.log)\n",
|
||||
" log_lo = (price_data[\"Low\"] / price_data[\"Open\"]).apply(np.log)\n",
|
||||
" log_co = (price_data[\"Close\"] / price_data[\"Open\"]).apply(np.log)\n",
|
||||
"\n",
|
||||
" # Compute the Rogers-Satchell volatility estimate\n",
|
||||
" rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)\n",
|
||||
"\n",
|
||||
" # Define a function to apply the rolling mean and scale by trading periods\n",
|
||||
" def f(v):\n",
|
||||
" return (trading_periods * v.mean()) ** 0.5\n",
|
||||
"\n",
|
||||
" # Apply the rolling window calculation to the Rogers-Satchell estimate\n",
|
||||
" result = rs.rolling(window=window, center=False).apply(func=f)\n",
|
||||
"\n",
|
||||
" # Return the cleaned result or the full result based on the 'clean' parameter\n",
|
||||
" if clean:\n",
|
||||
" return result.dropna()\n",
|
||||
" else:\n",
|
||||
" return result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bea6c84e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot Rogers-Satchell volatility over time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2887f9a0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"rogers_satchell(data).plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "06167183",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def yang_zhang(price_data, window=30, trading_periods=252, clean=True):\n",
|
||||
" \"\"\"Calculate volatility using the Yang-Zhang model\n",
|
||||
"\n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" price_data : DataFrame\n",
|
||||
" Historical stock price data\n",
|
||||
" window : int, optional\n",
|
||||
" Rolling window size in days (default is 30)\n",
|
||||
" trading_periods : int, optional\n",
|
||||
" Number of trading periods per year (default is 252)\n",
|
||||
" clean : bool, optional\n",
|
||||
" Whether to drop NaN values in the result (default is True)\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" Series\n",
|
||||
" Yang-Zhang volatility\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Calculate log returns of high/open, low/open, close/open, open/close, and close/close prices\n",
|
||||
" log_ho = (price_data[\"High\"] / price_data[\"Open\"]).apply(np.log)\n",
|
||||
" log_lo = (price_data[\"Low\"] / price_data[\"Open\"]).apply(np.log)\n",
|
||||
" log_co = (price_data[\"Close\"] / price_data[\"Open\"]).apply(np.log)\n",
|
||||
"\n",
|
||||
" log_oc = (price_data[\"Open\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n",
|
||||
" log_oc_sq = log_oc**2\n",
|
||||
"\n",
|
||||
" log_cc = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n",
|
||||
" log_cc_sq = log_cc**2\n",
|
||||
"\n",
|
||||
" # Compute the Rogers-Satchell volatility estimate\n",
|
||||
" rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)\n",
|
||||
"\n",
|
||||
" # Compute close-to-close and open-to-close volatilities\n",
|
||||
" close_vol = log_cc_sq.rolling(window=window, center=False).sum() * (\n",
|
||||
" 1.0 / (window - 1.0)\n",
|
||||
" )\n",
|
||||
" open_vol = log_oc_sq.rolling(window=window, center=False).sum() * (\n",
|
||||
" 1.0 / (window - 1.0)\n",
|
||||
" )\n",
|
||||
" window_rs = rs.rolling(window=window, center=False).sum() * (1.0 / (window - 1.0))\n",
|
||||
"\n",
|
||||
" # Calculate the weighting factor 'k'\n",
|
||||
" k = 0.34 / (1.34 + (window + 1) / (window - 1))\n",
|
||||
"\n",
|
||||
" # Compute the final Yang-Zhang volatility estimate\n",
|
||||
" result = (open_vol + k * close_vol + (1 - k) * window_rs).apply(\n",
|
||||
" np.sqrt\n",
|
||||
" ) * math.sqrt(trading_periods)\n",
|
||||
"\n",
|
||||
" # Return the cleaned result or the full result based on the 'clean' parameter\n",
|
||||
" if clean:\n",
|
||||
" return result.dropna()\n",
|
||||
" else:\n",
|
||||
" return result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "71ed8302",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot Yang-Zhang volatility over time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "40717ed4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"yang_zhang(data).plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e64bbc96",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def standard_deviation(price_data, window=30, trading_periods=252, clean=True):\n",
|
||||
" \"\"\"Calculate volatility using standard deviation of log returns\n",
|
||||
"\n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" price_data : DataFrame\n",
|
||||
" Historical stock price data\n",
|
||||
" window : int, optional\n",
|
||||
" Rolling window size in days (default is 30)\n",
|
||||
" trading_periods : int, optional\n",
|
||||
" Number of trading periods per year (default is 252)\n",
|
||||
" clean : bool, optional\n",
|
||||
" Whether to drop NaN values in the result (default is True)\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" Series\n",
|
||||
" Standard deviation volatility\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Calculate log returns of closing prices\n",
|
||||
" log_return = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n",
|
||||
"\n",
|
||||
" # Compute the rolling standard deviation and scale by trading periods\n",
|
||||
" result = log_return.rolling(window=window, center=False).std() * math.sqrt(\n",
|
||||
" trading_periods\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" # Return the cleaned result or the full result based on the 'clean' parameter\n",
|
||||
" if clean:\n",
|
||||
" return result.dropna()\n",
|
||||
" else:\n",
|
||||
" return result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "86c10b8e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot standard deviation volatility over time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "45c9f814",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"standard_deviation(data).plot()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "243a9283",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
361
to_explore/pyquantnews/30_AnimatedYieldCurve.ipynb
Normal file
361
to_explore/pyquantnews/30_AnimatedYieldCurve.ipynb
Normal file
@ -0,0 +1,361 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8a2b2530",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "70c08f76",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code retrieves U.S. Treasury bond yield data using the OpenBB SDK and visualizes the yield curve over time. It sets up the necessary imports, initializes plotting parameters, and configures the OpenBB SDK with a FRED API key. The script extracts treasury yield data for various maturities and indicates whether the yield curve is inverted. An animation is created to dynamically display the yield curve changes over time, highlighting inversions in red."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f47685af",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import matplotlib.animation as animation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "876b4eac",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from openbb_terminal.sdk import openbb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "77931a95",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define font properties for the plot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "21676002",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"font = {\n",
|
||||
" \"family\": \"normal\",\n",
|
||||
" \"weight\": \"normal\",\n",
|
||||
" \"size\": 12\n",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b8a309f7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.rc('font', **font)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "24fa7787",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Configure OpenBB SDK with FRED API key"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3632ee19",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"openbb.keys.fred(\n",
|
||||
" key=\"3d20c1fcbb26ea21b9f78fafbbdce900\",\n",
|
||||
" persist=True,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8bad0715",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define treasury bond maturities to be retrieved"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ab0acba1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"maturities = ['3m', '6m', '1y', '2y', '3y', '5y', '7y', '10y', '30y']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "717bade2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Retrieve treasury bond yield data from OpenBB SDK for specified maturities and time range"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "55e6010a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = openbb.economy.treasury(\n",
|
||||
" instruments=[\"nominal\"],\n",
|
||||
" maturities=maturities,\n",
|
||||
" start_date=\"1985-01-01\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4edab174",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Rename columns to match maturities list"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "96be72bd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data.columns = maturities"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ad0285d8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Add a column to indicate if yield curve is inverted (30y yield less than 3m yield)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b852c95a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data[\"inverted\"] = data[\"30y\"] < data[\"3m\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3ee1c2ee",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Initialize plot figure and axis"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6a134e05",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = plt.figure()\n",
|
||||
"ax = fig.add_subplot(1, 1, 1)\n",
|
||||
"line, = ax.plot([], [])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2c115cca",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Set x and y-axis limits for the plot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c552c60f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ax.set_xlim(0, 7)\n",
|
||||
"ax.set_ylim(0, 20)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6014ed6b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define tick locations and labels for both axes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "24cb514e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ax.set_xticks(range(8))\n",
|
||||
"ax.set_yticks([2, 4, 6, 8, 10, 12, 14, 16, 18])\n",
|
||||
"ax.set_xticklabels([\"1m\",\"3m\",\"6m\",\"1y\",\"5y\",\"10y\",\"20y\",\"30y\"])\n",
|
||||
"ax.set_yticklabels([2, 4, 6, 8, 10, 12, 14, 16, 18])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9c3581ec",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Force y-axis labels to appear on the left side"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "af5728b1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ax.yaxis.set_label_position(\"left\")\n",
|
||||
"ax.yaxis.tick_left()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e34a8998",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Add labels for both axes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0e470b81",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.ylabel(\"Yield (%)\")\n",
|
||||
"plt.xlabel(\"Time to maturty\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "25541eb8",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def init_func():\n",
|
||||
" \"\"\"Initialize plot with empty data and title\"\"\"\n",
|
||||
" line.set_data([], [])\n",
|
||||
" plt.title(\"U.S. Treasury Bond Yield Curve\")\n",
|
||||
" return line"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c3826d2c",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def animate(i):\n",
|
||||
" \"\"\"Update plot data for each frame in the animation\n",
|
||||
" \n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" i : int\n",
|
||||
" Current frame index\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" line : Line2D object\n",
|
||||
" \"\"\"\n",
|
||||
" x = range(0, len(maturities))\n",
|
||||
" y = data[maturities].iloc[i]\n",
|
||||
" dt_ = data.index[i].strftime(\"%Y-%m-%d\")\n",
|
||||
" \n",
|
||||
" # Change line color based on yield curve inversion\n",
|
||||
" if data.inverted.iloc[i]:\n",
|
||||
" line.set_color(\"r\")\n",
|
||||
" else:\n",
|
||||
" line.set_color(\"y\")\n",
|
||||
" \n",
|
||||
" line.set_data(x, y)\n",
|
||||
" \n",
|
||||
" plt.title(f\"U.S. Treasury Bond Yield Curve ({dt_})\")\n",
|
||||
" return line,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "144c195f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Create animation for the yield curve using the animate function"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7ccd9ff8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ani = animation.FuncAnimation(\n",
|
||||
" fig, animate, init_func=init_func, frames=len(data.index), interval=5, blit=True\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "305bb8bf",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
242
to_explore/pyquantnews/31_BarrierOptionPricing.ipynb
Normal file
242
to_explore/pyquantnews/31_BarrierOptionPricing.ipynb
Normal file
@ -0,0 +1,242 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fa749e17",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "58737b4a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This code simulates stock returns using Geometric Brownian Motion (GBM) and calculates the premium for a barrier option. It defines a function to simulate GBM paths based on initial stock price, drift, volatility, and other parameters. The code then generates multiple simulated price paths, checks if the maximum value of each path exceeds a barrier, and calculates the option payoff accordingly. Lastly, it discounts the payoffs to present value and computes the average premium. This is useful for pricing exotic financial derivatives and analyzing risk."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ecc8c484",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import matplotlib.pyplot as plt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "748351bc",
|
||||
"metadata": {
|
||||
"lines_to_next_cell": 1
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def simulate_gbm(s_0, mu, sigma, T, N, n_sims=10**3, random_seed=42):\n",
|
||||
" \"\"\"\n",
|
||||
" Function used for simulating stock returns using Geometric Brownian Motion.\n",
|
||||
"\n",
|
||||
" Parameters\n",
|
||||
" ------------\n",
|
||||
" s_0 : float\n",
|
||||
" Initial stock price\n",
|
||||
" mu : float\n",
|
||||
" Drift coefficient\n",
|
||||
" sigma : float\n",
|
||||
" Diffusion coefficient\n",
|
||||
" T : float\n",
|
||||
" Length of the forecast horizon, same unit as dt\n",
|
||||
" N : int\n",
|
||||
" Number of time increments in the forecast horizon\n",
|
||||
" n_sims : int\n",
|
||||
" Number of simulation paths\n",
|
||||
" random_seed : int\n",
|
||||
" Random seed for reproducibility\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -----------\n",
|
||||
" S_t : np.ndarray\n",
|
||||
" Matrix (size: n_sims x (T+1)) containing the simulation results.\n",
|
||||
" Rows represent sample paths, while columns point of time.\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" # Set random seed for reproducibility\n",
|
||||
" np.random.seed(random_seed)\n",
|
||||
"\n",
|
||||
" # Calculate time increment\n",
|
||||
" dt = T / N\n",
|
||||
"\n",
|
||||
" # Generate normally distributed random values for the Wiener process\n",
|
||||
" dW = np.random.normal(scale=np.sqrt(dt), size=(n_sims, N + 1))\n",
|
||||
"\n",
|
||||
" # Simulate the evolution of the process using GBM formula\n",
|
||||
" S_t = s_0 * np.exp(np.cumsum((mu - 0.5 * sigma**2) * dt + sigma * dW, axis=1))\n",
|
||||
" S_t[:, 0] = s_0\n",
|
||||
"\n",
|
||||
" return S_t"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e67fab55",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define initial stock price, drift, volatility, time horizon, and number of increments"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "96268bc8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"S_0 = 55\n",
|
||||
"r = 0.06\n",
|
||||
"sigma = 0.2\n",
|
||||
"T = 1\n",
|
||||
"N = 252"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "be23773e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define barrier level and strike price for the option"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "30090621",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BARRIER = 65\n",
|
||||
"K = 60"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "126163f8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Generate GBM simulations for the given parameters"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "227001bf",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"gbm_sims = simulate_gbm(s_0=S_0, mu=r, sigma=sigma, T=T, N=N)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6c5abfb3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Plot the simulated price paths and the barrier level"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b718dafd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plt.axhline(y=BARRIER, color='r', linestyle='-')\n",
|
||||
"plt.xlim(0, N)\n",
|
||||
"plt.plot(gbm_sims.T, linewidth=0.25)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "58aed041",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the maximum value per path to determine if the barrier was breached"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7fc85776",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"max_value_per_path = np.max(gbm_sims, axis=1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "589b129d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the payoff of the barrier option based on the barrier breach condition"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "685b4103",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"payoff = np.where(\n",
|
||||
" max_value_per_path > BARRIER, \n",
|
||||
" np.maximum(0, gbm_sims[:, -1] - K), \n",
|
||||
" 0\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "74527d4a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Calculate the discount factor and the average premium for the option"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0c654f62",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"discount_factor = np.exp(-r * T)\n",
|
||||
"premium = discount_factor * np.mean(payoff)\n",
|
||||
"premium"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "51efd6b6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"jupytext": {
|
||||
"cell_metadata_filter": "-all",
|
||||
"main_language": "python",
|
||||
"notebook_metadata_filter": "-all"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user