39875112a0
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.
76 lines
1.9 KiB
TypeScript
76 lines
1.9 KiB
TypeScript
const BASE = process.env.ALPACA_BASE_URL || "https://paper-api.alpaca.markets";
|
|
|
|
function headers(): HeadersInit {
|
|
const key = process.env.ALPACA_API_KEY || "";
|
|
const sec = process.env.ALPACA_API_SECRET || "";
|
|
return {
|
|
"APCA-API-KEY-ID": key,
|
|
"APCA-API-SECRET-KEY": sec,
|
|
Accept: "application/json",
|
|
};
|
|
}
|
|
|
|
export function credentialsPresent(): boolean {
|
|
return Boolean(process.env.ALPACA_API_KEY && process.env.ALPACA_API_SECRET);
|
|
}
|
|
|
|
async function alpacaGet<T>(pathname: string): Promise<T | null> {
|
|
if (!credentialsPresent()) return null;
|
|
try {
|
|
const res = await fetch(`${BASE}${pathname}`, {
|
|
headers: headers(),
|
|
cache: "no-store",
|
|
});
|
|
if (!res.ok) return null;
|
|
return (await res.json()) as T;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export type Account = {
|
|
equity: string;
|
|
cash: string;
|
|
buying_power: string;
|
|
portfolio_value: string;
|
|
status: string;
|
|
pattern_day_trader?: boolean;
|
|
last_equity?: string;
|
|
};
|
|
|
|
export type Position = {
|
|
symbol: string;
|
|
qty: string;
|
|
avg_entry_price: string;
|
|
current_price: string;
|
|
market_value: string;
|
|
unrealized_pl: string;
|
|
unrealized_plpc: string;
|
|
side: string;
|
|
asset_class: string;
|
|
};
|
|
|
|
export type Order = {
|
|
id: string;
|
|
symbol: string;
|
|
side: string;
|
|
qty: string | null;
|
|
filled_qty: string | null;
|
|
order_type: string;
|
|
status: string;
|
|
submitted_at: string;
|
|
filled_at: string | null;
|
|
filled_avg_price: string | null;
|
|
trail_percent?: string | null;
|
|
limit_price?: string | null;
|
|
stop_price?: string | null;
|
|
asset_class?: string;
|
|
};
|
|
|
|
export type Clock = { is_open: boolean; next_open: string; next_close: string };
|
|
|
|
export const getAccount = () => alpacaGet<Account>("/v2/account");
|
|
export const getPositions = () => alpacaGet<Position[]>("/v2/positions");
|
|
export const getOrders = () => alpacaGet<Order[]>("/v2/orders?status=all&limit=50&nested=true");
|
|
export const getClock = () => alpacaGet<Clock>("/v2/clock");
|