Feature/improve markets overview (#1206)
* Improve markets overview * Update changelog
This commit is contained in:
parent
ca08d3154a
commit
7a9fd18407
@ -7,9 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added a skeleton loader to the market mood component in the markets overview
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Moved the build pipeline from _Travis_ to _GitHub Actions_
|
- Moved the build pipeline from _Travis_ to _GitHub Actions_
|
||||||
|
- Increased the caching of the benchmarks
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -1,30 +1,20 @@
|
|||||||
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
|
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
|
||||||
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
|
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
|
||||||
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
import { BenchmarkResponse } from '@ghostfolio/common/interfaces';
|
||||||
import { PROPERTY_BENCHMARKS } from '@ghostfolio/common/config';
|
|
||||||
import { BenchmarkResponse, UniqueAsset } from '@ghostfolio/common/interfaces';
|
|
||||||
import { Controller, Get, UseInterceptors } from '@nestjs/common';
|
import { Controller, Get, UseInterceptors } from '@nestjs/common';
|
||||||
|
|
||||||
import { BenchmarkService } from './benchmark.service';
|
import { BenchmarkService } from './benchmark.service';
|
||||||
|
|
||||||
@Controller('benchmark')
|
@Controller('benchmark')
|
||||||
export class BenchmarkController {
|
export class BenchmarkController {
|
||||||
public constructor(
|
public constructor(private readonly benchmarkService: BenchmarkService) {}
|
||||||
private readonly benchmarkService: BenchmarkService,
|
|
||||||
private readonly propertyService: PropertyService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
||||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||||
public async getBenchmark(): Promise<BenchmarkResponse> {
|
public async getBenchmark(): Promise<BenchmarkResponse> {
|
||||||
const benchmarkAssets: UniqueAsset[] =
|
|
||||||
((await this.propertyService.getByKey(
|
|
||||||
PROPERTY_BENCHMARKS
|
|
||||||
)) as UniqueAsset[]) ?? [];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
benchmarks: await this.benchmarkService.getBenchmarks(benchmarkAssets)
|
benchmarks: await this.benchmarkService.getBenchmarks()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
||||||
import { MarketDataService } from '@ghostfolio/api/services/market-data.service';
|
import { MarketDataService } from '@ghostfolio/api/services/market-data.service';
|
||||||
|
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
||||||
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service';
|
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service';
|
||||||
|
import { PROPERTY_BENCHMARKS } from '@ghostfolio/common/config';
|
||||||
import { BenchmarkResponse, UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { BenchmarkResponse, UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import Big from 'big.js';
|
import Big from 'big.js';
|
||||||
|
import ms from 'ms';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BenchmarkService {
|
export class BenchmarkService {
|
||||||
@ -13,25 +16,32 @@ export class BenchmarkService {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private readonly dataProviderService: DataProviderService,
|
private readonly dataProviderService: DataProviderService,
|
||||||
private readonly marketDataService: MarketDataService,
|
private readonly marketDataService: MarketDataService,
|
||||||
|
private readonly propertyService: PropertyService,
|
||||||
private readonly redisCacheService: RedisCacheService,
|
private readonly redisCacheService: RedisCacheService,
|
||||||
private readonly symbolProfileService: SymbolProfileService
|
private readonly symbolProfileService: SymbolProfileService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async getBenchmarks(
|
public async getBenchmarks({ useCache = true } = {}): Promise<
|
||||||
benchmarkAssets: UniqueAsset[]
|
BenchmarkResponse['benchmarks']
|
||||||
): Promise<BenchmarkResponse['benchmarks']> {
|
> {
|
||||||
let benchmarks: BenchmarkResponse['benchmarks'];
|
let benchmarks: BenchmarkResponse['benchmarks'];
|
||||||
|
|
||||||
try {
|
if (useCache) {
|
||||||
benchmarks = JSON.parse(
|
try {
|
||||||
await this.redisCacheService.get(this.CACHE_KEY_BENCHMARKS)
|
benchmarks = JSON.parse(
|
||||||
);
|
await this.redisCacheService.get(this.CACHE_KEY_BENCHMARKS)
|
||||||
|
);
|
||||||
|
|
||||||
if (benchmarks) {
|
if (benchmarks) {
|
||||||
return benchmarks;
|
return benchmarks;
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const benchmarkAssets: UniqueAsset[] =
|
||||||
|
((await this.propertyService.getByKey(
|
||||||
|
PROPERTY_BENCHMARKS
|
||||||
|
)) as UniqueAsset[]) ?? [];
|
||||||
const promises: Promise<number>[] = [];
|
const promises: Promise<number>[] = [];
|
||||||
|
|
||||||
const [quotes, assetProfiles] = await Promise.all([
|
const [quotes, assetProfiles] = await Promise.all([
|
||||||
@ -76,7 +86,8 @@ export class BenchmarkService {
|
|||||||
|
|
||||||
await this.redisCacheService.set(
|
await this.redisCacheService.set(
|
||||||
this.CACHE_KEY_BENCHMARKS,
|
this.CACHE_KEY_BENCHMARKS,
|
||||||
JSON.stringify(benchmarks)
|
JSON.stringify(benchmarks),
|
||||||
|
ms('4 hours') / 1000
|
||||||
);
|
);
|
||||||
|
|
||||||
return benchmarks;
|
return benchmarks;
|
||||||
|
@ -7,7 +7,7 @@ import { Module } from '@nestjs/common';
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
exports: [TwitterBotService],
|
exports: [TwitterBotService],
|
||||||
imports: [BenchmarkModule, ConfigurationModule, PropertyModule, SymbolModule],
|
imports: [BenchmarkModule, ConfigurationModule, SymbolModule],
|
||||||
providers: [TwitterBotService]
|
providers: [TwitterBotService]
|
||||||
})
|
})
|
||||||
export class TwitterBotModule {}
|
export class TwitterBotModule {}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { BenchmarkService } from '@ghostfolio/api/app/benchmark/benchmark.service';
|
import { BenchmarkService } from '@ghostfolio/api/app/benchmark/benchmark.service';
|
||||||
import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service';
|
import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service';
|
||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
|
||||||
import {
|
import {
|
||||||
PROPERTY_BENCHMARKS,
|
|
||||||
ghostfolioFearAndGreedIndexDataSource,
|
ghostfolioFearAndGreedIndexDataSource,
|
||||||
ghostfolioFearAndGreedIndexSymbol
|
ghostfolioFearAndGreedIndexSymbol
|
||||||
} from '@ghostfolio/common/config';
|
} from '@ghostfolio/common/config';
|
||||||
@ -11,7 +9,6 @@ import {
|
|||||||
resolveFearAndGreedIndex,
|
resolveFearAndGreedIndex,
|
||||||
resolveMarketCondition
|
resolveMarketCondition
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { isWeekend } from 'date-fns';
|
import { isWeekend } from 'date-fns';
|
||||||
import { TwitterApi, TwitterApiReadWrite } from 'twitter-api-v2';
|
import { TwitterApi, TwitterApiReadWrite } from 'twitter-api-v2';
|
||||||
@ -23,7 +20,6 @@ export class TwitterBotService {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private readonly benchmarkService: BenchmarkService,
|
private readonly benchmarkService: BenchmarkService,
|
||||||
private readonly configurationService: ConfigurationService,
|
private readonly configurationService: ConfigurationService,
|
||||||
private readonly propertyService: PropertyService,
|
|
||||||
private readonly symbolService: SymbolService
|
private readonly symbolService: SymbolService
|
||||||
) {
|
) {
|
||||||
this.twitterClient = new TwitterApi({
|
this.twitterClient = new TwitterApi({
|
||||||
@ -82,14 +78,9 @@ export class TwitterBotService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getBenchmarkListing(aMax: number) {
|
private async getBenchmarkListing(aMax: number) {
|
||||||
const benchmarkAssets: UniqueAsset[] =
|
const benchmarks = await this.benchmarkService.getBenchmarks({
|
||||||
((await this.propertyService.getByKey(
|
useCache: false
|
||||||
PROPERTY_BENCHMARKS
|
});
|
||||||
)) as UniqueAsset[]) ?? [];
|
|
||||||
|
|
||||||
const benchmarks = await this.benchmarkService.getBenchmarks(
|
|
||||||
benchmarkAssets
|
|
||||||
);
|
|
||||||
|
|
||||||
const benchmarkListing: string[] = [];
|
const benchmarkListing: string[] = [];
|
||||||
|
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
<div class="align-items-center d-flex flex-row">
|
<div class="position-relative">
|
||||||
<div class="h2 mb-0 mr-2">{{ fearAndGreedIndexEmoji }}</div>
|
<div class="align-items-center d-flex flex-row" [hidden]="!fearAndGreedIndex">
|
||||||
<div>
|
<div class="h2 mb-0 mr-2">{{ fearAndGreedIndexEmoji }}</div>
|
||||||
<div class="h4 mb-0">
|
<div>
|
||||||
<span class="mr-2">{{ fearAndGreedIndexText }}</span>
|
<div class="h4 mb-0">
|
||||||
<small class="text-muted"
|
<span class="mr-2">{{ fearAndGreedIndexText }}</span>
|
||||||
><strong>{{ fearAndGreedIndex }}</strong
|
<small class="text-muted"
|
||||||
>/100</small
|
><strong>{{ fearAndGreedIndex }}</strong
|
||||||
>
|
>/100</small
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<small class="d-block" i18n>Current Market Mood</small>
|
||||||
</div>
|
</div>
|
||||||
<small class="d-block" i18n>Current Market Mood</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ngx-skeleton-loader
|
||||||
|
*ngIf="!fearAndGreedIndex"
|
||||||
|
animation="pulse"
|
||||||
|
class="position-absolute w-100"
|
||||||
|
[theme]="{
|
||||||
|
height: '100%'
|
||||||
|
}"
|
||||||
|
></ngx-skeleton-loader>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
ngx-skeleton-loader {
|
||||||
|
bottom: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
import { FearAndGreedIndexComponent } from './fear-and-greed-index.component';
|
import { FearAndGreedIndexComponent } from './fear-and-greed-index.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [FearAndGreedIndexComponent],
|
declarations: [FearAndGreedIndexComponent],
|
||||||
exports: [FearAndGreedIndexComponent],
|
exports: [FearAndGreedIndexComponent],
|
||||||
imports: [CommonModule]
|
imports: [CommonModule, NgxSkeletonLoaderModule]
|
||||||
})
|
})
|
||||||
export class GfFearAndGreedIndexModule {}
|
export class GfFearAndGreedIndexModule {}
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
<gf-fear-and-greed-index
|
<gf-fear-and-greed-index
|
||||||
class="d-flex justify-content-center"
|
class="d-flex justify-content-center"
|
||||||
[fearAndGreedIndex]="fearAndGreedIndex"
|
[fearAndGreedIndex]="fearAndGreedIndex"
|
||||||
[hidden]="isLoading"
|
|
||||||
></gf-fear-and-greed-index>
|
></gf-fear-and-greed-index>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user