ninjatrader/indicators/IndexTopN.cs

229 lines
8.5 KiB
C#
Raw Normal View History

#region Using declarations
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
2024-03-13 21:21:40 +00:00
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 = @"<tr>\s*<td>\d+</td>\s*<td><a href=""/symbol/[A-Z.]+?"">(.+?)</a></td>\s*<td><a href=""/symbol/[A-Z.]+?"">([A-Z.]+?)</a></td>\s*<td>(.+?)%</td>";
private List<string> Components = new List<string>();
private Dictionary<string, double> ComponentWeightings = new Dictionary<string, double>();
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));
}
2024-03-13 21:21:40 +00:00
// 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<double> 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<double> 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<IndexTopN>(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<double> 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<double> input , MarketIndex trackingIndex, int numComponents)
{
return indicator.IndexTopN(input, trackingIndex, numComponents);
}
}
}
#endregion