tradingkit 0.1.0

Exchange-agnostic trading library for equities and crypto
Documentation
# AGENTS.md — tradingkit

## Project Overview

Rust library crate for cryptocurrency and equity exchange trading. Provides
exchange-agnostic domain models, a data-access layer with cache-first reads,
and concrete exchange adapters (Alpaca live/paper, OKX signing). Uses serde for
serialization, reqwest for HTTP, and HMAC-SHA256 for payload signing.

**Workspace layout:**

```
tradingkit/                    # Library crate (Rust 2024 edition)
  src/
    lib.rs                     # Crate root — module declarations + crate-level docs only
    crypto.rs                  # HMAC-SHA256 signing, base64 encoding
    model.rs                   # Exchange-agnostic domain types (orders, accounts, options)
    data/
      mod.rs                   # Source / Store / Repository abstractions, InMemoryStore, DataApi
    exchange/
      mod.rs                   # Credentials, RequestHeaders, Exchange traits
      alpaca.rs                # Alpaca REST adapter + Source impls + TradingApi/DataApi impls
      okx.rs                   # OKX credential/signature adapter
    trading/
      mod.rs                   # TradingApi trait for write-side order operations
  strategy/
    test-strategy/             # Binary crate (Rust 2021 edition) — depends on tradingkit
```

## Toolchain

- **Rust nightly** (1.94.0-nightly) — required for `edition = "2024"`.
- No `rust-toolchain.toml` — ensure nightly is active: `rustup default nightly`.
- No `rustfmt.toml`, `clippy.toml`, or `.cargo/config.toml`.

## Build / Lint / Test Commands

```bash
cargo build                          # Build entire workspace
cargo build -p tradingkit            # Build only the library
cargo build -p test-strategy         # Build only the test-strategy binary
cargo check                          # Check without codegen (fast)
cargo clippy -- -D warnings          # Lint
cargo fmt                            # Format
cargo fmt --check                    # CI-style format check
cargo test                           # Run all tests (unit + doc-tests)
cargo test -p tradingkit             # Run tests for a single crate
cargo test -p tradingkit <name>      # Run a single test by name substring
cargo test -p tradingkit <name> -- --nocapture  # Single test with stdout
cargo run -p test-strategy           # Run the test-strategy binary (needs .env)
```

## Dependencies

| Crate               | Purpose                               |
|----------------------|---------------------------------------|
| `base64` (0.22)     | Base64 encoding for signatures        |
| `dotenvy` (0.15)    | `.env` file loading for credentials   |
| `hmac` (0.12)       | HMAC message authentication           |
| `reqwest` (0.12)    | HTTP client (with `json` feature)     |
| `reqwest-websocket`  | WebSocket support via reqwest         |
| `serde` (1.0)       | Serialization/deserialization (derive) |
| `serde_json` (1.0)  | JSON parsing and generation           |
| `sha2` (0.10)       | SHA-256 hashing                       |
| `tokio` (dev-dep)   | Async runtime for tests               |

## Code Style

### Module Structure

- Crate root (`lib.rs`) contains **only `mod`/`pub mod` declarations** and `//!` crate docs.
- Directory modules use `mod.rs` files (not the flat `module_name.rs` style).
- Submodule declarations go at the **bottom** of `mod.rs`, after all type and trait definitions.

### Imports

Grouped in this order, separated by blank lines:

```rust
use super::{Credentials, Exchange, RequestHeaders};               // 1. Parent re-exports

use std::collections::HashMap;                                     // 2. Standard library
use std::error::Error;

use reqwest::{Client, Method};                                     // 3. External crates
use serde::{Deserialize, Serialize};

use crate::model::{Order, OrderRequest, TradingAccount};           // 4. Internal crate
use tradingkit::trading::TradingApi;                               // 5. Public trait imports
```

- Import specific items — no glob imports (exception: `use super::*;` in `#[cfg(test)]` modules).
- Alias long paths at import site when used repeatedly (e.g., `STANDARD as base64`).
- In newer modules (alpaca), prefer explicit `use super::{...}` over `use super::*`.

### Naming Conventions

| Element       | Convention           | Example                          |
|---------------|----------------------|----------------------------------|
| Functions     | `snake_case`         | `encode_base64`, `send_get`      |
| Variables     | `snake_case`         | `secret_key`, `base_url`         |
| Types/Structs | `PascalCase`         | `OKXCredentials`, `Alpaca`       |
| Traits        | `PascalCase`         | `TradingApi`, `Source`, `Store`  |
| Constants     | `SCREAMING_SNAKE`    | `ALPACA_PAPER_URL`               |
| Modules       | `snake_case`         | `exchange`, `crypto`, `data`     |
| Crates        | `kebab-case`         | `test-strategy`                  |
| Enums         | `PascalCase` variants| `OrderSide::Buy`, `AssetClass::UsOption` |

- Exchange-specific types are prefixed with the exchange name: `Alpaca*`, `OKX*`.
- Private exchange-specific request/response DTOs: `AlpacaOrderRequest`, `AlpacaOrderResponse`.
- Canonical (public) types live in `model.rs` without exchange prefix.

### Error Handling

- All fallible functions return `Result<T, Box<dyn Error>>`.
- Propagate errors with the `?` operator — no manual matching/unwrapping.
- For quick ad-hoc errors: `std::io::Error::other("message")` converted to `Box<dyn Error>`.
- No custom error types — `Box<dyn Error>` is the current convention throughout.

### Type Patterns

- Use `&str` and `&[u8]` for function parameters (idiomatic Rust borrowing).
- Struct fields own their data (`String`, not `&str`).
- Use field shorthand in constructors when variable name matches field name.
- Request DTOs use lifetime references (`AlpacaOrderRequest<'a>`) borrowing from canonical types.

### Serde Patterns

- Canonical domain types derive `Serialize, Deserialize` and live in `model.rs`.
- Exchange-specific DTOs are private; convert via `From`/`TryFrom` impls to canonical types.
- Enums use `#[serde(rename_all = "snake_case")]` or `"lowercase"` as the API requires.
- Unknown enum variants use `#[serde(other)]` catch-all: `Unknown`.
- Optional request fields use `#[serde(skip_serializing_if = "Option::is_none")]`.
- Response field name mapping via `#[serde(rename = "...")]` or `#[serde(alias = "...")]`.

### Async Trait Pattern

Async methods in traits use native `async fn` with `#[allow(async_fn_in_trait)]`:

```rust
#[allow(async_fn_in_trait)]
pub trait TradingApi {
    async fn submit_order(&self, order: &OrderRequest) -> Result<Order, Box<dyn Error>>;
}
```

### Formatting

- Default `rustfmt` settings (no config file). 4-space indentation.
- Trailing commas in multi-line struct literals and function args.
- Opening brace on same line as declaration.

### Documentation

- Module-level `//!` doc comments on all public modules.
- `///` doc comments on all public types, traits, and methods.
- Include `# Examples` sections with runnable doc-tests.
- Use `rust,no_run` for examples requiring network/env; `rust` for pure examples.

## Architecture Notes

### Trait Hierarchy

- `Credentials` — payload signing (`sign`).
- `RequestHeaders` — HTTP header construction for auth.
- `Exchange` — associated `Credentials` type + constructor.
- `TradingApi` — write-only trading surface in `trading/mod.rs` (submit, replace, cancel, cancel_all).
- `DataApi` — read-only exchange data surface in `data/mod.rs` (get_account, get_order, get_order_by_client_order_id, list_orders, get_option_chain).
- `Source<Q, T>` / `Store<Q, T>` — data-access read/write abstractions.
- `Repository<S, C>` — cache-first facade composing Source + Store.

### Adding a New Exchange

1. Create `src/exchange/<name>.rs`.
2. Define `<Name>Credentials` implementing `Credentials` (and `RequestHeaders` if needed).
3. Define `<Name>` struct implementing `Exchange`, `TradingApi`, and `DataApi`.
4. Add `Source<...>` impls in the exchange adapter file to integrate with the data layer.
5. Add `pub mod <name>;` at the bottom of `src/exchange/mod.rs`.

### Strategy Crates

Strategy binaries live under `strategy/` and are separate workspace members.
They depend on `tradingkit` via path: `tradingkit = { path = "../../" }`.
Add new strategies to `[workspace] members` in the root `Cargo.toml`.

## Testing Guidelines

- **Unit tests**: In-file `#[cfg(test)] mod tests { use super::*; ... }`.
- **Async tests**: `#[tokio::test]` (tokio is already a dev-dependency).
- **Doc-tests**: All public items have `# Examples` sections that are compiled as tests.
- **Test naming**: `test_<subject>_<action>_<expected_behavior>`.
- **Fakes**: Use trait-implementing test doubles (e.g., `CountingSource` in `data/mod.rs`).
- Run `cargo test` to execute all 9 unit tests + 24 doc-tests.

## Common Pitfalls

- The `base64` crate requires importing the `Engine` trait alongside the engine constant.
- `Hmac::new_from_slice` can fail — always propagate the error with `?`.
- `reqwest` and async traits require a tokio runtime — strategy binaries use `#[tokio::main]`.
- Workspace uses mixed editions (2024 root, 2021 test-strategy) — be mindful of differences.
- Credentials loaded from `.env` via `dotenvy::dotenv()` — the `.env` file is not committed.
- Private exchange DTOs exist alongside public canonical types — convert via `From`/`TryFrom`.