Overall Statistics |
Total Trades 10137 Average Win 0.07% Average Loss -0.07% Compounding Annual Return -1.113% Drawdown 7.500% Expectancy -0.009 Net Profit -3.849% Sharpe Ratio -1.535 Probabilistic Sharpe Ratio 0.034% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 1.00 Alpha -0.024 Beta 0.007 Annual Standard Deviation 0.015 Annual Variance 0 Information Ratio -0.728 Tracking Error 0.152 Treynor Ratio -3.185 Total Fees $0.00 Estimated Strategy Capacity $2800000.00 Lowest Capacity Asset EWI R735QTJ8XC9X Portfolio Turnover 62.40% |
# region imports from AlgorithmImports import * from statsmodels.tsa.vector_ar.vecm import VECM # endregion class CreativeRedBadger(QCAlgorithm): tickers = ['SPY','QQQ','IWM','DIA','EZU', 'EWJ','FXI','EWT','EWG','EWC','EWI','EWW','INDA'] length = 10 sigma = 1.5 res = Resolution.Hour # Daily, Hour, Minute time_exit_bars = 10 # Same as length? (HL?) extra_edge = 0.05 # --------------------------------------------------- wt_arr = None # Numpy array. wt_vec = [] wts_by_symbol = {} series_ask_terms_by_symbol = {} series_bid_terms_by_symbol = {} position = 0 bar_ct = 0 def Initialize(self): self.SetStartDate(2020, 4, 19) # self.SetEndDate(2022,5, 20) self.SetCash(100000) self.BuyBasis = RollingWindow[float](self.length) self.SellBasis = RollingWindow[float](self.length) self.Basis = RollingWindow[float](self.length) self.bb = BollingerBands(self.length, self.sigma) symbols = [] for t in self.tickers: try: symbol = self.AddEquity(t, self.res).Symbol symbols.append(symbol) except: pass self.SetSecurityInitializer(lambda x: x.SetFeeModel(ConstantFeeModel(0))) # # 0 fees, for now. # for security in self.Securities: # security.SetFeeModel(ConstantFeeModel(0)) # Only run this weekly? self.Schedule.On(self.DateRules.WeekStart(0), self.TimeRules.AfterMarketOpen("SPY", -10), self.BeforeMarketOpen) def BeforeMarketOpen(self): ## TODO: when we are dealing with quoting, and bid/ask spreads -- use this. # quote_bars_df = self.History(QuoteBar, self.Securities.Keys, 5, Resolution.Minute) df = self.History(self.Securities.Keys, 10000, self.res) # IF fails, use DAILY, or HOURLY closes = df.unstack(level=0).close closes.dropna(inplace=True, how='any') self.Log(f'Closes -- head: {closes.head()}') closes = closes[[i for i in self.Securities.Keys]] # Addit, for order safety. vecm = VECM(closes, deterministic='n', k_ar_diff=1) vecm_fit = vecm.fit() norm_wts = vecm_fit.beta / np.abs(vecm_fit.beta).sum() self.wt_vec = norm_wts[:,0] # (n,) vec self.wt_arr = norm_wts # THIS might have broken something... this was using Securities.Keys for n, k in enumerate(self.Securities.Keys): # Ensure columns line up w this order. self.wts_by_symbol[k] = self.wt_vec[n] self.Log(f'Weights: {self.wts_by_symbol.items()}') self.basis = np.dot(closes, vecm_fit.beta) # Valid... # self.long_targets = [PortfolioTarget(self.s1, per_symbol), PortfolioTarget(self.s2, -per_symbol)] # self.short_targets = [PortfolioTarget(self.s1, -per_symbol), PortfolioTarget(self.s2, per_symbol)] # self.flat_targets = [PortfolioTarget(self.s1, 0.0), PortfolioTarget(self.s2, 0.0)] self.long_targets = [] self.flat_targets = [] self.short_targets = [] for symbol, wt in self.wts_by_symbol.items(): print(type(symbol), symbol) # self.Log(f'type: {type(symbol)}, symbol: {symbol} -- wt: {wt}, type: {type(wt)}') pft = PortfolioTarget(symbol, wt) self.long_targets.append(pft) pft = PortfolioTarget(symbol, 0.0) self.flat_targets.append(pft) pft = PortfolioTarget(symbol, -1.0 * wt) self.short_targets.append(pft) # WARMUP? basis = pd.DataFrame(self.basis[:,0], index=closes.index, columns=['Series']) for dt, row in basis.iterrows(): # self.Log(f'dt: {dt}, Basis: {row}, {row.Series}') b = row.Series self.bb.Update(dt, b) self.Basis.Add(b) def OnSecuritiesChanged(self, changes): for security in changes.AddedSecurities: security.SetFeeModel(ConstantFeeModel(0.0)) def OnData(self, data: Slice): # Check data valid # data = data.QuoteBars data = data.Bars if not data.ContainsKey('SPY'): self.Log('No Data -- return out') return # Check weights valid (Model Fit) if len(self.wt_vec) == 0: return # # How do we price this out, to quote them iteratively? # # i.e. pull a wrt b-f, etc. # # Price each 'term', maybe? curr_basis = 0 for symbol, wt in self.wts_by_symbol.items(): try: # If wt is positive, we are BUYING this, so price it wrt ASK (for pos spread) buy_term = data[symbol].Ask.Close * wt sell_term = data[symbol].Bid.Close * wt except: buy_term = data[symbol].Close sell_term = buy_term # Assuming this is a buy, our bid term would be pricing the series as if buying # thus, for bid, we are pricing at ask of positive weighted symbols (BUY Spread) # for ask, we are pricing at bid of negative weighted symbols.(SELL Spread) term = buy_term if wt > 0 else sell_term self.series_bid_terms_by_symbol[symbol] = buy_term if wt > 0 else buy_term self.series_ask_terms_by_symbol[symbol] = sell_term if wt > 0 else sell_term curr_basis += term self.Basis.Add(curr_basis) self.bb.Update(self.Time, curr_basis) # # This is a bit muddled, but it should work. # if not self.bb.IsReady: return # ## TODO: price out each leg, wrt the rest... # # Prototype elsewhere, then bring into this. upper, lower, mid = self.bb.UpperBand.Current.Value, self.bb.LowerBand.Current.Value, self.bb.MiddleBand.Current.Value self.Log(f'Basis BB: \ Upper: {self.bb.UpperBand.Current.Value},\ Lower: {self.bb.LowerBand.Current.Value}\ Middle: {self.bb.MiddleBand.Current.Value}') curr = curr_basis if self.position == 0: # could sum the bid_terms for bid basis. if curr < lower - self.extra_edge: self.position = 1 self.SetHoldings(self.short_targets, False, f"LE -- Series: {curr}") if curr > upper + self.extra_edge: self.position = -1 self.SetHoldings(self.long_targets, False, f"SE -- Series: {curr}") # Increment bar count if self.position != 0: self.bar_ct += 1 if self.bar_ct > self.time_exit_bars: self.Liquidate() opnl = self.Portfolio.TotalUnrealisedProfit # Long -- Looking to exit. if self.position > 0: if curr > mid: self.position = 0 self.bar_ct = 0 self.SetHoldings(self.flat_targets, False, f"LX -- Series: {curr}, PNL: {opnl}") return # Short -- Looking to exit. if self.position < 0: if curr < mid: self.position = 0 self.bar_ct = 0 self.SetHoldings(self.flat_targets, False, f"SX -- Series: {curr}, PNL: {opnl}") return