Overall Statistics |
Total Trades 143 Average Win 7.98% Average Loss -4.28% Compounding Annual Return 16.878% Drawdown 36.000% Expectancy 0.817 Net Profit 785.662% Sharpe Ratio 0.81 Loss Rate 37% Win Rate 63% Profit-Loss Ratio 1.87 Alpha 0.092 Beta 4.406 Annual Standard Deviation 0.222 Annual Variance 0.049 Information Ratio 0.72 Tracking Error 0.222 Treynor Ratio 0.041 Total Fees $2086.99 |
namespace QuantConnect { /* * Author: Tomer Borenstein * My attempt at whipping up an implementation of the "Global Market * Rotation" strategy. Original description of strategy is linked to * in the project notes. */ public class SGMR : QCAlgorithm { private string currentMonth; private DateTime currentDay; private MyMomentum momentumMDY; private MyMomentum momentumILF; private MyMomentum momentumFEZ; private MyMomentum momentumEEM; private MyMomentum momentumEPP; private MyMomentum momentumTLT; string currentETF; public override void Initialize(){ // start and End Date range for the backtest: SetStartDate(2004, 1, 1); SetEndDate(DateTime.Now.Date.AddDays(-1)); currentMonth = Time.ToString("MMM"); currentETF = ""; // cash allocation SetCash(25000); // 5 ETFs and a negatively correlated treasury ETF AddSecurity(SecurityType.Equity, "MDY", Resolution.Minute); AddSecurity(SecurityType.Equity, "ILF", Resolution.Minute); AddSecurity(SecurityType.Equity, "FEZ", Resolution.Minute); AddSecurity(SecurityType.Equity, "EEM", Resolution.Minute); AddSecurity(SecurityType.Equity, "EPP", Resolution.Minute); AddSecurity(SecurityType.Equity, "TLT", Resolution.Minute); // momentum over roughly 3 month period momentumMDY = new MyMomentum("MDY", 90); momentumILF = new MyMomentum("ILF", 90); momentumFEZ = new MyMomentum("FEZ", 90); momentumEEM = new MyMomentum("EEM", 90); momentumEPP = new MyMomentum("EPP", 90); momentumTLT = new MyMomentum("TLT", 90); } public void OnData(TradeBars data){ // day tick - add data to MyMomentum objects if(currentDay.Date != data.Time.Date){ currentDay = data.Time; if(data.ContainsKey("MDY")){ momentumMDY.add(data["MDY"].Close); } if(data.ContainsKey("ILF")){ momentumILF.add(data["ILF"].Close); } if(data.ContainsKey("FEZ")){ momentumFEZ.add(data["FEZ"].Close); } if(data.ContainsKey("EEM")){ momentumEEM.add(data["EEM"].Close); } if(data.ContainsKey("EPP")){ momentumEPP.add(data["EPP"].Close); } if(data.ContainsKey("TLT")){ momentumTLT.add(data["TLT"].Close); } } // month tick - use momentum to determine which ETF to invest in if(Time.ToString("MMM") != currentMonth){ currentMonth = Time.ToString("MMM"); string ETF = bestETF(); if(currentETF != ETF){ Liquidate(); // cash out // fully invest in best ETF if possible if(data.ContainsKey(ETF)){ decimal cash = Portfolio.Cash; decimal price = data[ETF].Close; int quantity = (int)Math.Floor(cash/price); Order(ETF, quantity); Debug(Time.ToString("MMM") + ": invested in " + ETF + "."); } else { Debug(Time.ToString("MMM") + ": could not invest in " + ETF + "."); } currentETF = ETF; } } } // returns the name of the ETF with the best return private string bestETF(){ MyMomentum[] ETFs = new MyMomentum[]{momentumMDY, momentumILF, momentumFEZ, momentumEEM, momentumEPP, momentumTLT}; string bestFund = ""; decimal bestReturn = 0m; for(int i = 0; i < ETFs.Length; i++){ MyMomentum mETF = ETFs[i]; string fund = mETF.getName(); decimal fundReturn = mETF.getReturn(); if(bestFund == "" || bestReturn < fundReturn){ bestFund = fund; bestReturn = fundReturn; } } return bestFund; } } }
using System; using System.Collections; using System.Collections.Generic; using QuantConnect.Securities; using QuantConnect.Models; namespace QuantConnect { // Author: Tomer Borenstein // MyMomentum - calculates a crude measure of an asset's return public class MyMomentum { private List<decimal> prices; private string name; private int window; public MyMomentum(string name, int window){ this.prices = new List<decimal>(); this.name = name; this.window = window; } public void add(decimal price){ prices.Add(price); if(prices.Count > window){ prices.RemoveAt(0); } } public string getName(){ return name; } public decimal getReturn(){ decimal start = prices[0]; decimal end = prices[prices.Count - 1]; return (end-start)/start; } } }