Merge pull request #338 from PTMagicians/develop

2.7.1
This commit is contained in:
HojouFotytu 2024-01-23 01:46:57 +09:00 committed by GitHub
commit 111c1b153b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 2163 additions and 1216 deletions

View File

@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
namespace Core.Main.DataObjects.PTMagicData
{
#region Settings Objects
public class GeneralSettingsWrapper
{
public GeneralSettings GeneralSettings { get; set; }
@ -21,7 +19,7 @@ namespace Core.Main.DataObjects.PTMagicData
public SecureSettings SecureSettings { get; set; }
}
#region GeneralSettings
public class GeneralSettings
{
public Application Application { get; set; }
@ -43,12 +41,10 @@ namespace Core.Main.DataObjects.PTMagicData
public string ProfitTrailerDefaultSettingName { get; set; } = "default";
public int FloodProtectionMinutes { get; set; } = 15;
public string Exchange { get; set; }
public double StartBalance { get; set; } = 0;
public string InstanceName { get; set; } = "PT Magic";
public string TimezoneOffset { get; set; } = "+0:00";
public string MainFiatCurrency { get; set; } = "USD";
public string CoinMarketCapAPIKey { get; set; }
public string FreeCurrencyConverterAPIKey { get; set; }
//public string FreeCurrencyConverterAPIKey { get; set; }
}
public class Monitor
@ -61,9 +57,11 @@ namespace Core.Main.DataObjects.PTMagicData
public string AnalyzerChart { get; set; } = "";
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 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;
@ -105,9 +103,7 @@ namespace Core.Main.DataObjects.PTMagicData
public Int64 ChatId { get; set; }
public bool SilentMode { get; set; } = false;
}
#endregion
#region AnalyzerSettings
public class AnalyzerSettings
{
public MarketAnalyzer MarketAnalyzer { get; set; }
@ -130,6 +126,7 @@ namespace Core.Main.DataObjects.PTMagicData
[DefaultValue("Market")]
public string TrendCurrency { get; set; } = "Market";
public string SplitCamelCaseName { get; set; }
[DefaultValue(0)]
public int MaxMarkets { get; set; } = 0;
@ -243,18 +240,12 @@ namespace Core.Main.DataObjects.PTMagicData
[DefaultValue(0)]
public int HoursSinceTriggered { get; set; } = 0;
}
#endregion
#region SecureSettings
public class SecureSettings
{
public string MonitorPassword { get; set; } = "";
}
#endregion
#endregion
#region Market Analyzer Objects
public class Market
{
public int Position;
@ -290,9 +281,7 @@ namespace Core.Main.DataObjects.PTMagicData
public DateTime FirstSeen = Constants.confMinDate;
public DateTime LastSeen = Constants.confMaxDate;
}
#endregion
#region Summary Objects
public class Summary
{
public string Version { get; set; } = "";
@ -323,13 +312,25 @@ namespace Core.Main.DataObjects.PTMagicData
public string SellStrategy { get; set; } = "";
public string MainMarket { get; set; } = "";
public double MainMarketPrice { get; set; } = 0;
public string MainFiatCurrency { get; set; } = "USD";
public double MainFiatCurrencyExchangeRate { get; set; } = 1;
private PropertiesData _propertiesData = new PropertiesData();
public string MainFiatCurrency => _propertiesData.Currency;
private MiscData _miscData = new MiscData();
public double MainFiatCurrencyExchangeRate => _miscData.FiatConversionRate;
public List<StrategySummary> BuyStrategies { get; set; } = new List<StrategySummary>();
public List<StrategySummary> SellStrategies { get; set; } = new List<StrategySummary>();
public List<StrategySummary> DCABuyStrategies { get; set; } = new List<StrategySummary>();
public List<StrategySummary> DCASellStrategies { get; set; } = new List<StrategySummary>();
}
public class PropertiesData
{
public string Currency { get; set; } = "";
public bool Shorting { get; set; } = false;
public bool Margin { get; set; } = false;
public string UpTime { get; set; } = "";
public int Port { get; set; } = 0;
public bool IsLeverageExchange { get; set; } = false;
public string BaseUrl { get; set; } = "";
}
public class StrategySummary
{
@ -363,22 +364,7 @@ namespace Core.Main.DataObjects.PTMagicData
public List<StrategySummary> DCABuyStrategies { get; set; } = new List<StrategySummary>();
public List<StrategySummary> DCASellStrategies { get; set; } = new List<StrategySummary>();
}
#endregion
#region Properties Objects
public class Properties
{
public string Currency { get; set; } = "";
public bool Shorting { get; set; } = false;
public bool Margin { get; set; } = false;
public string UpTime { get; set; } = "";
public int Port { get; set; } = 0;
public bool IsLeverageExchange { get; set; } = false;
public string BaseUrl { get; set; } = "";
}
#endregion
#region Transaction Objects
public class Transaction
{
public string GUID { get; set; } = "";
@ -396,9 +382,7 @@ namespace Core.Main.DataObjects.PTMagicData
return result.DateTime;
}
}
#endregion
#region SingleMarketSettingSummary Objects
public class SingleMarketSettingSummary
{
public string Market { get; set; } = "";
@ -414,24 +398,93 @@ namespace Core.Main.DataObjects.PTMagicData
public double LastPrice { get; set; } = 0;
public double Last24hVolume { get; set; } = 0;
}
#endregion
#region Profit Trailer JSON Objects
// 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 SellLogData
public class StatsData
{
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 SalesToday { get; set; }
public double ProfitToday { get; set; }
public double ProfitPercToday { get; set; }
public double SalesYesterday { get; set; }
public double ProfitYesterday { get; set; }
public double ProfitPercYesterday { get; set; }
public double SalesWeek { get; set; }
public double ProfitWeek { get; set; }
public double ProfitPercWeek { get; set; }
public double SalesThisMonth { get; set; }
public double ProfitThisMonth { get; set; }
public double ProfitPercThisMonth { get; set; }
public double SalesLastMonth { get; set; }
public double ProfitLastMonth { get; set; }
public double ProfitPercLastMonth { get; set; }
public double TotalProfit { get; set; }
public double TotalSales { get; set; }
public double TotalProfitPerc { get; set; }
public double FundingToday { get; set; }
public double FundingYesterday { get; set; }
public double FundingWeek { get; set; }
public double FundingThisMonth { get; set; }
public double FundingLastMonth { get; set; }
public double FundingTotal { get; set; }
public double TotalFundingPerc { get; set; }
public double TotalFundingPercYesterday { get; set; }
public double TotalFundingPercWeek { get; set; }
public double TotalFundingPercToday { get; set; }
}
public class DailyPNLData
{
public string Date { get; set; }
public double CumulativeProfitCurrency { get; set; }
public double Order { get; set; }
}
public class DailyTCVData
{
public string Date { get; set; }
public double TCV { get; set; }
public double Order { get; set; }
}
public class MonthlyStatsData
{
public string Month { get; set; }
public double TotalProfitCurrency { get; set; }
public double TotalSales { get; set; }
public double AvgGrowth { get; set; }
public double Order { get; set; }
}
public class ProfitablePairsData
{
public string Coin { get; set; }
public double ProfitCurrency { get; set; }
public int SoldTimes { get; set; }
public double Avg { get; set; }
}
public class DailyStatsData
{
public string Date { get; set; }
public int TotalSales { get; set; }
public int TotalBuys { get; set; }
public double TotalProfit { get; set; }
public double AvgProfit { get; set; }
public double AvgGrowth { get; set; }
}
public class PTStrategy
{
public string type { get; set; }
@ -507,16 +560,17 @@ namespace Core.Main.DataObjects.PTMagicData
public List<Strategy> BuyStrategies { get; set; } = new List<Strategy>();
}
public class SummaryData
public class MiscData
{
public double Balance { get; set; }
public double StartBalance { get; set; }
public double FiatConversionRate { get; set; }
public double PairsValue { get; set; }
public double DCAValue { get; set; }
public double PendingValue { get; set; }
public double DustValue { get; set; }
public string Market { get; set; }
public string TotalCurrentValue { get; set; }
public string TimeZoneOffset { get; set; }
}
#endregion
}

View File

@ -6,6 +6,7 @@ using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Diagnostics;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Core.Main.DataObjects.PTMagicData;
@ -14,18 +15,60 @@ namespace Core.Main.DataObjects
public class ProfitTrailerData
{
private SummaryData _summary = null;
private Properties _properties = null;
private List<SellLogData> _sellLog = new List<SellLogData>();
private MiscData _misc = null;
private PropertiesData _properties = null;
private StatsData _stats = null;
private List<DailyPNLData> _dailyPNL = new List<DailyPNLData>();
private List<DailyTCVData> _dailyTCV = new List<DailyTCVData>();
private List<MonthlyStatsData> _monthlyStats = new List<MonthlyStatsData>();
private List<ProfitablePairsData> _profitablePairs = new List<ProfitablePairsData>();
private List<DailyStatsData> _dailyStats = new List<DailyStatsData>();
private decimal? _totalProfit = null;
public decimal? TotalProfit
{
get { return _totalProfit; }
set { _totalProfit = value; }
}
private decimal? _totalSales = null;
public decimal? TotalSales
{
get { return _totalSales; }
set { _totalSales = value; }
}
//private List<SellLogData> _sellLog = new List<SellLogData>();
private List<DCALogData> _dcaLog = new List<DCALogData>();
private List<BuyLogData> _buyLog = new List<BuyLogData>();
private string _ptmBasePath = "";
private PTMagicConfiguration _systemConfiguration = null;
private TransactionData _transactionData = null;
private DateTime _buyLogRefresh = DateTime.UtcNow, _sellLogRefresh = DateTime.UtcNow, _dcaLogRefresh = DateTime.UtcNow, _summaryRefresh = DateTime.UtcNow, _propertiesRefresh = DateTime.UtcNow;
private volatile object _buyLock = new object(), _sellLock = new object(), _dcaLock = new object(), _summaryLock = new object(), _propertiesLock = new object();
private DateTime _dailyPNLRefresh = DateTime.UtcNow;
private DateTime _dailyTCVRefresh = DateTime.UtcNow;
private DateTime _monthlyStatsRefresh = DateTime.UtcNow;
private DateTime _statsRefresh = DateTime.UtcNow;
private DateTime _buyLogRefresh = DateTime.UtcNow;
//private DateTime _sellLogRefresh = DateTime.UtcNow;
private DateTime _dcaLogRefresh = DateTime.UtcNow;
private DateTime _miscRefresh = DateTime.UtcNow;
private DateTime _propertiesRefresh = DateTime.UtcNow;
private DateTime _profitablePairsRefresh = DateTime.UtcNow;
private DateTime _dailyStatsRefresh = DateTime.UtcNow;
private volatile object _dailyPNLLock = new object();
private volatile object _dailyTCVLock = new object();
private volatile object _monthlyStatsLock = new object();
private volatile object _statsLock = new object();
private volatile object _buyLock = new object();
private volatile object _sellLock = new object();
private volatile object _dcaLock = new object();
private volatile object _miscLock = new object();
private volatile object _propertiesLock = new object();
private volatile object _profitablePairsLock = new object();
private volatile object _dailyStatsLock = new object();
private TimeSpan? _offsetTimeSpan = null;
public void DoLog(string message)
{
// Implement your logging logic here
Console.WriteLine(message);
}
// Constructor
public ProfitTrailerData(PTMagicConfiguration systemConfiguration)
{
@ -56,27 +99,111 @@ namespace Core.Main.DataObjects
}
}
public SummaryData Summary
public MiscData Misc
{
get
{
if (_summary == null || (DateTime.UtcNow > _summaryRefresh))
if (_misc == null || (DateTime.UtcNow > _miscRefresh))
{
lock (_summaryLock)
lock (_miscLock)
{
// Thread double locking
if (_summary == null || (DateTime.UtcNow > _summaryRefresh))
if (_misc == null || (DateTime.UtcNow > _miscRefresh))
{
_summary = BuildSummaryData(GetDataFromProfitTrailer("api/v2/data/misc"));
_summaryRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
_misc = BuildMiscData(GetDataFromProfitTrailer("api/v2/data/misc"));
_miscRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
}
}
}
return _summary;
return _misc;
}
}
public Properties Properties
private MiscData BuildMiscData(dynamic PTData)
{
return new MiscData()
{
Market = PTData.market,
FiatConversionRate = PTData.priceDataFiatConversionRate,
Balance = PTData.realBalance,
PairsValue = PTData.totalPairsCurrentValue,
DCAValue = PTData.totalDCACurrentValue,
PendingValue = PTData.totalPendingCurrentValue,
DustValue = PTData.totalDustCurrentValue,
StartBalance = PTData.startBalance,
TotalCurrentValue = PTData.totalCurrentValue,
TimeZoneOffset = PTData.timeZoneOffset,
};
}
public List<DailyStatsData> DailyStats
{
get
{
if (_dailyStats == null || DateTime.UtcNow > _dailyStatsRefresh)
{
lock (_dailyStatsLock)
{
if (_dailyStats == null || DateTime.UtcNow > _dailyStatsRefresh)
{
using (var stream = GetDataFromProfitTrailerAsStream("/api/v2/data/stats"))
using (var reader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(reader))
{
JObject basicSection = null;
JObject extraSection = null;
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.PropertyName)
{
if ((string)jsonReader.Value == "basic")
{
jsonReader.Read(); // Move to the value of the "basic" property
basicSection = JObject.Load(jsonReader);
}
else if ((string)jsonReader.Value == "extra")
{
jsonReader.Read(); // Move to the value of the "extra" property
extraSection = JObject.Load(jsonReader);
}
}
if (basicSection != null && extraSection != null)
{
break;
}
}
if (basicSection != null)
{
if (extraSection != null)
{
JArray dailyStatsSection = (JArray)extraSection["dailyStats"];
_dailyStats = dailyStatsSection.Select(j => BuildDailyStatsData(j as JObject)).ToList();
_dailyStatsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
}
}
}
}
}
}
return _dailyStats;
}
}
private DailyStatsData BuildDailyStatsData(dynamic dailyStatsDataJson)
{
return new DailyStatsData()
{
Date = dailyStatsDataJson["date"],
TotalSales = dailyStatsDataJson["totalSales"],
TotalBuys = dailyStatsDataJson["totalBuys"],
TotalProfit = dailyStatsDataJson["totalProfitCurrency"],
AvgProfit = dailyStatsDataJson["avgProfit"],
AvgGrowth = dailyStatsDataJson["avgGrowth"],
};
}
public PropertiesData Properties
{
get
{
@ -96,81 +223,420 @@ namespace Core.Main.DataObjects
return _properties;
}
}
public List<SellLogData> SellLog
private PropertiesData BuildProptertiesData(dynamic PTProperties)
{
return new PropertiesData()
{
Currency = PTProperties.currency,
Shorting = PTProperties.shorting,
Margin = PTProperties.margin,
UpTime = PTProperties.upTime,
Port = PTProperties.port,
IsLeverageExchange = PTProperties.isLeverageExchange,
BaseUrl = PTProperties.baseUrl
};
}
public StatsData Stats
{
get
{
if (_sellLog == null || (DateTime.UtcNow > _sellLogRefresh))
if (_stats == null || DateTime.UtcNow > _statsRefresh)
{
lock (_sellLock)
lock (_statsLock)
{
// Thread double locking
if (_sellLog == null || (DateTime.UtcNow > _sellLogRefresh))
if (_stats == null || DateTime.UtcNow > _statsRefresh)
{
_sellLog.Clear();
// Page through the sales data summarizing it.
bool exitLoop = false;
int pageIndex = 1;
while (!exitLoop)
using (var stream = GetDataFromProfitTrailerAsStream("/api/v2/data/stats"))
using (var reader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(reader))
{
var sellDataPage = GetDataFromProfitTrailer("/api/v2/data/sales?perPage=5000&sort=SOLDDATE&sortDirection=ASCENDING&page=" + pageIndex);
if (sellDataPage != null && sellDataPage.data.Count > 0)
while (jsonReader.Read())
{
// Add sales data page to collection
this.BuildSellLogData(sellDataPage);
pageIndex++;
}
else
if (jsonReader.TokenType == JsonToken.PropertyName && (string)jsonReader.Value == "basic")
{
// All data retrieved
exitLoop = true;
}
}
// Update sell log refresh time
_sellLogRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
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);
break;
}
}
}
return _sellLog;
}
}
public List<SellLogData> SellLogToday
}
return _stats;
}
}
private StatsData BuildStatsData(dynamic statsDataJson)
{
return new StatsData()
{
SalesToday = statsDataJson["totalSalesToday"],
ProfitToday = statsDataJson["totalProfitToday"],
ProfitPercToday = statsDataJson["totalProfitPercToday"],
SalesYesterday = statsDataJson["totalSalesYesterday"],
ProfitYesterday = statsDataJson["totalProfitYesterday"],
ProfitPercYesterday = statsDataJson["totalProfitPercYesterday"],
SalesWeek = statsDataJson["totalSalesWeek"],
ProfitWeek = statsDataJson["totalProfitWeek"],
ProfitPercWeek = statsDataJson["totalProfitPercWeek"],
SalesThisMonth = statsDataJson["totalSalesThisMonth"],
ProfitThisMonth = statsDataJson["totalProfitThisMonth"],
ProfitPercThisMonth = statsDataJson["totalProfitPercThisMonth"],
SalesLastMonth = statsDataJson["totalSalesLastMonth"],
ProfitLastMonth = statsDataJson["totalProfitLastMonth"],
ProfitPercLastMonth = statsDataJson["totalProfitPercLastMonth"],
TotalProfit = statsDataJson["totalProfit"],
TotalSales = statsDataJson["totalSales"],
TotalProfitPerc = statsDataJson["totalProfitPerc"],
FundingToday = statsDataJson["totalFundingToday"],
FundingYesterday = statsDataJson["totalFundingYesterday"],
FundingWeek = statsDataJson["totalFundingWeek"],
FundingThisMonth = statsDataJson["totalFundingThisMonth"],
FundingLastMonth = statsDataJson["totalFundingLastMonth"],
FundingTotal = statsDataJson["totalFunding"],
TotalFundingPerc = statsDataJson["totalFundingPerc"],
TotalFundingPercYesterday = statsDataJson["totalFundingPercYesterday"],
TotalFundingPercWeek = statsDataJson["totalFundingPercWeekPerc"],
TotalFundingPercToday = statsDataJson["totalFundingPercTodayPerc"]
};
}
public List<DailyPNLData> DailyPNL
{
get
{
return SellLog.FindAll(sl => sl.SoldDate.Date == LocalizedTime.DateTime.Date);
if (_dailyPNL == null || DateTime.UtcNow > _dailyPNLRefresh)
{
lock (_dailyPNLLock)
{
if (_dailyPNL == null || DateTime.UtcNow > _dailyPNLRefresh)
{
using (var stream = GetDataFromProfitTrailerAsStream("/api/v2/data/stats"))
using (var reader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(reader))
{
JObject basicSection = null;
JObject extraSection = null;
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.PropertyName)
{
if ((string)jsonReader.Value == "basic")
{
jsonReader.Read(); // Move to the value of the "basic" property
basicSection = JObject.Load(jsonReader);
}
else if ((string)jsonReader.Value == "extra")
{
jsonReader.Read(); // Move to the value of the "extra" property
extraSection = JObject.Load(jsonReader);
}
}
public List<SellLogData> SellLogYesterday
if (basicSection != null && extraSection != null)
{
get
{
return SellLog.FindAll(sl => sl.SoldDate.Date == LocalizedTime.DateTime.AddDays(-1).Date);
break;
}
}
public List<SellLogData> SellLogLast7Days
if (basicSection != null &&
((_totalProfit == null ||
!Decimal.Equals(_totalProfit.Value, basicSection["totalProfit"].Value<decimal>())) ||
(_totalSales == null ||
!Decimal.Equals(_totalSales.Value, basicSection["totalSales"].Value<decimal>()))))
{
_totalProfit = basicSection["totalProfit"].Value<decimal>();
_totalSales = basicSection["totalSales"].Value<decimal>();
if (extraSection != null)
{
JArray dailyPNLSection = (JArray)extraSection["dailyPNLStats"];
_dailyPNL = dailyPNLSection.Select(j => BuildDailyPNLData(j as JObject)).ToList();
_dailyPNLRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
}
}
}
}
}
}
return _dailyPNL;
}
}
private DailyPNLData BuildDailyPNLData(dynamic dailyPNLDataJson)
{
return new DailyPNLData()
{
Date = dailyPNLDataJson["date"],
CumulativeProfitCurrency = dailyPNLDataJson["cumulativeProfitCurrency"],
Order = dailyPNLDataJson["order"],
};
}
public List<ProfitablePairsData> ProfitablePairs
{
get
{
return SellLog.FindAll(sl => sl.SoldDate.Date >= LocalizedTime.DateTime.AddDays(-7).Date);
if (_profitablePairs == null || DateTime.UtcNow > _profitablePairsRefresh)
{
lock (_profitablePairsLock)
{
if (_profitablePairs == null || DateTime.UtcNow > _profitablePairsRefresh)
{
using (var stream = GetDataFromProfitTrailerAsStream("/api/v2/data/stats"))
using (var reader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(reader))
{
JObject basicSection = null;
JObject extraSection = null;
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.PropertyName)
{
if ((string)jsonReader.Value == "basic")
{
jsonReader.Read(); // Move to the value of the "basic" property
basicSection = JObject.Load(jsonReader);
}
else if ((string)jsonReader.Value == "extra")
{
jsonReader.Read(); // Move to the value of the "extra" property
extraSection = JObject.Load(jsonReader);
}
}
public List<SellLogData> SellLogLast30Days
if (basicSection != null && extraSection != null)
{
break;
}
}
if (basicSection != null)
{
if (extraSection != null)
{
JObject profitablePairsSection = (JObject)extraSection["profitablePairs"];
_profitablePairs = new List<ProfitablePairsData>();
int counter = 0;
foreach (var j in profitablePairsSection)
{
if (counter >= _systemConfiguration.GeneralSettings.Monitor.MaxTopMarkets)
{
break;
}
// Process each JObject in the dictionary
JObject profitablePair = (JObject)j.Value;
_profitablePairs.Add(BuildProfitablePairs(profitablePair));
counter++;
}
_profitablePairsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
}
}
}
}
}
}
return _profitablePairs;
}
}
private ProfitablePairsData BuildProfitablePairs(JObject profitablePairsJson)
{
return new ProfitablePairsData()
{
Coin = profitablePairsJson["coin"].Value<string>(),
ProfitCurrency = profitablePairsJson["profitCurrency"].Value<double>(),
SoldTimes = profitablePairsJson["soldTimes"].Value<int>(),
Avg = profitablePairsJson["avg"].Value<double>(),
};
}
public List<DailyTCVData> DailyTCV
{
get
{
return SellLog.FindAll(sl => sl.SoldDate.Date >= LocalizedTime.DateTime.AddDays(-30).Date);
if (_dailyTCV == null || DateTime.UtcNow > _dailyTCVRefresh)
{
lock (_dailyTCVLock)
{
if (_dailyTCV == null || DateTime.UtcNow > _dailyTCVRefresh)
{
using (var stream = GetDataFromProfitTrailerAsStream("/api/v2/data/stats"))
using (var reader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(reader))
{
JObject basicSection = null;
JObject extraSection = null;
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.PropertyName)
{
if ((string)jsonReader.Value == "basic")
{
jsonReader.Read(); // Move to the value of the "basic" property
basicSection = JObject.Load(jsonReader);
}
else if ((string)jsonReader.Value == "extra")
{
jsonReader.Read(); // Move to the value of the "extra" property
extraSection = JObject.Load(jsonReader);
}
}
if (basicSection != null && extraSection != null)
{
break;
}
}
if (basicSection != null)
{
if (extraSection != null)
{
JArray dailyTCVSection = (JArray)extraSection["dailyTCVStats"];
_dailyTCV = dailyTCVSection.Select(j => BuildDailyTCVData(j as JObject)).ToList();
_dailyTCVRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
}
}
}
}
}
}
return _dailyTCV;
}
}
public int GetTotalTCVDays()
{
return DailyTCV?.Count ?? 0;
}
private DailyTCVData BuildDailyTCVData(dynamic dailyTCVDataJson)
{
return new DailyTCVData()
{
Date = dailyTCVDataJson["date"],
TCV = dailyTCVDataJson["TCV"],
Order = dailyTCVDataJson["order"],
};
}
public List<MonthlyStatsData> MonthlyStats
{
get
{
if (_monthlyStats == null || DateTime.UtcNow > _monthlyStatsRefresh)
{
lock (_monthlyStatsLock)
{
if (_monthlyStats == null || DateTime.UtcNow > _monthlyStatsRefresh)
{
using (var stream = GetDataFromProfitTrailerAsStream("/api/v2/data/stats"))
using (var reader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(reader))
{
JObject basicSection = null;
JObject extraSection = null;
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.PropertyName)
{
if ((string)jsonReader.Value == "basic")
{
jsonReader.Read(); // Move to the value of the "basic" property
basicSection = JObject.Load(jsonReader);
}
else if ((string)jsonReader.Value == "extra")
{
jsonReader.Read(); // Move to the value of the "extra" property
extraSection = JObject.Load(jsonReader);
}
}
if (basicSection != null && extraSection != null)
{
break;
}
}
if (basicSection != null)// &&
{
if (extraSection != null)
{
JArray monthlyStatsSection = (JArray)extraSection["monthlyStats"];
_monthlyStats = monthlyStatsSection.Select(j => BuildMonthlyStatsData(j as JObject)).ToList();
_monthlyStatsRefresh = DateTime.UtcNow.AddSeconds(_systemConfiguration.GeneralSettings.Monitor.RefreshSeconds - 1);
}
}
}
}
}
}
return _monthlyStats;
}
}
private MonthlyStatsData BuildMonthlyStatsData(dynamic monthlyStatsDataJson)
{
return new MonthlyStatsData()
{
Month = monthlyStatsDataJson["month"],
TotalSales = monthlyStatsDataJson["totalSales"],
TotalProfitCurrency = monthlyStatsDataJson["totalProfitCurrency"],
AvgGrowth = monthlyStatsDataJson["avgGrowth"],
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
{
get
@ -196,7 +662,6 @@ namespace Core.Main.DataObjects
() =>
{
pendingData = GetDataFromProfitTrailer("/api/v2/data/pending", true);
},
() =>
{
@ -247,41 +712,42 @@ namespace Core.Main.DataObjects
public double GetCurrentBalance()
{
return
(this.Summary.Balance);
(this.Misc.Balance);
}
public double GetPairsBalance()
{
return
(this.Summary.PairsValue);
(this.Misc.PairsValue);
}
public double GetDCABalance()
{
return
(this.Summary.DCAValue);
(this.Misc.DCAValue);
}
public double GetPendingBalance()
{
return
(this.Summary.PendingValue);
(this.Misc.PendingValue);
}
public double GetDustBalance()
{
return
(this.Summary.DustValue);
(this.Misc.DustValue);
}
public double GetSnapshotBalance(DateTime snapshotDateTime)
{
double result = _systemConfiguration.GeneralSettings.Application.StartBalance;
result += this.SellLog.FindAll(sl => sl.SoldDate.Date < snapshotDateTime.Date).Sum(sl => sl.Profit);
result += this.TransactionData.Transactions.FindAll(t => t.UTCDateTime < snapshotDateTime).Sum(t => t.Amount);
// public double GetSnapshotBalance(DateTime snapshotDateTime)
// {
// double result = _misc.StartBalance;
// Calculate holdings for snapshot date
result += this.DCALog.FindAll(pairs => pairs.FirstBoughtDate <= snapshotDateTime).Sum(pairs => pairs.CurrentValue);
// result += this.SellLog.FindAll(sl => sl.SoldDate.Date < snapshotDateTime.Date).Sum(sl => sl.Profit);
// result += this.TransactionData.Transactions.FindAll(t => t.UTCDateTime < snapshotDateTime).Sum(t => t.Amount);
return result;
}
// // Calculate holdings for snapshot date
// result += this.DCALog.FindAll(pairs => pairs.FirstBoughtDate <= snapshotDateTime).Sum(pairs => pairs.CurrentValue);
// return result;
// }
private dynamic GetDataFromProfitTrailer(string callPath, bool arrayReturned = false)
{
@ -308,7 +774,7 @@ namespace Core.Main.DataObjects
response.Close();
// Parse the JSON and build the data sets
if (!arrayReturned)
{
return JObject.Parse(rawBody);
@ -318,62 +784,58 @@ namespace Core.Main.DataObjects
return JArray.Parse(rawBody);
}
}
private Stream GetDataFromProfitTrailerAsStream(string callPath)
{
string url = string.Format("{0}{1}{2}token={3}", _systemConfiguration.GeneralSettings.Application.ProfitTrailerMonitorURL,
callPath,
callPath.Contains("?") ? "&" : "?",
_systemConfiguration.GeneralSettings.Application.ProfitTrailerServerAPIToken);
private SummaryData BuildSummaryData(dynamic PTData)
{
return new SummaryData()
{
Market = PTData.market,
Balance = PTData.realBalance,
PairsValue = PTData.totalPairsCurrentValue,
DCAValue = PTData.totalDCACurrentValue,
PendingValue = PTData.totalPendingCurrentValue,
DustValue = PTData.totalDustCurrentValue
};
// Get the data from PT
Debug.WriteLine(String.Format("{0} - Calling '{1}'", DateTime.UtcNow, url));
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.AutomaticDecompression = DecompressionMethods.GZip;
request.KeepAlive = true;
WebResponse response = request.GetResponse();
return response.GetResponseStream();
}
private Properties BuildProptertiesData(dynamic PTProperties)
{
return new Properties()
{
Currency = PTProperties.currency,
Shorting = PTProperties.shorting,
Margin = PTProperties.margin,
UpTime = PTProperties.upTime,
Port = PTProperties.port,
IsLeverageExchange = PTProperties.isLeverageExchange,
BaseUrl = PTProperties.baseUrl
};
}
private void BuildSellLogData(dynamic rawSellLogData)
{
foreach (var rsld in rawSellLogData.data)
{
SellLogData sellLogData = new SellLogData();
sellLogData.SoldAmount = rsld.soldAmount;
sellLogData.BoughtTimes = rsld.boughtTimes;
sellLogData.Market = rsld.market;
sellLogData.ProfitPercent = rsld.profit;
sellLogData.SoldPrice = rsld.currentPrice;
sellLogData.AverageBuyPrice = rsld.avgPrice;
sellLogData.TotalCost = rsld.totalCost;
sellLogData.Profit = rsld.profitCurrency;
//Convert Unix Timestamp to Datetime
System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, System.DateTimeKind.Utc);
dtDateTime = dtDateTime.AddSeconds((double)rsld.soldDate).ToUniversalTime();
// private void BuildSellLogData(dynamic rawSellLogData)
// {
// foreach (var rsld in rawSellLogData.data)
// {
// SellLogData sellLogData = new SellLogData();
// sellLogData.SoldAmount = rsld.soldAmount;
// sellLogData.BoughtTimes = rsld.boughtTimes;
// sellLogData.Market = rsld.market;
// sellLogData.ProfitPercent = rsld.profit;
// sellLogData.SoldPrice = rsld.currentPrice;
// sellLogData.AverageBuyPrice = rsld.avgPrice;
// sellLogData.TotalCost = rsld.totalCost;
// sellLogData.Profit = rsld.profitCurrency;
// Profit Trailer sales are saved in UTC
DateTimeOffset ptSoldDate = DateTimeOffset.Parse(dtDateTime.Year.ToString() + "-" + dtDateTime.Month.ToString("00") + "-" + dtDateTime.Day.ToString("00") + "T" + dtDateTime.Hour.ToString("00") + ":" + dtDateTime.Minute.ToString("00") + ":" + dtDateTime.Second.ToString("00"), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
// Convert UTC sales time to local offset time
ptSoldDate = ptSoldDate.ToOffset(OffsetTimeSpan);
// //Convert Unix Timestamp to Datetime
// System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, System.DateTimeKind.Utc);
// dtDateTime = dtDateTime.AddSeconds((double)rsld.soldDate).ToUniversalTime();
sellLogData.SoldDate = ptSoldDate.DateTime;
_sellLog.Add(sellLogData);
}
}
// // Profit Trailer sales are saved in UTC
// DateTimeOffset ptSoldDate = DateTimeOffset.Parse(dtDateTime.Year.ToString() + "-" + dtDateTime.Month.ToString("00") + "-" + dtDateTime.Day.ToString("00") + "T" + dtDateTime.Hour.ToString("00") + ":" + dtDateTime.Minute.ToString("00") + ":" + dtDateTime.Second.ToString("00"), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
// // Convert UTC sales time to local offset time
// ptSoldDate = ptSoldDate.ToOffset(OffsetTimeSpan);
// sellLogData.SoldDate = ptSoldDate.DateTime;
// _sellLog.Add(sellLogData);
// }
// }
private void BuildDCALogData(dynamic rawDCALogData, dynamic rawPairsLogData, dynamic rawPendingLogData, dynamic rawWatchModeLogData)
{
@ -388,7 +850,6 @@ namespace Core.Main.DataObjects
// Parse watch only pairs data
_dcaLog.AddRange(ParsePairsData(rawWatchModeLogData, false));
}
// Parse the pairs data from PT to our own common data structure.

View File

@ -14,7 +14,6 @@ namespace Core.Main
{
public class PTMagic
{
public PTMagic(LogHelper log)
{
this.Log = log;
@ -84,7 +83,10 @@ namespace Core.Main
_systemConfiguration = value;
}
}
public class IsAnalzyerRunning
{
private string _isAnalyzerRunning;
}
public System.Timers.Timer Timer
{
get
@ -720,15 +722,6 @@ namespace Core.Main
this.Log.DoLogInfo("No CoinMarketCap API KEY specified! That's ok, but you can't use CoinMarketCap in your settings.analyzer.json");
}
// Check for CurrencyConverterApi Key
if (!this.PTMagicConfiguration.GeneralSettings.Application.FreeCurrencyConverterAPIKey.Equals(""))
{
this.Log.DoLogInfo("FreeCurrencyConverterApi KEY found");
}
else
{
this.Log.DoLogInfo("No FreeCurrencyConverterApi KEY specified. That's ok! But you can only use USD; apply for a key at: https://freecurrencyrates.com/en");
}
}
catch (System.NullReferenceException)
{
@ -871,9 +864,6 @@ namespace Core.Main
// Check for latest GitHub version
this.CheckLatestGitHubVersion(this.LastRuntimeSummary.Version);
// Get latest main fiat currency exchange rate
this.GetMainFiatCurrencyDetails();
// Load current PT files
this.LoadCurrentProfitTrailerProperties();
@ -1127,35 +1117,6 @@ namespace Core.Main
}
}
private void GetMainFiatCurrencyDetails()
{
this.LastRuntimeSummary.MainFiatCurrency = this.LastMainFiatCurrency;
this.LastRuntimeSummary.MainFiatCurrencyExchangeRate = this.LastMainFiatCurrencyExchangeRate;
if (this.LastFiatCurrencyCheck < DateTime.UtcNow.AddHours(-12) && !this.PTMagicConfiguration.GeneralSettings.Application.MainFiatCurrency.Equals("USD", StringComparison.InvariantCultureIgnoreCase))
{
try
{
this.LastRuntimeSummary.MainFiatCurrency = this.PTMagicConfiguration.GeneralSettings.Application.MainFiatCurrency;
this.LastRuntimeSummary.MainFiatCurrencyExchangeRate = BaseAnalyzer.GetMainFiatCurrencyRate(this.PTMagicConfiguration.GeneralSettings.Application.MainFiatCurrency, this.PTMagicConfiguration.GeneralSettings.Application.FreeCurrencyConverterAPIKey, this.Log);
this.LastMainFiatCurrency = this.LastRuntimeSummary.MainFiatCurrency;
this.LastMainFiatCurrencyExchangeRate = this.LastRuntimeSummary.MainFiatCurrencyExchangeRate;
this.LastFiatCurrencyCheck = DateTime.UtcNow;
}
catch (Exception ex)
{
// Fallback to USD in case something went wrong
this.Log.DoLogError("Fixer.io exchange rate check error: " + ex.Message);
this.LastRuntimeSummary.MainFiatCurrency = "USD";
this.LastRuntimeSummary.MainFiatCurrencyExchangeRate = 1;
this.LastMainFiatCurrency = "USD";
this.LastMainFiatCurrencyExchangeRate = 1;
}
}
}
// Get current PT properties
private void LoadCurrentProfitTrailerProperties()
{

View File

@ -437,7 +437,7 @@ namespace Core.ProfitTrailer
}
else
{
strategyText += "<span class=\"label label-warning\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"CONDITIONAL FORMULA\">FORM</span> ";
strategyText += "";
}
}
else

View File

@ -64,6 +64,8 @@
var loadDashboardBottom = function () {
//destroy all d3 svg graph to avoid memory leak
setTimeout(function()
{
$(".nvtooltip").remove();
$("svg > *").remove();
$("svg").remove();
@ -71,6 +73,7 @@
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)

View File

@ -28,7 +28,6 @@
</button>
</span>
</div>
<p><input type="checkbox" name="cbRememberMe"/> <label for="cbRememberMe">Stay logged in</label></p>
</div>
</form>

View File

@ -31,7 +31,7 @@ namespace Monitor.Pages
{
HttpContext.Session.SetString("LoggedIn" + PTMagicConfiguration.GeneralSettings.Monitor.Port.ToString(), DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'"));
PTMagicConfiguration.GeneralSettings.Monitor.IsPasswordProtected = true;
PTMagicConfiguration.WriteGeneralSettings();
//PTMagicConfiguration.WriteGeneralSettings();
if (cbRememberMe != null)
{
if (cbRememberMe.Equals("on", StringComparison.InvariantCultureIgnoreCase))

View File

@ -155,14 +155,14 @@ else
<div class="row">
<div class="col-sm-12">
<div class="card-box">
<h4 class="m-t-0 m-b-20 header-title"><b>Market Trend Averages at @Model.PTMagicConfiguration.GeneralSettings.Application.Exchange</b></h4>
<h4 class="m-t-0 m-b-20 header-title"><b>Market Trends at @Model.PTMagicConfiguration.GeneralSettings.Application.Exchange </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>
<table class="table table-sm">
<thead>
<tr>
<th>Name</th>
<th class="text-right">Markets</th>
<th class="text-right">Timeframe</th>
<th class="text-right">Threshold %</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">Change</th>
</tr>
</thead>
@ -190,9 +190,9 @@ else
}
else
{
<td class="text-right">@marketTrend.TrendThreshold</td>
<td class="text-right">@marketTrend.TrendThreshold %</td>
}
<td class="text-right text-autocolor">@trendChangeOutput%</td>
<td class="text-right text-autocolor bold-text" >@trendChangeOutput%</td>
</tr>
}
}

View File

@ -4,6 +4,7 @@ using System.Linq;
using Core.Main;
using Core.Helper;
using Core.Main.DataObjects.PTMagicData;
using System.Globalization;
namespace Monitor.Pages
{
@ -11,6 +12,7 @@ namespace Monitor.Pages
{
public List<MarketTrend> MarketTrends { get; set; } = new List<MarketTrend>();
public string TrendChartDataJSON = "";
public double DataHours { get; set; }
public void OnGet()
{
@ -28,23 +30,18 @@ namespace Monitor.Pages
private void BuildMarketTrendChartData()
{
List<string> trendChartData = new List<string>();
if (MarketTrends.Count > 0)
{
TrendChartDataJSON = "[";
int mtIndex = 0;
foreach (MarketTrend mt in MarketTrends)
{
if (mt.DisplayGraph)
{
string lineColor = "";
if (mtIndex < Constants.ChartLineColors.Length)
{
lineColor = Constants.ChartLineColors[mtIndex];
}
else
{
lineColor = Constants.ChartLineColors[mtIndex - 20];
}
string lineColor = mtIndex < Constants.ChartLineColors.Length
? Constants.ChartLineColors[mtIndex]
: Constants.ChartLineColors[mtIndex - 20];
if (Summary.MarketTrendChanges.ContainsKey(mt.Name))
{
@ -52,53 +49,66 @@ namespace Monitor.Pages
if (marketTrendChangeSummaries.Count > 0)
{
if (!TrendChartDataJSON.Equals("[")) TrendChartDataJSON += ",";
List<string> trendValues = new List<string>();
TrendChartDataJSON += "{";
TrendChartDataJSON += "key: '" + SystemHelper.SplitCamelCase(mt.Name) + "',";
TrendChartDataJSON += "color: '" + lineColor + "',";
TrendChartDataJSON += "values: [";
// Sort marketTrendChangeSummaries by TrendDateTime
marketTrendChangeSummaries = marketTrendChangeSummaries.OrderBy(m => m.TrendDateTime).ToList();
// Get trend ticks for chart
DateTime currentDateTime = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, DateTime.UtcNow.Day, DateTime.UtcNow.Hour, 0, 0);
TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{
offset = TimeSpan.Zero; // If offset is invalid, set it to zero
}
DateTime currentDateTime = DateTime.UtcNow;
DateTime startDateTime = currentDateTime.AddHours(-PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours);
DateTime endDateTime = currentDateTime;
int trendChartTicks = 0;
// Ensure startDateTime doesn't exceed the available data
DateTime earliestTrendDateTime = marketTrendChangeSummaries.Min(mtc => mtc.TrendDateTime);
startDateTime = startDateTime > earliestTrendDateTime ? startDateTime : earliestTrendDateTime;
DataHours = (currentDateTime - earliestTrendDateTime).TotalHours;
// Cache the result of SplitCamelCase(mt.Name)
string splitCamelCaseName = SystemHelper.SplitCamelCase(mt.Name);
for (DateTime tickTime = startDateTime; tickTime <= endDateTime; tickTime = tickTime.AddMinutes(PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes))
{
List<MarketTrendChange> tickRange = marketTrendChangeSummaries.FindAll(m => m.TrendDateTime >= tickTime).OrderBy(m => m.TrendDateTime).ToList();
if (tickRange.Count > 0)
// Use binary search to find the range of items that match the condition
int index = marketTrendChangeSummaries.BinarySearch(new MarketTrendChange { TrendDateTime = tickTime }, Comparer<MarketTrendChange>.Create((x, y) => x.TrendDateTime.CompareTo(y.TrendDateTime)));
if (index < 0) index = ~index;
if (index < marketTrendChangeSummaries.Count)
{
MarketTrendChange mtc = tickRange.First();
if (tickTime != startDateTime) TrendChartDataJSON += ",\n";
MarketTrendChange mtc = marketTrendChangeSummaries[index];
if (Double.IsInfinity(mtc.TrendChange)) mtc.TrendChange = 0;
TrendChartDataJSON += "{ x: new Date('" + tickTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + mtc.TrendChange.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}";
trendChartTicks++;
// Adjust tickTime to the desired timezone before converting to string
DateTime adjustedTickTime = tickTime.Add(isNegative ? -offset : offset);
trendValues.Add("{ x: new Date('" + adjustedTickTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + mtc.TrendChange.ToString("0.00", CultureInfo.InvariantCulture) + "}");
}
}
// Add most recent tick
List<MarketTrendChange> latestTickRange = marketTrendChangeSummaries.OrderByDescending(m => m.TrendDateTime).ToList();
if (latestTickRange.Count > 0)
{
MarketTrendChange mtc = latestTickRange.First();
if (trendChartTicks > 0) TrendChartDataJSON += ",\n";
if (Double.IsInfinity(mtc.TrendChange)) mtc.TrendChange = 0;
MarketTrendChange latestMtc = marketTrendChangeSummaries.Last();
if (Double.IsInfinity(latestMtc.TrendChange)) latestMtc.TrendChange = 0;
TrendChartDataJSON += "{ x: new Date('" + mtc.TrendDateTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + mtc.TrendChange.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}";
}
TrendChartDataJSON += "]";
TrendChartDataJSON += "}";
// Adjust latestMtc.TrendDateTime to the desired timezone before converting to string
DateTime adjustedLatestTrendDateTime = latestMtc.TrendDateTime.Add(isNegative ? -offset : offset);
trendValues.Add("{ x: new Date('" + adjustedLatestTrendDateTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + latestMtc.TrendChange.ToString("0.00", CultureInfo.InvariantCulture) + "}");
// Use cached splitCamelCaseName
trendChartData.Add("{ key: '" + splitCamelCaseName + "', color: '" + lineColor + "', values: [" + string.Join(",\n", trendValues) + "] }");
mtIndex++;
}
}
}
}
TrendChartDataJSON += "]";
}
}
TrendChartDataJSON = "[" + string.Join(",", trendChartData) + "]";
}
}
}

View File

@ -1,5 +1,6 @@
@page
@model SalesAnalyzer
@using System.Globalization
@{
ViewData["Title"] = "";
}
@ -19,14 +20,11 @@
<thead>
<tr>
@{
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"));
double totalCurrentValue = double.Parse(Model.PTData.Misc.TotalCurrentValue);
string totalCurrentValueString = Model.PTData.Misc.TotalCurrentValue;
}
}
<th class="m-t-0 header-title text-left">Total Account Value: &nbsp; <text class="text-autocolor"> @totalCurrentValueString @Model.Summary.MainMarket </text> <small> <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This is based on your sales history, entries on the Transactions page and any currently held positions."></i></small></th>
<th class="text-right">Starting Value: &nbsp; <text class="text-autocolor"> @Model.PTMagicConfiguration.GeneralSettings.Application.StartBalance &nbsp; @Model.Summary.MainMarket </text> <small> <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This is the starting value found in your settings file"></i></small></th>
<th class="m-t-0 header-title text-left">Total Current Value: &nbsp; <text class="text-autocolor"> @totalCurrentValueString @Model.Summary.MainMarket </text> <small> <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This is based on the Total Current Value reported by Profit Trailer."></i></small></th>
<th class="text-right">Starting Value: &nbsp; <text class="text-autocolor"> @Model.MiscData.StartBalance &nbsp; @Model.Summary.MainMarket </text> <small> <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This is the starting value set in your PT account settings."></i></small></th>
</tr>
</thead>
</table>
@ -35,75 +33,126 @@
</div>
<div class="row">
<div class="col-md-12">
<div class="card-box">
<h4 class="m-t-0 header-title text-center">Cumulative Profits <small> <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This value is based on your sales history"></i></small></h4>
<div class="balance-chart">
<svg style="height:350px;width:100%"></svg>
</div>
</div>
</div>
</div>
@if (Model.PTData.SellLog.Count == 0) {
<div class="row">
<div class="col-md-12">
<div class="card-box">
<h4 class="m-t-0 header-title">No Sales!</h4>
<p>Sorry, but your Profit Trailer did not sell anything so far. Please wait for the bot to have at least one sale and you will start seeing data in here.</p>
</div>
</div>
<div class="col-md-6">
<div class="card-box px-2" style="height:320px;">
<h4 class="m-t-0 header-title"><b>Cumulative Profits</b>
@if (!Model.CumulativeProfitChartDataJSON.Equals("")) {
<div class="cumulative-profit-chart">
<svg style="height:300px;width:100%"></svg>
</div>
} else {
<p>Unable to load graph, no sales data found.</p>
}
</div>
</div>
<div class="col-md-6">
<div class="card-box px-2" style="height:320px;">
<h4 class="m-t-0 header-title"><b>Daily TCV</b>
@if (!Model.TCVChartDataJSON.Equals("")) {
<div class="TCV-chart">
<svg style="height:300px;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">
<div class="card-box px-3" style="height:340px;">
@{
var days = Math.Min(Model.DailyStats.Count, 30);
}
<h4 class="m-t-0 m-b-20 header-title"><b>Daily Buys/Sales (@days Days) </b>
<div class="sales-chart">
<svg style="height:300px;width:100%"></svg>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card-box px-3" style="height:340px;">
<h4 class="m-t-0 m-b-20 header-title"><b>Daily Profit (All Time) </b>
@if (!Model.ProfitChartDataJSON.Equals("")) {
<div class="profit-chart">
<svg style="height:300px;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">
<div class="card-box">
<h4 class="m-t-0 header-title">Sales Analysis</h4>
@{
double totalProfit = 0;
totalProfit = Model.PTData.SellLog.Sum(s => s.Profit);
double totalProfitFiat = Math.Round(totalProfit * Model.Summary.MainMarketPrice, 2);
double percentGain = Math.Round(totalProfit / Model.PTMagicConfiguration.GeneralSettings.Application.StartBalance * 100, 2);
double avgDailyGain = Model.DailyGains.Values.Average(dg => dg);
double avgMonthlyGain = Model.MonthlyGains.Values.Average(dg => dg);
string percentGainText = percentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
if (Model.PTData.TransactionData.Transactions.Count > 0) {
percentGainText = "<i class=\"fa fa-info-circle text-muted\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"You have added at least one manual transaction, so the total gain percentage cannot be calculated.\"></i>";
}
}
int totalDays = Model.PTData.DailyPNL.Count;
double startBalance = Model.MiscData.StartBalance;
double totalSales = Model.PTData.Stats.TotalSales;
double totalProfit = Model.PTData.Stats.TotalProfit;
double totalFundingFees = Model.PTData.Stats.FundingTotal;
double totalPercentGain = ((totalProfit + totalFundingFees) / startBalance) * 100;
double totalProfitFiat = @Math.Round((totalProfit + totalFundingFees) * Model.PTData.Misc.FiatConversionRate, 0);
double avgDailySales = @Math.Round(totalSales/totalDays, 1);
double avgDailyGain = totalPercentGain / totalDays;
var MonthlyAveragesResult = Model.MonthlyAverages(Model.PTData.MonthlyStats, Model.PTData.DailyPNL);
string startDate = MonthlyAveragesResult.startDate.ToString("d");
string endDate = MonthlyAveragesResult.endDate.ToString("d");
double totalMonths = Math.Round(MonthlyAveragesResult.totalMonths, 1);
double avgMonthlySales = @Math.Round(totalSales / totalMonths, 1);
double avgMonthlyProfit = totalProfit / totalMonths;
double avgMonthlyGain = totalPercentGain / totalMonths;
double avgMonthlyFunding = totalFundingFees / totalMonths;
}
<h4 class="m-t-0 header-title" style="display: inline;">Averages</h4>&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: x-small"> (@startDate - @endDate)</span>
<table class="table table-striped table-sm">
<thead>
<tr>
<th></th>
<th class="text-right">Total</th>
<th class="text-right">AVG/Day</th>
<th class="text-right">AVG/Month</th>
<th class="text-right">AVG/Day</span></th>
<th class="text-right">AVG/Month</span> <small><i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top"
title="Months are based on the size of the calendar months. Weighted to compensate for partial months."></i></small></th>
</tr>
</thead>
<tbody>
<tr>
<th>Sales</th>
<td class="text-right">@Model.PTData.SellLog.Count</td>
<td class="text-right">@Math.Round((double)Model.PTData.SellLog.Count / (double)Model.PTData.SellLog.GroupBy(d => d.SoldDate.Date).ToList().Count, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right">@Math.Round((double)Model.PTData.SellLog.Count / (double)Model.PTData.SellLog.GroupBy(d => d.SoldDate.Date.ToString("yyyy-MM")).ToList().Count, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right">@totalSales</td>
<td class="text-right">@avgDailySales</td>
<td class="text-right">@avgMonthlySales</td>
</tr>
<tr>
<th>Profit</th>
<th>@Model.PTData.Misc.Market</th>
<td class="text-right text-autocolor">@totalProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Math.Round(totalProfit / (double)Model.PTData.SellLog.GroupBy(d => d.SoldDate.Date).ToList().Count, 8).ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Math.Round(totalProfit / (double)Model.PTData.SellLog.GroupBy(d => d.SoldDate.Date.ToString("yyyy-MM")).ToList().Count, 8).ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Math.Round(totalProfit / totalDays, 8).ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@avgMonthlyProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
</tr>
@if(Model.PropertiesData.IsLeverageExchange)
{
<tr>
<th>Funding</th>
<td class="text-right text-autocolor">@Html.Raw(Math.Round(totalFundingFees,8).ToString("#0.00000000", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Html.Raw(Math.Round(totalFundingFees / totalDays,8).ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Html.Raw(Math.Round(totalFundingFees / totalMonths,8).ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US")))</td>
</tr>
}
<tr>
<th>@Model.PTData.Properties.Currency</th>
<td class="text-right text-autocolor">@Html.Raw(Math.Round(totalProfitFiat,0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Html.Raw(Math.Round(totalProfitFiat / totalDays, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Html.Raw(Math.Round(totalProfitFiat / totalMonths, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")))</td>
</tr>
<tr>
<th>Profit USD</th>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + totalProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + Math.Round(totalProfitFiat / (double)Model.PTData.SellLog.GroupBy(d => d.SoldDate.Date).ToList().Count, 8).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + Math.Round(totalProfitFiat / (double)Model.PTData.SellLog.GroupBy(d => d.SoldDate.Date.ToString("yyyy-MM")).ToList().Count, 8).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
</tr>
<tr>
<th>% Gain</th>
<td class="text-right text-autocolor">@Html.Raw(percentGainText)</td>
<th>Gain</th>
<td class="text-right text-autocolor">@totalPercentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
<td class="text-right text-autocolor">@avgDailyGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
<td class="text-right text-autocolor">@avgMonthlyGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
</tr>
@ -115,46 +164,52 @@
<div class="col-md-6">
<div class="card-box">
@{
double currentTotalBalance = Model.totalCurrentValue;
double estimatedBalance1Month = Math.Round(currentTotalBalance * Math.Pow((1 + (avgDailyGain / 100)), 30.0), 8);
double estimatedBalance3Months = Math.Round(currentTotalBalance * Math.Pow((1 + (avgDailyGain / 100)), 90.0), 8);
double estimatedBalance6Months = Math.Round(currentTotalBalance * Math.Pow((1 + (avgDailyGain / 100)), 180.0), 8);
double estimatedBalance1Year = Math.Round(currentTotalBalance * Math.Pow((1 + (avgDailyGain / 100)), 365.0), 8);
double estimatedBalance1Week = Math.Round(totalCurrentValue * Math.Pow((1 + (avgDailyGain / 100)), 7.0), 8);
double estimatedBalance1Month = Math.Round(totalCurrentValue * Math.Pow((1 + (avgDailyGain / 100)), 30.0), 8);
double estimatedBalance3Months = Math.Round(totalCurrentValue * Math.Pow((1 + (avgDailyGain / 100)), 90.0), 8);
double estimatedBalance6Months = Math.Round(totalCurrentValue * Math.Pow((1 + (avgDailyGain / 100)), 180.0), 8);
double estimatedBalance1Year = Math.Round(totalCurrentValue * Math.Pow((1 + (avgDailyGain / 100)), 365.0), 8);
}
<h4 class="m-t-0 header-title">Balance Prediction <small><i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The balance prediction is based on your daily average gain of @avgDailyGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))% and your current approximate balance of @currentTotalBalance.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))"></i></small></h4>
<h4 class="m-t-0 header-title">TCV Prediction <small><i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The balance prediction is based on your daily average gain of @avgDailyGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))% and your current TCV of @totalCurrentValue.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))"></i></small></h4>
<table class="table table-striped table-sm">
<thead>
<tr>
<th class="text-right"></th>
<th class="text-right">Est. Balance</th>
<th class="text-right">Est. @Model.Summary.MainFiatCurrency Value</th>
<th class="text-right">Est. Gain</th>
<th class="text-right">@Model.PTData.Misc.Market</th>
<th class="text-right">@Model.PTData.Properties.Currency</th>
<th class="text-right">Gain</th>
</tr>
</thead>
<tbody>
<tr>
<th>1 month</th>
<th>1 Week</th>
<td class="text-right text-autocolor">@estimatedBalance1Week.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Html.Raw(Math.Round(estimatedBalance1Week * Model.MiscData.FiatConversionRate, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Math.Round((estimatedBalance1Week - totalCurrentValue) / totalCurrentValue * 100, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
</tr>
<tr>
<th>1 Month</th>
<td class="text-right text-autocolor">@estimatedBalance1Month.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + Math.Round(estimatedBalance1Month * Model.Summary.MainMarketPrice, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Math.Round((estimatedBalance1Month - currentTotalBalance) / currentTotalBalance * 100, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
<td class="text-right text-autocolor">@Html.Raw(Math.Round(estimatedBalance1Month * Model.MiscData.FiatConversionRate, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Math.Round((estimatedBalance1Month - totalCurrentValue) / totalCurrentValue * 100, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
</tr>
<tr>
<th>3 months</th>
<th>3 Months</th>
<td class="text-right text-autocolor">@estimatedBalance3Months.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + Math.Round(estimatedBalance3Months * Model.Summary.MainMarketPrice, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Math.Round((estimatedBalance3Months - currentTotalBalance) / currentTotalBalance * 100, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
<td class="text-right text-autocolor">@Html.Raw(Math.Round(estimatedBalance3Months * Model.MiscData.FiatConversionRate, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Math.Round((estimatedBalance3Months - totalCurrentValue) / totalCurrentValue * 100, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
</tr>
<tr>
<th>6 months</th>
<th>6 Months</th>
<td class="text-right text-autocolor">@estimatedBalance6Months.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + Math.Round(estimatedBalance6Months * Model.Summary.MainMarketPrice, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Math.Round((estimatedBalance6Months - currentTotalBalance) / currentTotalBalance * 100, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
<td class="text-right text-autocolor">@Html.Raw(Math.Round(estimatedBalance6Months * Model.MiscData.FiatConversionRate, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Math.Round((estimatedBalance6Months - totalCurrentValue) / totalCurrentValue * 100, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
</tr>
<tr>
<th>1 year</th>
<th>1 Year</th>
<td class="text-right text-autocolor">@estimatedBalance1Year.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + Math.Round(estimatedBalance1Year * Model.Summary.MainMarketPrice, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Math.Round((estimatedBalance1Year - currentTotalBalance) / currentTotalBalance * 100, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
<td class="text-right text-autocolor">@Html.Raw(Math.Round(estimatedBalance1Year * Model.MiscData.FiatConversionRate, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Math.Round((estimatedBalance1Year - totalCurrentValue) / totalCurrentValue * 100, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
</tr>
</tbody>
</table>
@ -162,54 +217,48 @@
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="card-box">
<div class="trades-chart">
<svg style="height:220px;width:100%"></svg>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card-box">
<div class="profit-chart">
<svg style="height:220px;width:100%"></svg>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="card-box">
<h4 class="m-t-0 header-title">Last @Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDailySummaries days</h4>
@{
var maxDays = Math.Min(Model.DailyStats.Count, Math.Min(Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDailySummaries, 30));
}
<h4 class="m-t-0 header-title">Last @maxDays Days</h4>
<table class="table table-sm">
<thead>
<tr>
<th>Day</th>
<th class="text-right">Buys</th>
<th class="text-right">Sales</th>
<th class="text-right">Profit @Model.Summary.MainMarket</th>
<th class="text-right">Profit @Model.Summary.MainFiatCurrency</th>
<th class="text-right">% Gain</th>
<th class="text-right">Profit @Model.PTData.Properties.Currency</th>
<th class="text-right">Gain</th>
</tr>
</thead>
<tbody>
@for (DateTime salesDate = Model.DateTimeNow.DateTime.Date; salesDate >= Model.DateTimeNow.DateTime.AddDays(-Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDailySummaries) && salesDate >= Model.MinSellLogDate; salesDate = salesDate.AddDays(-1)) {
List<Core.Main.DataObjects.PTMagicData.SellLogData> salesDateSales = Model.PTData.SellLog.FindAll(sl => sl.SoldDate.Date == salesDate);
double salesDateProfit = 0;
salesDateProfit = salesDateSales.Sum(sl => sl.Profit);
double salesDateProfitFiat = Math.Round(salesDateProfit * Model.Summary.MainMarketPrice, 2);
double salesDateStartBalance = Model.PTData.GetSnapshotBalance(salesDate);
double salesDateGain = Math.Round(salesDateProfit / salesDateStartBalance * 100, 2);
@{
for (int i = 0; i < maxDays; i++)
{
DateTime salesDate = DateTime.ParseExact(Model.PTData.DailyStats[i].Date, "d-M-yyyy", CultureInfo.InvariantCulture);
var buys = Model.PTData.DailyStats[i].TotalBuys;
var sales = Model.PTData.DailyStats[i].TotalSales;
var profit = Model.PTData.DailyStats[i].TotalProfit;
var avgProfit = Model.PTData.DailyStats[i].AvgProfit;
var profitFiat = Math.Round(profit * Model.PTData.Misc.FiatConversionRate, 0);
<tr>
<td><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/SalesList/?d=@salesDate.ToString("yyyy-MM-dd")" data-remote="false" data-toggle="modal" data-target="#salesList">@salesDate.ToShortDateString()</a></td>
<td class="text-right">@salesDateSales.Count</td>
<td class="text-right text-autocolor">@salesDateProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + salesDateProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@salesDateGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
<td>@salesDate.ToString("yyyy-MM-dd")</td>
<td class="text-right text-autocolor"">@buys</td>
<td class="text-right text-autocolor"">@sales</td>
<td class="text-right text-autocolor">@profit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Math.Round(profitFiat,0).ToString("#,#0", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@avgProfit.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
</tr>
}
}
</tbody>
</table>
</div>
@ -217,110 +266,83 @@
<div class="col-md-6">
<div class="card-box">
<h4 class="m-t-0 header-title">Last @Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries months</h4>
@{
var maxMonths = Math.Min(Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries, Model.MonthlyStats.Count);
}
<h4 class="m-t-0 header-title">Last @maxMonths months</h4>
<table class="table table-sm">
<thead>
<tr>
<th>Month</th>
<th class="text-right">Sales</th>
<th class="text-right">Profit @Model.Summary.MainMarket</th>
<th class="text-right">Profit @Model.Summary.MainFiatCurrency</th>
<th class="text-right">% Gain</th>
<th class="text-right">AVG %/Day</th>
<th class="text-right">Profit @Model.PTData.Properties.Currency</th>
<th class="text-right">Growth</th>
</tr>
</thead>
<tbody>
@{
DateTime minSellLogMonthDate = new DateTime(Model.MinSellLogDate.Year, Model.MinSellLogDate.Month, 1).Date;
DateTime salesMonthStartDate = new DateTime(Model.DateTimeNow.DateTime.Year, Model.DateTimeNow.DateTime.Month, 1).Date;
}
@for (DateTime salesMonthDate = salesMonthStartDate.Date; salesMonthDate >= Model.DateTimeNow.DateTime.AddMonths(-Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries) && salesMonthDate >= minSellLogMonthDate; salesMonthDate = salesMonthDate.AddMonths(-1)) {
List<Core.Main.DataObjects.PTMagicData.SellLogData> salesMonthSales = Model.PTData.SellLog.FindAll(sl => sl.SoldDate.Date.Month == salesMonthDate.Month && sl.SoldDate.Date.Year == salesMonthDate.Year);
double salesDateProfit = 0;
salesDateProfit = salesMonthSales.Sum(sl => sl.Profit);
double salesDateProfitFiat = Math.Round(salesDateProfit * Model.Summary.MainMarketPrice, 2);
double salesDateStartBalance = Model.PTData.GetSnapshotBalance(salesMonthDate);
double salesDateGain = Math.Round(salesDateProfit / salesDateStartBalance * 100, 2);
double salesDateAVGDailyGain = 0;
double monthDailyProfit = 0;
int days = 0;
for (int d = 1; d <= DateTime.DaysInMonth(salesMonthDate.Year, salesMonthDate.Month); d++) {
DateTime monthDay = salesMonthDate.AddDays(-salesMonthDate.Day + d);
if (monthDay <= Model.DateTimeNow) {
days++;
List<Core.Main.DataObjects.PTMagicData.SellLogData> monthDaySales = Model.PTData.SellLog.FindAll(sl => sl.SoldDate.Date == monthDay.Date);
double monthDayProfit = 0;
monthDayProfit = monthDaySales.Sum(sl => sl.Profit);
double monthDayStartBalance = Model.PTData.GetSnapshotBalance(monthDay);
monthDailyProfit += Math.Round(monthDayProfit / monthDayStartBalance * 100, 2);
}
}
salesDateAVGDailyGain = Math.Round(monthDailyProfit / days, 2);
for (int i = 0; i < maxMonths; i++)
{
DateTime monthDate = DateTime.ParseExact(Model.PTData.MonthlyStats[i].Month, "M-yyyy", CultureInfo.InvariantCulture);
string monthName = monthDate.ToString("MMMM", CultureInfo.InvariantCulture);
var sales = Model.PTData.MonthlyStats[i].TotalSales;
var profit = Model.PTData.MonthlyStats[i].TotalProfitCurrency;
var profitFiat = Math.Round(profit * Model.PTData.Misc.FiatConversionRate, 0);
var growth = Math.Round(Model.PTData.MonthlyStats[i].AvgGrowth,2);
<tr>
<td><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/SalesList/?m=@salesMonthDate.ToString("yyyy-MM")" data-remote="false" data-toggle="modal" data-target="#salesList">@salesMonthDate.ToString("MMMM", new System.Globalization.CultureInfo("en-US"))</a></td>
<td class="text-right text-autocolor">@salesMonthSales.Count</td>
<td class="text-right text-autocolor">@salesDateProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + salesDateProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@salesDateGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
<td class="text-right text-autocolor">@salesDateAVGDailyGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
<td>@monthName</td>
<td class="text-right text-autocolor">@sales</td>
<td class="text-right text-autocolor">@profit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@profitFiat</td>
<td class="text-right text-autocolor">@growth %</td>
</tr>
}
}
</tbody>
</table>
</div>
</div>
</div
</div>
<div class="row">
<div class="col-sm-12">
@* <div class="col-md-6"> *@
<div class="card-box">
<h4 class="m-t-0 header-title"><b>Top @Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxTopMarkets Sales Market Analysis</b></h4>
<h4 class="m-t-0 header-title"><b>Top @Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxTopMarkets Sales Markets</b></h4>
<table class="tablesaw table m-b-0" data-tablesaw-sortable data-tablesaw-sortable-switch>
<thead>
<tr>
<th scope="col" data-tablesaw-priority="persist" data-tablesaw-sortable-col data-tablesaw-sortable-default-col>Rank</th>
<th scope="col" data-tablesaw-sortable-col>Market</th>
<th scope="col" class="text-right" data-tablesaw-sortable-col>Profit @Model.PTData.Misc.Market</th>
<th scope="col" class="text-right" data-tablesaw-sortable-col>Sales</th>
<th scope="col" class="text-right" data-tablesaw-sortable-col>Profit @Model.Summary.MainMarket</th>
<th scope="col" class="text-right" data-tablesaw-sortable-col>Profit @Model.Summary.MainFiatCurrency</th>
<th scope="col" class="text-right" data-tablesaw-sortable-col>Profit @Model.Summary.MainFiatCurrency/Trade</th>
<th scope="col" class="text-right" data-tablesaw-sortable-col>Profit %/Trade</th>
<th scope="col" class="text-right" data-tablesaw-sortable-col>Avg/Trade</th>
</tr>
</thead>
<tbody>
@{
var topMarkets = Model.PTData.SellLog.GroupBy(m => m.Market).Select(mg => mg.Sum(m => m.Profit));
int marketRank = 0;
}
@foreach (KeyValuePair<string, double> marketData in Model.TopMarkets) {
marketRank++;
int trades = Model.PTData.SellLog.FindAll(m => m.Market == marketData.Key).Count;
double profitFiat = Math.Round(marketData.Value * Model.Summary.MainMarketPrice, 2);
double profitFiatPerTrade = Math.Round(profitFiat / trades, 2);
int rank = 1;
foreach (var pair in Model.ProfitablePairs)
{
string coin = pair.Coin;
double profit = Math.Round(pair.ProfitCurrency,8);
int sales = pair.SoldTimes;
double avg = Math.Round(pair.Avg,8);
double profitFiat = Math.Round(profit * Model.MiscData.FiatConversionRate, 0);
<tr>
<td>@marketRank</td>
<td><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform, Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, marketData.Key, Model.Summary.MainMarket)" target="_blank">@marketData.Key</a></td>
<td class="text-right">@trades</td>
<td class="text-right text-autocolor-saw">@marketData.Value.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor-saw">@profitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainFiatCurrency</td>
<td class="text-right text-autocolor-saw">@profitFiatPerTrade.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainFiatCurrency</td>
<td class="text-right text-autocolor-saw">@Model.PTData.SellLog.FindAll(m => m.Market == marketData.Key).Average(p => p.ProfitPercent).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
<td>@rank</td>
<td><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform, Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, coin, Model.PTData.Misc.Market)" target="_blank">@coin</a></td>
<td class="text-right text-autocolor-saw">@profit</td>
<td class="text-right">@sales</td>
<td class="text-right text-autocolor-saw">@avg </td>
</tr>
rank++;
}
}
</tbody>
</table>
</div>
</div>
</div>
@* </div> *@
<div id="salesList" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" style="display: none;">
<div class="modal-dialog modal-full">
<div class="modal-content">
<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</div>
}
@section Scripts {
@ -343,60 +365,198 @@
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/buttons.print.min.js"></script>
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/buttons.colVis.min.js"></script>
<script type="text/javascript">
(function ($) {
'use strict';
nv.addGraph(function () {
var lineChart = nv.models.lineChart();
var height = 300;
/**/
var chartData = @Html.Raw(Model.TradesChartDataJSON);
/**/
lineChart.useInteractiveGuideline(true);
lineChart.xAxis.tickFormat(function (d) { return d3.time.format('%Y/%m/%d')(new Date(d)); });
lineChart.yAxis.axisLabel('Daily Sales').tickFormat(d3.format(','));
d3.select('.trades-chart svg').attr('perserveAspectRatio', 'xMinYMid').datum(chartData).transition().duration(500).call(lineChart);
nv.utils.windowResize(lineChart.update);
return lineChart;
});
nv.addGraph(function () {
var lineChart = nv.models.lineChart();
var height = 300;
/**/
var chartData = @Html.Raw(Model.ProfitChartDataJSON);
/**/
lineChart.useInteractiveGuideline(true);
lineChart.xAxis.tickFormat(function (d) { return d3.time.format('%Y/%m/%d')(new Date(d)); });
lineChart.yAxis.axisLabel('Daily Profit').tickFormat(d3.format(',.2f'));
d3.select('.profit-chart svg').attr('perserveAspectRatio', 'xMinYMid').datum(chartData).transition().duration(500).call(lineChart);
nv.utils.windowResize(lineChart.update);
return lineChart;
});
nv.addGraph(function () {
var lineChart = nv.models.lineChart();
var height = 400;
/**/
var chartData = @Html.Raw(Model.BalanceChartDataJSON);
/**/
lineChart.useInteractiveGuideline(true);
lineChart.xAxis.tickFormat(function (d) { return d3.time.format('%Y/%m/%d')(new Date(d)); });
lineChart.yAxis.axisLabel('Profit').tickFormat(d3.format(',.2f'));
d3.select('.balance-chart svg').attr('perserveAspectRatio', 'xMinYMid').datum(chartData).transition().duration(500).call(lineChart);
nv.utils.windowResize(lineChart.update);
return lineChart;
});
$("#salesList").on("show.bs.modal", function (e) {
$(this).find(".modal-content").html('<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>');
var link = $(e.relatedTarget);
$(this).find(".modal-content").load(link.attr("href"), function () {
$('[role="tooltip"]').remove();
$('[data-toggle="tooltip"]').tooltip();
$('.text-autocolor').autocolor(false);
var salesChart; // Keep a reference to the chart
var salesData; // Keep a reference to the data
@if (!Model.SalesChartDataJSON.Equals("")) {
<text>
nv.addGraph(function () {
salesChart = nv.models.multiBarChart();
var height = 300;
salesChart.groupSpacing(0.5);
salesChart.showControls(false);
//salesChart.useInteractiveGuideline(true);
salesChart.xAxis.tickFormat(function (d) { return d3.time.format('%m/%d')(new Date(d)); });
salesChart.yAxis.axisLabel('').tickFormat(d3.format(',.2f'));
salesData = @Html.Raw(Model.SalesChartDataJSON);
d3.select('.sales-chart svg')
.datum(salesData)
.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;
});
</text>
}
})(jQuery);
</script>
<script type="text/javascript">
(function ($) {
'use strict';
$('[role="tooltip"]').remove();
$('[data-toggle="tooltip"]').tooltip();
$('.text-autocolor').autocolor(false);
var cumulativeProfitChart; // Keep a reference to the chart
var cumulativeProfitData; // Keep a reference to the data
@if (!Model.CumulativeProfitChartDataJSON.Equals("")) {
<text>
nv.addGraph(function () {
cumulativeProfitChart = nv.models.lineChart();
var height = 300;
cumulativeProfitChart.useInteractiveGuideline(true);
cumulativeProfitChart.xAxis.tickFormat(function (d) { return d3.time.format('%m/%d')(new Date(d)); });
cumulativeProfitChart.yAxis.axisLabel('').tickFormat(d3.format(',.2f'));
cumulativeProfitData = @Html.Raw(Model.CumulativeProfitChartDataJSON);
d3.select('.cumulative-profit-chart svg')
.datum(cumulativeProfitData)
.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;
});
</text>
}
})(jQuery);
</script>
<script type="text/javascript">
(function ($) {
'use strict';
$('[role="tooltip"]').remove();
$('[data-toggle="tooltip"]').tooltip();
$('.text-autocolor').autocolor(false);
var TCVChart; // Keep a reference to the chart
var TCVData; // Keep a reference to the data
@if (!Model.TCVChartDataJSON.Equals("")) {
<text>
nv.addGraph(function () {
TCVChart = nv.models.lineChart();
var height = 300;
TCVChart.useInteractiveGuideline(true);
TCVChart.xAxis.tickFormat(function (d) { return d3.time.format('%m/%d')(new Date(d)); });
TCVChart.yAxis.axisLabel('').tickFormat(d3.format(',.2f'));
TCVData = @Html.Raw(Model.TCVChartDataJSON);
d3.select('.TCV-chart svg')
.datum(TCVData)
.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;
});
</text>
}
})(jQuery);
</script>
<script type="text/javascript">
(function ($) {
'use strict';
$('[role="tooltip"]').remove();
$('[data-toggle="tooltip"]').tooltip();
$('.text-autocolor').autocolor(false);
var profitChart; // Keep a reference to the chart
var profitData; // Keep a reference to the data
@if (!Model.ProfitChartDataJSON.Equals("")) {
<text>
nv.addGraph(function () {
profitChart = nv.models.lineChart();
var height = 300;
profitChart.useInteractiveGuideline(true);
profitChart.xAxis.tickFormat(function (d) { return d3.time.format('%m/%d')(new Date(d)); });
profitChart.yAxis.axisLabel('').tickFormat(d3.format(',.2f'));
profitData = @Html.Raw(Model.ProfitChartDataJSON);
d3.select('.profit-chart svg')
.datum(profitData)
.transition().duration(0)
.call(profitChart);
// Add mouseleave, and mousemove event listeners to hide tooltip
d3.select('.profit-chart').on('mouseleave', function() {
d3.select('.nvtooltip').style('opacity', 0);
});
d3.select('body').on('mousemove', function() {
var chartBounds = d3.select('.profit-chart')[0][0].getBoundingClientRect();
var mouseX = d3.event.clientX;
var mouseY = d3.event.clientY;
if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) {
d3.select('.nvtooltip').style('opacity', 0);
}
});
nv.utils.windowResize(profitChart.update);
return profitChart;
});
</text>
}
})(jQuery);
</script>
}

View File

@ -1,146 +1,352 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Core.Main;
using Core.Helper;
using System.Globalization;
using Core.Main.DataObjects;
using Core.Main.DataObjects.PTMagicData;
namespace Monitor.Pages
{
public class SalesAnalyzer : _Internal.BasePageModelSecure
{
public ProfitTrailerData PTData = null;
public MiscData MiscData { get; set; }
public PropertiesData PropertiesData { get; set; }
public StatsData StatsData { get; set; }
public List<DailyPNLData> DailyPNL { get; set; }
public List<DailyTCVData> DailyTCV { get; set; }
public List<ProfitablePairsData> ProfitablePairs { get; set; }
public List<DailyStatsData> DailyStats { get; set; }
public int ProfitDays { get; set; }
public int TCVDays { get; set; }
public int SalesDays { get; set; }
public List<MonthlyStatsData> MonthlyStats { get; set; }
public string TradesChartDataJSON = "";
public string CumulativeProfitChartDataJSON = "";
public string TCVChartDataJSON = "";
public string ProfitChartDataJSON = "";
public string BalanceChartDataJSON = "";
public string SalesChartDataJSON = "";
public IEnumerable<KeyValuePair<string, double>> TopMarkets = null;
public DateTime MinSellLogDate = Constants.confMinDate;
public Dictionary<DateTime, double> DailyGains = new Dictionary<DateTime, double>();
public Dictionary<DateTime, double> MonthlyGains = new Dictionary<DateTime, double>();
//public DateTime MinSellLogDate = Constants.confMinDate;
public DateTimeOffset DateTimeNow = Constants.confMinDate;
public double totalCurrentValue = 0;
public void OnGet()
{
base.Init();
BindData();
BuildTCV();
}
private void BindData()
{
PTData = this.PtDataObject;
MiscData = this.PTData.Misc;
PropertiesData = this.PTData.Properties;
StatsData = this.PTData.Stats;
MonthlyStats = this.PTData.MonthlyStats;
DailyPNL = this.PTData.DailyPNL;
DailyTCV = this.PTData.DailyTCV;
ProfitablePairs = this.PTData.ProfitablePairs;
DailyStats = this.PTData.DailyStats;
// Convert local offset time to UTC
TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
BuildTopMarkets();
BuildSalesChartData();
BuildProfitChartData();
BuildCumulativeProfitChartData();
BuildTCVChartData();
}
private void BuildTopMarkets()
private void BuildTCVChartData()
{
var markets = PTData.SellLog.GroupBy(m => m.Market);
Dictionary<string, double> topMarketsDic = new Dictionary<string, double>();
foreach (var market in markets)
List<object> TCVPerDayList = new List<object>();
if (PTData.DailyTCV.Count > 0)
{
double totalProfit = 0;
totalProfit = PTData.SellLog.FindAll(m => m.Market == market.Key).Sum(m => m.Profit);
topMarketsDic.Add(market.Key, totalProfit);
// Get timezone offset
TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{
offset = TimeSpan.Zero; // If offset is invalid, set it to zero
}
TopMarkets = new SortedDictionary<string, double>(topMarketsDic).OrderByDescending(m => m.Value).Take(PTMagicConfiguration.GeneralSettings.Monitor.MaxTopMarkets);
DateTime endDate = DateTime.UtcNow.Add(isNegative ? -offset : offset).Date;
// Parse dates once and adjust them to the local timezone
Dictionary<DateTime, DailyTCVData> dailyTCVByDate = PTData.DailyTCV
.Select(data => {
DateTime dateUtc = DateTime.ParseExact(data.Date, "d-M-yyyy", CultureInfo.InvariantCulture);
DateTime dateLocal = dateUtc.Add(isNegative ? -offset : offset);
return new { Date = dateLocal.Date, Data = data };
})
.ToDictionary(
item => item.Date,
item => item.Data
);
DateTime earliestDataDate = dailyTCVByDate.Keys.Min();
DateTime startDate = earliestDataDate;
// Calculate the total days of data available
TCVDays = (endDate - startDate).Days;
for (DateTime date = startDate; date <= endDate; date = date.AddDays(1))
{
// Use the dictionary to find the Data for the date
if (dailyTCVByDate.TryGetValue(date, out DailyTCVData dailyTCV))
{
double TCV = dailyTCV.TCV;
// Add the data point to the list
TCVPerDayList.Add(new { x = new DateTimeOffset(date).ToUnixTimeMilliseconds(), y = TCV });
}
}
// Convert the list to a JSON string using Newtonsoft.Json
TCVChartDataJSON = Newtonsoft.Json.JsonConvert.SerializeObject(new[] {
new {
key = "TCV in " + PTData.Misc.Market,
color = Constants.ChartLineColors[1],
values = TCVPerDayList
}
});
}
}
private void BuildCumulativeProfitChartData()
{
List<object> profitPerDayList = new List<object>();
if (PTData.DailyPNL.Count > 0)
{
// Get timezone offset
TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{
offset = TimeSpan.Zero; // If offset is invalid, set it to zero
}
DateTime endDate = DateTime.UtcNow.Add(isNegative ? -offset : offset).Date;
// Parse dates once and adjust them to the local timezone
Dictionary<DateTime, DailyPNLData> dailyPNLByDate = PTData.DailyPNL
.Select(data => {
DateTime dateUtc = DateTime.ParseExact(data.Date, "d-M-yyyy", CultureInfo.InvariantCulture);
DateTime dateLocal = dateUtc.Add(isNegative ? -offset : offset);
return new { Date = dateLocal.Date, Data = data };
})
.ToDictionary(
item => item.Date,
item => item.Data
);
DateTime earliestDataDate = dailyPNLByDate.Keys.Min();
DateTime startDate = earliestDataDate;
// Calculate the total days of data available
ProfitDays = (endDate - startDate).Days;
double previousDayCumulativeProfit = 0;
for (DateTime date = startDate; date <= endDate; date = date.AddDays(1))
{
// Use the dictionary to find the DailyPNLData for the date
if (dailyPNLByDate.TryGetValue(date, out DailyPNLData dailyPNL))
{
// Use the CumulativeProfitCurrency directly
double profitFiat = dailyPNL.CumulativeProfitCurrency;
// Add the data point to the list
profitPerDayList.Add(new { x = new DateTimeOffset(date).ToUnixTimeMilliseconds(), y = profitFiat });
previousDayCumulativeProfit = dailyPNL.CumulativeProfitCurrency;
}
}
// Convert the list to a JSON string using Newtonsoft.Json
CumulativeProfitChartDataJSON = Newtonsoft.Json.JsonConvert.SerializeObject(new[] {
new {
key = "Profit in " + PTData.Misc.Market,
color = Constants.ChartLineColors[1],
values = profitPerDayList
}
});
}
}
private void BuildProfitChartData()
{
List<object> profitPerDayList = new List<object>();
if (PTData.DailyPNL.Count > 0)
{
// Get timezone offset
TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{
offset = TimeSpan.Zero; // If offset is invalid, set it to zero
}
DateTime endDate = DateTime.UtcNow.Add(isNegative ? -offset : offset).Date;
// Parse dates once and adjust them to the local timezone
Dictionary<DateTime, DailyPNLData> dailyPNLByDate = PTData.DailyPNL
.Select(data => {
DateTime dateUtc = DateTime.ParseExact(data.Date, "d-M-yyyy", CultureInfo.InvariantCulture);
DateTime dateLocal = dateUtc.Add(isNegative ? -offset : offset);
return new { Date = dateLocal.Date, Data = data };
})
.ToDictionary(
item => item.Date,
item => item.Data
);
DateTime earliestDataDate = dailyPNLByDate.Keys.Min();
DateTime startDate = earliestDataDate;
// Calculate the total days of data available
ProfitDays = (endDate - startDate).Days;
double previousDayCumulativeProfit = 0;
bool isFirstDay = true;
for (DateTime date = startDate; date <= endDate; date = date.AddDays(1))
{
// Use the dictionary to find the DailyPNLData for the date
if (dailyPNLByDate.TryGetValue(date, out DailyPNLData dailyPNL))
{
if (isFirstDay)
{
isFirstDay = false;
}
else
{
// Calculate the profit for the current day
double profitFiat = Math.Round(dailyPNL.CumulativeProfitCurrency - previousDayCumulativeProfit, 2);
// Add the data point to the list
profitPerDayList.Add(new { x = new DateTimeOffset(date).ToUnixTimeMilliseconds(), y = profitFiat });
}
previousDayCumulativeProfit = dailyPNL.CumulativeProfitCurrency;
}
}
// Convert the list to a JSON string using Newtonsoft.Json
ProfitChartDataJSON = Newtonsoft.Json.JsonConvert.SerializeObject(new[] {
new {
key = "Profit in " + PTData.Misc.Market,
color = Constants.ChartLineColors[1],
values = profitPerDayList
}
});
}
}
public (double totalMonths, DateTime startDate, DateTime endDate) MonthlyAverages(List<MonthlyStatsData> monthlyStats, List<DailyPNLData> dailyPNL)
{
double totalMonths = 0;
// Get the exact start and end dates of sales data
DateTime startDate = dailyPNL.Min(d => DateTime.ParseExact(d.Date, "d-M-yyyy", CultureInfo.InvariantCulture));
DateTime endDate = dailyPNL.Max(d => DateTime.ParseExact(d.Date, "d-M-yyyy", CultureInfo.InvariantCulture));
int daysInFirstMonth = DateTime.DaysInMonth(startDate.Year, startDate.Month) - startDate.Day + 1;
int daysInLastMonth = endDate.Day;
//Console.WriteLine("Start Date: {0}, End Date: {1}, Days in first month: {2}, Days in last month: {3}", startDate, endDate, daysInFirstMonth, daysInLastMonth);
for (int i = 0; i < monthlyStats.Count; i++)
{
var monthStat = monthlyStats[i];
double weight;
// Parse the Month property into a DateTime object
DateTime monthDate = DateTime.ParseExact(monthStat.Month, "M-yyyy", CultureInfo.InvariantCulture);
// If it's the first or last month in the dataset, calculate the weight based on the number of days
if (i == 0)
{
// Calculate weight based on the number of days in the dataset for the first month
weight = daysInFirstMonth / 30.0;
}
else if (i == monthlyStats.Count - 1)
{
// Calculate weight based on the number of days in the dataset for the last month
weight = (daysInLastMonth / 30.0);
}
else
{
// Otherwise, assume it's a full month
weight = 1;
}
totalMonths += weight;
}
return (totalMonths, startDate, endDate);
}
private void BuildSalesChartData()
{
if (PTData.SellLog.Count > 0)
{
MinSellLogDate = PTData.SellLog.OrderBy(sl => sl.SoldDate).First().SoldDate.Date;
DateTime graphStartDate = DateTimeNow.DateTime.Date.AddDays(-1850);
if (MinSellLogDate > graphStartDate) graphStartDate = MinSellLogDate;
List<object> salesPerDayList = new List<object>();
List<object> buysPerDayList = new List<object>(); // New list for daily buys
int tradeDayIndex = 0;
string tradesPerDayJSON = "";
string profitPerDayJSON = "";
string balancePerDayJSON = "";
double balance = 0.0;
for (DateTime salesDate = graphStartDate; salesDate <= DateTimeNow.DateTime.Date; salesDate = salesDate.AddDays(1))
if (PTData.DailyStats.Count > 0)
{
if (tradeDayIndex > 0)
{
tradesPerDayJSON += ",\n";
profitPerDayJSON += ",\n";
balancePerDayJSON += ",\n";
}
double profit = 0;
int trades = PTData.SellLog.FindAll(t => t.SoldDate.Date == salesDate.Date).Count;
profit = PTData.SellLog.FindAll(t => t.SoldDate.Date == salesDate.Date).Sum(t => t.Profit);
double profitFiat = Math.Round(profit * Summary.MainMarketPrice, 2);
balance += profitFiat;
tradesPerDayJSON += "{x: new Date('" + salesDate.Date.ToString("yyyy-MM-dd") + "'), y: " + trades + "}";
profitPerDayJSON += "{x: new Date('" + salesDate.Date.ToString("yyyy-MM-dd") + "'), y: " + profitFiat.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}";
balancePerDayJSON += "{x: new Date('" + salesDate.Date.ToString("yyyy-MM-dd") + "'), y: " + balance.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}";
tradeDayIndex++;
}
TradesChartDataJSON = "[";
TradesChartDataJSON += "{";
TradesChartDataJSON += "key: 'Sales',";
TradesChartDataJSON += "color: '" + Constants.ChartLineColors[0] + "',";
TradesChartDataJSON += "values: [" + tradesPerDayJSON + "]";
TradesChartDataJSON += "}";
TradesChartDataJSON += "]";
// Get timezone offset
TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-');
ProfitChartDataJSON = "[";
ProfitChartDataJSON += "{";
ProfitChartDataJSON += "key: 'Profit in " + Summary.MainFiatCurrency + "',";
ProfitChartDataJSON += "color: '" + Constants.ChartLineColors[1] + "',";
ProfitChartDataJSON += "values: [" + profitPerDayJSON + "]";
ProfitChartDataJSON += "}";
ProfitChartDataJSON += "]";
if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{
offset = TimeSpan.Zero; // If offset is invalid, set it to zero
}
BalanceChartDataJSON = "[";
BalanceChartDataJSON += "{";
BalanceChartDataJSON += "key: 'Profit in " + Summary.MainFiatCurrency + "',";
BalanceChartDataJSON += "color: '" + Constants.ChartLineColors[1] + "',";
BalanceChartDataJSON += "values: [" + balancePerDayJSON + "]";
BalanceChartDataJSON += "}";
BalanceChartDataJSON += "]";
DateTime endDate = DateTime.UtcNow.Add(isNegative ? -offset : offset).Date;
for (DateTime salesDate = DateTimeNow.DateTime.Date; salesDate >= MinSellLogDate; salesDate = salesDate.AddDays(-1))
// Parse dates once and adjust them to the local timezone
Dictionary<DateTime, DailyStatsData> salesCountByDate = PTData.DailyStats
.ToDictionary(
data => DateTime.ParseExact(data.Date, "d-M-yyyy", CultureInfo.InvariantCulture),
data => data
);
DateTime earliestDataDate = salesCountByDate.Keys.Min();
DateTime startDate = earliestDataDate;
// Calculate the total days of data available
SalesDays = (endDate - startDate).Days;
int counter = 0;
for (DateTime date = startDate; date <= endDate && counter < 30; date = date.AddDays(1)) // Check the counter
{
List<SellLogData> salesDateSales = PTData.SellLog.FindAll(sl => sl.SoldDate.Date == salesDate);
double salesDateProfit;
salesDateProfit = salesDateSales.Sum(sl => sl.Profit);
double salesDateStartBalance = PTData.GetSnapshotBalance(salesDate);
double salesDateGain = Math.Round(salesDateProfit / salesDateStartBalance * 100, 2);
DailyGains.Add(salesDate, salesDateGain);
}
DateTime minSellLogMonthDate = new DateTime(MinSellLogDate.Year, MinSellLogDate.Month, 1).Date;
DateTime salesMonthStartDate = new DateTime(DateTimeNow.DateTime.Year, DateTimeNow.DateTime.Month, 1).Date;
for (DateTime salesMonthDate = salesMonthStartDate.Date; salesMonthDate >= minSellLogMonthDate; salesMonthDate = salesMonthDate.AddMonths(-1))
if (salesCountByDate.TryGetValue(date, out DailyStatsData dailyStatsData))
{
List<Core.Main.DataObjects.PTMagicData.SellLogData> salesMonthSales = PTData.SellLog.FindAll(sl => sl.SoldDate.Date.Month == salesMonthDate.Month && sl.SoldDate.Date.Year == salesMonthDate.Year);
double salesDateProfit;
salesDateProfit = salesMonthSales.Sum(sl => sl.Profit);
double salesDateStartBalance = PTData.GetSnapshotBalance(salesMonthDate);
double salesDateGain = Math.Round(salesDateProfit / salesDateStartBalance * 100, 2);
MonthlyGains.Add(salesMonthDate, salesDateGain);
buysPerDayList.Add(new { x = new DateTimeOffset(date).ToUnixTimeMilliseconds(), y = Convert.ToInt32(dailyStatsData.TotalBuys) });
salesPerDayList.Add(new { x = new DateTimeOffset(date).ToUnixTimeMilliseconds(), y = Convert.ToInt32(dailyStatsData.TotalSales) });
}
}
// Convert the lists to a JSON string using Newtonsoft.Json
SalesChartDataJSON = Newtonsoft.Json.JsonConvert.SerializeObject(new[] {
new { // New JSON object for daily buys
key = "Buys",
color = Constants.ChartLineColors[19], // Use a different color for buys
values = buysPerDayList
},
new {
key = "Sales",
color = Constants.ChartLineColors[1],
values = salesPerDayList
}
});
}
}
}
private void BuildTCV()
{
double AvailableBalance = PTData.GetCurrentBalance();
foreach (Core.Main.DataObjects.PTMagicData.DCALogData dcaLogEntry in PTData.DCALog)
{
double leverage = dcaLogEntry.Leverage;
if (leverage == 0)
{
leverage = 1;
}
totalCurrentValue = totalCurrentValue + ((dcaLogEntry.Amount * dcaLogEntry.CurrentPrice) / leverage);
}
totalCurrentValue = totalCurrentValue + AvailableBalance;
}
}
}

View File

@ -124,19 +124,19 @@
</div>
</div>
<div class="form-group row">
@* <div class="form-group row">
<label class="col-md-4 col-form-label">Main Fiat Currency <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This is the local currency you want PTM to use when showing your sales and account value in fiat."></i></label>
<div class="col-md-8">
<input type="text" class="form-control" name="Application_MainFiatCurrency" value="@Model.PTMagicConfiguration.GeneralSettings.Application.MainFiatCurrency">
</div>
</div>
</div> *@
<div class="form-group row">
@* <div class="form-group row">
<label class="col-md-4 col-form-label">Starting Balance <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="PTM will use this to calculate total profits to date, and projected profits."></i></label>
<div class="col-md-8">
<input type="text" class="form-control" name="Application_StartBalance" value="@Model.PTMagicConfiguration.GeneralSettings.Application.StartBalance">
</div>
<input type="text" class="form-control" name="Application_StartBalance" value="@Model.SummaryData.StartBalance">
</div>
</div> *@
<div class="form-group row">
<label class="col-md-4 col-form-label">CoinMarketCap API Key <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The API Key that will be used to get Coin Data from CoinMarketCap (optional)."></i></label>
@ -145,12 +145,12 @@
</div>
</div>
<div class="form-group row">
@* <div class="form-group row">
<label class="col-md-4 col-form-label">FreeCurrencyConverter API Key <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The api key needed for currency conversions for non-USD currency users (optional)."></i></label>
<div class="col-md-8">
<input type="text" class="form-control" name="Application_FreeCurrencyConverterAPIKey" value="@Model.PTMagicConfiguration.GeneralSettings.Application.FreeCurrencyConverterAPIKey">
</div>
</div>
</div> *@
</div>
</div>
@ -196,21 +196,28 @@
</div>
</div>
<div class="form-group row">
<label class="col-md-4 col-form-label">Graph Interval Minutes <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The interval for the monitor market trend graph to draw points."></i></label>
<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>
<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">Graph Max Timeframe Hours <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This defines the timeframe that your graph for market trends covers."></i></label>
<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>
<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">Refresh Seconds <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The refresh interval of your monitor main page."></i></label>
<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>
<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>
<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"))">
</div>
@ -240,6 +247,13 @@
</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="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"))">
</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>
<div class="col-md-8">
@ -262,7 +276,7 @@
</div>
<div class="form-group row">
<label class="col-md-4 col-form-label">Max Dashboard Buy Entries <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The amount of entries being shown in your dashboard for possible buys."></i></label>
<label class="col-md-4 col-form-label">Max Dashboard Buy Entries <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The amount of possible buys shown in your dashboard. Setting this to 0 completely hides the PBL table."></i></label>
<div class="col-md-8">
<input type="text" class="form-control" name="Monitor_MaxDashboardBuyEntries" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBuyEntries.ToString(new System.Globalization.CultureInfo("en-US"))">
</div>

View File

@ -1,16 +1,19 @@
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;
namespace Monitor.Pages
{
public class SettingsGeneralModel : _Internal.BasePageModelSecure
{
public string ValidationMessage = "";
public ProfitTrailerData PTData = null;
public MiscData MiscData { get; set; }
private string GetTimezoneOffsetString(TimeZoneInfo tzi)
{
@ -51,6 +54,8 @@ namespace Monitor.Pages
public void OnGet()
{
base.Init();
PTData = this.PtDataObject;
MiscData = this.PTData.Misc;
string notification = GetStringParameter("n", "");
if (notification.Equals("BackupRestored"))
@ -68,27 +73,29 @@ 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.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.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.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.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.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.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

@ -10,7 +10,8 @@
}
<div class="row">
<div class="col-md-5">
<div class="col-md-6">
<div class="card-box">
<h4 class="m-t-0 header-title">PTMagic Status <small id="last-refresh" class="pull-right"></small></h4>
@{
@ -67,11 +68,10 @@
</div>
<div class="card-box">
<h4 class="m-t-0 header-title">Active Settings</h4>
<h4 class="m-t-0 header-title">Active Settings &nbsp; <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="If DYNAMIC formulas are used in PT for these settings, the values will not be properly displayed here."></i></h4>
@{
string maxCostCaption = "Initial";
}
<table class="table table-striped table-sm">
<tbody>
<tr>
@ -86,8 +86,6 @@
}
</tr>
<tr>
<td>DCA Level</td>
<td class="text-right">@Model.Summary.DCALevels.ToString(new System.Globalization.CultureInfo("en-US"))</td>
<td>Min Vol.</td>
<td class="text-right">@Model.Summary.MinBuyVolume.ToString(new System.Globalization.CultureInfo("en-US"))</td>
</tr>
@ -101,90 +99,32 @@
</table>
</div>
<div class="card-box">
<h4 class="m-t-0 header-title">Active Buy Strategies</h4>
<table class="table table-striped table-sm">
<tbody>
@if (Model.Summary.BuyStrategies.Count == 0) {
<tr>
<td>Buy Strat.</td>
<td class="text-right">@Model.Summary.BuyStrategy</td>
<td>Buy Value</td>
<td class="text-right">@Model.Summary.BuyValue.ToString(new System.Globalization.CultureInfo("en-US"))</td>
</tr>
} else {
char buyStrategyIndex = 'A';
foreach (Core.Main.DataObjects.PTMagicData.StrategySummary buyStrategy in Model.Summary.BuyStrategies) {
<tr>
<td>Buy Strat. @buyStrategyIndex</td>
<td class="text-right">@buyStrategy.Name</td>
<td>Buy Value @buyStrategyIndex</td>
<td class="text-right">@buyStrategy.Value.ToString(new System.Globalization.CultureInfo("en-US"))</td>
</tr>
buyStrategyIndex++;
}
}
</tbody>
</table>
</div>
<div class="card-box">
<h4 class="m-t-0 header-title">Active Sell Strategies</h4>
<table class="table table-striped table-sm">
<tbody>
@if (Model.Summary.SellStrategies.Count == 0) {
<tr>
<td>Sell Strat.</td>
<td class="text-right">@Model.Summary.SellStrategy</td>
<td>Sell Value</td>
<td class="text-right">@Model.Summary.SellValue.ToString(new System.Globalization.CultureInfo("en-US"))</td>
</tr>
} else {
char sellStrategyIndex = 'A';
foreach (Core.Main.DataObjects.PTMagicData.StrategySummary sellStrategy in Model.Summary.SellStrategies) {
<tr>
<td>Sell Strat. @sellStrategyIndex</td>
<td class="text-right">@sellStrategy.Name</td>
<td>Sell Value @sellStrategyIndex</td>
<td class="text-right">@sellStrategy.Value.ToString(new System.Globalization.CultureInfo("en-US"))</td>
</tr>
sellStrategyIndex++;
}
}
</tbody>
</table>
</div>
</div>
<div class="col-md-7">
<div class="row">
<div class="col-md-6">
<div class="card-box">
<h4 class="m-t-0 header-title">Settings Active Time (Last 24h)</h4>
<div id="gsChart24h">
<svg style="height:300px;width:100%"></svg>
<svg style="height:200px;width:100%"></svg>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card-box">
<h4 class="m-t-0 header-title">Settings Active Time (Last 3 days)</h4>
<div id="gsChart3d">
<svg style="height:300px;width:100%"></svg>
<svg style="height:200px;width:100%"></svg>
</div>
</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">
<thead>
<tr>
@ -217,9 +157,6 @@
</tbody>
</table>
</div>
</div>
</div>
@section Scripts {

View File

@ -101,7 +101,6 @@
<ul class="submenu">
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SettingsGeneral"><i class="fa fa-power-off"></i> General</a></li>
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SettingsAnalyzer"><i class="fa fa-magic"></i> Analyzer</a></li>
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)Transactions"><i class="fa fa-exchange"></i> Transactions</a></li>
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)PresetFiles"><i class="fa fa-edit"></i> Presets Files</a></li>
</ul>
</li>

View File

@ -82,9 +82,9 @@
double sellTriggerPrice = Model.DCALogData.AverageBuyPrice + (Model.DCALogData.AverageBuyPrice * Model.DCALogData.SellTrigger / 100);
double averageProfitPercent = 0;
if (Model.PTData.SellLog.FindAll(m => m.Market == Model.DCALogData.Market).Count > 0) {
@* if (Model.PTData.SellLog.FindAll(m => m.Market == Model.DCALogData.Market).Count > 0) {
averageProfitPercent = Model.PTData.SellLog.FindAll(m => m.Market == Model.DCALogData.Market).Average(p => p.ProfitPercent);
}
} *@
double investedFiatValue = Math.Round(Model.DCALogData.TotalCost * Model.Summary.MainMarketPrice, 2);
double currentValue = Math.Round(Model.DCALogData.Amount * Model.DCALogData.CurrentPrice, 8);

View File

@ -11,8 +11,8 @@
<div class="row">
<div class="col-md-5 px-1">
<div class="card-box px-2" style="height:305px;">
<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;"><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("")) {
<div class="trend-chart">
<svg style="height: 300px;width: 100%;"></svg>
@ -22,9 +22,11 @@
}
</div>
</div>
<div class="col-md-3 px-1">
<div class="card-box px-3" style="height:305px;">
<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-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) {
@ -34,8 +36,8 @@
<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.PTMagicConfiguration.GeneralSettings.Application.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.PTMagicConfiguration.GeneralSettings.Application.StartBalance) / Model.PTMagicConfiguration.GeneralSettings.Application.StartBalance) * 100, 2)%</text></span>
<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">
@ -49,8 +51,9 @@
</div>
<div class="col-md-4 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:305px;">
@*<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>
@if (!Model.ProfitChartDataJSON.Equals("")) {
<div class="profit-chart">
<svg style="height:300px;width:100%"></svg>
@ -61,27 +64,31 @@
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 px-1">
<div class="card-box px-2">
<div class="cdev" data-percent="100" data-duration="@Html.Raw(@Model.PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds * 1000)" data-color="#aaa,#414d59"></div>
<br>
<h4 class="m-t-0 m-b-20 header-title"><b>Market Trends at @Model.PTMagicConfiguration.GeneralSettings.Application.Exchange</b>
<small class="pull-right"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)MarketAnalyzer">more</a></small></h4>
<div class="col-md-5 px-1">
<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>
<table class="table table-sm">
<thead>
<tr>
<th>Name</th>
<th class="text-right">Markets</th>
<th class="text-right">Timeframe</th>
<th class="text-right" data-toggle="tooltip" data-placement="top" title="Pairs exceeding this threshold are excluded from the trend average.">Threshold %</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>
<th class="text-right">Change</th>
</tr>
</thead>
<tbody>
@foreach (var marketTrend in Model.MarketTrends.OrderBy(mt => mt.TrendMinutes)) {
if (Model.Summary.MarketTrendChanges.ContainsKey(marketTrend.Name)) {
double trendChange = Model.Summary.MarketTrendChanges[marketTrend.Name].OrderByDescending(mtc => mtc.TrendDateTime).First().TrendChange;
double trendChange = Model.Summary.MarketTrendChanges[marketTrend.Name].Last().TrendChange;
string trendChangeOutput = trendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
int marketCount = marketTrend.MaxMarkets;
@ -92,8 +99,12 @@
} else if (marketCount > Model.Summary.MarketSummary.Keys.Count && marketTrend.Platform.Equals("Exchange", StringComparison.InvariantCultureIgnoreCase)) {
marketCountString = Model.Summary.MarketSummary.Keys.Count.ToString();
}
// Cache the result of SplitCamelCase(marketTrend.Name)
string splitCamelCaseName = Core.Helper.SystemHelper.SplitCamelCase(marketTrend.Name);
<tr>
<td>@Core.Helper.SystemHelper.SplitCamelCase(marketTrend.Name)</td>
<td>@splitCamelCaseName</td> <!-- Use the cached value here -->
<td class="text-right">@marketCountString</td>
<td class="text-right">@Core.Helper.SystemHelper.GetProperDurationTime(marketTrend.TrendMinutes * 60, false)</td>
@if (marketTrend.TrendThreshold == 0)
@ -102,9 +113,9 @@
}
else
{
<td class="text-right">@marketTrend.TrendThreshold</td>
<td class="text-right">@marketTrend.TrendThreshold %</td>
}
<td class="text-right text-autocolor">@trendChangeOutput%</td>
<td class="text-right text-autocolor" style="font-weight:bold;">@trendChangeOutput %</td>
</tr>
}
}
@ -113,92 +124,153 @@
</div>
</div>
<div class="col-md-6 px-1">
<div class="card-box px-2">
<div class="cdev" data-percent="100" data-duration="@Html.Raw(@Model.PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds * 1000)" data-color="#aaa,#414d59"></div>
<br>
<h4 class="m-t-0 m-b-20 header-title"><b>Sales Overview</b><small class="pull-right"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SalesAnalyzer">more</a></small></h4>
<div class="col-md-7 px-1">
<div class="card-box px-3">
<h4 class="m-t-0 m-b-20 header-title">Sales Overview
<i class="fa fa-info-circle text-muted" style="font-size x-small" data-toggle="tooltip" data-placement="top" title="All data acquired via Profit Trailer API."></i>
<small class="pull-right" style="font-size: small"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SalesAnalyzer">ANALYZER</a></small>
</h4>
@{
double totalProfit = 0;
totalProfit = Model.PTData.SellLog.Sum(s => s.Profit);
double totalProfitFiat = Math.Round(totalProfit * Model.Summary.MainMarketPrice, 2);
double percentGain = Math.Round(totalProfit / Model.PTMagicConfiguration.GeneralSettings.Application.StartBalance * 100, 2);
string percentGainText = percentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
if (Model.PTData.TransactionData.Transactions.Count > 0)
{
percentGainText = "<i class=\"fa fa-info-circle text-muted\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"You have added at least one manual transaction, so the total gain percentage cannot be calculated.\"></i>";
}
double avgGrowthThisMonth = Model.PTData.MonthlyStats.FirstOrDefault(data => data.Order == 1)?.AvgGrowth ?? 0.0;
double avgGrowthLastMonth = Model.PTData.MonthlyStats.FirstOrDefault(data => data.Order == 2)?.AvgGrowth ?? 0.0;
double todaysProfit = 0;
todaysProfit = Model.PTData.SellLogToday.Sum(s => s.Profit);
double todaysStartBalance = Model.PTData.GetSnapshotBalance(Model.DateTimeNow.DateTime);
double todaysProfitFiat = Math.Round(todaysProfit * Model.Summary.MainMarketPrice, 2);
double todaysPercentGain = Math.Round(todaysProfit / todaysStartBalance * 100, 2);
//var startingBalance = Model.MiscData.StartBalance;
var totalCurrentValue = Model.totalCurrentValue;
var overviewStats = Model.StatsData;
double yesterdaysProfit = 0;
yesterdaysProfit = Model.PTData.SellLogYesterday.Sum(s => s.Profit);
double yesterdaysStartBalance = Model.PTData.GetSnapshotBalance(Model.DateTimeNow.DateTime.AddDays(-1));
double yesterdaysProfitFiat = Math.Round(yesterdaysProfit * Model.Summary.MainMarketPrice, 2);
double yesterdaysPercentGain = Math.Round(yesterdaysProfit / yesterdaysStartBalance * 100, 2);
var todaysSales = overviewStats.SalesToday;
var todaysProfit = overviewStats.ProfitToday;
var todaysFunding = overviewStats.FundingToday;
var todaysPercentGain = overviewStats.ProfitPercToday + Model.PTData.Stats.TotalFundingPercToday;
double last7DaysProfit = 0;
last7DaysProfit = Model.PTData.SellLogLast7Days.Sum(s => s.Profit);
double last7DaysStartBalance = Model.PTData.GetSnapshotBalance(Model.DateTimeNow.DateTime.AddDays(-7));
double last7DaysProfitFiat = Math.Round(last7DaysProfit * Model.Summary.MainMarketPrice, 2);
double last7DaysPercentGain = Math.Round(last7DaysProfit / last7DaysStartBalance * 100, 2);
var yesterdaysSales = overviewStats.SalesYesterday;
var yesterdaysProfit = overviewStats.ProfitYesterday;
var yesterdaysFunding = overviewStats.FundingYesterday;
var yesterdaysPercentGain = overviewStats.ProfitPercYesterday + Model.PTData.Stats.TotalFundingPercYesterday;
var last7DaysSales = overviewStats.SalesWeek;
var last7DaysProfit = overviewStats.ProfitWeek;
var last7DaysFunding = overviewStats.FundingWeek;
var last7DaysPercentGain = overviewStats.ProfitPercWeek + Model.PTData.Stats.TotalFundingPercWeek;
var thisMonthSales = overviewStats.SalesThisMonth;
var thisMonthProfit = overviewStats.ProfitThisMonth;
var thisMonthFunding = overviewStats.FundingThisMonth;
var thisMonthPercentGain = avgGrowthThisMonth;
var lastMonthSales = overviewStats.SalesLastMonth;
var lastMonthProfit = overviewStats.ProfitLastMonth;
var lastMonthFunding = overviewStats.FundingLastMonth;
var lastMonthPercentGain = avgGrowthLastMonth;
var totalSales = overviewStats.TotalSales;
var totalProfit = overviewStats.TotalProfit;
var totalFunding = overviewStats.FundingTotal;
var totalPercentGain = overviewStats.TotalProfitPerc + Model.PTData.Stats.TotalFundingPerc;
double todaysProfitFiat = Math.Round((todaysProfit + todaysFunding) * Model.PTData.Misc.FiatConversionRate, 2);
double yesterdaysProfitFiat = Math.Round((yesterdaysProfit + yesterdaysFunding) * Model.PTData.Misc.FiatConversionRate, 2);
double last7DaysProfitFiat = Math.Round((last7DaysProfit + last7DaysFunding) * Model.PTData.Misc.FiatConversionRate, 2);
double thisMonthProfitFiat = Math.Round((thisMonthProfit + thisMonthFunding) * Model.PTData.Misc.FiatConversionRate, 2);
double lastMonthProfitFiat = Math.Round((lastMonthProfit + lastMonthFunding) * Model.PTData.Misc.FiatConversionRate, 2);
double totalProfitFiat = Math.Round((totalProfit + totalFunding) * Model.PTData.Misc.FiatConversionRate, 2);
bool futuresFunding = Model.PropertiesData.IsLeverageExchange;
double last30DaysProfit = 0;
last30DaysProfit = Model.PTData.SellLogLast30Days.Sum(s => s.Profit);
double last30DaysStartBalance = Model.PTData.GetSnapshotBalance(Model.DateTimeNow.DateTime.AddDays(-30));
double last30DaysProfitFiat = Math.Round(last30DaysProfit * Model.Summary.MainMarketPrice, 2);
double last30DaysPercentGain = Math.Round(last30DaysProfit / last30DaysStartBalance * 100, 2);
}
<table class="table table-sm">
<thead>
<tr>
<th></th>
<th class="text-right">Sales</th>
<th class="text-right">Profit @Model.Summary.MainMarket</th>
<th class="text-right">Profit @Model.Summary.MainFiatCurrency</th>
<th class="text-right">Profit @Model.PTData.Misc.Market</th>
@if (futuresFunding)
{
<th class="text-right">Funding</th>
}
<th class="text-right">@Model.PTData.Properties.Currency</th>
<th class="text-right">Gain</th>
</tr>
</thead>
<tbody>
<tr>
<th>Today</th>
<td class="text-right">@Model.PTData.SellLogToday.Count</td>
<td class="text-right">@overviewStats.SalesToday</td>
<td class="text-right text-autocolor">@todaysProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + todaysProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
@if (futuresFunding)
{
<td class="text-right text-autocolor">@todaysFunding.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
}
<td class="text-right text-autocolor">@Html.Raw(todaysProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@todaysPercentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
</tr>
<tr>
<th>Yesterday</th>
<td class="text-right">@Model.PTData.SellLogYesterday.Count</td>
<td class="text-right">@yesterdaysSales</td>
<td class="text-right text-autocolor">@yesterdaysProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + yesterdaysProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
@if (futuresFunding)
{
<td class="text-right text-autocolor">@yesterdaysFunding.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
}
<td class="text-right text-autocolor">@Html.Raw(yesterdaysProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@yesterdaysPercentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
</tr>
<tr>
<th>Last 7 Days</th>
<td class="text-right">@Model.PTData.SellLogLast7Days.Count</td>
<th>7 Days</th>
<td class="text-right">@last7DaysSales</td>
<td class="text-right text-autocolor">@last7DaysProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + last7DaysProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
@if (futuresFunding)
{
<td class="text-right text-autocolor">@last7DaysFunding.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
}
<td class="text-right text-autocolor">@Html.Raw(last7DaysProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@last7DaysPercentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
</tr>
<tr>
<th>Last 30 Days</th>
<td class="text-right">@Model.PTData.SellLogLast30Days.Count</td>
<td class="text-right text-autocolor">@last30DaysProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + last30DaysProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@last30DaysPercentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
@{
var timeParts = @Model.MiscData.TimeZoneOffset.Split(':');
var timeZoneOffsetHours = int.Parse(timeParts[0]);
var timeZoneOffset = TimeSpan.FromHours(timeZoneOffsetHours);
var timeZoneInfo = TimeZoneInfo.CreateCustomTimeZone("Custom", timeZoneOffset, "Custom", "Custom");
var currentDateTime = TimeZoneInfo.ConvertTime(DateTime.UtcNow, timeZoneInfo);
var currentMonthName = currentDateTime.ToString("MMMM");
}
<th>@currentMonthName</th>
<td class="text-right">@thisMonthSales</td>
<td class="text-right text-autocolor">@thisMonthProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
@if (futuresFunding)
{
<td class="text-right text-autocolor">@thisMonthFunding.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
}
<td class="text-right text-autocolor">@Html.Raw(thisMonthProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@thisMonthPercentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
</tr>
<tr>
@{
var previousMonthDateTime = currentDateTime.AddMonths(-1);
var previousMonthName = previousMonthDateTime.ToString("MMMM");
}
<th>@previousMonthName</th>
<td class="text-right">@lastMonthSales</td>
<td class="text-right text-autocolor">@lastMonthProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
@if (futuresFunding)
{
<td class="text-right text-autocolor">@lastMonthFunding.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
}
<td class="text-right text-autocolor">@Html.Raw(lastMonthProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@lastMonthPercentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
</tr>
<tr>
<th>Total</th>
<td class="text-right">@Model.PTData.SellLog.Count</td>
<td class="text-right">@totalSales</td>
<td class="text-right text-autocolor">@totalProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + totalProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@Html.Raw(percentGainText)</td>
@if (futuresFunding)
{
<td class="text-right text-autocolor">@totalFunding.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
}
<td class="text-right text-autocolor">@Html.Raw(totalProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
<td class="text-right text-autocolor">@totalPercentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) %</td>
</tr>
</tbody>
</table>
@ -210,74 +282,176 @@
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/nvd3/nv.d3.min.js"></script>
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw.js"></script>
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw-init.js"></script>
<script 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("")) {
<text>
nv.addGraph(function() {
var chart = nv.models.pieChart()
assetDistributionChart = nv.models.pieChart()
.x(function(d) { return d.label })
.y(function(d) { return d.value })
.showLabels(true) //Display pie labels
.labelThreshold(.1) //Configure the minimum slice size for labels to show up
.labelType("percent") //Configure what type of data to show in the label. Can be "key", "value" or "percent"
.donut(true) //Turn on Donut mode. Makes pie chart look tasty!
.donutRatio(0.3) //Configure how big you want the donut hole size to be.
;
.showLabels(true)
.labelThreshold(.1)
.labelType("percent")
.donut(true)
.donutRatio(0.3);
assetDistributionData = @Html.Raw(Model.AssetDistributionData);
d3.select("#AssetDistribution svg")
.datum(@Html.Raw(Model.AssetDistributionData))
.transition().duration(350)
.call(chart);
return chart;
.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;
});
</text>
}
});
</script>
<script type="text/javascript">
(function ($) {
'use strict';
$('[role="tooltip"]').remove();
$('[data-toggle="tooltip"]').tooltip();
$('.text-autocolor').autocolor(false);
@if (!Model.Summary.CurrentGlobalSetting.SettingName.Equals(Model.LastGlobalSetting)) {
<text>
$.Notification.notify('success', 'top left', '@Core.Helper.SystemHelper.SplitCamelCase(Model.Summary.CurrentGlobalSetting.SettingName) now active!', 'PTMagic switched Profit Trailer settings to "@Core.Helper.SystemHelper.SplitCamelCase(Model.Summary.CurrentGlobalSetting.SettingName)".');
</text>
}
var trendChart; // Keep a reference to the chart
var trendData; // Keep a reference to the data
@if (!Model.TrendChartDataJSON.Equals("")) {
<text>
nv.addGraph(function () {
var lineChart = nv.models.lineChart();
trendChart = 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;
trendChart.useInteractiveGuideline(true);
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')
.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);
});
</text>
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);
}
@if (!Model.ProfitChartDataJSON.Equals("")) {
<text>
nv.addGraph(function () {
var lineChart = nv.models.lineChart();
var height = 300;
var chartData = @Html.Raw(Model.ProfitChartDataJSON);
lineChart.useInteractiveGuideline(true);
lineChart.xAxis.tickFormat(function (d) { return d3.time.format('%Y/%m/%d')(new Date(d)); });
lineChart.yAxis.axisLabel('Daily Profit').tickFormat(d3.format(',.2f'));
d3.select('.profit-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;
});
nv.utils.windowResize(trendChart.update);
return trendChart;
});
</text>
}
})(jQuery);
</script>
<script type="text/javascript">
(function ($) {
'use strict';
$('[role="tooltip"]').remove();
$('[data-toggle="tooltip"]').tooltip();
$('.text-autocolor').autocolor(false);
var profitChart; // Keep a reference to the chart
var profitData; // Keep a reference to the data
@if (!Model.ProfitChartDataJSON.Equals("")) {
<text>
nv.addGraph(function () {
profitChart = nv.models.lineChart();
var height = 300;
profitChart.useInteractiveGuideline(true);
profitChart.xAxis.tickFormat(function (d) { return d3.time.format('%m/%d')(new Date(d)); });
profitChart.yAxis.axisLabel('Daily Profit').tickFormat(d3.format(',.2f'));
profitData = @Html.Raw(Model.ProfitChartDataJSON);
d3.select('.profit-chart svg')
.datum(profitData)
.transition().duration(0)
.call(profitChart);
// Add mouseleave, and mousemove event listeners to hide tooltip
d3.select('.profit-chart').on('mouseleave', function() {
d3.select('.nvtooltip').style('opacity', 0);
});
d3.select('body').on('mousemove', function() {
var chartBounds = d3.select('.profit-chart')[0][0].getBoundingClientRect();
var mouseX = d3.event.clientX;
var mouseY = d3.event.clientY;
if (mouseX < chartBounds.left || mouseX > chartBounds.right || mouseY < chartBounds.top || mouseY > chartBounds.bottom) {
d3.select('.nvtooltip').style('opacity', 0);
}
});
nv.utils.windowResize(profitChart.update);
return profitChart;
});
</text>
}
})(jQuery);
</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;
originalLeave.call(this, obj);
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);
});
});
}
};
$('[data-toggle="tooltip"]').tooltip();
});
</script>

View File

@ -6,14 +6,20 @@ using Core.Main;
using Core.Helper;
using Core.Main.DataObjects;
using Core.Main.DataObjects.PTMagicData;
using Core.MarketAnalyzer;
using System.Globalization;
using System.Text;
namespace Monitor.Pages
{
public class DashboardBottomModel : _Internal.BasePageModelSecureAJAX
{
public ProfitTrailerData PTData = null;
public StatsData StatsData { get; set; }
public PropertiesData PropertiesData { get; set; }
public MiscData MiscData { get; set; }
public List<MarketTrend> MarketTrends { get; set; } = new List<MarketTrend>();
public double DataHours { get; set; }
public int ProfitDays { get; set; }
public string TrendChartDataJSON = "";
public string ProfitChartDataJSON = "";
public string LastGlobalSetting = "Default";
@ -26,12 +32,18 @@ namespace Monitor.Pages
base.Init();
BindData();
BuildAssetDistributionData();
}
private void BindData()
{
PTData = this.PtDataObject;
StatsData = this.PTData.Stats;
PropertiesData = this.PTData.Properties;
MiscData = this.PTData.Misc;
List<MonthlyStatsData> monthlyStatsData = this.PTData.MonthlyStats;
List<DailyPNLData> dailyPNLData = this.PTData.DailyPNL;
// Cleanup temp files
FileHelper.CleanupFilesMinutes(PTMagicMonitorBasePath + "wwwroot" + System.IO.Path.DirectorySeparatorChar + "assets" + System.IO.Path.DirectorySeparatorChar + "tmp" + System.IO.Path.DirectorySeparatorChar, 5);
@ -51,27 +63,24 @@ namespace Monitor.Pages
MarketTrends = PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.OrderBy(mt => mt.TrendMinutes).ThenByDescending(mt => mt.Platform).ToList();
BuildMarketTrendChartData();
BuildAssetDistributionData();
BuildProfitChartData();
}
private void BuildMarketTrendChartData()
{
List<string> trendChartData = new List<string>();
if (MarketTrends.Count > 0)
{
TrendChartDataJSON = "[";
int mtIndex = 0;
foreach (MarketTrend mt in MarketTrends)
{
if (mt.DisplayGraph)
{
string lineColor = "";
if (mtIndex < Constants.ChartLineColors.Length)
{
lineColor = Constants.ChartLineColors[mtIndex];
}
else
{
lineColor = Constants.ChartLineColors[mtIndex - 20];
}
string lineColor = mtIndex < Constants.ChartLineColors.Length
? Constants.ChartLineColors[mtIndex]
: Constants.ChartLineColors[mtIndex - 20];
if (Summary.MarketTrendChanges.ContainsKey(mt.Name))
{
@ -79,85 +88,141 @@ namespace Monitor.Pages
if (marketTrendChangeSummaries.Count > 0)
{
if (!TrendChartDataJSON.Equals("[")) TrendChartDataJSON += ",";
List<string> trendValues = new List<string>();
TrendChartDataJSON += "{";
TrendChartDataJSON += "key: '" + SystemHelper.SplitCamelCase(mt.Name) + "',";
TrendChartDataJSON += "color: '" + lineColor + "',";
TrendChartDataJSON += "values: [";
// Sort marketTrendChangeSummaries by TrendDateTime
marketTrendChangeSummaries = marketTrendChangeSummaries.OrderBy(m => m.TrendDateTime).ToList();
// Get trend ticks for chart
DateTime currentDateTime = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, DateTime.UtcNow.Day, DateTime.UtcNow.Hour, 0, 0);
TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{
offset = TimeSpan.Zero; // If offset is invalid, set it to zero
}
DateTime currentDateTime = DateTime.UtcNow;
DateTime startDateTime = currentDateTime.AddHours(-PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours);
DateTime endDateTime = currentDateTime;
int trendChartTicks = 0;
// Ensure startDateTime doesn't exceed the available data
DateTime earliestTrendDateTime = marketTrendChangeSummaries.Min(mtc => mtc.TrendDateTime);
startDateTime = startDateTime > earliestTrendDateTime ? startDateTime : earliestTrendDateTime;
DataHours = (currentDateTime - earliestTrendDateTime).TotalHours;
// Cache the result of SplitCamelCase(mt.Name)
string splitCamelCaseName = SystemHelper.SplitCamelCase(mt.Name);
for (DateTime tickTime = startDateTime; tickTime <= endDateTime; tickTime = tickTime.AddMinutes(PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes))
{
List<MarketTrendChange> tickRange = marketTrendChangeSummaries.FindAll(m => m.TrendDateTime >= tickTime).OrderBy(m => m.TrendDateTime).ToList();
if (tickRange.Count > 0)
// Use binary search to find the range of items that match the condition
int index = marketTrendChangeSummaries.BinarySearch(new MarketTrendChange { TrendDateTime = tickTime }, Comparer<MarketTrendChange>.Create((x, y) => x.TrendDateTime.CompareTo(y.TrendDateTime)));
if (index < 0) index = ~index;
if (index < marketTrendChangeSummaries.Count)
{
MarketTrendChange mtc = tickRange.First();
if (tickTime != startDateTime) TrendChartDataJSON += ",\n";
MarketTrendChange mtc = marketTrendChangeSummaries[index];
if (Double.IsInfinity(mtc.TrendChange)) mtc.TrendChange = 0;
TrendChartDataJSON += "{ x: new Date('" + tickTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + mtc.TrendChange.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}";
trendChartTicks++;
// Adjust tickTime to the desired timezone before converting to string
DateTime adjustedTickTime = tickTime.Add(isNegative ? -offset : offset);
trendValues.Add("{ x: new Date('" + adjustedTickTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + mtc.TrendChange.ToString("0.00", CultureInfo.InvariantCulture) + "}");
}
}
// Add most recent tick
List<MarketTrendChange> latestTickRange = marketTrendChangeSummaries.OrderByDescending(m => m.TrendDateTime).ToList();
if (latestTickRange.Count > 0)
{
MarketTrendChange mtc = latestTickRange.First();
if (trendChartTicks > 0) TrendChartDataJSON += ",\n";
if (Double.IsInfinity(mtc.TrendChange)) mtc.TrendChange = 0;
TrendChartDataJSON += "{ x: new Date('" + mtc.TrendDateTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + mtc.TrendChange.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}";
}
TrendChartDataJSON += "]";
TrendChartDataJSON += "}";
MarketTrendChange latestMtc = marketTrendChangeSummaries.Last();
if (Double.IsInfinity(latestMtc.TrendChange)) latestMtc.TrendChange = 0;
// Adjust latestMtc.TrendDateTime to the desired timezone before converting to string
DateTime adjustedLatestTrendDateTime = latestMtc.TrendDateTime.Add(isNegative ? -offset : offset);
trendValues.Add("{ x: new Date('" + adjustedLatestTrendDateTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + latestMtc.TrendChange.ToString("0.00", CultureInfo.InvariantCulture) + "}");
// Use cached splitCamelCaseName
trendChartData.Add("{ key: '" + splitCamelCaseName + "', color: '" + lineColor + "', values: [" + string.Join(",\n", trendValues) + "] }");
mtIndex++;
}
}
}
}
TrendChartDataJSON += "]";
}
TrendChartDataJSON = "[" + string.Join(",", trendChartData) + "]";
}
private void BuildProfitChartData()
{
int tradeDayIndex = 0;
string profitPerDayJSON = "";
List<object> profitPerDayList = new List<object>();
if (PTData.SellLog.Count > 0)
if (PTData.DailyPNL.Count > 0)
{
DateTime minSellLogDate = PTData.SellLog.OrderBy(sl => sl.SoldDate).First().SoldDate.Date;
DateTime graphStartDate = DateTime.UtcNow.Date.AddDays(-30);
if (minSellLogDate > graphStartDate)
// Get timezone offset
TimeSpan offset;
bool isNegative = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.StartsWith("-");
string offsetWithoutSign = PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.TrimStart('+', '-');
if (!TimeSpan.TryParse(offsetWithoutSign, out offset))
{
graphStartDate = minSellLogDate;
offset = TimeSpan.Zero; // If offset is invalid, set it to zero
}
for (DateTime salesDate = graphStartDate; salesDate <= DateTime.UtcNow.Date; salesDate = salesDate.AddDays(1))
DateTime endDate = DateTime.UtcNow.Add(isNegative ? -offset : offset).Date;
// Parse dates once and adjust them to the local timezone
Dictionary<DateTime, DailyPNLData> dailyPNLByDate = PTData.DailyPNL
.Select(data => {
DateTime dateUtc = DateTime.ParseExact(data.Date, "d-M-yyyy", CultureInfo.InvariantCulture);
DateTime dateLocal = dateUtc.Add(isNegative ? -offset : offset);
return new { Date = dateLocal.Date, Data = data };
})
.ToDictionary(
item => item.Date,
item => item.Data
);
DateTime earliestDataDate = dailyPNLByDate.Keys.Min();
DateTime startDate = endDate.AddDays(-PTMagicConfiguration.GeneralSettings.Monitor.ProfitsMaxTimeframeDays - 1); // Fetch data for timeframe + 1 days
if (startDate < earliestDataDate)
{
if (tradeDayIndex > 0)
startDate = earliestDataDate;
}
// Calculate the total days of data available
ProfitDays = (endDate - startDate).Days;
double previousDayCumulativeProfit = 0;
bool isFirstDay = true;
for (DateTime date = startDate; date <= endDate; date = date.AddDays(1))
{
profitPerDayJSON += ",\n";
// Use the dictionary to find the DailyPNLData for the date
if (dailyPNLByDate.TryGetValue(date, out DailyPNLData dailyPNL))
{
if (isFirstDay)
{
isFirstDay = false;
}
int trades = PTData.SellLog.FindAll(t => t.SoldDate.Date == salesDate).Count;
double profit = PTData.SellLog.FindAll(t => t.SoldDate.Date == salesDate).Sum(t => t.Profit);
double profitFiat = Math.Round(profit * Summary.MainMarketPrice, 2);
profitPerDayJSON += "{x: new Date('" + salesDate.ToString("yyyy-MM-dd") + "'), y: " + profitFiat.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}";
tradeDayIndex++;
else
{
// Calculate the profit for the current day
double profitFiat = Math.Round(dailyPNL.CumulativeProfitCurrency - previousDayCumulativeProfit, 2);
// Add the data point to the list
profitPerDayList.Add(new { x = new DateTimeOffset(date).ToUnixTimeMilliseconds(), y = profitFiat });
}
ProfitChartDataJSON = "[";
ProfitChartDataJSON += "{";
ProfitChartDataJSON += "key: 'Profit in " + Summary.MainFiatCurrency + "',";
ProfitChartDataJSON += "color: '" + Constants.ChartLineColors[1] + "',";
ProfitChartDataJSON += "values: [" + profitPerDayJSON + "]";
ProfitChartDataJSON += "}";
ProfitChartDataJSON += "]";
previousDayCumulativeProfit = dailyPNL.CumulativeProfitCurrency;
}
}
// Convert the list to a JSON string using Newtonsoft.Json
ProfitChartDataJSON = Newtonsoft.Json.JsonConvert.SerializeObject(new[] {
new {
key = "Profit in " + PTData.Misc.Market,
color = Constants.ChartLineColors[1],
values = profitPerDayList
}
});
}
}
private void BuildAssetDistributionData()
{
// the per PT-Eelroy, the PT API doesn't provide these values when using leverage, so they are calculated here to cover either case.
@ -171,7 +236,6 @@ namespace Monitor.Pages
foreach (Core.Main.DataObjects.PTMagicData.DCALogData dcaLogEntry in PTData.DCALog)
{
string sellStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Summary, dcaLogEntry.SellStrategies, dcaLogEntry.SellStrategy, isSellStrategyTrue, isTrailingSellActive);
// Aggregate totals
double leverage = dcaLogEntry.Leverage;
if (leverage == 0)

View File

@ -1,86 +0,0 @@
@page
@model SalesListModel
@{
Layout = null;
}
<div class="modal-header">
<h4 class="modal-title mt-0">Showing @Model.SellLog.Count sales for @Model.SalesTimeframe</h4>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
</div>
<div class="modal-body">
<table id="sales-list" class="table table-sm">
<thead>
<tr>
<th>Sold Time</th>
<th>Market</th>
<th class="text-right">Avg. Bought Price</th>
<th class="text-right">DCA</th>
<th class="text-right">Sold Price</th>
<th class="text-right">Sold Amount</th>
<th class="text-right">Bought Cost</th>
<th class="text-right">Sold Value</th>
<th class="text-right">Profit @Model.Summary.MainMarket</th>
<th class="text-right">Profit @Model.Summary.MainFiatCurrency</th>
<th class="text-right">Profit %</th>
</tr>
</thead>
<tbody>
@foreach (Core.Main.DataObjects.PTMagicData.SellLogData sellLogEntry in Model.SellLog) {
double profitFiat = Math.Round(sellLogEntry.Profit * Model.Summary.MainMarketPrice, 2);
<tr>
<td>@sellLogEntry.SoldDate.ToShortDateString() @sellLogEntry.SoldDate.ToLongTimeString()</td>
<td><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, sellLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@sellLogEntry.Market</a></td>
<td class="text-right">@sellLogEntry.AverageBuyPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right">
@if (sellLogEntry.BoughtTimes > 0) {
@sellLogEntry.BoughtTimes;
}
</td>
<td class="text-right">@sellLogEntry.SoldPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right">@sellLogEntry.SoldAmount.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right">@sellLogEntry.TotalCost.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right">@sellLogEntry.SoldValue.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@sellLogEntry.Profit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@profitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))</td>
<td class="text-right text-autocolor">@sellLogEntry.ProfitPercent.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
</tr>
}
</tbody>
</table>
</div>
<div class="modal-footer">
</div>
<script type="text/javascript">
$(function () {
//Buttons examples
var table = $('#sales-list').DataTable({
lengthChange: false,
searching: false,
paging: false,
info: false,
ordering: false,
buttons: ['copy', 'excel', 'pdf']
});
table.buttons().container()
.appendTo('#sales-list_wrapper .col-md-6:eq(0)');
$('.btn-trend-relation').click(function () {
var relation = $(this).data('trend-relation');
$('.btn-trend-relation').addClass('btn-custom');
$(this).removeClass('btn-custom');
if (relation == 'absolute') {
$('#trends-absolute').removeClass('hidden');
$('#trends-relative').addClass('hidden');
} else {
$('#trends-absolute').addClass('hidden');
$('#trends-relative').removeClass('hidden');
}
});
})
</script>

View File

@ -1,49 +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 Core.MarketAnalyzer;
namespace Monitor.Pages {
public class SalesListModel : _Internal.BasePageModelSecure {
public ProfitTrailerData PTData = null;
private string salesDateString = "";
private string salesMonthString = "";
public string SalesTimeframe = "";
public DateTime SalesDate = Constants.confMinDate;
public List<SellLogData> SellLog = new List<SellLogData>();
public void OnGet() {
// Initialize Config
base.Init();
BindData();
}
private void BindData() {
salesDateString = GetStringParameter("d", "");
salesMonthString = GetStringParameter("m", "");
PTData = this.PtDataObject;
if (!salesDateString.Equals("")) {
SalesDate = SystemHelper.TextToDateTime(salesDateString, Constants.confMinDate);
if (SalesDate != Constants.confMinDate) {
SalesTimeframe = SalesDate.ToShortDateString();
SellLog = PTData.SellLog.FindAll(sl => sl.SoldDate.Date == SalesDate.Date).OrderByDescending(sl => sl.SoldDate).ToList();
}
} else if (!salesMonthString.Equals("")) {
SalesDate = SystemHelper.TextToDateTime(salesMonthString + "-01", Constants.confMinDate);
if (SalesDate != Constants.confMinDate) {
SalesTimeframe = SalesDate.ToString("MMMM yyyy", new System.Globalization.CultureInfo("en-US"));
SellLog = PTData.SellLog.FindAll(sl => sl.SoldDate.Date.Month == SalesDate.Month && sl.SoldDate.Date.Year == SalesDate.Year).OrderByDescending(sl => sl.SoldDate).ToList();
}
}
}
}
}

View File

@ -50,7 +50,7 @@ namespace Monitor
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(900);
options.IdleTimeout = TimeSpan.FromSeconds(1800);
options.Cookie.HttpOnly = true;
options.Cookie.Name = "PTMagicMonitor" + systemConfiguration.GeneralSettings.Monitor.Port.ToString();
});

View File

@ -639,7 +639,7 @@ a.text-dark:hover {
}
.bg-success {
background-color: #81c868 !important;
background-color: #296a12 !important;
}
.bg-info {
@ -647,11 +647,11 @@ a.text-dark:hover {
}
.bg-warning {
background-color: #ffbd4a !important;
background-color: #563a09 !important;
}
.bg-danger {
background-color: #f05050 !important;
background-color: #6e0e0e !important;
}
.bg-muted {
@ -759,7 +759,7 @@ a.text-dark:hover {
}
.label-success {
background-color: #81c868;
background-color: #3b5e2f;
}
.label-info {
@ -767,11 +767,11 @@ a.text-dark:hover {
}
.label-warning {
background-color: #ffbd4a;
background-color: #847f0a;
}
.label-danger {
background-color: #f05050;
background-color: #601f1f;
}
.label-purple {
@ -829,7 +829,7 @@ a.text-dark:hover {
}
.badge-success {
background-color: #81c868;
background-color: #307516;
}
.badge-info {
@ -837,11 +837,11 @@ a.text-dark:hover {
}
.badge-warning {
background-color: #ffbd4a;
background-color: #6f5019;
}
.badge-danger {
background-color: #f05050;
background-color: #6a1414;
}
.badge-purple {

37
PTMagic.sln Normal file
View File

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core.csproj", "{DC985FA9-87CC-4C60-A587-E646E74A1A4A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Monitor", "Monitor\Monitor.csproj", "{D967C466-E4BF-40A9-84FD-698079FAD3D5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PTMagic", "PTMagic\PTMagic.csproj", "{D81F5541-438E-42C7-B6E7-B859875B18A8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DC985FA9-87CC-4C60-A587-E646E74A1A4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC985FA9-87CC-4C60-A587-E646E74A1A4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC985FA9-87CC-4C60-A587-E646E74A1A4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC985FA9-87CC-4C60-A587-E646E74A1A4A}.Release|Any CPU.Build.0 = Release|Any CPU
{D967C466-E4BF-40A9-84FD-698079FAD3D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D967C466-E4BF-40A9-84FD-698079FAD3D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D967C466-E4BF-40A9-84FD-698079FAD3D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D967C466-E4BF-40A9-84FD-698079FAD3D5}.Release|Any CPU.Build.0 = Release|Any CPU
{D81F5541-438E-42C7-B6E7-B859875B18A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D81F5541-438E-42C7-B6E7-B859875B18A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D81F5541-438E-42C7-B6E7-B859875B18A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D81F5541-438E-42C7-B6E7-B859875B18A8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3646F7E-91F6-4D66-9B1B-2B74317580A4}
EndGlobalSection
EndGlobal

View File

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

View File

@ -10,13 +10,10 @@
"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
"StartBalance": 0, // The balance you had in your wallet when you started working with Profit Trailer
"TimezoneOffset": "+0:00", // Your timezone offset from UTC time
"MainFiatCurrency": "USD", // Your main fiat currency that will be used in the monitor
"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
"CoinMarketCapAPIKey": "", //CoinMarketCap Api
"FreeCurrencyConverterAPIKey": "" // If "MainFiatCurrency" above is anything other than USD, you must obtain an API key from https://free.currencyconverterapi.com/free-api-key
//"FreeCurrencyConverterAPIKey": "" // If "MainFiatCurrency" above is anything other than USD, you must obtain an API key from https://free.currencyconverterapi.com/free-api-key
},
"Monitor": {
"IsPasswordProtected": true, // Defines if your monitor will be asking to setup a password on its first start
@ -24,8 +21,9 @@
"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)
"GraphIntervalMinutes": 60, // The interval for the monitor market trend graph to draw points
"GraphMaxTimeframeHours": 24, // This will enable you to define the timeframe that your graph for market trends covers
"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
"MaxTopMarkets": 20, // The amount of top markets being shown in your Sales Analyzer

View File

@ -10,13 +10,10 @@
"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
"StartBalance": 0, // The balance you had in your wallet when you started working with Profit Trailer
"TimezoneOffset": "+0:00", // Your timezone offset from UTC time
"MainFiatCurrency": "USD", // Your main fiat currency that will be used in the monitor
"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
"CoinMarketCapAPIKey": "", //CoinMarketCap Api
"FreeCurrencyConverterAPIKey": "" // If "MainFiatCurrency" above is anything other than USD, you must obtain an API key from https://free.currencyconverterapi.com/free-api-key
},
"Monitor": {
"IsPasswordProtected": true, // Defines if your monitor will be asking to setup a password on its first start
@ -24,8 +21,9 @@
"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)
"GraphIntervalMinutes": 60, // The interval for the monitor market trend graph to draw points
"GraphMaxTimeframeHours": 24, // This will enable you to define the timeframe that your graph for market trends covers
"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
"MaxTopMarkets": 20, // The amount of top markets being shown in your Sales Analyzer

View File

@ -16,7 +16,7 @@
- This version of PT Magic only works with Profit Trailer v2.4.x and above
### .Net
- PTMagic requires .Net Core Runtime 3.1 on the host operating system (download: https://dotnet.microsoft.com/download)
- PTMagic requires .Net Core Runtime 7 on the host operating system (download: https://dotnet.microsoft.com/download)
### Settings
- **If you are updating from any version of PTMagic prior to 2.4.1,** check the default settings included with this release for any new lines that might need to be added to your settings.general.json file.