| Overall Statistics |
|
Total Trades 32 Average Win 0.32% Average Loss -0.17% Compounding Annual Return 149.867% Drawdown 1.800% Expectancy 1.361 Net Profit 3.748% Sharpe Ratio 7.031 Probabilistic Sharpe Ratio 85.029% Loss Rate 19% Win Rate 81% Profit-Loss Ratio 1.91 Alpha 0.143 Beta 0.575 Annual Standard Deviation 0.136 Annual Variance 0.018 Information Ratio -3.64 Tracking Error 0.126 Treynor Ratio 1.664 Total Fees $32.00 Estimated Strategy Capacity $7300000.00 Lowest Capacity Asset HOU R735QTJ8XC9X Portfolio Turnover 26.33% |
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# this is QuantConnect's HistoricalReturnsAlphaModel labelled here as CygnetCHR6
from AlgorithmImports import *
# this file is only to show how this Alpha works but not actually used. what is used is the compiled version from QuantConnect
# this is not imported in main.py
class CygnetCHR6Alpha(AlphaModel):
'''Uses Historical returns to create insights.'''
def __init__(self, *args, **kwargs):
'''Initializes a new default instance of the HistoricalReturnsAlphaModel class.
Args:
lookback(int): Historical return lookback period
resolution: The resolution of historical data'''
self.lookback = kwargs['lookback'] if 'lookback' in kwargs else 1
self.resolution = kwargs['resolution'] if 'resolution' in kwargs else Resolution.Daily
self.predictionInterval = Time.Multiply(Extensions.ToTimeSpan(self.resolution), self.lookback)
self.symbolDataBySymbol = {}
def Update(self, algorithm, data):
'''Updates this alpha model with the latest data from the algorithm.
This is called each time the algorithm receives data for subscribed securities
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated'''
insights = []
for symbol, symbolData in self.symbolDataBySymbol.items():
if symbolData.CanEmit:
direction = InsightDirection.Flat
magnitude = symbolData.Return
if magnitude > 0: direction = InsightDirection.Up
if magnitude < 0: direction = InsightDirection.Down
insights.append(Insight.Price(symbol, self.predictionInterval, direction, magnitude, None))
return insights
def OnSecuritiesChanged(self, algorithm, changes):
'''Event fired each time the we add/remove securities from the data feed
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
# clean up data for removed securities
for removed in changes.RemovedSecurities:
symbolData = self.symbolDataBySymbol.pop(removed.Symbol, None)
if symbolData is not None:
symbolData.RemoveConsolidators(algorithm)
# initialize data for added securities
symbols = [ x.Symbol for x in changes.AddedSecurities ]
history = algorithm.History(symbols, self.lookback, self.resolution)
if history.empty: return
tickers = history.index.levels[0]
for ticker in tickers:
symbol = SymbolCache.GetSymbol(ticker)
if symbol not in self.symbolDataBySymbol:
symbolData = SymbolData(symbol, self.lookback)
self.symbolDataBySymbol[symbol] = symbolData
symbolData.RegisterIndicators(algorithm, self.resolution)
symbolData.WarmUpIndicators(history.loc[ticker])
class SymbolData:
'''Contains data specific to a symbol required by this model'''
def __init__(self, symbol, lookback):
self.Symbol = symbol
self.ROC = RateOfChange('{}.ROC({})'.format(symbol, lookback), lookback)
self.Consolidator = None
self.previous = 0
def RegisterIndicators(self, algorithm, resolution):
self.Consolidator = algorithm.ResolveConsolidator(self.Symbol, resolution)
algorithm.RegisterIndicator(self.Symbol, self.ROC, self.Consolidator)
def RemoveConsolidators(self, algorithm):
if self.Consolidator is not None:
algorithm.SubscriptionManager.RemoveConsolidator(self.Symbol, self.Consolidator)
def WarmUpIndicators(self, history):
for tuple in history.itertuples():
self.ROC.Update(tuple.Index, tuple.close)
@property
def Return(self):
return float(self.ROC.Current.Value)
@property
def CanEmit(self):
if self.previous == self.ROC.Samples:
return False
self.previous = self.ROC.Samples
return self.ROC.IsReady
def __str__(self, **kwargs):
return '{}: {:.2%}'.format(self.ROC.Name, (1 + self.Return)**252 - 1)#region imports
from AlgorithmImports import *
#endregion
import random
from Signals import CygnetSignal
class CygnetHighestLoserAlpha(AlphaModel):
def __init__(self, algorithm, securities):
self.currentHoldingIndex = -1
self.cygnet = None
self.algo = algorithm
self.myList = securities
self.period = timedelta(days=7)
self.currentHolding = ""
self.newHolding = ""
#algorithm.Debug(f"In MyAlphaModelX.__ini__ method {algorithm.signalName}")
pass
def Update(self, algorithm, data):
'''
#This block of code generates insights with random
insights = []
m = self.currentHoldingIndex
n = random.randint(0, len(self.myList)-1)
if (m == n):
return []
#myList = ["FB", "AMZN", "AAPM", "MSFT", "NVDA", "GOOG"]
algorithm.Debug(f"{n} {self.myList[n]}")
if (m != -1):
insights.append(Insight(self.myList[m], timedelta(days=1), InsightType.Price, InsightDirection.Flat, None, 999, None, 999 ))
insights.append(Insight(self.myList[n], timedelta(days=1), InsightType.Price, InsightDirection.Up, None, 999, None, 999 ))
self.currentHolding = n
return insights
'''
#self.algo.Debug(f"In MyAlphaModelX.Update method {self.algo.Time}")
if self.cygnet == None:
self.cygnet = CygnetSignal(self.algo, self.myList)
self.cygnet.UpdateWithLatestMarketDataFeed(algorithm, data)
if (algorithm.Time.hour == 9 and algorithm.Time.minute == 40):
self.cygnet.ComputeSignalValues()
return []
equitiesCarried = ["Equities carried: "]
equitiesExited = ["Equities to be exited: "]
equitiesNew = ["New equities to be traded: "]
equityToTrade = ""
signalValue = 0.0
insights = []
if self.algo.tradeDirection == 1:
entryTradeDirection = InsightDirection.Up
exitTradeDirection = InsightDirection.Flat
if self.algo.tradeDirection == -1:
entryTradeDirection = InsightDirection.Down
exitTradeDirection = InsightDirection.Flat
if self.algo.Time.hour == 15 and self.algo.Time.minute == 40:
self.cygnet.ComputeSignalValues()
equityToTrade, signalValue = self.cygnet.GetEquityToTrade(self.algo.signalName, self.algo.strat)
self.algo.Debug(f"Current holding: {self.currentHolding} Equity to trade: {equityToTrade} Signal Value {signalValue}")
if not self.algo.Portfolio.Invested:
insights.append(Insight(Symbol.Create(equityToTrade, SecurityType.Equity, Market.USA), self.period, InsightType.Price, entryTradeDirection, None, 1, None, 1 ))
else:
for security in self.algo.Portfolio.Values:
if security.Symbol.Value == equityToTrade and not security.Invested:
equitiesNew.append(security.Symbol.Value)
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, entryTradeDirection, None, 1, None, 1 ))
self.currentHolding = equityToTrade
if self.algo.Time.hour == 15 and self.algo.Time.minute == 57:
for security in self.algo.Portfolio.Values:
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
return insights
def OnSecuritiesChanged(self, algorithm, changes):
'''Event fired each time the we add/remove securities from the data feed
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
pass
import pandas as pd
import datetime
from datetime import datetime
from AlgorithmImports import *
from dateutil.relativedelta import relativedelta
from io import StringIO
from scipy import stats
from statistics import mean
def get_historical_close(symbol: str, timedelta: dict, hist: pd.DataFrame, end_date):
'''
Accepts a symbol, a time delta to look in the past like {months: 6} or {years: 1} and the history
dataframe.
Returns the closing price of the symbol on or immediately after the time delta.
Assumes that the history dataframe is indexed on symbol then time ascending.
'''
single_symbol_data = hist[hist.index.get_level_values("symbol") == symbol]
return single_symbol_data[single_symbol_data.index.get_level_values("time") >= end_date - relativedelta(**timedelta)].iloc[0].close
def reorder_columns(dataframe, col_name, position):
"""Reorder a dataframe's column.
Args:
dataframe (pd.DataFrame): dataframe to use
col_name (string): column name to move
position (0-indexed position): where to relocate column to
Returns:
pd.DataFrame: re-assigned dataframe
"""
temp_col = dataframe[col_name]
dataframe = dataframe.drop(columns=[col_name])
dataframe.insert(loc=position, column=col_name, value=temp_col)
return dataframe
def calculate_hqm_score(df):
momentum_percentiles = []
# calculate percentiles of period returns
time_periods = ['3m', '1m'] # ['1y', '6m', '3m', '1m']
for time_period in time_periods:
momentum_percentiles.append(df[f'return_{time_period}_percentile'])
return sum(momentum_percentiles) #mean(momentum_percentiles)
class CygnetCM12Alpha(AlphaModel):
def __init__(self, algorithm, securities):
#algorithm.Debug("Begin of CygnetHQMAlpha.__init__()")
self.algo = algorithm
self.myList = securities
self.lastTradeTime = self.algo.Time - timedelta(days=31)
self.tradeFrequency = timedelta(days=7)
self.period = timedelta(days=31)
self.df = pd.DataFrame(columns=["Symbol", "Name", "Sector"])
self.df.Symbol = self.myList
self.df.Name = ""
self.df.Sector = ""
self.df = self.df.set_index("Symbol")
self.df = self.df.sort_index()
pass
def Update(self, algorithm, data):
# Return no insights if it's not time to rebalance
if (self.algo.Time - self.lastTradeTime) < self.tradeFrequency:
return []
#self.algo.Debug("Begin of Update()")
self.lastTradeTime = self.algo.Time
df = self.ComputeSignal()
#maxNumOfHoldings = 12
#startingRank = 5
#maxNumOfHoldings = min(len(df.index), maxNumOfHoldings)
#equitiesToTrade = [x for x in df.index[startingRank:maxNumOfHoldings] ]
minNumOfHoldings = 5
x = 10
while (True):
df2 = df [df['return_3m_percentile'] < x]
df2 = df2 [df2['return_1m_percentile'] > (100-x)]
if len(df2) >= minNumOfHoldings:
break
else:
x = x+5
df2 = df2.sort_values(by='return_1m_percentile', ascending=False)
equitiesToTrade = [x for x in df2.index]
#self.algo.Debug("New equities to acquire:")
self.algo.Debug(equitiesToTrade)
insights = []
#VR 4/1/23
# Close old positions if they aren't in equitiesToTrade list
for security in algorithm.Portfolio.Values:
if security.Invested and security.Symbol not in equitiesToTrade:
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
# figure out how many shares to buy
sumOfClosingPrices = df2["close_today"][:minNumOfHoldings].sum()
numOfSharesToBuy = math.floor(algorithm.Portfolio.TotalPortfolioValue / sumOfClosingPrices)
for sym in equitiesToTrade:
if not security.Invested: # if it is already in portfolio, do not buy again
self.algo.MarketOrder(sym, numOfSharesToBuy)
return insights
def OnSecuritiesChanged(self, algorithm, changes):
pass
def ComputeSignal(self):
#self.algo.Debug("Begin of ComputeSignal()")
end_date = self.algo.Time #datetime.datetime(2022, 4, 11) # Set Start Date
start_date = end_date - timedelta(days=190)
self.algo.Debug(f"ComputeSignal Start date {start_date} End date {end_date}")
#return None
histData = self.algo.History(self.myList, start_date, end_date, Resolution.Daily)["close"].unstack(level = 0)
#this block of code renames QuantConnect security identifiers to familiar stock symbols
newCols = []
for x in histData.columns:
newCols.append(str(self.algo.Symbol(x)))
histData.columns = newCols
#self.algo.Debug(f"Point 2B {histData.shape}")
histData = histData.transpose()
#self.algo.Debug(f"Point 2A Histdata shape: {histData.shape}")
momentum = pd.DataFrame(index=histData.index, columns=["close_today", "close_1m", "close_3m", "close_6m"])
momentum = momentum.fillna(0.0)
#momentum["EquitySymbol"] = "Unknown"
#momentum["EquitySymbol"] = momentum["symbol"].apply (lambda x: str(self.algo.Symbol(x)))
for i in range(len(momentum)):
momentum.iloc[i, 0] = histData.iloc[i, -1]
momentum.iloc[i, 1] = histData.iloc[i, -21]
momentum.iloc[i, 2] = histData.iloc[i, -63]
momentum.iloc[i, 3] = histData.iloc[i, -126]
# calculate percentiles of period returns
time_periods = ['3m', '1m']
numOfBins = 100
for time_period in time_periods:
momentum[f'return_{time_period}'] = (momentum.close_today - momentum[f'close_{time_period}']) / momentum[f'close_{time_period}']
#momentum[f'return_{time_period}_percentile'] = momentum[f'return_{time_period}'].apply (lambda x: stats.percentileofscore(momentum[f'return_{time_period}'], x))
momentum[f'return_{time_period}_percentile'] = pd.qcut(momentum[f'return_{time_period}'], numOfBins, labels = False) # divvy up into bins
momentum["HQM Score"] = momentum.apply (calculate_hqm_score, axis = 1)
#for i in range(len(momentum)):
# momentum["EquitySymbol"][i] = str(qb.Symbol(momentum["symbol"][i]))
#momentum = momentum.set_index("EquitySymbol")
momentum = momentum.sort_values(by="HQM Score", ascending = False)
momentum = reorder_columns(momentum, "HQM Score", 0)
self.algo.Debug(momentum.shape)
return momentum
import pandas as pd
import datetime
from datetime import datetime
from AlgorithmImports import *
from dateutil.relativedelta import relativedelta
from io import StringIO
from scipy import stats
from statistics import mean
'''Notes on performance of CygCM12Alpha2
CM12-Var2-Paper is the program used to gen these returns
Risk Mgmt: 1 "NullRiskMgmt", 2 "TrailingStop5%", 3 "Maxdrawdown4%", 4 "MaxUnrlzd4%", 5 "Bracketed4%Gain6%Loss"
NullRiskMgmt has the best performance
date: 4/14/23
In this study, the portfolio is equal share portofolio (as oppsoed equal $ value for each stock)
Items in ranks 6 thru 12 (7 items) were traded
CygnetCM12Alpha2 (Order paced using market orders, using equal # of shares for each); it picks ~5 to 10 stocks and trdes them each week
Capital 100k
Risk Mgmt 4 works the best
Net profits of CM12 strategy; RM vs. Year; net profits are in %
Risk Mgmt 2017 2018 2019 2020 2021 2022 Cum Return Ann. Return 2023 (upto 4/14/23)
1 -8.3 -13.4 11.5 -36.0 1.9 34.5 -22% -4.1% 9.65
2 -5.6 -8.4 11.0 -13.7 0.2 29.1 7% 1.2% 9.25
3 -5.9 -6.3 11.7 -22.7 -0.3 36.6 4% 0.6% 8.45
4 -8.6 -13.5 12.2 -20.5 -1.0 33.7 -7% -1.1% 7.47
5 -5.7 -9.8 10.9 -24.0 -1.9 29.8 -9% -1.5% 10.06
Grand Total -34.1 -51.4 57.3 -116.9 -1.0 163.8
SP500 (Nom) 20 -6.24 28.88 16.26 26.89 -19.44 72% 9.5%
SP500 (TR) 21.83 -4.38 31.49 18.4 28.71 -18.11 91% 11.4%
The above returns show that this strategy has no edge; over a long run. However, it seems to excellent peformance in 2022 which is a down year.
Does it mean that this strategy works well in a bear / down market? But then, it did not work well in 2018 which was a down year.
'''
def get_historical_close(symbol: str, timedelta: dict, hist: pd.DataFrame, end_date):
'''
Accepts a symbol, a time delta to look in the past like {months: 6} or {years: 1} and the history
dataframe.
Returns the closing price of the symbol on or immediately after the time delta.
Assumes that the history dataframe is indexed on symbol then time ascending.
'''
single_symbol_data = hist[hist.index.get_level_values("symbol") == symbol]
return single_symbol_data[single_symbol_data.index.get_level_values("time") >= end_date - relativedelta(**timedelta)].iloc[0].close
def reorder_columns(dataframe, col_name, position):
"""Reorder a dataframe's column.
Args:
dataframe (pd.DataFrame): dataframe to use
col_name (string): column name to move
position (0-indexed position): where to relocate column to
Returns:
pd.DataFrame: re-assigned dataframe
"""
temp_col = dataframe[col_name]
dataframe = dataframe.drop(columns=[col_name])
dataframe.insert(loc=position, column=col_name, value=temp_col)
return dataframe
def calculate_hqm_score(df):
momentum_percentiles = []
# calculate percentiles of period returns
time_periods = ['1m', '3m'] # ['1y', '6m', '3m', '1m']
for time_period in time_periods:
momentum_percentiles.append(df[f'return_{time_period}_percentile'])
return sum(momentum_percentiles) #mean(momentum_percentiles)
def rebound_score(df):
# calculate a rebound score based on how steep the 3m decline is and how steep 1 m recover is
pass
def populateHisotrycalPriceData(momentum, histData, intervalDays, closePriceColumnName):
#intervalDays = 30, 91, 182
#closePriceColumnName are close_1m, close_3m, and close_6m
endDate = histData.columns[-1] # the last column header is the latest /end date of the data
while(True):
lookbackDate = endDate - relativedelta(days=intervalDays)
isLookbackDateInHistData = histData.columns.isin([lookbackDate]).sum()
print(str(lookbackDate) + " " + str(isLookbackDateInHistData))
if isLookbackDateInHistData == 1:
momentum[closePriceColumnName] = histData[lookbackDate]
break
else:
intervalDays = intervalDays - 1
class CygnetCM12Alpha2(AlphaModel):
def __init__(self, algorithm, securities):
#algorithm.Debug("Begin of CygnetHQMAlpha.__init__()")
self.algo = algorithm
self.myList = securities
self.lastTradeTime = self.algo.Time - timedelta(days=31)
self.tradeFrequency = timedelta(days=1)
self.period = timedelta(days=31)
self.df = pd.DataFrame(columns=["Symbol", "Name", "Sector"])
self.df.Symbol = self.myList
self.df.Name = ""
self.df.Sector = ""
self.df = self.df.set_index("Symbol")
self.df = self.df.sort_index()
self.spy = self.algo.AddEquity("SPY")
self.weekdayNames = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
self.daysToNextBusinessDay = [1, 1, 1, 1, 3, 2, 1]
pass
def Update(self, algorithm, data):
# Return no insights if it's not time to rebalance
if (self.algo.Time - self.lastTradeTime) < self.tradeFrequency:
return []
insights = []
self.lastTradeTime = self.algo.Time
#self.algo.Debug(f"{self.algo.Time} {self.weekdayNames[self.algo.Time.weekday()]}")
''' Here are date/times of callbacks to Update function; note that due to holiday, no callback was made on 1/16
Two mysteries - sometimes Sat callbacks do not happen and sometimes time of callback is 9:31 instead of 0:00
Date Time Day Date Time Day Date Time Day Date Time Day Date Time Day
1/3/2023 0:00:00 Tue 1/9/2023 0:00:00 Mon 1/17/2023 0:00:00 Tue 1/23/2023 9:31:00 Mon 1/30/2023 0:00:00 Mon
1/4/2023 0:00:00 Wed 1/10/2023 0:00:00 Tue 1/18/2023 0:00:00 Wed 1/24/2023 9:31:00 Tue 1/31/2023 0:00:00 Tue
1/5/2023 0:00:00 Thu 1/11/2023 0:00:00 Wed 1/19/2023 0:00:00 Thu 1/25/2023 9:31:00 Wed 2/1/2023 0:00:00 Wed
1/6/2023 0:00:00 Fri 1/12/2023 0:00:00 Thu 1/20/2023 0:00:00 Fri 1/26/2023 9:31:00 Thu 2/2/2023 0:00:00 Thu
1/7/2023 0:00:00 Sat 1/13/2023 0:00:00 Fri 1/21/2023 0:00:00 Sat 1/27/2023 9:31:00 Fri 2/3/2023 0:00:00 Fri
1/14/2023 0:00:00 Sat 2/4/2023 0:00:00 Sat
'''
#if self.algo.Time.weekday() == 5:
# trading_day = datetime(self.algo.Time.year, self.algo.Time.month, self.algo.Time.day) + relativedelta(days=2)
#else:
# trading_day = datetime(self.algo.Time.year, self.algo.Time.month, self.algo.Time.day)
#if isMarketOpen == False and trading_day.weekday() <= 4:
# self.algo.Debug(f" {trading_day} {self.weekdayNames[trading_day.weekday()]} is a holiday; market is closed")
weekdayNum = self.algo.Time.weekday() #0=Mon, 1=Tue 2=Wed 3=Thu 4=Fri 5=Sat 6=Sun
weekdayName = self.weekdayNames[weekdayNum]
isMarketOpen = True
isMarketOpenNextDay = True
trading_day = datetime(self.algo.Time.year, self.algo.Time.month, self.algo.Time.day)
isMarketOpen = self.spy.Exchange.Hours.IsDateOpen(trading_day)
next_business_day = trading_day + relativedelta(days=self.daysToNextBusinessDay[weekdayNum])
isMarketOpenNextBusinessDay = self.spy.Exchange.Hours.IsDateOpen(next_business_day)
issueClosingOrders = False
issueOpeningOrders = False
if (weekdayName == "Thu" and isMarketOpenNextBusinessDay == False): # if markets are closed on Fri
issueClosingOrders = True
if (weekdayName == "Fri" and isMarketOpen == True): # if Friday and markets are open
issueClosingOrders = True
if (weekdayName == "Sat" and isMarketOpenNextBusinessDay == False): # if markets are closed on Monday
issueOpeningOrders = True
if (weekdayName == "Mon" and isMarketOpen == True): # if markets are open on Monday
issueOpeningOrders = True
''' This works for Resolution.Daily OnData() call backs but not for Update()
issueClosingOrders = False
issueOpeningOrders = False
if (self.algo.Time.weekday() == 3 and (isMarketOpen == False or isMarketOpenNextDay == False)): # if markets are closed on Thu or Fri
issueClosingOrders = True
if (self.algo.Time.weekday() == 4 and isMarketOpen == True): # if markets are open on Friday
issueClosingOrders = True
if (self.algo.Time.weekday() == 4 and isMarketOpen == False): # if Friday and market is closed
issueOpeningOrders = True
if (self.algo.Time.weekday() == 5): #Sat callback for OnOpen order for the next trading day
issueOpeningOrders = True
# 0 is monday, 4 is Fri, 6 is sunday; execute this code only on Friday to sell on Friday at close
if thrusday is callback day and thursday is holiday, there will not be a all back on Friday
that means issue closing orders on thursday, they will execute on Friday at close
If friday is callback day and Friday is holiday, then open positions on Friday instead of Saturday callback
openonmarket orders will execute monday morning
'''
#this is called at the first minute of the day, i.e. 0 hours, 0 minutes
# first call comes (in the week) on Tuesday's first minute (with Monday's close info) and last call comes on Saturday (with Friday's close info)
# if Friday happens to be closed (like on Good Friday), the call for Thursday's close comes on Friday
# so you issue position closing orders on Friday and issue buy orders on Saturday
if issueClosingOrders == True:
self.algo.Debug(f"Closing positions with MarketOnCloseOrders {self.weekdayNames[trading_day.weekday()]}")
for security in algorithm.Portfolio.Values:
if security.Invested: # and security.Symbol not in equitiesToTrade:
qty = self.algo.Portfolio[security.Symbol].Quantity
self.algo.MarketOnCloseOrder(security.Symbol, -1*qty)
#insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
if issueOpeningOrders == True:
self.algo.Debug(f"Opening positions with MarketOnOpenOrders on {self.weekdayNames[trading_day.weekday()]}")
df = self.ComputeSignal()
#maxNumOfHoldings = 12
#startingRank = 5
#maxNumOfHoldings = min(len(df.index), maxNumOfHoldings)
#equitiesToTrade = [x for x in df.index[startingRank:maxNumOfHoldings] ]
maxNumOfHoldings = 8
#use this code to buy the best perforing
# df2 = df[:minNumOfHoldings]
#use this block of code to pick poor q perf and good month perf
binNum = 6 # there are 20 bins from 1 thru 20; higher the number, the better it is
while (True):
df2 = df [df['return_3m_percentile'] <= binNum] # start with first 6 worst bins (1 thru 6)
df2 = df2 [df2['return_1m_percentile'] >= 18] # pick the top 3 bins for 1m perf
if len(df2) >= maxNumOfHoldings:
break
else:
binNum = binNum + 1
if binNum == 20: break
df2 = df2.sort_values(by="return_1m_percentile", ascending=False)[:maxNumOfHoldings]
#df2 = df2.sort_values(by='return_1m_percentile', ascending=False)
equitiesToTrade = [x for x in df2.index]
# figure out how many shares to buy
sumOfClosingPrices = df2["close_today"].sum()
numOfSharesToBuy = math.floor(algorithm.Portfolio.TotalPortfolioValue / sumOfClosingPrices)
#self.algo.Debug("New equities to acquire:")
self.algo.Debug(equitiesToTrade)
for sym in equitiesToTrade:
#if self.algo.Portfolio[sym].Quantity == 0:
self.algo.MarketOnOpenOrder(sym, numOfSharesToBuy)
return insights
def OnSecuritiesChanged(self, algorithm, changes):
pass
def ComputeSignal(self):
#self.algo.Debug("Begin of ComputeSignal()")
end_date = self.algo.Time #datetime.datetime(2022, 4, 11) # Set Start Date
start_date = end_date - timedelta(days=190) # 190 days is calendar days
#self.algo.Debug(f"ComputeSignal Start date {start_date} End date {end_date}")
#return None
histData = self.algo.History(self.myList, start_date, end_date, Resolution.Daily)["close"].unstack(level = 0)
# this is important to align dates with closing prices (see main.py for a lengthy write up on quantconnect times)
histData["Date"] = histData.index
#histData = reorder_columns(histData, "Date", 0)
histData["Date"] = histData["Date"].apply(lambda x: x - relativedelta(days=1) ) # decrease dates by 1 to compensate for QuantConnect way
histData = histData.set_index("Date")
#histData.tail()
#this block of code renames QuantConnect security identifiers to familiar stock symbols
newCols = []
for x in histData.columns:
newCols.append(str(self.algo.Symbol(x)))
histData.columns = newCols
#self.algo.Debug(f"Point 2B {histData.shape}")
histData = histData.transpose()
#self.algo.Debug(f"Point 2A Histdata shape: {histData.shape}")
momentum = pd.DataFrame(index=histData.index, columns=["close_today", "close_1m", "close_3m", "close_6m"])
momentum = momentum.fillna(0.0)
#momentum["EquitySymbol"] = "Unknown"
#momentum["EquitySymbol"] = momentum["symbol"].apply (lambda x: str(self.algo.Symbol(x)))
myDate = histData.columns[-1] # the latest date (close to now)
momentum["close_today"] = histData[myDate] #last column
populateHisotrycalPriceData(momentum, histData, 30, "close_1m")
populateHisotrycalPriceData(momentum, histData, 91, "close_3m")
populateHisotrycalPriceData(momentum, histData, 182, "close_6m")
#momentum.iloc[:, 0] = histData.iloc[:, -1] # last column in histData
#momentum.iloc[:, 1] = histData.iloc[:, -21]
#momentum.iloc[:, 2] = histData.iloc[:, -63]
#momentum.iloc[:, 3] = histData.iloc[:, -126]
# calculate percentiles of period returns
time_periods = ['1m', '3m', '6m']
numOfBins = 20
for time_period in time_periods:
momentum[f'return_{time_period}'] = (momentum.close_today - momentum[f'close_{time_period}']) / momentum[f'close_{time_period}']
#momentum[f'return_{time_period}_percentile'] = momentum[f'return_{time_period}'].apply (lambda x: stats.percentileofscore(momentum[f'return_{time_period}'], x))
momentum[f'return_{time_period}_percentile'] = pd.qcut(momentum[f'return_{time_period}'], numOfBins, labels = False) # divvy up into bins
momentum[f'return_{time_period}_percentile'] = momentum[f'return_{time_period}_percentile'] + 1 # get rid of zero base with starting with 1
momentum["HQM Score"] = momentum.apply (calculate_hqm_score, axis = 1)
#for i in range(len(momentum)):
# momentum["EquitySymbol"][i] = str(qb.Symbol(momentum["symbol"][i]))
#momentum = momentum.set_index("EquitySymbol")
momentum = momentum.sort_values(by="HQM Score", ascending = False)
momentum = reorder_columns(momentum, "HQM Score", 0)
#self.algo.Debug(momentum.shape)
return momentum
'''
end_date = self.algo.Time #datetime.datetime(2022, 4, 11) # Set Start Date
myList = ["v=111", "v=121", "v=161", "v=131", "v=141", "v=171"]
myList = ["v%3D111", "v%3D121", "v%3D161", "v%3D131", "v%3D141", "v%3D171"]
tdf = pd.DataFrame()
for x in myList:
#URL = "https://elite.finviz.com/export.ashx?" + x + "&f=cap_midover,geo_usa,ta_perf_52w50u&ft=4&o=-instown&auth=mcsic2021@gmail.com" # this corresponds to a screener setup in Finviz
#URL = "https://elite.finviz.com/export.ashx?" + x + "&ft=4&t=ALGN,CDW,DHR,DOV,ETN,FTNT,GOOG,GOOGL,GRMN,INTU,IT,JCI,LLY,PKI,PNR,RHI,RMD,TGT,WAT,WST&auth=mcsic2021@gmail.com" # this corresponds to a portfolio setup in Finviz
#URL = "https://elite.finviz.com/export.ashx?" + x + "&ft=4&t=TX,TSLA,AAPL,SU,CTRA,CAH,LOW,MRK,CSCO,PG,IBM,NVDA,AMAT,CSL,GBTC,AMCR,BMY,MMM,SPRE,DVN,PYPL,ALB,MSFT,EVGO,COIN,CRWD,CRM,AMD,WBA,SNOW,DNA,AMAGX,CLOU,AMANX,META,SQ,ABNB,GOOGL,VCYT,RIVN&auth=mcsic2021@gmail.com" #this is MCSIC holdings on 3/14/23
#URL = "https://elite.finviz.com/export.ashx?" + x + "&ft=4&t=AAPL,ABNB,ALB,AMAGX,AMANX,AMAT,AMCR,AMD,BMY,CAH,CLOU,COIN,CRM,CSCO,CSL,CTRA,DNA,DVN,EVGO,GBTC,GOOGL,IBM,LOW,META,MMM,MRK,MSFT,NVDA,PG,PYPL,RIVN,SNOW,SPRE,SQ,SU,TSLA,TX,VCYT,WBA&auth=mcsic2021@gmail.com"
#URL = "https://elite.finviz.com/export.ashx?" + x + "&f=idx_sp500&auth=mcsic2021@gmail.com" # this corresponds to all the stocks in SP500 index
#URL = "https://elite.finviz.com/export.ashx?" + x + "&t=VZ,TLRY,SPCE,RGTI,QUBT,PTON,MSFT,MPW,LOW,IONQ,HON,GOOG,FSLY,EVGO,DNA,CSCO,COIN,CHGG,APPN,AMAT,AI,PHM,LEN,MTH,DHI,TOL,CCS,RIVN,GOEV&auth=mcsic2021@gmail.com"
self.algo.Download('https://raw.githubusercontent.com/barryplasmid/CM12/main/finvizexport-v%3D171-04102023.csv')
print(URL)
response = requests.get(URL)
fileName = f"finvizexport-{x}.csv" #if run from local machine use "C:\\Users\\unk20\\OneDrive\\Data\\QuantTrading\\CSVFiles\\finvizexport.csv"
mydopfenf(fileName, "wb").write(response.content)
#files.download(fileName)
time.sleep(5) # sleep is needed for proper functioning of this
df = pd.read_csv(fileName)
#print (df.index)
if x == "v=111": #first iteration
tdf = df
else:
tdf = pd.merge(tdf, df, how='outer') #download from Finviz the "Overview", "Valuation", "Financial", "Ownership", "Performance", and "Technical" tabs and combine them into one dataframe
#print(URL)
print(f"{x} {df.shape} {tdf.shape}")
tdf.set_index("No.", inplace=True)
tdf.to_csv(fileName)'''
import pandas as pd
import datetime
from datetime import datetime
from AlgorithmImports import *
from dateutil.relativedelta import relativedelta
from io import StringIO
from scipy import stats
from statistics import mean
'''Notes on performance of CygCM12Alpha3
Due to getting data from a finviz exported file, we cannot run backtesting with this alpha
Var3 : add configurable (1 week or 2 wee) hold period
'''
class CygnetCM12Alpha3(AlphaModel):
def __init__(self, algorithm, securities):
#algorithm.Debug("Begin of CygnetHQMAlpha.__init__()")
self.algo = algorithm
self.myList = securities
self.lastTradeTime = self.algo.Time - timedelta(days=31)
self.tradeFrequency = timedelta(days=1)
self.period = timedelta(days=31)
self.df = pd.DataFrame(columns=["Symbol", "Name", "Sector"])
self.df.Symbol = self.myList
self.df.Name = ""
self.df.Sector = ""
self.df = self.df.set_index("Symbol")
self.df = self.df.sort_index()
self.spy = self.algo.AddEquity("SPY")
self.weekdayNames = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
self.daysToNextBusinessDay = [1, 1, 1, 1, 3, 2, 1]
pass
def Update(self, algorithm, data):
# Return no insights if it's not time to rebalance
if (self.algo.Time - self.lastTradeTime) < self.tradeFrequency:
return []
insights = []
self.lastTradeTime = self.algo.Time
weekdayNum = self.algo.Time.weekday() #0=Mon, 1=Tue 2=Wed 3=Thu 4=Fri 5=Sat 6=Sun
weekdayName = self.weekdayNames[weekdayNum]
isMarketOpen = True
isMarketOpenNextDay = True
trading_day = datetime(self.algo.Time.year, self.algo.Time.month, self.algo.Time.day)
#trading_day = trading_day - relativedelta(days=1)
isMarketOpen = self.spy.Exchange.Hours.IsDateOpen(trading_day)
daysToNextBizDay = self.daysToNextBusinessDay[weekdayNum]
next_business_day = trading_day + relativedelta(days=daysToNextBizDay)
isMarketOpenNextBusinessDay = self.spy.Exchange.Hours.IsDateOpen(next_business_day)
issueClosingOrders = False
issueOpeningOrders = False
if (weekdayName == "Thu" and isMarketOpenNextBusinessDay == False): # if markets are closed on Fri
issueClosingOrders = True
if (weekdayName == "Fri" and isMarketOpen == True): # if Friday and markets are open
issueClosingOrders = True
if (weekdayName == "Sat" and isMarketOpenNextBusinessDay == False): # if markets are closed on Monday
issueOpeningOrders = True
if (weekdayName == "Mon" and isMarketOpen == True): # if markets are open on Monday
issueOpeningOrders = True
if issueClosingOrders == True:
self.algo.Debug(f"Closing positions with MarketOnCloseOrders {self.weekdayNames[trading_day.weekday()]}")
for security in algorithm.Portfolio.Values:
if security.Invested: # and security.Symbol not in equitiesToTrade:
#find out when it was purchased
qty = self.algo.Portfolio[security.Symbol].Quantity
purchase_date = None
orders = self.algo.Portfolio.Transactions.GetOrders()
for order in orders:
if order.Symbol == security.Symbol and order.Status == OrderStatus.Filled and order.Direction == OrderDirection.Buy:
purchase_date = order.Time
self.algo.Debug(f"{security.Symbol} {purchase_date} {qty}")
self.algo.MarketOnCloseOrder(security.Symbol, -1*qty)
#insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
if issueOpeningOrders == True:
self.algo.Debug(f"Opening positions with MarketOnOpenOrders on {self.weekdayNames[trading_day.weekday()]}")
fileDate = None
if (weekdayName == "Mon"):
fileDate = trading_day - relativedelta(days=3)
if (weekdayName == "Sat"):
fileDate = trading_day - relativedelta(days=1)
sp500_analysis_filename = str(fileDate.month).zfill(2) + str(fileDate.day).zfill(2) + str(fileDate.year)
df = self.ComputeSignal(sp500_analysis_filename)
equitiesToTrade = [x for x in df.index]
# figure out how many shares to buy
sumOfClosingPrices = df["Price"].sum()
if sumOfClosingPrices != 0.0:
numOfSharesToBuy = math.floor(algorithm.Portfolio.TotalPortfolioValue / sumOfClosingPrices)
else:
numOfSharesToBuy = 10
#self.algo.Debug("New equities to acquire:")
self.algo.Debug(equitiesToTrade)
for sym in equitiesToTrade:
#if self.algo.Portfolio[sym].Quantity == 0:
self.algo.MarketOnOpenOrder(sym, numOfSharesToBuy)
return insights
def OnSecuritiesChanged(self, algorithm, changes):
pass
#for security in changes.RemovedSecurities:
# if security.Symbol in self.symbol_data_by_symbol:
# symbol_data = self.symbol_data_by_symbol.pop(security.Symbol, None)
# if symbol_data:
# symbol_data.dispose()
def ComputeSignal(self, fileDate: str):
#URL = "https://elite.finviz.com/export.ashx?" + x + "&t=VZ,TLRY&auth=mcsic2021@gmail.com"
#URL = 'https://raw.githubusercontent.com/barryplasmid/PublicLists/main/sp500-daily-analysis-04152023.csv'
URL = "https://raw.githubusercontent.com/barryplasmid/PublicLists/main/sp500-daily-analysis-" + fileDate + ".csv"
csvContent = self.algo.Download(URL)
df = pd.read_csv(StringIO(csvContent))
df = df.set_index("Ticker")
maxNumOfHoldings = 8 # 8 seems to be most optimal when backtested with 6, 7, 8, 9, 10 holdings
#use this block of code to pick poor q perf and good month perf
binNum = 15 # there are 20 bins from 1 thru 20; higher the number, the worse it is
# unlike, Alpha2, the output here is ranked the opposite; i.e., lower numeric rank is better
df2 = pd.DataFrame()
while (True):
df2 = df [df["Performance (Quarter)_dr"] >= binNum] # start with first 6 worst bins (15 thru 20)
df2 = df2 [df2["Performance (Month)_dr"] <= 3] # pick the top 3 bins for 1m perf
if len(df2) >= maxNumOfHoldings:
break
else:
binNum = binNum - 1
if binNum == 0: break
myColumns=["Price", "Performance (Month)_dr", "Performance (Quarter)_dr", "Combined_Score"]
df3 = pd.DataFrame(index=df2.index, columns=myColumns)
#momentum = momentum.fillna(0.0)
df3["Price"] = df2["Price"]
df3["Performance (Month)_dr"] = df2["Performance (Month)_dr"]
df3["Performance (Quarter)_dr"] = df2["Performance (Quarter)_dr"]
df3["Combined_Score"] = df2["Combined_Score"]
df3 = df3.sort_values(by="Combined_Score")[:maxNumOfHoldings]
return df3#region imports
from AlgorithmImports import *
#endregion
import random
from Signals import CygnetSignal
class CygnetCM1RotationAlpha(AlphaModel):
def __init__(self, algorithm, securities):
self.currentHoldingIndex = -1
self.cygnet = None
self.algo = algorithm
self.myList = securities
self.period = timedelta(days=7)
self.currentHolding = ""
self.newHolding = ""
#algorithm.Debug(f"In MyAlphaModelX.__ini__ method {algorithm.signalName}")
pass
def Update(self, algorithm, data):
'''
#This block of code generates insights with random
insights = []
m = self.currentHoldingIndex
n = random.randint(0, len(self.myList)-1)
if (m == n):
return []
#myList = ["FB", "AMZN", "AAPM", "MSFT", "NVDA", "GOOG"]
algorithm.Debug(f"{n} {self.myList[n]}")
if (m != -1):
insights.append(Insight(self.myList[m], timedelta(days=1), InsightType.Price, InsightDirection.Flat, None, 999, None, 999 ))
insights.append(Insight(self.myList[n], timedelta(days=1), InsightType.Price, InsightDirection.Up, None, 999, None, 999 ))
self.currentHolding = n
return insights
'''
#self.algo.Debug(f"In MyAlphaModelX.Update method {self.algo.Time}")
if self.cygnet == None:
self.cygnet = CygnetSignal(self.algo, self.myList)
self.cygnet.UpdateWithLatestMarketDataFeed(algorithm, data)
if (algorithm.Time.hour == 9 and algorithm.Time.minute == 40):
self.cygnet.ComputeSignalValues()
return []
equitiesCarried = ["Equities carried: "]
equitiesExited = ["Equities to be exited: "]
equitiesNew = ["New equities to be traded: "]
equityToTrade = ""
signalValue = 0.0
insights = []
if self.algo.tradeDirection == 1:
entryTradeDirection = InsightDirection.Up
exitTradeDirection = InsightDirection.Flat
if self.algo.tradeDirection == -1:
entryTradeDirection = InsightDirection.Down
exitTradeDirection = InsightDirection.Flat
if self.algo.Time.hour == 15 and self.algo.Time.minute == 50:
self.cygnet.ComputeSignalValues()
equityToTrade, signalValue = self.cygnet.GetEquityToTrade(self.algo.signalName, self.algo.strat)
self.algo.Debug(f"Current holding: {self.currentHolding} Equity to trade: {equityToTrade} Signal Value {signalValue}")
if not self.algo.Portfolio.Invested:
insights.append(Insight(Symbol.Create(equityToTrade, SecurityType.Equity, Market.USA), self.period, InsightType.Price, entryTradeDirection, None, 1, None, 1 ))
else:
for security in self.algo.Portfolio.Values:
if security.Invested and security.Symbol.Value == equityToTrade:
equitiesCarried.append(security.Symbol.Value)
if security.Invested and security.Symbol.Value != equityToTrade:
equitiesExited.append(security.Symbol.Value)
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
if security.Symbol.Value == equityToTrade and not security.Invested:
equitiesNew.append(security.Symbol.Value)
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, entryTradeDirection, None, 1, None, 1 ))
self.currentHolding = equityToTrade
return insights
def OnSecuritiesChanged(self, algorithm, changes):
'''Event fired each time the we add/remove securities from the data feed
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
pass
#region imports
from AlgorithmImports import *
#endregion
import pandas as pd
import datetime
from dateutil.relativedelta import relativedelta
from io import StringIO
from scipy import stats
from statistics import mean
import time
def reorder_columns(dataframe, col_name, position):
temp_col = dataframe[col_name]
dataframe = dataframe.drop(columns=[col_name])
dataframe.insert(loc=position, column=col_name, value=temp_col)
return dataframe
class CygnetCM2MomentumAlpha(AlphaModel):
def __init__(self, algorithm, securities):
self.algo = algorithm
self.myList = securities
self.lastTradeTime = self.algo.Time - timedelta(days=31)
self.tradeFrequency = timedelta(days=30)
self.period = timedelta(days=31)
#self.MyInit()
self.df = pd.DataFrame(columns=["Symbol", "Name", "Sector"])
self.df.Symbol = self.myList
self.df.Name = ""
self.df.Sector = ""
self.df = self.df.set_index("Symbol")
self.df = self.df.sort_index()
pass
def Update(self, algorithm, data):
# Return no insights if it's not time to rebalance
if (self.algo.Time - self.lastTradeTime) < self.tradeFrequency:
return []
#self.algo.Debug("Begin of Update()")
self.lastTradeTime = self.algo.Time
#self.MyInit()
df = self.ComputeSignal()
if len(df.index) <= 40:
numberOfEquitiesToTrade = int(0.5*len(df.index) )
equitiesToTrade = [x for x in df.index[0:numberOfEquitiesToTrade] ]
else:
equitiesToTrade = [x for x in df.index[0:20] ]
#self.algo.Debug("New equities to acquire:")
self.algo.Debug(equitiesToTrade)
#equitiesToTrade = ["IBM", "CSCO", "SPY"]
#for symbol in equitiesToTrade:
# self.algo.AddEquity(symbol)
equitiesCarriedFromPriorTrade = ["equitiesCarriedFromPriorTrade: "]
insights = []
#Close old positions if they aren't in equitiesToTrade
for security in self.algo.Portfolio.Values:
if security.Invested and security.Symbol in equitiesToTrade:
equitiesCarriedFromPriorTrade.append(str(security.Symbol))
if security.Invested and security.Symbol not in equitiesToTrade:
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
for symbol in equitiesToTrade:
insights.append(Insight(symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Up, None, 1, None, 1 ))
self.algo.Debug(equitiesCarriedFromPriorTrade)
#self.algo.Debug("End of Update()")
return insights
pass
def OnSecuritiesChanged(self, algorithm, changes):
pass
def ComputeSignal(self):
#set parameters for the Alpha, set end date and duration of analysis (replace months=1)
#end_date= date.today()
#start_date = datetime.datetime(2022, 4, 1) # Set Start Date
#self.algo.Debug("Begin of ComputeSignal()")
#self.MyInit()
#df = self.df
end_date = self.algo.Time #datetime.datetime(2022, 4, 11) # Set Start Date
start_date = end_date - timedelta(days=14)
#return None
#self.algo.Debug(pd.show_versions()) #this does not work in pandas running on QuantConnect
#pd.show_versions()
#end_date = datetime.datetime.now() #datetime(2022, 4, 3)
#start_date = end_date - timedelta(days=14)
self.algo.Debug(f"ComputeSignal Start date {start_date} End date {end_date}")
#return None
histData = self.algo.History(self.myList, start_date, end_date, Resolution.Daily)
#time.sleep(120)
#self.algo.Debug(f"Point 2 {histData.shape}")
histData = histData["close"].unstack(level = 0)
#this block of code renames QuantConnect security identifiers to familiar stock symbols
#self.algo.Debug(f"Point 2A Histdata shape: {histData.shape}")
newCols = []
for x in histData.columns:
newCols.append(str(self.algo.Symbol(x)))
histData.columns = newCols
#self.algo.Debug(f"Point 2B {histData.shape}")
#histData = self.algo.History(self.algo.Securities.Keys, start_date, end_date, Resolution.Daily).close.unstack(level = 0)
histRelChg = histData.pct_change()[1:] * 100
#self.algo.Debug(f"Point 3 {histData.shape}")
histRelChg = histRelChg.dropna()
#self.algo.Debug(f"Point 4 {histData.shape}")
histRelChg.columns = newCols
print(histRelChg.SPY)
#histRelChg
benchmark = "SPY"
self.algo.AddEquity(benchmark)
histSPYData = self.algo.History([benchmark], start_date, end_date, Resolution.Daily)["close"].unstack(level = 0)
histSPYRelChg = histSPYData.pct_change()[1:] * 100
histSPYRelChg = histSPYRelChg.dropna()
newSPYCols = [benchmark]
histSPYData.columns = [benchmark]
histSPYRelChg.columns = [benchmark]
bpm = histSPYRelChg [histSPYRelChg.SPY >= 0.0]
bnm = histSPYRelChg [histSPYRelChg.SPY < 0.0]
benchmarkPosMomentum = bpm.mean()
benchmarkNegMomentum = bnm.mean()
self.algo.Debug(f"benchmarkPosMomentum: {round(float(benchmarkPosMomentum),2)} benchmarkNegMomentum: {round(float(benchmarkNegMomentum),2)}")
dfp = histRelChg[histSPYRelChg["SPY"] >= 0]
dfn = histRelChg[histSPYRelChg["SPY"] < 0]
dfpmean = dfp.describe()
dfpmean = dfpmean.transpose()
dfnmean = dfn.describe()
dfnmean = dfnmean.transpose()
#self.algo.Debug("dfpmean shape")
#self.algo.Debug(dfpmean.shape)
#self.algo.Debug("dfnmean shape")
#self.algo.Debug(dfnmean.shape)
#df = self.df.copy(deep=True)
#self.algo.Debug("df shape")
#self.algo.Debug(df.shape)
df = self.df
df["PosMomentum"] = dfpmean["mean"]
df["NegMomentum"] = dfnmean["mean"]
df["PosMomentum"] = df["PosMomentum"].divide(float(benchmarkPosMomentum))
df["NegMomentum"] = df["NegMomentum"].divide(float(benchmarkNegMomentum))
df["CygnetMomentum"] = df["PosMomentum"] - df["NegMomentum"]
#df.reset_index(drop=False, inplace=True)
df = df.sort_values("CygnetMomentum", ascending=False)
#self.algo.Debug("End of ComputeSignal()")
return df
'''
how dfx.head() looks like
Name Sector PosMomentum NegMomentum CygnetMomentum
Symbol
NLSN Nielsen Holdings Industrials 3.387716 -0.562380 3.950096
TSLA Tesla Consumer Discretionary 3.419067 0.432448 2.986619
IRM Iron Mountain Real Estate 1.605596 -0.836687 2.442283
ALB Albemarle Corporation Materials 2.214053 -0.195778 2.409831
DXCM DexCom Health Care 3.483101 1.166991 2.316111
AAL American Airlines Group Industrials 2.672028 0.467772 2.204256
UAL United Airlines Industrials 2.167603 0.023625 2.143978
CTRA Coterra Energy -0.114747 -2.240039 2.125292
VLO Valero Energy Energy 0.045044 -2.046242 2.091286
NCLH Norwegian Cruise Line Holdings Consumer Discretionary 1.862464 -0.214142 2.076606
NEM Newmont Materials 0.628931 -1.435697 2.064628
RCL Royal Caribbean Group Consumer Discretionary 1.591941 -0.469435 2.061376
LW Lamb Weston Consumer Staples 2.396768 0.343169 2.053599
AES AES Corp Utilities 2.120699 0.072254 2.048444
ENPH Enphase Energy Information Technology 1.936349 -0.082361 2.018710
GIS General Mills Consumer Staples 1.245742 -0.744064 1.989806
EIX Edison International Utilities 1.248746 -0.738932 1.987678
PAYX Paychex Information Technology 1.477180 -0.394500 1.871680
EXC Exelon Utilities 1.356745 -0.440739 1.797484
ABMD Abiomed Health Care 2.013165 0.234625 1.778541
'''
import pandas as pd
import datetime
from datetime import datetime
from AlgorithmImports import *
from dateutil.relativedelta import relativedelta
from io import StringIO
from scipy import stats
from statistics import mean
def get_historical_close(symbol: str, timedelta: dict, hist: pd.DataFrame, end_date):
'''
Accepts a symbol, a time delta to look in the past like {months: 6} or {years: 1} and the history
dataframe.
Returns the closing price of the symbol on or immediately after the time delta.
Assumes that the history dataframe is indexed on symbol then time ascending.
'''
single_symbol_data = hist[hist.index.get_level_values("symbol") == symbol]
return single_symbol_data[single_symbol_data.index.get_level_values("time") >= end_date - relativedelta(**timedelta)].iloc[0].close
def reorder_columns(dataframe, col_name, position):
"""Reorder a dataframe's column.
Args:
dataframe (pd.DataFrame): dataframe to use
col_name (string): column name to move
position (0-indexed position): where to relocate column to
Returns:
pd.DataFrame: re-assigned dataframe
"""
temp_col = dataframe[col_name]
dataframe = dataframe.drop(columns=[col_name])
dataframe.insert(loc=position, column=col_name, value=temp_col)
return dataframe
def calculate_hqm_score(row):
momentum_percentiles = []
# calculate percentiles of period returns
time_periods = ['6m', '3m', '1m'] # ['1y', '6m', '3m', '1m']
for time_period in time_periods:
momentum_percentiles.append(row[f'return_{time_period}_percentile'])
return mean (momentum_percentiles)
class CygnetCM3HQMAlpha(AlphaModel):
def __init__(self, algorithm, securities):
#algorithm.Debug("Begin of CygnetHQMAlpha.__init__()")
self.algo = algorithm
self.myList = securities
self.lastTradeTime = self.algo.Time - timedelta(days=31)
self.tradeFrequency = timedelta(days=30)
self.period = timedelta(days=31)
self.df = pd.DataFrame(columns=["Symbol", "Name", "Sector"])
self.df.Symbol = self.myList
self.df.Name = ""
self.df.Sector = ""
self.df = self.df.set_index("Symbol")
self.df = self.df.sort_index()
pass
def Update(self, algorithm, data):
# Return no insights if it's not time to rebalance
if (self.algo.Time - self.lastTradeTime) < self.tradeFrequency:
return []
#self.algo.Debug("Begin of Update()")
self.lastTradeTime = self.algo.Time
df = self.ComputeSignal()
if len(df.index) <= 10:
#numberOfEquitiesToTrade = len(df(index))
equitiesToTrade = [x for x in df.index]
else:
equitiesToTrade = [x for x in df.index[0:10] ]
#self.algo.Debug("New equities to acquire:")
self.algo.Debug(equitiesToTrade)
#equitiesToTrade = ["IBM", "CSCO", "SPY"]
#for symbol in equitiesToTrade:
# self.algo.AddEquity(symbol)
equitiesCarriedFromPriorTrade = ["equitiesCarriedFromPriorTrade: "]
insights = []
#Close old positions if they aren't in equitiesToTrade
for security in self.algo.Portfolio.Values:
if security.Invested and security.Symbol in equitiesToTrade:
equitiesCarriedFromPriorTrade.append(str(security.Symbol))
if security.Invested and security.Symbol not in equitiesToTrade:
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
for symbol in equitiesToTrade:
insights.append(Insight(symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Up, None, 1, None, 1 ))
self.algo.Debug(equitiesCarriedFromPriorTrade)
#self.algo.Debug("End of Update()")
return insights
pass
def OnSecuritiesChanged(self, algorithm, changes):
pass
def ComputeSignal(self):
#self.algo.Debug("Begin of ComputeSignal()")
end_date = self.algo.Time #datetime.datetime(2022, 4, 11) # Set Start Date
start_date = end_date - timedelta(days=190)
self.algo.Debug(f"ComputeSignal Start date {start_date} End date {end_date}")
#return None
histData = self.algo.History(self.myList, start_date, end_date, Resolution.Daily) #["close"].unstack(level = 0)
#self.algo.Debug(f"Point 2A Histdata shape: {histData.shape}")
#this block of code renames QuantConnect security identifiers to familiar stock symbols
'''
newCols = []
for x in histData.columns:
newCols.append(str(self.algo.Symbol(x)))
histData.columns = newCols
#self.algo.Debug(f"Point 2B {histData.shape}")
'''
# sets up the table we will use to track the HQM scores by symbol (expensive)
# in trading this can be done using consolidation, but consolidation is not supported in QuantBook
momentum = pd.Series(histData.index.unique(level=0)).to_frame()
momentum["EquitySymbol"] = "Unknown"
momentum["EquitySymbol"] = momentum["symbol"].apply (lambda x: str(self.algo.Symbol(x)))
#this block of code, occassionally (eg, 2019 November / December tiemframe) throws SingleIndex out of position error; it is replaced with the code above
#momentum["close_1y"] = momentum.symbol.apply(lambda x: get_historical_close(x, {"years": 1}, histData, end_date))
momentum["close_6m"] = momentum.symbol.apply(lambda x: get_historical_close(x, {"months": 6}, histData, end_date))
momentum["close_3m"] = momentum.symbol.apply(lambda x: get_historical_close(x, {"months": 3}, histData, end_date))
momentum["close_1m"] = momentum.symbol.apply(lambda x: get_historical_close(x, {"months": 1}, histData, end_date))
momentum["close_today"] = momentum.symbol.apply(lambda x: histData[histData.index.get_level_values("symbol") == x].iloc[-1].close)
# in QuantConnect, if this test passes we don't need this research pipeline to be robust to null values
assert len(momentum[momentum.isna().any(axis=1)]) == 0
# calculate percentiles of period returns
time_periods = ['6m', '3m', '1m']
for time_period in time_periods:
momentum[f'return_{time_period}'] = (momentum.close_today - momentum[f'close_{time_period}']) / momentum[f'close_{time_period}']
momentum[f'return_{time_period}_percentile'] = momentum[f'return_{time_period}'].apply (lambda x: stats.percentileofscore(momentum[f'return_{time_period}'], x))
momentum["HQM Score"] = momentum.apply (calculate_hqm_score, axis = 1)
#momentum["EquitySymbol"] = ""
momentum = reorder_columns(momentum, "EquitySymbol", 0)
momentum = reorder_columns(momentum, "HQM Score", 1)
#for i in range(len(momentum)):
# momentum["EquitySymbol"][i] = str(qb.Symbol(momentum["symbol"][i]))
momentum = momentum.set_index("EquitySymbol").sort_values(by="HQM Score", ascending = False)
self.algo.Debug(momentum.shape)
return momentum
import pandas as pd
import datetime
from datetime import datetime
from AlgorithmImports import *
from dateutil.relativedelta import relativedelta
from io import StringIO
from scipy import stats
from statistics import mean
'''
version notes
v1 (original)
v2 - several code improvements
v3 - Replcing insights with straight buy orders
'''
def get_historical_close(symbol: str, timedelta: dict, hist: pd.DataFrame, end_date):
'''
Accepts a symbol, a time delta to look in the past like {months: 6} or {years: 1} and the history
dataframe.
Returns the closing price of the symbol on or immediately after the time delta.
Assumes that the history dataframe is indexed on symbol then time ascending.
'''
single_symbol_data = hist[hist.index.get_level_values("symbol") == symbol]
return single_symbol_data[single_symbol_data.index.get_level_values("time") >= end_date - relativedelta(**timedelta)].iloc[0].close
def reorder_columns(dataframe, col_name, position):
"""Reorder a dataframe's column.
Args:
dataframe (pd.DataFrame): dataframe to use
col_name (string): column name to move
position (0-indexed position): where to relocate column to
Returns:
pd.DataFrame: re-assigned dataframe
"""
temp_col = dataframe[col_name]
dataframe = dataframe.drop(columns=[col_name])
dataframe.insert(loc=position, column=col_name, value=temp_col)
return dataframe
def calculate_hqm_score(df):
momentum_percentiles = []
# calculate percentiles of period returns
time_periods = ['6m', '3m', '1m'] # ['1y', '6m', '3m', '1m']
for time_period in time_periods:
momentum_percentiles.append(df[f'return_{time_period}_percentile'])
return sum(momentum_percentiles) #mean(momentum_percentiles)
class CygnetCM3HQMAlpha2(AlphaModel):
def __init__(self, algorithm, securities):
#algorithm.Debug("Begin of CygnetHQMAlpha.__init__()")
self.algo = algorithm
self.myList = securities
self.lastTradeTime = self.algo.Time - timedelta(days=31)
self.tradeFrequency = timedelta(days=30)
self.period = timedelta(days=31)
self.longs = []
self.myStocksList = []
self.df = pd.DataFrame(columns=["Symbol", "Name", "Sector"])
self.df.Symbol = self.myList
self.df.Name = ""
self.df.Sector = ""
self.df = self.df.set_index("Symbol")
self.df = self.df.sort_index()
pass
def Update(self, algorithm, data):
# Return no insights if it's not time to rebalance
if (self.algo.Time - self.lastTradeTime) < self.tradeFrequency:
return []
#self.algo.Debug("Begin of Update()")
self.lastTradeTime = self.algo.Time
df = self.ComputeSignal()
maxNumOfHoldings = 12
startingRank = 5
maxNumOfHoldings = min(len(df.index), maxNumOfHoldings)
equitiesToTrade = [x for x in df.index[startingRank:maxNumOfHoldings] ]
#self.algo.Debug("New equities to acquire:")
self.algo.Debug(equitiesToTrade)
#equitiesToTrade = ["IBM", "CSCO", "SPY"]
#for symbol in equitiesToTrade:
# self.algo.AddEquity(symbol)
insights = []
'''
#Close old positions if they aren't in equitiesToTrade
for security in self.algo.Portfolio.Values:
if security.Invested and security.Symbol in equitiesToTrade:
equitiesCarriedFromPriorTrade.append(str(security.Symbol))
if security.Invested and security.Symbol not in equitiesToTrade:
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
for symbol in equitiesToTrade:
if self.algo.tradeDirection == 1:
insights.append(Insight(symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Up, None, 1, None, 1 ))
if self.algo.tradeDirection == -1:
insights.append(Insight(symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Down, None, 1, None, 1 ))
else:
insights.append(Insight(symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, 1, None, 1 ))
self.algo.Debug(equitiesCarriedFromPriorTrade)
#self.algo.Debug("End of Update()")
return insights
pass
'''
#VR 4/1/23
# Close old positions if they aren't in equitiesToTrade list
for security in algorithm.Portfolio.Values:
if security.Invested and security.Symbol not in equitiesToTrade:
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
sumOfClosingPrices = df["close_today"][startingRank:maxNumOfHoldings].sum() # calc sum of prices to figure out how many shares to buy
numOfSharesToBuy = math.floor(algorithm.Portfolio.TotalPortfolioValue / sumOfClosingPrices)
for sym in equitiesToTrade:
if not security.Invested: # if it is already in portfolio, do not buy again
self.algo.MarketOrder(sym, numOfSharesToBuy)
return insights
def OnSecuritiesChanged(self, algorithm, changes):
pass
def ComputeSignal(self):
#self.algo.Debug("Begin of ComputeSignal()")
end_date = self.algo.Time #datetime.datetime(2022, 4, 11) # Set Start Date
start_date = end_date - timedelta(days=190)
self.algo.Debug(f"ComputeSignal Start date {start_date} End date {end_date}")
#return None
histData = self.algo.History(self.myList, start_date, end_date, Resolution.Daily)["close"].unstack(level = 0)
#this block of code renames QuantConnect security identifiers to familiar stock symbols
newCols = []
for x in histData.columns:
newCols.append(str(self.algo.Symbol(x)))
histData.columns = newCols
#self.algo.Debug(f"Point 2B {histData.shape}")
histData = histData.transpose()
#self.algo.Debug(f"Point 2A Histdata shape: {histData.shape}")
momentum = pd.DataFrame(index=histData.index, columns=["close_today", "close_1m", "close_3m", "close_6m"])
momentum = momentum.fillna(0.0)
#momentum["EquitySymbol"] = "Unknown"
#momentum["EquitySymbol"] = momentum["symbol"].apply (lambda x: str(self.algo.Symbol(x)))
for i in range(len(momentum)):
momentum.iloc[i, 0] = histData.iloc[i, -1]
momentum.iloc[i, 1] = histData.iloc[i, -21]
momentum.iloc[i, 2] = histData.iloc[i, -63]
momentum.iloc[i, 3] = histData.iloc[i, -126]
# calculate percentiles of period returns
time_periods = ['6m', '3m', '1m']
numOfBins = 100
for time_period in time_periods:
momentum[f'return_{time_period}'] = (momentum.close_today - momentum[f'close_{time_period}']) / momentum[f'close_{time_period}']
#momentum[f'return_{time_period}_percentile'] = momentum[f'return_{time_period}'].apply (lambda x: stats.percentileofscore(momentum[f'return_{time_period}'], x))
momentum[f'return_{time_period}_percentile'] = pd.qcut(momentum[f'return_{time_period}'], numOfBins, labels = False) # divvy up into bins
momentum["HQM Score"] = momentum.apply (calculate_hqm_score, axis = 1)
#for i in range(len(momentum)):
# momentum["EquitySymbol"][i] = str(qb.Symbol(momentum["symbol"][i]))
#momentum = momentum.set_index("EquitySymbol")
momentum = momentum.sort_values(by="HQM Score", ascending = False)
momentum = reorder_columns(momentum, "HQM Score", 0)
self.algo.Debug(momentum.shape)
return momentumimport pandas as pd
import datetime
from datetime import datetime
from AlgorithmImports import *
from dateutil.relativedelta import relativedelta
from io import StringIO
from scipy import stats
from statistics import mean
def get_historical_close(symbol: str, timedelta: dict, hist: pd.DataFrame, end_date):
'''
Accepts a symbol, a time delta to look in the past like {months: 6} or {years: 1} and the history
dataframe.
Returns the closing price of the symbol on or immediately after the time delta.
Assumes that the history dataframe is indexed on symbol then time ascending.
'''
single_symbol_data = hist[hist.index.get_level_values("symbol") == symbol]
return single_symbol_data[single_symbol_data.index.get_level_values("time") >= end_date - relativedelta(**timedelta)].iloc[0].close
def reorder_columns(dataframe, col_name, position):
"""Reorder a dataframe's column.
Args:
dataframe (pd.DataFrame): dataframe to use
col_name (string): column name to move
position (0-indexed position): where to relocate column to
Returns:
pd.DataFrame: re-assigned dataframe
"""
temp_col = dataframe[col_name]
dataframe = dataframe.drop(columns=[col_name])
dataframe.insert(loc=position, column=col_name, value=temp_col)
return dataframe
def calculate_hqm_score(row):
momentum_percentiles = []
# calculate percentiles of period returns
time_periods = ['3m', '1m'] # ['1y', '6m', '3m', '1m']
for time_period in time_periods:
momentum_percentiles.append(row[f'return_{time_period}_percentile'])
return mean (momentum_percentiles)
class CygnetCM7Alpha(AlphaModel):
def __init__(self, algorithm, securities):
#algorithm.Debug("Begin of CygnetHQMAlpha.__init__()")
self.algo = algorithm
self.myList = securities
self.lastTradeTime = self.algo.Time - timedelta(days=31)
self.tradeFrequency = timedelta(days=30)
self.period = timedelta(days=31)
self.df = pd.DataFrame(columns=["Symbol", "Name", "Sector"])
self.df.Symbol = self.myList
self.df.Name = ""
self.df.Sector = ""
self.df = self.df.set_index("Symbol")
self.df = self.df.sort_index()
pass
def Update(self, algorithm, data):
# Return no insights if it's not time to rebalance
if (self.algo.Time - self.lastTradeTime) < self.tradeFrequency:
return []
#self.algo.Debug("Begin of Update()")
self.lastTradeTime = self.algo.Time
df = self.ComputeSignal()
if len(df.index) <= 40:
numberOfEquitiesToTrade = int(0.5*len(df.index) )
equitiesToTrade = [x for x in df.index[0:numberOfEquitiesToTrade] ]
else:
equitiesToTrade = [x for x in df.index[0:20] ]
#self.algo.Debug("New equities to acquire:")
self.algo.Debug(equitiesToTrade)
#equitiesToTrade = ["IBM", "CSCO", "SPY"]
#for symbol in equitiesToTrade:
# self.algo.AddEquity(symbol)
equitiesCarriedFromPriorTrade = ["equitiesCarriedFromPriorTrade: "]
insights = []
#Close old positions if they aren't in equitiesToTrade
for security in self.algo.Portfolio.Values:
if security.Invested and security.Symbol in equitiesToTrade:
equitiesCarriedFromPriorTrade.append(str(security.Symbol))
if security.Invested and security.Symbol not in equitiesToTrade:
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
for symbol in equitiesToTrade:
insights.append(Insight(symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Up, None, 1, None, 1 ))
self.algo.Debug(equitiesCarriedFromPriorTrade)
#self.algo.Debug("End of Update()")
return insights
pass
def OnSecuritiesChanged(self, algorithm, changes):
pass
def ComputeSignal(self):
#self.algo.Debug("Begin of ComputeSignal()")
end_date = self.algo.Time #datetime.datetime(2022, 4, 11) # Set Start Date
start_date = end_date - timedelta(days=190)
self.algo.Debug(f"ComputeSignal Start date {start_date} End date {end_date}")
#return None
histData = self.algo.History(self.myList, start_date, end_date, Resolution.Daily)["close"].unstack(level = 0)
#this block of code renames QuantConnect security identifiers to familiar stock symbols
newCols = []
for x in histData.columns:
newCols.append(str(self.algo.Symbol(x)))
histData.columns = newCols
#self.algo.Debug(f"Point 2B {histData.shape}")
histData = histData.transpose()
#self.algo.Debug(f"Point 2A Histdata shape: {histData.shape}")
momentum = pd.DataFrame(index=histData.index, columns=["close_today", "close_1m", "close_3m", "close_6m"])
momentum = momentum.fillna(0.0)
#momentum["EquitySymbol"] = "Unknown"
#momentum["EquitySymbol"] = momentum["symbol"].apply (lambda x: str(self.algo.Symbol(x)))
for i in range(len(momentum)):
momentum.iloc[i, 0] = histData.iloc[i, -1]
momentum.iloc[i, 1] = histData.iloc[i, -21]
momentum.iloc[i, 2] = histData.iloc[i, -63]
momentum.iloc[i, 3] = histData.iloc[i, -126]
# calculate percentiles of period returns
time_periods = ['3m', '1m']
for time_period in time_periods:
momentum[f'return_{time_period}'] = (momentum.close_today - momentum[f'close_{time_period}']) / momentum[f'close_{time_period}']
momentum[f'return_{time_period}_percentile'] = momentum[f'return_{time_period}'].apply (lambda x: stats.percentileofscore(momentum[f'return_{time_period}'], x))
momentum["HQM Score"] = momentum.apply (calculate_hqm_score, axis = 1)
#for i in range(len(momentum)):
# momentum["EquitySymbol"][i] = str(qb.Symbol(momentum["symbol"][i]))
#momentum = momentum.set_index("EquitySymbol")
momentum = momentum.sort_values(by="HQM Score", ascending = False)
momentum = reorder_columns(momentum, "HQM Score", 0)
self.algo.Debug(momentum.shape)
return momentum
import pandas as pd
import datetime
from datetime import datetime
from AlgorithmImports import *
from dateutil.relativedelta import relativedelta
from io import StringIO
from scipy import stats
from statistics import mean
def get_historical_close(symbol: str, timedelta: dict, hist: pd.DataFrame, end_date):
'''
Accepts a symbol, a time delta to look in the past like {months: 6} or {years: 1} and the history
dataframe.
Returns the closing price of the symbol on or immediately after the time delta.
Assumes that the history dataframe is indexed on symbol then time ascending.
'''
single_symbol_data = hist[hist.index.get_level_values("symbol") == symbol]
return single_symbol_data[single_symbol_data.index.get_level_values("time") >= end_date - relativedelta(**timedelta)].iloc[0].close
def reorder_columns(dataframe, col_name, position):
"""Reorder a dataframe's column.
Args:
dataframe (pd.DataFrame): dataframe to use
col_name (string): column name to move
position (0-indexed position): where to relocate column to
Returns:
pd.DataFrame: re-assigned dataframe
"""
temp_col = dataframe[col_name]
dataframe = dataframe.drop(columns=[col_name])
dataframe.insert(loc=position, column=col_name, value=temp_col)
return dataframe
def calculate_hqm_score(df):
momentum_percentiles = []
# calculate percentiles of period returns
time_periods = ['3m', '1m'] # ['1y', '6m', '3m', '1m']
for time_period in time_periods:
momentum_percentiles.append(df[f'return_{time_period}_percentile'])
return sum(momentum_percentiles) #mean(momentum_percentiles)
class CygnetCM7Alpha2(AlphaModel):
def __init__(self, algorithm, securities):
#algorithm.Debug("Begin of CygnetHQMAlpha.__init__()")
self.algo = algorithm
self.myList = securities
self.lastTradeTime = self.algo.Time - timedelta(days=31)
self.tradeFrequency = timedelta(days=30)
self.period = timedelta(days=31)
self.df = pd.DataFrame(columns=["Symbol", "Name", "Sector"])
self.df.Symbol = self.myList
self.df.Name = ""
self.df.Sector = ""
self.df = self.df.set_index("Symbol")
self.df = self.df.sort_index()
pass
def Update(self, algorithm, data):
# Return no insights if it's not time to rebalance
if (self.algo.Time - self.lastTradeTime) < self.tradeFrequency:
return []
#self.algo.Debug("Begin of Update()")
self.lastTradeTime = self.algo.Time
df = self.ComputeSignal()
equitiesToTrade = [x for x in df.index[:20] ]
# figure out how many shares to buy
sumOfClosingPrices = df["close_today"][:20].sum()
#self.algo.Debug("New equities to acquire:")
self.algo.Debug(equitiesToTrade)
insights = []
#VR 4/1/23
# Close old positions if they aren't in equitiesToTrade list
for security in algorithm.Portfolio.Values:
if security.Invested and security.Symbol not in equitiesToTrade:
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
numOfSharesToBuy = math.floor(algorithm.Portfolio.TotalPortfolioValue / sumOfClosingPrices)
for sym in equitiesToTrade:
if not security.Invested: # if it is already in portfolio, do not buy again
self.algo.MarketOrder(sym, numOfSharesToBuy)
return insights
def OnSecuritiesChanged(self, algorithm, changes):
pass
def ComputeSignal(self):
#self.algo.Debug("Begin of ComputeSignal()")
end_date = self.algo.Time #datetime.datetime(2022, 4, 11) # Set Start Date
start_date = end_date - timedelta(days=190)
self.algo.Debug(f"ComputeSignal Start date {start_date} End date {end_date}")
#return None
histData = self.algo.History(self.myList, start_date, end_date, Resolution.Daily)["close"].unstack(level = 0)
#this block of code renames QuantConnect security identifiers to familiar stock symbols
newCols = []
for x in histData.columns:
newCols.append(str(self.algo.Symbol(x)))
histData.columns = newCols
#self.algo.Debug(f"Point 2B {histData.shape}")
histData = histData.transpose()
#self.algo.Debug(f"Point 2A Histdata shape: {histData.shape}")
momentum = pd.DataFrame(index=histData.index, columns=["close_today", "close_1m", "close_3m", "close_6m"])
momentum = momentum.fillna(0.0)
#momentum["EquitySymbol"] = "Unknown"
#momentum["EquitySymbol"] = momentum["symbol"].apply (lambda x: str(self.algo.Symbol(x)))
for i in range(len(momentum)):
momentum.iloc[i, 0] = histData.iloc[i, -1]
momentum.iloc[i, 1] = histData.iloc[i, -21]
momentum.iloc[i, 2] = histData.iloc[i, -63]
momentum.iloc[i, 3] = histData.iloc[i, -126]
# calculate percentiles of period returns
time_periods = ['3m', '1m']
numOfBins = 100
for time_period in time_periods:
momentum[f'return_{time_period}'] = (momentum.close_today - momentum[f'close_{time_period}']) / momentum[f'close_{time_period}']
#momentum[f'return_{time_period}_percentile'] = momentum[f'return_{time_period}'].apply (lambda x: stats.percentileofscore(momentum[f'return_{time_period}'], x))
momentum[f'return_{time_period}_percentile'] = pd.qcut(momentum[f'return_{time_period}'], numOfBins, labels=False) # divvy up into bins
momentum["HQM Score"] = momentum.apply (calculate_hqm_score, axis = 1)
#for i in range(len(momentum)):
# momentum["EquitySymbol"][i] = str(qb.Symbol(momentum["symbol"][i]))
#momentum = momentum.set_index("EquitySymbol")
momentum = momentum.sort_values(by="HQM Score", ascending = False)
momentum = reorder_columns(momentum, "HQM Score", 0)
self.algo.Debug(momentum.shape)
return momentum
import pandas as pd
import datetime as dt
from AlgorithmImports import *
from dateutil.relativedelta import relativedelta
from io import StringIO
from scipy import stats
from statistics import mean
def most_recent_business_day(date:datetime):
#https://www.geeksforgeeks.org/python-get-most-recent-previous-business-day/Method 3
ts = pd.Timestamp(str(date))
offset = pd.tseries.offsets.BusinessDay(n=1)
return pd.to_datetime(ts - offset)
def get_historical_close(symbol: str, timedelta: dict, hist: pd.DataFrame, end_date):
'''
Accepts a symbol, a time delta to look in the past like {months: 6} or {years: 1} and the history
dataframe.
Returns the closing price of the symbol on or immediately after the time delta.
Assumes that the history dataframe is indexed on symbol then time ascending.
'''
single_symbol_data = hist[hist.index.get_level_values("symbol") == symbol]
return single_symbol_data[single_symbol_data.index.get_level_values("time") >= end_date - relativedelta(**timedelta)].iloc[0].close
def calculate_rv_score(row):
value_percentiles = []
for column in ["PERatio", "PBRatio", "PSRatio", "EVToEBITDA", "EVtoGrossProfit"]:
value_percentiles.append(row[f"{column}_percentile"])
return mean(value_percentiles)
class CygnetCV5Alpha(AlphaModel):
def __init__(self, algorithm, securities):
algorithm.Debug("Begin of CygnetCV5Alpha.__init__()")
self.algo = algorithm
self.myList = securities
self.lastTradeTime = self.algo.Time - timedelta(days=31)
self.tradeFrequency = timedelta(days=30)
self.period = timedelta(days=31)
algorithm.Debug("End of CygnetCV5Alpha.__init__()")
pass
def Update(self, algorithm, data):
# Return no insights if it's not time to rebalance
if (self.algo.Time - self.lastTradeTime) < self.tradeFrequency:
return []
self.algo.Debug("Begin of Update()")
self.lastTradeTime = self.algo.Time
df = self.ComputeSignal()
equitiesToTrade = [x for x in df.index[0:20] ]
#self.algo.Debug("New equities to acquire:")
self.algo.Debug(equitiesToTrade)
#equitiesToTrade = ["IBM", "CSCO", "SPY"]
#for symbol in equitiesToTrade:
# self.algo.AddEquity(symbol)
equitiesCarried = ["Equities carried: "]
equitiesExited = ["Equities to be exited: "]
equitiesNew = ["New equities to be traded: "]
insights = []
#Close old positions if they aren't in equitiesToTrade
for security in self.algo.Portfolio.Values:
if security.Invested and security.Symbol.Value in equitiesToTrade:
equitiesCarried.append(security.Symbol.Value)
if security.Invested and security.Symbol.Value not in equitiesToTrade:
equitiesExited.append(security.Symbol.Value)
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
if not security.Invested and security.Symbol.Value in equitiesToTrade:
equitiesNew.append(security.Symbol.Value)
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Up, None, 1, None, 1 ))
self.algo.Debug(equitiesCarried)
self.algo.Debug(equitiesExited)
self.algo.Debug(equitiesNew)
self.algo.Debug("End of Update()")
return insights
pass
def OnSecuritiesChanged(self, algorithm, universeChanges):
self.algo.Debug("Begin of OnSecuritiesChanged()")
'''
addedSecurities = [x.Symbol.Value for x in universeChanges.AddedSecurities]
self.algo.Log(addedSecurities)
if (self.algo.Time - self.lastTradeTime) < self.tradeFrequency:
return []
self.lastTradeTime = self.algo.Time
df = self.ComputeSignal()
'''
pass
def ComputeSignal(self):
self.algo.Debug("Begin of ComputeSignal()")
end_date = self.algo.Time #datetime.datetime(2022, 4, 11) # Set Start Date
start_date = end_date - timedelta(days=365)
self.algo.Debug(f"In ComputeSignal() algo.Time Start date {self.algo.Time}") #start and end date do not participate
valueMetrics = self.algo.fundamentalData.copy(deep=True)
self.algo.Debug(valueMetrics.shape)
valueMetrics.fillna(0, inplace=True)
# compute the last ratios
valueMetrics ["EVtoGrossProfit"] = valueMetrics["EnterpriseValue"] / valueMetrics ["GrossProfit"]
valueMetrics ["InvertedDivYield5Year"] = 1.0 - valueMetrics ["DivYield5Year"]
# cleanup the dataframe for metric calculation.
#valueMetrics = data.drop(['EnterpriseValue', 'GrossProfit', 'DivYield5Year'], axis = 1)
#valueMetrics = data
percentile_columns = ["PERatio", "PBRatio", "PSRatio", "EVToEBITDA", "EVtoGrossProfit", "InvertedDivYield5Year"] # all these are ratios such that lower they are, the more undervalued the stock is
for header in percentile_columns:
valueMetrics[f"{header}_percentile"] = valueMetrics[header].apply (lambda x: stats.percentileofscore(valueMetrics[header], x))
valueMetrics["rv_score"] = valueMetrics.apply(calculate_rv_score, axis = 1) #This line of code does the same as the code block below
'''valueMetrics["rv_score"] = 0.0
countOfCols = len(percentile_columns)
for sym in valueMetrics.index:
s = 0.0
for header in percentile_columns:
s += valueMetrics.loc[sym, f"{header}_percentile"]
valueMetrics.loc[sym, "rv_score"] = s / countOfCols'''
valueMetrics.sort_values("rv_score", ascending = True, inplace=True)
return valueMetrics
#region imports
from AlgorithmImports import *
#endregion
from System.Collections.Generic import List
from QuantConnect.Data.UniverseSelection import *
import operator
from math import ceil, floor
from scipy import stats
import numpy as np
from datetime import timedelta
class CygnetCV8PiotroskiFscoreAlpha(QCAlgorithm):
def __init__(self, algorithm, securities):
algorithm.Debug("Begin of PiotroskiFscoreAlpha.__init__()")
self.algo = algorithm
self.myList = securities
self.lastTradeTime = self.algo.Time - timedelta(days=30)
self.lastTradeTime2 = self.algo.Time - timedelta(days=30)
self.tradeFrequency = timedelta(days=30)
self.period = timedelta(days=31)
self.myCurrentSecurities = []
self.myNewSecurities = []
self.lastMonth1 = -1
self.lastMonth2 = -1
#algorithm.Debug("End of PiotroskiFscoreAlpha.__init__()")
pass
def Update(self, algorithm, data):
# Return no insights if it's not time to rebalance
if (self.lastMonth1 == self.algo.Time.month):
return []
self.lastMonth1 = self.algo.Time.month
self.algo.Debug("Begin of Update()")
#df = self.ComputeSignal()
equitiesToTrade = []
equitiesToTrade = [x.Value for x in self.myNewSecurities]
self.algo.Debug(equitiesToTrade)
#equitiesToTrade = ["IBM", "CSCO", "SPY"]
#for symbol in equitiesToTrade:
# self.algo.AddEquity(symbol)
equitiesCarried = ["Equities carried: "]
equitiesExited = ["Equities to be exited: "]
equitiesNew = ["New equities to be traded: "]
insights = []
#Close old positions if they aren't in equitiesToTrade
for security in self.algo.Portfolio.Values:
if security.Invested and security.Symbol.Value in equitiesToTrade:
equitiesCarried.append(security.Symbol.Value)
if security.Invested and security.Symbol.Value not in equitiesToTrade:
equitiesExited.append(security.Symbol.Value)
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
if not security.Invested and security.Symbol.Value in equitiesToTrade:
equitiesNew.append(security.Symbol.Value)
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Up, None, 1, None, 1 ))
self.algo.Debug(equitiesCarried)
self.algo.Debug(equitiesExited)
self.algo.Debug(equitiesNew)
self.myCurrentSecurities = self.myNewSecurities.copy()
self.algo.Debug("End of Update()")
return insights
pass
def OnSecuritiesChanged(self, algorithm, universeChanges):
if (self.lastMonth2 == self.algo.Time.month):
return []
self.lastMonth2 = self.algo.Time.month
self.lastTradeTime2 = self.algo.Time
self.algo.Debug("Begin of OnSecuritiesChanged()")
for x in universeChanges.AddedSecurities:
self.algo.Debug(f"Adding {len(universeChanges.AddedSecurities)}")
if x.Symbol not in self.myNewSecurities: self.myNewSecurities.append(x.Symbol)
z = len(universeChanges.RemovedSecurities)
for x in universeChanges.RemovedSecurities:
self.algo.Debug(f"Removing {len(universeChanges.RemovedSecurities)}")
sym = x.Symbol.Value
if x.Symbol in self.myNewSecurities: self.myNewSecurities.remove(x.Symbol)
self.algo.Debug(f"No of securities in {len(self.myNewSecurities)}")
#df = self.ComputeSignal()
pass
def ComputeSignal(self):
self.algo.Debug("Begin of ComputeSignal()")
end_date = self.algo.Time #datetime.datetime(2022, 4, 11) # Set Start Date
start_date = end_date - timedelta(days=365)
self.algo.Debug(f"In ComputeSignal() algo.Time Start date {self.algo.Time}") #start and end date do not participate
# return a dataframe with symbols in the index column
return valueMetrics
class PiotroskiFScore(object):
def __init__(self, netincome, operating_cashflow, roa_current,
roa_past, issued_current, issued_past, grossm_current, grossm_past,
longterm_current, longterm_past, curratio_current, curratio_past,
assetturn_current, assetturn_past):
self.netincome = netincome
self.operating_cashflow = operating_cashflow
self.roa_current = roa_current
self.roa_past = roa_past
self.issued_current = issued_current
self.issued_past = issued_past
self.grossm_current = grossm_current
self.grossm_past = grossm_past
self.longterm_current = longterm_current
self.longterm_past = longterm_past
self.curratio_current = curratio_current
self.curratio_past = curratio_past
self.assetturn_current = assetturn_current
self.assetturn_past = assetturn_past
def ObjectiveScore(self):
fscore = 0
fscore += np.where(self.netincome > 0, 1, 0)
fscore += np.where(self.operating_cashflow > 0, 1, 0)
fscore += np.where(self.roa_current > self.roa_past, 1, 0)
fscore += np.where(self.operating_cashflow > self.roa_current, 1, 0)
fscore += np.where(self.longterm_current <= self.longterm_past, 1, 0)
fscore += np.where(self.curratio_current >= self.curratio_past, 1, 0)
fscore += np.where(self.issued_current <= self.issued_past, 1, 0)
fscore += np.where(self.grossm_current >= self.grossm_past, 1, 0)
fscore += np.where(self.assetturn_current >= self.assetturn_past, 1, 0)
return fscore
#region imports
from AlgorithmImports import *
#endregion
from datetime import timedelta
#FundamentalFactorsAlpha
class CygnetCVM4Alpha(AlphaModel):
def __init__(self, algorithm, securities):
# Initialize the various variables/helpers we'll need
#algorithm.Debug("Begin of CygnetCV5Alpha.__init__()")
self.algo = algorithm
self.myList = securities #not used at all
self.lastTradeTime = self.algo.Time - timedelta(days=31)
self.tradeFrequency = timedelta(days=30)
self.period = timedelta(days=31)
self.lastMonth = -1
self.longs = []
self.num_fine = 20
# normalize quality, value, size weights
quality_weight, value_weight, size_weight = 1, 1, 2
weights = [quality_weight, value_weight, size_weight]
weights = [float(i)/sum(weights) for i in weights]
self.quality_weight = weights[0]
self.value_weight = weights[1]
self.size_weight = weights[2]
#self.MyInit()
#algorithm.Debug("End of CygnetCV5Alpha.__init__()")
pass
def Update(self, algorithm, data):
'''Updates this alpha model with the latest data from the algorithm.
This is called each time the algorithm receives data for subscribed securities
Args:
algorithm: The algorithm instance
data: The newa available
Returns:
New insights'''
# Return no insights if it's not time to rebalance
if algorithm.Time.month == self.lastMonth:
return []
self.lastMonth = algorithm.Time.month
# List of insights
# Insights of the form: Insight(symbol, timedelta, type, direction, magnitude, confidence, sourceModel, weight)
insights = []
# Close old positions if they aren't in longs
for security in algorithm.Portfolio.Values:
if security.Invested and security.Symbol not in self.longs:
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
length = len(self.longs)
for i in range(length):
insights.append(Insight(self.longs[i], self.algo.insightPeriod, InsightType.Price, InsightDirection.Up, None, (length - i)**2, None, (length - i)**2 ))
return insights
def OnSecuritiesChanged(self, algorithm, changes):
'''Event fired each time the we add/remove securities from the data feed
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
# Get the added securities
added = [x for x in changes.AddedSecurities]
# Assign quality, value, size score to each stock
quality_scores = self.Scores(added, [(lambda x: x.Fundamentals.OperationRatios.GrossMargin.Value, True, 1),
(lambda x: x.Fundamentals.OperationRatios.QuickRatio.Value, True, 2),
(lambda x: x.Fundamentals.OperationRatios.DebttoAssets.Value, False, 1)])
value_scores = self.Scores(added, [(lambda x: x.Fundamentals.ValuationRatios.BookValuePerShare, True, 0.25),
(lambda x: x.Fundamentals.ValuationRatios.CashReturn, True, 0.5),
(lambda x: x.Fundamentals.ValuationRatios.EarningYield, True, 0.5)])
size_scores = self.Scores(added, [(lambda x: x.Fundamentals.MarketCap, False, 1)])
scores = {}
# Assign a combined score to each stock
for symbol,value in quality_scores.items():
quality_rank = value
value_rank = value_scores[symbol]
size_rank = size_scores[symbol]
scores[symbol] = quality_rank*self.quality_weight + value_rank*self.value_weight + size_rank*self.size_weight # VR 5/5/21: what is the right way, multiply or divide
# Sort the securities by their scores
sorted_stock = sorted(scores.items(), key=lambda tup : tup[1], reverse=False) # VR 5/5/21: was reverse=False Are higher scores better or worse? Lower the score the better
sorted_symbol = [tup[0] for tup in sorted_stock][:self.num_fine]
# Sort the top stocks into the long_list
self.longs = [security.Symbol for security in sorted_symbol]
# Log longs symbols and their score
algorithm.Log(", ".join([str(x.Symbol.Value) + ": " + str(round(scores[x],2)) for x in sorted_symbol]))
def Scores(self, added, fundamentals):
'''Assigns scores to each stock in added
Args:
added: list of sceurities
fundamentals: list of 3-tuples (lambda function, bool, float)
Returns:
Dictionary with score for each security'''
length = len(fundamentals)
if length == 0:
return {}
# Initialize helper variables
scores = {}
sortedBy = []
rank = [0 for _ in fundamentals]
# Normalize weights
weights = [tup[2] for tup in fundamentals]
weights = [float(i)/sum(weights) for i in weights]
# Create sorted list for each fundamental factor passed
for tup in fundamentals:
sortedBy.append(sorted(added, key=tup[0], reverse=tup[1]))
# Create and save score for each symbol
for index, symbol in enumerate(sortedBy[0]):
# Save symbol's rank for each fundamental factor
rank[0] = index
for j in range(1, length):
rank[j] = sortedBy[j].index(symbol)
# Save symbol's total score
score = 0
for i in range(length):
score += rank[i] * weights[i]
scores[symbol] = score
return scores#region imports
from AlgorithmImports import *
#endregion
from datetime import timedelta
#FundamentalFactorsAlpha
'''
v2 Improvements:
Coarse selection will pick most liquid and have good fundamentals
'''
class CygnetCVM4AlphaV2(AlphaModel):
def __init__(self, algorithm, securities):
# Initialize the various variables/helpers we'll need
#algorithm.Debug("Begin of CygnetCV5Alpha.__init__()")
self.algo = algorithm
self.myList = securities #not used at all
self.lastTradeTime = self.algo.Time - timedelta(days=31)
self.tradeFrequency = timedelta(days=30)
self.period = timedelta(days=31)
self.lastMonth = -1
self.longs = []
self.num_fine = self.algo.num_fine
# normalize quality, value, size weights
quality_weight, value_weight, size_weight = 1, 1, 2
weights = [quality_weight, value_weight, size_weight]
weights = [float(i) / sum(weights) for i in weights]
self.quality_weight = weights[0]
self.value_weight = weights[1]
self.size_weight = weights[2]
self.myStocksList = []
#self.MyInit()
#algorithm.Debug("End of CygnetCV5Alpha.__init__()")
pass
def Update(self, algorithm, data):
'''Updates this alpha model with the latest data from the algorithm.
This is called each time the algorithm receives data for subscribed securities
Args:
algorithm: The algorithm instance
data: The newa available
Returns:
New insights'''
# Return no insights if it's not time to rebalance
if algorithm.Time.month == self.lastMonth:
return []
self.lastMonth = algorithm.Time.month
# List of insights
# Insights of the form: Insight(symbol, timedelta, type, direction, magnitude, confidence, sourceModel, weight)
insights = []
# Close old positions if they aren't in longs
for security in algorithm.Portfolio.Values:
if security.Invested and security.Symbol not in self.longs:
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
length = len(self.longs)
for i in range(length):
insights.append(Insight(self.longs[i], self.algo.insightPeriod, InsightType.Price, InsightDirection.Up, None, (length - i)**2, None, (length - i)**2 ))
return insights
def OnSecuritiesChanged(self, algorithm, changes):
'''Event fired each time the we add/remove securities from the data feed
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
# Get the added securities
securities = [x for x in changes.AddedSecurities]
# Assign quality, value, size score to each stock
quality_scores = self.Scores(securities, [(lambda x: x.Fundamentals.OperationRatios.GrossMargin.Value, True, 1),
(lambda x: x.Fundamentals.OperationRatios.QuickRatio.Value, True, 2),
(lambda x: x.Fundamentals.OperationRatios.DebttoAssets.Value, False, 1)])
value_scores = self.Scores(securities, [(lambda x: x.Fundamentals.ValuationRatios.BookValuePerShare, True, 1),
(lambda x: x.Fundamentals.ValuationRatios.CashReturn, True, 2),
(lambda x: x.Fundamentals.ValuationRatios.EarningYield, True, 2)])
size_scores = self.Scores(securities, [(lambda x: x.Fundamentals.MarketCap, False, 1)])
securityScores = {}
# Assign a combined score to each stock
#for symbol, value in quality_scores.items():
for symbol in securities:
quality_rank = quality_scores[symbol]
value_rank = value_scores[symbol]
size_rank = size_scores[symbol]
securityScores[symbol] = quality_rank*self.quality_weight + value_rank*self.value_weight + size_rank*self.size_weight # VR 5/5/21: what is the right way, multiply or divide
# Sort the securities by their scores
sorted_stock_dict = sorted(securityScores.items(), key = lambda tup : tup[1], reverse = False) # lower score is better
sorted_symbol_list = [tup[0] for tup in sorted_stock_dict][:self.num_fine]
# Sort the top stocks into the long_list
self.longs = [security.Symbol for security in sorted_symbol_list]
for x in self.longs:
self.myStocksList.append(x.Value)
# Log longs symbols and their score
algorithm.Log(", ".join([str(x.Symbol.Value) + ": " + str(round(securityScores[x],2)) for x in sorted_symbol_list]))
def Scores(self, securities, fundamentalFactors):
'''Assigns scores to each stock in added
Args:
securities: list of sceurities
fundamentalFactors: list of 3-tuples (lambda function, bool, float)
Returns:
Dictionary with score for each security'''
length = len(fundamentalFactors)
if length == 0:
return {}
# Initialize helper variables
scores = {} # this is the return dict object; contains security and score pairs
rankedLists = [] # this contains
# Normalize weights
weights = [factor[2] for factor in fundamentalFactors]
weights = [float(i) / sum(weights) for i in weights]
# Create sorted list for each fundamental factor passed
for factor in fundamentalFactors:
sList = sorted(securities, key = factor[0], reverse = factor[1])
rankedLists.append(sList)
# Create and save score for each symbol
for s in securities:
# Save symbol's rank for each fundamental factor
score = 0
for j in range(length):
symbol = s.Symbol.Value
index = rankedLists[j].index(s)
weight = weights[j]
product = index * weight
score += product
scores[s] = score
return scores#region imports
from AlgorithmImports import *
#endregion
from datetime import timedelta
#FundamentalFactorsAlpha
'''
v2 Improvements:
Coarse selection will pick most liquid and have good fundamentals
v3 Improvements:
Do away with Insights;
Once initial purchase is made, the equities are sold after 4% profit or loss, then they are not bought again
'''
class CygnetCVM4AlphaV3(AlphaModel):
def __init__(self, algorithm, securities):
# Initialize the various variables/helpers we'll need
#algorithm.Debug("Begin of CygnetCV5Alpha.__init__()")
self.algo = algorithm
self.myList = securities #not used at all
self.lastTradeTime = self.algo.Time - timedelta(days=31)
self.tradeFrequency = timedelta(days=30)
self.period = timedelta(days=31)
self.lastMonth = -1
self.longs = []
self.num_fine = self.algo.num_fine
# normalize quality, value, size weights
quality_weight, value_weight, size_weight = 1, 1, 2
weights = [quality_weight, value_weight, size_weight]
weights = [float(i) / sum(weights) for i in weights]
self.quality_weight = weights[0]
self.value_weight = weights[1]
self.size_weight = weights[2]
self.myStocksList = []
#self.MyInit()
#algorithm.Debug("End of CygnetCV5Alpha.__init__()")
pass
def buyOrder(symbol, numOfSymbols):
self.algo.MarketOrder(symbol, 100)
def Update(self, algorithm, data):
'''Updates this alpha model with the latest data from the algorithm.
This is called each time the algorithm receives data for subscribed securities
Args:
algorithm: The algorithm instance
data: The newa available
Returns:
New insights'''
# Return no insights if it's not time to rebalance
if algorithm.Time.month == self.lastMonth:
return []
self.lastMonth = algorithm.Time.month
# List of insights
# Insights of the form: Insight(symbol, timedelta, type, direction, magnitude, confidence, sourceModel, weight)
insights = []
# Close old positions if they aren't in longs
for security in algorithm.Portfolio.Values:
if security.Invested and security.Symbol not in self.longs:
insights.append(Insight(security.Symbol, self.algo.insightPeriod, InsightType.Price, InsightDirection.Flat, None, None, None, None))
#VR 1/17/23
#length = len(self.longs)
#for i in range(length):
# insights.append(Insight(self.longs[i], self.algo.insightPeriod, InsightType.Price, InsightDirection.Up, None, (length - i)**2, None, (length - i)**2 ))
numOfSymbols = len(self.longs)
for i in range(numOfSymbols):
self.algo.MarketOrder(self.longs[i], 100)
return insights
def OnSecuritiesChanged(self, algorithm, changes):
'''Event fired each time the we add/remove securities from the data feed
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
# Get the added securities
securities = [x for x in changes.AddedSecurities]
# Assign quality, value, size score to each stock
quality_scores = self.Scores(securities, [(lambda x: x.Fundamentals.OperationRatios.GrossMargin.Value, True, 1),
(lambda x: x.Fundamentals.OperationRatios.QuickRatio.Value, True, 2),
(lambda x: x.Fundamentals.OperationRatios.DebttoAssets.Value, False, 1)])
value_scores = self.Scores(securities, [(lambda x: x.Fundamentals.ValuationRatios.BookValuePerShare, True, 1),
(lambda x: x.Fundamentals.ValuationRatios.CashReturn, True, 2),
(lambda x: x.Fundamentals.ValuationRatios.EarningYield, True, 2)])
size_scores = self.Scores(securities, [(lambda x: x.Fundamentals.MarketCap, False, 1)])
securityScores = {}
# Assign a combined score to each stock
#for symbol, value in quality_scores.items():
for symbol in securities:
quality_rank = quality_scores[symbol]
value_rank = value_scores[symbol]
size_rank = size_scores[symbol]
securityScores[symbol] = quality_rank*self.quality_weight + value_rank*self.value_weight + size_rank*self.size_weight # VR 5/5/21: what is the right way, multiply or divide
# Sort the securities by their scores
sorted_stock_dict = sorted(securityScores.items(), key = lambda tup : tup[1], reverse = False) # lower score is better
sorted_symbol_list = [tup[0] for tup in sorted_stock_dict][:self.num_fine]
# Sort the top stocks into the long_list
self.longs = [security.Symbol for security in sorted_symbol_list]
for x in self.longs:
self.myStocksList.append(x.Value)
# Log longs symbols and their score
algorithm.Log(", ".join([str(x.Symbol.Value) + ": " + str(round(securityScores[x],2)) for x in sorted_symbol_list]))
def Scores(self, securities, fundamentalFactors):
'''Assigns scores to each stock in added
Args:
securities: list of sceurities
fundamentalFactors: list of 3-tuples (lambda function, bool, float)
Returns:
Dictionary with score for each security'''
length = len(fundamentalFactors)
if length == 0:
return {}
# Initialize helper variables
scores = {} # this is the return dict object; contains security and score pairs
rankedLists = [] # this contains
# Normalize weights
weights = [factor[2] for factor in fundamentalFactors]
weights = [float(i) / sum(weights) for i in weights]
# Create sorted list for each fundamental factor passed
for factor in fundamentalFactors:
sList = sorted(securities, key = factor[0], reverse = factor[1])
rankedLists.append(sList)
# Create and save score for each symbol
for s in securities:
# Save symbol's rank for each fundamental factor
score = 0
for j in range(length):
symbol = s.Symbol.Value
index = rankedLists[j].index(s)
weight = weights[j]
product = index * weight
score += product
scores[s] = score
return scores#region imports
from AlgorithmImports import *
#endregion
from datetime import timedelta
#FundamentalFactorsAlpha
'''
Alpha CVM9
This alpha works wiht a preselected basket and buys them immediately
After that it rebalances the portfolio per RiskMgmt module
'''
class CygnetCVM9Alpha(AlphaModel):
def __init__(self, algorithm, securities):
# Initialize the various variables/helpers we'll need
#algorithm.Debug("Begin of CygnetCV5Alpha.__init__()")
self.algo = algorithm
self.myList = securities #not used at all
self.lastTradeTime = self.algo.Time - timedelta(days=31)
self.tradeFrequency = timedelta(days=30)
self.period = timedelta(days=31)
self.tradeDone = False
#algorithm.Debug("End of CygnetCVM9Alpha.__init__()")
pass
def Update(self, algorithm, data):
# List of insights
# Insights of the form: Insight(symbol, timedelta, type, direction, magnitude, confidence, sourceModel, weight)
insights = []
if self.tradeDone == False:
# Close old positions if they aren't in longs
self.tradeDone = True
for security in algorithm.Portfolio.Values:
if security.Invested and security.Symbol not in self.myList:
insights.append(Insight(security.Symbol, timedelta(days=1), InsightType.Price, InsightDirection.Flat, None, None, None, None))
length = len(self.myList)
for i in range(length):
insights.append(Insight(self.myList[i], timedelta(days=1), InsightType.Price, InsightDirection.Up, None, 1, None, 1 ))
return insights
def OnSecuritiesChanged(self, algorithm, changes):
pass
#region imports
from AlgorithmImports import *
#endregion
def AnalyzeNan(df1):
#print NaN rows and Cols
self.algo.Log ("NaN analysis of argument dataframe")
self.algo.Log ("Number of rows with NaN entries: ")
self.algo.Log (df1.isna().sum())
self.algo.Log ("Number of data cells with NaN entries: ")
self.algo.Log (df1.isna().sum().sum())
nan_cols = [i for i in df1.columns if df1[i].isnull().all()]
self.algo.Log ("Columns which have all NaNs")
self.algo.Log (nan_cols)
nan_cols = [i for i in df1.columns if df1[i].isnull().any()]
self.algo.Log ("Columns which have one or more NaNs")
self.algo.Log (nan_cols)
def ConvertStringToList(string):
#st.text(string)
string = string.upper().replace(' ', '\t').replace(',', '\t').replace('\t\t', '\t')
#st.text(string)
mylist = list(string.split('\t'))
return mylist
def Reorder_columns(dataframe, col_name, position):
temp_col = dataframe[col_name]
dataframe = dataframe.drop(columns=[col_name])
dataframe.insert(loc=position, column=col_name, value=temp_col)
return dataframe
def NextChunk(myList, n):
#Yield successive n-sized chunks from list
for i in range(0, len(myList), n):
yield myList[i:i + n]
def AnalyzeNan2(df1):
#print NaN rows and Cols
print ("NaN analysis of argument dataframe")
print ("Shape of dataframe")
print(df1.shape)
print ("Number of rows with one or more NaN entries: ")
nan_rows = [i for i in df1.index if df1.iloc[i, :].isnull().any()]
print(len(nan_rows))
print ("Number of rows with All NaN entries: ")
nan_rows = [i for i in df1.index if df1.iloc[i, :].isnull().all()]
print(len(nan_rows))
#print (df1.isna().sum())
print ("Number of data cells with NaN entries: ")
print (df1.isna().sum().sum())
nan_cols = [i for i in df1.columns if df1[i].isnull().all()]
print ("Columns which have all NaNs")
print (nan_cols)
nan_cols = [i for i in df1.columns if df1[i].isnull().any()]
print ("Columns which have one or more NaNs")
print (nan_cols)
s = df1.isna().sum()
for i in range(len(s)):
if s[i] > 0:
print(s.index[i], s[i])
def last_date_of_month(year, month):
d1 = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
d2 = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if is_leap_year(year):
return d2[month]
else:
return d1[month]
def is_leap_year(year):
""" if year is a leap year return True
else return False """
if year % 100 == 0:
return year % 400 == 0
return year % 4 == 0
def doy(Y,M,D):
""" given year, month, day return day of year
Astronomical Algorithms, Jean Meeus, 2d ed, 1998, chap 7 """
if is_leap_year(Y):
K = 1
else:
K = 2
N = int((275 * M) / 9.0) - K * int((M + 9) / 12.0) + D - 30
return N
def ymd(Y,N):
""" given year = Y and day of year = N, return year, month, day
Astronomical Algorithms, Jean Meeus, 2d ed, 1998, chap 7 """
if is_leap_year(Y):
K = 1
else:
K = 2
M = int((9 * (K + N)) / 275.0 + 0.98)
if N < 32:
M = 1
D = N - int((275 * M) / 9.0) + K * int((M + 9) / 12.0) + 30
return Y, M, D
def RelChangePctRounded(self, p1, p2):
# p1 price 1, p2 price 2
if p1 != 0:
return round(100 * (p2 - p1) / p1, 2)
else:
return 9999.00#region imports
from AlgorithmImports import *
#endregion
class MyRiskMgmtModel(RiskManagementModel):
# Adjust the portfolio targets and return them. If no changes emit nothing.
def ManageRisk(self, algorithm, targets):
return []
# Optional: Be notified when securities change
def OnSecuritiesChanged(self, algorithm, changes):
pass#region imports
from AlgorithmImports import *
#endregion
import pandas as pd
import datetime
from Signals import doy, UpdateATRandRelChangePctValues, RSIInitialize, ComputeRSIValues, RSIUpdate, EndOfDayRSICalcs
'''
VarK4 New features
VarJ2:
Main driver: EOD Closing price
Certain parameters are read daily
Some parameters are read on Initialize only
ProfitMargin param with a default value of 0.04
IgnoreObjectStore Parameter added
VarK: Sells if equity drops by 2% from its purchase price
VarK2: Bug fixes: # this code block is to handle when a holding is sold when profitMargin or 2% loss is met and the same stock happens
to meet the end-of-day buy criterion
Improved logging
VarK3: Parameterize 2% stoploss;
Build shorting feature
VarK4: RiskMgmtStrategy 1 - Exit EOD 2 - TrailingStops 3 - Bracket (Limit and Stop orders)
VarK5: ATR used in RiskMgmt (vs. fixed 5% stoploss)
VarK6: Signals:
EOD price / EOD rel price change pct (done)
Consider 7 day or 10 or 14 day performance
RSI Signal
Which stock is the farthest from the mean or SMA20 or EMA14
VarK7: Trading fequncy (daily vs. weekly vs. monthly)
Incorporate market indexes and decisions based on the market index values;
market indexes do not work in real time; have to use ETFs as proxies SPY for SP500, ONEQ for Nasdaq composite, QQQ for Nasdaq 100 and DIA for DJIA
Change universe on a weekly or monthly basis based on the ...
Bring in news feed code
Notes: 3/1/22 thru 4/1/22 was an up market; 1/1 thru 2/1 was down market; FAAMNvG universe FB AMZN AAPL MSFT NVDA GOOG
Test case: 3/1/22 thru 4/1/22 1/1 thru 2/1 1/1 thru 4/1
Nasdaq Performce: +5.39% -9.39% -9.92%
Test case: 3/1/22 thru 4/1/22 & 1/1 thru 2/1; FAAMNvG universe; Long MeanReversion Strategey (buy the worst); Fixed time entry/exit (three cases below);
Timing 3/1/22 thru 4/1/22 1/1 thru 2/1
Exit order 10 mins before market close and Entry order 5 mins 25.70 % -4.16 %
Both Exit and Entry orders 10 mins before market closeIn three cases 27.31 % -5.86 %
Both Exit and Entry orders at MarketClose using MarketOnCloseOrder 22.85 % -5.57 %
The best returns have been with Case 2 when tested in 3/1/22 thru 4/1/22 with FAAMNvG universe; first number is for 3/1 thru 4/1 was an up market; 2nd is 1/1 thru 2/1 down market
Test case: 3/1/22 thru 4/1/22 & 1/1 thru 2/1; FAAMNvG universe; Long MeanReversion Strategey (buy the worst); Fixed time entry/exit (at EOD 10 mins, 5 mins before market close)
Signal 3/1/22 thru 4/1/22 1/1 thru 2/1
1DayRelChgPct 25.70 % -4.16 %
7DayRelChgPct 20.16 % -9.68 %
14DayRelChgPct 19.87 % -11.32 %
21DayRelChgPct 21.41 % -16.95 %
RSI (10day) 5.28 % -2.30 %
RSI (2day) 20.85 % 1.68 %
SMA(3) 22.85 % -2.09 %
SMA(7) 10.57 % -3.91 %
SMA(14) 19.49 % -6.08 %
EMA(3) 20.82 % -1.63 %
EMA(7) 11.98 % 0.15 %
EMA(14) 23.93 % -8.81 %
Test case: UsingATR for stop and limit orders; FAAMNvG universe; Long MeanReversion Strategey (buy the worst); Signal 1DayRelChgPct; Fixed Time Exit/Entry 10 and 5 mins before market close
Risk Mgmt 3/1/22 thru 4/1/22 1/1 thru 2/1
Fixed Time Exit/Entry 25.70 % -4.16 %
Trailing Stop Orders 24.59 % -11.02 %
Bracket 20.70 % -5.36 %
Test case: 2%stoploss, 2%limit for stop and limit orders; FAAMNvG universe; Long MeanReversion Strategey (buy the worst); Signal 1DayRelChgPct; Fixed Time Exit/Entry 10 and 5 mins before market close
Risk Mgmt 3/1/22 thru 4/1/22 1/1 thru 2/1
Fixed Time Exit/Entry 25.71 % -4.16 %
Trailing Stop Orders 3.68 % -5.68 %
Bracket 2.57 % 2.49 %
Test case: 5%stoploss, 5%limit for stop and limit orders; FAAMNvG universe; Long MeanReversion Strategey (buy the worst); Signal 1DayRelChgPct; Fixed Time Exit/Entry 10 and 5 mins before market close
Risk Mgmt 3/1/22 thru 4/1/22 1/1 thru 2/1
Fixed Time Exit/Entry 25.70 % -4.16 %
Trailing Stop Orders 26.23 % -8.94 %
Bracket 19.80 % -8.46 %
Best choices are 1DayRelChgPct, Fixed time exit and entry (10 mins, 5 mins)
7DayRelChgPct, 14Day, and 21Day signals produce worse results in both up and down markets
RSI(10) does a poor job of picking the right stocks in up market, but did better than all RelChgPct signals in down market
RSI(2) does a good job in down market and does ok in up market
Test case: 3/1/22 thru 4/1/22 & 1/1 thru 2/1; FAAMNvG universe; Long MeanReversion Strategey (buy the worst); Fixed time entry/exit (at EOD 10 mins, 5 mins before market close)
Signal 1/1/22 thru 4/1/22
1DayRelChgPct 3.83 % vs. Nasdaa perf -9.92%
Trading Frequency: Daily 3.83 %
EveryOtherDay 3.67 %
Weekly -17.66 %
Everymonth 4.85 %
'''
class Junk(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2022, 3, 1)
self.SetEndDate(2022, 4, 1)
self.SetCash(100000)
#self.myList = ['IBM', 'DIS', 'X']
#self.myList = ['PBW', 'COPX', 'CLOU', 'DRIV', 'FINX', 'QYLD', 'BOTZ', 'PAVE', 'LIT', 'SIL', 'SDIV', 'MLPA', 'NUSI', 'PBD']
#self.myList = ['XLB','XLC','XLY','XLP','XLE','XLF','XLV','XLI','XLRE','XLK','XLU']
# PBW COPX CLOU DRIV FINX QYLD BOTZ PAVE LIT SIL SDIV MLPA NUSI PBD
# JETS EWZ VPU SPYD CMRE BGS BHC GLD DOW NVEC XOM
self.useATR = True
self.signalName = "1DayRelChgPct" # "SMA" # "EMA" #"1DayRelChgPct" #"RSI" #
self.frequency = "EveryDay" # "EveryDay", "EveryOtherDay" "EveryWeek" "EveryMonth"
self.frequencyDayCount = {"EveryDay" : 1, "EveryOtherDay" : 2, "EveryWeek" : 7, "EveryMonth" : 30}
self.currentHoldingEntryPrice = 0.0
self.currentHoldingExitPrice = 0.0
self.priorHolding = ""
self.priorHoldingEntryPrice = 0.0
self.priorHoldingExitPrice = 0.0
self.priorHoldingTradeSize = 0
self.priorHoldingTradeReturn = 0.0
self.priorHoldingPL = 0.0
self.portfolioValueAt358pm = 0.0
self.newHolding = ""
self.switchStock = True
self.tradeSize = 0
self.cumReturn = 100.0
self.firstDay = True
self.tradeReturn = 0.0
self.stopLossOrderTicket = ""
self.limiOrderTicket = ""
self.ignoreObjectStore = True
self.myList = []
self.ReadParams()
self.ResetVariablesAtBeginOfDay()
WeekDayNames = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
self.myColumns = ['PriorDayClosingPrice', 'LatestClosingPrice', 'LatestHighPrice', 'LatestLowPrice', 'LatestVolume', \
'PurchasePrice', 'PurchasedQty', 'StopPrice', 'LimitPrice', \
"ATR", '1DayRelChgPct', "7DayRelChgPct", "14DayRelChgPct", "21DayRelChgPct", "RSIIndVal", "RSI", "EMA", "SMA"]
self.priceData = pd.DataFrame(index=self.myList, columns=self.myColumns)
self.priceData = self.priceData.fillna(0.0)
#self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
self.Debug(f"Algo Start time Year: {self.Time.year} Month: {self.Time.month} Date: {self.Time.day} Hour: {self.Time.hour} Minute: {self.Time.minute} ")
self.AlgoStartTime = datetime.datetime(self.Time.year, self.Time.month, self.Time.day, self.Time.hour, self.Time.minute)
self.AlgoStartDOY = doy(self.AlgoStartTime.year, self.AlgoStartTime.month, self.AlgoStartTime.day)
xday = self.AlgoStartTime.weekday()
self.Debug(f"Algo Start DOY: {self.AlgoStartDOY} Weekday Number: {xday} Weekday Name: {WeekDayNames[xday]} ")
xyz = self.frequencyDayCount[self.frequency]
self.AlgoLastTradeTime = self.AlgoStartTime - timedelta(days=xyz) #by setting the lastTradeTime in the past enables to trade on first day
#currDate = datetime.datetime(self.Time.year, self.Time.month, self.Time.day) - timedelta(days=2)
#daysSinceLastTrade = currDate - self.AlgoLastTradeTime
#self.Debug(f"Days Since Last Trade: {daysSinceLastTrade}")
self.SetTimeZone("America/New_York")
for tickerSymbol in self.myList:
symbol = self.AddEquity(tickerSymbol, Resolution.Minute)
symbol.SetDataNormalizationMode(DataNormalizationMode.Raw);
symbol.SlippageModel = ConstantSlippageModel(0.0)
symbol.FeeModel = ConstantFeeModel(0.0, "USD")
self.SPY = self.AddEquity("SPY", Resolution.Minute)
self.SPY.SetDataNormalizationMode(DataNormalizationMode.Raw)
self.SetBenchmark("SPY")
# schedule an event to fire every trading day for a security the
# time rule here tells it to fire 10 minutes after SPY's market open
self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.AfterMarketOpen("SPY", 10), self.EveryDayAfterMarketOpen)
self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 10), self.EndOfTradingDayExitOrder)
self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 5), self.EndOfTradingDayEntryOrder)
self.currentHolding = ""
if self.ignoreObjectStore == False:
if self.ObjectStore.ContainsKey("myCurrentHoldingSymbol"):
self.currentHolding = self.ObjectStore.Read("myCurrentHoldingSymbol")
self.currentHoldingSize = 0
if self.ignoreObjectStore == False:
if self.ObjectStore.ContainsKey("myCurrentHoldingSize"):
self.currentHoldingSize = int(self.ObjectStore.Read("myCurrentHoldingSize"))
self.Debug("VarK7-RotationStrategy" + \
"\n Risk Mgmt Strategy param (1 - EOD trade, 2 - trailing stop order 3 - bracket: " + str(self.riskMgmtStrategy) + \
"\n Trade Direction (1 long or 0 notrade or -1 short): " + str(self.tradeDirection) + \
"\n Strategy param: (1 - buy best (trend following), 2 - buy worst (mean reversion))" + str(self.strat) + \
"\n Universe: " + str(self.myList) + \
"\n CurrentHolding: " + self.currentHolding + \
"\n CurrentHoldingSize: " + str(self.currentHoldingSize) + \
"\n LiquidateInitialHolding: " + str(self.liquidateInitialHolding) + \
"\n initialHolding: " + self.initialHolding + \
"\n initialHoldingSize: " + str(self.initialHoldingSize) + \
"\n profitMargin (0.01 for 1%): " + str(self.profitMargin) + \
"\n stopLoss (0.01 for 1%): " + str(self.stopLoss) + \
"\n ignoreObjectStore: " + str(self.ignoreObjectStore) )
self.ResetVariablesAtBeginOfDay()
#self.Debug("InitialHolding params " + str(self.liquidateInitialHolding) + " " + self.initialHolding + " " + str(self.initialHoldingSize))
for tickerSymbol in self.myList:
hist = self.History(tickerSymbol, 1, Resolution.Daily)
for s in hist:
self.priceData.loc[tickerSymbol, 'PriorDayClosingPrice'] = s.Close
self.Debug(str(s.Time) + " " + tickerSymbol + " Prior day closing price: " + str(s.Close))
#self.myPriorDayClosingPrices.update({tickerSymbol : s.Close})
##RSIInitialize(self)
##self.InitATRValues()
UpdateATRandRelChangePctValues(self)
#self.Plot("Series Name", self.priceData["PriorDayClosingPrice"])
def OnData(self, data):
# figure out worse performing stock
# if the worst performing stock is the same as what is in portfolio, do nothing
# if not, liquidate and buy
# Note that not all subscribed symbols may be present on every onData call; for example if there are no trades
# also note that many OnData calls can be made within 1 minute even tho the Resolution used is Resolution.Minute
#self.Debug(str(self.Time)) first minute bar timestamp is 2020-12-31 09:31:00; time printed is the end time of the trade bar
if data.Bars == None:
return
if (self.Time.minute % 5 != 0):
return
#self.Debug(f"year: {self.Time.year} Month: {self.Time.month} Date: {self.Time.day} Hour: {self.Time.hour} Minute: {self.Time.minute} ")
#if self.Time.hour == 15 and self.Time.minute == 59: #self.Time.month == 6 and self.Time.day == 25 and
# self.Debug(str(self.Time) + " Cum return " + str(round(self.cumReturn, 2)))
##RSIUpdate(self, data)
for tickerSymbol in self.myList:
relChange = 0.0
if data.Bars.ContainsKey(tickerSymbol):
try:
symbolBar = data.Bars[tickerSymbol]
#self.myLatestClosingPrices.update({tickerSymbol : symbolBar.Close})
self.priceData.loc[tickerSymbol, 'LatestClosingPrice'] = symbolBar.Close
if symbolBar.High > self.priceData.loc[tickerSymbol, 'LatestHighPrice']:
self.priceData.loc[tickerSymbol, 'LatestHighPrice'] = symbolBar.High
if symbolBar.Low > self.priceData.loc[tickerSymbol, 'LatestLowPrice']:
self.priceData.loc[tickerSymbol, 'LatestLowPrice'] = symbolBar.Low
self.priceData.loc[tickerSymbol, 'LatestVolume'] += symbolBar.Volume
#if not self.myPriorDayClosingPrices.get(tickerSymbol) == None and not self.myLatestClosingPrices.get(tickerSymbol) == None:
currClosingPrice = self.priceData.loc[tickerSymbol, 'LatestClosingPrice']
prevClosingPrice = self.priceData.loc[tickerSymbol, 'PriorDayClosingPrice']
if prevClosingPrice == 0.0:
self.Debug(tickerSymbol + " prevClosingPrice is ZERO!")
else:
relChange = 100 * (currClosingPrice-prevClosingPrice) / prevClosingPrice
self.priceData.loc[tickerSymbol, '1DayRelChgPct'] = relChange
#self.Debug(tickerSymbol + " prevClosingPrice: " + str(round(prevClosingPrice,2)) + " latestClosingPrice: " + str(round(currClosingPrice,2)))
#prftMargin = self.profitMargin # * (1 - (self.Time.hour - 9) / 10 )
if self.riskMgmtStrategy == 1:
#Wait for end of day actions to trigger sell and buy
pass
if self.riskMgmtStrategy == 2:
if tickerSymbol == self.currentHolding:
#self.SetupOrUpdateStopOrder(tickerSymbol, cp)
cp = self.priceData.loc[tickerSymbol, 'LatestClosingPrice']
pp = self.priceData.loc[tickerSymbol, 'PurchasePrice']
sp = self.priceData.loc[tickerSymbol, 'StopPrice']
atrVal = self.priceData.loc[tickerSymbol, 'ATR']
if self.tradeDirection * (cp - pp) > 0: #setup a stopMarketOrder when trade moves favorbly
newStopPrice = round((1 - self.tradeDirection * self.stopLoss) * cp, 2) # trail by stopLoss percentage points
if self.useATR == True:
newStopPrice = round(cp - self.tradeDirection * atrVal, 2)
self.SetupOrUpdateStopOrder(tickerSymbol, newStopPrice)
if self.riskMgmtStrategy == 3:
pass
'''
if tickerSymbol == self.currentHolding:
if not math.isnan(self.priceData.loc[tickerSymbol, 'PurchasePrice']):
cp = self.priceData.loc[tickerSymbol, 'LatestClosingPrice']
pp = self.priceData.loc[tickerSymbol, 'PurchasePrice']
if self.tradeDirection == 1: #Long
if cp > (1 + self.profitMargin) * pp:
self.ExecuteExitOrder()
self.Debug("Selling " + tickerSymbol + " as " + str(round(100*self.profitMargin, 1)) + " % profit margin condition is met")
if cp < (1 - self.stopLoss) * pp:
self.ExecuteExitOrder()
self.Debug("Selling " + tickerSymbol + " as " + str(round(100*self.stopLoss, 1)) + "% loss exit condition is met")
pass
if self.tradeDirection == -1: #short
if cp < (1 - self.profitMargin) * pp:
self.ExecuteExitOrder()
self.Debug("Buying back " + tickerSymbol + " as " + str(round(100*self.profitMargin, 1)) + " % profit margin condition is met")
pass
if cp > (1 + self.stopLoss) * pp:
self.ExecuteExitOrder()
self.Debug("Buying back " + tickerSymbol + " as " + str(round(100*self.stopLoss, 1)) + "% loss exit condition is met")
pass
'''
except KeyNotFoundException: #this should not happen
self.Debug(tickerSymbol + " KeyNotFoundException caught")
def EndOfTradingDayEntryOrder(self):
if self.tradeDirection == 0:
return
dayCount = self.frequencyDayCount[self.frequency]
diff = self.Time - self.AlgoLastTradeTime
if diff < timedelta(days=dayCount):
return
#self.Log(f"EndOfTradingDayEntryOrder 5 min before close: Fired at: {self.Time}")
if self.switchStock == True:
# buy the new holding
if not self.newHolding == "":
tickerSymbol = self.newHolding
#currClosingPrice = data.Bars[self.newHolding].Close
currClosingPrice = self.priceData.loc[tickerSymbol, 'LatestClosingPrice']
if currClosingPrice == 0.0:
self.Debug(str(self.Time) + " Current price of " + str(tickerSymbol) + " is ZERO!")
return
self.tradeSize = math.floor(0.99 * self.Portfolio.TotalPortfolioValue / currClosingPrice) #0.99 multipler gives 1% for buffer for non-marginable equities when the price changes by the time IBKR receives the order
self.MarketOrder(self.newHolding, self.tradeDirection*self.tradeSize)
#do not use self.MarketOnCloseOrder(self.newHolding, self.tradeDirection*self.tradeSize)
self.Debug("Entry trade " + tickerSymbol + " " + str(self.tradeDirection*self.tradeSize) + " shares at " + str(round(currClosingPrice, 2)) )
self.AlgoLastTradeTime = datetime.datetime(self.Time.year, self.Time.month, self.Time.day, self.Time.hour, self.Time.minute-5)
self.currentHolding = self.newHolding
self.currentHoldingEntryPrice = currClosingPrice
self.priceData.loc[tickerSymbol, 'PurchasePrice'] = currClosingPrice
self.priceData.loc[tickerSymbol, 'PurchasedQty'] = self.tradeSize
# persist the current holding symbol and num of shares in ObjectStore
self.ObjectStore.Save("myCurrentHoldingSymbol", self.currentHolding)
self.ObjectStore.Save("myCurrentHoldingSize", str(self.tradeSize))
self.ObjectStore.Save("myCurrentLimitOrder", self.limiOrderTicket)
self.ObjectStore.Save("myCurrentStopLossOrder", self.stopLossOrderTicket)
if self.riskMgmtStrategy == 1:
pass
if self.riskMgmtStrategy == 2:
stopPrice = round(currClosingPrice * (1 - self.tradeDirection * self.stopLoss), 2)
if self.useATR == True:
atrVal = self.priceData.loc[tickerSymbol, 'ATR']
stopPrice = round(currClosingPrice - self.tradeDirection * atrVal, 2)
self.SetupOrUpdateStopOrder(tickerSymbol, stopPrice)
if self.riskMgmtStrategy == 3:
limitPrice = round(currClosingPrice * (1 + self.tradeDirection * self.profitMargin), 2)
stopPrice = round(currClosingPrice * (1 - self.tradeDirection * self.stopLoss), 2)
if self.useATR == True:
atrVal = self.priceData.loc[tickerSymbol, 'ATR']
limitPrice = round(currClosingPrice + self.tradeDirection * atrVal, 2)
stopPrice = round(currClosingPrice - self.tradeDirection * atrVal, 2)
self.SetupLimitOrder(tickerSymbol, limitPrice)
self.SetupOrUpdateStopOrder(tickerSymbol, stopPrice)
# reference on orders
# https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/OrderTicketDemoAlgorithm.py
def CancelOpenOrders(self):
if self.riskMgmtStrategy == 1:
return
if not self.stopLossOrderTicket == "":
response = self.stopLossOrderTicket.Cancel("Cancelled open stop order")
if response == OrderStatus.Canceled:
self.Debug("Open stoploss order cancelled successfully")
self.stopLossOrderTicket = ""
else:
self.Debug("No open stoploss orders to cancel")
if not self.limiOrderTicket == "":
response = self.limiOrderTicket.Cancel("Cancelled open limit order")
if response == OrderStatus.Canceled:
self.Debug("Open limit order cancelled successfully")
self.limiOrderTicket = ""
else:
self.Debug("No open limit orders to cancel")
def OnOrderEvent(self, orderEvent):
order = self.Transactions.GetOrderById(orderEvent.OrderId)
#self.Log("{0}: {1}: {2}".format(self.Time, order.Type, orderEvent))
self.Log("{0}: {1}".format(order.Type, orderEvent))
if orderEvent.Status == OrderStatus.Filled:
#self.stopLossOrderTicket = ""
self.Debug("Order filled successfully")
def SetupOrUpdateStopOrder(self, tickerSymbol, stopPrice):
#self.Log(f"SetupStopOrder: {self.Time}")
if not self.currentHolding == "":
qty = self.Portfolio[self.currentHolding].Quantity
if self.stopLossOrderTicket == "":
#self.DefaultOrderProperties.TimeInForce = TimeInForce.Day
#self.DefaultOrderProperties.TimeInForce = TimeInForce.GoodTilCanceled this is the default timeframe
self.stopLossOrderTicket = self.StopMarketOrder(self.currentHolding, -1 * qty, stopPrice)
self.priceData.loc[tickerSymbol, 'StopPrice'] = stopPrice
#self.Portfolio[self.currentHolding].Invested IsLong Quantity Price
else:
currentStopPrice = self.priceData.loc[tickerSymbol, 'StopPrice']
pp = self.priceData.loc[tickerSymbol, 'PurchasePrice']
# stop order for long position or short position
if (qty > 0 and ( (stopPrice - currentStopPrice) > 0.005 * pp) ) or \
(qty < 0 and ( (currentStopPrice - stopPrice) > 0.005 * pp) ): # issue updates to order only if there is at least 0.5% movement
updateOrderFields = UpdateOrderFields()
updateOrderFields.StopPrice = stopPrice
updateOrderFields.Tag = "Update #{0}".format(len(self.stopLossOrderTicket.UpdateRequests) + 1)
response = self.stopLossOrderTicket.Update(updateOrderFields)
self.Log(f"Updating stop order to {stopPrice}")
self.priceData.loc[tickerSymbol, 'StopPrice'] = stopPrice
if response == OrderStatus.Submitted:
self.Debug("Order submitted successfully")
def SetupMarketOnCloseOrder(self, tickerSymbol):
#self.Log(f"SetupMarketOnCloseOrder: {self.Time}")
if not self.currentHolding == "":
qty = self.Portfolio[self.currentHolding].Quantity
if self.stopLossOrderTicket == "":
self.stopLossOrderTicket = self.MarketOnCloseOrder(self.currentHolding, -1 * qty)
def SetupLimitOrder(self, tickerSymbol, limitPrice):
#self.Log(f"SetupLimitOrder: {self.Time}")
if not self.currentHolding == "":
qty = self.Portfolio[self.currentHolding].Quantity
if self.limiOrderTicket == "":
self.limiOrderTicket = self.LimitOrder(self.currentHolding, -1 * qty, limitPrice)
self.priceData.loc[tickerSymbol, 'LimitPrice'] = limitPrice
def Convert(string):
string = string.upper().replace(' ', '\t').replace(',', '\t').replace('\t\t', '\t')
stringList = list(string.split('\t'))
return stringList
def ReadParams(self): #called once in Initialize only
self.riskMgmtStrategy = 1
if self.GetParameter("RiskMgmtStrategy") == None:
self.riskMgmtStrategy = 2 #
else:
self.riskMgmtStrategy = int(self.GetParameter("RiskMgmtStrategy"))
''' riskMgmtStrategy
1. Exit at EOD (10 mins before close) price
2. Trailing stop order at 5% below (for long positions) or above (for short positions) purchase price
3. Bracket orders with a fixed profit margin limit order and a fixed stop loss exit orders
'''
self.strat = 2 # buy worst performing
if self.GetParameter("Strategy") == None:
self.strat = 2 # 1- buy the best performing, 2 means buy the worst perforing
else:
self.strat = int(self.GetParameter("Strategy")) # (1 to buy best or 2 to buy worst)
universe = "AA BCRX CLF CNR DXC ET M NTLA SKY THC ANF BKE EXP FL FLGT LOGI LPX MOH MRNA OAS OMI QDEL SIG SONO WFG WSM"
universe = self.GetParameter("Universe")
if universe == None:
#self.myList = ['PBW', 'COPX', 'CLOU', 'DRIV', 'FINX', 'QYLD', 'BOTZ', 'PAVE', 'LIT', 'SIL', 'SDIV', 'MLPA', 'NUSI', 'PBD']
self.myList = ['AA', 'BCRX', 'CAR', 'CLF', 'CNR', 'DXC', 'ET', 'M', 'NTLA', 'SKY', 'THC']
else:
universe = universe.upper().replace(' ', '\t').replace(',', '\t').replace('\t\t', '\t')
self.myList = list(universe.split('\t'))
self.ignoreObjectStore = True
param = self.GetParameter("IgnoreObjectStore")
if not param == None:
if param == "Yes":
self.ignoreObjectStore = True
else:
self.ignoreObjectStore = False
#default values
self.liquidateInitialHolding = False
self.initialHolding = ""
self.initialHoldingSize = 0
param = self.GetParameter("LiquidateInitialHolding")
if param == "Yes":
self.liquidateInitialHolding = True
param = self.GetParameter("InitialHolding")
if not param == None:
self.initialHolding = param
param = self.GetParameter("InitialHoldingSize")
if not param == None:
self.initialHoldingSize = int(param)
self.profitMargin = 0.04
param = self.GetParameter("ProfitMargin")
if not param == None:
self.profitMargin = float(param)
self.stopLoss = 0.04
param = self.GetParameter("StopLoss")
if not param == None:
self.stopLoss = float(param)
self.tradeDirection = 1 # possible values are 1 (long), 0 (no trade), -1 (short)
param = self.GetParameter("TradeDirection")
if not param == None:
td = int(param)
if td == 1 or td == 0 or td == -1:
self.tradeDirection = td
else:
self.tradeDirection = 1
def ResetVariablesAtBeginOfDay(self):
#log information for day that just ended
#self.Debug(str(self.Time) + " Begin of day") #output looks like this 2021-01-04 09:31:00 2020-12-31 23:58:00 Begin of day; it looks like it is called with the first bar of the following day
'''
self.Debug(str(self.Time) + "\t" + str(self.switchStock) + "\t" + self.priorHolding + "\t" + str(self.priorHoldingTradeSize) \
+ "\t" + str(round(self.priorHoldingEntryPrice,4)) + "\t" + str(round(self.priorHoldingExitPrice,2)) + "\t" + \
str(round(self.priorHoldingPL,2)) + "\t"+ str(round(self.priorHoldingTradeReturn,4)) + "\t"+ str(round(self.cumReturn,4)) \
+ "\t" + self.currentHolding + "\t" + str(self.tradeSize) + "\t" + str(round(self.currentHoldingEntryPrice,4)) \
+ "\t" + str(round(self.tradeSize*self.currentHoldingEntryPrice,2)) + "\t" + str(round(self.portfolioValueAt358pm,2)))
'''
# reset variables at the end of the day
self.bestStock = ""
self.worstStock = ""
self.lowestRelChange = 999
self.highestRelChange = -999
#self.Debug(str(self.Time) + " Variables are reset")
def EveryDayAfterMarketOpen(self):
#self.Log(f"EveryDayAfterMarketOpen.SPY 10 min after open: Fired at: {self.Time}")
self.ResetVariablesAtBeginOfDay()
##ComputeRSIValues(self)
# self.ReadParams2()
# this update occurs once a day in the begining
for tickerSymbol in self.myList:
hist = self.History(tickerSymbol, 1, Resolution.Daily)
for s in hist:
#self.Debug(str(s.Time) + " " + tickerSymbol + " Prior day closing price: " + str(s.Close))
#self.myPriorDayClosingPrices.update({tickerSymbol : s.Close})
self.priceData.loc[tickerSymbol, 'PriorDayClosingPrice'] = s.Close
#ComputeRSIValues(self)
##self.InitATRValues()
UpdateATRandRelChangePctValues(self)
def InitATRValues(self):
for tickerSymbol in self.myList:
self.atr = self.ATR(tickerSymbol, 14, MovingAverageType.Simple, Resolution.Daily)
history = self.History([tickerSymbol], 20, Resolution.Daily)
for bar in history.itertuples():
#creating a trade bar from rows of history dataframe
tradebar = TradeBar(bar.Index[1], tickerSymbol, bar.open, bar.high, bar.low, bar.close, bar.volume)
self.atr.Update(tradebar)
self.priceData.loc[tickerSymbol, 'ATR'] = self.atr.Current.Value
cp = self.priceData.loc[tickerSymbol, 'PriorDayClosingPrice']
atr = self.priceData.loc[tickerSymbol, 'ATR']
self.Debug(f"{tickerSymbol} Prior day closing price: {cp} ATR: {round(atr,2)} ATR %: {round(100 * atr / cp, 2)}")
def ExecuteExitOrder(self):
dayCount = self.frequencyDayCount[self.frequency]
diff = self.Time - self.AlgoLastTradeTime
if diff < timedelta(days=dayCount):
return
self.CancelOpenOrders() # first cancel any open stop orders
# first sell curernt holding
if not self.currentHolding == "":
self.currentHoldingExitPrice = self.priceData.loc[self.currentHolding, 'LatestClosingPrice']
if self.Portfolio.ContainsKey(self.currentHolding):
qty = self.Portfolio[self.currentHolding].Quantity #Quantity is positive if long position, negative if short
#if self.Portfolio[self.currentHolding].IsLong:
if qty != 0:
self.MarketOrder(self.currentHolding, -1*qty)
#do not use self.MarketOnCloseOrder(self.currentHolding, -1*qty)
self.Debug("Exit trade " + str(self.currentHolding) + " Qty: " + str(self.tradeDirection * qty) + " shares at " + str(round(self.currentHoldingExitPrice, 2)) )
#self.MarketOrder(self.currentHolding, -1*self.tradeDirection*self.tradeSize)
self.tradeReturn = self.tradeDirection * (self.currentHoldingExitPrice - self.currentHoldingEntryPrice) / self.currentHoldingEntryPrice
self.priorHolding = self.currentHolding
self.priorHoldingTradeSize = self.tradeSize
self.priorHoldingEntryPrice = self.currentHoldingEntryPrice
self.priorHoldingExitPrice = self.currentHoldingExitPrice
self.priorHoldingPL = self.tradeDirection * self.priorHoldingTradeSize * (self.priorHoldingExitPrice - self.priorHoldingEntryPrice)
self.priorHoldingTradeReturn = self.tradeReturn
self.cumReturn = self.cumReturn * (1 + self.tradeReturn)
#self.Debug(str(self.Time) + " Liquidating " + str(self.tradeSize) + " shares of " + self.currentHolding + " Trade return: " + str(round(self.tradeReturn, 4)) + " Cum return " + str(round(self.cumReturn, 2)))
self.portfolioValueAt358pm = self.Portfolio.TotalPortfolioValue
self.currentHolding = ""
self.ObjectStore.Save("myCurrentHoldingSymbol", "")
self.ObjectStore.Save("myCurrentHoldingSize", str(0))
self.ObjectStore.Save("myCurrentLimitOrder", "")
self.ObjectStore.Save("myCurrentStopLossOrder", "")
def EndOfTradingDayExitOrder(self):
#self.Log(f"EndOfTradingDayExitOrder.SPY 10 min before close: Fired at: {self.Time}")
##EndOfDayRSICalcs(self)
UpdateATRandRelChangePctValues(self)
bestStock = self.priceData.loc[:, self.signalName].idxmax()
worstStock = self.priceData.loc[:, self.signalName].idxmin()
highestRelChange = self.priceData.loc[bestStock, self.signalName]
lowestRelChange = self.priceData.loc[worstStock, self.signalName]
#self.Debug("Worst: " + worstStock + " " + str(round(lowestRelChange, 2)) + "% Best: " + bestStock + " " + str(round(highestRelChange, 2)) + "% " + str(self.liquidateInitialHolding) + " " + self.self.initialHolding + " " + str(self.initialHoldingSize))
self.Debug("Signal: " + self.signalName + " Worst: " + worstStock + " " + str(round(lowestRelChange, 2)) + "% Best: " + bestStock + " " + str(round(highestRelChange, 2)) + "%")
if self.strat == 1:
self.newHolding = bestStock
relChange = highestRelChange
else:
self.newHolding = worstStock
relChange = lowestRelChange
if self.newHolding == self.currentHolding:
self.switchStock = False
else:
self.switchStock = True
#self.Debug("self.switchStock = " + str(self.switchStock) + " Curr holding " + self.currentHolding + " New holding " + self.newHolding)
if self.liquidateInitialHolding == True:
self.liquidateInitialHolding = False # this block of code shoudl be executed only once
#self.Liquidate(self.initialHolding)
if self.Securities.ContainsKey(self.initialHolding):
self.MarketOrder(self.initialHolding, -1*self.initialHoldingSize)
# this code block is to handle when a holding is sold when limit or stoploss order is executed and the same stock happens to meet the end-of-day buy criterion
if self.switchStock == False:
if self.Portfolio.ContainsKey(self.currentHolding) == False: # this means the portfolio is empty
self.switchStock = True # this flag causes the buy order to go thru in EndOfTradingDayEntryOrder() call
self.Debug(self.currentHolding + " Holding is sold when profitMargin or 2% loss exit is met and the same stock happens to meet the end-of-day buy criterion")
if self.switchStock == True:
self.ExecuteExitOrder()
def OnEndOfAlgorithm(self):
#self.Debug(str(self.Time) + " OnEndOfAlgorithm") # output looks like 2021-06-28 16:00:00 2021-06-28 16:00:00 OnEndOfAlgorithm
self.Debug(str(self.Time) + " Strategy param: " + str(self.strat) + " Cum return (%): " + str(round(self.cumReturn-100, 2)) + " Portfolio: " + str(self.myList))
pass#region imports
from AlgorithmImports import *
#endregion
import pandas as pd
import datetime
class CygnetSignal():
def __init__(self, algorithm, securities):
# Initialize the various variables/helpers we'll need
self.currentHoldingIndex = -1
myColumns = ['PriorDayClosingPrice', 'LatestClosingPrice', 'LatestHighPrice', 'LatestLowPrice', 'LatestVolume', \
'PurchasePrice', 'PurchasedQty', 'StopPrice', 'LimitPrice', \
"ATR", '1DayRelChgPct', "7DayRelChgPct", "14DayRelChgPct", "21DayRelChgPct", "RSIIndVal", "RSI", "EMA", "SMA"]
self.myList = securities
self.priceData = pd.DataFrame(index=self.myList, columns=myColumns)
self.priceData = self.priceData.fillna(0.0)
self.algo = algorithm
#self.algo.Debug(f"CygnetSignal creation {self.myList}")
self.newHolding = ""
self.currentHolding = ""
self.histData = {}
self.ComputeSignalValues()
dummyvar = 1.0
pass
def RelChangePctRounded(self, p1, p2):
# p1 price 1, p2 price 2
if p1 != 0:
return round(100 * (p2 - p1) / p1, 2)
else:
return 9999.00
def UpdateWithLatestMarketDataFeed(self, algo, data): # update with the latest closing price
if(algo.Time.minute % 5 != 0): # update only every 5 minutes
return
#self.algo.Debug(f"In CygnetSignal.UpdateWithLatestMarketDataFeed method {self.algo.Time}")
for tickerSymbol in self.myList:
if data.Bars.ContainsKey(tickerSymbol):
symbolBar = data.Bars[tickerSymbol]
self.priceData.loc[tickerSymbol, 'LatestClosingPrice'] = symbolBar.Close
self.priceData.loc[tickerSymbol, 'LatestVolume'] += symbolBar.Volume
# these are not accurate; fix the code later
if symbolBar.High > self.priceData.loc[tickerSymbol, 'LatestHighPrice']:
self.priceData.loc[tickerSymbol, 'LatestHighPrice'] = symbolBar.High
if symbolBar.Low > self.priceData.loc[tickerSymbol, 'LatestLowPrice']:
self.priceData.loc[tickerSymbol, 'LatestLowPrice'] = symbolBar.Low
def ComputeSignalValues(self):
for tickerSymbol in self.myList:
hist = self.algo.History([tickerSymbol], 22, Resolution.Daily)["close"].unstack(level=0)
hist.columns = ["close"] #rename colname to "close" from the {security symbol}
#self.algo.Debug(hist.head())
# Update RelChgPct values
cp = self.priceData.loc[tickerSymbol, 'LatestClosingPrice']
if cp == 0.0:
cp = 1.0
self.priceData.loc[tickerSymbol, '1DayRelChgPct'] = self.RelChangePctRounded (hist.close[-1], cp)
self.priceData.loc[tickerSymbol, '7DayRelChgPct'] = self.RelChangePctRounded (hist.close[-7], cp)
self.priceData.loc[tickerSymbol, '14DayRelChgPct'] = self.RelChangePctRounded (hist.close[-14], cp)
self.priceData.loc[tickerSymbol, '21DayRelChgPct'] = self.RelChangePctRounded (hist.close[-21], cp)
#update ATR values
''' this itertuples() needs to be looked at; VR 2/23/23
atr = self.algo.ATR(tickerSymbol, 14, MovingAverageType.Simple, Resolution.Daily)
for bar in hist.itertuples():
tradebar = TradeBar(bar.Index[1], tickerSymbol, bar.open, bar.high, bar.low, bar.close, bar.volume)
atr.Update(tradebar)
self.priceData.loc[tickerSymbol, 'ATR'] = atr.Current.Value
'''
#cp = self.pric
#atrVal = self.priceData.loc[tickerSymbol, 'ATR']
#self.Debug(f"{tickerSymbol} Latest closing price: {cp} ATR: {round(atrVal,2)} ATR %: {round(100 * atrVal / cp, 2)}")
#update RSI values
'''
cp = self.priceData.loc[tickerSymbol, 'LatestClosingPrice']
rsi = self.algo.RSI(tickerSymbol, 10, MovingAverageType.Simple, Resolution.Daily)
for time, row in hist.loc[tickerSymbol].iterrows():
rsi.Update(time, row["close"])
rsi.Update(datetime.datetime.now(), cp)
self.priceData.loc[tickerSymbol, 'RSI'] = rsi.Current.Value
#update SMA values
cp = self.priceData.loc[tickerSymbol, 'LatestClosingPrice']
sma = self.algo.SMA(tickerSymbol, 14, Resolution.Daily)
for time, row in hist.loc[tickerSymbol].iterrows():
sma.Update(time, row["close"])
sma.Update(datetime.datetime.now(), cp)
self.priceData.loc[tickerSymbol, 'SMA'] = round(cp / sma.Current.Value, 2)
#update EMA values EMA does not work correclty
cp = self.priceData.loc[tickerSymbol, 'LatestClosingPrice']
ema = self.algo.EMA(tickerSymbol, 14, Resolution.Daily)
for time, row in hist.loc[tickerSymbol].iterrows():
ema.Update(time, row["close"])
ema.Update(datetime.datetime.now(), cp)
self.priceData.loc[tickerSymbol, 'EMA'] = round(cp / ema.Current.Value, 2)
'''
pass
def GetEquityToTrade(self, signalName, strat):
#UpdateATRandRelChangePctValues(self)
bestStock = self.priceData.loc[:, signalName].idxmax()
worstStock = self.priceData.loc[:, signalName].idxmin()
highestSignalValue = self.priceData.loc[bestStock, signalName]
lowestSignalValue = self.priceData.loc[worstStock, signalName]
self.algo.Debug("Signal: " + signalName + " Worst: " + worstStock + " " + str(round(lowestSignalValue, 2)) + \
"% Best: " + bestStock + " " + str(round(highestSignalValue, 2)) + "%")
if strat == 1:
return bestStock, highestSignalValue
if strat == 2:
return worstStock, lowestSignalValue
def is_leap_year(year):
""" if year is a leap year return True
else return False """
if year % 100 == 0:
return year % 400 == 0
return year % 4 == 0
def doy(Y,M,D):
""" given year, month, day return day of year
Astronomical Algorithms, Jean Meeus, 2d ed, 1998, chap 7 """
if is_leap_year(Y):
K = 1
else:
K = 2
N = int((275 * M) / 9.0) - K * int((M + 9) / 12.0) + D - 30
return N
def ymd(Y,N):
""" given year = Y and day of year = N, return year, month, day
Astronomical Algorithms, Jean Meeus, 2d ed, 1998, chap 7 """
if is_leap_year(Y):
K = 1
else:
K = 2
M = int((9 * (K + N)) / 275.0 + 0.98)
if N < 32:
M = 1
D = N - int((275 * M) / 9.0) + K * int((M + 9) / 12.0) + 30
return Y, M, D
from AlgorithmImports import *
from Alphas.EmaCrossAlphaModel import EmaCrossAlphaModel
from Alphas.HistoricalReturnsAlphaModel import HistoricalReturnsAlphaModel
from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel
from Risk.MaximumDrawdownPercentPerSecurity import MaximumDrawdownPercentPerSecurity
from Risk.TrailingStopRiskManagementModel import TrailingStopRiskManagementModel
from Risk.MaximumUnrealizedProfitPercentPerSecurity import MaximumUnrealizedProfitPercentPerSecurity
from Selection.QC500UniverseSelectionModel import QC500UniverseSelectionModel
from io import StringIO
from CygUtils import ConvertStringToList
from CygCM1RotationAlpha import CygnetCM1RotationAlpha
from CygCM2MomentumAlpha import CygnetCM2MomentumAlpha
from CygCM3HQMAlpha import CygnetCM3HQMAlpha
from CygCM3HQMAlpha2 import CygnetCM3HQMAlpha2
from CygCVM4AlphaV2 import CygnetCVM4AlphaV2
from CygCVM4AlphaV3 import CygnetCVM4AlphaV3
from CygCV5Alpha import CygnetCV5Alpha
from CygCM7Alpha import CygnetCM7Alpha
from CygCM7Alpha2 import CygnetCM7Alpha2
from CygCV8PiotroskiFScoreAlpha import CygnetCV8PiotroskiFscoreAlpha, PiotroskiFScore
from CygCVM9Alpha import CygnetCVM9Alpha
from CygCM10HighestLoserAlpha import CygnetHighestLoserAlpha
from CygCM12Alpha import CygnetCM12Alpha
from CygCM12Alpha2 import CygnetCM12Alpha2
from CygCM12Alpha3 import CygnetCM12Alpha3
'''
Note on onData() call backs (ad History data) and Resolution
if Resoltion.Daily is used, then a call will come at
2023-03-31 00:00:00 SPY Closing price: 403.7
2023-04-01 00:00:00 SPY Closing price: 409.39
2023-04-04 00:00:00 SPY Closing price: 410.95
It has a date of 3/31/23 0:0:0 and the closing price is that of 3/30
However, if the resolution is Minute, then it has the right date and time. The first bar starts with 9:31 and ends in 16:00
2023-03-31 15:59:00 SPY Closing price: 409.64
2023-03-31 16:00:00 SPY Closing price: 409.39
make sure you add self.SetTimeZone("America/New_York") to get the time in ET
With startdate specified as 2023-1-1 and enddate as 2023-4-12, here are the callbacks; Jan 1 was Sunday, no trading, Jan 2 market was closed no trading
market opened on Jan 3 @ 930 am. However, the first callback is with a date of Jan 4 with the closing price on Jan 3. Jan 16 was MLK day and markets were
closed. Jan 17 market reopned. We see a bar with Jan 18 date with Jan 17 closing price.
2023-01-01 00:00:00 Launching analysis for 3b42fcd335fe2075a613be2696c54bc6 with LEAN Engine v2.5.0.0.15384
2023-01-04 00:00:00 Wednesday SPY Closing price: 379.37
2023-01-05 00:00:00 Thursday SPY Closing price: 382.30
2023-01-06 00:00:00 Friday SPY Closing price: 377.94
2023-01-07 00:00:00 Saturday SPY Closing price: 386.60
2023-01-10 00:00:00 Tuesday SPY Closing price: 386.39
2023-01-11 00:00:00 Wednesday SPY Closing price: 389.09
2023-01-12 00:00:00 Thursday SPY Closing price: 394.02
2023-01-13 00:00:00 Friday SPY Closing price: 395.45
2023-01-14 00:00:00 Saturday SPY Closing price: 396.98
2023-01-18 00:00:00 Wednesday SPY Closing price: 396.25
2023-01-19 00:00:00 Thursday SPY Closing price: 390.00
2023-01-20 00:00:00 Friday SPY Closing price: 387.16
2023-01-21 00:00:00 Saturday SPY Closing price: 394.38
2023-01-24 00:00:00 Tuesday SPY Closing price: 399.11
2023-01-10 00:00:00 Tuesday SPY Closing price: 386.39
However, if Update() method is employed to get realtime data using self.AddUniverseSelection or self.AddUniverse functions:
the Update() function is called differently. It is called on Mon / Tues / Wed / Thu / Fri / Sat. See
--- End of notes on OnData() and Update() callbacks
Alphas to developed:
2. 14 day EMA with Heiken Ashi candles
12. Piotrioski's FScore
Semi-developed:
8. Mean reversion strat based KMeans ML algo identified clusters
9. DecisionTree/ LSTM prediction model
11. Hurst exponent
Features already developed:
1. Non-halal exclusion list and apply it to all strats
Features to develop:
1. Develop shorting feature in Alphas 2 thru 8 ie. the use of self.tradeDirection = -1 param in all the Alphas
2. More precise lookbacks in CM3 and CM7 alphas (21, 63, 126 days vs. 1m, 3m, 6m lookbacks)
Alphas to develop:
1. Larry Connors 7-5; buy after 7 consecuritve down days and sell after holding for 5 days
2. 14 day EMA with Heiken Ashi candles
3. 200 SMA long term trading - this strategy is for really long cycle investing; buy / sell when SMA200 crosses over
4. Dual momentum based on monthly performnace of two different asset classes (SPY vs. GLD vs. Treasuries)
5. Go long => 21 EMA and 50 EMA sloping up, Stochastic fast line crossing the slow line from the bottom, and both the lines are at 20 or below
Go short => 21 EMA and 50 EMA sloping down, Stochastic fast line crossing the slow line from the top, and both the lines are at 80 or higher
6. SuperTrend
7. Flat trend for 1 year and uptrend in the last one month
8. Mean reversion strat based KMeans ML algo identified clusters
9. DecisionTree / LSTM prediction model
10. ML Algo picking the strats to use based on Strats performance vs. economic conditions / indicators
11. Hurst exponent
12. Piotrioski's F Score
13. Altman Z score
14. Benish M score
15. Penny stocks with p/s ratios, high short interetst ration, sales growth
16. (StockRover scores) Good value, quality, growth, sentiment, piotroski f score, and altman z scores, high margin of safety (fair value being higher the current price)
Seen on Quant-Investing site, what are these?
Magic Formula
ERP5
Quality High Dividend
Tiny Titans
Piotroski F-Score
Net Net
Value Composite
Shareholder Yield
CM1 Strategy Parameters:
: ATR used in RiskMgmt (vs. fixed 5% stoploss)
: Signals:
EOD price / EOD rel price change pct (done)
Consider 7 day or 10 or 14 day performance
RSI Signal
Which stock is the farthest from the mean or SMA20 or EMA14
: Trading fequncy (daily vs. weekly vs. monthly)
CM1 Future enhancements:
Incorporate market indexes and decisions based on the market index values;
market indexes do not work in real time; have to use ETFs as proxies SPY for SP500, ONEQ for Nasdaq composite, QQQ for Nasdaq 100 and DIA for DJIA
Change universe on a weekly or monthly basis based on the ...
Bring in news feed code
'''
class CygnetStrategy(QCAlgorithm):
def Initialize(self):
'''
periodNumber = int(self.GetParameter("PeriodNumber"))
startYear = 2021
startMonth = periodNumber * 3
if (startMonth > 12):
q, r = divmod(startMonth, 12)
startYear = startYear + q
startMonth = r
self.SetStartDate(startYear, startMonth, 1)
endYear = startYear
endMonth = startMonth + 3
if (endMonth > 12):
q, r = divmod(endMonth, 12)
endYear = startYear + 1
endMonth = r
self.SetEndDate(endYear, endMonth, 1)'''
year = int(self.GetParameter("Year"))
self.SetStartDate(year, 3, 17)
self.SetEndDate(year, 3, 31)
#self.SetStartDate(2017, 1, 1) # Set Start Date
#self.SetEndDate(2018, 1, 1) # Set End Date
self.SetCash(100000) # Set Strategy Cash
self.myList = []
self.riskMgmt = 1 # nullriskmgmt
self.alphaStrategy = "CM1"
self.universe = "SP500"
self.stratDesc = "CM1-DailyRotation"
self.insightPeriod = timedelta(days=22)
self.myExclList = []
self.myCoarseList = []
self.myFineList = []
self.tradeDirection = 1
self.mode = "Backtest" #Backtest or Live
strats = ["", "CM1", "CM2", "CM3", "CVM4", "CV5", "CHR6", "CM7", "CV8", "CVM9", "CM10", "CM11", "CM12"]
stratDesriptions = ["", "CM1-DailyRotation", "CM2-CygnetMomentum", "CM3-HQM Perf", \
"CVM4-CygnetValueMomentum(WR11)", "CV5-CygnetValuationRatios", "CHR6-QuantConnect HistoricalReturns alpha", \
"CM7-QM Perf", "CV8-CygentPiotroskiFscore", "CVM9-Modified CVM4", \
"CM10-HighestLoser", "CM11", "CM12-WorstQtrBestMonth", "pl"]
riskMgmts = ["", "NullRiskMgmt", "TrailingStop5%", "Maxdrawdown4%", "MaxUnrlzd4%", "Bracketed4%Gain6%Loss"]
universes = ["", "SP500", "HalalSP500", "SP500SymbolsAddedPriorTo2017", "ManualList"]
tradeDirections = ["", "Long", "Short"] #
self.SetTimeZone("America/New_York")
if self.GetParameter("Strategy") != None:
self.alphaStrategy = strats[int(self.GetParameter("Strategy"))]
self.stratDesc = stratDesriptions[int(self.GetParameter("Strategy"))]
if self.GetParameter("RiskMgmt") != None:
self.riskMgmt = riskMgmts[int(self.GetParameter("RiskMgmt"))]
if self.GetParameter("Universe") != None:
self.universe = universes[int(self.GetParameter("Universe"))]
if self.GetParameter("InsightPeriod") != None:
x = int(self.GetParameter("InsightPeriod"))
self.insightPeriod = timedelta(days=x)
if self.GetParameter("TradeDirection") != None:
self.tradeDirection = int(self.GetParameter("TradeDirection"))
if self.GetParameter("Mode") != None:
self.mode = self.GetParameter("Mode")
self.BuildExclusionSymbolsList() # populates self.myExclList
self.Debug(f"Strategy {self.stratDesc} Risk Mgmt {self.riskMgmt} Universe {self.universe} Start date {str(self.SetStartDate)} end date {str(self.SetEndDate)}")
#self.alphaStrategy = "CV5" #CM1 CygnetRotationAlpha, CM2 CygnetMomentumAlpha, CM3 CygnetHQMAlpha, CVM4-CygnetValueMomentum(WR11), CV5 CygnetCV5Alpha
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
self.SetBenchmark("SPY")
seeder = FuncSecuritySeeder(self.GetLastKnownPrices)
self.SetSecurityInitializer(lambda security: seeder.SeedSecurity(security))
# Universe selection
if self.alphaStrategy == "CM1":
self.signalName = "1DayRelChgPct"
self.strat = 1 #1 best performing, 2 worst performing
#self.tradeDirection = 1
self.BuildSymbolsList("ManualList") # SP500 HalalSP500 SP500SymbolsAddedPriorTo2017 ManualList
#self.SubscribeForData(Resolution.Minute)
self.UniverseSettings.Resolution = Resolution.Minute
symbols = [Symbol.Create(mySymbol, SecurityType.Equity, Market.USA) for mySymbol in self.myList]
self.AddUniverseSelection(ManualUniverseSelectionModel(symbols))
self.AddAlpha(CygnetRotationAlpha(self, self.myList))
if self.alphaStrategy == "CM10":
self.signalName = "1DayRelChgPct"
self.strat = 2 #1 best performing, 2 worst performing
self.tradeDirection = 1
self.BuildSymbolsList("SP500") # SP500 HalalSP500 SP500SymbolsAddedPriorTo2017 ManualList
#self.SubscribeForData(Resolution.Minute)
self.UniverseSettings.Resolution = Resolution.Minute
symbols = [Symbol.Create(mySymbol, SecurityType.Equity, Market.USA) for mySymbol in self.myList]
self.AddUniverseSelection(ManualUniverseSelectionModel(symbols))
self.AddAlpha(CygnetHighestLoserAlpha(self, self.myList))
if self.alphaStrategy == "CM2":
self.BuildSymbolsList(self.universe) # SP500 HalalSP500 SP500SymbolsAddedPriorTo2017 ManualList
#self.SubscribeForData(Resolution.Daily)
self.UniverseSettings.Resolution = Resolution.Daily
symbols = [Symbol.Create(mySymbol, SecurityType.Equity, Market.USA) for mySymbol in self.myList]
self.AddUniverseSelection(ManualUniverseSelectionModel(symbols))
self.AddAlpha(CygnetMomentumAlpha(self, self.myList))
if self.alphaStrategy == "CM3":
self.BuildSymbolsList(self.universe) # SP500 HalalSP500 SP500SymbolsAddedPriorTo2017 ManualList
#self.SubscribeForData(Resolution.Daily)
self.UniverseSettings.Resolution = Resolution.Daily
symbols = [Symbol.Create(mySymbol, SecurityType.Equity, Market.USA) for mySymbol in self.myList]
self.AddUniverseSelection(ManualUniverseSelectionModel(symbols))
self.AddAlpha(CygnetHQMAlpha2(self, self.myList)) #vr 4/1/23
if self.alphaStrategy == "CVM4":
self.lastMonth = -1
self.num_coarse = 200
self.num_fine = 20
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CVM4CoarseSelectionFunction, self.CVM4FineSelectionFunction)
self.BuildSymbolsList(self.universe) # SP500 HalalSP500 SP500SymbolsAddedPriorTo2017 ManualList
self.cvm4Alpha = CygnetCVM4AlphaV3(self, self.myList) #vr 1/17/23
self.AddAlpha(self.cvm4Alpha)
if self.alphaStrategy == "CV5":
self.lastMonth = -1
self.num_coarse = 200
self.num_fine = 20
self.fundamentalData = None #Dataframe
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CV5CoarseSelectionFunction, self.CV5FineSelectionFunction)
self.BuildSymbolsList(self.universe) # SP500 HalalSP500 SP500SymbolsAddedPriorTo2017 ManualList
self.AddAlpha(CygnetCV5Alpha(self, self.myList))
if self.alphaStrategy == "CHR6":
self.UniverseSettings.Resolution = Resolution.Daily
#self.SetUniverseSelection(QC500UniverseSelectionModel())
self.BuildSymbolsList("SP500") # SP500 HalalSP500 SP500SymbolsAddedPriorTo2017 ManualList
symbols = [Symbol.Create(mySymbol, SecurityType.Equity, Market.USA) for mySymbol in self.myList]
self.AddUniverseSelection(ManualUniverseSelectionModel(symbols))
#self.SubscribeForData(Resolution.Daily)
self.AddAlpha(HistoricalReturnsAlphaModel(14, Resolution.Daily))
if self.alphaStrategy == "CM7":
self.BuildSymbolsList(self.universe) # SP500 HalalSP500 SP500SymbolsAddedPriorTo2017 ManualList
#self.SubscribeForData(Resolution.Daily)
self.UniverseSettings.Resolution = Resolution.Daily
symbols = [Symbol.Create(mySymbol, SecurityType.Equity, Market.USA) for mySymbol in self.myList]
self.AddUniverseSelection(ManualUniverseSelectionModel(symbols))
self.AddAlpha(CygnetCM7Alpha2(self, self.myList))
if self.alphaStrategy == "CV8":
self.lastMonth = -1
self.num_coarse = 500
self.num_fine = 20
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CV8CoarseSelectionFunction, self.CV8FineSelectionFunction)
self.BuildSymbolsList(self.universe) # SP500 HalalSP500 SP500SymbolsAddedPriorTo2017 ManualList
self.AddAlpha(CygnetPiotroskiFscoreAlpha(self, self.myList))
if self.alphaStrategy == "CVM9":
#manual universe
self.BuildSymbolsList("CVM9-ManualList") # SP500 HalalSP500 SP500SymbolsAddedPriorTo2017 ManualList
self.UniverseSettings.Resolution = Resolution.Daily
symbols = [Symbol.Create(mySymbol, SecurityType.Equity, Market.USA) for mySymbol in self.myList]
self.AddUniverseSelection(ManualUniverseSelectionModel(symbols))
self.AddAlpha(CygnetCVM9Alpha(self, self.myList))
if self.alphaStrategy == "CM12":
self.BuildSymbolsList(self.universe) # SP500 HalalSP500 SP500SymbolsAddedPriorTo2017 ManualList
self.SubscribeForData(Resolution.Daily)
self.UniverseSettings.Resolution = Resolution.Hour
symbols = [Symbol.Create(mySymbol, SecurityType.Equity, Market.USA) for mySymbol in self.myList]
#self.AddUniverse(self.Universe.ETF("SPY", Market.USA, self.UniverseSettings)) not working
self.AddUniverseSelection(ManualUniverseSelectionModel(symbols))
self.AddAlpha(CygnetCM12Alpha3(self, self.myList))
#self.SetUniverseSelection(QC500UniverseSelectionModel())
#manual universe
#self.UniverseSettings.Resolution = Resolution.Daily
#symbols = [Symbol.Create(mySymbol, SecurityType.Equity, Market.USA) for mySymbol in self.myList]
#self.AddUniverseSelection(ManualUniverseSelectionModel(symbols))
#self.AddAlpha(EmaCrossAlphaModel(50, 200, Resolution.Daily))
#self.AddAlpha(HistoricalReturnsAlphaModel(14, Resolution.Daily))
self.SetExecution(ImmediateExecutionModel())
self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
#self.SetPortfolioConstruction(BlackLittermanOptimizationPortfolioConstructionModel())
if self.riskMgmt == "NullRiskMgmt": self.SetRiskManagement(NullRiskManagementModel())
if self.riskMgmt == "TrailingStop5%": self.SetRiskManagement(TrailingStopRiskManagementModel(0.05))
if self.riskMgmt == "Maxdrawdown4%": self.SetRiskManagement(MaximumDrawdownPercentPerSecurity(0.04)) # 1.0 = 100% effectively, EOD exit
if self.riskMgmt == "MaxUnrlzd4%":
self.SetRiskManagement(MaximumUnrealizedProfitPercentPerSecurity(0.04)) # sell at a profit of 4%
if self.riskMgmt == "Bracketed4%Gain6%Loss":
self.SetRiskManagement(MaximumUnrealizedProfitPercentPerSecurity(0.04)) # sell at a profit of 4%
self.SetRiskManagement(MaximumDrawdownPercentPerSecurity(0.06)) # sell at a loss of 6%
if self.riskMgmt == "MaxUnrlzd4%" and self.alphaStrategy == "CM10":
self.SetRiskManagement(MaximumUnrealizedProfitPercentPerSecurity(0.0025))
def OnData(self, data: Slice):
pass
'''
if not self.Portfolio.Invested:
self.SetHoldings("FB", 0.33)
'''
def BuildExclusionSymbolsList(self):
self.Log("Begin BuildExclusionSymbolsList()")
csv = self.Download('https://raw.githubusercontent.com/barryplasmid/EquityExclusionList/main/EquityExclusionList.csv')
#self.myExclusionList = ConvertStringToList("BF.B LVS MGM MO PENN PM STZ TAP TSN WYNN CZR LVS MGM PENN WYNN BAC C JPM WFC CFG CMA FITB FRC HBAN KEY MTB PNC RF SIVB TFC USB ZION")
df = pd.read_csv(StringIO(csv))
self.myExclList = list(df.Symbol)
#self.myExclList = []
#additionalSymbols = ConvertStringToList(self.additionalExclusions) #("PENN PM STZ TAP")
pass
def BuildSymbolsList(self, symbolsListParam: str):
self.Log("Begin BuildSymbolsListForManualUniverse()")
if symbolsListParam == "SP500":
#csv = self.Download('https://raw.githubusercontent.com/datasets/s-and-p-500-companies/master/data/constituents.csv') 'donot use; old
csv = self.Download('https://raw.githubusercontent.com/barryplasmid/PublicLists/main/SP500-List-04152023.csv')
df = pd.read_csv(StringIO(csv))
self.myList = list(df.Symbol) #[x for x in self.df.Symbol]
pass
if symbolsListParam == "HalalSP500":
SP500SymbolsSPUS = "A AAP AAPL ABMD ABT ADBE ADI ADSK AKAM ALGN ALLE ALXN AMAT AMD AMGN ANET ANSS AOS APD APTV ATVI AVY AZO BAX BDX BIIB BIO BKNG BSX CDNS CDW CERN CHD CHRW CL CLX CMI COG COO COP CPRT CRM CSCO CTAS CTSH CTVA CTXS CVX DHI DHR DLTR DOV DXCM EBAY ECL EL EMR EOG EQR ETN ETSY EW EXPD FAST FB FBHS FFIV FLS FMC FTNT FTV GILD GOOG GOOGL GPC GRMN GWW HD HOLX HPQ HSIC HSY IDXX IEX ILMN INCY INTC INTU IPGP ISRG IT ITW JBHT JCI JNJ JNPR KLAC KMB KO KSU LIN LLY LOW LRCX MAS MDLZ MDT MKC MLM MMM MNST MRK MSFT MSI MTD MU MXIM NEM NKE NLOK NOW NSC NVDA NVR ODFL ORLY OTIS PAYC PEP PG PKG PKI PNR POOL PPG PXD QCOM REGN RHI RMD ROK ROL ROP ROST SBUX SHW SNPS STE SWK SYK TEL TER TFX TGT TIF TJX TMO TSCO TT TWTR TXN TYL UA UAA ULTA UNP UPS VAR VFC VMC VRSN VRTX WAT WM WST XRAY XYL ZBH ZBRA"
self.myList = ConvertStringToList(SP500SymbolsSPUS)
pass
if symbolsListParam == "SP500SymbolsAddedPriorTo2017":
SP500SymbolsAddedPriorTo2017 = "MMM ABT ABBV ACN ATVI ADM ADBE ADP AAP AES AFL A AIG APD AKAM ALK ALB ALLE LNT ALL GOOGL GOOG MO AMZN AEE AAL AEP AXP AMT AWK AMP ABC AME AMGN APH ADI ANTM AON APA AAPL AMAT APTV AIZ T ADSK AZO AVB AVY BLL BAC BBWI BAX BDX BRK.B BBY BIIB BLK BK BA BKNG BWA BXP BSX BMY AVGO BF.B CHRW CPB COF CAH KMX CCL CAT CBRE CNC CNP CERN CF SCHW CHTR CVX CMG CB CHD CI CINF CTAS CSCO C CFG CTXS CLX CME CMS KO CTSH CL CMCSA CMA CAG COP STZ COO COST CTRA CCI CSX CMI CVS DHI DVA DE DAL XRAY DVN DLR DFS DIS DG DLTR DOV DTE DUK EMN EBAY ECL EIX EW EA EMR ETR EOG EFX EQIX EQR ESS EL ES EXC EXPE EXPD EXR XOM FFIV FAST FRT FDX FIS FISV FMC F FTV FBHS FOXA FOX AJG GRMN GD GIS GPC GILD GL GPN GM GS GWW HAL HIG HAS HCA PEAK HSIC HSY HES HPE HOLX HD HON HRL HST HWM HPQ ITW ILMN INTC ICE IBM IP IPG IFF INTU ISRG IVZ IRM JBHT J JNJ JCI JPM JNPR K KEY KMB KIM KMI KHC KR LHX LH LRCX LEN LLY LNC LIN LKQ LMT LOW LUMN LYB MTB MRO MPC MMC MLM MAS MA MKC MCD MDT MRK FB MTD MCHP MU MSFT MAA MHK TAP MDLZ MNST MOS NDAQ NTAP NFLX NWL NEM NWSA NWS NEE NLSN NKE NSC NOC NLOK NRG NUE NVDA ORLY OXY OKE ORCL PCAR PH PYPL PNR PEP PKI PFE PM PSX PXD PNC PPG PFG PG PGR PLD PRU PEG PSA PHM PVH QRVO PWR DGX RL O REGN RF RSG RHI ROP ROST RCL CRM SLB STX SEE SHW SPG SWKS SJM SNA SO LUV SWK SBUX SYK SYF SYY TGT TEL TXT TMO TJX TSCO TT TDG TRV TFC TSN UDR ULTA UAA UA UNP UAL UNH UPS URI UHS VTR VRSN VRSK VZ VRTX VFC VTRS V VNO VMC WMT WBA WEC WFC WELL WDC WMB WTW WYNN XEL XYL YUM ZBH ZION ZTS"
self.myList = ConvertStringToList(SP500SymbolsAddedPriorTo2017)
pass
if symbolsListParam == "ManualList":
manualList = "MSFT AAPL AMZN META NVDA GOOG"
#manualList = "SQ DVN FANG TTD CLR TRGP RCL BLDR AA THC"
#manualList = "VCYT,SQ,CRWD,DDD,SPLK,QTRX,RIVN,QLYS,ABNB,NVDA,ADBE,TER,GOOGL,AAPL,INTU,AMAT,AMD,CLOU,MSFT,SPRE,AOS,AMAGX,UBER,SNOW,FTV,FB,EXPD,IBM,NXPI,BR,CRM,AMANX,AMCR,WBA,CSCO,FTNT,XOM,PANW,TX,DKS,ABBV,CAH"
#"FB AMZN AAPL MSFT GOOG" # ["FB", "AMZN", "AAPL", "MSFT", "GOOG"]
#manualList = "TGT ORLY AAP YUM DG AZO DLTR"
#manualList = "AA,BCRX,CAR,CLF,CNR,DXC,ET,M,NTLA,SKY,THC"
self.myList = ConvertStringToList(manualList)
if symbolsListParam == "CVM9-ManualList":
manualList = "VRTX GOLD ATVI AA NUE REGN KR MRO LHX PSX ACC CERN FANG INCY NLSN TWNK"
self.myList = ConvertStringToList(manualList)
if self.alphaStrategy == "CM2" or self.alphaStrategy == "CM3":
self.myList.append("SPY") #must add SPY for CM1 and CM3 strategies
self.myList.sort()
#for x in self.myExclList:
# if x in self.myList:
# self.myList.remove(x)
#self.myList = self.myList[:200]
self.Log(f"Selected Symbol List: {symbolsListParam} Num of symbols: {len(self.myList)} First 5 symbols: {self.myList[:5]}")
def SubscribeForData(self, dataResolution):
if self.alphaStrategy == "CM1" or self.alphaStrategy == "CM2" or self.alphaStrategy == "CM3":
for symbol in self.myList:
self.AddEquity(symbol, dataResolution) # Resolution.Daily or Resolution.Minute
self.Log(f"Manual universe num of symbols: {len(self.myList)} First 5 symbols: {self.myList[:5]}")
def CVM4CoarseSelectionFunction(self, coarse):
# If not time to rebalance, keep the same universe
if self.Time.month == self.lastMonth:
return Universe.Unchanged
# Else reassign the month variable
self.lastMonth = self.Time.month
if coarse == None:
self.Debug("Coarse is null")
return
# Sort by top dollar volume: most liquid to least liquid
selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 5], key = lambda x: x.DollarVolume, reverse=True)
#sortedByDollarVolume = sorted([x for x in coarse if x.HasFundamentalData], key=lambda x: x.DollarVolume, reverse=True)
#selected = [x for x in sortedByDollarVolume if x.Price > 10 and x.DollarVolume > 100000000 ]
for x in selected:
if x.Symbol.Value in self.myExclList:
selected.remove(x)
coarseList = [x.Symbol for x in selected[0:self.num_coarse]]
for x in coarseList:
self.myCoarseList.append(x.Value)
return coarseList
def CVM4FineSelectionFunction(self, fine):
# Filter the fine data for equities with non-zero/non-null Value,
filtered_fine = [x.Symbol for x in fine if x.OperationRatios.GrossMargin.Value > 0
and x.OperationRatios.QuickRatio.Value > 0
and x.OperationRatios.DebttoAssets.Value > 0
and x.ValuationRatios.BookValuePerShare > 0
and x.ValuationRatios.CashReturn > 0
and x.ValuationRatios.EarningYield > 0
and x.MarketCap > 0]
for x in filtered_fine:
self.myFineList.append(x.Value)
return filtered_fine
def CV5CoarseSelectionFunction(self, allEquityUniverse):
#self.Log("Begin CV5CoarseSelectionFunction()")
# If not time to rebalance, keep the same universe
if self.Time.month == self.lastMonth:
return Universe.Unchanged
# Else reassign the month variable
self.lastMonth = self.Time.month
if allEquityUniverse == None:
self.Log("allEquityUniverse is null")
return
# Sort by top dollar volume: most liquid to least liquid
selected = sorted([x for x in allEquityUniverse if x.HasFundamentalData and x.Price > 5], key = lambda x: x.DollarVolume, reverse=True)
for x in selected:
if x.Symbol.Value in self.myExclList:
selected.remove(x)
coarseSelected = [x.Symbol for x in selected[:self.num_coarse]]
return coarseSelected
def CV5FineSelectionFunction(self, coarseFilteredUniverse):
#self.Log("Begin CV5FineSelectionFunction()")
fine_filtered = []
self.fundamentalData = pd.DataFrame(columns=["Symbol", "PERatio", "PBRatio", "PSRatio", "EnterpriseValue", "EVToEBITDA", "GrossProfit", "DivYield5Year"])
j = 0
for x in coarseFilteredUniverse:
self.fundamentalData.loc[j] = [ x.Symbol.Value,
x.ValuationRatios.PERatio,
x.ValuationRatios.PBRatio,
x.ValuationRatios.PSRatio,
x.CompanyProfile.EnterpriseValue,
x.ValuationRatios.EVToEBITDA,
x.FinancialStatements.IncomeStatement.GrossProfit.TwelveMonths,
x.ValuationRatios.DivYield5Year]
j = j + 1
self.fundamentalData.set_index("Symbol", inplace=True)
self.Log(self.fundamentalData.shape)
self.fine_filtered = [x.Symbol for x in coarseFilteredUniverse]
self.Log(len(fine_filtered))
return self.fine_filtered
def CV8CoarseSelectionFunction(self, coarse):
# If not time to rebalance, keep the same universe
if self.Time.month == self.lastMonth:
return Universe.Unchanged
#self.Log("Begin CV8CoarseSelectionFunction()")
CoarseWithFundamental = [x for x in coarse if (x.HasFundamentalData) and (float(x.Price) > 5) and x.Market == Market.USA] # and x.Symbol.Value in self.myList ]
for x in CoarseWithFundamental:
if x.Symbol.Value in self.myExclList:
CoarseWithFundamental.remove(x)
sortedByDollarVolume = sorted(CoarseWithFundamental, key=lambda x: x.DollarVolume, reverse=True)
top = sortedByDollarVolume[:self.num_coarse]
return [i.Symbol for i in top]
def CV8FineSelectionFunction(self, fine):
if self.Time.month == self.lastMonth:
return Universe.Unchanged
self.lastMonth = self.Time.month
self.Log("Begin CV8FineSelectionFunction()")
filtered_fine = [x for x in fine if x.FinancialStatements.IncomeStatement.NetIncome.TwelveMonths and
x.FinancialStatements.CashFlowStatement.CashFlowFromContinuingOperatingActivities.TwelveMonths and
x.OperationRatios.ROA.ThreeMonths and x.OperationRatios.ROA.OneYear and
x.FinancialStatements.BalanceSheet.ShareIssued.ThreeMonths and x.FinancialStatements.BalanceSheet.ShareIssued.TwelveMonths and
x.OperationRatios.GrossMargin.ThreeMonths and x.OperationRatios.GrossMargin.OneYear and
x.OperationRatios.LongTermDebtEquityRatio.ThreeMonths and x.OperationRatios.LongTermDebtEquityRatio.OneYear and
x.OperationRatios.CurrentRatio.ThreeMonths and x.OperationRatios.CurrentRatio.OneYear and
x.OperationRatios.AssetsTurnover.ThreeMonths and x.OperationRatios.AssetsTurnover.OneYear and x.ValuationRatios.NormalizedPERatio]
fineSelection = []
for x in filtered_fine:
pfscore = PiotroskiFScore(x.FinancialStatements.IncomeStatement.NetIncome.TwelveMonths,
x.FinancialStatements.CashFlowStatement.CashFlowFromContinuingOperatingActivities.TwelveMonths,
x.OperationRatios.ROA.ThreeMonths, x.OperationRatios.ROA.OneYear,
x.FinancialStatements.BalanceSheet.ShareIssued.ThreeMonths, x.FinancialStatements.BalanceSheet.ShareIssued.TwelveMonths,
x.OperationRatios.GrossMargin.ThreeMonths, x.OperationRatios.GrossMargin.OneYear,
x.OperationRatios.LongTermDebtEquityRatio.ThreeMonths, x.OperationRatios.LongTermDebtEquityRatio.OneYear,
x.OperationRatios.CurrentRatio.ThreeMonths, x.OperationRatios.CurrentRatio.OneYear,
x.OperationRatios.AssetsTurnover.ThreeMonths, x.OperationRatios.AssetsTurnover.OneYear)
score = pfscore.ObjectiveScore()
#self.Log(f"{x.Symbol.Value} {score}")
if score > 7:
self.Log(f"{x.Symbol.Value} {x.CompanyProfile.HeadquarterCountry} {score}")
fineSelection.append(x)
fineSelection = sorted(fineSelection, key=lambda x: x.ValuationRatios.NormalizedPERatio, reverse = False)
if len(fineSelection) < 20:
return [x.Symbol for x in fineSelection]
else:
return [x.Symbol for x in fineSelection[:self.num_fine]]
def OnEndOfAlgorithm(self):
#Needed only in case of CVM4
if self.alphaStrategy == "CVM4":
coarseSet = set(self.myCoarseList)
fineSet = set(self.myFineList)
#self.Debug(coarseSet)
#self.Debug(fineSet)
#self.Debug("CoarseSet\n")
#for x in coarseSet:
# self.Debug(f"{x} {self.myCoarseList.count(x)}\n")
self.Debug("FineSet\n")
for x in self.cvm4Alpha.myStocksList:
self.Debug(f"{x} {self.myFineList.count(x)}\n")
pass