사용자 지정 브로커 만들기
사용자 지정 브로커로 AQE를 주문 실행을 제공하는 거래소나 브로커에 연결하세요. 어댑터는 통신, 계좌 관측값, 주문 실행을 담당합니다. 인사이트의 소유권은 전략에 남아 있으며, 브로커는 전략의 전체 인사이트 트리 대신 불변 주문 요청을 받습니다.
이 가이드는 AQE 0.2.0 인터페이스를 따릅니다. 전략이 사용하는 AQE 리비전의 트레이트에 맞춰 구현하세요. 실제 자금이 있는 계좌를 사용하지 말고, 모의 통신 계층이나 해당 거래 서비스의 샌드박스에서 시작하세요.
1. 연결 및 주문 관리 구현하기
섹션 제목: “1. 연결 및 주문 관리 구현하기”aq_engine::core::broker::traits::{Broker, OrderManagementProvider}를 구현하는 Rust 타입을 만듭니다. 이 타입은 Send + Sync여야 하며 연결, 캐시, 이벤트 대기열을 소유해야 합니다.
| 트레이트 | 필수 메서드 | 역할 |
|---|---|---|
Broker | connect, disconnect, is_connected | 통신 수명 주기를 소유하고 연결 시도가 취소된 뒤에도 정리합니다. |
Broker | get_name, get_account_type | 어댑터와 계좌 환경을 정확히 식별합니다. |
OrderManagementProvider | submit_order, cancel_order | BrokerOrderRequest를 제출하고 ID로 브로커 주문을 취소합니다. |
OrderManagementProvider | update_take_profit, update_stop_loss | 제공된 가격과 수량으로 보호 설정을 수정합니다. |
OrderManagementProvider | close_position, close_all_positions | 포지션 청산을 요청하고 남은 보호 설정을 관리합니다. |
OrderManagementProvider | get_orders, get_order | 실제 브로커 식별자와 상태를 가진 AQE Order 레코드를 반환합니다. |
OrderManagementProvider | get_positions, get_position | Position 레코드를 반환합니다. 단일 포지션 메서드는 심볼을 받습니다. |
OrderManagementProvider | get_account | 잔고, 통화, 매수 가능 금액, 한도가 포함된 Account를 반환합니다. |
OrderManagementProvider | drain_trade_events_bounded, has_pending_trade_events | 순서가 있는 비차단 이벤트 대기열을 제공합니다. |
OrderManagementProvider | subscribe_to_trade_stream, unsubscribe_from_trade_stream | 거래 콜백과 그 수명 주기를 관리합니다. |
connect, 개별 주문 변경, get_orders, get_positions, get_account를 비롯한 여러 메서드는 명시적으로 Send 퓨처를 요구합니다. 정확한 트레이트 시그니처를 따르세요. 퓨처가 Send인 경우 async fn 구현이 적합합니다. Send가 아닌 가드를 .await에 걸쳐 유지하거나 런타임 워커에서 차단 방식의 통신 작업을 수행하지 마세요.
푸시 이벤트가 없는 시장에서는 폴링으로 수명 주기 이벤트를 생성하고 필수 구독 계약을 지원할 방법을 계획하세요. 브로커가 AQE의 폴링 없이 실제 이벤트를 제공하는 경우가 아니라면 TradeEventStream을 제공한다고 명시하지 마세요.
네이티브 기능 선언하기
섹션 제목: “네이티브 기능 선언하기”Broker::get_features()를 재정의하여 BrokerFeature 값의 정적 슬라이스를 반환하세요. MarketOrder, LimitOrder, PartialClose, BrokerManagedOco 등 어댑터가 실제로 지원하는 기능만 명시하세요. 해당 시장이 미체결 주문의 진입 가격 변경을 지원하면 AmendLimitPrice와 AmendStopPrice를 각각 명시하세요.
매수 가능 금액을 일관되게 보고하세요. BuyingPowerLeveraged는 반환된 매수 가능 금액에 레버리지가 이미 포함되었다는 뜻입니다. 지원되는 상한을 Account.max_total_orders와 Asset.max_total_size에 매핑하세요. 개별 주문 크기는 Asset.max_order_size에 해당합니다. 브로커 한도를 임의로 만들거나 일반적인 명목 금액 계산만으로 얻은 추정치를 확정적 값으로 제공하지 마세요.
지원하지 않는 작업은 통신을 통한 변경을 수행하기 전에 BrokerError::UnsupportedOperation을 반환하세요. 알리지 않고 다른 주문 유형으로 대체하거나 성공을 반환하지 마세요.
2. 식별자를 잃지 않고 요청 변환하기
섹션 제목: “2. 식별자를 잃지 않고 요청 변환하기”submit_order는 BrokerOrderRequest를 받습니다. 심볼, 방향, 수량, 주문 유형 및 클래스, 지정가와 스톱 가격, 보호 수준, 트레일링 스톱을 해당 시장이 지원하는 표현으로 매핑하세요.
요청과 주문을 연결해 추적할 수 있도록 client_order_id를 보존하고, 거래 서비스가 지원하면 클라이언트 주문 식별자로 사용하세요. 동일한 진입 의도의 재시도나 복구 과정에서도 이 값은 유지됩니다. 다만 이 값이 있다고 해서 거래 서비스에 주문이 생성되었다는 뜻은 아닙니다. 결과 Order에는 실제 브로커 주문 ID를 반환하세요.
다음 결과를 서로 구분하세요.
| 결과 | 어댑터 동작 |
|---|---|
| 전송 전에 지원하지 않음이 확인됨 | UnsupportedOperation을 반환합니다. 변경은 발생하지 않았습니다. |
| 브로커 변경 호출 전에 거부가 입증됨 | 사유와 함께 SubmissionRejected를 사용합니다. |
| 전송 후 통신 시간 초과 | 불확실성을 유지합니다. 거부되었다고 단정하거나 확인 없이 다시 제출하지 마세요. |
| 요청 접수 | 브로커에서 관측한 주문 상태를 반환합니다. 접수만으로 체결을 의미하지는 않습니다. |
| 이후 체결, 거부, 취소, 청산 | 해당 주문과 이벤트를 통해 수명 주기 변경을 입증하는 정보를 전달합니다. |
replace_order는 선택 사항이며 기본적으로 지원하지 않는다는 오류를 반환합니다. 대기 중인 진입 주문의 가격 및 수량 교체를 지원하려면 재정의하세요. update_take_profit이나 update_stop_loss를 대체하는 메서드는 아닙니다.
amend_entry_prices(order_id, limit_price, stop_price)는 대기 중인 진입 주문의 수량을 바꾸지 않고 지정가 구성 요소나 스톱 발동 가격을 변경하는 별도의 선택적 메서드입니다. 기본 구현은 지원하지 않는다는 오류를 반환합니다. 두 정정 기능 중 하나라도 제공한다고 명시하기 전에 구현하세요. 스톱 발동 가격 정정을 보호용 스톱을 제어하는 update_stop_loss로 보내지 마세요.
계좌, 주문, 포지션 레코드 채우기
섹션 제목: “계좌, 주문, 포지션 레코드 채우기”Rust 구조체 리터럴에는 선택적 필드를 포함하여 현재 타입의 모든 필드가 있어야 합니다. #[serde(default)]는 이전에 직렬화된 레코드를 읽는 데 도움이 되지만, Rust 생성 코드에서 필드를 생략할 수 있게 해 주지는 않습니다.
| 레코드 및 필드 | 어댑터 요구 사항 |
|---|---|
Account.account_id, currency, accrued_commission, max_total_orders | 관측값이 실제 계좌와 통화에 해당하도록 유지하세요. 브로커의 증거를 바탕으로 누적 수수료를 채우고, 보고되는 경우 대기 주문 수의 상한을 채우세요. 기존 기본값은 비용이나 한도가 없다는 증거가 아닙니다. |
Position.order_id | 진입과 연결된 정확한 실행 또는 포지션 티켓을 담는 새 선택적 필드입니다. 어댑터가 연관성을 입증할 수 있을 때만 Some(...)을 사용하세요. 레거시 레코드나 소유 주체가 여럿인 심볼 단위 집계 레코드에는 None을 사용하세요. 심볼로 식별자를 임의 생성하지 마세요. |
Position.entry_commission | 포지션 진입 수수료를 미청산 포지션의 거래 손익과 별도로 보존하세요. 최종 회계 결과를 보고할 때 같은 비용을 다시 계산하지 마세요. |
Order.filled_qty, realized_pnl, commission, swap | 진입 스냅샷에는 누적 진입 체결량을 사용하되, 청산 이벤트에는 해당 청산으로 체결된 수량만 사용하세요. 브로커가 보고한 체결 및 현금 값을 보존하세요. 접수는 체결이 아닙니다. |
Order.position_accounting, OrderLifecycleCloseDetails.position_accounting | 소유 관계를 입증할 수 있는 포지션에 대한 선택적 PositionCloseAccounting 증거입니다. 지원하지 않거나 모호하면 None을 유지하세요. 단순히 주문 제출을 구현하는 데 필수인 정보는 아닙니다. |
BrokerLifecycleSnapshot.snapshot_complete, captured_at | 관련 수명 주기 뷰 전체를 열거한 뒤에만 완전함을 표시하고, 관측 시각을 유지하세요. 비어 있는 불완전한 스냅샷은 주문이나 포지션이 사라졌다는 증거가 될 수 없습니다. |
Order를 생성할 때 실제 브로커 주문 ID뿐 아니라 인사이트와 전략 식별자도 유지하세요. 주문 및 포지션과 함께 최신 Asset 메타데이터를 반환하고, 알려진 거래 규칙, 거래 일정, 비용 정보를 포함하세요. 종목 필드는 사용자 지정 데이터 피드 메타데이터를 참조하세요.
정확한 진입 식별자로 포지션 조회하기
섹션 제목: “정확한 진입 식별자로 포지션 조회하기”get_position(symbol)은 집계된 뷰를 제공할 수 있습니다. 새로 추가된 get_position_by_order_id(order_id, symbol)는 비동기 방식으로 정확한 포지션을 조회하고 Result<Option<Position>, BrokerError>를 반환합니다. 기본 구현은 get_positions()를 확인하며, 같은 심볼의 임의 포지션을 선택하지 않고 모호하거나 누락된 주문 식별자를 거부합니다. 해당 시장에서 전용 티켓 조회가 필요하면 재정의하세요.
정확한 인메모리 시뮬레이션이나 사용자 지정 캐시에서는 position_snapshot_reader가 주문 ID와 심볼을 받는, 소유권이 있고 스레드에 안전한 콜백인 PositionSnapshotReader를 반환할 수 있습니다. 이 콜백에서는 통신을 수행하지 마세요. 부재가 관측된 경우에만 Ok(None)을 반환하고, 데이터를 사용할 수 없거나 요청한 식별자를 확인할 수 없으면 오류를 반환하세요. 실시간 전략의 의사 결정에는 인사이트 콜백의 추가 네트워크 요청이 아니라 AQE의 타임스탬프가 있는 스냅샷 캐시를 사용합니다.
3. 수명 주기 이벤트를 신뢰성 있게 전달하기
섹션 제목: “3. 수명 주기 이벤트를 신뢰성 있게 전달하기”(Order, TradeUpdateEvent) 대기열을 소스 순서대로 유지하세요. drain_trade_events_bounded(max_events)는 최대 해당 개수만큼 이벤트를 제거하고 나머지는 대기열에 남깁니다. 한도가 0이면 아무것도 제거하지 않습니다. 전체를 비운 다음 반환 벡터를 잘라내는 방식은 사용하지 마세요.
다음 헬퍼는 특정 잠금 라이브러리에 의존하지 않는 대기열 처리 예제입니다. 어댑터 대기열의 잠금 가드를 짧게 유지한 상태에서 호출하세요.
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는 네트워크나 데이터베이스 I/O 없이 대기열 준비 상태를 확인합니다. 콜백은 잠금 밖에서 호출하고 짧게 유지하세요. 부분 체결과 보호 레그 업데이트를 정확하게 게시하고, 종료 상태를 임의로 만들어내지 말고 중복되거나 순서가 바뀐 공급자 메시지를 정규화하세요.
특정 브로커에 종속되지 않는 기본 이벤트 래퍼는 이벤트 ID를 생성하지만 원본 이벤트의 발생 시각은 알 수 없습니다. 어댑터가 신뢰할 수 있는 BrokerOrderEvent 식별자, 시퀀스 번호, 타임스탬프를 제공할 수 있다면 subscribe_to_broker_event_stream과 관련 이벤트 엔벌로프 처리를 재정의하세요.
4. 보호 설정과 복구 처리하기
섹션 제목: “4. 보호 설정과 복구 처리하기”close_position은 보호 설정 정리를 담당합니다. 전체 청산 시에는 남아 있는 네이티브 레그를 취소하고, 부분 청산 시에는 남은 익스포저에 맞게 보호 설정을 유지하거나 수량을 조정합니다. Ok(true)는 명령에 대한 확인 응답이지 청산 완료 확인이 아닙니다. 최종 거래 결과를 입증하는 이벤트는 별도로 전달하세요.
선택적 훅을 구현하면 모니터링과 복구 기능을 개선할 수 있습니다.
| 훅 | 구현해야 하는 경우 |
|---|---|
get_cached_account | 통신 I/O 없이 최신 계좌 스냅샷을 반환합니다. |
get_cached_positions | 결정론적 인메모리 실행을 위해 통신 없이 포지션을 제공합니다. 타임스탬프가 있는 실시간 갱신을 대체하지는 않습니다. |
get_cached_lifecycle_snapshot | I/O 없이 완전하고 확정적인 수명 주기 뷰를 제공합니다. 해당 뷰가 생길 때까지 None을 반환합니다. |
estimate_order_margin | 주문을 제출하지 않고 정확히 해당 요청에 대한 확정적인 증거금 추정치를 얻습니다. |
reconcile_submission | 다시 제출하지 않고 정확한 계좌와 클라이언트 명령을 조회합니다. |
reconcile_order_lifecycle | 재시작 후 주문 이력과 포지션 식별자를 사용해 주문 ID를 확인합니다. |
lifecycle_reconciler | 재시작 복구를 위해 소유권이 있는 Arc<dyn OrderLifecycleReconciler>를 제공합니다. |
get_position_by_order_id, position_snapshot_reader | 비동기 갱신과 통신 없는 조회를 분리한 상태로 정확한 진입 티켓을 확인합니다. |
pending_cancellation_recovery, retry_cancellation | 내구성 있는 명령 워커를 통해 취소 여부가 불확실한 요청의 증거 기반 복구를 지원합니다. |
execution_fill_history_reader | 정확히 지정된 계좌, 진입 주문, 심볼에 대해 범위가 제한된 읽기 전용 체결 이력을 제공합니다. |
position_valuation_reader | 심볼 전체 추정치가 아니라 정확히 식별된 실제 포지션의 계좌 통화 기준 평가값을 읽기 전용으로 제공합니다. |
account_cash_flow_reader | 출처 식별자, 시퀀스, 조회 범위를 포함한 계좌 자금 이동 및 조정 내역을 읽기 전용으로 제공합니다. |
configure_live_session, configure_asset_metadata | 어댑터가 필요로 할 때 세션 컨텍스트와 자산 규칙을 받습니다. |
기본 제출 확인기는 Unsupported를 반환합니다. 기본 수명 주기 확인기는 get_orders()를 사용하고 누락된 ID를 Unknown으로 보고합니다. 체결된 주문의 포지션이 청산되었는지는 확인할 수 없습니다. 주문 부재, 불완전한 스냅샷, 시간 초과를 취소나 청산의 증거로 해석하지 마세요.
주문을 다시 실행하지 않고 취소 복구하기
섹션 제목: “주문을 다시 실행하지 않고 취소 복구하기”pending_cancellation_recovery(account, order)는 취소 여부가 불확실한 요청이 여전히 대기 중인 주문을 대상으로 한다는 읽기 전용 증거를 제공합니다. 기본 반환값인 None은 최종 상태의 증거가 아닙니다. 내구성 있는 명령 워커만 복구 시도를 기록한 뒤 retry_cancellation을 호출할 수 있습니다. 이 훅은 주문 제출, 포지션 청산 또는 주문 교체를 수행해서는 안 됩니다. 확정적인 취소 증거를 확보하고 정상적인 브로커 이벤트 리듀서를 통해 전달해야 합니다. 기본 구현은 이 작업을 지원하지 않습니다. 이 절차를 우회하는 독립적인 재시도 타이머를 추가하지 마세요.
분석 기능을 선택적 읽기 전용으로 유지하기
섹션 제목: “분석 기능을 선택적 읽기 전용으로 유지하기”이력, 평가, 현금 흐름 리더 훅은 기본적으로 None을 반환합니다. 주문 실행 어댑터가 증거를 제공할 수 없으면 이 기본값을 유지해도 됩니다. 지원하지 않는 분석은 제공 불가 상태로 유지해야 하며, 빈 이력이나 포지션이 없는 포트폴리오, 현금 흐름 0을 임의로 만들어내서는 안 됩니다.
ExecutionFillHistoryReader::read(account_id, entry_order_id, symbol)는 단일 진입 주문에 속한다는 사실이 입증된 포지션에 대한ExecutionFillHistory를 반환합니다. 체결 식별자와 원본 발생 시각을 보존하세요.position_snapshot으로 독립적으로 관측한 수량을 추가 검증할 수 있습니다. 누락되었거나 상계 집계된 이력을 체결이 없다는 증거로 간주하지 마세요.PositionValuationReader::read_positions(account_id, symbol, position_ids)는 정확히 식별된 포지션의 제한된 배치에 대해PositionValuations를 반환합니다. 계좌 통화,as_of_us, 호가 시각, 거래 손익, 보유 비용 및 수익, 명시적인 명목 금액 산정 기준 또는 제공 불가 사유를 보존하세요.NotOpen은 부재를 관측한 결과이며, 청산 체결이나 인사이트를 종료해도 된다는 허가가 아닙니다.AccountCashFlowReader::read(account_id, source_id, after_sequence)는 출처 식별자, 통화, 시간 범위, 시퀀스 커서, 레코드를 포함하는AccountCashFlowPage를 반환합니다. 실제 자금 이동이나 조정 내역을 기록하세요. 계좌 순자산의 변화, 체결, 인사이트 상태로 입출금을 추론하지 마세요.
세 리더 모두 Result 안에 선택적 결과를 반환합니다. 조회 불가와 오류는 일치하는 레코드가 없다는 확정적이고 완전한 관측과 다릅니다. 분석 조회의 부수 효과로 주문을 제출하거나 수명 주기 상태를 대조하고 복구해서는 안 됩니다.
5. 주문 실행과 데이터 결합하기
섹션 제목: “5. 주문 실행과 데이터 결합하기”UnifiedBroker는 자체 주문 실행 타입을 직접 받습니다. 다음 헬퍼는 요구 사항을 충족하는 모든 브로커와 데이터 피드에서 동작합니다. 두 어댑터는 각각의 트레이트를 이미 구현한 상태여야 합니다.
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)}기본 제공 과거 데이터 시뮬레이터에는 PaperBroker와 UnifiedBroker::new_backtest를 사용하세요. 실시간 브로커 트레이트를 구현하는 것만으로 원격 주문 실행 시장이 해당 시뮬레이터로 바뀌지는 않습니다. AQS 연결 선택기 지원은 별도의 데스크톱 연동이며, 트레이트 구현만으로 자동 제공되지 않습니다.
6. 실시간 사용 전에 검증하기
섹션 제목: “6. 실시간 사용 전에 검증하기”- 연결 실패, 취소, 재연결, 완전한 종료를 시험합니다.
- 제공한다고 명시한 각 주문 유형, 정밀도 규칙, 한도를 테스트합니다.
- 부분 체결, 취소, 보호 설정 갱신, 부분 청산, 전체 청산 시 정리를 검증합니다.
- 이벤트가 0개, 1개, 여러 개인 경우 한도 내에서 대기열의 이벤트를 꺼내는 동작을 테스트합니다. 순서와 남은 이벤트가 보존되는지 확인합니다.
- 시장에서 접수한 뒤 제출 시간 초과가 발생하는 상황을 시뮬레이션합니다. 복구 시 주문이 중복 생성되지 않는지 확인합니다.
- 대기 주문과 미청산 포지션이 있는 상태에서 재시작합니다. 알 수 없는 결과와 확인된 결과가 계속 구분되는지 검증합니다.
- 실시간 사용을 고려하기 전에 계좌, 주문, 포지션 관측값을 해당 시장의 샌드박스 기록과 비교합니다.
- 같은 심볼의 진입 두 건을 별도로 유지합니다. 심볼이 처음 일치하는 포지션을 선택하지 말고, 정확한 티켓 조회, 알 수 없는 식별자, 불완전한 스냅샷을 테스트합니다.
- 취소 여부가 불확실한 상태에서 재시작하는 상황을 시뮬레이션하고, 내구성 있는 복구 경로만 취소를 재시도할 수 있는지 검증합니다.
- 지원하지 않는 데이터, 불완전하거나 오래된 데이터, 다른 계좌의 데이터로 선택적 리더를 테스트합니다. 분석 조회가 주문을 제출하거나 회계 증거를 임의 생성할 수 없는지 확인합니다.
소스 참조
섹션 제목: “소스 참조”AQE 체크아웃에서 다음 파일부터 살펴보세요.
src/core/broker/mod.rs: 주문 실행 트레이트, 기본값, 결합.src/core/broker/commands.rs:BrokerOrderRequest및SubmissionResolution.src/core/broker/types/mod.rs: 계좌, 주문, 포지션, 수명 주기, 오류 타입.src/core/broker/features.rs: 네이티브 기능 플래그.src/core/broker/fill_history.rs,position_valuation.rs,account_cash_flow.rs: 선택적 읽기 전용 분석 계약.live-sync-protocol/src/position_accounting.rs및position_valuation.rs: 회계 증거 및 평가 검증.src/core/broker/mt5_broker.rs및src/core/broker/paper_broker.rs: 실시간 및 모의 구현.
다음 문서: 사용자 지정 데이터 피드 만들기 또는 실시간 런타임.
로그인, 계정 보안, 색상 테마 설정, 이 알림 표시와 PostHog의 필수 내부 이용 현황·세션 품질·안정성 지표 수집을 위해 필수 쿠키와 저장소를 사용합니다. 광고 쿠키는 사용하지 않습니다.
