|
|
@ -18,12 +18,7 @@ import { |
|
|
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_HIGH, |
|
|
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_HIGH, |
|
|
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW |
|
|
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW |
|
|
} from '@ghostfolio/common/config'; |
|
|
} from '@ghostfolio/common/config'; |
|
|
import { |
|
|
import { DATE_FORMAT, getSum, parseDate } from '@ghostfolio/common/helper'; |
|
|
DATE_FORMAT, |
|
|
|
|
|
getSum, |
|
|
|
|
|
parseDate, |
|
|
|
|
|
resetHours |
|
|
|
|
|
} from '@ghostfolio/common/helper'; |
|
|
|
|
|
import { |
|
|
import { |
|
|
Activity, |
|
|
Activity, |
|
|
AssetProfileIdentifier, |
|
|
AssetProfileIdentifier, |
|
|
@ -51,14 +46,13 @@ import { |
|
|
format, |
|
|
format, |
|
|
isAfter, |
|
|
isAfter, |
|
|
isBefore, |
|
|
isBefore, |
|
|
isSameYear, |
|
|
|
|
|
isWithinInterval, |
|
|
isWithinInterval, |
|
|
min, |
|
|
min, |
|
|
startOfDay, |
|
|
startOfDay, |
|
|
startOfYear, |
|
|
startOfYear, |
|
|
subDays |
|
|
subDays |
|
|
} from 'date-fns'; |
|
|
} from 'date-fns'; |
|
|
import { isNumber, sortBy, sum, uniqBy } from 'lodash'; |
|
|
import { groupBy as ldGroupBy, isNumber, sortBy, sum, uniqBy } from 'lodash'; |
|
|
|
|
|
|
|
|
export abstract class PortfolioCalculator { |
|
|
export abstract class PortfolioCalculator { |
|
|
protected static readonly ENABLE_LOGGING = false; |
|
|
protected static readonly ENABLE_LOGGING = false; |
|
|
@ -717,105 +711,86 @@ export abstract class PortfolioCalculator { |
|
|
return this.snapshot.totalLiabilitiesWithCurrencyEffect; |
|
|
return this.snapshot.totalLiabilitiesWithCurrencyEffect; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public async getPerformance({ end, start }) { |
|
|
public async getPerformance({ data }: { data: HistoricalDataItem[] }) { |
|
|
await this.snapshotPromise; |
|
|
|
|
|
|
|
|
|
|
|
const { historicalData } = this.snapshot; |
|
|
|
|
|
|
|
|
|
|
|
const chart: HistoricalDataItem[] = []; |
|
|
const chart: HistoricalDataItem[] = []; |
|
|
|
|
|
|
|
|
let netPerformanceAtStartDate: number; |
|
|
let netPerformanceAtStartDate: number; |
|
|
let netPerformanceWithCurrencyEffectAtStartDate: number; |
|
|
let netPerformanceWithCurrencyEffectAtStartDate: number; |
|
|
const totalInvestmentValuesWithCurrencyEffect: number[] = []; |
|
|
const totalInvestmentValuesWithCurrencyEffect: number[] = []; |
|
|
|
|
|
|
|
|
for (const historicalDataItem of historicalData) { |
|
|
for (const historicalDataItem of data) { |
|
|
const date = resetHours(parseDate(historicalDataItem.date)); |
|
|
if (!isNumber(netPerformanceAtStartDate)) { |
|
|
|
|
|
netPerformanceAtStartDate = historicalDataItem.netPerformance; |
|
|
|
|
|
|
|
|
if (!isBefore(date, start) && !isAfter(date, end)) { |
|
|
netPerformanceWithCurrencyEffectAtStartDate = |
|
|
if (!isNumber(netPerformanceAtStartDate)) { |
|
|
historicalDataItem.netPerformanceWithCurrencyEffect; |
|
|
netPerformanceAtStartDate = historicalDataItem.netPerformance; |
|
|
} |
|
|
|
|
|
|
|
|
netPerformanceWithCurrencyEffectAtStartDate = |
|
|
const netPerformanceSinceStartDate = |
|
|
historicalDataItem.netPerformanceWithCurrencyEffect; |
|
|
historicalDataItem.netPerformance - netPerformanceAtStartDate; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const netPerformanceSinceStartDate = |
|
|
const netPerformanceWithCurrencyEffectSinceStartDate = |
|
|
historicalDataItem.netPerformance - netPerformanceAtStartDate; |
|
|
historicalDataItem.netPerformanceWithCurrencyEffect - |
|
|
|
|
|
netPerformanceWithCurrencyEffectAtStartDate; |
|
|
|
|
|
|
|
|
const netPerformanceWithCurrencyEffectSinceStartDate = |
|
|
if (historicalDataItem.totalInvestmentValueWithCurrencyEffect > 0) { |
|
|
historicalDataItem.netPerformanceWithCurrencyEffect - |
|
|
totalInvestmentValuesWithCurrencyEffect.push( |
|
|
netPerformanceWithCurrencyEffectAtStartDate; |
|
|
historicalDataItem.totalInvestmentValueWithCurrencyEffect |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (historicalDataItem.totalInvestmentValueWithCurrencyEffect > 0) { |
|
|
const timeWeightedInvestmentValue = |
|
|
totalInvestmentValuesWithCurrencyEffect.push( |
|
|
totalInvestmentValuesWithCurrencyEffect.length > 0 |
|
|
historicalDataItem.totalInvestmentValueWithCurrencyEffect |
|
|
? sum(totalInvestmentValuesWithCurrencyEffect) / |
|
|
); |
|
|
totalInvestmentValuesWithCurrencyEffect.length |
|
|
} |
|
|
: 0; |
|
|
|
|
|
|
|
|
const timeWeightedInvestmentValue = |
|
|
chart.push({ |
|
|
totalInvestmentValuesWithCurrencyEffect.length > 0 |
|
|
...historicalDataItem, |
|
|
? sum(totalInvestmentValuesWithCurrencyEffect) / |
|
|
netPerformance: |
|
|
totalInvestmentValuesWithCurrencyEffect.length |
|
|
historicalDataItem.netPerformance - netPerformanceAtStartDate, |
|
|
: 0; |
|
|
netPerformanceWithCurrencyEffect: |
|
|
|
|
|
netPerformanceWithCurrencyEffectSinceStartDate, |
|
|
chart.push({ |
|
|
netPerformanceInPercentage: |
|
|
...historicalDataItem, |
|
|
timeWeightedInvestmentValue === 0 |
|
|
netPerformance: |
|
|
? 0 |
|
|
historicalDataItem.netPerformance - netPerformanceAtStartDate, |
|
|
: netPerformanceSinceStartDate / timeWeightedInvestmentValue, |
|
|
netPerformanceWithCurrencyEffect: |
|
|
netPerformanceInPercentageWithCurrencyEffect: |
|
|
netPerformanceWithCurrencyEffectSinceStartDate, |
|
|
timeWeightedInvestmentValue === 0 |
|
|
netPerformanceInPercentage: |
|
|
? 0 |
|
|
timeWeightedInvestmentValue === 0 |
|
|
: netPerformanceWithCurrencyEffectSinceStartDate / |
|
|
? 0 |
|
|
timeWeightedInvestmentValue |
|
|
: netPerformanceSinceStartDate / timeWeightedInvestmentValue, |
|
|
// TODO: Add net worth
|
|
|
netPerformanceInPercentageWithCurrencyEffect: |
|
|
// netWorth: totalCurrentValueWithCurrencyEffect
|
|
|
timeWeightedInvestmentValue === 0 |
|
|
// .plus(totalAccountBalanceWithCurrencyEffect)
|
|
|
? 0 |
|
|
// .toNumber()
|
|
|
: netPerformanceWithCurrencyEffectSinceStartDate / |
|
|
// netWorth: 0
|
|
|
timeWeightedInvestmentValue |
|
|
}); |
|
|
// TODO: Add net worth
|
|
|
|
|
|
// netWorth: totalCurrentValueWithCurrencyEffect
|
|
|
|
|
|
// .plus(totalAccountBalanceWithCurrencyEffect)
|
|
|
|
|
|
// .toNumber()
|
|
|
|
|
|
// netWorth: 0
|
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return { chart }; |
|
|
return { chart }; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public async getPerformanceByGroup({ |
|
|
public async getPerformanceByGroup({ |
|
|
endDate, |
|
|
data, |
|
|
groupBy, |
|
|
groupBy |
|
|
startDate |
|
|
|
|
|
}: { |
|
|
}: { |
|
|
endDate: Date; |
|
|
data: HistoricalDataItem[]; |
|
|
groupBy: GroupBy; |
|
|
groupBy: Extract<GroupBy, 'year'>; |
|
|
startDate: Date; |
|
|
|
|
|
}) { |
|
|
}) { |
|
|
const interval = { start: startDate, end: endDate }; |
|
|
|
|
|
const chart: HistoricalDataItem[] = []; |
|
|
const chart: HistoricalDataItem[] = []; |
|
|
|
|
|
|
|
|
if (groupBy === 'year') { |
|
|
if (groupBy === 'year') { |
|
|
for (const year of eachYearOfInterval(interval)) { |
|
|
const dataByYear = ldGroupBy(data, (item) => item.date.slice(0, 4)); |
|
|
const yearStartDate = startOfYear(year); |
|
|
|
|
|
const yearEndDate = endOfYear(year); |
|
|
|
|
|
const yearIntervalStartDate = isSameYear(startDate, yearStartDate) |
|
|
|
|
|
? startDate |
|
|
|
|
|
: yearStartDate; |
|
|
|
|
|
const yearIntervalEndDate = isSameYear(endDate, yearEndDate) |
|
|
|
|
|
? endDate |
|
|
|
|
|
: yearEndDate; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const year of Object.keys(dataByYear)) { |
|
|
const { chart: yearChart } = await this.getPerformance({ |
|
|
const { chart: yearChart } = await this.getPerformance({ |
|
|
end: yearIntervalEndDate, |
|
|
data: Object.values(dataByYear[year]) |
|
|
start: yearIntervalStartDate |
|
|
|
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const yearPerformanceItem = { |
|
|
const yearPerformanceItem = { |
|
|
...(yearChart.at(-1) ?? ({} as HistoricalDataItem)), |
|
|
...(yearChart.at(-1) ?? ({} as HistoricalDataItem)), |
|
|
date: format(yearStartDate, DATE_FORMAT) |
|
|
date: format(startOfYear(year), DATE_FORMAT) |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
chart.push(yearPerformanceItem); |
|
|
chart.push(yearPerformanceItem); |
|
|
|