- ago
I have defined an indicator that takes two TimeSeries objects as inputs. I would like to parse some of their characteristics to formulate a unique cache Key, but I can't seem to extract any information about them. The lines sourceDescription = source.Description; weightsDescription = weights.Description; in the code below don't return anything useful. Why aren't they working, and what's the workaround to formulating the unique cache Key for these TimeSeries parameters?

This is for WL8 Build 173.

CODE:
   public VWEMA(TimeSeries source, TimeSeries weights, int period, string description = "") : base()    {       Parameters[0].Value = source;       Parameters[1].Value = weights;       Parameters[2].Value = period;       Parameters[3].Value = description;       Populate();    }    public override void Populate()    {       TimeSeries source = Parameters[0].AsTimeSeries;       TimeSeries weights = Parameters[1].AsTimeSeries;       sourceDescription = source.Description;       weightsDescription = weights.Description;       int period = Parameters[2].AsInt;       string description = Parameters[3].AsString;    . . .    }       protected override void GenerateParameters()    {       AddParameter("Source", ParameterType.TimeSeries, PriceComponent.Close);       AddParameter("Volume|Weights", ParameterType.TimeSeries, PriceComponent.Volume);       AddParameter("Period", ParameterType.Int32, 10); //RangeBoundInt32(14,2,Int32.MaxValue)       AddParameter("Description", ParameterType.String, "VWEMA(" + sourceDescription + "," + weightsDescription + "," + Parameters[2].AsInt + ")");    }
0
111
9 Replies

Reply

Bookmark

Jump to End
- ago
#1
Instead of source and weights.Description you could use source and weights.ToString() which will return a string that includes all of the parameter values.
0
- ago
#2
sourceDescription = source.ToString(); gives me nothing but a null string back. There's still a problem. What's going wrong?

CODE:
public class VWEMA : IndicatorBase //Volume-Weighted Exponential Moving Average; VWEMA indicator {       string sourceDescription; // = "srcDes";       string weightsDescription; // = "wtsDes";       public VWEMA() : base() { }       public VWEMA(TimeSeries source, TimeSeries weights, int period, string description = "") : base()       {          Parameters[0].Value = source;          Parameters[1].Value = weights;          Parameters[2].Value = period;          Parameters[3].Value = description;          Populate();       }       public override void Populate()       {          TimeSeries source = Parameters[0].AsTimeSeries;          TimeSeries weights = Parameters[1].AsTimeSeries;          sourceDescription = source.ToString();          weightsDescription = weights.ToString();          int period = Parameters[2].AsInt;          string description = Parameters[3].AsString;          if (description != null) { Description = description; }          DateTimes = source.DateTimes;          . . .       } public VWEMA(BarHistory bars, int period, string description) : this(bars.Close, bars.Volume, period, description) { } public static VWEMA Series(TimeSeries source, TimeSeries weights, int period) { string description = "VWEMA(" + source.Description + "," + weights.Description + "," + period + ")";          string key = CacheKey("VWEMA", source.Description, weights.Description, period);          if (source.Cache.ContainsKey(key)) //Is this TimeSeries in cache? return (VWEMA)source.Cache[key]; //No, so create, cache, & return Volume-Weighted EMA VWEMA vwema = new VWEMA(source, weights, period, description); source.Cache[key] = vwema; return vwema; }
0
- ago
#3
@superticker - give me about 1/2 hour and I should have a solution for you.
0
- ago
#4
Sorry I meant source.Parameters.ToString()
0
- ago
#5
QUOTE:
Sorry I meant source.Parameters.ToString()


source is a TimeSeries. It is not necessarily an indicator (IndicatorBase).
0
- ago
#6
I'm lost. Please correct the lines below. None seem to be working.

CODE:
      TimeSeries source = Parameters[0].AsTimeSeries;       TimeSeries weights = Parameters[1].AsTimeSeries;       sourceDescription = (IndicatorBase)Parameters[0].Parameters.ToString();       weightsDescription = weights.Parameters.ToString();
0
- ago
#7
Please read the comments in the following code. Note that you can probably just get rid of Description because I suspect you were trying to use that to solve your caching problem. You may have to add some using(s) to your indicator code.

CODE:
/// <summary> /// Returns a cached VWEMA instance for the given source/weights/period/description combination, /// creating and caching a new one if it does not already exist. /// </summary> /// <param name="source">The TimeSeries to compute the volume-weighted EMA from (e.g., Close prices).</param> /// <param name="weights">The TimeSeries used as weights (e.g., Volume).</param> /// <param name="period">The lookback period for the exponential moving average.</param> /// <param name="description">Optional description override for the resulting indicator.</param> /// <returns>The cached or newly created <see cref="VWEMA" /> instance.</returns> public static VWEMA Series(TimeSeries source, TimeSeries weights, int period, string description = "") { // WealthLab TimeSeries objects don't override Equals/GetHashCode in a way that lets us // identify "the same TimeSeries instance" just by comparing object references across calls // (the same logical series - e.g., bars.Close - can be wrapped/passed around multiple times). // To build a cache key that is stable for a given TimeSeries instance but distinct between // different instances, we attach a unique GUID to each TimeSeries the first time we see it // (via GetOrAssignId) and store that GUID inside the TimeSeries' own Cache dictionary. // On later calls with the *same* TimeSeries instance, GetOrAssignId finds the // previously stored GUID and returns it unchanged, so the resulting key stays identical. var sourceKey = GetOrAssignId(source); var weightsKey = GetOrAssignId(weights); // Because the key incorporates all the inputs, two calls will only produce the same // key if they represent the *exact same* VWEMA configuration - which is precisely the // condition under which it's safe (and desirable) to return a cached instance instead of // recomputing the indicator. var key = CacheKey("VWEMA", sourceKey, weightsKey, period, description); if (source.Cache.TryGetValue(key, out var value)) //Is this TimeSeries in cache? { return (VWEMA) value; } //Not in cache, so create, cache, and return Volume-Weighted EMA var vwema = new VWEMA(source, weights, period, description); source.Cache[key] = vwema; return vwema; } /// <summary> /// Retrieves the unique cache identifier previously assigned to the given TimeSeries, /// or generates and stores a new GUID-based identifier if none exists yet. /// </summary> /// <param name="ts">The TimeSeries to look up or assign a unique cache ID for.</param> /// <returns>The unique identifier string associated with the TimeSeries.</returns> private static string GetOrAssignId(TimeSeries ts) { const string key = "_UniqueID_"; if (ts.Cache.TryGetValue(key, out var id)) { return (string) id; } id = Guid.NewGuid().ToString(); ts.Cache[key] = id; return (string) id; }
0
- ago
#8
Yes, but the GUID approach in Post #7 fails to populate the source.Description with a meaningful label for the Chart itself. I also need this meaningful description for the Chart.

I like the way WL typically does it by extracting indicator input parameters, which can then be used to uniquely populate the source.Description attribute that's displayed on the Chart. How do I do that?
0
- ago
#9
You're trying too hard. Don't set the description whatsoever. Don't do it in the indicator's client and don't do it in the VWEMA indicator. Here's a complete VWEMA indicator and the chart will properly label the pane. Also, check Populate() because I had AI generate that code, so I don't know if the calculations are correct - adjust accordingly...
CODE:
using System; using WealthLab.Core; using WealthLab.Indicators; namespace WLUtility.Indicators { public sealed class VWEMA : IndicatorBase //Volume-Weighted Exponential Moving Average; VWEMA indicator { public VWEMA() { } public VWEMA(TimeSeries source, TimeSeries weights, int period) { Parameters[0].Value = source; Parameters[1].Value = weights; Parameters[2].Value = period; Populate(); } public VWEMA(BarHistory bars, int period) : this(bars.Close, bars.Volume, period) { } public override string Name => "VWEMA"; public override string Abbreviation => "VWEMA"; public override string HelpDescription { get; } = "The Volume-Weighted Exponential Moving Average (VWEMA) combines the concepts of volume weighting and exponential moving average."; public override string PaneTag => "VWEMA"; protected override void GenerateParameters() { AddParameter("Source", ParameterType.TimeSeries, PriceComponent.Close); AddParameter("Weights", ParameterType.TimeSeries, PriceComponent.Volume); AddParameter("Period", ParameterType.Int32, 10); } public override void Populate() { var source = Parameters[0].AsTimeSeries; var weights = Parameters[1].AsTimeSeries; var period = Parameters[2].AsInt; DateTimes = source.DateTimes; // this was generated by AI, so check it. I don't know if it is correct... var alpha = 2.0 / (period + 1); // standard EMA smoothing factor for the given period var emaWeightedValue = 0.0; // running EMA of (weight * value) var emaWeight = 0.0; // running EMA of weight alone for (var i = 0; i < source.Count; i++) { var weight = weights[i]; var value = source[i]; var weightedValue = weight * value; if (i == 0) { // Seed both EMAs with their first raw value - there's no prior EMA to blend with yet. emaWeightedValue = weightedValue; emaWeight = weight; } else { // Standard EMA recursion: new = alpha * current + (1 - alpha) * previous emaWeightedValue = alpha * weightedValue + (1 - alpha) * emaWeightedValue; emaWeight = alpha * weight + (1 - alpha) * emaWeight; } // Guard against a zero (or effectively zero) weight EMA, which would otherwise // produce a divide-by-zero/NaN result (e.g. if every weight so far has been 0). Values[i] = emaWeight != 0.0 ? emaWeightedValue / emaWeight : 0.0; } } /// <summary> /// Returns a cached VWEMA instance for the given source/weights/period combination, /// creating and caching a new one if it does not already exist. /// </summary> /// <param name="source">The TimeSeries to compute the volume-weighted EMA from (e.g., Close prices).</param> /// <param name="weights">The TimeSeries used as weights (e.g., Volume).</param> /// <param name="period">The lookback period for the exponential moving average.</param> /// <returns>The cached or newly created <see cref="VWEMA" /> instance.</returns> public static VWEMA Series(TimeSeries source, TimeSeries weights, int period) { // WealthLab TimeSeries objects don't override Equals/GetHashCode in a way that lets us // identify "the same TimeSeries instance" just by comparing object references across calls // (the same logical series - e.g., bars.Close - can be wrapped/passed around multiple times). // To build a cache key that is stable for a given TimeSeries instance but distinct between // different instances, we attach a unique GUID to each TimeSeries the first time we see it // (via GetOrAssignId) and store that GUID inside the TimeSeries' own Cache dictionary. // On later calls with the *same* TimeSeries instance, GetOrAssignId finds the // previously stored GUID and returns it unchanged, so the resulting key stays identical. var sourceKey = GetOrAssignId(source); var weightsKey = GetOrAssignId(weights); // Because the key incorporates all the inputs, two calls will only produce the same // key if they represent the *exact same* VWEMA configuration - which is precisely the // condition under which it's safe (and desirable) to return a cached instance instead of // recomputing the indicator. var key = CacheKey("VWEMA", sourceKey, weightsKey, period); if (source.Cache.TryGetValue(key, out var value)) //Is this TimeSeries in cache? { return (VWEMA) value; } //Not in cache, so create, cache, and return Volume-Weighted EMA var vwema = new VWEMA(source, weights, period); source.Cache[key] = vwema; return vwema; } /// <summary> /// Retrieves the unique cache identifier previously assigned to the given TimeSeries, /// or generates and stores a new GUID-based identifier if none exists yet. /// </summary> /// <param name="ts">The TimeSeries to look up or assign a unique cache ID for.</param> /// <returns>The unique identifier string associated with the TimeSeries.</returns> private static string GetOrAssignId(TimeSeries ts) { const string key = "_UniqueID_"; if (ts.Cache.TryGetValue(key, out var id)) { return (string) id; } id = Guid.NewGuid().ToString(); ts.Cache[key] = id; return (string) id; } } }

0

Reply

Bookmark

Jump to Top