Add Agent tracking balance
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -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 = () => {
|
||||
|
||||
Reference in New Issue
Block a user