Recreate Next.js source — shadcn components, chart, types, hooks

Source files were untracked and lost during reset. Recreated:
- page.tsx (full 3-tab dashboard), layout.tsx, globals.css (shadcn theme)
- types.ts, api.ts, utils.ts
- strategy-card.tsx, equity-chart.tsx, positions-panel.tsx
- ui/ (7 shadcn wrappers: card, badge, button, table, tabs, sheet, collapsible, select)
- Config files: next.config.ts, tsconfig.json, postcss.config.mjs, components.json
This commit is contained in:
ramseshk
2026-08-05 05:05:26 +00:00
parent ac1b33a014
commit a09954017e
23 changed files with 1354 additions and 0 deletions
+106
View File
@@ -0,0 +1,106 @@
"use client";
import { useEffect, useRef, useState } from "react";
import type { LiveMetrics, PaperMetrics } from "./types";
const API_BASE = "/cv/api";
export function useLiveMetrics() {
const [data, setData] = useState<LiveMetrics | null>(null);
const [connected, setConnected] = useState(false);
const wsRef = useRef<WebSocket | null>(null);
const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
useEffect(() => {
const WS_BASE = `${window.location.protocol === "https:" ? "wss:" : "ws:"}//${window.location.host}/cv/ws`;
function connect() {
try {
const ws = new WebSocket(WS_BASE);
wsRef.current = ws;
ws.onopen = () => setConnected(true);
ws.onclose = () => {
setConnected(false);
timerRef.current = setTimeout(connect, 5000);
};
ws.onmessage = (e) => {
try {
const d = JSON.parse(e.data) as LiveMetrics;
setData(d);
} catch { /* ignore */ }
};
} catch {
timerRef.current = setTimeout(connect, 5000);
}
}
connect();
return () => {
wsRef.current?.close();
clearTimeout(timerRef.current);
};
}, []);
return { data, connected };
}
export function usePaperMetrics() {
const [data, setData] = useState<PaperMetrics | null>(null);
const [connected, setConnected] = useState(false);
const wsRef = useRef<WebSocket | null>(null);
useEffect(() => {
const base = `${window.location.protocol === "https:" ? "wss:" : "ws:"}//${window.location.host}/cv/ws/paper`;
const connect = () => {
try {
const ws = new WebSocket(base);
wsRef.current = ws;
ws.onopen = () => setConnected(true);
ws.onclose = () => {
setConnected(false);
setTimeout(connect, 5000);
};
ws.onmessage = (e) => {
try {
const d = JSON.parse(e.data) as PaperMetrics;
setData(d);
} catch { /* ignore */ }
};
} catch {
setTimeout(connect, 5000);
}
};
connect();
return () => wsRef.current?.close();
}, []);
return { data, connected };
}
export async function fetchHistorical(): Promise<Record<string, import("./types").BacktestSummary>> {
const res = await fetch(`${API_BASE}/backtests/historical`);
const list: import("./types").BacktestSummary[] = await res.json();
const byStrat: Record<string, import("./types").BacktestSummary> = {};
for (const b of list) {
if (!byStrat[b.strategy]) byStrat[b.strategy] = b;
}
return byStrat;
}
export async function fetchBacktestDetail(name: string): Promise<import("./types").BacktestFull> {
const res = await fetch(`${API_BASE}/backtest/historical/${encodeURIComponent(name)}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
export async function recalcBacktest(
name: string,
feeTier: number,
stakingTier: string,
): Promise<import("./types").BacktestFull> {
const res = await fetch(
`${API_BASE}/backtest/${encodeURIComponent(name)}/recalc?fee_tier=${feeTier}&staking_tier=${stakingTier}`,
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
+118
View File
@@ -0,0 +1,118 @@
// ═══════════ FTDT Quant Lab — Type Definitions ═══════════
export interface Strategy {
allocation: number;
instrument: string;
pnl: number;
pnl_pct: number;
position: number;
trades_today: number;
wins: number;
win_rate: number;
status: "running" | "idle";
size: number;
fee_paid: number;
fee_model: "maker" | "taker";
type: string;
description: string;
signals: { signal: string; ts: number }[];
max_position?: number;
stop_loss?: number;
entry_price?: number;
}
export interface Trade {
time: string;
strategy: string;
side: string;
size: number;
price: number;
pnl: number;
fee: number;
reason?: string;
pnl_net?: number;
pnl_gross?: number;
}
export interface Position {
strategy: string;
size: number;
entry_px: number;
pnl?: number;
}
export interface Order {
coin: string;
side: "B" | "A";
sz: number;
limitPx?: string;
cloid?: string;
}
export interface LiveMetrics {
timestamp: number;
wallet: string;
total_equity: number;
base_equity: number;
total_pnl: number;
total_pnl_pct: number;
reserve: number;
equity_history: { t: number; v: number }[];
strategy_equity: Record<string, { t: number; v: number }[]>;
strategies: Record<string, Strategy>;
trades: Trade[];
status: string;
testnet_up: boolean;
open_positions: Position[];
open_orders: Order[];
}
export interface PaperMetrics {
timestamp: number;
total_equity: number;
base_equity: number;
total_pnl: number;
total_pnl_pct: number;
regime: string;
equity_history: { t: number; v: number }[];
strategy_equity: Record<string, { t: number; v: number }[]>;
strategies: Record<string, Strategy>;
per_strategy_trades: Record<string, Trade[]>;
open_positions: Position[];
open_orders: Order[];
}
export interface BacktestSummary {
name: string;
strategy: string;
start: string;
end: string;
sharpe: number;
sortino: number;
pnl_pct: number;
max_dd: number;
win_rate: number;
total_trades: number;
coin?: string;
}
export interface BacktestFull {
name?: string;
strategy: string;
pnl_net?: number;
pnl_gross?: number;
pnl_pct?: number;
pnl_net_pct?: number;
pnl_gross_pct?: number;
pnl: number;
sharpe: number;
sortino: number;
max_dd: number;
win_rate: number;
total_trades: number;
fees_total: number;
num_periods: number;
fee_model: string;
equity_curve: { t: number | string; v: number }[];
trades: Trade[];
}
+6
View File
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}