Overall Statistics |
Total Trades 41 Average Win 3.80% Average Loss -2.89% Compounding Annual Return -6.725% Drawdown 29.100% Expectancy -0.182 Net Profit -4.323% Sharpe Ratio -0.137 Loss Rate 65% Win Rate 35% Profit-Loss Ratio 1.32 Alpha -0.017 Beta -0.124 Annual Standard Deviation 0.225 Annual Variance 0.051 Information Ratio -0.523 Tracking Error 0.268 Treynor Ratio 0.248 Total Fees $862.10 |
namespace QuantConnect { /* * Term Structure Effect in Commodities * http://quantpedia.com/Screener/Details/22 * * It is generally accepted that futures markets provide insurance to * hedgers by ensuring the transfer of price risk to speculators. * The insurance that net hedgers are willing to pay equals the premium * earned by speculators for this risk bearing. As commodity futures * returns directly relate to the propensity of hedgers to be net long or * net short, it becomes natural to design an active strategy that buys * mostly backwardated contracts and shorts mostly contangoed contracts - * the strategy which exploits the term structure in commodities. * * This simple strategy buys each month the 20% of commodities with * the highest roll-returns and shorts the 20% of commodities with the * lowest roll-returns and holds the long-short positions for one month. * The contracts in each quintile are equally-weighted. * The investment universe is all commodity futures contracts. */ public class Quantpedia22 : QCAlgorithm { private FuturesChains _chains = new FuturesChains(); public override void Initialize() { SetStartDate(2016, 1, 1); SetEndDate(2016, 8, 20); SetCash(1000000); var tickers = new string[] { Futures.Softs.Cocoa, Futures.Softs.Coffee, Futures.Grains.Corn, Futures.Softs.Cotton2, Futures.Grains.Oats, Futures.Softs.OrangeJuice, Futures.Grains.SoybeanMeal, Futures.Grains.SoybeanOil, Futures.Grains.Soybeans, Futures.Softs.Sugar11, Futures.Grains.Wheat, Futures.Meats.FeederCattle, Futures.Meats.LeanHogs, Futures.Meats.LiveCattle, Futures.Energies.CrudeOilWTI, Futures.Energies.HeatingOil, Futures.Energies.NaturalGas, Futures.Energies.Gasoline, Futures.Metals.Gold, Futures.Metals.Palladium, Futures.Metals.Platinum, Futures.Metals.Silver }; foreach (var ticker in tickers) { var future = AddFuture(ticker); future.SetFilter(TimeSpan.Zero, TimeSpan.FromDays(90)); } } // Saves the Futures Chains public override void OnData(Slice slice) { foreach (var chain in slice.FutureChains) { if (chain.Value.Contracts.Count < 2) continue; if (!_chains.ContainsKey(chain.Key)) { _chains.Add(chain.Key, chain.Value); } _chains[chain.Key] = chain.Value; } } // Trades are only defined on end of day public override void OnEndOfDay() { /* * We are going to use TradingCalendar object to learn which are the * next futures' expiration date and, if today is one of these days * the algorithm will select the universe to open long-short positions */ var expiryDates = TradingCalendar.GetDaysByType(TradingDayType.FutureExpiration, Time, EndDate); if (!expiryDates.Select(x => x.Date).Contains(Time.Date)) return; Liquidate(); var quintile = (int)Math.Floor(_chains.Count / 5.0); var rollReturns = new Dictionary<Symbol, double>(); foreach (var chain in _chains) { var contracts = chain.Value.OrderBy(x => x.Expiry); if (contracts.Count() < 2) continue; // R = (log(Pn) - log(Pd)) * 365 / (Td - Tn) // R - Roll returns // Pn - Nearest contract price // Pd - Distant contract price // Tn - Nearest contract expire date // Pd - Distant contract expire date var nearestContract = contracts.FirstOrDefault(); var distantContract = contracts.ElementAtOrDefault(1); var priceNearest = nearestContract.LastPrice > 0 ? nearestContract.LastPrice : (nearestContract.AskPrice + nearestContract.BidPrice) / 2m; var priceDistant = distantContract.LastPrice > 0 ? distantContract.LastPrice : (distantContract.AskPrice + distantContract.BidPrice) / 2m; var logPriceNearest = Math.Log((double)priceNearest); var logPriceDistant = Math.Log((double)priceDistant); if(distantContract.Expiry == nearestContract.Expiry) { Log("ERROR: Nearest and distant contracts with same expire!" + nearestContract); continue; } var expireRange = 365 / (distantContract.Expiry - nearestContract.Expiry).TotalDays; rollReturns.Add(chain.Key, (logPriceNearest - logPriceDistant) * expireRange); } // Order positive roll returns var backwardation = rollReturns .OrderByDescending(x => x.Value) .Where(x => x.Value > 0) .Take(quintile) .ToDictionary(x => x.Key, y => y.Value); var contango = rollReturns .OrderBy(x => x.Value) .Where(x => x.Value < 0) .Take(quintile) .ToDictionary(x => x.Key, y => y.Value); // var count = Math.Min(backwardation.Count(), contango.Count()); if(count != quintile) { backwardation = backwardation.Take(count).ToDictionary(x => x.Key, y => y.Value); contango = contango.Take(count).ToDictionary(x => x.Key, y => y.Value); } //Log("backwardation: " + string.Join(", ", backwardation)); //Log(" contango: " + string.Join(", ", contango)); // We cannot long-short if count is zero if (count == 0) { _chains.Clear(); return; } var weight = 1m / count; // Buy top backwardation foreach (var symbol in backwardation.Keys) { var contractSymbol = _chains[symbol].ElementAtOrDefault(1).Symbol; SetHoldings(contractSymbol, weight); } // Sell top contango foreach (var symbol in contango.Keys) { var contractSymbol = _chains[symbol].ElementAtOrDefault(1).Symbol; SetHoldings(contractSymbol, -weight); } _chains.Clear(); } public override void OnOrderEvent(OrderEvent orderEvent) { Log(orderEvent.ToString()); } } }