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.
This commit is contained in:
150
frontend/src/composables/useBacktest.ts
Normal file
150
frontend/src/composables/useBacktest.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user