Alpaca: Alpaca market data, account, positions, and order management, with watchlist, account, and order-blotter views.
| 1 | // How Alpaca's fields become flat rows: numbers sent as strings, OCC option symbols, orders. |
| 2 | |
| 3 | /** A number from Alpaca's string or number, or null when missing. */ |
| 4 | export function num(value: unknown): number | null { |
| 5 | if (value === null || value === undefined || value === '') return null |
| 6 | const n = Number(value) |
| 7 | return Number.isFinite(n) ? n : null |
| 8 | } |
| 9 | |
| 10 | /** A string or null. */ |
| 11 | export function str(value: unknown): string | null { |
| 12 | return value === null || value === undefined || value === '' ? null : String(value) |
| 13 | } |
| 14 | |
| 15 | /** Underlying, expiration (YYYY-MM-DD), type, and strike from an OCC symbol like AAPL250117C00190000. */ |
| 16 | export function parseOcc(symbol: string): { |
| 17 | underlying: string | null |
| 18 | expiration: string | null |
| 19 | type: 'call' | 'put' | null |
| 20 | strike: number | null |
| 21 | } { |
| 22 | const m = /^([A-Z0-9.]+?)(\d{6})([CP])(\d{8})$/.exec(symbol.replace(/\s+/g, '').toUpperCase()) |
| 23 | if (!m) return { underlying: null, expiration: null, type: null, strike: null } |
| 24 | const [, root, date, cp, strike] = m as unknown as [string, string, string, string, string] |
| 25 | return { |
| 26 | underlying: root, |
| 27 | expiration: `20${date.slice(0, 2)}-${date.slice(2, 4)}-${date.slice(4, 6)}`, |
| 28 | type: cp === 'C' ? 'call' : 'put', |
| 29 | strike: Number(strike) / 1000, |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | /** The midpoint of a bid and ask, or null when either is missing or zero. */ |
| 34 | export function mid(bid: number | null, ask: number | null): number | null { |
| 35 | return bid && ask ? Math.round(((bid + ask) / 2) * 10000) / 10000 : null |
| 36 | } |
| 37 | |
| 38 | /** One order as a flat row. */ |
| 39 | export function orderRow(order: Record<string, unknown>): Record<string, unknown> { |
| 40 | return { |
| 41 | id: str(order.id), |
| 42 | client_order_id: str(order.client_order_id), |
| 43 | symbol: str(order.symbol), |
| 44 | asset_class: str(order.asset_class), |
| 45 | side: str(order.side), |
| 46 | type: str(order.type ?? order.order_type), |
| 47 | time_in_force: str(order.time_in_force), |
| 48 | order_class: str(order.order_class), |
| 49 | qty: num(order.qty), |
| 50 | notional: num(order.notional), |
| 51 | filled_qty: num(order.filled_qty), |
| 52 | limit_price: num(order.limit_price), |
| 53 | stop_price: num(order.stop_price), |
| 54 | trail_price: num(order.trail_price), |
| 55 | trail_percent: num(order.trail_percent), |
| 56 | filled_avg_price: num(order.filled_avg_price), |
| 57 | status: str(order.status), |
| 58 | extended_hours: Boolean(order.extended_hours), |
| 59 | created_at: str(order.created_at), |
| 60 | submitted_at: str(order.submitted_at), |
| 61 | filled_at: str(order.filled_at), |
| 62 | canceled_at: str(order.canceled_at), |
| 63 | expired_at: str(order.expired_at), |
| 64 | } |
| 65 | } |