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.
- Gateway:
resolveForexContract and the CASH contract union for preview and create. Release the client.
- CLI:
fx preview (LMT only) with currency-aware output. Tests.
- CLI:
fx submit, and FX support in orders output. Tests.
- MCP tools.
- 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
- The IBKR account has FX trading permission.
- Fractional quantities are not needed. Quantity is whole base-currency units only.
- Currency conversion mode is not needed. FX orders create normal FX positions only.
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.
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 (IBKRCASHshows asFOREX), but you cannot trade them.Current limits
src/equities/equityOrder.ts:EquityContractis fixed toassetClass: "STK",exchange: "SMART",currency: "USD".@huskly/ibkr-gateway-client0.17.0:EquityOrderMutationContract,EquityContractandPreviewOrdersRequest.contractare also STK/USD only.resolveEquityContractresolves stocks only.src/derivatives/: the contract type is limited to"OPT" | "FOP".place-orderalways sendsassetType: "EQUITY". The Schwab Trader API does not support spot FX. This feature is for IBKR only.currencyFormatUsd. FX prices are in the quote currency (JPY forUSD.JPY).Design
1. Gateway API (in the gateway repo, released as a new
@huskly/ibkr-gateway-clientversion)resolveForexContractwith the request{ pair: "USD.JPY" }.ForexContract:PreviewOrdersRequest.contract(and the create operation) to a discriminated union onassetClass: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.CASH, the gateway must rejectsession. FX trades 24/5, so the REGULAR/OVERNIGHT session does not apply.2. CLI domain layer (
src/forex/)Use the same structure as
src/equities/:forexOrder.ts:ForexContract,CanonicalForexIntent,ForexGatewayClienttypes.forexPair.ts: parse and validateBASE.QUOTE. Accept/^[A-Z]{3}\.[A-Z]{3}$/only. Reject a pair when base equals quote. Also accept the inputUSDJPYandUSD/JPY, and normalize them toUSD.JPY.forexGatewayAdapter.ts: map the gateway client toForexGatewayClient.forexOrderService.ts: the preview/submit flow. Use the same preview store, expiry, and contract-match check asequityOrderService.ts. If the equity and forex services share logic, move that logic into a shared helper. Do not copy it.Order terms (first version):
BUYorSELL(the base currency)25000= 25,000 USD)LMTonly in the first versionDAYorGTC3. CLI commands (
src/cli/forexOrders.ts)ibkr. Under--broker schwab, show a clear error ("FX orders are available for IBKR only").ordercommands. The operation model is the same.4. Formatting
formatMoney(value, currencyCode)insrc/format.ts(useIntl.NumberFormatwith the currency code). KeepcurrencyFormatUsdas a wrapper.ordersandorderContractQuotesso that FX orders do not show$prices.5. MCP
fx_order_previewandfx_order_submittools insrc/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.
resolveForexContractand theCASHcontract union for preview and create. Release the client.fx preview(LMT only) with currency-aware output. Tests.fx submit, and FX support inordersoutput. Tests.STPorders, and FX quotes inquote --broker ibkr.Tests
forexPair.ts(valid pairs,USDJPY,USD/JPY, bad codes, same currency).ForexGatewayClient: contract mismatch, preview expiry, idempotent submit, and odd-lot warning.--jsonDTO shape, and JPY price formatting.Decisions
positionsshows FX P/L in USD only. The gateway must return FX position values and P/L in USD, so the existingcurrencyFormatUsdoutput inpositionsstays the same.