Skip to content

Commit d7e5a97

Browse files
pcaspersGitlab CI
authored andcommitted
Merge branch 'feature/QPR-13698' into 'master'
QPR-13698 WorstPerformanceRainbowOption06 + Add script function Closes QPR-13698 See merge request qs/oreplus!3083
1 parent 88d3928 commit d7e5a97

19 files changed

Lines changed: 376 additions & 13 deletions

Docs/ScriptedTrade/docs/language.tex

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
\verb+max+ & \\
4242
\verb+min+ & \\
4343
\verb+pow+ & \\
44+
\verb+frac+ & \\
45+
\verb+round+ & \\
4446
\verb+black+ & \\
4547
\verb+dcf+ & \\
4648
\verb+days+ & \\ \hline
@@ -398,21 +400,21 @@
398400
\item {\tt REQUIRE Strike >= 0;}
399401
\end{itemize}
400402

401-
% ====================================================
402-
\stsubsection{Functions {\tt min}, {\tt max}, {\tt pow}}
403-
% ====================================================
403+
% =========================================================================
404+
\stsubsection{Functions {\tt min}, {\tt max}, {\tt pow}}, {\tt round(x,y)}
405+
% =========================================================================
404406

405-
Binary functions {\tt min(x,y)}, {\tt max(x,y)}, {\tt pow(x,y)}, applicable to numbers only.
407+
Binary functions {\tt min(x,y)}, {\tt max(x,y)}, {\tt pow(x,y)}, {\tt round(x,y)}, applicable to numbers only.
406408

407-
% ====================================================
408-
\stsubsection{Functions {\tt -}, abs, exp, ln, sqrt}
409-
% ====================================================
409+
% ========================================================
410+
\stsubsection{Functions {\tt -}, abs, exp, ln, sqrt, frac}
411+
% ========================================================
410412

411-
Unary functions {\tt -x}, {\tt abs(x)}, {\tt exp(x)}, {\tt ln(x)}, {\tt sqrt(x)}, applicable to numbers only.
413+
Unary functions {\tt -x}, {\tt abs(x)}, {\tt exp(x)}, {\tt ln(x)}, {\tt sqrt(x)}, {\tt frac(x)} which take the fractional part of a float, applicable to numbers only.
412414

413-
% ====================================================
415+
% =======================================================
414416
\stsubsection{Functions {\tt normalPdf}, {\tt normalCdf}}
415-
% ====================================================
417+
% =======================================================
416418

417419
Returns the standard normal pdf $\phi(x)$ resp. cdf $\Phi(x)$, applicable to numbers only.
418420

Docs/UserGuide/tradedata/rainbow_option.tex

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,3 +1648,169 @@ \subsubsection*{Worst Performance Rainbow Option 05}
16481648
COMM underlyings), this will result in a quanto payoff. Notice section ``Payment Currency'' in ore/Docs/ScriptedTrade. \\
16491649
Allowable values: See Table \ref{tab:currency} for allowable currency codes.
16501650
\end{itemize}
1651+
1652+
\subsubsection*{Worst Performance Rainbow Option 06}
1653+
1654+
A worst performance rainbow option 06 is represented as a {\em scripted trade},
1655+
refer to the scripted trade documentation in ore/Docs/ScriptedTrade
1656+
for an introduction.
1657+
1658+
Trade input and the payoff script are described below.
1659+
1660+
\begin{minted}[fontsize=\scriptsize]{xml}
1661+
<Trade id="EQ_WorstPerformanceRainbowOption06">
1662+
<TradeType>ScriptedTrade</TradeType>
1663+
<Envelope>
1664+
<CounterParty>CPTY_A</CounterParty>
1665+
<NettingSetId>CPTY_A</NettingSetId>
1666+
<AdditionalFields/>
1667+
</Envelope>
1668+
<WorstPerformanceRainbowOption06Data>
1669+
<LongShort type="longShort">Long</LongShort>
1670+
<Quantity type="number">200000</Quantity>
1671+
<Underlyings type="index">
1672+
<Value>EQ-RIC:.STOXX50E</Value>
1673+
<Value>EQ-RIC:.SPX</Value>
1674+
</Underlyings>
1675+
<InitialPrices type="number">
1676+
<Value>5455.60</Value>
1677+
<Value>500</Value>
1678+
</InitialPrices>
1679+
<StrikePrices type="number">
1680+
<Value>5600</Value>
1681+
<Value>550</Value>
1682+
</StrikePrices>
1683+
<BarrierLevels type="number">
1684+
<Value>5600</Value>
1685+
<Value>550</Value>
1686+
</BarrierLevels>
1687+
<KnockInPrices type="number">
1688+
<Value>5600</Value>
1689+
<Value>550</Value>
1690+
</KnockInPrices>
1691+
<BonusCoupon type="number">0.1430</BonusCoupon>
1692+
<ObservationSchedule type="event">
1693+
<ScheduleData>
1694+
<Rules>
1695+
<StartDate>2020-03-11</StartDate>
1696+
<EndDate>2020-09-04</EndDate>
1697+
<Tenor>1D</Tenor>
1698+
<Calendar>USD</Calendar>
1699+
<Convention>ModifiedFollowing</Convention>
1700+
<TermConvention>ModifiedFollowing</TermConvention>
1701+
<Rule>Forward</Rule>
1702+
</Rules>
1703+
</ScheduleData>
1704+
</ObservationSchedule>
1705+
<ObservationDate type="event">2020-09-04</ObservationDate>
1706+
<SettlementDate type="event">2020-09-11</SettlementDate>
1707+
<PayCcy type="currency">EUR</PayCcy>
1708+
</WorstPerformanceRainbowOption06Data>
1709+
</Trade>
1710+
\end{minted}
1711+
1712+
The WorstPerformanceRainbowOption06 script referenced in the trade above is shown in listing
1713+
\ref{lst:worst_performance_rainbow_option_06}.
1714+
1715+
\begin{listing}[hbt]
1716+
\begin{minted}[fontsize=\scriptsize]{Basic}
1717+
REQUIRE SIZE(Underlyings) == SIZE(InitialPrices) AND SIZE(Underlyings) == SIZE(StrikePrices);
1718+
REQUIRE SIZE(Underlyings) == SIZE(BarrierLevels) AND SIZE(Underlyings) == SIZE(KnockInPrices);
1719+
REQUIRE ObservationDate <= SettlementDate;
1720+
1721+
NUMBER indexInitial, indexFinal, performance, d, spot;
1722+
NUMBER worstPerformance, payoff, premium, knockedIn, u, worstUnderlying, worstUnderlyingFinalPrice;
1723+
NUMBER deliverableAsset,fractionalAmount, fractionalCashAmount;
1724+
1725+
worstUnderlying = 1;
1726+
1727+
FOR u IN (1, SIZE(Underlyings), 1) DO
1728+
indexInitial = InitialPrices[u];
1729+
indexFinal = Underlyings[u](ObservationDate);
1730+
performance = indexFinal / indexInitial;
1731+
1732+
IF {u == 1} OR {performance < worstPerformance} THEN
1733+
worstPerformance = performance;
1734+
worstUnderlyingFinalPrice = indexFinal;
1735+
worstUnderlying = u;
1736+
END;
1737+
END;
1738+
1739+
FOR d IN (1, SIZE(ObservationSchedule), 1) DO
1740+
IF knockedIn == 0 THEN
1741+
FOR u IN (1, SIZE(Underlyings), 1) DO
1742+
spot = Underlyings[u](ObservationSchedule[d]);
1743+
IF spot < KnockInPrices[u] THEN
1744+
knockedIn = 1;
1745+
END;
1746+
END;
1747+
END;
1748+
END;
1749+
1750+
IF knockedIn == 1 AND worstUnderlyingFinalPrice < StrikePrices[worstUnderlying] THEN
1751+
deliverableAsset = Quantity/StrikePrices[worstUnderlying];
1752+
fractionalAmount = frac(deliverableAsset);
1753+
fractionalAmount = round(fractionalAmount,4);
1754+
fractionalCashAmount = worstUnderlyingFinalPrice*fractionalAmount;
1755+
payoff = round(fractionalCashAmount,2)+round(deliverableAsset,0);
1756+
ELSE
1757+
IF worstUnderlyingFinalPrice >= BarrierLevels[worstUnderlying] THEN
1758+
payoff = Quantity*(1+max(BonusCoupon, worstPerformance-1));
1759+
ELSE
1760+
payoff = Quantity;
1761+
END;
1762+
END;
1763+
1764+
payoff = LOGPAY(payoff, ObservationDate, SettlementDate, PayCcy, 1, Payoff);
1765+
1766+
Option = LongShort * payoff;
1767+
\end{minted}
1768+
\caption{Payoff script for a WorstPerformanceRainbowOption06.}
1769+
\label{lst:worst_performance_rainbow_option_06}
1770+
\end{listing}
1771+
1772+
The payout formula, determined on the \lstinline!ObservationDate!, is as follows, where
1773+
$worstPerformance$ is the performance, i.e.\ $S_T/S_0$, of the worst-performing asset as
1774+
of the final determination date $T$. The payout for a long put option is as follows:
1775+
1776+
If a knock-in event was triggered and the Final Reference Price of the Worst Performing Underlying
1777+
is below its Strike Price,
1778+
\begin{equation*}
1779+
Payout = \text{\lstinline!Quantity!} * S * \text{FractionalAmount} + \text{round}(\text{fractionalCashAmount})
1780+
\end{equation*}
1781+
with Fractional Amount being the fractional share resulting from the calculation of the Deliverable Assets. $K$ being the strike level of the
1782+
worst underlying performing asset, $S$ that underlying final price.
1783+
\begin{equation*}
1784+
FractionalAmount = \text{\lstinline!Quantity!} / \text{K}
1785+
\end{equation*}
1786+
1787+
The meanings and allowable values for the \lstinline!WorstPerformanceRainbowOption06Data! node below.
1788+
1789+
\begin{itemize}
1790+
\item{}[longShort] \lstinline!LongShort!: Own party position in the option. \\
1791+
Allowable values: \emph{Long, Short}.
1792+
\item{}[index] \lstinline!Underlyings!: The basket of underlyings. \\
1793+
Allowable values: See ore/Docs/ScriptedTrade's Index Section for allowable values.
1794+
\item{}[number] \lstinline!InitialPrices!: The agreed initial price for each basket underlying. \\
1795+
Allowable values: Any positive number.
1796+
\item{}[number] \lstinline!StrikePrices!: The strike prices used within the calculation agent. \\
1797+
Allowable values: Any number.
1798+
\item{}[number] \lstinline!KnockInPrices!: The agreed European knock-in barrier level. \\
1799+
Allowable values: Any number.
1800+
\item{}[number] \lstinline!Quantity!: A quantity multiplier applied to the payoff. \\
1801+
Allowable values: Any number.
1802+
\item{}[number] \lstinline!BarrierLevels!: The agreed barrier level. \\
1803+
Allowable values: Any number.
1804+
\item{}[number] \lstinline!BonusCoupon!: A percentage. \\
1805+
Allowable values: Any number.
1806+
\item{}[event] \lstinline!ObservationDate!: The date on which the final levels of the assets are determined. \\
1807+
Allowable values: See \lstinline!Date! in Table \ref{tab:allow_stand_data}.
1808+
\item{}[event] \lstinline!SettlementDate!: The settlement date for the payoff. \\
1809+
Allowable values: See \lstinline!Date! in Table \ref{tab:allow_stand_data}.
1810+
\item{}[currency] \lstinline!PayCcy!: The payment currency. For FX, where the underlying is provided
1811+
in the form \lstinline!FX-SOURCE-CCY1-CCY2! (see Table \ref{tab:fxindex_data}) this should
1812+
be \lstinline!CCY2!. If \lstinline!CCY1! or the currency of the underlying (for EQ and
1813+
COMM underlyings), this will result in a quanto payoff. The \lstinline!StrikePrices! and \lstinline!BarrierLevels! should be expressed in
1814+
as amount of CCY1 in CCY2.Notice section ``Payment Currency'' in ore/Docs/ScriptedTrade. \\
1815+
Allowable values: See Table \ref{tab:currency} for allowable currency codes.
1816+
\end{itemize}

OREData/ored/scripting/ast.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,22 @@ void FunctionMinNode::accept(AcyclicVisitor& v) {
157157
ASTNode::accept(v);
158158
}
159159

160+
void FunctionFractionNode::accept(AcyclicVisitor& v) {
161+
auto v1 = dynamic_cast<Visitor<FunctionFractionNode>*>(&v);
162+
if (v1 != nullptr)
163+
v1->visit(*this);
164+
else
165+
ASTNode::accept(v);
166+
}
167+
168+
void FunctionRoundNode::accept(AcyclicVisitor& v) {
169+
auto v1 = dynamic_cast<Visitor<FunctionRoundNode>*>(&v);
170+
if (v1 != nullptr)
171+
v1->visit(*this);
172+
else
173+
ASTNode::accept(v);
174+
}
175+
160176
void FunctionPowNode::accept(AcyclicVisitor& v) {
161177
auto v1 = dynamic_cast<Visitor<FunctionPowNode>*>(&v);
162178
if (v1 != nullptr)

OREData/ored/scripting/ast.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ struct FunctionMinNode : public ASTNode {
129129
void accept(AcyclicVisitor&) override;
130130
};
131131

132+
struct FunctionFractionNode : public ASTNode {
133+
FunctionFractionNode(const std::vector<ASTNodePtr>& args) : ASTNode(args, 1, 1) {}
134+
void accept(AcyclicVisitor&) override;
135+
};
136+
137+
struct FunctionRoundNode : public ASTNode {
138+
FunctionRoundNode(const std::vector<ASTNodePtr>& args) : ASTNode(args, 2, 2) {}
139+
void accept(AcyclicVisitor&) override;
140+
};
141+
132142
struct FunctionPowNode : public ASTNode {
133143
FunctionPowNode(const std::vector<ASTNodePtr>& args) : ASTNode(args, 2, 2) {}
134144
void accept(AcyclicVisitor&) override;

OREData/ored/scripting/astprinter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class ASTPrinter : public AcyclicVisitor,
3939
public Visitor<FunctionNormalPdfNode>,
4040
public Visitor<FunctionMinNode>,
4141
public Visitor<FunctionMaxNode>,
42+
public Visitor<FunctionFractionNode>,
43+
public Visitor<FunctionRoundNode>,
4244
public Visitor<FunctionPowNode>,
4345
public Visitor<FunctionBlackNode>,
4446
public Visitor<FunctionDcfNode>,
@@ -92,6 +94,8 @@ class ASTPrinter : public AcyclicVisitor,
9294
void visit(FunctionNormalPdfNode& n) override { print("FunctionNormalPdf", n); }
9395
void visit(FunctionMinNode& n) override { print("FunctionMin", n); }
9496
void visit(FunctionMaxNode& n) override { print("FunctionMax", n); }
97+
void visit(FunctionFractionNode& n) override { print("FunctionFraction", n); }
98+
void visit(FunctionRoundNode& n) override { print("FunctionRound", n); }
9599
void visit(FunctionPowNode& n) override { print("FunctionPow", n); }
96100
void visit(FunctionBlackNode& n) override { print("FunctionBlack", n); }
97101
void visit(FunctionDcfNode& n) override { print("FunctionDcf", n); }

OREData/ored/scripting/asttoscriptconverter.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ class ASTToScriptConverter : public AcyclicVisitor,
4343
public Visitor<FunctionNormalPdfNode>,
4444
public Visitor<FunctionMinNode>,
4545
public Visitor<FunctionMaxNode>,
46+
public Visitor<FunctionFractionNode>,
47+
public Visitor<FunctionRoundNode>,
4648
public Visitor<FunctionPowNode>,
4749
public Visitor<FunctionBlackNode>,
4850
public Visitor<FunctionDcfNode>,
@@ -167,6 +169,19 @@ class ASTToScriptConverter : public AcyclicVisitor,
167169
script = "max(" + left + ", " + right + ")";
168170
}
169171

172+
void visit(FunctionFractionNode& n) override {
173+
n.args[0]->accept(*this);
174+
script = "frac(" + script + ")";
175+
}
176+
177+
void visit(FunctionRoundNode& n) override {
178+
n.args[0]->accept(*this);
179+
auto left = script;
180+
n.args[1]->accept(*this);
181+
auto right = script;
182+
script = "round(" + left + ", " + right + ")";
183+
}
184+
170185
void visit(FunctionPowNode& n) override {
171186
n.args[0]->accept(*this);
172187
auto left = script;

OREData/ored/scripting/computationgraphbuilder.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ class ASTRunner : public AcyclicVisitor,
8181
public Visitor<FunctionNormalPdfNode>,
8282
public Visitor<FunctionMinNode>,
8383
public Visitor<FunctionMaxNode>,
84+
public Visitor<FunctionFractionNode>,
85+
public Visitor<FunctionRoundNode>,
8486
public Visitor<FunctionPowNode>,
8587
public Visitor<FunctionBlackNode>,
8688
public Visitor<FunctionDcfNode>,
@@ -340,6 +342,14 @@ class ASTRunner : public AcyclicVisitor,
340342
binaryOp<ValueType>(n, "max", max, [this](std::size_t a, std::size_t b) { return cg_max(this->g_, a, b); });
341343
}
342344

345+
void visit(FunctionFractionNode& n) override {
346+
unaryOp<ValueType>(n, "frac", frac, [this](std::size_t a) { return cg_frac(this->g_, a); });
347+
}
348+
349+
void visit(FunctionRoundNode& n) override {
350+
binaryOp<ValueType>(n, "round", round, [this](std::size_t a, std::size_t b) { return cg_round(this->g_, a, b); });
351+
}
352+
343353
void visit(FunctionPowNode& n) override {
344354
binaryOp<ValueType>(n, "pow", pow, [this](std::size_t a, std::size_t b) { return cg_pow(this->g_, a, b); });
345355
}

OREData/ored/scripting/grammar.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ ScriptGrammar::ScriptGrammar(ScriptGrammarIterator first)
8585
| qi::lit("sqrt") | qi::lit("normalCdf") | qi::lit("normalPdf") | qi::lit("max") | qi::lit("min") | qi::lit("pow") | qi::lit("black")
8686
| qi::lit("dcf") | qi::lit("days") | qi::lit("PAY") | qi::lit("NPVMEM") | qi::lit("DISCOUNT") | qi::lit("SIZE") | qi::lit("SORT")
8787
| qi::lit("PERMUTE") | qi::lit("LOGPAY") | qi::lit("HISTFIXING") | qi::lit("FWDCOMP") | qi::lit("FWDAVG") | qi::lit("ABOVEPROB")
88-
| qi::lit("BELOWPROB") | qi::lit("NPV") | qi::lit("DATEINDEX")
88+
| qi::lit("BELOWPROB") | qi::lit("NPV") | qi::lit("DATEINDEX") | qi::lit("frac") | qi::lit("round")
8989
) >> !(qi::alnum | qi::char_('_')) ];
9090

9191
varname %= qi::lexeme[ (qi::alpha | qi::char_('_')) >> *(qi::alnum | qi::char_('_')) ] - keyword;
@@ -152,6 +152,8 @@ ScriptGrammar::ScriptGrammar(ScriptGrammarIterator first)
152152
| (qi::lit("normalPdf") > "(" > term > ')') [ createASTNode<FunctionNormalPdfNode>(evalStack, 1) ]
153153
| (qi::lit("max") > "(" > term > ',' > term > ')') [ createASTNode<FunctionMaxNode>(evalStack, 2) ]
154154
| (qi::lit("min") > "(" > term > ',' > term > ')') [ createASTNode<FunctionMinNode>(evalStack, 2) ]
155+
| (qi::lit("frac") > "(" > term > ')') [ createASTNode<FunctionFractionNode>(evalStack, 1) ]
156+
| (qi::lit("round") > "(" > term > ',' > term > ')') [ createASTNode<FunctionRoundNode>(evalStack, 2) ]
155157
| (qi::lit("pow") > "(" > term > ',' > term > ')') [ createASTNode<FunctionPowNode>(evalStack, 2) ]
156158
| (qi::lit("black") > "(" > term > ',' > term > ',' > term > ',' > term > ',' > term > ',' > term > ')')
157159
[ createASTNode<FunctionBlackNode>(evalStack, 6) ]

OREData/ored/scripting/randomastgenerator.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,20 @@ struct RandomASTGenerator {
570570
}
571571
current = QuantLib::ext::make_shared<VarEvaluationNode>(args);
572572
break;
573+
case 26:
574+
createTerm();
575+
args.push_back(current);
576+
createTerm();
577+
args.push_back(current);
578+
current = QuantLib::ext::make_shared<FunctionFractionNode>(args);
579+
break;
580+
case 27:
581+
createTerm();
582+
args.push_back(current);
583+
createTerm();
584+
args.push_back(current);
585+
current = QuantLib::ext::make_shared<FunctionRoundNode>(args);
586+
break;
573587
default:
574588
QL_FAIL("internal error");
575589
}

OREData/ored/scripting/scriptengine.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ class ASTRunner : public AcyclicVisitor,
6868
public Visitor<FunctionNormalPdfNode>,
6969
public Visitor<FunctionMinNode>,
7070
public Visitor<FunctionMaxNode>,
71+
public Visitor<FunctionFractionNode>,
72+
public Visitor<FunctionRoundNode>,
7173
public Visitor<FunctionPowNode>,
7274
public Visitor<FunctionBlackNode>,
7375
public Visitor<FunctionDcfNode>,
@@ -235,6 +237,8 @@ class ASTRunner : public AcyclicVisitor,
235237
void visit(FunctionNormalPdfNode& n) override { unaryOp<ValueType>(n, "normalPdf", normalPdf); }
236238
void visit(FunctionMinNode& n) override { binaryOp<ValueType>(n, "min", min); }
237239
void visit(FunctionMaxNode& n) override { binaryOp<ValueType>(n, "max", max); }
240+
void visit(FunctionFractionNode& n) override { unaryOp<ValueType>(n, "frac", frac); }
241+
void visit(FunctionRoundNode& n) override { binaryOp<ValueType>(n, "round", round); }
238242
void visit(FunctionPowNode& n) override { binaryOp<ValueType>(n, "pow", pow); }
239243

240244
// condition nodes

0 commit comments

Comments
 (0)