Overall Statistics |
Total Trades 4668 Average Win 0.21% Average Loss -0.20% Compounding Annual Return 14.762% Drawdown 8.000% Expectancy 0.123 Net Profit 76.140% Sharpe Ratio 0.899 Sortino Ratio 0.79 Probabilistic Sharpe Ratio 56.339% Loss Rate 45% Win Rate 55% Profit-Loss Ratio 1.05 Alpha 0.068 Beta 0.185 Annual Standard Deviation 0.093 Annual Variance 0.009 Information Ratio -0.018 Tracking Error 0.174 Treynor Ratio 0.452 Total Fees $4919.89 Estimated Strategy Capacity $63000000.00 Lowest Capacity Asset AMD R735QTJ8XC9X Portfolio Turnover 44.08% |
#region imports from AlgorithmImports import * #endregion # Your New Python File class Strategy: def __init__(self, algo, symbol): self.algo = algo self.symbol = symbol self.day_close = None self.day_open = None self.entries_by_date = {} self.high_of_day = None self.low_of_day = None # Just to see how it's done -- not too bad. self.bb = self.algo.BB(self.symbol, 50, 2, Resolution.Minute) self.atr = self.algo.ATR(self.symbol, 20, resolution=Resolution.Minute) def OnData(self, data: Slice): data = data.Bars if not data.ContainsKey(self.symbol): return bar = data[self.symbol] tm = bar.EndTime # Update things needed for strategy self.UpdateHighLowDay(bar) self.UpdateGaps(bar) # Entry Logic if not self.Invested: if self.EntriesToday(tm) < self.algo.entries_per_day: entered = self.EntryLogic(bar) if entered: self.IncrementEntriesToday(tm) # Exit Logic (Stops) if self.Invested: self.ExitPackage() #region Entry Logic def EntryLogic(self, bar): # TODO: consider using atr, a minimal range, to improve signal for GC # if self.bb.IsReady: # self.algo.Log(f'{self.symbol} -- bb: {self.bb.MiddleBand.Current.Value}') # self.algo.Log(f'{self.symbol} -- atr: {self.atr.Current.Value}') alloc_pct = 1.0 / len(self.algo.strats) # if self.algo.gap_fill and abs(self.GapPct) >= 2.0: return False # GAP UP if self.GapPct > self.algo.thres: # Gap up, we are selling if we can with dir. if self.algo.gap_fill: if self.algo.dir <= 0: self.algo.SetHoldings(self.symbol, -1 * alloc_pct) return True # Gap up, we are buying if we can w dir. else: # tried testing this high day thing... # and bar.High == self.high_of_day: if self.algo.dir >= 0: self.algo.SetHoldings(self.symbol, alloc_pct) return True # GAP DN if self.GapPct < -1 * self.algo.thres: # Buy, on gap down if self.algo.gap_fill: # and bar.High >= ((self.high_of_day - self.low_of_day)*.5) + self.low_of_day (kinda works..) if self.algo.dir >= 0: self.algo.SetHoldings(self.symbol, alloc_pct) return True else: if self.algo.dir <= 0: self.algo.SetHoldings(self.symbol, -1 * alloc_pct) return True return False #endregion # region Exit Logic def ExitPackage(self): self.StopPct() self.TgtPct() def StopPct(self, pct=None): urpct = self.algo.Portfolio[self.symbol].UnrealizedProfitPercent ur = self.algo.Portfolio[self.symbol].UnrealizedProfit # self.algo.Log(f'{self.symbol} -- {urpct} -- {ur}') if not pct: pct = self.algo.stop_pct if self.algo.Portfolio[self.symbol].UnrealizedProfitPercent < -1 * pct: self.algo.Liquidate(self.symbol, tag=f"Stop -- {ur}") def TgtPct(self, pct=None): if not pct: pct = self.algo.tgt_pct ur = self.algo.Portfolio[self.symbol].UnrealizedProfit if self.algo.Portfolio[self.symbol].UnrealizedProfitPercent > pct: self.algo.Liquidate(self.symbol, tag=f"Tgt -- {ur}") # endregion # region Properties @property def Gap(self): if self.day_close and self.day_open: return self.day_open - self.day_close return 0 # 0 or None? @property def GapPct(self): if self.day_close and self.day_open: return ((self.day_open - self.day_close) / self.day_close) * 100 return 0 # or none? @property def IsReady(self): return self.day_close and self.day_open @property def Invested(self): return self.algo.Portfolio[self.symbol].Invested # endregion # region State Mgmt def UpdateHighLowDay(self, bar): tm = bar.EndTime h,l = bar.High, bar.Low if tm.hour == 9 and tm.minute == 31: self.high_of_day = bar.High self.low_of_day = bar.Low elif tm.hour >= 16: self.high_of_day = None self.low_of_day = None else: self.high_of_day = max(h, self.high_of_day) self.low_of_day = min(l, self.low_of_day) def UpdateGaps(self, bar): tm = bar.EndTime # self.algo.Log(f'{self.symbol} -- tm: {tm} -- type: {type(tm)}') if tm.hour == self.algo.bod_hr and tm.minute == self.algo.bod_mn: self.day_open = bar.Close if tm.hour == self.algo.closed_hr and tm.minute == self.algo.closed_mn: self.day_close = bar.Close self.day_open = None # Need to await the next open! def EntriesToday(self, bar_end_time): date = bar_end_time.date() if date not in self.entries_by_date: self.entries_by_date[date] = 0 return self.entries_by_date[date] def IncrementEntriesToday(self, bar_end_time): date = bar_end_time.date() if date not in self.entries_by_date: self.entries_by_date[date] = 1 else: self.entries_by_date[date] += 1 # endregion
# region imports from AlgorithmImports import * # endregion from Strategy import Strategy ''' 'Best' GF Params: stop_dec: .005, tgt_dec: .0275 - .0325, gap_thres_pct: 3; (fill=1, dir=1) 'Best' GC Params: stop_dec: .055, tgt_dec: .035, gap_thres_pct: .75 ''' class GeekyVioletJellyfish(QCAlgorithm): tickers = ['NVDA','TSLA','AMD','AMZN','AAPL','MSFT','NFLX'] #,'MARA'] # How should I format this... # config = [] # want to give EACH symbol their own stop, target, etc? resolution = Resolution.Minute symbols = [] strats = {} # -------------------- Parameters ------------------- entries_per_day = 3 # Per strategy. # do 30 mins off open, and close. bod_hr = 9 bod_mn = 59 # GF does better with 31 time. closed_hr = 15 closed_mn = 30 eod_exit_offset = 30 def Initialize(self): self.SetStartDate(2020, 1, 1) # self.SetEndDate(2023, 2, 1) self.AddEquity('SPY', self.resolution) # stop, and target, are in form of decimal -- .05 = 5% self.stop_pct = self.GetParameter("stop_dec", .05) self.tgt_pct = self.GetParameter("tgt_dec", .1) self.thres = self.GetParameter("gap_thres_pct", 0.0) # 1 = 1%, REAL percent. self.gap_fill = bool(self.GetParameter("fill", 1)) self.dir = self.GetParameter("dir", 1) self.SetCash(100000) for t in self.tickers: s = self.AddEquity(t, self.resolution).Symbol self.symbols.append(s) self.strats[s] = Strategy(self, s) self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", self.eod_exit_offset), self.EODx) def EODx(self): self.Liquidate(tag="EOD") def OnData(self, data: Slice): gap_pcts = [] for symbol, strat in self.strats.items(): strat.OnData(data) if strat.IsReady: gap_pcts.append((symbol, strat.GapPct)) # Sort this... (Skip for now) # all_ready = len([i for i in self.strats if i.IsReady]) == len(self.strats) # desc_gap_pcts = [i for i in sorted(gap_pcts, key=lambda x: x[1], reverse=True)] # asc_gap_pcts = [i for i in sorted(gap_pcts, key=lambda x: x[1], reverse=False)] # biggest gaps first... # Try 'biggest' gaps -- clone and try that?