Jaspers Hub

jaspers / alpaca

pluginOfficialReviewed★ 0↓ 0

Alpaca: Alpaca brokerage plugin: stock/option market data, account, positions, orders, and order placement/cancellation, with watchlist, account, and blotter views.

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