Overall Statistics |
Total Trades 444 Average Win 0.72% Average Loss -0.59% Compounding Annual Return 17.818% Drawdown 20.700% Expectancy 0.595 Net Profit 109.357% Sharpe Ratio 1.257 Loss Rate 28% Win Rate 72% Profit-Loss Ratio 1.22 Alpha 0.021 Beta 0.85 Annual Standard Deviation 0.137 Annual Variance 0.019 Information Ratio -0.104 Tracking Error 0.059 Treynor Ratio 0.203 Total Fees $536.43 |
namespace QuantConnect { /* * QuantConnect University: Futures Example * * QuantConnect allows importing generic data sources! This example demonstrates importing a futures * data from the popular open data source Quandl. * * QuantConnect has a special deal with Quandl giving you access to Stevens Continuous Futurs (SCF) for free. * If you'd like to download SCF for local backtesting, you can download it through Quandl.com. */ public class DualMomentumSectorRotation : QCAlgorithm { // we'll use this to tell us when the month has ended DateTime LastRotationTime = DateTime.MinValue; TimeSpan RotationInterval = TimeSpan.FromDays(30); List<string> GEMSymbols = new List<string> { "SPY", "BIL", "AGG" }; // these are the growth symbols we'll rotate through List<string> SectorSymbols = new List<string> { "XLV", //healthcare "XLK", //technology "XLI", //industrial "XLU", //utilities "XLF", //financials "XLY", //consumerdisc "XLP", //consumerstap "XLB", //basic materials "XLE", // energy "PSR", //real estate "IYZ", // communications }; // we'll hold some computed data in these guys // List<SymbolData> SectorSymbolData = new List<SymbolData>(); Dictionary<string, SymbolData> SectorSymbolData = new Dictionary<string, SymbolData>(); Dictionary<string, SymbolData> GEMSymbolData = new Dictionary<string, SymbolData>(); List<string> strongSectors = new List<string>(); public override void Initialize() { //SetStartDate(2003, 7, 1); SetStartDate(2010, 7, 1); SetEndDate(2015, 1, 1); SetCash(25000); // define our daily trade bar consolidator. we can access the daily bar // from the DataConsolidated events var dailyConsolidator = new TradeBarConsolidator(TimeSpan.FromDays(1)); // attach our event handler. the event handler is a function that will be called each time we produce // a new consolidated piece of data. dailyConsolidator.DataConsolidated += OnDataDaily; foreach (var symbol in SectorSymbols) { AddSecurity(SecurityType.Equity, symbol, Resolution.Minute); Securities[symbol].SetDataNormalizationMode(DataNormalizationMode.TotalReturn); Securities[symbol].SetLeverage(1); var momentum = MOMP(symbol, 252, Resolution.Daily); var ma = SMA(symbol, 252/12*10, Resolution.Daily); // 10-month Simple Moving Average var close = SMA(symbol, 1, Resolution.Daily); // Store the most recent close SubscriptionManager.AddConsolidator(symbol, dailyConsolidator); SectorSymbolData.Add(symbol, new SymbolData { Symbol = symbol, MomScore = momentum, MovingAvg = ma, Close = close }); } foreach (var symbol in GEMSymbols) { AddSecurity(SecurityType.Equity, symbol, Resolution.Minute); Securities[symbol].SetDataNormalizationMode(DataNormalizationMode.TotalReturn); Securities[symbol].SetLeverage(1); var momentum = MOMP(symbol, 252, Resolution.Daily); SubscriptionManager.AddConsolidator(symbol, dailyConsolidator); GEMSymbolData.Add(symbol, new SymbolData { Symbol = symbol, MomScore = momentum }); } } private bool first = true; //Data Event Handler: New data arrives here. "TradeBars" type is a dictionary of strings so you can access it by symbol. private void OnData(TradeBars data) { var _momSPY = GEMSymbolData["SPY"].MomScore; var _momTbill = GEMSymbolData["BIL"].MomScore; var Bonds = GEMSymbolData["AGG"].Symbol; if (first) { first = false; LastRotationTime = Time; return; } } private void OnDataDaily(object sender, TradeBar consolidated) { var _momSPY = GEMSymbolData["SPY"].MomScore; var _momTbill = GEMSymbolData["BIL"].MomScore; var Bonds = GEMSymbolData["AGG"].Symbol; List<string> longSectors1 = new List<string>(); List<string> longSectors2 = new List<string>(); decimal holdingPercent = 1m; decimal bondHoldings = 1m; if (!_momSPY.IsReady) { if (Portfolio["SPY"].Quantity == 0) { SetHoldings("SPY", holdingPercent); } } if (first) { first = false; LastRotationTime = consolidated.Time; return; } var delta = Time.Subtract(LastRotationTime); // Check whether we have exceeded the rotation interval, time to assess rotation if (delta > RotationInterval) { // Easy rebalance for backtest foreach(string symbol in Securities.Keys) { Log("Liquidating " + Portfolio[symbol].Quantity + "of " + symbol); Liquidate(symbol); } LastRotationTime = Time; List<SymbolData> sectorSymbolDataList = new List<SymbolData>(); foreach (var x in SectorSymbolData) sectorSymbolDataList.Add(x.Value); var orderedMomScores = sectorSymbolDataList.OrderByDescending(x => x.MomScore.Current.Value).ToList(); int numberOfSectors = 4; for (int i = 0; i < numberOfSectors; i++) { strongSectors.Add(orderedMomScores[i].Symbol); Log("Strong Symbol #" + i + "is " + orderedMomScores[i].Symbol); } foreach (var x in orderedMomScores) { Log(">>SCORE>>" + x.Symbol + ">>" + x.MomScore); } // // Modify this section of code // Loop through each ETF in strongSectors // If TMOM and MA rules -> record ETF in longSectors1 // If TMOM rule only -> record ETF in longSectors2 // If MA rule only -> record ETF in longSectors2 // Set bondHoldings = 100% // Loop through each ETF in longSectors1 // Allocate holdings to the ETF equivalent to 1 / NumberOfSectors // Reduce bondHoldings percentage by 1 / NumberOfSectors // Loop through each ETF in longSectors2 // Allocate holdings to the ETF equivalent to half of 1 / NumberOfSectors // Reduce bondHoldings percentage by half of 1 / NumberOfSectors // Allocate remaining holdings to bonds (percentage is indicated in bondHoldings variable) // Loop through each ETF in strongSectors foreach (string etf in strongSectors) { bool tmomRule = SectorSymbolData[etf].MomScore > _momTbill; bool maRule = SectorSymbolData[etf].Close > SectorSymbolData[etf].MovingAvg; if (tmomRule && maRule) longSectors1.Add(etf); if (tmomRule && !maRule) longSectors2.Add(etf); else if (!tmomRule && maRule) longSectors2.Add(etf); } // Loop through each ETF in longSectors1 foreach (string etf in longSectors1) { SetHoldings(etf, holdingPercent * (1m / numberOfSectors)); bondHoldings -= (1m / numberOfSectors); } // Loop through each ETF in longSectors2 foreach (string etf in longSectors2) { SetHoldings(etf, holdingPercent * (0.5m / numberOfSectors)); bondHoldings -= (0.5m / numberOfSectors); } // Allocate remaining holdings to bonds SetHoldings(Bonds, bondHoldings); /* if (_momSPY < 0) { SetHoldings(Bonds, holdingPercent); Log("Holding Percent is " + holdingPercent); Log("Set Holdings to " + Portfolio[Bonds].Quantity + "of " + Bonds); } else { foreach (var etf in strongSectors) { SetHoldings(etf, holdingPercent * (1m / numberOfSectors)); Log("Count of strongSectors is " + numberOfSectors); } } */ strongSectors.Clear(); } } public MMomentumPercent MMOMP(string symbol, int period, int periodexc, Resolution? resolution = null) { var mmomp = new MMomentumPercent(period, periodexc); RegisterIndicator(symbol, mmomp, resolution); return mmomp; } } class SymbolData { public string Symbol; public MomentumPercent MomScore { get; set; } public SimpleMovingAverage MovingAvg { get; set; } // Quick and dirty - store most recent close here public SimpleMovingAverage Close { get; set; } } public class MMomentumPercent : WindowIndicator<IndicatorDataPoint> { private int periodexc = 21; // Number of most recent days to exclude from calculation public MMomentumPercent(int period, int periodexc) : base("MMOMP" + period, period) { } protected override decimal ComputeNextValue(IReadOnlyWindow<IndicatorDataPoint> window, IndicatorDataPoint input) { if (window.Samples <= window.Size) { // keep returning the delta from the first item put in there to init return (window[periodexc] - window[window.Count - 1]) / window[periodexc]; } return (window[periodexc] - window.MostRecentlyRemoved) / window[periodexc]; } } }