jquants.market.MoneyContext Maven / Gradle / Ivy
package jquants.market;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.googlecode.totallylazy.None;
import com.googlecode.totallylazy.Option;
import com.googlecode.totallylazy.Predicate;
import com.googlecode.totallylazy.Some;
import jquants.market.Money.Currency;
import static jquants.market.Money.Money;
import static com.googlecode.totallylazy.Sequences.sequence;
/**
* MoneyContext
*
* Provides a context for Money specific operations.
*
* When provided as an implicit parameter, the defaultCurrency will be used by the
* Money factory when no other currency is provided.
*
* Provides for cross-currency conversions.
*
* Will act as an implicit parameter to cross currency operations to allow for
* easy conversions
*
* @author Mathias Braeu
* @since 1.0
*
*/
public class MoneyContext {
Currency defaultCurrency;
Set currencies;
List rates;
boolean allowIndirectConversions = true;
private static ThreadLocal moneyContext = new ThreadLocal();
public static MoneyContext get() {
return moneyContext.get();
}
public static void set(Currency defaultCurrency, Set currencies, List rates, boolean allowIndirectConversions) {
MoneyContext ctx = new MoneyContext(defaultCurrency, currencies, rates, allowIndirectConversions);
moneyContext.set(ctx);
}
public static void set(Currency defaultCurrency, Set currencies, List rates) {
MoneyContext ctx = new MoneyContext(defaultCurrency, currencies, rates);
moneyContext.set(ctx);
}
public static void set(MoneyContext ctx) {
moneyContext.set(ctx);
}
public static void remove() {
moneyContext.remove();
}
public static MoneyContext current() {
return MoneyContext.get() != null ? MoneyContext.get() : Market.defaultMoneyContext;
}
/**
*
* @param defaultCurrency Currency used when none is supplied to the Money factory
* @param currencies Set of Currencies
* @param rates Collection of Exchange Rates used for currency conversions
*/
public MoneyContext(Currency defaultCurrency, Set currencies, List rates) {
this(defaultCurrency, currencies, rates, true);
}
/**
*
* @param defaultCurrency Currency used when none is supplied to the Money factory
* @param currencies Set of Currencies
* @param rates Collection of Exchange Rates used for currency conversions
* @param allowIndirectConversions boolean
*/
public MoneyContext(Currency defaultCurrency, Set currencies, List rates, boolean allowIndirectConversions) {
this.defaultCurrency = defaultCurrency;
this.currencies = currencies;
this.rates = rates;
this.allowIndirectConversions = allowIndirectConversions;
}
/**
* Returns an Option on an exchange rate if a direct rate exists, otherwise None
*
* @param curA Currency A
* @param curB Currency B
* @return
*/
public Option directRateFor(final Currency curA, final Currency curB ) {
Option find = sequence(rates).find(new Predicate() {
public boolean matches(CurrencyExchangeRate r) {
return (r.base.currency == curA && r.counter.currency == curB || r.base.currency == curB && r.counter.currency == curA);
}
});
return find;
}
/**
* Return an Option on an exchange rate. If a direct rate exists an Option on that will be returned.
* Otherwise, if a cross rate can be determined (1 hop limit), it will be created and returned in an Option.
* Otherwise, None will be returned
*
* @param curA Currency A
* @param curB Currency B
* @return
* @throws NoSuchExchangeRateException
*/
public Option indirectRateFor(final Currency curA, final Currency curB) throws NoSuchExchangeRateException {
// TODO Improve this to attempt to use defaultCurrency first
Option directRateFor = directRateFor(curA, curB);
if (!directRateFor.isEmpty()) {
return directRateFor;
} else {
List ratesWithCurA = new ArrayList();
List ratesWithCurB = new ArrayList();
for (CurrencyExchangeRate r : this.rates) {
if (r.base.currency == curA || r.counter.currency == curA) {
ratesWithCurA.add(r);
}
if (r.base.currency == curB || r.counter.currency == curB) {
ratesWithCurB.add(r);
}
}
List curs = new ArrayList();
for (Currency cur : this.currencies) {
if ((containsBaseCurrency(ratesWithCurA, cur) || containsCounterCurrency(ratesWithCurA, cur)) &&
(containsBaseCurrency(ratesWithCurB, cur) || containsCounterCurrency(ratesWithCurB, cur))) {
curs.add(cur);
}
}
if (curs.size() > 0) {
Money m = Money(1d, curs.get(0));
return Some.some(new CurrencyExchangeRate(convert(m, curA), convert(m, curB)));
} else {
return None.none();
}
}
}
private boolean containsBaseCurrency(List rates, Currency cur) {
for (CurrencyExchangeRate rate : rates) {
if (rate.base.currency.equals(cur)) return true;
}
return false;
}
private boolean containsCounterCurrency(List rates, Currency cur) {
for (CurrencyExchangeRate rate : rates) {
if (rate.counter.currency.equals(cur)) return true;
}
return false;
}
/**
* Converts a Money value to the specified currency.
*
* The conversion first attempts to use an existing exchange rate for the two currencies in question.
* If no direct exchange works, a cross rate (limited to 1 hop) will be calculated and used.
* If no cross rate can be calculated a NoSuchElementException is thrown
*
* @param money Money to be converted
* @param currency Currency to be converted to
* @return
* @throws NoSuchExchangeRateException when no exchange rate is available
*/
public Money convert(Money money, Currency currency) throws NoSuchExchangeRateException {
if (money.currency == currency) {
return money;
}else {
Option rate = directRateFor(money.currency, currency);
if (!rate.isEmpty()) {
return rate.get().convert(money);
} else {
if (allowIndirectConversions) {
Option crossRate = indirectRateFor(money.currency, currency);
if (!crossRate.isEmpty()) {
return crossRate.get().convert(money);
} else {
throw new NoSuchExchangeRateException(String.format("Rate for currency pair %s / %s)", money.currency, currency));
}
}
}
}
throw new NoSuchExchangeRateException(String.format("Rate for currency pair %s / %s)", money.currency, currency));
}
/**
* Adds two money values that may or may not be in the same currency.
*
* The result will be in the same currency as the first parameter.
*
* @param moneyA Money A
* @param moneyB Money B
* @return
* @throws NoSuchExchangeRateException when no exchange rate is available
*/
public Money add(Money moneyA, Money moneyB) throws NoSuchExchangeRateException {
return Money(moneyA.amount.add(convert(moneyB, moneyA.currency).amount), moneyA.currency);
}
/**
* Subtracts two money values that may or may not be in the same currency
*
* The result will be in the same currency as the first parameter.
*
* @param moneyA Money A
* @param moneyB Money B
* @return
* @throws NoSuchExchangeRateException when no exchange rate is available
*/
public Money subtract(Money moneyA, Money moneyB) throws NoSuchExchangeRateException {
return Money(moneyA.amount.subtract(convert(moneyB, moneyA.currency).amount), moneyA.currency);
}
/**
* Divides two money value that may or may not be in the same currency after converting the second to the first
*
* @param moneyA Money A
* @param moneyB Money B
* @return
* @throws NoSuchExchangeRateException
*/
public BigDecimal divide(Money moneyA, Money moneyB) throws NoSuchExchangeRateException {
BigDecimal amount = convert(moneyB, moneyA.currency).amount;
// BigDecimal representation will fail in case of a repetend as not representatble since not terminating: e.g. 1/3 = 0.3333333... as no
// TODO: Discuss the needed exactness (amount of scale);
// lower scale will impair the exactness; until then set to 40
return moneyA.amount.divide(amount, 40, RoundingMode.HALF_UP);
}
/**
* Performs a standard compare on two money values that may or may not be in the same currency
* @param moneyA Money A
* @param moneyB Money B
* @return
* @throws NoSuchExchangeRateException when no exchange rate is available
*/
public int compare(Money moneyA, Money moneyB) throws NoSuchExchangeRateException {
if (moneyA.amount.doubleValue() > convert(moneyB, moneyA.currency).amount.doubleValue()) return 1;
else if (moneyA.amount.doubleValue() < convert(moneyB, moneyA.currency).amount.doubleValue()) return -1;
else return 0;
}
/**
* Create a copy of this context with a new list of rates
* @param rates List[CurrencyExchangeRateshould]
* @return
*/
public List withExchangeRates(List rates) {
// TODO: return copy(rates = rates);
return rates;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy