parent
b8626c2086
commit
4e7b7375a9
@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added the _OpenFIGI_ data enhancer for _Financial Instrument Global Identifier_ (FIGI)
|
||||||
|
- Added `figi`, `figiComposite` and `figiShareClass` to the asset profile model
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Moved the fees on account level feature from experimental to general availability
|
- Moved the fees on account level feature from experimental to general availability
|
||||||
|
@ -280,6 +280,9 @@ export class ImportService {
|
|||||||
createdAt,
|
createdAt,
|
||||||
currency,
|
currency,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
figi,
|
||||||
|
figiComposite,
|
||||||
|
figiShareClass,
|
||||||
id,
|
id,
|
||||||
isin,
|
isin,
|
||||||
name,
|
name,
|
||||||
@ -350,6 +353,9 @@ export class ImportService {
|
|||||||
createdAt,
|
createdAt,
|
||||||
currency,
|
currency,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
figi,
|
||||||
|
figiComposite,
|
||||||
|
figiShareClass,
|
||||||
id,
|
id,
|
||||||
isin,
|
isin,
|
||||||
name,
|
name,
|
||||||
@ -509,6 +515,9 @@ export class ImportService {
|
|||||||
comment: null,
|
comment: null,
|
||||||
countries: null,
|
countries: null,
|
||||||
createdAt: undefined,
|
createdAt: undefined,
|
||||||
|
figi: null,
|
||||||
|
figiComposite: null,
|
||||||
|
figiShareClass: null,
|
||||||
id: undefined,
|
id: undefined,
|
||||||
isin: null,
|
isin: null,
|
||||||
name: null,
|
name: null,
|
||||||
|
@ -38,6 +38,7 @@ export class ConfigurationService {
|
|||||||
JWT_SECRET_KEY: str({}),
|
JWT_SECRET_KEY: str({}),
|
||||||
MAX_ACTIVITIES_TO_IMPORT: num({ default: Number.MAX_SAFE_INTEGER }),
|
MAX_ACTIVITIES_TO_IMPORT: num({ default: Number.MAX_SAFE_INTEGER }),
|
||||||
MAX_ITEM_IN_CACHE: num({ default: 9999 }),
|
MAX_ITEM_IN_CACHE: num({ default: 9999 }),
|
||||||
|
OPEN_FIGI_API_KEY: str({ default: '' }),
|
||||||
PORT: port({ default: 3333 }),
|
PORT: port({ default: 3333 }),
|
||||||
RAPID_API_API_KEY: str({ default: '' }),
|
RAPID_API_API_KEY: str({ default: '' }),
|
||||||
REDIS_HOST: str({ default: 'localhost' }),
|
REDIS_HOST: str({ default: 'localhost' }),
|
||||||
|
@ -164,6 +164,9 @@ export class DataGatheringService {
|
|||||||
countries,
|
countries,
|
||||||
currency,
|
currency,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
figi,
|
||||||
|
figiComposite,
|
||||||
|
figiShareClass,
|
||||||
isin,
|
isin,
|
||||||
name,
|
name,
|
||||||
sectors,
|
sectors,
|
||||||
@ -178,6 +181,9 @@ export class DataGatheringService {
|
|||||||
countries,
|
countries,
|
||||||
currency,
|
currency,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
figi,
|
||||||
|
figiComposite,
|
||||||
|
figiShareClass,
|
||||||
isin,
|
isin,
|
||||||
name,
|
name,
|
||||||
sectors,
|
sectors,
|
||||||
@ -189,6 +195,9 @@ export class DataGatheringService {
|
|||||||
assetSubClass,
|
assetSubClass,
|
||||||
countries,
|
countries,
|
||||||
currency,
|
currency,
|
||||||
|
figi,
|
||||||
|
figiComposite,
|
||||||
|
figiShareClass,
|
||||||
isin,
|
isin,
|
||||||
name,
|
name,
|
||||||
sectors,
|
sectors,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
||||||
import { CryptocurrencyModule } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.module';
|
import { CryptocurrencyModule } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.module';
|
||||||
|
import { OpenFigiDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/openfigi/openfigi.service';
|
||||||
import { TrackinsightDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/trackinsight/trackinsight.service';
|
import { TrackinsightDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/trackinsight/trackinsight.service';
|
||||||
import { YahooFinanceDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service';
|
import { YahooFinanceDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
@ -9,6 +10,7 @@ import { DataEnhancerService } from './data-enhancer.service';
|
|||||||
@Module({
|
@Module({
|
||||||
exports: [
|
exports: [
|
||||||
DataEnhancerService,
|
DataEnhancerService,
|
||||||
|
OpenFigiDataEnhancerService,
|
||||||
TrackinsightDataEnhancerService,
|
TrackinsightDataEnhancerService,
|
||||||
YahooFinanceDataEnhancerService,
|
YahooFinanceDataEnhancerService,
|
||||||
'DataEnhancers'
|
'DataEnhancers'
|
||||||
@ -16,15 +18,21 @@ import { DataEnhancerService } from './data-enhancer.service';
|
|||||||
imports: [ConfigurationModule, CryptocurrencyModule],
|
imports: [ConfigurationModule, CryptocurrencyModule],
|
||||||
providers: [
|
providers: [
|
||||||
DataEnhancerService,
|
DataEnhancerService,
|
||||||
|
OpenFigiDataEnhancerService,
|
||||||
TrackinsightDataEnhancerService,
|
TrackinsightDataEnhancerService,
|
||||||
YahooFinanceDataEnhancerService,
|
YahooFinanceDataEnhancerService,
|
||||||
{
|
{
|
||||||
inject: [
|
inject: [
|
||||||
|
OpenFigiDataEnhancerService,
|
||||||
TrackinsightDataEnhancerService,
|
TrackinsightDataEnhancerService,
|
||||||
YahooFinanceDataEnhancerService
|
YahooFinanceDataEnhancerService
|
||||||
],
|
],
|
||||||
provide: 'DataEnhancers',
|
provide: 'DataEnhancers',
|
||||||
useFactory: (trackinsight, yahooFinance) => [trackinsight, yahooFinance]
|
useFactory: (openfigi, trackinsight, yahooFinance) => [
|
||||||
|
openfigi,
|
||||||
|
trackinsight,
|
||||||
|
yahooFinance
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||||
|
import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface';
|
||||||
|
import { DEFAULT_REQUEST_TIMEOUT } from '@ghostfolio/common/config';
|
||||||
|
import { parseSymbol } from '@ghostfolio/common/helper';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { SymbolProfile } from '@prisma/client';
|
||||||
|
import got, { Headers } from 'got';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class OpenFigiDataEnhancerService implements DataEnhancerInterface {
|
||||||
|
private static baseUrl = 'https://api.openfigi.com';
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private readonly configurationService: ConfigurationService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async enhance({
|
||||||
|
response,
|
||||||
|
symbol
|
||||||
|
}: {
|
||||||
|
response: Partial<SymbolProfile>;
|
||||||
|
symbol: string;
|
||||||
|
}): Promise<Partial<SymbolProfile>> {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
response.assetClass === 'EQUITY' &&
|
||||||
|
(response.assetSubClass === 'ETF' || response.assetSubClass === 'STOCK')
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers: Headers = {};
|
||||||
|
const { exchange, ticker } = parseSymbol({
|
||||||
|
symbol,
|
||||||
|
dataSource: response.dataSource
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.configurationService.get('OPEN_FIGI_API_KEY')) {
|
||||||
|
headers['X-OPENFIGI-APIKEY'] =
|
||||||
|
this.configurationService.get('OPEN_FIGI_API_KEY');
|
||||||
|
}
|
||||||
|
|
||||||
|
let abortController = new AbortController();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
abortController.abort();
|
||||||
|
}, DEFAULT_REQUEST_TIMEOUT);
|
||||||
|
|
||||||
|
const mappings = await got
|
||||||
|
.post(`${OpenFigiDataEnhancerService.baseUrl}/v3/mapping`, {
|
||||||
|
headers,
|
||||||
|
json: [{ exchCode: exchange, idType: 'TICKER', idValue: ticker }],
|
||||||
|
// @ts-ignore
|
||||||
|
signal: abortController.signal
|
||||||
|
})
|
||||||
|
.json<any[]>();
|
||||||
|
|
||||||
|
if (mappings?.length === 1 && mappings[0].data?.length === 1) {
|
||||||
|
const { compositeFIGI, figi, shareClassFIGI } = mappings[0].data[0];
|
||||||
|
|
||||||
|
if (figi) {
|
||||||
|
response.figi = figi;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compositeFIGI) {
|
||||||
|
response.figiComposite = compositeFIGI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shareClassFIGI) {
|
||||||
|
response.figiShareClass = shareClassFIGI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getName() {
|
||||||
|
return 'OPENFIGI';
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTestSymbol() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@ export interface Environment extends CleanedEnvAccessors {
|
|||||||
JWT_SECRET_KEY: string;
|
JWT_SECRET_KEY: string;
|
||||||
MAX_ACTIVITIES_TO_IMPORT: number;
|
MAX_ACTIVITIES_TO_IMPORT: number;
|
||||||
MAX_ITEM_IN_CACHE: number;
|
MAX_ITEM_IN_CACHE: number;
|
||||||
|
OPEN_FIGI_API_KEY: string;
|
||||||
PORT: number;
|
PORT: number;
|
||||||
RAPID_API_API_KEY: string;
|
RAPID_API_API_KEY: string;
|
||||||
REDIS_HOST: string;
|
REDIS_HOST: string;
|
||||||
|
@ -322,6 +322,15 @@ export function parseDate(date: string): Date | null {
|
|||||||
return parseISO(date);
|
return parseISO(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseSymbol({ dataSource, symbol }: UniqueAsset) {
|
||||||
|
const [ticker, exchange] = symbol.split('.');
|
||||||
|
|
||||||
|
return {
|
||||||
|
ticker,
|
||||||
|
exchange: exchange ?? (dataSource === 'YAHOO' ? 'US' : undefined)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function prettifySymbol(aSymbol: string): string {
|
export function prettifySymbol(aSymbol: string): string {
|
||||||
return aSymbol?.replace(ghostfolioScraperApiSymbolPrefix, '');
|
return aSymbol?.replace(ghostfolioScraperApiSymbolPrefix, '');
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "SymbolProfile"
|
||||||
|
ADD COLUMN "figi" TEXT,
|
||||||
|
ADD COLUMN "figiComposite" TEXT,
|
||||||
|
ADD COLUMN "figiShareClass" TEXT;
|
@ -132,6 +132,9 @@ model SymbolProfile {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
currency String
|
currency String
|
||||||
dataSource DataSource
|
dataSource DataSource
|
||||||
|
figi String?
|
||||||
|
figiComposite String?
|
||||||
|
figiShareClass String?
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
isin String?
|
isin String?
|
||||||
name String?
|
name String?
|
||||||
|
Loading…
x
Reference in New Issue
Block a user