# 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

| Go module | Rust equivalent |
|---|---|
| `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

| Variable | Description |
|---|---|
| `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
let client = Client::start_with_handler(config, Some(|msg| {
    println!("event payloadType={:?}", msg.payload_type);
})).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.
