FixAlchemy.Backend behaviour (FIXAlchemy v0.2.4)

View Source

Base for a platform trading backend over the FIX engine.

use FixAlchemy.Backend gives a module the full backend API the platform calls — connect/2, send_order/2, get_positions/1, and the rest — plus the __backend__/0 marker that makes it auto-discoverable. The baseline :fix backend is this module's defaults: generic connection fields, the plain FixAlchemy.{Session,MarketData,Portfolio}.Plain handlers, FixAlchemy.Trading as the order surface, and readiness on logon.

A broker adapter overrides the callbacks with @impl FixAlchemy.Backend to change its config schema, handler set, readiness, trading surface, or the extras it starts per connection — the connection/dispatch plumbing is shared:

defmodule MyBroker.TradingBackend do
  use FixAlchemy.Backend

  @impl FixAlchemy.Backend
  def backend_config, do: %{FixAlchemy.Backend.default_config() | id: :mybroker}

  @impl FixAlchemy.Backend
  def trading_module, do: MyBroker
end

Summary

Callbacks

Config schema exposed to the platform for connection setup.

Whether sessions defer readiness until the adapter calls Client.ready/1.

Fetch historical candles for a symbol (default: unsupported).

Decimal places a price may carry for an instrument.

Per-session subscriber modules supervised with each client.

Start any per-connection extras once a trading session is up.

Stop any per-connection extras on disconnect.

The order-entry/read module used for this backend.

Functions

The sessions a connection config declares, in declaration order.

The baseline :fix backend config schema.

The generic connection fields shared by every FIX backend.

The sections a connection form lays its shared fields out in, in order.

The repeatable session group of a connection form.

The account the connection's orders and account reads use.

The accounts the connection's trading session holds.

The shared settings of fields a session may override, in declaration order.

Build what config needs before a session using it starts.

Select the account the connection's orders and account reads use.

Run fun with the client of the session serving role.

Run fun with the name of the session serving role.

Callbacks

backend_config()

@callback backend_config() :: map()

Config schema exposed to the platform for connection setup.

defer_ready?()

@callback defer_ready?() :: boolean()

Whether sessions defer readiness until the adapter calls Client.ready/1.

get_historical_candles(connection_id, symbol, opts)

@callback get_historical_candles(
  connection_id :: binary(),
  symbol :: binary(),
  opts :: keyword()
) :: {:ok, [map()]} | {:error, term()}

Fetch historical candles for a symbol (default: unsupported).

get_instrument_precision(connection_id, symbol)

@callback get_instrument_precision(connection_id :: binary(), symbol :: binary()) ::
  {:ok, non_neg_integer()} | {:error, term()}

Decimal places a price may carry for an instrument.

The default derives it from the security list's point size (FXCM's fxcm_sym_point_size). A venue that expresses precision differently overrides this with @impl FixAlchemy.Backend.

handlers()

@callback handlers() :: [module()]

Per-session subscriber modules supervised with each client.

start_extras(connection_id, config)

@callback start_extras(connection_id :: binary(), config :: keyword()) :: any()

Start any per-connection extras once a trading session is up.

stop_extras(connection_id)

@callback stop_extras(connection_id :: binary()) :: any()

Stop any per-connection extras on disconnect.

trading_module()

@callback trading_module() :: module()

The order-entry/read module used for this backend.

Functions

attach_protection(impl, connection_id, position, opts)

cancel_order(impl, connection_id, order_id)

close_all_for_instrument(impl, connection_id, symbol)

close_all_for_side(impl, connection_id, symbol, side)

close_position(impl, connection_id, position_id)

configured_sessions(config)

@spec configured_sessions(keyword() | map()) :: [FixAlchemy.SessionConfig.t()]

The sessions a connection config declares, in declaration order.

Delegates to FixAlchemy.SessionConfig.from_connection_config/1: a config listing sessions under :sessions gets those; one carrying the flat host/port/sender_comp_id and host_md/port_md/sender_comp_id_md groups instead gets the sessions :trading and :md those groups describe. Sessions that are not fully configured are dropped, so an empty list means the config names no usable session.

connect(impl, connection_id, config)

@spec connect(module(), binary(), keyword()) :: {:ok, pid()} | {:error, term()}

default_config()

@spec default_config() :: map()

The baseline :fix backend config schema.

default_fields()

@spec default_fields() :: [map()]

The generic connection fields shared by every FIX backend.

default_groups()

@spec default_groups() :: [map()]

The sections a connection form lays its shared fields out in, in order.

Every field of default_fields/0 carries the :group it belongs to.

default_session_group()

@spec default_session_group() :: map()

The repeatable session group of a connection form.

Each row is one FIX session: the fields it always carries, plus whichever shared settings it overrides. The overridable settings are not listed here — a form derives them from the backend's own :fields, taking every one that does not declare overridable: false, so a backend adding a shared field makes it overridable by the same act.

disconnect(impl, connection_id)

@spec disconnect(module(), binary()) :: :ok

get_account_info(impl, connection_id)

get_active_account(connection_id)

@spec get_active_account(binary()) :: {:ok, binary() | nil} | {:error, term()}

The account the connection's orders and account reads use.

get_capabilities(connection_id)

get_instrument_precision(impl, connection_id, symbol)

get_orders(connection_id)

get_positions(impl, connection_id)

get_status(connection_id)

list_accounts(connection_id)

@spec list_accounts(binary()) :: {:ok, [binary()]} | {:error, term()}

The accounts the connection's trading session holds.

Accounts come from the venue: whatever it named on the messages the session has received, in the order they were first seen. The :account a connection is configured with stands in while the venue has named none.

list_subscriptions(impl, connection_id)

modify_order(impl, connection_id, order_id, changes)

overridable_fields(fields)

@spec overridable_fields([map()]) :: [map()]

The shared settings of fields a session may override, in declaration order.

Every field is overridable unless it declares overridable: false, which a backend uses for a setting that belongs to the connection as a whole rather than to any one session.

prepare(config)

@spec prepare(keyword() | map()) :: :ok | {:error, term()}

Build what config needs before a session using it starts.

Generates and caches the dictionaries config names. Repeat calls reuse what is already built.

Returns {:error, reason} when a named dictionary cannot be built.

request_collateral(impl, connection_id)

send_order(impl, connection_id, order_params)

set_active_account(connection_id, account)

@spec set_active_account(binary(), binary()) :: :ok | {:error, term()}

Select the account the connection's orders and account reads use.

account must be one of list_accounts/1; any other is rejected with {:error, :unknown_account}. An order naming its own :account still goes to that one.

subscribe_market_data(impl, connection_id, symbols, pubsub_module, pubsub_topic)

unsubscribe_market_data(impl, connection_id, symbols)

with_client(connection_id, fun)

@spec with_client(binary(), (pid() -> result)) :: result | {:error, term()}
when result: term()

Run fun with the client of the session serving role.

The role is resolved through FixAlchemy.SessionDirectory, so a connection whose sessions are named anything at all is reached by what its sessions are for. with_client/2 uses the :trading role.

Returns {:error, :not_found} when the connection runs no session, {:error, {:no_session_for_role, role}} when none serves the role, and {:error, {:session_not_ready, name}} when the session that does has no live client.

with_client(connection_id, role, fun)

@spec with_client(binary(), FixAlchemy.SessionConfig.role(), (pid() -> result)) ::
  result | {:error, term()}
when result: term()

with_session(connection_id, role, fun)

@spec with_session(
  binary(),
  FixAlchemy.SessionConfig.role(),
  (FixAlchemy.SessionConfig.name() ->
     result)
) :: result | {:error, term()}
when result: term()

Run fun with the name of the session serving role.

Resolves as with_client/3 does, for callers addressing a session's subscriber processes rather than its client.