Overall Statistics |
Total Trades 5 Average Win 11.25% Average Loss 0% Compounding Annual Return 302.432% Drawdown 6.700% Expectancy 0 Net Profit 60.756% Sharpe Ratio 5.556 Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0.202 Annual Variance 0.041 Information Ratio 0 Tracking Error 0 Treynor Ratio 0 Total Fees $37.00 |
using MathNet.Numerics; using QuantConnect.Indicators; using System; using System.Linq; //Copied from this forum: //href https://www.quantconnect.com/forum/discussion/695/adjusted-slope--exponential-slope----annualized-slope--r-squuared--adjusted-slope/p1 namespace QuantConnect.Algorithm.CSharp.Helpers { public class AnnualizedExponentialSlopeIndicator : WindowIndicator<IndicatorDataPoint> { public AnnualizedExponentialSlopeIndicator(int period) : base("AESI(" + period + ")", period) { } public AnnualizedExponentialSlopeIndicator(string name, int period) : base(name, period) { } protected override decimal ComputeNextValue(IReadOnlyWindow<IndicatorDataPoint> window, IndicatorDataPoint input) { if (window.Count < 3) return 0m; var xVals = new double[window.Count]; var yVals = new double[window.Count]; // load input data for regression for (int i = 0; i < window.Count; i++) { xVals[i] = i; // we want the log of our y values yVals[i] = Math.Log((double)window[window.Count - i - 1].Value); } //http://numerics.mathdotnet.com/Regression.html // solves y=a + b*x via linear regression var fit = Fit.Line(xVals, yVals); var intercept = fit.Item1; var slope = fit.Item2; // compute rsquared var rsquared = GoodnessOfFit.RSquared(xVals.Select(x => intercept + slope * x), yVals); // 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 * Math.Pow(rsquared, 2); annualSlope = annualSlope * rsquared; if (annualSlope >= (double)decimal.MaxValue || annualSlope <= (double)decimal.MinValue) { annualSlope = -1000; //Debug("Negative slope due to arithmic overflow"); } return Convert.ToDecimal(annualSlope); } } }
using MathNet.Numerics; using MathNet.Numerics.Statistics; using System; using System.Collections.Concurrent; using System.Linq; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Indicators; using QuantConnect.Securities; using System.Collections.Generic; using QuantConnect.Data; using QuantConnect; using QuantConnect.Data.Fundamental; using QuantConnect.Brokerages; using QuantConnect.Algorithm.CSharp.Helpers; using QuantConnect.Data.Consolidators; namespace QuantConnect.Algorithm.CSharp { /* Momentum strategy according to ** Objective of the algorithm: ** ** Trade rules: ** The rules are: - Rule 00: ES futures ** Change history: ** 20180102_01 : Cannot get _spyMovingAverage to work. */ /// <summary> /// </summary> public class MomentumStrategyFuturesAlgorithm : QCAlgorithm { private const Resolution ResolutionAlgorithm = Resolution.Minute; // S&P 500 EMini futures private string[] roots = new[] { Futures.Indices.SP500EMini, //Futures.Metals.Gold, //^$TODO: do not add because current / previous contract goes wrong, futurechain to be added }; private HashSet<Symbol> _futureContracts = new HashSet<Symbol>(); private Dictionary<Symbol, AnnualizedExponentialSlopeIndicator> _annualizedExponentialSlope; private Dictionary<Symbol, SimpleMovingAverage> _simpelMovingAverage; private FuturesContract _currentContract = null; private FuturesContract _previousContract = null; // S&P 500 EMini futures private const string RootSP500 = Futures.Indices.SP500EMini; public Symbol SP500 = QuantConnect.Symbol.Create(RootSP500, SecurityType.Future, Market.USA); // Gold futures private const string RootGold = Futures.Metals.Gold; public Symbol Gold = QuantConnect.Symbol.Create(RootGold, SecurityType.Future, Market.USA); private const string SP500IndexSymbol = "SPY"; /// Rule 10: Momentum is calculated based on 90 past days annualized exponential regression slope; private int _momentumWindow = 12; // Rule 05: If the stock is below its 100 days moving average, sell it; private int _futureMovingAverageWindow = 7*24; // ATR private const int ATRWindowSize = 14; private Security _spy;// = QuantConnect.Symbol.Create(SP500IndexSymbol, SecurityType.Equity, Market.USA); private SimpleMovingAverage _spyMovingAverage; private decimal _spyPriceClose = 0; private decimal _esPriceClose = 0; private SecurityChanges _securityChanges = SecurityChanges.None; // Rule 08: If the SP500 is above the 200 days moving average we buy stocks, otherwise not; private int _trendfilter = 10; // Rule 04: If the stock is not in the top 100/ 20% ranking, sell it; private int _topNStockOfSp500 = 100; // Rule 06: If the stock gapped > 15%, sell it; private decimal _stockMaximumgap = 0.15m; // Look back period of stock gap private int _stockMaximumGapWindow = 60; // Rule 13: Trade maximum 30 stocks; private int _maxStockInPortfolio = 30; // Rule x: leverage increase if risk managable; private int LeverageFactor = 1; private bool _isDebugging = true; private bool _isPlotting = true; private bool _isPlotSpyMovingAverage = true; private int _isLogSpyMovingAveragePivot = 0; /// <summary> /// Helper to create AnnualizedExponentialSlopeIndicator /// </summary> /// <param name="symbol">symbol</param> /// <param name="period">period</param> /// <param name="resolution">resolution of data</param> /// <returns></returns> public AnnualizedExponentialSlopeIndicator AESI(string symbol, int period, Resolution? resolution = null, Func<IBaseData, decimal> selector = null) { var name = CreateIndicatorName(symbol, string.Format("AESI({0})", period), resolution); var aesi = new AnnualizedExponentialSlopeIndicator(name, period); RegisterIndicator(symbol, aesi, resolution, selector); return aesi; } /// <summary> /// 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() { //speed up execution if (IsDebugging) { _isPlotSpyMovingAverage = true; _trendfilter = 100; _topNStockOfSp500 = 20; _stockMaximumGapWindow = 14; _maxStockInPortfolio = 5; _futureMovingAverageWindow = 100; _momentumWindow = 90; } //Set trading window SetStartDate(year: 2017, month: 10, day: 1); SetEndDate(year: 2018, month: 2, day: 1); //SetEndDate(DateTime.Now); foreach (var root in roots) { // set our expiry filter for this futures chain AddFuture(root, ResolutionAlgorithm).SetFilter(TimeSpan.Zero, TimeSpan.FromDays(182)); } _annualizedExponentialSlope = new Dictionary<Symbol, AnnualizedExponentialSlopeIndicator>(); _simpelMovingAverage = new Dictionary<Symbol, SimpleMovingAverage>(); var benchmark = AddEquity(SP500IndexSymbol); SetBenchmark(benchmark.Symbol); SetBenchmark(d => 1m); //Set brokermodel SetCash(100000); SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin); //Set moving average on SPY and benchmark _spy = AddEquity(SP500IndexSymbol, Resolution.Minute); //_spyMovingAverage = SMA(SP500IndexSymbol, _trendfilter, Resolution.Daily); var consolidator = new TradeBarConsolidator(TimeSpan.FromDays(1)); consolidator.DataConsolidated += OnTradeBarDataConsolidated; SubscriptionManager.AddConsolidator(SP500IndexSymbol, consolidator); _spyMovingAverage = new SimpleMovingAverage(_trendfilter); RegisterIndicator(SP500IndexSymbol, _spyMovingAverage, consolidator); Log("Added new consolidator for " + _spy.Symbol.Value); //warm up https://www.quantconnect.com/forum/discussion/2557/dealing-with-futures-and-technical-indicators/p1 var history = History(SP500IndexSymbol, _trendfilter * 24 * 60, Resolution.Minute); foreach (var bar in history) { if (bar.EndTime.Minute == 0) { _spyMovingAverage.Update(new IndicatorDataPoint(SP500IndexSymbol, bar.EndTime, bar.Close)); } } //set warm up algorithm to avoid premature trading SetWarmUp(_trendfilter + 1); if (IsDebugging) { Log("*** DEBUGGING: TAKE CARE OF PARAMETERS ***"); } } /// Is debugging set, speed up processing public bool IsDebugging { get { return _isDebugging; } } /// Is plotting set public bool IsPlotting { get { return _isPlotting; } } /// <summary> /// Raises the data event. /// </summary> /// <param name="data">Data.</param> public void OnData(TradeBars data) { foreach (var bar in data.Values) { if (data.ContainsKey(SP500IndexSymbol)) { _spyPriceClose = data[SP500IndexSymbol].Close; } } } /// <summary> /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. /// </summary> /// <param name="slice">Slice object keyed by symbol containing the stock data</param> public override void OnData(Slice slice) { #region SecurityChanges // if we have no changes, do nothing if (_securityChanges != SecurityChanges.None) { // Liquidate removed securities that do not rank anymore // Rule 07: If the stock left the index, sell it; foreach (var security in _securityChanges.RemovedSecurities) { if (security.Invested) { Liquidate(security.Symbol); } //clean up dictionairies if (security.Type == SecurityType.Future) { if (_annualizedExponentialSlope.ContainsKey(security.Symbol)) { _annualizedExponentialSlope.Remove(security.Symbol); Log(string.Format("_annualizedExponentialSlope removed {0}", security.Symbol)); } if (_simpelMovingAverage.ContainsKey(security.Symbol)) { _simpelMovingAverage.Remove(security.Symbol); Log(string.Format("_simpelMovingAverage removed {0}", security.Symbol)); } } } //indicate that_changes are processed _securityChanges = SecurityChanges.None; } #endregion SecurityChanges #region consolidator of future data into days foreach (var chain in slice.FutureChains) { //Log(string.Format("slice.FutureChain: {0}", chain.ToString())); // find the front contract expiring no earlier than in 15 days var firstContract = ( from futuresContract in chain.Value.OrderBy(x => x.Expiry) where futuresContract.Expiry > Time.Date.AddDays(15) select futuresContract ).FirstOrDefault(); if (firstContract == null) return; //Log(string.Format("firstContract: {0}", firstContract.Symbol)); if(_currentContract == null || (_currentContract.Symbol != firstContract.Symbol)) { _previousContract = _currentContract; _currentContract = firstContract; Log(string.Format("CurrentContract: {0}, PreviousContract: {1}", _currentContract.Symbol, _previousContract == null ? "-" : _previousContract.Symbol.ToString())); } foreach (var contract in chain.Value) { if (!_futureContracts.Contains(contract.Symbol)) { _futureContracts.Add(contract.Symbol); var consolidator = new QuoteBarConsolidator(TimeSpan.FromHours(1)); consolidator.DataConsolidated += OnQuoteBarDataConsolidated; SubscriptionManager.AddConsolidator(contract.Symbol, consolidator); _annualizedExponentialSlope[contract.Symbol] = new AnnualizedExponentialSlopeIndicator(_momentumWindow); _simpelMovingAverage[contract.Symbol] = new SimpleMovingAverage(_futureMovingAverageWindow); RegisterIndicator(contract.Symbol, _annualizedExponentialSlope[contract.Symbol], consolidator); RegisterIndicator(contract.Symbol, _simpelMovingAverage[contract.Symbol], consolidator); Log("Added new consolidator(s) for " + contract.Symbol.Value); //warm up https://www.quantconnect.com/forum/discussion/2557/dealing-with-futures-and-technical-indicators/p1 var history = History(contract.Symbol, (Math.Max(_annualizedExponentialSlope[contract.Symbol].Period, _simpelMovingAverage[contract.Symbol].Period) + 1) * 60, Resolution.Minute); foreach (var bar in history) { if (bar.EndTime.Minute == 0) { _annualizedExponentialSlope[contract.Symbol].Update(new IndicatorDataPoint(contract.Symbol, bar.EndTime, bar.Close)); _simpelMovingAverage[contract.Symbol].Update(new IndicatorDataPoint(contract.Symbol, bar.EndTime, bar.Close)); } } Log(string.Format("_annualizedExponentialSlope.IsReady: {0}", _annualizedExponentialSlope[contract.Symbol].IsReady.ToString())); Log(string.Format("_simpelMovingAverage.IsReady: {0}", _simpelMovingAverage[contract.Symbol].IsReady.ToString())); } } } #endregion consolidator of future data into days if (IsWarmingUp) { //Log("IsWarmingUp"); return; } if (!Securities.ContainsKey(_spy.Symbol)) { Log(string.Format("!Securities.ContainsKey(_spy.Symbol:{0})", _spy.Symbol)); return; } //- Rule 08: If the SP500 is above the 200 days moving average we buy stocks, otherwise not; if (Securities[_spy.Symbol].Price <= _spyMovingAverage.Current.Value) { //$TODO: buy T-note/ gold/ silver? if (_isLogSpyMovingAveragePivot >= 0) { Log(string.Format("Spy in downtrend: {0} < {1}", Securities[_spy.Symbol].Price, _spyMovingAverage.Current.Value)); _isLogSpyMovingAveragePivot = -1; } } else { if (_isLogSpyMovingAveragePivot <= 0) { Log(string.Format("Spy in uptrend: {0} > {1}", Securities[_spy.Symbol].Price, _spyMovingAverage.Current.Value)); _isLogSpyMovingAveragePivot = 1; } //decimal investedFuturesQuantity = 0; //foreach (var securityKeyValuePair in Portfolio.Securities) //{ // Security security = securityKeyValuePair.Value; // if (security.Type == SecurityType.Future) // { // investedFuturesQuantity += security.Holdings.Quantity; // } //} if (_currentContract != null) { if(!_annualizedExponentialSlope.ContainsKey(_currentContract.Symbol)) { Log(string.Format("!_annualizedExponentialSlope.ContainsKey({0})", _currentContract.Symbol)); return; } if (!_simpelMovingAverage.ContainsKey(_currentContract.Symbol)) { Log(string.Format("!_simpelMovingAverage.ContainsKey({0})", _currentContract.Symbol)); return; } //Contract is most likely to (be) expire soon; liquidate contract if (_previousContract != null && Portfolio[_previousContract.Symbol].Invested) { Log(string.Format("!Liquidate(_previousContract.Symbol={0})", _previousContract.Symbol)); Liquidate(_previousContract.Symbol); //$TODO: buy immediately new current contract or decide based on new market entry. } if (!Portfolio[_currentContract.Symbol].Invested) { if (_annualizedExponentialSlope[_currentContract.Symbol] <= 0 && _simpelMovingAverage[_currentContract.Symbol] > _currentContract.LastPrice) { Log(string.Format("_annualizedExponentialSlope[{0}]={1} > 0", _currentContract.Symbol, _annualizedExponentialSlope[_currentContract.Symbol])); // Do we have cash to trade? if (Portfolio.Cash <= 0) { Log(string.Format("Portfolio.Cash <= 0 for symbol {0}", _currentContract.Symbol)); return; } //decimal estimatedPortfolioCashBalance = Portfolio.Cash - _currentContract.LastPrice; //if (estimatedPortfolioCashBalance >= 0) //SetHoldings(_currentContract.Symbol, 1); //does not work MarketOrder(_currentContract.Symbol, 4); } } else { if(_simpelMovingAverage[_currentContract.Symbol].Current.Value < _currentContract.LastPrice) { Log(string.Format("_simpelMovingAverage[{0}].Current.Value < _currentContract.LastPrice {1}", _currentContract.Symbol, _currentContract.LastPrice)); Liquidate(_currentContract.Symbol); //$TODO: replace by SP500 moving average. } } } } ///Plotting if (IsPlotting) { //foreach (var chain in slice.FutureChains) //{ // foreach (var contract in chain.Value) // { // Log(String.Format("{0},Bid={1} Ask={2} Last={3} OI={4}", // contract.Symbol.Value, // contract.BidPrice, // contract.AskPrice, // contract.LastPrice, // contract.OpenInterest)); // } //} } } private void OnQuoteBarDataConsolidated(object sender, QuoteBar quoteBar) { //Log(quoteBar.ToString()); //if(_annualizedExponentialSlope.ContainsKey(quoteBar.Symbol)) //{ // Log(_annualizedExponentialSlope[quoteBar.Symbol].ToDetailedString()); //} } private void OnTradeBarDataConsolidated(object sender, TradeBar tradeBar) { Log("****" + tradeBar.ToString()); } /// <summary> /// Portfolio risk weight /// - Rule 09: Calculate the position sizes, based on 10 basis points using ATR formula; /// - Rule 12: Position Size = (portfolioSize * 0, 001 / ATR) = #Shares; /// </summary> /// <param name="symbol"></param> /// <param name="atr"></param> /// <returns></returns> public decimal GetPositionSize(Symbol symbol, decimal atr) { if (atr == 0) return 0; decimal risk = this.Portfolio.TotalPortfolioValue * 0.001m; return (decimal)((risk / atr) * Securities[symbol].Price) / Portfolio.TotalPortfolioValue * 100; } /// <summary> /// Get the Average True Range (ATR) /// </summary> /// <param name="symbol"></param> /// <param name="windowSize"></param> /// <returns></returns> public decimal GetATR(Symbol symbol, int windowSize) { //validate that the security is in the universe if (!Securities.ContainsKey(symbol)) return 0; IEnumerable<Slice> slices = History(windowSize, Resolution.Daily); var window = slices.Get(symbol, Field.Close).ToList(); if (window.Count < 3) return 0m; var atr = ATR(symbol, windowSize, MovingAverageType.Exponential, Resolution.Daily); return atr.Current.Value; } /// <summary> /// Calculate Mean /// </summary> private decimal GetMean(Symbol symbol, int windowSize) { //validate that the security is in the universe if (!Securities.ContainsKey(symbol)) return 0; IEnumerable<Slice> slices = History(windowSize, Resolution.Daily); IEnumerable<decimal> close = slices.Get(symbol, Field.Close); var closes = close.ToDoubleArray(); return (decimal)(closes.Mean()); } /// <summary> /// Calculate Gap /// return np.max(np.abs(np.diff(close_data))/close_data[:-1]) // out[n] = (a[n+1] - a[n]) / a[n] /// </summary> private decimal GetGap(Symbol symbol, int windowSize) { //validate that the security is in the universe if (!Securities.ContainsKey(symbol)) return 0; IEnumerable<Slice> slices = History(windowSize, Resolution.Daily); var window = slices.Get(symbol, Field.Close).ToList(); //var closes = close.ToDoubleArray(); 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])); } return (decimal)diff.MaximumAbsolute(); } // this event fires whenever we have changes to our universe public override void OnSecuritiesChanged(SecurityChanges changes) { _securityChanges = changes; if (changes.AddedSecurities.Count > 0) { Log("Securities added: " + string.Join(",", changes.AddedSecurities.Select(x => x.Symbol.Value))); } if (changes.RemovedSecurities.Count > 0) { Log("Securities removed: " + string.Join(",", changes.RemovedSecurities.Select(x => x.Symbol.Value))); } foreach (var change in changes.AddedSecurities) { var history = History<TradeBar>(change.Symbol, 1, Resolution.Daily); foreach (var data in history.OrderByDescending(x => x.Time).Take(1)) { Log("History: " + data.Symbol.Value + ": " + data.Time + " > " + data.Close); } } } // Fire plotting events once per day: public override void OnEndOfDay() { //warm up https://www.quantconnect.com/forum/discussion/2557/dealing-with-futures-and-technical-indicators/p1 var history = History(SP500IndexSymbol, 1 * 60, Resolution.Minute); foreach (var bar in history) { if (bar.EndTime.Minute == 0) { _spyMovingAverage.Update(new IndicatorDataPoint(SP500IndexSymbol, bar.EndTime, bar.Close)); } } if (!_spyMovingAverage.IsReady) Log("*** !_spyMovingAverage.IsReady ***"); if (_spyMovingAverage.Current.Value == 0) Log("*** _spyMovingAverage.Current.Value == 0 ***"); if (_currentContract != null) { if (!_annualizedExponentialSlope.ContainsKey(_currentContract.Symbol)) Log(string.Format("_annualizedExponentialSlope does not contain {0}", _currentContract.Symbol)); if (!_simpelMovingAverage.ContainsKey(_currentContract.Symbol)) Log(string.Format("_simpelMovingAverage does not contain {0}", _currentContract.Symbol)); if (!_annualizedExponentialSlope[_currentContract.Symbol].IsReady) Log(string.Format("{0} _annualizedExponentialSlope.IsReady: {1}", _currentContract.Symbol, _annualizedExponentialSlope[_currentContract.Symbol].IsReady.ToString())); if(!_simpelMovingAverage[_currentContract.Symbol].IsReady) Log(string.Format("{0} _simpelMovingAverage.IsReady: {1}", _currentContract.Symbol, _simpelMovingAverage[_currentContract.Symbol].IsReady.ToString())); } ///Plotting //Assuming daily mode,dont chart in a smaller res and kill quota if (IsPlotting) { Plot(SP500IndexSymbol, "Price", _spyPriceClose); if (_isPlotSpyMovingAverage) { Plot(SP500IndexSymbol, _spyMovingAverage); } if (_currentContract != null) { Plot("ES", "Price", _currentContract.LastPrice); if (_annualizedExponentialSlope.ContainsKey(_currentContract.Symbol)) { Plot("ES", _annualizedExponentialSlope[_currentContract.Symbol].Current.Value); } } if (Portfolio.TotalPortfolioValue > 0) { var accountLeverage = Portfolio.TotalAbsoluteHoldingsCost / Portfolio.TotalPortfolioValue; Plot("Leverage", "Leverage", accountLeverage); } } } } }