Overall Statistics
Total Orders
187
Average Win
0.96%
Average Loss
-0.68%
Compounding Annual Return
15.412%
Drawdown
20.400%
Expectancy
0.477
Start Equity
100000
End Equity
115503.18
Net Profit
15.503%
Sharpe Ratio
0.34
Sortino Ratio
0.437
Probabilistic Sharpe Ratio
28.752%
Loss Rate
39%
Win Rate
61%
Profit-Loss Ratio
1.42
Alpha
-0.031
Beta
1.671
Annual Standard Deviation
0.24
Annual Variance
0.058
Information Ratio
0.076
Tracking Error
0.184
Treynor Ratio
0.049
Total Fees
$226.78
Estimated Strategy Capacity
$200000000.00
Lowest Capacity Asset
GME SC72NCBXXAHX
Portfolio Turnover
4.31%
# region imports
from AlgorithmImports import *

# endregion

class HipsterAsparagusDinosaur(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2023, 8, 5)
        self.set_end_date(2024, 8, 5)
        self.set_cash(100000)
        
        self.rebalanceTime = datetime.min #rebalnace time has been set to most recent time to start trading immediately
        self.activeStocks = set()

        self.add_universe(self.CoarseFilter, self.FineFilter)

        #default resolution is min
        self.universe_settings.resolution = Resolution.DAILY

        self.portfolioTargets = []


    def CoarseFilter(self, coarse):
        if self.time < self.rebalanceTime:
            return self.universe.unchanged
        
        self.rebalanceTime = self.time + timedelta(30) #timedelta of 30 days include weekends and hence adds 1 month to current time
        sortedByDollarVolume = sorted(coarse, key = lambda x: x.dollar_volume, reverse=True)

        #25 here is completely arbitrary choice as number of stocks from coarse filter
        return [x.symbol for x in sortedByDollarVolume if x.price > 10 and x.has_fundamental_data][:25]   
    
    
    def FineFilter(self, fine):
        sortedByPE = sorted(fine, key = lambda x: x.market_cap)
        #if market cap is missing for a security its default value is zero, we want to filter these too
        return [x.symbol for x in sortedByPE if x.market_cap > 0][:10]

    def on_securities_changed(self, changes):
        for x in changes.removed_securities:
            self.liquidate(x.symbol)
            self.activeStocks.remove(x.symbol)
        
        for x in changes.added_securities:
            self.activeStocks.add(x.symbol)

        #we create a portfolio target object--> PortfolioTarget is a class
        self.portfolioTargets = [PortfolioTarget(symbol, float(1/len(self.activeStocks))) for symbol in self.activeStocks]     

    def on_data(self, data: Slice): #here we are just rebalancing
        if self.portfolioTargets == []: #portfoliotarget is unchanged and there is nothing to rebalance
            return
        
        #we check if each symbol has data
        for symbol in self.activeStocks:
            if symbol not in data:
                return
        
        self.set_holdings(self.portfolioTargets)
        self.portfolioTargets = [] #so that we don't try to rebalance before universe changes again