BankingCircle.FX.Stream (banking_circle v1.0.0)

Copy Markdown View Source

Streaming FX quotes and Market Order execution over Banking Circle's FX WebSocket — the "sophisticated FX workflows" path Banking Circle's docs explicitly say to reach for only after you've outgrown the REST APIs (BankingCircle.FX).

This is a genuinely different integration shape from the rest of this library: a long-lived, stateful connection instead of request/response, built on WebSockex rather than Req. Start one per session you want open (typically one per customer_id/desk, not one per request).

Usage

{:ok, pid} =
  BankingCircle.FX.Stream.start_link(
    client: :default,
    customer_id: "000012345",
    handler: self()
  )

BankingCircle.FX.Stream.subscribe(pid, "EUR/USD", :spot)

receive do
  {:banking_circle_fx, %{"type" => "IncrementalRefresh"} = quote} ->
    IO.inspect(quote)
end

BankingCircle.FX.Stream.market_order(pid, %{
  client_order_id: "my-unique-id",
  buy_currency: "EUR",
  sell_currency: "USD",
  amount_currency: "EUR",
  amount: 3_000_000,
  tenor: :spot
})

handler receives every parsed server message as {:banking_circle_fx, decoded_map}decoded_map["type"] is one of "IncrementalRefresh", "ExecutionReport", "MarketDataRequestReject", or "ErrorMessage". handler can be a pid (messages sent via send/2) or a 1-arity function called with the decoded map.

Token refresh

The connection is authenticated once at handshake via the Authorization header; Banking Circle disconnects you if your token expires without a TokenRefresh message. This module handles that automatically — it fetches a fresh token from BankingCircle.Auth.TokenServer (the same cache the REST calls share) and sends TokenRefresh on a timer, well before the ~5-minute expiry.

Implementation note

This module is built on the WebSockex library and has been written and reviewed carefully but — like the rest of this package — not compiled or run against Banking Circle's actual WebSocket endpoint in this environment. Verify the connection lifecycle (especially reconnect-on-drop behavior, which WebSockex does not do automatically) against your own sandbox before depending on this for production FX execution.

Summary

Functions

Closes the WebSocket connection.

Executes a Market Order over the WebSocket. attrs requires :client_order_id, :buy_currency, :sell_currency, :amount_currency, :amount, :tenor; optionally :target_price, :max_slippage, :buy_account, :sell_account, :note.

Connects to the FX WebSocket for the given :client (config name, default :default) and :customer_id, forwarding all parsed server messages to :handler (a pid or 1-arity function).

Subscribes to a live quote stream for currency_pair (e.g. "EUR/USD") at the given tenor.

Unsubscribes from a previously-subscribed currency pair/tenor.

Functions

disconnect(pid)

@spec disconnect(pid()) :: :ok

Closes the WebSocket connection.

market_order(pid, attrs)

@spec market_order(pid(), map()) :: :ok

Executes a Market Order over the WebSocket. attrs requires :client_order_id, :buy_currency, :sell_currency, :amount_currency, :amount, :tenor; optionally :target_price, :max_slippage, :buy_account, :sell_account, :note.

Banking Circle replies asynchronously (to handler) with two ExecutionReport messages: an initial "New"/"Pending" ack, then a "Filled" or "Rejected" final report.

start_link(opts)

@spec start_link(keyword()) :: {:ok, pid()} | {:error, term()}

Connects to the FX WebSocket for the given :client (config name, default :default) and :customer_id, forwarding all parsed server messages to :handler (a pid or 1-arity function).

subscribe(pid, currency_pair, tenor, opts \\ [])

@spec subscribe(pid(), String.t(), atom(), keyword()) :: :ok

Subscribes to a live quote stream for currency_pair (e.g. "EUR/USD") at the given tenor.

unsubscribe(pid, currency_pair, tenor)

@spec unsubscribe(pid(), String.t(), atom()) :: :ok

Unsubscribes from a previously-subscribed currency pair/tenor.