Files
admin 39875112a0 initial: alpaclaudia paper-trading bot + dashboard
Python bot (bot/alpaclaudia): alpaca-py client, wheel strategy (CSP + covered
calls) plus equity trailing stops, risk gates (cash buffer, cost-basis guard,
per-symbol concentration cap), SQLite state log, Typer CLI (tick/loop/status/
report/dump-state), Discord daily report, pytest suite.

Next.js 14 dashboard (dashboard/): read-only — reads the bot's SQLite directly
and pulls live account/positions/orders from Alpaca. KPIs, equity chart,
positions, bot-intents audit table, and orders table. Dark UI with Tailwind.

systemd/: user-unit templates for the polling loop and the post-close report
timer.

docs/STRATEGY.md: wheel mechanics, risk invariants, later candidates.

Defaults to BOT_MODE=dry — nothing is submitted to Alpaca until explicitly
enabled in .env. ALPACA_ENV=paper by default; flipping to live requires an
explicit second guard.
2026-04-16 21:38:25 +02:00

66 lines
2.8 KiB
TypeScript

import type { Order } from "../lib/alpaca";
import { money, num, timeAgo } from "../lib/format";
function statusTone(status: string): string {
const s = status.toLowerCase();
if (["filled", "done_for_day"].includes(s)) return "text-up";
if (["canceled", "rejected", "expired"].includes(s)) return "text-down";
if (["new", "accepted", "pending_new", "held", "open"].includes(s)) return "text-warn";
return "text-mute";
}
export function OrdersTable({ orders }: { orders: Order[] }) {
if (!orders.length)
return <div className="panel p-6 text-mute text-sm">No orders yet.</div>;
return (
<div className="panel overflow-hidden">
<div className="px-4 py-3 border-b border-border flex items-center justify-between">
<div className="text-sm font-medium">Orders (Alpaca)</div>
<div className="text-xs text-mute">{orders.length}</div>
</div>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="table-header border-b border-border">
<th className="text-left px-4 py-2 font-normal">Submitted</th>
<th className="text-left px-4 py-2 font-normal">Symbol</th>
<th className="text-left px-4 py-2 font-normal">Side</th>
<th className="text-left px-4 py-2 font-normal">Type</th>
<th className="text-right px-4 py-2 font-normal">Qty</th>
<th className="text-right px-4 py-2 font-normal">Limit / Trail</th>
<th className="text-right px-4 py-2 font-normal">Filled</th>
<th className="text-left px-4 py-2 font-normal">Status</th>
</tr>
</thead>
<tbody>
{orders.map((o) => (
<tr key={o.id} className="row-hover border-b border-border/60">
<td className="px-4 py-2 text-mute text-xs">
{timeAgo(o.submitted_at)}
</td>
<td className="px-4 py-2 font-mono">{o.symbol}</td>
<td className="px-4 py-2 uppercase text-xs">{o.side}</td>
<td className="px-4 py-2 text-xs">{o.order_type}</td>
<td className="px-4 py-2 text-right num">{num(o.qty, 0)}</td>
<td className="px-4 py-2 text-right num text-xs">
{o.limit_price
? money(o.limit_price)
: o.trail_percent
? `${Number(o.trail_percent).toFixed(2)}%`
: "—"}
</td>
<td className="px-4 py-2 text-right num">
{o.filled_avg_price ? money(o.filled_avg_price) : "—"}
</td>
<td className={`px-4 py-2 text-xs ${statusTone(o.status)}`}>
{o.status}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}