mirror of https://github.com/ghostfolio/ghostfolio
Valentin Zickner
4 years ago
committed by
Thomas
6 changed files with 720 additions and 0 deletions
@ -0,0 +1,60 @@ |
|||||
|
import { CurrentRateService } from '@ghostfolio/api/app/core/current-rate.service'; |
||||
|
import { Currency } from '@prisma/client'; |
||||
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; |
||||
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service'; |
||||
|
|
||||
|
jest.mock('../../services/exchange-rate-data.service', () => { |
||||
|
return { |
||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
|
exchangeRateDataService: jest.fn().mockImplementation(() => { |
||||
|
return { |
||||
|
toCurrency: (aValue: number, |
||||
|
aFromCurrency: Currency, |
||||
|
aToCurrency: Currency) => { |
||||
|
return 1 * aValue; |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
// https://jestjs.io/docs/manual-mocks#mocking-node-modules
|
||||
|
// jest.mock('?', () => {
|
||||
|
// return {
|
||||
|
// // eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
|
// prismaService: jest.fn().mockImplementation(() => {
|
||||
|
// return {
|
||||
|
// marketData: {
|
||||
|
// findFirst: (data: any) => {
|
||||
|
// return {
|
||||
|
// marketPrice: 100
|
||||
|
// };
|
||||
|
// }
|
||||
|
// }
|
||||
|
// };
|
||||
|
// })
|
||||
|
// };
|
||||
|
// });
|
||||
|
|
||||
|
xdescribe('CurrentRateService', () => { |
||||
|
|
||||
|
let exchangeRateDataService: ExchangeRateDataService; |
||||
|
let prismaService: PrismaService; |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
exchangeRateDataService = new ExchangeRateDataService(undefined); |
||||
|
prismaService = new PrismaService(); |
||||
|
}); |
||||
|
|
||||
|
it('getValue', () => { |
||||
|
const currentRateService = new CurrentRateService(exchangeRateDataService, prismaService); |
||||
|
|
||||
|
expect(currentRateService.getValue({ |
||||
|
date: new Date(), |
||||
|
symbol: 'AIA', |
||||
|
currency: Currency.USD, |
||||
|
userCurrency: Currency.CHF |
||||
|
})).toEqual(0); |
||||
|
}); |
||||
|
|
||||
|
}); |
@ -0,0 +1,37 @@ |
|||||
|
import { Currency } from '@prisma/client'; |
||||
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service'; |
||||
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; |
||||
|
|
||||
|
export class CurrentRateService { |
||||
|
|
||||
|
public constructor( |
||||
|
private readonly exchangeRateDataService: ExchangeRateDataService, |
||||
|
private prisma: PrismaService |
||||
|
) {} |
||||
|
|
||||
|
/** |
||||
|
* TODO: @dtslvr |
||||
|
*/ |
||||
|
public async getValue({date, symbol, currency, userCurrency}: GetValueParams): Promise<number> { |
||||
|
const marketData = await this.prisma.marketData.findFirst({ |
||||
|
select: { date: true, marketPrice: true }, |
||||
|
where: { date: date, symbol: symbol } |
||||
|
}); |
||||
|
|
||||
|
console.log(marketData); // { date: Date, marketPrice: number }
|
||||
|
|
||||
|
return this.exchangeRateDataService.toCurrency( |
||||
|
marketData.marketPrice, |
||||
|
currency, |
||||
|
userCurrency |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
export interface GetValueParams { |
||||
|
date: Date; |
||||
|
symbol: string; |
||||
|
currency: Currency; |
||||
|
userCurrency: Currency; |
||||
|
} |
@ -0,0 +1,459 @@ |
|||||
|
import { PortfolioCalculator, PortfolioOrder } from '@ghostfolio/api/app/core/portfolio-calculator'; |
||||
|
import { CurrentRateService } from '@ghostfolio/api/app/core/current-rate.service'; |
||||
|
import { Currency } from '@prisma/client'; |
||||
|
import { OrderType } from '@ghostfolio/api/models/order-type'; |
||||
|
import Big from 'big.js'; |
||||
|
|
||||
|
jest.mock('./current-rate.service.ts', () => { |
||||
|
return { |
||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
|
CurrentRateService: jest.fn().mockImplementation(() => { |
||||
|
return { |
||||
|
getValue: (date: Date, symbol: string, currency: Currency) => { |
||||
|
return 4; |
||||
|
} |
||||
|
}; |
||||
|
}) |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
describe('PortfolioCalculator', () => { |
||||
|
|
||||
|
let currentRateService: CurrentRateService; |
||||
|
beforeEach(() => { |
||||
|
currentRateService = new CurrentRateService(null, null); |
||||
|
}); |
||||
|
|
||||
|
describe('calculate transaction points', () => { |
||||
|
it('with orders of only one symbol', () => { |
||||
|
const portfolioCalculator = new PortfolioCalculator(currentRateService, Currency.USD, ordersVTI); |
||||
|
const portfolioItemsAtTransactionPoints = portfolioCalculator.getPortfolioItemsAtTransactionPoints(); |
||||
|
|
||||
|
expect(portfolioItemsAtTransactionPoints).toEqual([ |
||||
|
{ |
||||
|
date: '2019-02-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('10'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('1443.8'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2019-08-03', |
||||
|
items: [{ |
||||
|
quantity: new Big('20'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('2923.7'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2020-02-02', |
||||
|
items: [{ |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('652.55'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2021-02-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('15'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('2684.05'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2021-08-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('25'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('4460.95'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
} |
||||
|
]); |
||||
|
}); |
||||
|
|
||||
|
it('with two orders at the same day of the same type', () => { |
||||
|
const orders = [ |
||||
|
...ordersVTI, |
||||
|
{ |
||||
|
date: '2021-02-01', |
||||
|
quantity: new Big('20'), |
||||
|
symbol: 'VTI', |
||||
|
type: OrderType.Buy, |
||||
|
unitPrice: new Big('197.15'), |
||||
|
currency: Currency.USD |
||||
|
} |
||||
|
]; |
||||
|
const portfolioCalculator = new PortfolioCalculator(currentRateService, Currency.USD, orders); |
||||
|
const portfolioItemsAtTransactionPoints = portfolioCalculator.getPortfolioItemsAtTransactionPoints(); |
||||
|
|
||||
|
expect(portfolioItemsAtTransactionPoints).toEqual([ |
||||
|
{ |
||||
|
date: '2019-02-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('10'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('1443.8'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2019-08-03', |
||||
|
items: [{ |
||||
|
quantity: new Big('20'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('2923.7'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2020-02-02', |
||||
|
items: [{ |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('652.55'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2021-02-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('35'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('6627.05'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2021-08-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('45'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('8403.95'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
} |
||||
|
]); |
||||
|
}); |
||||
|
|
||||
|
it('with additional order', () => { |
||||
|
const orders = [ |
||||
|
...ordersVTI, |
||||
|
{ |
||||
|
date: '2019-09-01', |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'AMZN', |
||||
|
type: OrderType.Buy, |
||||
|
unitPrice: new Big('2021.99'), |
||||
|
currency: Currency.USD |
||||
|
} |
||||
|
]; |
||||
|
const portfolioCalculator = new PortfolioCalculator(currentRateService, Currency.USD, orders); |
||||
|
const portfolioItemsAtTransactionPoints = portfolioCalculator.getPortfolioItemsAtTransactionPoints(); |
||||
|
|
||||
|
expect(portfolioItemsAtTransactionPoints).toEqual([ |
||||
|
{ |
||||
|
date: '2019-02-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('10'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('1443.8'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2019-08-03', |
||||
|
items: [{ |
||||
|
quantity: new Big('20'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('2923.7'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2019-09-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'AMZN', |
||||
|
investment: new Big('10109.95'), |
||||
|
currency: Currency.USD |
||||
|
}, { |
||||
|
quantity: new Big('20'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('2923.7'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2020-02-02', |
||||
|
items: [{ |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'AMZN', |
||||
|
investment: new Big('10109.95'), |
||||
|
currency: Currency.USD |
||||
|
}, { |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('652.55'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2021-02-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'AMZN', |
||||
|
investment: new Big('10109.95'), |
||||
|
currency: Currency.USD |
||||
|
}, { |
||||
|
quantity: new Big('15'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('2684.05'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2021-08-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'AMZN', |
||||
|
investment: new Big('10109.95'), |
||||
|
currency: Currency.USD |
||||
|
}, { |
||||
|
quantity: new Big('25'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('4460.95'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
} |
||||
|
]); |
||||
|
}); |
||||
|
|
||||
|
it('with additional buy & sell', () => { |
||||
|
const orders = [ |
||||
|
...ordersVTI, |
||||
|
{ |
||||
|
date: '2019-09-01', |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'AMZN', |
||||
|
type: OrderType.Buy, |
||||
|
unitPrice: new Big('2021.99'), |
||||
|
currency: Currency.USD |
||||
|
}, |
||||
|
{ |
||||
|
date: '2020-08-02', |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'AMZN', |
||||
|
type: OrderType.Sell, |
||||
|
unitPrice: new Big('2412.23'), |
||||
|
currency: Currency.USD |
||||
|
} |
||||
|
]; |
||||
|
const portfolioCalculator = new PortfolioCalculator(currentRateService, Currency.USD, orders); |
||||
|
const portfolioItemsAtTransactionPoints = portfolioCalculator.getPortfolioItemsAtTransactionPoints(); |
||||
|
|
||||
|
expect(portfolioItemsAtTransactionPoints).toEqual([ |
||||
|
{ |
||||
|
date: '2019-02-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('10'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('1443.8'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2019-08-03', |
||||
|
items: [{ |
||||
|
quantity: new Big('20'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('2923.7'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2019-09-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'AMZN', |
||||
|
investment: new Big('10109.95'), |
||||
|
currency: Currency.USD |
||||
|
}, { |
||||
|
quantity: new Big('20'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('2923.7'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2020-02-02', |
||||
|
items: [{ |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'AMZN', |
||||
|
investment: new Big('10109.95'), |
||||
|
currency: Currency.USD |
||||
|
}, { |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('652.55'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2020-08-02', |
||||
|
items: [{ |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('652.55'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2021-02-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('15'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('2684.05'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2021-08-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('25'), |
||||
|
symbol: 'VTI', |
||||
|
investment: new Big('4460.95'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
} |
||||
|
]); |
||||
|
}); |
||||
|
|
||||
|
it('with mixed symbols', () => { |
||||
|
const portfolioCalculator = new PortfolioCalculator(currentRateService, Currency.USD, ordersMixedSymbols); |
||||
|
const portfolioItemsAtTransactionPoints = portfolioCalculator.getPortfolioItemsAtTransactionPoints(); |
||||
|
|
||||
|
expect(portfolioItemsAtTransactionPoints).toEqual([ |
||||
|
{ |
||||
|
date: '2017-01-03', |
||||
|
items: [{ |
||||
|
quantity: new Big('50'), |
||||
|
symbol: 'TSLA', |
||||
|
investment: new Big('2148.5'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2017-07-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('0.5614682'), |
||||
|
symbol: 'BTCUSD', |
||||
|
investment: new Big('1999.9999999999998659756'), |
||||
|
currency: Currency.USD |
||||
|
}, { |
||||
|
quantity: new Big('50'), |
||||
|
symbol: 'TSLA', |
||||
|
investment: new Big('2148.5'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
}, |
||||
|
{ |
||||
|
date: '2018-09-01', |
||||
|
items: [{ |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'AMZN', |
||||
|
investment: new Big('10109.95'), |
||||
|
currency: Currency.USD |
||||
|
}, { |
||||
|
quantity: new Big('0.5614682'), |
||||
|
symbol: 'BTCUSD', |
||||
|
investment: new Big('1999.9999999999998659756'), |
||||
|
currency: Currency.USD |
||||
|
}, { |
||||
|
quantity: new Big('50'), |
||||
|
symbol: 'TSLA', |
||||
|
investment: new Big('2148.5'), |
||||
|
currency: Currency.USD |
||||
|
}] |
||||
|
} |
||||
|
]); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
const ordersMixedSymbols: PortfolioOrder[] = [ |
||||
|
{ |
||||
|
date: '2017-01-03', |
||||
|
quantity: new Big('50'), |
||||
|
symbol: 'TSLA', |
||||
|
type: OrderType.Buy, |
||||
|
unitPrice: new Big('42.97'), |
||||
|
currency: Currency.USD |
||||
|
}, |
||||
|
{ |
||||
|
date: '2017-07-01', |
||||
|
quantity: new Big('0.5614682'), |
||||
|
symbol: 'BTCUSD', |
||||
|
type: OrderType.Buy, |
||||
|
unitPrice: new Big('3562.089535970158'), |
||||
|
currency: Currency.USD |
||||
|
}, |
||||
|
{ |
||||
|
date: '2018-09-01', |
||||
|
quantity: new Big('5'), |
||||
|
symbol: 'AMZN', |
||||
|
type: OrderType.Buy, |
||||
|
unitPrice: new Big('2021.99'), |
||||
|
currency: Currency.USD |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
const ordersVTI: PortfolioOrder[] = [ |
||||
|
{ |
||||
|
date: '2019-02-01', |
||||
|
quantity: new Big('10'), |
||||
|
symbol: 'VTI', |
||||
|
type: OrderType.Buy, |
||||
|
unitPrice: new Big('144.38'), |
||||
|
currency: Currency.USD |
||||
|
}, |
||||
|
{ |
||||
|
date: '2019-08-03', |
||||
|
quantity: new Big('10'), |
||||
|
symbol: 'VTI', |
||||
|
type: OrderType.Buy, |
||||
|
unitPrice: new Big('147.99'), |
||||
|
currency: Currency.USD |
||||
|
}, |
||||
|
{ |
||||
|
date: '2020-02-02', |
||||
|
quantity: new Big('15'), |
||||
|
symbol: 'VTI', |
||||
|
type: OrderType.Sell, |
||||
|
unitPrice: new Big('151.41'), |
||||
|
currency: Currency.USD |
||||
|
}, |
||||
|
{ |
||||
|
date: '2021-08-01', |
||||
|
quantity: new Big('10'), |
||||
|
symbol: 'VTI', |
||||
|
type: OrderType.Buy, |
||||
|
unitPrice: new Big('177.69'), |
||||
|
currency: Currency.USD |
||||
|
}, |
||||
|
{ |
||||
|
date: '2021-02-01', |
||||
|
quantity: new Big('10'), |
||||
|
symbol: 'VTI', |
||||
|
type: OrderType.Buy, |
||||
|
unitPrice: new Big('203.15'), |
||||
|
currency: Currency.USD |
||||
|
} |
||||
|
]; |
@ -0,0 +1,154 @@ |
|||||
|
import { Currency } from '@prisma/client'; |
||||
|
import { CurrentRateService } from '@ghostfolio/api/app/core/current-rate.service'; |
||||
|
import { OrderType } from '@ghostfolio/api/models/order-type'; |
||||
|
import Big from 'big.js'; |
||||
|
|
||||
|
export class PortfolioCalculator { |
||||
|
|
||||
|
private transactionPoints: TransactionPoint[]; |
||||
|
|
||||
|
constructor( |
||||
|
private currentRateService: CurrentRateService, |
||||
|
private currency: Currency, |
||||
|
orders: PortfolioOrder[] |
||||
|
) { |
||||
|
this.computeTransactionPoints(orders); |
||||
|
} |
||||
|
|
||||
|
addOrder(order: PortfolioOrder): void { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
deleteOrder(order: PortfolioOrder): void { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
getPortfolioItemsAtTransactionPoints(): TransactionPoint[] { |
||||
|
return this.transactionPoints; |
||||
|
} |
||||
|
|
||||
|
getCurrentPositions(): { [symbol: string]: TimelinePosition } { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
calculateTimeline(timelineSpecification: TimelineSpecification[], endDate: Date): TimelinePeriod[] { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
private computeTransactionPoints(orders: PortfolioOrder[]) { |
||||
|
orders.sort((a, b) => a.date.localeCompare(b.date)); |
||||
|
|
||||
|
this.transactionPoints = []; |
||||
|
const symbols: { [symbol: string]: TransactionPointSymbol } = {}; |
||||
|
|
||||
|
let lastDate: string = null; |
||||
|
let lastTransactionPoint: TransactionPoint = null; |
||||
|
for (const order of orders) { |
||||
|
const currentDate = order.date; |
||||
|
|
||||
|
let currentTransactionPointItem: TransactionPointSymbol; |
||||
|
const oldAccumulatedSymbol = symbols[order.symbol]; |
||||
|
|
||||
|
const factor = this.getFactor(order.type); |
||||
|
const unitPrice = new Big(order.unitPrice); |
||||
|
if (oldAccumulatedSymbol) { |
||||
|
currentTransactionPointItem = { |
||||
|
quantity: order.quantity.mul(factor).plus(oldAccumulatedSymbol.quantity), |
||||
|
symbol: order.symbol, |
||||
|
investment: unitPrice.mul(order.quantity).mul(factor).add(oldAccumulatedSymbol.investment), |
||||
|
currency: order.currency |
||||
|
}; |
||||
|
} else { |
||||
|
currentTransactionPointItem = { |
||||
|
quantity: order.quantity.mul(factor), |
||||
|
symbol: order.symbol, |
||||
|
investment: unitPrice.mul(order.quantity).mul(factor), |
||||
|
currency: order.currency |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
symbols[order.symbol] = currentTransactionPointItem; |
||||
|
|
||||
|
const items = lastTransactionPoint?.items ?? []; |
||||
|
const newItems = items.filter(transactionPointItem => transactionPointItem.symbol !== order.symbol); |
||||
|
if (!currentTransactionPointItem.quantity.eq(0)) { |
||||
|
newItems.push(currentTransactionPointItem); |
||||
|
} else { |
||||
|
delete symbols[order.symbol]; |
||||
|
} |
||||
|
newItems.sort((a, b) => a.symbol.localeCompare(b.symbol)); |
||||
|
if (lastDate !== currentDate || lastTransactionPoint === null) { |
||||
|
lastTransactionPoint = { |
||||
|
date: currentDate, |
||||
|
items: newItems |
||||
|
}; |
||||
|
this.transactionPoints.push(lastTransactionPoint); |
||||
|
} else { |
||||
|
lastTransactionPoint.items = newItems; |
||||
|
} |
||||
|
lastDate = currentDate; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private getFactor(type: OrderType) { |
||||
|
let factor: number; |
||||
|
switch (type) { |
||||
|
case OrderType.Buy: |
||||
|
factor = 1; |
||||
|
break; |
||||
|
case OrderType.Sell: |
||||
|
factor = -1; |
||||
|
break; |
||||
|
default: |
||||
|
factor = 0; |
||||
|
break; |
||||
|
} |
||||
|
return factor; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
interface TransactionPoint { |
||||
|
date: string; |
||||
|
items: TransactionPointSymbol[]; |
||||
|
} |
||||
|
|
||||
|
interface TransactionPointSymbol { |
||||
|
quantity: Big; |
||||
|
symbol: string; |
||||
|
investment: Big; |
||||
|
currency: Currency; |
||||
|
} |
||||
|
|
||||
|
interface TimelinePosition { |
||||
|
averagePrice: Big; |
||||
|
firstBuyDate: string; |
||||
|
quantity: Big; |
||||
|
symbol: string; |
||||
|
investment: Big; |
||||
|
marketPrice: number; |
||||
|
transactionCount: number; |
||||
|
} |
||||
|
|
||||
|
type Accuracy = 'year' | 'month' | 'day'; |
||||
|
|
||||
|
interface TimelineSpecification { |
||||
|
start: Date; |
||||
|
accuracy: Accuracy |
||||
|
} |
||||
|
|
||||
|
interface TimelinePeriod { |
||||
|
date: Date; |
||||
|
grossPerformance: number; |
||||
|
investment: number; |
||||
|
value: number; |
||||
|
} |
||||
|
|
||||
|
export interface PortfolioOrder { |
||||
|
date: string; |
||||
|
quantity: Big; |
||||
|
symbol: string; |
||||
|
type: OrderType; |
||||
|
unitPrice: Big; |
||||
|
currency: Currency; |
||||
|
} |
Loading…
Reference in new issue