Overall Statistics |
Total Trades 933 Average Win 1.98% Average Loss -1.29% Compounding Annual Return 13.111% Drawdown 27.600% Expectancy 0.358 Net Profit 652.897% Sharpe Ratio 0.796 Probabilistic Sharpe Ratio 13.622% Loss Rate 46% Win Rate 54% Profit-Loss Ratio 1.53 Alpha 0.128 Beta -0.085 Annual Standard Deviation 0.15 Annual Variance 0.022 Information Ratio 0.089 Tracking Error 0.241 Treynor Ratio -1.403 Total Fees $50177.66 Estimated Strategy Capacity $670000000.00 Lowest Capacity Asset SPY R735QTJ8XC9X |
# # 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(2005, 1, 1) self.SetEndDate(datetime.today()) self.SetCash(1000000) self.SetBrokerageModel(AlphaStreamsBrokerageModel()) res = Resolution.Daily self.market = self.AddEquity('SPY', res).Symbol # Get Parameters: # Universe size that conforms the top: try: self.n_stocks = int(self.GetParameter("n_stocks")) except: self.Log('Defaulting stocks parameter.') self.n_stocks = 15 # Period to compute correlation try: self.corr_period = int(self.GetParameter("corr_period")) except: self.Log('Defaulting period parameter.') self.corr_period = 60 # Minimum correlation for "sell" signal: try: self.min_corr = float(self.GetParameter("min_corr")) except: self.Log('Defaulting minimum correlation parameter.') self.min_corr = 0.2 # Risk control parameters: try: self.rc = float(self.GetParameter("risk_factor")) except: self.Log('Defaulting risk parameter.') self.rc = 0.03 self.UniverseSettings.Resolution = res universe = fusm(n_fine=self.n_stocks) self.AddUniverseSelection(universe) self.SetRiskManagement(TrailingStopRiskManagementModel(self.rc)) self.AddAlpha(CorrAtTopAlphaModel(self.market, self.corr_period, self.min_corr, self.rc)) self.SetPortfolioConstruction(InsightWeightingPortfolioConstructionModel()) self.SetExecution(ImmediateExecutionModel()) class CorrAtTopAlphaModel(AlphaModel): """ """ def __init__(self, market, corr_period, min_corr, risk): self.symbol_data = {} self.market = market # These are taken from parameter grid: self.period = corr_period self.min_corr = min_corr # Normal, non-parameter variables: 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 < self.min_corr: algorithm.Debug('Correlation: '+str(self.corr)) 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 insights
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: 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