Overall Statistics
Total Orders
2746
Average Win
0.01%
Average Loss
-0.02%
Compounding Annual Return
-7.314%
Drawdown
18.600%
Expectancy
-0.576
Start Equity
10000000
End Equity
8634388.66
Net Profit
-13.656%
Sharpe Ratio
-1.465
Sortino Ratio
-0.349
Probabilistic Sharpe Ratio
0.083%
Loss Rate
75%
Win Rate
25%
Profit-Loss Ratio
0.67
Alpha
-0.1
Beta
-0.017
Annual Standard Deviation
0.07
Annual Variance
0.005
Information Ratio
-1.922
Tracking Error
0.128
Treynor Ratio
6.118
Total Fees
$21748.35
Estimated Strategy Capacity
$5700000.00
Lowest Capacity Asset
GF YXMQEYU84SG1
Portfolio Turnover
5.71%
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Securities.Future;
using QuantConnect.Securities;

using System;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Globalization;

namespace QuantConnect.Algorithm.CSharp
{
    public class FuturesTradeBacktestAlgorithm : QCAlgorithm
    {
        private List<Order> orders = new List<Order>();
        private DateTime nextOrderTime;
        private Dictionary<string, Symbol> futureSymbols = new Dictionary<string, Symbol>();

        public override void Initialize()
        {
            // Set backtest start and end dates
            SetStartDate(2023, 1, 1);
            SetEndDate(2025, 12, 31);

            // Set cash
            SetCash(10000000);

            // Add futures contracts dynamically during backtesting
            UniverseSettings.Resolution = Resolution.Minute;

            // var f = AddFuture(QuantConnect.Securities.Futures.Meats.LeanHogs);

            // Debug($"Mapped:{f.Mapped} Market:{f.Symbol.ID.Market}");

            // var fContracts = FutureChainProvider.GetFutureContractList(f.Symbol, Time);

            // foreach (var contract in fContracts)
            // {
            //     Debug($"Future Symbol:{contract.ID.Symbol} Expiry:{contract.ID.Date}");
            // }

            // Read trades from CSV
            orders = LoadTradesFromObjectStore("TT Trades 2024-12-02 17_03_39.csv");

            var uniqueSymbolsPair = orders
                .Select(o => new { Symbol = o.Symbol, Exchange = o.Exchange, Year = o.ContractYear, Month = o.ContractMonth, Key = o.Key })
                .Distinct()
                .ToList();

            foreach (var symbolPair in uniqueSymbolsPair)
            {
                Debug($"Creating future symbol for {symbolPair.Key}");
                var futureSymbol = CreateFutureSymbol(symbolPair.Symbol, symbolPair.Exchange, symbolPair.Year, symbolPair.Month);
                if (futureSymbol != null)
                {
                    futureSymbols[symbolPair.Key] = futureSymbol;
                }
            }

            if (orders.Any())
            {
                nextOrderTime = orders[0].TradeDate;
                Debug($"nextOrderTime {nextOrderTime.ToString()}");
            }
        }

        private Symbol CreateFutureSymbol(string underlying, string market, int year, int month)
        {
            var futureSymbol = AddFuture(underlying, market:market).Symbol;

            var contracts = FutureChainProvider.GetFutureContractList(futureSymbol, new DateTime(year, month, 1));

            // Find the contract that expires in the specified year and month
            var targetContract = contracts
                .Where(c => c.ID.Date.Year == year && c.ID.Date.Month == month)
                .OrderBy(c => c.ID.Date)
                .FirstOrDefault();

            if (targetContract == null)
            {
                Error($"No future contract found for {underlying} in {year}-{month:D2}");
                return targetContract;
            }

            Debug($"Future contract {market}.{underlying}@{year}.{month}={targetContract.Value}");

            AddFutureContract(targetContract);

            return targetContract;
        }

        public override void OnData(Slice data)
        {
            if (!orders.Any() || Time < nextOrderTime)
            {
                Debug(orders.Any() ? "" : "No orders remaining");
                return;
            }

            var currentOrders = orders.TakeWhile(o => o.TradeDate == nextOrderTime).ToList();
            // Debug($"Found {currentOrders.Count} orders to be executed at {Time}");

            foreach (var order in currentOrders)
            {
                ExecuteOrder(order);
            }

            UpdateOrdersAndNextTime(currentOrders);
        }

        private void ExecuteOrder(Order order)
        {
            if (!futureSymbols.TryGetValue(order.Key, out Symbol futureSymbol))
            {
                Error($"Symbol not found: {order.Symbol} in futureSymbols");
                return;
            }

            if (!Securities.ContainsKey(futureSymbol))
            {
                Error($"Symbol {futureSymbol} {futureSymbol.Value} not found in Securities collection. Available symbols: {getSecurities()}");
                return;
            }

            var future = Securities[futureSymbol];
            if (future == null)
            {
                Error($"Symbol {futureSymbol.Value} not found in securities");
                return;
            }

            if (!future.IsTradable)
            {
                Error($"Symbol {order.Symbol} is not tradable");
                return;
            }

            switch (order.Action)
            {
                case "b":
                    // Debug($"Buying {order.Quantity} {order.Key} [{future.Symbol.Value}] at {Time}");
                    MarketOrder(future.Symbol, order.Quantity);
                    Debug($"B {order.Quantity} {order.Key} at {Time}");
                    break;
                case "s":
                    // Debug($"Selling {order.Quantity} {order.Key} [{future.Symbol.Value}] at {Time}");
                    MarketOrder(future.Symbol, -order.Quantity);
                    Debug($"S {order.Quantity} {order.Key} at {Time}");
                    break;
                default:
                    Error($"Unknown action {order.Action}, {order.Quantity} {order.Key} at {Time}");
                    break;
            }
        }

        private void UpdateOrdersAndNextTime(List<Order> executedOrders)
        {
            orders = orders.Skip(executedOrders.Count).ToList();
            if (orders.Any())
            {
                nextOrderTime = orders[0].TradeDate;
                // Debug($"nextOrderTime {nextOrderTime}");
            }
        }

        public override void OnEndOfAlgorithm()
        {
            Debug($"Final Portfolio Value: {Portfolio.TotalPortfolioValue}");
        }

        private List<Order> LoadTradesFromObjectStore(string fileName)
        {
            if (!ObjectStore.ContainsKey(fileName))
            {
                throw new Exception($"File {fileName} not found in Object Store.");
            }

            var orders = new List<Order>();

            var csvContent = ObjectStore.ReadBytes(fileName);
            var csvText = System.Text.Encoding.UTF8.GetString(csvContent);
            var lines = csvText.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

            string pattern = @"^\s*[A-Z]+\s+[A-Z][a-z]{2}\d{2}\s*$";

            // Skip the header row
            for (int i = 1; i < lines.Length; i++)
            {
                var columns = lines[i].Split(',');
                if (columns.Length >= 8) // Ensure the line has enough fields
                {

                    var fullContract = columns[3].Trim();
                    if (!Regex.IsMatch(fullContract, pattern))
                    {
                        continue;
                    }

                    var symbolName = fullContract.SafeSubstring(0, 2);
                    var contractMonth = DateTime.ParseExact(fullContract.SafeSubstring(3, 3), "MMM", CultureInfo.InvariantCulture).Month;
                    int.TryParse($"20{fullContract.SafeSubstring(6, 2)}", out int contractYear);
                    var trade = new Order
                    {
                        Exchange = Market.CME, //columns[2].Trim(),
                        Symbol = symbolName, // Future Symbol
                        ContractMonth = contractMonth,
                        ContractYear = contractYear,

                        Action = columns[4].ToLower().Trim(), // Buy or Sell
                        Quantity = int.Parse(columns[5].Trim()), // Quantity
                        Price = decimal.Parse(columns[6].Trim()), // Trade price

                        TradeDate = DateTime.Parse($"{columns[0].Trim()}T{columns[1].Trim()}Z"), // Date of trade
                    };
                    orders.Add(trade);
                }
            }

            orders = orders
            // .Where(o => o.Symbol != "ZC")
            // .Where(o => o.Symbol != "ZM")
            // .Take(10)
            // .Where(o => o.Symbol != "LE")
            .ToList();

            orders.Sort((x, y) => x.TradeDate.CompareTo(y.TradeDate));
            // Debug($"{orders[1]}");

            Debug($"{orders.Count} Orders to be executed, total data in the file ${lines.Length - 1}");

            return orders;
        }

        private string getSecurities()
        {
            return string.Join(", ", Securities.Keys);
        }
    }

}
using System;

namespace QuantConnect.Algorithm.CSharp
{
    public class Order
    {
        public DateTime TradeDate { get; set; }
        public string Exchange { get; set; }

        public string Contract { get; set; }
        public string Symbol { get; set; }
        public int ContractMonth { get; set; }
        public int ContractYear { get; set; }

        public string Key {
            get {
                return $"{Exchange}.{Symbol}@{ContractYear}.{ContractMonth:D2}";
            }
        }

        public string Action { get; set; }
        public int Quantity { get; set; }
        public decimal Price { get; set; }
        public string PF { get; set; }
        public string Type { get; set; }
        public string Route { get; set; }
        public string Account { get; set; }
        public string Originator { get; set; }
        public string CurrentUser { get; set; }
        public Guid TTOrderID { get; set; }
        public string ParentID { get; set; }
        public string ManualFill { get; set; }

        public override string ToString()
        {
            return $"S:{Symbol} {ContractYear} {ContractMonth} Q:{Quantity} A:{Action} P:{Price} D:{TradeDate.ToString()}";
        }
    }
}