mirror of https://github.com/ghostfolio/ghostfolio
committed by
GitHub
19 changed files with 342 additions and 13 deletions
@ -0,0 +1,129 @@ |
|||
import { GfFearAndGreedIndexModule } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.module'; |
|||
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module'; |
|||
import { DataService } from '@ghostfolio/client/services/data.service'; |
|||
import { UserService } from '@ghostfolio/client/services/user/user.service'; |
|||
import { resetHours } from '@ghostfolio/common/helper'; |
|||
import { |
|||
Benchmark, |
|||
HistoricalDataItem, |
|||
MarketDataOfMarketsResponse, |
|||
ToggleOption, |
|||
User |
|||
} from '@ghostfolio/common/interfaces'; |
|||
import { FearAndGreedIndexMode } from '@ghostfolio/common/types'; |
|||
import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark'; |
|||
import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; |
|||
|
|||
import { CommonModule } from '@angular/common'; |
|||
import { |
|||
ChangeDetectionStrategy, |
|||
ChangeDetectorRef, |
|||
Component, |
|||
CUSTOM_ELEMENTS_SCHEMA, |
|||
OnDestroy, |
|||
OnInit |
|||
} from '@angular/core'; |
|||
import { DeviceDetectorService } from 'ngx-device-detector'; |
|||
import { Subject } from 'rxjs'; |
|||
import { takeUntil } from 'rxjs/operators'; |
|||
|
|||
@Component({ |
|||
changeDetection: ChangeDetectionStrategy.OnPush, |
|||
imports: [ |
|||
CommonModule, |
|||
GfBenchmarkComponent, |
|||
GfFearAndGreedIndexModule, |
|||
GfLineChartComponent, |
|||
GfToggleModule |
|||
], |
|||
schemas: [CUSTOM_ELEMENTS_SCHEMA], |
|||
selector: 'gf-markets', |
|||
styleUrls: ['./markets.scss'], |
|||
templateUrl: './markets.html' |
|||
}) |
|||
export class MarketsComponent implements OnDestroy, OnInit { |
|||
public benchmarks: Benchmark[]; |
|||
public deviceType: string; |
|||
public fearAndGreedIndex: number; |
|||
public fearAndGreedIndexData: MarketDataOfMarketsResponse['fearAndGreedIndex']; |
|||
public fearLabel = $localize`Fear`; |
|||
public greedLabel = $localize`Greed`; |
|||
public historicalDataItems: HistoricalDataItem[]; |
|||
public fearAndGreedIndexMode: FearAndGreedIndexMode = 'STOCKS'; |
|||
public fearAndGreedIndexModeOptions: ToggleOption[] = [ |
|||
{ label: $localize`Stocks`, value: 'STOCKS' }, |
|||
{ label: $localize`Cryptocurrencies`, value: 'CRYPTOCURRENCIES' } |
|||
]; |
|||
public readonly numberOfDays = 365; |
|||
public user: User; |
|||
|
|||
private unsubscribeSubject = new Subject<void>(); |
|||
|
|||
public constructor( |
|||
private changeDetectorRef: ChangeDetectorRef, |
|||
private dataService: DataService, |
|||
private deviceService: DeviceDetectorService, |
|||
private userService: UserService |
|||
) { |
|||
this.deviceType = this.deviceService.getDeviceInfo().deviceType; |
|||
|
|||
this.userService.stateChanged |
|||
.pipe(takeUntil(this.unsubscribeSubject)) |
|||
.subscribe((state) => { |
|||
if (state?.user) { |
|||
this.user = state.user; |
|||
|
|||
this.changeDetectorRef.markForCheck(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public ngOnInit() { |
|||
this.dataService |
|||
.fetchMarketDataOfMarkets({ includeHistoricalData: this.numberOfDays }) |
|||
.pipe(takeUntil(this.unsubscribeSubject)) |
|||
.subscribe(({ fearAndGreedIndex }) => { |
|||
this.fearAndGreedIndexData = fearAndGreedIndex; |
|||
|
|||
this.initialize(); |
|||
|
|||
this.changeDetectorRef.markForCheck(); |
|||
}); |
|||
|
|||
this.dataService |
|||
.fetchBenchmarks() |
|||
.pipe(takeUntil(this.unsubscribeSubject)) |
|||
.subscribe(({ benchmarks }) => { |
|||
this.benchmarks = benchmarks; |
|||
|
|||
this.changeDetectorRef.markForCheck(); |
|||
}); |
|||
} |
|||
|
|||
public initialize() { |
|||
this.fearAndGreedIndex = |
|||
this.fearAndGreedIndexData[this.fearAndGreedIndexMode]?.marketPrice; |
|||
|
|||
this.historicalDataItems = [ |
|||
...(this.fearAndGreedIndexData[this.fearAndGreedIndexMode] |
|||
?.historicalData ?? []), |
|||
{ |
|||
date: resetHours(new Date()).toISOString(), |
|||
value: this.fearAndGreedIndex |
|||
} |
|||
]; |
|||
} |
|||
|
|||
public onChangeFearAndGreedIndexMode( |
|||
aFearAndGreedIndexMode: FearAndGreedIndexMode |
|||
) { |
|||
this.fearAndGreedIndexMode = aFearAndGreedIndexMode; |
|||
|
|||
this.initialize(); |
|||
} |
|||
|
|||
public ngOnDestroy() { |
|||
this.unsubscribeSubject.next(); |
|||
this.unsubscribeSubject.complete(); |
|||
} |
|||
} |
@ -0,0 +1,60 @@ |
|||
<div class="container"> |
|||
<h1 class="d-none d-sm-block h3 mb-4 text-center" i18n>Markets</h1> |
|||
<div class="mb-5 row"> |
|||
<div class="col-xs-12 col-md-10 offset-md-1"> |
|||
@if (user?.settings?.isExperimentalFeatures) { |
|||
<div class="align-items-center d-flex flex-grow-1 justify-content-end"> |
|||
<gf-toggle |
|||
class="d-none d-lg-block" |
|||
[defaultValue]="fearAndGreedIndexMode" |
|||
[isLoading]="false" |
|||
[options]="fearAndGreedIndexModeOptions" |
|||
(change)="onChangeFearAndGreedIndexMode($event.value)" |
|||
/> |
|||
</div> |
|||
} |
|||
<div class="mb-2 text-center text-muted"> |
|||
<small i18n>Last {{ numberOfDays }} Days</small> |
|||
</div> |
|||
<gf-line-chart |
|||
class="mb-3" |
|||
symbol="Fear & Greed Index" |
|||
[colorScheme]="user?.settings?.colorScheme" |
|||
[historicalDataItems]="historicalDataItems" |
|||
[isAnimated]="true" |
|||
[locale]="user?.settings?.locale || undefined" |
|||
[showXAxis]="true" |
|||
[showYAxis]="true" |
|||
[yMax]="100" |
|||
[yMaxLabel]="greedLabel" |
|||
[yMin]="0" |
|||
[yMinLabel]="fearLabel" |
|||
/> |
|||
<gf-fear-and-greed-index |
|||
class="d-flex justify-content-center" |
|||
[fearAndGreedIndex]="fearAndGreedIndex" |
|||
/> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="mb-3 row"> |
|||
<div class="col-xs-12 col-md-10 offset-md-1"> |
|||
<gf-benchmark |
|||
[benchmarks]="benchmarks" |
|||
[deviceType]="deviceType" |
|||
[locale]="user?.settings?.locale || undefined" |
|||
[user]="user" |
|||
/> |
|||
@if (benchmarks?.length > 0) { |
|||
<div |
|||
class="gf-text-wrap-balance line-height-1 mt-3 text-center text-muted" |
|||
> |
|||
<small i18n> |
|||
Calculations are based on delayed market data and may not be |
|||
displayed in real-time.</small |
|||
> |
|||
</div> |
|||
} |
|||
</div> |
|||
</div> |
|||
</div> |
@ -0,0 +1,7 @@ |
|||
:host { |
|||
display: block; |
|||
|
|||
gf-line-chart { |
|||
aspect-ratio: 16 / 9; |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface'; |
|||
|
|||
export interface MarketDataOfMarketsResponse { |
|||
fearAndGreedIndex: { |
|||
CRYPTOCURRENCIES: SymbolItem; |
|||
STOCKS: SymbolItem; |
|||
}; |
|||
} |
@ -0,0 +1 @@ |
|||
export type FearAndGreedIndexMode = 'CRYPTOCURRENCIES' | 'STOCKS'; |
Loading…
Reference in new issue