Overall Statistics
Total Trades
181
Average Win
1.45%
Average Loss
-0.97%
Compounding Annual Return
106.800%
Drawdown
37.000%
Expectancy
0.747
Net Profit
108.039%
Sharpe Ratio
2.467
Probabilistic Sharpe Ratio
77.315%
Loss Rate
30%
Win Rate
70%
Profit-Loss Ratio
1.49
Alpha
1.042
Beta
-0.204
Annual Standard Deviation
0.395
Annual Variance
0.156
Information Ratio
1.211
Tracking Error
0.532
Treynor Ratio
-4.766
Total Fees
$831.68
Estimated Strategy Capacity
$56.00
from QuantConnect import *
from QuantConnect.Parameters import *
from QuantConnect.Benchmarks import *
from QuantConnect.Brokerages import *
from QuantConnect.Util import *
from QuantConnect.Interfaces import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Selection import *
from QuantConnect.Algorithm.Framework.Alphas import *
from QuantConnect.Algorithm.Framework.Portfolio import *
from QuantConnect.Algorithm.Framework.Execution import *
from QuantConnect.Algorithm.Framework.Risk import *
from QuantConnect.Indicators import *
from QuantConnect.Data import *
from QuantConnect.Data.Consolidators import *
from QuantConnect.Data.Custom import *
from QuantConnect.Data.Fundamental import *
from QuantConnect.Data.Market import *
from QuantConnect.Data.UniverseSelection import *
from QuantConnect.Notifications import *
from QuantConnect.Orders import *
from QuantConnect.Orders.Fees import *
from QuantConnect.Orders.Fills import *
from QuantConnect.Orders.Slippage import *
from QuantConnect.Scheduling import *
from QuantConnect.Securities import *
from QuantConnect.Securities.Equity import *
from QuantConnect.Securities.Forex import *
from QuantConnect.Securities.Interfaces import *
from datetime import date, datetime, timedelta
from QuantConnect.Python import *
from QuantConnect.Storage import *
QCAlgorithmFramework = QCAlgorithm
QCAlgorithmFrameworkBridge = QCAlgorithm
import math
import numpy as np
import pandas as pd
import scipy as sp

class MicroGrowth(QCAlgorithm):

    def Initialize(self):
        #self.SetStartDate(2020, 2, 12)  # Set Start Date
        self.SetStartDate(2020, 2, 28)
        self.SetEndDate(2021, 3, 1)
        self.SetCash(100000)  # Set Strategy Cash
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        self.lastmonth = -1
        self.lastday = -1
        self.monthinterval = 3
        self.Symbols = None
        self.tobeliquidated = None
        self.numsecurities = 25
        #self.SetWarmUp(timedelta(365))

    def OnData(self, data):
        if self.IsWarmingUp: return
        '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
            Arguments:
                data: Slice object keyed by symbol containing the stock data
        '''
        if self.Time.day == self.lastday + 1 and self.Time.month == self.lastmonth:
            self.Log('========== AFTER ORDER IS EXECUTED ==========')
            self.Log(f'POST-LIQUIDATION: {[[sym.Value, self.Portfolio[sym].Quantity] for sym in self.tobeliquidated]}')
            self.Log(f'POST-SET-QUANTITY: {[[sym.Value,self.Portfolio[sym].Quantity] for sym in self.Symbols]}')
            self.Log(f'PORTFOLIO CASH AFTER REBALANCING: {self.Portfolio.Cash}')
            self.Log(f'PORTFOLIO UNSETTLED CASH AFTER REBALANCING: {self.Portfolio.UnsettledCash}')
            self.Log(f'ACTUAL CURRENT STATE: {sorted([x.Key.Value for x in self.Portfolio if x.Value.Invested])}')
            
    def CoarseSelectionFunction(self,coarse):
        if self.IsWarmingUp: return
        if self.lastmonth == -1 or ((self.Time.month-self.lastmonth) % self.monthinterval == 0 and self.Time.month != self.lastmonth):
            self.lastmonth = self.Time.month
            self.lastday = self.Time.day
            return [x.Symbol for x in coarse if x.HasFundamentalData]
        else:
            return Universe.Unchanged
    
    def FineSelectionFunction(self,fine):
        #momo_dict = {}
        security_momo_list = []
        MKTCAP_dict = {}
        #exclude delisted
        excluded_delisted = [i for i in fine if isinstance(i.SecurityReference.DelistingDate.date(),datetime) == False]
        
        #filter by mkt_cap
        for i in fine:
            if isinstance(i.MarketCap,(float,int)) and i.MarketCap != 0:
                MKTCAP_dict[i]=i.MarketCap
        microcap = [i for i in excluded_delisted if isinstance(MKTCAP_dict.get(i),(int,float)) and MKTCAP_dict.get(i)>25e6 and MKTCAP_dict.get(i)<250e6]
        
        #filter by Price-to-Sales Ratio < 1 (defined to be null if result <= 0)
        micro_PSR = [i for i in microcap if isinstance(i.ValuationRatios.PSRatio,(float,int)) and i.ValuationRatios.PSRatio < 1 and i.ValuationRatios.PSRatio > 0]
        
        #sorting by momentum
        hist = self.History([i.Symbol for i in micro_PSR], 365, Resolution.Daily)
        self.Log(f'INDICES: {hist.index}')
        
        #data_top= hist.head()
        #header_list = []
        #for row in data_top.index:
        #    if row[0] not in header_list:
        #        header_list.append(row[0])
        #self.Debug(f'LIST OF KEYS: {header_list}')
        
        for i in micro_PSR:
            #hist = self.History(i.Symbol, 365, Resolution.Daily)
            close_list = hist.loc[str(i.Symbol)]['close'].tolist()
            if len(close_list) == 365:
                #self.Debug(f'LENGTH IS: {len(close_list)}')
                curr_price = close_list[364]
                price_1Y = close_list[0]
                price_6M = close_list[180]
                price_3M = close_list[90]
                #if i.Symbol.Value == "TOPS":
                #    self.Log(f'CURRENT PRICE: {curr_price},    BASELINE PRICE: {baseline_price}')
                momo_3M = curr_price/price_3M/90
                momo_6M = curr_price/price_6M/180
                momo_1Y = curr_price/price_1Y/364
                if momo_3M > momo_6M and momo_6M > momo_1Y:
                    security_momo_list.append([i.Symbol,momo_1Y])
        
        security_momo_list_sorted = sorted(security_momo_list,key = lambda i : i[1],reverse = True)
        output = [f[0] for f in security_momo_list_sorted[:self.numsecurities]]
        #output = [f[0] for f in security_momo_list]
        self.Symbols = output
        return output
        
    def OnSecuritiesChanged(self, changes):
        # selected symbols will be found in Log
        self.Log('\n'+f'========== NEW CYCLE ==========')
        #self.Log(f'New Securities Added: {[security.Symbol.Value for security in changes.AddedSecurities]}')
        #self.Log(f'Securities Removed{[security.Symbol.Value for security in changes.RemovedSecurities]}')
        self.Log(f'PORTFOLIO CASH BEFORE LIQUIDATION: {self.Portfolio.Cash}')
        self.Log(f'PORTFOLIO UNSETTLED CASH BEFORE LIQUIDATION: {self.Portfolio.UnsettledCash}')
        self.tobeliquidated = [security.Symbol for security in changes.RemovedSecurities]
        for sym in self.tobeliquidated:
            self.Liquidate(sym)
        self.Log(f'PRE-LIQUIDATION: {[[sym.Value, self.Portfolio[sym].Quantity] for sym in self.tobeliquidated]}')
        
        for sym in self.Symbols:
            self.SetHoldings(str(sym),1/self.numsecurities)
        self.Log(f'PRE-SET-QUANTITY: {[[sym.Value,self.Portfolio[sym].Quantity] for sym in self.Symbols]}')
        self.Log(f'EXPECTED CURRENT STATE: {sorted([sym.Value for sym in self.Symbols])}')
        return