Overall Statistics |
Total Trades 8 Average Win 0.44% Average Loss 0% Compounding Annual Return 10.781% Drawdown 2.500% Expectancy 0 Net Profit 6.384% Sharpe Ratio 1.873 Probabilistic Sharpe Ratio 72.651% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha 0.003 Beta 0.317 Annual Standard Deviation 0.058 Annual Variance 0.003 Information Ratio -2.324 Tracking Error 0.098 Treynor Ratio 0.344 Total Fees $8.00 Estimated Strategy Capacity $23000000.00 Lowest Capacity Asset JNJ R735QTJ8XC9X |
from QuantConnect.DataSource import SmartInsiderTransaction import numpy as np from scipy.optimize import curve_fit class SmartInsiderCorporateBuybacksAlgorithm(QCAlgorithm): def Initialize(self): # parameter: minimal information coefficient accpetable self.minIC = 0.05 # parameter: minimal expected return accpetable self.minExpectedReturn = 0.005 self.SetStartDate(2021, 1, 1) self.SetCash(100000) self.SetPortfolioConstruction(InsightWeightingPortfolioConstructionModel(timedelta(days=252))) self.SetExecution(ImmediateExecutionModel()) # include "MMM", "BA", "GS", "HON", "JNJ", "MSFT", "TRV" as a result of the research self.symbols = [self.AddEquity(symbol, Resolution.Minute).Symbol for symbol in ["MMM", "GS", "HON", "JNJ", "MSFT", "TRV", "V"]] self.buybackSymbols = {symbol: self.AddData(SmartInsiderTransaction, symbol, Resolution.Daily).Symbol for symbol in self.symbols} # dict contains rolling windows storing tradebar data self.history = {symbol: RollingWindow[TradeBar](252*5) for symbol in self.symbols} # warm up rolling windows data = self.History(self.symbols, 252*5, Resolution.Daily) for symbol in self.symbols: for time, bar in data.loc[symbol].iterrows(): tradeBar = TradeBar(time, symbol, bar.open, bar.high, bar.low, bar.close, bar.volume) self.history[symbol].Add(tradeBar) # set up consolidator for future auto-update self.Consolidate(symbol, Resolution.Daily, self.DailyBarHandler) # schedule daily check for entering position self.Schedule.On(self.DateRules.EveryDay("MMM"), self.TimeRules.At(10, 0), self.Entry) def DailyBarHandler(self, bar): self.history[bar.Symbol].Add(bar) def Entry(self): ''' check entry signal ''' for symbol, tSymbol in self.buybackSymbols.items(): self.transaction = self.History(tSymbol, timedelta(days=1), Resolution.Daily) if self.transaction.empty: continue # buyback% self.transaction = self.transaction.usdvalue.unstack("symbol") / self.transaction.usdmarketcap.unstack("symbol") # sum up for daily self.transaction.index = self.transaction.index.date self.transaction = self.transaction.groupby(self.transaction.index).sum() # log self.transaction = np.log(self.transaction) self.GetData(symbol) # find IC ic = self.GetIC(symbol) # discontinue if IC too low if ic < self.minIC: continue # find expected return expectation = self.GetExpectation(symbol) # discontinue if expected return too low if expectation < self.minExpectedReturn: continue # emit insight for entry self.EmitInsights(Insight.Price(symbol, timedelta(days=252), InsightDirection.Up, None, None, None, expectation*ic)) def GetData(self, symbol): ''' get the processed dataframe Args: symbol: the symbol that we wish to get the processed dataframe''' # get historical close price data data = pd.DataFrame(self.history[symbol])[::-1] history = data.applymap(lambda bar: bar.Close) history.index = data.applymap(lambda bar: bar.EndTime.date()).values.flatten().tolist() # 1y forward history = history.pct_change(252).shift(-252).dropna() # get buyback transaction data transactionHistory = self.History(self.buybackSymbols[symbol], 252*5, Resolution.Daily) if transactionHistory.empty: return -1 # buyback% transactionHistory = transactionHistory.usdvalue.unstack("symbol") / transactionHistory.usdmarketcap.unstack("symbol") # sum up for daily transactionHistory.index = transactionHistory.index.date transactionHistory = transactionHistory.groupby(transactionHistory.index).sum() # concatenate the dataframes to left only the slices with data df = pd.concat([history, np.log(transactionHistory)], axis=1).replace([np.inf, -np.inf], np.nan).dropna() self.df = df def GetIC(self, symbol): ''' get the correlation coefficient as information coefficient Args: symbol: the symbol that we wish to get IC between 1y forward return and buyback transaction Return: (float) the correlation coefficient/IC''' return self.df.corr().values[0, 1] def GetExpectation(self, symbol): ''' get expected return in 1 year given the buyback% Args: symbol: the Symbol that we wish to get ins expected return Return: (float) expected return%''' def Function(x, a, b, c): ''' the function to be fitted ''' return a * np.exp(-b * x) + c try: popt, pcov = curve_fit(Function, self.df.iloc[:, 1].values, self.df.iloc[:, 0].values) # in case no optimal coefficients can be fit except: return -1 return Function(self.transaction, *popt).values