Browse Source

Merge branch 'main' into feature/Some_performance_optimizations

pull/5027/head
dandevaud 2 years ago
committed by GitHub
parent
commit
2a1d81166d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      CHANGELOG.md
  2. 6
      apps/api/src/app/benchmark/benchmark.service.ts
  3. 2
      apps/api/src/app/portfolio/current-rate.service.ts
  4. 26
      apps/api/src/app/portfolio/portfolio.service.ts
  5. 6
      apps/api/src/app/symbol/symbol.service.ts
  6. 42
      apps/api/src/services/data-provider/data-provider.service.ts
  7. 6
      apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts
  8. 21
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  9. 46
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html
  10. 2
      apps/client/src/app/pages/resources/personal-finance-tools/product-page-template.html
  11. 2
      apps/client/src/app/pages/resources/resources-page.html
  12. 2850
      apps/client/src/locales/messages.de.xlf
  13. 2850
      apps/client/src/locales/messages.es.xlf
  14. 2852
      apps/client/src/locales/messages.fr.xlf
  15. 2850
      apps/client/src/locales/messages.it.xlf
  16. 2850
      apps/client/src/locales/messages.nl.xlf
  17. 2850
      apps/client/src/locales/messages.pt.xlf
  18. 2826
      apps/client/src/locales/messages.xlf
  19. 7
      libs/ui/src/lib/i18n.ts
  20. 6
      package.json
  21. 16
      yarn.lock

17
CHANGELOG.md

@ -7,10 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Added hints to the activity types in the create or edit activity dialog
### Changed
- Disabled the caching in the health check endpoints for data providers
## 1.289.0 - 2023-07-14
### Changed
- Upgraded `yahoo-finance2` from version `2.4.1` to `2.4.2`
## 1.288.0 - 2023-07-12
### Changed ### Changed
- Improved the loading state during filtering on the allocations page - Improved the loading state during filtering on the allocations page
- Beautified the names with ampersand (`&`) in the asset profile - Beautified the names with ampersand (`&`) in the asset profile
- Improved the language localization for German (`de`)
## 1.287.0 - 2023-07-09 ## 1.287.0 - 2023-07-09

6
apps/api/src/app/benchmark/benchmark.service.ts

@ -66,11 +66,11 @@ export class BenchmarkService {
const promises: Promise<number>[] = []; const promises: Promise<number>[] = [];
const quotes = await this.dataProviderService.getQuotes( const quotes = await this.dataProviderService.getQuotes({
benchmarkAssetProfiles.map(({ dataSource, symbol }) => { items: benchmarkAssetProfiles.map(({ dataSource, symbol }) => {
return { dataSource, symbol }; return { dataSource, symbol };
}) })
); });
for (const { dataSource, symbol } of benchmarkAssetProfiles) { for (const { dataSource, symbol } of benchmarkAssetProfiles) {
promises.push(this.marketDataService.getMax({ dataSource, symbol })); promises.push(this.marketDataService.getMax({ dataSource, symbol }));

2
apps/api/src/app/portfolio/current-rate.service.ts

@ -38,7 +38,7 @@ export class CurrentRateService {
if (includeToday) { if (includeToday) {
promises.push( promises.push(
this.dataProviderService this.dataProviderService
.getQuotes(dataGatheringItems) .getQuotes({ items: dataGatheringItems })
.then((dataResultProvider) => { .then((dataResultProvider) => {
const result: GetValueObject[] = []; const result: GetValueObject[] = [];
for (const dataGatheringItem of dataGatheringItems) { for (const dataGatheringItem of dataGatheringItems) {

26
apps/api/src/app/portfolio/portfolio.service.ts

@ -504,15 +504,17 @@ export class PortfolioService {
); );
} }
const dataGatheringItems = currentPositions.positions.map((position) => { const dataGatheringItems = currentPositions.positions.map(
({ dataSource, symbol }) => {
return { return {
dataSource: position.dataSource, dataSource,
symbol: position.symbol symbol
}; };
}); }
);
const [dataProviderResponses, symbolProfiles] = await Promise.all([ const [dataProviderResponses, symbolProfiles] = await Promise.all([
this.dataProviderService.getQuotes(dataGatheringItems), this.dataProviderService.getQuotes({ items: dataGatheringItems }),
this.symbolProfileService.getSymbolProfiles(dataGatheringItems) this.symbolProfileService.getSymbolProfiles(dataGatheringItems)
]); ]);
@ -971,9 +973,9 @@ export class PortfolioService {
) )
}; };
} else { } else {
const currentData = await this.dataProviderService.getQuotes([ const currentData = await this.dataProviderService.getQuotes({
{ dataSource: DataSource.YAHOO, symbol: aSymbol } items: [{ dataSource: DataSource.YAHOO, symbol: aSymbol }]
]); });
const marketPrice = currentData[aSymbol]?.marketPrice; const marketPrice = currentData[aSymbol]?.marketPrice;
let historicalData = await this.dataProviderService.getHistorical( let historicalData = await this.dataProviderService.getHistorical(
@ -1075,15 +1077,15 @@ export class PortfolioService {
(item) => !item.quantity.eq(0) (item) => !item.quantity.eq(0)
); );
const dataGatheringItem = positions.map((position) => { const dataGatheringItems = positions.map(({ dataSource, symbol }) => {
return { return {
dataSource: position.dataSource, dataSource,
symbol: position.symbol symbol
}; };
}); });
const [dataProviderResponses, symbolProfiles] = await Promise.all([ const [dataProviderResponses, symbolProfiles] = await Promise.all([
this.dataProviderService.getQuotes(dataGatheringItem), this.dataProviderService.getQuotes({ items: dataGatheringItems }),
this.symbolProfileService.getSymbolProfiles( this.symbolProfileService.getSymbolProfiles(
positions.map(({ dataSource, symbol }) => { positions.map(({ dataSource, symbol }) => {
return { dataSource, symbol }; return { dataSource, symbol };

6
apps/api/src/app/symbol/symbol.service.ts

@ -27,9 +27,9 @@ export class SymbolService {
dataGatheringItem: IDataGatheringItem; dataGatheringItem: IDataGatheringItem;
includeHistoricalData?: number; includeHistoricalData?: number;
}): Promise<SymbolItem> { }): Promise<SymbolItem> {
const quotes = await this.dataProviderService.getQuotes([ const quotes = await this.dataProviderService.getQuotes({
dataGatheringItem items: [dataGatheringItem]
]); });
const { currency, marketPrice } = quotes[dataGatheringItem.symbol] ?? {}; const { currency, marketPrice } = quotes[dataGatheringItem.symbol] ?? {};
if (dataGatheringItem.dataSource && marketPrice >= 0) { if (dataGatheringItem.dataSource && marketPrice >= 0) {

42
apps/api/src/services/data-provider/data-provider.service.ts

@ -3,7 +3,6 @@ import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.in
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface';
import { import {
IDataGatheringItem,
IDataProviderHistoricalResponse, IDataProviderHistoricalResponse,
IDataProviderResponse IDataProviderResponse
} from '@ghostfolio/api/services/interfaces/interfaces'; } from '@ghostfolio/api/services/interfaces/interfaces';
@ -12,6 +11,7 @@ import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { PROPERTY_DATA_SOURCE_MAPPING } from '@ghostfolio/common/config'; import { PROPERTY_DATA_SOURCE_MAPPING } from '@ghostfolio/common/config';
import { DATE_FORMAT, getStartOfUtcDate } from '@ghostfolio/common/helper'; import { DATE_FORMAT, getStartOfUtcDate } from '@ghostfolio/common/helper';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import type { Granularity, UserWithSettings } from '@ghostfolio/common/types'; import type { Granularity, UserWithSettings } from '@ghostfolio/common/types';
import { Inject, Injectable, Logger } from '@nestjs/common'; import { Inject, Injectable, Logger } from '@nestjs/common';
import { DataSource, MarketData, SymbolProfile } from '@prisma/client'; import { DataSource, MarketData, SymbolProfile } from '@prisma/client';
@ -45,12 +45,15 @@ export class DataProviderService {
const dataProvider = this.getDataProvider(dataSource); const dataProvider = this.getDataProvider(dataSource);
const symbol = dataProvider.getTestSymbol(); const symbol = dataProvider.getTestSymbol();
const quotes = await this.getQuotes([ const quotes = await this.getQuotes({
items: [
{ {
dataSource, dataSource,
symbol symbol
} }
]); ],
useCache: false
});
if (quotes[symbol]?.marketPrice > 0) { if (quotes[symbol]?.marketPrice > 0) {
return true; return true;
@ -59,14 +62,16 @@ export class DataProviderService {
return false; return false;
} }
public async getAssetProfiles(items: IDataGatheringItem[]): Promise<{ public async getAssetProfiles(items: UniqueAsset[]): Promise<{
[symbol: string]: Partial<SymbolProfile>; [symbol: string]: Partial<SymbolProfile>;
}> { }> {
const response: { const response: {
[symbol: string]: Partial<SymbolProfile>; [symbol: string]: Partial<SymbolProfile>;
} = {}; } = {};
const itemsGroupedByDataSource = groupBy(items, (item) => item.dataSource); const itemsGroupedByDataSource = groupBy(items, ({ dataSource }) => {
return dataSource;
});
const promises = []; const promises = [];
@ -127,7 +132,7 @@ export class DataProviderService {
} }
public async getHistorical( public async getHistorical(
aItems: IDataGatheringItem[], aItems: UniqueAsset[],
aGranularity: Granularity = 'month', aGranularity: Granularity = 'month',
from: Date, from: Date,
to: Date to: Date
@ -155,11 +160,11 @@ export class DataProviderService {
)}'` )}'`
: ''; : '';
const dataSources = aItems.map((item) => { const dataSources = aItems.map(({ dataSource }) => {
return item.dataSource; return dataSource;
}); });
const symbols = aItems.map((item) => { const symbols = aItems.map(({ symbol }) => {
return item.symbol; return symbol;
}); });
try { try {
@ -192,7 +197,7 @@ export class DataProviderService {
} }
public async getHistoricalRaw( public async getHistoricalRaw(
aDataGatheringItems: IDataGatheringItem[], aDataGatheringItems: UniqueAsset[],
from: Date, from: Date,
to: Date to: Date
): Promise<{ ): Promise<{
@ -229,7 +234,13 @@ export class DataProviderService {
return result; return result;
} }
public async getQuotes(items: IDataGatheringItem[]): Promise<{ public async getQuotes({
items,
useCache = true
}: {
items: UniqueAsset[];
useCache?: boolean;
}): Promise<{
[symbol: string]: IDataProviderResponse; [symbol: string]: IDataProviderResponse;
}> { }> {
const response: { const response: {
@ -238,9 +249,10 @@ export class DataProviderService {
const startTimeTotal = performance.now(); const startTimeTotal = performance.now();
// Get items from cache // Get items from cache
const itemsToFetch: IDataGatheringItem[] = []; const itemsToFetch: UniqueAsset[] = [];
for (const { dataSource, symbol } of items) { for (const { dataSource, symbol } of items) {
if (useCache) {
const quoteString = await this.redisCacheService.get( const quoteString = await this.redisCacheService.get(
this.redisCacheService.getQuoteKey({ dataSource, symbol }) this.redisCacheService.getQuoteKey({ dataSource, symbol })
); );
@ -249,13 +261,13 @@ export class DataProviderService {
try { try {
const cachedDataProviderResponse = JSON.parse(quoteString); const cachedDataProviderResponse = JSON.parse(quoteString);
response[symbol] = cachedDataProviderResponse; response[symbol] = cachedDataProviderResponse;
continue;
} catch {} } catch {}
} }
}
if (!quoteString) {
itemsToFetch.push({ dataSource, symbol }); itemsToFetch.push({ dataSource, symbol });
} }
}
const numberOfItemsInCache = Object.keys(response)?.length; const numberOfItemsInCache = Object.keys(response)?.length;

6
apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts

@ -64,11 +64,11 @@ export class ExchangeRateDataService {
if (Object.keys(result).length !== this.currencyPairs.length) { if (Object.keys(result).length !== this.currencyPairs.length) {
// Load currencies directly from data provider as a fallback // Load currencies directly from data provider as a fallback
// if historical data is not fully available // if historical data is not fully available
const quotes = await this.dataProviderService.getQuotes( const quotes = await this.dataProviderService.getQuotes({
this.currencyPairs.map(({ dataSource, symbol }) => { items: this.currencyPairs.map(({ dataSource, symbol }) => {
return { dataSource, symbol }; return { dataSource, symbol };
}) })
); });
for (const symbol of Object.keys(quotes)) { for (const symbol of Object.keys(quotes)) {
if (isNumber(quotes[symbol].marketPrice)) { if (isNumber(quotes[symbol].marketPrice)) {

21
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -14,23 +14,13 @@ import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { getDateFormatString } from '@ghostfolio/common/helper'; import { getDateFormatString } from '@ghostfolio/common/helper';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
import { AssetClass, AssetSubClass, Tag, Type } from '@prisma/client'; import { AssetClass, AssetSubClass, Tag, Type } from '@prisma/client';
import { isUUID } from 'class-validator'; import { isUUID } from 'class-validator';
import { isString } from 'lodash';
import { EMPTY, Observable, Subject, lastValueFrom, of } from 'rxjs'; import { EMPTY, Observable, Subject, lastValueFrom, of } from 'rxjs';
import { import { catchError, map, startWith, takeUntil } from 'rxjs/operators';
catchError,
debounceTime,
distinctUntilChanged,
map,
startWith,
switchMap,
takeUntil
} from 'rxjs/operators';
import { CreateOrUpdateActivityDialogParams } from './interfaces/interfaces'; import { CreateOrUpdateActivityDialogParams } from './interfaces/interfaces';
@ -61,6 +51,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
public separatorKeysCodes: number[] = [ENTER, COMMA]; public separatorKeysCodes: number[] = [ENTER, COMMA];
public tags: Tag[] = []; public tags: Tag[] = [];
public total = 0; public total = 0;
public typesTranslationMap = new Map<Type, string>();
public Validators = Validators; public Validators = Validators;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -91,6 +82,10 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
}; };
}); });
Object.keys(Type).forEach((type) => {
this.typesTranslationMap[Type[type]] = translate(Type[type]);
});
this.activityForm = this.formBuilder.group({ this.activityForm = this.formBuilder.group({
accountId: [this.data.activity?.accountId, Validators.required], accountId: [this.data.activity?.accountId, Validators.required],
assetClass: [this.data.activity?.SymbolProfile?.assetClass], assetClass: [this.data.activity?.SymbolProfile?.assetClass],
@ -378,10 +373,6 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
}); });
} }
public displayFn(aLookupItem: LookupItem) {
return aLookupItem?.symbol ?? '';
}
public onAddTag(event: MatAutocompleteSelectedEvent) { public onAddTag(event: MatAutocompleteSelectedEvent) {
this.activityForm.controls['tags'].setValue([ this.activityForm.controls['tags'].setValue([
...(this.activityForm.controls['tags'].value ?? []), ...(this.activityForm.controls['tags'].value ?? []),

46
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html

@ -11,14 +11,44 @@
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Type</mat-label> <mat-label i18n>Type</mat-label>
<mat-select formControlName="type"> <mat-select formControlName="type">
<mat-option i18n value="BUY">Buy</mat-option> <mat-select-trigger
<mat-option i18n value="DIVIDEND">Dividend</mat-option> >{{ typesTranslationMap[activityForm.controls['type'].value]
<mat-option i18n value="STAKE" }}</mat-select-trigger
>Stake reward / Stockdividend</mat-option >
> <mat-option class="line-height-1" value="BUY">
<mat-option i18n value="ITEM">Item</mat-option> <span><b>{{ typesTranslationMap['BUY'] }}</b></span>
<mat-option i18n value="LIABILITY">Liability</mat-option> <br />
<mat-option i18n value="SELL">Sell</mat-option> <small class="text-muted text-nowrap" i18n
>Stocks, ETFs, bonds, cryptocurrencies, commodities</small
>
</mat-option>
<mat-option class="line-height-1" value="DIVIDEND">
<span><b>{{ typesTranslationMap['DIVIDEND'] }}</b></span>
</mat-option>
<mat-option class="line-height-1" value="STAKE">
<span><b>{{ typesTranslationMap['STAKE'] }}</b></span>
</mat-option>
<mat-option class="line-height-1" value="LIABILITY">
<span><b>{{ typesTranslationMap['LIABILITY'] }}</b></span>
<br />
<small class="text-muted text-nowrap" i18n
>Mortgages, personal loans, credit cards</small
>
</mat-option>
<mat-option class="line-height-1" value="SELL">
<span><b>{{ typesTranslationMap['SELL'] }}</b></span>
<br />
<small class="text-muted text-nowrap" i18n
>Stocks, ETFs, bonds, cryptocurrencies, commodities</small
>
</mat-option>
<mat-option class="line-height-1" value="ITEM">
<span><b>{{ typesTranslationMap['ITEM'] }}</b></span>
<br />
<small class="text-muted text-nowrap" i18n
>Luxury items, real estate, private companies</small
>
</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>

2
apps/client/src/app/pages/resources/personal-finance-tools/product-page-template.html

@ -89,7 +89,7 @@
</td> </td>
</tr> </tr>
<tr class="mat-mdc-row"> <tr class="mat-mdc-row">
<td class="mat-mdc-cell px-3 py-2 text-right" i18n> <td class="mat-mdc-cell px-3 py-2 text-right">
Open Source Software Open Source Software
</td> </td>
<td class="mat-mdc-cell px-1 py-2"> <td class="mat-mdc-cell px-1 py-2">

2
apps/client/src/app/pages/resources/resources-page.html

@ -179,7 +179,7 @@
monitor investments, and make informed financial decisions. monitor investments, and make informed financial decisions.
</div> </div>
<div> <div>
<a i18n [routerLink]="['/resources', 'personal-finance-tools']" <a [routerLink]="['/resources', 'personal-finance-tools']"
>Personal Finance Tools →</a >Personal Finance Tools →</a
> >
</div> </div>

2850
apps/client/src/locales/messages.de.xlf

File diff suppressed because it is too large

2850
apps/client/src/locales/messages.es.xlf

File diff suppressed because it is too large

2852
apps/client/src/locales/messages.fr.xlf

File diff suppressed because it is too large

2850
apps/client/src/locales/messages.it.xlf

File diff suppressed because it is too large

2850
apps/client/src/locales/messages.nl.xlf

File diff suppressed because it is too large

2850
apps/client/src/locales/messages.pt.xlf

File diff suppressed because it is too large

2826
apps/client/src/locales/messages.xlf

File diff suppressed because it is too large

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

@ -24,6 +24,13 @@ const locales = {
YEAR: $localize`Year`, YEAR: $localize`Year`,
YEARS: $localize`Years`, YEARS: $localize`Years`,
// Activity types
BUY: $localize`Buy`,
DIVIDEND: $localize`Dividend`,
ITEM: $localize`Valuable`,
LIABILITY: $localize`Liability`,
SELL: $localize`Sell`,
// enum AssetClass // enum AssetClass
CASH: $localize`Cash`, CASH: $localize`Cash`,
COMMODITY: $localize`Commodity`, COMMODITY: $localize`Commodity`,

6
package.json

@ -1,6 +1,6 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "1.287.0", "version": "1.289.0",
"homepage": "https://ghostfol.io", "homepage": "https://ghostfol.io",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
@ -127,7 +127,7 @@
"svgmap": "2.6.0", "svgmap": "2.6.0",
"twitter-api-v2": "1.14.2", "twitter-api-v2": "1.14.2",
"uuid": "9.0.0", "uuid": "9.0.0",
"yahoo-finance2": "2.4.1", "yahoo-finance2": "2.4.2",
"zone.js": "0.12.0" "zone.js": "0.12.0"
}, },
"devDependencies": { "devDependencies": {
@ -165,7 +165,7 @@
"@types/color": "3.0.3", "@types/color": "3.0.3",
"@types/google-spreadsheet": "3.1.5", "@types/google-spreadsheet": "3.1.5",
"@types/jest": "29.4.4", "@types/jest": "29.4.4",
"@types/lodash": "4.14.191", "@types/lodash": "4.14.195",
"@types/marked": "4.0.8", "@types/marked": "4.0.8",
"@types/node": "18.11.18", "@types/node": "18.11.18",
"@types/papaparse": "5.3.7", "@types/papaparse": "5.3.7",

16
yarn.lock

@ -4860,10 +4860,10 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/lodash@4.14.191": "@types/lodash@4.14.195":
version "4.14.191" version "4.14.195"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632"
integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ== integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==
"@types/lodash@^4.14.167": "@types/lodash@^4.14.167":
version "4.14.194" version "4.14.194"
@ -17558,10 +17558,10 @@ y18n@^5.0.5:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yahoo-finance2@2.4.1: yahoo-finance2@2.4.2:
version "2.4.1" version "2.4.2"
resolved "https://registry.yarnpkg.com/yahoo-finance2/-/yahoo-finance2-2.4.1.tgz#2ccd422e33228fc34d42e919b0d2fdd8d3f76bbf" resolved "https://registry.yarnpkg.com/yahoo-finance2/-/yahoo-finance2-2.4.2.tgz#b21ae13e20e9c9cb081011fb8a2d74b5d74cf210"
integrity sha512-jl5oHr25RC24nOmoIiDqjnc/Iiy3MZAB+dIPCyUR+o5uz72xHfTZDM9tPeheggmlAGV+KftPP6smZ6L6lNgkSQ== integrity sha512-Ze9UeW3vM/4thklQMbZNpvBWXf4Eys86QV2n2L21WqHJLvF2dedzVPGJJvRrslElI5UW4ry1x8QAUVfyPQm1TA==
dependencies: dependencies:
"@types/tough-cookie" "^4.0.2" "@types/tough-cookie" "^4.0.2"
ajv "8.10.0" ajv "8.10.0"

Loading…
Cancel
Save