Browse Source

Harmonize sector names

pull/6994/head
Thomas Kaul 3 days ago
parent
commit
a414c44013
  1. 29
      apps/api/src/helper/sector.helper.ts
  2. 15
      apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts
  3. 66
      apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts
  4. 2
      apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
  5. 2
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  6. 17
      libs/common/src/lib/config.ts
  7. 2
      libs/common/src/lib/types/index.ts
  8. 13
      libs/common/src/lib/types/sector-name.type.ts
  9. 20
      libs/ui/src/lib/i18n.ts

29
apps/api/src/helper/sector.helper.ts

@ -0,0 +1,29 @@
import { SECTORS, UNKNOWN_KEY } from '@ghostfolio/common/config';
import { Logger } from '@nestjs/common';
export function getSectorName({
aliases = {},
name
}: {
aliases?: Record<string, string>;
name: string;
}): string {
const mappedName = aliases[name];
if (mappedName) {
return mappedName;
}
if ((SECTORS as readonly string[]).includes(name)) {
return name;
}
if (name) {
const logger = new Logger('getSectorName');
logger.warn(`Could not map the sector "${name}" to the ontology`);
}
return UNKNOWN_KEY;
}

15
apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts

@ -1,10 +1,12 @@
import { getCountryCodeByName } from '@ghostfolio/api/helper/country.helper';
import { getSectorName } from '@ghostfolio/api/helper/sector.helper';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface';
import { FetchService } from '@ghostfolio/api/services/fetch/fetch.service';
import { Holding } from '@ghostfolio/common/interfaces';
import { Country } from '@ghostfolio/common/interfaces/country.interface';
import { Sector } from '@ghostfolio/common/interfaces/sector.interface';
import { SectorName } from '@ghostfolio/common/types';
import { Injectable, Logger } from '@nestjs/common';
import { SymbolProfile } from '@prisma/client';
@ -17,11 +19,13 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface {
USA: 'United States'
};
private static holdingsWeightTreshold = 0.85;
private static sectorsMapping = {
private static sectorsMapping: Record<string, SectorName> = {
'Consumer Discretionary': 'Consumer Cyclical',
'Consumer Defensive': 'Consumer Staples',
'Consumer Staples': 'Consumer Defensive',
Financials: 'Financial Services',
'Health Care': 'Healthcare',
'Information Technology': 'Technology'
'Information Technology': 'Technology',
Materials: 'Basic Materials'
};
private readonly logger = new Logger(TrackinsightDataEnhancerService.name);
@ -155,7 +159,10 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface {
holdings?.sectors ?? {}
)) {
response.sectors.push({
name: TrackinsightDataEnhancerService.sectorsMapping[name] ?? name,
name: getSectorName({
name,
aliases: TrackinsightDataEnhancerService.sectorsMapping
}),
weight: value.weight
});
}

66
apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts

@ -1,12 +1,13 @@
import { getSectorName } from '@ghostfolio/api/helper/sector.helper';
import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service';
import { AssetProfileDelistedError } from '@ghostfolio/api/services/data-provider/errors/asset-profile-delisted.error';
import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface';
import {
DEFAULT_CURRENCY,
REPLACE_NAME_PARTS,
UNKNOWN_KEY
REPLACE_NAME_PARTS
} from '@ghostfolio/common/config';
import { isCurrency } from '@ghostfolio/common/helper';
import { SectorName } from '@ghostfolio/common/types';
import { Injectable, Logger } from '@nestjs/common';
import {
@ -23,6 +24,20 @@ import type { Price } from 'yahoo-finance2/esm/src/modules/quoteSummary-iface';
@Injectable()
export class YahooFinanceDataEnhancerService implements DataEnhancerInterface {
private static sectorsMapping: Record<string, SectorName> = {
basic_materials: 'Basic Materials',
communication_services: 'Communication Services',
consumer_cyclical: 'Consumer Cyclical',
consumer_defensive: 'Consumer Defensive',
energy: 'Energy',
financial_services: 'Financial Services',
healthcare: 'Healthcare',
industrials: 'Industrials',
realestate: 'Real Estate',
technology: 'Technology',
utilities: 'Utilities'
};
private readonly logger = new Logger(YahooFinanceDataEnhancerService.name);
private readonly yahooFinance = new YahooFinance({
@ -224,7 +239,10 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface {
.flatMap((sectorWeighting) => {
return Object.entries(sectorWeighting).map(([sector, weight]) => {
return {
name: this.parseSector(sector),
name: getSectorName({
aliases: YahooFinanceDataEnhancerService.sectorsMapping,
name: sector
}),
weight: weight as number
};
});
@ -331,46 +349,4 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface {
return { assetClass, assetSubClass };
}
private parseSector(aString: string) {
let sector = UNKNOWN_KEY;
switch (aString) {
case 'basic_materials':
sector = 'Basic Materials';
break;
case 'communication_services':
sector = 'Communication Services';
break;
case 'consumer_cyclical':
sector = 'Consumer Cyclical';
break;
case 'consumer_defensive':
sector = 'Consumer Staples';
break;
case 'energy':
sector = 'Energy';
break;
case 'financial_services':
sector = 'Financial Services';
break;
case 'healthcare':
sector = 'Healthcare';
break;
case 'industrials':
sector = 'Industrials';
break;
case 'realestate':
sector = 'Real Estate';
break;
case 'technology':
sector = 'Technology';
break;
case 'utilities':
sector = 'Utilities';
break;
}
return sector;
}
}

2
apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts

@ -441,6 +441,8 @@ export class GfHoldingDetailDialogComponent implements OnInit {
if (SymbolProfile?.sectors?.length > 0) {
for (const sector of SymbolProfile.sectors) {
sector.name = translate(sector.name);
this.sectors[sector.name] = {
name: sector.name,
value: sector.weight

2
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts

@ -442,7 +442,7 @@ export class GfAllocationsPageComponent implements OnInit {
: position.valueInPercentage);
} else {
this.sectors[name] = {
name,
name: translate(name),
value:
weight *
(isNumber(position.valueInBaseCurrency)

17
libs/common/src/lib/config.ts

@ -2,7 +2,7 @@ import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client';
import { JobOptions, JobStatus } from 'bull';
import ms from 'ms';
import { ColorScheme, DateRange } from './types';
import { ColorScheme, DateRange, SectorName } from './types';
export const ghostfolioPrefix = 'GF';
export const ghostfolioScraperApiSymbolPrefix = `_${ghostfolioPrefix}_`;
@ -55,6 +55,21 @@ export const ASSET_CLASS_MAPPING = new Map<AssetClass, AssetSubClass[]>([
export const BULL_BOARD_COOKIE_NAME = 'bull_board_token';
export const SECTORS = [
'Basic Materials',
'Communication Services',
'Consumer Cyclical',
'Consumer Defensive',
'Energy',
'Financial Services',
'Healthcare',
'Industrials',
'Other',
'Real Estate',
'Technology',
'Utilities'
] as const satisfies readonly SectorName[];
/**
* WARNING: This route is mirrored in `apps/client/proxy.conf.json`.
* If you update this value, you must also update the proxy configuration.

2
libs/common/src/lib/types/index.ts

@ -17,6 +17,7 @@ import type { MarketState } from './market-state.type';
import type { Market } from './market.type';
import type { OrderWithAccount } from './order-with-account.type';
import type { RequestWithUser } from './request-with-user.type';
import type { SectorName } from './sector-name.type';
import type { SubscriptionOfferKey } from './subscription-offer-key.type';
import type { UserWithSettings } from './user-with-settings.type';
import type { ViewMode } from './view-mode.type';
@ -41,6 +42,7 @@ export type {
MarketState,
OrderWithAccount,
RequestWithUser,
SectorName,
SubscriptionOfferKey,
UserWithSettings,
ViewMode

13
libs/common/src/lib/types/sector-name.type.ts

@ -0,0 +1,13 @@
export type SectorName =
| 'Basic Materials'
| 'Communication Services'
| 'Consumer Cyclical'
| 'Consumer Defensive'
| 'Energy'
| 'Financial Services'
| 'Healthcare'
| 'Industrials'
| 'Other'
| 'Real Estate'
| 'Technology'
| 'Utilities';

20
libs/ui/src/lib/i18n.ts

@ -1,3 +1,5 @@
import type { SectorName } from '@ghostfolio/common/types';
import '@angular/localize/init';
const locales = {
@ -107,8 +109,22 @@ const locales = {
EXTREME_GREED: $localize`Extreme Greed`,
FEAR: $localize`Fear`,
GREED: $localize`Greed`,
NEUTRAL: $localize`Neutral`
};
NEUTRAL: $localize`Neutral`,
// Sectors
'Basic Materials': $localize`Basic Materials`,
'Communication Services': $localize`Communication Services`,
'Consumer Cyclical': $localize`Consumer Cyclical`,
'Consumer Defensive': $localize`Consumer Defensive`,
Energy: $localize`Energy`,
'Financial Services': $localize`Financial Services`,
Healthcare: $localize`Healthcare`,
Industrials: $localize`Industrials`,
Other: $localize`Other`,
'Real Estate': $localize`Real Estate`,
Technology: $localize`Technology`,
Utilities: $localize`Utilities`
} satisfies Record<SectorName, string> & Record<string, string>;
export function translate(aKey: string): string {
return locales[aKey] ?? aKey;

Loading…
Cancel
Save