Technisch artikel

Obligatie- en treasuryformules in Delphi met HotXLS

HotXLS evalueert de obligatie- en treasury-financefuncties van Excel native in Delphi en C++Builder: PRICE, YIELD, DURATION, MDURATION, de zes coupondatum-primitieven, het treasury-billtrio, de discount-familie en de Franse afschrijvingsfuncties. Ze rekenen binnen de eigen formule-engine van de library, zodat een werkmap die een obligatieportefeuille prijst, opnieuw kan worden berekend op een server zonder Excel en zonder Analysis ToolPak-invoegtoepassing in zicht

Deze functies zaten vroeger in de Analysis ToolPak, wat verklaart waarom ze nog steeds de reputatie hebben lastig te zijn. Sinds Excel 2007 maken ze deel uit van de standaardfunctieset en worden ze kaal geschreven in het bestandsformaat, zonder het _xlfn.-voorvoegsel dat werkelijk nieuwere functies dragen. Een werkmap die ze gebruikt, is een gewone werkmap; de enige vraag is of wat hem ook maar herberekent, de functies kent

Wat de coupondatum-engine correct moet doen

Elke obligatieberekening berust op dezelfde primitieven: hoeveel dagen sinds de laatste coupon, hoeveel dagen in de huidige couponperiode, hoeveel dagen tot de volgende coupon, wanneer die coupons vallen, en hoeveel er nog resteren. Dat zijn COUPDAYBS, COUPDAYS, COUPDAYSNC, COUPPCD, COUPNCD en COUPNUM, en elke prijs- en rendementsfunctie roept ze aan

Wat ze niet-triviaal maakt, is het basis-argument. Er spelen vijf day-count-conventies mee — 30/360 US, actual/actual, actual/360, actual/365 en 30/360 Europees — en ze zijn het oneens over vragen zoals wat er gebeurt wanneer een settlementdatum op de 31e van een maand valt, of hoe een coupondatum in februari zich gedraagt in een schrikkeljaar. Eén conventie correct implementeren en de andere benaderen levert resultaten op die plausibel lijken en enkele basispunten fout zitten, wat bij een grote positie echt geld is

HotXLS implementeert alle vijf in een zelfstandige, geneste coupondatum-engine die wordt gedeeld door elke functie die hem nodig heeft, zodat PRICE en DURATION het onderling niet oneens kunnen zijn over waar de coupondatums liggen

HotXLS coupondatum-engine voor Delphi: obligatie-inputs en de gekozen day-count-basis voeden zes COUP-primitieven die worden gedeeld door PRICE, YIELD, DURATION en MDURATION
Alle vijf day-count-bases voeden één geneste coupondatum-engine die wordt gedeeld door elke financiële functie in HotXLS, zodat PRICE en DURATION het nooit oneens kunnen zijn over waar de coupondatums vallen

Prijsstelling en rendement in een werkmap

Er is niets bijzonders aan de aanroepende code: schrijf de formules, herbereken, lees de waarden. Dat is precies het punt — de werkmap is het model, en uw Delphi-toepassing is de rekenmachine:

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);   // settlement
    Sheet.Cells[2, 1].Value := EncodeDate(2034, 11, 15);  // vervaldatum
    Sheet.Cells[3, 1].Value := 0.0425;                    // couponrente
    Sheet.Cells[4, 1].Value := 0.0391;                    // vereist rendement
    Sheet.Cells[5, 1].Value := 100;                       // aflossing
    Sheet.Cells[6, 1].Value := 2;                         // frequentie
    Sheet.Cells[7, 1].Value := 1;                         // basis: 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;

Datums gaan erin als echte datumwaarden in plaats van als strings. Een tekstdatum werkt in Excel omdat Excel deze afdwingt, en die afdwinging is afhankelijk van de locale, wat precies het soort afhankelijkheid is die een server-side herberekening niet zou moeten hebben. De onderliggende datumsysteemregels worden behandeld in datumserienummers en het 1904-systeem

Yield heeft geen gesloten vorm, en dat heeft gevolgen

PRICE is een directe berekening: verdisconteer de cashflows, tel ze op. YIELD is de inverse ervan en heeft geen gesloten-vorm-oplossing voor een obligatie met meer dan één resterende coupon, dus moet het numeriek worden opgelost. HotXLS gebruikt een bisectie-oplosser, die betrouwbaar convergeert in plaats van snel

Daaruit volgen twee praktische gevolgen. Yield is meetbaar duurder dan price, dus een portefeuilleherwaardering die yields berekent voor tienduizend posities kost merkbaar meer dan één die prices berekent, en het is de moeite waard te weten welke van de twee uw model daadwerkelijk nodig heeft. En een yield die niet kan worden ingesloten — omdat de inputs een obligatie beschrijven die door geen enkele yield tegen de gegeven waarde kan worden geprijsd — retourneert een fout in plaats van een fout getal, wat het juiste gedrag is en waarop u moet testen

In HotXLS op Delphi is PRICE één directe pass over verdisconteerde cashflows, terwijl YIELD geen gesloten vorm heeft en wordt opgelost door een bisectielus die een fout retourneert wanneer er geen insluiting bestaat
PRICE is één directe pass over de verdisconteerde cashflows, terwijl YIELD geen gesloten vorm heeft en wordt ingesloten en bisecteerd, waarbij een fout wordt geretourneerd in plaats van een fout getal wanneer de inputs niet kunnen worden geprijsd

Treasury bills vormen hun eigen familie

Bills zijn discount-instrumenten zonder coupons, dus gebruiken ze TBILLPRICE, TBILLYIELD en TBILLEQ in plaats van de obligatiefuncties. TBILLEQ zet een discountrente om naar een bond-equivalent rendement, en het heeft een gedocumenteerde eigenaardigheid die het waard is te kennen: voor looptijden van meer dan 182 dagen is de omzetting kwadratisch in plaats van lineair, omdat het effect meer dan één halfjaarperiode overspant

Een bill in plaats daarvan invoeren in PRICE, met een nulcoupon en een frequentie, levert niet hetzelfde getal op. De discount-instrumentfamilie — DISC, INTRATE, RECEIVED, PRICEDISC en YIELDDISC — bestaat juist omdat deze instrumenten anders worden genoteerd en afgewikkeld

De rest van de set, op één plek

Naast obligaties en bills dekt dezelfde batch de functies die er in echte modellen omheen zitten. PRICEMAT en YIELDMAT behandelen effecten die rente uitkeren bij vervaldatum. ACCRINTM berekent de opgelopen rente daarvoor. EFFECT en NOMINAL zetten om tussen effectieve en nominale jaarlijkse rentes, wat de omzetting is die het vaakst met de hand fout wordt gedaan. CUMIPMT en CUMPRINC geven cumulatieve rente en aflossing over een reeks periodes. FVSCHEDULE past een reeks verschillende rentes achtereenvolgens toe, en neemt daarbij een bereik of een scalar

DOLLARDE en DOLLARFR zetten om tussen decimale en gebroken dollarnoteringen, wat van belang is voor elk instrument dat nog in tweeëndertigsten wordt genoteerd. En AMORDEGRC en AMORLINC implementeren de Franse afschrijvingsmethoden, die verplicht zijn volgens de Franse boekhoudregels en geen optioneel gemak zijn

// Een rente-omzetting die mensen routinematig met de hand fout doen
Sheet.Cells[1, 3].Formula := '=EFFECT(0.0425,12)';   // nominaal naar effectief
Sheet.Cells[2, 3].Formula := '=NOMINAL(0.043338,12)'; // en terug

// Cumulatieve rente over de eerste twee jaar van een lening van 25 jaar
Sheet.Cells[4, 3].Formula :=
  '=CUMIPMT(0.0389/12,300,340000,1,24,0)';

// Een projectie met variabele rente: 3 procent, dan 3,5, dan 4
Sheet.Cells[6, 3].Formula := '=FVSCHEDULE(10000,{0.03;0.035;0.04})';

Een implementatie valideren die u niet zelf hebt geschreven

Financiële functies zijn het ene gebied waar "hij geeft een getal terug" geen enkel bewijs is. Het getal is door constructie plausibel, en een fout in een day-count-conventie verschuift resultaten met een bedrag dat te klein is om op te merken en te groot om te accepteren

De test die daadwerkelijk werkt, is een differentiële. Bouw een spreadsheet met representatieve instrumenten — één obligatie per day-count-basis, één die op een maandeinde afwikkelt, één die een schrikkeljaarfebruari overspant, één bill onder de 182 dagen en één erboven — bereken de waarden één keer in Excel, bewaar ze als verwachte resultaten, en toets eraan na elke library-upgrade. Posities die over een coupondatum heen lopen en afwikkelingen op de 31e zijn waar implementaties uiteenlopen, dus neem beide bewust op

Differentiële validatieset voor de HotXLS-obligatie- en treasuryfuncties in Delphi: representatieve instrumenten, inclusief schrikkeljaar- en maandeindecases, worden één keer in Excel berekend en getoetst na elke library-upgrade
Een differentiële suite prijst representatieve instrumenten één keer in Excel, bewaart de waarden als verwachtingen en toetst ze na elke HotXLS-upgrade, zodat basispuntafwijking zich niet kan verschuilen achter plausibele output

Voor de werkmappen zelf zijn de twee metgezellen van deze functie de rekenengine die beslist wat opnieuw moet worden berekend, beschreven in incrementele herberekening en de afhankelijkheidsgraaf, en de statistische familie behandeld in statistische verdelingsfuncties. Samen zijn ze wat een financieel model onbeheerd op een server laat draaien, wat de hele reden was om ze native te implementeren

Obligatiefinanciering, statistiek, technische functies en de herberekeningsengine worden geleverd in één library voor Delphi en C++Builder; de volledige functielijst staat op de HotXLS Delphi spreadsheetcomponent-pagina