package net.objectlab.kit.datecalc.common; import java.io.Serializable; import net.objectlab.kit.datecalc.common.ccy.CurrencyCalculatorConfig; /** * Provides enough information to create an immutable CurrencyDateCalculator. * * <pre> CurrencyDateCalculatorBuilder<LocalDate> builder = new CurrencyDateCalculatorBuilder<LocalDate>() // .currencyPair("EUR", "GBP", SpotLag.T_2) // .ccy1Calendar(new DefaultHolidayCalendar<LocalDate>()) // empty .ccy1Week(WorkingWeek.DEFAULT) // Mon-Fri .ccy2Calendar(gbpCalendar) // .ccy2Week(WorkingWeek.DEFAULT) // Mon-Fri .crossCcy("USD") // the usual suspect .crossCcyCalendar(usdCalendar) // .crossCcyWeek(WorkingWeek.DEFAULT) // Mon-Fri; .adjustStartDateWithCurrencyPair(true) // default is true, Move the startDate to a working date for ccy1 and ccy2 .tenorHolidayHandler(new LocalDateForwardHandler()) // Forward (or equivalent for your implementation) .brokenDateAllowed(false) // use the CrossCcy holidays on Spot and Tenor Date .currencyCalculatorConfig(new DefaultCurrencyCalculatorConfig()) // Will be used for finding Working Weeks IF NOT PROVIDED and Latin // American ccy USD handling. * </pre> * * @param <E> JDK Date/Calendar, JDK8 LocalDate or Joda LocalDate * @since 1.4.0 */ public class CurrencyDateCalculatorBuilder<E extends Serializable> { private String ccy1; private String ccy2; private String crossCcy = CurrencyDateCalculator.USD_CODE; private HolidayCalendar<E> ccy1Calendar = new DefaultHolidayCalendar<E>(); private HolidayCalendar<E> ccy2Calendar = new DefaultHolidayCalendar<E>(); private HolidayCalendar<E> crossCcyCalendar = new DefaultHolidayCalendar<E>(); private HolidayHandler<E> tenorHolidayHandler; private WorkingWeek ccy1Week; private WorkingWeek ccy2Week; private WorkingWeek crossCcyWeek; private CurrencyCalculatorConfig currencyCalculatorConfig; private boolean brokenDateAllowed; private boolean adjustStartDateWithCurrencyPair = true; private SpotLag spotLag = SpotLag.T_2; /** * Default values are: * <ul> * <li>crossCcy = USD</li> * <li>ccy1Calendar = Empty Calendar</li> * <li>ccy2Calendar = Empty Calendar</li> * <li>crossCcyCalendar = Empty Calendar</li> * <li>brokenDateAllowed = false</li> * <li>adjustStartDateWithCurrencyPair = true</li> * <li>spotLag = SpotLag.T_2</li> * </ul> */ public CurrencyDateCalculatorBuilder() { } /** * Checks the builder and throws an IllegalArgumentException if there are issues e.g. * <ul> * <li>ccy1 or ccy2 missing</li> * <li>ccy1Week or ccy2Week missing</li> * <li>spotLag is missing</li> * <li>tenorHolidayHandler is missing</li> * <li>If brokenDate is not allowed, we need crossCcy, crossCcyWeek and crossCcyCalendar.</li> * </ul> */ public void checkValidity() { StringBuilder b = new StringBuilder(); if (ccy1 == null || ccy1.length() == 0) { b.append("ccy1 is required"); } if (ccy2 == null || ccy2.length() == 0) { append(b, "ccy2 is required"); } if (getCcy1Week() == null) { append(b, "ccy1Week is required"); } if (getCcy2Week() == null) { append(b, "ccy2Week is required"); } if (!brokenDateAllowed) { if (getCrossCcy() == null) { append(b, "crossCcy is required"); } if (getCrossCcyWeek() == null) { append(b, "crossCcyWeek is required"); } if (getCrossCcyCalendar() == null) { append(b, "crossCcyCalendar is required"); } } if (spotLag == null) { append(b, "spotLag is required"); } if (tenorHolidayHandler == null) { append(b, "tenorHolidayHandler is required"); } if (b.length() > 0) { throw new IllegalArgumentException(b.toString()); } } private static void append(StringBuilder b, String string) { if (b.length() > 0) { b.append(","); } b.append(string); } public String getCcy1() { return ccy1; } public String getCcy2() { return ccy2; } public String getCrossCcy() { return crossCcy; } public HolidayCalendar<E> getCcy1Calendar() { return ccy1Calendar; } public HolidayCalendar<E> getCcy2Calendar() { return ccy2Calendar; } public HolidayCalendar<E> getCrossCcyCalendar() { return crossCcyCalendar; } public HolidayHandler<E> getTenorHolidayHandler() { return tenorHolidayHandler; } public WorkingWeek getCcy1Week() { return ccy1Week != null ? ccy1Week : currencyCalculatorConfig != null ? currencyCalculatorConfig.getWorkingWeek(ccy1) : null; } public WorkingWeek getCcy2Week() { return ccy2Week != null ? ccy2Week : currencyCalculatorConfig != null ? currencyCalculatorConfig.getWorkingWeek(ccy2) : null; } public WorkingWeek getCrossCcyWeek() { return crossCcyWeek != null ? crossCcyWeek : currencyCalculatorConfig != null ? currencyCalculatorConfig.getWorkingWeek(crossCcy) : null; } public CurrencyCalculatorConfig getCurrencyCalculatorConfig() { return currencyCalculatorConfig; } public boolean isBrokenDateAllowed() { return brokenDateAllowed; } /** * This specialises the calculator to the given currency pair and the SpotLag (0, 1, 2). Given than some currencies can have different * SpotLag depending on the business, this is something that the user will have to provide. * @param ccy1 * @param ccy2 * @param spotLag * @return the builder */ public CurrencyDateCalculatorBuilder<E> currencyPair(final String ccy1, final String ccy2, final SpotLag spotLag) { this.ccy1 = ccy1; this.ccy2 = ccy2; this.spotLag = spotLag; return this; } public SpotLag getSpotLag() { return spotLag; } public boolean isAdjustStartDateWithCurrencyPair() { return adjustStartDateWithCurrencyPair; } /** * If true, the startDate given to the calculator will be move to the NEXT working day for both currencies (e.g. it will skip weekends * and any holidays). * @param adjustStartDateWithCurrencyPair default true * @return the builder */ public CurrencyDateCalculatorBuilder<E> adjustStartDateWithCurrencyPair(final boolean adjustStartDateWithCurrencyPair) { this.adjustStartDateWithCurrencyPair = adjustStartDateWithCurrencyPair; return this; } /** * If true, then the calculator can return a SpotDate/TenorDate where the cross currency is NOT a trading date (e.g. July 4 for EUR/GBP which * usually would be skipped). * @param brokenDateAllowed default false * @return the builder */ public CurrencyDateCalculatorBuilder<E> brokenDateAllowed(final boolean brokenDateAllowed) { this.brokenDateAllowed = brokenDateAllowed; return this; } /** * Provides information about currencies subject to USD on T+1 and WorkingWeeks if not specified individually. * @param currencyCalculatorConfig the config * @return */ public CurrencyDateCalculatorBuilder<E> currencyCalculatorConfig(final CurrencyCalculatorConfig currencyCalculatorConfig) { this.currencyCalculatorConfig = currencyCalculatorConfig; return this; } /** * The holiday calendar for ccy1, if null or not set, then a default calendar will be used with NO holidays. * @param ccy1Calendar the Calendar for ccy1 * @return the builder */ public CurrencyDateCalculatorBuilder<E> ccy1Calendar(final HolidayCalendar<E> ccy1Calendar) { if (ccy1Calendar != null) { this.ccy1Calendar = ccy1Calendar; } return this; } /** * The holiday calendar for ccy2, if null or not set, then a default calendar will be used with NO holidays. * @param ccy2Calendar the Calendar for ccy2 * @return the builder */ public CurrencyDateCalculatorBuilder<E> ccy2Calendar(final HolidayCalendar<E> ccy2Calendar) { if (ccy2Calendar != null) { this.ccy2Calendar = ccy2Calendar; } return this; } /** * If brokenDate is not allowed, we do require to check the WorkingWeek and Holiday for the crossCcy when * validating the SpotDate or a Tenor date; if null or not set, then a default calendar will be used with NO holidays. * @param crossCcy the crossCcy (default USD). * @return the builder */ public CurrencyDateCalculatorBuilder<E> crossCcy(final String crossCcy) { this.crossCcy = crossCcy; return this; } /** * If brokenDate is not allowed, we do require to check the WorkingWeek and Holiday for the crossCcy when * validating the SpotDate or a Tenor date. * @param crossCcyCalendar the set of holidays for the crossCcy * @return the builder */ public CurrencyDateCalculatorBuilder<E> crossCcyCalendar(final HolidayCalendar<E> crossCcyCalendar) { if (crossCcyCalendar != null) { this.crossCcyCalendar = crossCcyCalendar; } return this; } /** * Provides the holiday handler for the Tenor Date, note that Spot is ALWAYS using Forward. * @param holidayHandler the Handler to work out what to do if a Tenor Date falls on a non WorkingDay. * @return the builder */ public CurrencyDateCalculatorBuilder<E> tenorHolidayHandler(final HolidayHandler<E> holidayHandler) { this.tenorHolidayHandler = holidayHandler; return this; } /** * Provides the definition of a working week for the currency; if not provided and the currencyCalculatorConfig is given, it * will do a look up for this currency. * @param ccy1Week WorkingWeek definition * @return the builder */ public CurrencyDateCalculatorBuilder<E> ccy1Week(final WorkingWeek ccy1Week) { this.ccy1Week = ccy1Week; return this; } /** * Provides the definition of a working week for the currency; if not provided and the currencyCalculatorConfig is given, it * will do a look up for this currency. * @param ccy2Week WorkingWeek definition * @return the builder */ public CurrencyDateCalculatorBuilder<E> ccy2Week(final WorkingWeek ccy2Week) { this.ccy2Week = ccy2Week; return this; } /** * If brokenDate is not allowed, we do require to check the WorkingWeek and Holiday for the crossCcy when * validating the SpotDate or a Tenor date. * @param crossCcyWeek the crossCcy WorkingWeek. * @return the builder */ public CurrencyDateCalculatorBuilder<E> crossCcyWeek(final WorkingWeek crossCcyWeek) { this.crossCcyWeek = crossCcyWeek; return this; } }