| 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?