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}