Overall Statistics
Total Trades
9309
Average Win
0.46%
Average Loss
-0.11%
Compounding Annual Return
-4.806%
Drawdown
25.400%
Expectancy
-0.015
Net Profit
-10.119%
Sharpe Ratio
-0.397
Sortino Ratio
-0.567
Probabilistic Sharpe Ratio
2.107%
Loss Rate
82%
Win Rate
18%
Profit-Loss Ratio
4.34
Alpha
-0.067
Beta
0.665
Annual Standard Deviation
0.15
Annual Variance
0.022
Information Ratio
-0.591
Tracking Error
0.119
Treynor Ratio
-0.089
Total Fees
$12027.11
Estimated Strategy Capacity
$8400000.00
Lowest Capacity Asset
QQQ RIWIV7K5Z9LX
Portfolio Turnover
1171.47%
#region imports
from AlgorithmImports import *
#endregion


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, self.algo.length, self.algo.nstd, Resolution.Minute)
        self.vwap = self.algo.VWAP(self.symbol)
        self.sma = self.algo.SMA(self.symbol, self.algo.length)
        self.sigma = self.algo.STD(self.symbol, self.algo.length)
         
        self.smas = RollingWindow[float](self.algo.n + 1)
        self.vwaps = RollingWindow[float](self.algo.n + 1)

    # Decided on a delegate method, rather than a consolidator 
    # just simpler, less boilerplate.
    def OnData(self, data: Slice):
        data = data.Bars
        if not data.ContainsKey(self.symbol): return 
        bar = data[self.symbol]
        tm = bar.EndTime

        self.UpdateWindows(bar)
        if self.IsReady:
            
            if self.EntriesToday(tm) < self.algo.entries_per_day:
                entered = self.EntryLogic(bar)
                if entered:
                    self.IncrementEntriesToday(tm)

        if self.Invested:
            self.ExitPackage()

            if self.algo.signal_exit:
                if self.IsLong and bar.Close < self.vwaps[0]:
                    self.algo.Liquidate(self.symbol, "VWAP LX")
            
                if self.IsShort and bar.Close > self.vwaps[0]:
                    self.algo.Liquidate(self.symbol, "VWAP SX")


    def UpdateWindows(self, bar):
        if self.sma.IsReady:
            self.smas.Add(self.sma.Current.Value)

        if self.vwap.IsReady:
            self.vwaps.Add(self.vwap.Current.Value)

    def EntryLogic(self, bar):
        alloc_pct = 1.0 / len(self.algo.strats) 

        long_ok = self.algo.direction >= 0 and not self.IsLong
        if long_ok:
            sma_ok = bar.Close > self.sma.Current.Value
            sma_trend = self.smas[0] > self.smas[self.algo.n]
            above_vwap = bar.Close > self.vwaps[0]

            # A bit hacked -- just using what we have available. (No vwap bands)
            # below_lower = bar.Close < self.vwaps[0]  - self.sigma.Current.Value * self.algo.nstd

            # Removed sma_ok and sma_trend
            if above_vwap:
                self.algo.SetHoldings(self.symbol, alloc_pct)
                return True 

        short_ok = self.algo.direction <= 0 and not self.IsShort
        if short_ok:
            below_vwap = bar.Close < self.vwaps[0]

            if below_vwap:
                self.algo.SetHoldings(self.symbol, -alloc_pct)
                return True

    def ExitPackage(self):
        self.StopPct(self.algo.stop_pct)
        self.TgtPct(self.algo.tgt_pct)


    def StopPct(self, pct):
        if pct == 0.0: return

        urpct = self.algo.Portfolio[self.symbol].UnrealizedProfitPercent
        ur = self.algo.Portfolio[self.symbol].UnrealizedProfit
        if self.algo.Portfolio[self.symbol].UnrealizedProfitPercent < -1 * pct:
            self.algo.Liquidate(self.symbol, tag=f"Stop -- {ur}")
    
    def TgtPct(self, pct):
        if pct == 0.0: return

        ur = self.algo.Portfolio[self.symbol].UnrealizedProfit
        if self.algo.Portfolio[self.symbol].UnrealizedProfitPercent > pct:
            self.algo.Liquidate(self.symbol, tag=f"Tgt -- {ur}")
    
    @property
    def Sigma(self):
        if self.IsReady:
            return self.bb.Upper.Current.Value - self.bb.Middle.Current.Value 

    @property
    def IsReady(self):
        return self.bb.IsReady and self.vwap.IsReady and self.sma.IsReady and self.vwaps.IsReady

    @property
    def Invested(self):
        return self.algo.Portfolio[self.symbol].Invested

    @property
    def IsLong(self):
        return self.algo.Portfolio[self.symbol].IsLong

    @property    
    def IsShort(self):
        return self.algo.Portfolio[self.symbol].IsShort     

    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
# region imports
from AlgorithmImports import *
# endregion

from Strategy import Strategy

class SwimmingRedTermite(QCAlgorithm):

    tickers = ['QQQ']

    length = 50
    nstd = 2

    # Direction -- 1 is long only, -1 is short only, 0 is both.
    direction = 1

    entries_per_day = 100

    # Bars back, used for slope measure.
    n = 7

    # If 0, will not set. 
    # .01 == 1%
    tgt_pct = 0.0
    stop_pct = 0.0 
    signal_exit = True
    eodx = True

    

    symbols = []
    strats = {}

    def Initialize(self):
        self.SetStartDate(2022, 1, 1)
        self.SetCash(100000)

        # Set benchmark -- SPY (most common, need it unrelated to tickers)
        self.AddEquity('SPY', Resolution.Minute)
        self.SetBenchmark("SPY")

        # symbol = self.AddEquity("QQQ", Resolution.Minute).Symbol
        for t in self.tickers:
            s = self.AddEquity(t, Resolution.Minute).Symbol
            self.symbols.append(s)
            self.strats[s] = Strategy(self, s)

        self.Schedule.On(self.DateRules.EveryDay("SPY"),
                 self.TimeRules.BeforeMarketClose("SPY", 1),
                 self.EODx)

    def EODx(self):
        if self.eodx:
            self.Liquidate(tag="EOD")

    def OnData(self, data: Slice):
        gap_pcts = []
        for symbol, strat in self.strats.items():
            strat.OnData(data)