Overall Statistics
Total Trades
34
Average Win
5.89%
Average Loss
-2.40%
Compounding Annual Return
13.427%
Drawdown
9.600%
Expectancy
1.030
Net Profit
47.893%
Sharpe Ratio
1.226
Probabilistic Sharpe Ratio
59.821%
Loss Rate
41%
Win Rate
59%
Profit-Loss Ratio
2.45
Alpha
0.094
Beta
0.268
Annual Standard Deviation
0.116
Annual Variance
0.013
Information Ratio
-0.189
Tracking Error
0.193
Treynor Ratio
0.531
Total Fees
$64.25
Estimated Strategy Capacity
$71000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
false_bool = False
if false_bool:
    from AlgorithmImports import *
from collections import deque
import configs as cfg

from datetime import timedelta

class EnergeticBlueDonkey(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2018, 1, 1)
        self.SetEndDate(2021, 2, 5)
        self.SetCash(100000)
        self.symbol = self.AddEquity('SPY', Resolution.Minute).Symbol
        self.gc = GoldenCross(cfg.fast_sma_period, cfg.slow_sma_period)

        # makes it so we emulate Daily Resolution on Minute Resolution
        # this is necessary since we are dealing with options, which only work on Minute or finer data
        self.curr_day = -1 

        self.SetWarmUp(cfg.slow_sma_period + 3, Resolution.Daily)

    def OnData(self, data:Slice):
        if self.curr_day == self.Time.day or not data.Bars.ContainsKey(self.symbol):
            return
        self.curr_day = self.Time.day

        self.gc.Update(data[self.symbol])

        if self.IsWarmingUp or not self.gc.IsReady:
            return
        
        self.Plot('GoldenCross', 'Value', self.gc.Value)
        
        # buy if golden cross + entry timing
        # liquidate if death cross
        if self.gc.Value == 2:
            self.SetHoldings(self.symbol, 1)
        elif self.gc.Value == 0:
            self.SetHoldings(self.symbol, 0)


class GoldenCross:
    def __init__(self, fast_period:int, slow_period:int):
        '''
        GoldenCross indicator
        .Value = 0 -> not golden cross or death cross
        .Value = 1 -> golden cross formed, entry not
        .Value = 2 -> entry formed after golden cross
        '''
        self.Value = 0

        self.fast_sma = SimpleMovingAverage(fast_period)
        self.slow_sma = SimpleMovingAverage(slow_period)

        # fast sma - slow sma
        self.sma_diffs = deque(maxlen=3)

    def dq_rdy(self, vals:deque):
        '''
        returns True iff the deque is has maxlen elements
        '''
        return len(vals) == vals.maxlen

    def Update(self, input:TradeBar):
        '''
        updates the Golden Cross indicator with a new bar of data
        returns self.IsReady
        '''
        self.Time = input.EndTime
        close = input.Close

        self.fast_sma.Update(self.Time, close)
        self.slow_sma.Update(self.Time, close)
        
        if not self.slow_sma.IsReady:
            # since the slow_sma takes more values, if its ready
            # the fast_sma must be ready
            return False

        self.sma_diffs.append(
            self.fast_sma.Current.Value - self.slow_sma.Current.Value  
        )
        
        if not self.dq_rdy(self.sma_diffs):
            return False 
        
        is_crossed = (
            self.sma_diffs[2] > 0 and self.sma_diffs[1] < 0 and self.sma_diffs[0] < 0
        ) # if the fast just recently rises above the slow

        is_death_crossed = (
            self.sma_diffs[2] < 0 and self.sma_diffs[1] > 0 and self.sma_diffs[0] > 0
        ) # if the fast just recently dips above the slow

        if is_death_crossed:
            self.Value = 0
        if self.Value <= 0 and is_crossed:
            self.Value = 1
        elif self.Value == 1 and cfg.entry_condition(close, self.fast_sma.Current.Value, self.slow_sma.Current.Value)   :
            self.Value = 2

        return True
    
    def Warmup(self):
        pass

    @property
    def IsReady(self):
        '''
        returns True iff the indicator is ready to use
        '''
        return self.dq_rdy(self.sma_diffs)
#BEGIN GoldenCross configurations
fast_sma_period = 5
slow_sma_period = 20
assert(fast_sma_period < slow_sma_period)

# entry condition after cross has formed
def entry_condition(curr_price:float, fast_sma:float, slow_sma:float)->bool:
    '''
    return True iff entry condition is met 
    '''
    sma_avg = (fast_sma + slow_sma) / 2

    # 4% within average of two SMAs
    return abs(1-(curr_price / sma_avg)) < .04
#END GoldenCross configurations