diff --git a/src/Managing.Application/Abstractions/ITradingBot.cs b/src/Managing.Application/Abstractions/ITradingBot.cs index f862d5d..ec4e773 100644 --- a/src/Managing.Application/Abstractions/ITradingBot.cs +++ b/src/Managing.Application/Abstractions/ITradingBot.cs @@ -20,7 +20,7 @@ namespace Managing.Application.Abstractions List Positions { get; set; } Dictionary WalletBalances { get; set; } Dictionary IndicatorsValues { get; set; } - DateTime StartupTime { get; set; } + DateTime StartupTime { get; } DateTime CreateDate { get; } DateTime PreloadSince { get; set; } int PreloadedCandlesCount { get; set; } diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index 21f86e7..a55a01e 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -116,14 +116,15 @@ public class TradingBot : Bot, ITradingBot // Send startup message only for fresh starts (not reboots) // Consider it a reboot if the bot was created more than 5 minutes ago var timeSinceCreation = DateTime.UtcNow - CreateDate; - var isReboot = timeSinceCreation.TotalMinutes > 5; + var isReboot = timeSinceCreation.TotalMinutes > 3; - StartupTime = DateTime.UtcNow; if (!isReboot) { try { + StartupTime = DateTime.UtcNow; + var indicatorNames = Indicators.Select(i => i.Type.ToString()).ToList(); var startupMessage = $"🚀 **Bot Started Successfully!**\n\n" + $"📊 **Trading Setup:**\n" + @@ -157,6 +158,7 @@ public class TradingBot : Bot, ITradingBot } } + InitWorker(Run).GetAwaiter().GetResult(); } @@ -1458,6 +1460,7 @@ public class TradingBot : Bot, ITradingBot Identifier = backup.Identifier; User = backup.User; Status = backup.LastStatus; + StartupTime = data.StartupTime; } /// diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs index cee6b69..ffe52a1 100644 --- a/src/Managing.Application/ManageBot/BotService.cs +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -174,7 +174,18 @@ namespace Managing.Application.ManageBot bot.User = user; // Config is already set correctly from backup data, so we only need to restore signals, positions, etc. bot.LoadBackup(backupBot); - bot.Start(); + + // Only start the bot if the backup status is Up + if (backupBot.LastStatus == BotStatus.Up) + { + // Start the bot asynchronously without waiting for completion + _ = Task.Run(() => bot.Start()); + } + else + { + // Keep the bot in Down status if it was originally Down + bot.Stop(); + } } public IBot CreateSimpleBot(string botName, Workflow workflow) @@ -246,12 +257,15 @@ namespace Managing.Application.ManageBot { // Stop the bot first to ensure clean state bot.Stop(); - + // Small delay to ensure stop is complete await Task.Delay(100); - + // Restart the bot (this will update StartupTime) bot.Restart(); + + // Start the bot asynchronously without waiting for completion + _ = Task.Run(() => bot.Start()); var restartMessage = $"🔄 **Bot Restarted**\n\n" + $"🎯 **Agent:** {bot.User.AgentName}\n" + diff --git a/src/Managing.Application/ManageBot/Commands/RestartBotCommand.cs b/src/Managing.Application/ManageBot/Commands/RestartBotCommand.cs index 40ee74a..d92a34a 100644 --- a/src/Managing.Application/ManageBot/Commands/RestartBotCommand.cs +++ b/src/Managing.Application/ManageBot/Commands/RestartBotCommand.cs @@ -1,13 +1,16 @@ using MediatR; +using static Managing.Common.Enums; namespace Managing.Application.ManageBot.Commands { - public class ToggleIsForWatchingCommand : IRequest + public class RestartBotCommand : IRequest { public string Name { get; } + public BotType BotType { get; } - public ToggleIsForWatchingCommand(string name) + public RestartBotCommand(BotType botType, string name) { + BotType = botType; Name = name; } } diff --git a/src/Managing.Application/ManageBot/Commands/ToggleIsForWatchingCommand.cs b/src/Managing.Application/ManageBot/Commands/ToggleIsForWatchingCommand.cs index d92a34a..40ee74a 100644 --- a/src/Managing.Application/ManageBot/Commands/ToggleIsForWatchingCommand.cs +++ b/src/Managing.Application/ManageBot/Commands/ToggleIsForWatchingCommand.cs @@ -1,16 +1,13 @@ using MediatR; -using static Managing.Common.Enums; namespace Managing.Application.ManageBot.Commands { - public class RestartBotCommand : IRequest + public class ToggleIsForWatchingCommand : IRequest { public string Name { get; } - public BotType BotType { get; } - public RestartBotCommand(BotType botType, string name) + public ToggleIsForWatchingCommand(string name) { - BotType = botType; Name = name; } } diff --git a/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs index 4bd225e..9f344f9 100644 --- a/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs @@ -31,13 +31,6 @@ public class LoadBackupBotCommandHandler : IRequestHandler b.Identifier == backupBot.Identifier); if (activeBot == null) @@ -61,10 +54,20 @@ public class LoadBackupBotCommandHandler : IRequestHandler b.Identifier == backupBot.Identifier); if (activeBot != null) { - result[activeBot.Identifier] = BotStatus.Up; - anyBackupStarted = true; - _logger.LogInformation("Backup bot {Identifier} started successfully.", - backupBot.Identifier); + // Check if the bot was originally Down + if (backupBot.LastStatus == BotStatus.Down) + { + result[activeBot.Identifier] = BotStatus.Down; + _logger.LogInformation("Backup bot {Identifier} loaded but kept in Down status as it was originally Down.", + backupBot.Identifier); + } + else + { + result[activeBot.Identifier] = BotStatus.Up; + anyBackupStarted = true; + _logger.LogInformation("Backup bot {Identifier} started successfully.", + backupBot.Identifier); + } break; } diff --git a/src/Managing.Domain/Bots/Bot.cs b/src/Managing.Domain/Bots/Bot.cs index 7f5194f..96f53dc 100644 --- a/src/Managing.Domain/Bots/Bot.cs +++ b/src/Managing.Domain/Bots/Bot.cs @@ -85,13 +85,12 @@ namespace Managing.Domain.Bots { Status = BotStatus.Down; SaveBackup(); - CancellationToken.Cancel(); + // CancellationToken.Cancel(); } public void Restart() { Status = BotStatus.Up; - SaveBackup(); StartupTime = DateTime.UtcNow; // Update the startup time when the bot is restarted } diff --git a/src/Managing.WebApp/src/pages/botsPage/botList.tsx b/src/Managing.WebApp/src/pages/botsPage/botList.tsx index e20a17f..8b853a0 100644 --- a/src/Managing.WebApp/src/pages/botsPage/botList.tsx +++ b/src/Managing.WebApp/src/pages/botsPage/botList.tsx @@ -6,9 +6,17 @@ import {CardPosition, CardSignal, CardText, Toast,} from '../../components/molle import ManualPositionModal from '../../components/mollecules/ManualPositionModal' import TradesModal from '../../components/mollecules/TradesModal/TradesModal' import {TradeChart, UnifiedTradingModal} from '../../components/organism' -import {BotClient, BotType, MoneyManagement, Position, TradingBotResponse} from '../../generated/ManagingApi' +import { + BotClient, + BotType, + MoneyManagement, + Position, + TradingBotResponse, + UserClient +} from '../../generated/ManagingApi' import type {IBotList} from '../../global/type.tsx' import MoneyManagementModal from '../settingsPage/moneymanagement/moneyManagementModal' +import {useQuery} from '@tanstack/react-query' function baseBadgeClass(isOutlined = false) { let classes = 'text-xs badge badge-sm transition-all duration-200 hover:scale-105 ' @@ -29,6 +37,13 @@ function cardClasses(botStatus: string) { const BotList: React.FC = ({ list }) => { const { apiUrl } = useApiUrlStore() const client = new BotClient({}, apiUrl) + const userClient = new UserClient({}, apiUrl) + + const { data: currentUser } = useQuery({ + queryFn: () => userClient.user_GetCurrentUser(), + queryKey: ['currentUser'], + }) + const [showMoneyManagementModal, setShowMoneyManagementModal] = useState(false) const [selectedMoneyManagement, setSelectedMoneyManagement] = @@ -38,12 +53,16 @@ const BotList: React.FC = ({ list }) => { const [showTradesModal, setShowTradesModal] = useState(false) const [selectedBotForTrades, setSelectedBotForTrades] = useState<{ identifier: string; agentName: string } | null>(null) const [showBotConfigModal, setShowBotConfigModal] = useState(false) - const [botConfigModalMode, setBotConfigModalMode] = useState<'createBot' | 'updateBot'>('createBot') const [selectedBotForUpdate, setSelectedBotForUpdate] = useState<{ identifier: string config: any } | null>(null) + // Helper function to check if current user owns the bot + const isBotOwner = (botAgentName: string): boolean => { + return currentUser?.agentName === botAgentName + } + function getIsForWatchingBadge(isForWatchingOnly: boolean, identifier: string) { const classes = baseBadgeClass() + (isForWatchingOnly ? ' bg-accent' : ' bg-primary') @@ -204,26 +223,7 @@ const BotList: React.FC = ({ list }) => { ) } - function getCreateBotBadge() { - const classes = baseBadgeClass() + ' bg-success' - return ( - - ) - } - - function openCreateBotModal() { - setBotConfigModalMode('createBot') - setSelectedBotForUpdate(null) - setShowBotConfigModal(true) - } - function openUpdateBotModal(bot: TradingBotResponse) { - setBotConfigModalMode('updateBot') setSelectedBotForUpdate({ identifier: bot.identifier, config: bot.config @@ -233,12 +233,6 @@ const BotList: React.FC = ({ list }) => { return (
-
-
- {getCreateBotBadge()} -
-
- {list.map((bot: TradingBotResponse, index: number) => (
= ({ list }) => { >
- { + {bot.candles && bot.candles.length > 0 ? ( - } + ) : null}
@@ -266,16 +260,18 @@ const BotList: React.FC = ({ list }) => { {/* Info Badges */}
{getMoneyManagementBadge(bot.config.moneyManagement)} - {getIsForWatchingBadge(bot.config.isForWatchingOnly, bot.identifier)}
- {/* Action Badges */} -
- {getToggleBotStatusBadge(bot.status, bot.identifier, bot.config.flipPosition ? BotType.FlippingBot : BotType.SimpleBot)} - {getUpdateBotBadge(bot)} - {getManualPositionBadge(bot.identifier)} - {getDeleteBadge(bot.identifier)} -
+ {/* Action Badges - Only show for bot owners */} + {isBotOwner(bot.agentName) && ( +
+ {getIsForWatchingBadge(bot.config.isForWatchingOnly, bot.identifier)} + {getToggleBotStatusBadge(bot.status, bot.identifier, bot.config.flipPosition ? BotType.FlippingBot : BotType.SimpleBot)} + {getUpdateBotBadge(bot)} + {getManualPositionBadge(bot.identifier)} + {getDeleteBadge(bot.identifier)} +
+ )}
@@ -356,7 +352,7 @@ const BotList: React.FC = ({ list }) => { }} /> { setShowBotConfigModal(false) diff --git a/src/Managing.WebApp/src/pages/botsPage/bots.tsx b/src/Managing.WebApp/src/pages/botsPage/bots.tsx index 7e1edbb..7996ab0 100644 --- a/src/Managing.WebApp/src/pages/botsPage/bots.tsx +++ b/src/Managing.WebApp/src/pages/botsPage/bots.tsx @@ -1,70 +1,58 @@ -import { PlayIcon, StopIcon, ViewGridAddIcon } from '@heroicons/react/solid' -import React, { useEffect, useState } from 'react' -import type { SubmitHandler } from 'react-hook-form' -import { useForm } from 'react-hook-form' +import {PlayIcon, StopIcon, ViewGridAddIcon} from '@heroicons/react/solid' +import React, {useState} from 'react' import 'react-toastify/dist/ReactToastify.css' - -import { Hub } from '../../app/providers/Hubs' import useApiUrlStore from '../../app/store/apiStore' -import { Toast } from '../../components/mollecules' -import type { - Account, - Scenario, - StartBotRequest, - TradingBot, -} from '../../generated/ManagingApi' -import { - AccountClient, - ScenarioClient, - BotClient, - BotType, - Ticker, - Timeframe, -} from '../../generated/ManagingApi' +import {Toast} from '../../components/mollecules' +import {UnifiedTradingModal} from '../../components/organism' +import type {TradingBotResponse,} from '../../generated/ManagingApi' +import {BotClient, UserClient,} from '../../generated/ManagingApi' import BotList from './botList' -import { useQuery } from '@tanstack/react-query' +import {useQuery} from '@tanstack/react-query' const Bots: React.FC = () => { - const [showModal, setShowModal] = useState(false) - const { register, handleSubmit } = useForm() + const [activeTab, setActiveTab] = useState(0) + const [showBotConfigModal, setShowBotConfigModal] = useState(false) const { apiUrl } = useApiUrlStore() const botClient = new BotClient({}, apiUrl) - - const onSubmit: SubmitHandler = async (form) => { - closeModal() - const t = new Toast('Bot is starting') - await botClient - .bot_Start(form) - .then((status: string) => { - t.update('success', 'Bot status : ' + status) - }) - .catch((err) => { - t.update('error', err) - }) - } + const userClient = new UserClient({}, apiUrl) const { data: bots } = useQuery({ queryFn: () => botClient.bot_GetActiveBots(), queryKey: ['bots'], }) - const scenarioClient = new ScenarioClient({}, apiUrl) - const { data: scenarios } = useQuery({ - queryFn: () => scenarioClient.scenario_GetScenarios(), - queryKey: ['scenarios'], + const { data: currentUser } = useQuery({ + queryFn: () => userClient.user_GetCurrentUser(), + queryKey: ['currentUser'], }) - const accountClient = new AccountClient({}, apiUrl) - const { data: accounts } = useQuery({ - queryFn: () => accountClient.account_GetAccounts(), - queryKey: ['accounts'], - }) + // Filter bots based on active tab and current user + const getFilteredBots = (): TradingBotResponse[] => { + if (!bots || !currentUser) return [] + + switch (activeTab) { + case 0: // All Active Bots + return bots.filter(bot => bot.status === 'Up') + case 1: // My Active Bots + return bots.filter(bot => bot.status === 'Up' && bot.agentName === currentUser.agentName) + case 2: // My Down Bots + return bots.filter(bot => bot.status === 'Down' && bot.agentName === currentUser.agentName) + default: + return bots + } + } + + const filteredBots = getFilteredBots() + + function openCreateBotModal() { + setShowBotConfigModal(true) + } // const setupHubConnection = async () => { // const hub = new Hub('bothub', apiUrl).hub - // hub.on('BotsSubscription', (bots: TradingBot[]) => { + // hub.on('BotsSubscription', (bots: TradingBotResponse[]) => { // // eslint-disable-next-line no-console // console.log( // 'bot List', @@ -78,14 +66,6 @@ const Bots: React.FC = () => { // return hub // } - function openModal() { - setShowModal(true) - } - - function closeModal() { - setShowModal(false) - } - async function stopAllBots() { const t = new Toast('Stoping all bots') await botClient @@ -110,163 +90,61 @@ const Bots: React.FC = () => { }) } + const tabs = [ + { label: 'All Active Bots', index: 0 }, + { label: 'My Active Bots', index: 1 }, + { label: 'My Down Bots', index: 2 }, + ] + return (
-
- + {/* Action Buttons */} +
+
+ +
+
+ +
+
+ +
-
- + + {/* Tabs */} +
+ {tabs.map((tab) => ( + + ))}
-
- -
- - {showModal ? ( - <> -
-
-
-
- -
Run a bot
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
- -
-
-
-
-
- - ) : null} + + {/* Bot List */} + + + {/* Unified Trading Modal */} + { + setShowBotConfigModal(false) + }} + />
)