Portfolio.cpp

#include "Portfolio.h"
#include "CallOption.h"
#include "PutOption.h"
#include "UpAndOutOption.h"
#include "MonteCarloPricer.h"

using namespace std;

/*  
 *  By using an abstract interface class with a factory constructor
 *  and only having the implementation in the C++ file we increase information
 *  hiding. Nobody knows about the PortfolioImpl class outside of the C++ file,
 *  so we can change it without any impact on anything else.
 */
class PortfolioImpl : public Portfolio {
public:
    /*  Returns the number of items in the portflio */
    int size() const;    
    /*  Add a new security to the portfolio, returns the index
        at which it was added */
    int add( double quantity,
		shared_ptr<ContinuousTimeOption> security);
    /*  Update the quantity at a given index */
    void setQuantity( int index, double quantity );
    /*  Compute the current price */
    double price( const MultiStockModel& model ) const;    

	/*  Price this portfolio using one consistent set of monte carlo simulations */
	double monteCarloPrice(
		const MultiStockModel& model, const MonteCarloPricer& pricer) const;
//private:
    vector<double> quantities;
	vector< shared_ptr<ContinuousTimeOption> > securities;
};

int PortfolioImpl::size() const {
    return quantities.size();
}

int PortfolioImpl::add( double quantity,
	shared_ptr<ContinuousTimeOption> security) {
    quantities.push_back( quantity );
    securities.push_back( security );
    return quantities.size();
}

double PortfolioImpl::price(
        const MultiStockModel& model ) const {
    double ret = 0;
    int n = size();
    for (int i=0; i<n; i++) {
        ret += quantities[i] * securities[i]->price( model );
    }
    return ret;
}

void PortfolioImpl::setQuantity( int index,
        double quantity ) {
    quantities[index] = quantity;
}

//
//  More efficient monte carlo pricing implementation
//  

/*  A Maturity Grouping is a sub portfolio consisting of
	securities with the same maturity */
class MaturityGrouping : public ContinuousTimeOption {
public:

	MaturityGrouping(double maturity) : maturity(maturity) {
	}

	double getMaturity() const {
		return maturity;
	}

	bool isPathDependent() const {
		for (auto& sec : securities) {
			if (sec->isPathDependent()) {
				return true;
			}
		}
		return false;
	}

	set<string> getStocks() const {
		set<string> ret;
		for (auto& sec : securities) {
			for (auto& stock : sec->getStocks()) {
				ret.insert(stock);
			}
		}
		return ret;
	}

	double price(const MultiStockModel& m) const {
		MonteCarloPricer pricer;
		return pricer.price(*this, m);
	}

	Matrix payoff(const MarketSimulation& simulation) const {
		ASSERT(securities.size() > 0);
		Matrix current = quantities[0]*securities[0]->payoff(simulation);
		for (int i = 1; i < (int)securities.size(); i++) {
			current += quantities[i] * securities[i]->payoff(simulation);
		}
		return current;
	}

	void add( double quantity, SPContinuousTimeOption o ) {
		quantities.push_back(quantity);
		securities.push_back(o);
	}
private:
	vector<SPContinuousTimeOption> securities;
	vector<double> quantities;
	double maturity;

};

typedef shared_ptr<MaturityGrouping> SPMaturityGrouping;

/*  Price this portfolio using one consistent set of monte carlo simulations */
double PortfolioImpl::monteCarloPrice(
	const MultiStockModel& model, const MonteCarloPricer& pricer) const {
	map<double, SPMaturityGrouping> maturityGroupings;
	for (int i = 0; i < (int)securities.size(); i++) {
		auto security = securities[i];
		double quantity = quantities[i];
		double maturity = security->getMaturity();
		auto pairPtr = maturityGroupings.find(maturity);
		if (pairPtr == maturityGroupings.end()) {
			SPMaturityGrouping newGrouping(new MaturityGrouping(maturity));
			maturityGroupings[maturity] = newGrouping;
			pairPtr = maturityGroupings.find(maturity);
		}
		pairPtr->second->add(quantity, security);
	}

	double ret = 0;
	for (auto& pair : maturityGroupings) {
		MaturityGrouping& grouping = *pair.second;
		ret += pricer.price( grouping, model );
	}
	return ret;
}


/**
 *   Create a Portfolio
 */
shared_ptr<Portfolio> Portfolio::newInstance() {
    shared_ptr<Portfolio> ret=make_shared<PortfolioImpl>();
    return ret;
}




/////////////////////////////
//  Tests
/////////////////////////////

static void testSingleSecurity() {
    shared_ptr<Portfolio> portfolio = Portfolio::newInstance();
    
    shared_ptr<CallOption> c=make_shared<CallOption>();
    c->setStrike(110);
    c->setMaturity(1.0);

    portfolio->add( 100, c );

    BlackScholesModel bsm;
    bsm.volatility = 0.1;
    bsm.stockPrice = 100;

	MultiStockModel msm(bsm);
    
    double unitPrice = c->price( msm );
    double portfolioPrice = portfolio->price( msm );
    ASSERT_APPROX_EQUAL( 100*unitPrice, portfolioPrice, 0.0001);
}

static void testPutCallParity() {
    shared_ptr<Portfolio> portfolio
        = Portfolio::newInstance();
    
    shared_ptr<CallOption> c=make_shared<CallOption>();
    c->setStrike(110);
    c->setMaturity(1.0);

    shared_ptr<PutOption> p=make_shared<PutOption>();
    p->setStrike(110);
    p->setMaturity(1.0);


    portfolio->add( 100, c );
    portfolio->add( -100, p );

    BlackScholesModel bsm;
    bsm.volatility = 0.1;
    bsm.stockPrice = 100;
    bsm.riskFreeRate = 0;

	MultiStockModel msm(bsm);

    double expected = bsm.stockPrice - c->getStrike();
    double portfolioPrice = portfolio->price( msm );
    
    ASSERT_APPROX_EQUAL(100*expected,portfolioPrice,0.0001);

}

void testMultiStockPortfolio() {
	auto model = MultiStockModel::createTestModel();
	auto p = Portfolio::newInstance();
	auto stocks = model.getStocks();

	double q0 = 1.0;
	auto stock0 = stocks[0];
	SPUpAndOutOption o0=make_shared<UpAndOutOption>();
	o0->setStock(stock0);
	o0->setStrike(model.getStockPrice(stock0));
	o0->setBarrier(2*model.getStockPrice(stock0));
	p->add(q0,o0);
	
	double q1 = 2.0;
	auto stock1 = stocks[1];
	SPUpAndOutOption o1=make_shared<UpAndOutOption>();
	o1->setStock(stock1);
	o1->setStrike(model.getStockPrice(stock1));
	o1->setBarrier(2 * model.getStockPrice(stock1));
	p->add(q1, o1);

	MonteCarloPricer pricer;
	pricer.nScenarios = 100000;
	double p0 = pricer.price(*o0,model);
	double p1 = pricer.price(*o1, model);
	double expected = q0*p0 + q1*p1;

	double actual = p->price(model);
	ASSERT_APPROX_EQUAL(expected, actual, 0.3);

	double calculatedDifferently = p->monteCarloPrice(model, pricer);
	ASSERT_APPROX_EQUAL(calculatedDifferently, actual, 0.3);
}

void testPerformanceImprovement() {
	shared_ptr<Portfolio> portfolio
		= Portfolio::newInstance();

	BlackScholesModel bsm;
	bsm.volatility = 0.1;
	bsm.stockPrice = 100;
	bsm.riskFreeRate = 0;

	for (int i = 0; i < 5; i++) {
		SPUpAndOutOption option(new UpAndOutOption());
		option->setBarrier(bsm.stockPrice + i / 100.0);
		option->setStrike(bsm.stockPrice);
		option->setMaturity(1.0);
		portfolio->add(1.0, option);
	}

	MultiStockModel msm(bsm);

	auto start1 = clock();
	portfolio->price(msm);
	auto diff1 = clock() - start1;

	auto start2 = clock();
	MonteCarloPricer pricer;
	portfolio->monteCarloPrice(msm, pricer);
	auto diff2 = clock() - start2;

	INFO("Naive method took " << diff1);
	INFO("Improved method took " << diff2);

}

void testPortfolio() {
    TEST( testSingleSecurity );
    TEST( testPutCallParity );
	TEST( testMultiStockPortfolio );
	TEST(testPerformanceImprovement);
}