Current section

Files

Jump to
snowhite lib modules stock_market server.ex
Raw

lib/modules/stock_market/server.ex

defmodule Snowhite.Modules.StockMarket.Server do
use GenServer
alias Snowhite.Modules.StockMarket
@sync_timer :timer.minutes(5)
def start_link(opts) do
name =
case Keyword.get(opts, :name, __MODULE__) do
nil -> __MODULE__
name -> name
end
GenServer.start_link(__MODULE__, opts, name: name)
end
def prices(name \\ __MODULE__) do
GenServer.call(name, :prices)
end
@impl GenServer
def init(options) do
send(self(), {:sync, true})
{:ok, init_state(options)}
end
@impl GenServer
def handle_info({:sync, force?}, state) do
state =
if force? or market_open?(state) do
state = update_prices(state)
Phoenix.PubSub.broadcast!(Snowhite.PubSub, "snowhite:modules:stockmarket", :updated)
state
else
state
end
Process.send_after(self(), {:sync, false}, @sync_timer)
{:noreply, state}
end
@impl GenServer
def handle_call(:prices, _, %{prices: prices} = state) do
{:reply, prices, state}
end
defp update_prices(
%{adapter: adapter, symbols: symbols, adapter_options: adapter_options, prices: prices} =
state
) do
prices =
Enum.reduce(symbols, prices, fn symbol, acc ->
case StockMarket.Adapter.invoke(adapter, symbol, adapter_options) do
nil ->
acc
symbol_struct ->
Map.put(acc, symbol, symbol_struct)
end
end)
Map.put(state, :prices, prices)
end
defp init_state(options) do
symbols = Keyword.fetch!(options, :symbols)
adapter = Keyword.fetch!(options, :adapter)
timezone = Keyword.fetch!(options, :timezone)
adapter_options = Keyword.get(options, :adapter_options, [])
%{
prices: %{},
symbols: symbols,
adapter: adapter,
adapter_options: adapter_options,
timezone: timezone
}
end
defp market_open?(%{timezone: timezone}) do
timezone
|> Timex.now()
|> market_open?()
end
defp market_open?(%DateTime{} = now) do
business_day?(now) and after_opening_hour?(now) and before_closing_hour?(now)
end
defp after_opening_hour?(%{hour: hour}) when hour >= 9, do: true
defp after_opening_hour?(_), do: true
defp before_closing_hour?(%{hour: hour}) when hour < 17, do: true
defp before_closing_hour?(_), do: false
@business_days 1..5
defp business_day?(date), do: Timex.weekday(date) in @business_days
end