lib/tai/venue_adapters/ftx/product.ex

defmodule Tai.VenueAdapters.Ftx.Product do
  alias ExFtx.Market

  defmodule Options do
    @type t :: %Options{
      type: Tai.Venues.Product.type(),
      collateral: Tai.Venues.Product.collateral(),
      collateral_weight: Tai.Venues.Product.collateral_weight(),
      expiry: Tai.Venues.Product.expiry()
    }

    @enforce_keys ~w[type collateral]a
    defstruct ~w[type collateral collateral_weight expiry]a
  end

  @type venue :: Tai.Venue.id()
  @type product :: Tai.Venues.Product.t()
  @type options :: Options.t()
  @type market :: Market.t()

  @spec build(market, venue, options) :: product
  def build(%Market{} = market, venue_id, options) do
    %Tai.Venues.Product{
      venue_id: venue_id,
      symbol: market.name |> to_symbol,
      venue_symbol: market.name,
      alias: nil,
      base: market |> base_currency(),
      quote: market |> quote_currency(),
      venue_base: market.base_currency,
      venue_quote: market.quote_currency,
      status: market |> status(),
      type: {market, options} |> type(),
      listing: nil,
      expiry: options.expiry,
      collateral: options.collateral,
      collateral_weight: options.collateral_weight,
      price_increment: market.price_increment |> Tai.Utils.Decimal.cast!(),
      size_increment: market.size_increment |> Tai.Utils.Decimal.cast!(),
      min_price: market.price_increment |> Tai.Utils.Decimal.cast!(),
      min_size: market.min_provide_size |> Tai.Utils.Decimal.cast!(),
      value: Decimal.new(1),
      value_side: :base,
      is_quanto: false,
      is_inverse: false
    }
  end

  def to_symbol(market_name), do: market_name |> downcase_and_atom()

  defp status(%Market{enabled: true, restricted: false}), do: :trading
  defp status(%Market{enabled: true, restricted: true}), do: :restricted
  defp status(%Market{enabled: false}), do: :halt

  defp base_currency(%Market{type: "spot"} = m), do: m.base_currency |> downcase_and_atom()
  defp base_currency(m), do: m.underlying |> downcase_and_atom()

  defp quote_currency(%Market{type: "spot"} = m), do: m.quote_currency |> downcase_and_atom()
  defp quote_currency(_), do: :usd

  defp type({market, options}) do
    cond do
      String.ends_with?(market.name, "BULL/USD") -> :leveraged_token
      String.ends_with?(market.name, "BULL/USDT") -> :leveraged_token
      String.ends_with?(market.name, "BEAR/USD") -> :leveraged_token
      String.ends_with?(market.name, "BEAR/USDT") -> :leveraged_token
      String.ends_with?(market.name, "HEDGE/USD") -> :leveraged_token
      String.ends_with?(market.name, "HEDGE/USDT") -> :leveraged_token
      String.ends_with?(market.name, "HALF/USD") -> :leveraged_token
      String.ends_with?(market.name, "HALF/USDT") -> :leveraged_token
      market.type == "spot" -> :spot
      String.ends_with?(market.name, "-PERP") -> :swap
      String.starts_with?(market.name, "BVOL") -> :bvol
      String.starts_with?(market.name, "IBVOL") -> :ibvol
      String.contains?(market.name, "-MOVE-") -> :move
      true -> options.type || :future
    end
  end

  defp downcase_and_atom(str), do: str |> String.downcase() |> String.to_atom()
end