Files
lowkey_backtest/frontend/src/composables/useBacktest.ts
Simon Moisy 0c82c4f366 Implement FastAPI backend and Vue 3 frontend for Lowkey Backtest UI
- Added FastAPI backend with core API endpoints for strategies, backtests, and data management.
- Introduced Vue 3 frontend with a dark theme, enabling users to run backtests, adjust parameters, and compare results.
- Implemented Pydantic schemas for request/response validation and SQLAlchemy models for database interactions.
- Enhanced project structure with dedicated modules for services, routers, and components.
- Updated dependencies in `pyproject.toml` and `frontend/package.json` to include FastAPI, SQLAlchemy, and Vue-related packages.
- Improved `.gitignore` to exclude unnecessary files and directories.
2026-01-14 21:44:04 +08:00

151 lines
3.5 KiB
TypeScript

/**
* Composable for managing backtest state across components.
*/
import { ref, computed } from 'vue'
import type { BacktestResult, BacktestSummary, StrategyInfo, SymbolInfo } from '@/api/types'
import { getStrategies, getSymbols, getBacktests, getBacktest, runBacktest, deleteBacktest } from '@/api/client'
import type { BacktestRequest } from '@/api/types'
// Shared state
const strategies = ref<StrategyInfo[]>([])
const symbols = ref<SymbolInfo[]>([])
const runs = ref<BacktestSummary[]>([])
const currentResult = ref<BacktestResult | null>(null)
const selectedRuns = ref<string[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
// Computed
const symbolsByMarket = computed(() => {
const grouped: Record<string, SymbolInfo[]> = {}
for (const s of symbols.value) {
const key = `${s.market_type}`
if (!grouped[key]) grouped[key] = []
grouped[key].push(s)
}
return grouped
})
export function useBacktest() {
/**
* Load strategies and symbols on app init.
*/
async function init() {
try {
const [stratRes, symRes] = await Promise.all([
getStrategies(),
getSymbols(),
])
strategies.value = stratRes.strategies
symbols.value = symRes.symbols
} catch (e) {
error.value = `Failed to load initial data: ${e}`
}
}
/**
* Refresh the run history list.
*/
async function refreshRuns() {
try {
const res = await getBacktests({ limit: 100 })
runs.value = res.runs
} catch (e) {
error.value = `Failed to load runs: ${e}`
}
}
/**
* Execute a new backtest.
*/
async function executeBacktest(request: BacktestRequest) {
loading.value = true
error.value = null
try {
const result = await runBacktest(request)
currentResult.value = result
await refreshRuns()
return result
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : String(e)
error.value = `Backtest failed: ${msg}`
throw e
} finally {
loading.value = false
}
}
/**
* Load a specific run by ID.
*/
async function loadRun(runId: string) {
loading.value = true
error.value = null
try {
const result = await getBacktest(runId)
currentResult.value = result
return result
} catch (e) {
error.value = `Failed to load run: ${e}`
throw e
} finally {
loading.value = false
}
}
/**
* Delete a run.
*/
async function removeRun(runId: string) {
try {
await deleteBacktest(runId)
await refreshRuns()
if (currentResult.value?.run_id === runId) {
currentResult.value = null
}
selectedRuns.value = selectedRuns.value.filter(id => id !== runId)
} catch (e) {
error.value = `Failed to delete run: ${e}`
}
}
/**
* Toggle run selection for comparison.
*/
function toggleRunSelection(runId: string) {
const idx = selectedRuns.value.indexOf(runId)
if (idx >= 0) {
selectedRuns.value.splice(idx, 1)
} else if (selectedRuns.value.length < 5) {
selectedRuns.value.push(runId)
}
}
/**
* Clear all selections.
*/
function clearSelections() {
selectedRuns.value = []
}
return {
// State
strategies,
symbols,
symbolsByMarket,
runs,
currentResult,
selectedRuns,
loading,
error,
// Actions
init,
refreshRuns,
executeBacktest,
loadRun,
removeRun,
toggleRunSelection,
clearSelections,
}
}