/// <summary>
        /// Creates an identifier for an ETD option instrument.
        /// <para>
        /// This takes into account the expiry of the underlying instrument. If the underlying expiry
        /// is the same as the expiry of the option, the identifier is the same as the normal one.
        /// Otherwise, the underlying expiry is added after the option expiry. For example:
        /// {@code 'OG-ETD~O-ECAG-OGBS-201706-P1.50-U201709'}.
        ///
        /// </para>
        /// </summary>
        /// <param name="exchangeId">  the MIC code of the exchange where the instruments are traded </param>
        /// <param name="contractCode">  the code supplied by the exchange for use in clearing and margining, such as in SPAN </param>
        /// <param name="expiryMonth">  the month of expiry </param>
        /// <param name="variant">  the variant of the ETD, such as 'Monthly', 'Weekly, 'Daily' or 'Flex' </param>
        /// <param name="version">  the non-negative version, zero by default </param>
        /// <param name="putCall">  the Put/Call flag </param>
        /// <param name="strikePrice">  the strike price </param>
        /// <param name="underlyingExpiryMonth">  the expiry of the underlying instrument, such as a future, may be null </param>
        /// <returns> the identifier </returns>
        public static SecurityId optionId(ExchangeId exchangeId, EtdContractCode contractCode, YearMonth expiryMonth, EtdVariant variant, int version, PutCall putCall, double strikePrice, YearMonth underlyingExpiryMonth)
        {
            ArgChecker.notNull(exchangeId, "exchangeId");
            ArgChecker.notNull(contractCode, "contractCode");
            ArgChecker.notNull(expiryMonth, "expiryMonth");
            ArgChecker.notNull(variant, "variant");
            ArgChecker.notNull(putCall, "putCall");

            string putCallStr  = putCall == PutCall.PUT ? "P" : "C";
            string versionCode = version > 0 ? "V" + version + SEPARATOR : "";

            NumberFormat f = NumberFormat.getIntegerInstance(Locale.ENGLISH);

            f.GroupingUsed          = false;
            f.MaximumFractionDigits = 8;
            string strikeStr = f.format(strikePrice).replace('-', 'M');

            string underlying = "";

            if (underlyingExpiryMonth != null && !underlyingExpiryMonth.Equals(expiryMonth))
            {
                underlying = SEPARATOR + "U" + underlyingExpiryMonth.format(YM_FORMAT);
            }

            string id = (new StringBuilder(40)).Append(OPT_PREFIX).Append(exchangeId).Append(SEPARATOR).Append(contractCode).Append(SEPARATOR).Append(expiryMonth.format(YM_FORMAT)).Append(variant.Code).Append(SEPARATOR).Append(versionCode).Append(putCallStr).Append(strikeStr).Append(underlying).ToString();

            return(SecurityId.of(ETD_SCHEME, id));
        }
 //-------------------------------------------------------------------------
 /// <summary>
 /// Obtains an instance using the year-month.
 /// </summary>
 /// <param name="date">  the date associated with the parameter </param>
 /// <param name="yearMonth">  the year-month of the curve node </param>
 /// <returns> the parameter metadata based on the year-month </returns>
 public static YearMonthDateParameterMetadata of(LocalDate date, YearMonth yearMonth)
 {
     ArgChecker.notNull(date, "date");
     ArgChecker.notNull(yearMonth, "yearMonth");
     return(new YearMonthDateParameterMetadata(date, yearMonth, yearMonth.format(FORMATTER)));
 }
        /// <summary>
        /// Creates an identifier for an ETD future instrument.
        /// <para>
        /// A typical monthly ETD will have the format:
        /// {@code 'OG-ETD~O-ECAG-OGBS-201706'}.
        /// </para>
        /// <para>
        /// A more complex flex ETD (12th of the month, Physical settlement) will have the format:
        /// {@code 'OG-ETD~O-ECAG-OGBS-20170612E'}.
        ///
        /// </para>
        /// </summary>
        /// <param name="exchangeId">  the MIC code of the exchange where the instruments are traded </param>
        /// <param name="contractCode">  the code supplied by the exchange for use in clearing and margining, such as in SPAN </param>
        /// <param name="expiryMonth">  the month of expiry </param>
        /// <param name="variant">  the variant of the ETD, such as 'Monthly', 'Weekly, 'Daily' or 'Flex' </param>
        /// <returns> the identifier </returns>
        public static SecurityId futureId(ExchangeId exchangeId, EtdContractCode contractCode, YearMonth expiryMonth, EtdVariant variant)
        {
            ArgChecker.notNull(exchangeId, "exchangeId");
            ArgChecker.notNull(contractCode, "contractCode");
            ArgChecker.notNull(expiryMonth, "expiryMonth");
            ArgChecker.isTrue(expiryMonth.Year >= 1000 && expiryMonth.Year <= 9999, "Invalid expiry year: ", expiryMonth);
            ArgChecker.notNull(variant, "variant");

            string id = (new StringBuilder(40)).Append(FUT_PREFIX).Append(exchangeId).Append(SEPARATOR).Append(contractCode).Append(SEPARATOR).Append(expiryMonth.format(YM_FORMAT)).Append(variant.Code).ToString();

            return(SecurityId.of(ETD_SCHEME, id));
        }