| Overall Statistics |
|
Total Trades 0 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Net Profit 0% Sharpe Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio -2.031 Tracking Error 0.142 Treynor Ratio 0 Total Fees $0.00 Estimated Strategy Capacity $0 |
'''
MIT License
Copyright (c) 2021 Ostirion.net
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
'''
class EmptyAlgoToShareNotebooks(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020,12,1) # Set Start Date
self.SetCash(1) # Set Strategy Cash
def OnData(self, data):
pass# (c) 2021 Ostirion.net
# This code is licensed under MIT license (see LICENSE for details)
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import seaborn as sns
import base64
import matplotlib.image as image
import matplotlib.gridspec as gridspec
# Small Ostirion Logo as PNG string:
SMALL_LOGO = "iVBORw0KGgoAAAANSUhEUgAAADAAAAAqCAYAAAD1T9h6AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAK6SURBVGhD7ZnPavpAEMdXDx4EqYiIqO3JFxAVwb8HvVjPfQGfwENBKHjTS8GXaC0iePOmqEfFfy8gFIroTURBqKJNTX5j0WY2Was1v4If+Mpk4szsaHbJJipCCLfRn+UkDVitVnJ7e0uur68FGQwGYjQa4eymiEpFFosFmU6nZDQakX6/TwaDASkWi/CN4+AbOFjxeJyr1+vcKcjn81wsFkPrMAh1UvXy8gJlf4dMJoPWlRDqFCmVSkGJ8xAIBNBxIEKdeyqXy5D2vNzd3aHj+SbU+aVsNgvplMHpdKLj2kpyFbJYLGQ4HMIRO7PZTIibz+fCCsRzdXVF7Ha7YB/KNgcNUVdb9Xo9+B3keXh44HQ6HZrnuzweD1coFCBSnvv7ezQPCHUKYqHb7aKxLNJqtZBFmuVyicaDUKcwgVjAYg8Rf42zgMXyUm8+UBwOB1h0np+fwfo5m38QLGlo46E2YDabwaLDWlyO1WoFFh2NRgPWPtQG1us1WHRYCrOgVlOH8cW/q0iMfKQEcsvbOTiqgf+BSwNKc2lAaS4NKM2lAaW5NKA0lwaUhtoAy53mx8cHWMpBbYBlP6DX68E6DpZ9BW3PQG1gPB6DRSccDoN1HLTd1i6TyQQsMaKNMi/+YSsLJpMJjWdVOp2GTNJgsSDUKYiVRCKBxsspl8tBBmlarRYaz0vyyVylUiGRSASO5Hl7eyONRoO0223hEnx/fxcWA36y63Q64Umfy+UiwWDwoPnjdrtJp9OBIzGirnalNLVaDR3XjlDnl0KhEKQ6P7PZDB3TN6HOPfn9fkh5PqrVKjoWRKgTValUgvS/x2bucF6vF61PEeqUVDKZ5F5fX6HkaXh8fORubm7QelKSXIVYiEajxOfzCeKf/9tsNjiDw7+p5N9S8itVs9kkT09PcOZnHN2A0vzxu1FCPgGAb5goqktPowAAAABJRU5ErkJggg=="
imgdata = base64.b64decode(SMALL_LOGO)
filename = 'small_logo.png'
# Remove comments to use notebook plots:
'''with o#pen(filename, 'wb') as f:
f.write(imgdata)'''
def plot_df(df, color='blue', size=(16, 7), legend='Close Price', y_label='Price in USD', title=None, kind='line'):
im = image.imread(filename)
plt.style.use('dark_background')
plt.rcParams["figure.figsize"] = size
ax = df.plot(kind=kind, color=color)
ax.figure.figimage(im, 0, 0, alpha=1.0, zorder=1)
plt.title(title)
plt.ylabel(y_label)
x = 0.01
y = 0.01
plt.text(x, y, 'www.ostirion.net', fontsize=15, transform=ax.transAxes)
plt.legend(ncol=int(len(df.columns) / 2))
date_form = mdates.DateFormatter("%m-%Y")
plt.xticks(rotation=45);
plt.show()
def plot_corr_hm(df, title='Title', size=(16, 7), annot = True):
corr = df.corr()
plt.style.use('dark_background')
plt.rcParams["figure.figsize"] = size
mask = np.triu(np.ones_like(corr, dtype=bool))
cmap = sns.color_palette("RdBu")
ax = sns.heatmap(corr, mask=mask, vmax=.3, center=0, cmap=cmap, annot=annot,
square=True, linewidths=0, cbar_kws={"shrink": .5}, fmt='g')
ax.set_title(title)
plt.setp(ax.get_yticklabels(), rotation=0);
plt.setp(ax.get_xticklabels(), rotation=90);
plt.show()
def plot_cm(df, title='Title', size=(16,7)):
plt.style.use('dark_background')
plt.rcParams["figure.figsize"] = size
cmap = sns.color_palette("Blues")
ax = sns.heatmap(df, cmap=cmap, annot=True, linewidths=0, cbar_kws={"shrink": .5}, fmt='g')
ax.set_title(title)
plt.xlabel('Predicted')
plt.ylabel('True')
plt.setp(ax.get_xticklabels(), rotation=0);
def plot_hm(df, title='Title', size=(16, 7), annot = True, x_rot=90):
plt.style.use('dark_background')
plt.rcParams["figure.figsize"] = size
cmap = sns.color_palette("RdBu")
ax = sns.heatmap(df, vmax=.3, center=0, cmap=cmap, annot=annot,
square=True, linewidths=0, cbar_kws={"shrink": .5}, fmt='g')
ax.set_title(title)
plt.setp(ax.get_yticklabels(), rotation=0);
plt.setp(ax.get_xticklabels(), rotation=x_rot);
plt.show()# (c) 2021 Ostirion.net
# This code is licensed under MIT license (see LICENSE for details)
import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import adfuller
from scipy.stats import entropy
def compute_weights(d: float,
size: int) -> pd.DataFrame:
'''
Compute the weights of individual data points
for fractional differentiation:
Args:
d (float): Fractional differentiation value.
size (int): Length of the data series.
Returns:
pd.DataFrame: Dataframe containing the weights for each point.
'''
w = [1.0]
for k in range(1, size):
w.append(-w[-1]/k*(d-k+1))
w = np.array(w[::-1]).reshape(-1, 1)
return pd.DataFrame(w)
def standard_frac_diff(df: pd.DataFrame,
d: float,
thres: float=.01) -> pd.DataFrame:
'''
Compute the d fractional difference of the series.
Args:
df (pd.DataFrame): Dataframe with series to be differentiated in a single
column.
d (float): Order of differentiation.
thres (float): threshold value to drop non-significant weights.
Returns:
pd.DataFrame: Dataframe containing differentiated series.
'''
w = compute_weights(d, len(df))
w_ = np.cumsum(abs(w))
w_ /= w_.iloc[-1]
skip = int((w_ > thres).sum().values)
results = {}
index = df.index
for name in df.columns:
series_f = df[name].fillna(method='ffill').dropna()
r = range(skip, series_f.shape[0])
df_ = pd.Series(index=r)
for idx in r:
if not np.isfinite(df[name].iloc[idx]):
continue
results[idx] = np.dot(w.iloc[-(idx):, :].T, series_f.iloc[:idx])[0]
result = pd.DataFrame(pd.Series(results), columns=['Frac_diff'])
result.set_index(df[skip:].index, inplace=True)
return result
def compute_weights_fixed_window(d: float,
threshold: float=1e-5) -> pd.DataFrame:
'''
Compute the weights of individual data points
for fractional differentiation with fixed window:
Args:
d (float): Fractional differentiation value.
threshold (float): Minimum weight to calculate.
Returns:
pd.DataFrame: Dataframe containing the weights for each point.
'''
w = [1.0]
k = 1
while True:
v = -w[-1]/k*(d-k+1)
if abs(v) < threshold:
break
w.append(v)
k += 1
w = np.array(w[::-1]).reshape(-1, 1)
return pd.DataFrame(w)
def fixed_window_fracc_diff(df: pd.DataFrame,
d: float,
threshold: float=1e-5) -> pd.DataFrame:
'''
Compute the d fractional difference of the series with
a fixed width window. It defaults to standard fractional
differentiation when the length of the weights becomes 0.
Args:
df (pd.DataFrame): Dataframe with series to be differentiated in a single
column.
d (float): Order of differentiation.
threshold (float): threshold value to drop non-significant weights.
Returns:
pd.DataFrame: Dataframe containing differentiated series.
'''
w = compute_weights_fixed_window(d, threshold)
l = len(w)
results = {}
names = df.columns
for name in names:
series_f = df[name].fillna(method='ffill').dropna()
if l > series_f.shape[0]:
return standard_frac_diff(df, d, threshold)
r = range(l, series_f.shape[0])
df_ = pd.Series(index=r)
for idx in r:
if not np.isfinite(df[name].iloc[idx]):
continue
results[idx] = np.dot(w.iloc[-(idx):, :].T,
series_f.iloc[idx-l:idx])[0]
result = pd.DataFrame(pd.Series(results), columns=['Frac_diff'])
result.set_index(df[l:].index, inplace=True)
return result
def find_stat_series(df: pd.DataFrame,
threshold: float=0.0001,
diffs: np.linspace=np.linspace(0.05, 0.95, 19),
p_value: float=0.05) -> pd.DataFrame:
'''
Find the series that passes the adf test at the given
p_value.
The time series must be a single column dataframe.
Args:
df (pd.DataFrame): Dataframe with series to be differentiated.
threshold (float): threshold value to drop non-significant weights.
diffs (np.linspace): Space for candidate d values.
p_value (float): ADF test p-value limit for rejection of null
hypothesis.
Returns:
pd.DataFrame: Dataframe containing differentiated series. This series
is stationary and maintains maximum memory information.
'''
for diff in diffs:
if diff == 0:
continue
s = fixed_window_fracc_diff(df, diff, threshold)
adf_stat = adfuller(s, maxlag=1, regression='c', autolag=None)[1]
if adf_stat < p_value:
s.columns = ['d='+str(diff)]
return s
def compute_vol(df: pd.DataFrame,
span: int=100) -> pd.DataFrame:
'''
Compute period volatility of returns as exponentially weighted
moving standard deviation:
Args:
df (pd.DataFrame): Dataframe with price series in a single column.
span (int): Span for exponential weighting.
Returns:
pd.DataFrame: Dataframe containing volatility estimates.
'''
df.fillna(method='ffill', inplace=True)
r = df.pct_change()
return r.ewm(span=span).std()
def triple_barrier_labels(
df: pd.DataFrame,
t: int,
upper: float=None,
lower: float=None,
devs: float=2.5,
join: bool=False,
span: int=100) -> pd.DataFrame:
'''
Compute the triple barrier label for a price time series:
Args:
df (pd.DataFrame): Dataframe with price series in a single column.
t (int): Future periods to obtain the lable for.
upper (float): Returns for upper limit.
lower (float): Returns for lower limit.
devs (float): Standard deviations to set the upper and lower return
limits to when no limits passed.
join (bool): Return a join of the input dataframe and the labels.
span (int): Span for exponential weighting.
Returns:
pd.DataFrame: Dataframe containing labels and optinanlly (join=True)
input values.
'''
# Incorrect time delta:
if t < 1:
raise ValueError("Look ahead time invalid, t<1.")
# Lower limit must be negative:
if lower is not None:
if lower > 0:
raise ValueError("Lower limit must be a negative value.")
df.fillna(method='ffill', inplace=True)
lims = np.array([upper, lower])
labels = pd.DataFrame(index=df.index, columns=['Label'])
returns = df.pct_change()
r = range(0, len(df)-1-t)
for idx in r:
s = returns.iloc[idx:idx+t]
minimum = s.cumsum().values.min()
maximum = s.cumsum().values.max()
if not all(np.isfinite(s.cumsum().values)):
labels['Label'].iloc[idx] = np.nan
continue
if any(lims == None):
vol = compute_vol(df[:idx+t], span)
if upper is None:
u = vol.iloc[idx].values*devs
else:
u = upper
if lower is None:
l = -vol.iloc[idx].values*devs
else:
l = lower
valid = np.isfinite(u) and np.isfinite(l)
if not valid:
labels['Label'].iloc[idx] = np.nan
continue
if any(s.cumsum().values >= u):
labels['Label'].iloc[idx] = 1
elif any(s.cumsum().values <= l):
labels['Label'].iloc[idx] = -1
else:
labels['Label'].iloc[idx] = 0
if join:
df = df.join(labels)
return df
return labels
def get_entropic_labels(df: pd.DataFrame,
side: str = 'max',
future_space: np.linspace = np.linspace(2,90,40, dtype=int),
tbl_settings: dict = {}) -> pd.DataFrame:
'''
Compute the series of triple barrier labels for a price series that
results in the maximum or minimum entropy for label distribution.
Args:
df (pd.Dataframe): Dataframe with price series in a single column.
side (str): 'max' or 'min' to select maximum or minimim entropies.
'min' entropy may not result in usable data.
future_space (np.linspace): Space of future windows to analyze.
tbl_settings (dict): Dictionary with settings for triple_barrier_labels function.
Returns:
pd.DataFrame: Dataframe with the selected entropy distribution of labels.
'''
if side not in ['max', 'min']:
raise ValueError("Side must be 'max' or 'min'.")
# Labels:
l = {}
for f in future_space:
# Check this for references:
l[f] = triple_barrier_labels(df, f, **tbl_settings)
# Counts:
c = {}
for f in l.keys():
s = l[f].squeeze()
c[f] = s.value_counts(normalize=True)
# Entropies:
e = {}
for f, c in c.items():
e[f] = entropy(c)
# Maximum and minimum entropies:
max_e = [k for k,v in e.items() if v == max(e.values())][0]
min_e = [k for k,v in e.items() if v == min(e.values())][0]
if side == 'max':
e_labels = l[max_e]
t = max_e
if side == 'min':
e_labels = l[min_e]
t = min_e
e_labels.columns = ['t_delta='+str(t)]
return e_labels