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