diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..6da5cbc
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,5 @@
+.git
+node_modules
+docs
+.env
+*.md
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..0b71fc0
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,13 @@
+FROM oven/bun:1.3
+
+WORKDIR /app
+COPY package.json bun.lock ./
+RUN bun install --frozen-lockfile --production
+
+COPY . .
+
+EXPOSE 8080
+HEALTHCHECK --interval=30s --timeout=5s --start-period=30s \
+ CMD bun -e "fetch('http://localhost:8080/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"
+
+CMD ["bun", "run", "start"]
diff --git a/package.json b/package.json
index e3c045a..f2b9d9a 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
"private": true,
"type": "module",
"scripts": {
+ "start": "bun run db:migrate && bun run src/server/index.ts",
"test": "bun test",
"backfill": "bun run src/server/scripts/backfill.ts",
"walkforward": "bun run src/server/scripts/walkforward.ts",
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..63d847e
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,166 @@
+
+
+
+
+
+trade-kuns — Paper Trading
+
+
+
+
+ trade-kuns Donchian-Trendfolge · Paper · BTC ETH SOL XRP
+ lade…
+
+
+
+
+Equity-Kurve (4h)
+
+
+
+
+
+Letzte Entscheidungen (4h-Bars)
+
+
+
+
diff --git a/src/server/api/server.ts b/src/server/api/server.ts
new file mode 100644
index 0000000..ff9b454
--- /dev/null
+++ b/src/server/api/server.ts
@@ -0,0 +1,163 @@
+import { and, desc, eq, gte } from 'drizzle-orm';
+import { db } from '../db/client';
+import { botState, candles, decisionLogs, equitySnapshots, paperTrades, positions } from '../db/schema';
+import { aggregate4h } from '../market/aggregate';
+import { computeMetrics, type EquityPoint } from '../backtest/metrics';
+import type { ClosedTrade } from '../engine/portfolio';
+import type { Pair } from '../types';
+import { PAIRS } from '../types';
+import type { LiveEngine } from '../live/engine';
+
+function json(data: unknown, status = 200): Response {
+ return new Response(JSON.stringify(data), { status, headers: { 'content-type': 'application/json' } });
+}
+
+function clampLimit(url: URL, def: number, max: number): number {
+ const n = Number(url.searchParams.get('limit') ?? def);
+ return Number.isFinite(n) ? Math.min(Math.max(1, Math.floor(n)), max) : def;
+}
+
+async function latestCloses(): Promise