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

11
.vscode/tasks.json vendored
View File

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

View File

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

View File

@ -42,7 +42,7 @@ namespace Core.Main.DataObjects.PTMagicData
public int FloodProtectionMinutes { get; set; } = 15;
public string Exchange { get; set; }
public string InstanceName { get; set; } = "PT Magic";
public string TimezoneOffset { get; set; } = "+0:00";
//public string TimezoneOffset { get; set; } = "+0:00";
public string CoinMarketCapAPIKey { get; set; }
//public string FreeCurrencyConverterAPIKey { get; set; }
}
@ -55,13 +55,13 @@ namespace Core.Main.DataObjects.PTMagicData
public bool OpenBrowserOnStart { get; set; } = false;
public int Port { get; set; } = 5000;
public string AnalyzerChart { get; set; } = "";
public int LiveTCVTimeframeMinutes { get; set; } = 60;
public int GraphIntervalMinutes { get; set; } = 60;
public int GraphMaxTimeframeHours { get; set; } = 24;
public int ProfitsMaxTimeframeDays { get; set; } = 60;
public int RefreshSeconds { get; set; } = 30;
public int DashboardChartsRefreshSeconds { get; set; } = 30;
public int BagAnalyzerRefreshSeconds { get; set; } = 5;
public int BuyAnalyzerRefreshSeconds { get; set; } = 5;
//public int MaxSalesRecords { get; set; } = 99999;
public int MaxTopMarkets { get; set; } = 20;
public int MaxDailySummaries { get; set; } = 10;
public int MaxMonthlySummaries { get; set; } = 10;
@ -70,6 +70,7 @@ namespace Core.Main.DataObjects.PTMagicData
public int MaxDCAPairs { get; set; } = 24;
public int MaxSettingsLogEntries { get; set; } = 20;
public string LinkPlatform { get; set; } = "TradingView";
public string TVCustomLayout { get; set; } = "";
public string DefaultDCAMode { get; set; } = "Simple";
public string TvStudyA { get; set; } = "";
public string TvStudyB { get; set; } = "";
@ -164,6 +165,7 @@ namespace Core.Main.DataObjects.PTMagicData
public class SingleMarketSetting
{
public string SettingName { get; set; }
public string TriggerConnection { get; set; } = "AND";
[DefaultValue("AND")]
@ -194,7 +196,10 @@ namespace Core.Main.DataObjects.PTMagicData
}
public class Trigger
{
{
[DefaultValue("")]
public string Tag { get; set; } = "";
[DefaultValue("")]
public string MarketTrendName { get; set; } = "";
@ -218,7 +223,10 @@ namespace Core.Main.DataObjects.PTMagicData
}
public class OffTrigger
{
{
[DefaultValue("")]
public string Tag { get; set; } = "";
[DefaultValue("")]
public string MarketTrendName { get; set; } = "";
@ -398,21 +406,6 @@ namespace Core.Main.DataObjects.PTMagicData
public double LastPrice { get; set; } = 0;
public double Last24hVolume { get; set; } = 0;
}
// public class SellLogData
// {
// public double SoldAmount { get; set; }
// public DateTime SoldDate { get; set; }
// public int BoughtTimes { get; set; }
// public string Market { get; set; }
// public double ProfitPercent { get; set; }
// public double Profit { get; set; }
// public double AverageBuyPrice { get; set; }
// public double TotalCost { get; set; }
// public double SoldPrice { get; set; }
// public double SoldValue { get; set; }
// public double TotalSales { get; set; }
// }
public class StatsData
{
@ -572,5 +565,7 @@ namespace Core.Main.DataObjects.PTMagicData
public string Market { get; set; }
public string TotalCurrentValue { get; set; }
public string TimeZoneOffset { get; set; }
public string ExchangeURL { get; set; }
public string PTVersion { get; set; }
}
}

View File

@ -82,8 +82,11 @@ namespace Core.Main.DataObjects
{
if (!_offsetTimeSpan.HasValue)
{
// Get offset for settings.
_offsetTimeSpan = TimeSpan.Parse(_systemConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
// Ensure Misc is populated
var misc = this.Misc;
// Get offset from Misc
_offsetTimeSpan = TimeSpan.Parse(misc.TimeZoneOffset);
}
return _offsetTimeSpan.Value;
@ -111,7 +114,7 @@ namespace Core.Main.DataObjects
if (_misc == null || (DateTime.UtcNow > _miscRefresh))
{
_misc = BuildMiscData(GetDataFromProfitTrailer("api/v2/data/misc"));
_miscRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
_miscRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1);
}
}
}
@ -133,6 +136,8 @@ namespace Core.Main.DataObjects
StartBalance = PTData.startBalance,
TotalCurrentValue = PTData.totalCurrentValue,
TimeZoneOffset = PTData.timeZoneOffset,
ExchangeURL = PTData.exchangeUrl,
PTVersion = PTData.version,
};
}
public List<DailyStatsData> DailyStats
@ -180,7 +185,7 @@ namespace Core.Main.DataObjects
{
JArray dailyStatsSection = (JArray)extraSection["dailyStats"];
_dailyStats = dailyStatsSection.Select(j => BuildDailyStatsData(j as JObject)).ToList();
_dailyStatsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
_dailyStatsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1);
}
}
}
@ -215,7 +220,7 @@ namespace Core.Main.DataObjects
if (_properties == null || (DateTime.UtcNow > _propertiesRefresh))
{
_properties = BuildProptertiesData(GetDataFromProfitTrailer("api/v2/data/properties"));
_propertiesRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
_propertiesRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1);
}
}
}
@ -258,7 +263,7 @@ namespace Core.Main.DataObjects
jsonReader.Read(); // Move to the value of the "basic" property
JObject basicSection = JObject.Load(jsonReader);
_stats = BuildStatsData(basicSection);
_statsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
_statsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1);
break;
}
}
@ -355,7 +360,7 @@ namespace Core.Main.DataObjects
{
JArray dailyPNLSection = (JArray)extraSection["dailyPNLStats"];
_dailyPNL = dailyPNLSection.Select(j => BuildDailyPNLData(j as JObject)).ToList();
_dailyPNLRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
_dailyPNLRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1);
}
}
}
@ -429,7 +434,7 @@ namespace Core.Main.DataObjects
_profitablePairs.Add(BuildProfitablePairs(profitablePair));
counter++;
}
_profitablePairsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
_profitablePairsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1);
}
}
}
@ -495,7 +500,7 @@ namespace Core.Main.DataObjects
{
JArray dailyTCVSection = (JArray)extraSection["dailyTCVStats"];
_dailyTCV = dailyTCVSection.Select(j => BuildDailyTCVData(j as JObject)).ToList();
_dailyTCVRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
_dailyTCVRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1);
}
}
}
@ -563,7 +568,7 @@ namespace Core.Main.DataObjects
{
JArray monthlyStatsSection = (JArray)extraSection["monthlyStats"];
_monthlyStats = monthlyStatsSection.Select(j => BuildMonthlyStatsData(j as JObject)).ToList();
_monthlyStatsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
_monthlyStatsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds - 1);
}
}
}
@ -585,57 +590,6 @@ namespace Core.Main.DataObjects
Order = monthlyStatsDataJson["order"],
};
}
// public List<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
{
@ -677,26 +631,30 @@ namespace Core.Main.DataObjects
return _dcaLog;
}
}
public List<BuyLogData> BuyLog
{
get
{
if (_buyLog == null || (DateTime.UtcNow > _buyLogRefresh))
{
lock (_buyLock)
if (_systemConfiguration.GeneralSettings.Monitor.MaxDashboardBuyEntries == 0)
{
// Thread double locking
if (_buyLog == null || (DateTime.UtcNow > _buyLogRefresh))
{
_buyLog.Clear();
this.BuildBuyLogData(GetDataFromProfitTrailer("/api/v2/data/pbl", true));
_buyLogRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds - 1);
}
return _buyLog;
}
}
return _buyLog;
if (_buyLog == null || (DateTime.UtcNow > _buyLogRefresh))
{
lock (_buyLock)
{
if (_buyLog == null || (DateTime.UtcNow > _buyLogRefresh))
{
_buyLog.Clear();
this.BuildBuyLogData(GetDataFromProfitTrailer("/api/v2/data/pbl", true));
_buyLogRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds - 1);
}
}
}
return _buyLog;
}
}

View File

@ -1,6 +1,4 @@
using System;
using System.Text.RegularExpressions;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Net;
@ -535,18 +533,22 @@ namespace Core.Helper
}
}
public static string GetMarketLink(string platform, string exchange, string market, string mainMarket)
public static string GetMarketLink(string platform, string exchange, string market, string mainMarket, string tvCustomLayout)
{
string result = "#";
if (tvCustomLayout.Length > 0)
{
tvCustomLayout += "/";
}
if (platform.Equals("TradingView"))
{
if (exchange.Equals("binancefutures", StringComparison.InvariantCultureIgnoreCase))
{
result = "https://uk.tradingview.com/chart/?symbol=BINANCE:" + market.ToUpper() + "PERP";
result = "https://uk.tradingview.com/chart/"+tvCustomLayout+"?symbol=BINANCE:" + market.ToUpper() + ".P";
}
else
{
result = "https://uk.tradingview.com/chart/?symbol=" + exchange.ToUpper() + ":" + market.ToUpper();
result = "https://uk.tradingview.com/chart/"+tvCustomLayout+"?symbol=" + exchange.ToUpper() + ":" + market.ToUpper();
}
}
else

View File

@ -1,9 +1,12 @@
using System;
using System.Data;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;
using System.IO;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using Core.Helper;
using Core.Main.DataObjects.PTMagicData;
using Core.MarketAnalyzer;
@ -59,6 +62,7 @@ namespace Core.Main
private Dictionary<string, int> _singleMarketSettingsCount = new Dictionary<string, int>();
Dictionary<string, List<SingleMarketSetting>> _triggeredSingleMarketSettings = new Dictionary<string, List<SingleMarketSetting>>();
private static volatile object _lockObj = new object();
private Mutex mutex = new Mutex(false, "analyzerStateMutex");
public LogHelper Log
{
@ -122,6 +126,27 @@ namespace Core.Main
_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
{
@ -828,6 +853,8 @@ namespace Core.Main
#region PTMagic Interval Methods
public void PTMagicIntervalTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// Check if the bot is idle
@ -840,6 +867,7 @@ namespace Core.Main
{
// Change state to "Running"
this.State = Constants.PTMagicBotState_Running;
this.WriteStateToFile();
this.RunCount++;
this.LastRuntime = DateTime.UtcNow;
@ -975,6 +1003,7 @@ namespace Core.Main
// Change state to Finished / Stopped
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.");
this.State = Constants.PTMagicBotState_Idle;
this.WriteStateToFile();
Log.DoLogInfo("PTMagic status reset, waiting for the next raid to be good to go again.");
}
}
@ -998,6 +1028,7 @@ namespace Core.Main
{
Log.DoLogWarn("No LastRuntimeSummary.json found after raid " + this.RunCount.ToString() + ", trying to reset PT Magic status...");
this.State = Constants.PTMagicBotState_Idle;
this.WriteStateToFile();
Log.DoLogInfo("PTMagic status reset, waiting for the next raid to be good to go again.");
}
}
@ -1269,6 +1300,8 @@ namespace Core.Main
}
}
private void BuildGlobalMarketTrends()
{
this.Log.DoLogInfo("Build global market trends...");
@ -1300,86 +1333,130 @@ namespace Core.Main
private void CheckGlobalSettingsTriggers(ref GlobalSetting triggeredSetting, ref List<string> matchedTriggers)
{
this.Log.DoLogInfo("Checking global settings triggers...");
foreach (GlobalSetting globalSetting in this.PTMagicConfiguration.AnalyzerSettings.GlobalSettings)
{
// Reset triggers for each setting
matchedTriggers = new List<string>();
this.Log.DoLogInfo("Checking global settings triggers...");
if (globalSetting.Triggers.Count > 0)
foreach (GlobalSetting globalSetting in this.PTMagicConfiguration.AnalyzerSettings.GlobalSettings)
{
this.Log.DoLogInfo("Checking triggers for '" + globalSetting.SettingName + "'...");
List<bool> triggerResults = new List<bool>();
foreach (Trigger trigger in globalSetting.Triggers)
{
MarketTrend marketTrend = this.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.Find(mt => mt.Name == trigger.MarketTrendName);
if (marketTrend != null)
// Reset triggers for each setting
matchedTriggers = new List<string>();
if (globalSetting.Triggers.Count > 0)
{
// Get market trend change for trigger
if (this.AverageMarketTrendChanges.ContainsKey(marketTrend.Name))
{
double averageMarketTrendChange = this.AverageMarketTrendChanges[marketTrend.Name];
if (averageMarketTrendChange >= trigger.MinChange && averageMarketTrendChange < trigger.MaxChange)
this.Log.DoLogInfo("Checking triggers for '" + globalSetting.SettingName + "'...");
Dictionary<string, bool> triggerResults = new Dictionary<string, bool>();
foreach (Trigger trigger in globalSetting.Triggers)
{
MarketTrend marketTrend = this.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.Find(mt => mt.Name == trigger.MarketTrendName);
if (marketTrend != null)
{
// Get market trend change for trigger
if (this.AverageMarketTrendChanges.ContainsKey(marketTrend.Name))
{
double averageMarketTrendChange = this.AverageMarketTrendChanges[marketTrend.Name];
bool isTriggered = averageMarketTrendChange >= trigger.MinChange && averageMarketTrendChange < trigger.MaxChange;
triggerResults[trigger.Tag] = isTriggered;
// Trigger met!
this.Log.DoLogInfo("Trigger '" + trigger.MarketTrendName + "' triggered! TrendChange = " + averageMarketTrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%");
if (isTriggered)
{
// Trigger met!
this.Log.DoLogInfo("Trigger '" + trigger.MarketTrendName + "' triggered! TrendChange = " + averageMarketTrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%");
string triggerContent = trigger.MarketTrendName + " - ";
if (trigger.MinChange != Constants.MinTrendChange)
{
triggerContent += " - Min: " + trigger.MinChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
}
string triggerContent = trigger.MarketTrendName + " - ";
if (trigger.MinChange != Constants.MinTrendChange)
{
triggerContent += " - Min: " + trigger.MinChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
}
if (trigger.MaxChange != Constants.MaxTrendChange)
{
triggerContent += " - Max: " + trigger.MaxChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
}
if (trigger.MaxChange != Constants.MaxTrendChange)
{
triggerContent += " - Max: " + trigger.MaxChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
}
matchedTriggers.Add(triggerContent);
matchedTriggers.Add(triggerContent);
}
else
{
this.Log.DoLogDebug("Trigger '" + trigger.MarketTrendName + "' not triggered. TrendChange = " + averageMarketTrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%");
}
}
else
{
this.Log.DoLogError("Trigger '" + trigger.MarketTrendName + "' not found in this.AverageMarketTrendChanges[] (" + SystemHelper.ConvertListToTokenString(this.AverageMarketTrendChanges.Keys.ToList(), ",", true) + "). Unable to load recent trends?");
}
}
else
{
this.Log.DoLogWarn("Market Trend '" + trigger.MarketTrendName + "' not found! Trigger ignored!");
}
}
triggerResults.Add(true);
// Check if the TriggerConnection field exists
if (!string.IsNullOrEmpty(globalSetting.TriggerConnection))
{
// Check if TriggerConnection is using the old logic
if (globalSetting.TriggerConnection.ToLower() == "and" || globalSetting.TriggerConnection.ToLower() == "or")
{
// Old logic
bool settingTriggered = false;
switch (globalSetting.TriggerConnection.ToLower())
{
case "and":
settingTriggered = triggerResults.Values.All(tr => tr);
break;
case "or":
settingTriggered = triggerResults.Values.Any(tr => tr);
break;
}
// Setting got triggered -> Activate it!
if (settingTriggered)
{
triggeredSetting = globalSetting;
break;
}
}
else
{
// New logic
string triggerConnection = globalSetting.TriggerConnection;
foreach (var triggerResult in triggerResults)
{
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
{
this.Log.DoLogDebug("Trigger '" + trigger.MarketTrendName + "' not triggered. TrendChange = " + averageMarketTrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%");
triggerResults.Add(false);
this.Log.DoLogError($"ERROR: Trigger Connection for global setting {globalSetting.SettingName} is missing. Program halted.");
Environment.Exit(1); // Stop the program
}
}
else
{
this.Log.DoLogError("Trigger '" + trigger.MarketTrendName + "' not found in this.AverageMarketTrendChanges[] (" + SystemHelper.ConvertListToTokenString(this.AverageMarketTrendChanges.Keys.ToList(), ",", true) + "). Unable to load recent trends?");
triggerResults.Add(false);
}
}
else
{
this.Log.DoLogWarn("Market Trend '" + trigger.MarketTrendName + "' not found! Trigger ignored!");
triggerResults.Add(false);
}
}
// Check if all triggers have to get triggered or just one
bool settingTriggered = false;
switch (globalSetting.TriggerConnection.ToLower())
{
case "and":
settingTriggered = triggerResults.FindAll(tr => tr == false).Count == 0;
break;
case "or":
settingTriggered = triggerResults.FindAll(tr => tr == true).Count > 0;
break;
}
// Setting got triggered -> Activate it!
if (settingTriggered)
{
triggeredSetting = globalSetting;
break;
}
}
}
}
private void ActivateSetting(ref GlobalSetting triggeredSetting, ref List<string> matchedTriggers)
@ -1751,6 +1828,14 @@ namespace Core.Main
Dictionary<int, double> relevantTriggers = new Dictionary<int, double>();
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
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);
triggerResults.Add(true);
triggerTagsResults[trigger.Tag] = true;
}
else
{
@ -1816,6 +1902,7 @@ namespace Core.Main
relevantTriggers.Add(triggerIndex, marketAge);
triggerResults.Add(true);
triggerTagsResults[trigger.Tag] = true;
}
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")) + "%");
triggerResults.Add(true);
triggerTagsResults[trigger.Tag] = true;
}
else
{
@ -1938,19 +2026,53 @@ namespace Core.Main
triggerIndex++;
} // End loop SMS triggers
// Check if all triggers have to get triggered or just one
bool settingTriggered = false;
switch (marketSetting.TriggerConnection.ToLower())
if (marketSetting.TriggerConnection.ToLower() == "and" || marketSetting.TriggerConnection.ToLower() == "or")
{
case "and":
settingTriggered = triggerResults.FindAll(tr => tr == false).Count == 0;
break;
case "or":
settingTriggered = triggerResults.FindAll(tr => tr == true).Count > 0;
break;
switch (marketSetting.TriggerConnection.ToLower())
{
case "and":
settingTriggered = triggerResults.FindAll(tr => tr == false).Count == 0;
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
bool isFreshTrigger = true;
// Setting not triggered -> Check if it is already active as a long term SMS using Off Triggers

View File

@ -25,10 +25,13 @@
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/nvd3/nv.d3.min.js"></script>
<script type="text/javascript">
var errCountIndex = [];
var errCountIndex = [];
var counterIndex = []; // Add this line
var intervalDashboardTop;
var intervalDashboardBottom;
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>');
$("#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 () {
//destroy all d3 svg graph to avoid memory leak
setTimeout(function()
{
$(".nvtooltip").remove();
$("svg > *").remove();
$("svg").remove();
nv.charts = {};
nv.graphs = [];
nv.logs = {};
nv.tooltip = {};
}, 10 * intervalDashboardBottom * 1000); // 10 times intervalDashboardBottom in milliseconds
// Clear exisitng interval to stop multiple attempts to load at the same time.
if (intervalDashboardBottom != null)
{
// Clear existing interval to stop multiple attempts to load at the same time.
if (intervalDashboardBottom != null) {
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
$("#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");
} else {
errCountIndex["DashboardBottom"] = 0;
// Increment the counter
counterIndex["DashboardBottom"] = (counterIndex["DashboardBottom"] || 0) + 1;
}
// 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>
}
}
@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>
</p>
</tbody>
@ -69,7 +74,7 @@
<tr>
<th>
@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>
<td>
@ -197,7 +202,7 @@
</button>
</div>
<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>
</div>
<div class="modal-footer">
@ -208,7 +213,89 @@
</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 {
<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">
$(function () {

View File

@ -3,7 +3,8 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Hosting;
using Core.Main;
using Core.Main.DataObjects.PTMagicData;
using Newtonsoft.Json;
@ -13,7 +14,12 @@ namespace Monitor.Pages
public class ManageSMSModel : _Internal.BasePageModelSecure
{
public List<SingleMarketSettingSummary> SingleMarketSettingSummaries = new List<SingleMarketSettingSummary>();
private readonly IWebHostEnvironment _hostingEnvironment;
public ManageSMSModel(IWebHostEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
public void OnGet()
{
base.Init();
@ -34,6 +40,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()
{

View File

@ -162,7 +162,7 @@ else
<th>Name</th>
<th class="text-right">Markets</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>
</tr>
</thead>
@ -271,9 +271,9 @@ else
}
</th>
@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 {
<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">@Math.Round(mps.Latest24hVolume, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainMarket</td>
@ -336,9 +336,9 @@ else
}
</th>
@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 {
<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">@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 type="text/javascript">
$(function () {
$('.btn-trend-relation').click(function () {
var relation = $(this).data('trend-relation');
$(function () {
$('.btn-trend-relation').click(function () {
var relation = $(this).data('trend-relation');
$('.btn-trend-relation').addClass('btn-custom');
$(this).removeClass('btn-custom');
$('.btn-trend-relation').addClass('btn-custom');
$(this).removeClass('btn-custom');
if (relation == 'absolute') {
$('#trends-absolute').removeClass('hidden');
$('#trends-relative').addClass('hidden');
if (relation == 'absolute') {
$('#trends-absolute').removeClass('hidden');
$('#trends-relative').addClass('hidden');
} else {
$('#trends-absolute').addClass('hidden');
$('#trends-relative').removeClass('hidden');
}
} else {
$('#trends-absolute').addClass('hidden');
$('#trends-relative').removeClass('hidden');
}
$('[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;
$('[data-toggle="tooltip"]').tooltip();
});
</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>
}

View File

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

View File

@ -329,7 +329,7 @@
double profitFiat = Math.Round(profit * Model.MiscData.FiatConversionRate, 0);
<tr>
<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">@sales</td>
<td class="text-right text-autocolor-saw">@avg </td>
@ -394,20 +394,7 @@
.transition().duration(0)
.call(salesChart);
// Add mouseleave, and mousemove event listeners to hide tooltip
d3.select('.sales-chart').on('mouseleave', function() {
d3.select('.nvtooltip').style('opacity', 0);
});
d3.select('body').on('mousemove', function() {
var chartBounds = d3.select('.sales-chart')[0][0].getBoundingClientRect();
var mouseX = d3.event.clientX;
var mouseY = d3.event.clientY;
if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) {
d3.select('.nvtooltip').style('opacity', 0);
}
});
nv.utils.windowResize(salesChart.update);
return salesChart;
});
@ -442,20 +429,7 @@
.transition().duration(0)
.call(cumulativeProfitChart);
// Add mouseleave, and mousemove event listeners to hide tooltip
d3.select('.cumulative-profit-chart').on('mouseleave', function() {
d3.select('.nvtooltip').style('opacity', 0);
});
d3.select('body').on('mousemove', function() {
var chartBounds = d3.select('.cumulative-profit-chart')[0][0].getBoundingClientRect();
var mouseX = d3.event.clientX;
var mouseY = d3.event.clientY;
if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) {
d3.select('.nvtooltip').style('opacity', 0);
}
});
nv.utils.windowResize(cumulativeProfitChart.update);
return cumulativeProfitChart;
});
@ -490,20 +464,6 @@
.transition().duration(0)
.call(TCVChart);
// Add mouseleave, and mousemove event listeners to hide tooltip
d3.select('.TCV-chart').on('mouseleave', function() {
d3.select('.nvtooltip').style('opacity', 0);
});
d3.select('body').on('mousemove', function() {
var chartBounds = d3.select('.TCV-chart')[0][0].getBoundingClientRect();
var mouseX = d3.event.clientX;
var mouseY = d3.event.clientY;
if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) {
d3.select('.nvtooltip').style('opacity', 0);
}
});
nv.utils.windowResize(TCVChart.update);
return TCVChart;
});
@ -538,20 +498,33 @@
.transition().duration(0)
.call(profitChart);
// Add mouseleave, and mousemove event listeners to hide tooltip
d3.select('.profit-chart').on('mouseleave', function() {
d3.select('.nvtooltip').style('opacity', 0);
profitChart.dispatch.on('renderEnd', function() {
// Get the chart's container
var container = d3.select('.profit-chart .nv-wrap.nv-lineChart .nv-linesWrap');
// Remove any existing y=0 line
container.selectAll('.zero-line').remove();
// Check if profitData[0].values is not empty
if (profitData[0].values.length > 0) {
// Get the x-values of the first and last data points
var xMin = profitChart.xAxis.scale()(profitData[0].values[0].x);
var xMax = profitChart.xAxis.scale()(profitData[0].values[profitData[0].values.length - 1].x);
// Add a line at y=0
container.insert('line', ':first-child')
.attr('class', 'zero-line') // Add a class to the line for easy selection
.attr('x1', xMin) // x position of the first end of the line
.attr('y1', profitChart.yAxis.scale()(0)) // y position of the first end of the line
.attr('x2', xMax) // x position of the second end of the line
.attr('y2', profitChart.yAxis.scale()(0)) // y position of the second end of the line
.attr('stroke', 'gray') // color of the line
.attr('stroke-width', 2); // width of the line
}
});
d3.select('body').on('mousemove', function() {
var chartBounds = d3.select('.profit-chart')[0][0].getBoundingClientRect();
var mouseX = d3.event.clientX;
var mouseY = d3.event.clientY;
if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) {
d3.select('.nvtooltip').style('opacity', 0);
}
});
nv.utils.windowResize(profitChart.update);
return profitChart;
});
@ -559,4 +532,34 @@
}
})(jQuery);
</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;
// Convert local offset time to UTC
TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
TimeSpan offsetTimeSpan = TimeSpan.Parse(MiscData.TimeZoneOffset.Replace("+", ""));
DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
BuildSalesChartData();
@ -71,8 +71,8 @@ namespace Monitor.Pages
{
// Get timezone offset
TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-');
bool isNegative = MiscData.TimeZoneOffset.StartsWith("-");
string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{
@ -129,8 +129,8 @@ namespace Monitor.Pages
{
// Get timezone offset
TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-');
bool isNegative = MiscData.TimeZoneOffset.StartsWith("-");
string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{
@ -190,8 +190,8 @@ namespace Monitor.Pages
{
// Get timezone offset
TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-');
bool isNegative = MiscData.TimeZoneOffset.StartsWith("-");
string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{
@ -301,8 +301,8 @@ namespace Monitor.Pages
{
// Get timezone offset
TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-');
bool isNegative = MiscData.TimeZoneOffset.StartsWith("-");
string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{

View File

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

View File

@ -117,12 +117,12 @@
</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>
<div class="col-md-8">
<input type="text" class="form-control" name="Application_TimezoneOffset" value="@Model.PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset">
</div>
</div>
</div> *@
@* <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>
@ -196,42 +196,48 @@
</div>
</div>
<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">
<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 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">
<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 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">
<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 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">
<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 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">
<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 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">
<input type="text" class="form-control" name="Monitor_BuyAnalyzerRefreshSeconds" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds.ToString(new System.Globalization.CultureInfo("en-US"))">
</div>
@ -247,12 +253,12 @@
</div>
</div>
@* <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>
<div class="form-group row">
<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">
<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 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>

View File

@ -27,30 +27,6 @@ namespace Monitor.Pages
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()
{
base.Init();
@ -72,30 +48,25 @@ namespace Monitor.Pages
// Read the new settings
PTMagicConfiguration.GeneralSettings.Application.IsEnabled = HttpContext.Request.Form["Application_IsEnabled"].Equals("on");
PTMagicConfiguration.GeneralSettings.Application.TestMode = HttpContext.Request.Form["Application_TestMode"].Equals("on");
//PTMagicConfiguration.GeneralSettings.Application.StartBalance = SystemHelper.TextToDouble(HttpContext.Request.Form["Application_StartBalance"], PTMagicConfiguration.GeneralSettings.Application.StartBalance, "en-US");
PTMagicConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName = HttpContext.Request.Form["Application_ProfitTrailerDefaultSettingName"];
PTMagicConfiguration.GeneralSettings.Application.Exchange = HttpContext.Request.Form["Application_Exchange"];
PTMagicConfiguration.GeneralSettings.Application.ProfitTrailerMonitorURL = HttpContext.Request.Form["Application_ProfitTrailerMonitorURL"];
PTMagicConfiguration.GeneralSettings.Application.ProfitTrailerServerAPIToken = HttpContext.Request.Form["Application_ProfitTrailerServerAPIToken"];
PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset = HttpContext.Request.Form["Application_TimezoneOffset"];
//PTMagicConfiguration.GeneralSettings.Application.MainFiatCurrency = HttpContext.Request.Form["Application_MainFiatCurrency"];
PTMagicConfiguration.GeneralSettings.Application.FloodProtectionMinutes = SystemHelper.TextToInteger(HttpContext.Request.Form["Application_FloodProtectionMinutes"], PTMagicConfiguration.GeneralSettings.Application.FloodProtectionMinutes);
PTMagicConfiguration.GeneralSettings.Application.InstanceName = HttpContext.Request.Form["Application_InstanceName"];
PTMagicConfiguration.GeneralSettings.Application.CoinMarketCapAPIKey = HttpContext.Request.Form["Application_CoinMarketCapAPIKey"];
//PTMagicConfiguration.GeneralSettings.Application.FreeCurrencyConverterAPIKey = HttpContext.Request.Form["Application_FreeCurrencyConverterAPIKey"];
PTMagicConfiguration.GeneralSettings.Monitor.IsPasswordProtected = HttpContext.Request.Form["Monitor_IsPasswordProtected"].Equals("on");
PTMagicConfiguration.GeneralSettings.Monitor.OpenBrowserOnStart = HttpContext.Request.Form["Monitor_OpenBrowserOnStart"].Equals("on");
PTMagicConfiguration.GeneralSettings.Monitor.AnalyzerChart = HttpContext.Request.Form["Monitor_AnalyzerChart"];
PTMagicConfiguration.GeneralSettings.Monitor.Port = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_Port"], PTMagicConfiguration.GeneralSettings.Monitor.Port);
PTMagicConfiguration.GeneralSettings.Monitor.LiveTCVTimeframeMinutes = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_LiveTCVTimeframeMinutes"], PTMagicConfiguration.GeneralSettings.Monitor.LiveTCVTimeframeMinutes);
PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_GraphIntervalMinutes"], PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes);
PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_GraphMaxTimeframeHours"], PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours);
PTMagicConfiguration.GeneralSettings.Monitor.ProfitsMaxTimeframeDays = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_ProfitsMaxTimeframeDays"], PTMagicConfiguration.GeneralSettings.Monitor.ProfitsMaxTimeframeDays);
PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_RefreshSeconds"], PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds);
PTMagicConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_RefreshSeconds"], PTMagicConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds);
PTMagicConfiguration.GeneralSettings.Monitor.BagAnalyzerRefreshSeconds = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_BagAnalyzerRefreshSeconds"], PTMagicConfiguration.GeneralSettings.Monitor.BagAnalyzerRefreshSeconds);
PTMagicConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_BuyAnalyzerRefreshSeconds"], PTMagicConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds);
PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform = HttpContext.Request.Form["Monitor_LinkPlatform"];
//PTMagicConfiguration.GeneralSettings.Monitor.MaxSalesRecords = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxSalesRecords"], PTMagicConfiguration.GeneralSettings.Monitor.MaxSalesRecords);
PTMagicConfiguration.GeneralSettings.Monitor.TVCustomLayout = HttpContext.Request.Form["Monitor_TVCustomLayout"];
PTMagicConfiguration.GeneralSettings.Monitor.MaxTopMarkets = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxTopMarkets"], PTMagicConfiguration.GeneralSettings.Monitor.MaxTopMarkets);
PTMagicConfiguration.GeneralSettings.Monitor.MaxDailySummaries = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxDailySummaries"], PTMagicConfiguration.GeneralSettings.Monitor.MaxDailySummaries);
PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxMonthlySummaries"], PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries);

View File

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

View File

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

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.
interval = setInterval(function () { loadWidgets(); }, 5000);
interval = setInterval(function () { loadWidgets(); }, 3000);
});
};

View File

@ -103,7 +103,7 @@
<div class="tradingview-widget-container">
<div id="tradingview_6aa22" style="height:600px;"></div>
<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>
</div>
</div>

View File

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

View File

@ -84,9 +84,9 @@
<tr @(lostValue ? "class=errorRow" : "" ) >
// Market
@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 {
<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>
</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);
<tr>
@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 {
<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>
@if (buyDisabled) {

View File

@ -10,7 +10,48 @@
}
<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;">
<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("")) {
@ -23,37 +64,12 @@
</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="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("")) {
<div class="profit-chart">
<svg style="height:300px;width:100%"></svg>
@ -70,9 +86,9 @@
<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>
<br> *@
<h4 class="m-t-0 m-b-20 header-title">Live 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>
<small class="pull-right" style="font-size: x-small"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)MarketAnalyzer">ANALYZER</a></small>
<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.DashboardChartsRefreshSeconds seconds in general settings."></i>
<small class="pull-right" style="font-size: small"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)MarketAnalyzer">ANALYZER</a></small>
</h4>
<table class="table table-sm">
<thead>
@ -80,7 +96,7 @@
<th>Name</th>
<th class="text-right">Markets</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 class="text-right">Change</th>
</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-init.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$('[data-toggle="tooltip"]').tooltip();
});
</script>
<script type="text/javascript">
$(document).ready(function () {
$(".cdev").circlos();
$('[data-toggle="tooltip"]').tooltip();
$('.text-autocolor').autocolor(false);
var assetDistributionChart; // Keep a reference to the chart
var assetDistributionData; // Keep a reference to the data
@if (!Model.AssetDistributionData.Equals("")) {
@ -302,29 +325,19 @@
.labelThreshold(.1)
.labelType("percent")
.donut(true)
.donutRatio(0.3);
.donutRatio(0.3)
.margin({top: 10, bottom: 10})
.showLegend(false); // Hide the legend
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)
.transition().duration(0)
.call(assetDistributionChart);
// Add mouseleave, and mousemove event listeners to hide tooltip
d3.select('.profit-chart').on('mouseleave', function() {
d3.select('.nvtooltip').style('opacity', 0);
});
d3.select('body').on('mousemove', function() {
var chartBounds = d3.select('.profit-chart')[0][0].getBoundingClientRect();
var mouseX = d3.event.clientX;
var mouseY = d3.event.clientY;
if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) {
d3.select('.nvtooltip').style('opacity', 0);
}
});
nv.utils.windowResize(assetDistributionChart.update);
return assetDistributionChart;
});
@ -336,6 +349,7 @@
<script type="text/javascript">
(function ($) {
'use strict';
// $('head').append('<style>.nv-point { stroke-width: 1px; }</style>');
$('[role="tooltip"]').remove();
$('[data-toggle="tooltip"]').tooltip();
$('.text-autocolor').autocolor(false);
@ -349,28 +363,39 @@
trendChart = nv.models.lineChart();
var height = 300;
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.yAxis.axisLabel('Trend %').tickFormat(d3.format(',.2f'));
trendData = @Html.Raw(Model.TrendChartDataJSON);
d3.select('.trend-chart svg')
var svg = d3.select('.trend-chart svg').node();
d3.select(svg)
.datum(trendData)
.transition().duration(0)
.call(trendChart);
// Add mouseleave, and mousemove event listeners to hide tooltip
d3.select('.profit-chart').on('mouseleave', function() {
d3.select('.nvtooltip').style('opacity', 0);
});
d3.select('body').on('mousemove', function() {
var chartBounds = d3.select('.profit-chart')[0][0].getBoundingClientRect();
var mouseX = d3.event.clientX;
var mouseY = d3.event.clientY;
trendChart.dispatch.on('renderEnd', function() {
// Get the chart's container
var container = d3.select('.trend-chart .nv-wrap.nv-lineChart .nv-linesWrap');
if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) {
d3.select('.nvtooltip').style('opacity', 0);
}
// Remove any existing y=0 line
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);
return trendChart;
@ -389,6 +414,7 @@
var profitChart; // Keep a reference to the chart
var profitData; // Keep a reference to the data
@if (!Model.ProfitChartDataJSON.Equals("")) {
<text>
@ -406,20 +432,33 @@
.transition().duration(0)
.call(profitChart);
// Add mouseleave, and mousemove event listeners to hide tooltip
d3.select('.profit-chart').on('mouseleave', function() {
d3.select('.nvtooltip').style('opacity', 0);
});
profitChart.dispatch.on('renderEnd', function() {
// Get the chart's container
var container = d3.select('.profit-chart .nv-wrap.nv-lineChart .nv-linesWrap');
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;
// Remove any existing y=0 line
container.selectAll('.zero-line').remove();
if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) {
d3.select('.nvtooltip').style('opacity', 0);
}
// 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(); });
}
});
nv.utils.windowResize(profitChart.update);
return profitChart;
});
@ -429,29 +468,78 @@
</script>
<script type="text/javascript">
$(document).ready(function(){
var originalLeave = $.fn.tooltip.Constructor.prototype.leave;
$.fn.tooltip.Constructor.prototype.leave = function(obj){
var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type);
var container, timeout;
(function ($) {
'use strict';
$('[role="tooltip"]').remove();
$('[data-toggle="tooltip"]').tooltip();
$('.text-autocolor').autocolor(false);
originalLeave.call(this, obj);
var TCVLiveChart; // Keep a reference to the chart
var TCVLiveData; // Keep a reference to the data
if(obj.currentTarget) {
container = $(obj.currentTarget).siblings('.tooltip');
timeout = self.timeout;
container.one('mouseenter', function(){
//We entered the actual tooltip call off the dogs
clearTimeout(timeout);
//Let's monitor tooltip content instead
container.one('mouseleave', function(){
$.fn.tooltip.Constructor.prototype.leave.call(self, self);
});
@if (!Model.TotalCurrentValueLiveChartDataJSON.Equals("")) {
<text>
nv.addGraph(function () {
TCVLiveChart = nv.models.lineChart();
TCVLiveChart.useInteractiveGuideline(true);
TCVLiveChart.xAxis.tickFormat(function (d) { return d3.time.format('%H:%M:%S')(new Date(d)); });
TCVLiveChart.yAxis.axisLabel('').tickFormat(d3.format(',.2f'));
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>

View File

@ -26,6 +26,7 @@ namespace Monitor.Pages
public DateTimeOffset DateTimeNow = Constants.confMinDate;
public string AssetDistributionData = "";
public double totalCurrentValue = 0;
public string TotalCurrentValueLiveChartDataJSON { get; set; }
public void OnGet()
{
// Initialize Config
@ -49,7 +50,7 @@ namespace Monitor.Pages
FileHelper.CleanupFilesMinutes(PTMagicMonitorBasePath + "wwwroot" + System.IO.Path.DirectorySeparatorChar + "assets" + System.IO.Path.DirectorySeparatorChar + "tmp" + System.IO.Path.DirectorySeparatorChar, 5);
// Convert local offset time to UTC
TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
TimeSpan offsetTimeSpan = TimeSpan.Parse(MiscData.TimeZoneOffset.Replace("+", ""));
DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
// Get last and current active setting
@ -65,8 +66,109 @@ namespace Monitor.Pages
BuildMarketTrendChartData();
BuildAssetDistributionData();
BuildProfitChartData();
StartUpdatingTotalCurrentValueLive();
UpdateTotalCurrentValueLive();
BuildTotalCurrentValueLiveChartData();
}
private static System.Timers.Timer timer;
private static List<(DateTime Timestamp, double TotalCurrentValue)> totalCurrentValueLiveList;
public void StartUpdatingTotalCurrentValueLive()
{
int liveTCVInterval = PTMagicConfiguration.GeneralSettings.Monitor.DashboardChartsRefreshSeconds;
if (timer != null)
{
// Timer is already running
return;
}
totalCurrentValueLiveList = new List<(DateTime Timestamp, double TotalCurrentValueLive)>();
timer = new System.Timers.Timer(liveTCVInterval * 1000); // Set interval to liveTCVTimer seconds
timer.Elapsed += (sender, e) => UpdateTotalCurrentValueLive();
timer.Start();
}
private void UpdateTotalCurrentValueLive()
{
double PairsBalance = 0.0;
double DCABalance = 0.0;
double PendingBalance = 0.0;
double AvailableBalance = PTData.GetCurrentBalance();
bool isSellStrategyTrue = false;
bool isTrailingSellActive = false;
foreach (DCALogData dcaLogEntry in PTData.DCALog)
{
string sellStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Summary, dcaLogEntry.SellStrategies, dcaLogEntry.SellStrategy, isSellStrategyTrue, isTrailingSellActive);
// Aggregate totals
double leverage = dcaLogEntry.Leverage;
if (leverage == 0)
{
leverage = 1;
}
if (sellStrategyText.Contains("PENDING"))
{
PendingBalance = PendingBalance + (dcaLogEntry.Amount * dcaLogEntry.CurrentPrice / leverage);
}
else if (dcaLogEntry.BuyStrategies.Count > 0)
{
DCABalance = DCABalance + (dcaLogEntry.Amount * dcaLogEntry.CurrentPrice / leverage);
}
else
{
PairsBalance = PairsBalance + (dcaLogEntry.Amount * dcaLogEntry.CurrentPrice / leverage);
}
}
double totalCurrentValueLive = PendingBalance + DCABalance + PairsBalance + AvailableBalance;
// Get the current time
DateTime now = DateTime.UtcNow;
totalCurrentValueLiveList.Add((now, totalCurrentValueLive));
// Get liveTCVTimeframe from PTMagicConfiguration.GeneralSettings.Monitor
int liveTCVTimeframe = PTMagicConfiguration.GeneralSettings.Monitor.LiveTCVTimeframeMinutes;
// Calculate the timestamp that is liveTCVTimeframe minutes ago
DateTime threshold = now.AddMinutes(-liveTCVTimeframe);
// Remove all data points that are older than the threshold
while (totalCurrentValueLiveList.Count > 0 && totalCurrentValueLiveList[0].Item1 < threshold)
{
totalCurrentValueLiveList.RemoveAt(0);
}
}
private void BuildTotalCurrentValueLiveChartData()
{
List<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()
{
List<string> trendChartData = new List<string>();
@ -95,8 +197,8 @@ namespace Monitor.Pages
// Get trend ticks for chart
TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-');
bool isNegative = MiscData.TimeZoneOffset.StartsWith("-");
string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{
@ -158,8 +260,8 @@ namespace Monitor.Pages
{
// Get timezone offset
TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-');
bool isNegative = MiscData.TimeZoneOffset.StartsWith("-");
string offsetWithoutSign = MiscData.TimeZoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{

View File

@ -3,13 +3,15 @@
@{
Layout = null;
}
<div class="row">
@{
bool sideBySide = true;
}
<div class="@(sideBySide ? "row" : "col-md-12")">
@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">
<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)
{
<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">
<thead>
<tr>
<th>Market</th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="24 Hour price trend">24H</th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="24 Hour trading volume">Volume</th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="Current ask price for this market">Ask</th>
<th>Buy Strategies</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><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><strong>Ask </strong></th>
<th><strong>Buy Strategies </strong></th>
</tr>
</thead>
<tbody>
@ -57,13 +58,21 @@
<tr>
@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 {
<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 class="text">@string.Format("{0}", (buyLogEntry.Volume24h).ToString())</td>
<td class="text-left">@buyLogEntry.CurrentPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td >@string.Format("{0}", (buyLogEntry.Volume24h).ToString())</td>
<td >@buyLogEntry.CurrentPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
@if (buyDisabled) {
<td>@Html.Raw(buyStrategyText)</td>
@ -85,7 +94,7 @@
<div class="col-md px-1">
<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)
{
@ -94,16 +103,15 @@
else
{
<div class="table-responsive">
<table class="table table-sm m-b-0">
<table class="table table-sm m-b-0 table-auto-width">
<thead>
<tr>
<th>Market</th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="24 Hour Trend">24H</th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="Total Buy Cost">Cost</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><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></th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="Active Buy Strategies">DCA</th>
<th class="text-left" data-toggle="tooltip" data-placement="top" title="Active Sell Strategies">Sell</th>
<th class="text-left" data-toggle="tooltip" data-html="true" data-placement="top" title="Profit Target <br> Current Profit">Profit</th>
<th><strong>DCA </strong></th>
<th><strong>Sell </strong></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>
</tr>
</thead>
@ -142,7 +150,6 @@
if (dcaLogEntry.SellStrategies.Count > 0) {
isSellStrategyTrue = (dcaLogEntry.SellStrategies.FindAll(ss => !ss.IsTrue).Count == 0);
}
string leverage = "";
double leverageValue = 1;
string buyStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Model.Summary, dcaLogEntry.BuyStrategies, dcaLogEntry.BuyStrategy, isBuyStrategyTrue, isTrailingBuyActive);
@ -154,55 +161,38 @@
// Profit percentage
var profitPercentage = dcaLogEntry.ProfitPercent;
// if (dcaLogEntry.SellStrategies != null)
// {
// var gainStrategy = dcaLogEntry.SellStrategies.FirstOrDefault(x => x.Name.Contains(" GAIN", StringComparison.InvariantCultureIgnoreCase));
// if (gainStrategy != null)
// {
// // Use the gain percentage value as it is accurate to what can be achieved with the order book!
// profitPercentage = gainStrategy.CurrentValue;
// }
// }
// Render the row
if (!sellStrategyText.Contains("PENDING-BUY"))
if (!sellStrategyText.Contains("PENDING-BUY"))
{
<tr @(lostValue ? "class=errorRow" : "") >
<!-- Market -->
<td class="align-top; text-nowrap">
<b>
@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
{
<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>
<br>
@bagAgeText
<br><span class="text-autocolor">@Html.Raw((dcaLogEntry.PercChange * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))) %</span>
</td>
<!-- 24hr change -->
<td class="text-autocolor">@Html.Raw((dcaLogEntry.PercChange * 100).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))%</td>
<!-- 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 -->
<td class="text-right">
@if (dcaEnabled)
{
@if (dcaLogEntry.BoughtTimes > 0)
if (dcaLogEntry.BoughtTimes > 0)
{
@dcaLogEntry.BoughtTimes;
}
} 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>
<!-- DCA Strategy -->
@ -231,10 +221,10 @@
@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"))%
<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>
}
else
@ -242,7 +232,7 @@
<td>
<div class="text-left">None</div>
<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>
}
@ -255,24 +245,22 @@
}
<!-- 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>
</tr>
{
// Aggregate totals
double bagGain = (profitPercentage / 100) * dcaLogEntry.TotalCost;
Model.TotalBagCost = Model.TotalBagCost + dcaLogEntry.TotalCost;
Model.TotalBagGain = Model.TotalBagGain + bagGain;
}
</tr>
{
// Aggregate totals
double bagGain = (profitPercentage / 100) * dcaLogEntry.TotalCost;
Model.TotalBagCost = Model.TotalBagCost + dcaLogEntry.TotalCost;
Model.TotalBagGain = Model.TotalBagGain + bagGain;
}
}
}
<td>Totals:</td>
<td></td>
<td>@Html.Raw(Model.TotalBagCost.ToString("#,#0.000000", new System.Globalization.CultureInfo("en-US")))</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>
}
<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></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>
</tbody>
</table>
</div>

View File

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

View File

@ -36,12 +36,7 @@
@if (!Model.GlobalSetting.SettingName.StartsWith("Default", StringComparison.InvariantCultureIgnoreCase)) {
<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>
<div class="col-md-8">
<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>
<input type="text" name="MarketAnalyzer_GlobalSetting_@(Model.SettingName)|TriggerConnection" class="form-control" value="@Model.GlobalSetting.TriggerConnection" />
</div>
<hr />

View File

@ -78,7 +78,7 @@
</div>
<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">
<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>

View File

@ -29,20 +29,14 @@
<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>
<div class="col-md-8">
<select name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|TriggerConnection" class="form-control">
<option selected="@(Model.SingleMarketSetting.TriggerConnection.Equals("AND", StringComparison.InvariantCultureIgnoreCase))">AND</option>
<option selected="@(Model.SingleMarketSetting.TriggerConnection.Equals("OR", StringComparison.InvariantCultureIgnoreCase))">OR</option>
</select>
<input type="text" name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|TriggerConnection" class="form-control" value="@Model.SingleMarketSetting.TriggerConnection" />
</div>
</div>
<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>
<div class="col-md-8">
<select name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|OffTriggerConnection" class="form-control">
<option selected="@(Model.SingleMarketSetting.OffTriggerConnection.Equals("AND", StringComparison.InvariantCultureIgnoreCase))">AND</option>
<option selected="@(Model.SingleMarketSetting.OffTriggerConnection.Equals("OR", StringComparison.InvariantCultureIgnoreCase))">OR</option>
</select>
<input type="text" name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|OffTriggerConnection" class="form-control" value="@Model.SingleMarketSetting.OffTriggerConnection" />
</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.";
healthIconColor = "text-danger";
}
if (Model.IsAnalyzerRunning()) {
ptMagicHealthIcon = "fa-cog fa-spin";
}
}
<div class="card-box card-box-mini card-box-ptmagic-outlined @globalIconColor">

View File

@ -1,16 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Core.Main;
using Core.Main.DataObjects;
using Core.Main.DataObjects.PTMagicData;
using Core.MarketAnalyzer;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using System.Threading;
namespace Monitor.Pages {
public class TickerWidgetsModel : _Internal.BasePageModelSecureAJAX {
public ProfitTrailerData PTData = null;
public List<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() {
// Initialize Config
@ -18,6 +25,33 @@ namespace Monitor.Pages {
BindData();
}
public bool IsAnalyzerRunning()
{
bool ownsMutex = false;
try
{
// Try to acquire the mutex.
ownsMutex = mutex.WaitOne(0);
string webRootParent = Directory.GetParent(_hostingEnvironment.WebRootPath).FullName;
string ptMagicRoot = Directory.GetParent(webRootParent).FullName;
string analyzerStatePath = Path.Combine(ptMagicRoot, "_data", "AnalyzerState");
if (System.IO.File.Exists(analyzerStatePath))
{
string state = System.IO.File.ReadAllText(analyzerStatePath);
return state == "1";
}
return false;
}
finally
{
// Only release the mutex if this thread owns it.
if (ownsMutex)
{
mutex.ReleaseMutex();
}
}
}
private void BindData() {
PTData = this.PtDataObject;

View File

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

View File

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

View File

@ -1,7 +1,6 @@
//
// The settings below offer a basic example of some of the options available when using PTMagic.
// You should take your time and adjust these settings according to your own personal preferences, and settings.
//
// You should take your time and adjust these settings according to your own personal preferences.
// Always test your PTMagic settings by running a Profit Trailer bot in TESTMODE, to make sure
// it is performing as you expect.
//
@ -11,19 +10,22 @@
{
"AnalyzerSettings": {
"MarketAnalyzer": {
"StoreDataMaxHours": 48, // Number of hours to store market data
"IntervalMinutes": 2, // Interval in minutes for PTMagic to check market trends and triggers
"ExcludeMainCurrency": true, // Excludes the main currency (for example BTC) from market trend analysis
"StoreDataMaxHours": 48, // Number of hours to store market data
"IntervalMinutes": 2, // Interval in minutes for PTMagic to check market trends and triggers
"ExcludeMainCurrency": true, // Excludes the main currency (for example BTC, if you are trading against BTC) from market trend analysis
"MarketTrends": [
{
"Name": "1h", // UNIQUE market trend name (to be referenced by your triggers below)
"Platform": "Exchange", // Platform to grab prices from (Allowed values are: CoinMarketCap, Exchange)
"MaxMarkets": 50, // Number of markets/pairs to analyze sorted by 24h volume
"TrendMinutes": 60, // Number of minutes to build a trend (1440 = 24h, 720 = 12h, 60 = 1h)
"TrendCurrency": "Market", // Trend Currency to build the trend against. If set to "Fiat", the trend will take the USD value of your main currency into account to build the trend. "Market" will build a trend against your base currency, such as BTC or USDT.
"TrendThreshold": 15, // Any coin that is above 15% or below -15% for this timeframe will not be used when calculating the market average.
"DisplayGraph": false, // Use this trend in the graph on the PTM Monitor dashboard and market analyzer?
"DisplayOnMarketAnalyzerList": false // Disply this trend on the PTM Monitor market analyzer?
"Name": "1h", // UNIQUE market trend name (to be referenced by your triggers below)
"Platform": "Exchange", // Platform to grab prices from (Allowed values are: CoinMarketCap, Exchange)
"MaxMarkets": 50, // Number of markets/pairs to analyze sorted by 24h volume
"TrendMinutes": 60, // Number of minutes to build a trend (1440 = 24h, 720 = 12h, 60 = 1h)
"TrendCurrency": "Market", // Trend Currency to build the trend against. If set to "Fiat", the trend will
// take the USD value of your main currency into account to build the trend.
// "Market" will build a trend against your base currency, such as BTC or USDT.
"TrendThreshold": 15, // Any coin that is above 15% or below -15% for this timeframe will be considered an outlier,
// and not used when calculating the market average.
"DisplayGraph": false, // Use this trend in the graph on the PTM Monitor dashboard and market analyzer
"DisplayOnMarketAnalyzerList": false // Disply this trend for all coins on the PTM Monitor market analyzer
},
{
"Name": "6h",
@ -32,8 +34,8 @@
"TrendMinutes": 360,
"TrendCurrency": "Market",
"TrendThreshold": 30,
"DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true
"DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true
},
{
"Name": "12h",
@ -42,8 +44,8 @@
"TrendMinutes": 720,
"TrendCurrency": "Market",
"TrendThreshold": 50,
"DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true
"DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true
},
{
"Name": "24h",
@ -52,73 +54,89 @@
"TrendMinutes": 1440,
"TrendCurrency": "Market",
"TrendThreshold": 75,
"DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true
"DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true
}
]
},
// ================================ GLOBAL SETTINGS ================================
//
"GlobalSettings": [ // Global settings for Profit Trailer properties
//
// ===================================================================================
// -----------------------------
//
// ===================================================================================
// Each setting here is checked in order. If it is true, the analysis stops. If it is false, it moves on to check the next setting.
// This way, you don't need to define ranges for each setting, just the minimums or maximums.
// -----------------------------
{
"SettingName": "EndOfTheWorld", // ANY UNIQUE name of your setting
"TriggerConnection": "AND", // Define if triggers will be connected by AND or OR
"Triggers": [ // Your triggers for this setting. You can use any of your defined trends from above
"SettingName": "EndOfTheWorld", // ANY UNIQUE name of your setting
"TriggerConnection": "AND", // Define if triggers will be connected by AND or OR.
//If you give each trigger a Tag, then you can use more robust boolean logic, such as: (A && B) || (B && C)
"Triggers": [ // Your triggers for this setting. You can use any of your defined trends from above
{
"MarketTrendName": "1h", // Reference to the market trend specified above
"MaxChange": 0 // The maximum value for this trigger to be true. (Any value below "0" will trigger this)
"Tag": "A", // OPTIONAL: Give your triggers Tags, so you can use more robust boolean logic, such as: (A && B) || (C && D)
"MarketTrendName": "1h", // Reference to the market trend specified above
"MaxChange": 0 // The maximum value for this trigger. (Any value below "0" will trigger this)
},
{
"Tag": "B",
"MarketTrendName": "12h",
"MaxChange": -2
},
{
"Tag": "C",
"MarketTrendName": "24h",
"MaxChange": -5
}
],
"PairsProperties": { // Changes you wish to make to your PAIRS.properties settings
// Any valid setting from https://wiki.profittrailer.com/en/config can be used here.
// You can use a specific value, or apply a discrete OFFSET or OFFSETPERCENT to the value in your default PAIRS setting.
"PairsProperties": { // Properties for PAIRS.PROPERTIES
// Any valid setting from https://wiki.profittrailer.com/en/config can be used here.
// You can use a specific value, or apply a discrete OFFSET or OFFSETPERCENT to the value in your default PAIRS setting.
"DEFAULT_sell_only_mode_enabled": true,
"DEFAULT_trailing_profit_OFFSETPERCENT": -50
},
"DCAProperties": { // Changes you wish to make to your DCA.properties settings
"DCAProperties": { // Properties for DCA.PROPERTIES
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -75
},
"IndicatorsProperties": { // Changes you wish to make to your INDICATORS.properties settings
}
},
// -----------------------------
// -----------------------------
{
"SettingName": "TankingDown",
"TriggerConnection": "AND",
"TriggerConnection": "AND", // You can use complex boolean logic for some settings, and not others
"Triggers": [
{
"MarketTrendName": "1h",
"MaxChange": 0,
"MinChange": -5 // You can use Maxchange and Minchange together to create a range.
},
{
"MarketTrendName": "12h",
"MaxChange": 0
},
{
"MarketTrendName": "24h", // Any value between -5 and -3 will make this trigger true.
"MaxChange": -3,
"MinChange": -5 // The minimum value for this trigger to be true. (Any value above "-5" will trigger this)
}
],
"PairsProperties": {
"max_trading_pairs_OFFSET": -2,
"DEFAULT_min_buy_volume_OFFSETPERCENT": 100,
//"DEFAULT_initial_cost_OFFSETPERCENT": -50,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -50,
"DEFAULT_trailing_buy_OFFSETPERCENT": 25,
"DEFAULT_trailing_profit_OFFSETPERCENT": -25
"max_trading_pairs_OFFSET": -2,
"DEFAULT_min_buy_volume_OFFSETPERCENT": 100,
//"DEFAULT_initial_cost_OFFSETPERCENT": -50,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -50,
"DEFAULT_trailing_buy_OFFSETPERCENT": 25,
"DEFAULT_trailing_profit_OFFSETPERCENT": -25
},
"DCAProperties": {
//"DEFAULT_DCA_rebuy_timeout_OFFSETPERCENT": 100,
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 25,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -50
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -50
},
"IndicatorsProperties": {
"IndicatorsProperties": {
}
},
// -----------------------------
// -----------------------------
{
"SettingName": "BearSighted",
"TriggerConnection": "AND",
@ -139,16 +157,16 @@
],
"PairsProperties": {
"max_trading_pairs_OFFSET": -1,
//"DEFAULT_initial_cost_OFFSETPERCENT": -25,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -25,
"DEFAULT_trailing_buy_OFFSETPERCENT": 10,
"DEFAULT_trailing_profit_OFFSETPERCENT": -10
//"DEFAULT_initial_cost_OFFSETPERCENT": -25,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -25,
"DEFAULT_trailing_buy_OFFSETPERCENT": 10,
"DEFAULT_trailing_profit_OFFSETPERCENT": -10
},
"DCAProperties": {
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 10,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -10,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -10,
},
"IndicatorsProperties": {
"IndicatorsProperties": {
}
},
// -----------------------------
@ -172,19 +190,19 @@
],
"PairsProperties": {
"max_trading_pairs_OFFSET": 1,
//"DEFAULT_initial_cost_OFFSETPERCENT": 10,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 10,
"DEFAULT_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_A_sell_value_OFFSETPERCENT": 10
//"DEFAULT_initial_cost_OFFSETPERCENT": 10,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 10,
"DEFAULT_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_A_sell_value_OFFSETPERCENT": 10
},
"DCAProperties": {
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 10,
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 10,
},
"IndicatorsProperties": {
"IndicatorsProperties": {
}
},
// -----------------------------
// -----------------------------
{
"SettingName": "ToTheMoon",
"TriggerConnection": "AND",
@ -204,8 +222,8 @@
],
"PairsProperties": {
"max_trading_pairs_OFFSET": 2,
//"DEFAULT_initial_cost_OFFSETPERCENT": 20,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 20,
//"DEFAULT_initial_cost_OFFSETPERCENT": 20,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 20,
"DEFAULT_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_A_sell_value_OFFSETPERCENT": 20
},
@ -213,7 +231,7 @@
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -20,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 20,
},
"IndicatorsProperties": {
"IndicatorsProperties": {
}
},
// -----------------------------
@ -230,49 +248,67 @@
}
}
],
//
// ================================ COIN-SPECIFIC SETTINGS ================================
//
"SingleMarketSettings": [ // Single market/pair settings for Profit Trailer properties
// Any setting from https://wiki.profittrailer.com/en/config marked as COIN (coin-specific) can be used here.
// Only coins that meet the triggered conditions will have the settings applied.
// A variety of SMS can be employed to check for long-term down trends, sideways trends, over-extended uptrends, etc.
// If more than one SMS is true, the settings of the last applied SMS over-rides any prior SMS
"SingleMarketSettings": [ // Single market/pair settings for Profit Trailer properties
// Only coins that meet the triggered conditions will have the settings applied.
// If StopProcessWhenTriggered is false, as the analyzer goes down the list multiple settings
// can be applied to a single coin. This can allow for more complex logic and settings.
// However, if two settings apply the same property, the property from the last setting
// on the list will be the one that is used.
{
"SettingName": "PumpNDumpProtection",
"TriggerConnection": "OR",
//"StopProcessWhenTriggered": true, // No SMS after this will be analyzed or applied if this SMS is true
//"AllowedGlobalSettings": "Default", // You can specify that this setting will only apply when a specific Global setting is active
//"IgnoredGlobalSettings": "Default", // You can specify that this setting will NOT apply when a specific Global setting is active
"SettingName": "BlacklistCoins",
"StopProcessWhenTriggered": true,
"TriggerConnection": "OR", // Just like Global Settings, you can use complex boolean logic for some settings, and not others
"Triggers": [
{
"AgeDaysLowerThan": 21
}
],
"PairsProperties": {
"DEFAULT_trading_enabled": false, // Any setting from PT that begins with DEFAULT_ can be used here.
"DEFAULT_sell_only_mode_enabled": true,
"DEFAULT_DCA_enabled": false
}
},
// -----------------------------
{
"SettingName": "PumpNDumpProtection",
"TriggerConnection": "A || B || C",
"Triggers": [
{
"Tag": "A",
"MarketTrendName": "1h",
"MarketTrendRelation": "Relative", // Relative = The single market trend is compared to the overall trend of the entire market
// Absolute = The Single market trend is considered on its own
"MarketTrendRelation": "Relative", // The relation of the single market trend. Relative = The trend of the coin market
// is compared to the average trend of all other coins in the market market.
// Absolute = Single market trend is considered on it's own, without reference to the market.
"MinChange": 8
},
{
"Tag": "B",
"MarketTrendName": "12h",
"MarketTrendRelation": "Relative",
"MinChange": 10
},
{
"Tag": "C",
"MarketTrendName": "24h",
"MarketTrendRelation": "Relative",
"MinChange": 12
}
],
"OffTriggers": [
{
"HoursSinceTriggered": 3 // Any coin that triggers this setting, will remain under this setting
// for 3 hours, since the last time it triggered.
"OffTriggers": [
{
"HoursSinceTriggered": 3 // Any coin that triggers this setting, will remain under this setting
// for 3 hours, since the last time it triggered.
}
],
"PairsProperties": {
"DEFAULT_sell_only_mode_enabled": "true",
"DEFAULT_DCA_enabled": "false"
"DEFAULT_sell_only_mode_enabled": true,
"DEFAULT_DCA_enabled": false
}
},
// -----------------------------
{
"SettingName": "FreefallBlock",
"TriggerConnection": "OR",
@ -283,14 +319,14 @@
"MaxChange": -5
}
],
"OffTriggers": [
{
"OffTriggers": [
{
"HoursSinceTriggered": 1
}
],
"PairsProperties": {
"DEFAULT_sell_only_mode_enabled": "true",
"DEFAULT_DCA_enabled": "false"
"PairsProperties": {
"DEFAULT_sell_only_mode_enabled": true,
"DEFAULT_DCA_enabled": false
}
}
]

View File

@ -2,33 +2,44 @@
"GeneralSettings": {
"Application": {
"IsEnabled": true, // Enables the PTMagic bot (needs restart to take effect)
"TestMode": false, // If TestMode is active, no properties files will be changed
"TestMode": true, // If TestMode is active, no properties files will be changed
"ProfitTrailerLicense": "ptlicense1asdf234fljlasdf014325ehm", // Your Profit Trailer license key (needed to change your settings)
"ProfitTrailerLicenseXtra": "", // Licenses for additional bots for PTM to update (optional - comma separated list)
"ProfitTrailerServerAPIToken": "", //Your Profit Trailer Server API Token
"ProfitTrailerMonitorURL": "http://localhost:8081/", // The URL to your profit trailer monitor (needed to change your settings)
"ProfitTrailerMonitorURLXtra": "", // URLs for additional bots you want PTM to update (optional - comma separated list)
"ProfitTrailerDefaultSettingName": "default", // Your Profit Trailer default setting name (needed to change your settings)
"Exchange": "Bittrex", // The exchange your are running Profit Trailer on
"TimezoneOffset": "+0:00", // Your timezone offset from UTC time
"FloodProtectionMinutes": 0, // If a price trend is just zig-zagging around its trigger, you may want to protect your settings from getting switched back and forth every minute
"InstanceName": "PT Magic", // The name of the instance of this bot. This will be used in your monitor and your Telegram messages. In case you are running more than one bot, you may set different names to separate them
//"FreeCurrencyConverterAPIKey": "" // If "MainFiatCurrency" above is anything other than USD, you must obtain an API key from https://free.currencyconverterapi.com/free-api-key
"Exchange": "BinanceFutures", // The exchange your are running Profit Trailer on
"FloodProtectionMinutes": 0, // If a price trend is just zig-zagging around its trigger, you may want to protect your settings from
// getting switched back and forth every minute
"InstanceName": "MyBTCbot", // The name of the instance of this bot. This will be used in your monitor and your Telegram messages.
//In case you are running more than one bot, you may set different names to separate them
"CoinMarketCapAPIKey": "", //CoinMarketCap Api
},
"Monitor": {
"IsPasswordProtected": true, // Defines if your monitor will be asking to setup a password on its first start
"OpenBrowserOnStart": false, // If active, a browser window will open as soon as you start the monitor
"Port": 8080, // The port you want to run your monitor on
"RootUrl": "/", // The root Url of your monitor
"AnalyzerChart": "", // By default the chart on the Market Analyzer page will use your default currency against USD. You can change that here. (eg., BTCEUR)
"OpenBrowserOnStart": true, // If active, a browser window will open as soon as you start the monitor
"Port": 8080, // The port you want to run your PTMagic monitor on, to connect via browser. The url will be your IP:Port or localhost:Port
"AnalyzerChart": "", // By default the chart on the Market Analyzer page will use your market currency against USD.
//You can change that here. (eg., BTCEUR)
"LiveTCVTimeframeMinutes": 10, // The timeframe for the live TCV chart on the dashboard
"GraphIntervalMinutes": 60, // The interval for the monitor market trend graph to draw points in minutes
"GraphMaxTimeframeHours": 24, // This will enable you to define the timeframe that your graph for market trends covers in hours
"ProfitsMaxTimeframeDays": 30, // This will enable you to define the timeframe for your dashboard profits graph in days
"RefreshSeconds": 30, // The refresh interval of your monitor main page
"LinkPlatform": "TradingView", // The platform to which the pair name will link if you click on it
"DashboardChartsRefreshSeconds": 30, // The refresh interval of your dashboard charts in seconds
"BagAnalyzerRefreshSeconds": 60,
"BuyAnalyzerRefreshSeconds": 60,
"MaxDashboardBuyEntries": 5, // The number of coins in your Possible Buy List on the dashboard. Set to 0 to hide the list completely
"MaxDashboardBagEntries": 9999, // The number of coins in your Positions List on the dashboard.
"MaxTopMarkets": 20, // The amount of top markets being shown in your Sales Analyzer
"MaxDailySummaries": 10, // The amount of "Last Days" being shown in your Sales Analyzer
"MaxDCAPairs": 25, // For DCA calculations in the DCA Analyzer.
"DefaultDCAMode": "Advanced", // The default DCA mode to use in the DCA Analyzer. Options are "Simple" or "Advanced"
"MaxSettingsLogEntries": 500, // The number of entries in the Global Settings Log on the Status & Summary page
"MaxMonthlySummaries": 10, // The amount of "Last Months" being shown in your Sales Analyzer
"LinkPlatform": "TradingView", // The platform to which the pair name will link if you click on it
"TVCustomLayout": "EbSR85R8", // A TradingView layout to use when clicking on a pair name while using TradingView as your platform
// When saving a custom layout in TV, you will get a URL like this: https://www.tradingview.com/chart/EbSR85R8/
"TvStudyA": "BB@tv-basicstudies", // See available STUDIES at https://www.tradingview.com/wiki/Widget:TradingView_Widget
"TvStudyB": "",
"TvStudyC": "",

View File

@ -1,7 +1,6 @@
//
// The settings below offer a basic example of some of the options available when using PTMagic.
// You should take your time and adjust these settings according to your own personal preferences, and settings.
//
// You should take your time and adjust these settings according to your own personal preferences.
// Always test your PTMagic settings by running a Profit Trailer bot in TESTMODE, to make sure
// it is performing as you expect.
//
@ -11,19 +10,22 @@
{
"AnalyzerSettings": {
"MarketAnalyzer": {
"StoreDataMaxHours": 48, // Number of hours to store market data
"IntervalMinutes": 2, // Interval in minutes for PTMagic to check market trends and triggers
"ExcludeMainCurrency": true, // Excludes the main currency (for example BTC) from market trend analysis
"StoreDataMaxHours": 48, // Number of hours to store market data
"IntervalMinutes": 2, // Interval in minutes for PTMagic to check market trends and triggers
"ExcludeMainCurrency": true, // Excludes the main currency (for example BTC, if you are trading against BTC) from market trend analysis
"MarketTrends": [
{
"Name": "1h", // UNIQUE market trend name (to be referenced by your triggers below)
"Platform": "Exchange", // Platform to grab prices from (Allowed values are: CoinMarketCap, Exchange)
"MaxMarkets": 50, // Number of markets/pairs to analyze sorted by 24h volume
"TrendMinutes": 60, // Number of minutes to build a trend (1440 = 24h, 720 = 12h, 60 = 1h)
"TrendCurrency": "Market", // Trend Currency to build the trend against. If set to "Fiat", the trend will take the USD value of your main currency into account to build the trend. "Market" will build a trend against your base currency, such as BTC or USDT.
"TrendThreshold": 15, // Any coin that is above 15% or below -15% for this timeframe will not be used when calculating the market average.
"DisplayGraph": false, // Use this trend in the graph on the PTM Monitor dashboard and market analyzer?
"DisplayOnMarketAnalyzerList": false // Disply this trend on the PTM Monitor market analyzer?
"Name": "1h", // UNIQUE market trend name (to be referenced by your triggers below)
"Platform": "Exchange", // Platform to grab prices from (Allowed values are: CoinMarketCap, Exchange)
"MaxMarkets": 50, // Number of markets/pairs to analyze sorted by 24h volume
"TrendMinutes": 60, // Number of minutes to build a trend (1440 = 24h, 720 = 12h, 60 = 1h)
"TrendCurrency": "Market", // Trend Currency to build the trend against. If set to "Fiat", the trend will
// take the USD value of your main currency into account to build the trend.
// "Market" will build a trend against your base currency, such as BTC or USDT.
"TrendThreshold": 15, // Any coin that is above 15% or below -15% for this timeframe will be considered an outlier,
// and not used when calculating the market average.
"DisplayGraph": false, // Use this trend in the graph on the PTM Monitor dashboard and market analyzer
"DisplayOnMarketAnalyzerList": false // Disply this trend for all coins on the PTM Monitor market analyzer
},
{
"Name": "6h",
@ -32,8 +34,8 @@
"TrendMinutes": 360,
"TrendCurrency": "Market",
"TrendThreshold": 30,
"DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true
"DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true
},
{
"Name": "12h",
@ -42,8 +44,8 @@
"TrendMinutes": 720,
"TrendCurrency": "Market",
"TrendThreshold": 50,
"DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true
"DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true
},
{
"Name": "24h",
@ -52,73 +54,89 @@
"TrendMinutes": 1440,
"TrendCurrency": "Market",
"TrendThreshold": 75,
"DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true
"DisplayGraph": true,
"DisplayOnMarketAnalyzerList": true
}
]
},
// ================================ GLOBAL SETTINGS ================================
//
"GlobalSettings": [ // Global settings for Profit Trailer properties
//
// ===================================================================================
// -----------------------------
//
// ===================================================================================
// Each setting here is checked in order. If it is true, the analysis stops. If it is false, it moves on to check the next setting.
// This way, you don't need to define ranges for each setting, just the minimums or maximums.
// -----------------------------
{
"SettingName": "EndOfTheWorld", // ANY UNIQUE name of your setting
"TriggerConnection": "AND", // Define if triggers will be connected by AND or OR
"Triggers": [ // Your triggers for this setting. You can use any of your defined trends from above
"SettingName": "EndOfTheWorld", // ANY UNIQUE name of your setting
"TriggerConnection": "AND", // Define if triggers will be connected by AND or OR.
//If you give each trigger a Tag, then you can use more robust boolean logic, such as: (A && B) || (B && C)
"Triggers": [ // Your triggers for this setting. You can use any of your defined trends from above
{
"MarketTrendName": "1h", // Reference to the market trend specified above
"MaxChange": 0 // The maximum value for this trigger to be true. (Any value below "0" will trigger this)
"Tag": "A", // OPTIONAL: Give your triggers Tags, so you can use more robust boolean logic, such as: (A && B) || (C && D)
"MarketTrendName": "1h", // Reference to the market trend specified above
"MaxChange": 0 // The maximum value for this trigger. (Any value below "0" will trigger this)
},
{
"Tag": "B",
"MarketTrendName": "12h",
"MaxChange": -2
},
{
"Tag": "C",
"MarketTrendName": "24h",
"MaxChange": -5
}
],
"PairsProperties": { // Changes you wish to make to your PAIRS.properties settings
// Any valid setting from https://wiki.profittrailer.com/en/config can be used here.
// You can use a specific value, or apply a discrete OFFSET or OFFSETPERCENT to the value in your default PAIRS setting.
"PairsProperties": { // Properties for PAIRS.PROPERTIES
// Any valid setting from https://wiki.profittrailer.com/en/config can be used here.
// You can use a specific value, or apply a discrete OFFSET or OFFSETPERCENT to the value in your default PAIRS setting.
"DEFAULT_sell_only_mode_enabled": true,
"DEFAULT_trailing_profit_OFFSETPERCENT": -50
},
"DCAProperties": { // Changes you wish to make to your DCA.properties settings
"DCAProperties": { // Properties for DCA.PROPERTIES
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -75
},
"IndicatorsProperties": { // Changes you wish to make to your INDICATORS.properties settings
}
},
// -----------------------------
// -----------------------------
{
"SettingName": "TankingDown",
"TriggerConnection": "AND",
"TriggerConnection": "AND", // You can use complex boolean logic for some settings, and not others
"Triggers": [
{
"MarketTrendName": "1h",
"MaxChange": 0,
"MinChange": -5 // You can use Maxchange and Minchange together to create a range.
},
{
"MarketTrendName": "12h",
"MaxChange": 0
},
{
"MarketTrendName": "24h", // Any value between -5 and -3 will make this trigger true.
"MaxChange": -3,
"MinChange": -5 // The minimum value for this trigger to be true. (Any value above "-5" will trigger this)
}
],
"PairsProperties": {
"max_trading_pairs_OFFSET": -2,
"DEFAULT_min_buy_volume_OFFSETPERCENT": 100,
//"DEFAULT_initial_cost_OFFSETPERCENT": -50,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -50,
"DEFAULT_trailing_buy_OFFSETPERCENT": 25,
"DEFAULT_trailing_profit_OFFSETPERCENT": -25
"max_trading_pairs_OFFSET": -2,
"DEFAULT_min_buy_volume_OFFSETPERCENT": 100,
//"DEFAULT_initial_cost_OFFSETPERCENT": -50,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -50,
"DEFAULT_trailing_buy_OFFSETPERCENT": 25,
"DEFAULT_trailing_profit_OFFSETPERCENT": -25
},
"DCAProperties": {
//"DEFAULT_DCA_rebuy_timeout_OFFSETPERCENT": 100,
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 25,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -50
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -50
},
"IndicatorsProperties": {
"IndicatorsProperties": {
}
},
// -----------------------------
// -----------------------------
{
"SettingName": "BearSighted",
"TriggerConnection": "AND",
@ -139,16 +157,16 @@
],
"PairsProperties": {
"max_trading_pairs_OFFSET": -1,
//"DEFAULT_initial_cost_OFFSETPERCENT": -25,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -25,
"DEFAULT_trailing_buy_OFFSETPERCENT": 10,
"DEFAULT_trailing_profit_OFFSETPERCENT": -10
//"DEFAULT_initial_cost_OFFSETPERCENT": -25,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": -25,
"DEFAULT_trailing_buy_OFFSETPERCENT": 10,
"DEFAULT_trailing_profit_OFFSETPERCENT": -10
},
"DCAProperties": {
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": 10,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -10,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": -10,
},
"IndicatorsProperties": {
"IndicatorsProperties": {
}
},
// -----------------------------
@ -172,19 +190,19 @@
],
"PairsProperties": {
"max_trading_pairs_OFFSET": 1,
//"DEFAULT_initial_cost_OFFSETPERCENT": 10,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 10,
"DEFAULT_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_A_sell_value_OFFSETPERCENT": 10
//"DEFAULT_initial_cost_OFFSETPERCENT": 10,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 10,
"DEFAULT_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_A_sell_value_OFFSETPERCENT": 10
},
"DCAProperties": {
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 10,
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 10,
},
"IndicatorsProperties": {
"IndicatorsProperties": {
}
},
// -----------------------------
// -----------------------------
{
"SettingName": "ToTheMoon",
"TriggerConnection": "AND",
@ -204,8 +222,8 @@
],
"PairsProperties": {
"max_trading_pairs_OFFSET": 2,
//"DEFAULT_initial_cost_OFFSETPERCENT": 20,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 20,
//"DEFAULT_initial_cost_OFFSETPERCENT": 20,
//"DEFAULT_initial_cost_percentage_OFFSETPERCENT": 20,
"DEFAULT_trailing_buy_OFFSETPERCENT": -10,
"DEFAULT_A_sell_value_OFFSETPERCENT": 20
},
@ -213,7 +231,7 @@
"DEFAULT_DCA_trailing_buy_OFFSETPERCENT": -20,
"DEFAULT_DCA_trailing_profit_OFFSETPERCENT": 20,
},
"IndicatorsProperties": {
"IndicatorsProperties": {
}
},
// -----------------------------
@ -230,49 +248,67 @@
}
}
],
//
// ================================ COIN-SPECIFIC SETTINGS ================================
//
"SingleMarketSettings": [ // Single market/pair settings for Profit Trailer properties
// Any setting from https://wiki.profittrailer.com/en/config marked as COIN (coin-specific) can be used here.
// Only coins that meet the triggered conditions will have the settings applied.
// A variety of SMS can be employed to check for long-term down trends, sideways trends, over-extended uptrends, etc.
// If more than one SMS is true, the settings of the last applied SMS over-rides any prior SMS
"SingleMarketSettings": [ // Single market/pair settings for Profit Trailer properties
// Only coins that meet the triggered conditions will have the settings applied.
// If StopProcessWhenTriggered is false, as the analyzer goes down the list multiple settings
// can be applied to a single coin. This can allow for more complex logic and settings.
// However, if two settings apply the same property, the property from the last setting
// on the list will be the one that is used.
{
"SettingName": "PumpNDumpProtection",
"TriggerConnection": "OR",
//"StopProcessWhenTriggered": true, // No SMS after this will be analyzed or applied if this SMS is true
//"AllowedGlobalSettings": "Default", // You can specify that this setting will only apply when a specific Global setting is active
//"IgnoredGlobalSettings": "Default", // You can specify that this setting will NOT apply when a specific Global setting is active
"SettingName": "BlacklistCoins",
"StopProcessWhenTriggered": true,
"TriggerConnection": "OR", // Just like Global Settings, you can use complex boolean logic for some settings, and not others
"Triggers": [
{
"AgeDaysLowerThan": 21
}
],
"PairsProperties": {
"DEFAULT_trading_enabled": false, // Any setting from PT that begins with DEFAULT_ can be used here.
"DEFAULT_sell_only_mode_enabled": true,
"DEFAULT_DCA_enabled": false
}
},
// -----------------------------
{
"SettingName": "PumpNDumpProtection",
"TriggerConnection": "A || B || C",
"Triggers": [
{
"Tag": "A",
"MarketTrendName": "1h",
"MarketTrendRelation": "Relative", // Relative = The single market trend is compared to the overall trend of the entire market
// Absolute = The Single market trend is considered on its own
"MarketTrendRelation": "Relative", // The relation of the single market trend. Relative = The trend of the coin market
// is compared to the average trend of all other coins in the market market.
// Absolute = Single market trend is considered on it's own, without reference to the market.
"MinChange": 8
},
{
"Tag": "B",
"MarketTrendName": "12h",
"MarketTrendRelation": "Relative",
"MinChange": 10
},
{
"Tag": "C",
"MarketTrendName": "24h",
"MarketTrendRelation": "Relative",
"MinChange": 12
}
],
"OffTriggers": [
{
"HoursSinceTriggered": 3 // Any coin that triggers this setting, will remain under this setting
// for 3 hours, since the last time it triggered.
"OffTriggers": [
{
"HoursSinceTriggered": 3 // Any coin that triggers this setting, will remain under this setting
// for 3 hours, since the last time it triggered.
}
],
"PairsProperties": {
"DEFAULT_sell_only_mode_enabled": "true",
"DEFAULT_DCA_enabled": "false"
"DEFAULT_sell_only_mode_enabled": true,
"DEFAULT_DCA_enabled": false
}
},
// -----------------------------
{
"SettingName": "FreefallBlock",
"TriggerConnection": "OR",
@ -283,14 +319,14 @@
"MaxChange": -5
}
],
"OffTriggers": [
{
"OffTriggers": [
{
"HoursSinceTriggered": 1
}
],
"PairsProperties": {
"DEFAULT_sell_only_mode_enabled": "true",
"DEFAULT_DCA_enabled": "false"
"PairsProperties": {
"DEFAULT_sell_only_mode_enabled": true,
"DEFAULT_DCA_enabled": false
}
}
]

View File

@ -2,33 +2,44 @@
"GeneralSettings": {
"Application": {
"IsEnabled": true, // Enables the PTMagic bot (needs restart to take effect)
"TestMode": false, // If TestMode is active, no properties files will be changed
"TestMode": true, // If TestMode is active, no properties files will be changed
"ProfitTrailerLicense": "ptlicense1asdf234fljlasdf014325ehm", // Your Profit Trailer license key (needed to change your settings)
"ProfitTrailerLicenseXtra": "", // Licenses for additional bots for PTM to update (optional - comma separated list)
"ProfitTrailerServerAPIToken": "", //Your Profit Trailer Server API Token
"ProfitTrailerMonitorURL": "http://localhost:8081/", // The URL to your profit trailer monitor (needed to change your settings)
"ProfitTrailerMonitorURLXtra": "", // URLs for additional bots you want PTM to update (optional - comma separated list)
"ProfitTrailerDefaultSettingName": "default", // Your Profit Trailer default setting name (needed to change your settings)
"Exchange": "Bittrex", // The exchange your are running Profit Trailer on
"TimezoneOffset": "+0:00", // Your timezone offset from UTC time
"FloodProtectionMinutes": 0, // If a price trend is just zig-zagging around its trigger, you may want to protect your settings from getting switched back and forth every minute
"InstanceName": "PT Magic", // The name of the instance of this bot. This will be used in your monitor and your Telegram messages. In case you are running more than one bot, you may set different names to separate them
"Exchange": "BinanceFutures", // The exchange your are running Profit Trailer on
"FloodProtectionMinutes": 0, // If a price trend is just zig-zagging around its trigger, you may want to protect your settings from
// getting switched back and forth every minute
"InstanceName": "MyBTCbot", // The name of the instance of this bot. This will be used in your monitor and your Telegram messages.
//In case you are running more than one bot, you may set different names to separate them
"CoinMarketCapAPIKey": "", //CoinMarketCap Api
},
"Monitor": {
"IsPasswordProtected": true, // Defines if your monitor will be asking to setup a password on its first start
"OpenBrowserOnStart": false, // If active, a browser window will open as soon as you start the monitor
"Port": 8080, // The port you want to run your monitor on
"RootUrl": "/", // The root Url of your monitor
"AnalyzerChart": "", // By default the chart on the Market Analyzer page will use your default currency against USD. You can change that here. (eg., BTCEUR)
"OpenBrowserOnStart": true, // If active, a browser window will open as soon as you start the monitor
"Port": 8080, // The port you want to run your PTMagic monitor on, to connect via browser. The url will be your IP:Port or localhost:Port
"AnalyzerChart": "", // By default the chart on the Market Analyzer page will use your market currency against USD.
//You can change that here. (eg., BTCEUR)
"LiveTCVTimeframeMinutes": 10, // The timeframe for the live TCV chart on the dashboard
"GraphIntervalMinutes": 60, // The interval for the monitor market trend graph to draw points in minutes
"GraphMaxTimeframeHours": 24, // This will enable you to define the timeframe that your graph for market trends covers in hours
"ProfitsMaxTimeframeDays": 30, // This will enable you to define the timeframe for your dashboard profits graph in days
"RefreshSeconds": 30, // The refresh interval of your monitor main page
"LinkPlatform": "TradingView", // The platform to which the pair name will link if you click on it
"DashboardChartsRefreshSeconds": 30, // The refresh interval of your dashboard charts in seconds
"BagAnalyzerRefreshSeconds": 60,
"BuyAnalyzerRefreshSeconds": 60,
"MaxDashboardBuyEntries": 5, // The number of coins in your Possible Buy List on the dashboard. Set to 0 to hide the list completely
"MaxDashboardBagEntries": 9999, // The number of coins in your Positions List on the dashboard.
"MaxTopMarkets": 20, // The amount of top markets being shown in your Sales Analyzer
"MaxDailySummaries": 10, // The amount of "Last Days" being shown in your Sales Analyzer
"MaxDCAPairs": 25, // For DCA calculations in the DCA Analyzer.
"DefaultDCAMode": "Advanced", // The default DCA mode to use in the DCA Analyzer. Options are "Simple" or "Advanced"
"MaxSettingsLogEntries": 500, // The number of entries in the Global Settings Log on the Status & Summary page
"MaxMonthlySummaries": 10, // The amount of "Last Months" being shown in your Sales Analyzer
"LinkPlatform": "TradingView", // The platform to which the pair name will link if you click on it
"TVCustomLayout": "EbSR85R8", // A TradingView layout to use when clicking on a pair name while using TradingView as your platform
// When saving a custom layout in TV, you will get a URL like this: https://www.tradingview.com/chart/EbSR85R8/
"TvStudyA": "BB@tv-basicstudies", // See available STUDIES at https://www.tradingview.com/wiki/Widget:TradingView_Widget
"TvStudyB": "",
"TvStudyC": "",