I've created a custom indicators, that downloads data from an API.
As an input to the indicator you can use the symbol to be fetched from the API.
To have a working cache I need to use LoadDataFromCache and Save.
This however means that, I first have to load the data into "this", to be able to save it.
This seems to work.
Problem then comes as I need to syncronize the loaded date with the source.
TimeSeriesSynchronizer.Synchronize seems to work ok. But then I can't seem to get the data (variable ts) into "this", without something breaking.
What i've tried in Populate:
1. Just adding syncronized data ontop of existing data with AddRangethis.DateTimes.AddRange(ts.DateTimes);
this.Values.AddRange(ts.Values);
Existing data in of course then already present, so there are multiple copies for som datetimes. => Graph not shown at all.
2. Different ways of emptying this.Values and this.DateTime before.
this.Values.RemoveRange(0, this.Values.Count);
this.DateTimes.RemoveRange(0, this.Values.Count);
and adding range (same as in 1), or looping through and adding individual Dates/Values, or settings the whole list with this.Values = ts.Values..
This makes all values NaN.
As an input to the indicator you can use the symbol to be fetched from the API.
To have a working cache I need to use LoadDataFromCache and Save.
This however means that, I first have to load the data into "this", to be able to save it.
This seems to work.
Problem then comes as I need to syncronize the loaded date with the source.
TimeSeriesSynchronizer.Synchronize seems to work ok. But then I can't seem to get the data (variable ts) into "this", without something breaking.
What i've tried in Populate:
1. Just adding syncronized data ontop of existing data with AddRangethis.DateTimes.AddRange(ts.DateTimes);
this.Values.AddRange(ts.Values);
Existing data in of course then already present, so there are multiple copies for som datetimes. => Graph not shown at all.
2. Different ways of emptying this.Values and this.DateTime before.
this.Values.RemoveRange(0, this.Values.Count);
this.DateTimes.RemoveRange(0, this.Values.Count);
and adding range (same as in 1), or looping through and adding individual Dates/Values, or settings the whole list with this.Values = ts.Values..
This makes all values NaN.
CODE:
using WealthLab.Core; using System; using System.Text; using System.Collections.Generic; using System.Globalization; using WealthLab.Indicators; using Newtonsoft.Json.Linq; using System.Diagnostics; using System.Text.RegularExpressions; namespace WealthLab.MyIndicators { public class BitcoinData : IndicatorBase { //parameterless constructor public BitcoinData() : base() { } //for code based construction public BitcoinData(TimeSeries source, String symbol) : base() { Parameters[0].Value = source; Parameters[1].Value = symbol; Populate(); } //static Series method public static BitcoinData Series(TimeSeries source, String symbol) { return new BitcoinData(source, symbol); } //name public override string Name { get { return "BitcoinData"; } } //abbreviation public override string Abbreviation { get { return "BitcoinData"; } } //description public override string HelpDescription { get { return ""; } } //price pane public override string PaneTag { get { return "BitcoinData"; } } //default color public override WLColor DefaultColor { get { return WLColor.FromArgb(255,0,0,255); } } //default plot style public override PlotStyle DefaultPlotStyle { get { return PlotStyle.Line; } } //populate public override void Populate() { TimeSeries source = Parameters[0].AsTimeSeries; String symbol = Parameters[1].AsString; //DateTimes = source.DateTimes; WLHost.Instance.AddLogItem(Name, $"Generating data: (Start)", WLColor.Green); try { bool loadedFromCache = false; //this.LoadDataFromCache(this.Name, symbol, DateTime.MinValue, DateTime.MaxValue, 0); if (this.Count == 0 || (loadedFromCache && this.Count > 0 && this.DateTimes[^1] < DateTime.Today.AddDays(-1)) ||true) { var data = GetBitcoinData(symbol); Tooltip = $"{Name}:{symbol}"; foreach (var kvp in data) { try { if (!Double.IsNaN(kvp.Value)) // (!this.DateTimes.Contains(kvp.Key)) && { WLHost.Instance.AddLogItem(Name, $"{kvp.Key} {kvp.Value}", WLColor.Green); this.Add(kvp.Value, kvp.Key); } } catch (Exception ex1) { WLHost.Instance.AddLogItem(Name, $"Generating data foreach (var kvp in data):{kvp.Key} : {kvp.Value} : {ex1.Message} " + ex1.StackTrace, WLColor.Green); } } try { this.SaveHistoryToCache(Name, symbol); } catch (Exception ex1) { WLHost.Instance.AddLogItem(Name, $"Generating data SaveHistoryToCache: {ex1.Message} " + ex1.StackTrace, WLColor.Green); } } } catch (Exception ex) { // Log any errors during data retrieval. WLHost.Instance.AddLogItem(Name, $"Generating data: {ex.Message} " + ex.StackTrace, WLColor.Green); } TimeSeries ts = null; if (source != null && source.Count > 0) { WLHost.Instance.AddLogItem(Name, $"Generating data asBarHistory != null && asBarHistory.Count > 0:", WLColor.Green); ts = TimeSeriesSynchronizer.Synchronize(this, source); } if (ts != null) { WLHost.Instance.AddLogItem(Name, $"Generating data timeSeries != null" + ts.DateTimes.Count + " " + ts.Values.Count, WLColor.Green); //this.Values.RemoveRange(0, this.Values.Count); //this.DateTimes.RemoveRange(0, this.Values.Count); this.DateTimes.AddRange(ts.DateTimes); this.Values.AddRange(ts.Values); for (int n = 0; n < this.DateTimes.Count; n++) { //this.Add(timeSeries.Values[n], timeSeries.DateTimes[n]); //Values[n] = timeSeries.Values[n]; //DateTimes[n] = timeSeries.DateTimes[n]; WLHost.Instance.AddLogItem(Name, $"Generating data timeSeries: {n} {this.Values[n]} {this.DateTimes[n]}", WLColor.Green); } // this.DateTimes = new List< //this.DateTimes = new List<DateTime>(); //timeSeries.DateTimes.ToList(); //this.DateTimes.AddRange((IEnumerable<DateTime>)timeSeries.DateTimes); //this.Values = //this.Values = new List<double>(); //timeSeries.Values.AddRange((IEnumerable<double>)timeSeries.Values); //timeSeries.Values.ToList(); //.AddRange((IEnumerable<double>)timeSeries.Values); } else { WLHost.Instance.AddLogItem(Name, $"Generating data timeSeries", WLColor.Green); this.DateTimes = source.DateTimes; } } //generate parameters protected override void GenerateParameters() { AddParameter("Source", ParameterType.TimeSeries, PriceComponent.Close); AddParameter("Symbol", ParameterType.String, "mvrv"); } private Dictionary<DateTime, double> GetBitcoinData(String symbol) { var data = new Dictionary<DateTime, double>(); try { WLHost.Instance.AddLogItem(Name, $"Fetching data for :" + symbol, WLColor.Green); // Set up HTTP client with necessary headers. WLWebClient client = new WLWebClient(WLWebClientOptions.Compression); client.AddAcceptHeaders("application/json"); client.AddLanguageHeaders("en-US,en;q=0.9"); client.AddHeader("Upgrade-Insecure-Requests", "1"); string response = client.Get("<a href="https://bitcoin-data.com/v1/" target="_blank">https://bitcoin-data.com/v1/</a>" + symbol); // Parse the JSON response. var jsonResponse = JArray.Parse(response); var dataArray = jsonResponse; var adjustedSymbolName = ConvertSymbolName(symbol); // Extract date and value pairs from the JSON array. foreach (var item in dataArray) { WLHost.Instance.AddLogItem(Name, item.ToString(), WLColor.Green); if (DateTime.TryParseExact(item["d"].ToString(), "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime date) && item[adjustedSymbolName] != null && double.TryParse(item[adjustedSymbolName].ToString().Replace(".", ","), out double value)) { data[date] = value; // Store the date-value pair in the dictionary. } } } catch (Exception ex) { // Log any errors during data retrieval. WLHost.Instance.AddLogItem(Name, $"Error fetching data: {ex.Message} " + ex.StackTrace, WLColor.Green); } return data; // Return the populated dictionary. } public static string ConvertSymbolName(string input) { if (string.IsNullOrEmpty(input)) return input; var result = new StringBuilder(); bool capitalizeNext = false; foreach (char c in input) { if (c == '-') { capitalizeNext = true; } else { if (capitalizeNext) { result.Append(char.ToUpper(c)); capitalizeNext = false; } else { result.Append(c); } } } return result.ToString(); } } }
Rename
You can use AssumeValuesOf to assign the values of ts to your indicator's values, it's a shortcut method. But you need to assign DateTimes = souerce.DateTimes first.
Here's the modification:
Here's the modification:
CODE:
using WealthLab.Core; using System; using System.Text; using System.Collections.Generic; using System.Globalization; using WealthLab.Indicators; using Newtonsoft.Json.Linq; using System.Diagnostics; using System.Text.RegularExpressions; namespace WealthLab.MyIndicators { public class BitcoinData : IndicatorBase { //parameterless constructor public BitcoinData() : base() { } //for code based construction public BitcoinData(TimeSeries source, String symbol) : base() { Parameters[0].Value = source; Parameters[1].Value = symbol; Populate(); } //static Series method public static BitcoinData Series(TimeSeries source, String symbol) { return new BitcoinData(source, symbol); } //name public override string Name { get { return "BitcoinData"; } } //abbreviation public override string Abbreviation { get { return "BitcoinData"; } } //description public override string HelpDescription { get { return ""; } } //price pane public override string PaneTag { get { return "BitcoinData"; } } //default color public override WLColor DefaultColor { get { return WLColor.FromArgb(255, 0, 0, 255); } } //default plot style public override PlotStyle DefaultPlotStyle { get { return PlotStyle.Line; } } //populate public override void Populate() { TimeSeries source = Parameters[0].AsTimeSeries; String symbol = Parameters[1].AsString; //DateTimes = source.DateTimes; WLHost.Instance.AddLogItem(Name, $"Generating data: (Start)", WLColor.Green); try { bool loadedFromCache = false; //this.LoadDataFromCache(this.Name, symbol, DateTime.MinValue, DateTime.MaxValue, 0); if (this.Count == 0 || (loadedFromCache && this.Count > 0 && this.DateTimes[^1] < DateTime.Today.AddDays(-1)) || true) { var data = GetBitcoinData(symbol); Tooltip = $"{Name}:{symbol}"; foreach (var kvp in data) { try { if (!Double.IsNaN(kvp.Value)) // (!this.DateTimes.Contains(kvp.Key)) && { WLHost.Instance.AddLogItem(Name, $"{kvp.Key} {kvp.Value}", WLColor.Green); this.Add(kvp.Value, kvp.Key); } } catch (Exception ex1) { WLHost.Instance.AddLogItem(Name, $"Generating data foreach (var kvp in data):{kvp.Key} : {kvp.Value} : {ex1.Message} " + ex1.StackTrace, WLColor.Green); } } try { this.SaveHistoryToCache(Name, symbol); } catch (Exception ex1) { WLHost.Instance.AddLogItem(Name, $"Generating data SaveHistoryToCache: {ex1.Message} " + ex1.StackTrace, WLColor.Green); } } } catch (Exception ex) { // Log any errors during data retrieval. WLHost.Instance.AddLogItem(Name, $"Generating data: {ex.Message} " + ex.StackTrace, WLColor.Green); } TimeSeries ts = null; if (source != null && source.Count > 0) { WLHost.Instance.AddLogItem(Name, $"Generating data asBarHistory != null && asBarHistory.Count > 0:", WLColor.Green); ts = TimeSeriesSynchronizer.Synchronize(this, source); } if (ts != null) { WLHost.Instance.AddLogItem(Name, $"Generating data timeSeries != null" + ts.DateTimes.Count + " " + ts.Values.Count, WLColor.Green); //this.Values.RemoveRange(0, this.Values.Count); //this.DateTimes.RemoveRange(0, this.Values.Count); DateTimes = source.DateTimes; AssumeValuesOf(ts); for (int n = 0; n < this.DateTimes.Count; n++) { //this.Add(timeSeries.Values[n], timeSeries.DateTimes[n]); //Values[n] = timeSeries.Values[n]; //DateTimes[n] = timeSeries.DateTimes[n]; WLHost.Instance.AddLogItem(Name, $"Generating data timeSeries: {n} {this.Values[n]} {this.DateTimes[n]}", WLColor.Green); } // this.DateTimes = new List< //this.DateTimes = new List<DateTime>(); //timeSeries.DateTimes.ToList(); //this.DateTimes.AddRange((IEnumerable<DateTime>)timeSeries.DateTimes); //this.Values = //this.Values = new List<double>(); //timeSeries.Values.AddRange((IEnumerable<double>)timeSeries.Values); //timeSeries.Values.ToList(); //.AddRange((IEnumerable<double>)timeSeries.Values); } else { WLHost.Instance.AddLogItem(Name, $"Generating data timeSeries", WLColor.Green); this.DateTimes = source.DateTimes; } } //generate parameters protected override void GenerateParameters() { AddParameter("Source", ParameterType.TimeSeries, PriceComponent.Close); AddParameter("Symbol", ParameterType.String, "mvrv"); } private Dictionary<DateTime, double> GetBitcoinData(String symbol) { var data = new Dictionary<DateTime, double>(); try { WLHost.Instance.AddLogItem(Name, $"Fetching data for :" + symbol, WLColor.Green); // Set up HTTP client with necessary headers. WLWebClient client = new WLWebClient(WLWebClientOptions.Compression); client.AddAcceptHeaders("application/json"); client.AddLanguageHeaders("en-US,en;q=0.9"); client.AddHeader("Upgrade-Insecure-Requests", "1"); string response = client.Get("<a href="https://bitcoin-data.com/v1/" target="_blank">https://bitcoin-data.com/v1/</a>" + symbol); // Parse the JSON response. var jsonResponse = JArray.Parse(response); var dataArray = jsonResponse; var adjustedSymbolName = ConvertSymbolName(symbol); // Extract date and value pairs from the JSON array. foreach (var item in dataArray) { WLHost.Instance.AddLogItem(Name, item.ToString(), WLColor.Green); if (DateTime.TryParseExact(item["d"].ToString(), "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime date) && item[adjustedSymbolName] != null && double.TryParse(item[adjustedSymbolName].ToString().Replace(".", ","), out double value)) { data[date] = value; // Store the date-value pair in the dictionary. } } } catch (Exception ex) { // Log any errors during data retrieval. WLHost.Instance.AddLogItem(Name, $"Error fetching data: {ex.Message} " + ex.StackTrace, WLColor.Green); } return data; // Return the populated dictionary. } public static string ConvertSymbolName(string input) { if (string.IsNullOrEmpty(input)) return input; var result = new StringBuilder(); bool capitalizeNext = false; foreach (char c in input) { if (c == '-') { capitalizeNext = true; } else { if (capitalizeNext) { result.Append(char.ToUpper(c)); capitalizeNext = false; } else { result.Append(c); } } } return result.ToString(); } } }
UPDATED:
I could never get Syncronize to work, without giving me NaN for all Values.
So I wrote my own function and now it works as expected (will probably have some bugs).
Orginal msg below:
____________________________________
Thank you for your help but still cant get it to work.
Here is what happens (see logs also).
1. When fetching data, it is added correctly. Se log of This (1).
2. Runnings Syncronizer doesnt touch this (expected), but all Values in ths synced (ts) is NaN. (ts (2)), and no values from This are added.
3. After AssumeValuesOf
this and ts become the same as ts (3).
Then Populate is rerun (not sure why?)
Generates errors when Adding data at this.Add(kvp.Value, kvp.Key);
Then this.Values becomes empty, but DateTime is still 8045 (same as Source)
2025-01-14 11:32:35:751
BitcoinData
Generating data this.Count (1)8045 0
Assumed is ran again, and Values is filled with NaN
I could never get Syncronize to work, without giving me NaN for all Values.
So I wrote my own function and now it works as expected (will probably have some bugs).
CODE:
public TimeSeries MergeDataPoints(TimeSeries instance1, TimeSeries instance2) { // Create a dictionary from instance1 for quick DateTime to Value lookup var valueDict = new Dictionary<DateTime, double>(); for (int i = 0; i < instance1.DateTimes.Count; i++) { // Ensure there are equal counts of DateTimes and Values if (i < instance1.Values.Count) { // If there are duplicate DateTimes in instance1, decide how to handle them. // Here, we'll overwrite with the latest value. valueDict[instance1.DateTimes[i]] = instance1.Values[i]; } } // Prepare the new DataClass instance TimeSeries mergedInstance = new TimeSeries(); foreach (var dt in instance2.DateTimes) { mergedInstance.DateTimes.Add(dt); if (valueDict.TryGetValue(dt, out double value)) { mergedInstance.Values.Add(value); } else { mergedInstance.Values.Add(Double.NaN); } } return mergedInstance; }
Orginal msg below:
____________________________________
Thank you for your help but still cant get it to work.
Here is what happens (see logs also).
1. When fetching data, it is added correctly. Se log of This (1).
2. Runnings Syncronizer doesnt touch this (expected), but all Values in ths synced (ts) is NaN. (ts (2)), and no values from This are added.
3. After AssumeValuesOf
this and ts become the same as ts (3).
Then Populate is rerun (not sure why?)
Generates errors when Adding data at this.Add(kvp.Value, kvp.Key);
Then this.Values becomes empty, but DateTime is still 8045 (same as Source)
2025-01-14 11:32:35:751
BitcoinData
Generating data this.Count (1)8045 0
Assumed is ran again, and Values is filled with NaN
CODE:
public override void Populate() { TimeSeries source = Parameters[0].AsTimeSeries; String symbol = Parameters[1].AsString; //DateTimes = source.DateTimes; WLHost.Instance.AddLogItem(Name, $"Generating data: (Start)", WLColor.Green); try { bool loadedFromCache = false; //this.LoadDataFromCache(this.Name, symbol, DateTime.MinValue, DateTime.MaxValue, 0); if (this.Count == 0 || (loadedFromCache && this.Count > 0 && this.DateTimes[^1] < DateTime.Today.AddDays(-1)) ||true) { var data = GetBitcoinData(symbol); Tooltip = $"{Name}:{symbol}"; var j = 0; foreach (var kvp in data) { try { if (!Double.IsNaN(kvp.Value) && !this.DateTimes.Contains(kvp.Key)) // (!this.DateTimes.Contains(kvp.Key)) && { WLHost.Instance.AddLogItem(Name, $"{kvp.Key} {kvp.Value}", WLColor.Green); this.Add(kvp.Value, kvp.Key); } } catch (Exception ex1) { WLHost.Instance.AddLogItem(Name, $"Generating data foreach (var kvp in data):{kvp.Key} : {kvp.Value} : {ex1.Message} " + ex1.StackTrace, WLColor.Green); } } try { //this.SaveHistoryToCache(Name, symbol); } catch (Exception ex1) { WLHost.Instance.AddLogItem(Name, $"Generating data SaveHistoryToCache: {ex1.Message} " + ex1.StackTrace, WLColor.Green); } } } catch (Exception ex) { // Log any errors during data retrieval. WLHost.Instance.AddLogItem(Name, $"Generating data: {ex.Message} " + ex.StackTrace, WLColor.Green); } WLHost.Instance.AddLogItem(Name, $"Generating data this.Count (1)" + this.DateTimes.Count + " " + this.Values.Count, WLColor.Green); LogFirstAndLast(this, 2, "This (1) "); TimeSeries ts = null; if (source != null && source.Count > 0) { ts = TimeSeriesSynchronizer.Synchronize(this.Clone(), source.Clone()); } if (ts != null) { LogFirstAndLast(this, 2, "This (2) "); LogFirstAndLast(ts, 2, "ts (2) "); WLHost.Instance.AddLogItem(Name, $"Generating data this.Count (2):" + this.DateTimes.Count + " " + this.Values.Count, WLColor.Green); DateTimes = source.DateTimes; this.AssumeValuesOf(ts); WLHost.Instance.AddLogItem(Name, $"Generating data this.Count (3):" + this.DateTimes.Count + " " + this.Values.Count, WLColor.Green); LogFirstAndLast(this, 2, "This (3) "); LogFirstAndLast(ts, 2, "ts (3) "); } else { WLHost.Instance.AddLogItem(Name, $"Generating data timeSeries", WLColor.Green); this.DateTimes = source.DateTimes; } }
QUOTE:
2025-01-14 11:32:35:747
BitcoinData
Generating data: (Start)
--------
2025-01-14 11:32:35:747
BitcoinData
2025-01-14 00:00:00 2
--------
2025-01-14 11:32:35:748
BitcoinData
2025-01-13 00:00:00 2,1
--------
2025-01-14 11:32:35:748
BitcoinData
2025-01-12 00:00:00 2,2
--------
2025-01-14 11:32:35:748
BitcoinData
2025-01-11 00:00:00 2,3
--------
2025-01-14 11:32:35:748
BitcoinData
2025-01-10 00:00:00 2,4
--------
2025-01-14 11:32:35:748
BitcoinData
2025-01-09 00:00:00 2,5
--------
2025-01-14 11:32:35:748
BitcoinData
Generating data this.Count (1)6 6
--------
2025-01-14 11:32:35:749
BitcoinData
This (1) Values[0] 2
--------
2025-01-14 11:32:35:749
BitcoinData
This (1) Values[1] 2,1
--------
2025-01-14 11:32:35:749
BitcoinData
This (1) Values[5] 2,5
--------
2025-01-14 11:32:35:749
BitcoinData
This (1) Values[4] 2,4
--------
2025-01-14 11:32:35:749
BitcoinData
This (1) DateTimes[0] 2025-01-14 00:00:00
--------
2025-01-14 11:32:35:749
BitcoinData
This (1) DateTimes[1] 2025-01-13 00:00:00
--------
2025-01-14 11:32:35:749
BitcoinData
This (1) DateTimes[5] 2025-01-09 00:00:00
--------
2025-01-14 11:32:35:749
BitcoinData
This (1) DateTimes[4] 2025-01-10 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
This (2) Values[0] 2
--------
2025-01-14 11:32:35:750
BitcoinData
This (2) Values[1] 2,1
--------
2025-01-14 11:32:35:750
BitcoinData
This (2) Values[5] 2,5
--------
2025-01-14 11:32:35:750
BitcoinData
This (2) Values[4] 2,4
--------
2025-01-14 11:32:35:750
BitcoinData
This (2) DateTimes[0] 2025-01-14 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
This (2) DateTimes[1] 2025-01-13 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
This (2) DateTimes[5] 2025-01-09 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
This (2) DateTimes[4] 2025-01-10 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
ts (2) Values[0] NaN
--------
2025-01-14 11:32:35:750
BitcoinData
ts (2) Values[1] NaN
--------
2025-01-14 11:32:35:750
BitcoinData
ts (2) Values[8044] NaN
--------
2025-01-14 11:32:35:750
BitcoinData
ts (2) Values[8043] NaN
--------
2025-01-14 11:32:35:750
BitcoinData
ts (2) DateTimes[0] 1993-01-29 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
ts (2) DateTimes[1] 1993-02-01 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
ts (2) DateTimes[8044] 2025-01-13 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
ts (2) DateTimes[8043] 2025-01-10 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
Generating data this.Count (2):6 6
--------
2025-01-14 11:32:35:750
BitcoinData
Generating data this.Count (3):8045 8045
--------
2025-01-14 11:32:35:750
BitcoinData
This (3) Values[0] NaN
--------
2025-01-14 11:32:35:750
BitcoinData
This (3) Values[1] NaN
--------
2025-01-14 11:32:35:750
BitcoinData
This (3) Values[8044] NaN
--------
2025-01-14 11:32:35:750
BitcoinData
This (3) Values[8043] NaN
--------
2025-01-14 11:32:35:750
BitcoinData
This (3) DateTimes[0] 1993-01-29 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
This (3) DateTimes[1] 1993-02-01 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
This (3) DateTimes[8044] 2025-01-13 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
This (3) DateTimes[8043] 2025-01-10 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
ts (3) Values[0] NaN
--------
2025-01-14 11:32:35:750
BitcoinData
ts (3) Values[1] NaN
--------
2025-01-14 11:32:35:750
BitcoinData
ts (3) Values[8044] NaN
--------
2025-01-14 11:32:35:750
BitcoinData
ts (3) Values[8043] NaN
--------
2025-01-14 11:32:35:750
BitcoinData
ts (3) DateTimes[0] 1993-01-29 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
ts (3) DateTimes[1] 1993-02-01 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
ts (3) DateTimes[8044] 2025-01-13 00:00:00
--------
2025-01-14 11:32:35:750
BitcoinData
ts (3) DateTimes[8043] 2025-01-10 00:00:00
--------
2025-01-14 11:32:35:751
BitcoinData
Generating data: (Start)
--------
2025-01-14 11:32:35:751
BitcoinData
2025-01-14 00:00:00 2
--------
2025-01-14 11:32:35:751
BitcoinData
Generating data foreach (var kvp in data):2025-01-14 00:00:00 : 2 : Should not add DateTime at WealthLab.Core.DateSynchedList`1.Add(T value, DateTime dateTime)
at WealthLab.MyIndicators.BitcoinData.Populate() in :line 119
--------
2025-01-14 11:32:35:751
BitcoinData
2025-01-12 00:00:00 2,2
--------
2025-01-14 11:32:35:751
BitcoinData
Generating data foreach (var kvp in data):2025-01-12 00:00:00 : 2,2 : Should not add DateTime at WealthLab.Core.DateSynchedList`1.Add(T value, DateTime dateTime)
at WealthLab.MyIndicators.BitcoinData.Populate() in :line 119
--------
2025-01-14 11:32:35:751
BitcoinData
2025-01-11 00:00:00 2,3
--------
2025-01-14 11:32:35:751
BitcoinData
Generating data foreach (var kvp in data):2025-01-11 00:00:00 : 2,3 : Should not add DateTime at WealthLab.Core.DateSynchedList`1.Add(T value, DateTime dateTime)
at WealthLab.MyIndicators.BitcoinData.Populate() in :line 119
--------
2025-01-14 11:32:35:751
BitcoinData
2025-01-09 00:00:00 2,5
--------
2025-01-14 11:32:35:751
BitcoinData
Generating data foreach (var kvp in data):2025-01-09 00:00:00 : 2,5 : Should not add DateTime at WealthLab.Core.DateSynchedList`1.Add(T value, DateTime dateTime)
at WealthLab.MyIndicators.BitcoinData.Populate() in :line 119
--------
2025-01-14 11:32:35:751
BitcoinData
Generating data this.Count (1)8045 0
--------
2025-01-14 11:32:35:753
BitcoinData
ts (2) Values[0] NaN
--------
2025-01-14 11:32:35:753
BitcoinData
ts (2) Values[1] NaN
--------
2025-01-14 11:32:35:753
BitcoinData
ts (2) Values[8044] NaN
--------
2025-01-14 11:32:35:753
BitcoinData
ts (2) Values[8043] NaN
--------
2025-01-14 11:32:35:753
BitcoinData
ts (2) DateTimes[0] 1993-01-29 00:00:00
--------
2025-01-14 11:32:35:753
BitcoinData
ts (2) DateTimes[1] 1993-02-01 00:00:00
--------
2025-01-14 11:32:35:753
BitcoinData
ts (2) DateTimes[8044] 2025-01-13 00:00:00
--------
2025-01-14 11:32:35:753
BitcoinData
ts (2) DateTimes[8043] 2025-01-10 00:00:00
--------
2025-01-14 11:32:35:753
BitcoinData
Generating data this.Count (2):8045 0
--------
2025-01-14 11:32:35:753
BitcoinData
Generating data this.Count (3):8045 8045
--------
2025-01-14 11:32:35:753
BitcoinData
This (3) Values[0] NaN
--------
2025-01-14 11:32:35:753
BitcoinData
This (3) Values[1] NaN
--------
2025-01-14 11:32:35:753
BitcoinData
This (3) Values[8044] NaN
--------
2025-01-14 11:32:35:753
BitcoinData
This (3) Values[8043] NaN
--------
2025-01-14 11:32:35:753
BitcoinData
This (3) DateTimes[0] 1993-01-29 00:00:00
--------
2025-01-14 11:32:35:753
BitcoinData
This (3) DateTimes[1] 1993-02-01 00:00:00
--------
2025-01-14 11:32:35:753
BitcoinData
This (3) DateTimes[8044] 2025-01-13 00:00:00
--------
2025-01-14 11:32:35:753
BitcoinData
This (3) DateTimes[8043] 2025-01-10 00:00:00
--------
2025-01-14 11:32:35:753
BitcoinData
ts (3) Values[0] NaN
--------
2025-01-14 11:32:35:753
BitcoinData
ts (3) Values[1] NaN
--------
2025-01-14 11:32:35:753
BitcoinData
ts (3) Values[8044] NaN
--------
2025-01-14 11:32:35:753
BitcoinData
ts (3) Values[8043] NaN
--------
2025-01-14 11:32:35:753
BitcoinData
ts (3) DateTimes[0] 1993-01-29 00:00:00
--------
2025-01-14 11:32:35:753
BitcoinData
ts (3) DateTimes[1] 1993-02-01 00:00:00
--------
2025-01-14 11:32:35:753
BitcoinData
ts (3) DateTimes[8044] 2025-01-13 00:00:00
--------
2025-01-14 11:32:35:753
BitcoinData
ts (3) DateTimes[8043] 2025-01-10 00:00:00
--------
Did you copy and paste my code in Post #1? It works for me 🤷♂️
Yes, it still gave (and gives as I tried again) NaN for all values.
But counld't find any logic supporting it, so probably something on my system only.
Thank you for helping.
But counld't find any logic supporting it, so probably something on my system only.
Thank you for helping.
Make sure you're using my exact code, here's what I get.
Tried again now with same exact code you posted (without the href...).
Still NaN for all values. (but correctly added from API at first)
Still NaN for all values. (but correctly added from API at first)
I took the liberty of refactoring it a bit. It's working now smoothly with caching in place. Basically, the cache is storing the raw data, unsynced with any symbol. For this we're creating and using a new plain instance of the BitcoinData indicator not tied to the source.
Afterward, we synchronize that with the source using the TimeSeriesSynchronizer to get the synced version for each symbol that needs the data.
Afterward, we synchronize that with the source using the TimeSeriesSynchronizer to get the synced version for each symbol that needs the data.
CODE:
using WealthLab.Core; using System; using System.Text; using System.Collections.Generic; using System.Globalization; using WealthLab.Indicators; using Newtonsoft.Json.Linq; using System.Diagnostics; using System.Text.RegularExpressions; namespace WealthLab.MyIndicators { public class BitcoinData : IndicatorBase { //parameterless constructor public BitcoinData() : base() { } //for code based construction public BitcoinData(TimeSeries source, String symbol) : base() { Parameters[0].Value = source; Parameters[1].Value = symbol; Populate(); } //static Series method public static BitcoinData Series(TimeSeries source, String symbol) { return new BitcoinData(source, symbol); } //name public override string Name { get { return "BitcoinData"; } } //abbreviation public override string Abbreviation { get { return "BitcoinData"; } } //description public override string HelpDescription { get { return ""; } } //price pane public override string PaneTag { get { return "BitcoinData"; } } //default color public override WLColor DefaultColor { get { return WLColor.FromArgb(255, 0, 0, 255); } } //default plot style public override PlotStyle DefaultPlotStyle { get { return PlotStyle.Line; } } //populate public override void Populate() { TimeSeries source = Parameters[0].AsTimeSeries; String symbol = Parameters[1].AsString; DateTimes = source.DateTimes; //see if we have data in cache BitcoinData btc = new BitcoinData(); bool cacheNeedsUpdate = btc.LoadDataFromCache(this.Name, "BitCoinData", DateTime.MinValue, DateTime.MaxValue, 0); if (!cacheNeedsUpdate) { //yes we have a current cached copy, sync with source TimeSeries ts = TimeSeriesSynchronizer.Synchronize(btc, source); AssumeValuesOf(ts); } else { //no, get raw data from service btc = new BitcoinData(); var data = GetBitcoinData(symbol); Tooltip = $"{Name}:{symbol}"; foreach (var kvp in data) { try { if (!Double.IsNaN(kvp.Value)) // (!this.DateTimes.Contains(kvp.Key)) && { btc.Add(kvp.Value, kvp.Key); } } catch (Exception ex1) { WLHost.Instance.AddLogItem(Name, $"Generating data foreach (var kvp in data):{kvp.Key} : {kvp.Value} : {ex1.Message} " + ex1.StackTrace, WLColor.Green); } } //and save to cache btc.SaveHistoryToCache(this.Name, "BitCoinData"); //sync with source TimeSeries ts = TimeSeriesSynchronizer.Synchronize(btc, source); AssumeValuesOf(ts); } } //generate parameters protected override void GenerateParameters() { AddParameter("Source", ParameterType.TimeSeries, PriceComponent.Close); AddParameter("Symbol", ParameterType.String, "mvrv"); } private Dictionary<DateTime, double> GetBitcoinData(String symbol) { var data = new Dictionary<DateTime, double>(); try { WLHost.Instance.AddLogItem(Name, $"Fetching data for :" + symbol, WLColor.Green); // Set up HTTP client with necessary headers. WLWebClient client = new WLWebClient(WLWebClientOptions.Compression); client.AddAcceptHeaders("application/json"); client.AddLanguageHeaders("en-US,en;q=0.9"); client.AddHeader("Upgrade-Insecure-Requests", "1"); string response = client.Get("<a href="https://bitcoin-data.com/v1/" target="_blank">https://bitcoin-data.com/v1/</a>" + symbol); // Parse the JSON response. var jsonResponse = JArray.Parse(response); var dataArray = jsonResponse; var adjustedSymbolName = ConvertSymbolName(symbol); // Extract date and value pairs from the JSON array. foreach (var item in dataArray) { WLHost.Instance.AddLogItem(Name, item.ToString(), WLColor.Green); if (DateTime.TryParseExact(item["d"].ToString(), "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime date) && item[adjustedSymbolName] != null && double.TryParse(item[adjustedSymbolName].ToString().Replace(".", ","), out double value)) { data[date] = value; // Store the date-value pair in the dictionary. } } } catch (Exception ex) { // Log any errors during data retrieval. WLHost.Instance.AddLogItem(Name, $"Error fetching data: {ex.Message} " + ex.StackTrace, WLColor.Green); } return data; // Return the populated dictionary. } public static string ConvertSymbolName(string input) { if (string.IsNullOrEmpty(input)) return input; var result = new StringBuilder(); bool capitalizeNext = false; foreach (char c in input) { if (c == '-') { capitalizeNext = true; } else { if (capitalizeNext) { result.Append(char.ToUpper(c)); capitalizeNext = false; } else { result.Append(c); } } } return result.ToString(); } } }
Got you code working now by creating a new Indicator with new name BitcoinData2.
But I'll still use mine with the caching of each symbol.
But I'll still use mine with the caching of each symbol.
If you cache the synchronized data for each symbol you might run into problems if you change the data range. For example, if you first run the strategy on one year of data, presumably your cached data for that symbol would contain only one year of data. Then, if you increase the range to 10 years, the cached copy will only load the one year of data.
Your Response
Post
Edit Post
Login is required