Behaviour of cross asset simulation configuration (FX)
After trying a base currency change, I've discovered some irritating behaviour in the simulation configuration validity checking:
I tried to change the base currency to USD by simply changing the BaseCurrency tag in both CrossAssetModel and Market to USD:
However, unless the FIRST currency is not set to the base currency, ORE fails checking this config by passing back an error occurred: currency mismatch between IR and FX config vectors as an error. So this is actually working:
I'm tempted to add a detailed explanation in the user guide, but I'm not sure whether this behaviour shouldn't be rather treated as a bug (as the definition of the currency sequence should be free as long as it is consistent between CrossAssetModel and Market)...
Absolutely, a bug or at least an unnecessary restriction. We should fix this.
Thanks for checking, after looking into the source, I found that actually the currencies defined in the Market section are irrelevant (at least in their order) for the Cross asset model. The problem is only within the CrossAssetModel node.
Checking the validation code, considering the build methods, it is assumed that irConfigs_[0] contains the domestic currency, meaning it has to come first:
for (Size i = 0; i < fxConfigs_.size(); ++i)
QL_REQUIRE(fxConfigs_[i]->foreignCcy() == irConfigs_[i + 1]->ccy(),
"currency mismatch between IR and FX config vectors");
....
as the buildIrConfigs method builds all currencies nodes
void CrossAssetModelData::buildIrConfigs(std::map<std::string, boost::shared_ptr<IrModelData>>& irDataMap) {
irConfigs_.resize(currencies_.size());
for (Size i = 0; i < currencies_.size(); i++) {
string ccy = currencies_[i];
std::string ccyKey;
for (auto const& d : irDataMap) {
if (d.second->ccy() == ccy) {
QL_REQUIRE(ccyKey.empty(), "CrossAssetModelData: duplicate ir config for ccy " << ccy);
ccyKey = d.first;
}
}
if (!ccyKey.empty())
irConfigs_[i] = irDataMap.at(ccyKey);
else { // copy from default
LOG("IR configuration missing for currency " << ccy << ", using default");
....
and the buildFxConfigs method skips the node where the currency is the domestic currency:
void CrossAssetModelData::buildFxConfigs(std::map<std::string, boost::shared_ptr<FxBsData>>& fxDataMap) {
for (Size i = 0; i < currencies_.size(); i++) {
string ccy = currencies_[i];
if (ccy == domesticCurrency_)
continue;
if (fxDataMap.find(ccy) != fxDataMap.end())
fxConfigs_.push_back(fxDataMap[ccy]);
else { // copy from default
LOG("FX configuration missing for foreign currency " << ccy << ", using default");
if (fxDataMap.find("default") == fxDataMap.end()) {
Considering the validation code above, this can only work if the first currency is the domestic currency, if it is not, then the fxConfigs_ vector will always miss the domestic currency at the wrong place.
I don't have a clever idea that generalizes this without probably breaking a lot of other dependent code (e.g. a possibility might be to move the skipping of the domestic currency in buildFxConfigs to a later place and compare fxConfigs_[i]->foreignCcy() == irConfigs_[i]->ccy()).
I think a better and simpler solution (in addition to documenting this in the user guide) might be to simply assert that the first currency has to be the domestic currency and failing if it is not, e.g.:
QL_REQUIRE(fxConfigs_.size() == irConfigs_.size() - 1, "inconsistent number of FX data provided");
QL_REQUIRE( irConfigs_[0]->ccy() == domesticCurrency_,"first currency defined has to be the domestic currency");
for (Size i = 0; i < fxConfigs_.size(); ++i)
QL_REQUIRE(fxConfigs_[i]->foreignCcy() == irConfigs_[i + 1]->ccy(),
"currency mismatch between IR and FX config vectors");
...