Artykuł techniczny

Formuły obligacji i bonów skarbowych w Delphi z HotXLS

HotXLS oblicza natywnie w Delphi i C++Builder finansowe funkcje obligacji i bonów skarbowych Excela: PRICE, YIELD, DURATION, MDURATION, sześć prymitywów dat kuponów, trójkę funkcji bonów skarbowych, rodzinę dyskontową i francuskie funkcje amortyzacji. Obliczają się one wewnątrz własnego silnika formuł biblioteki, więc skoroszyt wyceniający portfel obligacji przelicza się na serwerze bez Excela i bez dodatku Analysis ToolPak gdziekolwiek w zasięgu wzroku

Te funkcje kiedyś należały do Analysis ToolPak, dlatego wciąż mają opinię kłopotliwych. Od Excela 2007 są częścią standardowego zestawu funkcji i są zapisywane w formacie pliku bez ozdobników, bez prefiksu _xlfn., który niosą naprawdę nowsze funkcje. Skoroszyt ich używający jest zwykłym skoroszytem; jedyne pytanie brzmi, czy cokolwiek go przelicza, zna te funkcje

Co silnik dat kuponów musi zrobić poprawnie

Każde obliczenie obligacji opiera się na tych samych prymitywach: ile dni minęło od ostatniego kuponu, ile dni ma bieżący okres kuponowy, ile dni pozostało do następnego kuponu, kiedy przypadają te kupony i ile ich zostało. To COUPDAYBS, COUPDAYS, COUPDAYSNC, COUPPCD, COUPNCD i COUPNUM, a wywołuje je każda funkcja ceny i rentowności

Nietrywialny czyni je argument bazy. W grze jest pięć konwencji liczenia dni — 30/360 US, actual/actual, actual/360, actual/365 i 30/360 European — a różnią się one w kwestiach takich jak to, co dzieje się, gdy data rozliczenia przypada na 31. dzień miesiąca, albo jak zachowuje się lutowa data kuponu w roku przestępnym. Poprawne zaimplementowanie jednej konwencji i przybliżenie pozostałych daje wyniki, które wyglądają wiarygodnie, a są błędne o kilka punktów bazowych, co przy dużej pozycji oznacza realne pieniądze

HotXLS implementuje wszystkie pięć w samodzielnym, zagnieżdżonym silniku dat kuponów, współdzielonym przez każdą funkcję, która go potrzebuje, dzięki czemu PRICE i DURATION nie mogą się między sobą różnić co do tego, gdzie leżą daty kuponów

Silnik dat kuponów HotXLS dla Delphi: dane wejściowe obligacji i wybrana baza liczenia dni zasilają sześć prymitywów COUP współdzielonych przez PRICE, YIELD, DURATION i MDURATION
Wszystkie pięć baz liczenia dni zasila jeden zagnieżdżony silnik dat kuponów współdzielony przez każdą funkcję finansową w HotXLS, dzięki czemu PRICE i DURATION nigdy nie mogą się różnić co do tego, gdzie przypadają daty kuponów

Wycena i rentowność w skoroszycie

W kodzie wywołującym nie ma niczego szczególnego: zapisz formuły, przelicz, odczytaj wartości. O to właśnie chodzi — skoroszyt jest modelem, a twoja aplikacja Delphi jest kalkulatorem:

uses
  lxHandleX;

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Bond');
    Sheet.Cells[1, 1].Value := EncodeDate(2026, 3, 15);   // rozliczenie
    Sheet.Cells[2, 1].Value := EncodeDate(2034, 11, 15);  // termin wykupu
    Sheet.Cells[3, 1].Value := 0.0425;                    // stopa kuponu
    Sheet.Cells[4, 1].Value := 0.0391;                    // wymagana rentowność
    Sheet.Cells[5, 1].Value := 100;                       // wartość wykupu
    Sheet.Cells[6, 1].Value := 2;                         // częstotliwość
    Sheet.Cells[7, 1].Value := 1;                         // baza: actual/actual

    Sheet.Cells[9, 1].Formula  := '=PRICE(A1,A2,A3,A4,A5,A6,A7)';
    Sheet.Cells[10, 1].Formula := '=DURATION(A1,A2,A3,A4,A6,A7)';
    Sheet.Cells[11, 1].Formula := '=MDURATION(A1,A2,A3,A4,A6,A7)';
    Sheet.Cells[12, 1].Formula := '=COUPNUM(A1,A2,A6,A7)';

    Book.Recalculate;

    Writeln(Format('price %.6f  Macaulay %.6f  modified %.6f  coupons %d',
      [Double(Sheet.Cells[9, 1].Value), Double(Sheet.Cells[10, 1].Value),
       Double(Sheet.Cells[11, 1].Value),
       Integer(Sheet.Cells[12, 1].Value)]));
  finally
    Book.Free;
  end;
end;

Daty wchodzą jako prawdziwe wartości daty, a nie jako łańcuchy tekstowe. Data tekstowa działa w Excelu, ponieważ Excel ją wymusza, a to wymuszanie zależy od locale, co jest dokładnie takim rodzajem zależności, jakiej przeliczanie po stronie serwera nie powinno mieć. Reguły systemu dat leżące u podstaw opisano w numerach seryjnych dat i systemie 1904

Rentowność nie ma postaci zamkniętej, i to ma konsekwencje

PRICE to obliczenie bezpośrednie: zdyskontuj przepływy pieniężne, zsumuj je. YIELD jest jej odwrotnością i nie ma rozwiązania w postaci zamkniętej dla obligacji z więcej niż jednym pozostałym kuponem, więc trzeba je rozwiązać numerycznie. HotXLS używa solvera bisekcji, który zbiega niezawodnie, choć nie szybko

Wynikają z tego dwie praktyczne konsekwencje. Rentowność jest mierzalnie droższa niż cena, więc przewartościowanie portfela obliczające rentowności dla dziesięciu tysięcy pozycji kosztuje zauważalnie więcej niż obliczające ceny, i warto wiedzieć, którego z nich naprawdę potrzebuje twój model. A rentowność, której nie da się objąć nawiasem — ponieważ dane wejściowe opisują obligację, której żadna rentowność nie wyceni na daną wartość — zwraca błąd, a nie błędną liczbę, co jest poprawnym zachowaniem i tym, które warto testować

W HotXLS na Delphi PRICE to jeden bezpośredni przebieg zdyskontowanych przepływów pieniężnych, podczas gdy YIELD nie ma postaci zamkniętej i jest rozwiązywana pętlą bisekcji, która zwraca błąd, gdy nie istnieje przedział obejmujący rozwiązanie
PRICE to jeden bezpośredni przebieg po zdyskontowanych przepływach pieniężnych, podczas gdy YIELD nie ma postaci zamkniętej i jest obejmowana nawiasem oraz rozwiązywana bisekcją, zwracając błąd zamiast błędnej liczby, gdy danych wejściowych nie da się wycenić

Bony skarbowe to osobna rodzina

Bony to instrumenty dyskontowe bez kuponów, więc korzystają z TBILLPRICE, TBILLYIELD i TBILLEQ, a nie z funkcji obligacji. TBILLEQ zamienia stopę dyskontową na rentowność ekwiwalentną obligacji i ma udokumentowaną osobliwość wartą poznania: dla terminów wykupu dłuższych niż 182 dni konwersja jest kwadratowa, a nie liniowa, ponieważ papier obejmuje więcej niż jeden półroczny okres

Podanie bonu zamiast tego do PRICE, z zerowym kuponem i częstotliwością, nie daje tej samej liczby. Rodzina instrumentów dyskontowych — DISC, INTRATE, RECEIVED, PRICEDISC i YIELDDISC — istnieje właśnie dlatego, że te instrumenty są kwotowane i rozliczane inaczej

Reszta zestawu, w jednym miejscu

Poza obligacjami i bonami ten sam pakiet obejmuje funkcje, które otaczają je w prawdziwych modelach. PRICEMAT i YIELDMAT obsługują papiery wypłacające odsetki przy wykupie. ACCRINTM oblicza dla nich narosłe odsetki. EFFECT i NOMINAL konwertują między efektywną a nominalną stopą roczną, co jest konwersją najczęściej błędnie liczoną ręcznie. CUMIPMT i CUMPRINC dają skumulowane odsetki i kapitał w zakresie okresów. FVSCHEDULE stosuje po kolei serię różnych stóp, przyjmując zakres albo wartość skalarną

DOLLARDE i DOLLARFR konwertują między dziesiętnymi a ułamkowymi notowaniami dolarowymi, co ma znaczenie dla każdego instrumentu wciąż kwotowanego w trzydziestych drugich. A AMORDEGRC i AMORLINC implementują francuskie metody amortyzacji, wymagane przez francuskie zasady rachunkowości, a nie opcjonalne udogodnienia

// Konwersja stopy, którą ludzie rutynowo liczą źle ręcznie
Sheet.Cells[1, 3].Formula := '=EFFECT(0.0425,12)';   // nominalna na efektywną
Sheet.Cells[2, 3].Formula := '=NOMINAL(0.043338,12)'; // i z powrotem

// Skumulowane odsetki za pierwsze dwa lata 25-letniego kredytu
Sheet.Cells[4, 3].Formula :=
  '=CUMIPMT(0.0389/12,300,340000,1,24,0)';

// Projekcja przy zmiennej stopie: 3 procent, potem 3,5, potem 4
Sheet.Cells[6, 3].Formula := '=FVSCHEDULE(10000,{0.03;0.035;0.04})';

Walidacja implementacji, której nie napisałeś

Funkcje finansowe to jeden obszar, w którym „zwraca liczbę” niczego nie dowodzi. Liczba jest z konstrukcji wiarygodna, a błąd w konwencji liczenia dni przesuwa wyniki o wartość zbyt małą, by ją zauważyć, i zbyt dużą, by ją zaakceptować

Test, który naprawdę działa, jest testem różnicowym. Zbuduj arkusz reprezentatywnych instrumentów — po jednej obligacji na każdą bazę liczenia dni, jedną rozliczaną na koniec miesiąca, jedną obejmującą przestępny luty, jeden bon poniżej 182 dni i jeden powyżej — oblicz wartości raz w Excelu, zapisz je jako oczekiwane wyniki i porównuj z nimi po każdej aktualizacji biblioteki. Pozycje przekraczające datę kuponu i rozliczenia na 31. dzień miesiąca to miejsca, w których implementacje się rozjeżdżają, więc celowo uwzględnij oba przypadki

Zestaw walidacji różnicowej dla funkcji obligacji i bonów skarbowych HotXLS w Delphi: reprezentatywne instrumenty, w tym przypadki roku przestępnego i końca miesiąca, obliczone raz w Excelu i porównywane po każdej aktualizacji biblioteki
Zestaw różnicowy wycenia reprezentatywne instrumenty raz w Excelu, zapisuje wartości jako oczekiwania i porównuje z nimi po każdej aktualizacji HotXLS, dzięki czemu dryf o punkty bazowe nie może ukryć się za wiarygodnym wynikiem

Jeśli chodzi o same skoroszyty, dwoma towarzyszami tej funkcji są silnik obliczeniowy decydujący, co przeliczyć, opisany w przyrostowym przeliczaniu i grafie zależności, oraz rodzina statystyczna opisana w funkcjach rozkładów statystycznych. Razem to one pozwalają modelowi finansowemu działać bez nadzoru na serwerze, co było całym powodem ich natywnej implementacji

Finanse obligacji, statystyka, funkcje inżynierskie i silnik przeliczania są dostarczane w jednej bibliotece dla Delphi i C++Builder; pełną listę funkcji znajdziesz na stronie komponentu arkusza kalkulacyjnego HotXLS dla Delphi