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)