Overall Statistics |
Total Trades 18 Average Win 0.14% Average Loss -0.16% Compounding Annual Return 2.766% Drawdown 0.200% Expectancy 0.472 Net Profit 0.688% Sharpe Ratio 2.195 Probabilistic Sharpe Ratio 80.776% Loss Rate 22% Win Rate 78% Profit-Loss Ratio 0.89 Alpha 0.021 Beta -0.006 Annual Standard Deviation 0.009 Annual Variance 0 Information Ratio -2.757 Tracking Error 0.08 Treynor Ratio -3.483 Total Fees $0.00 Estimated Strategy Capacity $0 Lowest Capacity Asset ZC XUBT0M6O6LNP Portfolio Turnover 5.56% |
#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.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.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 QuantConnect.Data.Custom.AlphaStreams; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; #endregion namespace QuantConnect.Algorithm.CSharp { public class YourAlgorithm : QCAlgorithm { DateTime startDate = new DateTime(2021, 6, 1); DateTime endDate = new DateTime(2021, 8, 31); //SetWarmUp(5, Resolution.Tick); //SetWarmUp(TimeSpan.FromDays(5), Resolution.Tick); Future future_contract; bool isWarmedup = false; List<string> futuresContracts = new List<string> { Futures.Grains.Corn }; Dictionary<string, Future> futureSecurities = new Dictionary<string, Future>(); //Dictionary<string, TimeSpan> customCloseTimes = new Dictionary<string, TimeSpan> { { Symbols.FutureGrainsCorn, TimeSpan.Zero } }; //Dictionary<string, (decimal, decimal)> futuresVolatility = new Dictionary<string, (decimal, decimal)> { {Symbols.FutureGrainsCorn, (0.0074m, 0.0106m)} }; decimal lastValidBidSize = 0m, lastValidAskSize = 0m; public static decimal lastValidAskPrice = 0m; public static decimal lastValidBidPrice = 0m; decimal priceMagnifier; decimal contractMultiplier; int minute_period = 120; decimal lowest_low = 0m; decimal highest_high = 0m; bool isFirstTick = true; LinkedList<decimal> rollingPrices = new LinkedList<decimal>(); LinkedList<decimal> rollingVolumes = new LinkedList<decimal>(); LinkedList<DateTime> rollingTimeStamps = new LinkedList<DateTime>(); decimal averageVolUpdate = 0m; DateTime longCooldown = DateTime.MinValue; DateTime shortCooldown = DateTime.MinValue; DateTime startTimeRollingWindow = DateTime.MinValue; bool volume_box_ready = false; bool totalVolumeLogged = false; bool volumeBoxReadyLogged = false; DateTime lastCalculated = DateTime.MinValue; DateTime lastCheckedDate = DateTime.MinValue; DateTime loopStart = DateTime.MinValue; DateTime exchangeOpenTime = DateTime.MinValue; // Add this line DateTime exchangeCloseTime = DateTime.MinValue; decimal maxPrice = decimal.MinValue; decimal minPrice = decimal.MaxValue; decimal maxVolumeBoxPrice = decimal.MaxValue; decimal minVolumeBoxPrice = decimal.MinValue; decimal totalVolume = 0; decimal currentTotalVolume = 0; bool printonce = false; List<decimal> volatility = new List<decimal>(); Dictionary<int, DateTime> positionCreationTimes = new Dictionary<int, DateTime>(); Dictionary<int, decimal> positionPnLs = new Dictionary<int, decimal>(); decimal contractMultiplier2 = 5000.0m; decimal contractMultiplier3 = 5000.0m; bool isFirstOrderOfTheDay = false; decimal highestLastValidAskPrice = 0m; public override void Initialize() { SetStartDate(startDate); SetEndDate(endDate); SetWarmUp(TimeSpan.FromDays(5), Resolution.Tick); future_contract = AddFuture(Futures.Grains.Corn, Resolution.Tick, extendedMarketHours: false, dataNormalizationMode: DataNormalizationMode.Raw, dataMappingMode: DataMappingMode.OpenInterest, contractDepthOffset: 0); SetSecurityInitializer(CustomSecurityInitializer); contractMultiplier = Securities[future_contract.Symbol].SymbolProperties.ContractMultiplier; //contractMultiplier2 = Securities[future_contract.Symbol].SymbolProperties.ContractMultiplier; //contractMultiplier3 = Securities[future_contract.Symbol].SymbolProperties.ContractMultiplier; //future_contract.SetFillModel(new BestBidAskFillModel()); //foreach (var contract in futuresContracts) //{ // var future = AddFuture(contract, Resolution.Tick, extendedMarketHours: false, dataNormalizationMode: DataNormalizationMode.Raw, dataMappingMode: DataMappingMode.OpenInterest, contractDepthOffset: 0); // Set the same fill model for the contract future as you did for the continuous future // future.SetFillModel(new BestBidAskFillModel()); //futureSecurities[future.Symbol] = future; //} } public override void OnData(Slice data) { DateTime sliceDate = Time.Date; DateTime targetDate = new DateTime(2021, 8, 2); //foreach (var symbol in futureSecurities.Keys) if (sliceDate > DateTime.MinValue) { //if (!data.Ticks.ContainsKey(future_contract.Symbol)) continue; //if (!data.Ticks.ContainsKey(symbol)) continue; if (sliceDate != lastCheckedDate) // Assuming lastCheckedDate is a DateTime variable declared at class level to track the last checked date { ResetVariables(); var future = future_contract; var exchangeHours = future.Exchange.Hours; var exchangeOpenTimeSpan = exchangeHours.MarketHours[sliceDate.DayOfWeek].GetMarketOpen(TimeSpan.Zero, false); // Get exchange close time var exchangeCloseTimeSpan = exchangeHours.MarketHours[sliceDate.DayOfWeek].GetMarketClose(TimeSpan.Zero, false); exchangeOpenTime = sliceDate.Date + exchangeOpenTimeSpan.GetValueOrDefault(); exchangeCloseTime = sliceDate.Date + exchangeCloseTimeSpan.GetValueOrDefault(); loopStart = exchangeOpenTime; lastCheckedDate = sliceDate; //var contractMultiplier2 = Securities[future.Symbol].SymbolProperties.ContractMultiplier; //var contractMultiplier3 = 5000.0m; //Debug($"contact multi {contractMultiplier}"); //Debug($"contact multi {contractMultiplier2}"); //Debug($"contact multi {contractMultiplier3}"); } foreach (var ticks in data.Ticks.Values) { foreach (var tick in ticks) { if (tick.Suspicious) continue; if (tick.TickType == TickType.Quote) { if (tick.BidPrice != 0) lastValidBidPrice = tick.BidPrice; if (tick.AskPrice != 0) lastValidAskPrice = tick.AskPrice; if (tick.BidSize != 0) lastValidBidSize = tick.BidSize; if (tick.AskSize != 0) lastValidAskSize = tick.AskSize; if (lastValidAskPrice > highestLastValidAskPrice && isFirstOrderOfTheDay == true && sliceDate == targetDate) { highestLastValidAskPrice = lastValidAskPrice; //Debug($"new highest {highestLastValidAskPrice}"); } foreach (var holding in Portfolio.Values) { if (holding.Invested) { var positionId = holding.Symbol.GetHashCode(); //var pnl = holding.UnrealizedProfitPercent; //positionPnLs[positionId] = pnl; decimal pnl; if(holding.Quantity > 0) { pnl = ((lastValidBidPrice - holding.AveragePrice) / holding.AveragePrice); } else { pnl = ((lastValidAskPrice - holding.AveragePrice) / holding.AveragePrice) *-1; } // Close position based on PnL conditions if (pnl >= 0.0028m ) { var qty = -Portfolio[holding.Symbol].Quantity; if(qty > 0) { MarketOrder(holding.Symbol, qty, false, $"0.28% take profit hit ask price: {lastValidAskPrice}, entry {holding.AveragePrice}, profit {((lastValidAskPrice - holding.AveragePrice) *-1)}"); } else { MarketOrder(holding.Symbol, qty, false, $"0.28% take profit hit bid price: {lastValidBidPrice}, entry {holding.AveragePrice}, profit {(lastValidBidPrice - holding.AveragePrice) * contractMultiplier2}"); } positionPnLs.Remove(positionId); positionCreationTimes.Remove(positionId); } if (pnl <= -0.006m) { var qty = -Portfolio[holding.Symbol].Quantity; if(qty > 0) { MarketOrder(holding.Symbol, qty, false, $"0.6% stop loss hit ask price: {lastValidAskPrice}, entry {holding.AveragePrice}, profit {((lastValidAskPrice - holding.AveragePrice) *-1) * contractMultiplier2}"); } else { MarketOrder(holding.Symbol, qty, false, $"0.6% stop loss hit bid price: {lastValidBidPrice}, entry {holding.AveragePrice}, profit {((lastValidBidPrice - holding.AveragePrice) * contractMultiplier2)}"); } positionPnLs.Remove(positionId); positionCreationTimes.Remove(positionId); } // Close position if it's been open for 2 hours or more if (positionCreationTimes.ContainsKey(positionId) && Time - positionCreationTimes[positionId] >= TimeSpan.FromHours(2)) { var qty = -Portfolio[holding.Symbol].Quantity; //MarketOrder(holding.Symbol, qty, false, $"Time limit reached, pnl {pnl}"); if(qty > 0) { MarketOrder(holding.Symbol, qty, false, $"Time limit reached ask price: {lastValidAskPrice}, entry {holding.AveragePrice}, profit {((lastValidAskPrice - holding.AveragePrice) *-1) * contractMultiplier2}"); } else { MarketOrder(holding.Symbol, qty, false, $"Time limit reached bid price: {lastValidBidPrice}, entry {holding.AveragePrice}, profit {(lastValidBidPrice - holding.AveragePrice) * contractMultiplier2}"); } positionPnLs.Remove(positionId); positionCreationTimes.Remove(positionId); } } } } if (tick.TickType == TickType.Trade) { if (isWarmedup) { DateTime cutOffTime = tick.Time.AddMinutes(-30); while (rollingTimeStamps.Count > 0 && rollingTimeStamps.First.Value <= cutOffTime) { rollingPrices.RemoveFirst(); rollingVolumes.RemoveFirst(); rollingTimeStamps.RemoveFirst(); } rollingPrices.AddLast(tick.Price); rollingVolumes.AddLast(tick.Quantity); rollingTimeStamps.AddLast(tick.Time); if ((tick.Time - lastCalculated).TotalSeconds >= 5) { maxPrice = rollingPrices.Max(); minPrice = rollingPrices.Min(); currentTotalVolume = rollingVolumes.Sum(); if(minPrice == 0) { continue; } if ((maxPrice - minPrice) / minPrice <= (averageVolUpdate * 0.66m) && tick.Time >= exchangeOpenTime.AddMinutes(30)) { volume_box_ready = true; maxVolumeBoxPrice = maxPrice; minVolumeBoxPrice = minPrice; //Debug($"volume_box_ready"); } lastCalculated = tick.Time; } totalVolume = currentTotalVolume; if (volume_box_ready && tick.Time < exchangeCloseTime.AddMinutes(-30) ) { if (tick.Price > maxVolumeBoxPrice * (1.000m + (averageVolUpdate * 0.66m)) && tick.Time > longCooldown.AddMinutes(5)) { decimal averageVolatility = volatility.Any() ? volatility.Average() : 0m; //Debug($"order long "); //currentContract = self.Securities[continuousContract.Mapped] //MarketOrder(futureSecurities[symbol].Symbol, 1, false, ""); MarketOrder(future_contract.Mapped, 1, true, $"ask price: {lastValidAskPrice} bid price: {lastValidBidPrice}"); isFirstOrderOfTheDay = true; longCooldown = tick.Time; volume_box_ready = false; maxVolumeBoxPrice = decimal.MaxValue; minVolumeBoxPrice = decimal.MinValue; } else if (tick.Price < minVolumeBoxPrice * (1.0m - (averageVolUpdate * 0.66m)) && tick.Time > shortCooldown.AddMinutes(5)) { decimal averageVolatility = volatility.Any() ? volatility.Average() : 0; //MarketOrder(future_contract.Mapped, -1, true, $"ask price: {lastValidAskPrice} bid price: {lastValidBidPrice}"); //MarketOrder(futureSecurities[symbolKey].Symbol, -1, false, "", false); shortCooldown = tick.Time; //Console.WriteLine($"order short: {tick.Time}, price: {tick.Price}"); volume_box_ready = false; maxVolumeBoxPrice = decimal.MaxValue; minVolumeBoxPrice = decimal.MinValue; } } //end order logic // Close all positions 15 minutes before market close if (tick.Time >= exchangeCloseTime.AddMinutes(-15)) { foreach (var holding in Portfolio.Values) { if (holding.Invested) { var qty = -holding.Quantity; //MarketOrder(holding.Symbol, qty, false, "Market close"); if(qty > 0) { MarketOrder(holding.Symbol, qty, false, $"market closing ask price: {lastValidAskPrice}, entry {holding.AveragePrice}, profit {((lastValidAskPrice - holding.AveragePrice) *-1) * contractMultiplier2}"); } else { MarketOrder(holding.Symbol, qty, false, $"market closing bid price: {lastValidBidPrice}, entry {holding.AveragePrice}, profit {(lastValidBidPrice - holding.AveragePrice) * contractMultiplier2}"); } } } positionPnLs.Clear(); positionCreationTimes.Clear(); } } if (isFirstTick && lastValidBidPrice != 0m && lastValidAskPrice != 0m) { lowest_low = lastValidBidPrice; highest_high = lastValidAskPrice; loopStart = tick.Time; isFirstTick = false; } // Compare and update lowest_low and highest_high if (tick.Price < lowest_low) { lowest_low = tick.Price; } if (tick.Price > highest_high) { highest_high = tick.Price; } // Check if an hour has passed since loop start, if yes calculate the volatility and reset // start vol if ((tick.Time - loopStart).TotalHours >= 1) { //Console.WriteLine($"lowest_low {lowest_low}"); //Debug($"tick time {tick.Time} market open {exchangeOpenTime}, best bid {lastValidBidPrice}, best ask {lastValidAskPrice}, is first tick {isFirstTick}"); decimal price_diff_percentage = ((highest_high - lowest_low) / lowest_low); // Manage volatility list size if (volatility.Count >= 20) { volatility.RemoveAt(0); } volatility.Add(price_diff_percentage); //string volatilityCsv = string.Join(",", volatility); //Console.WriteLine(volatilityCsv); // Reset lowest_low and highest_high for the next hour lowest_low = lastValidBidPrice; highest_high = lastValidAskPrice; loopStart = tick.Time; averageVolUpdate = volatility.Average(); //Console.WriteLine($"volatility: {price_diff_percentage}"); } } } } } } public override void OnWarmupFinished() { Log("Algorithm Ready"); isWarmedup = true; } public override void OnOrderEvent(OrderEvent orderEvent) { if (orderEvent.Status == OrderStatus.Filled) { var positionId = Securities[orderEvent.Symbol].Holdings.Symbol.GetHashCode(); positionCreationTimes[positionId] = Time; } } private void CustomSecurityInitializer(Security security) { security.SetFeeModel(new ConstantFeeModel(0.0m)); security.SetSlippageModel(new ConstantSlippageModel(0.0m)); security.SetFillModel(new BestBidAskFillModel()); // Add other models as necessary } private void ResetVariables() { lastValidBidPrice = 0m; lastValidAskPrice = 0m; lastValidBidSize = 0m; lastValidAskSize = 0m; priceMagnifier = default; contractMultiplier = default; lowest_low = 0m; highest_high = 0m; isFirstTick = true; rollingPrices.Clear(); rollingVolumes.Clear(); rollingTimeStamps.Clear(); averageVolUpdate = 0m; longCooldown = DateTime.MinValue; shortCooldown = DateTime.MinValue; startTimeRollingWindow = DateTime.MinValue; volume_box_ready = false; totalVolumeLogged = false; volumeBoxReadyLogged = false; lastCalculated = DateTime.MinValue; loopStart = DateTime.MinValue; exchangeOpenTime = DateTime.MinValue; exchangeCloseTime = DateTime.MinValue; maxPrice = decimal.MinValue; minPrice = decimal.MaxValue; maxVolumeBoxPrice = decimal.MaxValue; minVolumeBoxPrice = decimal.MinValue; totalVolume = 0; currentTotalVolume = 0; positionCreationTimes.Clear(); positionPnLs.Clear(); } public class BestBidAskFillModel : ImmediateFillModel { public override OrderEvent MarketFill(Security asset, MarketOrder order) { var fill = base.MarketFill(asset, order); var lastData = asset.GetLastData(); var tick = lastData as Tick; if (tick != null) { if (order.Direction == OrderDirection.Buy) { fill.FillPrice = YourAlgorithm.lastValidAskPrice != 0 ? YourAlgorithm.lastValidAskPrice : fill.FillPrice; } else { fill.FillPrice = YourAlgorithm.lastValidBidPrice != 0 ? YourAlgorithm.lastValidBidPrice : fill.FillPrice; } } return fill; } } } }
Notebook too long to render.