Overall Statistics |
Total Trades 2814 Average Win 0.08% Average Loss -0.07% Compounding Annual Return 0.761% Drawdown 9.300% Expectancy 0.014 Net Profit 1.206% Sharpe Ratio 0.116 Probabilistic Sharpe Ratio 9.069% Loss Rate 53% Win Rate 47% Profit-Loss Ratio 1.17 Alpha -0.003 Beta 0.062 Annual Standard Deviation 0.062 Annual Variance 0.004 Information Ratio -0.706 Tracking Error 0.223 Treynor Ratio 0.116 Total Fees $4483.81 |
from QuantConnect.Data.Custom.Tiingo import * from SentimentByPhrase import SentimentByPhrase from nltk.util import ngrams class DrugNewsSentimentAlphaModel(AlphaModel): """ This class emits insights to take long intraday positions for securities that have positive news sentiment. """ symbol_data_by_symbol = {} sentiment_by_phrase = SentimentByPhrase.dictionary max_phrase_words = max([len(phrase.split()) for phrase in sentiment_by_phrase.keys()]) sign = lambda _, x: int(x and (1, -1)[x < 0]) def __init__(self, bars_before_insight=30): """ Input: - bars_before_insight The number of bars to wait each morning before looking to emit insights """ self.bars_before_insight = bars_before_insight def Update(self, algorithm, data): """ Called each time our alpha model receives a new data slice. Input: - algorithm Algorithm instance running the backtest - data A data structure for all of an algorithm's data at a single time step Returns a list of Insights to the portfolio construction model """ insights = [] for symbol, symbol_data in self.symbol_data_by_symbol.items(): # If it's after-hours or within 30-minutes of the open, update # cumulative sentiment for each symbol if symbol_data.bars_seen_today < self.bars_before_insight: tiingo_symbol = symbol_data.tiingo_symbol if data.ContainsKey(tiingo_symbol) and data[tiingo_symbol] is not None: article = data[tiingo_symbol] symbol_data.cumulative_sentiment += self.CalculateSentiment(article) if data.ContainsKey(symbol) and data[symbol] is not None: symbol_data.bars_seen_today += 1 # 30-mintes after the open, emit insights in the direction of the cumulative sentiment. # Only emit insights on Wednesdays to capture the analomaly documented by Berument and # Kiymaz (2001). if symbol_data.bars_seen_today == self.bars_before_insight and data.Time.weekday() == 2: next_close_time = symbol_data.exchange.Hours.GetNextMarketClose(data.Time, False) direction = self.sign(symbol_data.cumulative_sentiment) if direction == 0: continue insight = Insight.Price(symbol, next_close_time - timedelta(minutes=2), direction) insights.append(insight) # At the close, reset the cumulative sentiment if not symbol_data.exchange.DateTimeIsOpen(data.Time): symbol_data.cumulative_sentiment = 0 symbol_data.bars_seen_today = 0 return insights def OnSecuritiesChanged(self, algorithm, changes): """ Called each time our universe has changed. Input: - algorithm Algorithm instance running the backtest - changes The additions and subtractions to the algorithm's security subscriptions """ for security in changes.AddedSecurities: self.symbol_data_by_symbol[security.Symbol] = SymbolData(security, algorithm) for security in changes.RemovedSecurities: symbol_data = self.symbol_data_by_symbol.pop(security.Symbol, None) if symbol_data: algorithm.RemoveSecurity(symbol_data.tiingo_symbol) def CalculateSentiment(self, article): """ Calculates the sentiment of a Tiingo news article by analyzing the article's title and description. We utilize a dictionary of sentiment values composed by experts in the domain who reviewed news articles over several years. Input: - article Tiingo news article object Returns the sentiment value of the article. """ sentiment = 0 for content in (article.Title, article.Description): words = content.lower().split() for num_words in range(1, self.max_phrase_words + 1): for gram in ngrams(words, num_words): phrase = ' '.join(gram) if phrase in self.sentiment_by_phrase.keys(): sentiment += self.sentiment_by_phrase[phrase] return sentiment class SymbolData: """ This class is used to store information on each security in the universe and initilize the Tiingo news feeds for the security. """ cumulative_sentiment = 0 bars_seen_today = 0 def __init__(self, security, algorithm): self.exchange = security.Exchange self.tiingo_symbol = algorithm.AddData(TiingoNews, security.Symbol).Symbol
# 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. from clr import AddReference AddReference("System") AddReference("QuantConnect.Algorithm") AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from DrugManufacturerUniverseSelection import DrugManufacturerUniverseSelection from DrugNewsSentimentAlphaModel import DrugNewsSentimentAlphaModel class NewsSentimentDrugManufacturerAlgorithm(QCAlgorithm): def Initialize(self): self.SetStartDate(2019, 1, 1) self.SetCash(100000) self.SetUniverseSelection(DrugManufacturerUniverseSelection()) self.UniverseSettings.Resolution = Resolution.Minute self.SetAlpha(DrugNewsSentimentAlphaModel()) self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel()) self.SetExecution(ImmediateExecutionModel())
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel class DrugManufacturerUniverseSelection(FundamentalUniverseSelectionModel): """ This universe selection model contain securities in the drug manufacturing industry group. """ def __init__(self, coarse_size=2500, fine_size=50): self.coarse_size = coarse_size self.fine_size = fine_size super().__init__(True) def SelectCoarse(self, algorithm, coarse): """ Coarse universe selection is called each day at midnight. Input: - algorithm Algorithm instance running the backtest - coarse List of CoarseFundamental objects Returns the symbols that have fundamental data and the largest dollar volume. """ has_fundamentals = [c for c in coarse if c.HasFundamentalData] sorted_by_dollar_volume = sorted(has_fundamentals, key=lambda c: c.DollarVolume, reverse=True) return [ x.Symbol for x in sorted_by_dollar_volume[:self.coarse_size] ] def SelectFine(self, algorithm, fine): """ Fine universe selection is performed each day at midnight after `SelectCoarse`. Input: - algorithm Algorithm instance running the backtest - fine List of FineFundamental objects that result from `SelectCoarse` processing Returns a list of symbols that are in the drug manufacturing industry and have the greatest PE ratios. """ drug_manufacturers = [f for f in fine if f.AssetClassification.MorningstarIndustryGroupCode == MorningstarIndustryGroupCode.DrugManufacturers] sorted_by_pe = sorted(drug_manufacturers, key=lambda f: f.ValuationRatios.PERatio, reverse=True) return [ x.Symbol for x in sorted_by_pe[:self.fine_size] ]
""" Sentiment dicationary retrieved from: https://github.com/queensbamlab/NewsSentiments/blob/master/dict.csv "The dictionary was created by leveraging author's domain expertise and thorough analysis of news articles over the years." (Isah, Shah, & Zulkernine, , Merchant, & Sargeant, 2018, p. 3) The dictionary has been adjusted to lowercase. """ class SentimentByPhrase: dictionary = { 'okay from fda' : 1, 'fda approval' : 1, 'usfda approval' : 1, 'weaker rupee' : 1, 'positive step' : 1, 'resolution' : 1, 'successful' : 1, 'stellar' : 1, 'better' : 1, 'much better' : 1, 'better margins' : 1, 'favourable' : 1, 'approval' : 1, 'tough' : -1, 'reported lower than expected sales' : -1, 'lower than expected sales' : -1, 'affecting sales growth' : -1, 'difficult one' : -1, 'pricing pressure' : -1, 'sales declined' : -1, 'dull' : -1, 'significant violations' : -1, 'warning letter' : -1, 'issued warning letter' : -1, 'adulterate' : -1, 'potentially contaminate' : -1, 'contaminate' : -1, 'fail' : -1, 'warn' : -1, 'violation' : -1, 'legal action' : -1, 'drag' : -1, 'sales decline' : -1, 'margins decline' : -1, 'weak' : -1, 'offset price erosion' : 1, 'price erosion' : -1, 'slowdown' : -1, 'sanction' : -1, 'concern' : -1, 'drag on sale' : -1, 'drop' : -1, 'challenge' : -1, 'toll' : -1, 'uncertain' : -1, 'recall' : -1, 'health' : 1, 'stability' : 1, 'mixed set' : -1, 'shares declined' : 0, 'major breakthrough' : 1, 'good quarter' : 1, 'appreciating rupee' : -1, 'depreciating rupee' : 1, 'heightened competition' : -1, 'incorrect instructions' : -1, 'shares decline' : 0, 'zero observations' : 1, 'strong us pipeline' : 1, 'upgrade' : 1, 'downgrade' : -1, 'mixed bag' : -1, 'disappointing year' : -1, 'domestic challenges' : -1, 'benefit' : 1, 'percent growth' : 1, 'flat revenue' : -1, 'flat' : -1, 'beat' : 1, 'achieve' : 1, 'steady margins' : 1, 'rise' : 1, 'expand' : 1, 'ramp up' : 1, 'launch' : 1, 'not issued' : 1, 'clear' : 1, 'address' : 0, 'observation' : 0, 'procedural' : 0, 'eir' : 1, 'monetise' : 1, 'outperform' : 1, 'enhance' : 1, 'form 483' : -1, 'clarify' : 1, 'facility' : 0, 'starts' : 1, 'stable' : 1, 'initiative' : 1, 'sold rights' : 1, 'terminate' : -1, 'strengthen' : 1, 'sahpra approval' : 1, 'nod' : 1, 'acquire' : 1, 'raise target' : 1, 'scaling up' : 1, 'raise' : 1, 'subject to clearance' : 0 }