Current section
Files
Jump to
Current section
Files
lib/alpa/market_data/bars.ex
defmodule Alpa.MarketData.Bars do
@moduledoc """
Historical bar (OHLCV) data from the Alpaca Market Data API v2.
"""
alias Alpa.Client
alias Alpa.Models.Bar
@doc """
Get historical bars for a single symbol.
## Required Parameters
* `symbol` - Stock symbol (e.g., "AAPL")
## Options
* `:timeframe` - Bar timeframe: "1Min", "5Min", "15Min", "30Min", "1Hour", "4Hour", "1Day", "1Week", "1Month"
* `:start` - Start time (DateTime or ISO 8601 string)
* `:end` - End time (DateTime or ISO 8601 string)
* `:limit` - Max bars to return (default 1000, max 10000)
* `:adjustment` - Price adjustment: "raw", "split", "dividend", "all" (default: "raw")
* `:feed` - Data feed: "iex", "sip" (default depends on subscription)
* `:page_token` - Pagination token
## Examples
iex> Alpa.MarketData.Bars.get("AAPL", timeframe: "1Day", start: ~U[2024-01-01 00:00:00Z])
{:ok, [%Alpa.Models.Bar{...}]}
"""
@spec get(String.t(), keyword()) :: {:ok, [Bar.t()]} | {:error, Alpa.Error.t()}
def get(symbol, opts \\ []) do
params = build_params(opts)
encoded_symbol = URI.encode_www_form(symbol)
case Client.get_data("/v2/stocks/#{encoded_symbol}/bars", Keyword.put(opts, :params, params)) do
{:ok, data} -> {:ok, parse_bars(data, symbol)}
{:error, _} = error -> error
end
end
@doc """
Get historical bars for multiple symbols.
## Required Parameters
* `symbols` - List of stock symbols (e.g., ["AAPL", "MSFT", "GOOGL"])
## Options
Same as `get/2`.
## Examples
iex> Alpa.MarketData.Bars.get_multi(["AAPL", "MSFT"], timeframe: "1Day", start: ~U[2024-01-01 00:00:00Z])
{:ok, %{"AAPL" => [%Alpa.Models.Bar{...}], "MSFT" => [%Alpa.Models.Bar{...}]}}
"""
@spec get_multi([String.t()], keyword()) :: {:ok, %{String.t() => [Bar.t()]}} | {:error, Alpa.Error.t()}
def get_multi(symbols, opts \\ []) when is_list(symbols) do
params =
opts
|> build_params()
|> Map.put(:symbols, Enum.join(symbols, ","))
case Client.get_data("/v2/stocks/bars", Keyword.put(opts, :params, params)) do
{:ok, data} -> {:ok, parse_multi_bars(data)}
{:error, _} = error -> error
end
end
@doc """
Get the latest bar for a symbol.
## Options
* `:feed` - Data feed: "iex", "sip"
## Examples
iex> Alpa.MarketData.Bars.latest("AAPL")
{:ok, %Alpa.Models.Bar{...}}
"""
@spec latest(String.t(), keyword()) :: {:ok, Bar.t()} | {:error, Alpa.Error.t()}
def latest(symbol, opts \\ []) do
params =
opts
|> Keyword.take([:feed])
|> Enum.reject(fn {_, v} -> is_nil(v) end)
|> Map.new()
encoded_symbol = URI.encode_www_form(symbol)
case Client.get_data("/v2/stocks/#{encoded_symbol}/bars/latest", Keyword.put(opts, :params, params)) do
{:ok, %{"bar" => bar}} -> {:ok, Bar.from_map(bar, symbol)}
{:error, _} = error -> error
end
end
@doc """
Get latest bars for multiple symbols.
## Examples
iex> Alpa.MarketData.Bars.latest_multi(["AAPL", "MSFT"])
{:ok, %{"AAPL" => %Alpa.Models.Bar{...}, "MSFT" => %Alpa.Models.Bar{...}}}
"""
@spec latest_multi([String.t()], keyword()) :: {:ok, %{String.t() => Bar.t()}} | {:error, Alpa.Error.t()}
def latest_multi(symbols, opts \\ []) when is_list(symbols) do
params =
opts
|> Keyword.take([:feed])
|> Enum.reject(fn {_, v} -> is_nil(v) end)
|> Map.new()
|> Map.put(:symbols, Enum.join(symbols, ","))
case Client.get_data("/v2/stocks/bars/latest", Keyword.put(opts, :params, params)) do
{:ok, %{"bars" => bars}} ->
result = Map.new(bars, fn {symbol, bar} -> {symbol, Bar.from_map(bar, symbol)} end)
{:ok, result}
{:error, _} = error ->
error
end
end
# Private helpers
defp build_params(opts) do
opts
|> Keyword.take([:timeframe, :start, :end, :limit, :adjustment, :feed, :page_token])
|> Enum.map(fn
{:start, %DateTime{} = dt} -> {:start, DateTime.to_iso8601(dt)}
{:end, %DateTime{} = dt} -> {:end, DateTime.to_iso8601(dt)}
other -> other
end)
|> Enum.reject(fn {_, v} -> is_nil(v) end)
|> Map.new()
end
defp parse_bars(%{"bars" => bars}, symbol) when is_list(bars) do
Enum.map(bars, &Bar.from_map(&1, symbol))
end
defp parse_bars(%{"bars" => nil}, _symbol), do: []
defp parse_bars(_, _symbol), do: []
defp parse_multi_bars(%{"bars" => bars}) when is_map(bars) do
Map.new(bars, fn {symbol, bar_list} ->
{symbol, Enum.map(bar_list, &Bar.from_map(&1, symbol))}
end)
end
defp parse_multi_bars(%{"bars" => nil}), do: %{}
defp parse_multi_bars(_), do: %{}
end