#region Using declarations using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; using System.Windows.Media; using System.Xml.Serialization; using NinjaTrader.Cbi; using NinjaTrader.Data; using NinjaTrader.Gui; using NinjaTrader.Gui.Chart; #endregion //This namespace holds Indicators in this folder and is required. Do not change it. namespace NinjaTrader.NinjaScript.Indicators { [CategoryOrder("Index Top N", 1)] [CategoryOrder("Plots", 2)] public class IndexTopN : Indicator { private const int PrimaryBars = 0; private const int RegularTradingHoursBars = 1; private const int ComponentBarsStartIndex = 2; private TimeSpan RegularTradingHoursOpen; private TimeSpan RegularTradingHoursClose; private const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"; // Regular expression to match the Slickcharts table row containing the components' symbols and index weightings. private const string ComponentRegex = @"\s*\d+\s*(.+?)\s*([A-Z.]+?)\s*(.+?)%"; private List Components = new List(); private Dictionary ComponentWeightings = new Dictionary(); protected override void OnStateChange() { if (State == State.SetDefaults) { Description = @"Plots the top N components (weighted by market cap) of the specified index."; Name = "Index Top N"; Calculate = Calculate.OnPriceChange; IsOverlay = true; DisplayInDataBox = true; DrawOnPricePanel = true; ScaleJustification = ScaleJustification.Overlay; IsSuspendedWhileInactive = false; TrackingIndex = MarketIndex.SP500; NumComponents = 10; AddPlot(new Stroke(Brushes.Yellow, DashStyleHelper.Solid, 3), PlotStyle.Line, "Top N Index"); } else if (State == State.Configure) { // For determining when RTH begins and ends. AddDataSeries(Instrument.FullName, new BarsPeriod { BarsPeriodType = BarsPeriodType.Day, Value = 1 }, "US Equities RTH"); RetrieveComponentData(); foreach (var component in Components) { //Replace "." with "_" in component name to avoid issues with NinjaTrader's naming conventions and "BRK.B". AddDataSeries(component.Replace(".", "_")); } } else if (State == State.DataLoaded) { SessionIterator regularTradingHoursSession = new SessionIterator(BarsArray[RegularTradingHoursBars]); RegularTradingHoursOpen = regularTradingHoursSession .GetTradingDayBeginLocal(regularTradingHoursSession.ActualTradingDayExchange).TimeOfDay; RegularTradingHoursClose = regularTradingHoursSession .GetTradingDayEndLocal(regularTradingHoursSession.ActualTradingDayExchange).TimeOfDay; } else if (State == State.Historical) { SetZOrder(-1); // Display behind bars on chart. } } protected override void OnBarUpdate() { if (BarsInProgress == PrimaryBars && IsRegularTradingHours()) { double indexValue = 0; for (int i = ComponentBarsStartIndex; i < BarsArray.Length; i++) { string component = Components[i - ComponentBarsStartIndex]; int bar = BarsArray[i].GetBar(Time[0]); indexValue += BarsArray[i].GetClose(bar) * ComponentWeightings[component]; } if (indexValue > 0) Index[0] = indexValue; } } private bool IsRegularTradingHours() { return Time[0].TimeOfDay > RegularTradingHoursOpen && Time[0].TimeOfDay <= RegularTradingHoursClose; } private void RetrieveComponentData() { string url = "https://www.slickcharts.com/sp500"; if (TrackingIndex == MarketIndex.NASDAQ) url = "https://www.slickcharts.com/nasdaq100"; var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Add("User-Agent", UserAgent); var htmlContent = httpClient.GetStringAsync(url).Result; int count = 0; var matches = Regex.Matches(htmlContent, ComponentRegex); foreach (Match match in matches) { if (count++ >= NumComponents) break; var symbol = match.Groups[2].Value; var percentage = match.Groups[3].Value; Components.Add(symbol); ComponentWeightings.Add(symbol, double.Parse(percentage)); } // Normalize the component weightings to add up to 1. double totalWeighting = ComponentWeightings.Values.Sum(); foreach (var component in Components) { ComponentWeightings[component] /= totalWeighting; } } public override string DisplayName { get { string indexName = (TrackingIndex == MarketIndex.NASDAQ) ? "NASDAQ" : "S&P 500"; return indexName + " Top " + NumComponents; } } #region Plots [Browsable(false)] [XmlIgnore] public Series Index { get { return Values[0]; } } #endregion #region Properties [NinjaScriptProperty] [Display(Name = "Tracking Index", GroupName = "Index Top N", Order = 1)] public MarketIndex TrackingIndex { get; set; } [Range(1, int.MaxValue), NinjaScriptProperty] [Display(Name = "Number of Components", GroupName = "Index Top N", Order = 2)] public int NumComponents { get; set; } #endregion } } public enum MarketIndex { NASDAQ, SP500 } #region NinjaScript generated code. Neither change nor remove. namespace NinjaTrader.NinjaScript.Indicators { public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase { private IndexTopN[] cacheIndexTopN; public IndexTopN IndexTopN(MarketIndex trackingIndex, int numComponents) { return IndexTopN(Input, trackingIndex, numComponents); } public IndexTopN IndexTopN(ISeries input, MarketIndex trackingIndex, int numComponents) { if (cacheIndexTopN != null) for (int idx = 0; idx < cacheIndexTopN.Length; idx++) if (cacheIndexTopN[idx] != null && cacheIndexTopN[idx].TrackingIndex == trackingIndex && cacheIndexTopN[idx].NumComponents == numComponents && cacheIndexTopN[idx].EqualsInput(input)) return cacheIndexTopN[idx]; return CacheIndicator(new IndexTopN(){ TrackingIndex = trackingIndex, NumComponents = numComponents }, input, ref cacheIndexTopN); } } } namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns { public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase { public Indicators.IndexTopN IndexTopN(MarketIndex trackingIndex, int numComponents) { return indicator.IndexTopN(Input, trackingIndex, numComponents); } public Indicators.IndexTopN IndexTopN(ISeries input , MarketIndex trackingIndex, int numComponents) { return indicator.IndexTopN(input, trackingIndex, numComponents); } } } namespace NinjaTrader.NinjaScript.Strategies { public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase { public Indicators.IndexTopN IndexTopN(MarketIndex trackingIndex, int numComponents) { return indicator.IndexTopN(Input, trackingIndex, numComponents); } public Indicators.IndexTopN IndexTopN(ISeries input , MarketIndex trackingIndex, int numComponents) { return indicator.IndexTopN(input, trackingIndex, numComponents); } } } #endregion