parent
cf582b2e98
commit
5cb69291f5
@ -1,3 +1,4 @@
|
|||||||
|
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
|
|
||||||
export interface PortfolioPosition {
|
export interface PortfolioPosition {
|
||||||
@ -7,10 +8,10 @@ export interface PortfolioPosition {
|
|||||||
grossPerformancePercent: number;
|
grossPerformancePercent: number;
|
||||||
industry?: string;
|
industry?: string;
|
||||||
investment: number;
|
investment: number;
|
||||||
isMarketOpen: boolean;
|
|
||||||
marketChange?: number;
|
marketChange?: number;
|
||||||
marketChangePercent?: number;
|
marketChangePercent?: number;
|
||||||
marketPrice: number;
|
marketPrice: number;
|
||||||
|
marketState: MarketState;
|
||||||
name: string;
|
name: string;
|
||||||
platforms: {
|
platforms: {
|
||||||
[name: string]: { current: number; original: number };
|
[name: string]: { current: number; original: number };
|
||||||
|
@ -5,9 +5,11 @@ import { Currency, Role, Type } from '@prisma/client';
|
|||||||
import { ConfigurationService } from '../services/configuration.service';
|
import { ConfigurationService } from '../services/configuration.service';
|
||||||
import { DataProviderService } from '../services/data-provider.service';
|
import { DataProviderService } from '../services/data-provider.service';
|
||||||
import { AlphaVantageService } from '../services/data-provider/alpha-vantage/alpha-vantage.service';
|
import { AlphaVantageService } from '../services/data-provider/alpha-vantage/alpha-vantage.service';
|
||||||
|
import { GhostfolioScraperApiService } from '../services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||||
import { RakutenRapidApiService } from '../services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
import { RakutenRapidApiService } from '../services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||||
import { YahooFinanceService } from '../services/data-provider/yahoo-finance/yahoo-finance.service';
|
import { YahooFinanceService } from '../services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||||
import { ExchangeRateDataService } from '../services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '../services/exchange-rate-data.service';
|
||||||
|
import { MarketState } from '../services/interfaces/interfaces';
|
||||||
import { PrismaService } from '../services/prisma.service';
|
import { PrismaService } from '../services/prisma.service';
|
||||||
import { RulesService } from '../services/rules.service';
|
import { RulesService } from '../services/rules.service';
|
||||||
import { Portfolio } from './portfolio';
|
import { Portfolio } from './portfolio';
|
||||||
@ -17,6 +19,7 @@ describe('Portfolio', () => {
|
|||||||
let configurationService: ConfigurationService;
|
let configurationService: ConfigurationService;
|
||||||
let dataProviderService: DataProviderService;
|
let dataProviderService: DataProviderService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
|
let ghostfolioScraperApiService: GhostfolioScraperApiService;
|
||||||
let portfolio: Portfolio;
|
let portfolio: Portfolio;
|
||||||
let prismaService: PrismaService;
|
let prismaService: PrismaService;
|
||||||
let rakutenRapidApiService: RakutenRapidApiService;
|
let rakutenRapidApiService: RakutenRapidApiService;
|
||||||
@ -31,6 +34,7 @@ describe('Portfolio', () => {
|
|||||||
ConfigurationService,
|
ConfigurationService,
|
||||||
DataProviderService,
|
DataProviderService,
|
||||||
ExchangeRateDataService,
|
ExchangeRateDataService,
|
||||||
|
GhostfolioScraperApiService,
|
||||||
PrismaService,
|
PrismaService,
|
||||||
RakutenRapidApiService,
|
RakutenRapidApiService,
|
||||||
RulesService,
|
RulesService,
|
||||||
@ -44,6 +48,9 @@ describe('Portfolio', () => {
|
|||||||
exchangeRateDataService = app.get<ExchangeRateDataService>(
|
exchangeRateDataService = app.get<ExchangeRateDataService>(
|
||||||
ExchangeRateDataService
|
ExchangeRateDataService
|
||||||
);
|
);
|
||||||
|
ghostfolioScraperApiService = app.get<GhostfolioScraperApiService>(
|
||||||
|
GhostfolioScraperApiService
|
||||||
|
);
|
||||||
prismaService = app.get<PrismaService>(PrismaService);
|
prismaService = app.get<PrismaService>(PrismaService);
|
||||||
rakutenRapidApiService = app.get<RakutenRapidApiService>(
|
rakutenRapidApiService = app.get<RakutenRapidApiService>(
|
||||||
RakutenRapidApiService
|
RakutenRapidApiService
|
||||||
@ -154,8 +161,8 @@ describe('Portfolio', () => {
|
|||||||
Currency.USD,
|
Currency.USD,
|
||||||
baseCurrency
|
baseCurrency
|
||||||
),
|
),
|
||||||
isMarketOpen: true,
|
|
||||||
// marketPrice: 57973.008,
|
// marketPrice: 57973.008,
|
||||||
|
marketState: MarketState.open,
|
||||||
name: 'Bitcoin USD',
|
name: 'Bitcoin USD',
|
||||||
platforms: {
|
platforms: {
|
||||||
Other: {
|
Other: {
|
||||||
|
@ -8,7 +8,8 @@ import { DataProviderInterface } from '../../interfaces/data-provider.interface'
|
|||||||
import { Granularity } from '../../interfaces/granularity.type';
|
import { Granularity } from '../../interfaces/granularity.type';
|
||||||
import {
|
import {
|
||||||
IDataProviderHistoricalResponse,
|
IDataProviderHistoricalResponse,
|
||||||
IDataProviderResponse
|
IDataProviderResponse,
|
||||||
|
MarketState
|
||||||
} from '../../interfaces/interfaces';
|
} from '../../interfaces/interfaces';
|
||||||
import { PrismaService } from '../../prisma.service';
|
import { PrismaService } from '../../prisma.service';
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
|
|||||||
[symbol]: {
|
[symbol]: {
|
||||||
marketPrice,
|
marketPrice,
|
||||||
currency: scraperConfig?.currency,
|
currency: scraperConfig?.currency,
|
||||||
isMarketOpen: false,
|
marketState: MarketState.delayed,
|
||||||
name: scraperConfig?.name
|
name: scraperConfig?.name
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,8 @@ import { DataProviderInterface } from '../../interfaces/data-provider.interface'
|
|||||||
import { Granularity } from '../../interfaces/granularity.type';
|
import { Granularity } from '../../interfaces/granularity.type';
|
||||||
import {
|
import {
|
||||||
IDataProviderHistoricalResponse,
|
IDataProviderHistoricalResponse,
|
||||||
IDataProviderResponse
|
IDataProviderResponse,
|
||||||
|
MarketState
|
||||||
} from '../../interfaces/interfaces';
|
} from '../../interfaces/interfaces';
|
||||||
import { PrismaService } from '../../prisma.service';
|
import { PrismaService } from '../../prisma.service';
|
||||||
|
|
||||||
@ -38,8 +39,8 @@ export class RakutenRapidApiService implements DataProviderInterface {
|
|||||||
return {
|
return {
|
||||||
'GF.FEAR_AND_GREED_INDEX': {
|
'GF.FEAR_AND_GREED_INDEX': {
|
||||||
currency: undefined,
|
currency: undefined,
|
||||||
isMarketOpen: true,
|
|
||||||
marketPrice: fgi.now.value,
|
marketPrice: fgi.now.value,
|
||||||
|
marketState: MarketState.open,
|
||||||
name: RakutenRapidApiService.FEAR_AND_GREED_INDEX_NAME
|
name: RakutenRapidApiService.FEAR_AND_GREED_INDEX_NAME
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
IDataProviderHistoricalResponse,
|
IDataProviderHistoricalResponse,
|
||||||
IDataProviderResponse,
|
IDataProviderResponse,
|
||||||
Industry,
|
Industry,
|
||||||
|
MarketState,
|
||||||
Sector,
|
Sector,
|
||||||
Type
|
Type
|
||||||
} from '../../interfaces/interfaces';
|
} from '../../interfaces/interfaces';
|
||||||
@ -49,8 +50,10 @@ export class YahooFinanceService implements DataProviderInterface {
|
|||||||
response[symbol] = {
|
response[symbol] = {
|
||||||
currency: parseCurrency(value.price?.currency),
|
currency: parseCurrency(value.price?.currency),
|
||||||
exchange: this.parseExchange(value.price?.exchangeName),
|
exchange: this.parseExchange(value.price?.exchangeName),
|
||||||
isMarketOpen:
|
marketState:
|
||||||
value.price?.marketState === 'REGULAR' || isCrypto(symbol),
|
value.price?.marketState === 'REGULAR' || isCrypto(symbol)
|
||||||
|
? MarketState.open
|
||||||
|
: MarketState.closed,
|
||||||
marketPrice: value.price?.regularMarketPrice || 0,
|
marketPrice: value.price?.regularMarketPrice || 0,
|
||||||
name: value.price?.longName || value.price?.shortName || symbol,
|
name: value.price?.longName || value.price?.shortName || symbol,
|
||||||
type: this.parseType(this.getType(symbol, value))
|
type: this.parseType(this.getType(symbol, value))
|
||||||
|
@ -12,6 +12,12 @@ export const Industry = {
|
|||||||
Software: 'Software'
|
Software: 'Software'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const MarketState = {
|
||||||
|
closed: 'closed',
|
||||||
|
delayed: 'delayed',
|
||||||
|
open: 'open'
|
||||||
|
};
|
||||||
|
|
||||||
export const Sector = {
|
export const Sector = {
|
||||||
Consumer: 'Consumer',
|
Consumer: 'Consumer',
|
||||||
Healthcare: 'Healthcare',
|
Healthcare: 'Healthcare',
|
||||||
@ -47,10 +53,10 @@ export interface IDataProviderResponse {
|
|||||||
currency: Currency;
|
currency: Currency;
|
||||||
exchange?: string;
|
exchange?: string;
|
||||||
industry?: Industry;
|
industry?: Industry;
|
||||||
isMarketOpen: boolean;
|
|
||||||
marketChange?: number;
|
marketChange?: number;
|
||||||
marketChangePercent?: number;
|
marketChangePercent?: number;
|
||||||
marketPrice: number;
|
marketPrice: number;
|
||||||
|
marketState: MarketState;
|
||||||
name: string;
|
name: string;
|
||||||
sector?: Sector;
|
sector?: Sector;
|
||||||
type?: Type;
|
type?: Type;
|
||||||
@ -59,6 +65,8 @@ export interface IDataProviderResponse {
|
|||||||
|
|
||||||
export type Industry = typeof Industry[keyof typeof Industry];
|
export type Industry = typeof Industry[keyof typeof Industry];
|
||||||
|
|
||||||
|
export type MarketState = typeof MarketState[keyof typeof MarketState];
|
||||||
|
|
||||||
export type Sector = typeof Sector[keyof typeof Sector];
|
export type Sector = typeof Sector[keyof typeof Sector];
|
||||||
|
|
||||||
export type Type = typeof Type[keyof typeof Type];
|
export type Type = typeof Type[keyof typeof Type];
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<gf-trend-indicator
|
<gf-trend-indicator
|
||||||
class="d-flex"
|
class="d-flex"
|
||||||
[isLoading]="isLoading"
|
[isLoading]="isLoading"
|
||||||
[isPaused]="!position?.isMarketOpen"
|
[marketState]="position?.marketState"
|
||||||
[range]="range"
|
[range]="range"
|
||||||
[value]="position?.grossPerformancePercent"
|
[value]="position?.grossPerformancePercent"
|
||||||
></gf-trend-indicator>
|
></gf-trend-indicator>
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
OnInit
|
OnInit
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||||
|
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-positions',
|
selector: 'gf-positions',
|
||||||
@ -40,7 +41,10 @@ export class PositionsComponent implements OnChanges, OnInit {
|
|||||||
this.positionsWithPriority = [];
|
this.positionsWithPriority = [];
|
||||||
|
|
||||||
for (const [, portfolioPosition] of Object.entries(this.positions)) {
|
for (const [, portfolioPosition] of Object.entries(this.positions)) {
|
||||||
if (portfolioPosition.isMarketOpen || this.range !== '1d') {
|
if (
|
||||||
|
portfolioPosition.marketState === MarketState.open ||
|
||||||
|
this.range !== '1d'
|
||||||
|
) {
|
||||||
// Only show positions where the market is open in today's view
|
// Only show positions where the market is open in today's view
|
||||||
this.positionsWithPriority.push(portfolioPosition);
|
this.positionsWithPriority.push(portfolioPosition);
|
||||||
} else {
|
} else {
|
||||||
|
@ -10,12 +10,21 @@
|
|||||||
|
|
||||||
<ng-template #other>
|
<ng-template #other>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
*ngIf="isPaused && range === '1d'; else trend"
|
*ngIf="marketState === 'closed' && range === '1d'; else delayed"
|
||||||
class="text-muted"
|
class="text-muted"
|
||||||
name="pause-circle-outline"
|
name="pause-circle-outline"
|
||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
|
<ng-template #delayed>
|
||||||
|
<ion-icon
|
||||||
|
*ngIf="marketState === 'delayed' && range === '1d'; else trend"
|
||||||
|
class="text-muted"
|
||||||
|
name="time-outline"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
</ion-icon>
|
||||||
|
</ng-template>
|
||||||
<ng-template #trend>
|
<ng-template #trend>
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
OnInit
|
OnInit
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-trend-indicator',
|
selector: 'gf-trend-indicator',
|
||||||
@ -13,7 +14,7 @@ import {
|
|||||||
})
|
})
|
||||||
export class TrendIndicatorComponent implements OnInit {
|
export class TrendIndicatorComponent implements OnInit {
|
||||||
@Input() isLoading: boolean;
|
@Input() isLoading: boolean;
|
||||||
@Input() isPaused: boolean;
|
@Input() marketState: MarketState;
|
||||||
@Input() range: string;
|
@Input() range: string;
|
||||||
@Input() value: number;
|
@Input() value: number;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user