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:
144
frontend/src/components/MetricsPanel.vue
Normal file
144
frontend/src/components/MetricsPanel.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<script setup lang="ts">
|
||||
import type { BacktestMetrics } from '@/api/types'
|
||||
|
||||
const props = defineProps<{
|
||||
metrics: BacktestMetrics
|
||||
leverage?: number
|
||||
marketType?: string
|
||||
}>()
|
||||
|
||||
function formatPercent(val: number): string {
|
||||
return (val >= 0 ? '+' : '') + val.toFixed(2) + '%'
|
||||
}
|
||||
|
||||
function formatNumber(val: number | null | undefined, decimals = 2): string {
|
||||
if (val === null || val === undefined) return '-'
|
||||
return val.toFixed(decimals)
|
||||
}
|
||||
|
||||
function formatCurrency(val: number): string {
|
||||
return '$' + val.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<!-- Total Return -->
|
||||
<div class="card">
|
||||
<div class="metric-label">Strategy Return</div>
|
||||
<div
|
||||
class="metric-value"
|
||||
:class="metrics.total_return >= 0 ? 'profit' : 'loss'"
|
||||
>
|
||||
{{ formatPercent(metrics.total_return) }}
|
||||
</div>
|
||||
<div v-if="metrics.adjusted_return !== null && metrics.adjusted_return !== metrics.total_return" class="text-xs text-text-muted mt-1">
|
||||
Adj: {{ formatPercent(metrics.adjusted_return) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Benchmark Return -->
|
||||
<div class="card">
|
||||
<div class="metric-label">Benchmark (B&H)</div>
|
||||
<div
|
||||
class="metric-value"
|
||||
:class="metrics.benchmark_return >= 0 ? 'profit' : 'loss'"
|
||||
>
|
||||
{{ formatPercent(metrics.benchmark_return) }}
|
||||
</div>
|
||||
<div class="text-xs text-text-muted mt-1">
|
||||
Market change
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alpha -->
|
||||
<div class="card">
|
||||
<div class="metric-label">Alpha</div>
|
||||
<div
|
||||
class="metric-value"
|
||||
:class="metrics.alpha >= 0 ? 'profit' : 'loss'"
|
||||
>
|
||||
{{ formatPercent(metrics.alpha) }}
|
||||
</div>
|
||||
<div class="text-xs text-text-muted mt-1">
|
||||
vs Buy & Hold
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sharpe Ratio -->
|
||||
<div class="card">
|
||||
<div class="metric-label">Sharpe Ratio</div>
|
||||
<div
|
||||
class="metric-value"
|
||||
:class="metrics.sharpe_ratio >= 1 ? 'profit' : metrics.sharpe_ratio < 0 ? 'loss' : ''"
|
||||
>
|
||||
{{ formatNumber(metrics.sharpe_ratio) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Max Drawdown -->
|
||||
<div class="card">
|
||||
<div class="metric-label">Max Drawdown</div>
|
||||
<div class="metric-value loss">
|
||||
{{ formatPercent(metrics.max_drawdown) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Win Rate -->
|
||||
<div class="card">
|
||||
<div class="metric-label">Win Rate</div>
|
||||
<div
|
||||
class="metric-value"
|
||||
:class="metrics.win_rate >= 50 ? 'profit' : 'loss'"
|
||||
>
|
||||
{{ formatNumber(metrics.win_rate, 1) }}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total Trades -->
|
||||
<div class="card">
|
||||
<div class="metric-label">Total Trades</div>
|
||||
<div class="metric-value">
|
||||
{{ metrics.total_trades }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profit Factor -->
|
||||
<div class="card">
|
||||
<div class="metric-label">Profit Factor</div>
|
||||
<div
|
||||
class="metric-value"
|
||||
:class="(metrics.profit_factor || 0) >= 1 ? 'profit' : 'loss'"
|
||||
>
|
||||
{{ formatNumber(metrics.profit_factor) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total Fees -->
|
||||
<div class="card">
|
||||
<div class="metric-label">Total Fees</div>
|
||||
<div class="metric-value text-warning">
|
||||
{{ formatCurrency(metrics.total_fees) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Funding (perpetual only) -->
|
||||
<div v-if="marketType === 'perpetual'" class="card">
|
||||
<div class="metric-label">Funding Paid</div>
|
||||
<div class="metric-value text-warning">
|
||||
{{ formatCurrency(metrics.total_funding) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Liquidations (if any) -->
|
||||
<div v-if="metrics.liquidation_count > 0" class="card">
|
||||
<div class="metric-label">Liquidations</div>
|
||||
<div class="metric-value loss">
|
||||
{{ metrics.liquidation_count }}
|
||||
</div>
|
||||
<div class="text-xs text-text-muted mt-1">
|
||||
Lost: {{ formatCurrency(metrics.liquidation_loss) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user