Trading e historial
Órdenes, cierres, órdenes pendientes, órdenes multicuenta y el historial que dejan.
Cómo enviar órdenes, y el registro permanente que dejan.
Si lees una sola sección, lee Los tres fallos. Es la diferencia entre un cliente que reintenta de forma segura y uno que, de vez en cuando, abre dos posiciones donde pediste una.
Enviar una orden de mercado
curl -sS -X POST "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/orders/market" \
-H "Authorization: Bearer $FXAPIS_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"symbol": "EURUSD",
"side": "buy",
"volume": "0.10",
"stopLoss": "1.0890",
"takeProfit": "1.1010",
"deviationPoints": 20,
"clientOrderId": "strategy-a-0417"
}'La cuenta debe estar en línea (ready). Una cuenta en modo warm_on_demand se pone
en línea sola para la orden; consulta Cuentas.
Símbolo o instrumento
symbol es el nombre propio del broker para el mercado, exactamente como lo escribe ese broker (XAUUSD.a, US30.cash). Para operar el mismo mercado en cuentas de distintos brokers, envía instrument en su lugar —XAUUSD, US30, BTCUSD— y cada orden opera el símbolo propio de esa cuenta para ese mercado. Consulta Instrumentos y símbolos.
Los volúmenes y los precios son strings
"0.10", no 0.1. Esto es obligatorio: un número JSON se rechaza con
INVALID_REQUEST, no se acepta en silencio.
Un número JSON es un double IEEE. 0.1 + 0.2 es 0.30000000000000004, y un volumen
que pasó por un double ya perdió una precisión que no podemos recuperar. Aceptarlo
significaría operar en silencio un tamaño que no pediste, así que la API lo rechaza
mientras todavía puedes corregirlo.
Usa un tipo decimal de tu lado y serialízalo como string.
Los stops son precios absolutos
stopLoss: "1.0890" es un precio, no una distancia en pips. Comprobamos el error que
el broker no puede ver —stops del lado equivocado para la dirección que querías— antes
de enviar nada, y lo rechazamos con 400 INVALID_REQUEST:
- El stop loss de una compra debe estar por debajo de su take profit (el de una venta, por encima).
- Los stops de una orden pendiente se comprueban contra su propio precio o, en una stop-limit, contra su precio límite.
- El precio de ejecución de una orden de mercado todavía no se conoce, así que sus stops se comprueban contra el mercado —el ask para una compra, el bid para una venta— cuando fxapis tiene un precio reciente del símbolo en esa cuenta; si no, se deja al broker. Un precio reciente es una cotización en vivo ya leída para esa cuenta, con un tick de menos de 10 segundos, o si no, el precio de una posición abierta en el símbolo. La orden nunca espera a que se obtenga un precio.
Las reglas de distancia y de paso son del broker, y las aplica su order_check antes de
enviar nada; un stop que rechaza vuelve como ORDER_REJECTED con el motivo del broker.
Envía siempre un Idempotency-Key
Idempotency-Key: 8f14e45f-ea8d-4b1e-9c2a-7b3d5e9f0a11Sin uno, una solicitud que agota el tiempo de espera te deja atascado. No puedes saber si la orden se envió; reintentar arriesga una segunda posición, y no reintentar arriesga no tener ninguna. Con uno, reintentar no cuesta nada.
| Situación | Lo que recibes |
|---|---|
| Misma clave, mismo cuerpo, la primera llamada terminó | La misma respuesta, repetida, con idempotent-replay: true |
| Misma clave, mismo cuerpo, la primera llamada sigue en curso | 409 IDEMPOTENCY_IN_FLIGHT: reintenta en breve |
| Misma clave, cuerpo distinto | 409 IDEMPOTENCY_KEY_REUSED |
Misma clave, después de un SEND_FAILED | Un intento nuevo de verdad: nada llegó al broker, así que la clave se libera |
| Misma clave, después de un rechazo | Se repite el rechazo. Para volver a intentarlo, usa una clave nueva |
Esa última fila es la sutil, y es deliberada. Una clave solo se libera cuando sabemos que el broker nunca vio la orden. Si el resultado no está resuelto, la clave sigue reclamada y un reintento repite la respuesta sin resolver en lugar de enviar una segunda orden.
Las claves se respetan durante 24 horas y su alcance es tu workspace; después, una clave se libera y se puede volver a usar. Usa una nueva por orden: un UUID es ideal.
Los tres fallos
Una orden puede fallar de tres maneras, y la respuesta te dice cuál. Una cuarta
respuesta, NO_PRICE, se describe junto con ellas: no se envió nada, pero reintentar de
inmediato no puede ayudar.
SEND_FAILED (503): nunca llegó al broker
{"error":{"code":"SEND_FAILED","message":"… the order was never sent. It is safe to retry.",
"details":[{"orderId":"...","state":"failed","retryable":true}]}}Sabemos con certeza que no se envió nada al broker. La orden queda failed.
Reinténtala. Es el único estado de orden en el que reenviar es seguro.
ORDER_REJECTED (422): el broker dijo que no
{"error":{"code":"ORDER_REJECTED","message":"The broker rejected the order: No money",
"details":[{"orderId":"...","state":"rejected","retryable":false}]}}El broker la rechazó, con un motivo, y no se ejecutó nada. La orden lleva el retcode
propio del broker: consúltala con GET /v1/orders/{id} y verás 10019 aquí, sin
modificar.
retryable te dice si el motivo era transitorio:
retryable: true: una recotización (10004), un precio que se movió (10020), sin cotizaciones en este momento (10021). El mercado se movió; enviar una orden nueva —con unIdempotency-Keynuevo— es lo correcto. Mira primero el nuevo precio.retryable: false: margen insuficiente (10019), mercado cerrado (10018), trading desactivado (10017), un volumen o un stop inválido. Reintentar solo trae el mismo rechazo.
NO_PRICE (422): el broker no tenía precio
{"error":{"code":"NO_PRICE","message":"The broker has no current price for USDJPY on this account, so nothing was sent. …",
"details":[{"orderId":"...","state":"failed","retryable":false}]}}La terminal de la cuenta no tenía cotización para el símbolo, así que no envió la orden.
Normalmente el mercado está cerrado, o este broker no ofrece el símbolo con ese nombre.
La orden queda failed.
No reintentes de inmediato. Revisa el horario de trading del símbolo en
GET /v1/accounts/{id}/instruments y reintenta cuando esté cotizando. No se envió nada,
así que se puede reutilizar el mismo Idempotency-Key.
ORDER_UNRESOLVED (503): no lo sabemos
{"error":{"code":"ORDER_UNRESOLVED","message":"… we do not know whether the order reached the broker. Do not resend it.",
"details":[{"orderId":"...","state":"unknown","retryable":false}]}}No reenvíes esta orden. Puede estar activa en el broker ahora mismo.
Esto ocurre cuando un envío agota el tiempo de espera, la conexión se cae a mitad de la
solicitud, o el broker devuelve un código que no dice qué pasó: 10012 TIMEOUT cancela
nuestra espera, no necesariamente la orden.
Consulta GET /v1/orders/{id}. Para una orden que abre una posición, confirmamos el
resultado con el broker automáticamente y la sacamos de unknown a lo que realmente
pasó; needsReconciliation es true hasta entonces. Un cierre, un cambio de stop o una
cancelación que termina en unknown no se resuelve automáticamente: su stateDetail te
indica revisar la posición o la orden en el broker. Consulta
Confirmar resultados más abajo.
Aquí somos cautelosos a propósito. Un resultado que no podemos confirmar es unknown,
nunca "probablemente falló", incluso para un código de retorno que nunca habíamos visto.
Equivocarse en esta dirección cuesta una espera corta; equivocarse en la otra cuesta una
posición que nunca pediste.
Confirmar resultados
Una orden de apertura en unknown se comprueba automáticamente contra el historial de
deals del broker, cada minuto. Un cierre, un cambio de stop o una cancelación no produce
un deal de apertura con el cual compararse, así que se queda en unknown con un
stateDetail que te pide revisar en el broker: interpretar una ausencia como "falló"
podría ser un error. POST /v1/accounts/{id}/reconcile lo hace de inmediato para una
cuenta.
curl -sS -X POST "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/reconcile" -H "Authorization: Bearer $FXAPIS_KEY"{"data":{"examined":1,"resolved":1,"stillUnknown":0,"ambiguous":0,
"dealsIngested":1,"positionsOpen":1,"positionsClosed":0,"skipped":null}}Se puede llamar en cualquier momento, y llamarlo dos veces no tiene riesgo.
En qué se resuelve
| En el broker | La orden pasa a |
|---|---|
| Operaciones que cubren todo el volumen | filled |
| Operaciones que cubren parte | partially_filled |
| Ninguna operación coincide, y ya pasó suficiente tiempo | rejected |
| Ninguna operación coincide todavía | sigue en unknown por ahora |
| Más de una coincidencia posible | sigue en unknown, contada como ambiguous |
Una orden unknown nunca pasa a failed: ese estado significa que la orden, de forma
demostrable, nunca salió de fxapis, y después de los hechos solo el registro del broker
puede decir qué pasó.
Cuando no puede estar seguro, no adivina. Si dos operaciones en el broker podrían ser
tu orden, la orden sigue en unknown y se cuenta en ambiguous: asignar una ejecución a
la orden equivocada produciría un historial que parece correcto y no lo es. Esas órdenes
necesitan a una persona:
curl -sS "$FXAPIS_API/v1/orders?state=unknown" -H "Authorization: Bearer $FXAPIS_KEY"El stateDetail de la orden dice cuántas candidatas había.
Los comentarios de tus órdenes
fxapis agrega una etiqueta corta al inicio del comentario de MT5 de cada orden; así se
relaciona el registro del broker con tu orden. El comentario admite 29 caracteres; tu
propio comment recibe los 17 que quedan después de la etiqueta, en ASCII simple. Un
comentario más largo se recorta para que quepa, nunca se rechaza.
Mantiene tu historial completo
Cada comprobación también registra los deals del broker del período, haya o no algo que confirmar —incluidos los deals que no causó ninguna orden tuya—, y actualiza las posiciones abiertas. Una posición que el broker ya no informa se marca como cerrada, no se elimina.
Cerrar una posición
curl -sS -X POST "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/positions/2104151615/close" \
-H "Authorization: Bearer $FXAPIS_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" -d '{"volume": "0.05"}'Omite volume para cerrarla completa.
El ticket a cerrar es el brokerPositionId de la posición. Para una posición que abrió tu
propia orden, esa orden también lo lleva, como order.brokerPositionId, tomado de la
propia ejecución cuando la orden abre. Una orden pendiente que se ejecuta después también
lo recibe, vinculado desde las posiciones y los deals del broker, normalmente en unos
minutos, y pasa de working a filled al mismo tiempo (consulta
Estados de una orden). Mientras siga en null, busca la posición en
GET /v1/accounts/{id}/positions: una posición nueva lleva el ticket de la orden que la
abrió, que es el brokerOrderId de esa orden.
Una clave de idempotencia importa más aquí que en una orden de apertura. Un cierre reintentado no cierra dos veces: en una cuenta hedging abre una posición opuesta, y la cuenta termina con una exposición que nadie eligió.
Un cierre es una orden. Tiene su propio registro con intent: "close", los mismos tres
modos de fallo y la misma regla: ORDER_UNRESOLVED significa no reenviar. Un cierre
sin resolver es el caso en el que podrías creer que no tienes posición y no ser así.
Dos cosas se rechazan antes de enviar nada:
- Cerrar más de lo que tiene la posición. El mensaje nombra ambos números.
- Un cierre parcial que dejaría menos del volumen mínimo del símbolo (
volumeMinen su lista de símbolos): el broker lo rechaza después del cierre, y deja una posición demasiado pequeña para operar y demasiado pequeña para cerrar. Ciérrala completa, o deja al menos el mínimo.
Mover un stop
curl -sS -X POST "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/positions/2104151615/modify" \
-H "Authorization: Bearer $FXAPIS_KEY" -H "Content-Type: application/json" \
-d '{"stopLoss": "1.0890", "takeProfit": null}'null quita un nivel. Omitir el campo lo deja sin cambios. Que un campo omitido
borre un stop en silencio es la forma en que la protección desaparece sin que nadie lo
elija.
Se registra con intent: "modify", porque ampliar un stop cambia lo que la cuenta
arriesga y pertenece junto a la operación que protege: un stop que se movió antes de una
pérdida es exactamente lo que pregunta una auditoría.
Los stops se comprueban contra el precio actual del mercado, no contra la entrada: el stop loss de una compra debe estar por debajo del mercado y su take profit por encima (en una venta, al revés). Así que mover un stop a break-even, o arrastrarlo hacia la ganancia, está permitido. Un stop que el mercado ya superó se rechaza antes de enviar nada, porque cerraría la posición de inmediato. Los stops de una orden nueva se comprueban contra su entrada: consulta Los stops son precios absolutos.
Órdenes pendientes
curl -sS -X POST "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/orders/pending" \
-H "Authorization: Bearer $FXAPIS_KEY" -H "Content-Type: application/json" \
-d '{"symbol":"EURUSD","side":"buy","kind":"limit","volume":"0.10","price":"1.0900"}'| Tipo | La compra espera | La venta espera |
|---|---|---|
limit | por debajo del mercado | por encima |
stop | por encima del mercado | por debajo |
stop_limit | el stop se activa y luego coloca una limit | igual |
Una limit espera un precio mejor; una stop espera uno peor y es la forma de operar una
ruptura. Cuando fxapis tiene un precio reciente del símbolo en esa cuenta (consulta
Los stops son precios absolutos), el lado equivocado se
rechaza antes de enviar nada, con un mensaje que nombra la orden que probablemente
querías. Sin él, el broker responde con su propio INVALID_PRICE, que no dice nada sobre
en qué sentido debía ir.
Las órdenes pendientes son parte de los planes de pago; en un plan sin ellas, la solicitud
se rechaza con 402 FEATURE_NOT_IN_PLAN.
Para mover una orden pendiente activa —su precio, sus stops, su precio stop-limit o su
vencimiento— usa POST /v1/orders/{id}/modify. Conserva el ticket de la orden, así que
conserva su lugar en la cola del broker; un campo omitido no se toca, y
"stopLoss": null quita un stop. Solo se puede mover una orden working.
Una orden pendiente colocada está working, no filled. Esa distinción es dinero
real: filled significa que existe una posición. Algunos brokers responden a la
colocación de una pendiente con DONE, el mismo código que devuelve una ejecución, e
interpretarlo como ejecución te diría que estás en el mercado cuando estás esperando
entrar.
Cancela una con POST /v1/orders/{id}/cancel. Si ya se activó, el broker la rechaza con
10035: en ese punto hay una posición, no una orden, y lo que quieres es un cierre. Una
cancelación exitosa pasa la orden pendiente a cancelled y registra la propia
instrucción como completed.
Margen y ganancia, antes de operar
curl -sS -X POST "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/calculate" \
-H "Authorization: Bearer $FXAPIS_KEY" -H "Content-Type: application/json" \
-d '{"kind":"margin","symbol":"EURUSD","side":"buy","volume":"0.10","price":"1.1370"}'
# → { "data": { "kind": "margin", "value": "22.74000000" } }No abre nada y se puede llamar libremente. La cuenta debe estar en línea: una fuera de
línea se rechaza con 409 NO_RUNTIME. Así averiguas que una orden será rechazada por
margen antes de enviarla, en lugar de recibir un 10019. "kind": "profit" necesita
un closePrice.
Un value nulo significa que el broker no quiso responder, lo que no es lo mismo que cero.
Precios en vivo
curl -sS "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/quotes?symbols=EURUSD&instruments=XAUUSD" \
-H "Authorization: Bearer $FXAPIS_KEY"
# → { "data": [
# { "symbol": "EURUSD", "instrument": null, "status": "ok", "bid": "1.08450000", "ask": "1.08462000",
# "spread": 12, "digits": 5, "brokerTime": "2026-10-15 15:00:00.123", "time": "2026-10-15T12:00:00.123Z",
# "observedAt": "2026-10-15T12:00:00.300Z", "ageMs": 177, "stale": false, "marketOpen": true, "tradeMode": "full" },
# { "symbol": "XAUUSD.a", "instrument": "XAUUSD", "status": "ok", "bid": "2650.10000000", … } ] }Los precios vienen del propio broker de la cuenta. Cada broker cotiza distinto, y estos son los precios a los que opera esta cuenta.
- Qué puedes pedir: hasta 20
symbolsdel broker einstrumentsen total, separados por comas, todos leídos en una llamada. Un instrumento se cotiza como el símbolo propio de esta cuenta para ese mercado. La cuenta debe estar en línea:ready,executingodegraded(si no,409 ACCOUNT_NOT_READY). - Cada cotización con un tick lleva
time(UTC),brokerTime(el reloj propio del broker, como lo muestra su terminal) yageMs, la antigüedad del tick cuando se leyó la terminal enobservedAt. Aplica tu propia regla de frescura a partir de esos datos. - Un mercado cerrado responde su último precio con
marketOpen: falseystale: false, nunca con un error. stalees true solo mientras el mercado está abierto y el último tick tiene más de 60 segundos.marketOpenviene de las sesiones de trading propias del broker. Esnull(ystaleesfalse) hasta que la terminal las haya informado, más o menos un minuto después de que una cuenta se pone en línea por primera vez.tradeModees lo que el broker permite en el símbolo en este momento:full,longOnly,shortOnly,closeOnlyodisabled.closeOnlyydisabledno permiten posiciones nuevas.- Un símbolo que este broker no tiene responde
status: "unknown_symbol"en su lugar; uno del que todavía no envió precio,status: "no_price". - Consultas repetidas: no hay stream. Las respuestas se comparten durante un cuarto
de segundo, así que una cotización puede haberse leído hasta 250 ms antes de que la
pidieras: esa es la antigüedad de la lectura, no del precio, que es lo que te dicen
ageMsytime. A la terminal de cada cuenta se le pueden hacer 300 consultas por minuto con ráfagas de 50, sumando todos los llamadores y todas las claves; las respuestas compartidas son gratis. Por encima de eso:429 RATE_LIMITEDconretry-after. - Una terminal que todavía corre un worker anterior —algo raro— responde
501 CAPABILITY_UNAVAILABLEen su lugar. No lo reintentes; mientras tanto, elcurrentPricede una posición abierta es el precio.
Los stops de una orden se comprueban contra este precio cuando se leyó uno recientemente: el ask para una compra, el bid para una venta (Los stops son precios absolutos).
Historial de precios
Disponible después del próximo mantenimiento programado. Hasta que la terminal de la
cuenta se haya actualizado, esto responde 501 CAPABILITY_UNAVAILABLE.
curl -sS "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/candles?instrument=XAUUSD&timeframe=H1&count=100" \
-H "Authorization: Bearer $FXAPIS_KEY"
# → { "data": { "symbol": "XAUUSD.a", "instrument": "XAUUSD", "timeframe": "H1", "digits": 2, "candles": [
# { "time": "2026-10-23T13:00:00Z", "open": "2650.10", "high": "2655.25", "low": "2648.00",
# "close": "2652.50", "tickVolume": 1234, "spread": 35 }, … ] } }Barras (velas) del propio broker de la cuenta, para un symbol o un instrument.
timeframe:M1,M5,M15,M30,H1,H4,D1,W1oMN1.count: de 1 a 1,000 barras (100 por defecto), contando hacia atrás desde la actual. De la más antigua a la más reciente; la última barra todavía se está formando.timees cuándo abrió la barra, en UTC, convertido con el desfase de reloj actual del broker: las barras anteriores al último cambio de horario de verano del broker pueden tener una hora de diferencia.tickVolumecuenta los cambios de precio en la barra, no el volumen operado.spreadestá en puntos.- La cuenta debe estar en línea (si no,
409 ACCOUNT_NOT_READY). Un símbolo que el broker no tiene responde404 SYMBOL_NOT_FOUND. Cada llamada llega a la terminal y cuenta dentro de las mismas 300 por minuto que las cotizaciones.
Estados de una orden
| Estado | Significado | ¿Reenviar? |
|---|---|---|
accepted | Recibida, todavía no enviada | — |
sending | En curso | No |
working | Una orden pendiente esperando en el broker. No existe ninguna posición. | No: ya está ahí |
completed | Terminó una instrucción que no es una operación: una cancelación, un stop movido | — |
filled | El broker confirmó todo el volumen | No |
partially_filled | Ejecutada en parte; el resto aún puede ejecutarse | No |
rejected | El broker la rechazó. No se ejecutó nada. | Solo si es retryable |
failed | Demostrado que nunca llegó al broker | Sí |
unknown | Resultado no establecido | Nunca |
cancelled | Retirada antes de ejecutarse | — |
expired | Una orden pendiente venció en el broker | — |
failed y unknown nunca son intercambiables. failed es algo que afirmamos solo con
pruebas.
Una orden pendiente que se activa pasa de working a filled (o
partially_filled) cuando su ejecución aparece en el historial de deals del broker o
como una posición con su ticket, normalmente en unos minutos, con un único webhook
order.filled. filledPrice y filledVolume vienen de su deal de ejecución; cuando solo
se conoce la posición, se toman de ella en una cuenta hedging (el volumen solo mientras
siga siendo igual al de la orden) y se dejan en null en una cuenta netting, cuya
posición puede contener ejecuciones de otras órdenes, hasta que llega el deal. Una orden
pendiente que el broker cancela o deja vencer por su cuenta todavía no se detecta: se
queda en working.
Historial
Tres tablas, porque las preguntas son distintas.
Órdenes: lo que pediste
curl -sS "$FXAPIS_API/v1/orders?state=unknown&limit=50" -H "Authorization: Bearer $FXAPIS_KEY"Cada orden, con el código de retorno del broker conservado exactamente como llegó.
Filtra por accountId, state, symbol y una ventana since/until.
state=unknown es la consulta sobre la que vale la pena construir una alerta: es todo lo
que todavía no tiene un resultado establecido.
Deals: lo que el broker realmente hizo
curl -sS "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/deals?since=2026-09-01T00:00:00Z" \
-H "Authorization: Bearer $FXAPIS_KEY"Un deal es el registro del broker de dinero que cambia de manos. Es contra lo que se concilia un estado de cuenta, y no es una fila por orden:
- Una orden puede producir varios deals. Una ejecución parcial deja más de uno.
- Un deal puede existir sin ninguna orden tuya detrás. Un stop loss que se activa en el broker, un cargo de swap, un depósito, una corrección, o alguien que opera el mismo login desde MetaTrader en el escritorio.
Ese segundo punto es la razón por la que este endpoint existe por separado. Un historial
que solo contuviera tus propias órdenes mostraría una cuenta cuyo balance se mueve sin
motivo visible. orderId es null exactamente en esas filas, para que puedas
distinguirlas.
Ordenados por la marca de tiempo propia del broker, no la nuestra. dealType tiene nombre
en lugar de número —balance, commission, buy— porque 2 no significa nada en un
estado de cuenta.
Deals de una orden: GET /v1/orders/{id}/deals.
Posiciones: lo que está abierto ahora
curl -sS "$FXAPIS_API/v1/accounts/$FXAPIS_ACCOUNT/positions" -H "Authorization: Bearer $FXAPIS_KEY"Una instantánea con observedAt indicando cuándo era cierta. El broker es la fuente de
verdad; esto es lo último que vimos, y la marca de tiempo está ahí para que puedas
distinguir una vista actual de una desactualizada en lugar de suponerlo.
Paginación
Por cursor, no por offset:
curl -sS "$FXAPIS_API/v1/orders?limit=100" -H "Authorization: Bearer $FXAPIS_KEY"
# → { "data": [...], "page": { "hasMore": true, "nextCursor": "2026-09-28T14:02:11.482Z" } }
curl -sS "$FXAPIS_API/v1/orders?limit=100&cursor=2026-09-28T14:02:11.482Z" -H "Authorization: Bearer $FXAPIS_KEY"Un offset se desplaza a medida que llegan órdenes nuevas, así que la segunda página se saltaría filas en silencio: en un historial de trading, justamente las filas agregadas mientras paginabas.
Retención
Las órdenes y los deals se conservan durante la vida de la cuenta y no se depuran. Desconectar una cuenta borra sus credenciales; no borra su historial, porque ese historial es con lo que se resuelve una disputa o una auditoría.
Un cliente de ejemplo
import uuid, time, httpx
class OrderError(Exception): ...
class Unresolved(OrderError):
"""Do not resend. The order may be live."""
def place(client, account_id, **order):
key = str(uuid.uuid4())
for attempt in range(3):
response = client.post(
f"/v1/accounts/{account_id}/orders/market",
json=order,
# The SAME key on every attempt. That is the whole point: the
# server decides whether this is a retry or a new order, and it is
# the only party that can know.
headers={"Idempotency-Key": key},
)
if response.status_code == 201:
return response.json()["data"]
error = response.json()["error"]
code = error["code"]
if code == "SEND_FAILED":
# Proven not to have reached the broker.
time.sleep(2 ** attempt)
continue
if code == "IDEMPOTENCY_IN_FLIGHT":
time.sleep(1)
continue
if code == "ORDER_UNRESOLVED":
# Never retried. Hand the order id to whatever follows up on
# unresolved orders, and stop.
raise Unresolved(error["details"][0]["orderId"])
if code == "ORDER_REJECTED":
if error["details"][0].get("retryable"):
# The market moved: a new decision needs a NEW key. Resending
# with this one would only replay the rejection.
raise OrderError(f"retryable rejection: {error['message']}")
raise OrderError(error["message"])
raise OrderError(f"{code}: {error['message']}")
raise OrderError("gave up after 3 attempts")La idea clave del ejemplo: se usa la misma clave en cada intento, porque el servidor
es el único que puede saber si se trata de un reintento o de una orden nueva;
ORDER_UNRESOLVED nunca se reintenta; y un rechazo retryable necesita una decisión
nueva con una clave nueva.
Órdenes multicuenta
Una operación, enviada en muchas cuentas en el mismo momento. La API las llama execution waves.
curl -sS -X POST "$FXAPIS_API/v1/execution-waves" \
-H "Authorization: Bearer $FXAPIS_KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" -d '{
"accountIds": ["…", "…", "…"],
"symbol": "EURUSD", "side": "buy", "volume": "0.01",
"weights": {"<account-id>": "0.05"},
"barrierPolicy": "release-ready",
"executeAt": "2026-10-01T13:30:00Z"
}'Primero se pone en línea cada cuenta; luego todas las órdenes salen juntas. Consulta
GET /v1/execution-waves/{id} para ver el resultado.
weights define el tamaño de la operación por cuenta, para cuentas con balances distintos.
Cada cuenta informa su propio resultado
Los brokers son independientes entre sí, así que cien cuentas no pueden ejecutarse como una sola transacción: las ejecuciones llegan a precios ligeramente distintos, algunas cuentas pueden rechazarse por margen y, de vez en cuando, una queda sin respuesta.
Por eso una orden multicuenta nunca informa un único éxito o fallo. Se liquida, y
summary cuenta lo que pasó:
{"state":"settled","dispatchSpreadMs":30,
"summary":{"total":100,"filled":97,"rejected":2,"skipped":0,"unresolved":1}}La operación de cada cuenta tiene su propio orderId y es una orden normal: se consulta,
se confirma y se audita como cualquier otra. unresolved significa lo mismo que en una
orden individual: no reenvíes, la estamos confirmando con el broker.
Cuando algunas cuentas no están en línea a tiempo
barrierPolicy | Qué pasa |
|---|---|
release-ready | Opera las cuentas que están en línea; omite el resto. Una entrada parcial es mejor que una tardía. |
all-or-nothing | No opera ninguna a menos que todas las cuentas estén en línea. Para estrategias que solo tienen sentido en el conjunto completo. |
wait | Espera después de executeAt hasta que todas las cuentas estén en línea, hasta expiresAt. |
all-or-nothing se refiere al envío, no al resultado: un broker todavía puede rechazar
la orden de una cuenta después de que se envió.
Con executeAt, las órdenes salen a esa hora, no antes. Sin él, salen en cuanto todas las
cuentas están en línea: se espera a las cuentas que todavía están iniciando sesión, hasta
60 segundos, y después decide la política.
Dispersión
dispatchSpreadMs es el tiempo entre la salida de la orden de la primera cuenta y la de
la última. Es el número sobre el que vale la pena alertar: una dispersión creciente
significa que las cuentas están obteniendo precios distintos. Cada orden multicuenta
informa la suya; estas son de nuestro benchmark contra un broker instantáneo, así que un
broker real agrega su propia latencia encima:
| cuentas | dispersión |
|---|---|
| 10 | 17 ms |
| 50 | 26 ms |
| 100 | 30 ms |
Medido de nuestro lado; cada broker agrega su propia latencia encima.
Cancelar una
POST /v1/execution-waves/{id}/cancel, pero solo antes de que haya salido alguna orden.
Una vez que las órdenes se están enviando, no se pueden recuperar.
Cuentas
Cómo conectar cuentas de MT5, mantenerlas en línea y qué significa cada estado.
Instrumentos y símbolos
Nombra un mercado una sola vez —XAUUSD, US30, BTCUSD— y opera el símbolo propio del broker de cada cuenta. Lista los símbolos, las especificaciones de contrato y el horario de mercado de cualquier broker conectado.