# `DpExchange.Core.Types.Balance`
[🔗](https://github.com/DistortionPoint/dp-exchange-core/blob/v0.3.120/lib/dp_exchange/core/types/balance.ex#L1)

Normalised balance entry, returned by every venue package.

## `:timestamp` means *when we asked*

A balance is the only one of the six value types with no venue event time behind it.
It is a snapshot, not an occurrence: no exchange reports "this balance happened at
10:04". So its timestamp is the moment the request was made, and **that is its
freshness** — the only honest answer to "how old is this number".

It is enforced for the same reason every other type enforces its timestamp. A balance
with no freshness is indistinguishable from a stale one, and a consumer sizing a trade
against a balance it believes is current is exactly the failure this family is built to
refuse.

## `:balance` may honestly be `nil`; `:currency` may not

These two enforced keys are not the same kind of required, and this type used to claim
they were — `balance: Decimal.t()`, with `new/1` refusing a `nil` in either.

**A total the venue did not state is a real answer.** `dp_exchange_coinbase` derives the
total from the venue's `available_balance` and `hold` and carries `nil` when either side
is missing, on the stated ground that "available 1, total unknown" and "total equals
available" are different claims and a consumer sizing against the second when the first
is true trades against money that is held. That `nil` is the *correct* value, and the
`available_balance` beside it is still real and still useful — so refusing the whole entry
would throw away good data to avoid reporting an absence honestly. The typespec now says
`Decimal.t() | nil`, which is what the family actually produces.

**A currency the venue did not state is not.** A balance attributable to no asset cannot
be acted on by anyone: there is no reading of it under which a consumer can size, book or
reconcile anything. Unlike a missing quantity there is no "the venue declined to say"
case either — a holdings row names its asset, and a `nil` here means a decode read the
wrong key, which is precisely what `DpExchange.Core.Types.Validate`'s moduledoc describes
a renamed venue field producing. It stays required, and a venue decoder that cannot name
the asset must refuse the row rather than emit one.

Same shape of decision, and the same reason, as `DpExchange.Core.Types.Order`'s narrowed
`@required_non_nil` directly below its own `@enforce_keys`: state what is true rather than
a stricter rule the packages then quietly violate.

# `t`

```elixir
@type t() :: %DpExchange.Core.Types.Balance{
  available_balance: Decimal.t() | nil,
  balance: Decimal.t() | nil,
  currency: String.t(),
  hold: Decimal.t() | nil,
  provider: atom() | String.t(),
  timestamp: DateTime.t()
}
```

# `new`

```elixir
@spec new(keyword() | map()) :: t()
```

Builds a `t:t/0`, failing closed if a required field is absent or `nil`.

`@enforce_keys` guards presence, not `nil` — see `DpExchange.Core.Types.Validate`. The
fields checked here are narrower than `@enforce_keys` on purpose: `:balance` may honestly
be `nil` and the moduledoc says when.

---

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