| Overall Statistics |
|
Total Trades 2408 Average Win 0.70% Average Loss -0.61% Compounding Annual Return 22.327% Drawdown 25.900% Expectancy 0.290 Net Profit 774.302% Sharpe Ratio 0.964 Probabilistic Sharpe Ratio 33.652% Loss Rate 40% Win Rate 60% Profit-Loss Ratio 1.15 Alpha 0.015 Beta 0.991 Annual Standard Deviation 0.171 Annual Variance 0.029 Information Ratio 0.143 Tracking Error 0.094 Treynor Ratio 0.167 Total Fees $6248.40 Estimated Strategy Capacity $3600000.00 Lowest Capacity Asset KLAC R735QTJ8XC9X |
using System.Collections.Generic;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using System.Linq;
using QuantConnect.Data.Fundamental;
using System;
namespace QuantConnect.Algorithm.CSharp
{
public class MomentumRotation : QCAlgorithm
{
private const int PORTFOLIO_NUM = 10;
private const int _numberOfSymbolsCoarse = 1000;
private const int _numberOfSymbolsFine = 500;
// rebalances at the start of each month
private int _lastYear = -1;
private readonly Dictionary<Symbol, decimal> _dollarVolumeBySymbol = new Dictionary<Symbol, decimal>();
private Dictionary<Symbol, SymbolData> SymbolDict = new Dictionary<Symbol, SymbolData>();
public override void Initialize()
{
SetStartDate(2009, 4, 1); // Set Start Date
SetEndDate(2020, 1, 1); // Set Start Date
SetCash(50000); // Set Strategy Cash
UniverseSettings.Resolution = Resolution.Daily;
EnableAutomaticIndicatorWarmUp = true;
var security = AddEquity("QQQ", Resolution.Daily);
SetBenchmark("QQQ");
AddUniverseSelection(new FineFundamentalUniverseSelectionModel(SelectCoarse, SelectFine));
Schedule.On(Schedule.DateRules.MonthStart("QQQ"),
Schedule.TimeRules.AfterMarketOpen("QQQ", 1), Monthly);
Schedule.On(Schedule.DateRules.WeekStart("QQQ"),
Schedule.TimeRules.AfterMarketOpen("QQQ", 1), Weekly);
//Schedule.On(Schedule.DateRules.EveryDay("QQQ"),
// Schedule.TimeRules.AfterMarketOpen("QQQ", 1), Daily);
}
public void Weekly()
{
//if(ActiveSecurities["QQQ"].Close < SymbolDict["QQQ"].TwoHundredEma)
{
//Liquidate();
}
}
public void Daily()
{
/*if(ActiveSecurities["QQQ"].Close < SymbolDict["QQQ"].TwoHundredEma)
{
Liquidate();
}
else if(!Portfolio.Invested)
{*/
var symbols = (from symbolData in SymbolDict.Values
orderby symbolData.AdjustedStrength descending
select symbolData.Symbol).Take(PORTFOLIO_NUM).ToList();
decimal positionSizing = 1 / (decimal)PORTFOLIO_NUM;
foreach(var symbol in symbols)
{
SetHoldings(symbol, positionSizing);
}
// }
}
public void Monthly()
{
Liquidate();
/*if(ActiveSecurities["QQQ"].Close < SymbolDict["QQQ"].TwoHundredEma)
{
Liquidate();
}
else*/
{
var symbols = (from symbolData in SymbolDict.Values
orderby symbolData.AdjustedStrength descending
select symbolData.Symbol).Take(PORTFOLIO_NUM).ToList();
decimal positionSizing = 1 / (decimal)PORTFOLIO_NUM;
foreach(var symbol in symbols)
{
SetHoldings(symbol, positionSizing);
}
}
}
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
/// Slice object keyed by symbol containing the stock data
public override void OnData(Slice data)
{
}
public override void OnSecuritiesChanged(SecurityChanges changes)
{
// if we have no changes, do nothing
if (changes == SecurityChanges.None) return;
foreach (var security in changes.RemovedSecurities)
{
if(SymbolDict.ContainsKey(security.Symbol))
{
SymbolDict.Remove(security.Symbol);
}
}
foreach (var security in changes.AddedSecurities)
{
if(!SymbolDict.ContainsKey(security.Symbol))
{
SymbolDict.Add(security.Symbol, new SymbolData(security.Symbol, this));
}
}
}
/// <summary>
/// Performs coarse selection for the QC500 constituents.
/// The stocks must have fundamental data
/// The stock must have positive previous-day close price
/// The stock must have positive volume on the previous trading day
/// </summary>
IEnumerable<Symbol> SelectCoarse(IEnumerable<CoarseFundamental> coarse)
{
if (Time.Year == _lastYear)
{
return Universe.Unchanged;
}
Liquidate();
var sortedByDollarVolume =
(from x in coarse
where x.HasFundamentalData && x.Volume > 0 && x.Price > 0
orderby x.DollarVolume descending
select x).Take(_numberOfSymbolsCoarse).ToList();
_dollarVolumeBySymbol.Clear();
foreach (var x in sortedByDollarVolume)
{
_dollarVolumeBySymbol[x.Symbol] = x.DollarVolume;
}
// If no security has met the QC500 criteria, the universe is unchanged.
// A new selection will be attempted on the next trading day as _lastMonth is not updated
if (_dollarVolumeBySymbol.Count == 0)
{
return Universe.Unchanged;
}
return _dollarVolumeBySymbol.Keys;
}
/// <summary>
/// Performs fine selection for the QC500 constituents
/// The company's headquarter must in the U.S.
/// The stock must be traded on either the NYSE or NASDAQ
/// At least half a year since its initial public offering
/// The stock's market cap must be greater than 500 million
/// </summary>
IEnumerable<Symbol> SelectFine(IEnumerable<FineFundamental> fine)
{
var filteredFine =
(from x in fine
where x.CompanyReference.CountryId == "USA" &&
( (x.CompanyReference.PrimaryExchangeID == "NAS") /* ||
(x.CompanyReference.PrimaryExchangeID == "NYS")*/ ) &&
(Time - x.SecurityReference.IPODate).Days > 360
orderby x.MarketCap descending
//orderby x.DollarVolume descending
select x.Symbol).Take(100).ToList();
// Update _lastMonth after all QC500 criteria checks passed
_lastYear = Time.Year;
return filteredFine;
}
}
class SymbolData
{
public Symbol Symbol;
public RateOfChangePercent oneMonth;
public RateOfChangePercent threeMonth;
public RateOfChangePercent sixMonth;
public RateOfChangePercent nineMonth;
public RateOfChangePercent twelveMonth;
public ExponentialMovingAverage TwoHundredEma;
public SymbolData(Symbol symbol, QCAlgorithm algorithm)
{
Symbol = symbol;
oneMonth = algorithm.ROCP(symbol, 21, Resolution.Daily);
threeMonth = algorithm.ROCP(symbol, 63, Resolution.Daily);
sixMonth = algorithm.ROCP(symbol, 126, Resolution.Daily);
nineMonth = algorithm.ROCP(symbol, 189, Resolution.Daily);
twelveMonth = algorithm.ROCP(symbol, 252, Resolution.Daily);
TwoHundredEma = algorithm.EMA(symbol, 200, Resolution.Daily);
}
public decimal AdjustedStrength
{
get
{
return sixMonth; // (threeMonth*0.4m)+(sixMonth*0.2m)+(nineMonth*0.2m)+(twelveMonth*0.2m);
}
}
}
}