| Overall Statistics |
|
Total Orders 28 Average Win 1.69% Average Loss -2.51% Compounding Annual Return -21.447% Drawdown 21.800% Expectancy -0.485 Start Equity 1000000 End Equity 833158.73 Net Profit -16.684% Sharpe Ratio -1.182 Sortino Ratio -1.142 Probabilistic Sharpe Ratio 1.904% Loss Rate 69% Win Rate 31% Profit-Loss Ratio 0.67 Alpha -0.217 Beta 0.35 Annual Standard Deviation 0.167 Annual Variance 0.028 Information Ratio -1.305 Tracking Error 0.194 Treynor Ratio -0.563 Total Fees $415.71 Estimated Strategy Capacity $0 Lowest Capacity Asset NFLX SEWJWLJNHZDX Portfolio Turnover 3.52% Drawdown Recovery 4 |
#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 = 100;
///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(2000, 1, 1);
SetEndDate(2025, 1, 1);
//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);
}
}
}
}
}
}