# `BankingCircle.FX.Stream`
[🔗](https://github.com/iamkanishka/banking_circle/blob/main/lib/banking_circle/fx/stream.ex#L1)

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.

# `disconnect`

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

Closes the WebSocket connection.

# `market_order`

```elixir
@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`

```elixir
@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`

```elixir
@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`

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

Unsubscribes from a previously-subscribed currency pair/tenor.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
