feat(ctrader-openapi): scaffold crate with config and error types

New workspace crate for cTrader Open API client (Protobuf over TCP+TLS).
Includes CTraderConfig, CTraderEnvironment, CTraderError, and RateLimitBucket types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-02-22 15:29:05 +01:00
parent 8b81138262
commit f63ba8627c
5 changed files with 220 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
//! cTrader Open API configuration.
use serde::{Deserialize, Serialize};
/// Target environment (demo vs. live).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CTraderEnvironment {
/// `demo.ctraderapi.com:5035`
Demo,
/// `live.ctraderapi.com:5035`
Live,
}
impl CTraderEnvironment {
/// TCP host for the environment.
pub const fn host(&self) -> &'static str {
match self {
Self::Demo => "demo.ctraderapi.com",
Self::Live => "live.ctraderapi.com",
}
}
/// TCP port (both envs use 5035).
pub const fn port(&self) -> u16 {
5035
}
}
impl Default for CTraderEnvironment {
fn default() -> Self {
Self::Demo
}
}
/// Configuration for a cTrader Open API connection.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CTraderConfig {
/// OAuth2 application client ID.
pub client_id: String,
/// OAuth2 application client secret.
pub client_secret: String,
/// OAuth2 access token (obtained via auth code or refresh flow).
pub access_token: String,
/// cTID trader account ID.
pub account_id: i64,
/// Demo or Live environment.
#[serde(default)]
pub environment: CTraderEnvironment,
/// Heartbeat interval in seconds (cTrader requires ≤ 10s).
#[serde(default = "default_heartbeat_interval")]
pub heartbeat_interval_secs: u64,
/// Per-request timeout in milliseconds.
#[serde(default = "default_request_timeout")]
pub request_timeout_ms: u64,
/// Maximum reconnection attempts before giving up.
#[serde(default = "default_max_reconnect")]
pub max_reconnect_attempts: u32,
}
const fn default_heartbeat_interval() -> u64 {
10
}
const fn default_request_timeout() -> u64 {
5000
}
const fn default_max_reconnect() -> u32 {
5
}

View File

@@ -0,0 +1,84 @@
//! cTrader Open API error types.
use std::fmt;
/// Errors from the cTrader Open API client.
#[derive(Debug, thiserror::Error)]
pub enum CTraderError {
/// TCP or TLS connection failed.
#[error("connection failed: {0}")]
ConnectionFailed(String),
/// TLS handshake error.
#[error("TLS error: {0}")]
TlsError(String),
/// OAuth2 token exchange failed.
#[error("OAuth error: {0}")]
OAuthError(String),
/// Application-level authentication rejected.
#[error("application auth failed: {0}")]
AppAuthFailed(String),
/// Account-level authentication rejected.
#[error("account auth failed: {0}")]
AccountAuthFailed(String),
/// Protobuf encode/decode error.
#[error("protocol error: {0}")]
ProtocolError(String),
/// Codec framing error.
#[error("codec error: {0}")]
CodecError(String),
/// Rate limit exceeded (50 req/s or 5 req/s for historical).
#[error("rate limit exceeded for {bucket}")]
RateLimitExceeded {
/// Which bucket was exhausted.
bucket: RateLimitBucket,
},
/// Request timed out waiting for a response.
#[error("request timed out after {0:?}")]
Timeout(std::time::Duration),
/// Server returned an error payload.
#[error("server error {code}: {description}")]
ServerError {
/// cTrader error code.
code: String,
/// Human-readable description.
description: String,
},
/// Client is not connected.
#[error("not connected")]
NotConnected,
/// Symbol not found in the cached symbol list.
#[error("unknown symbol: {0}")]
UnknownSymbol(String),
}
/// Rate-limit bucket categories.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RateLimitBucket {
/// Standard messages: 50 per second.
NonHistorical,
/// Historical data requests: 5 per second.
Historical,
}
impl fmt::Display for RateLimitBucket {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NonHistorical => f.write_str("non-historical (50/s)"),
Self::Historical => f.write_str("historical (5/s)"),
}
}
}
/// Convenience alias used throughout the crate.
pub type Result<T> = std::result::Result<T, CTraderError>;

View File

@@ -0,0 +1,25 @@
//! cTrader Open API client for Rust.
//!
//! Provides a production-grade async client for the cTrader Open API protocol
//! (Protobuf over TCP+TLS), targeting ICMarkets broker connectivity.
//!
//! # Architecture
//!
//! - **config** — connection configuration (environment, credentials, timeouts)
//! - **error** — error types for all failure modes
//! - **proto** — compiled protobuf message types
//! - **codec** — length-delimited protobuf wire codec
//! - **connection** — TCP+TLS transport with heartbeat
//! - **auth** — OAuth2 token exchange + protobuf auth sequence
//! - **dispatch** — request/response correlation and event broadcasting
//! - **rate_limiter** — token-bucket rate limiter (50/s + 5/s historical)
//! - **orders** — order request builders and type conversions
//! - **symbols** — symbol name → ID resolution cache
//! - **account** — account info and position reconciliation
//! - **client** — high-level `CTraderClient` API
pub mod config;
pub mod error;
pub use config::{CTraderConfig, CTraderEnvironment};
pub use error::{CTraderError, Result};