| Overall Statistics |
|
Total Trades 2581 Average Win 0.66% Average Loss -0.67% Compounding Annual Return 16.300% Drawdown 47.900% Expectancy 0.175 Net Profit 353.639% Sharpe Ratio 0.989 Probabilistic Sharpe Ratio 38.754% Loss Rate 41% Win Rate 59% Profit-Loss Ratio 0.98 Alpha 0 Beta 0 Annual Standard Deviation 0.186 Annual Variance 0.034 Information Ratio 0.989 Tracking Error 0.186 Treynor Ratio 0 Total Fees $15064.04 Estimated Strategy Capacity $3.40 |
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(2011, 2, 28)
self.SetEndDate(2021, 3, 1)
self.SetCash(100000) # Set Strategy Cash
#self.Settings.FreePortfolioValuePercentage = 0.6
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash)
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.lastmonth = -1
self.lastday = -1
self.monthinterval = 1
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])}')
# self.Log(f'PORTFOLIO TOTAL HOLDINGS VALUE: {self.Portfolio.TotalHoldingsValue}')
# self.Log(f'PORTFOLIO TOTAL EQUITY VALUE: {self.Portfolio.TotalPortfolioValue}')
# self.Log(f'PORTFOLIO TOTAL (HOLDINGS - EQUITY) VALUE: {self.Portfolio.TotalHoldingsValue - self.Portfolio.TotalPortfolioValue}')
def CoarseSelectionFunction(self,coarse):
if self.IsWarmingUp: return
if self.lastmonth == -1 or 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 and TOPS (due to split value issue)
excluded_delisted = [i for i in fine if isinstance(i.SecurityReference.DelistingDate.date(),datetime) == False and i.Symbol.Value != "TOPS"]
#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]
#hist = self.History([i.Symbol for i in micro_PSR], 365, Resolution.Daily)
#sym_indices=[]
#for ind in hist.index:
# if ind[0] not in sym_indices:
# sym_indices.append(ind[0])
#self.Log(f'INDICES: {sym_indices}')
#sorting by momentum
for i in micro_PSR:
hist = self.History(i.Symbol, 180 * self.monthinterval + 1, Resolution.Daily)
#close_list = self.History(i.Symbol, 365, Resolution.Daily).loc[str(i.Symbol)]['close'].tolist()
if 'close' not in list(hist.columns):
self.Debug(f'{i.Symbol.Value} DOES NOT HAVE "close". List of headers: {list(hist.columns)}')
continue
close_list = hist['close'].tolist()
#self.Error(f'{i.Symbol.Value} INDICES: {[col for col in hist.columns]}')
if len(close_list) == 180 *self.monthinterval + 1:
#self.Debug(f'LENGTH IS: {len(close_list)}')
curr_price = close_list[-1]
price_6M = close_list[0]
price_2M = close_list[60*self.monthinterval]
price_1M = close_list[30*self.monthinterval]
#if i.Symbol.Value == "TOPS":
# self.Log(f'CURRENT PRICE: {curr_price}, BASELINE PRICE: {baseline_price}')
momo_1M = curr_price/price_1M
momo_2M = curr_price/price_2M/2
momo_6M = curr_price/price_6M/6
if momo_1M > momo_2M and momo_2M > momo_6M:
security_momo_list.append([i.Symbol,momo_1M])
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]]
#self.Debug(f'{[f.Value for f in output]}')
#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\n\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]}')
#self.Settings.FreePortfolioValuePercentage = 0.6
self.Settings.FreePortfolioValue = self.Settings.FreePortfolioValuePercentage * self.Portfolio.TotalHoldingsValue
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