Overall Statistics |
Total Orders 422 Average Win 0.41% Average Loss -0.39% Compounding Annual Return 0.115% Drawdown 8.700% Expectancy 0.016 Start Equity 1000000 End Equity 1005778.92 Net Profit 0.578% Sharpe Ratio -0.372 Sortino Ratio -0.158 Probabilistic Sharpe Ratio 0.439% Loss Rate 51% Win Rate 49% Profit-Loss Ratio 1.06 Alpha -0.014 Beta -0.003 Annual Standard Deviation 0.037 Annual Variance 0.001 Information Ratio -0.84 Tracking Error 0.162 Treynor Ratio 4.265 Total Fees $8422.96 Estimated Strategy Capacity $1600000.00 Lowest Capacity Asset DAL TSAT9G6HOJ8L Portfolio Turnover 8.20% |
#region impor#region imports from AlgorithmImports import * from statsmodels.discrete.discrete_model import Logit #endregion class AirlineBuybacksDemo(QCAlgorithm): def initialize(self): #1. Required: Five years of backtest history self.set_start_date(2017, 1, 1) self.set_end_date(2022, 1, 1) #2. Required: Alpha Streams Models: self.set_brokerage_model(BrokerageName.ALPHA_STREAMS) #3. Required: Significant AUM Capacity self.set_cash(1000000) #4. Required: Benchmark to SPY self.set_benchmark("SPY") self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel()) self.set_execution(ImmediateExecutionModel()) # Set our strategy to be take 5% profit and 5% stop loss. self.add_risk_management(MaximumUnrealizedProfitPercentPerSecurity(0.05)) self.add_risk_management(MaximumDrawdownPercentPerSecurity(0.05)) # Select the airline tickers for research. self.symbols = {} assets = ["LUV", # Southwest Airlines "DAL", # Delta Airlines "UAL", # United Airlines Holdings "AAL", # American Airlines Group "SKYW", # SkyWest Inc. "ALGT", # Allegiant Travel Co. "ALK" # Alaska Air Group Inc. ] # Call the AddEquity method with the tickers, and its corresponding resolution. Then call AddData with SmartInsiderTransaction to subscribe to their buyback transaction data. for ticker in assets: symbol = self.add_equity(ticker, Resolution.MINUTE).symbol self.symbols[symbol] = self.add_data(SmartInsiderTransaction, symbol).symbol self.add_equity("SPY") # Initialize the model self.build_model() # Set Scheduled Event Method For Our Model Recalibration every month self.schedule.on(self.date_rules.month_start(), self.time_rules.at(0, 0), self.build_model) # Set Scheduled Event Method For Trading self.schedule.on(self.date_rules.every_day(), self.time_rules.before_market_close("SPY", 5), self.every_day_before_market_close) def build_model(self): qb = self # Call the History method with list of tickers, time argument(s), and resolution to request historical data for the symbol. history = qb.history(list(self.symbols.keys()), datetime(2015, 1, 1), datetime.now(), Resolution.DAILY) # Call SPY history as reference spy = qb.history(["SPY"], datetime(2015, 1, 1), datetime.now(), Resolution.DAILY) # Call the History method with list of buyback tickers, time argument(s), and resolution to request buyback data for the symbol. history_buybacks = qb.history(list(self.symbols.values()), datetime(2015, 1, 1), datetime.now(), Resolution.DAILY) # Select the close column and then call the unstack method to get the close price dataframe. df = history['close'].unstack(level=0) spy_close = spy['close'].unstack(level=0) # Call pct_change to get the daily return of close price, then shift 1-step backward as prediction. ret = df.pct_change().shift(-1).iloc[:-1] ret_spy = spy_close.pct_change().shift(-1).iloc[:-1] # Get the active return active_ret = ret.sub(ret_spy.values, axis=0) # Select the ExecutionPrice column and then call the unstack method to get the dataframe. history_buybacks = history_buybacks[~history_buybacks.index.duplicated(keep='first')] df_buybacks = history_buybacks['executionprice'].unstack(level=0) # Convert buyback history into daily mean data df_buybacks = df_buybacks.groupby(df_buybacks.index.date).mean() df_buybacks.columns = df.columns # Get the buyback premium/discount df_close = df.reindex(df_buybacks.index)[~df_buybacks.isna()] df_buybacks = (df_buybacks - df_close)/df_close # Create a dataframe to hold the buyback and 1-day forward return data data = pd.DataFrame(columns=["Buybacks", "Return"]) # Append the data into the dataframe for row, row_buyback in zip(active_ret.reindex(df_buybacks.index).itertuples(), df_buybacks.itertuples()): index = row[0] for i in range(1, df_buybacks.shape[1]+1): if row_buyback[i] != 0: data = pd.concat([data, pd.DataFrame({"Buybacks": row_buyback[i], "Return":row[i]}, index=[index])]) # Call dropna to drop NaNs data.dropna(inplace=True) # Get binary return (+/-) binary_ret = data["Return"].copy() binary_ret[binary_ret < 0] = 0 binary_ret[binary_ret > 0] = 1 # Construct a logistic regression model self.model = Logit(binary_ret.values, data["Buybacks"].values).fit() def every_day_before_market_close(self): qb = self # Get any buyback event today history_buybacks = qb.history(list(self.symbols.values()), timedelta(days=1), Resolution.DAILY) if history_buybacks.empty or "executionprice" not in history_buybacks.columns: return # Select the ExecutionPrice column and then call the unstack method to get the dataframe. history_buybacks = history_buybacks[~history_buybacks.index.duplicated(keep='first')] df_buybacks = history_buybacks['executionprice'].unstack(level=0) # Convert buyback history into daily mean data df_buybacks = df_buybacks.groupby(df_buybacks.index.date).mean() # ============================== insights = [] # Iterate the buyback data, thne pass to the model for prediction row = df_buybacks.iloc[-1] for i in range(len(row)): prediction = self.model.predict(row[i]) # Long if the prediction predict price goes up, short otherwise. Do opposite for SPY (active return) if prediction > 0.5: insights.append( Insight.price(row.index[i].split(".")[0], timedelta(days=1), InsightDirection.UP) ) insights.append( Insight.price("SPY", timedelta(days=1), InsightDirection.DOWN) ) else: insights.append( Insight.price(row.index[i].split(".")[0], timedelta(days=1), InsightDirection.DOWN) ) insights.append( Insight.price("SPY", timedelta(days=1), InsightDirection.UP) ) self.emit_insights(insights)