Finish copy trading
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 %',
|
||||
|
||||
@@ -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
|
||||
@@ -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',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user