Merge pull request #347 from PTMagicians/develop

2.8.4
This commit is contained in:
HojouFotytu 2024-04-10 19:55:02 +09:00 committed by GitHub
commit 1298ff0c8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 1288 additions and 992 deletions

9
.vscode/tasks.json vendored
View File

@ -10,7 +10,7 @@
"PTMagic" "PTMagic"
], ],
"group": { "group": {
"kind":"build", "kind": "build",
"isDefault": true "isDefault": true
}, },
"presentation": { "presentation": {
@ -104,6 +104,13 @@
"focus": true "focus": true
}, },
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
},
{
"type": "dotnet",
"task": "build",
"group": "build",
"problemMatcher": [],
"label": "dotnet: build"
} }
] ]
} }

View File

@ -14,6 +14,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0" />
<PackageReference Include="SharpZipLib" Version="*" /> <PackageReference Include="SharpZipLib" Version="*" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.8" />
<PackageReference Include="Telegram.Bot" Version="*" /> <PackageReference Include="Telegram.Bot" Version="*" />
</ItemGroup> </ItemGroup>

View File

@ -42,7 +42,7 @@ namespace Core.Main.DataObjects.PTMagicData
public int FloodProtectionMinutes { get; set; } = 15; public int FloodProtectionMinutes { get; set; } = 15;
public string Exchange { get; set; } public string Exchange { get; set; }
public string InstanceName { get; set; } = "PT Magic"; 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 CoinMarketCapAPIKey { get; set; }
//public string FreeCurrencyConverterAPIKey { get; set; } //public string FreeCurrencyConverterAPIKey { get; set; }
} }
@ -55,13 +55,13 @@ namespace Core.Main.DataObjects.PTMagicData
public bool OpenBrowserOnStart { get; set; } = false; public bool OpenBrowserOnStart { get; set; } = false;
public int Port { get; set; } = 5000; public int Port { get; set; } = 5000;
public string AnalyzerChart { get; set; } = ""; public string AnalyzerChart { get; set; } = "";
public int LiveTCVTimeframeMinutes { get; set; } = 60;
public int GraphIntervalMinutes { get; set; } = 60; public int GraphIntervalMinutes { get; set; } = 60;
public int GraphMaxTimeframeHours { get; set; } = 24; public int GraphMaxTimeframeHours { get; set; } = 24;
public int ProfitsMaxTimeframeDays { get; set; } = 60; 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 BagAnalyzerRefreshSeconds { get; set; } = 5;
public int BuyAnalyzerRefreshSeconds { get; set; } = 5; public int BuyAnalyzerRefreshSeconds { get; set; } = 5;
//public int MaxSalesRecords { get; set; } = 99999;
public int MaxTopMarkets { get; set; } = 20; public int MaxTopMarkets { get; set; } = 20;
public int MaxDailySummaries { get; set; } = 10; public int MaxDailySummaries { get; set; } = 10;
public int MaxMonthlySummaries { 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 MaxDCAPairs { get; set; } = 24;
public int MaxSettingsLogEntries { get; set; } = 20; public int MaxSettingsLogEntries { get; set; } = 20;
public string LinkPlatform { get; set; } = "TradingView"; public string LinkPlatform { get; set; } = "TradingView";
public string TVCustomLayout { get; set; } = "";
public string DefaultDCAMode { get; set; } = "Simple"; public string DefaultDCAMode { get; set; } = "Simple";
public string TvStudyA { get; set; } = ""; public string TvStudyA { get; set; } = "";
public string TvStudyB { get; set; } = ""; public string TvStudyB { get; set; } = "";
@ -164,6 +165,7 @@ namespace Core.Main.DataObjects.PTMagicData
public class SingleMarketSetting public class SingleMarketSetting
{ {
public string SettingName { get; set; } public string SettingName { get; set; }
public string TriggerConnection { get; set; } = "AND"; public string TriggerConnection { get; set; } = "AND";
[DefaultValue("AND")] [DefaultValue("AND")]
@ -195,6 +197,9 @@ namespace Core.Main.DataObjects.PTMagicData
public class Trigger public class Trigger
{ {
[DefaultValue("")]
public string Tag { get; set; } = "";
[DefaultValue("")] [DefaultValue("")]
public string MarketTrendName { get; set; } = ""; public string MarketTrendName { get; set; } = "";
@ -219,6 +224,9 @@ namespace Core.Main.DataObjects.PTMagicData
public class OffTrigger public class OffTrigger
{ {
[DefaultValue("")]
public string Tag { get; set; } = "";
[DefaultValue("")] [DefaultValue("")]
public string MarketTrendName { get; set; } = ""; public string MarketTrendName { get; set; } = "";
@ -399,21 +407,6 @@ namespace Core.Main.DataObjects.PTMagicData
public double Last24hVolume { 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 public class StatsData
{ {
public double SalesToday { get; set; } public double SalesToday { get; set; }
@ -572,5 +565,7 @@ namespace Core.Main.DataObjects.PTMagicData
public string Market { get; set; } public string Market { get; set; }
public string TotalCurrentValue { get; set; } public string TotalCurrentValue { get; set; }
public string TimeZoneOffset { get; set; } public string TimeZoneOffset { get; set; }
public string ExchangeURL { get; set; }
public string PTVersion { get; set; }
} }
} }

View File

@ -82,8 +82,11 @@ namespace Core.Main.DataObjects
{ {
if (!_offsetTimeSpan.HasValue) if (!_offsetTimeSpan.HasValue)
{ {
// Get offset for settings. // Ensure Misc is populated
_offsetTimeSpan = TimeSpan.Parse(_systemConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", "")); var misc = this.Misc;
// Get offset from Misc
_offsetTimeSpan = TimeSpan.Parse(misc.TimeZoneOffset);
} }
return _offsetTimeSpan.Value; return _offsetTimeSpan.Value;
@ -111,7 +114,7 @@ namespace Core.Main.DataObjects
if (_misc == null || (DateTime.UtcNow > _miscRefresh)) if (_misc == null || (DateTime.UtcNow > _miscRefresh))
{ {
_misc = BuildMiscData(GetDataFromProfitTrailer("api/v2/data/misc")); _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, StartBalance = PTData.startBalance,
TotalCurrentValue = PTData.totalCurrentValue, TotalCurrentValue = PTData.totalCurrentValue,
TimeZoneOffset = PTData.timeZoneOffset, TimeZoneOffset = PTData.timeZoneOffset,
ExchangeURL = PTData.exchangeUrl,
PTVersion = PTData.version,
}; };
} }
public List<DailyStatsData> DailyStats public List<DailyStatsData> DailyStats
@ -180,7 +185,7 @@ namespace Core.Main.DataObjects
{ {
JArray dailyStatsSection = (JArray)extraSection["dailyStats"]; JArray dailyStatsSection = (JArray)extraSection["dailyStats"];
_dailyStats = dailyStatsSection.Select(j => BuildDailyStatsData(j as JObject)).ToList(); _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)) if (_properties == null || (DateTime.UtcNow > _propertiesRefresh))
{ {
_properties = BuildProptertiesData(GetDataFromProfitTrailer("api/v2/data/properties")); _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 jsonReader.Read(); // Move to the value of the "basic" property
JObject basicSection = JObject.Load(jsonReader); JObject basicSection = JObject.Load(jsonReader);
_stats = BuildStatsData(basicSection); _stats = BuildStatsData(basicSection);
_statsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1); _statsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1);
break; break;
} }
} }
@ -355,7 +360,7 @@ namespace Core.Main.DataObjects
{ {
JArray dailyPNLSection = (JArray)extraSection["dailyPNLStats"]; JArray dailyPNLSection = (JArray)extraSection["dailyPNLStats"];
_dailyPNL = dailyPNLSection.Select(j => BuildDailyPNLData(j as JObject)).ToList(); _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)); _profitablePairs.Add(BuildProfitablePairs(profitablePair));
counter++; 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"]; JArray dailyTCVSection = (JArray)extraSection["dailyTCVStats"];
_dailyTCV = dailyTCVSection.Select(j => BuildDailyTCVData(j as JObject)).ToList(); _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"]; JArray monthlyStatsSection = (JArray)extraSection["monthlyStats"];
_monthlyStats = monthlyStatsSection.Select(j => BuildMonthlyStatsData(j as JObject)).ToList(); _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"], Order = monthlyStatsDataJson["order"],
}; };
} }
// public List<SellLogData> 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<DCALogData> DCALog public List<DCALogData> DCALog
{ {
@ -682,21 +636,25 @@ namespace Core.Main.DataObjects
{ {
get get
{ {
if (_buyLog == null || (DateTime.UtcNow > _buyLogRefresh)) if (_systemConfiguration.GeneralSettings.Monitor.MaxDashboardBuyEntries == 0)
{
lock (_buyLock)
{ {
// Thread double locking return _buyLog;
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; 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;
} }
} }

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Text.RegularExpressions;
using System.IO;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; 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 = "#"; string result = "#";
if (tvCustomLayout.Length > 0)
{
tvCustomLayout += "/";
}
if (platform.Equals("TradingView")) if (platform.Equals("TradingView"))
{ {
if (exchange.Equals("binancefutures", StringComparison.InvariantCultureIgnoreCase)) 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 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 else

View File

@ -1,9 +1,12 @@
using System; using System;
using System.Data;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading; using System.Threading;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using Core.Helper; using Core.Helper;
using Core.Main.DataObjects.PTMagicData; using Core.Main.DataObjects.PTMagicData;
using Core.MarketAnalyzer; using Core.MarketAnalyzer;
@ -59,6 +62,7 @@ namespace Core.Main
private Dictionary<string, int> _singleMarketSettingsCount = new Dictionary<string, int>(); private Dictionary<string, int> _singleMarketSettingsCount = new Dictionary<string, int>();
Dictionary<string, List<SingleMarketSetting>> _triggeredSingleMarketSettings = new Dictionary<string, List<SingleMarketSetting>>(); Dictionary<string, List<SingleMarketSetting>> _triggeredSingleMarketSettings = new Dictionary<string, List<SingleMarketSetting>>();
private static volatile object _lockObj = new object(); private static volatile object _lockObj = new object();
private Mutex mutex = new Mutex(false, "analyzerStateMutex");
public LogHelper Log public LogHelper Log
{ {
@ -122,6 +126,27 @@ namespace Core.Main
_state = value; _state = value;
} }
} }
public void WriteStateToFile()
{
try
{
mutex.WaitOne(); // Acquire the mutex
string dirPath = "_data";
string filePath = Path.Combine(dirPath, "AnalyzerState.");
// Ensure the directory exists
Directory.CreateDirectory(dirPath);
File.WriteAllText(filePath, this.State.ToString());
}
finally
{
mutex.ReleaseMutex(); // Release the mutex even if exceptions occur
}
}
public int RunCount public int RunCount
{ {
@ -828,6 +853,8 @@ namespace Core.Main
#region PTMagic Interval Methods #region PTMagic Interval Methods
public void PTMagicIntervalTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) public void PTMagicIntervalTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{ {
// Check if the bot is idle // Check if the bot is idle
@ -840,6 +867,7 @@ namespace Core.Main
{ {
// Change state to "Running" // Change state to "Running"
this.State = Constants.PTMagicBotState_Running; this.State = Constants.PTMagicBotState_Running;
this.WriteStateToFile();
this.RunCount++; this.RunCount++;
this.LastRuntime = DateTime.UtcNow; this.LastRuntime = DateTime.UtcNow;
@ -975,6 +1003,7 @@ namespace Core.Main
// Change state to Finished / Stopped // Change state to Finished / Stopped
this.State = Constants.PTMagicBotState_Idle; this.State = Constants.PTMagicBotState_Idle;
this.WriteStateToFile();
} }
} }
} }
@ -991,6 +1020,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."); 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.State = Constants.PTMagicBotState_Idle;
this.WriteStateToFile();
Log.DoLogInfo("PTMagic status reset, waiting for the next raid to be good to go again."); Log.DoLogInfo("PTMagic status reset, waiting for the next raid to be good to go again.");
} }
} }
@ -998,6 +1028,7 @@ namespace Core.Main
{ {
Log.DoLogWarn("No LastRuntimeSummary.json found after raid " + this.RunCount.ToString() + ", trying to reset PT Magic status..."); Log.DoLogWarn("No LastRuntimeSummary.json found after raid " + this.RunCount.ToString() + ", trying to reset PT Magic status...");
this.State = Constants.PTMagicBotState_Idle; this.State = Constants.PTMagicBotState_Idle;
this.WriteStateToFile();
Log.DoLogInfo("PTMagic status reset, waiting for the next raid to be good to go again."); Log.DoLogInfo("PTMagic status reset, waiting for the next raid to be good to go again.");
} }
} }
@ -1269,6 +1300,8 @@ namespace Core.Main
} }
} }
private void BuildGlobalMarketTrends() private void BuildGlobalMarketTrends()
{ {
this.Log.DoLogInfo("Build global market trends..."); this.Log.DoLogInfo("Build global market trends...");
@ -1300,86 +1333,130 @@ namespace Core.Main
private void CheckGlobalSettingsTriggers(ref GlobalSetting triggeredSetting, ref List<string> matchedTriggers) private void CheckGlobalSettingsTriggers(ref GlobalSetting triggeredSetting, ref List<string> matchedTriggers)
{ {
this.Log.DoLogInfo("Checking global settings triggers..."); this.Log.DoLogInfo("Checking global settings triggers...");
foreach (GlobalSetting globalSetting in this.PTMagicConfiguration.AnalyzerSettings.GlobalSettings)
{
// Reset triggers for each setting
matchedTriggers = new List<string>();
if (globalSetting.Triggers.Count > 0) foreach (GlobalSetting globalSetting in this.PTMagicConfiguration.AnalyzerSettings.GlobalSettings)
{ {
this.Log.DoLogInfo("Checking triggers for '" + globalSetting.SettingName + "'..."); // Reset triggers for each setting
List<bool> triggerResults = new List<bool>(); matchedTriggers = new List<string>();
foreach (Trigger trigger in globalSetting.Triggers)
{ if (globalSetting.Triggers.Count > 0)
MarketTrend marketTrend = this.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.Find(mt => mt.Name == trigger.MarketTrendName);
if (marketTrend != null)
{ {
this.Log.DoLogInfo("Checking triggers for '" + globalSetting.SettingName + "'...");
// Get market trend change for trigger Dictionary<string, bool> triggerResults = new Dictionary<string, bool>();
if (this.AverageMarketTrendChanges.ContainsKey(marketTrend.Name)) foreach (Trigger trigger in globalSetting.Triggers)
{
double averageMarketTrendChange = this.AverageMarketTrendChanges[marketTrend.Name];
if (averageMarketTrendChange >= trigger.MinChange && averageMarketTrendChange < trigger.MaxChange)
{ {
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! if (isTriggered)
this.Log.DoLogInfo("Trigger '" + trigger.MarketTrendName + "' triggered! TrendChange = " + averageMarketTrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"); {
// Trigger met!
this.Log.DoLogInfo("Trigger '" + trigger.MarketTrendName + "' triggered! TrendChange = " + averageMarketTrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%");
string triggerContent = trigger.MarketTrendName + " - "; string triggerContent = trigger.MarketTrendName + " - ";
if (trigger.MinChange != Constants.MinTrendChange) if (trigger.MinChange != Constants.MinTrendChange)
{ {
triggerContent += " - Min: " + trigger.MinChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"; triggerContent += " - Min: " + trigger.MinChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
} }
if (trigger.MaxChange != Constants.MaxTrendChange) if (trigger.MaxChange != Constants.MaxTrendChange)
{ {
triggerContent += " - Max: " + trigger.MaxChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"; 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)
{
if (!string.IsNullOrEmpty(triggerResult.Key))
{
triggerConnection = triggerConnection.Replace(triggerResult.Key, triggerResult.Value.ToString().ToLower());
}
else
{
this.Log.DoLogError($"ERROR: A required trigger Tag is missing for global setting {globalSetting.SettingName}. Program halted.");
Environment.Exit(1); // Stop the program
}
}
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 else
{ {
this.Log.DoLogDebug("Trigger '" + trigger.MarketTrendName + "' not triggered. TrendChange = " + averageMarketTrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"); this.Log.DoLogError($"ERROR: Trigger Connection for global setting {globalSetting.SettingName} is missing. Program halted.");
triggerResults.Add(false); 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<string> matchedTriggers) private void ActivateSetting(ref GlobalSetting triggeredSetting, ref List<string> matchedTriggers)
@ -1751,6 +1828,14 @@ namespace Core.Main
Dictionary<int, double> relevantTriggers = new Dictionary<int, double>(); Dictionary<int, double> relevantTriggers = new Dictionary<int, double>();
int triggerIndex = 0; int triggerIndex = 0;
// Create a dictionary to store the tag and its corresponding result
Dictionary<string, bool> triggerTagsResults = new Dictionary<string, bool>();
// Initialize all tags with a value of false
foreach (Trigger trigger in marketSetting.Triggers)
{
triggerTagsResults[trigger.Tag] = false;
}
// Loop through SMS triggers // Loop through SMS triggers
foreach (Trigger trigger in marketSetting.Triggers) foreach (Trigger trigger in marketSetting.Triggers)
{ {
@ -1786,6 +1871,7 @@ namespace Core.Main
matchedSingleMarketTriggers.Add(marketSetting.SettingName + ": " + triggerContent + " - 24h volume = " + mtc.Volume24h.ToString(new System.Globalization.CultureInfo("en-US")) + " " + this.LastRuntimeSummary.MainMarket); matchedSingleMarketTriggers.Add(marketSetting.SettingName + ": " + triggerContent + " - 24h volume = " + mtc.Volume24h.ToString(new System.Globalization.CultureInfo("en-US")) + " " + this.LastRuntimeSummary.MainMarket);
triggerResults.Add(true); triggerResults.Add(true);
triggerTagsResults[trigger.Tag] = true;
} }
else else
{ {
@ -1816,6 +1902,7 @@ namespace Core.Main
relevantTriggers.Add(triggerIndex, marketAge); relevantTriggers.Add(triggerIndex, marketAge);
triggerResults.Add(true); triggerResults.Add(true);
triggerTagsResults[trigger.Tag] = true;
} }
else else
{ {
@ -1897,6 +1984,7 @@ namespace Core.Main
matchedSingleMarketTriggers.Add(marketSetting.SettingName + ": " + triggerContent + " - TrendChange (" + trigger.MarketTrendRelation + ") = " + trendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%"); matchedSingleMarketTriggers.Add(marketSetting.SettingName + ": " + triggerContent + " - TrendChange (" + trigger.MarketTrendRelation + ") = " + trendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%");
triggerResults.Add(true); triggerResults.Add(true);
triggerTagsResults[trigger.Tag] = true;
} }
else else
{ {
@ -1938,16 +2026,50 @@ namespace Core.Main
triggerIndex++; triggerIndex++;
} // End loop SMS triggers } // End loop SMS triggers
// Check if all triggers have to get triggered or just one // Check if all triggers have to get triggered or just one
bool settingTriggered = false; bool settingTriggered = false;
switch (marketSetting.TriggerConnection.ToLower()) if (marketSetting.TriggerConnection.ToLower() == "and" || marketSetting.TriggerConnection.ToLower() == "or")
{ {
case "and": switch (marketSetting.TriggerConnection.ToLower())
settingTriggered = triggerResults.FindAll(tr => tr == false).Count == 0; {
break; case "and":
case "or": settingTriggered = triggerResults.FindAll(tr => tr == false).Count == 0;
settingTriggered = triggerResults.FindAll(tr => tr == true).Count > 0; break;
break; case "or":
settingTriggered = triggerResults.FindAll(tr => tr == true).Count > 0;
break;
}
}
else
{
// Parse the TriggerConnection string into a logical expression
string triggerConnection = marketSetting.TriggerConnection;
foreach (var triggerResult in triggerTagsResults)
{
// Replace the tag in the expression with its corresponding value from triggerTagsResults
if (!string.IsNullOrEmpty(triggerResult.Key))
{
triggerConnection = triggerConnection.Replace(triggerResult.Key, triggerResult.Value.ToString().ToLower());
}
else
{
this.Log.DoLogError($"ERROR: A required trigger Tag is missing for global setting {marketSetting.SettingName}. Program halted.");
Environment.Exit(1); // Stop the program
}
}
try
{
// Evaluate the expression using ParseLambda
settingTriggered = (bool)DynamicExpressionParser.ParseLambda(ParsingConfig.Default, new ParameterExpression[0], typeof(bool), triggerConnection).Compile().DynamicInvoke();
}
catch (Exception ex)
{
this.Log.DoLogError($"ERROR: Trigger Connection for global setting {marketSetting.SettingName} is invalid or missing. Program halted.");
Environment.Exit(1); // Stop the program
}
} }
#endregion #endregion

View File

@ -26,9 +26,12 @@
<script type="text/javascript"> <script type="text/javascript">
var errCountIndex = []; var errCountIndex = [];
var counterIndex = []; // Add this line
var intervalDashboardTop; var intervalDashboardTop;
var intervalDashboardBottom; var intervalDashboardBottom;
var loadDashboardTop = function () { var loadDashboardTop = function () {
$("#baglist-refresh-icon").html('<i class="fa fa-circle-o-notch fa-spin fa-fw" data-toggle="tooltip" data-placement="top" title="Loading fresh data..."></i>'); $("#baglist-refresh-icon").html('<i class="fa fa-circle-o-notch fa-spin fa-fw" data-toggle="tooltip" data-placement="top" title="Loading fresh data..."></i>');
$("#buylist-refresh-icon").html('<i class="fa fa-circle-o-notch fa-spin fa-fw" data-toggle="tooltip" data-placement="top" title="Loading fresh data..."></i>'); $("#buylist-refresh-icon").html('<i class="fa fa-circle-o-notch fa-spin fa-fw" data-toggle="tooltip" data-placement="top" title="Loading fresh data..."></i>');
@ -63,23 +66,27 @@
}; };
var loadDashboardBottom = function () { var loadDashboardBottom = function () {
//destroy all d3 svg graph to avoid memory leak // Clear existing interval to stop multiple attempts to load at the same time.
setTimeout(function() if (intervalDashboardBottom != null) {
{
$(".nvtooltip").remove();
$("svg > *").remove();
$("svg").remove();
nv.charts = {};
nv.graphs = [];
nv.logs = {};
nv.tooltip = {};
}, 10 * intervalDashboardBottom * 1000); // 10 times intervalDashboardBottom in milliseconds
// Clear exisitng interval to stop multiple attempts to load at the same time.
if (intervalDashboardBottom != null)
{
clearInterval(intervalDashboardBottom); clearInterval(intervalDashboardBottom);
} }
console.log(counterIndex["DashboardBottom"]);
// Destroy all d3 svg graph to avoid memory leak every 10 refreshes of Dashboard Bottom
if (counterIndex["DashboardBottom"] >= 10) {
$(".nvtooltip").remove();
$("svg > *").remove();
$("svg").remove();
$("svg").off(); // Remove all event listeners from SVG elements
nv.charts = {};
nv.graphs = [];
nv.logs = {};
nv.tooltip = {};
window.cleanupData();
// Reset the counter
counterIndex["DashboardBottom"] = 0;
console.log("d3 svg graph destroyed");
}
// Load dashboard // Load dashboard
$("#dashboardBottom").load('@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/DashboardBottom', '', function (responseText, textStatus, XMLHttpRequest) { $("#dashboardBottom").load('@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/DashboardBottom', '', function (responseText, textStatus, XMLHttpRequest) {
@ -92,10 +99,15 @@
window.location.replace("@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)Login"); window.location.replace("@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)Login");
} else { } else {
errCountIndex["DashboardBottom"] = 0; errCountIndex["DashboardBottom"] = 0;
// Increment the counter
counterIndex["DashboardBottom"] = (counterIndex["DashboardBottom"] || 0) + 1;
} }
// Reinstate the interval. // Reinstate the interval.
intervalDashboardBottom = setInterval(function () { loadDashboardBottom(); }, @Model.PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds * 1000); intervalDashboardBottom = setInterval(function () { loadDashboardBottom(); }, @Model.PTMagicConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds * 1000);
}); });
}; };

View File

@ -29,7 +29,12 @@
} }
<td><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SettingsAnalyzer#SingleMarketSetting_@sms">@sms</a>:&nbsp;@smsCount &emsp; &emsp;</td> <td><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SettingsAnalyzer#SingleMarketSetting_@sms">@sms</a>:&nbsp;@smsCount &emsp; &emsp;</td>
} }
} @if (Model.PTMagicConfiguration.GeneralSettings.Monitor.IsPasswordProtected) {
<a class="btn btn-danger btn-sm btn-custom btn-block text-uppercase btn-deleteall" style="margin-top: 10px;" data-filename="SingleMarketSettingSummary.json" href="#">Reset ALL Single MarketSettings</a>
} else {
<a class="btn btn-danger btn-custom btn-block text-uppercase" data-toggle="tooltip" data-placement="top" title="This is only accessible when you protect your monitor with a password!"><i class="fa fa-lock text-danger"></i> Delete File</a>
}
}
</tr> </tr>
</p> </p>
</tbody> </tbody>
@ -69,7 +74,7 @@
<tr> <tr>
<th> <th>
@if (!lastMarket.Equals(smsSummary.Market)) { @if (!lastMarket.Equals(smsSummary.Market)) {
<a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform, Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, smsSummary.Market, Model.Summary.MainMarket)" target="_blank">@smsSummary.Market</a> <a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform, Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, smsSummary.Market, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@smsSummary.Market</a>
} }
</th> </th>
<td> <td>
@ -197,7 +202,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
Do you really want to reset this Single Market Setting during the next run of PT Magic? Do you really want to reset this Single Market Settings for the next Analyzer run?
<p class="m-t-10"><span class="text-warning">Please note:</span> Even if you reset a setting, it may get triggered again on the next run depending on current market conditions.</p> <p class="m-t-10"><span class="text-warning">Please note:</span> Even if you reset a setting, it may get triggered again on the next run depending on current market conditions.</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@ -208,7 +213,89 @@
</div> </div>
</div> </div>
<div class="modal fade" id="modalDeleteFile" tabindex="-1" role="dialog" aria-labelledby="modalDeleteFile" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalDeleteFile">Are you sure?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Do you really want to reset ALL Single Market Settings for the next Analyzer run?
<p class="m-t-10"><span class="text-warning">Please note:</span> Even if you reset your settings, they may get triggered again on the next run depending on current market conditions.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-ptmagic text-uppercase waves-effect waves-light btn-confirmdelete" data-datatarget="" data-setting="">Yes, do it!</button>
<button type="button" class="btn btn-secondary text-uppercase" data-dismiss="modal">No...</button>
</div>
</div>
</div>
</div>
@section Scripts { @section Scripts {
<script type="text/javascript">
$(document).on('click', '.btn-deleteall', function(e) {
e.preventDefault();
var filename = $(this).data("filename");
// Store the filename in the modal's data
$('#modalDeleteFile').data('filename', filename);
// Show the modal
$('#modalDeleteFile').modal('show');
});
$(document).on('click', '.btn-confirmdelete', function(e) {
e.preventDefault();
// Retrieve the filename from the modal's data
var filename = $('#modalDeleteFile').data('filename');
var baseUrl = "@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)";
var endpoint = "ManageSMS?handler=DeleteFile";
var url = baseUrl + endpoint;
$.ajax({
url: url,
type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader("XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
data: { filename: filename },
success: function(result) {
if (result.success) {
// If the server returned success, display the success message
$.Notification.notify('success', 'top left', 'Success', result.message);
// Wait for 5 seconds, then refresh the page
setTimeout(function() {
location.reload();
}, 5000);
} else {
// If the server returned failure, display the error message
$.Notification.notify('error', 'top left', 'Error', result.message);
}
// Close the modal
$('#modalDeleteFile').modal('hide');
},
error: function(xhr, status, error) {
var response = JSON.parse(xhr.responseText);
// Show an alert for error
alert(response.message);
}
});
});
$('#modalDeleteFile').on('hidden.bs.modal', function (e) {
// Reset the modal's content
$('#modalDeleteFile .modal-title').text('Are you sure?');
$('#modalDeleteFile .modal-body').html('Do you really want to reset ALL Single Market Settings for the next Analyzer run? <p class="m-t-10"><span class="text-warning">Please note:</span> Even if you reset your settings, they may get triggered again on the next run depending on current market conditions.</p>');
});
</script>
<script type="text/javascript"> <script type="text/javascript">
$(function () { $(function () {

View File

@ -3,7 +3,8 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Hosting;
using Core.Main; using Core.Main;
using Core.Main.DataObjects.PTMagicData; using Core.Main.DataObjects.PTMagicData;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -13,7 +14,12 @@ namespace Monitor.Pages
public class ManageSMSModel : _Internal.BasePageModelSecure public class ManageSMSModel : _Internal.BasePageModelSecure
{ {
public List<SingleMarketSettingSummary> SingleMarketSettingSummaries = new List<SingleMarketSettingSummary>(); public List<SingleMarketSettingSummary> SingleMarketSettingSummaries = new List<SingleMarketSettingSummary>();
private readonly IWebHostEnvironment _hostingEnvironment;
public ManageSMSModel(IWebHostEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
public void OnGet() public void OnGet()
{ {
base.Init(); base.Init();
@ -35,6 +41,38 @@ namespace Monitor.Pages
} }
} }
public IActionResult OnPostDeleteFile(string filename)
{
string webRootParent = Directory.GetParent(_hostingEnvironment.WebRootPath).FullName;
string ptMagicRoot = Directory.GetParent(webRootParent).FullName;
string analyzerStatePath = Path.Combine(ptMagicRoot, "_data", "AnalyzerState");
// Read the AnalyzerState file
if (System.IO.File.Exists(analyzerStatePath))
{
string state = System.IO.File.ReadAllText(analyzerStatePath);
if (state != "0")
{
return new JsonResult(new { success = false, message = "Tha Analyzer is in the middle of a run. Try again in a moment." });
}
}
// If state is "0", proceed to delete the file
try
{
string path = Path.Combine(ptMagicRoot, "_data", filename);
if (System.IO.File.Exists(path))
{
System.IO.File.Delete(path);
}
return new JsonResult(new { success = true, message = "All SMS settings reset!" });
}
catch (Exception ex)
{
return new JsonResult(new { success = false, message = ex.Message });
}
}
private void BindData() private void BindData()
{ {
if (System.IO.File.Exists(PTMagicBasePath + Constants.PTMagicPathData + Path.DirectorySeparatorChar + "SingleMarketSettingSummary.json")) if (System.IO.File.Exists(PTMagicBasePath + Constants.PTMagicPathData + Path.DirectorySeparatorChar + "SingleMarketSettingSummary.json"))

View File

@ -162,7 +162,7 @@ else
<th>Name</th> <th>Name</th>
<th class="text-right">Markets</th> <th class="text-right">Markets</th>
<th class="text-right">Timeframe</th> <th class="text-right">Timeframe</th>
<th class="text-right">Threshold <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Pairs exceeding this threshold are excluded from the trend average."></i></th> <th class="text-right">Threshold <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Pairs exceeding this threshold are considered outliers that are excluded from the trend average."></i></th>
<th class="text-right">Change</th> <th class="text-right">Change</th>
</tr> </tr>
</thead> </thead>
@ -271,9 +271,9 @@ else
} }
</th> </th>
@if (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0) { @if (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0) {
<th><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, market, Model.Summary.MainMarket)" target="_blank">@market</a></th> <th><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, market, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@market</a></th>
} else { } else {
<th><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, market, Model.Summary.MainMarket)" target="_blank">@market</a> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i></th> <th><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, market, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@market</a> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i></th>
} }
<td class="text-right">@mps.LatestPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainMarket</td> <td class="text-right">@mps.LatestPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainMarket</td>
<td class="text-right">@Math.Round(mps.Latest24hVolume, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainMarket</td> <td class="text-right">@Math.Round(mps.Latest24hVolume, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainMarket</td>
@ -336,9 +336,9 @@ else
} }
</th> </th>
@if (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0) { @if (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0) {
<th><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, market, Model.Summary.MainMarket)" target="_blank">@market</a></th> <th><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, market, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@market</a></th>
} else { } else {
<th><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, market, Model.Summary.MainMarket)" target="_blank">@market</a> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i></th> <th><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, market, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@market</a> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i></th>
} }
<td class="text-right">@mps.LatestPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td> <td class="text-right">@mps.LatestPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right">@Math.Round(mps.Latest24hVolume, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US"))</td> <td class="text-right">@Math.Round(mps.Latest24hVolume, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US"))</td>
@ -368,41 +368,81 @@ else
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw-init.js"></script> <script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw-init.js"></script>
<script type="text/javascript"> <script type="text/javascript">
$(function () { $(function () {
$('.btn-trend-relation').click(function () { $('.btn-trend-relation').click(function () {
var relation = $(this).data('trend-relation'); var relation = $(this).data('trend-relation');
$('.btn-trend-relation').addClass('btn-custom'); $('.btn-trend-relation').addClass('btn-custom');
$(this).removeClass('btn-custom'); $(this).removeClass('btn-custom');
if (relation == 'absolute') { if (relation == 'absolute') {
$('#trends-absolute').removeClass('hidden'); $('#trends-absolute').removeClass('hidden');
$('#trends-relative').addClass('hidden'); $('#trends-relative').addClass('hidden');
} else { } else {
$('#trends-absolute').addClass('hidden'); $('#trends-absolute').addClass('hidden');
$('#trends-relative').removeClass('hidden'); $('#trends-relative').removeClass('hidden');
} }
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
});
@if (!Model.TrendChartDataJSON.Equals("")) {
<text>
nv.addGraph(function () {
var lineChart = nv.models.lineChart();
var height = 300;
var chartData = @Html.Raw(Model.TrendChartDataJSON);
lineChart.useInteractiveGuideline(true);
lineChart.xAxis.tickFormat(function (d) { return d3.time.format('%H:%M')(new Date(d)); });
lineChart.yAxis.axisLabel('Trend %').tickFormat(d3.format(',.2f'));
d3.select('.trend-chart svg').attr('perserveAspectRatio', 'xMinYMid').datum(chartData).transition().duration(500).call(lineChart);
//nv.utils.windowResize(lineChart.update); v1.3.0 => Removed this line to prevent memory leak
return lineChart;
}); });
</text>
} @if (!Model.TrendChartDataJSON.Equals("")) {
}) <text>
nv.addGraph(function () {
lineChart = nv.models.lineChart();
var height = 300;
var chartData = @Html.Raw(Model.TrendChartDataJSON);
lineChart.useInteractiveGuideline(true);
lineChart.xAxis.tickFormat(function (d) { return d3.time.format('%H:%M')(new Date(d)); });
lineChart.yAxis.axisLabel('Trend %').tickFormat(d3.format(',.2f'));
d3.select('.trend-chart svg').attr('perserveAspectRatio', 'xMinYMid').datum(chartData).transition().duration(500).call(lineChart);
//nv.utils.windowResize(lineChart.update); v1.3.0 => Removed this line to prevent memory leak
lineChart.dispatch.on('renderEnd', function() {
// Get the chart's container
var container = d3.select('.trend-chart .nv-wrap.nv-lineChart .nv-linesWrap');
// Remove any existing y=0 line
container.selectAll('.zero-line').remove();
// Get the x-values of the first and last data points
var xRange = lineChart.xAxis.scale().range();
var xMin = xRange[0];
var xMax = xRange[1];
// Add a line at y=0
container.insert('line', ':first-child')
.attr('class', 'zero-line') // Add a class to the line for easy
.attr('x1', xMin) // x position of the first end of the line
.attr('y1', lineChart.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', lineChart.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
});
// Add mouseleave, and mousemove event listeners to hide tooltip
d3.select('.trend-chart').on('mouseleave', function() {
d3.select('.nvtooltip').remove();
});
d3.select('.trend-chart').on('mousemove', function() {
var chartBounds = d3.select('.trend-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').remove();
}
});
return lineChart;
});
</text>
}
})
</script> </script>
} }

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Core.Main; using Core.Main;
using Core.Helper; using Core.Helper;
using Core.Main.DataObjects;
using Core.Main.DataObjects.PTMagicData; using Core.Main.DataObjects.PTMagicData;
using System.Globalization; using System.Globalization;
@ -11,6 +12,8 @@ namespace Monitor.Pages
public class MarketAnalyzerModel : _Internal.BasePageModelSecure public class MarketAnalyzerModel : _Internal.BasePageModelSecure
{ {
public List<MarketTrend> MarketTrends { get; set; } = new List<MarketTrend>(); public List<MarketTrend> MarketTrends { get; set; } = new List<MarketTrend>();
public ProfitTrailerData PTData = null;
public MiscData MiscData { get; set; }
public string TrendChartDataJSON = ""; public string TrendChartDataJSON = "";
public double DataHours { get; set; } public double DataHours { get; set; }
@ -22,7 +25,8 @@ namespace Monitor.Pages
private void BindData() 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(); MarketTrends = PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.OrderBy(mt => mt.TrendMinutes).ThenByDescending(mt => mt.Platform).ToList();
BuildMarketTrendChartData(); BuildMarketTrendChartData();
@ -56,8 +60,8 @@ namespace Monitor.Pages
// Get trend ticks for chart // Get trend ticks for chart
TimeSpan offset; TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); bool isNegative = MiscData.TimeZoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{ {

View File

@ -329,7 +329,7 @@
double profitFiat = Math.Round(profit * Model.MiscData.FiatConversionRate, 0); double profitFiat = Math.Round(profit * Model.MiscData.FiatConversionRate, 0);
<tr> <tr>
<td>@rank</td> <td>@rank</td>
<td><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform, Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, coin, Model.PTData.Misc.Market)" target="_blank">@coin</a></td> <td><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform, Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, coin, Model.PTData.Misc.Market, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@coin</a></td>
<td class="text-right text-autocolor-saw">@profit</td> <td class="text-right text-autocolor-saw">@profit</td>
<td class="text-right">@sales</td> <td class="text-right">@sales</td>
<td class="text-right text-autocolor-saw">@avg </td> <td class="text-right text-autocolor-saw">@avg </td>
@ -394,20 +394,7 @@
.transition().duration(0) .transition().duration(0)
.call(salesChart); .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); nv.utils.windowResize(salesChart.update);
return salesChart; return salesChart;
}); });
@ -442,20 +429,7 @@
.transition().duration(0) .transition().duration(0)
.call(cumulativeProfitChart); .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); nv.utils.windowResize(cumulativeProfitChart.update);
return cumulativeProfitChart; return cumulativeProfitChart;
}); });
@ -490,20 +464,6 @@
.transition().duration(0) .transition().duration(0)
.call(TCVChart); .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); nv.utils.windowResize(TCVChart.update);
return TCVChart; return TCVChart;
}); });
@ -538,20 +498,33 @@
.transition().duration(0) .transition().duration(0)
.call(profitChart); .call(profitChart);
// Add mouseleave, and mousemove event listeners to hide tooltip
d3.select('.profit-chart').on('mouseleave', function() { profitChart.dispatch.on('renderEnd', function() {
d3.select('.nvtooltip').style('opacity', 0); // 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); nv.utils.windowResize(profitChart.update);
return profitChart; return profitChart;
}); });
@ -559,4 +532,34 @@
} }
})(jQuery); })(jQuery);
</script> </script>
<script type="text/javascript">
function attachTooltipRemoval(chartClass) {
d3.selectAll(chartClass).on('mouseleave', function() {
var tooltip = d3.select(this).select('.nvtooltip');
if (!tooltip.empty()) {
tooltip.style('opacity', 0);
}
});
d3.selectAll(chartClass).on('mouseenter', function() {
var tooltip = d3.select(this).select('.nvtooltip');
if (!tooltip.empty()) {
tooltip.style('opacity', 1);
}
});
}
$(document).ready(function () {
// Hide all tooltips when the page is loaded
d3.selectAll('.nvtooltip').style('opacity', 0);
// Call the function for each chart
attachTooltipRemoval('.asset-distribution svg');
attachTooltipRemoval('.trend-chart svg');
attachTooltipRemoval('.profit-chart svg');
attachTooltipRemoval('.TCVLive-chart svg');
// Add other charts as needed
});
</script>
} }

View File

@ -54,7 +54,7 @@ namespace Monitor.Pages
DailyStats = this.PTData.DailyStats; DailyStats = this.PTData.DailyStats;
// Convert local offset time to UTC // 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); DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
BuildSalesChartData(); BuildSalesChartData();
@ -71,8 +71,8 @@ namespace Monitor.Pages
{ {
// Get timezone offset // Get timezone offset
TimeSpan offset; TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); bool isNegative = MiscData.TimeZoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{ {
@ -129,8 +129,8 @@ namespace Monitor.Pages
{ {
// Get timezone offset // Get timezone offset
TimeSpan offset; TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); bool isNegative = MiscData.TimeZoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{ {
@ -190,8 +190,8 @@ namespace Monitor.Pages
{ {
// Get timezone offset // Get timezone offset
TimeSpan offset; TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); bool isNegative = MiscData.TimeZoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{ {
@ -301,8 +301,8 @@ namespace Monitor.Pages
{ {
// Get timezone offset // Get timezone offset
TimeSpan offset; TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); bool isNegative = MiscData.TimeZoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{ {

View File

@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Mvc;
using Core.Main; using Core.Main;
using Core.Helper; using Core.Helper;
using Core.Main.DataObjects.PTMagicData; using Core.Main.DataObjects.PTMagicData;
using Microsoft.Extensions.Primitives;
namespace Monitor.Pages namespace Monitor.Pages
{ {

View File

@ -117,12 +117,12 @@
</div> </div>
</div> </div>
<div class="form-group row"> @* <div class="form-group row">
<label class="col-md-4 col-form-label">Timezone Offset <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The time difference between your current location and UTC time. Examples: +7:00, -3:00"></i></label> <label class="col-md-4 col-form-label">Timezone Offset <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The time difference between your current location and UTC time. Examples: +7:00, -3:00"></i></label>
<div class="col-md-8"> <div class="col-md-8">
<input type="text" class="form-control" name="Application_TimezoneOffset" value="@Model.PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset"> <input type="text" class="form-control" name="Application_TimezoneOffset" value="@Model.PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset">
</div> </div>
</div> </div> *@
@* <div class="form-group row"> @* <div class="form-group row">
<label class="col-md-4 col-form-label">Main Fiat Currency <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This is the local currency you want PTM to use when showing your sales and account value in fiat."></i></label> <label class="col-md-4 col-form-label">Main Fiat Currency <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This is the local currency you want PTM to use when showing your sales and account value in fiat."></i></label>
@ -196,42 +196,48 @@
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-md-4 col-form-label">Market Trend Graph Interval Minutes <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The time interval for the market trend graph (Dashboard and Sales Analyzer) between data points. Very small intervals on large timeframe graphs can significantly impact performance."></i></label> <label class="col-md-4 col-form-label">Live TCV Timeframe (Minutes) <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The total timeframe for the Live TCV chart on the Dashboard. Very large timeframes can significantly impact performance."></i></label>
<div class="col-md-8">
<input type="text" class="form-control" name="Monitor_LiveTCVTimeframeMinutes" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.LiveTCVTimeframeMinutes.ToString(new System.Globalization.CultureInfo("en-US"))">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 col-form-label">Trend Chart Interval (Minutes) <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The time interval for the market trend graph (Dashboard and Sales Analyzer) between data points. Very small intervals on large timeframe graphs can significantly impact performance."></i></label>
<div class="col-md-8"> <div class="col-md-8">
<input type="text" class="form-control" name="Monitor_GraphIntervalMinutes" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes.ToString(new System.Globalization.CultureInfo("en-US"))"> <input type="text" class="form-control" name="Monitor_GraphIntervalMinutes" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes.ToString(new System.Globalization.CultureInfo("en-US"))">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-md-4 col-form-label">Market Trend Graph Max Timeframe <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This defines the total timeframe for the market trends graph (Dashboard and Sales Analyzer) in hours. Large timeframe graphs can significantly impact performance."></i></label> <label class="col-md-4 col-form-label">Trend Chart Timeframe (Hours) <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This defines the total timeframe for the market trends graph (Dashboard and Sales Analyzer) in hours. Large timeframe graphs can significantly impact performance."></i></label>
<div class="col-md-8"> <div class="col-md-8">
<input type="text" class="form-control" name="Monitor_GraphMaxTimeframeHours" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours.ToString(new System.Globalization.CultureInfo("en-US"))"> <input type="text" class="form-control" name="Monitor_GraphMaxTimeframeHours" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours.ToString(new System.Globalization.CultureInfo("en-US"))">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-md-4 col-form-label">Daily Profit Graph Max Timeframe <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This defines the total timeframe for the daily profits graph on the dashboard in days. Large timeframes can significantly impact performance."></i></label> <label class="col-md-4 col-form-label">Daily Profit Chart Timeframe (Days) <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This defines the total timeframe for the daily profits graph on the dashboard in days. Large timeframes can significantly impact performance."></i></label>
<div class="col-md-8"> <div class="col-md-8">
<input type="text" class="form-control" name="Monitor_ProfitsMaxTimeframeDays" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.ProfitsMaxTimeframeDays.ToString(new System.Globalization.CultureInfo("en-US"))"> <input type="text" class="form-control" name="Monitor_ProfitsMaxTimeframeDays" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.ProfitsMaxTimeframeDays.ToString(new System.Globalization.CultureInfo("en-US"))">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-md-4 col-form-label">Dashboard Bottom Refresh <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The refresh interval in seconds, of the charts and graphs on then main page."></i></label> <label class="col-md-4 col-form-label">Dashboard Charts Refresh (Seconds) <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The refresh interval in seconds, of the charts and graphs on then main page."></i></label>
<div class="col-md-8"> <div class="col-md-8">
<input type="text" class="form-control" name="Monitor_RefreshSeconds" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds.ToString(new System.Globalization.CultureInfo("en-US"))"> <input type="text" class="form-control" name="Monitor_RefreshSeconds" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds.ToString(new System.Globalization.CultureInfo("en-US"))">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-md-4 col-form-label">Bag AnalyzerRefresh Seconds <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The refresh interval of your monitor bag analyzer page."></i></label> <label class="col-md-4 col-form-label">Bag Analyzer Refresh (Seconds) <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The refresh interval of your monitor bag analyzer page."></i></label>
<div class="col-md-8"> <div class="col-md-8">
<input type="text" class="form-control" name="Monitor_BagAnalyzerRefreshSeconds" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.BagAnalyzerRefreshSeconds.ToString(new System.Globalization.CultureInfo("en-US"))"> <input type="text" class="form-control" name="Monitor_BagAnalyzerRefreshSeconds" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.BagAnalyzerRefreshSeconds.ToString(new System.Globalization.CultureInfo("en-US"))">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-md-4 col-form-label">Buy AnalyzerRefresh Seconds <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The refresh interval of your monitor buy analyzer page."></i></label> <label class="col-md-4 col-form-label">Buy Analyzer Refresh (Seconds) <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The refresh interval of your monitor buy analyzer page."></i></label>
<div class="col-md-8"> <div class="col-md-8">
<input type="text" class="form-control" name="Monitor_BuyAnalyzerRefreshSeconds" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds.ToString(new System.Globalization.CultureInfo("en-US"))"> <input type="text" class="form-control" name="Monitor_BuyAnalyzerRefreshSeconds" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds.ToString(new System.Globalization.CultureInfo("en-US"))">
</div> </div>
@ -247,12 +253,12 @@
</div> </div>
</div> </div>
@* <div class="form-group row"> <div class="form-group row">
<label class="col-md-4 col-form-label">Max Sales Records<i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The number of sales records PTMagic pulls from Profit Trailer. Changes require a Monitor Restart."></i></label> <label class="col-md-4 col-form-label">TV Custom Chart <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="If TradingView is selected above, you can use the custom layout code found in your TV URL - ex. AGrfHNiI."></i></label>
<div class="col-md-8"> <div class="col-md-8">
<input type="text" class="form-control" name="Monitor_MaxSalesRecords" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxSalesRecords.ToString(new System.Globalization.CultureInfo("en-US"))"> <input type="text" class="form-control" name="Monitor_TVCustomLayout" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout">
</div> </div>
</div> *@ </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-md-4 col-form-label">Max Top Markets <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The amount of top markets being show in your Sales Analyzer."></i></label> <label class="col-md-4 col-form-label">Max Top Markets <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The amount of top markets being show in your Sales Analyzer."></i></label>

View File

@ -27,30 +27,6 @@ namespace Monitor.Pages
return result; return result;
} }
public string GetTimezoneSelection()
{
string result = "";
List<string> tzOffsetList = new List<string>();
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 += "<option" + selected + ">" + offsetString + "</option>\n";
tzOffsetList.Add(offsetString);
}
}
return result;
}
public void OnGet() public void OnGet()
{ {
base.Init(); base.Init();
@ -72,30 +48,25 @@ namespace Monitor.Pages
// Read the new settings // Read the new settings
PTMagicConfiguration.GeneralSettings.Application.IsEnabled = HttpContext.Request.Form["Application_IsEnabled"].Equals("on"); 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.ProfitTrailerDefaultSettingName = HttpContext.Request.Form["Application_ProfitTrailerDefaultSettingName"];
PTMagicConfiguration.GeneralSettings.Application.Exchange = HttpContext.Request.Form["Application_Exchange"]; PTMagicConfiguration.GeneralSettings.Application.Exchange = HttpContext.Request.Form["Application_Exchange"];
PTMagicConfiguration.GeneralSettings.Application.ProfitTrailerMonitorURL = HttpContext.Request.Form["Application_ProfitTrailerMonitorURL"]; 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.FloodProtectionMinutes = SystemHelper.TextToInteger(HttpContext.Request.Form["Application_FloodProtectionMinutes"], PTMagicConfiguration.GeneralSettings.Application.FloodProtectionMinutes);
PTMagicConfiguration.GeneralSettings.Application.InstanceName = HttpContext.Request.Form["Application_InstanceName"]; PTMagicConfiguration.GeneralSettings.Application.InstanceName = HttpContext.Request.Form["Application_InstanceName"];
PTMagicConfiguration.GeneralSettings.Application.CoinMarketCapAPIKey = HttpContext.Request.Form["Application_CoinMarketCapAPIKey"]; 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.IsPasswordProtected = HttpContext.Request.Form["Monitor_IsPasswordProtected"].Equals("on");
PTMagicConfiguration.GeneralSettings.Monitor.OpenBrowserOnStart = HttpContext.Request.Form["Monitor_OpenBrowserOnStart"].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.AnalyzerChart = HttpContext.Request.Form["Monitor_AnalyzerChart"];
PTMagicConfiguration.GeneralSettings.Monitor.Port = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_Port"], PTMagicConfiguration.GeneralSettings.Monitor.Port); 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.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.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.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.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.BuyAnalyzerRefreshSeconds = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_BuyAnalyzerRefreshSeconds"], PTMagicConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds);
PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform = HttpContext.Request.Form["Monitor_LinkPlatform"]; 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.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.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); PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxMonthlySummaries"], PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries);

View File

@ -116,13 +116,8 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="card-box"> <div class="card-box">
<h4 class="m-t-0 header-title">Global Settings Log</h4> <h4 class="m-t-0 header-title">Global Settings Log</h4>
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
@ -136,7 +131,7 @@
</thead> </thead>
<tbody> <tbody>
@foreach (Core.Main.DataObjects.PTMagicData.GlobalSettingSummary gss in Model.Summary.GlobalSettingSummary.OrderByDescending(g => g.SwitchDateTime).Take(Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxSettingsLogEntries)) { @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; DateTimeOffset settingActivationTime = gss.SwitchDateTime;
settingActivationTime = settingActivationTime.ToOffset(offsetTimeSpan); settingActivationTime = settingActivationTime.ToOffset(offsetTimeSpan);

View File

@ -4,6 +4,7 @@ using System.Linq;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Core.Main; using Core.Main;
using Core.Helper; using Core.Helper;
using Core.Main.DataObjects;
using Core.Main.DataObjects.PTMagicData; using Core.Main.DataObjects.PTMagicData;
namespace Monitor.Pages namespace Monitor.Pages
@ -14,6 +15,8 @@ namespace Monitor.Pages
public string SettingsDistribution24hChartDataJSON = ""; public string SettingsDistribution24hChartDataJSON = "";
public string SettingsDistribution3dChartDataJSON = ""; public string SettingsDistribution3dChartDataJSON = "";
private Dictionary<string, string> settingsChartColors = new Dictionary<string, string>(); private Dictionary<string, string> settingsChartColors = new Dictionary<string, string>();
public ProfitTrailerData PTData = null;
public MiscData MiscData { get; set; }
public void OnGet() public void OnGet()
{ {
@ -24,6 +27,8 @@ namespace Monitor.Pages
private void BindData() private void BindData()
{ {
PTData = this.PtDataObject;
MiscData = this.PTData.Misc;
BuildMarketsWithSingleSettings(); BuildMarketsWithSingleSettings();
BuildChartColors(); BuildChartColors();
Build24hChartData(); Build24hChartData();

View File

@ -1,174 +0,0 @@
@page
@model TransactionsModel
@{
ViewData["Title"] = "";
}
@section Styles {
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/css/tablesaw.css" rel="stylesheet" type="text/css" />
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/bootstrap-datepicker/css/bootstrap-datepicker.min.css" rel="stylesheet">
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/clockpicker/css/bootstrap-clockpicker.min.css" rel="stylesheet">
}
<div class="row">
<div class="col-md-12">
<div class="card-box">
<h4 class="m-t-0 header-title">Transactions</h4>
<p>
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.
</p>
</div>
</div>
</div>
<form class="form-horizontal m-t-20" method="post">
<div class="row">
<div class="col-md-12">
<div class="card-box">
<h4 class="m-t-0 header-title">New Transaction</h4>
<div class="form-group row">
<label class="col-md-4 col-form-label">Amount <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The amount of your transaction. Positive numbers for deposits and negative numbers for withdrawals."></i></label>
<div class="col-md-8">
<input type="text" class="form-control" name="Transaction_Amount">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 col-form-label">Date <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The date of your transaction."></i></label>
<div class="col-md-8">
<div class="input-group">
<input type="text" class="form-control" placeholder="mm/dd/yyyy" id="datepicker-autoclose" name="Transaction_Date">
<span class="input-group-addon bg-custom b-0"><i class="md md-event-note text-dark"></i></span>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 col-form-label">Time <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The time of your transaction."></i></label>
<div class="col-md-8">
<div class="input-group clockpicker m-b-20" data-placement="top" data-align="top" data-autoclose="true">
<input type="text" class="form-control" name="Transaction_Time">
<span class="input-group-addon"> <span class="md md-access-time"></span> </span>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 col-form-label"></label>
<div class="col-md-8">
<button class="btn btn-ptmagic btn-block text-uppercase waves-effect waves-light" type="submit">
Save Transaction
</button>
</div>
</div>
</div>
</div>
</div>
@if (!Model.ValidationMessage.Equals("")) {
<div class="row">
<div class="col-md-12">
<div class="card-box text-danger">
@Model.ValidationMessage
</div>
</div>
</div>
}
</form>
<div class="row">
<div class="col-md-12">
<div class="card-box">
<h4 class="m-t-0 header-title">Your Transactions</h4>
@if (Model.TransactionData.Transactions.Count > 0) {
<table class="tablesaw table m-b-0" data-tablesaw-sortable data-tablesaw-sortable-switch>
<thead>
<tr>
<th></th>
<th scope="col" data-tablesaw-sortable-col data-tablesaw-sortable-default-col>Time</th>
<th scope="col" data-tablesaw-sortable-col class="text-right">Amount</th>
<th scope="col" data-tablesaw-sortable-col>Type</th>
</tr>
</thead>
<tbody>
@foreach (Core.Main.DataObjects.PTMagicData.Transaction transaction in Model.TransactionData.Transactions) {
<tr>
<td style="width:20px;"><a href="#" class="btn-remove" data-transactionguid="@transaction.GUID"><i class="fa fa-remove text-danger"></i></a></td>
<td>@transaction.GetLocalDateTime(Model.PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset).ToShortDateString() @transaction.GetLocalDateTime(Model.PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset).ToShortTimeString()</td>
<td class="text-right text-autocolor">@transaction.Amount.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
@if (transaction.Amount > 0) {
<td class="text-success">Deposit</td>
} else {
<td class="text-danger">Withdrawal</td>
}
</tr>
}
</tbody>
</table>
} else {
<p>No transactions found.</p>
}
</div>
</div>
</div>
@section Scripts {
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw.js"></script>
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw-init.js"></script>
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/bootstrap-datepicker/js/bootstrap-datepicker.min.js"></script>
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/clockpicker/js/bootstrap-clockpicker.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip();
$('.text-autocolor').autocolor(false);
jQuery('#datepicker-autoclose').datepicker({
weekStart: 1,
autoclose: true,
todayHighlight: true
});
$('.clockpicker').clockpicker({
donetext: 'Done'
});
@if (!Model.NotifyType.Equals("") && !Model.NotifyHeadline.Equals("") && !Model.NotifyMessage.Equals("")) {
<text>
$.Notification.notify('@Model.NotifyType', 'top left', '@Model.NotifyHeadline', '@Model.NotifyMessage');
</text>
}
$('.btn-remove').click(function () {
var tGuid = $(this).data('transactionguid');
var postData = { Transaction_GUID: tGuid };
$.ajax({
type: 'POST',
url: "@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_post/RemoveTransaction",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify(postData),
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
success: function (data) {
$.Notification.notify('success', 'top left', 'Transaction removed!', 'Transaction "' + tGuid + '" was successfully removed.');
window.location = window.location.href;
},
error: function (jqxhr, errorText, thrownError) {
$.Notification.notify('error', 'top left', 'Error removing transaction "' + tGuid + '"!', 'Error message: ' + errorText);
}
});
return false;
});
});
</script>
}

View File

@ -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";
}
}
}
}
}

View File

@ -192,7 +192,7 @@
} }
// Reinstate the interval. // Reinstate the interval.
interval = setInterval(function () { loadWidgets(); }, 5000); interval = setInterval(function () { loadWidgets(); }, 3000);
}); });
}; };

View File

@ -103,7 +103,7 @@
<div class="tradingview-widget-container"> <div class="tradingview-widget-container">
<div id="tradingview_6aa22" style="height:600px;"></div> <div id="tradingview_6aa22" style="height:600px;"></div>
<div class="tradingview-widget-copyright"> <div class="tradingview-widget-copyright">
<a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform, Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, Model.DCAMarket, Model.Summary.MainMarket)" rel="noopener" target="_blank"> <a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform, Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, Model.DCAMarket, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" rel="noopener" target="_blank">
<span class="blue-text">@Model.DCAMarket</span> <span class="blue-text">chart</span> by TradingView</a> <span class="blue-text">@Model.DCAMarket</span> <span class="blue-text">chart</span> by TradingView</a>
</div> </div>
</div> </div>

View File

@ -10,6 +10,7 @@ using Core.MarketAnalyzer;
namespace Monitor.Pages { namespace Monitor.Pages {
public class BagDetailsModel : _Internal.BasePageModelSecure { public class BagDetailsModel : _Internal.BasePageModelSecure {
public ProfitTrailerData PTData = null; public ProfitTrailerData PTData = null;
public MiscData MiscData = null;
public string DCAMarket = ""; public string DCAMarket = "";
public DCALogData DCALogData = null; public DCALogData DCALogData = null;
public DateTimeOffset DateTimeNow = Constants.confMinDate; public DateTimeOffset DateTimeNow = Constants.confMinDate;
@ -23,13 +24,12 @@ namespace Monitor.Pages {
private void BindData() { private void BindData() {
DCAMarket = GetStringParameter("m", ""); DCAMarket = GetStringParameter("m", "");
PTData = this.PtDataObject; PTData = this.PtDataObject;
MiscData = this.PTData.Misc;
DCALogData = PTData.DCALog.Find(d => d.Market == DCAMarket); DCALogData = PTData.DCALog.Find(d => d.Market == DCAMarket);
// Convert local offset time to UTC // 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); DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
} }
} }

View File

@ -84,9 +84,9 @@
<tr @(lostValue ? "class=errorRow" : "" ) > <tr @(lostValue ? "class=errorRow" : "" ) >
// Market // Market
@if (mps != null && (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0)) { @if (mps != null && (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0)) {
<th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, dcaLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@dcaLogEntry.Market</a></th> <th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, dcaLogEntry.Market, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@dcaLogEntry.Market</a></th>
} else { } else {
<th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, dcaLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@dcaLogEntry.Market</a> <th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, dcaLogEntry.Market, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@dcaLogEntry.Market</a>
<i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>">
</i> </i>
</th> </th>

View File

@ -37,9 +37,9 @@
string triggerValueText = Core.ProfitTrailer.StrategyHelper.GetTriggerValueText(Model.Summary, buyLogEntry.BuyStrategies, buyLogEntry.BuyStrategy, buyLogEntry.BBTrigger, buyLogEntry.TriggerValue, 0, true); string triggerValueText = Core.ProfitTrailer.StrategyHelper.GetTriggerValueText(Model.Summary, buyLogEntry.BuyStrategies, buyLogEntry.BuyStrategy, buyLogEntry.BBTrigger, buyLogEntry.TriggerValue, 0, true);
<tr> <tr>
@if (mps != null && (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0)) { @if (mps != null && (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0)) {
<th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, buyLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@buyLogEntry.Market</a></th> <th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, buyLogEntry.Market, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@buyLogEntry.Market</a></th>
} else { } else {
<th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, buyLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@buyLogEntry.Market</a> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i></th> <th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, buyLogEntry.Market, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@buyLogEntry.Market</a> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i></th>
} }
<td class="text-autocolor">@buyLogEntry.PercChange.ToString("#,#0.00")%</td> <td class="text-autocolor">@buyLogEntry.PercChange.ToString("#,#0.00")%</td>
@if (buyDisabled) { @if (buyDisabled) {

View File

@ -10,7 +10,48 @@
} }
<div class="row"> <div class="row">
<div class="col-md-5 px-1">
<div class="col-md-3 px-1">
<div class="card-box px-1" style="height:240px;">
<div class="cdev" data-percent="100" data-duration="@Html.Raw(@Model.PTMagicConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds * 1000)" data-color="#aaa,#414d59"
title="All charts set to refresh every @Model.PTMagicConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds seconds in your general settings."></div>
@{
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"));
}
}
<div class="asset-distribution container" style="height: 100%; width: 100%;">
<div class="text-center">
<span data-toggle="tooltip" data-placement="top" title="Total current account value">TCV: <text class="text-autocolor">@totalCurrentValueString @Model.Summary.MainMarket </text> </span>
</div>
<div class="text-center">
<small>
<span data-toggle="tooltip" data-placement="top" title="Starting balance from PTM settings">Start: <text class="text-autocolor">@Model.MiscData.StartBalance </text></span>
<span data-toggle="tooltip" data-placement="top" title="TCV gain on starting balance">&emsp; Gain: <text class="text-autocolor">@Math.Round(((Model.totalCurrentValue - Model.MiscData.StartBalance) / Model.MiscData.StartBalance) * 100, 2)%</text></span>
</small>
</div>
<svg style="height:100%;width:100%"></svg>
</div>
</div>
</div>
<div class="col-md-9">
<div class="card-box px-2" style="height:240px;">
<h4 class="m-t-0 header-title"><b>TCV Trend </b><i class="fa fa-info-circle text-muted" style="font-size x-small" data-toggle="tooltip" data-placement="top" title="Data is added while the monitor is running. Currently set to show the past @Model.PTMagicConfiguration.GeneralSettings.Monitor.LiveTCVTimeframeMinutes minutes in your monitor settings."></i>
@if (!Model.TotalCurrentValueLiveChartDataJSON.Equals("")) {
<div class="TCVLive-chart">
<svg style="height:220px;width:100%"></svg>
</div>
} else {
<p>Unable to load graph, no sales data found.</p>
}
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 px-1">
<div class="card-box px-2" style="height:340px;"> <div class="card-box px-2" style="height:340px;">
<h4 class="m-t-0 m-b-20 header-title" style="display: inline;"><b>Market Trend History </b><i class="fa fa-info-circle text-muted" style="font-size x-small" data-toggle="tooltip" data-placement="top" title="@Math.Round(Model.DataHours, 1) hours of data available. Currently set to show @Model.PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours hours at @Model.PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes intervals, in general settings."></i></h4> <h4 class="m-t-0 m-b-20 header-title" style="display: inline;"><b>Market Trend History </b><i class="fa fa-info-circle text-muted" style="font-size x-small" data-toggle="tooltip" data-placement="top" title="@Math.Round(Model.DataHours, 1) hours of data available. Currently set to show @Model.PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours hours at @Model.PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes intervals, in general settings."></i></h4>
@if (!Model.TrendChartDataJSON.Equals("")) { @if (!Model.TrendChartDataJSON.Equals("")) {
@ -23,37 +64,12 @@
</div> </div>
</div> </div>
<div class="col-md-3 px-1">
<div class="card-box px-3" style="height:340px;">
<div class="cdev" data-percent="100" data-duration="@Html.Raw(@Model.PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds * 1000)" data-color="#aaa,#414d59"
title="All charts set to refresh every @Model.PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds seconds in your general settings."></div>
@{
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"));
}
}
<div id="AssetDistribution" class="container">
<div class="text-center">
<small>
<span data-toggle="tooltip" data-placement="top" title="Starting balance from PTM settings">Start: &nbsp; <text class="text-autocolor"> @Model.MiscData.StartBalance @Model.Summary.MainMarket </text></span>
<span data-toggle="tooltip" data-placement="top" title="TCV gain on starting balance"> &emsp; &emsp; Gain:&nbsp;<text class="text-autocolor">@Math.Round(((Model.totalCurrentValue - Model.MiscData.StartBalance) / Model.MiscData.StartBalance) * 100, 2)%</text></span>
</small>
</div>
<div class="text-center">
<span data-toggle="tooltip" data-placement="top" title="Total current account value">TCV: &nbsp; <text class="text-autocolor"> @totalCurrentValueString @Model.Summary.MainMarket </text> </span>
</div>
<div class="row px1">
<svg style="height:260px;width:100%"></svg>
</div>
</div>
</div>
</div>
<div class="col-md-4 px-1">
<div class="col-md-6 px-1">
@*<div class="cdev" data-percent="100" data-duration="@Html.Raw(@Model.PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds * 1000)" data-color="#aaa,#414d59"></div>*@ @*<div class="cdev" data-percent="100" data-duration="@Html.Raw(@Model.PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds * 1000)" data-color="#aaa,#414d59"></div>*@
<div class="card-box px-2" style="height:340px;"> <div class="card-box px-2" style="height:340px;">
<h4 class="m-t-0 m-b-20 header-title" style="display: inline;">Daily Profit <i class="fa fa-info-circle text-muted" style="font-size x-small" data-toggle="tooltip" data-placement="top" title="@Model.ProfitDays days of data available. Currently Set to @Model.PTMagicConfiguration.GeneralSettings.Monitor.ProfitsMaxTimeframeDays days in general settings."></i> <h4 class="m-t-0 m-b-20 header-title" style="display: inline;">Daily Profit <i class="fa fa-info-circle text-muted" style="font-size x-small" data-toggle="tooltip" data-placement="top" title="@Model.PTData.DailyPNL.Count days of data available. Currently Set to @Model.PTMagicConfiguration.GeneralSettings.Monitor.ProfitsMaxTimeframeDays days in general settings."></i>
@if (!Model.ProfitChartDataJSON.Equals("")) { @if (!Model.ProfitChartDataJSON.Equals("")) {
<div class="profit-chart"> <div class="profit-chart">
<svg style="height:300px;width:100%"></svg> <svg style="height:300px;width:100%"></svg>
@ -70,9 +86,9 @@
<div class="card-box px-3"> <div class="card-box px-3">
@* <div class="cdev" data-percent="100" data-duration="@Html.Raw(@Model.PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeco;nds * 1000)" data-color="#aaa,#414d59"></div> @* <div class="cdev" data-percent="100" data-duration="@Html.Raw(@Model.PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeco;nds * 1000)" data-color="#aaa,#414d59"></div>
<br> *@ <br> *@
<h4 class="m-t-0 m-b-20 header-title">Live Trends <h4 class="m-t-0 m-b-20 header-title">Live Market Trends
<i class="fa fa-info-circle text-muted" style="font-size small" data-toggle="tooltip" data-placement="top" title="Set to refresh every @Model.PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds seconds in general settings."></i> <i class="fa fa-info-circle text-muted" style="font-size small" data-toggle="tooltip" data-placement="top" title="Set to refresh every @Model.PTMagicConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds seconds in general settings."></i>
<small class="pull-right" style="font-size: x-small"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)MarketAnalyzer">ANALYZER</a></small> <small class="pull-right" style="font-size: small"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)MarketAnalyzer">ANALYZER</a></small>
</h4> </h4>
<table class="table table-sm"> <table class="table table-sm">
<thead> <thead>
@ -80,7 +96,7 @@
<th>Name</th> <th>Name</th>
<th class="text-right">Markets</th> <th class="text-right">Markets</th>
<th class="text-right">Timeframe</th> <th class="text-right">Timeframe</th>
<th class="text-right">Threshold&nbsp;&nbsp;<i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Pairs exceeding this threshold are excluded from the trend average."></i> <th class="text-right">Threshold&nbsp;&nbsp;<i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Pairs exceeding this threshold are considered outliers that are excluded from the trend average."></i>
</th> </th>
<th class="text-right">Change</th> <th class="text-right">Change</th>
</tr> </tr>
@ -283,13 +299,20 @@
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw.js"></script> <script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw.js"></script>
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw-init.js"></script> <script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw-init.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$('[data-toggle="tooltip"]').tooltip();
});
</script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
$(".cdev").circlos(); $(".cdev").circlos();
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
$('.text-autocolor').autocolor(false); $('.text-autocolor').autocolor(false);
var assetDistributionChart; // Keep a reference to the chart
var assetDistributionData; // Keep a reference to the data var assetDistributionData; // Keep a reference to the data
@if (!Model.AssetDistributionData.Equals("")) { @if (!Model.AssetDistributionData.Equals("")) {
@ -302,29 +325,19 @@
.labelThreshold(.1) .labelThreshold(.1)
.labelType("percent") .labelType("percent")
.donut(true) .donut(true)
.donutRatio(0.3); .donutRatio(0.3)
.margin({top: 10, bottom: 10})
.showLegend(false); // Hide the legend
assetDistributionData = @Html.Raw(Model.AssetDistributionData); assetDistributionData = @Html.Raw(Model.AssetDistributionData);
d3.select("#AssetDistribution svg") d3.select(".asset-distribution svg") // Change this line
.style('height', '90%')
.style('width', '100%')
.datum(assetDistributionData) .datum(assetDistributionData)
.transition().duration(0) .transition().duration(0)
.call(assetDistributionChart); .call(assetDistributionChart);
// Add mouseleave, and mousemove event listeners to hide tooltip
d3.select('.profit-chart').on('mouseleave', function() {
d3.select('.nvtooltip').style('opacity', 0);
});
d3.select('body').on('mousemove', function() {
var chartBounds = d3.select('.profit-chart')[0][0].getBoundingClientRect();
var mouseX = d3.event.clientX;
var mouseY = d3.event.clientY;
if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) {
d3.select('.nvtooltip').style('opacity', 0);
}
});
nv.utils.windowResize(assetDistributionChart.update); nv.utils.windowResize(assetDistributionChart.update);
return assetDistributionChart; return assetDistributionChart;
}); });
@ -336,6 +349,7 @@
<script type="text/javascript"> <script type="text/javascript">
(function ($) { (function ($) {
'use strict'; 'use strict';
// $('head').append('<style>.nv-point { stroke-width: 1px; }</style>');
$('[role="tooltip"]').remove(); $('[role="tooltip"]').remove();
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
$('.text-autocolor').autocolor(false); $('.text-autocolor').autocolor(false);
@ -349,28 +363,39 @@
trendChart = nv.models.lineChart(); trendChart = nv.models.lineChart();
var height = 300; var height = 300;
trendChart.useInteractiveGuideline(true); trendChart.useInteractiveGuideline(true);
trendChart.pointSize(0.25); // Set the point size to a smaller value
trendChart.xAxis.tickFormat(function (d) { return d3.time.format('%H:%M')(new Date(d)); }); trendChart.xAxis.tickFormat(function (d) { return d3.time.format('%H:%M')(new Date(d)); });
trendChart.yAxis.axisLabel('Trend %').tickFormat(d3.format(',.2f')); trendChart.yAxis.axisLabel('Trend %').tickFormat(d3.format(',.2f'));
trendData = @Html.Raw(Model.TrendChartDataJSON); trendData = @Html.Raw(Model.TrendChartDataJSON);
d3.select('.trend-chart svg') var svg = d3.select('.trend-chart svg').node();
d3.select(svg)
.datum(trendData) .datum(trendData)
.transition().duration(0) .transition().duration(0)
.call(trendChart); .call(trendChart);
// Add mouseleave, and mousemove event listeners to hide tooltip
d3.select('.profit-chart').on('mouseleave', function() {
d3.select('.nvtooltip').style('opacity', 0);
});
d3.select('body').on('mousemove', function() { trendChart.dispatch.on('renderEnd', function() {
var chartBounds = d3.select('.profit-chart')[0][0].getBoundingClientRect(); // Get the chart's container
var mouseX = d3.event.clientX; var container = d3.select('.trend-chart .nv-wrap.nv-lineChart .nv-linesWrap');
var mouseY = d3.event.clientY;
if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) { // Remove any existing y=0 line
d3.select('.nvtooltip').style('opacity', 0); container.selectAll('.zero-line').remove();
}
// Get the x-values of the first and last data points
var xRange = trendChart.xAxis.scale().range();
var xMin = xRange[0];
var xMax = xRange[1];
// 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', trendChart.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', trendChart.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
}); });
nv.utils.windowResize(trendChart.update); nv.utils.windowResize(trendChart.update);
return trendChart; return trendChart;
@ -390,6 +415,7 @@
var profitChart; // Keep a reference to the chart var profitChart; // Keep a reference to the chart
var profitData; // Keep a reference to the data var profitData; // Keep a reference to the data
@if (!Model.ProfitChartDataJSON.Equals("")) { @if (!Model.ProfitChartDataJSON.Equals("")) {
<text> <text>
nv.addGraph(function () { nv.addGraph(function () {
@ -406,20 +432,33 @@
.transition().duration(0) .transition().duration(0)
.call(profitChart); .call(profitChart);
// Add mouseleave, and mousemove event listeners to hide tooltip profitChart.dispatch.on('renderEnd', function() {
d3.select('.profit-chart').on('mouseleave', function() { // Get the chart's container
d3.select('.nvtooltip').style('opacity', 0); 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
window.profitChartUpdate = nv.utils.windowResize(function() { profitChart.update(); });
}
}); });
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); nv.utils.windowResize(profitChart.update);
return profitChart; return profitChart;
}); });
@ -429,29 +468,78 @@
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){ (function ($) {
var originalLeave = $.fn.tooltip.Constructor.prototype.leave; 'use strict';
$.fn.tooltip.Constructor.prototype.leave = function(obj){ $('[role="tooltip"]').remove();
var self = obj instanceof this.constructor ? $('[data-toggle="tooltip"]').tooltip();
obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type); $('.text-autocolor').autocolor(false);
var container, timeout;
originalLeave.call(this, obj); var TCVLiveChart; // Keep a reference to the chart
var TCVLiveData; // Keep a reference to the data
if(obj.currentTarget) { @if (!Model.TotalCurrentValueLiveChartDataJSON.Equals("")) {
container = $(obj.currentTarget).siblings('.tooltip'); <text>
timeout = self.timeout; nv.addGraph(function () {
container.one('mouseenter', function(){ TCVLiveChart = nv.models.lineChart();
//We entered the actual tooltip call off the dogs TCVLiveChart.useInteractiveGuideline(true);
clearTimeout(timeout); TCVLiveChart.xAxis.tickFormat(function (d) { return d3.time.format('%H:%M:%S')(new Date(d)); });
//Let's monitor tooltip content instead TCVLiveChart.yAxis.axisLabel('').tickFormat(d3.format(',.2f'));
container.one('mouseleave', function(){
$.fn.tooltip.Constructor.prototype.leave.call(self, self); TCVLiveData = @Html.Raw(Model.TotalCurrentValueLiveChartDataJSON);
});
d3.select('.TCVLive-chart svg')
.style('width', '100%')
.datum(TCVLiveData)
.transition().duration(0)
.call(TCVLiveChart);
nv.utils.windowResize(TCVLiveChart.update);
return TCVLiveChart;
}); });
</text>
} }
}; })(jQuery);
</script>
$('[data-toggle="tooltip"]').tooltip(); <script type="text/javascript">
}); function attachTooltipRemoval(chartClass) {
d3.selectAll(chartClass).on('mouseleave', function() {
var tooltip = d3.select(this).select('.nvtooltip');
if (!tooltip.empty()) {
tooltip.style('opacity', 0);
}
});
d3.selectAll(chartClass).on('mouseenter', function() {
var tooltip = d3.select(this).select('.nvtooltip');
if (!tooltip.empty()) {
tooltip.style('opacity', 1);
}
});
}
$(document).ready(function () {
// Hide all tooltips when the page is loaded
d3.selectAll('.nvtooltip').style('opacity', 0);
// Call the function for each chart
attachTooltipRemoval('.asset-distribution svg');
attachTooltipRemoval('.trend-chart svg');
attachTooltipRemoval('.profit-chart svg');
attachTooltipRemoval('.TCVLive-chart svg');
// Add other charts as needed
});
</script>
<script type="text/javascript">
window.cleanupData = function() {
TCVLiveChart = null;
TCVLiveData = null;
assetDistributionChart = null;
assetDistributionData = null;
trendChart = null;
trendData = null;
profitChart = null;
profitData = null;
};
</script> </script>

View File

@ -26,6 +26,7 @@ namespace Monitor.Pages
public DateTimeOffset DateTimeNow = Constants.confMinDate; public DateTimeOffset DateTimeNow = Constants.confMinDate;
public string AssetDistributionData = ""; public string AssetDistributionData = "";
public double totalCurrentValue = 0; public double totalCurrentValue = 0;
public string TotalCurrentValueLiveChartDataJSON { get; set; }
public void OnGet() public void OnGet()
{ {
// Initialize Config // 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); 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 // 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); DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
// Get last and current active setting // Get last and current active setting
@ -65,7 +66,108 @@ namespace Monitor.Pages
BuildMarketTrendChartData(); BuildMarketTrendChartData();
BuildAssetDistributionData(); BuildAssetDistributionData();
BuildProfitChartData(); 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<object> TotalCurrentValueLivePerIntervalList = new List<object>();
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() private void BuildMarketTrendChartData()
{ {
@ -95,8 +197,8 @@ namespace Monitor.Pages
// Get trend ticks for chart // Get trend ticks for chart
TimeSpan offset; TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); bool isNegative = MiscData.TimeZoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{ {
@ -158,8 +260,8 @@ namespace Monitor.Pages
{ {
// Get timezone offset // Get timezone offset
TimeSpan offset; TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-"); bool isNegative = MiscData.TimeZoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-'); string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset)) if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{ {

View File

@ -3,13 +3,15 @@
@{ @{
Layout = null; Layout = null;
} }
@{
<div class="row"> bool sideBySide = true;
}
<div class="@(sideBySide ? "row" : "col-md-12")">
@if (Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBuyEntries>0) @if (Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBuyEntries>0)
{ {
<div class="col-md-6 px-1"> <div class="col-md px-1">
<div class="card-box px-2"> <div class="card-box px-2">
<h4 class="m-t-0 m-b-20 header-title"><b>Possible Buys (@Model.PTData.BuyLog.Count)</b><small id="buylist-refresh-icon"></small><small class="pull-right"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)BuyAnalyzer">more</a></small></h4> <h4 class="m-t-0 m-b-20 header-title"><b>Possible Buys (@Model.PTData.BuyLog.Count)</b><small id="buylist-refresh-icon"><i class="fa fa-info-circle text-muted" style="font-size x-small" data-toggle="tooltip" data-placement="top" title="Set 'Max Dashboard Buy Entries' to zero in Monitor Settings, to hide this table."></i></small><small class="pull-right"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)BuyAnalyzer">more</a></small></h4>
@if (Model.PTData.BuyLog.Count == 0) @if (Model.PTData.BuyLog.Count == 0)
{ {
<p>Your Profit Trailer did not find anything worth buying so far.</p> <p>Your Profit Trailer did not find anything worth buying so far.</p>
@ -19,11 +21,10 @@
<table class="table table-sm m-b-0"> <table class="table table-sm m-b-0">
<thead> <thead>
<tr> <tr>
<th>Market</th> <th><strong>Market </strong><i class="fa fa-info-circle text-muted" style="font-size: x-small" data-toggle="tooltip" data-placement="top" title="Market and 24h CHANGE"></i></th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="24 Hour price trend">24H</th> <th><strong>Volume </strong><i class="fa fa-info-circle text-muted" style="font-size: x-small" data-toggle="tooltip" data-placement="top" title="24h VOLUME"></i></th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="24 Hour trading volume">Volume</th> <th><strong>Ask </strong></th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="Current ask price for this market">Ask</th> <th><strong>Buy Strategies </strong></th>
<th>Buy Strategies</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -57,13 +58,21 @@
<tr> <tr>
@if (mps == null || mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0) { @if (mps == null || mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0) {
<th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, buyLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@buyLogEntry.Market</a></th> <th>
<span style="font-size: 1.3em;"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, buyLogEntry.Market, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@buyLogEntry.Market</span>
<br><span class="text-autocolor">@Html.Raw((buyLogEntry.PercChange * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))) % </span>
</th>
} else { } else {
<th class="align-top; text-nowrap"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, buyLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@buyLogEntry.Market &nbsp;</a> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i></th> <th>
<div style="white-space: nowrap;">
<span style="font-size: 1.3em;"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, buyLogEntry.Market, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@buyLogEntry.Market</a></span>
&nbsp;<i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i>
</div>
<span class="text-autocolor">@Html.Raw((buyLogEntry.PercChange * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))) % </span>
</th>
} }
<td class="text-autocolor">@string.Format("{0}%", (buyLogEntry.PercChange * 100).ToString("#,#0.00"))</td> <td >@string.Format("{0}", (buyLogEntry.Volume24h).ToString())</td>
<td class="text">@string.Format("{0}", (buyLogEntry.Volume24h).ToString())</td> <td >@buyLogEntry.CurrentPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-left">@buyLogEntry.CurrentPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
@if (buyDisabled) { @if (buyDisabled) {
<td>@Html.Raw(buyStrategyText)</td> <td>@Html.Raw(buyStrategyText)</td>
@ -85,7 +94,7 @@
<div class="col-md px-1"> <div class="col-md px-1">
<div class="card-box px-2"> <div class="card-box px-2">
<h4 class="m-t-0 m-b-20 header-title"><b>Pairs / DCA / Pending (@Model.PTData.DCALog.Count)</b><small id="baglist-refresh-icon"></small><small class="pull-right"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)BagAnalyzer">more</a></small></h4> <h4 class="m-t-0 m-b-20 header-title"><b>Positions (@Model.PTData.DCALog.Count)</b><small id="baglist-refresh-icon"></small><small class="pull-right"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)BagAnalyzer">more</a></small></h4>
@if (Model.PTData.DCALog.Count == 0) @if (Model.PTData.DCALog.Count == 0)
{ {
@ -94,16 +103,15 @@
else else
{ {
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-sm m-b-0"> <table class="table table-sm m-b-0 table-auto-width">
<thead> <thead>
<tr> <tr>
<th>Market</th> <th><strong>Market </strong><i class="fa fa-info-circle text-muted" style="font-size: x-small" data-toggle="tooltip" data-placement="top" title="Market and 24h CHANGE"></i></th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="24 Hour Trend">24H</th> <th><strong>Cost </strong><i class="fa fa-info-circle text-muted" style="font-size: x-small" data-toggle="tooltip" data-placement="top" title="Total COST and TIME HELD"></i></th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="Total Buy Cost">Cost</th>
<th></th> <th></th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="Active Buy Strategies">DCA</th> <th><strong>DCA </strong></th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="Active Sell Strategies">Sell</th> <th><strong>Sell </strong></th>
<th class="text-left" data-toggle="tooltip" data-html="true" data-placement="top" title="Profit Target <br> Current Profit">Profit</th> <th><strong>Profit </strong><i class="fa fa-info-circle text-muted" style="font-size: x-small" data-toggle="tooltip" data-placement="top" title="Proft TARGET and CURRENT Profit"></i></th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@ -142,7 +150,6 @@
if (dcaLogEntry.SellStrategies.Count > 0) { if (dcaLogEntry.SellStrategies.Count > 0) {
isSellStrategyTrue = (dcaLogEntry.SellStrategies.FindAll(ss => !ss.IsTrue).Count == 0); isSellStrategyTrue = (dcaLogEntry.SellStrategies.FindAll(ss => !ss.IsTrue).Count == 0);
} }
string leverage = ""; string leverage = "";
double leverageValue = 1; double leverageValue = 1;
string buyStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Model.Summary, dcaLogEntry.BuyStrategies, dcaLogEntry.BuyStrategy, isBuyStrategyTrue, isTrailingBuyActive); string buyStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Model.Summary, dcaLogEntry.BuyStrategies, dcaLogEntry.BuyStrategy, isBuyStrategyTrue, isTrailingBuyActive);
@ -155,54 +162,37 @@
// Profit percentage // Profit percentage
var profitPercentage = dcaLogEntry.ProfitPercent; 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 // Render the row
if (!sellStrategyText.Contains("PENDING-BUY"))
if (!sellStrategyText.Contains("PENDING-BUY"))
{ {
<tr @(lostValue ? "class=errorRow" : "") > <tr @(lostValue ? "class=errorRow" : "") >
<!-- Market --> <!-- Market -->
<td class="align-top; text-nowrap"> <td class="align-top; text-nowrap">
<b> <b>
@if (mps == null || mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0) @if (mps == null || mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0)
{ {
<a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, dcaLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@dcaLogEntry.Market</a> <span style="font-size: 1.3em;"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, dcaLogEntry.Market, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@dcaLogEntry.Market</a></span>
} else } else
{ {
<a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, dcaLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@dcaLogEntry.Market &nbsp;</a><i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i> <span style="font-size: 1.3em;"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, dcaLogEntry.Market, Model.Summary.MainMarket, Model.PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout)" target="_blank">@dcaLogEntry.Market &nbsp;</a></span><i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i>
} }
</b> </b>
<br> <br><span class="text-autocolor">@Html.Raw((dcaLogEntry.PercChange * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))) %</span>
@bagAgeText
</td>
<!-- 24hr change --> </td>
<td class="text-autocolor">@Html.Raw((dcaLogEntry.PercChange * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))%</td>
<!-- Cost --> <!-- Cost -->
<td class="text-left">@Html.Raw(dcaLogEntry.TotalCost.ToString("#,#0.000000", new System.Globalization.CultureInfo("en-US")))</td> <td class="text-left">@Html.Raw(dcaLogEntry.TotalCost.ToString("#,#0.000000", new System.Globalization.CultureInfo("en-US")))<br><span class="text-highlight">@bagAgeText</span></td>
<!-- DCA Count --> <!-- DCA Count -->
<td class="text-right"> <td class="text-right">
@if (dcaEnabled) @if (dcaEnabled)
{ {
@if (dcaLogEntry.BoughtTimes > 0) if (dcaLogEntry.BoughtTimes > 0)
{ {
@dcaLogEntry.BoughtTimes; @dcaLogEntry.BoughtTimes;
} }
} else } else
{ {
<span data-toggle="tooltip" data-placement="top" title="DCA is disabled"><i class="fa fa-ban text-highlight"></i></span> <span data-toggle="tooltip" data-placement="top" title="DCA disabled"><i class="fa fa-ban text-highlight"></i></span>
} }
</td> </td>
<!-- DCA Strategy --> <!-- DCA Strategy -->
@ -231,10 +221,10 @@
@if ( !(sellStrategyText.Contains("WATCHMODE")) && !(sellStrategyText.Contains("PENDING"))) @if ( !(sellStrategyText.Contains("WATCHMODE")) && !(sellStrategyText.Contains("PENDING")))
{ {
double TargetGain = leverageValue * dcaLogEntry.TargetGainValue.Value; double TargetGain = leverageValue * dcaLogEntry.TargetGainValue.GetValueOrDefault();
<td>@TargetGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))% <td>@TargetGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%
<br> <br>
<div class="text-autocolor">@profitPercentage.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</div> <div class="text-autocolor" style="font-size: 1.2em; white-space: nowrap;">@profitPercentage.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</div>
</td> </td>
} }
else else
@ -242,7 +232,7 @@
<td> <td>
<div class="text-left">None</div> <div class="text-left">None</div>
<br> <br>
<div class="text-autocolor">@profitPercentage.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</div> <div class="text-autocolor">@profitPercentage.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</div>
</td> </td>
} }
@ -255,24 +245,22 @@
} }
<!-- Bag details --> <!-- Bag details -->
<td class="text-right"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/BagDetails/?m=@dcaLogEntry.Market" data-remote="false" data-toggle="modal" data-target="#dca-chart"><i class="fa fa-plus-circle"></i></a></td> <td class="text-right"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/BagDetails/?m=@dcaLogEntry.Market" data-remote="false" data-toggle="modal" data-target="#dca-chart"><i class="fa fa-plus-circle"></i></a></td>
</tr> </tr>
{ {
// Aggregate totals // Aggregate totals
double bagGain = (profitPercentage / 100) * dcaLogEntry.TotalCost; double bagGain = (profitPercentage / 100) * dcaLogEntry.TotalCost;
Model.TotalBagCost = Model.TotalBagCost + dcaLogEntry.TotalCost; Model.TotalBagCost = Model.TotalBagCost + dcaLogEntry.TotalCost;
Model.TotalBagGain = Model.TotalBagGain + bagGain; Model.TotalBagGain = Model.TotalBagGain + bagGain;
}
} }
} }
<td style="font-size: 1.2em;"><strong>Totals:</strong></td>
} <td style="font-size: 1.2em;"><strong>@Html.Raw(Model.TotalBagCost.ToString("#,#0.000000", new System.Globalization.CultureInfo("en-US")))</strong></td>
<td>Totals:</td> <td></td>
<td></td> <td></td>
<td>@Html.Raw(Model.TotalBagCost.ToString("#,#0.000000", new System.Globalization.CultureInfo("en-US")))</td> <td></td>
<td></td> <td class="text-autocolor; font-size: 1.2em;"><strong>@Html.Raw((((Model.TotalBagGain) / Model.TotalBagCost) * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))) %</strong></td>
<td></td> <td></td>
<td></td>
<td class="text-autocolor">@Html.Raw((((Model.TotalBagGain) / Model.TotalBagCost) * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))%</td>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -1,15 +1,16 @@
using System; using System;
using Core.Main; using Core.Main;
using Core.Main.DataObjects; using Core.Main.DataObjects;
using Core.Main.DataObjects.PTMagicData;
namespace Monitor.Pages { namespace Monitor.Pages {
public class DashboardTopModel : _Internal.BasePageModelSecureAJAX { public class DashboardTopModel : _Internal.BasePageModelSecureAJAX {
public ProfitTrailerData PTData = null; public ProfitTrailerData PTData = null;
public MiscData MiscData = null;
public DateTimeOffset DateTimeNow = Constants.confMinDate; public DateTimeOffset DateTimeNow = Constants.confMinDate;
public void OnGet() { public void OnGet() {
// Initialize Config // Initialize Config
base.Init(); base.Init();
BindData(); BindData();
} }
public double TotalBagCost = 0; public double TotalBagCost = 0;
@ -17,9 +18,9 @@ namespace Monitor.Pages {
public double TotalBagGain = 0; public double TotalBagGain = 0;
private void BindData() { private void BindData() {
PTData = this.PtDataObject; PTData = this.PtDataObject;
MiscData = this.PTData.Misc;
// Convert local offset time to UTC // 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); DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
} }
} }

View File

@ -36,12 +36,7 @@
@if (!Model.GlobalSetting.SettingName.StartsWith("Default", StringComparison.InvariantCultureIgnoreCase)) { @if (!Model.GlobalSetting.SettingName.StartsWith("Default", StringComparison.InvariantCultureIgnoreCase)) {
<div class="form-group row"> <div class="form-group row">
<label class="col-md-4 col-form-label">Trigger Connection <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Define if triggers will be connected by AND or OR"></i></label> <label class="col-md-4 col-form-label">Trigger Connection <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Define if triggers will be connected by AND or OR"></i></label>
<div class="col-md-8"> <input type="text" name="MarketAnalyzer_GlobalSetting_@(Model.SettingName)|TriggerConnection" class="form-control" value="@Model.GlobalSetting.TriggerConnection" />
<select name="MarketAnalyzer_GlobalSetting_@(Model.SettingName)|TriggerConnection" class="form-control">
<option selected="@(Model.GlobalSetting.TriggerConnection.Equals("AND", StringComparison.InvariantCultureIgnoreCase))">AND</option>
<option selected="@(Model.GlobalSetting.TriggerConnection.Equals("OR", StringComparison.InvariantCultureIgnoreCase))">OR</option>
</select>
</div>
</div> </div>
<hr /> <hr />

View File

@ -78,7 +78,7 @@
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-md-4 col-form-label">Trend Threshold <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Exclude coins above/below this value when calculing market trend average."></i></label> <label class="col-md-4 col-form-label">Trend Threshold <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Exclude coins above/below this value as outliers, when calculing market trend average."></i></label>
<div class="col-md-8"> <div class="col-md-8">
<input type="text" class="form-control" name="MarketAnalyzer_MarketTrend_@(Model.MarketTrendName)|TrendThreshold" value="@Model.MarketTrend.TrendThreshold.ToString()"> <input type="text" class="form-control" name="MarketAnalyzer_MarketTrend_@(Model.MarketTrendName)|TrendThreshold" value="@Model.MarketTrend.TrendThreshold.ToString()">
<span class="help-block"><small>Leave empty to exclude none</small></span> <span class="help-block"><small>Leave empty to exclude none</small></span>

View File

@ -29,20 +29,14 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-md-4 col-form-label">Trigger Connection <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Define if triggers will be connected by AND or OR"></i></label> <label class="col-md-4 col-form-label">Trigger Connection <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Define if triggers will be connected by AND or OR"></i></label>
<div class="col-md-8"> <div class="col-md-8">
<select name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|TriggerConnection" class="form-control"> <input type="text" name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|TriggerConnection" class="form-control" value="@Model.SingleMarketSetting.TriggerConnection" />
<option selected="@(Model.SingleMarketSetting.TriggerConnection.Equals("AND", StringComparison.InvariantCultureIgnoreCase))">AND</option>
<option selected="@(Model.SingleMarketSetting.TriggerConnection.Equals("OR", StringComparison.InvariantCultureIgnoreCase))">OR</option>
</select>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-md-4 col-form-label">Off Trigger Connection <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Define if off triggers will be connected by AND or OR"></i></label> <label class="col-md-4 col-form-label">Off Trigger Connection <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Define if off triggers will be connected by AND or OR"></i></label>
<div class="col-md-8"> <div class="col-md-8">
<select name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|OffTriggerConnection" class="form-control"> <input type="text" name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|OffTriggerConnection" class="form-control" value="@Model.SingleMarketSetting.OffTriggerConnection" />
<option selected="@(Model.SingleMarketSetting.OffTriggerConnection.Equals("AND", StringComparison.InvariantCultureIgnoreCase))">AND</option>
<option selected="@(Model.SingleMarketSetting.OffTriggerConnection.Equals("OR", StringComparison.InvariantCultureIgnoreCase))">OR</option>
</select>
</div> </div>
</div> </div>

View File

@ -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."; ptMagicHealthTooltip = "PT Magic seems to have problems, check the logs! Time elapsed since last run: " + Math.Round(elapsedSecondsSinceRuntime / 60, 1) + " mins.";
healthIconColor = "text-danger"; healthIconColor = "text-danger";
} }
if (Model.IsAnalyzerRunning()) {
ptMagicHealthIcon = "fa-cog fa-spin";
}
} }
<div class="card-box card-box-mini card-box-ptmagic-outlined @globalIconColor"> <div class="card-box card-box-mini card-box-ptmagic-outlined @globalIconColor">

View File

@ -1,16 +1,23 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Http;
using Core.Main;
using Core.Main.DataObjects; using Core.Main.DataObjects;
using Core.Main.DataObjects.PTMagicData; using Core.Main.DataObjects.PTMagicData;
using Core.MarketAnalyzer; using System.IO;
using Microsoft.AspNetCore.Hosting;
using System.Threading;
namespace Monitor.Pages { namespace Monitor.Pages {
public class TickerWidgetsModel : _Internal.BasePageModelSecureAJAX { public class TickerWidgetsModel : _Internal.BasePageModelSecureAJAX {
public ProfitTrailerData PTData = null; public ProfitTrailerData PTData = null;
public List<string> MarketsWithSingleSettings = new List<string>(); public List<string> MarketsWithSingleSettings = new List<string>();
private readonly IWebHostEnvironment _hostingEnvironment;
private Mutex mutex = new Mutex(false, "analyzerStateMutex");
public TickerWidgetsModel(IWebHostEnvironment hostingEnvironment) // Add this constructor
{
_hostingEnvironment = hostingEnvironment;
}
public void OnGet() { public void OnGet() {
// Initialize Config // Initialize Config
@ -18,6 +25,33 @@ namespace Monitor.Pages {
BindData(); 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() { private void BindData() {
PTData = this.PtDataObject; PTData = this.PtDataObject;

View File

@ -22,4 +22,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.8.0" />
</ItemGroup>
</Project> </Project>

View File

@ -6,7 +6,7 @@ using Core.Helper;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
[assembly: AssemblyVersion("2.7.1")] [assembly: AssemblyVersion("2.8.4")]
[assembly: AssemblyProduct("PT Magic")] [assembly: AssemblyProduct("PT Magic")]
namespace PTMagic namespace PTMagic

View File

@ -1,7 +1,6 @@
// //
// The settings below offer a basic example of some of the options available when using PTMagic. // 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 // Always test your PTMagic settings by running a Profit Trailer bot in TESTMODE, to make sure
// it is performing as you expect. // it is performing as you expect.
// //
@ -11,19 +10,22 @@
{ {
"AnalyzerSettings": { "AnalyzerSettings": {
"MarketAnalyzer": { "MarketAnalyzer": {
"StoreDataMaxHours": 48, // Number of hours to store market data "StoreDataMaxHours": 48, // Number of hours to store market data
"IntervalMinutes": 2, // Interval in minutes for PTMagic to check market trends and triggers "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 "ExcludeMainCurrency": true, // Excludes the main currency (for example BTC, if you are trading against BTC) from market trend analysis
"MarketTrends": [ "MarketTrends": [
{ {
"Name": "1h", // UNIQUE market trend name (to be referenced by your triggers below) "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) "Platform": "Exchange", // Platform to grab prices from (Allowed values are: CoinMarketCap, Exchange)
"MaxMarkets": 50, // Number of markets/pairs to analyze sorted by 24h volume "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) "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. "TrendCurrency": "Market", // Trend Currency to build the trend against. If set to "Fiat", the trend will
"TrendThreshold": 15, // Any coin that is above 15% or below -15% for this timeframe will not be used when calculating the market average. // take the USD value of your main currency into account to build the trend.
"DisplayGraph": false, // Use this trend in the graph on the PTM Monitor dashboard and market analyzer? // "Market" will build a trend against your base currency, such as BTC or USDT.
"DisplayOnMarketAnalyzerList": false // Disply this trend on the PTM Monitor market analyzer? "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", "Name": "6h",
@ -32,8 +34,8 @@
"TrendMinutes": 360, "TrendMinutes": 360,
"TrendCurrency": "Market", "TrendCurrency": "Market",
"TrendThreshold": 30, "TrendThreshold": 30,
"DisplayGraph": true, "DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true "DisplayOnMarketAnalyzerList": true
}, },
{ {
"Name": "12h", "Name": "12h",
@ -42,8 +44,8 @@
"TrendMinutes": 720, "TrendMinutes": 720,
"TrendCurrency": "Market", "TrendCurrency": "Market",
"TrendThreshold": 50, "TrendThreshold": 50,
"DisplayGraph": true, "DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true "DisplayOnMarketAnalyzerList": true
}, },
{ {
"Name": "24h", "Name": "24h",
@ -52,73 +54,89 @@
"TrendMinutes": 1440, "TrendMinutes": 1440,
"TrendCurrency": "Market", "TrendCurrency": "Market",
"TrendThreshold": 75, "TrendThreshold": 75,
"DisplayGraph": true, "DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true "DisplayOnMarketAnalyzerList": true
} }
] ]
}, },
// ================================ GLOBAL SETTINGS ================================ // ================================ GLOBAL SETTINGS ================================
// //
"GlobalSettings": [ // Global settings for Profit Trailer properties "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 "SettingName": "EndOfTheWorld", // ANY UNIQUE name of your setting
"TriggerConnection": "AND", // Define if triggers will be connected by AND or OR "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 //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 "Tag": "A", // OPTIONAL: Give your triggers Tags, so you can use more robust boolean logic, such as: (A && B) || (C && D)
"MaxChange": 0 // The maximum value for this trigger to be true. (Any value below "0" will trigger this) "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", "MarketTrendName": "12h",
"MaxChange": -2 "MaxChange": -2
}, },
{ {
"Tag": "C",
"MarketTrendName": "24h", "MarketTrendName": "24h",
"MaxChange": -5 "MaxChange": -5
} }
], ],
"PairsProperties": { // Changes you wish to make to your PAIRS.properties settings "PairsProperties": { // Properties for PAIRS.PROPERTIES
// Any valid setting from https://wiki.profittrailer.com/en/config can be used here. // 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. // 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_sell_only_mode_enabled": true,
"DEFAULT_trailing_profit_OFFSETPERCENT": -50 "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 "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -75
},
"IndicatorsProperties": { // Changes you wish to make to your INDICATORS.properties settings
} }
}, },
// ----------------------------- // -----------------------------
{ {
"SettingName": "TankingDown", "SettingName": "TankingDown",
"TriggerConnection": "AND", "TriggerConnection": "AND", // You can use complex boolean logic for some settings, and not others
"Triggers": [ "Triggers": [
{ {
"MarketTrendName": "1h", "MarketTrendName": "1h",
"MaxChange": 0,
"MinChange": -5 // You can use Maxchange and Minchange together to create a range.
},
{
"MarketTrendName": "12h",
"MaxChange": 0 "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": { "PairsProperties": {
"max_trading_pairs_OFFSET": -2, "max_trading_pairs_OFFSET": -2,
"DEFAULT_min_buy_volume_OFFSETPERCENT": 100, "DEFAULT_min_buy_volume_OFFSETPERCENT": 100,
//"DEFAULT_initial_cost_OFFSETPERCENT": -50, //"DEFAULT_initial_cost_OFFSETPERCENT": -50,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -50, //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -50,
"DEFAULT_trailing_buy_OFFSETPERCENT": 25, "DEFAULT_trailing_buy_OFFSETPERCENT": 25,
"DEFAULT_trailing_profit_OFFSETPERCENT": -25 "DEFAULT_trailing_profit_OFFSETPERCENT": -25
}, },
"DCAProperties": { "DCAProperties": {
//"DEFAULT_DCA_rebuy_timeout_OFFSETPERCENT": 100, //"DEFAULT_DCA_rebuy_timeout_OFFSETPERCENT": 100,
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 25, "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 25,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -50 "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -50
}, },
"IndicatorsProperties": { "IndicatorsProperties": {
} }
}, },
// ----------------------------- // -----------------------------
{ {
"SettingName": "BearSighted", "SettingName": "BearSighted",
"TriggerConnection": "AND", "TriggerConnection": "AND",
@ -139,16 +157,16 @@
], ],
"PairsProperties": { "PairsProperties": {
"max_trading_pairs_OFFSET": -1, "max_trading_pairs_OFFSET": -1,
//"DEFAULT_initial_cost_OFFSETPERCENT": -25, //"DEFAULT_initial_cost_OFFSETPERCENT": -25,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -25, //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -25,
"DEFAULT_trailing_buy_OFFSETPERCENT": 10, "DEFAULT_trailing_buy_OFFSETPERCENT": 10,
"DEFAULT_trailing_profit_OFFSETPERCENT": -10 "DEFAULT_trailing_profit_OFFSETPERCENT": -10
}, },
"DCAProperties": { "DCAProperties": {
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 10, "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": { "PairsProperties": {
"max_trading_pairs_OFFSET": 1, "max_trading_pairs_OFFSET": 1,
//"DEFAULT_initial_cost_OFFSETPERCENT": 10, //"DEFAULT_initial_cost_OFFSETPERCENT": 10,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 10, //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 10,
"DEFAULT_trailing_buy_OFFSETPERCENT": -10, "DEFAULT_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_A_sell_value_OFFSETPERCENT": 10 "DEFAULT_A_sell_value_OFFSETPERCENT": 10
}, },
"DCAProperties": { "DCAProperties": {
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -10, "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 10, "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 10,
}, },
"IndicatorsProperties": { "IndicatorsProperties": {
} }
}, },
// ----------------------------- // -----------------------------
{ {
"SettingName": "ToTheMoon", "SettingName": "ToTheMoon",
"TriggerConnection": "AND", "TriggerConnection": "AND",
@ -204,8 +222,8 @@
], ],
"PairsProperties": { "PairsProperties": {
"max_trading_pairs_OFFSET": 2, "max_trading_pairs_OFFSET": 2,
//"DEFAULT_initial_cost_OFFSETPERCENT": 20, //"DEFAULT_initial_cost_OFFSETPERCENT": 20,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 20, //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 20,
"DEFAULT_trailing_buy_OFFSETPERCENT": -10, "DEFAULT_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_A_sell_value_OFFSETPERCENT": 20 "DEFAULT_A_sell_value_OFFSETPERCENT": 20
}, },
@ -213,7 +231,7 @@
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -20, "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -20,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 20, "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 20,
}, },
"IndicatorsProperties": { "IndicatorsProperties": {
} }
}, },
// ----------------------------- // -----------------------------
@ -230,49 +248,67 @@
} }
} }
], ],
//
// ================================ COIN-SPECIFIC SETTINGS ================================ // ================================ COIN-SPECIFIC SETTINGS ================================
// //
"SingleMarketSettings": [ // Single market/pair settings for Profit Trailer properties "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.
// 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
// A variety of SMS can be employed to check for long-term down trends, sideways trends, over-extended uptrends, etc. // can be applied to a single coin. This can allow for more complex logic and settings.
// If more than one SMS is true, the settings of the last applied SMS over-rides any prior SMS // 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", "SettingName": "BlacklistCoins",
"TriggerConnection": "OR", "StopProcessWhenTriggered": true,
//"StopProcessWhenTriggered": true, // No SMS after this will be analyzed or applied if this SMS is true "TriggerConnection": "OR", // Just like Global Settings, you can use complex boolean logic for some settings, and not others
//"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
"Triggers": [ "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", "MarketTrendName": "1h",
"MarketTrendRelation": "Relative", // Relative = The single market trend is compared to the overall trend of the entire market "MarketTrendRelation": "Relative", // The relation of the single market trend. Relative = The trend of the coin market
// Absolute = The Single market trend is considered on its own // 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 "MinChange": 8
}, },
{ {
"Tag": "B",
"MarketTrendName": "12h", "MarketTrendName": "12h",
"MarketTrendRelation": "Relative", "MarketTrendRelation": "Relative",
"MinChange": 10 "MinChange": 10
}, },
{ {
"Tag": "C",
"MarketTrendName": "24h", "MarketTrendName": "24h",
"MarketTrendRelation": "Relative", "MarketTrendRelation": "Relative",
"MinChange": 12 "MinChange": 12
} }
], ],
"OffTriggers": [ "OffTriggers": [
{ {
"HoursSinceTriggered": 3 // Any coin that triggers this setting, will remain under this setting "HoursSinceTriggered": 3 // Any coin that triggers this setting, will remain under this setting
// for 3 hours, since the last time it triggered. // for 3 hours, since the last time it triggered.
} }
], ],
"PairsProperties": { "PairsProperties": {
"DEFAULT_sell_only_mode_enabled": "true", "DEFAULT_sell_only_mode_enabled": true,
"DEFAULT_DCA_enabled": "false" "DEFAULT_DCA_enabled": false
} }
}, },
// -----------------------------
{ {
"SettingName": "FreefallBlock", "SettingName": "FreefallBlock",
"TriggerConnection": "OR", "TriggerConnection": "OR",
@ -283,14 +319,14 @@
"MaxChange": -5 "MaxChange": -5
} }
], ],
"OffTriggers": [ "OffTriggers": [
{ {
"HoursSinceTriggered": 1 "HoursSinceTriggered": 1
} }
], ],
"PairsProperties": { "PairsProperties": {
"DEFAULT_sell_only_mode_enabled": "true", "DEFAULT_sell_only_mode_enabled": true,
"DEFAULT_DCA_enabled": "false" "DEFAULT_DCA_enabled": false
} }
} }
] ]

View File

@ -2,33 +2,44 @@
"GeneralSettings": { "GeneralSettings": {
"Application": { "Application": {
"IsEnabled": true, // Enables the PTMagic bot (needs restart to take effect) "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) "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) "ProfitTrailerLicenseXtra": "", // Licenses for additional bots for PTM to update (optional - comma separated list)
"ProfitTrailerServerAPIToken": "", //Your Profit Trailer Server API Token "ProfitTrailerServerAPIToken": "", //Your Profit Trailer Server API Token
"ProfitTrailerMonitorURL": "http://localhost:8081/", // The URL to your profit trailer monitor (needed to change your settings) "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) "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) "ProfitTrailerDefaultSettingName": "default", // Your Profit Trailer default setting name (needed to change your settings)
"Exchange": "Bittrex", // The exchange your are running Profit Trailer on "Exchange": "BinanceFutures", // 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
"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 // 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 "InstanceName": "MyBTCbot", // The name of the instance of this bot. This will be used in your monitor and your Telegram messages.
//"FreeCurrencyConverterAPIKey": "" // If "MainFiatCurrency" above is anything other than USD, you must obtain an API key from https://free.currencyconverterapi.com/free-api-key //In case you are running more than one bot, you may set different names to separate them
"CoinMarketCapAPIKey": "", //CoinMarketCap Api
}, },
"Monitor": { "Monitor": {
"IsPasswordProtected": true, // Defines if your monitor will be asking to setup a password on its first start "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 "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 monitor on "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
"RootUrl": "/", // The root Url of your monitor "AnalyzerChart": "", // By default the chart on the Market Analyzer page will use your market currency against USD.
"AnalyzerChart": "", // By default the chart on the Market Analyzer page will use your default currency against USD. You can change that here. (eg., BTCEUR) //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 "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 "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 "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 "DashboardChartsRefreshSeconds": 30, // The refresh interval of your dashboard charts in seconds
"LinkPlatform": "TradingView", // The platform to which the pair name will link if you click on it "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 "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 "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 "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 "TvStudyA": "BB@tv-basicstudies", // See available STUDIES at https://www.tradingview.com/wiki/Widget:TradingView_Widget
"TvStudyB": "", "TvStudyB": "",
"TvStudyC": "", "TvStudyC": "",

View File

@ -1,7 +1,6 @@
// //
// The settings below offer a basic example of some of the options available when using PTMagic. // 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 // Always test your PTMagic settings by running a Profit Trailer bot in TESTMODE, to make sure
// it is performing as you expect. // it is performing as you expect.
// //
@ -11,19 +10,22 @@
{ {
"AnalyzerSettings": { "AnalyzerSettings": {
"MarketAnalyzer": { "MarketAnalyzer": {
"StoreDataMaxHours": 48, // Number of hours to store market data "StoreDataMaxHours": 48, // Number of hours to store market data
"IntervalMinutes": 2, // Interval in minutes for PTMagic to check market trends and triggers "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 "ExcludeMainCurrency": true, // Excludes the main currency (for example BTC, if you are trading against BTC) from market trend analysis
"MarketTrends": [ "MarketTrends": [
{ {
"Name": "1h", // UNIQUE market trend name (to be referenced by your triggers below) "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) "Platform": "Exchange", // Platform to grab prices from (Allowed values are: CoinMarketCap, Exchange)
"MaxMarkets": 50, // Number of markets/pairs to analyze sorted by 24h volume "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) "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. "TrendCurrency": "Market", // Trend Currency to build the trend against. If set to "Fiat", the trend will
"TrendThreshold": 15, // Any coin that is above 15% or below -15% for this timeframe will not be used when calculating the market average. // take the USD value of your main currency into account to build the trend.
"DisplayGraph": false, // Use this trend in the graph on the PTM Monitor dashboard and market analyzer? // "Market" will build a trend against your base currency, such as BTC or USDT.
"DisplayOnMarketAnalyzerList": false // Disply this trend on the PTM Monitor market analyzer? "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", "Name": "6h",
@ -32,8 +34,8 @@
"TrendMinutes": 360, "TrendMinutes": 360,
"TrendCurrency": "Market", "TrendCurrency": "Market",
"TrendThreshold": 30, "TrendThreshold": 30,
"DisplayGraph": true, "DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true "DisplayOnMarketAnalyzerList": true
}, },
{ {
"Name": "12h", "Name": "12h",
@ -42,8 +44,8 @@
"TrendMinutes": 720, "TrendMinutes": 720,
"TrendCurrency": "Market", "TrendCurrency": "Market",
"TrendThreshold": 50, "TrendThreshold": 50,
"DisplayGraph": true, "DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true "DisplayOnMarketAnalyzerList": true
}, },
{ {
"Name": "24h", "Name": "24h",
@ -52,73 +54,89 @@
"TrendMinutes": 1440, "TrendMinutes": 1440,
"TrendCurrency": "Market", "TrendCurrency": "Market",
"TrendThreshold": 75, "TrendThreshold": 75,
"DisplayGraph": true, "DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true "DisplayOnMarketAnalyzerList": true
} }
] ]
}, },
// ================================ GLOBAL SETTINGS ================================ // ================================ GLOBAL SETTINGS ================================
// //
"GlobalSettings": [ // Global settings for Profit Trailer properties "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 "SettingName": "EndOfTheWorld", // ANY UNIQUE name of your setting
"TriggerConnection": "AND", // Define if triggers will be connected by AND or OR "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 //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 "Tag": "A", // OPTIONAL: Give your triggers Tags, so you can use more robust boolean logic, such as: (A && B) || (C && D)
"MaxChange": 0 // The maximum value for this trigger to be true. (Any value below "0" will trigger this) "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", "MarketTrendName": "12h",
"MaxChange": -2 "MaxChange": -2
}, },
{ {
"Tag": "C",
"MarketTrendName": "24h", "MarketTrendName": "24h",
"MaxChange": -5 "MaxChange": -5
} }
], ],
"PairsProperties": { // Changes you wish to make to your PAIRS.properties settings "PairsProperties": { // Properties for PAIRS.PROPERTIES
// Any valid setting from https://wiki.profittrailer.com/en/config can be used here. // 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. // 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_sell_only_mode_enabled": true,
"DEFAULT_trailing_profit_OFFSETPERCENT": -50 "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 "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -75
},
"IndicatorsProperties": { // Changes you wish to make to your INDICATORS.properties settings
} }
}, },
// ----------------------------- // -----------------------------
{ {
"SettingName": "TankingDown", "SettingName": "TankingDown",
"TriggerConnection": "AND", "TriggerConnection": "AND", // You can use complex boolean logic for some settings, and not others
"Triggers": [ "Triggers": [
{ {
"MarketTrendName": "1h", "MarketTrendName": "1h",
"MaxChange": 0,
"MinChange": -5 // You can use Maxchange and Minchange together to create a range.
},
{
"MarketTrendName": "12h",
"MaxChange": 0 "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": { "PairsProperties": {
"max_trading_pairs_OFFSET": -2, "max_trading_pairs_OFFSET": -2,
"DEFAULT_min_buy_volume_OFFSETPERCENT": 100, "DEFAULT_min_buy_volume_OFFSETPERCENT": 100,
//"DEFAULT_initial_cost_OFFSETPERCENT": -50, //"DEFAULT_initial_cost_OFFSETPERCENT": -50,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -50, //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -50,
"DEFAULT_trailing_buy_OFFSETPERCENT": 25, "DEFAULT_trailing_buy_OFFSETPERCENT": 25,
"DEFAULT_trailing_profit_OFFSETPERCENT": -25 "DEFAULT_trailing_profit_OFFSETPERCENT": -25
}, },
"DCAProperties": { "DCAProperties": {
//"DEFAULT_DCA_rebuy_timeout_OFFSETPERCENT": 100, //"DEFAULT_DCA_rebuy_timeout_OFFSETPERCENT": 100,
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 25, "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 25,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -50 "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -50
}, },
"IndicatorsProperties": { "IndicatorsProperties": {
} }
}, },
// ----------------------------- // -----------------------------
{ {
"SettingName": "BearSighted", "SettingName": "BearSighted",
"TriggerConnection": "AND", "TriggerConnection": "AND",
@ -139,16 +157,16 @@
], ],
"PairsProperties": { "PairsProperties": {
"max_trading_pairs_OFFSET": -1, "max_trading_pairs_OFFSET": -1,
//"DEFAULT_initial_cost_OFFSETPERCENT": -25, //"DEFAULT_initial_cost_OFFSETPERCENT": -25,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -25, //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -25,
"DEFAULT_trailing_buy_OFFSETPERCENT": 10, "DEFAULT_trailing_buy_OFFSETPERCENT": 10,
"DEFAULT_trailing_profit_OFFSETPERCENT": -10 "DEFAULT_trailing_profit_OFFSETPERCENT": -10
}, },
"DCAProperties": { "DCAProperties": {
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 10, "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": { "PairsProperties": {
"max_trading_pairs_OFFSET": 1, "max_trading_pairs_OFFSET": 1,
//"DEFAULT_initial_cost_OFFSETPERCENT": 10, //"DEFAULT_initial_cost_OFFSETPERCENT": 10,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 10, //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 10,
"DEFAULT_trailing_buy_OFFSETPERCENT": -10, "DEFAULT_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_A_sell_value_OFFSETPERCENT": 10 "DEFAULT_A_sell_value_OFFSETPERCENT": 10
}, },
"DCAProperties": { "DCAProperties": {
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -10, "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 10, "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 10,
}, },
"IndicatorsProperties": { "IndicatorsProperties": {
} }
}, },
// ----------------------------- // -----------------------------
{ {
"SettingName": "ToTheMoon", "SettingName": "ToTheMoon",
"TriggerConnection": "AND", "TriggerConnection": "AND",
@ -204,8 +222,8 @@
], ],
"PairsProperties": { "PairsProperties": {
"max_trading_pairs_OFFSET": 2, "max_trading_pairs_OFFSET": 2,
//"DEFAULT_initial_cost_OFFSETPERCENT": 20, //"DEFAULT_initial_cost_OFFSETPERCENT": 20,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 20, //"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 20,
"DEFAULT_trailing_buy_OFFSETPERCENT": -10, "DEFAULT_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_A_sell_value_OFFSETPERCENT": 20 "DEFAULT_A_sell_value_OFFSETPERCENT": 20
}, },
@ -213,7 +231,7 @@
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -20, "DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -20,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 20, "DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 20,
}, },
"IndicatorsProperties": { "IndicatorsProperties": {
} }
}, },
// ----------------------------- // -----------------------------
@ -230,49 +248,67 @@
} }
} }
], ],
//
// ================================ COIN-SPECIFIC SETTINGS ================================ // ================================ COIN-SPECIFIC SETTINGS ================================
// //
"SingleMarketSettings": [ // Single market/pair settings for Profit Trailer properties "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.
// 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
// A variety of SMS can be employed to check for long-term down trends, sideways trends, over-extended uptrends, etc. // can be applied to a single coin. This can allow for more complex logic and settings.
// If more than one SMS is true, the settings of the last applied SMS over-rides any prior SMS // 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", "SettingName": "BlacklistCoins",
"TriggerConnection": "OR", "StopProcessWhenTriggered": true,
//"StopProcessWhenTriggered": true, // No SMS after this will be analyzed or applied if this SMS is true "TriggerConnection": "OR", // Just like Global Settings, you can use complex boolean logic for some settings, and not others
//"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
"Triggers": [ "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", "MarketTrendName": "1h",
"MarketTrendRelation": "Relative", // Relative = The single market trend is compared to the overall trend of the entire market "MarketTrendRelation": "Relative", // The relation of the single market trend. Relative = The trend of the coin market
// Absolute = The Single market trend is considered on its own // 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 "MinChange": 8
}, },
{ {
"Tag": "B",
"MarketTrendName": "12h", "MarketTrendName": "12h",
"MarketTrendRelation": "Relative", "MarketTrendRelation": "Relative",
"MinChange": 10 "MinChange": 10
}, },
{ {
"Tag": "C",
"MarketTrendName": "24h", "MarketTrendName": "24h",
"MarketTrendRelation": "Relative", "MarketTrendRelation": "Relative",
"MinChange": 12 "MinChange": 12
} }
], ],
"OffTriggers": [ "OffTriggers": [
{ {
"HoursSinceTriggered": 3 // Any coin that triggers this setting, will remain under this setting "HoursSinceTriggered": 3 // Any coin that triggers this setting, will remain under this setting
// for 3 hours, since the last time it triggered. // for 3 hours, since the last time it triggered.
} }
], ],
"PairsProperties": { "PairsProperties": {
"DEFAULT_sell_only_mode_enabled": "true", "DEFAULT_sell_only_mode_enabled": true,
"DEFAULT_DCA_enabled": "false" "DEFAULT_DCA_enabled": false
} }
}, },
// -----------------------------
{ {
"SettingName": "FreefallBlock", "SettingName": "FreefallBlock",
"TriggerConnection": "OR", "TriggerConnection": "OR",
@ -283,14 +319,14 @@
"MaxChange": -5 "MaxChange": -5
} }
], ],
"OffTriggers": [ "OffTriggers": [
{ {
"HoursSinceTriggered": 1 "HoursSinceTriggered": 1
} }
], ],
"PairsProperties": { "PairsProperties": {
"DEFAULT_sell_only_mode_enabled": "true", "DEFAULT_sell_only_mode_enabled": true,
"DEFAULT_DCA_enabled": "false" "DEFAULT_DCA_enabled": false
} }
} }
] ]

View File

@ -2,33 +2,44 @@
"GeneralSettings": { "GeneralSettings": {
"Application": { "Application": {
"IsEnabled": true, // Enables the PTMagic bot (needs restart to take effect) "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) "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) "ProfitTrailerLicenseXtra": "", // Licenses for additional bots for PTM to update (optional - comma separated list)
"ProfitTrailerServerAPIToken": "", //Your Profit Trailer Server API Token "ProfitTrailerServerAPIToken": "", //Your Profit Trailer Server API Token
"ProfitTrailerMonitorURL": "http://localhost:8081/", // The URL to your profit trailer monitor (needed to change your settings) "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) "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) "ProfitTrailerDefaultSettingName": "default", // Your Profit Trailer default setting name (needed to change your settings)
"Exchange": "Bittrex", // The exchange your are running Profit Trailer on "Exchange": "BinanceFutures", // 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
"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 // 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 "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 "CoinMarketCapAPIKey": "", //CoinMarketCap Api
}, },
"Monitor": { "Monitor": {
"IsPasswordProtected": true, // Defines if your monitor will be asking to setup a password on its first start "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 "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 monitor on "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
"RootUrl": "/", // The root Url of your monitor "AnalyzerChart": "", // By default the chart on the Market Analyzer page will use your market currency against USD.
"AnalyzerChart": "", // By default the chart on the Market Analyzer page will use your default currency against USD. You can change that here. (eg., BTCEUR) //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 "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 "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 "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 "DashboardChartsRefreshSeconds": 30, // The refresh interval of your dashboard charts in seconds
"LinkPlatform": "TradingView", // The platform to which the pair name will link if you click on it "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 "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 "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 "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 "TvStudyA": "BB@tv-basicstudies", // See available STUDIES at https://www.tradingview.com/wiki/Widget:TradingView_Widget
"TvStudyB": "", "TvStudyB": "",
"TvStudyC": "", "TvStudyC": "",