FixAlchemy.Portfolio behaviour (FIXAlchemy v0.2.2)
View SourceBase for a per-session portfolio subscriber.
use FixAlchemy.Portfolio gives a GenServer that owns positions, orders,
and account state for one FIX session. It subscribes to the session's
execution, position, and reject message types on the FixAlchemy.Client
dispatch bus and processes each off the socket loop, so trading state and the
TCP session survive independently of any single message's decoding.
The defaults implement plain-FIX portfolio tracking: working orders from ExecutionReports, net-per-symbol positions from fills and PositionReports, and an account summary derived from the account in force.
Accounts
A session holds every account the venue names on the messages it sends, in the
order they were first seen. list_accounts/2 returns them; get_account/2
returns the one in force — the account set_active_account/3 selected, else
the first reported, else the :account given at start, which applies only
while the venue has reported none. set_active_account/3 accepts an account
drawn from list_accounts/2 and rejects any other with
{:error, :unknown_account}.
An adapter records an account the base does not see with observe_account/2,
and files a CollateralReport under the account it belongs to with
put_collateral/3; collateral/1 reads back the report for the account in
force.
A broker adapter overrides the callbacks with @impl FixAlchemy.Portfolio to
add proprietary identity (e.g. a position id tag), collateral handling, or
extra message types, while reusing the get/broadcast machinery:
defmodule MyBroker.Portfolio do
use FixAlchemy.Portfolio
@impl FixAlchemy.Portfolio
def subscribed_types, do: super() ++ ["BA"]
@impl FixAlchemy.Portfolio
def handle_message("BA", raw, meta, state), do: apply_collateral(raw, state)
def handle_message(type, raw, meta, state), do: super(type, raw, meta, state)
endAdapter state
State the base does not model — a trade blotter, per-asset-class bookkeeping —
goes in the :extra map, read back with get_extra/2,3:
def handle_message("AE", raw, _meta, state) do
trade = raw |> FixAlchemy.Portfolio.decode(state) |> Map.delete(:raw)
update_in(state.extra, &Map.update(&1, :trades, [trade], fn ts -> [trade | ts] end))
end:extra is deliberately outside the change-detection that broadcasts positions,
orders, and the account summary, so holding state there costs nothing per
message. An adapter that wants its own updates published calls
FixAlchemy.Portfolio.broadcast/3.
Summary
Callbacks
Fold an ExecutionReport into the positions map.
Fold a PositionReport into the positions map.
Build the backend-neutral account summary from current state.
Handle a dispatched message, returning the new state.
Message types this portfolio subscribes to (default execution/position/reject).
Normalize the tracked orders map for broadcast.
Functions
The accounts state holds, in the order they were first seen.
The account state's orders and account reads use.
Publish message on topic_prefix <> connection_id.
The CollateralReport for the account in force.
Decode a raw message into a field map using the session's dictionary.
The account this session's orders and account reads use.
Adapter-owned state the base does not model.
The accounts this session holds, in the order the venue first named them.
Net signed quantity from a position report's long and short quantities.
Record account as one the venue has named.
Parse a FIX numeric field into a float, defaulting to 0.0.
File a CollateralReport under the account it reports on.
Select the account this session's orders and account reads use.
Callbacks
@callback apply_execution_report( positions :: map(), msg :: map(), order :: map(), order_id :: binary() | nil, state :: term() ) :: map()
Fold an ExecutionReport into the positions map.
Fold a PositionReport into the positions map.
Build the backend-neutral account summary from current state.
@callback handle_message( type :: binary(), raw :: binary(), meta :: map(), state :: term() ) :: term()
Handle a dispatched message, returning the new state.
@callback subscribed_types() :: [binary()]
Message types this portfolio subscribes to (default execution/position/reject).
Normalize the tracked orders map for broadcast.
Functions
The accounts state holds, in the order they were first seen.
The account state was started with stands in while the venue has named none.
The account state's orders and account reads use.
Publish message on topic_prefix <> connection_id.
For adapter-owned updates the base does not detect; positions, orders, and the account summary are already broadcast on change. No-op without a PubSub module.
The CollateralReport for the account in force.
Decode a raw message into a field map using the session's dictionary.
@spec get_account(binary(), FixAlchemy.SessionConfig.name()) :: binary() | nil
The account this session's orders and account reads use.
@spec get_account_summary(binary(), FixAlchemy.SessionConfig.name()) :: map() | nil
@spec get_collateral(binary(), FixAlchemy.SessionConfig.name()) :: map() | nil
@spec get_extra(binary(), FixAlchemy.SessionConfig.name()) :: map()
Adapter-owned state the base does not model.
Returns the whole :extra map, or the value under key when given.
@spec get_extra(binary(), FixAlchemy.SessionConfig.name(), term()) :: term()
@spec get_order(binary(), FixAlchemy.SessionConfig.name(), binary()) :: map() | nil
@spec get_orders(binary(), FixAlchemy.SessionConfig.name()) :: map()
@spec get_position(binary(), FixAlchemy.SessionConfig.name(), binary()) :: map() | nil
@spec get_positions(binary(), FixAlchemy.SessionConfig.name()) :: map()
@spec get_positions_by_symbol(binary(), FixAlchemy.SessionConfig.name(), binary()) :: [map()]
@spec list_accounts(binary(), FixAlchemy.SessionConfig.name()) :: [binary()]
The accounts this session holds, in the order the venue first named them.
Falls back to the :account the session was started with while the venue has
named none, and is empty when neither is known.
Net signed quantity from a position report's long and short quantities.
Record account as one the venue has named.
Blank and already-known accounts leave state as it is.
Parse a FIX numeric field into a float, defaulting to 0.0.
File a CollateralReport under the account it reports on.
A report naming no account is filed as the session's own, read back when the account in force has no report of its own.
@spec set_active_account(binary(), FixAlchemy.SessionConfig.name(), binary()) :: :ok | {:error, :unknown_account}
Select the account this session's orders and account reads use.
account must be one of list_accounts/2.