Skip to main content

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