Browse Source

Refactoring

pull/3203/head
Thomas Kaul 1 year ago
parent
commit
dd701eaba8
  1. 204
      apps/api/src/app/portfolio/portfolio-calculator.ts
  2. 79
      apps/api/src/app/portfolio/portfolio.service.ts

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

@ -81,99 +81,6 @@ export class PortfolioCalculator {
this.computeTransactionPoints(); this.computeTransactionPoints();
} }
private computeTransactionPoints() {
this.transactionPoints = [];
const symbols: { [symbol: string]: TransactionPointSymbol } = {};
let lastDate: string = null;
let lastTransactionPoint: TransactionPoint = null;
for (const order of this.orders) {
const currentDate = order.date;
let currentTransactionPointItem: TransactionPointSymbol;
const oldAccumulatedSymbol = symbols[order.SymbolProfile.symbol];
const factor = getFactor(order.type);
if (oldAccumulatedSymbol) {
let investment = oldAccumulatedSymbol.investment;
const newQuantity = order.quantity
.mul(factor)
.plus(oldAccumulatedSymbol.quantity);
if (order.type === 'BUY') {
investment = oldAccumulatedSymbol.investment.plus(
order.quantity.mul(order.unitPrice)
);
} else if (order.type === 'SELL') {
investment = oldAccumulatedSymbol.investment.minus(
order.quantity.mul(oldAccumulatedSymbol.averagePrice)
);
}
currentTransactionPointItem = {
investment,
averagePrice: newQuantity.gt(0)
? investment.div(newQuantity)
: new Big(0),
currency: order.SymbolProfile.currency,
dataSource: order.SymbolProfile.dataSource,
dividend: new Big(0),
fee: order.fee.plus(oldAccumulatedSymbol.fee),
firstBuyDate: oldAccumulatedSymbol.firstBuyDate,
quantity: newQuantity,
symbol: order.SymbolProfile.symbol,
tags: order.tags,
transactionCount: oldAccumulatedSymbol.transactionCount + 1
};
} else {
currentTransactionPointItem = {
averagePrice: order.unitPrice,
currency: order.SymbolProfile.currency,
dataSource: order.SymbolProfile.dataSource,
dividend: new Big(0),
fee: order.fee,
firstBuyDate: order.date,
investment: order.unitPrice.mul(order.quantity).mul(factor),
quantity: order.quantity.mul(factor),
symbol: order.SymbolProfile.symbol,
tags: order.tags,
transactionCount: 1
};
}
symbols[order.SymbolProfile.symbol] = currentTransactionPointItem;
const items = lastTransactionPoint?.items ?? [];
const newItems = items.filter(
(transactionPointItem) =>
transactionPointItem.symbol !== order.SymbolProfile.symbol
);
newItems.push(currentTransactionPointItem);
newItems.sort((a, b) => {
return 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;
}
}
public getAnnualizedPerformancePercent({ public getAnnualizedPerformancePercent({
daysInMarket, daysInMarket,
netPerformancePercent netPerformancePercent
@ -191,10 +98,6 @@ export class PortfolioCalculator {
return new Big(0); return new Big(0);
} }
public getTransactionPoints(): TransactionPoint[] {
return this.transactionPoints;
}
public async getChartData({ public async getChartData({
end = new Date(Date.now()), end = new Date(Date.now()),
start, start,
@ -258,7 +161,7 @@ export class PortfolioCalculator {
await this.exchangeRateDataService.getExchangeRatesByCurrency({ await this.exchangeRateDataService.getExchangeRatesByCurrency({
currencies: uniq(Object.values(currencies)), currencies: uniq(Object.values(currencies)),
endDate: endOfDay(end), endDate: endOfDay(end),
startDate: parseDate(this.transactionPoints?.[0]?.date), startDate: this.getStartDate(),
targetCurrency: this.currency targetCurrency: this.currency
}); });
@ -562,7 +465,7 @@ export class PortfolioCalculator {
await this.exchangeRateDataService.getExchangeRatesByCurrency({ await this.exchangeRateDataService.getExchangeRatesByCurrency({
currencies: uniq(Object.values(currencies)), currencies: uniq(Object.values(currencies)),
endDate: endOfDay(endDate), endDate: endOfDay(endDate),
startDate: parseDate(this.transactionPoints?.[0]?.date), startDate: this.getStartDate(),
targetCurrency: this.currency targetCurrency: this.currency
}); });
@ -856,6 +759,109 @@ export class PortfolioCalculator {
}; };
} }
public getStartDate() {
return this.transactionPoints.length > 0
? parseDate(this.transactionPoints[0].date)
: new Date();
}
public getTransactionPoints() {
return this.transactionPoints;
}
private computeTransactionPoints() {
this.transactionPoints = [];
const symbols: { [symbol: string]: TransactionPointSymbol } = {};
let lastDate: string = null;
let lastTransactionPoint: TransactionPoint = null;
for (const order of this.orders) {
const currentDate = order.date;
let currentTransactionPointItem: TransactionPointSymbol;
const oldAccumulatedSymbol = symbols[order.SymbolProfile.symbol];
const factor = getFactor(order.type);
if (oldAccumulatedSymbol) {
let investment = oldAccumulatedSymbol.investment;
const newQuantity = order.quantity
.mul(factor)
.plus(oldAccumulatedSymbol.quantity);
if (order.type === 'BUY') {
investment = oldAccumulatedSymbol.investment.plus(
order.quantity.mul(order.unitPrice)
);
} else if (order.type === 'SELL') {
investment = oldAccumulatedSymbol.investment.minus(
order.quantity.mul(oldAccumulatedSymbol.averagePrice)
);
}
currentTransactionPointItem = {
investment,
averagePrice: newQuantity.gt(0)
? investment.div(newQuantity)
: new Big(0),
currency: order.SymbolProfile.currency,
dataSource: order.SymbolProfile.dataSource,
dividend: new Big(0),
fee: order.fee.plus(oldAccumulatedSymbol.fee),
firstBuyDate: oldAccumulatedSymbol.firstBuyDate,
quantity: newQuantity,
symbol: order.SymbolProfile.symbol,
tags: order.tags,
transactionCount: oldAccumulatedSymbol.transactionCount + 1
};
} else {
currentTransactionPointItem = {
averagePrice: order.unitPrice,
currency: order.SymbolProfile.currency,
dataSource: order.SymbolProfile.dataSource,
dividend: new Big(0),
fee: order.fee,
firstBuyDate: order.date,
investment: order.unitPrice.mul(order.quantity).mul(factor),
quantity: order.quantity.mul(factor),
symbol: order.SymbolProfile.symbol,
tags: order.tags,
transactionCount: 1
};
}
symbols[order.SymbolProfile.symbol] = currentTransactionPointItem;
const items = lastTransactionPoint?.items ?? [];
const newItems = items.filter(
(transactionPointItem) =>
transactionPointItem.symbol !== order.SymbolProfile.symbol
);
newItems.push(currentTransactionPointItem);
newItems.sort((a, b) => {
return 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 getSymbolMetrics({ private getSymbolMetrics({
dataSource, dataSource,
end, end,

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

@ -266,14 +266,14 @@ export class PortfolioService {
}): Promise<PortfolioInvestments> { }): Promise<PortfolioInvestments> {
const userId = await this.getUserId(impersonationId, this.request.user.id); const userId = await this.getUserId(impersonationId, this.request.user.id);
const { activities, transactionPoints } = await this.getTransactionPoints({ const { activities } = await this.getTransactionPoints({
filters, filters,
userId, userId,
includeDrafts: true, includeDrafts: true,
types: ['BUY', 'SELL'] types: ['BUY', 'SELL']
}); });
if (transactionPoints.length === 0) { if (activities.length === 0) {
return { return {
investments: [], investments: [],
streaks: { currentStreak: 0, longestStreak: 0 } streaks: { currentStreak: 0, longestStreak: 0 }
@ -291,7 +291,6 @@ export class PortfolioService {
dateRange, dateRange,
impersonationId, impersonationId,
portfolioCalculator, portfolioCalculator,
transactionPoints,
userId, userId,
withDataDecimation: false withDataDecimation: false
}); });
@ -362,7 +361,7 @@ export class PortfolioService {
}); });
} }
const { activities, transactionPoints } = await this.getTransactionPoints({ const { activities } = await this.getTransactionPoints({
filters, filters,
types, types,
userId, userId,
@ -376,10 +375,10 @@ export class PortfolioService {
exchangeRateDataService: this.exchangeRateDataService exchangeRateDataService: this.exchangeRateDataService
}); });
const portfolioStart = parseDate( const startDate = this.getStartDate(
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT) dateRange,
portfolioCalculator.getStartDate()
); );
const startDate = this.getStartDate(dateRange, portfolioStart);
const currentPositions = const currentPositions =
await portfolioCalculator.getCurrentPositions(startDate); await portfolioCalculator.getCurrentPositions(startDate);
@ -962,13 +961,13 @@ export class PortfolioService {
const userId = await this.getUserId(impersonationId, this.request.user.id); const userId = await this.getUserId(impersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
const { activities, transactionPoints } = await this.getTransactionPoints({ const { activities } = await this.getTransactionPoints({
filters, filters,
userId, userId,
types: ['BUY', 'SELL'] types: ['BUY', 'SELL']
}); });
if (transactionPoints?.length <= 0) { if (activities?.length <= 0) {
return { return {
hasErrors: false, hasErrors: false,
positions: [] positions: []
@ -982,8 +981,10 @@ export class PortfolioService {
exchangeRateDataService: this.exchangeRateDataService exchangeRateDataService: this.exchangeRateDataService
}); });
const portfolioStart = parseDate(transactionPoints[0].date); const startDate = this.getStartDate(
const startDate = this.getStartDate(dateRange, portfolioStart); dateRange,
portfolioCalculator.getStartDate()
);
const currentPositions = const currentPositions =
await portfolioCalculator.getCurrentPositions(startDate); await portfolioCalculator.getCurrentPositions(startDate);
@ -1132,14 +1133,14 @@ export class PortfolioService {
) )
); );
const { activities, transactionPoints } = await this.getTransactionPoints({ const { activities } = await this.getTransactionPoints({
filters, filters,
userId, userId,
withExcludedAccounts, withExcludedAccounts,
types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL'] types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL']
}); });
if (accountBalanceItems?.length <= 0 && transactionPoints?.length <= 0) { if (accountBalanceItems?.length <= 0 && activities?.length <= 0) {
return { return {
chart: [], chart: [],
firstOrderDate: undefined, firstOrderDate: undefined,
@ -1170,7 +1171,7 @@ export class PortfolioService {
const portfolioStart = min( const portfolioStart = min(
[ [
parseDate(accountBalanceItems[0]?.date), parseDate(accountBalanceItems[0]?.date),
parseDate(transactionPoints[0]?.date) portfolioCalculator.getStartDate()
].filter((date) => { ].filter((date) => {
return isValid(date); return isValid(date);
}) })
@ -1206,7 +1207,6 @@ export class PortfolioService {
dateRange, dateRange,
impersonationId, impersonationId,
portfolioCalculator, portfolioCalculator,
transactionPoints,
userId userId
}); });
@ -1283,7 +1283,7 @@ export class PortfolioService {
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
const userCurrency = this.getUserCurrency(user); const userCurrency = this.getUserCurrency(user);
const { activities, transactionPoints } = await this.getTransactionPoints({ const { activities } = await this.getTransactionPoints({
userId, userId,
types: ['BUY', 'SELL'] types: ['BUY', 'SELL']
}); });
@ -1295,11 +1295,9 @@ export class PortfolioService {
exchangeRateDataService: this.exchangeRateDataService exchangeRateDataService: this.exchangeRateDataService
}); });
const portfolioStart = parseDate( const currentPositions = await portfolioCalculator.getCurrentPositions(
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT) portfolioCalculator.getStartDate()
); );
const currentPositions =
await portfolioCalculator.getCurrentPositions(portfolioStart);
const positions = currentPositions.positions.filter( const positions = currentPositions.positions.filter(
(item) => !item.quantity.eq(0) (item) => !item.quantity.eq(0)
@ -1429,18 +1427,16 @@ export class PortfolioService {
dateRange = 'max', dateRange = 'max',
impersonationId, impersonationId,
portfolioCalculator, portfolioCalculator,
transactionPoints,
userId, userId,
withDataDecimation = true withDataDecimation = true
}: { }: {
dateRange?: DateRange; dateRange?: DateRange;
impersonationId: string; impersonationId: string;
portfolioCalculator: PortfolioCalculator; portfolioCalculator: PortfolioCalculator;
transactionPoints: TransactionPoint[];
userId: string; userId: string;
withDataDecimation?: boolean; withDataDecimation?: boolean;
}): Promise<HistoricalDataContainer> { }): Promise<HistoricalDataContainer> {
if (transactionPoints.length === 0) { if (portfolioCalculator.getTransactionPoints().length === 0) {
return { return {
isAllTimeHigh: false, isAllTimeHigh: false,
isAllTimeLow: false, isAllTimeLow: false,
@ -1450,8 +1446,10 @@ export class PortfolioService {
userId = await this.getUserId(impersonationId, userId); userId = await this.getUserId(impersonationId, userId);
const portfolioStart = parseDate(transactionPoints[0].date); const startDate = this.getStartDate(
const startDate = this.getStartDate(dateRange, portfolioStart); dateRange,
portfolioCalculator.getStartDate()
);
const endDate = new Date(); const endDate = new Date();
const daysInMarket = differenceInDays(endDate, startDate) + 1; const daysInMarket = differenceInDays(endDate, startDate) + 1;
const step = withDataDecimation const step = withDataDecimation
@ -1944,12 +1942,11 @@ export class PortfolioService {
withExcludedAccounts?: boolean; withExcludedAccounts?: boolean;
}): Promise<{ }): Promise<{
activities: Activity[]; activities: Activity[];
transactionPoints: TransactionPoint[];
}> { }> {
const userCurrency = const userCurrency =
this.request.user?.Settings?.settings.baseCurrency ?? DEFAULT_CURRENCY; this.request.user?.Settings?.settings.baseCurrency ?? DEFAULT_CURRENCY;
const { activities, count } = await this.orderService.getOrders({ const { activities } = await this.orderService.getOrders({
filters, filters,
includeDrafts, includeDrafts,
types, types,
@ -1958,34 +1955,8 @@ export class PortfolioService {
withExcludedAccounts withExcludedAccounts
}); });
if (count <= 0) {
return { activities: [], transactionPoints: [] };
}
const portfolioOrders: PortfolioOrder[] = activities.map((order) => ({
// currency: order.SymbolProfile.currency,
// dataSource: order.SymbolProfile.dataSource,
date: format(order.date, DATE_FORMAT),
fee: new Big(order.fee),
// name: order.SymbolProfile?.name,
quantity: new Big(order.quantity),
// symbol: order.SymbolProfile.symbol,
SymbolProfile: order.SymbolProfile,
// tags: order.tags,
type: order.type,
unitPrice: new Big(order.unitPrice)
}));
const portfolioCalculator = new PortfolioCalculator({
activities,
currency: userCurrency,
currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService
});
return { return {
activities, activities
transactionPoints: portfolioCalculator.getTransactionPoints()
}; };
} }

Loading…
Cancel
Save