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()}"; } } }