feat(assets): dönemsel rapor, net varlık ve aynî kayıt bağlantısı
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,14 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { formatBp, holdingSummary, runMovements, type MovementInput } from "./portfolio";
|
||||
import {
|
||||
formatBp,
|
||||
holdingSummary,
|
||||
periodPoints,
|
||||
periodReport,
|
||||
runMovements,
|
||||
sumReports,
|
||||
type DatedMovement,
|
||||
type MovementInput,
|
||||
} from "./portfolio";
|
||||
|
||||
const buy = (quantity: number, amountMinor: number): MovementInput => ({
|
||||
kind: "buy",
|
||||
@@ -70,3 +79,67 @@ describe("ağırlıklı ortalama maliyet", () => {
|
||||
expect(formatBp(0)).toBe("%0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("dönemsel rapor", () => {
|
||||
it("noktalar: başlangıç, ay sonları, bitiş", () => {
|
||||
expect(periodPoints("2026-05-06", "2026-08-15")).toEqual([
|
||||
"2026-05-06",
|
||||
"2026-05-31",
|
||||
"2026-06-30",
|
||||
"2026-07-31",
|
||||
"2026-08-15",
|
||||
]);
|
||||
expect(periodPoints("2026-05-31", "2026-06-30")).toEqual(["2026-05-31", "2026-06-30"]);
|
||||
});
|
||||
|
||||
// Örnek: 10 gr'ı 25.000'e aldım, her ay 1 gr ekledim; 6 ay sonra ne kazandım?
|
||||
const prices: Record<string, number> = {
|
||||
"2026-01-01": 2_500_00,
|
||||
"2026-02-01": 2_600_00,
|
||||
"2026-03-01": 2_700_00,
|
||||
"2026-07-01": 3_000_00,
|
||||
};
|
||||
const priceAt = (date: string) => {
|
||||
const keys = Object.keys(prices)
|
||||
.filter((k) => k <= date)
|
||||
.sort();
|
||||
return keys.length ? prices[keys.at(-1)!]! : null;
|
||||
};
|
||||
const ms: DatedMovement[] = [
|
||||
{ kind: "buy", date: "2026-01-01", quantity: 10_000, amountMinor: 25_000_00 },
|
||||
{ kind: "buy", date: "2026-02-01", quantity: 1_000, amountMinor: 2_600_00 },
|
||||
{ kind: "buy", date: "2026-03-01", quantity: 1_000, amountMinor: 2_700_00 },
|
||||
{ kind: "gift_out", date: "2026-07-01", quantity: 1_000, amountMinor: 0 },
|
||||
];
|
||||
|
||||
it("örnek senaryo: yatırılan, dönem sonu değer, kâr", () => {
|
||||
const r = periodReport(ms, priceAt, "2026-01-01", "2026-07-02");
|
||||
expect(r.startValueMinor).toBe(0);
|
||||
expect(r.investedMinor).toBe(30_300_00);
|
||||
// 11 gr × 3.000 = 33.000; çıkan 1 gr o günkü değeri 3.000
|
||||
expect(r.endValueMinor).toBe(33_000_00);
|
||||
expect(r.giftOutMinor).toBe(3_000_00);
|
||||
expect(r.profitMinor).toBe(5_700_00);
|
||||
expect(r.profitBp).toBe(1881);
|
||||
expect(r.points.map((p) => p.valueMinor)).toEqual([
|
||||
0, 25_000_00, 28_600_00, 32_400_00, 32_400_00, 32_400_00, 32_400_00, 33_000_00,
|
||||
]);
|
||||
});
|
||||
|
||||
it("başlangıçta eldeki değer dönem içi kârdan düşülür; fiyat yoksa null", () => {
|
||||
const r = periodReport(ms, priceAt, "2026-03-15", "2026-07-02");
|
||||
expect(r.startValueMinor).toBe(32_400_00);
|
||||
expect(r.investedMinor).toBe(0);
|
||||
expect(r.profitMinor).toBe(33_000_00 + 3_000_00 - 32_400_00);
|
||||
expect(periodReport(ms, () => null, "2026-01-01", "2026-07-02").profitMinor).toBeNull();
|
||||
});
|
||||
|
||||
it("toplam", () => {
|
||||
const a = periodReport(ms, priceAt, "2026-01-01", "2026-07-02");
|
||||
const t = sumReports([a, a], "2026-01-01", "2026-07-02");
|
||||
expect(t.investedMinor).toBe(60_600_00);
|
||||
expect(t.profitMinor).toBe(11_400_00);
|
||||
expect(t.points.at(-1)!.valueMinor).toBe(66_000_00);
|
||||
expect(sumReports([], "2026-01-01", "2026-01-31").points).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user