Overall Statistics |
Total Orders 149 Average Win 1.72% Average Loss -1.30% Compounding Annual Return 17.790% Drawdown 36.100% Expectancy 0.502 Start Equity 1000000 End Equity 1722305.39 Net Profit 72.231% Sharpe Ratio 0.529 Sortino Ratio 0.698 Probabilistic Sharpe Ratio 18.643% Loss Rate 35% Win Rate 65% Profit-Loss Ratio 1.32 Alpha 0.076 Beta 0.777 Annual Standard Deviation 0.231 Annual Variance 0.053 Information Ratio 0.303 Tracking Error 0.206 Treynor Ratio 0.157 Total Fees $1830.19 Estimated Strategy Capacity $56000000.00 Lowest Capacity Asset GE R735QTJ8XC9X Portfolio Turnover 2.42% |
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Drawing; using QuantConnect; using QuantConnect.Algorithm.Framework; using QuantConnect.Algorithm.Framework.Selection; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Algorithm.Framework.Execution; using QuantConnect.Algorithm.Framework.Risk; using QuantConnect.Parameters; using QuantConnect.Benchmarks; using QuantConnect.Brokerages; using QuantConnect.Util; using QuantConnect.Interfaces; using QuantConnect.Algorithm; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Custom; using QuantConnect.DataSource; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Notifications; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.Slippage; using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; using QuantConnect.Securities.Interfaces; using QuantConnect.Storage; using QuantConnect.Data.Custom.AlphaStreams; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; using MathNet.Numerics; using MathNet.Numerics.LinearAlgebra; #endregion namespace QuantConnect.Algorithm.CSharp.Helpers { public class AnnualizedExponentialSlopeIndicator : WindowIndicator<IndicatorDataPoint> { /// <summary> /// Array representing the time. /// </summary> private readonly double[] t; public AnnualizedExponentialSlopeIndicator(int period) : base("AESI(" + period + ")", period) { t = Vector<double>.Build.Dense(period, i => i + 1).ToArray(); } public AnnualizedExponentialSlopeIndicator(string name, int period) : base(name, period) { t = Vector<double>.Build.Dense(period, i => i + 1).ToArray(); } protected override decimal ComputeNextValue(IReadOnlyWindow<IndicatorDataPoint> window, IndicatorDataPoint input) { // Until the window is ready, the indicator returns the input value. if (window.Samples <= window.Size) return 0m; // Sort the window by time, convert the observations to double and transform it to an array var series = window .OrderBy(i => i.Time) .Select(i => Convert.ToDouble(Math.Log(Convert.ToDouble(i.Value)))) .ToArray(); // Fit OLS // solves y=a + b*x via linear regression // http://numerics.mathdotnet.com/Regression.html var ols = Fit.Line(x: t, y: series); var intercept = ols.Item1; var slope = ols.Item2; // compute rsquared var rsquared = GoodnessOfFit.RSquared(t.Select(x => intercept + slope * x), series); // anything this small can be viewed as flat if (double.IsNaN(slope) || Math.Abs(slope) < 1e-25) return 0m; // trading days per year for us equities const int dayCount = 252; // annualize dy/dt var annualSlope = ((Math.Pow(Math.Exp(slope), dayCount)) - 1) * 100; // scale with rsquared annualSlope = annualSlope * rsquared; if (annualSlope >= (double)decimal.MaxValue || annualSlope <= (double)decimal.MinValue) { annualSlope = 0; } return Convert.ToDecimal(annualSlope); } } }
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Drawing; using QuantConnect; using QuantConnect.Algorithm.Framework; using QuantConnect.Algorithm.Framework.Selection; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Algorithm.Framework.Execution; using QuantConnect.Algorithm.Framework.Risk; using QuantConnect.Parameters; using QuantConnect.Benchmarks; using QuantConnect.Brokerages; using QuantConnect.Util; using QuantConnect.Interfaces; using QuantConnect.Algorithm; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Custom; using QuantConnect.DataSource; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Notifications; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.Slippage; using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; using QuantConnect.Securities.Interfaces; using QuantConnect.Storage; using QuantConnect.Data.Custom.AlphaStreams; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; #endregion namespace QuantConnect.Algorithm.CSharp.Helpers { public class CustomMomentumIndicator : TradeBarIndicator { private Symbol _symbol; private int _windowSize; public readonly AnnualizedExponentialSlopeIndicator AnnualizedSlope; public readonly ExponentialMovingAverage MovingAverage; public readonly GapIndicator Gap; public readonly AverageTrueRange Atr; public CustomMomentumIndicator(Symbol symbol, int annualizedSlopeWindow, int movingAverageWindow, int gapWindow, int atrWindow) : base($"CMI({symbol}, {annualizedSlopeWindow}, {movingAverageWindow}, {gapWindow})") { _symbol = symbol; AnnualizedSlope = new AnnualizedExponentialSlopeIndicator(annualizedSlopeWindow); MovingAverage = new ExponentialMovingAverage(movingAverageWindow); Gap = new GapIndicator(gapWindow); Atr = new AverageTrueRange(atrWindow); _windowSize = (new int[] { movingAverageWindow, annualizedSlopeWindow, gapWindow, atrWindow }).Max(); } public Symbol Symbol { get { return _symbol; } } public override void Reset() { AnnualizedSlope.Reset(); MovingAverage.Reset(); Gap.Reset(); Atr.Reset(); base.Reset(); } protected override decimal ComputeNextValue(TradeBar input) { AnnualizedSlope.Update(input.EndTime, input.Value); MovingAverage.Update(input.EndTime, input.Value); Gap.Update(input.EndTime, input.Value); Atr.Update(input); return AnnualizedSlope; } /// <summary> /// Are the indicators ready to be used? /// </summary> public override bool IsReady { get { return AnnualizedSlope.IsReady && MovingAverage.IsReady && Gap.IsReady && Atr.IsReady; } } /// <summary> /// Returns the Window of the indicator required to warm up indicator /// </summary> public int Window { get {return _windowSize;} } public new string ToDetailedString() { return $"Symbol:{_symbol} Slope:{AnnualizedSlope.ToDetailedString()} Average:{MovingAverage.ToDetailedString()} Gap:{Gap.ToDetailedString()} Atr:{Atr.ToDetailedString()} IsReady:{IsReady}"; } } }
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Drawing; using QuantConnect; using QuantConnect.Algorithm.Framework; using QuantConnect.Algorithm.Framework.Selection; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Algorithm.Framework.Execution; using QuantConnect.Algorithm.Framework.Risk; using QuantConnect.Parameters; using QuantConnect.Benchmarks; using QuantConnect.Brokerages; using QuantConnect.Util; using QuantConnect.Interfaces; using QuantConnect.Algorithm; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Custom; using QuantConnect.DataSource; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Notifications; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.Slippage; using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; using QuantConnect.Securities.Interfaces; using QuantConnect.Storage; using QuantConnect.Data.Custom.AlphaStreams; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; using MathNet.Numerics; using MathNet.Numerics.Statistics; #endregion /// <summary> /// Indicator to indicate the percentage (0.10 = 10%) of which a security gapped over the last period; /// </summary> namespace QuantConnect.Algorithm.CSharp.Helpers { public class GapIndicator : WindowIndicator<IndicatorDataPoint> { public GapIndicator(int period) : base("GAP(" + period + ")", period) { } public GapIndicator(string name, int period) : base(name, period) { } public override bool IsReady { get { return Samples >= Period; } } protected override decimal ComputeNextValue(IReadOnlyWindow<IndicatorDataPoint> window, IndicatorDataPoint input) { if (window.Count < 3) return 0m; var diff = new double[window.Count]; // load input data for regression for (int i = 0; i < window.Count - 1; i++) { diff[i] = (double)((window[i + 1] - window[i]) / (window[i] == 0 ? 1 : window[i].Value)); } return (decimal) diff.MaximumAbsolute(); } } }
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Drawing; using QuantConnect; using QuantConnect.Algorithm.Framework; using QuantConnect.Algorithm.Framework.Selection; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Algorithm.Framework.Execution; using QuantConnect.Algorithm.Framework.Risk; using QuantConnect.Parameters; using QuantConnect.Benchmarks; using QuantConnect.Brokerages; using QuantConnect.Util; using QuantConnect.Interfaces; using QuantConnect.Algorithm; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Custom; using QuantConnect.DataSource; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Notifications; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.Slippage; using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; using QuantConnect.Securities.Interfaces; using QuantConnect.Storage; using QuantConnect.Data.Custom.AlphaStreams; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; #endregion namespace QuantConnect.Algorithm.CSharp { public class MarketRegimeFilter { private SimpleMovingAverage _spyMovingAverage200; public MarketRegimeFilter(SimpleMovingAverage spyMovingAverage200) { _spyMovingAverage200 = spyMovingAverage200; } // Checks if the SPY is over its 200d SMA public bool RiskON(decimal spyPrice){ bool riskonSPY = false; if (spyPrice > _spyMovingAverage200){ riskonSPY = true; } if (riskonSPY) return true; else return false; } } }
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Drawing; using QuantConnect; using QuantConnect.Algorithm.Framework; using QuantConnect.Algorithm.Framework.Selection; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Algorithm.Framework.Execution; using QuantConnect.Algorithm.Framework.Risk; using QuantConnect.Parameters; using QuantConnect.Benchmarks; using QuantConnect.Brokerages; using QuantConnect.Util; using QuantConnect.Interfaces; using QuantConnect.Algorithm; using QuantConnect.Algorithm.CSharp.Helpers; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Custom; using QuantConnect.DataSource; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Notifications; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.Slippage; using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; using QuantConnect.Securities.Interfaces; using QuantConnect.Storage; using QuantConnect.Data.Custom.AlphaStreams; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; #endregion namespace QuantConnect.Algorithm.CSharp { public class StocksOnTheMoveAlgorithm : QCAlgorithm { ///Momentum is calculated based on 90 past days annualized exponential regression slope; private int _annualizedSlopeWindow = 90; /// If the stock is below its 150 days moving average, sell it; private int _movingAverageWindow = 150; /// ATR window private int _atrWindow = 20; /// Daily Risk of each trade on the portfolio (0,5%) private const decimal RiskPerContractOnPortfolio = 0.015m; /// Total number of security symbols in the Universe private static int _universeSelectMaxStocks = 100; /// Holds our security custom indicators per symbol private Dictionary<Symbol, CustomMomentumIndicator> _customIndicators = new Dictionary<QuantConnect.Symbol, CustomMomentumIndicator>(_universeSelectMaxStocks); // If the SP500 is above the 200 days moving average we buy stocks, otherwise not; private MarketRegimeFilter _marketRegimeFilter; //If the stock is not in the top 100/ 20% ranking, sell it; private int _topNStockOfSp500 = 20; ///If the stock gapped > 15% over period (90d) Do not buy: Maximum Gap in percentage private decimal _maximumGap = 0.15m; private int _gapWindow = 90; ///Minimum annualized slope before buying stock. private decimal _minimumAnnualizedSlope = 0m; ///Twice a month rebalance the positions sizes (risk); private bool _rebalanceWeek = false; public bool RebalanceWeek { get { return _rebalanceWeek; } } ///Broker fee to take into account to check if Cash is avalaible private const decimal BrokerFee = 0.005m; // Debug parameters private bool _isLogging = false; /// Is debugging set? public bool IsLooging { get { return _isLogging; } } public new void Log(string message) { if (IsLooging) base.Log(message); } /// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. public override void Initialize() { _isLogging = false; //Set trading window SetStartDate(2021, 1, 1); SetEndDate(DateTime.Now); //Set cash and brokermodel SetCash(1000000); SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin); //Set Benchmark Security security = AddEquity("SPY", Resolution.Daily); SetBenchmark(security.Symbol); // Set the MarketRegimeFilter SimpleMovingAverage spyMovingAverage200 = SMA("SPY", 10, Resolution.Daily); //Warm up SMA SetWarmUp(200); IEnumerable<TradeBar> history = History("SPY", 10, Resolution.Daily); foreach (TradeBar tradeBar in history) { spyMovingAverage200.Update(tradeBar.EndTime, tradeBar.Close); } _marketRegimeFilter = new MarketRegimeFilter(spyMovingAverage200); //Setup universe based on ETF: https://www.quantconnect.com/docs#Universes UniverseSettings.Resolution = Resolution.Daily; AddUniverse(Universe.ETF("OEF", Market.USA, UniverseSettings)); //Trade only on Wednesday at opening after 1 minutes Schedule.On(DateRules.Every(DayOfWeek.Thursday), TimeRules.AfterMarketOpen("SPY", 1), ScheduledOnWednesday1MinuteAfterMarketOpen); } // SECURITY RANKING, SELL, REBALANCE AND BUY private void ScheduledOnWednesday1MinuteAfterMarketOpen() { if (IsWarmingUp) return; // First, we order by slope and we take top 20% ranked var sortedEquityListBySlope = _customIndicators.Where(x => x.Value.IsReady) .OrderByDescending(x => x.Value.AnnualizedSlope) .Take(_topNStockOfSp500) .ToList(); // Second, we filter by minimum slope, above moving average and max gap sortedEquityListBySlope = sortedEquityListBySlope .Where(x => x.Value.AnnualizedSlope > _minimumAnnualizedSlope && Securities[x.Key].Price > x.Value.MovingAverage && x.Value.Gap < _maximumGap).ToList(); //Sell if security is not in list foreach (var security in Portfolio.Values.Where(x => x.Invested)) { var symbolHold = security.Symbol; if (!sortedEquityListBySlope.Exists(x => x.Value.Symbol == symbolHold)) { Liquidate(symbolHold); } } bool riskON = _marketRegimeFilter.RiskON(Securities["SPY"].Price); //Twice a month rebalance the positions sizes (risk); if (RebalanceWeek) { _rebalanceWeek = false; var risk = Portfolio.TotalPortfolioValue * RiskPerContractOnPortfolio; foreach (var security in Portfolio.Values.Where(x => x.Invested)) { var symbolHold = security.Symbol; var quantityHold = security.Quantity; var priceHold = Securities[symbolHold].Price; foreach (var customIndicator in sortedEquityListBySlope.Where(x => x.Key == symbolHold)) { var numberStocks = (int)Math.Floor(risk / customIndicator.Value.Atr); if (Math.Abs(quantityHold - numberStocks) > 0 && quantityHold > 1) { // Sell or Buy the stocks diff if (quantityHold > numberStocks) { Sell(symbolHold, (quantityHold - numberStocks)); } else { //If the MarketRegimeIndicator indicator is RiskON, we buy stocks, otherwise not; if (riskON) { if (quantityHold < numberStocks) { decimal portfolioCashBalance = Portfolio.TotalPortfolioValue - Portfolio.TotalHoldingsValue; // Do we have cash to trade? if (portfolioCashBalance > ((numberStocks - quantityHold) * priceHold + (numberStocks - quantityHold) * priceHold * BrokerFee)) { Order(symbolHold, (numberStocks - quantityHold)); } } } } } } } } else { _rebalanceWeek = true; } //If the MarketRegimeIndicator indicator is RiskON, we buy stocks, otherwise not; if (riskON) { foreach (var customIndicatorItem in sortedEquityListBySlope) { CustomMomentumIndicator customIndicator = customIndicatorItem.Value; var symbol = customIndicator.Symbol; var inPortfolio = false; foreach (var security in Portfolio.Values.Where(x => x.Invested)) { if (security.Symbol == symbol) { inPortfolio = true; } } if (!inPortfolio) { var risk = Portfolio.TotalPortfolioValue * RiskPerContractOnPortfolio; var numberStocks = (int)Math.Floor(risk / customIndicator.Atr); var price = Securities[symbol].Price; if (numberStocks > 0) { decimal portfolioCashBalance = Portfolio.TotalPortfolioValue - Portfolio.TotalHoldingsValue; // Do we have cash to trade? if (portfolioCashBalance > (numberStocks * price + (numberStocks * price) * BrokerFee)) { Order(symbol, numberStocks); } } } } } } // creating custom indicators for each symbol public override void OnSecuritiesChanged(SecurityChanges changes) { if (changes.AddedSecurities.Count > 0) { foreach (Security security in changes.AddedSecurities) { if (!_customIndicators.ContainsKey(security.Symbol) && (security.Symbol.Value != "SPY")) { var customIndicator = new CustomMomentumIndicator(security.Symbol, _annualizedSlopeWindow, _movingAverageWindow, _gapWindow, _atrWindow); //warm up indicator var history = History(security.Symbol, customIndicator.Window, Resolution.Daily); foreach (TradeBar tradeBar in history) customIndicator.Update(tradeBar); _customIndicators.Add(security.Symbol, customIndicator); RegisterIndicator(security.Symbol, customIndicator, Resolution.Daily); } } } if (changes.RemovedSecurities.Count > 0) { foreach (var security in changes.RemovedSecurities) { if (security.Invested) { Liquidate(security.Symbol); } if (_customIndicators.ContainsKey(security.Symbol)) { _customIndicators.Remove(security.Symbol); } } } } } }