Skip to content

Commit aeb845e

Browse files
nathaniel.volfangojenkins
authored andcommitted
QPR-12477 -- Allow for double digital option underlying as a spread between two indices
1 parent 8e8a46d commit aeb845e

4 files changed

Lines changed: 68 additions & 11 deletions

File tree

Docs/UserGuide/tradedata/doubledigitaloption.tex

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ \subsubsection{Double Digital Option}
1212
<BinaryPayout>12000000</BinaryPayout>
1313
<BinaryLevel1>1.1</BinaryLevel1>
1414
<BinaryLevel2>0.006</BinaryLevel2>
15-
<BinaryLevelCollarUpperBound2>0.008</BinaryLevelCollarUpperBound2>
15+
<BinaryLevelUpper2>0.008</BinaryLevelUpper2>
1616
<Type1>Call</Type1>
1717
<Type2>Collar</Type2>
1818
<Position>Long</Position>
@@ -44,17 +44,19 @@ \subsubsection{Double Digital Option}
4444
CCY2 per units of CCY1. For an Equity underlying this is the equity price expressed in the equity ccy. For a Commodity underlying this is the commodity price expressed in the commodity ccy. For an IR underlying this is the rate expressed in decimal form. Allowable values
4545
are non-negative numbers.
4646
\item Type1: The option type that applies to underlying 1. Allowable values: {\em Call}, {\em Put} or {\em Collar}. Underlying 1 is
47-
considered to be in the money if the spot is above (Call) / below (Put) the BinaryLevel1 resp. between (Collar) the BinaryLevel1 and BinaryLevelCollarUpperBound1 at the expiry.
47+
considered to be in the money if the spot is above (Call) / below (Put) the BinaryLevel1 resp. between (Collar) the BinaryLevel1 and BinaryLevelUpper1 at the expiry.
4848
\item Type2: The option type that applies to underlying 2. Allowable values: {\em Call}, {\em Put} or {\em Collar}. Underlying 2 is
49-
considered to be in the money if the spot is above (Call) / below (Put) the BinaryLevel1 resp. between (Collar) the BinaryLevel2 andthe BinaryLevelCollarUpperBound2 at the expiry.
49+
considered to be in the money if the spot is above (Call) / below (Put) the BinaryLevel1 resp. between (Collar) the BinaryLevel2 andthe BinaryLevelUpper2 at the expiry.
5050
\item Position: The option position type. Allowable values: {\em Long} or {\em Short}.
5151
\item Underlying1: The first underlying, see \ref{ss:underlying}.
5252
\item Underlying2: The second underlying, see \ref{ss:underlying}. Note that Type for both underlyings has allowable values \emph{Equity}, \emph{Commodity}, \emph{FX}, and \emph{IR}.
53+
\item Underlying3 [Optional]: If defined, the first underlying in this transaction is treated as a spread between Underlying1 and Underlying3 (i.e.\ Underlying1 fixing minus Underlying3 fixing), see \ref{ss:underlying}. Underlying3 Type must be the same as Underlying1 Type.
54+
\item Underlying4 [Optional]: If defined, the second underlying in this transaction is treated as a spread between Underlying2 and Underlying4 (i.e.\ Underlying2 fixing minus Underlying4 fixing), see \ref{ss:underlying}. Underlying4 Type must be the same as Underlying2 Type.
5355
\item PayCcy: The currency in which the \verb+BinaryPayout+ is paid. See Table \ref{tab:currency} for allowable currency codes.
54-
\item BinaryLevelCollarUpperBound1: Optional field used only for Collar option. The upper bound for underlying 1. For an FX underlying (SOURCE-CCY1-CCY2) this is the number of units of
56+
\item BinaryLevelUpper1 [Optional]: This is field is used only for Collar option. The upper bound for underlying 1. For an FX underlying (SOURCE-CCY1-CCY2) this is the number of units of
5557
CCY2 per units of CCY1. For an Equity underlying this is the equity price expressed in the equity ccy. For a Commodity underlying this is the commodity price expressed in the commodity ccy. For an IR underlying this is the rate expressed in decimal form. Allowable values
5658
are non-negative numbers.
57-
\item BinaryLevelCollarUpperBound2: Optional field used only for Collar option. The upper bound for underlying 2. For an FX underlying (SOURCE-CCY1-CCY2) this is the number of units of
59+
\item BinaryLevelUpper2 [Optional]: This field is used only for Collar option. The upper bound for underlying 2. For an FX underlying (SOURCE-CCY1-CCY2) this is the number of units of
5860
CCY2 per units of CCY1. For an Equity underlying this is the equity price expressed in the equity ccy. For a Commodity underlying this is the commodity price expressed in the commodity ccy. For an IR underlying this is the rate expressed in decimal form. Allowable values
5961
are non-negative numbers.
6062

OREData/ored/portfolio/doubledigitaloption.cpp

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,24 @@ void DoubleDigitalOption::build(const QuantLib::ext::shared_ptr<EngineFactory>&
7070
QL_REQUIRE(underlying2_->type() == "Equity" || underlying2_->type() == "Commodity" ||
7171
underlying2_->type() == "FX" || underlying2_->type() == "InterestRate",
7272
"underlying type " << underlying2_->type() << " not supported");
73+
if (underlying3_) {
74+
QL_REQUIRE(underlying3_->type() == "Equity" || underlying3_->type() == "Commodity" ||
75+
underlying3_->type() == "FX" || underlying3_->type() == "InterestRate",
76+
"underlying type " << underlying3_->type() << " not supported");
77+
78+
QL_REQUIRE(underlying1_->type() == underlying3_->type(),
79+
"Underlying1 and Underlying3 must belong to the same asset class. Got "
80+
<< underlying1_->type() << " and " << underlying3_->type());
81+
}
82+
if (underlying4_) {
83+
QL_REQUIRE(underlying4_->type() == "Equity" || underlying4_->type() == "Commodity" ||
84+
underlying4_->type() == "FX" || underlying4_->type() == "InterestRate",
85+
"underlying type " << underlying4_->type() << " not supported");
86+
87+
QL_REQUIRE(underlying2_->type() == underlying4_->type(),
88+
"Underlying2 and Underlying4 must belong to the same asset class. Got "
89+
<< underlying2_->type() << " and " << underlying4_->type());
90+
}
7391

7492
// set product tag accordingly
7593
if (underlying1_->type() == "InterestRate" && underlying2_->type() == "InterestRate")
@@ -83,10 +101,15 @@ void DoubleDigitalOption::build(const QuantLib::ext::shared_ptr<EngineFactory>&
83101

84102
// set script
85103

104+
string underlying1Str, underlying2Str;
105+
underlying1Str = underlying3_ ? "(Underlying1(Expiry) - Underlying3(Expiry))" : "Underlying1(Expiry)";
106+
underlying2Str = underlying4_ ? "(Underlying2(Expiry) - Underlying4(Expiry))" : "Underlying2(Expiry)";
107+
108+
// clang-format off
86109
script_ = {
87110
{"", ScriptedTradeScriptData("NUMBER ExerciseProbability;\n"
88-
"IF Underlying1(Expiry) >= LowerBound1 AND Underlying1(Expiry) <= UpperBound1 AND\n"
89-
" Underlying2(Expiry) >= LowerBound2 AND Underlying2(Expiry) <= UpperBound2 THEN\n"
111+
"IF " + underlying1Str + " >= LowerBound1 AND " + underlying1Str + " <= UpperBound1 AND\n"
112+
" " + underlying2Str + " >= LowerBound2 AND " + underlying2Str + " <= UpperBound2 THEN\n"
90113
" Option = LongShort * LOGPAY( BinaryPayout, Expiry, Settlement, PayCcy);\n"
91114
" ExerciseProbability = 1;\n"
92115
"END;\n",
@@ -95,6 +118,7 @@ void DoubleDigitalOption::build(const QuantLib::ext::shared_ptr<EngineFactory>&
95118
{"currentNotional", "BinaryPayout"},
96119
{"notionalCurrency", "PayCcy"}},
97120
{})}};
121+
// clang-format on
98122

99123
// build trade
100124

@@ -104,6 +128,10 @@ void DoubleDigitalOption::build(const QuantLib::ext::shared_ptr<EngineFactory>&
104128
void DoubleDigitalOption::initIndices() {
105129
indices_.emplace_back("Index", "Underlying1", scriptedIndexName(underlying1_));
106130
indices_.emplace_back("Index", "Underlying2", scriptedIndexName(underlying2_));
131+
if (underlying3_)
132+
indices_.emplace_back("Index", "Underlying3", scriptedIndexName(underlying3_));
133+
if (underlying4_)
134+
indices_.emplace_back("Index", "Underlying4", scriptedIndexName(underlying4_));
107135
}
108136

109137
void DoubleDigitalOption::fromXML(XMLNode* node) {
@@ -148,6 +176,24 @@ void DoubleDigitalOption::fromXML(XMLNode* node) {
148176
underlyingBuilder2.fromXML(tmp);
149177
underlying2_ = underlyingBuilder2.underlying();
150178

179+
tmp = XMLUtils::getChildNode(tradeDataNode, "Underlying3");
180+
if (!tmp)
181+
tmp = XMLUtils::getChildNode(tradeDataNode, "Name3");
182+
if (tmp) {
183+
UnderlyingBuilder underlyingBuilder3("Underlying3", "Name3");
184+
underlyingBuilder3.fromXML(tmp);
185+
underlying3_ = underlyingBuilder3.underlying();
186+
}
187+
188+
tmp = XMLUtils::getChildNode(tradeDataNode, "Underlying4");
189+
if (!tmp)
190+
tmp = XMLUtils::getChildNode(tradeDataNode, "Name4");
191+
if (tmp) {
192+
UnderlyingBuilder underlyingBuilder4("Underlying4", "Name4");
193+
underlyingBuilder4.fromXML(tmp);
194+
underlying4_ = underlyingBuilder4.underlying();
195+
}
196+
151197
payCcy_ = XMLUtils::getChildValue(tradeDataNode, "PayCcy", true);
152198

153199
initIndices();
@@ -173,6 +219,10 @@ XMLNode* DoubleDigitalOption::toXML(XMLDocument& doc) const {
173219
XMLUtils::addChild(doc, tradeNode, "Position", position_);
174220
XMLUtils::appendNode(tradeNode, underlying1_->toXML(doc));
175221
XMLUtils::appendNode(tradeNode, underlying2_->toXML(doc));
222+
if (underlying3_)
223+
XMLUtils::appendNode(tradeNode, underlying3_->toXML(doc));
224+
if (underlying4_)
225+
XMLUtils::appendNode(tradeNode, underlying4_->toXML(doc));
176226
XMLUtils::addChild(doc, tradeNode, "PayCcy", payCcy_);
177227
return node;
178228
}

OREData/ored/portfolio/doubledigitaloption.hpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,19 @@ class DoubleDigitalOption : public ScriptedTrade {
3737
DoubleDigitalOption() : ScriptedTrade("DoubleDigitalOption") {}
3838
DoubleDigitalOption(const Envelope& env, const string& expiry, const string& settlement, const string& binaryPayout,
3939
const string& binaryLevel1, const string& binaryLevel2, const string& type1,
40-
const string& type2, const string& position, const QuantLib::ext::shared_ptr<Underlying>& underlying1,
41-
const QuantLib::ext::shared_ptr<Underlying>& underlying2, const string& payCcy,
40+
const string& type2, const string& position,
41+
const QuantLib::ext::shared_ptr<Underlying>& underlying1,
42+
const QuantLib::ext::shared_ptr<Underlying>& underlying2,
43+
const QuantLib::ext::shared_ptr<Underlying>& underlying3,
44+
const QuantLib::ext::shared_ptr<Underlying>& underlying4, const string& payCcy,
4245
const QuantLib::ext::shared_ptr<Conventions>& conventions = nullptr,
4346
const std::string& binaryLevelUpper1 = std::string(),
4447
const std::string& binaryLevelUpper2 = std::string())
4548
: ScriptedTrade("DoubleDigitalOption", env), expiry_(expiry), settlement_(settlement),
4649
binaryPayout_(binaryPayout), binaryLevel1_(binaryLevel1), binaryLevel2_(binaryLevel2), type1_(type1),
4750
type2_(type2), position_(position), payCcy_(payCcy), binaryLevelUpper1_(binaryLevelUpper1),
4851
binaryLevelUpper2_(binaryLevelUpper2), underlying1_(underlying1),
49-
underlying2_(underlying2) {
52+
underlying2_(underlying2), underlying3_(underlying3), underlying4_(underlying4) {
5053
initIndices();
5154
}
5255
void build(const QuantLib::ext::shared_ptr<EngineFactory>&) override;
@@ -57,7 +60,7 @@ class DoubleDigitalOption : public ScriptedTrade {
5760
void initIndices();
5861
string expiry_, settlement_, binaryPayout_, binaryLevel1_, binaryLevel2_, type1_, type2_, position_, payCcy_,
5962
binaryLevelUpper1_, binaryLevelUpper2_;
60-
QuantLib::ext::shared_ptr<Underlying> underlying1_, underlying2_;
63+
QuantLib::ext::shared_ptr<Underlying> underlying1_, underlying2_, underlying3_, underlying4_;
6164
};
6265

6366
} // namespace data

xsd/instruments.xsd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3865,6 +3865,8 @@
38653865
<xs:element type="longShort" name="Position"/>
38663866
<xs:element type="underlying" name="Underlying1" minOccurs="0"/>
38673867
<xs:element type="underlying" name="Underlying2" minOccurs="0"/>
3868+
<xs:element type="underlying" name="Underlying3" minOccurs="0"/>
3869+
<xs:element type="underlying" name="Underlying4" minOccurs="0"/>
38683870
<xs:element type="xs:string" name="Name1" minOccurs="0"/>
38693871
<xs:element type="xs:string" name="Name2" minOccurs="0"/>
38703872
<xs:element type="currencyCode" name="PayCcy"/>

0 commit comments

Comments
 (0)