Co-authored-by: Thomas <4159106+dtslvr@users.noreply.github.com>pull/239/head
parent
5c0f710563
commit
9de56c32ac
@ -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