mirror of
https://github.com/Dansen999/at-certification-stock.git
synced 2026-01-10 21:13:40 +00:00
Implemented features.
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {SentimentComponent} from "./pages/sentiment/sentiment.component";
|
||||
import {SentimentPageComponent} from "./pages/sentiment-page/sentiment-page.component";
|
||||
import {LandingPageComponent} from "./pages/landing-page/landing-page.component";
|
||||
|
||||
const routes: Routes = [
|
||||
|
||||
{path: 'sentiment/:symbol', component: SentimentComponent},
|
||||
{path: 'sentiment/:symbol', component: SentimentPageComponent},
|
||||
{path: '**', component: LandingPageComponent}
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.content {
|
||||
max-width: 60rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
<router-outlet></router-outlet>
|
||||
<div class="content">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import {BrowserModule} from '@angular/platform-browser';
|
||||
import {AppRoutingModule} from './app-routing.module';
|
||||
import {AppComponent} from './app.component';
|
||||
import {HttpClientModule} from "@angular/common/http";
|
||||
import {SentimentComponent} from './pages/sentiment/sentiment.component';
|
||||
import {SentimentPageComponent} from './pages/sentiment-page/sentiment-page.component';
|
||||
import {LandingPageComponent} from './pages/landing-page/landing-page.component';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {MatCardModule} from "@angular/material/card";
|
||||
@@ -13,18 +13,25 @@ import {MatButtonModule} from "@angular/material/button";
|
||||
import {MatInputModule} from "@angular/material/input";
|
||||
import {MatDividerModule} from "@angular/material/divider";
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
|
||||
import {MatSnackBarModule} from "@angular/material/snack-bar";
|
||||
import {MatGridListModule} from "@angular/material/grid-list";
|
||||
import { StockCardComponent } from './components/stock-card/stock-card.component';
|
||||
import {StockCardComponent} from './components/stock-card/stock-card.component';
|
||||
import {MatIconModule} from "@angular/material/icon";
|
||||
import {SentimentCardComponent} from './components/sentiment-card/sentiment-card.component';
|
||||
import {TendencyComponent} from './components/tendency/tendency.component';
|
||||
import {SentimentComponent} from './components/sentinment/sentiment.component';
|
||||
import { MonthPipe } from './pipes/month.pipe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
SentimentComponent,
|
||||
SentimentPageComponent,
|
||||
LandingPageComponent,
|
||||
StockCardComponent
|
||||
StockCardComponent,
|
||||
SentimentCardComponent,
|
||||
TendencyComponent,
|
||||
SentimentComponent,
|
||||
MonthPipe
|
||||
],
|
||||
imports: [
|
||||
FormsModule,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
.mat-card {
|
||||
margin: 1rem 0;
|
||||
height: 14rem;
|
||||
line-height: 2rem;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto;
|
||||
padding: 10px;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<mat-card *ngIf="loaded; else loading">
|
||||
<ng-container>
|
||||
<mat-card-title>{{company.description}} ({{company.symbol}})</mat-card-title>
|
||||
<mat-card-content>
|
||||
<div class="grid-container content">
|
||||
<div class="grid-item" *ngFor="let insiderSentiment of insiderSentiments ">
|
||||
<app-sentiment [month]="insiderSentiment.month"
|
||||
[change]="insiderSentiment.change"
|
||||
[mspr]="insiderSentiment.mspr"></app-sentiment>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</ng-container>
|
||||
</mat-card>
|
||||
|
||||
<ng-template #loading>
|
||||
<mat-card>
|
||||
<mat-card-title>Loading...</mat-card-title>
|
||||
<mat-card-content></mat-card-content>
|
||||
</mat-card>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SentimentCardComponent } from './sentiment-card.component';
|
||||
|
||||
describe('SentimentCardComponent', () => {
|
||||
let component: SentimentCardComponent;
|
||||
let fixture: ComponentFixture<SentimentCardComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ SentimentCardComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SentimentCardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Company, InsiderSentimentData} from "../../model/company-data";
|
||||
import {FinnhubService} from "../../services/finnhub.service";
|
||||
import {Subscription} from "rxjs";
|
||||
|
||||
@Component({
|
||||
selector: 'app-sentiment-card',
|
||||
templateUrl: './sentiment-card.component.html',
|
||||
styleUrls: ['./sentiment-card.component.css']
|
||||
})
|
||||
export class SentimentCardComponent implements OnInit, OnDestroy {
|
||||
|
||||
@Input()
|
||||
symbol = '';
|
||||
|
||||
loaded = false;
|
||||
company!: Company;
|
||||
insiderSentiments!: Array<InsiderSentimentData>;
|
||||
|
||||
private loadedSubs = 0;
|
||||
private subs: Subscription[] = [];
|
||||
|
||||
constructor(private finnhubService: FinnhubService) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.subs.push(this.finnhubService.getCompany(this.symbol).subscribe(value => {
|
||||
this.company = value;
|
||||
this.loaded = ++this.loadedSubs == this.subs.length;
|
||||
}));
|
||||
|
||||
let current = new Date()
|
||||
let from = new Date(current.getFullYear(), current.getMonth() - 3, 1);
|
||||
let to = new Date(current.getFullYear(), current.getMonth() - 1, 1);
|
||||
|
||||
this.subs.push(this.finnhubService.getInsideSentiment(this.symbol, from, to).subscribe(value => {
|
||||
this.insiderSentiments = value.data;
|
||||
this.loaded = ++this.loadedSubs == this.subs.length;
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs.forEach(s => s.unsubscribe());
|
||||
}
|
||||
}
|
||||
10
src/app/components/sentinment/sentiment.component.css
Normal file
10
src/app/components/sentinment/sentiment.component.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.grid-item .label {
|
||||
font-weight: bold;
|
||||
margin: 1rem;
|
||||
}
|
||||
11
src/app/components/sentinment/sentiment.component.html
Normal file
11
src/app/components/sentinment/sentiment.component.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="grid-container ">
|
||||
<div class="grid-item">
|
||||
<p><span class="label">{{month | month }}</span></p>
|
||||
<p><span class="label">Change:</span><span class="value">{{change | number: '1.0-0'}}</span></p>
|
||||
<p><span class="label">MSPR:</span><span class="value">{{mspr | number: '1.2-2'}}</span></p>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<app-tendency [value]="change"></app-tendency>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SentimentComponent } from './sentiment.component';
|
||||
|
||||
describe('SentimentComponent', () => {
|
||||
describe('SentinmentComponent', () => {
|
||||
let component: SentimentComponent;
|
||||
let fixture: ComponentFixture<SentimentComponent>;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import {FinnhubService} from "../../services/finnhub.service";
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sentiment',
|
||||
@@ -8,9 +7,19 @@ import {FinnhubService} from "../../services/finnhub.service";
|
||||
})
|
||||
export class SentimentComponent implements OnInit {
|
||||
|
||||
constructor(private finnhubService: FinnhubService) { }
|
||||
@Input()
|
||||
month!: number;
|
||||
|
||||
@Input()
|
||||
change!: number;
|
||||
|
||||
@Input()
|
||||
mspr!: number;
|
||||
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,24 @@
|
||||
.mat-card {
|
||||
margin: 1rem;
|
||||
margin: 1rem 0;
|
||||
height: 14rem;
|
||||
line-height: 2rem;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.mat-card-title .mat-icon {
|
||||
a.remove {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.mat-grid-tile .mat-icon {
|
||||
font-size: 4rem;
|
||||
height: 4rem;
|
||||
width: 4rem;
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.grid-item .label {
|
||||
font-weight: bold;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,27 +1,54 @@
|
||||
<mat-card>
|
||||
<mat-card *ngIf="loaded; else loading">
|
||||
|
||||
<mat-card-title>{{company.description}} ({{company.symbol}})
|
||||
<mat-icon (click)="remove()" fontIcon="close"></mat-icon>
|
||||
<a (click)="remove()" id="{{'remove'+ company.symbol}}" class="remove">
|
||||
<mat-icon fontIcon="close"></mat-icon>
|
||||
</a>
|
||||
</mat-card-title>
|
||||
|
||||
|
||||
<mat-card-content>
|
||||
<ng-container *ngIf="getQuote() | async; let quote">
|
||||
<mat-grid-list cols="4" rowHeight="4:1">
|
||||
|
||||
<mat-grid-tile colspan="2"></mat-grid-tile>
|
||||
<mat-grid-tile colspan="2" rowspan="3">
|
||||
<mat-icon *ngIf="quote.dp>0" color="primary" fontIcon="arrow_upward"></mat-icon>
|
||||
<mat-icon *ngIf="quote.dp<0" color="warn" fontIcon="arrow_downward"></mat-icon>
|
||||
<mat-icon *ngIf="quote.dp==0" fontIcon="east"></mat-icon>
|
||||
</mat-grid-tile>
|
||||
<div class="grid-container content">
|
||||
<div class="grid-item">
|
||||
<div class="grid-container values">
|
||||
<div class="grid-item">
|
||||
<span class="label">Change today:</span>
|
||||
<span class="value">{{quote.dp / 100 | percent: '1.1-1'}}</span>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<span class="label">Opening price:</span>
|
||||
<span class="value">{{quote.o | currency: 'USD'}}</span>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<span class="label">Current price:</span>
|
||||
<span class="value">{{quote.c | currency: 'USD'}}</span>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<span class="label">High price:</span>
|
||||
<span class="value">{{quote.h | currency: 'USD'}}</span>
|
||||
</div>
|
||||
|
||||
<mat-grid-tile><p>Change today: {{quote.dp / 100 | percent: '1.1-1'}}</p></mat-grid-tile>
|
||||
<mat-grid-tile><p>Opening price: {{quote.o | currency: 'USD'}}</p></mat-grid-tile>
|
||||
<mat-grid-tile><p>Current price: {{quote.c | currency: 'USD'}}</p></mat-grid-tile>
|
||||
<mat-grid-tile><p>High price: {{quote.h | currency: 'USD'}}</p></mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<app-tendency [value]="quote.dp"></app-tendency>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions align="end">
|
||||
<button mat-button [routerLink]="'/sentiment/' + company.symbol">Go to social sentiment details</button>
|
||||
<button mat-button
|
||||
id="{{'sentiment'+ company.symbol}}"
|
||||
[routerLink]="'/sentiment/' + symbol">Go to social sentiment details
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
|
||||
</mat-card>
|
||||
|
||||
<ng-template #loading>
|
||||
<mat-card>
|
||||
<mat-card-title>Loading...</mat-card-title>
|
||||
<mat-card-content></mat-card-content>
|
||||
</mat-card>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {FinnhubService} from "../../services/finnhub.service";
|
||||
import {Company, QuoteResponse} from "../../model/company-data";
|
||||
import {Subject, Subscription} from "rxjs";
|
||||
import {Subscription} from "rxjs";
|
||||
import {StorageService} from "../../services/storage.service";
|
||||
|
||||
@Component({
|
||||
@@ -12,9 +12,14 @@ import {StorageService} from "../../services/storage.service";
|
||||
export class StockCardComponent implements OnInit, OnDestroy {
|
||||
|
||||
@Input()
|
||||
company = {} as Company;
|
||||
symbol = '';
|
||||
|
||||
private _quote$ = new Subject<QuoteResponse>();
|
||||
loaded = false;
|
||||
company!: Company;
|
||||
quote!: QuoteResponse;
|
||||
|
||||
|
||||
private loadedSubs = 0;
|
||||
private subs: Subscription[] = [];
|
||||
|
||||
constructor(public finnhubService: FinnhubService,
|
||||
@@ -22,19 +27,22 @@ export class StockCardComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.subs.push(this.finnhubService.getQuote(this.company.symbol).subscribe(value => this._quote$.next(value)));
|
||||
this.subs.push(this.finnhubService.getCompany(this.symbol).subscribe(value => {
|
||||
this.company = value;
|
||||
this.loaded = ++this.loadedSubs == this.subs.length;
|
||||
}));
|
||||
this.subs.push(this.finnhubService.getQuote(this.symbol).subscribe(value => {
|
||||
this.quote = value;
|
||||
this.loaded = ++this.loadedSubs == this.subs.length;
|
||||
}));
|
||||
}
|
||||
|
||||
getQuote(): Subject<QuoteResponse> {
|
||||
return this._quote$;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs.forEach(s => s.unsubscribe());
|
||||
this._quote$.complete();
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.storageService.remove(this.company.symbol);
|
||||
this.storageService.remove(this.symbol);
|
||||
}
|
||||
}
|
||||
|
||||
6
src/app/components/tendency/tendency.component.css
Normal file
6
src/app/components/tendency/tendency.component.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.mat-icon {
|
||||
font-size: 6rem;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
3
src/app/components/tendency/tendency.component.html
Normal file
3
src/app/components/tendency/tendency.component.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<mat-icon *ngIf="value>0" color="primary" fontIcon="arrow_upward"></mat-icon>
|
||||
<mat-icon *ngIf="value<0" color="warn" fontIcon="arrow_downward"></mat-icon>
|
||||
<mat-icon *ngIf="value==0" fontIcon="east"></mat-icon>
|
||||
23
src/app/components/tendency/tendency.component.spec.ts
Normal file
23
src/app/components/tendency/tendency.component.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TendencyComponent } from './tendency.component';
|
||||
|
||||
describe('TendencyComponent', () => {
|
||||
let component: TendencyComponent;
|
||||
let fixture: ComponentFixture<TendencyComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TendencyComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TendencyComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
19
src/app/components/tendency/tendency.component.ts
Normal file
19
src/app/components/tendency/tendency.component.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tendency',
|
||||
templateUrl: './tendency.component.html',
|
||||
styleUrls: ['./tendency.component.css']
|
||||
})
|
||||
export class TendencyComponent implements OnInit {
|
||||
|
||||
@Input()
|
||||
value!: number;
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,3 +19,18 @@ export interface QuoteResponse {
|
||||
o: number;
|
||||
pc: number;
|
||||
}
|
||||
|
||||
export interface InsiderSentimentData {
|
||||
symbol: string;
|
||||
year: number;
|
||||
month: number;
|
||||
change: number;
|
||||
mspr: number;
|
||||
}
|
||||
|
||||
export interface InsiderSentimentResponse {
|
||||
data: Array<InsiderSentimentData>;
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
.mat-card {
|
||||
margin: 1rem;
|
||||
.search-box {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mat-divider {
|
||||
margin: 0 1rem;
|
||||
.mat-form-field {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.mat-card {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<p>
|
||||
Enter the symbol of a stock to track (i.e. AAPL, TSLA, GOOGL)
|
||||
</p>
|
||||
<div class="search-box">
|
||||
<p>
|
||||
Enter the symbol of a stock to track (i.e. AAPL, TSLA, GOOGL)
|
||||
</p>
|
||||
|
||||
<mat-form-field>
|
||||
<input id="stockInput"
|
||||
matInput
|
||||
[(ngModel)]="symbol"/>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input id="stockInput"
|
||||
matInput
|
||||
[(ngModel)]="symbolInput"/>
|
||||
</mat-form-field>
|
||||
|
||||
<button id="trackBtn"
|
||||
mat-stroked-button
|
||||
color="primary"
|
||||
[disabled]="disabled"
|
||||
(click)="addSymbol()">Track Stock
|
||||
</button>
|
||||
<button id="trackBtn"
|
||||
mat-stroked-button
|
||||
color="primary"
|
||||
[disabled]="disabled"
|
||||
(click)="addSymbol()">Track Stock
|
||||
</button>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<ng-container *ngFor="let company of storageService.get() | async ">
|
||||
<app-stock-card [company]="company"></app-stock-card>
|
||||
<ng-container *ngFor="let symbol of storageService.get() | async ">
|
||||
<app-stock-card [symbol]="symbol"></app-stock-card>
|
||||
</ng-container>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {MatSnackBar} from "@angular/material/snack-bar";
|
||||
})
|
||||
export class LandingPageComponent implements OnInit {
|
||||
|
||||
symbol = '';
|
||||
symbolInput = '';
|
||||
disabled = false;
|
||||
|
||||
constructor(public finnhubService: FinnhubService,
|
||||
@@ -24,29 +24,16 @@ export class LandingPageComponent implements OnInit {
|
||||
|
||||
|
||||
addSymbol() {
|
||||
console.log('search for ' + this.symbol)
|
||||
|
||||
// start loading...
|
||||
this.disabled = true;
|
||||
this.finnhubService.searchSymbol(this.symbol).subscribe(value => {
|
||||
|
||||
if (value.count > 0) {
|
||||
|
||||
let company = value.result.find(company => company.symbol == this.symbol);
|
||||
if (company) {
|
||||
this.storageService.add(company);
|
||||
this.symbol = '';
|
||||
} else {
|
||||
this.snackBar.open('No unique result', 'close', {duration: 10000});
|
||||
}
|
||||
|
||||
this.disabled = false
|
||||
} else {
|
||||
this.snackBar.open('No result', 'close', {duration: 10000});
|
||||
this.disabled = false
|
||||
}
|
||||
}, (error) => {
|
||||
this.snackBar.open('Communication error', 'close', {duration: 10000});
|
||||
this.finnhubService.getCompany(this.symbolInput).subscribe(value => {
|
||||
this.storageService.add(value.symbol);
|
||||
this.symbolInput = '';
|
||||
this.disabled = false;
|
||||
}, (error: string) => {
|
||||
this.snackBar.open(error, 'close', {duration: 10000});
|
||||
this.disabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.mat-card {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<ng-container *ngFor="let symbol of _symbols$ | async ">
|
||||
<app-sentiment-card [symbol]="symbol"></app-sentiment-card>
|
||||
</ng-container>
|
||||
|
||||
<button mat-button id="backBtn" [routerLink]="'/'">Back to list of stocks</button>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SentimentPageComponent } from './sentiment-page.component';
|
||||
|
||||
describe('SentimentComponent', () => {
|
||||
let component: SentimentPageComponent;
|
||||
let fixture: ComponentFixture<SentimentPageComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ SentimentPageComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SentimentPageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
25
src/app/pages/sentiment-page/sentiment-page.component.ts
Normal file
25
src/app/pages/sentiment-page/sentiment-page.component.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
import {BehaviorSubject} from "rxjs";
|
||||
import {StorageService} from "../../services/storage.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-sentiment-page',
|
||||
templateUrl: './sentiment-page.component.html',
|
||||
styleUrls: ['./sentiment-page.component.css']
|
||||
})
|
||||
export class SentimentPageComponent implements OnInit {
|
||||
|
||||
_symbols$ = new BehaviorSubject<string[]>([]);
|
||||
|
||||
constructor(private route: ActivatedRoute,
|
||||
private storageService: StorageService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.route.params.subscribe(params => {
|
||||
this._symbols$.next([params['symbol']]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
<p>sentiment works!</p>
|
||||
|
||||
|
||||
<button mat-button [routerLink]="'/'">Back to list of stocks</button>
|
||||
8
src/app/pipes/month.pipe.spec.ts
Normal file
8
src/app/pipes/month.pipe.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { MonthPipe } from './month.pipe';
|
||||
|
||||
describe('MonthPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new MonthPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
16
src/app/pipes/month.pipe.ts
Normal file
16
src/app/pipes/month.pipe.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import {formatDate} from "@angular/common";
|
||||
|
||||
@Pipe({
|
||||
name: 'month'
|
||||
})
|
||||
export class MonthPipe implements PipeTransform {
|
||||
|
||||
transform(value: number): unknown {
|
||||
|
||||
let date = new Date();
|
||||
date.setMonth(value);
|
||||
return formatDate(date, 'MMMM','en-US');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {Observable} from "rxjs";
|
||||
import {QuoteResponse, SearchResponse} from "../model/company-data";
|
||||
import {catchError, from, map, Observable} from "rxjs";
|
||||
import {Company, InsiderSentimentResponse, QuoteResponse, SearchResponse} from "../model/company-data";
|
||||
import {formatDate} from "@angular/common";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -12,7 +13,7 @@ export class FinnhubService {
|
||||
private readonly api = 'https://finnhub.io/api/v1';
|
||||
private readonly apiQuote = this.api + '/quote';
|
||||
private readonly apiSearch = this.api + '/search';
|
||||
|
||||
private readonly apiSentiment = this.api + '/stock/insider-sentiment';
|
||||
|
||||
|
||||
constructor(private httpClient: HttpClient) { }
|
||||
@@ -21,9 +22,37 @@ export class FinnhubService {
|
||||
return this.httpClient.get<SearchResponse>(this.apiSearch, {params: { ...this.token, q }});
|
||||
}
|
||||
|
||||
getQuote(symbol: string) {
|
||||
getCompany(symbol: string): Observable<Company> {
|
||||
return this.searchSymbol(symbol).pipe(map(value => {
|
||||
if (value.count > 0) {
|
||||
let company = value.result.find(company => company.symbol == symbol);
|
||||
if (company) {
|
||||
return company;
|
||||
} else {
|
||||
throw new Error('No unique result');
|
||||
}
|
||||
} else {
|
||||
throw new Error('No result');
|
||||
}
|
||||
}), catchError((err: Error, caught) => {
|
||||
throw new Error(err.message);
|
||||
}));
|
||||
}
|
||||
|
||||
getQuote(symbol: string): Observable<QuoteResponse> {
|
||||
return this.httpClient.get<QuoteResponse>(this.apiQuote, {params: { ...this.token, symbol }});
|
||||
}
|
||||
|
||||
getInsideSentiment(symbol: string, fromDate: Date, toDate: Date): Observable<InsiderSentimentResponse> {
|
||||
|
||||
|
||||
|
||||
|
||||
let from = formatDate(fromDate,'yyyy-MM-dd','en-US');
|
||||
let to = formatDate(toDate,'yyyy-MM-dd','en-US');
|
||||
|
||||
return this.httpClient.get<InsiderSentimentResponse>(this.apiSentiment, {params: { ...this.token, symbol, from, to }});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
import {Injectable, OnDestroy} from '@angular/core';
|
||||
import {BehaviorSubject} from "rxjs";
|
||||
import {Company} from "../model/company-data";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class StorageService implements OnDestroy {
|
||||
|
||||
private storage: Company[] = [];
|
||||
|
||||
private _storage$ = new BehaviorSubject<Company[]>([]);
|
||||
private storage: string[] = [];
|
||||
private _storage$ = new BehaviorSubject<string[]>([]);
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
add(company: Company): void {
|
||||
this.storage.push(company);
|
||||
add(symbol: string): void {
|
||||
this.storage.push(symbol);
|
||||
this._storage$.next(this.storage)
|
||||
}
|
||||
|
||||
remove(symbol: string): void {
|
||||
console.log('Remove ' + symbol)
|
||||
|
||||
let index = this.storage.findIndex(company => company.symbol == symbol);
|
||||
let index = this.storage.findIndex(item => item == symbol);
|
||||
if (index > -1) {
|
||||
this.storage.splice(index, 1);
|
||||
}
|
||||
@@ -31,7 +29,7 @@ export class StorageService implements OnDestroy {
|
||||
this._storage$.next(this.storage)
|
||||
}
|
||||
|
||||
get(): BehaviorSubject<Company[]> {
|
||||
get(): BehaviorSubject<string[]> {
|
||||
return this._storage$;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
|
||||
html, body { height: 100%; }
|
||||
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Roboto, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user