QuantLib: a free/open-source library for quantitative finance
Fully annotated sources - version 1.32
Loading...
Searching...
No Matches
latentmodel.hpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2008 Roland Lichters
5 Copyright (C) 2014 Jose Aparicio
6
7 This file is part of QuantLib, a free-software/open-source library
8 for financial quantitative analysts and developers - http://quantlib.org/
9
10 QuantLib is free software: you can redistribute it and/or modify it
11 under the terms of the QuantLib license. You should have received a
12 copy of the license along with this program; if not, please email
13 <quantlib-dev@lists.sf.net>. The license is also available online at
14 <http://quantlib.org/license.shtml>.
15
16 This program is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 FOR A PARTICULAR PURPOSE. See the license for more details.
19*/
20
21#ifndef quantlib_latent_model_hpp
22#define quantlib_latent_model_hpp
23
24#include <ql/experimental/math/multidimquadrature.hpp>
25#include <ql/experimental/math/multidimintegrator.hpp>
26#include <ql/math/integrals/trapezoidintegral.hpp>
27#include <ql/math/randomnumbers/randomsequencegenerator.hpp>
28#include <ql/experimental/math/gaussiancopulapolicy.hpp>
29#include <ql/experimental/math/tcopulapolicy.hpp>
30#include <ql/math/randomnumbers/boxmullergaussianrng.hpp>
31#include <ql/experimental/math/polarstudenttrng.hpp>
32#include <ql/handle.hpp>
33#include <ql/quote.hpp>
34#include <vector>
35
40namespace QuantLib {
41
42 namespace detail {
43 // havent figured out how to do this in-place
44 struct multiplyV {
48 QL_DEPRECATED
49 typedef std::vector<Real> result_type;
50
51 std::vector<Real> operator()(Real d, std::vector<Real> v)
52 {
53 std::transform(v.begin(), v.end(), v.begin(),
54 [=](Real x) -> Real { return x * d; });
55 return v;
56 }
57 };
58 }
59
61
62 /* Things trying to achieve here:
63 1.- Unify the two branches of integrators in the library, they do not
64 hang from a common base class and here a common ptr for the
65 factory is needed.
66 2.- Have a common signature for the integration call.
67 3.- Factory construction so integrable latent models can choose the
68 integration algorithm separately.
69 */
71 public:
72 // Interface with actual integrators:
73 // integral of a scalar function
74 virtual Real integrate(const ext::function<Real (
75 const std::vector<Real>& arg)>& f) const = 0;
76 // integral of a vector function
77 /* I had to use a different name, since the compiler does not
78 recognise the overload; MSVC sees the argument as
79 ext::function<Signature> in both cases....
80 I could do the as with the quadratures and have this as a template
81 function and spez for the vector case but I prefer to understand
82 why the overload fails....
83 FIX ME
84 */
85 virtual std::vector<Real> integrateV(
86 const ext::function<std::vector<Real> (
87 const std::vector<Real>& arg)>& f) const {
88 QL_FAIL("No vector integration provided");
89 }
90 virtual ~LMIntegration() = default;
91 };
92
93 //CRTP-ish for joining the integrations, class above to have the factory
94 template <class I_T>
96 public I_T, public LMIntegration {// diamond on 'integrate'
97 // this class template always to be fully specialized:
98 private:
99 IntegrationBase() = default;
100 };
102
103 // gcc reports value collision with heston engine (?!) thats why the name
105 typedef
107 #ifndef QL_PATCH_SOLARIS
109 #endif
111 // etc....
113 }
114
115 #ifndef QL_PATCH_SOLARIS
116
117 /* class template specializations. I havent use CRTP type cast directly
118 because the signature of the integrators is different, grid integration
119 needs the domain. */
122 public:
123 IntegrationBase(Size dimension, Size order)
124 : GaussianQuadMultidimIntegrator(dimension, order) {}
125 Real integrate(const ext::function<Real(const std::vector<Real>& arg)>& f) const override {
126 return GaussianQuadMultidimIntegrator::integrate<Real>(f);
127 }
128 std::vector<Real> integrateV(
129 const ext::function<std::vector<Real>(const std::vector<Real>& arg)>& f)
130 const override {
131 return GaussianQuadMultidimIntegrator::integrate<std::vector<Real>>(f);
132 }
133 ~IntegrationBase() override = default;
134 };
135
136 #endif
137
139 public MultidimIntegral, public LMIntegration {
140 public:
142 const std::vector<ext::shared_ptr<Integrator> >& integrators,
143 Real a, Real b)
144 : MultidimIntegral(integrators),
145 a_(integrators.size(),a), b_(integrators.size(),b) {}
146 Real integrate(const ext::function<Real(const std::vector<Real>& arg)>& f) const override {
147 return MultidimIntegral::operator()(f, a_, b_);
148 }
149 // vector version here....
150 ~IntegrationBase() override = default;
151 const std::vector<Real> a_, b_;
152 };
153
154 // Intended to replace OneFactorCopula
155
283 template <class copulaPolicyImpl>
285 : public virtual Observer , public virtual Observable
286 {//observer if factors as quotes
287 public:
288 void update() override;
290
291 typedef copulaPolicyImpl copulaType;
295 Probability cumulativeY(Real val, Size iVariable) const {
296 return copula_.cumulativeY(val, iVariable);
297 }
300 return copula_.cumulativeZ(z);
301 }
303 Probability density(const std::vector<Real>& m) const {
304 #if defined(QL_EXTRA_SAFETY_CHECKS)
305 QL_REQUIRE(m.size() == nFactors_,
306 "Factor size must match that of model.");
307 #endif
308 return copula_.density(m);
309 }
312 return copula_.inverseCumulativeDensity(p, iFactor);
313 }
317 return copula_.inverseCumulativeY(p, iVariable);
318 }
322 return copula_.inverseCumulativeZ(p);
323 }
329 std::vector<Real> allFactorCumulInverter(const std::vector<Real>& probs) const {
330 return copula_.allFactorCumulInverter(probs);
331 }
333
343 Real latentVarValue(const std::vector<Real>& allFactors,
344 Size iVar) const
345 {
346 return std::inner_product(factorWeights_[iVar].begin(),
347 // systemic term:
348 factorWeights_[iVar].end(), allFactors.begin(),
349 // idiosyncratic term:
350 Real(allFactors[numFactors()+iVar] * idiosyncFctrs_[iVar]));
351 }
352 // \to do write variants of the above, although is the most common case
353
354 const copulaType& copula() const {
355 return copula_;
356 }
357
358
359 // protected:
361
362
371 /*
372 Several (very different) usages make the spez non trivial
373 The final goal is to obtain a sequence generator of the factor
374 samples, several routes are possible depending on the algorithms:
375
376 1.- URNG -> Sequence Gen -> CopulaInversion
377 e.g.: CopulaInversion(RandomSequenceGenerator<MersenneTwisterRNG>)
378 2.- PseudoRSG ------------> CopulaInversion
379 e.g.: CopulaInversion(SobolRSG)
380 3.- URNG -> SpecificMapping -> Sequence Gen (bypasses the copula
381 for performance)
382 e.g.: RandomSequenceGenerator<BoxMullerGaussianRng<
383 MersenneTwisterRNG> >
384
385 Notice that the order the three algorithms involved (uniform gen,
386 sequence construction, distribution mapping) is not always the same.
387 (in fact there could be some other ways to generate but these are
388 the ones in the library now.)
389 Difficulties arise when wanting to use situation 3.- whith a generic
390 RNG, leaving it unspecified
391
392 Derived classes might specialize (on the copula
393 type) to another type of generator if a more efficient algorithm
394 that the distribution inversion is available; rewritig then the
395 nextSequence method for a particular copula implementation.
396 Some combinations of generators might make no sense, while it
397 could be possible to block template classes corresponding to those
398 cases its not done (yet?) (e.g. a BoxMuller under a TCopula.)
399 Dimensionality coherence (between the generator and the copula)
400 should have been checked by the client code.
401 In multithread usage the sequence generator is expect to be already
402 in position.
403 To sample the latent variable itself users should call
404 LatentModel::latentVarValue with these samples.
405 */
406 // Cant use InverseCumulativeRsg since the inverse there has to return a
407 // real number and here a vector is needed, the function inverted here
408 // is multivalued.
409 template <class USNG,
410 // dummy template parameter to allow for 'full' specialization of
411 // inner class without specialization of the outer.
412 bool = true>
414 public:
417 BigNatural seed = 0)
418 : sequenceGen_(copula.numFactors(), seed), // base case construction
419 x_(std::vector<Real>(copula.numFactors()), 1.0),
420 copula_(copula) { }
431 const sample_type& nextSequence() const {
432 typename USNG::sample_type sample =
433 sequenceGen_.nextSequence();
434 x_.value = copula_.allFactorCumulInverter(sample.value);
435 return x_;
436 }
437 private:
438 USNG sequenceGen_;// copy, we might be mutithreaded
440 // no copies
442 };
444 protected:
445 /* \todo Move integrator traits like number of quadrature points,
446 integration domain dimensions, etc to the copula through a static
447 member function. Since they depend on the nature of the probability
448 density distribution thats where they belong.
449 This is why theres one factory per copula policy template parameter
450 (even if this is not used...yet)
451 */
453 public:
454 static ext::shared_ptr<LMIntegration> createLMIntegration(
455 Size dimension,
457 #ifndef QL_PATCH_SOLARIS
459 #else
461 #endif
462 {
463 switch(type) {
464 #ifndef QL_PATCH_SOLARIS
466 return
467 ext::make_shared<
469 dimension, 25);
470 #endif
472 {
473 std::vector<ext::shared_ptr<Integrator> > integrals;
474 for(Size i=0; i<dimension; i++)
475 integrals.push_back(
476 ext::make_shared<TrapezoidIntegral<Default> >(
477 1.e-4, 20));
478 /* This integration domain is tailored for the T
479 distribution; it is too wide for normals or Ts of high
480 order.
481 \todo This needs to be solved by having the copula to
482 provide the integration traits for any integration
483 algorithm since it is the copula that knows the relevant
484 domain for its density distributions. Also to be able to
485 block integrations which will fail; like a quadrature
486 here in some cases.
487 */
488 return
489 ext::make_shared<IntegrationBase<MultidimIntegral> >
490 (integrals, -35., 35.);
491 }
492 default:
493 QL_FAIL("Unknown latent model integration type.");
494 }
495 }
496 private:
498 };
500
501
502 public:
503 // model size, number of latent variables modelled
504 Size size() const {return nVariables_;}
506 Size numFactors() const {return nFactors_;}
509
518 explicit LatentModel(
519 const std::vector<std::vector<Real> >& factorsWeights,
520 const typename copulaType::initTraits& ini =
521 copulaType::initTraits());
531 explicit LatentModel(const std::vector<Real>& factorsWeight,
532 const typename copulaType::initTraits& ini =
533 copulaType::initTraits());
545 explicit LatentModel(Real correlSqr,
546 Size nVariables,
547 const typename copulaType::initTraits& ini = copulaType::initTraits());
560 explicit LatentModel(const Handle<Quote>& singleFactorCorrel,
561 Size nVariables,
562 const typename copulaType::initTraits& ini =
563 copulaType::initTraits());
564
566 const std::vector<std::vector<Real> >& factorWeights() const {
567 return factorWeights_;
568 }
570 const std::vector<Real>& idiosyncFctrs() const {return idiosyncFctrs_;}
571
573 Real latentVariableCorrel(Size iVar1, Size iVar2) const {
574 // true for any normalized combination
575 Real init = (iVar1 == iVar2 ?
576 idiosyncFctrs_[iVar1] * idiosyncFctrs_[iVar1] : Real(0.));
577 return std::inner_product(factorWeights_[iVar1].begin(),
578 factorWeights_[iVar1].end(), factorWeights_[iVar2].begin(),
579 init);
580 }
582
583
587 const ext::function<Real(const std::vector<Real>& v1)>& f) const {
588 // function composition: composes the integrand with the density
589 // through a product.
590 return integration()->integrate(
591 [&](const std::vector<Real>& x){ return copula_.density(x) * f(x); });
592 }
596 std::vector<Real> integratedExpectedValueV(
597 // const ext::function<std::vector<Real>(
598 const ext::function<std::vector<Real>(
599 const std::vector<Real>& v1)>& f ) const {
601 return integration()->integrateV(//see note in LMIntegrators base class
602 [&](const std::vector<Real>& x){ return M(copula_.density(x), f(x)); });
603 }
604 protected:
605 // Integrable models must provide their integrator.
606 // Arguable, not having the integration in the LM class saves that
607 // memory but have an entry in the VT...
608 virtual const ext::shared_ptr<LMIntegration>& integration() const {
609 QL_FAIL("Integration non implemented in Latent model.");
610 }
612
613 // Ordering is: factorWeights_[iVariable][iFactor]
614 mutable std::vector<std::vector<Real> > factorWeights_;
615 /* This is a duplicated value from the data above chosen for memory
616 reasons.
617 I have opted for this one value redundant memory rather than have the
618 memory load of the observable in all factors. Typically Latent models
619 are used in two very different ways: with many factors and not linked
620 to a market observable (typical matrix size above is of tens of
621 thousands entries) or with just one observable value and the matrix is
622 just a scalar. Otherwise, to remove the redundancy, the matrix
623 factorWeights_ should be one of Quotes Handles.
624 Yet it is not entirely true that quotes might be used only in pricing,
625 think sensitivity analysis....
626 \todo Reconsider this, see how expensive truly is.
627 */
629
630 // updated only by correlation observability and constructors.
631 // \sqrt{1-\sum_k \beta_{i,k}^2} the addition being along the factors.
632 // It has therefore the size of the basket. Cached for perfomance
633 mutable std::vector<Real> idiosyncFctrs_;
635 mutable Size nFactors_;//matches idiosyncFctrs_[0].size();i=0 or any
637 mutable Size nVariables_;// matches idiosyncFctrs_.size()
638
640 };
641
642
643
644
645 // Defines ----------------------------------------------------------------
646
647#ifndef __DOXYGEN__
648
649 template <class Impl>
651 const std::vector<std::vector<Real> >& factorWeights,
652 const typename Impl::initTraits& ini)
653 : factorWeights_(factorWeights),
654 nFactors_(factorWeights[0].size()),
655 nVariables_(factorWeights.size()), copula_(factorWeights, ini)
656 {
657 for(Size i=0; i<factorWeights.size(); i++) {
658 idiosyncFctrs_.push_back(std::sqrt(1.-
659 std::inner_product(factorWeights[i].begin(),
660 factorWeights[i].end(),
661 factorWeights[i].begin(), Real(0.))));
662 // while at it, check sizes are coherent:
663 QL_REQUIRE(factorWeights[i].size() == nFactors_,
664 "Name " << i << " provides a different number of factors");
665 }
666 }
667
668 template <class Impl>
670 const std::vector<Real>& factorWeights,
671 const typename Impl::initTraits& ini)
672 : nFactors_(1),
673 nVariables_(factorWeights.size())
674 {
675 for (Real factorWeight : factorWeights)
676 factorWeights_.emplace_back(1, factorWeight);
677 for (Real factorWeight : factorWeights)
678 idiosyncFctrs_.push_back(std::sqrt(1. - factorWeight * factorWeight));
679 //convert row to column vector....
681 }
682
683 template <class Impl>
685 const Real correlSqr,
686 Size nVariables,
687 const typename Impl::initTraits& ini)
688 : factorWeights_(nVariables, std::vector<Real>(1, correlSqr)),
689 idiosyncFctrs_(nVariables,
690 std::sqrt(1.-correlSqr*correlSqr)),
691 nFactors_(1),
692 nVariables_(nVariables),
693 copula_(factorWeights_, ini)
694 { }
695
696 template <class Impl>
698 const Handle<Quote>& singleFactorCorrel,
699 Size nVariables,
700 const typename Impl::initTraits& ini)
701 : factorWeights_(nVariables, std::vector<Real>(1,
702 std::sqrt(singleFactorCorrel->value()))),
703 cachedMktFactor_(singleFactorCorrel),
704 idiosyncFctrs_(nVariables,
705 std::sqrt(1.-singleFactorCorrel->value())),
706 nFactors_(1),
707 nVariables_(nVariables),
708 copula_(factorWeights_, ini)
709 {
711 }
712
713#endif
714
715 template <class Impl>
717 /* only registration with the single market correl quote. If we get
718 register with something else remember that the quote stores correlation
719 and the model need factor values; which for one factor models are the
720 square root of the correlation.
721 */
722 factorWeights_ = std::vector<std::vector<Real> >(nVariables_,
723 std::vector<Real>(1, std::sqrt(cachedMktFactor_->value())));
724 idiosyncFctrs_ = std::vector<Real>(nVariables_,
725 std::sqrt(1.-cachedMktFactor_->value()));
726 copula_ = copulaType(factorWeights_, copula_.getInitTraits());
727 notifyObservers();
728 }
729
730#ifndef __DOXYGEN__
731
732 //----Template partial specializations of the random FactorSampler--------
733 /*
734 Notice that while the default template needs a sequence generator the
735 specializations need a number generator. This is forced at the time the
736 concrete policy class is used in the template parameter, if it has been
737 specialized it needs the sample type typedef to match at compilation.
738
739 Notice here the outer class template is specialized only, leaving the inner
740 generator still a class template. Apparently old versions of gcc (3.x) bug
741 on this one not recognizing the specialization.
742 */
747 template<class TC> template<class URNG, bool dummy>
748 class LatentModel<TC>
749 ::FactorSampler <RandomSequenceGenerator<BoxMullerGaussianRng<URNG> > ,
750 dummy> {
751 typedef URNG urng_type;
752 public:
753 //Size below must be == to the numb of factors idiosy + systemi
754 typedef Sample<std::vector<Real> > sample_type;
755 explicit FactorSampler(const GaussianCopulaPolicy& copula,
756 BigNatural seed = 0)
757 : boxMullRng_(copula.numFactors(),
758 BoxMullerGaussianRng<urng_type>(urng_type(seed))){ }
759 const sample_type& nextSequence() const {
760 return boxMullRng_.nextSequence();
761 }
762 private:
763 RandomSequenceGenerator<BoxMullerGaussianRng<urng_type> > boxMullRng_;
764 };
765
773 template<class TC> template<class URNG, bool dummy>//uniform number expected
774 class LatentModel<TC>
775 ::FactorSampler<RandomSequenceGenerator<PolarStudentTRng<URNG> > ,
776 dummy> {
777 typedef URNG urng_type;
778 public:
779 typedef Sample<std::vector<Real> > sample_type;
780 explicit FactorSampler(const TCopulaPolicy& copula, BigNatural seed = 0)
781 : sequence_(std::vector<Real> (copula.numFactors()), 1.0),
782 urng_(seed) {
783 // 1 == urng.dimension() is enforced by the sample type
784 const std::vector<Real>& varF = copula.varianceFactors();
785 for (Real i : varF) // ...use back inserter lambda
786 trng_.push_back(PolarStudentTRng<urng_type>(2. / (1. - i * i), urng_));
787 }
788 const sample_type& nextSequence() const {
789 Size i=0;
790 for(; i<trng_.size(); i++)//systemic samples plus one idiosyncratic
791 sequence_.value[i] = trng_[i].next().value;
792 for(; i<sequence_.value.size(); i++)//rest of idiosyncratic samples
793 sequence_.value[i] = trng_.back().next().value;
794 return sequence_;
795 }
796 private:
797 mutable sample_type sequence_;
798 urng_type urng_;
799 mutable std::vector<PolarStudentTRng<urng_type> > trng_;
800 };
801
802#endif
803
804}
805
806
807#endif
Gaussian random number generator.
Integrates a vector or scalar function of vector domain.
Shared handle to an observable.
Definition: handle.hpp:41
std::vector< Real > integrateV(const ext::function< std::vector< Real >(const std::vector< Real > &arg)> &f) const override
Real integrate(const ext::function< Real(const std::vector< Real > &arg)> &f) const override
IntegrationBase(const std::vector< ext::shared_ptr< Integrator > > &integrators, Real a, Real b)
Real integrate(const ext::function< Real(const std::vector< Real > &arg)> &f) const override
virtual std::vector< Real > integrateV(const ext::function< std::vector< Real >(const std::vector< Real > &arg)> &f) const
Definition: latentmodel.hpp:85
virtual ~LMIntegration()=default
virtual Real integrate(const ext::function< Real(const std::vector< Real > &arg)> &f) const =0
const sample_type & nextSequence() const
FactorSampler(const copulaType &copula, BigNatural seed=0)
Sample< std::vector< Real > > sample_type
static ext::shared_ptr< LMIntegration > createLMIntegration(Size dimension, LatentModelIntegrationType::LatentModelIntegrationType type=LatentModelIntegrationType::GaussianQuadrature)
Generic multifactor latent variable model.
Real latentVarValue(const std::vector< Real > &allFactors, Size iVar) const
Handle< Quote > cachedMktFactor_
LatentModel(const Handle< Quote > &singleFactorCorrel, Size nVariables, const typename copulaType::initTraits &ini=copulaType::initTraits())
Real latentVariableCorrel(Size iVar1, Size iVar2) const
Latent variable correlations:
Real inverseCumulativeY(Probability p, Size iVariable) const
std::vector< std::vector< Real > > factorWeights_
void update() override
Size nVariables_
Number of latent model variables, idiosyncratic terms or model dim.
const std::vector< Real > & idiosyncFctrs() const
Provides values of the normalized idiosyncratic factors .
Real inverseCumulativeDensity(Probability p, Size iFactor) const
Inverse cumulative distribution of the systemic factor iFactor.
const copulaType & copula() const
Size numFactors() const
Number of systemic factors.
Probability density(const std::vector< Real > &m) const
Density function of M, the market/systemic factors.
Probability cumulativeY(Real val, Size iVariable) const
std::vector< Real > allFactorCumulInverter(const std::vector< Real > &probs) const
std::vector< Real > idiosyncFctrs_
LatentModel(Real correlSqr, Size nVariables, const typename copulaType::initTraits &ini=copulaType::initTraits())
LatentModel(const std::vector< Real > &factorsWeight, const typename copulaType::initTraits &ini=copulaType::initTraits())
copulaPolicyImpl copulaType
Probability cumulativeZ(Real z) const
Cumulative distribution of Z, the idiosyncratic/error factors.
const std::vector< std::vector< Real > > & factorWeights() const
Provides values of the factors .
Size numTotalFactors() const
Number of total free random factors; systemic and idiosyncratic.
Real inverseCumulativeZ(Probability p) const
virtual const ext::shared_ptr< LMIntegration > & integration() const
Size nFactors_
Number of systemic factors.
Real integratedExpectedValue(const ext::function< Real(const std::vector< Real > &v1)> &f) const
LatentModel(const std::vector< std::vector< Real > > &factorsWeights, const typename copulaType::initTraits &ini=copulaType::initTraits())
std::vector< Real > integratedExpectedValueV(const ext::function< std::vector< Real >(const std::vector< Real > &v1)> &f) const
Integrates a vector or scalar function of vector domain.
Real operator()(const ext::function< Real(const std::vector< Real > &)> &f, const std::vector< Real > &a, const std::vector< Real > &b) const
Object that notifies its changes to a set of observers.
Definition: observable.hpp:62
Object that gets notified when a given observable changes.
Definition: observable.hpp:116
std::pair< iterator, bool > registerWith(const ext::shared_ptr< Observable > &)
Definition: observable.hpp:228
Integral of a one-dimensional function.
QL_REAL Real
real number
Definition: types.hpp:50
Real Probability
probability
Definition: types.hpp:82
std::size_t Size
size of a container
Definition: types.hpp:58
Definition: any.hpp:35
unsigned QL_BIG_INTEGER BigNatural
large positive integer
Definition: types.hpp:46
STL namespace.
weighted sample
Definition: sample.hpp:35
QL_DEPRECATED typedef std::vector< Real > result_type
Definition: latentmodel.hpp:49
std::vector< Real > operator()(Real d, std::vector< Real > v)
Definition: latentmodel.hpp:51