feat: expose broker position counts

This commit is contained in:
Sergey Krylov 2026-06-18 23:15:32 +03:00
parent da6d05e425
commit 8aea56cbbf
5 changed files with 53 additions and 1 deletions

View File

@ -42,10 +42,27 @@ export class BrokerPortfolioYieldsDto {
dailyPercent!: number | null;
}
export class BrokerPortfolioPositionCountsDto {
@ApiProperty({ minimum: 0 })
shares!: number;
@ApiProperty({ minimum: 0 })
bonds!: number;
@ApiProperty({ minimum: 0 })
etf!: number;
@ApiProperty({ minimum: 0 })
other!: number;
}
export class BrokerPortfolioResponseDto {
@ApiProperty({ type: BrokerAccountResponseDto })
account!: BrokerAccountResponseDto;
@ApiProperty({ type: BrokerPortfolioPositionCountsDto })
positionCounts!: BrokerPortfolioPositionCountsDto;
@ApiProperty({ type: BrokerPortfolioTotalsDto })
totals!: BrokerPortfolioTotalsDto;

View File

@ -30,6 +30,12 @@ describe('portfolio.mapper', () => {
currentPrice: { currency: 'rub', units: '250', nano: 0 },
averagePositionPrice: { currency: 'rub', units: '200', nano: 0 },
},
{ instrumentType: 'SHARE' },
{ instrumentType: 'bond' },
{ instrumentType: 'etf' },
{ instrumentType: 'fund' },
{ instrumentType: 'future' },
{},
],
},
positions: {
@ -43,5 +49,6 @@ describe('portfolio.mapper', () => {
expect(result.totals.shares?.value).toBe(1000);
expect(result.cash[0].value).toBe(500);
expect(result.blockedCash[0].value).toBe(10);
expect(result.positionCounts).toEqual({ shares: 2, bonds: 1, etf: 2, other: 2 });
});
});

View File

@ -74,8 +74,28 @@ export function mapBrokerPosition(input: {
}
export function mapBrokerPortfolio(input: MapBrokerPortfolioInput): BrokerPortfolio {
const positionCounts = { shares: 0, bonds: 0, etf: 0, other: 0 };
for (const position of input.portfolio.positions ?? []) {
switch (position.instrumentType?.toLowerCase()) {
case 'share':
positionCounts.shares += 1;
break;
case 'bond':
positionCounts.bonds += 1;
break;
case 'etf':
case 'fund':
positionCounts.etf += 1;
break;
default:
positionCounts.other += 1;
}
}
return {
account: input.account,
positionCounts,
totals: {
shares: mapMoneyValue(input.portfolio.totalAmountShares),
bonds: mapMoneyValue(input.portfolio.totalAmountBonds),

View File

@ -46,7 +46,7 @@ describe('BrokerPortfolioService', () => {
.mockResolvedValueOnce({
accountId: 'acc-1',
totalAmountPortfolio: { currency: 'rub', units: '1000', nano: 0 },
positions: [],
positions: [{ instrumentType: 'share' }, { instrumentType: 'bond' }],
})
.mockResolvedValueOnce({
accountId: 'acc-1',
@ -60,7 +60,9 @@ describe('BrokerPortfolioService', () => {
expect(result.data.account.id).toBe('acc-1');
expect(result.data.cash[0].value).toBe(1000);
expect(result.data.positionCounts).toEqual({ shares: 1, bonds: 1, etf: 0, other: 0 });
expect('positions' in result.data).toBe(false);
expect(client.callUnary).toHaveBeenCalledTimes(2);
expect(cache.getOrFetch).toHaveBeenCalledWith(
'tbank:portfolio',
['acc-1'],

View File

@ -33,6 +33,12 @@ export type BrokerPosition = {
export type BrokerPortfolio = {
account: BrokerAccount;
positionCounts: {
shares: number;
bonds: number;
etf: number;
other: number;
};
totals: {
shares: BrokerMoney | null;
bonds: BrokerMoney | null;