From 982ba7377a0f99485c625a62c2f99b88ebdb471b Mon Sep 17 00:00:00 2001 From: Valentin Zickner Date: Sun, 1 Aug 2021 00:38:49 +0200 Subject: [PATCH] remove legacy portfolio.ts --- .../experimental/experimental.controller.ts | 22 - .../app/experimental/experimental.service.ts | 44 - apps/api/src/models/portfolio.spec.ts | 478 ---------- apps/api/src/models/portfolio.ts | 872 ------------------ 4 files changed, 1416 deletions(-) delete mode 100644 apps/api/src/models/portfolio.spec.ts delete mode 100644 apps/api/src/models/portfolio.ts diff --git a/apps/api/src/app/experimental/experimental.controller.ts b/apps/api/src/app/experimental/experimental.controller.ts index 0736a0012..0851e4be4 100644 --- a/apps/api/src/app/experimental/experimental.controller.ts +++ b/apps/api/src/app/experimental/experimental.controller.ts @@ -66,26 +66,4 @@ export class ExperimentalController { return marketData; } - - @Post('value/:dateString?') - public async getValue( - @Body() orders: CreateOrderDto[], - @Headers('Authorization') apiToken: string, - @Param('dateString') dateString: string - ): Promise { - if (!isApiTokenAuthorized(apiToken)) { - throw new HttpException( - getReasonPhrase(StatusCodes.FORBIDDEN), - StatusCodes.FORBIDDEN - ); - } - - let date = new Date(); - - if (dateString) { - date = parse(dateString, DATE_FORMAT, new Date()); - } - - return this.experimentalService.getValue(orders, date, baseCurrency); - } } diff --git a/apps/api/src/app/experimental/experimental.service.ts b/apps/api/src/app/experimental/experimental.service.ts index eda248f52..edbe7bce3 100644 --- a/apps/api/src/app/experimental/experimental.service.ts +++ b/apps/api/src/app/experimental/experimental.service.ts @@ -1,16 +1,9 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { Portfolio } from '@ghostfolio/api/models/portfolio'; import { DataProviderService } from '@ghostfolio/api/services/data-provider.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { RulesService } from '@ghostfolio/api/services/rules.service'; -import { OrderWithAccount } from '@ghostfolio/common/types'; import { Injectable } from '@nestjs/common'; -import { Currency, Type } from '@prisma/client'; -import { parseISO } from 'date-fns'; - -import { CreateOrderDto } from './create-order.dto'; -import { Data } from './interfaces/data.interface'; @Injectable() export class ExperimentalService { @@ -29,41 +22,4 @@ export class ExperimentalService { where: { symbol: aSymbol } }); } - - public async getValue( - aOrders: CreateOrderDto[], - aDate: Date, - aBaseCurrency: Currency - ): Promise { - const ordersWithPlatform: OrderWithAccount[] = aOrders.map((order) => { - return { - ...order, - accountId: undefined, - accountUserId: undefined, - createdAt: new Date(), - dataSource: undefined, - date: parseISO(order.date), - fee: 0, - id: undefined, - platformId: undefined, - symbolProfileId: undefined, - type: Type.BUY, - updatedAt: undefined, - userId: undefined - }; - }); - - const portfolio = new Portfolio( - this.accountService, - this.dataProviderService, - this.exchangeRateDataService, - this.rulesService - ); - await portfolio.setOrders(ordersWithPlatform); - - return { - currency: aBaseCurrency, - value: portfolio.getValue(aDate) - }; - } } diff --git a/apps/api/src/models/portfolio.spec.ts b/apps/api/src/models/portfolio.spec.ts deleted file mode 100644 index cf28763a7..000000000 --- a/apps/api/src/models/portfolio.spec.ts +++ /dev/null @@ -1,478 +0,0 @@ -import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { UNKNOWN_KEY, baseCurrency } from '@ghostfolio/common/config'; -import { DATE_FORMAT, getUtc, getYesterday } from '@ghostfolio/common/helper'; -import { - AccountType, - Currency, - DataSource, - Role, - Type, - ViewMode -} from '@prisma/client'; -import { format } from 'date-fns'; - -import { DataProviderService } from '../services/data-provider.service'; -import { ExchangeRateDataService } from '../services/exchange-rate-data.service'; -import { MarketState } from '../services/interfaces/interfaces'; -import { RulesService } from '../services/rules.service'; -import { Portfolio } from './portfolio'; - -jest.mock('../app/account/account.service', () => { - return { - AccountService: jest.fn().mockImplementation(() => { - return { - getCashDetails: () => Promise.resolve({ accounts: [], balance: 0 }) - }; - }) - }; -}); - -jest.mock('../services/data-provider.service', () => { - return { - DataProviderService: jest.fn().mockImplementation(() => { - const today = format(new Date(), DATE_FORMAT); - const yesterday = format(getYesterday(), DATE_FORMAT); - - return { - get: () => { - return Promise.resolve({ - BTCUSD: { - currency: Currency.USD, - dataSource: DataSource.YAHOO, - exchange: UNKNOWN_KEY, - marketPrice: 57973.008, - marketState: MarketState.open, - name: 'Bitcoin USD', - type: 'Cryptocurrency' - }, - ETHUSD: { - currency: Currency.USD, - dataSource: DataSource.YAHOO, - exchange: UNKNOWN_KEY, - marketPrice: 3915.337, - marketState: MarketState.open, - name: 'Ethereum USD', - type: 'Cryptocurrency' - } - }); - }, - getHistorical: () => { - return Promise.resolve({ - BTCUSD: { - [yesterday]: 56710.122, - [today]: 57973.008 - }, - ETHUSD: { - [yesterday]: 3641.984, - [today]: 3915.337 - } - }); - } - }; - }) - }; -}); - -jest.mock('../services/exchange-rate-data.service', () => { - return { - ExchangeRateDataService: jest.fn().mockImplementation(() => { - return { - initialize: () => Promise.resolve(), - toCurrency: (value: number) => { - return 1 * value; - } - }; - }) - }; -}); - -jest.mock('../services/rules.service'); - -const DEFAULT_ACCOUNT_ID = '693a834b-eb89-42c9-ae47-35196c25d269'; -const USER_ID = 'ca6ce867-5d31-495a-bce9-5942bbca9237'; - -describe('Portfolio', () => { - let accountService: AccountService; - let dataProviderService: DataProviderService; - let exchangeRateDataService: ExchangeRateDataService; - let portfolio: Portfolio; - let rulesService: RulesService; - - beforeAll(async () => { - accountService = new AccountService(null, null, null); - dataProviderService = new DataProviderService( - null, - null, - null, - null, - null, - null - ); - exchangeRateDataService = new ExchangeRateDataService(null); - rulesService = new RulesService(); - - await exchangeRateDataService.initialize(); - - portfolio = new Portfolio( - accountService, - dataProviderService, - exchangeRateDataService, - rulesService - ); - portfolio.setUser({ - accessToken: null, - Account: [ - { - accountType: AccountType.SECURITIES, - balance: 0, - createdAt: new Date(), - currency: Currency.USD, - id: DEFAULT_ACCOUNT_ID, - isDefault: true, - name: 'Default Account', - platformId: null, - updatedAt: new Date(), - userId: USER_ID - } - ], - alias: 'Test', - authChallenge: null, - createdAt: new Date(), - id: USER_ID, - provider: null, - role: Role.USER, - Settings: { - currency: Currency.CHF, - updatedAt: new Date(), - userId: USER_ID, - viewMode: ViewMode.DEFAULT - }, - thirdPartyId: null, - updatedAt: new Date() - }); - }); - - describe('works with no orders', () => { - it('should return []', () => { - expect(portfolio.get(new Date())).toEqual([]); - expect(portfolio.getFees()).toEqual(0); - expect(portfolio.getPositions(new Date())).toEqual({}); - }); - - it('should return empty details', async () => { - const details = await portfolio.getDetails('1d'); - expect(details).toMatchObject({ - _GF_CASH: { - accounts: {}, - allocationCurrent: NaN, // TODO - allocationInvestment: NaN, // TODO - countries: [], - currency: 'CHF', - grossPerformance: 0, - grossPerformancePercent: 0, - investment: 0, - marketPrice: 0, - marketState: 'open', - name: 'Cash', - quantity: 0, - sectors: [], - symbol: '_GF_CASH', - transactionCount: 0, - type: 'Cash', - value: 0 - } - }); - }); - - it('should return empty details', async () => { - const details = await portfolio.getDetails('max'); - expect(details).toMatchObject({ - _GF_CASH: { - accounts: {}, - allocationCurrent: NaN, // TODO - allocationInvestment: NaN, // TODO - countries: [], - currency: 'CHF', - grossPerformance: 0, - grossPerformancePercent: 0, - investment: 0, - marketPrice: 0, - marketState: 'open', - name: 'Cash', - quantity: 0, - sectors: [], - symbol: '_GF_CASH', - transactionCount: 0, - type: 'Cash', - value: 0 - } - }); - }); - }); - - describe('works with orders', () => { - it('should return ["ETHUSD"]', async () => { - await portfolio.setOrders([ - { - accountId: DEFAULT_ACCOUNT_ID, - accountUserId: USER_ID, - createdAt: null, - currency: Currency.USD, - dataSource: DataSource.YAHOO, - fee: 0, - date: new Date(getUtc('2018-01-05')), - id: '4a5a5c6e-659d-45cc-9fd4-fd6c873b50fb', - quantity: 0.2, - symbol: 'ETHUSD', - symbolProfileId: null, - type: Type.BUY, - unitPrice: 991.49, - updatedAt: null, - userId: USER_ID - } - ]); - - expect(portfolio.getFees()).toEqual(0); - - expect(portfolio.getPositions(getYesterday())).toMatchObject({ - ETHUSD: { - averagePrice: 991.49, - currency: Currency.USD, - firstBuyDate: '2018-01-05T00:00:00.000Z', - investment: exchangeRateDataService.toCurrency( - 0.2 * 991.49, - Currency.USD, - baseCurrency - ), - investmentInOriginalCurrency: 0.2 * 991.49, - // marketPrice: 3915.337, - quantity: 0.2 - } - }); - - expect(portfolio.getSymbols(getYesterday())).toEqual(['ETHUSD']); - }); - - it('should return ["ETHUSD"]', async () => { - await portfolio.setOrders([ - { - accountId: DEFAULT_ACCOUNT_ID, - accountUserId: USER_ID, - createdAt: null, - currency: Currency.USD, - dataSource: DataSource.YAHOO, - fee: 0, - date: new Date(getUtc('2018-01-05')), - id: '4a5a5c6e-659d-45cc-9fd4-fd6c873b50fb', - quantity: 0.2, - symbol: 'ETHUSD', - symbolProfileId: null, - type: Type.BUY, - unitPrice: 991.49, - updatedAt: null, - userId: USER_ID - }, - { - accountId: DEFAULT_ACCOUNT_ID, - accountUserId: USER_ID, - createdAt: null, - currency: Currency.USD, - dataSource: DataSource.YAHOO, - fee: 0, - date: new Date(getUtc('2018-01-28')), - id: '4a5a5c6e-659d-45cc-9fd4-fd6c873b50fc', - quantity: 0.3, - symbol: 'ETHUSD', - symbolProfileId: null, - type: Type.BUY, - unitPrice: 1050, - updatedAt: null, - userId: USER_ID - } - ]); - - expect(portfolio.getFees()).toEqual(0); - - expect(portfolio.getPositions(getYesterday())).toMatchObject({ - ETHUSD: { - averagePrice: (0.2 * 991.49 + 0.3 * 1050) / (0.2 + 0.3), - currency: Currency.USD, - firstBuyDate: '2018-01-05T00:00:00.000Z', - investment: - exchangeRateDataService.toCurrency( - 0.2 * 991.49, - Currency.USD, - baseCurrency - ) + - exchangeRateDataService.toCurrency( - 0.3 * 1050, - Currency.USD, - baseCurrency - ), - investmentInOriginalCurrency: 0.2 * 991.49 + 0.3 * 1050, - // marketPrice: 3641.984, - quantity: 0.5 - } - }); - - expect(portfolio.getSymbols(getYesterday())).toEqual(['ETHUSD']); - }); - - it('should return ["BTCUSD", "ETHUSD"]', async () => { - await portfolio.setOrders([ - { - accountId: DEFAULT_ACCOUNT_ID, - accountUserId: USER_ID, - createdAt: null, - currency: Currency.EUR, - dataSource: DataSource.YAHOO, - date: new Date(getUtc('2017-08-16')), - fee: 2.99, - id: 'd96795b2-6ae6-420e-aa21-fabe5e45d475', - quantity: 0.05614682, - symbol: 'BTCUSD', - symbolProfileId: null, - type: Type.BUY, - unitPrice: 3562.089535970158, - updatedAt: null, - userId: USER_ID - }, - { - accountId: DEFAULT_ACCOUNT_ID, - accountUserId: USER_ID, - createdAt: null, - currency: Currency.USD, - dataSource: DataSource.YAHOO, - fee: 2.99, - date: new Date(getUtc('2018-01-05')), - id: '4a5a5c6e-659d-45cc-9fd4-fd6c873b50fb', - quantity: 0.2, - symbol: 'ETHUSD', - symbolProfileId: null, - type: Type.BUY, - unitPrice: 991.49, - updatedAt: null, - userId: USER_ID - } - ]); - - expect(portfolio.getFees()).toEqual( - exchangeRateDataService.toCurrency(2.99, Currency.EUR, baseCurrency) + - exchangeRateDataService.toCurrency(2.99, Currency.USD, baseCurrency) - ); - - expect(portfolio.getPositions(getYesterday())).toMatchObject({ - BTCUSD: { - averagePrice: 3562.089535970158, - currency: Currency.EUR, - firstBuyDate: '2017-08-16T00:00:00.000Z', - investment: exchangeRateDataService.toCurrency( - 0.05614682 * 3562.089535970158, - Currency.EUR, - baseCurrency - ), - investmentInOriginalCurrency: 0.05614682 * 3562.089535970158, - // marketPrice: 0, - quantity: 0.05614682 - }, - ETHUSD: { - averagePrice: 991.49, - currency: Currency.USD, - firstBuyDate: '2018-01-05T00:00:00.000Z', - investment: exchangeRateDataService.toCurrency( - 0.2 * 991.49, - Currency.USD, - baseCurrency - ), - investmentInOriginalCurrency: 0.2 * 991.49, - // marketPrice: 0, - quantity: 0.2 - } - }); - - expect(portfolio.getSymbols(getYesterday())).toEqual([ - 'BTCUSD', - 'ETHUSD' - ]); - }); - - it('should work with buy and sell', async () => { - await portfolio.setOrders([ - { - accountId: DEFAULT_ACCOUNT_ID, - accountUserId: USER_ID, - createdAt: null, - currency: Currency.USD, - dataSource: DataSource.YAHOO, - fee: 1.0, - date: new Date(getUtc('2018-01-05')), - id: '4a5a5c6e-659d-45cc-9fd4-fd6c873b50fb', - quantity: 0.2, - symbol: 'ETHUSD', - symbolProfileId: null, - type: Type.BUY, - unitPrice: 991.49, - updatedAt: null, - userId: USER_ID - }, - { - accountId: DEFAULT_ACCOUNT_ID, - accountUserId: USER_ID, - createdAt: null, - currency: Currency.USD, - dataSource: DataSource.YAHOO, - fee: 1.0, - date: new Date(getUtc('2018-01-28')), - id: '4a5a5c6e-659d-45cc-9fd4-fd6c873b50fc', - quantity: 0.1, - symbol: 'ETHUSD', - symbolProfileId: null, - type: Type.SELL, - unitPrice: 1050, - updatedAt: null, - userId: USER_ID - }, - { - accountId: DEFAULT_ACCOUNT_ID, - accountUserId: USER_ID, - createdAt: null, - currency: Currency.USD, - dataSource: DataSource.YAHOO, - fee: 1.0, - date: new Date(getUtc('2018-01-31')), - id: '4a5a5c6e-659d-45cc-9fd4-fd6c873b50fc', - quantity: 0.2, - symbol: 'ETHUSD', - symbolProfileId: null, - type: Type.BUY, - unitPrice: 1050, - updatedAt: null, - userId: USER_ID - } - ]); - - expect(portfolio.getFees()).toEqual( - exchangeRateDataService.toCurrency(3, Currency.USD, baseCurrency) - ); - - expect(portfolio.getPositions(getYesterday())).toMatchObject({ - ETHUSD: { - averagePrice: - (0.2 * 991.49 - 0.1 * 1050 + 0.2 * 1050) / (0.2 - 0.1 + 0.2), - currency: Currency.USD, - firstBuyDate: '2018-01-05T00:00:00.000Z', - investment: exchangeRateDataService.toCurrency( - 0.2 * 991.49 - 0.1 * 1050 + 0.2 * 1050, - Currency.USD, - baseCurrency - ), - investmentInOriginalCurrency: 0.2 * 991.49 - 0.1 * 1050 + 0.2 * 1050, - // marketPrice: 0, - quantity: 0.2 - 0.1 + 0.2 - } - }); - - expect(portfolio.getSymbols(getYesterday())).toEqual(['ETHUSD']); - }); - }); -}); diff --git a/apps/api/src/models/portfolio.ts b/apps/api/src/models/portfolio.ts deleted file mode 100644 index b416f9872..000000000 --- a/apps/api/src/models/portfolio.ts +++ /dev/null @@ -1,872 +0,0 @@ -import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details.interface'; -import { UNKNOWN_KEY, ghostfolioCashSymbol } from '@ghostfolio/common/config'; -import { - DATE_FORMAT, - getToday, - getYesterday, - resetHours -} from '@ghostfolio/common/helper'; -import { - PortfolioItem, - PortfolioPerformance, - PortfolioPosition, - PortfolioReport, - Position, - UserWithSettings -} from '@ghostfolio/common/interfaces'; -import { Country } from '@ghostfolio/common/interfaces/country.interface'; -import { Sector } from '@ghostfolio/common/interfaces/sector.interface'; -import { DateRange, OrderWithAccount } from '@ghostfolio/common/types'; -import { Currency, Prisma } from '@prisma/client'; -import { continents, countries } from 'countries-list'; -import { - add, - format, - getDate, - getMonth, - getYear, - isAfter, - isBefore, - isSameDay, - isToday, - isYesterday, - parseISO, - setDate, - setMonth, - sub -} from 'date-fns'; -import { cloneDeep, isEmpty } from 'lodash'; -import * as roundTo from 'round-to'; - -import { DataProviderService } from '../services/data-provider.service'; -import { ExchangeRateDataService } from '../services/exchange-rate-data.service'; -import { IOrder, MarketState, Type } from '../services/interfaces/interfaces'; -import { RulesService } from '../services/rules.service'; -import { PortfolioInterface } from './interfaces/portfolio.interface'; -import { Order } from './order'; -import { OrderType } from './order-type'; -import { AccountClusterRiskCurrentInvestment } from './rules/account-cluster-risk/current-investment'; -import { AccountClusterRiskInitialInvestment } from './rules/account-cluster-risk/initial-investment'; -import { AccountClusterRiskSingleAccount } from './rules/account-cluster-risk/single-account'; -import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from './rules/currency-cluster-risk/base-currency-current-investment'; -import { CurrencyClusterRiskBaseCurrencyInitialInvestment } from './rules/currency-cluster-risk/base-currency-initial-investment'; -import { CurrencyClusterRiskCurrentInvestment } from './rules/currency-cluster-risk/current-investment'; -import { CurrencyClusterRiskInitialInvestment } from './rules/currency-cluster-risk/initial-investment'; -import { FeeRatioInitialInvestment } from './rules/fees/fee-ratio-initial-investment'; - -export class Portfolio implements PortfolioInterface { - private orders: Order[] = []; - private portfolioItems: PortfolioItem[] = []; - private user: UserWithSettings; - - public constructor( - private accountService: AccountService, - private dataProviderService: DataProviderService, - private exchangeRateDataService: ExchangeRateDataService, - private rulesService: RulesService - ) {} - - public async addCurrentPortfolioItems() { - const currentData = await this.dataProviderService.get(this.getSymbols()); - - const currentDate = new Date(); - - const year = getYear(currentDate); - const month = getMonth(currentDate); - const day = getDate(currentDate); - - const today = new Date(Date.UTC(year, month, day)); - const yesterday = getYesterday(); - - const [portfolioItemsYesterday] = this.get(yesterday); - - const positions: { [symbol: string]: Position } = {}; - - this.getSymbols().forEach((symbol) => { - positions[symbol] = { - symbol, - averagePrice: portfolioItemsYesterday?.positions[symbol]?.averagePrice, - currency: portfolioItemsYesterday?.positions[symbol]?.currency, - firstBuyDate: portfolioItemsYesterday?.positions[symbol]?.firstBuyDate, - investment: portfolioItemsYesterday?.positions[symbol]?.investment, - investmentInOriginalCurrency: - portfolioItemsYesterday?.positions[symbol] - ?.investmentInOriginalCurrency, - marketPrice: - currentData[symbol]?.marketPrice ?? - portfolioItemsYesterday.positions[symbol]?.marketPrice, - quantity: portfolioItemsYesterday?.positions[symbol]?.quantity, - transactionCount: - portfolioItemsYesterday?.positions[symbol]?.transactionCount - }; - }); - - if (portfolioItemsYesterday?.investment) { - const portfolioItemsLength = this.portfolioItems.push( - cloneDeep({ - date: today.toISOString(), - grossPerformancePercent: 0, - investment: portfolioItemsYesterday?.investment, - positions: positions, - value: 0 - }) - ); - - // Set value after pushing today's portfolio items - this.portfolioItems[portfolioItemsLength - 1].value = - this.getValue(today); - } - - return this; - } - - public async addFuturePortfolioItems() { - let investment = this.getInvestment(new Date()); - - this.getOrders() - .filter((order) => order.getIsDraft() === true) - .forEach((order) => { - investment += this.exchangeRateDataService.toCurrency( - order.getTotal(), - order.getCurrency(), - this.user.Settings.currency - ); - - const portfolioItem = this.portfolioItems.find((item) => { - return item.date === order.getDate(); - }); - - if (portfolioItem) { - portfolioItem.investment = investment; - } else { - this.portfolioItems.push({ - investment, - date: order.getDate(), - grossPerformancePercent: 0, - positions: {}, - value: 0 - }); - } - }); - - return this; - } - - public createFromData({ - orders, - portfolioItems, - user - }: { - orders: IOrder[]; - portfolioItems: PortfolioItem[]; - user: UserWithSettings; - }): Portfolio { - orders.forEach( - ({ - account, - currency, - fee, - date, - id, - quantity, - symbol, - symbolProfile, - type, - unitPrice - }) => { - this.orders.push( - new Order({ - account, - currency, - fee, - date, - id, - quantity, - symbol, - symbolProfile, - type, - unitPrice - }) - ); - } - ); - - portfolioItems.forEach( - ({ date, grossPerformancePercent, investment, positions, value }) => { - this.portfolioItems.push({ - date, - grossPerformancePercent, - investment, - positions, - value - }); - } - ); - - this.setUser(user); - - return this; - } - - public get(aDate?: Date): PortfolioItem[] { - if (aDate) { - const filteredPortfolio = this.portfolioItems.find((item) => { - return isSameDay(aDate, new Date(item.date)); - }); - - if (filteredPortfolio) { - return [cloneDeep(filteredPortfolio)]; - } - - return []; - } - - return cloneDeep(this.portfolioItems); - } - - public async getDetails( - aDateRange: DateRange = 'max' - ): Promise<{ [symbol: string]: PortfolioPosition }> { - const dateRangeDate = this.convertDateRangeToDate( - aDateRange, - this.getMinDate() - ); - - const [portfolioItemsBefore] = this.get(dateRangeDate); - - const [portfolioItemsNow] = await this.get(new Date()); - - const cashDetails = await this.accountService.getCashDetails( - this.user.id, - this.user.Settings.currency - ); - const investment = this.getInvestment(new Date()) + cashDetails.balance; - const portfolioItems = this.get(new Date()); - const symbols = this.getSymbols(new Date()); - const value = this.getValue() + cashDetails.balance; - - const details: { [symbol: string]: PortfolioPosition } = {}; - - const data = await this.dataProviderService.get(symbols); - - symbols.forEach((symbol) => { - const accounts: PortfolioPosition['accounts'] = {}; - let countriesOfSymbol: Country[]; - let sectorsOfSymbol: Sector[]; - const [portfolioItem] = portfolioItems; - - const ordersBySymbol = this.getOrders().filter((order) => { - return order.getSymbol() === symbol; - }); - - ordersBySymbol.forEach((orderOfSymbol) => { - let currentValueOfSymbol = this.exchangeRateDataService.toCurrency( - orderOfSymbol.getQuantity() * - portfolioItemsNow.positions[symbol].marketPrice, - orderOfSymbol.getCurrency(), - this.user.Settings.currency - ); - let originalValueOfSymbol = this.exchangeRateDataService.toCurrency( - orderOfSymbol.getQuantity() * orderOfSymbol.getUnitPrice(), - orderOfSymbol.getCurrency(), - this.user.Settings.currency - ); - - if (orderOfSymbol.getType() === 'SELL') { - currentValueOfSymbol *= -1; - originalValueOfSymbol *= -1; - } - - if ( - accounts[orderOfSymbol.getAccount()?.name || UNKNOWN_KEY]?.current - ) { - accounts[orderOfSymbol.getAccount()?.name || UNKNOWN_KEY].current += - currentValueOfSymbol; - accounts[orderOfSymbol.getAccount()?.name || UNKNOWN_KEY].original += - originalValueOfSymbol; - } else { - accounts[orderOfSymbol.getAccount()?.name || UNKNOWN_KEY] = { - current: currentValueOfSymbol, - original: originalValueOfSymbol - }; - } - - countriesOfSymbol = ( - (orderOfSymbol.getSymbolProfile()?.countries as Prisma.JsonArray) ?? - [] - ).map((country) => { - const { code, weight } = country as Prisma.JsonObject; - - return { - code: code as string, - continent: - continents[countries[code as string]?.continent] ?? UNKNOWN_KEY, - name: countries[code as string]?.name ?? UNKNOWN_KEY, - weight: weight as number - }; - }); - - sectorsOfSymbol = ( - (orderOfSymbol.getSymbolProfile()?.sectors as Prisma.JsonArray) ?? [] - ).map((sector) => { - const { name, weight } = sector as Prisma.JsonObject; - - return { - name: (name as string) ?? UNKNOWN_KEY, - weight: weight as number - }; - }); - }); - - let now = portfolioItemsNow.positions[symbol].marketPrice; - - // 1d - let before = portfolioItemsBefore?.positions[symbol].marketPrice; - - if (aDateRange === 'ytd') { - before = - portfolioItemsBefore.positions[symbol].marketPrice || - portfolioItemsNow.positions[symbol].averagePrice; - } else if ( - aDateRange === '1y' || - aDateRange === '5y' || - aDateRange === 'max' - ) { - before = portfolioItemsNow.positions[symbol].averagePrice; - } - - if ( - !isBefore( - parseISO(portfolioItemsNow.positions[symbol].firstBuyDate), - parseISO(portfolioItemsBefore?.date) - ) - ) { - // Trade was not before the date of portfolioItemsBefore, then override it with average price - // (e.g. on same day) - before = portfolioItemsNow.positions[symbol].averagePrice; - } - - if (isToday(parseISO(portfolioItemsNow.positions[symbol].firstBuyDate))) { - now = portfolioItemsNow.positions[symbol].averagePrice; - } - - details[symbol] = { - ...data[symbol], - accounts, - symbol, - allocationCurrent: - this.exchangeRateDataService.toCurrency( - portfolioItem.positions[symbol].quantity * now, - data[symbol]?.currency, - this.user.Settings.currency - ) / value, - allocationInvestment: - portfolioItem.positions[symbol].investment / investment, - countries: countriesOfSymbol, - grossPerformance: roundTo( - portfolioItemsNow.positions[symbol].quantity * (now - before), - 2 - ), - grossPerformancePercent: roundTo((now - before) / before, 4), - investment: portfolioItem.positions[symbol].investment, - quantity: portfolioItem.positions[symbol].quantity, - sectors: sectorsOfSymbol, - transactionCount: portfolioItem.positions[symbol].transactionCount, - value: this.exchangeRateDataService.toCurrency( - portfolioItem.positions[symbol].quantity * now, - data[symbol]?.currency, - this.user.Settings.currency - ) - }; - }); - - details[ghostfolioCashSymbol] = await this.getCashPosition({ - cashDetails, - investment, - value - }); - - return details; - } - - public getFees(aDate = new Date(0)) { - return this.orders - .filter((order) => { - // Filter out all orders before given date - return isBefore(aDate, new Date(order.getDate())); - }) - .map((order) => { - return this.exchangeRateDataService.toCurrency( - order.getFee(), - order.getCurrency(), - this.user.Settings.currency - ); - }) - .reduce((previous, current) => previous + current, 0); - } - - public getInvestment(aDate: Date): number { - return this.get(aDate)[0]?.investment || 0; - } - - public getMinDate() { - const orders = this.getOrders().filter( - (order) => order.getIsDraft() === false - ); - - if (orders.length > 0) { - return new Date(this.orders[0].getDate()); - } - - return null; - } - - public getPositions(aDate: Date) { - const [portfolioItem] = this.get(aDate); - - if (portfolioItem) { - return portfolioItem.positions; - } - - return {}; - } - - public getPortfolioItems() { - return this.portfolioItems; - } - - public getSymbols(aDate?: Date) { - let symbols: string[] = []; - - if (aDate) { - const positions = this.getPositions(aDate); - - for (const symbol in positions) { - if (positions[symbol].quantity > 0) { - symbols.push(symbol); - } - } - } else { - symbols = this.orders - .filter((order) => order.getIsDraft() === false) - .map((order) => { - return order.getSymbol(); - }); - } - - // unique values - return Array.from(new Set(symbols)); - } - - public getTotalBuy() { - return this.orders - .filter( - (order) => order.getIsDraft() === false && order.getType() === 'BUY' - ) - .map((order) => { - return this.exchangeRateDataService.toCurrency( - order.getTotal(), - order.getCurrency(), - this.user.Settings.currency - ); - }) - .reduce((previous, current) => previous + current, 0); - } - - public getTotalSell() { - return this.orders - .filter( - (order) => order.getIsDraft() === false && order.getType() === 'SELL' - ) - .map((order) => { - return this.exchangeRateDataService.toCurrency( - order.getTotal(), - order.getCurrency(), - this.user.Settings.currency - ); - }) - .reduce((previous, current) => previous + current, 0); - } - - public getOrders(aSymbol?: string) { - if (aSymbol) { - return this.orders.filter((order) => { - return order.getSymbol() === aSymbol; - }); - } - - return this.orders; - } - - public getValue(aDate = getToday()) { - const positions = this.getPositions(aDate); - let value = 0; - - const [portfolioItem] = this.get(aDate); - - for (const symbol in positions) { - if (portfolioItem.positions[symbol]?.quantity > 0) { - if ( - isBefore( - aDate, - parseISO(portfolioItem.positions[symbol]?.firstBuyDate) - ) || - portfolioItem.positions[symbol]?.marketPrice === 0 - ) { - value += this.exchangeRateDataService.toCurrency( - portfolioItem.positions[symbol]?.quantity * - portfolioItem.positions[symbol]?.averagePrice, - portfolioItem.positions[symbol]?.currency, - this.user.Settings.currency - ); - } else { - value += this.exchangeRateDataService.toCurrency( - portfolioItem.positions[symbol]?.quantity * - portfolioItem.positions[symbol]?.marketPrice, - portfolioItem.positions[symbol]?.currency, - this.user.Settings.currency - ); - } - } - } - - return isFinite(value) ? value : null; - } - - public async setOrders(aOrders: OrderWithAccount[]) { - this.orders = []; - - // Map data - aOrders.forEach((order) => { - this.orders.push( - new Order({ - account: order.Account, - currency: order.currency, - date: order.date.toISOString(), - fee: order.fee, - quantity: order.quantity, - symbol: order.symbol, - symbolProfile: order.SymbolProfile, - type: order.type, - unitPrice: order.unitPrice - }) - ); - }); - - await this.update(); - - return this; - } - - public setUser(aUser: UserWithSettings) { - this.user = aUser; - - return this; - } - - private async getCashPosition({ - cashDetails, - investment, - value - }: { - cashDetails: CashDetails; - investment: number; - value: number; - }) { - const accounts = {}; - const cashValue = cashDetails.balance; - - cashDetails.accounts.forEach((account) => { - accounts[account.name] = { - current: account.balance, - original: account.balance - }; - }); - - return { - accounts, - allocationCurrent: cashValue / value, - allocationInvestment: cashValue / investment, - countries: [], - currency: Currency.CHF, - grossPerformance: 0, - grossPerformancePercent: 0, - investment: cashValue, - marketPrice: 0, - marketState: MarketState.open, - name: Type.Cash, - quantity: 0, - sectors: [], - symbol: ghostfolioCashSymbol, - type: Type.Cash, - transactionCount: 0, - value: cashValue - }; - } - - /** - * TODO: Refactor - */ - private async update() { - this.portfolioItems = []; - - let currentDate = this.getMinDate(); - - if (!currentDate) { - return; - } - - // Set current date to first of month - currentDate = setDate(currentDate, 1); - - const historicalData = await this.dataProviderService.getHistorical( - this.getSymbols(), - 'month', - currentDate, - new Date() - ); - - while (isBefore(currentDate, Date.now())) { - const positions: { [symbol: string]: Position } = {}; - this.getSymbols().forEach((symbol) => { - positions[symbol] = { - symbol, - averagePrice: 0, - currency: undefined, - firstBuyDate: null, - investment: 0, - investmentInOriginalCurrency: 0, - marketPrice: - historicalData[symbol]?.[format(currentDate, DATE_FORMAT)] - ?.marketPrice || 0, - quantity: 0, - transactionCount: 0 - }; - }); - - if (!isYesterday(currentDate) && !isToday(currentDate)) { - // Add to portfolio (ignore yesterday and today because they are added later) - this.portfolioItems.push( - cloneDeep({ - date: currentDate.toISOString(), - grossPerformancePercent: 0, - investment: 0, - positions: positions, - value: 0 - }) - ); - } - - const year = getYear(currentDate); - const month = getMonth(currentDate); - const day = getDate(currentDate); - - // Count month one up for iteration - currentDate = new Date(Date.UTC(year, month + 1, day, 0)); - } - - const yesterday = getYesterday(); - - const positions: { [symbol: string]: Position } = {}; - - if (isAfter(yesterday, this.getMinDate())) { - // Add yesterday - this.getSymbols().forEach((symbol) => { - positions[symbol] = { - symbol, - averagePrice: 0, - currency: undefined, - firstBuyDate: null, - investment: 0, - investmentInOriginalCurrency: 0, - marketPrice: - historicalData[symbol]?.[format(yesterday, DATE_FORMAT)] - ?.marketPrice || 0, - name: '', - quantity: 0, - transactionCount: 0 - }; - }); - - this.portfolioItems.push( - cloneDeep({ - positions, - date: yesterday.toISOString(), - grossPerformancePercent: 0, - investment: 0, - value: 0 - }) - ); - } - - this.updatePortfolioItems(); - } - - private convertDateRangeToDate(aDateRange: DateRange, aMinDate: Date) { - let currentDate = new Date(); - - const normalizedMinDate = - getDate(aMinDate) === 1 - ? aMinDate - : add(setDate(aMinDate, 1), { months: 1 }); - - const year = getYear(currentDate); - const month = getMonth(currentDate); - const day = getDate(currentDate); - - currentDate = new Date(Date.UTC(year, month, day, 0)); - - switch (aDateRange) { - case '1d': - return sub(currentDate, { - days: 1 - }); - case 'ytd': - currentDate = setDate(currentDate, 1); - currentDate = setMonth(currentDate, 0); - return isAfter(currentDate, normalizedMinDate) - ? currentDate - : undefined; - case '1y': - currentDate = setDate(currentDate, 1); - currentDate = sub(currentDate, { - years: 1 - }); - return isAfter(currentDate, normalizedMinDate) - ? currentDate - : undefined; - case '5y': - currentDate = setDate(currentDate, 1); - currentDate = sub(currentDate, { - years: 5 - }); - return isAfter(currentDate, normalizedMinDate) - ? currentDate - : undefined; - default: - // Gets handled as all data - return undefined; - } - } - - private updatePortfolioItems() { - let currentDate = new Date(); - - const year = getYear(currentDate); - const month = getMonth(currentDate); - const day = getDate(currentDate); - - currentDate = new Date(Date.UTC(year, month, day, 0)); - - if (this.portfolioItems?.length === 1) { - // At least one portfolio items is needed, keep it but change the date to today. - // This happens if there are only orders from today - this.portfolioItems[0].date = currentDate.toISOString(); - } else { - // Only keep entries which are not before first buy date - this.portfolioItems = this.portfolioItems.filter((portfolioItem) => { - return ( - isSameDay(parseISO(portfolioItem.date), this.getMinDate()) || - isAfter(parseISO(portfolioItem.date), this.getMinDate()) - ); - }); - } - - this.orders.forEach((order) => { - if (order.getIsDraft() === false) { - let index = this.portfolioItems.findIndex((item) => { - const dateOfOrder = setDate(parseISO(order.getDate()), 1); - return isSameDay(parseISO(item.date), dateOfOrder); - }); - - if (index === -1) { - // if not found, we only have one order, which means we do not loop below - index = 0; - } - - for (let i = index; i < this.portfolioItems.length; i++) { - // Set currency - this.portfolioItems[i].positions[order.getSymbol()].currency = - order.getCurrency(); - - this.portfolioItems[i].positions[ - order.getSymbol() - ].transactionCount += 1; - - if (order.getType() === 'BUY') { - if ( - !this.portfolioItems[i].positions[order.getSymbol()].firstBuyDate - ) { - this.portfolioItems[i].positions[order.getSymbol()].firstBuyDate = - resetHours(parseISO(order.getDate())).toISOString(); - } - - this.portfolioItems[i].positions[order.getSymbol()].quantity += - order.getQuantity(); - this.portfolioItems[i].positions[order.getSymbol()].investment += - this.exchangeRateDataService.toCurrency( - order.getTotal(), - order.getCurrency(), - this.user.Settings.currency - ); - this.portfolioItems[i].positions[ - order.getSymbol() - ].investmentInOriginalCurrency += order.getTotal(); - - this.portfolioItems[i].investment += - this.exchangeRateDataService.toCurrency( - order.getTotal(), - order.getCurrency(), - this.user.Settings.currency - ); - } else if (order.getType() === 'SELL') { - this.portfolioItems[i].positions[order.getSymbol()].quantity -= - order.getQuantity(); - - if ( - this.portfolioItems[i].positions[order.getSymbol()].quantity === 0 - ) { - this.portfolioItems[i].positions[ - order.getSymbol() - ].investment = 0; - this.portfolioItems[i].positions[ - order.getSymbol() - ].investmentInOriginalCurrency = 0; - } else { - this.portfolioItems[i].positions[order.getSymbol()].investment -= - this.exchangeRateDataService.toCurrency( - order.getTotal(), - order.getCurrency(), - this.user.Settings.currency - ); - this.portfolioItems[i].positions[ - order.getSymbol() - ].investmentInOriginalCurrency -= order.getTotal(); - } - - this.portfolioItems[i].investment -= - this.exchangeRateDataService.toCurrency( - order.getTotal(), - order.getCurrency(), - this.user.Settings.currency - ); - } - - this.portfolioItems[i].positions[order.getSymbol()].averagePrice = - this.portfolioItems[i].positions[order.getSymbol()] - .investmentInOriginalCurrency / - this.portfolioItems[i].positions[order.getSymbol()].quantity; - - const currentValue = this.getValue( - parseISO(this.portfolioItems[i].date) - ); - - this.portfolioItems[i].grossPerformancePercent = - currentValue / this.portfolioItems[i].investment - 1 || 0; - this.portfolioItems[i].value = currentValue; - } - } - }); - } -}