Overall Statistics |
Total Orders 32886 Average Win 0.06% Average Loss -0.01% Compounding Annual Return -35.521% Drawdown 57.200% Expectancy -0.367 Start Equity 10000.00 End Equity 7081.85 Net Profit -29.182% Sharpe Ratio -0.272 Sortino Ratio -0.518 Probabilistic Sharpe Ratio 15.316% Loss Rate 95% Win Rate 5% Profit-Loss Ratio 12.45 Alpha -0.287 Beta 0.092 Annual Standard Deviation 0.8 Annual Variance 0.64 Information Ratio -1.037 Tracking Error 0.935 Treynor Ratio -2.381 Total Fees ₮5418.47 Estimated Strategy Capacity ₮610000000.00 Lowest Capacity Asset BTCUSDT 18R Portfolio Turnover 198.94% |
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Drawing; using QuantConnect; using QuantConnect.Algorithm.Framework; using QuantConnect.Algorithm.Framework.Selection; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Algorithm.Framework.Execution; using QuantConnect.Algorithm.Framework.Risk; using QuantConnect.Algorithm.Selection; using QuantConnect.Parameters; using QuantConnect.Benchmarks; using QuantConnect.Brokerages; using QuantConnect.Util; using QuantConnect.Interfaces; using QuantConnect.Algorithm; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Custom; using QuantConnect.DataSource; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Notifications; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.Slippage; using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Statistics; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; using QuantConnect.Securities.Interfaces; using QuantConnect.Storage; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; using QuantConnect.Algorithm.CSharp.PricingModels; using QuantConnect.Algorithm.CSharp.qlnet.tools; #endregion namespace QuantConnect.Algorithm.CSharp { public class BinanceSynSellCovex : QCAlgorithm { [Parameter("optionRatio")] public decimal optionRatio = 4m; [Parameter("inOutRatio")] public decimal inOutRatio = 3m; [Parameter("maxDrawdownObey")] public decimal maxDrawdownObey = 0.35m; [Parameter("rollDays")] public double rollDays = 6; [Parameter("optionMinUnit")] public decimal optionMinUnit = 0.01m; protected string hedgeWay = "bound"; protected string expiryDatesType = "Restrict"; protected string expityType = "Quarterly"; protected decimal hedgeRange = 0.1m; protected decimal hedgeRangeClear = 0.001m; protected decimal totalDelta; protected decimal futureTradeMinUnit = 0.002m; protected decimal lastValidPrice; protected decimal MaxNet; protected decimal RollingSumNetValue; protected Symbol FutureSymbol; protected Symbol SpotSymbol; protected double lastPrice; protected double currentVolatility; protected double currentVolatilityInOut; protected double lambda = 0.98; protected double lambdaInOut = 0.99989; protected double volMultiplier = 525600; protected double riskFreeRate = 0; protected double dividend = 0; protected double startClearIv = 0.7; protected double IVForClear = 0.6; protected double lambdaIV = 0; protected double decreaseTime = 60; protected bool isFirstTime = true; protected bool isBacktest = false; protected bool isStartClear = false; protected bool isIvForClearUpdated = false; protected long lastOrderId; protected DateTime lastHedgedTime; protected DateTime lastOnDataTime; protected DateTime startTime; protected DateTime lastHour; protected DateTime lastLossControlTime; protected DateTime lastCheckTime; protected DateTime clearTime; protected (decimal Value, int Count) currentHourData; protected List<SyntheticOption> SyntheticOptions = new List<SyntheticOption>(); protected List<decimal> optionsCenterList = new List<decimal>(); protected Queue<Tuple<DateTime, decimal>> TenDaysNetQueue = new Queue<Tuple<DateTime, decimal>>(); protected Dictionary<string, BinanceFutureTrader> BinanceFutureTraderDictionary = new Dictionary<string, BinanceFutureTrader> { }; public override void Initialize() { isBacktest = true; SetTimeZone( TimeZones.Utc); SetStartDate(2024, 1, 1); SetEndDate(2025,1,5); SetAccountCurrency("USDT", 10000); SetBrokerageModel(BrokerageName.Binance, AccountType.Margin); FutureSymbol = AddCryptoFuture("BTCUSDT", Resolution.Second, Market.Binance).Symbol; SpotSymbol = AddCrypto("BTCUSDT", Resolution.Second, Market.Binance).Symbol; SetBenchmark(SpotSymbol); Debug("Algorithm Started"); Securities[FutureSymbol].SetLeverage(20); var chart = new Chart("EWMA Volatility"); AddChart(chart); chart.AddSeries(new Series("EWMA", SeriesType.Line, "$", Color.Orange)); chart.AddSeries(new Series("EWMALong", SeriesType.Line, "$", Color.Blue)); chart = new Chart("Delta"); AddChart(chart); chart.AddSeries(new Series("Delta", SeriesType.Line, "$", Color.Green)); chart = new Chart("Holding"); AddChart(chart); chart.AddSeries(new Series("Holding", SeriesType.Line, "$", Color.Orange)); } public void plot() { Plot("EWMA Volatility", "EWMA", Math.Sqrt(currentVolatility * volMultiplier)); Plot("EWMA Volatility", "EWMALong", Math.Sqrt(currentVolatilityInOut * volMultiplier)); Plot("Holding", "Holding", Securities[FutureSymbol].Holdings.Quantity); var validOptions = SyntheticOptions.Where(o => o.Quantity != 0m).ToList(); var futPrice = Securities[FutureSymbol].Cache.Price; var sptPrice = Securities[SpotSymbol].Cache.Price; if ( futPrice == 0 || sptPrice == 0 ) { return; } //var totalDeltaPlot = Securities[FutureSymbol].Holdings.UnrealizedProfit / futPrice; var totalDeltaPlot = Securities[FutureSymbol].Holdings.Quantity * (futPrice / sptPrice); if (validOptions.Any()) { foreach (var optionDetail in validOptions) { var option = optionDetail; var quantity = option.Quantity; var underlying = option.UnderlyingAsset; double S = (double)Securities[underlying].Price; double X = (double)option.StrikePrice; var t2M = (option.ExpiryDate - Time).TotalDays / 365; var side = option.OptionType == OptionType.Call ? BSModel.EPutCall.Call : BSModel.EPutCall.Put; decimal bsDelta = 0; double selectediv = Math.Sqrt(currentVolatility * volMultiplier); bsDelta = (decimal)BSModel.GetDelta(S, X, dividend, riskFreeRate, selectediv, t2M, (BSModel.EPutCall)side); totalDeltaPlot += bsDelta * quantity; } } var baseDelta = (Portfolio.TotalPortfolioValue + 20000) * optionRatio / Securities[FutureSymbol].Cache.Price; Plot("Delta", "Delta", totalDeltaPlot / baseDelta); } public override void PostInitialize() { base.PostInitialize(); Debug("PostInitialize Started"); currentVolatility = 0.2025 / volMultiplier; currentVolatilityInOut = 0.2025 / volMultiplier; Schedule.On(DateRules.EveryDay(), TimeRules.Every(TimeSpan.FromMinutes(1)), UpdateVolatility); Schedule.On(DateRules.EveryDay(), TimeRules.At(0,0,1), RollingOptions); Schedule.On(DateRules.EveryDay(), TimeRules.Every(TimeSpan.FromMinutes(720)), plot); } public enum OptionType { Call, Put } public class SyntheticOption { public string UnderlyingAsset { get; set; } // 标的资产 public decimal Quantity { get; set; } // 当前持有的期权数量 public decimal StrikePrice { get; set; } // 行权价 public DateTime ExpiryDate { get; set; } // 到期日 public OptionType OptionType { get; set; } // 期权类型(Call 或者 Put) public SyntheticOption(string underlying, decimal quantity, decimal strikePrice, DateTime expiryDate, OptionType optionType) { UnderlyingAsset = underlying; Quantity = quantity; StrikePrice = strikePrice; ExpiryDate = expiryDate; OptionType = optionType; } public void UpdateQuantity(decimal quantityChange) { Quantity += quantityChange; } public override string ToString() { return $"Synthetic Option - Underlying: {UnderlyingAsset}, Quantity: {Quantity}, Strike Price: {StrikePrice}, Expiry Date: {ExpiryDate.ToShortDateString()}, Type: {OptionType}"; } } protected DateTime GenerateExDate(string ExpiryType) { var outDate = Time; var nextMonth = Time.AddMonths(1).AddDays(-Time.Day + 1); var validExpiry = Enumerable.Range(0, 365) .Select(i => Time.AddDays(i)) .Where(date => date.DayOfWeek == DayOfWeek.Friday); var validExpiry1 = validExpiry.Where(date => date > Time.AddDays(10)); var allMonthly = validExpiry.Where(date => date > Time.AddDays(10) && date.AddDays(7) >= date.AddMonths(1).AddDays(-date.Day + 1)); var allQuarterly = allMonthly.Where(date => date > Time.AddDays(30) && (date.Month == 3 || date.Month == 6 || date.Month == 9 || date.Month == 12)); if (ExpiryType == "Weekly" && validExpiry.Any()) { outDate = validExpiry.OrderBy(date => date).First(); } else if (ExpiryType == "Weekly_next" && validExpiry1.Any()) { outDate = validExpiry1.OrderBy(date => date).First(); } else if (ExpiryType == "Monthly" && allMonthly.Any()) { outDate = allMonthly.OrderBy(date => date).First(); } else if (ExpiryType == "Quarterly" && allQuarterly.Any()) { outDate = allQuarterly.OrderBy(date => date).First(); } return outDate; } protected DateTime GenerateExDate1(string ExpiryType) { DateTime outDate = Time; switch (ExpiryType) { case "Weekly": outDate = Time.AddDays(7).Date.AddHours(8); // +7天,早上8点 break; case "Weekly_next": outDate = Time.AddDays(15).Date.AddHours(8); // +15天,早上8点 break; case "Monthly": outDate = Time.AddDays(30).Date.AddHours(8); // +30天,早上8点 break; case "Quarterly": outDate = Time.AddDays(90).Date.AddHours(8); // +90天,早上8点 break; } return outDate; } protected void UpdateVolatility() { var price = (double)Securities[FutureSymbol].Price; if (price == 0) { return; } if (lastPrice == 0) { lastPrice = price; return; } if (price == lastPrice) { return; } var newReturn = Math.Log(Convert.ToDouble(price / lastPrice),Math.E); lastPrice = price; currentVolatility = lambda * currentVolatility + (1 - lambda) * newReturn * newReturn; currentVolatilityInOut = lambdaInOut * currentVolatilityInOut + (1 - lambdaInOut) * newReturn * newReturn; if (Math.Sqrt(currentVolatility * volMultiplier) >= 8) { currentVolatility = 64 / volMultiplier; } if (Math.Sqrt(currentVolatilityInOut * volMultiplier) >= 8) { currentVolatilityInOut = 64 / volMultiplier; } if (isStartClear) { lambdaIV += 1 / decreaseTime; lambdaIV = Math.Min(1, lambdaIV); var ewma = Math.Sqrt(currentVolatility * volMultiplier); var ewmaInOut = Math.Sqrt(currentVolatilityInOut * volMultiplier); var IV = (1 - lambdaIV) * startClearIv + lambdaIV * ewmaInOut; IVForClear = Math.Min(ewma, IV); isIvForClearUpdated = true; } } protected virtual void RollingOptions() { var holdingOptions = SyntheticOptions.Where(o => o.Quantity != 0m).OrderBy(o => o.ExpiryDate); foreach (var option in holdingOptions) { RollSyntheticOption(option); } void RollSyntheticOption(SyntheticOption oldOption) { if (oldOption.Quantity == 0) { SyntheticOptions.Remove(oldOption); } if (oldOption.ExpiryDate.Date <= Time.AddDays(rollDays)) { oldOption.UpdateQuantity(-oldOption.Quantity); if (oldOption.Quantity == 0) { SyntheticOptions.Remove(oldOption); } } } } public static int GetDecimalPrecision(decimal number) { // 将数字转换为绝对值 number = Math.Abs(number); // 如果是整数,返回0 if (number == Math.Floor(number)) { return 0; } // 将数字转换为字符串并分割小数部分 string numberStr = number.ToString("G29"); int decimalPointIndex = numberStr.IndexOf('.'); // 如果存在小数点,则计算小数位数 if (decimalPointIndex >= 0) { return numberStr.Length - decimalPointIndex - 1; } return 0; // 如果没有小数部分 } /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. /// Slice object keyed by symbol containing the stock data public override void OnData(Slice data) { if (Securities[FutureSymbol].Cache.Price == 0 || Securities[SpotSymbol].Cache.Price == 0) { Debug("No price data"); return; } if (isFirstTime) { isFirstTime = false; lastOnDataTime = Time; lastHedgedTime = Time; startTime = Time; } var idleOrders = Transactions.GetOpenOrders(); foreach (var order in idleOrders) { if (Time - order.CreatedTime > TimeSpan.FromSeconds(120)) { Transactions.CancelOrder(order.Id); } } if (!isBacktest && Time - startTime <= TimeSpan.FromMinutes(1)) { return; } DateTime currentHour = new DateTime(Time.Year, Time.Month, Time.Day, Time.Hour, 0, 0); if( lastHour != currentHour ) { if (currentHourData.Item2 > 0) { var lastHourAverage = currentHourData.Item1 / currentHourData.Item2; TenDaysNetQueue.Enqueue(new Tuple<DateTime, decimal>(lastHour, lastHourAverage)); RollingSumNetValue += lastHourAverage; } while (TenDaysNetQueue.Count > 0 && (Time - TenDaysNetQueue.Peek().Item1).TotalDays > 1) { var removed = TenDaysNetQueue.Dequeue(); RollingSumNetValue -= removed.Item2; } currentHourData = ((Portfolio.TotalPortfolioValue + 20000), 1); lastHour = currentHour; } else { currentHourData = ( currentHourData.Item1 + (Portfolio.TotalPortfolioValue + 20000), currentHourData.Item2 + 1 ); } decimal rollingAvgNetValue = TenDaysNetQueue.Count > 0 ? RollingSumNetValue / TenDaysNetQueue.Count : 0; // 更新最大净值并检查回撤 MaxNet = Math.Max(rollingAvgNetValue, MaxNet); if (MaxNet != 0) { if ((MaxNet - (Portfolio.TotalPortfolioValue + 20000)) / MaxNet > maxDrawdownObey && !isStartClear) { isStartClear = true; clearTime = Time; startClearIv = Math.Sqrt(currentVolatility * volMultiplier); lambdaIV = 0; } } // 检查清除标志过期 if (Time - clearTime > TimeSpan.FromDays(1) && isStartClear) { isStartClear = false; MaxNet = (Portfolio.TotalPortfolioValue + 20000); TenDaysNetQueue.Clear(); RollingSumNetValue = 0; } if (isStartClear && isIvForClearUpdated) { if (Time - lastLossControlTime >= TimeSpan.FromMinutes(1)) { LossControl(); lastLossControlTime = Time; } return; } if (hedgeWay != "none" && Time - lastHedgedTime >= TimeSpan.FromSeconds( 5 )) { TradeDelta( hedgeWay ); } if ( Time - lastOnDataTime <= TimeSpan.FromSeconds(5)) { return; } lastOnDataTime = Time; TryAddSynOptions( expityType, OptionType.Call); } protected void TradeDelta( string hedgeWayUsed) { var validOptions = SyntheticOptions.Where(o => o.Quantity != 0m).ToList(); if (hedgeWayUsed == "none") { return; } if (optionsCenterList.Count > 0) { hedgeWayUsed = "progressive"; } var futPrice = Securities[FutureSymbol].Cache.Price; var sptPrice = Securities[SpotSymbol].Cache.Price; if ( futPrice == 0 || sptPrice == 0 ) { return; } //totalDelta = Securities[FutureSymbol].Holdings.UnrealizedProfit / futPrice; totalDelta = Securities[FutureSymbol].Holdings.Quantity * (futPrice / sptPrice); if (validOptions.Any()) { foreach (var optionDetail in validOptions) { var option = optionDetail; var quantity = option.Quantity; var underlying = option.UnderlyingAsset; double S = (double)Securities[underlying].Price; double X = (double)option.StrikePrice; var t2M = (option.ExpiryDate - Time).TotalDays / 365; var side = option.OptionType == OptionType.Call ? BSModel.EPutCall.Call : BSModel.EPutCall.Put; decimal bsDelta = 0; double selectediv = Math.Sqrt(currentVolatility * volMultiplier); bsDelta = (decimal)BSModel.GetDelta(S, X, dividend, riskFreeRate, selectediv, t2M, (BSModel.EPutCall)side); totalDelta += bsDelta * quantity; } } var baseDelta = (Portfolio.TotalPortfolioValue + 20000) * optionRatio / Securities[FutureSymbol].Cache.Price; var marketPrice = Securities[FutureSymbol].Price; if (hedgeWayUsed == "progressive") { var tVolume = -totalDelta; if (Math.Abs(tVolume) >= futureTradeMinUnit) { if(optionsCenterList.Sum() > Math.Abs(tVolume) * marketPrice) { UpdateOptionCenterList(optionsCenterList.Sum() - Math.Abs(tVolume) * marketPrice); } tVolume = Math.Sign(tVolume) * Math.Min(optionsCenterList.Sum() / marketPrice, Math.Abs(tVolume)); tVolume = Math.Round(tVolume, GetDecimalPrecision(futureTradeMinUnit)); UpdateOptionCenterList(0); if (Math.Abs(tVolume) >= futureTradeMinUnit) { MarketOrderMessage(FutureSymbol, tVolume, "渐进中心对冲"); } } } if (hedgeWayUsed == "bound") { if (totalDelta > hedgeRange * baseDelta) { var tVolume = -totalDelta + hedgeRange * baseDelta; if ( Math.Abs( tVolume * marketPrice ) > (Portfolio.TotalPortfolioValue + 20000) / 10) { tVolume = tVolume > 0 ? (Portfolio.TotalPortfolioValue + 20000) / 10 / marketPrice : -(Portfolio.TotalPortfolioValue + 20000) / 10 / marketPrice; } tVolume = Math.Round(tVolume, GetDecimalPrecision(futureTradeMinUnit)); if ( Math.Abs( tVolume) >= futureTradeMinUnit ) { MarketOrderMessage( FutureSymbol, tVolume, "边界对冲"); lastHedgedTime = Time; } } else if (totalDelta < -hedgeRange * baseDelta) { var tVolume = -totalDelta - hedgeRange * baseDelta; if ( Math.Abs (tVolume * marketPrice) > (Portfolio.TotalPortfolioValue + 20000) / 10) { tVolume = tVolume > 0 ? (Portfolio.TotalPortfolioValue + 20000) / 10 / marketPrice : -(Portfolio.TotalPortfolioValue + 20000) / 10 / marketPrice; } tVolume = Math.Round(tVolume, GetDecimalPrecision(futureTradeMinUnit)); if ( Math.Abs( tVolume ) >= futureTradeMinUnit ) { MarketOrderMessage( FutureSymbol, tVolume, "边界对冲"); lastHedgedTime = Time; } } } } protected void LossControl() { var futPrice = Securities[FutureSymbol].Cache.Price; var sptPrice = Securities[SpotSymbol].Cache.Price; if ( futPrice == 0 || sptPrice == 0) { return; } //totalDelta = Securities[FutureSymbol].Holdings.UnrealizedProfit / futPrice; totalDelta = Securities[FutureSymbol].Holdings.Quantity * (futPrice / sptPrice); var validOptions = SyntheticOptions.Where(o => o.Quantity != 0m).ToList(); if (validOptions.Any()) { foreach (var optionDetail in validOptions) { var option = optionDetail; var quantity = option.Quantity; var underlying = option.UnderlyingAsset; double S = (double)Securities[underlying].Price; double X = (double)option.StrikePrice; var t2M = (option.ExpiryDate - Time).TotalDays / 365; var side = option.OptionType == OptionType.Call ? BSModel.EPutCall.Call : BSModel.EPutCall.Put; decimal bsDelta = 0; bsDelta = (decimal)BSModel.GetDelta(S, X, dividend, riskFreeRate, IVForClear, t2M, (BSModel.EPutCall)side); totalDelta += bsDelta * quantity; } } var baseDelta = (Portfolio.TotalPortfolioValue + 20000) * optionRatio / Securities[FutureSymbol].Cache.Price; isIvForClearUpdated = false; var marketPrice = Securities[FutureSymbol].Price; if ( totalDelta > hedgeRangeClear * baseDelta ) { var tVolume = -totalDelta + hedgeRangeClear * baseDelta ; if ( Math.Abs( tVolume * marketPrice ) > (Portfolio.TotalPortfolioValue + 20000) / 10 ) { tVolume = tVolume > 0 ? (Portfolio.TotalPortfolioValue + 20000) / 10 / marketPrice : -(Portfolio.TotalPortfolioValue + 20000) / 10 / marketPrice; } tVolume = Math.Round(tVolume, GetDecimalPrecision(futureTradeMinUnit)); if( Math.Abs( tVolume ) >= futureTradeMinUnit ) { MarketOrderMessage( FutureSymbol, tVolume, "清仓对冲"); } } } void UpdateOptionCenterList(decimal tradedPervol) { for (int i = 0; i < optionsCenterList.Count; i++) { if (tradedPervol <= 0) { break; } if (optionsCenterList[i] >= tradedPervol) { optionsCenterList[i] -= tradedPervol; tradedPervol = 0; break; } else { tradedPervol -= optionsCenterList[i]; optionsCenterList[i] = 0; } } optionsCenterList.RemoveAll(item => Math.Abs(item) < 100); } protected bool TryAddSynOptions(string ExpiryType, OptionType optionType) { var sptPrice = Securities[SpotSymbol].Cache.Price; decimal positionForWritingOption = 0m; if ( sptPrice != 0) { lastValidPrice = sptPrice; } if (lastValidPrice != 0) { var portfolioValue = -(Portfolio.TotalPortfolioValue + 20000) * optionRatio; var optionPosition = SyntheticOptions.Where(o => o.Quantity != 0m).Sum(o => o.Quantity * lastValidPrice); positionForWritingOption = (portfolioValue - optionPosition) / lastValidPrice; } if ( positionForWritingOption > 0 ) { return false; } DateTime expiryDate = Time; if ( expiryDatesType == "noRestrict") { expiryDate = GenerateExDate1(ExpiryType); } else { expiryDate = GenerateExDate(ExpiryType); } expiryDate = new DateTime(expiryDate.Year, expiryDate.Month, expiryDate.Day, 8, 0, 0); var currentDate = Time; if ((expiryDate - currentDate).TotalDays < 3) { return false; } var strikePriceRaw = Securities[SpotSymbol].Cache.Price * (1m + (decimal)Math.Sqrt( currentVolatilityInOut * volMultiplier ) * inOutRatio); var aggregationFactor = 0.03m; var strikePrice = Math.Round( strikePriceRaw / Math.Round(Securities[SpotSymbol].Cache.Price * aggregationFactor, 0)) * Math.Round(Securities[SpotSymbol].Cache.Price * aggregationFactor, 0); var syntheticOption = SyntheticOptions.FirstOrDefault(o => o.UnderlyingAsset == SpotSymbol && o.ExpiryDate == expiryDate && o.OptionType == optionType && o.StrikePrice == strikePrice); decimal orderQuantity = 0m; if (syntheticOption == null) { decimal openQuantity = Math.Round( positionForWritingOption / 100, GetDecimalPrecision(optionMinUnit) ); orderQuantity = Math.Min(openQuantity, -Math.Round(optionMinUnit, GetDecimalPrecision(optionMinUnit))); syntheticOption = new SyntheticOption(SpotSymbol, orderQuantity, strikePrice, expiryDate, optionType); Debug($"Creating new synthetic option for {SpotSymbol}: Order Position = {orderQuantity}, StrikePrice = {strikePrice}, ExpiryDate = {expiryDate}, OptionType = {optionType}"); SyntheticOptions.Add(syntheticOption); var centralUnit = optionRatio * (Portfolio.TotalPortfolioValue + 20000) / 100; optionsCenterList.Add(centralUnit); } else { decimal quantityChange = Math.Round( positionForWritingOption / 100, GetDecimalPrecision(optionMinUnit) ); orderQuantity = Math.Min(quantityChange, -Math.Round(optionMinUnit, GetDecimalPrecision(optionMinUnit))); syntheticOption.UpdateQuantity(orderQuantity); Debug($"Updating synthetic option for {SpotSymbol}: StrikePrice = {strikePrice}, ExpiryDate = {expiryDate}, Quantity Change = {orderQuantity}, Current Quantity = {syntheticOption.Quantity}"); var centralUnit = optionRatio * (Portfolio.TotalPortfolioValue + 20000) / 100; optionsCenterList.Add(centralUnit); } return true; } protected OrderTicket MarketOrderMessage( Symbol symbol, decimal quantity, string message) { string symbolStr = symbol.Value; if ( Securities[symbolStr].Price == 0 ) { return null; } if ( BinanceFutureTraderDictionary.ContainsKey( symbolStr ) ) { if ( BinanceFutureTraderDictionary[symbolStr].Status != FutureStatus.Traded ) { return null; } } var trade_P = Securities[symbolStr].Price; // 永续持仓不超过净值的杠杆倍数 if (Securities[FutureSymbol].Holdings.Quantity * trade_P > optionRatio * (Portfolio.TotalPortfolioValue + 20000) && quantity > 0) { return null; } BinanceFutureTrader FutureTrader = new BinanceFutureTrader( symbol, quantity, trade_P, Time, Transactions ); BinanceFutureTraderDictionary[symbolStr] = FutureTrader; var order = FutureTrader.StartTrade( (symbol, quantity, tag, orderProperties) => MarketOrder(symbol, quantity, tag: tag, orderProperties: orderProperties), message ); return order; } public override void OnOrderEvent( OrderEvent orderEvent ) { string symbolStr = orderEvent.Symbol.Value; if( BinanceFutureTraderDictionary.ContainsKey( orderEvent.Symbol.Value ) == false ) { if(orderEvent.OrderId == lastOrderId) { return; } else { return; } } BinanceFutureTrader FutureTrader = BinanceFutureTraderDictionary[symbolStr]; FutureTrader.OnOrderEvent( orderEvent ); var order = Transactions.GetOrderById((int)FutureTrader.OrderId); if (order != null) { var message1 = string.Empty; if (!string.IsNullOrEmpty(order.Tag)) { var splitParts = order.Tag.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries); message1 = splitParts.Length > 0 ? splitParts[0].Trim() : string.Empty; } if( FutureTrader.Status == FutureStatus.Traded ) { BinanceFutureTraderDictionary.Remove( symbolStr ); if ( message1 == "渐进中心对冲" && optionsCenterList.Count > 0) { UpdateOptionCenterList(Math.Abs(orderEvent.FillQuantity * orderEvent.FillPrice)); } } if(FutureTrader.Status == FutureStatus.PartiallyFilled) { lastOrderId = orderEvent.OrderId; if ( message1 == "渐进中心对冲" && optionsCenterList.Count > 0) { UpdateOptionCenterList(Math.Abs(orderEvent.FillQuantity * orderEvent.FillPrice)); } } if(FutureTrader.Status == FutureStatus.Canceled) { BinanceFutureTraderDictionary.Remove( symbolStr ); } if(FutureTrader.Status == FutureStatus.Invalid) { BinanceFutureTraderDictionary.Remove( symbolStr ); } } } } }
#region imports using System; using System.Collections.Generic; #endregion namespace QuantConnect.Algorithm.CSharp.PricingModels { /// <summary> /// This example demonstrates how to add options for a given underlying equity security. /// It also shows how you can prefilter contracts easily based on strikes and expirations, and how you /// can inspect the option chain to pick a specific option contract to trade. /// </summary> /// <meta name="tag" content="using data" /> /// <meta name="tag" content="options" /> /// <meta name="tag" content="filter selection" /> public static class BSModel { //S:标的资产现价 //X:执行价 //r:无风险利率 //q:连续分红率,Cost of Carry = r-q //sigma:波动率 //t:距离到期时间 //PutCall:Call/Put public enum EPutCall { Call, Put, } public static EPutCall PutCall { get; set; } public static double GetOptionValue(double S, double X, double q, double r, double sigma, double t, EPutCall PutCall) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); double d2 = d1 - t_sqrt * sigma; switch (PutCall) { case EPutCall.Call: return S * Math.Exp(-q * t) * N(d1) - X * Math.Exp(-r * t) * N(d2); case EPutCall.Put: return -S * Math.Exp(-q * t) * N(-d1) + X * Math.Exp(-r * t) * N(-d2); default: return 0.0; } } // provide old delta if using delta decay public static double GetDelta(double S, double X, double q, double r, double sigma, double t, EPutCall PutCall) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); switch (PutCall) { case EPutCall.Call: return Math.Exp(-q * t) * N(d1); case EPutCall.Put: return -Math.Exp(-q * t) * N(-d1); default: return 0.0; } } public static double GetGamma(double S, double X, double q, double r, double sigma, double t) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); return Math.Exp(-q * t) * n(d1) / S / t_sqrt / sigma; } public static Dictionary<string, decimal> ZakDelta(double S, double X, double r, double sigma, double t, double cost, double risk, EPutCall PutCall) { double K = -4.76 * Math.Pow(cost, 0.78) / Math.Pow(t, 0.02) * Math.Pow(Math.Exp(-r * t) / sigma, 0.25) * Math.Pow(risk * S * S * Math.Abs(GetGamma(S, X, 0, r, sigma, t)), 0.15); double sigma_m = sigma * Math.Sqrt(1 + K); double H0 = cost / (risk * S * sigma * sigma * t); double H1 = 1.12 * Math.Pow(cost, 0.31) * Math.Pow(t, 0.05) * Math.Pow(Math.Exp(-r * t) / sigma, 0.25) * Math.Pow(Math.Abs(GetGamma(S, X, 0, r, sigma, t)) / risk, 0.5); decimal delta_up = Convert.ToDecimal(GetDelta(S, X, 0, r, sigma_m, t, PutCall) + H1 + H0); decimal delta_down = Convert.ToDecimal(GetDelta(S, X, 0, r, sigma_m, t, PutCall) - H1 - H0); decimal delta = Convert.ToDecimal(GetDelta(S, X, 0, r, sigma_m, t, PutCall)); Dictionary<string, decimal> ret = new Dictionary<string, decimal>(); ret.Add("delta_up", delta_up); ret.Add("delta_down", delta_down); ret.Add("delta", delta); return ret; } public static Dictionary<string, decimal> ZakDeltaMulti(double S, double r, double sigma, double t, double cost, double risk, double delta_multi, double gamma_multi) { double K = -4.76 * Math.Pow(cost, 0.78) / Math.Pow(t, 0.02) * Math.Pow(Math.Exp(-r * t) / sigma, 0.25) * Math.Pow(risk * S * S * Math.Abs(gamma_multi), 0.15); double sigma_m = sigma * Math.Sqrt(1 + K); double H0 = cost / (risk * S * sigma * sigma * t); double H1 = 1.12 * Math.Pow(cost, 0.31) * Math.Pow(t, 0.05) * Math.Pow(Math.Exp(-r * t) / sigma, 0.25) * Math.Pow(Math.Abs(gamma_multi) / risk, 0.5); decimal delta_up = Convert.ToDecimal(delta_multi + H1 + H0); decimal delta_down = Convert.ToDecimal(delta_multi - H1 - H0); decimal delta = Convert.ToDecimal(delta_multi); Dictionary<string, decimal> ret = new Dictionary<string, decimal>(); ret.Add("delta_up", delta_up); ret.Add("delta_down", delta_down); ret.Add("delta", delta); return ret; } public static double GetTheta(double S, double X, double q, double r, double sigma, double t, EPutCall PutCall) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); double d2 = d1 - t_sqrt * sigma; double part1 = S * sigma * Math.Exp(-q * t) * n(d1) / 2.0 / t_sqrt; double part2 = -q * S * Math.Exp(-q * t); double part3 = r * X * Math.Exp(-r * t); switch (PutCall) { case EPutCall.Call: return -part1 - part2 * N(d1) - part3 * N(d2); case EPutCall.Put: return -part1 + part2 * N(-d1) + part3 * N(-d2); default: return 0.0; } } public static double GetVega(double S, double X, double q, double r, double sigma, double t) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); return S * Math.Exp(-q * t) * n(d1) * t_sqrt; } public static double GetRho(double S, double X, double q, double r, double sigma, double t, EPutCall PutCall) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); double d2 = d1 - t_sqrt * sigma; switch (PutCall) { case EPutCall.Call: return t * X * Math.Exp(-r * t) * N(d2); case EPutCall.Put: return -t * X * Math.Exp(-r * t) * N(-d2); default: return 0.0; } } public static double GetVanna(double S, double X, double q, double r, double sigma, double t) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); double d2 = d1 - t_sqrt * sigma; double v = GetVega( S, X, q, r, sigma, t); return v/S * (1 - d1/sigma/t_sqrt); } public static double GetCharm(double S, double X, double q, double r, double sigma, double t, EPutCall PutCall) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); double d2 = d1 - t_sqrt * sigma; double part1 = -q * Math.Exp(-q * t) * N(d1); double part1_2 = q * Math.Exp(-q * t) * N(-d1); double part2 = Math.Exp(-q * t) * n(d1) ; double part3 = 2 * (r-q) * t - d2 * sigma * t_sqrt; double part4 = 2 * t * sigma * t_sqrt; switch (PutCall) { case EPutCall.Call: return part1 + part2 * part3 / part4; case EPutCall.Put: return part1_2 + part2 * part3 / part4; default: return 0.0; } } public static double GetSpeed(double S, double X, double q, double r, double sigma, double t) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); double d2 = d1 - t_sqrt * sigma; double gamma = GetGamma( S, X, q, r, sigma, t); return -gamma/S * (d1/sigma/t_sqrt + 1); } public static double GetZomma(double S, double X, double q, double r, double sigma, double t) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); double d2 = d1 - t_sqrt * sigma; double gamma = GetGamma( S, X, q, r, sigma, t); return gamma * ((d1 * d2) - 1)/ sigma; } public static double GetColor(double S, double X, double q, double r, double sigma, double t) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); double d2 = d1 - t_sqrt * sigma; double part1 = -Math.Exp(-q * t) * n(d1); double part2 = 2 * S * t * sigma * t_sqrt ; double part3 = 2 * q * t + 1; double part4 = 2 * (r - q) * t - d2 * sigma * t_sqrt; double part5 = d1 / sigma / t_sqrt; return part1 / part2 * (part3 + part4 * part5); } public static double GetDvegaDtime(double S, double X, double q, double r, double sigma, double t) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); double d2 = d1 - t_sqrt * sigma; double part1 = S * Math.Exp(-q * t) * n(d1) * t_sqrt; double part2 = q; double part3 = (r - q) * d1 / sigma / t_sqrt; double part4 = - (1 + d1 * d2) / t_sqrt /2 ; return part1 * (part2 + part3 + part4); } public static double GetVomma(double S, double X, double q, double r, double sigma, double t) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); double d2 = d1 - t_sqrt * sigma; double v = GetVega( S, X, q, r, sigma, t); return v* d1 * d2 /sigma; } public static double GetDualDelta(double S, double X, double q, double r, double sigma, double t, EPutCall PutCall) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); double d2 = d1 - t_sqrt * sigma; switch (PutCall) { case EPutCall.Call: return -Math.Exp(-r * t) * N(d2); case EPutCall.Put: return Math.Exp(-r * t) * N(-d2); default: return 0.0; } } public static double GetDualGamma(double S, double X, double q, double r, double sigma, double t) { double t_sqrt = Math.Sqrt(t); double sigma2 = sigma * sigma; double d1 = (Math.Log(S / X) + (r - q + sigma2 * 0.5) * t) / (t_sqrt * sigma); double d2 = d1 - t_sqrt * sigma; return -Math.Exp(-r * t) * n(d2) / X / sigma /t_sqrt; } public static double GetImpliedVol(double S, double X, double q, double r, double optionPrice, double t, EPutCall PutCall, double accuracy, int maxIterations) { if (optionPrice < 0.99 * (S - X * Math.Exp(-r * t))) return 0.0; double t_sqrt = Math.Sqrt(t); double sigma = optionPrice / S / 0.398 / t_sqrt; for (int i = 0; i < maxIterations; i++) { double price = GetOptionValue(S, X, q, r, sigma, t, PutCall); double diff = optionPrice - price; if (Math.Abs(diff) < accuracy) return sigma; double vega = GetVega(S, X, q, r, sigma, t); if (vega != 0) { sigma = sigma + diff / vega; } } if (sigma < 0 || sigma > 2 || double.IsNaN(sigma)) { return 0.0; } return sigma; } // 二分法求隐波 public static double GetImpliedVolBisection(double S, double X, double q, double r, double optionPrice, double t, EPutCall PutCall, double accuracy, int maxIterations) { double top = 20; double floor = 0; double sigma = 0.2; for (int i = 0; i < maxIterations; i++) { double price = GetOptionValue(S, X, q, r, sigma, t, PutCall); double diff = optionPrice - price; if (Math.Abs(diff) < accuracy) { return sigma; } else { if (diff > 0) { floor = sigma; sigma = (sigma + top) / 2; } else { top = sigma; sigma = (sigma + floor) / 2; } } } return sigma; } public static double N(double d) { /* //Bagby, R. J. "Calculating Normal Probabilities." Amer. Math. Monthly 102, 46-49, 1995 double part1 = 7.0*Math.Exp(-d*d/2.0); double part2 = 16.0*Math.Exp(-d*d*(2.0-Math.Sqrt(2.0))); double part3 = (7.0+Math.PI*d*d/4.0)*Math.Exp(-d*d); double cumProb = Math.Sqrt(1.0-(part1+part2+part3)/30.0)/2.0; if(d>0) cumProb = 0.5+cumProb; else cumProb = 0.5-cumProb; return cumProb; */ //出自Financial Numerical Recipes in C++ if (d > 6.0) return 1.0; else if (d < -6.0) return 0.0; double b1 = 0.31938153; double b2 = -0.356563782; double b3 = 1.781477937; double b4 = -1.821255978; double b5 = 1.330274429; double p = 0.2316419; double c2 = 0.3989423; double a = Math.Abs(d); double t = 1.0 / (1.0 + a * p); double b = c2 * Math.Exp((-d) * (d * 0.5)); double n = ((((b5 * t + b4) * t + b3) * t + b2) * t + b1) * t; n = 1.0 - b * n; if (d < 0) n = 1.0 - n; return n; } public static double n(double d) { return 1.0 / Math.Sqrt(2.0 * Math.PI) * Math.Exp(-d * d * 0.5); } } }
#region imports using System; using System.Collections.Generic; using System.Linq; using BSModel = QuantConnect.Algorithm.CSharp.PricingModels.BSModel; using QuantConnect.Algorithm.CSharp.qlnet.tools; using QuantConnect.Data.Market; using QuantConnect.Logging; using QuantConnect.Orders; using QuantConnect.Securities; using QuantConnect; using QuantConnect.Statistics; using QuantConnect.Parameters; using QuantConnect.Configuration; using QuantConnect.Securities.Option; using QuantConnect.Interfaces; #endregion namespace QuantConnect.Algorithm.CSharp.qlnet.tools { public class BinanceFutureTrader { public Symbol Future { get; set; } public decimal TradePrice { get; set; } = 0; public decimal TradeVolume { get; set; } = 0; public decimal TradedPrice { get; set; } = 0; public decimal TradedVolume { get; set; } = 0; public double SecondForCancelOrder { get; set; } = 30; public FutureStatus Status { get; set; } public long OrderId { get; set; } public DateTime CreateTime { get; set; } = DateTime.MinValue; public DateTime LastCheckTime { get; set; } = DateTime.MinValue; private decimal _tradedVolume; private OrderStatus _orderStatus; private int _timerCount; private bool _placeOrderFailed = false; private readonly SecurityTransactionManager _transactions; public BinanceFutureTrader(Symbol future, decimal tradeVolume, decimal tradePrice,DateTime Time, SecurityTransactionManager transactions,double secondForCancelOrder = 5) { Future = future; TradeVolume = tradeVolume; TradePrice = tradePrice; Status = FutureStatus.Created; _transactions = transactions; _orderStatus = OrderStatus.None; _timerCount = 0; CreateTime = Time; LastCheckTime = Time; SecondForCancelOrder = secondForCancelOrder; } public OrderTicket StartTrade(Func<Symbol, decimal, string, IOrderProperties, OrderTicket> marketOrder, string message1 = "") { // 使用新版 MarketOrder 方法创建市价单 var order = marketOrder(Future, TradeVolume, message1, null); OrderId = order.OrderId; // 更新 OrderId _orderStatus = order.Status; // 更新订单状态 if (OrderId <= 0) { _placeOrderFailed = true; Log.Trace("DeribitPerpetualTrader: Failed to place order"); var message = $"永续下单失败!\nOrderId: {OrderId}"; } else { _placeOrderFailed = false; } return order; } public string OnOrderEvent(OrderEvent orderEvent) { Status = FutureStatus.Created; var message = ""; _orderStatus = orderEvent.Status; if (orderEvent.Status == OrderStatus.Filled || orderEvent.Status == OrderStatus.PartiallyFilled) { TradedPrice = (_tradedVolume * TradedPrice + orderEvent.FillPrice * orderEvent.FillQuantity) / (_tradedVolume + orderEvent.FillQuantity); _tradedVolume += orderEvent.FillQuantity; } if (orderEvent.Status == OrderStatus.Filled) { Status = FutureStatus.Traded; message += $"该轮永续交易结束! 合约id: {Future}, 成交量: {_tradedVolume}"; //_orderMessage.SendMessage(message, _dingKey, _dingToken); return message; } if (orderEvent.Status == OrderStatus.Canceled) { Status = FutureStatus.Canceled; } if (orderEvent.Status == OrderStatus.PartiallyFilled) { Status = FutureStatus.PartiallyFilled; } if (orderEvent.Status == OrderStatus.Invalid) { Status = FutureStatus.Invalid; } return message; } public decimal GetTradedVolume() { return _tradedVolume; } } }
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Drawing; using QuantConnect; using QuantConnect.Algorithm.Framework; using QuantConnect.Algorithm.Framework.Selection; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Algorithm.Framework.Portfolio.SignalExports; using QuantConnect.Algorithm.Framework.Execution; using QuantConnect.Algorithm.Framework.Risk; using QuantConnect.Algorithm.Selection; using QuantConnect.Api; using QuantConnect.Parameters; using QuantConnect.Benchmarks; using QuantConnect.Brokerages; using QuantConnect.Configuration; using QuantConnect.Util; using QuantConnect.Interfaces; using QuantConnect.Algorithm; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Auxiliary; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Custom; using QuantConnect.Data.Custom.IconicTypes; using QuantConnect.DataSource; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.Shortable; using QuantConnect.Data.UniverseSelection; using QuantConnect.Notifications; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.OptionExercise; using QuantConnect.Orders.Slippage; using QuantConnect.Orders.TimeInForces; using QuantConnect.Python; using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Securities.Positions; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; using QuantConnect.Securities.CryptoFuture; using QuantConnect.Securities.Interfaces; using QuantConnect.Securities.Volatility; using QuantConnect.Storage; using QuantConnect.Statistics; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; #endregion namespace QuantConnect.Statistics { /// <summary> /// Direction of a trade /// </summary> public enum TradeDirection { /// <summary> /// Long direction /// </summary> Long, /// <summary> /// Short direction /// </summary> Short } /// <summary> /// The method used to group order fills into trades /// </summary> public enum FillGroupingMethod { /// <summary> /// A Trade is defined by a fill that establishes or increases a position and an offsetting fill that reduces the position size. /// </summary> FillToFill, /// <summary> /// A Trade is defined by a sequence of fills, from a flat position to a non-zero position which may increase or decrease in quantity, and back to a flat position. /// </summary> FlatToFlat, /// <summary> /// A Trade is defined by a sequence of fills, from a flat position to a non-zero position and an offsetting fill that reduces the position size. /// </summary> FlatToReduced, } /// <summary> /// The method used to match offsetting order fills /// </summary> public enum FillMatchingMethod { /// <summary> /// First In First Out fill matching method /// </summary> FIFO, /// <summary> /// Last In Last Out fill matching method /// </summary> LIFO } public enum OptionStatus { Created, Traded, Hedging, Canceled, PartiallyFilled, } public enum FutureStatus { Created, Traded, Hedging, Canceled, PartiallyFilled, Invalid, } public enum ETFStatus { Created, Traded, Hedging, Canceled, PartiallyFilled } public enum MinOptionCostMode { None, TimeDecay, BS } public enum PositionAllocationMode { None, Average, FirstExpiry, Combined } public enum SpotRebalanceFrequency { None, Daily, Weekly, Monthly, Quarterly, Annually } public enum HedgeVolatilityChoice { EWMA1HourHistoricalVol, ImpliedVol } }