Trading bot grain (#33)
* Trading bot Grain * Fix a bit more of the trading bot * Advance on the tradingbot grain * Fix build * Fix db script * Fix user login * Fix a bit backtest * Fix cooldown and backtest * start fixing bot start * Fix startup * Setup local db * Fix build and update candles and scenario * Add bot registry * Add reminder * Updateing the grains * fix bootstraping * Save stats on tick * Save bot data every tick * Fix serialization * fix save bot stats * Fix get candles * use dict instead of list for position * Switch hashset to dict * Fix a bit * Fix bot launch and bot view * add migrations * Remove the tolist * Add agent grain * Save agent summary * clean * Add save bot * Update get bots * Add get bots * Fix stop/restart * fix Update config * Update scanner table on new backtest saved * Fix backtestRowDetails.tsx * Fix agentIndex * Update agentIndex * Fix more things * Update user cache * Fix * Fix account load/start/restart/run
This commit is contained in:
@@ -340,7 +340,7 @@ public class BacktestController : BaseController
|
||||
// Convert IndicatorRequest objects to Indicator domain objects
|
||||
foreach (var indicatorRequest in request.Config.Scenario.Indicators)
|
||||
{
|
||||
var indicator = new Indicator(indicatorRequest.Name, indicatorRequest.Type)
|
||||
var indicator = new IndicatorBase(indicatorRequest.Name, indicatorRequest.Type)
|
||||
{
|
||||
SignalType = indicatorRequest.SignalType,
|
||||
MinimumHistory = indicatorRequest.MinimumHistory,
|
||||
@@ -706,7 +706,6 @@ public class BacktestController : BaseController
|
||||
}
|
||||
|
||||
|
||||
|
||||
public MoneyManagement Map(MoneyManagementRequest moneyManagementRequest)
|
||||
{
|
||||
return new MoneyManagement
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
using Managing.Api.Models.Requests;
|
||||
using Managing.Api.Models.Responses;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Hubs;
|
||||
using Managing.Application.ManageBot.Commands;
|
||||
@@ -8,7 +7,6 @@ using Managing.Domain.Backtests;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Shared.Helpers;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Strategies.Base;
|
||||
@@ -244,7 +242,7 @@ public class DataController : ControllerBase
|
||||
{
|
||||
return Ok(new CandlesWithIndicatorsResponse
|
||||
{
|
||||
Candles = new List<Candle>(),
|
||||
Candles = new HashSet<Candle>(),
|
||||
IndicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>()
|
||||
});
|
||||
}
|
||||
@@ -290,8 +288,8 @@ public class DataController : ControllerBase
|
||||
}
|
||||
|
||||
// Get active bots
|
||||
var activeBots = await _mediator.Send(new GetActiveBotsCommand());
|
||||
var currentCount = activeBots.Count;
|
||||
var activeBots = await _mediator.Send(new GetBotsByStatusCommand(BotStatus.Up));
|
||||
var currentCount = activeBots.Count();
|
||||
|
||||
// Get previous count from cache
|
||||
var previousCount = _cacheService.GetValue<int>(previousCountKey);
|
||||
@@ -343,11 +341,11 @@ public class DataController : ControllerBase
|
||||
}
|
||||
|
||||
// Get active bots
|
||||
var activeBots = await _mediator.Send(new GetActiveBotsCommand());
|
||||
var activeBots = await _mediator.Send(new GetBotsByStatusCommand(BotStatus.Up));
|
||||
|
||||
// Calculate PnL for each bot once and store in a list of tuples
|
||||
var botsWithPnL = activeBots
|
||||
.Select(bot => new { Bot = bot, PnL = bot.GetProfitAndLoss() })
|
||||
.Select(bot => new { Bot = bot, PnL = bot.Pnl })
|
||||
.OrderByDescending(item => item.PnL)
|
||||
.Take(3)
|
||||
.ToList();
|
||||
@@ -441,55 +439,42 @@ public class DataController : ControllerBase
|
||||
/// </summary>
|
||||
/// <param name="strategy">The trading bot to map</param>
|
||||
/// <returns>A view model with detailed strategy information</returns>
|
||||
private UserStrategyDetailsViewModel MapStrategyToViewModel(ITradingBot strategy)
|
||||
private UserStrategyDetailsViewModel MapStrategyToViewModel(Bot strategy)
|
||||
{
|
||||
// Get the runtime directly from the bot
|
||||
TimeSpan runtimeSpan = strategy.GetRuntime();
|
||||
|
||||
// Get the startup time from the bot's internal property
|
||||
// If bot is not running, we use MinValue as a placeholder
|
||||
DateTime startupTime = DateTime.MinValue;
|
||||
if (strategy is Bot bot && bot.StartupTime != DateTime.MinValue)
|
||||
{
|
||||
startupTime = bot.StartupTime;
|
||||
}
|
||||
|
||||
// Calculate ROI percentage based on PnL relative to account value
|
||||
decimal pnl = strategy.GetProfitAndLoss();
|
||||
decimal pnl = strategy.Pnl;
|
||||
|
||||
// If we had initial investment amount, we could calculate ROI like:
|
||||
decimal initialInvestment = 1000; // Example placeholder, ideally should come from the account
|
||||
decimal roi = pnl != 0 ? (pnl / initialInvestment) * 100 : 0;
|
||||
|
||||
// Calculate volume statistics
|
||||
decimal totalVolume = TradingBox.GetTotalVolumeTraded(strategy.Positions);
|
||||
decimal volumeLast24h = TradingBox.GetLast24HVolumeTraded(strategy.Positions);
|
||||
decimal totalVolume = strategy.Volume;
|
||||
decimal volumeLast24h = strategy.Volume;
|
||||
|
||||
// Calculate win/loss statistics
|
||||
(int wins, int losses) = TradingBox.GetWinLossCount(strategy.Positions);
|
||||
(int wins, int losses) = (strategy.TradeWins, strategy.TradeLosses);
|
||||
|
||||
int winRate = wins + losses > 0 ? (wins * 100) / (wins + losses) : 0;
|
||||
// Calculate ROI for last 24h
|
||||
decimal roiLast24h = TradingBox.GetLast24HROI(strategy.Positions);
|
||||
decimal roiLast24h = strategy.Roi;
|
||||
|
||||
return new UserStrategyDetailsViewModel
|
||||
{
|
||||
Name = strategy.Name,
|
||||
ScenarioName = strategy.Config.ScenarioName,
|
||||
State = strategy.GetStatus() == BotStatus.Up.ToString() ? "RUNNING" :
|
||||
strategy.GetStatus() == BotStatus.Down.ToString() ? "STOPPED" : "UNUSED",
|
||||
State = strategy.Status.ToString(),
|
||||
PnL = pnl,
|
||||
ROIPercentage = roi,
|
||||
ROILast24H = roiLast24h,
|
||||
Runtime = startupTime,
|
||||
WinRate = strategy.GetWinRate(),
|
||||
Runtime = strategy.StartupTime,
|
||||
WinRate = winRate,
|
||||
TotalVolumeTraded = totalVolume,
|
||||
VolumeLast24H = volumeLast24h,
|
||||
Wins = wins,
|
||||
Losses = losses,
|
||||
Positions = strategy.Positions.OrderByDescending(p => p.Date)
|
||||
.ToList(), // Include sorted positions with most recent first
|
||||
Identifier = strategy.Identifier,
|
||||
WalletBalances = strategy.WalletBalances,
|
||||
Positions = new Dictionary<Guid, Position>(),
|
||||
Identifier = strategy.Identifier.ToString(),
|
||||
WalletBalances = new Dictionary<DateTime, decimal>(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -544,18 +529,17 @@ public class DataController : ControllerBase
|
||||
continue; // Skip agents with no strategies
|
||||
}
|
||||
|
||||
// Combine all positions from all strategies
|
||||
var allPositions = strategies.SelectMany<ITradingBot, Position>(s => s.Positions).ToList();
|
||||
// TODO: Add this calculation into repository for better performance
|
||||
|
||||
var globalPnL = strategies.Sum(s => s.Pnl);
|
||||
var globalVolume = strategies.Sum(s => s.Volume);
|
||||
var globalVolumeLast24h = strategies.Sum(s => s.Volume);
|
||||
|
||||
// Calculate agent metrics for platform totals
|
||||
decimal totalPnL = TradingBox.GetPnLInTimeRange(allPositions, timeFilter);
|
||||
decimal totalVolume = TradingBox.GetTotalVolumeTraded(allPositions);
|
||||
decimal volumeLast24h = TradingBox.GetLast24HVolumeTraded(allPositions);
|
||||
|
||||
// Add to platform totals
|
||||
totalPlatformPnL += totalPnL;
|
||||
totalPlatformVolume += totalVolume;
|
||||
totalPlatformVolumeLast24h += volumeLast24h;
|
||||
totalPlatformPnL += globalPnL;
|
||||
totalPlatformVolume += globalVolume;
|
||||
totalPlatformVolumeLast24h += globalVolumeLast24h;
|
||||
}
|
||||
|
||||
// Set the platform totals
|
||||
@@ -569,128 +553,25 @@ public class DataController : ControllerBase
|
||||
return Ok(summary);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a list of agent summaries for the agent index page
|
||||
/// </summary>
|
||||
/// <param name="timeFilter">Time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)</param>
|
||||
/// <returns>A list of agent summaries sorted by performance</returns>
|
||||
[HttpGet("GetAgentIndex")]
|
||||
public async Task<ActionResult<AgentIndexViewModel>> GetAgentIndex(string timeFilter = "Total")
|
||||
{
|
||||
// Validate time filter
|
||||
var validTimeFilters = new[] { "24H", "3D", "1W", "1M", "1Y", "Total" };
|
||||
if (!validTimeFilters.Contains(timeFilter))
|
||||
{
|
||||
timeFilter = "Total"; // Default to Total if invalid
|
||||
}
|
||||
|
||||
string cacheKey = $"AgentIndex_{timeFilter}";
|
||||
|
||||
// Check if the agent index is already cached
|
||||
var cachedIndex = _cacheService.GetValue<AgentIndexViewModel>(cacheKey);
|
||||
|
||||
if (cachedIndex != null)
|
||||
{
|
||||
return Ok(cachedIndex);
|
||||
}
|
||||
|
||||
// Get all agents and their strategies
|
||||
var agentsWithStrategies = await _mediator.Send(new GetAllAgentsCommand(timeFilter));
|
||||
|
||||
// Create the agent index response
|
||||
var agentIndex = new AgentIndexViewModel
|
||||
{
|
||||
TimeFilter = timeFilter
|
||||
};
|
||||
|
||||
// Create summaries for each agent
|
||||
foreach (var agent in agentsWithStrategies)
|
||||
{
|
||||
var user = agent.Key;
|
||||
var strategies = agent.Value;
|
||||
|
||||
if (strategies.Count == 0)
|
||||
{
|
||||
continue; // Skip agents with no strategies
|
||||
}
|
||||
|
||||
// Combine all positions from all strategies
|
||||
var allPositions = strategies.SelectMany<ITradingBot, Position>(s => s.Positions).ToList();
|
||||
|
||||
// Calculate agent metrics
|
||||
decimal totalPnL = TradingBox.GetPnLInTimeRange(allPositions, timeFilter);
|
||||
decimal pnlLast24h = TradingBox.GetPnLInTimeRange(allPositions, "24H");
|
||||
|
||||
decimal totalROI = TradingBox.GetROIInTimeRange(allPositions, timeFilter);
|
||||
decimal roiLast24h = TradingBox.GetROIInTimeRange(allPositions, "24H");
|
||||
|
||||
(int wins, int losses) = TradingBox.GetWinLossCountInTimeRange(allPositions, timeFilter);
|
||||
|
||||
// Calculate trading volumes
|
||||
decimal totalVolume = TradingBox.GetTotalVolumeTraded(allPositions);
|
||||
decimal volumeLast24h = TradingBox.GetLast24HVolumeTraded(allPositions);
|
||||
|
||||
// Calculate win rate
|
||||
int averageWinRate = 0;
|
||||
if (wins + losses > 0)
|
||||
{
|
||||
averageWinRate = (wins * 100) / (wins + losses);
|
||||
}
|
||||
|
||||
// Add to agent summaries
|
||||
var agentSummary = new AgentSummaryViewModel
|
||||
{
|
||||
AgentName = user.AgentName,
|
||||
TotalPnL = totalPnL,
|
||||
PnLLast24h = pnlLast24h,
|
||||
TotalROI = totalROI,
|
||||
ROILast24h = roiLast24h,
|
||||
Wins = wins,
|
||||
Losses = losses,
|
||||
AverageWinRate = averageWinRate,
|
||||
ActiveStrategiesCount = strategies.Count,
|
||||
TotalVolume = totalVolume,
|
||||
VolumeLast24h = volumeLast24h
|
||||
};
|
||||
|
||||
agentIndex.AgentSummaries.Add(agentSummary);
|
||||
}
|
||||
|
||||
// Sort agent summaries by total PnL (highest first)
|
||||
agentIndex.AgentSummaries = agentIndex.AgentSummaries.OrderByDescending(a => a.TotalPnL).ToList();
|
||||
|
||||
// Cache the results for 5 minutes
|
||||
_cacheService.SaveValue(cacheKey, agentIndex, TimeSpan.FromMinutes(5));
|
||||
|
||||
return Ok(agentIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a paginated list of agent summaries for the agent index page
|
||||
/// </summary>
|
||||
/// <param name="timeFilter">Time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)</param>
|
||||
/// <param name="page">Page number (defaults to 1)</param>
|
||||
/// <param name="pageSize">Number of items per page (defaults to 10, max 100)</param>
|
||||
/// <param name="sortBy">Field to sort by (TotalPnL, PnLLast24h, TotalROI, ROILast24h, Wins, Losses, AverageWinRate, ActiveStrategiesCount, TotalVolume, VolumeLast24h)</param>
|
||||
/// <param name="sortBy">Field to sort by (TotalPnL, TotalROI, Wins, Losses, AgentName, CreatedAt, UpdatedAt)</param>
|
||||
/// <param name="sortOrder">Sort order - "asc" or "desc" (defaults to "desc")</param>
|
||||
/// <param name="agentNames">Optional comma-separated list of agent names to filter by</param>
|
||||
/// <returns>A paginated list of agent summaries sorted by the specified field</returns>
|
||||
[HttpGet("GetAgentIndexPaginated")]
|
||||
public async Task<ActionResult<PaginatedAgentIndexResponse>> GetAgentIndexPaginated(
|
||||
string timeFilter = "Total",
|
||||
int page = 1,
|
||||
int pageSize = 10,
|
||||
string sortBy = "TotalPnL",
|
||||
SortableFields sortBy = SortableFields.TotalPnL,
|
||||
string sortOrder = "desc",
|
||||
string? agentNames = null)
|
||||
{
|
||||
// Validate time filter
|
||||
var validTimeFilters = new[] { "24H", "3D", "1W", "1M", "1Y", "Total" };
|
||||
if (!validTimeFilters.Contains(timeFilter))
|
||||
{
|
||||
timeFilter = "Total"; // Default to Total if invalid
|
||||
}
|
||||
|
||||
// Validate pagination parameters
|
||||
if (page < 1)
|
||||
{
|
||||
@@ -708,177 +589,59 @@ public class DataController : ControllerBase
|
||||
return BadRequest("Sort order must be 'asc' or 'desc'");
|
||||
}
|
||||
|
||||
// Validate sort by field
|
||||
var validSortFields = new[]
|
||||
{
|
||||
"TotalPnL", "PnLLast24h", "TotalROI", "ROILast24h", "Wins", "Losses", "AverageWinRate",
|
||||
"ActiveStrategiesCount", "TotalVolume", "VolumeLast24h"
|
||||
};
|
||||
if (!validSortFields.Contains(sortBy))
|
||||
{
|
||||
sortBy = "TotalPnL"; // Default to TotalPnL if invalid
|
||||
}
|
||||
|
||||
// Create cache key that includes agent names filter
|
||||
var agentNamesForCache = !string.IsNullOrWhiteSpace(agentNames)
|
||||
? string.Join("_", agentNames.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(name => name.Trim())
|
||||
.Where(name => !string.IsNullOrWhiteSpace(name))
|
||||
.OrderBy(name => name))
|
||||
: "all";
|
||||
string cacheKey = $"AgentIndex_{timeFilter}_{agentNamesForCache}";
|
||||
|
||||
// Check if the agent index is already cached
|
||||
var cachedIndex = _cacheService.GetValue<AgentIndexViewModel>(cacheKey);
|
||||
|
||||
List<AgentSummaryViewModel> allAgentSummaries;
|
||||
|
||||
if (cachedIndex != null)
|
||||
{
|
||||
allAgentSummaries = cachedIndex.AgentSummaries.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get all agents and their strategies
|
||||
var agentsWithStrategies = await _mediator.Send(new GetAllAgentsCommand(timeFilter));
|
||||
|
||||
allAgentSummaries = new List<AgentSummaryViewModel>();
|
||||
|
||||
// Create summaries for each agent
|
||||
foreach (var agent in agentsWithStrategies)
|
||||
{
|
||||
var user = agent.Key;
|
||||
var strategies = agent.Value;
|
||||
|
||||
if (strategies.Count == 0)
|
||||
{
|
||||
continue; // Skip agents with no strategies
|
||||
}
|
||||
|
||||
// Combine all positions from all strategies
|
||||
var allPositions = strategies.SelectMany<ITradingBot, Position>(s => s.Positions).ToList();
|
||||
|
||||
// Calculate agent metrics
|
||||
decimal totalPnL = TradingBox.GetPnLInTimeRange(allPositions, timeFilter);
|
||||
decimal pnlLast24h = TradingBox.GetPnLInTimeRange(allPositions, "24H");
|
||||
|
||||
decimal totalROI = TradingBox.GetROIInTimeRange(allPositions, timeFilter);
|
||||
decimal roiLast24h = TradingBox.GetROIInTimeRange(allPositions, "24H");
|
||||
|
||||
(int wins, int losses) = TradingBox.GetWinLossCountInTimeRange(allPositions, timeFilter);
|
||||
|
||||
// Calculate trading volumes
|
||||
decimal totalVolume = TradingBox.GetTotalVolumeTraded(allPositions);
|
||||
decimal volumeLast24h = TradingBox.GetLast24HVolumeTraded(allPositions);
|
||||
|
||||
// Calculate win rate
|
||||
int averageWinRate = 0;
|
||||
if (wins + losses > 0)
|
||||
{
|
||||
averageWinRate = (wins * 100) / (wins + losses);
|
||||
}
|
||||
|
||||
// Add to agent summaries
|
||||
var agentSummary = new AgentSummaryViewModel
|
||||
{
|
||||
AgentName = user.AgentName,
|
||||
TotalPnL = totalPnL,
|
||||
PnLLast24h = pnlLast24h,
|
||||
TotalROI = totalROI,
|
||||
ROILast24h = roiLast24h,
|
||||
Wins = wins,
|
||||
Losses = losses,
|
||||
AverageWinRate = averageWinRate,
|
||||
ActiveStrategiesCount = strategies.Count,
|
||||
TotalVolume = totalVolume,
|
||||
VolumeLast24h = volumeLast24h
|
||||
};
|
||||
|
||||
allAgentSummaries.Add(agentSummary);
|
||||
}
|
||||
|
||||
// Cache the results for 5 minutes
|
||||
var agentIndex = new AgentIndexViewModel
|
||||
{
|
||||
TimeFilter = timeFilter,
|
||||
AgentSummaries = allAgentSummaries
|
||||
};
|
||||
_cacheService.SaveValue(cacheKey, agentIndex, TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
// Apply agent name filtering if specified
|
||||
// Parse agent names filter
|
||||
IEnumerable<string>? agentNamesList = null;
|
||||
if (!string.IsNullOrWhiteSpace(agentNames))
|
||||
{
|
||||
var agentNameList = agentNames.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
agentNamesList = agentNames.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(name => name.Trim())
|
||||
.Where(name => !string.IsNullOrWhiteSpace(name))
|
||||
.ToList();
|
||||
|
||||
if (agentNameList.Any())
|
||||
{
|
||||
allAgentSummaries = allAgentSummaries
|
||||
.Where(agent => agentNameList.Contains(agent.AgentName, StringComparer.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
var sortedSummaries = sortBy switch
|
||||
// Get paginated results from database
|
||||
var command = new GetPaginatedAgentSummariesCommand(page, pageSize, sortBy, sortOrder, agentNamesList);
|
||||
var result = await _mediator.Send(command);
|
||||
var agentSummaries = result.Results;
|
||||
var totalCount = result.TotalCount;
|
||||
|
||||
// Map to view models
|
||||
var agentSummaryViewModels = new List<AgentSummaryViewModel>();
|
||||
foreach (var agentSummary in agentSummaries)
|
||||
{
|
||||
"TotalPnL" => sortOrder == "desc"
|
||||
? allAgentSummaries.OrderByDescending(a => a.TotalPnL)
|
||||
: allAgentSummaries.OrderBy(a => a.TotalPnL),
|
||||
"PnLLast24h" => sortOrder == "desc"
|
||||
? allAgentSummaries.OrderByDescending(a => a.PnLLast24h)
|
||||
: allAgentSummaries.OrderBy(a => a.PnLLast24h),
|
||||
"TotalROI" => sortOrder == "desc"
|
||||
? allAgentSummaries.OrderByDescending(a => a.TotalROI)
|
||||
: allAgentSummaries.OrderBy(a => a.TotalROI),
|
||||
"ROILast24h" => sortOrder == "desc"
|
||||
? allAgentSummaries.OrderByDescending(a => a.ROILast24h)
|
||||
: allAgentSummaries.OrderBy(a => a.ROILast24h),
|
||||
"Wins" => sortOrder == "desc"
|
||||
? allAgentSummaries.OrderByDescending(a => a.Wins)
|
||||
: allAgentSummaries.OrderBy(a => a.Wins),
|
||||
"Losses" => sortOrder == "desc"
|
||||
? allAgentSummaries.OrderByDescending(a => a.Losses)
|
||||
: allAgentSummaries.OrderBy(a => a.Losses),
|
||||
"AverageWinRate" => sortOrder == "desc"
|
||||
? allAgentSummaries.OrderByDescending(a => a.AverageWinRate)
|
||||
: allAgentSummaries.OrderBy(a => a.AverageWinRate),
|
||||
"ActiveStrategiesCount" => sortOrder == "desc"
|
||||
? allAgentSummaries.OrderByDescending(a => a.ActiveStrategiesCount)
|
||||
: allAgentSummaries.OrderBy(a => a.ActiveStrategiesCount),
|
||||
"TotalVolume" => sortOrder == "desc"
|
||||
? allAgentSummaries.OrderByDescending(a => a.TotalVolume)
|
||||
: allAgentSummaries.OrderBy(a => a.TotalVolume),
|
||||
"VolumeLast24h" => sortOrder == "desc"
|
||||
? allAgentSummaries.OrderByDescending(a => a.VolumeLast24h)
|
||||
: allAgentSummaries.OrderBy(a => a.VolumeLast24h),
|
||||
_ => sortOrder == "desc"
|
||||
? allAgentSummaries.OrderByDescending(a => a.TotalPnL)
|
||||
: allAgentSummaries.OrderBy(a => a.TotalPnL)
|
||||
};
|
||||
// Calculate win rate
|
||||
int averageWinRate = 0;
|
||||
if (agentSummary.Wins + agentSummary.Losses > 0)
|
||||
{
|
||||
averageWinRate = (agentSummary.Wins * 100) / (agentSummary.Wins + agentSummary.Losses);
|
||||
}
|
||||
|
||||
// Map to view model
|
||||
var agentSummaryViewModel = new AgentSummaryViewModel
|
||||
{
|
||||
AgentName = agentSummary.AgentName,
|
||||
TotalPnL = agentSummary.TotalPnL,
|
||||
TotalROI = agentSummary.TotalROI,
|
||||
Wins = agentSummary.Wins,
|
||||
Losses = agentSummary.Losses,
|
||||
ActiveStrategiesCount = agentSummary.ActiveStrategiesCount,
|
||||
TotalVolume = agentSummary.TotalVolume,
|
||||
};
|
||||
|
||||
agentSummaryViewModels.Add(agentSummaryViewModel);
|
||||
}
|
||||
|
||||
var totalCount = allAgentSummaries.Count;
|
||||
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
||||
|
||||
// Apply pagination
|
||||
var paginatedSummaries = sortedSummaries
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToList();
|
||||
|
||||
var response = new PaginatedAgentIndexResponse
|
||||
{
|
||||
AgentSummaries = paginatedSummaries,
|
||||
AgentSummaries = agentSummaryViewModels,
|
||||
TotalCount = totalCount,
|
||||
CurrentPage = page,
|
||||
PageSize = pageSize,
|
||||
TotalPages = totalPages,
|
||||
HasNextPage = page < totalPages,
|
||||
HasPreviousPage = page > 1,
|
||||
TimeFilter = timeFilter,
|
||||
SortBy = sortBy,
|
||||
SortOrder = sortOrder,
|
||||
FilteredAgentNames = agentNames
|
||||
@@ -970,7 +733,7 @@ public class DataController : ControllerBase
|
||||
|
||||
foreach (var indicatorRequest in scenarioRequest.Indicators)
|
||||
{
|
||||
var indicator = new Indicator(indicatorRequest.Name, indicatorRequest.Type)
|
||||
var indicator = new IndicatorBase(indicatorRequest.Name, indicatorRequest.Type)
|
||||
{
|
||||
SignalType = indicatorRequest.SignalType,
|
||||
MinimumHistory = indicatorRequest.MinimumHistory,
|
||||
|
||||
@@ -197,23 +197,23 @@ public class ScenarioController : BaseController
|
||||
};
|
||||
}
|
||||
|
||||
private static IndicatorViewModel MapToIndicatorViewModel(Indicator indicator)
|
||||
private static IndicatorViewModel MapToIndicatorViewModel(IndicatorBase indicatorBase)
|
||||
{
|
||||
return new IndicatorViewModel
|
||||
{
|
||||
Name = indicator.Name,
|
||||
Type = indicator.Type,
|
||||
SignalType = indicator.SignalType,
|
||||
MinimumHistory = indicator.MinimumHistory,
|
||||
Period = indicator.Period,
|
||||
FastPeriods = indicator.FastPeriods,
|
||||
SlowPeriods = indicator.SlowPeriods,
|
||||
SignalPeriods = indicator.SignalPeriods,
|
||||
Multiplier = indicator.Multiplier,
|
||||
SmoothPeriods = indicator.SmoothPeriods,
|
||||
StochPeriods = indicator.StochPeriods,
|
||||
CyclePeriods = indicator.CyclePeriods,
|
||||
UserName = indicator.User?.Name
|
||||
Name = indicatorBase.Name,
|
||||
Type = indicatorBase.Type,
|
||||
SignalType = indicatorBase.SignalType,
|
||||
MinimumHistory = indicatorBase.MinimumHistory,
|
||||
Period = indicatorBase.Period,
|
||||
FastPeriods = indicatorBase.FastPeriods,
|
||||
SlowPeriods = indicatorBase.SlowPeriods,
|
||||
SignalPeriods = indicatorBase.SignalPeriods,
|
||||
Multiplier = indicatorBase.Multiplier,
|
||||
SmoothPeriods = indicatorBase.SmoothPeriods,
|
||||
StochPeriods = indicatorBase.StochPeriods,
|
||||
CyclePeriods = indicatorBase.CyclePeriods,
|
||||
UserName = indicatorBase.User?.Name
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@ public class TradingController : BaseController
|
||||
/// <param name="identifier">The unique identifier of the position to close.</param>
|
||||
/// <returns>The closed position.</returns>
|
||||
[HttpPost("ClosePosition")]
|
||||
public async Task<ActionResult<Position>> ClosePosition(string identifier)
|
||||
public async Task<ActionResult<Position>> ClosePosition(Guid identifier)
|
||||
{
|
||||
var position = await _tradingService.GetPositionByIdentifierAsync(identifier);
|
||||
var result = await _closeTradeCommandHandler.Handle(new ClosePositionCommand(position));
|
||||
|
||||
@@ -26,7 +26,8 @@ public class UserController : BaseController
|
||||
/// <param name="userService">Service for user-related operations.</param>
|
||||
/// <param name="jwtUtils">Utility for JWT token operations.</param>
|
||||
/// <param name="webhookService">Service for webhook operations.</param>
|
||||
public UserController(IConfiguration config, IUserService userService, IJwtUtils jwtUtils, IWebhookService webhookService)
|
||||
public UserController(IConfiguration config, IUserService userService, IJwtUtils jwtUtils,
|
||||
IWebhookService webhookService)
|
||||
: base(userService)
|
||||
{
|
||||
_config = config;
|
||||
@@ -40,7 +41,7 @@ public class UserController : BaseController
|
||||
/// <param name="login">The login request containing user credentials.</param>
|
||||
/// <returns>A JWT token if authentication is successful; otherwise, an Unauthorized result.</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost]
|
||||
[HttpPost("create-token")]
|
||||
public async Task<ActionResult<string>> CreateToken([FromBody] LoginRequest login)
|
||||
{
|
||||
var user = await _userService.Authenticate(login.Name, login.Address, login.Message, login.Signature);
|
||||
@@ -52,16 +53,18 @@ public class UserController : BaseController
|
||||
}
|
||||
|
||||
return Unauthorized();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current user's information.
|
||||
/// </summary>
|
||||
/// <returns>The current user's information.</returns>
|
||||
[Authorize]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<User>> GetCurrentUser()
|
||||
{
|
||||
var user = await base.GetUser();
|
||||
user = await _userService.GetUserByName(user.Name);
|
||||
return Ok(user);
|
||||
}
|
||||
|
||||
@@ -70,6 +73,7 @@ public class UserController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="agentName">The new agent name to set.</param>
|
||||
/// <returns>The updated user with the new agent name.</returns>
|
||||
[Authorize]
|
||||
[HttpPut("agent-name")]
|
||||
public async Task<ActionResult<User>> UpdateAgentName([FromBody] string agentName)
|
||||
{
|
||||
@@ -83,6 +87,7 @@ public class UserController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="avatarUrl">The new avatar URL to set.</param>
|
||||
/// <returns>The updated user with the new avatar URL.</returns>
|
||||
[Authorize]
|
||||
[HttpPut("avatar")]
|
||||
public async Task<ActionResult<User>> UpdateAvatarUrl([FromBody] string avatarUrl)
|
||||
{
|
||||
@@ -96,35 +101,37 @@ public class UserController : BaseController
|
||||
/// </summary>
|
||||
/// <param name="telegramChannel">The new Telegram channel to set.</param>
|
||||
/// <returns>The updated user with the new Telegram channel.</returns>
|
||||
[Authorize]
|
||||
[HttpPut("telegram-channel")]
|
||||
public async Task<ActionResult<User>> UpdateTelegramChannel([FromBody] string telegramChannel)
|
||||
{
|
||||
var user = await GetUser();
|
||||
var updatedUser = await _userService.UpdateTelegramChannel(user, telegramChannel);
|
||||
|
||||
|
||||
// Send welcome message to the newly configured telegram channel
|
||||
if (!string.IsNullOrEmpty(telegramChannel))
|
||||
{
|
||||
try
|
||||
{
|
||||
var welcomeMessage = $"🎉 **Trading Bot - Welcome!**\n\n" +
|
||||
$"🎯 **Agent:** {user.Name}\n" +
|
||||
$"📡 **Channel ID:** {telegramChannel}\n" +
|
||||
$"⏰ **Setup Time:** {DateTime.UtcNow:MMM dd, yyyy • HH:mm:ss} UTC\n\n" +
|
||||
$"🔔 **Notification Types:**\n" +
|
||||
$"• 📈 Position Opens & Closes\n" +
|
||||
$"• 🤖 Bot configuration changes\n\n" +
|
||||
$"🚀 **Welcome aboard!** Your trading notifications are now live.";
|
||||
$"🎯 **Agent:** {user.Name}\n" +
|
||||
$"📡 **Channel ID:** {telegramChannel}\n" +
|
||||
$"⏰ **Setup Time:** {DateTime.UtcNow:MMM dd, yyyy • HH:mm:ss} UTC\n\n" +
|
||||
$"🔔 **Notification Types:**\n" +
|
||||
$"• 📈 Position Opens & Closes\n" +
|
||||
$"• 🤖 Bot configuration changes\n\n" +
|
||||
$"🚀 **Welcome aboard!** Your trading notifications are now live.";
|
||||
|
||||
await _webhookService.SendMessage(welcomeMessage, telegramChannel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log the error but don't fail the update operation
|
||||
Console.WriteLine($"Failed to send welcome message to telegram channel {telegramChannel}: {ex.Message}");
|
||||
Console.WriteLine(
|
||||
$"Failed to send welcome message to telegram channel {telegramChannel}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return Ok(updatedUser);
|
||||
}
|
||||
|
||||
@@ -132,11 +139,12 @@ public class UserController : BaseController
|
||||
/// Tests the Telegram channel configuration by sending a test message.
|
||||
/// </summary>
|
||||
/// <returns>A message indicating the test result.</returns>
|
||||
[Authorize]
|
||||
[HttpPost("telegram-channel/test")]
|
||||
public async Task<ActionResult<string>> TestTelegramChannel()
|
||||
{
|
||||
var user = await GetUser();
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(user.TelegramChannel))
|
||||
{
|
||||
return BadRequest("No Telegram channel configured for this user. Please set a Telegram channel first.");
|
||||
@@ -144,7 +152,7 @@ public class UserController : BaseController
|
||||
|
||||
try
|
||||
{
|
||||
var testMessage = $"🚀 **Trading Bot - Channel Test**\n\n" +
|
||||
var testMessage = $"🚀 **Trading Bot - Channel Test**\n\n" +
|
||||
$"🎯 **Agent:** {user.Name}\n" +
|
||||
$"📡 **Channel ID:** {user.TelegramChannel}\n" +
|
||||
$"⏰ **Test Time:** {DateTime.UtcNow:MMM dd, yyyy • HH:mm:ss} UTC\n\n" +
|
||||
@@ -154,13 +162,13 @@ public class UserController : BaseController
|
||||
$"🎉 **Ready to trade!** Your notifications are now active.";
|
||||
|
||||
await _webhookService.SendMessage(testMessage, user.TelegramChannel);
|
||||
|
||||
return Ok($"Test message sent successfully to Telegram channel {user.TelegramChannel}. Please check your Telegram to verify delivery.");
|
||||
|
||||
return Ok(
|
||||
$"Test message sent successfully to Telegram channel {user.TelegramChannel}. Please check your Telegram to verify delivery.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, $"Failed to send test message: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user