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