001package net.objectlab.kit.datecalc.common; 002 003import java.io.Serializable; 004 005import net.objectlab.kit.datecalc.common.ccy.CurrencyCalculatorConfig; 006 007/** 008 * Provides enough information to create an immutable CurrencyDateCalculator. 009 * 010 * <pre> 011 CurrencyDateCalculatorBuilder<LocalDate> builder = new CurrencyDateCalculatorBuilder<LocalDate>() // 012 .currencyPair("EUR", "GBP", SpotLag.T_2) // 013 .ccy1Calendar(new DefaultHolidayCalendar<LocalDate>()) // empty 014 .ccy1Week(WorkingWeek.DEFAULT) // Mon-Fri 015 .ccy2Calendar(gbpCalendar) // 016 .ccy2Week(WorkingWeek.DEFAULT) // Mon-Fri 017 .crossCcy("USD") // the usual suspect 018 .crossCcyCalendar(usdCalendar) // 019 .crossCcyWeek(WorkingWeek.DEFAULT) // Mon-Fri; 020 .adjustStartDateWithCurrencyPair(true) // default is true, Move the startDate to a working date for ccy1 and ccy2 021 .tenorHolidayHandler(new LocalDateForwardHandler()) // Forward (or equivalent for your implementation) 022 .brokenDateAllowed(false) // use the CrossCcy holidays on Spot and Tenor Date 023 .currencyCalculatorConfig(new DefaultCurrencyCalculatorConfig()) // Will be used for finding Working Weeks IF NOT PROVIDED and Latin 024 // American ccy USD handling. 025 * </pre> 026 * 027 * @param <E> JDK Date/Calendar, JDK8 LocalDate or Joda LocalDate 028 * @since 1.4.0 029 */ 030public class CurrencyDateCalculatorBuilder<E extends Serializable> { 031 private String ccy1; 032 private String ccy2; 033 private String crossCcy = CalculatorConstants.USD_CODE; 034 private HolidayCalendar<E> ccy1Calendar = new DefaultHolidayCalendar<>(); 035 private HolidayCalendar<E> ccy2Calendar = new DefaultHolidayCalendar<>(); 036 private HolidayCalendar<E> crossCcyCalendar = new DefaultHolidayCalendar<>(); 037 private HolidayHandler<E> tenorHolidayHandler; 038 private WorkingWeek ccy1Week; 039 private WorkingWeek ccy2Week; 040 private WorkingWeek crossCcyWeek; 041 private CurrencyCalculatorConfig currencyCalculatorConfig; 042 private boolean brokenDateAllowed; 043 private boolean adjustStartDateWithCurrencyPair = true; 044 private SpotLag spotLag = SpotLag.T_2; 045 046 /** 047 * Default values are: 048 * <ul> 049 * <li>crossCcy = USD</li> 050 * <li>ccy1Calendar = Empty Calendar</li> 051 * <li>ccy2Calendar = Empty Calendar</li> 052 * <li>crossCcyCalendar = Empty Calendar</li> 053 * <li>brokenDateAllowed = false</li> 054 * <li>adjustStartDateWithCurrencyPair = true</li> 055 * <li>spotLag = SpotLag.T_2</li> 056 * </ul> 057 */ 058 public CurrencyDateCalculatorBuilder() { 059 // using default values ok 060 } 061 062 /** 063 * Checks the builder and throws an IllegalArgumentException if there are issues e.g. 064 * <ul> 065 * <li>ccy1 or ccy2 missing</li> 066 * <li>ccy1Week or ccy2Week missing</li> 067 * <li>spotLag is missing</li> 068 * <li>tenorHolidayHandler is missing</li> 069 * <li>If brokenDate is not allowed, we need crossCcy, crossCcyWeek and crossCcyCalendar.</li> 070 * </ul> 071 */ 072 public void checkValidity() { 073 StringBuilder b = new StringBuilder(); 074 if (ccy1 == null || ccy1.length() == 0) { 075 b.append("ccy1 is required"); 076 } 077 if (ccy2 == null || ccy2.length() == 0) { 078 append(b, "ccy2 is required"); 079 } 080 if (getCcy1Week() == null) { 081 append(b, "ccy1Week is required"); 082 } 083 if (getCcy2Week() == null) { 084 append(b, "ccy2Week is required"); 085 } 086 if (!brokenDateAllowed) { 087 checkBrokenDate(b); 088 } 089 if (spotLag == null) { 090 append(b, "spotLag is required"); 091 } 092 if (tenorHolidayHandler == null) { 093 append(b, "tenorHolidayHandler is required"); 094 } 095 if (b.length() > 0) { 096 throw new IllegalArgumentException(b.toString()); 097 } 098 } 099 100 private void checkBrokenDate(StringBuilder b) { 101 if (getCrossCcy() == null) { 102 append(b, "crossCcy is required"); 103 } 104 if (getCrossCcyWeek() == null) { 105 append(b, "crossCcyWeek is required"); 106 } 107 if (getCrossCcyCalendar() == null) { 108 append(b, "crossCcyCalendar is required"); 109 } 110 } 111 112 private static void append(StringBuilder b, String string) { 113 if (b.length() > 0) { 114 b.append(","); 115 } 116 b.append(string); 117 } 118 119 public String getCcy1() { 120 return ccy1; 121 } 122 123 public String getCcy2() { 124 return ccy2; 125 } 126 127 public String getCrossCcy() { 128 return crossCcy; 129 } 130 131 public HolidayCalendar<E> getCcy1Calendar() { 132 return ccy1Calendar; 133 } 134 135 public HolidayCalendar<E> getCcy2Calendar() { 136 return ccy2Calendar; 137 } 138 139 public HolidayCalendar<E> getCrossCcyCalendar() { 140 return crossCcyCalendar; 141 } 142 143 public HolidayHandler<E> getTenorHolidayHandler() { 144 return tenorHolidayHandler; 145 } 146 147 public WorkingWeek getCcy1Week() { 148 if (ccy1Week != null) { 149 return ccy1Week; 150 } 151 return currencyCalculatorConfig != null ? currencyCalculatorConfig.getWorkingWeek(ccy1) : null; 152 } 153 154 public WorkingWeek getCcy2Week() { 155 if (ccy2Week != null) { 156 return ccy2Week; 157 } 158 return currencyCalculatorConfig != null ? currencyCalculatorConfig.getWorkingWeek(ccy2) : null; 159 } 160 161 public WorkingWeek getCrossCcyWeek() { 162 if (crossCcyWeek != null) { 163 return crossCcyWeek; 164 } 165 return currencyCalculatorConfig != null ? currencyCalculatorConfig.getWorkingWeek(crossCcy) : null; 166 } 167 168 public CurrencyCalculatorConfig getCurrencyCalculatorConfig() { 169 return currencyCalculatorConfig; 170 } 171 172 public boolean isBrokenDateAllowed() { 173 return brokenDateAllowed; 174 } 175 176 /** 177 * This specialises the calculator to the given currency pair and the SpotLag (0, 1, 2). Given than some currencies can have different 178 * SpotLag depending on the business, this is something that the user will have to provide. 179 * @param ccy1 180 * @param ccy2 181 * @param spotLag 182 * @return the builder 183 */ 184 public CurrencyDateCalculatorBuilder<E> currencyPair(final String ccy1, final String ccy2, final SpotLag spotLag) { 185 this.ccy1 = ccy1; 186 this.ccy2 = ccy2; 187 this.spotLag = spotLag; 188 return this; 189 } 190 191 public SpotLag getSpotLag() { 192 return spotLag; 193 } 194 195 public boolean isAdjustStartDateWithCurrencyPair() { 196 return adjustStartDateWithCurrencyPair; 197 } 198 199 /** 200 * 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 201 * and any holidays). 202 * @param adjustStartDateWithCurrencyPair default true 203 * @return the builder 204 */ 205 public CurrencyDateCalculatorBuilder<E> adjustStartDateWithCurrencyPair(final boolean adjustStartDateWithCurrencyPair) { 206 this.adjustStartDateWithCurrencyPair = adjustStartDateWithCurrencyPair; 207 return this; 208 } 209 210 /** 211 * 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 212 * usually would be skipped). 213 * @param brokenDateAllowed default false 214 * @return the builder 215 */ 216 public CurrencyDateCalculatorBuilder<E> brokenDateAllowed(final boolean brokenDateAllowed) { 217 this.brokenDateAllowed = brokenDateAllowed; 218 return this; 219 } 220 221 /** 222 * Provides information about currencies subject to USD on T+1 and WorkingWeeks if not specified individually. 223 * @param currencyCalculatorConfig the config 224 * @return 225 */ 226 public CurrencyDateCalculatorBuilder<E> currencyCalculatorConfig(final CurrencyCalculatorConfig currencyCalculatorConfig) { 227 this.currencyCalculatorConfig = currencyCalculatorConfig; 228 return this; 229 } 230 231 /** 232 * The holiday calendar for ccy1, if null or not set, then a default calendar will be used with NO holidays. 233 * @param ccy1Calendar the Calendar for ccy1 234 * @return the builder 235 */ 236 public CurrencyDateCalculatorBuilder<E> ccy1Calendar(final HolidayCalendar<E> ccy1Calendar) { 237 if (ccy1Calendar != null) { 238 this.ccy1Calendar = ccy1Calendar; 239 } 240 return this; 241 } 242 243 /** 244 * The holiday calendar for ccy2, if null or not set, then a default calendar will be used with NO holidays. 245 * @param ccy2Calendar the Calendar for ccy2 246 * @return the builder 247 */ 248 public CurrencyDateCalculatorBuilder<E> ccy2Calendar(final HolidayCalendar<E> ccy2Calendar) { 249 if (ccy2Calendar != null) { 250 this.ccy2Calendar = ccy2Calendar; 251 } 252 return this; 253 } 254 255 /** 256 * If brokenDate is not allowed, we do require to check the WorkingWeek and Holiday for the crossCcy when 257 * validating the SpotDate or a Tenor date; if null or not set, then a default calendar will be used with NO holidays. 258 * @param crossCcy the crossCcy (default USD). 259 * @return the builder 260 */ 261 public CurrencyDateCalculatorBuilder<E> crossCcy(final String crossCcy) { 262 this.crossCcy = crossCcy; 263 return this; 264 } 265 266 /** 267 * If brokenDate is not allowed, we do require to check the WorkingWeek and Holiday for the crossCcy when 268 * validating the SpotDate or a Tenor date. 269 * @param crossCcyCalendar the set of holidays for the crossCcy 270 * @return the builder 271 */ 272 public CurrencyDateCalculatorBuilder<E> crossCcyCalendar(final HolidayCalendar<E> crossCcyCalendar) { 273 if (crossCcyCalendar != null) { 274 this.crossCcyCalendar = crossCcyCalendar; 275 } 276 return this; 277 } 278 279 /** 280 * Provides the holiday handler for the Tenor Date, note that Spot is ALWAYS using Forward. 281 * @param holidayHandler the Handler to work out what to do if a Tenor Date falls on a non WorkingDay. 282 * @return the builder 283 */ 284 public CurrencyDateCalculatorBuilder<E> tenorHolidayHandler(final HolidayHandler<E> holidayHandler) { 285 this.tenorHolidayHandler = holidayHandler; 286 return this; 287 } 288 289 /** 290 * Provides the definition of a working week for the currency; if not provided and the currencyCalculatorConfig is given, it 291 * will do a look up for this currency. 292 * @param ccy1Week WorkingWeek definition 293 * @return the builder 294 */ 295 public CurrencyDateCalculatorBuilder<E> ccy1Week(final WorkingWeek ccy1Week) { 296 this.ccy1Week = ccy1Week; 297 return this; 298 } 299 300 /** 301 * Provides the definition of a working week for the currency; if not provided and the currencyCalculatorConfig is given, it 302 * will do a look up for this currency. 303 * @param ccy2Week WorkingWeek definition 304 * @return the builder 305 */ 306 public CurrencyDateCalculatorBuilder<E> ccy2Week(final WorkingWeek ccy2Week) { 307 this.ccy2Week = ccy2Week; 308 return this; 309 } 310 311 /** 312 * If brokenDate is not allowed, we do require to check the WorkingWeek and Holiday for the crossCcy when 313 * validating the SpotDate or a Tenor date. 314 * @param crossCcyWeek the crossCcy WorkingWeek. 315 * @return the builder 316 */ 317 public CurrencyDateCalculatorBuilder<E> crossCcyWeek(final WorkingWeek crossCcyWeek) { 318 this.crossCcyWeek = crossCcyWeek; 319 return this; 320 } 321}