messy work. backtests / mock-data

This commit is contained in:
Boki 2025-07-03 08:37:23 -04:00
parent 4e4a048988
commit fa70ada2bb
51 changed files with 2576 additions and 887 deletions

View file

@ -1,169 +1,175 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { BacktestService } from '../services/backtestService';
import type { BacktestConfig, BacktestResult, BacktestStatus } from '../types';
import type { BacktestJob, BacktestRequest, BacktestResult } from '../services/backtestApi';
import { backtestApi, } from '../services/backtestApi';
export function useBacktest() {
const [backtestId, setBacktestId] = useState<string | null>(null);
const [config, setConfig] = useState<BacktestConfig | null>(null);
const [status, setStatus] = useState<BacktestStatus>('idle');
interface UseBacktestReturn {
// State
backtest: BacktestJob | null;
results: BacktestResult | null;
isLoading: boolean;
isPolling: boolean;
error: string | null;
// Actions
createBacktest: (request: BacktestRequest) => Promise<void>;
cancelBacktest: () => Promise<void>;
reset: () => void;
}
export function useBacktest(): UseBacktestReturn {
const [backtest, setBacktest] = useState<BacktestJob | null>(null);
const [results, setResults] = useState<BacktestResult | null>(null);
const [currentTime, setCurrentTime] = useState<number | null>(null);
const [progress, setProgress] = useState<number>(0);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isPolling, setIsPolling] = useState(false);
const [error, setError] = useState<string | null>(null);
const pollingIntervalRef = useRef<NodeJS.Timeout | null>(null);
const cleanupRef = useRef<(() => void) | null>(null);
const handleConfigSubmit = useCallback(async (newConfig: BacktestConfig) => {
// Poll for status updates
const pollStatus = useCallback(async (backtestId: string) => {
try {
setIsLoading(true);
setError(null);
const updatedBacktest = await backtestApi.getBacktest(backtestId);
setBacktest(updatedBacktest);
if (updatedBacktest.status === 'completed') {
// Fetch results
const backtestResults = await backtestApi.getBacktestResults(backtestId);
setResults(backtestResults);
setIsPolling(false);
// Clear polling interval
if (pollingIntervalRef.current) {
clearInterval(pollingIntervalRef.current);
pollingIntervalRef.current = null;
}
} else if (updatedBacktest.status === 'failed' || updatedBacktest.status === 'cancelled') {
setIsPolling(false);
// Clear polling interval
if (pollingIntervalRef.current) {
clearInterval(pollingIntervalRef.current);
pollingIntervalRef.current = null;
}
if (updatedBacktest.status === 'failed' && updatedBacktest.error) {
setError(updatedBacktest.error);
}
}
} catch (err) {
console.error('Error polling backtest status:', err);
// Don't stop polling on transient errors
}
}, []);
// Create a new backtest
const createBacktest = useCallback(async (request: BacktestRequest) => {
setIsLoading(true);
setError(null);
setResults(null);
try {
const newBacktest = await backtestApi.createBacktest(request);
setBacktest(newBacktest);
// Create backtest
const { id } = await BacktestService.createBacktest(newConfig);
// Start polling for updates
setIsPolling(true);
pollingIntervalRef.current = setInterval(() => {
pollStatus(newBacktest.id);
}, 2000); // Poll every 2 seconds
setBacktestId(id);
setConfig(newConfig);
setStatus('configured');
setResults(null);
setCurrentTime(null);
setProgress(0);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to create backtest');
} finally {
setIsLoading(false);
}
}, []);
}, [pollStatus]);
const handleStart = useCallback(async () => {
if (!backtestId) return;
// Cancel running backtest
const cancelBacktest = useCallback(async () => {
if (!backtest || backtest.status !== 'running') return;
try {
setIsLoading(true);
setError(null);
await backtestApi.cancelBacktest(backtest.id);
setBacktest({ ...backtest, status: 'cancelled' });
setIsPolling(false);
await BacktestService.startBacktest(backtestId);
setStatus('running');
// Start polling for updates
cleanupRef.current = await BacktestService.pollBacktestUpdates(
backtestId,
(newStatus, newProgress, newTime) => {
setStatus(newStatus);
if (newProgress !== undefined) setProgress(newProgress);
if (newTime !== undefined) setCurrentTime(newTime);
// Fetch full results when completed
if (newStatus === 'completed') {
BacktestService.getBacktestResults(backtestId).then(setResults);
}
}
);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to start backtest');
} finally {
setIsLoading(false);
}
}, [backtestId]);
const handlePause = useCallback(async () => {
if (!backtestId) return;
try {
setIsLoading(true);
setError(null);
await BacktestService.pauseBacktest(backtestId);
setStatus('paused');
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to pause backtest');
} finally {
setIsLoading(false);
}
}, [backtestId]);
const handleResume = useCallback(async () => {
if (!backtestId) return;
try {
setIsLoading(true);
setError(null);
await BacktestService.resumeBacktest(backtestId);
setStatus('running');
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to resume backtest');
} finally {
setIsLoading(false);
}
}, [backtestId]);
const handleStop = useCallback(async () => {
if (!backtestId) return;
try {
setIsLoading(true);
setError(null);
await BacktestService.stopBacktest(backtestId);
setStatus('stopped');
// Stop polling
if (cleanupRef.current) {
cleanupRef.current();
cleanupRef.current = null;
// Clear polling interval
if (pollingIntervalRef.current) {
clearInterval(pollingIntervalRef.current);
pollingIntervalRef.current = null;
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to stop backtest');
} finally {
setIsLoading(false);
setError(err instanceof Error ? err.message : 'Failed to cancel backtest');
}
}, [backtestId]);
}, [backtest]);
const handleStep = useCallback(async () => {
if (!backtestId) return;
try {
setIsLoading(true);
setError(null);
await BacktestService.stepBacktest(backtestId);
// Get updated status
const statusUpdate = await BacktestService.getBacktestStatus(backtestId);
setStatus(statusUpdate.status);
if (statusUpdate.progress !== undefined) setProgress(statusUpdate.progress);
if (statusUpdate.currentTime !== undefined) setCurrentTime(statusUpdate.currentTime);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to step backtest');
} finally {
setIsLoading(false);
// Reset state
const reset = useCallback(() => {
setBacktest(null);
setResults(null);
setError(null);
setIsLoading(false);
setIsPolling(false);
// Clear polling interval
if (pollingIntervalRef.current) {
clearInterval(pollingIntervalRef.current);
pollingIntervalRef.current = null;
}
}, [backtestId]);
}, []);
// Cleanup on unmount
useEffect(() => {
return () => {
if (cleanupRef.current) {
cleanupRef.current();
if (pollingIntervalRef.current) {
clearInterval(pollingIntervalRef.current);
}
};
}, []);
return {
backtestId,
config,
status,
backtest,
results,
currentTime,
progress,
error,
isLoading,
handleConfigSubmit,
handleStart,
handlePause,
handleResume,
handleStop,
handleStep,
isPolling,
error,
createBacktest,
cancelBacktest,
reset,
};
}
// Separate hook for listing backtests
interface UseBacktestListReturn {
backtests: BacktestJob[];
isLoading: boolean;
error: string | null;
loadBacktests: (limit?: number, offset?: number) => Promise<void>;
}
export function useBacktestList(): UseBacktestListReturn {
const [backtests, setBacktests] = useState<BacktestJob[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const loadBacktests = useCallback(async (limit = 50, offset = 0) => {
setIsLoading(true);
setError(null);
try {
const list = await backtestApi.listBacktests(limit, offset);
setBacktests(list);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load backtests');
} finally {
setIsLoading(false);
}
}, []);
return {
backtests,
isLoading,
error,
loadBacktests,
};
}