FixAlchemy.Trading (FIXAlchemy v0.2.1)

View Source

Generic FIX order-entry and account-read API.

Every function takes the session's FixAlchemy.Client pid, resolves its {connection_id, session_name}, and either sends a FIX message through the engine or reads state a subscriber (FixAlchemy.Portfolio / FixAlchemy.MarketData) has accumulated. Outbound orders use named atom fields resolved to tags through the connection's dictionary; only standard FIX fields are built here.

Venues that require their own tags on an order pass them as :extra_fields, a list of {tag, value} appended verbatim:

FixAlchemy.Trading.order(conn, "EUR/USD", 1000, :sell,
  position_effect: "C",
  extra_fields: [{9041, "74615492"}]
)

This is the baseline :fix backend's order entry. Broker adapters reuse it and supply their own position identity and close semantics on top.

Summary

Functions

Attach stop loss and/or take profit to an open position.

Place an entry order with attached stop loss and/or take profit.

Cancel a working order (35=F).

Close every position for a symbol with offsetting orders.

Close all positions for a symbol by sending offsetting orders.

Close a position by id with an offsetting order (position effect close).

The account this session's orders and account reads use.

Place a limit order (35=D, OrdType=2).

The accounts this session holds, in the order the venue first named them.

Amend a working order's price, stop price, or quantity (35=G).

Send a market or price-typed order (35=D).

Send a contingency order group (35=E NewOrderList).

Request a fresh CollateralInquiry (35=BB).

Fetch historical candles over the session (see FixAlchemy.MarketData).

Request the security list (35=x).

Request open orders (35=AF).

Request a position report (35=AN).

Select the account this session's orders and account reads use.

Place a stop order (35=D, OrdType=3).

Subscribe to streaming market data for instruments (35=V).

Unsubscribe from streaming market data for instruments (35=V).

Run fun with the account an order is to be sent for.

Functions

attach_protection(conn, position, opts)

@spec attach_protection(GenServer.server(), map(), keyword()) ::
  :ok | {:error, :no_protection_given}

Attach stop loss and/or take profit to an open position.

Both given → an OCO pair; one given → a single closing order. Orders carry PositionEffect close; a venue that identifies the position with a proprietary tag passes it as position[:extra_fields].

bracket_order(conn, symbol, quantity, side, opts)

@spec bracket_order(GenServer.server(), binary(), integer(), atom(), keyword()) ::
  :ok | {:error, :no_protection_given}

Place an entry order with attached stop loss and/or take profit.

Options

  • :stop_loss / :take_profit - protective prices
  • :entry_type - :market (default), :limit, :stop, :stop_limit
  • :price / :entry_stop - entry prices
  • :tif - time in force (default :gtc)
  • :contingency - group type (default :els)

cancel_order(conn, order_id)

@spec cancel_order(GenServer.server(), binary()) :: :ok | {:error, :order_not_found}

Cancel a working order (35=F).

close_all_for_symbol(conn, symbol, opts \\ [])

@spec close_all_for_symbol(GenServer.server(), binary(), keyword()) :: :ok

Close every position for a symbol with offsetting orders.

close_position(conn, symbol, size, opts \\ [])

@spec close_position(GenServer.server(), binary(), integer(), keyword()) :: :ok

Close all positions for a symbol by sending offsetting orders.

close_position_by_id(conn, position_id, size, opts \\ [])

@spec close_position_by_id(GenServer.server(), binary(), integer() | nil, keyword()) ::
  :ok | {:error, term()}

Close a position by id with an offsetting order (position effect close).

Sends a market order on the opposite side for the position's quantity. A broker adapter overrides this to use proprietary close semantics.

get_account(conn)

@spec get_account(GenServer.server()) :: binary() | nil

The account this session's orders and account reads use.

get_account_summary(conn)

@spec get_account_summary(GenServer.server()) :: map() | nil

get_capabilities(conn)

@spec get_capabilities(GenServer.server()) :: FixAlchemy.Capabilities.t()

get_collateral(conn)

@spec get_collateral(GenServer.server()) :: map() | nil

get_instrument(conn, instrument)

@spec get_instrument(GenServer.server(), binary()) :: map() | nil

get_order(conn, order_id)

@spec get_order(GenServer.server(), binary()) :: map() | nil

get_orders(conn)

@spec get_orders(GenServer.server()) :: map()

get_position(conn, position_id)

@spec get_position(GenServer.server(), binary()) :: map() | nil

get_positions(conn)

@spec get_positions(GenServer.server()) :: map()

get_positions_by_symbol(conn, symbol)

@spec get_positions_by_symbol(GenServer.server(), binary()) :: [map()]

limit_order(conn, symbol, size, side, price, opts \\ [])

@spec limit_order(
  GenServer.server(),
  binary(),
  integer(),
  atom() | binary(),
  binary(),
  keyword()
) :: :ok | {:error, :account_unknown}

Place a limit order (35=D, OrdType=2).

list_accounts(conn)

@spec list_accounts(GenServer.server()) :: [binary()]

The accounts this session holds, in the order the venue first named them.

Any of them may be passed as set_active_account/2's account, or as an order's :account option.

list_instruments(conn)

@spec list_instruments(GenServer.server()) :: [binary()]

modify_order(conn, order_id, opts \\ [])

@spec modify_order(GenServer.server(), binary(), keyword()) ::
  :ok | {:error, :order_not_found}

Amend a working order's price, stop price, or quantity (35=G).

order(conn, symbol, size, side, opts \\ [])

@spec order(GenServer.server(), binary(), integer(), atom() | binary(), keyword()) ::
  :ok | {:error, :account_unknown}

Send a market or price-typed order (35=D).

Returns {:error, :account_unknown} if the account is not yet known — orders are gated on account discovery.

order_list(conn, contingency, orders)

@spec order_list(GenServer.server(), atom() | integer(), [map()]) ::
  :ok | {:error, :account_unknown}

Send a contingency order group (35=E NewOrderList).

contingency is :oco, :oto, :els, or a raw integer. Orders are maps with :symbol, :quantity, :side, and optionally :type (default :market), :price, :stop_price, :tif (default :gtc), :position_effect, and :extra_fields. All members share one ClOrdLinkID.

request_collateral(conn)

@spec request_collateral(GenServer.server()) :: :ok

Request a fresh CollateralInquiry (35=BB).

request_historical_candles(conn, symbol, opts \\ [])

@spec request_historical_candles(GenServer.server(), binary(), keyword()) ::
  {:ok, [map()]} | {:error, term()}

Fetch historical candles over the session (see FixAlchemy.MarketData).

request_instruments(conn)

@spec request_instruments(GenServer.server()) :: :ok

Request the security list (35=x).

request_orders(conn)

@spec request_orders(GenServer.server()) :: :ok

Request open orders (35=AF).

request_positions(conn, opts \\ [])

@spec request_positions(
  GenServer.server(),
  keyword()
) :: :ok

Request a position report (35=AN).

set_active_account(conn, account)

@spec set_active_account(GenServer.server(), binary()) ::
  :ok | {:error, :unknown_account}

Select the account this session's orders and account reads use.

account must be one of list_accounts/1.

stop_order(conn, symbol, size, side, stop_price, opts \\ [])

@spec stop_order(
  GenServer.server(),
  binary(),
  integer(),
  atom() | binary(),
  binary(),
  keyword()
) :: :ok | {:error, :account_unknown}

Place a stop order (35=D, OrdType=3).

subscribe_market_data(conn, instrument)

@spec subscribe_market_data(GenServer.server(), binary() | [binary()]) :: :ok

Subscribe to streaming market data for instruments (35=V).

Each instrument is registered as a logon-persistent send on the engine (FixAlchemy.Client.register_logon_send/3), so the MarketDataRequest is sent once the session is logged in and re-sent on every reconnect — the retry lives in the engine, not here.

unsubscribe_market_data(conn, instrument)

@spec unsubscribe_market_data(GenServer.server(), binary() | [binary()]) :: :ok

Unsubscribe from streaming market data for instruments (35=V).

with_account(conn, opts, fun)

@spec with_account(GenServer.server(), keyword(), (binary() -> result)) ::
  result | {:error, :account_unknown}
when result: term()

Run fun with the account an order is to be sent for.

The account is opts[:account] when the caller names one, else the account selected with set_active_account/2, else the first the venue named, else the :account the session was started with. Returns {:error, :account_unknown} when none of those is known, which is how order entry is gated on account discovery.