messy work. backtests / mock-data
This commit is contained in:
parent
4e4a048988
commit
fa70ada2bb
51 changed files with 2576 additions and 887 deletions
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue