Skip to content

Commit c75a5c8

Browse files
pcaspersjenkins
authored andcommitted
QPR-12417 generate past cashflows in scripted trade
1 parent 452c469 commit c75a5c8

6 files changed

Lines changed: 45 additions & 17 deletions

File tree

Docs/ScriptedTrade/docs/language.tex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,8 @@
466466
\item here $P_{ccy}$ is the discount factor in currency ccy, $FX$ is the FX spot from ccy to base and $N$ is the model
467467
numeraire
468468
\item $d\leq p$ must hold
469-
\item if $p$ lies on or before the evaluation date, the result is zero; $X$ is not evaluated in this case
469+
\item if $p$ lies on or before the evaluation date, the result is zero; $X$ is not evaluated in this case.
470+
Note that $X$ is evaluated in the LOGPAY function though, see \ref{function_logpay}.
470471
\item avoids reading non-relevant past fixings from the index history
471472
\item if $d$ lies before (but $p$ after) the evaluation date, it is set to the evaluation date, i.e. the result is
472473
computed as of the evaluation date
@@ -514,6 +515,10 @@
514515
under leg number $2$ and flow Type ``Interest''. The same holds for Payoff3, but if any amounts were booked using the
515516
slot parameter $3$ previously they will be overwritten with the current amount.
516517

518+
Note: Even if $p$ lies on or before the evaluation date, a cashflow entry will be generated. If one wants to explicitly
519+
avoid that, e.g. because past fixings are not available to evaluate the past paid amounts, it can be done with an IF
520+
construct comparing the pay date with TODAY.
521+
517522
% ====================================================
518523
\stsubsection{Function {\tt NPV, NPVMEM}}\label{function_npv}
519524
% ====================================================

OREAnalytics/orea/app/reportwriter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,9 @@ void ReportWriter::writeCashflow(ore::data::Report& report, const std::string& b
580580
if (cf.effectiveCapVolatility != Null<Real>())
581581
capVolatility = cf.effectiveCapVolatility;
582582

583+
if (!includePastCashflows && cf.payDate <= asof)
584+
continue;
585+
583586
report.next()
584587
.add(trade->id())
585588
.add(trade->tradeType())

OREData/ored/scripting/computationgraphbuilder.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,7 +1029,7 @@ class ASTRunner : public AcyclicVisitor,
10291029
QL_REQUIRE(model_, "model is null");
10301030
// handle case of past payments: do not evaluate the other parameters, since not needed (e.g. past fixings)
10311031
Date pay = boost::get<EventVec>(paydate).value;
1032-
if (pay <= model_->referenceDate()) {
1032+
if (pay <= model_->referenceDate() && !log) {
10331033
value.push(RandomVariable(size_, 0.0));
10341034
std::size_t node = cg_const(g_, 0.0);
10351035
value_node.push(node);
@@ -1053,7 +1053,9 @@ class ASTRunner : public AcyclicVisitor,
10531053
QL_REQUIRE(obs <= pay, "observation date (" << obs << ") <= payment date (" << pay << ") required");
10541054
RandomVariable result; // uninitialised, since model dependent
10551055
value.push(result);
1056-
std::size_t node = model_->pay(amount_node, obs, pay, pccy);
1056+
std::size_t node =
1057+
pay <= model_->referenceDate() ? cg_const(g_, 0.0) : model_->pay(amount_node, obs, pay, pccy);
1058+
std::size_t cfnode = pay <= model_->referenceDate() ? amount_node : node;
10571059
value_node.push(node);
10581060
TRACE("pay( " << amount << " , " << obsdate << " , " << paydate << " , " << paycurr << " ) (#" << node
10591061
<< ")",
@@ -1092,10 +1094,10 @@ class ASTRunner : public AcyclicVisitor,
10921094
}
10931095
// add nodes necessary to write paylog to keepNodes set
10941096
std::size_t filterNode = filter_node.top() == ComputationGraph::nan ? cg_const(g_, 1.0) : filter_node.top();
1095-
keepNodes_.insert(node);
1097+
keepNodes_.insert(cfnode);
10961098
keepNodes_.insert(filterNode);
10971099
// add paylog entry data
1098-
payLogEntries_.push_back({node, filterNode, obs, pay, pccy, (Size)legno, cftype, (Size)slot});
1100+
payLogEntries_.push_back({cfnode, filterNode, obs, pay, pccy, (Size)legno, cftype, (Size)slot});
10991101
}
11001102
}
11011103
}

OREData/ored/scripting/engines/scriptedinstrumentpricingengine.cpp

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,11 @@ void ScriptedInstrumentPricingEngine::calculate() const {
107107
// set up script engine and run it
108108

109109
ScriptEngine engine(ast_, workingContext, model_);
110-
auto paylog = boost::make_shared<PayLog>();
110+
111+
boost::shared_ptr<PayLog> paylog;
112+
if (generateAdditionalResults_)
113+
paylog = boost::make_shared<PayLog>();
114+
111115
engine.run(script_, interactive_, paylog);
112116

113117
// extract npv result and set it
@@ -191,8 +195,12 @@ void ScriptedInstrumentPricingEngine::calculate() const {
191195
for (Size i = 0; i < paylog->size(); ++i) {
192196
// cashflow is written as expectation of deflated base ccy amount at T0, converted to flow ccy
193197
// with the T0 FX Spot and compounded back to the pay date on T0 curves
194-
Real fx = model_->fxSpotT0(paylog->currencies().at(i), model_->baseCcy());
195-
Real discount = model_->discount(referenceDate, paylog->dates().at(i), paylog->currencies().at(i)).at(0);
198+
Real fx = 1.0;
199+
Real discount = 1.0;
200+
if (paylog->dates().at(i) > model_->referenceDate()) {
201+
fx = model_->fxSpotT0(paylog->currencies().at(i), model_->baseCcy());
202+
discount = model_->discount(referenceDate, paylog->dates().at(i), paylog->currencies().at(i)).at(0);
203+
}
196204
cashFlowResults[i].amount = model_->extractT0Result(paylog->amounts().at(i)) / fx / discount;
197205
cashFlowResults[i].payDate = paylog->dates().at(i);
198206
cashFlowResults[i].currency = paylog->currencies().at(i);
@@ -202,10 +210,12 @@ void ScriptedInstrumentPricingEngine::calculate() const {
202210
<< cashFlowResults[i].currency << cashFlowResults[i].amount << " "
203211
<< cashFlowResults[i].currency << "-" << model_->baseCcy() << " " << fx << " discount("
204212
<< cashFlowResults[i].currency << ") " << discount);
205-
addMcErrorEstimate("cashflow_" + std::to_string(paylog->legNos().at(i)) + "_" +
206-
std::to_string(++cashflowNumber[paylog->legNos().at(i)]) + "_MCErrEst",
207-
paylog->amounts().at(i) /
208-
RandomVariable(paylog->amounts().at(i).size(), (fx * discount)));
213+
if (paylog->dates().at(i) > model_->referenceDate()) {
214+
addMcErrorEstimate("cashflow_" + std::to_string(paylog->legNos().at(i)) + "_" +
215+
std::to_string(++cashflowNumber[paylog->legNos().at(i)]) + "_MCErrEst",
216+
paylog->amounts().at(i) /
217+
RandomVariable(paylog->amounts().at(i).size(), (fx * discount)));
218+
}
209219
}
210220
results_.additionalResults["cashFlowResults"] = cashFlowResults;
211221

OREData/ored/scripting/engines/scriptedinstrumentpricingenginecg.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,12 @@ void ScriptedInstrumentPricingEngineCG::calculate() const {
384384
for (Size i = 0; i < paylog->size(); ++i) {
385385
// cashflow is written as expectation of deflated base ccy amount at T0, converted to flow ccy
386386
// with the T0 FX Spot and compounded back to the pay date on T0 curves
387-
Real fx = model_->getDirectFxSpotT0(paylog->currencies().at(i), model_->baseCcy());
388-
Real discount = model_->getDirectDiscountT0(paylog->dates().at(i), paylog->currencies().at(i));
387+
Real fx = 1.0;
388+
Real discount = 1.0;
389+
if (paylog->dates().at(i) > model_->referenceDate()) {
390+
fx = model_->getDirectFxSpotT0(paylog->currencies().at(i), model_->baseCcy());
391+
discount = model_->getDirectDiscountT0(paylog->dates().at(i), paylog->currencies().at(i));
392+
}
389393
cashFlowResults[i].amount = model_->extractT0Result(paylog->amounts().at(i)) / fx / discount;
390394
cashFlowResults[i].payDate = paylog->dates().at(i);
391395
cashFlowResults[i].currency = paylog->currencies().at(i);

OREData/ored/scripting/scriptengine.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,7 @@ class ASTRunner : public AcyclicVisitor,
769769
QL_REQUIRE(model_, "model is null");
770770
// handle case of past payments: do not evaluate the other parameters, since not needed (e.g. past fixings)
771771
Date pay = boost::get<EventVec>(paydate).value;
772-
if (pay <= model_->referenceDate()) {
772+
if (pay <= model_->referenceDate() && !log) {
773773
value.push(RandomVariable(size_, 0.0));
774774
TRACE("pay() = 0, since paydate " << paydate << " <= " << model_->referenceDate(), n);
775775
} else {
@@ -786,7 +786,11 @@ class ASTRunner : public AcyclicVisitor,
786786
Date obs = boost::get<EventVec>(obsdate).value;
787787
std::string pccy = boost::get<CurrencyVec>(paycurr).value;
788788
QL_REQUIRE(obs <= pay, "observation date (" << obs << ") <= payment date (" << pay << ") required");
789-
RandomVariable result = model_->pay(boost::get<RandomVariable>(amount), obs, pay, pccy);
789+
RandomVariable result = pay <= model_->referenceDate()
790+
? RandomVariable(model_->size(), 0.0)
791+
: model_->pay(boost::get<RandomVariable>(amount), obs, pay, pccy);
792+
RandomVariable cashflowResult =
793+
pay <= model_->referenceDate() ? boost::get<RandomVariable>(amount) : result;
790794
if (!log || paylog_ == nullptr) {
791795
TRACE("pay( " << amount << " , " << obsdate << " , " << paydate << " , " << paycurr << " )", n);
792796
} else {
@@ -819,7 +823,7 @@ class ASTRunner : public AcyclicVisitor,
819823
QL_REQUIRE(slot >= 1, " slot must be >= 1");
820824
}
821825
}
822-
paylog_->write(result, filter.top(), obs, pay, pccy, static_cast<Size>(legno), cftype,
826+
paylog_->write(cashflowResult, filter.top(), obs, pay, pccy, static_cast<Size>(legno), cftype,
823827
static_cast<Size>(slot));
824828
TRACE("logpay( " << amount << " , " << obsdate << " , " << paydate << " , " << paycurr << " , " << legno
825829
<< " , " << cftype << " , " << slot << ")",

0 commit comments

Comments
 (0)