E5C2 GitHub - phiat/alpa_ex: Elixir SDK for the Alpaca Trading API · GitHub
[go: up one dir, main page]

Skip to content

phiat/alpa_ex

Repository files navigation

alpa_ex

alpa_ex

Hex.pm Hex Docs License: MIT

Elixir client library for the Alpaca Trading API.

Commission-free stock, options, and crypto trading with real-time market data.

Features

  • Trading API - Account management, orders, positions, watchlists
  • Market Data API - Historical and real-time bars, quotes, trades, snapshots
  • WebSocket Streaming - Real-time trade updates and market data
  • Options Trading - Option contract search and trading
  • Crypto Trading - 24/7 cryptocurrency trading
  • TypedStruct Models - Fully typed responses with Decimal precision
  • Crypto Market Data - Bars, quotes, trades, snapshots, and order books
  • Corporate Actions - Announcements and corporate action tracking
  • Pagination Helpers - Eager all/2 and lazy stream/2 for paginated endpoints
  • Telemetry - Built-in :telemetry events for observability
  • Modern Stack - Elixir 1.14+, Req HTTP client, WebSockex

Installation

Add alpa_ex to your list of dependencies in mix.exs:

def deps do
  [
    {:alpa_ex, "~> 1.0"}
  ]
end

Configuration

Set your API credentials as environment variables:

export APCA_API_KEY_ID="your-api-key"
export APCA_API_SECRET_KEY="your-api-secret"
export APCA_USE_PAPER="true"  # Use paper trading (default: true)

Or configure in your application:

# config/runtime.exs
config :alpa_ex,
  api_key: System.get_env("APCA_API_KEY_ID"),
  api_secret: System.get_env("APCA_API_SECRET_KEY"),
  use_paper: true

Quick Start

# Get account info
{:ok, account} = Alpa.account()
IO.puts("Buying power: $#{account.buying_power}")

# Check if market is open
{:ok, open?} = Alpa.market_open?()

# Place a market order
{:ok, order} = Alpa.buy("AAPL", 10)

# Get positions
{:ok, positions} = Alpa.positions()

# Get market data
{:ok, bars} = Alpa.bars("AAPL", timeframe: "1Day", limit: 30)

# Get a market snapshot
{:ok, snapshot} = Alpa.snapshot("AAPL")
IO.puts("AAPL last trade: $#{snapshot.latest_trade.price}")

Trading

Orders

# Market orders
{:ok, order} = Alpa.buy("AAPL", 10)
{:ok, order} = Alpa.sell("AAPL", 10)

# Limit order
{:ok, order} = Alpa.place_order(
  symbol: "AAPL",
  qty: 10,
  side: "buy",
  type: "limit",
  limit_price: "150.00",
  time_in_force: "gtc"
)

# Bracket order (entry with take-profit and stop-loss)
{:ok, order} = Alpa.place_order(
  symbol: "AAPL",
  qty: 10,
  side: "buy",
  type: "market",
  time_in_force: "day",
  order_class: "bracket",
  take_profit: %{limit_price: "160.00"},
  stop_loss: %{stop_price: "140.00", limit_price: "139.00"}
)

# List orders
{:ok, orders} = Alpa.orders(status: "open")

# Cancel orders
{:ok, _} = Alpa.cancel_order(order_id)
{:ok, _} = Alpa.cancel_all_orders()

Positions

# List all positions
{:ok, positions} = Alpa.positions()

# Get specific position
{:ok, position} = Alpa.position("AAPL")

# Close positions
{:ok, order} = Alpa.close_position("AAPL")
{:ok, order} = Alpa.close_position("AAPL", percentage: 50)  # Close 50%
{:ok, _} = Alpa.close_all_positions()

Account

# Account info
{:ok, account} = Alpa.account()

# Portfolio history
{:ok, history} = Alpa.history(period: "1M", timeframe: "1D")

# Account configuration
{:ok, config} = Alpa.account_config()

Market Data

Historical Data

# Bars (OHLCV)
{:ok, bars} = Alpa.bars("AAPL",
  timeframe: "1Day",
  start: ~U[2024-01-01 00:00:00Z],
  limit: 100
)

# Quotes
{:ok, quotes} = Alpa.quotes("AAPL",
  start: ~U[2024-01-15 14:30:00Z],
  limit: 100
)

# Trades
{:ok, trades} = Alpa.trades("AAPL",
  start: ~U[2024-01-15 14:30:00Z],
  limit: 100
)

# Multi-symbol
{:ok, bars} = Alpa.MarketData.Bars.get_multi(
  ["AAPL", "MSFT", "GOOGL"],
  timeframe: "1Day"
)

Latest Data

# Latest bar, quote, trade
{:ok, bar} = Alpa.latest_bar("AAPL")
{:ok, quote} = Alpa.latest_quote("AAPL")
{:ok, trade} = Alpa.latest_trade("AAPL")

# Snapshot (all latest data combined)
{:ok, snapshot} = Alpa.snapshot("AAPL")
# => %Alpa.Models.Snapshot{
#      latest_trade: %Alpa.Models.Trade{...},
#      latest_quote: %Alpa.Models.Quote{...},
#      minute_bar: %Alpa.Models.Bar{...},
#      daily_bar: %Alpa.Models.Bar{...},
#      prev_daily_bar: %Alpa.Models.Bar{...}
#    }

# Multiple snapshots
{:ok, snapshots} = Alpa.snapshots(["AAPL", "MSFT", "GOOGL"])

Real-Time Streaming

Trade Updates (Order Status)

# Stream order fills, cancellations, etc.
{:ok, pid} = Alpa.Stream.TradeUpdates.start_link(
  callback: fn event ->
    IO.puts("#{event.event}: #{event.order.symbol} @ #{event.price}")
  end
)

# Events: new, fill, partial_fill, canceled, expired, replaced, rejected

Market Data Streaming

# Start the stream
{:ok, pid} = Alpa.Stream.MarketData.start_link(
  callback: fn event ->
    case event.type do
      :trade -> IO.puts("Trade: #{event.data.symbol} @ #{event.data.price}")
      :quote -> IO.puts("Quote: #{event.data.symbol} bid=#{event.data.bid_price}")
      :bar -> IO.puts("Bar: #{event.data.symbol} close=#{event.data.close}")
    end
  end,
  feed: "iex"  # or "sip" for all exchanges
)

# Subscribe to symbols
Alpa.Stream.MarketData.subscribe(pid,
  trades: ["AAPL", "MSFT"],
  quotes: ["AAPL"],
  bars: ["SPY"]
)

# Unsubscribe
Alpa.Stream.MarketData.unsubscribe(pid, trades: ["MSFT"])

# Stop
Alpa.Stream.MarketData.stop(pid)

Options Trading

# Search for option contracts
{:ok, result} = Alpa.Options.Contracts.search("AAPL",
  type: :call,
  expiration_date_gte: ~D[2024-03-01],
  strike_price_gte: "150",
  strike_price_lte: "200"
)

# Get specific contract
{:ok, contract} = Alpa.Options.Contracts.get("AAPL240315C00175000")

# Trade options using regular order functions
{:ok, order} = Alpa.place_order(
  symbol: "AAPL240315C00175000",
  qty: 1,
  side: "buy",
  type: "limit",
  limit_price: "5.00",
  time_in_force: "day"
)

Crypto Trading

# List crypto assets
{:ok, assets} = Alpa.Crypto.Trading.assets()

# Buy/sell crypto
{:ok, order} = Alpa.Crypto.Trading.buy("BTC/USD", "0.001")
{:ok, order} = Alpa.Crypto.Trading.sell("ETH/USD", "0.1")

# Buy by dollar amount
{:ok, order} = Alpa.Crypto.Trading.buy_notional("BTC/USD", "100")  # $100 of BTC

# Crypto positions
{:ok, positions} = Alpa.Crypto.Trading.positions()

Modules

Module Description
Alpa Main facade with delegated functions
Alpa.Trading.Account Account info, config, activities, portfolio history
Alpa.Trading.Orders Order placement and management
Alpa.Trading.Positions Position management
Alpa.Trading.Assets Asset information
Alpa.Trading.Watchlists Watchlist CRUD
Alpa.Trading.Market Market clock and calendar
Alpa.Trading.CorporateActions Corporate action announcements
Alpa.MarketData.Bars Historical bar data
Alpa.MarketData.Quotes Historical quote data
Alpa.MarketData.Trades Historical trade data
Alpa.MarketData.Snapshots Market snapshots
Alpa.Stream.TradeUpdates Real-time trade updates
Alpa.Stream.MarketData Real-time market data
Alpa.Options.Contracts Options contract search
Alpa.Crypto.Trading Crypto trading operations
Alpa.Crypto.MarketData Crypto bars, quotes, trades, snapshots, order books
Alpa.Crypto.Funding Crypto transfers and wallets
Alpa.Helpers Shared parse helpers (decimal, datetime, date)

Error Handling

All API functions return {:ok, result} or {:error, %Alpa.Error{}}:

case Alpa.account() do
  {:ok, account} ->
    IO.puts("Balance: $#{account.cash}")

  {:error, %Alpa.Error{type: :unauthorized}} ->
    IO.puts("Check your API credentials")

  {:error, %Alpa.Error{type: :rate_limited}} ->
    IO.puts("Rate limited, try again later")

  {:error, error} ->
    IO.puts("Error: #{error}")
end

Error types: :unauthorized, :forbidden, :not_found, :unprocessable_entity, :rate_limited, :server_error, :network_error, :timeout

Configuration Options

Option Environment Variable Fallback Default
api_key APCA_API_KEY_ID ALPACA_API_KEY -
api_secret APCA_API_SECRET_KEY ALPACA_API_SECRET -
use_paper APCA_USE_PAPER - true
timeout - - 30_000
receive_timeout - - 30_000

Options can be passed to any function to override config:

Alpa.account(api_key: "other-key", api_secret: "other-secret")

Development

# Run tests
mix test

# Run tests with coverage
mix coveralls

# Generate docs
mix docs

# Run static analysis
mix credo
mix dialyzer

License

MIT License - see LICENSE for details.

Links

About

Elixir SDK for the Alpaca Trading API

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

0