Extend current rate service with getRange()
This commit is contained in:
parent
7dac059a55
commit
099571437e
@ -17,6 +17,28 @@ jest.mock('./market-data.service', () => {
|
|||||||
id: 'aefcbe3a-ee10-4c4f-9f2d-8ffad7b05584',
|
id: 'aefcbe3a-ee10-4c4f-9f2d-8ffad7b05584',
|
||||||
marketPrice: 1847.839966
|
marketPrice: 1847.839966
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
getRange: (
|
||||||
|
dateRangeEnd: Date,
|
||||||
|
dateRangeStart: Date,
|
||||||
|
symbol: string
|
||||||
|
) => {
|
||||||
|
return Promise.resolve<MarketData[]>([
|
||||||
|
{
|
||||||
|
date: dateRangeStart,
|
||||||
|
symbol,
|
||||||
|
createdAt: dateRangeStart,
|
||||||
|
id: '8fa48fde-f397-4b0d-adbc-fb940e830e6d',
|
||||||
|
marketPrice: 1841.823902
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: dateRangeEnd,
|
||||||
|
symbol,
|
||||||
|
createdAt: dateRangeEnd,
|
||||||
|
id: '082d6893-df27-4c91-8a5d-092e84315b56',
|
||||||
|
marketPrice: 1847.839966
|
||||||
|
}
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@ -71,6 +93,29 @@ describe('CurrentRateService', () => {
|
|||||||
symbol: 'AMZN',
|
symbol: 'AMZN',
|
||||||
userCurrency: Currency.CHF
|
userCurrency: Currency.CHF
|
||||||
})
|
})
|
||||||
).toEqual(1847.839966);
|
).toMatchObject({
|
||||||
|
marketPrice: 1847.839966
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getValues', async () => {
|
||||||
|
expect(
|
||||||
|
await currentRateService.getValues({
|
||||||
|
currency: Currency.USD,
|
||||||
|
dateRangeEnd: new Date(Date.UTC(2020, 0, 2, 0, 0, 0)),
|
||||||
|
dateRangeStart: new Date(Date.UTC(2020, 0, 1, 0, 0, 0)),
|
||||||
|
symbol: 'AMZN',
|
||||||
|
userCurrency: Currency.CHF
|
||||||
|
})
|
||||||
|
).toMatchObject([
|
||||||
|
{
|
||||||
|
// date
|
||||||
|
marketPrice: 1841.823902
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// date
|
||||||
|
marketPrice: 1847.839966
|
||||||
|
}
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||||
|
import { resetHours } from '@ghostfolio/common/helper';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import { isToday } from 'date-fns';
|
import { isToday } from 'date-fns';
|
||||||
@ -19,10 +20,13 @@ export class CurrentRateService {
|
|||||||
date,
|
date,
|
||||||
symbol,
|
symbol,
|
||||||
userCurrency
|
userCurrency
|
||||||
}: GetValueParams): Promise<number> {
|
}: GetValueParams): Promise<GetValueObject> {
|
||||||
if (isToday(date)) {
|
if (isToday(date)) {
|
||||||
const dataProviderResult = await this.dataProviderService.get([symbol]);
|
const dataProviderResult = await this.dataProviderService.get([symbol]);
|
||||||
return dataProviderResult?.[symbol]?.marketPrice ?? 0;
|
return {
|
||||||
|
date: resetHours(date),
|
||||||
|
marketPrice: dataProviderResult?.[symbol]?.marketPrice ?? 0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const marketData = await this.marketDataService.get({
|
const marketData = await this.marketDataService.get({
|
||||||
@ -31,14 +35,50 @@ export class CurrentRateService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (marketData) {
|
if (marketData) {
|
||||||
return this.exchangeRateDataService.toCurrency(
|
return {
|
||||||
marketData.marketPrice,
|
date: marketData.date,
|
||||||
currency,
|
marketPrice: this.exchangeRateDataService.toCurrency(
|
||||||
userCurrency
|
marketData.marketPrice,
|
||||||
);
|
currency,
|
||||||
|
userCurrency
|
||||||
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Value not found for ${symbol} at ${date}`);
|
throw new Error(`Value not found for ${symbol} at ${resetHours(date)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getValues({
|
||||||
|
currency,
|
||||||
|
dateRangeEnd,
|
||||||
|
dateRangeStart,
|
||||||
|
symbol,
|
||||||
|
userCurrency
|
||||||
|
}: GetValuesParams): Promise<GetValueObject[]> {
|
||||||
|
const marketData = await this.marketDataService.getRange({
|
||||||
|
dateRangeEnd,
|
||||||
|
dateRangeStart,
|
||||||
|
symbol
|
||||||
|
});
|
||||||
|
|
||||||
|
if (marketData) {
|
||||||
|
return marketData.map((marketDataItem) => {
|
||||||
|
return {
|
||||||
|
date: marketDataItem.date,
|
||||||
|
marketPrice: this.exchangeRateDataService.toCurrency(
|
||||||
|
marketDataItem.marketPrice,
|
||||||
|
currency,
|
||||||
|
userCurrency
|
||||||
|
)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Values not found for ${symbol} from ${resetHours(
|
||||||
|
dateRangeStart
|
||||||
|
)} to ${resetHours(dateRangeEnd)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,3 +88,16 @@ export interface GetValueParams {
|
|||||||
currency: Currency;
|
currency: Currency;
|
||||||
userCurrency: Currency;
|
userCurrency: Currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GetValuesParams {
|
||||||
|
dateRangeEnd: Date;
|
||||||
|
dateRangeStart: Date;
|
||||||
|
symbol: string;
|
||||||
|
currency: Currency;
|
||||||
|
userCurrency: Currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetValueObject {
|
||||||
|
date: Date;
|
||||||
|
marketPrice: number;
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
|||||||
import { resetHours } from '@ghostfolio/common/helper';
|
import { resetHours } from '@ghostfolio/common/helper';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { MarketData } from '@prisma/client';
|
import { MarketData } from '@prisma/client';
|
||||||
|
import { endOfDay } from 'date-fns';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MarketDataService {
|
export class MarketDataService {
|
||||||
@ -21,4 +22,24 @@ export class MarketDataService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getRange({
|
||||||
|
dateRangeEnd,
|
||||||
|
dateRangeStart,
|
||||||
|
symbol
|
||||||
|
}: {
|
||||||
|
dateRangeEnd: Date;
|
||||||
|
dateRangeStart: Date;
|
||||||
|
symbol: string;
|
||||||
|
}): Promise<MarketData[]> {
|
||||||
|
return await this.prisma.marketData.findMany({
|
||||||
|
where: {
|
||||||
|
date: {
|
||||||
|
gte: dateRangeStart,
|
||||||
|
lt: endOfDay(dateRangeEnd)
|
||||||
|
},
|
||||||
|
symbol
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
|
import {
|
||||||
|
CurrentRateService,
|
||||||
|
GetValueParams
|
||||||
|
} from '@ghostfolio/api/app/core/current-rate.service';
|
||||||
import {
|
import {
|
||||||
PortfolioCalculator,
|
PortfolioCalculator,
|
||||||
PortfolioOrder,
|
PortfolioOrder,
|
||||||
TimelinePeriod,
|
TimelinePeriod,
|
||||||
TimelineSpecification
|
TimelineSpecification
|
||||||
} from '@ghostfolio/api/app/core/portfolio-calculator';
|
} from '@ghostfolio/api/app/core/portfolio-calculator';
|
||||||
import {
|
|
||||||
CurrentRateService,
|
|
||||||
GetValueParams
|
|
||||||
} from '@ghostfolio/api/app/core/current-rate.service';
|
|
||||||
import { Currency } from '@prisma/client';
|
|
||||||
import { OrderType } from '@ghostfolio/api/models/order-type';
|
import { OrderType } from '@ghostfolio/api/models/order-type';
|
||||||
|
import { Currency } from '@prisma/client';
|
||||||
import Big from 'big.js';
|
import Big from 'big.js';
|
||||||
import { differenceInCalendarDays, parse } from 'date-fns';
|
import { differenceInCalendarDays, parse } from 'date-fns';
|
||||||
|
|
||||||
@ -46,21 +46,21 @@ jest.mock('./current-rate.service.ts', () => {
|
|||||||
const today = new Date();
|
const today = new Date();
|
||||||
if (symbol === 'VTI') {
|
if (symbol === 'VTI') {
|
||||||
if (dateEqual(today, date)) {
|
if (dateEqual(today, date)) {
|
||||||
return Promise.resolve(new Big('213.32'));
|
return Promise.resolve({ marketPrice: new Big('213.32') });
|
||||||
} else {
|
} else {
|
||||||
const startDate = parse('2019-02-01', 'yyyy-MM-dd', new Date());
|
const startDate = parse('2019-02-01', 'yyyy-MM-dd', new Date());
|
||||||
const daysInBetween = differenceInCalendarDays(date, startDate);
|
const daysInBetween = differenceInCalendarDays(date, startDate);
|
||||||
|
|
||||||
const result = new Big('144.38').plus(
|
const marketPrice = new Big('144.38').plus(
|
||||||
new Big('0.08').mul(daysInBetween)
|
new Big('0.08').mul(daysInBetween)
|
||||||
);
|
);
|
||||||
return Promise.resolve(result);
|
return Promise.resolve({ marketPrice });
|
||||||
}
|
}
|
||||||
} else if (symbol === 'AMZN') {
|
} else if (symbol === 'AMZN') {
|
||||||
return Promise.resolve(2021.99);
|
return Promise.resolve({ marketPrice: new Big('2021.99') });
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(new Big('0'));
|
return Promise.resolve({ marketPrice: new Big('0') });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Currency } from '@prisma/client';
|
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/core/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/core/current-rate.service';
|
||||||
import { OrderType } from '@ghostfolio/api/models/order-type';
|
import { OrderType } from '@ghostfolio/api/models/order-type';
|
||||||
|
import { Currency } from '@prisma/client';
|
||||||
import Big from 'big.js';
|
import Big from 'big.js';
|
||||||
import {
|
import {
|
||||||
addDays,
|
addDays,
|
||||||
@ -110,7 +110,7 @@ export class PortfolioCalculator {
|
|||||||
|
|
||||||
const result: { [symbol: string]: TimelinePosition } = {};
|
const result: { [symbol: string]: TimelinePosition } = {};
|
||||||
for (const item of lastTransactionPoint.items) {
|
for (const item of lastTransactionPoint.items) {
|
||||||
const marketPrice = await this.currentRateService.getValue({
|
const marketValue = await this.currentRateService.getValue({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
symbol: item.symbol,
|
symbol: item.symbol,
|
||||||
currency: item.currency,
|
currency: item.currency,
|
||||||
@ -122,7 +122,7 @@ export class PortfolioCalculator {
|
|||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
symbol: item.symbol,
|
symbol: item.symbol,
|
||||||
investment: item.investment,
|
investment: item.investment,
|
||||||
marketPrice: marketPrice,
|
marketPrice: marketValue.marketPrice,
|
||||||
transactionCount: item.transactionCount
|
transactionCount: item.transactionCount
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -186,7 +186,7 @@ export class PortfolioCalculator {
|
|||||||
currency: item.currency,
|
currency: item.currency,
|
||||||
userCurrency: this.currency
|
userCurrency: this.currency
|
||||||
})
|
})
|
||||||
.then((v) => new Big(v).mul(item.quantity))
|
.then(({ marketPrice }) => new Big(marketPrice).mul(item.quantity))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user