diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0887ec1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +env/ +ENV/ + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Testing +.pytest_cache/ +.coverage +htmlcov/ + +# Data files +*.csv +*.db +*.sqlite +data/ + +# Logs +*.log + +# Environment +.env diff --git a/README.md b/README.md index b5826e1..d129b61 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,309 @@ -# Institutional-Microstructure- -My liberty of Code to my Scripts 🗽 +# Institutional Microstructure Toolkit 🗽 + +A comprehensive Python toolkit for institutional trading and market microstructure analysis. This library provides tools for market data analysis, order book management, trade execution, microstructure metrics, technical indicators, and risk management. + +## Features + +### 📊 Market Data +- Real-time and historical market data fetching +- Tick-level data support +- Order book snapshots +- Multiple data source support (simulated, with extensibility for real APIs) + +### 📈 Order Book Analysis +- Limit order book representation and manipulation +- Bid-ask spread calculations +- Market depth analysis +- Order book imbalance metrics +- Market impact estimation +- VWAP calculations + +### 🔄 Trade Execution +- Order creation and management (Market, Limit, Stop orders) +- Order lifecycle tracking +- Position calculation +- Fill management +- Time-in-force support (GTC, DAY, IOC, FOK) + +### 🔬 Market Microstructure Metrics +- Quoted and relative spread +- Effective and realized spread +- Price impact measures +- Roll's measure +- Amihud illiquidity ratio +- Kyle's lambda +- PIN (Probability of Informed Trading) +- Market depth metrics +- Volatility measures + +### 📉 Technical Indicators +- Moving Averages (SMA, EMA) +- RSI (Relative Strength Index) +- MACD (Moving Average Convergence Divergence) +- Bollinger Bands +- ATR (Average True Range) +- Stochastic Oscillator +- ADX (Average Directional Index) +- OBV (On-Balance Volume) +- VWAP (Volume Weighted Average Price) +- CCI (Commodity Channel Index) +- Momentum and Rate of Change + +### ⚠️ Risk Management +- Value at Risk (VaR) +- Conditional VaR (CVaR/Expected Shortfall) +- Sharpe Ratio +- Sortino Ratio +- Maximum Drawdown +- Position sizing algorithms +- Kelly Criterion +- Beta calculation +- Portfolio risk metrics + +## Installation + +```bash +# Clone the repository +git clone https://github.com/Netrade1/Institutional-Microstructure-.git +cd Institutional-Microstructure- + +# Install dependencies +pip install -r requirements.txt + +# Or install in development mode +pip install -e . +``` + +## Requirements + +- Python 3.8+ +- numpy >= 1.24.0 +- pandas >= 2.0.0 +- requests >= 2.31.0 +- websocket-client >= 1.6.0 +- python-dateutil >= 2.8.2 +- pytz >= 2023.3 + +## Quick Start + +### Market Data Example + +```python +from src.market_data.data_fetcher import MarketDataFetcher +from datetime import datetime, timedelta + +# Initialize market data fetcher +fetcher = MarketDataFetcher(data_source="simulated") + +# Get real-time quote +quote = fetcher.get_quote("AAPL") +print(f"Bid: ${quote['bid']:.2f}, Ask: ${quote['ask']:.2f}") + +# Get historical data +end_date = datetime.now() +start_date = end_date - timedelta(days=30) +historical = fetcher.get_historical_data("AAPL", start_date, end_date) +print(historical.head()) +``` + +### Order Book Analysis + +```python +from src.market_data.data_fetcher import MarketDataFetcher +from src.order_book.order_book import OrderBook + +# Fetch order book data +fetcher = MarketDataFetcher() +ob_data = fetcher.get_order_book_snapshot("MSFT", depth=10) + +# Analyze order book +order_book = OrderBook("MSFT") +order_book.update(ob_data['bids'], ob_data['asks']) + +print(f"Mid Price: ${order_book.get_mid_price():.2f}") +print(f"Spread: ${order_book.get_spread():.4f}") +print(f"Imbalance: {order_book.get_imbalance():.4f}") +``` + +### Order Management + +```python +from src.execution.order_manager import OrderManager + +# Initialize order manager +order_manager = OrderManager() + +# Create a limit order +order = order_manager.create_order( + symbol="AAPL", + side="buy", + quantity=100, + order_type="limit", + price=150.50 +) + +# Submit the order +order_manager.submit_order(order.order_id) + +# Check position +position = order_manager.calculate_position("AAPL") +print(f"Position: {position['quantity']} @ ${position['average_price']:.2f}") +``` + +### Technical Indicators + +```python +from src.indicators.technical_indicators import TechnicalIndicators +import pandas as pd + +indicators = TechnicalIndicators() + +# Calculate RSI +rsi = indicators.rsi(price_series, period=14) + +# Calculate MACD +macd_line, signal_line, histogram = indicators.macd(price_series) + +# Calculate Bollinger Bands +upper, middle, lower = indicators.bollinger_bands(price_series) +``` + +### Risk Management + +```python +from src.risk_management.risk_calculator import RiskCalculator + +risk_calc = RiskCalculator() + +# Calculate VaR +var_95 = risk_calc.calculate_var(returns, confidence_level=0.95) + +# Calculate Sharpe Ratio +sharpe = risk_calc.calculate_sharpe_ratio(returns) + +# Position sizing +position_size = risk_calc.calculate_position_size( + account_value=100000, + risk_per_trade=0.02, + entry_price=150.00, + stop_loss_price=145.00 +) +``` + +## Examples + +The `examples/` directory contains comprehensive examples: + +- `example_market_data.py` - Market data fetching and analysis +- `example_order_book.py` - Order book analysis and microstructure metrics +- `example_order_management.py` - Order creation and management +- `example_technical_indicators.py` - Technical indicator calculations +- `example_risk_management.py` - Risk metrics and position sizing + +Run examples: + +```bash +cd examples +python example_market_data.py +python example_order_book.py +python example_order_management.py +python example_technical_indicators.py +python example_risk_management.py +``` + +## Project Structure + +``` +Institutional-Microstructure-/ +├── src/ +│ ├── market_data/ # Market data fetching +│ │ └── data_fetcher.py +│ ├── order_book/ # Order book analysis +│ │ └── order_book.py +│ ├── execution/ # Order management +│ │ └── order_manager.py +│ ├── microstructure/ # Microstructure metrics +│ │ └── metrics.py +│ ├── indicators/ # Technical indicators +│ │ └── technical_indicators.py +│ ├── risk_management/ # Risk calculations +│ │ └── risk_calculator.py +│ └── utils/ # Utility functions +│ └── config.py +├── examples/ # Example scripts +├── requirements.txt # Dependencies +├── setup.py # Package setup +└── README.md # This file +``` + +## Configuration + +The toolkit supports configuration through the `ConfigManager` class: + +```python +from src.utils.config import ConfigManager + +config = ConfigManager() +config.set('market_data.source', 'simulated') +config.set('risk.max_position_size', 100000) +config.save() +``` + +## Use Cases + +This toolkit is designed for: + +- **Quantitative Researchers**: Analyze market microstructure and develop trading strategies +- **Algorithmic Traders**: Build and test trading algorithms with realistic market data +- **Risk Managers**: Calculate risk metrics and monitor portfolio risk +- **Market Makers**: Analyze order books and optimize quote placement +- **Academic Research**: Study market microstructure phenomena +- **Portfolio Managers**: Track positions and calculate performance metrics + +## Extending the Toolkit + +### Adding New Data Sources + +Extend the `MarketDataFetcher` class to support additional data sources: + +```python +class CustomDataFetcher(MarketDataFetcher): + def __init__(self, api_key): + super().__init__(api_key=api_key, data_source="custom") + + def get_quote(self, symbol): + # Implement custom data source logic + pass +``` + +### Custom Indicators + +Add custom technical indicators by extending the `TechnicalIndicators` class: + +```python +class CustomIndicators(TechnicalIndicators): + @staticmethod + def my_custom_indicator(prices, period): + # Implement custom indicator logic + pass +``` + +## Contributing + +Contributions are welcome! Please feel free to submit issues, fork the repository, and create pull requests. + +## License + +This project is open source and available under the MIT License. + +## Disclaimer + +This toolkit is for educational and research purposes. Always perform thorough testing before using in production trading environments. Past performance does not guarantee future results. + +## Author + +**Netrade1** + +## Acknowledgments + +Built with passion for quantitative finance and market microstructure analysis. 🗽 diff --git a/examples/example_market_data.py b/examples/example_market_data.py new file mode 100644 index 0000000..b4b531a --- /dev/null +++ b/examples/example_market_data.py @@ -0,0 +1,64 @@ +""" +Example: Market Data Fetching +Demonstrates how to fetch and analyze market data +""" + +import sys +import os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from src.market_data.data_fetcher import MarketDataFetcher +from datetime import datetime, timedelta +import pandas as pd + + +def main(): + print("=" * 60) + print("Market Data Fetching Example") + print("=" * 60) + + # Initialize market data fetcher + fetcher = MarketDataFetcher(data_source="simulated") + + # Example 1: Get real-time quote + print("\n1. Real-time Quote:") + print("-" * 40) + quote = fetcher.get_quote("AAPL") + print(f"Symbol: {quote['symbol']}") + print(f"Bid: ${quote['bid']:.2f} x {quote['bid_size']}") + print(f"Ask: ${quote['ask']:.2f} x {quote['ask_size']}") + print(f"Last: ${quote['last']:.2f}") + print(f"Volume: {quote['volume']:,}") + + # Example 2: Get historical data + print("\n2. Historical Data (Last 30 days):") + print("-" * 40) + end_date = datetime.now() + start_date = end_date - timedelta(days=30) + + historical = fetcher.get_historical_data("AAPL", start_date, end_date, interval='1D') + print(historical.head()) + print(f"\nTotal rows: {len(historical)}") + + # Example 3: Get tick data + print("\n3. Tick Data (Last 10 ticks):") + print("-" * 40) + ticks = fetcher.get_tick_data("AAPL", num_ticks=10) + print(ticks) + + # Example 4: Get order book snapshot + print("\n4. Order Book Snapshot:") + print("-" * 40) + order_book = fetcher.get_order_book_snapshot("AAPL", depth=5) + print(f"Symbol: {order_book['symbol']}") + print(f"Timestamp: {order_book['timestamp']}") + print("\nBids:") + for price, size in order_book['bids']: + print(f" ${price:.2f} x {size}") + print("\nAsks:") + for price, size in order_book['asks']: + print(f" ${price:.2f} x {size}") + + +if __name__ == "__main__": + main() diff --git a/examples/example_order_book.py b/examples/example_order_book.py new file mode 100644 index 0000000..da572ac --- /dev/null +++ b/examples/example_order_book.py @@ -0,0 +1,90 @@ +""" +Example: Order Book Analysis +Demonstrates order book analysis and microstructure metrics +""" + +import sys +import os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from src.market_data.data_fetcher import MarketDataFetcher +from src.order_book.order_book import OrderBook +from src.microstructure.metrics import MicrostructureMetrics + + +def main(): + print("=" * 60) + print("Order Book Analysis Example") + print("=" * 60) + + # Fetch order book data + fetcher = MarketDataFetcher(data_source="simulated") + ob_data = fetcher.get_order_book_snapshot("MSFT", depth=10) + + # Create order book object + order_book = OrderBook("MSFT") + order_book.update(ob_data['bids'], ob_data['asks']) + + # Example 1: Basic order book metrics + print("\n1. Basic Order Book Metrics:") + print("-" * 40) + best_bid = order_book.get_best_bid() + best_ask = order_book.get_best_ask() + print(f"Best Bid: ${best_bid[0]:.2f} x {best_bid[1]}") + print(f"Best Ask: ${best_ask[0]:.2f} x {best_ask[1]}") + print(f"Mid Price: ${order_book.get_mid_price():.2f}") + print(f"Spread: ${order_book.get_spread():.4f}") + print(f"Relative Spread: {order_book.get_relative_spread():.4f}%") + + # Example 2: Market depth + print("\n2. Market Depth:") + print("-" * 40) + bid_depth = order_book.get_depth('bid', levels=5) + ask_depth = order_book.get_depth('ask', levels=5) + print(f"Bid Depth (5 levels): {bid_depth:,}") + print(f"Ask Depth (5 levels): {ask_depth:,}") + print(f"Order Book Imbalance: {order_book.get_imbalance(5):.4f}") + print(f"Weighted Mid Price: ${order_book.get_weighted_mid_price(5):.2f}") + + # Example 3: Market impact estimation + print("\n3. Market Impact Estimation:") + print("-" * 40) + volume = 1000 + buy_vwap = order_book.calculate_vwap('buy', volume) + sell_vwap = order_book.calculate_vwap('sell', volume) + buy_impact = order_book.get_market_impact('buy', volume) + sell_impact = order_book.get_market_impact('sell', volume) + + print(f"For {volume} shares:") + print(f" Buy VWAP: ${buy_vwap:.2f}") + print(f" Buy Impact: {buy_impact:.2f} bps") + print(f" Sell VWAP: ${sell_vwap:.2f}") + print(f" Sell Impact: {sell_impact:.2f} bps") + + # Example 4: Microstructure metrics + print("\n4. Microstructure Metrics:") + print("-" * 40) + metrics = MicrostructureMetrics() + quoted_spread = metrics.calculate_quoted_spread(best_bid[0], best_ask[0]) + relative_spread = metrics.calculate_relative_spread(best_bid[0], best_ask[0]) + + print(f"Quoted Spread: ${quoted_spread:.4f}") + print(f"Relative Spread: {relative_spread:.4f}%") + + # Calculate depth metrics + depth_metrics = metrics.calculate_market_depth( + bid_depth, ask_depth, order_book.get_mid_price() + ) + print(f"Total Depth: {depth_metrics['total_depth']:,}") + print(f"Depth Imbalance: {depth_metrics['depth_imbalance']:.4f}") + print(f"Dollar Depth: ${depth_metrics['dollar_depth']:,.2f}") + + # Example 5: Order book DataFrame + print("\n5. Order Book DataFrame:") + print("-" * 40) + df = order_book.to_dataframe() + print(df.head()) + + +if __name__ == "__main__": + main() diff --git a/examples/example_order_management.py b/examples/example_order_management.py new file mode 100644 index 0000000..69ea484 --- /dev/null +++ b/examples/example_order_management.py @@ -0,0 +1,97 @@ +""" +Example: Order Management +Demonstrates order creation and management +""" + +import sys +import os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from src.execution.order_manager import OrderManager, OrderType, OrderSide + + +def main(): + print("=" * 60) + print("Order Management Example") + print("=" * 60) + + # Initialize order manager + order_manager = OrderManager() + + # Example 1: Create market orders + print("\n1. Creating Market Orders:") + print("-" * 40) + buy_order = order_manager.create_order( + symbol="AAPL", + side="buy", + quantity=100, + order_type="market" + ) + print(f"Created: {buy_order}") + + sell_order = order_manager.create_order( + symbol="AAPL", + side="sell", + quantity=50, + order_type="market" + ) + print(f"Created: {sell_order}") + + # Example 2: Create limit orders + print("\n2. Creating Limit Orders:") + print("-" * 40) + limit_order = order_manager.create_order( + symbol="MSFT", + side="buy", + quantity=200, + order_type="limit", + price=350.50 + ) + print(f"Created: {limit_order}") + + # Example 3: Submit orders + print("\n3. Submitting Orders:") + print("-" * 40) + order_manager.submit_order(buy_order.order_id) + print(f"Submitted order: {buy_order.order_id[:8]}") + + # Simulate fills + buy_order.add_fill(50, 150.25) + buy_order.add_fill(50, 150.30) + print(f"Order filled: {buy_order.filled_quantity}/{buy_order.quantity} @ avg ${buy_order.average_fill_price:.2f}") + + # Example 4: View active orders + print("\n4. Active Orders:") + print("-" * 40) + active = order_manager.get_active_orders() + print(f"Total active orders: {len(active)}") + for order in active: + print(f" {order}") + + # Example 5: Cancel an order + print("\n5. Cancelling Order:") + print("-" * 40) + cancelled = order_manager.cancel_order(limit_order.order_id) + if cancelled: + print(f"Cancelled order: {limit_order.order_id[:8]}") + + # Example 6: Calculate position + print("\n6. Position Calculation:") + print("-" * 40) + position = order_manager.calculate_position("AAPL") + print(f"Symbol: {position['symbol']}") + print(f"Quantity: {position['quantity']}") + print(f"Average Price: ${position['average_price']:.2f}") + print(f"Total Cost: ${position['total_cost']:.2f}") + + # Example 7: Order history + print("\n7. Order History:") + print("-" * 40) + history = order_manager.get_order_history("AAPL") + print(f"Total orders for AAPL: {len(history)}") + for order in history: + print(f" {order}") + + +if __name__ == "__main__": + main() diff --git a/examples/example_risk_management.py b/examples/example_risk_management.py new file mode 100644 index 0000000..3a1534b --- /dev/null +++ b/examples/example_risk_management.py @@ -0,0 +1,108 @@ +""" +Example: Risk Management +Demonstrates risk calculation and position sizing +""" + +import sys +import os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from src.market_data.data_fetcher import MarketDataFetcher +from src.risk_management.risk_calculator import RiskCalculator +from datetime import datetime, timedelta + + +def main(): + print("=" * 60) + print("Risk Management Example") + print("=" * 60) + + # Fetch historical data + fetcher = MarketDataFetcher(data_source="simulated") + end_date = datetime.now() + start_date = end_date - timedelta(days=365) + + data = fetcher.get_historical_data("AAPL", start_date, end_date, interval='1D') + returns = data['close'].pct_change().dropna() + + risk_calc = RiskCalculator() + + # Example 1: Value at Risk (VaR) + print("\n1. Value at Risk (VaR):") + print("-" * 40) + var_95 = risk_calc.calculate_var(returns, 0.95) + var_99 = risk_calc.calculate_var(returns, 0.99) + print(f"VaR (95%): {var_95*100:.2f}%") + print(f"VaR (99%): {var_99*100:.2f}%") + + # Example 2: Conditional VaR (CVaR) + print("\n2. Conditional VaR (CVaR):") + print("-" * 40) + cvar_95 = risk_calc.calculate_cvar(returns, 0.95) + print(f"CVaR (95%): {cvar_95*100:.2f}%") + + # Example 3: Sharpe Ratio + print("\n3. Sharpe Ratio:") + print("-" * 40) + sharpe = risk_calc.calculate_sharpe_ratio(returns) + print(f"Sharpe Ratio: {sharpe:.4f}") + + # Example 4: Sortino Ratio + print("\n4. Sortino Ratio:") + print("-" * 40) + sortino = risk_calc.calculate_sortino_ratio(returns) + print(f"Sortino Ratio: {sortino:.4f}") + + # Example 5: Maximum Drawdown + print("\n5. Maximum Drawdown:") + print("-" * 40) + drawdown_info = risk_calc.calculate_max_drawdown(data['close']) + print(f"Max Drawdown: {drawdown_info['max_drawdown_pct']:.2f}%") + print(f"Peak Date: {drawdown_info['peak_date']}") + print(f"Trough Date: {drawdown_info['trough_date']}") + + # Example 6: Position Sizing + print("\n6. Position Sizing:") + print("-" * 40) + account_value = 100000 + risk_per_trade = 0.02 # 2% + entry_price = 150.00 + stop_loss_price = 145.00 + + position_size = risk_calc.calculate_position_size( + account_value, risk_per_trade, entry_price, stop_loss_price + ) + print(f"Account Value: ${account_value:,}") + print(f"Risk per Trade: {risk_per_trade*100}%") + print(f"Entry Price: ${entry_price:.2f}") + print(f"Stop Loss: ${stop_loss_price:.2f}") + print(f"Position Size: {position_size} shares") + print(f"Position Value: ${position_size * entry_price:,.2f}") + + # Example 7: Kelly Criterion + print("\n7. Kelly Criterion:") + print("-" * 40) + win_rate = 0.55 + avg_win = 0.03 + avg_loss = 0.02 + + kelly = risk_calc.calculate_kelly_criterion(win_rate, avg_win, avg_loss) + print(f"Win Rate: {win_rate*100}%") + print(f"Average Win: {avg_win*100}%") + print(f"Average Loss: {avg_loss*100}%") + print(f"Kelly %: {kelly*100:.2f}%") + print(f"Half Kelly (recommended): {kelly*50:.2f}%") + + # Example 8: Risk-Adjusted Returns + print("\n8. Risk-Adjusted Return Metrics:") + print("-" * 40) + metrics = risk_calc.calculate_risk_adjusted_return(returns) + print(f"Total Return: {metrics['total_return']*100:.2f}%") + print(f"Annualized Return: {metrics['annualized_return']*100:.2f}%") + print(f"Sharpe Ratio: {metrics['sharpe_ratio']:.4f}") + print(f"Sortino Ratio: {metrics['sortino_ratio']:.4f}") + print(f"Volatility: {metrics['volatility']*100:.2f}%") + + +if __name__ == "__main__": + main() diff --git a/examples/example_technical_indicators.py b/examples/example_technical_indicators.py new file mode 100644 index 0000000..721d3c3 --- /dev/null +++ b/examples/example_technical_indicators.py @@ -0,0 +1,100 @@ +""" +Example: Technical Indicators +Demonstrates technical indicator calculations +""" + +import sys +import os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from src.market_data.data_fetcher import MarketDataFetcher +from src.indicators.technical_indicators import TechnicalIndicators +from datetime import datetime, timedelta + + +def main(): + print("=" * 60) + print("Technical Indicators Example") + print("=" * 60) + + # Fetch historical data + fetcher = MarketDataFetcher(data_source="simulated") + end_date = datetime.now() + start_date = end_date - timedelta(days=90) + + data = fetcher.get_historical_data("AAPL", start_date, end_date, interval='1D') + + indicators = TechnicalIndicators() + + # Example 1: Moving Averages + print("\n1. Moving Averages:") + print("-" * 40) + sma_20 = indicators.sma(data['close'], 20) + ema_20 = indicators.ema(data['close'], 20) + print(f"Latest Close: ${data['close'].iloc[-1]:.2f}") + print(f"SMA(20): ${sma_20.iloc[-1]:.2f}") + print(f"EMA(20): ${ema_20.iloc[-1]:.2f}") + + # Example 2: RSI + print("\n2. Relative Strength Index (RSI):") + print("-" * 40) + rsi = indicators.rsi(data['close'], 14) + latest_rsi = rsi.iloc[-1] + print(f"RSI(14): {latest_rsi:.2f}") + if latest_rsi > 70: + print("Signal: Overbought") + elif latest_rsi < 30: + print("Signal: Oversold") + else: + print("Signal: Neutral") + + # Example 3: MACD + print("\n3. MACD:") + print("-" * 40) + macd_line, signal_line, histogram = indicators.macd(data['close']) + print(f"MACD Line: {macd_line.iloc[-1]:.4f}") + print(f"Signal Line: {signal_line.iloc[-1]:.4f}") + print(f"Histogram: {histogram.iloc[-1]:.4f}") + if histogram.iloc[-1] > 0: + print("Signal: Bullish") + else: + print("Signal: Bearish") + + # Example 4: Bollinger Bands + print("\n4. Bollinger Bands:") + print("-" * 40) + upper, middle, lower = indicators.bollinger_bands(data['close'], 20, 2.0) + print(f"Upper Band: ${upper.iloc[-1]:.2f}") + print(f"Middle Band: ${middle.iloc[-1]:.2f}") + print(f"Lower Band: ${lower.iloc[-1]:.2f}") + print(f"Current Price: ${data['close'].iloc[-1]:.2f}") + + # Example 5: ATR + print("\n5. Average True Range (ATR):") + print("-" * 40) + atr = indicators.atr(data['high'], data['low'], data['close'], 14) + print(f"ATR(14): ${atr.iloc[-1]:.2f}") + + # Example 6: Stochastic + print("\n6. Stochastic Oscillator:") + print("-" * 40) + k, d = indicators.stochastic(data['high'], data['low'], data['close']) + print(f"%K: {k.iloc[-1]:.2f}") + print(f"%D: {d.iloc[-1]:.2f}") + + # Example 7: OBV + print("\n7. On-Balance Volume (OBV):") + print("-" * 40) + obv = indicators.obv(data['close'], data['volume']) + print(f"OBV: {obv.iloc[-1]:,.0f}") + + # Example 8: VWAP + print("\n8. Volume Weighted Average Price (VWAP):") + print("-" * 40) + vwap = indicators.vwap(data['high'], data['low'], data['close'], data['volume']) + print(f"VWAP: ${vwap.iloc[-1]:.2f}") + print(f"Current Price: ${data['close'].iloc[-1]:.2f}") + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..090dac4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +numpy>=1.24.0 +pandas>=2.0.0 +requests>=2.31.0 +websocket-client>=1.6.0 +python-dateutil>=2.8.2 +pytz>=2023.3 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..eafb2a0 --- /dev/null +++ b/setup.py @@ -0,0 +1,29 @@ +from setuptools import setup, find_packages + +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setup( + name="institutional-microstructure", + version="0.1.0", + author="Netrade1", + description="A toolkit for institutional trading and market microstructure analysis", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/Netrade1/Institutional-Microstructure-", + packages=find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires=">=3.8", + install_requires=[ + "numpy>=1.24.0", + "pandas>=2.0.0", + "requests>=2.31.0", + "websocket-client>=1.6.0", + "python-dateutil>=2.8.2", + "pytz>=2023.3", + ], +) diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..b6aa875 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,23 @@ +""" +Institutional Microstructure Toolkit +A comprehensive toolkit for institutional trading and market microstructure analysis +""" + +__version__ = "0.1.0" +__author__ = "Netrade1" + +from .market_data.data_fetcher import MarketDataFetcher +from .order_book.order_book import OrderBook +from .execution.order_manager import OrderManager +from .microstructure.metrics import MicrostructureMetrics +from .indicators.technical_indicators import TechnicalIndicators +from .risk_management.risk_calculator import RiskCalculator + +__all__ = [ + 'MarketDataFetcher', + 'OrderBook', + 'OrderManager', + 'MicrostructureMetrics', + 'TechnicalIndicators', + 'RiskCalculator', +] diff --git a/src/execution/__init__.py b/src/execution/__init__.py new file mode 100644 index 0000000..ca02d8e --- /dev/null +++ b/src/execution/__init__.py @@ -0,0 +1 @@ +"""Trade execution and order management module""" diff --git a/src/execution/order_manager.py b/src/execution/order_manager.py new file mode 100644 index 0000000..89f6dea --- /dev/null +++ b/src/execution/order_manager.py @@ -0,0 +1,294 @@ +""" +Order Manager +Manages order creation, execution, and tracking +""" + +import uuid +from datetime import datetime +from typing import Dict, List, Optional +from enum import Enum + + +class OrderType(Enum): + """Order type enumeration""" + MARKET = "market" + LIMIT = "limit" + STOP = "stop" + STOP_LIMIT = "stop_limit" + + +class OrderSide(Enum): + """Order side enumeration""" + BUY = "buy" + SELL = "sell" + + +class OrderStatus(Enum): + """Order status enumeration""" + PENDING = "pending" + SUBMITTED = "submitted" + PARTIAL = "partial" + FILLED = "filled" + CANCELLED = "cancelled" + REJECTED = "rejected" + + +class Order: + """Represents a trading order""" + + def __init__( + self, + symbol: str, + side: OrderSide, + quantity: int, + order_type: OrderType, + price: Optional[float] = None, + stop_price: Optional[float] = None, + time_in_force: str = "GTC" + ): + """ + Initialize an order. + + Args: + symbol: Trading symbol + side: Order side (BUY or SELL) + quantity: Order quantity + order_type: Order type (MARKET, LIMIT, etc.) + price: Limit price (for limit orders) + stop_price: Stop price (for stop orders) + time_in_force: Time in force ('GTC', 'DAY', 'IOC', 'FOK') + """ + self.order_id = str(uuid.uuid4()) + self.symbol = symbol + self.side = side + self.quantity = quantity + self.order_type = order_type + self.price = price + self.stop_price = stop_price + self.time_in_force = time_in_force + self.status = OrderStatus.PENDING + self.filled_quantity = 0 + self.average_fill_price = 0.0 + self.created_at = datetime.now() + self.updated_at = datetime.now() + self.fills: List[Dict] = [] + + def add_fill(self, quantity: int, price: float): + """ + Add a fill to the order. + + Args: + quantity: Filled quantity + price: Fill price + """ + fill = { + 'timestamp': datetime.now(), + 'quantity': quantity, + 'price': price + } + self.fills.append(fill) + + # Update filled quantity and average price + total_value = self.average_fill_price * self.filled_quantity + price * quantity + self.filled_quantity += quantity + self.average_fill_price = total_value / self.filled_quantity + + # Update status + if self.filled_quantity >= self.quantity: + self.status = OrderStatus.FILLED + elif self.filled_quantity > 0: + self.status = OrderStatus.PARTIAL + + self.updated_at = datetime.now() + + def to_dict(self) -> Dict: + """Convert order to dictionary""" + return { + 'order_id': self.order_id, + 'symbol': self.symbol, + 'side': self.side.value, + 'quantity': self.quantity, + 'order_type': self.order_type.value, + 'price': self.price, + 'stop_price': self.stop_price, + 'time_in_force': self.time_in_force, + 'status': self.status.value, + 'filled_quantity': self.filled_quantity, + 'average_fill_price': self.average_fill_price, + 'created_at': self.created_at.isoformat(), + 'updated_at': self.updated_at.isoformat(), + 'fills': self.fills + } + + def __repr__(self) -> str: + return (f"Order(id={self.order_id[:8]}, {self.side.value} {self.quantity} " + f"{self.symbol} @ {self.price or 'MKT'}, status={self.status.value})") + + +class OrderManager: + """ + Manages order lifecycle and execution. + """ + + def __init__(self): + """Initialize the order manager""" + self.orders: Dict[str, Order] = {} + self.active_orders: Dict[str, Order] = {} + + def create_order( + self, + symbol: str, + side: str, + quantity: int, + order_type: str = "market", + price: Optional[float] = None, + stop_price: Optional[float] = None, + time_in_force: str = "GTC" + ) -> Order: + """ + Create a new order. + + Args: + symbol: Trading symbol + side: Order side ('buy' or 'sell') + quantity: Order quantity + order_type: Order type ('market', 'limit', 'stop', 'stop_limit') + price: Limit price + stop_price: Stop price + time_in_force: Time in force + + Returns: + Created Order object + """ + # Convert string inputs to enums + side_enum = OrderSide.BUY if side.lower() == 'buy' else OrderSide.SELL + + try: + type_enum = OrderType[order_type.upper()] + except KeyError: + valid_types = [t.value for t in OrderType] + raise ValueError(f"Invalid order type '{order_type}'. Valid types: {valid_types}") + + order = Order( + symbol=symbol, + side=side_enum, + quantity=quantity, + order_type=type_enum, + price=price, + stop_price=stop_price, + time_in_force=time_in_force + ) + + self.orders[order.order_id] = order + self.active_orders[order.order_id] = order + + return order + + def submit_order(self, order_id: str) -> bool: + """ + Submit an order for execution. + + Args: + order_id: Order ID + + Returns: + True if submitted successfully + """ + if order_id in self.orders: + order = self.orders[order_id] + order.status = OrderStatus.SUBMITTED + order.updated_at = datetime.now() + return True + raise ValueError(f"Order not found: {order_id}") + + def cancel_order(self, order_id: str) -> bool: + """ + Cancel an active order. + + Args: + order_id: Order ID + + Returns: + True if cancelled successfully + """ + if order_id in self.active_orders: + order = self.active_orders[order_id] + order.status = OrderStatus.CANCELLED + order.updated_at = datetime.now() + del self.active_orders[order_id] + return True + return False + + def get_order(self, order_id: str) -> Optional[Order]: + """ + Get an order by ID. + + Args: + order_id: Order ID + + Returns: + Order object or None + """ + return self.orders.get(order_id) + + def get_active_orders(self, symbol: Optional[str] = None) -> List[Order]: + """ + Get all active orders, optionally filtered by symbol. + + Args: + symbol: Optional symbol filter + + Returns: + List of active orders + """ + if symbol: + return [order for order in self.active_orders.values() + if order.symbol == symbol] + return list(self.active_orders.values()) + + def get_order_history(self, symbol: Optional[str] = None) -> List[Order]: + """ + Get order history, optionally filtered by symbol. + + Args: + symbol: Optional symbol filter + + Returns: + List of all orders + """ + if symbol: + return [order for order in self.orders.values() + if order.symbol == symbol] + return list(self.orders.values()) + + def calculate_position(self, symbol: str) -> Dict: + """ + Calculate current position for a symbol based on filled orders. + + Args: + symbol: Trading symbol + + Returns: + Dictionary with position information + """ + position = 0 + total_cost = 0.0 + + for order in self.orders.values(): + if order.symbol == symbol and order.filled_quantity > 0: + quantity = order.filled_quantity + if order.side == OrderSide.BUY: + position += quantity + total_cost += quantity * order.average_fill_price + else: + position -= quantity + total_cost -= quantity * order.average_fill_price + + avg_price = total_cost / position if position != 0 else 0.0 + + return { + 'symbol': symbol, + 'quantity': position, + 'average_price': avg_price, + 'total_cost': total_cost + } diff --git a/src/indicators/__init__.py b/src/indicators/__init__.py new file mode 100644 index 0000000..fa63bb8 --- /dev/null +++ b/src/indicators/__init__.py @@ -0,0 +1 @@ +"""Technical indicators module""" diff --git a/src/indicators/technical_indicators.py b/src/indicators/technical_indicators.py new file mode 100644 index 0000000..3821a03 --- /dev/null +++ b/src/indicators/technical_indicators.py @@ -0,0 +1,339 @@ +""" +Technical Indicators +Common technical analysis indicators for trading +""" + +import pandas as pd +import numpy as np +from typing import Optional, Tuple + + +class TechnicalIndicators: + """ + Calculates technical indicators commonly used in trading. + """ + + @staticmethod + def sma(prices: pd.Series, period: int) -> pd.Series: + """ + Calculate Simple Moving Average. + + Args: + prices: Series of prices + period: Lookback period + + Returns: + Series of SMA values + """ + return prices.rolling(window=period).mean() + + @staticmethod + def ema(prices: pd.Series, period: int) -> pd.Series: + """ + Calculate Exponential Moving Average. + + Args: + prices: Series of prices + period: Lookback period + + Returns: + Series of EMA values + """ + return prices.ewm(span=period, adjust=False).mean() + + @staticmethod + def rsi(prices: pd.Series, period: int = 14) -> pd.Series: + """ + Calculate Relative Strength Index. + + Args: + prices: Series of prices + period: Lookback period (default 14) + + Returns: + Series of RSI values (0-100) + """ + delta = prices.diff() + + gain = (delta.where(delta > 0, 0)).rolling(window=period).mean() + loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean() + + # Handle division by zero + with np.errstate(divide='ignore', invalid='ignore'): + rs = gain / loss + rs = rs.replace([np.inf, -np.inf], 100) # If loss is 0, RSI = 100 + rs = rs.fillna(0) + + rsi = 100 - (100 / (1 + rs)) + + return rsi + + @staticmethod + def macd( + prices: pd.Series, + fast_period: int = 12, + slow_period: int = 26, + signal_period: int = 9 + ) -> Tuple[pd.Series, pd.Series, pd.Series]: + """ + Calculate MACD (Moving Average Convergence Divergence). + + Args: + prices: Series of prices + fast_period: Fast EMA period (default 12) + slow_period: Slow EMA period (default 26) + signal_period: Signal line period (default 9) + + Returns: + Tuple of (MACD line, Signal line, Histogram) + """ + fast_ema = TechnicalIndicators.ema(prices, fast_period) + slow_ema = TechnicalIndicators.ema(prices, slow_period) + + macd_line = fast_ema - slow_ema + signal_line = macd_line.ewm(span=signal_period, adjust=False).mean() + histogram = macd_line - signal_line + + return macd_line, signal_line, histogram + + @staticmethod + def bollinger_bands( + prices: pd.Series, + period: int = 20, + std_dev: float = 2.0 + ) -> Tuple[pd.Series, pd.Series, pd.Series]: + """ + Calculate Bollinger Bands. + + Args: + prices: Series of prices + period: Lookback period (default 20) + std_dev: Number of standard deviations (default 2.0) + + Returns: + Tuple of (Upper band, Middle band, Lower band) + """ + middle_band = prices.rolling(window=period).mean() + standard_deviation = prices.rolling(window=period).std() + + upper_band = middle_band + (standard_deviation * std_dev) + lower_band = middle_band - (standard_deviation * std_dev) + + return upper_band, middle_band, lower_band + + @staticmethod + def atr( + high: pd.Series, + low: pd.Series, + close: pd.Series, + period: int = 14 + ) -> pd.Series: + """ + Calculate Average True Range. + + Args: + high: Series of high prices + low: Series of low prices + close: Series of close prices + period: Lookback period (default 14) + + Returns: + Series of ATR values + """ + high_low = high - low + high_close = (high - close.shift()).abs() + low_close = (low - close.shift()).abs() + + true_range = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1) + atr = true_range.rolling(window=period).mean() + + return atr + + @staticmethod + def stochastic( + high: pd.Series, + low: pd.Series, + close: pd.Series, + period: int = 14, + smooth_k: int = 3, + smooth_d: int = 3 + ) -> Tuple[pd.Series, pd.Series]: + """ + Calculate Stochastic Oscillator. + + Args: + high: Series of high prices + low: Series of low prices + close: Series of close prices + period: Lookback period (default 14) + smooth_k: %K smoothing period (default 3) + smooth_d: %D smoothing period (default 3) + + Returns: + Tuple of (%K line, %D line) + """ + lowest_low = low.rolling(window=period).min() + highest_high = high.rolling(window=period).max() + + # Handle division by zero when price range is zero + price_range = highest_high - lowest_low + with np.errstate(divide='ignore', invalid='ignore'): + k_percent = 100 * ((close - lowest_low) / price_range) + k_percent = k_percent.replace([np.inf, -np.inf], np.nan) + + k_smooth = k_percent.rolling(window=smooth_k).mean() + d_smooth = k_smooth.rolling(window=smooth_d).mean() + + return k_smooth, d_smooth + + @staticmethod + def adx( + high: pd.Series, + low: pd.Series, + close: pd.Series, + period: int = 14 + ) -> pd.Series: + """ + Calculate Average Directional Index. + + Args: + high: Series of high prices + low: Series of low prices + close: Series of close prices + period: Lookback period (default 14) + + Returns: + Series of ADX values + """ + # Calculate +DM and -DM + high_diff = high.diff() + low_diff = -low.diff() + + plus_dm = high_diff.where((high_diff > low_diff) & (high_diff > 0), 0) + minus_dm = low_diff.where((low_diff > high_diff) & (low_diff > 0), 0) + + # Calculate True Range + tr = TechnicalIndicators.atr(high, low, close, 1) + + # Calculate smoothed +DI and -DI + atr_period = tr.rolling(window=period).sum() + + # Handle division by zero + with np.errstate(divide='ignore', invalid='ignore'): + plus_di = 100 * (plus_dm.rolling(window=period).sum() / atr_period) + minus_di = 100 * (minus_dm.rolling(window=period).sum() / atr_period) + + # Calculate DX + di_sum = plus_di + minus_di + dx = 100 * (plus_di - minus_di).abs() / di_sum + dx = dx.replace([np.inf, -np.inf], np.nan) + + adx = dx.rolling(window=period).mean() + + return adx + + @staticmethod + def obv(close: pd.Series, volume: pd.Series) -> pd.Series: + """ + Calculate On-Balance Volume. + + Args: + close: Series of close prices + volume: Series of volumes + + Returns: + Series of OBV values + """ + direction = close.diff().apply(lambda x: 1 if x > 0 else (-1 if x < 0 else 0)) + obv = (direction * volume).cumsum() + + return obv + + @staticmethod + def vwap( + high: pd.Series, + low: pd.Series, + close: pd.Series, + volume: pd.Series + ) -> pd.Series: + """ + Calculate Volume Weighted Average Price. + + Args: + high: Series of high prices + low: Series of low prices + close: Series of close prices + volume: Series of volumes + + Returns: + Series of VWAP values + """ + typical_price = (high + low + close) / 3 + vwap = (typical_price * volume).cumsum() / volume.cumsum() + + return vwap + + @staticmethod + def cci( + high: pd.Series, + low: pd.Series, + close: pd.Series, + period: int = 20 + ) -> pd.Series: + """ + Calculate Commodity Channel Index. + + Args: + high: Series of high prices + low: Series of low prices + close: Series of close prices + period: Lookback period (default 20) + + Returns: + Series of CCI values + """ + typical_price = (high + low + close) / 3 + sma = typical_price.rolling(window=period).mean() + mad = typical_price.rolling(window=period).apply( + lambda x: np.abs(x - x.mean()).mean() + ) + + # Handle division by zero when MAD is zero + with np.errstate(divide='ignore', invalid='ignore'): + cci = (typical_price - sma) / (0.015 * mad) + cci = cci.replace([np.inf, -np.inf], np.nan) + + return cci + + @staticmethod + def momentum(prices: pd.Series, period: int = 10) -> pd.Series: + """ + Calculate Momentum indicator. + + Args: + prices: Series of prices + period: Lookback period (default 10) + + Returns: + Series of momentum values + """ + return prices.diff(period) + + @staticmethod + def roc(prices: pd.Series, period: int = 10) -> pd.Series: + """ + Calculate Rate of Change. + + Args: + prices: Series of prices + period: Lookback period (default 10) + + Returns: + Series of ROC values (percentage) + """ + # Handle division by zero + with np.errstate(divide='ignore', invalid='ignore'): + roc = ((prices - prices.shift(period)) / prices.shift(period)) * 100 + roc = roc.replace([np.inf, -np.inf], np.nan) + + return roc diff --git a/src/market_data/__init__.py b/src/market_data/__init__.py new file mode 100644 index 0000000..02f3e59 --- /dev/null +++ b/src/market_data/__init__.py @@ -0,0 +1 @@ +"""Market data fetching module""" diff --git a/src/market_data/data_fetcher.py b/src/market_data/data_fetcher.py new file mode 100644 index 0000000..e8e441a --- /dev/null +++ b/src/market_data/data_fetcher.py @@ -0,0 +1,216 @@ +""" +Market Data Fetcher +Fetches real-time and historical market data from various sources +""" + +import pandas as pd +import numpy as np +from datetime import datetime, timedelta +from typing import Dict, List, Optional, Union +import requests +import json + + +class MarketDataFetcher: + """ + Fetches market data from various sources including exchanges and data providers. + Supports both real-time and historical data retrieval. + """ + + def __init__(self, api_key: Optional[str] = None, data_source: str = "simulated"): + """ + Initialize the market data fetcher. + + Args: + api_key: API key for data provider (if required) + data_source: Data source to use ('simulated', 'alpha_vantage', 'polygon', etc.) + """ + self.api_key = api_key + self.data_source = data_source + self.base_urls = { + 'alpha_vantage': 'https://www.alphavantage.co/query', + 'polygon': 'https://api.polygon.io', + } + + def get_quote(self, symbol: str) -> Dict: + """ + Get real-time quote for a symbol. + + Args: + symbol: Trading symbol (e.g., 'AAPL', 'MSFT') + + Returns: + Dictionary with quote data including bid, ask, last, volume + """ + if self.data_source == "simulated": + return self._generate_simulated_quote(symbol) + else: + raise NotImplementedError(f"Data source {self.data_source} not yet implemented") + + def get_historical_data( + self, + symbol: str, + start_date: Union[str, datetime], + end_date: Union[str, datetime], + interval: str = '1D' + ) -> pd.DataFrame: + """ + Get historical OHLCV data for a symbol. + + Args: + symbol: Trading symbol + start_date: Start date for historical data + end_date: End date for historical data + interval: Data interval ('1m', '5m', '1h', '1D', etc.) + + Returns: + DataFrame with columns: timestamp, open, high, low, close, volume + """ + if self.data_source == "simulated": + return self._generate_simulated_historical_data(symbol, start_date, end_date, interval) + else: + raise NotImplementedError(f"Data source {self.data_source} not yet implemented") + + def get_tick_data(self, symbol: str, num_ticks: int = 100) -> pd.DataFrame: + """ + Get tick-level data for a symbol. + + Args: + symbol: Trading symbol + num_ticks: Number of ticks to retrieve + + Returns: + DataFrame with columns: timestamp, price, size, side + """ + if self.data_source == "simulated": + return self._generate_simulated_tick_data(symbol, num_ticks) + else: + raise NotImplementedError(f"Data source {self.data_source} not yet implemented") + + def _generate_simulated_quote(self, symbol: str) -> Dict: + """Generate simulated real-time quote data""" + base_price = hash(symbol) % 500 + 50 + spread = base_price * 0.001 + + return { + 'symbol': symbol, + 'timestamp': datetime.now().isoformat(), + 'bid': round(base_price - spread/2, 2), + 'ask': round(base_price + spread/2, 2), + 'last': round(base_price + np.random.uniform(-spread, spread), 2), + 'volume': int(np.random.uniform(100000, 10000000)), + 'bid_size': int(np.random.uniform(100, 10000)), + 'ask_size': int(np.random.uniform(100, 10000)), + } + + def _generate_simulated_historical_data( + self, + symbol: str, + start_date: Union[str, datetime], + end_date: Union[str, datetime], + interval: str + ) -> pd.DataFrame: + """Generate simulated historical OHLCV data""" + if isinstance(start_date, str): + start_date = pd.to_datetime(start_date) + if isinstance(end_date, str): + end_date = pd.to_datetime(end_date) + + # Generate date range based on interval + if interval == '1D': + dates = pd.date_range(start=start_date, end=end_date, freq='D') + elif interval == '1h': + dates = pd.date_range(start=start_date, end=end_date, freq='H') + elif interval == '5m': + dates = pd.date_range(start=start_date, end=end_date, freq='5min') + else: + dates = pd.date_range(start=start_date, end=end_date, freq='D') + + base_price = hash(symbol) % 500 + 50 + + # Generate price series with random walk + returns = np.random.normal(0.0002, 0.02, len(dates)) + prices = base_price * np.exp(np.cumsum(returns)) + + data = [] + for i, date in enumerate(dates): + open_price = prices[i] + high_price = open_price * (1 + abs(np.random.normal(0, 0.01))) + low_price = open_price * (1 - abs(np.random.normal(0, 0.01))) + close_price = open_price * (1 + np.random.normal(0, 0.005)) + volume = int(np.random.uniform(100000, 10000000)) + + data.append({ + 'timestamp': date, + 'open': round(open_price, 2), + 'high': round(high_price, 2), + 'low': round(low_price, 2), + 'close': round(close_price, 2), + 'volume': volume + }) + + return pd.DataFrame(data) + + def _generate_simulated_tick_data(self, symbol: str, num_ticks: int) -> pd.DataFrame: + """Generate simulated tick-level data""" + base_price = hash(symbol) % 500 + 50 + spread = base_price * 0.001 + + data = [] + current_time = datetime.now() + + for i in range(num_ticks): + tick_time = current_time - timedelta(seconds=(num_ticks - i)) + price = base_price + np.random.normal(0, spread) + size = int(np.random.uniform(10, 1000)) + side = np.random.choice(['buy', 'sell']) + + data.append({ + 'timestamp': tick_time, + 'price': round(price, 2), + 'size': size, + 'side': side + }) + + return pd.DataFrame(data) + + def get_order_book_snapshot(self, symbol: str, depth: int = 10) -> Dict: + """ + Get order book snapshot for a symbol. + + Args: + symbol: Trading symbol + depth: Number of price levels to retrieve on each side + + Returns: + Dictionary with 'bids' and 'asks' lists of [price, size] pairs + """ + if self.data_source == "simulated": + return self._generate_simulated_order_book(symbol, depth) + else: + raise NotImplementedError(f"Data source {self.data_source} not yet implemented") + + def _generate_simulated_order_book(self, symbol: str, depth: int) -> Dict: + """Generate simulated order book snapshot""" + base_price = hash(symbol) % 500 + 50 + spread = base_price * 0.001 + tick_size = 0.01 + + bids = [] + asks = [] + + for i in range(depth): + bid_price = round(base_price - spread/2 - i * tick_size, 2) + bid_size = int(np.random.uniform(100, 5000)) + bids.append([bid_price, bid_size]) + + ask_price = round(base_price + spread/2 + i * tick_size, 2) + ask_size = int(np.random.uniform(100, 5000)) + asks.append([ask_price, ask_size]) + + return { + 'symbol': symbol, + 'timestamp': datetime.now().isoformat(), + 'bids': bids, + 'asks': asks + } diff --git a/src/microstructure/__init__.py b/src/microstructure/__init__.py new file mode 100644 index 0000000..d3e0fcc --- /dev/null +++ b/src/microstructure/__init__.py @@ -0,0 +1 @@ +"""Market microstructure analysis module""" diff --git a/src/microstructure/metrics.py b/src/microstructure/metrics.py new file mode 100644 index 0000000..1c5c770 --- /dev/null +++ b/src/microstructure/metrics.py @@ -0,0 +1,304 @@ +""" +Microstructure Metrics +Calculates various market microstructure metrics and measures +""" + +import pandas as pd +import numpy as np +from typing import Optional, Dict + + +class MicrostructureMetrics: + """ + Calculates market microstructure metrics including spread measures, + price impact, liquidity metrics, and information metrics. + """ + + @staticmethod + def calculate_quoted_spread(bid: float, ask: float) -> float: + """ + Calculate quoted (nominal) spread. + + Args: + bid: Best bid price + ask: Best ask price + + Returns: + Quoted spread + """ + return ask - bid + + @staticmethod + def calculate_relative_spread(bid: float, ask: float) -> float: + """ + Calculate relative (percentage) spread. + + Args: + bid: Best bid price + ask: Best ask price + + Returns: + Relative spread as percentage + """ + mid_price = (bid + ask) / 2 + return ((ask - bid) / mid_price) * 100 + + @staticmethod + def calculate_effective_spread(trade_price: float, mid_price: float, side: str) -> float: + """ + Calculate effective spread for a trade. + + Args: + trade_price: Actual trade price + mid_price: Mid-quote at time of trade + side: Trade side ('buy' or 'sell') + + Returns: + Effective spread in basis points + """ + if side.lower() == 'buy': + spread = (trade_price - mid_price) / mid_price + else: + spread = (mid_price - trade_price) / mid_price + + return spread * 10000 # Convert to basis points + + @staticmethod + def calculate_realized_spread( + trade_price: float, + mid_price_at_trade: float, + mid_price_later: float, + side: str + ) -> float: + """ + Calculate realized spread (measure of adverse selection). + + Args: + trade_price: Actual trade price + mid_price_at_trade: Mid-quote at time of trade + mid_price_later: Mid-quote at later time + side: Trade side ('buy' or 'sell') + + Returns: + Realized spread in basis points + """ + if side.lower() == 'buy': + spread = (trade_price - mid_price_later) / mid_price_at_trade + else: + spread = (mid_price_later - trade_price) / mid_price_at_trade + + return spread * 10000 + + @staticmethod + def calculate_price_impact( + mid_price_before: float, + mid_price_after: float, + side: str + ) -> float: + """ + Calculate permanent price impact of a trade. + + Args: + mid_price_before: Mid-quote before trade + mid_price_after: Mid-quote after trade + side: Trade side ('buy' or 'sell') + + Returns: + Price impact in basis points + """ + if side.lower() == 'buy': + impact = (mid_price_after - mid_price_before) / mid_price_before + else: + impact = (mid_price_before - mid_price_after) / mid_price_before + + return impact * 10000 + + @staticmethod + def calculate_roll_measure(returns: pd.Series) -> float: + """ + Calculate Roll's measure of effective spread from returns. + + Args: + returns: Series of price returns + + Returns: + Roll's measure estimate + """ + # Roll's measure: spread = 2 * sqrt(-Cov(r_t, r_{t-1})) + cov = returns.autocorr(lag=1) + + if cov < 0: + return 2 * np.sqrt(-cov) + else: + return 0.0 # Roll's measure undefined for positive autocorrelation + + @staticmethod + def calculate_amihud_illiquidity( + returns: pd.Series, + volumes: pd.Series + ) -> float: + """ + Calculate Amihud's (2002) illiquidity measure. + + Args: + returns: Series of daily returns + volumes: Series of daily dollar volumes + + Returns: + Amihud illiquidity ratio + """ + illiquidity = (np.abs(returns) / volumes).mean() + return illiquidity * 1e6 # Scale for readability + + @staticmethod + def calculate_kyle_lambda( + price_changes: pd.Series, + signed_volumes: pd.Series + ) -> float: + """ + Calculate Kyle's lambda (price impact coefficient). + + Args: + price_changes: Series of price changes + signed_volumes: Series of signed volumes (positive for buys) + + Returns: + Kyle's lambda coefficient + """ + # Run regression: ΔP = λ * Q + ε + if len(price_changes) == len(signed_volumes) and len(price_changes) > 1: + std_volume = np.std(signed_volumes) + + if std_volume <= 1e-8: + return 0.0 + + coef = np.corrcoef(signed_volumes, price_changes)[0, 1] + std_price = np.std(price_changes) + + return coef * (std_price / std_volume) + + return 0.0 + + @staticmethod + def calculate_pin( + buys: int, + sells: int, + alpha: float = 0.5, + delta: float = 0.5 + ) -> float: + """ + Calculate Probability of Informed Trading (PIN). + Simplified version using buy-sell imbalance. + + Args: + buys: Number of buy trades + sells: Number of sell trades + alpha: Probability of information event + delta: Probability that information is good news + + Returns: + Estimated PIN + """ + total_trades = buys + sells + if total_trades == 0: + return 0.0 + + imbalance = abs(buys - sells) / total_trades + + # Simplified PIN estimate based on order flow imbalance + pin = alpha * imbalance + + return min(pin, 1.0) + + @staticmethod + def calculate_order_flow_toxicity( + vwap: float, + mid_price: float + ) -> float: + """ + Calculate order flow toxicity (VPIN-style measure). + + Args: + vwap: Volume-weighted average price + mid_price: Mid-quote price + + Returns: + Toxicity measure + """ + if mid_price > 0: + return abs((vwap - mid_price) / mid_price) + return 0.0 + + @staticmethod + def calculate_market_depth( + bid_depth: float, + ask_depth: float, + mid_price: float + ) -> Dict[str, float]: + """ + Calculate market depth metrics. + + Args: + bid_depth: Total bid volume + ask_depth: Total ask volume + mid_price: Mid-quote price + + Returns: + Dictionary with depth metrics + """ + total_depth = bid_depth + ask_depth + + return { + 'total_depth': total_depth, + 'bid_depth': bid_depth, + 'ask_depth': ask_depth, + 'depth_imbalance': (bid_depth - ask_depth) / total_depth if total_depth > 0 else 0.0, + 'depth_ratio': bid_depth / ask_depth if ask_depth > 1e-8 else float('inf'), + 'dollar_depth': total_depth * mid_price + } + + @staticmethod + def calculate_volatility_metrics(returns: pd.Series) -> Dict[str, float]: + """ + Calculate various volatility metrics. + + Args: + returns: Series of returns + + Returns: + Dictionary with volatility metrics + """ + return { + 'std_dev': returns.std(), + 'variance': returns.var(), + 'realized_volatility': np.sqrt(np.sum(returns ** 2)), + 'mean_absolute_deviation': returns.abs().mean(), + 'downside_deviation': returns[returns < 0].std(), + 'skewness': returns.skew(), + 'kurtosis': returns.kurtosis() + } + + @staticmethod + def calculate_information_share( + price_series: pd.Series, + returns_series: pd.Series + ) -> float: + """ + Calculate information share (contribution to price discovery). + + Args: + price_series: Series of prices + returns_series: Series of returns + + Returns: + Information share estimate + """ + # Simplified information share based on variance contribution + total_variance = returns_series.var() + + if total_variance <= 1e-8: + return 0.0 + + # Calculate how much of the total variance is explained + explained_variance = returns_series.abs().mean() + return explained_variance / np.sqrt(total_variance) diff --git a/src/order_book/__init__.py b/src/order_book/__init__.py new file mode 100644 index 0000000..469ee42 --- /dev/null +++ b/src/order_book/__init__.py @@ -0,0 +1 @@ +"""Order book analysis module""" diff --git a/src/order_book/order_book.py b/src/order_book/order_book.py new file mode 100644 index 0000000..07a8e4e --- /dev/null +++ b/src/order_book/order_book.py @@ -0,0 +1,233 @@ +""" +Order Book +Represents and analyzes the limit order book +""" + +import pandas as pd +import numpy as np +from typing import List, Tuple, Dict, Optional +from datetime import datetime + + +class OrderBook: + """ + Represents a limit order book with bid and ask sides. + Provides methods for order book analysis and manipulation. + """ + + def __init__(self, symbol: str): + """ + Initialize an order book for a symbol. + + Args: + symbol: Trading symbol + """ + self.symbol = symbol + self.bids: List[Tuple[float, int]] = [] # [(price, size), ...] + self.asks: List[Tuple[float, int]] = [] # [(price, size), ...] + self.timestamp = None + + def update(self, bids: List[List], asks: List[List]): + """ + Update the order book with new bid and ask levels. + + Args: + bids: List of [price, size] pairs for bids (sorted high to low) + asks: List of [price, size] pairs for asks (sorted low to high) + """ + self.bids = [(price, size) for price, size in bids] + self.asks = [(price, size) for price, size in asks] + self.timestamp = datetime.now() + + # Sort to ensure correct ordering + self.bids.sort(key=lambda x: x[0], reverse=True) + self.asks.sort(key=lambda x: x[0]) + + def get_best_bid(self) -> Optional[Tuple[float, int]]: + """Get the best (highest) bid price and size""" + return self.bids[0] if self.bids else None + + def get_best_ask(self) -> Optional[Tuple[float, int]]: + """Get the best (lowest) ask price and size""" + return self.asks[0] if self.asks else None + + def get_mid_price(self) -> Optional[float]: + """Calculate the mid price between best bid and ask""" + best_bid = self.get_best_bid() + best_ask = self.get_best_ask() + + if best_bid and best_ask: + return (best_bid[0] + best_ask[0]) / 2 + return None + + def get_spread(self) -> Optional[float]: + """Calculate the bid-ask spread""" + best_bid = self.get_best_bid() + best_ask = self.get_best_ask() + + if best_bid and best_ask: + return best_ask[0] - best_bid[0] + return None + + def get_relative_spread(self) -> Optional[float]: + """Calculate the relative (percentage) spread""" + spread = self.get_spread() + mid_price = self.get_mid_price() + + if spread is not None and mid_price and mid_price > 0: + return (spread / mid_price) * 100 + return None + + def get_depth(self, side: str, levels: int = 5) -> float: + """ + Calculate the depth (total volume) for a given side. + + Args: + side: 'bid' or 'ask' + levels: Number of price levels to include + + Returns: + Total volume at the specified levels + """ + if side.lower() == 'bid': + return sum(size for _, size in self.bids[:levels]) + elif side.lower() == 'ask': + return sum(size for _, size in self.asks[:levels]) + return 0.0 + + def get_imbalance(self, levels: int = 5) -> Optional[float]: + """ + Calculate order book imbalance. + + Args: + levels: Number of price levels to include + + Returns: + Imbalance ratio between -1 (all asks) and 1 (all bids) + """ + bid_depth = self.get_depth('bid', levels) + ask_depth = self.get_depth('ask', levels) + + total_depth = bid_depth + ask_depth + if total_depth > 0: + return (bid_depth - ask_depth) / total_depth + return None + + def get_weighted_mid_price(self, levels: int = 5) -> Optional[float]: + """ + Calculate volume-weighted mid price. + + Args: + levels: Number of price levels to include + + Returns: + Volume-weighted mid price + """ + bid_depth = self.get_depth('bid', levels) + ask_depth = self.get_depth('ask', levels) + best_bid = self.get_best_bid() + best_ask = self.get_best_ask() + + if best_bid and best_ask and (bid_depth + ask_depth) > 0: + return (best_bid[0] * ask_depth + best_ask[0] * bid_depth) / (bid_depth + ask_depth) + return None + + def calculate_vwap(self, side: str, volume: int) -> Optional[float]: + """ + Calculate VWAP for a market order of given volume. + + Args: + side: 'buy' or 'sell' + volume: Order volume + + Returns: + Volume-weighted average price for the order + """ + if side.lower() == 'buy': + levels = self.asks + elif side.lower() == 'sell': + levels = self.bids + else: + return None + + remaining_volume = volume + total_cost = 0.0 + + for price, size in levels: + if remaining_volume <= 0: + break + + fill_volume = min(remaining_volume, size) + total_cost += price * fill_volume + remaining_volume -= fill_volume + + if remaining_volume > 0: + # Not enough liquidity + return None + + return total_cost / volume + + def get_market_impact(self, side: str, volume: int) -> Optional[float]: + """ + Estimate market impact of a market order. + + Args: + side: 'buy' or 'sell' + volume: Order volume + + Returns: + Price impact in basis points + """ + vwap = self.calculate_vwap(side, volume) + mid_price = self.get_mid_price() + + if vwap and mid_price and mid_price > 0: + if side.lower() == 'buy': + impact = (vwap - mid_price) / mid_price + else: + impact = (mid_price - vwap) / mid_price + + return impact * 10000 # Convert to basis points + return None + + def to_dataframe(self) -> pd.DataFrame: + """ + Convert order book to a DataFrame for analysis. + + Returns: + DataFrame with bid and ask levels + """ + max_levels = max(len(self.bids), len(self.asks)) + + data = [] + for i in range(max_levels): + row = {'level': i} + + if i < len(self.bids): + row['bid_price'] = self.bids[i][0] + row['bid_size'] = self.bids[i][1] + else: + row['bid_price'] = None + row['bid_size'] = None + + if i < len(self.asks): + row['ask_price'] = self.asks[i][0] + row['ask_size'] = self.asks[i][1] + else: + row['ask_price'] = None + row['ask_size'] = None + + data.append(row) + + return pd.DataFrame(data) + + def __repr__(self) -> str: + """String representation of the order book""" + best_bid = self.get_best_bid() + best_ask = self.get_best_ask() + spread = self.get_spread() + + return (f"OrderBook(symbol={self.symbol}, " + f"best_bid={best_bid}, best_ask={best_ask}, " + f"spread={spread:.4f})" if spread else + f"OrderBook(symbol={self.symbol}, empty)") diff --git a/src/risk_management/__init__.py b/src/risk_management/__init__.py new file mode 100644 index 0000000..f4ff58f --- /dev/null +++ b/src/risk_management/__init__.py @@ -0,0 +1 @@ +"""Risk management module""" diff --git a/src/risk_management/risk_calculator.py b/src/risk_management/risk_calculator.py new file mode 100644 index 0000000..8ed713f --- /dev/null +++ b/src/risk_management/risk_calculator.py @@ -0,0 +1,285 @@ +""" +Risk Calculator +Calculates various risk metrics and position sizing +""" + +import pandas as pd +import numpy as np +from typing import Dict, Optional + + +# Constants +TRADING_DAYS_PER_YEAR = 252 + + +class RiskCalculator: + """ + Calculates risk metrics and helps with position sizing and risk management. + """ + + @staticmethod + def calculate_var( + returns: pd.Series, + confidence_level: float = 0.95, + method: str = 'historical' + ) -> float: + """ + Calculate Value at Risk (VaR). + + Args: + returns: Series of returns + confidence_level: Confidence level (e.g., 0.95 for 95%) + method: Calculation method ('historical', 'parametric', 'monte_carlo') + + Returns: + VaR value + """ + if method == 'historical': + return np.percentile(returns, (1 - confidence_level) * 100) + + elif method == 'parametric': + mean = returns.mean() + std = returns.std() + z_score = np.abs(np.percentile(np.random.standard_normal(10000), + (1 - confidence_level) * 100)) + return mean - z_score * std + + else: + raise ValueError(f"Unknown VaR method: {method}") + + @staticmethod + def calculate_cvar( + returns: pd.Series, + confidence_level: float = 0.95 + ) -> float: + """ + Calculate Conditional Value at Risk (CVaR/Expected Shortfall). + + Args: + returns: Series of returns + confidence_level: Confidence level + + Returns: + CVaR value + """ + var = RiskCalculator.calculate_var(returns, confidence_level) + cvar = returns[returns <= var].mean() + + return cvar + + @staticmethod + def calculate_sharpe_ratio( + returns: pd.Series, + risk_free_rate: float = 0.02 + ) -> float: + """ + Calculate Sharpe Ratio. + + Args: + returns: Series of returns + risk_free_rate: Risk-free rate (annualized) + + Returns: + Sharpe ratio + """ + excess_returns = returns - risk_free_rate / TRADING_DAYS_PER_YEAR # Assume daily returns + + if returns.std() > 1e-8: # Add minimum threshold + return np.sqrt(TRADING_DAYS_PER_YEAR) * (excess_returns.mean() / returns.std()) + return 0.0 + + @staticmethod + def calculate_sortino_ratio( + returns: pd.Series, + risk_free_rate: float = 0.02 + ) -> float: + """ + Calculate Sortino Ratio (uses downside deviation). + + Args: + returns: Series of returns + risk_free_rate: Risk-free rate (annualized) + + Returns: + Sortino ratio + """ + excess_returns = returns - risk_free_rate / TRADING_DAYS_PER_YEAR + downside_returns = returns[returns < 0] + + if len(downside_returns) > 0 and downside_returns.std() > 1e-8: # Add minimum threshold + downside_deviation = downside_returns.std() + return np.sqrt(TRADING_DAYS_PER_YEAR) * (excess_returns.mean() / downside_deviation) + return 0.0 + + @staticmethod + def calculate_max_drawdown(prices: pd.Series) -> Dict[str, float]: + """ + Calculate maximum drawdown. + + Args: + prices: Series of prices or portfolio values + + Returns: + Dictionary with max drawdown information + """ + cumulative_max = prices.cummax() + drawdown = (prices - cumulative_max) / cumulative_max + max_drawdown = drawdown.min() + + max_dd_idx = drawdown.idxmin() + peak_idx = prices[:max_dd_idx].idxmax() + + return { + 'max_drawdown': max_drawdown, + 'max_drawdown_pct': max_drawdown * 100, + 'peak_date': peak_idx, + 'trough_date': max_dd_idx, + 'recovery_date': None # Would need to calculate if price recovers + } + + @staticmethod + def calculate_position_size( + account_value: float, + risk_per_trade: float, + entry_price: float, + stop_loss_price: float, + contract_size: float = 1.0 + ) -> int: + """ + Calculate position size based on risk management rules. + + Args: + account_value: Total account value + risk_per_trade: Risk per trade as decimal (e.g., 0.02 for 2%) + entry_price: Entry price + stop_loss_price: Stop loss price + contract_size: Size of one contract/share + + Returns: + Position size (number of shares/contracts) + """ + risk_amount = account_value * risk_per_trade + price_risk = abs(entry_price - stop_loss_price) + + if price_risk <= 0: + raise ValueError("Stop loss price must be different from entry price") + + position_size = int(risk_amount / (price_risk * contract_size)) + return max(position_size, 0) + + @staticmethod + def calculate_kelly_criterion( + win_rate: float, + avg_win: float, + avg_loss: float + ) -> float: + """ + Calculate Kelly Criterion for position sizing. + + Args: + win_rate: Probability of winning (0-1) + avg_win: Average win amount + avg_loss: Average loss amount + + Returns: + Kelly percentage (fraction of capital to risk) + """ + if avg_loss <= 0: + raise ValueError("Average loss must be positive") + + win_loss_ratio = avg_win / avg_loss + kelly = (win_rate * win_loss_ratio - (1 - win_rate)) / win_loss_ratio + return max(0, min(kelly, 1)) # Constrain between 0 and 1 + + @staticmethod + def calculate_beta( + asset_returns: pd.Series, + market_returns: pd.Series + ) -> float: + """ + Calculate beta (systematic risk). + + Args: + asset_returns: Series of asset returns + market_returns: Series of market returns + + Returns: + Beta coefficient + """ + covariance = asset_returns.cov(market_returns) + market_variance = market_returns.var() + + if market_variance <= 1e-8: + raise ValueError("Market returns have zero or near-zero variance") + + return covariance / market_variance + + @staticmethod + def calculate_portfolio_metrics( + weights: Dict[str, float], + returns: pd.DataFrame, + covariance_matrix: Optional[pd.DataFrame] = None + ) -> Dict: + """ + Calculate portfolio-level risk metrics. + + Args: + weights: Dictionary of asset weights + returns: DataFrame of asset returns + covariance_matrix: Optional pre-calculated covariance matrix + + Returns: + Dictionary with portfolio metrics + """ + weight_array = np.array([weights.get(col, 0) for col in returns.columns]) + + # Portfolio return + portfolio_return = (returns.mean() * weight_array).sum() + + # Portfolio variance and volatility + if covariance_matrix is None: + covariance_matrix = returns.cov() + + portfolio_variance = np.dot(weight_array, np.dot(covariance_matrix, weight_array)) + portfolio_volatility = np.sqrt(portfolio_variance) + + # Portfolio VaR + portfolio_returns = (returns * weight_array).sum(axis=1) + var_95 = RiskCalculator.calculate_var(portfolio_returns, 0.95) + cvar_95 = RiskCalculator.calculate_cvar(portfolio_returns, 0.95) + + return { + 'expected_return': portfolio_return * TRADING_DAYS_PER_YEAR, # Annualized + 'volatility': portfolio_volatility * np.sqrt(TRADING_DAYS_PER_YEAR), # Annualized + 'var_95': var_95, + 'cvar_95': cvar_95, + 'sharpe_ratio': RiskCalculator.calculate_sharpe_ratio(portfolio_returns) + } + + @staticmethod + def calculate_risk_adjusted_return( + returns: pd.Series, + risk_free_rate: float = 0.02 + ) -> Dict[str, float]: + """ + Calculate various risk-adjusted return metrics. + + Args: + returns: Series of returns + risk_free_rate: Risk-free rate + + Returns: + Dictionary with risk-adjusted metrics + """ + total_return = (1 + returns).prod() - 1 + annualized_return = (1 + total_return) ** (TRADING_DAYS_PER_YEAR / len(returns)) - 1 + + return { + 'total_return': total_return, + 'annualized_return': annualized_return, + 'sharpe_ratio': RiskCalculator.calculate_sharpe_ratio(returns, risk_free_rate), + 'sortino_ratio': RiskCalculator.calculate_sortino_ratio(returns, risk_free_rate), + 'volatility': returns.std() * np.sqrt(TRADING_DAYS_PER_YEAR), + 'var_95': RiskCalculator.calculate_var(returns, 0.95), + 'cvar_95': RiskCalculator.calculate_cvar(returns, 0.95) + } diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..c92d739 --- /dev/null +++ b/src/utils/__init__.py @@ -0,0 +1 @@ +"""Utility functions module""" diff --git a/src/utils/config.py b/src/utils/config.py new file mode 100644 index 0000000..cd5af57 --- /dev/null +++ b/src/utils/config.py @@ -0,0 +1,136 @@ +""" +Configuration Manager +Handles configuration loading and management +""" + +import json +import os +from typing import Dict, Any, Optional + + +class ConfigManager: + """ + Manages application configuration settings. + """ + + def __init__(self, config_path: Optional[str] = None): + """ + Initialize configuration manager. + + Args: + config_path: Path to configuration file + """ + self.config_path = config_path or "config.json" + self.config: Dict[str, Any] = {} + + if os.path.exists(self.config_path): + self.load() + + def load(self): + """Load configuration from file""" + try: + with open(self.config_path, 'r') as f: + self.config = json.load(f) + except Exception as e: + print(f"Error loading config: {e}") + self.config = {} + + def save(self): + """Save configuration to file""" + try: + with open(self.config_path, 'w') as f: + json.dump(self.config, f, indent=2) + except Exception as e: + print(f"Error saving config: {e}") + + def get(self, key: str, default: Any = None) -> Any: + """ + Get configuration value. + + Args: + key: Configuration key (supports dot notation, e.g., 'api.key') + default: Default value if key not found + + Returns: + Configuration value + """ + keys = key.split('.') + value = self.config + + for k in keys: + if isinstance(value, dict) and k in value: + value = value[k] + else: + return default + + return value + + def set(self, key: str, value: Any): + """ + Set configuration value. + + Args: + key: Configuration key (supports dot notation) + value: Value to set + """ + keys = key.split('.') + config = self.config + + for k in keys[:-1]: + if k not in config: + config[k] = {} + config = config[k] + + config[keys[-1]] = value + + def get_all(self) -> Dict[str, Any]: + """Get all configuration""" + return self.config.copy() + + def update(self, config_dict: Dict[str, Any]): + """ + Update configuration with dictionary. + + Args: + config_dict: Dictionary with configuration updates + """ + self._deep_update(self.config, config_dict) + + def _deep_update(self, base_dict: Dict, update_dict: Dict): + """Recursively update nested dictionary""" + for key, value in update_dict.items(): + if key in base_dict and isinstance(base_dict[key], dict) and isinstance(value, dict): + self._deep_update(base_dict[key], value) + else: + base_dict[key] = value + + +# Default configuration +DEFAULT_CONFIG = { + "market_data": { + "source": "simulated", + "api_key": None, + "cache_enabled": True, + "cache_duration": 300 + }, + "execution": { + "default_time_in_force": "GTC", + "max_order_size": 10000, + "enable_pre_trade_checks": True + }, + "risk": { + "max_position_size": 100000, + "max_portfolio_risk": 0.20, + "default_risk_per_trade": 0.02, + "enable_position_limits": True + }, + "microstructure": { + "order_book_depth": 10, + "tick_data_buffer_size": 1000 + }, + "logging": { + "level": "INFO", + "file": "trading.log", + "console": True + } +}