From 3439f131567d72fccd575695305ff225ca337c94 Mon Sep 17 00:00:00 2001 From: Oda <102867384+CryptoOda@users.noreply.github.com> Date: Tue, 8 Jul 2025 20:06:09 +0700 Subject: [PATCH] Genetics front (#29) * Add Genetics js * Fix some error for genetics * Update removed backtest * Fixes --- package-lock.json | 14 +- package.json | 5 + src/Managing.Application/Bots/TradingBot.cs | 2 +- src/Managing.WebApp/package.json | 3 +- .../src/app/store/accountStore.tsx | 37 +- .../organism/Backtest/backtestRowDetails.tsx | 2 +- .../organism/Backtest/backtestTable.tsx | 5 +- src/Managing.WebApp/src/global/type.tsx | 3 + .../src/pages/authPage/auth.tsx | 15 +- .../src/pages/backtestPage/backtest.tsx | 6 + .../pages/backtestPage/backtestGenetic.tsx | 1875 +++++++++++++++++ src/Managing.WebApp/src/types/genetic-js.d.ts | 65 + 12 files changed, 2014 insertions(+), 18 deletions(-) create mode 100644 package.json create mode 100644 src/Managing.WebApp/src/pages/backtestPage/backtestGenetic.tsx create mode 100644 src/Managing.WebApp/src/types/genetic-js.d.ts diff --git a/package-lock.json b/package-lock.json index 9725388..70bb0bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2,5 +2,17 @@ "name": "managing-apps", "lockfileVersion": 3, "requires": true, - "packages": {} + "packages": { + "": { + "dependencies": { + "genetic-js": "^0.1.14" + } + }, + "node_modules/genetic-js": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/genetic-js/-/genetic-js-0.1.14.tgz", + "integrity": "sha512-HHm21naCEF1EVKTWPFzKX4ENB7Nn/my4kTy2POi4u/2gB0XPUOh8oDlhhESVCZVBge3b7nuLrZNZNAt4ObH19Q==", + "license": "BSD" + } + } } diff --git a/package.json b/package.json new file mode 100644 index 0000000..59feab5 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "genetic-js": "^0.1.14" + } +} diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index baf493f..4022fd7 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -1504,7 +1504,7 @@ public class TradingBot : Bot, ITradingBot /// True if the position has exceeded the time limit, false otherwise private bool HasPositionExceededTimeLimit(Position position, DateTime currentTime) { - if (!Config.MaxPositionTimeHours.HasValue) + if (!Config.MaxPositionTimeHours.HasValue || Config.MaxPositionTimeHours.Value <= 0) { return false; // Time-based closure is disabled } diff --git a/src/Managing.WebApp/package.json b/src/Managing.WebApp/package.json index e37f8a4..e99c67c 100644 --- a/src/Managing.WebApp/package.json +++ b/src/Managing.WebApp/package.json @@ -34,6 +34,7 @@ "connectkit": "^1.8.2", "date-fns": "^2.30.0", "elliptic": "^6.6.1", + "genetic-js": "^0.1.14", "jotai": "^1.6.7", "latest-version": "^9.0.0", "lightweight-charts": "git+https://github.com/ntf/lightweight-charts.git", @@ -65,7 +66,7 @@ "@types/react": "^18.0.9", "@types/react-dom": "^18.0.4", "@types/react-grid-layout": "^1.3.2", - "@types/react-plotly.js": "^2.6.0", + "@types/react-plotly.js": "^2.6.3", "@types/react-slider": "^1.3.1", "@types/react-table": "^7.7.12", "@types/signalr": "^2.2.37", diff --git a/src/Managing.WebApp/src/app/store/accountStore.tsx b/src/Managing.WebApp/src/app/store/accountStore.tsx index 8a9a899..e2a60b8 100644 --- a/src/Managing.WebApp/src/app/store/accountStore.tsx +++ b/src/Managing.WebApp/src/app/store/accountStore.tsx @@ -1,17 +1,28 @@ import {create} from 'zustand' import type {AccountStore} from '../../global/type.tsx' +import {AccountClient} from '../../generated/ManagingApi' +import useApiUrlStore from './apiStore' -export const useAuthStore = create((set) => ({ +export const useAuthStore = create((set, get) => ({ accounts: [], - onInitialize: () => { - console.log('useFlowStore onInitialize') - - // const accountClient = new AccountClient({}, apiUrl) - // const accounts = await accountClient.account_GetAccounts() - // if (accounts.length > 0) { - // get().setAccounts(accounts) - // } + onInitialize: async () => { + console.log('useAuthStore onInitialize') + + try { + const { apiUrl } = useApiUrlStore.getState() + const accountClient = new AccountClient({}, apiUrl) + const accounts = await accountClient.account_GetAccounts() + + if (accounts.length > 0) { + get().setAccounts(accounts) + console.log('Accounts loaded:', accounts.length) + } else { + console.log('No accounts found') + } + } catch (error) { + console.error('Failed to load accounts:', error) + } }, setAccounts: (accounts) => { set((state) => ({ @@ -19,4 +30,12 @@ export const useAuthStore = create((set) => ({ accounts: accounts, })) }, + getFirstAccount: () => { + const state = get() + return state.accounts.length > 0 ? state.accounts[0] : null + }, + getFirstAccountName: () => { + const state = get() + return state.accounts.length > 0 ? state.accounts[0].name : '' + }, })) diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx index f7dc341..8c2237d 100644 --- a/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx @@ -288,7 +288,7 @@ const BacktestRowDetails: React.FC = ({ title="Money Management" content={ "SL: " +(config.moneyManagement?.stopLoss * 100).toFixed(2) + "% TP: " + - (config.moneyManagement?.takeProfit * 100).toFixed(2) + "%" + (config.moneyManagement?.takeProfit * 100).toFixed(2) + "%" + " Lev.: x" + config.moneyManagement?.leverage } > = ({list, isFetching}) => { +const BacktestTable: React.FC = ({list, isFetching, displaySummary = true}) => { const [rows, setRows] = useState([]) const {apiUrl} = useApiUrlStore() const {removeBacktest} = useBacktestStore() @@ -462,7 +463,7 @@ const BacktestTable: React.FC = ({list, isFetching}) => { ) : ( <> - {list && list.length > 0 && ( + {list && list.length > 0 && displaySummary && ( <>
void accounts: Account[] + onInitialize: () => Promise + getFirstAccount: () => Account | null + getFirstAccountName: () => string } export type ILoader = { diff --git a/src/Managing.WebApp/src/pages/authPage/auth.tsx b/src/Managing.WebApp/src/pages/authPage/auth.tsx index de71eb0..f6f311c 100644 --- a/src/Managing.WebApp/src/pages/authPage/auth.tsx +++ b/src/Managing.WebApp/src/pages/authPage/auth.tsx @@ -1,15 +1,17 @@ -import { usePrivy } from '@privy-io/react-auth' -import { useAccount } from 'wagmi' +import {usePrivy} from '@privy-io/react-auth' +import {useAccount} from 'wagmi' import LogIn from '../../components/mollecules/LogIn/LogIn' import useCookie from '../../hooks/useCookie' -import { useEffect, useState } from 'react' +import {useEffect, useState} from 'react' +import {useAuthStore} from '../../app/store/accountStore' export const Auth = ({ children }: any) => { const { getCookie, deleteCookie } = useCookie() const { isConnected } = useAccount() const { login, ready, authenticated, user } = usePrivy() const token = getCookie('token') + const onInitialize = useAuthStore((state) => state.onInitialize) const [isLoading, setIsLoading] = useState(true); @@ -23,6 +25,13 @@ export const Auth = ({ children }: any) => { } }, [ready]); + // Initialize accounts when authenticated and token is available + useEffect(() => { + if (authenticated && token) { + onInitialize() + } + }, [authenticated, token, onInitialize]); + if (!ready || isLoading) { return
Loading...
; } diff --git a/src/Managing.WebApp/src/pages/backtestPage/backtest.tsx b/src/Managing.WebApp/src/pages/backtestPage/backtest.tsx index 5f459a1..beb5243 100644 --- a/src/Managing.WebApp/src/pages/backtestPage/backtest.tsx +++ b/src/Managing.WebApp/src/pages/backtestPage/backtest.tsx @@ -5,6 +5,7 @@ import {Tabs} from '../../components/mollecules' import BacktestPlayground from './backtestPlayground' import BacktestScanner from './backtestScanner' import BacktestUpload from './backtestUpload' +import BacktestGenetic from './backtestGenetic' import type {TabsType} from '../../global/type.tsx' // Tabs Array @@ -24,6 +25,11 @@ const tabs: TabsType = [ index: 3, label: 'Upload', }, + { + Component: BacktestGenetic, + index: 4, + label: 'Genetic', + }, ] const Backtest: React.FC = () => { diff --git a/src/Managing.WebApp/src/pages/backtestPage/backtestGenetic.tsx b/src/Managing.WebApp/src/pages/backtestPage/backtestGenetic.tsx new file mode 100644 index 0000000..8a1a4a7 --- /dev/null +++ b/src/Managing.WebApp/src/pages/backtestPage/backtestGenetic.tsx @@ -0,0 +1,1875 @@ +import React, {useCallback, useState} from 'react' +import {useQuery} from '@tanstack/react-query' +import Plot from 'react-plotly.js' +import {useForm} from 'react-hook-form' + +import useApiUrlStore from '../../app/store/apiStore' +import useBacktestStore from '../../app/store/backtestStore' +import { + AccountClient, + type Backtest, + BacktestClient, + DataClient, + type IndicatorRequest, + IndicatorType, + MoneyManagementClient, + type MoneyManagementRequest, + type RunBacktestRequest, + ScenarioClient, + type ScenarioRequest, + SignalType, + Ticker, + Timeframe, + type TradingBotConfigRequest, +} from '../../generated/ManagingApi' +import {Toast} from '../../components/mollecules' +import BacktestTable from '../../components/organism/Backtest/backtestTable' +import useTheme from '../../hooks/useTheme' + +// ============================================================================ +// CONFIGURABLE PARAMETERS - ADJUST THESE TO CUSTOMIZE THE GENETIC ALGORITHM +// ============================================================================ + +// Genetic Algorithm Settings +const GENETIC_CONFIG = { + populationSize: 10, // Number of individuals per generation + generations: 5, // Number of generations to evolve + mutationRate: 0.3, // Probability of mutation (0.0 - 1.0) + crossoverRate: 0.7, // Probability of crossover (0.0 - 1.0) + maxIndicators: 4, // Maximum number of indicators per scenario + minIndicators: 1, // Minimum number of indicators per scenario + phase1Generations: 3, // Generations for single indicator testing + phase2Generations: 2, // Generations for 2-indicator combinations + phase3Generations: 2, // Generations for 3-indicator combinations + phase4Generations: 2, // Generations for 4-indicator combinations +} + +// Trading Parameters Ranges (in whole number percentages) +const PARAMETER_RANGES = { + stopLoss: { min: 2, max: 15 }, // 2% - 15% + takeProfit: { min: 0.9, max: 4 }, // 0.9% - 4% (default) + leverage: { min: 1, max: 10 }, // 1x - 10x + cooldownPeriod: { min: 5, max: 25 }, // 5 - 25 candles + maxLossStreak: { min: 0, max: 4 }, // 0 - 4 losses + maxPositionTimeHours: { min: 1, max: 48 }, // 1 - 48 hours +} + +// Indicator Parameters Ranges (based on CustomScenario defaults) +const INDICATOR_RANGES = { + period: { min: 10, max: 20 }, // 10 - 20 periods (default: 14) + fastPeriods: { min: 8, max: 18 }, // 8 - 18 periods (default: 12) + slowPeriods: { min: 20, max: 35 }, // 20 - 35 periods (default: 26) + signalPeriods: { min: 7, max: 12 }, // 7 - 12 periods (default: 9) + multiplier: { min: 2.0, max: 4.5 }, // 2.0 - 4.5 (default: 3.0) + stochPeriods: { min: 10, max: 20 }, // 10 - 20 periods (default: 14) + smoothPeriods: { min: 2, max: 5 }, // 2 - 5 periods (default: 3) + cyclePeriods: { min: 8, max: 15 }, // 8 - 15 periods (default: 10) +} + +// Default indicator values (from CustomScenario) +const DEFAULT_INDICATOR_VALUES = { + period: 14, + fastPeriods: 12, + slowPeriods: 26, + signalPeriods: 9, + multiplier: 3.0, + stochPeriods: 14, + smoothPeriods: 3, + cyclePeriods: 10, +} + +// Loopback Period Configuration +const LOOPBACK_CONFIG = { + singleIndicator: 1, // 1 candle for single indicator + multipleIndicators: { min: 5, max: 15 }, // 5-15 candles for multiple indicators +} + +// Available Indicator Types (excluding Composite as it's a special case) +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, +] + +// Indicator type to parameter mapping +const INDICATOR_PARAM_MAPPING = { + [IndicatorType.RsiDivergence]: ['period'], + [IndicatorType.RsiDivergenceConfirm]: ['period'], + [IndicatorType.EmaCross]: ['period'], + [IndicatorType.EmaTrend]: ['period'], + [IndicatorType.StDev]: ['period'], + [IndicatorType.ThreeWhiteSoldiers]: ['period'], + [IndicatorType.MacdCross]: ['fastPeriods', 'slowPeriods', 'signalPeriods'], + [IndicatorType.DualEmaCross]: ['fastPeriods', 'slowPeriods'], + [IndicatorType.SuperTrend]: ['period', 'multiplier'], + [IndicatorType.SuperTrendCrossEma]: ['period', 'multiplier'], + [IndicatorType.ChandelierExit]: ['period', 'multiplier'], + [IndicatorType.StochRsiTrend]: ['period', 'stochPeriods', 'signalPeriods', 'smoothPeriods'], + [IndicatorType.Stc]: ['cyclePeriods', 'fastPeriods', 'slowPeriods'], + [IndicatorType.LaggingStc]: ['cyclePeriods', 'fastPeriods', 'slowPeriods'], +} + +// ============================================================================ +// END CONFIGURABLE PARAMETERS +// ============================================================================ + +// Form Interface +interface GeneticFormData { + ticker: Ticker + timeframe: Timeframe + startDate: string + endDate: string + balance: number + populationSize: number + generations: number + mutationRate: number + selectionMethod: 'tournament' | 'roulette' | 'fitness-weighted' + elitismPercentage: number + maxTakeProfit: number +} + +// Genetic Individual Interface +interface GeneticIndividual { + // Money Management Parameters + stopLoss: number + takeProfit: number + leverage: number + + // Trading Bot Parameters + cooldownPeriod: number + maxLossStreak: number + maxPositionTimeHours: number + flipOnlyWhenInProfit: boolean + closeEarlyWhenProfitable: boolean + + // Synth API Parameters (always false for performance) + useSynthApi: boolean + useForPositionSizing: boolean + useForSignalFiltering: boolean + useForDynamicStopLoss: boolean + + // Indicator Configuration + indicators: GeneticIndicator[] +} + +// Genetic Indicator Interface +interface GeneticIndicator { + type: IndicatorType + period?: number + fastPeriods?: number + slowPeriods?: number + signalPeriods?: number + multiplier?: number + stochPeriods?: number + smoothPeriods?: number + cyclePeriods?: number +} + +const BacktestGenetic: React.FC = () => { + const {apiUrl} = useApiUrlStore() + const {addBacktest} = useBacktestStore() + const theme = useTheme().themeProperty() + + // API Clients + const backtestClient = new BacktestClient({}, apiUrl) + const scenarioClient = new ScenarioClient({}, apiUrl) + const moneyManagementClient = new MoneyManagementClient({}, apiUrl) + const accountClient = new AccountClient({}, apiUrl) + const dataClient = new DataClient({}, apiUrl) + + // State + const [isRunning, setIsRunning] = useState(false) + const [loading, setLoading] = useState(false) + const [results, setResults] = useState([]) + const [currentGeneration, setCurrentGeneration] = useState(0) + const [currentIndividual, setCurrentIndividual] = useState(0) + const [bestFitness, setBestFitness] = useState(0) + const [showResults, setShowResults] = useState(false) + const [totalIndividuals, setTotalIndividuals] = useState(0) + const [backtestTimes, setBacktestTimes] = useState([]) + const [startTime, setStartTime] = useState(null) + const [estimatedCompletion, setEstimatedCompletion] = useState('') + const [currentPhase, setCurrentPhase] = useState<'phase1' | 'phase2' | 'phase3' | 'phase4'>('phase1') + const [bestSingleIndicators, setBestSingleIndicators] = useState>([]) + const [eligibleIndicators, setEligibleIndicators] = useState(ALL_INDICATORS) + const [removedIndicators, setRemovedIndicators] = useState>(new Set()) + + // Calculate default date range (2 weeks ago to now) + const getDefaultDateRange = () => { + const endDate = new Date() + const startDate = new Date() + startDate.setDate(startDate.getDate() - 14) // 2 weeks ago + return { + start: startDate.toISOString().split('T')[0], + end: endDate.toISOString().split('T')[0] + } + } + + // React Hook Form + const {register, handleSubmit, watch, setValue, formState: {errors}} = useForm({ + defaultValues: { + ticker: Ticker.BTC, + timeframe: Timeframe.FifteenMinutes, + startDate: getDefaultDateRange().start, + endDate: getDefaultDateRange().end, + balance: 10000, + populationSize: GENETIC_CONFIG.populationSize, + generations: GENETIC_CONFIG.generations, + mutationRate: GENETIC_CONFIG.mutationRate, + selectionMethod: 'tournament', + elitismPercentage: 15, + maxTakeProfit: 4, + } + }) + + // Watch form values + const formValues = watch() + + // Genetic Algorithm Configuration (from form values) + const geneticConfig = { + populationSize: formValues.populationSize || GENETIC_CONFIG.populationSize, + generations: formValues.generations || GENETIC_CONFIG.generations, + mutationRate: formValues.mutationRate || GENETIC_CONFIG.mutationRate, + crossoverRate: GENETIC_CONFIG.crossoverRate, + } + + // Fetch accounts + const {data: accounts} = useQuery({ + queryKey: ['accounts'], + queryFn: () => accountClient.account_GetAccounts(), + }) + + // Helper function to get random value within range + const getRandomInRange = (range: { min: number; max: number }): number => { + return Math.random() * (range.max - range.min) + range.min + } + + // Helper function to get random integer within range + const getRandomIntInRange = (range: { min: number; max: number }): number => { + return Math.floor(Math.random() * (range.max - range.min + 1)) + range.min + } + + // Helper function to format time duration + const formatTimeDuration = (milliseconds: number): string => { + const seconds = Math.floor(milliseconds / 1000) + const minutes = Math.floor(seconds / 60) + const hours = Math.floor(minutes / 60) + + if (hours > 0) { + return `${hours}h ${minutes % 60}m` + } else if (minutes > 0) { + return `${minutes}m ${seconds % 60}s` + } else { + return `${seconds}s` + } + } + + // Helper function to calculate estimated completion time + const calculateEstimatedCompletion = (processedCount: number, totalCount: number, averageTimeMs: number): string => { + if (processedCount === 0 || averageTimeMs === 0) return 'Calculating...' + + const remainingCount = totalCount - processedCount + const estimatedTimeMs = remainingCount * averageTimeMs + const estimatedCompletionTime = new Date(Date.now() + estimatedTimeMs) + + return estimatedCompletionTime.toLocaleTimeString() + } + + // Helper function to get dynamic max take profit value + const getMaxTakeProfit = (): number => { + return Math.min(formValues.maxTakeProfit || 4, PARAMETER_RANGES.takeProfit.max) + } + + // Create a random indicator with bias towards CustomScenario defaults + const createRandomIndicator = (eligibleIndicators: IndicatorType[]): GeneticIndicator => { + const type = eligibleIndicators[Math.floor(Math.random() * eligibleIndicators.length)] + + const indicator: GeneticIndicator = { type } + + // Add parameters based on indicator type using the mapping + const requiredParams = INDICATOR_PARAM_MAPPING[type as keyof typeof INDICATOR_PARAM_MAPPING] || [] + + requiredParams.forEach((param: string) => { + // 70% chance to use default value, 30% chance to use random value in range + const useDefault = Math.random() < 0.7 + + switch (param) { + case 'period': + indicator.period = useDefault ? DEFAULT_INDICATOR_VALUES.period : getRandomIntInRange(INDICATOR_RANGES.period) + break + case 'fastPeriods': + indicator.fastPeriods = useDefault ? DEFAULT_INDICATOR_VALUES.fastPeriods : getRandomIntInRange(INDICATOR_RANGES.fastPeriods) + break + case 'slowPeriods': + indicator.slowPeriods = useDefault ? DEFAULT_INDICATOR_VALUES.slowPeriods : getRandomIntInRange(INDICATOR_RANGES.slowPeriods) + break + case 'signalPeriods': + indicator.signalPeriods = useDefault ? DEFAULT_INDICATOR_VALUES.signalPeriods : getRandomIntInRange(INDICATOR_RANGES.signalPeriods) + break + case 'multiplier': + indicator.multiplier = useDefault ? DEFAULT_INDICATOR_VALUES.multiplier : getRandomInRange(INDICATOR_RANGES.multiplier) + break + case 'stochPeriods': + indicator.stochPeriods = useDefault ? DEFAULT_INDICATOR_VALUES.stochPeriods : getRandomIntInRange(INDICATOR_RANGES.stochPeriods) + break + case 'smoothPeriods': + indicator.smoothPeriods = useDefault ? DEFAULT_INDICATOR_VALUES.smoothPeriods : getRandomIntInRange(INDICATOR_RANGES.smoothPeriods) + break + case 'cyclePeriods': + indicator.cyclePeriods = useDefault ? DEFAULT_INDICATOR_VALUES.cyclePeriods : getRandomIntInRange(INDICATOR_RANGES.cyclePeriods) + break + } + }) + + return indicator + } + + // Create combinations of indicators from best single indicators + const createIndicatorCombinations = (bestIndicators: Array<{individual: GeneticIndividual, backtest: Backtest | null, fitness: number}>, targetSize: number): GeneticIndicator[][] => { + const combinations: GeneticIndicator[][] = [] + const indicatorTypes = bestIndicators.map(item => item.individual.indicators[0]) + + // Generate combinations of specific size without duplicates + const generateCombinations = (arr: GeneticIndicator[], size: number, start: number = 0, current: GeneticIndicator[] = []): void => { + if (current.length === size) { + // Check for duplicate indicator types + const types = current.map(ind => ind.type) + const uniqueTypes = new Set(types) + if (uniqueTypes.size === types.length) { + combinations.push([...current]) + } + return + } + + for (let i = start; i < arr.length; i++) { + current.push(arr[i]) + generateCombinations(arr, size, i + 1, current) + current.pop() + } + } + + generateCombinations(indicatorTypes, targetSize) + return combinations + } + + // Function to ensure no duplicate indicators in a scenario + const ensureNoDuplicateIndicators = (indicators: GeneticIndicator[]): GeneticIndicator[] => { + const seen = new Set() + const uniqueIndicators: GeneticIndicator[] = [] + + for (const indicator of indicators) { + if (!seen.has(indicator.type)) { + seen.add(indicator.type) + uniqueIndicators.push(indicator) + } + } + + return uniqueIndicators + } + + // Create a random individual with positive risk-reward ratio + const createIndividual = (phase: 'phase1' | 'phase2' | 'phase3' | 'phase4' = 'phase1', bestIndicators: Array<{individual: GeneticIndividual, backtest: Backtest | null, fitness: number}> = [], currentEligibleIndicators: IndicatorType[] = eligibleIndicators): GeneticIndividual => { + // Generate stop loss first + const minTP = 0.9; + const maxTP = getMaxTakeProfit(); + const tp = getRandomInRange({ min: minTP, max: maxTP }); + const minSL = 0.5; + const maxSL = Math.min(tp / 1.1, tp - 0.1); + const sl = maxSL >= minSL ? getRandomInRange({ min: minSL, max: maxSL }) : minSL; + + let indicators: GeneticIndicator[] + + if (phase === 'phase1') { + // Phase 1: Single indicators only + indicators = [createRandomIndicator(currentEligibleIndicators)] + } else { + // Phase 2, 3, 4: Use combinations of best indicators with specific sizes + const targetSize = phase === 'phase2' ? 2 : phase === 'phase3' ? 3 : 4 + + if (bestIndicators.length > 0) { + const combinations = createIndicatorCombinations(bestIndicators, targetSize) + if (combinations.length > 0) { + // Randomly select a combination + const randomCombination = combinations[Math.floor(Math.random() * combinations.length)] + indicators = randomCombination + } else { + // Fallback to random indicators of target size + const randomIndicators = [] + const usedTypes = new Set() + + while (randomIndicators.length < targetSize && usedTypes.size < currentEligibleIndicators.length) { + const randomIndicator = createRandomIndicator(currentEligibleIndicators) + if (!usedTypes.has(randomIndicator.type)) { + usedTypes.add(randomIndicator.type) + randomIndicators.push(randomIndicator) + } + } + + indicators = randomIndicators + } + } else { + // Fallback to random indicators of target size + const randomIndicators = [] + const usedTypes = new Set() + + while (randomIndicators.length < targetSize && usedTypes.size < currentEligibleIndicators.length) { + const randomIndicator = createRandomIndicator(currentEligibleIndicators) + if (!usedTypes.has(randomIndicator.type)) { + usedTypes.add(randomIndicator.type) + randomIndicators.push(randomIndicator) + } + } + + indicators = randomIndicators + } + } + + // Ensure no duplicate indicators in the final scenario + const uniqueIndicators = ensureNoDuplicateIndicators(indicators) + + return { + // Money Management Parameters + stopLoss: sl, + takeProfit: tp, + leverage: 1, + + // Trading Bot Parameters + cooldownPeriod: getRandomIntInRange(PARAMETER_RANGES.cooldownPeriod), + maxLossStreak: getRandomIntInRange(PARAMETER_RANGES.maxLossStreak), + maxPositionTimeHours: 0, // Always 0 to prevent early position cutting + flipOnlyWhenInProfit: Math.random() > 0.5, + closeEarlyWhenProfitable: Math.random() > 0.5, + + // Synth API Parameters (always false for performance) + useSynthApi: false, + useForPositionSizing: false, + useForSignalFiltering: false, + useForDynamicStopLoss: false, + + // Indicator Configuration + indicators: uniqueIndicators, + } + } + + // Validate risk-reward ratio and minimum take profit + const validateRiskRewardRatio = (stopLoss: number, takeProfit: number): boolean => { + const minTakeProfitForRR = stopLoss * 1.1 // At least 10% better than 1:1 + const minTakeProfitAbsolute = 0.9 // Minimum 0.9% + const minTakeProfit = Math.max(minTakeProfitForRR, minTakeProfitAbsolute) + return takeProfit >= minTakeProfit + } + + // Function to remove bad indicators from eligible list + const removeBadIndicators = (fitnessResults: Array<{individual: GeneticIndividual, backtest: Backtest | null, fitness: number}>) => { + const badIndicators = new Set() + + // Find indicators with very poor performance (fitness < 0.1) + fitnessResults.forEach(result => { + if (result.fitness < 0.1 && result.individual.indicators.length === 1) { + const indicatorType = result.individual.indicators[0].type + badIndicators.add(indicatorType) + } + }) + + // Remove bad indicators from eligible list + if (badIndicators.size > 0) { + setEligibleIndicators(prev => prev.filter(indicator => !badIndicators.has(indicator))) + setRemovedIndicators(prev => new Set([...prev, ...badIndicators])) + + // Log removed indicators + console.log(`Removed bad indicators: ${Array.from(badIndicators).join(', ')}`) + } + } + + // Calculate fitness score based on backtest results + const calculateFitnessScore = (backtest: Backtest, individual: GeneticIndividual): number => { + if (!backtest.statistics) return 0 + + const stats = backtest.statistics + + // Multi-objective fitness function + const pnlScore = Math.max(0, (stats.totalPnL || 0) / 1000) // Normalize PnL + const winRateScore = (backtest.winRate || 0) / 100 // Normalize win rate + const riskRewardScore = Math.min(2, (stats.winningTrades || 0) / Math.max(1, Math.abs(stats.loosingTrades || 1))) + const consistencyScore = 1 - Math.abs((stats.totalPnL || 0) - (backtest.finalPnl || 0)) / Math.max(1, Math.abs(stats.totalPnL || 1)) + + // Risk-reward ratio bonus (encourage better R:R ratios) + const riskRewardRatio = individual.takeProfit / individual.stopLoss + const riskRewardBonus = Math.min(0.2, (riskRewardRatio - 1.1) * 0.1) // Bonus for R:R > 1.1 + + // Weighted combination + const fitness = + pnlScore * 0.35 + + winRateScore * 0.25 + + riskRewardScore * 0.2 + + consistencyScore * 0.1 + + riskRewardBonus * 0.1 + + return Math.max(0, fitness) + } + + // Convert genetic indicator to IndicatorRequest + const convertToIndicatorRequest = (indicator: GeneticIndicator, index: number): IndicatorRequest => { + const baseRequest: IndicatorRequest = { + name: `Genetic_${indicator.type}_${index}_${Date.now()}`, + type: indicator.type, + signalType: SignalType.Signal, + minimumHistory: 14, + } + + // Add parameters based on indicator type + if (indicator.period !== undefined) baseRequest.period = indicator.period + if (indicator.fastPeriods !== undefined) baseRequest.fastPeriods = indicator.fastPeriods + if (indicator.slowPeriods !== undefined) baseRequest.slowPeriods = indicator.slowPeriods + if (indicator.signalPeriods !== undefined) baseRequest.signalPeriods = indicator.signalPeriods + if (indicator.multiplier !== undefined) baseRequest.multiplier = indicator.multiplier + if (indicator.stochPeriods !== undefined) baseRequest.stochPeriods = indicator.stochPeriods + if (indicator.smoothPeriods !== undefined) baseRequest.smoothPeriods = indicator.smoothPeriods + if (indicator.cyclePeriods !== undefined) baseRequest.cyclePeriods = indicator.cyclePeriods + + return baseRequest + } + + // Selection functions for genetic algorithm + const selectParentByTournament = ( + fitnessResults: Array<{individual: GeneticIndividual, backtest: Backtest | null, fitness: number}>, + tournamentSize: number + ): GeneticIndividual => { + let bestIndividual = fitnessResults[0].individual + let bestFitness = fitnessResults[0].fitness + + for (let i = 0; i < tournamentSize; i++) { + const randomIndex = Math.floor(Math.random() * fitnessResults.length) + const candidate = fitnessResults[randomIndex] + + if (candidate.fitness > bestFitness) { + bestFitness = candidate.fitness + bestIndividual = candidate.individual + } + } + + return bestIndividual + } + + const selectParentByRouletteWheel = ( + fitnessResults: Array<{individual: GeneticIndividual, backtest: Backtest | null, fitness: number}> + ): GeneticIndividual => { + // Calculate total fitness + const totalFitness = fitnessResults.reduce((sum, result) => sum + Math.max(0, result.fitness), 0) + + if (totalFitness <= 0) { + // If all fitness scores are negative or zero, use tournament selection as fallback + return selectParentByTournament(fitnessResults, 3) + } + + // Generate random number between 0 and total fitness + const randomValue = Math.random() * totalFitness + + // Find the individual corresponding to this random value + let cumulativeFitness = 0 + for (const result of fitnessResults) { + cumulativeFitness += Math.max(0, result.fitness) + if (cumulativeFitness >= randomValue) { + return result.individual + } + } + + // Fallback to best individual + return fitnessResults[0].individual + } + + const selectParentByFitnessWeighted = ( + fitnessResults: Array<{individual: GeneticIndividual, backtest: Backtest | null, fitness: number}> + ): GeneticIndividual => { + // Normalize fitness scores to positive values and calculate selection probabilities + const minFitness = Math.min(...fitnessResults.map(r => r.fitness)) + const normalizedFitness = fitnessResults.map(result => ({ + ...result, + normalizedFitness: result.fitness - minFitness + 1 // Add 1 to avoid zero probabilities + })) + + const totalNormalizedFitness = normalizedFitness.reduce((sum, result) => sum + result.normalizedFitness, 0) + + // Calculate selection probabilities + const selectionProbabilities = normalizedFitness.map(result => ({ + ...result, + probability: result.normalizedFitness / totalNormalizedFitness + })) + + // Sort by probability (best first) + selectionProbabilities.sort((a, b) => b.probability - a.probability) + + // Use roulette wheel selection with normalized probabilities + const randomValue = Math.random() + let cumulativeProbability = 0 + + for (const result of selectionProbabilities) { + cumulativeProbability += result.probability + if (cumulativeProbability >= randomValue) { + return result.individual + } + } + + // Fallback to best individual + return selectionProbabilities[0].individual + } + + const selectParent = ( + fitnessResults: Array<{individual: GeneticIndividual, backtest: Backtest | null, fitness: number}> + ): GeneticIndividual => { + switch (formValues.selectionMethod) { + case 'roulette': + return selectParentByRouletteWheel(fitnessResults) + case 'fitness-weighted': + return selectParentByFitnessWeighted(fitnessResults) + case 'tournament': + default: + return selectParentByTournament(fitnessResults, 3) + } + } + + // Run backtest for an individual + const runBacktestForIndividual = useCallback(async (individual: GeneticIndividual): Promise => { + // Create money management (values are already in percentage format) + const moneyManagement: MoneyManagementRequest = { + name: `Genetic_${Date.now()}`, + timeframe: formValues.timeframe, + stopLoss: individual.stopLoss, + takeProfit: individual.takeProfit, + leverage: individual.leverage, + } + + // Create indicators + const indicators: IndicatorRequest[] = individual.indicators.map((indicator, index) => + convertToIndicatorRequest(indicator, index) + ) + + // Calculate loopback period based on number of indicators + const loopbackPeriod = individual.indicators.length === 1 + ? LOOPBACK_CONFIG.singleIndicator + : getRandomIntInRange(LOOPBACK_CONFIG.multipleIndicators) + + // Create scenario + const scenario: ScenarioRequest = { + name: `Genetic_Scenario_${Date.now()}`, + indicators: indicators, + loopbackPeriod: loopbackPeriod, + } + + // Create trading bot config + const config: TradingBotConfigRequest = { + accountName: accounts?.[0]?.name || 'default', + ticker: formValues.ticker, + timeframe: formValues.timeframe, + isForWatchingOnly: false, + botTradingBalance: formValues.balance, + name: `Genetic_Bot_${Date.now()}`, + flipPosition: false, + cooldownPeriod: individual.cooldownPeriod, + maxLossStreak: individual.maxLossStreak, + scenario: scenario, + moneyManagement: moneyManagement, + maxPositionTimeHours: individual.maxPositionTimeHours, + flipOnlyWhenInProfit: individual.flipOnlyWhenInProfit, + closeEarlyWhenProfitable: individual.closeEarlyWhenProfitable, + useSynthApi: individual.useSynthApi, + useForPositionSizing: individual.useForPositionSizing, + useForSignalFiltering: individual.useForSignalFiltering, + useForDynamicStopLoss: individual.useForDynamicStopLoss, + } + + // Run backtest + const request: RunBacktestRequest = { + config: config, + startDate: new Date(formValues.startDate), + endDate: new Date(formValues.endDate), + save: false, + } + + const backtest = await backtestClient.backtest_Run(request) + addBacktest(backtest) + + return backtest + }, [formValues.timeframe, formValues.ticker, formValues.balance, formValues.startDate, formValues.endDate, accounts, backtestClient, addBacktest]) + + // Two-phase genetic algorithm implementation with real-time updates + const runGeneticAlgorithm = useCallback(async () => { + setIsRunning(true) + setLoading(true) + setResults([]) + setCurrentGeneration(0) + setCurrentIndividual(0) + setBestFitness(0) + setShowResults(true) // Show results immediately for real-time updates + setBacktestTimes([]) + setStartTime(new Date()) + setEstimatedCompletion('Calculating...') + setCurrentPhase('phase1') + setBestSingleIndicators([]) + setEligibleIndicators(ALL_INDICATORS) + setRemovedIndicators(new Set()) + + const phase1Individuals = GENETIC_CONFIG.phase1Generations * geneticConfig.populationSize + const phase2Individuals = GENETIC_CONFIG.phase2Generations * geneticConfig.populationSize + const phase3Individuals = GENETIC_CONFIG.phase3Generations * geneticConfig.populationSize + const phase4Individuals = GENETIC_CONFIG.phase4Generations * geneticConfig.populationSize + const totalIndividuals = phase1Individuals + phase2Individuals + phase3Individuals + phase4Individuals + setTotalIndividuals(totalIndividuals) + + const t = new Toast('Starting two-phase genetic algorithm optimization...') + + try { + let processedIndividuals = 0 + let bestFitnessScore = 0 + let bestIndividual: GeneticIndividual | null = null + + // Phase 1: Test single indicators + t.update('info', 'Phase 1: Testing single indicators...') + setCurrentPhase('phase1') + + let population = Array.from({length: geneticConfig.populationSize}, () => createIndividual('phase1')) + const phase1Results: Array<{individual: GeneticIndividual, backtest: Backtest | null, fitness: number}> = [] + + for (let generation = 0; generation < GENETIC_CONFIG.phase1Generations; generation++) { + setCurrentGeneration(generation + 1) + + // Evaluate fitness for individuals one by one for real-time updates + const fitnessResults = [] + + for (let i = 0; i < population.length; i++) { + setCurrentIndividual(i + 1) + const individual = population[i] + + try { + const backtestStartTime = Date.now() + const backtest = await runBacktestForIndividual(individual) + const backtestEndTime = Date.now() + const backtestDuration = backtestEndTime - backtestStartTime + + const fitness = calculateFitnessScore(backtest, individual) + const result = {individual, backtest, fitness} + fitnessResults.push(result) + phase1Results.push(result) + + processedIndividuals++ + + // Track backtest times for average calculation + setBacktestTimes(prev => { + const newTimes = [...prev, backtestDuration] + return newTimes.slice(-10) + }) + + // Update results in real-time + setResults(prev => { + const newResults = [...prev, { + individual: result.individual, + backtest: result.backtest, + fitness: result.fitness, + generation: generation + 1, + phase: 'phase1', + }] + return newResults + }) + + // Update best fitness in real-time + if (fitness > bestFitnessScore) { + bestFitnessScore = fitness + bestIndividual = individual + setBestFitness(bestFitnessScore) + } + + // Calculate and update estimated completion time + const currentTimes = backtestTimes.length > 0 ? backtestTimes : [backtestDuration] + const averageTimeMs = currentTimes.reduce((sum, time) => sum + time, 0) / currentTimes.length + const estimatedCompletionTime = calculateEstimatedCompletion(processedIndividuals, totalIndividuals, averageTimeMs) + setEstimatedCompletion(estimatedCompletionTime) + + // Update progress toast + const progress = (processedIndividuals / totalIndividuals * 100).toFixed(1) + const avgTimeFormatted = formatTimeDuration(averageTimeMs) + t.update('info', `Phase 1 - Progress: ${progress}% - Gen ${generation + 1}/${GENETIC_CONFIG.phase1Generations} - Individual ${i + 1}/${geneticConfig.populationSize} - Best: ${bestFitnessScore.toFixed(2)} - Avg: ${avgTimeFormatted} - ETA: ${estimatedCompletionTime}`) + + } catch (error) { + console.error('Fitness calculation error:', error) + fitnessResults.push({individual, backtest: null, fitness: 0}) + processedIndividuals++ + } + } + + // Sort by fitness and create next generation + fitnessResults.sort((a, b) => b.fitness - a.fitness) + + // Store best single indicators for subsequent phases + if (generation === GENETIC_CONFIG.phase1Generations - 1) { + const topSingleIndicators = fitnessResults + .filter(result => result.fitness > 0) + .slice(0, Math.min(8, fitnessResults.length)) // Keep top 8 single indicators + setBestSingleIndicators(topSingleIndicators) + + // Remove bad indicators after phase 1 + removeBadIndicators(fitnessResults) + } + + // Create next generation for phase 1 + const newPopulation = [] + const eliteCount = Math.max(1, Math.floor(geneticConfig.populationSize * (formValues.elitismPercentage || 15) / 100)) + for (let i = 0; i < eliteCount; i++) { + newPopulation.push(fitnessResults[i].individual) + } + + while (newPopulation.length < geneticConfig.populationSize) { + const parent1 = selectParent(fitnessResults) + const parent2 = selectParent(fitnessResults) + + const child = {...parent1} + if (Math.random() < geneticConfig.crossoverRate) { + child.stopLoss = parent2.stopLoss + child.takeProfit = parent2.takeProfit + child.leverage = 1 + child.cooldownPeriod = parent2.cooldownPeriod + child.indicators = parent2.indicators + + // Validate risk-reward ratio after crossover + const minTakeProfit = child.stopLoss * 1.1 + if (child.takeProfit < minTakeProfit) { + child.takeProfit = minTakeProfit + getRandomInRange({ + min: 0, + max: getMaxTakeProfit() - minTakeProfit + }) + } + } + + // Mutation + if (Math.random() < geneticConfig.mutationRate) { + // Mutate stop loss + child.stopLoss = getRandomInRange(PARAMETER_RANGES.stopLoss); + // Adjust take profit to maintain minimum R:R ratio and respect max TP + const minTP = 0.9; + const maxTP = getMaxTakeProfit(); + child.takeProfit = getRandomInRange({ min: minTP, max: maxTP }); + const minSL = 0.5; + const maxSL = Math.min(child.takeProfit / 1.1, child.takeProfit - 0.1); + child.stopLoss = maxSL >= minSL ? getRandomInRange({ min: minSL, max: maxSL }) : minSL; + } + if (Math.random() < geneticConfig.mutationRate) { + // Mutate take profit while respecting minimum R:R ratio and max TP + const minTP = 0.9; + const maxTP = getMaxTakeProfit(); + child.takeProfit = getRandomInRange({ min: minTP, max: maxTP }); + const minSL = 0.5; + const maxSL = Math.min(child.takeProfit / 1.1, child.takeProfit - 0.1); + child.stopLoss = maxSL >= minSL ? getRandomInRange({ min: minSL, max: maxSL }) : minSL; + } + if (Math.random() < geneticConfig.mutationRate) { + // 50% chance to create completely new indicator, 50% chance to slightly modify existing + if (Math.random() < 0.5) { + child.indicators = [createRandomIndicator(eligibleIndicators)] + } else { + // Slight modification of existing indicator parameters + const existingIndicator = child.indicators[0] + const modifiedIndicator = {...existingIndicator} + + // Randomly modify one parameter with small variation + const paramKeys = Object.keys(modifiedIndicator).filter(key => + key !== 'type' && typeof modifiedIndicator[key as keyof GeneticIndicator] === 'number' + ) as Array + + if (paramKeys.length > 0) { + const randomParam = paramKeys[Math.floor(Math.random() * paramKeys.length)] + const currentValue = modifiedIndicator[randomParam] as number + const defaultValue = DEFAULT_INDICATOR_VALUES[randomParam as keyof typeof DEFAULT_INDICATOR_VALUES] + + // Add small random variation (±20% of the difference from default) + const variation = (currentValue - defaultValue) * 0.2 * (Math.random() - 0.5) + const newValue = Math.max(1, Math.round(currentValue + variation)) + + ;(modifiedIndicator as any)[randomParam] = newValue + child.indicators = [modifiedIndicator] + } + } + } + + // Enforce RR >= 1.1:1 + if (child.takeProfit < child.stopLoss * 1.1) { + child.takeProfit = Math.min(getMaxTakeProfit(), child.stopLoss * 1.1); + } + if (child.stopLoss > child.takeProfit / 1.1) { + child.stopLoss = Math.max(PARAMETER_RANGES.stopLoss.min, child.takeProfit / 1.1); + } + + newPopulation.push(child) + } + + population = newPopulation + } + + // Phase 2: Test 2-indicator combinations + t.update('info', 'Phase 2: Testing 2-indicator combinations...') + setCurrentPhase('phase2') + + const bestIndicators = bestSingleIndicators + population = Array.from({length: geneticConfig.populationSize}, () => createIndividual('phase2', bestIndicators)) + + for (let generation = 0; generation < GENETIC_CONFIG.phase2Generations; generation++) { + setCurrentGeneration(GENETIC_CONFIG.phase1Generations + generation + 1) + + const fitnessResults = [] + + for (let i = 0; i < population.length; i++) { + setCurrentIndividual(i + 1) + const individual = population[i] + + try { + const backtestStartTime = Date.now() + const backtest = await runBacktestForIndividual(individual) + const backtestEndTime = Date.now() + const backtestDuration = backtestEndTime - backtestStartTime + + const fitness = calculateFitnessScore(backtest, individual) + const result = {individual, backtest, fitness} + fitnessResults.push(result) + + processedIndividuals++ + + // Track backtest times for average calculation + setBacktestTimes(prev => { + const newTimes = [...prev, backtestDuration] + return newTimes.slice(-10) + }) + + // Update results in real-time + setResults(prev => { + const newResults = [...prev, { + individual: result.individual, + backtest: result.backtest, + fitness: result.fitness, + generation: GENETIC_CONFIG.phase1Generations + generation + 1, + phase: 'phase2', + }] + return newResults + }) + + // Update best fitness in real-time + if (fitness > bestFitnessScore) { + bestFitnessScore = fitness + bestIndividual = individual + setBestFitness(bestFitnessScore) + } + + // Calculate and update estimated completion time + const currentTimes = backtestTimes.length > 0 ? backtestTimes : [backtestDuration] + const averageTimeMs = currentTimes.reduce((sum, time) => sum + time, 0) / currentTimes.length + const estimatedCompletionTime = calculateEstimatedCompletion(processedIndividuals, totalIndividuals, averageTimeMs) + setEstimatedCompletion(estimatedCompletionTime) + + // Update progress toast + const progress = (processedIndividuals / totalIndividuals * 100).toFixed(1) + const avgTimeFormatted = formatTimeDuration(averageTimeMs) + t.update('info', `Phase 2 - Progress: ${progress}% - Gen ${generation + 1}/${GENETIC_CONFIG.phase2Generations} - Individual ${i + 1}/${geneticConfig.populationSize} - Best: ${bestFitnessScore.toFixed(2)} - Avg: ${avgTimeFormatted} - ETA: ${estimatedCompletionTime}`) + + } catch (error) { + console.error('Fitness calculation error:', error) + fitnessResults.push({individual, backtest: null, fitness: 0}) + processedIndividuals++ + } + } + + // Sort by fitness and create next generation + fitnessResults.sort((a, b) => b.fitness - a.fitness) + + // Create next generation for phase 2 + const newPopulation = [] + const eliteCount = Math.max(1, Math.floor(geneticConfig.populationSize * (formValues.elitismPercentage || 15) / 100)) + for (let i = 0; i < eliteCount; i++) { + newPopulation.push(fitnessResults[i].individual) + } + + while (newPopulation.length < geneticConfig.populationSize) { + const parent1 = selectParent(fitnessResults) + const parent2 = selectParent(fitnessResults) + + const child = {...parent1} + if (Math.random() < geneticConfig.crossoverRate) { + child.stopLoss = parent2.stopLoss + child.takeProfit = parent2.takeProfit + child.leverage = 1 + child.cooldownPeriod = parent2.cooldownPeriod + child.indicators = parent2.indicators + + // Validate risk-reward ratio after crossover + const minTakeProfit = child.stopLoss * 1.1 + if (child.takeProfit < minTakeProfit) { + child.takeProfit = minTakeProfit + getRandomInRange({ + min: 0, + max: getMaxTakeProfit() - minTakeProfit + }) + } + } + + // Mutation + if (Math.random() < geneticConfig.mutationRate) { + // Mutate stop loss + child.stopLoss = getRandomInRange(PARAMETER_RANGES.stopLoss); + // Adjust take profit to maintain minimum R:R ratio and respect max TP + const minTP = 0.9; + const maxTP = getMaxTakeProfit(); + child.takeProfit = getRandomInRange({ min: minTP, max: maxTP }); + const minSL = 0.5; + const maxSL = Math.min(child.takeProfit / 1.1, child.takeProfit - 0.1); + child.stopLoss = maxSL >= minSL ? getRandomInRange({ min: minSL, max: maxSL }) : minSL; + } + if (Math.random() < geneticConfig.mutationRate) { + // Mutate take profit while respecting minimum R:R ratio and max TP + const minTP = 0.9; + const maxTP = getMaxTakeProfit(); + child.takeProfit = getRandomInRange({ min: minTP, max: maxTP }); + const minSL = 0.5; + const maxSL = Math.min(child.takeProfit / 1.1, child.takeProfit - 0.1); + child.stopLoss = maxSL >= minSL ? getRandomInRange({ min: minSL, max: maxSL }) : minSL; + } + if (Math.random() < geneticConfig.mutationRate) { + // 50% chance to create new combination, 50% chance to slightly modify existing + if (Math.random() < 0.5) { + // Create new 2-indicator combination + const combinations = createIndicatorCombinations(bestIndicators, 2) + if (combinations.length > 0) { + const randomCombination = combinations[Math.floor(Math.random() * combinations.length)] + child.indicators = randomCombination + } + } else { + // Slight modification of existing indicator parameters + const modifiedIndicators = child.indicators.map(indicator => { + const modifiedIndicator = {...indicator} + + // Randomly modify one parameter with small variation + const paramKeys = Object.keys(modifiedIndicator).filter(key => + key !== 'type' && typeof modifiedIndicator[key as keyof GeneticIndicator] === 'number' + ) as Array + + if (paramKeys.length > 0 && Math.random() < 0.3) { // 30% chance to modify each indicator + const randomParam = paramKeys[Math.floor(Math.random() * paramKeys.length)] + const currentValue = modifiedIndicator[randomParam] as number + const defaultValue = DEFAULT_INDICATOR_VALUES[randomParam as keyof typeof DEFAULT_INDICATOR_VALUES] + + // Add small random variation (±20% of the difference from default) + const variation = (currentValue - defaultValue) * 0.2 * (Math.random() - 0.5) + const newValue = Math.max(1, Math.round(currentValue + variation)) + + ;(modifiedIndicator as any)[randomParam] = newValue + } + + return modifiedIndicator + }) + + child.indicators = modifiedIndicators + } + } + + // Enforce RR >= 1.1:1 + if (child.takeProfit < child.stopLoss * 1.1) { + child.takeProfit = Math.min(getMaxTakeProfit(), child.stopLoss * 1.1); + } + if (child.stopLoss > child.takeProfit / 1.1) { + child.stopLoss = Math.max(PARAMETER_RANGES.stopLoss.min, child.takeProfit / 1.1); + } + + newPopulation.push(child) + } + + population = newPopulation + } + + // Phase 3: Test 3-indicator combinations + t.update('info', 'Phase 3: Testing 3-indicator combinations...') + setCurrentPhase('phase3') + + population = Array.from({length: geneticConfig.populationSize}, () => createIndividual('phase3', bestIndicators)) + + for (let generation = 0; generation < GENETIC_CONFIG.phase3Generations; generation++) { + setCurrentGeneration(GENETIC_CONFIG.phase1Generations + GENETIC_CONFIG.phase2Generations + generation + 1) + + const fitnessResults = [] + + for (let i = 0; i < population.length; i++) { + setCurrentIndividual(i + 1) + const individual = population[i] + + try { + const backtestStartTime = Date.now() + const backtest = await runBacktestForIndividual(individual) + const backtestEndTime = Date.now() + const backtestDuration = backtestEndTime - backtestStartTime + + const fitness = calculateFitnessScore(backtest, individual) + const result = {individual, backtest, fitness} + fitnessResults.push(result) + + processedIndividuals++ + + // Track backtest times for average calculation + setBacktestTimes(prev => { + const newTimes = [...prev, backtestDuration] + return newTimes.slice(-10) + }) + + // Update results in real-time + setResults(prev => { + const newResults = [...prev, { + individual: result.individual, + backtest: result.backtest, + fitness: result.fitness, + generation: GENETIC_CONFIG.phase1Generations + GENETIC_CONFIG.phase2Generations + generation + 1, + phase: 'phase3', + }] + return newResults + }) + + // Update best fitness in real-time + if (fitness > bestFitnessScore) { + bestFitnessScore = fitness + bestIndividual = individual + setBestFitness(bestFitnessScore) + } + + // Calculate and update estimated completion time + const currentTimes = backtestTimes.length > 0 ? backtestTimes : [backtestDuration] + const averageTimeMs = currentTimes.reduce((sum, time) => sum + time, 0) / currentTimes.length + const estimatedCompletionTime = calculateEstimatedCompletion(processedIndividuals, totalIndividuals, averageTimeMs) + setEstimatedCompletion(estimatedCompletionTime) + + // Update progress toast + const progress = (processedIndividuals / totalIndividuals * 100).toFixed(1) + const avgTimeFormatted = formatTimeDuration(averageTimeMs) + t.update('info', `Phase 3 - Progress: ${progress}% - Gen ${generation + 1}/${GENETIC_CONFIG.phase3Generations} - Individual ${i + 1}/${geneticConfig.populationSize} - Best: ${bestFitnessScore.toFixed(2)} - Avg: ${avgTimeFormatted} - ETA: ${estimatedCompletionTime}`) + + } catch (error) { + console.error('Fitness calculation error:', error) + fitnessResults.push({individual, backtest: null, fitness: 0}) + processedIndividuals++ + } + } + + // Sort by fitness and create next generation + fitnessResults.sort((a, b) => b.fitness - a.fitness) + + // Create next generation for phase 3 + const newPopulation = [] + const eliteCount = Math.max(1, Math.floor(geneticConfig.populationSize * (formValues.elitismPercentage || 15) / 100)) + for (let i = 0; i < eliteCount; i++) { + newPopulation.push(fitnessResults[i].individual) + } + + while (newPopulation.length < geneticConfig.populationSize) { + const parent1 = selectParent(fitnessResults) + const parent2 = selectParent(fitnessResults) + + const child = {...parent1} + if (Math.random() < geneticConfig.crossoverRate) { + child.stopLoss = parent2.stopLoss + child.takeProfit = parent2.takeProfit + child.leverage = 1 + child.cooldownPeriod = parent2.cooldownPeriod + child.indicators = parent2.indicators + + // Validate risk-reward ratio after crossover + const minTakeProfit = child.stopLoss * 1.1 + if (child.takeProfit < minTakeProfit) { + child.takeProfit = minTakeProfit + getRandomInRange({ + min: 0, + max: getMaxTakeProfit() - minTakeProfit + }) + } + } + + // Mutation + if (Math.random() < geneticConfig.mutationRate) { + // Mutate stop loss + child.stopLoss = getRandomInRange(PARAMETER_RANGES.stopLoss); + // Adjust take profit to maintain minimum R:R ratio and respect max TP + const minTP = 0.9; + const maxTP = getMaxTakeProfit(); + child.takeProfit = getRandomInRange({ min: minTP, max: maxTP }); + const minSL = 0.5; + const maxSL = Math.min(child.takeProfit / 1.1, child.takeProfit - 0.1); + child.stopLoss = maxSL >= minSL ? getRandomInRange({ min: minSL, max: maxSL }) : minSL; + } + if (Math.random() < geneticConfig.mutationRate) { + // Mutate take profit while respecting minimum R:R ratio and max TP + const minTP = 0.9; + const maxTP = getMaxTakeProfit(); + child.takeProfit = getRandomInRange({ min: minTP, max: maxTP }); + const minSL = 0.5; + const maxSL = Math.min(child.takeProfit / 1.1, child.takeProfit - 0.1); + child.stopLoss = maxSL >= minSL ? getRandomInRange({ min: minSL, max: maxSL }) : minSL; + } + if (Math.random() < geneticConfig.mutationRate) { + // 50% chance to create new combination, 50% chance to slightly modify existing + if (Math.random() < 0.5) { + // Create new 3-indicator combination + const combinations = createIndicatorCombinations(bestIndicators, 3) + if (combinations.length > 0) { + const randomCombination = combinations[Math.floor(Math.random() * combinations.length)] + child.indicators = randomCombination + } + } else { + // Slight modification of existing indicator parameters + const modifiedIndicators = child.indicators.map(indicator => { + const modifiedIndicator = {...indicator} + + // Randomly modify one parameter with small variation + const paramKeys = Object.keys(modifiedIndicator).filter(key => + key !== 'type' && typeof modifiedIndicator[key as keyof GeneticIndicator] === 'number' + ) as Array + + if (paramKeys.length > 0 && Math.random() < 0.3) { // 30% chance to modify each indicator + const randomParam = paramKeys[Math.floor(Math.random() * paramKeys.length)] + const currentValue = modifiedIndicator[randomParam] as number + const defaultValue = DEFAULT_INDICATOR_VALUES[randomParam as keyof typeof DEFAULT_INDICATOR_VALUES] + + // Add small random variation (±20% of the difference from default) + const variation = (currentValue - defaultValue) * 0.2 * (Math.random() - 0.5) + const newValue = Math.max(1, Math.round(currentValue + variation)) + + ;(modifiedIndicator as any)[randomParam] = newValue + } + + return modifiedIndicator + }) + + child.indicators = modifiedIndicators + } + } + + // Enforce RR >= 1.1:1 + if (child.takeProfit < child.stopLoss * 1.1) { + child.takeProfit = Math.min(getMaxTakeProfit(), child.stopLoss * 1.1); + } + if (child.stopLoss > child.takeProfit / 1.1) { + child.stopLoss = Math.max(PARAMETER_RANGES.stopLoss.min, child.takeProfit / 1.1); + } + + newPopulation.push(child) + } + + population = newPopulation + } + + // Phase 4: Test 4-indicator combinations + t.update('info', 'Phase 4: Testing 4-indicator combinations...') + setCurrentPhase('phase4') + + population = Array.from({length: geneticConfig.populationSize}, () => createIndividual('phase4', bestIndicators)) + + for (let generation = 0; generation < GENETIC_CONFIG.phase4Generations; generation++) { + setCurrentGeneration(GENETIC_CONFIG.phase1Generations + GENETIC_CONFIG.phase2Generations + GENETIC_CONFIG.phase3Generations + generation + 1) + + const fitnessResults = [] + + for (let i = 0; i < population.length; i++) { + setCurrentIndividual(i + 1) + const individual = population[i] + + try { + const backtestStartTime = Date.now() + const backtest = await runBacktestForIndividual(individual) + const backtestEndTime = Date.now() + const backtestDuration = backtestEndTime - backtestStartTime + + const fitness = calculateFitnessScore(backtest, individual) + const result = {individual, backtest, fitness} + fitnessResults.push(result) + + processedIndividuals++ + + // Track backtest times for average calculation + setBacktestTimes(prev => { + const newTimes = [...prev, backtestDuration] + return newTimes.slice(-10) + }) + + // Update results in real-time + setResults(prev => { + const newResults = [...prev, { + individual: result.individual, + backtest: result.backtest, + fitness: result.fitness, + generation: GENETIC_CONFIG.phase1Generations + GENETIC_CONFIG.phase2Generations + GENETIC_CONFIG.phase3Generations + generation + 1, + phase: 'phase4', + }] + return newResults + }) + + // Update best fitness in real-time + if (fitness > bestFitnessScore) { + bestFitnessScore = fitness + bestIndividual = individual + setBestFitness(bestFitnessScore) + } + + // Calculate and update estimated completion time + const currentTimes = backtestTimes.length > 0 ? backtestTimes : [backtestDuration] + const averageTimeMs = currentTimes.reduce((sum, time) => sum + time, 0) / currentTimes.length + const estimatedCompletionTime = calculateEstimatedCompletion(processedIndividuals, totalIndividuals, averageTimeMs) + setEstimatedCompletion(estimatedCompletionTime) + + // Update progress toast + const progress = (processedIndividuals / totalIndividuals * 100).toFixed(1) + const avgTimeFormatted = formatTimeDuration(averageTimeMs) + t.update('info', `Phase 4 - Progress: ${progress}% - Gen ${generation + 1}/${GENETIC_CONFIG.phase4Generations} - Individual ${i + 1}/${geneticConfig.populationSize} - Best: ${bestFitnessScore.toFixed(2)} - Avg: ${avgTimeFormatted} - ETA: ${estimatedCompletionTime}`) + + } catch (error) { + console.error('Fitness calculation error:', error) + fitnessResults.push({individual, backtest: null, fitness: 0}) + processedIndividuals++ + } + } + + // Sort by fitness and create next generation + fitnessResults.sort((a, b) => b.fitness - a.fitness) + + // Create next generation for phase 4 + const newPopulation = [] + const eliteCount = Math.max(1, Math.floor(geneticConfig.populationSize * (formValues.elitismPercentage || 15) / 100)) + for (let i = 0; i < eliteCount; i++) { + newPopulation.push(fitnessResults[i].individual) + } + + while (newPopulation.length < geneticConfig.populationSize) { + const parent1 = selectParent(fitnessResults) + const parent2 = selectParent(fitnessResults) + + const child = {...parent1} + if (Math.random() < geneticConfig.crossoverRate) { + child.stopLoss = parent2.stopLoss + child.takeProfit = parent2.takeProfit + child.leverage = 1 + child.cooldownPeriod = parent2.cooldownPeriod + child.indicators = parent2.indicators + + // Validate risk-reward ratio after crossover + const minTakeProfit = child.stopLoss * 1.1 + if (child.takeProfit < minTakeProfit) { + child.takeProfit = minTakeProfit + getRandomInRange({ + min: 0, + max: getMaxTakeProfit() - minTakeProfit + }) + } + } + + // Mutation + if (Math.random() < geneticConfig.mutationRate) { + // Mutate stop loss + child.stopLoss = getRandomInRange(PARAMETER_RANGES.stopLoss); + // Adjust take profit to maintain minimum R:R ratio and respect max TP + const minTP = 0.9; + const maxTP = getMaxTakeProfit(); + child.takeProfit = getRandomInRange({ min: minTP, max: maxTP }); + const minSL = 0.5; + const maxSL = Math.min(child.takeProfit / 1.1, child.takeProfit - 0.1); + child.stopLoss = maxSL >= minSL ? getRandomInRange({ min: minSL, max: maxSL }) : minSL; + } + if (Math.random() < geneticConfig.mutationRate) { + // Mutate take profit while respecting minimum R:R ratio and max TP + const minTP = 0.9; + const maxTP = getMaxTakeProfit(); + child.takeProfit = getRandomInRange({ min: minTP, max: maxTP }); + const minSL = 0.5; + const maxSL = Math.min(child.takeProfit / 1.1, child.takeProfit - 0.1); + child.stopLoss = maxSL >= minSL ? getRandomInRange({ min: minSL, max: maxSL }) : minSL; + } + if (Math.random() < geneticConfig.mutationRate) { + // 50% chance to create new combination, 50% chance to slightly modify existing + if (Math.random() < 0.5) { + // Create new 4-indicator combination + const combinations = createIndicatorCombinations(bestIndicators, 4) + if (combinations.length > 0) { + const randomCombination = combinations[Math.floor(Math.random() * combinations.length)] + child.indicators = randomCombination + } + } else { + // Slight modification of existing indicator parameters + const modifiedIndicators = child.indicators.map(indicator => { + const modifiedIndicator = {...indicator} + + // Randomly modify one parameter with small variation + const paramKeys = Object.keys(modifiedIndicator).filter(key => + key !== 'type' && typeof modifiedIndicator[key as keyof GeneticIndicator] === 'number' + ) as Array + + if (paramKeys.length > 0 && Math.random() < 0.3) { // 30% chance to modify each indicator + const randomParam = paramKeys[Math.floor(Math.random() * paramKeys.length)] + const currentValue = modifiedIndicator[randomParam] as number + const defaultValue = DEFAULT_INDICATOR_VALUES[randomParam as keyof typeof DEFAULT_INDICATOR_VALUES] + + // Add small random variation (±20% of the difference from default) + const variation = (currentValue - defaultValue) * 0.2 * (Math.random() - 0.5) + const newValue = Math.max(1, Math.round(currentValue + variation)) + + ;(modifiedIndicator as any)[randomParam] = newValue + } + + return modifiedIndicator + }) + + child.indicators = modifiedIndicators + } + } + + // Enforce RR >= 1.1:1 + if (child.takeProfit < child.stopLoss * 1.1) { + child.takeProfit = Math.min(getMaxTakeProfit(), child.stopLoss * 1.1); + } + if (child.stopLoss > child.takeProfit / 1.1) { + child.stopLoss = Math.max(PARAMETER_RANGES.stopLoss.min, child.takeProfit / 1.1); + } + + newPopulation.push(child) + } + + population = newPopulation + } + + t.update('success', 'Four-phase genetic algorithm completed!') + + } catch (error) { + console.error('Genetic algorithm error:', error) + t.update('error', 'Genetic algorithm failed: ' + error) + } finally { + setIsRunning(false) + setLoading(false) + } + }, [geneticConfig, runBacktestForIndividual, calculateFitnessScore]) + + // Prepare 3D plot data + const plotData = results.length > 0 ? [ + { + type: 'scatter3d' as const, + mode: 'markers' as const, + x: results.map(r => r.fitness), + y: results.map(r => r.backtest?.score || 0), + z: results.map(r => r.backtest?.winRate || 0), + marker: { + size: 5, + color: results.map(r => r.generation), + colorscale: 'Viridis' as const, + opacity: 0.8, + }, + text: results.map(r => { + const loopbackPeriod = r.individual.indicators.length === 1 + ? LOOPBACK_CONFIG.singleIndicator + : '5-15' + + // Format indicator parameters for display + const indicatorDetails = r.individual.indicators.map((indicator: GeneticIndicator, index: number) => { + let details = indicator.type.toString() + if (indicator.period) details += ` (${indicator.period})` + if (indicator.fastPeriods && indicator.slowPeriods) details += ` (${indicator.fastPeriods}/${indicator.slowPeriods})` + if (indicator.multiplier) details += ` (${indicator.multiplier.toFixed(1)}x)` + return details + }).join(', ') + + const riskRewardRatio = r.individual.takeProfit / r.individual.stopLoss + const riskRewardColor = riskRewardRatio >= 2 ? '🟢' : riskRewardRatio >= 1.5 ? '🟡' : '🔴' + + return `Phase: ${r.phase || 'Unknown'}
` + + `Gen: ${r.generation}
` + + `Fitness: ${r.fitness.toFixed(2)}
` + + `PnL: $${r.backtest?.statistics?.totalPnL?.toFixed(2) || 'N/A'}
` + + `Win Rate: ${r.backtest?.winRate?.toFixed(1) || 'N/A'}%
` + + `SL: ${r.individual.stopLoss.toFixed(1)}%
` + + `TP: ${r.individual.takeProfit.toFixed(1)}%
` + + `R/R: ${riskRewardColor} ${riskRewardRatio.toFixed(2)}
` + + `Leverage: ${r.individual.leverage}x
` + + `Cooldown: ${r.individual.cooldownPeriod} candles
` + + `Max Loss Streak: ${r.individual.maxLossStreak}
` + + `Max Time: ${r.individual.maxPositionTimeHours}h
` + + `Indicators: ${indicatorDetails}
` + + `Loopback: ${loopbackPeriod}` + }), + hovertemplate: '%{text}', + }, + ] : [] + + return ( +
+

Genetic Algorithm Optimization

+ + {/* Configuration Panel */} +
+
+

Genetic Algorithm Configuration

+ + {/* Selection Method Descriptions */} +
+
+

Selection Methods:

+
    +
  • Tournament Selection: Randomly selects 3 individuals and picks the best one
  • +
  • Roulette Wheel: Selects individuals based on their fitness proportion
  • +
  • Fitness Weighted: Normalizes fitness scores and uses weighted probabilities
  • +
+

Elitism: Preserves the top percentage of individuals unchanged in the next generation

+
+
+ + {/* Four-Phase Optimization Strategy */} +
+
+

Four-Phase Optimization Strategy:

+

+ Phase 1: Tests individual indicators (3 generations) to identify the best performing single indicators. +

+

+ Phase 2: Tests 2-indicator combinations (2 generations) using the top 8 single indicators. +

+

+ Phase 3: Tests 3-indicator combinations (2 generations) using the top 8 single indicators. +

+

+ Phase 4: Tests 4-indicator combinations (2 generations) using the top 8 single indicators. +

+

+ No Duplicates: Each scenario contains unique indicator types to avoid redundancy. +

+

+ Parameter Optimization: Uses CustomScenario defaults as starting points with 70% bias towards proven values. +

+
+
+ + {/* Risk-Reward Ratio Constraint */} +
+
+

Risk-Reward Ratio Constraint:

+

+ All generated individuals are guaranteed to have a positive risk-reward ratio (R:R ≥ 1.1). + The algorithm automatically adjusts take profit levels to ensure profitable trading strategies. +

+

+ Parameter Ranges: Stop Loss: 2-15% | Take Profit: 0.9-{formValues.maxTakeProfit || 4}% | Minimum TP: 0.9% +

+

+ R:R Ratio Legend: 🟢 ≥2.0 (Excellent) | 🟡 1.5-2.0 (Good) | 🔴 1.1-1.5 (Minimum) +

+
+
+ + {/* Eligible Indicators Feature */} +
+
+

Adaptive Indicator Selection:

+

+ Manual Selection: Choose which indicators to include in the optimization process. +

+

+ Automatic Removal: After Phase 1, indicators with fitness scores below 0.1 are automatically removed from consideration. +

+

+ Smart Optimization: The algorithm focuses on promising indicators, improving convergence and reducing computation time. +

+

+ No Duplicates: Each scenario is guaranteed to contain unique indicator types only. +

+
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ {ALL_INDICATORS.map(indicator => ( + + ))} +
+
+ {eligibleIndicators.length} of {ALL_INDICATORS.length} indicators selected +
+
+
+
+
+ + {/* Control Panel */} +
+ + + + + {showResults && ( + + )} +
+ + {/* Progress */} + {isRunning && ( +
+
+ Phase: {currentPhase === 'phase1' ? '1 - Single Indicators' : currentPhase === 'phase2' ? '2 - 2-Indicators' : currentPhase === 'phase3' ? '3 - 3-Indicators' : '4 - 4-Indicators'} + Generation: {currentGeneration}/{GENETIC_CONFIG.phase1Generations + GENETIC_CONFIG.phase2Generations + GENETIC_CONFIG.phase3Generations + GENETIC_CONFIG.phase4Generations} + Individual: {currentIndividual}/{geneticConfig.populationSize} + Best Fitness: {bestFitness.toFixed(2)} +
+
+ Overall Progress: {totalIndividuals > 0 ? Math.round(((currentGeneration - 1) * geneticConfig.populationSize + currentIndividual) / totalIndividuals * 100) : 0}% + Results: {results.length} + Best Single Indicators: {bestSingleIndicators.length} +
+
+ Eligible Indicators: {eligibleIndicators.length}/{ALL_INDICATORS.length} + Removed Indicators: {removedIndicators.size} + {removedIndicators.size > 0 && ( + + Removed: {Array.from(removedIndicators).slice(0, 3).join(', ')} + {removedIndicators.size > 3 && ` +${removedIndicators.size - 3} more`} + + )} +
+
+ Average Time per Backtest: {backtestTimes.length > 0 ? formatTimeDuration(backtestTimes.reduce((sum, time) => sum + time, 0) / backtestTimes.length) : 'Calculating...'} + Estimated Completion: {estimatedCompletion} +
+ +
+ )} + + {/* Results */} + {showResults && results.length > 0 && ( +
+ {/* 3D Visualization */} +
+
+

3D Optimization Results

+ +
+
+ + {/* Results Table */} +
+
+

Best Results

+ r.backtest).filter(Boolean) as Backtest[]} displaySummary={false} /> +
+
+
+ )} +
+ ) +} + +export default BacktestGenetic \ No newline at end of file diff --git a/src/Managing.WebApp/src/types/genetic-js.d.ts b/src/Managing.WebApp/src/types/genetic-js.d.ts new file mode 100644 index 0000000..0e2ff5a --- /dev/null +++ b/src/Managing.WebApp/src/types/genetic-js.d.ts @@ -0,0 +1,65 @@ +declare module 'genetic-js' { + interface GeneticConfig { + size?: number + crossover?: number + mutation?: number + iterations?: number + fittestAlwaysSurvives?: boolean + webWorkers?: boolean + skip?: number + maxResults?: number + } + + interface GeneticStats { + generation: number + fittest: any + fitness: number + average: number + worst: number + } + + interface Genetic { + seed: () => any + fitness: (individual: any) => number | Promise + mutate?: (individual: any) => any + crossover?: (mother: any, father: any) => [any, any] + select1?: (population: any[]) => any + select2?: (population: any[]) => [any, any] + generation?: (population: any[], generation: number, stats: GeneticStats) => boolean | Promise + notification?: (population: any[], generation: number, stats: GeneticStats, isFinished: boolean) => void + optimize: any + evolve: () => Promise + size: number + crossover: number + mutation: number + iterations: number + fittestAlwaysSurvives: boolean + } + + interface GeneticStatic { + create: (config?: GeneticConfig) => Genetic + Optimize: { + Minimize: any + Maximize: any + } + Select1: { + Tournament2: any + Tournament3: any + Fittest: any + Random: any + RandomLinearRank: any + Sequential: any + } + Select2: { + Tournament2: any + Tournament3: any + Random: any + RandomLinearRank: any + Sequential: any + FittestRandom: any + } + } + + const Genetic: GeneticStatic + export = Genetic +} \ No newline at end of file