Add copy trading functionality with StartCopyTrading endpoint and related models. Implemented position copying from master bot and subscription to copy trading stream in LiveTradingBotGrain. Updated TradingBotConfig to support copy trading parameters.
This commit is contained in:
@@ -1479,6 +1479,45 @@ export class BotClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<string>(null as any);
|
||||
}
|
||||
|
||||
bot_StartCopyTrading(request: StartCopyTradingRequest): Promise<string> {
|
||||
let url_ = this.baseUrl + "/Bot/StartCopyTrading";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
const content_ = JSON.stringify(request);
|
||||
|
||||
let options_: RequestInit = {
|
||||
body: content_,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processBot_StartCopyTrading(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBot_StartCopyTrading(response: Response): Promise<string> {
|
||||
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 string;
|
||||
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<string>(null as any);
|
||||
}
|
||||
|
||||
bot_Save(request: SaveBotRequest): Promise<string> {
|
||||
let url_ = this.baseUrl + "/Bot/Save";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
@@ -4772,6 +4811,8 @@ export interface TradingBotConfig {
|
||||
useForPositionSizing?: boolean;
|
||||
useForSignalFiltering?: boolean;
|
||||
useForDynamicStopLoss?: boolean;
|
||||
isForCopyTrading?: boolean;
|
||||
masterBotIdentifier?: string | null;
|
||||
}
|
||||
|
||||
export interface LightMoneyManagement {
|
||||
@@ -5334,6 +5375,11 @@ export interface StartBotRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
}
|
||||
|
||||
export interface StartCopyTradingRequest {
|
||||
masterBotIdentifier?: string;
|
||||
botTradingBalance?: number;
|
||||
}
|
||||
|
||||
export interface SaveBotRequest extends StartBotRequest {
|
||||
}
|
||||
|
||||
|
||||
@@ -334,6 +334,8 @@ export interface TradingBotConfig {
|
||||
useForPositionSizing?: boolean;
|
||||
useForSignalFiltering?: boolean;
|
||||
useForDynamicStopLoss?: boolean;
|
||||
isForCopyTrading?: boolean;
|
||||
masterBotIdentifier?: string | null;
|
||||
}
|
||||
|
||||
export interface LightMoneyManagement {
|
||||
@@ -896,6 +898,11 @@ export interface StartBotRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
}
|
||||
|
||||
export interface StartCopyTradingRequest {
|
||||
masterBotIdentifier?: string;
|
||||
botTradingBalance?: number;
|
||||
}
|
||||
|
||||
export interface SaveBotRequest extends StartBotRequest {
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
import {ChartBarIcon, CogIcon, EyeIcon, PlayIcon, PlusCircleIcon, StopIcon, TrashIcon} from '@heroicons/react/solid'
|
||||
import {
|
||||
ChartBarIcon,
|
||||
CogIcon,
|
||||
DocumentDuplicateIcon,
|
||||
EyeIcon,
|
||||
PlayIcon,
|
||||
PlusCircleIcon,
|
||||
StopIcon,
|
||||
TrashIcon
|
||||
} from '@heroicons/react/solid'
|
||||
import React, {useState} from 'react'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import {useCurrentUser} from '../../app/store/userStore'
|
||||
import {CardPosition, CardSignal, CardText, Toast,} from '../../components/mollecules'
|
||||
import {Toast,} from '../../components/mollecules'
|
||||
import ManualPositionModal from '../../components/mollecules/ManualPositionModal'
|
||||
import TradesModal from '../../components/mollecules/TradesModal/TradesModal'
|
||||
import {TradeChart, UnifiedTradingModal} from '../../components/organism'
|
||||
import Table from '../../components/mollecules/Table/Table'
|
||||
import {UnifiedTradingModal} from '../../components/organism'
|
||||
import {
|
||||
BotClient,
|
||||
BotSortableColumn,
|
||||
BotStatus,
|
||||
MoneyManagement,
|
||||
Position,
|
||||
StartCopyTradingRequest,
|
||||
TradingBotConfig,
|
||||
TradingBotResponse
|
||||
} from '../../generated/ManagingApi'
|
||||
@@ -51,15 +63,29 @@ function cardClasses(botStatus: BotStatus) {
|
||||
const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const client = new BotClient({}, apiUrl)
|
||||
|
||||
|
||||
// Call the hook at the top level
|
||||
const { user } = useCurrentUser()
|
||||
|
||||
|
||||
// Filter and pagination states
|
||||
const [pageNumber, setPageNumber] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
const [statusFilter, setStatusFilter] = useState<BotStatus | undefined>(undefined)
|
||||
const [agentFilter, setAgentFilter] = useState<string | undefined>(undefined)
|
||||
const [sortBy, setSortBy] = useState<BotSortableColumn>(BotSortableColumn.Roi)
|
||||
const [sortDirection, setSortDirection] = useState<'Asc' | 'Desc'>('Desc')
|
||||
|
||||
// Use the user data for bot ownership checking
|
||||
const checkIsBotOwner = (botAgentName: string) => {
|
||||
return user?.agentName === botAgentName
|
||||
}
|
||||
|
||||
// Fetch paginated bot data
|
||||
const { data: paginatedBots, isLoading } = useQuery({
|
||||
queryFn: () => client.bot_GetBotsPaginated(pageNumber, pageSize, statusFilter, undefined, undefined, agentFilter, sortBy, sortDirection),
|
||||
queryKey: ['bots', pageNumber, pageSize, statusFilter, agentFilter, sortBy, sortDirection],
|
||||
})
|
||||
|
||||
const [showMoneyManagementModal, setShowMoneyManagementModal] =
|
||||
useState(false)
|
||||
const [selectedMoneyManagement, setSelectedMoneyManagement] =
|
||||
@@ -73,6 +99,9 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
identifier: string
|
||||
config: TradingBotConfig
|
||||
} | null>(null)
|
||||
const [showCopyTradingModal, setShowCopyTradingModal] = useState(false)
|
||||
const [selectedBotForCopy, setSelectedBotForCopy] = useState<{ name: string; identifier: string } | null>(null)
|
||||
const [copyTradingBalance, setCopyTradingBalance] = useState<number>(1000)
|
||||
|
||||
function getDeleteBadge(identifier: string) {
|
||||
const classes = baseBadgeClass() + 'bg-error'
|
||||
@@ -145,6 +174,17 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
)
|
||||
}
|
||||
|
||||
function getCopyTradingBadge(name: string, botIdentifier: string) {
|
||||
const classes = baseBadgeClass() + ' bg-purple-500'
|
||||
return (
|
||||
<button className={classes} onClick={() => openCopyTradingModal(name, botIdentifier)}>
|
||||
<p className="text-primary-content flex">
|
||||
<DocumentDuplicateIcon width={15}></DocumentDuplicateIcon>
|
||||
</p>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function openManualPositionModal(botIdentifier: string) {
|
||||
setSelectedBotForManualPosition(botIdentifier)
|
||||
setShowManualPositionModal(true)
|
||||
@@ -155,6 +195,11 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
setShowTradesModal(true)
|
||||
}
|
||||
|
||||
function openCopyTradingModal(name: string, botIdentifier: string) {
|
||||
setSelectedBotForCopy({ name: name, identifier: botIdentifier })
|
||||
setShowCopyTradingModal(true)
|
||||
}
|
||||
|
||||
function toggleBotStatus(status: BotStatus, identifier: string) {
|
||||
const isUp = status == BotStatus.Running
|
||||
const t = new Toast(isUp ? 'Stoping bot' : 'Restarting bot')
|
||||
@@ -193,6 +238,29 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
})
|
||||
}
|
||||
|
||||
function copyTrading() {
|
||||
if (!selectedBotForCopy) return
|
||||
|
||||
const t = new Toast('Starting copy trading bot')
|
||||
|
||||
const request: StartCopyTradingRequest = {
|
||||
masterBotIdentifier: selectedBotForCopy.identifier,
|
||||
botTradingBalance: copyTradingBalance
|
||||
}
|
||||
|
||||
client
|
||||
.bot_StartCopyTrading(request)
|
||||
.then((result) => {
|
||||
t.update('success', result || 'Copy trading bot started successfully', { autoClose: 3000 })
|
||||
setShowCopyTradingModal(false)
|
||||
setSelectedBotForCopy(null)
|
||||
setCopyTradingBalance(1000)
|
||||
})
|
||||
.catch((err) => {
|
||||
t.update('error', err.message || 'Failed to start copy trading bot', { autoClose: 5000 })
|
||||
})
|
||||
}
|
||||
|
||||
function getUpdateBotBadge(bot: TradingBotResponse) {
|
||||
const classes = baseBadgeClass() + ' bg-orange-500'
|
||||
return (
|
||||
@@ -206,7 +274,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
|
||||
async function openUpdateBotModal(bot: TradingBotResponse) {
|
||||
const t = new Toast('Loading bot configuration...')
|
||||
|
||||
|
||||
try {
|
||||
const config = await client.bot_GetBotConfig(bot.identifier)
|
||||
setSelectedBotForUpdate({
|
||||
@@ -221,94 +289,158 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap m-4 -mx-4">
|
||||
{list.map((bot: TradingBotResponse, index: number) => (
|
||||
<div
|
||||
key={index.toString()}
|
||||
className="sm:w-1 md:w-1/2 xl:w-1/2 w-full p-2"
|
||||
>
|
||||
<div className={cardClasses(bot.status as BotStatus)}>
|
||||
<figure className="w-full">
|
||||
{bot.candles && bot.candles.length > 0 ? (
|
||||
<TradeChart
|
||||
candles={bot.candles}
|
||||
positions={Object.values(bot.positions)}
|
||||
signals={Object.values(bot.signals)}
|
||||
></TradeChart>
|
||||
) : null}
|
||||
</figure>
|
||||
<div className="card-body">
|
||||
<div className="mb-4">
|
||||
{/* Bot Name - Always on its own line */}
|
||||
<h2 className="card-title text-sm mb-3">
|
||||
{bot.name}
|
||||
</h2>
|
||||
|
||||
{/* Badge Container - Responsive */}
|
||||
<div className="flex flex-wrap gap-1 sm:gap-2">
|
||||
|
||||
{/* Action Badges - Only show for bot owners */}
|
||||
{ (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{getToggleBotStatusBadge(bot.status as BotStatus, bot.identifier)}
|
||||
{getUpdateBotBadge(bot)}
|
||||
{getManualPositionBadge(bot.identifier)}
|
||||
{getDeleteBadge(bot.identifier)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="columns-2">
|
||||
<div>
|
||||
<div>
|
||||
<CardText
|
||||
title="Ticker"
|
||||
content={bot.ticker}
|
||||
></CardText>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns-2">
|
||||
<CardText
|
||||
title="Agent"
|
||||
content={bot.agentName}
|
||||
></CardText>
|
||||
<CardSignal signals={Object.values(bot.signals ?? {})}></CardSignal>
|
||||
</div>
|
||||
<div className="columns-2">
|
||||
<CardPosition
|
||||
positivePosition={true}
|
||||
positions={Object.values(bot.positions ?? {}).filter((p: Position) => {
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0
|
||||
return realized > 0 ? p : null
|
||||
})}
|
||||
></CardPosition>
|
||||
<CardPosition
|
||||
positivePosition={false}
|
||||
positions={Object.values(bot.positions ?? {}).filter((p: Position) => {
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0
|
||||
return realized <= 0 ? p : null
|
||||
})}
|
||||
></CardPosition>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<div className="card-actions justify-center pt-2">
|
||||
<div className={baseBadgeClass(true)}>
|
||||
WR {bot.winRate?.toFixed(2).toString()} %
|
||||
</div>
|
||||
<div className={baseBadgeClass(true)}>
|
||||
PNL {bot.profitAndLoss.toFixed(2).toString()} $
|
||||
</div>
|
||||
{getTradesBadge(bot.name, bot.agentName, bot.identifier)}
|
||||
</div>
|
||||
</div>
|
||||
// Table columns definition
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Name',
|
||||
accessor: 'name',
|
||||
Cell: ({ row }: any) => (
|
||||
<div className="font-medium">{row.original.name}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
Cell: ({ row }: any) => {
|
||||
const status = row.original.status as BotStatus
|
||||
let statusClass = 'badge '
|
||||
switch (status) {
|
||||
case BotStatus.Running:
|
||||
statusClass += 'badge-success'
|
||||
break
|
||||
case BotStatus.Stopped:
|
||||
statusClass += 'badge-warning'
|
||||
break
|
||||
case BotStatus.Saved:
|
||||
statusClass += 'badge-info'
|
||||
break
|
||||
default:
|
||||
statusClass += 'badge-neutral'
|
||||
}
|
||||
return <div className={statusClass}>{BotStatus[status]}</div>
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: 'Ticker',
|
||||
accessor: 'ticker',
|
||||
},
|
||||
{
|
||||
Header: 'Agent',
|
||||
accessor: 'agentName',
|
||||
},
|
||||
{
|
||||
Header: 'Win Rate %',
|
||||
accessor: 'winRate',
|
||||
Cell: ({ value }: any) => value?.toFixed(2) || '0.00',
|
||||
},
|
||||
{
|
||||
Header: 'PNL $',
|
||||
accessor: 'profitAndLoss',
|
||||
Cell: ({ value }: any) => value?.toFixed(2) || '0.00',
|
||||
},
|
||||
{
|
||||
Header: 'Actions',
|
||||
accessor: 'actions',
|
||||
disableSortBy: true,
|
||||
Cell: ({ row }: any) => {
|
||||
const bot = row.original
|
||||
return (
|
||||
<div className="flex gap-1">
|
||||
{getToggleBotStatusBadge(bot.status, bot.identifier)}
|
||||
{getUpdateBotBadge(bot)}
|
||||
{getManualPositionBadge(bot.identifier)}
|
||||
{getCopyTradingBadge(bot.name, bot.identifier)}
|
||||
{getTradesBadge(bot.name, bot.agentName, bot.identifier)}
|
||||
{getDeleteBadge(bot.identifier)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
{/* Filters */}
|
||||
<div className="mb-4 flex flex-wrap gap-4 items-center">
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Status</span>
|
||||
</label>
|
||||
<select
|
||||
className="select select-bordered select-sm"
|
||||
value={statusFilter || ''}
|
||||
onChange={(e) => setStatusFilter(e.target.value ? (Number(e.target.value) as unknown as BotStatus) : undefined)}
|
||||
>
|
||||
<option value="">All Status</option>
|
||||
<option value={BotStatus.Running}>Running</option>
|
||||
<option value={BotStatus.Stopped}>Stopped</option>
|
||||
<option value={BotStatus.Saved}>Saved</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Agent</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filter by agent name"
|
||||
className="input input-bordered input-sm"
|
||||
value={agentFilter || ''}
|
||||
onChange={(e) => setAgentFilter(e.target.value || undefined)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Sort By</span>
|
||||
</label>
|
||||
<select
|
||||
className="select select-bordered select-sm"
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(Number(e.target.value) as unknown as BotSortableColumn)}
|
||||
>
|
||||
<option value={BotSortableColumn.Roi}>ROI</option>
|
||||
<option value={BotSortableColumn.Pnl}>Profit & Loss</option>
|
||||
<option value={BotSortableColumn.WinRate}>Win Rate</option>
|
||||
<option value={BotSortableColumn.Name}>Name</option>
|
||||
<option value={BotSortableColumn.Status}>Status</option>
|
||||
<option value={BotSortableColumn.CreateDate}>Created At</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Sort Direction</span>
|
||||
</label>
|
||||
<select
|
||||
className="select select-bordered select-sm"
|
||||
value={sortDirection}
|
||||
onChange={(e) => setSortDirection(e.target.value as 'Asc' | 'Desc')}
|
||||
>
|
||||
<option value="Desc">Descending</option>
|
||||
<option value="Asc">Ascending</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<div className="loading loading-spinner loading-lg"></div>
|
||||
</div>
|
||||
) : (
|
||||
<Table
|
||||
columns={columns}
|
||||
data={paginatedBots?.items || []}
|
||||
showPagination={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Modals */}
|
||||
<MoneyManagementModal
|
||||
showModal={showMoneyManagementModal}
|
||||
moneyManagement={selectedMoneyManagement}
|
||||
@@ -345,6 +477,51 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
config: selectedBotForUpdate.config
|
||||
} : undefined}
|
||||
/>
|
||||
|
||||
{/* Copy Trading Modal */}
|
||||
{showCopyTradingModal && selectedBotForCopy && (
|
||||
<div className="modal modal-open">
|
||||
<div className="modal-box">
|
||||
<h3 className="font-bold text-lg">Start Copy Trading</h3>
|
||||
<p className="py-4">
|
||||
Copy trades from <strong>{selectedBotForCopy.name}</strong>
|
||||
</p>
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Trading Balance (USDC)</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
placeholder="Enter trading balance"
|
||||
className="input input-bordered"
|
||||
value={copyTradingBalance}
|
||||
onChange={(e) => setCopyTradingBalance(Number(e.target.value))}
|
||||
min="1"
|
||||
step="0.01"
|
||||
/>
|
||||
</div>
|
||||
<div className="modal-action">
|
||||
<button
|
||||
className="btn"
|
||||
onClick={() => {
|
||||
setShowCopyTradingModal(false)
|
||||
setSelectedBotForCopy(null)
|
||||
setCopyTradingBalance(1000)
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={copyTrading}
|
||||
disabled={copyTradingBalance <= 0}
|
||||
>
|
||||
Start Copy Trading
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,92 +1,16 @@
|
||||
import {ViewGridAddIcon} from '@heroicons/react/solid'
|
||||
import React, {useState} from 'react'
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import {useCurrentAgentName} from '../../app/store/userStore'
|
||||
import {UnifiedTradingModal} from '../../components/organism'
|
||||
import {BotClient, BotSortableColumn, BotStatus} from '../../generated/ManagingApi'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import BotList from './botList'
|
||||
|
||||
const Bots: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState(0)
|
||||
const [showBotConfigModal, setShowBotConfigModal] = useState(false)
|
||||
const [pageNumber, setPageNumber] = useState(1)
|
||||
const [pageSize] = useState(50) // Fixed page size for now
|
||||
|
||||
// Reset page number when tab changes
|
||||
const handleTabChange = (newTab: number) => {
|
||||
setActiveTab(newTab)
|
||||
setPageNumber(1) // Reset to first page when changing tabs
|
||||
}
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const botClient = new BotClient({}, apiUrl)
|
||||
|
||||
// Use the new user store hook to get current agent name
|
||||
const currentAgentName = useCurrentAgentName()
|
||||
|
||||
// Query for paginated bots using the new endpoint
|
||||
const { data: paginatedBots } = useQuery({
|
||||
queryFn: () => {
|
||||
switch (activeTab) {
|
||||
case 0: // All Active Bots
|
||||
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Running, undefined, undefined, undefined, BotSortableColumn.Roi, 'Desc')
|
||||
case 1: // My Active Bots
|
||||
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Running, undefined, undefined, currentAgentName, BotSortableColumn.Roi, 'Desc')
|
||||
case 2: // My Down Bots
|
||||
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Stopped, undefined, undefined, currentAgentName, BotSortableColumn.Roi, 'Desc')
|
||||
case 3: // Saved Bots
|
||||
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Saved, undefined, undefined, currentAgentName, BotSortableColumn.Roi, 'Desc')
|
||||
default:
|
||||
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, undefined, undefined, undefined, undefined, BotSortableColumn.Roi, 'Desc')
|
||||
}
|
||||
},
|
||||
queryKey: ['paginatedBots', activeTab, pageNumber, pageSize, currentAgentName],
|
||||
enabled: !!currentAgentName,
|
||||
})
|
||||
|
||||
const filteredBots = paginatedBots?.items || []
|
||||
|
||||
function openCreateBotModal() {
|
||||
setShowBotConfigModal(true)
|
||||
}
|
||||
|
||||
|
||||
const tabs = [
|
||||
{ label: 'All Active Bots', index: 0 },
|
||||
{ label: 'My Active Bots', index: 1 },
|
||||
{ label: 'My Down Bots', index: 2 },
|
||||
{ label: 'Saved Bots', index: 3 },
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="container mx-auto">
|
||||
{/* Action Buttons */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
<div className="tooltip" data-tip="Create new bot">
|
||||
<button className="btn btn-primary m-1 text-xs" onClick={openCreateBotModal}>
|
||||
<ViewGridAddIcon width="20"></ViewGridAddIcon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="tabs tabs-boxed mb-4">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.index}
|
||||
className={`tab ${activeTab === tab.index ? 'tab-active' : ''}`}
|
||||
onClick={() => handleTabChange(tab.index)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Bot List */}
|
||||
<BotList list={filteredBots} />
|
||||
<BotList list={[]} />
|
||||
|
||||
{/* Unified Trading Modal */}
|
||||
<UnifiedTradingModal
|
||||
|
||||
Reference in New Issue
Block a user