def __init__(self, output_dir, verbose=False): """ :param output_dir: Absolute filepath to output directory. :param verbose: boolean: Write info to console. :return: """ self.dbmodel = BasicDbModel() self.s_analyzer = LexiconSentimentAnalyzer() self.text_writer = TextWriter(output_dir) # writing CSV files self.verbose = verbose # verbose output self.stock_processor = StockPriceProcessor() # Object for price movements self.source_metrics_calculator = SourceMetricsCalculator(output_dir) self.total_metrics_calculator = TotalMetricsCalculator(output_dir) self.source_metrics_calculator_2_classes = SourceMetricsCalculator2classes(output_dir)
class DocumentsAnalyzer(object): def __init__(self, output_dir, verbose=False): """ :param output_dir: Absolute filepath to output directory. :param verbose: boolean: Write info to console. :return: """ self.dbmodel = BasicDbModel() self.s_analyzer = LexiconSentimentAnalyzer() self.text_writer = TextWriter(output_dir) # writing CSV files self.verbose = verbose # verbose output self.stock_processor = StockPriceProcessor() # Object for price movements self.source_metrics_calculator = SourceMetricsCalculator(output_dir) self.total_metrics_calculator = TotalMetricsCalculator(output_dir) self.source_metrics_calculator_2_classes = SourceMetricsCalculator2classes(output_dir) ## Analyze output file def analyze_all_companies(self, from_date, to_date, file_name, price_type, const_boundaries, used_dict_name='vader', classes_count=3): """ Analyze all documents for all companies. :param from_date: :param to_date: :param file_name: :param price_type: :param used_dict_name: :return: """ # Reset files. self.text_writer.write_econometric_file(file_name, [self._get_days_stats_header()], 'w') total_m_header = self.total_metrics_calculator.get_total_metrics_header() self.text_writer.write_econometric_file(file_name + '_total-metrics', [total_m_header], 'w') source_m_header = self.source_metrics_calculator.get_source_metrics_header() self.text_writer.write_econometric_file(file_name + '_source-metrics', [source_m_header], 'w') # Process companies companies = self.dbmodel.get_companies_order_by_total_documents(from_date, to_date) for comp in companies: print("<<<<<Company %d>>>>>") % comp['id'] if not self.verbose: with FaCommon.Helpers.suppress_stdout(): self.analyze_company(comp['id'], from_date, to_date, file_name, price_type, const_boundaries, used_dict_name, False, classes_count) else: self.analyze_company(comp['id'], from_date, to_date, file_name, price_type, const_boundaries, used_dict_name, False, classes_count) print('>>>All stuff saved.') def analyze_company(self, company_id, from_date, to_date, file_name, price_type, const_boundaries, used_dict_name, write_header=False, classes_count=3): """ Analyze documents about company (from_date -> present date). :return: list of days, where every row contains information for documents for this day. """ # Prepare variables. examined_date = from_date last_date = to_date total_data = [] max_sent = float('-inf') # Set stock prices for this company ID. self.stock_processor.set_stock_prices(company_id, examined_date, price_type) #exit(self.stock_processor.get_price_movement_with_delay(examined_date, 2)) # Prepare list for writing to a file. # For every day (from "from_date" to "to_date"), query the DB for documents created on the day. while examined_date <= last_date: print("===%s===") % examined_date # For every document type, process all documents and count number of neutral, positive, negative documents. yahoo_values = self._process_yahoo(company_id, examined_date, used_dict_name) fb_p_values = self._process_fb_posts(company_id, examined_date, used_dict_name) fb_c_values = self._process_fb_comments(company_id, examined_date, used_dict_name) tw_values = self._process_tweets(company_id, examined_date, used_dict_name) # Save acquired data day_data = [ company_id, examined_date.strftime('%d.%m.%Y'), fb_p_values['neu'], fb_p_values['pos'], fb_p_values['neg'], fb_c_values['neu'], fb_c_values['pos'], fb_c_values['neg'], yahoo_values['neu'], yahoo_values['pos'], yahoo_values['neg'], tw_values['neu'], tw_values['pos'], tw_values['neg'], ] # Get stock price movement direction for 1,2,3 days from examined date. Also for previous day. day_data.append(self.stock_processor.get_price_movement_with_delay(examined_date, -1, const_boundaries)) day_data.append(self.stock_processor.get_price_movement_with_delay(examined_date, 1, const_boundaries)) day_data.append(self.stock_processor.get_price_movement_with_delay(examined_date, 2, const_boundaries)) day_data.append(self.stock_processor.get_price_movement_with_delay(examined_date, 3, const_boundaries)) # Calculate simple sentiment for all sources. fb_post_s = self._calc_source_sentiment(fb_p_values) fb_comment_s = self._calc_source_sentiment(fb_c_values) yahoo_s = self._calc_source_sentiment(yahoo_values) twitter_s = self._calc_source_sentiment(tw_values) day_data.extend([fb_post_s, fb_comment_s, yahoo_s, twitter_s]) # Calculate overall sentiment for the day. (max_sent, day_sent) = self._calc_overall_sentiment_for_day(max_sent, fb_p_values, fb_c_values, yahoo_values, tw_values) day_data.append(day_sent) # Save day data to total data. total_data.append(day_data) # Increment examined date. examined_date = examined_date + datetime.timedelta(days=1) # Normalize sentiment values. for i, day_data in enumerate(total_data): norm_sent = self._normalize_sentiment(total_data[i][-1], max_sent) string_sent = self._format_sentiment(norm_sent) total_data[i][-1] = string_sent # Write results to file. if write_header: total_data.insert(0, self._get_days_stats_header()) self.text_writer.write_econometric_file(file_name, total_data, 'w') del(total_data[0]) else: self.text_writer.write_econometric_file(file_name, total_data, 'a') # Calculate metrics by source. m_filename = file_name + '_source-metrics' if classes_count == 3: self.source_metrics_calculator.calculate_metrics_by_source(company_id, total_data, m_filename, price_type, write_header) else: self.source_metrics_calculator_2_classes.calculate_metrics_by_source(company_id, total_data, m_filename, price_type, write_header) # Calculate total metrics. m_filename = file_name + '_total-metrics' self.total_metrics_calculator.calculate_total_metrics(company_id, total_data, m_filename, price_type, write_header) #### PRIVATE methods for processing documents def _process_fb_posts(self, company_id, examined_date, used_dict_name='vader'): # Select all FB posts for given company created on given date. posts = self.dbmodel.get_daily_fb_posts(company_id, examined_date) counter = {'pos': 0, 'neu': 0, 'neg': 0} # Calculate sentiment for all posts for post in posts: #print("FB post: %s") % post['id'], post_text = TextProcessing.process_facebook_text(post['text']) if len(post_text) == 0: continue # skip empty posts sent_value = self.s_analyzer.calculate_vader_sentiment(used_dict_name, post_text, False) polarity = self.s_analyzer.format_sentiment_value(sent_value) counter[polarity] += 1 #print("| %s ... %s") % (str(round(sent_value, 4)), polarity) # result return counter def _process_fb_comments(self, company_id, examined_date, used_dict_name='vader'): # Select all FB comments. comments = self.dbmodel.get_daily_fb_comments(company_id, examined_date) counter = {'pos': 0, 'neu': 0, 'neg': 0} # Calculate sentiment for all posts for com in comments: #print("FB comment: %s") % com['id'], com_text = TextProcessing.process_facebook_text(com['text']) if len(com_text) == 0: continue # skip empty comments sent_value = self.s_analyzer.calculate_vader_sentiment(used_dict_name, com_text, False) polarity = self.s_analyzer.format_sentiment_value(sent_value) counter[polarity] += 1 #print("| %s ... %s") % (str(round(sent_value, 4)), polarity) # result return counter def _process_yahoo(self, company_id, examined_date, used_dict_name='vader'): # Select all Yahoo Finance articles. articles = self.dbmodel.get_daily_articles(company_id, examined_date) counter = {'pos': 0, 'neu': 0, 'neg': 0} # Calculate sentiment for all articles for art in articles: #print("Yahoo article: %s") % art['id'], art_text = TextProcessing.process_article_text(art['text']) if len(art_text) == 0: continue # skip empty articles sent_value = self.s_analyzer.calculate_vader_sentiment(used_dict_name, art_text, True) polarity = self.s_analyzer.format_sentiment_value(sent_value) counter[polarity] += 1 #print("| %s ... %s") % (str(round(sent_value, 4)), polarity) # result return counter def _process_tweets(self, company_id, examined_date, used_dict_name='vader'): # Select all Yahoo Finance articles. tweets = self.dbmodel.get_daily_tweets(company_id, examined_date) counter = {'pos': 0, 'neu': 0, 'neg': 0} # Calculate sentiment for all articles. for tw in tweets: #print("Tweet: %s") % tw['tw_id'], tw_text = TextProcessing.process_facebook_text(tw['text']) if len(tw_text) == 0: continue # skip empty tweets sent_value = self.s_analyzer.calculate_vader_sentiment(used_dict_name, tw_text, False) polarity = self.s_analyzer.format_sentiment_value(sent_value) counter[polarity] += 1 #print("| %s ... %s") % (str(round(sent_value, 4)), polarity) # result return counter ## PRIVATE methods for determining sentiment of the whole day def _calc_source_sentiment(self, s_dict): """ Calculate sentiment for given source dictionary. :param s_dict: dictionary (sentiment -> number of documents} :return: string (pos, neg, neu) """ max_s = max(s_dict.keys(), key=lambda k: s_dict[k]) # If neutral value is also the biggest one, choose it. if s_dict['neu'] == s_dict[max_s]: return 'neu' return max_s @staticmethod def _calc_overall_sentiment_for_day(max_sent, fb_p_values, fb_c_values, yahoo_values, tw_values): # Calculate numeric sentiment fb_p_sent = fb_p_values['pos'] - fb_p_values['neg'] fb_c_sent = fb_c_values['pos'] - fb_p_values['neg'] yahoo_sent = yahoo_values['pos'] - fb_p_values['neg'] tw_sent = tw_values['pos'] - fb_p_values['neg'] overall_sent = fb_p_sent + fb_c_sent + yahoo_sent + tw_sent #print fb_p_sent,fb_c_sent,yahoo_sent,tw_sent # Is the new sentiment larger than current largest one? if overall_sent > max_sent: max_sent = overall_sent return max_sent, overall_sent @staticmethod def _normalize_sentiment(score, alpha=100): """ Normalize the score to be between -1 and 1 using an alpha that approximates the max expected value. """ try: norm_score = score/math.sqrt((score*score) + alpha) except ZeroDivisionError: norm_score = score return norm_score @staticmethod def _format_sentiment(norm_score): if -0.1 < norm_score < 0.1: return 'neu' elif norm_score > 0: return 'pos' elif norm_score < 0: return 'neg' @staticmethod def _get_days_stats_header(): header_days = [ 'company_id', 'date', 'fb_post_neutral', 'fb_post_positive', 'fb_post_negative', 'fb_comment_neutral', 'fb_comment_positive', 'fb_comment_negative', 'yahoo_neutral', 'yahoo_positive', 'yahoo_negative', 'twitter_neutral', 'twitter_positive', 'twitter_negative', 'stock_dir_-1', 'stock_dir_1', 'stock_dir_2', 'stock_dir_3', 'sentiment_fb_post', 'sentiment_fb_comment', 'sentiment_yahoo', 'sentiment_twitter', 'overall_sentiment', ] return header_days