Sales Analyzer Top Markets

This commit is contained in:
HojouFotytu 2024-01-18 02:13:09 +09:00
parent 615e07986b
commit b6b8647f21
5 changed files with 149 additions and 131 deletions

View File

@ -467,7 +467,13 @@ namespace Core.Main.DataObjects.PTMagicData
public double TotalSales { get; set; } public double TotalSales { get; set; }
public double AvgGrowth { get; set; } public double AvgGrowth { get; set; }
public double Order { 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 PTStrategy public class PTStrategy

View File

@ -21,6 +21,7 @@ namespace Core.Main.DataObjects
private List<DailyPNLData> _dailyPNL = new List<DailyPNLData>(); private List<DailyPNLData> _dailyPNL = new List<DailyPNLData>();
private List<DailyTCVData> _dailyTCV = new List<DailyTCVData>(); private List<DailyTCVData> _dailyTCV = new List<DailyTCVData>();
private List<MonthlyStatsData> _monthlyStats = new List<MonthlyStatsData>(); private List<MonthlyStatsData> _monthlyStats = new List<MonthlyStatsData>();
private List<ProfitablePairsData> _profitablePairs = new List<ProfitablePairsData>();
private decimal? _totalProfit = null; private decimal? _totalProfit = null;
public decimal? TotalProfit public decimal? TotalProfit
{ {
@ -48,6 +49,7 @@ namespace Core.Main.DataObjects
private DateTime _dcaLogRefresh = DateTime.UtcNow; private DateTime _dcaLogRefresh = DateTime.UtcNow;
private DateTime _miscRefresh = DateTime.UtcNow; private DateTime _miscRefresh = DateTime.UtcNow;
private DateTime _propertiesRefresh = DateTime.UtcNow; private DateTime _propertiesRefresh = DateTime.UtcNow;
private DateTime _profitablePairsRefresh = DateTime.UtcNow;
private volatile object _dailyPNLLock = new object(); private volatile object _dailyPNLLock = new object();
private volatile object _dailyTCVLock = new object(); private volatile object _dailyTCVLock = new object();
private volatile object _monthlyStatsLock = new object(); private volatile object _monthlyStatsLock = new object();
@ -56,7 +58,8 @@ namespace Core.Main.DataObjects
private volatile object _sellLock = new object(); private volatile object _sellLock = new object();
private volatile object _dcaLock = new object(); private volatile object _dcaLock = new object();
private volatile object _miscLock = new object(); private volatile object _miscLock = new object();
private volatile object _propertiesLock = new object(); private volatile object _propertiesLock = new object();
private volatile object _profitablePairsLock = new object();
private TimeSpan? _offsetTimeSpan = null; private TimeSpan? _offsetTimeSpan = null;
public void DoLog(string message) public void DoLog(string message)
{ {
@ -305,6 +308,90 @@ namespace Core.Main.DataObjects
Order = dailyPNLDataJson["order"], Order = dailyPNLDataJson["order"],
}; };
} }
public List<ProfitablePairsData> ProfitablePairs
{
get
{
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);
}
}
if (basicSection != null && extraSection != null)
{
break;
}
}
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)
{
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 public List<DailyTCVData> DailyTCV
@ -493,7 +580,7 @@ namespace Core.Main.DataObjects
this.BuildSellLogData(sellDataPage); this.BuildSellLogData(sellDataPage);
pageIndex++; pageIndex++;
requestedPages++; requestedPages++;
Console.WriteLine($"Importing sale: {pageIndex}"); Console.WriteLine($"Importing sale: {pageIndex}");
} }
else else

View File

@ -219,8 +219,49 @@
<p>Unable to load graph, no sales data found.</p> <p>Unable to load graph, no sales data found.</p>
} }
</div> </div>
</div>
</div> </div>
<div class="row">
<div class="col-sm-12">
<div class="card-box">
<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>Avg/Trade</th>
<th scope="col" class="text-right" data-tablesaw-sortable-col>Profit @Model.PTData.Properties.Currency</th>
</tr>
</thead>
<tbody>
@{
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>@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>
<td class="text-right text-autocolor-saw">@profitFiat</td>
</tr>
rank++;
}
}
</tbody>
</table>
</div>
</div>
</div> </div>
<div class="row"> <div class="row">
@ -315,55 +356,15 @@
</div> </div>
</div> </div>
<div class="row">
<div class="col-sm-12">
<div class="card-box">
<h4 class="m-t-0 header-title"><b>Top @Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxTopMarkets Sales Market Analysis</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>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>
</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);
<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>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
<div id="salesList" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" style="display: none;"> @* <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-dialog modal-full">
<div class="modal-content"> <div class="modal-content">
<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i> <i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>
</div><!-- /.modal-content --> </div><!-- /.modal-content -->
</div><!-- /.modal-dialog --> </div><!-- /.modal-dialog -->
</div><!-- /.modal --> </div><!-- /.modal --> *@
} }
@section Scripts { @section Scripts {
@ -402,34 +403,6 @@
nv.utils.windowResize(lineChart.update); nv.utils.windowResize(lineChart.update);
return lineChart; 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) { $("#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>'); $(this).find(".modal-content").html('<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>');
@ -481,6 +454,7 @@
d3.select('.nvtooltip').style('opacity', 0); d3.select('.nvtooltip').style('opacity', 0);
} }
}); });
nv.utils.windowResize(cumulativeProfitChart.update);
return cumulativeProfitChart; return cumulativeProfitChart;
}); });
</text> </text>
@ -528,6 +502,7 @@
d3.select('.nvtooltip').style('opacity', 0); d3.select('.nvtooltip').style('opacity', 0);
} }
}); });
nv.utils.windowResize(TCVChart.update);
return TCVChart; return TCVChart;
}); });
</text> </text>
@ -575,6 +550,7 @@
d3.select('.nvtooltip').style('opacity', 0); d3.select('.nvtooltip').style('opacity', 0);
} }
}); });
nv.utils.windowResize(profitChart.update);
return profitChart; return profitChart;
}); });
</text> </text>

View File

@ -16,6 +16,7 @@ namespace Monitor.Pages
public StatsData StatsData { get; set; } public StatsData StatsData { get; set; }
public List<DailyPNLData> DailyPNL { get; set; } public List<DailyPNLData> DailyPNL { get; set; }
public List<DailyTCVData> DailyTCV { get; set; } public List<DailyTCVData> DailyTCV { get; set; }
public List<ProfitablePairsData> ProfitablePairs { get; set; }
public int ProfitDays { get; set; } public int ProfitDays { get; set; }
public int TCVDays { get; set; } public int TCVDays { get; set; }
public List<MonthlyStatsData> MonthlyStats { get; set; } public List<MonthlyStatsData> MonthlyStats { get; set; }
@ -48,21 +49,16 @@ namespace Monitor.Pages
MonthlyStats = this.PTData.MonthlyStats; MonthlyStats = this.PTData.MonthlyStats;
DailyPNL = this.PTData.DailyPNL; DailyPNL = this.PTData.DailyPNL;
DailyTCV = this.PTData.DailyTCV; DailyTCV = this.PTData.DailyTCV;
ProfitablePairs = this.PTData.ProfitablePairs;
//List<MonthlyStatsData> monthlyStatsData = this.PTData.MonthlyStats;
//List<DailyPNLData> dailyPNLData = this.PTData.DailyPNL;
// Convert local offset time to UTC // Convert local offset time to UTC
TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", "")); TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan); DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
BuildTopMarkets();
BuildSalesChartData(); BuildSalesChartData();
BuildProfitChartData(); BuildProfitChartData();
BuildCumulativeProfitChartData(); BuildCumulativeProfitChartData();
BuildTCVChartData(); BuildTCVChartData();
//MonthlyAverages(monthlyStatsData, PTData.Stats.FundingTotal);
} }
private void BuildTCVChartData() private void BuildTCVChartData()
{ {
@ -314,25 +310,14 @@ namespace Monitor.Pages
int tradeDayIndex = 0; int tradeDayIndex = 0;
string tradesPerDayJSON = ""; string tradesPerDayJSON = "";
string profitPerDayJSON = "";
string balancePerDayJSON = "";
double balance = 0.0;
for (DateTime salesDate = graphStartDate; salesDate <= DateTimeNow.DateTime.Date; salesDate = salesDate.AddDays(1)) for (DateTime salesDate = graphStartDate; salesDate <= DateTimeNow.DateTime.Date; salesDate = salesDate.AddDays(1))
{ {
if (tradeDayIndex > 0) if (tradeDayIndex > 0)
{ {
tradesPerDayJSON += ",\n"; tradesPerDayJSON += ",\n";
profitPerDayJSON += ",\n";
balancePerDayJSON += ",\n";
} }
double profit = 0;
int trades = PTData.SellLog.FindAll(t => t.SoldDate.Date == salesDate.Date).Count; 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 + "}"; 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++; tradeDayIndex++;
} }
TradesChartDataJSON = "["; TradesChartDataJSON = "[";
@ -343,42 +328,7 @@ namespace Monitor.Pages
TradesChartDataJSON += "}"; TradesChartDataJSON += "}";
TradesChartDataJSON += "]"; TradesChartDataJSON += "]";
ProfitChartDataJSON = "[";
ProfitChartDataJSON += "{";
ProfitChartDataJSON += "key: 'Profit in " + Summary.MainFiatCurrency + "',";
ProfitChartDataJSON += "color: '" + Constants.ChartLineColors[1] + "',";
ProfitChartDataJSON += "values: [" + profitPerDayJSON + "]";
ProfitChartDataJSON += "}";
ProfitChartDataJSON += "]";
BalanceChartDataJSON = "[";
BalanceChartDataJSON += "{";
BalanceChartDataJSON += "key: 'Profit in " + Summary.MainFiatCurrency + "',";
BalanceChartDataJSON += "color: '" + Constants.ChartLineColors[1] + "',";
BalanceChartDataJSON += "values: [" + balancePerDayJSON + "]";
BalanceChartDataJSON += "}";
BalanceChartDataJSON += "]";
for (DateTime salesDate = DateTimeNow.DateTime.Date; salesDate >= MinSellLogDate; salesDate = salesDate.AddDays(-1))
{
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))
{
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);
}
} }
} }
} }

View File

@ -51,7 +51,7 @@
<div class="col-md-4 px-1"> <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="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:320px;"> <div class="card-box px-2" style="height:340px;">
<h4 class="m-t-0 m-b-20 header-title" style="display: inline;"><b>Daily Profit </b><i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="@Model.ProfitDays days of data available. Currently Set to @Model.PTMagicConfiguration.GeneralSettings.Monitor.ProfitsMaxTimeframeDays days in general settings."></i> <h4 class="m-t-0 m-b-20 header-title" style="display: inline;"><b>Daily Profit </b><i class="fa fa-info-circle text-muted" 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("")) { @if (!Model.ProfitChartDataJSON.Equals("")) {
<div class="profit-chart"> <div class="profit-chart">
@ -308,7 +308,7 @@
d3.select('.nvtooltip').style('opacity', 0); d3.select('.nvtooltip').style('opacity', 0);
} }
}); });
nv.utils.windowResize(assetDistributionChart.update);
return assetDistributionChart; return assetDistributionChart;
}); });
</text> </text>
@ -355,7 +355,7 @@
d3.select('.nvtooltip').style('opacity', 0); d3.select('.nvtooltip').style('opacity', 0);
} }
}); });
nv.utils.windowResize(trendChart.update);
return trendChart; return trendChart;
}); });
</text> </text>
@ -403,8 +403,7 @@
d3.select('.nvtooltip').style('opacity', 0); d3.select('.nvtooltip').style('opacity', 0);
} }
}); });
nv.utils.windowResize(profitChart.update);
return profitChart; return profitChart;
}); });
</text> </text>