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();
}
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({
daysInMarket,
netPerformancePercent
@ -191,10 +98,6 @@ export class PortfolioCalculator {
return new Big(0);
}
public getTransactionPoints(): TransactionPoint[] {
return this.transactionPoints;
}
public async getChartData({
end = new Date(Date.now()),
start,
@ -258,7 +161,7 @@ export class PortfolioCalculator {
await this.exchangeRateDataService.getExchangeRatesByCurrency({
currencies: uniq(Object.values(currencies)),
endDate: endOfDay(end),
startDate: parseDate(this.transactionPoints?.[0]?.date),
startDate: this.getStartDate(),
targetCurrency: this.currency
});
@ -562,7 +465,7 @@ export class PortfolioCalculator {
await this.exchangeRateDataService.getExchangeRatesByCurrency({
currencies: uniq(Object.values(currencies)),
endDate: endOfDay(endDate),
startDate: parseDate(this.transactionPoints?.[0]?.date),
startDate: this.getStartDate(),
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({
dataSource,
end,

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

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

Loading…
Cancel
Save