Overall Statistics |
Total Trades 954 Average Win 0.73% Average Loss -0.55% Compounding Annual Return 7.609% Drawdown 14.200% Expectancy 0.391 Net Profit 229.019% Sharpe Ratio 0.644 Probabilistic Sharpe Ratio 4.949% Loss Rate 40% Win Rate 60% Profit-Loss Ratio 1.31 Alpha 0.067 Beta 0.011 Annual Standard Deviation 0.106 Annual Variance 0.011 Information Ratio -0.131 Tracking Error 0.203 Treynor Ratio 6.42 Total Fees $1011.39 Estimated Strategy Capacity $62000.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 = 3; [Parameter("CashReservePct")] public decimal CashReservePct = 0.010m; [Parameter("WeightRa")] public decimal WeightRa = 0.20m; [Parameter("WeightRb")] public decimal WeightRb = 0.50m; [Parameter("WeightV")] public decimal WeightV = 0.30m; // vol weight (decimal percentage) [Parameter("FilterSMA")] public int FilterSMA = 8; // 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 = "Daily"; 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(2005, 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("SPY"); GrowthSymbols.Add("DVY"); GrowthSymbols.Add("PFF"); GrowthSymbols.Add("MDY"); GrowthSymbols.Add("AGG"); GrowthSymbols.Add("IWM"); GrowthSymbols.Add("TBF"); GrowthSymbols.Add("IEF"); GrowthSymbols.Add("TIP"); GrowthSymbols.Add("EUM"); GrowthSymbols.Add("DBA"); GrowthSymbols.Add("EFA"); GrowthSymbols.Add("FXA"); GrowthSymbols.Add("EEM"); GrowthSymbols.Add("IDV"); GrowthSymbols.Add("FXE"); GrowthSymbols.Add("EFZ"); GrowthSymbols.Add("SH"); GrowthSymbols.Add("DBC"); } 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; } } }