- 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.
142 lines
4.1 KiB
Vue
142 lines
4.1 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted } from 'vue'
|
|
import { useBacktest } from '@/composables/useBacktest'
|
|
import { useRouter } from 'vue-router'
|
|
|
|
const router = useRouter()
|
|
const {
|
|
runs,
|
|
currentResult,
|
|
selectedRuns,
|
|
refreshRuns,
|
|
loadRun,
|
|
removeRun,
|
|
toggleRunSelection
|
|
} = useBacktest()
|
|
|
|
onMounted(() => {
|
|
refreshRuns()
|
|
})
|
|
|
|
function formatDate(iso: string): string {
|
|
const d = new Date(iso)
|
|
return d.toLocaleDateString('en-US', {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})
|
|
}
|
|
|
|
function formatReturn(val: number): string {
|
|
return (val >= 0 ? '+' : '') + val.toFixed(2) + '%'
|
|
}
|
|
|
|
async function handleClick(runId: string) {
|
|
await loadRun(runId)
|
|
router.push('/')
|
|
}
|
|
|
|
function handleCheckbox(e: Event, runId: string) {
|
|
e.stopPropagation()
|
|
toggleRunSelection(runId)
|
|
}
|
|
|
|
function handleDelete(e: Event, runId: string) {
|
|
e.stopPropagation()
|
|
if (confirm('Delete this run?')) {
|
|
removeRun(runId)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex flex-col h-full">
|
|
<!-- Header -->
|
|
<div class="p-4 border-b border-border">
|
|
<h2 class="text-sm font-semibold text-text-secondary uppercase tracking-wide">
|
|
Run History
|
|
</h2>
|
|
<p class="text-xs text-text-muted mt-1">
|
|
{{ runs.length }} runs | {{ selectedRuns.length }} selected
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Run List -->
|
|
<div class="flex-1 overflow-y-auto">
|
|
<div
|
|
v-for="run in runs"
|
|
:key="run.run_id"
|
|
@click="handleClick(run.run_id)"
|
|
class="p-3 border-b border-border-muted cursor-pointer hover:bg-bg-hover transition-colors"
|
|
:class="{ 'bg-bg-tertiary': currentResult?.run_id === run.run_id }"
|
|
>
|
|
<div class="flex items-start gap-2">
|
|
<!-- Checkbox for comparison -->
|
|
<input
|
|
type="checkbox"
|
|
:checked="selectedRuns.includes(run.run_id)"
|
|
@click="handleCheckbox($event, run.run_id)"
|
|
class="mt-1 w-4 h-4 rounded border-border bg-bg-tertiary"
|
|
/>
|
|
|
|
<div class="flex-1 min-w-0">
|
|
<!-- Strategy & Symbol -->
|
|
<div class="flex items-center gap-2">
|
|
<span class="font-medium text-sm truncate">{{ run.strategy }}</span>
|
|
<span class="text-xs px-1.5 py-0.5 rounded bg-bg-tertiary text-text-secondary">
|
|
{{ run.symbol }}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Metrics -->
|
|
<div class="flex items-center gap-3 mt-1">
|
|
<span
|
|
class="text-sm font-mono"
|
|
:class="run.total_return >= 0 ? 'profit' : 'loss'"
|
|
>
|
|
{{ formatReturn(run.total_return) }}
|
|
</span>
|
|
<span class="text-xs text-text-muted">
|
|
SR {{ run.sharpe_ratio.toFixed(2) }}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Date -->
|
|
<div class="text-xs text-text-muted mt-1">
|
|
{{ formatDate(run.created_at) }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete button -->
|
|
<button
|
|
@click="handleDelete($event, run.run_id)"
|
|
class="p-1 rounded hover:bg-loss/20 text-text-muted hover:text-loss transition-colors"
|
|
title="Delete run"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty state -->
|
|
<div v-if="runs.length === 0" class="p-8 text-center text-text-muted">
|
|
<p>No runs yet.</p>
|
|
<p class="text-xs mt-1">Run a backtest to see results here.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Compare Button -->
|
|
<div v-if="selectedRuns.length >= 2" class="p-4 border-t border-border">
|
|
<router-link
|
|
to="/compare"
|
|
class="btn btn-primary w-full"
|
|
>
|
|
Compare {{ selectedRuns.length }} Runs
|
|
</router-link>
|
|
</div>
|
|
</div>
|
|
</template>
|