├── .github └── workflows │ ├── bump_tag.yml │ ├── deno_deploy.yml │ ├── deno_test.yml │ └── deno_test_coverage.yml ├── .gitignore ├── README.md ├── api ├── marketData.ts └── trade.ts ├── build.ts ├── deno.json ├── deno.lock ├── factory ├── createClient.test.ts ├── createClient.ts ├── createStream.ts ├── createTokenBucket.test.ts └── createTokenBucket.ts ├── mod.ts └── util ├── mockFetch.test.ts └── mockFetch.ts /.github/workflows/bump_tag.yml: -------------------------------------------------------------------------------- 1 | name: Bump Tag 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: write 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | fetch-depth: "0" 15 | - name: get latest tag 16 | id: latest_tag 17 | run: | 18 | git fetch --tags 19 | latest_tag=$(git tag --sort=-creatordate | head -n 1) 20 | if [ -z "$latest_tag" ]; then 21 | latest_tag="0.0.0-preview" 22 | fi 23 | echo "::set-output name=tag::$latest_tag" 24 | - name: bump tag 25 | uses: actions/github-script@v6 26 | with: 27 | github-token: ${{ secrets.GITHUB_TOKEN }} 28 | script: | 29 | const currentTag = '${{ steps.latest_tag.outputs.tag }}'; 30 | const [version, suffix] = currentTag.split('-'); 31 | const [major, minor, patch] = version.split('.').map(Number); 32 | const newTag = `${major}.${minor}.${patch + 1}-${suffix || 'preview'}`; 33 | await github.rest.git.createRef({ 34 | owner: context.repo.owner, 35 | repo: context.repo.repo, 36 | ref: `refs/tags/${newTag}`, 37 | sha: context.sha 38 | }); 39 | console.log(`New tag created: ${newTag}`); 40 | -------------------------------------------------------------------------------- /.github/workflows/deno_deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deno Deploy 2 | on: 3 | workflow_run: 4 | workflows: ["Bump Tag"] 5 | types: 6 | - completed 7 | jobs: 8 | build-and-release: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | - name: get latest tag 15 | id: latest_tag 16 | run: | 17 | git fetch --tags 18 | latest_tag=$(git describe --tags --abbrev=0) 19 | echo "::set-output name=tag::$latest_tag" 20 | - name: setup deno 21 | uses: denolib/setup-deno@v2 22 | with: 23 | deno-version: v1.x 24 | - name: build npm package 25 | run: | 26 | deno run -A ./build.ts ${{ steps.latest_tag.outputs.tag }} 27 | - name: setup node 28 | uses: actions/setup-node@v2 29 | with: 30 | node-version: "14.x" 31 | registry-url: "https://registry.npmjs.org" 32 | - name: publish to npm 33 | run: | 34 | cd npm 35 | npm publish --access public 36 | env: 37 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 38 | -------------------------------------------------------------------------------- /.github/workflows/deno_test.yml: -------------------------------------------------------------------------------- 1 | name: Deno Test 2 | on: 3 | push: 4 | branches: [main] 5 | paths: 6 | - "**/*.ts" 7 | pull_request: 8 | branches: [main] 9 | paths: 10 | - "**/*.ts" 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: setup deno 17 | uses: denoland/setup-deno@v1 18 | with: 19 | deno-version: v1.x 20 | - name: run tests 21 | run: deno test --allow-net --allow-env --allow-read --allow-write 22 | working-directory: ./ 23 | -------------------------------------------------------------------------------- /.github/workflows/deno_test_coverage.yml: -------------------------------------------------------------------------------- 1 | name: Deno Test Coverage 2 | on: 3 | push: 4 | branches: [main] 5 | paths: 6 | - "**/*.ts" 7 | pull_request: 8 | branches: [main] 9 | paths: 10 | - "**/*.ts" 11 | jobs: 12 | coverage: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: setup deno 17 | uses: denoland/setup-deno@v1 18 | with: 19 | deno-version: v1.x 20 | - name: generate coverage 21 | run: | 22 | deno test --allow-all --coverage=coverage 23 | deno coverage coverage --lcov > lcov.info 24 | TOTAL_LINES=$(grep -o '^DA:[^,]*' lcov.info | wc -l | xargs) 25 | COVERED_LINES=$(grep -o '^DA:[^,]*' lcov.info | grep -o '1' | wc -l | xargs) 26 | COVERAGE_PERCENT=$((COVERED_LINES * 100 / TOTAL_LINES)) 27 | echo "Test coverage: $COVERAGE_PERCENT%" 28 | if [ "$COVERAGE_PERCENT" -lt 25 ]; then 29 | echo "test coverage is below 25%" 30 | exit 1 31 | fi 32 | shell: bash 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | lcov.info 3 | .env 4 | playground.ts 5 | dist 6 | node_modules 7 | npm 8 | npm-test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > 🚧 **WARNING** 🚧 2 | > This SDK is currently in development and not yet stable. The API may change. Please report any issues you find. Thank you! 🙏 When the version number loses the `-preview` suffix, the SDK is ready for production use. You can track progress and join the discussion [here](https://github.com/alpacahq/typescript-sdk/issues/1) 😃. 3 | 4 | # typescript-sdk 5 | 6 | ![version](https://img.shields.io/badge/dynamic/json?label=version&query=$[0].name&url=https://api.github.com/repos/alpacahq/typescript-sdk/tags&style=flat&color=FF33A0) 7 | ![code](https://img.shields.io/github/languages/code-size/alpacahq/typescript-sdk?style=flat&color=196DFF&label=code) 8 | ![test](https://img.shields.io/github/actions/workflow/status/alpacahq/typescript-sdk/deno_test.yml?style=flat&label=test) 9 | ![coverage](https://img.shields.io/github/actions/workflow/status/alpacahq/typescript-sdk/deno_test_coverage.yml?style=flat&label=coverage) 10 | ![build](https://img.shields.io/github/actions/workflow/status/alpacahq/typescript-sdk/deno_deploy.yml?style=flat&label=deploy) 11 | 12 | A TypeScript SDK for the https://alpaca.markets REST API and WebSocket streams. 13 | 14 | - [Features](#features) 15 | - [Install](#install) 16 | - [Usage](#getting-started) 17 | - [Create a Client](#create-a-client) 18 | - [Configuration](#configuration) 19 | - [Environment Variables](#environment-variables) 20 | - [Rate Limiting](#rate-limiting) 21 | - [Methods](#methods) 22 | - [Trading API](#trading-api) 23 | - [Market Data API](#market-data-api) 24 | - [WebSocket](#websocket) 25 | - [How It Works](#how-it-works) 26 | - [Channels](#channels) 27 | - [Examples](#examples) 28 | - [Subscribe](#subscribe) 29 | - [Unsubscribe](#unsubscribe) 30 | - [Handle Messages](#handle-messages) 31 | - [Need Help?](#need-help) 32 | 33 | ## Features 34 | 35 | - [x] REST API 36 | - [x] WebSocket Streams 37 | - [x] Built-in Rate Limiting (Token Bucket) 38 | - [x] TypeScript 39 | - [x] Deno 40 | - [x] Node (ESM) 41 | - [x] > 35% Test Coverage (and growing) 42 | - [x] Tree-shakable 43 | - [x] Both ESM and CJS Support 44 | - [x] Zero Dependencies 🤯 (you read that right) 45 | - [x] Community Driven 🚀 46 | 47 | Feel free to contribute and PR to your 💖's content. 48 | 49 | ## Install 50 | 51 | From NPM: 52 | 53 | ```terminal 54 | npm install @alpacahq/typescript-sdk 55 | ``` 56 | 57 | From Skypack (or any CDN that supports ESM): 58 | 59 | ```ts 60 | import { createClient } from "https://cdn.skypack.dev/@alpacahq/typescript-sdk"; 61 | ``` 62 | 63 | ## Usage 64 | 65 | ### Create a Client 66 | 67 | First, you'll need to create an API key on the Alpaca website. You can do that [here](https://app.alpaca.markets). Once you have an API key, you can use it to create a client. 68 | 69 | ```ts 70 | import { createClient } from "@alpacahq/typescript-sdk"; 71 | 72 | const client = createClient({ 73 | key: "YOUR_API_KEY_ID", 74 | secret: "YOUR_API_SECRET_KEY", 75 | // Or, provide an access token if you're using OAuth. 76 | // accessToken: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 77 | }); 78 | ``` 79 | 80 | By default, the client will make requests to the paper trading environment (`https://paper-api.alpaca.markets`). This is a safety measure to prevent accidental trades. 81 | 82 | ### Configuration 83 | 84 | #### Environment Variables 85 | 86 | You can set the following environment variables to configure the client: 87 | 88 | - `APCA_KEY_ID`: Your API key. 89 | - `APCA_KEY_SECRET`: Your API secret. 90 | - `APCA_ACCESS_TOKEN`: Your access token (if using OAuth). 91 | - `APCA_DEBUG`: Enables debug logging. 92 | 93 | The client will automatically use these values if they are set. They will not override any credentials explicitly passed to `createClient`. 94 | 95 | #### Rate Limiting 96 | 97 | You can customize the rate limiting by passing a `tokenBucket` object to the `createClient` function. This object should contain the `capacity` and `fillRate` for the rate limiter. 98 | 99 | ```ts 100 | tokenBucket: { 101 | // Maximum number of tokens that can be stored 102 | capacity: 200, 103 | // Number of tokens refilled per second 104 | fillRate: 60, 105 | } 106 | ``` 107 | 108 | Bursting is allowed, but the client will block requests if the token bucket is empty. The token bucket is shared across all requests. If you have multiple clients they will not share the same bucket. 109 | 110 | ### Methods 111 | 112 | #### Trading API 113 | 114 | - [`getAccount`](#getaccount) 115 | - [`createOrder`](#createorder) 116 | - [`getOrder`](#getorder) 117 | - [`getOrders`](#getorders) 118 | - [`replaceOrder`](#replaceorder) 119 | - [`cancelOrder`](#cancelorder) 120 | - [`cancelOrders`](#cancelorders) 121 | - [`getPosition`](#getposition) 122 | - [`getPositions`](#getpositions) 123 | - [`closePosition`](#closeposition) 124 | - [`closePositions`](#closepositions) 125 | - [`exerciseOption`](#exerciseoption) 126 | - [`getCalendar`](#getcalendar) 127 | - [`getClock`](#getclock) 128 | - [`getAsset`](#getasset) 129 | - [`getAssets`](#getassets) 130 | - [`getWatchlist`](#getwatchlist) 131 | - [`getWatchlists`](#getwatchlists) 132 | - [`createWatchlist`](#createwatchlist) 133 | - [`updateWatchlist`](#updatewatchlist) 134 | - [`deleteWatchlist`](#deletewatchlist) 135 | - [`getPortfolioHistory`](#getportfoliohistory) 136 | - [`getConfigurations`](#getconfigurations) 137 | - [`updateConfigurations`](#updateconfigurations) 138 | - [`getActivity`](#getactivity) 139 | - [`getActivities`](#getactivities) 140 | - [`getOptionsContract`](#getoptionscontract) 141 | - [`getOptionsContracts`](#getoptionscontracts) 142 | - [`getCorporateAction`](#getcorporateaction) 143 | - [`getCorporateActions`](#getcorporateactions) 144 | - [`getCryptoWallet`](#getcryptowallet) 145 | - [`getCryptoWallets`](#getcryptowallets) 146 | - [`getFeeEstimate`](#getfeeestimate) 147 | - [`getCryptoTransfer`](#getcryptotransfer) 148 | - [`getCryptoTransfers`](#getcryptotransfers) 149 | - [`createCryptoTransfer`](#createcryptotransfer) 150 | - [`getCryptoWhitelistedAddress`](#getcryptowhitelistedaddress) 151 | - [`getCryptoWhitelistedAddresses`](#getcryptowhitelistedaddresses) 152 | - [`requestCryptoWhitelistedAddress`](#requestcryptowhitelistedaddress) 153 | - [`removeCryptoWhitelistedAddress`](#removecryptowhitelistedaddress) 154 | 155 | #### Market Data API 156 | 157 | - [`getStocksCorporateActions`](#getstockscorporateactions) 158 | - [`getLogo`](#getlogo) 159 | - [`getNews`](#getnews) 160 | - [`getStocksMostActives`](#getstocksmostactives) 161 | - [`getStocksMarketMovers`](#getstocksmarketmovers) 162 | - [`getStocksQuotes`](#getstocksquotes) 163 | - [`getStocksQuotesLatest`](#getstocksquoteslatest) 164 | - [`getStocksBars`](#getstocksbars) 165 | - [`getStocksBarsLatest`](#getstocksbarslatest) 166 | - [`getForexRates`](#getforexrates) 167 | - [`getLatestForexRates`](#getlatestforexrates) 168 | - [`getStocksSnapshots`](#getstockssnapshots) 169 | - [`getStocksAuctions`](#getstocksauctions) 170 | - [`getStocksConditions`](#getstocksconditions) 171 | - [`getStocksExchangeCodes`](#getstocksexchangecodes) 172 | - [`getStocksTrades`](#getstockstrades) 173 | - [`getStocksTradesLatest`](#getstockstradeslatest) 174 | - [`getOptionsBars`](#getoptionsbars) 175 | - [`getOptionsExchanges`](#getoptionsexchanges) 176 | - [`getOptionsSnapshots`](#getoptionssnapshots) 177 | - [`getOptionsTrades`](#getoptionstrades) 178 | - [`getOptionsTradesLatest`](#getoptionstradeslatest) 179 | - [`getCryptoBars`](#getcryptobars) 180 | - [`getLatestCryptoBars`](#getlatestcryptobars) 181 | - [`getCryptoQuotes`](#getcryptoquotes) 182 | - [`getCryptoQuotesLatest`](#getcryptoquoteslatest) 183 | - [`getCryptoSnapshots`](#getcryptosnapshots) 184 | - [`getCryptoTrades`](#getcryptotrades) 185 | - [`getCryptoTradesLatest`](#getcryptotradeslatest) 186 | - [`getLatestCryptoOrderbooks`](#getlatestcryptoorderbooks) 187 | 188 | #### `getAccount` 189 | 190 | Retrieves the account information. 191 | 192 | ```typescript 193 | client.getAccount().then(console.log); 194 | ``` 195 | 196 | #### `createOrder` 197 | 198 | Creates a new order. 199 | 200 | ```typescript 201 | client 202 | .createOrder({ 203 | symbol: "AAPL", 204 | qty: 1, 205 | side: "buy", 206 | type: "market", 207 | time_in_force: "day", 208 | }) 209 | .then(console.log); 210 | ``` 211 | 212 | #### `getOrder` 213 | 214 | Retrieves a specific order by its ID. 215 | 216 | ```typescript 217 | client 218 | .getOrder({ order_id: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }) 219 | .then(console.log); 220 | ``` 221 | 222 | #### `getOrders` 223 | 224 | Retrieves a list of orders based on the specified parameters. 225 | 226 | ```typescript 227 | client 228 | .getOrders({ 229 | status: "open", 230 | limit: 10, 231 | direction: "desc", 232 | }) 233 | .then(console.log); 234 | ``` 235 | 236 | #### `replaceOrder` 237 | 238 | Replaces an existing order with updated parameters. 239 | 240 | ```typescript 241 | client 242 | .replaceOrder({ 243 | qty: 2, 244 | limit_price: 150.0, 245 | order_id: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 246 | }) 247 | .then(console.log); 248 | ``` 249 | 250 | #### `cancelOrder` 251 | 252 | Cancels a specific order by its ID. 253 | 254 | ```typescript 255 | client 256 | .cancelOrder({ order_id: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }) 257 | .then(console.log); 258 | ``` 259 | 260 | #### `cancelOrders` 261 | 262 | Cancels all open orders. 263 | 264 | ```typescript 265 | client.cancelOrders().then(console.log); 266 | ``` 267 | 268 | #### `getPosition` 269 | 270 | Retrieves a specific position by symbol or asset ID. 271 | 272 | ```typescript 273 | client 274 | .getPosition({ 275 | symbol_or_asset_id: "AAPL", 276 | }) 277 | .then(console.log); 278 | ``` 279 | 280 | #### `getPositions` 281 | 282 | Retrieves all positions. 283 | 284 | ```typescript 285 | client.getPositions().then(console.log); 286 | ``` 287 | 288 | #### `closePosition` 289 | 290 | Closes a specific position by symbol or asset ID. 291 | 292 | ```typescript 293 | client 294 | .closePosition({ 295 | symbol_or_asset_id: "AAPL", 296 | }) 297 | .then(console.log); 298 | ``` 299 | 300 | #### `closePositions` 301 | 302 | Closes all positions. 303 | 304 | ```typescript 305 | client.closePositions().then(console.log); 306 | ``` 307 | 308 | #### `exerciseOption` 309 | 310 | Exercises an options contract. 311 | 312 | ```typescript 313 | client 314 | .exerciseOption({ 315 | symbol_or_contract_id: "xxxxxxxx", 316 | }) 317 | .then(console.log); 318 | ``` 319 | 320 | #### `getCalendar` 321 | 322 | Retrieves the market calendar. 323 | 324 | ```typescript 325 | client 326 | .getCalendar({ 327 | start: "2023-01-01", 328 | end: "2023-12-31", 329 | }) 330 | .then(console.log); 331 | ``` 332 | 333 | #### `getClock` 334 | 335 | Retrieves the current market clock. 336 | 337 | ```typescript 338 | client.getClock().then(console.log); 339 | ``` 340 | 341 | #### `getAsset` 342 | 343 | Retrieves a specific asset by symbol or asset ID. 344 | 345 | ```typescript 346 | client 347 | .getAsset({ 348 | symbol_or_asset_id: "AAPL", 349 | }) 350 | .then(console.log); 351 | ``` 352 | 353 | #### `getAssets` 354 | 355 | Retrieves a list of assets based on the specified parameters. 356 | 357 | ```typescript 358 | client 359 | .getAssets({ 360 | status: "active", 361 | asset_class: "us_equity", 362 | }) 363 | .then(console.log); 364 | ``` 365 | 366 | #### `getWatchlist` 367 | 368 | Retrieves a specific watchlist by ID. 369 | 370 | ```typescript 371 | client 372 | .getWatchlist({ 373 | watchlist_id: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 374 | }) 375 | .then(console.log); 376 | ``` 377 | 378 | #### `getWatchlists` 379 | 380 | Retrieves all watchlists. 381 | 382 | ```typescript 383 | client.getWatchlists().then(console.log); 384 | ``` 385 | 386 | #### `createWatchlist` 387 | 388 | Creates a new watchlist. 389 | 390 | ```typescript 391 | client 392 | .createWatchlist({ 393 | name: "My Watchlist", 394 | symbols: ["AAPL", "GOOGL", "AMZN"], 395 | }) 396 | .then(console.log); 397 | ``` 398 | 399 | #### `updateWatchlist` 400 | 401 | Updates an existing watchlist. 402 | 403 | ```typescript 404 | client 405 | .updateWatchlist({ 406 | name: "Updated Watchlist", 407 | symbols: ["AAPL", "GOOGL", "MSFT"], 408 | watchlist_id: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 409 | }) 410 | .then(console.log); 411 | ``` 412 | 413 | #### `deleteWatchlist` 414 | 415 | Deletes a specific watchlist by ID. 416 | 417 | ```typescript 418 | client 419 | .deleteWatchlist({ 420 | watchlist_id: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 421 | }) 422 | .then(console.log); 423 | ``` 424 | 425 | #### `getPortfolioHistory` 426 | 427 | Retrieves the portfolio history. 428 | 429 | ```typescript 430 | client 431 | .getPortfolioHistory({ 432 | period: "1M", 433 | timeframe: "1D", 434 | }) 435 | .then(console.log); 436 | ``` 437 | 438 | #### `getConfigurations` 439 | 440 | Retrieves the account configurations. 441 | 442 | ```typescript 443 | client.getConfigurations().then(console.log); 444 | ``` 445 | 446 | #### `updateConfigurations` 447 | 448 | Updates the account configurations. 449 | 450 | ```typescript 451 | client 452 | .updateConfigurations({ 453 | trade_confirm_email: "all", 454 | suspend_trade: false, 455 | }) 456 | .then(console.log); 457 | ``` 458 | 459 | #### `getActivity` 460 | 461 | Retrieves a specific activity type. 462 | 463 | ```typescript 464 | client 465 | .getActivity({ 466 | activity_type: "FILL", 467 | }) 468 | .then(console.log); 469 | ``` 470 | 471 | #### `getActivities` 472 | 473 | Retrieves all activities. 474 | 475 | ```typescript 476 | client.getActivities().then(console.log); 477 | ``` 478 | 479 | #### `getOptionsContract` 480 | 481 | Retrieves a specific options contract by symbol or contract ID. 482 | 483 | ```typescript 484 | client 485 | .getOptionsContract({ 486 | symbol_or_contract_id: "AAPL230616C00150000", 487 | }) 488 | .then(console.log); 489 | ``` 490 | 491 | #### `getOptionsContracts` 492 | 493 | Retrieves a list of options contracts based on the specified parameters. 494 | 495 | ```typescript 496 | client 497 | .getOptionsContracts({ 498 | underlying_symbols: "AAPL", 499 | expiration_date: "2023-06-16", 500 | }) 501 | .then(console.log); 502 | ``` 503 | 504 | #### `getCorporateAction` 505 | 506 | Retrieves a specific corporate action by ID. 507 | 508 | ```typescript 509 | client 510 | .getCorporateAction({ 511 | id: "xxxxxxxx", 512 | }) 513 | .then(console.log); 514 | ``` 515 | 516 | #### `getCorporateActions` 517 | 518 | Retrieves a list of corporate actions based on the specified parameters. 519 | 520 | ```typescript 521 | client 522 | .getCorporateActions({ 523 | ca_types: "MERGER", 524 | since: "2023-01-01", 525 | until: "2023-12-31", 526 | }) 527 | .then(console.log); 528 | ``` 529 | 530 | #### `getCryptoWallet` 531 | 532 | Retrieves a specific crypto wallet by asset. 533 | 534 | ```typescript 535 | client 536 | .getCryptoWallet({ 537 | asset: "BTCUSD", 538 | }) 539 | .then(console.log); 540 | ``` 541 | 542 | #### `getCryptoWallets` 543 | 544 | Retrieves all crypto wallets. 545 | 546 | ```typescript 547 | client.getCryptoWallets().then(console.log); 548 | ``` 549 | 550 | #### `getFeeEstimate` 551 | 552 | Retrieves the fee estimate for a crypto withdrawal. 553 | 554 | ```typescript 555 | client 556 | .getFeeEstimate({ 557 | asset: "BTCUSD", 558 | from_address: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 559 | to_address: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 560 | amount: "0.1", 561 | }) 562 | .then(console.log); 563 | ``` 564 | 565 | #### `getCryptoTransfer` 566 | 567 | Retrieves a specific crypto transfer by ID. 568 | 569 | ```typescript 570 | client 571 | .getCryptoTransfer({ 572 | transfer_id: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 573 | }) 574 | .then(console.log); 575 | ``` 576 | 577 | #### `getCryptoTransfers` 578 | 579 | Retrieves a list of crypto transfers based on the specified parameters. 580 | 581 | ```typescript 582 | client 583 | .getCryptoTransfers({ 584 | asset: "BTCUSD", 585 | }) 586 | .then(console.log); 587 | ``` 588 | 589 | #### `createCryptoTransfer` 590 | 591 | Creates a new crypto withdrawal. 592 | 593 | ```typescript 594 | client 595 | .createCryptoTransfer({ 596 | amount: "0.1", 597 | address: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 598 | asset: "BTCUSD", 599 | }) 600 | .then(console.log); 601 | ``` 602 | 603 | #### `getCryptoWhitelistedAddress` 604 | 605 | Retrieves a specific whitelisted crypto address. 606 | 607 | ```typescript 608 | client 609 | .getCryptoWhitelistedAddress({ 610 | address: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 611 | asset: "BTCUSD", 612 | }) 613 | .then(console.log); 614 | ``` 615 | 616 | #### `getCryptoWhitelistedAddresses` 617 | 618 | Retrieves all whitelisted crypto addresses. 619 | 620 | ```typescript 621 | client.getCryptoWhitelistedAddresses().then(console.log); 622 | ``` 623 | 624 | #### `requestCryptoWhitelistedAddress` 625 | 626 | Requests a new whitelisted crypto address. 627 | 628 | ```typescript 629 | client 630 | .requestCryptoWhitelistedAddress({ 631 | address: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 632 | asset: "BTCUSD", 633 | }) 634 | .then(console.log); 635 | ``` 636 | 637 | #### `removeCryptoWhitelistedAddress` 638 | 639 | Removes a specific whitelisted crypto address. 640 | 641 | ```typescript 642 | client 643 | .removeCryptoWhitelistedAddress({ 644 | whitelisted_address_id: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 645 | }) 646 | .then(console.log); 647 | ``` 648 | 649 | #### `getStocksCorporateActions` 650 | 651 | Retrieves a list of corporate actions based on the specified parameters. 652 | 653 | ```typescript 654 | client 655 | .getStocksCorporateActions({ 656 | symbols: "AAPL", 657 | types: "cash_dividends", 658 | }) 659 | .then(console.log); 660 | ``` 661 | 662 | #### `getLogo` 663 | 664 | Retrieves the logo for a specific symbol. 665 | 666 | ```typescript 667 | client 668 | .getLogo({ 669 | symbol: "AAPL", 670 | }) 671 | .then(console.log); 672 | ``` 673 | 674 | #### `getNews` 675 | 676 | Retrieves the latest news. 677 | 678 | ```typescript 679 | client 680 | .getNews({ 681 | symbols: "AAPL,TSLA", 682 | limit: 10, 683 | }) 684 | .then(console.log); 685 | ``` 686 | 687 | #### `getStocksMostActives` 688 | 689 | Retrieves a list of the most active stocks. 690 | 691 | ```typescript 692 | client 693 | .getStocksMostActives({ 694 | by: "volume", 695 | top: 10, 696 | }) 697 | .then(console.log); 698 | ``` 699 | 700 | #### `getStocksMarketMovers` 701 | 702 | Retrieves a list of the top market movers. 703 | 704 | ```typescript 705 | client 706 | .getStocksMarketMovers({ 707 | by: "change", 708 | top: 10, 709 | }) 710 | .then(console.log); 711 | ``` 712 | 713 | #### `getStocksQuotes` 714 | 715 | Retrieves a list of stock quotes. 716 | 717 | ```typescript 718 | client 719 | .getStocksQuotes({ 720 | symbols: "AAPL,TSLA", 721 | limit: 10, 722 | }) 723 | .then(console.log); 724 | ``` 725 | 726 | #### `getStocksQuotesLatest` 727 | 728 | Retrieves the latest stock quotes. 729 | 730 | ```typescript 731 | client 732 | .getStocksQuotesLatest({ 733 | symbols: "AAPL,TSLA", 734 | }) 735 | .then(console.log); 736 | ``` 737 | 738 | #### `getStocksBars` 739 | 740 | Retrieves a list of stock bars. 741 | 742 | ```typescript 743 | client 744 | .getStocksBars({ 745 | symbols: "AAPL,TSLA", 746 | timeframe: "1Day", 747 | limit: 10, 748 | }) 749 | .then(console.log); 750 | ``` 751 | 752 | #### `getStocksBarsLatest` 753 | 754 | Retrieves the latest stock bars. 755 | 756 | ```typescript 757 | client 758 | .getStocksBarsLatest({ 759 | symbols: "AAPL,TSLA", 760 | }) 761 | .then(console.log); 762 | ``` 763 | 764 | #### `getForexRates` 765 | 766 | Retrieves a list of forex rates. 767 | 768 | ```typescript 769 | client 770 | .getForexRates({ 771 | currency_pairs: "EURUSD,GBPUSD", 772 | timeframe: "1Min", 773 | limit: 10, 774 | }) 775 | .then(console.log); 776 | ``` 777 | 778 | #### `getLatestForexRates` 779 | 780 | Retrieves the latest forex rates. 781 | 782 | ```typescript 783 | client 784 | .getLatestForexRates({ 785 | currency_pairs: "EURUSD,GBPUSD", 786 | }) 787 | .then(console.log); 788 | ``` 789 | 790 | #### `getStocksSnapshots` 791 | 792 | Retrieves a list of stock snapshots. 793 | 794 | ```typescript 795 | client 796 | .getStocksSnapshots({ 797 | symbols: "AAPL,TSLA", 798 | }) 799 | .then(console.log); 800 | ``` 801 | 802 | #### `getStocksAuctions` 803 | 804 | Retrieves a list of stock auctions. 805 | 806 | ```typescript 807 | client 808 | .getStocksAuctions({ 809 | symbols: "AAPL,TSLA", 810 | limit: 10, 811 | }) 812 | .then(console.log); 813 | ``` 814 | 815 | #### `getStocksConditions` 816 | 817 | Retrieves a list of stock conditions. 818 | 819 | ```typescript 820 | client 821 | .getStocksConditions({ 822 | tickType: "trades", 823 | tape: "xxx", 824 | }) 825 | .then(console.log); 826 | ``` 827 | 828 | #### `getStocksExchangeCodes` 829 | 830 | Retrieves a list of stock exchange codes. 831 | 832 | ```typescript 833 | client.getStocksExchangeCodes().then(console.log); 834 | ``` 835 | 836 | #### `getStocksTrades` 837 | 838 | Retrieves a list of stock trades. 839 | 840 | ```typescript 841 | client 842 | .getStocksTrades({ 843 | symbols: "AAPL,TSLA", 844 | limit: 10, 845 | }) 846 | .then(console.log); 847 | ``` 848 | 849 | #### `getStocksTradesLatest` 850 | 851 | Retrieves the latest stock trades. 852 | 853 | ```typescript 854 | client 855 | .getStocksTradesLatest({ 856 | symbols: "AAPL,TSLA", 857 | }) 858 | .then(console.log); 859 | ``` 860 | 861 | #### `getOptionsBars` 862 | 863 | Retrieves a list of options bars. 864 | 865 | ```typescript 866 | client 867 | .getOptionsBars({ 868 | symbols: "AAPL220617C00270000,TSLA220617C01000000", 869 | timeframe: "1Day", 870 | limit: 10, 871 | }) 872 | .then(console.log); 873 | ``` 874 | 875 | #### `getOptionsExchanges` 876 | 877 | Retrieves a list of options exchanges. 878 | 879 | ```typescript 880 | client.getOptionsExchanges().then(console.log); 881 | ``` 882 | 883 | #### `getOptionsSnapshots` 884 | 885 | Retrieves a list of options snapshots. 886 | 887 | ```typescript 888 | client 889 | .getOptionsSnapshots({ 890 | symbols: "AAPL220617C00270000,TSLA220617C01000000", 891 | }) 892 | .then(console.log); 893 | ``` 894 | 895 | #### `getOptionsTrades` 896 | 897 | Retrieves a list of options trades. 898 | 899 | ```typescript 900 | client 901 | .getOptionsTrades({ 902 | symbols: "AAPL220617C00270000,TSLA220617C01000000", 903 | limit: 10, 904 | }) 905 | .then(console.log); 906 | ``` 907 | 908 | #### `getOptionsTradesLatest` 909 | 910 | Retrieves the latest options trades. 911 | 912 | ```typescript 913 | client 914 | .getOptionsTradesLatest({ 915 | symbols: "AAPL220617C00270000,TSLA220617C01000000", 916 | }) 917 | .then(console.log); 918 | ``` 919 | 920 | #### `getCryptoBars` 921 | 922 | Retrieves a list of crypto bars. 923 | 924 | ```typescript 925 | client 926 | .getCryptoBars({ 927 | symbols: "BTCUSD,ETHUSD", 928 | timeframe: "1Min", 929 | limit: 10, 930 | }) 931 | .then(console.log); 932 | ``` 933 | 934 | #### `getLatestCryptoBars` 935 | 936 | Retrieves the latest crypto bars. 937 | 938 | ```typescript 939 | client 940 | .getLatestCryptoBars({ 941 | loc: "US", 942 | symbols: "BTCUSD,ETHUSD", 943 | }) 944 | .then(console.log); 945 | ``` 946 | 947 | #### `getCryptoQuotes` 948 | 949 | Retrieves a list of crypto quotes. 950 | 951 | ```typescript 952 | client 953 | .getCryptoQuotes({ 954 | symbols: "BTCUSD,ETHUSD", 955 | limit: 10, 956 | }) 957 | .then(console.log); 958 | ``` 959 | 960 | #### `getCryptoQuotesLatest` 961 | 962 | Retrieves the latest crypto quotes. 963 | 964 | ```typescript 965 | client 966 | .getCryptoQuotesLatest({ 967 | loc: "US", 968 | symbols: "BTCUSD,ETHUSD", 969 | }) 970 | .then(console.log); 971 | ``` 972 | 973 | #### `getCryptoSnapshots` 974 | 975 | Retrieves a list of crypto snapshots. 976 | 977 | ```typescript 978 | client 979 | .getCryptoSnapshots({ 980 | loc: "US", 981 | symbols: "BTCUSD,ETHUSD", 982 | }) 983 | .then(console.log); 984 | ``` 985 | 986 | #### `getCryptoTrades` 987 | 988 | Retrieves a list of crypto trades. 989 | 990 | ```typescript 991 | client 992 | .getCryptoTrades({ 993 | loc: "US", 994 | symbols: "BTCUSD,ETHUSD", 995 | limit: 10, 996 | }) 997 | .then(console.log); 998 | ``` 999 | 1000 | #### `getCryptoTradesLatest` 1001 | 1002 | Retrieves the latest crypto trades. 1003 | 1004 | ```typescript 1005 | client 1006 | .getCryptoTradesLatest({ 1007 | loc: "US", 1008 | symbols: "BTCUSD,ETHUSD", 1009 | }) 1010 | .then(console.log); 1011 | ``` 1012 | 1013 | #### `getLatestCryptoOrderbooks` 1014 | 1015 | Retrieves the latest crypto orderbooks. 1016 | 1017 | ```typescript 1018 | client 1019 | .getLatestCryptoOrderbooks({ 1020 | loc: "US", 1021 | symbols: "BTCUSD,ETHUSD", 1022 | }) 1023 | .then(console.log); 1024 | ``` 1025 | 1026 | ### WebSocket 1027 | 1028 | #### How It Works 1029 | 1030 | todo 1031 | 1032 | #### Channels 1033 | 1034 | todo 1035 | 1036 | #### Examples 1037 | 1038 | todo 1039 | 1040 | ## Need Help? 1041 | 1042 | The primary maintainer of this project is [@117](https://github.com/117). Feel free to reach out on [Slack](https://alpaca-community.slack.com/join/shared_invite/zt-2ebgo7i1f-HbNoBjPWZ_bX72IVQTkcwg) 👋 or by opening an issue on this repo. I'm happy to help with any questions or issues you may have. 1043 | -------------------------------------------------------------------------------- /api/marketData.ts: -------------------------------------------------------------------------------- 1 | import { baseURLs, ClientContext } from "../factory/createClient.ts"; 2 | import { Nullable } from "./trade.ts"; 3 | 4 | export type ReverseSplit = { 5 | symbol: string; 6 | new_rate: number; 7 | old_rate: number; 8 | process_date: string; 9 | ex_date: string; 10 | record_date: string; 11 | payable_date?: string; 12 | }; 13 | 14 | export type ForwardSplit = { 15 | symbol: string; 16 | new_rate: number; 17 | old_rate: number; 18 | process_date: string; 19 | ex_date: string; 20 | record_date: string; 21 | payable_date?: string; 22 | due_bill_redemption_date?: string; 23 | }; 24 | 25 | export type UnitSplit = { 26 | old_symbol: string; 27 | old_rate: number; 28 | new_symbol: string; 29 | new_rate: number; 30 | alternate_symbol: string; 31 | alternate_rate: number; 32 | process_date: string; 33 | effective_date: string; 34 | payable_date?: string; 35 | }; 36 | 37 | export type StockDividend = { 38 | symbol: string; 39 | rate: number; 40 | process_date: string; 41 | ex_date: string; 42 | record_date: string; 43 | payable_date?: string; 44 | }; 45 | 46 | export type CashDividend = { 47 | symbol: string; 48 | rate: number; 49 | special: boolean; 50 | foreign: boolean; 51 | process_date: string; 52 | ex_date: string; 53 | record_date: string; 54 | payable_date?: string; 55 | due_bill_on_date?: string; 56 | due_bill_off_date?: string; 57 | }; 58 | 59 | export type SpinOff = { 60 | source_symbol: string; 61 | source_rate: number; 62 | new_symbol: string; 63 | new_rate: number; 64 | process_date: string; 65 | ex_date: string; 66 | record_date: string; 67 | payable_date?: string; 68 | due_bill_redemption_date?: string; 69 | }; 70 | 71 | export type CashMerger = { 72 | acquirer_symbol: string; 73 | acquiree_symbol: string; 74 | rate: number; 75 | process_date: string; 76 | effective_date: string; 77 | payable_date?: string; 78 | }; 79 | 80 | export type StockMerger = { 81 | acquirer_symbol: string; 82 | acquirer_rate: number; 83 | acquiree_symbol: string; 84 | acquiree_rate: number; 85 | process_date: string; 86 | effective_date: string; 87 | payable_date?: string; 88 | }; 89 | 90 | export type StockAndCashMerger = { 91 | acquirer_symbol: string; 92 | acquirer_rate: number; 93 | acquiree_symbol: string; 94 | acquiree_rate: number; 95 | cash_rate: number; 96 | process_date: string; 97 | effective_date: string; 98 | payable_date?: string; 99 | }; 100 | 101 | export type Redemption = { 102 | symbol: string; 103 | rate: number; 104 | process_date: string; 105 | payable_date?: string; 106 | }; 107 | 108 | export type NameChange = { 109 | old_symbol: string; 110 | new_symbol: string; 111 | process_date: string; 112 | }; 113 | 114 | export type WorthlessRemoval = { 115 | symbol: string; 116 | process_date: string; 117 | }; 118 | 119 | export type Sort = "asc" | "desc"; 120 | 121 | export type CorporateActions = { 122 | corporate_actions: { 123 | reverse_splits?: ReverseSplit[]; 124 | forward_splits?: ForwardSplit[]; 125 | unit_splits?: UnitSplit[]; 126 | stock_dividends?: StockDividend[]; 127 | cash_dividends?: CashDividend[]; 128 | spin_offs?: SpinOff[]; 129 | cash_mergers?: CashMerger[]; 130 | stock_mergers?: StockMerger[]; 131 | stock_and_cash_mergers?: StockAndCashMerger[]; 132 | redemptions?: Redemption[]; 133 | name_changes?: NameChange[]; 134 | worthless_removals?: WorthlessRemoval[]; 135 | next_page_token: Nullable; 136 | }; 137 | }; 138 | 139 | export type GetStocksCorporateActionsOptions = { 140 | symbols: string; 141 | types?: string; 142 | start?: string; 143 | end?: string; 144 | limit?: number; 145 | page_token?: string; 146 | sort?: Sort; 147 | }; 148 | 149 | export const getStocksCorporateActions = 150 | (context: ClientContext) => (params: GetStocksCorporateActionsOptions) => 151 | context.request({ 152 | baseURL: baseURLs.marketData, 153 | path: "/v1beta1/corporate-actions", 154 | method: "GET", 155 | params, 156 | }); 157 | 158 | export type Logo = string; 159 | 160 | export type GetLogoOptions = { 161 | symbol: string; 162 | placeholder?: boolean; 163 | }; 164 | 165 | export const getLogo = (context: ClientContext) => (params: GetLogoOptions) => 166 | context.request({ 167 | baseURL: baseURLs.marketData, 168 | path: "/v1beta1/logos/:symbol", 169 | method: "GET", 170 | params, 171 | }); 172 | 173 | export interface News { 174 | news: NewsArticle[]; 175 | next_page_token: Nullable; 176 | } 177 | 178 | export interface NewsArticleImage { 179 | size: "thumb" | "small" | "large"; 180 | url: string; 181 | } 182 | 183 | export interface NewsArticle { 184 | id: number; 185 | headline: string; 186 | author: string; 187 | created_at: string; 188 | updated_at: string; 189 | summary: string; 190 | content: string; 191 | url: Nullable; 192 | images: NewsArticleImage[]; 193 | symbols: string[]; 194 | source: string; 195 | } 196 | 197 | export type GetNewsOptions = { 198 | start?: string; 199 | end?: string; 200 | sort?: string; 201 | symbols?: string; 202 | limit?: number; 203 | include_content?: boolean; 204 | exclude_contentless?: boolean; 205 | page_token?: string; 206 | }; 207 | 208 | export const getNews = (context: ClientContext) => (params: GetNewsOptions) => 209 | context.request({ 210 | baseURL: baseURLs.marketData, 211 | path: "/v1beta1/news", 212 | method: "GET", 213 | params, 214 | }); 215 | 216 | export type MostActive = { 217 | symbol: string; 218 | volume: number; 219 | trade_count: number; 220 | }; 221 | 222 | export type MostActives = { 223 | most_actives: MostActive[]; 224 | last_updated: string; 225 | }; 226 | 227 | export type MarketMovers = { 228 | gainers: MarketMover[]; 229 | losers: MarketMover[]; 230 | market_type: string; 231 | last_updated: string; 232 | }; 233 | 234 | export type MarketMover = { 235 | symbol: string; 236 | percent_change: number; 237 | change: number; 238 | price: number; 239 | }; 240 | 241 | export type GetStocksMostActivesOptions = { 242 | by?: string; 243 | top?: number; 244 | }; 245 | 246 | export const getStocksMostActives = 247 | (context: ClientContext) => (params: GetStocksMostActivesOptions) => 248 | context.request({ 249 | baseURL: baseURLs.marketData, 250 | path: "/v1beta1/screener/stocks/most-actives", 251 | method: "GET", 252 | params, 253 | }); 254 | 255 | export type GetStocksMarketMoversOptions = { 256 | by?: string; 257 | top?: number; 258 | }; 259 | 260 | export const getStocksMarketMovers = 261 | (context: ClientContext) => (params: GetStocksMarketMoversOptions) => 262 | context.request({ 263 | baseURL: baseURLs.marketData, 264 | path: "/v1beta1/screener/stocks/market-movers", 265 | method: "GET", 266 | params, 267 | }); 268 | 269 | export type GetStocksQuotesOptions = { 270 | symbols: string; 271 | start?: string; 272 | end?: string; 273 | limit?: number; 274 | asof?: string; 275 | feed?: string; 276 | sip?: string; 277 | page_token?: string; 278 | sort?: Sort; 279 | }; 280 | 281 | export type StocksQuotes = { 282 | quotes: { [symbol: string]: StockQuote[] }; 283 | next_page_token: Nullable; 284 | }; 285 | 286 | export const getStocksQuotes = 287 | (context: ClientContext) => (params: GetStocksQuotesOptions) => 288 | context.request({ 289 | baseURL: baseURLs.marketData, 290 | path: "/v2/stocks/quotes", 291 | method: "GET", 292 | params, 293 | }); 294 | 295 | export type GetStocksQuotesLatestOptions = { 296 | symbols: string; 297 | feed?: string; 298 | }; 299 | 300 | export type StocksQuotesLatest = { 301 | quotes: { [symbol: string]: StockQuote }; 302 | }; 303 | 304 | export const getStocksQuotesLatest = 305 | (context: ClientContext) => (params: GetStocksQuotesLatestOptions) => 306 | context.request({ 307 | baseURL: baseURLs.marketData, 308 | path: "/v2/stocks/quotes/latest", 309 | method: "GET", 310 | params, 311 | }); 312 | 313 | export type GetStocksBarsOptions = { 314 | symbols: string; 315 | timeframe: string; 316 | start?: string; 317 | end?: string; 318 | limit?: number; 319 | adjustment?: string; 320 | asof?: string; 321 | feed?: string; 322 | page_token?: string; 323 | sort?: string; 324 | }; 325 | 326 | export type StocksBars = { 327 | bars: { [symbol: string]: StocksBar[] }; 328 | next_page_token: Nullable; 329 | }; 330 | 331 | export type StocksBar = { 332 | t: string; 333 | o: number; 334 | h: number; 335 | l: number; 336 | c: number; 337 | v: number; 338 | n: number; 339 | vw: number; 340 | }; 341 | 342 | export const getStocksBars = 343 | (context: ClientContext) => (params: GetStocksBarsOptions) => 344 | context.request({ 345 | baseURL: baseURLs.marketData, 346 | path: "/v2/stocks/bars", 347 | method: "GET", 348 | params, 349 | }); 350 | 351 | export type GetStocksBarsLatestOptions = { 352 | symbols: string; 353 | feed?: string; 354 | currency?: string; 355 | }; 356 | 357 | export type StocksBarsLatest = { 358 | bars: { [symbol: string]: StocksBar }; 359 | }; 360 | 361 | export const getStocksBarsLatest = 362 | (context: ClientContext) => (params: GetStocksBarsLatestOptions) => 363 | context.request({ 364 | baseURL: baseURLs.marketData, 365 | path: "/v2/stocks/bars/latest", 366 | method: "GET", 367 | params, 368 | }); 369 | 370 | export type GetForexRatesOptions = { 371 | currency_pairs: string; 372 | timeframe: string; 373 | start?: string; 374 | end?: string; 375 | limit?: number; 376 | sort?: Sort; 377 | page_token?: string; 378 | }; 379 | 380 | export type ForexRate = { 381 | bp: number; 382 | mp: number; 383 | ap: number; 384 | t: string; 385 | }; 386 | 387 | export type ForexRates = { 388 | next_page_token: string; 389 | rates: { 390 | [currencyPair: string]: ForexRate[]; 391 | }; 392 | }; 393 | 394 | export const getForexRates = 395 | (context: ClientContext) => (params: GetForexRatesOptions) => 396 | context.request({ 397 | baseURL: baseURLs.marketData, 398 | path: "/v1beta1/forex/rates", 399 | method: "GET", 400 | params, 401 | }); 402 | 403 | export type ForexRatesLatest = { 404 | rates: { 405 | [currencyPair: string]: ForexRate; 406 | }; 407 | }; 408 | 409 | export type GetForexRatesLatestOptions = { 410 | currency_pairs: string; 411 | }; 412 | 413 | export const getLatestForexRates = 414 | (context: ClientContext) => (params: GetForexRatesLatestOptions) => 415 | context.request({ 416 | baseURL: baseURLs.marketData, 417 | path: "/v1beta1/forex/latest/rates", 418 | method: "GET", 419 | params, 420 | }); 421 | 422 | export type GetStocksSnapshotsOptions = { 423 | symbols: string; 424 | feed?: "sip" | "iex" | "otc"; 425 | sip?: string; 426 | }; 427 | 428 | export type StockTrade = { 429 | t: string; 430 | p: number; 431 | s: number; 432 | c: string[]; 433 | i: number; 434 | z: string; 435 | }; 436 | 437 | export type StockQuote = { 438 | t: string; 439 | ax: string; 440 | ap: number; 441 | as: number; 442 | bx: string; 443 | bp: number; 444 | bs: number; 445 | c: string[]; 446 | z: string; 447 | }; 448 | 449 | export type StockBar = { 450 | t: string; 451 | o: number; 452 | h: number; 453 | l: number; 454 | c: number; 455 | v: number; 456 | n: number; 457 | vw: number; 458 | }; 459 | 460 | export type StockSnapshots = { 461 | snapshots: { 462 | [symbol: string]: { 463 | latest_trade: StockTrade; 464 | latest_quote: StockQuote; 465 | minute_bar: StockBar; 466 | daily_bar: StockBar; 467 | prev_daily_bar: StockBar; 468 | }; 469 | }; 470 | }; 471 | 472 | export const getStocksSnapshots = 473 | (context: ClientContext) => (params: GetStocksSnapshotsOptions) => 474 | context.request({ 475 | baseURL: baseURLs.marketData, 476 | path: "/v1beta1/stocks/snapshots", 477 | method: "GET", 478 | params, 479 | }); 480 | 481 | export type StocksAuctions = { 482 | auctions: { [symbol: string]: StocksAuction[] }; 483 | next_page_token: Nullable; 484 | }; 485 | 486 | export type StocksAuction = { 487 | d: string; 488 | o: StocksAuctionPrice[]; 489 | c: StocksAuctionPrice[]; 490 | }; 491 | 492 | export type StocksAuctionPrice = { 493 | c: string; 494 | p: number; 495 | t: string; 496 | x: string; 497 | }; 498 | 499 | export type GetStocksAuctionsOptions = { 500 | symbols: string; 501 | start?: string; 502 | end?: string; 503 | limit?: number; 504 | asof?: string; 505 | feed?: string; 506 | page_token?: string; 507 | sort?: string; 508 | }; 509 | 510 | export const getStocksAuctions = 511 | (context: ClientContext) => (params: GetStocksAuctionsOptions) => 512 | context.request({ 513 | baseURL: baseURLs.marketData, 514 | path: "/v2/stocks/auctions", 515 | method: "GET", 516 | params, 517 | }); 518 | 519 | export type GetStocksConditionsOptions = { 520 | tickType: string; 521 | tape: string; 522 | }; 523 | 524 | export type StocksConditions = { 525 | conditions: { [code: string]: string }; 526 | }; 527 | 528 | export const getStocksConditions = 529 | (context: ClientContext) => (params: GetStocksConditionsOptions) => 530 | context.request({ 531 | baseURL: baseURLs.marketData, 532 | path: `/v2/stocks/meta/conditions/${params.tickType}`, 533 | method: "GET", 534 | params: { tape: params.tape }, 535 | }); 536 | 537 | export type StocksExchangeCodes = { 538 | exchanges: { [code: string]: string }; 539 | }; 540 | 541 | export const getStocksExchangeCodes = (context: ClientContext) => () => 542 | context.request({ 543 | baseURL: baseURLs.marketData, 544 | path: "/v2/stocks/meta/exchanges", 545 | method: "GET", 546 | }); 547 | 548 | export type GetStocksTradesOptions = { 549 | symbols: string; 550 | start?: string; 551 | end?: string; 552 | limit?: number; 553 | asof?: string; 554 | feed?: string; 555 | sip?: string; 556 | page_token?: string; 557 | sort?: Sort; 558 | }; 559 | 560 | export type StocksTrades = { 561 | trades: { [symbol: string]: StockTrade[] }; 562 | next_page_token: Nullable; 563 | }; 564 | 565 | export const getStocksTrades = 566 | (context: ClientContext) => (params: GetStocksTradesOptions) => 567 | context.request({ 568 | baseURL: baseURLs.marketData, 569 | path: "/v2/stocks/trades", 570 | method: "GET", 571 | params, 572 | }); 573 | 574 | export type GetStocksTradesLatestOptions = { 575 | symbols: string; 576 | feed?: "sip" | "iex" | "otc"; 577 | sip?: string; 578 | }; 579 | 580 | export type StocksTradesLatest = { 581 | trades: { [symbol: string]: StockTrade }; 582 | }; 583 | 584 | export const getStocksTradesLatest = 585 | (context: ClientContext) => (params: GetStocksTradesLatestOptions) => 586 | context.request({ 587 | baseURL: baseURLs.marketData, 588 | path: "/v2/stocks/trades/latest", 589 | method: "GET", 590 | params, 591 | }); 592 | 593 | export type OptionsBars = { 594 | bars: OptionBar[]; 595 | next_page_token: Nullable; 596 | }; 597 | 598 | export type OptionBar = { 599 | t: string; 600 | o: number; 601 | h: number; 602 | l: number; 603 | c: number; 604 | v: number; 605 | n: number; 606 | vw: number; 607 | }; 608 | 609 | export type GetOptionsBarsOptions = { 610 | symbols: string; 611 | timeframe: string; 612 | start?: string; 613 | end?: string; 614 | limit?: number; 615 | page_token?: string; 616 | sort?: Sort; 617 | }; 618 | 619 | export const getOptionsBars = 620 | (context: ClientContext) => (params: GetOptionsBarsOptions) => 621 | context.request({ 622 | baseURL: baseURLs.marketData, 623 | path: "/v1beta1/options/bars", 624 | method: "GET", 625 | params, 626 | }); 627 | 628 | export type OptionsExchanges = { 629 | [exchangeCode: string]: string; 630 | }; 631 | 632 | export const getOptionsExchanges = (context: ClientContext) => () => 633 | context.request({ 634 | baseURL: baseURLs.marketData, 635 | path: "/v1beta1/options/meta/exchanges", 636 | method: "GET", 637 | }); 638 | 639 | export type OptionsSnapshotsTrade = { 640 | t: string; 641 | x: string; 642 | p: number; 643 | s: number; 644 | c: string; 645 | }; 646 | 647 | export type OptionsSnapshotsQuote = { 648 | t: string; 649 | ax: string; 650 | ap: number; 651 | as: number; 652 | bx: string; 653 | bp: number; 654 | bs: number; 655 | c: string; 656 | }; 657 | 658 | export type OptionsSnapshots = { 659 | snapshots: { 660 | [symbol: string]: { 661 | latest_trade: OptionsSnapshotsTrade; 662 | latest_quote: OptionsSnapshotsQuote; 663 | }; 664 | }; 665 | }; 666 | 667 | export type GetOptionsSnapshotsOptions = { 668 | symbols: string; 669 | feed?: string; 670 | }; 671 | 672 | export const getOptionsSnapshots = 673 | (context: ClientContext) => (params: GetOptionsSnapshotsOptions) => 674 | context.request({ 675 | baseURL: baseURLs.marketData, 676 | path: "/v1beta1/options/snapshots", 677 | method: "GET", 678 | params, 679 | }); 680 | 681 | export type GetOptionsTradesOptions = { 682 | symbols: string; 683 | start?: string; 684 | end?: string; 685 | limit?: number; 686 | page_token?: string; 687 | sort?: Sort; 688 | }; 689 | 690 | export type OptionsTrades = { 691 | trades: { 692 | [symbol: string]: OptionsSnapshotsTrade[]; 693 | }; 694 | next_page_token: Nullable; 695 | }; 696 | 697 | export const getOptionsTrades = 698 | (context: ClientContext) => (params: GetOptionsTradesOptions) => 699 | context.request({ 700 | baseURL: baseURLs.marketData, 701 | path: "/v1beta1/options/trades", 702 | method: "GET", 703 | params, 704 | }); 705 | 706 | export type OptionsTradesLatest = { 707 | trades: { 708 | [symbol: string]: OptionsSnapshotsTrade[]; 709 | }; 710 | next_page_token: Nullable; 711 | }; 712 | 713 | export type GetOptionsTradesLatestOptions = { 714 | symbols: string; 715 | feed?: string; 716 | }; 717 | 718 | export const getOptionsTradesLatest = 719 | (context: ClientContext) => (params: GetOptionsTradesLatestOptions) => 720 | context.request({ 721 | baseURL: baseURLs.marketData, 722 | path: "/v1beta1/options/trades/latest", 723 | method: "GET", 724 | params, 725 | }); 726 | 727 | export type GetCryptoBarsOptions = { 728 | symbols: string; 729 | timeframe: string; 730 | start?: string; 731 | end?: string; 732 | limit?: number; 733 | page_token?: string; 734 | sort?: Sort; 735 | }; 736 | 737 | export type CryptoBars = { 738 | bars: { 739 | [symbol: string]: CryptoBar[]; 740 | }; 741 | next_page_token: Nullable; 742 | }; 743 | 744 | export type CryptoBar = { 745 | t: string; 746 | o: number; 747 | h: number; 748 | l: number; 749 | c: number; 750 | v: number; 751 | n: number; 752 | vw: number; 753 | }; 754 | 755 | export const getCryptoBars = 756 | (context: ClientContext) => (params: GetCryptoBarsOptions) => 757 | context.request({ 758 | baseURL: baseURLs.marketData, 759 | path: "/v1beta1/crypto/bars", 760 | method: "GET", 761 | params, 762 | }); 763 | 764 | export type GetCryptoBarsLatestOptions = { 765 | loc: string; 766 | symbols: string; 767 | }; 768 | 769 | export type CryptoBarsLatest = { 770 | bars: { [symbol: string]: CryptoBar }; 771 | }; 772 | 773 | export const getLatestCryptoBars = 774 | (context: ClientContext) => (params: GetCryptoBarsLatestOptions) => 775 | context.request({ 776 | baseURL: baseURLs.marketData, 777 | path: `/v1beta3/crypto/${params.loc}/latest/bars`, 778 | method: "GET", 779 | params: { 780 | symbols: params.symbols, 781 | }, 782 | }); 783 | 784 | export type GetCryptoQuotesOptions = { 785 | symbols: string; 786 | start?: string; 787 | end?: string; 788 | limit?: number; 789 | page_token?: string; 790 | sort?: Sort; 791 | }; 792 | 793 | export type CryptoQuote = { 794 | t: string; 795 | bp: number; 796 | bs: number; 797 | ap: number; 798 | as: number; 799 | }; 800 | 801 | export type CryptoQuotes = { 802 | quotes: { 803 | [symbol: string]: CryptoQuote[]; 804 | }; 805 | next_page_token: Nullable; 806 | }; 807 | 808 | export const getCryptoQuotes = 809 | (context: ClientContext) => (params: GetCryptoQuotesOptions) => 810 | context.request({ 811 | baseURL: baseURLs.marketData, 812 | path: "/v1beta1/crypto/quotes", 813 | method: "GET", 814 | params, 815 | }); 816 | 817 | export type CryptoQuotesLatest = { 818 | quotes: { [symbol: string]: CryptoQuote }; 819 | }; 820 | 821 | export type GetCryptoQuotesLatestOptions = { 822 | loc: string; 823 | symbols: string; 824 | }; 825 | 826 | export const getCryptoQuotesLatest = 827 | (context: ClientContext) => (params: GetCryptoQuotesLatestOptions) => 828 | context.request({ 829 | baseURL: baseURLs.marketData, 830 | path: `/v1beta3/crypto/${params.loc}/latest/quotes`, 831 | method: "GET", 832 | params: { 833 | symbols: params.symbols, 834 | }, 835 | }); 836 | 837 | export type CryptoTrade = { 838 | t: string; 839 | p: number; 840 | s: number; 841 | tks: string; 842 | i: number; 843 | }; 844 | 845 | export type CryptoSnapshots = { 846 | snapshots: { 847 | [symbol: string]: { 848 | daily_bar: CryptoBar; 849 | latest_quote: CryptoQuote; 850 | latest_trade: CryptoTrade; 851 | minute_bar: CryptoBar; 852 | prev_daily_bar: CryptoBar; 853 | }; 854 | }; 855 | }; 856 | 857 | export type GetCryptoSnapshotsOptions = { 858 | loc: string; 859 | symbols: string; 860 | }; 861 | 862 | export const getCryptoSnapshots = 863 | (context: ClientContext) => (params: GetCryptoSnapshotsOptions) => 864 | context.request({ 865 | baseURL: baseURLs.marketData, 866 | path: "/v1beta1/crypto/snapshots", 867 | method: "GET", 868 | params, 869 | }); 870 | 871 | export type GetCryptoTradesOptions = { 872 | loc: string; 873 | symbols: string; 874 | start?: string; 875 | end?: string; 876 | limit?: number; 877 | page_token?: string; 878 | sort?: string; 879 | }; 880 | 881 | export type CryptoTrades = { 882 | trades: { [symbol: string]: CryptoTrade[] }; 883 | next_page_token: Nullable; 884 | }; 885 | 886 | export const getCryptoTrades = 887 | (context: ClientContext) => (params: GetCryptoTradesOptions) => 888 | context.request({ 889 | baseURL: baseURLs.marketData, 890 | path: "/v1beta3/crypto/:loc/trades", 891 | method: "GET", 892 | params, 893 | }); 894 | 895 | export type CryptoTradesLatest = { 896 | trades: { [symbol: string]: CryptoTrade[] }; 897 | next_page_token: Nullable; 898 | }; 899 | 900 | export type GetCryptoTradesLatestOptions = { 901 | loc: string; 902 | symbols: string; 903 | }; 904 | 905 | export const getCryptoTradesLatest = 906 | (context: ClientContext) => (params: GetCryptoTradesLatestOptions) => 907 | context.request({ 908 | baseURL: baseURLs.marketData, 909 | path: `/v1beta3/crypto/${params.loc}/latest/trades`, 910 | method: "GET", 911 | params: { 912 | symbols: params.symbols, 913 | }, 914 | }); 915 | 916 | export type CryptoOrderbooksLatest = { 917 | orderbooks: { [symbol: string]: CryptoOrderbook }; 918 | }; 919 | 920 | export type CryptoOrderbook = { 921 | t: string; 922 | b: CryptoOrderbookEntry[]; 923 | a: CryptoOrderbookEntry[]; 924 | }; 925 | 926 | export type CryptoOrderbookEntry = { 927 | p: number; 928 | s: number; 929 | }; 930 | 931 | export type GetCryptoOrderbooksLatestOptions = { 932 | loc: string; 933 | symbols: string; 934 | }; 935 | 936 | export const getLatestCryptoOrderbooks = 937 | (context: ClientContext) => (params: GetCryptoOrderbooksLatestOptions) => 938 | context.request({ 939 | baseURL: baseURLs.marketData, 940 | path: `/v1beta3/crypto/${params.loc}/latest/orderbooks`, 941 | method: "GET", 942 | params: { 943 | symbols: params.symbols, 944 | }, 945 | }); 946 | -------------------------------------------------------------------------------- /api/trade.ts: -------------------------------------------------------------------------------- 1 | import { ClientContext } from "../factory/createClient.ts"; 2 | 3 | // Used for fields where the type may change based on the context, such as prices. 4 | export type UnstableNumber = string | number; 5 | 6 | export type Nullable = T | null; 7 | 8 | export type AccountStatus = 9 | | "ONBOARDING" 10 | | "SUBMISSION_FAILED" 11 | | "SUBMITTED" 12 | | "ACCOUNT_UPDATED" 13 | | "APPROVAL_PENDING" 14 | | "ACTIVE" 15 | | "REJECTED"; 16 | 17 | export type OptionsApprovedLevel = 0 | 1 | 2; 18 | 19 | export type OptionsTradingLevel = 0 | 1 | 2; 20 | 21 | export type Account = { 22 | id: string; 23 | account_number: string; 24 | status: AccountStatus; 25 | currency: string; 26 | cash: string; 27 | portfolio_value: string; 28 | non_marginable_buying_power: string; 29 | accrued_fees: string; 30 | pending_transfer_in: string; 31 | pending_transfer_out: string; 32 | pattern_day_trader: boolean; 33 | trade_suspended_by_user: boolean; 34 | trading_blocked: boolean; 35 | transfers_blocked: boolean; 36 | account_blocked: boolean; 37 | created_at: string; 38 | shorting_enabled: boolean; 39 | long_market_value: string; 40 | short_market_value: string; 41 | equity: string; 42 | last_equity: string; 43 | multiplier: string; 44 | buying_power: string; 45 | initial_margin: string; 46 | maintenance_margin: string; 47 | sma: string; 48 | daytrade_count: number; 49 | last_maintenance_margin: string; 50 | daytrading_buying_power: string; 51 | regt_buying_power: string; 52 | options_buying_power: string; 53 | options_approved_level: OptionsApprovedLevel; 54 | options_trading_level: OptionsTradingLevel; 55 | }; 56 | 57 | export const getAccount = ({ request }: ClientContext) => () => 58 | request({ 59 | path: "/v2/account", 60 | }); 61 | 62 | export type BaseOrder = { 63 | id: string; 64 | client_order_id: string; 65 | created_at: string; 66 | updated_at: string; 67 | submitted_at: string; 68 | filled_at: Nullable; 69 | expired_at: Nullable; 70 | canceled_at: Nullable; 71 | failed_at: Nullable; 72 | replaced_at: Nullable; 73 | replaced_by: Nullable; 74 | replaces: Nullable; 75 | asset_id: string; 76 | notional: Nullable; 77 | qty: string; 78 | filled_qty: string; 79 | filled_avg_price: Nullable; 80 | order_class: string; 81 | order_type: string; 82 | type: Type; 83 | side: string; 84 | time_in_force: string; 85 | limit_price: Nullable; 86 | stop_price: Nullable; 87 | status: string; 88 | extended_hours: boolean; 89 | legs?: Nullable; 90 | trail_percent: Nullable; 91 | trail_price: Nullable; 92 | hwm: Nullable; 93 | subtag: Nullable; 94 | source: Nullable; 95 | }; 96 | 97 | export type EquityOrder = BaseOrder & { 98 | asset_class: "us_equity"; 99 | symbol: string; 100 | }; 101 | 102 | export type OptionsOrder = BaseOrder & { 103 | asset_class: "us_option"; 104 | symbol: string; 105 | order_class: "simple"; 106 | }; 107 | 108 | export type CryptoOrder = BaseOrder & { 109 | asset_class: "crypto"; 110 | symbol: string; 111 | }; 112 | 113 | export type Order = EquityOrder | OptionsOrder | CryptoOrder; 114 | 115 | export type Position = { 116 | asset_id: string; 117 | exchange: string; 118 | asset_class: string; 119 | symbol: string; 120 | asset_marginable: boolean; 121 | qty: string; 122 | avg_entry_price: string; 123 | side: Direction; 124 | market_value: string; 125 | cost_basis: string; 126 | unrealized_pl: string; 127 | unrealized_plpc: string; 128 | unrealized_intraday_pl: string; 129 | unrealized_intraday_plpc: string; 130 | current_price: string; 131 | lastday_price: string; 132 | change_today: string; 133 | qty_available: string; 134 | }; 135 | 136 | export type TimeInForce = "day" | "gtc" | "opg" | "cls" | "ioc" | "fok"; 137 | 138 | export type PositionIntent = 139 | | "buy_to_open" 140 | | "buy_to_close" 141 | | "sell_to_open" 142 | | "sell_to_close"; 143 | 144 | export type Type = "market" | "limit" | "stop" | "stop_limit" | "trailing_stop"; 145 | 146 | export type Side = "buy" | "sell"; 147 | 148 | export type OrderClass = "simple" | "oco" | "oto" | "bracket" | ""; 149 | 150 | export type Direction = "long" | "short"; 151 | 152 | export type TakeProfit = { 153 | limit_price?: string; 154 | }; 155 | 156 | export type StopLoss = { 157 | stop_price?: string; 158 | limit_price?: string; 159 | }; 160 | 161 | export type CreateOrderOptions = { 162 | symbol: string; 163 | qty?: UnstableNumber; 164 | notional?: UnstableNumber; 165 | side: Side; 166 | type: Type; 167 | time_in_force: TimeInForce; 168 | limit_price?: UnstableNumber; 169 | stop_price?: UnstableNumber; 170 | trail_price?: UnstableNumber; 171 | trail_percent?: UnstableNumber; 172 | extended_hours?: boolean; 173 | client_order_id?: string; 174 | order_class?: OrderClass; 175 | take_profit?: TakeProfit; 176 | stop_loss?: StopLoss; 177 | position_intent?: PositionIntent; 178 | }; 179 | 180 | export const createOrder = 181 | ({ request }: ClientContext) => (data: CreateOrderOptions) => 182 | request({ 183 | path: "/v2/orders", 184 | method: "POST", 185 | data, 186 | }); 187 | 188 | export type GetOrderOptions = { 189 | order_id: string; 190 | }; 191 | 192 | export const getOrder = 193 | ({ request }: ClientContext) => ({ order_id }: GetOrderOptions) => 194 | request({ 195 | path: `/v2/orders/${order_id}`, 196 | method: "GET", 197 | }); 198 | 199 | export type GetOrdersOptions = { 200 | status?: string; 201 | limit?: UnstableNumber; 202 | after?: string; 203 | until?: string; 204 | direction?: Direction; 205 | nested?: boolean; 206 | symbols?: string; 207 | side?: Side; 208 | order_id: string; 209 | }; 210 | 211 | export const getOrders = 212 | ({ request }: ClientContext) => (params: GetOrdersOptions) => 213 | request({ 214 | path: "/v2/orders", 215 | method: "GET", 216 | params, 217 | }); 218 | 219 | export type ReplaceOrderOptions = { 220 | qty?: UnstableNumber; 221 | time_in_force?: string; 222 | limit_price?: UnstableNumber; 223 | stop_price?: UnstableNumber; 224 | trail?: UnstableNumber; 225 | client_order_id?: string; 226 | order_id: string; 227 | }; 228 | 229 | export const replaceOrder = 230 | ({ request }: ClientContext) => (data: ReplaceOrderOptions) => 231 | request({ 232 | path: `/v2/orders/${data.order_id}`, 233 | method: "PATCH", 234 | data, 235 | }); 236 | 237 | export type CancelOrderOptions = { 238 | order_id: string; 239 | }; 240 | 241 | export const cancelOrder = 242 | ({ request }: ClientContext) => ({ order_id }: CancelOrderOptions) => 243 | request({ 244 | path: `/v2/orders/${order_id}`, 245 | method: "DELETE", 246 | }); 247 | 248 | export const cancelOrders = ({ request }: ClientContext) => () => 249 | request({ 250 | path: "/v2/orders", 251 | method: "DELETE", 252 | }); 253 | 254 | export type GetPositionOptions = { 255 | symbol_or_asset_id: string; 256 | }; 257 | 258 | export const getPosition = 259 | ({ request }: ClientContext) => 260 | ({ symbol_or_asset_id }: GetPositionOptions) => 261 | request({ 262 | path: `/v2/positions/${symbol_or_asset_id}`, 263 | method: "GET", 264 | }); 265 | 266 | export const getPositions = ({ request }: ClientContext) => () => 267 | request>({ 268 | path: "/v2/positions", 269 | method: "GET", 270 | }); 271 | 272 | export type ClosePositionOptions = { 273 | symbol_or_asset_id: string; 274 | }; 275 | 276 | export const closePosition = 277 | ({ request }: ClientContext) => 278 | ({ symbol_or_asset_id }: ClosePositionOptions) => 279 | request({ 280 | path: `/v2/positions/${symbol_or_asset_id}`, 281 | method: "DELETE", 282 | }); 283 | 284 | export const closePositions = ({ request }: ClientContext) => () => 285 | request>>({ 286 | path: "/v2/positions", 287 | method: "DELETE", 288 | }); 289 | 290 | export type GetAssetsOptions = { 291 | status?: string; 292 | asset_class?: string; 293 | asset_status?: string; 294 | }; 295 | 296 | export type ExerciseOption = { 297 | symbol_or_contract_id: string; 298 | }; 299 | 300 | export const exerciseOption = 301 | ({ request }: ClientContext) => ({ symbol_or_contract_id }: ExerciseOption) => 302 | request({ 303 | path: `/v2/positions/${symbol_or_contract_id}/exercise`, 304 | method: "POST", 305 | }); 306 | 307 | export type GetCalendarOptions = { 308 | start?: string; 309 | end?: string; 310 | dateType?: "TRADING" | "SETTLEMENT"; 311 | }; 312 | 313 | export type Calendar = { 314 | date: string; 315 | open: string; 316 | close: string; 317 | settlement_date: string; 318 | }; 319 | 320 | export const getCalendar = 321 | ({ request }: ClientContext) => (params: GetCalendarOptions = {}) => 322 | request({ 323 | path: "/v2/calendar", 324 | method: "GET", 325 | params, 326 | }); 327 | 328 | export type Clock = { 329 | timestamp: string; 330 | is_open: boolean; 331 | next_open: string; 332 | next_close: string; 333 | }; 334 | 335 | export const getClock = ({ request }: ClientContext) => () => 336 | request({ 337 | path: "/v2/clock", 338 | }); 339 | 340 | export type GetAssetOptions = { 341 | symbol_or_asset_id: string; 342 | }; 343 | 344 | export const getAsset = 345 | ({ request }: ClientContext) => ({ symbol_or_asset_id }: GetAssetOptions) => 346 | request({ 347 | path: `/v2/assets/${symbol_or_asset_id}`, 348 | method: "GET", 349 | }); 350 | 351 | export type Asset = { 352 | id: string; 353 | class: string; 354 | exchange: string; 355 | symbol: string; 356 | name: string; 357 | status: string; 358 | tradable: boolean; 359 | marginable: boolean; 360 | shortable: boolean; 361 | easy_to_borrow: boolean; 362 | fractionable: boolean; 363 | maintenance_margin_requirement: string; 364 | tradability: string; 365 | symbol_with_class: string; 366 | asset_id: string; 367 | }; 368 | 369 | export const getAssets = 370 | ({ request }: ClientContext) => (params: GetAssetsOptions = {}) => 371 | request({ 372 | path: "/v2/assets", 373 | method: "GET", 374 | params, 375 | }); 376 | 377 | export type Watchlist = { 378 | id: string; 379 | account_id: string; 380 | created_at: string; 381 | updated_at: string; 382 | name: string; 383 | assets: Asset[]; 384 | }; 385 | 386 | export type GetWatchlistOptions = { 387 | watchlist_id: string; 388 | }; 389 | 390 | export const getWatchlist = 391 | ({ request }: ClientContext) => ({ watchlist_id }: GetWatchlistOptions) => 392 | request({ 393 | path: `/v2/watchlists/${watchlist_id}`, 394 | }); 395 | 396 | export const getWatchlists = ({ request }: ClientContext) => () => 397 | request({ 398 | path: "/v2/watchlists", 399 | }); 400 | 401 | export type CreateWatchlistOptions = { 402 | name: string; 403 | symbols: Nullable; 404 | }; 405 | 406 | export const createWatchlist = 407 | ({ request }: ClientContext) => (data: CreateWatchlistOptions) => 408 | request({ 409 | path: "/v2/watchlists", 410 | method: "POST", 411 | data, 412 | }); 413 | 414 | export type UpdateWatchlistOptions = { 415 | name: string; 416 | symbols: Nullable; 417 | watchlist_id: string; 418 | }; 419 | 420 | export const updateWatchlist = 421 | ({ request }: ClientContext) => (data: UpdateWatchlistOptions) => 422 | request({ 423 | path: `/v2/watchlists/${data.watchlist_id}`, 424 | method: "PATCH", 425 | data, 426 | }); 427 | 428 | export type DeleteWatchlistOptions = { 429 | watchlist_id: string; 430 | }; 431 | 432 | export const deleteWatchlist = 433 | ({ request }: ClientContext) => ({ watchlist_id }: DeleteWatchlistOptions) => 434 | request({ 435 | path: `/v2/watchlists/${watchlist_id}`, 436 | method: "DELETE", 437 | }); 438 | 439 | export type PortfolioHistory = { 440 | timestamp: string[]; 441 | equity: string[]; 442 | profit_loss: string[]; 443 | profit_loss_pct: string[]; 444 | base_value: string; 445 | base_value_asof: string; 446 | timeframe: string; 447 | }; 448 | 449 | export type GetPortfolioHistoryOptions = { 450 | period?: string; 451 | timeframe?: string; 452 | intraday_reporting?: string; 453 | start?: string; 454 | end?: string; 455 | pnl_reset?: string; 456 | }; 457 | 458 | export const getPortfolioHistory = 459 | ({ request }: ClientContext) => (params: GetPortfolioHistoryOptions) => 460 | request({ 461 | path: "/v2/account/portfolio/history", 462 | method: "GET", 463 | params, 464 | }); 465 | 466 | export type Configurations = { 467 | dtbp_check: string; 468 | trade_confirm_email: string; 469 | suspend_trade: boolean; 470 | no_shorting: boolean; 471 | fractional_trading: boolean; 472 | max_margin_multiplier: string; 473 | max_options_trading_level: number; 474 | pdt_check: string; 475 | ptp_no_exception_entry: boolean; 476 | }; 477 | 478 | export const getConfigurations = ({ request }: ClientContext) => () => 479 | request({ 480 | path: "/v2/account/configurations", 481 | }); 482 | 483 | export type UpdateConfigurationsOptions = { 484 | dtbp_check?: string; 485 | trade_confirm_email?: string; 486 | suspend_trade?: boolean; 487 | no_shorting?: boolean; 488 | fractional_trading?: boolean; 489 | max_margin_multiplier?: string; 490 | max_options_trading_level?: number; 491 | pdt_check?: string; 492 | ptp_no_exception_entry?: boolean; 493 | }; 494 | 495 | export const updateConfigurations = 496 | ({ request }: ClientContext) => (data: UpdateConfigurationsOptions) => 497 | request({ 498 | path: "/v2/account/configurations", 499 | method: "PATCH", 500 | data, 501 | }); 502 | 503 | export type TradingActivity = { 504 | activity_type: string; 505 | id: string; 506 | cum_qty: string; 507 | leaves_qty: string; 508 | price: string; 509 | qty: string; 510 | side: string; 511 | symbol: string; 512 | transaction_time: string; 513 | order_id: string; 514 | type: string; 515 | order_status: string; 516 | }; 517 | 518 | export type NonTradeActivity = { 519 | activity_type: string; 520 | id: string; 521 | date: string; 522 | net_amount: string; 523 | symbol?: string; 524 | qty?: string; 525 | per_share_amount?: string; 526 | }; 527 | 528 | export type Activity = TradingActivity | NonTradeActivity; 529 | 530 | export type GetActivityOptions = { 531 | activity_type: string; 532 | activity_types?: string; 533 | date?: string; 534 | until?: string; 535 | after?: string; 536 | direction?: string; 537 | pageSize?: number; 538 | pageToken?: string; 539 | category?: string; 540 | }; 541 | 542 | export const getActivity = 543 | ({ request }: ClientContext) => ({ activity_type }: GetActivityOptions) => 544 | request({ 545 | path: `/v2/account/activities/${activity_type}`, 546 | method: "GET", 547 | }); 548 | 549 | export const getActivities = ({ request }: ClientContext) => () => 550 | request({ 551 | path: "/v2/account/activities", 552 | method: "GET", 553 | }); 554 | 555 | export type OptionsContract = { 556 | id: string; 557 | symbol: string; 558 | name: string; 559 | status: string; 560 | tradable: boolean; 561 | tradability: string; 562 | chain_id: string; 563 | type: string; 564 | option_type: string; 565 | expiration_date: string; 566 | strike_price: string; 567 | min_ticks: { 568 | above_tick: string; 569 | below_tick: string; 570 | cutoff_price: string; 571 | }; 572 | option_style: string; 573 | created_at: string; 574 | updated_at: string; 575 | last_trade_date: string; 576 | underlying: string; 577 | tradable_chain_id: string; 578 | chain_symbol: string; 579 | description: string; 580 | asset_id: string; 581 | }; 582 | 583 | export type GetOptionsContractOptions = { 584 | symbol_or_contract_id: string; 585 | }; 586 | 587 | export const getOptionsContract = 588 | ({ request }: ClientContext) => 589 | ({ symbol_or_contract_id }: GetOptionsContractOptions) => 590 | request({ 591 | path: `/v2/options/contracts/${symbol_or_contract_id}`, 592 | method: "GET", 593 | }); 594 | 595 | export type GetOptionsContractsOptions = { 596 | underlying_symbols?: string; 597 | status?: string; 598 | active?: boolean; 599 | expiration_date?: string; 600 | expiration_date_gte?: string; 601 | expiration_date_lte?: string; 602 | root_symbol?: string; 603 | type?: string; 604 | style?: string; 605 | strike_price_gte?: UnstableNumber; 606 | strike_price_lte?: UnstableNumber; 607 | page_token?: string; 608 | limit?: UnstableNumber; 609 | symbol_or_contract_id: string; 610 | }; 611 | 612 | export const getOptionsContracts = 613 | ({ request }: ClientContext) => (params: GetOptionsContractsOptions) => 614 | request({ 615 | path: "/v2/options/contracts", 616 | method: "GET", 617 | params, 618 | }); 619 | 620 | export type CorporateAction = { 621 | id: string; 622 | corporate_actions_id: string; 623 | ca_type: string; 624 | ca_sub_type: string; 625 | initiating_symbol: string; 626 | initiating_original_cusip: string; 627 | target_symbol: string; 628 | target_original_cusip: string; 629 | declaration_date: string; 630 | expiration_date: string; 631 | record_date: string; 632 | payable_date: string; 633 | cash: string; 634 | old_rate: string; 635 | new_rate: string; 636 | }; 637 | 638 | export type GetCorporateActionOptions = { 639 | id: string; 640 | }; 641 | 642 | export const getCorporateAction = 643 | ({ request }: ClientContext) => ({ id }: GetCorporateActionOptions) => 644 | request({ 645 | path: `/v2/corporate_actions/announcements/${id}`, 646 | method: "GET", 647 | }); 648 | 649 | export type GetCorporateActionsOptions = { 650 | ca_types: string; 651 | since: string; 652 | until: string; 653 | symbol?: string; 654 | cusip?: string; 655 | date_type?: string; 656 | }; 657 | 658 | export const getCorporateActions = 659 | ({ request }: ClientContext) => (params: GetCorporateActionsOptions) => 660 | request({ 661 | path: "/v2/corporate_actions/announcements", 662 | method: "GET", 663 | params, 664 | }); 665 | 666 | export type CryptoWallet = { 667 | id: string; 668 | currency: string; 669 | balance: string; 670 | available: string; 671 | held: string; 672 | profile_id: string; 673 | }; 674 | 675 | export type GetWalletOptions = { 676 | asset: string; 677 | }; 678 | 679 | export const getCryptoWallet = 680 | ({ request }: ClientContext) => ({ asset }: GetWalletOptions) => 681 | request({ 682 | path: `/v2/wallets/${asset}`, 683 | method: "GET", 684 | }); 685 | 686 | export const getCryptoWallets = ({ request }: ClientContext) => () => 687 | request({ 688 | path: "/v2/wallets", 689 | method: "GET", 690 | }); 691 | 692 | export type CryptoFee = { 693 | fee: string; 694 | network_fee: string; 695 | estimated_delivery: string; 696 | }; 697 | 698 | export type GetCryptoFeeEstimateOptions = { 699 | asset: string; 700 | from_address: string; 701 | to_address: string; 702 | amount: string; 703 | }; 704 | 705 | export const getFeeEstimate = 706 | ({ request }: ClientContext) => (params: GetCryptoFeeEstimateOptions) => 707 | request({ 708 | path: "/v2/wallets/fees/estimate", 709 | method: "GET", 710 | params, 711 | }); 712 | 713 | export type CryptoTransfer = { 714 | id: string; 715 | tx_hash: string; 716 | direction: "INCOMING" | "OUTGOING"; 717 | status: "PROCESSING" | "FAILED" | "COMPLETE"; 718 | amount: string; 719 | usd_value: string; 720 | network_fee: string; 721 | fees: string; 722 | chain: string; 723 | asset: string; 724 | from_address: string; 725 | to_address: string; 726 | created_at: string; 727 | }; 728 | 729 | export type CryptoTransferResponse = { 730 | wallets?: CryptoWallet | CryptoWallet[]; 731 | transfers?: CryptoTransfer[]; 732 | }; 733 | 734 | export type GetCryptoTransferOptions = { 735 | transfer_id: string; 736 | }; 737 | 738 | export const getCryptoTransfer = 739 | ({ request }: ClientContext) => ({ transfer_id }: GetCryptoTransferOptions) => 740 | request({ 741 | path: `/v2/wallets/transfers/${transfer_id}`, 742 | method: "GET", 743 | }); 744 | 745 | export type GetCryptoTransfersOptions = { 746 | asset?: string; 747 | }; 748 | 749 | export const getCryptoTransfers = 750 | ({ request }: ClientContext) => (params?: GetCryptoTransfersOptions) => 751 | request({ 752 | path: "/v2/wallets/transfers", 753 | method: "GET", 754 | params, 755 | }); 756 | 757 | export type CreateCryptoTransferOptions = { 758 | amount: string; 759 | address: string; 760 | asset: string; 761 | }; 762 | 763 | export const createCryptoTransfer = 764 | ({ request }: ClientContext) => (data: CreateCryptoTransferOptions) => 765 | request({ 766 | path: "/v2/wallets/transfers", 767 | method: "POST", 768 | data, 769 | }); 770 | 771 | export type WhitelistedAddress = { 772 | id: string; 773 | chain: string; 774 | asset: string; 775 | address: string; 776 | status: "ACTIVE" | "PENDING"; 777 | created_at: string; 778 | }; 779 | 780 | export type GetCryptoWhitelistedAddressOptions = { 781 | address: string; 782 | asset: string; 783 | }; 784 | 785 | export const getCryptoWhitelistedAddress = 786 | ({ request }: ClientContext) => 787 | (params: GetCryptoWhitelistedAddressOptions) => 788 | request({ 789 | path: "/v2/wallets/whitelists", 790 | method: "GET", 791 | params, 792 | }); 793 | 794 | export const getCryptoWhitelistedAddresses = 795 | ({ request }: ClientContext) => () => 796 | request({ 797 | path: "/v2/wallets/whitelists", 798 | method: "GET", 799 | }); 800 | 801 | export type RequestCryptoWhitelistedAddressOptions = { 802 | address: string; 803 | asset: string; 804 | }; 805 | 806 | export const requestCryptoWhitelistedAddress = 807 | ({ request }: ClientContext) => 808 | (data: RequestCryptoWhitelistedAddressOptions) => 809 | request({ 810 | path: "/v2/wallets/whitelists", 811 | method: "POST", 812 | data, 813 | }); 814 | 815 | export type RemoveCryptoWhitelistedAddressOptions = { 816 | whitelisted_address_id: string; 817 | }; 818 | 819 | export const removeCryptoWhitelistedAddress = 820 | ({ request }: ClientContext) => 821 | ({ whitelisted_address_id }: RemoveCryptoWhitelistedAddressOptions) => 822 | request({ 823 | path: `/v2/wallets/whitelists/${whitelisted_address_id}`, 824 | method: "DELETE", 825 | }); 826 | -------------------------------------------------------------------------------- /build.ts: -------------------------------------------------------------------------------- 1 | import { build, emptyDir } from "@deno/dnt"; 2 | 3 | // Create the npm directory 4 | await emptyDir("./npm"); 5 | 6 | // Generate the npm package 7 | await build({ 8 | entryPoints: ["./mod.ts"], 9 | outDir: "./npm", 10 | shims: { 11 | deno: true, 12 | }, 13 | // @todo: enable typeCheck in the future (unfinished types right now) 14 | typeCheck: false, 15 | test: false, 16 | scriptModule: false, 17 | declaration: "inline", 18 | package: { 19 | name: "@alpacahq/typescript-sdk", 20 | version: Deno.args[0], 21 | description: 22 | "A TypeScript SDK for the https://alpaca.markets REST API and WebSocket streams.", 23 | repository: { 24 | type: "git", 25 | url: "git+https://github.com/@alpacahq/typescript-sdk.git", 26 | }, 27 | types: "./esm/mod.d.ts", 28 | keywords: [ 29 | "alpaca", 30 | "alpaca.markets", 31 | "alpaca api", 32 | "alpaca sdk", 33 | "alpaca typescript", 34 | "alpaca websocket", 35 | "alpaca rest", 36 | "alpaca trading", 37 | "alpaca trading api", 38 | "alpaca trading sdk", 39 | "alpaca trading typescript", 40 | "alpaca trading websocket", 41 | "alpaca trading rest", 42 | "alpaca markets", 43 | "alpaca markets api", 44 | "alpaca markets sdk", 45 | "alpaca markets typescript", 46 | "alpaca markets websocket", 47 | "alpaca markets rest", 48 | ], 49 | author: "117", 50 | license: "MIT", 51 | bugs: { 52 | url: "https://github.com/@alpacahq/typescript-sdk/issues", 53 | }, 54 | homepage: "https://github.com/@alpacahq/typescript-sdk#readme", 55 | main: "mod.js", 56 | }, 57 | postBuild() { 58 | // Copy the README to the npm directory (for npmjs.com) 59 | Deno.copyFileSync("README.md", "npm/README.md"); 60 | }, 61 | }); 62 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "@deno/dnt": "jsr:@deno/dnt@^0.41.1" 4 | }, 5 | "tasks": { 6 | "build": "deno run -A build.ts" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3", 3 | "packages": { 4 | "specifiers": { 5 | "jsr:@deno/cache-dir@^0.8.0": "jsr:@deno/cache-dir@0.8.0", 6 | "jsr:@deno/dnt@^0.41.1": "jsr:@deno/dnt@0.41.1", 7 | "jsr:@deno/graph@^0.69.7": "jsr:@deno/graph@0.69.10", 8 | "jsr:@std/assert@^0.218.2": "jsr:@std/assert@0.218.2", 9 | "jsr:@std/bytes@^0.218.2": "jsr:@std/bytes@0.218.2", 10 | "jsr:@std/fmt@^0.218.2": "jsr:@std/fmt@0.218.2", 11 | "jsr:@std/fs@^0.218.2": "jsr:@std/fs@0.218.2", 12 | "jsr:@std/io@^0.218.2": "jsr:@std/io@0.218.2", 13 | "jsr:@std/path@^0.218.2": "jsr:@std/path@0.218.2", 14 | "npm:@ts-morph/bootstrap@0.22": "npm:@ts-morph/bootstrap@0.22.0", 15 | "npm:code-block-writer@^13.0.1": "npm:code-block-writer@13.0.1" 16 | }, 17 | "jsr": { 18 | "@deno/cache-dir@0.8.0": { 19 | "integrity": "e87e80a404958f6350d903e6238b72afb92468378b0b32111f7a1e4916ac7fe7", 20 | "dependencies": [ 21 | "jsr:@deno/graph@^0.69.7", 22 | "jsr:@std/fs@^0.218.2", 23 | "jsr:@std/io@^0.218.2" 24 | ] 25 | }, 26 | "@deno/dnt@0.41.1": { 27 | "integrity": "8746a773e031ae19ef43d0eece850217b76cf1d0118fdd8e059652d7023d4aff", 28 | "dependencies": [ 29 | "jsr:@deno/cache-dir@^0.8.0", 30 | "jsr:@std/fmt@^0.218.2", 31 | "jsr:@std/fs@^0.218.2", 32 | "jsr:@std/path@^0.218.2", 33 | "npm:@ts-morph/bootstrap@0.22", 34 | "npm:code-block-writer@^13.0.1" 35 | ] 36 | }, 37 | "@deno/graph@0.69.10": { 38 | "integrity": "38fe22ac5686f6ece5daeec5a4df65c6314d7d32adcc33f77917a13cfaffa26f" 39 | }, 40 | "@std/assert@0.218.2": { 41 | "integrity": "7f0a5a1a8cf86607cd6c2c030584096e1ffad27fc9271429a8cb48cfbdee5eaf" 42 | }, 43 | "@std/bytes@0.218.2": { 44 | "integrity": "91fe54b232dcca73856b79a817247f4a651dbb60d51baafafb6408c137241670" 45 | }, 46 | "@std/fmt@0.218.2": { 47 | "integrity": "99526449d2505aa758b6cbef81e7dd471d8b28ec0dcb1491d122b284c548788a" 48 | }, 49 | "@std/fs@0.218.2": { 50 | "integrity": "dd9431453f7282e8c577cc22c9e6d036055a9a980b5549f887d6012969fabcca", 51 | "dependencies": [ 52 | "jsr:@std/assert@^0.218.2", 53 | "jsr:@std/path@^0.218.2" 54 | ] 55 | }, 56 | "@std/io@0.218.2": { 57 | "integrity": "c64fbfa087b7c9d4d386c5672f291f607d88cb7d44fc299c20c713e345f2785f", 58 | "dependencies": [ 59 | "jsr:@std/bytes@^0.218.2" 60 | ] 61 | }, 62 | "@std/path@0.218.2": { 63 | "integrity": "b568fd923d9e53ad76d17c513e7310bda8e755a3e825e6289a0ce536404e2662", 64 | "dependencies": [ 65 | "jsr:@std/assert@^0.218.2" 66 | ] 67 | } 68 | }, 69 | "npm": { 70 | "@nodelib/fs.scandir@2.1.5": { 71 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 72 | "dependencies": { 73 | "@nodelib/fs.stat": "@nodelib/fs.stat@2.0.5", 74 | "run-parallel": "run-parallel@1.2.0" 75 | } 76 | }, 77 | "@nodelib/fs.stat@2.0.5": { 78 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 79 | "dependencies": {} 80 | }, 81 | "@nodelib/fs.walk@1.2.8": { 82 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 83 | "dependencies": { 84 | "@nodelib/fs.scandir": "@nodelib/fs.scandir@2.1.5", 85 | "fastq": "fastq@1.17.1" 86 | } 87 | }, 88 | "@ts-morph/bootstrap@0.22.0": { 89 | "integrity": "sha512-MI5q7pid4swAlE2lcHwHRa6rcjoIMyT6fy8uuZm8BGg7DHGi/H5bQ0GMZzbk3N0r/LfStMdOYPkl+3IwvfIQ2g==", 90 | "dependencies": { 91 | "@ts-morph/common": "@ts-morph/common@0.22.0" 92 | } 93 | }, 94 | "@ts-morph/common@0.22.0": { 95 | "integrity": "sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==", 96 | "dependencies": { 97 | "fast-glob": "fast-glob@3.3.2", 98 | "minimatch": "minimatch@9.0.3", 99 | "mkdirp": "mkdirp@3.0.1", 100 | "path-browserify": "path-browserify@1.0.1" 101 | } 102 | }, 103 | "balanced-match@1.0.2": { 104 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 105 | "dependencies": {} 106 | }, 107 | "brace-expansion@2.0.1": { 108 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 109 | "dependencies": { 110 | "balanced-match": "balanced-match@1.0.2" 111 | } 112 | }, 113 | "braces@3.0.2": { 114 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 115 | "dependencies": { 116 | "fill-range": "fill-range@7.0.1" 117 | } 118 | }, 119 | "code-block-writer@13.0.1": { 120 | "integrity": "sha512-c5or4P6erEA69TxaxTNcHUNcIn+oyxSRTOWV+pSYF+z4epXqNvwvJ70XPGjPNgue83oAFAPBRQYwpAJ/Hpe/Sg==", 121 | "dependencies": {} 122 | }, 123 | "fast-glob@3.3.2": { 124 | "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", 125 | "dependencies": { 126 | "@nodelib/fs.stat": "@nodelib/fs.stat@2.0.5", 127 | "@nodelib/fs.walk": "@nodelib/fs.walk@1.2.8", 128 | "glob-parent": "glob-parent@5.1.2", 129 | "merge2": "merge2@1.4.1", 130 | "micromatch": "micromatch@4.0.5" 131 | } 132 | }, 133 | "fastq@1.17.1": { 134 | "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", 135 | "dependencies": { 136 | "reusify": "reusify@1.0.4" 137 | } 138 | }, 139 | "fill-range@7.0.1": { 140 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 141 | "dependencies": { 142 | "to-regex-range": "to-regex-range@5.0.1" 143 | } 144 | }, 145 | "glob-parent@5.1.2": { 146 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 147 | "dependencies": { 148 | "is-glob": "is-glob@4.0.3" 149 | } 150 | }, 151 | "is-extglob@2.1.1": { 152 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 153 | "dependencies": {} 154 | }, 155 | "is-glob@4.0.3": { 156 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 157 | "dependencies": { 158 | "is-extglob": "is-extglob@2.1.1" 159 | } 160 | }, 161 | "is-number@7.0.0": { 162 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 163 | "dependencies": {} 164 | }, 165 | "merge2@1.4.1": { 166 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 167 | "dependencies": {} 168 | }, 169 | "micromatch@4.0.5": { 170 | "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", 171 | "dependencies": { 172 | "braces": "braces@3.0.2", 173 | "picomatch": "picomatch@2.3.1" 174 | } 175 | }, 176 | "minimatch@9.0.3": { 177 | "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", 178 | "dependencies": { 179 | "brace-expansion": "brace-expansion@2.0.1" 180 | } 181 | }, 182 | "mkdirp@3.0.1": { 183 | "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", 184 | "dependencies": {} 185 | }, 186 | "path-browserify@1.0.1": { 187 | "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", 188 | "dependencies": {} 189 | }, 190 | "picomatch@2.3.1": { 191 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 192 | "dependencies": {} 193 | }, 194 | "queue-microtask@1.2.3": { 195 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 196 | "dependencies": {} 197 | }, 198 | "reusify@1.0.4": { 199 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 200 | "dependencies": {} 201 | }, 202 | "run-parallel@1.2.0": { 203 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 204 | "dependencies": { 205 | "queue-microtask": "queue-microtask@1.2.3" 206 | } 207 | }, 208 | "to-regex-range@5.0.1": { 209 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 210 | "dependencies": { 211 | "is-number": "is-number@7.0.0" 212 | } 213 | } 214 | } 215 | }, 216 | "remote": { 217 | "https://deno.land/std@0.217.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", 218 | "https://deno.land/std@0.217.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", 219 | "https://deno.land/std@0.220.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", 220 | "https://deno.land/std@0.220.0/assert/_diff.ts": "4bf42969aa8b1a33aaf23eb8e478b011bfaa31b82d85d2ff4b5c4662d8780d2b", 221 | "https://deno.land/std@0.220.0/assert/_format.ts": "0ba808961bf678437fb486b56405b6fefad2cf87b5809667c781ddee8c32aff4", 222 | "https://deno.land/std@0.220.0/assert/assert_equals.ts": "4497c56fe7d2993b0d447926702802fc0becb44e319079e8eca39b482ee01b4e", 223 | "https://deno.land/std@0.220.0/assert/assert_is_error.ts": "6596f2b5ba89ba2fe9b074f75e9318cda97a2381e59d476812e30077fbdb6ed2", 224 | "https://deno.land/std@0.220.0/assert/assert_throws.ts": "31f3c061338aec2c2c33731973d58ccd4f14e42f355501541409ee958d2eb8e5", 225 | "https://deno.land/std@0.220.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", 226 | "https://deno.land/std@0.220.0/assert/equal.ts": "fae5e8a52a11d3ac694bbe1a53e13a7969e3f60791262312e91a3e741ae519e2", 227 | "https://deno.land/std@0.220.0/fmt/colors.ts": "d239d84620b921ea520125d778947881f62c50e78deef2657073840b8af9559a", 228 | "https://deno.land/std@0.92.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", 229 | "https://deno.land/std@0.92.0/_util/has_own_property.ts": "f5edd94ed3f3c20c517d812045deb97977e18501c9b7105b5f5c11a31893d7a2", 230 | "https://deno.land/std@0.92.0/async/deferred.ts": "624bef4b755b71394620508a0c234a93cb8020cbd1b04bfcdad41c174392cef6", 231 | "https://deno.land/std@0.92.0/async/delay.ts": "9de1d8d07d1927767ab7f82434b883f3d8294fb19cad819691a2ad81a728cf3d", 232 | "https://deno.land/std@0.92.0/async/mod.ts": "253b41c658d768613eacfb11caa0a9ca7148442f932018a45576f7f27554c853", 233 | "https://deno.land/std@0.92.0/async/mux_async_iterator.ts": "b9091909db04cdb0af6f7807677372f64c1488de6c4bd86004511b064bf230d6", 234 | "https://deno.land/std@0.92.0/async/pool.ts": "876f9e6815366cd017a3b4fbb9e9ae40310b1b6972f1bd541c94358bc11fb7e5", 235 | "https://deno.land/std@0.92.0/bytes/mod.ts": "1ae1ccfe98c4b979f12b015982c7444f81fcb921bea7aa215bf37d84f46e1e13", 236 | "https://deno.land/std@0.92.0/hash/sha1.ts": "1cca324b4b253885a47f121adafcfac55b4cc96113e22b338e1db26f37a730b8", 237 | "https://deno.land/std@0.92.0/http/_io.ts": "bf1331dd3be8aace9120614c1fedc2bb2449edc4779e31b74c0181ea9173f702", 238 | "https://deno.land/std@0.92.0/http/http_status.ts": "ebaa9bebfb8adc3d7b20c49e11037e4eefd79629ad80d81383933f4cdc91b3eb", 239 | "https://deno.land/std@0.92.0/http/server.ts": "d4e17c2aa5a5c65a2d19b9f24483be5f6c2a3e03665996fdf973e53c43091b48", 240 | "https://deno.land/std@0.92.0/io/buffer.ts": "2a92f02c1d8daaebaf13e5678ea5969c89f4fab533f687b9e7e86f49f11c3118", 241 | "https://deno.land/std@0.92.0/io/bufio.ts": "4053ea5d978479be68ae4d73424045a59c6b7a6e8f66727e4bfde516baa07126", 242 | "https://deno.land/std@0.92.0/io/ioutil.ts": "275fa440494df9b4b3aa656301ced2eeac533feec128b3a39b2b40f4cd957e42", 243 | "https://deno.land/std@0.92.0/io/util.ts": "03ca10e063afce551c501505c607ec336a40b9cb72363f5508e2a9ac81096bbf", 244 | "https://deno.land/std@0.92.0/textproto/mod.ts": "1c89b39a079dd158893ab2e9ff79391c66049433d6ca82da7d64b32280570d51", 245 | "https://deno.land/std@0.92.0/ws/mod.ts": "bc521b3066441eb115ac94e3507bcc73098542f81d8d3ce7aad8d837316ce990" 246 | }, 247 | "workspace": { 248 | "dependencies": [ 249 | "jsr:@deno/dnt@^0.41.1" 250 | ] 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /factory/createClient.test.ts: -------------------------------------------------------------------------------- 1 | // import { assert } from "https://deno.land/std@0.217.0/assert/assert.ts"; 2 | // import { assertEquals } from "https://deno.land/std@0.220.0/assert/assert_equals.ts"; 3 | // import { assertThrows } from "https://deno.land/std@0.220.0/assert/assert_throws.ts"; 4 | // import { createClient } from "../src/factory/createClient.ts"; 5 | // import { mockFetch } from "../src/util/mockFetch.ts"; 6 | 7 | // Deno.test( 8 | // "createClient should create a trade client with valid options", 9 | // () => { 10 | // const client = createClient({ 11 | // baseURL: "https://paper-api.alpaca.markets", 12 | // key: "EXAMPLE_KEY_ID", 13 | // secret: "EXAMPLE_KEY_SECRET", 14 | // }); 15 | 16 | // assert(client.v2.account !== undefined); 17 | // assert(typeof client.v2.account.get === "function"); 18 | // } 19 | // ); 20 | 21 | // Deno.test( 22 | // "createClient should create a market data client with valid options", 23 | // () => { 24 | // const client = createClient({ 25 | // baseURL: "https://data.alpaca.markets", 26 | // key: "EXAMPLE_KEY_ID", 27 | // secret: "EXAMPLE_KEY_SECRET", 28 | // }); 29 | 30 | // assert(client.v2.stocks !== undefined); 31 | // assert(typeof client.v2.stocks.bars.get === "function"); 32 | // } 33 | // ); 34 | 35 | // Deno.test("createClient should throw an error with an invalid base URL", () => { 36 | // assertThrows( 37 | // () => 38 | // createClient({ 39 | // // deno-lint-ignore ban-ts-comment 40 | // // @ts-expect-error 41 | // baseURL: "https://invalid-url.com", 42 | // key: "EXAMPLE_KEY_ID", 43 | // secret: "EXAMPLE_KEY_SECRET", 44 | // }), 45 | // Error, 46 | // "Invalid base URL" 47 | // ); 48 | // }); 49 | 50 | // Deno.test("createClient should use the provided token bucket options", () => { 51 | // const tokenBucketOptions = { 52 | // capacity: 100, 53 | // fillRate: 2, 54 | // }; 55 | 56 | // const client = createClient({ 57 | // baseURL: "https://paper-api.alpaca.markets", 58 | // key: "EXAMPLE_KEY_ID", 59 | // secret: "EXAMPLE_KEY_SECRET", 60 | // tokenBucket: tokenBucketOptions, 61 | // }); 62 | 63 | // assert(client._context.options.tokenBucket === tokenBucketOptions); 64 | // }); 65 | 66 | // Deno.test( 67 | // "createClient should use default token bucket options if not provided", 68 | // () => { 69 | // const client = createClient({ 70 | // baseURL: "https://paper-api.alpaca.markets", 71 | // key: "EXAMPLE_KEY_ID", 72 | // secret: "EXAMPLE_KEY_SECRET", 73 | // }); 74 | 75 | // assert(client._context.options.tokenBucket === undefined); 76 | // } 77 | // ); 78 | 79 | // Deno.test( 80 | // "createClient should make a request with the correct options", 81 | // async () => { 82 | // const mockResponse = { mock: "data" }; 83 | // const originalFetch = globalThis.fetch; 84 | 85 | // // deno-lint-ignore ban-ts-comment 86 | // // @ts-expect-error 87 | // globalThis.fetch = mockFetch(mockResponse); 88 | 89 | // const client = createClient({ 90 | // baseURL: "https://paper-api.alpaca.markets", 91 | // key: "EXAMPLE_KEY_ID", 92 | // secret: "EXAMPLE_KEY_SECRET", 93 | // }); 94 | 95 | // const response = await client._context.request({ 96 | // path: "/v2/account", 97 | // }); 98 | 99 | // assertEquals(response, mockResponse); 100 | // globalThis.fetch = originalFetch; 101 | // } 102 | // ); 103 | 104 | // Deno.test( 105 | // "createClient should throttle requests based on token bucket", 106 | // async () => { 107 | // const mockResponse = { mock: "data" }; 108 | // const originalFetch = globalThis.fetch; 109 | 110 | // // deno-lint-ignore ban-ts-comment 111 | // // @ts-expect-error 112 | // globalThis.fetch = mockFetch(mockResponse); 113 | 114 | // const client = createClient({ 115 | // baseURL: "https://paper-api.alpaca.markets", 116 | // key: "EXAMPLE_KEY_ID", 117 | // secret: "EXAMPLE_KEY_SECRET", 118 | // tokenBucket: { 119 | // capacity: 2, 120 | // fillRate: 1, 121 | // }, 122 | // }); 123 | 124 | // const startTime = Date.now(); 125 | 126 | // await Promise.all([ 127 | // client._context.request({ path: "/v2/account" }), 128 | // client._context.request({ path: "/v2/account" }), 129 | // client._context.request({ path: "/v2/account" }), 130 | // ]); 131 | 132 | // const endTime = Date.now(); 133 | // const elapsedTime = endTime - startTime; 134 | 135 | // assert(elapsedTime >= 2000, "Requests should be throttled"); 136 | // globalThis.fetch = originalFetch; 137 | // } 138 | // ); 139 | -------------------------------------------------------------------------------- /factory/createClient.ts: -------------------------------------------------------------------------------- 1 | import * as marketData from "../api/marketData.ts"; 2 | import * as trade from "../api/trade.ts"; 3 | 4 | import { createTokenBucket, TokenBucketOptions } from "./createTokenBucket.ts"; 5 | 6 | export const baseURLs = { 7 | live: "https://api.alpaca.markets", 8 | paper: "https://paper-api.alpaca.markets", 9 | marketData: "https://data.alpaca.markets", 10 | } as const; 11 | 12 | export type BaseURLKey = keyof typeof baseURLs; 13 | 14 | export type RequestOptions = { 15 | path: string; 16 | method?: string; 17 | baseURL?: (typeof baseURLs)[BaseURLKey]; 18 | data?: object; 19 | params?: object; 20 | }; 21 | 22 | export type Client = 23 | & { [K in keyof typeof trade]: ReturnType<(typeof trade)[K]> } 24 | & { [K in keyof typeof marketData]: ReturnType<(typeof marketData)[K]> }; 25 | 26 | export type ClientContext = { 27 | options: CreateClientOptions; 28 | request: (options: RequestOptions) => Promise; 29 | }; 30 | 31 | export type CreateClientOptions = { 32 | key?: string; 33 | secret?: string; 34 | accessToken?: string; 35 | baseURL?: string; 36 | paper?: boolean; 37 | tokenBucket?: TokenBucketOptions; 38 | }; 39 | 40 | export const createClient = (options: CreateClientOptions) => { 41 | const { 42 | key = "", 43 | secret = "", 44 | accessToken = "", 45 | paper = true, 46 | } = { 47 | key: options.key || Deno.env.get("APCA_KEY_ID"), 48 | secret: options.secret || Deno.env.get("APCA_KEY_SECRET"), 49 | accessToken: options.accessToken || Deno.env.get("APCA_ACCESS_TOKEN"), 50 | }; 51 | 52 | // Check if credentials are provided 53 | if (!accessToken && (!key || !secret)) { 54 | throw new Error("Missing credentials (need accessToken or key/secret)"); 55 | } 56 | 57 | const baseURL = options.baseURL || (paper ? baseURLs.paper : baseURLs.live); 58 | 59 | // Create a token bucket for rate limiting 60 | const bucket = createTokenBucket(options.tokenBucket); 61 | 62 | // Throttled request function that respects the token bucket 63 | const request = async ({ 64 | method = "GET", 65 | path, 66 | params, 67 | data, 68 | }: RequestOptions): Promise => { 69 | await new Promise((resolve) => { 70 | // Poll the token bucket every second 71 | const timer = setInterval(() => { 72 | // If a token is available, resolve the promise 73 | if (bucket.take(1)) { 74 | clearInterval(timer); 75 | resolve(true); 76 | } 77 | }, 1000); 78 | }); 79 | 80 | // Construct the URL 81 | const url = new URL(path, baseURL); 82 | 83 | if (params) { 84 | // Append query parameters to the URL 85 | url.search = new URLSearchParams( 86 | Object.entries(params) as [string, string][], 87 | ).toString(); 88 | } 89 | 90 | // Construct the headers 91 | const headers = new Headers({ 92 | "Content-Type": "application/json", 93 | }); 94 | 95 | if (accessToken) { 96 | // Use the access token for authentication 97 | headers.set("Authorization", `Bearer ${accessToken}`); 98 | } else { 99 | // Use the API key and secret for authentication 100 | headers.set("APCA-API-KEY-ID", key); 101 | headers.set("APCA-API-SECRET-KEY", secret); 102 | } 103 | 104 | // Make the request 105 | return fetch(url, { 106 | method, 107 | headers, 108 | body: data ? JSON.stringify(data) : null, 109 | }).then(async (response) => { 110 | if (!response.ok) { 111 | // The response will contain an error message (usually) 112 | throw new Error(await response.text()); 113 | } 114 | 115 | // Parse the response and cast it to the expected type 116 | return response.json().catch(() => ({})) as Promise; 117 | }); 118 | }; 119 | 120 | // Create a context object to pass to the client factory 121 | const context: ClientContext = { options, request }; 122 | 123 | // Return an object with all methods 124 | return [...Object.values(trade), ...Object.values(marketData)].reduce( 125 | (prev, fn) => ({ ...prev, [fn.name]: fn(context) }), 126 | {} as Client, 127 | ); 128 | }; 129 | -------------------------------------------------------------------------------- /factory/createStream.ts: -------------------------------------------------------------------------------- 1 | // NOT CLEANED UP; TYPES MISSING FOR CALLBACKS; BARELY FUNCTIONAL 2 | 3 | import { Nullable } from "../api/trade.ts"; 4 | 5 | const baseURLs = { 6 | data: "wss://stream.data.alpaca.markets", 7 | data_sandbox: "wss://stream.data.sandbox.alpaca.markets", 8 | data_test: "wss://stream.data.alpaca.markets/v2/test", 9 | account: "wss://api.alpaca.markets", 10 | account_paper: "wss://paper-api.alpaca.markets", 11 | } as const; 12 | 13 | type BaseURLKey = keyof typeof baseURLs; 14 | 15 | type CreateStreamOptions = { 16 | type: BaseURLKey; 17 | key?: string; 18 | secret?: string; 19 | version?: "v2"; 20 | feed?: "iex" | "sip"; 21 | autoReconnect?: boolean; 22 | maxRetries?: number; 23 | retryDelay?: number; 24 | }; 25 | 26 | type EventCallback = (data: any) => void; 27 | 28 | type TradeUpdate = { 29 | event: string; 30 | price: string; 31 | qty: string; 32 | timestamp: string; 33 | }; 34 | 35 | export const createStream = (options: CreateStreamOptions) => { 36 | const { 37 | type, 38 | version = "v2", 39 | feed = "iex", 40 | autoReconnect = true, 41 | maxRetries = 5, 42 | retryDelay = 3000, 43 | } = options; 44 | 45 | const key = options.key || Deno.env.get("APCA_KEY_ID"); 46 | const secret = options.secret || Deno.env.get("APCA_KEY_SECRET"); 47 | 48 | if (!key || !secret) { 49 | console.error("API key and secret are required."); 50 | return; 51 | } 52 | 53 | let url: string; 54 | 55 | if (type === "data" || type === "data_sandbox") { 56 | url = `${baseURLs[type]}/${version}/${feed}`; 57 | } else if (type === "data_test") { 58 | url = baseURLs[type]; 59 | } else { 60 | url = `${baseURLs[type]}/stream`; 61 | } 62 | 63 | console.log(url); 64 | let socket: Nullable = null; 65 | let retries = 0; 66 | const eventCallbacks: { [event: string]: EventCallback[] } = {}; 67 | const activeStreams: Set = new Set(); 68 | let isAuthenticated = false; 69 | 70 | const handle = (message: any) => { 71 | const event = message.stream; 72 | if (event && eventCallbacks[event]) { 73 | eventCallbacks[event].forEach((callback) => callback(message)); 74 | } else { 75 | console.debug("Unhandled message:", message); 76 | } 77 | }; 78 | 79 | const connect = () => { 80 | if (!autoReconnect || (maxRetries !== undefined && retries >= maxRetries)) { 81 | console.debug("Auto-reconnect is disabled or max retries reached."); 82 | socket?.close(); 83 | return; 84 | } 85 | 86 | socket = new WebSocket(url); 87 | 88 | socket.onopen = () => { 89 | console.debug( 90 | "WebSocket connection established. Sending authentication message.", 91 | ); 92 | 93 | socket?.send( 94 | JSON.stringify({ 95 | action: "auth", 96 | key: key, 97 | secret: secret, 98 | }), 99 | ); 100 | }; 101 | 102 | socket.onclose = () => { 103 | console.debug("WebSocket connection closed. Attempting to reconnect..."); 104 | retries++; 105 | setTimeout(connect, retryDelay); 106 | }; 107 | 108 | socket.onerror = (error) => { 109 | console.debug("WebSocket encountered an error:", error); 110 | socket?.close(); 111 | }; 112 | 113 | socket.onmessage = ({ data }) => { 114 | if (typeof data === "string") { 115 | console.log("Received text message:", data); 116 | try { 117 | const result = JSON.parse(data); 118 | if ( 119 | result.stream === "authorization" && 120 | result.data.status === "authorized" 121 | ) { 122 | isAuthenticated = true; 123 | sendListenMessage(); 124 | } 125 | handle(result); 126 | } catch (error) { 127 | console.debug("Error parsing text message:", error); 128 | } 129 | } else if (data instanceof Blob) { 130 | console.log("Received binary message:", data); 131 | const reader = new FileReader(); 132 | reader.onload = function () { 133 | if (typeof reader.result === "string") { 134 | try { 135 | const result = JSON.parse(reader.result); 136 | if ( 137 | result.stream === "authorization" && 138 | result.data.status === "authorized" 139 | ) { 140 | isAuthenticated = true; 141 | sendListenMessage(); 142 | } 143 | handle(result); 144 | } catch (error) { 145 | console.debug("Error parsing binary message:", error); 146 | } 147 | } 148 | }; 149 | reader.readAsText(data); 150 | } else { 151 | console.debug("Unknown message type:", data); 152 | } 153 | }; 154 | }; 155 | 156 | connect(); 157 | 158 | const sendListenMessage = () => { 159 | if (socket && socket.readyState === WebSocket.OPEN && isAuthenticated) { 160 | console.log("Sending listen message:", Array.from(activeStreams)); 161 | socket.send( 162 | JSON.stringify({ 163 | action: "listen", 164 | data: { 165 | streams: Array.from(activeStreams), 166 | }, 167 | }), 168 | ); 169 | } else { 170 | console.debug( 171 | "Socket is not open or not authenticated. Cannot send listen message.", 172 | ); 173 | } 174 | }; 175 | 176 | const subscribe = (event: string, callback: EventCallback) => { 177 | console.log("Subscribing to event:", event); 178 | if (!eventCallbacks[event]) { 179 | eventCallbacks[event] = []; 180 | } 181 | eventCallbacks[event].push(callback); 182 | activeStreams.add(event); 183 | sendListenMessage(); 184 | }; 185 | 186 | const unsubscribe = (event: string) => { 187 | if (eventCallbacks[event]) { 188 | delete eventCallbacks[event]; 189 | activeStreams.delete(event); 190 | sendListenMessage(); 191 | } 192 | }; 193 | 194 | return { 195 | socket, 196 | close: () => socket?.close(), 197 | subscribe, 198 | unsubscribe, 199 | }; 200 | }; 201 | 202 | // const stream = createStream({ 203 | // type: "account_paper", 204 | // key: "key", 205 | // secret: "secret", 206 | // autoReconnect: true, 207 | // maxRetries: 5, 208 | // retryDelay: 3000, 209 | // }); 210 | 211 | // stream.subscribe("trade_updates", (data) => { 212 | // // trade update received 213 | // }); 214 | -------------------------------------------------------------------------------- /factory/createTokenBucket.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "https://deno.land/std@0.217.0/assert/assert.ts"; 2 | import { createTokenBucket } from "./createTokenBucket.ts"; 3 | 4 | Deno.test( 5 | "createTokenBucket should allow taking tokens within capacity", 6 | () => { 7 | const tokenBucket = createTokenBucket({ 8 | capacity: 200, 9 | fillRate: 3, 10 | }); 11 | 12 | assert(tokenBucket.take(50) === true); 13 | }, 14 | ); 15 | 16 | Deno.test( 17 | "createTokenBucket should reject taking tokens beyond capacity", 18 | () => { 19 | const tokenBucket = createTokenBucket({ 20 | capacity: 200, 21 | fillRate: 3, 22 | }); 23 | 24 | assert(tokenBucket.take(300) === false); 25 | }, 26 | ); 27 | 28 | Deno.test( 29 | "createTokenBucket should refill tokens based on fill rate", 30 | async () => { 31 | const tokenBucket = createTokenBucket({ 32 | capacity: 200, 33 | fillRate: 3, 34 | }); 35 | 36 | tokenBucket.take(50); 37 | 38 | await new Promise((resolve) => setTimeout(resolve, 3000)); 39 | 40 | assert(tokenBucket.take(50) === true); 41 | }, 42 | ); 43 | 44 | Deno.test( 45 | "createTokenBucket should support 200 requests per 60 seconds", 46 | () => { 47 | const tokenBucket = createTokenBucket({ capacity: 200, fillRate: 3 }); 48 | 49 | let successfulRequests = 0; 50 | 51 | for (let i = 0; i < 200; i++) { 52 | if (tokenBucket.take(1)) { 53 | successfulRequests++; 54 | } 55 | } 56 | 57 | assert(successfulRequests === 200); 58 | }, 59 | ); 60 | -------------------------------------------------------------------------------- /factory/createTokenBucket.ts: -------------------------------------------------------------------------------- 1 | export type TokenBucketOptions = { 2 | capacity: number; 3 | fillRate: number; 4 | }; 5 | 6 | export const createTokenBucket = ( 7 | { capacity, fillRate }: TokenBucketOptions = { 8 | capacity: 200, 9 | fillRate: 3, 10 | } 11 | ) => { 12 | // Initialize tokens and last fill time 13 | let tokens = capacity; 14 | 15 | // Initialize last fill time 16 | let lastFillTime = Date.now(); 17 | 18 | return { 19 | tokens, 20 | take: (count: number) => { 21 | // Calculate time delta 22 | const now = Date.now(); 23 | const delta = (now - lastFillTime) / 1000; 24 | 25 | // Update last fill time and tokens 26 | lastFillTime = now; 27 | tokens = Math.min(capacity, tokens + delta * fillRate); 28 | 29 | // Check if tokens are available and take them if so 30 | // Otherwise, return false 31 | return tokens >= count ? ((tokens -= count), true) : false; 32 | }, 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | // If `APCA_DEBUG` false, console.debug() should not log anything. 2 | if (JSON.parse(Deno.env.get("APCA_DEBUG") || "false")) { 3 | console.debug = () => {}; 4 | } else { 5 | // Prefix all debug logs with "@alpacahq/typescript-sdk" to make it easier to 6 | // for users to filter out debug logs from this SDK. 7 | console.debug = (...args) => 8 | console.log("@alpacahq/typescript-sdk:debug", ...args); 9 | } 10 | export { 11 | type CashDividend, 12 | type CashMerger, 13 | type CorporateActions, 14 | type CryptoBar, 15 | type CryptoBars, 16 | type CryptoBarsLatest, 17 | type CryptoOrderbook, 18 | type CryptoOrderbookEntry, 19 | type CryptoOrderbooksLatest, 20 | type CryptoQuote, 21 | type CryptoQuotes, 22 | type CryptoQuotesLatest, 23 | type CryptoSnapshots, 24 | type CryptoTrade, 25 | type CryptoTrades, 26 | type CryptoTradesLatest, 27 | type ForexRate, 28 | type ForexRates, 29 | type ForexRatesLatest, 30 | type ForwardSplit, 31 | getCryptoBars, 32 | type GetCryptoBarsLatestOptions, 33 | type GetCryptoBarsOptions, 34 | type GetCryptoOrderbooksLatestOptions, 35 | getCryptoQuotes, 36 | getCryptoQuotesLatest, 37 | type GetCryptoQuotesLatestOptions, 38 | type GetCryptoQuotesOptions, 39 | getCryptoSnapshots, 40 | type GetCryptoSnapshotsOptions, 41 | getCryptoTrades, 42 | getCryptoTradesLatest, 43 | type GetCryptoTradesLatestOptions, 44 | type GetCryptoTradesOptions, 45 | getForexRates, 46 | type GetForexRatesLatestOptions, 47 | type GetForexRatesOptions, 48 | getLatestCryptoBars, 49 | getLatestCryptoOrderbooks, 50 | getLatestForexRates, 51 | getLogo, 52 | type GetLogoOptions, 53 | getNews, 54 | type GetNewsOptions, 55 | getOptionsBars, 56 | type GetOptionsBarsOptions, 57 | getOptionsExchanges, 58 | getOptionsSnapshots, 59 | type GetOptionsSnapshotsOptions, 60 | getOptionsTrades, 61 | getOptionsTradesLatest, 62 | type GetOptionsTradesLatestOptions, 63 | type GetOptionsTradesOptions, 64 | getStocksAuctions, 65 | type GetStocksAuctionsOptions, 66 | getStocksBars, 67 | getStocksBarsLatest, 68 | type GetStocksBarsLatestOptions, 69 | type GetStocksBarsOptions, 70 | getStocksConditions, 71 | type GetStocksConditionsOptions, 72 | getStocksCorporateActions, 73 | type GetStocksCorporateActionsOptions, 74 | getStocksExchangeCodes, 75 | getStocksMarketMovers, 76 | type GetStocksMarketMoversOptions, 77 | getStocksMostActives, 78 | type GetStocksMostActivesOptions, 79 | getStocksQuotes, 80 | getStocksQuotesLatest, 81 | type GetStocksQuotesLatestOptions, 82 | type GetStocksQuotesOptions, 83 | getStocksSnapshots, 84 | type GetStocksSnapshotsOptions, 85 | getStocksTrades, 86 | getStocksTradesLatest, 87 | type GetStocksTradesLatestOptions, 88 | type GetStocksTradesOptions, 89 | type Logo, 90 | type MarketMover, 91 | type MarketMovers, 92 | type MostActive, 93 | type MostActives, 94 | type NameChange, 95 | type OptionBar, 96 | type OptionsBars, 97 | type OptionsExchanges, 98 | type OptionsSnapshots, 99 | type OptionsSnapshotsQuote, 100 | type OptionsSnapshotsTrade, 101 | type OptionsTrades, 102 | type OptionsTradesLatest, 103 | type Redemption, 104 | type ReverseSplit, 105 | type Sort, 106 | type SpinOff, 107 | type StockAndCashMerger, 108 | type StockBar, 109 | type StockDividend, 110 | type StockMerger, 111 | type StockQuote, 112 | type StocksAuction, 113 | type StocksAuctionPrice, 114 | type StocksAuctions, 115 | type StocksBar, 116 | type StocksBars, 117 | type StocksBarsLatest, 118 | type StocksConditions, 119 | type StocksExchangeCodes, 120 | type StockSnapshots, 121 | type StocksQuotes, 122 | type StocksQuotesLatest, 123 | type StocksTrades, 124 | type StocksTradesLatest, 125 | type StockTrade, 126 | type UnitSplit, 127 | type WorthlessRemoval, 128 | } from "./api/marketData.ts"; 129 | 130 | export { 131 | type Account, 132 | type AccountStatus, 133 | type Activity, 134 | type Asset, 135 | type BaseOrder, 136 | type Calendar, 137 | cancelOrder, 138 | type CancelOrderOptions, 139 | cancelOrders, 140 | type Clock, 141 | closePosition, 142 | type ClosePositionOptions, 143 | closePositions, 144 | type Configurations, 145 | type CorporateAction, 146 | createCryptoTransfer, 147 | type CreateCryptoTransferOptions, 148 | createOrder, 149 | type CreateOrderOptions, 150 | createWatchlist, 151 | type CreateWatchlistOptions, 152 | type CryptoFee, 153 | type CryptoOrder, 154 | type CryptoTransfer, 155 | type CryptoTransferResponse, 156 | type CryptoWallet, 157 | deleteWatchlist, 158 | type DeleteWatchlistOptions, 159 | type Direction, 160 | type EquityOrder, 161 | type ExerciseOption, 162 | exerciseOption, 163 | getAccount, 164 | getActivities, 165 | getActivity, 166 | type GetActivityOptions, 167 | getAsset, 168 | type GetAssetOptions, 169 | getAssets, 170 | type GetAssetsOptions, 171 | getCalendar, 172 | type GetCalendarOptions, 173 | getClock, 174 | getConfigurations, 175 | getCorporateAction, 176 | type GetCorporateActionOptions, 177 | getCorporateActions, 178 | type GetCorporateActionsOptions, 179 | type GetCryptoFeeEstimateOptions, 180 | getCryptoTransfer, 181 | type GetCryptoTransferOptions, 182 | getCryptoTransfers, 183 | type GetCryptoTransfersOptions, 184 | getCryptoWallet, 185 | getCryptoWallets, 186 | getCryptoWhitelistedAddress, 187 | getCryptoWhitelistedAddresses, 188 | type GetCryptoWhitelistedAddressOptions, 189 | getFeeEstimate, 190 | getOptionsContract, 191 | type GetOptionsContractOptions, 192 | getOptionsContracts, 193 | type GetOptionsContractsOptions, 194 | getOrder, 195 | type GetOrderOptions, 196 | getOrders, 197 | type GetOrdersOptions, 198 | getPortfolioHistory, 199 | type GetPortfolioHistoryOptions, 200 | getPosition, 201 | type GetPositionOptions, 202 | getPositions, 203 | type GetWalletOptions, 204 | getWatchlist, 205 | type GetWatchlistOptions, 206 | getWatchlists, 207 | type NonTradeActivity, 208 | type OptionsApprovedLevel, 209 | type OptionsContract, 210 | type OptionsOrder, 211 | type OptionsTradingLevel, 212 | type Order, 213 | type OrderClass, 214 | type PortfolioHistory, 215 | type PositionIntent, 216 | removeCryptoWhitelistedAddress, 217 | type RemoveCryptoWhitelistedAddressOptions, 218 | replaceOrder, 219 | type ReplaceOrderOptions, 220 | requestCryptoWhitelistedAddress, 221 | type RequestCryptoWhitelistedAddressOptions, 222 | type Side, 223 | type StopLoss, 224 | type TakeProfit, 225 | type TimeInForce, 226 | type TradingActivity, 227 | type Type, 228 | type UnstableNumber, 229 | updateConfigurations, 230 | type UpdateConfigurationsOptions, 231 | updateWatchlist, 232 | type UpdateWatchlistOptions, 233 | type Watchlist, 234 | type WhitelistedAddress, 235 | } from "./api/trade.ts"; 236 | 237 | export { 238 | baseURLs, 239 | type Client, 240 | type ClientContext, 241 | createClient, 242 | type CreateClientOptions, 243 | type RequestOptions, 244 | } from "./factory/createClient.ts"; 245 | 246 | export { 247 | createTokenBucket, 248 | type TokenBucketOptions, 249 | } from "./factory/createTokenBucket.ts"; 250 | 251 | export { 252 | type MockFetch, 253 | mockFetch, 254 | type MockResponse, 255 | } from "./util/mockFetch.ts"; 256 | -------------------------------------------------------------------------------- /util/mockFetch.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "https://deno.land/std@0.217.0/assert/assert.ts"; 2 | import { mockFetch } from "./mockFetch.ts"; 3 | 4 | Deno.test("mockFetch should return a function", () => { 5 | const response = { data: "mocked response" }; 6 | const result = mockFetch(response); 7 | 8 | assert(typeof result === "function"); 9 | }); 10 | 11 | Deno.test( 12 | "mockFetch should return a promise that resolves to a response object", 13 | async () => { 14 | const response = { data: "mocked response" }; 15 | const fetch = mockFetch(response); 16 | const result = await fetch("https://example.com"); 17 | 18 | assert(result instanceof Response); 19 | assert(result.ok === true); 20 | assert(result.status === 200); 21 | assert(result.headers.get("Content-Type") === "application/json"); 22 | } 23 | ); 24 | 25 | Deno.test("mockFetch should return the mocked response data", async () => { 26 | const response = { data: "mocked response" }; 27 | const fetch = mockFetch(response); 28 | const result = await fetch("https://example.com"); 29 | const data = await result.json(); 30 | 31 | assert(data.data === response.data); 32 | }); 33 | 34 | Deno.test("mockFetch should ignore the url and init parameters", async () => { 35 | const response = { data: "mocked response" }; 36 | const fetch = mockFetch(response); 37 | 38 | const a = await fetch("https://example.com"); 39 | const b = await fetch("https://example.com/other", { 40 | method: "POST", 41 | body: JSON.stringify({ key: "value" }), 42 | }); 43 | 44 | const c = await a.json(); 45 | const d = await b.json(); 46 | 47 | assert(c.data === response.data); 48 | assert(d.data === response.data); 49 | }); 50 | -------------------------------------------------------------------------------- /util/mockFetch.ts: -------------------------------------------------------------------------------- 1 | export type MockResponse = Response | object | string; 2 | 3 | export type MockFetch = (url: string, init?: RequestInit) => Promise; 4 | 5 | // Used to mock the fetch function in tests 6 | export const mockFetch: (response: MockResponse) => MockFetch = 7 | (response) => (_url, _init?) => 8 | // Return a promise that resolves with a response 9 | Promise.resolve( 10 | new Response( 11 | typeof response === "object" 12 | ? JSON.stringify(response) 13 | : (response as string), 14 | { 15 | status: 200, 16 | headers: { 17 | "Content-Type": "application/json", 18 | }, 19 | } 20 | ) 21 | ); 22 | --------------------------------------------------------------------------------