Jaspers Hub

biseroff / alpaca

pluginReviewed★ 0↓ 3

Alpaca: Alpaca market data, account, positions, and order management, with watchlist, account, and order-blotter views.

4.7 KB
1import { useData, usePublish, type PanelRef } from '@jaspers-ai/sdk'
2import { fmt, sign } from './format'
3import './styles.css'
4
5interface Account {
6 mode: string
7 account_number: string | null
8 status: string | null
9 equity: number | null
10 cash: number | null
11 buying_power: number | null
12 day_pl: number | null
13 day_pl_pct: number | null
14 portfolio_value: number | null
15 trading_blocked: boolean
16 pattern_day_trader: boolean
17 daytrade_count: number | null
18}
19
20interface Position {
21 symbol: string
22 side: string | null
23 qty: number | null
24 avg_entry_price: number | null
25 current_price: number | null
26 market_value: number | null
27 unrealized_pl: number | null
28 unrealized_plpc: number | null
29 change_today: number | null
30}
31
32export function AccountPanel({ panel }: { panel: PanelRef }) {
33 const state = useData(`workspaces/${panel.workspaceId}/panels/${panel.id}/state`) as
34 | { live_account?: boolean }
35 | undefined
36 if (state === undefined) return <p className="al-note">Loading…</p>
37 return <Panel panel={panel} live={state.live_account === true} />
38}
39
40function Panel({ panel, live }: { panel: PanelRef; live: boolean }) {
41 const acct = useData('alpaca/account', { live_account: live })
42 const pos = useData('alpaca/positions', { live_account: live })
43 const a = ((acct.data ?? []) as Account[])[0]
44 const rows = (pos.data ?? []) as Position[]
45 usePublish(panel, {
46 mode: live ? 'live' : 'paper',
47 equity: a?.equity ?? null,
48 cash: a?.cash ?? null,
49 buying_power: a?.buying_power ?? null,
50 day_pl: a?.day_pl ?? null,
51 positions: rows.length,
52 })
53
54 return (
55 <div className="al">
56 <div className="al-head">
57 <h3 className="al-title">Alpaca account</h3>
58 <span className={live ? 'al-badge al-badge-live' : 'al-badge'}>{live ? 'LIVE' : 'paper'}</span>
59 {a?.account_number && <span className="al-muted">{a.account_number}</span>}
60 {a?.status && <span className="al-muted">{a.status}</span>}
61 {a?.trading_blocked && <span className="al-badge al-badge-live">trading blocked</span>}
62 <button
63 className="al-btn"
64 onClick={() => {
65 acct.refetch?.()
66 pos.refetch?.()
67 }}
68 >
69 Refresh
70 </button>
71 </div>
72 {acct.error ? (
73 <p className="al-note al-error">{acct.error}</p>
74 ) : !a ? (
75 <p className="al-note">Loading…</p>
76 ) : (
77 <div className="al-cards">
78 <Card label="Equity" value={fmt(a.equity)} />
79 <Card label="Cash" value={fmt(a.cash)} />
80 <Card label="Buying power" value={fmt(a.buying_power)} />
81 <Card
82 label="Day P/L"
83 value={`${fmt(a.day_pl)} (${fmt(a.day_pl_pct)}%)`}
84 className={sign(a.day_pl)}
85 />
86 <Card label="Day trades" value={`${a.daytrade_count ?? '–'}${a.pattern_day_trader ? ' · PDT' : ''}`} />
87 </div>
88 )}
89 {pos.error ? (
90 <p className="al-note al-error">{pos.error}</p>
91 ) : !pos.data ? null : rows.length === 0 ? (
92 <p className="al-note">No open positions.</p>
93 ) : (
94 <table className="al-table">
95 <thead>
96 <tr>
97 <th>Symbol</th>
98 <th>Side</th>
99 <th className="al-num">Qty</th>
100 <th className="al-num">Avg entry</th>
101 <th className="al-num">Price</th>
102 <th className="al-num">Market value</th>
103 <th className="al-num">Unrealized P/L</th>
104 <th className="al-num">P/L %</th>
105 <th className="al-num">Today %</th>
106 </tr>
107 </thead>
108 <tbody>
109 {rows.map((p) => (
110 <tr key={p.symbol}>
111 <td>{p.symbol}</td>
112 <td>{p.side ?? '–'}</td>
113 <td className="al-num">{fmt(p.qty, 0)}</td>
114 <td className="al-num">{fmt(p.avg_entry_price)}</td>
115 <td className="al-num">{fmt(p.current_price)}</td>
116 <td className="al-num">{fmt(p.market_value)}</td>
117 <td className={`al-num ${sign(p.unrealized_pl)}`}>{fmt(p.unrealized_pl)}</td>
118 <td className={`al-num ${sign(p.unrealized_plpc)}`}>
119 {p.unrealized_plpc === null ? '–' : `${fmt(p.unrealized_plpc * 100)}%`}
120 </td>
121 <td className={`al-num ${sign(p.change_today)}`}>
122 {p.change_today === null ? '–' : `${fmt(p.change_today * 100)}%`}
123 </td>
124 </tr>
125 ))}
126 </tbody>
127 </table>
128 )}
129 </div>
130 )
131}
132
133function Card({ label, value, className = '' }: { label: string; value: string; className?: string }) {
134 return (
135 <div className="al-card">
136 <div className="al-card-label">{label}</div>
137 <div className={`al-card-value ${className}`}>{value}</div>
138 </div>
139 )
140}