Finish copy trading

This commit is contained in:
2025-11-20 14:46:54 +07:00
parent ff2df2d9ac
commit 190a9cf12d
13 changed files with 96 additions and 222 deletions

View File

@@ -4866,7 +4866,6 @@ export interface Backtest {
endDate: Date;
statistics: PerformanceMetrics;
fees: number;
walletBalances: KeyValuePairOfDateTimeAndDecimal[];
user: User;
score: number;
requestId?: string;
@@ -4874,6 +4873,7 @@ export interface Backtest {
scoreMessage?: string;
initialBalance: number;
netPnl: number;
positionCount: number;
}
export interface TradingBotConfig {
@@ -4900,6 +4900,7 @@ export interface TradingBotConfig {
useForDynamicStopLoss?: boolean;
isForCopyTrading?: boolean;
masterBotIdentifier?: string | null;
masterBotUserId?: number | null;
}
export interface LightMoneyManagement {
@@ -5007,6 +5008,7 @@ export interface Position {
initiator: PositionInitiator;
user: User;
initiatorIdentifier: string;
recoveryAttempted?: boolean;
}
export enum TradeDirection {
@@ -5128,11 +5130,6 @@ export interface PerformanceMetrics {
totalPnL?: number;
}
export interface KeyValuePairOfDateTimeAndDecimal {
key?: Date;
value?: number;
}
export interface DeleteBacktestsRequest {
backtestIds: string[];
}
@@ -5153,6 +5150,7 @@ export interface LightBacktestResponse {
scoreMessage: string;
initialBalance: number;
netPnl: number;
positionCount: number;
}
export interface PaginatedBacktestsResponse {
@@ -5200,6 +5198,7 @@ export interface LightBacktest {
ticker?: string | null;
initialBalance?: number;
netPnl?: number;
positionCount?: number;
}
export interface RunBacktestRequest {
@@ -5490,6 +5489,7 @@ export interface TradingBotResponse {
startupTime: Date;
name: string;
ticker: Ticker;
masterAgentName?: string | null;
}
export interface PaginatedResponseOfTradingBotResponse {

View File

@@ -332,7 +332,6 @@ export interface Backtest {
endDate: Date;
statistics: PerformanceMetrics;
fees: number;
walletBalances: KeyValuePairOfDateTimeAndDecimal[];
user: User;
score: number;
requestId?: string;
@@ -340,6 +339,7 @@ export interface Backtest {
scoreMessage?: string;
initialBalance: number;
netPnl: number;
positionCount: number;
}
export interface TradingBotConfig {
@@ -366,6 +366,7 @@ export interface TradingBotConfig {
useForDynamicStopLoss?: boolean;
isForCopyTrading?: boolean;
masterBotIdentifier?: string | null;
masterBotUserId?: number | null;
}
export interface LightMoneyManagement {
@@ -473,6 +474,7 @@ export interface Position {
initiator: PositionInitiator;
user: User;
initiatorIdentifier: string;
recoveryAttempted?: boolean;
}
export enum TradeDirection {
@@ -594,11 +596,6 @@ export interface PerformanceMetrics {
totalPnL?: number;
}
export interface KeyValuePairOfDateTimeAndDecimal {
key?: Date;
value?: number;
}
export interface DeleteBacktestsRequest {
backtestIds: string[];
}
@@ -619,6 +616,7 @@ export interface LightBacktestResponse {
scoreMessage: string;
initialBalance: number;
netPnl: number;
positionCount: number;
}
export interface PaginatedBacktestsResponse {
@@ -666,6 +664,7 @@ export interface LightBacktest {
ticker?: string | null;
initialBalance?: number;
netPnl?: number;
positionCount?: number;
}
export interface RunBacktestRequest {
@@ -956,6 +955,7 @@ export interface TradingBotResponse {
startupTime: Date;
name: string;
ticker: Ticker;
masterAgentName?: string | null;
}
export interface PaginatedResponseOfTradingBotResponse {

View File

@@ -328,6 +328,22 @@ const BotList: React.FC<IBotList> = ({ list }) => {
{
Header: 'Agent',
accessor: 'agentName',
Cell: ({ row }: any) => {
const bot = row.original
const hasMasterAgent = bot.masterAgentName && bot.masterAgentName !== bot.agentName
if (hasMasterAgent) {
return (
<div className="tooltip tooltip-bottom" data-tip={`Strategy created by ${bot.masterAgentName}`}>
<span className="underline decoration-dotted decoration-primary cursor-help">
{bot.agentName}
</span>
</div>
)
}
return <span>{bot.agentName}</span>
},
},
{
Header: 'Win Rate %',

View File

@@ -1,126 +0,0 @@
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
}>;
}
const FILTERS = [
{ label: '24H', value: '24H', days: 1 },
{ label: '3D', value: '3D', days: 3 },
{ label: '1W', value: '1W', days: 7 },
{ label: '1M', value: '1M', days: 30 },
{ label: '1Y', value: '1Y', days: 365 },
{ label: 'Total', value: 'Total', days: null },
]
function BestAgents({ index }: { index: number }) {
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 [selectedFilter, setSelectedFilter] = useState('Total')
useEffect(() => {
setIsLoading(true)
const client = new DataClient({}, apiUrl)
const now = new Date()
// Calculate start date based on selected filter
const filterObj = FILTERS.find(f => f.value === selectedFilter)
let startDate: Date
if (filterObj?.days) {
// Use the filter's days value to calculate start date
startDate = new Date(now.getTime() - filterObj.days * 24 * 60 * 60 * 1000)
} else {
// For 'Total', fetch from a far past date (e.g., 5 years ago)
startDate = new Date(now.getFullYear() - 5, now.getMonth(), now.getDate())
}
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, selectedFilter])
function filterBalancesByRange(agent: AgentBalanceWithBalances) {
if (!agent.agentBalances || selectedFilter === 'Total') return agent.agentBalances ?? []
const days = FILTERS.find(f => f.value === selectedFilter)?.days
if (!days) return agent.agentBalances ?? []
const now = new Date()
const cutoff = new Date(now.getTime() - days * 24 * 60 * 60 * 1000)
return agent.agentBalances.filter(b => b.time && new Date(b.time) >= cutoff)
}
// Get the latest balance for each agent
const latestBalances = data.map(agent => {
const filteredBalances = filterBalancesByRange(agent)
if (filteredBalances.length > 0) {
const lastBalance = filteredBalances[filteredBalances.length - 1]
return {
agentName: agent.agentName,
originalAgent: { ...agent, agentBalances: filteredBalances },
...lastBalance
}
}
return { agentName: agent.agentName, originalAgent: { ...agent, agentBalances: filteredBalances } }
})
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">
<div className="flex gap-2 mb-3">
{FILTERS.map(f => (
<button
key={f.value}
className={`px-2 py-0.5 text-xs rounded ${selectedFilter === f.value ? 'bg-primary text-primary-content' : 'bg-base-200'}`}
onClick={() => setSelectedFilter(f.value)}
>
{f.label}
</button>
))}
</div>
{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

@@ -5,7 +5,6 @@ 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'
import AgentStrategy from './agentStrategy'
@@ -27,24 +26,19 @@ const tabs: ITabsType = [
index: 3,
label: 'Analytics',
},
{
Component: BestAgents,
index: 4,
label: 'Best Agents',
},
{
Component: AgentSearch,
index: 5,
index: 4,
label: 'Agent Search',
},
{
Component: AgentIndex,
index: 6,
index: 5,
label: 'Agent Index',
},
{
Component: AgentStrategy,
index: 7,
index: 6,
label: 'Agent Strategy',
},
]

View File

@@ -198,13 +198,6 @@ function PlatformSummary({index}: { index: number }) {
{topStrategies?.topStrategies?.slice(0, 3).map((strategy, index) => (
<div key={index} className="flex items-center justify-between gap-1 md:gap-2">
<div className="flex items-center gap-1.5 md:gap-3 min-w-0 flex-1">
<div className="avatar placeholder flex-shrink-0 hidden md:flex">
<div className="bg-primary text-primary-content rounded-full w-8 h-8">
<span className="text-xs font-bold">
{strategy.strategyName?.charAt(0) || 'S'}
</span>
</div>
</div>
<div className="min-w-0 flex-1">
<div className="text-xs md:text-sm text-base-content font-medium truncate">
{strategy.strategyName || '[Strategy Name]'}
@@ -235,13 +228,6 @@ function PlatformSummary({index}: { index: number }) {
{topStrategiesByRoi?.topStrategiesByRoi?.slice(0, 3).map((strategy, index) => (
<div key={index} className="flex items-center justify-between gap-1 md:gap-2">
<div className="flex items-center gap-1.5 md:gap-3 min-w-0 flex-1">
<div className="avatar placeholder flex-shrink-0 hidden md:flex">
<div className="bg-success text-success-content rounded-full w-8 h-8">
<span className="text-xs font-bold">
{strategy.strategyName?.charAt(0) || 'S'}
</span>
</div>
</div>
<div className="min-w-0 flex-1">
<div className="text-xs md:text-sm text-base-content font-medium truncate">
{strategy.strategyName || '[Strategy Name]'}
@@ -280,13 +266,6 @@ function PlatformSummary({index}: { index: number }) {
{topAgentsByPnL?.slice(0, 3).map((agent, index) => (
<div key={index} className="flex items-center justify-between gap-1 md:gap-2">
<div className="flex items-center gap-1.5 md:gap-3 min-w-0 flex-1">
<div className="avatar placeholder flex-shrink-0 hidden md:flex">
<div className="bg-secondary text-secondary-content rounded-full w-8 h-8">
<span className="text-xs font-bold">
{agent.agentName?.charAt(0) || 'A'}
</span>
</div>
</div>
<div className="min-w-0 flex-1">
<div className="text-xs md:text-sm text-base-content font-medium truncate">
{agent.agentName || '[Agent Name]'}
@@ -485,13 +464,6 @@ function PlatformSummary({index}: { index: number }) {
.map(([asset, volume]) => (
<div key={asset} className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="avatar placeholder">
<div className="bg-primary text-primary-content rounded-full w-8">
<span className="text-xs font-bold">
{asset.substring(0, 2)}
</span>
</div>
</div>
<span className="text-base-content font-medium">{asset}</span>
</div>
<div className="text-right">
@@ -520,13 +492,6 @@ function PlatformSummary({index}: { index: number }) {
.map(([asset, count]) => (
<div key={asset} className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="avatar placeholder">
<div className="bg-secondary text-secondary-content rounded-full w-8">
<span className="text-xs font-bold">
{asset.substring(0, 2)}
</span>
</div>
</div>
<span className="text-base-content font-medium">{asset}</span>
</div>
<div className="text-right">