Showing posts with label Bond Pricing. Show all posts
Showing posts with label Bond Pricing. Show all posts

Tuesday, April 30, 2019

Python-SciPy: Solving Zero-Coupon Term Structure Using Optimization

This post is presenting how to use Python SciPy Optimization package for solving out zero-coupon rate term structure from a given set of zero-coupon bond prices. Needless to say, we do not need any numerical method to do this, since we have exact analytical formulas for backing out zero-coupon rates from zero-coupon bond prices. However, approach presented in this relatively simple example can be applied into more interesting schemes, such as solving out smooth Libor forward curve from a given set of vanilla swaps. Example of such scheme using Solver and Excel/VBA can be found in here.

Let us assume zero-coupon rate term structure as shown below. Let us also assume, that the only information we can observe is the zero-coupon bond prices. Our task is to solve zero-coupon rates term structure.
















In a nutshell, we first set initial guesses for ten zero-coupon rates. After this we minimize sum of squared differences between adjacent zero-coupon rates (in objective function), but subject to constraints. In this case, constraints are differences between observed zero-coupon bond market prices and calculated bond prices, which need to be pushed to zero. When this optimization task has been completed, the resulting zero-coupon term structure will successfully re-price the current zero-coupon bond market, while curve smoothness is maximized. Example program results are shown below.


















Thanks for reading this blog.
-Mike

import numpy as np
import scipy.optimize as opt
import matplotlib.pyplot as pl

# sum of squared errors of decision variables
def ObjectiveFunction(x, args):
    return np.sum(np.power(np.diff(x), 2) * args[0])

# zero coupon bond pricing function
def ZeroCouponBond(x, args):
    # return difference between calculated and market price
    return ((1 / (1 + x[args[3]])**args[2]) - args[0]) * args[1]

# zero coupon bond pricing functions as constraints
# args: market price, scaling factor, maturity, index number for rate array
zeroPrices = ({'type': 'eq', 'fun': ZeroCouponBond, 'args': [[0.998801438274071, 1000000.0, 1.0, 0]]},
            {'type': 'eq', 'fun': ZeroCouponBond, 'args': [[0.996210802629012, 1000000.0, 2.0, 1]]},
            {'type': 'eq', 'fun': ZeroCouponBond, 'args': [[0.991943543964159, 1000000.0, 3.0, 2]]},
            {'type': 'eq', 'fun': ZeroCouponBond, 'args': [[0.981028206597786, 1000000.0, 4.0, 3]]},
            {'type': 'eq', 'fun': ZeroCouponBond, 'args': [[0.962851266220459, 1000000.0, 5.0, 4]]},
            {'type': 'eq', 'fun': ZeroCouponBond, 'args': [[0.946534719794057, 1000000.0, 6.0, 5]]},
            {'type': 'eq', 'fun': ZeroCouponBond, 'args': [[0.924997530805076, 1000000.0, 7.0, 6]]},
            {'type': 'eq', 'fun': ZeroCouponBond, 'args': [[0.912584111300984, 1000000.0, 8.0, 7]]},
            {'type': 'eq', 'fun': ZeroCouponBond, 'args': [[0.892632531026722, 1000000.0, 9.0, 8]]},
            {'type': 'eq', 'fun': ZeroCouponBond, 'args': [[0.877098137542374, 1000000.0, 10.0, 9]]})

# initial guesses for ten zero-coupon rates
initialGuess = np.full(10, 0.005)
model = opt.minimize(ObjectiveFunction, initialGuess, args = ([1000000.0]), method = 'SLSQP', constraints = zeroPrices)

# print selected model results
print('Success: ' + str(model.success))
print('Message: ' + str(model.message))
print('Number of iterations: ' + str(model.nit))
print('Objective function (sum of squared errors): ' + str(model.fun))
print('Changing variables (zero-coupon rates): ' + str(model.x * 100))
pl.plot(model.x)
pl.show()

Friday, May 11, 2018

C++/CLI Interoperability : Using QuantLib in C#, part II


This post is just a recap, which summarizes the content of two previous posts, published in here and here. It is presenting one possible solution for processing XML-based transactions by using QuantLib C++ native library, via C++/CLI wrapper in C# client. In order to implement the scheme presented here, all instructions given in those above-mentioned posts should be followed carefully.

Pipeline


Even the fanciest restaurant needs to have dedicated staff to visit fresh market and peel potatoes, in order to have everything ready for head chef to prepare delicious late night dinners for us. Similarly, having a "state-of-the-art" analytics library (such as QuantLib) available for calculations is still only "halfway home", since all required data inputs (ex. transactions) need to be constructed beforehand. It is highly preferred for this part of the process to be performed in a way, that maximum re-configurability would be maintained, while manual labour would be completely avoided.

In a nutshell, this post will present, how to go from having several XML transaction configuration files (such as the one example presented below) in a directory

<ZeroCouponBond>
  <transactionType>ZeroCouponBond</transactionType>
  <transactionID>DBS.1057</transactionID>
  <faceAmount>1000000</faceAmount>
  <tradeDate>2018-05-07T00:00:00</tradeDate>
  <settlementDate>2018-05-09T00:00:00</settlementDate>
  <maturityDate>2048-12-16T00:00:00</maturityDate>
  <calendar>SINGAPORE.SGX</calendar>
  <paymentConvention>MODIFIEDFOLLOWING</paymentConvention>
</ZeroCouponBond>

to have all these transactions constructed and processed through QuantLib. Console print below is showing 31 such constructed and processed dummy zero-coupon bond transactions.























C# project


Client

The story here goes roughly as follows. Create QuantLib::FlatForward [Wrapper.QlFlatForward] curve and QuantLib::DiscountingBondEngine [Wrapper.QlDiscountingBondEngine] instances by using C++/CLI wrapper classes. After this, all transactions will be created by Builder.TransactionsBuilder from specific directory, by using XML de-serialization. Instances of QuantLib::ZeroCouponBond [Wrapper.QlZeroCouponBond] objects will be created and paired with existing pricing engine (DiscountingBondEngine). Finally, PV for each transaction will be processed by pricing engine and resulting PV attribute will be stored back to transaction. Resulting information will also being printed back to console.

namespace Client {
    
    using Wrapper;
    using Builder;
    using Library;

    static class Program {
        static void Main(string[] args) {

            try {

                // use C++/CLI wrapper : create QuantLib::FlatForward as discounting curve
                double riskFreeRate = 0.015;
                string dayCounter = "ACTUAL360";
                DateTime settlementDate = new DateTime(2018, 5, 9);
                QlFlatForward discountingCurve = new QlFlatForward(riskFreeRate, dayCounter, settlementDate);

                // use C++/CLI wrapper : create QuantLib::DiscountingBondEngine for pricing
                QlDiscountingBondEngine pricer = new QlDiscountingBondEngine(discountingCurve);

                // use builder to create transactions from directory
                var transactions = TransactionsBuilder.Build();

                // use C++/CLI wrapper : create QuantLib::ZeroCouponBond instruments to list
                var zeroCouponBonds = transactions
                    .Where(t => t.transactionType == "ZeroCouponBond")
                    .Select(t => new QlZeroCouponBond(
                        t.faceAmount, 
                        t.tradeDate, 
                        t.settlementDate,
                        t.maturityDate, 
                        t.calendar, 
                        t.paymentConvention))
                    .ToList<QlZeroCouponBond>();

                // use C++/CLI wrapper : pair created bonds with pricing engine
                zeroCouponBonds.ForEach(z => z.SetPricingEngine(pricer));

                // use C++/CLI wrapper : assign processed pv attributes to transactions
                for(int i = 0; i < zeroCouponBonds.Count; i++) {
                    transactions[i].PV = zeroCouponBonds[i].PV();
                }

                // print information on transactions
                transactions.ForEach(t => {
                    string message = String.Format("ID:{0, -15} Maturity:{1, -15} PV:{2, -10}", 
                        t.transactionID,
                        t.maturityDate.ToShortDateString(),
                        String.Format("{0:0,0}", t.PV));
                    Console.WriteLine(message);
                });

                zeroCouponBonds.ForEach(z => GC.SuppressFinalize(z));
                GC.SuppressFinalize(discountingCurve);
                GC.SuppressFinalize(pricer);

            } 
            catch (Exception e) {
                Console.WriteLine(e.Message);
            }
        }
    }

}

Library


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;
 
namespace Library {

    // abstract base class for all transaction types
    // storing only properties common for all transactions
    public abstract class Transaction {

        // no de-serialization for pv attribute
        private double pv;
        public double PV { get { return pv; } set { pv = value; } }

        public string transactionType;
        public string transactionID;

        // default ctor, required for serialization
        public Transaction() {
        }

        // parameter ctor
        public Transaction(string transactionType, string transactionID) {
            this.transactionType = transactionType;
            this.transactionID = transactionID;
        }

    }

    // class for hosting zero-coupon bond term sheet information
    public class ZeroCouponBond : Transaction {

        public double faceAmount;
        public DateTime tradeDate;
        public DateTime settlementDate;
        public DateTime maturityDate;
        public string calendar;
        public string paymentConvention;

        public ZeroCouponBond()
            : base() {
            // required ctor for serialization
        }

        public ZeroCouponBond(string transactionType, string transactionID,
            double faceAmount, DateTime tradeDate, DateTime settlementDate,
            DateTime maturityDate, string calendar, string paymentConvention)
            : base(transactionType, transactionID) {

            this.faceAmount = faceAmount;
            this.tradeDate = tradeDate;
            this.settlementDate = settlementDate;
            this.maturityDate = maturityDate;
            this.calendar = calendar;
            this.paymentConvention = paymentConvention;
        }

    }

}

Builder

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;

namespace Builder {

    public static class SerializerFactory {
        // create generic de-serializer instance from a given transaction type string
        public static dynamic Create(string transactionType) {
            // note : required type string ("Namespace.Classname") is received without namespace string
            Type t = typeof(TransactionSerializer<>).MakeGenericType(Type.GetType(String.Format("Library.{0}", transactionType)));
            return Assembly.GetAssembly(t).CreateInstance(t.FullName);
        }
    }

    // de-serialize given xml file to transaction of type T
    public class TransactionSerializer<T> {
        public T Create(string transactionFilePathName) {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            FileStream stream = File.OpenRead(transactionFilePathName);
            return (T)serializer.Deserialize(stream);
        }
    }

    public static class TransactionsBuilder {

        // return list of transaction instances, de-serialized from xml files 
        public static List<dynamic> Build() {

            // create configurations
            // NOTE : use environmental variable in path construction
            string configurationsFilePathName =
                Path.Combine(Environment.GetEnvironmentVariable("CONFIGURATIONS").ToString(), "configurations.xml");
            XmlDocument configurations = new XmlDocument();
            configurations.Load(configurationsFilePathName);
            string transactionsDirectory =
                configurations.SelectSingleNode("Configurations/TransactionsFilePathName").InnerText.ToString();

            // create transaction file names and empty list for storing transaction instances
            string[] transactionFiles = Directory.GetFiles(transactionsDirectory);
            List<dynamic> transactions = new List<dynamic>();

            // loop through transaction file names
            foreach(string transactionFile in transactionFiles) {

                // create transaction xml file
                XmlDocument transactionXMLDocument = new XmlDocument();
                transactionXMLDocument.Load(transactionFile);

                // investigate transaction type inside file
                string transactionType = (transactionXMLDocument.DocumentElement)["transactionType"].InnerText.ToString();

                // use factory class for creating correct de-serializer
                dynamic factory = SerializerFactory.Create(transactionType);

                // use de-serializer to create transaction instance
                dynamic transaction = factory.Create(transactionFile);
                transactions.Add(transaction);
            }
            return transactions;
        }

    }
}



C++/CLI project


Header file

Note, that in this C++/CLI example project it is assumed, that DiscountingBondEngine is always used for pricing ZeroCouponBond. Moreover, it is assumed, that pricing engine is always using FlatForward term structure for valuation purposes. Needless to say, these assumptions are used here purely for brevity reasons. It is possible to create class hierarchies in C++/CLI wrapper for instruments, pricing engines and term structures. Such scheme would then enable the usage of more realistic valuation scheme, in which client (C# program) would be able to select desired type for valuation curve and pricing engine for some specific instrument.

Moreover, there is currently only one constructor implementation given for each of the classes. In reality, there are always several ways to create these objects (curve, engine, instrument) in QuantLib.

Finally, there is QuantLibConversion namespace, which offers set of static functions for handling different type conversions between C# types and QuantLib types. Needless to say, there are several ways to implement such type conversion scheme, but (IMHO) implementing TypeConverter would have been a bit overkill for this purpose.

// Wrapper.h
#include "QuantLibLibrary.h"
using namespace System;

namespace Wrapper {

 // class for wrapping native QuantLib::FlatForward
 public ref class QlFlatForward {
 public:
  FlatForward* curve_;

  QlFlatForward(double riskFreeRate, String^ dayCounter, DateTime settlementDate);
  ~QlFlatForward();
  !QlFlatForward();
 };


 // class for wrapping native QuantLib::DiscountingBondEngine
 public ref class QlDiscountingBondEngine {
 public:
  DiscountingBondEngine* pricer_;

  QlDiscountingBondEngine(QlFlatForward^ flatForward);
  ~QlDiscountingBondEngine();
  !QlDiscountingBondEngine();
 };


 // class for wrapping native QuantLib::ZeroCouponBond
 public ref class QlZeroCouponBond {
 public:
  ZeroCouponBond* bond_;

  QlZeroCouponBond(double faceAmount,
   DateTime transactionDate,
   DateTime settlementDate,
   DateTime maturityDate,
   String^ calendar,
   String^ paymentConvention);
  ~QlZeroCouponBond();
  !QlZeroCouponBond();

  double PV();
  void SetPricingEngine(QlDiscountingBondEngine^ engine);

 };


}


namespace QuantLibConversions {
 
 // one static class for all QuantLib-related type conversions
 public ref class Convert abstract sealed {
 public:

  // convert System.String to QuantLib.DayCounter class
  static DayCounter ToDayCounter(String^ dayCounterString);

  // convert System.DateTime to QuantLib.Date class
  static Date ToDate(DateTime dateTime);

  // convert System.String to QuantLib.Calendar class
  static Calendar ToCalendar(String^ calendarString);

  // convert System.String to QuantLib.BusinessDayConvention enumerator
  static BusinessDayConvention ToBusinessDayConvention(String^ businessDayConventionString);

 };

}

Implementation file

// Wrapper.cpp
#pragma once
#include "Wrapper.h"

namespace Wrapper {
 using Ql = QuantLibConversions::Convert;


 // implementations for yield curve class
 QlFlatForward::QlFlatForward(double riskFreeRate, String^ dayCounter, DateTime settlementDate) {

  DayCounter dayCounter_ = Ql::ToDayCounter(dayCounter);
  Date settlementDate_ = Ql::ToDate(settlementDate);
  Handle<Quote> riskFreeRateHandle_(boost::make_shared<SimpleQuote>(riskFreeRate));
  curve_ = new FlatForward(settlementDate_, riskFreeRateHandle_, dayCounter_);
 }

 QlFlatForward::~QlFlatForward() {
  this->!QlFlatForward();
 }

 QlFlatForward::!QlFlatForward() {
  delete curve_;
 }



 // implementations for zero bond class
 QlZeroCouponBond::QlZeroCouponBond(double faceAmount,
  DateTime transactionDate,
  DateTime settlementDate,
  DateTime maturityDate,
  String^ calendar,
  String^ paymentConvention) {

  Date transactionDate_ = Ql::ToDate(transactionDate);
  Date settlementDate_ = Ql::ToDate(settlementDate);
  Date maturityDate_ = Ql::ToDate(maturityDate);

  Calendar calendar_ = Ql::ToCalendar(calendar);
  BusinessDayConvention paymentConvention_ = Ql::ToBusinessDayConvention(paymentConvention);
  Natural settlementDays_ = settlementDate_ - transactionDate_;
  bond_ = new ZeroCouponBond(settlementDays_, calendar_, faceAmount, maturityDate_, paymentConvention_);
 }

 QlZeroCouponBond::~QlZeroCouponBond() {
  this->!QlZeroCouponBond();
 }

 QlZeroCouponBond::!QlZeroCouponBond() {
  delete bond_;
 }

 double QlZeroCouponBond::PV() {
  return bond_->NPV();
 }

 void QlZeroCouponBond::SetPricingEngine(QlDiscountingBondEngine^ engine) {
  bond_->setPricingEngine(static_cast<boost::shared_ptr<DiscountingBondEngine>>(engine->pricer_));
 }



 // implementations for zero pricer class
 QlDiscountingBondEngine::QlDiscountingBondEngine(QlFlatForward^ flatForward) {
  
  Handle<YieldTermStructure> discountCurveHandle(static_cast<boost::shared_ptr<FlatForward>>(flatForward->curve_));
  pricer_ = new DiscountingBondEngine(discountCurveHandle);
  Settings::instance().evaluationDate() = flatForward->curve_->referenceDate();
 }

 QlDiscountingBondEngine::~QlDiscountingBondEngine() {
  this->!QlDiscountingBondEngine();
 }

 QlDiscountingBondEngine::!QlDiscountingBondEngine() {
  delete pricer_;
 }


}


namespace QuantLibConversions {

 DayCounter Convert::ToDayCounter(String^ dayCounterString) {
  if (dayCounterString->ToUpper() == "ACTUAL360") return Actual360();
  if (dayCounterString->ToUpper() == "THIRTY360") return Thirty360();
  if (dayCounterString->ToUpper() == "ACTUALACTUAL") return ActualActual();
  if (dayCounterString->ToUpper() == "BUSINESS252") return Business252();
  if (dayCounterString->ToUpper() == "ACTUAL365NOLEAP") return Actual365NoLeap();
  if (dayCounterString->ToUpper() == "ACTUAL365FIXED") return Actual365Fixed();
  // requested day counter not found, throw exception
  throw gcnew System::Exception("undefined daycounter");
 }

 Date Convert::ToDate(DateTime dateTime) {
  // Date constructor using Excel dateserial
  return Date(dateTime.ToOADate());
 }

 Calendar Convert::ToCalendar(String^ calendarString) {
  if (calendarString->ToUpper() == "ARGENTINA.MERVAL") return Argentina(Argentina::Market::Merval);
  if (calendarString->ToUpper() == "AUSTRALIA") return Australia();
  if (calendarString->ToUpper() == "BRAZIL.EXCHANGE") return Brazil(Brazil::Market::Exchange);
  if (calendarString->ToUpper() == "BRAZIL.SETTLEMENT") return Brazil(Brazil::Market::Settlement);
  if (calendarString->ToUpper() == "CANADA.SETTLEMENT") return Canada(Canada::Market::Settlement);
  if (calendarString->ToUpper() == "CANADA.TSX") return Canada(Canada::Market::TSX);
  if (calendarString->ToUpper() == "CHINA.IB") return China(China::Market::IB);
  if (calendarString->ToUpper() == "CHINA.SSE") return China(China::Market::SSE);
  if (calendarString->ToUpper() == "CZECHREPUBLIC.PSE") return CzechRepublic(CzechRepublic::Market::PSE);
  if (calendarString->ToUpper() == "DENMARK") return Denmark();
  if (calendarString->ToUpper() == "FINLAND") return Finland();
  if (calendarString->ToUpper() == "GERMANY.SETTLEMENT") return Germany(Germany::Market::Settlement);
  if (calendarString->ToUpper() == "GERMANY.FRANKFURTSTOCKEXCHANGE") return Germany(Germany::Market::FrankfurtStockExchange);
  if (calendarString->ToUpper() == "GERMANY.XETRA") return Germany(Germany::Market::Xetra);
  if (calendarString->ToUpper() == "GERMANY.EUREX") return Germany(Germany::Market::Eurex);
  if (calendarString->ToUpper() == "GERMANY.EUWAX") return Germany(Germany::Market::Euwax);
  if (calendarString->ToUpper() == "HONGKONG.HKEX") return HongKong(HongKong::Market::HKEx);
  if (calendarString->ToUpper() == "INDIA.NSE") return India(India::Market::NSE);
  if (calendarString->ToUpper() == "INDONESIA.BEJ") return Indonesia(Indonesia::Market::BEJ);
  if (calendarString->ToUpper() == "INDONESIA.IDX") return Indonesia(Indonesia::Market::IDX);
  if (calendarString->ToUpper() == "INDONESIA.JSX") return Indonesia(Indonesia::Market::JSX);
  if (calendarString->ToUpper() == "ISRAEL.SETTLEMENT") return Israel(Israel::Market::Settlement);
  if (calendarString->ToUpper() == "ISRAEL.TASE") return Israel(Israel::Market::TASE);
  if (calendarString->ToUpper() == "ITALY.EXCHANGE") return Italy(Italy::Market::Exchange);
  if (calendarString->ToUpper() == "ITALY.SETTLEMENT") return Italy(Italy::Market::Settlement);
  if (calendarString->ToUpper() == "JAPAN") return Japan();
  if (calendarString->ToUpper() == "MEXICO.BMV") return Mexico(Mexico::Market::BMV);
  if (calendarString->ToUpper() == "NEWZEALAND") return NewZealand();
  if (calendarString->ToUpper() == "NORWAY") return Norway();
  if (calendarString->ToUpper() == "POLAND") return Poland();
  if (calendarString->ToUpper() == "ROMANIA") return Romania();
  if (calendarString->ToUpper() == "RUSSIA.MOEX") return Russia(Russia::Market::MOEX);
  if (calendarString->ToUpper() == "RUSSIA.SETTLEMENT") return Russia(Russia::Market::Settlement);
  if (calendarString->ToUpper() == "SAUDIARABIA.TADAWUL") return SaudiArabia(SaudiArabia::Market::Tadawul);
  if (calendarString->ToUpper() == "SINGAPORE.SGX") return Singapore(Singapore::Market::SGX);
  if (calendarString->ToUpper() == "SLOVAKIA.BSSE") return Slovakia(Slovakia::Market::BSSE);
  if (calendarString->ToUpper() == "SOUTHAFRICA") return SouthAfrica();
  if (calendarString->ToUpper() == "SOUTHKOREA.KRX") return SouthKorea(SouthKorea::Market::KRX);
  if (calendarString->ToUpper() == "SOUTHKOREA.SETTLEMENT") return SouthKorea(SouthKorea::Market::Settlement);
  if (calendarString->ToUpper() == "SWEDEN") return Sweden();
  if (calendarString->ToUpper() == "SWITZERLAND") return Switzerland();
  if (calendarString->ToUpper() == "TAIWAN.TSEC") return Taiwan(Taiwan::Market::TSEC);
  if (calendarString->ToUpper() == "TARGET") return TARGET();
  if (calendarString->ToUpper() == "TURKEY") return Turkey();
  if (calendarString->ToUpper() == "UKRAINE.USE") return Ukraine(Ukraine::Market::USE);
  if (calendarString->ToUpper() == "UNITEDKINGDOM.EXCHANGE") return UnitedKingdom(UnitedKingdom::Market::Exchange);
  if (calendarString->ToUpper() == "UNITEDKINGDOM.METALS") return UnitedKingdom(UnitedKingdom::Market::Metals);
  if (calendarString->ToUpper() == "UNITEDKINGDOM.SETTLEMENT") return UnitedKingdom(UnitedKingdom::Market::Settlement);
  if (calendarString->ToUpper() == "UNITEDSTATES.GOVERNMENTBOND") return UnitedStates(UnitedStates::Market::GovernmentBond);
  if (calendarString->ToUpper() == "UNITEDSTATES.LIBORIMPACT") return UnitedStates(UnitedStates::Market::LiborImpact);
  if (calendarString->ToUpper() == "UNITEDSTATES.NERC") return UnitedStates(UnitedStates::Market::NERC);
  if (calendarString->ToUpper() == "UNITEDSTATES.NYSE") return UnitedStates(UnitedStates::Market::NYSE);
  if (calendarString->ToUpper() == "UNITEDSTATES.SETTLEMENT") return UnitedStates(UnitedStates::Market::Settlement);
  // requested calendar not found, throw exception
  throw gcnew System::Exception("undefined calendar");
 }

 BusinessDayConvention Convert::ToBusinessDayConvention(String^ businessDayConventionString) {
  if (businessDayConventionString->ToUpper() == "FOLLOWING") return BusinessDayConvention::Following;
  if (businessDayConventionString->ToUpper() == "HALFMONTHMODIFIEDFOLLOWING") return BusinessDayConvention::HalfMonthModifiedFollowing;
  if (businessDayConventionString->ToUpper() == "MODIFIEDFOLLOWING") return BusinessDayConvention::ModifiedFollowing;
  if (businessDayConventionString->ToUpper() == "MODIFIEDPRECEDING") return BusinessDayConvention::ModifiedPreceding;
  if (businessDayConventionString->ToUpper() == "NEAREST") return BusinessDayConvention::Nearest;
  if (businessDayConventionString->ToUpper() == "PRECEDING") return BusinessDayConvention::Preceding;
  if (businessDayConventionString->ToUpper() == "UNADJUSTED") return BusinessDayConvention::Unadjusted;
  // requested business day convention not found, throw exception
  throw gcnew System::Exception("undefined business day convention");
 }

}

As always, thanks for reading this blog.
-Mike

Friday, December 29, 2017

QuantLib : custom instrument and pricing engine implementation

Using in-built QuantLib instruments and pricing engines is almost trivial, since the library is actually performing all required groundwork on behalf of a lucky user. Hair may start to get gray, as soon as one wants to implement a new instrument and related pricing engine. The example presented below is implementing a simple zero-coupon bond instrument and corresponding (analytical) pricing engine.

// MJZeroCouponBond.h
#pragma once
#include <ql/quantlib.hpp>
using namespace QuantLib;
//
// implementation for zero-coupon bond instrument
class MJZeroCouponBond : public Instrument {
public:
 // forward class declarations
 class arguments;
 class engine;
 //
 // ctor and implementations for required base class methods
 MJZeroCouponBond(Real faceAmount, Date maturityDate);
 bool isExpired() const;
private:
 void setupArguments(PricingEngine::arguments* args) const;
 // term sheet related information
 Real faceAmount_;
 Date maturityDate_;
};
// inner arguments class
class MJZeroCouponBond::arguments : public PricingEngine::arguments {
public:
 void validate() const;
 Real faceAmount;
 Date maturityDate;
};
// inner engine class
class MJZeroCouponBond::engine 
 : public GenericEngine<MJZeroCouponBond::arguments, MJZeroCouponBond::results> {
 // base class for all further engine implementations
};
//
// implementation for base class engine
class MJZeroCouponBondEngine : public MJZeroCouponBond::engine {
public:
 MJZeroCouponBondEngine(const Handle<YieldTermStructure>& curve);
 void calculate() const;
private:
 Handle<YieldTermStructure> curve_;
};
//
//
//
//
//
// MJZeroCouponBond.cpp
#include "MJZeroCouponBond.h"
using namespace QuantLib;
//
// implementations for MJZeroCouponBond instrument
MJZeroCouponBond::MJZeroCouponBond(Real faceAmount, Date maturityDate) 
 : faceAmount_(faceAmount), maturityDate_(maturityDate) {
 // ctor
}
bool MJZeroCouponBond::isExpired() const {
 return Settings::instance().evaluationDate() >= maturityDate_;
}
void MJZeroCouponBond::setupArguments(PricingEngine::arguments* args) const {
 MJZeroCouponBond::arguments* args_ = dynamic_cast<MJZeroCouponBond::arguments*>(args);
 QL_REQUIRE(args_ != nullptr, "arguments casting error");
 args_->faceAmount = faceAmount_;
 args_->maturityDate = maturityDate_;
}
void MJZeroCouponBond::arguments::validate() const {
 QL_REQUIRE(faceAmount > 0.0, "transaction face amount cannot be zero");
}
//
// implementations for MJZeroCouponBondEngine
MJZeroCouponBondEngine::MJZeroCouponBondEngine(const Handle<YieldTermStructure>& curve)
 : curve_(curve) {
 // register observer (MJZeroCouponBondEngine) with observable (curve)
 registerWith(curve_);
}
void MJZeroCouponBondEngine::calculate() const {
 // extract required parameters from arguments or curve object
 // implement the actual pricing algorithm and store result
 Date maturityDate = arguments_.maturityDate;
 Real P = arguments_.faceAmount;
 DiscountFactor df = curve_->discount(maturityDate);
 Real PV = P * df;
 results_.value = PV;
}
//
//
//
//
//
// Tester.cpp
#pragma once
#include "MJZeroCouponBond.h"
using namespace QuantLib;
//
int main() {
 //
 try {
  // common data : calendar, daycounter, dates
  Calendar calendar = TARGET();
  DayCounter dayCounter = Actual360();
  Date transactionDate(18, December, 2017);
  Date maturityDate(18, December, 2030);
  Natural settlementDays = 2;
  Date settlementDate = calendar.advance(transactionDate, Period(settlementDays, Days));
  Settings::instance().evaluationDate() = settlementDate;
  //
  // create flat discount curve
  auto riskFreeRate = boost::make_shared<SimpleQuote>(0.01);
  Handle<Quote> riskFreeRateHandle(riskFreeRate);
  auto riskFreeRateTermStructure = boost::make_shared<FlatForward>(settlementDate, riskFreeRateHandle, dayCounter);
  Handle<YieldTermStructure> riskFreeRateTermStructureHandle(riskFreeRateTermStructure);
  //
  // create zero coupon bonds (QL in-built and custom implementation)
  Real faceAmount = 250000.0;
  auto QLZero = boost::make_shared<ZeroCouponBond>(settlementDays, calendar, faceAmount, maturityDate);
  auto MJZero = boost::make_shared<MJZeroCouponBond>(faceAmount, maturityDate);
  //
  // create pricing engines  (QL in-built and custom implementation)
  auto QLEngine = boost::make_shared<DiscountingBondEngine>(riskFreeRateTermStructureHandle);
  auto MJEngine = boost::make_shared<MJZeroCouponBondEngine>(riskFreeRateTermStructureHandle);
  //
  // set engines to instruments and price the both transaction
  QLZero->setPricingEngine(QLEngine);
  MJZero->setPricingEngine(MJEngine);
  //
  std::cout << "pre-shift QL zero " << QLZero->NPV() << std::endl;
  std::cout << "pre-shift MJ zero " << MJZero->NPV() << std::endl;
  //
  // shift valuation curve and re-price the both transactions
  riskFreeRate->setValue(0.0101);
  std::cout << "post-shift QL zero " << QLZero->NPV() << std::endl;
  std::cout << "post-shift MJ zero " << MJZero->NPV() << std::endl;
 }
 catch (std::exception& e) {
  std::cout << e.what() << std::endl;
 }
 return 0;
}


In order to get more familiar with QuantLib Monte Carlo framework, one may take a look at Implementing QuantLib blog or get the actual book on QuantLib implementation from Leanpub. Also, MoneyScience is organizing Introduction to QuantLib Development course regularly. The most essential parts of the library are thoroughly covered during this 3-day training course, hosted by QuantLib lead developer Luigi Ballabio. Thanks for reading this blog. Merry Christmas and Happy New Year for everybody.
-Mike


Sunday, August 23, 2015

Simulating Discount Factor and Forward Rate Curves using Monte Carlo in C#

The process of creating discount factor and forward rate curves with traditional bootstrapping algorithm was presented in the last post. In this post we are going to do the same thing, but following a bit different approach.

There are two ways to use the market data when creating these curves. The first approach is to fit the curve exactly into the current market data (calibration, bootstrapping). Another approach is to select an interest rate model, estimate all required parameters from the current market data and finally build the curves by using Monte Carlo method, for example. The advantage of the first approach is, that we are able to reprice (or replicate) the market with the resulting curves, while this (almost surely) will never happen with the second approach.

Monte Carlo method itself is widely used for a wide array of complex calculation tasks. Just for an example, calculating CVA for an interest rate swap requires us to calculate swap PV for a number of chosen timepoints happening in the future, before the expiration of a swap contract. For this complex calculation task, we can select an interest rate model and use Monte Carlo to calculate swap PV for any point in time in the future.

PROJECT OUTCOME

In this project, we are following this second approach and use Vasicek one-factor interest rate model in our example. The resulting C# program uses Monte Carlo for simulating discount curve (a set of zero-coupon bonds). After this, client can request discount factor DF(0, T) for any given maturity or forward rate FWD(t, T) for any given period in the future. The resulting example program is a console project. However, with all the information available in this blog, one should be able to interface the program with Excel, if so desired. Needless to say, the part of the program which actually creates the curves can be used as a part of any other C# project.

PROGRAM DESIGN

For the purpose of getting some conceptual overview, about how the objects in the program are connected with each other, a rough UML class diagram is shown in the picture below.
















YELLOW
First of all, Client is requesting MonteCarloEngine object to simulate the short rate paths. MonteCarloEngine is sending all simulated paths for MonteCarloCurveProcessor, using delegate method. For the simulation task, MonteCarloEngine needs (aggregation) three different objects : RandomNumber, SDE and Discretization. These objects are going to be created by HardCodedExampleBuilder (aggregation), which is a specialization for abstract MonteCarloBuilder class.

BLUE
All the needed information concerning the short rate model is embedded in EulerDiscretization (specialization for abstract Discretization) and Vasicek (specialization for abstract SDE) objects. These objects are aggregated in MonteCarloEngine.

GREEN
For performing Monte Carlo simulation task, random numbers are needed. NAG_BasicRandomNumber object (specialization for abstract RandomNumber) is aggregated in MonteCarloEngine. This object is using (aggregation) two other objects to perform the task. NAG_BasicGenerator (specialization for abstract UniformRandomGenerator) and StandardNormal (specialization for abstract InverseTransformation).

With the design used in the program, a client is able to change
  • uniform random number generator
  • the class, which is processing generated uniform randon numbers
  • inverse transformation method for mapping generated uniform random numbers into a given probability distribution
  • stochastic differential equation
  • discretization scheme for a given stochastic differential equation

So, we are having a lot of flexibility with this presented design.

It should be noted, that the basic task of this program is to simulate paths for any given (one-factor) stochastic differential equation, using a given discretization scheme. MonteCarloEngine is kind of a Mediator object, which is performing this task. MonteCarloCurveProcessor object, which has been added into this design later, is only using the results generated by MonteCarloEngine for creating a set of discount factors and calculating forward rates, when requested by the client.

THE PROGRAM

Create a new C# console project and copyPaste the following program into a new cs-file. Remember to add reference to NAG .NET library. If you are not registrated NAG user, create a new implementation for UniformRandomGenerator and completely remove the current uniform generator implementation.


using System;
using System.Collections.Generic;
using System.Linq;
using NagLibrary;

namespace RandomDesign
{
    // public delegates
    public delegate double Interpolator(Dictionary<double, double> data, double key);
    public delegate void PathSender(double[] path);
    public delegate void ProcessStopper();
    public delegate int Seeder();
    //
    // Client
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                // create required objects, data and run monte carlo engine
                double tenor = 0.25;
                double maturity = 10.0;
                int paths = 250000;
                int steps = 1000;
                MonteCarloEngine engine = new MonteCarloEngine(new HardCodedExampleBuilder(), paths, steps);
                MonteCarloCurveProcessor curve = new MonteCarloCurveProcessor(maturity, tenor, Interpolation.LinearInterpolation);
                engine.sendPath += curve.Process;
                engine.stopProcess += curve.Calculate;
                engine.run();
                //
                // after engine run, discount factors and simply-compounded 
                // forward rates can be requested by the client
                Console.WriteLine("printing quarterly discount factors >");
                int n = Convert.ToInt32(maturity / tenor);
                for (int i = 1; i <= n; i++)
                {
                    double T = (tenor * i);
                    double df = curve.GetDF(T);
                    Console.WriteLine(Math.Round(df, 4));
                }
                Console.WriteLine();
                //
                Console.WriteLine("printing quarterly forward rates >");
                for (int i = 2; i <= n; i++)
                {
                    double t = (tenor * (i - 1));
                    double T = (tenor * i);
                    double f = curve.GetFWD(t, T);
                    Console.WriteLine(Math.Round(f, 4));
                }
                Console.WriteLine();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
    //
    //
    //
    // class for :
    // - processing stochastic paths sent by monte carlo engine
    // - calculating zero-coupon bond prices using collected information on stochastic paths
    // - retrieving discount factors or simply-compounded forward rates for any given period
    public class MonteCarloCurveProcessor
    {
        private Dictionary<double, double> discountCurve;
        private Interpolator interpolator;
        private double maturity;
        private double tenor;
        private int nBonds;
        private int nSteps;
        private double[] paths;
        private bool hasDimension = false;
        private int counter;
        //
        public MonteCarloCurveProcessor(double maturity, double tenor, Interpolator interpolator)
        {
            discountCurve = new Dictionary<double, double>();
            this.interpolator = interpolator;
            this.maturity = maturity;
            this.tenor = tenor;
            nBonds = Convert.ToInt32(this.maturity / this.tenor);
            counter = 0;
        }
        public void Process(double[] path)
        {
            if (!hasDimension)
            {
                nSteps = path.GetLength(0);
                paths = new double[nSteps];
                hasDimension = true;
            }
            // add path to all existing paths
            for (int i = 0; i < nSteps; i++)
            {
                paths[i] += path[i];
            }
            counter++;
        }
        public void Calculate()
        {
            // calculate the average path
            for (int i = 0; i < nSteps; i++)
            {
                paths[i] /= counter;
            }
            // integrate zero-coupon bond prices
            double dt = maturity / (nSteps - 1);
            int takeCount = Convert.ToInt32(tenor / dt);
            for (int i = 0; i < nBonds; i++)
            {
                double integral = paths.ToList<double>().Take(takeCount * (i + 1) + 1).Sum();
                discountCurve.Add(tenor * (i + 1), Math.Exp(-integral * dt));
            }
        }
        public double GetDF(double T)
        {
            // interpolate discount factor from discount curve
            return interpolator(discountCurve, T);
        }
        public double GetFWD(double t, double T)
        {
            // using discount factors, calculate forward discount 
            // factor and convert it into simply-compounded forward rate
            double df_T = interpolator(discountCurve, T);
            double df_t = interpolator(discountCurve, t);
            double fdf = (df_T / df_t);
            return ((1 / fdf) - 1) / (T - t);
        }
    }
    //
    //
    //
    // class hierarchy for simulation object builders
    public abstract class MonteCarloBuilder
    {
        public abstract Tuple<SDE, Discretization, RandomNumber> build();
    }
    // implementation for hard-coded example
    public class HardCodedExampleBuilder : MonteCarloBuilder
    {
        public override Tuple<SDE, Discretization, RandomNumber> build()
        {
            // create objects for generating standard normally distributed random numbers
            UniformRandomGenerator uniformRandom = new NAG_BasicGenerator(Seed.GetGUIDSeed);
            InverseTransformation transformer = new StandardNormal();
            RandomNumber nag = new NAG_BasicRandomNumber(uniformRandom, transformer); 
            //
            // create stochastic differential equation and discretization objects
            SDE vasicek = new Vasicek(0.1, 0.29, 0.0185);
            Discretization euler = new EulerDiscretization(vasicek, 0.02, 10.0);
            //
            return new Tuple<SDE, Discretization, RandomNumber>(vasicek, euler, nag);
        }
    }
    //
    //
    //
    // class hierarchy for stochastic differential equations
    public abstract class SDE
    {
        public abstract double drift(double s, double t);
        public abstract double diffusion(double s, double t);
    }
    // implementation for Vasicek one-factor interest rate model
    public class Vasicek : SDE
    {
        private double longTermRate;
        private double reversionSpeed;
        private double rateVolatility;
        //
        public Vasicek(double longTermRate, double reversionSpeed, double rateVolatility)
        {
            this.longTermRate = longTermRate;
            this.reversionSpeed = reversionSpeed;
            this.rateVolatility = rateVolatility;
        }
        public override double drift(double s, double t)
        {
            return reversionSpeed * (longTermRate - s);
        }
        public override double diffusion(double s, double t)
        {
            return rateVolatility;
        }
    }
    //
    //
    //
    // class hierarchy for discretization schemes
    public abstract class Discretization
    {
        protected SDE sde;
        protected double initialPrice;
        protected double expiration;
        //
        // read-only properties for initial price and expiration
        public double InitialPrice { get { return initialPrice; } }
        public double Expiration { get { return expiration; } }
        public Discretization(SDE sde, double initialPrice, double expiration)
        {
            this.sde = sde;
            this.initialPrice = initialPrice;
            this.expiration = expiration;
        }
        public abstract double next(double s, double t, double dt, double rnd);
    }
    // implementation for Euler discretization scheme
    public class EulerDiscretization : Discretization
    {
        public EulerDiscretization(SDE sde, double initialPrice, double expiration)
            : base(sde, initialPrice, expiration) 
        { 
            //
        }
        public override double next(double s, double t, double dt, double rnd)
        {
            return s + sde.drift(s, t) * dt + sde.diffusion(s, t) * Math.Sqrt(dt) * rnd;
        }
    }
    //
    //
    //
    // mediator class for creating stochastic paths
    public class MonteCarloEngine
    {
        private SDE sde;
        private Discretization discretization;
        private RandomNumber random;
        private long paths;
        private int steps;
        public event PathSender sendPath;
        public event ProcessStopper stopProcess;
        //
        public MonteCarloEngine(MonteCarloBuilder builder, long paths, int steps)
        {
            Tuple<SDE, Discretization, RandomNumber> components = builder.build();
            this.sde = components.Item1;
            this.discretization = components.Item2;
            this.random = components.Item3;
            this.paths = paths;
            this.steps = steps;
        }
        public void run()
        {
            // create skeleton for path values
            double[] path = new double[steps + 1]; 
            double dt = discretization.Expiration / steps;
            double vOld = 0.0; double vNew = 0.0;
            //
            for (int i = 0; i < paths; i++)
            {
                // request random numbers for a path
                double[] rand = random.GetPath(steps);
                path[0] = vOld = discretization.InitialPrice;
                //
                for (int j = 1; j <= steps; j++)
                {
                    // get next path value using a given discretization scheme
                    vNew = discretization.next(vOld, (dt * j), dt, rand[j - 1]);
                    path[j] = vNew; vOld = vNew;
                }
                sendPath(path); // send one path to client (MonteCarloCurveProcessor) for processing
            }
            stopProcess(); // notify client (MonteCarloCurveProcessor)
        }
    }
    //
    //
    //
    // class hierarchy for uniform random generators
    public abstract class UniformRandomGenerator
    {
        public abstract double[] Get(int n);
    }
    // implementation for NAG basic uniform pseudorandom number generator
    public class NAG_BasicGenerator : UniformRandomGenerator
    {
        private Seeder seeder;
        private int[] seed;
        private const int baseGenerator = 1; // hard-coded NAG basic generator
        private const int subGenerator = 1; // hard-coded sub-generator (not referenced)
        //
        public NAG_BasicGenerator(Seeder seeder)
        {
            this.seeder = seeder;
        }
        public override double[] Get(int n)
        {
            seed = new int[] { seeder() };
            double[] rnd = new double[n];
            int errorState = 0;
            G05.G05State g05State = new G05.G05State(baseGenerator, subGenerator, seed, out errorState);
            if (errorState != 0) throw new Exception("NAG : g05State error");
            G05.g05sq(n, 0.0, 1.0, g05State, rnd, out errorState);
            if (errorState != 0) throw new Exception("NAG : g05sq error");
            return rnd;
        }
    }
    //
    //
    //
    // class hierarchy for random number processors 
    public abstract class RandomNumber
    {
        public readonly UniformRandomGenerator generator;
        public readonly InverseTransformation transformer;
        //
        public RandomNumber(UniformRandomGenerator generator, 
            InverseTransformation transformer = null)
        {
            this.generator = generator;
            this.transformer = transformer;
        }
        public abstract double[] GetPath(int nSteps);
    }
    // implementation for class processing NAG basic random numbers
    public class NAG_BasicRandomNumber : RandomNumber
    {
        public NAG_BasicRandomNumber(UniformRandomGenerator generator,
            InverseTransformation transformer = null)
            : base(generator, transformer)
        {
            // initialize base class data members
        }
        public override double[] GetPath(int nSteps)
        {
            // create and return one random number path
            double[] path = base.generator.Get(nSteps);
            //
            // conditionally, map uniform random numbers to a given distribution
            if (base.transformer != null)
            {
                for (int i = 0; i < nSteps; i++)
                {
                    path[i] = base.transformer.Inverse(path[i]);
                }
            }
            return path;
        }
    }
    //
    //
    //
    // class hierarchy for all inverse transformation classes
    public abstract class InverseTransformation
    {
        public abstract double Inverse(double x);
    }
    // mapping uniform random variate to standard normal distribution
    public class StandardNormal : InverseTransformation
    {
        public override double Inverse(double x)
        {
            const double a1 = -39.6968302866538; const double a2 = 220.946098424521;
            const double a3 = -275.928510446969; const double a4 = 138.357751867269;
            const double a5 = -30.6647980661472; const double a6 = 2.50662827745924;
            //
            const double b1 = -54.4760987982241; const double b2 = 161.585836858041;
            const double b3 = -155.698979859887; const double b4 = 66.8013118877197;
            const double b5 = -13.2806815528857;
            //
            const double c1 = -7.78489400243029E-03; const double c2 = -0.322396458041136;
            const double c3 = -2.40075827716184; const double c4 = -2.54973253934373;
            const double c5 = 4.37466414146497; const double c6 = 2.93816398269878;
            //
            const double d1 = 7.78469570904146E-03; const double d2 = 0.32246712907004;
            const double d3 = 2.445134137143; const double d4 = 3.75440866190742;
            //
            const double p_low = 0.02425; const double p_high = 1.0 - p_low;
            double q = 0.0; double r = 0.0; double c = 0.0;
            //
            if ((x > 0.0) && (x < 1.0))
            {
                if (x < p_low)
                {
                    // rational approximation for lower region
                    q = Math.Sqrt(-2.0 * Math.Log(x));
                    c = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6)
                        / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
                }
                if ((x >= p_low) && (x <= p_high))
                {
                    // rational approximation for middle region
                    q = x - 0.5; r = q * q;
                    c = (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q
                        / (((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1);
                }
                if (x > p_high)
                {
                    // rational approximation for upper region
                    q = Math.Sqrt(-2 * Math.Log(1 - x));
                    c = -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6)
                        / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
                }
            }
            else
            {
                // throw if given x value is out of bounds
                // (less or equal to 0.0, greater or equal to 1.0)
                throw new Exception("StandardNormal : out of bounds error");
            }
            return c;
        }
    }
    //
    //
    //
    // static library class consisting of seeding methods for uniform random number generators
    public static class Seed
    {
        public static int GetGUIDSeed() { return Math.Abs(Guid.NewGuid().GetHashCode()); }
    }
    //
    //
    //
    // static library class for interpolation methods
    public static class Interpolation
    {
        public static double LinearInterpolation(Dictionary<double, double> data, double key)
        {
            double value = 0.0;
            int n = data.Count;
            //
            // boundary checkings
            if ((key < data.ElementAt(0).Key) || (key > data.ElementAt(data.Count - 1).Key))
            {
                if (key < data.ElementAt(0).Key) value = data.ElementAt(0).Value;
                if (key > data.ElementAt(data.Count - 1).Key) value = data.ElementAt(data.Count - 1).Value;
            }
            else
            {
                // iteration through all existing elements
                for (int i = 0; i < n; i++)
                {
                    if ((key >= data.ElementAt(i).Key) && (key <= data.ElementAt(i + 1).Key))
                    {
                        double t = data.ElementAt(i + 1).Key - data.ElementAt(i).Key;
                        double w = (key - data.ElementAt(i).Key) / t;
                        value = data.ElementAt(i).Value * (1 - w) + data.ElementAt(i + 1).Value * w;
                        break;
                    }
                }
            }
            return value;
        }
    }
}


VASICEK PARAMETERS ESTIMATION

Parameters (long-term mean rate, reversion speed and rate volatility) for Vasicek model have to be estimated from the market data. For this task, we can use OLS method or Maximum Likelihood method. In this case, the both methods should end up with the same set of result parameters. Concrete hands-on example on parameter estimation with the both methods for Ornstein-Uhlenbeck model (Vasicek), has been presented in this great website by Thijs van den Berg.

When using OLS method, the task can easily be performed in Excel using Data Analysis tools. In the screenshot presented below, I have replicated the parameter estimation using Excel regression tool and example dataset from the website given above.

















The parameter estimation procedure with OLS method is straightforward in Excel and should be relatively easy to perform on any new chosen set of market data. Needless to say, the real deal in the parameter estimation approach is linked with the market data sample properties. The central question is the correct period to include into a sample for estimating such parameters.

Thanks a lot for using your precious time and reading my blog.

-Mike Juniperhill