/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.forex.method;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.ObjectUtils;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.money.Currency;
import com.opengamma.util.money.CurrencyAmount;
import com.opengamma.util.money.MultipleCurrencyAmount;
/**
* Class describing a set of currencies and all the cross rates between them.
*/
public class FXMatrix implements Serializable {
private static final long serialVersionUID = 1L;
/**
* The map between the currencies and their order.
*/
private final Map<Currency, Integer> _currencies;
/**
* The matrix with all exchange rates. The entry [i][j] is such that 1.0 * Currency[i] = _fxrate * Currency[j].
* If _currencies.get(EUR) = 0 and _currencies.get(USD) = 1, the element _fxRate[0][1] is likely to be something like 1.40 and _fxRate[1][0] like 0.7142...
* (the rate _fxRate[1][0] will be computed from _fxRate[0][1] when the object is constructed or updated).
* All the element of the matrix are meaningful and coherent (the matrix is always completed in a coherent way when a currency is added or a rate updated).
*/
private double[][] _fxRates;
/**
* The number of currencies.
*/
private int _nbCurrencies;
/**
* Constructor with no currency. The FXMatrix constructed has no currency and no fx rates.
*/
public FXMatrix() {
_currencies = new LinkedHashMap<>();
_fxRates = new double[0][0];
_nbCurrencies = 0;
}
/**
* Constructor with one currency. The FXMatrix has one currency with a 1.0 exchange rate to itself.
* @param ccy The currency.
*/
public FXMatrix(final Currency ccy) {
ArgumentChecker.notNull(ccy, "Currency");
_currencies = new LinkedHashMap<>();
_currencies.put(ccy, 0);
_fxRates = new double[1][1];
_fxRates[0][0] = 1.0;
_nbCurrencies = 1;
}
/**
* Constructor with an initial currency pair.
* @param ccy1 The first currency.
* @param ccy2 The second currency.
* @param fxRate The FX rate between ccy1 and the ccy2. It is 1 ccy1 = fxRate * ccy2. The FX matrix will be completed with the ccy2/ccy1 rate.
*/
public FXMatrix(final Currency ccy1, final Currency ccy2, final double fxRate) {
_currencies = new LinkedHashMap<>();
_fxRates = new double[0][0];
addCurrency(ccy1, ccy2, fxRate);
}
/**
* Constructor from a map of currency to order and an array of FX rates. The input data is copied.
* @param currencies The currencies, not null
* @param fxRates The rates, not null
*/
public FXMatrix(final Map<Currency, Integer> currencies, final double[][] fxRates) {
ArgumentChecker.notNull(currencies, "currencies");
ArgumentChecker.notNull(fxRates, "FX rates");
_nbCurrencies = currencies.size();
_currencies = new LinkedHashMap<>(currencies);
_fxRates = new double[_nbCurrencies][];
for (int loopc = 0; loopc < _nbCurrencies; loopc++) {
final double[] src = fxRates[loopc];
final int length = src.length;
_fxRates[loopc] = new double[length];
System.arraycopy(src, 0, _fxRates[loopc], 0, length);
}
}
/**
* Constructor from an existing FXMatrix. A new map and array are created.
* @param fxMatrix The FXMatrix.
*/
public FXMatrix(final FXMatrix fxMatrix) {
ArgumentChecker.notNull(fxMatrix, "FXMatrix");
_nbCurrencies = fxMatrix._nbCurrencies;
_currencies = new LinkedHashMap<>(fxMatrix._currencies);
_fxRates = new double[_nbCurrencies][];
for (int loopc = 0; loopc < _nbCurrencies; loopc++) {
final double[] src = fxMatrix._fxRates[loopc];
final int length = src.length;
_fxRates[loopc] = new double[length];
System.arraycopy(src, 0, _fxRates[loopc], 0, length);
}
}
/**
* Add a new currency to the FX matrix.
* @param ccyToAdd The currency to add. Should not be in the FX matrix already.
* @param ccyReference The reference currency used to compute the cross rates with the new currency. Should already be in the matrix, except if the matrix is empty.
* IF the FX matrix is empty, the reference currency will be used as currency 0.
* @param fxRate TheFX rate between the new currency and the reference currency. It is 1 ccyToAdd = fxrate ccyReference. The FX matrix will be completed using cross rate
* coherent with the data provided.
*/
public void addCurrency(final Currency ccyToAdd, final Currency ccyReference, final double fxRate) {
ArgumentChecker.notNull(ccyToAdd, "Currency to add to the FX matrix should not be null");
ArgumentChecker.notNull(ccyReference, "Reference currency should not be null");
ArgumentChecker.isTrue(!ccyToAdd.equals(ccyReference), "Currencies should be different");
if (_nbCurrencies == 0) { // FX Matrix is empty.
_currencies.put(ccyReference, 0);
_currencies.put(ccyToAdd, 1);
_fxRates = new double[2][2];
_fxRates[0][0] = 1.0;
_fxRates[1][1] = 1.0;
_fxRates[1][0] = fxRate;
_fxRates[0][1] = 1.0 / fxRate;
_nbCurrencies = 2;
} else {
ArgumentChecker.isTrue(_currencies.containsKey(ccyReference), "Reference currency {} not in the FX matrix", ccyReference);
ArgumentChecker.isTrue(!_currencies.containsKey(ccyToAdd), "New currency {} already in the FX matrix", ccyToAdd);
_currencies.put(ccyToAdd, _nbCurrencies);
_nbCurrencies++;
final double[][] fxRatesNew = new double[_nbCurrencies][_nbCurrencies];
// Copy the previous matrix
for (int loopccy = 0; loopccy < _nbCurrencies - 1; loopccy++) {
System.arraycopy(_fxRates[loopccy], 0, fxRatesNew[loopccy], 0, _nbCurrencies - 1);
}
fxRatesNew[_nbCurrencies - 1][_nbCurrencies - 1] = 1.0;
final int indexRef = _currencies.get(ccyReference);
for (int loopccy = 0; loopccy < _nbCurrencies - 1; loopccy++) {
fxRatesNew[_nbCurrencies - 1][loopccy] = fxRate * _fxRates[indexRef][loopccy];
fxRatesNew[loopccy][_nbCurrencies - 1] = 1.0 / fxRatesNew[_nbCurrencies - 1][loopccy];
}
_fxRates = fxRatesNew;
}
}
/**
* Return the exchange rate between two currencies.
* @param ccy1 The first currency.
* @param ccy2 The second currency.
* @return The exchange rate: 1.0 * ccy1 = x * ccy2.
*/
public double getFxRate(final Currency ccy1, final Currency ccy2) {
if (ccy1.equals(ccy2)) {
return 1;
}
final Integer index1 = _currencies.get(ccy1);
final Integer index2 = _currencies.get(ccy2);
ArgumentChecker.isTrue(index1 != null, "{} not found in FX matrix", ccy1);
ArgumentChecker.isTrue(index2 != null, "{} not found in FX matrix", ccy2);
return _fxRates[index1][index2];
}
/**
* @param ccy1 The first currency
* @param ccy2 The second currency
* @return True if the matrix contains both currencies
*/
public boolean containsPair(final Currency ccy1, final Currency ccy2) {
return _currencies.containsKey(ccy1) && _currencies.containsKey(ccy2);
}
/**
* Convert a multiple currency amount into a amount in a given currency.
* @param amount The multiple currency amount, not null
* @param ccy The currency for the conversion.
* @return The amount.
*/
public CurrencyAmount convert(final MultipleCurrencyAmount amount, final Currency ccy) {
ArgumentChecker.notNull(amount, "amount");
double conversion = 0;
final CurrencyAmount[] ca = amount.getCurrencyAmounts();
for (final CurrencyAmount element : ca) {
conversion += element.getAmount() * getFxRate(element.getCurrency(), ccy);
}
return CurrencyAmount.of(ccy, conversion);
}
/**
* Reset the exchange rate of a given currency.
* @param ccyToUpdate The currency for which the exchange rates should be updated. Should be in the FX matrix already.
* @param ccyReference The reference currency used to compute the cross rates with the new currency. Should already be in the matrix.
* @param fxRate TheFX rate between the new currency and the reference currency. It is 1.0 * ccyToAdd = fxrate * ccyReference. The FX matrix will be changed for currency1
* using cross rate coherent with the data provided.
*/
public void updateRates(final Currency ccyToUpdate, final Currency ccyReference, final double fxRate) {
ArgumentChecker.isTrue(_currencies.containsKey(ccyReference), "Reference currency not in the FX matrix");
ArgumentChecker.isTrue(_currencies.containsKey(ccyToUpdate), "Currency to update not in the FX matrix");
final int indexUpdate = _currencies.get(ccyToUpdate);
final int indexRef = _currencies.get(ccyReference);
for (int loopccy = 0; loopccy < _nbCurrencies; loopccy++) {
_fxRates[indexUpdate][loopccy] = fxRate * _fxRates[indexRef][loopccy];
_fxRates[loopccy][indexUpdate] = 1.0 / _fxRates[indexUpdate][loopccy];
}
_fxRates[indexUpdate][indexUpdate] = 1.0;
}
/**
* Returns an unmodifiable copy of the map containing currency and order information.
* @return The currency and order information
*/
public Map<Currency, Integer> getCurrencies() {
return Collections.unmodifiableMap(_currencies);
}
/**
* Returns the array of FX rates.
* @return The FX rates
*/
public double[][] getRates() {
return _fxRates;
}
/**
* Returns the number of currencies in the matrix.
* @return The number of currencies.
*/
public int getNumberOfCurrencies() {
return _nbCurrencies;
}
public FXMatrix copy() {
return new FXMatrix(this);
}
@Override
public String toString() {
return _currencies.keySet().toString() + " - " + ArrayUtils.toString(_fxRates);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + _currencies.hashCode();
result = prime * result + Arrays.deepHashCode(_fxRates);
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
// Special case for EmptyFXMatrix.
if (!(obj instanceof FXMatrix)) {
return false;
}
final FXMatrix other = (FXMatrix) obj;
if (!ObjectUtils.equals(_currencies, other._currencies)) {
return false;
}
if (!Arrays.deepEquals(_fxRates, other._fxRates)) {
return false;
}
return true;
}
}