Add agent index with pagination
This commit is contained in:
@@ -664,6 +664,198 @@ public class DataController : ControllerBase
|
||||
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="sortOrder">Sort order - "asc" or "desc" (defaults to "desc")</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",
|
||||
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<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 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves balance history for a specific agent within a date range
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
namespace Managing.Api.Models.Responses;
|
||||
|
||||
/// <summary>
|
||||
/// Response model for paginated agent index results
|
||||
/// </summary>
|
||||
public class PaginatedAgentIndexResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of agent summaries for the current page
|
||||
/// </summary>
|
||||
public IEnumerable<AgentSummaryViewModel> AgentSummaries { get; set; } = new List<AgentSummaryViewModel>();
|
||||
|
||||
/// <summary>
|
||||
/// Total number of agents across all pages
|
||||
/// </summary>
|
||||
public int TotalCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current page number
|
||||
/// </summary>
|
||||
public int CurrentPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of items per page
|
||||
/// </summary>
|
||||
public int PageSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total number of pages
|
||||
/// </summary>
|
||||
public int TotalPages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether there are more pages available
|
||||
/// </summary>
|
||||
public bool HasNextPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether there are previous pages available
|
||||
/// </summary>
|
||||
public bool HasPreviousPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time filter applied to the data
|
||||
/// </summary>
|
||||
public string TimeFilter { get; set; } = "Total";
|
||||
|
||||
/// <summary>
|
||||
/// Field used for sorting
|
||||
/// </summary>
|
||||
public string SortBy { get; set; } = "TotalPnL";
|
||||
|
||||
/// <summary>
|
||||
/// Sort order (asc or desc)
|
||||
/// </summary>
|
||||
public string SortOrder { get; set; } = "desc";
|
||||
}
|
||||
@@ -677,7 +677,7 @@ export class BacktestClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<PaginatedBacktestsResponse>(null as any);
|
||||
}
|
||||
|
||||
backtest_Run(request: RunBacktestRequest): Promise<Backtest> {
|
||||
backtest_Run(request: RunBacktestRequest): Promise<LightBacktest> {
|
||||
let url_ = this.baseUrl + "/Backtest/Run";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
@@ -699,13 +699,13 @@ export class BacktestClient extends AuthorizedApiBase {
|
||||
});
|
||||
}
|
||||
|
||||
protected processBacktest_Run(response: Response): Promise<Backtest> {
|
||||
protected processBacktest_Run(response: Response): Promise<LightBacktest> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Backtest;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as LightBacktest;
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
@@ -713,7 +713,7 @@ export class BacktestClient extends AuthorizedApiBase {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<Backtest>(null as any);
|
||||
return Promise.resolve<LightBacktest>(null as any);
|
||||
}
|
||||
|
||||
backtest_RunBundle(request: RunBundleBacktestRequest): Promise<BundleBacktestRequest> {
|
||||
@@ -1882,6 +1882,92 @@ export class DataClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<PlatformSummaryViewModel>(null as any);
|
||||
}
|
||||
|
||||
data_GetAgentIndex(timeFilter: string | null | undefined): Promise<AgentIndexViewModel> {
|
||||
let url_ = this.baseUrl + "/Data/GetAgentIndex?";
|
||||
if (timeFilter !== undefined && timeFilter !== null)
|
||||
url_ += "timeFilter=" + encodeURIComponent("" + timeFilter) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processData_GetAgentIndex(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processData_GetAgentIndex(response: Response): Promise<AgentIndexViewModel> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as AgentIndexViewModel;
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<AgentIndexViewModel>(null as any);
|
||||
}
|
||||
|
||||
data_GetAgentIndexPaginated(timeFilter: string | null | undefined, page: number | undefined, pageSize: number | undefined, sortBy: string | null | undefined, sortOrder: string | null | undefined): Promise<PaginatedAgentIndexResponse> {
|
||||
let url_ = this.baseUrl + "/Data/GetAgentIndexPaginated?";
|
||||
if (timeFilter !== undefined && timeFilter !== null)
|
||||
url_ += "timeFilter=" + encodeURIComponent("" + timeFilter) + "&";
|
||||
if (page === null)
|
||||
throw new Error("The parameter 'page' cannot be null.");
|
||||
else if (page !== undefined)
|
||||
url_ += "page=" + encodeURIComponent("" + page) + "&";
|
||||
if (pageSize === null)
|
||||
throw new Error("The parameter 'pageSize' cannot be null.");
|
||||
else if (pageSize !== undefined)
|
||||
url_ += "pageSize=" + encodeURIComponent("" + pageSize) + "&";
|
||||
if (sortBy !== undefined && sortBy !== null)
|
||||
url_ += "sortBy=" + encodeURIComponent("" + sortBy) + "&";
|
||||
if (sortOrder !== undefined && sortOrder !== null)
|
||||
url_ += "sortOrder=" + encodeURIComponent("" + sortOrder) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processData_GetAgentIndexPaginated(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processData_GetAgentIndexPaginated(response: Response): Promise<PaginatedAgentIndexResponse> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as PaginatedAgentIndexResponse;
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<PaginatedAgentIndexResponse>(null as any);
|
||||
}
|
||||
|
||||
data_GetAgentBalances(agentName: string | null | undefined, startDate: Date | undefined, endDate: Date | null | undefined): Promise<AgentBalanceHistory> {
|
||||
let url_ = this.baseUrl + "/Data/GetAgentBalances?";
|
||||
if (agentName !== undefined && agentName !== null)
|
||||
@@ -2776,45 +2862,6 @@ export class TradingClient extends AuthorizedApiBase {
|
||||
this.baseUrl = baseUrl ?? "http://localhost:5000";
|
||||
}
|
||||
|
||||
trading_GetPositions(positionInitiator: PositionInitiator | undefined): Promise<Position[]> {
|
||||
let url_ = this.baseUrl + "/Trading/GetPositions?";
|
||||
if (positionInitiator === null)
|
||||
throw new Error("The parameter 'positionInitiator' cannot be null.");
|
||||
else if (positionInitiator !== undefined)
|
||||
url_ += "positionInitiator=" + encodeURIComponent("" + positionInitiator) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processTrading_GetPositions(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processTrading_GetPositions(response: Response): Promise<Position[]> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Position[];
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<Position[]>(null as any);
|
||||
}
|
||||
|
||||
trading_GetTrade(accountName: string | null | undefined, ticker: Ticker | undefined, exchangeOrderId: string | null | undefined): Promise<Trade> {
|
||||
let url_ = this.baseUrl + "/Trading/GetTrade?";
|
||||
if (accountName !== undefined && accountName !== null)
|
||||
@@ -3670,7 +3717,7 @@ export interface Backtest {
|
||||
|
||||
export interface TradingBotConfig {
|
||||
accountName: string;
|
||||
moneyManagement: MoneyManagement;
|
||||
moneyManagement: LightMoneyManagement;
|
||||
ticker: Ticker;
|
||||
timeframe: Timeframe;
|
||||
isForWatchingOnly: boolean;
|
||||
@@ -3681,7 +3728,7 @@ export interface TradingBotConfig {
|
||||
flipPosition: boolean;
|
||||
name: string;
|
||||
riskManagement?: RiskManagement | null;
|
||||
scenario?: Scenario | null;
|
||||
scenario?: LightScenario | null;
|
||||
scenarioName?: string | null;
|
||||
maxPositionTimeHours?: number | null;
|
||||
closeEarlyWhenProfitable?: boolean;
|
||||
@@ -3692,13 +3739,12 @@ export interface TradingBotConfig {
|
||||
useForDynamicStopLoss?: boolean;
|
||||
}
|
||||
|
||||
export interface MoneyManagement {
|
||||
export interface LightMoneyManagement {
|
||||
name: string;
|
||||
timeframe: Timeframe;
|
||||
stopLoss: number;
|
||||
takeProfit: number;
|
||||
leverage: number;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export enum Timeframe {
|
||||
@@ -3734,14 +3780,13 @@ export enum RiskToleranceLevel {
|
||||
Aggressive = "Aggressive",
|
||||
}
|
||||
|
||||
export interface Scenario {
|
||||
export interface LightScenario {
|
||||
name?: string | null;
|
||||
indicators?: Indicator[] | null;
|
||||
indicators?: LightIndicator[] | null;
|
||||
loopbackPeriod?: number | null;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface Indicator {
|
||||
export interface LightIndicator {
|
||||
name?: string | null;
|
||||
type?: IndicatorType;
|
||||
signalType?: SignalType;
|
||||
@@ -3754,7 +3799,6 @@ export interface Indicator {
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export enum IndicatorType {
|
||||
@@ -3786,7 +3830,7 @@ export interface Position {
|
||||
date: Date;
|
||||
originDirection: TradeDirection;
|
||||
ticker: Ticker;
|
||||
moneyManagement: MoneyManagement;
|
||||
moneyManagement: LightMoneyManagement;
|
||||
Open: Trade;
|
||||
StopLoss: Trade;
|
||||
TakeProfit1: Trade;
|
||||
@@ -4047,6 +4091,22 @@ export interface LightBacktestResponse {
|
||||
scoreMessage: string;
|
||||
}
|
||||
|
||||
export interface LightBacktest {
|
||||
id?: string | null;
|
||||
config?: TradingBotConfig | null;
|
||||
finalPnl?: number;
|
||||
winRate?: number;
|
||||
growthPercentage?: number;
|
||||
hodlPercentage?: number;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
maxDrawdown?: number | null;
|
||||
fees?: number;
|
||||
sharpeRatio?: number | null;
|
||||
score?: number;
|
||||
scoreMessage?: string | null;
|
||||
}
|
||||
|
||||
export interface RunBacktestRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
startDate?: Date;
|
||||
@@ -4227,6 +4287,10 @@ export interface RunGeneticRequest {
|
||||
eligibleIndicators?: IndicatorType[] | null;
|
||||
}
|
||||
|
||||
export interface MoneyManagement extends LightMoneyManagement {
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface StartBotRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
}
|
||||
@@ -4285,6 +4349,39 @@ export interface Spotlight {
|
||||
tickerSignals: TickerSignal[];
|
||||
}
|
||||
|
||||
export interface Scenario {
|
||||
name?: string | null;
|
||||
indicators?: Indicator[] | null;
|
||||
loopbackPeriod?: number | null;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface Indicator {
|
||||
name?: string | null;
|
||||
candles?: FixedSizeQueueOfCandle | null;
|
||||
type?: IndicatorType;
|
||||
signalType?: SignalType;
|
||||
minimumHistory?: number;
|
||||
period?: number | null;
|
||||
fastPeriods?: number | null;
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface Anonymous {
|
||||
maxSize?: number;
|
||||
}
|
||||
|
||||
export interface FixedSizeQueueOfCandle extends Anonymous {
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface TickerSignal {
|
||||
ticker: Ticker;
|
||||
fiveMinutes: LightSignal[];
|
||||
@@ -4344,6 +4441,10 @@ export interface PlatformSummaryViewModel {
|
||||
totalPlatformPnL?: number;
|
||||
totalPlatformVolume?: number;
|
||||
totalPlatformVolumeLast24h?: number;
|
||||
timeFilter?: string | null;
|
||||
}
|
||||
|
||||
export interface AgentIndexViewModel {
|
||||
agentSummaries?: AgentSummaryViewModel[] | null;
|
||||
timeFilter?: string | null;
|
||||
}
|
||||
@@ -4362,6 +4463,19 @@ export interface AgentSummaryViewModel {
|
||||
volumeLast24h?: number;
|
||||
}
|
||||
|
||||
export interface PaginatedAgentIndexResponse {
|
||||
agentSummaries?: AgentSummaryViewModel[] | null;
|
||||
totalCount?: number;
|
||||
currentPage?: number;
|
||||
pageSize?: number;
|
||||
totalPages?: number;
|
||||
hasNextPage?: boolean;
|
||||
hasPreviousPage?: boolean;
|
||||
timeFilter?: string | null;
|
||||
sortBy?: string | null;
|
||||
sortOrder?: string | null;
|
||||
}
|
||||
|
||||
export interface AgentBalanceHistory {
|
||||
agentName?: string | null;
|
||||
agentBalances?: AgentBalance[] | null;
|
||||
|
||||
@@ -242,7 +242,7 @@ export interface Backtest {
|
||||
|
||||
export interface TradingBotConfig {
|
||||
accountName: string;
|
||||
moneyManagement: MoneyManagement;
|
||||
moneyManagement: LightMoneyManagement;
|
||||
ticker: Ticker;
|
||||
timeframe: Timeframe;
|
||||
isForWatchingOnly: boolean;
|
||||
@@ -253,7 +253,7 @@ export interface TradingBotConfig {
|
||||
flipPosition: boolean;
|
||||
name: string;
|
||||
riskManagement?: RiskManagement | null;
|
||||
scenario?: Scenario | null;
|
||||
scenario?: LightScenario | null;
|
||||
scenarioName?: string | null;
|
||||
maxPositionTimeHours?: number | null;
|
||||
closeEarlyWhenProfitable?: boolean;
|
||||
@@ -264,13 +264,12 @@ export interface TradingBotConfig {
|
||||
useForDynamicStopLoss?: boolean;
|
||||
}
|
||||
|
||||
export interface MoneyManagement {
|
||||
export interface LightMoneyManagement {
|
||||
name: string;
|
||||
timeframe: Timeframe;
|
||||
stopLoss: number;
|
||||
takeProfit: number;
|
||||
leverage: number;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export enum Timeframe {
|
||||
@@ -306,14 +305,13 @@ export enum RiskToleranceLevel {
|
||||
Aggressive = "Aggressive",
|
||||
}
|
||||
|
||||
export interface Scenario {
|
||||
export interface LightScenario {
|
||||
name?: string | null;
|
||||
indicators?: Indicator[] | null;
|
||||
indicators?: LightIndicator[] | null;
|
||||
loopbackPeriod?: number | null;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface Indicator {
|
||||
export interface LightIndicator {
|
||||
name?: string | null;
|
||||
type?: IndicatorType;
|
||||
signalType?: SignalType;
|
||||
@@ -326,7 +324,6 @@ export interface Indicator {
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export enum IndicatorType {
|
||||
@@ -358,7 +355,7 @@ export interface Position {
|
||||
date: Date;
|
||||
originDirection: TradeDirection;
|
||||
ticker: Ticker;
|
||||
moneyManagement: MoneyManagement;
|
||||
moneyManagement: LightMoneyManagement;
|
||||
Open: Trade;
|
||||
StopLoss: Trade;
|
||||
TakeProfit1: Trade;
|
||||
@@ -619,6 +616,22 @@ export interface LightBacktestResponse {
|
||||
scoreMessage: string;
|
||||
}
|
||||
|
||||
export interface LightBacktest {
|
||||
id?: string | null;
|
||||
config?: TradingBotConfig | null;
|
||||
finalPnl?: number;
|
||||
winRate?: number;
|
||||
growthPercentage?: number;
|
||||
hodlPercentage?: number;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
maxDrawdown?: number | null;
|
||||
fees?: number;
|
||||
sharpeRatio?: number | null;
|
||||
score?: number;
|
||||
scoreMessage?: string | null;
|
||||
}
|
||||
|
||||
export interface RunBacktestRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
startDate?: Date;
|
||||
@@ -799,6 +812,10 @@ export interface RunGeneticRequest {
|
||||
eligibleIndicators?: IndicatorType[] | null;
|
||||
}
|
||||
|
||||
export interface MoneyManagement extends LightMoneyManagement {
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface StartBotRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
}
|
||||
@@ -857,6 +874,39 @@ export interface Spotlight {
|
||||
tickerSignals: TickerSignal[];
|
||||
}
|
||||
|
||||
export interface Scenario {
|
||||
name?: string | null;
|
||||
indicators?: Indicator[] | null;
|
||||
loopbackPeriod?: number | null;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface Indicator {
|
||||
name?: string | null;
|
||||
candles?: FixedSizeQueueOfCandle | null;
|
||||
type?: IndicatorType;
|
||||
signalType?: SignalType;
|
||||
minimumHistory?: number;
|
||||
period?: number | null;
|
||||
fastPeriods?: number | null;
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface Anonymous {
|
||||
maxSize?: number;
|
||||
}
|
||||
|
||||
export interface FixedSizeQueueOfCandle extends Anonymous {
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface TickerSignal {
|
||||
ticker: Ticker;
|
||||
fiveMinutes: LightSignal[];
|
||||
@@ -916,6 +966,10 @@ export interface PlatformSummaryViewModel {
|
||||
totalPlatformPnL?: number;
|
||||
totalPlatformVolume?: number;
|
||||
totalPlatformVolumeLast24h?: number;
|
||||
timeFilter?: string | null;
|
||||
}
|
||||
|
||||
export interface AgentIndexViewModel {
|
||||
agentSummaries?: AgentSummaryViewModel[] | null;
|
||||
timeFilter?: string | null;
|
||||
}
|
||||
@@ -934,6 +988,19 @@ export interface AgentSummaryViewModel {
|
||||
volumeLast24h?: number;
|
||||
}
|
||||
|
||||
export interface PaginatedAgentIndexResponse {
|
||||
agentSummaries?: AgentSummaryViewModel[] | null;
|
||||
totalCount?: number;
|
||||
currentPage?: number;
|
||||
pageSize?: number;
|
||||
totalPages?: number;
|
||||
hasNextPage?: boolean;
|
||||
hasPreviousPage?: boolean;
|
||||
timeFilter?: string | null;
|
||||
sortBy?: string | null;
|
||||
sortOrder?: string | null;
|
||||
}
|
||||
|
||||
export interface AgentBalanceHistory {
|
||||
agentName?: string | null;
|
||||
agentBalances?: AgentBalance[] | null;
|
||||
|
||||
332
src/Managing.WebApp/src/pages/dashboardPage/agentIndex.tsx
Normal file
332
src/Managing.WebApp/src/pages/dashboardPage/agentIndex.tsx
Normal file
@@ -0,0 +1,332 @@
|
||||
import React, {useEffect, useMemo, useState} from 'react'
|
||||
import {GridTile} from '../../components/mollecules'
|
||||
import Table from '../../components/mollecules/Table/Table'
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import {type AgentSummaryViewModel, DataClient, type PaginatedAgentIndexResponse} from '../../generated/ManagingApi'
|
||||
|
||||
const TIME_FILTERS = [
|
||||
{ label: '24H', value: '24H' },
|
||||
{ label: '3D', value: '3D' },
|
||||
{ label: '1W', value: '1W' },
|
||||
{ label: '1M', value: '1M' },
|
||||
{ label: '1Y', value: '1Y' },
|
||||
{ label: 'Total', value: 'Total' },
|
||||
]
|
||||
|
||||
const SORT_OPTIONS = [
|
||||
{ label: 'Total PnL', value: 'TotalPnL' },
|
||||
{ label: '24H PnL', value: 'PnLLast24h' },
|
||||
{ label: 'Total ROI', value: 'TotalROI' },
|
||||
{ label: '24H ROI', value: 'ROILast24h' },
|
||||
{ label: 'Wins', value: 'Wins' },
|
||||
{ label: 'Losses', value: 'Losses' },
|
||||
{ label: 'Win Rate', value: 'AverageWinRate' },
|
||||
{ label: 'Active Strategies', value: 'ActiveStrategiesCount' },
|
||||
{ label: 'Total Volume', value: 'TotalVolume' },
|
||||
{ label: '24H Volume', value: 'VolumeLast24h' },
|
||||
]
|
||||
|
||||
function AgentIndex({ index }: { index: number }) {
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const [data, setData] = useState<PaginatedAgentIndexResponse | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
// Pagination state
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(10)
|
||||
|
||||
// Filter and sort state
|
||||
const [timeFilter, setTimeFilter] = useState('Total')
|
||||
const [sortBy, setSortBy] = useState('TotalPnL')
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc')
|
||||
|
||||
const fetchData = async () => {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
const client = new DataClient({}, apiUrl)
|
||||
const response = await client.data_GetAgentIndexPaginated(
|
||||
timeFilter,
|
||||
currentPage,
|
||||
pageSize,
|
||||
sortBy,
|
||||
sortOrder
|
||||
)
|
||||
setData(response)
|
||||
} catch (err) {
|
||||
setError('Failed to fetch agent data. Please try again.')
|
||||
console.error('Error fetching agent data:', err)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
}, [currentPage, pageSize, timeFilter, sortBy, sortOrder])
|
||||
|
||||
const handleSort = (columnId: string) => {
|
||||
if (sortBy === columnId) {
|
||||
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
|
||||
} else {
|
||||
setSortBy(columnId)
|
||||
setSortOrder('desc')
|
||||
}
|
||||
}
|
||||
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
Header: 'Agent Name',
|
||||
accessor: 'agentName',
|
||||
Cell: ({ value }: { value: string }) => (
|
||||
<span className="font-medium">{value || 'Unknown'}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'Total PnL',
|
||||
accessor: 'totalPnL',
|
||||
Cell: ({ value }: { value: number }) => (
|
||||
<span className={value >= 0 ? 'text-green-500' : 'text-red-500'}>
|
||||
{value >= 0 ? '+' : ''}${value.toLocaleString(undefined, { maximumFractionDigits: 2 })}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: '24H PnL',
|
||||
accessor: 'pnLLast24h',
|
||||
Cell: ({ value }: { value: number }) => (
|
||||
<span className={value >= 0 ? 'text-green-500' : 'text-red-500'}>
|
||||
{value >= 0 ? '+' : ''}${value.toLocaleString(undefined, { maximumFractionDigits: 2 })}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'Total ROI',
|
||||
accessor: 'totalROI',
|
||||
Cell: ({ value }: { value: number }) => (
|
||||
<span className={value >= 0 ? 'text-green-500' : 'text-red-500'}>
|
||||
{value >= 0 ? '+' : ''}{value.toFixed(2)}%
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: '24H ROI',
|
||||
accessor: 'roiLast24h',
|
||||
Cell: ({ value }: { value: number }) => (
|
||||
<span className={value >= 0 ? 'text-green-500' : 'text-red-500'}>
|
||||
{value >= 0 ? '+' : ''}{value.toFixed(2)}%
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'Wins/Losses',
|
||||
accessor: 'wins',
|
||||
Cell: ({ row }: { row: { original: AgentSummaryViewModel } }) => (
|
||||
<span>
|
||||
{row.original.wins}/{row.original.losses}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'Win Rate',
|
||||
accessor: 'averageWinRate',
|
||||
Cell: ({ value }: { value: number }) => (
|
||||
<span className="font-medium">{value.toFixed(1)}%</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'Active Strategies',
|
||||
accessor: 'activeStrategiesCount',
|
||||
Cell: ({ value }: { value: number }) => (
|
||||
<span className="badge badge-primary">{value}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'Total Volume',
|
||||
accessor: 'totalVolume',
|
||||
Cell: ({ value }: { value: number }) => (
|
||||
<span>${value.toLocaleString(undefined, { maximumFractionDigits: 2 })}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: '24H Volume',
|
||||
accessor: 'volumeLast24h',
|
||||
Cell: ({ value }: { value: number }) => (
|
||||
<span>${value.toLocaleString(undefined, { maximumFractionDigits: 2 })}</span>
|
||||
),
|
||||
},
|
||||
], [])
|
||||
|
||||
const tableData = useMemo(() => {
|
||||
if (!data?.agentSummaries) return []
|
||||
return data.agentSummaries.map(agent => ({
|
||||
...agent,
|
||||
// Ensure all numeric values are numbers for proper sorting
|
||||
totalPnL: Number(agent.totalPnL) || 0,
|
||||
pnLLast24h: Number(agent.pnLLast24h) || 0,
|
||||
totalROI: Number(agent.totalROI) || 0,
|
||||
roiLast24h: Number(agent.roiLast24h) || 0,
|
||||
wins: Number(agent.wins) || 0,
|
||||
losses: Number(agent.losses) || 0,
|
||||
averageWinRate: Number(agent.averageWinRate) || 0,
|
||||
activeStrategiesCount: Number(agent.activeStrategiesCount) || 0,
|
||||
totalVolume: Number(agent.totalVolume) || 0,
|
||||
volumeLast24h: Number(agent.volumeLast24h) || 0,
|
||||
}))
|
||||
}, [data?.agentSummaries])
|
||||
|
||||
return (
|
||||
<div className="container mx-auto pt-6 space-y-6">
|
||||
<GridTile title="Agent Index">
|
||||
{/* Filters and Controls */}
|
||||
<div className="flex flex-wrap gap-4 mb-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm font-medium">Time Filter:</label>
|
||||
<select
|
||||
className="select select-bordered select-sm"
|
||||
value={timeFilter}
|
||||
onChange={(e) => setTimeFilter(e.target.value)}
|
||||
>
|
||||
{TIME_FILTERS.map(filter => (
|
||||
<option key={filter.value} value={filter.value}>
|
||||
{filter.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm font-medium">Sort By:</label>
|
||||
<select
|
||||
className="select select-bordered select-sm"
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(e.target.value)}
|
||||
>
|
||||
{SORT_OPTIONS.map(option => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm font-medium">Order:</label>
|
||||
<select
|
||||
className="select select-bordered select-sm"
|
||||
value={sortOrder}
|
||||
onChange={(e) => setSortOrder(e.target.value as 'asc' | 'desc')}
|
||||
>
|
||||
<option value="desc">Descending</option>
|
||||
<option value="asc">Ascending</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm font-medium">Page Size:</label>
|
||||
<select
|
||||
className="select select-bordered select-sm"
|
||||
value={pageSize}
|
||||
onChange={(e) => setPageSize(Number(e.target.value))}
|
||||
>
|
||||
{[10, 20, 50, 100].map(size => (
|
||||
<option key={size} value={size}>
|
||||
{size}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Loading State */}
|
||||
{isLoading && (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<span className="loading loading-spinner loading-lg"></span>
|
||||
<span className="ml-2">Loading agents...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error State */}
|
||||
{error && (
|
||||
<div className="alert alert-error">
|
||||
<span>{error}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Data Summary */}
|
||||
{data && (
|
||||
<div className="mb-4 p-4 bg-base-200 rounded-lg">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<span className="text-sm opacity-70">Showing {data.agentSummaries?.length || 0} of {data.totalCount || 0} agents</span>
|
||||
</div>
|
||||
<div className="text-sm opacity-70">
|
||||
Page {data.currentPage || 1} of {data.totalPages || 1}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Table */}
|
||||
{data && data.agentSummaries && data.agentSummaries.length > 0 && (
|
||||
<Table
|
||||
columns={columns}
|
||||
data={tableData}
|
||||
showPagination={false} // We'll handle pagination manually
|
||||
hiddenColumns={[]}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Manual Pagination */}
|
||||
{data && data.totalPages && data.totalPages > 1 && (
|
||||
<div className="flex justify-center items-center gap-2 mt-4">
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => setCurrentPage(1)}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
{'<<'}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => setCurrentPage(currentPage - 1)}
|
||||
disabled={!data.hasPreviousPage}
|
||||
>
|
||||
{'<'}
|
||||
</button>
|
||||
|
||||
<span className="px-4">
|
||||
Page {currentPage} of {data.totalPages}
|
||||
</span>
|
||||
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => setCurrentPage(currentPage + 1)}
|
||||
disabled={!data.hasNextPage}
|
||||
>
|
||||
{'>'}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => setCurrentPage(data.totalPages)}
|
||||
disabled={currentPage === data.totalPages}
|
||||
>
|
||||
{'>>'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* No Data State */}
|
||||
{data && (!data.agentSummaries || data.agentSummaries.length === 0) && !isLoading && (
|
||||
<div className="text-center py-8">
|
||||
<p>No agents found for the selected criteria.</p>
|
||||
</div>
|
||||
)}
|
||||
</GridTile>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AgentIndex
|
||||
@@ -22,7 +22,7 @@ const FILTERS = [
|
||||
{ label: 'Total', value: 'Total', days: null },
|
||||
]
|
||||
|
||||
function AgentSearch() {
|
||||
function AgentSearch({ index }: { index: number }) {
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const [agentName, setAgentName] = useState('')
|
||||
const [agentData, setAgentData] = useState<AgentData | null>(null)
|
||||
@@ -231,15 +231,15 @@ function AgentSearch() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-lg font-bold ${
|
||||
(position.profitAndLoss?.realized || 0) >= 0 ? 'text-green-500' : 'text-red-500'
|
||||
(position.ProfitAndLoss?.realized || 0) >= 0 ? 'text-green-500' : 'text-red-500'
|
||||
}`}>
|
||||
{(position.profitAndLoss?.realized || 0) >= 0 ? '+' : ''}${(position.profitAndLoss?.realized || 0).toFixed(2)}
|
||||
{(position.ProfitAndLoss?.realized || 0) >= 0 ? '+' : ''}${(position.ProfitAndLoss?.realized || 0).toFixed(2)}
|
||||
</span>
|
||||
<span className={`flex items-center gap-1 text-sm ${
|
||||
(position.profitAndLoss?.realized || 0) >= 0 ? 'text-green-500' : 'text-red-500'
|
||||
(position.ProfitAndLoss?.realized || 0) >= 0 ? 'text-green-500' : 'text-red-500'
|
||||
}`}>
|
||||
{(position.profitAndLoss?.realized || 0) >= 0 ? '↑' : '↓'}
|
||||
{Math.abs(((position.profitAndLoss?.realized || 0) / (position.open?.price * position.open?.quantity || 1)) * 100).toFixed(2)}%
|
||||
{(position.ProfitAndLoss?.realized || 0) >= 0 ? '↑' : '↓'}
|
||||
{Math.abs(((position.ProfitAndLoss?.realized || 0) / (position.Open?.price * position.Open?.quantity || 1)) * 100).toFixed(2)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import {useState} from 'react'
|
||||
|
||||
import Tabs from '../../../components/mollecules/Tabs/Tabs'
|
||||
import type { TabsType } from '../../../global/type'
|
||||
import type {TabsType} from '../../../global/type'
|
||||
|
||||
import Cme from './cme'
|
||||
import Futures from './futures'
|
||||
@@ -37,7 +37,7 @@ const tabs: TabsType = [
|
||||
},
|
||||
]
|
||||
|
||||
const Analytics: React.FC = () => {
|
||||
const Analytics: React.FC<{ index: number }> = ({ index }) => {
|
||||
const [selectedTab, setSelectedTab] = useState<number>(tabs[0].index)
|
||||
|
||||
return (
|
||||
|
||||
@@ -23,7 +23,7 @@ const FILTERS = [
|
||||
{ label: 'Total', value: 'Total', days: null },
|
||||
]
|
||||
|
||||
function BestAgents() {
|
||||
function BestAgents({ index }: { index: number }) {
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const [data, setData] = useState<AgentBalanceWithBalances[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, {useEffect, useState} from 'react'
|
||||
|
||||
import Tabs from '../../components/mollecules/Tabs/Tabs'
|
||||
import type {ITabsType} from '../../global/type'
|
||||
import type {ITabsType} from '../../global/type.tsx'
|
||||
|
||||
import Analytics from './analytics/analytics'
|
||||
import Monitoring from './monitoring'
|
||||
import BestAgents from './analytics/bestAgents'
|
||||
import AgentSearch from './agentSearch'
|
||||
import AgentIndex from './agentIndex'
|
||||
|
||||
const tabs: ITabsType = [
|
||||
{
|
||||
@@ -29,6 +30,11 @@ const tabs: ITabsType = [
|
||||
index: 4,
|
||||
label: 'Agent Search',
|
||||
},
|
||||
{
|
||||
Component: AgentIndex,
|
||||
index: 5,
|
||||
label: 'Agent Index',
|
||||
},
|
||||
]
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ActiveBots } from '../../components/organism'
|
||||
import {ActiveBots} from '../../components/organism'
|
||||
|
||||
const Monitoring: React.FC = () => {
|
||||
const Monitoring: React.FC<{ index: number }> = ({ index }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-1 xl:grid-cols-1 container grid gap-8 pt-6 mx-auto mb-5">
|
||||
|
||||
Reference in New Issue
Block a user