Browse Source

Feature/support unlimited currencies (#387)

* Support unlimited currencies

* Update changelog
pull/389/head
Thomas Kaul 3 years ago
committed by GitHub
parent
commit
dcee651098
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      CHANGELOG.md
  2. 4
      apps/api/src/app/account/account.service.ts
  3. 4
      apps/api/src/app/account/create-account.dto.ts
  4. 4
      apps/api/src/app/account/update-account.dto.ts
  5. 65
      apps/api/src/app/admin/admin.service.ts
  6. 3
      apps/api/src/app/cache/cache.module.ts
  7. 6
      apps/api/src/app/experimental/create-order.dto.ts
  8. 4
      apps/api/src/app/experimental/interfaces/data.interface.ts
  9. 3
      apps/api/src/app/export/export.module.ts
  10. 2
      apps/api/src/app/info/info.module.ts
  11. 5
      apps/api/src/app/info/info.service.ts
  12. 4
      apps/api/src/app/order/create-order.dto.ts
  13. 6
      apps/api/src/app/order/update-order.dto.ts
  14. 12
      apps/api/src/app/portfolio/current-rate.service.spec.ts
  15. 6
      apps/api/src/app/portfolio/interfaces/get-value-params.interface.ts
  16. 5
      apps/api/src/app/portfolio/interfaces/get-values-params.interface.ts
  17. 4
      apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts
  18. 4
      apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts
  19. 4
      apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts
  20. 227
      apps/api/src/app/portfolio/portfolio-calculator.spec.ts
  21. 7
      apps/api/src/app/portfolio/portfolio-calculator.ts
  22. 16
      apps/api/src/app/portfolio/portfolio.service.ts
  23. 3
      apps/api/src/app/portfolio/rules.service.ts
  24. 4
      apps/api/src/app/symbol/interfaces/lookup-item.interface.ts
  25. 4
      apps/api/src/app/symbol/interfaces/symbol-item.interface.ts
  26. 4
      apps/api/src/app/symbol/symbol.service.ts
  27. 4
      apps/api/src/app/user/interfaces/user-settings-params.interface.ts
  28. 4
      apps/api/src/app/user/update-user-settings.dto.ts
  29. 12
      apps/api/src/app/user/user.service.ts
  30. 4
      apps/api/src/models/interfaces/user-settings.interface.ts
  31. 4
      apps/api/src/models/order.ts
  32. 3
      apps/api/src/models/rule.ts
  33. 4
      apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts
  34. 3
      apps/api/src/models/rules/currency-cluster-risk/base-currency-initial-investment.ts
  35. 3
      apps/api/src/models/rules/currency-cluster-risk/current-investment.ts
  36. 3
      apps/api/src/models/rules/currency-cluster-risk/initial-investment.ts
  37. 3
      apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts
  38. 9
      apps/api/src/services/data-gathering.module.ts
  39. 21
      apps/api/src/services/data-gathering.service.ts
  40. 4
      apps/api/src/services/data-provider/ghostfolio-scraper-api/interfaces/scraper-config.interface.ts
  41. 20
      apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts
  42. 4
      apps/api/src/services/exchange-rate-data.module.ts
  43. 95
      apps/api/src/services/exchange-rate-data.service.ts
  44. 5
      apps/api/src/services/interfaces/interfaces.ts
  45. 9
      apps/api/src/services/interfaces/symbol-profile.interface.ts
  46. 3
      apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts
  47. 3
      apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts
  48. 3
      apps/client/src/app/components/world-map-chart/world-map-chart.component.ts
  49. 3
      apps/client/src/app/pages/account/account-page.component.ts
  50. 5
      apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts
  51. 3
      apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts
  52. 21
      libs/common/src/lib/config.ts
  53. 11
      libs/common/src/lib/helper.ts
  54. 4
      libs/common/src/lib/interfaces/info-item.interface.ts
  55. 4
      libs/common/src/lib/interfaces/portfolio-position.interface.ts
  56. 4
      libs/common/src/lib/interfaces/position.interface.ts
  57. 4
      libs/common/src/lib/interfaces/timeline-position.interface.ts
  58. 4
      libs/common/src/lib/interfaces/user-settings.interface.ts
  59. 3
      libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
  60. 14
      prisma/migrations/20210921151004_changed_currency_from_enum_to_string/migration.sql
  61. 15
      prisma/schema.prisma
  62. 25
      prisma/seed.ts

6
CHANGELOG.md

@ -15,12 +15,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Changed the navigation to always show the portfolio page - Changed the navigation to always show the portfolio page
- Migrated the data type of currencies from `enum` to `string` in the database
- Supported unlimited currencies (instead of `CHF`, `EUR`, `GBP` and `USD`)
### Fixed ### Fixed
- Hid the actions from the accounts table in the _Presenter View_ - Hid the actions from the accounts table in the _Presenter View_
- Hid the actions from the transactions table in the _Presenter View_ - Hid the actions from the transactions table in the _Presenter View_
### Todo
- Apply data migration (`yarn prisma migrate deploy`)
## 1.55.0 - 20.09.2021 ## 1.55.0 - 20.09.2021
### Changed ### Changed

4
apps/api/src/app/account/account.service.ts

@ -1,7 +1,7 @@
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Account, Currency, Order, Platform, Prisma } from '@prisma/client'; import { Account, Order, Platform, Prisma } from '@prisma/client';
import { CashDetails } from './interfaces/cash-details.interface'; import { CashDetails } from './interfaces/cash-details.interface';
@ -95,7 +95,7 @@ export class AccountService {
public async getCashDetails( public async getCashDetails(
aUserId: string, aUserId: string,
aCurrency: Currency aCurrency: string
): Promise<CashDetails> { ): Promise<CashDetails> {
let totalCashBalance = 0; let totalCashBalance = 0;

4
apps/api/src/app/account/create-account.dto.ts

@ -1,4 +1,4 @@
import { AccountType, Currency } from '@prisma/client'; import { AccountType } from '@prisma/client';
import { IsNumber, IsString, ValidateIf } from 'class-validator'; import { IsNumber, IsString, ValidateIf } from 'class-validator';
export class CreateAccountDto { export class CreateAccountDto {
@ -9,7 +9,7 @@ export class CreateAccountDto {
balance: number; balance: number;
@IsString() @IsString()
currency: Currency; currency: string;
@IsString() @IsString()
name: string; name: string;

4
apps/api/src/app/account/update-account.dto.ts

@ -1,4 +1,4 @@
import { AccountType, Currency } from '@prisma/client'; import { AccountType } from '@prisma/client';
import { IsNumber, IsString, ValidateIf } from 'class-validator'; import { IsNumber, IsString, ValidateIf } from 'class-validator';
export class UpdateAccountDto { export class UpdateAccountDto {
@ -9,7 +9,7 @@ export class UpdateAccountDto {
balance: number; balance: number;
@IsString() @IsString()
currency: Currency; currency: string;
@IsString() @IsString()
id: string; id: string;

65
apps/api/src/app/admin/admin.service.ts

@ -3,9 +3,9 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration.ser
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service'; import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { baseCurrency } from '@ghostfolio/common/config';
import { AdminData } from '@ghostfolio/common/interfaces'; import { AdminData } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Currency } from '@prisma/client';
import { differenceInDays } from 'date-fns'; import { differenceInDays } from 'date-fns';
@Injectable() @Injectable()
@ -20,53 +20,22 @@ export class AdminService {
public async get(): Promise<AdminData> { public async get(): Promise<AdminData> {
return { return {
exchangeRates: [ exchangeRates: this.exchangeRateDataService
{ .getCurrencies()
label1: Currency.EUR, .filter((currency) => {
label2: Currency.CHF, return currency !== baseCurrency;
value: await this.exchangeRateDataService.toCurrency( })
1, .map((currency) => {
Currency.EUR, return {
Currency.CHF label1: baseCurrency,
) label2: currency,
}, value: this.exchangeRateDataService.toCurrency(
{ 1,
label1: Currency.GBP, baseCurrency,
label2: Currency.CHF, currency
value: await this.exchangeRateDataService.toCurrency( )
1, };
Currency.GBP, }),
Currency.CHF
)
},
{
label1: Currency.USD,
label2: Currency.CHF,
value: await this.exchangeRateDataService.toCurrency(
1,
Currency.USD,
Currency.CHF
)
},
{
label1: Currency.USD,
label2: Currency.EUR,
value: await this.exchangeRateDataService.toCurrency(
1,
Currency.USD,
Currency.EUR
)
},
{
label1: Currency.USD,
label2: Currency.GBP,
value: await this.exchangeRateDataService.toCurrency(
1,
Currency.USD,
Currency.GBP
)
}
],
lastDataGathering: await this.getLastDataGathering(), lastDataGathering: await this.getLastDataGathering(),
transactionCount: await this.prismaService.order.count(), transactionCount: await this.prismaService.order.count(),
userCount: await this.prismaService.user.count(), userCount: await this.prismaService.user.count(),

3
apps/api/src/app/cache/cache.module.ts

@ -7,13 +7,14 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service'; import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service'; import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service'; import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module';
import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { CacheController } from './cache.controller'; import { CacheController } from './cache.controller';
@Module({ @Module({
imports: [RedisCacheModule], imports: [ExchangeRateDataModule, RedisCacheModule],
controllers: [CacheController], controllers: [CacheController],
providers: [ providers: [
AlphaVantageService, AlphaVantageService,

6
apps/api/src/app/experimental/create-order.dto.ts

@ -1,9 +1,9 @@
import { Currency, Type } from '@prisma/client'; import { Type } from '@prisma/client';
import { IsISO8601, IsNumber, IsString, ValidateIf } from 'class-validator'; import { IsISO8601, IsNumber, IsString } from 'class-validator';
export class CreateOrderDto { export class CreateOrderDto {
@IsString() @IsString()
currency: Currency; currency: string;
@IsISO8601() @IsISO8601()
date: string; date: string;

4
apps/api/src/app/experimental/interfaces/data.interface.ts

@ -1,6 +1,4 @@
import { Currency } from '@prisma/client';
export interface Data { export interface Data {
currency: Currency; currency: string;
value: number; value: number;
} }

3
apps/api/src/app/export/export.module.ts

@ -1,4 +1,3 @@
import { CacheService } from '@ghostfolio/api/app/cache/cache.service';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module'; import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
@ -18,6 +17,6 @@ import { ExportService } from './export.service';
RedisCacheModule RedisCacheModule
], ],
controllers: [ExportController], controllers: [ExportController],
providers: [CacheService, ExportService] providers: [ExportService]
}) })
export class ExportModule {} export class ExportModule {}

2
apps/api/src/app/info/info.module.ts

@ -5,6 +5,7 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service'; import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service'; import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service'; import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module';
import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
@ -14,6 +15,7 @@ import { InfoService } from './info.service';
@Module({ @Module({
imports: [ imports: [
ExchangeRateDataModule,
JwtModule.register({ JwtModule.register({
secret: process.env.JWT_SECRET_KEY, secret: process.env.JWT_SECRET_KEY,
signOptions: { expiresIn: '30 days' } signOptions: { expiresIn: '30 days' }

5
apps/api/src/app/info/info.service.ts

@ -1,12 +1,12 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service'; import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { InfoItem } from '@ghostfolio/common/interfaces'; import { InfoItem } from '@ghostfolio/common/interfaces';
import { Subscription } from '@ghostfolio/common/interfaces/subscription.interface'; import { Subscription } from '@ghostfolio/common/interfaces/subscription.interface';
import { permissions } from '@ghostfolio/common/permissions'; import { permissions } from '@ghostfolio/common/permissions';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { Currency } from '@prisma/client';
import * as bent from 'bent'; import * as bent from 'bent';
import { subDays } from 'date-fns'; import { subDays } from 'date-fns';
@ -16,6 +16,7 @@ export class InfoService {
public constructor( public constructor(
private readonly configurationService: ConfigurationService, private readonly configurationService: ConfigurationService,
private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly dataGatheringService: DataGatheringService, private readonly dataGatheringService: DataGatheringService,
private readonly jwtService: JwtService, private readonly jwtService: JwtService,
private readonly prismaService: PrismaService private readonly prismaService: PrismaService
@ -56,7 +57,7 @@ export class InfoService {
...info, ...info,
globalPermissions, globalPermissions,
platforms, platforms,
currencies: Object.values(Currency), currencies: this.exchangeRateDataService.getCurrencies(),
demoAuthToken: this.getDemoAuthToken(), demoAuthToken: this.getDemoAuthToken(),
lastDataGathering: await this.getLastDataGathering(), lastDataGathering: await this.getLastDataGathering(),
statistics: await this.getStatistics(), statistics: await this.getStatistics(),

4
apps/api/src/app/order/create-order.dto.ts

@ -1,4 +1,4 @@
import { Currency, DataSource, Type } from '@prisma/client'; import { DataSource, Type } from '@prisma/client';
import { IsISO8601, IsNumber, IsString } from 'class-validator'; import { IsISO8601, IsNumber, IsString } from 'class-validator';
export class CreateOrderDto { export class CreateOrderDto {
@ -6,7 +6,7 @@ export class CreateOrderDto {
accountId: string; accountId: string;
@IsString() @IsString()
currency: Currency; currency: string;
@IsString() @IsString()
dataSource: DataSource; dataSource: DataSource;

6
apps/api/src/app/order/update-order.dto.ts

@ -1,12 +1,12 @@
import { Currency, DataSource, Type } from '@prisma/client'; import { DataSource, Type } from '@prisma/client';
import { IsISO8601, IsNumber, IsString, ValidateIf } from 'class-validator'; import { IsISO8601, IsNumber, IsString } from 'class-validator';
export class UpdateOrderDto { export class UpdateOrderDto {
@IsString() @IsString()
accountId: string; accountId: string;
@IsString() @IsString()
currency: Currency; currency: string;
@IsString() @IsString()
dataSource: DataSource; dataSource: DataSource;

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

@ -1,6 +1,6 @@
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.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { Currency, DataSource, MarketData } from '@prisma/client'; import { DataSource, MarketData } from '@prisma/client';
import { CurrentRateService } from './current-rate.service'; import { CurrentRateService } from './current-rate.service';
import { MarketDataService } from './market-data.service'; import { MarketDataService } from './market-data.service';
@ -80,7 +80,7 @@ describe('CurrentRateService', () => {
null, null,
null null
); );
exchangeRateDataService = new ExchangeRateDataService(null); exchangeRateDataService = new ExchangeRateDataService(null, null);
marketDataService = new MarketDataService(null); marketDataService = new MarketDataService(null);
await exchangeRateDataService.initialize(); await exchangeRateDataService.initialize();
@ -95,10 +95,10 @@ describe('CurrentRateService', () => {
it('getValue', async () => { it('getValue', async () => {
expect( expect(
await currentRateService.getValue({ await currentRateService.getValue({
currency: Currency.USD, currency: 'USD',
date: new Date(Date.UTC(2020, 0, 1, 0, 0, 0)), date: new Date(Date.UTC(2020, 0, 1, 0, 0, 0)),
symbol: 'AMZN', symbol: 'AMZN',
userCurrency: Currency.CHF userCurrency: 'CHF'
}) })
).toMatchObject({ ).toMatchObject({
marketPrice: 1847.839966 marketPrice: 1847.839966
@ -108,13 +108,13 @@ describe('CurrentRateService', () => {
it('getValues', async () => { it('getValues', async () => {
expect( expect(
await currentRateService.getValues({ await currentRateService.getValues({
currencies: { AMZN: Currency.USD }, currencies: { AMZN: 'USD' },
dataGatheringItems: [{ dataSource: DataSource.YAHOO, symbol: 'AMZN' }], dataGatheringItems: [{ dataSource: DataSource.YAHOO, symbol: 'AMZN' }],
dateQuery: { dateQuery: {
lt: new Date(Date.UTC(2020, 0, 2, 0, 0, 0)), lt: new Date(Date.UTC(2020, 0, 2, 0, 0, 0)),
gte: new Date(Date.UTC(2020, 0, 1, 0, 0, 0)) gte: new Date(Date.UTC(2020, 0, 1, 0, 0, 0))
}, },
userCurrency: Currency.CHF userCurrency: 'CHF'
}) })
).toMatchObject([ ).toMatchObject([
{ {

6
apps/api/src/app/portfolio/interfaces/get-value-params.interface.ts

@ -1,8 +1,6 @@
import { Currency } from '@prisma/client';
export interface GetValueParams { export interface GetValueParams {
currency: Currency; currency: string;
date: Date; date: Date;
symbol: string; symbol: string;
userCurrency: Currency; userCurrency: string;
} }

5
apps/api/src/app/portfolio/interfaces/get-values-params.interface.ts

@ -1,11 +1,10 @@
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { Currency } from '@prisma/client';
import { DateQuery } from './date-query.interface'; import { DateQuery } from './date-query.interface';
export interface GetValuesParams { export interface GetValuesParams {
currencies: { [symbol: string]: Currency }; currencies: { [symbol: string]: string };
dataGatheringItems: IDataGatheringItem[]; dataGatheringItems: IDataGatheringItem[];
dateQuery: DateQuery; dateQuery: DateQuery;
userCurrency: Currency; userCurrency: string;
} }

4
apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts

@ -1,9 +1,9 @@
import { OrderType } from '@ghostfolio/api/models/order-type'; import { OrderType } from '@ghostfolio/api/models/order-type';
import { Currency, DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
import Big from 'big.js'; import Big from 'big.js';
export interface PortfolioOrder { export interface PortfolioOrder {
currency: Currency; currency: string;
date: string; date: string;
dataSource: DataSource; dataSource: DataSource;
fee: Big; fee: Big;

4
apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts

@ -1,8 +1,6 @@
import { Currency } from '@prisma/client';
export interface PortfolioPositionDetail { export interface PortfolioPositionDetail {
averagePrice: number; averagePrice: number;
currency: Currency; currency: string;
firstBuyDate: string; firstBuyDate: string;
grossPerformance: number; grossPerformance: number;
grossPerformancePercent: number; grossPerformancePercent: number;

4
apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts

@ -1,8 +1,8 @@
import { Currency, DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
import Big from 'big.js'; import Big from 'big.js';
export interface TransactionPointSymbol { export interface TransactionPointSymbol {
currency: Currency; currency: string;
dataSource: DataSource; dataSource: DataSource;
fee: Big; fee: Big;
firstBuyDate: string; firstBuyDate: string;

227
apps/api/src/app/portfolio/portfolio-calculator.spec.ts

@ -1,7 +1,6 @@
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { OrderType } from '@ghostfolio/api/models/order-type'; import { OrderType } from '@ghostfolio/api/models/order-type';
import { parseDate, resetHours } from '@ghostfolio/common/helper'; import { parseDate, resetHours } from '@ghostfolio/common/helper';
import { Currency, DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
import Big from 'big.js'; import Big from 'big.js';
import { import {
addDays, addDays,
@ -134,7 +133,7 @@ describe('PortfolioCalculator', () => {
it('with orders of only one symbol', () => { it('with orders of only one symbol', () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.computeTransactionPoints(ordersVTI); portfolioCalculator.computeTransactionPoints(ordersVTI);
const portfolioItemsAtTransactionPoints = const portfolioItemsAtTransactionPoints =
@ -148,7 +147,7 @@ describe('PortfolioCalculator', () => {
it('with orders of only one symbol and a fee', () => { it('with orders of only one symbol and a fee', () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
const orders: PortfolioOrder[] = [ const orders: PortfolioOrder[] = [
{ {
@ -158,7 +157,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI', symbol: 'VTI',
type: OrderType.Buy, type: OrderType.Buy,
unitPrice: new Big('144.38'), unitPrice: new Big('144.38'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big('5') fee: new Big('5')
}, },
@ -169,7 +168,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI', symbol: 'VTI',
type: OrderType.Buy, type: OrderType.Buy,
unitPrice: new Big('147.99'), unitPrice: new Big('147.99'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big('10') fee: new Big('10')
}, },
@ -180,7 +179,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI', symbol: 'VTI',
type: OrderType.Sell, type: OrderType.Sell,
unitPrice: new Big('151.41'), unitPrice: new Big('151.41'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big('5') fee: new Big('5')
} }
@ -198,7 +197,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('10'), quantity: new Big('10'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('1443.8'), investment: new Big('1443.8'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
transactionCount: 1, transactionCount: 1,
fee: new Big('5') fee: new Big('5')
@ -213,7 +212,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('20'), quantity: new Big('20'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2923.7'), investment: new Big('2923.7'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
transactionCount: 2, transactionCount: 2,
fee: new Big('15') fee: new Big('15')
@ -228,7 +227,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('652.55'), investment: new Big('652.55'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
transactionCount: 3, transactionCount: 3,
fee: new Big('20') fee: new Big('20')
@ -241,7 +240,7 @@ describe('PortfolioCalculator', () => {
it('with orders of two different symbols and a fee', () => { it('with orders of two different symbols and a fee', () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
const orders: PortfolioOrder[] = [ const orders: PortfolioOrder[] = [
{ {
@ -251,7 +250,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI', symbol: 'VTI',
type: OrderType.Buy, type: OrderType.Buy,
unitPrice: new Big('144.38'), unitPrice: new Big('144.38'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big('5') fee: new Big('5')
}, },
@ -262,7 +261,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTX', symbol: 'VTX',
type: OrderType.Buy, type: OrderType.Buy,
unitPrice: new Big('147.99'), unitPrice: new Big('147.99'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big('10') fee: new Big('10')
}, },
@ -273,7 +272,7 @@ describe('PortfolioCalculator', () => {
symbol: 'VTI', symbol: 'VTI',
type: OrderType.Sell, type: OrderType.Sell,
unitPrice: new Big('151.41'), unitPrice: new Big('151.41'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big('5') fee: new Big('5')
} }
@ -291,7 +290,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('10'), quantity: new Big('10'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('1443.8'), investment: new Big('1443.8'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
transactionCount: 1, transactionCount: 1,
fee: new Big('5') fee: new Big('5')
@ -306,7 +305,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('10'), quantity: new Big('10'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('1443.8'), investment: new Big('1443.8'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
transactionCount: 1, transactionCount: 1,
fee: new Big('5') fee: new Big('5')
@ -316,7 +315,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('10'), quantity: new Big('10'),
symbol: 'VTX', symbol: 'VTX',
investment: new Big('1479.9'), investment: new Big('1479.9'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-08-03', firstBuyDate: '2019-08-03',
transactionCount: 1, transactionCount: 1,
fee: new Big('10') fee: new Big('10')
@ -331,7 +330,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('686.75'), investment: new Big('686.75'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
transactionCount: 2, transactionCount: 2,
fee: new Big('10') fee: new Big('10')
@ -341,7 +340,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('10'), quantity: new Big('10'),
symbol: 'VTX', symbol: 'VTX',
investment: new Big('1479.9'), investment: new Big('1479.9'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-08-03', firstBuyDate: '2019-08-03',
transactionCount: 1, transactionCount: 1,
fee: new Big('10') fee: new Big('10')
@ -355,7 +354,7 @@ describe('PortfolioCalculator', () => {
const orders: PortfolioOrder[] = [ const orders: PortfolioOrder[] = [
...ordersVTI, ...ordersVTI,
{ {
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
date: '2021-02-01', date: '2021-02-01',
name: 'Vanguard Total Stock Market Index Fund ETF Shares', name: 'Vanguard Total Stock Market Index Fund ETF Shares',
@ -368,7 +367,7 @@ describe('PortfolioCalculator', () => {
]; ];
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.computeTransactionPoints(orders); portfolioCalculator.computeTransactionPoints(orders);
const portfolioItemsAtTransactionPoints = const portfolioItemsAtTransactionPoints =
@ -379,7 +378,7 @@ describe('PortfolioCalculator', () => {
date: '2019-02-01', date: '2019-02-01',
items: [ items: [
{ {
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
investment: new Big('1443.8'), investment: new Big('1443.8'),
@ -394,7 +393,7 @@ describe('PortfolioCalculator', () => {
date: '2019-08-03', date: '2019-08-03',
items: [ items: [
{ {
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
investment: new Big('2923.7'), investment: new Big('2923.7'),
@ -409,7 +408,7 @@ describe('PortfolioCalculator', () => {
date: '2020-02-02', date: '2020-02-02',
items: [ items: [
{ {
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
investment: new Big('652.55'), investment: new Big('652.55'),
@ -424,7 +423,7 @@ describe('PortfolioCalculator', () => {
date: '2021-02-01', date: '2021-02-01',
items: [ items: [
{ {
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
investment: new Big('6627.05'), investment: new Big('6627.05'),
@ -439,7 +438,7 @@ describe('PortfolioCalculator', () => {
date: '2021-08-01', date: '2021-08-01',
items: [ items: [
{ {
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
investment: new Big('8403.95'), investment: new Big('8403.95'),
@ -457,7 +456,7 @@ describe('PortfolioCalculator', () => {
const orders: PortfolioOrder[] = [ const orders: PortfolioOrder[] = [
...ordersVTI, ...ordersVTI,
{ {
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
date: '2019-09-01', date: '2019-09-01',
name: 'Amazon.com, Inc.', name: 'Amazon.com, Inc.',
@ -470,7 +469,7 @@ describe('PortfolioCalculator', () => {
]; ];
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.computeTransactionPoints(orders); portfolioCalculator.computeTransactionPoints(orders);
const portfolioItemsAtTransactionPoints = const portfolioItemsAtTransactionPoints =
@ -485,7 +484,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('10'), quantity: new Big('10'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('1443.8'), investment: new Big('1443.8'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
transactionCount: 1 transactionCount: 1
@ -500,7 +499,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('20'), quantity: new Big('20'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2923.7'), investment: new Big('2923.7'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
transactionCount: 2 transactionCount: 2
@ -515,7 +514,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'AMZN', symbol: 'AMZN',
investment: new Big('10109.95'), investment: new Big('10109.95'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-09-01', firstBuyDate: '2019-09-01',
fee: new Big(0), fee: new Big(0),
transactionCount: 1 transactionCount: 1
@ -525,7 +524,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('20'), quantity: new Big('20'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2923.7'), investment: new Big('2923.7'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
transactionCount: 2 transactionCount: 2
@ -540,7 +539,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'AMZN', symbol: 'AMZN',
investment: new Big('10109.95'), investment: new Big('10109.95'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-09-01', firstBuyDate: '2019-09-01',
fee: new Big(0), fee: new Big(0),
transactionCount: 1 transactionCount: 1
@ -550,7 +549,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('652.55'), investment: new Big('652.55'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
transactionCount: 3 transactionCount: 3
@ -565,7 +564,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'AMZN', symbol: 'AMZN',
investment: new Big('10109.95'), investment: new Big('10109.95'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-09-01', firstBuyDate: '2019-09-01',
fee: new Big(0), fee: new Big(0),
transactionCount: 1 transactionCount: 1
@ -575,7 +574,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('15'), quantity: new Big('15'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2684.05'), investment: new Big('2684.05'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
transactionCount: 4 transactionCount: 4
@ -590,7 +589,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'AMZN', symbol: 'AMZN',
investment: new Big('10109.95'), investment: new Big('10109.95'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-09-01', firstBuyDate: '2019-09-01',
fee: new Big(0), fee: new Big(0),
transactionCount: 1 transactionCount: 1
@ -600,7 +599,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('25'), quantity: new Big('25'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('4460.95'), investment: new Big('4460.95'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
transactionCount: 5 transactionCount: 5
@ -620,7 +619,7 @@ describe('PortfolioCalculator', () => {
symbol: 'AMZN', symbol: 'AMZN',
type: OrderType.Buy, type: OrderType.Buy,
unitPrice: new Big('2021.99'), unitPrice: new Big('2021.99'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big(0) fee: new Big(0)
}, },
@ -631,14 +630,14 @@ describe('PortfolioCalculator', () => {
symbol: 'AMZN', symbol: 'AMZN',
type: OrderType.Sell, type: OrderType.Sell,
unitPrice: new Big('2412.23'), unitPrice: new Big('2412.23'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big(0) fee: new Big(0)
} }
]; ];
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.computeTransactionPoints(orders); portfolioCalculator.computeTransactionPoints(orders);
const portfolioItemsAtTransactionPoints = const portfolioItemsAtTransactionPoints =
@ -652,7 +651,7 @@ describe('PortfolioCalculator', () => {
it('with mixed symbols', () => { it('with mixed symbols', () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.computeTransactionPoints(ordersMixedSymbols); portfolioCalculator.computeTransactionPoints(ordersMixedSymbols);
const portfolioItemsAtTransactionPoints = const portfolioItemsAtTransactionPoints =
@ -667,7 +666,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('50'), quantity: new Big('50'),
symbol: 'TSLA', symbol: 'TSLA',
investment: new Big('2148.5'), investment: new Big('2148.5'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2017-01-03', firstBuyDate: '2017-01-03',
fee: new Big(0), fee: new Big(0),
transactionCount: 1 transactionCount: 1
@ -682,7 +681,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('0.5614682'), quantity: new Big('0.5614682'),
symbol: 'BTCUSD', symbol: 'BTCUSD',
investment: new Big('1999.9999999999998659756'), investment: new Big('1999.9999999999998659756'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2017-07-01', firstBuyDate: '2017-07-01',
fee: new Big(0), fee: new Big(0),
transactionCount: 1 transactionCount: 1
@ -692,7 +691,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('50'), quantity: new Big('50'),
symbol: 'TSLA', symbol: 'TSLA',
investment: new Big('2148.5'), investment: new Big('2148.5'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2017-01-03', firstBuyDate: '2017-01-03',
fee: new Big(0), fee: new Big(0),
transactionCount: 1 transactionCount: 1
@ -707,7 +706,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'AMZN', symbol: 'AMZN',
investment: new Big('10109.95'), investment: new Big('10109.95'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2018-09-01', firstBuyDate: '2018-09-01',
fee: new Big(0), fee: new Big(0),
transactionCount: 1 transactionCount: 1
@ -717,7 +716,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('0.5614682'), quantity: new Big('0.5614682'),
symbol: 'BTCUSD', symbol: 'BTCUSD',
investment: new Big('1999.9999999999998659756'), investment: new Big('1999.9999999999998659756'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2017-07-01', firstBuyDate: '2017-07-01',
fee: new Big(0), fee: new Big(0),
transactionCount: 1 transactionCount: 1
@ -727,7 +726,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('50'), quantity: new Big('50'),
symbol: 'TSLA', symbol: 'TSLA',
investment: new Big('2148.5'), investment: new Big('2148.5'),
currency: Currency.USD, currency: 'USD',
firstBuyDate: '2017-01-03', firstBuyDate: '2017-01-03',
fee: new Big(0), fee: new Big(0),
transactionCount: 1 transactionCount: 1
@ -742,7 +741,7 @@ describe('PortfolioCalculator', () => {
it('with single TSLA and early start', async () => { it('with single TSLA and early start', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.setTransactionPoints(orderTslaTransactionPoint); portfolioCalculator.setTransactionPoints(orderTslaTransactionPoint);
@ -782,7 +781,7 @@ describe('PortfolioCalculator', () => {
it('with single TSLA and buy day start', async () => { it('with single TSLA and buy day start', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.setTransactionPoints(orderTslaTransactionPoint); portfolioCalculator.setTransactionPoints(orderTslaTransactionPoint);
@ -822,7 +821,7 @@ describe('PortfolioCalculator', () => {
it('with single TSLA and late start', async () => { it('with single TSLA and late start', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.setTransactionPoints(orderTslaTransactionPoint); portfolioCalculator.setTransactionPoints(orderTslaTransactionPoint);
@ -862,7 +861,7 @@ describe('PortfolioCalculator', () => {
it('with VTI only', async () => { it('with VTI only', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.setTransactionPoints(ordersVTITransactionPoints); portfolioCalculator.setTransactionPoints(ordersVTITransactionPoints);
@ -905,7 +904,7 @@ describe('PortfolioCalculator', () => {
it('with buy and sell', async () => { it('with buy and sell', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.setTransactionPoints(transactionPointsBuyAndSell); portfolioCalculator.setTransactionPoints(transactionPointsBuyAndSell);
@ -959,7 +958,7 @@ describe('PortfolioCalculator', () => {
it('with buy, sell, buy', async () => { it('with buy, sell, buy', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.setTransactionPoints([ portfolioCalculator.setTransactionPoints([
{ {
@ -969,7 +968,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('805.9'), investment: new Big('805.9'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01', firstBuyDate: '2019-09-01',
fee: new Big(0), fee: new Big(0),
@ -984,7 +983,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('0'), quantity: new Big('0'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('0'), investment: new Big('0'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01', firstBuyDate: '2019-09-01',
fee: new Big(0), fee: new Big(0),
@ -999,7 +998,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('1013.9'), investment: new Big('1013.9'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01', firstBuyDate: '2019-09-01',
fee: new Big(0), fee: new Big(0),
@ -1047,7 +1046,7 @@ describe('PortfolioCalculator', () => {
it('with performance since Jan 1st, 2020', async () => { it('with performance since Jan 1st, 2020', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
const transactionPoints: TransactionPoint[] = [ const transactionPoints: TransactionPoint[] = [
{ {
@ -1057,7 +1056,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('10'), quantity: new Big('10'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('1443.8'), investment: new Big('1443.8'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -1072,7 +1071,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('20'), quantity: new Big('20'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2923.7'), investment: new Big('2923.7'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -1130,7 +1129,7 @@ describe('PortfolioCalculator', () => {
it('with net performance since Jan 1st, 2020 - include fees', async () => { it('with net performance since Jan 1st, 2020 - include fees', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
const transactionPoints: TransactionPoint[] = [ const transactionPoints: TransactionPoint[] = [
{ {
@ -1140,7 +1139,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('10'), quantity: new Big('10'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('1443.8'), investment: new Big('1443.8'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(50), fee: new Big(50),
@ -1155,7 +1154,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('20'), quantity: new Big('20'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2923.7'), investment: new Big('2923.7'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(100), fee: new Big(100),
@ -1223,7 +1222,7 @@ describe('PortfolioCalculator', () => {
it('with net performance since Feb 1st, 2019 - include fees', async () => { it('with net performance since Feb 1st, 2019 - include fees', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
const transactionPoints: TransactionPoint[] = [ const transactionPoints: TransactionPoint[] = [
{ {
@ -1233,7 +1232,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('10'), quantity: new Big('10'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('1443.8'), investment: new Big('1443.8'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(50), fee: new Big(50),
@ -1248,7 +1247,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('20'), quantity: new Big('20'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2923.7'), investment: new Big('2923.7'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(100), fee: new Big(100),
@ -1311,7 +1310,7 @@ describe('PortfolioCalculator', () => {
it('with TWR example from Investopedia: Scenario 1', async () => { it('with TWR example from Investopedia: Scenario 1', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.setTransactionPoints([ portfolioCalculator.setTransactionPoints([
{ {
@ -1321,7 +1320,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('1000000'), // 1 million quantity: new Big('1000000'), // 1 million
symbol: 'MFA', // Mutual Fund A symbol: 'MFA', // Mutual Fund A
investment: new Big('1000000'), // 1 million investment: new Big('1000000'), // 1 million
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2010-12-31', firstBuyDate: '2010-12-31',
fee: new Big(0), fee: new Big(0),
@ -1336,7 +1335,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('1086022.689344541'), // 1,000,000 + 100,000 / 1.162484 quantity: new Big('1086022.689344541'), // 1,000,000 + 100,000 / 1.162484
symbol: 'MFA', // Mutual Fund A symbol: 'MFA', // Mutual Fund A
investment: new Big('1100000'), // 1,000,000 + 100,000 investment: new Big('1100000'), // 1,000,000 + 100,000
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2010-12-31', firstBuyDate: '2010-12-31',
fee: new Big(0), fee: new Big(0),
@ -1388,7 +1387,7 @@ describe('PortfolioCalculator', () => {
it('with example from chsoft.ch: Performance of a Combination of Investments', async () => { it('with example from chsoft.ch: Performance of a Combination of Investments', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.CHF 'CHF'
); );
portfolioCalculator.setTransactionPoints([ portfolioCalculator.setTransactionPoints([
{ {
@ -1398,7 +1397,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('200'), quantity: new Big('200'),
symbol: 'SPA', // Sub Portfolio A symbol: 'SPA', // Sub Portfolio A
investment: new Big('200'), investment: new Big('200'),
currency: Currency.CHF, currency: 'CHF',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2012-12-31', firstBuyDate: '2012-12-31',
fee: new Big(0), fee: new Big(0),
@ -1408,7 +1407,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('300'), quantity: new Big('300'),
symbol: 'SPB', // Sub Portfolio B symbol: 'SPB', // Sub Portfolio B
investment: new Big('300'), investment: new Big('300'),
currency: Currency.CHF, currency: 'CHF',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2012-12-31', firstBuyDate: '2012-12-31',
fee: new Big(0), fee: new Big(0),
@ -1423,7 +1422,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('200'), quantity: new Big('200'),
symbol: 'SPA', // Sub Portfolio A symbol: 'SPA', // Sub Portfolio A
investment: new Big('200'), investment: new Big('200'),
currency: Currency.CHF, currency: 'CHF',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2012-12-31', firstBuyDate: '2012-12-31',
fee: new Big(0), fee: new Big(0),
@ -1433,7 +1432,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('300'), quantity: new Big('300'),
symbol: 'SPB', // Sub Portfolio B symbol: 'SPB', // Sub Portfolio B
investment: new Big('300'), investment: new Big('300'),
currency: Currency.CHF, currency: 'CHF',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2012-12-31', firstBuyDate: '2012-12-31',
fee: new Big(0), fee: new Big(0),
@ -1494,7 +1493,7 @@ describe('PortfolioCalculator', () => {
it('with yearly', async () => { it('with yearly', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.setTransactionPoints(ordersVTITransactionPoints); portfolioCalculator.setTransactionPoints(ordersVTITransactionPoints);
const timelineSpecification: TimelineSpecification[] = [ const timelineSpecification: TimelineSpecification[] = [
@ -1537,7 +1536,7 @@ describe('PortfolioCalculator', () => {
it('with yearly and fees', async () => { it('with yearly and fees', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
const transactionPoints: TransactionPoint[] = [ const transactionPoints: TransactionPoint[] = [
{ {
@ -1547,7 +1546,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('10'), quantity: new Big('10'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('1443.8'), investment: new Big('1443.8'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(50), fee: new Big(50),
@ -1562,7 +1561,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('20'), quantity: new Big('20'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2923.7'), investment: new Big('2923.7'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(100), fee: new Big(100),
@ -1577,7 +1576,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('652.55'), investment: new Big('652.55'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(150), fee: new Big(150),
@ -1592,7 +1591,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('15'), quantity: new Big('15'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2684.05'), investment: new Big('2684.05'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(200), fee: new Big(200),
@ -1607,7 +1606,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('25'), quantity: new Big('25'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('4460.95'), investment: new Big('4460.95'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(250), fee: new Big(250),
@ -1657,7 +1656,7 @@ describe('PortfolioCalculator', () => {
it('with monthly', async () => { it('with monthly', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.setTransactionPoints(ordersVTITransactionPoints); portfolioCalculator.setTransactionPoints(ordersVTITransactionPoints);
const timelineSpecification: TimelineSpecification[] = [ const timelineSpecification: TimelineSpecification[] = [
@ -1889,7 +1888,7 @@ describe('PortfolioCalculator', () => {
it('with yearly and monthly mixed', async () => { it('with yearly and monthly mixed', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.setTransactionPoints(ordersVTITransactionPoints); portfolioCalculator.setTransactionPoints(ordersVTITransactionPoints);
const timelineSpecification: TimelineSpecification[] = [ const timelineSpecification: TimelineSpecification[] = [
@ -1971,7 +1970,7 @@ describe('PortfolioCalculator', () => {
it('with all mixed', async () => { it('with all mixed', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.setTransactionPoints(ordersVTITransactionPoints); portfolioCalculator.setTransactionPoints(ordersVTITransactionPoints);
const timelineSpecification: TimelineSpecification[] = [ const timelineSpecification: TimelineSpecification[] = [
@ -2262,7 +2261,7 @@ describe('PortfolioCalculator', () => {
it('with mixed portfolio', async () => { it('with mixed portfolio', async () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
portfolioCalculator.setTransactionPoints([ portfolioCalculator.setTransactionPoints([
{ {
@ -2272,7 +2271,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'AMZN', symbol: 'AMZN',
investment: new Big('10109.95'), investment: new Big('10109.95'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -2282,7 +2281,7 @@ describe('PortfolioCalculator', () => {
quantity: new Big('10'), quantity: new Big('10'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('1443.8'), investment: new Big('1443.8'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -2325,7 +2324,7 @@ describe('PortfolioCalculator', () => {
describe('annualized performance percentage', () => { describe('annualized performance percentage', () => {
const portfolioCalculator = new PortfolioCalculator( const portfolioCalculator = new PortfolioCalculator(
currentRateService, currentRateService,
Currency.USD 'USD'
); );
it('Get annualized performance', async () => { it('Get annualized performance', async () => {
@ -2391,7 +2390,7 @@ const ordersMixedSymbols: PortfolioOrder[] = [
symbol: 'TSLA', symbol: 'TSLA',
type: OrderType.Buy, type: OrderType.Buy,
unitPrice: new Big('42.97'), unitPrice: new Big('42.97'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big(0) fee: new Big(0)
}, },
@ -2402,7 +2401,7 @@ const ordersMixedSymbols: PortfolioOrder[] = [
symbol: 'BTCUSD', symbol: 'BTCUSD',
type: OrderType.Buy, type: OrderType.Buy,
unitPrice: new Big('3562.089535970158'), unitPrice: new Big('3562.089535970158'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big(0) fee: new Big(0)
}, },
@ -2413,7 +2412,7 @@ const ordersMixedSymbols: PortfolioOrder[] = [
symbol: 'AMZN', symbol: 'AMZN',
type: OrderType.Buy, type: OrderType.Buy,
unitPrice: new Big('2021.99'), unitPrice: new Big('2021.99'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big(0) fee: new Big(0)
} }
@ -2427,7 +2426,7 @@ const ordersVTI: PortfolioOrder[] = [
symbol: 'VTI', symbol: 'VTI',
type: OrderType.Buy, type: OrderType.Buy,
unitPrice: new Big('144.38'), unitPrice: new Big('144.38'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big(0) fee: new Big(0)
}, },
@ -2438,7 +2437,7 @@ const ordersVTI: PortfolioOrder[] = [
symbol: 'VTI', symbol: 'VTI',
type: OrderType.Buy, type: OrderType.Buy,
unitPrice: new Big('147.99'), unitPrice: new Big('147.99'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big(0) fee: new Big(0)
}, },
@ -2449,7 +2448,7 @@ const ordersVTI: PortfolioOrder[] = [
symbol: 'VTI', symbol: 'VTI',
type: OrderType.Sell, type: OrderType.Sell,
unitPrice: new Big('151.41'), unitPrice: new Big('151.41'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big(0) fee: new Big(0)
}, },
@ -2460,7 +2459,7 @@ const ordersVTI: PortfolioOrder[] = [
symbol: 'VTI', symbol: 'VTI',
type: OrderType.Buy, type: OrderType.Buy,
unitPrice: new Big('177.69'), unitPrice: new Big('177.69'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big(0) fee: new Big(0)
}, },
@ -2471,7 +2470,7 @@ const ordersVTI: PortfolioOrder[] = [
symbol: 'VTI', symbol: 'VTI',
type: OrderType.Buy, type: OrderType.Buy,
unitPrice: new Big('203.15'), unitPrice: new Big('203.15'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
fee: new Big(0) fee: new Big(0)
} }
@ -2485,7 +2484,7 @@ const orderTslaTransactionPoint: TransactionPoint[] = [
quantity: new Big('1'), quantity: new Big('1'),
symbol: 'TSLA', symbol: 'TSLA',
investment: new Big('719.46'), investment: new Big('719.46'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2021-01-01', firstBuyDate: '2021-01-01',
fee: new Big(0), fee: new Big(0),
@ -2503,7 +2502,7 @@ const ordersVTITransactionPoints: TransactionPoint[] = [
quantity: new Big('10'), quantity: new Big('10'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('1443.8'), investment: new Big('1443.8'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -2518,7 +2517,7 @@ const ordersVTITransactionPoints: TransactionPoint[] = [
quantity: new Big('20'), quantity: new Big('20'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2923.7'), investment: new Big('2923.7'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -2533,7 +2532,7 @@ const ordersVTITransactionPoints: TransactionPoint[] = [
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('652.55'), investment: new Big('652.55'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -2548,7 +2547,7 @@ const ordersVTITransactionPoints: TransactionPoint[] = [
quantity: new Big('15'), quantity: new Big('15'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2684.05'), investment: new Big('2684.05'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -2563,7 +2562,7 @@ const ordersVTITransactionPoints: TransactionPoint[] = [
quantity: new Big('25'), quantity: new Big('25'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('4460.95'), investment: new Big('4460.95'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -2581,7 +2580,7 @@ const transactionPointsBuyAndSell: TransactionPoint[] = [
quantity: new Big('10'), quantity: new Big('10'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('1443.8'), investment: new Big('1443.8'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -2596,7 +2595,7 @@ const transactionPointsBuyAndSell: TransactionPoint[] = [
quantity: new Big('20'), quantity: new Big('20'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2923.7'), investment: new Big('2923.7'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -2611,7 +2610,7 @@ const transactionPointsBuyAndSell: TransactionPoint[] = [
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'AMZN', symbol: 'AMZN',
investment: new Big('10109.95'), investment: new Big('10109.95'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01', firstBuyDate: '2019-09-01',
fee: new Big(0), fee: new Big(0),
@ -2621,7 +2620,7 @@ const transactionPointsBuyAndSell: TransactionPoint[] = [
quantity: new Big('20'), quantity: new Big('20'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2923.7'), investment: new Big('2923.7'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -2636,7 +2635,7 @@ const transactionPointsBuyAndSell: TransactionPoint[] = [
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'AMZN', symbol: 'AMZN',
investment: new Big('10109.95'), investment: new Big('10109.95'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01', firstBuyDate: '2019-09-01',
fee: new Big(0), fee: new Big(0),
@ -2646,7 +2645,7 @@ const transactionPointsBuyAndSell: TransactionPoint[] = [
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('652.55'), investment: new Big('652.55'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -2661,7 +2660,7 @@ const transactionPointsBuyAndSell: TransactionPoint[] = [
quantity: new Big('0'), quantity: new Big('0'),
symbol: 'AMZN', symbol: 'AMZN',
investment: new Big('0'), investment: new Big('0'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01', firstBuyDate: '2019-09-01',
fee: new Big(0), fee: new Big(0),
@ -2671,7 +2670,7 @@ const transactionPointsBuyAndSell: TransactionPoint[] = [
quantity: new Big('5'), quantity: new Big('5'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('652.55'), investment: new Big('652.55'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -2686,7 +2685,7 @@ const transactionPointsBuyAndSell: TransactionPoint[] = [
quantity: new Big('0'), quantity: new Big('0'),
symbol: 'AMZN', symbol: 'AMZN',
investment: new Big('0'), investment: new Big('0'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01', firstBuyDate: '2019-09-01',
fee: new Big(0), fee: new Big(0),
@ -2696,7 +2695,7 @@ const transactionPointsBuyAndSell: TransactionPoint[] = [
quantity: new Big('15'), quantity: new Big('15'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('2684.05'), investment: new Big('2684.05'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),
@ -2711,7 +2710,7 @@ const transactionPointsBuyAndSell: TransactionPoint[] = [
quantity: new Big('0'), quantity: new Big('0'),
symbol: 'AMZN', symbol: 'AMZN',
investment: new Big('0'), investment: new Big('0'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-09-01', firstBuyDate: '2019-09-01',
fee: new Big(0), fee: new Big(0),
@ -2721,7 +2720,7 @@ const transactionPointsBuyAndSell: TransactionPoint[] = [
quantity: new Big('25'), quantity: new Big('25'),
symbol: 'VTI', symbol: 'VTI',
investment: new Big('4460.95'), investment: new Big('4460.95'),
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
firstBuyDate: '2019-02-01', firstBuyDate: '2019-02-01',
fee: new Big(0), fee: new Big(0),

7
apps/api/src/app/portfolio/portfolio-calculator.ts

@ -2,7 +2,6 @@ import { OrderType } from '@ghostfolio/api/models/order-type';
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper'; import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
import { TimelinePosition } from '@ghostfolio/common/interfaces'; import { TimelinePosition } from '@ghostfolio/common/interfaces';
import { Currency, DataSource } from '@prisma/client';
import Big from 'big.js'; import Big from 'big.js';
import { import {
addDays, addDays,
@ -35,7 +34,7 @@ export class PortfolioCalculator {
public constructor( public constructor(
private currentRateService: CurrentRateService, private currentRateService: CurrentRateService,
private currency: Currency private currency: string
) {} ) {}
public computeTransactionPoints(orders: PortfolioOrder[]) { public computeTransactionPoints(orders: PortfolioOrder[]) {
@ -157,7 +156,7 @@ export class PortfolioCalculator {
let firstIndex = this.transactionPoints.length; let firstIndex = this.transactionPoints.length;
const dates = []; const dates = [];
const dataGatheringItems: IDataGatheringItem[] = []; const dataGatheringItems: IDataGatheringItem[] = [];
const currencies: { [symbol: string]: Currency } = {}; const currencies: { [symbol: string]: string } = {};
dates.push(resetHours(start)); dates.push(resetHours(start));
for (const item of this.transactionPoints[firstIndex - 1].items) { for (const item of this.transactionPoints[firstIndex - 1].items) {
@ -521,7 +520,7 @@ export class PortfolioCalculator {
[date: string]: { [symbol: string]: Big }; [date: string]: { [symbol: string]: Big };
} = {}; } = {};
if (j >= 0) { if (j >= 0) {
const currencies: { [name: string]: Currency } = {}; const currencies: { [name: string]: string } = {};
const dataGatheringItems: IDataGatheringItem[] = []; const dataGatheringItems: IDataGatheringItem[] = [];
for (const item of this.transactionPoints[j].items) { for (const item of this.transactionPoints[j].items) {

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

@ -39,15 +39,9 @@ import type {
} from '@ghostfolio/common/types'; } from '@ghostfolio/common/types';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { REQUEST } from '@nestjs/core'; import { REQUEST } from '@nestjs/core';
import { import { AssetClass, DataSource, Type as TypeOfOrder } from '@prisma/client';
AssetClass,
Currency,
DataSource,
Type as TypeOfOrder
} from '@prisma/client';
import Big from 'big.js'; import Big from 'big.js';
import { import {
differenceInDays,
endOfToday, endOfToday,
format, format,
isAfter, isAfter,
@ -59,7 +53,7 @@ import {
subDays, subDays,
subYears subYears
} from 'date-fns'; } from 'date-fns';
import { isEmpty, isNumber } from 'lodash'; import { isEmpty } from 'lodash';
import { import {
HistoricalDataItem, HistoricalDataItem,
@ -775,7 +769,7 @@ export class PortfolioService {
assetClass: AssetClass.CASH, assetClass: AssetClass.CASH,
assetSubClass: AssetClass.CASH, assetSubClass: AssetClass.CASH,
countries: [], countries: [],
currency: Currency.CHF, currency: 'CHF',
grossPerformance: 0, grossPerformance: 0,
grossPerformancePercent: 0, grossPerformancePercent: 0,
investment: cashValue.toNumber(), investment: cashValue.toNumber(),
@ -865,7 +859,7 @@ export class PortfolioService {
private async getAccounts( private async getAccounts(
orders: OrderWithAccount[], orders: OrderWithAccount[],
portfolioItemsNow: { [p: string]: TimelinePosition }, portfolioItemsNow: { [p: string]: TimelinePosition },
userCurrency: Currency, userCurrency: string,
userId: string userId: string
) { ) {
const accounts: PortfolioDetails['accounts'] = {}; const accounts: PortfolioDetails['accounts'] = {};
@ -938,7 +932,7 @@ export class PortfolioService {
private getTotalByType( private getTotalByType(
orders: OrderWithAccount[], orders: OrderWithAccount[],
currency: Currency, currency: string,
type: TypeOfOrder type: TypeOfOrder
) { ) {
return orders return orders

3
apps/api/src/app/portfolio/rules.service.ts

@ -1,7 +1,6 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule'; import { Rule } from '@ghostfolio/api/models/rule';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Currency } from '@prisma/client';
@Injectable() @Injectable()
export class RulesService { export class RulesService {
@ -9,7 +8,7 @@ export class RulesService {
public async evaluate<T extends RuleSettings>( public async evaluate<T extends RuleSettings>(
aRules: Rule<T>[], aRules: Rule<T>[],
aUserSettings: { baseCurrency: Currency } aUserSettings: { baseCurrency: string }
) { ) {
return aRules return aRules
.filter((rule) => { .filter((rule) => {

4
apps/api/src/app/symbol/interfaces/lookup-item.interface.ts

@ -1,7 +1,7 @@
import { Currency, DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
export interface LookupItem { export interface LookupItem {
currency: Currency; currency: string;
dataSource: DataSource; dataSource: DataSource;
name: string; name: string;
symbol: string; symbol: string;

4
apps/api/src/app/symbol/interfaces/symbol-item.interface.ts

@ -1,7 +1,7 @@
import { Currency, DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
export interface SymbolItem { export interface SymbolItem {
currency: Currency; currency: string;
dataSource: DataSource; dataSource: DataSource;
marketPrice: number; marketPrice: number;
} }

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

@ -2,7 +2,7 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Currency, DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
import { LookupItem } from './interfaces/lookup-item.interface'; import { LookupItem } from './interfaces/lookup-item.interface';
import { SymbolItem } from './interfaces/symbol-item.interface'; import { SymbolItem } from './interfaces/symbol-item.interface';
@ -20,8 +20,8 @@ export class SymbolService {
if (dataGatheringItem.dataSource && marketPrice) { if (dataGatheringItem.dataSource && marketPrice) {
return { return {
currency,
marketPrice, marketPrice,
currency: <Currency>(<unknown>currency),
dataSource: dataGatheringItem.dataSource dataSource: dataGatheringItem.dataSource
}; };
} }

4
apps/api/src/app/user/interfaces/user-settings-params.interface.ts

@ -1,7 +1,7 @@
import { Currency, ViewMode } from '@prisma/client'; import { ViewMode } from '@prisma/client';
export interface UserSettingsParams { export interface UserSettingsParams {
currency?: Currency; currency?: string;
userId: string; userId: string;
viewMode?: ViewMode; viewMode?: ViewMode;
} }

4
apps/api/src/app/user/update-user-settings.dto.ts

@ -1,9 +1,9 @@
import { Currency, ViewMode } from '@prisma/client'; import { ViewMode } from '@prisma/client';
import { IsString } from 'class-validator'; import { IsString } from 'class-validator';
export class UpdateUserSettingsDto { export class UpdateUserSettingsDto {
@IsString() @IsString()
baseCurrency: Currency; baseCurrency: string;
@IsString() @IsString()
viewMode: ViewMode; viewMode: ViewMode;

12
apps/api/src/app/user/user.service.ts

@ -1,12 +1,12 @@
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service'; import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { locale } from '@ghostfolio/common/config'; import { baseCurrency, locale } from '@ghostfolio/common/config';
import { User as IUser, UserWithSettings } from '@ghostfolio/common/interfaces'; import { User as IUser, UserWithSettings } from '@ghostfolio/common/interfaces';
import { getPermissions, permissions } from '@ghostfolio/common/permissions'; import { getPermissions, permissions } from '@ghostfolio/common/permissions';
import { SubscriptionType } from '@ghostfolio/common/types/subscription.type'; import { SubscriptionType } from '@ghostfolio/common/types/subscription.type';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Currency, Prisma, Provider, User, ViewMode } from '@prisma/client'; import { Prisma, Provider, User, ViewMode } from '@prisma/client';
import { UserSettingsParams } from './interfaces/user-settings-params.interface'; import { UserSettingsParams } from './interfaces/user-settings-params.interface';
import { UserSettings } from './interfaces/user-settings.interface'; import { UserSettings } from './interfaces/user-settings.interface';
@ -15,7 +15,7 @@ const crypto = require('crypto');
@Injectable() @Injectable()
export class UserService { export class UserService {
public static DEFAULT_CURRENCY = Currency.USD; public static DEFAULT_CURRENCY = 'USD';
public constructor( public constructor(
private readonly configurationService: ConfigurationService, private readonly configurationService: ConfigurationService,
@ -144,9 +144,15 @@ export class UserService {
...data, ...data,
Account: { Account: {
create: { create: {
currency: baseCurrency,
isDefault: true, isDefault: true,
name: 'Default Account' name: 'Default Account'
} }
},
Settings: {
create: {
currency: baseCurrency
}
} }
} }
}); });

4
apps/api/src/models/interfaces/user-settings.interface.ts

@ -1,5 +1,3 @@
import { Currency } from '@prisma/client';
export interface UserSettings { export interface UserSettings {
baseCurrency: Currency; baseCurrency: string;
} }

4
apps/api/src/models/order.ts

@ -1,4 +1,4 @@
import { Account, Currency, SymbolProfile } from '@prisma/client'; import { Account, SymbolProfile } from '@prisma/client';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { IOrder } from '../services/interfaces/interfaces'; import { IOrder } from '../services/interfaces/interfaces';
@ -6,7 +6,7 @@ import { OrderType } from './order-type';
export class Order { export class Order {
private account: Account; private account: Account;
private currency: Currency; private currency: string;
private fee: number; private fee: number;
private date: string; private date: string;
private id: string; private id: string;

3
apps/api/src/models/rule.ts

@ -3,7 +3,6 @@ import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.in
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { groupBy } from '@ghostfolio/common/helper'; import { groupBy } from '@ghostfolio/common/helper';
import { TimelinePosition } from '@ghostfolio/common/interfaces'; import { TimelinePosition } from '@ghostfolio/common/interfaces';
import { Currency } from '@prisma/client';
import { EvaluationResult } from './interfaces/evaluation-result.interface'; import { EvaluationResult } from './interfaces/evaluation-result.interface';
import { RuleInterface } from './interfaces/rule.interface'; import { RuleInterface } from './interfaces/rule.interface';
@ -29,7 +28,7 @@ export abstract class Rule<T extends RuleSettings> implements RuleInterface<T> {
public groupCurrentPositionsByAttribute( public groupCurrentPositionsByAttribute(
positions: TimelinePosition[], positions: TimelinePosition[],
attribute: keyof TimelinePosition, attribute: keyof TimelinePosition,
baseCurrency: Currency baseCurrency: string
) { ) {
return Array.from(groupBy(attribute, positions).entries()).map( return Array.from(groupBy(attribute, positions).entries()).map(
([attributeValue, objs]) => ({ ([attributeValue, objs]) => ({

4
apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts

@ -2,8 +2,6 @@ import { CurrentPositions } from '@ghostfolio/api/app/portfolio/interfaces/curre
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface'; import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
import { Currency } from '@prisma/client';
import { Rule } from '../../rule'; import { Rule } from '../../rule';
@ -69,5 +67,5 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Setti
} }
interface Settings extends RuleSettings { interface Settings extends RuleSettings {
baseCurrency: Currency; baseCurrency: string;
} }

3
apps/api/src/models/rules/currency-cluster-risk/base-currency-initial-investment.ts

@ -1,7 +1,6 @@
import { CurrentPositions } from '@ghostfolio/api/app/portfolio/interfaces/current-positions.interface'; import { CurrentPositions } from '@ghostfolio/api/app/portfolio/interfaces/current-positions.interface';
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface'; import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface';
import { Currency } from '@prisma/client';
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service'; import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
import { Rule } from '../../rule'; import { Rule } from '../../rule';
@ -69,5 +68,5 @@ export class CurrencyClusterRiskBaseCurrencyInitialInvestment extends Rule<Setti
} }
interface Settings extends RuleSettings { interface Settings extends RuleSettings {
baseCurrency: Currency; baseCurrency: string;
} }

3
apps/api/src/models/rules/currency-cluster-risk/current-investment.ts

@ -1,7 +1,6 @@
import { CurrentPositions } from '@ghostfolio/api/app/portfolio/interfaces/current-positions.interface'; import { CurrentPositions } from '@ghostfolio/api/app/portfolio/interfaces/current-positions.interface';
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface'; import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface';
import { Currency } from '@prisma/client';
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service'; import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
import { Rule } from '../../rule'; import { Rule } from '../../rule';
@ -69,6 +68,6 @@ export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
} }
interface Settings extends RuleSettings { interface Settings extends RuleSettings {
baseCurrency: Currency; baseCurrency: string;
threshold: number; threshold: number;
} }

3
apps/api/src/models/rules/currency-cluster-risk/initial-investment.ts

@ -1,7 +1,6 @@
import { CurrentPositions } from '@ghostfolio/api/app/portfolio/interfaces/current-positions.interface'; import { CurrentPositions } from '@ghostfolio/api/app/portfolio/interfaces/current-positions.interface';
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface'; import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface';
import { Currency } from '@prisma/client';
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service'; import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
import { Rule } from '../../rule'; import { Rule } from '../../rule';
@ -69,6 +68,6 @@ export class CurrencyClusterRiskInitialInvestment extends Rule<Settings> {
} }
interface Settings extends RuleSettings { interface Settings extends RuleSettings {
baseCurrency: Currency; baseCurrency: string;
threshold: number; threshold: number;
} }

3
apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts

@ -1,6 +1,5 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface'; import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface';
import { Currency } from '@prisma/client';
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service'; import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
import { Rule } from '../../rule'; import { Rule } from '../../rule';
@ -46,6 +45,6 @@ export class FeeRatioInitialInvestment extends Rule<Settings> {
} }
interface Settings extends RuleSettings { interface Settings extends RuleSettings {
baseCurrency: Currency; baseCurrency: string;
threshold: number; threshold: number;
} }

9
apps/api/src/services/data-gathering.module.ts

@ -4,8 +4,15 @@ import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-
import { PrismaModule } from '@ghostfolio/api/services/prisma.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ExchangeRateDataModule } from './exchange-rate-data.module';
@Module({ @Module({
imports: [ConfigurationModule, DataProviderModule, PrismaModule], imports: [
ConfigurationModule,
DataProviderModule,
ExchangeRateDataModule,
PrismaModule
],
providers: [DataGatheringService], providers: [DataGatheringService],
exports: [DataGatheringService] exports: [DataGatheringService]
}) })

21
apps/api/src/services/data-gathering.service.ts

@ -1,6 +1,5 @@
import { import {
benchmarks, benchmarks,
currencyPairs,
ghostfolioFearAndGreedIndexSymbol ghostfolioFearAndGreedIndexSymbol
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { DATE_FORMAT, getUtc, resetHours } from '@ghostfolio/common/helper'; import { DATE_FORMAT, getUtc, resetHours } from '@ghostfolio/common/helper';
@ -19,6 +18,7 @@ import {
import { ConfigurationService } from './configuration.service'; import { ConfigurationService } from './configuration.service';
import { DataProviderService } from './data-provider/data-provider.service'; import { DataProviderService } from './data-provider/data-provider.service';
import { GhostfolioScraperApiService } from './data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service'; import { GhostfolioScraperApiService } from './data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
import { ExchangeRateDataService } from './exchange-rate-data.service';
import { IDataGatheringItem } from './interfaces/interfaces'; import { IDataGatheringItem } from './interfaces/interfaces';
import { PrismaService } from './prisma.service'; import { PrismaService } from './prisma.service';
@ -27,6 +27,7 @@ export class DataGatheringService {
public constructor( public constructor(
private readonly configurationService: ConfigurationService, private readonly configurationService: ConfigurationService,
private readonly dataProviderService: DataProviderService, private readonly dataProviderService: DataProviderService,
private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly ghostfolioScraperApi: GhostfolioScraperApiService, private readonly ghostfolioScraperApi: GhostfolioScraperApiService,
private readonly prismaService: PrismaService private readonly prismaService: PrismaService
) {} ) {}
@ -230,6 +231,8 @@ export class DataGatheringService {
} }
} }
await this.exchangeRateDataService.initialize();
if (hasError) { if (hasError) {
throw ''; throw '';
} }
@ -316,15 +319,15 @@ export class DataGatheringService {
}; };
}); });
const currencyPairsToGather = currencyPairs.map( const currencyPairsToGather = this.exchangeRateDataService
({ dataSource, symbol }) => { .getCurrencyPairs()
.map(({ dataSource, symbol }) => {
return { return {
dataSource, dataSource,
symbol, symbol,
date: startDate date: startDate
}; };
} });
);
const customSymbolsToGather = const customSymbolsToGather =
await this.ghostfolioScraperApi.getCustomSymbolsToGather(startDate); await this.ghostfolioScraperApi.getCustomSymbolsToGather(startDate);
@ -343,15 +346,15 @@ export class DataGatheringService {
const customSymbolsToGather = const customSymbolsToGather =
await this.ghostfolioScraperApi.getCustomSymbolsToGather(startDate); await this.ghostfolioScraperApi.getCustomSymbolsToGather(startDate);
const currencyPairsToGather = currencyPairs.map( const currencyPairsToGather = this.exchangeRateDataService
({ dataSource, symbol }) => { .getCurrencyPairs()
.map(({ dataSource, symbol }) => {
return { return {
dataSource, dataSource,
symbol, symbol,
date: startDate date: startDate
}; };
} });
);
const symbolProfilesToGather = const symbolProfilesToGather =
await this.prismaService.symbolProfile.findMany({ await this.prismaService.symbolProfile.findMany({

4
apps/api/src/services/data-provider/ghostfolio-scraper-api/interfaces/scraper-config.interface.ts

@ -1,7 +1,5 @@
import { Currency } from '@prisma/client';
export interface ScraperConfig { export interface ScraperConfig {
currency: Currency; currency: string;
selector: string; selector: string;
symbol: string; symbol: string;
url: string; url: string;

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

@ -1,19 +1,9 @@
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { import { DATE_FORMAT, isCrypto, isCurrency } from '@ghostfolio/common/helper';
DATE_FORMAT,
isCrypto,
isCurrency,
parseCurrency
} from '@ghostfolio/common/helper';
import { Granularity } from '@ghostfolio/common/types'; import { Granularity } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { import { AssetClass, AssetSubClass, DataSource } from '@prisma/client';
AssetClass,
AssetSubClass,
Currency,
DataSource
} from '@prisma/client';
import * as bent from 'bent'; import * as bent from 'bent';
import Big from 'big.js'; import Big from 'big.js';
import { countries } from 'countries-list'; import { countries } from 'countries-list';
@ -68,7 +58,7 @@ export class YahooFinanceService implements DataProviderInterface {
response[symbol] = { response[symbol] = {
assetClass, assetClass,
assetSubClass, assetSubClass,
currency: parseCurrency(value.price?.currency), currency: value.price?.currency,
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
exchange: this.parseExchange(value.price?.exchangeName), exchange: this.parseExchange(value.price?.exchangeName),
marketState: marketState:
@ -81,7 +71,7 @@ export class YahooFinanceService implements DataProviderInterface {
if (value.price?.currency === 'GBp') { if (value.price?.currency === 'GBp') {
// Convert GBp (pence) to GBP // Convert GBp (pence) to GBP
response[symbol].currency = Currency.GBP; response[symbol].currency = 'GBP';
response[symbol].marketPrice = new Big( response[symbol].marketPrice = new Big(
value.price?.regularMarketPrice ?? 0 value.price?.regularMarketPrice ?? 0
) )
@ -200,7 +190,7 @@ export class YahooFinanceService implements DataProviderInterface {
.filter(({ quoteType, symbol }) => { .filter(({ quoteType, symbol }) => {
if (quoteType === 'CRYPTOCURRENCY') { if (quoteType === 'CRYPTOCURRENCY') {
// Only allow cryptocurrencies in USD // Only allow cryptocurrencies in USD
return symbol.includes(Currency.USD); return symbol.includes('USD');
} }
return true; return true;

4
apps/api/src/services/exchange-rate-data.module.ts

@ -2,8 +2,10 @@ import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma.module';
@Module({ @Module({
imports: [DataProviderModule], imports: [DataProviderModule, PrismaModule],
providers: [ExchangeRateDataService], providers: [ExchangeRateDataService],
exports: [ExchangeRateDataService] exports: [ExchangeRateDataService]
}) })

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

@ -1,27 +1,45 @@
import { currencyPairs } from '@ghostfolio/common/config'; import { baseCurrency } from '@ghostfolio/common/config';
import { DATE_FORMAT, getYesterday } from '@ghostfolio/common/helper'; import { DATE_FORMAT, getYesterday } from '@ghostfolio/common/helper';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Currency, DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { isEmpty, isNumber } from 'lodash'; import { isEmpty, isNumber, uniq } from 'lodash';
import { DataProviderService } from './data-provider/data-provider.service'; import { DataProviderService } from './data-provider/data-provider.service';
import { IDataGatheringItem } from './interfaces/interfaces'; import { IDataGatheringItem } from './interfaces/interfaces';
import { PrismaService } from './prisma.service';
@Injectable() @Injectable()
export class ExchangeRateDataService { export class ExchangeRateDataService {
private currencies: string[] = [];
private currencyPairs: IDataGatheringItem[] = []; private currencyPairs: IDataGatheringItem[] = [];
private exchangeRates: { [currencyPair: string]: number } = {}; private exchangeRates: { [currencyPair: string]: number } = {};
public constructor(private dataProviderService: DataProviderService) { public constructor(
private readonly dataProviderService: DataProviderService,
private readonly prismaService: PrismaService
) {
this.initialize(); this.initialize();
} }
public getCurrencies() {
return this.currencies?.length > 0 ? this.currencies : [baseCurrency];
}
public getCurrencyPairs() {
return this.currencyPairs;
}
public async initialize() { public async initialize() {
this.currencies = await this.prepareCurrencies();
this.currencyPairs = []; this.currencyPairs = [];
this.exchangeRates = {}; this.exchangeRates = {};
for (const { currency1, currency2, dataSource } of currencyPairs) { for (const {
currency1,
currency2,
dataSource
} of this.prepareCurrencyPairs(this.currencies)) {
this.addCurrencyPairs({ currency1, currency2, dataSource }); this.addCurrencyPairs({ currency1, currency2, dataSource });
} }
@ -77,8 +95,8 @@ export class ExchangeRateDataService {
if (!this.exchangeRates[symbol]) { if (!this.exchangeRates[symbol]) {
// Not found, calculate indirectly via USD // Not found, calculate indirectly via USD
this.exchangeRates[symbol] = this.exchangeRates[symbol] =
resultExtended[`${currency1}${Currency.USD}`]?.[date]?.marketPrice * resultExtended[`${currency1}${'USD'}`]?.[date]?.marketPrice *
resultExtended[`${Currency.USD}${currency2}`]?.[date]?.marketPrice; resultExtended[`${'USD'}${currency2}`]?.[date]?.marketPrice;
// Calculate the opposite direction // Calculate the opposite direction
this.exchangeRates[`${currency2}${currency1}`] = this.exchangeRates[`${currency2}${currency1}`] =
@ -89,10 +107,14 @@ export class ExchangeRateDataService {
public toCurrency( public toCurrency(
aValue: number, aValue: number,
aFromCurrency: Currency, aFromCurrency: string,
aToCurrency: Currency aToCurrency: string
) { ) {
if (isNaN(this.exchangeRates[`${Currency.USD}${Currency.CHF}`])) { const hasNaN = Object.values(this.exchangeRates).some((exchangeRate) => {
return isNaN(exchangeRate);
});
if (hasNaN) {
// Reinitialize if data is not loaded correctly // Reinitialize if data is not loaded correctly
this.initialize(); this.initialize();
} }
@ -104,8 +126,8 @@ export class ExchangeRateDataService {
factor = this.exchangeRates[`${aFromCurrency}${aToCurrency}`]; factor = this.exchangeRates[`${aFromCurrency}${aToCurrency}`];
} else { } else {
// Calculate indirectly via USD // Calculate indirectly via USD
const factor1 = this.exchangeRates[`${aFromCurrency}${Currency.USD}`]; const factor1 = this.exchangeRates[`${aFromCurrency}${'USD'}`];
const factor2 = this.exchangeRates[`${Currency.USD}${aToCurrency}`]; const factor2 = this.exchangeRates[`${'USD'}${aToCurrency}`];
factor = factor1 * factor2; factor = factor1 * factor2;
@ -129,8 +151,8 @@ export class ExchangeRateDataService {
currency2, currency2,
dataSource dataSource
}: { }: {
currency1: Currency; currency1: string;
currency2: Currency; currency2: string;
dataSource: DataSource; dataSource: DataSource;
}) { }) {
this.currencyPairs.push({ this.currencyPairs.push({
@ -142,4 +164,49 @@ export class ExchangeRateDataService {
symbol: `${currency2}${currency1}` symbol: `${currency2}${currency1}`
}); });
} }
private async prepareCurrencies(): Promise<string[]> {
const currencies: string[] = [];
const settings = await this.prismaService.settings.findMany({
distinct: ['currency'],
orderBy: [{ currency: 'asc' }],
select: { currency: true }
});
settings.forEach((settingsItem) => {
if (settingsItem.currency) {
currencies.push(settingsItem.currency);
}
});
const symbolProfiles = await this.prismaService.symbolProfile.findMany({
distinct: ['currency'],
orderBy: [{ currency: 'asc' }],
select: { currency: true }
});
symbolProfiles.forEach((symbolProfile) => {
if (symbolProfile.currency) {
currencies.push(symbolProfile.currency);
}
});
return uniq(currencies).sort();
}
private prepareCurrencyPairs(aCurrencies: string[]) {
return aCurrencies
.filter((currency) => {
return currency !== baseCurrency;
})
.map((currency) => {
return {
currency1: baseCurrency,
currency2: currency,
dataSource: DataSource.YAHOO,
symbol: `${baseCurrency}${currency}`
};
});
}
} }

5
apps/api/src/services/interfaces/interfaces.ts

@ -2,7 +2,6 @@ import {
Account, Account,
AssetClass, AssetClass,
AssetSubClass, AssetSubClass,
Currency,
DataSource, DataSource,
SymbolProfile SymbolProfile
} from '@prisma/client'; } from '@prisma/client';
@ -17,7 +16,7 @@ export const MarketState = {
export interface IOrder { export interface IOrder {
account: Account; account: Account;
currency: Currency; currency: string;
date: string; date: string;
fee: number; fee: number;
id?: string; id?: string;
@ -38,7 +37,7 @@ export interface IDataProviderResponse {
assetClass?: AssetClass; assetClass?: AssetClass;
assetSubClass?: AssetSubClass; assetSubClass?: AssetSubClass;
countries?: { code: string; weight: number }[]; countries?: { code: string; weight: number }[];
currency: Currency; currency: string;
dataSource: DataSource; dataSource: DataSource;
exchange?: string; exchange?: string;
marketChange?: number; marketChange?: number;

9
apps/api/src/services/interfaces/symbol-profile.interface.ts

@ -1,17 +1,12 @@
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';
import { import { AssetClass, AssetSubClass, DataSource } from '@prisma/client';
AssetClass,
AssetSubClass,
Currency,
DataSource
} from '@prisma/client';
export interface EnhancedSymbolProfile { export interface EnhancedSymbolProfile {
assetClass: AssetClass; assetClass: AssetClass;
assetSubClass: AssetSubClass; assetSubClass: AssetSubClass;
createdAt: Date; createdAt: Date;
currency: Currency | null; currency: string | null;
dataSource: DataSource; dataSource: DataSource;
id: string; id: string;
name: string | null; name: string | null;

3
apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts

@ -8,7 +8,6 @@ import {
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { PortfolioPerformance } from '@ghostfolio/common/interfaces'; import { PortfolioPerformance } from '@ghostfolio/common/interfaces';
import { Currency } from '@prisma/client';
import { CountUp } from 'countup.js'; import { CountUp } from 'countup.js';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
@ -19,7 +18,7 @@ import { isNumber } from 'lodash';
styleUrls: ['./portfolio-performance.component.scss'] styleUrls: ['./portfolio-performance.component.scss']
}) })
export class PortfolioPerformanceComponent implements OnChanges, OnInit { export class PortfolioPerformanceComponent implements OnChanges, OnInit {
@Input() baseCurrency: Currency; @Input() baseCurrency: string;
@Input() isLoading: boolean; @Input() isLoading: boolean;
@Input() locale: string; @Input() locale: string;
@Input() performance: PortfolioPerformance; @Input() performance: PortfolioPerformance;

3
apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts

@ -6,7 +6,6 @@ import {
OnInit OnInit
} from '@angular/core'; } from '@angular/core';
import { PortfolioSummary } from '@ghostfolio/common/interfaces'; import { PortfolioSummary } from '@ghostfolio/common/interfaces';
import { Currency } from '@prisma/client';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
@Component({ @Component({
@ -16,7 +15,7 @@ import { formatDistanceToNow } from 'date-fns';
styleUrls: ['./portfolio-summary.component.scss'] styleUrls: ['./portfolio-summary.component.scss']
}) })
export class PortfolioSummaryComponent implements OnChanges, OnInit { export class PortfolioSummaryComponent implements OnChanges, OnInit {
@Input() baseCurrency: Currency; @Input() baseCurrency: string;
@Input() isLoading: boolean; @Input() isLoading: boolean;
@Input() locale: string; @Input() locale: string;
@Input() summary: PortfolioSummary; @Input() summary: PortfolioSummary;

3
apps/client/src/app/components/world-map-chart/world-map-chart.component.ts

@ -7,7 +7,6 @@ import {
OnDestroy, OnDestroy,
OnInit OnInit
} from '@angular/core'; } from '@angular/core';
import { Currency } from '@prisma/client';
import svgMap from 'svgmap'; import svgMap from 'svgmap';
@Component({ @Component({
@ -17,7 +16,7 @@ import svgMap from 'svgmap';
styleUrls: ['./world-map-chart.component.scss'] styleUrls: ['./world-map-chart.component.scss']
}) })
export class WorldMapChartComponent implements OnChanges, OnDestroy, OnInit { export class WorldMapChartComponent implements OnChanges, OnDestroy, OnInit {
@Input() baseCurrency: Currency; @Input() baseCurrency: string;
@Input() countries: { [code: string]: { name: string; value: number } }; @Input() countries: { [code: string]: { name: string; value: number } };
public isLoading = true; public isLoading = true;

3
apps/client/src/app/pages/account/account-page.component.ts

@ -15,7 +15,6 @@ import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service';
import { DEFAULT_DATE_FORMAT, baseCurrency } from '@ghostfolio/common/config'; import { DEFAULT_DATE_FORMAT, baseCurrency } from '@ghostfolio/common/config';
import { Access, User } from '@ghostfolio/common/interfaces'; import { Access, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { Currency } from '@prisma/client';
import { StripeService } from 'ngx-stripe'; import { StripeService } from 'ngx-stripe';
import { EMPTY, Subject } from 'rxjs'; import { EMPTY, Subject } from 'rxjs';
import { catchError, switchMap, takeUntil } from 'rxjs/operators'; import { catchError, switchMap, takeUntil } from 'rxjs/operators';
@ -33,7 +32,7 @@ export class AccountPageComponent implements OnDestroy, OnInit {
public baseCurrency = baseCurrency; public baseCurrency = baseCurrency;
public coupon: number; public coupon: number;
public couponId: string; public couponId: string;
public currencies: Currency[] = []; public currencies: string[] = [];
public defaultDateFormat = DEFAULT_DATE_FORMAT; public defaultDateFormat = DEFAULT_DATE_FORMAT;
public hasPermissionForSubscription: boolean; public hasPermissionForSubscription: boolean;
public hasPermissionToUpdateViewMode: boolean; public hasPermissionToUpdateViewMode: boolean;

5
apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts

@ -1,12 +1,10 @@
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef,
Component, Component,
Inject, Inject,
OnDestroy OnDestroy
} from '@angular/core'; } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Currency } from '@prisma/client';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { DataService } from '../../../services/data.service'; import { DataService } from '../../../services/data.service';
@ -20,13 +18,12 @@ import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces';
templateUrl: 'create-or-update-account-dialog.html' templateUrl: 'create-or-update-account-dialog.html'
}) })
export class CreateOrUpdateAccountDialog implements OnDestroy { export class CreateOrUpdateAccountDialog implements OnDestroy {
public currencies: Currency[] = []; public currencies: string[] = [];
public platforms: { id: string; name: string }[]; public platforms: { id: string; name: string }[];
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService, private dataService: DataService,
public dialogRef: MatDialogRef<CreateOrUpdateAccountDialog>, public dialogRef: MatDialogRef<CreateOrUpdateAccountDialog>,
@Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateAccountDialogParams @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateAccountDialogParams

3
apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts

@ -11,7 +11,6 @@ import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { Currency } from '@prisma/client';
import { isString } from 'lodash'; import { isString } from 'lodash';
import { EMPTY, Observable, Subject } from 'rxjs'; import { EMPTY, Observable, Subject } from 'rxjs';
import { import {
@ -35,7 +34,7 @@ import { CreateOrUpdateTransactionDialogParams } from './interfaces/interfaces';
export class CreateOrUpdateTransactionDialog implements OnDestroy { export class CreateOrUpdateTransactionDialog implements OnDestroy {
@ViewChild('autocomplete') autocomplete; @ViewChild('autocomplete') autocomplete;
public currencies: Currency[] = []; public currencies: string[] = [];
public currentMarketPrice = null; public currentMarketPrice = null;
public filteredLookupItems: LookupItem[]; public filteredLookupItems: LookupItem[];
public filteredLookupItemsObservable: Observable<LookupItem[]>; public filteredLookupItemsObservable: Observable<LookupItem[]>;

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

@ -1,31 +1,12 @@
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { Currency } from '@prisma/client';
import { DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
export const baseCurrency = Currency.USD; export const baseCurrency = 'USD';
export const benchmarks: Partial<IDataGatheringItem>[] = [ export const benchmarks: Partial<IDataGatheringItem>[] = [
{ dataSource: DataSource.YAHOO, symbol: 'VOO' } { dataSource: DataSource.YAHOO, symbol: 'VOO' }
]; ];
export const currencyPairs: Partial<
IDataGatheringItem & {
currency1: Currency;
currency2: Currency;
}
>[] = (Object.keys(Currency) as Array<keyof typeof Currency>)
.filter((currency) => {
return currency !== Currency.USD;
})
.map((currency) => {
return {
currency1: Currency.USD,
currency2: Currency[currency],
dataSource: DataSource.YAHOO,
symbol: `${Currency.USD}${Currency[currency]}`
};
});
export const ghostfolioScraperApiSymbolPrefix = '_GF_'; export const ghostfolioScraperApiSymbolPrefix = '_GF_';
export const ghostfolioCashSymbol = `${ghostfolioScraperApiSymbolPrefix}CASH`; export const ghostfolioCashSymbol = `${ghostfolioScraperApiSymbolPrefix}CASH`;
export const ghostfolioFearAndGreedIndexSymbol = `${ghostfolioScraperApiSymbolPrefix}FEAR_AND_GREED_INDEX`; export const ghostfolioFearAndGreedIndexSymbol = `${ghostfolioScraperApiSymbolPrefix}FEAR_AND_GREED_INDEX`;

11
libs/common/src/lib/helper.ts

@ -1,4 +1,3 @@
import { Currency } from '@prisma/client';
import { getDate, getMonth, getYear, parse, subDays } from 'date-fns'; import { getDate, getMonth, getYear, parse, subDays } from 'date-fns';
import { ghostfolioScraperApiSymbolPrefix } from './config'; import { ghostfolioScraperApiSymbolPrefix } from './config';
@ -87,9 +86,9 @@ export function isCrypto(aSymbol = '') {
export function isCurrency(aSymbol = '') { export function isCurrency(aSymbol = '') {
return ( return (
(aSymbol.includes(Currency.CHF) || (aSymbol.includes('CHF') ||
aSymbol.includes(Currency.EUR) || aSymbol.includes('EUR') ||
aSymbol.includes(Currency.USD)) && aSymbol.includes('USD')) &&
aSymbol.length >= 6 aSymbol.length >= 6
); );
} }
@ -102,10 +101,6 @@ export function isRakutenRapidApiSymbol(aSymbol = '') {
return aSymbol === 'GF.FEAR_AND_GREED_INDEX'; return aSymbol === 'GF.FEAR_AND_GREED_INDEX';
} }
export function parseCurrency(aCurrency: string): Currency {
return Currency[aCurrency];
}
export function resetHours(aDate: Date) { export function resetHours(aDate: Date) {
const year = getYear(aDate); const year = getYear(aDate);
const month = getMonth(aDate); const month = getMonth(aDate);

4
libs/common/src/lib/interfaces/info-item.interface.ts

@ -1,10 +1,8 @@
import { Currency } from '@prisma/client';
import { Statistics } from './statistics.interface'; import { Statistics } from './statistics.interface';
import { Subscription } from './subscription.interface'; import { Subscription } from './subscription.interface';
export interface InfoItem { export interface InfoItem {
currencies: Currency[]; currencies: string[];
demoAuthToken: string; demoAuthToken: string;
globalPermissions: string[]; globalPermissions: string[];
lastDataGathering?: Date; lastDataGathering?: Date;

4
libs/common/src/lib/interfaces/portfolio-position.interface.ts

@ -1,5 +1,5 @@
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces'; import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
import { AssetClass, AssetSubClass, Currency } from '@prisma/client'; import { AssetClass, AssetSubClass } from '@prisma/client';
import { Country } from './country.interface'; import { Country } from './country.interface';
import { Sector } from './sector.interface'; import { Sector } from './sector.interface';
@ -10,7 +10,7 @@ export interface PortfolioPosition {
assetClass?: AssetClass; assetClass?: AssetClass;
assetSubClass?: AssetSubClass | 'CASH'; assetSubClass?: AssetSubClass | 'CASH';
countries: Country[]; countries: Country[];
currency: Currency; currency: string;
exchange?: string; exchange?: string;
grossPerformance: number; grossPerformance: number;
grossPerformancePercent: number; grossPerformancePercent: number;

4
libs/common/src/lib/interfaces/position.interface.ts

@ -1,10 +1,10 @@
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces'; import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
import { AssetClass, Currency } from '@prisma/client'; import { AssetClass } from '@prisma/client';
export interface Position { export interface Position {
assetClass: AssetClass; assetClass: AssetClass;
averagePrice: number; averagePrice: number;
currency: Currency; currency: string;
firstBuyDate: string; firstBuyDate: string;
grossPerformance?: number; grossPerformance?: number;
grossPerformancePercentage?: number; grossPerformancePercentage?: number;

4
libs/common/src/lib/interfaces/timeline-position.interface.ts

@ -1,9 +1,9 @@
import { Currency, DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
import Big from 'big.js'; import Big from 'big.js';
export interface TimelinePosition { export interface TimelinePosition {
averagePrice: Big; averagePrice: Big;
currency: Currency; currency: string;
dataSource: DataSource; dataSource: DataSource;
firstBuyDate: string; firstBuyDate: string;
grossPerformance: Big; grossPerformance: Big;

4
libs/common/src/lib/interfaces/user-settings.interface.ts

@ -1,7 +1,7 @@
import { Currency, ViewMode } from '@prisma/client'; import { ViewMode } from '@prisma/client';
export interface UserSettings { export interface UserSettings {
baseCurrency?: Currency; baseCurrency?: string;
isRestrictedView?: boolean; isRestrictedView?: boolean;
locale: string; locale: string;
viewMode?: ViewMode; viewMode?: ViewMode;

3
libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts

@ -11,7 +11,6 @@ import {
import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { getTextColor } from '@ghostfolio/common/helper'; import { getTextColor } from '@ghostfolio/common/helper';
import { PortfolioPosition } from '@ghostfolio/common/interfaces'; import { PortfolioPosition } from '@ghostfolio/common/interfaces';
import { Currency } from '@prisma/client';
import { Tooltip } from 'chart.js'; import { Tooltip } from 'chart.js';
import { LinearScale } from 'chart.js'; import { LinearScale } from 'chart.js';
import { ArcElement } from 'chart.js'; import { ArcElement } from 'chart.js';
@ -29,7 +28,7 @@ import * as Color from 'color';
export class PortfolioProportionChartComponent export class PortfolioProportionChartComponent
implements AfterViewInit, OnChanges, OnDestroy implements AfterViewInit, OnChanges, OnDestroy
{ {
@Input() baseCurrency: Currency; @Input() baseCurrency: string;
@Input() isInPercent = false; @Input() isInPercent = false;
@Input() keys: string[] = []; @Input() keys: string[] = [];
@Input() locale = ''; @Input() locale = '';

14
prisma/migrations/20210921151004_changed_currency_from_enum_to_string/migration.sql

@ -0,0 +1,14 @@
-- AlterTable
ALTER TABLE "Account" ALTER COLUMN "currency" TYPE TEXT;
-- AlterTable
ALTER TABLE "Order" ALTER COLUMN "currency" TYPE TEXT;
-- AlterTable
ALTER TABLE "Settings" ALTER COLUMN "currency" TYPE TEXT;
-- AlterTable
ALTER TABLE "SymbolProfile" ALTER COLUMN "currency" TYPE TEXT;
-- DropEnum
DROP TYPE "Currency" CASCADE;

15
prisma/schema.prisma

@ -28,7 +28,7 @@ model Account {
accountType AccountType @default(SECURITIES) accountType AccountType @default(SECURITIES)
balance Float @default(0) balance Float @default(0)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
currency Currency @default(USD) currency String?
id String @default(uuid()) id String @default(uuid())
isDefault Boolean @default(false) isDefault Boolean @default(false)
name String? name String?
@ -77,7 +77,7 @@ model Order {
accountId String? accountId String?
accountUserId String? accountUserId String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
currency Currency? currency String?
dataSource DataSource dataSource DataSource
date DateTime date DateTime
fee Float fee Float
@ -109,7 +109,7 @@ model Property {
} }
model Settings { model Settings {
currency Currency? currency String?
settings Json? settings Json?
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
viewMode ViewMode? viewMode ViewMode?
@ -122,7 +122,7 @@ model SymbolProfile {
assetSubClass AssetSubClass? assetSubClass AssetSubClass?
countries Json? countries Json?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
currency Currency? currency String?
dataSource DataSource dataSource DataSource
id String @id @default(uuid()) id String @id @default(uuid())
name String? name String?
@ -182,13 +182,6 @@ enum AssetSubClass {
STOCK STOCK
} }
enum Currency {
CHF
EUR
GBP
USD
}
enum DataSource { enum DataSource {
ALPHA_VANTAGE ALPHA_VANTAGE
GHOSTFOLIO GHOSTFOLIO

25
prisma/seed.ts

@ -1,6 +1,5 @@
import { import {
AccountType, AccountType,
Currency,
DataSource, DataSource,
PrismaClient, PrismaClient,
Role, Role,
@ -88,7 +87,7 @@ async function main() {
{ {
accountType: AccountType.SECURITIES, accountType: AccountType.SECURITIES,
balance: 0, balance: 0,
currency: Currency.USD, currency: 'USD',
id: 'f4425b66-9ba9-4ac4-93d7-fdf9a145e8cb', id: 'f4425b66-9ba9-4ac4-93d7-fdf9a145e8cb',
isDefault: true, isDefault: true,
name: 'Default Account' name: 'Default Account'
@ -112,7 +111,7 @@ async function main() {
{ {
accountType: AccountType.SECURITIES, accountType: AccountType.SECURITIES,
balance: 0, balance: 0,
currency: Currency.USD, currency: 'USD',
id: 'd804de69-0429-42dc-b6ca-b308fd7dd926', id: 'd804de69-0429-42dc-b6ca-b308fd7dd926',
name: 'Coinbase Account', name: 'Coinbase Account',
platformId: platformCoinbase.id platformId: platformCoinbase.id
@ -120,7 +119,7 @@ async function main() {
{ {
accountType: AccountType.SECURITIES, accountType: AccountType.SECURITIES,
balance: 0, balance: 0,
currency: Currency.EUR, currency: 'EUR',
id: '65cfb79d-b6c7-4591-9d46-73426bc62094', id: '65cfb79d-b6c7-4591-9d46-73426bc62094',
name: 'DEGIRO Account', name: 'DEGIRO Account',
platformId: platformDegiro.id platformId: platformDegiro.id
@ -128,7 +127,7 @@ async function main() {
{ {
accountType: AccountType.SECURITIES, accountType: AccountType.SECURITIES,
balance: 0, balance: 0,
currency: Currency.USD, currency: 'USD',
id: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', id: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
isDefault: true, isDefault: true,
name: 'Interactive Brokers Account', name: 'Interactive Brokers Account',
@ -201,7 +200,7 @@ async function main() {
{ {
accountId: '65cfb79d-b6c7-4591-9d46-73426bc62094', accountId: '65cfb79d-b6c7-4591-9d46-73426bc62094',
accountUserId: userDemo.id, accountUserId: userDemo.id,
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
date: new Date(Date.UTC(2017, 0, 3, 0, 0, 0)), date: new Date(Date.UTC(2017, 0, 3, 0, 0, 0)),
fee: 30, fee: 30,
@ -216,7 +215,7 @@ async function main() {
{ {
accountId: 'd804de69-0429-42dc-b6ca-b308fd7dd926', accountId: 'd804de69-0429-42dc-b6ca-b308fd7dd926',
accountUserId: userDemo.id, accountUserId: userDemo.id,
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
date: new Date(Date.UTC(2017, 7, 16, 0, 0, 0)), date: new Date(Date.UTC(2017, 7, 16, 0, 0, 0)),
fee: 29.9, fee: 29.9,
@ -231,7 +230,7 @@ async function main() {
{ {
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
accountUserId: userDemo.id, accountUserId: userDemo.id,
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
date: new Date(Date.UTC(2018, 9, 1, 0, 0, 0)), date: new Date(Date.UTC(2018, 9, 1, 0, 0, 0)),
fee: 80.79, fee: 80.79,
@ -246,7 +245,7 @@ async function main() {
{ {
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
accountUserId: userDemo.id, accountUserId: userDemo.id,
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
date: new Date(Date.UTC(2019, 2, 1, 0, 0, 0)), date: new Date(Date.UTC(2019, 2, 1, 0, 0, 0)),
fee: 19.9, fee: 19.9,
@ -261,7 +260,7 @@ async function main() {
{ {
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
accountUserId: userDemo.id, accountUserId: userDemo.id,
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
date: new Date(Date.UTC(2019, 8, 3, 0, 0, 0)), date: new Date(Date.UTC(2019, 8, 3, 0, 0, 0)),
fee: 19.9, fee: 19.9,
@ -276,7 +275,7 @@ async function main() {
{ {
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
accountUserId: userDemo.id, accountUserId: userDemo.id,
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
date: new Date(Date.UTC(2020, 2, 2, 0, 0, 0)), date: new Date(Date.UTC(2020, 2, 2, 0, 0, 0)),
fee: 19.9, fee: 19.9,
@ -291,7 +290,7 @@ async function main() {
{ {
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
accountUserId: userDemo.id, accountUserId: userDemo.id,
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
date: new Date(Date.UTC(2020, 8, 1, 0, 0, 0)), date: new Date(Date.UTC(2020, 8, 1, 0, 0, 0)),
fee: 19.9, fee: 19.9,
@ -306,7 +305,7 @@ async function main() {
{ {
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
accountUserId: userDemo.id, accountUserId: userDemo.id,
currency: Currency.USD, currency: 'USD',
dataSource: DataSource.YAHOO, dataSource: DataSource.YAHOO,
date: new Date(Date.UTC(2020, 2, 1, 0, 0, 0)), date: new Date(Date.UTC(2020, 2, 1, 0, 0, 0)),
fee: 19.9, fee: 19.9,

Loading…
Cancel
Save