Finish copy trading
This commit is contained in:
@@ -509,6 +509,7 @@ public class BotController : BaseController
|
||||
Roi = item.Roi,
|
||||
Identifier = item.Identifier.ToString(),
|
||||
AgentName = item.User.AgentName,
|
||||
MasterAgentName = item.MasterBotUser?.AgentName ?? item.User.AgentName,
|
||||
CreateDate = item.CreateDate,
|
||||
StartupTime = item.StartupTime,
|
||||
Name = item.Name,
|
||||
|
||||
@@ -407,7 +407,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
||||
_copyTradingStreamHandle = await streamProvider.GetStream<Position>(streamId)
|
||||
.SubscribeAsync(OnCopyTradingPositionReceivedAsync);
|
||||
|
||||
_logger.LogInformation("LiveTradingBotGrain {GrainId} subscribed to copy trading stream for master bot {MasterBotId}",
|
||||
_logger.LogInformation(
|
||||
"LiveTradingBotGrain {GrainId} subscribed to copy trading stream for master bot {MasterBotId}",
|
||||
this.GetPrimaryKey(), _state.State.Config.MasterBotIdentifier.Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -425,7 +426,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
||||
{
|
||||
await _copyTradingStreamHandle.UnsubscribeAsync();
|
||||
_copyTradingStreamHandle = null;
|
||||
_logger.LogInformation("LiveTradingBotGrain {GrainId} unsubscribed from copy trading stream", this.GetPrimaryKey());
|
||||
_logger.LogInformation("LiveTradingBotGrain {GrainId} unsubscribed from copy trading stream",
|
||||
this.GetPrimaryKey());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +440,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
||||
{
|
||||
if (_tradingBot == null)
|
||||
{
|
||||
_logger.LogWarning("Received copy trading position {PositionId} but trading bot is not running for bot {GrainId}",
|
||||
_logger.LogWarning(
|
||||
"Received copy trading position {PositionId} but trading bot is not running for bot {GrainId}",
|
||||
masterPosition.Identifier, this.GetPrimaryKey());
|
||||
return;
|
||||
}
|
||||
@@ -510,6 +513,12 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
||||
|
||||
// Check if copy trading authorization is still valid
|
||||
if (_state.State.Config.IsForCopyTrading && _state.State.Config.MasterBotIdentifier.HasValue)
|
||||
{
|
||||
// Check if copy trading validation should be bypassed (for testing)
|
||||
var enableValidation = Environment.GetEnvironmentVariable("ENABLE_COPY_TRADING_VALIDATION")?
|
||||
.Equals("true", StringComparison.OrdinalIgnoreCase) ?? true;
|
||||
|
||||
if (enableValidation)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -517,16 +526,19 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
||||
|
||||
var masterStrategy = await ServiceScopeHelpers.WithScopedService<IBotService, Bot>(
|
||||
_scopeFactory,
|
||||
async botService => await botService.GetBotByIdentifier(_state.State.Config.MasterBotIdentifier.Value));
|
||||
async botService =>
|
||||
await botService.GetBotByIdentifier(_state.State.Config.MasterBotIdentifier.Value));
|
||||
|
||||
if (masterStrategy == null)
|
||||
{
|
||||
_logger.LogWarning("Master strategy {MasterBotId} not found", _state.State.Config.MasterBotIdentifier.Value);
|
||||
_logger.LogWarning("Master strategy {MasterBotId} not found",
|
||||
_state.State.Config.MasterBotIdentifier.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
var hasMasterStrategyKey = ownedKeys.Items.Any(key =>
|
||||
string.Equals(key.AgentName, masterStrategy.User.AgentName, StringComparison.OrdinalIgnoreCase) &&
|
||||
string.Equals(key.AgentName, masterStrategy.User.AgentName,
|
||||
StringComparison.OrdinalIgnoreCase) &&
|
||||
key.Owned >= 1);
|
||||
|
||||
if (!hasMasterStrategyKey)
|
||||
@@ -535,7 +547,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
||||
"Copy trading bot {GrainId} no longer has authorization for master strategy {MasterBotId}. Stopping bot.",
|
||||
this.GetPrimaryKey(), _state.State.Config.MasterBotIdentifier.Value);
|
||||
|
||||
await StopAsync("Copy trading authorization revoked - user no longer owns keys for master strategy");
|
||||
await StopAsync(
|
||||
"Copy trading authorization revoked - user no longer owns keys for master strategy");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -547,6 +560,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
||||
SentrySdk.CaptureException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_tradingBot.Positions.Any(p => p.Value.IsOpen() || p.Value.Status.Equals(PositionStatus.New)))
|
||||
{
|
||||
@@ -1128,7 +1142,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
||||
_scopeFactory,
|
||||
async tradingService => await tradingService.GetPositionsByInitiatorIdentifierAsync(botId));
|
||||
|
||||
var openPositions = positions?.Where(p => p.IsOpen() || p.Status.Equals(PositionStatus.New)).ToList() ?? new List<Position>();
|
||||
var openPositions = positions?.Where(p => p.IsOpen() || p.Status.Equals(PositionStatus.New)).ToList() ??
|
||||
new List<Position>();
|
||||
|
||||
if (openPositions.Any())
|
||||
{
|
||||
@@ -1140,13 +1155,16 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Closing position {PositionId} for bot {GrainId}", position.Identifier, botId);
|
||||
_logger.LogInformation("Closing position {PositionId} for bot {GrainId}", position.Identifier,
|
||||
botId);
|
||||
await ClosePositionAsync(position.Identifier);
|
||||
_logger.LogInformation("Successfully closed position {PositionId} for bot {GrainId}", position.Identifier, botId);
|
||||
_logger.LogInformation("Successfully closed position {PositionId} for bot {GrainId}",
|
||||
position.Identifier, botId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to close position {PositionId} for bot {GrainId}", position.Identifier, botId);
|
||||
_logger.LogError(ex, "Failed to close position {PositionId} for bot {GrainId}",
|
||||
position.Identifier, botId);
|
||||
// Continue with other positions even if one fails
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,13 +93,16 @@ public class TradingBotBase : ITradingBot
|
||||
{
|
||||
case BotStatus.Saved:
|
||||
var indicatorNames = Config.Scenario.Indicators.Select(i => i.Type.ToString()).ToList();
|
||||
var modeText = Config.IsForWatchingOnly ? "Watch Only" :
|
||||
Config.IsForCopyTrading ? "Copy Trading" : "Live Trading";
|
||||
|
||||
var startupMessage = $"🚀 Bot Started Successfully\n\n" +
|
||||
$"📊 Trading Setup:\n" +
|
||||
$"🎯 Ticker: `{Config.Ticker}`\n" +
|
||||
$"⏰ Timeframe: `{Config.Timeframe}`\n" +
|
||||
$"🎮 Scenario: `{Config.Scenario?.Name ?? "Unknown"}`\n" +
|
||||
$"💰 Balance: `${Config.BotTradingBalance:F2}`\n" +
|
||||
$"👀 Mode: `{(Config.IsForWatchingOnly ? "Watch Only" : "Live Trading")}`\n\n" +
|
||||
$"👀 Mode: `{modeText}`\n\n" +
|
||||
$"📈 Active Indicators: `{string.Join(", ", indicatorNames)}`\n\n" +
|
||||
$"✅ Ready to monitor signals and execute trades\n" +
|
||||
$"📢 Notifications will be sent when positions are triggered";
|
||||
|
||||
@@ -71,7 +71,9 @@ namespace Managing.Application.ManageBot
|
||||
try
|
||||
{
|
||||
var config = await grain.GetConfiguration();
|
||||
var account = await grain.GetAccount();
|
||||
var account = await ServiceScopeHelpers.WithScopedService<IAccountService, Account>(
|
||||
_scopeFactory,
|
||||
async accountService => await accountService.GetAccount(config.AccountName, true, false));
|
||||
await grain.StopAsync("Deleting bot");
|
||||
await _botRepository.DeleteBot(identifier);
|
||||
await grain.DeleteAsync();
|
||||
|
||||
@@ -8,7 +8,6 @@ using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Users;
|
||||
using MediatR;
|
||||
using System;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.ManageBot
|
||||
@@ -50,7 +49,7 @@ namespace Managing.Application.ManageBot
|
||||
|
||||
// Check if copy trading validation should be bypassed (for testing)
|
||||
var enableValidation = Environment.GetEnvironmentVariable("ENABLE_COPY_TRADING_VALIDATION")?
|
||||
.Equals("true", StringComparison.OrdinalIgnoreCase) == true;
|
||||
.Equals("true", StringComparison.OrdinalIgnoreCase) ?? true;
|
||||
|
||||
if (enableValidation)
|
||||
{
|
||||
|
||||
@@ -194,6 +194,7 @@ public class PostgreSqlBotRepository : IBotRepository
|
||||
var query = _context.Bots
|
||||
.AsNoTracking()
|
||||
.Include(m => m.User)
|
||||
.Include(m => m.MasterBotUser)
|
||||
.AsQueryable();
|
||||
|
||||
// Apply filters
|
||||
|
||||
@@ -754,7 +754,8 @@ public static class PostgreSqlMappers
|
||||
Fees = entity.Fees,
|
||||
LongPositionCount = entity.LongPositionCount,
|
||||
ShortPositionCount = entity.ShortPositionCount,
|
||||
MasterBotUserId = entity.MasterBotUserId
|
||||
MasterBotUserId = entity.MasterBotUserId,
|
||||
MasterBotUser = entity.MasterBotUser != null ? Map(entity.MasterBotUser) : null
|
||||
};
|
||||
|
||||
return bot;
|
||||
|
||||
@@ -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