diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 4039d3e..f99eb36 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,9 +10,9 @@ "PTMagic" ], "group": { - "kind":"build", + "kind": "build", "isDefault": true - }, + }, "presentation": { "reveal": "always", "focus": true @@ -104,6 +104,13 @@ "focus": true }, "problemMatcher": "$msCompile" + }, + { + "type": "dotnet", + "task": "build", + "group": "build", + "problemMatcher": [], + "label": "dotnet: build" } ] } \ No newline at end of file diff --git a/Core/Core.csproj b/Core/Core.csproj index 6d671c2..4b67620 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -14,6 +14,7 @@ + diff --git a/Core/DataObjects/PTMagicData.cs b/Core/DataObjects/PTMagicData.cs index 3f80cd4..3b1a668 100644 --- a/Core/DataObjects/PTMagicData.cs +++ b/Core/DataObjects/PTMagicData.cs @@ -42,7 +42,7 @@ namespace Core.Main.DataObjects.PTMagicData public int FloodProtectionMinutes { get; set; } = 15; public string Exchange { get; set; } public string InstanceName { get; set; } = "PT Magic"; - public string TimezoneOffset { get; set; } = "+0:00"; + //public string TimezoneOffset { get; set; } = "+0:00"; public string CoinMarketCapAPIKey { get; set; } //public string FreeCurrencyConverterAPIKey { get; set; } } @@ -55,13 +55,13 @@ namespace Core.Main.DataObjects.PTMagicData public bool OpenBrowserOnStart { get; set; } = false; public int Port { get; set; } = 5000; public string AnalyzerChart { get; set; } = ""; + public int LiveTCVTimeframeMinutes { get; set; } = 60; public int GraphIntervalMinutes { get; set; } = 60; public int GraphMaxTimeframeHours { get; set; } = 24; public int ProfitsMaxTimeframeDays { get; set; } = 60; - public int RefreshSeconds { get; set; } = 30; + public int DashboardChartsRefreshSeconds { get; set; } = 30; public int BagAnalyzerRefreshSeconds { get; set; } = 5; public int BuyAnalyzerRefreshSeconds { get; set; } = 5; - //public int MaxSalesRecords { get; set; } = 99999; public int MaxTopMarkets { get; set; } = 20; public int MaxDailySummaries { get; set; } = 10; public int MaxMonthlySummaries { get; set; } = 10; @@ -70,6 +70,7 @@ namespace Core.Main.DataObjects.PTMagicData public int MaxDCAPairs { get; set; } = 24; public int MaxSettingsLogEntries { get; set; } = 20; public string LinkPlatform { get; set; } = "TradingView"; + public string TVCustomLayout { get; set; } = ""; public string DefaultDCAMode { get; set; } = "Simple"; public string TvStudyA { get; set; } = ""; public string TvStudyB { get; set; } = ""; @@ -164,6 +165,7 @@ namespace Core.Main.DataObjects.PTMagicData public class SingleMarketSetting { public string SettingName { get; set; } + public string TriggerConnection { get; set; } = "AND"; [DefaultValue("AND")] @@ -194,7 +196,10 @@ namespace Core.Main.DataObjects.PTMagicData } public class Trigger - { + { + [DefaultValue("")] + public string Tag { get; set; } = ""; + [DefaultValue("")] public string MarketTrendName { get; set; } = ""; @@ -218,7 +223,10 @@ namespace Core.Main.DataObjects.PTMagicData } public class OffTrigger - { + { + [DefaultValue("")] + public string Tag { get; set; } = ""; + [DefaultValue("")] public string MarketTrendName { get; set; } = ""; @@ -398,21 +406,6 @@ namespace Core.Main.DataObjects.PTMagicData public double LastPrice { get; set; } = 0; public double Last24hVolume { get; set; } = 0; } - -// public class SellLogData -// { -// public double SoldAmount { get; set; } -// public DateTime SoldDate { get; set; } -// public int BoughtTimes { get; set; } -// public string Market { get; set; } -// public double ProfitPercent { get; set; } -// public double Profit { get; set; } -// public double AverageBuyPrice { get; set; } -// public double TotalCost { get; set; } -// public double SoldPrice { get; set; } -// public double SoldValue { get; set; } -// public double TotalSales { get; set; } -// } public class StatsData { @@ -572,5 +565,7 @@ namespace Core.Main.DataObjects.PTMagicData public string Market { get; set; } public string TotalCurrentValue { get; set; } public string TimeZoneOffset { get; set; } + public string ExchangeURL { get; set; } + public string PTVersion { get; set; } } } \ No newline at end of file diff --git a/Core/DataObjects/ProfitTrailerData.cs b/Core/DataObjects/ProfitTrailerData.cs index c96c69d..c4008b1 100644 --- a/Core/DataObjects/ProfitTrailerData.cs +++ b/Core/DataObjects/ProfitTrailerData.cs @@ -82,8 +82,11 @@ namespace Core.Main.DataObjects { if (!_offsetTimeSpan.HasValue) { - // Get offset for settings. - _offsetTimeSpan = TimeSpan.Parse(_systemConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", "")); + // Ensure Misc is populated + var misc = this.Misc; + + // Get offset from Misc + _offsetTimeSpan = TimeSpan.Parse(misc.TimeZoneOffset); } return _offsetTimeSpan.Value; @@ -111,7 +114,7 @@ namespace Core.Main.DataObjects if (_misc == null || (DateTime.UtcNow > _miscRefresh)) { _misc = BuildMiscData(GetDataFromProfitTrailer("api/v2/data/misc")); - _miscRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1); + _miscRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1); } } } @@ -133,6 +136,8 @@ namespace Core.Main.DataObjects StartBalance = PTData.startBalance, TotalCurrentValue = PTData.totalCurrentValue, TimeZoneOffset = PTData.timeZoneOffset, + ExchangeURL = PTData.exchangeUrl, + PTVersion = PTData.version, }; } public List DailyStats @@ -180,7 +185,7 @@ namespace Core.Main.DataObjects { JArray dailyStatsSection = (JArray)extraSection["dailyStats"]; _dailyStats = dailyStatsSection.Select(j => BuildDailyStatsData(j as JObject)).ToList(); - _dailyStatsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1); + _dailyStatsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1); } } } @@ -215,7 +220,7 @@ namespace Core.Main.DataObjects if (_properties == null || (DateTime.UtcNow > _propertiesRefresh)) { _properties = BuildProptertiesData(GetDataFromProfitTrailer("api/v2/data/properties")); - _propertiesRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1); + _propertiesRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1); } } } @@ -258,7 +263,7 @@ namespace Core.Main.DataObjects jsonReader.Read(); // Move to the value of the "basic" property JObject basicSection = JObject.Load(jsonReader); _stats = BuildStatsData(basicSection); - _statsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1); + _statsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1); break; } } @@ -355,7 +360,7 @@ namespace Core.Main.DataObjects { JArray dailyPNLSection = (JArray)extraSection["dailyPNLStats"]; _dailyPNL = dailyPNLSection.Select(j => BuildDailyPNLData(j as JObject)).ToList(); - _dailyPNLRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1); + _dailyPNLRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1); } } } @@ -429,7 +434,7 @@ namespace Core.Main.DataObjects _profitablePairs.Add(BuildProfitablePairs(profitablePair)); counter++; } - _profitablePairsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1); + _profitablePairsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1); } } } @@ -495,7 +500,7 @@ namespace Core.Main.DataObjects { JArray dailyTCVSection = (JArray)extraSection["dailyTCVStats"]; _dailyTCV = dailyTCVSection.Select(j => BuildDailyTCVData(j as JObject)).ToList(); - _dailyTCVRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1); + _dailyTCVRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1); } } } @@ -563,7 +568,7 @@ namespace Core.Main.DataObjects { JArray monthlyStatsSection = (JArray)extraSection["monthlyStats"]; _monthlyStats = monthlyStatsSection.Select(j => BuildMonthlyStatsData(j as JObject)).ToList(); - _monthlyStatsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1); + _monthlyStatsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1); } } } @@ -585,57 +590,6 @@ namespace Core.Main.DataObjects Order = monthlyStatsDataJson["order"], }; } -// public List SellLog -// { -// get -// { - -// if (_sellLog == null || (DateTime.UtcNow > _sellLogRefresh)) -// { -// lock (_sellLock) -// { -// // Thread double locking -// if (_sellLog == null || (DateTime.UtcNow > _sellLogRefresh)) -// { -// _sellLog.Clear(); - - -// // Page through the sales data summarizing it. -// bool exitLoop = false; -// int pageIndex = 1; - -// // 1 record per page to allow user to set max records to retrieve -// int maxPages = _systemConfiguration.GeneralSettings.Monitor.MaxSalesRecords; -// int requestedPages = 0; - -// while (!exitLoop && requestedPages < maxPages) -// { -// var sellDataPage = GetDataFromProfitTrailer("/api/v2/data/sales?Page=1&perPage=1&sort=SOLDDATE&sortDirection=DESCENDING&page=" + pageIndex); -// if (sellDataPage != null && sellDataPage.data.Count > 0) -// { -// // Add sales data page to collection -// this.BuildSellLogData(sellDataPage); -// pageIndex++; -// requestedPages++; -// Console.WriteLine($"Importing salesLog: {pageIndex}"); - -// } -// else -// { -// // All data retrieved -// exitLoop = true; -// } -// } - -// // Update sell log refresh time -// _sellLogRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds -1); -// } -// } -// } -// return _sellLog; -// } -// } - public List DCALog { @@ -677,26 +631,30 @@ namespace Core.Main.DataObjects return _dcaLog; } } - + public List BuyLog { get { - if (_buyLog == null || (DateTime.UtcNow > _buyLogRefresh)) - { - lock (_buyLock) + if (_systemConfiguration.GeneralSettings.Monitor.MaxDashboardBuyEntries == 0) { - // Thread double locking - if (_buyLog == null || (DateTime.UtcNow > _buyLogRefresh)) - { - _buyLog.Clear(); - this.BuildBuyLogData(GetDataFromProfitTrailer("/api/v2/data/pbl", true)); - _buyLogRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds - 1); - } + return _buyLog; } - } - return _buyLog; + if (_buyLog == null || (DateTime.UtcNow > _buyLogRefresh)) + { + lock (_buyLock) + { + if (_buyLog == null || (DateTime.UtcNow > _buyLogRefresh)) + { + _buyLog.Clear(); + this.BuildBuyLogData(GetDataFromProfitTrailer("/api/v2/data/pbl", true)); + _buyLogRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds - 1); + } + } + } + + return _buyLog; } } diff --git a/Core/Helper/SystemHelper.cs b/Core/Helper/SystemHelper.cs index 9687e39..f2564f2 100644 --- a/Core/Helper/SystemHelper.cs +++ b/Core/Helper/SystemHelper.cs @@ -1,6 +1,4 @@ using System; -using System.Text.RegularExpressions; -using System.IO; using System.Collections; using System.Collections.Generic; using System.Net; @@ -535,18 +533,22 @@ namespace Core.Helper } } - public static string GetMarketLink(string platform, string exchange, string market, string mainMarket) + public static string GetMarketLink(string platform, string exchange, string market, string mainMarket, string tvCustomLayout) { string result = "#"; + if (tvCustomLayout.Length > 0) + { + tvCustomLayout += "/"; + } if (platform.Equals("TradingView")) { if (exchange.Equals("binancefutures", StringComparison.InvariantCultureIgnoreCase)) { - result = "https://uk.tradingview.com/chart/?symbol=BINANCE:" + market.ToUpper() + "PERP"; + result = "https://uk.tradingview.com/chart/"+tvCustomLayout+"?symbol=BINANCE:" + market.ToUpper() + ".P"; } else { - result = "https://uk.tradingview.com/chart/?symbol=" + exchange.ToUpper() + ":" + market.ToUpper(); + result = "https://uk.tradingview.com/chart/"+tvCustomLayout+"?symbol=" + exchange.ToUpper() + ":" + market.ToUpper(); } } else diff --git a/Core/Main/PTMagic.cs b/Core/Main/PTMagic.cs index 1270cd0..c9103d4 100644 --- a/Core/Main/PTMagic.cs +++ b/Core/Main/PTMagic.cs @@ -4,6 +4,8 @@ using System.Collections.Concurrent; using System.Threading; using System.IO; using System.Linq; +using System.Linq.Dynamic.Core; +using System.Linq.Expressions; using Core.Helper; using Core.Main.DataObjects.PTMagicData; using Core.MarketAnalyzer; @@ -59,6 +61,7 @@ namespace Core.Main private Dictionary _singleMarketSettingsCount = new Dictionary(); Dictionary> _triggeredSingleMarketSettings = new Dictionary>(); private static volatile object _lockObj = new object(); + private Mutex mutex = new Mutex(false, "analyzerStateMutex"); public LogHelper Log { @@ -122,6 +125,22 @@ namespace Core.Main _state = value; } } + public void WriteStateToFile() + { + try + { + mutex.WaitOne(); // Acquire the mutex + + string filePath = "_data/AnalyzerState."; + File.WriteAllText(filePath, this.State.ToString()); + + } + finally + { + mutex.ReleaseMutex(); // Release the mutex even if exceptions occur + } + } + public int RunCount { @@ -840,6 +859,7 @@ namespace Core.Main { // Change state to "Running" this.State = Constants.PTMagicBotState_Running; + this.WriteStateToFile(); this.RunCount++; this.LastRuntime = DateTime.UtcNow; @@ -975,6 +995,7 @@ namespace Core.Main // Change state to Finished / Stopped this.State = Constants.PTMagicBotState_Idle; + this.WriteStateToFile(); } } } @@ -991,6 +1012,7 @@ namespace Core.Main { Log.DoLogWarn("PTMagic raid " + this.RunCount.ToString() + " is taking longer than expected. Consider increasing your IntervalMinues setting, reducing other processes on your PC, or raising PTMagic's priority."); this.State = Constants.PTMagicBotState_Idle; + this.WriteStateToFile(); Log.DoLogInfo("PTMagic status reset, waiting for the next raid to be good to go again."); } } @@ -998,6 +1020,7 @@ namespace Core.Main { Log.DoLogWarn("No LastRuntimeSummary.json found after raid " + this.RunCount.ToString() + ", trying to reset PT Magic status..."); this.State = Constants.PTMagicBotState_Idle; + this.WriteStateToFile(); Log.DoLogInfo("PTMagic status reset, waiting for the next raid to be good to go again."); } } @@ -1300,86 +1323,122 @@ namespace Core.Main private void CheckGlobalSettingsTriggers(ref GlobalSetting triggeredSetting, ref List matchedTriggers) { - this.Log.DoLogInfo("Checking global settings triggers..."); - foreach (GlobalSetting globalSetting in this.PTMagicConfiguration.AnalyzerSettings.GlobalSettings) - { - // Reset triggers for each setting - matchedTriggers = new List(); + this.Log.DoLogInfo("Checking global settings triggers..."); - if (globalSetting.Triggers.Count > 0) + foreach (GlobalSetting globalSetting in this.PTMagicConfiguration.AnalyzerSettings.GlobalSettings) { - this.Log.DoLogInfo("Checking triggers for '" + globalSetting.SettingName + "'..."); - List triggerResults = new List(); - foreach (Trigger trigger in globalSetting.Triggers) - { - MarketTrend marketTrend = this.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.Find(mt => mt.Name == trigger.MarketTrendName); - if (marketTrend != null) + // Reset triggers for each setting + matchedTriggers = new List(); + + if (globalSetting.Triggers.Count > 0) { - - // Get market trend change for trigger - if (this.AverageMarketTrendChanges.ContainsKey(marketTrend.Name)) - { - double averageMarketTrendChange = this.AverageMarketTrendChanges[marketTrend.Name]; - if (averageMarketTrendChange >= trigger.MinChange && averageMarketTrendChange < trigger.MaxChange) + this.Log.DoLogInfo("Checking triggers for '" + globalSetting.SettingName + "'..."); + Dictionary triggerResults = new Dictionary(); + foreach (Trigger trigger in globalSetting.Triggers) { + MarketTrend marketTrend = this.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.Find(mt => mt.Name == trigger.MarketTrendName); + if (marketTrend != null) + { + // Get market trend change for trigger + if (this.AverageMarketTrendChanges.ContainsKey(marketTrend.Name)) + { + double averageMarketTrendChange = this.AverageMarketTrendChanges[marketTrend.Name]; + bool isTriggered = averageMarketTrendChange >= trigger.MinChange && averageMarketTrendChange < trigger.MaxChange; + triggerResults[trigger.Tag] = isTriggered; - // Trigger met! - this.Log.DoLogInfo("Trigger '" + trigger.MarketTrendName + "' triggered! TrendChange = " + averageMarketTrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"); + if (isTriggered) + { + // Trigger met! + this.Log.DoLogInfo("Trigger '" + trigger.MarketTrendName + "' triggered! TrendChange = " + averageMarketTrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"); - string triggerContent = trigger.MarketTrendName + " - "; - if (trigger.MinChange != Constants.MinTrendChange) - { - triggerContent += " - Min: " + trigger.MinChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"; - } + string triggerContent = trigger.MarketTrendName + " - "; + if (trigger.MinChange != Constants.MinTrendChange) + { + triggerContent += " - Min: " + trigger.MinChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"; + } - if (trigger.MaxChange != Constants.MaxTrendChange) - { - triggerContent += " - Max: " + trigger.MaxChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"; - } + if (trigger.MaxChange != Constants.MaxTrendChange) + { + triggerContent += " - Max: " + trigger.MaxChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"; + } - matchedTriggers.Add(triggerContent); + matchedTriggers.Add(triggerContent); + } + else + { + this.Log.DoLogDebug("Trigger '" + trigger.MarketTrendName + "' not triggered. TrendChange = " + averageMarketTrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"); + } + } + else + { + this.Log.DoLogError("Trigger '" + trigger.MarketTrendName + "' not found in this.AverageMarketTrendChanges[] (" + SystemHelper.ConvertListToTokenString(this.AverageMarketTrendChanges.Keys.ToList(), ",", true) + "). Unable to load recent trends?"); + } + } + else + { + this.Log.DoLogWarn("Market Trend '" + trigger.MarketTrendName + "' not found! Trigger ignored!"); + } + } - triggerResults.Add(true); + // Check if the TriggerConnection field exists + if (!string.IsNullOrEmpty(globalSetting.TriggerConnection)) + { + // Check if TriggerConnection is using the old logic + if (globalSetting.TriggerConnection.ToLower() == "and" || globalSetting.TriggerConnection.ToLower() == "or") + { + // Old logic + bool settingTriggered = false; + switch (globalSetting.TriggerConnection.ToLower()) + { + case "and": + settingTriggered = triggerResults.Values.All(tr => tr); + break; + case "or": + settingTriggered = triggerResults.Values.Any(tr => tr); + break; + } + + // Setting got triggered -> Activate it! + if (settingTriggered) + { + triggeredSetting = globalSetting; + break; + } + } + else + { + // New logic + string triggerConnection = globalSetting.TriggerConnection; + foreach (var triggerResult in triggerResults) + { + triggerConnection = triggerConnection.Replace(triggerResult.Key, triggerResult.Value.ToString().ToLower()); + } + + try + { + bool settingTriggered = (bool)System.Linq.Dynamic.Core.DynamicExpressionParser.ParseLambda(System.Linq.Dynamic.Core.ParsingConfig.Default, new ParameterExpression[0], typeof(bool), triggerConnection).Compile().DynamicInvoke(); + + // Setting got triggered -> Activate it! + if (settingTriggered) + { + triggeredSetting = globalSetting; + break; + } + } + catch (Exception ex) + { + this.Log.DoLogError($"ERROR: Trigger Connection for global setting {globalSetting.SettingName} is invalid or missing. Program halted."); + Environment.Exit(1); // Stop the program + } + } } else { - this.Log.DoLogDebug("Trigger '" + trigger.MarketTrendName + "' not triggered. TrendChange = " + averageMarketTrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"); - triggerResults.Add(false); + this.Log.DoLogError($"ERROR: Trigger Connection for global setting {globalSetting.SettingName} is missing. Program halted."); + Environment.Exit(1); // Stop the program } - } - else - { - this.Log.DoLogError("Trigger '" + trigger.MarketTrendName + "' not found in this.AverageMarketTrendChanges[] (" + SystemHelper.ConvertListToTokenString(this.AverageMarketTrendChanges.Keys.ToList(), ",", true) + "). Unable to load recent trends?"); - triggerResults.Add(false); - } } - else - { - this.Log.DoLogWarn("Market Trend '" + trigger.MarketTrendName + "' not found! Trigger ignored!"); - triggerResults.Add(false); - } - } - - // Check if all triggers have to get triggered or just one - bool settingTriggered = false; - switch (globalSetting.TriggerConnection.ToLower()) - { - case "and": - settingTriggered = triggerResults.FindAll(tr => tr == false).Count == 0; - break; - case "or": - settingTriggered = triggerResults.FindAll(tr => tr == true).Count > 0; - break; - } - - // Setting got triggered -> Activate it! - if (settingTriggered) - { - triggeredSetting = globalSetting; - break; - } } - } } private void ActivateSetting(ref GlobalSetting triggeredSetting, ref List matchedTriggers) diff --git a/Monitor/Pages/Index.cshtml b/Monitor/Pages/Index.cshtml index 770f037..2ff2ffe 100644 --- a/Monitor/Pages/Index.cshtml +++ b/Monitor/Pages/Index.cshtml @@ -25,10 +25,13 @@ + } diff --git a/Monitor/Pages/MarketAnalyzer.cshtml.cs b/Monitor/Pages/MarketAnalyzer.cshtml.cs index be4faf6..8b4d780 100644 --- a/Monitor/Pages/MarketAnalyzer.cshtml.cs +++ b/Monitor/Pages/MarketAnalyzer.cshtml.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Core.Main; using Core.Helper; +using Core.Main.DataObjects; using Core.Main.DataObjects.PTMagicData; using System.Globalization; @@ -11,6 +12,8 @@ namespace Monitor.Pages public class MarketAnalyzerModel : _Internal.BasePageModelSecure { public List MarketTrends { get; set; } = new List(); + public ProfitTrailerData PTData = null; + public MiscData MiscData { get; set; } public string TrendChartDataJSON = ""; public double DataHours { get; set; } @@ -22,7 +25,8 @@ namespace Monitor.Pages private void BindData() { - // Get market trends + PTData = this.PtDataObject; + MiscData = this.PTData.Misc; MarketTrends = PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.OrderBy(mt => mt.TrendMinutes).ThenByDescending(mt => mt.Platform).ToList(); BuildMarketTrendChartData(); @@ -56,8 +60,8 @@ namespace Monitor.Pages // Get trend ticks for chart TimeSpan offset; - bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); - string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); + bool isNegative = MiscData.TimeZoneOffset.StartsWith("-"); + string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-'); if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) { diff --git a/Monitor/Pages/SalesAnalyzer.cshtml b/Monitor/Pages/SalesAnalyzer.cshtml index 459f086..29554b6 100644 --- a/Monitor/Pages/SalesAnalyzer.cshtml +++ b/Monitor/Pages/SalesAnalyzer.cshtml @@ -329,7 +329,7 @@ double profitFiat = Math.Round(profit * Model.MiscData.FiatConversionRate, 0); @rank - @coin + @coin @profit @sales @avg @@ -394,20 +394,7 @@ .transition().duration(0) .call(salesChart); - // Add mouseleave, and mousemove event listeners to hide tooltip - d3.select('.sales-chart').on('mouseleave', function() { - d3.select('.nvtooltip').style('opacity', 0); - }); - - d3.select('body').on('mousemove', function() { - var chartBounds = d3.select('.sales-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); - } - }); + nv.utils.windowResize(salesChart.update); return salesChart; }); @@ -442,20 +429,7 @@ .transition().duration(0) .call(cumulativeProfitChart); - // Add mouseleave, and mousemove event listeners to hide tooltip - d3.select('.cumulative-profit-chart').on('mouseleave', function() { - d3.select('.nvtooltip').style('opacity', 0); - }); - - d3.select('body').on('mousemove', function() { - var chartBounds = d3.select('.cumulative-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); - } - }); + nv.utils.windowResize(cumulativeProfitChart.update); return cumulativeProfitChart; }); @@ -490,20 +464,6 @@ .transition().duration(0) .call(TCVChart); - // Add mouseleave, and mousemove event listeners to hide tooltip - d3.select('.TCV-chart').on('mouseleave', function() { - d3.select('.nvtooltip').style('opacity', 0); - }); - - d3.select('body').on('mousemove', function() { - var chartBounds = d3.select('.TCV-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); - } - }); nv.utils.windowResize(TCVChart.update); return TCVChart; }); @@ -538,20 +498,33 @@ .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); + + profitChart.dispatch.on('renderEnd', function() { + // Get the chart's container + var container = d3.select('.profit-chart .nv-wrap.nv-lineChart .nv-linesWrap'); + + // Remove any existing y=0 line + container.selectAll('.zero-line').remove(); + + // Check if profitData[0].values is not empty + if (profitData[0].values.length > 0) { + // Get the x-values of the first and last data points + var xMin = profitChart.xAxis.scale()(profitData[0].values[0].x); + var xMax = profitChart.xAxis.scale()(profitData[0].values[profitData[0].values.length - 1].x); + + // Add a line at y=0 + container.insert('line', ':first-child') + .attr('class', 'zero-line') // Add a class to the line for easy selection + .attr('x1', xMin) // x position of the first end of the line + .attr('y1', profitChart.yAxis.scale()(0)) // y position of the first end of the line + .attr('x2', xMax) // x position of the second end of the line + .attr('y2', profitChart.yAxis.scale()(0)) // y position of the second end of the line + .attr('stroke', 'gray') // color of the line + .attr('stroke-width', 2); // width of the line + } }); - 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); - } - }); nv.utils.windowResize(profitChart.update); return profitChart; }); @@ -559,4 +532,34 @@ } })(jQuery); + + + } diff --git a/Monitor/Pages/SalesAnalyzer.cshtml.cs b/Monitor/Pages/SalesAnalyzer.cshtml.cs index dbe7c92..a37d9bd 100644 --- a/Monitor/Pages/SalesAnalyzer.cshtml.cs +++ b/Monitor/Pages/SalesAnalyzer.cshtml.cs @@ -54,7 +54,7 @@ namespace Monitor.Pages DailyStats = this.PTData.DailyStats; // Convert local offset time to UTC - TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", "")); + TimeSpan offsetTimeSpan = TimeSpan.Parse(MiscData.TimeZoneOffset.Replace("+", "")); DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan); BuildSalesChartData(); @@ -71,8 +71,8 @@ namespace Monitor.Pages { // Get timezone offset TimeSpan offset; - bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); - string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); + bool isNegative = MiscData.TimeZoneOffset.StartsWith("-"); + string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-'); if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) { @@ -129,8 +129,8 @@ namespace Monitor.Pages { // Get timezone offset TimeSpan offset; - bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); - string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); + bool isNegative = MiscData.TimeZoneOffset.StartsWith("-"); + string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-'); if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) { @@ -190,8 +190,8 @@ namespace Monitor.Pages { // Get timezone offset TimeSpan offset; - bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); - string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); + bool isNegative = MiscData.TimeZoneOffset.StartsWith("-"); + string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-'); if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) { @@ -301,8 +301,8 @@ namespace Monitor.Pages { // Get timezone offset TimeSpan offset; - bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); - string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); + bool isNegative = MiscData.TimeZoneOffset.StartsWith("-"); + string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-'); if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) { diff --git a/Monitor/Pages/SettingsAnalyzer.cshtml.cs b/Monitor/Pages/SettingsAnalyzer.cshtml.cs index dcca15d..e627651 100644 --- a/Monitor/Pages/SettingsAnalyzer.cshtml.cs +++ b/Monitor/Pages/SettingsAnalyzer.cshtml.cs @@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Mvc; using Core.Main; using Core.Helper; using Core.Main.DataObjects.PTMagicData; -using Microsoft.Extensions.Primitives; namespace Monitor.Pages { diff --git a/Monitor/Pages/SettingsGeneral.cshtml b/Monitor/Pages/SettingsGeneral.cshtml index 3b01e47..f01ef9b 100644 --- a/Monitor/Pages/SettingsGeneral.cshtml +++ b/Monitor/Pages/SettingsGeneral.cshtml @@ -117,12 +117,12 @@ -
+ @*
-
+
*@ @*
@@ -196,42 +196,48 @@
- + +
+ +
+
+
+
- +
- +
- +
- +
- +
- +
@@ -247,12 +253,12 @@
- @*
- +
+
- +
-
*@ +
diff --git a/Monitor/Pages/SettingsGeneral.cshtml.cs b/Monitor/Pages/SettingsGeneral.cshtml.cs index 85a10d4..83f297f 100644 --- a/Monitor/Pages/SettingsGeneral.cshtml.cs +++ b/Monitor/Pages/SettingsGeneral.cshtml.cs @@ -27,30 +27,6 @@ namespace Monitor.Pages return result; } - public string GetTimezoneSelection() - { - string result = ""; - - List tzOffsetList = new List(); - foreach (TimeZoneInfo tzi in TimeZoneInfo.GetSystemTimeZones()) - { - string offsetString = this.GetTimezoneOffsetString(tzi); - if (!tzOffsetList.Contains(offsetString)) - { - string selected = ""; - if (PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Equals(offsetString, StringComparison.InvariantCultureIgnoreCase)) - { - selected = " selected=\"selected\""; - } - - result += "" + offsetString + "\n"; - tzOffsetList.Add(offsetString); - } - } - - return result; - } - public void OnGet() { base.Init(); @@ -72,30 +48,25 @@ namespace Monitor.Pages // Read the new settings PTMagicConfiguration.GeneralSettings.Application.IsEnabled = HttpContext.Request.Form["Application_IsEnabled"].Equals("on"); - PTMagicConfiguration.GeneralSettings.Application.TestMode = HttpContext.Request.Form["Application_TestMode"].Equals("on"); - //PTMagicConfiguration.GeneralSettings.Application.StartBalance = SystemHelper.TextToDouble(HttpContext.Request.Form["Application_StartBalance"], PTMagicConfiguration.GeneralSettings.Application.StartBalance, "en-US"); PTMagicConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName = HttpContext.Request.Form["Application_ProfitTrailerDefaultSettingName"]; PTMagicConfiguration.GeneralSettings.Application.Exchange = HttpContext.Request.Form["Application_Exchange"]; PTMagicConfiguration.GeneralSettings.Application.ProfitTrailerMonitorURL = HttpContext.Request.Form["Application_ProfitTrailerMonitorURL"]; - PTMagicConfiguration.GeneralSettings.Application.ProfitTrailerServerAPIToken = HttpContext.Request.Form["Application_ProfitTrailerServerAPIToken"]; - PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset = HttpContext.Request.Form["Application_TimezoneOffset"]; - //PTMagicConfiguration.GeneralSettings.Application.MainFiatCurrency = HttpContext.Request.Form["Application_MainFiatCurrency"]; PTMagicConfiguration.GeneralSettings.Application.FloodProtectionMinutes = SystemHelper.TextToInteger(HttpContext.Request.Form["Application_FloodProtectionMinutes"], PTMagicConfiguration.GeneralSettings.Application.FloodProtectionMinutes); PTMagicConfiguration.GeneralSettings.Application.InstanceName = HttpContext.Request.Form["Application_InstanceName"]; PTMagicConfiguration.GeneralSettings.Application.CoinMarketCapAPIKey = HttpContext.Request.Form["Application_CoinMarketCapAPIKey"]; - //PTMagicConfiguration.GeneralSettings.Application.FreeCurrencyConverterAPIKey = HttpContext.Request.Form["Application_FreeCurrencyConverterAPIKey"]; PTMagicConfiguration.GeneralSettings.Monitor.IsPasswordProtected = HttpContext.Request.Form["Monitor_IsPasswordProtected"].Equals("on"); PTMagicConfiguration.GeneralSettings.Monitor.OpenBrowserOnStart = HttpContext.Request.Form["Monitor_OpenBrowserOnStart"].Equals("on"); PTMagicConfiguration.GeneralSettings.Monitor.AnalyzerChart = HttpContext.Request.Form["Monitor_AnalyzerChart"]; PTMagicConfiguration.GeneralSettings.Monitor.Port = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_Port"], PTMagicConfiguration.GeneralSettings.Monitor.Port); + PTMagicConfiguration.GeneralSettings.Monitor.LiveTCVTimeframeMinutes = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_LiveTCVTimeframeMinutes"], PTMagicConfiguration.GeneralSettings.Monitor.LiveTCVTimeframeMinutes); PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_GraphIntervalMinutes"], PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes); PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_GraphMaxTimeframeHours"], PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours); PTMagicConfiguration.GeneralSettings.Monitor.ProfitsMaxTimeframeDays = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_ProfitsMaxTimeframeDays"], PTMagicConfiguration.GeneralSettings.Monitor.ProfitsMaxTimeframeDays); - PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_RefreshSeconds"], PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds); + PTMagicConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_RefreshSeconds"], PTMagicConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds); PTMagicConfiguration.GeneralSettings.Monitor.BagAnalyzerRefreshSeconds = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_BagAnalyzerRefreshSeconds"], PTMagicConfiguration.GeneralSettings.Monitor.BagAnalyzerRefreshSeconds); PTMagicConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_BuyAnalyzerRefreshSeconds"], PTMagicConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds); PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform = HttpContext.Request.Form["Monitor_LinkPlatform"]; - //PTMagicConfiguration.GeneralSettings.Monitor.MaxSalesRecords = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxSalesRecords"], PTMagicConfiguration.GeneralSettings.Monitor.MaxSalesRecords); + PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout = HttpContext.Request.Form["Monitor_TVCustomLayout"]; PTMagicConfiguration.GeneralSettings.Monitor.MaxTopMarkets = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxTopMarkets"], PTMagicConfiguration.GeneralSettings.Monitor.MaxTopMarkets); PTMagicConfiguration.GeneralSettings.Monitor.MaxDailySummaries = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxDailySummaries"], PTMagicConfiguration.GeneralSettings.Monitor.MaxDailySummaries); PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxMonthlySummaries"], PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries); diff --git a/Monitor/Pages/StatusSummary.cshtml b/Monitor/Pages/StatusSummary.cshtml index 7bab1c6..cb07c84 100644 --- a/Monitor/Pages/StatusSummary.cshtml +++ b/Monitor/Pages/StatusSummary.cshtml @@ -116,13 +116,8 @@
- - - - -

Global Settings Log

@@ -136,7 +131,7 @@ @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("+", "")); + TimeSpan offsetTimeSpan = TimeSpan.Parse(Model.MiscData.TimeZoneOffset.Replace("+", "")); DateTimeOffset settingActivationTime = gss.SwitchDateTime; settingActivationTime = settingActivationTime.ToOffset(offsetTimeSpan); diff --git a/Monitor/Pages/StatusSummary.cshtml.cs b/Monitor/Pages/StatusSummary.cshtml.cs index 8532f09..6f78877 100644 --- a/Monitor/Pages/StatusSummary.cshtml.cs +++ b/Monitor/Pages/StatusSummary.cshtml.cs @@ -4,6 +4,7 @@ using System.Linq; using Microsoft.AspNetCore.Http; using Core.Main; using Core.Helper; +using Core.Main.DataObjects; using Core.Main.DataObjects.PTMagicData; namespace Monitor.Pages @@ -14,6 +15,8 @@ namespace Monitor.Pages public string SettingsDistribution24hChartDataJSON = ""; public string SettingsDistribution3dChartDataJSON = ""; private Dictionary settingsChartColors = new Dictionary(); + public ProfitTrailerData PTData = null; + public MiscData MiscData { get; set; } public void OnGet() { @@ -24,6 +27,8 @@ namespace Monitor.Pages private void BindData() { + PTData = this.PtDataObject; + MiscData = this.PTData.Misc; BuildMarketsWithSingleSettings(); BuildChartColors(); Build24hChartData(); diff --git a/Monitor/Pages/Transactions.cshtml b/Monitor/Pages/Transactions.cshtml deleted file mode 100644 index b41eb9a..0000000 --- a/Monitor/Pages/Transactions.cshtml +++ /dev/null @@ -1,174 +0,0 @@ -@page -@model TransactionsModel -@{ - ViewData["Title"] = ""; -} - -@section Styles { - - - -} - -
-
-
-

Transactions

- -

- In this area you may add manual transactions (like deposits and withdrawals) to your PT Magic data. Adding this kind of information will help PT Magic calculating your percentage gains and your balance more accurately. -

-
-
-
- -
-
-
-
-

New Transaction

- -
- -
- -
-
- -
- -
-
- - -
-
-
- -
- -
-
- - -
-
-
- -
- -
- -
-
-
-
-
- - @if (!Model.ValidationMessage.Equals("")) { -
-
-
- @Model.ValidationMessage -
-
-
- } - - - -
-
-
-

Your Transactions

- - @if (Model.TransactionData.Transactions.Count > 0) { -
- - - - - - - - - - @foreach (Core.Main.DataObjects.PTMagicData.Transaction transaction in Model.TransactionData.Transactions) { - - - - - @if (transaction.Amount > 0) { - - } else { - - } - - } - -
TimeAmountType
@transaction.GetLocalDateTime(Model.PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset).ToShortDateString() @transaction.GetLocalDateTime(Model.PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset).ToShortTimeString()@transaction.Amount.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))DepositWithdrawal
- } else { -

No transactions found.

- } -
- - - - - -@section Scripts { - - - - - - - -} diff --git a/Monitor/Pages/Transactions.cshtml.cs b/Monitor/Pages/Transactions.cshtml.cs deleted file mode 100644 index 8312f30..0000000 --- a/Monitor/Pages/Transactions.cshtml.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Http; -using Core.Main; -using Core.Helper; -using Core.Main.DataObjects; -using Core.Main.DataObjects.PTMagicData; -using System.Globalization; - -namespace Monitor.Pages -{ - public class TransactionsModel : _Internal.BasePageModelSecure - { - public TransactionData TransactionData = null; - public string ValidationMessage = ""; - - public void OnGet() - { - base.Init(); - - BindData(); - } - - private void BindData() - { - TransactionData = new TransactionData(PTMagicBasePath); - } - - public void OnPost() - { - base.Init(); - - BindData(); - - SaveTransaction(); - } - - private void SaveTransaction() - { - double transactionAmount = 0; - DateTimeOffset transactionDateTime = Constants.confMinDate; - - try - { - transactionAmount = SystemHelper.TextToDouble(HttpContext.Request.Form["Transaction_Amount"], transactionAmount, "en-US"); - //transactionDateTime = DateTimeOffset.Parse(HttpContext.Request.Form["Transaction_Date"] + " " + HttpContext.Request.Form["Transaction_Time"], CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); - DateTime tmp = DateTime.Parse(HttpContext.Request.Form["Transaction_Date"] + " " + HttpContext.Request.Form["Transaction_Time"], CultureInfo.InvariantCulture, DateTimeStyles.None); - - // Convert local offset time to UTC - TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", "")); - transactionDateTime = new DateTimeOffset(tmp, offsetTimeSpan); - } - catch { } - - if (transactionAmount == 0) - { - ValidationMessage = "Please enter a valid amount in the format 123.45!"; - } - else - { - if (transactionDateTime == Constants.confMinDate) - { - ValidationMessage = "Please select a valid date and time!"; - } - else - { - TransactionData.Transactions.Add(new Transaction() { GUID = Guid.NewGuid().ToString(), Amount = transactionAmount, UTCDateTime = transactionDateTime.UtcDateTime }); - TransactionData.SaveTransactions(PTMagicBasePath); - - NotifyHeadline = "Transaction saved!"; - NotifyMessage = "Transaction saved successfully to _data/Transactions.json."; - NotifyType = "success"; - } - } - } - } -} diff --git a/Monitor/Pages/_Layout.cshtml b/Monitor/Pages/_Layout.cshtml index 337bab8..a4b16ba 100644 --- a/Monitor/Pages/_Layout.cshtml +++ b/Monitor/Pages/_Layout.cshtml @@ -192,7 +192,7 @@ } // Reinstate the interval. - interval = setInterval(function () { loadWidgets(); }, 5000); + interval = setInterval(function () { loadWidgets(); }, 3000); }); }; diff --git a/Monitor/Pages/_get/BagDetails.cshtml b/Monitor/Pages/_get/BagDetails.cshtml index aba6d69..30bb6a4 100644 --- a/Monitor/Pages/_get/BagDetails.cshtml +++ b/Monitor/Pages/_get/BagDetails.cshtml @@ -103,7 +103,7 @@ diff --git a/Monitor/Pages/_get/BagDetails.cshtml.cs b/Monitor/Pages/_get/BagDetails.cshtml.cs index b6e651b..e1463b6 100644 --- a/Monitor/Pages/_get/BagDetails.cshtml.cs +++ b/Monitor/Pages/_get/BagDetails.cshtml.cs @@ -10,6 +10,7 @@ using Core.MarketAnalyzer; namespace Monitor.Pages { public class BagDetailsModel : _Internal.BasePageModelSecure { public ProfitTrailerData PTData = null; + public MiscData MiscData = null; public string DCAMarket = ""; public DCALogData DCALogData = null; public DateTimeOffset DateTimeNow = Constants.confMinDate; @@ -23,13 +24,12 @@ namespace Monitor.Pages { private void BindData() { DCAMarket = GetStringParameter("m", ""); - PTData = this.PtDataObject; - + MiscData = this.PTData.Misc; DCALogData = PTData.DCALog.Find(d => d.Market == DCAMarket); // Convert local offset time to UTC - TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", "")); + TimeSpan offsetTimeSpan = TimeSpan.Parse(MiscData.TimeZoneOffset.Replace("+", "")); DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan); } } diff --git a/Monitor/Pages/_get/BagList.cshtml b/Monitor/Pages/_get/BagList.cshtml index 2323bc1..2c072db 100644 --- a/Monitor/Pages/_get/BagList.cshtml +++ b/Monitor/Pages/_get/BagList.cshtml @@ -84,9 +84,9 @@ // Market @if (mps != null && (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0)) { - @dcaLogEntry.Market + @dcaLogEntry.Market } else { - @dcaLogEntry.Market + @dcaLogEntry.Market diff --git a/Monitor/Pages/_get/BuyList.cshtml b/Monitor/Pages/_get/BuyList.cshtml index 373d1d0..01b29c9 100644 --- a/Monitor/Pages/_get/BuyList.cshtml +++ b/Monitor/Pages/_get/BuyList.cshtml @@ -37,9 +37,9 @@ string triggerValueText = Core.ProfitTrailer.StrategyHelper.GetTriggerValueText(Model.Summary, buyLogEntry.BuyStrategies, buyLogEntry.BuyStrategy, buyLogEntry.BBTrigger, buyLogEntry.TriggerValue, 0, true); @if (mps != null && (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0)) { - @buyLogEntry.Market + @buyLogEntry.Market } else { - @buyLogEntry.Market + @buyLogEntry.Market } @buyLogEntry.PercChange.ToString("#,#0.00")% @if (buyDisabled) { diff --git a/Monitor/Pages/_get/DashboardBottom.cshtml b/Monitor/Pages/_get/DashboardBottom.cshtml index 2e09d17..6b66682 100644 --- a/Monitor/Pages/_get/DashboardBottom.cshtml +++ b/Monitor/Pages/_get/DashboardBottom.cshtml @@ -10,7 +10,48 @@ }
-
+ +
+
+
+ @{ + string totalCurrentValueString = Model.totalCurrentValue.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US")); + if (Model.totalCurrentValue > 100) { + totalCurrentValueString = Math.Round(Model.totalCurrentValue, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")); + } + } +
+
+ TCV: @totalCurrentValueString @Model.Summary.MainMarket +
+
+ + Start: @Model.MiscData.StartBalance +   Gain: @Math.Round(((Model.totalCurrentValue - Model.MiscData.StartBalance) / Model.MiscData.StartBalance) * 100, 2)% + +
+ +
+
+
+ +
+
+

Live TCV Trend + @if (!Model.TotalCurrentValueLiveChartDataJSON.Equals("")) { +
+ +
+ } else { +

Unable to load graph, no sales data found.

+ } +

+
+
+ +
+

Market Trend History

@if (!Model.TrendChartDataJSON.Equals("")) { @@ -23,37 +64,12 @@
-
-
-
- @{ - string totalCurrentValueString = Model.totalCurrentValue.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US")); - if (Model.totalCurrentValue > 100) { - totalCurrentValueString = Math.Round(Model.totalCurrentValue, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")); - } - } -
-
- - Start:   @Model.MiscData.StartBalance @Model.Summary.MainMarket -     Gain: @Math.Round(((Model.totalCurrentValue - Model.MiscData.StartBalance) / Model.MiscData.StartBalance) * 100, 2)% - -
-
- TCV:   @totalCurrentValueString @Model.Summary.MainMarket -
-
- -
-
-
-
+ -
+
@*
*@
-

Daily Profit +

Daily Profit @if (!Model.ProfitChartDataJSON.Equals("")) {
@@ -70,9 +86,9 @@
@*

*@ -

Live Trends - - ANALYZER +

Live Market Trends + + ANALYZER

@@ -80,7 +96,7 @@ - @@ -283,13 +299,20 @@ + + + + - $('[data-toggle="tooltip"]').tooltip(); - }); + + + diff --git a/Monitor/Pages/_get/DashboardBottom.cshtml.cs b/Monitor/Pages/_get/DashboardBottom.cshtml.cs index 6685342..8fdb9b0 100644 --- a/Monitor/Pages/_get/DashboardBottom.cshtml.cs +++ b/Monitor/Pages/_get/DashboardBottom.cshtml.cs @@ -26,6 +26,7 @@ namespace Monitor.Pages public DateTimeOffset DateTimeNow = Constants.confMinDate; public string AssetDistributionData = ""; public double totalCurrentValue = 0; + public string TotalCurrentValueLiveChartDataJSON { get; set; } public void OnGet() { // Initialize Config @@ -49,7 +50,7 @@ namespace Monitor.Pages FileHelper.CleanupFilesMinutes(PTMagicMonitorBasePath + "wwwroot" + System.IO.Path.DirectorySeparatorChar + "assets" + System.IO.Path.DirectorySeparatorChar + "tmp" + System.IO.Path.DirectorySeparatorChar, 5); // Convert local offset time to UTC - TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", "")); + TimeSpan offsetTimeSpan = TimeSpan.Parse(MiscData.TimeZoneOffset.Replace("+", "")); DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan); // Get last and current active setting @@ -65,8 +66,109 @@ namespace Monitor.Pages BuildMarketTrendChartData(); BuildAssetDistributionData(); BuildProfitChartData(); + StartUpdatingTotalCurrentValueLive(); + UpdateTotalCurrentValueLive(); + BuildTotalCurrentValueLiveChartData(); } + private static System.Timers.Timer timer; + private static List<(DateTime Timestamp, double TotalCurrentValue)> totalCurrentValueLiveList; + +public void StartUpdatingTotalCurrentValueLive() +{ + int liveTCVInterval = PTMagicConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds; + if (timer != null) + { + // Timer is already running + return; + } + + totalCurrentValueLiveList = new List<(DateTime Timestamp, double TotalCurrentValueLive)>(); + + timer = new System.Timers.Timer(liveTCVInterval * 1000); // Set interval to liveTCVTimer seconds + timer.Elapsed += (sender, e) => UpdateTotalCurrentValueLive(); + timer.Start(); +} + +private void UpdateTotalCurrentValueLive() +{ + double PairsBalance = 0.0; + double DCABalance = 0.0; + double PendingBalance = 0.0; + double AvailableBalance = PTData.GetCurrentBalance(); + bool isSellStrategyTrue = false; + bool isTrailingSellActive = false; + + foreach (DCALogData dcaLogEntry in PTData.DCALog) + { + string sellStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Summary, dcaLogEntry.SellStrategies, dcaLogEntry.SellStrategy, isSellStrategyTrue, isTrailingSellActive); + // Aggregate totals + double leverage = dcaLogEntry.Leverage; + if (leverage == 0) + { + leverage = 1; + } + if (sellStrategyText.Contains("PENDING")) + { + PendingBalance = PendingBalance + (dcaLogEntry.Amount * dcaLogEntry.CurrentPrice / leverage); + } + else if (dcaLogEntry.BuyStrategies.Count > 0) + { + DCABalance = DCABalance + (dcaLogEntry.Amount * dcaLogEntry.CurrentPrice / leverage); + } + else + { + PairsBalance = PairsBalance + (dcaLogEntry.Amount * dcaLogEntry.CurrentPrice / leverage); + } + } + double totalCurrentValueLive = PendingBalance + DCABalance + PairsBalance + AvailableBalance; + + // Get the current time + DateTime now = DateTime.UtcNow; + totalCurrentValueLiveList.Add((now, totalCurrentValueLive)); + // Get liveTCVTimeframe from PTMagicConfiguration.GeneralSettings.Monitor + int liveTCVTimeframe = PTMagicConfiguration.GeneralSettings.Monitor.LiveTCVTimeframeMinutes; + + // Calculate the timestamp that is liveTCVTimeframe minutes ago + DateTime threshold = now.AddMinutes(-liveTCVTimeframe); + + // Remove all data points that are older than the threshold + while (totalCurrentValueLiveList.Count > 0 && totalCurrentValueLiveList[0].Item1 < threshold) + { + totalCurrentValueLiveList.RemoveAt(0); + } +} + +private void BuildTotalCurrentValueLiveChartData() +{ + List TotalCurrentValueLivePerIntervalList = new List(); + + if (totalCurrentValueLiveList.Count > 0) + { + foreach (var dataPoint in totalCurrentValueLiveList) + { + DateTime timestamp = dataPoint.Timestamp; + double totalCurrentValueLive = dataPoint.TotalCurrentValue; + + // Convert the timestamp to a Unix timestamp + long unixTimestamp = new DateTimeOffset(timestamp).ToUnixTimeMilliseconds(); + + // Add the data point to the list + TotalCurrentValueLivePerIntervalList.Add(new { x = unixTimestamp, y = totalCurrentValueLive }); + } + + // Convert the list to a JSON string using Newtonsoft.Json + TotalCurrentValueLiveChartDataJSON = Newtonsoft.Json.JsonConvert.SerializeObject(new[] { + new { + key = "Total Current Value", + color = Constants.ChartLineColors[1], + values = TotalCurrentValueLivePerIntervalList + } + }); + } +} + + private void BuildMarketTrendChartData() { List trendChartData = new List(); @@ -95,8 +197,8 @@ namespace Monitor.Pages // Get trend ticks for chart TimeSpan offset; - bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); - string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); + bool isNegative = MiscData.TimeZoneOffset.StartsWith("-"); + string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-'); if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) { @@ -158,8 +260,8 @@ namespace Monitor.Pages { // Get timezone offset TimeSpan offset; - bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); - string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); + bool isNegative = MiscData.TimeZoneOffset.StartsWith("-"); + string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-'); if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) { diff --git a/Monitor/Pages/_get/DashboardTop.cshtml b/Monitor/Pages/_get/DashboardTop.cshtml index 3a0b262..c0805d1 100644 --- a/Monitor/Pages/_get/DashboardTop.cshtml +++ b/Monitor/Pages/_get/DashboardTop.cshtml @@ -3,13 +3,15 @@ @{ Layout = null; } - -
+@{ + bool sideBySide = true; + } +
@if (Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBuyEntries>0) { -
+
-

Possible Buys (@Model.PTData.BuyLog.Count)more

+

Possible Buys (@Model.PTData.BuyLog.Count)more

@if (Model.PTData.BuyLog.Count == 0) {

Your Profit Trailer did not find anything worth buying so far.

@@ -19,11 +21,10 @@
Name Markets TimeframeThreshold   + Threshold   Change
- - - - - + + + + @@ -57,13 +58,21 @@ @if (mps == null || mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0) { - + } else { - + } - - - + + @if (buyDisabled) { @@ -85,7 +94,7 @@
-

Pairs / DCA / Pending (@Model.PTData.DCALog.Count)more

+

Positions (@Model.PTData.DCALog.Count)more

@if (Model.PTData.DCALog.Count == 0) { @@ -94,16 +103,15 @@ else {
-
Market24HVolumeAskBuy StrategiesMarket Volume Ask Buy Strategies
@buyLogEntry.Market + @buyLogEntry.Market +
@Html.Raw((buyLogEntry.PercChange * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))) % +
@buyLogEntry.Market   + + @Html.Raw((buyLogEntry.PercChange * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))) % + @string.Format("{0}%", (buyLogEntry.PercChange * 100).ToString("#,#0.00"))@string.Format("{0}", (buyLogEntry.Volume24h).ToString())@buyLogEntry.CurrentPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))@string.Format("{0}", (buyLogEntry.Volume24h).ToString())@buyLogEntry.CurrentPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))@Html.Raw(buyStrategyText)
+
- - - + + - - - + + + @@ -142,7 +150,6 @@ if (dcaLogEntry.SellStrategies.Count > 0) { isSellStrategyTrue = (dcaLogEntry.SellStrategies.FindAll(ss => !ss.IsTrue).Count == 0); } - string leverage = ""; double leverageValue = 1; string buyStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Model.Summary, dcaLogEntry.BuyStrategies, dcaLogEntry.BuyStrategy, isBuyStrategyTrue, isTrailingBuyActive); @@ -154,55 +161,38 @@ // Profit percentage var profitPercentage = dcaLogEntry.ProfitPercent; - - // if (dcaLogEntry.SellStrategies != null) - // { - // var gainStrategy = dcaLogEntry.SellStrategies.FirstOrDefault(x => x.Name.Contains(" GAIN", StringComparison.InvariantCultureIgnoreCase)); - // if (gainStrategy != null) - // { - // // Use the gain percentage value as it is accurate to what can be achieved with the order book! - // profitPercentage = gainStrategy.CurrentValue; - // } - // } // Render the row - - if (!sellStrategyText.Contains("PENDING-BUY")) + if (!sellStrategyText.Contains("PENDING-BUY")) { - - - - - - + @@ -234,7 +224,7 @@ double TargetGain = leverageValue * dcaLogEntry.TargetGainValue.Value; } else @@ -242,7 +232,7 @@ } @@ -255,24 +245,22 @@ } - - { - // Aggregate totals - double bagGain = (profitPercentage / 100) * dcaLogEntry.TotalCost; - Model.TotalBagCost = Model.TotalBagCost + dcaLogEntry.TotalCost; - Model.TotalBagGain = Model.TotalBagGain + bagGain; - } - + + { + // Aggregate totals + double bagGain = (profitPercentage / 100) * dcaLogEntry.TotalCost; + Model.TotalBagCost = Model.TotalBagCost + dcaLogEntry.TotalCost; + Model.TotalBagGain = Model.TotalBagGain + bagGain; + } } - - } - - - - - - - + } + + + + + + +
Market24HCostMarket Cost DCASellProfitDCA Sell Profit
@if (mps == null || mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0) { - @dcaLogEntry.Market + @dcaLogEntry.Market } else { - @dcaLogEntry.Market   - + @dcaLogEntry.Market   } -
- @bagAgeText +
@Html.Raw((dcaLogEntry.PercChange * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))) % +
@Html.Raw((dcaLogEntry.PercChange * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))% @Html.Raw(dcaLogEntry.TotalCost.ToString("#,#0.000000", new System.Globalization.CultureInfo("en-US")))@Html.Raw(dcaLogEntry.TotalCost.ToString("#,#0.000000", new System.Globalization.CultureInfo("en-US")))
@bagAgeText
@if (dcaEnabled) { - @if (dcaLogEntry.BoughtTimes > 0) + if (dcaLogEntry.BoughtTimes > 0) { @dcaLogEntry.BoughtTimes; } } else { - + } @TargetGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%
-
@profitPercentage.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%
+
@profitPercentage.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %
None

-
@profitPercentage.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%
+
@profitPercentage.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %
Totals:@Html.Raw(Model.TotalBagCost.ToString("#,#0.000000", new System.Globalization.CultureInfo("en-US")))@Html.Raw((((Model.TotalBagGain) / Model.TotalBagCost) * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))%Totals:@Html.Raw(Model.TotalBagCost.ToString("#,#0.000000", new System.Globalization.CultureInfo("en-US")))@Html.Raw((((Model.TotalBagGain) / Model.TotalBagCost) * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))) %
diff --git a/Monitor/Pages/_get/DashboardTop.cshtml.cs b/Monitor/Pages/_get/DashboardTop.cshtml.cs index 0efd6e9..8bc48cf 100644 --- a/Monitor/Pages/_get/DashboardTop.cshtml.cs +++ b/Monitor/Pages/_get/DashboardTop.cshtml.cs @@ -1,15 +1,16 @@ using System; using Core.Main; using Core.Main.DataObjects; +using Core.Main.DataObjects.PTMagicData; namespace Monitor.Pages { public class DashboardTopModel : _Internal.BasePageModelSecureAJAX { public ProfitTrailerData PTData = null; + public MiscData MiscData = null; public DateTimeOffset DateTimeNow = Constants.confMinDate; public void OnGet() { // Initialize Config base.Init(); - BindData(); } public double TotalBagCost = 0; @@ -17,9 +18,9 @@ namespace Monitor.Pages { public double TotalBagGain = 0; private void BindData() { PTData = this.PtDataObject; - + MiscData = this.PTData.Misc; // Convert local offset time to UTC - TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", "")); + TimeSpan offsetTimeSpan = TimeSpan.Parse(MiscData.TimeZoneOffset.Replace("+", "")); DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan); } } diff --git a/Monitor/Pages/_get/SettingsGlobalSettings.cshtml b/Monitor/Pages/_get/SettingsGlobalSettings.cshtml index f7f6cfc..cc98576 100644 --- a/Monitor/Pages/_get/SettingsGlobalSettings.cshtml +++ b/Monitor/Pages/_get/SettingsGlobalSettings.cshtml @@ -36,12 +36,7 @@ @if (!Model.GlobalSetting.SettingName.StartsWith("Default", StringComparison.InvariantCultureIgnoreCase)) {
-
- -
+

diff --git a/Monitor/Pages/_get/SettingsMarketTrends.cshtml b/Monitor/Pages/_get/SettingsMarketTrends.cshtml index a4611bf..f32ef9f 100644 --- a/Monitor/Pages/_get/SettingsMarketTrends.cshtml +++ b/Monitor/Pages/_get/SettingsMarketTrends.cshtml @@ -78,7 +78,7 @@
- +
Leave empty to exclude none diff --git a/Monitor/Pages/_get/TickerWidgets.cshtml b/Monitor/Pages/_get/TickerWidgets.cshtml index 89d2529..c130305 100644 --- a/Monitor/Pages/_get/TickerWidgets.cshtml +++ b/Monitor/Pages/_get/TickerWidgets.cshtml @@ -31,6 +31,9 @@ ptMagicHealthTooltip = "PT Magic seems to have problems, check the logs! Time elapsed since last run: " + Math.Round(elapsedSecondsSinceRuntime / 60, 1) + " mins."; healthIconColor = "text-danger"; } + if (Model.IsAnalyzerRunning()) { + ptMagicHealthIcon = "fa-cog fa-spin"; + } }
diff --git a/Monitor/Pages/_get/TickerWidgets.cshtml.cs b/Monitor/Pages/_get/TickerWidgets.cshtml.cs index 44268af..b11d08a 100644 --- a/Monitor/Pages/_get/TickerWidgets.cshtml.cs +++ b/Monitor/Pages/_get/TickerWidgets.cshtml.cs @@ -1,16 +1,23 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.Http; -using Core.Main; using Core.Main.DataObjects; using Core.Main.DataObjects.PTMagicData; -using Core.MarketAnalyzer; +using System.IO; +using Microsoft.AspNetCore.Hosting; +using System.Threading; namespace Monitor.Pages { public class TickerWidgetsModel : _Internal.BasePageModelSecureAJAX { public ProfitTrailerData PTData = null; public List MarketsWithSingleSettings = new List(); + private readonly IWebHostEnvironment _hostingEnvironment; + private Mutex mutex = new Mutex(false, "analyzerStateMutex"); + + public TickerWidgetsModel(IWebHostEnvironment hostingEnvironment) // Add this constructor + { + _hostingEnvironment = hostingEnvironment; + } public void OnGet() { // Initialize Config @@ -18,6 +25,33 @@ namespace Monitor.Pages { BindData(); } + public bool IsAnalyzerRunning() + { + bool ownsMutex = false; + try + { + // Try to acquire the mutex. + ownsMutex = mutex.WaitOne(0); + + string webRootParent = Directory.GetParent(_hostingEnvironment.WebRootPath).FullName; + string ptMagicRoot = Directory.GetParent(webRootParent).FullName; + string analyzerStatePath = Path.Combine(ptMagicRoot, "_data", "AnalyzerState"); + if (System.IO.File.Exists(analyzerStatePath)) + { + string state = System.IO.File.ReadAllText(analyzerStatePath); + return state == "1"; + } + return false; + } + finally + { + // Only release the mutex if this thread owns it. + if (ownsMutex) + { + mutex.ReleaseMutex(); + } + } + } private void BindData() { PTData = this.PtDataObject; diff --git a/PTMagic/PTMagic.csproj b/PTMagic/PTMagic.csproj index 68d6734..8e9c961 100644 --- a/PTMagic/PTMagic.csproj +++ b/PTMagic/PTMagic.csproj @@ -22,4 +22,8 @@ + + + + diff --git a/PTMagic/Program.cs b/PTMagic/Program.cs index ca7ee6a..919933d 100644 --- a/PTMagic/Program.cs +++ b/PTMagic/Program.cs @@ -6,7 +6,7 @@ using Core.Helper; using Microsoft.Extensions.DependencyInjection; -[assembly: AssemblyVersion("2.7.1")] +[assembly: AssemblyVersion("2.8.1")] [assembly: AssemblyProduct("PT Magic")] namespace PTMagic diff --git a/PTMagic/_defaults/_default_settings_PT_2.x/settings.analyzer.json b/PTMagic/_defaults/_default_settings_PT_2.x/settings.analyzer.json index dcbd5bf..764781b 100644 --- a/PTMagic/_defaults/_default_settings_PT_2.x/settings.analyzer.json +++ b/PTMagic/_defaults/_default_settings_PT_2.x/settings.analyzer.json @@ -1,7 +1,6 @@ // // The settings below offer a basic example of some of the options available when using PTMagic. -// You should take your time and adjust these settings according to your own personal preferences, and settings. -// +// You should take your time and adjust these settings according to your own personal preferences. // Always test your PTMagic settings by running a Profit Trailer bot in TESTMODE, to make sure // it is performing as you expect. // @@ -11,19 +10,22 @@ { "AnalyzerSettings": { "MarketAnalyzer": { - "StoreDataMaxHours": 48, // Number of hours to store market data - "IntervalMinutes": 2, // Interval in minutes for PTMagic to check market trends and triggers - "ExcludeMainCurrency": true, // Excludes the main currency (for example BTC) from market trend analysis + "StoreDataMaxHours": 48, // Number of hours to store market data + "IntervalMinutes": 2, // Interval in minutes for PTMagic to check market trends and triggers + "ExcludeMainCurrency": true, // Excludes the main currency (for example BTC, if you are trading against BTC) from market trend analysis "MarketTrends": [ { - "Name": "1h", // UNIQUE market trend name (to be referenced by your triggers below) - "Platform": "Exchange", // Platform to grab prices from (Allowed values are: CoinMarketCap, Exchange) - "MaxMarkets": 50, // Number of markets/pairs to analyze sorted by 24h volume - "TrendMinutes": 60, // Number of minutes to build a trend (1440 = 24h, 720 = 12h, 60 = 1h) - "TrendCurrency": "Market", // Trend Currency to build the trend against. If set to "Fiat", the trend will take the USD value of your main currency into account to build the trend. "Market" will build a trend against your base currency, such as BTC or USDT. - "TrendThreshold": 15, // Any coin that is above 15% or below -15% for this timeframe will not be used when calculating the market average. - "DisplayGraph": false, // Use this trend in the graph on the PTM Monitor dashboard and market analyzer? - "DisplayOnMarketAnalyzerList": false // Disply this trend on the PTM Monitor market analyzer? + "Name": "1h", // UNIQUE market trend name (to be referenced by your triggers below) + "Platform": "Exchange", // Platform to grab prices from (Allowed values are: CoinMarketCap, Exchange) + "MaxMarkets": 50, // Number of markets/pairs to analyze sorted by 24h volume + "TrendMinutes": 60, // Number of minutes to build a trend (1440 = 24h, 720 = 12h, 60 = 1h) + "TrendCurrency": "Market", // Trend Currency to build the trend against. If set to "Fiat", the trend will + // take the USD value of your main currency into account to build the trend. + // "Market" will build a trend against your base currency, such as BTC or USDT. + "TrendThreshold": 15, // Any coin that is above 15% or below -15% for this timeframe will be considered an outlier, + // and not used when calculating the market average. + "DisplayGraph": false, // Use this trend in the graph on the PTM Monitor dashboard and market analyzer + "DisplayOnMarketAnalyzerList": false // Disply this trend for all coins on the PTM Monitor market analyzer }, { "Name": "6h", @@ -32,8 +34,8 @@ "TrendMinutes": 360, "TrendCurrency": "Market", "TrendThreshold": 30, - "DisplayGraph": true, - "DisplayOnMarketAnalyzerList": true + "DisplayGraph": true, + "DisplayOnMarketAnalyzerList": true }, { "Name": "12h", @@ -42,8 +44,8 @@ "TrendMinutes": 720, "TrendCurrency": "Market", "TrendThreshold": 50, - "DisplayGraph": true, - "DisplayOnMarketAnalyzerList": true + "DisplayGraph": true, + "DisplayOnMarketAnalyzerList": true }, { "Name": "24h", @@ -52,73 +54,89 @@ "TrendMinutes": 1440, "TrendCurrency": "Market", "TrendThreshold": 75, - "DisplayGraph": true, - "DisplayOnMarketAnalyzerList": true + "DisplayGraph": true, + "DisplayOnMarketAnalyzerList": true } ] }, // ================================ GLOBAL SETTINGS ================================ // "GlobalSettings": [ // Global settings for Profit Trailer properties - // - // =================================================================================== - // ----------------------------- + // + // =================================================================================== + + // Each setting here is checked in order. If it is true, the analysis stops. If it is false, it moves on to check the next setting. + // This way, you don't need to define ranges for each setting, just the minimums or maximums. + + // ----------------------------- { - "SettingName": "EndOfTheWorld", // ANY UNIQUE name of your setting - "TriggerConnection": "AND", // Define if triggers will be connected by AND or OR - "Triggers": [ // Your triggers for this setting. You can use any of your defined trends from above + "SettingName": "EndOfTheWorld", // ANY UNIQUE name of your setting + "TriggerConnection": "AND", // Define if triggers will be connected by AND or OR. + //If you give each trigger a Tag, then you can use more robust boolean logic, such as: (A && B) || (B && C) + "Triggers": [ // Your triggers for this setting. You can use any of your defined trends from above { - "MarketTrendName": "1h", // Reference to the market trend specified above - "MaxChange": 0 // The maximum value for this trigger to be true. (Any value below "0" will trigger this) + "Tag": "A", // OPTIONAL: Give your triggers Tags, so you can use more robust boolean logic, such as: (A && B) || (C && D) + "MarketTrendName": "1h", // Reference to the market trend specified above + "MaxChange": 0 // The maximum value for this trigger. (Any value below "0" will trigger this) }, { + "Tag": "B", "MarketTrendName": "12h", "MaxChange": -2 }, { + "Tag": "C", "MarketTrendName": "24h", "MaxChange": -5 } ], - "PairsProperties": { // Changes you wish to make to your PAIRS.properties settings - // Any valid setting from https://wiki.profittrailer.com/en/config can be used here. - // You can use a specific value, or apply a discrete OFFSET or OFFSETPERCENT to the value in your default PAIRS setting. + "PairsProperties": { // Properties for PAIRS.PROPERTIES + // Any valid setting from https://wiki.profittrailer.com/en/config can be used here. + // You can use a specific value, or apply a discrete OFFSET or OFFSETPERCENT to the value in your default PAIRS setting. "DEFAULT_sell_only_mode_enabled": true, "DEFAULT_trailing_profit_OFFSETPERCENT": -50 }, - "DCAProperties": { // Changes you wish to make to your DCA.properties settings + "DCAProperties": { // Properties for DCA.PROPERTIES "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -75 - }, - "IndicatorsProperties": { // Changes you wish to make to your INDICATORS.properties settings } }, - // ----------------------------- + // ----------------------------- { "SettingName": "TankingDown", - "TriggerConnection": "AND", + "TriggerConnection": "AND", // You can use complex boolean logic for some settings, and not others "Triggers": [ { "MarketTrendName": "1h", + "MaxChange": 0, + "MinChange": -5 // You can use Maxchange and Minchange together to create a range. + }, + { + "MarketTrendName": "12h", "MaxChange": 0 + }, + { + "MarketTrendName": "24h", // Any value between -5 and -3 will make this trigger true. + "MaxChange": -3, + "MinChange": -5 // The minimum value for this trigger to be true. (Any value above "-5" will trigger this) } ], "PairsProperties": { - "max_trading_pairs_OFFSET": -2, - "DEFAULT_min_buy_volume_OFFSETPERCENT": 100, - //"DEFAULT_initial_cost_OFFSETPERCENT": -50, - //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -50, - "DEFAULT_trailing_buy_OFFSETPERCENT": 25, - "DEFAULT_trailing_profit_OFFSETPERCENT": -25 + "max_trading_pairs_OFFSET": -2, + "DEFAULT_min_buy_volume_OFFSETPERCENT": 100, + //"DEFAULT_initial_cost_OFFSETPERCENT": -50, + //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -50, + "DEFAULT_trailing_buy_OFFSETPERCENT": 25, + "DEFAULT_trailing_profit_OFFSETPERCENT": -25 }, "DCAProperties": { //"DEFAULT_DCA_rebuy_timeout_OFFSETPERCENT": 100, "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 25, - "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -50 + "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -50 }, - "IndicatorsProperties": { + "IndicatorsProperties": { } }, - // ----------------------------- + // ----------------------------- { "SettingName": "BearSighted", "TriggerConnection": "AND", @@ -139,16 +157,16 @@ ], "PairsProperties": { "max_trading_pairs_OFFSET": -1, - //"DEFAULT_initial_cost_OFFSETPERCENT": -25, - //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -25, - "DEFAULT_trailing_buy_OFFSETPERCENT": 10, - "DEFAULT_trailing_profit_OFFSETPERCENT": -10 + //"DEFAULT_initial_cost_OFFSETPERCENT": -25, + //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -25, + "DEFAULT_trailing_buy_OFFSETPERCENT": 10, + "DEFAULT_trailing_profit_OFFSETPERCENT": -10 }, "DCAProperties": { "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 10, - "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -10, + "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -10, }, - "IndicatorsProperties": { + "IndicatorsProperties": { } }, // ----------------------------- @@ -172,19 +190,19 @@ ], "PairsProperties": { "max_trading_pairs_OFFSET": 1, - //"DEFAULT_initial_cost_OFFSETPERCENT": 10, - //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 10, - "DEFAULT_trailing_buy_OFFSETPERCENT": -10, - "DEFAULT_A_sell_value_OFFSETPERCENT": 10 + //"DEFAULT_initial_cost_OFFSETPERCENT": 10, + //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 10, + "DEFAULT_trailing_buy_OFFSETPERCENT": -10, + "DEFAULT_A_sell_value_OFFSETPERCENT": 10 }, "DCAProperties": { - "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -10, - "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 10, + "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -10, + "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 10, }, - "IndicatorsProperties": { + "IndicatorsProperties": { } }, - // ----------------------------- + // ----------------------------- { "SettingName": "ToTheMoon", "TriggerConnection": "AND", @@ -204,8 +222,8 @@ ], "PairsProperties": { "max_trading_pairs_OFFSET": 2, - //"DEFAULT_initial_cost_OFFSETPERCENT": 20, - //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 20, + //"DEFAULT_initial_cost_OFFSETPERCENT": 20, + //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 20, "DEFAULT_trailing_buy_OFFSETPERCENT": -10, "DEFAULT_A_sell_value_OFFSETPERCENT": 20 }, @@ -213,7 +231,7 @@ "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -20, "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 20, }, - "IndicatorsProperties": { + "IndicatorsProperties": { } }, // ----------------------------- @@ -230,49 +248,67 @@ } } ], - // // ================================ COIN-SPECIFIC SETTINGS ================================ // - "SingleMarketSettings": [ // Single market/pair settings for Profit Trailer properties - // Any setting from https://wiki.profittrailer.com/en/config marked as COIN (coin-specific) can be used here. - // Only coins that meet the triggered conditions will have the settings applied. - // A variety of SMS can be employed to check for long-term down trends, sideways trends, over-extended uptrends, etc. - // If more than one SMS is true, the settings of the last applied SMS over-rides any prior SMS + "SingleMarketSettings": [ // Single market/pair settings for Profit Trailer properties + // Only coins that meet the triggered conditions will have the settings applied. + // If StopProcessWhenTriggered is false, as the analyzer goes down the list multiple settings + // can be applied to a single coin. This can allow for more complex logic and settings. + // However, if two settings apply the same property, the property from the last setting + // on the list will be the one that is used. { - "SettingName": "PumpNDumpProtection", - "TriggerConnection": "OR", - //"StopProcessWhenTriggered": true, // No SMS after this will be analyzed or applied if this SMS is true - //"AllowedGlobalSettings": "Default", // You can specify that this setting will only apply when a specific Global setting is active - //"IgnoredGlobalSettings": "Default", // You can specify that this setting will NOT apply when a specific Global setting is active + "SettingName": "BlacklistCoins", + "StopProcessWhenTriggered": true, + "TriggerConnection": "OR", // Just like Global Settings, you can use complex boolean logic for some settings, and not others "Triggers": [ { + "AgeDaysLowerThan": 21 + } + ], + "PairsProperties": { + "DEFAULT_trading_enabled": false, // Any setting from PT that begins with DEFAULT_ can be used here. + "DEFAULT_sell_only_mode_enabled": true, + "DEFAULT_DCA_enabled": false + } + }, + // ----------------------------- + { + "SettingName": "PumpNDumpProtection", + "TriggerConnection": "A || B || C", + "Triggers": [ + { + "Tag": "A", "MarketTrendName": "1h", - "MarketTrendRelation": "Relative", // Relative = The single market trend is compared to the overall trend of the entire market - // Absolute = The Single market trend is considered on its own + "MarketTrendRelation": "Relative", // The relation of the single market trend. Relative = The trend of the coin market + // is compared to the average trend of all other coins in the market market. + // Absolute = Single market trend is considered on it's own, without reference to the market. "MinChange": 8 }, { + "Tag": "B", "MarketTrendName": "12h", "MarketTrendRelation": "Relative", "MinChange": 10 }, { + "Tag": "C", "MarketTrendName": "24h", "MarketTrendRelation": "Relative", "MinChange": 12 } ], - "OffTriggers": [ - { - "HoursSinceTriggered": 3 // Any coin that triggers this setting, will remain under this setting - // for 3 hours, since the last time it triggered. + "OffTriggers": [ + { + "HoursSinceTriggered": 3 // Any coin that triggers this setting, will remain under this setting + // for 3 hours, since the last time it triggered. } ], "PairsProperties": { - "DEFAULT_sell_only_mode_enabled": "true", - "DEFAULT_DCA_enabled": "false" + "DEFAULT_sell_only_mode_enabled": true, + "DEFAULT_DCA_enabled": false } }, + // ----------------------------- { "SettingName": "FreefallBlock", "TriggerConnection": "OR", @@ -283,14 +319,14 @@ "MaxChange": -5 } ], - "OffTriggers": [ - { + "OffTriggers": [ + { "HoursSinceTriggered": 1 } ], - "PairsProperties": { - "DEFAULT_sell_only_mode_enabled": "true", - "DEFAULT_DCA_enabled": "false" + "PairsProperties": { + "DEFAULT_sell_only_mode_enabled": true, + "DEFAULT_DCA_enabled": false } } ] diff --git a/PTMagic/_defaults/_default_settings_PT_2.x/settings.general.json b/PTMagic/_defaults/_default_settings_PT_2.x/settings.general.json index 650c85b..3e13de7 100644 --- a/PTMagic/_defaults/_default_settings_PT_2.x/settings.general.json +++ b/PTMagic/_defaults/_default_settings_PT_2.x/settings.general.json @@ -2,33 +2,44 @@ "GeneralSettings": { "Application": { "IsEnabled": true, // Enables the PTMagic bot (needs restart to take effect) - "TestMode": false, // If TestMode is active, no properties files will be changed + "TestMode": true, // If TestMode is active, no properties files will be changed "ProfitTrailerLicense": "ptlicense1asdf234fljlasdf014325ehm", // Your Profit Trailer license key (needed to change your settings) "ProfitTrailerLicenseXtra": "", // Licenses for additional bots for PTM to update (optional - comma separated list) "ProfitTrailerServerAPIToken": "", //Your Profit Trailer Server API Token "ProfitTrailerMonitorURL": "http://localhost:8081/", // The URL to your profit trailer monitor (needed to change your settings) "ProfitTrailerMonitorURLXtra": "", // URLs for additional bots you want PTM to update (optional - comma separated list) "ProfitTrailerDefaultSettingName": "default", // Your Profit Trailer default setting name (needed to change your settings) - "Exchange": "Bittrex", // The exchange your are running Profit Trailer on - "TimezoneOffset": "+0:00", // Your timezone offset from UTC time - "FloodProtectionMinutes": 0, // If a price trend is just zig-zagging around its trigger, you may want to protect your settings from getting switched back and forth every minute - "InstanceName": "PT Magic", // The name of the instance of this bot. This will be used in your monitor and your Telegram messages. In case you are running more than one bot, you may set different names to separate them - //"FreeCurrencyConverterAPIKey": "" // If "MainFiatCurrency" above is anything other than USD, you must obtain an API key from https://free.currencyconverterapi.com/free-api-key + "Exchange": "BinanceFutures", // The exchange your are running Profit Trailer on + "FloodProtectionMinutes": 0, // If a price trend is just zig-zagging around its trigger, you may want to protect your settings from + // getting switched back and forth every minute + "InstanceName": "MyBTCbot", // The name of the instance of this bot. This will be used in your monitor and your Telegram messages. + //In case you are running more than one bot, you may set different names to separate them + "CoinMarketCapAPIKey": "", //CoinMarketCap Api }, "Monitor": { "IsPasswordProtected": true, // Defines if your monitor will be asking to setup a password on its first start - "OpenBrowserOnStart": false, // If active, a browser window will open as soon as you start the monitor - "Port": 8080, // The port you want to run your monitor on - "RootUrl": "/", // The root Url of your monitor - "AnalyzerChart": "", // By default the chart on the Market Analyzer page will use your default currency against USD. You can change that here. (eg., BTCEUR) + "OpenBrowserOnStart": true, // If active, a browser window will open as soon as you start the monitor + "Port": 8080, // The port you want to run your PTMagic monitor on, to connect via browser. The url will be your IP:Port or localhost:Port + "AnalyzerChart": "", // By default the chart on the Market Analyzer page will use your market currency against USD. + //You can change that here. (eg., BTCEUR) + "LiveTCVTimeframeMinutes": 10, // The timeframe for the live TCV chart on the dashboard "GraphIntervalMinutes": 60, // The interval for the monitor market trend graph to draw points in minutes "GraphMaxTimeframeHours": 24, // This will enable you to define the timeframe that your graph for market trends covers in hours "ProfitsMaxTimeframeDays": 30, // This will enable you to define the timeframe for your dashboard profits graph in days - "RefreshSeconds": 30, // The refresh interval of your monitor main page - "LinkPlatform": "TradingView", // The platform to which the pair name will link if you click on it + "DashboardChartsRefreshSeconds": 30, // The refresh interval of your dashboard charts in seconds + "BagAnalyzerRefreshSeconds": 60, + "BuyAnalyzerRefreshSeconds": 60, + "MaxDashboardBuyEntries": 5, // The number of coins in your Possible Buy List on the dashboard. Set to 0 to hide the list completely + "MaxDashboardBagEntries": 9999, // The number of coins in your Positions List on the dashboard. "MaxTopMarkets": 20, // The amount of top markets being shown in your Sales Analyzer "MaxDailySummaries": 10, // The amount of "Last Days" being shown in your Sales Analyzer + "MaxDCAPairs": 25, // For DCA calculations in the DCA Analyzer. + "DefaultDCAMode": "Advanced", // The default DCA mode to use in the DCA Analyzer. Options are "Simple" or "Advanced" + "MaxSettingsLogEntries": 500, // The number of entries in the Global Settings Log on the Status & Summary page "MaxMonthlySummaries": 10, // The amount of "Last Months" being shown in your Sales Analyzer + "LinkPlatform": "TradingView", // The platform to which the pair name will link if you click on it + "TVCustomLayout": "EbSR85R8", // A TradingView layout to use when clicking on a pair name while using TradingView as your platform + // When saving a custom layout in TV, you will get a URL like this: https://www.tradingview.com/chart/EbSR85R8/ "TvStudyA": "BB@tv-basicstudies", // See available STUDIES at https://www.tradingview.com/wiki/Widget:TradingView_Widget "TvStudyB": "", "TvStudyC": "", diff --git a/_Development/DevSettings/settings.analyzer.json b/_Development/DevSettings/settings.analyzer.json index dcbd5bf..753d4fb 100644 --- a/_Development/DevSettings/settings.analyzer.json +++ b/_Development/DevSettings/settings.analyzer.json @@ -1,7 +1,6 @@ // // The settings below offer a basic example of some of the options available when using PTMagic. -// You should take your time and adjust these settings according to your own personal preferences, and settings. -// +// You should take your time and adjust these settings according to your own personal preferences. // Always test your PTMagic settings by running a Profit Trailer bot in TESTMODE, to make sure // it is performing as you expect. // @@ -11,19 +10,22 @@ { "AnalyzerSettings": { "MarketAnalyzer": { - "StoreDataMaxHours": 48, // Number of hours to store market data - "IntervalMinutes": 2, // Interval in minutes for PTMagic to check market trends and triggers - "ExcludeMainCurrency": true, // Excludes the main currency (for example BTC) from market trend analysis + "StoreDataMaxHours": 48, // Number of hours to store market data + "IntervalMinutes": 2, // Interval in minutes for PTMagic to check market trends and triggers + "ExcludeMainCurrency": true, // Excludes the main currency (for example BTC, if you are trading against BTC) from market trend analysis "MarketTrends": [ { - "Name": "1h", // UNIQUE market trend name (to be referenced by your triggers below) - "Platform": "Exchange", // Platform to grab prices from (Allowed values are: CoinMarketCap, Exchange) - "MaxMarkets": 50, // Number of markets/pairs to analyze sorted by 24h volume - "TrendMinutes": 60, // Number of minutes to build a trend (1440 = 24h, 720 = 12h, 60 = 1h) - "TrendCurrency": "Market", // Trend Currency to build the trend against. If set to "Fiat", the trend will take the USD value of your main currency into account to build the trend. "Market" will build a trend against your base currency, such as BTC or USDT. - "TrendThreshold": 15, // Any coin that is above 15% or below -15% for this timeframe will not be used when calculating the market average. - "DisplayGraph": false, // Use this trend in the graph on the PTM Monitor dashboard and market analyzer? - "DisplayOnMarketAnalyzerList": false // Disply this trend on the PTM Monitor market analyzer? + "Name": "1h", // UNIQUE market trend name (to be referenced by your triggers below) + "Platform": "Exchange", // Platform to grab prices from (Allowed values are: CoinMarketCap, Exchange) + "MaxMarkets": 50, // Number of markets/pairs to analyze sorted by 24h volume + "TrendMinutes": 60, // Number of minutes to build a trend (1440 = 24h, 720 = 12h, 60 = 1h) + "TrendCurrency": "Market", // Trend Currency to build the trend against. If set to "Fiat", the trend will + // take the USD value of your main currency into account to build the trend. + // "Market" will build a trend against your base currency, such as BTC or USDT. + "TrendThreshold": 15, // Any coin that is above 15% or below -15% for this timeframe will be considered an outlier, + // and not used when calculating the market average. + "DisplayGraph": false, // Use this trend in the graph on the PTM Monitor dashboard and market analyzer + "DisplayOnMarketAnalyzerList": false // Disply this trend for all coins on the PTM Monitor market analyzer }, { "Name": "6h", @@ -32,8 +34,8 @@ "TrendMinutes": 360, "TrendCurrency": "Market", "TrendThreshold": 30, - "DisplayGraph": true, - "DisplayOnMarketAnalyzerList": true + "DisplayGraph": true, + "DisplayOnMarketAnalyzerList": true }, { "Name": "12h", @@ -42,8 +44,8 @@ "TrendMinutes": 720, "TrendCurrency": "Market", "TrendThreshold": 50, - "DisplayGraph": true, - "DisplayOnMarketAnalyzerList": true + "DisplayGraph": true, + "DisplayOnMarketAnalyzerList": true }, { "Name": "24h", @@ -52,73 +54,89 @@ "TrendMinutes": 1440, "TrendCurrency": "Market", "TrendThreshold": 75, - "DisplayGraph": true, - "DisplayOnMarketAnalyzerList": true + "DisplayGraph": true, + "DisplayOnMarketAnalyzerList": true } ] }, // ================================ GLOBAL SETTINGS ================================ // "GlobalSettings": [ // Global settings for Profit Trailer properties - // - // =================================================================================== - // ----------------------------- + // + // =================================================================================== + + // Each setting here is checked in order. If it is true, the analysis stops. If it is false, it moves on to check the next setting. + // This way, you don't need to define ranges for each setting, just the minimums or maximums. + + // ----------------------------- { - "SettingName": "EndOfTheWorld", // ANY UNIQUE name of your setting - "TriggerConnection": "AND", // Define if triggers will be connected by AND or OR - "Triggers": [ // Your triggers for this setting. You can use any of your defined trends from above + "SettingName": "EndOfTheWorld", // ANY UNIQUE name of your setting + "TriggerConnection": "AND", // Define if triggers will be connected by AND or OR. + //If you give each trigger a Tag, then you can use more robust boolean logic, such as: (A && B) || (B && C) + "Triggers": [ // Your triggers for this setting. You can use any of your defined trends from above { - "MarketTrendName": "1h", // Reference to the market trend specified above - "MaxChange": 0 // The maximum value for this trigger to be true. (Any value below "0" will trigger this) + "Tag": "A", // OPTIONAL: Give your triggers Tags, so you can use more robust boolean logic, such as: (A && B) || (C && D) + "MarketTrendName": "1h", // Reference to the market trend specified above + "MaxChange": 0 // The maximum value for this trigger. (Any value below "0" will trigger this) }, { + "Tag": "B", "MarketTrendName": "12h", "MaxChange": -2 }, { + "Tag": "C", "MarketTrendName": "24h", "MaxChange": -5 } ], - "PairsProperties": { // Changes you wish to make to your PAIRS.properties settings - // Any valid setting from https://wiki.profittrailer.com/en/config can be used here. - // You can use a specific value, or apply a discrete OFFSET or OFFSETPERCENT to the value in your default PAIRS setting. + "PairsProperties": { // Properties for PAIRS.PROPERTIES + // Any valid setting from https://wiki.profittrailer.com/en/config can be used here. + // You can use a specific value, or apply a discrete OFFSET or OFFSETPERCENT to the value in your default PAIRS setting. "DEFAULT_sell_only_mode_enabled": true, "DEFAULT_trailing_profit_OFFSETPERCENT": -50 }, - "DCAProperties": { // Changes you wish to make to your DCA.properties settings + "DCAProperties": { // Properties for DCA.PROPERTIES "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -75 - }, - "IndicatorsProperties": { // Changes you wish to make to your INDICATORS.properties settings } }, - // ----------------------------- + // ----------------------------- { "SettingName": "TankingDown", - "TriggerConnection": "AND", + "TriggerConnection": "AND", // You can use complex boolean logic for some settings, and not others "Triggers": [ { "MarketTrendName": "1h", + "MaxChange": 0, + "MinChange": -5 // You can use Maxchange and Minchange together to create a range. + }, + { + "MarketTrendName": "12h", "MaxChange": 0 + }, + { + "MarketTrendName": "24h", // Any value between -5 and -3 will make this trigger true. + "MaxChange": -3, + "MinChange": -5 // The minimum value for this trigger to be true. (Any value above "-5" will trigger this) } ], "PairsProperties": { - "max_trading_pairs_OFFSET": -2, - "DEFAULT_min_buy_volume_OFFSETPERCENT": 100, - //"DEFAULT_initial_cost_OFFSETPERCENT": -50, - //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -50, - "DEFAULT_trailing_buy_OFFSETPERCENT": 25, - "DEFAULT_trailing_profit_OFFSETPERCENT": -25 + "max_trading_pairs_OFFSET": -2, + "DEFAULT_min_buy_volume_OFFSETPERCENT": 100, + //"DEFAULT_initial_cost_OFFSETPERCENT": -50, + //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -50, + "DEFAULT_trailing_buy_OFFSETPERCENT": 25, + "DEFAULT_trailing_profit_OFFSETPERCENT": -25 }, "DCAProperties": { //"DEFAULT_DCA_rebuy_timeout_OFFSETPERCENT": 100, "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 25, - "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -50 + "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -50 }, - "IndicatorsProperties": { + "IndicatorsProperties": { } }, - // ----------------------------- + // ----------------------------- { "SettingName": "BearSighted", "TriggerConnection": "AND", @@ -139,16 +157,16 @@ ], "PairsProperties": { "max_trading_pairs_OFFSET": -1, - //"DEFAULT_initial_cost_OFFSETPERCENT": -25, - //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -25, - "DEFAULT_trailing_buy_OFFSETPERCENT": 10, - "DEFAULT_trailing_profit_OFFSETPERCENT": -10 + //"DEFAULT_initial_cost_OFFSETPERCENT": -25, + //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -25, + "DEFAULT_trailing_buy_OFFSETPERCENT": 10, + "DEFAULT_trailing_profit_OFFSETPERCENT": -10 }, "DCAProperties": { "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 10, - "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -10, + "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -10, }, - "IndicatorsProperties": { + "IndicatorsProperties": { } }, // ----------------------------- @@ -172,19 +190,19 @@ ], "PairsProperties": { "max_trading_pairs_OFFSET": 1, - //"DEFAULT_initial_cost_OFFSETPERCENT": 10, - //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 10, - "DEFAULT_trailing_buy_OFFSETPERCENT": -10, - "DEFAULT_A_sell_value_OFFSETPERCENT": 10 + //"DEFAULT_initial_cost_OFFSETPERCENT": 10, + //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 10, + "DEFAULT_trailing_buy_OFFSETPERCENT": -10, + "DEFAULT_A_sell_value_OFFSETPERCENT": 10 }, "DCAProperties": { - "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -10, - "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 10, + "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -10, + "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 10, }, - "IndicatorsProperties": { + "IndicatorsProperties": { } }, - // ----------------------------- + // ----------------------------- { "SettingName": "ToTheMoon", "TriggerConnection": "AND", @@ -204,8 +222,8 @@ ], "PairsProperties": { "max_trading_pairs_OFFSET": 2, - //"DEFAULT_initial_cost_OFFSETPERCENT": 20, - //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 20, + //"DEFAULT_initial_cost_OFFSETPERCENT": 20, + //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 20, "DEFAULT_trailing_buy_OFFSETPERCENT": -10, "DEFAULT_A_sell_value_OFFSETPERCENT": 20 }, @@ -213,7 +231,7 @@ "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -20, "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 20, }, - "IndicatorsProperties": { + "IndicatorsProperties": { } }, // ----------------------------- @@ -230,49 +248,67 @@ } } ], - // // ================================ COIN-SPECIFIC SETTINGS ================================ // - "SingleMarketSettings": [ // Single market/pair settings for Profit Trailer properties - // Any setting from https://wiki.profittrailer.com/en/config marked as COIN (coin-specific) can be used here. - // Only coins that meet the triggered conditions will have the settings applied. - // A variety of SMS can be employed to check for long-term down trends, sideways trends, over-extended uptrends, etc. - // If more than one SMS is true, the settings of the last applied SMS over-rides any prior SMS + "SingleMarketSettings": [ // Single market/pair settings for Profit Trailer properties + // Only coins that meet the triggered conditions will have the settings applied. + // If StopProcessWhenTriggered is false, as the analyzer goes down the list multiple settings + // can be applied to a single coin. This can allow for more complex logic and settings. + // However, if two settings apply the same property, the property from the last setting + // on the list will be the one that is used. { - "SettingName": "PumpNDumpProtection", - "TriggerConnection": "OR", - //"StopProcessWhenTriggered": true, // No SMS after this will be analyzed or applied if this SMS is true - //"AllowedGlobalSettings": "Default", // You can specify that this setting will only apply when a specific Global setting is active - //"IgnoredGlobalSettings": "Default", // You can specify that this setting will NOT apply when a specific Global setting is active + "SettingName": "BlacklistCoins", + "StopProcessWhenTriggered": true, + "TriggerConnection": "OR", // Just like Global Settings, you can use complex boolean logic for some settings, and not others "Triggers": [ { + "AgeDaysLowerThan": 21 + } + ], + "PairsProperties": { + "DEFAULT_trading_enabled": false, // Any setting from PT that begins with DEFAULT_ can be used here. + "DEFAULT_sell_only_mode_enabled": true, + "DEFAULT_DCA_enabled": false + } + }, + // ----------------------------- + { + "SettingName": "PumpNDumpProtection", + "TriggerConnection": "A || B || C", + "Triggers": [ + { + "Tag": "A", "MarketTrendName": "1h", - "MarketTrendRelation": "Relative", // Relative = The single market trend is compared to the overall trend of the entire market - // Absolute = The Single market trend is considered on its own + "MarketTrendRelation": "Relative", // The relation of the single market trend. Relative = The trend of the coin market + // is compared to the average trend of all other coins in the market market. + // Absolute = Single market trend is considered on it's own, without reference to the market. "MinChange": 8 }, { + "Tag": "B", "MarketTrendName": "12h", "MarketTrendRelation": "Relative", "MinChange": 10 }, { + "Tag": "C", "MarketTrendName": "24h", "MarketTrendRelation": "Relative", "MinChange": 12 } ], - "OffTriggers": [ - { - "HoursSinceTriggered": 3 // Any coin that triggers this setting, will remain under this setting - // for 3 hours, since the last time it triggered. + "OffTriggers": [ + { + "HoursSinceTriggered": 3 // Any coin that triggers this setting, will remain under this setting + // for 3 hours, since the last time it triggered. } ], "PairsProperties": { - "DEFAULT_sell_only_mode_enabled": "true", - "DEFAULT_DCA_enabled": "false" + "DEFAULT_sell_only_mode_enabled": true, + "DEFAULT_DCA_enabled": false } }, + // ----------------------------- { "SettingName": "FreefallBlock", "TriggerConnection": "OR", @@ -283,14 +319,14 @@ "MaxChange": -5 } ], - "OffTriggers": [ - { + "OffTriggers": [ + { "HoursSinceTriggered": 1 } ], - "PairsProperties": { - "DEFAULT_sell_only_mode_enabled": "true", - "DEFAULT_DCA_enabled": "false" + "PairsProperties": { + "DEFAULT_sell_only_mode_enabled": true, + "DEFAULT_DCA_enabled": false } } ] diff --git a/_Development/DevSettings/settings.general.json b/_Development/DevSettings/settings.general.json index 9b8e822..3e13de7 100644 --- a/_Development/DevSettings/settings.general.json +++ b/_Development/DevSettings/settings.general.json @@ -2,33 +2,44 @@ "GeneralSettings": { "Application": { "IsEnabled": true, // Enables the PTMagic bot (needs restart to take effect) - "TestMode": false, // If TestMode is active, no properties files will be changed + "TestMode": true, // If TestMode is active, no properties files will be changed "ProfitTrailerLicense": "ptlicense1asdf234fljlasdf014325ehm", // Your Profit Trailer license key (needed to change your settings) "ProfitTrailerLicenseXtra": "", // Licenses for additional bots for PTM to update (optional - comma separated list) "ProfitTrailerServerAPIToken": "", //Your Profit Trailer Server API Token "ProfitTrailerMonitorURL": "http://localhost:8081/", // The URL to your profit trailer monitor (needed to change your settings) "ProfitTrailerMonitorURLXtra": "", // URLs for additional bots you want PTM to update (optional - comma separated list) "ProfitTrailerDefaultSettingName": "default", // Your Profit Trailer default setting name (needed to change your settings) - "Exchange": "Bittrex", // The exchange your are running Profit Trailer on - "TimezoneOffset": "+0:00", // Your timezone offset from UTC time - "FloodProtectionMinutes": 0, // If a price trend is just zig-zagging around its trigger, you may want to protect your settings from getting switched back and forth every minute - "InstanceName": "PT Magic", // The name of the instance of this bot. This will be used in your monitor and your Telegram messages. In case you are running more than one bot, you may set different names to separate them + "Exchange": "BinanceFutures", // The exchange your are running Profit Trailer on + "FloodProtectionMinutes": 0, // If a price trend is just zig-zagging around its trigger, you may want to protect your settings from + // getting switched back and forth every minute + "InstanceName": "MyBTCbot", // The name of the instance of this bot. This will be used in your monitor and your Telegram messages. + //In case you are running more than one bot, you may set different names to separate them "CoinMarketCapAPIKey": "", //CoinMarketCap Api }, "Monitor": { "IsPasswordProtected": true, // Defines if your monitor will be asking to setup a password on its first start - "OpenBrowserOnStart": false, // If active, a browser window will open as soon as you start the monitor - "Port": 8080, // The port you want to run your monitor on - "RootUrl": "/", // The root Url of your monitor - "AnalyzerChart": "", // By default the chart on the Market Analyzer page will use your default currency against USD. You can change that here. (eg., BTCEUR) + "OpenBrowserOnStart": true, // If active, a browser window will open as soon as you start the monitor + "Port": 8080, // The port you want to run your PTMagic monitor on, to connect via browser. The url will be your IP:Port or localhost:Port + "AnalyzerChart": "", // By default the chart on the Market Analyzer page will use your market currency against USD. + //You can change that here. (eg., BTCEUR) + "LiveTCVTimeframeMinutes": 10, // The timeframe for the live TCV chart on the dashboard "GraphIntervalMinutes": 60, // The interval for the monitor market trend graph to draw points in minutes "GraphMaxTimeframeHours": 24, // This will enable you to define the timeframe that your graph for market trends covers in hours "ProfitsMaxTimeframeDays": 30, // This will enable you to define the timeframe for your dashboard profits graph in days - "RefreshSeconds": 30, // The refresh interval of your monitor main page - "LinkPlatform": "TradingView", // The platform to which the pair name will link if you click on it + "DashboardChartsRefreshSeconds": 30, // The refresh interval of your dashboard charts in seconds + "BagAnalyzerRefreshSeconds": 60, + "BuyAnalyzerRefreshSeconds": 60, + "MaxDashboardBuyEntries": 5, // The number of coins in your Possible Buy List on the dashboard. Set to 0 to hide the list completely + "MaxDashboardBagEntries": 9999, // The number of coins in your Positions List on the dashboard. "MaxTopMarkets": 20, // The amount of top markets being shown in your Sales Analyzer "MaxDailySummaries": 10, // The amount of "Last Days" being shown in your Sales Analyzer + "MaxDCAPairs": 25, // For DCA calculations in the DCA Analyzer. + "DefaultDCAMode": "Advanced", // The default DCA mode to use in the DCA Analyzer. Options are "Simple" or "Advanced" + "MaxSettingsLogEntries": 500, // The number of entries in the Global Settings Log on the Status & Summary page "MaxMonthlySummaries": 10, // The amount of "Last Months" being shown in your Sales Analyzer + "LinkPlatform": "TradingView", // The platform to which the pair name will link if you click on it + "TVCustomLayout": "EbSR85R8", // A TradingView layout to use when clicking on a pair name while using TradingView as your platform + // When saving a custom layout in TV, you will get a URL like this: https://www.tradingview.com/chart/EbSR85R8/ "TvStudyA": "BB@tv-basicstudies", // See available STUDIES at https://www.tradingview.com/wiki/Widget:TradingView_Widget "TvStudyB": "", "TvStudyC": "",