Update config

This commit is contained in:
2025-06-04 23:15:50 +07:00
parent 756cd5fb11
commit 973a8c7c61
14 changed files with 969 additions and 369 deletions

View File

@@ -0,0 +1,619 @@
import React, {useEffect, useState} from 'react'
import {useQuery} from '@tanstack/react-query'
import useApiUrlStore from '../../../app/store/apiStore'
import {
AccountClient,
Backtest,
BotClient,
BotType,
MoneyManagement,
MoneyManagementClient,
ScenarioClient,
StartBotRequest,
Ticker,
Timeframe,
TradingBotConfig,
UpdateBotConfigRequest
} from '../../../generated/ManagingApi'
import Toast from '../Toast/Toast'
interface BotConfigModalProps {
showModal: boolean
onClose: () => void
backtest?: Backtest // When creating from backtest
existingBot?: {
identifier: string
config: TradingBotConfig
} // When updating existing bot
mode: 'create' | 'update' // Explicitly specify the mode
}
const BotConfigModal: React.FC<BotConfigModalProps> = ({
showModal,
onClose,
backtest,
existingBot,
mode
}) => {
const { apiUrl } = useApiUrlStore()
// Form state
const [formData, setFormData] = useState<{
name: string
accountName: string
moneyManagementName: string
ticker: Ticker
scenarioName: string
timeframe: Timeframe
isForWatchingOnly: boolean
botTradingBalance: number
botType: BotType
cooldownPeriod: number
maxLossStreak: number
maxPositionTimeHours: number | null
flipOnlyWhenInProfit: boolean
flipPosition: boolean
closeEarlyWhenProfitable: boolean
useCustomMoneyManagement: boolean
customStopLoss: number
customTakeProfit: number
customLeverage: number
}>({
name: '',
accountName: '',
moneyManagementName: '',
ticker: Ticker.BTC,
scenarioName: '',
timeframe: Timeframe.FifteenMinutes,
isForWatchingOnly: false,
botTradingBalance: 1000,
botType: BotType.ScalpingBot,
cooldownPeriod: 1,
maxLossStreak: 0,
maxPositionTimeHours: null,
flipOnlyWhenInProfit: true,
flipPosition: false,
closeEarlyWhenProfitable: false,
useCustomMoneyManagement: false,
customStopLoss: 0.01,
customTakeProfit: 0.02,
customLeverage: 1
})
// Fetch data
const { data: accounts } = useQuery({
queryFn: async () => {
const accountClient = new AccountClient({}, apiUrl)
return await accountClient.account_GetAccounts()
},
queryKey: ['accounts']
})
const { data: moneyManagements } = useQuery({
queryFn: async () => {
const moneyManagementClient = new MoneyManagementClient({}, apiUrl)
return await moneyManagementClient.moneyManagement_GetMoneyManagements()
},
queryKey: ['moneyManagements']
})
const { data: scenarios } = useQuery({
queryFn: async () => {
const scenarioClient = new ScenarioClient({}, apiUrl)
return await scenarioClient.scenario_GetScenarios()
},
queryKey: ['scenarios']
})
// Initialize form data based on props
useEffect(() => {
if (mode === 'create' && backtest) {
// Initialize from backtest
setFormData({
name: `Bot-${backtest.config.scenarioName}-${new Date().toISOString().slice(0, 19).replace(/[-:]/g, '')}`,
accountName: backtest.config.accountName,
moneyManagementName: moneyManagements?.[0]?.name || '',
ticker: backtest.config.ticker,
scenarioName: backtest.config.scenarioName,
timeframe: backtest.config.timeframe,
isForWatchingOnly: false,
botTradingBalance: 1000,
botType: backtest.config.botType,
cooldownPeriod: backtest.config.cooldownPeriod,
maxLossStreak: backtest.config.maxLossStreak,
maxPositionTimeHours: backtest.config.maxPositionTimeHours ?? null,
flipOnlyWhenInProfit: backtest.config.flipOnlyWhenInProfit,
flipPosition: backtest.config.flipPosition,
closeEarlyWhenProfitable: backtest.config.closeEarlyWhenProfitable || false,
useCustomMoneyManagement: true, // Default to custom for backtests
customStopLoss: backtest.config.moneyManagement?.stopLoss || 0.01,
customTakeProfit: backtest.config.moneyManagement?.takeProfit || 0.02,
customLeverage: backtest.config.moneyManagement?.leverage || 1
})
} else if (mode === 'update' && existingBot) {
// Initialize from existing bot
setFormData({
name: existingBot.config.name,
accountName: existingBot.config.accountName,
moneyManagementName: existingBot.config.moneyManagement?.name || '',
ticker: existingBot.config.ticker,
scenarioName: existingBot.config.scenarioName,
timeframe: existingBot.config.timeframe,
isForWatchingOnly: existingBot.config.isForWatchingOnly,
botTradingBalance: existingBot.config.botTradingBalance,
botType: existingBot.config.botType,
cooldownPeriod: existingBot.config.cooldownPeriod,
maxLossStreak: existingBot.config.maxLossStreak,
maxPositionTimeHours: existingBot.config.maxPositionTimeHours ?? null,
flipOnlyWhenInProfit: existingBot.config.flipOnlyWhenInProfit,
flipPosition: existingBot.config.flipPosition,
closeEarlyWhenProfitable: existingBot.config.closeEarlyWhenProfitable || false,
useCustomMoneyManagement: false,
customStopLoss: existingBot.config.moneyManagement?.stopLoss || 0.01,
customTakeProfit: existingBot.config.moneyManagement?.takeProfit || 0.02,
customLeverage: existingBot.config.moneyManagement?.leverage || 1
})
} else if (mode === 'create' && !backtest) {
// Initialize for new bot creation
setFormData({
name: `Bot-${new Date().toISOString().slice(0, 19).replace(/[-:]/g, '')}`,
accountName: accounts?.[0]?.name || '',
moneyManagementName: moneyManagements?.[0]?.name || '',
ticker: Ticker.BTC,
scenarioName: scenarios?.[0]?.name || '',
timeframe: Timeframe.FifteenMinutes,
isForWatchingOnly: false,
botTradingBalance: 1000,
botType: BotType.ScalpingBot,
cooldownPeriod: 1,
maxLossStreak: 0,
maxPositionTimeHours: null,
flipOnlyWhenInProfit: true,
flipPosition: false,
closeEarlyWhenProfitable: false,
useCustomMoneyManagement: false,
customStopLoss: 0.01,
customTakeProfit: 0.02,
customLeverage: 1
})
}
}, [mode, backtest, existingBot, accounts, moneyManagements, scenarios])
// Set default money management when data loads
useEffect(() => {
if (moneyManagements && moneyManagements.length > 0 && !formData.moneyManagementName) {
setFormData(prev => ({
...prev,
moneyManagementName: moneyManagements[0].name
}))
}
}, [moneyManagements])
// Set default account when data loads
useEffect(() => {
if (accounts && accounts.length > 0 && !formData.accountName) {
setFormData(prev => ({
...prev,
accountName: accounts[0].name
}))
}
}, [accounts])
// Set default scenario when data loads
useEffect(() => {
if (scenarios && scenarios.length > 0 && !formData.scenarioName) {
setFormData(prev => ({
...prev,
scenarioName: scenarios[0].name || ''
}))
}
}, [scenarios])
const handleInputChange = (field: string, value: any) => {
setFormData(prev => ({
...prev,
[field]: value
}))
}
const handleSubmit = async () => {
const t = new Toast(mode === 'create' ? 'Creating bot...' : 'Updating bot...')
const client = new BotClient({}, apiUrl)
try {
// Create the money management object
let moneyManagement: MoneyManagement | undefined = undefined
if (formData.useCustomMoneyManagement || (mode === 'create' && backtest)) {
// Use custom money management
moneyManagement = {
name: 'custom',
leverage: formData.customLeverage,
stopLoss: formData.customStopLoss,
takeProfit: formData.customTakeProfit,
timeframe: formData.timeframe
}
} else {
// Use saved money management - load the complete object
const selectedMoneyManagement = moneyManagements?.find(mm => mm.name === formData.moneyManagementName)
if (selectedMoneyManagement) {
moneyManagement = selectedMoneyManagement
} else {
t.update('error', 'Selected money management not found')
return
}
}
if (!moneyManagement) {
t.update('error', 'Money management is required')
return
}
// Create TradingBotConfig (reused for both create and update)
const tradingBotConfig: TradingBotConfig = {
accountName: formData.accountName,
ticker: formData.ticker,
scenarioName: formData.scenarioName,
timeframe: formData.timeframe,
botType: formData.botType,
isForWatchingOnly: formData.isForWatchingOnly,
isForBacktest: false,
cooldownPeriod: formData.cooldownPeriod,
maxLossStreak: formData.maxLossStreak,
maxPositionTimeHours: formData.maxPositionTimeHours,
flipOnlyWhenInProfit: formData.flipOnlyWhenInProfit,
flipPosition: formData.flipPosition,
name: formData.name,
botTradingBalance: formData.botTradingBalance,
moneyManagement: moneyManagement,
closeEarlyWhenProfitable: formData.closeEarlyWhenProfitable
}
if (mode === 'create') {
// Create new bot
const request: StartBotRequest = {
config: tradingBotConfig,
moneyManagementName: formData.useCustomMoneyManagement ? undefined : formData.moneyManagementName
}
await client.bot_Start(request)
t.update('success', 'Bot created successfully!')
} else {
// Update existing bot
const request: UpdateBotConfigRequest = {
identifier: existingBot!.identifier,
config: tradingBotConfig,
moneyManagementName: formData.useCustomMoneyManagement ? undefined : formData.moneyManagementName
}
await client.bot_UpdateBotConfig(request)
t.update('success', 'Bot updated successfully!')
}
onClose()
} catch (error: any) {
t.update('error', `Error: ${error.message || error}`)
}
}
if (!showModal) return null
return (
<div className="modal modal-open">
<div className="modal-box w-11/12 max-w-4xl">
<h3 className="font-bold text-lg mb-4">
{mode === 'create' ? 'Create Bot' : 'Update Bot Configuration'}
{backtest && ` from Backtest`}
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Basic Configuration */}
<div className="form-control">
<label className="label">
<span className="label-text">Bot Name</span>
</label>
<input
type="text"
className="input input-bordered"
value={formData.name}
onChange={(e) => handleInputChange('name', e.target.value)}
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Account</span>
</label>
<select
className="select select-bordered"
value={formData.accountName}
onChange={(e) => handleInputChange('accountName', e.target.value)}
>
{accounts?.map((account) => (
<option key={account.name} value={account.name}>
{account.name}
</option>
))}
</select>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Ticker</span>
</label>
<select
className="select select-bordered"
value={formData.ticker}
onChange={(e) => handleInputChange('ticker', e.target.value as Ticker)}
>
{Object.values(Ticker).map((ticker) => (
<option key={ticker} value={ticker}>
{ticker}
</option>
))}
</select>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Scenario</span>
</label>
<select
className="select select-bordered"
value={formData.scenarioName}
onChange={(e) => handleInputChange('scenarioName', e.target.value)}
>
{scenarios?.map((scenario) => (
<option key={scenario.name || ''} value={scenario.name || ''}>
{scenario.name}
</option>
))}
</select>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Timeframe</span>
</label>
<select
className="select select-bordered"
value={formData.timeframe}
onChange={(e) => handleInputChange('timeframe', e.target.value as Timeframe)}
>
{Object.values(Timeframe).map((timeframe) => (
<option key={timeframe} value={timeframe}>
{timeframe}
</option>
))}
</select>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Bot Type</span>
</label>
<select
className="select select-bordered"
value={formData.botType}
onChange={(e) => handleInputChange('botType', e.target.value as BotType)}
disabled={mode === 'update'} // Can't change bot type for existing bots
>
{Object.values(BotType).map((botType) => (
<option key={botType} value={botType}>
{botType}
</option>
))}
</select>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Trading Balance</span>
</label>
<input
type="number"
className="input input-bordered"
value={formData.botTradingBalance}
onChange={(e) => handleInputChange('botTradingBalance', parseFloat(e.target.value))}
min="1"
step="0.01"
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Cooldown Period (candles)</span>
</label>
<input
type="number"
className="input input-bordered"
value={formData.cooldownPeriod}
onChange={(e) => handleInputChange('cooldownPeriod', parseInt(e.target.value))}
min="1"
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Max Loss Streak</span>
</label>
<input
type="number"
className="input input-bordered"
value={formData.maxLossStreak}
onChange={(e) => handleInputChange('maxLossStreak', parseInt(e.target.value))}
min="0"
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Max Position Time (hours)</span>
</label>
<input
type="number"
className="input input-bordered"
value={formData.maxPositionTimeHours || ''}
onChange={(e) => handleInputChange('maxPositionTimeHours', e.target.value ? parseFloat(e.target.value) : null)}
min="0.1"
step="0.1"
placeholder="Optional"
/>
</div>
{/* Checkboxes */}
<div className="form-control">
<label className="label cursor-pointer">
<span className="label-text">Watch Only Mode</span>
<input
type="checkbox"
className="checkbox"
checked={formData.isForWatchingOnly}
onChange={(e) => handleInputChange('isForWatchingOnly', e.target.checked)}
/>
</label>
</div>
<div className="form-control">
<label className="label cursor-pointer">
<span className="label-text">Flip Only When In Profit</span>
<input
type="checkbox"
className="checkbox"
checked={formData.flipOnlyWhenInProfit}
onChange={(e) => handleInputChange('flipOnlyWhenInProfit', e.target.checked)}
/>
</label>
</div>
<div className="form-control">
<label className="label cursor-pointer">
<span className="label-text">Enable Position Flipping</span>
<input
type="checkbox"
className="checkbox"
checked={formData.flipPosition}
onChange={(e) => handleInputChange('flipPosition', e.target.checked)}
/>
</label>
</div>
<div className="form-control">
<label className="label cursor-pointer">
<span className="label-text">Close Early When Profitable</span>
<input
type="checkbox"
className="checkbox"
checked={formData.closeEarlyWhenProfitable}
onChange={(e) => handleInputChange('closeEarlyWhenProfitable', e.target.checked)}
disabled={!formData.maxPositionTimeHours}
/>
</label>
</div>
</div>
{/* Money Management Section */}
<div className="divider">Money Management</div>
<div className="form-control">
<label className="label cursor-pointer">
<span className="label-text">Use Custom Money Management</span>
<input
type="checkbox"
className="checkbox"
checked={formData.useCustomMoneyManagement}
onChange={(e) => handleInputChange('useCustomMoneyManagement', e.target.checked)}
/>
</label>
</div>
{formData.useCustomMoneyManagement ? (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4">
<div className="form-control">
<label className="label">
<span className="label-text">Stop Loss (%)</span>
</label>
<input
type="number"
className="input input-bordered"
value={formData.customStopLoss}
onChange={(e) => handleInputChange('customStopLoss', parseFloat(e.target.value))}
min="0.001"
max="1"
step="0.001"
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Take Profit (%)</span>
</label>
<input
type="number"
className="input input-bordered"
value={formData.customTakeProfit}
onChange={(e) => handleInputChange('customTakeProfit', parseFloat(e.target.value))}
min="0.001"
max="1"
step="0.001"
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Leverage</span>
</label>
<input
type="number"
className="input input-bordered"
value={formData.customLeverage}
onChange={(e) => handleInputChange('customLeverage', parseInt(e.target.value))}
min="1"
max="100"
/>
</div>
</div>
) : (
<div className="form-control mt-4">
<label className="label">
<span className="label-text">Money Management</span>
</label>
<select
className="select select-bordered"
value={formData.moneyManagementName}
onChange={(e) => handleInputChange('moneyManagementName', e.target.value)}
>
{moneyManagements?.map((mm) => (
<option key={mm.name} value={mm.name}>
{mm.name} (SL: {(mm.stopLoss * 100).toFixed(2)}%, TP: {(mm.takeProfit * 100).toFixed(2)}%)
</option>
))}
</select>
</div>
)}
{/* Validation Messages */}
{formData.closeEarlyWhenProfitable && !formData.maxPositionTimeHours && (
<div className="alert alert-warning mt-4">
<span>Close Early When Profitable requires Max Position Time to be set.</span>
</div>
)}
<div className="modal-action">
<button className="btn" onClick={onClose}>
Cancel
</button>
<button
className="btn btn-primary"
onClick={handleSubmit}
disabled={formData.closeEarlyWhenProfitable && !formData.maxPositionTimeHours}
>
{mode === 'create' ? 'Create Bot' : 'Update Bot'}
</button>
</div>
</div>
</div>
)
}
export default BotConfigModal

View File

@@ -1,13 +1,13 @@
import {ChevronDownIcon, ChevronRightIcon, EyeIcon, PlayIcon, TrashIcon} from '@heroicons/react/solid'
import {ChevronDownIcon, ChevronRightIcon, PlayIcon, TrashIcon} from '@heroicons/react/solid'
import React, {useEffect, useState} from 'react'
import {useQuery} from '@tanstack/react-query'
import useApiUrlStore from '../../../app/store/apiStore'
import type {Backtest, StartBotRequest, Ticker, TradingBotConfig} from '../../../generated/ManagingApi'
import {BacktestClient, BotClient, MoneyManagementClient} from '../../../generated/ManagingApi'
import type {Backtest} from '../../../generated/ManagingApi'
import {BacktestClient} from '../../../generated/ManagingApi'
import type {IBacktestCards} from '../../../global/type'
import {CardText, SelectColumnFilter, Table, Toast} from '../../mollecules'
import {BotNameModal} from '../index'
import {CardText, SelectColumnFilter, Table} from '../../mollecules'
import BotConfigModal from '../../mollecules/BotConfigModal/BotConfigModal'
import Toast from '../../mollecules/Toast/Toast'
import BacktestRowDetails from './backtestRowDetails'
@@ -27,95 +27,19 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
averageCooldown: 0,
medianCooldown: 0,
})
const [showBotNameModal, setShowBotNameModal] = useState(false)
const [isForWatchOnly, setIsForWatchOnly] = useState(false)
const [currentBacktest, setCurrentBacktest] = useState<Backtest | null>(null)
const [selectedMoneyManagement, setSelectedMoneyManagement] = useState<string>('')
// Bot configuration modal state
const [showBotConfigModal, setShowBotConfigModal] = useState(false)
const [selectedBacktest, setSelectedBacktest] = useState<Backtest | null>(null)
// Fetch money managements
const { data: moneyManagements } = useQuery({
queryFn: async () => {
const moneyManagementClient = new MoneyManagementClient({}, apiUrl)
return await moneyManagementClient.moneyManagement_GetMoneyManagements()
},
queryKey: ['moneyManagements'],
})
// Set the first money management as default when the data is loaded
useEffect(() => {
if (moneyManagements && moneyManagements.length > 0) {
setSelectedMoneyManagement(moneyManagements[0].name)
}
}, [moneyManagements])
async function runBot(botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string, initialTradingBalance: number) {
const t = new Toast('Bot is starting')
const client = new BotClient({}, apiUrl)
// Check if the money management name is "custom" or contains "custom"
const isCustomMoneyManagement =
!moneyManagementName ||
moneyManagementName.toLowerCase() === 'custom' ||
moneyManagementName.toLowerCase().includes('custom');
// Create TradingBotConfig from the backtest configuration
const tradingBotConfig: TradingBotConfig = {
accountName: backtest.config.accountName,
ticker: backtest.config.ticker,
scenarioName: backtest.config.scenarioName,
timeframe: backtest.config.timeframe,
botType: backtest.config.botType,
isForWatchingOnly: isForWatchOnly,
isForBacktest: false, // This is for running a live bot
cooldownPeriod: backtest.config.cooldownPeriod,
maxLossStreak: backtest.config.maxLossStreak,
maxPositionTimeHours: backtest.config.maxPositionTimeHours,
flipOnlyWhenInProfit: backtest.config.flipOnlyWhenInProfit,
flipPosition: backtest.config.flipPosition,
name: botName,
botTradingBalance: initialTradingBalance,
// Use the money management from backtest if it's custom, otherwise leave null and use moneyManagementName
moneyManagement: isCustomMoneyManagement ?
(backtest.config.moneyManagement || {
name: 'default',
leverage: 1,
stopLoss: 0.01,
takeProfit: 0.02,
timeframe: backtest.config.timeframe
}) :
backtest.config.moneyManagement, // Always provide a valid MoneyManagement object
closeEarlyWhenProfitable: backtest.config.closeEarlyWhenProfitable || false
};
const request: StartBotRequest = {
config: tradingBotConfig,
// Only use the money management name if it's not a custom money management
moneyManagementName: isCustomMoneyManagement ? undefined : moneyManagementName
}
await client
.bot_Start(request)
.then((botStatus: string) => {
t.update('info', 'Bot status: ' + botStatus)
})
.catch((err) => {
t.update('error', 'Error: ' + err)
})
const handleOpenBotConfigModal = (backtest: Backtest) => {
setSelectedBacktest(backtest)
setShowBotConfigModal(true)
}
const handleOpenBotNameModal = (backtest: Backtest, isForWatchOnly: boolean) => {
setCurrentBacktest(backtest)
setIsForWatchOnly(isForWatchOnly)
setShowBotNameModal(true)
}
const handleCloseBotNameModal = () => {
setShowBotNameModal(false)
}
const handleSubmitBotName = (botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string, initialTradingBalance: number) => {
runBot(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance)
setShowBotNameModal(false)
const handleCloseBotConfigModal = () => {
setShowBotConfigModal(false)
setSelectedBacktest(null)
}
async function deleteBacktest(id: string) {
@@ -292,27 +216,10 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
{
Cell: ({ cell }: any) => (
<>
<div className="tooltip" data-tip="Run in watch-only mode">
<div className="tooltip" data-tip="Create bot from backtest">
<button
data-value={cell.row.values.name}
onClick={() => handleOpenBotNameModal(cell.row.original as Backtest, true)}
>
<EyeIcon className="text-primary w-4"></EyeIcon>
</button>
</div>
</>
),
Header: '',
accessor: 'watcher',
disableFilters: true,
},
{
Cell: ({ cell }: any) => (
<>
<div className="tooltip" data-tip="Run bot">
<button
data-value={cell.row.values.name}
onClick={() => handleOpenBotNameModal(cell.row.original as Backtest, false)}
onClick={() => handleOpenBotConfigModal(cell.row.original as Backtest)}
>
<PlayIcon className="text-primary w-4"></PlayIcon>
</button>
@@ -475,8 +382,6 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
}
}, [list])
return (
<>
{isFetching ? (
@@ -528,18 +433,14 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
/>
)}
/>
{showBotNameModal && currentBacktest && moneyManagements && (
<BotNameModal
showModal={showBotNameModal}
onClose={handleCloseBotNameModal}
backtest={currentBacktest}
isForWatchOnly={isForWatchOnly}
onSubmitBotName={(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance) =>
handleSubmitBotName(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance)
}
moneyManagements={moneyManagements}
selectedMoneyManagement={selectedMoneyManagement}
setSelectedMoneyManagement={setSelectedMoneyManagement}
{/* Bot Configuration Modal */}
{selectedBacktest && (
<BotConfigModal
showModal={showBotConfigModal}
mode="create"
backtest={selectedBacktest}
onClose={handleCloseBotConfigModal}
/>
)}
</>

View File

@@ -653,7 +653,7 @@ export class BotClient extends AuthorizedApiBase {
return Promise.resolve<string>(null as any);
}
bot_GetActiveBots(): Promise<TradingBot[]> {
bot_GetActiveBots(): Promise<TradingBotResponse[]> {
let url_ = this.baseUrl + "/Bot";
url_ = url_.replace(/[?&]$/, "");
@@ -671,13 +671,13 @@ export class BotClient extends AuthorizedApiBase {
});
}
protected processBot_GetActiveBots(response: Response): Promise<TradingBot[]> {
protected processBot_GetActiveBots(response: Response): Promise<TradingBotResponse[]> {
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 TradingBot[];
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as TradingBotResponse[];
return result200;
});
} else if (status !== 200 && status !== 204) {
@@ -685,7 +685,7 @@ export class BotClient extends AuthorizedApiBase {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<TradingBot[]>(null as any);
return Promise.resolve<TradingBotResponse[]>(null as any);
}
bot_OpenPositionManually(request: OpenPositionManuallyRequest): Promise<Position> {
@@ -3118,25 +3118,16 @@ export interface StartBotRequest {
moneyManagementName?: string | null;
}
export interface TradingBot {
name: string;
export interface TradingBotResponse {
status: string;
signals: Signal[];
positions: Position[];
candles: Candle[];
winRate: number;
profitAndLoss: number;
timeframe: Timeframe;
ticker: Ticker;
scenario: string;
isForWatchingOnly: boolean;
botType: BotType;
accountName: string;
moneyManagement: MoneyManagement;
identifier: string;
agentName: string;
maxPositionTimeHours: number;
flipOnlyWhenInProfit: boolean;
config: TradingBotConfig;
}
export interface OpenPositionManuallyRequest {
@@ -3150,20 +3141,9 @@ export interface ClosePositionRequest {
}
export interface UpdateBotConfigRequest {
identifier?: string | null;
accountName?: string | null;
identifier: string;
config: TradingBotConfig;
moneyManagementName?: string | null;
ticker?: Ticker | null;
scenarioName?: string | null;
timeframe?: Timeframe | null;
isForWatchingOnly?: boolean | null;
botTradingBalance?: number | null;
cooldownPeriod?: number | null;
maxLossStreak?: number | null;
maxPositionTimeHours?: number | null;
flipOnlyWhenInProfit?: boolean | null;
flipPosition?: boolean | null;
name?: string | null;
}
export interface TickerInfos {

View File

@@ -1,12 +1,13 @@
import {ChartBarIcon, EyeIcon, PlayIcon, PlusCircleIcon, StopIcon, TrashIcon} from '@heroicons/react/solid'
import {ChartBarIcon, CogIcon, EyeIcon, PlayIcon, PlusCircleIcon, StopIcon, TrashIcon} from '@heroicons/react/solid'
import React, {useState} from 'react'
import useApiUrlStore from '../../app/store/apiStore'
import {CardPosition, CardSignal, CardText, Toast,} from '../../components/mollecules'
import ManualPositionModal from '../../components/mollecules/ManualPositionModal'
import TradesModal from '../../components/mollecules/TradesModal/TradesModal'
import BotConfigModal from '../../components/mollecules/BotConfigModal/BotConfigModal'
import {TradeChart} from '../../components/organism'
import type {BotType, MoneyManagement, TradingBot,} from '../../generated/ManagingApi'
import type {BotType, MoneyManagement, Position, TradingBotResponse} from '../../generated/ManagingApi'
import {BotClient} from '../../generated/ManagingApi'
import type {IBotList} from '../../global/type'
import MoneyManagementModal from '../settingsPage/moneymanagement/moneyManagementModal'
@@ -38,6 +39,12 @@ const BotList: React.FC<IBotList> = ({ list }) => {
const [selectedBotForManualPosition, setSelectedBotForManualPosition] = useState<string | null>(null)
const [showTradesModal, setShowTradesModal] = useState(false)
const [selectedBotForTrades, setSelectedBotForTrades] = useState<{ identifier: string; agentName: string } | null>(null)
const [showBotConfigModal, setShowBotConfigModal] = useState(false)
const [botConfigModalMode, setBotConfigModalMode] = useState<'create' | 'update'>('create')
const [selectedBotForUpdate, setSelectedBotForUpdate] = useState<{
identifier: string
config: any
} | null>(null)
function getIsForWatchingBadge(isForWatchingOnly: boolean, identifier: string) {
const classes =
@@ -188,9 +195,53 @@ const BotList: React.FC<IBotList> = ({ list }) => {
})
}
function getUpdateBotBadge(bot: TradingBotResponse) {
const classes = baseBadgeClass() + ' bg-warning'
return (
<button className={classes} onClick={() => openUpdateBotModal(bot)}>
<p className="text-primary-content flex">
<CogIcon width={15}></CogIcon>
</p>
</button>
)
}
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('create')
setSelectedBotForUpdate(null)
setShowBotConfigModal(true)
}
function openUpdateBotModal(bot: TradingBotResponse) {
setBotConfigModalMode('update')
setSelectedBotForUpdate({
identifier: bot.identifier,
config: bot.config
})
setShowBotConfigModal(true)
}
return (
<div className="flex flex-wrap m-4 -mx-4">
{list.map((bot: TradingBot, index) => (
<div className="w-full p-2 mb-4">
<div className="flex justify-end">
{getCreateBotBadge()}
</div>
</div>
{list.map((bot: TradingBotResponse, index) => (
<div
key={index.toString()}
className="sm:w-1 md:w-1/2 xl:w-1/2 w-full p-2"
@@ -209,10 +260,11 @@ const BotList: React.FC<IBotList> = ({ list }) => {
</figure>
<div className="card-body">
<h2 className="card-title text-sm">
{bot.ticker}
{getMoneyManagementBadge(bot.moneyManagement)}
{getIsForWatchingBadge(bot.isForWatchingOnly, bot.identifier)}
{getToggleBotStatusBadge(bot.status, bot.identifier, bot.botType)}
{bot.config.ticker}
{getMoneyManagementBadge(bot.config.moneyManagement)}
{getIsForWatchingBadge(bot.config.isForWatchingOnly, bot.identifier)}
{getToggleBotStatusBadge(bot.status, bot.identifier, bot.config.botType)}
{getUpdateBotBadge(bot)}
{getManualPositionBadge(bot.identifier)}
{getDeleteBadge(bot.identifier)}
</h2>
@@ -223,26 +275,26 @@ const BotList: React.FC<IBotList> = ({ list }) => {
<div>
<CardText
title="Scenario"
content={bot.scenario}
content={bot.config.scenarioName}
></CardText>
</div>
</div>
</div>
<div className="columns-2">
<CardSignal signals={bot.signals}></CardSignal>
<CardText title="Type" content={bot.botType}></CardText>
<CardText title="Type" content={bot.config.botType}></CardText>
</div>
<div className="columns-2">
<CardPosition
positivePosition={true}
positions={bot.positions.filter((p) => {
positions={bot.positions.filter((p: Position) => {
const realized = p.profitAndLoss?.realized ?? 0
return realized > 0 ? p : null
})}
></CardPosition>
<CardPosition
positivePosition={false}
positions={bot.positions.filter((p) => {
positions={bot.positions.filter((p: Position) => {
const realized = p.profitAndLoss?.realized ?? 0
return realized <= 0 ? p : null
})}
@@ -287,6 +339,15 @@ const BotList: React.FC<IBotList> = ({ list }) => {
setSelectedBotForTrades(null)
}}
/>
<BotConfigModal
showModal={showBotConfigModal}
mode={botConfigModalMode}
existingBot={selectedBotForUpdate || undefined}
onClose={() => {
setShowBotConfigModal(false)
setSelectedBotForUpdate(null)
}}
/>
</div>
)
}