- ago
Here's a C# extension method for ordering the chart panes alphabetically by paneTag with a couple of exceptions (Price and Volume). See the method's comments for details.

CODE:
using System.Collections.Generic; using System.Linq; using System.Reflection; using WealthLab.Backtest; using WealthLab.Indicators; namespace WLUtility.Extensions { public static class UsbExtensions { public enum VolumePaneSortOrder { Last, AccordingToPaneSortOrder, AfterPrice } /// <summary> /// Sort the panes in alphabetical order. However, the "Price" pane is always first. /// The "Volume" pane can be placed immediately after the "Price" pane, as the last pane or /// in alphabetical order. /// Supply otherPaneTags for panes that are for TimeSeries and other drawings. This is /// necessary because as of WL8 build 74 a list of other such panes does not seem readily available. /// Place a call to this method after your code has plotted all its panes. /// Note: default heights are assumed (100 for Price, 33 for Volume, 33 for all others). /// Usage from your UserStrategyBase class after all plot calls: /// this.SortPanesByPaneTag(UsbExtensions.VolumePaneSortOrder.AfterPrice, "MyTimeSeriesPane1", /// "MyTimeSeriesPane2"); /// </summary> /// <param name="userStrategyBase"> /// The user strategy for which the panes are to be sorted alphabetically with exceptions /// noted in summary, above. /// </param> /// <param name="volumePaneSortOrder"> /// Specifies the order in which the "Volume" pane is to appear /// relative to the other panes. Use VolumePaneSortOrder.Last to place the Volume pane at the /// bottom. Use VolumePaneSortOrder.AfterPrice to place Volume pane immediately after the /// Price pane. Use VolumePaneSortOrder.AccordingToPaneSortOrder to have it placed in /// alphabetical order (Price is always first, regardless). The default is Last. /// </param> /// <param name="otherPaneTags"> /// Provide any additional paneTag(s) for panes that result from plotting BarHistory or /// TimeSeries and any calls to drawing methods that specify a unique paneTag not covered otherwise. /// Here, for otherPaneTags, your code does not need to supply the paneTags for plotted indicators, /// nor Price nor Volume. /// If your code doesn't supply a paneTag that is related to a non-indicator plotted pane then that pane will /// be placed after all of the other pane tags that could be ordered, but Volume being the exception if /// it is specified to be Last. /// </param> public static void SortPanesByPaneTag(this UserStrategyBase userStrategyBase, VolumePaneSortOrder volumePaneSortOrder = VolumePaneSortOrder.Last, params string[] otherPaneTags) { // get a list of properties of the userStrategyBase instance that are indicators var props = userStrategyBase.GetType() .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where( propertyInfo => propertyInfo.PropertyType.IsAssignableTo(typeof(IndicatorBase))).ToList(); // collect the pane names in a HashSet (note that the HashSet will eliminate duplicates) var panes = new HashSet<string>(); foreach (var indicatorBase in props .Select(propertyInfo => (IndicatorBase) propertyInfo.GetValue(userStrategyBase)) .Where(indicatorBase => indicatorBase != null && !string.IsNullOrEmpty(indicatorBase.PaneTag))) { panes.Add(indicatorBase.PaneTag); } // tack on the otherPaneTags foreach (var paneTag in otherPaneTags.Where(p => !string.IsNullOrEmpty(p))) { panes.Add(paneTag); } // insure price and volume are present in the pane list panes.Add("Price"); panes.Add("Volume"); // To keep the Price pane at the top, Price is 0. Volume might be 1. Hence, start at 2. var sortValue = 2; foreach (var paneTag in panes.OrderBy(p => p)) { var isPricePane = paneTag == "Price"; var isVolumePane = paneTag == "Volume"; userStrategyBase.SetPaneDrawingOptions(paneTag, isPricePane ? 100 : 33, isPricePane ? 0 : isVolumePane ? GetVolumePaneSortOrder() : sortValue); sortValue++; } return; int GetVolumePaneSortOrder() { return volumePaneSortOrder == VolumePaneSortOrder.AfterPrice ? 1 : volumePaneSortOrder == VolumePaneSortOrder.Last ? 100 : sortValue; } } } }
2
171
Solved
3 Replies

Reply

Bookmark

Sort
- ago
#1
Below is updated code that fixes a bug where the original code did not take into account the UserAssignedPaneTag. Also, the updated code now does most of the work in one LINQ statement...

CODE:
using System.Linq; using System.Reflection; using WealthLab.Backtest; using WealthLab.Indicators; namespace WLUtility.Extensions { public static class UsbExtensions { public enum VolumePaneSortOrder { Last, AccordingToPaneSortOrder, AfterPrice } /// <summary> /// Sort the panes in alphabetical order. However, the "Price" pane is always first. /// The "Volume" pane can be placed immediately after the "Price" pane, as the last pane or /// in alphabetical order. /// Supply otherPaneTags for panes that are for TimeSeries and other drawings. This is /// necessary because as of WL8 build 74 a list of other such panes is not readily available. /// Place a call to this method after your code has plotted all its panes. /// Note: default heights are assumed (100 for Price, 33 for Volume, 33 for all others). /// Usage from your UserStrategyBase class after all plot calls: /// this.SortPanesByPaneTag(UsbExtensions.VolumePaneSortOrder.AfterPrice, "MyTimeSeriesPane1", /// "MyTimeSeriesPane2"); /// </summary> /// <param name="userStrategyBase"> /// The user strategy for which the panes are to be sorted alphabetically with exceptions /// noted in summary, above. /// </param> /// <param name="volumePaneSortOrder"> /// Specifies the order in which the "Volume" pane is to appear /// relative to the other panes. Use VolumePaneSortOrder.Last to place the Volume pane at the /// bottom. Use VolumePaneSortOrder.AfterPrice to place Volume pane immediately after the /// Price pane. Use VolumePaneSortOrder.AccordingToPaneSortOrder to have it placed in /// alphabetical order (Price is always first, regardless). The default is Last. /// </param> /// <param name="otherPaneTags"> /// Provide any additional paneTag(s) for panes that result from plotting BarHistory or /// TimeSeries and any calls to drawing methods that specify a unique paneTag not covered otherwise. /// Here, for otherPaneTags, your code does not need to supply the paneTags for plotted indicators, /// nor Price nor Volume. /// If your code doesn't supply a paneTag that is related to a non-indicator plotted pane then that pane will /// be placed after all of the other pane tags that could be ordered, but Volume being the exception if /// it is specified to be Last. /// </param> public static void SortPanesByPaneTag(this UserStrategyBase userStrategyBase, VolumePaneSortOrder volumePaneSortOrder = VolumePaneSortOrder.Last, params string[] otherPaneTags) { // To keep the Price pane at the top, Price is 0. Volume might be 1. Hence, start at 2. var sortValue = 2; // HashSet will eliminate duplicates... foreach (var paneTag in userStrategyBase.GetType() .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Where(propertyInfo => propertyInfo.PropertyType.IsAssignableTo(typeof(IndicatorBase))) .Select(propertyInfo => (IndicatorBase) propertyInfo.GetValue(userStrategyBase)) .Where(indicatorBase => indicatorBase != null && !string.IsNullOrEmpty(indicatorBase.PaneTag) && indicatorBase.PaneTag != "Price") .Select(indicatorBase => !string.IsNullOrEmpty(indicatorBase.UserAssignedPaneTag) ? indicatorBase.UserAssignedPaneTag : indicatorBase.PaneTag).Append("Volume") .Concat(otherPaneTags.Where(p => !string.IsNullOrEmpty(p))).ToHashSet().OrderBy(p => p)) { userStrategyBase.SetPaneDrawingOptions(paneTag, 33, paneTag == "Volume" ? volumePaneSortOrder == VolumePaneSortOrder.AfterPrice ? 1 : volumePaneSortOrder == VolumePaneSortOrder.Last ? 100 : sortValue : sortValue); sortValue++; } userStrategyBase.SetPaneDrawingOptions("Price", 100, 0); } } }
0
- ago
#2
Here is an updated version that includes support for indicators stored in properies and fields. (I overlooked fields in prior version because I always use properties).

CODE:
using System.Linq; using System.Reflection; using WealthLab.Backtest; using WealthLab.Indicators; namespace WLUtility.Extensions { public static class UsbExtensions { public enum VolumePaneSortOrder { Last, AccordingToPaneSortOrder, AfterPrice } /// <summary> /// Sort the panes in alphabetical order. However, the "Price" pane is always first. /// The "Volume" pane can be placed immediately after the "Price" pane, as the last pane or /// in alphabetical order. /// Supply otherPaneTags for panes that are for TimeSeries and other drawings. This is /// necessary because as of WL8 build 74 a list of other such panes is not readily available. /// Place a call to this method after your code has plotted all its panes. /// Note: default heights are assumed (100 for Price, 33 for Volume, 33 for all others). /// Usage from your UserStrategyBase class after all plot calls: /// this.SortPanesByPaneTag(UsbExtensions.VolumePaneSortOrder.AfterPrice, "MyTimeSeriesPane1", /// "MyTimeSeriesPane2"); /// </summary> /// <param name="userStrategyBase"> /// The user strategy for which the panes are to be sorted alphabetically with exceptions /// noted in summary, above. /// </param> /// <param name="volumePaneSortOrder"> /// Specifies the order in which the "Volume" pane is to appear /// relative to the other panes. Use VolumePaneSortOrder.Last to place the Volume pane at the /// bottom. Use VolumePaneSortOrder.AfterPrice to place Volume pane immediately after the /// Price pane. Use VolumePaneSortOrder.AccordingToPaneSortOrder to have it placed in /// alphabetical order (Price is always first, regardless). The default is Last. /// </param> /// <param name="otherPaneTags"> /// Provide any additional paneTag(s) for panes that result from plotting BarHistory or /// TimeSeries and any calls to drawing methods that specify a unique paneTag not covered otherwise. /// Here, for otherPaneTags, your code does not need to supply the paneTags for plotted indicators, /// nor Price nor Volume. /// If your code doesn't supply a paneTag that is related to a non-indicator plotted pane then that pane will /// be placed after all of the other pane tags that could be ordered, but Volume being the exception if /// it is specified to be Last. /// </param> public static void SortPanesByPaneTag(this UserStrategyBase userStrategyBase, VolumePaneSortOrder volumePaneSortOrder = VolumePaneSortOrder.Last, params string[] otherPaneTags) { // To keep the Price pane at the top, Price is 0. Volume might be 1. Hence, start at 2. var sortValue = 2; // HashSet will eliminate duplicates... foreach (var paneTag in userStrategyBase.GetType() .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Where(fieldInfo => fieldInfo.FieldType.IsAssignableTo(typeof(IndicatorBase))) .Select(fieldInfo => (IndicatorBase) fieldInfo.GetValue(userStrategyBase)) .Where(indicatorBase => indicatorBase != null && !string.IsNullOrEmpty(indicatorBase.PaneTag) && indicatorBase.PaneTag != "Price") .Select(indicatorBase => !string.IsNullOrEmpty(indicatorBase.UserAssignedPaneTag) ? indicatorBase.UserAssignedPaneTag : indicatorBase.PaneTag).Append("Volume") .Concat(otherPaneTags.Where(p => !string.IsNullOrEmpty(p))).ToHashSet().OrderBy(p => p)) { userStrategyBase.SetPaneDrawingOptions(paneTag, 33, paneTag == "Volume" ? volumePaneSortOrder == VolumePaneSortOrder.AfterPrice ? 1 : volumePaneSortOrder == VolumePaneSortOrder.Last ? 100 : sortValue : sortValue); sortValue++; } userStrategyBase.SetPaneDrawingOptions("Price", 100, 0); } } }


Edit: Removed properties reflection lookup because properties have backing fields. So, it is only necessary to query for the fields that store indicators.
1
Best Answer
Glitch8
 ( 12.05% )
- ago
#3
Now THAT’s a Linq statement! 😳
1

Reply

Bookmark

Sort