Add Agent tracking balance

This commit is contained in:
2025-05-16 22:30:18 +07:00
parent b34e3aa886
commit 1cfb83f0b1
34 changed files with 764 additions and 115 deletions

View File

@@ -1131,6 +1131,98 @@ export class DataClient extends AuthorizedApiBase {
}
return Promise.resolve<PlatformSummaryViewModel>(null as any);
}
data_GetAgentBalances(agentName: string | null | undefined, startDate: Date | undefined, endDate: Date | null | undefined): Promise<AgentBalance[]> {
let url_ = this.baseUrl + "/Data/GetAgentBalances?";
if (agentName !== undefined && agentName !== null)
url_ += "agentName=" + encodeURIComponent("" + agentName) + "&";
if (startDate === null)
throw new Error("The parameter 'startDate' cannot be null.");
else if (startDate !== undefined)
url_ += "startDate=" + encodeURIComponent(startDate ? "" + startDate.toISOString() : "") + "&";
if (endDate !== undefined && endDate !== null)
url_ += "endDate=" + encodeURIComponent(endDate ? "" + endDate.toISOString() : "") + "&";
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_GetAgentBalances(_response);
});
}
protected processData_GetAgentBalances(response: Response): Promise<AgentBalance[]> {
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 AgentBalance[];
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<AgentBalance[]>(null as any);
}
data_GetBestAgents(startDate: Date | undefined, endDate: Date | null | undefined, page: number | undefined, pageSize: number | undefined): Promise<BestAgentsResponse> {
let url_ = this.baseUrl + "/Data/GetBestAgents?";
if (startDate === null)
throw new Error("The parameter 'startDate' cannot be null.");
else if (startDate !== undefined)
url_ += "startDate=" + encodeURIComponent(startDate ? "" + startDate.toISOString() : "") + "&";
if (endDate !== undefined && endDate !== null)
url_ += "endDate=" + encodeURIComponent(endDate ? "" + endDate.toISOString() : "") + "&";
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) + "&";
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_GetBestAgents(_response);
});
}
protected processData_GetBestAgents(response: Response): Promise<BestAgentsResponse> {
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 BestAgentsResponse;
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<BestAgentsResponse>(null as any);
}
}
export class MoneyManagementClient extends AuthorizedApiBase {
@@ -3132,6 +3224,7 @@ export interface UserStrategyDetailsViewModel {
wins?: number;
losses?: number;
positions?: Position[] | null;
identifier?: string | null;
}
export interface PlatformSummaryViewModel {
@@ -3158,6 +3251,38 @@ export interface AgentSummaryViewModel {
volumeLast24h?: number;
}
export interface AgentBalance {
agentName?: string | null;
totalValue?: number;
totalAccountUsdValue?: number;
botsAllocationUsdValue?: number;
pnL?: number;
time?: Date;
}
export interface BestAgentsResponse {
agents?: AgentBalanceHistory[] | null;
totalCount?: number;
currentPage?: number;
pageSize?: number;
totalPages?: number;
}
export interface AgentBalanceHistory {
agentName?: string | null;
totalValue?: number;
totalAccountUsdValue?: number;
botsAllocationUsdValue?: number;
pnL?: number;
time?: Date;
pnLHistory?: PnLPoint[] | null;
}
export interface PnLPoint {
time?: Date;
value?: number;
}
export enum RiskLevel {
Low = "Low",
Medium = "Medium",

View File

@@ -0,0 +1,92 @@
import React, {useEffect, useState} from 'react'
import {GridTile, Table} from '../../../components/mollecules'
import useApiUrlStore from '../../../app/store/apiStore'
import {type AgentBalanceHistory, type BestAgentsResponse, DataClient} from '../../../generated/ManagingApi'
// Extend the type to include agentBalances for runtime use
export interface AgentBalanceWithBalances extends AgentBalanceHistory {
agentBalances?: Array<{
totalValue?: number
totalAccountUsdValue?: number
botsAllocationUsdValue?: number
pnL?: number
time?: string
}>;
}
function BestAgents() {
const { apiUrl } = useApiUrlStore()
const [data, setData] = useState<AgentBalanceWithBalances[]>([])
const [isLoading, setIsLoading] = useState(true)
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [totalPages, setTotalPages] = useState(1)
const [expandedAgent, setExpandedAgent] = useState<string | null>(null)
useEffect(() => {
setIsLoading(true)
const client = new DataClient({}, apiUrl)
const now = new Date()
const startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 30)
client.data_GetBestAgents(startDate, now, page, pageSize).then((res: BestAgentsResponse) => {
setData(res.agents as AgentBalanceWithBalances[] ?? [])
setTotalPages(res.totalPages ?? 1)
console.log(res)
}).finally(() => setIsLoading(false))
}, [apiUrl, page, pageSize])
// Type guard for agentBalances
function hasAgentBalances(agent: AgentBalanceWithBalances): agent is Required<AgentBalanceWithBalances> {
return Array.isArray(agent.agentBalances) && agent.agentBalances.length > 0;
}
// Get the latest balance for each agent
const latestBalances = data.map(agent => {
if (hasAgentBalances(agent)) {
const lastBalance = agent.agentBalances[agent.agentBalances.length - 1]
return {
agentName: agent.agentName,
originalAgent: agent, // Store the original agent for row details
...lastBalance
}
}
// fallback: just agentName
return { agentName: agent.agentName, originalAgent: agent }
})
const columns = [
{ Header: 'Agent', accessor: 'agentName' },
{ Header: 'Total Value (USD)', accessor: 'totalValue', Cell: ({ value }: any) => value?.toLocaleString(undefined, { maximumFractionDigits: 2 }) },
{ Header: 'Account Value (USD)', accessor: 'totalAccountUsdValue', Cell: ({ value }: any) => value?.toLocaleString(undefined, { maximumFractionDigits: 2 }) },
{ Header: 'Bots Allocation (USD)', accessor: 'botsAllocationUsdValue', Cell: ({ value }: any) => value?.toLocaleString(undefined, { maximumFractionDigits: 2 }) },
{ Header: 'PnL (USD)', accessor: 'pnL', Cell: ({ value }: any) => value?.toLocaleString(undefined, { maximumFractionDigits: 2 }) },
{ Header: 'Last Update', accessor: 'time', Cell: ({ value }: any) => value ? new Date(value).toLocaleString() : '' },
]
return (
<div className="container mx-auto pt-6">
<GridTile title="Best Agents">
{isLoading ? (
<progress className="progress progress-primary w-56"></progress>
) : (
<Table
columns={columns}
data={latestBalances}
showPagination={false}
/>
)}
<div className="flex justify-between items-center mt-4">
<button className="btn" onClick={() => setPage((p) => Math.max(1, p - 1))} disabled={page === 1}>Previous</button>
<span>Page {page} of {totalPages}</span>
<button className="btn" onClick={() => setPage((p) => Math.min(totalPages, p + 1))} disabled={page === totalPages}>Next</button>
<select className="select select-bordered ml-2" value={pageSize} onChange={e => { setPageSize(Number(e.target.value)); setPage(1) }}>
{[10, 20, 30, 40, 50].map(size => <option key={size} value={size}>Show {size}</option>)}
</select>
</div>
</GridTile>
</div>
)
}
export default BestAgents

View File

@@ -1,10 +1,11 @@
import React, { useEffect, useState } from 'react'
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'
import Analytics from './analytics/analytics'
import Monitoring from './monitoring'
import BestAgents from './analytics/bestAgents'
const tabs: ITabsType = [
{
@@ -17,6 +18,11 @@ const tabs: ITabsType = [
index: 2,
label: 'Analytics',
},
{
Component: BestAgents,
index: 3,
label: 'Best Agents',
},
]
const Dashboard: React.FC = () => {