| Overall Statistics |
|
Total Trades 204 Average Win 2.51% Average Loss -1.31% Compounding Annual Return 14.394% Drawdown 25.000% Expectancy 0.512 Net Profit 96.467% Sharpe Ratio 0.881 Probabilistic Sharpe Ratio 34.055% Loss Rate 48% Win Rate 52% Profit-Loss Ratio 1.91 Alpha 0.15 Beta -0.13 Annual Standard Deviation 0.146 Annual Variance 0.021 Information Ratio -0.123 Tracking Error 0.24 Treynor Ratio -0.994 Total Fees $3521.57 Estimated Strategy Capacity $920000000.00 |
#
# Original File:
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect
# Corporation.
#
# Changes:
# The universe selection model is extended to take parameters as
# optional arguments.
# Ostirion SLU Copyright 2021
# Madrid, Spain
# Hector Barrio - hbarrio@ostirion.net.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from QuantConnect.Data.UniverseSelection import *
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel
from itertools import groupby
from math import ceil
from clr import AddReference
import numpy as np
from typing import List, Set, Tuple, Dict
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm.Framework")
class FlexibleUniverseSelectionModel(FundamentalUniverseSelectionModel):
'''
Class representing a parametrically selected securities universe.
Attributes:
n_coarse (int): Number of securities in the coarse selection.
n_fine (int): Number of securities in fine selection.
age (int): Minimum time since IPO.
recent (int): Maximum time from IPO.
vol_lim (float): Minimum daily volume of each security.
min_price (float): Minimum price of each security.
max_price (float): Maximum price of each security.
period (str): "Month" or "Day". Recalculate the universe every period.
m_cap_lim (float): Minimum market cap of security to be considered.
markets (list[str]): Markets in which the security trades.
c_id (str): Code of the country of origin of securities.
from_top (bool): Take the top (True) or bottom (False) volume securities.
restrict_country (bool): Restrict the country of origin and market for securities.
verbose (bool): False for silent, True for announcing size and components.
'''
def __init__(self: None,
n_coarse: int=1000,
n_fine: int=500,
age: int=1250,
recent: int=-1,
vol_lim: int=0,
min_price: int=0,
max_price: float=np.Inf,
period: str='Month',
m_cap_lim: float=5e8,
markets: List[str]=["NYS", "NAS"],
c_id: str='USA',
from_top: bool=True,
restrict_country: bool=True,
verbose: bool=False,
filterFineData: bool=True,
universeSettings: UniverseSettings=None,
securityInitializer: SecurityInitializer=None) -> None:
super().__init__(filterFineData, universeSettings, securityInitializer)
# Parameter settings:
self.n_symbols_coarse = n_coarse
self.n_symbols_fine = n_fine
self.age = age
self.recent = recent
self.vol_lim = vol_lim
self.min_price = min_price
self.max_price = max_price
self.period = period
self.m_cap_lim = m_cap_lim
self.markets = markets
self.c_id = c_id
self.reverse = from_top
self.restrict_country = restrict_country
self.verbose = verbose
self.usd_vol = {}
self.last_month = -1
def SelectCoarse(self,
algorithm: QCAlgorithm,
coarse: CoarseFundamental) -> FineFundamental:
'''
Coarse unviverse selection method.
Args:
algorithm (QCAlgorithm): Current algorithm instance.
coarse (CoarseFundamental): QC Coarse universe object.
Returns:
fine (FineFundamental): QC fine universe object.
'''
if self.period == 'Month':
if algorithm.Time.month == self.last_month:
return Universe.Unchanged
elif self.period != 'Day':
algoithm.Log('Period not valid.. Choose "Day" or "Month". Defaulting to "Month".')
c = coarse
usd_vol = sorted([x for x in c if
x.HasFundamentalData and
x.Volume > self.vol_lim and
self.max_price > x.Price > self.min_price],
key=lambda x: x.DollarVolume,
reverse=self.reverse)[:self.n_symbols_coarse]
self.usd_vol = {x.Symbol: x.DollarVolume for x in usd_vol}
if len(self.usd_vol) == 0:
return Universe.Unchanged
return list(self.usd_vol.keys())
def SelectFine(self,
algorithm: QCAlgorithm,
fine: FineFundamental) -> FineFundamental:
'''
Coarse unviverse selection method.
Args:
algorithm (QCAlgorithm): Current algorithm instance.
fine (FineFundamental): QC fine universe object.
Returns:
new_universe (FineFundamental): QC fine universe object.
'''
f = fine
a = algorithm
sort_sector = sorted([x for x in f if
x.MarketCap > self.m_cap_lim],
key=lambda x: x.CompanyReference.IndustryTemplateCode)
count = len(sort_sector)
if count == 0:
return Universe.Unchanged
if self.recent != -1:
sort_sector = [x for x in sort_sector if
(a.Time -
x.SecurityReference.IPODate).days < self.recent]
else:
sort_sector = [x for x in sort_sector if
(a.Time -
x.SecurityReference.IPODate).days > self.age]
if self.restrict_country:
sort_sector = [x for x in sort_sector if
x.CompanyReference.CountryId == self.c_id and
x.CompanyReference.PrimaryExchangeID in self.markets]
self.last_month = a.Time.month
percent = self.n_symbols_fine / count
sort_usd_vol = []
for c, g in groupby(sort_sector,
lambda x: x.CompanyReference.IndustryTemplateCode):
y = sorted(g, key=lambda x: self.usd_vol[x.Symbol],
reverse=self.reverse)
c = ceil(len(y) * percent)
sort_usd_vol.extend(y[:c])
sort_usd_vol = sorted(sort_usd_vol,
key=lambda x: self.usd_vol[x.Symbol],
reverse=self.reverse)
new_universe = [x.Symbol for x in sort_usd_vol[:self.n_symbols_fine]]
if self.verbose:
for s in new_universe:
algorithm.Log('Adding: '+str(s.Symbol))
algorithm.Log('Universe members: ' + str(len(new_universe)))
return new_universe'''
*******************************************************
Copyright (C) 2021 Ostirion SLU
Madrid, Spain
Hector Barrio <hbarrio@ostirion.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*******************************************************
'''
import numpy as np
from FlexibleUniverseSelectionModel import FlexibleUniverseSelectionModel as fusm
class CorrAtTop(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2016, 5, 1)
self.SetEndDate(datetime.today())
self.SetCash(1000000)
self.SetBrokerageModel(AlphaStreamsBrokerageModel())
res = Resolution.Daily
self.market = self.AddEquity('SPY', res).Symbol
# Universe selection parameters:
try:
self.n_stocks = int(self.GetParameter("n_stocks"))
except:
self.n_stocks = 15
self.UniverseSettings.Resolution = res
universe = fusm(n_fine=self.n_stocks)
self.AddUniverseSelection(universe)
# Risk control parameters:
try:
self.rc = float(self.GetParameter("risk_factor"))
except:
self.rc = 0.03
self.SetRiskManagement(TrailingStopRiskManagementModel(self.rc))
self.AddAlpha(CorrAtTopAlphaModel(self.market,
self.rc))
self.SetPortfolioConstruction(InsightWeightingPortfolioConstructionModel())
self.SetExecution(ImmediateExecutionModel())
class CorrAtTopAlphaModel(AlphaModel):
"""
"""
def __init__(self, market, risk):
self.symbol_data = {}
self.market = market
# Approximate 3-month correlation:
self.period = 60
self.Name = 'Correlation at Top'
self.fut_ret = risk # Future returns are not calculated.
self.counter = False
self.refresh = 2
def Update(self, algorithm, data):
insights = []
if not data:
return []
symbols = data.keys()
if not self.counter or self.counter % self.refresh == 0:
if self.market in symbols:
symbols.remove(self.market)
price = algorithm.History(symbols,
self.period,
Resolution.Daily).unstack(level=0)['close']
self.corr = price.corr().mean().mean()
algorithm.Debug(str(len(symbols)))
algorithm.Plot("corr", "Correlation", self.corr)
# Inelegant counter, to be replaced by
# timer.
self.counter += 1
if self.corr < 0.2:
direction = InsightDirection.Flat
algorithm.Debug('Low Correlation, dropping positions.')
else:
direction = InsightDirection.Up
p = timedelta(days=self.refresh)
active = algorithm.ActiveSecurities.Values
insights.append(Insight(self.market, p, InsightType.Price,
direction, self.fut_ret, 1, self.Name, 1))
return insightsimport 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:
code = '''
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'
with op_blanco_en(filename, 'wb') as f:
f.write(imgdata)
'''
code=code.replace('_blanco_','')
exec(code)
def plot_df(df, color='blue', size=(16, 7), legend='Close Price', y_label='Price in USD', title=None, kind='line', remove_legend=False):
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.05
y = -0.25
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")
if remove_legend: ax.get_legend().remove()
plt.xticks(rotation=45);
plt.show()
def plot_corr_hm(df, title='Title', size=(16, 7), annot = True):
im = image.imread(filename)
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=1, center=0, cmap=cmap, annot=annot,
square=True, linewidths=0, cbar_kws={"shrink": .5}, fmt='g')
ax.figure.figimage(im, 0, 0, alpha=1.0, zorder=1)
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()
# Useful variables:
def sp500():
SP_500_tickers = [
'TMUS','AWK','ODFL','JBHT','URI','PM','MO','PBCT','WDC','STX','NTAP','HPQ','HPE','AAPL','CDW','NOW','MSFT','FTNT',
'NUE','ULTA','TSCO','ORLY','GPC','KMX','AZO','SHW','PPG','LYB','IFF','ECL','DD','CE','ALB','WY','SBAC','PSA','IRM',
'EXR','EQIX','DLR','CCI','AMT','PEP','MNST','KO','XLNX','TXN','SWKS','QCOM','QRVO','NXPI','NVDA','MPWR','MU','MCHP',
'MXIM','INTC','AVGO','ADI','AMD','TER','LRCX','KLAC','AMAT','SPG','REG','O','KIM','FRT','YUM','SBUX','MCD','DPZ',
'DRI','CMG','UDR','MAA','ESS','EQR','AVB','VRSK','NLSN','INFO','EFX','RE','ZION','TFC','SIVB','RF','PNC','MTB','KEY',
'HBAN','FRC','FITB','CFG','CBRE','UNP','NSC','KSU','CSX','NWS','NWSA','WRB','TRV','PGR','HIG','CINF','CB','AIG','ALL',
'ZTS','VTRS','PFE','PRGO','MRK','LLY','JNJ','CTLT','ALXN','ABBV','PG','EL','WRK','SEE','PKG','IP','AVY','AMCR','TSN','MDLZ',
'MKC','LW','KHC','K','SJM','HRL','HSY','GIS','CAG','CPB','WMB','OKE','KMI','VLO','PSX','MPC','HFC','PXD','OXY','MRO',
'EOG','FANG','DVN','COP','COG','APA','SLB','NOV','HAL','BKR','VNO','BXP','ARE','XEL','SRE','PNW','NI','NEE','EXC','ES',
'DTE','CMS','CNP','AEE','BRK.B','L','LNC','AIZ','DIS','VIAC','NFLX','LYV','FOX','FOXA','BLL','UNH','HUM','CI','CNC','ANTM',
'TMO','MTD','IQV','ILMN','BIO','UNM','PRU','PFG','MET','GL','AFL','HAS','IBM','IT','DXC','CTSH','ACN','RJF','MS','GS','SCHW',
'VRSN','AKAM','EXPE','ETSY','EBAY','BKNG','AMZN','TWTR','FB','GOOG','GOOGL','TTWO','EA','ATVI','VZ','T','HES','XOM','CVX',
'WLTW','MMC','AJG','AON','PLD','DRE','XYL','SWK','SNA','PNR','PH','OTIS','IR','ITW','IEX','GWW','FTV','DOV','CMI','LIN',
'APD','ROP','HON','GE','MMM','NRG','AES','WMT','COST','RHI','NWL','KMB','CL','CLX','CHD','WHR','RCL','NCLH','MAR','HLT',
'CCL','HST','PHM','NVR','LEN','DHI','LOW','HD','MHK','LEG','CERN','WST','XRAY','COO','ALGN','DGX','LH','CVS','WELL','VTR',
'PEAK','UHS','HCA','DVA','ZBH','VAR','TFX','SYK','STE','RMD','PKI','MDT','ISRG','IDXX','HOLX','EW','DXCM','DHR','BSX','BDX',
'BAX','A','ABMD','ABT','WAT','MCK','HSIC','CAH','BMY','ABC','NEM','TGT','DLTR','DG','ATO','KR','SYY','SPGI','NDAQ','MSCI','MCO',
'MKTX','ICE','CME','CBOE','MOS','FMC','CTVA','CF','WM','ROL','RSG','TEL','IPGP','ZBRA','TRMB','KEYS','FLIR','ENPH','GLW','APH',
'ROK','GNRC','EMR','ETN','AME','WEC','SO','PEG','PPL','FE','EVRG','ETR','EIX','DUK','D','ED','AEP','LNT','WBA','LDOS','CPRT',
'CTAS','EMN','WFC','USB','JPM','CMA','C','BAC','POOL','LKQ','STZ','BF.B','WU','V','PYPL','PAYX','MA','JKHY','GPN','FLT','FISV',
'FIS','BR','ADP','FCX','SYF','DFS','COF','AXP','GRMN','VMC','MLM','WAB','PCAR','CAT','PWR','J','BBY','MSI','JNPR','FFIV',
'CSCO','ANET','DOW','WYNN','PENN','MGM','LVS','CZR','DISH','CMCSA','CHTR','TT','MAS','JCI','FBHS','FAST','CARR','AOS',
'ALLE','DISCK','DISCA','TAP','VRTX','REGN','INCY','GILD','BIIB','AMGN','AAP','TSLA','GM','F','BWA','APTV','TROW',
'STT','NTRS','IVZ','BEN','BLK','BK','AMP','TYL','SNPS','CRM','PAYC','ORCL','NLOK','INTU','CTXS','CDNS','ADSK',
'ANSS','ADBE','VFC','UA','UAA','TPR','RL','PVH','NKE','HBI','TJX','ROST','LB','GPS','LUMN','UAL','LUV','DAL',
'AAL','ALK','UPS','FDX','EXPD','CHRW','ADM','DE','TDG','TXT','TDY','RTX','NOC','LMT','LHX','HII','HWM','GD','BA','OMC','IPG']
return SP_500_tickers