Browse Source

Implement holding and overall performance of ROI portfolio calculator

Extract getDateRangeBoundaries and getNetPerformanceOfDateRange into the base calculator and use them in both the ROAI and the ROI per-date-range loops to avoid duplicated date-range logic. ROAI behaviour is unchanged.
pull/8188/head
Pedro Moreira 1 week ago
parent
commit
2f07e54c89
  1. 57
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 37
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
  3. 243
      apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-reference-examples.spec.ts
  4. 266
      apps/api/src/app/portfolio/calculator/roi/portfolio-calculator.ts
  5. 36
      apps/api/src/app/portfolio/current-rate.service.mock.ts

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

@ -221,7 +221,8 @@ export abstract class PortfolioCalculator {
}
protected abstract calculateOverallPerformance(
positions: PortfolioCalculatorHolding[]
positions: PortfolioCalculatorHolding[],
accumulatedValuesByDate: { [date: string]: AccumulatedValues }
): PortfolioSnapshot;
protected abstract calculatePerformancePercentages({
@ -753,7 +754,10 @@ export abstract class PortfolioCalculator {
};
});
const overall = this.calculateOverallPerformance(positions);
const overall = this.calculateOverallPerformance(
positions,
accumulatedValuesByDate
);
return {
...overall,
@ -926,6 +930,28 @@ export abstract class PortfolioCalculator {
return this.dataProviderInfos;
}
/**
* Returns the start and the end date string of a date range, where the
* start date is not before the start date of the calculation
*/
protected getDateRangeBoundaries({
dateRange,
start
}: {
dateRange: DateRange;
start: Date;
}) {
const { endDate, startDate } = getIntervalFromDateRange({ dateRange });
return {
rangeEndDateString: format(endDate, DATE_FORMAT),
rangeStartDateString: format(
isBefore(startDate, start) ? start : startDate,
DATE_FORMAT
)
};
}
/**
* Returns the date ranges of the performance of a holding: the fixed ranges
* and each past calendar year between the start and the end date
@ -1617,6 +1643,33 @@ export abstract class PortfolioCalculator {
return this.snapshot.totalLiabilitiesWithCurrencyEffect;
}
/**
* Returns the net performance with currency effect within a date range
*/
protected getNetPerformanceOfDateRange({
dateRange,
netPerformanceValuesWithCurrencyEffect,
rangeEndDateString,
rangeStartDateString
}: {
dateRange: DateRange;
netPerformanceValuesWithCurrencyEffect: { [date: string]: Big };
rangeEndDateString: string;
rangeStartDateString: string;
}) {
return (
netPerformanceValuesWithCurrencyEffect[rangeEndDateString]?.minus(
// If the date range is 'max', take 0 as a start value. Otherwise,
// the value of the end of the day of the start date is taken which
// differs from the buying price.
dateRange === 'max'
? new Big(0)
: (netPerformanceValuesWithCurrencyEffect[rangeStartDateString] ??
new Big(0))
) ?? new Big(0)
);
}
public async getPerformance({ end, start }: { end: Date; start: Date }) {
await this.snapshotPromise;

37
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts

@ -5,11 +5,8 @@ import { HoldingPerformance } from '@ghostfolio/api/app/portfolio/interfaces/hol
import { PortfolioCalculatorHolding } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-calculator-holding.interface';
import { WeightedInvestment } from '@ghostfolio/api/app/portfolio/interfaces/weighted-investment.interface';
import { PerformancePercentages } from '@ghostfolio/api/app/portfolio/types/performance-percentages.type';
import {
getAnnualizedPerformancePercent,
getIntervalFromDateRange
} from '@ghostfolio/common/calculation-helper';
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
import { getAnnualizedPerformancePercent } from '@ghostfolio/common/calculation-helper';
import { parseDate } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
HistoricalDataItem
@ -19,7 +16,7 @@ import { DateRange } from '@ghostfolio/common/types';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';
import { differenceInDays, format, isBefore } from 'date-fns';
import { differenceInDays } from 'date-fns';
import { sortedIndex } from 'lodash-es';
export class RoaiPortfolioCalculator extends PortfolioCalculator {
@ -294,6 +291,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
symbol
});
// `!` does not narrow the union under `strictNullChecks: false`
if (holdingValuationOfPeriod.hasMarketPrices === false) {
return holdingValuationOfPeriod.holdingPerformance;
}
@ -514,16 +512,8 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
} = {};
for (const dateRange of this.getDateRanges({ end, start })) {
const dateInterval = getIntervalFromDateRange({ dateRange });
const endDate = dateInterval.endDate;
let startDate = dateInterval.startDate;
if (isBefore(startDate, start)) {
startDate = start;
}
const rangeEndDateString = format(endDate, DATE_FORMAT);
const rangeStartDateString = format(startDate, DATE_FORMAT);
const { rangeEndDateString, rangeStartDateString } =
this.getDateRangeBoundaries({ dateRange, start });
const currentValuesAtDateRangeStartWithCurrencyEffect =
currentValuesWithCurrencyEffect[rangeStartDateString] ?? new Big(0);
@ -568,15 +558,12 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
: new Big(0);
netPerformanceWithCurrencyEffectMap[dateRange] =
netPerformanceValuesWithCurrencyEffect[rangeEndDateString]?.minus(
// If the date range is 'max', take 0 as a start value. Otherwise,
// the value of the end of the day of the start date is taken which
// differs from the buying price.
dateRange === 'max'
? new Big(0)
: (netPerformanceValuesWithCurrencyEffect[rangeStartDateString] ??
new Big(0))
) ?? new Big(0);
this.getNetPerformanceOfDateRange({
dateRange,
netPerformanceValuesWithCurrencyEffect,
rangeEndDateString,
rangeStartDateString
});
netPerformancePercentageWithCurrencyEffectMap[dateRange] = average.gt(0)
? netPerformanceWithCurrencyEffectMap[dateRange].div(average)

243
apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-reference-examples.spec.ts

@ -0,0 +1,243 @@
import {
activityDummyData,
assetProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';
jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
return {
CurrentRateService: jest.fn().mockImplementation(() => {
return CurrentRateServiceMock;
})
};
});
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;
})
};
});
function getActivity({
date,
quantity,
symbol,
type,
unitPrice
}: {
date: string;
quantity: number;
symbol: string;
type: Activity['type'];
unitPrice: number;
}): Activity {
return {
...activityDummyData,
quantity,
type,
assetProfile: {
...assetProfileDummyData,
symbol,
currency: 'CHF',
dataSource: 'YAHOO',
name: symbol
},
date: parseDate(date),
feeInAssetProfileCurrency: 0,
feeInBaseCurrency: 0,
unitPriceInAssetProfileCurrency: unitPrice
};
}
describe('RoiPortfolioCalculator', () => {
let portfolioCalculatorFactory: PortfolioCalculatorFactory;
beforeEach(() => {
PortfolioSnapshotServiceMock.reset();
RedisCacheServiceMock.reset();
portfolioCalculatorFactory = new PortfolioCalculatorFactory(
new ConfigurationService(),
new CurrentRateService(null, null, null),
new ExchangeRateDataService(null, null, null, null),
new PortfolioSnapshotService(null, null),
new RedisCacheService(null, null)
);
});
afterEach(() => {
jest.useRealTimers();
});
describe('get current positions', () => {
it('with a small buy followed by a large buy', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2024-12-31').getTime());
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities: [
getActivity({
date: '2024-01-01',
quantity: 100,
symbol: 'ROI-DCA',
type: 'BUY',
unitPrice: 10
}),
getActivity({
date: '2024-10-27',
quantity: 600,
symbol: 'ROI-DCA',
type: 'BUY',
unitPrice: 15
})
],
calculationType: PerformanceCalculationType.ROI,
currency: 'CHF',
userId: userDummyData.id
});
const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
// Gain: 700 * 15 - (1000 + 9000) = 500, total buy value: 10000
expect(portfolioSnapshot.positions).toMatchObject([
{
netPerformance: new Big('500'),
netPerformancePercentage: new Big('500').div('10000'),
netPerformancePercentageWithCurrencyEffectMap: {
max: new Big('500').div('10000')
},
netPerformanceWithCurrencyEffectMap: { max: new Big('500') }
}
]);
expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject({
date: '2024-12-31',
netPerformance: 500,
netPerformanceInPercentage: 0.05,
netPerformanceInPercentageWithCurrencyEffect: 0.05,
totalBuyValue: 10000,
totalBuyValueWithCurrencyEffect: 10000,
totalInvestment: 10000,
value: 10500
});
});
it('with a buy followed by a partial sell', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2024-12-31').getTime());
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities: [
getActivity({
date: '2024-01-01',
quantity: 100,
symbol: 'ROI-PARTIAL-SELL',
type: 'BUY',
unitPrice: 10
}),
getActivity({
date: '2024-06-01',
quantity: 50,
symbol: 'ROI-PARTIAL-SELL',
type: 'SELL',
unitPrice: 16
})
],
calculationType: PerformanceCalculationType.ROI,
currency: 'CHF',
userId: userDummyData.id
});
const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
// Gain: 50 * (16 - 10) + 50 * (18 - 10) = 700, total buy value: 1000
expect(portfolioSnapshot.positions).toMatchObject([
{
netPerformance: new Big('700'),
netPerformancePercentage: new Big('700').div('1000'),
netPerformancePercentageWithCurrencyEffectMap: {
max: new Big('700').div('1000')
}
}
]);
expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject({
netPerformanceInPercentage: 0.7,
totalBuyValue: 1000
});
});
it('with a buy, a full sell and a buy again', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2024-12-31').getTime());
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities: [
getActivity({
date: '2024-01-01',
quantity: 100,
symbol: 'ROI-REBUY',
type: 'BUY',
unitPrice: 10
}),
getActivity({
date: '2024-06-01',
quantity: 100,
symbol: 'ROI-REBUY',
type: 'SELL',
unitPrice: 10
}),
getActivity({
date: '2024-06-02',
quantity: 100,
symbol: 'ROI-REBUY',
type: 'BUY',
unitPrice: 10
})
],
calculationType: PerformanceCalculationType.ROI,
currency: 'CHF',
userId: userDummyData.id
});
const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
// Gain: 100 * (11 - 10) = 100, the money is invested twice: 2000
expect(portfolioSnapshot.positions).toMatchObject([
{
netPerformance: new Big('100'),
netPerformancePercentage: new Big('100').div('2000')
}
]);
expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject({
netPerformanceInPercentage: 0.05,
totalBuyValue: 2000
});
});
});
});

266
apps/api/src/app/portfolio/calculator/roi/portfolio-calculator.ts

@ -1,19 +1,89 @@
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
import { AccumulatedValues } from '@ghostfolio/api/app/portfolio/interfaces/accumulated-values.interface';
import { HoldingPerformance } from '@ghostfolio/api/app/portfolio/interfaces/holding-performance.interface';
import { PortfolioCalculatorHolding } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-calculator-holding.interface';
import { PerformancePercentages } from '@ghostfolio/api/app/portfolio/types/performance-percentages.type';
import { getAnnualizedPerformancePercent } from '@ghostfolio/common/calculation-helper';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
HistoricalDataItem
} from '@ghostfolio/common/interfaces';
import { PortfolioSnapshot } from '@ghostfolio/common/models';
import { DateRange } from '@ghostfolio/common/types';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';
import { differenceInDays, format } from 'date-fns';
export class RoiPortfolioCalculator extends PortfolioCalculator {
protected calculateOverallPerformance(): PortfolioSnapshot {
throw new Error('Method not implemented.');
protected calculateOverallPerformance(
positions: PortfolioCalculatorHolding[],
accumulatedValuesByDate: { [date: string]: AccumulatedValues }
): PortfolioSnapshot {
const {
currentValueInBaseCurrency,
hasErrors,
totalFeesWithCurrencyEffect,
totalInvestment,
totalInvestmentWithCurrencyEffect
} = this.getTotalsOfPositions(positions);
const dateOfFirstActivity = this.getStartDate();
const daysInMarket = dateOfFirstActivity
? differenceInDays(new Date(), dateOfFirstActivity)
: 0;
// The values of the last chart date contain the dividends and the buys of
// the holdings with market prices only. Thus, the dividend yield is not
// distorted by holdings without a total buy value.
const lastAccumulatedValues = Object.values(accumulatedValuesByDate).at(-1);
const totalBuyValue = lastAccumulatedValues?.totalBuyValue ?? new Big(0);
const totalBuyValueWithCurrencyEffect =
lastAccumulatedValues?.totalBuyValueWithCurrencyEffect ?? new Big(0);
const totalDividendValueWithCurrencyEffect =
lastAccumulatedValues?.totalDividendValueWithCurrencyEffect ?? new Big(0);
const dividendYieldPercent = getAnnualizedPerformancePercent({
daysInMarket,
netPerformancePercentage: totalBuyValue.eq(0)
? new Big(0)
: totalDividendValueWithCurrencyEffect.div(totalBuyValue)
});
const dividendYieldPercentWithCurrencyEffect =
getAnnualizedPerformancePercent({
daysInMarket,
netPerformancePercentage: totalBuyValueWithCurrencyEffect.eq(0)
? new Big(0)
: totalDividendValueWithCurrencyEffect.div(
totalBuyValueWithCurrencyEffect
)
});
return {
currentValueInBaseCurrency,
dividendYieldPercent,
dividendYieldPercentWithCurrencyEffect,
hasErrors,
positions,
totalFeesWithCurrencyEffect,
totalInvestment,
totalInvestmentWithCurrencyEffect,
activitiesCount: this.activities.filter(({ type }) => {
return ['BUY', 'SELL'].includes(type);
}).length,
createdAt: new Date(),
errors: [],
historicalData: [],
totalCashInBaseCurrency: new Big(0),
totalInterestWithCurrencyEffect: new Big(0),
totalLiabilitiesWithCurrencyEffect: new Big(0)
};
}
protected calculatePerformancePercentages({
@ -122,8 +192,17 @@ export class RoiPortfolioCalculator extends PortfolioCalculator {
};
}
protected getHoldingPerformance({}: {
protected getHoldingPerformance({
chartDates,
dataSource,
end,
exchangeRates,
marketSymbolMap,
start,
symbol
}: {
chartDates: string[];
daysUntilNextChartDate: number[];
end: Date;
exchangeRates: { [dateString: string]: number };
marketSymbolMap: {
@ -131,7 +210,186 @@ export class RoiPortfolioCalculator extends PortfolioCalculator {
};
start: Date;
} & AssetProfileIdentifier): HoldingPerformance {
throw new Error('Method not implemented.');
const holdingValuationOfPeriod = this.getHoldingValuationOfPeriod({
chartDates,
dataSource,
end,
exchangeRates,
marketSymbolMap,
start,
symbol
});
// `!` does not narrow the union under `strictNullChecks: false`
if (holdingValuationOfPeriod.hasMarketPrices === false) {
return holdingValuationOfPeriod.holdingPerformance;
}
const {
dateOfFirstActivity,
holdingValuation: {
buyValuesAccumulated,
buyValuesAccumulatedWithCurrencyEffect,
currentValues,
currentValuesWithCurrencyEffect,
dividendValuesWithCurrencyEffect,
initialValue,
investmentValuesAccumulated,
investmentValuesAccumulatedWithCurrencyEffect,
investmentValuesWithCurrencyEffect,
items,
netPerformanceValues,
netPerformanceValuesWithCurrencyEffect
},
totalDividend,
totalDividendInBaseCurrency,
totalInterestInBaseCurrency,
totalLiabilitiesInBaseCurrency,
unitPriceAtEndDate
} = holdingValuationOfPeriod;
const endItem = items.find(({ itemType }) => {
return itemType === 'end';
});
const startItem = items.find(({ itemType }) => {
return itemType === 'start';
});
const endDateString = format(end, DATE_FORMAT);
// The invested capital of the period is the value at the start date plus
// the value of each buy. A sell does not reduce it.
const investedCapital = startItem.value.plus(
buyValuesAccumulated[endDateString] ?? new Big(0)
);
const investedCapitalWithCurrencyEffect =
startItem.valueWithCurrencyEffect.plus(
buyValuesAccumulatedWithCurrencyEffect[endDateString] ?? new Big(0)
);
const totalGrossPerformance = endItem.grossPerformance.minus(
startItem.grossPerformance
);
const totalGrossPerformanceWithCurrencyEffect =
endItem.grossPerformanceWithCurrencyEffect.minus(
startItem.grossPerformanceWithCurrencyEffect
);
const totalNetPerformance = totalGrossPerformance.minus(
endItem.fees.minus(startItem.fees)
);
const grossPerformancePercentage = investedCapital.gt(0)
? totalGrossPerformance.div(investedCapital)
: new Big(0);
const grossPerformancePercentageWithCurrencyEffect =
investedCapitalWithCurrencyEffect.gt(0)
? totalGrossPerformanceWithCurrencyEffect.div(
investedCapitalWithCurrencyEffect
)
: new Big(0);
const netPerformancePercentage = investedCapital.gt(0)
? totalNetPerformance.div(investedCapital)
: new Big(0);
const daysInMarket = differenceInDays(new Date(), dateOfFirstActivity);
const dividendYieldPercent = getAnnualizedPerformancePercent({
daysInMarket,
netPerformancePercentage: investedCapital.eq(0)
? new Big(0)
: totalDividendInBaseCurrency.div(investedCapital)
});
const dividendYieldPercentWithCurrencyEffect =
getAnnualizedPerformancePercent({
daysInMarket,
netPerformancePercentage: investedCapitalWithCurrencyEffect.eq(0)
? new Big(0)
: totalDividendInBaseCurrency.div(investedCapitalWithCurrencyEffect)
});
const netPerformancePercentageWithCurrencyEffectMap: {
[key: DateRange]: Big;
} = {};
const netPerformanceWithCurrencyEffectMap: {
[key: DateRange]: Big;
} = {};
for (const dateRange of this.getDateRanges({ end, start })) {
const { rangeEndDateString, rangeStartDateString } =
this.getDateRangeBoundaries({ dateRange, start });
netPerformanceWithCurrencyEffectMap[dateRange] =
this.getNetPerformanceOfDateRange({
dateRange,
netPerformanceValuesWithCurrencyEffect,
rangeEndDateString,
rangeStartDateString
});
const investedCapitalOfDateRange = (
currentValuesWithCurrencyEffect[rangeStartDateString] ?? new Big(0)
)
.plus(
buyValuesAccumulatedWithCurrencyEffect[rangeEndDateString] ??
new Big(0)
)
.minus(
buyValuesAccumulatedWithCurrencyEffect[rangeStartDateString] ??
new Big(0)
);
netPerformancePercentageWithCurrencyEffectMap[dateRange] =
investedCapitalOfDateRange.gt(0)
? netPerformanceWithCurrencyEffectMap[dateRange].div(
investedCapitalOfDateRange
)
: new Big(0);
}
return {
buyValuesAccumulated,
buyValuesAccumulatedWithCurrencyEffect,
currentValues,
currentValuesWithCurrencyEffect,
dividendValuesWithCurrencyEffect,
dividendYieldPercent,
dividendYieldPercentWithCurrencyEffect,
grossPerformancePercentage,
grossPerformancePercentageWithCurrencyEffect,
investmentValuesAccumulated,
investmentValuesAccumulatedWithCurrencyEffect,
investmentValuesWithCurrencyEffect,
netPerformancePercentage,
netPerformancePercentageWithCurrencyEffectMap,
netPerformanceValues,
netPerformanceValuesWithCurrencyEffect,
netPerformanceWithCurrencyEffectMap,
totalDividend,
totalDividendInBaseCurrency,
totalInterestInBaseCurrency,
totalLiabilitiesInBaseCurrency,
// The ROI does not weight the investment by time
averageInvestment: new Big(0),
averageInvestmentValues: {},
averageInvestmentValuesWithCurrencyEffect: {},
averageInvestmentWithCurrencyEffect: new Big(0),
grossPerformance: totalGrossPerformance,
grossPerformanceWithCurrencyEffect:
totalGrossPerformanceWithCurrencyEffect,
hasErrors:
endItem.quantity.gt(0) && (!initialValue || !unitPriceAtEndDate),
netPerformance: totalNetPerformance,
totalInvestment: endItem.investment,
totalInvestmentWithCurrencyEffect: endItem.investmentWithCurrencyEffect
};
}
protected getPerformanceCalculationType() {

36
apps/api/src/app/portfolio/current-rate.service.mock.ts

@ -123,6 +123,42 @@ function mockGetValue({
return { marketPrice: 0 };
case 'ROI-DCA':
if (isSameDay(parseDate('2024-01-01'), date)) {
return { marketPrice: 10 };
} else if (
isSameDay(parseDate('2024-10-27'), date) ||
isSameDay(parseDate('2024-12-31'), date)
) {
return { marketPrice: 15 };
}
return { marketPrice: 0 };
case 'ROI-PARTIAL-SELL':
if (isSameDay(parseDate('2024-01-01'), date)) {
return { marketPrice: 10 };
} else if (isSameDay(parseDate('2024-06-01'), date)) {
return { marketPrice: 16 };
} else if (isSameDay(parseDate('2024-12-31'), date)) {
return { marketPrice: 18 };
}
return { marketPrice: 0 };
case 'ROI-REBUY':
if (
isSameDay(parseDate('2024-01-01'), date) ||
isSameDay(parseDate('2024-06-01'), date) ||
isSameDay(parseDate('2024-06-02'), date)
) {
return { marketPrice: 10 };
} else if (isSameDay(parseDate('2024-12-31'), date)) {
return { marketPrice: 11 };
}
return { marketPrice: 0 };
default:
return { marketPrice: 0 };
}

Loading…
Cancel
Save