using System.Text.RegularExpressions; namespace YLErp.BasicTests { [TestClass] public class OptionStrategyCodeParseTest { [TestMethod] public void TestOneCode() { var optionCode = "RB1805C30D222.23"; var parts = OptionStrategyCodeParser.ParseOptionStrategyCode(optionCode); Assert.AreEqual(1, parts.Length); Assert.AreEqual(1, parts[0].Notional); Assert.AreEqual("RB1805", parts[0].UnderlyingCode); Assert.AreEqual("Call", parts[0].OptionType); Assert.AreEqual("30D", parts[0].Maturity); Assert.AreEqual(222.23, parts[0].Strike, 1e-8); Assert.AreEqual(false, parts[0].IsSell); } [TestMethod] public void TestOneCodeWithSign() { var optionCode = "-CC185P1M222"; var parts = OptionStrategyCodeParser.ParseOptionStrategyCode(optionCode); Assert.AreEqual(1, parts.Length); Assert.AreEqual(1, parts[0].Notional); Assert.AreEqual("CC185", parts[0].UnderlyingCode); Assert.AreEqual("Put", parts[0].OptionType); Assert.AreEqual("1M", parts[0].Maturity); Assert.AreEqual(222.0, parts[0].Strike, 1e-8); Assert.AreEqual(true, parts[0].IsSell); } [TestMethod] public void TestBullSpread() { var optionCode = "-RB1806C1M3700+RB1806C1M3400"; var parts = OptionStrategyCodeParser.ParseOptionStrategyCode(optionCode); Assert.AreEqual(2, parts.Length); Assert.AreEqual(1, parts[0].Notional); Assert.AreEqual("RB1806", parts[0].UnderlyingCode); Assert.AreEqual("Call", parts[0].OptionType); Assert.AreEqual("1M", parts[0].Maturity); Assert.AreEqual(3700.0, parts[0].Strike, 1e-8); Assert.AreEqual(true, parts[0].IsSell); Assert.AreEqual(1, parts[1].Notional); Assert.AreEqual("RB1806", parts[1].UnderlyingCode); Assert.AreEqual("Call", parts[1].OptionType); Assert.AreEqual("1M", parts[1].Maturity); Assert.AreEqual(3400.0, parts[1].Strike, 1e-8); Assert.AreEqual(false, parts[1].IsSell); } [TestMethod] public void TestButterfly() { var optionCode = "RB1806C1M3700-2*RB1806C1M3400+RB1806C1M3100"; var parts = OptionStrategyCodeParser.ParseOptionStrategyCode(optionCode); Assert.AreEqual(3, parts.Length); Assert.AreEqual(1, parts[0].Notional); Assert.AreEqual("RB1806", parts[0].UnderlyingCode); Assert.AreEqual("Call", parts[0].OptionType); Assert.AreEqual("1M", parts[0].Maturity); Assert.AreEqual(3700.0, parts[0].Strike, 1e-8); Assert.AreEqual(false, parts[0].IsSell); Assert.AreEqual(2, parts[1].Notional); Assert.AreEqual("RB1806", parts[1].UnderlyingCode); Assert.AreEqual("Call", parts[1].OptionType); Assert.AreEqual("1M", parts[1].Maturity); Assert.AreEqual(3400.0, parts[1].Strike, 1e-8); Assert.AreEqual(true, parts[1].IsSell); Assert.AreEqual(1, parts[2].Notional); Assert.AreEqual("RB1806", parts[2].UnderlyingCode); Assert.AreEqual("Call", parts[2].OptionType); Assert.AreEqual("1M", parts[2].Maturity); Assert.AreEqual(3100.0, parts[2].Strike, 1e-8); Assert.AreEqual(false, parts[2].IsSell); } public class OptionStrategyCodeParser { private static readonly Regex codePattern = new(@"(?[\+-]?)\s*((?\d+)\*)?(?[a-zA-Z]{1,2}[0-9]{3,4})(?C|P)(?\d+[DMWY]{1})(?[0-9]+(?:\.[0-9]*)?)"); public static OptionStrategyCodeParts[] ParseOptionStrategyCode(string code) { var mc = codePattern.Matches(code); if (mc.Count == 0) { throw new ArgumentException(string.Format("不合法的策略代码:{0}", code)); } return mc.Cast().Select(m => ParseOneMatch(m)).ToArray(); } private static OptionStrategyCodeParts ParseOneMatch(Match m) { var parts = new OptionStrategyCodeParts(); var groups = m.Groups; parts.UnderlyingCode = groups["underlying"].Value; parts.Strike = double.Parse(groups["strike"].Value); parts.Maturity = groups["maturity"].Value; var optionType = groups["optionType"].Value; if (optionType == "C") { parts.OptionType = "Call"; } else if (optionType == "P") { parts.OptionType = "Put"; } else { // Shouldn't come to here, the regex ensured only C|P is matched throw new ArgumentException(string.Format("不合法的期权类型{0}. 合法值为[CP]", optionType)); } if (groups["notional"].Length > 0) { parts.Notional = int.Parse(groups["notional"].Value); } else { parts.Notional = 1; } if (groups["sign"].Length > 0) { if (groups["sign"].Value == "-") { parts.IsSell = true; } else { parts.IsSell = false; } } else { parts.IsSell = false; } return parts; } } public class OptionStrategyCodeParts { public int Notional { get; set; } public string UnderlyingCode { get; set; } public string OptionType { get; set; } public string Maturity { get; set; } public double Strike { get; set; } public bool IsSell { get; set; } public override string ToString() { return string.Format( "{0}{1}{2}{3}{4}{5}", IsSell ? "-" : "", Notional == 1 ? "" : string.Format("{0}*", Notional), UnderlyingCode, "Call".Equals(OptionType) ? "C" : "P", Maturity, Strike); } } } }