Fix stop/restart
This commit is contained in:
@@ -20,7 +20,7 @@ namespace Managing.Application.Abstractions
|
||||
List<Position> Positions { get; set; }
|
||||
Dictionary<DateTime, decimal> WalletBalances { get; set; }
|
||||
Dictionary<IndicatorType, IndicatorsResultBase> IndicatorsValues { get; set; }
|
||||
DateTime StartupTime { get; set; }
|
||||
DateTime StartupTime { get; }
|
||||
DateTime CreateDate { get; }
|
||||
DateTime PreloadSince { get; set; }
|
||||
int PreloadedCandlesCount { get; set; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using MediatR;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.ManageBot.Commands
|
||||
{
|
||||
public class ToggleIsForWatchingCommand : IRequest<string>
|
||||
public class RestartBotCommand : IRequest<string>
|
||||
{
|
||||
public string Name { get; }
|
||||
public BotType BotType { get; }
|
||||
|
||||
public ToggleIsForWatchingCommand(string name)
|
||||
public RestartBotCommand(BotType botType, string name)
|
||||
{
|
||||
BotType = botType;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
using MediatR;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.ManageBot.Commands
|
||||
{
|
||||
public class RestartBotCommand : IRequest<string>
|
||||
public class ToggleIsForWatchingCommand : IRequest<string>
|
||||
{
|
||||
public string Name { get; }
|
||||
public BotType BotType { get; }
|
||||
|
||||
public RestartBotCommand(BotType botType, string name)
|
||||
public ToggleIsForWatchingCommand(string name)
|
||||
{
|
||||
BotType = botType;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,13 +31,6 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
||||
{
|
||||
try
|
||||
{
|
||||
if (backupBot.LastStatus == BotStatus.Down)
|
||||
{
|
||||
_logger.LogInformation("Skipping backup bot {Identifier} as it is marked as Down.",
|
||||
backupBot.Identifier);
|
||||
continue;
|
||||
}
|
||||
|
||||
var activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.Identifier == backupBot.Identifier);
|
||||
|
||||
if (activeBot == null)
|
||||
@@ -61,10 +54,20 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
||||
.FirstOrDefault(b => 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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<IBotList> = ({ 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<IBotList> = ({ 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<IBotList> = ({ list }) => {
|
||||
)
|
||||
}
|
||||
|
||||
function getCreateBotBadge() {
|
||||
const classes = baseBadgeClass() + ' bg-success'
|
||||
return (
|
||||
<button className={classes} onClick={() => openCreateBotModal()}>
|
||||
<p className="text-primary-content flex">
|
||||
<PlusCircleIcon width={15}></PlusCircleIcon>
|
||||
Create Bot
|
||||
</p>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
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<IBotList> = ({ list }) => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap m-4 -mx-4">
|
||||
<div className="w-full p-2 mb-4">
|
||||
<div className="flex justify-end">
|
||||
{getCreateBotBadge()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{list.map((bot: TradingBotResponse, index: number) => (
|
||||
<div
|
||||
key={index.toString()}
|
||||
@@ -246,13 +240,13 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
>
|
||||
<div className={cardClasses(bot.status)}>
|
||||
<figure className="w-full">
|
||||
{
|
||||
{bot.candles && bot.candles.length > 0 ? (
|
||||
<TradeChart
|
||||
candles={bot.candles}
|
||||
positions={bot.positions}
|
||||
signals={bot.signals}
|
||||
></TradeChart>
|
||||
}
|
||||
) : null}
|
||||
</figure>
|
||||
<div className="card-body">
|
||||
<div className="mb-4">
|
||||
@@ -266,16 +260,18 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
{/* Info Badges */}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{getMoneyManagementBadge(bot.config.moneyManagement)}
|
||||
{getIsForWatchingBadge(bot.config.isForWatchingOnly, bot.identifier)}
|
||||
</div>
|
||||
|
||||
{/* Action Badges */}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{getToggleBotStatusBadge(bot.status, bot.identifier, bot.config.flipPosition ? BotType.FlippingBot : BotType.SimpleBot)}
|
||||
{getUpdateBotBadge(bot)}
|
||||
{getManualPositionBadge(bot.identifier)}
|
||||
{getDeleteBadge(bot.identifier)}
|
||||
</div>
|
||||
{/* Action Badges - Only show for bot owners */}
|
||||
{isBotOwner(bot.agentName) && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{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)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -356,7 +352,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
}}
|
||||
/>
|
||||
<UnifiedTradingModal
|
||||
mode={botConfigModalMode}
|
||||
mode="updateBot"
|
||||
showModal={showBotConfigModal}
|
||||
closeModal={() => {
|
||||
setShowBotConfigModal(false)
|
||||
|
||||
@@ -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<StartBotRequest>()
|
||||
const [activeTab, setActiveTab] = useState(0)
|
||||
const [showBotConfigModal, setShowBotConfigModal] = useState(false)
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const botClient = new BotClient({}, apiUrl)
|
||||
|
||||
const onSubmit: SubmitHandler<StartBotRequest> = 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 (
|
||||
<div>
|
||||
<div className="container mx-auto">
|
||||
<div className="tooltip" data-tip="Run new bot">
|
||||
<button className="btn btn-primary m-1 text-xs" onClick={openModal}>
|
||||
<ViewGridAddIcon width="20"></ViewGridAddIcon>
|
||||
</button>
|
||||
{/* 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 className="tooltip" data-tip="Stop all bots">
|
||||
<button className="btn btn-error m-1 text-xs" onClick={stopAllBots}>
|
||||
<StopIcon width="20"></StopIcon>
|
||||
</button>
|
||||
</div>
|
||||
<div className="tooltip" data-tip="Restart all bots">
|
||||
<button
|
||||
className="btn btn-success m-1 text-xs"
|
||||
onClick={restartAllBots}
|
||||
>
|
||||
<PlayIcon width="20"></PlayIcon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tooltip" data-tip="Stop all bots">
|
||||
<button className="btn btn-error m-1 text-xs" onClick={stopAllBots}>
|
||||
<StopIcon width="20"></StopIcon>
|
||||
</button>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="tabs tabs-boxed mb-4">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.index}
|
||||
className={`tab ${activeTab === tab.index ? 'tab-active' : ''}`}
|
||||
onClick={() => setActiveTab(tab.index)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="tooltip" data-tip="Restart all bots">
|
||||
<button
|
||||
className="btn btn-success m-1 text-xs"
|
||||
onClick={restartAllBots}
|
||||
>
|
||||
<PlayIcon width="20"></PlayIcon>
|
||||
</button>
|
||||
</div>
|
||||
<BotList list={bots || []} />
|
||||
{showModal ? (
|
||||
<>
|
||||
<div>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="modal modal-bottom sm:modal-middle modal-open">
|
||||
<div className="modal-box">
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="btn btn-sm btn-circle right-2 top-2 absolute"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
<div className="text-primary mb-3 text-xl">Run a bot</div>
|
||||
<div className="form-control">
|
||||
<div className="input-group">
|
||||
<label htmlFor="accountName" className="label mr-6">
|
||||
Account
|
||||
</label>
|
||||
<select
|
||||
className="select select-bordered w-full h-auto max-w-xs"
|
||||
{...register('accountName')}
|
||||
>
|
||||
{accounts.map((item) => (
|
||||
<option
|
||||
style={{ color: 'black' }}
|
||||
key={item.name}
|
||||
value={item.name}
|
||||
>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-control">
|
||||
<div className="input-group">
|
||||
<label htmlFor="botName" className="label mr-6">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
className="w-full max-w-xs"
|
||||
{...register('botName')}
|
||||
></input>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-control">
|
||||
<div className="input-group">
|
||||
<label htmlFor="botType" className="label mr-6">
|
||||
Type
|
||||
</label>
|
||||
<select
|
||||
className="select w-full max-w-xs"
|
||||
{...register('botType')}
|
||||
>
|
||||
{Object.keys(BotType).map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-control">
|
||||
<div className="input-group">
|
||||
<label htmlFor="ticker" className="label mr-6">
|
||||
Ticker
|
||||
</label>
|
||||
<select
|
||||
className="select w-full max-w-xs"
|
||||
{...register('ticker')}
|
||||
>
|
||||
{Object.keys(Ticker).map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-control">
|
||||
<div className="input-group">
|
||||
<label htmlFor="timeframe" className="label mr-6">
|
||||
Timeframes
|
||||
</label>
|
||||
<select
|
||||
className="select w-full max-w-xs"
|
||||
{...register('timeframe')}
|
||||
>
|
||||
{Object.keys(Timeframe).map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-control">
|
||||
<div className="input-group">
|
||||
<label htmlFor="scenario" className="label mr-6">
|
||||
Scenario
|
||||
</label>
|
||||
<select
|
||||
className="select select-bordered w-full h-auto max-w-xs"
|
||||
{...register('scenario')}
|
||||
>
|
||||
{scenarios.map((item) => (
|
||||
<option key={item.name} value={item.name}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-control">
|
||||
<div className="input-group">
|
||||
<label htmlFor="isForWatchOnly" className="label mr-6">
|
||||
Watch Only
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
{...register('isForWatchOnly')}
|
||||
></input>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-action">
|
||||
<button type="submit" className="btn">
|
||||
Run
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{/* Bot List */}
|
||||
<BotList list={filteredBots} />
|
||||
|
||||
{/* Unified Trading Modal */}
|
||||
<UnifiedTradingModal
|
||||
mode="createBot"
|
||||
showModal={showBotConfigModal}
|
||||
closeModal={() => {
|
||||
setShowBotConfigModal(false)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user