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