@@ -18,6 +18,7 @@ under the terms of the QLNet license. You should have received a
1818 FOR A PARTICULAR PURPOSE. See the license for more details.
1919*/
2020
21+ using System ;
2122using System . Collections . Generic ;
2223using System . Linq ;
2324
@@ -33,6 +34,44 @@ namespace QLNet
3334 - price/yield calculations are checked against known good values. */
3435 public class Bond : Instrument
3536 {
37+ public enum BondEquivalentYearType
38+ {
39+ Always365 ,
40+ Always366 ,
41+ SettlementFwdOneYear ,
42+ IssueFwdOneYear ,
43+ MaturityBackOneYear
44+ }
45+
46+ /// <summary>
47+ /// Bond price information
48+ /// </summary>
49+ public class Price
50+ {
51+ private double ? amount_ ;
52+ private Type type_ ;
53+ public enum Type { Dirty , Clean }
54+
55+ public Price ( )
56+ {
57+ amount_ = null ;
58+ }
59+
60+ public Price ( double amount , Type type )
61+ {
62+ amount_ = amount ;
63+ type_ = type ;
64+ }
65+
66+ public double amount ( )
67+ {
68+ Utils . QL_REQUIRE ( amount_ != null , ( ) => "no amount given" ) ;
69+ return amount_ . Value ;
70+ }
71+
72+ public Type type ( ) { return type_ ; }
73+ }
74+
3675 #region Constructors
3776 //! constructor for amortizing or non-amortizing bonds.
3877 /*! Redemptions and maturity are calculated from the coupon
@@ -282,6 +321,59 @@ public virtual double accruedAmount(Date settlement = null)
282321
283322 }
284323
324+ // Bond equivalent yield
325+ public virtual double bondEquivalentYield ( BondEquivalentYearType yearType , DateTime settlementDate , decimal price ,
326+ DayCounter dc , Calendar calendar , Frequency frequency )
327+ {
328+ Utils . QL_REQUIRE ( settlementDate != DateTime . MinValue , ( ) => "invalid settlement date" ) ;
329+ Utils . QL_REQUIRE ( maturityDate_ != null , ( ) => "maturity date not provided" ) ;
330+ double yearDays = 365 ;
331+
332+ // Year type calculation
333+ bool eom ;
334+ Date endDate ;
335+ switch ( yearType )
336+ {
337+ case BondEquivalentYearType . Always365 :
338+ break ;
339+ case BondEquivalentYearType . Always366 :
340+ yearDays = 366 ;
341+ break ;
342+ case BondEquivalentYearType . SettlementFwdOneYear :
343+ eom = calendar . isEndOfMonth ( settlementDate ) ;
344+ endDate = calendar . advance ( settlementDate , new Period ( 1 , TimeUnit . Years ) , BusinessDayConvention . Unadjusted , eom ) ;
345+ yearDays = dc . dayCount ( settlementDate , endDate ) ;
346+ break ;
347+ case BondEquivalentYearType . IssueFwdOneYear :
348+ if ( issueDate_ != null )
349+ {
350+ eom = calendar . isEndOfMonth ( issueDate_ ) ;
351+ endDate = calendar . advance ( issueDate_ , new Period ( 1 , TimeUnit . Years ) , BusinessDayConvention . Unadjusted , eom ) ;
352+ yearDays = dc . dayCount ( issueDate_ , endDate ) ;
353+ }
354+ break ;
355+ case BondEquivalentYearType . MaturityBackOneYear :
356+ eom = calendar . isEndOfMonth ( maturityDate_ ) ;
357+ endDate = calendar . advance ( maturityDate_ , new Period ( - 1 , TimeUnit . Years ) , BusinessDayConvention . Unadjusted , eom ) ;
358+ yearDays = dc . dayCount ( endDate , maturityDate_ ) ;
359+ break ;
360+ default :
361+ throw new ArgumentOutOfRangeException ( nameof ( yearType ) , yearType , null ) ;
362+ }
363+
364+ // BEY Calc
365+ double daysToMaturity = dc . dayCount ( settlementDate , maturityDate_ ) ;
366+ var periodLenght = Math . Ceiling ( 365 / ( double ) frequency ) ;
367+ if ( daysToMaturity <= periodLenght )
368+ {
369+ return ( double ) ( ( 100 - price ) / price * ( ( decimal ) yearDays / dc . dayCount ( settlementDate , maturityDate_ ) ) ) ;
370+ }
371+ var numerator = ( ( - 2 * daysToMaturity ) / yearDays ) + 2 * Math . Pow (
372+ Math . Pow ( daysToMaturity / yearDays , 2 ) - ( ( 2 * daysToMaturity / yearDays - 1 ) * ( 1 - 100 / ( double ) price ) ) , 0.5 ) ;
373+ var denominator = 2 * daysToMaturity / yearDays - 1 ;
374+ return numerator / denominator ;
375+ }
376+
285377 #endregion
286378
287379 /*! Expected next coupon: depending on (the bond and) the given date
0 commit comments