Ir al contenido

Crear un bróker personalizado

Usa un bróker personalizado para conectar AQE a un centro de ejecución. Tu adaptador se encarga del transporte, las observaciones de cuenta y la ejecución de órdenes. La estrategia conserva el control de sus insights; el bróker recibe solicitudes de órdenes inmutables en lugar del árbol completo de insights de la estrategia.

Esta guía sigue las interfaces de AQE 0.2.0. Ajusta tu implementación a los traits de la revisión de AQE que use tu estrategia. Empieza con un transporte simulado o el entorno de pruebas del centro, no con una cuenta real con fondos.

1. Implementar la conexión y la gestión de órdenes

Sección titulada «1. Implementar la conexión y la gestión de órdenes»

Crea un tipo de Rust que implemente aq_engine::core::broker::traits::{Broker, OrderManagementProvider}. Debe ser Send + Sync y mantener la propiedad de su conexión, sus cachés y su cola de eventos.

TraitMétodos requeridosResponsabilidad
Brokerconnect, disconnect, is_connectedGestionar el ciclo de vida del transporte y liberar recursos incluso tras cancelar un intento de conexión.
Brokerget_name, get_account_typeIdentificar con precisión el adaptador y el entorno de la cuenta.
OrderManagementProvidersubmit_order, cancel_orderEnviar un BrokerOrderRequest y cancelar una orden del bróker por ID.
OrderManagementProviderupdate_take_profit, update_stop_lossModificar la protección con el precio y la cantidad proporcionados.
OrderManagementProviderclose_position, close_all_positionsSolicitar el cierre de posiciones y gestionar la protección restante.
OrderManagementProviderget_orders, get_orderDevolver registros Order de AQE con identidades y estados reales del bróker.
OrderManagementProviderget_positions, get_positionDevolver registros Position; el método de una sola posición recibe un símbolo.
OrderManagementProviderget_accountDevolver un Account con saldos, moneda, poder de compra y límites.
OrderManagementProviderdrain_trade_events_bounded, has_pending_trade_eventsExponer una cola de eventos ordenada y no bloqueante.
OrderManagementProvidersubscribe_to_trade_stream, unsubscribe_from_trade_streamGestionar los callbacks de operaciones y su ciclo de vida.

Varios métodos requieren explícitamente futuros Send, entre ellos connect, las mutaciones individuales de órdenes, get_orders, get_positions y get_account. Sigue las firmas exactas de los traits; una implementación con async fn es adecuada cuando su futuro es Send. Nunca mantengas una guarda que no sea Send a través de .await ni realices operaciones de transporte bloqueantes en un trabajador del entorno de ejecución.

Para un centro sin eventos push, planifica cómo el sondeo generará eventos del ciclo de vida y cumplirá el contrato de suscripción requerido. No declares TradeEventStream salvo que el bróker proporcione realmente eventos sin que AQE tenga que realizar sondeos.

Sobrescribe Broker::get_features() con un segmento estático de valores BrokerFeature. Declara solo funcionalidades que el adaptador admita realmente, como MarketOrder, LimitOrder, PartialClose o BrokerManagedOco. Declara AmendLimitPrice y AmendStopPrice por separado cuando el centro permita esos cambios de precio de entrada en una orden sin ejecutar.

Informa del poder de compra de forma coherente: BuyingPowerLeveraged significa que el poder de compra devuelto ya incluye el apalancamiento. Asigna los límites máximos admitidos a Account.max_total_orders y Asset.max_total_size; el tamaño de cada orden corresponde a Asset.max_order_size. No inventes límites del bróker ni presentes como definitivas estimaciones basadas únicamente en cálculos genéricos del valor nocional.

Para una operación no compatible, devuelve BrokerError::UnsupportedOperation antes de cualquier mutación de transporte. No sustituyas silenciosamente el tipo de orden ni devuelvas un resultado satisfactorio.

2. Traducir solicitudes sin perder la identidad

Sección titulada «2. Traducir solicitudes sin perder la identidad»

submit_order recibe un BrokerOrderRequest. Convierte su símbolo, dirección de la operación, cantidad, tipo y clase de orden, precios límite y stop, niveles de protección y stop dinámico a la representación admitida por el centro.

Conserva client_order_id para correlacionar los datos y úsalo como identificador de orden del cliente del centro cuando sea compatible. Permanece estable durante los reintentos o la recuperación de la misma intención de entrada; no demuestra que el centro haya creado una orden. Devuelve el ID real de la orden del bróker en el Order resultante.

Mantén estos resultados diferenciados:

ResultadoComportamiento del adaptador
No compatible antes del envíoDevolver UnsupportedOperation; no se produjo ninguna mutación.
Rechazo demostrado antes de la llamada de mutación al brókerUsar SubmissionRejected con el motivo.
Tiempo de espera del transporte agotado después del envíoConservar la incertidumbre. No afirmar que hubo rechazo ni reenviar a ciegas.
Solicitud aceptadaDevolver el estado de la orden observado por el bróker; la aceptación por sí sola no equivale a ejecución.
Ejecución, rechazo, cancelación o cierre posteriorEmitir evidencia del ciclo de vida con la orden y el evento correspondientes.

replace_order es opcional y devuelve por defecto un error de operación no compatible. Sobrescríbelo para sustituir el precio o la cantidad de una entrada pendiente. No sustituye a update_take_profit ni a update_stop_loss.

amend_entry_prices(order_id, limit_price, stop_price) es un método opcional independiente para cambiar el precio límite o el precio de activación stop de una entrada pendiente sin cambiar su cantidad. Por defecto devuelve un error de operación no compatible. Impleméntalo antes de declarar cualquiera de las capacidades de modificación. No uses update_stop_loss para modificar el precio de activación stop de la entrada: ese método controla los stops de protección.

Rellenar los registros de cuenta, órdenes y posiciones

Sección titulada «Rellenar los registros de cuenta, órdenes y posiciones»

Los literales de estructura de Rust deben incluir todos los campos del tipo actual, incluidos los opcionales. #[serde(default)] ayuda a leer registros serializados antiguos; no hace que se pueda omitir un campo en un constructor de Rust.

Registro y camposRequisito del adaptador
Account.account_id, currency, accrued_commission, max_total_ordersMantén las observaciones vinculadas a la cuenta y la moneda reales. Rellena la comisión acumulada a partir de evidencia del bróker y el límite de órdenes pendientes cuando se comunique. Los valores predeterminados heredados no demuestran que no existan costes o límites.
Position.order_idNuevo campo opcional para el ticket exacto de ejecución o posición asociado a la entrada. Usa Some(...) solo cuando el adaptador pueda demostrar la asociación; usa None para registros heredados o agregados por símbolo con varios propietarios. Nunca inventes una identidad a partir del símbolo.
Position.entry_commissionConserva la comisión de entrada de la posición por separado de las ganancias y pérdidas de negociación de la posición abierta. No vuelvas a contabilizar el mismo cargo al comunicar la contabilidad final.
Order.filled_qty, realized_pnl, commission, swapUsa las ejecuciones de entrada acumuladas para una instantánea de entrada, pero solo la cantidad ejecutada por esa salida para un evento de salida. Conserva los valores de ejecución y los importes monetarios comunicados por el bróker; la aceptación no equivale a ejecución.
Order.position_accounting, OrderLifecycleCloseDetails.position_accountingEvidencia opcional de PositionCloseAccounting para una posición cuya pertenencia pueda demostrarse. Mantén None si no se admite o hay ambigüedad; no es necesaria por el mero hecho de implementar el envío de órdenes.
BrokerLifecycleSnapshot.snapshot_complete, captured_atMarca la instantánea como completa solo después de enumerar toda la vista pertinente del ciclo de vida y conserva su momento de observación. Una instantánea vacía e incompleta no demuestra que una orden o posición haya desaparecido.

Al construir un Order, conserva sus identificadores de insight y estrategia, además del ID real de la orden del bróker. Devuelve los metadatos actuales de Asset con las órdenes y posiciones, incluidas sus reglas de negociación, calendario y gastos cuando se conozcan. Consulta los metadatos de la fuente de datos personalizada para ver los campos del instrumento.

Consultar posiciones por la identidad exacta de entrada

Sección titulada «Consultar posiciones por la identidad exacta de entrada»

get_position(symbol) puede ofrecer una vista agregada. El método más reciente get_position_by_order_id(order_id, symbol) realiza una consulta asíncrona exacta y devuelve Result<Option<Position>, BrokerError>. Su implementación predeterminada comprueba get_positions() y rechaza las identidades de orden ambiguas o ausentes en lugar de seleccionar una posición arbitraria del mismo símbolo. Sobrescríbelo si el centro necesita una consulta específica por ticket.

Para una simulación exacta en memoria o una caché personalizada, position_snapshot_reader puede devolver un PositionSnapshotReader: un callback con propiedad independiente y seguro para múltiples hilos que recibe el ID de la orden y el símbolo. Mantén su ejecución libre de E/S de transporte. Devuelve Ok(None) solo si se ha observado la ausencia; devuelve un error si los datos no están disponibles o no permiten establecer la identidad solicitada. Las decisiones de la estrategia en vivo usan la caché de instantáneas con marca de tiempo de AQE, no una solicitud de red adicional desde un callback de insight.

3. Entregar los eventos del ciclo de vida de forma fiable

Sección titulada «3. Entregar los eventos del ciclo de vida de forma fiable»

Mantén una cola de (Order, TradeUpdateEvent) en el orden de origen. drain_trade_events_bounded(max_events) extrae como máximo ese número de eventos y deja el resto en la cola. Un límite de cero no extrae nada. Nunca vacíes toda la cola para después truncar el vector devuelto.

Esta función auxiliar ilustra la operación de la cola sin imponer una biblioteca de bloqueo. Llámala mientras mantienes una guarda de corta duración sobre la cola del adaptador:

use aq_engine::core::broker::types::{Order, TradeUpdateEvent};
use std::collections::VecDeque;
pub fn take_trade_events(
queue: &mut VecDeque<(Order, TradeUpdateEvent)>,
max_events: usize,
) -> Vec<(Order, TradeUpdateEvent)> {
let count = max_events.min(queue.len());
queue.drain(..count).collect()
}

has_pending_trade_events comprueba si hay eventos disponibles en la cola sin E/S de red ni de base de datos. Invoca los callbacks fuera de los bloqueos y mantenlos breves. Publica con precisión las ejecuciones parciales y las actualizaciones de órdenes de protección, y normaliza los mensajes duplicados o fuera de orden del proveedor en lugar de inventar estados terminales.

Los envoltorios de eventos predeterminados, independientes del bróker, generan IDs de evento sin información temporal del origen. Sobrescribe subscribe_to_broker_event_stream y el manejo relacionado de envoltorios cuando tu adaptador pueda proporcionar identidades, números de secuencia y marcas de tiempo fiables de BrokerOrderEvent.

4. Gestionar la protección y la recuperación

Sección titulada «4. Gestionar la protección y la recuperación»

close_position se encarga de retirar la protección: un cierre completo cancela las órdenes nativas restantes; un cierre parcial conserva o redimensiona la protección para la exposición restante. Ok(true) confirma la recepción del comando, no el cierre de la posición. Emite por separado la evidencia del estado final de la operación.

Los hooks opcionales mejoran la supervisión y la recuperación:

HookCuándo implementarlo
get_cached_accountDevolver la instantánea más reciente de la cuenta sin E/S de transporte.
get_cached_positionsProporcionar posiciones sin transporte para ejecución determinista en memoria, no como sustituto de actualizaciones en vivo con marca de tiempo.
get_cached_lifecycle_snapshotProporcionar una vista completa del ciclo de vida que sirva de referencia definitiva, sin E/S; devolver None hasta que exista una.
estimate_order_marginObtener del bróker una estimación para la solicitud exacta, sin enviarla.
reconcile_submissionConsultar la cuenta y el comando del cliente exactos sin reenviarlo.
reconcile_order_lifecycleResolver IDs de órdenes mediante el historial de órdenes y la identidad de las posiciones tras reiniciar.
lifecycle_reconcilerProporcionar un Arc<dyn OrderLifecycleReconciler> propio para la recuperación tras reiniciar.
get_position_by_order_id, position_snapshot_readerResolver el ticket exacto de entrada, manteniendo separadas la actualización asíncrona y la consulta sin E/S de transporte.
pending_cancellation_recovery, retry_cancellationPermitir la recuperación basada en evidencia de una cancelación incierta a través del trabajador de comandos con persistencia.
execution_fill_history_readerProporcionar un historial acotado y de solo lectura de ejecuciones para una cuenta, orden de entrada y símbolo exactos.
position_valuation_readerProporcionar valoraciones de solo lectura en la moneda de la cuenta para posiciones físicas exactas, no una estimación agregada por símbolo.
account_cash_flow_readerProporcionar registros de solo lectura de aportaciones, retiradas y ajustes de la cuenta, con identidad de origen, secuencia y cobertura.
configure_live_session, configure_asset_metadataAceptar el contexto de sesión y las reglas de activos cuando el adaptador los necesite.

El resolvedor de envíos predeterminado devuelve Unsupported. El resolvedor del ciclo de vida predeterminado usa get_orders() e informa de los IDs ausentes como Unknown; no puede establecer que se haya cerrado la posición de una orden ejecutada. Nunca interpretes una orden ausente, una instantánea incompleta o un tiempo de espera agotado como prueba de cancelación o cierre.

Recuperar cancelaciones sin volver a enviar órdenes

Sección titulada «Recuperar cancelaciones sin volver a enviar órdenes»

pending_cancellation_recovery(account, order) proporciona evidencia de solo lectura de que una cancelación incierta sigue dirigida a una orden pendiente. Su implementación predeterminada devuelve None, lo que no demuestra un estado terminal. Solo el trabajador de comandos con persistencia puede llamar a retry_cancellation después de registrar un intento de recuperación. Ese hook no debe enviar, cerrar ni sustituir una orden; debe obtener evidencia concluyente de cancelación y entregarla a través del reductor habitual de eventos del bróker. La implementación predeterminada indica que la operación no es compatible. No añadas un temporizador de reintentos independiente que eluda este proceso.

Mantener los análisis opcionales y de solo lectura

Sección titulada «Mantener los análisis opcionales y de solo lectura»

Los hooks de lectura de historial, valoraciones y flujos de efectivo devuelven None por defecto. Un adaptador de ejecución puede conservar esos valores predeterminados cuando no pueda proporcionar la evidencia. Los análisis no compatibles deben seguir sin estar disponibles, no convertirse en un historial vacío inventado, una cartera sin posiciones o un flujo de efectivo de cero.

  • ExecutionFillHistoryReader::read(account_id, entry_order_id, symbol) devuelve un ExecutionFillHistory para una posición que pueda vincularse de forma demostrable a una única orden de entrada. Conserva las identidades de las ejecuciones y los tiempos de origen; position_snapshot permite añadir una comprobación de cantidad observada de forma independiente. Un historial ausente o con posiciones compensadas no demuestra que no haya ejecuciones.
  • PositionValuationReader::read_positions(account_id, symbol, position_ids) devuelve PositionValuations para un lote acotado de posiciones exactas. Conserva la moneda de la cuenta, as_of_us, los tiempos de cotización, las ganancias y pérdidas de negociación, los costes o ingresos de mantenimiento y una base explícita del valor nocional o un motivo de indisponibilidad. NotOpen es una observación de ausencia, no una ejecución de cierre ni un permiso para cerrar un insight.
  • AccountCashFlowReader::read(account_id, source_id, after_sequence) devuelve un AccountCashFlowPage con identidad de origen, moneda, cobertura temporal, cursor de secuencia y registros. Registra las aportaciones, retiradas o ajustes reales; no deduzcas depósitos ni retiradas a partir de cambios en el patrimonio, ejecuciones o el estado de los insights.

Los tres devuelven un resultado opcional dentro de Result. Una lectura no disponible y un error son distintos de una observación completa y concluyente sin registros coincidentes. Nunca deben enviar órdenes ni realizar una conciliación del ciclo de vida como efecto secundario de la lectura de análisis.

UnifiedBroker acepta directamente tu tipo de ejecución. Esta función auxiliar funciona con cualquier bróker y fuente de datos compatibles; ambos adaptadores ya deben implementar sus respectivos traits:

use aq_engine::core::broker::{
traits::{Broker, DataFeed, DataProvider, OrderManagementProvider},
UnifiedBroker,
};
pub fn live_with<E, D>(execution: E, datafeed: D) -> UnifiedBroker<E, D>
where
E: Broker + OrderManagementProvider,
D: DataFeed + DataProvider,
{
UnifiedBroker::new(execution, datafeed)
}

Usa PaperBroker con UnifiedBroker::new_backtest para el simulador histórico integrado. Implementar únicamente los traits del bróker en vivo no convierte un centro de ejecución remoto en ese simulador. La compatibilidad con el selector de conexiones de AQS es una integración de escritorio independiente, no una consecuencia automática de implementar los traits.

  1. Prueba los fallos de conexión, la cancelación, la reconexión y el apagado completo.
  2. Prueba cada tipo de orden, regla de precisión y límite de capacidad anunciados.
  3. Verifica las ejecuciones parciales, la cancelación, las actualizaciones de protección, los cierres parciales y la retirada de protección tras el cierre completo.
  4. Prueba la extracción acotada de eventos con cero, uno y varios eventos; comprueba que se conserven el orden y los eventos restantes.
  5. Simula que se agota el tiempo de espera del envío después de que el centro lo acepte. Confirma que la recuperación no duplique la orden.
  6. Reinicia con órdenes pendientes y posiciones abiertas; verifica que los resultados desconocidos y confirmados sigan diferenciados.
  7. Compara las observaciones de cuenta, órdenes y posiciones con los registros del entorno de pruebas del centro antes de considerar el uso en vivo.
  8. Mantén separadas dos entradas del mismo símbolo. Prueba la consulta por ticket exacto, las identidades desconocidas y las instantáneas incompletas en lugar de seleccionar la primera coincidencia del símbolo.
  9. Simula una cancelación incierta que siga sin resolverse tras un reinicio y verifica que solo la vía de recuperación persistente pueda reintentarla.
  10. Prueba los lectores opcionales con datos no compatibles, incompletos, desactualizados y de otras cuentas. Confirma que las lecturas de análisis no puedan enviar órdenes ni inventar evidencia contable.

En tu copia de trabajo de AQE, empieza por:

  • src/core/broker/mod.rs: traits de ejecución, valores predeterminados y composición.
  • src/core/broker/commands.rs: BrokerOrderRequest y SubmissionResolution.
  • src/core/broker/types/mod.rs: tipos de cuenta, orden, posición, ciclo de vida y error.
  • src/core/broker/features.rs: indicadores de capacidades nativas.
  • src/core/broker/fill_history.rs, position_valuation.rs y account_cash_flow.rs: contratos opcionales de análisis de solo lectura.
  • live-sync-protocol/src/position_accounting.rs y position_valuation.rs: evidencia contable y validación de valoraciones.
  • src/core/broker/mt5_broker.rs y src/core/broker/paper_broker.rs: implementaciones en vivo y simulada.

Siguiente: Crear una fuente de datos personalizada o Ejecución en vivo.