Cómo evitar órdenes duplicadas en MT5 por reintentos y doble clic
Por qué un reintento o un doble clic puede abrir dos posiciones en MT5, y cómo un identificador por solicitud, la clave de idempotencia, lo hace seguro.
En esta página
Un usuario toca Comprar. Parece que no pasa nada, así que vuelve a tocar. Salen dos solicitudes de su teléfono y termina con dos posiciones donde quería una. La solución es un identificador por decisión, enviado con cada intento: una clave de idempotencia.
De dónde vienen los duplicados
La misma decisión se envía más de una vez de formas muy comunes:
- Un doble clic o un doble toque en una pantalla lenta.
- Un reintento después de un tiempo de espera agotado. Tu solicitud llegó al broker, la respuesta no llegó a ti, y tu código volvió a intentarlo.
- Un webhook entregado dos veces por el sistema que lo envió.
- Un trabajo de una cola ejecutado dos veces después de que un worker se reinicia.
El tiempo de espera agotado es el difícil. Cuando una solicitud expira, no sabes si la orden se colocó. Reintentar arriesga una segunda posición; no reintentar arriesga no tener ninguna.
Una clave por decisión
Envía un encabezado Idempotency-Key con cada orden. fxapis recuerda la clave durante 24 horas:
| Situación | Lo que recibes |
|---|---|
| Misma clave, misma orden, la primera llamada terminó | La primera respuesta, repetida: ninguna segunda orden |
| Misma clave, la primera llamada sigue en curso | 409 IDEMPOTENCY_IN_FLIGHT: inténtalo de nuevo en un momento |
| Misma clave, otra orden | 409 IDEMPOTENCY_KEY_REUSED: rechazada |
| Clave nueva | Una orden nueva |
Así, un reintento con la misma clave siempre es seguro, haya llegado o no el primer intento al broker.
- Tu códigoenvía la orden con Idempotency-Key: 7c1e…
- Tiempo agotadola respuesta se pierde; ¿se ejecutó?
- Reintento, misma clavefxapis reconoce la clave
- Mismo resultadola respuesta de la primera orden, sin segunda posición
En tu servidor, el ciclo de reintentos conserva la clave que recibió:
import os, uuid, requests
def buy(account_id: str, key: str) -> dict:
# `key` was made once, when the decision was made, and is reused by every attempt.
for attempt in range(3):
try:
r = requests.post(
f"https://api.fxapis.com/v1/accounts/{account_id}/orders/market",
headers={"Authorization": f"Bearer {os.environ['FXAPIS_KEY']}", "Idempotency-Key": key},
json={"symbol": "EURUSD", "side": "buy", "volume": "0.10"},
timeout=90,
)
return r.json()
except requests.Timeout:
continue # same key: a retry can never open a second position
raise RuntimeError("no answer; poll the order before deciding anything")
result = buy(os.environ["ACCOUNT_ID"], str(uuid.uuid4()))
El ejemplo del doble clic
La clave tiene que crearse cuando el usuario decide, no cuando se envía la solicitud. Créala una vez cuando se abre la pantalla de la orden o cuando se presiona el botón por primera vez, y reutilízala en cada envío que siga:
// One key per decision: created once, reused by every click and every retry.
const orderKey = crypto.randomUUID();
async function buy() {
const res = await fetch("/api/orders", {
method: "POST",
headers: { "Content-Type": "application/json", "Idempotency-Key": orderKey },
body: JSON.stringify({ symbol: "EURUSD", side: "buy", volume: "0.10" }),
});
return res.json();
}
Tu backend le pasa la misma clave a fxapis. Dos toques envían la misma clave, y el segundo recibe el resultado del primero. Desactivar el botón también ayuda, pero solo la clave cubre además los reintentos y los tiempos de espera agotados.
El error común es generar la clave dentro del ciclo de reintentos. Una clave nueva en cada intento le dice al servidor que cada intento es una orden nueva: justo lo que intentabas evitar.
Cómo elegir la clave
Un UUID aleatorio por decisión sirve para la mayoría de los casos. Cuando la decisión tiene una identidad natural, construye la clave a partir de ella, para que la misma decisión siempre dé la misma clave:
- Un miembro que toma una señal:
signal_9931:member_4821. - Un copiador que sigue una operación maestra: el deal maestro + la cuenta seguidora.
- Un trabajo en cola: el id del propio trabajo.
Abrir, cerrar y cada cierre parcial son decisiones distintas, con claves distintas.
El único resultado que no debes reintentar
Una clave de idempotencia hace seguros los reintentos; no convierte cada respuesta en un motivo para reintentar. Si una orden vuelve como ORDER_UNRESOLVED, fxapis no sabe si el broker la ejecutó. No la reenvíes con una clave nueva. Consulta la orden: fxapis la confirma contra los registros del broker y la pasa a lo que realmente ocurrió.
Y cuando una orden se rechaza con retryable: true (el precio se movió), enviarla de nuevo es una decisión nueva, así que lleva una clave nueva. Reenviarla con la clave anterior solo repite el rechazo.
Próximos pasos
- Órdenes idempotentes en MT5: repeticiones, el resultado desconocido, patrones de clave para señales, copiadores y colas, y una lista de control.
- Los tres fallos: qué errores es seguro reenviar.
- Copy trading: una orden en muchas cuentas, cada una con su propio resultado y sin duplicados (en inglés).