Skip to main content

lib/ccxt/pro/order_cache.ex

defmodule Ccxt.Pro.OrderCache do
  @moduledoc false

  alias Ccxt.Transpiled.Runtime

  @spec put(list() | term() | nil, map(), pos_integer()) :: list()
  def put(cache, message, limit) when is_map(message) do
    Ccxt.Pro.IndexedArrayCache.put(cache, message,
      key: &message_order_id/1,
      symbol: &message_symbol/1,
      limit: limit,
      merge: &merge_order_message/2
    )
  end

  @spec hashmap(list() | term() | nil) :: map()
  def hashmap(cache) do
    Ccxt.Pro.IndexedArrayCache.hashmap(cache,
      key: &message_order_id/1,
      symbol: &message_symbol/1
    )
  end

  defp merge_order_message(message, previous) do
    message
    |> merge_nested_order_message(previous)
    |> merge_order_trade_metadata(previous)
  end

  defp merge_nested_order_message(%{"o" => order} = message, %{"o" => previous_order})
       when is_map(order) and is_map(previous_order) do
    %{message | "o" => Map.merge(previous_order, order)}
  end

  defp merge_nested_order_message(message, previous) when is_map(previous) do
    Map.merge(previous, message)
  end

  defp merge_nested_order_message(message, _previous), do: message

  defp merge_order_trade_metadata(message, previous) do
    previous_trades = metadata_list(previous, "__ccxt_trades")
    previous_fee = metadata_value(previous, "__ccxt_fee")
    order = nested_order_message(message)

    message
    |> maybe_put_metadata("__ccxt_trades", merge_order_trade_list(previous_trades, message))
    |> maybe_put_metadata("__ccxt_fee", merge_order_fee(previous_fee, order))
  end

  defp merge_order_trade_list(previous_trades, message) do
    order = nested_order_message(message)

    if Runtime.safe_string(order, "x") == "TRADE" do
      trade_id = order |> Runtime.safe_value("t") |> value_to_string()

      previous_trades
      |> Enum.reject(fn trade ->
        trade |> nested_order_message() |> Runtime.safe_value("t") |> value_to_string() ==
          trade_id
      end)
      |> Kernel.++([message])
    else
      previous_trades
    end
  end

  defp merge_order_fee(nil, order), do: order_fee(order)

  defp merge_order_fee(previous_fee, order) do
    case order_fee(order) do
      nil ->
        previous_fee

      %{currency: currency, cost: cost} = fee ->
        if previous_fee[:currency] == currency do
          %{fee | cost: Runtime.add_number_strings(previous_fee[:cost], cost) || cost}
        else
          fee
        end
    end
  end

  defp order_fee(order) do
    case Runtime.safe_string(order, "n") do
      nil ->
        nil

      cost ->
        %{cost: cost, currency: Runtime.safe_currency_code(Runtime.safe_string(order, "N"))}
    end
  end

  defp nested_order_message(%{"o" => order}) when is_map(order), do: order
  defp nested_order_message(message), do: message

  defp message_symbol(message) do
    message
    |> nested_order_message()
    |> Runtime.safe_string("s")
    |> safe_symbol()
  end

  defp message_order_id(message) do
    message
    |> nested_order_message()
    |> Runtime.safe_value("i")
    |> value_to_string()
  end

  defp metadata_list(nil, _key), do: []
  defp metadata_list(message, key), do: List.wrap(Map.get(nested_order_message(message), key))

  defp metadata_value(nil, _key), do: nil
  defp metadata_value(message, key), do: Map.get(nested_order_message(message), key)

  defp put_metadata(%{"o" => order} = message, key, value) when is_map(order) do
    put_in(message, ["o", key], value)
  end

  defp put_metadata(message, key, value), do: Map.put(message, key, value)

  defp maybe_put_metadata(message, _key, nil), do: message
  defp maybe_put_metadata(message, _key, []), do: message
  defp maybe_put_metadata(message, key, value), do: put_metadata(message, key, value)

  defp value_to_string(nil), do: nil
  defp value_to_string(value), do: to_string(value)

  defp safe_symbol(nil), do: nil

  defp safe_symbol(market_id) do
    case Regex.run(~r/^([A-Z0-9]+)(USDT|FDUSD|USDC|BUSD|BTC|ETH|BNB|EUR|TRY|BRL|USD)$/, market_id) do
      [_full, base, quote] -> base <> "/" <> quote
      _other -> market_id
    end
  end
end