ig_client/lib.rs
1//! # IG Markets API Client for Rust
2//!
3//! A comprehensive Rust client for interacting with the IG Markets trading API. This library provides a type-safe and ergonomic way to access IG Markets' REST and WebSocket APIs for trading and market data retrieval.
4//!
5//! ## Overview
6//!
7//! The IG Markets API Client for Rust is designed to provide a reliable and efficient interface to the IG Markets trading platform. It handles authentication, session management, and all API interactions while providing a clean, idiomatic Rust interface for developers.
8//!
9//! ## Features
10//!
11//! - **Authentication**: Secure authentication with the IG Markets API including session refresh and account switching
12//! - **Account Management**: Access account information, balances, and activity history
13//! - **Market Data**: Retrieve market data, prices, instrument details, and historical prices
14//! - **Order Management**: Create, modify, and close positions and orders with various order types
15//! - **Working Orders**: Create and manage working orders with support for limit and stop orders
16//! - **Transaction History**: Access detailed transaction and activity history
17//! - **WebSocket Support**: Real-time market data streaming via WebSocket connections
18//! - **Advanced Rate Limiting**: Sophisticated rate limiting with automatic backoff, concurrent request management, and explicit rate limit error handling
19//! - **Fully Documented**: Comprehensive documentation for all components and methods
20//! - **Error Handling**: Robust error handling and reporting with detailed error types
21//! - **Type Safety**: Strong type checking for API requests and responses
22//! - **Async Support**: Built with async/await for efficient non-blocking operations
23//! - **Concurrency Management**: Built-in semaphores and thread-safe primitives for handling concurrent API requests
24//! - **Configurable**: Flexible configuration options for different environments (demo/live)
25//! - **Persistence**: Optional database integration for storing historical data
26//! - **Database Support**: Integration with SQLx for storing and retrieving transaction data
27//! - **Serialization Utilities**: Custom serialization helpers for handling IG Markets API responses
28//!
29//! ## Installation
30//!
31//! Add this to your `Cargo.toml`:
32//!
33//! ```toml
34//! [dependencies]
35//! ig-client = "0.1.21"
36//! tokio = { version = "1", features = ["full"] } # For async runtime
37//! dotenv = "0.15" # For environment variable loading
38//! tracing = "0.1" # For logging
39//! sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] } # Optional for database support
40//! ```
41//!
42//! ### Requirements
43//!
44//! - Rust 1.56 or later (for async/await support)
45//! - An IG Markets account (demo or live)
46//! - API credentials from IG Markets
47//! - PostgreSQL database (optional, for data persistence)
48//!
49//! ## Configuration
50//!
51//! Create a `.env` file in your project root with the following variables:
52//!
53//! ```text
54//! IG_USERNAME=your_username
55//! IG_PASSWORD=your_password
56//! IG_API_KEY=your_api_key
57//! IG_ACCOUNT_ID=your_account_id
58//! IG_BASE_URL=https://demo-api.ig.com/gateway/deal # Use demo or live as needed
59//! IG_TIMEOUT=30 # HTTP request timeout in seconds
60//! IG_WS_URL=wss://demo-apd.marketdatasystems.com # WebSocket URL
61//! IG_WS_RECONNECT=5 # WebSocket reconnect interval in seconds
62//! DATABASE_URL=postgres://user:password@localhost/ig_db # Optional for data persistence
63//! IG_DB_MAX_CONN=5 # Maximum database connections
64//! TX_LOOP_INTERVAL_HOURS=1 # Transaction loop interval in hours
65//! TX_PAGE_SIZE=20 # Transaction page size
66//! TX_DAYS_BACK=7 # Number of days to look back for transactions
67//! ```
68//!
69//! ## Usage Examples
70//!
71//! ### Complete Example Application
72//!
73//! Here's a complete example showing how to set up the client, authenticate, and perform various operations:
74//!
75//! ```rust,ignore
76//! use ig_client::application::services::account_service::{AccountService, IgAccountService};
77//! use ig_client::application::services::market_service::{IgMarketService, MarketService};
78//! use ig_client::application::services::order_service::{IgOrderService, OrderService};
79//! use ig_client::application::models::order::{CreateOrderRequest, Direction};
80//! use ig_client::config::Config;
81//! use ig_client::session::auth::{IgAuth, IgAuthenticator};
82//! use std::sync::Arc;
83//! use dotenv::dotenv;
84//! use tracing::{info, error, Level};
85//! use tracing_subscriber::FmtSubscriber;
86//!
87//! #[tokio::main]
88//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
89//! // Initialize logging
90//! let subscriber = FmtSubscriber::builder()
91//! .with_max_level(Level::INFO)
92//! .finish();
93//! tracing::subscriber::set_global_default(subscriber)?;
94//!
95//! // Load environment variables
96//! dotenv().ok();
97//! info!("Environment variables loaded");
98//!
99//! // Create configuration
100//! let config = Arc::new(Config::new());
101//! info!("Configuration created");
102//!
103//! // Authenticate
104//! let auth = IgAuth::new(&config);
105//! let session = auth.login().await?;
106//! info!("Authentication successful");
107//!
108//! // Create services
109//! let account_service = IgAccountService::new(config.clone());
110//! let market_service = IgMarketService::new(config.clone());
111//! // Get account information
112//! let account_info = account_service.get_accounts(&session).await?;
113//! info!("Account information retrieved: {} accounts", account_info.accounts.len());
114//!
115//! // Switch to a different account if needed
116//! if account_info.accounts.len() > 1 {
117//! let target_account = &account_info.accounts[1];
118//! let updated_session = auth.switch_account(&session, &target_account.account_id, Some(true)).await?;
119//! info!("Switched to account: {}", updated_session.account_id);
120//! }
121//!
122//! // Search for a market
123//! let search_term = "US 500";
124//! let search_results = market_service.search_markets(&session, search_term).await?;
125//! info!("Found {} markets matching '{}'", search_results.markets.len(), search_term);
126//!
127//! // Get market details for the first result
128//! if let Some(market) = search_results.markets.first() {
129//! let epic = &market.epic;
130//! let market_details = market_service.get_market_details(&session, epic).await?;
131//! info!("Market details for {}: {}", epic, market_details.instrument.name);
132//!
133//! // Get historical prices
134//! let prices = market_service.get_prices_by_date(
135//! &session,
136//! epic,
137//! "MINUTE",
138//! "1",
139//! "2023-01-01T00:00:00",
140//! "2023-01-02T00:00:00"
141//! ).await?;
142//! info!("Retrieved {} price points", prices.prices.len());
143//!
144//! // Check if the market is tradable
145//! if market_details.snapshot.market_status == "TRADEABLE" {
146//! // Create a market order
147//! let order_request = CreateOrderRequest::market(
148//! epic.clone(),
149//! Direction::Buy,
150//! 1.0, // Size
151//! );
152//!
153//! let order_result = order_service.create_order(&session, &order_request).await?;
154//! info!("Order placed: deal reference = {}", order_result.deal_reference);
155//!
156//! // Get positions
157//! let positions = account_service.get_positions(&session).await?;
158//! info!("Current positions: {}", positions.positions.len());
159//! }
160//! }
161//!
162//! info!("Example completed successfully");
163//! Ok(())
164//! }
165//! ```
166//!
167//! ### Authentication
168//!
169//! ```rust,ignore
170//! use ig_client::session::auth::IgAuth;
171//! use ig_client::config::Config;
172//! use std::sync::Arc;
173//!
174//! #[tokio::main]
175//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
176//! // Load configuration from environment variables
177//! let config = Arc::new(Config::new());
178//!
179//! // Create authentication handler
180//! let auth = IgAuth::new(config.clone());
181//!
182//! // Authenticate and get a session
183//! let session = auth.authenticate().await?;
184//!
185//! info!("Successfully authenticated!");
186//! Ok(())
187//! }
188//! ```
189//!
190//! ### Getting Account Information
191//!
192//! ```rust,ignore
193//! use ig_client::application::services::account_service::{AccountService, IgAccountService};
194//! use std::sync::Arc;
195//!
196//! // Create account service
197//! let account_service = IgAccountService::new(config.clone());
198//!
199//! // Get account information
200//! let account_info = account_service.get_accounts(&session).await?;
201//! info!("Accounts: {:?}", account_info);
202//!
203//! // Get positions
204//! let positions = account_service.get_positions(&session).await?;
205//! info!("Open positions: {}", positions.positions.len());
206//!
207//! // Get transaction history
208//! let from_date = chrono::Utc::now() - chrono::Duration::days(7);
209//! let to_date = chrono::Utc::now();
210//! let transactions = account_service.get_transactions(&session, from_date, to_date).await?;
211//! info!("Transactions in the last week: {}", transactions.transactions.len());
212//! ```
213//!
214//! ### Market Data
215//!
216//! ```rust,ignore
217//! use ig_client::application::services::market_service::{MarketService, IgMarketService};
218//!
219//! // Create market service
220//! let market_service = IgMarketService::new(config.clone());
221//!
222//! // Search for markets
223//! let search_result = market_service.search_markets(&session, "Apple").await?;
224//! info!("Found {} markets matching 'Apple'", search_result.markets.len());
225//!
226//! // Get market details
227//! if let Some(market) = search_result.markets.first() {
228//! let details = market_service.get_market_details(&session, &market.epic).await?;
229//! info!("Market details for {}: {}", market.instrument_name, details.instrument.name);
230//!
231//! // Get historical prices
232//! let prices = market_service.get_prices(
233//! &session,
234//! &market.epic,
235//! "DAY", // Resolution
236//! 30, // Number of data points
237//! ).await?;
238//! info!("Retrieved {} historical price points", prices.prices.len());
239//! }
240//! ```
241//!
242//! ### Placing and Managing Orders
243//!
244//! ```rust,ignore
245//! use ig_client::application::services::order_service::{OrderService, IgOrderService};
246//! use ig_client::application::models::order::{CreateOrderRequest, Direction, OrderType, TimeInForce};
247//!
248//! // Create order service
249//! let order_service = IgOrderService::new(config.clone());
250//!
251//! // Create a market order
252//! let market_order = CreateOrderRequest::market(
253//! "OP.D.OTCDAX1.021100P.IP".to_string(), // EPIC
254//! Direction::Buy, // Direction
255//! 1.0, // Size
256//! None, // Limit level
257//! None, // Stop level
258//! );
259//!
260//! // Place the order
261//! let result = order_service.create_order(&session, &market_order).await?;
262//! info!("Market order placed: {:?}", result);
263//!
264//! // Create a limit order
265//! let limit_order = CreateOrderRequest {
266//! epic: "OP.D.OTCDAX1.021100P.IP".to_string(),
267//! direction: Direction::Buy,
268//! size: 1.0,
269//! order_type: OrderType::Limit,
270//! level: Some(1.05), // Limit price
271//! guaranteed_stop: false,
272//! time_in_force: TimeInForce::GoodTillDate,
273//! good_till_date: Some("2025-06-01T12:00:00".to_string()),
274//! stop_level: None,
275//! stop_distance: None,
276//! limit_level: None,
277//! limit_distance: None,
278//! deal_reference: Some("my-custom-reference".to_string()),
279//! };
280//!
281//! let result = order_service.create_order(&session, &limit_order).await?;
282//! info!("Limit order placed: {:?}", result);
283//!
284//! // Close a position
285//! let positions = account_service.get_positions(&session).await?;
286//! if let Some(position) = positions.positions.first() {
287//! let close_request = order_service.close_position(
288//! &session,
289//! &position.position.deal_id,
290//! position.position.direction.clone(),
291//! position.position.size,
292//! ).await?;
293//! info!("Position closed: {:?}", close_request);
294//! }
295//! ```
296//!
297//! ### WebSocket Streaming
298//!
299//! ```rust,ignore
300//! use ig_client::application::services::market_listener::{MarketListener, MarketListenerCallback};
301//! use ig_client::application::models::market::MarketData;
302//! use std::sync::Arc;
303//! use tokio::sync::mpsc;
304//!
305//! // Create a channel to receive market updates
306//! let (tx, mut rx) = mpsc::channel(100);
307//!
308//! // Create a callback function to handle market updates
309//! let callback: MarketListenerCallback = Arc::new(move |market_data: &MarketData| {
310//! let data = market_data.clone();
311//! let tx = tx.clone();
312//! tokio::spawn(async move {
313//! let _ = tx.send(data).await;
314//! });
315//! Ok(())
316//! });
317//!
318//! // Create and start the market listener
319//! let listener = MarketListener::new(callback);
320//! listener.connect(&session).await?;
321//!
322//! // Subscribe to market updates
323//! let epics = vec!["OP.D.OTCDAX1.021100P.IP", "CS.D.USDJPY.CFD.IP"];
324//! listener.subscribe(&epics).await?;
325//!
326//! // Process market updates
327//! while let Some(market_data) = rx.recv().await {
328//! info!("Market update for {}: bid={}, offer={}",
329//! market_data.epic, market_data.bid.unwrap_or(0.0), market_data.offer.unwrap_or(0.0));
330//! }
331//! ```
332//!
333//! ## Documentation
334//!
335//! Comprehensive documentation is available for all components of the library. The documentation includes detailed explanations of all modules, structs, and functions, along with examples of how to use them.
336//!
337//! ### API Documentation
338//!
339//! You can access the API documentation on [docs.rs](https://docs.rs/ig-client) or generate it locally with:
340//!
341//! ```bash
342//! make doc-open
343//! ```
344//!
345//! ### Architecture
346//!
347//! The library is organized into several modules:
348//!
349//! - **config**: Configuration handling and environment variable loading
350//! - **session**: Authentication and session management
351//! - **application**: Core business logic and services
352//! - **models**: Data structures for API requests and responses
353//! - **services**: Service implementations for different API areas
354//! - **transport**: HTTP and WebSocket communication with the IG Markets API
355//! - **utils**: Utility functions for parsing, logging, etc.
356//! - **error**: Error types and handling
357//!
358//! ## Development
359//!
360//! This project includes a comprehensive Makefile with commands for common development tasks.
361//!
362//! ### Building
363//!
364//! ```bash
365//! make build # Debug build
366//! make release # Release build
367//! ```
368//!
369//! ### Testing
370//!
371//! ```bash
372//! make test # Run all tests
373//! ```
374//!
375//! ### Code Quality
376//!
377//! ```bash
378//! make fmt # Format code with rustfmt
379//! make lint # Run clippy linter
380//! make doc # Check documentation coverage
381//! make check # Run tests, format check, and linting
382//! make pre-push # Run all checks before pushing code
383//! ```
384//!
385//! ### Documentation
386//!
387//! ```bash
388//! make doc-open # Generate and open documentation
389//! ```
390//!
391//! ### Code Coverage
392//!
393//! ```bash
394//! make coverage # Generate code coverage report (XML)
395//! make coverage-html # Generate HTML coverage report
396//! make open-coverage # Open HTML coverage report
397//! ```
398//!
399//! ### Benchmarking
400//!
401//! ```bash
402//! make bench # Run benchmarks
403//! make bench-show # Show benchmark results
404//! ```
405//!
406//! ### Rate Limiting
407//!
408//! The library includes a sophisticated rate limiting system to comply with IG Markets API restrictions:
409//!
410//! - **Multiple Rate Limit Types**: Different limits for trading, non-trading, and historical data requests
411//! - **Thread-Safe Implementation**: Uses `tokio::sync::Mutex` for safe concurrent access
412//! - **Automatic Backoff**: Dynamically calculates wait times based on request history
413//! - **Explicit Rate Limit Handling**: Detects and handles rate limit errors from the API
414//! - **Global Semaphore**: Limits concurrent API requests to prevent overwhelming the API
415//! - **Configurable Safety Margins**: Adjustable safety margins to stay below API limits
416//! - **Rate Limit Error Recovery**: Automatic cooldown and recovery when rate limits are exceeded
417//!
418//! Example of configuring rate limits:
419//!
420//! ```rust,ignore
421//! // Create a configuration with custom rate limit settings
422//! let config = Arc::new(Config::with_rate_limit_type(
423//! RateLimitType::NonTradingAccount, // Type of rate limit to enforce
424//! 0.8, // Safety margin (80% of actual limit)
425//! ));
426//!
427//! // The rate limiter will automatically be used by all services
428//! let http_client = IgHttpClientImpl::new(config.clone());
429//! let auth = IgAuth::new(config.clone());
430//!
431//! // When rate limits are exceeded, the system will automatically:
432//! // 1. Detect the rate limit error from the API
433//! // 2. Enforce a mandatory cooldown period
434//! // 3. Gradually resume requests with appropriate delays
435//! ```
436//!
437//! ### Continuous Integration
438//!
439//! ```bash
440//! make workflow # Run all CI workflow steps locally
441//! ```
442//!
443//! ## Project Structure
444//!
445//! ```text
446//! ├── src/
447//! │ ├── application/ # Core business logic
448//! │ │ ├── models/ # Data models
449//! │ │ │ ├── account.rs # Account-related models
450//! │ │ │ ├── market.rs # Market data models
451//! │ │ │ ├── order.rs # Order models
452//! │ │ │ ├── transaction.rs # Transaction models
453//! │ │ │ └── working_order.rs # Working order models
454//! │ │ └── services/ # Service implementations
455//! │ │ ├── account_service.rs
456//! │ │ ├── interfaces/ # Service interfaces
457//! │ │ ├── listener.rs # Price listener service
458//! │ │ ├── market_service.rs
459//! │ │ └── order_service.rs
460//! │ ├── config.rs # Configuration handling
461//! │ ├── constants.rs # Global constants
462//! │ ├── error.rs # Error types
463//! │ ├── presentation/ # Presentation layer
464//! │ │ ├── account.rs # Account presentation
465//! │ │ ├── market.rs # Market presentation
466//! │ │ ├── serialization.rs # Serialization utilities
467//! │ │ └── trade.rs # Trade presentation
468//! │ ├── session/ # Authentication and session
469//! │ │ ├── auth.rs # Authentication handler
470//! │ │ └── interface.rs # Session interface
471//! │ ├── storage/ # Data persistence
472//! │ │ ├── config.rs # Database configuration
473//! │ │ └── utils.rs # Storage utilities
474//! │ ├── transport/ # API communication
475//! │ │ └── http_client.rs # HTTP client
476//! │ └── utils/ # Utility functions
477//! │ ├── display.rs # Display utilities
478//! │ ├── finance.rs # Financial calculations
479//! │ ├── logger.rs # Logging utilities
480//! │ ├── market_parser.rs # Market data parsing utilities
481//! │ ├── parsing.rs # Parsing utilities
482//! │ └── rate_limiter.rs # Advanced rate limiting with concurrency management
483//! ├── examples/ # Example applications
484//! ├── tests/ # Tests
485//! │ ├── integration/ # Integration tests
486//! │ └── unit/ # Unit tests
487//! └── Makefile # Development commands
488//! ```
489//!
490//! ## Contributing
491//!
492//! Contributions are welcome! Here's how you can contribute:
493//!
494//! 1. Fork the repository
495//! 2. Create a feature branch: `git checkout -b feature/my-feature`
496//! 3. Make your changes and commit them: `git commit -m 'Add some feature'`
497//! 4. Run the tests: `make test`
498//! 5. Push to the branch: `git push origin feature/my-feature`
499//! 6. Submit a pull request
500//!
501//! Please make sure your code passes all tests and linting checks before submitting a pull request.
502
503/// Configuration for the IG Markets API client
504pub mod config;
505
506/// Core application logic and services
507pub mod application;
508
509/// User interface and presentation layer
510pub mod presentation;
511
512/// Authentication and session management
513pub mod session;
514
515/// Network transport and API communication
516pub mod transport;
517
518/// Module containing global constants used throughout the library
519pub mod constants;
520
521/// Error types and handling
522pub mod error;
523
524/// Data persistence and storage
525pub mod storage;
526
527/// Utility functions and helpers
528pub mod utils;
529
530/// Library version
531pub const VERSION: &str = env!("CARGO_PKG_VERSION");
532
533/// Returns the library version
534pub fn version() -> &'static str {
535 VERSION
536}