woat 6 年前
父节点
当前提交
373098c449
共有 59 个文件被更改,包括 1180 次插入67 次删除
  1. 18
    3
      client/src/app/app.module.ts
  2. 35
    0
      client/src/app/home/chart-display/chart-display.component.css
  3. 22
    0
      client/src/app/home/chart-display/chart-display.component.html
  4. 25
    0
      client/src/app/home/chart-display/chart-display.component.spec.ts
  5. 21
    0
      client/src/app/home/chart-display/chart-display.component.ts
  6. 54
    0
      client/src/app/home/home.component.css
  7. 32
    6
      client/src/app/home/home.component.html
  8. 8
    0
      client/src/app/home/main/main.component.css
  9. 27
    0
      client/src/app/home/main/main.component.html
  10. 25
    0
      client/src/app/home/main/main.component.spec.ts
  11. 18
    0
      client/src/app/home/main/main.component.ts
  12. 6
    0
      client/src/app/home/score/score.component.css
  13. 12
    0
      client/src/app/home/score/score.component.html
  14. 25
    0
      client/src/app/home/score/score.component.spec.ts
  15. 64
    0
      client/src/app/home/score/score.component.ts
  16. 4
    0
      client/src/app/home/top-articles/top-articles.component.css
  17. 12
    0
      client/src/app/home/top-articles/top-articles.component.html
  18. 25
    0
      client/src/app/home/top-articles/top-articles.component.spec.ts
  19. 15
    0
      client/src/app/home/top-articles/top-articles.component.ts
  20. 9
    0
      client/src/app/home/trends/trends.component.css
  21. 19
    0
      client/src/app/home/trends/trends.component.html
  22. 25
    0
      client/src/app/home/trends/trends.component.spec.ts
  23. 27
    0
      client/src/app/home/trends/trends.component.ts
  24. 36
    0
      client/src/app/news/article/article.component.css
  25. 2
    6
      client/src/app/news/article/article.component.html
  26. 0
    1
      client/src/app/news/news.component.html
  27. 30
    14
      client/src/app/news/news.component.ts
  28. 15
    0
      client/src/app/post.service.spec.ts
  29. 9
    0
      client/src/app/post.service.ts
  30. 6
    9
      client/src/app/search/search.component.html
  31. 2
    2
      client/src/app/search/search.component.ts
  32. 3
    8
      client/src/app/stocks.service.ts
  33. 0
    0
      client/src/app/stocks/chart/chart.component.css
  34. 1
    0
      client/src/app/stocks/chart/chart.component.html
  35. 25
    0
      client/src/app/stocks/chart/chart.component.spec.ts
  36. 136
    0
      client/src/app/stocks/chart/chart.component.ts
  37. 1
    3
      client/src/app/stocks/stocks.component.html
  38. 29
    7
      client/src/app/stocks/stocks.component.ts
  39. 1
    0
      client/src/index.html
  40. 13
    1
      client/src/styles.css
  41. 13
    0
      server/pom.xml
  42. 2
    0
      server/src/main/java/com/stockr/server/ServerApplication.java
  43. 3
    2
      server/src/main/java/com/stockr/server/news/Article.java
  44. 7
    2
      server/src/main/java/com/stockr/server/news/NewsController.java
  45. 2
    2
      server/src/main/java/com/stockr/server/news/NewsService.java
  46. 28
    0
      server/src/main/java/com/stockr/server/sentiment/Sentiment.java
  47. 90
    0
      server/src/main/java/com/stockr/server/sentiment/SentimentService.java
  48. 19
    0
      server/src/main/java/com/stockr/server/sentiment/SentimentThread.java
  49. 26
    0
      server/src/main/java/com/stockr/server/stocks/Gainer.java
  50. 7
    0
      server/src/main/java/com/stockr/server/stocks/StocksController.java
  51. 19
    0
      server/src/main/java/com/stockr/server/stocks/StocksService.java
  52. 9
    0
      server/src/test/java/com/stockr/server/news/ArticleTest.java
  53. 19
    0
      server/src/test/java/com/stockr/server/paralleldots/ParallelDotsApiTest.java
  54. 31
    0
      server/src/test/java/com/stockr/server/sentiment/SentimentServiceTest.java
  55. 22
    0
      server/src/test/java/com/stockr/server/sentiment/SentimentTest.java
  56. 29
    0
      server/src/test/java/com/stockr/server/stocks/GainerTest.java
  57. 9
    0
      server/src/test/java/com/stockr/server/stocks/StocksControllerTest.java
  58. 7
    0
      server/src/test/java/com/stockr/server/stocks/StocksServiceTest.java
  59. 1
    1
      server/src/test/java/com/stockr/server/stocks/StocksTest.java

+ 18
- 3
client/src/app/app.module.ts 查看文件

@@ -5,11 +5,20 @@ import { RouterModule, Routes } from '@angular/router';
5 5
 import { FormsModule } from '@angular/forms';
6 6
 
7 7
 import { AppComponent } from './app.component';
8
+import { HomeComponent } from './home/home.component';
9
+
10
+import { SearchComponent } from './search/search.component';
11
+
8 12
 import { NewsComponent } from './news/news.component';
9 13
 import { ArticleComponent } from './news/article/article.component';
10
-import { HomeComponent } from './home/home.component';
14
+
11 15
 import { StocksComponent } from './stocks/stocks.component';
12
-import { SearchComponent } from './search/search.component';
16
+import { ChartComponent } from './stocks/chart/chart.component';
17
+import { MainComponent } from './home/main/main.component';
18
+import { ChartDisplayComponent } from './home/chart-display/chart-display.component';
19
+import { ScoreComponent } from './home/score/score.component';
20
+import { TrendsComponent } from './home/trends/trends.component';
21
+import { TopArticlesComponent } from './home/top-articles/top-articles.component';
13 22
 
14 23
 const appRoutes: Routes = [
15 24
 	{ path: 'home', component: HomeComponent }
@@ -22,7 +31,13 @@ const appRoutes: Routes = [
22 31
 		HomeComponent,
23 32
 		StocksComponent,
24 33
 		ArticleComponent,
25
-		SearchComponent
34
+		SearchComponent,
35
+		ChartComponent,
36
+		MainComponent,
37
+		ChartDisplayComponent,
38
+		ScoreComponent,
39
+		TrendsComponent,
40
+		TopArticlesComponent
26 41
 	],
27 42
 	imports: [
28 43
 		BrowserModule,

+ 35
- 0
client/src/app/home/chart-display/chart-display.component.css 查看文件

@@ -0,0 +1,35 @@
1
+.card-content {
2
+	padding: 0;
3
+}
4
+
5
+.card-header-icon {
6
+	color: rgba(var(--pink), 1) !important;
7
+	transition: .2s ease;
8
+}
9
+
10
+.card-header-icon:hover {
11
+	color: rgba(var(--dark-pink), 1) !important;
12
+	transition: .2s ease;
13
+}
14
+
15
+.card-footer {
16
+	border: 0;
17
+}
18
+
19
+.card-footer-item {
20
+	font-size: 0.6em;
21
+	color: rgba(255, 255, 255, .3);
22
+	font-weight: 100;
23
+	border: 0;
24
+	border-top: 3px solid rgba(255, 255, 255, .1);
25
+}
26
+
27
+.card-footer-item:hover {
28
+	color: rgba(var(--yellow), 1);
29
+	border-top: 3px solid rgba(var(--yellow), 1);
30
+}
31
+
32
+.active {
33
+	color: rgba(var(--yellow), 1);
34
+	border-top: 3px solid rgba(var(--yellow), 1);
35
+}

+ 22
- 0
client/src/app/home/chart-display/chart-display.component.html 查看文件

@@ -0,0 +1,22 @@
1
+<div class="card">
2
+	<header class="card-header">
3
+		<p class="card-header-title">
4
+			Chart
5
+		</p>
6
+		<a [href]="company.website" *ngIf="company" class="card-header-icon">
7
+			{{ company.companyName }}
8
+		</a>
9
+	</header>
10
+	<div class="card-content">
11
+		<div class="content">
12
+			<app-stocks (sendCompany)="company= $event.company"></app-stocks>
13
+		</div>
14
+	</div>
15
+	<footer class="card-footer">
16
+		<a [class.active]="currentTimeFrame === 1" class="card-footer-item" (click)="selectTimeFrame(1)">1W</a>
17
+		<a [class.active]="currentTimeFrame === 2" class="card-footer-item" (click)="selectTimeFrame(2)">1M</a>
18
+		<a [class.active]="currentTimeFrame === 3" class="card-footer-item" (click)="selectTimeFrame(3)">3M</a>
19
+		<a [class.active]="currentTimeFrame === 4" class="card-footer-item" (click)="selectTimeFrame(4)">6M</a>
20
+		<a [class.active]="currentTimeFrame === 5" class="card-footer-item" (click)="selectTimeFrame(5)">1Y</a>
21
+	</footer>
22
+</div>

+ 25
- 0
client/src/app/home/chart-display/chart-display.component.spec.ts 查看文件

@@ -0,0 +1,25 @@
1
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+import { ChartDisplayComponent } from './chart-display.component';
4
+
5
+describe('ChartDisplayComponent', () => {
6
+  let component: ChartDisplayComponent;
7
+  let fixture: ComponentFixture<ChartDisplayComponent>;
8
+
9
+  beforeEach(async(() => {
10
+    TestBed.configureTestingModule({
11
+      declarations: [ ChartDisplayComponent ]
12
+    })
13
+    .compileComponents();
14
+  }));
15
+
16
+  beforeEach(() => {
17
+    fixture = TestBed.createComponent(ChartDisplayComponent);
18
+    component = fixture.componentInstance;
19
+    fixture.detectChanges();
20
+  });
21
+
22
+  it('should create', () => {
23
+    expect(component).toBeTruthy();
24
+  });
25
+});

+ 21
- 0
client/src/app/home/chart-display/chart-display.component.ts 查看文件

@@ -0,0 +1,21 @@
1
+import { Component, ViewChild, OnInit } from '@angular/core';
2
+
3
+@Component({
4
+	selector: 'app-chart-display',
5
+	templateUrl: './chart-display.component.html',
6
+	styleUrls: ['./chart-display.component.css', '../home.component.css']
7
+})
8
+export class ChartDisplayComponent implements OnInit {
9
+	company: any;
10
+	currentTimeFrame = 2;
11
+
12
+	constructor() { }
13
+
14
+	ngOnInit() {
15
+	}
16
+
17
+	selectTimeFrame = (x) => {
18
+		this.currentTimeFrame = x;
19
+		console.log(x);
20
+	}
21
+}

+ 54
- 0
client/src/app/home/home.component.css 查看文件

@@ -0,0 +1,54 @@
1
+.home__section {
2
+	background-color: rgba(var(--dark-blue), 1);
3
+}
4
+
5
+.columns.columns__charts {
6
+	display: flex;
7
+}
8
+
9
+.card {
10
+	background-color: rgba(255, 255, 255, .05);
11
+	display: flex;
12
+	flex-direction: column;
13
+	justify-content: space-around;
14
+	height: 100%;
15
+}
16
+
17
+.card-header {
18
+	background-color: rgba(255, 255, 255, .05);
19
+}
20
+
21
+.card-header-title {
22
+	color: rgba(255, 255, 255, .6);
23
+	font-weight: 300 !important;
24
+}
25
+
26
+.card-footer {
27
+	margin-top: auto;
28
+}
29
+
30
+.text__body--color {
31
+	color: rgba(255, 255, 255, .5);
32
+}
33
+
34
+::placeholder {
35
+	color: rgba(255, 255, 255, .4);
36
+}
37
+
38
+.input {
39
+	border-color: rgba(var(--dark-blue), 1);
40
+	background-color: rgba(255, 255, 255, .1);
41
+	color: rgba(255, 255, 255, .5);
42
+}
43
+
44
+.button {
45
+	border-color: rgba(var(--dark-blue), 1);
46
+	background-color: rgba(255, 255, 255, .1);
47
+	color: rgba(255, 255, 255, .5);
48
+	transition: .2s ease;
49
+}
50
+
51
+.button:hover {
52
+	background-color: rgba(255, 255, 255, .15);
53
+	color: rgba(255, 255, 255, .5);
54
+}

+ 32
- 6
client/src/app/home/home.component.html 查看文件

@@ -1,7 +1,33 @@
1
-<p>
2
-  home works!
3
-</p>
1
+<section class="hero is-fullheight home__section">
2
+	<div class="hero-head">
3
+		
4
+	</div>
5
+	<div class="hero-body">
6
+		<div class="container">
4 7
 
5
-<app-search></app-search>
6
-<app-news (searchEvent)="app-search.test()"></app-news>
7
-<app-stocks></app-stocks>
8
+			<div class="columns">
9
+				<div class="column">
10
+					<app-main></app-main>
11
+				</div>
12
+				<div class="column is-3">
13
+					<app-trends></app-trends>
14
+				</div>
15
+			</div>
16
+
17
+			<div class="columns columns__charts">
18
+				<div class="column">
19
+					<app-chart-display></app-chart-display>
20
+				</div>
21
+				<div class="column is-4">
22
+					<app-score></app-score>
23
+				</div>
24
+			</div>
25
+
26
+			<app-top-articles></app-top-articles>
27
+
28
+		</div>
29
+	</div>	
30
+	<div class="hero-foot">
31
+
32
+	</div>
33
+</section>

+ 8
- 0
client/src/app/home/main/main.component.css 查看文件

@@ -0,0 +1,8 @@
1
+p {
2
+	margin-bottom: 0 !important;
3
+}
4
+
5
+.sub__text {
6
+	font-size: 0.7em;
7
+	color: rgba(255, 255, 255, 0.3) !important;
8
+}

+ 27
- 0
client/src/app/home/main/main.component.html 查看文件

@@ -0,0 +1,27 @@
1
+<div class="card">
2
+	<header class="card-header">
3
+		<p class="card-header-title">
4
+			Welcome 
5
+		</p>
6
+	</header>
7
+
8
+	<div class="card-content">
9
+		<div class="content">
10
+
11
+			<nav class="level">
12
+				<div class="level-item">
13
+					<div>
14
+						<p class="text__body--color">Hello [name],</p>
15
+						<p class="sub__text text__body--color">Today is {{ todaysDate }}</p>
16
+					</div>
17
+				</div>
18
+				<div class="level-item has-text-centered">
19
+					<div>
20
+						<app-search></app-search>
21
+					</div>
22
+				</div>
23
+			</nav>
24
+
25
+		</div>
26
+	</div>
27
+</div>

+ 25
- 0
client/src/app/home/main/main.component.spec.ts 查看文件

@@ -0,0 +1,25 @@
1
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+import { MainComponent } from './main.component';
4
+
5
+describe('MainComponent', () => {
6
+  let component: MainComponent;
7
+  let fixture: ComponentFixture<MainComponent>;
8
+
9
+  beforeEach(async(() => {
10
+    TestBed.configureTestingModule({
11
+      declarations: [ MainComponent ]
12
+    })
13
+    .compileComponents();
14
+  }));
15
+
16
+  beforeEach(() => {
17
+    fixture = TestBed.createComponent(MainComponent);
18
+    component = fixture.componentInstance;
19
+    fixture.detectChanges();
20
+  });
21
+
22
+  it('should create', () => {
23
+    expect(component).toBeTruthy();
24
+  });
25
+});

+ 18
- 0
client/src/app/home/main/main.component.ts 查看文件

@@ -0,0 +1,18 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import * as moment from 'moment';
3
+
4
+@Component({
5
+	selector: 'app-main',
6
+	templateUrl: './main.component.html',
7
+	styleUrls: ['./main.component.css', '../home.component.css']
8
+})
9
+export class MainComponent implements OnInit {
10
+	todaysDate: any;
11
+
12
+	constructor() { }
13
+
14
+	ngOnInit() {
15
+		this.todaysDate = moment().format('DD MMMM, YYYY.');
16
+	}
17
+
18
+}

+ 6
- 0
client/src/app/home/score/score.component.css 查看文件

@@ -0,0 +1,6 @@
1
+.card-content {
2
+}
3
+
4
+.content {
5
+	height: inherit;
6
+}

+ 12
- 0
client/src/app/home/score/score.component.html 查看文件

@@ -0,0 +1,12 @@
1
+<div class="card">
2
+	<div class="card-header">
3
+		<p class="card-header-title">
4
+			Sentiment Score
5
+		</p>
6
+	</div>
7
+	<div class="card-content">
8
+		<div class="content">
9
+			<div id="donut_chart" style="width: inherit; height: inherit"></div>
10
+		</div>
11
+	</div>
12
+</div>

+ 25
- 0
client/src/app/home/score/score.component.spec.ts 查看文件

@@ -0,0 +1,25 @@
1
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+import { ScoreComponent } from './score.component';
4
+
5
+describe('ScoreComponent', () => {
6
+  let component: ScoreComponent;
7
+  let fixture: ComponentFixture<ScoreComponent>;
8
+
9
+  beforeEach(async(() => {
10
+    TestBed.configureTestingModule({
11
+      declarations: [ ScoreComponent ]
12
+    })
13
+    .compileComponents();
14
+  }));
15
+
16
+  beforeEach(() => {
17
+    fixture = TestBed.createComponent(ScoreComponent);
18
+    component = fixture.componentInstance;
19
+    fixture.detectChanges();
20
+  });
21
+
22
+  it('should create', () => {
23
+    expect(component).toBeTruthy();
24
+  });
25
+});

+ 64
- 0
client/src/app/home/score/score.component.ts 查看文件

@@ -0,0 +1,64 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+declare const google;
4
+
5
+@Component({
6
+	selector: 'app-score',
7
+	templateUrl: './score.component.html',
8
+	styleUrls: ['./score.component.css', '../home.component.css']
9
+})
10
+
11
+export class ScoreComponent implements OnInit {
12
+
13
+	constructor() { }
14
+
15
+	ngOnInit() {
16
+		google.charts.load('current', {packages: ['corechart']});
17
+		google.charts.setOnLoadCallback(() => {
18
+			this.drawChart();
19
+		});
20
+	}
21
+
22
+	drawChart() {
23
+		const data = google.visualization.arrayToDataTable([
24
+			['Emotion', 'Percentage'],
25
+			['Positive', 66],
26
+			['Neutral', 20],
27
+			['Negative', 14]
28
+		]);
29
+
30
+		const options = {
31
+			legend: {
32
+				position: 'bottom',
33
+				textStyle: {
34
+					color: '#a8a9b2',
35
+					fontName: 'inherit',
36
+					fontSize: 12
37
+				}
38
+			},
39
+			pieHole: 0.9,
40
+			pieSliceBorderColor: 'transparent',
41
+			pieSliceText: 'none',
42
+			slices: {
43
+				2: {
44
+					textStyle: {
45
+						color: 'black'
46
+					}
47
+				}
48
+			},
49
+			reverseCategories: true,
50
+			chartArea: {
51
+				left: 0,
52
+				top: 0,
53
+				width: '100%',
54
+				height: '90%',
55
+			},
56
+			colors: ['#7630C9', '#FFBF8D', '#CE74C6'],
57
+			backgroundColor: 'transparent',
58
+		};
59
+
60
+		const chart = new google.visualization.PieChart(document.getElementById('donut_chart'));
61
+		chart.draw(data, options);
62
+	}
63
+
64
+}

+ 4
- 0
client/src/app/home/top-articles/top-articles.component.css 查看文件

@@ -0,0 +1,4 @@
1
+.card-content {
2
+	padding: 0;
3
+}
4
+

+ 12
- 0
client/src/app/home/top-articles/top-articles.component.html 查看文件

@@ -0,0 +1,12 @@
1
+<div class="card">
2
+	<div class="card-header">
3
+		<p class="card-header-title">
4
+			Top Articles
5
+		</p>
6
+	</div>
7
+	<div class="card-content">
8
+		<div class="content">
9
+			<app-news></app-news>
10
+		</div>
11
+	</div>
12
+</div>

+ 25
- 0
client/src/app/home/top-articles/top-articles.component.spec.ts 查看文件

@@ -0,0 +1,25 @@
1
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+import { TopArticlesComponent } from './top-articles.component';
4
+
5
+describe('TopArticlesComponent', () => {
6
+  let component: TopArticlesComponent;
7
+  let fixture: ComponentFixture<TopArticlesComponent>;
8
+
9
+  beforeEach(async(() => {
10
+    TestBed.configureTestingModule({
11
+      declarations: [ TopArticlesComponent ]
12
+    })
13
+    .compileComponents();
14
+  }));
15
+
16
+  beforeEach(() => {
17
+    fixture = TestBed.createComponent(TopArticlesComponent);
18
+    component = fixture.componentInstance;
19
+    fixture.detectChanges();
20
+  });
21
+
22
+  it('should create', () => {
23
+    expect(component).toBeTruthy();
24
+  });
25
+});

+ 15
- 0
client/src/app/home/top-articles/top-articles.component.ts 查看文件

@@ -0,0 +1,15 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+@Component({
4
+  selector: 'app-top-articles',
5
+  templateUrl: './top-articles.component.html',
6
+  styleUrls: ['./top-articles.component.css', '../home.component.css']
7
+})
8
+export class TopArticlesComponent implements OnInit {
9
+
10
+  constructor() { }
11
+
12
+  ngOnInit() {
13
+  }
14
+
15
+}

+ 9
- 0
client/src/app/home/trends/trends.component.css 查看文件

@@ -0,0 +1,9 @@
1
+.card-content {
2
+	padding: 0;
3
+	height: 100px !important;
4
+	overflow: auto;
5
+}
6
+
7
+.is-pulled-right {
8
+	color: rgba(var(--yellow), 1) !important;
9
+}

+ 19
- 0
client/src/app/home/trends/trends.component.html 查看文件

@@ -0,0 +1,19 @@
1
+<div class="card">
2
+	<div class="card-header">
3
+		<p class="card-header-title">
4
+			Trends
5
+		</p>
6
+	</div>
7
+	<div class="card-content">
8
+		<div class="content">
9
+			<table class="table is-fullwidth">
10
+				<tbody>
11
+					<tr *ngFor="let gainer of gainers">
12
+						<td>{{ gainer.symbol }}</td>
13
+						<td class="is-pulled-right">{{ parsePercent(gainer.changePercent) }}</td>
14
+					</tr>
15
+				</tbody>
16
+			</table>
17
+		</div>
18
+	</div>
19
+</div>

+ 25
- 0
client/src/app/home/trends/trends.component.spec.ts 查看文件

@@ -0,0 +1,25 @@
1
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+import { TrendsComponent } from './trends.component';
4
+
5
+describe('TrendsComponent', () => {
6
+  let component: TrendsComponent;
7
+  let fixture: ComponentFixture<TrendsComponent>;
8
+
9
+  beforeEach(async(() => {
10
+    TestBed.configureTestingModule({
11
+      declarations: [ TrendsComponent ]
12
+    })
13
+    .compileComponents();
14
+  }));
15
+
16
+  beforeEach(() => {
17
+    fixture = TestBed.createComponent(TrendsComponent);
18
+    component = fixture.componentInstance;
19
+    fixture.detectChanges();
20
+  });
21
+
22
+  it('should create', () => {
23
+    expect(component).toBeTruthy();
24
+  });
25
+});

+ 27
- 0
client/src/app/home/trends/trends.component.ts 查看文件

@@ -0,0 +1,27 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { StocksService } from '../../stocks.service';
3
+
4
+@Component({
5
+	selector: 'app-trends',
6
+	templateUrl: './trends.component.html',
7
+	styleUrls: ['./trends.component.css',
8
+		'../home.component.css',
9
+		'../../news/article/article.component.css']
10
+})
11
+
12
+export class TrendsComponent implements OnInit {
13
+	gainers: any;
14
+
15
+	constructor(private stocksService: StocksService) { }
16
+
17
+	ngOnInit() {
18
+		this.stocksService.getGainers()
19
+			.subscribe(gainers => {
20
+				this.gainers = gainers;
21
+			});
22
+	}
23
+
24
+	parsePercent(percent) {
25
+		return Math.floor(percent * 100) + '%';
26
+	}
27
+}

+ 36
- 0
client/src/app/news/article/article.component.css 查看文件

@@ -0,0 +1,36 @@
1
+.table {
2
+	background-color: transparent;
3
+}
4
+td {
5
+	color: black;
6
+}
7
+
8
+th {
9
+	border: 0 !important;
10
+	font-size: .8em;
11
+	font-weight: 300;
12
+	color: rgba(255, 255, 255, .3) !important;
13
+}
14
+
15
+td {
16
+	border: 0 !important;
17
+	font-size: .8em;
18
+	font-weight: 300;
19
+	color: rgba(255, 255, 255, .5) !important;
20
+}
21
+
22
+tr {
23
+	transition: .2s ease;
24
+}
25
+
26
+td > a {
27
+	color: rgba(var(--pink), 1) !important;
28
+}
29
+
30
+td > a:hover {
31
+	color: rgba(var(--dark-pink), 1) !important;
32
+}
33
+
34
+tr:hover {
35
+	background-color: rgba(255, 255, 255, .05);
36
+}

+ 2
- 6
client/src/app/news/article/article.component.html 查看文件

@@ -4,7 +4,7 @@
4 4
 			<th>Source Name</th>
5 5
 			<th>Title</th>
6 6
 			<th>Published</th>
7
-			<th>Img</th>
7
+			<th>Score</th>
8 8
 		</tr>
9 9
 	</thead>
10 10
 	<tbody>
@@ -12,11 +12,7 @@
12 12
 			<td>{{ article.sourceName }}</td>
13 13
 			<td><a [href]="article.url">{{ article.title }}</a></td>
14 14
 			<td>{{ computeDate(article.publishedAt) }}</td>
15
-			<td>
16
-				<figure class="image is-128x128">
17
-					<img [src]="article.urlToImage" />
18
-				</figure>
19
-			</td>
15
+			<td>[score]</td>
20 16
 		</tr>
21 17
 	</tbody>
22 18
 </table>

+ 0
- 1
client/src/app/news/news.component.html 查看文件

@@ -1,2 +1 @@
1
-<p>news works!</p>
2 1
 <app-article *ngIf="news" [articles]="news"></app-article>

+ 30
- 14
client/src/app/news/news.component.ts 查看文件

@@ -1,5 +1,6 @@
1
-import { Component, Input, OnInit } from '@angular/core';
1
+import { Component, OnInit } from '@angular/core';
2 2
 import { NewsService } from '../news.service';
3
+import { SearchService } from '../search.service';
3 4
 
4 5
 @Component({
5 6
 	selector: 'app-news',
@@ -8,24 +9,39 @@ import { NewsService } from '../news.service';
8 9
 })
9 10
 
10 11
 export class NewsComponent implements OnInit {
11
-	query: string;
12 12
 	news: any;
13 13
 
14
-	constructor(private newsService: NewsService) { }
14
+	constructor(
15
+		private newsService: NewsService,
16
+		private searchService: SearchService
17
+	) { }
15 18
 
16 19
 	ngOnInit() {
20
+		this.news = [
21
+			{
22
+			sourceName: 'CNBC',
23
+			url: '#',
24
+			title: 'Facebook is suggesting security app that tracks mobile usage.',
25
+			publishedAt: '12 February 2018',
26
+			urlToImage: '#'
27
+			},
28
+			{
29
+			sourceName: 'CNBC',
30
+			url: '#',
31
+			title: 'Facebook is suggesting security app that tracks mobile usage.',
32
+			publishedAt: '12 February 2018',
33
+			urlToImage: '#'
34
+			}
35
+		];
36
+		this.searchService.currentQuery.subscribe(query => {
37
+			if (query) {
38
+				this.getNews(query);
39
+			}
40
+		});
17 41
 	}
18 42
 
19
-	getNews() {
20
-		console.log(this.news);
21
-		this.newsService.getNews(this.query)
22
-			.subscribe(articles => {
23
-				this.news = articles;
24
-			});
43
+	getNews(query) {
44
+		this.newsService.getNews(query)
45
+			.subscribe(articles => this.news = articles);
25 46
 	}
26
-
27
-	displayNews() {
28
-
29
-	}
30
-
31 47
 }

+ 15
- 0
client/src/app/post.service.spec.ts 查看文件

@@ -0,0 +1,15 @@
1
+import { TestBed, inject } from '@angular/core/testing';
2
+
3
+import { PostService } from './post.service';
4
+
5
+describe('PostService', () => {
6
+  beforeEach(() => {
7
+    TestBed.configureTestingModule({
8
+      providers: [PostService]
9
+    });
10
+  });
11
+
12
+  it('should be created', inject([PostService], (service: PostService) => {
13
+    expect(service).toBeTruthy();
14
+  }));
15
+});

+ 9
- 0
client/src/app/post.service.ts 查看文件

@@ -0,0 +1,9 @@
1
+import { Injectable } from '@angular/core';
2
+
3
+@Injectable({
4
+  providedIn: 'root'
5
+})
6
+export class PostService {
7
+
8
+  constructor() { }
9
+}

+ 6
- 9
client/src/app/search/search.component.html 查看文件

@@ -1,11 +1,8 @@
1
-<div class="columns">
2
-	<div class="column is-offset-4 is-4">
3
-		<div class="field">
4
-			<label>Ticker</label>
5
-			<div class="control">
6
-				<input [(ngModel)]="query" class="input" type="text" placeholder="Enter ticker" />
7
-			</div>
8
-			<button class="button" (click)="submit()">submit</button>
9
-		</div>
1
+<div class="field has-addons">
2
+	<div class="control">
3
+		<input [(ngModel)]="query" class="input" type="text" placeholder="Enter ticker" />
4
+	</div>
5
+	<div class="control">
6
+		<button class="button" (click)="submit()">Search</button>
10 7
 	</div>
11 8
 </div>

+ 2
- 2
client/src/app/search/search.component.ts 查看文件

@@ -5,7 +5,7 @@ import { SearchService } from '../search.service';
5 5
 @Component({
6 6
 	selector: 'app-search',
7 7
 	templateUrl: './search.component.html',
8
-	styleUrls: ['./search.component.css']
8
+	styleUrls: ['./search.component.css', '../home/home.component.css']
9 9
 })
10 10
 
11 11
 export class SearchComponent implements OnInit {
@@ -19,6 +19,6 @@ export class SearchComponent implements OnInit {
19 19
 	}
20 20
 
21 21
 	submit() {
22
-		this.searchService.setQuery(this.query);
22
+		this.searchService.changeQuery(this.query);
23 23
 	}
24 24
 }

+ 3
- 8
client/src/app/stocks.service.ts 查看文件

@@ -1,24 +1,19 @@
1 1
 import { Injectable } from '@angular/core';
2 2
 import { HttpClient } from '@angular/common/http';
3 3
 
4
-import { SearchService } from './search.service';
5
-
6 4
 @Injectable({
7 5
 	providedIn: 'root'
8 6
 })
9 7
 
10 8
 export class StocksService {
11 9
 
12
-	constructor(
13
-		private http: HttpClient,
14
-		private searchService: SearchService
15
-	) { }
10
+	constructor(private http: HttpClient) { }
16 11
 
17 12
 	getStocks(ticker) {
18 13
 		return this.http.get(`/api/stocks?ticker=${ticker}`);
19 14
 	}
20 15
 
21
-	getSearch() {
22
-		console.log(this.searchService);
16
+	getGainers() {
17
+		return this.http.get(`/api/stocks/gainers`);
23 18
 	}
24 19
 }

+ 0
- 0
client/src/app/stocks/chart/chart.component.css 查看文件


+ 1
- 0
client/src/app/stocks/chart/chart.component.html 查看文件

@@ -0,0 +1 @@
1
+<div id="chart_div" style="width: inherit; height: inherit"></div>

+ 25
- 0
client/src/app/stocks/chart/chart.component.spec.ts 查看文件

@@ -0,0 +1,25 @@
1
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+import { ChartComponent } from './chart.component';
4
+
5
+describe('ChartComponent', () => {
6
+  let component: ChartComponent;
7
+  let fixture: ComponentFixture<ChartComponent>;
8
+
9
+  beforeEach(async(() => {
10
+    TestBed.configureTestingModule({
11
+      declarations: [ ChartComponent ]
12
+    })
13
+    .compileComponents();
14
+  }));
15
+
16
+  beforeEach(() => {
17
+    fixture = TestBed.createComponent(ChartComponent);
18
+    component = fixture.componentInstance;
19
+    fixture.detectChanges();
20
+  });
21
+
22
+  it('should create', () => {
23
+    expect(component).toBeTruthy();
24
+  });
25
+});

+ 136
- 0
client/src/app/stocks/chart/chart.component.ts 查看文件

@@ -0,0 +1,136 @@
1
+import { Component, Input, OnInit } from '@angular/core';
2
+import * as moment from 'moment';
3
+
4
+declare const google;
5
+
6
+@Component({
7
+	selector: 'app-chart',
8
+	templateUrl: './chart.component.html',
9
+	styleUrls: ['./chart.component.css']
10
+})
11
+
12
+export class ChartComponent implements OnInit {
13
+	@Input()stock;
14
+
15
+	constructor() { }
16
+
17
+	ngOnInit() {
18
+		google.charts.load('current', {'packages': ['corechart', 'line']});
19
+
20
+		google.charts.setOnLoadCallback(() => {
21
+			const data = this.stock ? this.initChartData() : this.initNullChart();
22
+			this.drawChart(data);
23
+		});
24
+	}
25
+
26
+	initChartData() {
27
+		const chartData = this.stock.chart.reduce((a, price) => {
28
+			a.push([this.parseDate(price.date), price.close]);
29
+			return a;
30
+		}, []);
31
+
32
+		return google.visualization.arrayToDataTable([
33
+			['Date', 'Close'],
34
+			...chartData
35
+		]);
36
+	}
37
+
38
+	parseDate = (date) => moment(date).format('MMM DD');
39
+
40
+	drawChart(data) {
41
+		const options = {
42
+			lineWidth: 5,
43
+			baselineColor: 'transparent',
44
+			focusTarget: 'category',
45
+			hAxis: {
46
+				showTextEvery: 5,
47
+				gridlines: {
48
+					color: '#333',
49
+					count: 4
50
+				},
51
+				minorGridlines: {
52
+					color: '#FFFFFF',
53
+					count: 4
54
+				}
55
+			},
56
+			legend: 'none',
57
+			chartArea: {
58
+				left: 0,
59
+				top: 0,
60
+				width: '100%',
61
+				height: '100%',
62
+			},
63
+			backgroundColor: 'transparent',
64
+			vAxis: {
65
+				gridlines: {
66
+					color: 'transparent'
67
+				}
68
+			},
69
+			curveType: 'function'
70
+		};
71
+
72
+		const chart = new google.visualization.LineChart(document.getElementById('chart_div'));
73
+
74
+		google.visualization.events.addOneTimeListener(chart, 'ready', () => {
75
+			this.addChartGradient(chart);
76
+		});
77
+
78
+		chart.draw(data, options);
79
+	}
80
+
81
+
82
+	addChartGradient(chart) {
83
+		const chartDiv = chart.getContainer();
84
+		const svg = chartDiv.getElementsByTagName('svg')[0];
85
+		const properties = {
86
+			id: 'chartGradient',
87
+			x1: '0%',
88
+			y1: '0%',
89
+			x2: '0%',
90
+			y2: '100%',
91
+			stops: [
92
+				{ offset: '5%', 'stop-color': '#FFBF8D' },
93
+				{ offset: '95%', 'stop-color': '#CE74C6' }
94
+			]
95
+		};
96
+
97
+
98
+		this.createGradient(svg, properties);
99
+		const chartPath = svg.getElementsByTagName('path')[0];
100
+		chartPath.setAttribute('stroke', 'url(#chartGradient)');
101
+	}
102
+
103
+
104
+	createGradient(svg, properties) {
105
+		const svgNS = svg.namespaceURI;
106
+		const grad = document.createElementNS(svgNS, 'linearGradient');
107
+		grad.setAttribute('id', properties.id);
108
+		['x1', 'y1', 'x2', 'y2'].forEach(function(name) {
109
+			if (properties.hasOwnProperty(name)) {
110
+				grad.setAttribute(name, properties[name]);
111
+			}
112
+		});
113
+		for (let i = 0; i < properties.stops.length; i++) {
114
+			const attrs = properties.stops[i];
115
+			const stop = document.createElementNS(svgNS, 'stop');
116
+			for (const attr in attrs) {
117
+				if (attrs.hasOwnProperty(attr)) {
118
+					stop.setAttribute(attr, attrs[attr]);
119
+				}
120
+			}
121
+			grad.appendChild(stop);
122
+		}
123
+
124
+		const defs = svg.querySelector('defs') ||
125
+			svg.insertBefore(document.createElementNS(svgNS, 'defs'), svg.firstChild);
126
+		return defs.appendChild(grad);
127
+	}
128
+
129
+	initNullChart() {
130
+		return google.visualization.arrayToDataTable([
131
+			['Date', 'Close'],
132
+			['0', 1],
133
+			['1', 1],
134
+		]);
135
+	}
136
+}

+ 1
- 3
client/src/app/stocks/stocks.component.html 查看文件

@@ -1,3 +1 @@
1
-<p>
2
-  stocks works!
3
-</p>
1
+<app-chart *ngIf="stockData" [stock]="stockData"></app-chart>

+ 29
- 7
client/src/app/stocks/stocks.component.ts 查看文件

@@ -1,15 +1,37 @@
1
-import { Component, OnInit } from '@angular/core';
1
+import { Component, EventEmitter, OnInit, Output } from '@angular/core';
2
+import { StocksService } from '../stocks.service';
3
+import { SearchService } from '../search.service';
2 4
 
3 5
 @Component({
4
-  selector: 'app-stocks',
5
-  templateUrl: './stocks.component.html',
6
-  styleUrls: ['./stocks.component.css']
6
+	selector: 'app-stocks',
7
+	templateUrl: './stocks.component.html',
8
+	styleUrls: ['./stocks.component.css']
7 9
 })
10
+
8 11
 export class StocksComponent implements OnInit {
12
+	@Output() sendCompany = new EventEmitter();
13
+	stockData: any;
9 14
 
10
-  constructor() { }
15
+	constructor(
16
+		private stocksService: StocksService,
17
+		private searchService: SearchService
18
+	) { }
11 19
 
12
-  ngOnInit() {
13
-  }
20
+	ngOnInit() {
21
+		this.searchService.currentQuery
22
+			.subscribe(query => {
23
+				if (query) {
24
+					this.stockData = null;
25
+					this.getStocks(query);
26
+				}
27
+			});
28
+	}
14 29
 
30
+	getStocks(ticker) {
31
+		this.stocksService.getStocks(ticker)
32
+			.subscribe(stock => {
33
+				this.stockData = stock;
34
+					this.sendCompany.emit(this.stockData);
35
+			});
36
+	}
15 37
 }

+ 1
- 0
client/src/index.html 查看文件

@@ -8,6 +8,7 @@
8 8
   <meta name="viewport" content="width=device-width, initial-scale=1">
9 9
   <link rel="icon" type="image/x-icon" href="favicon.ico">
10 10
 	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.css" />
11
+	<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
11 12
 </head>
12 13
 <body>
13 14
   <app-root></app-root>

+ 13
- 1
client/src/styles.css 查看文件

@@ -1 +1,13 @@
1
-/* You can add global styles to this file, and also import other style files */
1
+:root {
2
+	--dark-blue: 51, 55, 75;
3
+
4
+	--dark-purple: 148, 65, 161;
5
+	--light-purple: 206, 116, 198;
6
+	--purple: 118, 48, 201;
7
+
8
+	--yellow: 255, 191, 141;
9
+
10
+	--dark-pink: 236, 120, 155;
11
+	--pink: 252, 167, 146;
12
+}
13
+

+ 13
- 0
server/pom.xml 查看文件

@@ -24,6 +24,13 @@
24 24
 		<java.version>1.8</java.version>
25 25
 	</properties>
26 26
 
27
+	<repositories>
28
+		<repository>
29
+			<id>jitpack.io</id>
30
+			<url>https://jitpack.io</url>
31
+		</repository>
32
+	</repositories>
33
+
27 34
 	<dependencies>
28 35
 		<dependency>
29 36
 			<groupId>org.springframework.boot</groupId>
@@ -70,6 +77,12 @@
70 77
 			<artifactId>spring-boot-devtools</artifactId>
71 78
 			<version>2.0.3.RELEASE</version>
72 79
 		</dependency>
80
+
81
+		<dependency>
82
+			<groupId>com.github.ParallelDots</groupId>
83
+			<artifactId>ParallelDots-Java-API</artifactId>
84
+			<version>master</version>
85
+		</dependency>
73 86
 	</dependencies>
74 87
 
75 88
 	<build>

+ 2
- 0
server/src/main/java/com/stockr/server/ServerApplication.java 查看文件

@@ -2,8 +2,10 @@ package com.stockr.server;
2 2
 
3 3
 import org.springframework.boot.SpringApplication;
4 4
 import org.springframework.boot.autoconfigure.SpringBootApplication;
5
+import org.springframework.scheduling.annotation.EnableAsync;
5 6
 
6 7
 @SpringBootApplication
8
+@EnableAsync
7 9
 public class ServerApplication {
8 10
 
9 11
 	public static void main(String[] args) {

+ 3
- 2
server/src/main/java/com/stockr/server/news/Article.java 查看文件

@@ -1,21 +1,22 @@
1 1
 package com.stockr.server.news;
2 2
 
3 3
 import com.fasterxml.jackson.annotation.JsonProperty;
4
+import com.stockr.server.sentiment.Sentiment;
4 5
 import lombok.Getter;
5 6
 import lombok.Setter;
6 7
 
7 8
 import java.util.Map;
8 9
 
9 10
 @Getter @Setter
10
-class Article {
11
+public class Article {
11 12
     private String sourceId;
12 13
     private String sourceName;
13 14
     private String author;
14 15
     private String title;
15 16
     private String description;
16 17
     private String url;
17
-    private String urlToImage;
18 18
     private String publishedAt;
19
+    private Sentiment sentiment;
19 20
 
20 21
     @SuppressWarnings("unused")
21 22
     @JsonProperty("source")

+ 7
- 2
server/src/main/java/com/stockr/server/news/NewsController.java 查看文件

@@ -1,5 +1,6 @@
1 1
 package com.stockr.server.news;
2 2
 
3
+import com.stockr.server.sentiment.SentimentService;
3 4
 import org.springframework.beans.factory.annotation.Autowired;
4 5
 import org.springframework.web.bind.annotation.*;
5 6
 
@@ -11,8 +12,12 @@ public class NewsController {
11 12
     @Autowired
12 13
     NewsService ns;
13 14
 
15
+    SentimentService ss = new SentimentService();
16
+
14 17
     @GetMapping// root
15
-    public ArrayList<Article> getAllArticles(@RequestParam("query") String query){
16
-        return ns.getAllArticles(query);
18
+    public ArrayList<Article> getAllArticles(@RequestParam("query") String query) throws Exception {
19
+        ArrayList<Article> articles = ns.getAllArticles(query);
20
+        ArrayList<Article> sentimentArticles = ss.getAllSentiments(articles);
21
+        return sentimentArticles;
17 22
     }
18 23
 }

+ 2
- 2
server/src/main/java/com/stockr/server/news/NewsService.java 查看文件

@@ -41,7 +41,7 @@ public class NewsService {
41 41
         return n;
42 42
     }
43 43
 
44
-    ArrayList<Article> getAllArticles(String query) {
44
+    public ArrayList<Article> getAllArticles(String query) {
45 45
         return getNews(query)
46 46
                 .getArticles();
47 47
     }
@@ -50,6 +50,6 @@ public class NewsService {
50 50
         return  "https://newsapi.org/v2/everything?q=" + query +
51 51
                 "&language=en" +
52 52
                 "&sources=bloomberg,business-insider,cnbc,the-wall-street-journal" +
53
-                "&pageSize=100";
53
+                "&pageSize=25";
54 54
     }
55 55
 }

+ 28
- 0
server/src/main/java/com/stockr/server/sentiment/Sentiment.java 查看文件

@@ -0,0 +1,28 @@
1
+package com.stockr.server.sentiment;
2
+
3
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4
+import com.fasterxml.jackson.annotation.JsonProperty;
5
+import lombok.Getter;
6
+import lombok.NoArgsConstructor;
7
+import lombok.Setter;
8
+
9
+import java.util.Map;
10
+
11
+@NoArgsConstructor
12
+@Getter @Setter
13
+@JsonIgnoreProperties(ignoreUnknown = true)
14
+public class Sentiment {
15
+    @JsonProperty("sentiment")
16
+    String likely;
17
+    double positive;
18
+    double neutral;
19
+    double negative;
20
+
21
+    @SuppressWarnings("unused")
22
+    @JsonProperty("probabilities")
23
+    private void unpackSource(Map<String, Object> source) {
24
+        this.positive = (double)source.get("positive");
25
+        this.neutral = (double)source.get("neutral");
26
+        this.negative = (double)source.get("negative");
27
+    }
28
+}

+ 90
- 0
server/src/main/java/com/stockr/server/sentiment/SentimentService.java 查看文件

@@ -0,0 +1,90 @@
1
+package com.stockr.server.sentiment;
2
+
3
+import com.fasterxml.jackson.databind.ObjectMapper;
4
+import com.paralleldots.paralleldots.App;
5
+import com.stockr.server.news.Article;
6
+import org.springframework.beans.factory.annotation.Autowired;
7
+import org.springframework.context.annotation.Bean;
8
+import org.springframework.core.task.TaskExecutor;
9
+import org.springframework.scheduling.annotation.Async;
10
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
11
+import org.springframework.stereotype.Service;
12
+
13
+import java.io.IOException;
14
+import java.lang.reflect.Array;
15
+import java.util.*;
16
+import java.util.concurrent.CompletableFuture;
17
+import java.util.concurrent.Future;
18
+import java.util.concurrent.PriorityBlockingQueue;
19
+import java.util.stream.Collectors;
20
+
21
+@Service
22
+public class SentimentService {
23
+    App pd = new App("ksfXrGlQnbn5jrJQBiGvxS0gdFVI8oHm6S3ZAxGz9EM");
24
+    ObjectMapper om = new ObjectMapper();
25
+
26
+    public Sentiment getSentiment(String text) throws Exception {
27
+        String json = pd.sentiment(text);
28
+        Sentiment sentiment = om.readValue(json, Sentiment.class);
29
+        return sentiment;
30
+    }
31
+
32
+    public ArrayList<Article> getAllSentiments(ArrayList<Article> articles) throws Exception {
33
+        ArrayList<Article> sentimentArticles = new ArrayList<>();
34
+        for (Article article : articles) {
35
+            Sentiment sentiment = getSentiment(article.getDescription());
36
+            article.setSentiment(sentiment);
37
+            sentimentArticles.add(article);
38
+        }
39
+        return sentimentArticles;
40
+    }
41
+
42
+    @Bean
43
+    public TaskExecutor exec() {
44
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
45
+        executor.setCorePoolSize(10);
46
+        executor.setMaxPoolSize(10);
47
+        executor.setQueueCapacity(500);
48
+        executor.setThreadNamePrefix("GithubLookup-");
49
+        executor.initialize();
50
+        return executor;
51
+    }
52
+
53
+    @Async(value = "exec")
54
+    public CompletableFuture<Sentiment> gs(String text) {
55
+        return CompletableFuture.supplyAsync(() -> {
56
+            String json = null;
57
+            try {
58
+                json = pd.sentiment(text);
59
+            } catch (Exception e) {
60
+                e.printStackTrace();
61
+            }
62
+            Sentiment sentiment = null;
63
+            try {
64
+                sentiment = om.readValue(json, Sentiment.class);
65
+            } catch (IOException e) {
66
+                e.printStackTrace();
67
+            }
68
+            return sentiment;
69
+        });
70
+    }
71
+
72
+    public List<Sentiment> gas(ArrayList<Article> articles) throws Exception {
73
+        System.out.println(System.currentTimeMillis());
74
+        List<CompletableFuture<Sentiment>> sentimentArticles = articles.stream()
75
+                .map(article -> gs(article.getDescription()))
76
+                .collect(Collectors.toList());
77
+
78
+        CompletableFuture<Void> allFutures = CompletableFuture.allOf(
79
+                sentimentArticles.toArray(new CompletableFuture[sentimentArticles.size()])
80
+        );
81
+
82
+        CompletableFuture<List<Sentiment>> sentimentFutures = allFutures.thenApply(v -> {
83
+            return sentimentArticles.stream().map(x -> x.join()).collect(Collectors.toList());
84
+        });
85
+
86
+        List<Sentiment> fin = sentimentFutures.get();
87
+        System.out.println(System.currentTimeMillis());
88
+        return fin;
89
+    }
90
+}

+ 19
- 0
server/src/main/java/com/stockr/server/sentiment/SentimentThread.java 查看文件

@@ -0,0 +1,19 @@
1
+package com.stockr.server.sentiment;
2
+
3
+import com.stockr.server.news.Article;
4
+import org.springframework.scheduling.annotation.Async;
5
+import org.springframework.scheduling.annotation.AsyncResult;
6
+
7
+import java.util.ArrayList;
8
+import java.util.concurrent.Future;
9
+
10
+public class SentimentThread {
11
+    SentimentService ss = new SentimentService();
12
+
13
+    @Async
14
+    public Future<Article> getSentimentAsync(Article article) throws Exception {
15
+        Sentiment sentiment = ss.getSentiment(article.getDescription());
16
+        article.setSentiment(sentiment);
17
+        return new AsyncResult<>(article);
18
+    }
19
+}

+ 26
- 0
server/src/main/java/com/stockr/server/stocks/Gainer.java 查看文件

@@ -0,0 +1,26 @@
1
+package com.stockr.server.stocks;
2
+
3
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4
+import lombok.Getter;
5
+import lombok.NoArgsConstructor;
6
+import lombok.Setter;
7
+import pl.zankowski.iextrading4j.api.stocks.Quote;
8
+
9
+import java.math.BigDecimal;
10
+
11
+@NoArgsConstructor
12
+@Getter @Setter
13
+@JsonIgnoreProperties
14
+public class Gainer {
15
+    String symbol;
16
+    String companyName;
17
+    BigDecimal close;
18
+    BigDecimal changePercent;
19
+
20
+    Gainer(Quote quote) {
21
+        this.symbol = quote.getSymbol();
22
+        this.companyName = quote.getCompanyName();
23
+        this.close = quote.getClose();
24
+        this.changePercent = quote.getChangePercent();
25
+    }
26
+}

+ 7
- 0
server/src/main/java/com/stockr/server/stocks/StocksController.java 查看文件

@@ -6,6 +6,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
6 6
 import org.springframework.web.bind.annotation.RequestParam;
7 7
 import org.springframework.web.bind.annotation.RestController;
8 8
 
9
+import java.util.List;
10
+
9 11
 @RestController
10 12
 @RequestMapping("stocks")
11 13
 public class StocksController {
@@ -16,4 +18,9 @@ public class StocksController {
16 18
     public Stocks getStocks(@RequestParam("ticker") String ticker) {
17 19
         return stocksService.getStocks(ticker);
18 20
     }
21
+
22
+    @GetMapping("/gainers")
23
+    public List<Gainer> getGainers() {
24
+        return stocksService.getGainers();
25
+    }
19 26
 }

+ 19
- 0
server/src/main/java/com/stockr/server/stocks/StocksService.java 查看文件

@@ -3,9 +3,16 @@ package com.stockr.server.stocks;
3 3
 import org.springframework.context.annotation.Bean;
4 4
 import org.springframework.stereotype.Service;
5 5
 import pl.zankowski.iextrading4j.api.stocks.BatchStocks;
6
+import pl.zankowski.iextrading4j.api.stocks.Quote;
6 7
 import pl.zankowski.iextrading4j.client.IEXTradingClient;
8
+import pl.zankowski.iextrading4j.client.rest.manager.RestRequest;
7 9
 import pl.zankowski.iextrading4j.client.rest.request.stocks.BatchStocksRequestBuilder;
8 10
 import pl.zankowski.iextrading4j.client.rest.request.stocks.BatchStocksType;
11
+import pl.zankowski.iextrading4j.client.rest.request.stocks.ListRequestBuilder;
12
+import pl.zankowski.iextrading4j.client.rest.request.stocks.ListType;
13
+
14
+import java.util.List;
15
+import java.util.stream.Collectors;
9 16
 
10 17
 @Service
11 18
 public class StocksService {
@@ -15,6 +22,18 @@ public class StocksService {
15 22
         return new Stocks(getBatchFromTicker(ticker));
16 23
     }
17 24
 
25
+    // https://api.iextrading.com/1.0/stock/market/list/gainers
26
+    public List<Gainer> getGainers() {
27
+        List<Quote> quotes = iex.executeRequest(new ListRequestBuilder()
28
+                .withListType(ListType.GAINERS)
29
+                .build());
30
+
31
+        return quotes
32
+                .stream()
33
+                .map(Gainer::new)
34
+                .collect(Collectors.toList());
35
+    }
36
+
18 37
     private BatchStocks getBatchFromTicker(String ticker) {
19 38
         return iex.executeRequest(new BatchStocksRequestBuilder()
20 39
                 .withSymbol(ticker)

+ 9
- 0
server/src/test/java/com/stockr/server/news/ArticleTest.java 查看文件

@@ -2,8 +2,10 @@ package com.stockr.server.news;
2 2
 
3 3
 import com.fasterxml.jackson.databind.ObjectMapper;
4 4
 import org.junit.Test;
5
+import org.springframework.boot.web.client.RestTemplateBuilder;
5 6
 
6 7
 import java.io.IOException;
8
+import java.util.List;
7 9
 
8 10
 import static org.junit.Assert.*;
9 11
 
@@ -20,4 +22,11 @@ public class ArticleTest {
20 22
         assertEquals("ID", a.getSourceId());
21 23
         assertEquals("NAME", a.getSourceName());
22 24
     }
25
+
26
+    @Test
27
+    public void sentiment_ShouldBeNull() throws IOException {
28
+        NewsService ns = new NewsService(new RestTemplateBuilder());
29
+        List<Article> n = ns.getAllArticles("tesla");
30
+        assertNull(n.get(0).getSentiment());
31
+    }
23 32
 }

+ 19
- 0
server/src/test/java/com/stockr/server/paralleldots/ParallelDotsApiTest.java 查看文件

@@ -0,0 +1,19 @@
1
+package com.stockr.server.paralleldots;
2
+
3
+import com.paralleldots.paralleldots.App;
4
+import org.junit.Before;
5
+import org.junit.Test;
6
+
7
+public class ParallelDotsApiTest {
8
+    App pd;
9
+    @Before
10
+    public void setUp() {
11
+        pd = new App("ksfXrGlQnbn5jrJQBiGvxS0gdFVI8oHm6S3ZAxGz9EM");
12
+    }
13
+
14
+    @Test
15
+    public void sentiment_ShouldReturnSentiment_GivenString() throws Exception {
16
+        String s = pd.sentiment("I am very happy today.");
17
+        System.out.println(s);
18
+    }
19
+}

+ 31
- 0
server/src/test/java/com/stockr/server/sentiment/SentimentServiceTest.java 查看文件

@@ -0,0 +1,31 @@
1
+package com.stockr.server.sentiment;
2
+
3
+import com.stockr.server.news.Article;
4
+import com.stockr.server.news.NewsService;
5
+import org.junit.Test;
6
+import org.springframework.beans.factory.annotation.Autowired;
7
+import org.springframework.boot.web.client.RestTemplateBuilder;
8
+
9
+import java.util.ArrayList;
10
+import java.util.List;
11
+
12
+import static org.junit.Assert.*;
13
+
14
+public class SentimentServiceTest {
15
+    NewsService ns = new NewsService(new RestTemplateBuilder());
16
+
17
+    SentimentService ss = new SentimentService();
18
+
19
+    @Test
20
+    public void sentiment_Async() throws Exception {
21
+        ArrayList<Article> articles = ns.getAllArticles("tesla");
22
+        List<Sentiment> sentimentArticles = ss.gas(articles);
23
+        sentimentArticles.stream().forEach(x -> System.out.println(x.getLikely()));
24
+    }
25
+
26
+    @Test
27
+    public void sentiment_Nonasync() throws Exception {
28
+        ArrayList<Article> articles = ns.getAllArticles("tesla");
29
+        List<Article> sentimentArticles = ss.getAllSentiments(articles);
30
+    }
31
+}

+ 22
- 0
server/src/test/java/com/stockr/server/sentiment/SentimentTest.java 查看文件

@@ -0,0 +1,22 @@
1
+package com.stockr.server.sentiment;
2
+
3
+import com.fasterxml.jackson.databind.ObjectMapper;
4
+import com.paralleldots.paralleldots.App;
5
+import org.junit.Test;
6
+
7
+import static org.junit.Assert.*;
8
+
9
+public class SentimentTest {
10
+    @Test
11
+    public void constructor_ShouldFillInFields_GivenJsonString() throws Exception {
12
+        App pd = new App("ksfXrGlQnbn5jrJQBiGvxS0gdFVI8oHm6S3ZAxGz9EM");
13
+        String json = pd.sentiment("test");
14
+
15
+        Sentiment sentiment = new ObjectMapper().readValue(json, Sentiment.class);
16
+        assertNotNull(sentiment.getLikely());
17
+        assertNotNull(sentiment.getNegative());
18
+        assertNotNull(sentiment.getNeutral());
19
+        assertNotNull(sentiment.getPositive());
20
+    }
21
+
22
+}

+ 29
- 0
server/src/test/java/com/stockr/server/stocks/GainerTest.java 查看文件

@@ -0,0 +1,29 @@
1
+package com.stockr.server.stocks;
2
+
3
+import org.junit.Assert;
4
+import org.junit.Test;
5
+import pl.zankowski.iextrading4j.api.stocks.Quote;
6
+import pl.zankowski.iextrading4j.client.IEXTradingClient;
7
+import pl.zankowski.iextrading4j.client.rest.request.stocks.ListRequestBuilder;
8
+import pl.zankowski.iextrading4j.client.rest.request.stocks.ListType;
9
+
10
+import java.util.List;
11
+
12
+import static org.junit.Assert.*;
13
+
14
+public class GainerTest {
15
+    @Test
16
+    public void constructor_ShouldFillInFields_GivenQuote() {
17
+        IEXTradingClient iex = IEXTradingClient.create();
18
+        List<Quote> quotes = iex.executeRequest(new ListRequestBuilder()
19
+                .withListType(ListType.GAINERS)
20
+                .build());
21
+
22
+        Gainer gainer = new Gainer(quotes.get(0));
23
+
24
+        assertNotNull(gainer.getCompanyName());
25
+        assertNotNull(gainer.getChangePercent());
26
+        assertNotNull(gainer.getClose());
27
+        assertNotNull(gainer.getSymbol());
28
+    }
29
+}

+ 9
- 0
server/src/test/java/com/stockr/server/stocks/StocksControllerTest.java 查看文件

@@ -44,4 +44,13 @@ public class StocksControllerTest {
44 44
                 .andDo(print())
45 45
                 .andExpect(status().isBadRequest());
46 46
     }
47
+
48
+    @Test
49
+    public void gainers_ShouldUseMethod_getGainers() throws Exception {
50
+        when(service.getStocks("test")).thenReturn(new Stocks());
51
+        this.mockMvc.perform(get("/stocks/gainers"))
52
+                .andDo(print())
53
+                .andExpect(status().isOk())
54
+                .andExpect(handler().methodName("getGainers"));
55
+    }
47 56
 }

+ 7
- 0
server/src/test/java/com/stockr/server/stocks/StocksServiceTest.java 查看文件

@@ -13,4 +13,11 @@ public class StocksServiceTest {
13 13
         Stocks s = ss.getStocks("tsla");
14 14
         assertEquals(s.getCompany().getCompanyName(), "Tesla Inc.");
15 15
     }
16
+
17
+    // get market gainers
18
+    // https://api.iextrading.com/1.0/stock/market/list/gainers
19
+    @Test
20
+    public void getGainers_ShouldReceiveCorrectCompanyName() {
21
+        System.out.println(ss.getGainers().get(0).getCompanyName());
22
+    }
16 23
 }

+ 1
- 1
server/src/test/java/com/stockr/server/stocks/StocksTest.java 查看文件

@@ -12,7 +12,7 @@ import static org.junit.Assert.*;
12 12
 
13 13
 public class StocksTest {
14 14
     @Test
15
-    public void constructor_ShouldFillFields() {
15
+    public void constructor_ShouldFillFields_GivenBatchStocks() {
16 16
         // TODO mock this...
17 17
         IEXTradingClient iex = IEXTradingClient.create();
18 18
         BatchStocks bs = iex.executeRequest(new BatchStocksRequestBuilder()