Dear all,
I'm a complete newbie as far as quantitative finance is concerned (I have no educational background in finance, coding, or science in general). However, I am an avid investor who truly believes in QuantConnect's vision and mission to transition humanity to the future of trading. As such, I will regularly post quant strategies here in this post.
Here is my first paper. (Comment enabled! Feel free to drop some comments in this doc!)
Method: Price Earnings Anomaly
from QuantConnect.Data.UniverseSelection import *
import math
import numpy as np
import pandas as pd
import scipy as sp
class PriceEarningsAnamoly(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 5, 1)
self.SetEndDate(2021, 5, 1)
self.SetCash(1000000)
self.SetBenchmark("SPY")
self.UniverseSettings.Resolution = Resolution.Daily
self.symbols = []
# record the year that have passed since the algorithm starts
self.year = -1
self._NumCoarseStocks = 200
self._NumStocksInPortfolio = 10
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
def CoarseSelectionFunction(self, coarse):
if self.Time.year == self.year:
return self.symbols
# drop stocks which have no fundamental data or have low price
CoarseWithFundamental = [x for x in coarse if x.HasFundamentalData and x.Price > 5]
sortedByDollarVolume = sorted(CoarseWithFundamental, key=lambda x: x.DollarVolume, reverse=False)
return [i.Symbol for i in sortedByDollarVolume[:self._NumCoarseStocks]]
def FineSelectionFunction(self, fine):
if self.Time.year == self.year:
return self.symbols
self.year = self.Time.year
fine = [x for x in fine if x.ValuationRatios.PERatio > 0]
sortedPERatio = sorted(fine, key=lambda x: x.ValuationRatios.PERatio)
self.symbols = [i.Symbol for i in sortedPERatio[:self._NumStocksInPortfolio]]
return self.symbols
def OnSecuritiesChanged(self, change):
# liquidate securities that removed from the universe
for security in change.RemovedSecurities:
if self.Portfolio[security.Symbol].Invested:
self.Liquidate(security.Symbol)
count = len(change.AddedSecurities)
# evenly invest on securities that newly added to the universe
for security in change.AddedSecurities:
self.SetHoldings(security.Symbol, 1.0/count)
Method: Small Capitalization Stocks Premium Anomaly
class SmallCapInvestmentAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 5, 1)
self.SetEndDate(2021, 5, 1)
self.SetCash(500000)
self.SetBenchmark("SPY")
self.year = -1
self.count = 10
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
def CoarseSelectionFunction(self, coarse):
''' Drop stocks which have no fundamental data or have low price '''
if self.year == self.Time.year:
return Universe.Unchanged
return [x.Symbol for x in coarse if x.HasFundamentalData and x.Price > 5]
def FineSelectionFunction(self, fine):
''' Selects the stocks by lowest market cap '''
sorted_market_cap = sorted([x for x in fine if x.MarketCap > 0],
key=lambda x: x.MarketCap)
return [x.Symbol for x in sorted_market_cap[:self.count]]
def OnData(self, data):
if self.year == self.Time.year:
return
self.year = self.Time.year
for symbol in self.ActiveSecurities.Keys:
self.SetHoldings(symbol, 1/self.count)
def OnSecuritiesChanged(self, changes):
''' Liquidate the securities that were removed from the universe '''
for security in changes.RemovedSecurities:
symbol = security.Symbol
if self.Portfolio[symbol].Invested:
self.Liquidate(symbol, 'Removed from Universe')
Method: Combination Strategy
from QuantConnect.Data.UniverseSelection import *
import math
import numpy as np
import pandas as pd
import scipy as sp
class PriceEarningsAnamoly(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 5, 1)
self.SetEndDate(2021, 5, 1)
self.SetCash(500000)
self.SetBenchmark("SPY")
self.UniverseSettings.Resolution = Resolution.Daily
self.symbols = []
# record the year that have passed since the algorithm starts
self.year = -1
self._NumCoarseStocks = 200
self._NumStocksInPortfolio = 10
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
def CoarseSelectionFunction(self, coarse):
if self.Time.year == self.year:
return self.symbols
# drop stocks which have no fundamental data or have low price
CoarseWithFundamental = [x for x in coarse if x.HasFundamentalData and x.Price > 5]
sortedByDollarVolume = sorted(CoarseWithFundamental, key=lambda x: x.DollarVolume, reverse=False)
return [i.Symbol for i in sortedByDollarVolume[:self._NumCoarseStocks]]
def FineSelectionFunction(self, fine):
if self.Time.year == self.year:
return self.symbols
self.year = self.Time.year
fine = [x for x in fine if x.ValuationRatios.PERatio > 0]
sortedPERatio = sorted(fine, key=lambda x: x.ValuationRatios.PERatio)
self.symbols = [i.Symbol for i in sortedPERatio[:self._NumStocksInPortfolio]]
return self.symbols
def OnSecuritiesChanged(self, change):
# liquidate securities that removed from the universe
for security in change.RemovedSecurities:
if self.Portfolio[security.Symbol].Invested:
self.Liquidate(security.Symbol)
count = len(change.AddedSecurities)
# evenly invest on securities that newly added to the universe
for security in change.AddedSecurities:
self.SetHoldings(security.Symbol, 1.0/count)
class SmallCapInvestmentAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 5, 1)
self.SetEndDate(2021, 5, 1)
self.SetCash(500000)
self.SetBenchmark("SPY")
self.year = -1
self.count = 10
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
def CoarseSelectionFunction(self, coarse):
''' Drop stocks which have no fundamental data or have low price '''
if self.year == self.Time.year:
return Universe.Unchanged
return [x.Symbol for x in coarse if x.HasFundamentalData and x.Price > 5]
def FineSelectionFunction(self, fine):
''' Selects the stocks by lowest market cap '''
sorted_market_cap = sorted([x for x in fine if x.MarketCap > 0],
key=lambda x: x.MarketCap)
return [x.Symbol for x in sorted_market_cap[:self.count]]
def OnData(self, data):
if self.year == self.Time.year:
return
self.year = self.Time.year
for symbol in self.ActiveSecurities.Keys:
self.SetHoldings(symbol, 1/self.count)
def OnSecuritiesChanged(self, changes):
''' Liquidate the securities that were removed from the universe '''
for security in changes.RemovedSecurities:
symbol = security.Symbol
if self.Portfolio[symbol].Invested:
self.Liquidate(symbol, 'Removed from Universe')
As you can see, I referenced Quant tutorials heavily, as I didn't know where else to start, and the combination strategy looks really ugly. I don't even know if my logic is correct.
What do you think about the paper? Are there any flaws in my reasoning / code? How could I further optimize it?
Thank you for your time and patience. I'm willing to learn from all of you, and let's develop the best strategy together!
Shaun Dawson
Zi Han,
It's a pleasure to meet you. I'm no more experienced than you are, but just as eager to learn.
I've read your paper, and taken a look at the Algorithms you posted. Very interesting. I have a couple of thoughts.
First, have you considered migrating to using more of the elements of the Algorithm Framework? In other words, splitting the logic of the algorithm into Universe Selection (which you've done), Alpha, and Execution Model. If I understood some of the conclusions in your paper properly, this would really facilitate your goal of tweaking with strategies to use in certain market conditions.
I have an interesting idea to enhance your P/E ratio strategy. Today, that strategy buys the 10 stocks with the lowest P/E ratio, assuming that the stock passes certain filters ($5 per share, etc.). I'm skeptical of magic numbers like this. Why $5 per share and not $1, or $10? Why 10 stocks, and not 2, or 50? I fear that this will lead to overfitting.
The fundamental theory of that strategy is that you want to buy undervalued stocks. (And presumably sell overvalued ones, though you aren't doing this explicitly). Consider instead coming up with a metric for how undervalued the stock is. One way to do that is calculate “intrinsic value” (there are various ways to do that, I believe). And compare the valuation implied by the Price to the Intrinsic value, and buy anything where that difference is sufficiently large. If you were using Alphas in the Algorithm Framework, you could emit an “Up”, “Down”, or “Flat” Insight based on the magnitude of that difference.
What do you think?
Shaun
Zhi Han Chew
Hi Shaun,
Honestly, I think there's a lot of things to be unpacked here XD
I'll get back to you in a week, with a follow-up research paper based on your suggestions and my optimization tips.
I look forward to having a follow-up discussion with you soon. Cheers!
Zhihan Chew
Varad Kabade
Hi Zhi Han Chew,
Thank you for sharing the algorithms with the community.
The above method of combining the strategies will not work as the IDE will only consider one instance of the QCAlgorithm class.To combine the above two strategies, we need to combine the fine filter function. We can first sort the symbols by the market cap and then by the PE ratio or vice versa.
Best,
Varad Kabade
Zhi Han Chew
Dear Varad,
Thanks for the comment! I'll implement your suggestion in 7 days.
Shaun Dawson
Hi, Zhihan,
I found some additional resources to consider:
Here's an article about Value Strategies in general:
https://quantpedia.com/best-performing-value-strategies-part-1/
And here's a thread in this community about a “Buffett Indicator” of over/under valuation of the market in general.
https://www.quantconnect.com/forum/discussion/4360/buffet-indicator-for-macro-trends/p1
Shaun
Zhi Han Chew
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!