(1) Wrapper.h
(2) Wrapper.mm
(3) Bridging-Header.h, this will include all header files (e.g. #include "Wrapper.h") for bridging to Swift
(4) pre-compiled libraries preferably in a framework. E.g. QuantLib and Boost frameworks for macOS and iOS which can be downloaded from the previous post here
The following example code uses the pre-built QuantLib and Boost frameworks to demonstrate a working C++ Wrapper for Swift.
(1) Wrapper.h
Wrapper.h is the header file for the wrapper classes of QuantLib for Swift. Important points for writing header file when wrapping C++ classes to Swift are:
(a) No C++ header files, no C++ classes nor other C++ syntax stuff here, use void* to point to shared_ptr object instead. This header file Wrapper.h will be included in the bridging header file, so avoid c++ stuffs and use only C and Objective C stuffs when compiling in Swift.
(b) Avoid using C++ typedef here, define them in the implementation .mm source files.
(c) Use NS_ENUM macro to replace c++ enum or struct enum here, cast them to c++ enum in the implementation .mm source files. This helps when bridging to Swift enum
(d) use NS_ASSUME_NONNULL macro here and put nullable for optional variable (when bridging to Swift).
(e) use the new lightweight generic <ClassName*> feature in Objective C for NSArray and other collection types e.g. (NSArray<QLRateHelper*>*)rateHelpers
(f) Use the following Objective C types for conversion to Swift
Objective C Type | Swift Type |
---|---|
bool | Bool |
char,unsigned char | UInt8, UInt8 |
short, unsigned short | Int16, UInt16 |
long, unsigned long | Int, UInt |
long long, unsigned long long | Int64, UInt64 |
double, float | Double, Float |
NSString | String |
NSArray, NSDictionary | Array, Dictionary |
Void * | UnsafePointer<Void> |
- Wrapper.h Select all
//
// Wrapper.h
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface QLDate : NSObject
typedef NS_ENUM(int, QLMonth) {
QLMonthJan=1,
QLMonthFeb=2,
QLMonthMar=3,
QLMonthApr=4,
QLMonthMay=5,
QLMonthJun=6,
QLMonthJul=7,
QLMonthAug=8,
QLMonthSep=9,
QLMonthOct=10,
QLMonthNov=11,
QLMonthDec=12,
QLMonthJanuary=1,
QLMonthFebruary=2,
QLMonthMarch=3,
QLMonthApril=4,
QLMonthJune=6,
QLMonthJuly=7,
QLMonthAugust=8,
QLMonthSeptember=9,
QLMonthOctober=10,
QLMonthNovember=11,
QLMonthDecember=12
};
@property (unsafe_unretained, nonatomic, readonly, nullable) void *impl;
-(instancetype) initWithD:(long)day Month:(QLMonth)month Y:(long)year;
-(instancetype) initWithD:(long)dy M:(long)mn Y:(long)yr;
-(instancetype) initWithYear:(long)year Month:(long)month Day:(long)day;
-(instancetype) initWithSerialNumber:(long) serialNumber;
-(NSString *) weekday;
-(NSString *) description;
@end
typedef NS_ENUM(int, QLTimeUnit) {
QLTimeUnitDays,
QLTimeUnitWeeks,
QLTimeUnitMonths,
QLTimeUnitYears
};
@interface QLCalendar : NSObject
@property (unsafe_unretained, nonatomic, readonly, nullable) void *impl;
+(instancetype) TARGET;
+(instancetype) UnitedStatesGovernmentBond;
+(instancetype) UnitedStatesNYSE;
-(QLDate *) adjust:(QLDate *)settlementDate;
-(QLDate *) advance:(QLDate *)settlementDate fixingDays:(long)fixingDays timeUnit:(QLTimeUnit)timeUnit;
-(NSString *) description;
@end
// TODO: Other Classes definition here
NS_ASSUME_NONNULL_END
(2) Wrapper.mm
Wrapper.mm is the Objective-C++ implementation file for the Wrapper Classes of QuantLib for Swift
(a) Put all the required C++ header files here.
(b) Use boost::shared_ptr<QuantLib::ClassName> or std::shared_ptr<QuantLib::ClassName> in the implementation and point to the C++ objects.
(c) Use static_cast to cast the shared_ptr impl back to QuantLib C++ object. For example
// cast void* back to QuantLib::ClassName
QuantLib::Date tmpSettlementDate = *static_cast<QuantLib::Date *>(settlementDate.impl);
// cast boost::shared_ptr<QuantLib::ClassName> back to QuantLib::ClassName
static_cast<QuantLib::Date>(*(self._impl));
(d) Use pointer function to access the QuantLib member function. For example
[[QLDate alloc] initWithSerialNumber:((long)self._impl->adjust(tmpSettlementDate).serialNumber())] ;
(e) Should implement description method for Swift.
-(NSString *) description
- Wrapper.mm Select all
#import "Wrapper.h"
#include <memory>
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <ql/quantlib.hpp>
// MARK: QLDate
@interface QLDate()
@property (unsafe_unretained, nonatomic) boost::shared_ptr<QuantLib::Date> _impl;
@end
@implementation QLDate
-(instancetype) init
{
self = [super init];
if (self) {
self._impl = boost::shared_ptr<QuantLib::Date>(new QuantLib::Date());
}
return self;
}
-(instancetype) initWithD:(long)day Month:(QLMonth)month Y:(long)year
{
self = [super init];
if (self) {
self._impl = boost::shared_ptr<QuantLib::Date>(new QuantLib::Date((int)day, static_cast<QuantLib::Month>(month), (int)year));
}
return self;
}
-(instancetype) initWithD:(long)dy M:(long)mn Y:(long)yr
{
self = [super init];
if (self) {
self._impl = boost::shared_ptr<QuantLib::Date>(new QuantLib::Date((int)dy, static_cast<QuantLib::Month>(mn), (int)yr));
}
return self;
}
-(instancetype) initWithYear:(long)year Month:(long)month Day:(long)day {
self = [super init];
if (self) {
self._impl = boost::shared_ptr<QuantLib::Date>(new QuantLib::Date((int)day, static_cast<QuantLib::Month>((int)month), (int)year));
}
return self;
}
-(instancetype) initWithSerialNumber:(long) serialNumber
{
self = [super init];
if (self) {
self._impl = boost::shared_ptr<QuantLib::Date>(new QuantLib::Date((QuantLib::BigInteger)serialNumber));
}
return self;
}
-(void *) impl
{
return static_cast<void *>(self._impl.get());
}
-(NSString *) description
{
std::ostringstream stream;
stream << static_cast<QuantLib::Date>(*(self._impl));
std::string name = stream.str();
return [NSString stringWithUTF8String:name.c_str()];
}
-(NSString *) weekday
{
std::ostringstream stream;
stream << static_cast<QuantLib::Weekday>(self._impl->weekday());
std::string name = stream.str();
return [NSString stringWithUTF8String:name.c_str()];
}
@end
// MARK: QLCalendar
@implementation QLCalendar
-(instancetype) init
{
self = [super init];
if (self) {
self._impl = boost::shared_ptr<QuantLib::Calendar>(new QuantLib::Calendar());
}
return self;
}
-(instancetype) initWithTarget
{
self = [super init];
if (self) {
self._impl = boost::shared_ptr<QuantLib::Calendar>(new QuantLib::TARGET());
}
return self;
}
-(instancetype) initWithUnitedStatesGovernmentBond
{
self = [super init];
if (self) {
self._impl = boost::shared_ptr<QuantLib::Calendar>(new QuantLib::UnitedStates(QuantLib::UnitedStates::GovernmentBond));
}
return self;
}
-(instancetype) initWithUnitedStatesNYSE
{
self = [super init];
if (self) {
self._impl = boost::shared_ptr<QuantLib::Calendar>(new QuantLib::UnitedStates(QuantLib::UnitedStates::NYSE));
}
return self;
}
+(instancetype) TARGET
{
return [[QLCalendar alloc] initWithTarget];
}
+(instancetype) UnitedStatesGovernmentBond
{
return [[QLCalendar alloc] initWithUnitedStatesGovernmentBond];
}
+(instancetype) UnitedStatesNYSE
{
return [[QLCalendar alloc] initWithUnitedStatesNYSE];
}
-(void *) impl
{
return static_cast<void *>(self._impl.get());
}
-(NSString *) description
{
std::string name = self._impl->name();
return [NSString stringWithUTF8String:name.c_str()];
}
-(QLDate *) adjust:(QLDate *)settlementDate {
// cast void* to std::shared_ptr<mytype>
QuantLib::Date tmpSettlementDate = *static_cast<QuantLib::Date *>(settlementDate.impl);
return [[QLDate alloc] initWithSerialNumber:((long)self._impl->adjust(tmpSettlementDate).serialNumber())] ;
}
-(QLDate *) advance:(QLDate *)settlementDate fixingDays:(long)fixingDays timeUnit:(QLTimeUnit)timeUnit {
// cast void* to std::shared_ptr<mytype>
QuantLib::Date tmpSettlementDate = *static_cast<QuantLib::Date *>(settlementDate.impl);
return [[QLDate alloc] initWithSerialNumber:((long)self._impl->advance(tmpSettlementDate,QuantLib::Integer((int)fixingDays), static_cast<QuantLib::TimeUnit>(timeUnit)).serialNumber())] ;
}
@end
// TODO: Other Classes definition here
(3) main.swift
This is the swift 3.0 example source file (Xcode 8.0) for extending and using the QuantLib C++ classes after wrapping. This example use the calculation method of zeroCouponBond in Bonds.cpp from QuantLib Example source repo.
- main.swift Select all
//
// main.swift
//
import Foundation
public extension QLDate {
public class func parse(_ string : String, format : String = "yyyy-mm-dd") -> QLDate {
var slist : [String]
var flist : [String]
var d : Int = 0
var m : Int = 0
var y : Int = 0
var delim : String
if string.range(of:"/") != nil {
delim = "/"
} else {
delim = "-"
}
slist = string.components(separatedBy: delim)
flist = format.components(separatedBy: delim)
for i in 0..<flist.count {
let sub = flist[i]
if sub.lowercased() == "dd" {
d = Int(slist[i])!
} else if sub.lowercased() == "mm" {
m = Int(slist[i])!
} else if sub.lowercased() == "yyyy" {
y = Int(slist[i])!
}
}
if y < 100 {
y += 2000
}
return QLDate(year: y, month: m, day: d)
}
}
public func char2TimeUnit(_ unit:String) -> QLTimeUnit {
switch unit {
case "D", "d": return .days
case "W", "w": return .weeks
case "M", "m": return .months
case "Y", "y": return .years
default: return .months
}
}
/*********************
*** MARKET DATA ***
*********************/
let calendar = QLCalendar.target()
var settlementDate = QLDate.parse("2008-09-18")
settlementDate = calendar.adjust(settlementDate);
let fixingDays = 3
let settlementDays = 3
let todaysDate = calendar.advance(settlementDate, fixingDays: -(fixingDays), timeUnit: .days)
print("Today: \(todaysDate.weekday()), \(todaysDate)")
print("Settlement date: \(settlementDate.weekday()), \(settlementDate)")
// TODO: Other implementations
/***********************************************
** CURVE BUILDING DEPOSIT + FIXED RATE BOND **
***********************************************/
var rateHelpers = [QLRateHelper]()
/*********************
** DEPOSIT DATA **
*********************/
let depositData: [(Double, Int, String)]
= [(0.0096, 3, "M"),
(0.0145, 6, "M"),
(0.0194, 1, "Y")]
for (rate, num, unit) in depositData {
let timeunit:QLTimeUnit = char2TimeUnit(unit)
let ratehandle = QLHandleQuote(simpleQuoteRate: QLRate(rate:rate))
let depositehelper = QLDepositRateHelper(handle: ratehandle, tenor: QLPeriod(num: num, of: timeunit), fixingDays: fixingDays, calendar: calendar, convention: .modifiedFollowing, endOfMonth: true, dayCounter: QLDayCounter.actual365Fixed())
rateHelpers.append(depositehelper)
}
/****************************
** FIXED RATE BOND DATA **
****************************/
let faceAmount = 100.0
let redemption = 100.0
let tolerance = 1.0e-15
let fixedRateBondData:[(Double, Double, String, String, Double, Double )]
= [(faceAmount, redemption, "2005-03-15", "2010-08-31", 0.02375, 100.390625),
(faceAmount, redemption, "2005-06-15", "2011-08-31", 0.04625, 106.21875),
(faceAmount, redemption, "2006-06-30", "2013-08-31", 0.03125, 100.59375),
(faceAmount, redemption, "2002-11-15", "2018-08-15", 0.04000, 101.6875),
(faceAmount, redemption, "1987-05-15", "2038-05-15", 0.04500, 102.140625)]
for (faceamount, redemption, issuedate, maturitydate, couponrate, marketquote) in fixedRateBondData {
let schedule = QLSchedule(issueDate: QLDate.parse(issuedate), maturityDate: QLDate.parse(maturitydate), frequency: QLPeriod(frequency:QLFrequency.semiannual), calendar: QLCalendar.unitedStatesGovernmentBond(), convention: .unadjusted, terminationConvention: .unadjusted, dateGenerationRule: .backward, endOfMonth: false)
let quoteHandle = QLRelinkableHandleQuote(marketQuote: marketquote)
let bondHelper = QLFixedRateBondHelper(handle: quoteHandle, settlementDays: settlementDays, faceAmount: faceamount, schedule: schedule, couponRate: couponrate, dayCounter: QLDayCounter.actualActualBond(), convention: .unadjusted, redemption: redemption, issueDate: QLDate.parse(issuedate))
rateHelpers.append(bondHelper)
}
let bondEngine = QLBondPricingEngine(rateHelpers: rateHelpers, settlementDate: settlementDate, tolerance: tolerance)
print("bondEngine: \(bondEngine)")
/*********************
* BONDS TO BE PRICED *
**********************/
let zeroCouponBond = QLZeroCouponBond(bondPricingEngine: bondEngine, settlementDays: settlementDays, calendar: QLCalendar.unitedStatesGovernmentBond(), faceAmount: 100.0, maturityDate: QLDate.parse("2013-08-15"), convention: .following, price: 116.92, issueDate: QLDate.parse("2003-08-15"))
print("\(zeroCouponBond) NPV = \(String(format: "%.2f", zeroCouponBond.npv()))")
print("\(zeroCouponBond) Clean price = \(String(format: "%.2f", zeroCouponBond.cleanPrice()))")
print("\(zeroCouponBond) Dirty price = \(String(format: "%.2f", zeroCouponBond.dirtyPrice()))")
print("\(zeroCouponBond) Yield = \(String(format: "%.2f%%", zeroCouponBond.yield()*100))")
example xcode project file will be posted here once ready
No comments:
Post a Comment