Overall Statistics
Total Trades
797
Average Win
0.01%
Average Loss
-0.02%
Compounding Annual Return
-0.836%
Drawdown
1.200%
Expectancy
-0.137
Net Profit
-0.949%
Sharpe Ratio
-1.643
Probabilistic Sharpe Ratio
0.009%
Loss Rate
47%
Win Rate
53%
Profit-Loss Ratio
0.63
Alpha
-0.006
Beta
0.002
Annual Standard Deviation
0.004
Annual Variance
0
Information Ratio
0.103
Tracking Error
0.189
Treynor Ratio
-2.326
Total Fees
$798.17
Estimated Strategy Capacity
$13000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
from AlgorithmImports import *
from QuantConnect.Indicators import Indicator, BarIndicator
from QuantConnect.Python import PythonSlice
from QuantConnect.Orders import OrderTicket, OrderStatus, OrderResponse, OrderEvent
import datetime as dt
import time as tm

class SymbolData:
    def __init__(self, algorithm: QCAlgorithm, symbol: Symbol):
        self.algorithm = algorithm
        self.symbol = symbol

        # Create an indicator
        self.indicators: Dict[str, IndicatorBase] = {}
        
        # price window rolling
        self.trade_bars: RollingWindow[TradeBar] = RollingWindow[TradeBar](10)
        
        # indicators
        self.indicators['ema9'] = SimpleMovingAverage(symbol.Value, 9)
        self.indicators['ema21'] = SimpleMovingAverage(symbol.Value, 21)
        self.indicators['ema50'] = SimpleMovingAverage(symbol.Value, 50)
        self.indicators['sar'] = ParabolicStopAndReverse(symbol.Value, 0.02, 0.2)
        self.indicators['adx'] = AverageDirectionalIndex(symbol.Value, 14)
        self.indicators['std'] = StandardDeviation(symbol.Value, 20)
        
        # trade targets
        self.orders: Dict[Symbol, OrderTicket] = {'stop': None, 'tp': None, 'entry': None}
        
        # warm up indicators
        for _, indicator in self.indicators.items():
            algorithm.WarmUpIndicator(symbol, indicator)

        # Create a consolidator to update the indicator
        self.consolidator = TradeBarConsolidator(1) 
        self.consolidator.DataConsolidated += self.OnDataConsolidated

        # Register the consolidator to update the indicator
        algorithm.SubscriptionManager.AddConsolidator(symbol, self.consolidator)    
        

    def OnDataConsolidated(self, sender: object, consolidated_bar: TradeBar) -> None:
        for _, indicator in self.indicators.items():
            if issubclass(type(indicator), Indicator):
                indicator.Update(consolidated_bar.Time, consolidated_bar.Close)
            elif issubclass(type(indicator), BarIndicator):
                indicator.Update(consolidated_bar)
            else:
                indicator.Update(consolidated_bar.Time, consolidated_bar.Close)
        self.trade_bars.Add(consolidated_bar)

    # If you have a dynamic universe, remove consolidators for the securities removed from the universe
    def dispose(self) -> None:
        self.algorithm.SubscriptionManager.RemoveConsolidator(self.symbol, self.consolidator)
    
class Test(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2021, 10, 7)  # Set Start Date
        self.SetEndDate(2022, 11, 27)  # Set End Date
        self.SetCash(100000)  # Set Strategy Cash
        self.SetBrokerageModel(BrokerageName.QuantConnectBrokerage, AccountType.Margin)    
        
        # list of symbols
        self.symbol_data_by_symbol: Dict[Symbol,SymbolData] = {}
        
        # Add SPY to the algorithm
        self.AddEquity("SPY", Resolution.Minute, extendedMarketHours=True)

    def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
        for security in changes.AddedSecurities:
            self.symbol_data_by_symbol[security.Symbol] = SymbolData(self, security.Symbol)

        # If you have a dynamic universe, track removed securities
        for security in changes.RemovedSecurities:
            symbol_data = self.symbol_data_by_symbol.pop(security.Symbol, None)
            if symbol_data:
                symbol_data.dispose()
    
    def OnOrderEvent(self, orderEvent: OrderEvent) -> None:
        cur_sym_port = self.symbol_data_by_symbol[orderEvent.Symbol]
        cur_orders = cur_sym_port.orders
        if orderEvent.Status == OrderStatus.Filled:
            if cur_orders['stop']!= None and orderEvent.OrderId == cur_orders['stop'].OrderId:
                res = cur_orders['tp'].Cancel()
                if not res.IsSuccess:
                    self.Debug(f"Failed to cancel take profit order: {res}")
                else:
                    cur_orders['tp'] = None
                    cur_orders['stop'] = None
                    cur_orders['entry'] = None
            
            elif cur_orders['tp']!= None and orderEvent.OrderId == cur_orders['tp'].OrderId:
                if cur_orders['stop'].Quantity - orderEvent.FillQuantity == 0:
                    cur_orders['stop'].Cancel()
                    cur_orders['tp'] = None
                    cur_orders['stop'] = None
                    cur_orders['entry'] = None
                else:
                    nq = cur_orders['stop'].Quantity - orderEvent.FillQuantity
                    res = cur_orders['stop'].UpdateQuantity(nq)
                    if not res.IsSuccess:
                        self.Debug(f"Failed to update stop loss order: {res}")
                    shares = int(0.5*nq) if int(0.5*nq) > 5 else nq
                    cur_orders['tp'] = self.LimitOrder(orderEvent.Symbol, shares, (orderEvent.FillPrice + (orderEvent.FillPrice - cur_orders['entry'].AverageFillPrice)))
        self.symbol_data_by_symbol[orderEvent.Symbol].orders = cur_orders             

    def OnData(self, data: PythonSlice):
        sym: Symbol
        for sym in data.Keys:
            # current bar
            sym_data: TradeBar = data.get(sym)
            if sym_data == None:
                continue
            
            # data
            bars = self.symbol_data_by_symbol[sym].trade_bars
            inds = self.symbol_data_by_symbol[sym].indicators
            
            # validating indicator state
            ready = all([ind.IsReady for _, ind in inds.items()]) and bars.IsReady
            if not ready:
                continue

            # functional code
            mas = 1 if ((inds['ema9'].Current.Value > inds['ema21'].Current.Value) and (inds['ema21'].Current.Value > inds['ema50'].Current.Value)) else 0
            mas = -1 if ((inds['ema9'].Current.Value < inds['ema21'].Current.Value) and (inds['ema21'].Current.Value < inds['ema50'].Current.Value)) else mas
            
            adx = 1 if ((inds['adx'].Current.Value > 25) and (inds['adx'].PositiveDirectionalIndex.Current.Value > inds['adx'].NegativeDirectionalIndex.Current.Value)) else 0
            adx = -1 if ((inds['adx'].Current.Value > 25) and (inds['adx'].PositiveDirectionalIndex.Current.Value < inds['adx'].NegativeDirectionalIndex.Current.Value)) else adx
            
            sar = 1 if inds['sar'].Current.Value < bars[0].Close else 0
            sar = -1 if inds['sar'].Current.Value > bars[0].Close else sar
            
            bo = 1 if ((bars[1].Low < bars[2].Low) and (bars[2].Low < bars[3].Low) and (bars[0].Close > bars[1].High)) else 0
            bo = -1 if ((bars[1].High > bars[2].High) and (bars[2].High > bars[3].High) and (bars[0].Close < bars[1].Low)) else bo
            
            
            timeh = int(str(sym_data.EndTime)[11:13])
            timem = int(str(sym_data.EndTime)[14:16])
            time = 1 if (timeh>=9) and timeh<16 else 0
            time = 0 if (timeh==9 and timem<30) else time
            time = 2 if (timeh==16 and timem==0) else time

            # make a new long trade setting stopout and take profit
            if mas==1 and adx==1 and sar==1 and bo==1 and time==1:
                if not self.Portfolio[sym].Invested:
                    stopout = min([bars[0].Low, bars[1].Low, bars[2].Low, bars[3].Low])
                    shares = int(25/(1.1*(bars[0].Close-stopout)))
                    sym_port = self.symbol_data_by_symbol[sym]
                    sym_port.orders['entry'] = self.MarketOrder(sym, shares)         
                    
                    tm.sleep(5)
                    
                    if sym_port.orders['entry'].Status != OrderStatus.Filled:
                        res = sym_port.orders['entry'].Cancel()
                        if res.IsSuccess != OrderResponse.Success:
                            self.Debug(f"Failed to cancel order: {res}")
                    else:
                        sym_port.orders['stop'] = self.StopMarketOrder(sym, -1*sym_port.orders['entry'].QuantityFilled, stopout)
                        sym_port.orders['tp'] = self.LimitOrder(sym, int(-0.5*sym_port.orders['entry'].QuantityFilled), bars[0].Close+(sym_port.orders['entry'].AverageFillPrice-stopout))
            
            if (self.Portfolio[sym].Invested and not (mas==1 and adx==1 and sar==1)) or time==2:
                self.Liquidate(sym)
                self.symbol_data_by_symbol[sym].orders['stop'] = None
                self.symbol_data_by_symbol[sym].orders['tp'] = None           
                self.symbol_data_by_symbol[sym].orders['entry'] = None