feat(performance): investment performance vs Moscow Exchange index (WealthLedger/main#54) (#200)
Backend verify / commits (push) Skipped
Backend verify / security (push) Successful in 1m7s
Backend verify / verify (push) Failing after 7m19s

This commit was merged in pull request #200.
This commit is contained in:
2026-11-08 16:45:29 +03:00
parent 84433ee207
commit fee61537b6
28 changed files with 1507 additions and 4 deletions
@@ -0,0 +1,52 @@
import { Injectable } from '@nestjs/common';
import type { MoexIndexClose } from '#src/integrations/moex/moex.client';
import { PrismaService } from '#src/prisma/prisma.service';
@Injectable()
export class MarketIndexRepository {
constructor(private readonly prisma: PrismaService) {}
async read(secid: 'IMOEX' | 'MCFTRR', from: string, to: string) {
const [closes, state] = await Promise.all([
this.prisma.marketIndexDailyClose.findMany({
where: { secid, tradeDate: { gte: from, lte: to } },
orderBy: { tradeDate: 'asc' },
}),
this.prisma.marketIndexSyncState.findUnique({ where: { secid } }),
]);
return { closes, state };
}
async attempt(secid: 'IMOEX' | 'MCFTRR', at: Date) {
await this.prisma.marketIndexSyncState.upsert({
where: { secid }, create: { secid, lastAttemptAt: at }, update: { lastAttemptAt: at },
});
}
// coveredTo is the `till` MOEX answered for: a close for today that is not
// published yet is re-checked by the service while the last close < to.
async save(secid: 'IMOEX' | 'MCFTRR', closes: MoexIndexClose[], from: string, to: string, at: Date) {
await this.prisma.$transaction(async (tx) => {
for (const close of closes) {
await tx.marketIndexDailyClose.upsert({
where: { secid_tradeDate: { secid, tradeDate: close.tradeDate } },
create: { secid, ...close },
update: { close: close.close },
});
}
const state = await tx.marketIndexSyncState.findUniqueOrThrow({ where: { secid } });
const joined = Boolean(state.coveredFrom && state.coveredTo &&
from <= state.coveredTo && to >= state.coveredFrom);
await tx.marketIndexSyncState.update({
where: { secid },
data: {
lastSuccessAt: at,
coveredFrom: joined && state.coveredFrom && state.coveredFrom < from ? state.coveredFrom : from,
coveredTo: joined && state.coveredTo && state.coveredTo > to ? state.coveredTo : to,
},
});
});
}
}