Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 29m12s
All checks were successful
Docker image CD / build_and_push (push) Successful in 29m12s
This commit is contained in:
commit
3a97054cae
13
CHANGELOG.md
13
CHANGELOG.md
@ -7,11 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Added missing assets in _Storybook_ setup
|
||||||
|
|
||||||
|
## 2.139.1 - 2025-02-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added a new static portfolio analysis rule: _Regional Market Cluster Risk_ (Asia-Pacific Markets)
|
- Added a new static portfolio analysis rule: _Regional Market Cluster Risk_ (Asia-Pacific Markets)
|
||||||
- Added a new static portfolio analysis rule: _Regional Market Cluster Risk_ (Japan)
|
- Added a new static portfolio analysis rule: _Regional Market Cluster Risk_ (Japan)
|
||||||
- Added support to create custom tags in the holding detail dialog
|
- Added support to create custom tags in the holding detail dialog (experimental)
|
||||||
- Extended the tags selector component by a `readonly` attribute
|
- Extended the tags selector component by a `readonly` attribute
|
||||||
- Extended the tags selector component to support creating custom tags
|
- Extended the tags selector component to support creating custom tags
|
||||||
- Extended the holding detail dialog by the historical market data editor (experimental)
|
- Extended the holding detail dialog by the historical market data editor (experimental)
|
||||||
@ -19,9 +25,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Improved the symbol lookup in the _Trackinsight_ data enhancer for asset profile data
|
||||||
- Improved the language localization for German (`de`)
|
- Improved the language localization for German (`de`)
|
||||||
- Upgraded `@trivago/prettier-plugin-sort-imports` from version `5.2.1` to `5.2.2`
|
- Upgraded `@trivago/prettier-plugin-sort-imports` from version `5.2.1` to `5.2.2`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed the gaps in the chart of the benchmark comparator
|
||||||
|
|
||||||
## 2.138.0 - 2025-02-08
|
## 2.138.0 - 2025-02-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { BenchmarkModule } from '@ghostfolio/api/app/benchmark/benchmark.module';
|
|
||||||
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
|
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
|
||||||
import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module';
|
import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module';
|
||||||
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
|
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
|
||||||
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
|
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
|
||||||
|
import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module';
|
||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
||||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||||
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
|
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { BenchmarkService } from '@ghostfolio/api/app/benchmark/benchmark.service';
|
|
||||||
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
||||||
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
|
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
|
||||||
import { environment } from '@ghostfolio/api/environments/environment';
|
import { environment } from '@ghostfolio/api/environments/environment';
|
||||||
|
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service';
|
||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||||
|
@ -29,13 +29,14 @@ import { AppController } from './app.controller';
|
|||||||
import { AssetModule } from './asset/asset.module';
|
import { AssetModule } from './asset/asset.module';
|
||||||
import { AuthDeviceModule } from './auth-device/auth-device.module';
|
import { AuthDeviceModule } from './auth-device/auth-device.module';
|
||||||
import { AuthModule } from './auth/auth.module';
|
import { AuthModule } from './auth/auth.module';
|
||||||
import { BenchmarkModule } from './benchmark/benchmark.module';
|
|
||||||
import { CacheModule } from './cache/cache.module';
|
import { CacheModule } from './cache/cache.module';
|
||||||
import { AiModule } from './endpoints/ai/ai.module';
|
import { AiModule } from './endpoints/ai/ai.module';
|
||||||
import { ApiKeysModule } from './endpoints/api-keys/api-keys.module';
|
import { ApiKeysModule } from './endpoints/api-keys/api-keys.module';
|
||||||
|
import { BenchmarksModule } from './endpoints/benchmarks/benchmarks.module';
|
||||||
import { GhostfolioModule } from './endpoints/data-providers/ghostfolio/ghostfolio.module';
|
import { GhostfolioModule } from './endpoints/data-providers/ghostfolio/ghostfolio.module';
|
||||||
import { MarketDataModule } from './endpoints/market-data/market-data.module';
|
import { MarketDataModule } from './endpoints/market-data/market-data.module';
|
||||||
import { PublicModule } from './endpoints/public/public.module';
|
import { PublicModule } from './endpoints/public/public.module';
|
||||||
|
import { TagsModule } from './endpoints/tags/tags.module';
|
||||||
import { ExchangeRateModule } from './exchange-rate/exchange-rate.module';
|
import { ExchangeRateModule } from './exchange-rate/exchange-rate.module';
|
||||||
import { ExportModule } from './export/export.module';
|
import { ExportModule } from './export/export.module';
|
||||||
import { HealthModule } from './health/health.module';
|
import { HealthModule } from './health/health.module';
|
||||||
@ -49,7 +50,6 @@ import { RedisCacheModule } from './redis-cache/redis-cache.module';
|
|||||||
import { SitemapModule } from './sitemap/sitemap.module';
|
import { SitemapModule } from './sitemap/sitemap.module';
|
||||||
import { SubscriptionModule } from './subscription/subscription.module';
|
import { SubscriptionModule } from './subscription/subscription.module';
|
||||||
import { SymbolModule } from './symbol/symbol.module';
|
import { SymbolModule } from './symbol/symbol.module';
|
||||||
import { TagModule } from './tag/tag.module';
|
|
||||||
import { UserModule } from './user/user.module';
|
import { UserModule } from './user/user.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@ -63,7 +63,7 @@ import { UserModule } from './user/user.module';
|
|||||||
AssetModule,
|
AssetModule,
|
||||||
AuthDeviceModule,
|
AuthDeviceModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
BenchmarkModule,
|
BenchmarksModule,
|
||||||
BullModule.forRoot({
|
BullModule.forRoot({
|
||||||
redis: {
|
redis: {
|
||||||
db: parseInt(process.env.REDIS_DB ?? '0', 10),
|
db: parseInt(process.env.REDIS_DB ?? '0', 10),
|
||||||
@ -124,7 +124,7 @@ import { UserModule } from './user/user.module';
|
|||||||
SitemapModule,
|
SitemapModule,
|
||||||
SubscriptionModule,
|
SubscriptionModule,
|
||||||
SymbolModule,
|
SymbolModule,
|
||||||
TagModule,
|
TagsModule,
|
||||||
TwitterBotModule,
|
TwitterBotModule,
|
||||||
UserModule
|
UserModule
|
||||||
],
|
],
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
|
||||||
import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module';
|
|
||||||
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
|
|
||||||
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
|
|
||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
|
||||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
|
||||||
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
|
|
||||||
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
|
|
||||||
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
|
|
||||||
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
|
|
||||||
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
|
|
||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { BenchmarkController } from './benchmark.controller';
|
|
||||||
import { BenchmarkService } from './benchmark.service';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
controllers: [BenchmarkController],
|
|
||||||
exports: [BenchmarkService],
|
|
||||||
imports: [
|
|
||||||
ConfigurationModule,
|
|
||||||
DataProviderModule,
|
|
||||||
ExchangeRateDataModule,
|
|
||||||
MarketDataModule,
|
|
||||||
PrismaModule,
|
|
||||||
PropertyModule,
|
|
||||||
RedisCacheModule,
|
|
||||||
SymbolModule,
|
|
||||||
SymbolProfileModule,
|
|
||||||
TransformDataSourceInRequestModule,
|
|
||||||
TransformDataSourceInResponseModule
|
|
||||||
],
|
|
||||||
providers: [BenchmarkService]
|
|
||||||
})
|
|
||||||
export class BenchmarkModule {}
|
|
@ -2,7 +2,10 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorat
|
|||||||
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
|
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
|
||||||
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
|
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
|
||||||
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
|
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
|
||||||
|
import { ApiService } from '@ghostfolio/api/services/api/api.service';
|
||||||
|
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service';
|
||||||
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
|
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
|
||||||
|
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
|
||||||
import type {
|
import type {
|
||||||
AssetProfileIdentifier,
|
AssetProfileIdentifier,
|
||||||
BenchmarkMarketDataDetails,
|
BenchmarkMarketDataDetails,
|
||||||
@ -16,6 +19,7 @@ import {
|
|||||||
Controller,
|
Controller,
|
||||||
Delete,
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
|
Headers,
|
||||||
HttpException,
|
HttpException,
|
||||||
Inject,
|
Inject,
|
||||||
Param,
|
Param,
|
||||||
@ -29,12 +33,14 @@ import { AuthGuard } from '@nestjs/passport';
|
|||||||
import { DataSource } from '@prisma/client';
|
import { DataSource } from '@prisma/client';
|
||||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
|
|
||||||
import { BenchmarkService } from './benchmark.service';
|
import { BenchmarksService } from './benchmarks.service';
|
||||||
|
|
||||||
@Controller('benchmark')
|
@Controller('benchmarks')
|
||||||
export class BenchmarkController {
|
export class BenchmarksController {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
private readonly apiService: ApiService,
|
||||||
private readonly benchmarkService: BenchmarkService,
|
private readonly benchmarkService: BenchmarkService,
|
||||||
|
private readonly benchmarksService: BenchmarksService,
|
||||||
@Inject(REQUEST) private readonly request: RequestWithUser
|
@Inject(REQUEST) private readonly request: RequestWithUser
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -108,23 +114,43 @@ export class BenchmarkController {
|
|||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
||||||
public async getBenchmarkMarketDataForUser(
|
public async getBenchmarkMarketDataForUser(
|
||||||
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
|
||||||
@Param('dataSource') dataSource: DataSource,
|
@Param('dataSource') dataSource: DataSource,
|
||||||
@Param('startDateString') startDateString: string,
|
@Param('startDateString') startDateString: string,
|
||||||
@Param('symbol') symbol: string,
|
@Param('symbol') symbol: string,
|
||||||
@Query('range') dateRange: DateRange = 'max'
|
@Query('range') dateRange: DateRange = 'max',
|
||||||
|
@Query('accounts') filterByAccounts?: string,
|
||||||
|
@Query('assetClasses') filterByAssetClasses?: string,
|
||||||
|
@Query('dataSource') filterByDataSource?: string,
|
||||||
|
@Query('symbol') filterBySymbol?: string,
|
||||||
|
@Query('tags') filterByTags?: string,
|
||||||
|
@Query('withExcludedAccounts') withExcludedAccountsParam = 'false'
|
||||||
): Promise<BenchmarkMarketDataDetails> {
|
): Promise<BenchmarkMarketDataDetails> {
|
||||||
const { endDate, startDate } = getIntervalFromDateRange(
|
const { endDate, startDate } = getIntervalFromDateRange(
|
||||||
dateRange,
|
dateRange,
|
||||||
new Date(startDateString)
|
new Date(startDateString)
|
||||||
);
|
);
|
||||||
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
|
||||||
|
|
||||||
return this.benchmarkService.getMarketDataForUser({
|
const filters = this.apiService.buildFiltersFromQueryParams({
|
||||||
|
filterByAccounts,
|
||||||
|
filterByAssetClasses,
|
||||||
|
filterByDataSource,
|
||||||
|
filterBySymbol,
|
||||||
|
filterByTags
|
||||||
|
});
|
||||||
|
|
||||||
|
const withExcludedAccounts = withExcludedAccountsParam === 'true';
|
||||||
|
|
||||||
|
return this.benchmarksService.getMarketDataForUser({
|
||||||
dataSource,
|
dataSource,
|
||||||
|
dateRange,
|
||||||
endDate,
|
endDate,
|
||||||
|
filters,
|
||||||
|
impersonationId,
|
||||||
startDate,
|
startDate,
|
||||||
symbol,
|
symbol,
|
||||||
userCurrency
|
withExcludedAccounts,
|
||||||
|
user: this.request.user
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
63
apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts
Normal file
63
apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
|
||||||
|
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
||||||
|
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
|
||||||
|
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
|
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
|
||||||
|
import { RulesService } from '@ghostfolio/api/app/portfolio/rules.service';
|
||||||
|
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
||||||
|
import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module';
|
||||||
|
import { UserModule } from '@ghostfolio/api/app/user/user.module';
|
||||||
|
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
|
||||||
|
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
|
||||||
|
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
|
||||||
|
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service';
|
||||||
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
||||||
|
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||||
|
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
|
||||||
|
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
|
||||||
|
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
|
||||||
|
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
||||||
|
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
|
||||||
|
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
|
||||||
|
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
|
||||||
|
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
|
||||||
|
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { BenchmarksController } from './benchmarks.controller';
|
||||||
|
import { BenchmarksService } from './benchmarks.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [BenchmarksController],
|
||||||
|
imports: [
|
||||||
|
ApiModule,
|
||||||
|
ConfigurationModule,
|
||||||
|
DataProviderModule,
|
||||||
|
ExchangeRateDataModule,
|
||||||
|
ImpersonationModule,
|
||||||
|
MarketDataModule,
|
||||||
|
OrderModule,
|
||||||
|
PortfolioSnapshotQueueModule,
|
||||||
|
PrismaModule,
|
||||||
|
PropertyModule,
|
||||||
|
RedisCacheModule,
|
||||||
|
SymbolModule,
|
||||||
|
SymbolProfileModule,
|
||||||
|
TransformDataSourceInRequestModule,
|
||||||
|
TransformDataSourceInResponseModule,
|
||||||
|
UserModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AccountBalanceService,
|
||||||
|
AccountService,
|
||||||
|
BenchmarkService,
|
||||||
|
BenchmarksService,
|
||||||
|
CurrentRateService,
|
||||||
|
MarketDataService,
|
||||||
|
PortfolioCalculatorFactory,
|
||||||
|
PortfolioService,
|
||||||
|
RulesService
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class BenchmarksModule {}
|
163
apps/api/src/app/endpoints/benchmarks/benchmarks.service.ts
Normal file
163
apps/api/src/app/endpoints/benchmarks/benchmarks.service.ts
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
|
||||||
|
import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service';
|
||||||
|
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service';
|
||||||
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||||
|
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
||||||
|
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
|
||||||
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
|
BenchmarkMarketDataDetails,
|
||||||
|
Filter
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
|
import { DateRange, UserWithSettings } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { format, isSameDay } from 'date-fns';
|
||||||
|
import { isNumber } from 'lodash';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BenchmarksService {
|
||||||
|
public constructor(
|
||||||
|
private readonly benchmarkService: BenchmarkService,
|
||||||
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||||
|
private readonly marketDataService: MarketDataService,
|
||||||
|
private readonly portfolioService: PortfolioService,
|
||||||
|
private readonly symbolService: SymbolService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async getMarketDataForUser({
|
||||||
|
dataSource,
|
||||||
|
dateRange,
|
||||||
|
endDate = new Date(),
|
||||||
|
filters,
|
||||||
|
impersonationId,
|
||||||
|
startDate,
|
||||||
|
symbol,
|
||||||
|
user,
|
||||||
|
withExcludedAccounts
|
||||||
|
}: {
|
||||||
|
dateRange: DateRange;
|
||||||
|
endDate?: Date;
|
||||||
|
filters?: Filter[];
|
||||||
|
impersonationId: string;
|
||||||
|
startDate: Date;
|
||||||
|
user: UserWithSettings;
|
||||||
|
withExcludedAccounts?: boolean;
|
||||||
|
} & AssetProfileIdentifier): Promise<BenchmarkMarketDataDetails> {
|
||||||
|
const marketData: { date: string; value: number }[] = [];
|
||||||
|
const userCurrency = user.Settings.settings.baseCurrency;
|
||||||
|
const userId = user.id;
|
||||||
|
|
||||||
|
const { chart } = await this.portfolioService.getPerformance({
|
||||||
|
dateRange,
|
||||||
|
filters,
|
||||||
|
impersonationId,
|
||||||
|
userId,
|
||||||
|
withExcludedAccounts
|
||||||
|
});
|
||||||
|
|
||||||
|
const [currentSymbolItem, marketDataItems] = await Promise.all([
|
||||||
|
this.symbolService.get({
|
||||||
|
dataGatheringItem: {
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
this.marketDataService.marketDataItems({
|
||||||
|
orderBy: {
|
||||||
|
date: 'asc'
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
dataSource,
|
||||||
|
symbol,
|
||||||
|
date: {
|
||||||
|
in: chart.map(({ date }) => {
|
||||||
|
return resetHours(parseDate(date));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
const exchangeRates =
|
||||||
|
await this.exchangeRateDataService.getExchangeRatesByCurrency({
|
||||||
|
startDate,
|
||||||
|
currencies: [currentSymbolItem.currency],
|
||||||
|
targetCurrency: userCurrency
|
||||||
|
});
|
||||||
|
|
||||||
|
const exchangeRateAtStartDate =
|
||||||
|
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
|
||||||
|
format(startDate, DATE_FORMAT)
|
||||||
|
];
|
||||||
|
|
||||||
|
const marketPriceAtStartDate = marketDataItems?.find(({ date }) => {
|
||||||
|
return isSameDay(date, startDate);
|
||||||
|
})?.marketPrice;
|
||||||
|
|
||||||
|
if (!marketPriceAtStartDate) {
|
||||||
|
Logger.error(
|
||||||
|
`No historical market data has been found for ${symbol} (${dataSource}) at ${format(
|
||||||
|
startDate,
|
||||||
|
DATE_FORMAT
|
||||||
|
)}`,
|
||||||
|
'BenchmarkService'
|
||||||
|
);
|
||||||
|
|
||||||
|
return { marketData };
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const marketDataItem of marketDataItems) {
|
||||||
|
const exchangeRate =
|
||||||
|
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
|
||||||
|
format(marketDataItem.date, DATE_FORMAT)
|
||||||
|
];
|
||||||
|
|
||||||
|
const exchangeRateFactor =
|
||||||
|
isNumber(exchangeRateAtStartDate) && isNumber(exchangeRate)
|
||||||
|
? exchangeRate / exchangeRateAtStartDate
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
marketData.push({
|
||||||
|
date: format(marketDataItem.date, DATE_FORMAT),
|
||||||
|
value:
|
||||||
|
marketPriceAtStartDate === 0
|
||||||
|
? 0
|
||||||
|
: this.benchmarkService.calculateChangeInPercentage(
|
||||||
|
marketPriceAtStartDate,
|
||||||
|
marketDataItem.marketPrice * exchangeRateFactor
|
||||||
|
) * 100
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const includesEndDate = isSameDay(
|
||||||
|
parseDate(marketData.at(-1).date),
|
||||||
|
endDate
|
||||||
|
);
|
||||||
|
|
||||||
|
if (currentSymbolItem?.marketPrice && !includesEndDate) {
|
||||||
|
const exchangeRate =
|
||||||
|
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
|
||||||
|
format(endDate, DATE_FORMAT)
|
||||||
|
];
|
||||||
|
|
||||||
|
const exchangeRateFactor =
|
||||||
|
isNumber(exchangeRateAtStartDate) && isNumber(exchangeRate)
|
||||||
|
? exchangeRate / exchangeRateAtStartDate
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
marketData.push({
|
||||||
|
date: format(endDate, DATE_FORMAT),
|
||||||
|
value:
|
||||||
|
this.benchmarkService.calculateChangeInPercentage(
|
||||||
|
marketPriceAtStartDate,
|
||||||
|
currentSymbolItem.marketPrice * exchangeRateFactor
|
||||||
|
) * 100
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
marketData
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
|
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
|
||||||
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
|
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
|
||||||
|
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { RequestWithUser } from '@ghostfolio/common/types';
|
import { RequestWithUser } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
@ -21,23 +22,15 @@ import { Tag } from '@prisma/client';
|
|||||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
|
|
||||||
import { CreateTagDto } from './create-tag.dto';
|
import { CreateTagDto } from './create-tag.dto';
|
||||||
import { TagService } from './tag.service';
|
|
||||||
import { UpdateTagDto } from './update-tag.dto';
|
import { UpdateTagDto } from './update-tag.dto';
|
||||||
|
|
||||||
@Controller('tag')
|
@Controller('tags')
|
||||||
export class TagController {
|
export class TagsController {
|
||||||
public constructor(
|
public constructor(
|
||||||
@Inject(REQUEST) private readonly request: RequestWithUser,
|
@Inject(REQUEST) private readonly request: RequestWithUser,
|
||||||
private readonly tagService: TagService
|
private readonly tagService: TagService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
|
||||||
@HasPermission(permissions.readTags)
|
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
|
||||||
public async getTags() {
|
|
||||||
return this.tagService.getTagsWithActivityCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async createTag(@Body() data: CreateTagDto): Promise<Tag> {
|
public async createTag(@Body() data: CreateTagDto): Promise<Tag> {
|
||||||
@ -70,6 +63,31 @@ export class TagController {
|
|||||||
return this.tagService.createTag(data);
|
return this.tagService.createTag(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@HasPermission(permissions.deleteTag)
|
||||||
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
|
public async deleteTag(@Param('id') id: string) {
|
||||||
|
const originalTag = await this.tagService.getTag({
|
||||||
|
id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!originalTag) {
|
||||||
|
throw new HttpException(
|
||||||
|
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||||
|
StatusCodes.FORBIDDEN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tagService.deleteTag({ id });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@HasPermission(permissions.readTags)
|
||||||
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
|
public async getTags() {
|
||||||
|
return this.tagService.getTagsWithActivityCount();
|
||||||
|
}
|
||||||
|
|
||||||
@HasPermission(permissions.updateTag)
|
@HasPermission(permissions.updateTag)
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
@ -94,22 +112,4 @@ export class TagController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
|
||||||
@HasPermission(permissions.deleteTag)
|
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
|
||||||
public async deleteTag(@Param('id') id: string) {
|
|
||||||
const originalTag = await this.tagService.getTag({
|
|
||||||
id
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!originalTag) {
|
|
||||||
throw new HttpException(
|
|
||||||
getReasonPhrase(StatusCodes.FORBIDDEN),
|
|
||||||
StatusCodes.FORBIDDEN
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.tagService.deleteTag({ id });
|
|
||||||
}
|
|
||||||
}
|
}
|
12
apps/api/src/app/endpoints/tags/tags.module.ts
Normal file
12
apps/api/src/app/endpoints/tags/tags.module.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
|
||||||
|
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';
|
||||||
|
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { TagsController } from './tags.controller';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [TagsController],
|
||||||
|
imports: [PrismaModule, TagModule]
|
||||||
|
})
|
||||||
|
export class TagsModule {}
|
@ -1,8 +1,8 @@
|
|||||||
import { BenchmarkModule } from '@ghostfolio/api/app/benchmark/benchmark.module';
|
|
||||||
import { PlatformModule } from '@ghostfolio/api/app/platform/platform.module';
|
import { PlatformModule } from '@ghostfolio/api/app/platform/platform.module';
|
||||||
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
||||||
import { UserModule } from '@ghostfolio/api/app/user/user.module';
|
import { UserModule } from '@ghostfolio/api/app/user/user.module';
|
||||||
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
|
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
|
||||||
|
import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module';
|
||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
||||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||||
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
|
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { BenchmarkService } from '@ghostfolio/api/app/benchmark/benchmark.service';
|
|
||||||
import { PlatformService } from '@ghostfolio/api/app/platform/platform.service';
|
import { PlatformService } from '@ghostfolio/api/app/platform/platform.service';
|
||||||
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||||
|
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service';
|
||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||||
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
|
|
||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { TagController } from './tag.controller';
|
|
||||||
import { TagService } from './tag.service';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
controllers: [TagController],
|
|
||||||
exports: [TagService],
|
|
||||||
imports: [PrismaModule],
|
|
||||||
providers: [TagService]
|
|
||||||
})
|
|
||||||
export class TagModule {}
|
|
@ -1,81 +0,0 @@
|
|||||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { Prisma, Tag } from '@prisma/client';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class TagService {
|
|
||||||
public constructor(private readonly prismaService: PrismaService) {}
|
|
||||||
|
|
||||||
public async createTag(data: Prisma.TagCreateInput) {
|
|
||||||
return this.prismaService.tag.create({
|
|
||||||
data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteTag(where: Prisma.TagWhereUniqueInput): Promise<Tag> {
|
|
||||||
return this.prismaService.tag.delete({ where });
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getTag(
|
|
||||||
tagWhereUniqueInput: Prisma.TagWhereUniqueInput
|
|
||||||
): Promise<Tag> {
|
|
||||||
return this.prismaService.tag.findUnique({
|
|
||||||
where: tagWhereUniqueInput
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getTags({
|
|
||||||
cursor,
|
|
||||||
orderBy,
|
|
||||||
skip,
|
|
||||||
take,
|
|
||||||
where
|
|
||||||
}: {
|
|
||||||
cursor?: Prisma.TagWhereUniqueInput;
|
|
||||||
orderBy?: Prisma.TagOrderByWithRelationInput;
|
|
||||||
skip?: number;
|
|
||||||
take?: number;
|
|
||||||
where?: Prisma.TagWhereInput;
|
|
||||||
} = {}) {
|
|
||||||
return this.prismaService.tag.findMany({
|
|
||||||
cursor,
|
|
||||||
orderBy,
|
|
||||||
skip,
|
|
||||||
take,
|
|
||||||
where
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getTagsWithActivityCount() {
|
|
||||||
const tagsWithOrderCount = await this.prismaService.tag.findMany({
|
|
||||||
include: {
|
|
||||||
_count: {
|
|
||||||
select: { orders: true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return tagsWithOrderCount.map(({ _count, id, name, userId }) => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
userId,
|
|
||||||
activityCount: _count.orders
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updateTag({
|
|
||||||
data,
|
|
||||||
where
|
|
||||||
}: {
|
|
||||||
data: Prisma.TagUpdateInput;
|
|
||||||
where: Prisma.TagWhereUniqueInput;
|
|
||||||
}): Promise<Tag> {
|
|
||||||
return this.prismaService.tag.update({
|
|
||||||
data,
|
|
||||||
where
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
24
apps/api/src/services/benchmark/benchmark.module.ts
Normal file
24
apps/api/src/services/benchmark/benchmark.module.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
||||||
|
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||||
|
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
|
||||||
|
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
|
||||||
|
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
|
||||||
|
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
|
||||||
|
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { BenchmarkService } from './benchmark.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
exports: [BenchmarkService],
|
||||||
|
imports: [
|
||||||
|
DataProviderModule,
|
||||||
|
MarketDataModule,
|
||||||
|
PrismaModule,
|
||||||
|
PropertyModule,
|
||||||
|
RedisCacheModule,
|
||||||
|
SymbolProfileModule
|
||||||
|
],
|
||||||
|
providers: [BenchmarkService]
|
||||||
|
})
|
||||||
|
export class BenchmarkModule {}
|
@ -4,17 +4,7 @@ describe('BenchmarkService', () => {
|
|||||||
let benchmarkService: BenchmarkService;
|
let benchmarkService: BenchmarkService;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
benchmarkService = new BenchmarkService(
|
benchmarkService = new BenchmarkService(null, null, null, null, null, null);
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calculateChangeInPercentage', async () => {
|
it('calculateChangeInPercentage', async () => {
|
@ -1,8 +1,5 @@
|
|||||||
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service';
|
|
||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
|
||||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
|
||||||
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
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';
|
||||||
@ -11,16 +8,10 @@ import {
|
|||||||
CACHE_TTL_INFINITE,
|
CACHE_TTL_INFINITE,
|
||||||
PROPERTY_BENCHMARKS
|
PROPERTY_BENCHMARKS
|
||||||
} from '@ghostfolio/common/config';
|
} from '@ghostfolio/common/config';
|
||||||
import {
|
import { calculateBenchmarkTrend } from '@ghostfolio/common/helper';
|
||||||
DATE_FORMAT,
|
|
||||||
calculateBenchmarkTrend,
|
|
||||||
parseDate,
|
|
||||||
resetHours
|
|
||||||
} from '@ghostfolio/common/helper';
|
|
||||||
import {
|
import {
|
||||||
AssetProfileIdentifier,
|
AssetProfileIdentifier,
|
||||||
Benchmark,
|
Benchmark,
|
||||||
BenchmarkMarketDataDetails,
|
|
||||||
BenchmarkProperty,
|
BenchmarkProperty,
|
||||||
BenchmarkResponse
|
BenchmarkResponse
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
@ -29,16 +20,8 @@ import { BenchmarkTrend } from '@ghostfolio/common/types';
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { SymbolProfile } from '@prisma/client';
|
import { SymbolProfile } from '@prisma/client';
|
||||||
import { Big } from 'big.js';
|
import { Big } from 'big.js';
|
||||||
import {
|
import { addHours, isAfter, subDays } from 'date-fns';
|
||||||
addHours,
|
import { uniqBy } from 'lodash';
|
||||||
differenceInDays,
|
|
||||||
eachDayOfInterval,
|
|
||||||
format,
|
|
||||||
isAfter,
|
|
||||||
isSameDay,
|
|
||||||
subDays
|
|
||||||
} from 'date-fns';
|
|
||||||
import { isNumber, uniqBy } from 'lodash';
|
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
|
||||||
import { BenchmarkValue } from './interfaces/benchmark-value.interface';
|
import { BenchmarkValue } from './interfaces/benchmark-value.interface';
|
||||||
@ -48,15 +31,12 @@ export class BenchmarkService {
|
|||||||
private readonly CACHE_KEY_BENCHMARKS = 'BENCHMARKS';
|
private readonly CACHE_KEY_BENCHMARKS = 'BENCHMARKS';
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly configurationService: ConfigurationService,
|
|
||||||
private readonly dataProviderService: DataProviderService,
|
private readonly dataProviderService: DataProviderService,
|
||||||
private readonly exchangeRateDataService: ExchangeRateDataService,
|
|
||||||
private readonly marketDataService: MarketDataService,
|
private readonly marketDataService: MarketDataService,
|
||||||
private readonly prismaService: PrismaService,
|
private readonly prismaService: PrismaService,
|
||||||
private readonly propertyService: PropertyService,
|
private readonly propertyService: PropertyService,
|
||||||
private readonly redisCacheService: RedisCacheService,
|
private readonly redisCacheService: RedisCacheService,
|
||||||
private readonly symbolProfileService: SymbolProfileService,
|
private readonly symbolProfileService: SymbolProfileService
|
||||||
private readonly symbolService: SymbolService
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public calculateChangeInPercentage(baseValue: number, currentValue: number) {
|
public calculateChangeInPercentage(baseValue: number, currentValue: number) {
|
||||||
@ -156,138 +136,6 @@ export class BenchmarkService {
|
|||||||
.sort((a, b) => a.name.localeCompare(b.name));
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getMarketDataForUser({
|
|
||||||
dataSource,
|
|
||||||
endDate = new Date(),
|
|
||||||
startDate,
|
|
||||||
symbol,
|
|
||||||
userCurrency
|
|
||||||
}: {
|
|
||||||
endDate?: Date;
|
|
||||||
startDate: Date;
|
|
||||||
userCurrency: string;
|
|
||||||
} & AssetProfileIdentifier): Promise<BenchmarkMarketDataDetails> {
|
|
||||||
const marketData: { date: string; value: number }[] = [];
|
|
||||||
|
|
||||||
const days = differenceInDays(endDate, startDate) + 1;
|
|
||||||
const dates = eachDayOfInterval(
|
|
||||||
{
|
|
||||||
start: startDate,
|
|
||||||
end: endDate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
step: Math.round(
|
|
||||||
days /
|
|
||||||
Math.min(days, this.configurationService.get('MAX_CHART_ITEMS'))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
).map((date) => {
|
|
||||||
return resetHours(date);
|
|
||||||
});
|
|
||||||
|
|
||||||
const [currentSymbolItem, marketDataItems] = await Promise.all([
|
|
||||||
this.symbolService.get({
|
|
||||||
dataGatheringItem: {
|
|
||||||
dataSource,
|
|
||||||
symbol
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
this.marketDataService.marketDataItems({
|
|
||||||
orderBy: {
|
|
||||||
date: 'asc'
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
dataSource,
|
|
||||||
symbol,
|
|
||||||
date: {
|
|
||||||
in: dates
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
|
|
||||||
const exchangeRates =
|
|
||||||
await this.exchangeRateDataService.getExchangeRatesByCurrency({
|
|
||||||
startDate,
|
|
||||||
currencies: [currentSymbolItem.currency],
|
|
||||||
targetCurrency: userCurrency
|
|
||||||
});
|
|
||||||
|
|
||||||
const exchangeRateAtStartDate =
|
|
||||||
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
|
|
||||||
format(startDate, DATE_FORMAT)
|
|
||||||
];
|
|
||||||
|
|
||||||
const marketPriceAtStartDate = marketDataItems?.find(({ date }) => {
|
|
||||||
return isSameDay(date, startDate);
|
|
||||||
})?.marketPrice;
|
|
||||||
|
|
||||||
if (!marketPriceAtStartDate) {
|
|
||||||
Logger.error(
|
|
||||||
`No historical market data has been found for ${symbol} (${dataSource}) at ${format(
|
|
||||||
startDate,
|
|
||||||
DATE_FORMAT
|
|
||||||
)}`,
|
|
||||||
'BenchmarkService'
|
|
||||||
);
|
|
||||||
|
|
||||||
return { marketData };
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const marketDataItem of marketDataItems) {
|
|
||||||
const exchangeRate =
|
|
||||||
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
|
|
||||||
format(marketDataItem.date, DATE_FORMAT)
|
|
||||||
];
|
|
||||||
|
|
||||||
const exchangeRateFactor =
|
|
||||||
isNumber(exchangeRateAtStartDate) && isNumber(exchangeRate)
|
|
||||||
? exchangeRate / exchangeRateAtStartDate
|
|
||||||
: 1;
|
|
||||||
|
|
||||||
marketData.push({
|
|
||||||
date: format(marketDataItem.date, DATE_FORMAT),
|
|
||||||
value:
|
|
||||||
marketPriceAtStartDate === 0
|
|
||||||
? 0
|
|
||||||
: this.calculateChangeInPercentage(
|
|
||||||
marketPriceAtStartDate,
|
|
||||||
marketDataItem.marketPrice * exchangeRateFactor
|
|
||||||
) * 100
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const includesEndDate = isSameDay(
|
|
||||||
parseDate(marketData.at(-1).date),
|
|
||||||
endDate
|
|
||||||
);
|
|
||||||
|
|
||||||
if (currentSymbolItem?.marketPrice && !includesEndDate) {
|
|
||||||
const exchangeRate =
|
|
||||||
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
|
|
||||||
format(endDate, DATE_FORMAT)
|
|
||||||
];
|
|
||||||
|
|
||||||
const exchangeRateFactor =
|
|
||||||
isNumber(exchangeRateAtStartDate) && isNumber(exchangeRate)
|
|
||||||
? exchangeRate / exchangeRateAtStartDate
|
|
||||||
: 1;
|
|
||||||
|
|
||||||
marketData.push({
|
|
||||||
date: format(endDate, DATE_FORMAT),
|
|
||||||
value:
|
|
||||||
this.calculateChangeInPercentage(
|
|
||||||
marketPriceAtStartDate,
|
|
||||||
currentSymbolItem.marketPrice * exchangeRateFactor
|
|
||||||
) * 100
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
marketData
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async addBenchmark({
|
public async addBenchmark({
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol
|
symbol
|
@ -44,29 +44,32 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let trackinsightSymbol = await this.searchTrackinsightSymbol({
|
||||||
|
requestTimeout,
|
||||||
|
symbol
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!trackinsightSymbol) {
|
||||||
|
trackinsightSymbol = await this.searchTrackinsightSymbol({
|
||||||
|
requestTimeout,
|
||||||
|
symbol: symbol.split('.')?.[0]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!trackinsightSymbol) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
const profile = await fetch(
|
const profile = await fetch(
|
||||||
`${TrackinsightDataEnhancerService.baseUrl}/funds/${symbol}.json`,
|
`${TrackinsightDataEnhancerService.baseUrl}/funds/${trackinsightSymbol}.json`,
|
||||||
{
|
{
|
||||||
signal: AbortSignal.timeout(requestTimeout)
|
signal: AbortSignal.timeout(requestTimeout)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
.then((res) => res.json())
|
|
||||||
.catch(() => {
|
|
||||||
return fetch(
|
|
||||||
`${TrackinsightDataEnhancerService.baseUrl}/funds/${
|
|
||||||
symbol.split('.')?.[0]
|
|
||||||
}.json`,
|
|
||||||
{
|
|
||||||
signal: AbortSignal.timeout(
|
|
||||||
this.configurationService.get('REQUEST_TIMEOUT')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const isin = profile?.isin?.split(';')?.[0];
|
const isin = profile?.isin?.split(';')?.[0];
|
||||||
|
|
||||||
@ -75,30 +78,15 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const holdings = await fetch(
|
const holdings = await fetch(
|
||||||
`${TrackinsightDataEnhancerService.baseUrl}/holdings/${symbol}.json`,
|
`${TrackinsightDataEnhancerService.baseUrl}/holdings/${trackinsightSymbol}.json`,
|
||||||
{
|
{
|
||||||
signal: AbortSignal.timeout(
|
signal: AbortSignal.timeout(requestTimeout)
|
||||||
this.configurationService.get('REQUEST_TIMEOUT')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then((res) => res.json())
|
|
||||||
.catch(() => {
|
|
||||||
return fetch(
|
|
||||||
`${TrackinsightDataEnhancerService.baseUrl}/holdings/${
|
|
||||||
symbol.split('.')?.[0]
|
|
||||||
}.json`,
|
|
||||||
{
|
|
||||||
signal: AbortSignal.timeout(
|
|
||||||
this.configurationService.get('REQUEST_TIMEOUT')
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
holdings?.weight < TrackinsightDataEnhancerService.holdingsWeightTreshold
|
holdings?.weight < TrackinsightDataEnhancerService.holdingsWeightTreshold
|
||||||
@ -180,4 +168,33 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface {
|
|||||||
public getTestSymbol() {
|
public getTestSymbol() {
|
||||||
return 'QQQ';
|
return 'QQQ';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async searchTrackinsightSymbol({
|
||||||
|
requestTimeout,
|
||||||
|
symbol
|
||||||
|
}: {
|
||||||
|
requestTimeout: number;
|
||||||
|
symbol: string;
|
||||||
|
}) {
|
||||||
|
return fetch(
|
||||||
|
`https://www.trackinsight.com/search-api/search_v2/${symbol}/_/ticker/default/0/3`,
|
||||||
|
{
|
||||||
|
signal: AbortSignal.timeout(requestTimeout)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((jsonRes) => {
|
||||||
|
if (
|
||||||
|
jsonRes['results']?.['count'] === 1 ||
|
||||||
|
jsonRes['results']?.['docs']?.[0]?.['ticker'] === symbol
|
||||||
|
) {
|
||||||
|
return jsonRes['results']['docs'][0]['ticker'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,52 @@
|
|||||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Prisma, Tag } from '@prisma/client';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TagService {
|
export class TagService {
|
||||||
public constructor(private readonly prismaService: PrismaService) {}
|
public constructor(private readonly prismaService: PrismaService) {}
|
||||||
|
|
||||||
|
public async createTag(data: Prisma.TagCreateInput) {
|
||||||
|
return this.prismaService.tag.create({
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteTag(where: Prisma.TagWhereUniqueInput): Promise<Tag> {
|
||||||
|
return this.prismaService.tag.delete({ where });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getTag(
|
||||||
|
tagWhereUniqueInput: Prisma.TagWhereUniqueInput
|
||||||
|
): Promise<Tag> {
|
||||||
|
return this.prismaService.tag.findUnique({
|
||||||
|
where: tagWhereUniqueInput
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getTags({
|
||||||
|
cursor,
|
||||||
|
orderBy,
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
where
|
||||||
|
}: {
|
||||||
|
cursor?: Prisma.TagWhereUniqueInput;
|
||||||
|
orderBy?: Prisma.TagOrderByWithRelationInput;
|
||||||
|
skip?: number;
|
||||||
|
take?: number;
|
||||||
|
where?: Prisma.TagWhereInput;
|
||||||
|
} = {}) {
|
||||||
|
return this.prismaService.tag.findMany({
|
||||||
|
cursor,
|
||||||
|
orderBy,
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
where
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async getTagsForUser(userId: string) {
|
public async getTagsForUser(userId: string) {
|
||||||
const tags = await this.prismaService.tag.findMany({
|
const tags = await this.prismaService.tag.findMany({
|
||||||
include: {
|
include: {
|
||||||
@ -41,4 +82,36 @@ export class TagService {
|
|||||||
isUsed: _count.orders > 0
|
isUsed: _count.orders > 0
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getTagsWithActivityCount() {
|
||||||
|
const tagsWithOrderCount = await this.prismaService.tag.findMany({
|
||||||
|
include: {
|
||||||
|
_count: {
|
||||||
|
select: { orders: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tagsWithOrderCount.map(({ _count, id, name, userId }) => {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
userId,
|
||||||
|
activityCount: _count.orders
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateTag({
|
||||||
|
data,
|
||||||
|
where
|
||||||
|
}: {
|
||||||
|
data: Prisma.TagUpdateInput;
|
||||||
|
where: Prisma.TagWhereUniqueInput;
|
||||||
|
}): Promise<Tag> {
|
||||||
|
return this.prismaService.tag.update({
|
||||||
|
data,
|
||||||
|
where
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BenchmarkModule } from '@ghostfolio/api/app/benchmark/benchmark.module';
|
|
||||||
import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module';
|
import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module';
|
||||||
|
import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module';
|
||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
||||||
import { TwitterBotService } from '@ghostfolio/api/services/twitter-bot/twitter-bot.service';
|
import { TwitterBotService } from '@ghostfolio/api/services/twitter-bot/twitter-bot.service';
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BenchmarkService } from '@ghostfolio/api/app/benchmark/benchmark.service';
|
|
||||||
import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service';
|
import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service';
|
||||||
|
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service';
|
||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||||
import {
|
import {
|
||||||
ghostfolioFearAndGreedIndexDataSource,
|
ghostfolioFearAndGreedIndexDataSource,
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { CreateTagDto } from '@ghostfolio/api/app/tag/create-tag.dto';
|
import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto';
|
||||||
import { UpdateTagDto } from '@ghostfolio/api/app/tag/update-tag.dto';
|
import { UpdateTagDto } from '@ghostfolio/api/app/endpoints/tags/update-tag.dto';
|
||||||
import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type';
|
import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type';
|
||||||
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
|
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
|
||||||
import { AdminService } from '@ghostfolio/client/services/admin.service';
|
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
|
|
||||||
@ -43,7 +42,6 @@ export class AdminTagComponent implements OnInit, OnDestroy {
|
|||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private adminService: AdminService,
|
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
private deviceService: DeviceDetectorService,
|
private deviceService: DeviceDetectorService,
|
||||||
@ -100,7 +98,7 @@ export class AdminTagComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private deleteTag(aId: string) {
|
private deleteTag(aId: string) {
|
||||||
this.adminService
|
this.dataService
|
||||||
.deleteTag(aId)
|
.deleteTag(aId)
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
@ -116,7 +114,7 @@ export class AdminTagComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fetchTags() {
|
private fetchTags() {
|
||||||
this.adminService
|
this.dataService
|
||||||
.fetchTags()
|
.fetchTags()
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((tags) => {
|
.subscribe((tags) => {
|
||||||
@ -148,7 +146,7 @@ export class AdminTagComponent implements OnInit, OnDestroy {
|
|||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((tag: CreateTagDto | null) => {
|
.subscribe((tag: CreateTagDto | null) => {
|
||||||
if (tag) {
|
if (tag) {
|
||||||
this.adminService
|
this.dataService
|
||||||
.postTag(tag)
|
.postTag(tag)
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
@ -184,7 +182,7 @@ export class AdminTagComponent implements OnInit, OnDestroy {
|
|||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((tag: UpdateTagDto | null) => {
|
.subscribe((tag: UpdateTagDto | null) => {
|
||||||
if (tag) {
|
if (tag) {
|
||||||
this.adminService
|
this.dataService
|
||||||
.putTag(tag)
|
.putTag(tag)
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { CreateTagDto } from '@ghostfolio/api/app/tag/create-tag.dto';
|
import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto';
|
||||||
import { UpdateTagDto } from '@ghostfolio/api/app/tag/update-tag.dto';
|
import { UpdateTagDto } from '@ghostfolio/api/app/endpoints/tags/update-tag.dto';
|
||||||
import { validateObjectForForm } from '@ghostfolio/client/util/form.util';
|
import { validateObjectForForm } from '@ghostfolio/client/util/form.util';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -2,7 +2,6 @@ import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interf
|
|||||||
import { GfAccountsTableModule } from '@ghostfolio/client/components/accounts-table/accounts-table.module';
|
import { GfAccountsTableModule } from '@ghostfolio/client/components/accounts-table/accounts-table.module';
|
||||||
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
|
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
|
||||||
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
|
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
|
||||||
import { AdminService } from '@ghostfolio/client/services/admin.service';
|
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { NUMERICAL_PRECISION_THRESHOLD } from '@ghostfolio/common/config';
|
import { NUMERICAL_PRECISION_THRESHOLD } from '@ghostfolio/common/config';
|
||||||
@ -133,7 +132,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
|
|||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private adminService: AdminService,
|
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
public dialogRef: MatDialogRef<GfHoldingDetailDialogComponent>,
|
public dialogRef: MatDialogRef<GfHoldingDetailDialogComponent>,
|
||||||
@ -162,7 +160,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (newTag && this.hasPermissionToCreateOwnTag) {
|
if (newTag && this.hasPermissionToCreateOwnTag) {
|
||||||
this.adminService
|
this.dataService
|
||||||
.postTag({ ...newTag, userId: this.user.id })
|
.postTag({ ...newTag, userId: this.user.id })
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((createdTag) => {
|
switchMap((createdTag) => {
|
||||||
|
@ -388,7 +388,9 @@
|
|||||||
</mat-tab-group>
|
</mat-tab-group>
|
||||||
|
|
||||||
<gf-tags-selector
|
<gf-tags-selector
|
||||||
[hasPermissionToCreateTag]="hasPermissionToCreateOwnTag"
|
[hasPermissionToCreateTag]="
|
||||||
|
hasPermissionToCreateOwnTag && user?.settings?.isExperimentalFeatures
|
||||||
|
"
|
||||||
[readonly]="!data.hasPermissionToUpdateOrder"
|
[readonly]="!data.hasPermissionToUpdateOrder"
|
||||||
[tags]="activityForm.get('tags')?.value"
|
[tags]="activityForm.get('tags')?.value"
|
||||||
[tagsAvailable]="tagsAvailable"
|
[tagsAvailable]="tagsAvailable"
|
||||||
|
@ -353,6 +353,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
.fetchBenchmarkForUser({
|
.fetchBenchmarkForUser({
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
|
filters: this.userService.getFilters(),
|
||||||
range: this.user?.settings?.dateRange,
|
range: this.user?.settings?.dateRange,
|
||||||
startDate: this.firstOrderDate
|
startDate: this.firstOrderDate
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto';
|
import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto';
|
||||||
import { CreatePlatformDto } from '@ghostfolio/api/app/platform/create-platform.dto';
|
import { CreatePlatformDto } from '@ghostfolio/api/app/platform/create-platform.dto';
|
||||||
import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform.dto';
|
import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform.dto';
|
||||||
import { CreateTagDto } from '@ghostfolio/api/app/tag/create-tag.dto';
|
|
||||||
import { UpdateTagDto } from '@ghostfolio/api/app/tag/update-tag.dto';
|
|
||||||
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
|
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import {
|
import {
|
||||||
HEADER_KEY_SKIP_INTERCEPTOR,
|
HEADER_KEY_SKIP_INTERCEPTOR,
|
||||||
@ -25,7 +23,7 @@ import {
|
|||||||
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { SortDirection } from '@angular/material/sort';
|
import { SortDirection } from '@angular/material/sort';
|
||||||
import { DataSource, MarketData, Platform, Tag } from '@prisma/client';
|
import { DataSource, MarketData, Platform } from '@prisma/client';
|
||||||
import { JobStatus } from 'bull';
|
import { JobStatus } from 'bull';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { switchMap } from 'rxjs';
|
import { switchMap } from 'rxjs';
|
||||||
@ -75,10 +73,6 @@ export class AdminService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteTag(aId: string) {
|
|
||||||
return this.http.delete<void>(`/api/v1/tag/${aId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public executeJob(aId: string) {
|
public executeJob(aId: string) {
|
||||||
return this.http.get<void>(`/api/v1/admin/queue/job/${aId}/execute`);
|
return this.http.get<void>(`/api/v1/admin/queue/job/${aId}/execute`);
|
||||||
}
|
}
|
||||||
@ -155,10 +149,6 @@ export class AdminService {
|
|||||||
return this.http.get<Platform[]>('/api/v1/platform');
|
return this.http.get<Platform[]>('/api/v1/platform');
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchTags() {
|
|
||||||
return this.http.get<Tag[]>('/api/v1/tag');
|
|
||||||
}
|
|
||||||
|
|
||||||
public fetchUsers({
|
public fetchUsers({
|
||||||
skip,
|
skip,
|
||||||
take = DEFAULT_PAGE_SIZE
|
take = DEFAULT_PAGE_SIZE
|
||||||
@ -261,10 +251,6 @@ export class AdminService {
|
|||||||
return this.http.post<Platform>(`/api/v1/platform`, aPlatform);
|
return this.http.post<Platform>(`/api/v1/platform`, aPlatform);
|
||||||
}
|
}
|
||||||
|
|
||||||
public postTag(aTag: CreateTagDto) {
|
|
||||||
return this.http.post<Tag>(`/api/v1/tag`, aTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
public putPlatform(aPlatform: UpdatePlatformDto) {
|
public putPlatform(aPlatform: UpdatePlatformDto) {
|
||||||
return this.http.put<Platform>(
|
return this.http.put<Platform>(
|
||||||
`/api/v1/platform/${aPlatform.id}`,
|
`/api/v1/platform/${aPlatform.id}`,
|
||||||
@ -272,10 +258,6 @@ export class AdminService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public putTag(aTag: UpdateTagDto) {
|
|
||||||
return this.http.put<Tag>(`/api/v1/tag/${aTag.id}`, aTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
public testMarketData({
|
public testMarketData({
|
||||||
dataSource,
|
dataSource,
|
||||||
scraperConfiguration,
|
scraperConfiguration,
|
||||||
|
@ -4,6 +4,8 @@ import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto
|
|||||||
import { TransferBalanceDto } from '@ghostfolio/api/app/account/transfer-balance.dto';
|
import { TransferBalanceDto } from '@ghostfolio/api/app/account/transfer-balance.dto';
|
||||||
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
|
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
|
||||||
import { UpdateBulkMarketDataDto } from '@ghostfolio/api/app/admin/update-bulk-market-data.dto';
|
import { UpdateBulkMarketDataDto } from '@ghostfolio/api/app/admin/update-bulk-market-data.dto';
|
||||||
|
import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto';
|
||||||
|
import { UpdateTagDto } from '@ghostfolio/api/app/endpoints/tags/update-tag.dto';
|
||||||
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
|
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
|
||||||
import {
|
import {
|
||||||
Activities,
|
Activities,
|
||||||
@ -301,13 +303,17 @@ export class DataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public deleteBenchmark({ dataSource, symbol }: AssetProfileIdentifier) {
|
public deleteBenchmark({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
return this.http.delete<any>(`/api/v1/benchmark/${dataSource}/${symbol}`);
|
return this.http.delete<any>(`/api/v1/benchmarks/${dataSource}/${symbol}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteOwnUser(aData: DeleteOwnUserDto) {
|
public deleteOwnUser(aData: DeleteOwnUserDto) {
|
||||||
return this.http.delete<any>(`/api/v1/user`, { body: aData });
|
return this.http.delete<any>(`/api/v1/user`, { body: aData });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public deleteTag(aId: string) {
|
||||||
|
return this.http.delete<void>(`/api/v1/tags/${aId}`);
|
||||||
|
}
|
||||||
|
|
||||||
public deleteUser(aId: string) {
|
public deleteUser(aId: string) {
|
||||||
return this.http.delete<any>(`/api/v1/user/${aId}`);
|
return this.http.delete<any>(`/api/v1/user/${aId}`);
|
||||||
}
|
}
|
||||||
@ -332,21 +338,27 @@ export class DataService {
|
|||||||
|
|
||||||
public fetchBenchmarkForUser({
|
public fetchBenchmarkForUser({
|
||||||
dataSource,
|
dataSource,
|
||||||
|
filters,
|
||||||
range,
|
range,
|
||||||
startDate,
|
startDate,
|
||||||
symbol
|
symbol,
|
||||||
|
withExcludedAccounts
|
||||||
}: {
|
}: {
|
||||||
|
filters?: Filter[];
|
||||||
range: DateRange;
|
range: DateRange;
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
|
withExcludedAccounts?: boolean;
|
||||||
} & AssetProfileIdentifier): Observable<BenchmarkMarketDataDetails> {
|
} & AssetProfileIdentifier): Observable<BenchmarkMarketDataDetails> {
|
||||||
let params = new HttpParams();
|
let params = this.buildFiltersAsQueryParams({ filters });
|
||||||
|
|
||||||
if (range) {
|
|
||||||
params = params.append('range', range);
|
params = params.append('range', range);
|
||||||
|
|
||||||
|
if (withExcludedAccounts) {
|
||||||
|
params = params.append('withExcludedAccounts', withExcludedAccounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.http.get<BenchmarkMarketDataDetails>(
|
return this.http.get<BenchmarkMarketDataDetails>(
|
||||||
`/api/v1/benchmark/${dataSource}/${symbol}/${format(
|
`/api/v1/benchmarks/${dataSource}/${symbol}/${format(
|
||||||
startDate,
|
startDate,
|
||||||
DATE_FORMAT
|
DATE_FORMAT
|
||||||
)}`,
|
)}`,
|
||||||
@ -355,7 +367,7 @@ export class DataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fetchBenchmarks() {
|
public fetchBenchmarks() {
|
||||||
return this.http.get<BenchmarkResponse>('/api/v1/benchmark');
|
return this.http.get<BenchmarkResponse>('/api/v1/benchmarks');
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchExport({
|
public fetchExport({
|
||||||
@ -662,6 +674,10 @@ export class DataService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fetchTags() {
|
||||||
|
return this.http.get<Tag[]>('/api/v1/tags');
|
||||||
|
}
|
||||||
|
|
||||||
public loginAnonymous(accessToken: string) {
|
public loginAnonymous(accessToken: string) {
|
||||||
return this.http.post<OAuthResponse>('/api/v1/auth/anonymous', {
|
return this.http.post<OAuthResponse>('/api/v1/auth/anonymous', {
|
||||||
accessToken
|
accessToken
|
||||||
@ -688,7 +704,7 @@ export class DataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public postBenchmark(benchmark: AssetProfileIdentifier) {
|
public postBenchmark(benchmark: AssetProfileIdentifier) {
|
||||||
return this.http.post('/api/v1/benchmark', benchmark);
|
return this.http.post('/api/v1/benchmarks', benchmark);
|
||||||
}
|
}
|
||||||
|
|
||||||
public postMarketData({
|
public postMarketData({
|
||||||
@ -709,6 +725,10 @@ export class DataService {
|
|||||||
return this.http.post<OrderModel>('/api/v1/order', aOrder);
|
return this.http.post<OrderModel>('/api/v1/order', aOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public postTag(aTag: CreateTagDto) {
|
||||||
|
return this.http.post<Tag>(`/api/v1/tags`, aTag);
|
||||||
|
}
|
||||||
|
|
||||||
public postUser() {
|
public postUser() {
|
||||||
return this.http.post<UserItem>('/api/v1/user', {});
|
return this.http.post<UserItem>('/api/v1/user', {});
|
||||||
}
|
}
|
||||||
@ -736,6 +756,10 @@ export class DataService {
|
|||||||
return this.http.put<UserItem>(`/api/v1/order/${aOrder.id}`, aOrder);
|
return this.http.put<UserItem>(`/api/v1/order/${aOrder.id}`, aOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public putTag(aTag: UpdateTagDto) {
|
||||||
|
return this.http.put<Tag>(`/api/v1/tags/${aTag.id}`, aTag);
|
||||||
|
}
|
||||||
|
|
||||||
public putUserSetting(aData: UpdateUserSettingDto) {
|
public putUserSetting(aData: UpdateUserSettingDto) {
|
||||||
return this.http.put<User>('/api/v1/user/setting', aData);
|
return this.http.put<User>('/api/v1/user/setting', aData);
|
||||||
}
|
}
|
||||||
|
@ -1787,7 +1787,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="936788a5ab949fe0d70098ba051ac7a44999ff08" datatype="html">
|
<trans-unit id="936788a5ab949fe0d70098ba051ac7a44999ff08" datatype="html">
|
||||||
@ -2239,7 +2239,7 @@
|
|||||||
<target state="translated">Està segur que vol eliminar aquesta etiqueta?</target>
|
<target state="translated">Està segur que vol eliminar aquesta etiqueta?</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">85</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
||||||
@ -2639,7 +2639,7 @@
|
|||||||
<target state="translated">Informar d’un Problema amb les Dades</target>
|
<target state="translated">Informar d’un Problema amb les Dades</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">385</context>
|
<context context-type="linenumber">409</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8204176479746810612" datatype="html">
|
<trans-unit id="8204176479746810612" datatype="html">
|
||||||
@ -7756,6 +7756,14 @@
|
|||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="cdcd7c871f3bc0326ee77e5aea82af1ef26f46f2" datatype="html">
|
||||||
|
<source>Market Data</source>
|
||||||
|
<target state="new">Market Data</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
|
<context context-type="linenumber">374</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -1370,7 +1370,7 @@
|
|||||||
<target state="translated">Datenfehler melden</target>
|
<target state="translated">Datenfehler melden</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">385</context>
|
<context context-type="linenumber">409</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
||||||
@ -3370,7 +3370,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4798457301875181136" datatype="html">
|
<trans-unit id="4798457301875181136" datatype="html">
|
||||||
@ -5891,7 +5891,7 @@
|
|||||||
<target state="translated">Möchtest du diesen Tag wirklich löschen?</target>
|
<target state="translated">Möchtest du diesen Tag wirklich löschen?</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">85</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
||||||
@ -7756,6 +7756,14 @@
|
|||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="cdcd7c871f3bc0326ee77e5aea82af1ef26f46f2" datatype="html">
|
||||||
|
<source>Market Data</source>
|
||||||
|
<target state="translated">Marktdaten</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
|
<context context-type="linenumber">374</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -1371,7 +1371,7 @@
|
|||||||
<target state="translated">Reporta un anomalía de los datos</target>
|
<target state="translated">Reporta un anomalía de los datos</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">385</context>
|
<context context-type="linenumber">409</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
||||||
@ -3371,7 +3371,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4798457301875181136" datatype="html">
|
<trans-unit id="4798457301875181136" datatype="html">
|
||||||
@ -5892,7 +5892,7 @@
|
|||||||
<target state="new">Do you really want to delete this tag?</target>
|
<target state="new">Do you really want to delete this tag?</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">85</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
||||||
@ -7757,6 +7757,14 @@
|
|||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="cdcd7c871f3bc0326ee77e5aea82af1ef26f46f2" datatype="html">
|
||||||
|
<source>Market Data</source>
|
||||||
|
<target state="new">Market Data</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
|
<context context-type="linenumber">374</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -1718,7 +1718,7 @@
|
|||||||
<target state="translated">Signaler une Erreur de Données</target>
|
<target state="translated">Signaler une Erreur de Données</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">385</context>
|
<context context-type="linenumber">409</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6048892649018070225" datatype="html">
|
<trans-unit id="6048892649018070225" datatype="html">
|
||||||
@ -2598,7 +2598,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2666668717343771434" datatype="html">
|
<trans-unit id="2666668717343771434" datatype="html">
|
||||||
@ -5891,7 +5891,7 @@
|
|||||||
<target state="translated">Confirmez la suppression de ce tag ?</target>
|
<target state="translated">Confirmez la suppression de ce tag ?</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">85</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
||||||
@ -7756,6 +7756,14 @@
|
|||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="cdcd7c871f3bc0326ee77e5aea82af1ef26f46f2" datatype="html">
|
||||||
|
<source>Market Data</source>
|
||||||
|
<target state="new">Market Data</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
|
<context context-type="linenumber">374</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -1371,7 +1371,7 @@
|
|||||||
<target state="translated">Segnala un’anomalia dei dati</target>
|
<target state="translated">Segnala un’anomalia dei dati</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">385</context>
|
<context context-type="linenumber">409</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
||||||
@ -3371,7 +3371,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4798457301875181136" datatype="html">
|
<trans-unit id="4798457301875181136" datatype="html">
|
||||||
@ -5892,7 +5892,7 @@
|
|||||||
<target state="translated">Sei sicuro di voler eliminare questo tag?</target>
|
<target state="translated">Sei sicuro di voler eliminare questo tag?</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">85</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
||||||
@ -7757,6 +7757,14 @@
|
|||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="cdcd7c871f3bc0326ee77e5aea82af1ef26f46f2" datatype="html">
|
||||||
|
<source>Market Data</source>
|
||||||
|
<target state="new">Market Data</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
|
<context context-type="linenumber">374</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -1370,7 +1370,7 @@
|
|||||||
<target state="translated">Gegevensstoring melden</target>
|
<target state="translated">Gegevensstoring melden</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">385</context>
|
<context context-type="linenumber">409</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
||||||
@ -3370,7 +3370,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4798457301875181136" datatype="html">
|
<trans-unit id="4798457301875181136" datatype="html">
|
||||||
@ -5891,7 +5891,7 @@
|
|||||||
<target state="new">Do you really want to delete this tag?</target>
|
<target state="new">Do you really want to delete this tag?</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">85</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
||||||
@ -7756,6 +7756,14 @@
|
|||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="cdcd7c871f3bc0326ee77e5aea82af1ef26f46f2" datatype="html">
|
||||||
|
<source>Market Data</source>
|
||||||
|
<target state="new">Market Data</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
|
<context context-type="linenumber">374</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -1639,7 +1639,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="936788a5ab949fe0d70098ba051ac7a44999ff08" datatype="html">
|
<trans-unit id="936788a5ab949fe0d70098ba051ac7a44999ff08" datatype="html">
|
||||||
@ -2067,7 +2067,7 @@
|
|||||||
<target state="translated">Czy naprawdę chcesz usunąć ten tag?</target>
|
<target state="translated">Czy naprawdę chcesz usunąć ten tag?</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">85</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
||||||
@ -2803,7 +2803,7 @@
|
|||||||
<target state="translated">Zgłoś Błąd Danych</target>
|
<target state="translated">Zgłoś Błąd Danych</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">385</context>
|
<context context-type="linenumber">409</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2573979fd7d8602db44b7b4ad493428bc354d2f5" datatype="html">
|
<trans-unit id="2573979fd7d8602db44b7b4ad493428bc354d2f5" datatype="html">
|
||||||
@ -7756,6 +7756,14 @@
|
|||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="cdcd7c871f3bc0326ee77e5aea82af1ef26f46f2" datatype="html">
|
||||||
|
<source>Market Data</source>
|
||||||
|
<target state="new">Market Data</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
|
<context context-type="linenumber">374</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -1678,7 +1678,7 @@
|
|||||||
<target state="translated">Dados do Relatório com Problema</target>
|
<target state="translated">Dados do Relatório com Problema</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">385</context>
|
<context context-type="linenumber">409</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
||||||
@ -3378,7 +3378,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7765499580020598783" datatype="html">
|
<trans-unit id="7765499580020598783" datatype="html">
|
||||||
@ -5891,7 +5891,7 @@
|
|||||||
<target state="new">Do you really want to delete this tag?</target>
|
<target state="new">Do you really want to delete this tag?</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">85</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
||||||
@ -7756,6 +7756,14 @@
|
|||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="cdcd7c871f3bc0326ee77e5aea82af1ef26f46f2" datatype="html">
|
||||||
|
<source>Market Data</source>
|
||||||
|
<target state="new">Market Data</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
|
<context context-type="linenumber">374</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -2647,7 +2647,7 @@
|
|||||||
<target state="translated">Rapor Veri Sorunu</target>
|
<target state="translated">Rapor Veri Sorunu</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">385</context>
|
<context context-type="linenumber">409</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2573979fd7d8602db44b7b4ad493428bc354d2f5" datatype="html">
|
<trans-unit id="2573979fd7d8602db44b7b4ad493428bc354d2f5" datatype="html">
|
||||||
@ -3955,7 +3955,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2666668717343771434" datatype="html">
|
<trans-unit id="2666668717343771434" datatype="html">
|
||||||
@ -5891,7 +5891,7 @@
|
|||||||
<target state="translated">Bu etiketi silmeyi gerçekten istiyor musunuz?</target>
|
<target state="translated">Bu etiketi silmeyi gerçekten istiyor musunuz?</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">85</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
||||||
@ -7756,6 +7756,14 @@
|
|||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="cdcd7c871f3bc0326ee77e5aea82af1ef26f46f2" datatype="html">
|
||||||
|
<source>Market Data</source>
|
||||||
|
<target state="new">Market Data</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
|
<context context-type="linenumber">374</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -2363,7 +2363,7 @@
|
|||||||
<target state="translated">Ви дійсно хочете видалити цей тег?</target>
|
<target state="translated">Ви дійсно хочете видалити цей тег?</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">85</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
||||||
@ -2767,7 +2767,7 @@
|
|||||||
<target state="translated">Повідомити про збій даних</target>
|
<target state="translated">Повідомити про збій даних</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">385</context>
|
<context context-type="linenumber">409</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8204176479746810612" datatype="html">
|
<trans-unit id="8204176479746810612" datatype="html">
|
||||||
@ -5139,7 +5139,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2666668717343771434" datatype="html">
|
<trans-unit id="2666668717343771434" datatype="html">
|
||||||
@ -7756,6 +7756,14 @@
|
|||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="cdcd7c871f3bc0326ee77e5aea82af1ef26f46f2" datatype="html">
|
||||||
|
<source>Market Data</source>
|
||||||
|
<target state="new">Market Data</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
|
<context context-type="linenumber">374</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -1569,7 +1569,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="936788a5ab949fe0d70098ba051ac7a44999ff08" datatype="html">
|
<trans-unit id="936788a5ab949fe0d70098ba051ac7a44999ff08" datatype="html">
|
||||||
@ -1962,7 +1962,7 @@
|
|||||||
<source>Do you really want to delete this tag?</source>
|
<source>Do you really want to delete this tag?</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">85</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
||||||
@ -2623,7 +2623,7 @@
|
|||||||
<source>Report Data Glitch</source>
|
<source>Report Data Glitch</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">385</context>
|
<context context-type="linenumber">409</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2573979fd7d8602db44b7b4ad493428bc354d2f5" datatype="html">
|
<trans-unit id="2573979fd7d8602db44b7b4ad493428bc354d2f5" datatype="html">
|
||||||
@ -7014,6 +7014,13 @@
|
|||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="cdcd7c871f3bc0326ee77e5aea82af1ef26f46f2" datatype="html">
|
||||||
|
<source>Market Data</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
|
<context context-type="linenumber">374</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -1648,7 +1648,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="936788a5ab949fe0d70098ba051ac7a44999ff08" datatype="html">
|
<trans-unit id="936788a5ab949fe0d70098ba051ac7a44999ff08" datatype="html">
|
||||||
@ -2084,7 +2084,7 @@
|
|||||||
<target state="translated">您真的要删除此标签吗?</target>
|
<target state="translated">您真的要删除此标签吗?</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-tag/admin-tag.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">85</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
<trans-unit id="a4b530787884b16ad8cca4fecf19b2d22c1f4c6a" datatype="html">
|
||||||
@ -2820,7 +2820,7 @@
|
|||||||
<target state="translated">报告数据故障</target>
|
<target state="translated">报告数据故障</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">385</context>
|
<context context-type="linenumber">409</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2573979fd7d8602db44b7b4ad493428bc354d2f5" datatype="html">
|
<trans-unit id="2573979fd7d8602db44b7b4ad493428bc354d2f5" datatype="html">
|
||||||
@ -7757,6 +7757,14 @@
|
|||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">50</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="cdcd7c871f3bc0326ee77e5aea82af1ef26f46f2" datatype="html">
|
||||||
|
<source>Market Data</source>
|
||||||
|
<target state="new">Market Data</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
|
||||||
|
<context context-type="linenumber">374</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
|
/** @type {import('@storybook/angular').StorybookConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
addons: ['@storybook/addon-essentials'],
|
addons: ['@storybook/addon-essentials'],
|
||||||
framework: {
|
framework: {
|
||||||
name: '@storybook/angular',
|
name: '@storybook/angular',
|
||||||
options: {}
|
options: {}
|
||||||
},
|
},
|
||||||
|
staticDirs: [
|
||||||
|
{
|
||||||
|
from: '../../../apps/client/src/assets',
|
||||||
|
to: '/assets'
|
||||||
|
}
|
||||||
|
],
|
||||||
stories: ['../**/*.stories.@(js|jsx|ts|tsx|mdx)']
|
stories: ['../**/*.stories.@(js|jsx|ts|tsx|mdx)']
|
||||||
};
|
};
|
||||||
|
|
||||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ghostfolio",
|
"name": "ghostfolio",
|
||||||
"version": "2.138.0",
|
"version": "2.139.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ghostfolio",
|
"name": "ghostfolio",
|
||||||
"version": "2.138.0",
|
"version": "2.139.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ghostfolio",
|
"name": "ghostfolio",
|
||||||
"version": "2.138.0",
|
"version": "2.139.1",
|
||||||
"homepage": "https://ghostfol.io",
|
"homepage": "https://ghostfol.io",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"repository": "https://github.com/ghostfolio/ghostfolio",
|
"repository": "https://github.com/ghostfolio/ghostfolio",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user