1 | # alpaca
|
2 |
|
3 | ![version](https://img.shields.io/github/package-json/v/117/alpaca?color=196DFF&style=flat-square)
|
4 | ![code](https://img.shields.io/github/languages/code-size/117/alpaca?color=F1A42E&style=flat-square&label=size)
|
5 | ![build](https://img.shields.io/github/workflow/status/117/alpaca/test?style=flat-square)
|
6 | ![prettier](https://img.shields.io/static/v1?label=style&message=prettier&color=ff51bc&style=flat-square)
|
7 |
|
8 | A TypeScript Node.js library for the https://alpaca.markets REST API and
|
9 | WebSocket streams.
|
10 |
|
11 | ## Contents
|
12 |
|
13 | - [Features](#features)
|
14 | - [Install](#install)
|
15 | - [Client](#client)
|
16 | - [Stream](#stream)
|
17 | - [Common Issues](#common-issues)
|
18 | - [Examples](#examples)
|
19 | - [Contributing](#contributing)
|
20 |
|
21 | ## Features
|
22 |
|
23 | - [x] Fully typed.
|
24 | - [x] Fully asynchronous promise based API.
|
25 | - [x] Extensible `AlpacaClient` and `AlpacaStream` classes.
|
26 | - [x] Built-in rate limiting.
|
27 | - [x] Built-in number and date parsing.
|
28 | - [x] A 1:1 mapping of the official Alpaca [docs](https://docs.alpaca.markets/).
|
29 | - [x] Auto-transpiled modern ESM alternative.
|
30 | - [x] OAuth integration support.
|
31 | - [x] Minified and non-minified bundles.
|
32 | - [x] Various bundles provided:
|
33 | - `alpaca.js` - ESM bundle (for node)
|
34 | - `alpaca.bundle.js` - ESM bundle with dependencies (for node)
|
35 | - `alpaca.browser.js` - UMD bundle (for browser)
|
36 | - `alpaca.browser.modern.js` - ESM modern bundle (for browser)
|
37 |
|
38 | ## Install
|
39 |
|
40 | From NPM:
|
41 |
|
42 | ```cmd
|
43 | > npm i @master-chief/alpaca
|
44 | ```
|
45 |
|
46 | From GitHub:
|
47 |
|
48 | - [CommonJS](./dist/cjs)
|
49 | - [Typescript](./src)
|
50 | - [ES](./dist/mjs)
|
51 | - [ES bundled ](./dist/alpaca.js)
|
52 | - [ES bundled with dependencies](./dist/alpaca.bundle.js)
|
53 | - [ES6 + UMD (classic)](./dist/alpaca.browser.js)
|
54 | - [ES6 + ESM (modern) ](./dist/alpaca.browser.modern.js)
|
55 |
|
56 | From these popular CDNs:
|
57 |
|
58 | - [UNPKG](https://unpkg.com/browse/@master-chief/alpaca/)
|
59 | - [JSDelivr](https://cdn.jsdelivr.net/npm/@master-chief/alpaca/)
|
60 | - [SkyPack](https://cdn.skypack.dev/@master-chief/alpaca)
|
61 |
|
62 | ## Import
|
63 |
|
64 | Import with CommonJS:
|
65 |
|
66 | ```typescript
|
67 | let { AlpacaClient, AlpacaStream } = require('@master-chief/alpaca');
|
68 | ```
|
69 |
|
70 | Import with ESM:
|
71 |
|
72 | ```typescript
|
73 | import { AlpacaClient, AlpacaStream } from '@master-chief/alpaca';
|
74 | ```
|
75 |
|
76 | Import as script:
|
77 |
|
78 | ```html
|
79 | <script src="https://unpkg.com/@master-chief/alpaca/dist/alpaca.browser.min.js"></script>
|
80 | ```
|
81 |
|
82 | Import as module:
|
83 |
|
84 | ```html
|
85 | <script type="module">
|
86 | import alpaca from 'alpaca.browser.modern.min.js';
|
87 | </script>
|
88 | ```
|
89 |
|
90 | ## Client
|
91 |
|
92 | ### Creating a new client
|
93 |
|
94 | If you wish to use env vars, populate these fields with `process.env` on your
|
95 | own. Paper account key detection is automatic. Using OAuth? Simply pass an
|
96 | `access_token` in the credentials object.
|
97 |
|
98 | ```typescript
|
99 | const client = new AlpacaClient({
|
100 | credentials: {
|
101 | key: 'xxxxxx',
|
102 | secret: 'xxxxxxxxxxxx',
|
103 | // access_token: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
|
104 | paper: true,
|
105 | },
|
106 | rate_limit: true,
|
107 | });
|
108 | ```
|
109 |
|
110 | ### Built-in parsing
|
111 |
|
112 | Alpaca provides numbers as strings. From
|
113 | [their docs](https://alpaca.markets/docs/api-documentation/api-v2/#numbers):
|
114 |
|
115 | > Decimal numbers are returned as strings to preserve full precision across
|
116 | > platforms. When making a request, it is recommended that you also convert your
|
117 | > numbers to strings to avoid truncation and precision errors.
|
118 |
|
119 | This package provides numbers as `number` instead, and date strings as `Date`
|
120 | objects which is what most developers want out of the box. If you want the
|
121 | original data, as it came from Alpaca, you can call `raw()` on any entity.
|
122 |
|
123 | ```javascript
|
124 | const account = await client.getAccount();
|
125 |
|
126 | console.log(typeof account.buying_power); // number
|
127 | console.log(typeof account.raw().buying_power); // string
|
128 | ```
|
129 |
|
130 | ### Methods
|
131 |
|
132 | The following methods are available on the client.
|
133 |
|
134 | #### Account
|
135 |
|
136 | - [isAuthenticated](#isauthenticated)
|
137 | - [getAccount](#getaccount)
|
138 | - [getOrder](#getorder)
|
139 | - [getOrders](#getorders)
|
140 | - [placeOrder](#placeorder)
|
141 | - [replaceOrder](#replaceorder)
|
142 | - [cancelOrder](#cancelorder)
|
143 | - [cancelOrders](#cancelorders)
|
144 | - [getPosition](#getposition)
|
145 | - [getPositions](#getpositions)
|
146 | - [closePosition](#closePosition)
|
147 | - [closePositions](#closePositions)
|
148 | - [getAsset](#getasset)
|
149 | - [getAssets](#getassets)
|
150 | - [getWatchlist](#getwatchlist)
|
151 | - [getWatchlists](#getwatchlists)
|
152 | - [createWatchlist](#createwatchlist)
|
153 | - [updateWatchlist](#updatewatchlist)
|
154 | - [addToWatchlist](#addtowatchlist)
|
155 | - [removeFromWatchlist](#removefromwatchlist)
|
156 | - [deleteWatchlist](#deletewatchlist)
|
157 | - [getCalendar](#getcalendar)
|
158 | - [getClock](#getclock)
|
159 | - [getAccountConfigurations](#getAccountConfigurations)
|
160 | - [updateAccountConfigurations](#updateAccountConfigurations)
|
161 | - [getAccountActivities](#getAccountActivities)
|
162 | - [getPortfolioHistory](#getPortfolioHistory)
|
163 |
|
164 | #### Market Data v1
|
165 |
|
166 | - [getLastTrade_v1](#getLastTrade_v1)
|
167 | - [getLastQuote_v1](#getLastQuote_v1)
|
168 | - [getBars_v1](#getBars_v1)
|
169 |
|
170 | #### Market Data v2
|
171 |
|
172 | - [getTrades](#getTrades)
|
173 | - [getLatestTrade](#getLatestTrade)
|
174 | - [getQuotes](#getQuotes)
|
175 | - [getBars](#getBars)
|
176 | - [getSnapshot](#getSnapshot)
|
177 | - [getSnapshots](#getSnapshots)
|
178 | - [getNews](#getNews)
|
179 |
|
180 | #### isAuthenticated
|
181 |
|
182 | ```typescript
|
183 | await client.isAuthenticated();
|
184 | ```
|
185 |
|
186 | #### getAccount
|
187 |
|
188 | ```typescript
|
189 | await client.getAccount();
|
190 | ```
|
191 |
|
192 | #### getOrder
|
193 |
|
194 | ```typescript
|
195 | await client.getOrder({ order_id: '6187635d-04e5-485b-8a94-7ce398b2b81c' });
|
196 | ```
|
197 |
|
198 | #### getOrders
|
199 |
|
200 | ```typescript
|
201 | await client.getOrders({ limit: 25, status: 'all' });
|
202 | ```
|
203 |
|
204 | #### placeOrder
|
205 |
|
206 | ```typescript
|
207 | await client.placeOrder({
|
208 | symbol: 'SPY',
|
209 | qty: 1,
|
210 | // or
|
211 | // notional: 100,
|
212 | side: 'buy',
|
213 | type: 'market',
|
214 | time_in_force: 'day',
|
215 | });
|
216 | ```
|
217 |
|
218 | #### replaceOrder
|
219 |
|
220 | ```typescript
|
221 | await client.replaceOrder({
|
222 | order_id: '69a3db8b-cc63-44da-a26a-e3cca9490308',
|
223 | limit_price: 9.74,
|
224 | });
|
225 | ```
|
226 |
|
227 | #### cancelOrder
|
228 |
|
229 | ```typescript
|
230 | await client.cancelOrder({ order_id: '69a3db8b-cc63-44da-a26a-e3cca9490308' });
|
231 | ```
|
232 |
|
233 | #### cancelOrders
|
234 |
|
235 | ```typescript
|
236 | await client.cancelOrders();
|
237 | ```
|
238 |
|
239 | #### getPosition
|
240 |
|
241 | ```typescript
|
242 | await client.getPosition({ symbol: 'SPY' });
|
243 | ```
|
244 |
|
245 | #### getPositions
|
246 |
|
247 | ```typescript
|
248 | await client.getPositions();
|
249 | ```
|
250 |
|
251 | #### closePosition
|
252 |
|
253 | ```typescript
|
254 | await client.closePosition({ symbol: 'SPY' });
|
255 | ```
|
256 |
|
257 | #### closePositions
|
258 |
|
259 | ```typescript
|
260 | await client.closePositions();
|
261 | ```
|
262 |
|
263 | #### getAsset
|
264 |
|
265 | ```typescript
|
266 | await client.getAsset({ asset_id_or_symbol: 'SPY' });
|
267 | ```
|
268 |
|
269 | #### getAssets
|
270 |
|
271 | ```typescript
|
272 | await client.getAssets({ status: 'active' });
|
273 | ```
|
274 |
|
275 | #### getWatchlist
|
276 |
|
277 | ```typescript
|
278 | await client.getWatchlist({ uuid: '2000e463-6f87-41c0-a8ba-3e40cbf67128' });
|
279 | ```
|
280 |
|
281 | #### getWatchlists
|
282 |
|
283 | ```typescript
|
284 | await client.getWatchlists();
|
285 | ```
|
286 |
|
287 | #### createWatchlist
|
288 |
|
289 | ```typescript
|
290 | await client.createWatchlist({
|
291 | name: 'my watchlist',
|
292 | symbols: ['SPY', 'DIA', 'EEM', 'XLF'],
|
293 | });
|
294 | ```
|
295 |
|
296 | #### updateWatchlist
|
297 |
|
298 | ```typescript
|
299 | await client.updateWatchlist({
|
300 | uuid: '2000e463-6f87-41c0-a8ba-3e40cbf67128',
|
301 | name: 'new watchlist name',
|
302 | symbols: ['TSLA', 'AAPL'],
|
303 | });
|
304 | ```
|
305 |
|
306 | #### addToWatchlist
|
307 |
|
308 | ```typescript
|
309 | await client.addToWatchlist({
|
310 | uuid: '2000e463-6f87-41c0-a8ba-3e40cbf67128',
|
311 | symbol: 'F',
|
312 | });
|
313 | ```
|
314 |
|
315 | #### removeFromWatchlist
|
316 |
|
317 | ```typescript
|
318 | await client.removeFromWatchlist({
|
319 | uuid: '2000e463-6f87-41c0-a8ba-3e40cbf67128',
|
320 | symbol: 'F',
|
321 | });
|
322 | ```
|
323 |
|
324 | #### deleteWatchlist
|
325 |
|
326 | ```typescript
|
327 | await client.deleteWatchlist({ uuid: '2000e463-6f87-41c0-a8ba-3e40cbf67128' });
|
328 | ```
|
329 |
|
330 | #### getCalendar
|
331 |
|
332 | ```typescript
|
333 | await client.getCalendar({ start: new Date(), end: new Date() });
|
334 | ```
|
335 |
|
336 | #### getClock
|
337 |
|
338 | ```typescript
|
339 | await client.getClock();
|
340 | ```
|
341 |
|
342 | #### getAccountConfigurations
|
343 |
|
344 | ```typescript
|
345 | await client.getAccountConfigurations();
|
346 | ```
|
347 |
|
348 | #### updateAccountConfigurations
|
349 |
|
350 | ```typescript
|
351 | await client.updateAccountConfigurations({
|
352 | no_shorting: true,
|
353 | suspend_trade: true,
|
354 | });
|
355 | ```
|
356 |
|
357 | #### getAccountActivities
|
358 |
|
359 | ```typescript
|
360 | await client.getAccountActivities({ activity_type: 'FILL' });
|
361 | ```
|
362 |
|
363 | #### getPortfolioHistory
|
364 |
|
365 | ```typescript
|
366 | await client.getPortfolioHistory({ period: '1D', timeframe: '1Min' });
|
367 | ```
|
368 |
|
369 | #### getLastTrade_v1
|
370 |
|
371 | ```typescript
|
372 | await client.getLastTrade_v1({ symbol: 'SPY' });
|
373 | ```
|
374 |
|
375 | #### getLastQuote_v1
|
376 |
|
377 | ```typescript
|
378 | await client.getLastQuote_v1({ symbol: 'SPY' });
|
379 | ```
|
380 |
|
381 | #### getBars_v1
|
382 |
|
383 | ```typescript
|
384 | await client.getBars_v1({ symbols: ['SPY', 'DIA', 'XLF'], timeframe: '1Min' });
|
385 | ```
|
386 |
|
387 | #### getLatestTrade
|
388 |
|
389 | ```typescript
|
390 | await client.getLatestTrade({ symbol: 'SPY' });
|
391 | ```
|
392 |
|
393 | #### getTrades
|
394 |
|
395 | ##### Basic
|
396 |
|
397 | ```typescript
|
398 | await client.getTrades({
|
399 | symbol: 'SPY',
|
400 | start: new Date('2021-02-26T14:30:00.007Z'),
|
401 | end: new Date('2021-02-26T14:35:00.007Z'),
|
402 | });
|
403 | ```
|
404 |
|
405 | ##### Paginated
|
406 |
|
407 | ```typescript
|
408 | let trades = []
|
409 | let page_token = ''
|
410 |
|
411 | // until the next token we receive is null
|
412 | while (page_token != null) {
|
413 | let resp = await client.getTrades({ ..., page_token })
|
414 | trades.push(...resp.trades)
|
415 | page_token = resp.next_page_token
|
416 | }
|
417 |
|
418 | // wooh! we have collected trades from multiple pages
|
419 | console.log(trades.length)
|
420 | ```
|
421 |
|
422 | #### getQuotes
|
423 |
|
424 | ##### Basic
|
425 |
|
426 | ```typescript
|
427 | await client.getQuotes({
|
428 | symbol: 'SPY',
|
429 | start: new Date('2021-02-26T14:30:00.007Z'),
|
430 | end: new Date('2021-02-26T14:35:00.007Z'),
|
431 | });
|
432 | ```
|
433 |
|
434 | ##### Paginated
|
435 |
|
436 | ```typescript
|
437 | let quotes = []
|
438 | let page_token = ''
|
439 |
|
440 | // until the next token we receive is null
|
441 | while (page_token != null) {
|
442 | let resp = await client.getQuotes({ ..., page_token })
|
443 | quotes.push(...resp.quotes)
|
444 | page_token = resp.next_page_token
|
445 | }
|
446 |
|
447 | // wooh! we have collected quotes from multiple pages
|
448 | console.log(quotes.length)
|
449 | ```
|
450 |
|
451 | #### getBars
|
452 |
|
453 | ##### Basic
|
454 |
|
455 | ```typescript
|
456 | await client.getBars({
|
457 | symbol: 'SPY',
|
458 | start: new Date('2021-02-26T14:30:00.007Z'),
|
459 | end: new Date('2021-02-26T14:35:00.007Z'),
|
460 | timeframe: '1Min',
|
461 | // page_token: "MjAyMS0wMi0wNlQxMzowOTo0Mlo7MQ=="
|
462 | });
|
463 | ```
|
464 |
|
465 | ##### Paginated
|
466 |
|
467 | ```typescript
|
468 | let bars = []
|
469 | let page_token = ''
|
470 |
|
471 | // until the next token we receive is null
|
472 | while (page_token != null) {
|
473 | let resp = await client.getBars({ ..., page_token })
|
474 | bars.push(...resp.bars)
|
475 | page_token = resp.next_page_token
|
476 | }
|
477 |
|
478 | // wooh! we have collected bars from multiple pages
|
479 | console.log(bars.length)
|
480 | ```
|
481 |
|
482 | #### getSnapshot
|
483 |
|
484 | ```typescript
|
485 | await client.getSnapshot({ symbol: 'SPY' });
|
486 | ```
|
487 |
|
488 | #### getSnapshots
|
489 |
|
490 | ```typescript
|
491 | await client.getSnapshots({ symbols: ['SPY', 'DIA'] });
|
492 | ```
|
493 |
|
494 | #### getNews
|
495 |
|
496 | ```typescript
|
497 | await client.getNews({ symbols: ['SPY'] });
|
498 | ```
|
499 |
|
500 | ## Stream
|
501 |
|
502 | ### Creating a new stream
|
503 |
|
504 | If you wish to use env vars, populate these fields with `process.env` on your
|
505 | own.
|
506 |
|
507 | ```typescript
|
508 | import { AlpacaStream } from '@master-chief/alpaca';
|
509 |
|
510 | const stream = new AlpacaStream({
|
511 | credentials: {
|
512 | key: 'xxxxxx',
|
513 | secret: 'xxxxxxxxxxxx',
|
514 | paper: true,
|
515 | },
|
516 | type: 'market_data', // or "account"
|
517 | source: 'iex', // or "sip" depending on your subscription
|
518 | });
|
519 | ```
|
520 |
|
521 | ### Methods
|
522 |
|
523 | The following methods are available on the stream.
|
524 |
|
525 | - [subscribe](#subscribe)
|
526 | - [unsubscribe](#unsubscribe)
|
527 | - [on](#on)
|
528 | - [getConnection](#getConnection)
|
529 |
|
530 | ### Channels
|
531 |
|
532 | | Channel | Type |
|
533 | | :-------------- | :------------ |
|
534 | | `trade_updates` | `account` |
|
535 | | `trades` | `market_data` |
|
536 | | `quotes` | `market_data` |
|
537 | | `bars` | `market_data` |
|
538 |
|
539 | #### subscribe
|
540 |
|
541 | ```typescript
|
542 | stream.once('authenticated', () =>
|
543 | stream.subscribe('bars', ['SPY', 'AAPL', 'TSLA']),
|
544 | );
|
545 | ```
|
546 |
|
547 | #### unsubscribe
|
548 |
|
549 | ```typescript
|
550 | stream.unsubscribe('bars', ['SPY']);
|
551 | ```
|
552 |
|
553 | #### on
|
554 |
|
555 | ```typescript
|
556 | stream.on('message', (message) => console.log(message));
|
557 | stream.on('trade', (trade) => console.log(trade));
|
558 | stream.on('bar', (bar) => console.log(bar));
|
559 | stream.on('quote', (quote) => console.log(quote));
|
560 | stream.on('trade_updates', (update) => console.log(update));
|
561 | stream.on('error', (error) => console.warn(error));
|
562 | ```
|
563 |
|
564 | #### getConnection
|
565 |
|
566 | ```typescript
|
567 | stream.getConnection();
|
568 | ```
|
569 |
|
570 | ## Common Issues
|
571 |
|
572 | If you are having difficulty getting Jest to work with this library, add this to your configuration:
|
573 |
|
574 | ```js
|
575 | moduleNameMapper: {
|
576 | '@master-chief/alpaca': '<rootDir>/node_modules/@master-chief/alpaca/dist/cjs/index.cjs',
|
577 | },
|
578 | ```
|
579 |
|
580 | Credit to [@calvintwr](https://github.com/calvintwr) and [@wailinkyaww](https://github.com/wailinkyaww) for finding this solution.
|
581 |
|
582 | ## Examples
|
583 |
|
584 | Don't know where to start? Check out our community-made examples
|
585 | [here](./example).
|
586 |
|
587 | ## Contributing
|
588 |
|
589 | Feel free to contribute and PR to your 💖's content.
|