Overall Statistics
Total Orders
302
Average Win
3.96%
Average Loss
-3.30%
Compounding Annual Return
6.394%
Drawdown
34.600%
Expectancy
0.056
Start Equity
1000000
End Equity
1127750.54
Net Profit
12.775%
Sharpe Ratio
0.06
Sortino Ratio
0.049
Probabilistic Sharpe Ratio
13.048%
Loss Rate
52%
Win Rate
48%
Profit-Loss Ratio
1.20
Alpha
0
Beta
0
Annual Standard Deviation
0.201
Annual Variance
0.04
Information Ratio
0.326
Tracking Error
0.201
Treynor Ratio
0
Total Fees
$28232.10
Estimated Strategy Capacity
$10000000.00
Lowest Capacity Asset
VX YPDDEQD90YQX
Portfolio Turnover
31.45%
#region imports
from AlgorithmImports import *
#endregion


general_setting = {
    "lookback": 100,
    "lookback_RESOLUTION": "HOUR",

    "ratio_method": "Regression",

    "Take_Profit_pct": 0.3,
    "Stop_Loss_pct": 0.08,

    "p_value_threshold_entry": 0.0001,
    "p_value_threshold_exit": 0.00001,
    "rollover_days": 2, 

}
from AlgorithmImports import *
from QuantConnect.DataSource import *

from config import general_setting
import pickle

import numpy as np
import pandas as pd
import math
import statsmodels.api as sm
from pandas.tseries.offsets import BDay
from pykalman import KalmanFilter
from statsmodels.tsa.stattools import coint, adfuller

class CalendarSpread(QCAlgorithm):

    def initialize(self) -> None:
        self.SetTimeZone(TimeZones.NEW_YORK)

        self.set_start_date(2023, 1, 1) 
        # self.set_end_date(2024,9,10)
        self.set_cash(1000000) 
        self.universe_settings.asynchronous = True

        self.zscore_df = {}
        self.note1_price = {}
        self.note2_price = {}


        # Requesting  data
        # Futures.Currencies.EUR
        # Futures.Currencies.MICRO_EUR
        # Futures.Financials.Y_2_TREASURY_NOTE
        # Futures.Financials.Y_5_TREASURY_NOTE
        # Futures.Indices.MICRO_NASDAQ_100_E_MINI
        # Futures.Indices.SP_500_E_MINI
        # Futures.Indices.VIX

        future_vix = self.add_future(Futures.Indices.VIX, resolution = Resolution.HOUR, extended_market_hours = True) 
        self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)
        future_vix.set_filter(0, 180)
        self.future_vix_symbol = future_vix.symbol

        self.first_vix_contract = None
        self.second_vix_contract = None
        self.third_vix_contract = None

        self.first_vix_expiry = None
        self.second_vix_expiry = None
        self.third_vix_expiry = None

        self.lookback  = general_setting['lookback']
        self.p_threshold_entry = general_setting['p_value_threshold_entry']
        self.p_threshold_exit = general_setting['p_value_threshold_exit']
        self.rollover_days = general_setting['rollover_days']

        self.wt_1 = None
        self.wt_2 = None

        self.roll_signal = False
        self.Margin_Call = False

        self.prev_cap = None

        self.large_diff = None
        self.backwardation = False

        self.diversion = None
        self.coefs = []

        self.entry1 = 1.78
        self.entry2 = 2.42
        self.entry3 = -0.94
        self.entry4 = -1.73

        self.exit1 = 1.15
        self.exit2 = -0.38

        self.ratio_100 = {}
        self.quantile25_100_30_pos = {}
        self.quantile50_100_30_pos = {}
        self.quantile75_100_30_pos = {}

        self.quantile25_100_30_neg = {}
        self.quantile50_100_30_neg = {}
        self.quantile75_100_30_neg = {}

    def stats(self):
        # Request Historical Data
        df_vix1 = self.History(self.first_vix_contract.symbol, timedelta(self.lookback), Resolution.HOUR).rename(columns = {'close':'first'})
        df_vix2 = self.History(self.second_vix_contract.symbol, timedelta(self.lookback), Resolution.HOUR).rename(columns = {'close':'second'})
        # df_vix3 = self.History(self.third_vix_contract.symbol,timedelta(self.lookback), Resolution.HOUR).rename(columns = {'close':'third'})

        df_merge = pd.merge(df_vix1, df_vix2, on = ['time'], how = 'inner')

        vix1_log = np.array(df_merge['first'].apply(lambda x: math.log(x))) 
        vix2_log = np.array(df_merge['second'].apply(lambda x: math.log(x))) 
        # vix3_log = np.array(df_Gold3.apply(lambda x: math.log(x))) 

        # 1st & 2nd

    
        X1 = sm.add_constant(vix1_log)
        Y1 = vix2_log
        model1 = sm.OLS(Y1, X1)
        results1 = model1.fit()
        sigma1 = math.sqrt(results1.mse_resid)
        slope1 = results1.params[1]
        intercept1 = results1.params[0]
        res1 = results1.resid
        zscore1 = res1/sigma1

        adf1 = adfuller(res1)

        p_value1 = adf1[1]

        # spread = res1[len(res1)-1]
        df_merge['spread'] = df_merge['second'] - df_merge['first']
        
        spread = np.array(df_merge['spread'])
        # test_passed1 = p_value1 <= self.p_threshold
        # self.debug(f"p value is {p_value1}")

        return [p_value1, zscore1, slope1, spread]

    
    def on_data(self, slice: Slice) -> None:


        # Entry signal
        # if self.time.minute == 0 or self.time.minute ==10 or self.time.minute == 20 or self.time.minute==30 or self.time.minute == 40 or self.time.minute == 50:
        if self.roll_signal == False and self.time.hour < 17 and self.time.hour > 8:
            if not self.portfolio.Invested:

                chain = slice.futures_chains.get(self.future_vix_symbol)
                if chain:
                    contracts = [i for i in chain ]
                    
                    e = [i.expiry for i in contracts]
                    e = sorted(list(set(sorted(e, reverse = True))))
                    # e = [i.expiry for i in contracts if i.expiry- self.Time> timedelta(5)]
                    # self.debug(f"the first contract is {e[0]}, the length of e is {len(e)}")
                    # expiry = e[0]
                    
                    try:
                        self.first_vix_contract = [contract for contract in contracts if contract.expiry == e[0]][0]
                        self.second_vix_contract = [contract for contract in contracts if contract.expiry == e[1]][0]
                        # self.third_gold_contract = [contract for contract in contracts if contract.expiry == e[2]][0]
                        self.first_vix_expiry = e[0]
                        self.second_vix_expiry = e[1]
                        # self.third_gold_expiry = e[2]

                        stats = self.stats()
                        self.zscore_df[self.time] = stats[1][-1]
                        self.note1_price[self.time] = self.Securities[self.first_vix_contract.symbol].Price
                        self.note2_price[self.time] = self.Securities[self.second_vix_contract.symbol].Price
                        sigma = stats[3].std()
                        mean = stats[3].mean()
                        last_spread = stats[3][-1]
                        n = (last_spread-mean)/sigma
                        self.coefs.append(n)
                        self.ratio_100[self.time] = n
                        
                        if len(self.coefs) >= 24 * 30:
                            self.coefs = self.coefs[-24 * 30:]
                            self.pos_coefs = [i for i in self.coefs if i > 0]
                            self.neg_coefs = [i for i in self.coefs if i < 0]
                            if len(self.pos_coefs) > 24 * 10:
                                pos_quantile = np.quantile( self.pos_coefs, [0.25,0.5,0.75])
                                self.entry1 = pos_quantile[1]
                                self.entry2 = pos_quantile[2]
                                self.exit1 = pos_quantile[0]
                                self.quantile25_100_30_pos[self.time] = pos_quantile[0]
                                self.quantile50_100_30_pos[self.time] = pos_quantile[1]
                                self.quantile75_100_30_pos[self.time] = pos_quantile[2]

                            if len(self.neg_coefs) > 24 * 10:
                                neg_quantile = np.quantile( self.neg_coefs, [0.25,0.5,0.75])
                                self.entry3 = neg_quantile[1]
                                self.entry4 = neg_quantile[0]
                                self.exit2 = neg_quantile[2]
                                self.quantile25_100_30_neg[self.time] = neg_quantile[0]
                                self.quantile50_100_30_neg[self.time] = neg_quantile[1]
                                self.quantile75_100_30_neg[self.time] = neg_quantile[2]

                        
                        # if (self.first_vix_expiry.date() - self.time.date()).days > self.rollover_day:
                        self.trade_signal = True
                        
                        # else:
                        #     self.trade_signal = False

                        if self.trade_signal and ((self.first_vix_expiry.date() - self.time.date()).days > self.rollover_days):
                            
                            self.wt_1 = 1/(1+stats[2])
                            self.wt_2 = 1 - self.wt_1


                            # if stats[3]<0:
                            if  n > self.entry1 and (n < self.entry2):
                                self.set_holdings(self.first_vix_contract.symbol, -self.wt_1, tag = f'spread  = mean + {round(n,2)}*sigma (diversion)')
                                self.set_holdings(self.second_vix_contract.symbol,  self.wt_2, tag = f'spread = mean + {round(n,2)}*sigma  (diversion)')
                                self.prev_cap = self.portfolio.total_portfolio_value
                                self.large_diff = True

                            if  (n > self.entry2):

                                self.set_holdings(self.first_vix_contract.symbol, self.wt_1, tag = f'spread  = mean + {round(n,2)}*sigma (mean reversion)')
                                self.set_holdings(self.second_vix_contract.symbol,  -self.wt_2, tag = f'spread = mean + {round(n,2)}*sigma  (mean reversion)')
                                self.prev_cap = self.portfolio.total_portfolio_value
                                self.large_diff = True
                                # self.debug(f"enter position: z score is {stats[1][-1]}")

                            elif n < self.entry3 and n > self.entry4:

                                self.set_holdings(self.first_vix_contract.symbol, self.wt_1, tag = f'spread < mean - {round(abs(n),2)}*sigma (diversion)')
                                self.set_holdings(self.second_vix_contract.symbol, -self.wt_2, tag = f'spread < mean - {round(abs(n),2)}*sigma (diversion)')
                                self.prev_cap = self.portfolio.total_portfolio_value
                                self.large_diff = False
                                # self.debug(f"enter position: z score is {stats[1][-1]}")
                                self.diversion = True

                            elif n < self.entry4:
                                self.set_holdings(self.first_vix_contract.symbol, -self.wt_1, tag = f'spread < mean - {round(abs(n),2)}*sigma (mean reversion)')
                                self.set_holdings(self.second_vix_contract.symbol, self.wt_2, tag = f'spread < mean - {round(abs(n),2)}*sigma (mean reversion)')
                                self.prev_cap = self.portfolio.total_portfolio_value
                                self.large_diff = False
                                # self.debug(f"enter position: z score is {stats[1][-1]}")

                            self.trade_signal = False

                    except:
                        return

            else:
                # exit signal
                stats = self.stats()
                sigma = stats[3].std()
                mean = stats[3].mean()
                last_spread = stats[3][-1]
                n = (last_spread-mean)/sigma
                self.wt_1 = 1/(1+stats[2])
                self.wt_2 = 1 - self.wt_1
                self.coefs.append(n)
                self.ratio_100[self.time] = n
                
                if len(self.coefs) >= 24 * 30:
                    self.coefs = self.coefs[-24 * 30:]
                    self.pos_coefs = [i for i in self.coefs if i > 0]
                    self.neg_coefs = [i for i in self.coefs if i < 0]
                    if len(self.pos_coefs) > 24 * 10:
                        pos_quantile = np.quantile( self.pos_coefs, [0.25,0.5,0.75])
                        self.entry1 = pos_quantile[1]
                        self.entry2 = pos_quantile[2]
                        self.exit1 = pos_quantile[0]
                        self.quantile25_100_30_pos[self.time] = pos_quantile[0]
                        self.quantile50_100_30_pos[self.time] = pos_quantile[1]
                        self.quantile75_100_30_pos[self.time] = pos_quantile[2]

                    if len(self.neg_coefs) > 24 * 10:
                        neg_quantile = np.quantile( self.neg_coefs, [0.25,0.5,0.75])
                        self.entry3 = neg_quantile[1]
                        self.entry4 = neg_quantile[0]
                        self.exit2 = neg_quantile[2]
                        self.quantile25_100_30_neg[self.time] = neg_quantile[0]
                        self.quantile50_100_30_neg[self.time] = neg_quantile[1]
                        self.quantile75_100_30_neg[self.time] = neg_quantile[2]
                        
                # self.zscore_df[self.time] = stats[1][-1]
                # self.note1_price[self.time] = self.Securities[self.first_vix_contract.symbol].Price
                # self.note2_price[self.time] = self.Securities[self.second_vix_contract.symbol].Price
                # Roll over

                if ((self.first_vix_expiry.date() - self.time.date()).days <= self.rollover_days and self.time.hour == 10 ):
                    self.roll_signal = True
                    if self.portfolio.total_portfolio_value>= self.prev_cap:
                        self.liquidate(tag = 'rollover; Win')
                    else:
                        self.liquidate(tag = 'rollover; Loss')
                    self.prev_cap = None
                    self.large_diff = None

                    return
                    
                # Take Profit / Stop Loss    
                # if self.prev_cap :
                #     if self.portfolio.total_portfolio_value> 1.1 * self.prev_cap:
                #         self.liquidate(tag = 'Take Profit')
                #         self.prev_cap = None
                #         self.large_diff = None
                #         return

                #     elif self.portfolio.total_portfolio_value< 0.93 * self.prev_cap:
                #         self.liquidate(tag = 'Stop Loss')
                #         self.prev_cap = None
                #         self.large_diff = None
                #         return

                if self.diversion == True:

                    if (n >  self.entry2 and self.large_diff == True):
                        if self.portfolio.total_portfolio_value>= self.prev_cap:
                            self.liquidate(tag = 'Diversion; Win')
                        else:
                            self.liquidate(tag = 'Diversion; Loss')

                        self.set_holdings(self.first_vix_contract.symbol, self.wt_1, tag = f'spread  = mean + {round(n,2)}*sigma (mean_revesion)')
                        self.set_holdings(self.second_vix_contract.symbol,  -self.wt_2, tag = f'spread = mean + {round(n,2)}*sigma (mean_reversion)')
                        self.prev_cap = self.portfolio.total_portfolio_value

                        self.large_diff = True
                        self.diversion = False

                    elif (n < self.entry4 and self.large_diff == False):
                        if self.portfolio.total_portfolio_value>= self.prev_cap:
                            self.liquidate(tag = 'Diversion; Win')
                        else:
                            self.liquidate(tag = 'Diversion; Loss')

                        self.set_holdings(self.first_vix_contract.symbol, -self.wt_1, tag = f'spread  = mean - {abs(round(n,2))}*sigma (mean_revesion)')
                        self.set_holdings(self.second_vix_contract.symbol,  self.wt_2, tag = f'spread = mean - {abs(round(n,2))}*sigma (mean_reversion)')
                        self.prev_cap = self.portfolio.total_portfolio_value
                        self.large_diff = False
                        self.diversion = False

                    # elif : 
                    #     if self.portfolio.total_portfolio_value>= self.prev_cap:
                    #         self.liquidate(tag = 'Diversion; Win')
                    #     else:
                    #         self.liquidate(tag = 'Diversion; Loss')

                    #     stats = self.stats()
                    #     self.zscore_df[self.time] = stats[1][-1]
                    #     self.note1_price[self.time] = self.Securities[self.first_vix_contract.symbol].Price
                    #     self.note2_price[self.time] = self.Securities[self.second_vix_contract.symbol].Price
                    #     sigma = stats[3].std()
                    #     mean = stats[3].mean()
                    #     last_spread = stats[3][-1]
                    #     n = (last_spread-mean)/sigma
                    #     self.set_holdings(self.first_vix_contract.symbol, self.wt_1, tag = f'spread  = mean + {round(n,2)}*sigma (mean_revesion)')
                    #     self.set_holdings(self.second_vix_contract.symbol,  -self.wt_2, tag = f'spread = mean + {round(n,2)}*sigma (mean_reversion)')
                    #     self.prev_cap = self.portfolio.total_portfolio_value

                    #     self.large_diff = True
                    #     # self.debug(f"exit position: z score is {stats[1][-1]}")
                    #     self.diversion = False

                else:
                    if ( n < self.exit1 and self.large_diff == True):
                        if self.portfolio.total_portfolio_value>= self.prev_cap:
                            self.liquidate(tag = 'Mean Reversion; Win')
                        else:
                            self.liquidate(tag = 'Mean Reversion; Loss')
                        
                        
                        self.diversion = None
                        self.prev_cap = None
                        self.large_diff = None


                        # self.debug(f"exit position: z score is {stats[1][-1]}")
                        
                    elif (n > self.exit2 and self.large_diff == False):
                        if self.portfolio.total_portfolio_value>= self.prev_cap:
                            self.liquidate(tag = 'Mean Reversion; Win')
                        else:
                            self.liquidate(tag = 'Mean Reversion; Loss')

                        self.prev_cap = None
                        self.large_diff = None
                        self.diversion = None

                # if not self.large_diff:
                #     if n > 0:
                #         if self.portfolio.total_portfolio_value>= self.prev_cap:
                #             self.close = self.liquidate(tag = 'Wrong Direction (n > 0); Win')
                #         else:
                #             self.close = self.liquidate(tag = 'Wrong Direction (n > 0); Loss')
                #         return


                # if self.large_diff:
                #     if n < -0.3:
                #         if self.portfolio.total_portfolio_value>= self.prev_cap:
                #             self.close = self.liquidate(tag = 'Wrong Direction (n < 0); Win')
                #         else:
                #             self.close = self.liquidate(tag = 'Wrong Direction (n < 0); Loss')
                #         return



        else:

            stats = self.stats()

            # self.plot('z_score_plot','z_score',stats[1][-1] )            
            # self.plot('p_value_plot','p_value', stats[0])

            if self.first_vix_expiry.date() < self.time.date():
                self.roll_signal = False


        # if self.zscore_df:
        #     df = pd.DataFrame.from_dict(self.zscore_df, orient='index',columns=['zscore'])
        #     file_name = 'CalendarSpread/zscore_df'
        #     self.object_store.SaveBytes(file_name, pickle.dumps(df))


        # if self.note1_price:
        #     df = pd.DataFrame.from_dict(self.note1_price, orient='index',columns=['price1'])
        #     file_name = 'CalendarSpread/note1_df'
        #     self.object_store.SaveBytes(file_name, pickle.dumps(df))

        if self.ratio_100:
            df = pd.DataFrame.from_dict(self.ratio_100, orient='index',columns=['ratio'])
            file_name = 'CalendarSpread/ratio_100'
            self.object_store.SaveBytes(file_name, pickle.dumps(df))

        if self.quantile25_100_30_pos:
            df = pd.DataFrame.from_dict(self.quantile25_100_30_pos, orient='index',columns=['quantile25_pos'])
            file_name = 'CalendarSpread/quantile25_100_30_pos'
            self.object_store.SaveBytes(file_name, pickle.dumps(df))


        if self.quantile50_100_30_pos:
            df = pd.DataFrame.from_dict(self.quantile50_100_30_pos, orient='index',columns=['quantile50_pos'])
            file_name = 'CalendarSpread/quantile50_100_30_pos'
            self.object_store.SaveBytes(file_name, pickle.dumps(df))

        if self.quantile75_100_30_pos:
            df = pd.DataFrame.from_dict(self.quantile75_100_30_pos, orient='index',columns=['quantile75_pos'])
            file_name = 'CalendarSpread/quantile75_100_30_pos'
            self.object_store.SaveBytes(file_name, pickle.dumps(df))

        if self.quantile25_100_30_neg:
            df = pd.DataFrame.from_dict(self.quantile25_100_30_neg, orient='index',columns=['quantile25_neg'])
            file_name = 'CalendarSpread/quantile25_100_30_neg'
            self.object_store.SaveBytes(file_name, pickle.dumps(df))

        if self.quantile50_100_30_neg:
            df = pd.DataFrame.from_dict(self.quantile50_100_30_neg, orient='index',columns=['quantile50_neg'])
            file_name = 'CalendarSpread/quantile50_100_30_neg'
            self.object_store.SaveBytes(file_name, pickle.dumps(df))

        if self.quantile75_100_30_neg:
            df = pd.DataFrame.from_dict(self.quantile75_100_30_neg, orient='index',columns=['quantile75_neg'])
            file_name = 'CalendarSpread/quantile75_100_30_neg'
            self.object_store.SaveBytes(file_name, pickle.dumps(df))




    def OnOrderEvent(self, orderEvent):

        
        if orderEvent.Status != OrderStatus.Filled:
            return
        

        # Webhook Notification    
        symbol = orderEvent.symbol
        price = orderEvent.FillPrice
        quantity = orderEvent.quantity
        a = { "text": f"[Calendar Arbitrage Paper order update] \nSymbol: {symbol} \nPrice: {price} \nQuantity: {quantity}" }
        payload = json.dumps(a)
        self.notify.web("https://hooks.slack.com/services/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", payload)


    def on_margin_call(self, requests):
        self.debug('Margin Call is coming')
        self.Margin_Call =  True

        a = { "text": f"[Calendar Spread Margin Call update]Margin Call is coming" }
        payload = json.dumps(a)
        self.notify.web("https://hooks.slack.com/services/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", payload)

        return requests

    def OnOrderEvent(self, orderEvent):

        
        if orderEvent.Status != OrderStatus.Filled:
            return

        if self.Margin_Call:
            qty = orderEvent.quantity
            symbol = orderEvent.symbol
            
            self.Margin_Call = False
            self.debug(f'Hit margin call, the qty is {qty}')

            if symbol == self.first_vix_contract.symbol:
                self.debug(f'if come here, symbol is {symbol}, qty is {qty}')
                self.market_order(self.second_es_contract.symbol, -qty)

            if symbol == self.second_vix_contract.symbol:
                self.debug(f'if come here, symbol is {symbol}, qty is {qty}')
                self.market_order(self.first_es_contract.symbol, -qty)

            # self.liquidate(tag = 'margin call')

# region imports
from AlgorithmImports import *
import numpy as np
import pandas as pd
import math
import statsmodels.api as sm
from pandas.tseries.offsets import BDay
from pykalman import KalmanFilter
from statsmodels.tsa.stattools import coint, adfuller
# endregion

from config import general_setting


class BasicTemplateFuturesAlgorithm(QCAlgorithm):
    def Initialize(self):
        self.debug("start calendar spread algo")
        self.SetStartDate(2023, 10, 8)
        self.SetCash(1000000)

        self.universe_settings.resolution = Resolution.MINUTE

        # lookback frequency settings
        self.lookback = general_setting['lookback']
        self.lookback_RESOLUTION = general_setting['lookback_RESOLUTION'] 

        self.enter = general_setting["enter_level"]
        self.exit = general_setting["exit_level"]

        # Subscribe and set our expiry filter for the futures chain
        future1 = self.AddFuture(Futures.Metals.GOLD, resolution=Resolution.MINUTE)
        future1.SetFilter(timedelta(0), timedelta(365))

        # benchmark = self.AddEquity("SPY")
        # self.SetBenchmark(benchmark.Symbol)

        seeder = FuncSecuritySeeder(self.GetLastKnownPrices)
        self.SetSecurityInitializer(lambda security: seeder.SeedSecurity(security))
        
        self.gold1_contract = None
        self.gold2_contract = None
        self.gold3_contract = None

        self.minute_counter = 0
        self.Schedule.On(self.date_rules.every_day(), self.TimeRules.At(18,0), self.reset_minute_counter)  # Check Take profit and STOP LOSS every minute


    def reset_minute_counter(self):
        self.minute_counter = 0


    def stats(self, symbols, method="Regression"):
        # lookback here refers to market hour, whereas additional extended-market-hour data are also included.
        if self.lookback_RESOLUTION == "MINUTE":
            df_Gold1 = self.History(symbols[0], self.lookback, Resolution.MINUTE)
            df_Gold2 = self.History(symbols[1], self.lookback, Resolution.MINUTE)
            df_Gold3 = self.History(symbols[2], self.lookback, Resolution.MINUTE)
        elif self.lookback_RESOLUTION == "HOUR":
            df_Gold1 = self.History(symbols[0], self.lookback, Resolution.HOUR)
            df_Gold2 = self.History(symbols[1], self.lookback, Resolution.HOUR)
            df_Gold3 = self.History(symbols[2], self.lookback, Resolution.HOUR)
        else:
            df_Gold1 = self.History(symbols[0], self.lookback, Resolution.DAILY)
            df_Gold2 = self.History(symbols[1], self.lookback, Resolution.DAILY)
            df_Gold3 = self.History(symbols[2], self.lookback, Resolution.DAILY)
        
        if df_Gold1.empty or df_Gold2.empty:
            return 0

        df_Gold1 = df_Gold1["close"]
        df_Gold2 = df_Gold2["close"]
        df_Gold3 = df_Gold3["close"]

        Gold1_log = np.array(df_Gold1.apply(lambda x: math.log(x))) 
        Gold2_log = np.array(df_Gold2.apply(lambda x: math.log(x))) 
        Gold3_log = np.array(df_Gold3.apply(lambda x: math.log(x))) 


        # Gold1 & Gold2 Regression and ADF test
        X1 = sm.add_constant(Gold1_log)
        Y1 = Gold2_log
        model1 = sm.OLS(Y1, X1)
        results1 = model1.fit()
        sigma1 = math.sqrt(results1.mse_resid)
        slope1 = results1.params[1]
        intercept1 = results1.params[0]
        res1 = results1.resid
        zscore1 = res1/sigma1

        adf1 = adfuller(res1)

        p_value1 = adf1[1]
        test_passed1 = p_value1 <= general_setting['p_value_threshold']
        self.debug(f"p value is {p_value1}")
        # p 越小越显著

        # Gold1 & Gold3 Regression and ADF test
        X2 = sm.add_constant(Gold1_log)
        Y2 = Gold3_log
        model2 = sm.OLS(Y2, X2)
        results2 = model2.fit()
        sigma2 = math.sqrt(results2.mse_resid)
        slope2 = results2.params[1]
        intercept2 = results2.params[0]
        res2 = results2.resid
        zscore2 = res2/sigma2

        adf2 = adfuller(res2)

        p_value2 = adf2[1]
        test_passed2 = p_value2 <= general_setting['p_value_threshold']

        
        # Gold1 & Gold3 Regression and ADF test
        X3 = sm.add_constant(Gold2_log)
        Y3 = Gold3_log
        model3 = sm.OLS(Y3, X3)
        results3 = model3.fit()
        sigma3 = math.sqrt(results3.mse_resid)
        slope3 = results3.params[1]
        intercept3 = results3.params[0]
        res3 = results3.resid
        zscore3 = res3/sigma3

        adf3 = adfuller(res3)

        p_value3 = adf3[1]
        test_passed3 = p_value3 <= general_setting['p_value_threshold']


        # Kalman Filtering to get parameters
        if method == "Kalman_Filter":
            obs_mat = sm.add_constant(Gold1_log, prepend=False)[:, np.newaxis]
            trans_cov = 1e-5 / (1 - 1e-5) * np.eye(2)
            kf = KalmanFilter(n_dim_obs=1, n_dim_state=2,
                  initial_state_mean=np.ones(2),
                  initial_state_covariance=np.ones((2, 2)),
                  transition_matrices=np.eye(2),
                  observation_matrices=obs_mat,
                  observation_covariance=0.5,
                  transition_covariance=0.000001 * np.eye(2))
        
            state_means, state_covs = kf.filter(Gold2_log)
            slope = state_means[:, 0][-1]
            intercept = state_means[:, 1][-1]

        self.printed = True
        return [test_passed1, zscore1, slope1]


    def OnData(self,slice):

        for chain in slice.FutureChains:
            contracts = list(filter(lambda x: x.Expiry > self.Time + timedelta(90), chain.Value))
            if len(contracts) == 0: 
                continue
            front1 = sorted(contracts, key = lambda x: x.Expiry)[0]
            front2 = sorted(contracts, key = lambda x: x.Expiry)[1]
            front3 = sorted(contracts, key = lambda x: x.Expiry)[2]

            self.Debug (" Expiry " + str(front3.Expiry) + " - " + str(front3.Symbol))
            self.gold1_contract = front1.Symbol
            self.gold2_contract = front2.Symbol
            self.gold3_contract = front3.Symbol