Add new parameters
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { IModalProps } from '../../../global/type'
|
||||
import type {IModalProps} from '../../../global/type'
|
||||
|
||||
import ModalHeader from './ModalHeader'
|
||||
|
||||
@@ -16,7 +16,7 @@ const Modal: React.FC<IModalProps> = ({
|
||||
{showModal ? (
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="modal modal-bottom sm:modal-middle modal-open">
|
||||
<div className="modal-box">
|
||||
<div className="modal-box !max-w-4xl !w-11/12">
|
||||
<ModalHeader
|
||||
titleHeader={titleHeader}
|
||||
onClose={onClose}
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import {DotsVerticalIcon, TrashIcon} from '@heroicons/react/solid'
|
||||
import moment from 'moment'
|
||||
import React from 'react'
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import type {Backtest, MoneyManagement, StartBotRequest, Ticker,} from '../../../generated/ManagingApi'
|
||||
import {BacktestClient, BotClient, BotType,} from '../../../generated/ManagingApi'
|
||||
import type {
|
||||
Backtest,
|
||||
MoneyManagement,
|
||||
RunBacktestRequest,
|
||||
StartBotRequest,
|
||||
Ticker,
|
||||
TradingBotConfig
|
||||
} from '../../../generated/ManagingApi'
|
||||
import {BacktestClient, BotClient, MoneyManagementClient} from '../../../generated/ManagingApi'
|
||||
import type {IBacktestCards} from '../../../global/type'
|
||||
import MoneyManagementModal from '../../../pages/settingsPage/moneymanagement/moneyManagementModal'
|
||||
import {CardPosition, CardText, Toast} from '../../mollecules'
|
||||
import CardPositionItem from '../Trading/CardPositionItem'
|
||||
import TradeChart from '../Trading/TradeChart/TradeChart'
|
||||
import {BotNameModal} from '../index'
|
||||
|
||||
function baseBadgeClass(isOutlined = false) {
|
||||
let classes = 'text-xs badge '
|
||||
@@ -50,33 +59,81 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
|
||||
React.useState(false)
|
||||
const [selectedMoneyManagement, setSelectedMoneyManagement] =
|
||||
React.useState<MoneyManagement>()
|
||||
const [showBotNameModal, setShowBotNameModal] = useState(false)
|
||||
const [isForWatchOnly, setIsForWatchOnly] = useState(false)
|
||||
const [currentBacktest, setCurrentBacktest] = useState<Backtest | null>(null)
|
||||
const [selectedMoneyManagementName, setSelectedMoneyManagementName] = useState<string>('')
|
||||
|
||||
async function runBot(backtest: Backtest, isForWatchOnly: boolean) {
|
||||
// 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) {
|
||||
setSelectedMoneyManagementName(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');
|
||||
|
||||
const request: StartBotRequest = {
|
||||
accountName: backtest.config.accountName,
|
||||
name: backtest.config.ticker + '-' + backtest.config.timeframe?.toString(),
|
||||
botType: BotType.ScalpingBot,
|
||||
name: botName,
|
||||
botType: backtest.config.botType,
|
||||
isForWatchOnly: isForWatchOnly,
|
||||
moneyManagementName: backtest.config.moneyManagement?.name,
|
||||
// Only use the money management name if it's not a custom money management, otherwise use optimized
|
||||
moneyManagementName: isCustomMoneyManagement ?
|
||||
(backtest.optimizedMoneyManagement?.name || backtest.config.moneyManagement?.name) :
|
||||
moneyManagementName,
|
||||
scenario: backtest.config.scenarioName,
|
||||
ticker: backtest.config.ticker as Ticker,
|
||||
timeframe: backtest.config.timeframe,
|
||||
initialTradingBalance: 1000,
|
||||
initialTradingBalance: initialTradingBalance,
|
||||
cooldownPeriod: backtest.config.cooldownPeriod,
|
||||
maxLossStreak: backtest.config.maxLossStreak,
|
||||
maxPositionTimeHours: backtest.config.maxPositionTimeHours,
|
||||
flipOnlyWhenInProfit: backtest.config.flipOnlyWhenInProfit
|
||||
}
|
||||
|
||||
await client
|
||||
.bot_Start(request)
|
||||
.then((botStatus: string) => {
|
||||
t.update('info', 'Bot status :' + botStatus)
|
||||
t.update('info', 'Bot status: ' + botStatus)
|
||||
})
|
||||
.catch((err) => {
|
||||
t.update('error', 'Error :' + err)
|
||||
t.update('error', 'Error: ' + err)
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
async function runOptimizedBacktest(backtest: Backtest) {
|
||||
const t = new Toast('Optimized backtest is running')
|
||||
const client = new BacktestClient({}, apiUrl)
|
||||
@@ -85,23 +142,26 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
|
||||
const startDate = backtest.candles[0].date
|
||||
const endDate = backtest.candles[backtest.candles.length - 1].date
|
||||
|
||||
// Create optimized backtest config
|
||||
const optimizedConfig: TradingBotConfig = {
|
||||
...backtest.config,
|
||||
name: `${backtest.config.ticker}-${backtest.config.scenarioName}-Optimized`,
|
||||
moneyManagement: backtest.optimizedMoneyManagement || backtest.config.moneyManagement
|
||||
}
|
||||
|
||||
const request: RunBacktestRequest = {
|
||||
config: optimizedConfig,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
balance: backtest.walletBalances[0].value,
|
||||
watchOnly: false,
|
||||
save: false,
|
||||
moneyManagementName: undefined, // We're passing the moneyManagement object directly
|
||||
moneyManagement: optimizedConfig.moneyManagement
|
||||
}
|
||||
|
||||
await client
|
||||
.backtest_Run(
|
||||
backtest.config.accountName,
|
||||
backtest.config.botType,
|
||||
backtest.config.ticker as Ticker,
|
||||
backtest.config.scenarioName,
|
||||
backtest.config.timeframe,
|
||||
false, // watchOnly
|
||||
backtest.walletBalances[0].value, // balance
|
||||
'', // moneyManagementName (empty since we're passing the optimized moneyManagement object)
|
||||
startDate, // startDate
|
||||
endDate, // endDate
|
||||
false, // save
|
||||
backtest.config.cooldownPeriod,
|
||||
backtest.config.maxLossStreak,
|
||||
backtest.config.moneyManagement as MoneyManagement, // moneyManagement object
|
||||
)
|
||||
.backtest_Run(request)
|
||||
.then((backtest: Backtest) => {
|
||||
t.update('success', `${backtest.config.ticker} Backtest Succeeded`)
|
||||
setBacktests((arr) => [...arr, backtest])
|
||||
@@ -162,7 +222,7 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
|
||||
<li>
|
||||
<button
|
||||
className="text-xs"
|
||||
onClick={() => runBot(backtest, false)}
|
||||
onClick={() => handleOpenBotNameModal(backtest, false)}
|
||||
>
|
||||
Run bot
|
||||
</button>
|
||||
@@ -170,7 +230,7 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
|
||||
<li>
|
||||
<button
|
||||
className="text-xs"
|
||||
onClick={() => runBot(backtest, true)}
|
||||
onClick={() => handleOpenBotNameModal(backtest, true)}
|
||||
>
|
||||
Run watcher
|
||||
</button>
|
||||
@@ -303,6 +363,21 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
|
||||
moneyManagement={selectedMoneyManagement}
|
||||
onClose={() => setShowMoneyManagementModal(false)}
|
||||
/>
|
||||
|
||||
{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={selectedMoneyManagementName}
|
||||
setSelectedMoneyManagement={setSelectedMoneyManagementName}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,15 +4,17 @@ import {type SubmitHandler, useForm} from 'react-hook-form'
|
||||
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import {
|
||||
AccountClient,
|
||||
BacktestClient,
|
||||
BotType,
|
||||
DataClient,
|
||||
MoneyManagement,
|
||||
MoneyManagementClient,
|
||||
ScenarioClient,
|
||||
Ticker,
|
||||
Timeframe,
|
||||
AccountClient,
|
||||
BacktestClient,
|
||||
BotType,
|
||||
DataClient,
|
||||
MoneyManagement,
|
||||
MoneyManagementClient,
|
||||
RunBacktestRequest,
|
||||
ScenarioClient,
|
||||
Ticker,
|
||||
Timeframe,
|
||||
TradingBotConfig,
|
||||
} from '../../../generated/ManagingApi'
|
||||
import type {BacktestModalProps, IBacktestsFormInput,} from '../../../global/type'
|
||||
import {Loader, Slider} from '../../atoms'
|
||||
@@ -42,8 +44,10 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
defaultValues: {
|
||||
startDate: defaultStartDateString,
|
||||
endDate: defaultEndDateString,
|
||||
cooldownPeriod: 1, // Default cooldown period of 1 minute
|
||||
maxLossStreak: 0 // Default max loss streak of 0 (no limit)
|
||||
cooldownPeriod: 10, // Default cooldown period of 10 minutes
|
||||
maxLossStreak: 0, // Default max loss streak of 0 (no limit)
|
||||
maxPositionTimeHours: null, // Default to null (disabled)
|
||||
flipOnlyWhenInProfit: true // Default to true
|
||||
}
|
||||
});
|
||||
const [selectedAccount, setSelectedAccount] = useState<string>('')
|
||||
@@ -100,28 +104,48 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
loopCount: number
|
||||
): Promise<void> {
|
||||
const t = new Toast(ticker + ' is running')
|
||||
// Use the name of the money management strategy if custom is not provided
|
||||
const moneyManagementName = customMoneyManagement ? undefined : selectedMoneyManagement
|
||||
|
||||
console.log(customMoneyManagement)
|
||||
|
||||
try {
|
||||
const backtest = await backtestClient.backtest_Run(
|
||||
form.accountName,
|
||||
form.botType,
|
||||
ticker as Ticker,
|
||||
scenarioName,
|
||||
form.timeframe,
|
||||
false, // watchOnly
|
||||
balance,
|
||||
moneyManagementName,
|
||||
new Date(form.startDate), // startDate
|
||||
new Date(form.endDate), // endDate
|
||||
form.save,
|
||||
form.cooldownPeriod, // Use the cooldown period from the form
|
||||
form.maxLossStreak, // Add the max loss streak parameter
|
||||
customMoneyManagement
|
||||
);
|
||||
// Create the TradingBotConfig
|
||||
const tradingBotConfig: TradingBotConfig = {
|
||||
accountName: form.accountName,
|
||||
ticker: ticker as Ticker,
|
||||
scenarioName: scenarioName,
|
||||
timeframe: form.timeframe,
|
||||
botType: form.botType,
|
||||
isForWatchingOnly: false, // Always false for backtests
|
||||
isForBacktest: true, // Always true for backtests
|
||||
cooldownPeriod: form.cooldownPeriod || 1,
|
||||
maxLossStreak: form.maxLossStreak || 0,
|
||||
maxPositionTimeHours: form.maxPositionTimeHours || null,
|
||||
flipOnlyWhenInProfit: form.flipOnlyWhenInProfit ?? true,
|
||||
flipPosition: form.botType === BotType.FlippingBot, // Set based on bot type
|
||||
name: `Backtest-${scenarioName}-${ticker}-${new Date().toISOString()}`,
|
||||
botTradingBalance: 0, // Will be set in the request
|
||||
moneyManagement: customMoneyManagement || moneyManagements?.find(m => m.name === selectedMoneyManagement) || moneyManagements?.[0] || {
|
||||
name: 'placeholder',
|
||||
leverage: 1,
|
||||
stopLoss: 0.01,
|
||||
takeProfit: 0.02,
|
||||
timeframe: form.timeframe
|
||||
}
|
||||
};
|
||||
|
||||
// Create the RunBacktestRequest
|
||||
const request: RunBacktestRequest = {
|
||||
config: tradingBotConfig,
|
||||
startDate: new Date(form.startDate),
|
||||
endDate: new Date(form.endDate),
|
||||
balance: balance,
|
||||
watchOnly: false,
|
||||
save: form.save || false,
|
||||
moneyManagementName: customMoneyManagement ? undefined : selectedMoneyManagement,
|
||||
moneyManagement: customMoneyManagement
|
||||
};
|
||||
|
||||
const backtest = await backtestClient.backtest_Run(request);
|
||||
|
||||
t.update('success', `${backtest.config.ticker} Backtest Succeeded`)
|
||||
setBacktests((arr) => [...arr, backtest])
|
||||
@@ -228,64 +252,79 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
titleHeader="Run Backtest"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<FormInput label="Account" htmlFor="accountName">
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
{...register('accountName', {
|
||||
onChange(e) {
|
||||
setSelectedAccountEvent(e)
|
||||
},
|
||||
})}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Select an account
|
||||
</option>
|
||||
{accounts.map((item) => (
|
||||
<option key={item.name} value={item.name}>
|
||||
{item.name}
|
||||
{/* First Row: Account & Timeframe */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormInput label="Account" htmlFor="accountName">
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
{...register('accountName', {
|
||||
onChange(e) {
|
||||
setSelectedAccountEvent(e)
|
||||
},
|
||||
})}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Select an account
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
{accounts.map((item) => (
|
||||
<option key={item.name} value={item.name}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
|
||||
|
||||
<FormInput label="Timeframe" htmlFor="timeframe">
|
||||
<select
|
||||
className="select w-full max-w-xs"
|
||||
{...register('timeframe', {
|
||||
onChange(event) {
|
||||
setSelectedTimeframeEvent(event)
|
||||
},
|
||||
})}
|
||||
value={selectedTimeframe}
|
||||
>
|
||||
{Object.keys(Timeframe).map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
<FormInput label="Timeframe" htmlFor="timeframe">
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
{...register('timeframe', {
|
||||
onChange(event) {
|
||||
setSelectedTimeframeEvent(event)
|
||||
},
|
||||
})}
|
||||
value={selectedTimeframe}
|
||||
>
|
||||
{Object.keys(Timeframe).map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
<FormInput label="Money Management" htmlFor="moneyManagement">
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
{...register('moneyManagement', {
|
||||
onChange(event) {
|
||||
onMoneyManagementChange(event)
|
||||
},
|
||||
})}
|
||||
>
|
||||
{moneyManagements.map((item) => (
|
||||
<option key={item.name} value={item.name}>
|
||||
{item.name}
|
||||
{/* Second Row: Money Management & Bot Type */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormInput label="Money Management" htmlFor="moneyManagement">
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
{...register('moneyManagement', {
|
||||
onChange(event) {
|
||||
onMoneyManagementChange(event)
|
||||
},
|
||||
})}
|
||||
>
|
||||
{moneyManagements.map((item) => (
|
||||
<option key={item.name} value={item.name}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
<option key="custom" value="custom">
|
||||
Custom
|
||||
</option>
|
||||
))}
|
||||
<option key="custom" value="custom">
|
||||
Custom
|
||||
</option>
|
||||
</select>
|
||||
</FormInput>
|
||||
</select>
|
||||
</FormInput>
|
||||
|
||||
<FormInput label="Bot Type" htmlFor="botType">
|
||||
<select className="select select-bordered w-full" {...register('botType')}>
|
||||
{[BotType.ScalpingBot, BotType.FlippingBot].map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
{/* Losing streak info */}
|
||||
{(() => {
|
||||
@@ -314,55 +353,48 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
})()}
|
||||
|
||||
{showCustomMoneyManagement && (
|
||||
<div className="mt-6">
|
||||
<CustomMoneyManagement
|
||||
onCreateMoneyManagement={setCustomMoneyManagement}
|
||||
timeframe={selectedTimeframe || Timeframe.FiveMinutes}
|
||||
showCustomMoneyManagement={showCustomMoneyManagement}
|
||||
></CustomMoneyManagement>
|
||||
<div className="mt-6">
|
||||
<CustomMoneyManagement
|
||||
onCreateMoneyManagement={setCustomMoneyManagement}
|
||||
timeframe={selectedTimeframe || Timeframe.FiveMinutes}
|
||||
showCustomMoneyManagement={showCustomMoneyManagement}
|
||||
></CustomMoneyManagement>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Third Row: Scenario & Tickers (full width since they need more space) */}
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<FormInput label="Scenario" htmlFor="scenarioName">
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
{...register('scenarioName')}
|
||||
>
|
||||
<option value="" disabled>Select a scenario</option>
|
||||
{scenarios.map((item) => (
|
||||
<option key={item.name || 'unnamed'} value={item.name || ''}>
|
||||
{item.name || 'Unnamed Scenario'}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
|
||||
<FormInput label="Tickers" htmlFor="tickers">
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
multiple
|
||||
{...register('tickers')}
|
||||
>
|
||||
{tickers?.map((item) => (
|
||||
<option key={item.ticker} value={item.ticker}>
|
||||
<img src={item.imageUrl || ''} alt={item.ticker} className="w-4 h-4 mr-2" />
|
||||
{item.ticker}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FormInput label="Type" htmlFor="botType">
|
||||
<select className="select w-full max-w-xs" {...register('botType')}>
|
||||
{[BotType.ScalpingBot, BotType.FlippingBot].map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
|
||||
|
||||
<FormInput label="Scenario" htmlFor="scenarioName">
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
{...register('scenarioName')}
|
||||
>
|
||||
<option value="" disabled>Select a scenario</option>
|
||||
{scenarios.map((item) => (
|
||||
<option key={item.name || 'unnamed'} value={item.name || ''}>
|
||||
{item.name || 'Unnamed Scenario'}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
|
||||
<FormInput label="Tickers" htmlFor="tickers">
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
multiple
|
||||
{...register('tickers')}
|
||||
>
|
||||
{tickers?.map((item) => (
|
||||
<option key={item.ticker} value={item.ticker}>
|
||||
<img src={item.imageUrl || ''} alt={item.ticker} className="w-4 h-4 mr-2" />
|
||||
{item.ticker}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
|
||||
{/* Fourth Row: Balance & Cooldown Period */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormInput label="Balance" htmlFor="balance">
|
||||
<input
|
||||
@@ -384,6 +416,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
{/* Fifth Row: Max Loss Streak & Max Position Time */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormInput label="Max Loss Streak" htmlFor="maxLossStreak">
|
||||
<input
|
||||
@@ -395,6 +428,34 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
/>
|
||||
</FormInput>
|
||||
|
||||
<FormInput label="Max Position Time (hours)" htmlFor="maxPositionTimeHours">
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered w-full"
|
||||
min="0"
|
||||
step="0.5"
|
||||
placeholder="Leave empty to disable"
|
||||
{...register('maxPositionTimeHours', { valueAsNumber: true })}
|
||||
/>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
Leave empty to disable time-based position closure
|
||||
</div>
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
{/* Sixth Row: Flip Only When In Profit & Save */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormInput label="Flip Only When In Profit" htmlFor="flipOnlyWhenInProfit">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
{...register('flipOnlyWhenInProfit')}
|
||||
/>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
If enabled, positions will only flip when current position is profitable
|
||||
</div>
|
||||
</FormInput>
|
||||
|
||||
<FormInput label="Save" htmlFor="save">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -404,6 +465,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
{/* Seventh Row: Start Date & End Date */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormInput label="Start Date" htmlFor="startDate">
|
||||
<input
|
||||
@@ -430,6 +492,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
{/* Loop Slider (if enabled) */}
|
||||
{showLoopSlider && (
|
||||
<FormInput label="Loop" htmlFor="loop">
|
||||
<Slider
|
||||
@@ -443,7 +506,6 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="modal-action">
|
||||
<button type="submit" className="btn">
|
||||
Run
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {Backtest, StartBotRequest, Ticker,} from '../../../generated/Managi
|
||||
import {BacktestClient, BotClient, MoneyManagementClient} from '../../../generated/ManagingApi'
|
||||
import type {IBacktestCards} from '../../../global/type'
|
||||
import {CardText, SelectColumnFilter, Table, Toast} from '../../mollecules'
|
||||
import BotNameModal from '../../mollecules/Modal/BotNameModal'
|
||||
import {BotNameModal} from '../index'
|
||||
|
||||
import BacktestRowDetails from './backtestRowDetails'
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ import React, {useEffect, useState} from 'react'
|
||||
|
||||
import type {Backtest, MoneyManagement} from '../../../generated/ManagingApi'
|
||||
import type {IModalProps} from '../../../global/type'
|
||||
|
||||
import Modal from './Modal'
|
||||
import {Modal} from '../../mollecules'
|
||||
|
||||
interface IBotNameModalProps extends IModalProps {
|
||||
backtest: Backtest
|
||||
@@ -30,7 +29,7 @@ const BotNameModal: React.FC<IBotNameModalProps> = ({
|
||||
// Initialize botName when backtest changes
|
||||
useEffect(() => {
|
||||
if (backtest) {
|
||||
setBotName(`${backtest.ticker}-${backtest.timeframe}`)
|
||||
setBotName(`${backtest.config.ticker}-${backtest.config.timeframe}`)
|
||||
}
|
||||
}, [backtest])
|
||||
|
||||
@@ -46,42 +46,44 @@ const CustomMoneyManagement: React.FC<ICustomMoneyManagement> = ({
|
||||
Custom MoneyManagement
|
||||
</div>
|
||||
<div className="collapse-content">
|
||||
<FormInput label="Leverage" htmlFor="leverage" inline={false}>
|
||||
<input
|
||||
id="leverage"
|
||||
value={leverage}
|
||||
max="50"
|
||||
min="1"
|
||||
step="1"
|
||||
onChange={(e: any) => setLeverage(e.target.value)}
|
||||
type='number'
|
||||
className='input input-bordered'
|
||||
></input>
|
||||
</FormInput>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<FormInput label="Leverage" htmlFor="leverage" inline={false}>
|
||||
<input
|
||||
id="leverage"
|
||||
value={leverage}
|
||||
max="50"
|
||||
min="1"
|
||||
step="1"
|
||||
onChange={(e: any) => setLeverage(e.target.value)}
|
||||
type='number'
|
||||
className='input input-bordered w-full'
|
||||
></input>
|
||||
</FormInput>
|
||||
|
||||
<FormInput label="TP %" htmlFor="takeProfit" inline={false}>
|
||||
<input
|
||||
id="takeProfit"
|
||||
value={takeProfit}
|
||||
onChange={(e: any) => setTakeProfit(e.target.value)}
|
||||
step="0.01"
|
||||
max="100"
|
||||
type='number'
|
||||
className='input input-bordered'
|
||||
></input>
|
||||
</FormInput>
|
||||
<FormInput label="TP %" htmlFor="takeProfit" inline={false}>
|
||||
<input
|
||||
id="takeProfit"
|
||||
value={takeProfit}
|
||||
onChange={(e: any) => setTakeProfit(e.target.value)}
|
||||
step="0.01"
|
||||
max="100"
|
||||
type='number'
|
||||
className='input input-bordered w-full'
|
||||
></input>
|
||||
</FormInput>
|
||||
|
||||
<FormInput label="SL %" htmlFor="stopLoss" inline={false}>
|
||||
<input
|
||||
id="stopLoss"
|
||||
value={stopLoss}
|
||||
onChange={(e: any) => setStopLoss(e.target.value)}
|
||||
step="0.01"
|
||||
max="100"
|
||||
type='number'
|
||||
className='input input-bordered'
|
||||
></input>
|
||||
</FormInput>
|
||||
<FormInput label="SL %" htmlFor="stopLoss" inline={false}>
|
||||
<input
|
||||
id="stopLoss"
|
||||
value={stopLoss}
|
||||
onChange={(e: any) => setStopLoss(e.target.value)}
|
||||
step="0.01"
|
||||
max="100"
|
||||
type='number'
|
||||
className='input input-bordered w-full'
|
||||
></input>
|
||||
</FormInput>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -9,3 +9,4 @@ export { default as StatusBadge } from './StatusBadge/StatusBadge'
|
||||
export { default as PositionsList } from './Positions/PositionList'
|
||||
export { default as WorkflowCanvas } from './Workflow/workflowCanvas'
|
||||
export { default as ScenarioModal } from './ScenarioModal'
|
||||
export { default as BotNameModal } from './BotNameModal/BotNameModal'
|
||||
|
||||
@@ -337,57 +337,11 @@ export class BacktestClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<Backtest>(null as any);
|
||||
}
|
||||
|
||||
backtest_Run(accountName: string | null | undefined, botType: BotType | undefined, ticker: Ticker | undefined, scenarioName: string | null | undefined, timeframe: Timeframe | undefined, watchOnly: boolean | undefined, balance: number | undefined, moneyManagementName: string | null | undefined, startDate: Date | undefined, endDate: Date | undefined, save: boolean | undefined, cooldownPeriod: number | undefined, maxLossStreak: number | undefined, moneyManagement: MoneyManagement | undefined): Promise<Backtest> {
|
||||
let url_ = this.baseUrl + "/Backtest/Run?";
|
||||
if (accountName !== undefined && accountName !== null)
|
||||
url_ += "accountName=" + encodeURIComponent("" + accountName) + "&";
|
||||
if (botType === null)
|
||||
throw new Error("The parameter 'botType' cannot be null.");
|
||||
else if (botType !== undefined)
|
||||
url_ += "botType=" + encodeURIComponent("" + botType) + "&";
|
||||
if (ticker === null)
|
||||
throw new Error("The parameter 'ticker' cannot be null.");
|
||||
else if (ticker !== undefined)
|
||||
url_ += "ticker=" + encodeURIComponent("" + ticker) + "&";
|
||||
if (scenarioName !== undefined && scenarioName !== null)
|
||||
url_ += "scenarioName=" + encodeURIComponent("" + scenarioName) + "&";
|
||||
if (timeframe === null)
|
||||
throw new Error("The parameter 'timeframe' cannot be null.");
|
||||
else if (timeframe !== undefined)
|
||||
url_ += "timeframe=" + encodeURIComponent("" + timeframe) + "&";
|
||||
if (watchOnly === null)
|
||||
throw new Error("The parameter 'watchOnly' cannot be null.");
|
||||
else if (watchOnly !== undefined)
|
||||
url_ += "watchOnly=" + encodeURIComponent("" + watchOnly) + "&";
|
||||
if (balance === null)
|
||||
throw new Error("The parameter 'balance' cannot be null.");
|
||||
else if (balance !== undefined)
|
||||
url_ += "balance=" + encodeURIComponent("" + balance) + "&";
|
||||
if (moneyManagementName !== undefined && moneyManagementName !== null)
|
||||
url_ += "moneyManagementName=" + encodeURIComponent("" + moneyManagementName) + "&";
|
||||
if (startDate === null)
|
||||
throw new Error("The parameter 'startDate' cannot be null.");
|
||||
else if (startDate !== undefined)
|
||||
url_ += "startDate=" + encodeURIComponent(startDate ? "" + startDate.toISOString() : "") + "&";
|
||||
if (endDate === null)
|
||||
throw new Error("The parameter 'endDate' cannot be null.");
|
||||
else if (endDate !== undefined)
|
||||
url_ += "endDate=" + encodeURIComponent(endDate ? "" + endDate.toISOString() : "") + "&";
|
||||
if (save === null)
|
||||
throw new Error("The parameter 'save' cannot be null.");
|
||||
else if (save !== undefined)
|
||||
url_ += "save=" + encodeURIComponent("" + save) + "&";
|
||||
if (cooldownPeriod === null)
|
||||
throw new Error("The parameter 'cooldownPeriod' cannot be null.");
|
||||
else if (cooldownPeriod !== undefined)
|
||||
url_ += "cooldownPeriod=" + encodeURIComponent("" + cooldownPeriod) + "&";
|
||||
if (maxLossStreak === null)
|
||||
throw new Error("The parameter 'maxLossStreak' cannot be null.");
|
||||
else if (maxLossStreak !== undefined)
|
||||
url_ += "maxLossStreak=" + encodeURIComponent("" + maxLossStreak) + "&";
|
||||
backtest_Run(request: RunBacktestRequest): Promise<Backtest> {
|
||||
let url_ = this.baseUrl + "/Backtest/Run";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
const content_ = JSON.stringify(moneyManagement);
|
||||
const content_ = JSON.stringify(request);
|
||||
|
||||
let options_: RequestInit = {
|
||||
body: content_,
|
||||
@@ -811,6 +765,45 @@ export class BotClient extends AuthorizedApiBase {
|
||||
}
|
||||
return Promise.resolve<Position>(null as any);
|
||||
}
|
||||
|
||||
bot_UpdateBotConfig(request: UpdateBotConfigRequest): Promise<string> {
|
||||
let url_ = this.baseUrl + "/Bot/UpdateConfig";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
const content_ = JSON.stringify(request);
|
||||
|
||||
let options_: RequestInit = {
|
||||
body: content_,
|
||||
method: "PUT",
|
||||
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_UpdateBotConfig(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBot_UpdateBotConfig(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);
|
||||
}
|
||||
}
|
||||
|
||||
export class DataClient extends AuthorizedApiBase {
|
||||
@@ -2705,6 +2698,8 @@ export interface TradingBotConfig {
|
||||
maxLossStreak: number;
|
||||
flipPosition: boolean;
|
||||
name: string;
|
||||
maxPositionTimeHours?: number | null;
|
||||
flipOnlyWhenInProfit: boolean;
|
||||
}
|
||||
|
||||
export interface MoneyManagement {
|
||||
@@ -3106,6 +3101,17 @@ export interface SuperTrendResult extends ResultBase {
|
||||
lowerBand?: number | null;
|
||||
}
|
||||
|
||||
export interface RunBacktestRequest {
|
||||
config?: TradingBotConfig | null;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
balance?: number;
|
||||
watchOnly?: boolean;
|
||||
save?: boolean;
|
||||
moneyManagementName?: string | null;
|
||||
moneyManagement?: MoneyManagement | null;
|
||||
}
|
||||
|
||||
export interface StartBotRequest {
|
||||
botType?: BotType;
|
||||
identifier?: string | null;
|
||||
@@ -3119,6 +3125,8 @@ export interface StartBotRequest {
|
||||
cooldownPeriod?: number;
|
||||
maxLossStreak?: number;
|
||||
name?: string | null;
|
||||
maxPositionTimeHours?: number | null;
|
||||
flipOnlyWhenInProfit?: boolean;
|
||||
}
|
||||
|
||||
export interface TradingBot {
|
||||
@@ -3138,6 +3146,8 @@ export interface TradingBot {
|
||||
moneyManagement: MoneyManagement;
|
||||
identifier: string;
|
||||
agentName: string;
|
||||
maxPositionTimeHours: number;
|
||||
flipOnlyWhenInProfit: boolean;
|
||||
}
|
||||
|
||||
export interface OpenPositionManuallyRequest {
|
||||
@@ -3150,6 +3160,23 @@ export interface ClosePositionRequest {
|
||||
positionId?: string | null;
|
||||
}
|
||||
|
||||
export interface UpdateBotConfigRequest {
|
||||
identifier?: string | null;
|
||||
accountName?: string | null;
|
||||
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 {
|
||||
ticker?: Ticker;
|
||||
imageUrl?: string | null;
|
||||
|
||||
@@ -114,6 +114,8 @@ export type IBacktestsFormInput = {
|
||||
endDate: string
|
||||
cooldownPeriod: number
|
||||
maxLossStreak: number
|
||||
maxPositionTimeHours?: number | null
|
||||
flipOnlyWhenInProfit?: boolean
|
||||
}
|
||||
|
||||
export type IBacktestCards = {
|
||||
|
||||
Reference in New Issue
Block a user