Overall Statistics
Total Trades
1735
Average Win
0.00%
Average Loss
0%
Compounding Annual Return
53.244%
Drawdown
0.300%
Expectancy
0
Net Profit
1.750%
Sharpe Ratio
20.416
Probabilistic Sharpe Ratio
99.675%
Loss Rate
0%
Win Rate
100%
Profit-Loss Ratio
0
Alpha
0.576
Beta
0.363
Annual Standard Deviation
0.03
Annual Variance
0.001
Information Ratio
13.75
Tracking Error
0.038
Treynor Ratio
1.667
Total Fees
$2324.90
Estimated Strategy Capacity
$0
Lowest Capacity Asset
ES XRX5ODZTG8OX
from math import floor
from collections import deque
import numpy as np
from AlgorithmImports import *
# import requests

class FatRedOrangeLeopard(QCAlgorithm):

    def Initialize(self):
        
        #1. Initialize the start, end dates and cash
        self.SetStartDate(2021, 7, 1)
        self.SetEndDate(2021, 7, 15) 
        self.SetCash(1000000) 
        self.SetBenchmark("SPY")
        
        #1. Request /ES futures and save the ES security
        self.EMini = self.AddFuture(Futures.Indices.SP500EMini, Resolution.Tick)
        self.EMini.FeeModel = ConstantFeeModel(1.34)
        self.EMini.SetFilter(0, 90)
        
        # MISC
        self.Bid = 0
        self.Ask = 0
        self.BidSize = 0
        self.AskSize = 0
        self.LastPrice = 0
        self.Volume = 0
        
        # Entry Parameters        
        self.entryPrice = 0
        self.entryTicket = None
        self.entryTime = datetime.min
        self.nextEntryTime = self.Time
        
        # Exit Parameters
        self.exitPrice = 0
        self.exitTicket = None
        self.exitTime = datetime.min
        
        self.liquidContract = None
        self.timeToExpiryLimit = 5 * 24 * 60 * 60 # 5 days, in seconds
        self.minDelay = 1 * 60 # seconds
        self.maxDelay = 10 * 60 # seconds
        self.chaseBid = 60 # seconds
        self.contractsToBuy = 1
        self.lastHour = 0 # hours
        
        #MISC:
        # Set up a fail safe to ensure we're flat before market close
        # self.Schedule.On(self.DateRules.EveryDay(),
        #          self.TimeRules.BeforeMarketClose(self.symbol, 15),      
        #          self.ExitPositions)

    def OnMarginCallWarning(self):
        self.Error("You received a margin call warning. The assets will be liquidated to cover losses.")

    def OnData(self, slice):
        
        # Enforce time constraints
        # if not (self.Time.hour == 9 and self.Time.minute == 31): # or not self.symbol in data
        #     return
        
        if self.Time.hour != self.lastHour:
            self.Debug(str(self.Time))
            self.lastHour = self.Time.hour
        
        self.liquidContract = first([sorted([contract for contract in chain.Value if contract.OpenInterest > 10000 and contract.Expiry > self.Time], key=lambda k : k.OpenInterest, reverse=True) for chain in slice.FutureChains])
        
        # If a contract exists and we're not too close to expiry, attempt to scalp
        if self.liquidContract and (self.liquidContract.Expiry - self.Time).total_seconds() > self.timeToExpiryLimit:
            self.Securities[self.liquidContract.Symbol].FeeModel = ConstantFeeModel(1.34)
            
            self.Bid = self.liquidContract.BidPrice if self.liquidContract.BidPrice else self.Bid
            self.Ask = self.liquidContract.AskPrice if self.liquidContract.AskPrice else self.Ask
            self.BidSize = self.liquidContract.BidSize if self.liquidContract.BidSize else self.BidSize
            self.AskSize = self.liquidContract.AskSize if self.liquidContract.AskSize else self.AskSize
            self.LastPrice = self.liquidContract.LastPrice if self.liquidContract.LastPrice else self.LastPrice
            self.Volume = self.liquidContract.Volume if self.liquidContract.Volume else self.Volume
            
            # send entry limit order
            if not self.Portfolio.Invested and not self.Transactions.GetOpenOrders(self.liquidContract.Symbol) and self.Time >= self.nextEntryTime:
 
                #4. Make a limit order for the number of contracts we calculated for that symbol
                self.entryPrice = self.Bid
                if self.entryPrice:
                    self.entryTicket = self.LimitOrder(self.liquidContract.Symbol, self.contractsToBuy, self.entryPrice, "Entry Order")
                    self.entryTime = self.Time
                    # self.Debug("BUY /ES @" + str(self.entryPrice))
                
            # move limit price if not filled after N bars / seconds / etc
            if self.entryTicket is not None and (self.Time - self.entryTime).seconds > self.chaseBid and self.entryTicket.Status != OrderStatus.Filled:
                self.entryTime = self.Time
                updateFields = UpdateOrderFields()
                updateFields.LimitPrice = self.Bid
                self.entryTicket.Update(updateFields)
            
            # Send a protective exit order to cover any orphan positions
            if self.Portfolio.Invested and self.Securities[self.liquidContract.Symbol].Holdings.Quantity > 0 and not self.Transactions.GetOpenOrders(self.liquidContract.Symbol):
                self.exitPrice = self.Ask
                if self.exitPrice:
                    self.exitTicket = self.LimitOrder(self.liquidContract.Symbol, -self.contractsToBuy, self.exitPrice, "Exit Order")
                    self.exitTime = self.Time
                    # self.Debug("SELL /ES @" + str(self.exitPrice))
                
               
    
    def OnOrderEvent(self, orderEvent):
        
        order = self.Transactions.GetOrderById(orderEvent.OrderId)
    
        if orderEvent.Status == OrderStatus.Canceled:
            self.Debug("{0}: {1}: {2}".format(self.Time, order.Type, orderEvent))
            
        if orderEvent.Status != OrderStatus.Filled:
            return
        
        if self.liquidContract is not None:
            # send stop loss order if entry limit order is filled
            if self.entryTicket is not None and self.entryTicket.OrderId == orderEvent.OrderId and self.Securities[self.liquidContract.Symbol].Holdings.Quantity > 0:
                self.entryTicketFillTime = self.Time
                self.exitPrice = self.entryTicket.AverageFillPrice + 0.25
                self.exitTicket = self.LimitOrder(self.liquidContract.Symbol, -self.entryTicket.Quantity, self.exitPrice, "Exit Order")
                self.exitTime = self.Time
                
            # If we exit, specify the new lag if necessary, many minutes delay
            if self.exitTicket is not None and self.exitTicket.OrderId == orderEvent.OrderId: 
                self.exitTicketFillTime = self.Time
                self.nextEntryTime = self.Time + timedelta(seconds=np.random.randint(self.minDelay,self.maxDelay))

    def ExitPositions(self):
        self.Transactions.CancelOpenOrders()
        self.Liquidate()
        
def first(t):
    res = [item for sublist in t for item in sublist]
    if len(res) > 0:
        return res[0]
    else: 
        return False
        
        
#0. TODO: Delete the old code for manually calculating the position size (lines 30-33)   
#LEGACY Save the notional value of the futures contract to self.notionalValue  
#LEGACY Calculate the number of contracts we can afford based on the margin required, Divide the margin remaining by the initial margin and save to self.contractsToBuy
# self.notionalValue = self.liquidContract.AskPrice*self.symbol.SymbolProperties.ContractMultiplier 
# self.contractsToBuy = floor( self.Portfolio.MarginRemaining / future.BuyingPowerModel.InitialOvernightMarginRequirement )
# self.contractsToBuy = self.CalculateOrderQuantity(self.symbol, 1)