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;
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;
using Managing.Domain.Trades;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using static Managing.Common.Enums;
namespace Managing.Api.Controllers;
///
/// Controller for handling data-related operations such as retrieving tickers, spotlight data, and candles.
/// Requires authorization for access.
///
[ApiController]
[Route("[controller]")]
public class DataController : ControllerBase
{
private readonly IExchangeService _exchangeService;
private readonly IAccountService _accountService;
private readonly ICacheService _cacheService;
private readonly IStatisticService _statisticService;
private readonly IHubContext _hubContext;
private readonly IMediator _mediator;
private readonly ITradingService _tradingService;
///
/// Initializes a new instance of the class.
///
/// Service for interacting with exchanges.
/// Service for account management.
/// Service for caching data.
/// Service for statistical analysis.
/// SignalR hub context for real-time communication.
/// Mediator for handling commands and queries.
/// Service for trading operations.
public DataController(
IExchangeService exchangeService,
IAccountService accountService,
ICacheService cacheService,
IStatisticService statisticService,
IHubContext hubContext,
IMediator mediator,
ITradingService tradingService)
{
_exchangeService = exchangeService;
_accountService = accountService;
_cacheService = cacheService;
_statisticService = statisticService;
_hubContext = hubContext;
_mediator = mediator;
_tradingService = tradingService;
}
///
/// Retrieves tickers for a given account and timeframe, utilizing caching to improve performance.
///
/// The timeframe for which to retrieve tickers.
/// An array of tickers.
[HttpPost("GetTickers")]
public async Task>> GetTickers(Timeframe timeframe)
{
var cacheKey = string.Concat(timeframe.ToString());
var tickers = _cacheService.GetValue>(cacheKey);
if (tickers == null || tickers.Count == 0)
{
var availableTicker = await _exchangeService.GetTickers(timeframe);
tickers = MapTickerToTickerInfos(availableTicker);
_cacheService.SaveValue(cacheKey, tickers, TimeSpan.FromHours(2));
}
return Ok(tickers);
}
private List MapTickerToTickerInfos(List availableTicker)
{
var tickerInfos = new List();
var tokens = new Dictionary
{
{ "AAVE", "https://assets.coingecko.com/coins/images/12645/standard/AAVE.png?1696512452" },
{ "ADA", "https://assets.coingecko.com/coins/images/975/standard/cardano.png?1696502090" },
{ "APE", "https://assets.coingecko.com/coins/images/24383/standard/apecoin.jpg?1696523566" },
{
"ARB", "https://assets.coingecko.com/coins/images/16547/small/photo_2023-03-29_21.47.00.jpeg?1680097630"
},
{ "ATOM", "https://assets.coingecko.com/coins/images/1481/standard/cosmos_hub.png?1696502525" },
{ "AVAX", "https://assets.coingecko.com/coins/images/12559/small/coin-round-red.png?1604021818" },
{ "BNB", "https://assets.coingecko.com/coins/images/825/standard/bnb-icon2_2x.png?1696501970" },
{ "BTC", "https://assets.coingecko.com/coins/images/1/small/bitcoin.png?1547033579" },
{ "DOGE", "https://assets.coingecko.com/coins/images/5/small/dogecoin.png?1547792256" },
{
"DOT",
"https://static.coingecko.com/s/polkadot-73b0c058cae10a2f076a82dcade5cbe38601fad05d5e6211188f09eb96fa4617.gif"
},
{ "ETH", "https://assets.coingecko.com/coins/images/279/small/ethereum.png?1595348880" },
{ "FIL", "https://assets.coingecko.com/coins/images/12817/standard/filecoin.png?1696512609" },
{ "GMX", "https://assets.coingecko.com/coins/images/18323/small/arbit.png?1631532468" },
{ "LINK", "https://assets.coingecko.com/coins/images/877/thumb/chainlink-new-logo.png?1547034700" },
{ "LTC", "https://assets.coingecko.com/coins/images/2/small/litecoin.png?1547033580" },
{ "MATIC", "https://assets.coingecko.com/coins/images/32440/standard/polygon.png?1698233684" },
{ "NEAR", "https://assets.coingecko.com/coins/images/10365/standard/near.jpg?1696510367" },
{ "OP", "https://assets.coingecko.com/coins/images/25244/standard/Optimism.png?1696524385" },
{ "PEPE", "https://assets.coingecko.com/coins/images/29850/standard/pepe-token.jpeg?1696528776" },
{ "SOL", "https://assets.coingecko.com/coins/images/4128/small/solana.png?1640133422" },
{ "UNI", "https://assets.coingecko.com/coins/images/12504/thumb/uniswap-uni.png?1600306604" },
{ "USDC", "https://assets.coingecko.com/coins/images/6319/thumb/USD_Coin_icon.png?1547042389" },
{ "USDT", "https://assets.coingecko.com/coins/images/325/thumb/Tether-logo.png?1598003707" },
{ "WIF", "https://assets.coingecko.com/coins/images/33566/standard/dogwifhat.jpg?1702499428" },
{ "XRP", "https://assets.coingecko.com/coins/images/44/small/xrp-symbol-white-128.png?1605778731" },
{ "SHIB", "https://assets.coingecko.com/coins/images/11939/standard/shiba.png?1696511800" },
{ "STX", "https://assets.coingecko.com/coins/images/2069/standard/Stacks_Logo_png.png?1709979332" },
{ "ORDI", "https://assets.coingecko.com/coins/images/30162/standard/ordi.png?1696529082" },
{ "APT", "https://assets.coingecko.com/coins/images/26455/standard/aptos_round.png?1696525528" },
{ "BOME", "https://assets.coingecko.com/coins/images/36071/standard/bome.png?1710407255" },
{ "MEME", "https://assets.coingecko.com/coins/images/32528/standard/memecoin_%282%29.png?1698912168" },
{ "FLOKI", "https://assets.coingecko.com/coins/images/16746/standard/PNG_image.png?1696516318" },
{ "MEW", "https://assets.coingecko.com/coins/images/36440/standard/MEW.png?1711442286" },
{ "TAO", "https://assets.coingecko.com/coins/images/28452/standard/ARUsPeNQ_400x400.jpeg?1696527447" },
{ "BONK", "https://assets.coingecko.com/coins/images/28600/standard/bonk.jpg?1696527587" },
{ "WLD", "https://assets.coingecko.com/coins/images/31069/standard/worldcoin.jpeg?1696529903" },
{
"tBTC",
"https://assets.coingecko.com/coins/images/11224/standard/0x18084fba666a33d37592fa2633fd49a74dd93a88.png?1696511155"
},
{ "EIGEN", "https://assets.coingecko.com/coins/images/37441/standard/eigen.jpg?1728023974" },
{ "SUI", "https://assets.coingecko.com/coins/images/26375/standard/sui-ocean-square.png?1727791290" },
{ "SEI", "https://assets.coingecko.com/coins/images/28205/standard/Sei_Logo_-_Transparent.png?1696527207" },
{ "DAI", "https://assets.coingecko.com/coins/images/9956/thumb/4943.png?1636636734" },
{ "TIA", "https://assets.coingecko.com/coins/images/31967/standard/tia.jpg?1696530772" },
{ "TRX", "https://assets.coingecko.com/coins/images/1094/standard/tron-logo.png?1696502193" },
{
"TON",
"https://assets.coingecko.com/coins/images/17980/standard/photo_2024-09-10_17.09.00.jpeg?1725963446"
},
{
"PENDLE",
"https://assets.coingecko.com/coins/images/15069/standard/Pendle_Logo_Normal-03.png?1696514728"
},
{ "wstETH", "https://assets.coingecko.com/coins/images/18834/standard/wstETH.png?1696518295" },
{ "USDe", "https://assets.coingecko.com/coins/images/33613/standard/USDE.png?1716355685" },
{ "SATS", "https://assets.coingecko.com/coins/images/30666/standard/_dD8qr3M_400x400.png?1702913020" },
{ "POL", "https://assets.coingecko.com/coins/images/32440/standard/polygon.png?1698233684" },
{ "XLM", "https://assets.coingecko.com/coins/images/100/standard/Stellar_symbol_black_RGB.png?1696501482" },
{ "BCH", "https://assets.coingecko.com/coins/images/780/standard/bitcoin-cash-circle.png?1696501932" },
{ "ICP", "https://assets.coingecko.com/coins/images/14495/standard/Internet_Computer_logo.png?1696514180" },
{ "RENDER", "https://assets.coingecko.com/coins/images/11636/standard/rndr.png?1696511529" },
{ "INJ", "https://assets.coingecko.com/coins/images/12882/standard/Secondary_Symbol.png?1696512670" },
{ "TRUMP", "https://assets.coingecko.com/coins/images/53746/standard/trump.png?1737171561" },
{ "MELANIA", "https://assets.coingecko.com/coins/images/53775/standard/melania-meme.png?1737329885" },
{ "ENA", "https://assets.coingecko.com/coins/images/36530/standard/ethena.png?1711701436" },
{ "FARTCOIN", "https://assets.coingecko.com/coins/images/50891/standard/fart.jpg?1729503972" },
{ "AI16Z", "https://assets.coingecko.com/coins/images/51090/standard/AI16Z.jpg?1730027175" },
{ "ANIME", "https://assets.coingecko.com/coins/images/53575/standard/anime.jpg?1736748703" },
{ "BERA", "https://assets.coingecko.com/coins/images/25235/standard/BERA.png?1738822008" },
{ "VIRTUAL", "https://assets.coingecko.com/coins/images/34057/standard/LOGOMARK.png?1708356054" },
{
"PENGU",
"https://assets.coingecko.com/coins/images/52622/standard/PUDGY_PENGUINS_PENGU_PFP.png?1733809110"
},
{ "FET", "https://assets.coingecko.com/coins/images/5681/standard/ASI.png?1719827289" },
{ "ONDO", "https://assets.coingecko.com/coins/images/26580/standard/ONDO.png?1696525656" },
{ "AIXBT", "https://assets.coingecko.com/coins/images/51784/standard/3.png?1731981138" },
{
"CAKE",
"https://assets.coingecko.com/coins/images/12632/standard/pancakeswap-cake-logo_%281%29.png?1696512440"
},
{ "S", "https://assets.coingecko.com/coins/images/38108/standard/200x200_Sonic_Logo.png?1734679256" },
{ "JUP", "https://assets.coingecko.com/coins/images/34188/standard/jup.png?1704266489" },
{ "HYPE", "https://assets.coingecko.com/coins/images/50882/standard/hyperliquid.jpg?1729431300" },
{ "OM", "https://assets.coingecko.com/coins/images/12151/standard/OM_Token.png?1696511991" }
};
foreach (var ticker in availableTicker)
{
var tickerInfo = new TickerInfos
{
Ticker = ticker,
ImageUrl = tokens.GetValueOrDefault(ticker.ToString(),
"https://assets.coingecko.com/coins/images/1/small/bitcoin.png?1547033579") // Default to BTC image if not found
};
tickerInfos.Add(tickerInfo);
}
return tickerInfos;
}
///
/// Retrieves the latest spotlight overview, using caching to enhance response times.
///
/// A object containing spotlight data.
[Authorize]
[HttpGet("Spotlight")]
public async Task> GetSpotlight()
{
var cacheKey = $"Spotlight_{DateTime.Now.AddDays(-2).ToString("yyyy-MM-dd")}";
var overview = _cacheService.GetValue(cacheKey);
if (overview == null)
{
overview = await _statisticService.GetLastSpotlight(DateTime.Now.AddDays(-2));
_cacheService.SaveValue(cacheKey, overview, TimeSpan.FromMinutes(2));
}
return Ok(overview);
}
///
/// Retrieves candles with indicators values for backtest details display.
///
/// The trading exchange.
/// The ticker symbol.
/// The start date for the candles.
/// The end date for the candles.
/// The timeframe for the candles.
/// The scenario object to calculate indicators values (optional).
/// A response containing candles and indicators values.
[Authorize]
[HttpPost("GetCandlesWithIndicators")]
public async Task> GetCandlesWithIndicators(
[FromBody] GetCandlesWithIndicatorsRequest request)
{
try
{
// Get candles for the specified period
var candles = await _exchangeService.GetCandlesInflux(TradingExchanges.Evm, request.Ticker,
request.StartDate, request.Timeframe, request.EndDate);
if (candles == null || candles.Count == 0)
{
return Ok(new CandlesWithIndicatorsResponse
{
Candles = new List(),
IndicatorsValues = new Dictionary()
});
}
// Calculate indicators values if scenario is provided
Dictionary indicatorsValues = null;
if (request.Scenario != null && request.Scenario.Indicators != null &&
request.Scenario.Indicators.Count > 0)
{
// Map ScenarioRequest to domain Scenario object
var domainScenario = MapScenarioRequestToScenario(request.Scenario);
indicatorsValues = _tradingService.CalculateIndicatorsValuesAsync(domainScenario, candles);
}
return Ok(new CandlesWithIndicatorsResponse
{
Candles = candles,
IndicatorsValues = indicatorsValues ?? new Dictionary()
});
}
catch (Exception ex)
{
return StatusCode(500, $"Error retrieving candles with indicators: {ex.Message}");
}
}
///
/// Retrieves statistics about currently running bots and their change in the last 24 hours.
///
/// A containing bot statistics.
[HttpGet("GetStrategiesStatistics")]
public async Task> GetStrategiesStatistics()
{
const string cacheKey = "StrategiesStatistics";
const string previousCountKey = "PreviousBotsCount";
// Check if the statistics are already cached
var cachedStats = _cacheService.GetValue(cacheKey);
if (cachedStats != null)
{
return Ok(cachedStats);
}
// Get active bots
var activeBots = await _mediator.Send(new GetActiveBotsCommand());
var currentCount = activeBots.Count;
// Get previous count from cache
var previousCount = _cacheService.GetValue(previousCountKey);
// Calculate change - if no previous value, set current count as the change (all are new)
int change;
if (previousCount == 0)
{
// First time running - assume all bots are new (positive change)
change = currentCount;
}
else
{
// Calculate actual difference between current and previous count
change = currentCount - previousCount;
}
// Create the response
var botsStatistics = new StrategiesStatisticsViewModel
{
TotalStrategiesRunning = currentCount,
ChangeInLast24Hours = change
};
// Store current count for future comparison (with 24 hour expiration)
_cacheService.SaveValue(previousCountKey, currentCount, TimeSpan.FromHours(24));
// Cache the statistics for 5 minutes
_cacheService.SaveValue(cacheKey, botsStatistics, TimeSpan.FromMinutes(5));
return Ok(botsStatistics);
}
///
/// Retrieves the top 3 performing strategies based on ROI.
///
/// A containing the top performing strategies.
[HttpGet("GetTopStrategies")]
public async Task> GetTopStrategies()
{
const string cacheKey = "TopStrategies";
// Check if the top strategies are already cached
var cachedStrategies = _cacheService.GetValue(cacheKey);
if (cachedStrategies != null)
{
return Ok(cachedStrategies);
}
// Get active bots
var activeBots = await _mediator.Send(new GetActiveBotsCommand());
// Calculate PnL for each bot once and store in a list of tuples
var botsWithPnL = activeBots
.Select(bot => new { Bot = bot, PnL = bot.GetProfitAndLoss() })
.OrderByDescending(item => item.PnL)
.Take(3)
.ToList();
// Map to view model
var topStrategies = new TopStrategiesViewModel
{
TopStrategies = botsWithPnL
.Select(item => new StrategyPerformance
{
StrategyName = item.Bot.Name,
PnL = item.PnL
})
.ToList()
};
// Cache the result for 10 minutes
_cacheService.SaveValue(cacheKey, topStrategies, TimeSpan.FromMinutes(10));
return Ok(topStrategies);
}
///
/// Retrieves list of the active strategies for a user with detailed information
///
/// The agentName to retrieve strategies for
/// A list of detailed strategy information
[HttpGet("GetUserStrategies")]
public async Task>> GetUserStrategies(string agentName)
{
string cacheKey = $"UserStrategies_{agentName}";
// Check if the user strategy details are already cached
var cachedDetails = _cacheService.GetValue>(cacheKey);
if (cachedDetails != null && cachedDetails.Count > 0)
{
return Ok(cachedDetails);
}
// Get all strategies for the specified user
var userStrategies = await _mediator.Send(new GetUserStrategiesCommand(agentName));
// Convert to detailed view model with additional information
var result = userStrategies.Select(strategy => MapStrategyToViewModel(strategy)).ToList();
// Cache the results for 5 minutes
_cacheService.SaveValue(cacheKey, result, TimeSpan.FromMinutes(5));
return Ok(result);
}
///
/// Retrieves a specific strategy for a user by strategy name
///
/// The agent/user name to retrieve the strategy for
/// The name of the strategy to retrieve
/// Detailed information about the requested strategy
[HttpGet("GetUserStrategy")]
public async Task> GetUserStrategy(string agentName, string strategyName)
{
string cacheKey = $"UserStrategy_{agentName}_{strategyName}";
// Check if the user strategy details are already cached
var cachedDetails = _cacheService.GetValue(cacheKey);
if (cachedDetails != null)
{
return Ok(cachedDetails);
}
// Get the specific strategy for the user
var strategy = await _mediator.Send(new GetUserStrategyCommand(agentName, strategyName));
if (strategy == null)
{
return NotFound($"Strategy '{strategyName}' not found for user '{agentName}'");
}
// Map the strategy to a view model using the shared method
var result = MapStrategyToViewModel(strategy);
// Cache the results for 5 minutes
_cacheService.SaveValue(cacheKey, result, TimeSpan.FromMinutes(5));
return Ok(result);
}
///
/// Maps a trading bot to a strategy view model with detailed statistics
///
/// The trading bot to map
/// A view model with detailed strategy information
private UserStrategyDetailsViewModel MapStrategyToViewModel(ITradingBot 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();
// 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);
// Calculate win/loss statistics
(int wins, int losses) = TradingBox.GetWinLossCount(strategy.Positions);
// Calculate ROI for last 24h
decimal roiLast24h = TradingBox.GetLast24HROI(strategy.Positions);
return new UserStrategyDetailsViewModel
{
Name = strategy.Name,
ScenarioName = strategy.Config.ScenarioName,
State = strategy.GetStatus() == BotStatus.Up.ToString() ? "RUNNING" :
strategy.GetStatus() == BotStatus.Down.ToString() ? "STOPPED" : "UNUSED",
PnL = pnl,
ROIPercentage = roi,
ROILast24H = roiLast24h,
Runtime = startupTime,
WinRate = strategy.GetWinRate(),
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,
};
}
///
/// Retrieves a summary of platform activity across all agents (platform-level data only)
///
/// Time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)
/// A summary of platform activity without individual agent details
[HttpGet("GetPlatformSummary")]
public async Task> GetPlatformSummary(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 = $"PlatformSummary_{timeFilter}";
// Check if the platform summary is already cached
var cachedSummary = _cacheService.GetValue(cacheKey);
if (cachedSummary != null)
{
return Ok(cachedSummary);
}
// Get all agents and their strategies
var agentsWithStrategies = await _mediator.Send(new GetAllAgentsCommand(timeFilter));
// Create the platform summary
var summary = new PlatformSummaryViewModel
{
TotalAgents = agentsWithStrategies.Count,
TotalActiveStrategies = agentsWithStrategies.Values.Sum(list => list.Count),
TimeFilter = timeFilter
};
// Calculate total platform metrics
decimal totalPlatformPnL = 0;
decimal totalPlatformVolume = 0;
decimal totalPlatformVolumeLast24h = 0;
// Calculate totals from all agents
foreach (var agent in agentsWithStrategies)
{
var strategies = agent.Value;
if (strategies.Count == 0)
{
continue; // Skip agents with no strategies
}
// Combine all positions from all strategies
var allPositions = strategies.SelectMany(s => s.Positions).ToList();
// 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;
}
// Set the platform totals
summary.TotalPlatformPnL = totalPlatformPnL;
summary.TotalPlatformVolume = totalPlatformVolume;
summary.TotalPlatformVolumeLast24h = totalPlatformVolumeLast24h;
// Cache the results for 5 minutes
_cacheService.SaveValue(cacheKey, summary, TimeSpan.FromMinutes(5));
return Ok(summary);
}
///
/// Retrieves a list of agent summaries for the agent index page
///
/// Time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)
/// A list of agent summaries sorted by performance
[HttpGet("GetAgentIndex")]
public async Task> 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(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(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);
}
///
/// Retrieves a paginated list of agent summaries for the agent index page
///
/// Time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)
/// Page number (defaults to 1)
/// Number of items per page (defaults to 10, max 100)
/// Field to sort by (TotalPnL, PnLLast24h, TotalROI, ROILast24h, Wins, Losses, AverageWinRate, ActiveStrategiesCount, TotalVolume, VolumeLast24h)
/// Sort order - "asc" or "desc" (defaults to "desc")
/// A paginated list of agent summaries sorted by the specified field
[HttpGet("GetAgentIndexPaginated")]
public async Task> GetAgentIndexPaginated(
string timeFilter = "Total",
int page = 1,
int pageSize = 10,
string sortBy = "TotalPnL",
string sortOrder = "desc")
{
// 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)
{
return BadRequest("Page must be greater than 0");
}
if (pageSize < 1 || pageSize > 100)
{
return BadRequest("Page size must be between 1 and 100");
}
// Validate sort order
if (sortOrder != "asc" && sortOrder != "desc")
{
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
}
string cacheKey = $"AgentIndex_{timeFilter}";
// Check if the agent index is already cached
var cachedIndex = _cacheService.GetValue(cacheKey);
List 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();
// 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(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 sorting
var sortedSummaries = sortBy switch
{
"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)
};
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,
TotalCount = totalCount,
CurrentPage = page,
PageSize = pageSize,
TotalPages = totalPages,
HasNextPage = page < totalPages,
HasPreviousPage = page > 1,
TimeFilter = timeFilter,
SortBy = sortBy,
SortOrder = sortOrder
};
return Ok(response);
}
///
/// Retrieves balance history for a specific agent within a date range
///
/// The name of the agent to retrieve balances for
/// The start date for the balance history
/// Optional end date for the balance history (defaults to current time)
/// A list of agent balances within the specified date range
[HttpGet("GetAgentBalances")]
public async Task> GetAgentBalances(
string agentName,
DateTime startDate,
DateTime? endDate = null)
{
var balances = await _statisticService.GetAgentBalances(agentName, startDate, endDate);
return Ok(balances);
}
///
/// Retrieves a paginated list of the best performing agents based on their total value
///
/// The start date for calculating agent performance
/// Optional end date for calculating agent performance (defaults to current time)
/// Page number (defaults to 1)
/// Number of items per page (defaults to 10)
/// A paginated list of agent balances and total count
[HttpGet("GetBestAgents")]
public async Task> GetBestAgents(
DateTime startDate,
DateTime? endDate = null,
int page = 1,
int pageSize = 10)
{
var (agents, totalCount) = await _statisticService.GetBestAgents(startDate, endDate, page, pageSize);
var response = new BestAgentsResponse
{
Agents = agents,
TotalCount = totalCount,
CurrentPage = page,
PageSize = pageSize,
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
};
return Ok(response);
}
///
/// Retrieves an array of agent names and their statuses
///
/// An array of agent status information
[HttpGet("GetAgentStatuses")]
public async Task>> GetAgentStatuses()
{
const string cacheKey = "AgentStatuses";
// Check if the agent statuses are already cached
var cachedStatuses = _cacheService.GetValue>(cacheKey);
if (cachedStatuses != null)
{
return Ok(cachedStatuses);
}
// Get all agent statuses
var agentStatuses = await _mediator.Send(new GetAgentStatusesCommand());
// Cache the results for 2 minutes
_cacheService.SaveValue(cacheKey, agentStatuses, TimeSpan.FromMinutes(2));
return Ok(agentStatuses);
}
///
/// Maps a ScenarioRequest to a domain Scenario object.
///
/// The scenario request to map.
/// A domain Scenario object.
private Scenario MapScenarioRequestToScenario(ScenarioRequest scenarioRequest)
{
var scenario = new Scenario(scenarioRequest.Name, scenarioRequest.LoopbackPeriod);
foreach (var indicatorRequest in scenarioRequest.Indicators)
{
var indicator = new Indicator(indicatorRequest.Name, indicatorRequest.Type)
{
SignalType = indicatorRequest.SignalType,
MinimumHistory = indicatorRequest.MinimumHistory,
Period = indicatorRequest.Period,
FastPeriods = indicatorRequest.FastPeriods,
SlowPeriods = indicatorRequest.SlowPeriods,
SignalPeriods = indicatorRequest.SignalPeriods,
Multiplier = indicatorRequest.Multiplier,
SmoothPeriods = indicatorRequest.SmoothPeriods,
StochPeriods = indicatorRequest.StochPeriods,
CyclePeriods = indicatorRequest.CyclePeriods
};
scenario.AddIndicator(indicator);
}
return scenario;
}
}