| Overall Statistics |
|
Total Orders 16 Average Win 0.71% Average Loss 0% Compounding Annual Return 1.745% Drawdown 4.500% Expectancy 0 Start Equity 100000 End Equity 109194.97 Net Profit 9.195% Sharpe Ratio -1.417 Sortino Ratio -1.195 Probabilistic Sharpe Ratio 20.557% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha -0.029 Beta 0.061 Annual Standard Deviation 0.017 Annual Variance 0 Information Ratio -0.721 Tracking Error 0.133 Treynor Ratio -0.404 Total Fees $16.00 Estimated Strategy Capacity $86000000.00 Lowest Capacity Asset GS RKEOGCOG6RFP Portfolio Turnover 0.06% Drawdown Recovery 644 |
from QuantConnect.DataSource import SmartInsiderTransaction
import numpy as np
from scipy.optimize import curve_fit
from AlgorithmImports import *
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['buyback_pct'] = self.transaction.usdvalue / self.transaction.usdmarketcap
# sum up for daily
self.transaction.index = self.transaction.index.get_level_values('time').date if isinstance(self.transaction.index, pd.MultiIndex) else self.transaction.index.date
self.transaction = self.transaction.groupby(self.transaction.index)['buyback_pct'].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['buyback_pct'] = transactionHistory.usdvalue / transactionHistory.usdmarketcap
# sum up for daily
transactionHistory.index = transactionHistory.index.get_level_values('time').date if isinstance(transactionHistory.index, pd.MultiIndex) else transactionHistory.index.date
transactionHistory = transactionHistory.groupby(transactionHistory.index)['buyback_pct'].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