Feature/refactor unique asset type to asset profile identifier (#3636)
* Refactoring
This commit is contained in:
@@ -21,9 +21,9 @@ import {
|
|||||||
AdminMarketData,
|
AdminMarketData,
|
||||||
AdminMarketDataDetails,
|
AdminMarketDataDetails,
|
||||||
AdminMarketDataItem,
|
AdminMarketDataItem,
|
||||||
|
AssetProfileIdentifier,
|
||||||
EnhancedSymbolProfile,
|
EnhancedSymbolProfile,
|
||||||
Filter,
|
Filter
|
||||||
UniqueAsset
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { MarketDataPreset } from '@ghostfolio/common/types';
|
import { MarketDataPreset } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
@@ -59,7 +59,9 @@ export class AdminService {
|
|||||||
currency,
|
currency,
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol
|
symbol
|
||||||
}: UniqueAsset & { currency?: string }): Promise<SymbolProfile | never> {
|
}: AssetProfileIdentifier & { currency?: string }): Promise<
|
||||||
|
SymbolProfile | never
|
||||||
|
> {
|
||||||
try {
|
try {
|
||||||
if (dataSource === 'MANUAL') {
|
if (dataSource === 'MANUAL') {
|
||||||
return this.symbolProfileService.add({
|
return this.symbolProfileService.add({
|
||||||
@@ -96,7 +98,10 @@ export class AdminService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteProfileData({ dataSource, symbol }: UniqueAsset) {
|
public async deleteProfileData({
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
}: AssetProfileIdentifier) {
|
||||||
await this.marketDataService.deleteMany({ dataSource, symbol });
|
await this.marketDataService.deleteMany({ dataSource, symbol });
|
||||||
await this.symbolProfileService.delete({ dataSource, symbol });
|
await this.symbolProfileService.delete({ dataSource, symbol });
|
||||||
}
|
}
|
||||||
@@ -325,7 +330,7 @@ export class AdminService {
|
|||||||
public async getMarketDataBySymbol({
|
public async getMarketDataBySymbol({
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol
|
symbol
|
||||||
}: UniqueAsset): Promise<AdminMarketDataDetails> {
|
}: AssetProfileIdentifier): Promise<AdminMarketDataDetails> {
|
||||||
let activitiesCount: EnhancedSymbolProfile['activitiesCount'] = 0;
|
let activitiesCount: EnhancedSymbolProfile['activitiesCount'] = 0;
|
||||||
let currency: EnhancedSymbolProfile['currency'] = '-';
|
let currency: EnhancedSymbolProfile['currency'] = '-';
|
||||||
let dateOfFirstActivity: EnhancedSymbolProfile['dateOfFirstActivity'];
|
let dateOfFirstActivity: EnhancedSymbolProfile['dateOfFirstActivity'];
|
||||||
@@ -386,7 +391,7 @@ export class AdminService {
|
|||||||
symbol,
|
symbol,
|
||||||
symbolMapping,
|
symbolMapping,
|
||||||
url
|
url
|
||||||
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
|
}: AssetProfileIdentifier & Prisma.SymbolProfileUpdateInput) {
|
||||||
const symbolProfileOverrides = {
|
const symbolProfileOverrides = {
|
||||||
assetClass: assetClass as AssetClass,
|
assetClass: assetClass as AssetClass,
|
||||||
assetSubClass: assetSubClass as AssetSubClass,
|
assetSubClass: assetSubClass as AssetSubClass,
|
||||||
@@ -394,28 +399,28 @@ export class AdminService {
|
|||||||
url: url as string
|
url: url as string
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatedSymbolProfile: Prisma.SymbolProfileUpdateInput & UniqueAsset =
|
const updatedSymbolProfile: AssetProfileIdentifier &
|
||||||
{
|
Prisma.SymbolProfileUpdateInput = {
|
||||||
comment,
|
comment,
|
||||||
countries,
|
countries,
|
||||||
currency,
|
currency,
|
||||||
dataSource,
|
dataSource,
|
||||||
holdings,
|
holdings,
|
||||||
scraperConfiguration,
|
scraperConfiguration,
|
||||||
sectors,
|
sectors,
|
||||||
symbol,
|
symbol,
|
||||||
symbolMapping,
|
symbolMapping,
|
||||||
...(dataSource === 'MANUAL'
|
...(dataSource === 'MANUAL'
|
||||||
? { assetClass, assetSubClass, name, url }
|
? { assetClass, assetSubClass, name, url }
|
||||||
: {
|
: {
|
||||||
SymbolProfileOverrides: {
|
SymbolProfileOverrides: {
|
||||||
upsert: {
|
upsert: {
|
||||||
create: symbolProfileOverrides,
|
create: symbolProfileOverrides,
|
||||||
update: symbolProfileOverrides
|
update: symbolProfileOverrides
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
};
|
})
|
||||||
|
};
|
||||||
|
|
||||||
await this.symbolProfileService.updateSymbolProfile(updatedSymbolProfile);
|
await this.symbolProfileService.updateSymbolProfile(updatedSymbolProfile);
|
||||||
|
|
||||||
|
@@ -4,9 +4,9 @@ import { getInterval } from '@ghostfolio/api/helper/portfolio.helper';
|
|||||||
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 type {
|
import type {
|
||||||
|
AssetProfileIdentifier,
|
||||||
BenchmarkMarketDataDetails,
|
BenchmarkMarketDataDetails,
|
||||||
BenchmarkResponse,
|
BenchmarkResponse
|
||||||
UniqueAsset
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { permissions } from '@ghostfolio/common/permissions';
|
import { permissions } from '@ghostfolio/common/permissions';
|
||||||
import type { DateRange, RequestWithUser } from '@ghostfolio/common/types';
|
import type { DateRange, RequestWithUser } from '@ghostfolio/common/types';
|
||||||
@@ -41,7 +41,9 @@ export class BenchmarkController {
|
|||||||
@HasPermission(permissions.accessAdminControl)
|
@HasPermission(permissions.accessAdminControl)
|
||||||
@Post()
|
@Post()
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
public async addBenchmark(@Body() { dataSource, symbol }: UniqueAsset) {
|
public async addBenchmark(
|
||||||
|
@Body() { dataSource, symbol }: AssetProfileIdentifier
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const benchmark = await this.benchmarkService.addBenchmark({
|
const benchmark = await this.benchmarkService.addBenchmark({
|
||||||
dataSource,
|
dataSource,
|
||||||
|
@@ -17,11 +17,11 @@ import {
|
|||||||
resetHours
|
resetHours
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
Benchmark,
|
Benchmark,
|
||||||
BenchmarkMarketDataDetails,
|
BenchmarkMarketDataDetails,
|
||||||
BenchmarkProperty,
|
BenchmarkProperty,
|
||||||
BenchmarkResponse,
|
BenchmarkResponse
|
||||||
UniqueAsset
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { BenchmarkTrend } from '@ghostfolio/common/types';
|
import { BenchmarkTrend } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
@@ -61,7 +61,10 @@ export class BenchmarkService {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getBenchmarkTrends({ dataSource, symbol }: UniqueAsset) {
|
public async getBenchmarkTrends({
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
}: AssetProfileIdentifier) {
|
||||||
const historicalData = await this.marketDataService.marketDataItems({
|
const historicalData = await this.marketDataService.marketDataItems({
|
||||||
orderBy: {
|
orderBy: {
|
||||||
date: 'desc'
|
date: 'desc'
|
||||||
@@ -228,7 +231,7 @@ export class BenchmarkService {
|
|||||||
endDate?: Date;
|
endDate?: Date;
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
userCurrency: string;
|
userCurrency: string;
|
||||||
} & UniqueAsset): Promise<BenchmarkMarketDataDetails> {
|
} & AssetProfileIdentifier): Promise<BenchmarkMarketDataDetails> {
|
||||||
const marketData: { date: string; value: number }[] = [];
|
const marketData: { date: string; value: number }[] = [];
|
||||||
|
|
||||||
const days = differenceInDays(endDate, startDate) + 1;
|
const days = differenceInDays(endDate, startDate) + 1;
|
||||||
@@ -348,7 +351,7 @@ export class BenchmarkService {
|
|||||||
public async addBenchmark({
|
public async addBenchmark({
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol
|
symbol
|
||||||
}: UniqueAsset): Promise<Partial<SymbolProfile>> {
|
}: AssetProfileIdentifier): Promise<Partial<SymbolProfile>> {
|
||||||
const assetProfile = await this.prismaService.symbolProfile.findFirst({
|
const assetProfile = await this.prismaService.symbolProfile.findFirst({
|
||||||
where: {
|
where: {
|
||||||
dataSource,
|
dataSource,
|
||||||
@@ -385,7 +388,7 @@ export class BenchmarkService {
|
|||||||
public async deleteBenchmark({
|
public async deleteBenchmark({
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol
|
symbol
|
||||||
}: UniqueAsset): Promise<Partial<SymbolProfile>> {
|
}: AssetProfileIdentifier): Promise<Partial<SymbolProfile>> {
|
||||||
const assetProfile = await this.prismaService.symbolProfile.findFirst({
|
const assetProfile = await this.prismaService.symbolProfile.findFirst({
|
||||||
where: {
|
where: {
|
||||||
dataSource,
|
dataSource,
|
||||||
|
@@ -19,7 +19,7 @@ import {
|
|||||||
getAssetProfileIdentifier,
|
getAssetProfileIdentifier,
|
||||||
parseDate
|
parseDate
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||||
import {
|
import {
|
||||||
AccountWithPlatform,
|
AccountWithPlatform,
|
||||||
OrderWithAccount,
|
OrderWithAccount,
|
||||||
@@ -51,7 +51,7 @@ export class ImportService {
|
|||||||
dataSource,
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
userCurrency
|
userCurrency
|
||||||
}: UniqueAsset & { userCurrency: string }): Promise<Activity[]> {
|
}: AssetProfileIdentifier & { userCurrency: string }): Promise<Activity[]> {
|
||||||
try {
|
try {
|
||||||
const { firstBuyDate, historicalData, orders } =
|
const { firstBuyDate, historicalData, orders } =
|
||||||
await this.portfolioService.getPosition(dataSource, undefined, symbol);
|
await this.portfolioService.getPosition(dataSource, undefined, symbol);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||||
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
|
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
|
||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import { HttpException, Injectable } from '@nestjs/common';
|
import { HttpException, Injectable } from '@nestjs/common';
|
||||||
import { DataSource } from '@prisma/client';
|
import { DataSource } from '@prisma/client';
|
||||||
@@ -17,7 +17,7 @@ export class LogoService {
|
|||||||
public async getLogoByDataSourceAndSymbol({
|
public async getLogoByDataSourceAndSymbol({
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol
|
symbol
|
||||||
}: UniqueAsset) {
|
}: AssetProfileIdentifier) {
|
||||||
if (!DataSource[dataSource]) {
|
if (!DataSource[dataSource]) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
getReasonPhrase(StatusCodes.NOT_FOUND),
|
getReasonPhrase(StatusCodes.NOT_FOUND),
|
||||||
|
@@ -11,9 +11,9 @@ import {
|
|||||||
} from '@ghostfolio/common/config';
|
} from '@ghostfolio/common/config';
|
||||||
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
|
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
EnhancedSymbolProfile,
|
EnhancedSymbolProfile,
|
||||||
Filter,
|
Filter
|
||||||
UniqueAsset
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { OrderWithAccount } from '@ghostfolio/common/types';
|
import { OrderWithAccount } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ export class OrderService {
|
|||||||
symbol,
|
symbol,
|
||||||
tags,
|
tags,
|
||||||
userId
|
userId
|
||||||
}: { tags: Tag[]; userId: string } & UniqueAsset) {
|
}: { tags: Tag[]; userId: string } & AssetProfileIdentifier) {
|
||||||
const orders = await this.prismaService.order.findMany({
|
const orders = await this.prismaService.order.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId,
|
userId,
|
||||||
@@ -285,7 +285,7 @@ export class OrderService {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getLatestOrder({ dataSource, symbol }: UniqueAsset) {
|
public async getLatestOrder({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
return this.prismaService.order.findFirst({
|
return this.prismaService.order.findFirst({
|
||||||
orderBy: {
|
orderBy: {
|
||||||
date: 'desc'
|
date: 'desc'
|
||||||
@@ -464,7 +464,7 @@ export class OrderService {
|
|||||||
this.prismaService.order.count({ where })
|
this.prismaService.order.count({ where })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const uniqueAssets = uniqBy(
|
const assetProfileIdentifiers = uniqBy(
|
||||||
orders.map(({ SymbolProfile }) => {
|
orders.map(({ SymbolProfile }) => {
|
||||||
return {
|
return {
|
||||||
dataSource: SymbolProfile.dataSource,
|
dataSource: SymbolProfile.dataSource,
|
||||||
@@ -479,8 +479,9 @@ export class OrderService {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const assetProfiles =
|
const assetProfiles = await this.symbolProfileService.getSymbolProfiles(
|
||||||
await this.symbolProfileService.getSymbolProfiles(uniqueAssets);
|
assetProfileIdentifiers
|
||||||
|
);
|
||||||
|
|
||||||
const activities = orders.map((order) => {
|
const activities = orders.map((order) => {
|
||||||
const assetProfile = assetProfiles.find(({ dataSource, symbol }) => {
|
const assetProfile = assetProfiles.find(({ dataSource, symbol }) => {
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
|
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
|
||||||
import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
|
SymbolMetrics
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
|
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
|
||||||
|
|
||||||
export class MWRPortfolioCalculator extends PortfolioCalculator {
|
export class MWRPortfolioCalculator extends PortfolioCalculator {
|
||||||
@@ -27,7 +30,7 @@ export class MWRPortfolioCalculator extends PortfolioCalculator {
|
|||||||
};
|
};
|
||||||
start: Date;
|
start: Date;
|
||||||
step?: number;
|
step?: number;
|
||||||
} & UniqueAsset): SymbolMetrics {
|
} & AssetProfileIdentifier): SymbolMetrics {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,12 +19,12 @@ import {
|
|||||||
resetHours
|
resetHours
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
DataProviderInfo,
|
DataProviderInfo,
|
||||||
HistoricalDataItem,
|
HistoricalDataItem,
|
||||||
InvestmentItem,
|
InvestmentItem,
|
||||||
ResponseError,
|
ResponseError,
|
||||||
SymbolMetrics,
|
SymbolMetrics
|
||||||
UniqueAsset
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
|
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
|
||||||
import { DateRange, GroupBy } from '@ghostfolio/common/types';
|
import { DateRange, GroupBy } from '@ghostfolio/common/types';
|
||||||
@@ -356,15 +356,15 @@ export abstract class PortfolioCalculator {
|
|||||||
dataSource: item.dataSource,
|
dataSource: item.dataSource,
|
||||||
fee: item.fee,
|
fee: item.fee,
|
||||||
firstBuyDate: item.firstBuyDate,
|
firstBuyDate: item.firstBuyDate,
|
||||||
grossPerformance: !hasErrors ? grossPerformance ?? null : null,
|
grossPerformance: !hasErrors ? (grossPerformance ?? null) : null,
|
||||||
grossPerformancePercentage: !hasErrors
|
grossPerformancePercentage: !hasErrors
|
||||||
? grossPerformancePercentage ?? null
|
? (grossPerformancePercentage ?? null)
|
||||||
: null,
|
: null,
|
||||||
grossPerformancePercentageWithCurrencyEffect: !hasErrors
|
grossPerformancePercentageWithCurrencyEffect: !hasErrors
|
||||||
? grossPerformancePercentageWithCurrencyEffect ?? null
|
? (grossPerformancePercentageWithCurrencyEffect ?? null)
|
||||||
: null,
|
: null,
|
||||||
grossPerformanceWithCurrencyEffect: !hasErrors
|
grossPerformanceWithCurrencyEffect: !hasErrors
|
||||||
? grossPerformanceWithCurrencyEffect ?? null
|
? (grossPerformanceWithCurrencyEffect ?? null)
|
||||||
: null,
|
: null,
|
||||||
investment: totalInvestment,
|
investment: totalInvestment,
|
||||||
investmentWithCurrencyEffect: totalInvestmentWithCurrencyEffect,
|
investmentWithCurrencyEffect: totalInvestmentWithCurrencyEffect,
|
||||||
@@ -372,15 +372,15 @@ export abstract class PortfolioCalculator {
|
|||||||
marketSymbolMap[endDateString]?.[item.symbol]?.toNumber() ?? null,
|
marketSymbolMap[endDateString]?.[item.symbol]?.toNumber() ?? null,
|
||||||
marketPriceInBaseCurrency:
|
marketPriceInBaseCurrency:
|
||||||
marketPriceInBaseCurrency?.toNumber() ?? null,
|
marketPriceInBaseCurrency?.toNumber() ?? null,
|
||||||
netPerformance: !hasErrors ? netPerformance ?? null : null,
|
netPerformance: !hasErrors ? (netPerformance ?? null) : null,
|
||||||
netPerformancePercentage: !hasErrors
|
netPerformancePercentage: !hasErrors
|
||||||
? netPerformancePercentage ?? null
|
? (netPerformancePercentage ?? null)
|
||||||
: null,
|
: null,
|
||||||
netPerformancePercentageWithCurrencyEffect: !hasErrors
|
netPerformancePercentageWithCurrencyEffect: !hasErrors
|
||||||
? netPerformancePercentageWithCurrencyEffect ?? null
|
? (netPerformancePercentageWithCurrencyEffect ?? null)
|
||||||
: null,
|
: null,
|
||||||
netPerformanceWithCurrencyEffect: !hasErrors
|
netPerformanceWithCurrencyEffect: !hasErrors
|
||||||
? netPerformanceWithCurrencyEffect ?? null
|
? (netPerformanceWithCurrencyEffect ?? null)
|
||||||
: null,
|
: null,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
symbol: item.symbol,
|
symbol: item.symbol,
|
||||||
@@ -905,7 +905,7 @@ export abstract class PortfolioCalculator {
|
|||||||
};
|
};
|
||||||
start: Date;
|
start: Date;
|
||||||
step?: number;
|
step?: number;
|
||||||
} & UniqueAsset): SymbolMetrics;
|
} & AssetProfileIdentifier): SymbolMetrics;
|
||||||
|
|
||||||
public getTransactionPoints() {
|
public getTransactionPoints() {
|
||||||
return this.transactionPoints;
|
return this.transactionPoints;
|
||||||
|
@@ -2,7 +2,10 @@ import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/po
|
|||||||
import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface';
|
import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface';
|
||||||
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
|
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
|
||||||
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||||
import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
|
SymbolMetrics
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
|
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
|
||||||
|
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
@@ -151,7 +154,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
|
|||||||
};
|
};
|
||||||
start: Date;
|
start: Date;
|
||||||
step?: number;
|
step?: number;
|
||||||
} & UniqueAsset): SymbolMetrics {
|
} & AssetProfileIdentifier): SymbolMetrics {
|
||||||
const currentExchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)];
|
const currentExchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)];
|
||||||
const currentValues: { [date: string]: Big } = {};
|
const currentValues: { [date: string]: Big } = {};
|
||||||
const currentValuesWithCurrencyEffect: { [date: string]: Big } = {};
|
const currentValuesWithCurrencyEffect: { [date: string]: Big } = {};
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
||||||
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
||||||
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import { DataSource, MarketData } from '@prisma/client';
|
import { DataSource, MarketData } from '@prisma/client';
|
||||||
|
|
||||||
@@ -24,32 +24,32 @@ jest.mock('@ghostfolio/api/services/market-data/market-data.service', () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
getRange: ({
|
getRange: ({
|
||||||
|
assetProfileIdentifiers,
|
||||||
dateRangeEnd,
|
dateRangeEnd,
|
||||||
dateRangeStart,
|
dateRangeStart
|
||||||
uniqueAssets
|
|
||||||
}: {
|
}: {
|
||||||
|
assetProfileIdentifiers: AssetProfileIdentifier[];
|
||||||
dateRangeEnd: Date;
|
dateRangeEnd: Date;
|
||||||
dateRangeStart: Date;
|
dateRangeStart: Date;
|
||||||
uniqueAssets: UniqueAsset[];
|
|
||||||
}) => {
|
}) => {
|
||||||
return Promise.resolve<MarketData[]>([
|
return Promise.resolve<MarketData[]>([
|
||||||
{
|
{
|
||||||
createdAt: dateRangeStart,
|
createdAt: dateRangeStart,
|
||||||
dataSource: uniqueAssets[0].dataSource,
|
dataSource: assetProfileIdentifiers[0].dataSource,
|
||||||
date: dateRangeStart,
|
date: dateRangeStart,
|
||||||
id: '8fa48fde-f397-4b0d-adbc-fb940e830e6d',
|
id: '8fa48fde-f397-4b0d-adbc-fb940e830e6d',
|
||||||
marketPrice: 1841.823902,
|
marketPrice: 1841.823902,
|
||||||
state: 'CLOSE',
|
state: 'CLOSE',
|
||||||
symbol: uniqueAssets[0].symbol
|
symbol: assetProfileIdentifiers[0].symbol
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
createdAt: dateRangeEnd,
|
createdAt: dateRangeEnd,
|
||||||
dataSource: uniqueAssets[0].dataSource,
|
dataSource: assetProfileIdentifiers[0].dataSource,
|
||||||
date: dateRangeEnd,
|
date: dateRangeEnd,
|
||||||
id: '082d6893-df27-4c91-8a5d-092e84315b56',
|
id: '082d6893-df27-4c91-8a5d-092e84315b56',
|
||||||
marketPrice: 1847.839966,
|
marketPrice: 1847.839966,
|
||||||
state: 'CLOSE',
|
state: 'CLOSE',
|
||||||
symbol: uniqueAssets[0].symbol
|
symbol: assetProfileIdentifiers[0].symbol
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@@ -3,9 +3,9 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data
|
|||||||
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
||||||
import { resetHours } from '@ghostfolio/common/helper';
|
import { resetHours } from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
DataProviderInfo,
|
DataProviderInfo,
|
||||||
ResponseError,
|
ResponseError
|
||||||
UniqueAsset
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import type { RequestWithUser } from '@ghostfolio/common/types';
|
import type { RequestWithUser } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
@@ -80,17 +80,16 @@ export class CurrentRateService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const uniqueAssets: UniqueAsset[] = dataGatheringItems.map(
|
const assetProfileIdentifiers: AssetProfileIdentifier[] =
|
||||||
({ dataSource, symbol }) => {
|
dataGatheringItems.map(({ dataSource, symbol }) => {
|
||||||
return { dataSource, symbol };
|
return { dataSource, symbol };
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.marketDataService
|
this.marketDataService
|
||||||
.getRange({
|
.getRange({
|
||||||
dateQuery,
|
assetProfileIdentifiers,
|
||||||
uniqueAssets
|
dateQuery
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
return data.map(({ dataSource, date, marketPrice, symbol }) => {
|
return data.map(({ dataSource, date, marketPrice, symbol }) => {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
export interface GetValueObject extends UniqueAsset {
|
export interface GetValueObject extends AssetProfileIdentifier {
|
||||||
date: Date;
|
date: Date;
|
||||||
marketPrice: number;
|
marketPrice: number;
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||||
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
|
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
|
||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
@@ -28,7 +28,7 @@ export class RedisCacheService {
|
|||||||
return `portfolio-snapshot-${userId}`;
|
return `portfolio-snapshot-${userId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getQuoteKey({ dataSource, symbol }: UniqueAsset) {
|
public getQuoteKey({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
return `quote-${getAssetProfileIdentifier({ dataSource, symbol })}`;
|
return `quote-${getAssetProfileIdentifier({ dataSource, symbol })}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
import { HistoricalDataItem, UniqueAsset } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
|
HistoricalDataItem
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
export interface SymbolItem extends UniqueAsset {
|
export interface SymbolItem extends AssetProfileIdentifier {
|
||||||
currency: string;
|
currency: string;
|
||||||
historicalData: HistoricalDataItem[];
|
historicalData: HistoricalDataItem[];
|
||||||
marketPrice: number;
|
marketPrice: number;
|
||||||
|
@@ -40,13 +40,13 @@ export class SymbolService {
|
|||||||
const days = includeHistoricalData;
|
const days = includeHistoricalData;
|
||||||
|
|
||||||
const marketData = await this.marketDataService.getRange({
|
const marketData = await this.marketDataService.getRange({
|
||||||
dateQuery: { gte: subDays(new Date(), days) },
|
assetProfileIdentifiers: [
|
||||||
uniqueAssets: [
|
|
||||||
{
|
{
|
||||||
dataSource: dataGatheringItem.dataSource,
|
dataSource: dataGatheringItem.dataSource,
|
||||||
symbol: dataGatheringItem.symbol
|
symbol: dataGatheringItem.symbol
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
dateQuery: { gte: subDays(new Date(), days) }
|
||||||
});
|
});
|
||||||
|
|
||||||
historicalData = marketData.map(({ date, marketPrice: value }) => {
|
historicalData = marketData.map(({ date, marketPrice: value }) => {
|
||||||
|
@@ -7,7 +7,7 @@ import {
|
|||||||
GATHER_HISTORICAL_MARKET_DATA_PROCESS
|
GATHER_HISTORICAL_MARKET_DATA_PROCESS
|
||||||
} from '@ghostfolio/common/config';
|
} from '@ghostfolio/common/config';
|
||||||
import { DATE_FORMAT, getStartOfUtcDate } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT, getStartOfUtcDate } from '@ghostfolio/common/helper';
|
||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import { Process, Processor } from '@nestjs/bull';
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
@@ -35,7 +35,7 @@ export class DataGatheringProcessor {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Process({ concurrency: 1, name: GATHER_ASSET_PROFILE_PROCESS })
|
@Process({ concurrency: 1, name: GATHER_ASSET_PROFILE_PROCESS })
|
||||||
public async gatherAssetProfile(job: Job<UniqueAsset>) {
|
public async gatherAssetProfile(job: Job<AssetProfileIdentifier>) {
|
||||||
try {
|
try {
|
||||||
Logger.log(
|
Logger.log(
|
||||||
`Asset profile data gathering has been started for ${job.data.symbol} (${job.data.dataSource})`,
|
`Asset profile data gathering has been started for ${job.data.symbol} (${job.data.dataSource})`,
|
||||||
|
@@ -20,7 +20,10 @@ import {
|
|||||||
getAssetProfileIdentifier,
|
getAssetProfileIdentifier,
|
||||||
resetHours
|
resetHours
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
import { BenchmarkProperty, UniqueAsset } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
|
BenchmarkProperty
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import { InjectQueue } from '@nestjs/bull';
|
import { InjectQueue } from '@nestjs/bull';
|
||||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
@@ -91,7 +94,7 @@ export class DataGatheringService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async gatherSymbol({ dataSource, symbol }: UniqueAsset) {
|
public async gatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
await this.marketDataService.deleteMany({ dataSource, symbol });
|
await this.marketDataService.deleteMany({ dataSource, symbol });
|
||||||
|
|
||||||
const dataGatheringItems = (await this.getSymbolsMax()).filter(
|
const dataGatheringItems = (await this.getSymbolsMax()).filter(
|
||||||
@@ -146,23 +149,29 @@ export class DataGatheringService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async gatherAssetProfiles(aUniqueAssets?: UniqueAsset[]) {
|
public async gatherAssetProfiles(
|
||||||
let uniqueAssets = aUniqueAssets?.filter((dataGatheringItem) => {
|
aAssetProfileIdentifiers?: AssetProfileIdentifier[]
|
||||||
return dataGatheringItem.dataSource !== 'MANUAL';
|
) {
|
||||||
});
|
let assetProfileIdentifiers = aAssetProfileIdentifiers?.filter(
|
||||||
|
(dataGatheringItem) => {
|
||||||
|
return dataGatheringItem.dataSource !== 'MANUAL';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (!uniqueAssets) {
|
if (!assetProfileIdentifiers) {
|
||||||
uniqueAssets = await this.getAllAssetProfileIdentifiers();
|
assetProfileIdentifiers = await this.getAllAssetProfileIdentifiers();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uniqueAssets.length <= 0) {
|
if (assetProfileIdentifiers.length <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const assetProfiles =
|
const assetProfiles = await this.dataProviderService.getAssetProfiles(
|
||||||
await this.dataProviderService.getAssetProfiles(uniqueAssets);
|
assetProfileIdentifiers
|
||||||
const symbolProfiles =
|
);
|
||||||
await this.symbolProfileService.getSymbolProfiles(uniqueAssets);
|
const symbolProfiles = await this.symbolProfileService.getSymbolProfiles(
|
||||||
|
assetProfileIdentifiers
|
||||||
|
);
|
||||||
|
|
||||||
for (const [symbol, assetProfile] of Object.entries(assetProfiles)) {
|
for (const [symbol, assetProfile] of Object.entries(assetProfiles)) {
|
||||||
const symbolMapping = symbolProfiles.find((symbolProfile) => {
|
const symbolMapping = symbolProfiles.find((symbolProfile) => {
|
||||||
@@ -248,7 +257,7 @@ export class DataGatheringService {
|
|||||||
'DataGatheringService'
|
'DataGatheringService'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (uniqueAssets.length === 1) {
|
if (assetProfileIdentifiers.length === 1) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,7 +293,9 @@ export class DataGatheringService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllAssetProfileIdentifiers(): Promise<UniqueAsset[]> {
|
public async getAllAssetProfileIdentifiers(): Promise<
|
||||||
|
AssetProfileIdentifier[]
|
||||||
|
> {
|
||||||
const symbolProfiles = await this.prismaService.symbolProfile.findMany({
|
const symbolProfiles = await this.prismaService.symbolProfile.findMany({
|
||||||
orderBy: [{ symbol: 'asc' }]
|
orderBy: [{ symbol: 'asc' }]
|
||||||
});
|
});
|
||||||
@@ -305,7 +316,7 @@ export class DataGatheringService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getAssetProfileIdentifiersWithCompleteMarketData(): Promise<
|
private async getAssetProfileIdentifiersWithCompleteMarketData(): Promise<
|
||||||
UniqueAsset[]
|
AssetProfileIdentifier[]
|
||||||
> {
|
> {
|
||||||
return (
|
return (
|
||||||
await this.prismaService.marketData.groupBy({
|
await this.prismaService.marketData.groupBy({
|
||||||
|
@@ -20,7 +20,7 @@ import {
|
|||||||
getStartOfUtcDate,
|
getStartOfUtcDate,
|
||||||
isDerivedCurrency
|
isDerivedCurrency
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||||
import type { Granularity, UserWithSettings } from '@ghostfolio/common/types';
|
import type { Granularity, UserWithSettings } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
@@ -75,7 +75,7 @@ export class DataProviderService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetProfiles(items: UniqueAsset[]): Promise<{
|
public async getAssetProfiles(items: AssetProfileIdentifier[]): Promise<{
|
||||||
[symbol: string]: Partial<SymbolProfile>;
|
[symbol: string]: Partial<SymbolProfile>;
|
||||||
}> {
|
}> {
|
||||||
const response: {
|
const response: {
|
||||||
@@ -173,7 +173,7 @@ export class DataProviderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getHistorical(
|
public async getHistorical(
|
||||||
aItems: UniqueAsset[],
|
aItems: AssetProfileIdentifier[],
|
||||||
aGranularity: Granularity = 'month',
|
aGranularity: Granularity = 'month',
|
||||||
from: Date,
|
from: Date,
|
||||||
to: Date
|
to: Date
|
||||||
@@ -243,7 +243,7 @@ export class DataProviderService {
|
|||||||
from,
|
from,
|
||||||
to
|
to
|
||||||
}: {
|
}: {
|
||||||
dataGatheringItems: UniqueAsset[];
|
dataGatheringItems: AssetProfileIdentifier[];
|
||||||
from: Date;
|
from: Date;
|
||||||
to: Date;
|
to: Date;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
@@ -350,7 +350,7 @@ export class DataProviderService {
|
|||||||
useCache = true,
|
useCache = true,
|
||||||
user
|
user
|
||||||
}: {
|
}: {
|
||||||
items: UniqueAsset[];
|
items: AssetProfileIdentifier[];
|
||||||
requestTimeout?: number;
|
requestTimeout?: number;
|
||||||
useCache?: boolean;
|
useCache?: boolean;
|
||||||
user?: UserWithSettings;
|
user?: UserWithSettings;
|
||||||
@@ -376,7 +376,7 @@ export class DataProviderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get items from cache
|
// Get items from cache
|
||||||
const itemsToFetch: UniqueAsset[] = [];
|
const itemsToFetch: AssetProfileIdentifier[] = [];
|
||||||
|
|
||||||
for (const { dataSource, symbol } of items) {
|
for (const { dataSource, symbol } of items) {
|
||||||
if (useCache) {
|
if (useCache) {
|
||||||
@@ -633,7 +633,7 @@ export class DataProviderService {
|
|||||||
dataGatheringItems
|
dataGatheringItems
|
||||||
}: {
|
}: {
|
||||||
currency: string;
|
currency: string;
|
||||||
dataGatheringItems: UniqueAsset[];
|
dataGatheringItems: AssetProfileIdentifier[];
|
||||||
}) {
|
}) {
|
||||||
return dataGatheringItems.some(({ dataSource, symbol }) => {
|
return dataGatheringItems.some(({ dataSource, symbol }) => {
|
||||||
return (
|
return (
|
||||||
|
@@ -361,13 +361,13 @@ export class ExchangeRateDataService {
|
|||||||
const symbol = `${currencyFrom}${currencyTo}`;
|
const symbol = `${currencyFrom}${currencyTo}`;
|
||||||
|
|
||||||
const marketData = await this.marketDataService.getRange({
|
const marketData = await this.marketDataService.getRange({
|
||||||
dateQuery: { gte: startDate, lt: endDate },
|
assetProfileIdentifiers: [
|
||||||
uniqueAssets: [
|
|
||||||
{
|
{
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol
|
symbol
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
dateQuery: { gte: startDate, lt: endDate }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (marketData?.length > 0) {
|
if (marketData?.length > 0) {
|
||||||
@@ -392,13 +392,13 @@ export class ExchangeRateDataService {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const marketData = await this.marketDataService.getRange({
|
const marketData = await this.marketDataService.getRange({
|
||||||
dateQuery: { gte: startDate, lt: endDate },
|
assetProfileIdentifiers: [
|
||||||
uniqueAssets: [
|
|
||||||
{
|
{
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol: `${DEFAULT_CURRENCY}${currencyFrom}`
|
symbol: `${DEFAULT_CURRENCY}${currencyFrom}`
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
dateQuery: { gte: startDate, lt: endDate }
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const { date, marketPrice } of marketData) {
|
for (const { date, marketPrice } of marketData) {
|
||||||
@@ -415,16 +415,16 @@ export class ExchangeRateDataService {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const marketData = await this.marketDataService.getRange({
|
const marketData = await this.marketDataService.getRange({
|
||||||
dateQuery: {
|
assetProfileIdentifiers: [
|
||||||
gte: startDate,
|
|
||||||
lt: endDate
|
|
||||||
},
|
|
||||||
uniqueAssets: [
|
|
||||||
{
|
{
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol: `${DEFAULT_CURRENCY}${currencyTo}`
|
symbol: `${DEFAULT_CURRENCY}${currencyTo}`
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
dateQuery: {
|
||||||
|
gte: startDate,
|
||||||
|
lt: endDate
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const { date, marketPrice } of marketData) {
|
for (const { date, marketPrice } of marketData) {
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
import { DataProviderInfo, UniqueAsset } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
|
DataProviderInfo
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { MarketState } from '@ghostfolio/common/types';
|
import { MarketState } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -34,6 +37,6 @@ export interface IDataProviderResponse {
|
|||||||
marketState: MarketState;
|
marketState: MarketState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDataGatheringItem extends UniqueAsset {
|
export interface IDataGatheringItem extends AssetProfileIdentifier {
|
||||||
date?: Date;
|
date?: Date;
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ import { DateQuery } from '@ghostfolio/api/app/portfolio/interfaces/date-query.i
|
|||||||
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
|
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||||
import { resetHours } from '@ghostfolio/common/helper';
|
import { resetHours } from '@ghostfolio/common/helper';
|
||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
export class MarketDataService {
|
export class MarketDataService {
|
||||||
public constructor(private readonly prismaService: PrismaService) {}
|
public constructor(private readonly prismaService: PrismaService) {}
|
||||||
|
|
||||||
public async deleteMany({ dataSource, symbol }: UniqueAsset) {
|
public async deleteMany({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
return this.prismaService.marketData.deleteMany({
|
return this.prismaService.marketData.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
dataSource,
|
dataSource,
|
||||||
@@ -40,7 +40,7 @@ export class MarketDataService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getMax({ dataSource, symbol }: UniqueAsset) {
|
public async getMax({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
return this.prismaService.marketData.findFirst({
|
return this.prismaService.marketData.findFirst({
|
||||||
select: {
|
select: {
|
||||||
date: true,
|
date: true,
|
||||||
@@ -59,11 +59,11 @@ export class MarketDataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getRange({
|
public async getRange({
|
||||||
dateQuery,
|
assetProfileIdentifiers,
|
||||||
uniqueAssets
|
dateQuery
|
||||||
}: {
|
}: {
|
||||||
|
assetProfileIdentifiers: AssetProfileIdentifier[];
|
||||||
dateQuery: DateQuery;
|
dateQuery: DateQuery;
|
||||||
uniqueAssets: UniqueAsset[];
|
|
||||||
}): Promise<MarketData[]> {
|
}): Promise<MarketData[]> {
|
||||||
return this.prismaService.marketData.findMany({
|
return this.prismaService.marketData.findMany({
|
||||||
orderBy: [
|
orderBy: [
|
||||||
@@ -76,13 +76,13 @@ export class MarketDataService {
|
|||||||
],
|
],
|
||||||
where: {
|
where: {
|
||||||
dataSource: {
|
dataSource: {
|
||||||
in: uniqueAssets.map(({ dataSource }) => {
|
in: assetProfileIdentifiers.map(({ dataSource }) => {
|
||||||
return dataSource;
|
return dataSource;
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
date: dateQuery,
|
date: dateQuery,
|
||||||
symbol: {
|
symbol: {
|
||||||
in: uniqueAssets.map(({ symbol }) => {
|
in: assetProfileIdentifiers.map(({ symbol }) => {
|
||||||
return symbol;
|
return symbol;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||||
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
||||||
import {
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
EnhancedSymbolProfile,
|
EnhancedSymbolProfile,
|
||||||
Holding,
|
Holding,
|
||||||
ScraperConfiguration,
|
ScraperConfiguration
|
||||||
UniqueAsset
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { Country } from '@ghostfolio/common/interfaces/country.interface';
|
import { Country } from '@ghostfolio/common/interfaces/country.interface';
|
||||||
import { Sector } from '@ghostfolio/common/interfaces/sector.interface';
|
import { Sector } from '@ghostfolio/common/interfaces/sector.interface';
|
||||||
@@ -23,7 +23,7 @@ export class SymbolProfileService {
|
|||||||
return this.prismaService.symbolProfile.create({ data: assetProfile });
|
return this.prismaService.symbolProfile.create({ data: assetProfile });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async delete({ dataSource, symbol }: UniqueAsset) {
|
public async delete({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
return this.prismaService.symbolProfile.delete({
|
return this.prismaService.symbolProfile.delete({
|
||||||
where: { dataSource_symbol: { dataSource, symbol } }
|
where: { dataSource_symbol: { dataSource, symbol } }
|
||||||
});
|
});
|
||||||
@@ -36,7 +36,7 @@ export class SymbolProfileService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getSymbolProfiles(
|
public async getSymbolProfiles(
|
||||||
aUniqueAssets: UniqueAsset[]
|
aAssetProfileIdentifiers: AssetProfileIdentifier[]
|
||||||
): Promise<EnhancedSymbolProfile[]> {
|
): Promise<EnhancedSymbolProfile[]> {
|
||||||
return this.prismaService.symbolProfile
|
return this.prismaService.symbolProfile
|
||||||
.findMany({
|
.findMany({
|
||||||
@@ -54,7 +54,7 @@ export class SymbolProfileService {
|
|||||||
SymbolProfileOverrides: true
|
SymbolProfileOverrides: true
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
OR: aUniqueAssets.map(({ dataSource, symbol }) => {
|
OR: aAssetProfileIdentifiers.map(({ dataSource, symbol }) => {
|
||||||
return {
|
return {
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol
|
symbol
|
||||||
@@ -140,7 +140,7 @@ export class SymbolProfileService {
|
|||||||
symbolMapping,
|
symbolMapping,
|
||||||
SymbolProfileOverrides,
|
SymbolProfileOverrides,
|
||||||
url
|
url
|
||||||
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
|
}: AssetProfileIdentifier & Prisma.SymbolProfileUpdateInput) {
|
||||||
return this.prismaService.symbolProfile.update({
|
return this.prismaService.symbolProfile.update({
|
||||||
data: {
|
data: {
|
||||||
assetClass,
|
assetClass,
|
||||||
|
@@ -7,9 +7,9 @@ import {
|
|||||||
} from '@ghostfolio/common/config';
|
} from '@ghostfolio/common/config';
|
||||||
import { getDateFormatString } from '@ghostfolio/common/helper';
|
import { getDateFormatString } from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
Filter,
|
Filter,
|
||||||
InfoItem,
|
InfoItem,
|
||||||
UniqueAsset,
|
|
||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
|
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
|
||||||
@@ -225,7 +225,7 @@ export class AdminMarketDataComponent
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onDeleteAssetProfile({ dataSource, symbol }: UniqueAsset) {
|
public onDeleteAssetProfile({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
this.adminMarketDataService.deleteAssetProfile({ dataSource, symbol });
|
this.adminMarketDataService.deleteAssetProfile({ dataSource, symbol });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,21 +266,27 @@ export class AdminMarketDataComponent
|
|||||||
.subscribe(() => {});
|
.subscribe(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onGatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) {
|
public onGatherProfileDataBySymbol({
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
}: AssetProfileIdentifier) {
|
||||||
this.adminService
|
this.adminService
|
||||||
.gatherProfileDataBySymbol({ dataSource, symbol })
|
.gatherProfileDataBySymbol({ dataSource, symbol })
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(() => {});
|
.subscribe(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onGatherSymbol({ dataSource, symbol }: UniqueAsset) {
|
public onGatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
this.adminService
|
this.adminService
|
||||||
.gatherSymbol({ dataSource, symbol })
|
.gatherSymbol({ dataSource, symbol })
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(() => {});
|
.subscribe(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onOpenAssetProfileDialog({ dataSource, symbol }: UniqueAsset) {
|
public onOpenAssetProfileDialog({
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
}: AssetProfileIdentifier) {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: {
|
queryParams: {
|
||||||
dataSource,
|
dataSource,
|
||||||
|
@@ -2,8 +2,8 @@ import { AdminService } from '@ghostfolio/client/services/admin.service';
|
|||||||
import { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config';
|
import { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config';
|
||||||
import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper';
|
import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
AdminMarketDataItem,
|
AssetProfileIdentifier,
|
||||||
UniqueAsset
|
AdminMarketDataItem
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
@@ -13,7 +13,7 @@ import { EMPTY, catchError, finalize, forkJoin, takeUntil } from 'rxjs';
|
|||||||
export class AdminMarketDataService {
|
export class AdminMarketDataService {
|
||||||
public constructor(private adminService: AdminService) {}
|
public constructor(private adminService: AdminService) {}
|
||||||
|
|
||||||
public deleteAssetProfile({ dataSource, symbol }: UniqueAsset) {
|
public deleteAssetProfile({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
const confirmation = confirm(
|
const confirmation = confirm(
|
||||||
$localize`Do you really want to delete this asset profile?`
|
$localize`Do you really want to delete this asset profile?`
|
||||||
);
|
);
|
||||||
@@ -29,15 +29,19 @@ export class AdminMarketDataService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteAssetProfiles(uniqueAssets: UniqueAsset[]) {
|
public deleteAssetProfiles(
|
||||||
|
aAssetProfileIdentifiers: AssetProfileIdentifier[]
|
||||||
|
) {
|
||||||
const confirmation = confirm(
|
const confirmation = confirm(
|
||||||
$localize`Do you really want to delete these profiles?`
|
$localize`Do you really want to delete these profiles?`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (confirmation) {
|
if (confirmation) {
|
||||||
const deleteRequests = uniqueAssets.map(({ dataSource, symbol }) => {
|
const deleteRequests = aAssetProfileIdentifiers.map(
|
||||||
return this.adminService.deleteProfileData({ dataSource, symbol });
|
({ dataSource, symbol }) => {
|
||||||
});
|
return this.adminService.deleteProfileData({ dataSource, symbol });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
forkJoin(deleteRequests)
|
forkJoin(deleteRequests)
|
||||||
.pipe(
|
.pipe(
|
||||||
|
@@ -8,7 +8,7 @@ import { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config';
|
|||||||
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
AdminMarketDataDetails,
|
AdminMarketDataDetails,
|
||||||
UniqueAsset
|
AssetProfileIdentifier
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { translate } from '@ghostfolio/ui/i18n';
|
import { translate } from '@ghostfolio/ui/i18n';
|
||||||
|
|
||||||
@@ -175,20 +175,23 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
|||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) {
|
public onDeleteProfileData({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
this.adminMarketDataService.deleteAssetProfile({ dataSource, symbol });
|
this.adminMarketDataService.deleteAssetProfile({ dataSource, symbol });
|
||||||
|
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onGatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) {
|
public onGatherProfileDataBySymbol({
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
}: AssetProfileIdentifier) {
|
||||||
this.adminService
|
this.adminService
|
||||||
.gatherProfileDataBySymbol({ dataSource, symbol })
|
.gatherProfileDataBySymbol({ dataSource, symbol })
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(() => {});
|
.subscribe(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onGatherSymbol({ dataSource, symbol }: UniqueAsset) {
|
public onGatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
this.adminService
|
this.adminService
|
||||||
.gatherSymbol({ dataSource, symbol })
|
.gatherSymbol({ dataSource, symbol })
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
@@ -242,7 +245,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSetBenchmark({ dataSource, symbol }: UniqueAsset) {
|
public onSetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
this.dataService
|
this.dataService
|
||||||
.postBenchmark({ dataSource, symbol })
|
.postBenchmark({ dataSource, symbol })
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
@@ -342,7 +345,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onUnsetBenchmark({ dataSource, symbol }: UniqueAsset) {
|
public onUnsetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
this.dataService
|
this.dataService
|
||||||
.deleteBenchmark({ dataSource, symbol })
|
.deleteBenchmark({ dataSource, symbol })
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
@@ -2,8 +2,8 @@ import { DataService } from '@ghostfolio/client/services/data.service';
|
|||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import {
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
PortfolioPosition,
|
PortfolioPosition,
|
||||||
UniqueAsset,
|
|
||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
@@ -108,7 +108,7 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
|||||||
this.initialize();
|
this.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSymbolClicked({ dataSource, symbol }: UniqueAsset) {
|
public onSymbolClicked({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
if (dataSource && symbol) {
|
if (dataSource && symbol) {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: { dataSource, symbol, holdingDetailDialog: true }
|
queryParams: { dataSource, symbol, holdingDetailDialog: true }
|
||||||
|
@@ -5,9 +5,9 @@ import { ImpersonationStorageService } from '@ghostfolio/client/services/imperso
|
|||||||
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';
|
||||||
import {
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
LineChartItem,
|
LineChartItem,
|
||||||
PortfolioPerformance,
|
PortfolioPerformance,
|
||||||
UniqueAsset,
|
|
||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
@@ -26,7 +26,7 @@ import { takeUntil } from 'rxjs/operators';
|
|||||||
export class HomeOverviewComponent implements OnDestroy, OnInit {
|
export class HomeOverviewComponent implements OnDestroy, OnInit {
|
||||||
public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS;
|
public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS;
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public errors: UniqueAsset[];
|
public errors: AssetProfileIdentifier[];
|
||||||
public hasError: boolean;
|
public hasError: boolean;
|
||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
public hasPermissionToCreateOrder: boolean;
|
public hasPermissionToCreateOrder: boolean;
|
||||||
|
@@ -38,6 +38,7 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces';
|
|||||||
export class ImportActivitiesDialog implements OnDestroy {
|
export class ImportActivitiesDialog implements OnDestroy {
|
||||||
public accounts: CreateAccountDto[] = [];
|
public accounts: CreateAccountDto[] = [];
|
||||||
public activities: Activity[] = [];
|
public activities: Activity[] = [];
|
||||||
|
public assetProfileForm: FormGroup;
|
||||||
public dataSource: MatTableDataSource<Activity>;
|
public dataSource: MatTableDataSource<Activity>;
|
||||||
public details: any[] = [];
|
public details: any[] = [];
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
@@ -53,7 +54,6 @@ export class ImportActivitiesDialog implements OnDestroy {
|
|||||||
public sortDirection: SortDirection = 'desc';
|
public sortDirection: SortDirection = 'desc';
|
||||||
public stepperOrientation: StepperOrientation;
|
public stepperOrientation: StepperOrientation;
|
||||||
public totalItems: number;
|
public totalItems: number;
|
||||||
public uniqueAssetForm: FormGroup;
|
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
@@ -73,8 +73,8 @@ export class ImportActivitiesDialog implements OnDestroy {
|
|||||||
this.stepperOrientation =
|
this.stepperOrientation =
|
||||||
this.deviceType === 'mobile' ? 'vertical' : 'horizontal';
|
this.deviceType === 'mobile' ? 'vertical' : 'horizontal';
|
||||||
|
|
||||||
this.uniqueAssetForm = this.formBuilder.group({
|
this.assetProfileForm = this.formBuilder.group({
|
||||||
uniqueAsset: [undefined, Validators.required]
|
assetProfileIdentifier: [undefined, Validators.required]
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -85,7 +85,7 @@ export class ImportActivitiesDialog implements OnDestroy {
|
|||||||
|
|
||||||
this.dialogTitle = $localize`Import Dividends`;
|
this.dialogTitle = $localize`Import Dividends`;
|
||||||
this.mode = 'DIVIDEND';
|
this.mode = 'DIVIDEND';
|
||||||
this.uniqueAssetForm.get('uniqueAsset').disable();
|
this.assetProfileForm.get('assetProfileIdentifier').disable();
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchPortfolioHoldings({
|
.fetchPortfolioHoldings({
|
||||||
@@ -102,7 +102,7 @@ export class ImportActivitiesDialog implements OnDestroy {
|
|||||||
this.holdings = sortBy(holdings, ({ name }) => {
|
this.holdings = sortBy(holdings, ({ name }) => {
|
||||||
return name.toLowerCase();
|
return name.toLowerCase();
|
||||||
});
|
});
|
||||||
this.uniqueAssetForm.get('uniqueAsset').enable();
|
this.assetProfileForm.get('assetProfileIdentifier').enable();
|
||||||
|
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
|
||||||
@@ -167,10 +167,11 @@ export class ImportActivitiesDialog implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onLoadDividends(aStepper: MatStepper) {
|
public onLoadDividends(aStepper: MatStepper) {
|
||||||
this.uniqueAssetForm.get('uniqueAsset').disable();
|
this.assetProfileForm.get('assetProfileIdentifier').disable();
|
||||||
|
|
||||||
const { dataSource, symbol } =
|
const { dataSource, symbol } = this.assetProfileForm.get(
|
||||||
this.uniqueAssetForm.get('uniqueAsset').value;
|
'assetProfileIdentifier'
|
||||||
|
).value;
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchDividendsImport({
|
.fetchDividendsImport({
|
||||||
@@ -193,7 +194,7 @@ export class ImportActivitiesDialog implements OnDestroy {
|
|||||||
this.details = [];
|
this.details = [];
|
||||||
this.errorMessages = [];
|
this.errorMessages = [];
|
||||||
this.importStep = ImportStep.SELECT_ACTIVITIES;
|
this.importStep = ImportStep.SELECT_ACTIVITIES;
|
||||||
this.uniqueAssetForm.get('uniqueAsset').enable();
|
this.assetProfileForm.get('assetProfileIdentifier').enable();
|
||||||
|
|
||||||
aStepper.reset();
|
aStepper.reset();
|
||||||
}
|
}
|
||||||
|
@@ -25,14 +25,14 @@
|
|||||||
<div class="pt-3">
|
<div class="pt-3">
|
||||||
@if (mode === 'DIVIDEND') {
|
@if (mode === 'DIVIDEND') {
|
||||||
<form
|
<form
|
||||||
[formGroup]="uniqueAssetForm"
|
[formGroup]="assetProfileForm"
|
||||||
(ngSubmit)="onLoadDividends(stepper)"
|
(ngSubmit)="onLoadDividends(stepper)"
|
||||||
>
|
>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Holding</mat-label>
|
<mat-label i18n>Holding</mat-label>
|
||||||
<mat-select formControlName="uniqueAsset">
|
<mat-select formControlName="assetProfileIdentifier">
|
||||||
<mat-select-trigger>{{
|
<mat-select-trigger>{{
|
||||||
uniqueAssetForm.get('uniqueAsset')?.value?.name
|
assetProfileForm.get('assetProfileIdentifier')?.value?.name
|
||||||
}}</mat-select-trigger>
|
}}</mat-select-trigger>
|
||||||
@for (holding of holdings; track holding) {
|
@for (holding of holdings; track holding) {
|
||||||
<mat-option
|
<mat-option
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
color="primary"
|
color="primary"
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
type="submit"
|
type="submit"
|
||||||
[disabled]="!uniqueAssetForm.valid"
|
[disabled]="!assetProfileForm.valid"
|
||||||
>
|
>
|
||||||
<span i18n>Load Dividends</span>
|
<span i18n>Load Dividends</span>
|
||||||
</button>
|
</button>
|
||||||
|
@@ -6,10 +6,10 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
|
|||||||
import { MAX_TOP_HOLDINGS, UNKNOWN_KEY } from '@ghostfolio/common/config';
|
import { MAX_TOP_HOLDINGS, UNKNOWN_KEY } from '@ghostfolio/common/config';
|
||||||
import { prettifySymbol } from '@ghostfolio/common/helper';
|
import { prettifySymbol } from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
Holding,
|
Holding,
|
||||||
PortfolioDetails,
|
PortfolioDetails,
|
||||||
PortfolioPosition,
|
PortfolioPosition,
|
||||||
UniqueAsset,
|
|
||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { Market, MarketAdvanced } from '@ghostfolio/common/types';
|
import { Market, MarketAdvanced } from '@ghostfolio/common/types';
|
||||||
@@ -161,7 +161,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
this.initialize();
|
this.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onAccountChartClicked({ symbol }: UniqueAsset) {
|
public onAccountChartClicked({ symbol }: AssetProfileIdentifier) {
|
||||||
if (symbol && symbol !== UNKNOWN_KEY) {
|
if (symbol && symbol !== UNKNOWN_KEY) {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: { accountId: symbol, accountDetailDialog: true }
|
queryParams: { accountId: symbol, accountDetailDialog: true }
|
||||||
@@ -169,7 +169,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSymbolChartClicked({ dataSource, symbol }: UniqueAsset) {
|
public onSymbolChartClicked({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
if (dataSource && symbol) {
|
if (dataSource && symbol) {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: { dataSource, symbol, holdingDetailDialog: true }
|
queryParams: { dataSource, symbol, holdingDetailDialog: true }
|
||||||
|
@@ -7,13 +7,13 @@ 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 { DATE_FORMAT } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
AdminData,
|
AdminData,
|
||||||
AdminJobs,
|
AdminJobs,
|
||||||
AdminMarketData,
|
AdminMarketData,
|
||||||
AdminMarketDataDetails,
|
AdminMarketDataDetails,
|
||||||
EnhancedSymbolProfile,
|
EnhancedSymbolProfile,
|
||||||
Filter,
|
Filter
|
||||||
UniqueAsset
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||||
@@ -35,7 +35,7 @@ export class AdminService {
|
|||||||
private http: HttpClient
|
private http: HttpClient
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public addAssetProfile({ dataSource, symbol }: UniqueAsset) {
|
public addAssetProfile({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
return this.http.post<void>(
|
return this.http.post<void>(
|
||||||
`/api/v1/admin/profile-data/${dataSource}/${symbol}`,
|
`/api/v1/admin/profile-data/${dataSource}/${symbol}`,
|
||||||
null
|
null
|
||||||
@@ -62,7 +62,7 @@ export class AdminService {
|
|||||||
return this.http.delete<void>(`/api/v1/platform/${aId}`);
|
return this.http.delete<void>(`/api/v1/platform/${aId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteProfileData({ dataSource, symbol }: UniqueAsset) {
|
public deleteProfileData({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
return this.http.delete<void>(
|
return this.http.delete<void>(
|
||||||
`/api/v1/admin/profile-data/${dataSource}/${symbol}`
|
`/api/v1/admin/profile-data/${dataSource}/${symbol}`
|
||||||
);
|
);
|
||||||
@@ -167,7 +167,10 @@ export class AdminService {
|
|||||||
return this.http.post<void>('/api/v1/admin/gather/profile-data', {});
|
return this.http.post<void>('/api/v1/admin/gather/profile-data', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
public gatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) {
|
public gatherProfileDataBySymbol({
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
}: AssetProfileIdentifier) {
|
||||||
return this.http.post<void>(
|
return this.http.post<void>(
|
||||||
`/api/v1/admin/gather/profile-data/${dataSource}/${symbol}`,
|
`/api/v1/admin/gather/profile-data/${dataSource}/${symbol}`,
|
||||||
{}
|
{}
|
||||||
@@ -178,7 +181,7 @@ export class AdminService {
|
|||||||
dataSource,
|
dataSource,
|
||||||
date,
|
date,
|
||||||
symbol
|
symbol
|
||||||
}: UniqueAsset & {
|
}: AssetProfileIdentifier & {
|
||||||
date?: Date;
|
date?: Date;
|
||||||
}) {
|
}) {
|
||||||
let url = `/api/v1/admin/gather/${dataSource}/${symbol}`;
|
let url = `/api/v1/admin/gather/${dataSource}/${symbol}`;
|
||||||
@@ -217,7 +220,7 @@ export class AdminService {
|
|||||||
symbol,
|
symbol,
|
||||||
symbolMapping,
|
symbolMapping,
|
||||||
url
|
url
|
||||||
}: UniqueAsset & UpdateAssetProfileDto) {
|
}: AssetProfileIdentifier & UpdateAssetProfileDto) {
|
||||||
return this.http.patch<EnhancedSymbolProfile>(
|
return this.http.patch<EnhancedSymbolProfile>(
|
||||||
`/api/v1/admin/profile-data/${dataSource}/${symbol}`,
|
`/api/v1/admin/profile-data/${dataSource}/${symbol}`,
|
||||||
{
|
{
|
||||||
@@ -272,7 +275,7 @@ export class AdminService {
|
|||||||
dataSource,
|
dataSource,
|
||||||
scraperConfiguration,
|
scraperConfiguration,
|
||||||
symbol
|
symbol
|
||||||
}: UniqueAsset & UpdateAssetProfileDto['scraperConfiguration']) {
|
}: AssetProfileIdentifier & UpdateAssetProfileDto['scraperConfiguration']) {
|
||||||
return this.http.post<any>(
|
return this.http.post<any>(
|
||||||
`/api/v1/admin/market-data/${dataSource}/${symbol}/test`,
|
`/api/v1/admin/market-data/${dataSource}/${symbol}/test`,
|
||||||
{
|
{
|
||||||
|
@@ -20,6 +20,7 @@ import {
|
|||||||
AccountBalancesResponse,
|
AccountBalancesResponse,
|
||||||
Accounts,
|
Accounts,
|
||||||
AdminMarketDataDetails,
|
AdminMarketDataDetails,
|
||||||
|
AssetProfileIdentifier,
|
||||||
BenchmarkMarketDataDetails,
|
BenchmarkMarketDataDetails,
|
||||||
BenchmarkResponse,
|
BenchmarkResponse,
|
||||||
Export,
|
Export,
|
||||||
@@ -34,7 +35,6 @@ import {
|
|||||||
PortfolioPerformanceResponse,
|
PortfolioPerformanceResponse,
|
||||||
PortfolioPublicDetails,
|
PortfolioPublicDetails,
|
||||||
PortfolioReport,
|
PortfolioReport,
|
||||||
UniqueAsset,
|
|
||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { filterGlobalPermissions } from '@ghostfolio/common/permissions';
|
import { filterGlobalPermissions } from '@ghostfolio/common/permissions';
|
||||||
@@ -230,7 +230,7 @@ export class DataService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchDividendsImport({ dataSource, symbol }: UniqueAsset) {
|
public fetchDividendsImport({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
return this.http.get<ImportResponse>(
|
return this.http.get<ImportResponse>(
|
||||||
`/api/v1/import/dividends/${dataSource}/${symbol}`
|
`/api/v1/import/dividends/${dataSource}/${symbol}`
|
||||||
);
|
);
|
||||||
@@ -270,7 +270,7 @@ export class DataService {
|
|||||||
return this.http.delete<any>(`/api/v1/order/${aId}`);
|
return this.http.delete<any>(`/api/v1/order/${aId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteBenchmark({ dataSource, symbol }: UniqueAsset) {
|
public deleteBenchmark({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
return this.http.delete<any>(`/api/v1/benchmark/${dataSource}/${symbol}`);
|
return this.http.delete<any>(`/api/v1/benchmark/${dataSource}/${symbol}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ export class DataService {
|
|||||||
public fetchAsset({
|
public fetchAsset({
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol
|
symbol
|
||||||
}: UniqueAsset): Observable<AdminMarketDataDetails> {
|
}: AssetProfileIdentifier): Observable<AdminMarketDataDetails> {
|
||||||
return this.http.get<any>(`/api/v1/asset/${dataSource}/${symbol}`).pipe(
|
return this.http.get<any>(`/api/v1/asset/${dataSource}/${symbol}`).pipe(
|
||||||
map((data) => {
|
map((data) => {
|
||||||
for (const item of data.marketData) {
|
for (const item of data.marketData) {
|
||||||
@@ -308,7 +308,7 @@ export class DataService {
|
|||||||
}: {
|
}: {
|
||||||
range: DateRange;
|
range: DateRange;
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
} & UniqueAsset): Observable<BenchmarkMarketDataDetails> {
|
} & AssetProfileIdentifier): Observable<BenchmarkMarketDataDetails> {
|
||||||
let params = new HttpParams();
|
let params = new HttpParams();
|
||||||
|
|
||||||
if (range) {
|
if (range) {
|
||||||
@@ -630,7 +630,7 @@ export class DataService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public postBenchmark(benchmark: UniqueAsset) {
|
public postBenchmark(benchmark: AssetProfileIdentifier) {
|
||||||
return this.http.post(`/api/v1/benchmark`, benchmark);
|
return this.http.post(`/api/v1/benchmark`, benchmark);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -654,7 +654,7 @@ export class DataService {
|
|||||||
dataSource,
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
tags
|
tags
|
||||||
}: { tags: Tag[] } & UniqueAsset) {
|
}: { tags: Tag[] } & AssetProfileIdentifier) {
|
||||||
return this.http.put<void>(
|
return this.http.put<void>(
|
||||||
`/api/v1/portfolio/position/${dataSource}/${symbol}/tags`,
|
`/api/v1/portfolio/position/${dataSource}/${symbol}/tags`,
|
||||||
{ tags }
|
{ tags }
|
||||||
|
@@ -19,7 +19,7 @@ import {
|
|||||||
ghostfolioScraperApiSymbolPrefix,
|
ghostfolioScraperApiSymbolPrefix,
|
||||||
locale
|
locale
|
||||||
} from './config';
|
} from './config';
|
||||||
import { Benchmark, UniqueAsset } from './interfaces';
|
import { AssetProfileIdentifier, Benchmark } from './interfaces';
|
||||||
import { BenchmarkTrend, ColorScheme } from './types';
|
import { BenchmarkTrend, ColorScheme } from './types';
|
||||||
|
|
||||||
export const DATE_FORMAT = 'yyyy-MM-dd';
|
export const DATE_FORMAT = 'yyyy-MM-dd';
|
||||||
@@ -147,7 +147,10 @@ export function getAllActivityTypes(): ActivityType[] {
|
|||||||
return Object.values(ActivityType);
|
return Object.values(ActivityType);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAssetProfileIdentifier({ dataSource, symbol }: UniqueAsset) {
|
export function getAssetProfileIdentifier({
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
}: AssetProfileIdentifier) {
|
||||||
return `${dataSource}-${symbol}`;
|
return `${dataSource}-${symbol}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,7 +380,7 @@ export function parseDate(date: string): Date | null {
|
|||||||
return parseISO(date);
|
return parseISO(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseSymbol({ dataSource, symbol }: UniqueAsset) {
|
export function parseSymbol({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
const [ticker, exchange] = symbol.split('.');
|
const [ticker, exchange] = symbol.split('.');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import { Role } from '@prisma/client';
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import { UniqueAsset } from './unique-asset.interface';
|
import { Role } from '@prisma/client';
|
||||||
|
|
||||||
export interface AdminData {
|
export interface AdminData {
|
||||||
exchangeRates: ({
|
exchangeRates: ({
|
||||||
label1: string;
|
label1: string;
|
||||||
label2: string;
|
label2: string;
|
||||||
value: number;
|
value: number;
|
||||||
} & UniqueAsset)[];
|
} & AssetProfileIdentifier)[];
|
||||||
settings: { [key: string]: boolean | object | string | string[] };
|
settings: { [key: string]: boolean | object | string | string[] };
|
||||||
transactionCount: number;
|
transactionCount: number;
|
||||||
userCount: number;
|
userCount: number;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { DataSource } from '@prisma/client';
|
import { DataSource } from '@prisma/client';
|
||||||
|
|
||||||
export interface UniqueAsset {
|
export interface AssetProfileIdentifier {
|
||||||
dataSource: DataSource;
|
dataSource: DataSource;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
}
|
}
|
@@ -7,6 +7,7 @@ import type {
|
|||||||
AdminMarketData,
|
AdminMarketData,
|
||||||
AdminMarketDataItem
|
AdminMarketDataItem
|
||||||
} from './admin-market-data.interface';
|
} from './admin-market-data.interface';
|
||||||
|
import type { AssetProfileIdentifier } from './asset-profile-identifier.interface';
|
||||||
import type { BenchmarkMarketDataDetails } from './benchmark-market-data-details.interface';
|
import type { BenchmarkMarketDataDetails } from './benchmark-market-data-details.interface';
|
||||||
import type { BenchmarkProperty } from './benchmark-property.interface';
|
import type { BenchmarkProperty } from './benchmark-property.interface';
|
||||||
import type { Benchmark } from './benchmark.interface';
|
import type { Benchmark } from './benchmark.interface';
|
||||||
@@ -48,7 +49,6 @@ import type { Subscription } from './subscription.interface';
|
|||||||
import type { SymbolMetrics } from './symbol-metrics.interface';
|
import type { SymbolMetrics } from './symbol-metrics.interface';
|
||||||
import type { SystemMessage } from './system-message.interface';
|
import type { SystemMessage } from './system-message.interface';
|
||||||
import type { TabConfiguration } from './tab-configuration.interface';
|
import type { TabConfiguration } from './tab-configuration.interface';
|
||||||
import type { UniqueAsset } from './unique-asset.interface';
|
|
||||||
import type { UserSettings } from './user-settings.interface';
|
import type { UserSettings } from './user-settings.interface';
|
||||||
import type { User } from './user.interface';
|
import type { User } from './user.interface';
|
||||||
|
|
||||||
@@ -61,6 +61,7 @@ export {
|
|||||||
AdminMarketData,
|
AdminMarketData,
|
||||||
AdminMarketDataDetails,
|
AdminMarketDataDetails,
|
||||||
AdminMarketDataItem,
|
AdminMarketDataItem,
|
||||||
|
AssetProfileIdentifier,
|
||||||
Benchmark,
|
Benchmark,
|
||||||
BenchmarkMarketDataDetails,
|
BenchmarkMarketDataDetails,
|
||||||
BenchmarkProperty,
|
BenchmarkProperty,
|
||||||
@@ -101,7 +102,6 @@ export {
|
|||||||
Subscription,
|
Subscription,
|
||||||
SymbolMetrics,
|
SymbolMetrics,
|
||||||
TabConfiguration,
|
TabConfiguration,
|
||||||
UniqueAsset,
|
|
||||||
User,
|
User,
|
||||||
UserSettings
|
UserSettings
|
||||||
};
|
};
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { UniqueAsset } from '../unique-asset.interface';
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
export interface ResponseError {
|
export interface ResponseError {
|
||||||
errors?: UniqueAsset[];
|
errors?: AssetProfileIdentifier[];
|
||||||
hasErrors: boolean;
|
hasErrors: boolean;
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { transformToBig } from '@ghostfolio/common/class-transformer';
|
import { transformToBig } from '@ghostfolio/common/class-transformer';
|
||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||||
import { TimelinePosition } from '@ghostfolio/common/models';
|
import { TimelinePosition } from '@ghostfolio/common/models';
|
||||||
|
|
||||||
import { Big } from 'big.js';
|
import { Big } from 'big.js';
|
||||||
@@ -9,7 +9,7 @@ export class PortfolioSnapshot {
|
|||||||
@Transform(transformToBig, { toClassOnly: true })
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
@Type(() => Big)
|
@Type(() => Big)
|
||||||
currentValueInBaseCurrency: Big;
|
currentValueInBaseCurrency: Big;
|
||||||
errors?: UniqueAsset[];
|
errors?: AssetProfileIdentifier[];
|
||||||
|
|
||||||
@Transform(transformToBig, { toClassOnly: true })
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
@Type(() => Big)
|
@Type(() => Big)
|
||||||
|
@@ -3,7 +3,7 @@ import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset
|
|||||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||||
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
|
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
|
||||||
import { getDateFormatString, getLocale } from '@ghostfolio/common/helper';
|
import { getDateFormatString, getLocale } from '@ghostfolio/common/helper';
|
||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||||
import { OrderWithAccount } from '@ghostfolio/common/types';
|
import { OrderWithAccount } from '@ghostfolio/common/types';
|
||||||
import { GfActivityTypeComponent } from '@ghostfolio/ui/activity-type';
|
import { GfActivityTypeComponent } from '@ghostfolio/ui/activity-type';
|
||||||
import { GfNoTransactionsInfoComponent } from '@ghostfolio/ui/no-transactions-info';
|
import { GfNoTransactionsInfoComponent } from '@ghostfolio/ui/no-transactions-info';
|
||||||
@@ -99,7 +99,7 @@ export class GfActivitiesTableComponent
|
|||||||
@Output() export = new EventEmitter<void>();
|
@Output() export = new EventEmitter<void>();
|
||||||
@Output() exportDrafts = new EventEmitter<string[]>();
|
@Output() exportDrafts = new EventEmitter<string[]>();
|
||||||
@Output() import = new EventEmitter<void>();
|
@Output() import = new EventEmitter<void>();
|
||||||
@Output() importDividends = new EventEmitter<UniqueAsset>();
|
@Output() importDividends = new EventEmitter<AssetProfileIdentifier>();
|
||||||
@Output() pageChanged = new EventEmitter<PageEvent>();
|
@Output() pageChanged = new EventEmitter<PageEvent>();
|
||||||
@Output() selectedActivities = new EventEmitter<Activity[]>();
|
@Output() selectedActivities = new EventEmitter<Activity[]>();
|
||||||
@Output() sortChanged = new EventEmitter<Sort>();
|
@Output() sortChanged = new EventEmitter<Sort>();
|
||||||
@@ -263,7 +263,7 @@ export class GfActivitiesTableComponent
|
|||||||
alert(aComment);
|
alert(aComment);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onOpenPositionDialog({ dataSource, symbol }: UniqueAsset) {
|
public onOpenPositionDialog({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: { dataSource, symbol, holdingDetailDialog: true }
|
queryParams: { dataSource, symbol, holdingDetailDialog: true }
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
|
||||||
import { DateRange } from '@ghostfolio/common/types';
|
import { DateRange } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
export interface IDateRangeOption {
|
export interface IDateRangeOption {
|
||||||
@@ -6,7 +6,7 @@ export interface IDateRangeOption {
|
|||||||
value: DateRange;
|
value: DateRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISearchResultItem extends UniqueAsset {
|
export interface ISearchResultItem extends AssetProfileIdentifier {
|
||||||
assetSubClassString: string;
|
assetSubClassString: string;
|
||||||
currency: string;
|
currency: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
import { getLocale, resolveMarketCondition } from '@ghostfolio/common/helper';
|
import { getLocale, resolveMarketCondition } from '@ghostfolio/common/helper';
|
||||||
import { Benchmark, UniqueAsset, User } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
|
Benchmark,
|
||||||
|
User
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { translate } from '@ghostfolio/ui/i18n';
|
import { translate } from '@ghostfolio/ui/i18n';
|
||||||
import { GfTrendIndicatorComponent } from '@ghostfolio/ui/trend-indicator';
|
import { GfTrendIndicatorComponent } from '@ghostfolio/ui/trend-indicator';
|
||||||
import { GfValueComponent } from '@ghostfolio/ui/value';
|
import { GfValueComponent } from '@ghostfolio/ui/value';
|
||||||
@@ -84,7 +88,7 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onOpenBenchmarkDialog({ dataSource, symbol }: UniqueAsset) {
|
public onOpenBenchmarkDialog({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: { dataSource, symbol, benchmarkDetailDialog: true }
|
queryParams: { dataSource, symbol, benchmarkDetailDialog: true }
|
||||||
});
|
});
|
||||||
@@ -95,7 +99,10 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy {
|
|||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private openBenchmarkDetailDialog({ dataSource, symbol }: UniqueAsset) {
|
private openBenchmarkDetailDialog({
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
}: AssetProfileIdentifier) {
|
||||||
const dialogRef = this.dialog.open(GfBenchmarkDetailDialogComponent, {
|
const dialogRef = this.dialog.open(GfBenchmarkDetailDialogComponent, {
|
||||||
data: <BenchmarkDetailDialogParams>{
|
data: <BenchmarkDetailDialogParams>{
|
||||||
dataSource,
|
dataSource,
|
||||||
|
@@ -2,7 +2,10 @@ import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset
|
|||||||
import { GfHoldingDetailDialogComponent } from '@ghostfolio/client/components/holding-detail-dialog/holding-detail-dialog.component';
|
import { GfHoldingDetailDialogComponent } from '@ghostfolio/client/components/holding-detail-dialog/holding-detail-dialog.component';
|
||||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||||
import { getLocale } from '@ghostfolio/common/helper';
|
import { getLocale } from '@ghostfolio/common/helper';
|
||||||
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
|
PortfolioPosition
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { GfNoTransactionsInfoComponent } from '@ghostfolio/ui/no-transactions-info';
|
import { GfNoTransactionsInfoComponent } from '@ghostfolio/ui/no-transactions-info';
|
||||||
import { GfValueComponent } from '@ghostfolio/ui/value';
|
import { GfValueComponent } from '@ghostfolio/ui/value';
|
||||||
|
|
||||||
@@ -102,7 +105,7 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onOpenHoldingDialog({ dataSource, symbol }: UniqueAsset) {
|
public onOpenHoldingDialog({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
if (this.hasPermissionToOpenDetails) {
|
if (this.hasPermissionToOpenDetails) {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: { dataSource, symbol, holdingDetailDialog: true }
|
queryParams: { dataSource, symbol, holdingDetailDialog: true }
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import { getTooltipOptions } from '@ghostfolio/common/chart-helper';
|
import { getTooltipOptions } from '@ghostfolio/common/chart-helper';
|
||||||
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
||||||
import { getLocale, getTextColor } from '@ghostfolio/common/helper';
|
import { getLocale, getTextColor } from '@ghostfolio/common/helper';
|
||||||
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
|
PortfolioPosition
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { ColorScheme } from '@ghostfolio/common/types';
|
import { ColorScheme } from '@ghostfolio/common/types';
|
||||||
import { translate } from '@ghostfolio/ui/i18n';
|
import { translate } from '@ghostfolio/ui/i18n';
|
||||||
|
|
||||||
@@ -71,7 +74,7 @@ export class GfPortfolioProportionChartComponent
|
|||||||
};
|
};
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
@Output() proportionChartClicked = new EventEmitter<UniqueAsset>();
|
@Output() proportionChartClicked = new EventEmitter<AssetProfileIdentifier>();
|
||||||
|
|
||||||
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;
|
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;
|
||||||
|
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
import { getAnnualizedPerformancePercent } from '@ghostfolio/common/calculation-helper';
|
import { getAnnualizedPerformancePercent } from '@ghostfolio/common/calculation-helper';
|
||||||
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
AssetProfileIdentifier,
|
||||||
|
PortfolioPosition
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
@@ -40,7 +43,7 @@ export class GfTreemapChartComponent
|
|||||||
@Input() cursor: string;
|
@Input() cursor: string;
|
||||||
@Input() holdings: PortfolioPosition[];
|
@Input() holdings: PortfolioPosition[];
|
||||||
|
|
||||||
@Output() treemapChartClicked = new EventEmitter<UniqueAsset>();
|
@Output() treemapChartClicked = new EventEmitter<AssetProfileIdentifier>();
|
||||||
|
|
||||||
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;
|
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user