# ctrader-rs
Rust port of [diegobernardes/ctrader](https://github.com/diegobernardes/ctrader) — a strongly-typed, async client for the [cTrader Open API](https://help.ctrader.com/open-api).
## Architecture
| `transportTCP` | `src/transport.rs` — async TLS TCP via `tokio-native-tls` |
| `Client` struct | `src/client.rs` — `Client` struct |
| `Command[A,B]` | `Client::command::<Q,R>()` generic method |
| `keepalive()` goroutine | Tokio task in `client.rs` |
| Request registry (`map[string]chan`) | `HashMap<String, oneshot::Sender<ProtoMessage>>` |
| `openapi/*.pb.go` | `proto/*.proto` → compiled by `prost-build` in `build.rs` |
## Requirements
- Rust 1.75+
- `protoc` protobuf compiler on `$PATH`
```bash
# macOS
brew install protobuf
# Ubuntu/Debian
apt install -y protobuf-compiler
```
## Quick start
```bash
# 1. Clone / unzip the project
# 2. Fill in .env (credentials are already seeded from the Go project)
# 3. Run an example
cargo run --example get_accounts
cargo run --example symbols_list
```
## Environment variables
| `CTRADER_CLIENT_ID` | App client ID from openapi.ctrader.com |
| `CTRADER_SECRET` | App client secret |
| `CTRADER_TOKEN` | OAuth access token |
| `CTRADER_DEMO_ACCOUNT_ID` | Demo `ctidTradingAccountId` |
| `CTRADER_LIVE_ACCOUNT_ID` | Live `ctidTradingAccountId` |
## Usage in your own code
```rust
use std::time::Duration;
use ctrader::client::{Client, Config};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let config = Config::new(
std::env::var("CTRADER_CLIENT_ID")?,
std::env::var("CTRADER_SECRET")?,
);
// Connect + authenticate application
let client = Client::start(config).await?;
// Get all accounts linked to your token
let token = std::env::var("CTRADER_TOKEN")?;
let res = client.get_accounts_by_access_token(&token).await?;
for acc in res.ctid_trader_account {
println!("{} — {}", acc.ctid_trader_account_id,
if acc.is_live.unwrap_or(false) { "live" } else { "demo" });
}
// Authenticate a specific account then list symbols
let account_id: i64 = std::env::var("CTRADER_DEMO_ACCOUNT_ID")?.parse()?;
client.account_auth(account_id, &token).await?;
let symbols = client.symbols_list(account_id, false).await?;
println!("Symbols: {}", symbols.symbol.len());
Ok(())
}
```
## Listening to events
Pass an event handler to receive unsolicited messages (spot prices, execution events, …):
```rust
})).await?;
```
## Adding more API calls
Every cTrader request follows the same pattern — add a method to `Client` in `src/client.rs`:
```rust
pub async fn my_request(&self, account_id: i64) -> Result<MyRes, Error> {
let req = MyReq {
payload_type: Some(payload::MY_REQ),
ctid_trader_account_id: account_id,
};
self.command(payload::MY_REQ, req, payload::MY_RES).await
}
```
Then add the corresponding message to `proto/openapi.proto` and `build.rs` will compile it automatically.