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
    }
 
}