FixAlchemy.Backend behaviour (FIXAlchemy v0.2.0)

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 shared settings of fields a session may override, in declaration order.

Build what config needs before a session using it starts.

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_capabilities(connection_id)

get_instrument_precision(impl, connection_id, symbol)

get_orders(connection_id)

get_positions(impl, connection_id)

get_status(connection_id)

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)

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.