-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from 0xTxbi/sentiment-analysis-service
sentiment analysis service
- Loading branch information
Showing
11 changed files
with
321 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { MigrationInterface, QueryRunner } from "typeorm"; | ||
|
||
export class Migrationsc1702404205332 implements MigrationInterface { | ||
name = 'Migrationsc1702404205332' | ||
|
||
public async up(queryRunner: QueryRunner): Promise<void> { | ||
await queryRunner.query(`CREATE TABLE "sentiment_result" ("id" SERIAL NOT NULL, "stockSymbol" character varying NOT NULL, "sentiment" character varying NOT NULL, "confidence" integer NOT NULL, "currentStockPrice" integer, "priceChange" integer, "percentageChange" integer, CONSTRAINT "PK_86c8d740551a9efe751cbca494f" PRIMARY KEY ("id"))`); | ||
await queryRunner.query(`CREATE TABLE "news" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "link" character varying NOT NULL, "publisher" text NOT NULL, "description" character varying NOT NULL, "imageUrl" character varying NOT NULL, "content" character varying NOT NULL, "timestamp" TIMESTAMP NOT NULL, CONSTRAINT "PK_39a43dfcb6007180f04aff2357e" PRIMARY KEY ("id"))`); | ||
} | ||
|
||
public async down(queryRunner: QueryRunner): Promise<void> { | ||
await queryRunner.query(`DROP TABLE "news"`); | ||
await queryRunner.query(`DROP TABLE "sentiment_result"`); | ||
} | ||
|
||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"; | ||
|
||
@Entity() | ||
export class SentimentResult { | ||
@PrimaryGeneratedColumn() | ||
id: number; | ||
|
||
@Column() | ||
stockSymbol: string; | ||
|
||
@Column() | ||
sentiment: string; | ||
|
||
@Column() | ||
confidence: number; | ||
|
||
@Column({ nullable: true }) | ||
currentStockPrice: number; | ||
|
||
@Column({ nullable: true }) | ||
priceChange: number; | ||
|
||
@Column({ nullable: true }) | ||
percentageChange: number; | ||
} |
24 changes: 24 additions & 0 deletions
24
sentiment-analysis-service/src/controllers/SentimentController.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { JsonController, Get, Param } from "routing-controllers"; | ||
import { Service } from "typedi"; | ||
import SentimentService from "../services/SentimentService"; | ||
|
||
@JsonController() | ||
@Service() | ||
export class SentimentController { | ||
constructor(private sentimentService: SentimentService) {} | ||
|
||
@Get("/sentiment/:stockSymbol") | ||
async getSentimentAnalysis( | ||
@Param("stockSymbol") stockSymbol: string | ||
): Promise<any> { | ||
try { | ||
const result = | ||
await this.sentimentService.analyzeSentiment( | ||
stockSymbol | ||
); | ||
return { success: true, data: result }; | ||
} catch (error) { | ||
return { success: false, error: error.message }; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import "reflect-metadata"; | ||
import { createExpressServer, useContainer } from "routing-controllers"; | ||
import { Container } from "typedi"; | ||
import SentimentService from "./services/SentimentService"; | ||
import { SentimentController } from "./controllers/SentimentController"; | ||
|
||
useContainer(Container); | ||
|
||
const app = createExpressServer({ | ||
controllers: [SentimentController], | ||
}); | ||
|
||
const sentimentService = new SentimentService(); | ||
|
||
app.set("sentimentService", sentimentService); | ||
|
||
app.listen(3003, () => { | ||
console.log("Sentiment Analysis Service is running on port 3003"); | ||
}); |
130 changes: 130 additions & 0 deletions
130
sentiment-analysis-service/src/services/SentimentService.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import * as natural from "natural"; | ||
import axios from "axios"; | ||
import { Service } from "typedi"; | ||
import * as dotenv from "dotenv"; | ||
|
||
dotenv.config(); | ||
|
||
@Service() | ||
class SentimentService { | ||
private sentimentAnalyzer: any; | ||
// store fetched articles | ||
|
||
constructor() { | ||
// initialize the sentiment analyzer | ||
this.sentimentAnalyzer = new natural.SentimentAnalyzer( | ||
"English", | ||
natural.PorterStemmer, | ||
"afinn" | ||
); | ||
} | ||
|
||
async analyzeSentiment(stockSymbol: string): Promise<any> { | ||
try { | ||
// fetch stock information from the Stock Info service | ||
const stockInfo = await this.fetchStockInfo( | ||
stockSymbol | ||
); | ||
|
||
// fetch news articles from the news service | ||
const newsArticles = await this.fetchNewsData( | ||
stockSymbol | ||
); | ||
|
||
// perform sentiment analysis using fetched data | ||
const sentimentResult = this.performSentimentAnalysis( | ||
stockSymbol, | ||
newsArticles, | ||
stockInfo | ||
); | ||
|
||
return sentimentResult; | ||
} catch (error) { | ||
throw new Error( | ||
`Error analyzing sentiment: ${error.message}` | ||
); | ||
} | ||
} | ||
|
||
private async fetchStockInfo(stockSymbol: string): Promise<any> { | ||
try { | ||
const response = await axios.get( | ||
`${process.env.STOCK_INFO_SERVICE}/stock/${stockSymbol}` | ||
); | ||
|
||
return response.data; | ||
} catch (error) { | ||
throw new Error( | ||
`Error fetching stock information: ${error.message}` | ||
); | ||
} | ||
} | ||
|
||
private async fetchNewsData(stockSymbol: string): Promise<any[]> { | ||
try { | ||
const response = await axios.get( | ||
`${process.env.NEWS_SERVICE}/news/${stockSymbol}` | ||
); | ||
|
||
return response.data; | ||
} catch (error) { | ||
throw new Error( | ||
`Error fetching news data: ${error.message}` | ||
); | ||
} | ||
} | ||
|
||
// logic to perform sentiment analysis | ||
private performSentimentAnalysis( | ||
stockSymbol: string, | ||
newsArticles: any[], | ||
stockInfo: any | ||
): any { | ||
const overallSentiment = | ||
this.analyzeHeadlines(newsArticles) + | ||
this.analyzeStockInfo(stockInfo); | ||
|
||
const sentimentResult = { | ||
stockSymbol: stockSymbol, | ||
sentiment: | ||
overallSentiment >= 0 ? "positive" : "negative", | ||
confidence: | ||
Math.abs(overallSentiment) / | ||
(newsArticles.length + 1), | ||
currentStockPrice: stockInfo.currentStockPrice, | ||
}; | ||
|
||
return sentimentResult; | ||
} | ||
|
||
private analyzeHeadlines(newsArticles: any[]): number { | ||
// analyse sentiment based on the headlines of news articles | ||
return newsArticles.reduce((totalSentiment, article) => { | ||
// extract the title from the current article | ||
const articleTitle = article.title; | ||
|
||
// get the sentiment analysis result for the title | ||
const sentimentAnalysisResult = | ||
this.sentimentAnalyzer.getSentiment( | ||
articleTitle | ||
); | ||
|
||
console.log(sentimentAnalysisResult); | ||
|
||
// extract the sentiment score from the analysis result | ||
const sentimentScore = sentimentAnalysisResult.score; | ||
|
||
// add the sentiment score to the totalSentiment | ||
const updatedTotalSentiment = | ||
totalSentiment + sentimentScore; | ||
|
||
return updatedTotalSentiment; | ||
}, 0); | ||
} | ||
|
||
private analyzeStockInfo(stockInfo: any): number { | ||
return 0; | ||
} | ||
} | ||
|
||
export default SentimentService; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
const contractionMap = { | ||
"ain't": "am not", | ||
"aren't": "are not", | ||
"can't": "cannot", | ||
// todo: add more contractions | ||
}; | ||
|
||
// convert contractions to their standard lexicon | ||
export const expandContractions = (text: string) => { | ||
return text.replace(/(\b\w+'\w+\b)/g, (match) => { | ||
return contractionMap[match] || match; | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
const stopwords = [ | ||
"a", | ||
"an", | ||
"the", | ||
"and", | ||
"but", | ||
"or", | ||
"for", | ||
"nor", | ||
"on", | ||
"at", | ||
"to", | ||
"from", | ||
"by", | ||
"in", | ||
"of", | ||
]; | ||
|
||
export const removeStopwords = (words) => { | ||
return words.filter((word) => !stopwords.includes(word)); | ||
}; |
Oops, something went wrong.