Skip to content

Support IBKR spot FX orders (e.g. USD.JPY) #104

Description

@felipecsl

Summary

Add spot FX (forex) orders to the CLI and MCP server, for example USD.JPY. Use IBKR only.

At this time, the CLI can only place stock orders (equity preview/submit), option orders, and futures-option orders. You can see FX positions (IBKR CASH shows as FOREX), but you cannot trade them.

Current limits

  • src/equities/equityOrder.ts: EquityContract is fixed to assetClass: "STK", exchange: "SMART", currency: "USD".
  • @huskly/ibkr-gateway-client 0.17.0: EquityOrderMutationContract, EquityContract and PreviewOrdersRequest.contract are also STK/USD only. resolveEquityContract resolves stocks only.
  • src/derivatives/: the contract type is limited to "OPT" | "FOP".
  • Schwab place-order always sends assetType: "EQUITY". The Schwab Trader API does not support spot FX. This feature is for IBKR only.
  • Preview, order, and quote output use currencyFormatUsd. FX prices are in the quote currency (JPY for USD.JPY).

Design

1. Gateway API (in the gateway repo, released as a new @huskly/ibkr-gateway-client version)

  • Add operation resolveForexContract with the request { pair: "USD.JPY" }.
  • Add the schema ForexContract:
    {
      conid: number;
      assetClass: "CASH";
      symbol: string;     // base currency, e.g. "USD"
      currency: string;   // quote currency, e.g. "JPY"
      localSymbol: string; // "USD.JPY"
      exchange: "IDEALPRO";
    }
  • Change PreviewOrdersRequest.contract (and the create operation) to a discriminated union on assetClass: EquityOrderMutationContract | ForexOrderMutationContract. Use one order-mutation pipeline, not a second one. Preview IDs, idempotency keys, operator identity, warning acknowledgement, recovery and cancel stay the same.
  • For CASH, the gateway must reject session. FX trades 24/5, so the REGULAR/OVERNIGHT session does not apply.
  • The gateway must return what-if commission and margin in the account base currency, and it must include the currency code.

2. CLI domain layer (src/forex/)

Use the same structure as src/equities/:

  • forexOrder.ts: ForexContract, CanonicalForexIntent, ForexGatewayClient types.
  • forexPair.ts: parse and validate BASE.QUOTE. Accept /^[A-Z]{3}\.[A-Z]{3}$/ only. Reject a pair when base equals quote. Also accept the input USDJPY and USD/JPY, and normalize them to USD.JPY.
  • forexGatewayAdapter.ts: map the gateway client to ForexGatewayClient.
  • forexOrderService.ts: the preview/submit flow. Use the same preview store, expiry, and contract-match check as equityOrderService.ts. If the equity and forex services share logic, move that logic into a shared helper. Do not copy it.

Order terms (first version):

Field Rule
side BUY or SELL (the base currency)
quantity Positive integer, in base-currency units (e.g. 25000 = 25,000 USD)
orderType LMT only in the first version
limit Positive number, quote currency per 1 base unit
tif DAY or GTC
session Not used
  • Do not enforce tick size in the CLI. IBKR preview rejects bad increments. The CLI shows the rejection reasons.
  • IDEALPRO routes orders below the minimum size (about 25,000 USD equivalent) as odd lots with worse prices. The preview must show a warning when the gateway reports an odd-lot route. Do not hard-code the threshold in the CLI.

3. CLI commands (src/cli/forexOrders.ts)

huskly-cli fx preview <pair> <side> <quantity> --limit <price> [--tif DAY|GTC] [--json]
huskly-cli fx submit <preview-id> --confirm [--operator <name>] [--json]
  • Default broker: ibkr. Under --broker schwab, show a clear error ("FX orders are available for IBKR only").
  • Status, recovery, reconcile and cancel use the existing order commands. The operation model is the same.

4. Formatting

  • Add formatMoney(value, currencyCode) in src/format.ts (use Intl.NumberFormat with the currency code). Keep currencyFormatUsd as a wrapper.
  • Use the quote currency for FX limit prices, and the base currency for FX quantities.
  • Update orders and orderContractQuotes so that FX orders do not show $ prices.

5. MCP

  • Add fx_order_preview and fx_order_submit tools in src/mcp/tools/forexOrders.ts. Use the same input shape as the CLI.

Delivery layers

Each layer must work end to end before the next layer starts.

  1. Gateway: resolveForexContract and the CASH contract union for preview and create. Release the client.
  2. CLI: fx preview (LMT only) with currency-aware output. Tests.
  3. CLI: fx submit, and FX support in orders output. Tests.
  4. MCP tools.
  5. Later: STP orders, and FX quotes in quote --broker ibkr.

Tests

  • Unit tests for forexPair.ts (valid pairs, USDJPY, USD/JPY, bad codes, same currency).
  • Service tests with a fake ForexGatewayClient: contract mismatch, preview expiry, idempotent submit, and odd-lot warning.
  • Command tests: Schwab rejection, --json DTO shape, and JPY price formatting.

Decisions

  1. The IBKR account has FX trading permission.
  2. Fractional quantities are not needed. Quantity is whole base-currency units only.
  3. Currency conversion mode is not needed. FX orders create normal FX positions only.
  4. positions shows FX P/L in USD only. The gateway must return FX position values and P/L in USD, so the existing currencyFormatUsd output in positions stays the same.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions