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:
@@ -137,6 +137,7 @@ members = [
|
||||
"tests/load_tests",
|
||||
"foxhunt-deploy",
|
||||
"web-gateway",
|
||||
"ctrader-openapi",
|
||||
]
|
||||
exclude = [
|
||||
"performance-tests",
|
||||
@@ -391,6 +392,7 @@ storage = { path = "storage" }
|
||||
market-data = { path = "market-data" }
|
||||
config = { path = "config" }
|
||||
database = { path = "database" }
|
||||
ctrader-openapi = { path = "ctrader-openapi" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
33
ctrader-openapi/Cargo.toml
Normal file
33
ctrader-openapi/Cargo.toml
Normal file
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "ctrader-openapi"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
publish.workspace = true
|
||||
description = "cTrader Open API client — Protobuf over TCP+TLS for order routing, account queries, and market data"
|
||||
|
||||
[dependencies]
|
||||
tokio = { workspace = true, features = ["net", "sync", "time", "rt", "macros"] }
|
||||
tokio-native-tls.workspace = true
|
||||
native-tls = { version = "0.2", features = ["vendored"] }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
prost.workspace = true
|
||||
bytes.workspace = true
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
tracing.workspace = true
|
||||
thiserror.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
chrono.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
prost-build.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["test-util"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
76
ctrader-openapi/src/config.rs
Normal file
76
ctrader-openapi/src/config.rs
Normal 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
|
||||
}
|
||||
84
ctrader-openapi/src/error.rs
Normal file
84
ctrader-openapi/src/error.rs
Normal 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>;
|
||||
25
ctrader-openapi/src/lib.rs
Normal file
25
ctrader-openapi/src/lib.rs
Normal 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};
|
||||
Reference in New Issue
Block a user