diff --git a/Monitor/Pages/Index.cshtml b/Monitor/Pages/Index.cshtml index 930214b..770f037 100644 --- a/Monitor/Pages/Index.cshtml +++ b/Monitor/Pages/Index.cshtml @@ -33,11 +33,6 @@ $("#baglist-refresh-icon").html(''); $("#buylist-refresh-icon").html(''); - // Add the page refresh code here - setTimeout(function(){ - location.reload(); - }, 60 * 60 * 1000); // 60 minutes - // Clear exisitng interval to stop multiple attempts to load at the same time. if (intervalDashboardTop != null) { @@ -69,13 +64,16 @@ var loadDashboardBottom = function () { //destroy all d3 svg graph to avoid memory leak - //$(".nvtooltip").remove(); - //$("svg > *").remove(); - //$("svg").remove(); - //nv.charts = {}; - //nv.graphs = []; - //nv.logs = {}; - //nv.tooltip = {}; + setTimeout(function() + { + $(".nvtooltip").remove(); + $("svg > *").remove(); + $("svg").remove(); + nv.charts = {}; + nv.graphs = []; + nv.logs = {}; + nv.tooltip = {}; + }, 10 * intervalDashboardBottom * 1000); // 10 times intervalDashboardBottom in milliseconds // Clear exisitng interval to stop multiple attempts to load at the same time. if (intervalDashboardBottom != null) diff --git a/Monitor/Pages/SettingsGeneral.cshtml b/Monitor/Pages/SettingsGeneral.cshtml index d460948..3b8434d 100644 --- a/Monitor/Pages/SettingsGeneral.cshtml +++ b/Monitor/Pages/SettingsGeneral.cshtml @@ -196,21 +196,21 @@
- +
- +
- +
diff --git a/Monitor/Pages/StatusSummary.cshtml b/Monitor/Pages/StatusSummary.cshtml index 46eb477..7bab1c6 100644 --- a/Monitor/Pages/StatusSummary.cshtml +++ b/Monitor/Pages/StatusSummary.cshtml @@ -10,7 +10,8 @@ }
-
+ +

PTMagic Status

@{ @@ -71,7 +72,6 @@ @{ string maxCostCaption = "Initial"; } - @@ -99,69 +99,63 @@
- - -
-
-
-
-

Settings Active Time (Last 24h)

- -
- -
-
-
-
-
-

Settings Active Time (Last 3 days)

- -
- -
-
+
+
+
+

Settings Active Time (Last 24h)

+
+
-
-

Global Settings Log

- - - - - - - - - - - - @foreach (Core.Main.DataObjects.PTMagicData.GlobalSettingSummary gss in Model.Summary.GlobalSettingSummary.OrderByDescending(g => g.SwitchDateTime).Take(Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxSettingsLogEntries)) { - TimeSpan offsetTimeSpan = TimeSpan.Parse(Model.PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", "")); - DateTimeOffset settingActivationTime = gss.SwitchDateTime; - settingActivationTime = settingActivationTime.ToOffset(offsetTimeSpan); - - string marketTrendsSummary = ""; - foreach (string mt in gss.MarketTrendChanges.Keys) { - if (!marketTrendsSummary.Equals("")) { - marketTrendsSummary += "
"; - } - marketTrendsSummary += Core.Helper.SystemHelper.SplitCamelCase(mt) + ": " + gss.MarketTrendChanges[mt].TrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"; - } - - - - - - - } - -
Activation TimeSettingActive TimeMarket Trends
@settingActivationTime.DateTime.ToShortDateString() @settingActivationTime.DateTime.ToShortTimeString()@Core.Helper.SystemHelper.SplitCamelCase(gss.SettingName)@Core.Helper.SystemHelper.GetProperDurationTime(gss.ActiveSeconds)@Html.Raw(marketTrendsSummary)
+

Settings Active Time (Last 3 days)

+
+ +
+ + +
+ + + +
+

Global Settings Log

+ + + + + + + + + + + @foreach (Core.Main.DataObjects.PTMagicData.GlobalSettingSummary gss in Model.Summary.GlobalSettingSummary.OrderByDescending(g => g.SwitchDateTime).Take(Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxSettingsLogEntries)) { + TimeSpan offsetTimeSpan = TimeSpan.Parse(Model.PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", "")); + DateTimeOffset settingActivationTime = gss.SwitchDateTime; + settingActivationTime = settingActivationTime.ToOffset(offsetTimeSpan); + + string marketTrendsSummary = ""; + foreach (string mt in gss.MarketTrendChanges.Keys) { + if (!marketTrendsSummary.Equals("")) { + marketTrendsSummary += "
"; + } + marketTrendsSummary += Core.Helper.SystemHelper.SplitCamelCase(mt) + ": " + gss.MarketTrendChanges[mt].TrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"; + } + + + + + + + } + +
Activation TimeSettingActive TimeMarket Trends
@settingActivationTime.DateTime.ToShortDateString() @settingActivationTime.DateTime.ToShortTimeString()@Core.Helper.SystemHelper.SplitCamelCase(gss.SettingName)@Core.Helper.SystemHelper.GetProperDurationTime(gss.ActiveSeconds)@Html.Raw(marketTrendsSummary)
diff --git a/Monitor/Pages/_get/DashboardBottom.cshtml b/Monitor/Pages/_get/DashboardBottom.cshtml index c56c504..b4cf026 100644 --- a/Monitor/Pages/_get/DashboardBottom.cshtml +++ b/Monitor/Pages/_get/DashboardBottom.cshtml @@ -11,8 +11,8 @@
-
-
+
+

Market Trend History

@if (!Model.TrendChartDataJSON.Equals("")) {
@@ -22,9 +22,10 @@ }
+
-
- @*
*@ +
+
@{ string totalCurrentValueString = Model.totalCurrentValue.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US")); if (Model.totalCurrentValue > 100) { @@ -50,7 +51,8 @@
@*
*@ -
+
+

Daily Profit @if (!Model.ProfitChartDataJSON.Equals("")) {
@@ -65,8 +67,8 @@
-
-
+ @*
+
*@

Market Trends at @Model.PTMagicConfiguration.GeneralSettings.Application.Exchange more

@@ -293,6 +295,22 @@ .datum(assetDistributionData) .transition().duration(0) .call(assetDistributionChart); + + // Add mouseleave, and mousemove event listeners to hide tooltip + d3.select('.profit-chart').on('mouseleave', function() { + d3.select('.nvtooltip').style('opacity', 0); + }); + + d3.select('body').on('mousemove', function() { + var chartBounds = d3.select('.profit-chart')[0][0].getBoundingClientRect(); + var mouseX = d3.event.clientX; + var mouseY = d3.event.clientY; + + if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) { + d3.select('.nvtooltip').style('opacity', 0); + } + }); + return assetDistributionChart; }); @@ -325,6 +343,21 @@ .datum(trendData) .transition().duration(0) .call(trendChart); + // Add mouseleave, and mousemove event listeners to hide tooltip + d3.select('.profit-chart').on('mouseleave', function() { + d3.select('.nvtooltip').style('opacity', 0); + }); + + d3.select('body').on('mousemove', function() { + var chartBounds = d3.select('.profit-chart')[0][0].getBoundingClientRect(); + var mouseX = d3.event.clientX; + var mouseY = d3.event.clientY; + + if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) { + d3.select('.nvtooltip').style('opacity', 0); + } + }); + return trendChart; }); @@ -348,7 +381,7 @@ profitChart = nv.models.lineChart(); var height = 300; profitChart.useInteractiveGuideline(true); - profitChart.xAxis.tickFormat(function (d) { return d3.time.format('%Y/%m/%d')(new Date(d)); }); + profitChart.xAxis.tickFormat(function (d) { return d3.time.format('%m/%d')(new Date(d)); }); profitChart.yAxis.axisLabel('Daily Profit').tickFormat(d3.format(',.2f')); profitData = @Html.Raw(Model.ProfitChartDataJSON); @@ -357,9 +390,54 @@ .datum(profitData) .transition().duration(0) .call(profitChart); + + // Add mouseleave, and mousemove event listeners to hide tooltip + d3.select('.profit-chart').on('mouseleave', function() { + d3.select('.nvtooltip').style('opacity', 0); + }); + + d3.select('body').on('mousemove', function() { + var chartBounds = d3.select('.profit-chart')[0][0].getBoundingClientRect(); + var mouseX = d3.event.clientX; + var mouseY = d3.event.clientY; + + if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) { + d3.select('.nvtooltip').style('opacity', 0); + } + }); + + return profitChart; }); } })(jQuery); + + diff --git a/Monitor/Pages/_get/DashboardBottom.cshtml.cs b/Monitor/Pages/_get/DashboardBottom.cshtml.cs index b1bd454..3220282 100644 --- a/Monitor/Pages/_get/DashboardBottom.cshtml.cs +++ b/Monitor/Pages/_get/DashboardBottom.cshtml.cs @@ -8,6 +8,7 @@ using Core.Main.DataObjects; using Core.Main.DataObjects.PTMagicData; using System.Globalization; using System.Text; +using System.Diagnostics; namespace Monitor.Pages { @@ -19,6 +20,8 @@ namespace Monitor.Pages public SummaryData SummaryData { get; set; } public List MarketTrends { get; set; } = new List(); + public double DataHours { get; set; } + public int ProfitDays { get; set; } public string TrendChartDataJSON = ""; public string ProfitChartDataJSON = ""; public string LastGlobalSetting = "Default"; @@ -31,10 +34,9 @@ namespace Monitor.Pages base.Init(); BindData(); - - BuildAssetDistributionData(); + } - + private void BindData() { PTData = this.PtDataObject; @@ -62,110 +64,164 @@ namespace Monitor.Pages MarketTrends = PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.OrderBy(mt => mt.TrendMinutes).ThenByDescending(mt => mt.Platform).ToList(); BuildMarketTrendChartData(); + BuildAssetDistributionData(); BuildProfitChartData(); } private void BuildMarketTrendChartData() +{ + List trendChartData = new List(); + if (MarketTrends.Count > 0) { - List trendChartData = new List(); - if (MarketTrends.Count > 0) + + int mtIndex = 0; + foreach (MarketTrend mt in MarketTrends) { - - int mtIndex = 0; - foreach (MarketTrend mt in MarketTrends) + if (mt.DisplayGraph) { - if (mt.DisplayGraph) + string lineColor = mtIndex < Constants.ChartLineColors.Length + ? Constants.ChartLineColors[mtIndex] + : Constants.ChartLineColors[mtIndex - 20]; + + if (Summary.MarketTrendChanges.ContainsKey(mt.Name)) { - string lineColor = mtIndex < Constants.ChartLineColors.Length - ? Constants.ChartLineColors[mtIndex] - : Constants.ChartLineColors[mtIndex - 20]; + List marketTrendChangeSummaries = Summary.MarketTrendChanges[mt.Name]; - if (Summary.MarketTrendChanges.ContainsKey(mt.Name)) + if (marketTrendChangeSummaries.Count > 0) { - List marketTrendChangeSummaries = Summary.MarketTrendChanges[mt.Name]; + List trendValues = new List(); - if (marketTrendChangeSummaries.Count > 0) + // Sort marketTrendChangeSummaries by TrendDateTime + marketTrendChangeSummaries = marketTrendChangeSummaries.OrderBy(m => m.TrendDateTime).ToList(); + + // Get trend ticks for chart + TimeSpan offset; + bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); + string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); + + if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) { - List trendValues = new List(); - - // Sort marketTrendChangeSummaries by TrendDateTime - marketTrendChangeSummaries = marketTrendChangeSummaries.OrderBy(m => m.TrendDateTime).ToList(); - - // Get trend ticks for chart - DateTime currentDateTime = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, DateTime.UtcNow.Day, DateTime.UtcNow.Hour, 0, 0); - DateTime startDateTime = currentDateTime.AddHours(-PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours); - DateTime endDateTime = currentDateTime; - - // Cache the result of SplitCamelCase(mt.Name) - string splitCamelCaseName = SystemHelper.SplitCamelCase(mt.Name); - - for (DateTime tickTime = startDateTime; tickTime <= endDateTime; tickTime = tickTime.AddMinutes(PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes)) - { - // Use binary search to find the range of items that match the condition - int index = marketTrendChangeSummaries.BinarySearch(new MarketTrendChange { TrendDateTime = tickTime }, Comparer.Create((x, y) => x.TrendDateTime.CompareTo(y.TrendDateTime))); - if (index < 0) index = ~index; - if (index < marketTrendChangeSummaries.Count) - { - MarketTrendChange mtc = marketTrendChangeSummaries[index]; - if (Double.IsInfinity(mtc.TrendChange)) mtc.TrendChange = 0; - - trendValues.Add("{ x: new Date('" + tickTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + mtc.TrendChange.ToString("0.00", CultureInfo.InvariantCulture) + "}"); - } - } - - // Add most recent tick - MarketTrendChange latestMtc = marketTrendChangeSummaries.Last(); - if (Double.IsInfinity(latestMtc.TrendChange)) latestMtc.TrendChange = 0; - trendValues.Add("{ x: new Date('" + latestMtc.TrendDateTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + latestMtc.TrendChange.ToString("0.00", CultureInfo.InvariantCulture) + "}"); - - // Use cached splitCamelCaseName - trendChartData.Add("{ key: '" + splitCamelCaseName + "', color: '" + lineColor + "', values: [" + string.Join(",\n", trendValues) + "] }"); - mtIndex++; + offset = TimeSpan.Zero; // If offset is invalid, set it to zero } + + DateTime currentDateTime = DateTime.UtcNow; + DateTime startDateTime = currentDateTime.AddHours(-PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours); + DateTime endDateTime = currentDateTime; + + // Ensure startDateTime doesn't exceed the available data + DateTime earliestTrendDateTime = marketTrendChangeSummaries.Min(mtc => mtc.TrendDateTime); + startDateTime = startDateTime > earliestTrendDateTime ? startDateTime : earliestTrendDateTime; + DataHours = (currentDateTime - earliestTrendDateTime).TotalHours; + + // Cache the result of SplitCamelCase(mt.Name) + string splitCamelCaseName = SystemHelper.SplitCamelCase(mt.Name); + + for (DateTime tickTime = startDateTime; tickTime <= endDateTime; tickTime = tickTime.AddMinutes(PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes)) + { + // Use binary search to find the range of items that match the condition + int index = marketTrendChangeSummaries.BinarySearch(new MarketTrendChange { TrendDateTime = tickTime }, Comparer.Create((x, y) => x.TrendDateTime.CompareTo(y.TrendDateTime))); + if (index < 0) index = ~index; + if (index < marketTrendChangeSummaries.Count) + { + MarketTrendChange mtc = marketTrendChangeSummaries[index]; + if (Double.IsInfinity(mtc.TrendChange)) mtc.TrendChange = 0; + + // Adjust tickTime to the desired timezone before converting to string + DateTime adjustedTickTime = tickTime.Add(isNegative ? -offset : offset); + trendValues.Add("{ x: new Date('" + adjustedTickTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + mtc.TrendChange.ToString("0.00", CultureInfo.InvariantCulture) + "}"); + } + } + + // Add most recent tick + MarketTrendChange latestMtc = marketTrendChangeSummaries.Last(); + if (Double.IsInfinity(latestMtc.TrendChange)) latestMtc.TrendChange = 0; + + // Adjust latestMtc.TrendDateTime to the desired timezone before converting to string + DateTime adjustedLatestTrendDateTime = latestMtc.TrendDateTime.Add(isNegative ? -offset : offset); + trendValues.Add("{ x: new Date('" + adjustedLatestTrendDateTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + latestMtc.TrendChange.ToString("0.00", CultureInfo.InvariantCulture) + "}"); + + // Use cached splitCamelCaseName + trendChartData.Add("{ key: '" + splitCamelCaseName + "', color: '" + lineColor + "', values: [" + string.Join(",\n", trendValues) + "] }"); + mtIndex++; } } } - } - TrendChartDataJSON = "[" + string.Join(",", trendChartData) + "]"; } + TrendChartDataJSON = "[" + string.Join(",", trendChartData) + "]"; +} private void BuildProfitChartData() +{ + List profitPerDayList = new List(); + + if (PTData.DailyPNL.Count > 0) { - StringBuilder profitPerDayJSON = new StringBuilder(); + // Get timezone offset + TimeSpan offset; + bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); + string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); - if (PTData.DailyPNL.Count > 0) + if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) { - DateTime endDate = DateTime.UtcNow.Date; - DateTime startDate = endDate.AddDays(-PTMagicConfiguration.GeneralSettings.Monitor.ProfitsMaxTimeframeDays - 1); // Fetch data for timeframe + 1 days - double previousDayCumulativeProfit = 0; - bool isFirstDay = true; - for (DateTime date = startDate; date <= endDate; date = date.AddDays(1)) - { - DailyPNLData dailyPNL = PTData.DailyPNL.Find(ds => DateTime.ParseExact(ds.Date, "d-M-yyyy", CultureInfo.InvariantCulture) == date); - if (dailyPNL != null) - { - if (isFirstDay) - { - isFirstDay = false; - } - else - { - // Calculate the profit for the current day - double profitFiat = Math.Round(dailyPNL.CumulativeProfitCurrency - previousDayCumulativeProfit, 2); - - // Add the data point to the JSON string - if (profitPerDayJSON.Length > 0) - { - profitPerDayJSON.Append(",\n"); - } - profitPerDayJSON.Append("{x: new Date('" + date.ToString("yyyy-MM-dd") + "'), y: " + profitFiat.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}"); - } - previousDayCumulativeProfit = dailyPNL.CumulativeProfitCurrency; - } - } - ProfitChartDataJSON = "[{key: 'Profit in " + PTData.Properties.Currency + "',color: '" + Constants.ChartLineColors[1] + "',values: [" + profitPerDayJSON.ToString() + "]}]"; + offset = TimeSpan.Zero; // If offset is invalid, set it to zero } + + DateTime endDate = DateTime.UtcNow.Add(isNegative ? -offset : offset).Date; + + // Parse dates once and adjust them to the local timezone + Dictionary dailyPNLByDate = PTData.DailyPNL + .Select(data => { + DateTime dateUtc = DateTime.ParseExact(data.Date, "d-M-yyyy", CultureInfo.InvariantCulture); + DateTime dateLocal = dateUtc.Add(isNegative ? -offset : offset); + return new { Date = dateLocal.Date, Data = data }; + }) + .ToDictionary( + item => item.Date, + item => item.Data + ); + + DateTime earliestDataDate = dailyPNLByDate.Keys.Min(); + DateTime startDate = endDate.AddDays(-PTMagicConfiguration.GeneralSettings.Monitor.ProfitsMaxTimeframeDays - 1); // Fetch data for timeframe + 1 days + if (startDate < earliestDataDate) + { + startDate = earliestDataDate; + } + + // Calculate the total days of data available + ProfitDays = (endDate - startDate).Days; + + double previousDayCumulativeProfit = 0; + bool isFirstDay = true; + for (DateTime date = startDate; date <= endDate; date = date.AddDays(1)) + { + // Use the dictionary to find the DailyPNLData for the date + if (dailyPNLByDate.TryGetValue(date, out DailyPNLData dailyPNL)) + { + if (isFirstDay) + { + isFirstDay = false; + } + else + { + // Calculate the profit for the current day + double profitFiat = Math.Round(dailyPNL.CumulativeProfitCurrency - previousDayCumulativeProfit, 2); + + // Add the data point to the list + profitPerDayList.Add(new { x = new DateTimeOffset(date).ToUnixTimeMilliseconds(), y = profitFiat }); + } + previousDayCumulativeProfit = dailyPNL.CumulativeProfitCurrency; + } + } + // Convert the list to a JSON string using Newtonsoft.Json + ProfitChartDataJSON = Newtonsoft.Json.JsonConvert.SerializeObject(new[] { + new { + key = "Profit in " + PTData.Properties.Currency, + color = Constants.ChartLineColors[1], + values = profitPerDayList + } + }); } +} private void BuildAssetDistributionData() { @@ -179,8 +235,7 @@ namespace Monitor.Pages foreach (Core.Main.DataObjects.PTMagicData.DCALogData dcaLogEntry in PTData.DCALog) { - string sellStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Summary, dcaLogEntry.SellStrategies, dcaLogEntry.SellStrategy, isSellStrategyTrue, isTrailingSellActive); - + string sellStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Summary, dcaLogEntry.SellStrategies, dcaLogEntry.SellStrategy, isSellStrategyTrue, isTrailingSellActive); // Aggregate totals double leverage = dcaLogEntry.Leverage; if (leverage == 0)