Overall Statistics |
Total Trades 1169 Average Win 0.68% Average Loss -0.55% Compounding Annual Return 6.496% Drawdown 25.000% Expectancy 0.290 Net Profit 161.065% Sharpe Ratio 0.597 Probabilistic Sharpe Ratio 3.563% Loss Rate 42% Win Rate 58% Profit-Loss Ratio 1.22 Alpha 0.058 Beta -0.001 Annual Standard Deviation 0.098 Annual Variance 0.01 Information Ratio -0.203 Tracking Error 0.204 Treynor Ratio -55.671 Total Fees $1173.91 Estimated Strategy Capacity $11000.00 |
namespace QuantConnect { using System; using System.Net; using System.Collections.Generic; using System.Linq; using QuantConnect.Parameters; using QuantConnect.Data.Custom; using QuantConnect.Data; using QuantConnect.Data.UniverseSelection; using QuantConnect.Data.Market; using QuantConnect.Indicators; using QuantConnect.Orders; using QuantConnect.Parameters; /// <summary> /// ETF Global Rotation Strategy /// </summary> public class ETFReplay: QCAlgorithm { // we'll hold some computed data in these guys List<SymbolData> SymbolData = new List<SymbolData>(); [Parameter("Keeps")] public int Keeps = 2; [Parameter("CashReservePct")] public decimal CashReservePct = 0.010m; [Parameter("WeightRa")] public decimal WeightRa = 0.10m; [Parameter("WeightRb")] public decimal WeightRb = 0.50m; [Parameter("WeightV")] public decimal WeightV = 0.40m; // vol weight (decimal percentage) [Parameter("FilterSMA")] public int FilterSMA = 2; // MA filter, monthly length [Parameter("LookbackRa")] public string LookbackRa = "20D"; // return "a" lookback [Parameter("LookbackRb")] public string LookbackRb = "3M"; // return "b" lookback [Parameter("LookbackV")] public string LookbackV = "20D"; // volatility lookback [Parameter("SymbolsUrl")] public string Url = ""; [Parameter("RebalancePeriod")] public string RebalancePeriod = "Weekly"; List<string> GrowthSymbols = new List<String>{ // safety symbol "SHY" }; private bool first = true; private DateTime LastRotationTime = DateTime.Now; /// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized. /// </summary> public override void Initialize() { SetCash(10000); SetStartDate(2006, 1, 1); SetGrowthSymbols(); Debug("WeightRa: " + WeightRa); Debug("WeightRb: " + WeightRb); Debug("WeightV: " + WeightV); Debug("SymbolUrl: " + Url); Debug("RebalancePeriod: " + RebalancePeriod); int periodADays = DaysFromLookback(LookbackRa); int periodBDays = DaysFromLookback(LookbackRb); int periodVolDays = DaysFromLookback(LookbackV); Debug(String.Format("LookbackRa: {0} ({1} days)", LookbackRa, periodADays)); Debug(String.Format("LookbackRb: {0} ({1} days)", LookbackRb, periodBDays)); Debug(String.Format("LookbackV: {0} ({1} days)", LookbackV, periodVolDays)); foreach (var symbol in GrowthSymbols) { Debug("adding symbol to universe: " + symbol); AddSecurity(SecurityType.Equity, symbol, Resolution.Daily); // TODO - add parameters for lookback periods var periodAPerf = MOMP(symbol, periodADays, Resolution.Daily); var periodBPerf = MOMP(symbol, periodBDays, Resolution.Daily); // (252/12) * x months // FIXME -The moving average is calculated using the closing price from the last day of each month(e.g. a 10 month moving average has 10 datapoints) var filterSMA = SMA(symbol, 21*FilterSMA, Resolution.Daily); // we use log returns instead of definition in replay var std = new StandardDeviation(periodVolDays); decimal annualizationFactor = (decimal)Math.Sqrt(252); var logReturns = LOGR(symbol, periodVolDays, Resolution.Daily); CompositeIndicator<IndicatorDataPoint> volatility = std.Of(logReturns).Times(annualizationFactor); SymbolData.Add(new SymbolData { Symbol = symbol, ReturnA = periodAPerf, ReturnB = periodBPerf, Volatility = volatility, FilterSMA = filterSMA }); } // assumption we don't look back more than a year SetWarmup(TimeSpan.FromDays(365)); } // Convert the lookback strings to number of days (e.g. 20D->20, 2M->42) public int DaysFromLookback (string period) { string strAmount = period.Substring(0, period.Length - 1); int amount = Int32.Parse(strAmount); int multiplier = period.EndsWith("M") ? 21 : 1; return multiplier * amount; } // Downloads the symbols from dropbox, file should contain each symbol on a new line and nothing else private void SetGrowthSymbols() { // using (var client = new WebClient()) // { // // fetch the file from dropbox // var file = client.DownloadString(Url); // if (file.Length > 0) { // string[] contents = file.Split(new string[] {"\n", "\r\n"}, StringSplitOptions.RemoveEmptyEntries); // foreach(var symbol in contents){ // var cleaned = symbol.Trim(); // // skip comments // if (cleaned.StartsWith("#")) continue; // Debug("Adding to universe: " + cleaned); // GrowthSymbols.Add(cleaned); // } // } // } // Alpha symbols GrowthSymbols.Add("PBJ"); GrowthSymbols.Add("IYG"); GrowthSymbols.Add("IAT"); GrowthSymbols.Add("IXN"); GrowthSymbols.Add("IHI"); GrowthSymbols.Add("SPY"); GrowthSymbols.Add("KXI"); GrowthSymbols.Add("IXJ"); GrowthSymbols.Add("IGV"); GrowthSymbols.Add("IYT"); GrowthSymbols.Add("ITA"); GrowthSymbols.Add("ITB"); GrowthSymbols.Add("XRT"); GrowthSymbols.Add("IBB"); GrowthSymbols.Add("EXI"); GrowthSymbols.Add("IXP"); GrowthSymbols.Add("RXI"); GrowthSymbols.Add("IHE"); GrowthSymbols.Add("PFF"); GrowthSymbols.Add("IGN"); GrowthSymbols.Add("VEGI"); GrowthSymbols.Add("SMH"); GrowthSymbols.Add("IHF"); GrowthSymbols.Add("EPI"); GrowthSymbols.Add("IXG"); GrowthSymbols.Add("DGZ"); GrowthSymbols.Add("XPH"); GrowthSymbols.Add("CYB"); GrowthSymbols.Add("MBB"); GrowthSymbols.Add("MINT"); GrowthSymbols.Add("MINT"); GrowthSymbols.Add("BIL"); GrowthSymbols.Add("SHY"); GrowthSymbols.Add("AGZ"); GrowthSymbols.Add("AGZ"); GrowthSymbols.Add("TBX"); GrowthSymbols.Add("HYG"); GrowthSymbols.Add("JNK"); GrowthSymbols.Add("IEI"); GrowthSymbols.Add("ICN"); GrowthSymbols.Add("CIU"); GrowthSymbols.Add("PBS"); GrowthSymbols.Add("JXI"); GrowthSymbols.Add("TUR"); GrowthSymbols.Add("TBF"); GrowthSymbols.Add("IEF"); GrowthSymbols.Add("IGF"); GrowthSymbols.Add("LQD"); GrowthSymbols.Add("IHY"); GrowthSymbols.Add("TLT"); GrowthSymbols.Add("TIP"); GrowthSymbols.Add("CLY"); GrowthSymbols.Add("EZA"); GrowthSymbols.Add("ECH"); GrowthSymbols.Add("EUM"); GrowthSymbols.Add("EFA"); GrowthSymbols.Add("EWT"); GrowthSymbols.Add("IPE"); GrowthSymbols.Add("FXI"); GrowthSymbols.Add("AMLP"); GrowthSymbols.Add("ECON"); GrowthSymbols.Add("GAF"); GrowthSymbols.Add("GULF"); GrowthSymbols.Add("FXC"); GrowthSymbols.Add("IDV"); GrowthSymbols.Add("VWO"); GrowthSymbols.Add("EWX"); GrowthSymbols.Add("EWW"); GrowthSymbols.Add("FXA"); GrowthSymbols.Add("EWM"); GrowthSymbols.Add("MXI"); GrowthSymbols.Add("EEM"); GrowthSymbols.Add("EFZ"); GrowthSymbols.Add("EWC"); GrowthSymbols.Add("WIP"); GrowthSymbols.Add("DGS"); GrowthSymbols.Add("FXF"); GrowthSymbols.Add("EGPT"); GrowthSymbols.Add("BWX"); GrowthSymbols.Add("FXS"); GrowthSymbols.Add("FXE"); GrowthSymbols.Add("BIK"); GrowthSymbols.Add("SH"); GrowthSymbols.Add("EWY"); GrowthSymbols.Add("DOG"); GrowthSymbols.Add("IXC"); GrowthSymbols.Add("IGE"); GrowthSymbols.Add("IEO"); GrowthSymbols.Add("FXY"); GrowthSymbols.Add("IEZ"); GrowthSymbols.Add("DEM"); GrowthSymbols.Add("OIH"); GrowthSymbols.Add("GLD"); GrowthSymbols.Add("PICK"); GrowthSymbols.Add("PICK"); GrowthSymbols.Add("BZF"); GrowthSymbols.Add("LATM"); GrowthSymbols.Add("EWZ"); } private Dictionary<String, decimal> GetEtfReplayRankings() { // Rank according to ETF Replay rules (http://www.etfreplay.com/members/how_the_screener_works.aspx) var orderedReturnA = SymbolData.OrderByDescending(x => x.ReturnA); var orderedReturnB = SymbolData.OrderByDescending(x => x.ReturnB); var orderedVol = SymbolData.OrderBy(x => x.Volatility); var rankings = new Dictionary<String, decimal>(); // add all the symbols foreach(var sd in SymbolData) { rankings[sd.Symbol] = 0.0m; } for(int i = 0; i < orderedReturnA.Count(); ++i) { var current = orderedReturnA.ElementAt(i); rankings[current.Symbol] += i * WeightRa; } for(int i = 0; i < orderedReturnB.Count(); ++i) { var current = orderedReturnB.ElementAt(i); rankings[current.Symbol] += i * WeightRb; } for(int i = 0; i < orderedVol.Count(); ++i) { var current = orderedVol.ElementAt(i); rankings[current.Symbol] += i * WeightV; } return rankings; } public override void OnData(Slice data) { if (IsWarmingUp) return; Log("OnData slice: date=" + data.Time + ", cnt=" + data.Count); try { bool rebalance = false; if (first) { rebalance = true; first = false; }else { // var delta = Time.Subtract(LastRotationTime); // rebalance = delta > RotationInterval; rebalance = LastRotationTime.Month != Time.Month; if (RebalancePeriod == "Quarterly") { // January - 1 // April - 4 // July - 7 // Oct - 10 rebalance = (rebalance && ((Time.Month == 1) || (Time.Month == 4) || (Time.Month == 7) || (Time.Month == 10))); } } if (rebalance) { LastRotationTime = Time; // pick which one is best from growth and safety symbols //var orderedObjScores = SymbolData.OrderByDescending(x => x.ObjectiveScore).ToList(); var rankings = GetEtfReplayRankings(); var orderedObjScores = SymbolData.OrderBy(x => rankings[x.Symbol]).ToList(); List<String> nextHoldings = new List<string>(); Debug("OrderedObjScores Count: " + orderedObjScores.Count()); for (int i = 0; i < Keeps; ++i) { var currentSymbolData = orderedObjScores[i]; var lastClose = Securities[currentSymbolData.Symbol].Close; bool maFilter = lastClose > currentSymbolData.FilterSMA; if (maFilter) { // this meets our investment criteria nextHoldings.Add(currentSymbolData.Symbol); } // FIXME - allocate to "Safety" symbol, right now saftey symbol is part of the growth symbols so it should bubble up but that doesn't match replay } Log(">>NextPositions<<"); foreach(var position in nextHoldings) { Log("\t>>" + position); } if (nextHoldings.Count == 0) { // goto 100% cash Log("LIQUIDATE>>CASH"); Liquidate(); }else { foreach(var kvp in Portfolio.Securities.Values) { // liquidate anything we are currently invested in but not part of our next portfolio state if (kvp.Invested && !nextHoldings.Contains(kvp.Symbol)) { Log("LIQUIDATE>>" + kvp.Symbol); Liquidate(kvp.Symbol); } } decimal allocationPercentage = (1.0m - CashReservePct) / Keeps; foreach(var symbol in nextHoldings) { Log("BUY>>" + symbol); SetHoldings(symbol, allocationPercentage); } } } } catch (Exception ex) { Error("OnData: " + ex.Message + "\r\n\r\n" + ex.StackTrace); } } } class SymbolData { public string Symbol; public MomentumPercent ReturnA { get; set; } public MomentumPercent ReturnB { get; set; } public CompositeIndicator<IndicatorDataPoint> Volatility { get; set; } public SimpleMovingAverage FilterSMA { get; set; } } }