Add Genetic workers
This commit is contained in:
@@ -536,6 +536,161 @@ export class BacktestClient extends AuthorizedApiBase {
|
||||
}
|
||||
return Promise.resolve<Backtest>(null as any);
|
||||
}
|
||||
|
||||
backtest_RunGenetic(request: RunGeneticRequest): Promise<GeneticRequest> {
|
||||
let url_ = this.baseUrl + "/Backtest/Genetic";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
const content_ = JSON.stringify(request);
|
||||
|
||||
let options_: RequestInit = {
|
||||
body: content_,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processBacktest_RunGenetic(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBacktest_RunGenetic(response: Response): Promise<GeneticRequest> {
|
||||
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 GeneticRequest;
|
||||
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<GeneticRequest>(null as any);
|
||||
}
|
||||
|
||||
backtest_GetGeneticRequests(): Promise<GeneticRequest[]> {
|
||||
let url_ = this.baseUrl + "/Backtest/Genetic";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processBacktest_GetGeneticRequests(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBacktest_GetGeneticRequests(response: Response): Promise<GeneticRequest[]> {
|
||||
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 GeneticRequest[];
|
||||
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<GeneticRequest[]>(null as any);
|
||||
}
|
||||
|
||||
backtest_GetGeneticRequest(id: string): Promise<GeneticRequest> {
|
||||
let url_ = this.baseUrl + "/Backtest/Genetic/{id}";
|
||||
if (id === undefined || id === null)
|
||||
throw new Error("The parameter 'id' must be defined.");
|
||||
url_ = url_.replace("{id}", encodeURIComponent("" + id));
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processBacktest_GetGeneticRequest(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBacktest_GetGeneticRequest(response: Response): Promise<GeneticRequest> {
|
||||
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 GeneticRequest;
|
||||
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<GeneticRequest>(null as any);
|
||||
}
|
||||
|
||||
backtest_DeleteGeneticRequest(id: string): Promise<FileResponse> {
|
||||
let url_ = this.baseUrl + "/Backtest/Genetic/{id}";
|
||||
if (id === undefined || id === null)
|
||||
throw new Error("The parameter 'id' must be defined.");
|
||||
url_ = url_.replace("{id}", encodeURIComponent("" + id));
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Accept": "application/octet-stream"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processBacktest_DeleteGeneticRequest(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBacktest_DeleteGeneticRequest(response: Response): Promise<FileResponse> {
|
||||
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 || status === 206) {
|
||||
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
|
||||
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
|
||||
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
|
||||
if (fileName) {
|
||||
fileName = decodeURIComponent(fileName);
|
||||
} else {
|
||||
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
|
||||
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
|
||||
}
|
||||
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<FileResponse>(null as any);
|
||||
}
|
||||
}
|
||||
|
||||
export class BotClient extends AuthorizedApiBase {
|
||||
@@ -3513,6 +3668,54 @@ export interface MoneyManagementRequest {
|
||||
leverage: number;
|
||||
}
|
||||
|
||||
export interface GeneticRequest {
|
||||
requestId: string;
|
||||
user: User;
|
||||
createdAt: Date;
|
||||
completedAt?: Date | null;
|
||||
status: GeneticRequestStatus;
|
||||
ticker: Ticker;
|
||||
timeframe: Timeframe;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
balance: number;
|
||||
populationSize: number;
|
||||
generations: number;
|
||||
mutationRate: number;
|
||||
selectionMethod: string;
|
||||
elitismPercentage: number;
|
||||
maxTakeProfit: number;
|
||||
eligibleIndicators: IndicatorType[];
|
||||
results?: Backtest[] | null;
|
||||
bestFitness?: number | null;
|
||||
bestIndividual?: string | null;
|
||||
errorMessage?: string | null;
|
||||
progressInfo?: string | null;
|
||||
}
|
||||
|
||||
export enum GeneticRequestStatus {
|
||||
Pending = "Pending",
|
||||
Running = "Running",
|
||||
Completed = "Completed",
|
||||
Failed = "Failed",
|
||||
Cancelled = "Cancelled",
|
||||
}
|
||||
|
||||
export interface RunGeneticRequest {
|
||||
ticker?: Ticker;
|
||||
timeframe?: Timeframe;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
balance?: number;
|
||||
populationSize?: number;
|
||||
generations?: number;
|
||||
mutationRate?: number;
|
||||
selectionMethod?: string | null;
|
||||
elitismPercentage?: number;
|
||||
maxTakeProfit?: number;
|
||||
eligibleIndicators?: IndicatorType[] | null;
|
||||
}
|
||||
|
||||
export interface StartBotRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
}
|
||||
|
||||
@@ -653,6 +653,54 @@ export interface MoneyManagementRequest {
|
||||
leverage: number;
|
||||
}
|
||||
|
||||
export interface GeneticRequest {
|
||||
requestId: string;
|
||||
user: User;
|
||||
createdAt: Date;
|
||||
completedAt?: Date | null;
|
||||
status: GeneticRequestStatus;
|
||||
ticker: Ticker;
|
||||
timeframe: Timeframe;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
balance: number;
|
||||
populationSize: number;
|
||||
generations: number;
|
||||
mutationRate: number;
|
||||
selectionMethod: string;
|
||||
elitismPercentage: number;
|
||||
maxTakeProfit: number;
|
||||
eligibleIndicators: IndicatorType[];
|
||||
results?: Backtest[] | null;
|
||||
bestFitness?: number | null;
|
||||
bestIndividual?: string | null;
|
||||
errorMessage?: string | null;
|
||||
progressInfo?: string | null;
|
||||
}
|
||||
|
||||
export enum GeneticRequestStatus {
|
||||
Pending = "Pending",
|
||||
Running = "Running",
|
||||
Completed = "Completed",
|
||||
Failed = "Failed",
|
||||
Cancelled = "Cancelled",
|
||||
}
|
||||
|
||||
export interface RunGeneticRequest {
|
||||
ticker?: Ticker;
|
||||
timeframe?: Timeframe;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
balance?: number;
|
||||
populationSize?: number;
|
||||
generations?: number;
|
||||
mutationRate?: number;
|
||||
selectionMethod?: string | null;
|
||||
elitismPercentage?: number;
|
||||
maxTakeProfit?: number;
|
||||
eligibleIndicators?: IndicatorType[] | null;
|
||||
}
|
||||
|
||||
export interface StartBotRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import BacktestPlayground from './backtestPlayground'
|
||||
import BacktestScanner from './backtestScanner'
|
||||
import BacktestUpload from './backtestUpload'
|
||||
import BacktestGenetic from './backtestGenetic'
|
||||
import BacktestGeneticBundle from './backtestGeneticBundle'
|
||||
import type {TabsType} from '../../global/type.tsx'
|
||||
|
||||
// Tabs Array
|
||||
@@ -30,6 +31,11 @@ const tabs: TabsType = [
|
||||
index: 4,
|
||||
label: 'Genetic',
|
||||
},
|
||||
{
|
||||
Component: BacktestGeneticBundle,
|
||||
index: 5,
|
||||
label: 'GeneticBundle',
|
||||
},
|
||||
]
|
||||
|
||||
const Backtest: React.FC = () => {
|
||||
|
||||
@@ -0,0 +1,472 @@
|
||||
import React, {useState} from 'react'
|
||||
import {useForm} from 'react-hook-form'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import {
|
||||
BacktestClient,
|
||||
type GeneticRequest,
|
||||
IndicatorType,
|
||||
type RunGeneticRequest,
|
||||
Ticker,
|
||||
Timeframe,
|
||||
} from '../../generated/ManagingApi'
|
||||
import {Toast} from '../../components/mollecules'
|
||||
|
||||
// Available Indicator Types
|
||||
const ALL_INDICATORS = [
|
||||
IndicatorType.RsiDivergence,
|
||||
IndicatorType.RsiDivergenceConfirm,
|
||||
IndicatorType.MacdCross,
|
||||
IndicatorType.EmaCross,
|
||||
IndicatorType.ThreeWhiteSoldiers,
|
||||
IndicatorType.SuperTrend,
|
||||
IndicatorType.ChandelierExit,
|
||||
IndicatorType.EmaTrend,
|
||||
IndicatorType.StochRsiTrend,
|
||||
IndicatorType.Stc,
|
||||
IndicatorType.StDev,
|
||||
IndicatorType.LaggingStc,
|
||||
IndicatorType.SuperTrendCrossEma,
|
||||
IndicatorType.DualEmaCross,
|
||||
]
|
||||
|
||||
// Form Interface
|
||||
interface GeneticBundleFormData {
|
||||
ticker: Ticker
|
||||
timeframe: Timeframe
|
||||
startDate: string
|
||||
endDate: string
|
||||
balance: number
|
||||
populationSize: number
|
||||
generations: number
|
||||
mutationRate: number
|
||||
selectionMethod: string
|
||||
elitismPercentage: number
|
||||
maxTakeProfit: number
|
||||
eligibleIndicators: IndicatorType[]
|
||||
}
|
||||
|
||||
const BacktestGeneticBundle: React.FC = () => {
|
||||
const {apiUrl} = useApiUrlStore()
|
||||
const backtestClient = new BacktestClient({}, apiUrl)
|
||||
|
||||
// State
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [selectedIndicators, setSelectedIndicators] = useState<IndicatorType[]>(ALL_INDICATORS)
|
||||
const [geneticRequests, setGeneticRequests] = useState<GeneticRequest[]>([])
|
||||
|
||||
// Form setup
|
||||
const {register, handleSubmit, watch, setValue, formState: {errors}} = useForm<GeneticBundleFormData>({
|
||||
defaultValues: {
|
||||
ticker: Ticker.BTC,
|
||||
timeframe: Timeframe.OneHour,
|
||||
startDate: getDefaultDateRange().startDate,
|
||||
endDate: getDefaultDateRange().endDate,
|
||||
balance: 10000,
|
||||
populationSize: 10,
|
||||
generations: 5,
|
||||
mutationRate: 0.3,
|
||||
selectionMethod: 'tournament',
|
||||
elitismPercentage: 10,
|
||||
maxTakeProfit: 2.0,
|
||||
eligibleIndicators: ALL_INDICATORS,
|
||||
}
|
||||
})
|
||||
|
||||
const formValues = watch()
|
||||
|
||||
// Get default date range (last 30 days)
|
||||
function getDefaultDateRange() {
|
||||
const endDate = new Date()
|
||||
const startDate = new Date()
|
||||
startDate.setDate(startDate.getDate() - 30)
|
||||
|
||||
return {
|
||||
startDate: startDate.toISOString().split('T')[0],
|
||||
endDate: endDate.toISOString().split('T')[0],
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch existing genetic requests
|
||||
const {data: existingRequests, refetch: refetchRequests} = useQuery({
|
||||
queryKey: ['geneticRequests'],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const requests = await backtestClient.backtest_GetGeneticRequests()
|
||||
setGeneticRequests(requests)
|
||||
return requests
|
||||
} catch (error) {
|
||||
console.error('Error fetching genetic requests:', error)
|
||||
return []
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// Handle form submission
|
||||
const onSubmit = async (data: GeneticBundleFormData) => {
|
||||
if (selectedIndicators.length === 0) {
|
||||
new Toast('Please select at least one indicator', false)
|
||||
return
|
||||
}
|
||||
|
||||
setIsSubmitting(true)
|
||||
const t = new Toast('Creating genetic request...')
|
||||
|
||||
try {
|
||||
const request: RunGeneticRequest = {
|
||||
ticker: data.ticker,
|
||||
timeframe: data.timeframe,
|
||||
startDate: new Date(data.startDate),
|
||||
endDate: new Date(data.endDate),
|
||||
balance: data.balance,
|
||||
populationSize: data.populationSize,
|
||||
generations: data.generations,
|
||||
mutationRate: data.mutationRate,
|
||||
selectionMethod: data.selectionMethod,
|
||||
elitismPercentage: data.elitismPercentage,
|
||||
maxTakeProfit: data.maxTakeProfit,
|
||||
eligibleIndicators: selectedIndicators,
|
||||
}
|
||||
|
||||
const geneticRequest = await backtestClient.backtest_RunGenetic(request)
|
||||
|
||||
t.update('success', `Genetic request created successfully! ID: ${geneticRequest.requestId}`)
|
||||
|
||||
// Refresh the list of genetic requests
|
||||
await refetchRequests()
|
||||
|
||||
// Reset form
|
||||
setValue('ticker', Ticker.BTC)
|
||||
setValue('timeframe', Timeframe.OneHour)
|
||||
setValue('startDate', getDefaultDateRange().startDate)
|
||||
setValue('endDate', getDefaultDateRange().endDate)
|
||||
setValue('balance', 10000)
|
||||
setValue('populationSize', 10)
|
||||
setValue('generations', 5)
|
||||
setValue('mutationRate', 0.3)
|
||||
setValue('selectionMethod', 'tournament')
|
||||
setValue('elitismPercentage', 10)
|
||||
setValue('maxTakeProfit', 2.0)
|
||||
setSelectedIndicators(ALL_INDICATORS)
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating genetic request:', error)
|
||||
t.update('error', 'Failed to create genetic request. Please try again.')
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle indicator selection
|
||||
const handleIndicatorToggle = (indicator: IndicatorType) => {
|
||||
setSelectedIndicators(prev => {
|
||||
if (prev.includes(indicator)) {
|
||||
return prev.filter(i => i !== indicator)
|
||||
} else {
|
||||
return [...prev, indicator]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Get status badge color
|
||||
const getStatusBadgeColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'Pending':
|
||||
return 'badge-warning'
|
||||
case 'Running':
|
||||
return 'badge-info'
|
||||
case 'Completed':
|
||||
return 'badge-success'
|
||||
case 'Failed':
|
||||
return 'badge-error'
|
||||
case 'Cancelled':
|
||||
return 'badge-neutral'
|
||||
default:
|
||||
return 'badge-neutral'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="card bg-base-100 shadow-xl">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">Genetic Algorithm Bundle</h2>
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
Create a genetic algorithm request that will be processed in the background.
|
||||
The algorithm will optimize trading parameters and indicator combinations.
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Ticker</span>
|
||||
</label>
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
{...register('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">Timeframe</span>
|
||||
</label>
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
{...register('timeframe')}
|
||||
>
|
||||
{Object.values(Timeframe).map(tf => (
|
||||
<option key={tf} value={tf}>{tf}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Start Date</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
className="input input-bordered w-full"
|
||||
{...register('startDate')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">End Date</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
className="input input-bordered w-full"
|
||||
{...register('endDate')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Balance</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered w-full"
|
||||
{...register('balance', {valueAsNumber: true})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Population Size</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="5"
|
||||
max="100"
|
||||
className="input input-bordered w-full"
|
||||
{...register('populationSize', {valueAsNumber: true})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Generations</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="50"
|
||||
className="input input-bordered w-full"
|
||||
{...register('generations', {valueAsNumber: true})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Mutation Rate</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.1"
|
||||
min="0"
|
||||
max="1"
|
||||
className="input input-bordered w-full"
|
||||
{...register('mutationRate', {valueAsNumber: true})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Selection Method</span>
|
||||
</label>
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
{...register('selectionMethod')}
|
||||
>
|
||||
<option value="tournament">Tournament Selection</option>
|
||||
<option value="roulette">Roulette Wheel</option>
|
||||
<option value="fitness-weighted">Fitness Weighted</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Elitism Percentage</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="50"
|
||||
step="1"
|
||||
className="input input-bordered w-full"
|
||||
{...register('elitismPercentage', {valueAsNumber: true})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Max Take Profit (%)</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0.9"
|
||||
max="8"
|
||||
step="0.1"
|
||||
className="input input-bordered w-full"
|
||||
{...register('maxTakeProfit', {valueAsNumber: true})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Eligible Indicators</span>
|
||||
</label>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-2 max-h-40 overflow-y-auto border rounded-lg p-4">
|
||||
{ALL_INDICATORS.map(indicator => (
|
||||
<label key={indicator} className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedIndicators.includes(indicator)}
|
||||
onChange={() => handleIndicatorToggle(indicator)}
|
||||
className="checkbox checkbox-sm"
|
||||
/>
|
||||
<span className="text-sm">{indicator}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
{selectedIndicators.length} of {ALL_INDICATORS.length} indicators selected
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting || selectedIndicators.length === 0}
|
||||
className="btn btn-primary"
|
||||
>
|
||||
{isSubmitting ? 'Creating...' : 'Create Genetic Request'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedIndicators(ALL_INDICATORS)}
|
||||
className="btn btn-secondary"
|
||||
>
|
||||
Select All Indicators
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedIndicators([])}
|
||||
className="btn btn-secondary"
|
||||
>
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Existing Genetic Requests */}
|
||||
{geneticRequests.length > 0 && (
|
||||
<div className="card bg-base-100 shadow-xl">
|
||||
<div className="card-body">
|
||||
<h3 className="card-title">Existing Genetic Requests</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table table-zebra">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Ticker</th>
|
||||
<th>Timeframe</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Completed</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{geneticRequests.map((request) => (
|
||||
<tr key={request.requestId}>
|
||||
<td className="font-mono text-xs">{request.requestId.slice(0, 8)}...</td>
|
||||
<td>{request.ticker}</td>
|
||||
<td>{request.timeframe}</td>
|
||||
<td>
|
||||
<span className={`badge ${getStatusBadgeColor(request.status)}`}>
|
||||
{request.status}
|
||||
</span>
|
||||
</td>
|
||||
<td>{new Date(request.createdAt).toLocaleDateString()}</td>
|
||||
<td>
|
||||
{request.completedAt
|
||||
? new Date(request.completedAt).toLocaleDateString()
|
||||
: '-'
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
// TODO: Implement view details
|
||||
new Toast('View details not implemented yet', false)
|
||||
}}
|
||||
className="btn btn-sm btn-outline"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
try {
|
||||
await backtestClient.backtest_DeleteGeneticRequest(request.requestId)
|
||||
new Toast('Request deleted successfully', false)
|
||||
await refetchRequests()
|
||||
} catch (error) {
|
||||
new Toast('Failed to delete request', false)
|
||||
}
|
||||
}}
|
||||
className="btn btn-sm btn-error"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BacktestGeneticBundle
|
||||
Reference in New Issue
Block a user