diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index d49d775..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..70caa5e --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +SSID = '' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 47b7823..f9be025 100644 --- a/.gitignore +++ b/.gitignore @@ -147,4 +147,8 @@ Thumbs.db # Windows *.tmp -*.temp \ No newline at end of file +*.temp + +# SSID driver + +browser_profiles* \ No newline at end of file diff --git a/README.md b/README.md index 113d5bc..61d4b8f 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,50 @@ -# PocketOption Async API (V2) - By ChipaDevTeam - -Check BinaryOptionToolsv2: [https://github.com/ChipaDevTeam/BinaryOptionsTools-v2](https://github.com/ChipaDevTeam/BinaryOptionsTools-v2)
+# PocketOption API- By ChipaDevTeam - Modified by Six <3 ## Support us -join PocketOption with our affiliate link: [PocketOption Affiliate link](https://u3.shortink.io/smart/SDIaxbeamcYYqB)
-donate in paypal: [Paypal.me](https://paypal.me/ChipaCL?country.x=CL&locale.x=en_US)
-help us in patreon: [Patreon](https://patreon.com/VigoDEV?utm_medium=unknown&utm_source=join_link&utm_campaign=creatorshare_creator&utm_content=copyLink)
- -## check our bots and talk to us! -Join our comunity --> [discord](https://discord.com/invite/kaZ8uV9b6k) +Join PocketOption with Six's affiliate link: [Six PocketOption Affiliate link](https://u3.shortink.io/main?utm_campaign=821725&utm_source=affiliate&utm_medium=sr&a=IqeAmBtFTrEWbh&ac=api&code=DLN960) +
+Join PocketOption with Chipas affiliate link: [Chipas PocketOption Affiliate link](https://u3.shortink.io/smart/SDIaxbeamcYYqB) A comprehensive, modern async Python API for PocketOption trading platform with advanced features including persistent connections, monitoring, and extensive testing frameworks. -## ๐Ÿš€ Key Features +## Key Features -### โœจ Enhanced Connection Management +### Enhanced Connection Management - **Complete SSID Format Support**: Works with full authentication strings from browser (format: `42["auth",{"session":"...","isDemo":1,"uid":...,"platform":1}]`) - **Persistent Connections**: Automatic keep-alive with 20-second ping intervals (like the original API) - **Auto-Reconnection**: Intelligent reconnection with multiple region fallback - **Connection Pooling**: Optimized connection management for better performance -### ๐Ÿ” Advanced Monitoring & Diagnostics +### Advanced Monitoring & Diagnostics - **Real-time Monitoring**: Connection health, performance metrics, and error tracking - **Diagnostics Reports**: Comprehensive health assessments with recommendations - **Performance Analytics**: Response times, throughput analysis, and bottleneck detection - **Alert System**: Automatic alerts for connection issues and performance problems -### ๐Ÿงช Comprehensive Testing Framework +### Comprehensive Testing Framework - **Load Testing**: Concurrent client simulation and stress testing - **Integration Testing**: End-to-end validation of all components - **Performance Benchmarks**: Automated performance analysis and optimization - **Advanced Test Suites**: Edge cases, error scenarios, and long-running stability tests -### โšก Performance Optimizations +### Performance Optimizations - **Message Batching**: Efficient message queuing and processing - **Concurrent Operations**: Parallel API calls for better throughput - **Caching System**: Intelligent caching with TTL for frequently accessed data - **Rate Limiting**: Built-in protection against API rate limits -### ๐Ÿ›ก๏ธ Robust Error Handling +### Robust Error Handling - **Graceful Degradation**: Continues operation despite individual failures - **Automatic Recovery**: Self-healing connections and operations - **Comprehensive Logging**: Detailed error tracking and debugging information - **Exception Management**: Type-specific error handling and recovery strategies -## ๐Ÿ“ฆ Installation +## Installation ```bash # Clone the repository git clone -cd PocketOptionAPI-3 +cd PocketOptionAPI # Install dependencies pip install -r requirements.txt @@ -60,149 +55,7 @@ pip install -r requirements-dev.txt ## ๐Ÿ”ง Quick Start -### Basic Usage - -```python -import asyncio -from pocketoptionapi_async.client import AsyncPocketOptionClient -from pocketoptionapi_async.models import OrderDirection, TimeFrame - -async def main(): - # Complete SSID format (get from browser dev tools) - ssid = r'42["auth",{"session":"your_session_here","isDemo":1,"uid":12345,"platform":1}]' - - # Create client with persistent connection - client = AsyncPocketOptionClient( - ssid, - is_demo=True, - persistent_connection=True, # Enable keep-alive - auto_reconnect=True # Enable auto-reconnection - ) - - try: - # Connect - await client.connect() - - # Get balance - balance = await client.get_balance() - print(f"Balance: ${balance.balance}") - - # Get candles - candles = await client.get_candles("EURUSD", TimeFrame.M1, 100) - print(f"Retrieved {len(candles)} candles") - - # Place order (demo) - order = await client.place_order("EURUSD", 10, OrderDirection.CALL, 60) - print(f"Order placed: {order.order_id}") - - finally: - await client.disconnect() - -asyncio.run(main()) -``` - -### Persistent Connection with Keep-Alive - -```python -from connection_keep_alive import ConnectionKeepAlive - -async def persistent_connection_demo(): - ssid = r'42["auth",{"session":"your_session","isDemo":1,"uid":12345,"platform":1}]' - - # Create keep-alive manager - keep_alive = ConnectionKeepAlive(ssid, is_demo=True) - - # Add event handlers - async def on_connected(data): - print(f"Connected to: {data['region']}") - - async def on_message(data): - print(f"Message: {data['message'][:50]}...") - - keep_alive.add_event_handler('connected', on_connected) - keep_alive.add_event_handler('message_received', on_message) - - try: - # Start persistent connection (runs indefinitely with auto-reconnect) - await keep_alive.start_persistent_connection() - - # Send messages - await keep_alive.send_message('42["ps"]') - - # Keep running... - await asyncio.sleep(60) - - finally: - await keep_alive.stop_persistent_connection() - -asyncio.run(persistent_connection_demo()) -``` - -## ๐Ÿ” Advanced Features - -### Connection Monitoring - -```python -from connection_monitor import ConnectionMonitor - -async def monitoring_demo(): - monitor = ConnectionMonitor(ssid, is_demo=True) - - # Add alert handlers - async def on_alert(alert): - print(f"ALERT: {alert['message']}") - - monitor.add_event_handler('alert', on_alert) - - # Start monitoring - await monitor.start_monitoring() - - # Get real-time stats - stats = monitor.get_real_time_stats() - print(f"Messages: {stats['total_messages']}, Errors: {stats['total_errors']}") - - # Generate diagnostics report - report = monitor.generate_diagnostics_report() - print(f"Health Score: {report['health_score']}/100") - - await monitor.stop_monitoring() -``` - -### Load Testing - -```python -from load_testing_tool import LoadTester, LoadTestConfig - -async def load_test_demo(): - tester = LoadTester(ssid, is_demo=True) - - config = LoadTestConfig( - concurrent_clients=5, - operations_per_client=20, - use_persistent_connection=True, - stress_mode=False - ) - - report = await tester.run_load_test(config) - - print(f"Throughput: {report['test_summary']['avg_operations_per_second']:.1f} ops/sec") - print(f"Success Rate: {report['test_summary']['success_rate']:.1%}") -``` - -### Integration Testing - -```python -from integration_tests import IntegrationTester - -async def integration_test_demo(): - tester = IntegrationTester(ssid) - report = await tester.run_full_integration_tests() - - print(f"Health Score: {report['integration_summary']['health_score']:.1f}/100") - print(f"Tests Passed: {report['integration_summary']['passed_tests']}") -``` - -## ๐ŸŽฏ Getting Your SSID +### Getting Your SSID To use the API with real data, you need to extract your session ID from the browser: @@ -218,304 +71,4 @@ Example SSID format: 42["auth",{"session":"abcd1234efgh5678","isDemo":1,"uid":12345,"platform":1}] ``` -## ๐Ÿ“Š Monitoring and Diagnostics - -### Real-time Connection Monitor - -```bash -# Start real-time monitoring -python connection_monitor.py "your_ssid_here" -``` - -Features: -- Real-time connection status -- Performance metrics -- Error tracking -- Health score calculation -- Automatic alerts -- CSV export - -### Advanced Testing Suite - -```bash -# Run comprehensive tests -python advanced_testing_suite.py "your_ssid_here" -``` - -Includes: -- Connection stress tests -- Concurrent operations -- Data consistency checks -- Error handling validation -- Performance benchmarks -- Memory usage analysis - -### Load Testing - -```bash -# Run load tests -python load_testing_tool.py "your_ssid_here" -``` - -Capabilities: -- Multiple concurrent clients -- Stress testing modes -- Performance analysis -- Bottleneck identification -- Recommendation generation - -## ๐Ÿ—๏ธ Architecture - -### Component Overview - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ PocketOption Async API โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ AsyncPocketOptionClient (Main API Client) โ”‚ -โ”‚ โ”œโ”€โ”€ WebSocket Client (Connection Management) โ”‚ -โ”‚ โ”œโ”€โ”€ Keep-Alive Manager (Persistent Connections) โ”‚ -โ”‚ โ””โ”€โ”€ Event System (Callbacks & Handlers) โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Advanced Features โ”‚ -โ”‚ โ”œโ”€โ”€ Connection Monitor (Real-time Monitoring) โ”‚ -โ”‚ โ”œโ”€โ”€ Load Tester (Performance Testing) โ”‚ -โ”‚ โ”œโ”€โ”€ Integration Tester (E2E Validation) โ”‚ -โ”‚ โ””โ”€โ”€ Advanced Test Suite (Comprehensive Testing) โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Infrastructure โ”‚ -โ”‚ โ”œโ”€โ”€ Models & Types (Data Structures) โ”‚ -โ”‚ โ”œโ”€โ”€ Constants & Config (Settings) โ”‚ -โ”‚ โ”œโ”€โ”€ Exceptions (Error Handling) โ”‚ -โ”‚ โ””โ”€โ”€ Utils (Helper Functions) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### Connection Flow - -```mermaid -graph TD - A[Client Initialize] --> B{Persistent?} - B -->|Yes| C[ConnectionKeepAlive] - B -->|No| D[Regular Connection] - - C --> E[Multi-Region Connect] - D --> E - - E --> F[Authentication] - F --> G{Success?} - G -->|Yes| H[Start Keep-Alive Tasks] - G -->|No| I[Retry with Next Region] - - H --> J[Ping Loop 20s] - H --> K[Message Processing] - H --> L[Health Monitoring] - - I --> E - - J --> M{Connected?} - M -->|Yes| J - M -->|No| N[Auto Reconnect] - N --> E -``` - -## ๐Ÿงช Testing - -### Run All Tests - -```bash -# Complete integration test suite -python integration_tests.py "your_ssid_here" - -# Advanced test scenarios -python advanced_testing_suite.py "your_ssid_here" - -# Load and stress testing -python load_testing_tool.py "your_ssid_here" - -# Comprehensive demo -python comprehensive_demo.py "your_ssid_here" -``` - -### Test Categories - -1. **Unit Tests**: Individual component testing -2. **Integration Tests**: End-to-end functionality -3. **Load Tests**: Performance under load -4. **Stress Tests**: Extreme condition handling -5. **Monitoring Tests**: Real-time diagnostics -6. **Error Recovery Tests**: Failure scenarios - -## ๐Ÿ“ˆ Performance - -### Benchmarks - -Typical performance metrics (may vary based on network and server conditions): - -- **Connection Time**: < 2 seconds -- **Message Latency**: < 100ms -- **Throughput**: 10-50 operations/second -- **Memory Usage**: < 50MB for standard operations -- **CPU Usage**: < 5% during normal operation - -### Optimization Features - -- **Connection Pooling**: Reuse connections across operations -- **Message Batching**: Group messages for efficiency -- **Caching**: Store frequently accessed data -- **Concurrent Operations**: Parallel processing -- **Rate Limiting**: Prevent API overload - -## ๐Ÿ›ก๏ธ Error Handling - -### Automatic Recovery - -- **Connection Loss**: Auto-reconnect with exponential backoff -- **Authentication Errors**: Session refresh and retry -- **Network Issues**: Multiple region fallback -- **API Errors**: Graceful degradation and retry logic - -### Error Categories - -1. **Connection Errors**: Network and WebSocket issues -2. **Authentication Errors**: Invalid or expired sessions -3. **API Errors**: Invalid parameters or server errors -4. **Timeout Errors**: Operation timeout handling -5. **Rate Limit Errors**: API quota exceeded - -## ๐Ÿ”ง Configuration - -### Environment Variables - -```bash -# Optional configuration -export POCKETOPTION_DEBUG=true -export POCKETOPTION_LOG_LEVEL=DEBUG -export POCKETOPTION_MAX_RETRIES=5 -export POCKETOPTION_TIMEOUT=30 -``` - -### Client Configuration - -```python -client = AsyncPocketOptionClient( - ssid="your_ssid", - is_demo=True, # Demo/Live mode - region="eu", # Preferred region - persistent_connection=True, # Keep-alive enabled - auto_reconnect=True, # Auto-reconnection - uid=12345, # User ID - platform=1 # Platform identifier -) -``` - -## ๐Ÿ“š API Reference - -### AsyncPocketOptionClient - -Main client class for API operations. - -```python -class AsyncPocketOptionClient: - async def connect(regions: List[str] = None, persistent: bool = None) -> bool - async def disconnect() -> None - async def get_balance() -> Balance - async def get_candles(asset: str, timeframe: TimeFrame, count: int) -> List[Candle] - async def place_order(asset: str, amount: float, direction: OrderDirection, duration: int) -> OrderResult - async def send_message(message: str) -> bool - def get_connection_stats() -> Dict[str, Any] - def add_event_callback(event: str, callback: Callable) -> None -``` - -### ConnectionKeepAlive - -Advanced connection management with keep-alive. - -```python -class ConnectionKeepAlive: - async def start_persistent_connection() -> bool - async def stop_persistent_connection() -> None - async def send_message(message: str) -> bool - def add_event_handler(event: str, handler: Callable) -> None - def get_connection_stats() -> Dict[str, Any] -``` - -### ConnectionMonitor - -Real-time monitoring and diagnostics. - -```python -class ConnectionMonitor: - async def start_monitoring(persistent_connection: bool = True) -> bool - async def stop_monitoring() -> None - def get_real_time_stats() -> Dict[str, Any] - def generate_diagnostics_report() -> Dict[str, Any] - def export_metrics_csv(filename: str = None) -> str -``` - -## ๐Ÿค Contributing - -1. Fork the repository -2. Create a feature branch -3. Make your changes -4. Add tests for new functionality -5. Ensure all tests pass -6. Submit a pull request - -### Development Setup - -```bash -# Clone and setup -git clone -cd PocketOptionAPI-3 - -# Install development dependencies -pip install -r requirements-dev.txt - -# Run tests -python -m pytest tests/ - -# Run linting -flake8 pocketoptionapi_async/ -mypy pocketoptionapi_async/ -``` - -## ๐Ÿ“„ License - -This project is licensed under the MIT License - see the LICENSE file for details. - -## โš ๏ธ Disclaimer - -This software is for educational and research purposes only. Trading involves substantial risk and may not be suitable for all individuals. Past performance does not guarantee future results. Please trade responsibly and consider your financial situation before using this API for live trading. - -## ๐Ÿ†˜ Support - -- **Issues**: Report bugs or request features via GitHub Issues -- **Documentation**: See examples in the `examples/` directory -- **Discussions**: Join discussions in GitHub Discussions - -## ๐Ÿ† Changelog - -### v2.0.0 - Enhanced Edition - -- โœ… Complete SSID format support -- โœ… Persistent connections with keep-alive -- โœ… Advanced monitoring and diagnostics -- โœ… Comprehensive testing frameworks -- โœ… Performance optimizations -- โœ… Enhanced error handling -- โœ… Real-time connection monitoring -- โœ… Load testing capabilities -- โœ… Integration testing suite -- โœ… Modern async architecture - -### v1.0.0 - Initial Release - -- Basic async API client -- WebSocket connection management -- Core trading operations -- Error handling - ---- - -**Built with โค๏ธ for the PocketOption community** +If you are unable to find it, try running the automatic SSID scraper under the `SSID` folder. \ No newline at end of file diff --git a/README_ASYNC.md b/README_ASYNC.md deleted file mode 100644 index 9105c06..0000000 --- a/README_ASYNC.md +++ /dev/null @@ -1,420 +0,0 @@ -# Professional Async PocketOption API - -A complete rewrite of the PocketOption API with 100% async support and professional Python practices. - -## ๐Ÿš€ Features - -- **100% Async/Await Support**: Built from ground up with asyncio -- **Type Safety**: Full type hints with Pydantic models -- **Professional Code Quality**: Following Python best practices -- **Comprehensive Error Handling**: Custom exceptions with detailed error information -- **Real-time WebSocket**: Efficient async WebSocket client with automatic reconnection -- **Rate Limiting**: Built-in rate limiting to prevent API abuse -- **Context Manager Support**: Easy resource management with async context managers -- **Extensive Testing**: Comprehensive test suite with pytest -- **Rich Logging**: Structured logging with loguru -- **Modern Python**: Requires Python 3.8+ - -## ๐Ÿ“ฆ Installation - -```bash -# Install dependencies -pip install -r requirements.txt - -# For development -pip install -r requirements.txt pytest pytest-asyncio -``` - -## ๐Ÿ”ง Quick Start - -### Basic Usage - -```python -import asyncio -from pocketoptionapi_async import AsyncPocketOptionClient, OrderDirection - -async def main(): - # Initialize client - client = AsyncPocketOptionClient( - session_id="your_session_id_here", - is_demo=True # Use demo account - ) - - try: - # Connect to PocketOption - await client.connect() - - # Get account balance - balance = await client.get_balance() - print(f"Balance: ${balance.balance:.2f}") - - # Get historical data - candles = await client.get_candles( - asset="EURUSD_otc", - timeframe="1m", - count=100 - ) - print(f"Retrieved {len(candles)} candles") - - # Place an order - order_result = await client.place_order( - asset="EURUSD_otc", - amount=1.0, - direction=OrderDirection.CALL, - duration=60 - ) - print(f"Order placed: {order_result.order_id}") - - finally: - await client.disconnect() - -# Run the example -asyncio.run(main()) -``` - -### Using Context Manager - -```python -async def context_example(): - session_id = "your_session_id_here" - - # Automatic connection and cleanup - async with AsyncPocketOptionClient(session_id, is_demo=True) as client: - - # Add event callbacks - def on_order_closed(order_result): - print(f"Order closed with profit: ${order_result.profit:.2f}") - - client.add_event_callback('order_closed', on_order_closed) - - # Your trading logic here - balance = await client.get_balance() - print(f"Balance: ${balance.balance:.2f}") - -asyncio.run(context_example()) -``` - -### Advanced Example with DataFrame - -```python -import pandas as pd - -async def dataframe_example(): - async with AsyncPocketOptionClient("your_session_id", is_demo=True) as client: - - # Get data as pandas DataFrame - df = await client.get_candles_dataframe( - asset="EURUSD_otc", - timeframe="5m", - count=200 - ) - - # Technical analysis - df['sma_20'] = df['close'].rolling(20).mean() - df['rsi'] = calculate_rsi(df['close']) # Your TA function - - # Simple trading signal - if df['close'].iloc[-1] > df['sma_20'].iloc[-1]: - order = await client.place_order( - asset="EURUSD_otc", - amount=1.0, - direction=OrderDirection.CALL, - duration=300 - ) - print(f"Signal: BUY - Order: {order.order_id}") - -asyncio.run(dataframe_example()) -``` - -## ๐Ÿ“Š Available Assets - -The API supports 100+ assets including: - -- **Forex**: EURUSD, GBPUSD, USDJPY, etc. -- **Cryptocurrencies**: BTCUSD, ETHUSD, etc. -- **Commodities**: Gold (XAUUSD), Silver (XAGUSD), Oil, etc. -- **Indices**: SP500, NASDAQ, DAX, etc. -- **Stocks**: Apple, Microsoft, Tesla, etc. - -See `constants.py` for the complete list. - -## ๐Ÿ”ง Configuration - -### Environment Variables - -```bash -# Set your session ID -export POCKET_OPTION_SSID="your_actual_session_id" - -# Optional: Set log level -export LOG_LEVEL="INFO" -``` - -### Session ID - -To get your session ID: -1. Login to PocketOption in your browser -2. Open Developer Tools (F12) -3. Go to Network tab -4. Look for WebSocket connections -5. Find the auth message containing your session ID - -## ๐ŸŽฏ API Reference - -### AsyncPocketOptionClient - -#### Constructor -```python -client = AsyncPocketOptionClient( - session_id: str, # Your PocketOption session ID - is_demo: bool = True, # Use demo account - timeout: float = 30.0 # Default timeout for operations -) -``` - -#### Connection Methods -```python -await client.connect(regions: List[str] = None) # Connect to WebSocket -await client.disconnect() # Disconnect -client.is_connected -> bool # Check connection status -``` - -#### Trading Methods -```python -# Get balance -balance = await client.get_balance() - -# Place order -order_result = await client.place_order( - asset: str, # Asset symbol - amount: float, # Order amount - direction: OrderDirection, # CALL or PUT - duration: int # Duration in seconds -) - -# Check order result -result = await client.check_order_result(order_id: str) - -# Get active orders -active_orders = await client.get_active_orders() -``` - -#### Data Methods -```python -# Get candles -candles = await client.get_candles( - asset: str, # Asset symbol - timeframe: Union[str, int], # '1m', '5m', '1h' or seconds - count: int = 100, # Number of candles - end_time: datetime = None # End time (default: now) -) - -# Get candles as DataFrame -df = await client.get_candles_dataframe( - asset: str, - timeframe: Union[str, int], - count: int = 100, - end_time: datetime = None -) -``` - -#### Event Callbacks -```python -# Add event callback -client.add_event_callback(event: str, callback: Callable) - -# Remove event callback -client.remove_event_callback(event: str, callback: Callable) -``` - -### Available Events - -- `authenticated`: Authentication successful -- `balance_updated`: Balance changed -- `order_opened`: Order placed successfully -- `order_closed`: Order completed -- `disconnected`: WebSocket disconnected - -### Models - -#### Balance -```python -balance = Balance( - balance: float, # Account balance - currency: str = "USD", # Currency - is_demo: bool = True, # Demo account flag - last_updated: datetime # Last update time -) -``` - -#### Order -```python -order = Order( - asset: str, # Asset symbol - amount: float, # Order amount - direction: OrderDirection, # CALL or PUT - duration: int, # Duration in seconds - request_id: str = None # Auto-generated if None -) -``` - -#### OrderResult -```python -result = OrderResult( - order_id: str, # Order ID - asset: str, # Asset symbol - amount: float, # Order amount - direction: OrderDirection, # CALL or PUT - duration: int, # Duration in seconds - status: OrderStatus, # Order status - placed_at: datetime, # Placement time - expires_at: datetime, # Expiration time - profit: float = None, # Profit/loss - payout: float = None # Payout amount -) -``` - -## ๐Ÿงช Testing - -```bash -# Run all tests -python -m pytest tests/ -v - -# Run with coverage -python -m pytest tests/ --cov=pocketoptionapi_async --cov-report=html - -# Run specific test -python -m pytest tests/test_async_api.py::TestAsyncPocketOptionClient::test_connect_success -v -``` - -## ๐Ÿ“ Examples - -Check the `examples/` directory for comprehensive examples: - -- `async_examples.py`: Basic usage examples -- Advanced trading strategies -- Real-time monitoring -- Multiple order management - -## ๐Ÿ”’ Error Handling - -The API uses custom exceptions for better error handling: - -```python -from pocketoptionapi_async import ( - PocketOptionError, # Base exception - ConnectionError, # Connection issues - AuthenticationError, # Auth failures - OrderError, # Order problems - TimeoutError, # Operation timeouts - InvalidParameterError # Invalid parameters -) - -try: - await client.place_order(...) -except OrderError as e: - print(f"Order failed: {e.message}") -except ConnectionError as e: - print(f"Connection issue: {e.message}") -``` - -## ๐Ÿšจ Rate Limiting - -The API includes built-in rate limiting: - -```python -from pocketoptionapi_async.utils import RateLimiter - -# Create rate limiter (100 calls per minute) -limiter = RateLimiter(max_calls=100, time_window=60) - -# Use before API calls -await limiter.acquire() -await client.place_order(...) -``` - -## ๐Ÿ”ง Utilities - -### Retry Decorator -```python -from pocketoptionapi_async.utils import retry_async - -@retry_async(max_attempts=3, delay=1.0, backoff_factor=2.0) -async def unreliable_operation(): - # Your code here - pass -``` - -### Performance Monitor -```python -from pocketoptionapi_async.utils import performance_monitor - -@performance_monitor -async def monitored_function(): - # Execution time will be logged - pass -``` - -## ๐Ÿ“ˆ Migration from Old API - -### Old Synchronous Code -```python -# Old way -from pocketoptionapi.stable_api import PocketOption - -api = PocketOption(ssid) -api.connect() -balance = api.get_balance() -result, order_id = api.buy(1.0, "EURUSD_otc", "call", 60) -``` - -### New Async Code -```python -# New way -from pocketoptionapi_async import AsyncPocketOptionClient, OrderDirection - -async def main(): - async with AsyncPocketOptionClient(ssid, is_demo=True) as client: - balance = await client.get_balance() - result = await client.place_order( - asset="EURUSD_otc", - amount=1.0, - direction=OrderDirection.CALL, - duration=60 - ) - -asyncio.run(main()) -``` - -## ๐Ÿค Contributing - -1. Fork the repository -2. Create a feature branch -3. Write tests for your changes -4. Ensure all tests pass -5. Submit a pull request - -## ๐Ÿ“„ License - -This project is licensed under the MIT License - see the LICENSE file for details. - -## โš ๏ธ Disclaimer - -This software is for educational purposes only. Trading binary options involves significant risk. Always use demo accounts for testing and never trade with money you cannot afford to lose. - -## ๐Ÿ“ž Support - -- Create an issue on GitHub -- Check the examples directory -- Read the comprehensive documentation -- Join our Discord community - -## ๐Ÿ™ Acknowledgments - -- Original PocketOption API developers -- Python asyncio community -- Contributors and testers - ---- - -**Happy Trading! ๐Ÿš€** diff --git a/README_ENHANCED.md b/README_ENHANCED.md deleted file mode 100644 index 676ff75..0000000 --- a/README_ENHANCED.md +++ /dev/null @@ -1,521 +0,0 @@ -# PocketOption Async API - Enhanced Edition - -Check BinaryOptionToolsv2: [https://github.com/ChipaDevTeam/BinaryOptionsTools-v2](https://github.com/ChipaDevTeam/BinaryOptionsTools-v2)
- -## Support us -join PocketOption with our affiliate link: [PocketOption Affiliate link](https://u3.shortink.io/smart/SDIaxbeamcYYqB)
-donate in paypal: [Paypal.me](https://paypal.me/ChipaCL?country.x=CL&locale.x=en_US)
-help us in patreon: [Patreon](https://patreon.com/VigoDEV?utm_medium=unknown&utm_source=join_link&utm_campaign=creatorshare_creator&utm_content=copyLink)
- -## check our bots and talk to us! -Join our comunity --> [discord](https://discord.com/invite/kaZ8uV9b6k) - -A comprehensive, modern async Python API for PocketOption trading platform with advanced features including persistent connections, monitoring, and extensive testing frameworks. - -## ๐Ÿš€ Key Features - -### โœจ Enhanced Connection Management -- **Complete SSID Format Support**: Works with full authentication strings from browser (format: `42["auth",{"session":"...","isDemo":1,"uid":...,"platform":1}]`) -- **Persistent Connections**: Automatic keep-alive with 20-second ping intervals (like the original API) -- **Auto-Reconnection**: Intelligent reconnection with multiple region fallback -- **Connection Pooling**: Optimized connection management for better performance - -### ๐Ÿ” Advanced Monitoring & Diagnostics -- **Real-time Monitoring**: Connection health, performance metrics, and error tracking -- **Diagnostics Reports**: Comprehensive health assessments with recommendations -- **Performance Analytics**: Response times, throughput analysis, and bottleneck detection -- **Alert System**: Automatic alerts for connection issues and performance problems - -### ๐Ÿงช Comprehensive Testing Framework -- **Load Testing**: Concurrent client simulation and stress testing -- **Integration Testing**: End-to-end validation of all components -- **Performance Benchmarks**: Automated performance analysis and optimization -- **Advanced Test Suites**: Edge cases, error scenarios, and long-running stability tests - -### โšก Performance Optimizations -- **Message Batching**: Efficient message queuing and processing -- **Concurrent Operations**: Parallel API calls for better throughput -- **Caching System**: Intelligent caching with TTL for frequently accessed data -- **Rate Limiting**: Built-in protection against API rate limits - -### ๐Ÿ›ก๏ธ Robust Error Handling -- **Graceful Degradation**: Continues operation despite individual failures -- **Automatic Recovery**: Self-healing connections and operations -- **Comprehensive Logging**: Detailed error tracking and debugging information -- **Exception Management**: Type-specific error handling and recovery strategies - -## ๐Ÿ“ฆ Installation - -```bash -# Clone the repository -git clone -cd PocketOptionAPI-3 - -# Install dependencies -pip install -r requirements.txt - -# For development -pip install -r requirements-dev.txt -``` - -## ๐Ÿ”ง Quick Start - -### Basic Usage - -```python -import asyncio -from pocketoptionapi_async.client import AsyncPocketOptionClient -from pocketoptionapi_async.models import OrderDirection, TimeFrame - -async def main(): - # Complete SSID format (get from browser dev tools) - ssid = r'42["auth",{"session":"your_session_here","isDemo":1,"uid":12345,"platform":1}]' - - # Create client with persistent connection - client = AsyncPocketOptionClient( - ssid, - is_demo=True, - persistent_connection=True, # Enable keep-alive - auto_reconnect=True # Enable auto-reconnection - ) - - try: - # Connect - await client.connect() - - # Get balance - balance = await client.get_balance() - print(f"Balance: ${balance.balance}") - - # Get candles - candles = await client.get_candles("EURUSD", TimeFrame.M1, 100) - print(f"Retrieved {len(candles)} candles") - - # Place order (demo) - order = await client.place_order("EURUSD", 10, OrderDirection.CALL, 60) - print(f"Order placed: {order.order_id}") - - finally: - await client.disconnect() - -asyncio.run(main()) -``` - -### Persistent Connection with Keep-Alive - -```python -from connection_keep_alive import ConnectionKeepAlive - -async def persistent_connection_demo(): - ssid = r'42["auth",{"session":"your_session","isDemo":1,"uid":12345,"platform":1}]' - - # Create keep-alive manager - keep_alive = ConnectionKeepAlive(ssid, is_demo=True) - - # Add event handlers - async def on_connected(data): - print(f"Connected to: {data['region']}") - - async def on_message(data): - print(f"Message: {data['message'][:50]}...") - - keep_alive.add_event_handler('connected', on_connected) - keep_alive.add_event_handler('message_received', on_message) - - try: - # Start persistent connection (runs indefinitely with auto-reconnect) - await keep_alive.start_persistent_connection() - - # Send messages - await keep_alive.send_message('42["ps"]') - - # Keep running... - await asyncio.sleep(60) - - finally: - await keep_alive.stop_persistent_connection() - -asyncio.run(persistent_connection_demo()) -``` - -## ๐Ÿ” Advanced Features - -### Connection Monitoring - -```python -from connection_monitor import ConnectionMonitor - -async def monitoring_demo(): - monitor = ConnectionMonitor(ssid, is_demo=True) - - # Add alert handlers - async def on_alert(alert): - print(f"ALERT: {alert['message']}") - - monitor.add_event_handler('alert', on_alert) - - # Start monitoring - await monitor.start_monitoring() - - # Get real-time stats - stats = monitor.get_real_time_stats() - print(f"Messages: {stats['total_messages']}, Errors: {stats['total_errors']}") - - # Generate diagnostics report - report = monitor.generate_diagnostics_report() - print(f"Health Score: {report['health_score']}/100") - - await monitor.stop_monitoring() -``` - -### Load Testing - -```python -from load_testing_tool import LoadTester, LoadTestConfig - -async def load_test_demo(): - tester = LoadTester(ssid, is_demo=True) - - config = LoadTestConfig( - concurrent_clients=5, - operations_per_client=20, - use_persistent_connection=True, - stress_mode=False - ) - - report = await tester.run_load_test(config) - - print(f"Throughput: {report['test_summary']['avg_operations_per_second']:.1f} ops/sec") - print(f"Success Rate: {report['test_summary']['success_rate']:.1%}") -``` - -### Integration Testing - -```python -from integration_tests import IntegrationTester - -async def integration_test_demo(): - tester = IntegrationTester(ssid) - report = await tester.run_full_integration_tests() - - print(f"Health Score: {report['integration_summary']['health_score']:.1f}/100") - print(f"Tests Passed: {report['integration_summary']['passed_tests']}") -``` - -## ๐ŸŽฏ Getting Your SSID - -To use the API with real data, you need to extract your session ID from the browser: - -1. **Open PocketOption in your browser** -2. **Open Developer Tools (F12)** -3. **Go to Network tab** -4. **Filter by WebSocket (WS)** -5. **Look for authentication message starting with `42["auth"`** -6. **Copy the complete message including the `42["auth",{...}]` format** - -Example SSID format: -``` -42["auth",{"session":"abcd1234efgh5678","isDemo":1,"uid":12345,"platform":1}] -``` - -## ๐Ÿ“Š Monitoring and Diagnostics - -### Real-time Connection Monitor - -```bash -# Start real-time monitoring -python connection_monitor.py "your_ssid_here" -``` - -Features: -- Real-time connection status -- Performance metrics -- Error tracking -- Health score calculation -- Automatic alerts -- CSV export - -### Advanced Testing Suite - -```bash -# Run comprehensive tests -python advanced_testing_suite.py "your_ssid_here" -``` - -Includes: -- Connection stress tests -- Concurrent operations -- Data consistency checks -- Error handling validation -- Performance benchmarks -- Memory usage analysis - -### Load Testing - -```bash -# Run load tests -python load_testing_tool.py "your_ssid_here" -``` - -Capabilities: -- Multiple concurrent clients -- Stress testing modes -- Performance analysis -- Bottleneck identification -- Recommendation generation - -## ๐Ÿ—๏ธ Architecture - -### Component Overview - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ PocketOption Async API โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ AsyncPocketOptionClient (Main API Client) โ”‚ -โ”‚ โ”œโ”€โ”€ WebSocket Client (Connection Management) โ”‚ -โ”‚ โ”œโ”€โ”€ Keep-Alive Manager (Persistent Connections) โ”‚ -โ”‚ โ””โ”€โ”€ Event System (Callbacks & Handlers) โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Advanced Features โ”‚ -โ”‚ โ”œโ”€โ”€ Connection Monitor (Real-time Monitoring) โ”‚ -โ”‚ โ”œโ”€โ”€ Load Tester (Performance Testing) โ”‚ -โ”‚ โ”œโ”€โ”€ Integration Tester (E2E Validation) โ”‚ -โ”‚ โ””โ”€โ”€ Advanced Test Suite (Comprehensive Testing) โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Infrastructure โ”‚ -โ”‚ โ”œโ”€โ”€ Models & Types (Data Structures) โ”‚ -โ”‚ โ”œโ”€โ”€ Constants & Config (Settings) โ”‚ -โ”‚ โ”œโ”€โ”€ Exceptions (Error Handling) โ”‚ -โ”‚ โ””โ”€โ”€ Utils (Helper Functions) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### Connection Flow - -```mermaid -graph TD - A[Client Initialize] --> B{Persistent?} - B -->|Yes| C[ConnectionKeepAlive] - B -->|No| D[Regular Connection] - - C --> E[Multi-Region Connect] - D --> E - - E --> F[Authentication] - F --> G{Success?} - G -->|Yes| H[Start Keep-Alive Tasks] - G -->|No| I[Retry with Next Region] - - H --> J[Ping Loop 20s] - H --> K[Message Processing] - H --> L[Health Monitoring] - - I --> E - - J --> M{Connected?} - M -->|Yes| J - M -->|No| N[Auto Reconnect] - N --> E -``` - -## ๐Ÿงช Testing - -### Run All Tests - -```bash -# Complete integration test suite -python integration_tests.py "your_ssid_here" - -# Advanced test scenarios -python advanced_testing_suite.py "your_ssid_here" - -# Load and stress testing -python load_testing_tool.py "your_ssid_here" - -# Comprehensive demo -python comprehensive_demo.py "your_ssid_here" -``` - -### Test Categories - -1. **Unit Tests**: Individual component testing -2. **Integration Tests**: End-to-end functionality -3. **Load Tests**: Performance under load -4. **Stress Tests**: Extreme condition handling -5. **Monitoring Tests**: Real-time diagnostics -6. **Error Recovery Tests**: Failure scenarios - -## ๐Ÿ“ˆ Performance - -### Benchmarks - -Typical performance metrics (may vary based on network and server conditions): - -- **Connection Time**: < 2 seconds -- **Message Latency**: < 100ms -- **Throughput**: 10-50 operations/second -- **Memory Usage**: < 50MB for standard operations -- **CPU Usage**: < 5% during normal operation - -### Optimization Features - -- **Connection Pooling**: Reuse connections across operations -- **Message Batching**: Group messages for efficiency -- **Caching**: Store frequently accessed data -- **Concurrent Operations**: Parallel processing -- **Rate Limiting**: Prevent API overload - -## ๐Ÿ›ก๏ธ Error Handling - -### Automatic Recovery - -- **Connection Loss**: Auto-reconnect with exponential backoff -- **Authentication Errors**: Session refresh and retry -- **Network Issues**: Multiple region fallback -- **API Errors**: Graceful degradation and retry logic - -### Error Categories - -1. **Connection Errors**: Network and WebSocket issues -2. **Authentication Errors**: Invalid or expired sessions -3. **API Errors**: Invalid parameters or server errors -4. **Timeout Errors**: Operation timeout handling -5. **Rate Limit Errors**: API quota exceeded - -## ๐Ÿ”ง Configuration - -### Environment Variables - -```bash -# Optional configuration -export POCKETOPTION_DEBUG=true -export POCKETOPTION_LOG_LEVEL=DEBUG -export POCKETOPTION_MAX_RETRIES=5 -export POCKETOPTION_TIMEOUT=30 -``` - -### Client Configuration - -```python -client = AsyncPocketOptionClient( - ssid="your_ssid", - is_demo=True, # Demo/Live mode - region="eu", # Preferred region - persistent_connection=True, # Keep-alive enabled - auto_reconnect=True, # Auto-reconnection - uid=12345, # User ID - platform=1 # Platform identifier -) -``` - -## ๐Ÿ“š API Reference - -### AsyncPocketOptionClient - -Main client class for API operations. - -```python -class AsyncPocketOptionClient: - async def connect(regions: List[str] = None, persistent: bool = None) -> bool - async def disconnect() -> None - async def get_balance() -> Balance - async def get_candles(asset: str, timeframe: TimeFrame, count: int) -> List[Candle] - async def place_order(asset: str, amount: float, direction: OrderDirection, duration: int) -> OrderResult - async def send_message(message: str) -> bool - def get_connection_stats() -> Dict[str, Any] - def add_event_callback(event: str, callback: Callable) -> None -``` - -### ConnectionKeepAlive - -Advanced connection management with keep-alive. - -```python -class ConnectionKeepAlive: - async def start_persistent_connection() -> bool - async def stop_persistent_connection() -> None - async def send_message(message: str) -> bool - def add_event_handler(event: str, handler: Callable) -> None - def get_connection_stats() -> Dict[str, Any] -``` - -### ConnectionMonitor - -Real-time monitoring and diagnostics. - -```python -class ConnectionMonitor: - async def start_monitoring(persistent_connection: bool = True) -> bool - async def stop_monitoring() -> None - def get_real_time_stats() -> Dict[str, Any] - def generate_diagnostics_report() -> Dict[str, Any] - def export_metrics_csv(filename: str = None) -> str -``` - -## ๐Ÿค Contributing - -1. Fork the repository -2. Create a feature branch -3. Make your changes -4. Add tests for new functionality -5. Ensure all tests pass -6. Submit a pull request - -### Development Setup - -```bash -# Clone and setup -git clone -cd PocketOptionAPI-3 - -# Install development dependencies -pip install -r requirements-dev.txt - -# Run tests -python -m pytest tests/ - -# Run linting -flake8 pocketoptionapi_async/ -mypy pocketoptionapi_async/ -``` - -## ๐Ÿ“„ License - -This project is licensed under the MIT License - see the LICENSE file for details. - -## โš ๏ธ Disclaimer - -This software is for educational and research purposes only. Trading involves substantial risk and may not be suitable for all individuals. Past performance does not guarantee future results. Please trade responsibly and consider your financial situation before using this API for live trading. - -## ๐Ÿ†˜ Support - -- **Issues**: Report bugs or request features via GitHub Issues -- **Documentation**: See examples in the `examples/` directory -- **Discussions**: Join discussions in GitHub Discussions - -## ๐Ÿ† Changelog - -### v2.0.0 - Enhanced Edition - -- โœ… Complete SSID format support -- โœ… Persistent connections with keep-alive -- โœ… Advanced monitoring and diagnostics -- โœ… Comprehensive testing frameworks -- โœ… Performance optimizations -- โœ… Enhanced error handling -- โœ… Real-time connection monitoring -- โœ… Load testing capabilities -- โœ… Integration testing suite -- โœ… Modern async architecture - -### v1.0.0 - Initial Release - -- Basic async API client -- WebSocket connection management -- Core trading operations -- Error handling - ---- - -**Built with โค๏ธ for the PocketOption community** diff --git a/SSID/driver.py b/SSID/driver.py new file mode 100644 index 0000000..6303e06 --- /dev/null +++ b/SSID/driver.py @@ -0,0 +1,116 @@ +import os +import logging +from selenium import webdriver +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.chrome.options import Options as ChromeOptions +from selenium.webdriver.firefox.service import Service as FirefoxService +from selenium.webdriver.firefox.options import Options as FirefoxOptions +from webdriver_manager.chrome import ( + ChromeDriverManager, +) # Automatically downloads and manages ChromeDriver. +from webdriver_manager.firefox import ( + GeckoDriverManager, +) # Automatically downloads and manages GeckoDriver. + +# Configure logging for this module to provide clear output. +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(levelname)s - [%(threadName)s] - %(message)s", +) +logger = logging.getLogger(__name__) + + +def get_driver(browser_name: str = "chrome"): + """ + Initializes and returns a Selenium WebDriver instance for the specified browser. + Automatically handles driver downloads and configuration, and allows for persistent sessions + by storing browser profiles. + + Args: + browser_name: The name of the browser to use ('chrome' or 'firefox'). Defaults to 'chrome'. + + Returns: + A configured Selenium WebDriver instance. + + Raises: + ValueError: If an unsupported browser name is provided. + """ + # Define a base directory for storing browser profiles to maintain cookies, sessions, and logins. + # This allows for persistent sessions across multiple script runs. + base_profile_dir = os.path.join(os.getcwd(), "browser_profiles") + os.makedirs(base_profile_dir, exist_ok=True) + + if browser_name.lower() == "chrome": + chrome_options = ChromeOptions() + + # Define the path for the Chrome user data directory. Using a persistent directory + # allows Selenium to remember cookies, cache, and login sessions. + user_data_dir = os.path.join(base_profile_dir, "chrome_profile") + chrome_options.add_argument(f"--user-data-dir={user_data_dir}") + + # Add various arguments to optimize browser operation for automation. + chrome_options.add_argument( + "--disable-gpu" + ) # Disable GPU hardware acceleration, which can cause issues in some environments. + chrome_options.add_argument( + "--no-sandbox" + ) # Bypass OS security model; necessary for running as root in Docker/Linux. + chrome_options.add_argument( + "--disable-dev-shm-usage" + ) # Overcome limited resource problems in Docker and certain CI/CD environments. + chrome_options.add_argument( + "--window-size=1920,1080" + ) # Set a consistent window size for predictable rendering. + chrome_options.add_argument("--start-maximized") # Start the browser maximized. + chrome_options.add_argument( + "--log-level=3" + ) # Suppress excessive console logging from Chrome itself. + chrome_options.add_experimental_option("excludeSwitches", ["enable-logging"]) + + # Enable performance logging to capture network events, which can be useful for + # monitoring network traffic or waiting for specific resources to load. + chrome_options.set_capability("goog:loggingPrefs", {"performance": "ALL"}) + + logger.info("Initializing Chrome WebDriver...") + try: + # Use ChromeDriverManager to automatically download and manage the appropriate ChromeDriver. + service = Service(ChromeDriverManager().install()) + driver = webdriver.Chrome(service=service, options=chrome_options) + logger.info("Chrome WebDriver initialized successfully.") + return driver + except Exception as e: + logger.error(f"Error initializing Chrome WebDriver: {e}") + raise + + elif browser_name.lower() == "firefox": + firefox_options = FirefoxOptions() + + # Set up a persistent profile for Firefox to maintain sessions and logins. + profile_dir = os.path.join(base_profile_dir, "firefox_profile") + os.makedirs(profile_dir, exist_ok=True) + firefox_options.profile = webdriver.FirefoxProfile(profile_dir) + + # Set window size for consistent rendering. + firefox_options.add_argument("--width=1920") + firefox_options.add_argument("--height=1080") + + # Attempt to enable network logging persistence in Firefox developer tools. + firefox_options.set_capability( + "moz:firefoxOptions", {"prefs": {"devtools.netmonitor.persistlog": True}} + ) + + logger.info("Initializing Firefox WebDriver...") + try: + # Use GeckoDriverManager to automatically download and manage the appropriate GeckoDriver. + service = FirefoxService(GeckoDriverManager().install()) + driver = webdriver.Firefox(service=service, options=firefox_options) + logger.info("Firefox WebDriver initialized successfully.") + return driver + except Exception as e: + logger.error(f"Error initializing Firefox WebDriver: {e}") + raise + + else: + raise ValueError( + f"Unsupported browser: {browser_name}. Please choose 'chrome' or 'firefox'." + ) diff --git a/SSID/get_ssid.py b/SSID/get_ssid.py new file mode 100644 index 0000000..3403302 --- /dev/null +++ b/SSID/get_ssid.py @@ -0,0 +1,141 @@ +import os +import json +import time +import re +import logging +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from driver import get_driver + +# Configure logging for this script to provide clear, structured output. +# Logs will be directed to standard output, making them compatible with containerization +# and centralized log collection systems. +logging.basicConfig( + level=logging.INFO, + format='{"timestamp": "%(asctime)s", "level": "%(levelname)s", "module": "%(name)s", "message": "%(message)s"}', +) +logger = logging.getLogger(__name__) + + +def save_to_env(key: str, value: str): + """ + Saves or updates a key-value pair in the .env file. + If the key already exists, its value is updated. Otherwise, the new key-value pair is added. + + Args: + key: The environment variable key (e.g., "SSID"). + value: The value to be associated with the key. + """ + env_path = os.path.join(os.getcwd(), ".env") + lines = [] + found = False + + # Read existing .env file content + if os.path.exists(env_path): + with open(env_path, "r") as f: + for line in f: + if line.strip().startswith(f"{key}="): + # Update existing key + lines.append(f'{key}="{value}"\n') + found = True + else: + lines.append(line) + + if not found: + # Add new key if not found + lines.append(f'{key}="{value}"\n') + + # Write updated content back to .env file + with open(env_path, "w") as f: + f.writelines(lines) + logger.info(f"Successfully saved {key} to .env file.") + + +def get_pocketoption_ssid(): + """ + Automates the process of logging into PocketOption, navigating to a specific cabinet page, + and then scraping WebSocket traffic to extract the session ID (SSID). + The extracted SSID is then saved to the .env file. + """ + driver = None + try: + # Initialize the Selenium WebDriver using the helper function from driver.py. + # This ensures the browser profile is persistent for easier logins. + driver = get_driver("chrome") + login_url = "https://pocketoption.com/en/login" + cabinet_base_url = "https://pocketoption.com/en/cabinet" + target_cabinet_url = "https://pocketoption.com/en/cabinet/demo-quick-high-low/" + # Regex to capture the entire "42[\"auth\",{...}]" string. + # This pattern is designed to be robust and capture the full authentication message, + # regardless of the specific content of the 'session' field (e.g., simple string or serialized PHP array). + ssid_pattern = r'(42\["auth",\{"session":"[^"]+","isDemo":\d+,"uid":\d+,"platform":\d+,"isFastHistory":(?:true|false)\}\])' + + logger.info(f"Navigating to login page: {login_url}") + driver.get(login_url) + + # Wait indefinitely for the user to manually log in and be redirected to the cabinet base page. + # This uses an explicit wait condition to check if the current URL contains the cabinet_base_url. + logger.info(f"Waiting for user to login and redirect to {cabinet_base_url}...") + WebDriverWait(driver, 9999).until(EC.url_contains(cabinet_base_url)) + logger.info("Login successful. Redirected to cabinet base page.") + + # Now navigate to the specific target URL within the cabinet. + logger.info(f"Navigating to target cabinet page: {target_cabinet_url}") + driver.get(target_cabinet_url) + + # Wait for the target cabinet URL to be fully loaded. + # This ensures that any WebSocket connections initiated on this page are established. + WebDriverWait(driver, 60).until(EC.url_contains(target_cabinet_url)) + logger.info("Successfully navigated to the target cabinet page.") + + # Give the page some time to load all WebSocket connections and messages after redirection. + # This delay helps ensure that the relevant WebSocket frames are captured in the logs. + time.sleep(5) + + # Retrieve performance logs which include network requests and WebSocket frames. + # These logs are crucial for capturing the raw WebSocket messages. + performance_logs = driver.get_log("performance") + logger.info(f"Collected {len(performance_logs)} performance log entries.") + + found_full_ssid_string = None + # Iterate through the performance logs to find WebSocket frames. + for entry in performance_logs: + message = json.loads(entry["message"]) + # Check if the log entry is a WebSocket frame (either sent or received) + # and contains the desired payload data. + if ( + message["message"]["method"] == "Network.webSocketFrameReceived" + or message["message"]["method"] == "Network.webSocketFrameSent" + ): + payload_data = message["message"]["params"]["response"]["payloadData"] + # Attempt to find the full SSID string using the defined regex pattern. + match = re.search(ssid_pattern, payload_data) + if match: + # Capture the entire matched group as the full SSID string. + found_full_ssid_string = match.group(1) + logger.info( + f"Found full SSID string in WebSocket payload: {found_full_ssid_string}" + ) + # Break after finding the first match as it's likely the correct one. + break + + if found_full_ssid_string: + # Save the extracted full SSID string to the .env file. + save_to_env("SSID", found_full_ssid_string) + logger.info("Full SSID string successfully extracted and saved to .env.") + else: + logger.warning( + "Full SSID string pattern not found in WebSocket logs after login." + ) + + except Exception as e: + logger.error(f"An error occurred: {e}", exc_info=True) + finally: + # Ensure the WebDriver is closed even if an error occurs to free up resources. + if driver: + driver.quit() + logger.info("WebDriver closed.") + + +if __name__ == "__main__": + get_pocketoption_ssid() diff --git a/TODO/todo.md b/TODO/todo.md new file mode 100644 index 0000000..d1f45cb --- /dev/null +++ b/TODO/todo.md @@ -0,0 +1,6 @@ +# TO-DO List + +### Add Template for a basic PO bot + - TBD +### Organize files more efficiently + - TBD \ No newline at end of file diff --git a/advanced_testing_suite.py b/advanced_testing_suite.py index 2ddf2e5..58e8782 100644 --- a/advanced_testing_suite.py +++ b/advanced_testing_suite.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Advanced Testing Suite for PocketOption Async API Tests edge cases, performance, and advanced scenarios @@ -8,10 +7,9 @@ import time import random import json -from datetime import datetime, timedelta -from typing import List, Dict, Any, Optional +from datetime import datetime +from typing import Dict, Any from loguru import logger -import pandas as pd from pocketoptionapi_async.client import AsyncPocketOptionClient from pocketoptionapi_async.models import OrderDirection, TimeFrame @@ -20,16 +18,16 @@ class AdvancedTestSuite: """Advanced testing suite for the API""" - + def __init__(self, ssid: str): self.ssid = ssid self.test_results = {} self.performance_metrics = {} - + async def run_all_tests(self) -> Dict[str, Any]: """Run comprehensive test suite""" - logger.info("๐Ÿงช Starting Advanced Testing Suite") - + logger.info("Testing: Starting Advanced Testing Suite") + tests = [ ("Connection Stress Test", self.test_connection_stress), ("Concurrent Operations Test", self.test_concurrent_operations), @@ -40,388 +38,414 @@ async def run_all_tests(self) -> Dict[str, Any]: ("Network Resilience Test", self.test_network_resilience), ("Long Running Session Test", self.test_long_running_session), ("Multi-Asset Operations", self.test_multi_asset_operations), - ("Rapid Trading Simulation", self.test_rapid_trading_simulation) + ("Rapid Trading Simulation", self.test_rapid_trading_simulation), ] - + for test_name, test_func in tests: - logger.info(f"๐Ÿ” Running: {test_name}") + logger.info(f"Analysis: Running: {test_name}") try: start_time = time.time() result = await test_func() end_time = time.time() - + self.test_results[test_name] = { - 'status': 'PASSED' if result else 'FAILED', - 'result': result, - 'duration': end_time - start_time + "status": "PASSED" if result else "FAILED", + "result": result, + "duration": end_time - start_time, } - - logger.success(f"โœ… {test_name}: {'PASSED' if result else 'FAILED'}") - + + logger.success( + f"Success: {test_name}: {'PASSED' if result else 'FAILED'}" + ) + except Exception as e: self.test_results[test_name] = { - 'status': 'ERROR', - 'error': str(e), - 'duration': 0 + "status": "ERROR", + "error": str(e), + "duration": 0, } - logger.error(f"โŒ {test_name}: ERROR - {e}") - + logger.error(f"Error: {test_name}: ERROR - {e}") + return self._generate_test_report() - + async def test_connection_stress(self) -> bool: """Test connection under stress conditions""" - logger.info("๐Ÿ”ฅ Testing connection stress resistance...") - + logger.info("Stress: Testing connection stress resistance...") + try: client = AsyncPocketOptionClient(self.ssid, persistent_connection=True) - + # Connect and disconnect rapidly for i in range(5): - logger.info(f"Connection cycle {i+1}/5") + logger.info(f"Connection cycle {i + 1}/5") success = await client.connect() if not success: return False - + await asyncio.sleep(2) await client.disconnect() await asyncio.sleep(1) - + # Final connection for stability test success = await client.connect() if not success: return False - + # Send rapid messages for i in range(50): await client.send_message('42["ps"]') await asyncio.sleep(0.1) - + await client.disconnect() return True - + except Exception as e: logger.error(f"Connection stress test failed: {e}") return False - + async def test_concurrent_operations(self) -> bool: """Test concurrent API operations""" - logger.info("โšก Testing concurrent operations...") - + logger.info("Performance: Testing concurrent operations...") + try: client = AsyncPocketOptionClient(self.ssid, persistent_connection=True) await client.connect() - + # Concurrent tasks async def get_balance_task(): for _ in range(10): await client.get_balance() await asyncio.sleep(0.5) - + async def get_candles_task(): for _ in range(5): await client.get_candles("EURUSD", TimeFrame.M1, 50) await asyncio.sleep(1) - + async def ping_task(): for _ in range(20): await client.send_message('42["ps"]') await asyncio.sleep(0.3) - + # Run concurrently await asyncio.gather( get_balance_task(), get_candles_task(), ping_task(), - return_exceptions=True + return_exceptions=True, ) - + await client.disconnect() return True - + except Exception as e: logger.error(f"Concurrent operations test failed: {e}") return False - + async def test_data_consistency(self) -> bool: """Test data consistency across multiple requests""" - logger.info("๐Ÿ“Š Testing data consistency...") - + logger.info("Statistics: Testing data consistency...") + try: client = AsyncPocketOptionClient(self.ssid) await client.connect() - + # Get balance multiple times balances = [] for i in range(5): balance = await client.get_balance() balances.append(balance.balance) await asyncio.sleep(1) - + # Check if balance is consistent (allowing for small variations) if len(set(balances)) > 2: # Allow for some variation logger.warning(f"Balance inconsistency detected: {balances}") - + # Get candles and check consistency candles1 = await client.get_candles("EURUSD", TimeFrame.M1, 10) await asyncio.sleep(2) candles2 = await client.get_candles("EURUSD", TimeFrame.M1, 10) - + # Most candles should be the same (except maybe the latest) - consistent_candles = sum(1 for c1, c2 in zip(candles1[:-1], candles2[:-1]) - if c1.open == c2.open and c1.close == c2.close) - - consistency_ratio = consistent_candles / len(candles1[:-1]) if len(candles1) > 1 else 1 + consistent_candles = sum( + 1 + for c1, c2 in zip(candles1[:-1], candles2[:-1]) + if c1.open == c2.open and c1.close == c2.close + ) + + consistency_ratio = ( + consistent_candles / len(candles1[:-1]) if len(candles1) > 1 else 1 + ) logger.info(f"Data consistency ratio: {consistency_ratio:.2f}") - + await client.disconnect() return consistency_ratio > 0.8 # 80% consistency threshold - + except Exception as e: logger.error(f"Data consistency test failed: {e}") return False - + async def test_error_handling(self) -> bool: """Test error handling capabilities""" - logger.info("๐Ÿ›ก๏ธ Testing error handling...") - + logger.info("Error Handling: Testing error handling...") + try: client = AsyncPocketOptionClient(self.ssid) await client.connect() - + # Test invalid asset try: await client.get_candles("INVALID_ASSET", TimeFrame.M1, 10) logger.warning("Expected error for invalid asset didn't occur") except Exception: - logger.info("โœ… Invalid asset error handled correctly") - + logger.info("Success: Invalid asset error handled correctly") + # Test invalid order try: await client.place_order("EURUSD", -100, OrderDirection.CALL, 60) logger.warning("Expected error for negative amount didn't occur") except Exception: - logger.info("โœ… Invalid order error handled correctly") - + logger.info("Success: Invalid order error handled correctly") + # Test connection after disconnect await client.disconnect() try: await client.get_balance() logger.warning("Expected error for disconnected client didn't occur") except Exception: - logger.info("โœ… Disconnected client error handled correctly") - + logger.info("Success: Disconnected client error handled correctly") + return True - + except Exception as e: logger.error(f"Error handling test failed: {e}") return False - + async def test_performance_benchmarks(self) -> bool: """Test performance benchmarks""" - logger.info("๐Ÿš€ Running performance benchmarks...") - + logger.info("Starting: Running performance benchmarks...") + try: client = AsyncPocketOptionClient(self.ssid, persistent_connection=True) - + # Connection time benchmark start_time = time.time() await client.connect() connection_time = time.time() - start_time - + # Balance retrieval benchmark start_time = time.time() for _ in range(10): await client.get_balance() balance_time = (time.time() - start_time) / 10 - + # Candles retrieval benchmark start_time = time.time() await client.get_candles("EURUSD", TimeFrame.M1, 100) candles_time = time.time() - start_time - + # Message sending benchmark start_time = time.time() for _ in range(100): await client.send_message('42["ps"]') message_time = (time.time() - start_time) / 100 - + # Store performance metrics self.performance_metrics = { - 'connection_time': connection_time, - 'avg_balance_time': balance_time, - 'candles_retrieval_time': candles_time, - 'avg_message_time': message_time + "connection_time": connection_time, + "avg_balance_time": balance_time, + "candles_retrieval_time": candles_time, + "avg_message_time": message_time, } - - logger.info(f"๐Ÿ“ˆ Performance Metrics:") + + logger.info("Data: Performance Metrics:") logger.info(f" Connection Time: {connection_time:.3f}s") logger.info(f" Avg Balance Time: {balance_time:.3f}s") logger.info(f" Candles Retrieval: {candles_time:.3f}s") logger.info(f" Avg Message Time: {message_time:.4f}s") - + await client.disconnect() - + # Performance thresholds - return (connection_time < 10.0 and - balance_time < 2.0 and - candles_time < 5.0 and - message_time < 0.1) - + return ( + connection_time < 10.0 + and balance_time < 2.0 + and candles_time < 5.0 + and message_time < 0.1 + ) + except Exception as e: logger.error(f"Performance benchmark failed: {e}") return False - + async def test_memory_usage(self) -> bool: """Test memory usage patterns""" - logger.info("๐Ÿง  Testing memory usage...") - + logger.info("Memory: Testing memory usage...") + try: import psutil import os - + process = psutil.Process(os.getpid()) initial_memory = process.memory_info().rss / 1024 / 1024 # MB - + client = AsyncPocketOptionClient(self.ssid, persistent_connection=True) await client.connect() - + # Perform memory-intensive operations for i in range(50): await client.get_candles("EURUSD", TimeFrame.M1, 100) await client.get_balance() - + if i % 10 == 0: current_memory = process.memory_info().rss / 1024 / 1024 - logger.info(f"Memory usage after {i} operations: {current_memory:.1f} MB") - + logger.info( + f"Memory usage after {i} operations: {current_memory:.1f} MB" + ) + final_memory = process.memory_info().rss / 1024 / 1024 memory_increase = final_memory - initial_memory - + logger.info(f"Initial memory: {initial_memory:.1f} MB") logger.info(f"Final memory: {final_memory:.1f} MB") logger.info(f"Memory increase: {memory_increase:.1f} MB") - + await client.disconnect() - + # Check for memory leaks (threshold: 50MB increase) return memory_increase < 50.0 - + except ImportError: logger.warning("psutil not available, skipping memory test") return True except Exception as e: logger.error(f"Memory usage test failed: {e}") return False - + async def test_network_resilience(self) -> bool: """Test network resilience and reconnection""" - logger.info("๐ŸŒ Testing network resilience...") - + logger.info("Network: Testing network resilience...") + try: # Use keep-alive manager for this test keep_alive = ConnectionKeepAlive(self.ssid, is_demo=True) - + # Event tracking events = [] - + async def track_event(event_type): def handler(data): - events.append({'type': event_type, 'time': datetime.now(), 'data': data}) + events.append( + {"type": event_type, "time": datetime.now(), "data": data} + ) + return handler - - keep_alive.add_event_handler('connected', await track_event('connected')) - keep_alive.add_event_handler('reconnected', await track_event('reconnected')) - keep_alive.add_event_handler('message_received', await track_event('message')) - + + keep_alive.add_event_handler("connected", await track_event("connected")) + keep_alive.add_event_handler( + "reconnected", await track_event("reconnected") + ) + keep_alive.add_event_handler( + "message_received", await track_event("message") + ) + # Start connection success = await keep_alive.start_persistent_connection() if not success: return False - + # Let it run for a bit await asyncio.sleep(10) - + # Simulate network issues by stopping/starting await keep_alive.stop_persistent_connection() await asyncio.sleep(3) - + # Restart and check resilience success = await keep_alive.start_persistent_connection() if not success: return False - + await asyncio.sleep(5) await keep_alive.stop_persistent_connection() - + # Check events - connected_events = [e for e in events if e['type'] == 'connected'] - message_events = [e for e in events if e['type'] == 'message'] - - logger.info(f"Network resilience test: {len(connected_events)} connections, {len(message_events)} messages") - + connected_events = [e for e in events if e["type"] == "connected"] + message_events = [e for e in events if e["type"] == "message"] + + logger.info( + f"Network resilience test: {len(connected_events)} connections, {len(message_events)} messages" + ) + return len(connected_events) >= 2 and len(message_events) > 0 - + except Exception as e: logger.error(f"Network resilience test failed: {e}") return False - + async def test_long_running_session(self) -> bool: """Test long-running session stability""" - logger.info("โฐ Testing long-running session...") - + logger.info("Long-running: Testing long-running session...") + try: client = AsyncPocketOptionClient(self.ssid, persistent_connection=True) await client.connect() - + start_time = datetime.now() operations_count = 0 errors_count = 0 - + # Run for 2 minutes while (datetime.now() - start_time).total_seconds() < 120: try: # Perform various operations - operation = random.choice(['balance', 'candles', 'ping']) - - if operation == 'balance': + operation = random.choice(["balance", "candles", "ping"]) + + if operation == "balance": await client.get_balance() - elif operation == 'candles': - asset = random.choice(['EURUSD', 'GBPUSD', 'USDJPY']) + elif operation == "candles": + asset = random.choice(["EURUSD", "GBPUSD", "USDJPY"]) await client.get_candles(asset, TimeFrame.M1, 10) - elif operation == 'ping': + elif operation == "ping": await client.send_message('42["ps"]') - + operations_count += 1 - + except Exception as e: errors_count += 1 logger.warning(f"Operation error: {e}") - + await asyncio.sleep(random.uniform(1, 3)) - - success_rate = (operations_count - errors_count) / operations_count if operations_count > 0 else 0 - - logger.info(f"Long-running session: {operations_count} operations, {errors_count} errors") + + success_rate = ( + (operations_count - errors_count) / operations_count + if operations_count > 0 + else 0 + ) + + logger.info( + f"Long-running session: {operations_count} operations, {errors_count} errors" + ) logger.info(f"Success rate: {success_rate:.2%}") - + await client.disconnect() - + return success_rate > 0.9 # 90% success rate - + except Exception as e: logger.error(f"Long-running session test failed: {e}") return False - + async def test_multi_asset_operations(self) -> bool: """Test operations across multiple assets""" - logger.info("๐Ÿ“ˆ Testing multi-asset operations...") - + logger.info("Retrieved: Testing multi-asset operations...") + try: client = AsyncPocketOptionClient(self.ssid) await client.connect() - - assets = ['EURUSD', 'GBPUSD', 'USDJPY', 'USDCAD', 'AUDUSD'] - + + assets = ["EURUSD", "GBPUSD", "USDJPY", "USDCAD", "AUDUSD"] + # Get candles for multiple assets concurrently async def get_asset_candles(asset): try: @@ -430,153 +454,175 @@ async def get_asset_candles(asset): except Exception as e: logger.warning(f"Failed to get candles for {asset}: {e}") return asset, 0, False - - results = await asyncio.gather(*[get_asset_candles(asset) for asset in assets]) - + + results = await asyncio.gather( + *[get_asset_candles(asset) for asset in assets] + ) + successful_assets = sum(1 for _, _, success in results if success) total_candles = sum(count for _, count, _ in results) - - logger.info(f"Multi-asset test: {successful_assets}/{len(assets)} assets successful") + + logger.info( + f"Multi-asset test: {successful_assets}/{len(assets)} assets successful" + ) logger.info(f"Total candles retrieved: {total_candles}") - + await client.disconnect() - + return successful_assets >= len(assets) * 0.8 # 80% success rate - + except Exception as e: logger.error(f"Multi-asset operations test failed: {e}") return False - + async def test_rapid_trading_simulation(self) -> bool: """Simulate rapid trading operations""" - logger.info("โšก Testing rapid trading simulation...") - + logger.info("Performance: Testing rapid trading simulation...") + try: client = AsyncPocketOptionClient(self.ssid) await client.connect() - + # Simulate rapid order operations (without actually placing real orders) operations = [] - + for i in range(20): try: # Get balance before "trade" balance = await client.get_balance() - + # Get current market data candles = await client.get_candles("EURUSD", TimeFrame.M1, 5) - + # Simulate order decision (don't actually place) - direction = OrderDirection.CALL if len(candles) % 2 == 0 else OrderDirection.PUT + direction = ( + OrderDirection.CALL + if len(candles) % 2 == 0 + else OrderDirection.PUT + ) amount = random.uniform(1, 10) - - operations.append({ - 'balance': balance.balance, - 'direction': direction, - 'amount': amount, - 'candles_count': len(candles), - 'timestamp': datetime.now() - }) - + + operations.append( + { + "balance": balance.balance, + "direction": direction, + "amount": amount, + "candles_count": len(candles), + "timestamp": datetime.now(), + } + ) + await asyncio.sleep(0.5) # Rapid operations - + except Exception as e: logger.warning(f"Rapid trading simulation error: {e}") - + await client.disconnect() - - logger.info(f"Rapid trading simulation: {len(operations)} operations completed") - + + logger.info( + f"Rapid trading simulation: {len(operations)} operations completed" + ) + return len(operations) >= 18 # 90% completion rate - + except Exception as e: logger.error(f"Rapid trading simulation failed: {e}") return False - + def _generate_test_report(self) -> Dict[str, Any]: """Generate comprehensive test report""" - + total_tests = len(self.test_results) - passed_tests = sum(1 for result in self.test_results.values() if result['status'] == 'PASSED') - failed_tests = sum(1 for result in self.test_results.values() if result['status'] == 'FAILED') - error_tests = sum(1 for result in self.test_results.values() if result['status'] == 'ERROR') - - total_duration = sum(result.get('duration', 0) for result in self.test_results.values()) - + passed_tests = sum( + 1 for result in self.test_results.values() if result["status"] == "PASSED" + ) + failed_tests = sum( + 1 for result in self.test_results.values() if result["status"] == "FAILED" + ) + error_tests = sum( + 1 for result in self.test_results.values() if result["status"] == "ERROR" + ) + + total_duration = sum( + result.get("duration", 0) for result in self.test_results.values() + ) + report = { - 'summary': { - 'total_tests': total_tests, - 'passed': passed_tests, - 'failed': failed_tests, - 'errors': error_tests, - 'success_rate': passed_tests / total_tests if total_tests > 0 else 0, - 'total_duration': total_duration + "summary": { + "total_tests": total_tests, + "passed": passed_tests, + "failed": failed_tests, + "errors": error_tests, + "success_rate": passed_tests / total_tests if total_tests > 0 else 0, + "total_duration": total_duration, }, - 'detailed_results': self.test_results, - 'performance_metrics': self.performance_metrics, - 'timestamp': datetime.now().isoformat() + "detailed_results": self.test_results, + "performance_metrics": self.performance_metrics, + "timestamp": datetime.now().isoformat(), } - + return report async def run_advanced_tests(ssid: str = None): """Run the advanced testing suite""" - + if not ssid: # Use demo SSID for testing ssid = r'42["auth",{"session":"demo_session_for_testing","isDemo":1,"uid":0,"platform":1}]' - logger.warning("โš ๏ธ Using demo SSID - some tests may have limited functionality") - + logger.warning( + "Caution: Using demo SSID - some tests may have limited functionality" + ) + test_suite = AdvancedTestSuite(ssid) - - logger.info("๐Ÿ”ฌ Starting Advanced PocketOption API Testing Suite") + + logger.info("Testing: Starting Advanced PocketOption API Testing Suite") logger.info("=" * 60) - + try: report = await test_suite.run_all_tests() - + # Print summary logger.info("\n" + "=" * 60) - logger.info("๐Ÿ“‹ TEST SUMMARY") + logger.info("Demonstration: TEST SUMMARY") logger.info("=" * 60) - - summary = report['summary'] + + summary = report["summary"] logger.info(f"Total Tests: {summary['total_tests']}") - logger.info(f"Passed: {summary['passed']} โœ…") - logger.info(f"Failed: {summary['failed']} โŒ") - logger.info(f"Errors: {summary['errors']} ๐Ÿ’ฅ") + logger.info(f"Passed: {summary['passed']} Success") + logger.info(f"Failed: {summary['failed']} Error") + logger.info(f"Errors: {summary['errors']} Failure") logger.info(f"Success Rate: {summary['success_rate']:.1%}") logger.info(f"Total Duration: {summary['total_duration']:.2f}s") - + # Performance metrics - if report['performance_metrics']: - logger.info("\n๐Ÿ“Š PERFORMANCE METRICS") + if report["performance_metrics"]: + logger.info("\nStatistics: PERFORMANCE METRICS") logger.info("-" * 30) - for metric, value in report['performance_metrics'].items(): + for metric, value in report["performance_metrics"].items(): logger.info(f"{metric}: {value:.3f}s") - + # Save report to file report_file = f"test_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" - with open(report_file, 'w') as f: + with open(report_file, "w") as f: json.dump(report, f, indent=2, default=str) - - logger.info(f"\n๐Ÿ“„ Detailed report saved to: {report_file}") - + + logger.info(f"\nReport: Detailed report saved to: {report_file}") + return report - + except Exception as e: - logger.error(f"โŒ Test suite failed: {e}") + logger.error(f"Error: Test suite failed: {e}") raise if __name__ == "__main__": import sys - + # Allow passing SSID as command line argument ssid = None if len(sys.argv) > 1: ssid = sys.argv[1] logger.info(f"Using provided SSID: {ssid[:50]}...") - + asyncio.run(run_advanced_tests(ssid)) diff --git a/comprehensive_demo.py b/comprehensive_demo.py index 0c04e0d..a921760 100644 --- a/comprehensive_demo.py +++ b/comprehensive_demo.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Comprehensive Demo of Enhanced PocketOption Async API Showcases all advanced features and improvements @@ -6,159 +5,167 @@ import asyncio import time -import json -from datetime import datetime, timedelta -from typing import Optional +from datetime import datetime from loguru import logger from pocketoptionapi_async.client import AsyncPocketOptionClient -from pocketoptionapi_async.models import OrderDirection, TimeFrame +from pocketoptionapi_async.models import TimeFrame from connection_keep_alive import ConnectionKeepAlive from connection_monitor import ConnectionMonitor -from advanced_testing_suite import AdvancedTestSuite from load_testing_tool import LoadTester, LoadTestConfig -from integration_tests import IntegrationTester async def demo_ssid_format_support(): """Demo: Complete SSID format support""" - logger.info("๐Ÿ” Demo: Complete SSID Format Support") + logger.info("Authentication: Demo: Complete SSID Format Support") logger.info("=" * 50) - + # Example complete SSID (demo format) complete_ssid = r'42["auth",{"session":"demo_session_12345","isDemo":1,"uid":12345,"platform":1}]' - - logger.info("โœ… SUPPORTED SSID FORMATS:") + + logger.info("Success: SUPPORTED SSID FORMATS:") logger.info("โ€ข Complete authentication strings (like from browser)") - logger.info("โ€ข Format: 42[\"auth\",{\"session\":\"...\",\"isDemo\":1,\"uid\":...,\"platform\":1}]") + logger.info( + 'โ€ข Format: 42["auth",{"session":"...","isDemo":1,"uid":...,"platform":1}]' + ) logger.info("โ€ข Automatic parsing and component extraction") logger.info("") - + try: client = AsyncPocketOptionClient(complete_ssid, is_demo=True) - - logger.info("๐Ÿ” Parsing SSID components...") + + logger.info("Analysis: Parsing SSID components...") logger.info(f"โ€ข Session ID extracted: {complete_ssid[35:55]}...") - logger.info(f"โ€ข Demo mode: True") - logger.info(f"โ€ข Platform: 1") - + logger.info("โ€ข Demo mode: True") + logger.info("โ€ข Platform: 1") + success = await client.connect() if success: - logger.success("โœ… Connection successful with complete SSID format!") - + logger.success("Success: Connection successful with complete SSID format!") + # Test basic operation balance = await client.get_balance() if balance: logger.info(f"โ€ข Balance retrieved: ${balance.balance}") - + await client.disconnect() else: - logger.warning("โš ๏ธ Connection failed (expected with demo SSID)") - + logger.warning("Caution: Connection failed (expected with demo SSID)") + except Exception as e: - logger.info(f"โ„น๏ธ Demo connection attempt: {e}") - - logger.info("โœ… Complete SSID format is fully supported!") + logger.info(f"Note: Demo connection attempt: {e}") + + logger.info("Success: Complete SSID format is fully supported!") async def demo_persistent_connection(): """Demo: Persistent connection with keep-alive""" - logger.info("\n๐Ÿ”„ Demo: Persistent Connection with Keep-Alive") + logger.info("\nPersistent: Demo: Persistent Connection with Keep-Alive") logger.info("=" * 50) - + ssid = r'42["auth",{"session":"demo_persistent","isDemo":1,"uid":0,"platform":1}]' - - logger.info("๐Ÿš€ Starting persistent connection with automatic keep-alive...") - + + logger.info("Starting persistent connection with automatic keep-alive...") + # Method 1: Using AsyncPocketOptionClient with persistent connection - logger.info("\n๐Ÿ“ก Method 1: Enhanced AsyncPocketOptionClient") - + logger.info("\nMessage: Method 1: Enhanced AsyncPocketOptionClient") + try: client = AsyncPocketOptionClient( - ssid, + ssid, is_demo=True, persistent_connection=True, # Enable persistent connection - auto_reconnect=True # Enable auto-reconnection + auto_reconnect=True, # Enable auto-reconnection ) - + success = await client.connect(persistent=True) if success: - logger.success("โœ… Persistent connection established!") - + logger.success("Success: Persistent connection established!") + # Show connection statistics stats = client.get_connection_stats() - logger.info(f"โ€ข Connection type: {'Persistent' if stats['is_persistent'] else 'Regular'}") + logger.info( + f"โ€ข Connection type: {'Persistent' if stats['is_persistent'] else 'Regular'}" + ) logger.info(f"โ€ข Auto-reconnect: {stats['auto_reconnect']}") logger.info(f"โ€ข Region: {stats['current_region']}") - + # Demonstrate persistent operation - logger.info("\n๐Ÿ”„ Testing persistent operations...") + logger.info("\nPersistent: Testing persistent operations...") for i in range(3): balance = await client.get_balance() if balance: - logger.info(f"โ€ข Operation {i+1}: Balance = ${balance.balance}") + logger.info(f"โ€ข Operation {i + 1}: Balance = ${balance.balance}") await asyncio.sleep(2) - + await client.disconnect() else: - logger.warning("โš ๏ธ Connection failed (expected with demo SSID)") - + logger.warning("Caution: Connection failed (expected with demo SSID)") + except Exception as e: - logger.info(f"โ„น๏ธ Demo persistent connection: {e}") - + logger.info(f"Note: Demo persistent connection: {e}") + # Method 2: Using dedicated ConnectionKeepAlive manager - logger.info("\n๐Ÿ›ก๏ธ Method 2: Dedicated ConnectionKeepAlive Manager") - + logger.info("\nError Handling: Method 2: Dedicated ConnectionKeepAlive Manager") + try: keep_alive = ConnectionKeepAlive(ssid, is_demo=True) - + # Add event handlers to show keep-alive activity - events_count = {'connected': 0, 'messages': 0, 'pings': 0} - + events_count = {"connected": 0, "messages": 0, "pings": 0} + async def on_connected(data): - events_count['connected'] += 1 - logger.success(f"๐ŸŽ‰ Keep-alive connected to: {data.get('region', 'Unknown')}") - + events_count["connected"] += 1 + logger.success( + f"Successfully: Keep-alive connected to: {data.get('region', 'Unknown')}" + ) + async def on_message(data): - events_count['messages'] += 1 - if events_count['messages'] <= 3: # Show first few messages - logger.info(f"๐Ÿ“จ Message received: {data.get('message', '')[:30]}...") - - keep_alive.add_event_handler('connected', on_connected) - keep_alive.add_event_handler('message_received', on_message) - + events_count["messages"] += 1 + if events_count["messages"] <= 3: # Show first few messages + logger.info( + f"Message: Message received: {data.get('message', '')[:30]}..." + ) + + keep_alive.add_event_handler("connected", on_connected) + keep_alive.add_event_handler("message_received", on_message) + success = await keep_alive.start_persistent_connection() if success: - logger.success("โœ… Keep-alive manager started!") - + logger.success("Success: Keep-alive manager started!") + # Let it run and show automatic ping activity - logger.info("๐Ÿ“ Watching automatic ping activity...") + logger.info("Ping: Watching automatic ping activity...") for i in range(10): await asyncio.sleep(2) - + # Send test message if i % 3 == 0: msg_success = await keep_alive.send_message('42["ps"]') if msg_success: - events_count['pings'] += 1 - logger.info(f"๐Ÿ“ Manual ping {events_count['pings']} sent") - + events_count["pings"] += 1 + logger.info(f"Ping: Manual ping {events_count['pings']} sent") + # Show statistics every few seconds if i % 5 == 4: stats = keep_alive.get_connection_stats() - logger.info(f"๐Ÿ“Š Stats: Connected={stats['is_connected']}, " - f"Messages={stats['total_messages_sent']}, " - f"Uptime={stats.get('uptime', 'N/A')}") - + logger.info( + f"Statistics: Stats: Connected={stats['is_connected']}, " + f"Messages={stats['total_messages_sent']}, " + f"Uptime={stats.get('uptime', 'N/A')}" + ) + await keep_alive.stop_persistent_connection() - + else: - logger.warning("โš ๏ธ Keep-alive connection failed (expected with demo SSID)") - + logger.warning( + "Caution: Keep-alive connection failed (expected with demo SSID)" + ) + except Exception as e: - logger.info(f"โ„น๏ธ Demo keep-alive: {e}") - - logger.info("\nโœ… Persistent connection features demonstrated!") + logger.info(f"Note: Demo keep-alive: {e}") + + logger.info("\nSuccess: Persistent connection features demonstrated!") logger.info("โ€ข Automatic ping every 20 seconds (like old API)") logger.info("โ€ข Automatic reconnection on disconnection") logger.info("โ€ข Multiple region fallback") @@ -169,68 +176,76 @@ async def on_message(data): async def demo_advanced_monitoring(): """Demo: Advanced monitoring and diagnostics""" - logger.info("\n๐Ÿ” Demo: Advanced Monitoring and Diagnostics") + logger.info("\nAnalysis: Demo: Advanced Monitoring and Diagnostics") logger.info("=" * 50) - + ssid = r'42["auth",{"session":"demo_monitoring","isDemo":1,"uid":0,"platform":1}]' - - logger.info("๐Ÿ–ฅ๏ธ Starting advanced connection monitor...") - + + logger.info("Resources: Starting advanced connection monitor...") + try: monitor = ConnectionMonitor(ssid, is_demo=True) - + # Add alert handlers alerts_received = [] - + async def on_alert(alert_data): alerts_received.append(alert_data) - logger.warning(f"๐Ÿšจ ALERT: {alert_data['message']}") - + logger.warning(f"Alert: ALERT: {alert_data['message']}") + async def on_stats_update(stats): # Could integrate with external monitoring systems pass - - monitor.add_event_handler('alert', on_alert) - monitor.add_event_handler('stats_update', on_stats_update) - + + monitor.add_event_handler("alert", on_alert) + monitor.add_event_handler("stats_update", on_stats_update) + success = await monitor.start_monitoring(persistent_connection=True) if success: - logger.success("โœ… Monitoring started!") - + logger.success("Success: Monitoring started!") + # Let monitoring run and collect data - logger.info("๐Ÿ“Š Collecting monitoring data...") - + logger.info("Statistics: Collecting monitoring data...") + for i in range(15): await asyncio.sleep(2) - + if i % 5 == 4: # Show stats every 10 seconds stats = monitor.get_real_time_stats() - logger.info(f"๐Ÿ“ˆ Real-time: {stats['total_messages']} messages, " - f"{stats['error_rate']:.1%} error rate, " - f"{stats['messages_per_second']:.1f} msg/sec") - + logger.info( + f"Retrieved: Real-time: {stats['total_messages']} messages, " + f"{stats['error_rate']:.1%} error rate, " + f"{stats['messages_per_second']:.1f} msg/sec" + ) + # Generate diagnostics report - logger.info("\n๐Ÿฅ Generating diagnostics report...") + logger.info("\nHealth: Generating diagnostics report...") report = monitor.generate_diagnostics_report() - - logger.info(f"โ€ข Health Score: {report['health_score']}/100 ({report['health_status']})") - logger.info(f"โ€ข Total Messages: {report['real_time_stats']['total_messages']}") + + logger.info( + f"โ€ข Health Score: {report['health_score']}/100 ({report['health_status']})" + ) + logger.info( + f"โ€ข Total Messages: {report['real_time_stats']['total_messages']}" + ) logger.info(f"โ€ข Uptime: {report['real_time_stats']['uptime_str']}") - - if report['recommendations']: - logger.info("๐Ÿ’ก Recommendations:") - for rec in report['recommendations'][:2]: # Show first 2 + + if report["recommendations"]: + logger.info("Note: Recommendations:") + for rec in report["recommendations"][:2]: # Show first 2 logger.info(f" โ€ข {rec}") - + await monitor.stop_monitoring() - + else: - logger.warning("โš ๏ธ Monitoring failed to start (expected with demo SSID)") - + logger.warning( + "Caution: Monitoring failed to start (expected with demo SSID)" + ) + except Exception as e: - logger.info(f"โ„น๏ธ Demo monitoring: {e}") - - logger.info("\nโœ… Advanced monitoring features demonstrated!") + logger.info(f"Note: Demo monitoring: {e}") + + logger.info("\nSuccess: Advanced monitoring features demonstrated!") logger.info("โ€ข Real-time connection health monitoring") logger.info("โ€ข Performance metrics collection") logger.info("โ€ข Automatic alert generation") @@ -241,56 +256,64 @@ async def on_stats_update(stats): async def demo_load_testing(): """Demo: Load testing and stress testing""" - logger.info("\n๐Ÿš€ Demo: Load Testing and Stress Testing") + logger.info("\nStarting: Demo: Load Testing and Stress Testing") logger.info("=" * 50) - + ssid = r'42["auth",{"session":"demo_load_test","isDemo":1,"uid":0,"platform":1}]' - - logger.info("โšก Running mini load test demonstration...") - + + logger.info("Performance: Running mini load test demonstration...") + try: load_tester = LoadTester(ssid, is_demo=True) - + # Small scale demo configuration config = LoadTestConfig( concurrent_clients=2, operations_per_client=3, operation_delay=0.5, use_persistent_connection=True, - stress_mode=False + stress_mode=False, + ) + + logger.info( + f"Demonstration: Configuration: {config.concurrent_clients} clients, " + f"{config.operations_per_client} operations each" ) - - logger.info(f"๐Ÿ“‹ Configuration: {config.concurrent_clients} clients, " - f"{config.operations_per_client} operations each") - + report = await load_tester.run_load_test(config) - + # Show results summary = report["test_summary"] - logger.info(f"โœ… Load test completed!") + logger.info("Success: Load test completed!") logger.info(f"โ€ข Duration: {summary['total_duration']:.2f}s") logger.info(f"โ€ข Total Operations: {summary['total_operations']}") logger.info(f"โ€ข Success Rate: {summary['success_rate']:.1%}") logger.info(f"โ€ข Throughput: {summary['avg_operations_per_second']:.1f} ops/sec") - logger.info(f"โ€ข Peak Throughput: {summary['peak_operations_per_second']} ops/sec") - + logger.info( + f"โ€ข Peak Throughput: {summary['peak_operations_per_second']} ops/sec" + ) + # Show operation analysis if report["operation_analysis"]: - logger.info("\n๐Ÿ“Š Operation Analysis:") - for op_type, stats in list(report["operation_analysis"].items())[:2]: # Show first 2 - logger.info(f"โ€ข {op_type}: {stats['avg_duration']:.3f}s avg, " - f"{stats['success_rate']:.1%} success") - + logger.info("\nStatistics: Operation Analysis:") + for op_type, stats in list(report["operation_analysis"].items())[ + :2 + ]: # Show first 2 + logger.info( + f"โ€ข {op_type}: {stats['avg_duration']:.3f}s avg, " + f"{stats['success_rate']:.1%} success" + ) + # Show recommendations if report["recommendations"]: - logger.info("\n๐Ÿ’ก Recommendations:") + logger.info("\nNote: Recommendations:") for rec in report["recommendations"][:2]: # Show first 2 logger.info(f" โ€ข {rec}") - + except Exception as e: - logger.info(f"โ„น๏ธ Demo load testing: {e}") - - logger.info("\nโœ… Load testing features demonstrated!") + logger.info(f"Note: Demo load testing: {e}") + + logger.info("\nSuccess: Load testing features demonstrated!") logger.info("โ€ข Concurrent client simulation") logger.info("โ€ข Performance benchmarking") logger.info("โ€ข Stress testing capabilities") @@ -300,82 +323,96 @@ async def demo_load_testing(): async def demo_error_handling(): """Demo: Advanced error handling and recovery""" - logger.info("\n๐Ÿ›ก๏ธ Demo: Advanced Error Handling and Recovery") + logger.info("\nError Handling: Demo: Advanced Error Handling and Recovery") logger.info("=" * 50) - - ssid = r'42["auth",{"session":"demo_error_handling","isDemo":1,"uid":0,"platform":1}]' - - logger.info("๐Ÿ”ง Demonstrating error handling capabilities...") - + + ssid = ( + r'42["auth",{"session":"demo_error_handling","isDemo":1,"uid":0,"platform":1}]' + ) + + logger.info( + "Technical Implementation: Demonstrating error handling capabilities..." + ) + try: client = AsyncPocketOptionClient(ssid, is_demo=True, auto_reconnect=True) - + success = await client.connect() if success: - logger.success("โœ… Connected for error handling demo") - + logger.success("Success: Connected for error handling demo") + # Test 1: Invalid asset handling - logger.info("\n๐Ÿงช Test 1: Invalid asset handling") + logger.info("\nTesting: Test 1: Invalid asset handling") try: await client.get_candles("INVALID_ASSET", TimeFrame.M1, 10) logger.warning("No error raised for invalid asset") except Exception as e: - logger.success(f"โœ… Invalid asset error handled: {type(e).__name__}") - + logger.success( + f"Success: Invalid asset error handled: {type(e).__name__}" + ) + # Test 2: Invalid parameters - logger.info("\n๐Ÿงช Test 2: Invalid parameters") + logger.info("\nTesting: Test 2: Invalid parameters") try: await client.get_candles("EURUSD", "INVALID_TIMEFRAME", 10) logger.warning("No error raised for invalid timeframe") except Exception as e: - logger.success(f"โœ… Invalid parameter error handled: {type(e).__name__}") - + logger.success( + f"Success: Invalid parameter error handled: {type(e).__name__}" + ) + # Test 3: Connection recovery after errors - logger.info("\n๐Ÿงช Test 3: Connection recovery") + logger.info("\nTesting: Test 3: Connection recovery") try: balance = await client.get_balance() if balance: - logger.success(f"โœ… Connection still works after errors: ${balance.balance}") + logger.success( + f"Success: Connection still works after errors: ${balance.balance}" + ) else: - logger.info("โ„น๏ธ Balance retrieval returned None") + logger.info("Note: Balance retrieval returned None") except Exception as e: - logger.warning(f"โš ๏ธ Connection issue after errors: {e}") - + logger.warning(f"Caution: Connection issue after errors: {e}") + await client.disconnect() - + else: - logger.warning("โš ๏ธ Connection failed (expected with demo SSID)") - + logger.warning("Caution: Connection failed (expected with demo SSID)") + except Exception as e: - logger.info(f"โ„น๏ธ Demo error handling: {e}") - + logger.info(f"Note: Demo error handling: {e}") + # Demo automatic reconnection - logger.info("\n๐Ÿ”„ Demonstrating automatic reconnection...") - + logger.info("\nPersistent: Demonstrating automatic reconnection...") + try: keep_alive = ConnectionKeepAlive(ssid, is_demo=True) - + # Track reconnection events reconnections = [] - + async def on_reconnected(data): reconnections.append(data) - logger.success(f"๐Ÿ”„ Reconnection #{data.get('attempt', '?')} successful!") - - keep_alive.add_event_handler('reconnected', on_reconnected) - + logger.success( + f"Persistent: Reconnection #{data.get('attempt', '?')} successful!" + ) + + keep_alive.add_event_handler("reconnected", on_reconnected) + success = await keep_alive.start_persistent_connection() if success: - logger.info("โœ… Keep-alive started, will auto-reconnect on issues") + logger.info("Success: Keep-alive started, will auto-reconnect on issues") await asyncio.sleep(5) await keep_alive.stop_persistent_connection() else: - logger.warning("โš ๏ธ Keep-alive failed to start (expected with demo SSID)") - + logger.warning( + "Caution: Keep-alive failed to start (expected with demo SSID)" + ) + except Exception as e: - logger.info(f"โ„น๏ธ Demo reconnection: {e}") - - logger.info("\nโœ… Error handling features demonstrated!") + logger.info(f"Note: Demo reconnection: {e}") + + logger.info("\nSuccess: Error handling features demonstrated!") logger.info("โ€ข Graceful handling of invalid operations") logger.info("โ€ข Connection stability after errors") logger.info("โ€ข Automatic reconnection on disconnection") @@ -385,87 +422,91 @@ async def on_reconnected(data): async def demo_data_operations(): """Demo: Enhanced data operations""" - logger.info("\n๐Ÿ“Š Demo: Enhanced Data Operations") + logger.info("\nStatistics: Demo: Enhanced Data Operations") logger.info("=" * 50) - + ssid = r'42["auth",{"session":"demo_data_ops","isDemo":1,"uid":0,"platform":1}]' - - logger.info("๐Ÿ“ˆ Demonstrating enhanced data retrieval...") - + + logger.info("Retrieved: Demonstrating enhanced data retrieval...") + try: client = AsyncPocketOptionClient(ssid, is_demo=True) - + success = await client.connect() if success: - logger.success("โœ… Connected for data operations demo") - + logger.success("Success: Connected for data operations demo") + # Demo 1: Balance operations - logger.info("\n๐Ÿ’ฐ Balance Operations:") + logger.info("\nBalance: Balance Operations:") balance = await client.get_balance() if balance: logger.info(f"โ€ข Current Balance: ${balance.balance}") logger.info(f"โ€ข Currency: {balance.currency}") logger.info(f"โ€ข Demo Mode: {balance.is_demo}") else: - logger.info("โ„น๏ธ Balance data not available (demo)") - + logger.info("Note: Balance data not available (demo)") + # Demo 2: Candles operations - logger.info("\n๐Ÿ“ˆ Candles Operations:") + logger.info("\nRetrieved: Candles Operations:") assets = ["EURUSD", "GBPUSD", "USDJPY"] - + for asset in assets: try: candles = await client.get_candles(asset, TimeFrame.M1, 5) if candles: latest = candles[-1] - logger.info(f"โ€ข {asset}: {len(candles)} candles, latest close: {latest.close}") + logger.info( + f"โ€ข {asset}: {len(candles)} candles, latest close: {latest.close}" + ) else: logger.info(f"โ€ข {asset}: No candles available") except Exception as e: logger.info(f"โ€ข {asset}: Error - {type(e).__name__}") - + # Demo 3: DataFrame operations - logger.info("\n๐Ÿ“‹ DataFrame Operations:") + logger.info("\nDemonstration: DataFrame Operations:") try: df = await client.get_candles_dataframe("EURUSD", TimeFrame.M1, 10) if df is not None and not df.empty: logger.info(f"โ€ข DataFrame shape: {df.shape}") logger.info(f"โ€ข Columns: {list(df.columns)}") - logger.info(f"โ€ข Latest close: {df['close'].iloc[-1] if 'close' in df.columns else 'N/A'}") + logger.info( + f"โ€ข Latest close: {df['close'].iloc[-1] if 'close' in df.columns else 'N/A'}" + ) else: logger.info("โ€ข DataFrame: No data available") except Exception as e: logger.info(f"โ€ข DataFrame: {type(e).__name__}") - + # Demo 4: Concurrent data retrieval - logger.info("\nโšก Concurrent Data Retrieval:") - + logger.info("\nPerformance: Concurrent Data Retrieval:") + async def get_asset_data(asset): try: candles = await client.get_candles(asset, TimeFrame.M1, 3) return asset, len(candles), True except Exception: return asset, 0, False - + # Get data for multiple assets concurrently tasks = [get_asset_data(asset) for asset in ["EURUSD", "GBPUSD", "AUDUSD"]] results = await asyncio.gather(*tasks, return_exceptions=True) - + for result in results: if isinstance(result, tuple): asset, count, success = result - status = "โœ…" if success else "โŒ" + status = "Success" if success else "Error" logger.info(f"โ€ข {asset}: {status} {count} candles") - + await client.disconnect() - + else: - logger.warning("โš ๏ธ Connection failed (expected with demo SSID)") - + logger.warning("Caution: Connection failed (expected with demo SSID)") + except Exception as e: - logger.info(f"โ„น๏ธ Demo data operations: {e}") - - logger.info("\nโœ… Enhanced data operations demonstrated!") + logger.info(f"Note: Demo data operations: {e}") + + logger.info("\nSuccess: Enhanced data operations demonstrated!") logger.info("โ€ข Comprehensive balance information") logger.info("โ€ข Multi-asset candle retrieval") logger.info("โ€ข Pandas DataFrame integration") @@ -475,121 +516,125 @@ async def get_asset_data(asset): async def demo_performance_optimizations(): """Demo: Performance optimizations""" - logger.info("\nโšก Demo: Performance Optimizations") + logger.info("\nPerformance: Demo: Performance Optimizations") logger.info("=" * 50) - + ssid = r'42["auth",{"session":"demo_performance","isDemo":1,"uid":0,"platform":1}]' - - logger.info("๐Ÿš€ Demonstrating performance enhancements...") - + + logger.info("Starting: Demonstrating performance enhancements...") + # Performance comparison performance_results = {} - + # Test 1: Regular vs Persistent connection speed - logger.info("\n๐Ÿ”„ Connection Speed Comparison:") - + logger.info("\nPersistent: Connection Speed Comparison:") + try: # Regular connection start_time = time.time() - client1 = AsyncPocketOptionClient(ssid, is_demo=True, persistent_connection=False) + client1 = AsyncPocketOptionClient( + ssid, is_demo=True, persistent_connection=False + ) success1 = await client1.connect() regular_time = time.time() - start_time if success1: await client1.disconnect() - + # Persistent connection start_time = time.time() - client2 = AsyncPocketOptionClient(ssid, is_demo=True, persistent_connection=True) + client2 = AsyncPocketOptionClient( + ssid, is_demo=True, persistent_connection=True + ) success2 = await client2.connect() persistent_time = time.time() - start_time if success2: await client2.disconnect() - + logger.info(f"โ€ข Regular connection: {regular_time:.3f}s") logger.info(f"โ€ข Persistent connection: {persistent_time:.3f}s") - - performance_results['connection'] = { - 'regular': regular_time, - 'persistent': persistent_time + + performance_results["connection"] = { + "regular": regular_time, + "persistent": persistent_time, } - + except Exception as e: - logger.info(f"โ„น๏ธ Connection speed test: {e}") - + logger.info(f"Note: Connection speed test: {e}") + # Test 2: Message batching demonstration - logger.info("\n๐Ÿ“ฆ Message Batching:") + logger.info("\nBatching: Message Batching:") try: client = AsyncPocketOptionClient(ssid, is_demo=True) success = await client.connect() - + if success: # Send multiple messages and measure time start_time = time.time() for i in range(10): await client.send_message('42["ps"]') batch_time = time.time() - start_time - + logger.info(f"โ€ข 10 messages sent in: {batch_time:.3f}s") - logger.info(f"โ€ข Average per message: {batch_time/10:.4f}s") - - performance_results['messaging'] = { - 'total_time': batch_time, - 'avg_per_message': batch_time / 10 + logger.info(f"โ€ข Average per message: {batch_time / 10:.4f}s") + + performance_results["messaging"] = { + "total_time": batch_time, + "avg_per_message": batch_time / 10, } - + await client.disconnect() else: logger.info("โ€ข Messaging test skipped (connection failed)") - + except Exception as e: - logger.info(f"โ„น๏ธ Message batching test: {e}") - + logger.info(f"Note: Message batching test: {e}") + # Test 3: Concurrent operations - logger.info("\nโšก Concurrent Operations:") + logger.info("\nPerformance: Concurrent Operations:") try: client = AsyncPocketOptionClient(ssid, is_demo=True, persistent_connection=True) success = await client.connect() - + if success: # Concurrent operations start_time = time.time() - + async def operation_batch(): tasks = [] for _ in range(5): tasks.append(client.send_message('42["ps"]')) tasks.append(client.get_balance()) return await asyncio.gather(*tasks, return_exceptions=True) - + results = await operation_batch() concurrent_time = time.time() - start_time - + successful_ops = len([r for r in results if not isinstance(r, Exception)]) - + logger.info(f"โ€ข 10 concurrent operations in: {concurrent_time:.3f}s") logger.info(f"โ€ข Successful operations: {successful_ops}/10") - - performance_results['concurrent'] = { - 'total_time': concurrent_time, - 'successful_ops': successful_ops + + performance_results["concurrent"] = { + "total_time": concurrent_time, + "successful_ops": successful_ops, } - + await client.disconnect() else: logger.info("โ€ข Concurrent operations test skipped (connection failed)") - + except Exception as e: - logger.info(f"โ„น๏ธ Concurrent operations test: {e}") - + logger.info(f"Note: Concurrent operations test: {e}") + # Summary - logger.info("\n๐Ÿ“Š Performance Summary:") + logger.info("\nStatistics: Performance Summary:") if performance_results: for category, metrics in performance_results.items(): logger.info(f"โ€ข {category.title()}: {metrics}") else: logger.info("โ€ข Performance metrics collected (demo mode)") - - logger.info("\nโœ… Performance optimizations demonstrated!") + + logger.info("\nSuccess: Performance optimizations demonstrated!") logger.info("โ€ข Connection pooling and reuse") logger.info("โ€ข Message batching and queuing") logger.info("โ€ข Concurrent operation support") @@ -599,14 +644,14 @@ async def operation_batch(): async def demo_migration_compatibility(): """Demo: Migration from old API""" - logger.info("\n๐Ÿ”„ Demo: Migration from Old API") + logger.info("\nPersistent: Demo: Migration from Old API") logger.info("=" * 50) - - logger.info("๐Ÿ—๏ธ Migration compatibility features:") + + logger.info("Architecture: Migration compatibility features:") logger.info("") - + # Show old vs new API patterns - logger.info("๐Ÿ“‹ OLD API PATTERN:") + logger.info("Demonstration: OLD API PATTERN:") logger.info("```python") logger.info("from pocketoptionapi.pocket import PocketOptionApi") logger.info("api = PocketOptionApi(ssid=ssid, uid=uid)") @@ -614,8 +659,8 @@ async def demo_migration_compatibility(): logger.info("balance = api.get_balance()") logger.info("```") logger.info("") - - logger.info("๐Ÿ†• NEW ASYNC API PATTERN:") + + logger.info("NEW ASYNC API PATTERN:") logger.info("```python") logger.info("from pocketoptionapi_async.client import AsyncPocketOptionClient") logger.info("client = AsyncPocketOptionClient(ssid, persistent_connection=True)") @@ -623,40 +668,42 @@ async def demo_migration_compatibility(): logger.info("balance = await client.get_balance()") logger.info("```") logger.info("") - - logger.info("๐ŸŽฏ KEY IMPROVEMENTS:") - logger.info("โ€ข โœ… Complete SSID format support (browser-compatible)") - logger.info("โ€ข โœ… Persistent connections with automatic keep-alive") - logger.info("โ€ข โœ… Async/await for better performance") - logger.info("โ€ข โœ… Enhanced error handling and recovery") - logger.info("โ€ข โœ… Real-time monitoring and diagnostics") - logger.info("โ€ข โœ… Load testing and performance analysis") - logger.info("โ€ข โœ… Event-driven architecture") - logger.info("โ€ข โœ… Modern Python practices (type hints, dataclasses)") + + logger.info("Usage Examples: KEY IMPROVEMENTS:") + logger.info("โ€ข Success: Complete SSID format support (browser-compatible)") + logger.info("โ€ข Success: Persistent connections with automatic keep-alive") + logger.info("โ€ข Success: Async/await for better performance") + logger.info("โ€ข Success: Enhanced error handling and recovery") + logger.info("โ€ข Success: Real-time monitoring and diagnostics") + logger.info("โ€ข Success: Load testing and performance analysis") + logger.info("โ€ข Success: Event-driven architecture") + logger.info("โ€ข Success: Modern Python practices (type hints, dataclasses)") logger.info("") - - logger.info("๐Ÿ”„ MIGRATION BENEFITS:") - logger.info("โ€ข ๐Ÿš€ Better performance with async operations") - logger.info("โ€ข ๐Ÿ›ก๏ธ More reliable connections with keep-alive") - logger.info("โ€ข ๐Ÿ“Š Built-in monitoring and diagnostics") - logger.info("โ€ข ๐Ÿ”ง Better error handling and recovery") - logger.info("โ€ข โšก Concurrent operations support") - logger.info("โ€ข ๐Ÿ“ˆ Performance optimization features") + + logger.info("Persistent: MIGRATION BENEFITS:") + logger.info("โ€ข Starting: Better performance with async operations") + logger.info("โ€ข Error Handling: More reliable connections with keep-alive") + logger.info("โ€ข Statistics: Built-in monitoring and diagnostics") + logger.info("โ€ข Technical Implementation: Better error handling and recovery") + logger.info("โ€ข Performance: Concurrent operations support") + logger.info("โ€ข Retrieved: Performance optimization features") async def run_comprehensive_demo(ssid: str = None): """Run the comprehensive demo of all features""" - + if not ssid: ssid = r'42["auth",{"session":"comprehensive_demo_session","isDemo":1,"uid":12345,"platform":1}]' - logger.warning("โš ๏ธ Using demo SSID - some features will have limited functionality") - - logger.info("๐ŸŽ‰ PocketOption Async API - Comprehensive Feature Demo") + logger.warning( + "Caution: Using demo SSID - some features will have limited functionality" + ) + + logger.info("Completed: PocketOption Async API - Comprehensive Feature Demo") logger.info("=" * 70) logger.info("This demo showcases all enhanced features and improvements") logger.info("including persistent connections, monitoring, testing, and more!") logger.info("") - + demos = [ ("SSID Format Support", demo_ssid_format_support), ("Persistent Connection", demo_persistent_connection), @@ -665,69 +712,71 @@ async def run_comprehensive_demo(ssid: str = None): ("Error Handling", demo_error_handling), ("Data Operations", demo_data_operations), ("Performance Optimizations", demo_performance_optimizations), - ("Migration Compatibility", demo_migration_compatibility) + ("Migration Compatibility", demo_migration_compatibility), ] - + start_time = datetime.now() - + for i, (demo_name, demo_func) in enumerate(demos, 1): - logger.info(f"\n{'='*20} DEMO {i}/{len(demos)}: {demo_name.upper()} {'='*20}") - + logger.info( + f"\n{'=' * 20} DEMO {i}/{len(demos)}: {demo_name.upper()} {'=' * 20}" + ) + try: await demo_func() - + except Exception as e: - logger.error(f"โŒ Demo {demo_name} failed: {e}") - + logger.error(f"Error: Demo {demo_name} failed: {e}") + # Brief pause between demos if i < len(demos): await asyncio.sleep(2) - + total_time = (datetime.now() - start_time).total_seconds() - + # Final summary logger.info("\n" + "=" * 70) - logger.info("๐ŸŽŠ COMPREHENSIVE DEMO COMPLETED!") + logger.info("Completed: COMPREHENSIVE DEMO COMPLETED!") logger.info("=" * 70) logger.info(f"Total demo time: {total_time:.1f} seconds") logger.info(f"Features demonstrated: {len(demos)}") logger.info("") - - logger.info("๐Ÿš€ READY FOR PRODUCTION USE!") + + logger.info("Starting: READY FOR PRODUCTION USE!") logger.info("The enhanced PocketOption Async API is now ready with:") - logger.info("โ€ข โœ… Complete SSID format support") - logger.info("โ€ข โœ… Persistent connections with keep-alive") - logger.info("โ€ข โœ… Advanced monitoring and diagnostics") - logger.info("โ€ข โœ… Comprehensive testing frameworks") - logger.info("โ€ข โœ… Performance optimizations") - logger.info("โ€ข โœ… Robust error handling") - logger.info("โ€ข โœ… Modern async architecture") + logger.info("โ€ข Success: Complete SSID format support") + logger.info("โ€ข Success: Persistent connections with keep-alive") + logger.info("โ€ข Success: Advanced monitoring and diagnostics") + logger.info("โ€ข Success: Comprehensive testing frameworks") + logger.info("โ€ข Success: Performance optimizations") + logger.info("โ€ข Success: Robust error handling") + logger.info("โ€ข Success: Modern async architecture") logger.info("") - - logger.info("๐Ÿ“š NEXT STEPS:") + + logger.info("Next Steps: NEXT STEPS:") logger.info("1. Replace demo SSID with your real session data") logger.info("2. Choose connection type (regular or persistent)") logger.info("3. Implement your trading logic") logger.info("4. Use monitoring tools for production") logger.info("5. Run tests to validate functionality") logger.info("") - - logger.info("๐Ÿ”— For real usage, get your SSID from browser dev tools:") + + logger.info("Connection: For real usage, get your SSID from browser dev tools:") logger.info("โ€ข Open PocketOption in browser") logger.info("โ€ข F12 -> Network tab -> WebSocket connections") - logger.info("โ€ข Look for authentication message starting with 42[\"auth\"") + logger.info('โ€ข Look for authentication message starting with 42["auth"') logger.info("") - - logger.success("โœจ Demo completed successfully! The API is enhanced and ready! โœจ") + + logger.success("Completed successfully! The API is enhanced and ready!") if __name__ == "__main__": import sys - + # Allow passing SSID as command line argument ssid = None if len(sys.argv) > 1: ssid = sys.argv[1] logger.info(f"Using provided SSID: {ssid[:50]}...") - + asyncio.run(run_comprehensive_demo(ssid)) diff --git a/connection_keep_alive.py b/connection_keep_alive.py index 34f9cab..cdb4193 100644 --- a/connection_keep_alive.py +++ b/connection_keep_alive.py @@ -1,99 +1,106 @@ -#!/usr/bin/env python3 """ Enhanced Keep-Alive Connection Manager for PocketOption Async API """ import asyncio -import time -import threading from typing import Optional, List, Callable, Dict, Any from datetime import datetime, timedelta from loguru import logger import websockets -from websockets.exceptions import ConnectionClosed, WebSocketException +from websockets.exceptions import ConnectionClosed from pocketoptionapi_async.models import ConnectionInfo, ConnectionStatus -from pocketoptionapi_async.constants import REGIONS, CONNECTION_SETTINGS -from pocketoptionapi_async.exceptions import ConnectionError, WebSocketError +from pocketoptionapi_async.constants import REGIONS class ConnectionKeepAlive: """ Advanced connection keep-alive manager based on old API patterns """ - + def __init__(self, ssid: str, is_demo: bool = True): self.ssid = ssid self.is_demo = is_demo - + # Connection state self.websocket: Optional[websockets.WebSocketServerProtocol] = None self.connection_info: Optional[ConnectionInfo] = None self.is_connected = False self.should_reconnect = True - + # Background tasks self._ping_task: Optional[asyncio.Task] = None self._reconnect_task: Optional[asyncio.Task] = None self._message_task: Optional[asyncio.Task] = None self._health_task: Optional[asyncio.Task] = None - + # Keep-alive settings self.ping_interval = 20 # seconds (same as old API) self.reconnect_delay = 5 # seconds self.max_reconnect_attempts = 10 self.current_reconnect_attempts = 0 - + # Event handlers self._event_handlers: Dict[str, List[Callable]] = {} - + # Connection pool with multiple regions - self.available_urls = REGIONS.get_demo_regions() if is_demo else REGIONS.get_all() + self.available_urls = ( + REGIONS.get_demo_regions() if is_demo else REGIONS.get_all() + ) self.current_url_index = 0 - + # Statistics self.connection_stats = { - 'total_connections': 0, - 'successful_connections': 0, - 'total_reconnects': 0, - 'last_ping_time': None, - 'last_pong_time': None, - 'total_messages_sent': 0, - 'total_messages_received': 0 + "total_connections": 0, + "successful_connections": 0, + "total_reconnects": 0, + "last_ping_time": None, + "last_pong_time": None, + "total_messages_sent": 0, + "total_messages_received": 0, } - - logger.info(f"Initialized keep-alive manager with {len(self.available_urls)} available regions") - + + logger.info( + f"Initialized keep-alive manager with {len(self.available_urls)} available regions" + ) + async def start_persistent_connection(self) -> bool: """ Start a persistent connection with automatic keep-alive Similar to old API's daemon thread approach but with modern async """ - logger.info("๐Ÿš€ Starting persistent connection with keep-alive...") - + logger.info("Starting persistent connection with keep-alive...") + try: # Initial connection if await self._establish_connection(): # Start all background tasks await self._start_background_tasks() - logger.success("โœ… Persistent connection established with keep-alive active") + logger.success( + "Success: Persistent connection established with keep-alive active" + ) return True else: - logger.error("โŒ Failed to establish initial connection") + logger.error("Error: Failed to establish initial connection") return False - + except Exception as e: - logger.error(f"โŒ Error starting persistent connection: {e}") + logger.error(f"Error: Error starting persistent connection: {e}") return False - + async def stop_persistent_connection(self): """Stop the persistent connection and all background tasks""" - logger.info("๐Ÿ›‘ Stopping persistent connection...") - + logger.info("Stopping persistent connection...") + self.should_reconnect = False - + # Cancel all background tasks - tasks = [self._ping_task, self._reconnect_task, self._message_task, self._health_task] + tasks = [ + self._ping_task, + self._reconnect_task, + self._message_task, + self._health_task, + ] for task in tasks: if task and not task.done(): task.cancel() @@ -101,31 +108,34 @@ async def stop_persistent_connection(self): await task except asyncio.CancelledError: pass - + # Close connection if self.websocket: await self.websocket.close() self.websocket = None - + self.is_connected = False - logger.info("โœ… Persistent connection stopped") - + logger.info("Success: Persistent connection stopped") + async def _establish_connection(self) -> bool: """ Establish connection with fallback URLs (like old API) """ for attempt in range(len(self.available_urls)): url = self.available_urls[self.current_url_index] - + try: - logger.info(f"๐Ÿ”Œ Attempting connection to {url} (attempt {attempt + 1})") - + logger.info( + f"Connecting: Attempting connection to {url} (attempt {attempt + 1})" + ) + # SSL context (like old API) import ssl + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE - + # Connect with headers (like old API) self.websocket = await asyncio.wait_for( websockets.connect( @@ -134,15 +144,15 @@ async def _establish_connection(self) -> bool: extra_headers={ "Origin": "https://pocketoption.com", "Cache-Control": "no-cache", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", }, ping_interval=None, # We handle pings manually ping_timeout=None, - close_timeout=10 + close_timeout=10, ), - timeout=15.0 + timeout=15.0, ) - + # Update connection info region = self._extract_region_from_url(url) self.connection_info = ConnectionInfo( @@ -150,270 +160,293 @@ async def _establish_connection(self) -> bool: region=region, status=ConnectionStatus.CONNECTED, connected_at=datetime.now(), - reconnect_attempts=self.current_reconnect_attempts + reconnect_attempts=self.current_reconnect_attempts, ) - + self.is_connected = True self.current_reconnect_attempts = 0 - self.connection_stats['total_connections'] += 1 - self.connection_stats['successful_connections'] += 1 - + self.connection_stats["total_connections"] += 1 + self.connection_stats["successful_connections"] += 1 + # Send initial handshake (like old API) await self._send_handshake() - - logger.success(f"โœ… Connected to {region} region successfully") - await self._emit_event('connected', {'url': url, 'region': region}) - + + logger.success(f"Success: Connected to {region} region successfully") + await self._emit_event("connected", {"url": url, "region": region}) + return True - + except Exception as e: - logger.warning(f"โš ๏ธ Failed to connect to {url}: {e}") - + logger.warning(f"Caution: Failed to connect to {url}: {e}") + # Try next URL - self.current_url_index = (self.current_url_index + 1) % len(self.available_urls) - + self.current_url_index = (self.current_url_index + 1) % len( + self.available_urls + ) + if self.websocket: try: await self.websocket.close() - except: + except Exception: pass self.websocket = None - + await asyncio.sleep(1) # Brief delay before next attempt - + return False - + async def _send_handshake(self): """Send initial handshake sequence (like old API)""" try: # Wait for initial connection message - initial_message = await asyncio.wait_for(self.websocket.recv(), timeout=10.0) + initial_message = await asyncio.wait_for( + self.websocket.recv(), timeout=10.0 + ) logger.debug(f"Received initial: {initial_message}") - + # Send handshake sequence (like old API) await self.websocket.send("40") await asyncio.sleep(0.1) - + # Wait for connection establishment conn_message = await asyncio.wait_for(self.websocket.recv(), timeout=10.0) logger.debug(f"Received connection: {conn_message}") - + # Send SSID authentication await self.websocket.send(self.ssid) logger.debug("Handshake completed") - - self.connection_stats['total_messages_sent'] += 2 - + + self.connection_stats["total_messages_sent"] += 2 + except Exception as e: logger.error(f"Handshake failed: {e}") raise - + async def _start_background_tasks(self): """Start all background tasks (like old API's concurrent tasks)""" - logger.info("๐Ÿ”„ Starting background keep-alive tasks...") - + logger.info("Persistent: Starting background keep-alive tasks...") + # Ping task (every 20 seconds like old API) self._ping_task = asyncio.create_task(self._ping_loop()) - + # Message receiving task self._message_task = asyncio.create_task(self._message_loop()) - + # Health monitoring task self._health_task = asyncio.create_task(self._health_monitor_loop()) - + # Reconnection monitoring task self._reconnect_task = asyncio.create_task(self._reconnection_monitor()) - - logger.success("โœ… All background tasks started") - + + logger.success("Success: All background tasks started") + async def _ping_loop(self): """ Continuous ping loop (like old API's send_ping function) Sends '42["ps"]' every 20 seconds """ - logger.info("๐Ÿ“ Starting ping loop...") - + logger.info("Ping: Starting ping loop...") + while self.should_reconnect: try: if self.is_connected and self.websocket: # Send ping message (exact format from old API) await self.websocket.send('42["ps"]') - self.connection_stats['last_ping_time'] = datetime.now() - self.connection_stats['total_messages_sent'] += 1 - - logger.debug("๐Ÿ“ Ping sent") - + self.connection_stats["last_ping_time"] = datetime.now() + self.connection_stats["total_messages_sent"] += 1 + + logger.debug("Ping: Ping sent") + await asyncio.sleep(self.ping_interval) - + except ConnectionClosed: - logger.warning("๐Ÿ”Œ Connection closed during ping") + logger.warning("Connecting: Connection closed during ping") self.is_connected = False break except Exception as e: - logger.error(f"โŒ Ping failed: {e}") + logger.error(f"Error: Ping failed: {e}") self.is_connected = False break - + async def _message_loop(self): """ Continuous message receiving loop (like old API's websocket_listener) """ - logger.info("๐Ÿ“จ Starting message loop...") - + logger.info("Message: Starting message loop...") + while self.should_reconnect: try: if self.is_connected and self.websocket: try: # Receive message with timeout message = await asyncio.wait_for( - self.websocket.recv(), - timeout=30.0 + self.websocket.recv(), timeout=30.0 ) - - self.connection_stats['total_messages_received'] += 1 + + self.connection_stats["total_messages_received"] += 1 await self._process_message(message) - + except asyncio.TimeoutError: - logger.debug("๐Ÿ“จ Message receive timeout (normal)") + logger.debug("Message: Message receive timeout (normal)") continue else: await asyncio.sleep(1) - + except ConnectionClosed: - logger.warning("๐Ÿ”Œ Connection closed during message receive") + logger.warning("Connecting: Connection closed during message receive") self.is_connected = False break except Exception as e: - logger.error(f"โŒ Message loop error: {e}") + logger.error(f"Error: Message loop error: {e}") self.is_connected = False break - + async def _health_monitor_loop(self): """Monitor connection health and trigger reconnects if needed""" - logger.info("๐Ÿฅ Starting health monitor...") - + logger.info("Health: Starting health monitor...") + while self.should_reconnect: try: await asyncio.sleep(30) # Check every 30 seconds - + if not self.is_connected: - logger.warning("๐Ÿฅ Health check: Connection lost") + logger.warning("Health: Health check: Connection lost") continue - + # Check if we received a pong recently - if self.connection_stats['last_ping_time']: - time_since_ping = datetime.now() - self.connection_stats['last_ping_time'] - if time_since_ping > timedelta(seconds=60): # No response for 60 seconds - logger.warning("๐Ÿฅ Health check: No ping response, connection may be dead") + if self.connection_stats["last_ping_time"]: + time_since_ping = ( + datetime.now() - self.connection_stats["last_ping_time"] + ) + if time_since_ping > timedelta( + seconds=60 + ): # No response for 60 seconds + logger.warning( + "Health: Health check: No ping response, connection may be dead" + ) self.is_connected = False - + # Check WebSocket state if self.websocket and self.websocket.closed: - logger.warning("๐Ÿฅ Health check: WebSocket is closed") + logger.warning("Health: Health check: WebSocket is closed") self.is_connected = False - + except Exception as e: - logger.error(f"โŒ Health monitor error: {e}") - + logger.error(f"Error: Health monitor error: {e}") + async def _reconnection_monitor(self): """ Monitor for disconnections and automatically reconnect (like old API) """ - logger.info("๐Ÿ”„ Starting reconnection monitor...") - + logger.info("Persistent: Starting reconnection monitor...") + while self.should_reconnect: try: await asyncio.sleep(5) # Check every 5 seconds - + if not self.is_connected and self.should_reconnect: - logger.warning("๐Ÿ”„ Detected disconnection, attempting reconnect...") - + logger.warning( + "Persistent: Detected disconnection, attempting reconnect..." + ) + self.current_reconnect_attempts += 1 - self.connection_stats['total_reconnects'] += 1 - + self.connection_stats["total_reconnects"] += 1 + if self.current_reconnect_attempts <= self.max_reconnect_attempts: - logger.info(f"๐Ÿ”„ Reconnection attempt {self.current_reconnect_attempts}/{self.max_reconnect_attempts}") - + logger.info( + f"Persistent: Reconnection attempt {self.current_reconnect_attempts}/{self.max_reconnect_attempts}" + ) + # Clean up current connection if self.websocket: try: await self.websocket.close() - except: + except Exception: pass self.websocket = None - + # Try to reconnect success = await self._establish_connection() - + if success: - logger.success("โœ… Reconnection successful!") - await self._emit_event('reconnected', { - 'attempt': self.current_reconnect_attempts, - 'url': self.connection_info.url if self.connection_info else None - }) + logger.success("Success: Reconnection successful!") + await self._emit_event( + "reconnected", + { + "attempt": self.current_reconnect_attempts, + "url": self.connection_info.url + if self.connection_info + else None, + }, + ) else: - logger.error(f"โŒ Reconnection attempt {self.current_reconnect_attempts} failed") + logger.error( + f"Error: Reconnection attempt {self.current_reconnect_attempts} failed" + ) await asyncio.sleep(self.reconnect_delay) else: - logger.error(f"โŒ Max reconnection attempts ({self.max_reconnect_attempts}) reached") - await self._emit_event('max_reconnects_reached', { - 'attempts': self.current_reconnect_attempts - }) + logger.error( + f"Error: Max reconnection attempts ({self.max_reconnect_attempts}) reached" + ) + await self._emit_event( + "max_reconnects_reached", + {"attempts": self.current_reconnect_attempts}, + ) break - + except Exception as e: - logger.error(f"โŒ Reconnection monitor error: {e}") - + logger.error(f"Error: Reconnection monitor error: {e}") + async def _process_message(self, message): """Process incoming messages (like old API's on_message)""" try: # Convert bytes to string if needed if isinstance(message, bytes): - message = message.decode('utf-8') - - logger.debug(f"๐Ÿ“จ Received: {message[:100]}...") - + message = message.decode("utf-8") + + logger.debug(f"Message: Received: {message[:100]}...") + # Handle ping-pong (like old API) if message == "2": await self.websocket.send("3") - self.connection_stats['last_pong_time'] = datetime.now() - logger.debug("๐Ÿ“ Pong sent") + self.connection_stats["last_pong_time"] = datetime.now() + logger.debug("Ping: Pong sent") return - + # Handle authentication success (like old API) if "successauth" in message: - logger.success("โœ… Authentication successful") - await self._emit_event('authenticated', {}) + logger.success("Success: Authentication successful") + await self._emit_event("authenticated", {}) return - + # Handle other message types - await self._emit_event('message_received', {'message': message}) - + await self._emit_event("message_received", {"message": message}) + except Exception as e: - logger.error(f"โŒ Error processing message: {e}") - + logger.error(f"Error: Error processing message: {e}") + async def send_message(self, message: str) -> bool: """Send message with connection check""" try: if self.is_connected and self.websocket: await self.websocket.send(message) - self.connection_stats['total_messages_sent'] += 1 - logger.debug(f"๐Ÿ“ค Sent: {message[:50]}...") + self.connection_stats["total_messages_sent"] += 1 + logger.debug(f"Message: Sent: {message[:50]}...") return True else: - logger.warning("โš ๏ธ Cannot send message: not connected") + logger.warning("Caution: Cannot send message: not connected") return False except Exception as e: - logger.error(f"โŒ Failed to send message: {e}") + logger.error(f"Error: Failed to send message: {e}") self.is_connected = False return False - + def add_event_handler(self, event: str, handler: Callable): """Add event handler""" if event not in self._event_handlers: self._event_handlers[event] = [] self._event_handlers[event].append(handler) - + async def _emit_event(self, event: str, data: Any): """Emit event to handlers""" if event in self._event_handlers: @@ -424,92 +457,98 @@ async def _emit_event(self, event: str, data: Any): else: handler(data) except Exception as e: - logger.error(f"โŒ Error in event handler for {event}: {e}") - + logger.error(f"Error: Error in event handler for {event}: {e}") + def _extract_region_from_url(self, url: str) -> str: """Extract region name from URL""" try: - parts = url.split('//')[1].split('.')[0] - if 'api-' in parts: - return parts.replace('api-', '').upper() - elif 'demo' in parts: - return 'DEMO' + parts = url.split("//")[1].split(".")[0] + if "api-" in parts: + return parts.replace("api-", "").upper() + elif "demo" in parts: + return "DEMO" else: - return 'UNKNOWN' - except: - return 'UNKNOWN' - + return "UNKNOWN" + except Exception: + return "UNKNOWN" + def get_connection_stats(self) -> Dict[str, Any]: """Get detailed connection statistics""" return { **self.connection_stats, - 'is_connected': self.is_connected, - 'current_url': self.connection_info.url if self.connection_info else None, - 'current_region': self.connection_info.region if self.connection_info else None, - 'reconnect_attempts': self.current_reconnect_attempts, - 'uptime': ( + "is_connected": self.is_connected, + "current_url": self.connection_info.url if self.connection_info else None, + "current_region": self.connection_info.region + if self.connection_info + else None, + "reconnect_attempts": self.current_reconnect_attempts, + "uptime": ( datetime.now() - self.connection_info.connected_at if self.connection_info and self.connection_info.connected_at else timedelta() ), - 'available_regions': len(self.available_urls) + "available_regions": len(self.available_urls), } async def demo_keep_alive(): """Demo of the keep-alive connection manager""" - + # Example complete SSID ssid = r'42["auth",{"session":"n1p5ah5u8t9438rbunpgrq0hlq","isDemo":1,"uid":0,"platform":1}]' - + # Create keep-alive manager keep_alive = ConnectionKeepAlive(ssid, is_demo=True) - + # Add event handlers async def on_connected(data): - logger.success(f"๐ŸŽ‰ Connected to: {data}") - + logger.success(f"Successfully: Connected to: {data}") + async def on_reconnected(data): - logger.success(f"๐Ÿ”„ Reconnected after {data['attempt']} attempts") - + logger.success(f"Persistent: Reconnected after {data['attempt']} attempts") + async def on_message(data): - logger.info(f"๐Ÿ“จ Message: {data['message'][:50]}...") - - keep_alive.add_event_handler('connected', on_connected) - keep_alive.add_event_handler('reconnected', on_reconnected) - keep_alive.add_event_handler('message_received', on_message) - + logger.info(f"Message: Message: {data['message'][:50]}...") + + keep_alive.add_event_handler("connected", on_connected) + keep_alive.add_event_handler("reconnected", on_reconnected) + keep_alive.add_event_handler("message_received", on_message) + try: # Start persistent connection success = await keep_alive.start_persistent_connection() - + if success: - logger.info("๐Ÿš€ Keep-alive connection started, will maintain connection automatically...") - + logger.info( + "Starting: Keep-alive connection started, will maintain connection automatically..." + ) + # Let it run for a while to demonstrate keep-alive for i in range(60): # Run for 1 minute await asyncio.sleep(1) - + # Print stats every 10 seconds if i % 10 == 0: stats = keep_alive.get_connection_stats() - logger.info(f"๐Ÿ“Š Stats: Connected={stats['is_connected']}, " - f"Messages sent={stats['total_messages_sent']}, " - f"Messages received={stats['total_messages_received']}, " - f"Uptime={stats['uptime']}") - + logger.info( + f"Statistics: Stats: Connected={stats['is_connected']}, " + f"Messages sent={stats['total_messages_sent']}, " + f"Messages received={stats['total_messages_received']}, " + f"Uptime={stats['uptime']}" + ) + # Send a test message every 30 seconds if i % 30 == 0 and i > 0: await keep_alive.send_message('42["test"]') - + else: - logger.error("โŒ Failed to start keep-alive connection") - + logger.error("Error: Failed to start keep-alive connection") + finally: # Clean shutdown await keep_alive.stop_persistent_connection() if __name__ == "__main__": - logger.info("๐Ÿงช Testing Enhanced Keep-Alive Connection Manager") + logger.info("Testing: Testing Enhanced Keep-Alive Connection Manager") asyncio.run(demo_keep_alive()) diff --git a/connection_monitor.py b/connection_monitor.py index 4ef4471..e9a9d57 100644 --- a/connection_monitor.py +++ b/connection_monitor.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Advanced Connection Monitor and Diagnostics Tool Real-time monitoring, diagnostics, and performance analysis @@ -7,7 +6,6 @@ import asyncio import time import json -import threading from datetime import datetime, timedelta from typing import Dict, List, Any, Optional, Callable from dataclasses import dataclass, asdict @@ -16,12 +14,12 @@ from loguru import logger from pocketoptionapi_async.client import AsyncPocketOptionClient -from connection_keep_alive import ConnectionKeepAlive @dataclass class ConnectionMetrics: """Connection performance metrics""" + timestamp: datetime connection_time: float ping_time: Optional[float] @@ -34,6 +32,7 @@ class ConnectionMetrics: @dataclass class PerformanceSnapshot: """Performance snapshot""" + timestamp: datetime memory_usage_mb: float cpu_percent: float @@ -45,168 +44,173 @@ class PerformanceSnapshot: class ConnectionMonitor: """Advanced connection monitoring and diagnostics""" - + def __init__(self, ssid: str, is_demo: bool = True): self.ssid = ssid self.is_demo = is_demo - + # Monitoring state self.is_monitoring = False self.monitor_task: Optional[asyncio.Task] = None self.client: Optional[AsyncPocketOptionClient] = None - + # Metrics storage self.connection_metrics: deque = deque(maxlen=1000) self.performance_snapshots: deque = deque(maxlen=500) self.error_log: deque = deque(maxlen=200) self.message_stats: Dict[str, int] = defaultdict(int) - + # Real-time stats self.start_time = datetime.now() self.total_messages = 0 self.total_errors = 0 self.last_ping_time = None self.ping_times: deque = deque(maxlen=100) - + # Event handlers self.event_handlers: Dict[str, List[Callable]] = defaultdict(list) - + # Performance tracking self.response_times: deque = deque(maxlen=100) self.connection_attempts = 0 self.successful_connections = 0 - + async def start_monitoring(self, persistent_connection: bool = True) -> bool: """Start real-time monitoring""" - logger.info("๐Ÿ” Starting connection monitoring...") - + logger.info("Analysis: Starting connection monitoring...") + try: # Initialize client self.client = AsyncPocketOptionClient( - self.ssid, + self.ssid, is_demo=self.is_demo, persistent_connection=persistent_connection, - auto_reconnect=True + auto_reconnect=True, ) - + # Setup event handlers self._setup_event_handlers() - + # Connect self.connection_attempts += 1 start_time = time.time() - + success = await self.client.connect() - + if success: connection_time = time.time() - start_time self.successful_connections += 1 - + # Record connection metrics self._record_connection_metrics(connection_time, "CONNECTED") - + # Start monitoring tasks self.is_monitoring = True self.monitor_task = asyncio.create_task(self._monitoring_loop()) - - logger.success(f"โœ… Monitoring started (connection time: {connection_time:.3f}s)") + + logger.success( + f"Success: Monitoring started (connection time: {connection_time:.3f}s)" + ) return True else: self._record_connection_metrics(0, "FAILED") - logger.error("โŒ Failed to connect for monitoring") + logger.error("Error: Failed to connect for monitoring") return False - + except Exception as e: self.total_errors += 1 self._record_error("monitoring_start", str(e)) - logger.error(f"โŒ Failed to start monitoring: {e}") + logger.error(f"Error: Failed to start monitoring: {e}") return False - + async def stop_monitoring(self): """Stop monitoring""" - logger.info("๐Ÿ›‘ Stopping connection monitoring...") - + logger.info("Stopping connection monitoring...") + self.is_monitoring = False - + if self.monitor_task and not self.monitor_task.done(): self.monitor_task.cancel() try: await self.monitor_task except asyncio.CancelledError: pass - + if self.client: await self.client.disconnect() - - logger.info("โœ… Monitoring stopped") - + + logger.info("Success: Monitoring stopped") + def _setup_event_handlers(self): """Setup event handlers for monitoring""" if not self.client: return - + # Connection events - self.client.add_event_callback('connected', self._on_connected) - self.client.add_event_callback('disconnected', self._on_disconnected) - self.client.add_event_callback('reconnected', self._on_reconnected) - self.client.add_event_callback('auth_error', self._on_auth_error) - + self.client.add_event_callback("connected", self._on_connected) + self.client.add_event_callback("disconnected", self._on_disconnected) + self.client.add_event_callback("reconnected", self._on_reconnected) + self.client.add_event_callback("auth_error", self._on_auth_error) + # Data events - self.client.add_event_callback('balance_updated', self._on_balance_updated) - self.client.add_event_callback('candles_received', self._on_candles_received) - self.client.add_event_callback('message_received', self._on_message_received) - + self.client.add_event_callback("balance_updated", self._on_balance_updated) + self.client.add_event_callback("candles_received", self._on_candles_received) + self.client.add_event_callback("message_received", self._on_message_received) + async def _monitoring_loop(self): """Main monitoring loop""" - logger.info("๐Ÿ”„ Starting monitoring loop...") - + logger.info("Persistent: Starting monitoring loop...") + while self.is_monitoring: try: # Collect performance snapshot await self._collect_performance_snapshot() - + # Check connection health await self._check_connection_health() - + # Send ping and measure response await self._measure_ping_response() - + # Emit monitoring events await self._emit_monitoring_events() - + await asyncio.sleep(5) # Monitor every 5 seconds - + except Exception as e: self.total_errors += 1 self._record_error("monitoring_loop", str(e)) - logger.error(f"โŒ Monitoring loop error: {e}") - + logger.error(f"Error: Monitoring loop error: {e}") + async def _collect_performance_snapshot(self): """Collect performance metrics snapshot""" try: # Try to get system metrics memory_mb = 0 cpu_percent = 0 - + try: import psutil import os + process = psutil.Process(os.getpid()) memory_mb = process.memory_info().rss / 1024 / 1024 cpu_percent = process.cpu_percent() except ImportError: pass - + # Calculate messages per second uptime = (datetime.now() - self.start_time).total_seconds() messages_per_second = self.total_messages / uptime if uptime > 0 else 0 - + # Calculate error rate error_rate = self.total_errors / max(self.total_messages, 1) - + # Calculate average response time - avg_response_time = statistics.mean(self.response_times) if self.response_times else 0 - + avg_response_time = ( + statistics.mean(self.response_times) if self.response_times else 0 + ) + snapshot = PerformanceSnapshot( timestamp=datetime.now(), memory_usage_mb=memory_mb, @@ -214,121 +218,129 @@ async def _collect_performance_snapshot(self): active_connections=1 if self.client and self.client.is_connected else 0, messages_per_second=messages_per_second, error_rate=error_rate, - avg_response_time=avg_response_time + avg_response_time=avg_response_time, ) - + self.performance_snapshots.append(snapshot) - + except Exception as e: - logger.error(f"โŒ Error collecting performance snapshot: {e}") - + logger.error(f"Error: Error collecting performance snapshot: {e}") + async def _check_connection_health(self): """Check connection health status""" if not self.client: return - + try: # Check if still connected if not self.client.is_connected: self._record_connection_metrics(0, "DISCONNECTED") return - + # Try to get balance as health check start_time = time.time() balance = await self.client.get_balance() response_time = time.time() - start_time - + self.response_times.append(response_time) - + if balance: self._record_connection_metrics(response_time, "HEALTHY") else: self._record_connection_metrics(response_time, "UNHEALTHY") - + except Exception as e: self.total_errors += 1 self._record_error("health_check", str(e)) self._record_connection_metrics(0, "ERROR") - + async def _measure_ping_response(self): """Measure ping response time""" if not self.client or not self.client.is_connected: return - + try: start_time = time.time() await self.client.send_message('42["ps"]') - + # Note: We can't easily measure the actual ping response time # since it's handled internally. This measures send time. ping_time = time.time() - start_time - + self.ping_times.append(ping_time) self.last_ping_time = datetime.now() - + self.total_messages += 1 - self.message_stats['ping'] += 1 - + self.message_stats["ping"] += 1 + except Exception as e: self.total_errors += 1 self._record_error("ping_measure", str(e)) - + async def _emit_monitoring_events(self): """Emit monitoring events""" try: # Emit real-time stats stats = self.get_real_time_stats() - await self._emit_event('stats_update', stats) - + await self._emit_event("stats_update", stats) + # Emit alerts if needed await self._check_and_emit_alerts(stats) - + except Exception as e: - logger.error(f"โŒ Error emitting monitoring events: {e}") - + logger.error(f"Error: Error emitting monitoring events: {e}") + async def _check_and_emit_alerts(self, stats: Dict[str, Any]): """Check for alert conditions and emit alerts""" - + # High error rate alert - if stats['error_rate'] > 0.1: # 10% error rate - await self._emit_event('alert', { - 'type': 'high_error_rate', - 'value': stats['error_rate'], - 'threshold': 0.1, - 'message': f"High error rate detected: {stats['error_rate']:.1%}" - }) - + if stats["error_rate"] > 0.1: # 10% error rate + await self._emit_event( + "alert", + { + "type": "high_error_rate", + "value": stats["error_rate"], + "threshold": 0.1, + "message": f"High error rate detected: {stats['error_rate']:.1%}", + }, + ) + # Slow response time alert - if stats['avg_response_time'] > 5.0: # 5 seconds - await self._emit_event('alert', { - 'type': 'slow_response', - 'value': stats['avg_response_time'], - 'threshold': 5.0, - 'message': f"Slow response time: {stats['avg_response_time']:.2f}s" - }) - + if stats["avg_response_time"] > 5.0: # 5 seconds + await self._emit_event( + "alert", + { + "type": "slow_response", + "value": stats["avg_response_time"], + "threshold": 5.0, + "message": f"Slow response time: {stats['avg_response_time']:.2f}s", + }, + ) + # Connection issues alert - if not stats['is_connected']: - await self._emit_event('alert', { - 'type': 'connection_lost', - 'message': "Connection lost" - }) - + if not stats["is_connected"]: + await self._emit_event( + "alert", {"type": "connection_lost", "message": "Connection lost"} + ) + # Memory usage alert (if available) - if 'memory_usage_mb' in stats and stats['memory_usage_mb'] > 500: # 500MB - await self._emit_event('alert', { - 'type': 'high_memory', - 'value': stats['memory_usage_mb'], - 'threshold': 500, - 'message': f"High memory usage: {stats['memory_usage_mb']:.1f}MB" - }) - + if "memory_usage_mb" in stats and stats["memory_usage_mb"] > 500: # 500MB + await self._emit_event( + "alert", + { + "type": "high_memory", + "value": stats["memory_usage_mb"], + "threshold": 500, + "message": f"High memory usage: {stats['memory_usage_mb']:.1f}MB", + }, + ) + def _record_connection_metrics(self, connection_time: float, status: str): """Record connection metrics""" region = "UNKNOWN" if self.client and self.client.connection_info: region = self.client.connection_info.region or "UNKNOWN" - + metrics = ConnectionMetrics( timestamp=datetime.now(), connection_time=connection_time, @@ -336,20 +348,20 @@ def _record_connection_metrics(self, connection_time: float, status: str): message_count=self.total_messages, error_count=self.total_errors, region=region, - status=status + status=status, ) - + self.connection_metrics.append(metrics) - + def _record_error(self, error_type: str, error_message: str): """Record error for analysis""" error_record = { - 'timestamp': datetime.now(), - 'type': error_type, - 'message': error_message + "timestamp": datetime.now(), + "type": error_type, + "message": error_message, } self.error_log.append(error_record) - + async def _emit_event(self, event_type: str, data: Any): """Emit event to registered handlers""" if event_type in self.event_handlers: @@ -360,245 +372,288 @@ async def _emit_event(self, event_type: str, data: Any): else: handler(data) except Exception as e: - logger.error(f"โŒ Error in event handler for {event_type}: {e}") - + logger.error(f"Error: Error in event handler for {event_type}: {e}") + # Event handler methods async def _on_connected(self, data): self.total_messages += 1 - self.message_stats['connected'] += 1 - logger.info("๐Ÿ”— Connection established") - + self.message_stats["connected"] += 1 + logger.info("Connection established") + async def _on_disconnected(self, data): self.total_messages += 1 - self.message_stats['disconnected'] += 1 - logger.warning("๐Ÿ”Œ Connection lost") - + self.message_stats["disconnected"] += 1 + logger.warning("Connection lost") + async def _on_reconnected(self, data): self.total_messages += 1 - self.message_stats['reconnected'] += 1 - logger.info("๐Ÿ”„ Connection restored") - + self.message_stats["reconnected"] += 1 + logger.info("Connection restored") + async def _on_auth_error(self, data): self.total_errors += 1 - self.message_stats['auth_error'] += 1 + self.message_stats["auth_error"] += 1 self._record_error("auth_error", str(data)) - logger.error("๐Ÿ” Authentication error") - + logger.error("Authentication error") + async def _on_balance_updated(self, data): self.total_messages += 1 - self.message_stats['balance'] += 1 - + self.message_stats["balance"] += 1 + async def _on_candles_received(self, data): self.total_messages += 1 - self.message_stats['candles'] += 1 - + self.message_stats["candles"] += 1 + async def _on_message_received(self, data): self.total_messages += 1 - self.message_stats['message'] += 1 - + self.message_stats["message"] += 1 + def add_event_handler(self, event_type: str, handler: Callable): """Add event handler for monitoring events""" self.event_handlers[event_type].append(handler) - + def get_real_time_stats(self) -> Dict[str, Any]: """Get current real-time statistics""" uptime = datetime.now() - self.start_time - + stats = { - 'uptime': uptime.total_seconds(), - 'uptime_str': str(uptime).split('.')[0], - 'total_messages': self.total_messages, - 'total_errors': self.total_errors, - 'error_rate': self.total_errors / max(self.total_messages, 1), - 'messages_per_second': self.total_messages / uptime.total_seconds() if uptime.total_seconds() > 0 else 0, - 'connection_attempts': self.connection_attempts, - 'successful_connections': self.successful_connections, - 'connection_success_rate': self.successful_connections / max(self.connection_attempts, 1), - 'is_connected': self.client.is_connected if self.client else False, - 'last_ping_time': self.last_ping_time.isoformat() if self.last_ping_time else None, - 'message_types': dict(self.message_stats) + "uptime": uptime.total_seconds(), + "uptime_str": str(uptime).split(".")[0], + "total_messages": self.total_messages, + "total_errors": self.total_errors, + "error_rate": self.total_errors / max(self.total_messages, 1), + "messages_per_second": self.total_messages / uptime.total_seconds() + if uptime.total_seconds() > 0 + else 0, + "connection_attempts": self.connection_attempts, + "successful_connections": self.successful_connections, + "connection_success_rate": self.successful_connections + / max(self.connection_attempts, 1), + "is_connected": self.client.is_connected if self.client else False, + "last_ping_time": self.last_ping_time.isoformat() + if self.last_ping_time + else None, + "message_types": dict(self.message_stats), } - + # Add response time stats if self.response_times: - stats.update({ - 'avg_response_time': statistics.mean(self.response_times), - 'min_response_time': min(self.response_times), - 'max_response_time': max(self.response_times), - 'median_response_time': statistics.median(self.response_times) - }) - + stats.update( + { + "avg_response_time": statistics.mean(self.response_times), + "min_response_time": min(self.response_times), + "max_response_time": max(self.response_times), + "median_response_time": statistics.median(self.response_times), + } + ) + # Add ping stats if self.ping_times: - stats.update({ - 'avg_ping_time': statistics.mean(self.ping_times), - 'min_ping_time': min(self.ping_times), - 'max_ping_time': max(self.ping_times) - }) - + stats.update( + { + "avg_ping_time": statistics.mean(self.ping_times), + "min_ping_time": min(self.ping_times), + "max_ping_time": max(self.ping_times), + } + ) + # Add latest performance snapshot data if self.performance_snapshots: latest = self.performance_snapshots[-1] - stats.update({ - 'memory_usage_mb': latest.memory_usage_mb, - 'cpu_percent': latest.cpu_percent - }) - + stats.update( + { + "memory_usage_mb": latest.memory_usage_mb, + "cpu_percent": latest.cpu_percent, + } + ) + return stats - + def get_historical_metrics(self, hours: int = 1) -> Dict[str, Any]: """Get historical metrics for the specified time period""" cutoff_time = datetime.now() - timedelta(hours=hours) - + # Filter metrics - recent_metrics = [m for m in self.connection_metrics if m.timestamp > cutoff_time] - recent_snapshots = [s for s in self.performance_snapshots if s.timestamp > cutoff_time] - recent_errors = [e for e in self.error_log if e['timestamp'] > cutoff_time] - + recent_metrics = [ + m for m in self.connection_metrics if m.timestamp > cutoff_time + ] + recent_snapshots = [ + s for s in self.performance_snapshots if s.timestamp > cutoff_time + ] + recent_errors = [e for e in self.error_log if e["timestamp"] > cutoff_time] + historical = { - 'time_period_hours': hours, - 'connection_metrics_count': len(recent_metrics), - 'performance_snapshots_count': len(recent_snapshots), - 'error_count': len(recent_errors), - 'metrics': [asdict(m) for m in recent_metrics], - 'snapshots': [asdict(s) for s in recent_snapshots], - 'errors': recent_errors + "time_period_hours": hours, + "connection_metrics_count": len(recent_metrics), + "performance_snapshots_count": len(recent_snapshots), + "error_count": len(recent_errors), + "metrics": [asdict(m) for m in recent_metrics], + "snapshots": [asdict(s) for s in recent_snapshots], + "errors": recent_errors, } - + # Calculate trends if recent_snapshots: - memory_values = [s.memory_usage_mb for s in recent_snapshots if s.memory_usage_mb > 0] - response_values = [s.avg_response_time for s in recent_snapshots if s.avg_response_time > 0] - + memory_values = [ + s.memory_usage_mb for s in recent_snapshots if s.memory_usage_mb > 0 + ] + response_values = [ + s.avg_response_time for s in recent_snapshots if s.avg_response_time > 0 + ] + if memory_values: - historical['memory_trend'] = { - 'avg': statistics.mean(memory_values), - 'min': min(memory_values), - 'max': max(memory_values), - 'trend': 'increasing' if len(memory_values) > 1 and memory_values[-1] > memory_values[0] else 'stable' + historical["memory_trend"] = { + "avg": statistics.mean(memory_values), + "min": min(memory_values), + "max": max(memory_values), + "trend": "increasing" + if len(memory_values) > 1 and memory_values[-1] > memory_values[0] + else "stable", } - + if response_values: - historical['response_time_trend'] = { - 'avg': statistics.mean(response_values), - 'min': min(response_values), - 'max': max(response_values), - 'trend': 'improving' if len(response_values) > 1 and response_values[-1] < response_values[0] else 'stable' + historical["response_time_trend"] = { + "avg": statistics.mean(response_values), + "min": min(response_values), + "max": max(response_values), + "trend": "improving" + if len(response_values) > 1 + and response_values[-1] < response_values[0] + else "stable", } - + return historical - + def generate_diagnostics_report(self) -> Dict[str, Any]: """Generate comprehensive diagnostics report""" stats = self.get_real_time_stats() historical = self.get_historical_metrics(hours=2) - + # Health assessment health_score = 100 health_issues = [] - - if stats['error_rate'] > 0.05: + + if stats["error_rate"] > 0.05: health_score -= 20 health_issues.append(f"High error rate: {stats['error_rate']:.1%}") - - if not stats['is_connected']: + + if not stats["is_connected"]: health_score -= 30 health_issues.append("Not connected") - - if stats.get('avg_response_time', 0) > 3.0: + + if stats.get("avg_response_time", 0) > 3.0: health_score -= 15 - health_issues.append(f"Slow response time: {stats.get('avg_response_time', 0):.2f}s") - - if stats['connection_success_rate'] < 0.9: + health_issues.append( + f"Slow response time: {stats.get('avg_response_time', 0):.2f}s" + ) + + if stats["connection_success_rate"] < 0.9: health_score -= 10 - health_issues.append(f"Low connection success rate: {stats['connection_success_rate']:.1%}") - + health_issues.append( + f"Low connection success rate: {stats['connection_success_rate']:.1%}" + ) + health_score = max(0, health_score) - + # Recommendations recommendations = [] - - if stats['error_rate'] > 0.1: - recommendations.append("High error rate detected. Check network connectivity and SSID validity.") - - if stats.get('avg_response_time', 0) > 5.0: - recommendations.append("Slow response times. Consider using persistent connections or different region.") - - if stats.get('memory_usage_mb', 0) > 300: - recommendations.append("High memory usage detected. Monitor for memory leaks.") - + + if stats["error_rate"] > 0.1: + recommendations.append( + "High error rate detected. Check network connectivity and SSID validity." + ) + + if stats.get("avg_response_time", 0) > 5.0: + recommendations.append( + "Slow response times. Consider using persistent connections or different region." + ) + + if stats.get("memory_usage_mb", 0) > 300: + recommendations.append( + "High memory usage detected. Monitor for memory leaks." + ) + if not recommendations: recommendations.append("System is operating normally.") - + report = { - 'timestamp': datetime.now().isoformat(), - 'health_score': health_score, - 'health_status': 'EXCELLENT' if health_score > 90 else 'GOOD' if health_score > 70 else 'FAIR' if health_score > 50 else 'POOR', - 'health_issues': health_issues, - 'recommendations': recommendations, - 'real_time_stats': stats, - 'historical_metrics': historical, - 'connection_summary': { - 'total_attempts': stats['connection_attempts'], - 'successful_connections': stats['successful_connections'], - 'current_status': 'CONNECTED' if stats['is_connected'] else 'DISCONNECTED', - 'uptime': stats['uptime_str'] - } + "timestamp": datetime.now().isoformat(), + "health_score": health_score, + "health_status": "EXCELLENT" + if health_score > 90 + else "GOOD" + if health_score > 70 + else "FAIR" + if health_score > 50 + else "POOR", + "health_issues": health_issues, + "recommendations": recommendations, + "real_time_stats": stats, + "historical_metrics": historical, + "connection_summary": { + "total_attempts": stats["connection_attempts"], + "successful_connections": stats["successful_connections"], + "current_status": "CONNECTED" + if stats["is_connected"] + else "DISCONNECTED", + "uptime": stats["uptime_str"], + }, } - + return report - + def export_metrics_csv(self, filename: str = None) -> str: """Export metrics to CSV file""" if not filename: filename = f"metrics_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" - + try: import pandas as pd - + # Convert metrics to DataFrame metrics_data = [] for metric in self.connection_metrics: metrics_data.append(asdict(metric)) - + if metrics_data: df = pd.DataFrame(metrics_data) df.to_csv(filename, index=False) - logger.info(f"๐Ÿ“Š Metrics exported to {filename}") + logger.info(f"Statistics: Metrics exported to {filename}") else: logger.warning("No metrics data to export") - + return filename - + except ImportError: logger.error("pandas not available for CSV export") - + # Fallback: basic CSV export import csv - with open(filename, 'w', newline='') as csvfile: + + with open(filename, "w", newline="") as csvfile: if self.connection_metrics: fieldnames = asdict(self.connection_metrics[0]).keys() writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() for metric in self.connection_metrics: writer.writerow(asdict(metric)) - + return filename class RealTimeDisplay: """Real-time console display for monitoring""" - + def __init__(self, monitor: ConnectionMonitor): self.monitor = monitor self.display_task: Optional[asyncio.Task] = None self.is_displaying = False - + async def start_display(self): """Start real-time display""" self.is_displaying = True self.display_task = asyncio.create_task(self._display_loop()) - + async def stop_display(self): """Stop real-time display""" self.is_displaying = False @@ -608,61 +663,61 @@ async def stop_display(self): await self.display_task except asyncio.CancelledError: pass - + async def _display_loop(self): """Display loop""" while self.is_displaying: try: # Clear screen (ANSI escape sequence) print("\033[2J\033[H", end="") - + # Display header - print("๐Ÿ” PocketOption API Connection Monitor") + print("Analysis: PocketOption API Connection Monitor") print("=" * 60) - + # Get stats stats = self.monitor.get_real_time_stats() - + # Display connection status - status = "๐ŸŸข CONNECTED" if stats['is_connected'] else "๐Ÿ”ด DISCONNECTED" + status = "Connected" if stats["is_connected"] else "Disconnected" print(f"Status: {status}") print(f"Uptime: {stats['uptime_str']}") print() - + # Display metrics - print("๐Ÿ“Š Metrics:") + print("Statistics: Metrics:") print(f" Messages: {stats['total_messages']}") print(f" Errors: {stats['total_errors']}") print(f" Error Rate: {stats['error_rate']:.1%}") print(f" Messages/sec: {stats['messages_per_second']:.2f}") print() - + # Display performance - if 'avg_response_time' in stats: - print("โšก Performance:") + if "avg_response_time" in stats: + print("Performance:") print(f" Avg Response: {stats['avg_response_time']:.3f}s") print(f" Min Response: {stats['min_response_time']:.3f}s") print(f" Max Response: {stats['max_response_time']:.3f}s") print() - + # Display memory if available - if 'memory_usage_mb' in stats: - print("๐Ÿ’พ Resources:") + if "memory_usage_mb" in stats: + print("Resources:") print(f" Memory: {stats['memory_usage_mb']:.1f} MB") print(f" CPU: {stats['cpu_percent']:.1f}%") print() - + # Display message types - if stats['message_types']: - print("๐Ÿ“จ Message Types:") - for msg_type, count in stats['message_types'].items(): + if stats["message_types"]: + print("Message: Message Types:") + for msg_type, count in stats["message_types"].items(): print(f" {msg_type}: {count}") print() - + print("Press Ctrl+C to stop monitoring...") - + await asyncio.sleep(2) # Update every 2 seconds - + except Exception as e: logger.error(f"Display error: {e}") await asyncio.sleep(1) @@ -670,87 +725,91 @@ async def _display_loop(self): async def run_monitoring_demo(ssid: str = None): """Run monitoring demonstration""" - + if not ssid: ssid = r'42["auth",{"session":"demo_session_for_monitoring","isDemo":1,"uid":0,"platform":1}]' - logger.warning("โš ๏ธ Using demo SSID for monitoring") - - logger.info("๐Ÿ” Starting Advanced Connection Monitor Demo") - + logger.warning("Caution: Using demo SSID for monitoring") + + logger.info("Analysis: Starting Advanced Connection Monitor Demo") + # Create monitor monitor = ConnectionMonitor(ssid, is_demo=True) - + # Add event handlers for alerts async def on_alert(alert_data): - logger.warning(f"๐Ÿšจ ALERT: {alert_data['message']}") - + logger.warning(f"Alert: ALERT: {alert_data['message']}") + async def on_stats_update(stats): # Could send to external monitoring system pass - - monitor.add_event_handler('alert', on_alert) - monitor.add_event_handler('stats_update', on_stats_update) - + + monitor.add_event_handler("alert", on_alert) + monitor.add_event_handler("stats_update", on_stats_update) + # Create real-time display display = RealTimeDisplay(monitor) - + try: # Start monitoring success = await monitor.start_monitoring(persistent_connection=True) - + if success: # Start real-time display await display.start_display() - + # Let it run for a while await asyncio.sleep(120) # Run for 2 minutes - + else: - logger.error("โŒ Failed to start monitoring") - + logger.error("Error: Failed to start monitoring") + except KeyboardInterrupt: - logger.info("๐Ÿ›‘ Monitoring stopped by user") - + logger.info("Stopping: Monitoring stopped by user") + finally: # Stop display and monitoring await display.stop_display() await monitor.stop_monitoring() - + # Generate final report report = monitor.generate_diagnostics_report() - - logger.info("\n๐Ÿ FINAL DIAGNOSTICS REPORT") + + logger.info("\nCompleted: FINAL DIAGNOSTICS REPORT") logger.info("=" * 50) - logger.info(f"Health Score: {report['health_score']}/100 ({report['health_status']})") - - if report['health_issues']: + logger.info( + f"Health Score: {report['health_score']}/100 ({report['health_status']})" + ) + + if report["health_issues"]: logger.warning("Issues found:") - for issue in report['health_issues']: + for issue in report["health_issues"]: logger.warning(f" - {issue}") - + logger.info("Recommendations:") - for rec in report['recommendations']: + for rec in report["recommendations"]: logger.info(f" - {rec}") - + # Save detailed report - report_file = f"monitoring_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" - with open(report_file, 'w') as f: + report_file = ( + f"monitoring_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + ) + with open(report_file, "w") as f: json.dump(report, f, indent=2, default=str) - - logger.info(f"๐Ÿ“„ Detailed report saved to: {report_file}") - + + logger.info(f"Report: Detailed report saved to: {report_file}") + # Export metrics metrics_file = monitor.export_metrics_csv() - logger.info(f"๐Ÿ“Š Metrics exported to: {metrics_file}") + logger.info(f"Statistics: Metrics exported to: {metrics_file}") if __name__ == "__main__": import sys - + # Allow passing SSID as command line argument ssid = None if len(sys.argv) > 1: ssid = sys.argv[1] logger.info(f"Using provided SSID: {ssid[:50]}...") - + asyncio.run(run_monitoring_demo(ssid)) diff --git a/demo_enhanced_api.py b/demo_enhanced_api.py index 895302c..dea2356 100644 --- a/demo_enhanced_api.py +++ b/demo_enhanced_api.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Complete Demo of Enhanced PocketOption API with Keep-Alive Demonstrates all the improvements based on the old API patterns @@ -6,256 +5,267 @@ import asyncio import os -import time -from datetime import datetime, timedelta +from datetime import datetime from loguru import logger -from pocketoptionapi_async import AsyncPocketOptionClient, OrderDirection +from pocketoptionapi_async import AsyncPocketOptionClient async def demo_enhanced_features(): """Comprehensive demo of all enhanced features""" - - print("๐Ÿš€ PocketOption Enhanced API Demo") + + print("Starting PocketOption Enhanced API Demo") print("=" * 60) print("Demonstrating all enhancements based on old API patterns:") - print("โœ… Complete SSID format support") - print("โœ… Persistent connections with automatic keep-alive") - print("โœ… Background ping/pong handling (20-second intervals)") - print("โœ… Automatic reconnection with multiple region fallback") - print("โœ… Connection health monitoring and statistics") - print("โœ… Event-driven architecture with callbacks") - print("โœ… Enhanced error handling and recovery") - print("โœ… Modern async/await patterns") + print("Success: Complete SSID format support") + print("Success: Persistent connections with automatic keep-alive") + print("Success: Background ping/pong handling (20-second intervals)") + print("Success: Automatic reconnection with multiple region fallback") + print("Success: Connection health monitoring and statistics") + print("Success: Event-driven architecture with callbacks") + print("Success: Enhanced error handling and recovery") + print("Success: Modern async/await patterns") print("=" * 60) print() - + # Complete SSID format (as requested) ssid = r'42["auth",{"session":"n1p5ah5u8t9438rbunpgrq0hlq","isDemo":1,"uid":72645361,"platform":1,"isFastHistory":true}]' - print(f"๐Ÿ”‘ Using complete SSID format:") + print("Authentication: Using complete SSID format:") print(f" {ssid[:80]}...") print() - + # Demo 1: Basic Enhanced Client - print("๐Ÿ“‹ Demo 1: Enhanced Client with Complete SSID") + print("Demonstration 1: Enhanced Client with Complete SSID") print("-" * 50) - + try: # Create client with complete SSID (as user requested) client = AsyncPocketOptionClient(ssid=ssid, is_demo=True) - - print(f"โœ… Client created with parsed components:") + + print("Success: Client created with parsed components:") print(f" Session ID: {getattr(client, 'session_id', 'N/A')[:20]}...") print(f" UID: {client.uid}") print(f" Platform: {client.platform}") print(f" Demo Mode: {client.is_demo}") print(f" Fast History: {client.is_fast_history}") - + # Test connection - print("\n๐Ÿ”Œ Testing connection...") + print("\nConnecting: Testing connection...") try: await client.connect() if client.is_connected: - print("โœ… Connected successfully!") - + print("Success: Connected successfully!") + # Show connection stats stats = client.get_connection_stats() - print(f"๐Ÿ“Š Connection Stats: {stats}") - + print(f"Statistics: Connection Stats: {stats}") + else: - print("โ„น๏ธ Connection failed (expected with test SSID)") + print("Note: Connection failed (expected with test SSID)") except Exception as e: - print(f"โ„น๏ธ Connection error (expected): {str(e)[:100]}...") - + print(f"Note: Connection error (expected): {str(e)[:100]}...") + await client.disconnect() - + except Exception as e: - print(f"โ„น๏ธ Client demo error: {e}") - + print(f"Note: Client demonstration error: {e}") + print() - + # Demo 2: Persistent Connection Features - print("๐Ÿ”„ Demo 2: Persistent Connection with Keep-Alive") + print("Persistent: Demonstration 2: Persistent Connection with Keep-Alive") print("-" * 50) - + try: # Create client with persistent connection enabled persistent_client = AsyncPocketOptionClient( ssid=ssid, is_demo=True, persistent_connection=True, # Enable keep-alive like old API - auto_reconnect=True + auto_reconnect=True, ) - + # Add event handlers to monitor connection events events_log = [] - + def on_connected(data): - events_log.append(f"CONNECTED: {datetime.now().strftime('%H:%M:%S')} - {data}") - print(f"๐ŸŽ‰ Event: Connected at {datetime.now().strftime('%H:%M:%S')}") - + events_log.append( + f"CONNECTED: {datetime.now().strftime('%H:%M:%S')} - {data}" + ) + print( + f"Successfully: Event: Connected at {datetime.now().strftime('%H:%M:%S')}" + ) + def on_reconnected(data): - events_log.append(f"RECONNECTED: {datetime.now().strftime('%H:%M:%S')} - {data}") - print(f"๐Ÿ”„ Event: Reconnected at {datetime.now().strftime('%H:%M:%S')}") - + events_log.append( + f"RECONNECTED: {datetime.now().strftime('%H:%M:%S')} - {data}" + ) + print( + f"Reconnection: Event: Reconnected at {datetime.now().strftime('%H:%M:%S')}" + ) + def on_authenticated(data): events_log.append(f"AUTHENTICATED: {datetime.now().strftime('%H:%M:%S')}") - print(f"โœ… Event: Authenticated at {datetime.now().strftime('%H:%M:%S')}") - - persistent_client.add_event_callback('connected', on_connected) - persistent_client.add_event_callback('reconnected', on_reconnected) - persistent_client.add_event_callback('authenticated', on_authenticated) - - print("๐Ÿš€ Starting persistent connection...") + print( + f"Success: Event: Authenticated at {datetime.now().strftime('%H:%M:%S')}" + ) + + persistent_client.add_event_callback("connected", on_connected) + persistent_client.add_event_callback("reconnected", on_reconnected) + persistent_client.add_event_callback("authenticated", on_authenticated) + + print("Starting persistent connection...") try: success = await persistent_client.connect(persistent=True) - + if success: - print("โœ… Persistent connection established") - + print("Success: Persistent connection established") + # Monitor for 30 seconds to show keep-alive behavior - print("๐Ÿ“Š Monitoring persistent connection (30 seconds)...") + print("Statistics: Monitoring persistent connection (30 seconds)...") print(" Watch for automatic pings and reconnection attempts...") - + for i in range(30): await asyncio.sleep(1) - + # Show stats every 10 seconds if i % 10 == 0 and i > 0: stats = persistent_client.get_connection_stats() - print(f" ๐Ÿ“ˆ [{i}s] Connected: {persistent_client.is_connected}, " - f"Messages sent: {stats.get('messages_sent', 0)}, " - f"Reconnects: {stats.get('total_reconnects', 0)}") - + print( + f" Data: [{i}s] Connected: {persistent_client.is_connected}, " + f"Messages sent: {stats.get('messages_sent', 0)}, " + f"Reconnects: {stats.get('total_reconnects', 0)}" + ) + # Show final event log - print(f"\n๐Ÿ“‹ Connection Events ({len(events_log)} total):") + print(f"\nDemonstration: Connection Events ({len(events_log)} total):") for event in events_log: print(f" โ€ข {event}") - + else: - print("โ„น๏ธ Persistent connection failed (expected with test SSID)") - + print("Note: Persistent connection failed (expected with test SSID)") + except Exception as e: - print(f"โ„น๏ธ Persistent connection error: {str(e)[:100]}...") - + print(f"Note: Persistent connection error: {str(e)[:100]}...") + await persistent_client.disconnect() - + except Exception as e: - print(f"โ„น๏ธ Persistent demo error: {e}") - + print(f"Note: Persistent demonstration error: {e}") + print() - + # Demo 3: API Features with Real Data (if available) - print("๐Ÿ“Š Demo 3: API Features and Data Operations") + print("Statistics: Demonstration 3: API Features and Data Operations") print("-" * 50) - + real_ssid = os.getenv("POCKET_OPTION_SSID") - if real_ssid and not "n1p5ah5u8t9438rbunpgrq0hlq" in real_ssid: - print("๐Ÿ”‘ Real SSID detected - testing with live connection...") - + if real_ssid and "n1p5ah5u8t9438rbunpgrq0hlq" not in real_ssid: + print("Authentication: Real SSID detected - testing with live connection...") + try: live_client = AsyncPocketOptionClient( - ssid=real_ssid, - is_demo=True, - auto_reconnect=True + ssid=real_ssid, is_demo=True, auto_reconnect=True ) - + success = await live_client.connect() if success: - print("โœ… Live connection established") - + print("Success: Live connection established") + # Test balance try: balance = await live_client.get_balance() - print(f"๐Ÿ’ฐ Balance: ${balance.balance:.2f} {balance.currency}") + print(f"Balance: ${balance.balance:.2f} {balance.currency}") except Exception as e: - print(f"โ„น๏ธ Balance test: {e}") - + print(f"Note: Balance test: {e}") + # Test candles try: candles = await live_client.get_candles("EURUSD_otc", "1m", 5) - print(f"๐Ÿ“ˆ Retrieved {len(candles)} candles for EURUSD_otc") - + print(f"Retrieved: Retrieved {len(candles)} candles for EURUSD_otc") + # Test DataFrame conversion df = await live_client.get_candles_dataframe("EURUSD_otc", "1m", 5) - print(f"๐Ÿ“Š DataFrame shape: {df.shape}") + print(f"Statistics: DataFrame shape: {df.shape}") except Exception as e: - print(f"โ„น๏ธ Candles test: {e}") - + print(f"Note: Candles test: {e}") + # Test health monitoring health = await live_client.get_health_status() - print(f"๐Ÿฅ Health Status: {health}") - + print(f"Health Status: {health}") + # Test performance metrics metrics = await live_client.get_performance_metrics() - print(f"๐Ÿ“Š Performance Metrics: {metrics}") - + print(f"Statistics: Performance Metrics: {metrics}") + await live_client.disconnect() - + except Exception as e: - print(f"โŒ Live demo error: {e}") + print(f"Error: Live demonstration error: {e}") else: - print("โ„น๏ธ Skipping live demo - requires real SSID") - print(" Set environment variable: export POCKET_OPTION_SSID='your_complete_ssid'") - + print("Note: Skipping live demo - requires real SSID") + print( + " Set environment variable: export POCKET_OPTION_SSID='your_complete_ssid'" + ) + print() def show_api_improvements(): """Show comparison with old API""" - - print("๐Ÿ” API Improvements Summary") + + print("Analysis: API Improvements Summary") print("=" * 60) - - print("๐Ÿ—๏ธ ARCHITECTURE IMPROVEMENTS:") + + print("Architecture: ARCHITECTURE IMPROVEMENTS:") print(" Old API: Synchronous with threading") print(" New API: Fully async/await with modern patterns") print() - - print("๐Ÿ”Œ CONNECTION MANAGEMENT:") + + print("Connection: CONNECTION MANAGEMENT:") print(" Old API: Manual daemon threads + run_forever()") print(" New API: Persistent connections with asyncio tasks") - print(" โœ… Automatic ping every 20 seconds") - print(" โœ… Health monitoring and statistics") - print(" โœ… Graceful reconnection handling") + print(" Success: Automatic ping every 20 seconds") + print(" Success: Health monitoring and statistics") + print(" Success: Graceful reconnection handling") print() - - print("๐Ÿ“ก MESSAGE HANDLING:") + + print("Message: MESSAGE HANDLING:") print(" Old API: Basic message processing") print(" New API: Optimized message routing with caching") - print(" โœ… Message batching for performance") - print(" โœ… Event-driven callbacks") - print(" โœ… Type-safe message models") + print(" Success: Message batching for performance") + print(" Success: Event-driven callbacks") + print(" Success: Type-safe message models") print() - - print("๐Ÿ›ก๏ธ ERROR HANDLING:") + + print("Error Handling: ERROR HANDLING:") print(" Old API: Basic try/catch with global variables") print(" New API: Comprehensive error monitoring") - print(" โœ… Circuit breaker pattern") - print(" โœ… Retry mechanisms with backoff") - print(" โœ… Health checks and alerting") + print(" Success: Circuit breaker pattern") + print(" Success: Retry mechanisms with backoff") + print(" Success: Health checks and alerting") print() - - print("๐Ÿ“Š DATA MANAGEMENT:") + + print("Statistics: DATA MANAGEMENT:") print(" Old API: Basic data structures") print(" New API: Modern data handling") - print(" โœ… Pydantic models for type safety") - print(" โœ… pandas DataFrame integration") - print(" โœ… Automatic data validation") + print(" Success: Pydantic models for type safety") + print(" Success: pandas DataFrame integration") + print(" Success: Automatic data validation") print() - - print("๐Ÿ”ง DEVELOPER EXPERIENCE:") + + print("Developer Experience: DEVELOPER EXPERIENCE:") print(" Old API: Manual setup and configuration") print(" New API: Enhanced developer tools") - print(" โœ… Rich logging with loguru") - print(" โœ… Context manager support") - print(" โœ… Comprehensive testing") - print(" โœ… Performance monitoring") + print(" Success: Rich logging with loguru") + print(" Success: Context manager support") + print(" Success: Comprehensive testing") + print(" Success: Performance monitoring") print() - - print("๐ŸŽฏ USAGE EXAMPLES:") + + print("Usage Examples: USAGE EXAMPLES:") print() - + print(" OLD API STYLE:") print(" ```python") print(" api = PocketOption(ssid, demo=True)") @@ -265,11 +275,13 @@ def show_api_improvements(): print(" time.sleep(1)") print(" ```") print() - + print(" NEW API STYLE:") print(" ```python") - print(" ssid = r'42[\"auth\",{\"session\":\"...\",\"isDemo\":1,\"uid\":123}]'") - print(" async with AsyncPocketOptionClient(ssid=ssid, persistent_connection=True) as client:") + print(' ssid = r\'42["auth",{"session":"...","isDemo":1,"uid":123}]\'') + print( + " async with AsyncPocketOptionClient(ssid=ssid, persistent_connection=True) as client:" + ) print(" balance = await client.get_balance()") print(" df = await client.get_candles_dataframe('EURUSD_otc', '1m', 100)") print(" # Connection maintained automatically with keep-alive") @@ -279,24 +291,24 @@ def show_api_improvements(): def show_keep_alive_features(): """Show specific keep-alive features""" - - print("๐Ÿ”„ Keep-Alive Features Based on Old API Analysis") + + print("Persistent: Keep-Alive Features Based on Old API Analysis") print("=" * 60) - - print("๐Ÿ“‹ IMPLEMENTED FEATURES:") - print("โœ… Continuous ping loop (20-second intervals)") - print("โœ… Automatic reconnection on disconnection") - print("โœ… Multiple region fallback") - print("โœ… Background task management") - print("โœ… Connection health monitoring") - print("โœ… Message routing and processing") - print("โœ… Event-driven callbacks") - print("โœ… Connection statistics tracking") - print("โœ… Graceful shutdown and cleanup") - print("โœ… Complete SSID format support") + + print("Demonstration: IMPLEMENTED FEATURES:") + print("Success: Continuous ping loop (20-second intervals)") + print("Success: Automatic reconnection on disconnection") + print("Success: Multiple region fallback") + print("Success: Background task management") + print("Success: Connection health monitoring") + print("Success: Message routing and processing") + print("Success: Event-driven callbacks") + print("Success: Connection statistics tracking") + print("Success: Graceful shutdown and cleanup") + print("Success: Complete SSID format support") print() - - print("๐Ÿ”ง TECHNICAL IMPLEMENTATION:") + + print("Technical Implementation: TECHNICAL IMPLEMENTATION:") print("โ€ข AsyncWebSocketClient with persistent connections") print("โ€ข ConnectionKeepAlive manager for advanced scenarios") print("โ€ข Background asyncio tasks for ping/reconnect") @@ -305,8 +317,8 @@ def show_keep_alive_features(): print("โ€ข Message batching and optimization") print("โ€ข Health monitoring with alerts") print() - - print("๐Ÿ“Š MONITORING CAPABILITIES:") + + print("Statistics: MONITORING CAPABILITIES:") print("โ€ข Connection uptime tracking") print("โ€ข Message send/receive counters") print("โ€ข Reconnection attempt statistics") @@ -314,8 +326,8 @@ def show_keep_alive_features(): print("โ€ข Health check results") print("โ€ข Performance metrics collection") print() - - print("๐ŸŽ›๏ธ CONFIGURATION OPTIONS:") + + print("Configuration: CONFIGURATION OPTIONS:") print("โ€ข persistent_connection: Enable advanced keep-alive") print("โ€ข auto_reconnect: Automatic reconnection on failure") print("โ€ข ping_interval: Customizable ping frequency") @@ -326,31 +338,31 @@ def show_keep_alive_features(): async def main(): """Main demo function""" - - logger.info("๐Ÿš€ Starting Enhanced PocketOption API Demo") - + + logger.info("Starting Enhanced PocketOption API Demo") + # Run comprehensive demo await demo_enhanced_features() - + # Show improvements show_api_improvements() - + # Show keep-alive features show_keep_alive_features() - + print() - print("๐ŸŽ‰ Enhanced PocketOption API Demo Complete!") + print("Enhanced PocketOption API Demo Complete!") print() - print("๐Ÿ“š Next Steps:") + print("Next Steps:") print("1. Set your real SSID: export POCKET_OPTION_SSID='your_complete_ssid'") print("2. Use persistent_connection=True for long-running applications") print("3. Monitor connection with get_connection_stats()") print("4. Add event callbacks for connection management") print("5. Use async context managers for automatic cleanup") print() - print("๐Ÿ“– Documentation: README_ASYNC.md") - print("๐Ÿงช Examples: examples/async_examples.py") - print("๐Ÿ”ง Tests: test_persistent_connection.py") + print("Documentation: README_ASYNC.md") + print("Examples: examples/async_examples.py") + print("Tests: test_persistent_connection.py") if __name__ == "__main__": diff --git a/docs/todo.md b/docs/todo.md deleted file mode 100644 index 67b5a55..0000000 --- a/docs/todo.md +++ /dev/null @@ -1,6 +0,0 @@ -# todo - -### Add login system -- Not Done - --- updated \ No newline at end of file diff --git a/enhanced_performance_report.txt b/enhanced_performance_report.txt deleted file mode 100644 index baebf5b..0000000 --- a/enhanced_performance_report.txt +++ /dev/null @@ -1,26 +0,0 @@ -================================================================================ -ENHANCED POCKETOPTION API PERFORMANCE REPORT -================================================================================ -Generated: 2025-06-10 11:47:44 - -๐Ÿ” ERROR MONITORING ----------------------------------------- -Total Errors: 20 -Error Rate: 0.83/hour - -Top Error Types: - โ€ข test_spam: 15 - โ€ข connection_test_error: 1 - โ€ข connection_timeout: 1 - โ€ข invalid_session: 1 - โ€ข order_rejected: 1 - -๐Ÿฅ HEALTH MONITORING ----------------------------------------- -Overall Status: healthy - -๐Ÿ’ก RECOMMENDATIONS ----------------------------------------- -โ€ข High error count detected - investigate error patterns - -================================================================================ \ No newline at end of file diff --git a/examples/async_examples.py b/examples/async_examples.py deleted file mode 100644 index 0812109..0000000 --- a/examples/async_examples.py +++ /dev/null @@ -1,295 +0,0 @@ -""" -Example usage of the Professional Async PocketOption API -""" - -import asyncio -import os -from datetime import datetime, timedelta -from loguru import logger - -# Configure logging -logger.add("pocketoption.log", rotation="1 day", retention="7 days", level="INFO") - -from pocketoptionapi_async import ( - AsyncPocketOptionClient, - OrderDirection, - PocketOptionError, - ConnectionError, - OrderError -) - - -async def basic_example(): - """Basic example of using the async API""" - - # Replace with your actual session ID - session_id = os.getenv("POCKET_OPTION_SSID", "your_session_id_here") - - # Create client - client = AsyncPocketOptionClient( - session_id=session_id, - is_demo=True, # Use demo account - timeout=30.0 - ) - - try: - # Connect to PocketOption - logger.info("Connecting to PocketOption...") - await client.connect() - - # Get account balance - balance = await client.get_balance() - logger.info(f"Current balance: ${balance.balance:.2f} ({balance.currency})") - - # Get historical data - logger.info("Fetching candle data...") - candles = await client.get_candles( - asset="EURUSD_otc", - timeframe="1m", - count=100 - ) - logger.info(f"Retrieved {len(candles)} candles") - - # Get data as DataFrame - df = await client.get_candles_dataframe( - asset="EURUSD_otc", - timeframe="5m", - count=50 - ) - logger.info(f"DataFrame shape: {df.shape}") - - # Place a demo order - if balance.balance > 10: # Ensure sufficient balance - logger.info("Placing demo order...") - - order_result = await client.place_order( - asset="EURUSD_otc", - amount=1.0, # $1 minimum - direction=OrderDirection.CALL, - duration=60 # 60 seconds - ) - - logger.info(f"Order placed: {order_result.order_id}") - logger.info(f"Order status: {order_result.status}") - - # Wait for order to complete (in real trading, use callbacks) - logger.info("Waiting for order to complete...") - await asyncio.sleep(65) # Wait slightly longer than duration - - # Check order result - result = await client.check_order_result(order_result.order_id) - if result: - logger.info(f"Order completed: {result.status}") - if result.profit: - logger.info(f"Profit/Loss: ${result.profit:.2f}") - - except ConnectionError as e: - logger.error(f"Connection failed: {e}") - except OrderError as e: - logger.error(f"Order failed: {e}") - except PocketOptionError as e: - logger.error(f"API error: {e}") - except Exception as e: - logger.error(f"Unexpected error: {e}") - finally: - # Always disconnect - await client.disconnect() - logger.info("Disconnected from PocketOption") - - -async def context_manager_example(): - """Example using async context manager""" - - session_id = os.getenv("POCKET_OPTION_SSID", "your_session_id_here") - - try: - # Use async context manager (automatically connects and disconnects) - async with AsyncPocketOptionClient(session_id, is_demo=True) as client: - - # Add event callbacks - def on_balance_updated(balance): - logger.info(f"Balance updated: ${balance.balance:.2f}") - - def on_order_closed(order_result): - logger.info(f"Order {order_result.order_id} closed with profit: ${order_result.profit:.2f}") - - client.add_event_callback('balance_updated', on_balance_updated) - client.add_event_callback('order_closed', on_order_closed) - - # Get balance - balance = await client.get_balance() - logger.info(f"Account balance: ${balance.balance:.2f}") - - # Get historical data for multiple assets - assets = ["EURUSD_otc", "GBPUSD_otc", "USDJPY_otc"] - - for asset in assets: - try: - candles = await client.get_candles(asset, "1m", 50) - logger.info(f"{asset}: {len(candles)} candles retrieved") - - if candles: - last_price = candles[-1].close - logger.info(f"{asset} last price: {last_price}") - - except Exception as e: - logger.error(f"Error getting candles for {asset}: {e}") - - except Exception as e: - logger.error(f"Error in context manager example: {e}") - - -async def multiple_orders_example(): - """Example of managing multiple orders""" - - session_id = os.getenv("POCKET_OPTION_SSID", "your_session_id_here") - - async with AsyncPocketOptionClient(session_id, is_demo=True) as client: - - balance = await client.get_balance() - logger.info(f"Starting balance: ${balance.balance:.2f}") - - if balance.balance < 10: - logger.warning("Insufficient balance for multiple orders") - return - - # Place multiple orders - orders = [] - assets = ["EURUSD_otc", "GBPUSD_otc"] - - for asset in assets: - try: - # Alternate between CALL and PUT - direction = OrderDirection.CALL if len(orders) % 2 == 0 else OrderDirection.PUT - - order_result = await client.place_order( - asset=asset, - amount=1.0, - direction=direction, - duration=120 # 2 minutes - ) - - orders.append(order_result) - logger.info(f"Placed {direction.value} order for {asset}: {order_result.order_id}") - - except Exception as e: - logger.error(f"Failed to place order for {asset}: {e}") - - # Monitor active orders - while True: - active_orders = await client.get_active_orders() - - if not active_orders: - logger.info("All orders completed") - break - - logger.info(f"Active orders: {len(active_orders)}") - await asyncio.sleep(5) # Check every 5 seconds - - # Check all order results - total_profit = 0 - for order in orders: - result = await client.check_order_result(order.order_id) - if result and result.profit is not None: - total_profit += result.profit - logger.info(f"Order {order.order_id}: {result.status} - Profit: ${result.profit:.2f}") - - logger.info(f"Total profit/loss: ${total_profit:.2f}") - - -async def real_time_monitoring_example(): - """Example of real-time monitoring and automated trading""" - - session_id = os.getenv("POCKET_OPTION_SSID", "your_session_id_here") - - async with AsyncPocketOptionClient(session_id, is_demo=True) as client: - - # Track price movements - asset = "EURUSD_otc" - last_price = None - price_history = [] - - # Simple trading strategy: buy when price moves significantly - async def simple_strategy(): - nonlocal last_price, price_history - - try: - # Get recent candles - candles = await client.get_candles(asset, "1m", 5) - - if not candles: - return - - current_price = candles[-1].close - price_history.append(current_price) - - # Keep only last 10 prices - if len(price_history) > 10: - price_history.pop(0) - - logger.info(f"{asset} current price: {current_price}") - - # Simple strategy: if price moved more than 0.01% in last 5 minutes - if len(price_history) >= 5: - price_change = ((current_price - price_history[0]) / price_history[0]) * 100 - - logger.info(f"Price change: {price_change:.3f}%") - - # Only trade if change is significant and we have sufficient balance - if abs(price_change) > 0.01: # 0.01% threshold - balance = await client.get_balance() - - if balance.balance > 5: - direction = OrderDirection.CALL if price_change > 0 else OrderDirection.PUT - - try: - order = await client.place_order( - asset=asset, - amount=1.0, - direction=direction, - duration=60 - ) - - logger.info(f"Strategy triggered: {direction.value} order placed: {order.order_id}") - - except Exception as e: - logger.error(f"Failed to place strategy order: {e}") - - last_price = current_price - - except Exception as e: - logger.error(f"Error in strategy: {e}") - - # Run strategy every 30 seconds for 5 minutes - logger.info("Starting real-time monitoring...") - - for i in range(10): # 10 iterations = 5 minutes - await simple_strategy() - await asyncio.sleep(30) - - logger.info("Real-time monitoring completed") - - -if __name__ == "__main__": - # Set your session ID in environment variable or directly here - # export POCKET_OPTION_SSID="your_actual_session_id" - - print("Choose an example to run:") - print("1. Basic example") - print("2. Context manager example") - print("3. Multiple orders example") - print("4. Real-time monitoring example") - - choice = input("Enter choice (1-4): ").strip() - - if choice == "1": - asyncio.run(basic_example()) - elif choice == "2": - asyncio.run(context_manager_example()) - elif choice == "3": - asyncio.run(multiple_orders_example()) - elif choice == "4": - asyncio.run(real_time_monitoring_example()) - else: - print("Invalid choice. Running basic example...") - asyncio.run(basic_example()) diff --git a/load_testing_tool.py b/load_testing_tool.py index 9dd8d76..385213f 100644 --- a/load_testing_tool.py +++ b/load_testing_tool.py @@ -1,16 +1,13 @@ -#!/usr/bin/env python3 """ Load Testing and Stress Testing Tool for PocketOption Async API """ import asyncio -import time import random import json -import concurrent.futures -from datetime import datetime, timedelta -from typing import List, Dict, Any, Optional, Tuple -from dataclasses import dataclass, asdict +from datetime import datetime +from typing import List, Dict, Any, Optional +from dataclasses import dataclass from collections import defaultdict, deque import statistics from loguru import logger @@ -23,6 +20,7 @@ @dataclass class LoadTestResult: """Result of a load test operation""" + operation_type: str start_time: datetime end_time: datetime @@ -35,6 +33,7 @@ class LoadTestResult: @dataclass class LoadTestConfig: """Load test configuration""" + concurrent_clients: int = 5 operations_per_client: int = 20 operation_delay: float = 1.0 @@ -46,47 +45,49 @@ class LoadTestConfig: class LoadTester: """Advanced load testing framework""" - + def __init__(self, ssid: str, is_demo: bool = True): self.ssid = ssid self.is_demo = is_demo - + # Test state self.test_results: List[LoadTestResult] = [] self.active_clients: List[AsyncPocketOptionClient] = [] self.test_start_time: Optional[datetime] = None self.test_end_time: Optional[datetime] = None - + # Statistics self.operation_stats: Dict[str, List[float]] = defaultdict(list) self.error_counts: Dict[str, int] = defaultdict(int) self.success_counts: Dict[str, int] = defaultdict(int) - + # Real-time monitoring self.operations_per_second: deque = deque(maxlen=60) # Last 60 seconds self.current_operations = 0 self.peak_operations_per_second = 0 - + async def run_load_test(self, config: LoadTestConfig) -> Dict[str, Any]: """Run comprehensive load test""" - logger.info(f"๐Ÿš€ Starting Load Test") - logger.info(f"Config: {config.concurrent_clients} clients, {config.operations_per_client} ops/client") - + logger.info("Starting Load Test") + logger.info( + f"Config: {config.concurrent_clients} clients, {config.operations_per_client} ops/client" + ) + self.test_start_time = datetime.now() - + try: if config.stress_mode: return await self._run_stress_test(config) else: return await self._run_standard_load_test(config) - + finally: self.test_end_time = datetime.now() await self._cleanup_clients() - + async def _run_standard_load_test(self, config: LoadTestConfig) -> Dict[str, Any]: """Run standard load test with concurrent clients""" - + # Create client tasks client_tasks = [] for i in range(config.concurrent_clients): @@ -96,17 +97,17 @@ async def _run_standard_load_test(self, config: LoadTestConfig) -> Dict[str, Any operations_count=config.operations_per_client, delay=config.operation_delay, persistent=config.use_persistent_connection, - include_trading=config.include_trading_operations + include_trading=config.include_trading_operations, ) ) client_tasks.append(task) - + # Start monitoring task monitor_task = asyncio.create_task(self._monitor_operations()) - + # Run all client tasks - logger.info(f"๐Ÿ”„ Running {len(client_tasks)} concurrent clients...") - + logger.info(f"Persistent: Running {len(client_tasks)} concurrent clients...") + try: await asyncio.gather(*client_tasks, return_exceptions=True) finally: @@ -115,24 +116,26 @@ async def _run_standard_load_test(self, config: LoadTestConfig) -> Dict[str, Any await monitor_task except asyncio.CancelledError: pass - + return self._generate_load_test_report() - + async def _run_stress_test(self, config: LoadTestConfig) -> Dict[str, Any]: """Run stress test with extreme conditions""" - logger.info("๐Ÿ’ฅ Running STRESS TEST mode!") - + logger.info("Error: Running STRESS TEST mode!") + # Stress test phases phases = [ ("Ramp Up", config.concurrent_clients // 3, 0.1), ("Peak Load", config.concurrent_clients, 0.05), ("Extreme Load", config.concurrent_clients * 2, 0.01), - ("Cool Down", config.concurrent_clients // 2, 0.5) + ("Cool Down", config.concurrent_clients // 2, 0.5), ] - + for phase_name, clients, delay in phases: - logger.info(f"๐Ÿ”ฅ Stress Phase: {phase_name} ({clients} clients, {delay}s delay)") - + logger.info( + f"Stress: Stress Phase: {phase_name} ({clients} clients, {delay}s delay)" + ) + # Create tasks for this phase phase_tasks = [] for i in range(clients): @@ -141,310 +144,346 @@ async def _run_stress_test(self, config: LoadTestConfig) -> Dict[str, Any]: client_id=f"{phase_name}_{i}", operations_count=config.operations_per_client // 2, delay=delay, - persistent=config.use_persistent_connection + persistent=config.use_persistent_connection, ) ) phase_tasks.append(task) - + # Run phase try: await asyncio.wait_for( asyncio.gather(*phase_tasks, return_exceptions=True), - timeout=60 # 1 minute per phase + timeout=60, # 1 minute per phase ) except asyncio.TimeoutError: - logger.warning(f"โฐ Stress phase {phase_name} timed out") + logger.warning(f"Long-running: Stress phase {phase_name} timed out") # Cancel remaining tasks for task in phase_tasks: if not task.done(): task.cancel() - + # Brief pause between phases await asyncio.sleep(5) - + return self._generate_load_test_report() - - async def _run_client_operations(self, client_id: int, operations_count: int, - delay: float, persistent: bool, include_trading: bool) -> None: + + async def _run_client_operations( + self, + client_id: int, + operations_count: int, + delay: float, + persistent: bool, + include_trading: bool, + ) -> None: """Run operations for a single client""" client = None - + try: # Create and connect client client = AsyncPocketOptionClient( self.ssid, is_demo=self.is_demo, persistent_connection=persistent, - auto_reconnect=True + auto_reconnect=True, ) - + self.active_clients.append(client) - + # Connect connect_start = datetime.now() success = await client.connect() connect_end = datetime.now() - + if success: - self._record_result(LoadTestResult( - operation_type="connect", - start_time=connect_start, - end_time=connect_end, - duration=(connect_end - connect_start).total_seconds(), - success=True, - response_data={"client_id": client_id} - )) - - logger.info(f"โœ… Client {client_id} connected") + self._record_result( + LoadTestResult( + operation_type="connect", + start_time=connect_start, + end_time=connect_end, + duration=(connect_end - connect_start).total_seconds(), + success=True, + response_data={"client_id": client_id}, + ) + ) + + logger.info(f"Success: Client {client_id} connected") else: - self._record_result(LoadTestResult( - operation_type="connect", - start_time=connect_start, - end_time=connect_end, - duration=(connect_end - connect_start).total_seconds(), - success=False, - error_message="Connection failed" - )) + self._record_result( + LoadTestResult( + operation_type="connect", + start_time=connect_start, + end_time=connect_end, + duration=(connect_end - connect_start).total_seconds(), + success=False, + error_message="Connection failed", + ) + ) return - + # Run operations for op_num in range(operations_count): try: # Choose operation type operation_type = self._choose_operation_type(include_trading) - + # Execute operation await self._execute_operation(client, client_id, operation_type) - + # Delay between operations if delay > 0: await asyncio.sleep(delay) - + except Exception as e: - logger.error(f"โŒ Client {client_id} operation {op_num} failed: {e}") - self._record_result(LoadTestResult( - operation_type="unknown", - start_time=datetime.now(), - end_time=datetime.now(), - duration=0, - success=False, - error_message=str(e) - )) - + logger.error( + f"Error: Client {client_id} operation {op_num} failed: {e}" + ) + self._record_result( + LoadTestResult( + operation_type="unknown", + start_time=datetime.now(), + end_time=datetime.now(), + duration=0, + success=False, + error_message=str(e), + ) + ) + except Exception as e: - logger.error(f"โŒ Client {client_id} failed: {e}") - + logger.error(f"Error: Client {client_id} failed: {e}") + finally: if client: try: await client.disconnect() except: pass - - async def _run_stress_client(self, client_id: str, operations_count: int, delay: float, persistent: bool) -> None: + + async def _run_stress_client( + self, client_id: str, operations_count: int, delay: float, persistent: bool + ) -> None: """Run stress operations for a single client""" - + # Create keep-alive manager for stress testing keep_alive = None - + try: keep_alive = ConnectionKeepAlive(self.ssid, is_demo=self.is_demo) - + # Connect connect_start = datetime.now() success = await keep_alive.start_persistent_connection() connect_end = datetime.now() - + if not success: - self._record_result(LoadTestResult( - operation_type="stress_connect", - start_time=connect_start, - end_time=connect_end, - duration=(connect_end - connect_start).total_seconds(), - success=False, - error_message="Stress connection failed" - )) + self._record_result( + LoadTestResult( + operation_type="stress_connect", + start_time=connect_start, + end_time=connect_end, + duration=(connect_end - connect_start).total_seconds(), + success=False, + error_message="Stress connection failed", + ) + ) return - + # Rapid-fire operations for op_num in range(operations_count): try: op_start = datetime.now() - + # Send multiple messages rapidly for _ in range(3): await keep_alive.send_message('42["ps"]') await asyncio.sleep(0.01) # 10ms between messages - + op_end = datetime.now() - - self._record_result(LoadTestResult( - operation_type="stress_rapid_ping", - start_time=op_start, - end_time=op_end, - duration=(op_end - op_start).total_seconds(), - success=True, - response_data={"client_id": client_id, "messages": 3} - )) - + + self._record_result( + LoadTestResult( + operation_type="stress_rapid_ping", + start_time=op_start, + end_time=op_end, + duration=(op_end - op_start).total_seconds(), + success=True, + response_data={"client_id": client_id, "messages": 3}, + ) + ) + if delay > 0: await asyncio.sleep(delay) - + except Exception as e: - logger.error(f"โŒ Stress client {client_id} operation {op_num} failed: {e}") - + logger.error( + f"Error: Stress client {client_id} operation {op_num} failed: {e}" + ) + except Exception as e: - logger.error(f"โŒ Stress client {client_id} failed: {e}") - + logger.error(f"Error: Stress client {client_id} failed: {e}") + finally: if keep_alive: try: await keep_alive.stop_persistent_connection() except: pass - + def _choose_operation_type(self, include_trading: bool) -> str: """Choose random operation type""" basic_operations = ["balance", "candles", "ping", "market_data"] - + if include_trading: trading_operations = ["place_order", "check_order", "get_orders"] basic_operations.extend(trading_operations) - + return random.choice(basic_operations) - - async def _execute_operation(self, client: AsyncPocketOptionClient, client_id: int, operation_type: str) -> None: + + async def _execute_operation( + self, client: AsyncPocketOptionClient, client_id: int, operation_type: str + ) -> None: """Execute a specific operation and record results""" start_time = datetime.now() - + try: if operation_type == "balance": balance = await client.get_balance() result_data = {"balance": balance.balance if balance else None} - + elif operation_type == "candles": asset = random.choice(["EURUSD", "GBPUSD", "USDJPY", "AUDUSD"]) timeframe = random.choice([TimeFrame.M1, TimeFrame.M5, TimeFrame.M15]) - candles = await client.get_candles(asset, timeframe, random.randint(10, 50)) + candles = await client.get_candles( + asset, timeframe, random.randint(10, 50) + ) result_data = {"asset": asset, "candles_count": len(candles)} - + elif operation_type == "ping": await client.send_message('42["ps"]') result_data = {"message": "ping"} - + elif operation_type == "market_data": # Simulate getting multiple market data for asset in ["EURUSD", "GBPUSD"]: await client.get_candles(asset, TimeFrame.M1, 5) result_data = {"assets": 2} - + elif operation_type == "place_order": # Simulate order (don't actually place in demo) asset = random.choice(["EURUSD", "GBPUSD"]) amount = random.uniform(1, 10) direction = random.choice([OrderDirection.CALL, OrderDirection.PUT]) - result_data = {"asset": asset, "amount": amount, "direction": direction.value} - + result_data = { + "asset": asset, + "amount": amount, + "direction": direction.value, + } + elif operation_type == "check_order": # Simulate order check await asyncio.sleep(0.1) # Simulate API call result_data = {"orders": 0} - + elif operation_type == "get_orders": # Simulate getting orders await asyncio.sleep(0.1) # Simulate API call result_data = {"active_orders": 0} - + else: result_data = {} - + end_time = datetime.now() - - self._record_result(LoadTestResult( - operation_type=operation_type, - start_time=start_time, - end_time=end_time, - duration=(end_time - start_time).total_seconds(), - success=True, - response_data=result_data - )) - + + self._record_result( + LoadTestResult( + operation_type=operation_type, + start_time=start_time, + end_time=end_time, + duration=(end_time - start_time).total_seconds(), + success=True, + response_data=result_data, + ) + ) + self.current_operations += 1 - + except Exception as e: end_time = datetime.now() - - self._record_result(LoadTestResult( - operation_type=operation_type, - start_time=start_time, - end_time=end_time, - duration=(end_time - start_time).total_seconds(), - success=False, - error_message=str(e) - )) - + + self._record_result( + LoadTestResult( + operation_type=operation_type, + start_time=start_time, + end_time=end_time, + duration=(end_time - start_time).total_seconds(), + success=False, + error_message=str(e), + ) + ) + async def _monitor_operations(self): """Monitor operations per second""" while True: try: await asyncio.sleep(1) - + # Record operations per second ops_this_second = self.current_operations self.operations_per_second.append(ops_this_second) - + # Update peak if ops_this_second > self.peak_operations_per_second: self.peak_operations_per_second = ops_this_second - + # Reset counter self.current_operations = 0 - + # Log every 10 seconds if len(self.operations_per_second) % 10 == 0: avg_ops = statistics.mean(list(self.operations_per_second)[-10:]) - logger.info(f"๐Ÿ“Š Avg ops/sec (last 10s): {avg_ops:.1f}, Peak: {self.peak_operations_per_second}") - + logger.info( + f"Statistics: Avg ops/sec (last 10s): {avg_ops:.1f}, Peak: {self.peak_operations_per_second}" + ) + except Exception as e: logger.error(f"Monitor error: {e}") - + def _record_result(self, result: LoadTestResult): """Record test result""" self.test_results.append(result) - + # Update statistics if result.success: self.success_counts[result.operation_type] += 1 self.operation_stats[result.operation_type].append(result.duration) else: self.error_counts[result.operation_type] += 1 - + async def _cleanup_clients(self): """Clean up all active clients""" - logger.info("๐Ÿงน Cleaning up clients...") - + logger.info("Cleaning up clients...") + cleanup_tasks = [] for client in self.active_clients: if client.is_connected: cleanup_tasks.append(asyncio.create_task(client.disconnect())) - + if cleanup_tasks: await asyncio.gather(*cleanup_tasks, return_exceptions=True) - + self.active_clients.clear() - logger.info("โœ… Cleanup completed") - + logger.info("Success: Cleanup completed") + def _generate_load_test_report(self) -> Dict[str, Any]: """Generate comprehensive load test report""" - + if not self.test_start_time or not self.test_end_time: return {"error": "Test timing not available"} - + total_duration = (self.test_end_time - self.test_start_time).total_seconds() total_operations = len(self.test_results) successful_operations = sum(1 for r in self.test_results if r.success) failed_operations = total_operations - successful_operations - + # Calculate operation statistics operation_analysis = {} for op_type, durations in self.operation_stats.items(): @@ -453,24 +492,33 @@ def _generate_load_test_report(self) -> Dict[str, Any]: "count": len(durations), "success_count": self.success_counts[op_type], "error_count": self.error_counts[op_type], - "success_rate": self.success_counts[op_type] / (self.success_counts[op_type] + self.error_counts[op_type]), + "success_rate": self.success_counts[op_type] + / (self.success_counts[op_type] + self.error_counts[op_type]), "avg_duration": statistics.mean(durations), "min_duration": min(durations), "max_duration": max(durations), "median_duration": statistics.median(durations), - "p95_duration": sorted(durations)[int(len(durations) * 0.95)] if len(durations) > 20 else max(durations) + "p95_duration": sorted(durations)[int(len(durations) * 0.95)] + if len(durations) > 20 + else max(durations), } - + # Performance metrics - avg_ops_per_second = total_operations / total_duration if total_duration > 0 else 0 - + avg_ops_per_second = ( + total_operations / total_duration if total_duration > 0 else 0 + ) + # Error analysis error_summary = {} for result in self.test_results: if not result.success and result.error_message: - error_type = result.error_message.split(':')[0] if ':' in result.error_message else result.error_message + error_type = ( + result.error_message.split(":")[0] + if ":" in result.error_message + else result.error_message + ) error_summary[error_type] = error_summary.get(error_type, 0) + 1 - + report = { "test_summary": { "start_time": self.test_start_time.isoformat(), @@ -479,68 +527,88 @@ def _generate_load_test_report(self) -> Dict[str, Any]: "total_operations": total_operations, "successful_operations": successful_operations, "failed_operations": failed_operations, - "success_rate": successful_operations / total_operations if total_operations > 0 else 0, + "success_rate": successful_operations / total_operations + if total_operations > 0 + else 0, "avg_operations_per_second": avg_ops_per_second, - "peak_operations_per_second": self.peak_operations_per_second + "peak_operations_per_second": self.peak_operations_per_second, }, "operation_analysis": operation_analysis, "error_summary": error_summary, "performance_metrics": { "operations_per_second_history": list(self.operations_per_second), "peak_throughput": self.peak_operations_per_second, - "avg_throughput": avg_ops_per_second + "avg_throughput": avg_ops_per_second, }, - "recommendations": self._generate_recommendations(operation_analysis, avg_ops_per_second, successful_operations / total_operations if total_operations > 0 else 0) + "recommendations": self._generate_recommendations( + operation_analysis, + avg_ops_per_second, + successful_operations / total_operations if total_operations > 0 else 0, + ), } - + return report - - def _generate_recommendations(self, operation_analysis: Dict, avg_throughput: float, success_rate: float) -> List[str]: + + def _generate_recommendations( + self, operation_analysis: Dict, avg_throughput: float, success_rate: float + ) -> List[str]: """Generate performance recommendations""" recommendations = [] - + if success_rate < 0.95: - recommendations.append(f"Low success rate ({success_rate:.1%}). Check network stability and API limits.") - + recommendations.append( + f"Low success rate ({success_rate:.1%}). Check network stability and API limits." + ) + if avg_throughput < 1: - recommendations.append("Low throughput detected. Consider using persistent connections.") - + recommendations.append( + "Low throughput detected. Consider using persistent connections." + ) + # Check slow operations slow_operations = [] for op_type, stats in operation_analysis.items(): if stats["avg_duration"] > 2.0: slow_operations.append(f"{op_type} ({stats['avg_duration']:.2f}s avg)") - + if slow_operations: - recommendations.append(f"Slow operations detected: {', '.join(slow_operations)}") - + recommendations.append( + f"Slow operations detected: {', '.join(slow_operations)}" + ) + # Check high error rate operations error_operations = [] for op_type, stats in operation_analysis.items(): if stats["success_rate"] < 0.9: - error_operations.append(f"{op_type} ({stats['success_rate']:.1%} success)") - + error_operations.append( + f"{op_type} ({stats['success_rate']:.1%} success)" + ) + if error_operations: - recommendations.append(f"High error rate operations: {', '.join(error_operations)}") - + recommendations.append( + f"High error rate operations: {', '.join(error_operations)}" + ) + if not recommendations: - recommendations.append("System performance is good. No major issues detected.") - + recommendations.append( + "System performance is good. No major issues detected." + ) + return recommendations async def run_load_test_demo(ssid: str = None): """Run load testing demonstration""" - + if not ssid: ssid = r'42["auth",{"session":"demo_session_for_load_test","isDemo":1,"uid":0,"platform":1}]' - logger.warning("โš ๏ธ Using demo SSID for load testing") - - logger.info("๐Ÿš€ Starting Load Testing Demo") - + logger.warning("Caution: Using demo SSID for load testing") + + logger.info("Starting Load Testing Demo") + # Create load tester load_tester = LoadTester(ssid, is_demo=True) - + # Test configurations test_configs = [ LoadTestConfig( @@ -548,110 +616,118 @@ async def run_load_test_demo(ssid: str = None): operations_per_client=10, operation_delay=0.5, use_persistent_connection=False, - stress_mode=False + stress_mode=False, ), LoadTestConfig( concurrent_clients=5, operations_per_client=15, operation_delay=0.2, use_persistent_connection=True, - stress_mode=False + stress_mode=False, ), LoadTestConfig( concurrent_clients=2, operations_per_client=5, operation_delay=0.1, use_persistent_connection=True, - stress_mode=True - ) + stress_mode=True, + ), ] - + all_reports = [] - + for i, config in enumerate(test_configs, 1): - logger.info(f"\n๐Ÿงช Running Load Test {i}/{len(test_configs)}") + logger.info(f"\nTesting: Running Load Test {i}/{len(test_configs)}") logger.info(f"Configuration: {config}") - + try: report = await load_tester.run_load_test(config) all_reports.append(report) - + # Print summary summary = report["test_summary"] - logger.info(f"โœ… Test {i} completed:") + logger.info(f"Success: Test {i} completed:") logger.info(f" Duration: {summary['total_duration']:.2f}s") logger.info(f" Operations: {summary['total_operations']}") logger.info(f" Success Rate: {summary['success_rate']:.1%}") - logger.info(f" Throughput: {summary['avg_operations_per_second']:.1f} ops/sec") - + logger.info( + f" Throughput: {summary['avg_operations_per_second']:.1f} ops/sec" + ) + # Brief pause between tests await asyncio.sleep(5) - + except Exception as e: - logger.error(f"โŒ Load test {i} failed: {e}") - + logger.error(f"Error: Load test {i} failed: {e}") + # Generate comparison report if all_reports: comparison_report = { "test_comparison": [], "best_performance": {}, - "overall_recommendations": [] + "overall_recommendations": [], } - + best_throughput = 0 best_success_rate = 0 - + for i, report in enumerate(all_reports, 1): summary = report["test_summary"] - comparison_report["test_comparison"].append({ - "test_number": i, - "throughput": summary["avg_operations_per_second"], - "success_rate": summary["success_rate"], - "total_operations": summary["total_operations"], - "duration": summary["total_duration"] - }) - + comparison_report["test_comparison"].append( + { + "test_number": i, + "throughput": summary["avg_operations_per_second"], + "success_rate": summary["success_rate"], + "total_operations": summary["total_operations"], + "duration": summary["total_duration"], + } + ) + if summary["avg_operations_per_second"] > best_throughput: best_throughput = summary["avg_operations_per_second"] comparison_report["best_performance"]["throughput"] = f"Test {i}" - + if summary["success_rate"] > best_success_rate: best_success_rate = summary["success_rate"] comparison_report["best_performance"]["reliability"] = f"Test {i}" - + # Save reports - timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') - + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + for i, report in enumerate(all_reports, 1): report_file = f"load_test_{i}_{timestamp}.json" - with open(report_file, 'w') as f: + with open(report_file, "w") as f: json.dump(report, f, indent=2, default=str) - logger.info(f"๐Ÿ“„ Test {i} report saved to: {report_file}") - + logger.info(f"Report: Test {i} report saved to: {report_file}") + comparison_file = f"load_test_comparison_{timestamp}.json" - with open(comparison_file, 'w') as f: + with open(comparison_file, "w") as f: json.dump(comparison_report, f, indent=2, default=str) - - logger.info(f"๐Ÿ“Š Comparison report saved to: {comparison_file}") - + + logger.info(f"Statistics: Comparison report saved to: {comparison_file}") + # Final summary - logger.info("\n๐Ÿ LOAD TESTING SUMMARY") + logger.info("\nCompleted: LOAD TESTING SUMMARY") logger.info("=" * 50) - logger.info(f"Best Throughput: {comparison_report['best_performance'].get('throughput', 'N/A')}") - logger.info(f"Best Reliability: {comparison_report['best_performance'].get('reliability', 'N/A')}") - + logger.info( + f"Best Throughput: {comparison_report['best_performance'].get('throughput', 'N/A')}" + ) + logger.info( + f"Best Reliability: {comparison_report['best_performance'].get('reliability', 'N/A')}" + ) + return all_reports - + return [] if __name__ == "__main__": import sys - + # Allow passing SSID as command line argument ssid = None if len(sys.argv) > 1: ssid = sys.argv[1] logger.info(f"Using provided SSID: {ssid[:50]}...") - + asyncio.run(run_load_test_demo(ssid)) diff --git a/migration_guide.py b/migration_guide.py deleted file mode 100644 index be85ad6..0000000 --- a/migration_guide.py +++ /dev/null @@ -1,413 +0,0 @@ -""" -Migration script to convert old PocketOption API usage to new async API -""" - -import asyncio -import time -from datetime import datetime -from typing import Optional - -# Import the old API for comparison -try: - from pocketoptionapi.stable_api import PocketOption as OldPocketOption -except ImportError: - print("Old API not found, continuing with migration guide only") - OldPocketOption = None - -# Import the new async API -from pocketoptionapi_async import ( - AsyncPocketOptionClient, - OrderDirection, - OrderStatus, - PocketOptionError -) - - -class PocketOptionMigrationWrapper: - """ - Wrapper class to help migrate from old sync API to new async API - Provides similar interface to old API but with async implementation - """ - - def __init__(self, ssid: str, demo: bool = True): - """ - Initialize migration wrapper - - Args: - ssid: Session ID - demo: Whether to use demo account - """ - self.ssid = ssid - self.demo = demo - self.client = AsyncPocketOptionClient( - session_id=ssid, - is_demo=demo - ) - self._connected = False - - async def connect(self) -> bool: - """Connect to PocketOption (async version of old connect())""" - try: - await self.client.connect() - self._connected = True - print("โœ… Connected successfully") - return True - except Exception as e: - print(f"โŒ Connection failed: {e}") - return False - - async def disconnect(self): - """Disconnect from PocketOption""" - if self._connected: - await self.client.disconnect() - self._connected = False - print("โœ… Disconnected successfully") - - def check_connect(self) -> bool: - """Check if connected (similar to old API)""" - return self.client.is_connected - - async def get_balance(self) -> Optional[float]: - """Get balance (async version)""" - try: - balance = await self.client.get_balance() - return balance.balance - except Exception as e: - print(f"โŒ Failed to get balance: {e}") - return None - - async def buy(self, amount: float, active: str, action: str, - expirations: int) -> tuple[bool, Optional[str]]: - """ - Place order (async version of old buy method) - - Returns: - tuple: (success, order_id) - """ - try: - # Convert action to OrderDirection - direction = OrderDirection.CALL if action.lower() == "call" else OrderDirection.PUT - - # Place order - result = await self.client.place_order( - asset=active, - amount=amount, - direction=direction, - duration=expirations - ) - - return True, result.order_id - - except Exception as e: - print(f"โŒ Order failed: {e}") - return False, None - - async def check_win(self, order_id: str) -> tuple[Optional[float], str]: - """ - Check order result (async version) - - Returns: - tuple: (profit, status) - """ - try: - # Wait for order completion - max_wait = 300 # 5 minutes - wait_time = 0 - - while wait_time < max_wait: - result = await self.client.check_order_result(order_id) - - if result and result.status in [OrderStatus.WIN, OrderStatus.LOSE]: - status = "win" if result.status == OrderStatus.WIN else "lose" - return result.profit, status - - await asyncio.sleep(1) - wait_time += 1 - - return None, "timeout" - - except Exception as e: - print(f"โŒ Failed to check order result: {e}") - return None, "error" - - async def get_candles(self, active: str, period: int, count: int = 100) -> list: - """ - Get candles data (async version) - - Args: - active: Asset symbol - period: Period in seconds - count: Number of candles - - Returns: - list: Candle data - """ - try: - candles = await self.client.get_candles( - asset=active, - timeframe=period, - count=count - ) - - # Convert to old format for compatibility - result = [] - for candle in candles: - result.append({ - 'time': int(candle.timestamp.timestamp()), - 'open': candle.open, - 'high': candle.high, - 'low': candle.low, - 'close': candle.close, - 'asset': candle.asset - }) - - return result - - except Exception as e: - print(f"โŒ Failed to get candles: {e}") - return [] - - # Context manager support - async def __aenter__(self): - await self.connect() - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb): - await self.disconnect() - - -async def migration_example_old_style(): - """ - Example showing how to migrate old-style code to new async API - This maintains similar interface to old API - """ - - ssid = "your_session_id_here" - - # Old style with new async implementation - async with PocketOptionMigrationWrapper(ssid, demo=True) as api: - - print("=== Old-Style API with Async Implementation ===") - - # Check connection (similar to old API) - if api.check_connect(): - print("โœ… Connected successfully") - - # Get balance (similar to old API) - balance = await api.get_balance() - if balance: - print(f"๐Ÿ’ฐ Balance: ${balance:.2f}") - - # Place order (similar to old API) - success, order_id = await api.buy( - amount=1.0, - active="EURUSD_otc", - action="call", - expirations=60 - ) - - if success and order_id: - print(f"๐Ÿ“ˆ Order placed: {order_id}") - - # Check result (similar to old API) - profit, status = await api.check_win(order_id) - if profit is not None: - print(f"๐Ÿ’ฐ Result: {status} - Profit: ${profit:.2f}") - - # Get candles (similar to old API) - candles = await api.get_candles("EURUSD_otc", 60, 10) - if candles: - print(f"๐Ÿ“Š Retrieved {len(candles)} candles") - print(f" Last price: {candles[-1]['close']}") - - -async def migration_example_new_style(): - """ - Example showing the recommended new async API usage - """ - - ssid = "your_session_id_here" - - print("\n=== New Modern Async API ===") - - # Modern async approach - async with AsyncPocketOptionClient(ssid, is_demo=True) as client: - - # Add event callbacks for real-time updates - def on_balance_updated(balance): - print(f"๐Ÿ’ฐ Balance updated: ${balance.balance:.2f}") - - def on_order_closed(order_result): - status = "WIN" if order_result.status == OrderStatus.WIN else "LOSE" - print(f"๐Ÿ“Š Order {order_result.order_id}: {status} - Profit: ${order_result.profit:.2f}") - - client.add_event_callback('balance_updated', on_balance_updated) - client.add_event_callback('order_closed', on_order_closed) - - # Get balance with proper error handling - try: - balance = await client.get_balance() - print(f"๐Ÿ’ฐ Current balance: ${balance.balance:.2f}") - - # Get candles with DataFrame support - df = await client.get_candles_dataframe( - asset="EURUSD_otc", - timeframe="1m", - count=50 - ) - print(f"๐Ÿ“Š Retrieved DataFrame with {len(df)} rows") - - # Place order with modern error handling - order_result = await client.place_order( - asset="EURUSD_otc", - amount=1.0, - direction=OrderDirection.CALL, - duration=60 - ) - - print(f"๐Ÿ“ˆ Order placed: {order_result.order_id}") - print(f" Status: {order_result.status}") - print(f" Expires at: {order_result.expires_at}") - - except PocketOptionError as e: - print(f"โŒ API Error: {e.message}") - except Exception as e: - print(f"โŒ Unexpected error: {e}") - - -async def side_by_side_comparison(): - """Show side-by-side comparison of old vs new API""" - - print("\n" + "="*60) - print("MIGRATION COMPARISON") - print("="*60) - - print("\n๐Ÿ“ OLD SYNCHRONOUS API:") - print(""" - from pocketoptionapi.stable_api import PocketOption - - # Initialize - api = PocketOption(ssid, demo=True) - api.connect() - - # Get balance - balance = api.get_balance() - - # Place order - success, order_id = api.buy(1.0, "EURUSD_otc", "call", 60) - - # Check result - profit, status = api.check_win(order_id) - - # Get candles - candles = api.get_candles("EURUSD_otc", 60, 100) - - # Cleanup - api.disconnect() - """) - - print("\n๐Ÿš€ NEW ASYNC API:") - print(""" - from pocketoptionapi_async import AsyncPocketOptionClient, OrderDirection - - async def main(): - # Context manager handles connection/cleanup - async with AsyncPocketOptionClient(ssid, is_demo=True) as client: - - # Get balance - balance = await client.get_balance() - - # Place order - order_result = await client.place_order( - asset="EURUSD_otc", - amount=1.0, - direction=OrderDirection.CALL, - duration=60 - ) - - # Get candles (with DataFrame support) - df = await client.get_candles_dataframe( - asset="EURUSD_otc", - timeframe="1m", - count=100 - ) - - # Event-driven order monitoring - def on_order_closed(result): - print(f"Order closed: {result.profit}") - - client.add_event_callback('order_closed', on_order_closed) - - asyncio.run(main()) - """) - - print("\nโœ… KEY IMPROVEMENTS:") - print("- 100% async/await support") - print("- Type safety with Pydantic models") - print("- Automatic connection management") - print("- Event-driven architecture") - print("- pandas DataFrame integration") - print("- Professional error handling") - print("- Built-in rate limiting") - print("- Comprehensive testing") - - -def print_migration_checklist(): - """Print migration checklist""" - - print("\n" + "="*60) - print("MIGRATION CHECKLIST") - print("="*60) - - checklist = [ - "โœ… Install new dependencies (pip install -r requirements.txt)", - "โœ… Update imports to use pocketoptionapi_async", - "โœ… Convert functions to async/await", - "โœ… Use context managers for connection management", - "โœ… Replace string directions with OrderDirection enum", - "โœ… Update error handling to use custom exceptions", - "โœ… Consider using event callbacks for real-time updates", - "โœ… Migrate to DataFrame for candle data analysis", - "โœ… Update test cases for async functionality", - "โœ… Review and update logging configuration" - ] - - for item in checklist: - print(f" {item}") - - print("\n๐Ÿ’ก TIPS:") - print("- Start by wrapping existing code with migration wrapper") - print("- Gradually refactor to use modern async patterns") - print("- Use type hints for better code quality") - print("- Implement proper error handling strategies") - print("- Test thoroughly with demo account first") - - -async def main(): - """Main migration demonstration""" - - print("๐Ÿ”„ POCKETOPTION API MIGRATION GUIDE") - print("="*50) - - # Show side-by-side comparison - side_by_side_comparison() - - # Print migration checklist - print_migration_checklist() - - print("\n๐Ÿš€ RUNNING MIGRATION EXAMPLES...") - - # Note: Replace with actual session ID for testing - print("\nโš ๏ธ To run live examples, set your session ID in the code") - print(" Examples will show structure without making actual API calls") - - # You can uncomment these to test with real session ID - # await migration_example_old_style() - # await migration_example_new_style() - - print("\nโœ… Migration guide completed!") - print("๐Ÿ“– Check README_ASYNC.md for comprehensive documentation") - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/performance_report.txt b/performance_report.txt deleted file mode 100644 index 4498deb..0000000 --- a/performance_report.txt +++ /dev/null @@ -1,19 +0,0 @@ -============================================================ -POCKETOPTION ASYNC API PERFORMANCE REPORT -============================================================ - -๐Ÿ“ก CONNECTION PERFORMANCE ------------------------------- -โŒ Connection tests failed - -๐Ÿ“Š DATA RETRIEVAL PERFORMANCE ------------------------------------ -โŒ Data retrieval test error: Failed to connect: WebSocket is not connected - -โšก CONCURRENT OPERATIONS -------------------------- -โŒ Concurrent operations failed - -============================================================ -Report generated at: 2025-06-10 11:51:18 -============================================================ \ No newline at end of file diff --git a/performance_tests.py b/performance_tests.py index 16f9873..b529c14 100644 --- a/performance_tests.py +++ b/performance_tests.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Performance Tests for PocketOption Async API """ @@ -7,308 +6,340 @@ import time import statistics from typing import List, Dict, Any -import pandas as pd from loguru import logger from pocketoptionapi_async import AsyncPocketOptionClient, OrderDirection + class PerformanceTester: """Performance testing utilities for the async API""" - + def __init__(self, session_id: str, is_demo: bool = True): self.session_id = session_id self.is_demo = is_demo self.results: Dict[str, List[float]] = {} - - async def test_connection_performance(self, iterations: int = 5) -> Dict[str, float]: + + async def test_connection_performance( + self, iterations: int = 5 + ) -> Dict[str, float]: """Test connection establishment performance""" logger.info(f"Testing connection performance ({iterations} iterations)") - + connection_times = [] - + for i in range(iterations): start_time = time.time() - + client = AsyncPocketOptionClient( - session_id=self.session_id, - is_demo=self.is_demo + session_id=self.session_id, is_demo=self.is_demo ) - + try: await client.connect() if client.is_connected: connection_time = time.time() - start_time connection_times.append(connection_time) - logger.success(f"Connection {i+1}: {connection_time:.3f}s") + logger.success(f"Connection {i + 1}: {connection_time:.3f}s") else: - logger.warning(f"Connection {i+1}: Failed") - + logger.warning(f"Connection {i + 1}: Failed") + except Exception as e: - logger.error(f"Connection {i+1}: Error - {e}") + logger.error(f"Connection {i + 1}: Error - {e}") finally: await client.disconnect() await asyncio.sleep(1) # Cool down - + if connection_times: return { - 'avg_time': statistics.mean(connection_times), - 'min_time': min(connection_times), - 'max_time': max(connection_times), - 'std_dev': statistics.stdev(connection_times) if len(connection_times) > 1 else 0, - 'success_rate': len(connection_times) / iterations * 100 + "avg_time": statistics.mean(connection_times), + "min_time": min(connection_times), + "max_time": max(connection_times), + "std_dev": statistics.stdev(connection_times) + if len(connection_times) > 1 + else 0, + "success_rate": len(connection_times) / iterations * 100, } else: - return {'success_rate': 0} - - async def test_order_placement_performance(self, iterations: int = 10) -> Dict[str, float]: + return {"success_rate": 0} + + async def test_order_placement_performance( + self, iterations: int = 10 + ) -> Dict[str, float]: """Test order placement performance""" logger.info(f"Testing order placement performance ({iterations} iterations)") - + client = AsyncPocketOptionClient( - session_id=self.session_id, - is_demo=self.is_demo + session_id=self.session_id, is_demo=self.is_demo ) - + order_times = [] successful_orders = 0 - + try: await client.connect() - + if not client.is_connected: logger.error("Failed to connect for order testing") - return {'success_rate': 0} - + return {"success_rate": 0} + # Wait for balance await asyncio.sleep(2) - + for i in range(iterations): start_time = time.time() - + try: order = await client.place_order( asset="EURUSD_otc", amount=1.0, direction=OrderDirection.CALL, - duration=60 + duration=60, ) - + if order: order_time = time.time() - start_time order_times.append(order_time) successful_orders += 1 - logger.success(f"Order {i+1}: {order_time:.3f}s") + logger.success(f"Order {i + 1}: {order_time:.3f}s") else: - logger.warning(f"Order {i+1}: Failed (no response)") - + logger.warning(f"Order {i + 1}: Failed (no response)") + except Exception as e: - logger.error(f"Order {i+1}: Error - {e}") - + logger.error(f"Order {i + 1}: Error - {e}") + await asyncio.sleep(0.1) # Small delay between orders - + finally: await client.disconnect() - + if order_times: return { - 'avg_time': statistics.mean(order_times), - 'min_time': min(order_times), - 'max_time': max(order_times), - 'std_dev': statistics.stdev(order_times) if len(order_times) > 1 else 0, - 'success_rate': successful_orders / iterations * 100, - 'orders_per_second': 1 / statistics.mean(order_times) if order_times else 0 + "avg_time": statistics.mean(order_times), + "min_time": min(order_times), + "max_time": max(order_times), + "std_dev": statistics.stdev(order_times) if len(order_times) > 1 else 0, + "success_rate": successful_orders / iterations * 100, + "orders_per_second": 1 / statistics.mean(order_times) + if order_times + else 0, } else: - return {'success_rate': 0} - + return {"success_rate": 0} + async def test_data_retrieval_performance(self) -> Dict[str, float]: """Test data retrieval performance""" logger.info("Testing data retrieval performance") - + client = AsyncPocketOptionClient( - session_id=self.session_id, - is_demo=self.is_demo + session_id=self.session_id, is_demo=self.is_demo ) - + operations = { - 'balance': lambda: client.get_balance(), - 'candles': lambda: client.get_candles("EURUSD_otc", 60, 100), - 'active_orders': lambda: client.get_active_orders(), + "balance": lambda: client.get_balance(), + "candles": lambda: client.get_candles("EURUSD_otc", 60, 100), + "active_orders": lambda: client.get_active_orders(), } - + results = {} - + try: await client.connect() - + if not client.is_connected: logger.error("Failed to connect for data testing") return {} - + await asyncio.sleep(2) # Wait for initialization - + for operation_name, operation in operations.items(): times = [] - + for i in range(5): # 5 iterations per operation start_time = time.time() - + try: - result = await operation() + await operation() operation_time = time.time() - start_time times.append(operation_time) - logger.success(f"{operation_name} {i+1}: {operation_time:.3f}s") - + logger.success( + f"{operation_name} {i + 1}: {operation_time:.3f}s" + ) + except Exception as e: - logger.error(f"{operation_name} {i+1}: Error - {e}") - + logger.error(f"{operation_name} {i + 1}: Error - {e}") + await asyncio.sleep(0.1) - + if times: results[operation_name] = { - 'avg_time': statistics.mean(times), - 'min_time': min(times), - 'max_time': max(times) + "avg_time": statistics.mean(times), + "min_time": min(times), + "max_time": max(times), } - + finally: await client.disconnect() - + return results - - async def test_concurrent_operations(self, concurrency_level: int = 5) -> Dict[str, Any]: + + async def test_concurrent_operations( + self, concurrency_level: int = 5 + ) -> Dict[str, Any]: """Test concurrent operations performance""" logger.info(f"Testing concurrent operations (level: {concurrency_level})") - + async def perform_operation(operation_id: int): client = AsyncPocketOptionClient( - session_id=self.session_id, - is_demo=self.is_demo + session_id=self.session_id, is_demo=self.is_demo ) - + start_time = time.time() - + try: await client.connect() - + if client.is_connected: balance = await client.get_balance() operation_time = time.time() - start_time return { - 'operation_id': operation_id, - 'success': True, - 'time': operation_time, - 'balance': balance.balance if balance else None + "operation_id": operation_id, + "success": True, + "time": operation_time, + "balance": balance.balance if balance else None, } else: return { - 'operation_id': operation_id, - 'success': False, - 'time': time.time() - start_time, - 'error': 'Connection failed' + "operation_id": operation_id, + "success": False, + "time": time.time() - start_time, + "error": "Connection failed", } - + except Exception as e: return { - 'operation_id': operation_id, - 'success': False, - 'time': time.time() - start_time, - 'error': str(e) + "operation_id": operation_id, + "success": False, + "time": time.time() - start_time, + "error": str(e), } finally: await client.disconnect() - + # Run concurrent operations start_time = time.time() tasks = [perform_operation(i) for i in range(concurrency_level)] results = await asyncio.gather(*tasks, return_exceptions=True) total_time = time.time() - start_time - + # Analyze results - successful_operations = [r for r in results if isinstance(r, dict) and r.get('success')] - failed_operations = [r for r in results if not (isinstance(r, dict) and r.get('success'))] - + successful_operations = [ + r for r in results if isinstance(r, dict) and r.get("success") + ] + failed_operations = [ + r for r in results if not (isinstance(r, dict) and r.get("success")) + ] + if successful_operations: - operation_times = [r['time'] for r in successful_operations] - + operation_times = [r["time"] for r in successful_operations] + return { - 'total_time': total_time, - 'success_rate': len(successful_operations) / concurrency_level * 100, - 'avg_operation_time': statistics.mean(operation_times), - 'min_operation_time': min(operation_times), - 'max_operation_time': max(operation_times), - 'operations_per_second': len(successful_operations) / total_time, - 'failed_count': len(failed_operations) + "total_time": total_time, + "success_rate": len(successful_operations) / concurrency_level * 100, + "avg_operation_time": statistics.mean(operation_times), + "min_operation_time": min(operation_times), + "max_operation_time": max(operation_times), + "operations_per_second": len(successful_operations) / total_time, + "failed_count": len(failed_operations), } else: return { - 'total_time': total_time, - 'success_rate': 0, - 'failed_count': len(failed_operations) + "total_time": total_time, + "success_rate": 0, + "failed_count": len(failed_operations), } - + async def generate_performance_report(self) -> str: """Generate comprehensive performance report""" - logger.info("๐Ÿš€ Starting comprehensive performance tests...") - + logger.info("Starting comprehensive performance tests...") + report = [] report.append("=" * 60) report.append("POCKETOPTION ASYNC API PERFORMANCE REPORT") report.append("=" * 60) report.append("") - + # Test 1: Connection Performance - report.append("๐Ÿ“ก CONNECTION PERFORMANCE") + report.append("Connection: CONNECTION PERFORMANCE") report.append("-" * 30) try: conn_results = await self.test_connection_performance() - if conn_results.get('success_rate', 0) > 0: - report.append(f"โœ… Average Connection Time: {conn_results['avg_time']:.3f}s") - report.append(f"โœ… Min Connection Time: {conn_results['min_time']:.3f}s") - report.append(f"โœ… Max Connection Time: {conn_results['max_time']:.3f}s") - report.append(f"โœ… Success Rate: {conn_results['success_rate']:.1f}%") - report.append(f"โœ… Standard Deviation: {conn_results['std_dev']:.3f}s") + if conn_results.get("success_rate", 0) > 0: + report.append( + f"Success: Average Connection Time: {conn_results['avg_time']:.3f}s" + ) + report.append( + f"Success: Min Connection Time: {conn_results['min_time']:.3f}s" + ) + report.append( + f"Success: Max Connection Time: {conn_results['max_time']:.3f}s" + ) + report.append( + f"Success: Success Rate: {conn_results['success_rate']:.1f}%" + ) + report.append( + f"Success: Standard Deviation: {conn_results['std_dev']:.3f}s" + ) else: - report.append("โŒ Connection tests failed") + report.append("Error: Connection tests failed") except Exception as e: - report.append(f"โŒ Connection test error: {e}") - + report.append(f"Error: Connection test error: {e}") + report.append("") - + # Test 2: Data Retrieval Performance - report.append("๐Ÿ“Š DATA RETRIEVAL PERFORMANCE") + report.append("Statistics: DATA RETRIEVAL PERFORMANCE") report.append("-" * 35) try: data_results = await self.test_data_retrieval_performance() for operation, stats in data_results.items(): report.append(f" {operation.upper()}:") report.append(f" Average: {stats['avg_time']:.3f}s") - report.append(f" Range: {stats['min_time']:.3f}s - {stats['max_time']:.3f}s") + report.append( + f" Range: {stats['min_time']:.3f}s - {stats['max_time']:.3f}s" + ) except Exception as e: - report.append(f"โŒ Data retrieval test error: {e}") - + report.append(f"Error: Data retrieval test error: {e}") + report.append("") - + # Test 3: Concurrent Operations - report.append("โšก CONCURRENT OPERATIONS") + report.append("Performance: CONCURRENT OPERATIONS") report.append("-" * 25) try: concurrent_results = await self.test_concurrent_operations() - if concurrent_results.get('success_rate', 0) > 0: - report.append(f"โœ… Success Rate: {concurrent_results['success_rate']:.1f}%") - report.append(f"โœ… Operations/Second: {concurrent_results['operations_per_second']:.2f}") - report.append(f"โœ… Avg Operation Time: {concurrent_results['avg_operation_time']:.3f}s") - report.append(f"โœ… Total Time: {concurrent_results['total_time']:.3f}s") + if concurrent_results.get("success_rate", 0) > 0: + report.append( + f"Success: Success Rate: {concurrent_results['success_rate']:.1f}%" + ) + report.append( + f"Success: Operations/Second: {concurrent_results['operations_per_second']:.2f}" + ) + report.append( + f"Success: Avg Operation Time: {concurrent_results['avg_operation_time']:.3f}s" + ) + report.append( + f"Success: Total Time: {concurrent_results['total_time']:.3f}s" + ) else: - report.append("โŒ Concurrent operations failed") + report.append("Error: Concurrent operations failed") except Exception as e: - report.append(f"โŒ Concurrent test error: {e}") - + report.append(f"Error: Concurrent test error: {e}") + report.append("") report.append("=" * 60) report.append(f"Report generated at: {time.strftime('%Y-%m-%d %H:%M:%S')}") report.append("=" * 60) - + return "\n".join(report) @@ -317,16 +348,16 @@ async def main(): # Use test session (replace with real session for full tests) tester = PerformanceTester( session_id="n1p5ah5u8t9438rbunpgrq0hlq", # Replace with your session ID - is_demo=True + is_demo=True, ) - + report = await tester.generate_performance_report() print(report) - + # Save report to file with open("performance_report.txt", "w") as f: f.write(report) - + logger.success("Performance report saved to performance_report.txt") diff --git a/pocketoptionapi/_.py b/pocketoptionapi/_.py deleted file mode 100644 index e69de29..0000000 diff --git a/pocketoptionapi/__init__.py b/pocketoptionapi/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pocketoptionapi/__pycache__/__init__.cpython-310.pyc b/pocketoptionapi/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 4327073..0000000 Binary files a/pocketoptionapi/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/__init__.cpython-311.pyc b/pocketoptionapi/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index d6f4f31..0000000 Binary files a/pocketoptionapi/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/__init__.cpython-312.pyc b/pocketoptionapi/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 140c81a..0000000 Binary files a/pocketoptionapi/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/__init__.cpython-37.pyc b/pocketoptionapi/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 13a4bf9..0000000 Binary files a/pocketoptionapi/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/__init__.cpython-38.pyc b/pocketoptionapi/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 67ab17c..0000000 Binary files a/pocketoptionapi/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/api.cpython-310.pyc b/pocketoptionapi/__pycache__/api.cpython-310.pyc deleted file mode 100644 index 52860c5..0000000 Binary files a/pocketoptionapi/__pycache__/api.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/api.cpython-311.pyc b/pocketoptionapi/__pycache__/api.cpython-311.pyc deleted file mode 100644 index 61e04fd..0000000 Binary files a/pocketoptionapi/__pycache__/api.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/api.cpython-312.pyc b/pocketoptionapi/__pycache__/api.cpython-312.pyc deleted file mode 100644 index f88e8c1..0000000 Binary files a/pocketoptionapi/__pycache__/api.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/api.cpython-37.pyc b/pocketoptionapi/__pycache__/api.cpython-37.pyc deleted file mode 100644 index 816eeb4..0000000 Binary files a/pocketoptionapi/__pycache__/api.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/api.cpython-38.pyc b/pocketoptionapi/__pycache__/api.cpython-38.pyc deleted file mode 100644 index 3365c6b..0000000 Binary files a/pocketoptionapi/__pycache__/api.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/api.cpython-39.pyc b/pocketoptionapi/__pycache__/api.cpython-39.pyc deleted file mode 100644 index 68af619..0000000 Binary files a/pocketoptionapi/__pycache__/api.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/constants.cpython-310.pyc b/pocketoptionapi/__pycache__/constants.cpython-310.pyc deleted file mode 100644 index 8f08772..0000000 Binary files a/pocketoptionapi/__pycache__/constants.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/constants.cpython-311.pyc b/pocketoptionapi/__pycache__/constants.cpython-311.pyc deleted file mode 100644 index 83015a0..0000000 Binary files a/pocketoptionapi/__pycache__/constants.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/constants.cpython-312.pyc b/pocketoptionapi/__pycache__/constants.cpython-312.pyc deleted file mode 100644 index d0ac147..0000000 Binary files a/pocketoptionapi/__pycache__/constants.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/constants.cpython-37.pyc b/pocketoptionapi/__pycache__/constants.cpython-37.pyc deleted file mode 100644 index d1c7785..0000000 Binary files a/pocketoptionapi/__pycache__/constants.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/constants.cpython-38.pyc b/pocketoptionapi/__pycache__/constants.cpython-38.pyc deleted file mode 100644 index 5ad48c4..0000000 Binary files a/pocketoptionapi/__pycache__/constants.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/constants.cpython-39.pyc b/pocketoptionapi/__pycache__/constants.cpython-39.pyc deleted file mode 100644 index 619aef0..0000000 Binary files a/pocketoptionapi/__pycache__/constants.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/expiration.cpython-310.pyc b/pocketoptionapi/__pycache__/expiration.cpython-310.pyc deleted file mode 100644 index 70f0d08..0000000 Binary files a/pocketoptionapi/__pycache__/expiration.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/expiration.cpython-311.pyc b/pocketoptionapi/__pycache__/expiration.cpython-311.pyc deleted file mode 100644 index 5204f43..0000000 Binary files a/pocketoptionapi/__pycache__/expiration.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/expiration.cpython-312.pyc b/pocketoptionapi/__pycache__/expiration.cpython-312.pyc deleted file mode 100644 index eb86a1e..0000000 Binary files a/pocketoptionapi/__pycache__/expiration.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/expiration.cpython-37.pyc b/pocketoptionapi/__pycache__/expiration.cpython-37.pyc deleted file mode 100644 index 384058d..0000000 Binary files a/pocketoptionapi/__pycache__/expiration.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/expiration.cpython-38.pyc b/pocketoptionapi/__pycache__/expiration.cpython-38.pyc deleted file mode 100644 index 3e45a9b..0000000 Binary files a/pocketoptionapi/__pycache__/expiration.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/expiration.cpython-39.pyc b/pocketoptionapi/__pycache__/expiration.cpython-39.pyc deleted file mode 100644 index 0d18314..0000000 Binary files a/pocketoptionapi/__pycache__/expiration.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/global_value.cpython-310.pyc b/pocketoptionapi/__pycache__/global_value.cpython-310.pyc deleted file mode 100644 index f096c95..0000000 Binary files a/pocketoptionapi/__pycache__/global_value.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/global_value.cpython-311.pyc b/pocketoptionapi/__pycache__/global_value.cpython-311.pyc deleted file mode 100644 index d141896..0000000 Binary files a/pocketoptionapi/__pycache__/global_value.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/global_value.cpython-312.pyc b/pocketoptionapi/__pycache__/global_value.cpython-312.pyc deleted file mode 100644 index 4c4ffeb..0000000 Binary files a/pocketoptionapi/__pycache__/global_value.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/global_value.cpython-37.pyc b/pocketoptionapi/__pycache__/global_value.cpython-37.pyc deleted file mode 100644 index 268c77f..0000000 Binary files a/pocketoptionapi/__pycache__/global_value.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/global_value.cpython-38.pyc b/pocketoptionapi/__pycache__/global_value.cpython-38.pyc deleted file mode 100644 index fe0c4c1..0000000 Binary files a/pocketoptionapi/__pycache__/global_value.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/global_value.cpython-39.pyc b/pocketoptionapi/__pycache__/global_value.cpython-39.pyc deleted file mode 100644 index 7e529a8..0000000 Binary files a/pocketoptionapi/__pycache__/global_value.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/pocket.cpython-310.pyc b/pocketoptionapi/__pycache__/pocket.cpython-310.pyc deleted file mode 100644 index a77f843..0000000 Binary files a/pocketoptionapi/__pycache__/pocket.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/pocket.cpython-311.pyc b/pocketoptionapi/__pycache__/pocket.cpython-311.pyc deleted file mode 100644 index e301cf0..0000000 Binary files a/pocketoptionapi/__pycache__/pocket.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/stable_api.cpython-310.pyc b/pocketoptionapi/__pycache__/stable_api.cpython-310.pyc deleted file mode 100644 index 1983d9a..0000000 Binary files a/pocketoptionapi/__pycache__/stable_api.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/stable_api.cpython-311.pyc b/pocketoptionapi/__pycache__/stable_api.cpython-311.pyc deleted file mode 100644 index c58b1f1..0000000 Binary files a/pocketoptionapi/__pycache__/stable_api.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/stable_api.cpython-312.pyc b/pocketoptionapi/__pycache__/stable_api.cpython-312.pyc deleted file mode 100644 index 388669b..0000000 Binary files a/pocketoptionapi/__pycache__/stable_api.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/stable_api.cpython-37.pyc b/pocketoptionapi/__pycache__/stable_api.cpython-37.pyc deleted file mode 100644 index ae93045..0000000 Binary files a/pocketoptionapi/__pycache__/stable_api.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/stable_api.cpython-38.pyc b/pocketoptionapi/__pycache__/stable_api.cpython-38.pyc deleted file mode 100644 index aad7d75..0000000 Binary files a/pocketoptionapi/__pycache__/stable_api.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/__pycache__/stable_api.cpython-39.pyc b/pocketoptionapi/__pycache__/stable_api.cpython-39.pyc deleted file mode 100644 index 9e457df..0000000 Binary files a/pocketoptionapi/__pycache__/stable_api.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/api.py b/pocketoptionapi/api.py deleted file mode 100644 index f925d78..0000000 --- a/pocketoptionapi/api.py +++ /dev/null @@ -1,298 +0,0 @@ -"""Module for Pocket Option API.""" -import asyncio -import datetime -import time -import json -import logging -import threading -import requests -import ssl -import atexit -from collections import deque -from pocketoptionapi.ws.client import WebsocketClient -from pocketoptionapi.ws.channels.get_balances import * - -from pocketoptionapi.ws.channels.ssid import Ssid -# from pocketoptionapi.ws.channels.subscribe import * -# from pocketoptionapi.ws.channels.unsubscribe import * -# from pocketoptionapi.ws.channels.setactives import SetActives -from pocketoptionapi.ws.channels.candles import GetCandles -# from pocketoptionapi.ws.channels.buyv2 import Buyv2 -from pocketoptionapi.ws.channels.buyv3 import * -# from pocketoptionapi.ws.channels.user import * -# from pocketoptionapi.ws.channels.api_game_betinfo import Game_betinfo -# from pocketoptionapi.ws.channels.instruments import Get_instruments -# from pocketoptionapi.ws.channels.get_financial_information import GetFinancialInformation -# from pocketoptionapi.ws.channels.strike_list import Strike_list -# from pocketoptionapi.ws.channels.leaderboard import Leader_Board - -# from pocketoptionapi.ws.channels.traders_mood import Traders_mood_subscribe -# from pocketoptionapi.ws.channels.traders_mood import Traders_mood_unsubscribe -# from pocketoptionapi.ws.channels.buy_place_order_temp import Buy_place_order_temp -# from pocketoptionapi.ws.channels.get_order import Get_order -# from pocketoptionapi.ws.channels.get_deferred_orders import GetDeferredOrders -# from pocketoptionapi.ws.channels.get_positions import * - -# from pocketoptionapi.ws.channels.get_available_leverages import Get_available_leverages -# from pocketoptionapi.ws.channels.cancel_order import Cancel_order -# from pocketoptionapi.ws.channels.close_position import Close_position -# from pocketoptionapi.ws.channels.get_overnight_fee import Get_overnight_fee -# from pocketoptionapi.ws.channels.heartbeat import Heartbeat - -# from pocketoptionapi.ws.channels.digital_option import * -# from pocketoptionapi.ws.channels.api_game_getoptions import * -# from pocketoptionapi.ws.channels.sell_option import Sell_Option -# from pocketoptionapi.ws.channels.change_tpsl import Change_Tpsl -# from pocketoptionapi.ws.channels.change_auto_margin_call import ChangeAutoMarginCall - -from pocketoptionapi.ws.objects.timesync import TimeSync -# from pocketoptionapi.ws.objects.profile import Profile -from pocketoptionapi.ws.objects.candles import Candles -# from pocketoptionapi.ws.objects.listinfodata import ListInfoData -# from pocketoptionapi.ws.objects.betinfo import Game_betinfo_data -import pocketoptionapi.global_value as global_value -from pocketoptionapi.ws.channels.change_symbol import ChangeSymbol -from collections import defaultdict -from pocketoptionapi.ws.objects.time_sync import TimeSynchronizer - - -def nested_dict(n, type): - if n == 1: - return defaultdict(type) - else: - return defaultdict(lambda: nested_dict(n - 1, type)) - - -# InsecureRequestWarning: Unverified HTTPS request is being made. -# Adding certificate verification is strongly advised. -# See: https://urllib3.readthedocs.org/en/latest/security.html - - -class PocketOptionAPI(object): # pylint: disable=too-many-instance-attributes - """Class for communication with Pocket Option API.""" - - # pylint: disable=too-many-public-methods - socket_option_opened = {} - time_sync = TimeSync() - sync = TimeSynchronizer() - timesync = None - # pylint: disable=too-many-arguments - # profile = Profile() - candles = Candles() - # listinfodata = ListInfoData() - api_option_init_all_result = [] - api_option_init_all_result_v2 = [] - # for digital - underlying_list_data = None - position_changed = None - instrument_quites_generated_data = nested_dict(2, dict) - instrument_quotes_generated_raw_data = nested_dict(2, dict) - instrument_quites_generated_timestamp = nested_dict(2, dict) - strike_list = None - leaderboard_deals_client = None - # position_changed_data = nested_dict(2, dict) - # microserviceName_binary_options_name_option=nested_dict(2,dict) - order_async = None - # game_betinfo = Game_betinfo_data() - instruments = None - financial_information = None - buy_id = None - buy_order_id = None - traders_mood = {} # get hight(put) % - order_data = None - positions = None - position = None - deferred_orders = None - position_history = None - position_history_v2 = None - available_leverages = None - order_canceled = None - close_position_data = None - overnight_fee = None - # ---for real time - digital_option_placed_id = None - live_deal_data = nested_dict(3, deque) - - subscribe_commission_changed_data = nested_dict(2, dict) - real_time_candles = nested_dict(3, dict) - real_time_candles_maxdict_table = nested_dict(2, dict) - candle_generated_check = nested_dict(2, dict) - candle_generated_all_size_check = nested_dict(1, dict) - # ---for api_game_getoptions_result - api_game_getoptions_result = None - sold_options_respond = None - tpsl_changed_respond = None - auto_margin_call_changed_respond = None - top_assets_updated_data = {} - get_options_v2_data = None - # --for binary option multi buy - buy_multi_result = None - buy_multi_option = {} - # - result = None - training_balance_reset_request = None - balances_raw = None - user_profile_client = None - leaderboard_userinfo_deals_client = None - users_availability = None - history_data = None - historyNew = None - server_timestamp = None - sync_datetime = None - - # ------------------ - - def __init__(self, proxies=None): - """ - :param dict proxies: (optional) The http request proxies. - """ - self.websocket_client = None - self.websocket_thread = None - # self.wss_url = "wss://api-us-north.po.market/socket.io/?EIO=4&transport=websocket" - self.session = requests.Session() - self.session.verify = False - self.session.trust_env = False - self.proxies = proxies - # is used to determine if a buyOrder was set or failed. If - # it is None, there had been no buy order yet or just send. - # If it is false, the last failed - # If it is true, the last buy order was successful - self.buy_successful = None - self.loop = asyncio.get_event_loop() - self.websocket_client = WebsocketClient(self) - - @property - def websocket(self): - """Property to get websocket. - - :returns: The instance of :class:`WebSocket `. - """ - return self.websocket_client - - def send_websocket_request(self, name, msg, request_id="", no_force_send=True): - """Send websocket request to IQ Option server. - - :param no_force_send: - :param request_id: - :param str name: The websocket request name. - :param dict msg: The websocket request msg. - """ - - logger = logging.getLogger(__name__) - - # data = json.dumps(dict(name=name, msg=msg, request_id=request_id)) - data = f'42{json.dumps(msg)}' - - while (global_value.ssl_Mutual_exclusion or global_value.ssl_Mutual_exclusion_write) and no_force_send: - pass - global_value.ssl_Mutual_exclusion_write = True - - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - # Ejecutar la corutina connect dentro del bucle de eventos del nuevo hilo - loop.run_until_complete(self.websocket.send_message(data)) - - logger.debug(data) - global_value.ssl_Mutual_exclusion_write = False - - def start_websocket(self): - global_value.websocket_is_connected = False - global_value.check_websocket_if_error = False - global_value.websocket_error_reason = None - - # Obtener o crear un nuevo bucle de eventos para este hilo - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - # Ejecutar la corutina connect dentro del bucle de eventos del nuevo hilo - loop.run_until_complete(self.websocket.connect()) - loop.run_forever() - - while True: - try: - if global_value.check_websocket_if_error: - return False, global_value.websocket_error_reason - if global_value.websocket_is_connected is False: - return False, "Websocket connection closed." - elif global_value.websocket_is_connected is True: - return True, None - - except: - pass - pass - - def connect(self): - """Method for connection to Pocket Option API.""" - - global_value.ssl_Mutual_exclusion = False - global_value.ssl_Mutual_exclusion_write = False - - check_websocket, websocket_reason = self.start_websocket() - - if not check_websocket: - return check_websocket, websocket_reason - - self.time_sync.server_timestamps = None - while True: - try: - if self.time_sync.server_timestamps is not None: - break - except: - pass - return True, None - - async def close(self, error=None): - await self.websocket.on_close(error) - self.websocket_thread.join() - - def websocket_alive(self): - return self.websocket_thread.is_alive() - - @property - def get_balances(self): - """Property for get IQ Option http getprofile resource. - - :returns: The instance of :class:`Login - `. - """ - return Get_Balances(self) - - # ____________for_______binary_______option_____________ - - @property - def buyv3(self): - return Buyv3(self) - - @property - def getcandles(self): - """Property for get IQ Option websocket candles chanel. - - :returns: The instance of :class:`GetCandles - `. - """ - return GetCandles(self) - - @property - def change_symbol(self): - """Property for get Pocket Option websocket change_symbol chanel. - - :returns: The instance of :class:`ChangeSymbol - `. - """ - return ChangeSymbol(self) - - @property - def synced_datetime(self): - try: - if self.time_sync is not None: - self.sync.synchronize(self.time_sync.server_timestamp) - self.sync_datetime = self.sync.get_synced_datetime() - else: - logging.error("timesync no estรก establecido") - self.sync_datetime = None - except Exception as e: - logging.error(e) - self.sync_datetime = None - - return self.sync_datetime diff --git a/pocketoptionapi/backend/__init__.py b/pocketoptionapi/backend/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pocketoptionapi/backend/__pycache__/__init__.cpython-310.pyc b/pocketoptionapi/backend/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 7ef767b..0000000 Binary files a/pocketoptionapi/backend/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/backend/__pycache__/__init__.cpython-311.pyc b/pocketoptionapi/backend/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 026da60..0000000 Binary files a/pocketoptionapi/backend/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/backend/ws/__init__.py b/pocketoptionapi/backend/ws/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pocketoptionapi/backend/ws/__pycache__/__init__.cpython-310.pyc b/pocketoptionapi/backend/ws/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 101d86b..0000000 Binary files a/pocketoptionapi/backend/ws/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/backend/ws/__pycache__/__init__.cpython-311.pyc b/pocketoptionapi/backend/ws/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index e913397..0000000 Binary files a/pocketoptionapi/backend/ws/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/backend/ws/__pycache__/client.cpython-310.pyc b/pocketoptionapi/backend/ws/__pycache__/client.cpython-310.pyc deleted file mode 100644 index 20be704..0000000 Binary files a/pocketoptionapi/backend/ws/__pycache__/client.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/backend/ws/__pycache__/client.cpython-311.pyc b/pocketoptionapi/backend/ws/__pycache__/client.cpython-311.pyc deleted file mode 100644 index a235fbc..0000000 Binary files a/pocketoptionapi/backend/ws/__pycache__/client.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/backend/ws/chat/__init__.py b/pocketoptionapi/backend/ws/chat/__init__.py deleted file mode 100644 index 6a6e170..0000000 --- a/pocketoptionapi/backend/ws/chat/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -import websocket -import logging - -class WebSocketClientChat: - def __init__(self, url, pocket_api_instance=None): - self.url = url - self.pocket_api_instance = pocket_api_instance - self.logger = logging.getLogger(__name__) - self.logger.setLevel(logging.INFO) - formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') - - # Create file handler and add it to the logger - file_handler = logging.FileHandler('pocket.log') - file_handler.setFormatter(formatter) - self.logger.addHandler(file_handler) - self.ws = websocket.WebSocketApp(self.url, - on_open=self.on_open, - on_message=self.on_message, - on_error=self.on_error, - on_close=self.on_close) - self.logger.info("Starting websocket client...") - - def on_message(self, ws, message): - print(f"Message: {message}") - self.logger.info(f"Recieved a message!: {message}") - - def on_error(self, ws, error): - print(error) - self.logger.error(f"Got a error: {error}") - - def on_close(self, ws, close_status_code, close_msg): - print("### closed ###") - self.logger.warning(f"Connection closed, conections status_code: {close_status_code} and the message is: {close_msg}") - - def on_open(self, ws): - print("Opened connection") - self.logger.info("Opened!") - - def run(self): - self.ws.run_forever() # Use dispatcher for automatic reconnection diff --git a/pocketoptionapi/backend/ws/chat/__pycache__/__init__.cpython-311.pyc b/pocketoptionapi/backend/ws/chat/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 2d26e59..0000000 Binary files a/pocketoptionapi/backend/ws/chat/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/backend/ws/client.py b/pocketoptionapi/backend/ws/client.py deleted file mode 100644 index b6e3547..0000000 --- a/pocketoptionapi/backend/ws/client.py +++ /dev/null @@ -1,61 +0,0 @@ -# Client.py made by ยฉ Vigo Walker - -import websockets -import anyio -from rich.pretty import pprint as print -import json - -class WebSocketClient: - def __init__(self, session) -> None: - self.SESSION = session - async def websocket_client(self, url, pro): - while True: - try: - async with websockets.connect( - url, - extra_headers={ - # "Origin": "https://pocket-link19.co", - "Origin": "https://po.trade/" - }, - ) as websocket: - async for message in websocket: - await pro(message, websocket, url) - except KeyboardInterrupt: - exit() - except Exception as e: - print(e) - print("Connection lost... reconnecting") - await anyio.sleep(5) - return True - - - async def pro(self, message, websocket, url): - # if byte data - if type(message) == type(b""): - # cut 100 first symbols of byte date to prevent spam - print(str(message)[:100]) - return - else: - print(message) - - # Code to make order - # data = r'42["openOrder",{"asset":"#AXP_otc","amount":1,"action":"call","isDemo":1,"requestId":14680035,"optionType":100,"time":20}]' - # await websocket.send(data) - - if message.startswith('0{"sid":"'): - print(f"{url.split('/')[2]} got 0 sid send 40 ") - await websocket.send("40") - elif message == "2": - # ping-pong thing - print(f"{url.split('/')[2]} got 2 send 3") - await websocket.send("3") - - if message.startswith('40{"sid":"'): - print(f"{url.split('/')[2]} got 40 sid send session") - await websocket.send(self.SESSION) - print("message sent! We are logged in!!!") - - - async def main(self): - url = "wss://api-l.po.market/socket.io/?EIO=4&transport=websocket" - await self.websocket_client(url, self.pro) diff --git a/pocketoptionapi/candles.json b/pocketoptionapi/candles.json deleted file mode 100644 index 4519e16..0000000 --- a/pocketoptionapi/candles.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "asset": "AUDNZD_otc", - "index": 171201484810, - "data": - [ - { - "symbol_id": 70, - "time": 1712002800, - "open": 1.08567, - "close": 1.08553, - "high": 1.08586, - "low": 1.08475, - "asset": "AUDNZD_otc" - } - ], - "period": 60 -} diff --git a/pocketoptionapi/constants.py b/pocketoptionapi/constants.py deleted file mode 100644 index 3b09daf..0000000 --- a/pocketoptionapi/constants.py +++ /dev/null @@ -1,177 +0,0 @@ -import random - -ACTIVES = { - '#AAPL': 5, - '#AAPL_otc': 170, - '#AXP': 140, - '#AXP_otc': 291, - '#BA': 8, - '#BA_otc': 292, - '#CSCO': 154, - '#CSCO_otc': 427, - '#FB': 177, - '#FB_otc': 187, - '#INTC': 180, - '#INTC_otc': 190, - '#JNJ': 144, - '#JNJ_otc': 296, - '#JPM': 20, - '#MCD': 23, - '#MCD_otc': 175, - '#MSFT': 24, - '#MSFT_otc': 176, - '#PFE': 147, - '#PFE_otc': 297, - '#TSLA': 186, - '#TSLA_otc': 196, - '#XOM': 153, - '#XOM_otc': 426, - '100GBP': 315, - '100GBP_otc': 403, - 'AEX25': 449, - 'AMZN_otc': 412, - 'AUDCAD': 36, - 'AUDCAD_otc': 67, - 'AUDCHF': 37, - 'AUDCHF_otc': 68, - 'AUDJPY': 38, - 'AUDJPY_otc': 69, - 'AUDNZD_otc': 70, - 'AUDUSD': 40, - 'AUDUSD_otc': 71, - 'AUS200': 305, - 'AUS200_otc': 306, - 'BABA': 183, - 'BABA_otc': 428, - 'BCHEUR': 450, - 'BCHGBP': 451, - 'BCHJPY': 452, - 'BTCGBP': 453, - 'BTCJPY': 454, - 'BTCUSD': 197, - 'CAC40': 455, - 'CADCHF': 41, - 'CADCHF_otc': 72, - 'CADJPY': 42, - 'CADJPY_otc': 73, - 'CHFJPY': 43, - 'CHFJPY_otc': 74, - 'CHFNOK_otc': 457, - 'CITI': 326, - 'CITI_otc': 413, - 'D30EUR': 318, - 'D30EUR_otc': 406, - 'DASH_USD': 209, - 'DJI30': 322, - 'DJI30_otc': 409, - 'DOTUSD': 458, - 'E35EUR': 314, - 'E35EUR_otc': 402, - 'E50EUR': 319, - 'E50EUR_otc': 407, - 'ETHUSD': 272, - 'EURAUD': 44, - 'EURCAD': 45, - 'EURCHF': 46, - 'EURCHF_otc': 77, - 'EURGBP': 47, - 'EURGBP_otc': 78, - 'EURHUF_otc': 460, - 'EURJPY': 48, - 'EURJPY_otc': 79, - 'EURNZD_otc': 80, - 'EURRUB_otc': 200, - 'EURUSD': 1, - 'EURUSD_otc': 66, - 'F40EUR': 316, - 'F40EUR_otc': 404, - 'FDX_otc': 414, - 'GBPAUD': 51, - 'GBPAUD_otc': 81, - 'GBPCAD': 52, - 'GBPCHF': 53, - 'GBPJPY': 54, - 'GBPJPY_otc': 84, - 'GBPUSD': 56, - 'H33HKD': 463, - 'JPN225': 317, - 'JPN225_otc': 405, - 'LNKUSD': 464, - 'NASUSD': 323, - 'NASUSD_otc': 410, - 'NFLX': 182, - 'NFLX_otc': 429, - 'NZDJPY_otc': 89, - 'NZDUSD_otc': 90, - 'SMI20': 466, - 'SP500': 321, - 'SP500_otc': 408, - 'TWITTER': 330, - 'TWITTER_otc': 415, - 'UKBrent': 50, - 'UKBrent_otc': 164, - 'USCrude': 64, - 'USCrude_otc': 165, - 'USDCAD': 61, - 'USDCAD_otc': 91, - 'USDCHF': 62, - 'USDCHF_otc': 92, - 'USDJPY': 63, - 'USDJPY_otc': 93, - 'USDRUB_otc': 199, - 'VISA_otc': 416, - 'XAGEUR': 103, - 'XAGUSD': 65, - 'XAGUSD_otc': 167, - 'XAUEUR': 102, - 'XAUUSD': 2, - 'XAUUSD_otc': 169, - 'XNGUSD': 311, - 'XNGUSD_otc': 399, - 'XPDUSD': 313, - 'XPDUSD_otc': 401, - 'XPTUSD': 312, - 'XPTUSD_otc': 400, - - # Stocks - 'Microsoft_otc': 521, - 'Facebook_OTC': 522, - 'Tesla_otc': 523, - 'Boeing_OTC': 524, - 'American_Express_otc': 525 -} - - -class REGION: - REGIONS = { - "EUROPA": "wss://api-eu.po.market/socket.io/?EIO=4&transport=websocket", - "SEYCHELLES": "wss://api-sc.po.market/socket.io/?EIO=4&transport=websocket", - "HONGKONG": "wss://api-hk.po.market/socket.io/?EIO=4&transport=websocket", - "SERVER1": "wss://api-spb.po.market/socket.io/?EIO=4&transport=websocket", - "FRANCE2": "wss://api-fr2.po.market/socket.io/?EIO=4&transport=websocket", - "UNITED_STATES4": "wss://api-us4.po.market/socket.io/?EIO=4&transport=websocket", - "UNITED_STATES3": "wss://api-us3.po.market/socket.io/?EIO=4&transport=websocket", - "UNITED_STATES2": "wss://api-us2.po.market/socket.io/?EIO=4&transport=websocket", - "DEMO": "wss://demo-api-eu.po.market/socket.io/?EIO=4&transport=websocket", - "DEMO_2": "wss://try-demo-eu.po.market/socket.io/?EIO=4&transport=websocket", - "UNITED_STATES": "wss://api-us-north.po.market/socket.io/?EIO=4&transport=websocket", - "RUSSIA": "wss://api-msk.po.market/socket.io/?EIO=4&transport=websocket", - "SERVER2": "wss://api-l.po.market/socket.io/?EIO=4&transport=websocket", - "INDIA": "wss://api-in.po.market/socket.io/?EIO=4&transport=websocket", - "FRANCE": "wss://api-fr.po.market/socket.io/?EIO=4&transport=websocket", - "FINLAND": "wss://api-fin.po.market/socket.io/?EIO=4&transport=websocket", - "SERVER3": "wss://api-c.po.market/socket.io/?EIO=4&transport=websocket", - "ASIA": "wss://api-asia.po.market/socket.io/?EIO=4&transport=websocket", - "SERVER4": "wss://api-us-south.po.market/socket.io/?EIO=4&transport=websocket" - } - - def __getattr__(self, key): - try: - return self.REGIONS[key] - except KeyError: - raise AttributeError(f"'{self.REGIONS}' object has no attribute '{key}'") - - def get_regions(self, randomize: bool = True): - if randomize: - return sorted(list(self.REGIONS.values()), key=lambda k: random.random()) - return list(self.REGIONS.values()) diff --git a/pocketoptionapi/expiration.py b/pocketoptionapi/expiration.py deleted file mode 100644 index 88927dd..0000000 --- a/pocketoptionapi/expiration.py +++ /dev/null @@ -1,80 +0,0 @@ -import time -from datetime import datetime, timedelta - -# https://docs.python.org/3/library/datetime.html If optional argument tz is None or not specified, the timestamp is -# converted to the platform's local date and time, and the returned datetime object is naive. time.mktime( -# dt.timetuple()) - - -from datetime import datetime, timedelta -import time - - -def date_to_timestamp(date): - """Convierte un objeto datetime a timestamp.""" - return int(date.timestamp()) - - -def get_expiration_time(timestamp, duration): - """ - Calcula el tiempo de expiraciรณn mรกs cercano basado en un timestamp dado y una duraciรณn. - El tiempo de expiraciรณn siempre terminarรก en el segundo:30 del minuto. - - :param timestamp: El timestamp inicial para el cรกlculo. - :param duration: La duraciรณn deseada en minutos. - """ - # Convertir el timestamp dado a un objeto datetime - now_date = datetime.fromtimestamp(timestamp) - - # Ajustar los segundos a: 30 si no lo estรกn ya, de lo contrario, pasar al siguiente: 30 - if now_date.second < 30: - exp_date = now_date.replace(second=30, microsecond=0) - else: - exp_date = (now_date + timedelta(minutes=1)).replace(second=30, microsecond=0) - - # Calcular el tiempo de expiraciรณn teniendo en cuenta la duraciรณn - if duration > 1: - # Si la duraciรณn es mรกs de un minuto, calcular el tiempo final agregando la duraciรณn - # menos un minuto, ya que ya hemos ajustado para terminar en: 30 segundos. - exp_date += timedelta(minutes=duration - 1) - - # Sumar dos horas al tiempo de expiraciรณn - exp_date += timedelta(hours=2) - # Convertir el tiempo de expiraciรณn a timestamp - expiration_timestamp = date_to_timestamp(exp_date) - - return expiration_timestamp - - -def get_remaning_time(timestamp): - now_date = datetime.fromtimestamp(timestamp) - exp_date = now_date.replace(second=0, microsecond=0) - if (int(date_to_timestamp(exp_date+timedelta(minutes=1)))-timestamp) > 30: - exp_date = exp_date+timedelta(minutes=1) - - else: - exp_date = exp_date+timedelta(minutes=2) - exp = [] - for _ in range(5): - exp.append(date_to_timestamp(exp_date)) - exp_date = exp_date+timedelta(minutes=1) - idx = 11 - index = 0 - now_date = datetime.fromtimestamp(timestamp) - exp_date = now_date.replace(second=0, microsecond=0) - while index < idx: - if int(exp_date.strftime("%M")) % 15 == 0 and (int(date_to_timestamp(exp_date))-int(timestamp)) > 60*5: - exp.append(date_to_timestamp(exp_date)) - index = index+1 - exp_date = exp_date+timedelta(minutes=1) - - remaning = [] - - for idx, t in enumerate(exp): - if idx >= 5: - dr = 15*(idx-4) - else: - dr = idx+1 - remaning.append((dr, int(t)-int(time.time()))) - - return remaning \ No newline at end of file diff --git a/pocketoptionapi/global_value.py b/pocketoptionapi/global_value.py deleted file mode 100644 index 356be65..0000000 --- a/pocketoptionapi/global_value.py +++ /dev/null @@ -1,23 +0,0 @@ -# python -websocket_is_connected = False -# try fix ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:2361) -ssl_Mutual_exclusion = False # mutex read write -# if false websocket can sent self.websocket.send(data) -# else can not sent self.websocket.send(data) -ssl_Mutual_exclusion_write = False # if thread write - -SSID = None - -check_websocket_if_error = False -websocket_error_reason = None - -balance_id = None -balance = None -balance_type = None -balance_updated = None -result = None -order_data = {} -order_open = [] -order_closed = [] -stat = [] -DEMO = None diff --git a/pocketoptionapi/indicators.py b/pocketoptionapi/indicators.py deleted file mode 100644 index e69de29..0000000 diff --git a/pocketoptionapi/pocket.py b/pocketoptionapi/pocket.py deleted file mode 100644 index 5522440..0000000 --- a/pocketoptionapi/pocket.py +++ /dev/null @@ -1,166 +0,0 @@ -# Made by ยฉ Vigo Walker -from pocketoptionapi.backend.ws.client import WebSocketClient -from pocketoptionapi.backend.ws.chat import WebSocketClientChat -import threading -import ssl -import decimal -import json -import urllib -import websocket -import logging -import pause -from websocket._exceptions import WebSocketException - -class PocketOptionApi: - def __init__(self, init_msg) -> None: - self.ws_url = "wss://api-fin.po.market/socket.io/?EIO=4&transport=websocket" - self.token = "TEST_TOKEN" - self.connected_event = threading.Event() - self.client = WebSocketClient(self.ws_url) - self.logger = logging.getLogger(__name__) - self.logger.setLevel(logging.INFO) - self.init_msg = init_msg - formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') - - self.websocket_client = WebSocketClient(self.ws_url, pocket_api_instance=self) - - # Create file handler and add it to the logger - file_handler = logging.FileHandler('pocket.log') - file_handler.setFormatter(formatter) - self.logger.addHandler(file_handler) - - self.logger.info(f"initialiting Pocket API with token: {self.token}") - - self.websocket_client_chat = WebSocketClientChat(url="wss://chat-po.site/cabinet-client/socket.io/?EIO=4&transport=websocket") - self.websocket_client_chat.run() - - self.logger.info("Send chat websocket") - - self.websocket_client.ws.run_forever() - def auto_ping(self): - self.logger.info("Starting auto ping thread") - pause.seconds(5) - while True: - try: - if self.websocket_client.ws.sock and self.websocket_client.ws.sock.connected: # Check if socket is connected - self.ping() - else: - self.logger.warning("WebSocket is not connected. Attempting to reconnect.") - # Attempt reconnection - if self.connect(): - self.logger.info("Successfully reconnected.") - else: - self.logger.warning("Reconnection attempt failed.") - try: - self.ping() - self.logger.info("Sent ping reuqests successfully!") - except Exception as e: - self.logger.error(f"A error ocured trying to send ping: {e}") - except Exception as e: # Catch exceptions and log them - self.logger.error(f"An error occurred while sending ping or attempting to reconnect: {e}") - try: - self.logger.warning("Trying again...") - v1 = self.connect() - if v1: - self.logger.info("Conection completed!, sending ping...") - self.ping() - else: - self.logger.error("Connection was not established") - except Exception as e: - self.logger.error(f"A error ocured when trying again: {e}") - - def connect(self): - self.logger.info("Attempting to connect...") - - self.websocket_client_chat.ws.send("40") - data = r"""42["user_init",{"id":27658142,"secret":"8ed9be7299c3aa6363e57ae5a4e52b7a"}]""" - self.websocket_client_chat.ws.send(data) - try: - self.websocket_thread = threading.Thread(target=self.websocket_client.ws.run_forever, kwargs={ - 'sslopt': { - "check_hostname": False, - "cert_reqs": ssl.CERT_NONE, - "ca_certs": "cacert.pem" - }, - "ping_interval": 0, - 'skip_utf8_validation': True, - "origin": "https://pocketoption.com", - # "http_proxy_host": '127.0.0.1', "http_proxy_port": 8890 - }) - - self.websocket_thread.daemon = True - self.websocket_thread.start() - - self.logger.info("Connection successful.") - - self.send_websocket_request(msg="40") - self.send_websocket_request(self.init_msg) - except Exception as e: - print(f"Going for exception.... error: {e}") - self.logger.error(f"Connection failed with exception: {e}") - def send_websocket_request(self, msg): - """Send websocket request to PocketOption server. - :param dict msg: The websocket request msg. - """ - self.logger.info(f"Sending websocket request: {msg}") - def default(obj): - if isinstance(obj, decimal.Decimal): - return str(obj) - raise TypeError - - data = json.dumps(msg, default=default) - - try: - self.logger.info("Request sent successfully.") - self.websocket_client.ws.send(bytearray(urllib.parse.quote(data).encode('utf-8')), opcode=websocket.ABNF.OPCODE_BINARY) - return True - except Exception as e: - self.logger.error(f"Failed to send request with exception: {e}") - # Consider adding any necessary exception handling code here - try: - self.websocket_client.ws.send(bytearray(urllib.parse.quote(data).encode('utf-8')), opcode=websocket.ABNF.OPCODE_BINARY) - except Exception as e: - self.logger.warning(f"Was not able to reconnect: {e}") - - def _login(self, init_msg): - self.logger.info("Trying to login...") - - self.websocket_thread = threading.Thread(target=self.websocket_client.ws.run_forever, kwargs={ - 'sslopt': { - "check_hostname": False, - "cert_reqs": ssl.CERT_NONE, - "ca_certs": "cacert.pem" - }, - "ping_interval": 0, - 'skip_utf8_validation': True, - "origin": "https://pocketoption.com", - # "http_proxy_host": '127.0.0.1', "http_proxy_port": 8890 - }) - - self.websocket_thread.daemon = True - self.websocket_thread.start() - - self.logger.info("Login thread initialised successfully!") - - # self.send_websocket_request(msg=init_msg) - self.websocket_client.ws.send(init_msg) - - self.logger.info(f"Message was sent successfully to log you in!, mesage: {init_msg}") - - try: - self.websocket_client.ws.run_forever() - except WebSocketException as e: - self.logger.error(f"A error ocured with websocket: {e}") - # self.send_websocket_request(msg=init_msg) - try: - self.websocket_client.ws.run_forever() - self.send_websocket_request(msg=init_msg) - except Exception as e: - self.logger.error(f"Trying again failed, skiping... error: {e}") - # self.send_websocket_request(msg=init_msg) - - @property - def ping(self): - self.send_websocket_request(msg="3") - self.logger.info("Sent a ping request") - return True diff --git a/pocketoptionapi/prueba_temp.py b/pocketoptionapi/prueba_temp.py deleted file mode 100644 index 1141261..0000000 --- a/pocketoptionapi/prueba_temp.py +++ /dev/null @@ -1,9 +0,0 @@ -import pandas as pd - - -df_1 = pd.read_csv('datos_completos_AUDNZD_otc.csv') -df_2 = pd.read_csv('datos_completos_AUDNZD_otc_2.csv') - -df_full = pd.concat([df_1, df_2], axis=0) -print(df_full.shape) -df_full.to_csv('datos_full_AUDNZD_otc.csv', index=False) diff --git a/pocketoptionapi/stable_api.py b/pocketoptionapi/stable_api.py deleted file mode 100644 index 336432b..0000000 --- a/pocketoptionapi/stable_api.py +++ /dev/null @@ -1,408 +0,0 @@ -# This is a sample Python script. -import asyncio -import threading -import sys -from tzlocal import get_localzone -import json -from pocketoptionapi.api import PocketOptionAPI -import pocketoptionapi.constants as OP_code -# import pocketoptionapi.country_id as Country -# import threading -import time -import logging -import operator -import pocketoptionapi.global_value as global_value -from collections import defaultdict -from collections import deque -# from pocketoptionapi.expiration import get_expiration_time, get_remaning_time -import pandas as pd - -# Obtener la zona horaria local del sistema como una cadena en el formato IANA -local_zone_name = get_localzone() - - -def nested_dict(n, type): - if n == 1: - return defaultdict(type) - else: - return defaultdict(lambda: nested_dict(n - 1, type)) - - -def get_balance(): - # balances_raw = self.get_balances() - return global_value.balance - - -class PocketOption: - __version__ = "1.0.0" - - def __init__(self, ssid,demo): - self.size = [1, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, - 3600, 7200, 14400, 28800, 43200, 86400, 604800, 2592000] - global_value.SSID = ssid - global_value.DEMO = demo - self.suspend = 0.5 - self.thread = None - self.subscribe_candle = [] - self.subscribe_candle_all_size = [] - self.subscribe_mood = [] - # for digit - self.get_digital_spot_profit_after_sale_data = nested_dict(2, int) - self.get_realtime_strike_list_temp_data = {} - self.get_realtime_strike_list_temp_expiration = 0 - self.SESSION_HEADER = { - "User-Agent": r"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " - r"Chrome/66.0.3359.139 Safari/537.36"} - self.SESSION_COOKIE = {} - self.api = PocketOptionAPI() - self.loop = asyncio.get_event_loop() - - # - - # --start - # self.connect() - # this auto function delay too long - - # -------------------------------------------------------------------------- - - def get_server_timestamp(self): - return self.api.time_sync.server_timestamp - def Stop(self): - sys.exit() - - def get_server_datetime(self): - return self.api.time_sync.server_datetime - - def set_session(self, header, cookie): - self.SESSION_HEADER = header - self.SESSION_COOKIE = cookie - - def get_async_order(self, buy_order_id): - # name': 'position-changed', 'microserviceName': "portfolio"/"digital-options" - if self.api.order_async["deals"][0]["id"] == buy_order_id: - return self.api.order_async["deals"][0] - else: - return None - - def get_async_order_id(self, buy_order_id): - return self.api.order_async["deals"][0][buy_order_id] - - def start_async(self): - asyncio.run(self.api.connect()) - def disconnect(self): - """Gracefully close the WebSocket connection and clean up.""" - try: - # Close the WebSocket connection - if global_value.websocket_is_connected: - asyncio.run(self.api.close()) # Use the close method from the PocketOptionAPI class - print("WebSocket connection closed successfully.") - else: - print("WebSocket was not connected.") - - # Cancel any running asyncio tasks - if self.loop is not None: - for task in asyncio.all_tasks(self.loop): - task.cancel() - - # If you were using a custom event loop, stop and close it - if not self.loop.is_closed(): - self.loop.stop() - self.loop.close() - print("Event loop stopped and closed successfully.") - - # Clean up the WebSocket thread if it's still running - if self.api.websocket_thread is not None and self.api.websocket_thread.is_alive(): - self.api.websocket_thread.join() - print("WebSocket thread joined successfully.") - - except Exception as e: - print(f"Error during disconnect: {e}") - - def connect(self): - """ - Mรฉtodo sรญncrono para establecer la conexiรณn. - Utiliza internamente el bucle de eventos de asyncio para ejecutar la coroutine de conexiรณn. - """ - try: - # Iniciar el hilo que manejarรก la conexiรณn WebSocket - websocket_thread = threading.Thread(target=self.api.connect, daemon=True) - websocket_thread.start() - - except Exception as e: - print(f"Error al conectar: {e}") - return False - return True - - def GetPayout(self, pair): - data = self.api.GetPayoutData() - data = json.loads(data) - data2 = None - for i in data: - if i[1] == pair: - data2 = i - - return data2[5] - - @staticmethod - def check_connect(): - # True/False - if global_value.websocket_is_connected == 0: - return False - elif global_value.websocket_is_connected is None: - return False - else: - return True - - # wait for timestamp getting - - # self.update_ACTIVES_OPCODE() - @staticmethod - def get_balance(): - if global_value.balance_updated: - return global_value.balance - else: - return None - @staticmethod - def check_open(): - #print(global_value.order_open) - return global_value.order_open - @staticmethod - def check_order_closed(ido): - - while ido not in global_value.order_closed : - time.sleep(0.1) - - for pack in global_value.stat : - if pack[0] == ido : - print('Order Closed',pack[1]) - - #print(global_value.order_closed) - return pack[0] - - - def buy(self, amount, active, action, expirations): - self.api.buy_multi_option = {} - self.api.buy_successful = None - req_id = "buy" - - try: - if req_id not in self.api.buy_multi_option: - self.api.buy_multi_option[req_id] = {"id": None} - else: - self.api.buy_multi_option[req_id]["id"] = None - except Exception as e: - logging.error(f"Error initializing buy_multi_option: {e}") - return False, None - - global_value.order_data = None - global_value.result = None - - - - self.api.buyv3(amount, active, action, expirations, req_id) - - start_t = time.time() - while True: - if global_value.result is not None and global_value.order_data is not None: - break - if time.time() - start_t >= 5: - if isinstance(global_value.order_data, dict) and "error" in global_value.order_data: - logging.error(global_value.order_data["error"]) - else: - logging.error("Unknown error occurred during buy operation") - return False, None - time.sleep(0.1) # Sleep for a short period to prevent busy-waiting - - return global_value.result, global_value.order_data.get("id", None) - - def check_win(self, id_number): - """Return amount of deals and win/lose status.""" - - start_t = time.time() - order_info = None - - while True: - try: - order_info = self.get_async_order(id_number) - if order_info and "id" in order_info and order_info["id"] is not None: - break - except: - pass - # except Exception as e: - # logging.error(f"Error retrieving order info: {e}") - - if time.time() - start_t >= 120: - logging.error("Timeout: Could not retrieve order info in time.") - return None, "unknown" - - time.sleep(0.1) # Sleep for a short period to prevent busy-waiting - - if order_info and "profit" in order_info: - status = "win" if order_info["profit"] > 0 else "lose" - return order_info["profit"], status - else: - logging.error("Invalid order info retrieved.") - return None, "unknown" - - @staticmethod - def last_time(timestamp, period): - # Divide por 60 para convertir a minutos, usa int() para truncar al entero mรกs cercano (redondear hacia abajo), - # y luego multiplica por 60 para volver a convertir a segundos. - timestamp_redondeado = (timestamp // period) * period - return int(timestamp_redondeado) - - def get_candles(self, active, period, start_time=None, count=6000, count_request=1): - """ - Realiza mรบltiples peticiones para obtener datos histรณricos de velas y los procesa. - Devuelve un Dataframe ordenado de menor a mayor por la columna 'time'. - - :param active: El activo para el cual obtener las velas. - :param period: El intervalo de tiempo de cada vela en segundos. - :param count: El nรบmero de segundos a obtener en cada peticiรณn, max: 9000 = 150 datos de 1 min. - :param start_time: El tiempo final para la รบltima vela. - :param count_request: El nรบmero de peticiones para obtener mรกs datos histรณricos. - """ - try: - print("In try") - if start_time is None: - time_sync = self.get_server_timestamp() - time_red = self.last_time(time_sync, period) - else: - time_red = start_time - time_sync = self.get_server_timestamp() - - all_candles = [] - - for _ in range(count_request): - self.api.history_data = None - print("In FOr Loop") - - while True: - logging.info("Entered WHileloop in GetCandles") - print("In WHile loop") - try: - # Enviar la peticiรณn de velas - print("Before get candles") - self.api.getcandles(active, 30, count, time_red) - print("AFter get candles") - - # Esperar hasta que history_data no sea None - for i in range(1, 100): - if self.api.history_data is None: - print(f"SLeeping, attempt: {i} / 100") - time.sleep(0.1) - if i == 99: - break - - if self.api.history_data is not None: - print("In break") - all_candles.extend(self.api.history_data) - break - - except Exception as e: - logging.error(e) - # Puedes agregar lรณgica de reconexiรณn aquรญ si es necesario - #self.api.connect() - - # Ordenar all_candles por 'index' para asegurar que estรฉn en el orden correcto - all_candles = sorted(all_candles, key=lambda x: x["time"]) - - # Asegurarse de que se han recibido velas antes de actualizar time_red - if all_candles: - # Usar el tiempo de la รบltima vela recibida para la prรณxima peticiรณn - time_red = all_candles[0]["time"] - - # Crear un DataFrame con todas las velas obtenidas - df_candles = pd.DataFrame(all_candles) - - # Ordenar por la columna 'time' de menor a mayor - df_candles = df_candles.sort_values(by='time').reset_index(drop=True) - df_candles['time'] = pd.to_datetime(df_candles['time'], unit='s') - df_candles.set_index('time', inplace=True) - df_candles.index = df_candles.index.floor('1s') - - # Resamplear los datos en intervalos de 30 segundos y calcular open, high, low, close - df_resampled = df_candles['price'].resample(f'{period}s').ohlc() - - # Resetear el รญndice para que 'time' vuelva a ser una columna - df_resampled.reset_index(inplace=True) - - print("FINISHED!!!") - - return df_resampled - except: - print("In except") - return None - - @staticmethod - def process_data_history(data, period): - """ - Este mรฉtodo toma datos histรณricos, los convierte en un DataFrame de pandas, redondea los tiempos al minuto mรกs cercano, - y calcula los valores OHLC (Open, High, Low, Close) para cada minuto. Luego, convierte el resultado en un diccionario - y lo devuelve. - - :param dict data: Datos histรณricos que incluyen marcas de tiempo y precios. - :param int period: Periodo en minutos - :return: Un diccionario que contiene los valores OHLC agrupados por minutos redondeados. - """ - # Crear DataFrame - df = pd.DataFrame(data['history'], columns=['timestamp', 'price']) - # Convertir a datetime y redondear al minuto - df['datetime'] = pd.to_datetime(df['timestamp'], unit='s', utc=True) - # df['datetime'] = df['datetime'].dt.tz_convert(str(local_zone_name)) - df['minute_rounded'] = df['datetime'].dt.floor(f'{period / 60}min') - - # Calcular OHLC - ohlcv = df.groupby('minute_rounded').agg( - open=('price', 'first'), - high=('price', 'max'), - low=('price', 'min'), - close=('price', 'last') - ).reset_index() - - ohlcv['time'] = ohlcv['minute_rounded'].apply(lambda x: int(x.timestamp())) - ohlcv = ohlcv.drop(columns='minute_rounded') - - ohlcv = ohlcv.iloc[:-1] - - ohlcv_dict = ohlcv.to_dict(orient='records') - - return ohlcv_dict - - @staticmethod - def process_candle(candle_data, period): - """ - Resumen: Este mรฉtodo estรกtico de Python, denominado `process_candle`, toma datos de velas financieras y un perรญodo de tiempo especรญfico como entrada. - Realiza varias operaciones de limpieza y organizaciรณn de datos utilizando pandas, incluyendo la ordenaciรณn por tiempo, eliminaciรณn de duplicados, - y reindexaciรณn. Ademรกs, verifica si las diferencias de tiempo entre las entradas consecutivas son iguales al perรญodo especificado y retorna tanto el DataFrame procesado - como un booleano indicando si todas las diferencias son iguales al perรญodo dado. Este mรฉtodo es รบtil para preparar y verificar la consistencia de los datos de velas financieras - para anรกlisis posteriores. - - Procesa los datos de las velas recibidos como entrada. - Convierte los datos de entrada en un DataFrame de pandas, los ordena por tiempo de forma ascendente, - elimina duplicados basados en la columna 'time', y reinicia el รญndice del DataFrame. - Adicionalmente, verifica si las diferencias de tiempo entre las filas consecutivas son iguales al perรญodo especificado, - asumiendo que el perรญodo estรก dado en segundos, e imprime si todas las diferencias son de 60 segundos. - :param list candle_data: Datos de las velas a procesar. - :param int period: El perรญodo de tiempo entre las velas, usado para la verificaciรณn de diferencias de tiempo. - :return: DataFrame procesado con los datos de las velas. - """ - # Convierte los datos en un DataFrame y los aรฑade al DataFrame final - data_df = pd.DataFrame(candle_data) - # datos_completos = pd.concat([datos_completos, data_df], ignore_index=True) - # Procesa los datos obtenidos - data_df.sort_values(by='time', ascending=True, inplace=True) - data_df.drop_duplicates(subset='time', keep="first", inplace=True) - data_df.reset_index(drop=True, inplace=True) - data_df.ffill(inplace=True) - #data_df.drop(columns='symbol_id', inplace=True) - # Verificaciรณn opcional: Comprueba si las diferencias son todas de 60 segundos (excepto el primer valor NaN) - diferencias = data_df['time'].diff() - diff = (diferencias[1:] == period).all() - return data_df, diff - - def change_symbol(self, active, period): - return self.api.change_symbol(active, period) - - def sync_datetime(self): - return self.api.synced_datetime diff --git a/pocketoptionapi/test/__init__.py b/pocketoptionapi/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pocketoptionapi/test/webdrivertest.py b/pocketoptionapi/test/webdrivertest.py deleted file mode 100644 index 1e7889d..0000000 --- a/pocketoptionapi/test/webdrivertest.py +++ /dev/null @@ -1,15 +0,0 @@ -from webdriver_manager.chrome import ChromeDriverManager -from selenium import webdriver - -class WebdriverTest: - def __init__(self) -> None: - self.driver = webdriver.Chrome(ChromeDriverManager().install()) - self.url = "https://pocketoption.com" - def connect(self): - sevice = webdriver.ChromeService(executable_path=ChromeDriverManager().install()) - driver = webdriver.Chrome(service=sevice) - driver.get(url=self.url) - -# Example usage -wt = WebdriverTest() -wt.connect() \ No newline at end of file diff --git a/pocketoptionapi/ws/__pycache__/client.cpython-310.pyc b/pocketoptionapi/ws/__pycache__/client.cpython-310.pyc deleted file mode 100644 index 38cc8ac..0000000 Binary files a/pocketoptionapi/ws/__pycache__/client.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/__pycache__/client.cpython-311.pyc b/pocketoptionapi/ws/__pycache__/client.cpython-311.pyc deleted file mode 100644 index c231de3..0000000 Binary files a/pocketoptionapi/ws/__pycache__/client.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/__pycache__/client.cpython-312.pyc b/pocketoptionapi/ws/__pycache__/client.cpython-312.pyc deleted file mode 100644 index 8394e57..0000000 Binary files a/pocketoptionapi/ws/__pycache__/client.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/__pycache__/client.cpython-37.pyc b/pocketoptionapi/ws/__pycache__/client.cpython-37.pyc deleted file mode 100644 index b841419..0000000 Binary files a/pocketoptionapi/ws/__pycache__/client.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/__pycache__/client.cpython-38.pyc b/pocketoptionapi/ws/__pycache__/client.cpython-38.pyc deleted file mode 100644 index 9049a49..0000000 Binary files a/pocketoptionapi/ws/__pycache__/client.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/__pycache__/client.cpython-39.pyc b/pocketoptionapi/ws/__pycache__/client.cpython-39.pyc deleted file mode 100644 index 448012b..0000000 Binary files a/pocketoptionapi/ws/__pycache__/client.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/chanels/__pycache__/base.cpython-39.pyc b/pocketoptionapi/ws/chanels/__pycache__/base.cpython-39.pyc deleted file mode 100644 index 946781e..0000000 Binary files a/pocketoptionapi/ws/chanels/__pycache__/base.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/chanels/__pycache__/buyv3.cpython-39.pyc b/pocketoptionapi/ws/chanels/__pycache__/buyv3.cpython-39.pyc deleted file mode 100644 index 85ecc42..0000000 Binary files a/pocketoptionapi/ws/chanels/__pycache__/buyv3.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/chanels/__pycache__/candles.cpython-39.pyc b/pocketoptionapi/ws/chanels/__pycache__/candles.cpython-39.pyc deleted file mode 100644 index e553ea8..0000000 Binary files a/pocketoptionapi/ws/chanels/__pycache__/candles.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/chanels/__pycache__/get_balances.cpython-39.pyc b/pocketoptionapi/ws/chanels/__pycache__/get_balances.cpython-39.pyc deleted file mode 100644 index 1803a0b..0000000 Binary files a/pocketoptionapi/ws/chanels/__pycache__/get_balances.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/chanels/__pycache__/ssid.cpython-39.pyc b/pocketoptionapi/ws/chanels/__pycache__/ssid.cpython-39.pyc deleted file mode 100644 index 930d8f8..0000000 Binary files a/pocketoptionapi/ws/chanels/__pycache__/ssid.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/chanels/base.py b/pocketoptionapi/ws/chanels/base.py deleted file mode 100644 index 2733f90..0000000 --- a/pocketoptionapi/ws/chanels/base.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Module for base Pocket Option base websocket chanel.""" - - -class Base(object): - """Class for base Pocket Option websocket chanel.""" - - # pylint: disable=too-few-public-methods - - def __init__(self, api): - """ - :param api: The instance of :class:`IQOptionAPI - `. - """ - self.api = api - - def send_websocket_request(self, name, msg, request_id=""): - """Send request to Pocket Option server websocket. - - :param request_id: - :param str name: The websocket chanel name. - :param list msg: The websocket chanel msg. - - :returns: The instance of :class:`requests.Response`. - """ - - return self.api.send_websocket_request(name, msg, request_id) diff --git a/pocketoptionapi/ws/chanels/buyv3.py b/pocketoptionapi/ws/chanels/buyv3.py deleted file mode 100644 index f39fa99..0000000 --- a/pocketoptionapi/ws/chanels/buyv3.py +++ /dev/null @@ -1,61 +0,0 @@ -import datetime -import json -import time -from pocketoptionapi.ws.chanels.base import Base -import logging -import pocketoptionapi.global_value as global_value -from pocketoptionapi.expiration import get_expiration_time - - -class Buyv3(Base): - name = "sendMessage" - - def __call__(self, amount, active, direction, duration, request_id): - - # thank Darth-Carrotpie's code - # https://github.com/Lu-Yi-Hsun/iqoptionapi/issues/6 - exp = get_expiration_time(int(self.api.timesync.server_timestamps), duration) - """if idx < 5: - option = 3 # "turbo" - else: - option = 1 # "binary""" - # Construir el diccionario - data_dict = { - "asset": active, - "amount": amount, - "action": direction, - "isDemo": 1, - "requestId": request_id, - "optionType": 100, - "time": duration - } - - message = ["openOrder", data_dict] - - self.send_websocket_request(self.name, message, str(request_id)) - - -class Buyv3_by_raw_expired(Base): - name = "sendMessage" - - def __call__(self, price, active, direction, option, expired, request_id): - - # thank Darth-Carrotpie's code - # https://github.com/Lu-Yi-Hsun/iqoptionapi/issues/6 - - if option == "turbo": - option_id = 3 # "turbo" - elif option == "binary": - option_id = 1 # "binary" - data = { - "body": {"price": price, - "active_id": active, - "expired": int(expired), - "direction": direction.lower(), - "option_type_id": option_id, - "user_balance_id": int(global_value.balance_id) - }, - "name": "binary-options.open-option", - "version": "1.0" - } - self.send_websocket_request(self.name, data, str(request_id)) diff --git a/pocketoptionapi/ws/chanels/candles.py b/pocketoptionapi/ws/chanels/candles.py deleted file mode 100644 index 62dcc76..0000000 --- a/pocketoptionapi/ws/chanels/candles.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Module for Pocket option candles websocket chanel.""" - -from pocketoptionapi.ws.chanels.base import Base -import time -import random - - -def index_num(): - # El nรบmero mรญnimo serรญa 100000000000 (12 dรญgitos) - minimo = 5000 - # El nรบmero mรกximo serรญa 999999999999 (12 dรญgitos) - maximo = 10000 - 1 - # Generar y retornar un nรบmero aleatorio dentro del rango - return random.randint(minimo, maximo) - - -class GetCandles(Base): - """Class for Pocket option candles websocket chanel.""" - # pylint: disable=too-few-public-methods - - name = "sendMessage" - - def __call__(self, active_id, interval, count, end_time): - """Method to send message to candles websocket chanel. - - :param active_id: The active/asset identifier. - :param interval: The candle duration (timeframe for the candles). - :param count: The number of candles you want to have - """ - - # {"asset": "AUDNZD_otc", "index": 171201484810, "time": 1712002800, "offset": 9000, "period": 60}] - data = { - "asset": str(active_id), - "index": end_time, - "time": end_time, - "offset": count, # number of candles - "period": interval, # time size sample:if interval set 1 mean get time 0~1 candle - } - - data = ["loadHistoryPeriod", data] - - self.send_websocket_request(self.name, data) diff --git a/pocketoptionapi/ws/chanels/get_balances.py b/pocketoptionapi/ws/chanels/get_balances.py deleted file mode 100644 index 97659bb..0000000 --- a/pocketoptionapi/ws/chanels/get_balances.py +++ /dev/null @@ -1,18 +0,0 @@ -from pocketoptionapi.ws.chanels.base import Base -import time - - -class Get_Balances(Base): - name = "sendMessage" - - def __call__(self): - """ - :param options_ids: list or int - """ - - data = {"name": "get-balances", - "version": "1.0" - } - print("get_balances in get_balances.py") - - self.send_websocket_request(self.name, data) diff --git a/pocketoptionapi/ws/chanels/ssid.py b/pocketoptionapi/ws/chanels/ssid.py deleted file mode 100644 index 62582ae..0000000 --- a/pocketoptionapi/ws/chanels/ssid.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Module for Pocket Option API ssid websocket chanel.""" - -from pocketoptionapi.ws.chanels.base import Base - - -class Ssid(Base): - """Class for Pocket Option API ssid websocket chanel.""" - # pylint: disable=too-few-public-methods - - name = "ssid" - - def __call__(self, ssid): - """Method to send message to ssid websocket chanel. - - :param ssid: The session identifier. - """ - self.send_websocket_request(self.name, ssid) diff --git a/pocketoptionapi/ws/channels/__pycache__/base.cpython-310.pyc b/pocketoptionapi/ws/channels/__pycache__/base.cpython-310.pyc deleted file mode 100644 index f254efb..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/base.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/base.cpython-311.pyc b/pocketoptionapi/ws/channels/__pycache__/base.cpython-311.pyc deleted file mode 100644 index 8a9c9c1..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/base.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/base.cpython-312.pyc b/pocketoptionapi/ws/channels/__pycache__/base.cpython-312.pyc deleted file mode 100644 index 7dd9986..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/base.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/base.cpython-37.pyc b/pocketoptionapi/ws/channels/__pycache__/base.cpython-37.pyc deleted file mode 100644 index 3c4fbb0..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/base.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/base.cpython-38.pyc b/pocketoptionapi/ws/channels/__pycache__/base.cpython-38.pyc deleted file mode 100644 index 0379593..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/base.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/base.cpython-39.pyc b/pocketoptionapi/ws/channels/__pycache__/base.cpython-39.pyc deleted file mode 100644 index 6e0aec9..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/base.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-310.pyc b/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-310.pyc deleted file mode 100644 index c5ff517..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-311.pyc b/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-311.pyc deleted file mode 100644 index 5edc979..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-312.pyc b/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-312.pyc deleted file mode 100644 index 5c4e350..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-37.pyc b/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-37.pyc deleted file mode 100644 index 46d6cd2..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-38.pyc b/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-38.pyc deleted file mode 100644 index 79cc305..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-39.pyc b/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-39.pyc deleted file mode 100644 index 24ec963..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/candles.cpython-310.pyc b/pocketoptionapi/ws/channels/__pycache__/candles.cpython-310.pyc deleted file mode 100644 index 777acb4..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/candles.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/candles.cpython-311.pyc b/pocketoptionapi/ws/channels/__pycache__/candles.cpython-311.pyc deleted file mode 100644 index 2e0d25b..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/candles.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/candles.cpython-312.pyc b/pocketoptionapi/ws/channels/__pycache__/candles.cpython-312.pyc deleted file mode 100644 index 41eebcf..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/candles.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/candles.cpython-37.pyc b/pocketoptionapi/ws/channels/__pycache__/candles.cpython-37.pyc deleted file mode 100644 index 6e03805..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/candles.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/candles.cpython-38.pyc b/pocketoptionapi/ws/channels/__pycache__/candles.cpython-38.pyc deleted file mode 100644 index 4a4d37f..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/candles.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/candles.cpython-39.pyc b/pocketoptionapi/ws/channels/__pycache__/candles.cpython-39.pyc deleted file mode 100644 index 3e01754..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/candles.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-310.pyc b/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-310.pyc deleted file mode 100644 index 34a0144..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-311.pyc b/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-311.pyc deleted file mode 100644 index 8ed93bb..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-312.pyc b/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-312.pyc deleted file mode 100644 index 2753898..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-37.pyc b/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-37.pyc deleted file mode 100644 index fd305e0..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-38.pyc b/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-38.pyc deleted file mode 100644 index 027db20..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-39.pyc b/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-39.pyc deleted file mode 100644 index 395b0e1..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-310.pyc b/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-310.pyc deleted file mode 100644 index 66e1c60..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-311.pyc b/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-311.pyc deleted file mode 100644 index f5380dd..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-312.pyc b/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-312.pyc deleted file mode 100644 index 53705f2..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-37.pyc b/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-37.pyc deleted file mode 100644 index c78d9d3..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-38.pyc b/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-38.pyc deleted file mode 100644 index 3e1fd5a..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-39.pyc b/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-39.pyc deleted file mode 100644 index 47bd32f..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-310.pyc b/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-310.pyc deleted file mode 100644 index 0b94b8c..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-311.pyc b/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-311.pyc deleted file mode 100644 index 160e8f2..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-312.pyc b/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-312.pyc deleted file mode 100644 index 4d88ce0..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-37.pyc b/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-37.pyc deleted file mode 100644 index c7fb444..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-38.pyc b/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-38.pyc deleted file mode 100644 index 30c5b8b..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-39.pyc b/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-39.pyc deleted file mode 100644 index 33542f7..0000000 Binary files a/pocketoptionapi/ws/channels/__pycache__/ssid.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/channels/base.py b/pocketoptionapi/ws/channels/base.py deleted file mode 100644 index 5a93fb1..0000000 --- a/pocketoptionapi/ws/channels/base.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Module for base Pocket Option base websocket chanel.""" - - -class Base(object): - """Class for base Pocket Option websocket chanel.""" - - # pylint: disable=too-few-public-methods - - def __init__(self, api): - """ - :param api: The instance of :class:`PocketOptionAPI - `. - """ - self.api = api - - def send_websocket_request(self, name, msg, request_id=""): - """Send request to Pocket Option server websocket. - - :param request_id: - :param str name: The websocket chanel name. - :param list msg: The websocket chanel msg. - - :returns: The instance of :class:`requests.Response`. - """ - - return self.api.send_websocket_request(name, msg, request_id) diff --git a/pocketoptionapi/ws/channels/buyv3.py b/pocketoptionapi/ws/channels/buyv3.py deleted file mode 100644 index 406ecf4..0000000 --- a/pocketoptionapi/ws/channels/buyv3.py +++ /dev/null @@ -1,61 +0,0 @@ -import datetime -import json -import time -from pocketoptionapi.ws.channels.base import Base -import logging -import pocketoptionapi.global_value as global_value -from pocketoptionapi.expiration import get_expiration_time - - -class Buyv3(Base): - name = "sendMessage" - - def __call__(self, amount, active, direction, duration, request_id): - - # thank Darth-Carrotpie's code - # https://github.com/Lu-Yi-Hsun/iqoptionapi/issues/6 - # exp = get_expiration_time(int(self.api.timesync.server_timestamps), duration) - """if idx < 5: - option = 3 # "turbo" - else: - option = 1 # "binary""" - # Construir el diccionario - data_dict = { - "asset": active, - "amount": amount, - "action": direction, - "isDemo": 1, - "requestId": request_id, - "optionType": 100, - "time": duration - } - - message = ["openOrder", data_dict] - - self.send_websocket_request(self.name, message, str(request_id)) - - -class Buyv3_by_raw_expired(Base): - name = "sendMessage" - - def __call__(self, price, active, direction, option, expired, request_id): - - # thank Darth-Carrotpie's code - # https://github.com/Lu-Yi-Hsun/iqoptionapi/issues/6 - - if option == "turbo": - option_id = 3 # "turbo" - elif option == "binary": - option_id = 1 # "binary" - data = { - "body": {"price": price, - "active_id": active, - "expired": int(expired), - "direction": direction.lower(), - "option_type_id": option_id, - "user_balance_id": int(global_value.balance_id) - }, - "name": "binary-options.open-option", - "version": "1.0" - } - self.send_websocket_request(self.name, data, str(request_id)) diff --git a/pocketoptionapi/ws/channels/candles.py b/pocketoptionapi/ws/channels/candles.py deleted file mode 100644 index 806ca4f..0000000 --- a/pocketoptionapi/ws/channels/candles.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Module for Pocket option candles websocket chanel.""" - -from pocketoptionapi.ws.channels.base import Base -import time -import random - - -def index_num(): - # El nรบmero mรญnimo serรญa 100000000000 (12 dรญgitos) - minimo = 5000 - # El nรบmero mรกximo serรญa 999999999999 (12 dรญgitos) - maximo = 10000 - 1 - # Generar y retornar un nรบmero aleatorio dentro del rango - return random.randint(minimo, maximo) - - -class GetCandles(Base): - """Class for Pocket option candles websocket chanel.""" - # pylint: disable=too-few-public-methods - - name = "sendMessage" - - def __call__(self, active_id, interval, count, end_time): - """Method to send message to candles websocket chanel. - - :param active_id: The active/asset identifier. - :param interval: The candle duration (timeframe for the candles). - :param count: The number of candles you want to have - """ - - # {"asset": "AUDNZD_otc", "index": 171201484810, "time": 1712002800, "offset": 9000, "period": 60}] - data = { - "asset": str(active_id), - "index": end_time, - "offset": count, # number of candles - "period": interval, - "time": end_time, # time size sample:if interval set 1 mean get time 0~1 candle - } - - data = ["loadHistoryPeriod", data] - - self.send_websocket_request(self.name, data) diff --git a/pocketoptionapi/ws/channels/change_symbol.py b/pocketoptionapi/ws/channels/change_symbol.py deleted file mode 100644 index 9f6ff97..0000000 --- a/pocketoptionapi/ws/channels/change_symbol.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Module for PocketOption change symbol websocket chanel.""" - -from pocketoptionapi.ws.channels.base import Base -import time -import random - - -class ChangeSymbol(Base): - """Class for Pocket option change symbol websocket chanel.""" - # pylint: disable=too-few-public-methods - - name = "sendMessage" - - def __call__(self, active_id, interval): - """Method to send message to candles websocket chanel. - - :param active_id: The active/asset identifier. - :param interval: The candle duration (timeframe for the candles). - """ - - data_stream = ["changeSymbol", { - "asset": active_id, - "period": interval}] - - self.send_websocket_request(self.name, data_stream) diff --git a/pocketoptionapi/ws/channels/get_balances.py b/pocketoptionapi/ws/channels/get_balances.py deleted file mode 100644 index 377b90c..0000000 --- a/pocketoptionapi/ws/channels/get_balances.py +++ /dev/null @@ -1,18 +0,0 @@ -from pocketoptionapi.ws.channels.base import Base -import time - - -class Get_Balances(Base): - name = "sendMessage" - - def __call__(self): - """ - :param options_ids: list or int - """ - - data = {"name": "get-balances", - "version": "1.0" - } - print("get_balances in get_balances.py") - - self.send_websocket_request(self.name, data) diff --git a/pocketoptionapi/ws/channels/ssid.py b/pocketoptionapi/ws/channels/ssid.py deleted file mode 100644 index 777d934..0000000 --- a/pocketoptionapi/ws/channels/ssid.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Module for Pocket Option API ssid websocket chanel.""" - -from pocketoptionapi.ws.channels.base import Base - - -class Ssid(Base): - """Class for Pocket Option API ssid websocket chanel.""" - # pylint: disable=too-few-public-methods - - name = "ssid" - - def __call__(self, ssid): - """Method to send message to ssid websocket chanel. - - :param ssid: The session identifier. - """ - self.send_websocket_request(self.name, ssid) diff --git a/pocketoptionapi/ws/client.py b/pocketoptionapi/ws/client.py deleted file mode 100644 index a570c56..0000000 --- a/pocketoptionapi/ws/client.py +++ /dev/null @@ -1,271 +0,0 @@ -import asyncio -from datetime import datetime, timedelta, timezone - -import websockets -import json -import logging -import ssl - -# Suponiendo la existencia de estos mรณdulos basados en tu cรณdigo original -import pocketoptionapi.constants as OP_code -import pocketoptionapi.global_value as global_value -from pocketoptionapi.constants import REGION -from pocketoptionapi.ws.objects.timesync import TimeSync -from pocketoptionapi.ws.objects.time_sync import TimeSynchronizer - -logger = logging.getLogger(__name__) - -timesync = TimeSync() -sync = TimeSynchronizer() - - -async def on_open(): # pylint: disable=unused-argument - """Method to process websocket open.""" - print("CONNECTED SUCCESSFUL") - logger.debug("Websocket client connected.") - global_value.websocket_is_connected = True - - -async def send_ping(ws): - while global_value.websocket_is_connected is False: - await asyncio.sleep(0.1) - pass - while True: - await asyncio.sleep(20) - await ws.send('42["ps"]') - - -async def process_message(message): - try: - data = json.loads(message) - print(f"Received message: {data}") - - # Procesa el mensaje dependiendo del tipo - if isinstance(data, dict) and 'uid' in data: - uid = data['uid'] - print(f"UID: {uid}") - elif isinstance(data, list) and len(data) > 0: - event_type = data[0] - event_data = data[1] - print(f"Event type: {event_type}, Event data: {event_data}") - # Aquรญ puedes aรฑadir mรกs lรณgica para manejar diferentes tipos de eventos - - except json.JSONDecodeError as e: - print(f"JSON decode error: {e}") - except KeyError as e: - print(f"Key error: {e}") - except Exception as e: - print(f"Error processing message: {e}") - - -class WebsocketClient(object): - def __init__(self, api) -> None: - """ - Inicializa el cliente WebSocket. - - :param api: Instancia de la clase PocketOptionApi - """ - - self.updateHistoryNew = None - self.updateStream = None - self.history_data_ready = None - self.successCloseOrder = False - self.api = api - self.message = None - self.url = None - self.ssid = global_value.SSID - self.websocket = None - self.region = REGION() - self.loop = asyncio.get_event_loop() - self.wait_second_message = False - self._updateClosedDeals = False - - async def websocket_listener(self, ws): - try: - async for message in ws: - await self.on_message(message) - except Exception as e: - logging.warning(f"Error occurred: {e}") - - async def connect(self): - ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl.CERT_NONE - - try: - await self.api.close() - except: - pass - - while not global_value.websocket_is_connected: - for url in self.region.get_regions(True): - print(url) - try: - async with websockets.connect( - url, - ssl=ssl_context, - extra_headers={"Origin": "https://pocketoption.com", "Cache-Control": "no-cache"}, - user_agent_header="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, " - "like Gecko) Chrome/124.0.0.0 Safari/537.36" - ) as ws: - - # print("Connected a: ", url) - self.websocket = ws - self.url = url - global_value.websocket_is_connected = True - - # Crear y ejecutar tareas - # process_message_task = asyncio.create_task(process_message(self.message)) - on_message_task = asyncio.create_task(self.websocket_listener(ws)) - sender_task = asyncio.create_task(self.send_message(self.message)) - ping_task = asyncio.create_task(send_ping(ws)) - - await asyncio.gather(on_message_task, sender_task, ping_task) - - except websockets.ConnectionClosed as e: - global_value.websocket_is_connected = False - await self.on_close(e) - logger.warning("Trying another server") - - except Exception as e: - global_value.websocket_is_connected = False - await self.on_error(e) - - await asyncio.sleep(1) # Esperar antes de intentar reconectar - - return True - - async def send_message(self, message): - while global_value.websocket_is_connected is False: - await asyncio.sleep(0.1) - - self.message = message - - if global_value.websocket_is_connected and message is not None: - try: - await self.websocket.send(message) - except Exception as e: - logger.warning(f"Error sending message: {e}") - elif message is not None: - logger.warning("WebSocket not connected") - - @staticmethod - def dict_queue_add(self, dict, maxdict, key1, key2, key3, value): - if key3 in dict[key1][key2]: - dict[key1][key2][key3] = value - else: - while True: - try: - dic_size = len(dict[key1][key2]) - except: - dic_size = 0 - if dic_size < maxdict: - dict[key1][key2][key3] = value - break - else: - # del mini key - del dict[key1][key2][sorted(dict[key1][key2].keys(), reverse=False)[0]] - - async def on_message(self, message): # pylint: disable=unused-argument - """Method to process websocket messages.""" - # global_value.ssl_Mutual_exclusion = True - logger.debug(message) - - if type(message) is bytes: - message = message.decode('utf-8') - message = json.loads(message) - - # print(message, type(message)) - if "balance" in message: - if "uid" in message: - global_value.balance_id = message["uid"] - global_value.balance = message["balance"] - global_value.balance_type = message["isDemo"] - - elif "requestId" in message and message["requestId"] == 'buy': - global_value.order_data = message - - elif self.wait_second_message and isinstance(message, list): - self.wait_second_message = False # Restablecer para futuros mensajes - self._updateClosedDeals = False # Restablecer el estado - - elif isinstance(message, dict) and self.successCloseOrder: - self.api.order_async = message - self.successCloseOrder = False # Restablecer para futuros mensajes - - elif self.history_data_ready and isinstance(message, dict): - self.history_data_ready = False - self.api.history_data = message["data"] - - elif self.updateStream and isinstance(message, list): - self.updateStream = False - self.api.time_sync.server_timestamp = message[0][1] - - elif self.updateHistoryNew and isinstance(message, dict): - self.updateHistoryNew = False - self.api.historyNew = message - - return - - else: - pass - # print(message) - - if message.startswith('0') and "sid" in message: - await self.websocket.send("40") - - elif message == "2": - await self.websocket.send("3") - - elif "40" and "sid" in message: - await self.websocket.send(self.ssid) - - elif message.startswith('451-['): - json_part = message.split("-", 1)[1] # Eliminar el prefijo numรฉrico y el guion para obtener el JSON vรกlido - - # Convertir la parte JSON a un objeto Python - message = json.loads(json_part) - - if message[0] == "successauth": - await on_open() - - elif message[0] == "successupdateBalance": - global_value.balance_updated = True - elif message[0] == "successopenOrder": - global_value.result = True - - # Si es el primer mensaje de interรฉs - elif message[0] == "updateClosedDeals": - # Establecemos que hemos recibido el primer mensaje de interรฉs - self._updateClosedDeals = True - self.wait_second_message = True # Establecemos que esperamos el segundo mensaje de interรฉs - await self.websocket.send('42["changeSymbol",{"asset":"AUDNZD_otc","period":60}]') - - elif message[0] == "successcloseOrder": - self.successCloseOrder = True - self.wait_second_message = True # Establecemos que esperamos el segundo mensaje de interรฉs - - elif message[0] == "loadHistoryPeriod": - self.history_data_ready = True - - elif message[0] == "updateStream": - self.updateStream = True - - elif message[0] == "updateHistoryNew": - self.updateHistoryNew = True - # self.api.historyNew = None - - elif message.startswith("42") and "NotAuthorized" in message: - logging.error("User not Authorized: Please Change SSID for one valid") - global_value.ssl_Mutual_exclusion = False - await self.websocket.close() - - async def on_error(self, error): # pylint: disable=unused-argument - logger.error(error) - global_value.websocket_error_reason = str(error) - global_value.check_websocket_if_error = True - - async def on_close(self, error): # pylint: disable=unused-argument - # logger.debug("Websocket connection closed.") - # logger.warning(f"Websocket connection closed. Reason: {error}") - global_value.websocket_is_connected = False diff --git a/pocketoptionapi/ws/objects/__pycache__/base.cpython-310.pyc b/pocketoptionapi/ws/objects/__pycache__/base.cpython-310.pyc deleted file mode 100644 index 277920a..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/base.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/base.cpython-311.pyc b/pocketoptionapi/ws/objects/__pycache__/base.cpython-311.pyc deleted file mode 100644 index 0503f2b..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/base.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/base.cpython-312.pyc b/pocketoptionapi/ws/objects/__pycache__/base.cpython-312.pyc deleted file mode 100644 index 77965a6..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/base.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/base.cpython-37.pyc b/pocketoptionapi/ws/objects/__pycache__/base.cpython-37.pyc deleted file mode 100644 index dbb7ac8..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/base.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/base.cpython-38.pyc b/pocketoptionapi/ws/objects/__pycache__/base.cpython-38.pyc deleted file mode 100644 index 6ce24aa..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/base.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/base.cpython-39.pyc b/pocketoptionapi/ws/objects/__pycache__/base.cpython-39.pyc deleted file mode 100644 index cd49bfb..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/base.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/candles.cpython-310.pyc b/pocketoptionapi/ws/objects/__pycache__/candles.cpython-310.pyc deleted file mode 100644 index ed21b27..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/candles.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/candles.cpython-311.pyc b/pocketoptionapi/ws/objects/__pycache__/candles.cpython-311.pyc deleted file mode 100644 index 1a6608a..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/candles.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/candles.cpython-312.pyc b/pocketoptionapi/ws/objects/__pycache__/candles.cpython-312.pyc deleted file mode 100644 index 92bacfb..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/candles.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/candles.cpython-37.pyc b/pocketoptionapi/ws/objects/__pycache__/candles.cpython-37.pyc deleted file mode 100644 index ef7c9cf..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/candles.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/candles.cpython-38.pyc b/pocketoptionapi/ws/objects/__pycache__/candles.cpython-38.pyc deleted file mode 100644 index 2c911e6..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/candles.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/candles.cpython-39.pyc b/pocketoptionapi/ws/objects/__pycache__/candles.cpython-39.pyc deleted file mode 100644 index 44d0c72..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/candles.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-310.pyc b/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-310.pyc deleted file mode 100644 index c7b67a4..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-311.pyc b/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-311.pyc deleted file mode 100644 index 6aa4d02..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-312.pyc b/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-312.pyc deleted file mode 100644 index e4739d5..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-37.pyc b/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-37.pyc deleted file mode 100644 index bb26b23..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-38.pyc b/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-38.pyc deleted file mode 100644 index 5f4a5cf..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-310.pyc b/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-310.pyc deleted file mode 100644 index 1d5ad67..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-310.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-311.pyc b/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-311.pyc deleted file mode 100644 index 0f1d11f..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-311.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-312.pyc b/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-312.pyc deleted file mode 100644 index ee8bb76..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-312.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-37.pyc b/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-37.pyc deleted file mode 100644 index 02aa6eb..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-37.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-38.pyc b/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-38.pyc deleted file mode 100644 index b011954..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-38.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-39.pyc b/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-39.pyc deleted file mode 100644 index c89d48d..0000000 Binary files a/pocketoptionapi/ws/objects/__pycache__/timesync.cpython-39.pyc and /dev/null differ diff --git a/pocketoptionapi/ws/objects/base.py b/pocketoptionapi/ws/objects/base.py deleted file mode 100644 index 60c6d5d..0000000 --- a/pocketoptionapi/ws/objects/base.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Module for Pocket Option Base websocket object.""" - - -class Base(object): - """Class for Pocket Option Base websocket object.""" - # pylint: disable=too-few-public-methods - - def __init__(self): - self.__name = None - - @property - def name(self): - """Property to get websocket object name. - - :returns: The name of websocket object. - """ - return self.__name diff --git a/pocketoptionapi/ws/objects/candles.py b/pocketoptionapi/ws/objects/candles.py deleted file mode 100644 index cc022e8..0000000 --- a/pocketoptionapi/ws/objects/candles.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Module for Pocket Option Candles websocket object.""" - -from pocketoptionapi.ws.objects.base import Base - - -class Candle(object): - """Class for Pocket Option candle.""" - - def __init__(self, candle_data): - """ - :param candle_data: The list of candles data. - """ - self.__candle_data = candle_data - - @property - def candle_time(self): - """Property to get candle time. - - :returns: The candle time. - """ - return self.__candle_data[0] - - @property - def candle_open(self): - """Property to get candle open value. - - :returns: The candle open value. - """ - return self.__candle_data[1] - - @property - def candle_close(self): - """Property to get candle close value. - - :returns: The candle close value. - """ - return self.__candle_data[2] - - @property - def candle_high(self): - """Property to get candle high value. - - :returns: The candle high value. - """ - return self.__candle_data[3] - - @property - def candle_low(self): - """Property to get candle low value. - - :returns: The candle low value. - """ - return self.__candle_data[4] - - @property - def candle_type(self): # pylint: disable=inconsistent-return-statements - """Property to get candle type value. - - :returns: The candle type value. - """ - if self.candle_open < self.candle_close: - return "green" - elif self.candle_open > self.candle_close: - return "red" - - -class Candles(Base): - """Class for Pocket Option Candles websocket object.""" - - def __init__(self): - super(Candles, self).__init__() - self.__name = "candles" - self.__candles_data = None - - @property - def candles_data(self): - """Property to get candles data. - - :returns: The list of candles data. - """ - return self.__candles_data - - @candles_data.setter - def candles_data(self, candles_data): - """Method to set candles data.""" - self.__candles_data = candles_data - - @property - def first_candle(self): - """Method to get first candle. - - :returns: The instance of :class:`Candle - `. - """ - return Candle(self.candles_data[0]) - - @property - def second_candle(self): - """Method to get second candle. - - :returns: The instance of :class:`Candle - `. - """ - return Candle(self.candles_data[1]) - - @property - def current_candle(self): - """Method to get current candle. - - :returns: The instance of :class:`Candle - `. - """ - return Candle(self.candles_data[-1]) diff --git a/pocketoptionapi/ws/objects/time_sync.py b/pocketoptionapi/ws/objects/time_sync.py deleted file mode 100644 index e5d7995..0000000 --- a/pocketoptionapi/ws/objects/time_sync.py +++ /dev/null @@ -1,70 +0,0 @@ -import logging -import time -from datetime import datetime, timedelta, timezone - - -class TimeSynchronizer: - def __init__(self): - self.server_time_reference = None - self.local_time_reference = None - self.timezone_offset = timedelta(seconds=self._get_local_timezone_offset()) - - @staticmethod - def _get_local_timezone_offset(): - """ - Obtiene el desplazamiento de la zona horaria local en segundos. - - :return: Desplazamiento de la zona horaria local en segundos. - """ - local_time = datetime.now() - utc_time = datetime.utcnow() - offset = (local_time - utc_time).total_seconds() - return offset - - def synchronize(self, server_time): - """ - Sincroniza el tiempo local con el tiempo del servidor. - - :param server_time: Tiempo del servidor en segundos (puede ser un timestamp). - """ - - self.server_time_reference = server_time - self.local_time_reference = time.time() - - def get_synced_time(self): - """ - Obtiene el tiempo sincronizado basado en el tiempo actual del sistema. - - :return: Tiempo sincronizado en segundos. - """ - if self.server_time_reference is None or self.local_time_reference is None: - raise ValueError("El tiempo no ha sido sincronizado aรบn.") - - # Calcula la diferencia de tiempo desde la รบltima sincronizaciรณn - elapsed_time = time.time() - self.local_time_reference - # Calcula el tiempo sincronizado - synced_time = self.server_time_reference + elapsed_time - return synced_time - - def get_synced_datetime(self): - """ - Convierte el tiempo sincronizado a un objeto datetime ajustado a la zona horaria local. - - :return: Tiempo sincronizado como un objeto datetime. - """ - synced_time_seconds = self.get_synced_time() - # Redondear los segundos - rounded_time_seconds = round(synced_time_seconds) - # Convertir a datetime en UTC - synced_datetime_utc = datetime.fromtimestamp(rounded_time_seconds, tz=timezone.utc) - # Ajustar el tiempo sincronizado a la zona horaria local - synced_datetime_local = synced_datetime_utc + self.timezone_offset - return synced_datetime_local - - def update_sync(self, new_server_time): - """ - Actualiza la sincronizaciรณn con un nuevo tiempo del servidor. - - :param new_server_time: Nuevo tiempo del servidor en segundos. - """ - self.synchronize(new_server_time) diff --git a/pocketoptionapi/ws/objects/timesync.py b/pocketoptionapi/ws/objects/timesync.py deleted file mode 100644 index 34bed24..0000000 --- a/pocketoptionapi/ws/objects/timesync.py +++ /dev/null @@ -1,71 +0,0 @@ -"""Module for Pocket Option TimeSync websocket object.""" - -import time -import datetime - -from pocketoptionapi.ws.objects.base import Base - - -class TimeSync(Base): - """Class for Pocket Option TimeSync websocket object.""" - - def __init__(self): - super(TimeSync, self).__init__() - self.__name = "timeSync" - self.__server_timestamp = time.time() - self.__expiration_time = 1 - - @property - def server_timestamp(self): - """Property to get server timestamp. - - :returns: The server timestamp. - """ - return self.__server_timestamp - - @server_timestamp.setter - def server_timestamp(self, timestamp): - """Method to set server timestamp.""" - self.__server_timestamp = timestamp - - @property - def server_datetime(self): - """Property to get server datetime. - - :returns: The server datetime. - """ - return datetime.datetime.fromtimestamp(self.server_timestamp) - - @property - def expiration_time(self): - """Property to get expiration time. - - :returns: The expiration time. - """ - return self.__expiration_time - - @expiration_time.setter - def expiration_time(self, minutes): - """Method to set expiration time - - :param int minutes: The expiration time in minutes. - """ - self.__expiration_time = minutes - - @property - def expiration_datetime(self): - """Property to get expiration datetime. - - :returns: The expiration datetime. - """ - return self.server_datetime + datetime.timedelta(minutes=self.expiration_time) - - @property - def expiration_timestamp(self): - """Property to get expiration timestamp. - - :returns: The expiration timestamp. - """ - return time.mktime(self.expiration_datetime.timetuple()) - - diff --git a/pocketoptionapi_async/__init__.py b/pocketoptionapi_async/__init__.py index bb051e9..36d6164 100644 --- a/pocketoptionapi_async/__init__.py +++ b/pocketoptionapi_async/__init__.py @@ -11,7 +11,7 @@ OrderError, TimeoutError, InvalidParameterError, - WebSocketError + WebSocketError, ) from .models import ( Balance, @@ -21,13 +21,20 @@ OrderStatus, OrderDirection, Asset, - ConnectionStatus + ConnectionStatus, ) from .constants import ASSETS, Regions + # Import monitoring components from .monitoring import ( - ErrorMonitor, HealthChecker, ErrorSeverity, ErrorCategory, - CircuitBreaker, RetryPolicy, error_monitor, health_checker + ErrorMonitor, + HealthChecker, + ErrorSeverity, + ErrorCategory, + CircuitBreaker, + RetryPolicy, + error_monitor, + health_checker, ) # Create REGIONS instance @@ -39,7 +46,7 @@ __all__ = [ "AsyncPocketOptionClient", "PocketOptionError", - "ConnectionError", + "ConnectionError", "AuthenticationError", "OrderError", "TimeoutError", @@ -56,6 +63,12 @@ "ASSETS", "REGIONS", # Monitoring and error handling - 'ErrorMonitor', 'HealthChecker', 'ErrorSeverity', 'ErrorCategory', - 'CircuitBreaker', 'RetryPolicy', 'error_monitor', 'health_checker', + "ErrorMonitor", + "HealthChecker", + "ErrorSeverity", + "ErrorCategory", + "CircuitBreaker", + "RetryPolicy", + "error_monitor", + "health_checker", ] diff --git a/pocketoptionapi_async/client.py b/pocketoptionapi_async/client.py index f3b298c..ee1cc8d 100644 --- a/pocketoptionapi_async/client.py +++ b/pocketoptionapi_async/client.py @@ -15,13 +15,21 @@ from .monitoring import error_monitor, health_checker, ErrorCategory, ErrorSeverity from .websocket_client import AsyncWebSocketClient from .models import ( - Balance, Candle, Order, OrderResult, OrderStatus, OrderDirection, - ConnectionStatus, ServerTime + Balance, + Candle, + Order, + OrderResult, + OrderStatus, + OrderDirection, + ServerTime, ) from .constants import ASSETS, REGIONS, TIMEFRAMES, API_LIMITS from .exceptions import ( - PocketOptionError, ConnectionError, AuthenticationError, - OrderError, TimeoutError, InvalidParameterError + PocketOptionError, + ConnectionError, + AuthenticationError, + OrderError, + InvalidParameterError, ) @@ -29,14 +37,22 @@ class AsyncPocketOptionClient: """ Professional async PocketOption API client with modern Python practices """ - - def __init__(self, ssid: str, is_demo: bool = True, region: Optional[str] = None, - uid: int = 0, platform: int = 1, is_fast_history: bool = True, - persistent_connection: bool = False, auto_reconnect: bool = True, - enable_logging: bool = True): + + def __init__( + self, + ssid: str, + is_demo: bool = True, + region: Optional[str] = None, + uid: int = 0, + platform: int = 1, + is_fast_history: bool = True, + persistent_connection: bool = False, + auto_reconnect: bool = True, + enable_logging: bool = True, + ): """ Initialize async PocketOption client with enhanced monitoring - + Args: ssid: Complete SSID string or raw session ID for authentication is_demo: Whether to use demo account @@ -57,12 +73,12 @@ def __init__(self, ssid: str, is_demo: bool = True, region: Optional[str] = None self.persistent_connection = persistent_connection self.auto_reconnect = auto_reconnect self.enable_logging = enable_logging - + # Configure logging based on preference if not enable_logging: logger.remove() logger.add(lambda msg: None, level="CRITICAL") # Disable most logging - # Parse SSID if it's a complete auth message + # Parse SSID if it's a complete auth message self._original_demo = None # Store original demo value from SSID if ssid.startswith('42["auth",'): self._parse_complete_ssid(ssid) @@ -70,7 +86,7 @@ def __init__(self, ssid: str, is_demo: bool = True, region: Optional[str] = None # Treat as raw session ID self.session_id = ssid self._complete_ssid = None - + # Core components self._websocket = AsyncWebSocketClient() self._balance: Optional[Balance] = None @@ -80,86 +96,96 @@ def __init__(self, ssid: str, is_demo: bool = True, region: Optional[str] = None self._candles_cache: Dict[str, List[Candle]] = {} self._server_time: Optional[ServerTime] = None self._event_callbacks: Dict[str, List[Callable]] = defaultdict(list) - # Setup event handlers for websocket messages + # Setup event handlers for websocket messages self._setup_event_handlers() - + # Add handler for JSON data messages (contains detailed order data) - self._websocket.add_event_handler('json_data', self._on_json_data) - # Enhanced monitoring and error handling - from .monitoring import error_monitor, health_checker + self._websocket.add_event_handler("json_data", self._on_json_data) + # Enhanced monitoring and error handling + self._error_monitor = error_monitor self._health_checker = health_checker - + # Performance tracking self._operation_metrics: Dict[str, List[float]] = defaultdict(list) self._last_health_check = time.time() - + # Keep-alive functionality (based on old API patterns) self._keep_alive_manager = None self._ping_task: Optional[asyncio.Task] = None self._reconnect_task: Optional[asyncio.Task] = None self._is_persistent = False - + # Connection statistics (like old API) self._connection_stats = { - 'total_connections': 0, - 'successful_connections': 0, - 'total_reconnects': 0, - 'last_ping_time': None, - 'messages_sent': 0, - 'messages_received': 0, - 'connection_start_time': None + "total_connections": 0, + "successful_connections": 0, + "total_reconnects": 0, + "last_ping_time": None, + "messages_sent": 0, + "messages_received": 0, + "connection_start_time": None, } - - logger.info(f"Initialized PocketOption client (demo={is_demo}, uid={self.uid}, persistent={persistent_connection}) with enhanced monitoring" if enable_logging else "") + + logger.info( + f"Initialized PocketOption client (demo={is_demo}, uid={self.uid}, persistent={persistent_connection}) with enhanced monitoring" + if enable_logging + else "" + ) def _setup_event_handlers(self): """Setup WebSocket event handlers""" - self._websocket.add_event_handler('authenticated', self._on_authenticated) - self._websocket.add_event_handler('balance_updated', self._on_balance_updated) - self._websocket.add_event_handler('balance_data', self._on_balance_data) # Add balance_data handler - self._websocket.add_event_handler('order_opened', self._on_order_opened) - self._websocket.add_event_handler('order_closed', self._on_order_closed) - self._websocket.add_event_handler('stream_update', self._on_stream_update) - self._websocket.add_event_handler('candles_received', self._on_candles_received) - self._websocket.add_event_handler('disconnected', self._on_disconnected) - - async def connect(self, regions: Optional[List[str]] = None, persistent: bool = None) -> bool: + self._websocket.add_event_handler("authenticated", self._on_authenticated) + self._websocket.add_event_handler("balance_updated", self._on_balance_updated) + self._websocket.add_event_handler( + "balance_data", self._on_balance_data + ) # Add balance_data handler + self._websocket.add_event_handler("order_opened", self._on_order_opened) + self._websocket.add_event_handler("order_closed", self._on_order_closed) + self._websocket.add_event_handler("stream_update", self._on_stream_update) + self._websocket.add_event_handler("candles_received", self._on_candles_received) + self._websocket.add_event_handler("disconnected", self._on_disconnected) + + async def connect( + self, regions: Optional[List[str]] = None, persistent: bool = None + ) -> bool: """ Connect to PocketOption with multiple region support - + Args: regions: List of regions to try (uses defaults if None) persistent: Override persistent connection setting - + Returns: bool: True if connected successfully """ - logger.info("๐Ÿ”Œ Connecting to PocketOption...") - # Update persistent setting if provided + logger.info("Connecting to PocketOption...") + # Update persistent setting if provided if persistent is not None: self.persistent_connection = persistent - + try: if self.persistent_connection: return await self._start_persistent_connection(regions) else: return await self._start_regular_connection(regions) - + except Exception as e: logger.error(f"Connection failed: {e}") await self._error_monitor.record_error( error_type="connection_failed", severity=ErrorSeverity.HIGH, category=ErrorCategory.CONNECTION, - message=f"Connection failed: {e}" + message=f"Connection failed: {e}", ) return False - async def _start_regular_connection(self, regions: Optional[List[str]] = None) -> bool: + async def _start_regular_connection( + self, regions: Optional[List[str]] = None + ) -> bool: """Start regular connection (existing behavior)""" logger.info("Starting regular connection...") - # Use appropriate regions based on demo mode + # Use appropriate regions based on demo mode if not regions: if self.is_demo: # For demo mode, only use demo regions @@ -173,90 +199,114 @@ async def _start_regular_connection(self, regions: Optional[List[str]] = None) - else: # For live mode, use all regions except demo all_regions = REGIONS.get_all_regions() - regions = [name for name, url in all_regions.items() if "DEMO" not in name.upper()] + regions = [ + name + for name, url in all_regions.items() + if "DEMO" not in name.upper() + ] logger.info(f"Live mode: Using non-demo regions: {regions}") - # Update connection stats - self._connection_stats['total_connections'] += 1 - self._connection_stats['connection_start_time'] = time.time() - + # Update connection stats + self._connection_stats["total_connections"] += 1 + self._connection_stats["connection_start_time"] = time.time() + for region in regions: try: region_url = REGIONS.get_region(region) if not region_url: continue - + urls = [region_url] # Convert single URL to list logger.info(f"Trying region: {region} with URL: {region_url}") - + # Try to connect ssid_message = self._format_session_message() success = await self._websocket.connect(urls, ssid_message) - + if success: - logger.info(f"โœ… Connected to region: {region}") - + logger.info(f" Connected to region: {region}") + # Wait for authentication await self._wait_for_authentication() - + # Initialize data await self._initialize_data() - + # Start keep-alive tasks await self._start_keep_alive_tasks() - - self._connection_stats['successful_connections'] += 1 + + self._connection_stats["successful_connections"] += 1 logger.info("Successfully connected and authenticated") return True - + except Exception as e: logger.warning(f"Failed to connect to region {region}: {e}") continue - + return False - async def _start_persistent_connection(self, regions: Optional[List[str]] = None) -> bool: + async def _start_persistent_connection( + self, regions: Optional[List[str]] = None + ) -> bool: """Start persistent connection with keep-alive (like old API)""" - logger.info("๐Ÿš€ Starting persistent connection with automatic keep-alive...") - + logger.info("Starting persistent connection with automatic keep-alive...") + # Import the keep-alive manager from .connection_keep_alive import ConnectionKeepAlive - + # Create keep-alive manager complete_ssid = self.raw_ssid self._keep_alive_manager = ConnectionKeepAlive(complete_ssid, self.is_demo) - + # Add event handlers - self._keep_alive_manager.add_event_handler('connected', self._on_keep_alive_connected) - self._keep_alive_manager.add_event_handler('reconnected', self._on_keep_alive_reconnected) - self._keep_alive_manager.add_event_handler('message_received', self._on_keep_alive_message) - + self._keep_alive_manager.add_event_handler( + "connected", self._on_keep_alive_connected + ) + self._keep_alive_manager.add_event_handler( + "reconnected", self._on_keep_alive_reconnected + ) + self._keep_alive_manager.add_event_handler( + "message_received", self._on_keep_alive_message + ) + # Add handlers for forwarded WebSocket events - self._keep_alive_manager.add_event_handler('balance_data', self._on_balance_data) - self._keep_alive_manager.add_event_handler('balance_updated', self._on_balance_updated) - self._keep_alive_manager.add_event_handler('authenticated', self._on_authenticated) - self._keep_alive_manager.add_event_handler('order_opened', self._on_order_opened) - self._keep_alive_manager.add_event_handler('order_closed', self._on_order_closed) - self._keep_alive_manager.add_event_handler('stream_update', self._on_stream_update) - self._keep_alive_manager.add_event_handler('json_data', self._on_json_data) - + self._keep_alive_manager.add_event_handler( + "balance_data", self._on_balance_data + ) + self._keep_alive_manager.add_event_handler( + "balance_updated", self._on_balance_updated + ) + self._keep_alive_manager.add_event_handler( + "authenticated", self._on_authenticated + ) + self._keep_alive_manager.add_event_handler( + "order_opened", self._on_order_opened + ) + self._keep_alive_manager.add_event_handler( + "order_closed", self._on_order_closed + ) + self._keep_alive_manager.add_event_handler( + "stream_update", self._on_stream_update + ) + self._keep_alive_manager.add_event_handler("json_data", self._on_json_data) + # Connect with keep-alive success = await self._keep_alive_manager.connect_with_keep_alive(regions) - + if success: self._is_persistent = True - logger.info("โœ… Persistent connection established successfully") + logger.info(" Persistent connection established successfully") return True else: - logger.error("โŒ Failed to establish persistent connection") + logger.error("Failed to establish persistent connection") return False async def _start_keep_alive_tasks(self): """Start keep-alive tasks for regular connection""" - logger.info("๐Ÿ”„ Starting keep-alive tasks for regular connection...") - + logger.info("Starting keep-alive tasks for regular connection...") + # Start ping task (like old API) self._ping_task = asyncio.create_task(self._ping_loop()) - + # Start reconnection monitor if auto_reconnect is enabled if self.auto_reconnect: self._reconnect_task = asyncio.create_task(self._reconnection_monitor()) @@ -266,7 +316,7 @@ async def _ping_loop(self): while self.is_connected and not self._is_persistent: try: await self._websocket.send_message('42["ps"]') - self._connection_stats['last_ping_time'] = time.time() + self._connection_stats["last_ping_time"] = time.time() await asyncio.sleep(20) # Ping every 20 seconds except Exception as e: logger.warning(f"Ping failed: {e}") @@ -276,17 +326,17 @@ async def _reconnection_monitor(self): """Monitor and handle reconnections for regular connections""" while self.auto_reconnect and not self._is_persistent: await asyncio.sleep(30) # Check every 30 seconds - + if not self.is_connected: - logger.info("๐Ÿ”„ Connection lost, attempting reconnection...") - self._connection_stats['total_reconnects'] += 1 - + logger.info("Connection lost, attempting reconnection...") + self._connection_stats["total_reconnects"] += 1 + try: success = await self._start_regular_connection() if success: - logger.info("โœ… Reconnection successful") + logger.info(" Reconnection successful") else: - logger.error("โŒ Reconnection failed") + logger.error("Reconnection failed") await asyncio.sleep(10) # Wait before next attempt except Exception as e: logger.error(f"Reconnection error: {e}") @@ -294,68 +344,72 @@ async def _reconnection_monitor(self): async def disconnect(self) -> None: """Disconnect from PocketOption and cleanup all resources""" - logger.info("๐Ÿ”Œ Disconnecting from PocketOption...") - + logger.info("Disconnecting from PocketOption...") + # Cancel tasks if self._ping_task: self._ping_task.cancel() if self._reconnect_task: self._reconnect_task.cancel() - + # Disconnect based on connection type if self._is_persistent and self._keep_alive_manager: await self._keep_alive_manager.disconnect() else: await self._websocket.disconnect() - + # Reset state self._is_persistent = False self._balance = None self._orders.clear() - + logger.info("Disconnected successfully") async def get_balance(self) -> Balance: """ Get current account balance - + Returns: Balance: Current balance information """ if not self.is_connected: raise ConnectionError("Not connected to PocketOption") - + # Request balance update if needed - if not self._balance or (datetime.now() - self._balance.last_updated).seconds > 60: + if ( + not self._balance + or (datetime.now() - self._balance.last_updated).seconds > 60 + ): await self._request_balance_update() - + # Wait a bit for balance to be received await asyncio.sleep(1) - + if not self._balance: raise PocketOptionError("Balance data not available") - + return self._balance - async def place_order(self, asset: str, amount: float, direction: OrderDirection, - duration: int) -> OrderResult: + async def place_order( + self, asset: str, amount: float, direction: OrderDirection, duration: int + ) -> OrderResult: """ Place a binary options order - + Args: asset: Asset symbol (e.g., "EURUSD_otc") amount: Order amount direction: OrderDirection.CALL or OrderDirection.PUT duration: Duration in seconds - + Returns: OrderResult: Order placement result """ if not self.is_connected: raise ConnectionError("Not connected to PocketOption") - # Validate parameters + # Validate parameters self._validate_order_parameters(asset, amount, direction, duration) - + try: # Create order order_id = str(uuid.uuid4()) @@ -364,146 +418,168 @@ async def place_order(self, asset: str, amount: float, direction: OrderDirection amount=amount, direction=direction, duration=duration, - request_id=order_id # Use request_id, not order_id - ) # Send order + request_id=order_id, # Use request_id, not order_id + ) # Send order await self._send_order(order) - + # Wait for result (this will either get the real server response or create a fallback) result = await self._wait_for_order_result(order_id, order) - + # Don't store again - _wait_for_order_result already handles storage logger.info(f"Order placed: {result.order_id} - {result.status}") return result - + except Exception as e: logger.error(f"Order placement failed: {e}") raise OrderError(f"Failed to place order: {e}") - async def get_candles(self, asset: str, timeframe: Union[str, int], - count: int = 100, end_time: Optional[datetime] = None) -> List[Candle]: + async def get_candles( + self, + asset: str, + timeframe: Union[str, int], + count: int = 100, + end_time: Optional[datetime] = None, + ) -> List[Candle]: """ Get historical candle data with automatic reconnection - + Args: asset: Asset symbol timeframe: Timeframe (e.g., "1m", "5m", 60) count: Number of candles to retrieve end_time: End time for data (defaults to now) - + Returns: List[Candle]: Historical candle data """ # Check connection and attempt reconnection if needed if not self.is_connected: if self.auto_reconnect: - logger.info(f"๐Ÿ”„ Connection lost, attempting reconnection for {asset} candles...") + logger.info( + f"Connection lost, attempting reconnection for {asset} candles..." + ) reconnected = await self._attempt_reconnection() if not reconnected: - raise ConnectionError("Not connected to PocketOption and reconnection failed") + raise ConnectionError( + "Not connected to PocketOption and reconnection failed" + ) else: raise ConnectionError("Not connected to PocketOption") - + # Convert timeframe to seconds if isinstance(timeframe, str): timeframe_seconds = TIMEFRAMES.get(timeframe, 60) else: timeframe_seconds = timeframe - + # Validate asset if asset not in ASSETS: raise InvalidParameterError(f"Invalid asset: {asset}") - + # Set default end time if not end_time: end_time = datetime.now() - + max_retries = 2 for attempt in range(max_retries): try: # Request candle data - candles = await self._request_candles(asset, timeframe_seconds, count, end_time) - + candles = await self._request_candles( + asset, timeframe_seconds, count, end_time + ) + # Cache results cache_key = f"{asset}_{timeframe_seconds}" self._candles_cache[cache_key] = candles - + logger.info(f"Retrieved {len(candles)} candles for {asset}") return candles - + except Exception as e: if "WebSocket is not connected" in str(e) and attempt < max_retries - 1: - logger.warning(f"๐Ÿ”„ Connection lost during candle request for {asset}, attempting reconnection...") + logger.warning( + f"Connection lost during candle request for {asset}, attempting reconnection..." + ) if self.auto_reconnect: reconnected = await self._attempt_reconnection() if reconnected: - logger.info(f"โœ… Reconnected, retrying candle request for {asset}") + logger.info( + f" Reconnected, retrying candle request for {asset}" + ) continue - + logger.error(f"Failed to get candles for {asset}: {e}") raise PocketOptionError(f"Failed to get candles: {e}") - + raise PocketOptionError(f"Failed to get candles after {max_retries} attempts") - async def get_candles_dataframe(self, asset: str, timeframe: Union[str, int], - count: int = 100, end_time: Optional[datetime] = None) -> pd.DataFrame: + async def get_candles_dataframe( + self, + asset: str, + timeframe: Union[str, int], + count: int = 100, + end_time: Optional[datetime] = None, + ) -> pd.DataFrame: """ Get historical candle data as DataFrame - + Args: asset: Asset symbol timeframe: Timeframe (e.g., "1m", "5m", 60) count: Number of candles to retrieve end_time: End time for data (defaults to now) - + Returns: pd.DataFrame: Historical candle data """ candles = await self.get_candles(asset, timeframe, count, end_time) - + # Convert to DataFrame data = [] for candle in candles: - data.append({ - 'timestamp': candle.timestamp, - 'open': candle.open, - 'high': candle.high, - 'low': candle.low, - 'close': candle.close, - 'volume': candle.volume - }) + data.append( + { + "timestamp": candle.timestamp, + "open": candle.open, + "high": candle.high, + "low": candle.low, + "close": candle.close, + "volume": candle.volume, + } + ) df = pd.DataFrame(data) - + if not df.empty: - df.set_index('timestamp', inplace=True) + df.set_index("timestamp", inplace=True) df.sort_index(inplace=True) - + return df async def check_order_result(self, order_id: str) -> Optional[OrderResult]: """ Check the result of a specific order - + Args: order_id: Order ID to check - + Returns: OrderResult: Order result or None if not found """ # First check active orders if order_id in self._active_orders: return self._active_orders[order_id] - + # Then check completed orders if order_id in self._order_results: return self._order_results[order_id] - + # Not found return None async def get_active_orders(self) -> List[OrderResult]: """ Get all active orders - + Returns: List[OrderResult]: Active orders """ @@ -512,7 +588,7 @@ async def get_active_orders(self) -> List[OrderResult]: def add_event_callback(self, event: str, callback: Callable) -> None: """ Add event callback - + Args: event: Event name (e.g., 'order_closed', 'balance_updated') callback: Callback function @@ -524,7 +600,7 @@ def add_event_callback(self, event: str, callback: Callable) -> None: def remove_event_callback(self, event: str, callback: Callable) -> None: """ Remove event callback - + Args: event: Event name callback: Callback function to remove @@ -566,17 +642,19 @@ async def send_message(self, message: str) -> bool: def get_connection_stats(self) -> Dict[str, Any]: """Get comprehensive connection statistics""" stats = self._connection_stats.copy() - + if self._is_persistent and self._keep_alive_manager: stats.update(self._keep_alive_manager.get_stats()) else: - stats.update({ - 'websocket_connected': self._websocket.is_connected, - 'connection_info': self._websocket.connection_info - }) - - return stats # Private methods - + stats.update( + { + "websocket_connected": self._websocket.is_connected, + "connection_info": self._websocket.connection_info, + } + ) + + return stats # Private methods + def _format_session_message(self) -> str: """Format session authentication message""" # Always create auth message from components using constructor parameters @@ -585,30 +663,30 @@ def _format_session_message(self) -> str: "session": self.session_id, "isDemo": 1 if self.is_demo else 0, "uid": self.uid, - "platform": self.platform + "platform": self.platform, } - + if self.is_fast_history: auth_data["isFastHistory"] = True - + return f'42["auth",{json.dumps(auth_data)}]' def _parse_complete_ssid(self, ssid: str) -> None: """Parse complete SSID auth message to extract components""" try: # Extract JSON part - json_start = ssid.find('{') - json_end = ssid.rfind('}') + 1 + json_start = ssid.find("{") + json_end = ssid.rfind("}") + 1 if json_start != -1 and json_end > json_start: json_part = ssid[json_start:json_end] data = json.loads(json_part) - - self.session_id = data.get('session', '') + + self.session_id = data.get("session", "") # Store original demo value from SSID, but don't override the constructor parameter - self._original_demo = bool(data.get('isDemo', 1)) + self._original_demo = bool(data.get("isDemo", 1)) # Keep the is_demo value from constructor - don't override it - self.uid = data.get('uid', 0) - self.platform = data.get('platform', 1) + self.uid = data.get("uid", 0) + self.platform = data.get("platform", 1) # Don't store complete SSID - we'll reconstruct it with correct demo value self._complete_ssid = None except Exception as e: @@ -619,39 +697,39 @@ def _parse_complete_ssid(self, ssid: str) -> None: async def _wait_for_authentication(self, timeout: float = 10.0) -> None: """Wait for authentication to complete (like old API)""" auth_received = False - + def on_auth(data): nonlocal auth_received auth_received = True - + # Add temporary handler - self._websocket.add_event_handler('authenticated', on_auth) - + self._websocket.add_event_handler("authenticated", on_auth) + try: # Wait for authentication start_time = time.time() while not auth_received and (time.time() - start_time) < timeout: await asyncio.sleep(0.1) - + if not auth_received: raise AuthenticationError("Authentication timeout") - + finally: # Remove temporary handler - self._websocket.remove_event_handler('authenticated', on_auth) + self._websocket.remove_event_handler("authenticated", on_auth) async def _initialize_data(self) -> None: """Initialize client data after connection""" # Request initial balance await self._request_balance_update() - + # Setup time synchronization await self._setup_time_sync() async def _request_balance_update(self) -> None: """Request balance update from server""" message = '42["getBalance"]' - + # Use appropriate connection method if self._is_persistent and self._keep_alive_manager: await self._keep_alive_manager.send_message(message) @@ -664,22 +742,28 @@ async def _setup_time_sync(self) -> None: # For now, create a basic time sync object local_time = datetime.now().timestamp() self._server_time = ServerTime( - server_timestamp=local_time, - local_timestamp=local_time, - offset=0.0 + server_timestamp=local_time, local_timestamp=local_time, offset=0.0 ) - def _validate_order_parameters(self, asset: str, amount: float, - direction: OrderDirection, duration: int) -> None: + def _validate_order_parameters( + self, asset: str, amount: float, direction: OrderDirection, duration: int + ) -> None: """Validate order parameters""" if asset not in ASSETS: raise InvalidParameterError(f"Invalid asset: {asset}") - - if amount < API_LIMITS['min_order_amount'] or amount > API_LIMITS['max_order_amount']: - raise InvalidParameterError( f"Amount must be between {API_LIMITS['min_order_amount']} and {API_LIMITS['max_order_amount']}" + + if ( + amount < API_LIMITS["min_order_amount"] + or amount > API_LIMITS["max_order_amount"] + ): + raise InvalidParameterError( + f"Amount must be between {API_LIMITS['min_order_amount']} and {API_LIMITS['max_order_amount']}" ) - - if duration < API_LIMITS['min_duration'] or duration > API_LIMITS['max_duration']: + + if ( + duration < API_LIMITS["min_duration"] + or duration > API_LIMITS["max_duration"] + ): raise InvalidParameterError( f"Duration must be between {API_LIMITS['min_duration']} and {API_LIMITS['max_duration']} seconds" ) @@ -688,53 +772,61 @@ async def _send_order(self, order: Order) -> None: """Send order to server""" # Format asset name with # prefix if not already present asset_name = order.asset - + # Create the message in the correct PocketOption format message = f'42["openOrder",{{"asset":"{asset_name}","amount":{order.amount},"action":"{order.direction.value}","isDemo":{1 if self.is_demo else 0},"requestId":"{order.request_id}","optionType":100,"time":{order.duration}}}]' - + # Send using appropriate connection if self._is_persistent and self._keep_alive_manager: await self._keep_alive_manager.send_message(message) else: await self._websocket.send_message(message) - + if self.enable_logging: logger.debug(f"Sent order: {message}") - async def _wait_for_order_result(self, request_id: str, order: Order, timeout: float = 30.0) -> OrderResult: + async def _wait_for_order_result( + self, request_id: str, order: Order, timeout: float = 30.0 + ) -> OrderResult: """Wait for order execution result""" start_time = time.time() - + # Wait for order to appear in tracking system while time.time() - start_time < timeout: # Check if order was added to active orders (by _on_order_opened or _on_json_data) if request_id in self._active_orders: if self.enable_logging: - logger.success(f"โœ… Order {request_id} found in active tracking") + logger.success(f" Order {request_id} found in active tracking") return self._active_orders[request_id] - + # Check if order went directly to results (failed or completed) if request_id in self._order_results: if self.enable_logging: logger.info(f"๐Ÿ“‹ Order {request_id} found in completed results") return self._order_results[request_id] - + await asyncio.sleep(0.2) # Check every 200ms - + # Check one more time before creating fallback if request_id in self._active_orders: if self.enable_logging: - logger.success(f"โœ… Order {request_id} found in active tracking (final check)") + logger.success( + f" Order {request_id} found in active tracking (final check)" + ) return self._active_orders[request_id] - + if request_id in self._order_results: if self.enable_logging: - logger.info(f"๐Ÿ“‹ Order {request_id} found in completed results (final check)") + logger.info( + f"๐Ÿ“‹ Order {request_id} found in completed results (final check)" + ) return self._order_results[request_id] - + # If timeout, create a fallback result with the original order data if self.enable_logging: - logger.warning(f"โฐ Order {request_id} timed out waiting for server response, creating fallback result") + logger.warning( + f"โฐ Order {request_id} timed out waiting for server response, creating fallback result" + ) fallback_result = OrderResult( order_id=request_id, asset=order.asset, @@ -744,101 +836,122 @@ async def _wait_for_order_result(self, request_id: str, order: Order, timeout: f status=OrderStatus.ACTIVE, # Assume it's active since it was placed placed_at=datetime.now(), expires_at=datetime.now() + timedelta(seconds=order.duration), - error_message="Timeout waiting for server confirmation" - ) # Store it in active orders in case server responds later + error_message="Timeout waiting for server confirmation", + ) # Store it in active orders in case server responds later self._active_orders[request_id] = fallback_result if self.enable_logging: logger.info(f"๐Ÿ“ Created fallback order result for {request_id}") return fallback_result - async def check_win(self, order_id: str, max_wait_time: float = 300.0) -> Optional[Dict[str, Any]]: + async def check_win( + self, order_id: str, max_wait_time: float = 300.0 + ) -> Optional[Dict[str, Any]]: """ Check win functionality - waits for trade completion message - + Args: order_id: Order ID to check max_wait_time: Maximum time to wait for result (default 5 minutes) - + Returns: Dictionary with trade result or None if timeout/error """ start_time = time.time() - + if self.enable_logging: - logger.info(f"๐Ÿ” Starting check_win for order {order_id}, max wait: {max_wait_time}s") - + logger.info( + f"๐Ÿ” Starting check_win for order {order_id}, max wait: {max_wait_time}s" + ) + while time.time() - start_time < max_wait_time: # Check if order is in completed results if order_id in self._order_results: result = self._order_results[order_id] if self.enable_logging: - logger.success(f"โœ… Order {order_id} completed - Status: {result.status.value}, Profit: ${result.profit:.2f}") - + logger.success( + f" Order {order_id} completed - Status: {result.status.value}, Profit: ${result.profit:.2f}" + ) + return { - 'result': 'win' if result.status == OrderStatus.WIN else 'loss' if result.status == OrderStatus.LOSE else 'draw', - 'profit': result.profit if result.profit is not None else 0, - 'order_id': order_id, - 'completed': True, - 'status': result.status.value + "result": "win" + if result.status == OrderStatus.WIN + else "loss" + if result.status == OrderStatus.LOSE + else "draw", + "profit": result.profit if result.profit is not None else 0, + "order_id": order_id, + "completed": True, + "status": result.status.value, } - + # Check if order is still active (not expired yet) if order_id in self._active_orders: active_order = self._active_orders[order_id] - time_remaining = (active_order.expires_at - datetime.now()).total_seconds() - + time_remaining = ( + active_order.expires_at - datetime.now() + ).total_seconds() + if time_remaining <= 0: if self.enable_logging: - logger.info(f"โฐ Order {order_id} expired but no result yet, continuing to wait...") + logger.info( + f"โฐ Order {order_id} expired but no result yet, continuing to wait..." + ) else: - if self.enable_logging and int(time.time() - start_time) % 10 == 0: # Log every 10 seconds - logger.debug(f"โŒ› Order {order_id} still active, expires in {time_remaining:.0f}s") - + if ( + self.enable_logging and int(time.time() - start_time) % 10 == 0 + ): # Log every 10 seconds + logger.debug( + f"โŒ› Order {order_id} still active, expires in {time_remaining:.0f}s" + ) + await asyncio.sleep(1.0) # Check every second - + # Timeout reached if self.enable_logging: - logger.warning(f"โฐ check_win timeout for order {order_id} after {max_wait_time}s") - + logger.warning( + f"โฐ check_win timeout for order {order_id} after {max_wait_time}s" + ) + return { - 'result': 'timeout', - 'order_id': order_id, - 'completed': False, - 'timeout': True + "result": "timeout", + "order_id": order_id, + "completed": False, + "timeout": True, } - async def _request_candles(self, asset: str, timeframe: int, count: int, - end_time: datetime): + async def _request_candles( + self, asset: str, timeframe: int, count: int, end_time: datetime + ): """Request candle data from server using the correct changeSymbol format""" - + # Create message data in the format expected by PocketOption for real-time candles data = { "asset": str(asset), - "period": timeframe # timeframe in seconds + "period": timeframe, # timeframe in seconds } - + # Create the full message using changeSymbol message_data = ["changeSymbol", data] - message = f'42{json.dumps(message_data)}' - + message = f"42{json.dumps(message_data)}" + if self.enable_logging: logger.debug(f"Requesting candles with changeSymbol: {message}") - + # Create a future to wait for the response candle_future = asyncio.Future() request_id = f"{asset}_{timeframe}" - + # Store the future for this request - if not hasattr(self, '_candle_requests'): + if not hasattr(self, "_candle_requests"): self._candle_requests = {} self._candle_requests[request_id] = candle_future - + # Send the request using appropriate connection if self._is_persistent and self._keep_alive_manager: await self._keep_alive_manager.send_message(message) else: await self._websocket.send_message(message) - + try: # Wait for the response (with timeout) candles = await asyncio.wait_for(candle_future, timeout=10.0) @@ -855,7 +968,7 @@ async def _request_candles(self, asset: str, timeframe: int, count: int, def _parse_candles_data(self, candles_data: List[Any], asset: str, timeframe: int): """Parse candles data from server response""" candles = [] - + try: if isinstance(candles_data, list): for candle_data in candles_data: @@ -864,87 +977,104 @@ def _parse_candles_data(self, candles_data: List[Any], asset: str, timeframe: in # Note: Server sends low/high swapped compared to standard OHLC format raw_high = float(candle_data[2]) raw_low = float(candle_data[3]) - + # Ensure high >= low by swapping if necessary actual_high = max(raw_high, raw_low) actual_low = min(raw_high, raw_low) - + candle = Candle( timestamp=datetime.fromtimestamp(candle_data[0]), open=float(candle_data[1]), high=actual_high, low=actual_low, close=float(candle_data[4]), - volume=float(candle_data[5]) if len(candle_data) > 5 else 0.0, + volume=float(candle_data[5]) + if len(candle_data) > 5 + else 0.0, asset=asset, - timeframe=timeframe + timeframe=timeframe, ) candles.append(candle) - + except Exception as e: if self.enable_logging: logger.error(f"Error parsing candles data: {e}") - + return candles async def _on_json_data(self, data: Dict[str, Any]) -> None: """Handle detailed order data from JSON bytes messages""" if not isinstance(data, dict): return - # Check if this is candles data response + # Check if this is candles data response if "candles" in data and isinstance(data["candles"], list): # Find the corresponding candle request - if hasattr(self, '_candle_requests'): + if hasattr(self, "_candle_requests"): # Try to match the request based on asset and period asset = data.get("asset") period = data.get("period") if asset and period: request_id = f"{asset}_{period}" - if request_id in self._candle_requests and not self._candle_requests[request_id].done(): - candles = self._parse_candles_data(data["candles"], asset, period) + if ( + request_id in self._candle_requests + and not self._candle_requests[request_id].done() + ): + candles = self._parse_candles_data( + data["candles"], asset, period + ) self._candle_requests[request_id].set_result(candles) if self.enable_logging: - logger.success(f"โœ… Candles data received: {len(candles)} candles for {asset}") + logger.success( + f" Candles data received: {len(candles)} candles for {asset}" + ) del self._candle_requests[request_id] return return - + # Check if this is detailed order data with requestId if "requestId" in data and "asset" in data and "amount" in data: request_id = str(data["requestId"]) - + # If this is a new order, add it to tracking - if request_id not in self._active_orders and request_id not in self._order_results: + if ( + request_id not in self._active_orders + and request_id not in self._order_results + ): order_result = OrderResult( order_id=request_id, - asset=data.get('asset', 'UNKNOWN'), - amount=float(data.get('amount', 0)), - direction=OrderDirection.CALL if data.get('command', 0) == 0 else OrderDirection.PUT, - duration=int(data.get('time', 60)), + asset=data.get("asset", "UNKNOWN"), + amount=float(data.get("amount", 0)), + direction=OrderDirection.CALL + if data.get("command", 0) == 0 + else OrderDirection.PUT, + duration=int(data.get("time", 60)), status=OrderStatus.ACTIVE, placed_at=datetime.now(), - expires_at=datetime.now() + timedelta(seconds=int(data.get('time', 60))), - profit=float(data.get('profit', 0)) if 'profit' in data else None, - payout=data.get('payout') + expires_at=datetime.now() + + timedelta(seconds=int(data.get("time", 60))), + profit=float(data.get("profit", 0)) if "profit" in data else None, + payout=data.get("payout"), ) - + # Add to active orders self._active_orders[request_id] = order_result if self.enable_logging: - logger.success(f"โœ… Order {request_id} added to tracking from JSON data") - - await self._emit_event('order_opened', data) - + logger.success( + f" Order {request_id} added to tracking from JSON data" + ) + + await self._emit_event("order_opened", data) + # Check if this is order result data with deals elif "deals" in data and isinstance(data["deals"], list): for deal in data["deals"]: if isinstance(deal, dict) and "id" in deal: order_id = str(deal["id"]) - + if order_id in self._active_orders: active_order = self._active_orders[order_id] - profit = float(deal.get('profit', 0)) - + profit = float(deal.get("profit", 0)) + # Determine status if profit > 0: status = OrderStatus.WIN @@ -952,7 +1082,7 @@ async def _on_json_data(self, data: Dict[str, Any]) -> None: status = OrderStatus.LOSE else: status = OrderStatus.LOSE # Default for zero profit - + result = OrderResult( order_id=active_order.order_id, asset=active_order.asset, @@ -963,16 +1093,18 @@ async def _on_json_data(self, data: Dict[str, Any]) -> None: placed_at=active_order.placed_at, expires_at=active_order.expires_at, profit=profit, - payout=deal.get('payout') + payout=deal.get("payout"), ) - + # Move from active to completed self._order_results[order_id] = result del self._active_orders[order_id] - + if self.enable_logging: - logger.success(f"โœ… Order {order_id} completed via JSON data: {status.value} - Profit: ${profit:.2f}") - await self._emit_event('order_closed', result) + logger.success( + f" Order {order_id} completed via JSON data: {status.value} - Profit: ${profit:.2f}" + ) + await self._emit_event("order_closed", result) async def _emit_event(self, event: str, data: Any) -> None: """Emit event to registered callbacks""" @@ -991,21 +1123,22 @@ async def _emit_event(self, event: str, data: Any) -> None: async def _on_authenticated(self, data: Dict[str, Any]) -> None: """Handle authentication success""" if self.enable_logging: - logger.success("โœ… Successfully authenticated with PocketOption") - self._connection_stats['successful_connections'] += 1 - await self._emit_event('authenticated', data) + logger.success(" Successfully authenticated with PocketOption") + self._connection_stats["successful_connections"] += 1 + await self._emit_event("authenticated", data) + async def _on_balance_updated(self, data: Dict[str, Any]) -> None: """Handle balance update""" try: balance = Balance( - balance=float(data.get('balance', 0)), - currency=data.get('currency', 'USD'), - is_demo=self.is_demo + balance=float(data.get("balance", 0)), + currency=data.get("currency", "USD"), + is_demo=self.is_demo, ) self._balance = balance if self.enable_logging: - logger.info(f"๐Ÿ’ฐ Balance updated: ${balance.balance:.2f}") - await self._emit_event('balance_updated', balance) + logger.info(f"Balance updated: ${balance.balance:.2f}") + await self._emit_event("balance_updated", balance) except Exception as e: if self.enable_logging: logger.error(f"Failed to parse balance data: {e}") @@ -1018,52 +1151,60 @@ async def _on_balance_data(self, data: Dict[str, Any]) -> None: async def _on_order_opened(self, data: Dict[str, Any]) -> None: """Handle order opened event""" if self.enable_logging: - logger.info(f"๐Ÿ“ˆ Order opened: {data}") - await self._emit_event('order_opened', data) + logger.info(f"Order opened: {data}") + await self._emit_event("order_opened", data) async def _on_order_closed(self, data: Dict[str, Any]) -> None: """Handle order closed event""" if self.enable_logging: logger.info(f"๐Ÿ“Š Order closed: {data}") - await self._emit_event('order_closed', data) + await self._emit_event("order_closed", data) async def _on_stream_update(self, data: Dict[str, Any]) -> None: """Handle stream update event - includes real-time candle data""" if self.enable_logging: logger.debug(f"๐Ÿ“ก Stream update: {data}") - + # Check if this is candle data from changeSymbol subscription - if 'asset' in data and 'period' in data and ('candles' in data or 'data' in data): + if ( + "asset" in data + and "period" in data + and ("candles" in data or "data" in data) + ): await self._handle_candles_stream(data) - - await self._emit_event('stream_update', data) + + await self._emit_event("stream_update", data) async def _on_candles_received(self, data: Dict[str, Any]) -> None: """Handle candles data received""" if self.enable_logging: logger.info(f"๐Ÿ•ฏ๏ธ Candles received with data: {type(data)}") - # Check if we have pending candle requests - if hasattr(self, '_candle_requests') and self._candle_requests: + # Check if we have pending candle requests + if hasattr(self, "_candle_requests") and self._candle_requests: # Parse the candles data try: # Get the first pending request to extract asset and timeframe info for request_id, future in list(self._candle_requests.items()): if not future.done(): # Extract asset and timeframe from request_id format: "asset_timeframe" - parts = request_id.split('_') + parts = request_id.split("_") if len(parts) >= 2: - asset = '_'.join(parts[:-1]) # Handle assets with underscores + asset = "_".join( + parts[:-1] + ) # Handle assets with underscores timeframe = int(parts[-1]) - + candles = self._parse_candles_data(data, asset, timeframe) if self.enable_logging: - logger.info(f"๐Ÿ•ฏ๏ธ Parsed {len(candles)} candles from response") - + logger.info( + f"๐Ÿ•ฏ๏ธ Parsed {len(candles)} candles from response" + ) + future.set_result(candles) if self.enable_logging: logger.debug(f"Resolved candle request: {request_id}") break - + except Exception as e: if self.enable_logging: logger.error(f"Error processing candles data: {e}") @@ -1072,67 +1213,72 @@ async def _on_candles_received(self, data: Dict[str, Any]) -> None: if not future.done(): future.set_result([]) break - - await self._emit_event('candles_received', data) + + await self._emit_event("candles_received", data) async def _on_disconnected(self, data: Dict[str, Any]) -> None: """Handle disconnection event""" if self.enable_logging: - logger.warning("๐Ÿ”Œ Disconnected from PocketOption") - await self._emit_event('disconnected', data) + logger.warning("Disconnected from PocketOption") + await self._emit_event("disconnected", data) async def _handle_candles_stream(self, data: Dict[str, Any]) -> None: """Handle candle data from stream updates (changeSymbol responses)""" try: - asset = data.get('asset') - period = data.get('period') - + asset = data.get("asset") + period = data.get("period") + if not asset or not period: return - + request_id = f"{asset}_{period}" - + if self.enable_logging: logger.info(f"๐Ÿ•ฏ๏ธ Processing candle stream for {asset} ({period}s)") - + # Check if we have a pending request for this asset/period - if hasattr(self, '_candle_requests') and request_id in self._candle_requests: + if ( + hasattr(self, "_candle_requests") + and request_id in self._candle_requests + ): future = self._candle_requests[request_id] - + if not future.done(): # Parse candles from stream data candles = self._parse_stream_candles(data) if candles: future.set_result(candles) if self.enable_logging: - logger.info(f"๐Ÿ•ฏ๏ธ Resolved candle request for {asset} with {len(candles)} candles") - + logger.info( + f"๐Ÿ•ฏ๏ธ Resolved candle request for {asset} with {len(candles)} candles" + ) + # Clean up the request del self._candle_requests[request_id] - + except Exception as e: if self.enable_logging: - logger.error(f"โŒ Error handling candles stream: {e}") + logger.error(f"Error handling candles stream: {e}") def _parse_stream_candles(self, stream_data: Dict[str, Any]): """Parse candles from stream update data (changeSymbol response)""" candles = [] - + try: # Stream data might contain candles in different formats - candle_data = stream_data.get('data') or stream_data.get('candles') or [] - + candle_data = stream_data.get("data") or stream_data.get("candles") or [] + if isinstance(candle_data, list): for item in candle_data: if isinstance(item, dict): # Dict format candle = Candle( - timestamp=datetime.fromtimestamp(item.get('time', 0)), - open=float(item.get('open', 0)), - high=float(item.get('high', 0)), - low=float(item.get('low', 0)), - close=float(item.get('close', 0)), - volume=float(item.get('volume', 0)) + timestamp=datetime.fromtimestamp(item.get("time", 0)), + open=float(item.get("open", 0)), + high=float(item.get("high", 0)), + low=float(item.get("low", 0)), + close=float(item.get("close", 0)), + volume=float(item.get("volume", 0)), ) candles.append(candle) elif isinstance(item, (list, tuple)) and len(item) >= 6: @@ -1143,28 +1289,28 @@ def _parse_stream_candles(self, stream_data: Dict[str, Any]): high=float(item[3]), low=float(item[4]), close=float(item[2]), - volume=float(item[5]) if len(item) > 5 else 0.0 + volume=float(item[5]) if len(item) > 5 else 0.0, ) candles.append(candle) - + # Sort by timestamp candles.sort(key=lambda x: x.timestamp) - + except Exception as e: if self.enable_logging: logger.error(f"Error parsing stream candles: {e}") - + return candles async def _on_keep_alive_connected(self): """Handle event when keep-alive connection is established""" logger.info("Keep-alive connection established") - + # Initialize data after connection await self._initialize_data() - + # Emit event - for callback in self._event_callbacks.get('connected', []): + for callback in self._event_callbacks.get("connected", []): try: if asyncio.iscoroutinefunction(callback): await callback() @@ -1172,16 +1318,16 @@ async def _on_keep_alive_connected(self): callback() except Exception as e: logger.error(f"Error in connected callback: {e}") - + async def _on_keep_alive_reconnected(self): """Handle event when keep-alive connection is re-established""" logger.info("Keep-alive connection re-established") - + # Re-initialize data await self._initialize_data() - + # Emit event - for callback in self._event_callbacks.get('reconnected', []): + for callback in self._event_callbacks.get("reconnected", []): try: if asyncio.iscoroutinefunction(callback): await callback() @@ -1189,20 +1335,20 @@ async def _on_keep_alive_reconnected(self): callback() except Exception as e: logger.error(f"Error in reconnected callback: {e}") - + async def _on_keep_alive_message(self, message): """Handle messages received via keep-alive connection""" # Process the message - if message.startswith('42'): + if message.startswith("42"): try: # Parse the message (remove the 42 prefix and parse JSON) data_str = message[2:] data = json.loads(data_str) - + if isinstance(data, list) and len(data) >= 2: event_type = data[0] event_data = data[1] - + # Process different event types if event_type == "authenticated": await self._on_authenticated(event_data) @@ -1218,9 +1364,9 @@ async def _on_keep_alive_message(self, message): await self._on_stream_update(event_data) except Exception as e: logger.error(f"Error processing keep-alive message: {e}") - + # Emit raw message event - for callback in self._event_callbacks.get('message', []): + for callback in self._event_callbacks.get("message", []): try: if asyncio.iscoroutinefunction(callback): await callback(message) @@ -1232,45 +1378,47 @@ async def _on_keep_alive_message(self, message): async def _attempt_reconnection(self, max_attempts: int = 3) -> bool: """ Attempt to reconnect to PocketOption - + Args: max_attempts: Maximum number of reconnection attempts - + Returns: bool: True if reconnection was successful """ - logger.info(f"๐Ÿ”„ Attempting reconnection (max {max_attempts} attempts)...") - + logger.info(f"Attempting reconnection (max {max_attempts} attempts)...") + for attempt in range(max_attempts): try: - logger.info(f"๐Ÿ”„ Reconnection attempt {attempt + 1}/{max_attempts}") - + logger.info(f"Reconnection attempt {attempt + 1}/{max_attempts}") + # Disconnect first to clean up if self._is_persistent and self._keep_alive_manager: await self._keep_alive_manager.disconnect() else: await self._websocket.disconnect() - + # Wait a bit before reconnecting await asyncio.sleep(2 + attempt) # Progressive delay - + # Attempt to reconnect if self.persistent_connection: success = await self._start_persistent_connection() else: success = await self._start_regular_connection() - + if success: - logger.info(f"โœ… Reconnection successful on attempt {attempt + 1}") - + logger.info(f" Reconnection successful on attempt {attempt + 1}") + # Trigger reconnected event - await self._emit_event('reconnected', {}) + await self._emit_event("reconnected", {}) return True else: - logger.warning(f"โŒ Reconnection attempt {attempt + 1} failed") - + logger.warning(f"Reconnection attempt {attempt + 1} failed") + except Exception as e: - logger.error(f"โŒ Reconnection attempt {attempt + 1} failed with error: {e}") - - logger.error(f"โŒ All {max_attempts} reconnection attempts failed") + logger.error( + f"Reconnection attempt {attempt + 1} failed with error: {e}" + ) + + logger.error(f"All {max_attempts} reconnection attempts failed") return False diff --git a/pocketoptionapi_async/config.py b/pocketoptionapi_async/config.py index 8ef7018..0d6ca37 100644 --- a/pocketoptionapi_async/config.py +++ b/pocketoptionapi_async/config.py @@ -4,11 +4,13 @@ import os from dataclasses import dataclass -from typing import Optional, Dict, Any +from typing import Dict, Any + @dataclass class ConnectionConfig: """WebSocket connection configuration""" + ping_interval: int = 20 ping_timeout: int = 10 close_timeout: int = 10 @@ -16,9 +18,11 @@ class ConnectionConfig: reconnect_delay: int = 5 message_timeout: int = 30 -@dataclass + +@dataclass class TradingConfig: """Trading configuration""" + min_order_amount: float = 1.0 max_order_amount: float = 50000.0 min_duration: int = 60 @@ -26,43 +30,60 @@ class TradingConfig: max_concurrent_orders: int = 10 default_timeout: float = 30.0 + @dataclass class LoggingConfig: """Logging configuration""" + level: str = "INFO" - format: str = "{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{function}:{line} | {message}" + format: str = ( + "{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{function}:{line} | {message}" + ) rotation: str = "1 day" retention: str = "7 days" log_file: str = "pocketoption_async.log" + class Config: """Main configuration class""" - + def __init__(self): self.connection = ConnectionConfig() self.trading = TradingConfig() self.logging = LoggingConfig() - + # Load from environment variables self._load_from_env() - + def _load_from_env(self): """Load configuration from environment variables""" - + # Connection settings - self.connection.ping_interval = int(os.getenv("PING_INTERVAL", self.connection.ping_interval)) - self.connection.ping_timeout = int(os.getenv("PING_TIMEOUT", self.connection.ping_timeout)) - self.connection.max_reconnect_attempts = int(os.getenv("MAX_RECONNECT_ATTEMPTS", self.connection.max_reconnect_attempts)) - + self.connection.ping_interval = int( + os.getenv("PING_INTERVAL", self.connection.ping_interval) + ) + self.connection.ping_timeout = int( + os.getenv("PING_TIMEOUT", self.connection.ping_timeout) + ) + self.connection.max_reconnect_attempts = int( + os.getenv("MAX_RECONNECT_ATTEMPTS", self.connection.max_reconnect_attempts) + ) + # Trading settings - self.trading.min_order_amount = float(os.getenv("MIN_ORDER_AMOUNT", self.trading.min_order_amount)) - self.trading.max_order_amount = float(os.getenv("MAX_ORDER_AMOUNT", self.trading.max_order_amount)) - self.trading.default_timeout = float(os.getenv("DEFAULT_TIMEOUT", self.trading.default_timeout)) - + self.trading.min_order_amount = float( + os.getenv("MIN_ORDER_AMOUNT", self.trading.min_order_amount) + ) + self.trading.max_order_amount = float( + os.getenv("MAX_ORDER_AMOUNT", self.trading.max_order_amount) + ) + self.trading.default_timeout = float( + os.getenv("DEFAULT_TIMEOUT", self.trading.default_timeout) + ) + # Logging settings self.logging.level = os.getenv("LOG_LEVEL", self.logging.level) self.logging.log_file = os.getenv("LOG_FILE", self.logging.log_file) - + def to_dict(self) -> Dict[str, Any]: """Convert configuration to dictionary""" return { @@ -72,7 +93,7 @@ def to_dict(self) -> Dict[str, Any]: "close_timeout": self.connection.close_timeout, "max_reconnect_attempts": self.connection.max_reconnect_attempts, "reconnect_delay": self.connection.reconnect_delay, - "message_timeout": self.connection.message_timeout + "message_timeout": self.connection.message_timeout, }, "trading": { "min_order_amount": self.trading.min_order_amount, @@ -80,16 +101,17 @@ def to_dict(self) -> Dict[str, Any]: "min_duration": self.trading.min_duration, "max_duration": self.trading.max_duration, "max_concurrent_orders": self.trading.max_concurrent_orders, - "default_timeout": self.trading.default_timeout + "default_timeout": self.trading.default_timeout, }, "logging": { "level": self.logging.level, "format": self.logging.format, "rotation": self.logging.rotation, "retention": self.logging.retention, - "log_file": self.logging.log_file - } + "log_file": self.logging.log_file, + }, } + # Global configuration instance config = Config() diff --git a/pocketoptionapi_async/connection_keep_alive.py b/pocketoptionapi_async/connection_keep_alive.py index 34b2099..a32a698 100644 --- a/pocketoptionapi_async/connection_keep_alive.py +++ b/pocketoptionapi_async/connection_keep_alive.py @@ -1,23 +1,23 @@ -๏ปฟ""" +""" Connection Keep-Alive Manager for PocketOption API """ import asyncio import time -import json from collections import defaultdict -from typing import Dict, List, Callable, Any, Optional +from typing import Dict, List, Callable, Optional from loguru import logger + class ConnectionKeepAlive: """ Handles persistent connection with automatic keep-alive and reconnection """ - + def __init__(self, ssid: str, is_demo: bool = True): """ Initialize connection keep-alive manager - + Args: ssid: Session ID for authentication is_demo: Whether this is a demo account (default: True) @@ -30,24 +30,25 @@ def __init__(self, ssid: str, is_demo: bool = True): self._ping_task = None self._reconnect_task = None self._connection_stats = { - 'last_ping_time': None, - 'total_reconnections': 0, - 'messages_sent': 0, - 'messages_received': 0 + "last_ping_time": None, + "total_reconnections": 0, + "messages_sent": 0, + "messages_received": 0, } - + # Importing inside the class to avoid circular imports try: from .websocket_client import AsyncWebSocketClient + self._websocket_client_class = AsyncWebSocketClient except ImportError: logger.error("Failed to import AsyncWebSocketClient") raise ImportError("AsyncWebSocketClient module not available") - + def add_event_handler(self, event: str, handler: Callable): """Add event handler function""" self._event_handlers[event].append(handler) - + async def _trigger_event_async(self, event: str, *args, **kwargs): """Trigger event handlers asynchronously""" for handler in self._event_handlers.get(event, []): @@ -60,88 +61,105 @@ async def _trigger_event_async(self, event: str, *args, **kwargs): handler(*args, **kwargs) except Exception as e: logger.error(f"Error in {event} handler: {e}") - + def _trigger_event(self, event: str, *args, **kwargs): """Trigger event handlers""" for handler in self._event_handlers.get(event, []): try: if asyncio.iscoroutinefunction(handler): # Create task for async handlers - asyncio.create_task(self._handle_async_callback(handler, args, kwargs)) + asyncio.create_task( + self._handle_async_callback(handler, args, kwargs) + ) else: # Call sync handlers directly handler(*args, **kwargs) except Exception as e: logger.error(f"Error in {event} handler: {e}") - + async def _handle_async_callback(self, callback, args, kwargs): """Helper to handle async callbacks in tasks""" try: await callback(*args, **kwargs) except Exception as e: logger.error(f"Error in async callback: {e}") - + # Event forwarding methods async def _forward_balance_data(self, data): """Forward balance_data event from WebSocket to keep-alive handlers""" - await self._trigger_event_async('balance_data', data) - + await self._trigger_event_async("balance_data", data) + async def _forward_balance_updated(self, data): - """Forward balance_updated event from WebSocket to keep-alive handlers""" - await self._trigger_event_async('balance_updated', data) - + """Forward balance_updated event from WebSocket to keep-alive handlers""" + await self._trigger_event_async("balance_updated", data) + async def _forward_authenticated(self, data): """Forward authenticated event from WebSocket to keep-alive handlers""" - await self._trigger_event_async('authenticated', data) - + await self._trigger_event_async("authenticated", data) + async def _forward_order_opened(self, data): """Forward order_opened event from WebSocket to keep-alive handlers""" - await self._trigger_event_async('order_opened', data) - + await self._trigger_event_async("order_opened", data) + async def _forward_order_closed(self, data): """Forward order_closed event from WebSocket to keep-alive handlers""" - await self._trigger_event_async('order_closed', data) - + await self._trigger_event_async("order_closed", data) + async def _forward_stream_update(self, data): """Forward stream_update event from WebSocket to keep-alive handlers""" - await self._trigger_event_async('stream_update', data) - + await self._trigger_event_async("stream_update", data) + async def _forward_json_data(self, data): """Forward json_data event from WebSocket to keep-alive handlers""" - await self._trigger_event_async('json_data', data) + await self._trigger_event_async("json_data", data) - async def connect_with_keep_alive(self, regions: Optional[List[str]] = None) -> bool: + async def connect_with_keep_alive( + self, regions: Optional[List[str]] = None + ) -> bool: """ Connect with automatic keep-alive and reconnection - + Args: regions: List of region names to try (optional) - + Returns: bool: Success status """ # Create websocket client if needed if not self._websocket: self._websocket = self._websocket_client_class() - + # Forward WebSocket events to keep-alive events - self._websocket.add_event_handler('balance_data', self._forward_balance_data) - self._websocket.add_event_handler('balance_updated', self._forward_balance_updated) - self._websocket.add_event_handler('authenticated', self._forward_authenticated) - self._websocket.add_event_handler('order_opened', self._forward_order_opened) - self._websocket.add_event_handler('order_closed', self._forward_order_closed) - self._websocket.add_event_handler('stream_update', self._forward_stream_update) - self._websocket.add_event_handler('json_data', self._forward_json_data) - + self._websocket.add_event_handler( + "balance_data", self._forward_balance_data + ) + self._websocket.add_event_handler( + "balance_updated", self._forward_balance_updated + ) + self._websocket.add_event_handler( + "authenticated", self._forward_authenticated + ) + self._websocket.add_event_handler( + "order_opened", self._forward_order_opened + ) + self._websocket.add_event_handler( + "order_closed", self._forward_order_closed + ) + self._websocket.add_event_handler( + "stream_update", self._forward_stream_update + ) + self._websocket.add_event_handler("json_data", self._forward_json_data) + # Format auth message if self.ssid.startswith('42["auth",'): ssid_message = self.ssid else: # Create basic auth message from raw session ID ssid_message = f'42["auth", {{"ssid": "{self.ssid}", "is_demo": {str(self.is_demo).lower()}}}]' - + # Connect to WebSocket from .constants import REGIONS + if not regions: # Use appropriate regions based on demo mode if self.is_demo: @@ -154,107 +172,115 @@ async def connect_with_keep_alive(self, regions: Optional[List[str]] = None) -> else: # For live mode, use all regions except demo all_regions = REGIONS.get_all_regions() - regions = [name for name, url in all_regions.items() if "DEMO" not in name.upper()] - + regions = [ + name + for name, url in all_regions.items() + if "DEMO" not in name.upper() + ] + # Try to connect for region_name in regions: region_url = REGIONS.get_region(region_name) if not region_url: continue - + try: urls = [region_url] logger.info(f"Trying to connect to {region_name} ({region_url})") success = await self._websocket.connect(urls, ssid_message) - + if success: logger.info(f"Connected to {region_name}") self.is_connected = True - + # Start keep-alive self._start_keep_alive_tasks() - + # Notify connection (async-aware) - await self._trigger_event_async('connected') + await self._trigger_event_async("connected") return True except Exception as e: logger.warning(f"Failed to connect to {region_name}: {e}") - + return False def _start_keep_alive_tasks(self): """Start keep-alive tasks""" logger.info("Starting keep-alive tasks") - + # Start ping task if self._ping_task: self._ping_task.cancel() self._ping_task = asyncio.create_task(self._ping_loop()) - + # Start reconnection monitor if self._reconnect_task: self._reconnect_task.cancel() self._reconnect_task = asyncio.create_task(self._reconnection_monitor()) - + async def _ping_loop(self): """Send periodic pings to keep connection alive""" while self.is_connected and self._websocket: try: await self._websocket.send_message('42["ps"]') - self._connection_stats['last_ping_time'] = time.time() - self._connection_stats['messages_sent'] += 1 + self._connection_stats["last_ping_time"] = time.time() + self._connection_stats["messages_sent"] += 1 await asyncio.sleep(20) # Ping every 20 seconds except Exception as e: logger.warning(f"Ping failed: {e}") self.is_connected = False - + async def _reconnection_monitor(self): """Monitor and reconnect if connection is lost""" while True: await asyncio.sleep(30) # Check every 30 seconds - - if not self.is_connected or not self._websocket or not self._websocket.is_connected: + + if ( + not self.is_connected + or not self._websocket + or not self._websocket.is_connected + ): logger.info("Connection lost, reconnecting...") self.is_connected = False - + # Try to reconnect success = await self.connect_with_keep_alive() - + if success: - self._connection_stats['total_reconnections'] += 1 + self._connection_stats["total_reconnections"] += 1 logger.info("Reconnection successful") - await self._trigger_event_async('reconnected') + await self._trigger_event_async("reconnected") else: logger.error("Reconnection failed") await asyncio.sleep(10) # Wait before next attempt - + async def disconnect(self): """Disconnect and clean up resources""" logger.info("Disconnecting...") - + # Cancel tasks if self._ping_task: self._ping_task.cancel() if self._reconnect_task: self._reconnect_task.cancel() - + # Disconnect websocket if self._websocket: await self._websocket.disconnect() - + self.is_connected = False logger.info("Disconnected") - await self._trigger_event_async('disconnected') - + await self._trigger_event_async("disconnected") + async def send_message(self, message): """Send WebSocket message""" if not self.is_connected or not self._websocket: raise ConnectionError("Not connected") - + await self._websocket.send_message(message) - self._connection_stats['messages_sent'] += 1 - + self._connection_stats["messages_sent"] += 1 + async def on_message(self, message): """Handle WebSocket message""" - self._connection_stats['messages_received'] += 1 - await self._trigger_event_async('message_received', message) + self._connection_stats["messages_received"] += 1 + await self._trigger_event_async("message_received", message) diff --git a/pocketoptionapi_async/connection_keep_alive.py.bak b/pocketoptionapi_async/connection_keep_alive.py.bak deleted file mode 100644 index 25c5ed2..0000000 --- a/pocketoptionapi_async/connection_keep_alive.py.bak +++ /dev/null @@ -1,219 +0,0 @@ -""" -Connection Keep-Alive Manager for PocketOption API -""" - -import asyncio -import time -import json -from collections import defaultdict -from typing import Dict, List, Callable, Any, Optional -from loguru import logger - -class ConnectionKeepAlive: - """ - Handles persistent connection with automatic keep-alive and reconnection - """ - - def __init__(self, ssid: str, is_demo: bool = True): - """ - Initialize connection keep-alive manager - - Args: - ssid: Session ID for authentication - is_demo: Whether this is a demo account (default: True) - """ - self.ssid = ssid - self.is_demo = is_demo - self.is_connected = False - self._websocket = None # Will store reference to websocket client - self._event_handlers: Dict[str, List[Callable]] = defaultdict(list) - self._ping_task = None - self._reconnect_task = None - self._connection_stats = { - 'last_ping_time': None, - 'total_reconnections': 0, - 'messages_sent': 0, - 'messages_received': 0 - } - - # Importing inside the class to avoid circular imports - try: - from .websocket_client import AsyncWebSocketClient - self._websocket_client_class = AsyncWebSocketClient - except ImportError: - logger.error("Failed to import AsyncWebSocketClient") - raise ImportError("AsyncWebSocketClient module not available") - - def add_event_handler(self, event: str, handler: Callable): - """Add event handler function""" - self._event_handlers[event].append(handler) - async def _trigger_event_async(self, event: str, *args, **kwargs): - """Trigger event handlers asynchronously""" - for handler in self._event_handlers.get(event, []): - try: - if asyncio.iscoroutinefunction(handler): - # Call async handlers directly - await handler(*args, **kwargs) - else: - # Call sync handlers directly - handler(*args, **kwargs) - except Exception as e: - logger.error(f"Error in {event} handler: {e}") - - def _trigger_event(self, event: str, *args, **kwargs): - """Trigger event handlers""" - for handler in self._event_handlers.get(event, []): - try: - if asyncio.iscoroutinefunction(handler): - # Create task for async handlers - asyncio.create_task(self._handle_async_callback(handler, args, kwargs)) - else: - # Call sync handlers directly - handler(*args, **kwargs) - except Exception as e: - logger.error(f"Error in {event} handler: {e}") - - async def _handle_async_callback(self, callback, args, kwargs): - """Helper to handle async callbacks in tasks""" - try: - await callback(*args, **kwargs) - except Exception as e: - logger.error(f"Error in async callback: {e}") - - async def connect_with_keep_alive(self, regions: Optional[List[str]] = None) -> bool: - """ - Connect with automatic keep-alive and reconnection - - Args: - regions: List of region names to try (optional) - - Returns: - bool: Success status - """ - # Create websocket client if needed - if not self._websocket: - self._websocket = self._websocket_client_class() - - # Format auth message - if self.ssid.startswith('42["auth",'): - ssid_message = self.ssid - else: - # Create basic auth message from raw session ID - ssid_message = f'42["auth", {{"ssid": "{self.ssid}", "is_demo": {str(self.is_demo).lower()}}}]' - - # Connect to WebSocket - from .constants import REGIONS - if not regions: - # Use appropriate regions based on demo mode - if self.is_demo: - all_regions = REGIONS.get_all_regions() - demo_urls = REGIONS.get_demo_regions() - regions = [] - for name, url in all_regions.items(): - if url in demo_urls: - regions.append(name) - else: - # For live mode, use all regions except demo - all_regions = REGIONS.get_all_regions() - regions = [name for name, url in all_regions.items() if "DEMO" not in name.upper()] - - # Try to connect - for region_name in regions: - region_url = REGIONS.get_region(region_name) - if not region_url: - continue - - try: - urls = [region_url] - logger.info(f"Trying to connect to {region_name} ({region_url})") - success = await self._websocket.connect(urls, ssid_message) - if success: - logger.info(f"Connected to {region_name}") - self.is_connected = True - - # Start keep-alive - self._start_keep_alive_tasks() - - # Notify connection (async-aware) - await self._trigger_event_async('connected') - return True - except Exception as e: - logger.warning(f"Failed to connect to {region_name}: {e}") - - return False - - def _start_keep_alive_tasks(self): - """Start keep-alive tasks""" - logger.info("Starting keep-alive tasks") - - # Start ping task - if self._ping_task: - self._ping_task.cancel() - self._ping_task = asyncio.create_task(self._ping_loop()) - - # Start reconnection monitor - if self._reconnect_task: - self._reconnect_task.cancel() - self._reconnect_task = asyncio.create_task(self._reconnection_monitor()) - - async def _ping_loop(self): - """Send periodic pings to keep connection alive""" - while self.is_connected and self._websocket: - try: - await self._websocket.send_message('42["ps"]') - self._connection_stats['last_ping_time'] = time.time() - self._connection_stats['messages_sent'] += 1 - await asyncio.sleep(20) # Ping every 20 seconds - except Exception as e: - logger.warning(f"Ping failed: {e}") - self.is_connected = False - - async def _reconnection_monitor(self): - """Monitor and reconnect if connection is lost""" - while True: - await asyncio.sleep(30) # Check every 30 seconds - - if not self.is_connected or not self._websocket or not self._websocket.is_connected: - logger.info("Connection lost, reconnecting...") - self.is_connected = False - - # Try to reconnect - success = await self.connect_with_keep_alive() - if success: - self._connection_stats['total_reconnections'] += 1 - logger.info("Reconnection successful") - await self._trigger_event_async('reconnected') - else: - logger.error("Reconnection failed") - await asyncio.sleep(10) # Wait before next attempt - - async def disconnect(self): - """Disconnect and clean up resources""" - logger.info("Disconnecting...") - - # Cancel tasks - if self._ping_task: - self._ping_task.cancel() - if self._reconnect_task: - self._reconnect_task.cancel() - - # Disconnect websocket - if self._websocket: - await self._websocket.disconnect() - - self.is_connected = False - logger.info("Disconnected") - self._trigger_event('disconnected') - - async def send_message(self, message): - """Send WebSocket message""" - if not self.is_connected or not self._websocket: - raise ConnectionError("Not connected") - - await self._websocket.send_message(message) - self._connection_stats['messages_sent'] += 1 - - def on_message(self, message): - """Handle WebSocket message""" - self._connection_stats['messages_received'] += 1 - self._trigger_event('message_received', message) diff --git a/pocketoptionapi_async/constants.py b/pocketoptionapi_async/constants.py index 488d593..8080d9a 100644 --- a/pocketoptionapi_async/constants.py +++ b/pocketoptionapi_async/constants.py @@ -8,147 +8,141 @@ # Asset mappings with their corresponding IDs ASSETS: Dict[str, int] = { # Major Forex Pairs - 'EURUSD': 1, - 'GBPUSD': 56, - 'USDJPY': 63, - 'USDCHF': 62, - 'USDCAD': 61, - 'AUDUSD': 40, - 'NZDUSD': 90, - + "EURUSD": 1, + "GBPUSD": 56, + "USDJPY": 63, + "USDCHF": 62, + "USDCAD": 61, + "AUDUSD": 40, + "NZDUSD": 90, # OTC Forex Pairs - 'EURUSD_otc': 66, - 'GBPUSD_otc': 86, - 'USDJPY_otc': 93, - 'USDCHF_otc': 92, - 'USDCAD_otc': 91, - 'AUDUSD_otc': 71, - 'AUDNZD_otc': 70, - 'AUDCAD_otc': 67, - 'AUDCHF_otc': 68, - 'AUDJPY_otc': 69, - 'CADCHF_otc': 72, - 'CADJPY_otc': 73, - 'CHFJPY_otc': 74, - 'EURCHF_otc': 77, - 'EURGBP_otc': 78, - 'EURJPY_otc': 79, - 'EURNZD_otc': 80, - 'GBPAUD_otc': 81, - 'GBPJPY_otc': 84, - 'NZDJPY_otc': 89, - 'NZDUSD_otc': 90, - + "EURUSD_otc": 66, + "GBPUSD_otc": 86, + "USDJPY_otc": 93, + "USDCHF_otc": 92, + "USDCAD_otc": 91, + "AUDUSD_otc": 71, + "AUDNZD_otc": 70, + "AUDCAD_otc": 67, + "AUDCHF_otc": 68, + "AUDJPY_otc": 69, + "CADCHF_otc": 72, + "CADJPY_otc": 73, + "CHFJPY_otc": 74, + "EURCHF_otc": 77, + "EURGBP_otc": 78, + "EURJPY_otc": 79, + "EURNZD_otc": 80, + "GBPAUD_otc": 81, + "GBPJPY_otc": 84, + "NZDJPY_otc": 89, + "NZDUSD_otc": 90, # Commodities - 'XAUUSD': 2, # Gold - 'XAUUSD_otc': 169, - 'XAGUSD': 65, # Silver - 'XAGUSD_otc': 167, - 'UKBrent': 50, # Oil - 'UKBrent_otc': 164, - 'USCrude': 64, - 'USCrude_otc': 165, - 'XNGUSD': 311, # Natural Gas - 'XNGUSD_otc': 399, - 'XPTUSD': 312, # Platinum - 'XPTUSD_otc': 400, - 'XPDUSD': 313, # Palladium - 'XPDUSD_otc': 401, - + "XAUUSD": 2, # Gold + "XAUUSD_otc": 169, + "XAGUSD": 65, # Silver + "XAGUSD_otc": 167, + "UKBrent": 50, # Oil + "UKBrent_otc": 164, + "USCrude": 64, + "USCrude_otc": 165, + "XNGUSD": 311, # Natural Gas + "XNGUSD_otc": 399, + "XPTUSD": 312, # Platinum + "XPTUSD_otc": 400, + "XPDUSD": 313, # Palladium + "XPDUSD_otc": 401, # Cryptocurrencies - 'BTCUSD': 197, - 'ETHUSD': 272, - 'DASH_USD': 209, - 'BTCGBP': 453, - 'BTCJPY': 454, - 'BCHEUR': 450, - 'BCHGBP': 451, - 'BCHJPY': 452, - 'DOTUSD': 458, - 'LNKUSD': 464, - + "BTCUSD": 197, + "ETHUSD": 272, + "DASH_USD": 209, + "BTCGBP": 453, + "BTCJPY": 454, + "BCHEUR": 450, + "BCHGBP": 451, + "BCHJPY": 452, + "DOTUSD": 458, + "LNKUSD": 464, # Stock Indices - 'SP500': 321, - 'SP500_otc': 408, - 'NASUSD': 323, - 'NASUSD_otc': 410, - 'DJI30': 322, - 'DJI30_otc': 409, - 'JPN225': 317, - 'JPN225_otc': 405, - 'D30EUR': 318, - 'D30EUR_otc': 406, - 'E50EUR': 319, - 'E50EUR_otc': 407, - 'F40EUR': 316, - 'F40EUR_otc': 404, - 'E35EUR': 314, - 'E35EUR_otc': 402, - '100GBP': 315, - '100GBP_otc': 403, - 'AUS200': 305, - 'AUS200_otc': 306, - 'CAC40': 455, - 'AEX25': 449, - 'SMI20': 466, - 'H33HKD': 463, - + "SP500": 321, + "SP500_otc": 408, + "NASUSD": 323, + "NASUSD_otc": 410, + "DJI30": 322, + "DJI30_otc": 409, + "JPN225": 317, + "JPN225_otc": 405, + "D30EUR": 318, + "D30EUR_otc": 406, + "E50EUR": 319, + "E50EUR_otc": 407, + "F40EUR": 316, + "F40EUR_otc": 404, + "E35EUR": 314, + "E35EUR_otc": 402, + "100GBP": 315, + "100GBP_otc": 403, + "AUS200": 305, + "AUS200_otc": 306, + "CAC40": 455, + "AEX25": 449, + "SMI20": 466, + "H33HKD": 463, # US Stocks - '#AAPL': 5, - '#AAPL_otc': 170, - '#MSFT': 24, - '#MSFT_otc': 176, - '#TSLA': 186, - '#TSLA_otc': 196, - '#FB': 177, - '#FB_otc': 187, - '#AMZN_otc': 412, - '#NFLX': 182, - '#NFLX_otc': 429, - '#INTC': 180, - '#INTC_otc': 190, - '#BA': 8, - '#BA_otc': 292, - '#JPM': 20, - '#JNJ': 144, - '#JNJ_otc': 296, - '#PFE': 147, - '#PFE_otc': 297, - '#XOM': 153, - '#XOM_otc': 426, - '#AXP': 140, - '#AXP_otc': 291, - '#MCD': 23, - '#MCD_otc': 175, - '#CSCO': 154, - '#CSCO_otc': 427, - '#VISA_otc': 416, - '#CITI': 326, - '#CITI_otc': 413, - '#FDX_otc': 414, - '#TWITTER': 330, - '#TWITTER_otc': 415, - '#BABA': 183, - '#BABA_otc': 428, - + "#AAPL": 5, + "#AAPL_otc": 170, + "#MSFT": 24, + "#MSFT_otc": 176, + "#TSLA": 186, + "#TSLA_otc": 196, + "#FB": 177, + "#FB_otc": 187, + "#AMZN_otc": 412, + "#NFLX": 182, + "#NFLX_otc": 429, + "#INTC": 180, + "#INTC_otc": 190, + "#BA": 8, + "#BA_otc": 292, + "#JPM": 20, + "#JNJ": 144, + "#JNJ_otc": 296, + "#PFE": 147, + "#PFE_otc": 297, + "#XOM": 153, + "#XOM_otc": 426, + "#AXP": 140, + "#AXP_otc": 291, + "#MCD": 23, + "#MCD_otc": 175, + "#CSCO": 154, + "#CSCO_otc": 427, + "#VISA_otc": 416, + "#CITI": 326, + "#CITI_otc": 413, + "#FDX_otc": 414, + "#TWITTER": 330, + "#TWITTER_otc": 415, + "#BABA": 183, + "#BABA_otc": 428, # Additional assets - 'EURRUB_otc': 200, - 'USDRUB_otc': 199, - 'EURHUF_otc': 460, - 'CHFNOK_otc': 457, - + "EURRUB_otc": 200, + "USDRUB_otc": 199, + "EURHUF_otc": 460, + "CHFNOK_otc": 457, # Microsoft and other tech stocks - 'Microsoft_otc': 521, - 'Facebook_OTC': 522, - 'Tesla_otc': 523, - 'Boeing_OTC': 524, - 'American_Express_otc': 525 + "Microsoft_otc": 521, + "Facebook_OTC": 522, + "Tesla_otc": 523, + "Boeing_OTC": 524, + "American_Express_otc": 525, } + # WebSocket regions with their URLs class Regions: """WebSocket region endpoints""" - + _REGIONS = { "EUROPA": "wss://api-eu.po.market/socket.io/?EIO=4&transport=websocket", "SEYCHELLES": "wss://api-sc.po.market/socket.io/?EIO=4&transport=websocket", @@ -168,8 +162,9 @@ class Regions: "FINLAND": "wss://api-fin.po.market/socket.io/?EIO=4&transport=websocket", "SERVER3": "wss://api-c.po.market/socket.io/?EIO=4&transport=websocket", "ASIA": "wss://api-asia.po.market/socket.io/?EIO=4&transport=websocket", - "SERVER4": "wss://api-us-south.po.market/socket.io/?EIO=4&transport=websocket" } - + "SERVER4": "wss://api-us-south.po.market/socket.io/?EIO=4&transport=websocket", + } + @classmethod def get_all(cls, randomize: bool = True) -> List[str]: """Get all region URLs""" @@ -177,59 +172,60 @@ def get_all(cls, randomize: bool = True) -> List[str]: if randomize: random.shuffle(urls) return urls - + @classmethod def get_all_regions(cls) -> Dict[str, str]: """Get all regions as a dictionary""" return cls._REGIONS.copy() - + @classmethod def get_region(cls, region_name: str) -> str: """Get specific region URL""" return cls._REGIONS.get(region_name.upper()) - + @classmethod def get_demo_regions(cls) -> List[str]: """Get demo region URLs""" return [url for name, url in cls._REGIONS.items() if "DEMO" in name] + # Global constants REGIONS = Regions() # Timeframes (in seconds) TIMEFRAMES = { - '1m': 60, - '5m': 300, - '15m': 900, - '30m': 1800, - '1h': 3600, - '4h': 14400, - '1d': 86400, - '1w': 604800 + "1m": 60, + "5m": 300, + "15m": 900, + "30m": 1800, + "1h": 3600, + "4h": 14400, + "1d": 86400, + "1w": 604800, } # Connection settings CONNECTION_SETTINGS = { - 'ping_interval': 20, # seconds - 'ping_timeout': 10, # seconds - 'close_timeout': 10, # seconds - 'max_reconnect_attempts': 5, - 'reconnect_delay': 5, # seconds - 'message_timeout': 30, # seconds + "ping_interval": 20, # seconds + "ping_timeout": 10, # seconds + "close_timeout": 10, # seconds + "max_reconnect_attempts": 5, + "reconnect_delay": 5, # seconds + "message_timeout": 30, # seconds } # API Limits API_LIMITS = { - 'min_order_amount': 1.0, - 'max_order_amount': 50000.0, - 'min_duration': 5, # seconds - 'max_duration': 43200, # 12 hours in seconds - 'max_concurrent_orders': 10, - 'rate_limit': 100, # requests per minute + "min_order_amount": 1.0, + "max_order_amount": 50000.0, + "min_duration": 5, # seconds + "max_duration": 43200, # 12 hours in seconds + "max_concurrent_orders": 10, + "rate_limit": 100, # requests per minute } # Default headers DEFAULT_HEADERS = { - 'Origin': 'https://pocketoption.com', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' + "Origin": "https://pocketoption.com", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", } diff --git a/pocketoptionapi_async/exceptions.py b/pocketoptionapi_async/exceptions.py index 744a299..f8212ac 100644 --- a/pocketoptionapi_async/exceptions.py +++ b/pocketoptionapi_async/exceptions.py @@ -2,33 +2,47 @@ Custom exceptions for the PocketOption API """ + class PocketOptionError(Exception): """Base exception for all PocketOption API errors""" + def __init__(self, message: str, error_code: str = None): super().__init__(message) self.message = message self.error_code = error_code + class ConnectionError(PocketOptionError): """Raised when connection to PocketOption fails""" + pass + class AuthenticationError(PocketOptionError): """Raised when authentication fails""" + pass + class OrderError(PocketOptionError): """Raised when an order operation fails""" + pass + class TimeoutError(PocketOptionError): """Raised when an operation times out""" + pass + class InvalidParameterError(PocketOptionError): """Raised when invalid parameters are provided""" + pass + class WebSocketError(PocketOptionError): """Raised when WebSocket operations fail""" + pass diff --git a/pocketoptionapi_async/models.py b/pocketoptionapi_async/models.py index 6634137..e976917 100644 --- a/pocketoptionapi_async/models.py +++ b/pocketoptionapi_async/models.py @@ -2,17 +2,35 @@ Pydantic models for type safety and validation """ -from typing import Optional, List, Dict, Any, Union +from typing import Optional from pydantic import BaseModel, Field, validator from datetime import datetime from enum import Enum import uuid + class OrderDirection(str, Enum): + """ + Represents the direction of an order in trading. + - CALL: A call option, predicting the price will go up. + - PUT: A put option, predicting the price will go down. + """ + CALL = "call" PUT = "put" + class OrderStatus(str, Enum): + """ + Represents the current status of a trading order. + - PENDING: The order has been submitted but not yet processed. + - ACTIVE: The order is currently active in the market. + - CLOSED: The order has been closed (either naturally expired or manually). + - CANCELLED: The order was cancelled before execution or expiry. + - WIN: The order resulted in a win (profit). + - LOSE: The order resulted in a loss. + """ + PENDING = "pending" ACTIVE = "active" CLOSED = "closed" @@ -20,35 +38,84 @@ class OrderStatus(str, Enum): WIN = "win" LOSE = "lose" + class ConnectionStatus(str, Enum): + """ + Represents the connection status to the trading platform. + - CONNECTED: Successfully connected to the platform. + - DISCONNECTED: Connection has been lost. + - CONNECTING: Attempting to establish a connection. + - RECONNECTING: Attempting to re-establish a lost connection. + """ + CONNECTED = "connected" DISCONNECTED = "disconnected" CONNECTING = "connecting" RECONNECTING = "reconnecting" + +class TimeFrame(int, Enum): + """ + Represents standard timeframes for candlestick data in seconds. + These values are commonly used in financial charting to aggregate price data + over specific intervals. + """ + + S1 = 1 # 1 second + S5 = 5 # 5 seconds + S10 = 10 # 10 seconds + S15 = 15 # 15 seconds + S30 = 30 # 30 seconds + M1 = 60 # 1 minute + M5 = 300 # 5 minutes + M15 = 900 # 15 minutes + M30 = 1800 # 30 minutes + H1 = 3600 # 1 hour + H4 = 14400 # 4 hours + D1 = 86400 # 1 day + W1 = 604800 # 1 week + MN1 = 2592000 # 1 month (approximate, based on 30 days) + + class Asset(BaseModel): - """Asset information model""" + """ + Asset information model. + Defines the properties of a tradable asset, such as currency pairs or commodities. + """ + id: str name: str symbol: str is_active: bool = True payout: Optional[float] = None - + class Config: frozen = True + class Balance(BaseModel): - """Account balance model""" + """ + Account balance model. + Provides details about the user's current account balance, currency, + and whether it's a demo or real account. + """ + balance: float currency: str = "USD" is_demo: bool = True last_updated: datetime = Field(default_factory=datetime.now) - + class Config: frozen = True + class Candle(BaseModel): - """OHLC candle data model""" + """ + OHLC (Open, High, Low, Close) candle data model. + Represents a single candlestick, which summarizes price movements over a specific timeframe. + Includes validation to ensure logical consistency of high and low prices. + """ + timestamp: datetime open: float high: float @@ -56,45 +123,72 @@ class Candle(BaseModel): close: float volume: Optional[float] = None asset: str - timeframe: int # in seconds - - @validator('high') + timeframe: int # in seconds, representing the duration of the candle + + @validator("high") def high_must_be_valid(cls, v, values): - if 'low' in values and v < values['low']: - raise ValueError('High must be greater than or equal to low') + """ + Validator to ensure that the 'high' price is never less than the 'low' price. + This maintains the logical integrity of candlestick data. + """ + if "low" in values and v < values["low"]: + raise ValueError("High must be greater than or equal to low") return v - - @validator('low') + + @validator("low") def low_must_be_valid(cls, v, values): - if 'high' in values and v > values['high']: - raise ValueError('Low must be less than or equal to high') + """ + Validator to ensure that the 'low' price is never greater than the 'high' price. + This maintains the logical integrity of candlestick data. + """ + if "high" in values and v > values["high"]: + raise ValueError("Low must be less than or equal to high") return v - + class Config: frozen = True + class Order(BaseModel): - """Order request model""" + """ + Order request model. + Defines the parameters for placing a new trading order. + Includes validation for positive amount and minimum duration. + """ + asset: str amount: float direction: OrderDirection - duration: int # in seconds + duration: int # in seconds, how long the order is active request_id: Optional[str] = Field(default_factory=lambda: str(uuid.uuid4())) - - @validator('amount') + + @validator("amount") def amount_must_be_positive(cls, v): + """ + Validator to ensure the trading amount is a positive value. + An amount of zero or less is not valid for an order. + """ if v <= 0: - raise ValueError('Amount must be positive') + raise ValueError("Amount must be positive") return v - - @validator('duration') + + @validator("duration") def duration_must_be_valid(cls, v): + """ + Validator to ensure the order duration meets a minimum requirement. + This prevents orders with impractically short durations. + """ if v < 5: # minimum 5 seconds - raise ValueError('Duration must be at least 5 seconds') + raise ValueError("Duration must be at least 5 seconds") return v + class OrderResult(BaseModel): - """Order execution result model""" + """ + Order execution result model. + Provides details about a executed or closed trading order, including its outcome. + """ + order_id: str asset: str amount: float @@ -106,28 +200,40 @@ class OrderResult(BaseModel): profit: Optional[float] = None payout: Optional[float] = None error_message: Optional[str] = None - + class Config: frozen = True + class ServerTime(BaseModel): - """Server time synchronization model""" + """ + Server time synchronization model. + Used to synchronize local client time with the trading server's time, + important for accurate timestamping of trades and events. + """ + server_timestamp: float local_timestamp: float offset: float last_sync: datetime = Field(default_factory=datetime.now) - + class Config: frozen = True + class ConnectionInfo(BaseModel): - """Connection information model""" + """ + Connection information model. + Provides details about the current connection to the trading platform, + including URL, region, status, and connection metrics. + """ + url: str region: str status: ConnectionStatus connected_at: Optional[datetime] = None last_ping: Optional[datetime] = None reconnect_attempts: int = 0 - + class Config: frozen = True diff --git a/pocketoptionapi_async/monitoring.py b/pocketoptionapi_async/monitoring.py index a7bf4b2..accbb86 100644 --- a/pocketoptionapi_async/monitoring.py +++ b/pocketoptionapi_async/monitoring.py @@ -9,12 +9,12 @@ from dataclasses import dataclass from enum import Enum from collections import defaultdict, deque -import json from loguru import logger class ErrorSeverity(Enum): """Error severity levels""" + LOW = "low" MEDIUM = "medium" HIGH = "high" @@ -23,6 +23,7 @@ class ErrorSeverity(Enum): class ErrorCategory(Enum): """Error categories""" + CONNECTION = "connection" AUTHENTICATION = "authentication" TRADING = "trading" @@ -34,6 +35,7 @@ class ErrorCategory(Enum): @dataclass class ErrorEvent: """Error event data structure""" + timestamp: datetime error_type: str severity: ErrorSeverity @@ -48,6 +50,7 @@ class ErrorEvent: @dataclass class PerformanceMetrics: """Performance monitoring metrics""" + timestamp: datetime operation: str duration: float @@ -59,26 +62,28 @@ class PerformanceMetrics: class CircuitBreaker: """Circuit breaker pattern implementation""" - - def __init__(self, - failure_threshold: int = 5, - recovery_timeout: int = 60, - expected_exception: type = Exception): + + def __init__( + self, + failure_threshold: int = 5, + recovery_timeout: int = 60, + expected_exception: type = Exception, + ): self.failure_threshold = failure_threshold self.recovery_timeout = recovery_timeout self.expected_exception = expected_exception self.failure_count = 0 self.last_failure_time = None - self.state = 'CLOSED' # CLOSED, OPEN, HALF_OPEN - + self.state = "CLOSED" # CLOSED, OPEN, HALF_OPEN + async def call(self, func: Callable, *args, **kwargs): """Execute function with circuit breaker protection""" - if self.state == 'OPEN': + if self.state == "OPEN": if time.time() - self.last_failure_time < self.recovery_timeout: raise Exception("Circuit breaker is OPEN") else: - self.state = 'HALF_OPEN' - + self.state = "HALF_OPEN" + try: result = await func(*args, **kwargs) self.on_success() @@ -86,109 +91,115 @@ async def call(self, func: Callable, *args, **kwargs): except self.expected_exception as e: self.on_failure() raise e - + def on_success(self): """Handle successful operation""" self.failure_count = 0 - self.state = 'CLOSED' - + self.state = "CLOSED" + def on_failure(self): """Handle failed operation""" self.failure_count += 1 self.last_failure_time = time.time() - + if self.failure_count >= self.failure_threshold: - self.state = 'OPEN' - logger.warning(f"Circuit breaker opened after {self.failure_count} failures") + self.state = "OPEN" + logger.warning( + f"Circuit breaker opened after {self.failure_count} failures" + ) class RetryPolicy: """Advanced retry policy with exponential backoff""" - - def __init__(self, - max_attempts: int = 3, - base_delay: float = 1.0, - max_delay: float = 60.0, - exponential_base: float = 2.0, - jitter: bool = True): + + def __init__( + self, + max_attempts: int = 3, + base_delay: float = 1.0, + max_delay: float = 60.0, + exponential_base: float = 2.0, + jitter: bool = True, + ): self.max_attempts = max_attempts self.base_delay = base_delay self.max_delay = max_delay self.exponential_base = exponential_base self.jitter = jitter - + async def execute(self, func: Callable, *args, **kwargs): """Execute function with retry policy""" import random - + last_exception = None - + for attempt in range(self.max_attempts): try: return await func(*args, **kwargs) except Exception as e: last_exception = e - + if attempt == self.max_attempts - 1: break - + # Calculate delay delay = min( - self.base_delay * (self.exponential_base ** attempt), - self.max_delay + self.base_delay * (self.exponential_base**attempt), self.max_delay ) - + # Add jitter if self.jitter: - delay *= (0.5 + random.random() * 0.5) - - logger.warning(f"Attempt {attempt + 1} failed: {e}. Retrying in {delay:.2f}s") + delay *= 0.5 + random.random() * 0.5 + + logger.warning( + f"Attempt {attempt + 1} failed: {e}. Retrying in {delay:.2f}s" + ) await asyncio.sleep(delay) - + raise last_exception class ErrorMonitor: """Comprehensive error monitoring and handling system""" - - def __init__(self, - max_errors: int = 1000, - alert_threshold: int = 10, - alert_window: int = 300): # 5 minutes + + def __init__( + self, max_errors: int = 1000, alert_threshold: int = 10, alert_window: int = 300 + ): # 5 minutes self.max_errors = max_errors self.alert_threshold = alert_threshold self.alert_window = alert_window - + self.errors: deque = deque(maxlen=max_errors) self.error_counts: Dict[str, int] = defaultdict(int) self.error_patterns: Dict[str, List[datetime]] = defaultdict(list) self.alert_callbacks: List[Callable] = [] - + # Circuit breakers for different operations self.circuit_breakers = { - 'connection': CircuitBreaker(failure_threshold=3, recovery_timeout=30), - 'trading': CircuitBreaker(failure_threshold=5, recovery_timeout=60), - 'data': CircuitBreaker(failure_threshold=10, recovery_timeout=30) + "connection": CircuitBreaker(failure_threshold=3, recovery_timeout=30), + "trading": CircuitBreaker(failure_threshold=5, recovery_timeout=60), + "data": CircuitBreaker(failure_threshold=10, recovery_timeout=30), } - + # Retry policies self.retry_policies = { - 'connection': RetryPolicy(max_attempts=3, base_delay=2.0), - 'trading': RetryPolicy(max_attempts=2, base_delay=1.0), - 'data': RetryPolicy(max_attempts=5, base_delay=0.5) + "connection": RetryPolicy(max_attempts=3, base_delay=2.0), + "trading": RetryPolicy(max_attempts=2, base_delay=1.0), + "data": RetryPolicy(max_attempts=5, base_delay=0.5), } - + def add_alert_callback(self, callback: Callable): """Add alert callback function""" self.alert_callbacks.append(callback) - - async def record_error(self, - error_type: str, - severity: ErrorSeverity, - category: ErrorCategory, - message: str, - context: Dict[str, Any] = None, - stack_trace: str = None): + + async def record_error( + self, + error_type: str, + severity: ErrorSeverity, + category: ErrorCategory, + message: str, + context: Dict[str, Any] = None, + stack_trace: str = None, + ): """Record an error event""" error_event = ErrorEvent( timestamp=datetime.now(), @@ -197,104 +208,109 @@ async def record_error(self, category=category, message=message, context=context or {}, - stack_trace=stack_trace + stack_trace=stack_trace, ) - + self.errors.append(error_event) self.error_counts[error_type] += 1 self.error_patterns[error_type].append(error_event.timestamp) - + # Check for alert conditions await self._check_alert_conditions(error_event) - + logger.error(f"[{severity.value.upper()}] {category.value}: {message}") - + return error_event - + async def _check_alert_conditions(self, error_event: ErrorEvent): """Check if alert conditions are met""" current_time = datetime.now() window_start = current_time - timedelta(seconds=self.alert_window) - + # Count recent errors of the same type recent_errors = [ - timestamp for timestamp in self.error_patterns[error_event.error_type] + timestamp + for timestamp in self.error_patterns[error_event.error_type] if timestamp >= window_start ] - + if len(recent_errors) >= self.alert_threshold: await self._trigger_alert(error_event, len(recent_errors)) - + async def _trigger_alert(self, error_event: ErrorEvent, error_count: int): """Trigger alert for high error rate""" alert_data = { - 'error_type': error_event.error_type, - 'error_count': error_count, - 'time_window': self.alert_window, - 'severity': error_event.severity, - 'category': error_event.category, - 'latest_message': error_event.message + "error_type": error_event.error_type, + "error_count": error_count, + "time_window": self.alert_window, + "severity": error_event.severity, + "category": error_event.category, + "latest_message": error_event.message, } - - logger.critical(f"ALERT: High error rate for {error_event.error_type}: " - f"{error_count} errors in {self.alert_window}s") - + + logger.critical( + f"ALERT: High error rate for {error_event.error_type}: " + f"{error_count} errors in {self.alert_window}s" + ) + for callback in self.alert_callbacks: try: await callback(alert_data) except Exception as e: logger.error(f"Alert callback failed: {e}") - + def get_error_summary(self, hours: int = 24) -> Dict[str, Any]: """Get error summary for the specified time period""" cutoff_time = datetime.now() - timedelta(hours=hours) - + recent_errors = [ - error for error in self.errors - if error.timestamp >= cutoff_time + error for error in self.errors if error.timestamp >= cutoff_time ] - + summary = { - 'total_errors': len(recent_errors), - 'error_by_type': defaultdict(int), - 'error_by_category': defaultdict(int), - 'error_by_severity': defaultdict(int), - 'top_errors': [], - 'error_rate': len(recent_errors) / hours if hours > 0 else 0 + "total_errors": len(recent_errors), + "error_by_type": defaultdict(int), + "error_by_category": defaultdict(int), + "error_by_severity": defaultdict(int), + "top_errors": [], + "error_rate": len(recent_errors) / hours if hours > 0 else 0, } - + for error in recent_errors: - summary['error_by_type'][error.error_type] += 1 - summary['error_by_category'][error.category.value] += 1 - summary['error_by_severity'][error.severity.value] += 1 - + summary["error_by_type"][error.error_type] += 1 + summary["error_by_category"][error.category.value] += 1 + summary["error_by_severity"][error.severity.value] += 1 + # Get top errors - summary['top_errors'] = sorted( - summary['error_by_type'].items(), - key=lambda x: x[1], - reverse=True + summary["top_errors"] = sorted( + summary["error_by_type"].items(), key=lambda x: x[1], reverse=True )[:10] - + return summary - - async def execute_with_monitoring(self, - func: Callable, - operation_name: str, - category: ErrorCategory, - use_circuit_breaker: bool = False, - use_retry: bool = False, - *args, **kwargs): + + async def execute_with_monitoring( + self, + func: Callable, + operation_name: str, + category: ErrorCategory, + use_circuit_breaker: bool = False, + use_retry: bool = False, + *args, + **kwargs, + ): """Execute function with comprehensive error monitoring""" start_time = time.time() - + try: # Apply circuit breaker if requested if use_circuit_breaker and category.value in self.circuit_breakers: circuit_breaker = self.circuit_breakers[category.value] - + if use_retry and category.value in self.retry_policies: retry_policy = self.retry_policies[category.value] - result = await circuit_breaker.call(retry_policy.execute, func, *args, **kwargs) + result = await circuit_breaker.call( + retry_policy.execute, func, *args, **kwargs + ) else: result = await circuit_breaker.call(func, *args, **kwargs) elif use_retry and category.value in self.retry_policies: @@ -302,53 +318,53 @@ async def execute_with_monitoring(self, result = await retry_policy.execute(func, *args, **kwargs) else: result = await func(*args, **kwargs) - + # Record success metrics duration = time.time() - start_time logger.debug(f"Operation '{operation_name}' completed in {duration:.3f}s") - + return result - + except Exception as e: # Record error duration = time.time() - start_time - + await self.record_error( error_type=f"{operation_name}_error", severity=ErrorSeverity.MEDIUM, category=category, message=str(e), context={ - 'operation': operation_name, - 'duration': duration, - 'args': str(args)[:200], # Truncate for security - 'kwargs': str({k: str(v)[:100] for k, v in kwargs.items()})[:200] + "operation": operation_name, + "duration": duration, + "args": str(args)[:200], # Truncate for security + "kwargs": str({k: str(v)[:100] for k, v in kwargs.items()})[:200], }, - stack_trace=None # Could add traceback.format_exc() here + stack_trace=None, # Could add traceback.format_exc() here ) - + raise e class HealthChecker: """System health monitoring""" - + def __init__(self, check_interval: int = 30): self.check_interval = check_interval self.health_checks: Dict[str, Callable] = {} self.health_status: Dict[str, Dict[str, Any]] = {} self._running = False self._health_task: Optional[asyncio.Task] = None - + def register_health_check(self, name: str, check_func: Callable): """Register a health check function""" self.health_checks[name] = check_func - + async def start_monitoring(self): """Start health monitoring""" self._running = True self._health_task = asyncio.create_task(self._health_check_loop()) - + async def stop_monitoring(self): """Stop health monitoring""" self._running = False @@ -358,7 +374,7 @@ async def stop_monitoring(self): await self._health_task except asyncio.CancelledError: pass - + async def _health_check_loop(self): """Main health check loop""" while self._running: @@ -368,45 +384,47 @@ async def _health_check_loop(self): start_time = time.time() result = await check_func() duration = time.time() - start_time - + self.health_status[name] = { - 'status': 'healthy' if result else 'unhealthy', - 'last_check': datetime.now(), - 'response_time': duration, - 'details': result if isinstance(result, dict) else {} + "status": "healthy" if result else "unhealthy", + "last_check": datetime.now(), + "response_time": duration, + "details": result if isinstance(result, dict) else {}, } - + except Exception as e: self.health_status[name] = { - 'status': 'error', - 'last_check': datetime.now(), - 'error': str(e), - 'response_time': None + "status": "error", + "last_check": datetime.now(), + "error": str(e), + "response_time": None, } - + await asyncio.sleep(self.check_interval) - + except asyncio.CancelledError: break except Exception as e: logger.error(f"Health check loop error: {e}") await asyncio.sleep(self.check_interval) - + def get_health_report(self) -> Dict[str, Any]: """Get comprehensive health report""" overall_status = "healthy" unhealthy_services = [] - + for service, status in self.health_status.items(): - if status['status'] != 'healthy': - overall_status = "degraded" if overall_status == "healthy" else "unhealthy" + if status["status"] != "healthy": + overall_status = ( + "degraded" if overall_status == "healthy" else "unhealthy" + ) unhealthy_services.append(service) - + return { - 'overall_status': overall_status, - 'services': self.health_status, - 'unhealthy_services': unhealthy_services, - 'timestamp': datetime.now() + "overall_status": overall_status, + "services": self.health_status, + "unhealthy_services": unhealthy_services, + "timestamp": datetime.now(), } @@ -414,10 +432,14 @@ def get_health_report(self) -> Dict[str, Any]: error_monitor = ErrorMonitor() health_checker = HealthChecker() + # Example alert handler for demonstration async def default_alert_handler(alert_data: Dict[str, Any]): """Default alert handler""" - logger.critical(f"๐Ÿšจ ALERT: {alert_data['error_type']} - {alert_data['error_count']} errors") + logger.critical( + f"๐Ÿšจ ALERT: {alert_data['error_type']} - {alert_data['error_count']} errors" + ) + # Register default alert handler error_monitor.add_alert_callback(default_alert_handler) diff --git a/pocketoptionapi_async/utils.py b/pocketoptionapi_async/utils.py index 8a29a6e..86f93f7 100644 --- a/pocketoptionapi_async/utils.py +++ b/pocketoptionapi_async/utils.py @@ -12,252 +12,262 @@ from .models import Candle, OrderResult -def format_session_id(session_id: str, is_demo: bool = True, uid: int = 0, - platform: int = 1, is_fast_history: bool = True) -> str: +def format_session_id( + session_id: str, + is_demo: bool = True, + uid: int = 0, + platform: int = 1, + is_fast_history: bool = True, +) -> str: """ Format session ID for authentication - + Args: session_id: Raw session ID is_demo: Whether this is a demo account uid: User ID platform: Platform identifier (1=web, 3=mobile) is_fast_history: Enable fast history loading - + Returns: str: Formatted session message """ import json - + auth_data = { "session": session_id, "isDemo": 1 if is_demo else 0, "uid": uid, - "platform": platform + "platform": platform, } - + if is_fast_history: auth_data["isFastHistory"] = True - + return f'42["auth",{json.dumps(auth_data)}]' -def calculate_payout_percentage(entry_price: float, exit_price: float, - direction: str, payout_rate: float = 0.8) -> float: +def calculate_payout_percentage( + entry_price: float, exit_price: float, direction: str, payout_rate: float = 0.8 +) -> float: """ Calculate payout percentage for an order - + Args: entry_price: Entry price exit_price: Exit price direction: Order direction ('call' or 'put') payout_rate: Payout rate (default 80%) - + Returns: float: Payout percentage """ - if direction.lower() == 'call': + if direction.lower() == "call": win = exit_price > entry_price else: # put win = exit_price < entry_price - + return payout_rate if win else -1.0 def analyze_candles(candles: List[Candle]) -> Dict[str, Any]: """ Analyze candle data for basic statistics - + Args: candles: List of candle data - + Returns: Dict[str, Any]: Analysis results """ if not candles: return {} - + prices = [candle.close for candle in candles] highs = [candle.high for candle in candles] lows = [candle.low for candle in candles] - + return { - 'count': len(candles), - 'first_price': prices[0], - 'last_price': prices[-1], - 'price_change': prices[-1] - prices[0], - 'price_change_percent': ((prices[-1] - prices[0]) / prices[0]) * 100, - 'highest': max(highs), - 'lowest': min(lows), - 'average_close': sum(prices) / len(prices), - 'volatility': calculate_volatility(prices), - 'trend': determine_trend(prices) + "count": len(candles), + "first_price": prices[0], + "last_price": prices[-1], + "price_change": prices[-1] - prices[0], + "price_change_percent": ((prices[-1] - prices[0]) / prices[0]) * 100, + "highest": max(highs), + "lowest": min(lows), + "average_close": sum(prices) / len(prices), + "volatility": calculate_volatility(prices), + "trend": determine_trend(prices), } def calculate_volatility(prices: List[float], periods: int = 14) -> float: """ Calculate price volatility (standard deviation) - + Args: prices: List of prices periods: Number of periods for calculation - + Returns: float: Volatility value """ if len(prices) < periods: periods = len(prices) - + recent_prices = prices[-periods:] mean = sum(recent_prices) / len(recent_prices) - + variance = sum((price - mean) ** 2 for price in recent_prices) / len(recent_prices) - return variance ** 0.5 + return variance**0.5 def determine_trend(prices: List[float], periods: int = 10) -> str: """ Determine price trend direction - + Args: prices: List of prices periods: Number of periods to analyze - + Returns: str: Trend direction ('bullish', 'bearish', 'sideways') """ if len(prices) < periods: periods = len(prices) - + if periods < 2: - return 'sideways' - + return "sideways" + recent_prices = prices[-periods:] - first_half = recent_prices[:periods//2] - second_half = recent_prices[periods//2:] - + first_half = recent_prices[: periods // 2] + second_half = recent_prices[periods // 2 :] + first_avg = sum(first_half) / len(first_half) second_avg = sum(second_half) / len(second_half) - + change_percent = ((second_avg - first_avg) / first_avg) * 100 - + if change_percent > 0.1: - return 'bullish' + return "bullish" elif change_percent < -0.1: - return 'bearish' + return "bearish" else: - return 'sideways' + return "sideways" -def calculate_support_resistance(candles: List[Candle], periods: int = 20) -> Dict[str, float]: +def calculate_support_resistance( + candles: List[Candle], periods: int = 20 +) -> Dict[str, float]: """ Calculate support and resistance levels - + Args: candles: List of candle data periods: Number of periods to analyze - + Returns: Dict[str, float]: Support and resistance levels """ if len(candles) < periods: periods = len(candles) - + recent_candles = candles[-periods:] highs = [candle.high for candle in recent_candles] lows = [candle.low for candle in recent_candles] - + # Simple support/resistance calculation resistance = max(highs) support = min(lows) - - return { - 'support': support, - 'resistance': resistance, - 'range': resistance - support - } + + return {"support": support, "resistance": resistance, "range": resistance - support} def format_timeframe(seconds: int) -> str: """ Format timeframe seconds to human readable string - + Args: seconds: Timeframe in seconds - + Returns: str: Formatted timeframe (e.g., '1m', '5m', '1h') """ if seconds < 60: return f"{seconds}s" elif seconds < 3600: - return f"{seconds//60}m" + return f"{seconds // 60}m" elif seconds < 86400: - return f"{seconds//3600}h" + return f"{seconds // 3600}h" else: - return f"{seconds//86400}d" + return f"{seconds // 86400}d" def validate_asset_symbol(symbol: str, available_assets: Dict[str, int]) -> bool: """ Validate if asset symbol is available - + Args: symbol: Asset symbol to validate available_assets: Dictionary of available assets - + Returns: bool: True if asset is available """ return symbol in available_assets -def calculate_order_expiration(duration_seconds: int, - current_time: Optional[datetime] = None) -> datetime: +def calculate_order_expiration( + duration_seconds: int, current_time: Optional[datetime] = None +) -> datetime: """ Calculate order expiration time - + Args: duration_seconds: Duration in seconds current_time: Current time (default: now) - + Returns: datetime: Expiration time """ if current_time is None: current_time = datetime.now() - + return current_time + timedelta(seconds=duration_seconds) -def retry_async(max_attempts: int = 3, delay: float = 1.0, - backoff_factor: float = 2.0): +def retry_async(max_attempts: int = 3, delay: float = 1.0, backoff_factor: float = 2.0): """ Decorator for retrying async functions - + Args: max_attempts: Maximum number of attempts delay: Initial delay between attempts backoff_factor: Delay multiplier for each attempt """ + def decorator(func): async def wrapper(*args, **kwargs): current_delay = delay - + for attempt in range(max_attempts): try: return await func(*args, **kwargs) except Exception as e: if attempt == max_attempts - 1: - logger.error(f"Function {func.__name__} failed after {max_attempts} attempts: {e}") + logger.error( + f"Function {func.__name__} failed after {max_attempts} attempts: {e}" + ) raise - - logger.warning(f"Attempt {attempt + 1} failed for {func.__name__}: {e}") + + logger.warning( + f"Attempt {attempt + 1} failed for {func.__name__}: {e}" + ) await asyncio.sleep(current_delay) current_delay *= backoff_factor - + return wrapper + return decorator @@ -265,6 +275,7 @@ def performance_monitor(func): """ Decorator to monitor function performance """ + async def wrapper(*args, **kwargs): start_time = time.time() try: @@ -276,7 +287,7 @@ async def wrapper(*args, **kwargs): execution_time = time.time() - start_time logger.error(f"{func.__name__} failed after {execution_time:.3f}s: {e}") raise - + return wrapper @@ -284,11 +295,11 @@ class RateLimiter: """ Rate limiter for API calls """ - + def __init__(self, max_calls: int = 100, time_window: int = 60): """ Initialize rate limiter - + Args: max_calls: Maximum calls allowed time_window: Time window in seconds @@ -296,32 +307,33 @@ def __init__(self, max_calls: int = 100, time_window: int = 60): self.max_calls = max_calls self.time_window = time_window self.calls = [] - + async def acquire(self) -> bool: """ Acquire permission to make a call - + Returns: bool: True if permission granted """ now = time.time() - + # Remove old calls outside time window - self.calls = [call_time for call_time in self.calls - if now - call_time < self.time_window] - + self.calls = [ + call_time for call_time in self.calls if now - call_time < self.time_window + ] + # Check if we can make another call if len(self.calls) < self.max_calls: self.calls.append(now) return True - + # Calculate wait time wait_time = self.time_window - (now - self.calls[0]) if wait_time > 0: logger.warning(f"Rate limit exceeded, waiting {wait_time:.1f}s") await asyncio.sleep(wait_time) return await self.acquire() - + return True @@ -329,23 +341,23 @@ class OrderManager: """ Manage multiple orders and their results """ - + def __init__(self): self.active_orders: Dict[str, OrderResult] = {} self.completed_orders: Dict[str, OrderResult] = {} self.order_callbacks: Dict[str, List] = {} - + def add_order(self, order: OrderResult) -> None: """Add an active order""" self.active_orders[order.order_id] = order - + def complete_order(self, order_id: str, result: OrderResult) -> None: """Mark order as completed""" if order_id in self.active_orders: del self.active_orders[order_id] - + self.completed_orders[order_id] = result - + # Call any registered callbacks if order_id in self.order_callbacks: for callback in self.order_callbacks[order_id]: @@ -354,13 +366,13 @@ def complete_order(self, order_id: str, result: OrderResult) -> None: except Exception as e: logger.error(f"Error in order callback: {e}") del self.order_callbacks[order_id] - + def add_order_callback(self, order_id: str, callback) -> None: """Add callback for order completion""" if order_id not in self.order_callbacks: self.order_callbacks[order_id] = [] self.order_callbacks[order_id].append(callback) - + def get_order_status(self, order_id: str) -> Optional[OrderResult]: """Get order status""" if order_id in self.active_orders: @@ -368,11 +380,11 @@ def get_order_status(self, order_id: str) -> Optional[OrderResult]: elif order_id in self.completed_orders: return self.completed_orders[order_id] return None - + def get_active_count(self) -> int: """Get number of active orders""" return len(self.active_orders) - + def get_completed_count(self) -> int: """Get number of completed orders""" return len(self.completed_orders) @@ -381,28 +393,30 @@ def get_completed_count(self) -> int: def candles_to_dataframe(candles: List[Candle]) -> pd.DataFrame: """ Convert candles to pandas DataFrame - + Args: candles: List of candle objects - + Returns: pd.DataFrame: Candles as DataFrame """ data = [] for candle in candles: - data.append({ - 'timestamp': candle.timestamp, - 'open': candle.open, - 'high': candle.high, - 'low': candle.low, - 'close': candle.close, - 'volume': candle.volume, - 'asset': candle.asset - }) - + data.append( + { + "timestamp": candle.timestamp, + "open": candle.open, + "high": candle.high, + "low": candle.low, + "close": candle.close, + "volume": candle.volume, + "asset": candle.asset, + } + ) + df = pd.DataFrame(data) if not df.empty: - df.set_index('timestamp', inplace=True) + df.set_index("timestamp", inplace=True) df.sort_index(inplace=True) - + return df diff --git a/pocketoptionapi_async/websocket_client.py b/pocketoptionapi_async/websocket_client.py index 83e1421..7d54d75 100644 --- a/pocketoptionapi_async/websocket_client.py +++ b/pocketoptionapi_async/websocket_client.py @@ -7,10 +7,10 @@ import ssl import time from typing import Optional, Callable, Dict, Any, List, Deque -from datetime import datetime, timedelta +from datetime import datetime from collections import deque import websockets -from websockets.exceptions import ConnectionClosed, WebSocketException +from websockets.exceptions import ConnectionClosed from loguru import logger from .models import ConnectionInfo, ConnectionStatus, ServerTime @@ -20,31 +20,32 @@ class MessageBatcher: """Batch messages to improve performance""" - + def __init__(self, batch_size: int = 10, batch_timeout: float = 0.1): self.batch_size = batch_size self.batch_timeout = batch_timeout self.pending_messages: Deque[str] = deque() self._last_batch_time = time.time() self._batch_lock = asyncio.Lock() - + async def add_message(self, message: str) -> List[str]: """Add message to batch and return batch if ready""" async with self._batch_lock: self.pending_messages.append(message) current_time = time.time() - + # Check if batch is ready - if (len(self.pending_messages) >= self.batch_size or - current_time - self._last_batch_time >= self.batch_timeout): - + if ( + len(self.pending_messages) >= self.batch_size + or current_time - self._last_batch_time >= self.batch_timeout + ): batch = list(self.pending_messages) self.pending_messages.clear() self._last_batch_time = current_time return batch - + return [] - + async def flush_batch(self) -> List[str]: """Force flush current batch""" async with self._batch_lock: @@ -58,63 +59,65 @@ async def flush_batch(self) -> List[str]: class ConnectionPool: """Connection pool for better resource management""" - + def __init__(self, max_connections: int = 3): self.max_connections = max_connections self.active_connections: Dict[str, websockets.WebSocketServerProtocol] = {} self.connection_stats: Dict[str, Dict[str, Any]] = {} self._pool_lock = asyncio.Lock() - + async def get_best_connection(self) -> Optional[str]: """Get the best performing connection URL""" async with self._pool_lock: if not self.connection_stats: return None - + # Sort by response time and success rate best_url = min( self.connection_stats.keys(), key=lambda url: ( - self.connection_stats[url].get('avg_response_time', float('inf')), - -self.connection_stats[url].get('success_rate', 0) - ) + self.connection_stats[url].get("avg_response_time", float("inf")), + -self.connection_stats[url].get("success_rate", 0), + ), ) return best_url - + async def update_stats(self, url: str, response_time: float, success: bool): """Update connection statistics""" async with self._pool_lock: if url not in self.connection_stats: self.connection_stats[url] = { - 'response_times': deque(maxlen=100), - 'successes': 0, - 'failures': 0, - 'avg_response_time': 0, - 'success_rate': 0 + "response_times": deque(maxlen=100), + "successes": 0, + "failures": 0, + "avg_response_time": 0, + "success_rate": 0, } - + stats = self.connection_stats[url] - stats['response_times'].append(response_time) - + stats["response_times"].append(response_time) + if success: - stats['successes'] += 1 + stats["successes"] += 1 else: - stats['failures'] += 1 - + stats["failures"] += 1 + # Update averages - if stats['response_times']: - stats['avg_response_time'] = sum(stats['response_times']) / len(stats['response_times']) - - total_attempts = stats['successes'] + stats['failures'] + if stats["response_times"]: + stats["avg_response_time"] = sum(stats["response_times"]) / len( + stats["response_times"] + ) + + total_attempts = stats["successes"] + stats["failures"] if total_attempts > 0: - stats['success_rate'] = stats['successes'] / total_attempts + stats["success_rate"] = stats["successes"] / total_attempts class AsyncWebSocketClient: """ Professional async WebSocket client for PocketOption """ - + def __init__(self): self.websocket: Optional[websockets.WebSocketServerProtocol] = None self.connection_info: Optional[ConnectionInfo] = None @@ -124,94 +127,94 @@ def __init__(self): self._event_handlers: Dict[str, List[Callable]] = {} self._running = False self._reconnect_attempts = 0 - self._max_reconnect_attempts = CONNECTION_SETTINGS['max_reconnect_attempts'] - + self._max_reconnect_attempts = CONNECTION_SETTINGS["max_reconnect_attempts"] + # Performance improvements self._message_batcher = MessageBatcher() self._connection_pool = ConnectionPool() self._rate_limiter = asyncio.Semaphore(10) # Max 10 concurrent operations self._message_cache: Dict[str, Any] = {} self._cache_ttl = 5.0 # Cache TTL in seconds - + # Message processing optimization self._message_handlers = { - '0': self._handle_initial_message, - '2': self._handle_ping_message, - '40': self._handle_connection_message, - '451-[': self._handle_json_message_wrapper, - '42': self._handle_auth_message + "0": self._handle_initial_message, + "2": self._handle_ping_message, + "40": self._handle_connection_message, + "451-[": self._handle_json_message_wrapper, + "42": self._handle_auth_message, } - + async def connect(self, urls: List[str], ssid: str) -> bool: """ Connect to PocketOption WebSocket with fallback URLs - + Args: urls: List of WebSocket URLs to try ssid: Session ID for authentication - + Returns: bool: True if connected successfully """ for url in urls: try: logger.info(f"Attempting to connect to {url}") - + # SSL context setup ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE - + # Connect with timeout self.websocket = await asyncio.wait_for( websockets.connect( url, ssl=ssl_context, extra_headers=DEFAULT_HEADERS, - ping_interval=CONNECTION_SETTINGS['ping_interval'], - ping_timeout=CONNECTION_SETTINGS['ping_timeout'], - close_timeout=CONNECTION_SETTINGS['close_timeout'] + ping_interval=CONNECTION_SETTINGS["ping_interval"], + ping_timeout=CONNECTION_SETTINGS["ping_timeout"], + close_timeout=CONNECTION_SETTINGS["close_timeout"], ), - timeout=10.0 + timeout=10.0, ) - # Update connection info + # Update connection info region = self._extract_region_from_url(url) self.connection_info = ConnectionInfo( url=url, region=region, status=ConnectionStatus.CONNECTED, connected_at=datetime.now(), - reconnect_attempts=self._reconnect_attempts + reconnect_attempts=self._reconnect_attempts, ) - + logger.info(f"Connected to {region} region successfully") - # Start message handling + # Start message handling self._running = True - + # Send initial handshake and wait for completion await self._send_handshake(ssid) - + # Start background tasks after handshake is complete await self._start_background_tasks() - + self._reconnect_attempts = 0 return True - + except Exception as e: logger.warning(f"Failed to connect to {url}: {e}") if self.websocket: await self.websocket.close() self.websocket = None continue - + raise ConnectionError("Failed to connect to any WebSocket endpoint") - + async def disconnect(self): """Gracefully disconnect from WebSocket""" logger.info("Disconnecting from WebSocket") - + self._running = False - + # Cancel background tasks if self._ping_task and not self._ping_task.done(): self._ping_task.cancel() @@ -219,12 +222,12 @@ async def disconnect(self): await self._ping_task except asyncio.CancelledError: pass - + # Close WebSocket connection if self.websocket: await self.websocket.close() self.websocket = None - + # Update connection status if self.connection_info: self.connection_info = ConnectionInfo( @@ -232,68 +235,64 @@ async def disconnect(self): region=self.connection_info.region, status=ConnectionStatus.DISCONNECTED, connected_at=self.connection_info.connected_at, - reconnect_attempts=self.connection_info.reconnect_attempts + reconnect_attempts=self.connection_info.reconnect_attempts, ) - + async def send_message(self, message: str) -> None: """ Send message to WebSocket - + Args: message: Message to send """ if not self.websocket or self.websocket.closed: raise WebSocketError("WebSocket is not connected") - + try: await self.websocket.send(message) logger.debug(f"Sent message: {message}") except Exception as e: logger.error(f"Failed to send message: {e}") raise WebSocketError(f"Failed to send message: {e}") - + async def send_message_optimized(self, message: str) -> None: """ Send message with batching optimization - + Args: message: Message to send """ async with self._rate_limiter: if not self.websocket or self.websocket.closed: raise WebSocketError("WebSocket is not connected") - + try: start_time = time.time() - + # Add to batch batch = await self._message_batcher.add_message(message) - + # Send batch if ready if batch: for msg in batch: await self.websocket.send(msg) logger.debug(f"Sent batched message: {msg}") - + # Update connection stats response_time = time.time() - start_time if self.connection_info: await self._connection_pool.update_stats( - self.connection_info.url, - response_time, - True + self.connection_info.url, response_time, True ) - + except Exception as e: logger.error(f"Failed to send message: {e}") if self.connection_info: await self._connection_pool.update_stats( - self.connection_info.url, - 0, - False + self.connection_info.url, 0, False ) raise WebSocketError(f"Failed to send message: {e}") - + async def receive_messages(self) -> None: """ Continuously receive and process messages @@ -303,10 +302,10 @@ async def receive_messages(self) -> None: try: message = await asyncio.wait_for( self.websocket.recv(), - timeout=CONNECTION_SETTINGS['message_timeout'] + timeout=CONNECTION_SETTINGS["message_timeout"], ) await self._process_message(message) - + except asyncio.TimeoutError: logger.warning("Message receive timeout") continue @@ -314,15 +313,15 @@ async def receive_messages(self) -> None: logger.warning("WebSocket connection closed") await self._handle_disconnect() break - + except Exception as e: logger.error(f"Error in message receiving: {e}") await self._handle_disconnect() - + def add_event_handler(self, event: str, handler: Callable) -> None: """ Add event handler - + Args: event: Event name handler: Handler function @@ -330,11 +329,11 @@ def add_event_handler(self, event: str, handler: Callable) -> None: if event not in self._event_handlers: self._event_handlers[event] = [] self._event_handlers[event].append(handler) - + def remove_event_handler(self, event: str, handler: Callable) -> None: """ Remove event handler - + Args: event: Event name handler: Handler function to remove @@ -342,61 +341,69 @@ def remove_event_handler(self, event: str, handler: Callable) -> None: if event in self._event_handlers: try: self._event_handlers[event].remove(handler) - except ValueError: pass - + except ValueError: + pass + async def _send_handshake(self, ssid: str) -> None: """Send initial handshake messages (following old API pattern exactly)""" try: # Wait for initial connection message with "0" and "sid" (like old API) logger.debug("Waiting for initial handshake message...") - initial_message = await asyncio.wait_for(self.websocket.recv(), timeout=10.0) + initial_message = await asyncio.wait_for( + self.websocket.recv(), timeout=10.0 + ) logger.debug(f"Received initial: {initial_message}") - + # Check if it's the expected initial message format - if initial_message.startswith('0') and 'sid' in initial_message: + if initial_message.startswith("0") and "sid" in initial_message: # Send "40" response (like old API) await self.send_message("40") logger.debug("Sent '40' response") - + # Wait for connection establishment message with "40" and "sid" - conn_message = await asyncio.wait_for(self.websocket.recv(), timeout=10.0) + conn_message = await asyncio.wait_for( + self.websocket.recv(), timeout=10.0 + ) logger.debug(f"Received connection: {conn_message}") - + # Check if it's the expected connection message format - if conn_message.startswith('40') and 'sid' in conn_message: + if conn_message.startswith("40") and "sid" in conn_message: # Send SSID authentication (like old API) await self.send_message(ssid) logger.debug("Sent SSID authentication") else: - logger.warning(f"Unexpected connection message format: {conn_message}") + logger.warning( + f"Unexpected connection message format: {conn_message}" + ) else: logger.warning(f"Unexpected initial message format: {initial_message}") - + logger.debug("Handshake sequence completed") - + except asyncio.TimeoutError: logger.error("Handshake timeout - server didn't respond as expected") raise WebSocketError("Handshake timeout") except Exception as e: logger.error(f"Handshake failed: {e}") raise + async def _start_background_tasks(self) -> None: """Start background tasks""" # Start ping task self._ping_task = asyncio.create_task(self._ping_loop()) - + # Start message receiving task (only start it once here) asyncio.create_task(self.receive_messages()) - + async def _ping_loop(self) -> None: """Send periodic ping messages""" while self._running and self.websocket: try: - await asyncio.sleep(CONNECTION_SETTINGS['ping_interval']) - + await asyncio.sleep(CONNECTION_SETTINGS["ping_interval"]) + if self.websocket and not self.websocket.closed: await self.send_message('42["ps"]') - + # Update last ping time if self.connection_info: self.connection_info = ConnectionInfo( @@ -405,136 +412,137 @@ async def _ping_loop(self) -> None: status=self.connection_info.status, connected_at=self.connection_info.connected_at, last_ping=datetime.now(), - reconnect_attempts=self.connection_info.reconnect_attempts + reconnect_attempts=self.connection_info.reconnect_attempts, ) - + except Exception as e: logger.error(f"Ping failed: {e}") break + async def _process_message(self, message) -> None: """ Process incoming WebSocket message (following old API pattern exactly) - + Args: message: Raw message from WebSocket (bytes or str) """ try: # Handle bytes messages first (like old API) - these contain balance data if isinstance(message, bytes): - decoded_message = message.decode('utf-8') + decoded_message = message.decode("utf-8") try: # Try to parse as JSON (like old API) json_data = json.loads(decoded_message) logger.debug(f"Received JSON bytes message: {json_data}") - + # Handle balance data (like old API) if "balance" in json_data: balance_data = { - 'balance': json_data['balance'], - 'currency': 'USD', # Default currency - 'is_demo': bool(json_data.get('isDemo', 1)) + "balance": json_data["balance"], + "currency": "USD", # Default currency + "is_demo": bool(json_data.get("isDemo", 1)), } if "uid" in json_data: - balance_data['uid'] = json_data['uid'] - + balance_data["uid"] = json_data["uid"] + logger.info(f"Balance data received: {balance_data}") - await self._emit_event('balance_data', balance_data) - + await self._emit_event("balance_data", balance_data) + # Handle order data (like old API) - elif "requestId" in json_data and json_data["requestId"] == 'buy': - await self._emit_event('order_data', json_data) - + elif "requestId" in json_data and json_data["requestId"] == "buy": + await self._emit_event("order_data", json_data) + # Handle other JSON data else: - await self._emit_event('json_data', json_data) - + await self._emit_event("json_data", json_data) + except json.JSONDecodeError: # If not JSON, treat as regular bytes message logger.debug(f"Non-JSON bytes message: {decoded_message[:100]}...") - + return - + # Convert bytes to string if needed if isinstance(message, bytes): - message = message.decode('utf-8') - + message = message.decode("utf-8") + logger.debug(f"Received message: {message}") - + # Handle different message types - if message.startswith('0') and 'sid' in message: + if message.startswith("0") and "sid" in message: await self.send_message("40") - + elif message == "2": await self.send_message("3") - - elif message.startswith('40') and 'sid' in message: + + elif message.startswith("40") and "sid" in message: # Connection established - await self._emit_event('connected', {}) - - elif message.startswith('451-['): + await self._emit_event("connected", {}) + + elif message.startswith("451-["): # Parse JSON message json_part = message.split("-", 1)[1] data = json.loads(json_part) await self._handle_json_message(data) - - elif message.startswith('42') and 'NotAuthorized' in message: + + elif message.startswith("42") and "NotAuthorized" in message: logger.error("Authentication failed: Invalid SSID") - await self._emit_event('auth_error', {'message': 'Invalid SSID'}) - + await self._emit_event("auth_error", {"message": "Invalid SSID"}) + except Exception as e: logger.error(f"Error processing message: {e}") - + async def _handle_initial_message(self, message: str) -> None: """Handle initial connection message""" - if 'sid' in message: + if "sid" in message: await self.send_message("40") - + async def _handle_ping_message(self, message: str) -> None: """Handle ping message""" await self.send_message("3") - + async def _handle_connection_message(self, message: str) -> None: """Handle connection establishment message""" - if 'sid' in message: - await self._emit_event('connected', {}) - + if "sid" in message: + await self._emit_event("connected", {}) + async def _handle_json_message_wrapper(self, message: str) -> None: """Handle JSON message wrapper""" json_part = message.split("-", 1)[1] data = json.loads(json_part) await self._handle_json_message(data) - + async def _handle_auth_message(self, message: str) -> None: """Handle authentication message""" - if 'NotAuthorized' in message: + if "NotAuthorized" in message: logger.error("Authentication failed: Invalid SSID") - await self._emit_event('auth_error', {'message': 'Invalid SSID'}) - + await self._emit_event("auth_error", {"message": "Invalid SSID"}) + async def _process_message_optimized(self, message) -> None: """ Process incoming WebSocket message with optimization - + Args: message: Raw message from WebSocket (bytes or str) """ try: # Convert bytes to string if needed if isinstance(message, bytes): - message = message.decode('utf-8') - + message = message.decode("utf-8") + logger.debug(f"Received message: {message}") - + # Check cache first message_hash = hash(message) cached_time = self._message_cache.get(f"{message_hash}_time") - + if cached_time and time.time() - cached_time < self._cache_ttl: # Use cached processing result cached_result = self._message_cache.get(message_hash) if cached_result: - await self._emit_event('cached_message', cached_result) + await self._emit_event("cached_message", cached_result) return - + # Fast message routing for prefix, handler in self._message_handlers.items(): if message.startswith(prefix): @@ -543,56 +551,58 @@ async def _process_message_optimized(self, message) -> None: else: # Unknown message type logger.warning(f"Unknown message type: {message[:20]}...") - + # Cache processing result - self._message_cache[message_hash] = {'processed': True, 'type': 'unknown'} + self._message_cache[message_hash] = {"processed": True, "type": "unknown"} self._message_cache[f"{message_hash}_time"] = time.time() - + except Exception as e: logger.error(f"Error processing message: {e}") - + async def _handle_json_message(self, data: List[Any]) -> None: """ Handle JSON formatted messages - + Args: data: Parsed JSON data """ if not data or len(data) < 1: return - + event_type = data[0] event_data = data[1] if len(data) > 1 else {} - + # Handle specific events if event_type == "successauth": - await self._emit_event('authenticated', event_data) - + await self._emit_event("authenticated", event_data) + elif event_type == "successupdateBalance": - await self._emit_event('balance_updated', event_data) - + await self._emit_event("balance_updated", event_data) + elif event_type == "successopenOrder": - await self._emit_event('order_opened', event_data) - + await self._emit_event("order_opened", event_data) + elif event_type == "successcloseOrder": - await self._emit_event('order_closed', event_data) - + await self._emit_event("order_closed", event_data) + elif event_type == "updateStream": - await self._emit_event('stream_update', event_data) - + await self._emit_event("stream_update", event_data) + elif event_type == "loadHistoryPeriod": - await self._emit_event('candles_received', event_data) - + await self._emit_event("candles_received", event_data) + elif event_type == "updateHistoryNew": - await self._emit_event('history_update', event_data) - + await self._emit_event("history_update", event_data) + else: - await self._emit_event('unknown_event', {'type': event_type, 'data': event_data}) - + await self._emit_event( + "unknown_event", {"type": event_type, "data": event_data} + ) + async def _emit_event(self, event: str, data: Dict[str, Any]) -> None: """ Emit event to registered handlers - + Args: event: Event name data: Event data @@ -606,7 +616,7 @@ async def _emit_event(self, event: str, data: Dict[str, Any]) -> None: handler(data) except Exception as e: logger.error(f"Error in event handler for {event}: {e}") - + async def _handle_disconnect(self) -> None: """Handle WebSocket disconnection""" if self.connection_info: @@ -616,36 +626,40 @@ async def _handle_disconnect(self) -> None: status=ConnectionStatus.DISCONNECTED, connected_at=self.connection_info.connected_at, last_ping=self.connection_info.last_ping, - reconnect_attempts=self.connection_info.reconnect_attempts + reconnect_attempts=self.connection_info.reconnect_attempts, ) - - await self._emit_event('disconnected', {}) - + + await self._emit_event("disconnected", {}) + # Attempt reconnection if enabled if self._reconnect_attempts < self._max_reconnect_attempts: self._reconnect_attempts += 1 - logger.info(f"Attempting reconnection {self._reconnect_attempts}/{self._max_reconnect_attempts}") - await asyncio.sleep(CONNECTION_SETTINGS['reconnect_delay']) + logger.info( + f"Attempting reconnection {self._reconnect_attempts}/{self._max_reconnect_attempts}" + ) + await asyncio.sleep(CONNECTION_SETTINGS["reconnect_delay"]) # Note: Reconnection logic would be handled by the main client - + def _extract_region_from_url(self, url: str) -> str: """Extract region name from URL""" try: # Extract from URLs like "wss://api-eu.po.market/..." - parts = url.split('//')[1].split('.')[0] - if 'api-' in parts: - return parts.replace('api-', '').upper() - elif 'demo' in parts: - return 'DEMO' + parts = url.split("//")[1].split(".")[0] + if "api-" in parts: + return parts.replace("api-", "").upper() + elif "demo" in parts: + return "DEMO" else: - return 'UNKNOWN' + return "UNKNOWN" except: - return 'UNKNOWN' - + return "UNKNOWN" + @property def is_connected(self) -> bool: """Check if WebSocket is connected""" - return (self.websocket is not None and - not self.websocket.closed and - self.connection_info and - self.connection_info.status == ConnectionStatus.CONNECTED) + return ( + self.websocket is not None + and not self.websocket.closed + and self.connection_info + and self.connection_info.status == ConnectionStatus.CONNECTED + ) diff --git a/run_api.ps1 b/run_api.ps1 deleted file mode 100644 index 90f9f8f..0000000 --- a/run_api.ps1 +++ /dev/null @@ -1,231 +0,0 @@ -# PowerShell script to test and run the new Async PocketOption API -# Run this script from the project root directory - -param( - [string]$Command = "test", - [string]$SessionId = "", - [switch]$Demo = $true, - [switch]$Help -) - -function Show-Help { - Write-Host "PocketOption Async API - PowerShell Helper Script" -ForegroundColor Cyan - Write-Host "=" * 50 -ForegroundColor Cyan - Write-Host "" - Write-Host "Usage:" -ForegroundColor Yellow - Write-Host " .\run_api.ps1 [command] [options]" -ForegroundColor White - Write-Host "" - Write-Host "Commands:" -ForegroundColor Yellow - Write-Host " test - Run API tests (default)" -ForegroundColor White - Write-Host " install - Install dependencies" -ForegroundColor White - Write-Host " example - Run basic example" -ForegroundColor White - Write-Host " migrate - Show migration guide" -ForegroundColor White - Write-Host " help - Show this help" -ForegroundColor White - Write-Host "" - Write-Host "Options:" -ForegroundColor Yellow - Write-Host " -SessionId - Your PocketOption session ID" -ForegroundColor White - Write-Host " -Demo - Use demo account (default: true)" -ForegroundColor White - Write-Host " -Help - Show this help" -ForegroundColor White - Write-Host "" - Write-Host "Examples:" -ForegroundColor Yellow - Write-Host " .\run_api.ps1 test" -ForegroundColor Green - Write-Host " .\run_api.ps1 install" -ForegroundColor Green - Write-Host " .\run_api.ps1 example -SessionId 'your_session_id'" -ForegroundColor Green - Write-Host "" -} - -function Test-PythonInstalled { - try { - $pythonVersion = python --version 2>&1 - if ($LASTEXITCODE -eq 0) { - Write-Host "โœ… Python found: $pythonVersion" -ForegroundColor Green - return $true - } - } - catch { - Write-Host "โŒ Python not found. Please install Python 3.8+ first." -ForegroundColor Red - Write-Host " Download from: https://www.python.org/downloads/" -ForegroundColor Yellow - return $false - } - return $false -} - -function Install-Dependencies { - Write-Host "๐Ÿ“ฆ Installing dependencies..." -ForegroundColor Cyan - - if (-not (Test-PythonInstalled)) { - return $false - } - - try { - Write-Host "Installing main dependencies..." -ForegroundColor Yellow - python -m pip install --upgrade pip - python -m pip install -r requirements.txt - - Write-Host "Installing development dependencies..." -ForegroundColor Yellow - python -m pip install -r requirements-dev.txt - - Write-Host "โœ… Dependencies installed successfully!" -ForegroundColor Green - return $true - } - catch { - Write-Host "โŒ Failed to install dependencies: $($_.Exception.Message)" -ForegroundColor Red - return $false - } -} - -function Run-Tests { - Write-Host "๐Ÿงช Running API tests..." -ForegroundColor Cyan - - if (-not (Test-PythonInstalled)) { - return - } - - # Set session ID if provided - if ($SessionId) { - $env:POCKET_OPTION_SSID = $SessionId - Write-Host "๐Ÿ”‘ Session ID set for testing" -ForegroundColor Yellow - } - - try { - # Run the new API test - Write-Host "Running new async API test..." -ForegroundColor Yellow - python test_new_api.py - - # Run pytest if available - $pytestOutput = python -m pytest --version 2>&1 - if ($LASTEXITCODE -eq 0) { - Write-Host "`nRunning pytest suite..." -ForegroundColor Yellow - python -m pytest tests/ -v - } - else { - Write-Host "โ„น๏ธ pytest not available, install with: pip install pytest pytest-asyncio" -ForegroundColor Blue - } - } - catch { - Write-Host "โŒ Tests failed: $($_.Exception.Message)" -ForegroundColor Red - } -} - -function Run-Example { - Write-Host "๐Ÿš€ Running example..." -ForegroundColor Cyan - - if (-not (Test-PythonInstalled)) { - return - } - - if (-not $SessionId) { - Write-Host "โš ๏ธ No session ID provided. Using mock session for demonstration." -ForegroundColor Yellow - Write-Host " For live testing, use: .\run_api.ps1 example -SessionId 'your_session_id'" -ForegroundColor Blue - } - else { - $env:POCKET_OPTION_SSID = $SessionId - Write-Host "๐Ÿ”‘ Using provided session ID" -ForegroundColor Green - } - - try { - python examples/async_examples.py - } - catch { - Write-Host "โŒ Example failed: $($_.Exception.Message)" -ForegroundColor Red - } -} - -function Show-Migration { - Write-Host "๐Ÿ”„ Running migration guide..." -ForegroundColor Cyan - - if (-not (Test-PythonInstalled)) { - return - } - - try { - python migration_guide.py - } - catch { - Write-Host "โŒ Migration guide failed: $($_.Exception.Message)" -ForegroundColor Red - } -} - -function Show-Status { - Write-Host "๐Ÿ“Š PocketOption API Status" -ForegroundColor Cyan - Write-Host "=" * 30 -ForegroundColor Cyan - - # Check Python - if (Test-PythonInstalled) { - Write-Host "โœ… Python installed" -ForegroundColor Green - } - else { - Write-Host "โŒ Python not found" -ForegroundColor Red - } - - # Check if new API files exist - $apiFiles = @( - "pocketoptionapi_async\__init__.py", - "pocketoptionapi_async\client.py", - "pocketoptionapi_async\models.py", - "pocketoptionapi_async\websocket_client.py" - ) - - $missingFiles = @() - foreach ($file in $apiFiles) { - if (Test-Path $file) { - Write-Host "โœ… $file" -ForegroundColor Green - } - else { - Write-Host "โŒ $file" -ForegroundColor Red - $missingFiles += $file - } - } - - if ($missingFiles.Count -eq 0) { - Write-Host "`n๐ŸŽ‰ All API files present!" -ForegroundColor Green - } - else { - Write-Host "`nโš ๏ธ Missing files detected. Re-run setup." -ForegroundColor Yellow - } - - # Check environment - if ($env:POCKET_OPTION_SSID) { - Write-Host "๐Ÿ”‘ Session ID configured" -ForegroundColor Green - } - else { - Write-Host "โš ๏ธ No session ID set" -ForegroundColor Yellow - Write-Host " Set with: `$env:POCKET_OPTION_SSID='your_session_id'" -ForegroundColor Blue - } -} - -# Main script logic -if ($Help -or $Command -eq "help") { - Show-Help - exit 0 -} - -Write-Host "๐Ÿš€ PocketOption Async API Helper" -ForegroundColor Cyan -Write-Host "Current Time: $(Get-Date)" -ForegroundColor Gray -Write-Host "" - -switch ($Command.ToLower()) { - "test" { - Show-Status - Write-Host "" - Run-Tests - } - "install" { - Install-Dependencies - } - "example" { - Run-Example - } - "migrate" { - Show-Migration - } - "status" { - Show-Status - } - default { - Write-Host "โŒ Unknown command: $Command" -ForegroundColor Red - Write-Host "Use '.\run_api.ps1 help' for available commands" -ForegroundColor Yellow - } -} - -Write-Host "`nโœจ Script completed!" -ForegroundColor Green diff --git a/setup.py b/setup.py deleted file mode 100644 index a7c727f..0000000 --- a/setup.py +++ /dev/null @@ -1,68 +0,0 @@ -""" -Setup script for the Professional Async PocketOption API -""" - -from setuptools import setup, find_packages - -with open("README_ASYNC.md", "r", encoding="utf-8") as fh: - long_description = fh.read() - -with open("requirements.txt", "r", encoding="utf-8") as fh: - requirements = [line.strip() for line in fh if line.strip() and not line.startswith("#")] - -setup( - name="pocketoption-async-api", - version="2.0.0", - author="PocketOptionAPI Team", - author_email="support@pocketoptionapi.com", - description="Professional async PocketOption API with modern Python practices", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/your-username/PocketOptionAPI", - packages=find_packages(), - classifiers=[ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Office/Business :: Financial :: Investment", - "Topic :: Software Development :: Libraries :: Python Modules", - ], - python_requires=">=3.8", - install_requires=requirements, - extras_require={ - "dev": [ - "pytest>=7.0.0", - "pytest-asyncio>=0.21.0", - "pytest-cov>=4.0.0", - "black>=23.0.0", - "isort>=5.12.0", - "flake8>=6.0.0", - "mypy>=1.0.0", - ], - "examples": [ - "jupyter>=1.0.0", - "matplotlib>=3.5.0", - "seaborn>=0.11.0", - ] - }, - entry_points={ - "console_scripts": [ - "pocketoption-migrate=migration_guide:main", - ], - }, - project_urls={ - "Bug Reports": "https://github.com/your-username/PocketOptionAPI/issues", - "Source": "https://github.com/your-username/PocketOptionAPI", - "Documentation": "https://github.com/your-username/PocketOptionAPI#readme", - }, - keywords="pocketoption trading api async binary options forex crypto", - include_package_data=True, - zip_safe=False, -) diff --git a/test.py b/test.py deleted file mode 100644 index aaf6398..0000000 --- a/test.py +++ /dev/null @@ -1,48 +0,0 @@ -import random -import time -import dotenv -from pocketoptionapi_async import AsyncPocketOptionClient -import logging -import os -import time - -dotenv.load_dotenv() - -ssid = (r'42["auth",{"session":"t04ppgptp3404h0lajp4bo7smh","isDemo":1,"uid":101884312,"platform":2,"isFastHistory":true}]') #os.getenv("SSID") -print(ssid) -api = AsyncPocketOptionClient(ssid=ssid, is_demo=True) -async def main(): - await api.connect() - - await asyncio.sleep(5) # Wait for connection to establish - - balance = await api.get_balance() - print(f"Balance: {balance}") - - # order_Data = await api.place_order( - # asset="EURUSD_otc", - # amount=1, - # direction="call", - # duration=5 - # ) - # print(f"OrderData: {order_Data}") - # order_info = await api.check_order_result(order_Data.order_id) - # print(f"OrderInfo: {order_info}") - - candles = await api.get_candles( - asset="EURUSD_otc", - timeframe=5, - count=100 - ) - print(candles) - -if __name__ == "__main__": - import asyncio - try: - asyncio.run(main()) - except KeyboardInterrupt: - print("Exiting...") - except Exception as e: - print(f"An error occurred: {e}") - finally: - print("Closing connection...") \ No newline at end of file diff --git a/test/client_test_1.py b/test/client_test_1.py deleted file mode 100644 index d7f5620..0000000 --- a/test/client_test_1.py +++ /dev/null @@ -1,62 +0,0 @@ -import websockets -import anyio -from rich.pretty import pprint as print -import json - -SESSION = r'42["auth",{"session":"a:4:{s:10:\"session_id\";s:32:\"c53eec05c6f8a8be2d134d4fd55266f8\";s:10:\"ip_address\";s:14:\"46.138.176.190\";s:10:\"user_agent\";s:101:\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\";s:13:\"last_activity\";i:1707850603;}9f383935faff5a86bc1658bbde8c61e7","isDemo":1,"uid":72038016,"platform":3}]' - - -async def websocket_client(url, pro): - while True: - try: - async with websockets.connect( - url, - extra_headers={ - "Origin": "https://pocket-link19.co", - #"Origin": "https://po.trade/" - }, - ) as websocket: - async for message in websocket: - await pro(message, websocket, url) - except KeyboardInterrupt: - exit() - except Exception as e: - print(e) - print("Connection lost... reconnecting") - await anyio.sleep(5) - return True - - -async def pro(message, websocket, url): - # if byte data - if type(message) == type(b""): - # cut 100 first symbols of byte date to prevent spam - print(str(message)[:100]) - return - else: - print(message) - - # Code to make order - # data = r'42["openOrder",{"asset":"#AXP_otc","amount":1,"action":"call","isDemo":1,"requestId":14680035,"optionType":100,"time":20}]' - # await websocket.send(data) - - if message.startswith('0{"sid":"'): - print(f"{url.split('/')[2]} got 0 sid send 40 ") - await websocket.send("40") - elif message == "2": - # ping-pong thing - print(f"{url.split('/')[2]} got 2 send 3") - await websocket.send("3") - - if message.startswith('40{"sid":"'): - print(f"{url.split('/')[2]} got 40 sid send session") - await websocket.send(SESSION) - - -async def main(): - url = "wss://api-l.po.market/socket.io/?EIO=4&transport=websocket" - await websocket_client(url, pro) - - -if __name__ == "__main__": - anyio.run(main) \ No newline at end of file diff --git a/test_candles_fix.py b/test_candles_fix.py deleted file mode 100644 index e69de29..0000000 diff --git a/test_order_logging_fixes.py b/test_order_logging_fixes.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/client_test.py b/tests/client_test.py index d3feefd..451b058 100644 --- a/tests/client_test.py +++ b/tests/client_test.py @@ -1,7 +1,6 @@ import websockets import anyio from rich.pretty import pprint as print -import json from pocketoptionapi.constants import REGION SESSION = r'42["auth",{"session":"a:4:{s:10:\"session_id\";s:32:\"a1dc009a7f1f0c8267d940d0a036156f\";s:10:\"ip_address\";s:12:\"190.162.4.33\";s:10:\"user_agent\";s:120:\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 OP\";s:13:\"last_activity\";i:1709914958;}793884e7bccc89ec798c06ef1279fcf2","isDemo":0,"uid":27658142,"platform":1}]' @@ -12,9 +11,9 @@ async def websocket_client(url, pro): print(f"Trying {i}...") try: async with websockets.connect( - i, #teoria de los issues + i, # teoria de los issues extra_headers={ - #"Origin": "https://pocket-link19.co", + # "Origin": "https://pocket-link19.co", "Origin": "https://po.trade/" }, ) as websocket: @@ -62,4 +61,4 @@ async def main(): if __name__ == "__main__": - anyio.run(main) \ No newline at end of file + anyio.run(main) diff --git a/tests/enhanced_test.py b/tests/enhanced_test.py index 646850e..d6ee530 100644 --- a/tests/enhanced_test.py +++ b/tests/enhanced_test.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Enhanced PocketOption API Testing with Monitoring and Performance Analysis """ @@ -10,118 +9,122 @@ from loguru import logger from pocketoptionapi_async import ( - AsyncPocketOptionClient, OrderDirection, error_monitor, health_checker, - ErrorSeverity, ErrorCategory + AsyncPocketOptionClient, + error_monitor, + health_checker, + ErrorSeverity, + ErrorCategory, ) class EnhancedAPITester: """Enhanced API testing with monitoring capabilities""" - + def __init__(self, session_id: str, is_demo: bool = True): self.session_id = session_id self.is_demo = is_demo self.test_results = {} - + # Setup monitoring callbacks error_monitor.add_alert_callback(self.handle_error_alert) - + async def handle_error_alert(self, alert_data): """Handle error alerts from the monitoring system""" - logger.warning(f"๐Ÿšจ ERROR ALERT: {alert_data['error_type']} - " - f"{alert_data['error_count']} errors in {alert_data['time_window']}s") - + logger.warning( + f"๐Ÿšจ ERROR ALERT: {alert_data['error_type']} - " + f"{alert_data['error_count']} errors in {alert_data['time_window']}s" + ) + async def test_enhanced_connection(self): """Test connection with enhanced monitoring""" - logger.info("๐Ÿ”— Testing Enhanced Connection with Monitoring") + logger.info("Testing Enhanced Connection with Monitoring") print("=" * 60) - + client = AsyncPocketOptionClient( - session_id=self.session_id, - is_demo=self.is_demo + session_id=self.session_id, is_demo=self.is_demo ) - + try: # Test connection with monitoring success = await client.execute_with_monitoring( - operation_name="connection_test", - func=client.connect + operation_name="connection_test", func=client.connect ) - + if success: - logger.success("โœ… Connection successful with monitoring") - + logger.success(" Connection successful with monitoring") + # Get health status health = await client.get_health_status() logger.info(f"๐Ÿฅ Health Status: {health['overall_status']}") - + # Get performance metrics metrics = await client.get_performance_metrics() - logger.info(f"๐Ÿ“Š Performance: {len(metrics['operation_metrics'])} tracked operations") - + logger.info( + f"๐Ÿ“Š Performance: {len(metrics['operation_metrics'])} tracked operations" + ) + # Test some operations await self.test_monitored_operations(client) - + else: - logger.error("โŒ Connection failed") - + logger.error("Connection failed") + except Exception as e: - logger.error(f"โŒ Connection test failed: {e}") + logger.error(f"Connection test failed: {e}") finally: await client.disconnect() - + async def test_monitored_operations(self, client): """Test various operations with monitoring""" - logger.info("๐Ÿงช Testing Monitored Operations") - + logger.info("Testing Monitored Operations") + operations = [ ("balance_check", lambda: client.get_balance()), ("candles_fetch", lambda: client.get_candles("EURUSD_otc", 60, 50)), ("health_check", lambda: client.get_health_status()), ] - + for op_name, operation in operations: try: start_time = time.time() - - result = await client.execute_with_monitoring( - operation_name=op_name, - func=operation + + await client.execute_with_monitoring( + operation_name=op_name, func=operation ) - + duration = time.time() - start_time - logger.success(f"โœ… {op_name}: {duration:.3f}s") - + logger.success(f" {op_name}: {duration:.3f}s") + except Exception as e: - logger.error(f"โŒ {op_name} failed: {e}") - + logger.error(f"{op_name} failed: {e}") + # Record error in monitoring system await error_monitor.record_error( error_type=f"{op_name}_failure", severity=ErrorSeverity.MEDIUM, category=ErrorCategory.TRADING, message=str(e), - context={'operation': op_name} + context={"operation": op_name}, ) - + async def test_circuit_breaker(self): """Test circuit breaker functionality""" logger.info("โšก Testing Circuit Breaker") print("=" * 60) - + from pocketoptionapi_async.monitoring import CircuitBreaker - + # Create a circuit breaker breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=5) - + async def failing_operation(): """Simulated failing operation""" raise Exception("Simulated failure") - + async def working_operation(): """Simulated working operation""" return "Success" - + # Test circuit breaker with failing operations failures = 0 for i in range(5): @@ -129,89 +132,88 @@ async def working_operation(): await breaker.call(failing_operation) except Exception as e: failures += 1 - logger.warning(f"Attempt {i+1}: {e} (State: {breaker.state})") - + logger.warning(f"Attempt {i + 1}: {e} (State: {breaker.state})") + logger.info(f"๐Ÿ”ฅ Circuit breaker opened after {failures} failures") - + # Wait for recovery logger.info("โณ Waiting for recovery...") await asyncio.sleep(6) - + # Test with working operation try: result = await breaker.call(working_operation) - logger.success(f"โœ… Circuit breaker recovered: {result}") + logger.success(f" Circuit breaker recovered: {result}") except Exception as e: - logger.error(f"โŒ Recovery failed: {e}") - + logger.error(f"Recovery failed: {e}") + async def test_concurrent_performance(self): """Test concurrent operations performance""" - logger.info("๐Ÿš€ Testing Concurrent Performance") + logger.info("Testing Concurrent Performance") print("=" * 60) - + async def create_and_test_client(client_id: int): """Create client and perform operations""" client = AsyncPocketOptionClient( - session_id=self.session_id, - is_demo=self.is_demo + session_id=self.session_id, is_demo=self.is_demo ) - + start_time = time.time() - + try: await client.connect() - + if client.is_connected: # Perform some operations balance = await client.get_balance() health = await client.get_health_status() - + duration = time.time() - start_time return { - 'client_id': client_id, - 'success': True, - 'duration': duration, - 'balance': balance.balance if balance else None, - 'health': health['overall_status'] + "client_id": client_id, + "success": True, + "duration": duration, + "balance": balance.balance if balance else None, + "health": health["overall_status"], } - + except Exception as e: return { - 'client_id': client_id, - 'success': False, - 'duration': time.time() - start_time, - 'error': str(e) + "client_id": client_id, + "success": False, + "duration": time.time() - start_time, + "error": str(e), } finally: await client.disconnect() - + # Run concurrent clients concurrent_level = 3 logger.info(f"Running {concurrent_level} concurrent clients...") - + start_time = time.time() tasks = [create_and_test_client(i) for i in range(concurrent_level)] results = await asyncio.gather(*tasks, return_exceptions=True) total_time = time.time() - start_time - + # Analyze results - successful = [r for r in results if isinstance(r, dict) and r.get('success')] - failed = [r for r in results if not (isinstance(r, dict) and r.get('success'))] - - logger.info(f"๐Ÿ“Š Concurrent Test Results:") - logger.info(f" โœ… Successful: {len(successful)}/{concurrent_level}") - logger.info(f" โŒ Failed: {len(failed)}") + successful = [r for r in results if isinstance(r, dict) and r.get("success")] + failed = [r for r in results if not (isinstance(r, dict) and r.get("success"))] + + logger.info("๐Ÿ“Š Concurrent Test Results:") + logger.info(f" Successful: {len(successful)}/{concurrent_level}") + logger.info(f" Failed: {len(failed)}") logger.info(f" โฑ๏ธ Total Time: {total_time:.3f}s") - + if successful: - avg_time = sum(r['duration'] for r in successful) / len(successful) - logger.info(f" ๐Ÿ“ˆ Avg Client Time: {avg_time:.3f}s") - + avg_time = sum(r["duration"] for r in successful) / len(successful) + logger.info(f" Avg Client Time: {avg_time:.3f}s") + async def test_error_monitoring(self): """Test error monitoring capabilities""" logger.info("๐Ÿ” Testing Error Monitoring") print("=" * 60) - + # Generate some test errors test_errors = [ ("connection_timeout", ErrorSeverity.HIGH, ErrorCategory.CONNECTION), @@ -219,24 +221,24 @@ async def test_error_monitoring(self): ("order_rejected", ErrorSeverity.MEDIUM, ErrorCategory.TRADING), ("data_parsing", ErrorSeverity.LOW, ErrorCategory.DATA), ] - + for error_type, severity, category in test_errors: await error_monitor.record_error( error_type=error_type, severity=severity, category=category, message=f"Test {error_type} error", - context={'test': True, 'timestamp': datetime.now().isoformat()} + context={"test": True, "timestamp": datetime.now().isoformat()}, ) - + # Get error summary summary = error_monitor.get_error_summary(hours=1) - - logger.info("๐Ÿ“ˆ Error Summary:") + + logger.info("Error Summary:") logger.info(f" Total Errors: {summary['total_errors']}") logger.info(f" Error Rate: {summary['error_rate']:.2f}/hour") logger.info(f" Top Errors: {summary['top_errors'][:3]}") - + # Test alert threshold logger.info("๐Ÿšจ Testing Alert Threshold...") for i in range(15): # Generate many errors to trigger alert @@ -244,129 +246,133 @@ async def test_error_monitoring(self): error_type="test_spam", severity=ErrorSeverity.LOW, category=ErrorCategory.SYSTEM, - message=f"Spam test error #{i+1}", - context={'spam_test': True} + message=f"Spam test error #{i + 1}", + context={"spam_test": True}, ) - + async def generate_performance_report(self): """Generate comprehensive performance report""" logger.info("๐Ÿ“‹ Generating Performance Report") print("=" * 60) - + # Get error summary error_summary = error_monitor.get_error_summary() - + # Start health monitoring briefly await health_checker.start_monitoring() await asyncio.sleep(2) # Let it collect some data health_report = health_checker.get_health_report() await health_checker.stop_monitoring() - + report = [] report.append("=" * 80) report.append("ENHANCED POCKETOPTION API PERFORMANCE REPORT") report.append("=" * 80) report.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") report.append("") - + # Error monitoring section report.append("๐Ÿ” ERROR MONITORING") report.append("-" * 40) report.append(f"Total Errors: {error_summary['total_errors']}") report.append(f"Error Rate: {error_summary['error_rate']:.2f}/hour") report.append("") - - if error_summary['top_errors']: + + if error_summary["top_errors"]: report.append("Top Error Types:") - for error_type, count in error_summary['top_errors'][:5]: + for error_type, count in error_summary["top_errors"][:5]: report.append(f" โ€ข {error_type}: {count}") - + report.append("") - + # Health monitoring section report.append("๐Ÿฅ HEALTH MONITORING") report.append("-" * 40) report.append(f"Overall Status: {health_report['overall_status']}") - - if health_report['unhealthy_services']: - report.append(f"Unhealthy Services: {', '.join(health_report['unhealthy_services'])}") - + + if health_report["unhealthy_services"]: + report.append( + f"Unhealthy Services: {', '.join(health_report['unhealthy_services'])}" + ) + report.append("") - + # Recommendations section report.append("๐Ÿ’ก RECOMMENDATIONS") report.append("-" * 40) - - if error_summary['total_errors'] > 10: + + if error_summary["total_errors"] > 10: report.append("โ€ข High error count detected - investigate error patterns") - - if health_report['overall_status'] != 'healthy': + + if health_report["overall_status"] != "healthy": report.append("โ€ข System health issues detected - check service status") - - if not error_summary['top_errors']: - report.append("โ€ข โœ… No significant errors detected") - + + if not error_summary["top_errors"]: + report.append("โ€ข No significant errors detected") + report.append("") report.append("=" * 80) - + report_text = "\n".join(report) print(report_text) - + # Save to file with open("enhanced_performance_report.txt", "w") as f: f.write(report_text) - + logger.success("๐Ÿ“„ Report saved to enhanced_performance_report.txt") - + async def run_all_tests(self): """Run all enhanced tests""" - logger.info("๐Ÿš€ Starting Enhanced API Tests") + logger.info("Starting Enhanced API Tests") print("=" * 80) - + tests = [ ("Enhanced Connection", self.test_enhanced_connection), ("Circuit Breaker", self.test_circuit_breaker), ("Concurrent Performance", self.test_concurrent_performance), ("Error Monitoring", self.test_error_monitoring), ] - + for test_name, test_func in tests: try: - logger.info(f"๐Ÿงช Running {test_name}...") + logger.info(f"Running {test_name}...") await test_func() - logger.success(f"โœ… {test_name} completed") + logger.success(f" {test_name} completed") await asyncio.sleep(1) # Brief pause between tests except Exception as e: - logger.error(f"โŒ {test_name} failed: {e}") - + logger.error(f"{test_name} failed: {e}") + # Generate final report await self.generate_performance_report() async def main(): """Main enhanced testing function""" - print("๐Ÿš€ ENHANCED POCKETOPTION API TESTING") + print("ENHANCED POCKETOPTION API TESTING") print("=" * 80) print("Features being tested:") - print(" โœ… Enhanced Error Monitoring") - print(" โœ… Circuit Breaker Pattern") - print(" โœ… Health Checks") - print(" โœ… Performance Metrics") - print(" โœ… Concurrent Operations") - print(" โœ… Retry Policies") + print(" Enhanced Error Monitoring") + print(" Circuit Breaker Pattern") + print(" Health Checks") + print(" Performance Metrics") + print(" Concurrent Operations") + print(" Retry Policies") print("=" * 80) print() - + # Get session ID from environment or use test session - session_id = os.getenv('POCKET_OPTION_SSID', 'n1p5ah5u8t9438rbunpgrq0hlq') - - if session_id == 'test_session_id': - logger.warning("โš ๏ธ Using test session ID - set POCKET_OPTION_SSID for real testing") - + session_id = os.getenv("POCKET_OPTION_SSID", "n1p5ah5u8t9438rbunpgrq0hlq") + + if session_id == "test_session_id": + logger.warning( + " Using test session ID - set POCKET_OPTION_SSID for real testing" + ) + # Create and run enhanced tester tester = EnhancedAPITester(session_id=session_id, is_demo=True) await tester.run_all_tests() - + logger.success("๐ŸŽ‰ All enhanced tests completed!") @@ -376,12 +382,12 @@ async def main(): logger.add( "enhanced_api_test.log", format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", - level="DEBUG" + level="DEBUG", ) logger.add( lambda msg: print(msg, end=""), format="{time:HH:mm:ss} | {level} | {message}", - level="INFO" + level="INFO", ) - + asyncio.run(main()) diff --git a/tests/integration_tests.py b/tests/integration_tests.py index 04d218d..2ca0147 100644 --- a/tests/integration_tests.py +++ b/tests/integration_tests.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Integration Testing Script Tests all components of the PocketOption Async API working together @@ -7,31 +6,30 @@ import asyncio import time import json -from datetime import datetime, timedelta -from typing import Dict, List, Any, Optional +from datetime import datetime +from typing import Dict, Any from loguru import logger from pocketoptionapi_async.client import AsyncPocketOptionClient -from pocketoptionapi_async.models import OrderDirection, TimeFrame +from pocketoptionapi_async.models import TimeFrame from connection_keep_alive import ConnectionKeepAlive from connection_monitor import ConnectionMonitor -from advanced_testing_suite import AdvancedTestSuite from load_testing_tool import LoadTester, LoadTestConfig class IntegrationTester: """Comprehensive integration testing""" - + def __init__(self, ssid: str): self.ssid = ssid self.test_results = {} self.start_time = datetime.now() - + async def run_full_integration_tests(self) -> Dict[str, Any]: """Run all integration tests""" - logger.info("๐ŸŽฏ Starting Full Integration Testing Suite") + logger.info("Starting Full Integration Testing Suite") logger.info("=" * 60) - + # Test phases test_phases = [ ("Basic Connectivity", self.test_basic_connectivity), @@ -43,67 +41,83 @@ async def run_full_integration_tests(self) -> Dict[str, Any]: ("Error Recovery", self.test_error_recovery), ("Performance Under Load", self.test_performance_integration), ("Data Consistency", self.test_data_consistency_integration), - ("Long-Running Stability", self.test_long_running_stability) + ("Long-Running Stability", self.test_long_running_stability), ] - + for phase_name, phase_func in test_phases: logger.info(f"\n๐Ÿ” {phase_name}") logger.info("-" * 40) - + try: start_time = time.time() result = await phase_func() duration = time.time() - start_time - + self.test_results[phase_name] = { - 'status': 'PASSED' if result['success'] else 'FAILED', - 'duration': duration, - 'details': result + "status": "PASSED" if result["success"] else "FAILED", + "duration": duration, + "details": result, } - - status_emoji = "โœ…" if result['success'] else "โŒ" - logger.info(f"{status_emoji} {phase_name}: {'PASSED' if result['success'] else 'FAILED'} ({duration:.2f}s)") - - if not result['success']: + + status_emoji = "" if result["success"] else "โŒ" + logger.info( + f"{status_emoji} {phase_name}: {'PASSED' if result['success'] else 'FAILED'} ({duration:.2f}s)" + ) + + if not result["success"]: logger.error(f" Error: {result.get('error', 'Unknown error')}") - + except Exception as e: self.test_results[phase_name] = { - 'status': 'ERROR', - 'duration': 0, - 'error': str(e) + "status": "ERROR", + "duration": 0, + "error": str(e), } logger.error(f"๐Ÿ’ฅ {phase_name}: ERROR - {e}") - + return self._generate_integration_report() - + async def test_basic_connectivity(self) -> Dict[str, Any]: """Test basic connectivity across all client types""" try: results = {} - + # Test regular client logger.info("Testing regular AsyncPocketOptionClient...") client = AsyncPocketOptionClient(self.ssid, is_demo=True) success = await client.connect() if success: balance = await client.get_balance() - results['regular_client'] = {'connected': True, 'balance_retrieved': balance is not None} + results["regular_client"] = { + "connected": True, + "balance_retrieved": balance is not None, + } await client.disconnect() else: - results['regular_client'] = {'connected': False, 'balance_retrieved': False} - + results["regular_client"] = { + "connected": False, + "balance_retrieved": False, + } + # Test persistent client logger.info("Testing persistent connection client...") - persistent_client = AsyncPocketOptionClient(self.ssid, is_demo=True, persistent_connection=True) + persistent_client = AsyncPocketOptionClient( + self.ssid, is_demo=True, persistent_connection=True + ) success = await persistent_client.connect() if success: balance = await persistent_client.get_balance() - results['persistent_client'] = {'connected': True, 'balance_retrieved': balance is not None} + results["persistent_client"] = { + "connected": True, + "balance_retrieved": balance is not None, + } await persistent_client.disconnect() else: - results['persistent_client'] = {'connected': False, 'balance_retrieved': False} - + results["persistent_client"] = { + "connected": False, + "balance_retrieved": False, + } + # Test keep-alive manager logger.info("Testing ConnectionKeepAlive...") keep_alive = ConnectionKeepAlive(self.ssid, is_demo=True) @@ -111,23 +125,26 @@ async def test_basic_connectivity(self) -> Dict[str, Any]: if success: # Test message sending message_sent = await keep_alive.send_message('42["ps"]') - results['keep_alive'] = {'connected': True, 'message_sent': message_sent} + results["keep_alive"] = { + "connected": True, + "message_sent": message_sent, + } await keep_alive.stop_persistent_connection() else: - results['keep_alive'] = {'connected': False, 'message_sent': False} - + results["keep_alive"] = {"connected": False, "message_sent": False} + # Evaluate overall success - all_connected = all(r.get('connected', False) for r in results.values()) - + all_connected = all(r.get("connected", False) for r in results.values()) + return { - 'success': all_connected, - 'details': results, - 'message': f"Connectivity test: {len([r for r in results.values() if r.get('connected', False)])}/{len(results)} clients connected" + "success": all_connected, + "details": results, + "message": f"Connectivity test: {len([r for r in results.values() if r.get('connected', False)])}/{len(results)} clients connected", } - + except Exception as e: - return {'success': False, 'error': str(e)} - + return {"success": False, "error": str(e)} + async def test_ssid_formats(self) -> Dict[str, Any]: """Test different SSID format compatibility""" try: @@ -138,67 +155,78 @@ async def test_ssid_formats(self) -> Dict[str, Any]: # Alternative format test (would need valid session) # r'42["auth",{"session":"alternative_session","isDemo":1,"uid":123,"platform":1}]' ] - + results = {} - + for i, ssid_format in enumerate(ssid_formats): - logger.info(f"Testing SSID format {i+1}...") + logger.info(f"Testing SSID format {i + 1}...") try: client = AsyncPocketOptionClient(ssid_format, is_demo=True) success = await client.connect() - + if success: # Test basic operation balance = await client.get_balance() - results[f'format_{i+1}'] = { - 'connected': True, - 'authenticated': balance is not None, - 'format': ssid_format[:50] + "..." if len(ssid_format) > 50 else ssid_format + results[f"format_{i + 1}"] = { + "connected": True, + "authenticated": balance is not None, + "format": ssid_format[:50] + "..." + if len(ssid_format) > 50 + else ssid_format, } await client.disconnect() else: - results[f'format_{i+1}'] = { - 'connected': False, - 'authenticated': False, - 'format': ssid_format[:50] + "..." if len(ssid_format) > 50 else ssid_format + results[f"format_{i + 1}"] = { + "connected": False, + "authenticated": False, + "format": ssid_format[:50] + "..." + if len(ssid_format) > 50 + else ssid_format, } - + except Exception as e: - results[f'format_{i+1}'] = { - 'connected': False, - 'authenticated': False, - 'error': str(e), - 'format': ssid_format[:50] + "..." if len(ssid_format) > 50 else ssid_format + results[f"format_{i + 1}"] = { + "connected": False, + "authenticated": False, + "error": str(e), + "format": ssid_format[:50] + "..." + if len(ssid_format) > 50 + else ssid_format, } - + # At least one format should work - any_success = any(r.get('connected', False) for r in results.values()) - + any_success = any(r.get("connected", False) for r in results.values()) + return { - 'success': any_success, - 'details': results, - 'message': f"SSID format test: {len([r for r in results.values() if r.get('connected', False)])}/{len(results)} formats successful" + "success": any_success, + "details": results, + "message": f"SSID format test: {len([r for r in results.values() if r.get('connected', False)])}/{len(results)} formats successful", } - + except Exception as e: - return {'success': False, 'error': str(e)} - + return {"success": False, "error": str(e)} + async def test_persistent_integration(self) -> Dict[str, Any]: """Test persistent connection integration""" try: logger.info("Testing persistent connection features...") - - client = AsyncPocketOptionClient(self.ssid, is_demo=True, persistent_connection=True, auto_reconnect=True) - + + client = AsyncPocketOptionClient( + self.ssid, is_demo=True, persistent_connection=True, auto_reconnect=True + ) + # Connect success = await client.connect(persistent=True) if not success: - return {'success': False, 'error': 'Failed to establish persistent connection'} - + return { + "success": False, + "error": "Failed to establish persistent connection", + } + # Test multiple operations operations_successful = 0 total_operations = 10 - + for i in range(total_operations): try: # Alternate between different operations @@ -214,61 +242,71 @@ async def test_persistent_integration(self) -> Dict[str, Any]: success = await client.send_message('42["ps"]') if success: operations_successful += 1 - + await asyncio.sleep(0.5) - + except Exception as e: logger.warning(f"Operation {i} failed: {e}") - + # Test connection stats stats = client.get_connection_stats() - + await client.disconnect() - + success_rate = operations_successful / total_operations - + return { - 'success': success_rate > 0.8, # 80% success rate - 'details': { - 'operations_successful': operations_successful, - 'total_operations': total_operations, - 'success_rate': success_rate, - 'connection_stats': stats + "success": success_rate > 0.8, # 80% success rate + "details": { + "operations_successful": operations_successful, + "total_operations": total_operations, + "success_rate": success_rate, + "connection_stats": stats, }, - 'message': f"Persistent connection test: {operations_successful}/{total_operations} operations successful" + "message": f"Persistent connection test: {operations_successful}/{total_operations} operations successful", } - + except Exception as e: - return {'success': False, 'error': str(e)} - + return {"success": False, "error": str(e)} + async def test_keep_alive_integration(self) -> Dict[str, Any]: """Test keep-alive integration with all features""" try: logger.info("Testing keep-alive integration...") - + keep_alive = ConnectionKeepAlive(self.ssid, is_demo=True) - + # Event tracking events_received = [] - + async def track_events(event_type): def handler(data): - events_received.append({'type': event_type, 'time': datetime.now(), 'data': data}) + events_received.append( + {"type": event_type, "time": datetime.now(), "data": data} + ) + return handler - + # Add event handlers - keep_alive.add_event_handler('connected', await track_events('connected')) - keep_alive.add_event_handler('message_received', await track_events('message')) - keep_alive.add_event_handler('reconnected', await track_events('reconnected')) - + keep_alive.add_event_handler("connected", await track_events("connected")) + keep_alive.add_event_handler( + "message_received", await track_events("message") + ) + keep_alive.add_event_handler( + "reconnected", await track_events("reconnected") + ) + # Start connection success = await keep_alive.start_persistent_connection() if not success: - return {'success': False, 'error': 'Failed to start keep-alive connection'} - + return { + "success": False, + "error": "Failed to start keep-alive connection", + } + # Let it run and test messaging await asyncio.sleep(5) - + # Send test messages messages_sent = 0 for i in range(10): @@ -276,90 +314,92 @@ def handler(data): if success: messages_sent += 1 await asyncio.sleep(0.5) - + # Get statistics stats = keep_alive.get_connection_stats() - + await keep_alive.stop_persistent_connection() - + # Analyze results - connected_events = [e for e in events_received if e['type'] == 'connected'] - message_events = [e for e in events_received if e['type'] == 'message'] - + connected_events = [e for e in events_received if e["type"] == "connected"] + message_events = [e for e in events_received if e["type"] == "message"] + return { - 'success': len(connected_events) > 0 and messages_sent > 8, # Most messages should succeed - 'details': { - 'connected_events': len(connected_events), - 'message_events': len(message_events), - 'messages_sent': messages_sent, - 'connection_stats': stats, - 'total_events': len(events_received) + "success": len(connected_events) > 0 + and messages_sent > 8, # Most messages should succeed + "details": { + "connected_events": len(connected_events), + "message_events": len(message_events), + "messages_sent": messages_sent, + "connection_stats": stats, + "total_events": len(events_received), }, - 'message': f"Keep-alive test: {len(connected_events)} connections, {messages_sent} messages sent, {len(message_events)} messages received" + "message": f"Keep-alive test: {len(connected_events)} connections, {messages_sent} messages sent, {len(message_events)} messages received", } - + except Exception as e: - return {'success': False, 'error': str(e)} - + return {"success": False, "error": str(e)} + async def test_monitoring_integration(self) -> Dict[str, Any]: """Test monitoring integration""" try: logger.info("Testing monitoring integration...") - + monitor = ConnectionMonitor(self.ssid, is_demo=True) - + # Start monitoring success = await monitor.start_monitoring(persistent_connection=True) if not success: - return {'success': False, 'error': 'Failed to start monitoring'} - + return {"success": False, "error": "Failed to start monitoring"} + # Let monitoring run await asyncio.sleep(10) - + # Get stats and generate report stats = monitor.get_real_time_stats() historical = monitor.get_historical_metrics(hours=1) report = monitor.generate_diagnostics_report() - + await monitor.stop_monitoring() - + return { - 'success': stats['is_connected'] and stats['total_messages'] > 0, - 'details': { - 'real_time_stats': stats, - 'historical_metrics_count': historical['connection_metrics_count'], - 'health_score': report['health_score'], - 'health_status': report['health_status'] + "success": stats["is_connected"] and stats["total_messages"] > 0, + "details": { + "real_time_stats": stats, + "historical_metrics_count": historical["connection_metrics_count"], + "health_score": report["health_score"], + "health_status": report["health_status"], }, - 'message': f"Monitoring test: Health score {report['health_score']}/100, {stats['total_messages']} messages monitored" + "message": f"Monitoring test: Health score {report['health_score']}/100, {stats['total_messages']} messages monitored", } - + except Exception as e: - return {'success': False, 'error': str(e)} - + return {"success": False, "error": str(e)} + async def test_multi_client_scenarios(self) -> Dict[str, Any]: """Test multiple clients working simultaneously""" try: logger.info("Testing multi-client scenarios...") - + clients = [] - results = [] - + # Create multiple clients for i in range(3): client = AsyncPocketOptionClient( self.ssid, is_demo=True, - persistent_connection=(i % 2 == 0) # Mix of persistent and regular + persistent_connection=(i % 2 == 0), # Mix of persistent and regular ) clients.append(client) - + # Connect all clients connect_tasks = [client.connect() for client in clients] - connect_results = await asyncio.gather(*connect_tasks, return_exceptions=True) - + connect_results = await asyncio.gather( + *connect_tasks, return_exceptions=True + ) + successful_connections = sum(1 for r in connect_results if r is True) - + # Run operations on all connected clients async def client_operations(client, client_id): operations = 0 @@ -372,457 +412,574 @@ async def client_operations(client, client_id): except Exception as e: logger.warning(f"Client {client_id} operation failed: {e}") return operations - + # Run operations concurrently operation_tasks = [ - client_operations(client, i) - for i, client in enumerate(clients) + client_operations(client, i) + for i, client in enumerate(clients) if connect_results[i] is True ] - + if operation_tasks: - operation_results = await asyncio.gather(*operation_tasks, return_exceptions=True) - total_operations = sum(r for r in operation_results if isinstance(r, int)) + operation_results = await asyncio.gather( + *operation_tasks, return_exceptions=True + ) + total_operations = sum( + r for r in operation_results if isinstance(r, int) + ) else: total_operations = 0 - + # Cleanup - disconnect_tasks = [client.disconnect() for client in clients if client.is_connected] + disconnect_tasks = [ + client.disconnect() for client in clients if client.is_connected + ] if disconnect_tasks: await asyncio.gather(*disconnect_tasks, return_exceptions=True) - + return { - 'success': successful_connections >= 2 and total_operations > 10, # At least 2 clients, 10+ operations - 'details': { - 'total_clients': len(clients), - 'successful_connections': successful_connections, - 'total_operations': total_operations, - 'avg_operations_per_client': total_operations / max(successful_connections, 1) + "success": successful_connections >= 2 + and total_operations > 10, # At least 2 clients, 10+ operations + "details": { + "total_clients": len(clients), + "successful_connections": successful_connections, + "total_operations": total_operations, + "avg_operations_per_client": total_operations + / max(successful_connections, 1), }, - 'message': f"Multi-client test: {successful_connections}/{len(clients)} clients connected, {total_operations} total operations" + "message": f"Multi-client test: {successful_connections}/{len(clients)} clients connected, {total_operations} total operations", } - + except Exception as e: - return {'success': False, 'error': str(e)} - + return {"success": False, "error": str(e)} + async def test_error_recovery(self) -> Dict[str, Any]: """Test error recovery mechanisms""" try: logger.info("Testing error recovery...") - - client = AsyncPocketOptionClient(self.ssid, is_demo=True, auto_reconnect=True) - + + client = AsyncPocketOptionClient( + self.ssid, is_demo=True, auto_reconnect=True + ) + # Connect success = await client.connect() if not success: - return {'success': False, 'error': 'Initial connection failed'} - + return {"success": False, "error": "Initial connection failed"} + # Test graceful handling of invalid operations error_scenarios = [] - + # Test 1: Invalid asset try: await client.get_candles("INVALID_ASSET", TimeFrame.M1, 10) - error_scenarios.append({'test': 'invalid_asset', 'handled': False}) + error_scenarios.append({"test": "invalid_asset", "handled": False}) except Exception: - error_scenarios.append({'test': 'invalid_asset', 'handled': True}) - + error_scenarios.append({"test": "invalid_asset", "handled": True}) + # Test 2: Invalid timeframe try: await client.get_candles("EURUSD", "INVALID_TIMEFRAME", 10) - error_scenarios.append({'test': 'invalid_timeframe', 'handled': False}) + error_scenarios.append({"test": "invalid_timeframe", "handled": False}) except Exception: - error_scenarios.append({'test': 'invalid_timeframe', 'handled': True}) - + error_scenarios.append({"test": "invalid_timeframe", "handled": True}) + # Test 3: Connection still works after errors try: balance = await client.get_balance() connection_recovered = balance is not None except Exception: connection_recovered = False - + await client.disconnect() - - errors_handled = sum(1 for scenario in error_scenarios if scenario['handled']) - + + errors_handled = sum( + 1 for scenario in error_scenarios if scenario["handled"] + ) + return { - 'success': errors_handled >= 2 and connection_recovered, - 'details': { - 'error_scenarios': error_scenarios, - 'errors_handled': errors_handled, - 'connection_recovered': connection_recovered + "success": errors_handled >= 2 and connection_recovered, + "details": { + "error_scenarios": error_scenarios, + "errors_handled": errors_handled, + "connection_recovered": connection_recovered, }, - 'message': f"Error recovery test: {errors_handled}/{len(error_scenarios)} errors handled gracefully, connection recovery: {connection_recovered}" + "message": f"Error recovery test: {errors_handled}/{len(error_scenarios)} errors handled gracefully, connection recovery: {connection_recovered}", } - + except Exception as e: - return {'success': False, 'error': str(e)} - + return {"success": False, "error": str(e)} + async def test_performance_integration(self) -> Dict[str, Any]: """Test performance under integrated load""" try: logger.info("Testing performance integration...") - + # Use load tester for performance testing load_tester = LoadTester(self.ssid, is_demo=True) - + config = LoadTestConfig( concurrent_clients=2, operations_per_client=5, operation_delay=0.5, use_persistent_connection=True, - stress_mode=False + stress_mode=False, ) - + report = await load_tester.run_load_test(config) - + summary = report["test_summary"] - + # Performance thresholds good_throughput = summary["avg_operations_per_second"] > 1.0 good_success_rate = summary["success_rate"] > 0.9 reasonable_duration = summary["total_duration"] < 30.0 - + return { - 'success': good_throughput and good_success_rate and reasonable_duration, - 'details': { - 'throughput': summary["avg_operations_per_second"], - 'success_rate': summary["success_rate"], - 'duration': summary["total_duration"], - 'total_operations': summary["total_operations"] + "success": good_throughput + and good_success_rate + and reasonable_duration, + "details": { + "throughput": summary["avg_operations_per_second"], + "success_rate": summary["success_rate"], + "duration": summary["total_duration"], + "total_operations": summary["total_operations"], }, - 'message': f"Performance test: {summary['avg_operations_per_second']:.1f} ops/sec, {summary['success_rate']:.1%} success rate" + "message": f"Performance test: {summary['avg_operations_per_second']:.1f} ops/sec, {summary['success_rate']:.1%} success rate", } - + except Exception as e: - return {'success': False, 'error': str(e)} - + return {"success": False, "error": str(e)} + async def test_data_consistency_integration(self) -> Dict[str, Any]: """Test data consistency across different connection types""" try: logger.info("Testing data consistency...") - + # Get data from different client types data_sources = {} - + # Regular client client1 = AsyncPocketOptionClient(self.ssid, is_demo=True) success = await client1.connect() if success: balance1 = await client1.get_balance() candles1 = await client1.get_candles("EURUSD", TimeFrame.M1, 5) - data_sources['regular'] = { - 'balance': balance1.balance if balance1 else None, - 'candles_count': len(candles1), - 'latest_candle': candles1[-1].close if candles1 else None + data_sources["regular"] = { + "balance": balance1.balance if balance1 else None, + "candles_count": len(candles1), + "latest_candle": candles1[-1].close if candles1 else None, } await client1.disconnect() - + # Persistent client - client2 = AsyncPocketOptionClient(self.ssid, is_demo=True, persistent_connection=True) + client2 = AsyncPocketOptionClient( + self.ssid, is_demo=True, persistent_connection=True + ) success = await client2.connect() if success: balance2 = await client2.get_balance() candles2 = await client2.get_candles("EURUSD", TimeFrame.M1, 5) - data_sources['persistent'] = { - 'balance': balance2.balance if balance2 else None, - 'candles_count': len(candles2), - 'latest_candle': candles2[-1].close if candles2 else None + data_sources["persistent"] = { + "balance": balance2.balance if balance2 else None, + "candles_count": len(candles2), + "latest_candle": candles2[-1].close if candles2 else None, } await client2.disconnect() - + # Compare data consistency consistency_checks = [] - - if 'regular' in data_sources and 'persistent' in data_sources: + + if "regular" in data_sources and "persistent" in data_sources: # Balance should be the same (allowing for small differences due to timing) - balance_diff = abs(data_sources['regular']['balance'] - data_sources['persistent']['balance']) if data_sources['regular']['balance'] and data_sources['persistent']['balance'] else 0 - consistency_checks.append({ - 'check': 'balance_consistency', - 'consistent': balance_diff < 0.01 # Allow 1 cent difference - }) - + balance_diff = ( + abs( + data_sources["regular"]["balance"] + - data_sources["persistent"]["balance"] + ) + if data_sources["regular"]["balance"] + and data_sources["persistent"]["balance"] + else 0 + ) + consistency_checks.append( + { + "check": "balance_consistency", + "consistent": balance_diff < 0.01, # Allow 1 cent difference + } + ) + # Candle count should be the same - consistency_checks.append({ - 'check': 'candles_count_consistency', - 'consistent': data_sources['regular']['candles_count'] == data_sources['persistent']['candles_count'] - }) - - consistent_checks = sum(1 for check in consistency_checks if check['consistent']) - + consistency_checks.append( + { + "check": "candles_count_consistency", + "consistent": data_sources["regular"]["candles_count"] + == data_sources["persistent"]["candles_count"], + } + ) + + consistent_checks = sum( + 1 for check in consistency_checks if check["consistent"] + ) + return { - 'success': consistent_checks >= len(consistency_checks) * 0.8, # 80% consistency - 'details': { - 'data_sources': data_sources, - 'consistency_checks': consistency_checks, - 'consistent_checks': consistent_checks, - 'total_checks': len(consistency_checks) + "success": consistent_checks + >= len(consistency_checks) * 0.8, # 80% consistency + "details": { + "data_sources": data_sources, + "consistency_checks": consistency_checks, + "consistent_checks": consistent_checks, + "total_checks": len(consistency_checks), }, - 'message': f"Data consistency test: {consistent_checks}/{len(consistency_checks)} checks passed" + "message": f"Data consistency test: {consistent_checks}/{len(consistency_checks)} checks passed", } - + except Exception as e: - return {'success': False, 'error': str(e)} - + return {"success": False, "error": str(e)} + async def test_long_running_stability(self) -> Dict[str, Any]: """Test stability over extended period""" try: logger.info("Testing long-running stability...") - - client = AsyncPocketOptionClient(self.ssid, is_demo=True, persistent_connection=True, auto_reconnect=True) - + + client = AsyncPocketOptionClient( + self.ssid, is_demo=True, persistent_connection=True, auto_reconnect=True + ) + success = await client.connect() if not success: - return {'success': False, 'error': 'Failed to connect for stability test'} - + return { + "success": False, + "error": "Failed to connect for stability test", + } + # Track operations over time operations_log = [] start_time = datetime.now() - + # Run for 60 seconds while (datetime.now() - start_time).total_seconds() < 60: try: # Perform operation balance = await client.get_balance() - operations_log.append({ - 'time': datetime.now(), - 'success': balance is not None, - 'operation': 'get_balance' - }) - + operations_log.append( + { + "time": datetime.now(), + "success": balance is not None, + "operation": "get_balance", + } + ) + # Send ping ping_success = await client.send_message('42["ps"]') - operations_log.append({ - 'time': datetime.now(), - 'success': ping_success, - 'operation': 'ping' - }) - + operations_log.append( + { + "time": datetime.now(), + "success": ping_success, + "operation": "ping", + } + ) + await asyncio.sleep(2) # Operation every 2 seconds - + except Exception as e: - operations_log.append({ - 'time': datetime.now(), - 'success': False, - 'operation': 'error', - 'error': str(e) - }) - + operations_log.append( + { + "time": datetime.now(), + "success": False, + "operation": "error", + "error": str(e), + } + ) + await client.disconnect() - + # Analyze stability total_operations = len(operations_log) - successful_operations = sum(1 for op in operations_log if op['success']) - success_rate = successful_operations / total_operations if total_operations > 0 else 0 - + successful_operations = sum(1 for op in operations_log if op["success"]) + success_rate = ( + successful_operations / total_operations if total_operations > 0 else 0 + ) + # Check for any major gaps in operations time_gaps = [] for i in range(1, len(operations_log)): - gap = (operations_log[i]['time'] - operations_log[i-1]['time']).total_seconds() + gap = ( + operations_log[i]["time"] - operations_log[i - 1]["time"] + ).total_seconds() if gap > 10: # More than 10 seconds gap time_gaps.append(gap) - + return { - 'success': success_rate > 0.9 and len(time_gaps) == 0, # 90% success rate, no major gaps - 'details': { - 'total_operations': total_operations, - 'successful_operations': successful_operations, - 'success_rate': success_rate, - 'time_gaps': time_gaps, - 'duration_seconds': 60 + "success": success_rate > 0.9 + and len(time_gaps) == 0, # 90% success rate, no major gaps + "details": { + "total_operations": total_operations, + "successful_operations": successful_operations, + "success_rate": success_rate, + "time_gaps": time_gaps, + "duration_seconds": 60, }, - 'message': f"Stability test: {successful_operations}/{total_operations} operations successful ({success_rate:.1%}), {len(time_gaps)} gaps detected" + "message": f"Stability test: {successful_operations}/{total_operations} operations successful ({success_rate:.1%}), {len(time_gaps)} gaps detected", } - + except Exception as e: - return {'success': False, 'error': str(e)} - + return {"success": False, "error": str(e)} + def _generate_integration_report(self) -> Dict[str, Any]: """Generate comprehensive integration test report""" - + total_tests = len(self.test_results) - passed_tests = sum(1 for result in self.test_results.values() if result['status'] == 'PASSED') - failed_tests = sum(1 for result in self.test_results.values() if result['status'] == 'FAILED') - error_tests = sum(1 for result in self.test_results.values() if result['status'] == 'ERROR') - + passed_tests = sum( + 1 for result in self.test_results.values() if result["status"] == "PASSED" + ) + failed_tests = sum( + 1 for result in self.test_results.values() if result["status"] == "FAILED" + ) + error_tests = sum( + 1 for result in self.test_results.values() if result["status"] == "ERROR" + ) + total_duration = (datetime.now() - self.start_time).total_seconds() - test_duration = sum(result.get('duration', 0) for result in self.test_results.values()) - + test_duration = sum( + result.get("duration", 0) for result in self.test_results.values() + ) + # Calculate overall system health score health_score = (passed_tests / total_tests) * 100 if total_tests > 0 else 0 - + # Generate recommendations recommendations = [] - + if health_score < 80: - recommendations.append("System health below 80%. Review failed tests and address issues.") - + recommendations.append( + "System health below 80%. Review failed tests and address issues." + ) + if failed_tests > 0: - failed_test_names = [name for name, result in self.test_results.items() if result['status'] == 'FAILED'] - recommendations.append(f"Failed tests need attention: {', '.join(failed_test_names)}") - + failed_test_names = [ + name + for name, result in self.test_results.items() + if result["status"] == "FAILED" + ] + recommendations.append( + f"Failed tests need attention: {', '.join(failed_test_names)}" + ) + if error_tests > 0: - recommendations.append("Some tests encountered errors. Check logs for details.") - + recommendations.append( + "Some tests encountered errors. Check logs for details." + ) + if health_score >= 90: - recommendations.append("Excellent system health! All major components working well.") + recommendations.append( + "Excellent system health! All major components working well." + ) elif health_score >= 80: recommendations.append("Good system health with minor issues to address.") - + report = { - 'integration_summary': { - 'test_start_time': self.start_time.isoformat(), - 'test_end_time': datetime.now().isoformat(), - 'total_duration': total_duration, - 'test_execution_time': test_duration, - 'total_tests': total_tests, - 'passed_tests': passed_tests, - 'failed_tests': failed_tests, - 'error_tests': error_tests, - 'success_rate': passed_tests / total_tests if total_tests > 0 else 0, - 'health_score': health_score, - 'health_status': ( - 'EXCELLENT' if health_score >= 90 else - 'GOOD' if health_score >= 80 else - 'FAIR' if health_score >= 60 else - 'POOR' - ) + "integration_summary": { + "test_start_time": self.start_time.isoformat(), + "test_end_time": datetime.now().isoformat(), + "total_duration": total_duration, + "test_execution_time": test_duration, + "total_tests": total_tests, + "passed_tests": passed_tests, + "failed_tests": failed_tests, + "error_tests": error_tests, + "success_rate": passed_tests / total_tests if total_tests > 0 else 0, + "health_score": health_score, + "health_status": ( + "EXCELLENT" + if health_score >= 90 + else "GOOD" + if health_score >= 80 + else "FAIR" + if health_score >= 60 + else "POOR" + ), + }, + "detailed_results": self.test_results, + "recommendations": recommendations, + "system_assessment": { + "connectivity": self._assess_connectivity(), + "performance": self._assess_performance(), + "reliability": self._assess_reliability(), + "monitoring": self._assess_monitoring(), }, - 'detailed_results': self.test_results, - 'recommendations': recommendations, - 'system_assessment': { - 'connectivity': self._assess_connectivity(), - 'performance': self._assess_performance(), - 'reliability': self._assess_reliability(), - 'monitoring': self._assess_monitoring() - } } - + return report - + def _assess_connectivity(self) -> Dict[str, Any]: """Assess connectivity aspects""" - connectivity_tests = ['Basic Connectivity', 'SSID Format Compatibility', 'Persistent Connection Integration'] - passed = sum(1 for test in connectivity_tests if self.test_results.get(test, {}).get('status') == 'PASSED') - + connectivity_tests = [ + "Basic Connectivity", + "SSID Format Compatibility", + "Persistent Connection Integration", + ] + passed = sum( + 1 + for test in connectivity_tests + if self.test_results.get(test, {}).get("status") == "PASSED" + ) + return { - 'score': (passed / len(connectivity_tests)) * 100, - 'status': 'GOOD' if passed >= len(connectivity_tests) * 0.8 else 'NEEDS_ATTENTION', - 'details': f"{passed}/{len(connectivity_tests)} connectivity tests passed" + "score": (passed / len(connectivity_tests)) * 100, + "status": "GOOD" + if passed >= len(connectivity_tests) * 0.8 + else "NEEDS_ATTENTION", + "details": f"{passed}/{len(connectivity_tests)} connectivity tests passed", } - + def _assess_performance(self) -> Dict[str, Any]: """Assess performance aspects""" - performance_tests = ['Performance Under Load', 'Long-Running Stability', 'Multi-Client Scenarios'] - passed = sum(1 for test in performance_tests if self.test_results.get(test, {}).get('status') == 'PASSED') - + performance_tests = [ + "Performance Under Load", + "Long-Running Stability", + "Multi-Client Scenarios", + ] + passed = sum( + 1 + for test in performance_tests + if self.test_results.get(test, {}).get("status") == "PASSED" + ) + return { - 'score': (passed / len(performance_tests)) * 100, - 'status': 'GOOD' if passed >= len(performance_tests) * 0.8 else 'NEEDS_ATTENTION', - 'details': f"{passed}/{len(performance_tests)} performance tests passed" + "score": (passed / len(performance_tests)) * 100, + "status": "GOOD" + if passed >= len(performance_tests) * 0.8 + else "NEEDS_ATTENTION", + "details": f"{passed}/{len(performance_tests)} performance tests passed", } - + def _assess_reliability(self) -> Dict[str, Any]: """Assess reliability aspects""" - reliability_tests = ['Error Recovery', 'Keep-Alive Functionality', 'Data Consistency'] - passed = sum(1 for test in reliability_tests if self.test_results.get(test, {}).get('status') == 'PASSED') - + reliability_tests = [ + "Error Recovery", + "Keep-Alive Functionality", + "Data Consistency", + ] + passed = sum( + 1 + for test in reliability_tests + if self.test_results.get(test, {}).get("status") == "PASSED" + ) + return { - 'score': (passed / len(reliability_tests)) * 100, - 'status': 'GOOD' if passed >= len(reliability_tests) * 0.8 else 'NEEDS_ATTENTION', - 'details': f"{passed}/{len(reliability_tests)} reliability tests passed" + "score": (passed / len(reliability_tests)) * 100, + "status": "GOOD" + if passed >= len(reliability_tests) * 0.8 + else "NEEDS_ATTENTION", + "details": f"{passed}/{len(reliability_tests)} reliability tests passed", } - + def _assess_monitoring(self) -> Dict[str, Any]: """Assess monitoring aspects""" - monitoring_tests = ['Monitoring Integration'] - passed = sum(1 for test in monitoring_tests if self.test_results.get(test, {}).get('status') == 'PASSED') - + monitoring_tests = ["Monitoring Integration"] + passed = sum( + 1 + for test in monitoring_tests + if self.test_results.get(test, {}).get("status") == "PASSED" + ) + return { - 'score': (passed / len(monitoring_tests)) * 100 if len(monitoring_tests) > 0 else 100, - 'status': 'GOOD' if passed >= len(monitoring_tests) * 0.8 else 'NEEDS_ATTENTION', - 'details': f"{passed}/{len(monitoring_tests)} monitoring tests passed" + "score": (passed / len(monitoring_tests)) * 100 + if len(monitoring_tests) > 0 + else 100, + "status": "GOOD" + if passed >= len(monitoring_tests) * 0.8 + else "NEEDS_ATTENTION", + "details": f"{passed}/{len(monitoring_tests)} monitoring tests passed", } async def run_integration_tests(ssid: str = None): """Run the full integration test suite""" - + if not ssid: ssid = r'42["auth",{"session":"integration_test_session","isDemo":1,"uid":0,"platform":1}]' - logger.warning("โš ๏ธ Using demo SSID for integration testing") - - logger.info("๐ŸŽฏ PocketOption API Integration Testing Suite") + logger.warning("Using demo SSID for integration testing") + + logger.info("PocketOption API Integration Testing Suite") logger.info("=" * 60) logger.info("This comprehensive test validates all components working together") logger.info("") - + tester = IntegrationTester(ssid) - + try: report = await tester.run_full_integration_tests() - + # Print comprehensive summary logger.info("\n" + "=" * 60) logger.info("๐Ÿ INTEGRATION TEST SUMMARY") logger.info("=" * 60) - - summary = report['integration_summary'] + + summary = report["integration_summary"] logger.info(f"Tests Executed: {summary['total_tests']}") - logger.info(f"Passed: {summary['passed_tests']} โœ…") + logger.info(f"Passed: {summary['passed_tests']} ") logger.info(f"Failed: {summary['failed_tests']} โŒ") logger.info(f"Errors: {summary['error_tests']} ๐Ÿ’ฅ") logger.info(f"Success Rate: {summary['success_rate']:.1%}") - logger.info(f"Health Score: {summary['health_score']:.1f}/100 ({summary['health_status']})") + logger.info( + f"Health Score: {summary['health_score']:.1f}/100 ({summary['health_status']})" + ) logger.info(f"Total Duration: {summary['total_duration']:.2f}s") - + # System assessment logger.info("\n๐Ÿ“‹ SYSTEM ASSESSMENT") logger.info("-" * 30) - assessment = report['system_assessment'] + assessment = report["system_assessment"] for aspect, details in assessment.items(): - status_emoji = "โœ…" if details['status'] == 'GOOD' else "โš ๏ธ" - logger.info(f"{status_emoji} {aspect.title()}: {details['score']:.0f}/100 - {details['details']}") - + status_emoji = "" if details["status"] == "GOOD" else "โš ๏ธ" + logger.info( + f"{status_emoji} {aspect.title()}: {details['score']:.0f}/100 - {details['details']}" + ) + # Recommendations logger.info("\n๐Ÿ’ก RECOMMENDATIONS") logger.info("-" * 30) - for i, rec in enumerate(report['recommendations'], 1): + for i, rec in enumerate(report["recommendations"], 1): logger.info(f"{i}. {rec}") - + # Save detailed report - timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") report_file = f"integration_test_report_{timestamp}.json" - - with open(report_file, 'w') as f: + + with open(report_file, "w") as f: json.dump(report, f, indent=2, default=str) - + logger.info(f"\n๐Ÿ“„ Detailed report saved to: {report_file}") - + # Final verdict - if summary['health_score'] >= 90: + if summary["health_score"] >= 90: logger.success("๐ŸŽ‰ EXCELLENT: System is performing exceptionally well!") - elif summary['health_score'] >= 80: - logger.info("๐Ÿ‘ GOOD: System is performing well with minor areas for improvement") - elif summary['health_score'] >= 60: - logger.warning("โš ๏ธ FAIR: System has some issues that should be addressed") + elif summary["health_score"] >= 80: + logger.info( + "๐Ÿ‘ GOOD: System is performing well with minor areas for improvement" + ) + elif summary["health_score"] >= 60: + logger.warning("FAIR: System has some issues that should be addressed") else: - logger.error("โŒ POOR: System has significant issues requiring immediate attention") - + logger.error( + "POOR: System has significant issues requiring immediate attention" + ) + return report - + except Exception as e: - logger.error(f"โŒ Integration testing failed: {e}") + logger.error(f"Integration testing failed: {e}") raise if __name__ == "__main__": import sys - + # Allow passing SSID as command line argument ssid = None if len(sys.argv) > 1: ssid = sys.argv[1] logger.info(f"Using provided SSID: {ssid[:50]}...") - + asyncio.run(run_integration_tests(ssid)) diff --git a/tests/test_async_api.py b/tests/test_async_api.py index 2e7ff26..55f251e 100644 --- a/tests/test_async_api.py +++ b/tests/test_async_api.py @@ -3,7 +3,6 @@ """ import pytest -import asyncio from unittest.mock import AsyncMock, MagicMock, patch from datetime import datetime, timedelta @@ -14,26 +13,20 @@ Balance, Order, OrderResult, - PocketOptionError, ConnectionError, - OrderError, InvalidParameterError, - ConnectionStatus + ConnectionStatus, ) class TestAsyncPocketOptionClient: """Test suite for AsyncPocketOptionClient""" - + @pytest.fixture def client(self): """Create test client""" - return AsyncPocketOptionClient( - ssid="test_session", - is_demo=True, - uid=12345 - ) - + return AsyncPocketOptionClient(ssid="test_session", is_demo=True, uid=12345) + @pytest.fixture def mock_websocket(self): """Mock WebSocket client""" @@ -42,83 +35,76 @@ def mock_websocket(self): mock.connection_info = MagicMock() mock.connection_info.status = "connected" return mock - + def test_client_initialization(self, client): """Test client initialization""" assert client.session_id == "test_session" assert client.is_demo is True assert client.uid == 12345 assert client._balance is None - + @pytest.mark.asyncio async def test_connect_success(self, client, mock_websocket): """Test successful connection""" - with patch.object(client, '_websocket', mock_websocket): + with patch.object(client, "_websocket", mock_websocket): mock_websocket.connect.return_value = True - + result = await client.connect() - + assert result is True mock_websocket.connect.assert_called_once() - + @pytest.mark.asyncio async def test_connect_failure(self, client, mock_websocket): """Test connection failure""" - with patch.object(client, '_websocket', mock_websocket): + with patch.object(client, "_websocket", mock_websocket): mock_websocket.connect.side_effect = Exception("Connection failed") - + with pytest.raises(ConnectionError): await client.connect() - + @pytest.mark.asyncio async def test_disconnect(self, client, mock_websocket): """Test disconnection""" - with patch.object(client, '_websocket', mock_websocket): + with patch.object(client, "_websocket", mock_websocket): await client.disconnect() mock_websocket.disconnect.assert_called_once() - + @pytest.mark.asyncio async def test_get_balance_success(self, client): """Test getting balance""" # Set up test balance - test_balance = Balance( - balance=1000.0, - currency="USD", - is_demo=True - ) + test_balance = Balance(balance=1000.0, currency="USD", is_demo=True) client._balance = test_balance - + # Mock websocket as connected client._websocket.websocket = MagicMock() client._websocket.websocket.closed = False client._websocket.connection_info = MagicMock() client._websocket.connection_info.status = ConnectionStatus.CONNECTED - + balance = await client.get_balance() - + assert balance.balance == 1000.0 assert balance.currency == "USD" assert balance.is_demo is True - + @pytest.mark.asyncio async def test_get_balance_not_connected(self, client): """Test getting balance when not connected""" # Mock websocket as not connected client._websocket.websocket = None - + with pytest.raises(ConnectionError): await client.get_balance() - + def test_validate_order_parameters_valid(self, client): """Test order parameter validation with valid parameters""" # Should not raise any exception client._validate_order_parameters( - asset="EURUSD_otc", - amount=10.0, - direction=OrderDirection.CALL, - duration=120 + asset="EURUSD_otc", amount=10.0, direction=OrderDirection.CALL, duration=120 ) - + def test_validate_order_parameters_invalid_asset(self, client): """Test order parameter validation with invalid asset""" with pytest.raises(InvalidParameterError): @@ -126,9 +112,9 @@ def test_validate_order_parameters_invalid_asset(self, client): asset="INVALID_ASSET", amount=10.0, direction=OrderDirection.CALL, - duration=120 + duration=120, ) - + def test_validate_order_parameters_invalid_amount(self, client): """Test order parameter validation with invalid amount""" with pytest.raises(InvalidParameterError): @@ -136,9 +122,9 @@ def test_validate_order_parameters_invalid_amount(self, client): asset="EURUSD_otc", amount=0.5, # Too low direction=OrderDirection.CALL, - duration=120 + duration=120, ) - + def test_validate_order_parameters_invalid_duration(self, client): """Test order parameter validation with invalid duration""" with pytest.raises(InvalidParameterError): @@ -146,19 +132,19 @@ def test_validate_order_parameters_invalid_duration(self, client): asset="EURUSD_otc", amount=10.0, direction=OrderDirection.CALL, - duration=30 # Too short + duration=30, # Too short ) - + @pytest.mark.asyncio async def test_place_order_success(self, client, mock_websocket): """Test successful order placement""" - with patch.object(client, '_websocket', mock_websocket): + with patch.object(client, "_websocket", mock_websocket): # Mock websocket as connected mock_websocket.websocket = MagicMock() mock_websocket.websocket.closed = False mock_websocket.connection_info = MagicMock() mock_websocket.connection_info.status = ConnectionStatus.CONNECTED - + # Mock order result test_order_result = OrderResult( order_id="test_order_123", @@ -168,68 +154,68 @@ async def test_place_order_success(self, client, mock_websocket): duration=120, status=OrderStatus.ACTIVE, placed_at=datetime.now(), - expires_at=datetime.now() + timedelta(seconds=120) + expires_at=datetime.now() + timedelta(seconds=120), ) - - with patch.object(client, '_wait_for_order_result', return_value=test_order_result): + + with patch.object( + client, "_wait_for_order_result", return_value=test_order_result + ): result = await client.place_order( asset="EURUSD_otc", amount=10.0, direction=OrderDirection.CALL, - duration=120 + duration=120, ) - + assert result.order_id == "test_order_123" assert result.status == OrderStatus.ACTIVE assert result.asset == "EURUSD_otc" - + @pytest.mark.asyncio async def test_place_order_not_connected(self, client): """Test order placement when not connected""" # Mock websocket as not connected client._websocket.websocket = None - + with pytest.raises(ConnectionError): await client.place_order( asset="EURUSD_otc", amount=10.0, direction=OrderDirection.CALL, - duration=120 + duration=120, ) - + @pytest.mark.asyncio async def test_get_candles_success(self, client, mock_websocket): """Test successful candles retrieval""" - with patch.object(client, '_websocket', mock_websocket): + with patch.object(client, "_websocket", mock_websocket): # Mock websocket as connected mock_websocket.websocket = MagicMock() mock_websocket.websocket.closed = False mock_websocket.connection_info = MagicMock() mock_websocket.connection_info.status = ConnectionStatus.CONNECTED - + # Mock candles data test_candles = [ { - 'timestamp': datetime.now(), - 'open': 1.1000, - 'high': 1.1010, - 'low': 1.0990, - 'close': 1.1005, - 'asset': 'EURUSD_otc', - 'timeframe': 60 + "timestamp": datetime.now(), + "open": 1.1000, + "high": 1.1010, + "low": 1.0990, + "close": 1.1005, + "asset": "EURUSD_otc", + "timeframe": 60, } ] - - with patch.object(client, '_request_candles', return_value=test_candles): + + with patch.object(client, "_request_candles", return_value=test_candles): candles = await client.get_candles( - asset="EURUSD_otc", - timeframe="1m", - count=100 + asset="EURUSD_otc", timeframe="1m", count=100 ) - + assert len(candles) == 1 - assert candles[0]['asset'] == 'EURUSD_otc' - + assert candles[0]["asset"] == "EURUSD_otc" + @pytest.mark.asyncio async def test_get_candles_invalid_timeframe(self, client): """Test candles retrieval with invalid timeframe""" @@ -238,14 +224,10 @@ async def test_get_candles_invalid_timeframe(self, client): client._websocket.websocket.closed = False client._websocket.connection_info = MagicMock() client._websocket.connection_info.status = ConnectionStatus.CONNECTED - + with pytest.raises(InvalidParameterError): - await client.get_candles( - asset="EURUSD_otc", - timeframe="invalid", - count=100 - ) - + await client.get_candles(asset="EURUSD_otc", timeframe="invalid", count=100) + @pytest.mark.asyncio async def test_get_candles_invalid_asset(self, client): """Test candles retrieval with invalid asset""" @@ -254,77 +236,68 @@ async def test_get_candles_invalid_asset(self, client): client._websocket.websocket.closed = False client._websocket.connection_info = MagicMock() client._websocket.connection_info.status = ConnectionStatus.CONNECTED - + with pytest.raises(InvalidParameterError): - await client.get_candles( - asset="INVALID_ASSET", - timeframe="1m", - count=100 - ) - + await client.get_candles(asset="INVALID_ASSET", timeframe="1m", count=100) + def test_add_event_callback(self, client): """Test adding event callback""" + def test_callback(data): pass - - client.add_event_callback('test_event', test_callback) - - assert 'test_event' in client._event_callbacks - assert test_callback in client._event_callbacks['test_event'] - + + client.add_event_callback("test_event", test_callback) + + assert "test_event" in client._event_callbacks + assert test_callback in client._event_callbacks["test_event"] + def test_remove_event_callback(self, client): """Test removing event callback""" + def test_callback(data): pass - - client.add_event_callback('test_event', test_callback) - client.remove_event_callback('test_event', test_callback) - - assert test_callback not in client._event_callbacks.get('test_event', []) - + + client.add_event_callback("test_event", test_callback) + client.remove_event_callback("test_event", test_callback) + + assert test_callback not in client._event_callbacks.get("test_event", []) + @pytest.mark.asyncio async def test_context_manager(self, client, mock_websocket): """Test async context manager""" - with patch.object(client, '_websocket', mock_websocket): + with patch.object(client, "_websocket", mock_websocket): mock_websocket.connect.return_value = True - + async with client: assert mock_websocket.connect.called - + mock_websocket.disconnect.assert_called_once() class TestModels: """Test Pydantic models""" - + def test_balance_model(self): """Test Balance model""" - balance = Balance( - balance=1000.0, - currency="USD", - is_demo=True - ) - + balance = Balance(balance=1000.0, currency="USD", is_demo=True) + assert balance.balance == 1000.0 assert balance.currency == "USD" assert balance.is_demo is True assert isinstance(balance.last_updated, datetime) - + def test_order_model_valid(self): """Test Order model with valid data""" order = Order( - asset="EURUSD_otc", - amount=10.0, - direction=OrderDirection.CALL, - duration=120 + asset="EURUSD_otc", amount=10.0, direction=OrderDirection.CALL, duration=120 ) - + assert order.asset == "EURUSD_otc" assert order.amount == 10.0 assert order.direction == OrderDirection.CALL assert order.duration == 120 assert order.request_id is not None - + def test_order_model_invalid_amount(self): """Test Order model with invalid amount""" with pytest.raises(ValueError): @@ -332,9 +305,9 @@ def test_order_model_invalid_amount(self): asset="EURUSD_otc", amount=-10.0, # Negative amount direction=OrderDirection.CALL, - duration=120 + duration=120, ) - + def test_order_model_invalid_duration(self): """Test Order model with invalid duration""" with pytest.raises(ValueError): @@ -342,9 +315,9 @@ def test_order_model_invalid_duration(self): asset="EURUSD_otc", amount=10.0, direction=OrderDirection.CALL, - duration=30 # Too short + duration=30, # Too short ) - + def test_order_result_model(self): """Test OrderResult model""" result = OrderResult( @@ -356,9 +329,9 @@ def test_order_result_model(self): status=OrderStatus.WIN, placed_at=datetime.now(), expires_at=datetime.now() + timedelta(seconds=120), - profit=8.0 + profit=8.0, ) - + assert result.order_id == "test_123" assert result.status == OrderStatus.WIN assert result.profit == 8.0 @@ -366,28 +339,28 @@ def test_order_result_model(self): class TestUtilities: """Test utility functions""" - + def test_format_session_id(self): """Test session ID formatting""" from pocketoptionapi_async.utils import format_session_id - + formatted = format_session_id("test_session", True, 123, 1) - + assert "test_session" in formatted assert '"isDemo": 1' in formatted assert '"uid": 123' in formatted - + def test_calculate_payout_percentage_win(self): """Test payout calculation for winning trade""" from pocketoptionapi_async.utils import calculate_payout_percentage - + payout = calculate_payout_percentage(1.1000, 1.1010, "call", 0.8) assert payout == 0.8 - + def test_calculate_payout_percentage_loss(self): """Test payout calculation for losing trade""" from pocketoptionapi_async.utils import calculate_payout_percentage - + payout = calculate_payout_percentage(1.1000, 1.0990, "call", 0.8) assert payout == -1.0 diff --git a/tests/test_balance_fix.py b/tests/test_balance_fix.py index b7bb8c1..56c1f9c 100644 --- a/tests/test_balance_fix.py +++ b/tests/test_balance_fix.py @@ -1,92 +1,94 @@ -#!/usr/bin/env python3 """ Test script to verify the balance issue fix """ import asyncio -import os from loguru import logger # Mock test SSID for demonstration complete_ssid = r'42["auth",{"session":"n1p5ah5u8t9438rbunpgrq0hlq","isDemo":1,"uid":72645361,"platform":1,"isFastHistory":true}]' + async def test_balance_fix(): """Test the balance fix with the new async API""" - - logger.info("๐Ÿงช Testing Balance Fix") + + logger.info("Testing Balance Fix") logger.info("=" * 50) - + # Import here to avoid import issues during file changes try: from pocketoptionapi_async import AsyncPocketOptionClient - + # Create client client = AsyncPocketOptionClient(ssid=complete_ssid, is_demo=True) - + # Add balance event callback to test balance_received = False - + def on_balance_updated(balance): nonlocal balance_received balance_received = True - logger.success(f"โœ… Balance callback triggered: ${balance.balance:.2f}") - - client.add_event_callback('balance_updated', on_balance_updated) - + logger.success(f" Balance callback triggered: ${balance.balance:.2f}") + + client.add_event_callback("balance_updated", on_balance_updated) + # Test connection and balance retrieval try: await client.connect() - + if client.is_connected: - logger.info("โœ… Connected successfully") - + logger.info(" Connected successfully") + # Try to get balance try: balance = await client.get_balance() if balance: - logger.success(f"โœ… Balance retrieved successfully: ${balance.balance:.2f}") + logger.success( + f" Balance retrieved successfully: ${balance.balance:.2f}" + ) logger.info(f" Currency: {balance.currency}") logger.info(f" Demo: {balance.is_demo}") logger.info(f" Last updated: {balance.last_updated}") else: - logger.error("โŒ Balance is None - issue still exists") - + logger.error("Balance is None - issue still exists") + except Exception as e: - logger.error(f"โŒ Balance retrieval failed: {e}") - + logger.error(f"Balance retrieval failed: {e}") + # Wait for balance events logger.info("โณ Waiting for balance events...") await asyncio.sleep(5) - + if balance_received: - logger.success("โœ… Balance event received successfully!") + logger.success(" Balance event received successfully!") else: - logger.warning("โš ๏ธ No balance event received") - + logger.warning("No balance event received") + else: - logger.warning("โš ๏ธ Connection failed (expected with test SSID)") - + logger.warning("Connection failed (expected with test SSID)") + except Exception as e: - logger.info(f"โ„น๏ธ Connection test: {e}") - + logger.info(f"Connection test: {e}") + finally: await client.disconnect() - + except Exception as e: - logger.error(f"โŒ Test failed: {e}") + logger.error(f"Test failed: {e}") return False - + logger.info("=" * 50) - logger.success("โœ… Balance fix test completed!") + logger.success(" Balance fix test completed!") return True + if __name__ == "__main__": # Configure logging logger.remove() logger.add( lambda msg: print(msg, end=""), format="{time:HH:mm:ss} | {level} | {message}", - level="INFO" + level="INFO", ) - + asyncio.run(test_balance_fix()) diff --git a/tests/test_candles_fix.py b/tests/test_candles_fix.py index 0581bb7..921d718 100644 --- a/tests/test_candles_fix.py +++ b/tests/test_candles_fix.py @@ -4,159 +4,165 @@ import asyncio import json -from datetime import datetime, timedelta +from datetime import datetime from pocketoptionapi_async import AsyncPocketOptionClient + async def test_candles_retrieval(): """Test candles data retrieval with the fixed implementation""" - + # Replace with your actual SSID ssid = "po_session_id=your_session_id_here" - - print("๐Ÿงช Testing Candles Data Retrieval") + + print("Testing Candles Data Retrieval") print("=" * 50) - + try: # Create client with logging enabled to see detailed output client = AsyncPocketOptionClient(ssid, is_demo=True, enable_logging=True) - + print("๐Ÿ“ก Connecting to PocketOption...") await client.connect() - + print("\n๐Ÿ“Š Requesting candles data...") - + # Test 1: Get recent candles for EURUSD asset = "EURUSD" timeframe = 60 # 1 minute count = 20 - + print(f"Asset: {asset}") print(f"Timeframe: {timeframe}s (1 minute)") print(f"Count: {count}") - + candles = await client.get_candles(asset, timeframe, count) - + if candles: - print(f"\nโœ… Successfully retrieved {len(candles)} candles!") - + print(f"\n Successfully retrieved {len(candles)} candles!") + # Display first few candles - print("\n๐Ÿ“ˆ Sample candle data:") + print("\nSample candle data:") for i, candle in enumerate(candles[:5]): - print(f" {i+1}. {candle.timestamp.strftime('%H:%M:%S')} - " - f"O:{candle.open:.5f} H:{candle.high:.5f} L:{candle.low:.5f} C:{candle.close:.5f}") - + print( + f" {i + 1}. {candle.timestamp.strftime('%H:%M:%S')} - " + f"O:{candle.open:.5f} H:{candle.high:.5f} L:{candle.low:.5f} C:{candle.close:.5f}" + ) + if len(candles) > 5: print(f" ... and {len(candles) - 5} more candles") - + else: - print("โŒ No candles received - this may indicate an issue") - + print("No candles received - this may indicate an issue") + # Test 2: Get candles as DataFrame print("\n๐Ÿ“Š Testing DataFrame conversion...") try: df = await client.get_candles_dataframe(asset, timeframe, count) if not df.empty: - print(f"โœ… DataFrame created with {len(df)} rows") + print(f" DataFrame created with {len(df)} rows") print(f"Columns: {list(df.columns)}") print(f"Date range: {df.index[0]} to {df.index[-1]}") else: - print("โŒ Empty DataFrame received") + print("Empty DataFrame received") except Exception as e: - print(f"โŒ DataFrame test failed: {e}") - + print(f"DataFrame test failed: {e}") + # Test 3: Different timeframes print("\nโฑ๏ธ Testing different timeframes...") - timeframes_to_test = [ - (60, "1 minute"), - (300, "5 minutes"), - (900, "15 minutes") - ] - + timeframes_to_test = [(60, "1 minute"), (300, "5 minutes"), (900, "15 minutes")] + for tf_seconds, tf_name in timeframes_to_test: try: test_candles = await client.get_candles(asset, tf_seconds, 5) if test_candles: - print(f"โœ… {tf_name}: {len(test_candles)} candles") + print(f" {tf_name}: {len(test_candles)} candles") else: - print(f"โŒ {tf_name}: No data") + print(f"{tf_name}: No data") except Exception as e: - print(f"โŒ {tf_name}: Error - {e}") - + print(f"{tf_name}: Error - {e}") + print("\n๐Ÿ” Testing different assets...") assets_to_test = ["EURUSD", "GBPUSD", "USDJPY"] - + for test_asset in assets_to_test: try: test_candles = await client.get_candles(test_asset, 60, 3) if test_candles: latest = test_candles[-1] if test_candles else None - print(f"โœ… {test_asset}: Latest price {latest.close:.5f}" if latest else f"โœ… {test_asset}: {len(test_candles)} candles") + print( + f" {test_asset}: Latest price {latest.close:.5f}" + if latest + else f" {test_asset}: {len(test_candles)} candles" + ) else: - print(f"โŒ {test_asset}: No data") + print(f"{test_asset}: No data") except Exception as e: - print(f"โŒ {test_asset}: Error - {e}") - + print(f"{test_asset}: Error - {e}") + except Exception as e: - print(f"โŒ Test failed with error: {e}") + print(f"Test failed with error: {e}") import traceback + traceback.print_exc() - + finally: try: await client.disconnect() - print("\n๐Ÿ”Œ Disconnected from PocketOption") + print("\nDisconnected from PocketOption") except: pass + async def test_candles_message_format(): """Test the message format being sent""" - + print("\n๐Ÿ” Testing Message Format") print("=" * 30) - + # Simulate the message creation asset = "EURUSD" timeframe = 60 count = 10 end_time = datetime.now() end_timestamp = int(end_time.timestamp()) - + # Create message data in the format expected by PocketOption data = { "asset": str(asset), "index": end_timestamp, "offset": count, "period": timeframe, - "time": end_timestamp + "time": end_timestamp, } - + # Create the full message message_data = ["loadHistoryPeriod", data] message = f'42["sendMessage",{json.dumps(message_data)}]' - + print(f"Asset: {asset}") print(f"Timeframe: {timeframe}s") print(f"Count: {count}") print(f"End time: {end_time.strftime('%Y-%m-%d %H:%M:%S')}") print(f"Timestamp: {end_timestamp}") - print(f"\nGenerated message:") + print("\nGenerated message:") print(message) - print(f"\nMessage data structure:") + print("\nMessage data structure:") print(json.dumps(message_data, indent=2)) + if __name__ == "__main__": - print("๐Ÿงช PocketOption Candles Test Suite") + print("PocketOption Candles Test Suite") print("=" * 40) - + # Test message format first asyncio.run(test_candles_message_format()) - + # Then test actual retrieval (requires valid SSID) print("\n" + "=" * 40) - print("โš ๏ธ To test actual candles retrieval:") + print(" To test actual candles retrieval:") print("1. Replace 'your_session_id_here' with your actual SSID") print("2. Uncomment the line below") print("=" * 40) - + # Uncomment this line after adding your SSID: # asyncio.run(test_candles_retrieval()) diff --git a/tests/test_complete_order_tracking.py b/tests/test_complete_order_tracking.py index 468dca1..f39cb0a 100644 --- a/tests/test_complete_order_tracking.py +++ b/tests/test_complete_order_tracking.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Complete Order Tracking Test Tests the full order lifecycle including waiting for trade completion and profit/loss tracking @@ -6,7 +5,6 @@ import asyncio import os -import time from datetime import datetime, timedelta from loguru import logger @@ -15,153 +13,178 @@ async def wait_for_trade_completion(): """Test complete order lifecycle with profit tracking""" - + # Get SSID from environment ssid = os.getenv("POCKET_OPTION_SSID") - + if not ssid: - print("โŒ Please set POCKET_OPTION_SSID environment variable") + print("Please set POCKET_OPTION_SSID environment variable") print("Example: set POCKET_OPTION_SSID='your_session_id_here'") return - - print("๐Ÿš€ Complete Order Tracking Test") + + print("Complete Order Tracking Test") print("=" * 50) - + # Create client client = AsyncPocketOptionClient(ssid, is_demo=True) - + try: # Connect print("๐Ÿ“ก Connecting...") await client.connect() - + if not client.is_connected: - print("โŒ Failed to connect") + print("Failed to connect") return - - print("โœ… Connected successfully") - + + print(" Connected successfully") + # Wait for initialization await asyncio.sleep(3) - + # Get balance balance = await client.get_balance() if balance: - print(f"๐Ÿ’ฐ Balance: ${balance.balance:.2f} (Demo: {balance.is_demo})") + print(f"Balance: ${balance.balance:.2f} (Demo: {balance.is_demo})") else: - print("โš ๏ธ No balance received") - + print("No balance received") + # Add event callback to monitor order completion completed_orders = [] - + def on_order_closed(order_result): completed_orders.append(order_result) - status = "WIN" if order_result.profit > 0 else "LOSE" if order_result.profit < 0 else "EVEN" - print(f"๐ŸŽฏ Order completed: {status} - Profit: ${order_result.profit:.2f}") - - client.add_event_callback('order_closed', on_order_closed) - + status = ( + "WIN" + if order_result.profit > 0 + else "LOSE" + if order_result.profit < 0 + else "EVEN" + ) + print(f"Order completed: {status} - Profit: ${order_result.profit:.2f}") + + client.add_event_callback("order_closed", on_order_closed) + # Place a test order with shorter duration for faster results - print(f"\n๐Ÿ“ˆ Placing test order...") + print("\nPlacing test order...") order_result = await client.place_order( asset="EURUSD_otc", amount=1.0, direction=OrderDirection.CALL, - duration=60 # 1 minute for quick testing + duration=60, # 1 minute for quick testing ) - - print(f"โœ… Order placed: {order_result.order_id}") + + print(f" Order placed: {order_result.order_id}") print(f" Status: {order_result.status}") print(f" Asset: {order_result.asset}") print(f" Amount: ${order_result.amount}") print(f" Direction: {order_result.direction}") print(f" Duration: {order_result.duration}s") print(f" Expires at: {order_result.expires_at.strftime('%H:%M:%S')}") - + # Check immediate order result immediate_result = await client.check_order_result(order_result.order_id) if immediate_result: - print(f"โœ… Order immediately found in tracking system") + print(" Order immediately found in tracking system") else: - print("โŒ Order NOT found in tracking system - this is a problem!") + print("Order NOT found in tracking system - this is a problem!") return - + # Wait for the trade to complete - print(f"\nโฑ๏ธ Waiting for trade to complete (up to {order_result.duration + 30} seconds)...") + print( + f"\nโฑ๏ธ Waiting for trade to complete (up to {order_result.duration + 30} seconds)..." + ) start_time = datetime.now() - max_wait = timedelta(seconds=order_result.duration + 30) # Trade duration + 30 seconds buffer - + max_wait = timedelta( + seconds=order_result.duration + 30 + ) # Trade duration + 30 seconds buffer + last_status = None - + while datetime.now() - start_time < max_wait: result = await client.check_order_result(order_result.order_id) - + if result: # Only print status changes to avoid spam if result.status != last_status: - status_emoji = "๐ŸŸข" if result.status == "active" else "๐Ÿ”ด" if result.status in ["win", "lose"] else "๐ŸŸก" + status_emoji = ( + "๐ŸŸข" + if result.status == "active" + else "๐Ÿ”ด" + if result.status in ["win", "lose"] + else "๐ŸŸก" + ) print(f" {status_emoji} Order status: {result.status}") last_status = result.status - + # Check if order completed if result.profit is not None: - win_lose = "WIN" if result.profit > 0 else "LOSE" if result.profit < 0 else "EVEN" - print(f"\n๐ŸŽฏ TRADE COMPLETED!") + win_lose = ( + "WIN" + if result.profit > 0 + else "LOSE" + if result.profit < 0 + else "EVEN" + ) + print("\nTRADE COMPLETED!") print(f" Result: {win_lose}") print(f" Profit/Loss: ${result.profit:.2f}") if result.payout: print(f" Payout: ${result.payout:.2f}") - + # Calculate percentage return if result.profit != 0: percentage = (result.profit / order_result.amount) * 100 print(f" Return: {percentage:.1f}%") - + break - + # Check if status indicates completion but no profit yet elif result.status in ["win", "lose", "closed"]: - print(f" ๐Ÿ“Š Order marked as {result.status} but no profit data yet...") - + print( + f" ๐Ÿ“Š Order marked as {result.status} but no profit data yet..." + ) + else: - print(" โŒ Order disappeared from tracking system") + print(" Order disappeared from tracking system") break - + await asyncio.sleep(2) # Check every 2 seconds - + # Check if we completed via event callback if completed_orders: - print(f"\nโœ… Order completion detected via event callback!") + print("\n Order completion detected via event callback!") final_order = completed_orders[0] print(f" Final profit: ${final_order.profit:.2f}") - + # Final status check final_result = await client.check_order_result(order_result.order_id) if final_result: - print(f"\n๐Ÿ“‹ Final status:") + print("\n๐Ÿ“‹ Final status:") print(f" Order ID: {final_result.order_id}") print(f" Status: {final_result.status}") if final_result.profit is not None: print(f" Final Profit/Loss: ${final_result.profit:.2f}") else: - print(f" โš ๏ธ No profit data available (may indicate tracking issue)") + print(" No profit data available (may indicate tracking issue)") else: - print(f"\nโŒ Could not find final order result") - + print("\nCould not find final order result") + # Show active orders count active_orders = await client.get_active_orders() print(f"\n๐Ÿ“Š Active orders remaining: {len(active_orders)}") - + except Exception as e: - print(f"โŒ Error: {e}") + print(f"Error: {e}") import traceback + traceback.print_exc() - + finally: # Disconnect - print(f"\n๐Ÿ”Œ Disconnecting...") + print("\nDisconnecting...") await client.disconnect() - print("โœ… Test completed") + print(" Test completed") if __name__ == "__main__": @@ -170,7 +193,7 @@ def on_order_closed(order_result): logger.add( lambda msg: print(msg, end=""), format="{level} | {message}", - level="WARNING" # Only show warnings and errors from the library + level="WARNING", # Only show warnings and errors from the library ) - + asyncio.run(wait_for_trade_completion()) diff --git a/tests/test_complete_ssid.py b/tests/test_complete_ssid.py index 6d7fc52..58a7400 100644 --- a/tests/test_complete_ssid.py +++ b/tests/test_complete_ssid.py @@ -1,163 +1,164 @@ -#!/usr/bin/env python3 """ Test script demonstrating complete SSID format handling """ import asyncio import os -from loguru import logger from pocketoptionapi_async import AsyncPocketOptionClient async def test_complete_ssid_format(): """Test the complete SSID format functionality""" - - print("๐Ÿงช Testing Complete SSID Format Handling") + + print("Testing Complete SSID Format Handling") print("=" * 50) - + # Test 1: Complete SSID format (what the user wants) complete_ssid = r'42["auth",{"session":"n1p5ah5u8t9438rbunpgrq0hlq","isDemo":1,"uid":72645361,"platform":1,"isFastHistory":true}]' - - print(f"๐Ÿ“ Testing with complete SSID format:") + + print("๐Ÿ“ Testing with complete SSID format:") print(f" SSID: {complete_ssid[:50]}...") print() - + try: # Create client with complete SSID client = AsyncPocketOptionClient(ssid=complete_ssid, is_demo=True) - + # Check that the SSID is handled correctly formatted_message = client._format_session_message() - - print("โœ… Client created successfully") + + print(" Client created successfully") print(f"๐Ÿ“ค Formatted message: {formatted_message[:50]}...") print(f"๐Ÿ” Session extracted: {getattr(client, 'session_id', 'N/A')[:20]}...") print(f"๐Ÿ‘ค UID extracted: {client.uid}") print(f"๐Ÿท๏ธ Platform: {client.platform}") - print(f"๐ŸŽฏ Demo mode: {client.is_demo}") + print(f"Demo mode: {client.is_demo}") print(f"โšก Fast history: {client.is_fast_history}") - + # Test connection (will fail with test SSID but should show proper format) - print("\n๐Ÿ”Œ Testing connection...") + print("\nTesting connection...") try: await client.connect() if client.is_connected: - print("โœ… Connected successfully!") + print(" Connected successfully!") print(f"๐Ÿ“Š Connection info: {client.connection_info}") else: - print("โ„น๏ธ Connection failed (expected with test SSID)") + print(" Connection failed (expected with test SSID)") except Exception as e: - print(f"โ„น๏ธ Connection error (expected): {str(e)[:100]}...") - + print(f" Connection error (expected): {str(e)[:100]}...") + await client.disconnect() - + except Exception as e: - print(f"โŒ Error: {e}") - + print(f"Error: {e}") + print("\n" + "=" * 50) - + # Test 2: Raw session ID format (for comparison) raw_session = "n1p5ah5u8t9438rbunpgrq0hlq" - - print(f"๐Ÿ“ Testing with raw session ID:") + + print("๐Ÿ“ Testing with raw session ID:") print(f" Session: {raw_session}") print() - + try: # Create client with raw session client2 = AsyncPocketOptionClient( - ssid=raw_session, - is_demo=True, - uid=72645361, - platform=1 + ssid=raw_session, is_demo=True, uid=72645361, platform=1 ) - + formatted_message2 = client2._format_session_message() - - print("โœ… Client created successfully") + + print(" Client created successfully") print(f"๐Ÿ“ค Formatted message: {formatted_message2[:50]}...") print(f"๐Ÿ” Session: {getattr(client2, 'session_id', 'N/A')}") print(f"๐Ÿ‘ค UID: {client2.uid}") print(f"๐Ÿท๏ธ Platform: {client2.platform}") - + except Exception as e: - print(f"โŒ Error: {e}") - + print(f"Error: {e}") + print("\n" + "=" * 50) - print("โœ… SSID Format Tests Completed!") + print(" SSID Format Tests Completed!") async def test_real_connection(): """Test with real SSID if available""" - + print("\n๐ŸŒ Testing Real Connection (Optional)") print("=" * 40) - + # Check for real SSID in environment real_ssid = os.getenv("POCKET_OPTION_SSID") - + if not real_ssid: - print("โ„น๏ธ No real SSID found in environment variable POCKET_OPTION_SSID") + print(" No real SSID found in environment variable POCKET_OPTION_SSID") print(" Set it like this for real testing:") - print(' export POCKET_OPTION_SSID=\'42["auth",{"session":"your_session","isDemo":1,"uid":your_uid,"platform":1}]\'') + print( + ' export POCKET_OPTION_SSID=\'42["auth",{"session":"your_session","isDemo":1,"uid":your_uid,"platform":1}]\'' + ) return - + print(f"๐Ÿ”‘ Found real SSID: {real_ssid[:30]}...") - + try: client = AsyncPocketOptionClient(ssid=real_ssid) - - print("๐Ÿ”Œ Attempting real connection...") + + print("Attempting real connection...") await client.connect() - + if client.is_connected: - print("โœ… Successfully connected!") - + print(" Successfully connected!") + # Test basic functionality try: balance = await client.get_balance() - print(f"๐Ÿ’ฐ Balance: ${balance.balance:.2f}") - + print(f"Balance: ${balance.balance:.2f}") + # Test health status health = await client.get_health_status() print(f"๐Ÿฅ Health: {health}") - + except Exception as e: - print(f"โš ๏ธ API error: {e}") - + print(f" API error: {e}") + else: - print("โŒ Connection failed") - + print("Connection failed") + await client.disconnect() - print("๐Ÿ”Œ Disconnected") - + print("Disconnected") + except Exception as e: - print(f"โŒ Connection error: {e}") + print(f"Connection error: {e}") async def main(): """Main test function""" - - print("๐Ÿš€ PocketOption SSID Format Test Suite") + + print("PocketOption SSID Format Test Suite") print("=" * 60) print() - + # Test SSID format handling await test_complete_ssid_format() - + # Test real connection if available await test_real_connection() - + print("\n๐ŸŽ‰ All tests completed!") print() print("๐Ÿ“‹ Usage Examples:") print("1. Complete SSID format (recommended):") - print(' ssid = r\'42["auth",{"session":"your_session","isDemo":1,"uid":your_uid,"platform":1}]\'') + print( + ' ssid = r\'42["auth",{"session":"your_session","isDemo":1,"uid":your_uid,"platform":1}]\'' + ) print(" client = AsyncPocketOptionClient(ssid=ssid)") print() print("2. Raw session format:") - print(' client = AsyncPocketOptionClient(ssid="your_session", uid=your_uid, is_demo=True)') + print( + ' client = AsyncPocketOptionClient(ssid="your_session", uid=your_uid, is_demo=True)' + ) if __name__ == "__main__": diff --git a/tests/test_demo_live_connection.py b/tests/test_demo_live_connection.py index 4ebe9c3..ac6f72b 100644 --- a/tests/test_demo_live_connection.py +++ b/tests/test_demo_live_connection.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Test script to verify the demo/live connection fix """ @@ -6,68 +5,70 @@ import asyncio from pocketoptionapi_async import AsyncPocketOptionClient + async def test_demo_live_connection(): """Test that demo/live connections go to correct regions""" - + # Test SSID with demo=1 hardcoded (should be overridden by is_demo parameter) demo_ssid = r'42["auth",{"session":"n1p5ah5u8t9438rbunpgrq0hlq","isDemo":1,"uid":72645361,"platform":1,"isFastHistory":true}]' - + print("๐ŸŒ Testing Demo/Live Connection Fix") print("=" * 50) - + # Test 1: Demo mode connection (should connect to demo regions) - print("\n1๏ธโƒฃ Test: Demo mode connection (is_demo=True)") + print("\nTest: Demo mode connection (is_demo=True)") client_demo = AsyncPocketOptionClient(ssid=demo_ssid, is_demo=True) - + print(f" Client is_demo: {client_demo.is_demo}") - print(f" Attempting connection to demo regions...") - + print(" Attempting connection to demo regions...") + try: success = await asyncio.wait_for(client_demo.connect(), timeout=30) - + if success: - print(f" โœ… Connected successfully!") - if hasattr(client_demo, 'connection_info') and client_demo.connection_info: + print(" Connected successfully!") + if hasattr(client_demo, "connection_info") and client_demo.connection_info: print(f" ๐ŸŒ Connected to: {client_demo.connection_info.region}") await client_demo.disconnect() else: - print(f" โŒ Connection failed") - + print(" Connection failed") + except asyncio.TimeoutError: - print(f" โฐ Connection timeout (expected with test credentials)") + print(" โฐ Connection timeout (expected with test credentials)") except Exception as e: - print(f" โš ๏ธ Connection error: {e}") - + print(f" Connection error: {e}") + # Test 2: Live mode connection (should try non-demo regions) - print("\n2๏ธโƒฃ Test: Live mode connection (is_demo=False)") + print("\nTest: Live mode connection (is_demo=False)") client_live = AsyncPocketOptionClient(ssid=demo_ssid, is_demo=False) - + print(f" Client is_demo: {client_live.is_demo}") - print(f" Attempting connection to live regions...") - + print(" Attempting connection to live regions...") + try: success = await asyncio.wait_for(client_live.connect(), timeout=30) - + if success: - print(f" โœ… Connected successfully!") - if hasattr(client_live, 'connection_info') and client_live.connection_info: + print(" Connected successfully!") + if hasattr(client_live, "connection_info") and client_live.connection_info: print(f" ๐ŸŒ Connected to: {client_live.connection_info.region}") await client_live.disconnect() else: - print(f" โŒ Connection failed") - + print(" Connection failed") + except asyncio.TimeoutError: - print(f" โฐ Connection timeout (expected with test credentials)") + print(" โฐ Connection timeout (expected with test credentials)") except Exception as e: - print(f" โš ๏ธ Connection error: {e}") - + print(f" Connection error: {e}") + print("\n" + "=" * 50) - print("โœ… Demo/Live Connection Test Complete!") + print(" Demo/Live Connection Test Complete!") print("\nKey improvements:") - print("โ€ข โœ… is_demo parameter now properly overrides SSID values") - print("โ€ข โœ… Demo mode connects only to demo regions") - print("โ€ข โœ… Live mode excludes demo regions") - print("โ€ข โœ… Authentication messages use correct isDemo values") + print("โ€ข is_demo parameter now properly overrides SSID values") + print("โ€ข Demo mode connects only to demo regions") + print("โ€ข Live mode excludes demo regions") + print("โ€ข Authentication messages use correct isDemo values") + if __name__ == "__main__": asyncio.run(test_demo_live_connection()) diff --git a/tests/test_demo_live_fix.py b/tests/test_demo_live_fix.py index 47afb5c..20eb665 100644 --- a/tests/test_demo_live_fix.py +++ b/tests/test_demo_live_fix.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Test script to verify the demo/live mode fix """ @@ -7,98 +6,109 @@ import json from pocketoptionapi_async import AsyncPocketOptionClient + async def test_demo_live_fix(): """Test that is_demo parameter is properly respected""" - + # Test SSID with demo=1 hardcoded (should be overridden by is_demo parameter) demo_ssid = r'42["auth",{"session":"n1p5ah5u8t9438rbunpgrq0hlq","isDemo":1,"uid":72645361,"platform":1,"isFastHistory":true}]' - - print("๐Ÿงช Testing Demo/Live Mode Fix") + + print("Testing Demo/Live Mode Fix") print("=" * 50) - + # Test 1: Demo mode with demo SSID (should work) - print("\n1๏ธโƒฃ Test: is_demo=True with demo SSID") + print("\nTest: is_demo=True with demo SSID") client_demo = AsyncPocketOptionClient(ssid=demo_ssid, is_demo=True) formatted_demo = client_demo._format_session_message() parsed_demo = json.loads(formatted_demo[10:-1]) # Extract JSON part - + print(f" SSID isDemo value: {json.loads(demo_ssid[10:-1])['isDemo']}") - print(f" Constructor is_demo: True") + print(" Constructor is_demo: True") print(f" Client is_demo: {client_demo.is_demo}") print(f" Formatted message isDemo: {parsed_demo['isDemo']}") - print(f" โœ… Expected: 1, Got: {parsed_demo['isDemo']}") - + print(f" Expected: 1, Got: {parsed_demo['isDemo']}") + # Test 2: Live mode with demo SSID (should override to live) - print("\n2๏ธโƒฃ Test: is_demo=False with demo SSID") + print("\nTest: is_demo=False with demo SSID") client_live = AsyncPocketOptionClient(ssid=demo_ssid, is_demo=False) formatted_live = client_live._format_session_message() parsed_live = json.loads(formatted_live[10:-1]) # Extract JSON part - + print(f" SSID isDemo value: {json.loads(demo_ssid[10:-1])['isDemo']}") - print(f" Constructor is_demo: False") + print(" Constructor is_demo: False") print(f" Client is_demo: {client_live.is_demo}") print(f" Formatted message isDemo: {parsed_live['isDemo']}") - print(f" โœ… Expected: 0, Got: {parsed_live['isDemo']}") - + print(f" Expected: 0, Got: {parsed_live['isDemo']}") + # Test 3: Raw session ID with demo mode - print("\n3๏ธโƒฃ Test: Raw session with is_demo=True") + print("\nTest: Raw session with is_demo=True") raw_session = "n1p5ah5u8t9438rbunpgrq0hlq" - client_raw_demo = AsyncPocketOptionClient(ssid=raw_session, is_demo=True, uid=72645361) + client_raw_demo = AsyncPocketOptionClient( + ssid=raw_session, is_demo=True, uid=72645361 + ) formatted_raw_demo = client_raw_demo._format_session_message() parsed_raw_demo = json.loads(formatted_raw_demo[10:-1]) - - print(f" Constructor is_demo: True") + + print(" Constructor is_demo: True") print(f" Client is_demo: {client_raw_demo.is_demo}") print(f" Formatted message isDemo: {parsed_raw_demo['isDemo']}") - print(f" โœ… Expected: 1, Got: {parsed_raw_demo['isDemo']}") - + print(f" Expected: 1, Got: {parsed_raw_demo['isDemo']}") + # Test 4: Raw session ID with live mode - print("\n4๏ธโƒฃ Test: Raw session with is_demo=False") - client_raw_live = AsyncPocketOptionClient(ssid=raw_session, is_demo=False, uid=72645361) + print("\nTest: Raw session with is_demo=False") + client_raw_live = AsyncPocketOptionClient( + ssid=raw_session, is_demo=False, uid=72645361 + ) formatted_raw_live = client_raw_live._format_session_message() parsed_raw_live = json.loads(formatted_raw_live[10:-1]) - - print(f" Constructor is_demo: False") + + print(" Constructor is_demo: False") print(f" Client is_demo: {client_raw_live.is_demo}") print(f" Formatted message isDemo: {parsed_raw_live['isDemo']}") - print(f" โœ… Expected: 0, Got: {parsed_raw_live['isDemo']}") - + print(f" Expected: 0, Got: {parsed_raw_live['isDemo']}") + # Test 5: Region selection based on demo mode print("\n5๏ธโƒฃ Test: Region selection logic") - + # Import regions to check the logic from pocketoptionapi_async.constants import REGIONS + all_regions = REGIONS.get_all_regions() demo_regions = REGIONS.get_demo_regions() - + print(f" Total regions: {len(all_regions)}") print(f" Demo regions: {len(demo_regions)}") - + # Check demo client region selection - print(f"\n Demo client (is_demo=True):") - demo_region_names = [name for name, url in all_regions.items() if url in demo_regions] + print("\n Demo client (is_demo=True):") + demo_region_names = [ + name for name, url in all_regions.items() if url in demo_regions + ] print(f" Should use demo regions: {demo_region_names}") - - # Check live client region selection - print(f"\n Live client (is_demo=False):") - live_region_names = [name for name, url in all_regions.items() if "DEMO" not in name.upper()] + + # Check live client region selection + print("\n Live client (is_demo=False):") + live_region_names = [ + name for name, url in all_regions.items() if "DEMO" not in name.upper() + ] print(f" Should use non-demo regions: {live_region_names}") - + print("\n" + "=" * 50) - print("โœ… Demo/Live Mode Fix Test Complete!") - + print(" Demo/Live Mode Fix Test Complete!") + # Verify all tests passed - demo_test_pass = parsed_demo['isDemo'] == 1 - live_test_pass = parsed_live['isDemo'] == 0 - raw_demo_test_pass = parsed_raw_demo['isDemo'] == 1 - raw_live_test_pass = parsed_raw_live['isDemo'] == 0 - + demo_test_pass = parsed_demo["isDemo"] == 1 + live_test_pass = parsed_live["isDemo"] == 0 + raw_demo_test_pass = parsed_raw_demo["isDemo"] == 1 + raw_live_test_pass = parsed_raw_live["isDemo"] == 0 + if all([demo_test_pass, live_test_pass, raw_demo_test_pass, raw_live_test_pass]): print("๐ŸŽ‰ ALL TESTS PASSED! is_demo parameter is now properly respected!") else: - print("โŒ Some tests failed. The fix needs adjustment.") - + print("Some tests failed. The fix needs adjustment.") + return all([demo_test_pass, live_test_pass, raw_demo_test_pass, raw_live_test_pass]) + if __name__ == "__main__": asyncio.run(test_demo_live_fix()) diff --git a/tests/test_fixed_connection.py b/tests/test_fixed_connection.py index b92c5d8..357a3d4 100644 --- a/tests/test_fixed_connection.py +++ b/tests/test_fixed_connection.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Test script to verify the fixed connection issue in the new async API """ @@ -10,136 +9,149 @@ # Configure logging logger.remove() -logger.add(sys.stdout, format="{time:HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}") +logger.add( + sys.stdout, + format="{time:HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}", +) + async def test_connection_fix(): """Test the fixed connection with proper handshake sequence""" - + print("๐Ÿ”ง Testing Fixed Connection Issue") print("=" * 60) - + # Test with complete SSID format (like from browser) complete_ssid = r'42["auth",{"session":"test_session_12345","isDemo":1,"uid":12345,"platform":1,"isFastHistory":true}]' - - print(f"๐Ÿ“ Using complete SSID format:") + + print("๐Ÿ“ Using complete SSID format:") print(f" {complete_ssid[:50]}...") print() - + try: # Create client client = AsyncPocketOptionClient( ssid=complete_ssid, is_demo=True, persistent_connection=False, # Use regular connection for testing - auto_reconnect=True + auto_reconnect=True, ) - - print("โœ… Client created successfully") + + print(" Client created successfully") print(f"๐Ÿ” Session ID: {client.session_id}") print(f"๐Ÿ‘ค UID: {client.uid}") - print(f"๐ŸŽฏ Demo mode: {client.is_demo}") + print(f"Demo mode: {client.is_demo}") print(f"๐Ÿท๏ธ Platform: {client.platform}") print() - + # Test connection - print("๐Ÿ”Œ Testing connection with improved handshake...") + print("Testing connection with improved handshake...") try: success = await client.connect() - + if success: - print("โœ… CONNECTION SUCCESSFUL!") + print(" CONNECTION SUCCESSFUL!") print(f"๐Ÿ“Š Connection info: {client.connection_info}") - print(f"๐ŸŒ Connected to: {client.connection_info.region if client.connection_info else 'Unknown'}") - + print( + f"๐ŸŒ Connected to: {client.connection_info.region if client.connection_info else 'Unknown'}" + ) + # Test basic functionality print("\n๐Ÿ“‹ Testing basic functionality...") try: balance = await client.get_balance() if balance: - print(f"๐Ÿ’ฐ Balance: ${balance.balance}") + print(f"Balance: ${balance.balance}") else: - print("โš ๏ธ No balance data received (expected with test SSID)") + print(" No balance data received (expected with test SSID)") except Exception as e: - print(f"โ„น๏ธ Balance request failed (expected): {e}") - - print("\nโœ… All connection tests passed!") - + print(f" Balance request failed (expected): {e}") + + print("\n All connection tests passed!") + else: - print("โŒ Connection failed") - + print("Connection failed") + except Exception as e: # This is expected with test SSID, but we should see proper handshake messages - print(f"โ„น๏ธ Connection attempt result: {str(e)[:100]}...") + print(f" Connection attempt result: {str(e)[:100]}...") if "handshake" in str(e).lower() or "authentication" in str(e).lower(): - print("โœ… Handshake sequence is working (authentication failed as expected with test SSID)") + print( + " Handshake sequence is working (authentication failed as expected with test SSID)" + ) else: - print("โŒ Unexpected connection error") - + print("Unexpected connection error") + finally: await client.disconnect() print("๐Ÿ›‘ Disconnected") - + except Exception as e: - print(f"โŒ Test error: {e}") + print(f"Test error: {e}") return False - + return True + async def test_old_vs_new_comparison(): """Compare the handshake behavior with old API patterns""" - + print("\n" + "=" * 60) - print("๐Ÿ”„ Connection Pattern Comparison") + print("Connection Pattern Comparison") print("=" * 60) - + print("๐Ÿ“‹ OLD API Handshake Pattern:") - print(" 1. Server sends: 0{\"sid\":\"...\"}") + print(' 1. Server sends: 0{"sid":"..."}') print(" 2. Client sends: 40") - print(" 3. Server sends: 40{\"sid\":\"...\"}") + print(' 3. Server sends: 40{"sid":"..."}') print(" 4. Client sends: SSID message") - print(" 5. Server sends: 451-[\"successauth\",...]") + print(' 5. Server sends: 451-["successauth",...]') print() - + print("๐Ÿ“‹ NEW API Handshake Pattern (FIXED):") - print(" 1. โœ… Wait for server message with '0' and 'sid'") - print(" 2. โœ… Send '40' response") - print(" 3. โœ… Wait for server message with '40' and 'sid'") - print(" 4. โœ… Send SSID authentication") - print(" 5. โœ… Wait for authentication response") + print(" 1. Wait for server message with '0' and 'sid'") + print(" 2. Send '40' response") + print(" 3. Wait for server message with '40' and 'sid'") + print(" 4. Send SSID authentication") + print(" 5. Wait for authentication response") print() - + print("๐Ÿ”ง Key Fixes Applied:") - print(" โœ… Proper message sequence waiting (like old API)") - print(" โœ… Handshake completion before background tasks") - print(" โœ… Authentication event handling") - print(" โœ… Timeout handling for server responses") + print(" Proper message sequence waiting (like old API)") + print(" Handshake completion before background tasks") + print(" Authentication event handling") + print(" Timeout handling for server responses") print() + async def main(): """Main test function""" - - print("๐Ÿงช Testing Fixed Async API Connection") - print("๐ŸŽฏ Goal: Verify connection works like old API") + + print("Testing Fixed Async API Connection") + print("Goal: Verify connection works like old API") print() - + # Test the fixed connection success = await test_connection_fix() - + # Show comparison await test_old_vs_new_comparison() - + print("=" * 60) if success: - print("โœ… CONNECTION FIX VERIFICATION COMPLETE") - print("๐Ÿ“ The new async API now follows the same handshake pattern as the old API") + print(" CONNECTION FIX VERIFICATION COMPLETE") + print( + "๐Ÿ“ The new async API now follows the same handshake pattern as the old API" + ) print("๐Ÿ”ง Key improvements:") print(" โ€ข Proper server response waiting") print(" โ€ข Sequential handshake messages") print(" โ€ข Authentication event handling") print(" โ€ข Error handling with timeouts") else: - print("โŒ CONNECTION FIX NEEDS MORE WORK") + print("CONNECTION FIX NEEDS MORE WORK") print("=" * 60) + if __name__ == "__main__": asyncio.run(main()) diff --git a/tests/test_new_api.py b/tests/test_new_api.py index 07de133..d505419 100644 --- a/tests/test_new_api.py +++ b/tests/test_new_api.py @@ -4,67 +4,65 @@ import asyncio import os -from datetime import datetime # Import the new async API from pocketoptionapi_async import ( AsyncPocketOptionClient, OrderDirection, - PocketOptionError, ConnectionError, - OrderError ) async def test_basic_functionality(): """Test basic functionality of the new async API""" - - print("๐Ÿงช Testing Professional Async PocketOption API") + + print("Testing Professional Async PocketOption API") print("=" * 50) - + # Complete SSID format for testing (replace with real one for live testing) - complete_ssid = os.getenv("POCKET_OPTION_SSID", - r'42["auth",{"session":"n1p5ah5u8t9438rbunpgrq0hlq","isDemo":1,"uid":0,"platform":1}]') - + complete_ssid = os.getenv( + "POCKET_OPTION_SSID", + r'42["auth",{"session":"n1p5ah5u8t9438rbunpgrq0hlq","isDemo":1,"uid":0,"platform":1}]', + ) + if "n1p5ah5u8t9438rbunpgrq0hlq" in complete_ssid: - print("โš ๏ธ Using mock SSID. Set POCKET_OPTION_SSID environment variable for live testing.") - print(" Format: export POCKET_OPTION_SSID='42[\"auth\",{\"session\":\"your_session\",\"isDemo\":1,\"uid\":your_uid,\"platform\":1}]'") - + print( + " Using mock SSID. Set POCKET_OPTION_SSID environment variable for live testing." + ) + print( + ' Format: export POCKET_OPTION_SSID=\'42["auth",{"session":"your_session","isDemo":1,"uid":your_uid,"platform":1}]\'' + ) + try: # Test 1: Client initialization - print("\n1๏ธโƒฃ Testing client initialization...") - client = AsyncPocketOptionClient( - ssid=complete_ssid, - is_demo=True - ) - print("โœ… Client initialized successfully") - + print("\nTesting client initialization...") + client = AsyncPocketOptionClient(ssid=complete_ssid, is_demo=True) + print(" Client initialized successfully") + # Test 2: Connection (will fail with mock session, but tests the flow) - print("\n2๏ธโƒฃ Testing connection...") + print("\nTesting connection...") try: await client.connect() - print("โœ… Connected successfully") - + print(" Connected successfully") + # Test 3: Get balance - print("\n3๏ธโƒฃ Testing balance retrieval...") + print("\nTesting balance retrieval...") try: balance = await client.get_balance() - print(f"โœ… Balance: ${balance.balance:.2f} ({balance.currency})") + print(f" Balance: ${balance.balance:.2f} ({balance.currency})") except Exception as e: - print(f"โ„น๏ธ Balance test: {e}") - + print(f"Balance test: {e}") + # Test 4: Get candles - print("\n4๏ธโƒฃ Testing candles retrieval...") + print("\nTesting candles retrieval...") try: candles = await client.get_candles( - asset="EURUSD_otc", - timeframe="1m", - count=10 + asset="EURUSD_otc", timeframe="1m", count=10 ) - print(f"โœ… Retrieved {len(candles)} candles") + print(f" Retrieved {len(candles)} candles") except Exception as e: - print(f"โ„น๏ธ Candles test: {e}") - + print(f"Candles test: {e}") + # Test 5: Order placement (demo) print("\n5๏ธโƒฃ Testing order placement...") try: @@ -72,33 +70,35 @@ async def test_basic_functionality(): asset="EURUSD_otc", amount=1.0, direction=OrderDirection.CALL, - duration=60 + duration=60, ) - print(f"โœ… Order placed: {order_result.order_id}") + print(f" Order placed: {order_result.order_id}") except Exception as e: - print(f"โ„น๏ธ Order test: {e}") - + print(f"Order test: {e}") + except ConnectionError as e: - print(f"โ„น๏ธ Connection test (expected with mock session): {e}") - + print(f"Connection test (expected with mock session): {e}") + finally: # Test 6: Disconnection print("\n6๏ธโƒฃ Testing disconnection...") await client.disconnect() - print("โœ… Disconnected successfully") - + print(" Disconnected successfully") + except Exception as e: - print(f"โŒ Unexpected error: {e}") - - print("\n๐ŸŽฏ API Structure Tests") + print(f"Unexpected error: {e}") + + print("\nAPI Structure Tests") print("=" * 30) - + # Test API structure test_api_structure() - - print("\nโœ… All tests completed!") - print("\n๐Ÿ“– Next steps:") - print(" 1. Set your real session ID: $env:POCKET_OPTION_SSID='your_real_session_id'") + + print("\n All tests completed!") + print("\nNext steps:") + print( + " 1. Set your real session ID: $env:POCKET_OPTION_SSID='your_real_session_id'" + ) print(" 2. Run with real session: python test_new_api.py") print(" 3. Check examples in examples/async_examples.py") print(" 4. Read full documentation in README_ASYNC.md") @@ -106,7 +106,7 @@ async def test_basic_functionality(): def test_api_structure(): """Test that all API components are properly structured""" - + # Test imports try: from pocketoptionapi_async import ( @@ -117,120 +117,118 @@ def test_api_structure(): Order, OrderResult, ASSETS, - REGIONS + REGIONS, ) - print("โœ… All imports successful") + + print(" All imports successful") except ImportError as e: - print(f"โŒ Import error: {e}") + print(f"Import error: {e}") return - + # Test enums assert OrderDirection.CALL == "call" assert OrderDirection.PUT == "put" - print("โœ… Enums working correctly") - + print(" Enums working correctly") + # Test constants assert "EURUSD_otc" in ASSETS assert len(REGIONS.get_all()) > 0 - print("โœ… Constants available") - + print(" Constants available") + # Test model validation try: # Valid order - order = Order( - asset="EURUSD_otc", - amount=10.0, - direction=OrderDirection.CALL, - duration=120 + Order( + asset="EURUSD_otc", amount=10.0, direction=OrderDirection.CALL, duration=120 ) - print("โœ… Model validation working") - + print(" Model validation working") + # Invalid order (should raise ValueError) try: - invalid_order = Order( + Order( asset="EURUSD_otc", amount=-10.0, # Invalid amount direction=OrderDirection.CALL, - duration=120 + duration=120, ) - print("โŒ Model validation not working") + print("Model validation not working") except ValueError: - print("โœ… Model validation correctly catches errors") - + print(" Model validation correctly catches errors") + except Exception as e: - print(f"โŒ Model test error: {e}") + print(f"Model test error: {e}") async def test_context_manager(): """Test async context manager functionality""" - + print("\n๐Ÿ”ง Testing context manager...") - + session_id = "n1p5ah5u8t9438rbunpgrq0hlq" - + try: async with AsyncPocketOptionClient(session_id, is_demo=True) as client: - print("โœ… Context manager entry successful") + print(" Context manager entry successful") assert client is not None - print("โœ… Context manager exit successful") + print(" Context manager exit successful") except Exception as e: - print(f"โ„น๏ธ Context manager test (expected with mock): {e}") + print(f"Context manager test (expected with mock): {e}") async def test_event_callbacks(): """Test event callback system""" - + print("\n๐Ÿ“ก Testing event callbacks...") - + session_id = "n1p5ah5u8t9438rbunpgrq0hlq" client = AsyncPocketOptionClient(session_id, is_demo=True) - + # Test callback registration callback_called = False - + def test_callback(data): nonlocal callback_called callback_called = True - - client.add_event_callback('test_event', test_callback) - print("โœ… Event callback registered") - + + client.add_event_callback("test_event", test_callback) + print(" Event callback registered") + # Test callback removal - client.remove_event_callback('test_event', test_callback) - print("โœ… Event callback removed") + client.remove_event_callback("test_event", test_callback) + print(" Event callback removed") def print_api_features(): """Print the key features of the new API""" - - print("\n๐Ÿš€ NEW ASYNC API FEATURES") + + print("\nNEW ASYNC API FEATURES") print("=" * 40) - + features = [ - "โœ… 100% Async/Await Support", - "โœ… Type Safety with Pydantic Models", - "โœ… Professional Error Handling", - "โœ… Automatic Connection Management", - "โœ… Event-Driven Architecture", - "โœ… pandas DataFrame Integration", - "โœ… Built-in Rate Limiting", - "โœ… Context Manager Support", - "โœ… Comprehensive Testing", - "โœ… Rich Logging with loguru", - "โœ… WebSocket Auto-Reconnection", - "โœ… Modern Python Practices" + " 100% Async/Await Support", + " Type Safety with Pydantic Models", + " Professional Error Handling", + " Automatic Connection Management", + " Event-Driven Architecture", + " pandas DataFrame Integration", + " Built-in Rate Limiting", + " Context Manager Support", + " Comprehensive Testing", + " Rich Logging with loguru", + " WebSocket Auto-Reconnection", + " Modern Python Practices", ] - + for feature in features: print(f" {feature}") - + print("\n๐Ÿ“Š SUPPORTED ASSETS:") print(" - 50+ Forex pairs (major and exotic)") print(" - 20+ Cryptocurrencies") print(" - 15+ Commodities (Gold, Silver, Oil, etc.)") print(" - 25+ Stock Indices") print(" - 50+ Individual Stocks") - + print("\nโšก PERFORMANCE IMPROVEMENTS:") print(" - Non-blocking async operations") print(" - Concurrent order management") @@ -240,7 +238,7 @@ def print_api_features(): if __name__ == "__main__": print_api_features() - + # Run all tests asyncio.run(test_basic_functionality()) asyncio.run(test_context_manager()) diff --git a/tests/test_order_fix.py b/tests/test_order_fix.py index cca6a0f..a3aed62 100644 --- a/tests/test_order_fix.py +++ b/tests/test_order_fix.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Test script to verify the place_order fix """ @@ -7,67 +6,71 @@ from loguru import logger from pocketoptionapi_async import AsyncPocketOptionClient, OrderDirection + async def test_order_placement(): """Test placing an order to verify the fix""" - + ssid = r'42["auth",{"session":"n1p5ah5u8t9438rbunpgrq0hlq","isDemo":1,"uid":72645361,"platform":1,"isFastHistory":true}]' - + client = AsyncPocketOptionClient(ssid=ssid, is_demo=True) - + try: - logger.info("๐Ÿ”Œ Connecting to PocketOption...") + logger.info("Connecting to PocketOption...") await client.connect() - + if client.is_connected: - logger.success("โœ… Connected successfully!") - + logger.success(" Connected successfully!") + # Wait for authentication and balance await asyncio.sleep(3) - + try: balance = await client.get_balance() if balance: - logger.info(f"๐Ÿ’ฐ Balance: ${balance.balance:.2f}") + logger.info(f"Balance: ${balance.balance:.2f}") else: - logger.warning("โš ๏ธ No balance data received") + logger.warning("No balance data received") except Exception as e: - logger.info(f"โ„น๏ธ Balance error (expected with demo): {e}") - + logger.info(f"Balance error (expected with demo): {e}") + # Test placing an order (this should now work without the order_id error) - logger.info("๐Ÿ“ˆ Testing order placement...") + logger.info("esting order placement...") try: order_result = await client.place_order( asset="EURUSD_otc", amount=1.0, direction=OrderDirection.CALL, - duration=60 + duration=60, ) - - logger.success(f"โœ… Order placed successfully!") + + logger.success(" Order placed successfully!") logger.info(f" Order ID: {order_result.order_id}") logger.info(f" Status: {order_result.status}") logger.info(f" Asset: {order_result.asset}") logger.info(f" Amount: ${order_result.amount}") logger.info(f" Direction: {order_result.direction}") - + except Exception as e: - logger.error(f"โŒ Order placement failed: {e}") + logger.error(f"Order placement failed: {e}") # Check if it's the same error as before if "'Order' object has no attribute 'order_id'" in str(e): - logger.error("โŒ The original error is still present!") + logger.error("The original error is still present!") else: - logger.info("โ„น๏ธ Different error (this is expected with demo connection)") + logger.info( + "Different error (this is expected with demo connection)" + ) else: - logger.warning("โš ๏ธ Connection failed (expected with demo SSID)") - + logger.warning("Connection failed (expected with demo SSID)") + except Exception as e: - logger.error(f"โŒ Connection error: {e}") - + logger.error(f"Connection error: {e}") + finally: await client.disconnect() - logger.info("๐Ÿ”Œ Disconnected") + logger.info("Disconnected") + if __name__ == "__main__": - logger.info("๐Ÿงช Testing Order Placement Fix") + logger.info("Testing Order Placement Fix") logger.info("=" * 50) asyncio.run(test_order_placement()) diff --git a/tests/test_order_logging_fixes.py b/tests/test_order_logging_fixes.py index a646417..e29ddd6 100644 --- a/tests/test_order_logging_fixes.py +++ b/tests/test_order_logging_fixes.py @@ -1,11 +1,9 @@ -#!/usr/bin/env python3 """ Test Order Tracking and Logging Fixes """ import asyncio import os -from datetime import datetime from loguru import logger from pocketoptionapi_async import AsyncPocketOptionClient, OrderDirection @@ -13,121 +11,119 @@ async def test_fixes(): """Test that order tracking works correctly and logging can be disabled""" - + # Get SSID from environment or use placeholder ssid = os.getenv("POCKET_OPTION_SSID", "your_session_id_here") - + if ssid == "your_session_id_here": - print("โŒ Please set POCKET_OPTION_SSID environment variable") + print("Please set POCKET_OPTION_SSID environment variable") return - - print("๐Ÿงช Testing Order Tracking and Logging Fixes...") - + + print("Testing Order Tracking and Logging Fixes...") + # Test 1: Client with logging enabled (default) - print("\n1๏ธโƒฃ Test: Client with logging ENABLED") + print("\nTest: Client with logging ENABLED") client_with_logs = AsyncPocketOptionClient(ssid, is_demo=True, enable_logging=True) - + try: # Connect print("๐Ÿ“ก Connecting...") await client_with_logs.connect() - + if not client_with_logs.is_connected: - print("โŒ Failed to connect") + print("Failed to connect") return - - print("โœ… Connected successfully") - + + print(" Connected successfully") + # Wait for initialization await asyncio.sleep(3) - + # Get balance balance = await client_with_logs.get_balance() if balance: - print(f"๐Ÿ’ฐ Balance: ${balance.balance:.2f} (Demo: {balance.is_demo})") - + print(f"Balance: ${balance.balance:.2f} (Demo: {balance.is_demo})") + # Place a test order - print("\n๐ŸŽฏ Placing test order...") + print("\nPlacing test order...") order_result = await client_with_logs.place_order( - asset="EURUSD_otc", - amount=1.0, - direction=OrderDirection.CALL, - duration=60 + asset="EURUSD_otc", amount=1.0, direction=OrderDirection.CALL, duration=60 ) - - print(f"๐Ÿ“ˆ Order placed: {order_result.order_id}") + + print(f"Order placed: {order_result.order_id}") print(f" Status: {order_result.status}") print(f" Error Message: {order_result.error_message or 'None'}") - + # Check if order is properly tracked - immediate_result = await client_with_logs.check_order_result(order_result.order_id) + immediate_result = await client_with_logs.check_order_result( + order_result.order_id + ) if immediate_result: - print("โœ… Order found in tracking system immediately") + print(" Order found in tracking system immediately") else: - print("โŒ Order NOT found in tracking") - + print("Order NOT found in tracking") + # Wait a bit to see if it gets resolved await asyncio.sleep(10) - + # Check again final_result = await client_with_logs.check_order_result(order_result.order_id) if final_result: print(f"๐Ÿ“‹ Final order status: {final_result.status}") if final_result.profit is not None: - print(f"๐Ÿ’ฐ Profit: ${final_result.profit:.2f}") - + print(f"Profit: ${final_result.profit:.2f}") + finally: await client_with_logs.disconnect() - - print("\n" + "="*50) - + + print("\n" + "=" * 50) + # Test 2: Client with logging disabled - print("\n2๏ธโƒฃ Test: Client with logging DISABLED") + print("\nTest: Client with logging DISABLED") client_no_logs = AsyncPocketOptionClient(ssid, is_demo=True, enable_logging=False) - + try: # Connect (should be much quieter) print("๐Ÿ“ก Connecting (quietly)...") await client_no_logs.connect() - + if not client_no_logs.is_connected: - print("โŒ Failed to connect") + print("Failed to connect") return - - print("โœ… Connected successfully (no logs)") - + + print(" Connected successfully (no logs)") + # Wait for initialization await asyncio.sleep(3) - + # Get balance balance = await client_no_logs.get_balance() if balance: - print(f"๐Ÿ’ฐ Balance: ${balance.balance:.2f} (Demo: {balance.is_demo})") - + print(f"Balance: ${balance.balance:.2f} (Demo: {balance.is_demo})") + # Place a test order (should work silently) - print("\n๐ŸŽฏ Placing test order (silently)...") + print("\nPlacing test order (silently)...") order_result = await client_no_logs.place_order( - asset="EURUSD_otc", - amount=1.0, - direction=OrderDirection.CALL, - duration=60 + asset="EURUSD_otc", amount=1.0, direction=OrderDirection.CALL, duration=60 ) - - print(f"๐Ÿ“ˆ Order placed: {order_result.order_id}") + + print(f"Order placed: {order_result.order_id}") print(f" Status: {order_result.status}") print(f" Error Message: {order_result.error_message or 'None'}") - + # Check if order is properly tracked - immediate_result = await client_no_logs.check_order_result(order_result.order_id) + immediate_result = await client_no_logs.check_order_result( + order_result.order_id + ) if immediate_result: - print("โœ… Order found in tracking system (silent mode)") + print(" Order found in tracking system (silent mode)") else: - print("โŒ Order NOT found in tracking") - + print("Order NOT found in tracking") + finally: await client_no_logs.disconnect() - - print("\nโœ… Tests completed!") + + print("\n Tests completed!") if __name__ == "__main__": @@ -136,7 +132,7 @@ async def test_fixes(): logger.add( lambda msg: print(msg, end=""), format="{level} | {message}", - level="INFO" + level="INFO", ) - + asyncio.run(test_fixes()) diff --git a/tests/test_order_placement_fix.py b/tests/test_order_placement_fix.py index 31109cb..6d30569 100644 --- a/tests/test_order_placement_fix.py +++ b/tests/test_order_placement_fix.py @@ -1,11 +1,9 @@ -#!/usr/bin/env python3 """ Test script to verify order placement fix """ import asyncio import os -from datetime import datetime from loguru import logger # Configure logger @@ -13,7 +11,7 @@ logger.add( lambda msg: print(msg, end=""), format="{time:HH:mm:ss} | {level} | {message}", - level="INFO" + level="INFO", ) from pocketoptionapi_async import AsyncPocketOptionClient, OrderDirection @@ -21,82 +19,87 @@ async def test_order_placement_fix(): """Test the order placement fix""" - + # Get SSID from environment or use a placeholder ssid = os.getenv("POCKET_OPTION_SSID", "placeholder_session_id") - + if ssid == "placeholder_session_id": - logger.warning("โš ๏ธ No SSID provided - using placeholder (will fail connection)") + logger.warning("No SSID provided - using placeholder (will fail connection)") logger.info("Set POCKET_OPTION_SSID environment variable for real testing") - - logger.info("๐Ÿงช Testing order placement fix...") - + + logger.info("Testing order placement fix...") + # Create client client = AsyncPocketOptionClient(ssid, is_demo=True) - + try: # Test order creation (this should not fail with the attribute error anymore) logger.info("๐Ÿ“ Testing Order model creation...") - + # This should work now (Order uses request_id) from pocketoptionapi_async.models import Order + test_order = Order( - asset="EURUSD_otc", - amount=1.0, - direction=OrderDirection.CALL, - duration=60 + asset="EURUSD_otc", amount=1.0, direction=OrderDirection.CALL, duration=60 + ) + + logger.success( + f" Order created successfully with request_id: {test_order.request_id}" ) - - logger.success(f"โœ… Order created successfully with request_id: {test_order.request_id}") logger.info(f" Asset: {test_order.asset}") logger.info(f" Amount: {test_order.amount}") logger.info(f" Direction: {test_order.direction}") logger.info(f" Duration: {test_order.duration}") - + # Test that the order doesn't have order_id attribute - if not hasattr(test_order, 'order_id'): - logger.success("โœ… Order correctly uses request_id instead of order_id") + if not hasattr(test_order, "order_id"): + logger.success(" Order correctly uses request_id instead of order_id") else: - logger.error("โŒ Order still has order_id attribute - this should not exist") - + logger.error("Order still has order_id attribute - this should not exist") + # If we have a real SSID, try connecting and placing an order if ssid != "placeholder_session_id": - logger.info("๐Ÿ”Œ Attempting to connect and place order...") - + logger.info("Attempting to connect and place order...") + await client.connect() - + if client.is_connected: - logger.success("โœ… Connected successfully") - + logger.success(" Connected successfully") + # Try to place an order (this should not fail with attribute error) try: order_result = await client.place_order( asset="EURUSD_otc", amount=1.0, direction=OrderDirection.CALL, - duration=60 + duration=60, + ) + + logger.success( + f" Order placement succeeded: {order_result.order_id}" ) - - logger.success(f"โœ… Order placement succeeded: {order_result.order_id}") logger.info(f" Status: {order_result.status}") - + except Exception as e: if "'Order' object has no attribute 'order_id'" in str(e): - logger.error("โŒ The attribute error still exists!") + logger.error("The attribute error still exists!") else: - logger.warning(f"โš ๏ธ Order placement failed for other reason: {e}") - logger.info("This is likely due to connection/authentication issues, not the attribute fix") - + logger.warning(f"Order placement failed for other reason: {e}") + logger.info( + "This is likely due to connection/authentication issues, not the attribute fix" + ) + else: - logger.warning("โš ๏ธ Could not connect (expected with placeholder SSID)") - + logger.warning("Could not connect (expected with placeholder SSID)") + logger.success("๐ŸŽ‰ Order placement fix test completed!") - + except Exception as e: - logger.error(f"โŒ Test failed: {e}") + logger.error(f"Test failed: {e}") import traceback + traceback.print_exc() - + finally: if client.is_connected: await client.disconnect() diff --git a/tests/test_order_tracking_complete.py b/tests/test_order_tracking_complete.py index 26b3aa8..7437afa 100644 --- a/tests/test_order_tracking_complete.py +++ b/tests/test_order_tracking_complete.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Complete Order Tracking Test - Final Version Tests all the fixes made to the order tracking system: @@ -10,7 +9,6 @@ import asyncio import os -import time from datetime import datetime, timedelta from loguru import logger @@ -19,250 +17,279 @@ async def test_complete_order_lifecycle(): """Test the complete order lifecycle with all fixes""" - + # Get SSID from environment ssid = os.getenv("POCKET_OPTION_SSID") - + if not ssid: - print("โŒ Please set POCKET_OPTION_SSID environment variable") + print("Please set POCKET_OPTION_SSID environment variable") print("Example: set POCKET_OPTION_SSID='your_session_id_here'") return - - print("๐Ÿš€ Complete Order Tracking Test - Final Version") + + print("Complete Order Tracking Test - Final Version") print("=" * 60) - + # Create client client = AsyncPocketOptionClient(ssid, is_demo=True) - + try: # Connect print("๐Ÿ“ก Connecting...") await client.connect() - + if not client.is_connected: - print("โŒ Failed to connect") + print("Failed to connect") return - - print("โœ… Connected successfully") - + + print(" Connected successfully") + # Wait for initialization await asyncio.sleep(3) - + # Get balance balance = await client.get_balance() if balance: - print(f"๐Ÿ’ฐ Balance: ${balance.balance:.2f} (Demo: {balance.is_demo})") + print(f"Balance: ${balance.balance:.2f} (Demo: {balance.is_demo})") else: - print("โš ๏ธ No balance received") - + print("No balance received") + # Test 1: Order Placement (should not create duplicates) - print(f"\n๐Ÿ“‹ TEST 1: Order Placement Without Duplication") + print("\n๐Ÿ“‹ TEST 1: Order Placement Without Duplication") print("-" * 50) - + # Check initial active orders count initial_active = await client.get_active_orders() print(f"๐Ÿ“Š Initial active orders: {len(initial_active)}") - + # Place order - print(f"๐Ÿ“ˆ Placing order...") + print("Placing order...") order_result = await client.place_order( asset="EURUSD_otc", amount=1.0, direction=OrderDirection.CALL, - duration=60 # 1 minute + duration=60, # 1 minute ) - - print(f"โœ… Order placed: {order_result.order_id}") + + print(f" Order placed: {order_result.order_id}") print(f" Status: {order_result.status}") print(f" Asset: {order_result.asset}") print(f" Amount: ${order_result.amount}") print(f" Direction: {order_result.direction}") print(f" Duration: {order_result.duration}s") - + # Test 2: No Duplication Check - print(f"\n๐Ÿ“‹ TEST 2: No Order Duplication Check") + print("\n๐Ÿ“‹ TEST 2: No Order Duplication Check") print("-" * 50) - + # Check that only one order was created active_orders_after = await client.get_active_orders() added_orders = len(active_orders_after) - len(initial_active) - + if added_orders == 1: - print("โœ… PASS: Exactly 1 order was created (no duplication)") + print(" PASS: Exactly 1 order was created (no duplication)") else: - print(f"โŒ FAIL: {added_orders} orders were created (expected 1)") + print(f"FAIL: {added_orders} orders were created (expected 1)") for order in active_orders_after: print(f" - {order.order_id}: {order.status}") - + # Test 3: Order Tracking - print(f"\n๐Ÿ“‹ TEST 3: Order Tracking and Result Checking") + print("\n๐Ÿ“‹ TEST 3: Order Tracking and Result Checking") print("-" * 50) - + # Immediate check immediate_result = await client.check_order_result(order_result.order_id) if immediate_result: - print("โœ… Order immediately found in tracking system") + print(" Order immediately found in tracking system") print(f" ID: {immediate_result.order_id}") print(f" Status: {immediate_result.status}") else: - print("โŒ Order NOT found in tracking system - this is a problem!") + print("Order NOT found in tracking system - this is a problem!") return - + # Test 4: Event-Based Order Completion Monitoring - print(f"\n๐Ÿ“‹ TEST 4: Event-Based Order Completion") + print("\n๐Ÿ“‹ TEST 4: Event-Based Order Completion") print("-" * 50) - + # Set up event callback to detect completion completed_orders = [] - + def on_order_closed(order_result): completed_orders.append(order_result) - status = "WIN" if order_result.profit > 0 else "LOSE" if order_result.profit < 0 else "EVEN" - print(f"๐ŸŽฏ ORDER COMPLETED via EVENT: {status} - Profit: ${order_result.profit:.2f}") - - client.add_event_callback('order_closed', on_order_closed) - + status = ( + "WIN" + if order_result.profit > 0 + else "LOSE" + if order_result.profit < 0 + else "EVEN" + ) + print( + f"ORDER COMPLETED via EVENT: {status} - Profit: ${order_result.profit:.2f}" + ) + + client.add_event_callback("order_closed", on_order_closed) + # Test 5: Wait for Trade Completion - print(f"\n๐Ÿ“‹ TEST 5: Waiting for Trade Completion") + print("\n๐Ÿ“‹ TEST 5: Waiting for Trade Completion") print("-" * 50) - - print(f"โฑ๏ธ Waiting for trade to complete (up to {order_result.duration + 30} seconds)...") + + print( + f"โฑ๏ธ Waiting for trade to complete (up to {order_result.duration + 30} seconds)..." + ) start_time = datetime.now() - max_wait = timedelta(seconds=order_result.duration + 30) # Trade duration + buffer - + max_wait = timedelta( + seconds=order_result.duration + 30 + ) # Trade duration + buffer + last_status = None - + while datetime.now() - start_time < max_wait: result = await client.check_order_result(order_result.order_id) - + if result: # Only print status changes to avoid spam if result.status != last_status: - status_emoji = "๐ŸŸข" if result.status == "active" else "๐Ÿ”ด" if result.status in ["win", "lose"] else "๐ŸŸก" + status_emoji = ( + "๐ŸŸข" + if result.status == "active" + else "๐Ÿ”ด" + if result.status in ["win", "lose"] + else "๐ŸŸก" + ) print(f" {status_emoji} Order status: {result.status}") last_status = result.status - + # Check if order completed if result.profit is not None: - win_lose = "WIN" if result.profit > 0 else "LOSE" if result.profit < 0 else "EVEN" - print(f"\n๐ŸŽฏ TRADE COMPLETED!") + win_lose = ( + "WIN" + if result.profit > 0 + else "LOSE" + if result.profit < 0 + else "EVEN" + ) + print("\nTRADE COMPLETED!") print(f" Result: {win_lose}") print(f" Profit/Loss: ${result.profit:.2f}") if result.payout: print(f" Payout: ${result.payout:.2f}") - + # Calculate percentage return if result.profit != 0: percentage = (result.profit / order_result.amount) * 100 print(f" Return: {percentage:.1f}%") - + break - + # Check if status indicates completion but no profit yet elif result.status in ["win", "lose", "closed"]: - print(f" ๐Ÿ“Š Order marked as {result.status} but no profit data yet...") - + print( + f" ๐Ÿ“Š Order marked as {result.status} but no profit data yet..." + ) + else: - print(" โŒ Order disappeared from tracking system") + print(" Order disappeared from tracking system") break - + await asyncio.sleep(2) # Check every 2 seconds - + # Test 6: Event vs Polling Comparison - print(f"\n๐Ÿ“‹ TEST 6: Event vs Polling Results") + print("\n๐Ÿ“‹ TEST 6: Event vs Polling Results") print("-" * 50) - + # Check if we completed via event callback if completed_orders: - print(f"โœ… Order completion detected via EVENT callback!") + print(" Order completion detected via EVENT callback!") final_order_event = completed_orders[0] print(f" Event Result - Profit: ${final_order_event.profit:.2f}") else: - print(f"โš ๏ธ No completion event received") - + print("No completion event received") + # Check final polling result final_result_poll = await client.check_order_result(order_result.order_id) if final_result_poll: - print(f"โœ… Order completion detected via POLLING!") - print(f" Polling Result - Profit: ${final_result_poll.profit:.2f if final_result_poll.profit is not None else 'None'}") + print(" Order completion detected via POLLING!") + print( + f" Polling Result - Profit: ${final_result_poll.profit:.2f if final_result_poll.profit is not None else 'None'}" + ) else: - print(f"โŒ Order not found via polling") - + print("Order not found via polling") + # Test 7: Final System State - print(f"\n๐Ÿ“‹ TEST 7: Final System State") + print("\n๐Ÿ“‹ TEST 7: Final System State") print("-" * 50) - + # Check final counts final_active_orders = await client.get_active_orders() print(f"๐Ÿ“Š Final active orders: {len(final_active_orders)}") - + for order in final_active_orders: print(f" Active: {order.order_id} - {order.status}") - + # Show test summary - print(f"\n๐Ÿ“‹ TEST SUMMARY") + print("\n๐Ÿ“‹ TEST SUMMARY") print("=" * 60) - + tests_passed = 0 total_tests = 7 - + # Test results if added_orders == 1: - print("โœ… Order Placement (No Duplication): PASS") + print(" Order Placement (No Duplication): PASS") tests_passed += 1 else: - print("โŒ Order Placement (No Duplication): FAIL") - + print("Order Placement (No Duplication): FAIL") + if immediate_result: - print("โœ… Order Tracking: PASS") + print(" Order Tracking: PASS") tests_passed += 1 else: - print("โŒ Order Tracking: FAIL") - + print("Order Tracking: FAIL") + if completed_orders: - print("โœ… Event-Based Completion: PASS") + print(" Event-Based Completion: PASS") tests_passed += 1 else: - print("โŒ Event-Based Completion: FAIL") - + print("Event-Based Completion: FAIL") + if final_result_poll and final_result_poll.profit is not None: - print("โœ… Polling-Based Completion: PASS") + print(" Polling-Based Completion: PASS") tests_passed += 1 else: - print("โŒ Polling-Based Completion: FAIL") - + print("Polling-Based Completion: FAIL") + # Additional checks if len(final_active_orders) < len(active_orders_after): - print("โœ… Order Movement (Active -> Completed): PASS") + print(" Order Movement (Active -> Completed): PASS") tests_passed += 1 else: - print("โŒ Order Movement (Active -> Completed): FAIL") - + print("Order Movement (Active -> Completed): FAIL") + if balance: - print("โœ… Balance Retrieval: PASS") + print(" Balance Retrieval: PASS") tests_passed += 1 else: - print("โŒ Balance Retrieval: FAIL") - - print(f"\n๐ŸŽฏ OVERALL RESULT: {tests_passed}/{total_tests} tests passed") - + print("Balance Retrieval: FAIL") + + print(f"\nOVERALL RESULT: {tests_passed}/{total_tests} tests passed") + if tests_passed >= 5: print("๐ŸŽ‰ ORDER TRACKING SYSTEM IS WORKING WELL!") elif tests_passed >= 3: - print("โš ๏ธ Order tracking is partially working, some improvements needed") + print("Order tracking is partially working, some improvements needed") else: - print("โŒ Major issues with order tracking system") - + print("Major issues with order tracking system") + except Exception as e: - print(f"โŒ Error: {e}") + print(f"Error: {e}") import traceback + traceback.print_exc() - + finally: # Disconnect - print(f"\n๐Ÿ”Œ Disconnecting...") + print("\nDisconnecting...") await client.disconnect() - print("โœ… Test completed") + print(" Test completed") if __name__ == "__main__": @@ -271,7 +298,7 @@ def on_order_closed(order_result): logger.add( lambda msg: print(msg, end=""), format="{level} | {message}", - level="ERROR" # Only show errors from the library to keep output clean + level="ERROR", # Only show errors from the library to keep output clean ) - + asyncio.run(test_complete_order_lifecycle()) diff --git a/tests/test_order_tracking_fix.py b/tests/test_order_tracking_fix.py index 64e1b5f..a87f502 100644 --- a/tests/test_order_tracking_fix.py +++ b/tests/test_order_tracking_fix.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Test Order Tracking Fix Test to verify that order tracking and result checking works properly @@ -14,119 +13,123 @@ async def test_order_tracking(): """Test order tracking functionality""" - + # Get SSID from environment or use placeholder ssid = os.getenv("POCKET_OPTION_SSID", "your_session_id_here") - + if ssid == "your_session_id_here": - print("โŒ Please set POCKET_OPTION_SSID environment variable") + print("Please set POCKET_OPTION_SSID environment variable") return - - print("๐Ÿš€ Testing Order Tracking Fix...") - + + print("Testing Order Tracking Fix...") + # Create client client = AsyncPocketOptionClient(ssid, is_demo=True) - + try: # Connect print("๐Ÿ“ก Connecting...") await client.connect() - + if not client.is_connected: - print("โŒ Failed to connect") + print("Failed to connect") return - - print("โœ… Connected successfully") - + + print(" Connected successfully") + # Wait for initialization await asyncio.sleep(3) - + # Get balance balance = await client.get_balance() if balance: - print(f"๐Ÿ’ฐ Balance: ${balance.balance:.2f} (Demo: {balance.is_demo})") + print(f"Balance: ${balance.balance:.2f} (Demo: {balance.is_demo})") else: - print("โš ๏ธ No balance received") - + print("No balance received") + # Place a test order - print("\n๐ŸŽฏ Placing test order...") + print("\nPlacing test order...") order_result = await client.place_order( - asset="EURUSD_otc", - amount=1.0, - direction=OrderDirection.CALL, - duration=60 + asset="EURUSD_otc", amount=1.0, direction=OrderDirection.CALL, duration=60 ) - - print(f"๐Ÿ“ˆ Order placed: {order_result.order_id}") + + print(f"Order placed: {order_result.order_id}") print(f" Status: {order_result.status}") print(f" Asset: {order_result.asset}") print(f" Amount: ${order_result.amount}") print(f" Direction: {order_result.direction}") print(f" Duration: {order_result.duration}s") - + # Test order result checking - should return the active order immediately print("\n๐Ÿ” Checking order result immediately...") immediate_result = await client.check_order_result(order_result.order_id) - + if immediate_result: - print("โœ… Order found in tracking system:") + print(" Order found in tracking system:") print(f" Order ID: {immediate_result.order_id}") print(f" Status: {immediate_result.status}") print(f" Placed at: {immediate_result.placed_at}") print(f" Expires at: {immediate_result.expires_at}") else: - print("โŒ Order NOT found in tracking system") + print("Order NOT found in tracking system") return - + # Check active orders print("\n๐Ÿ“Š Checking active orders...") active_orders = await client.get_active_orders() print(f"Active orders count: {len(active_orders)}") - + for order in active_orders: print(f" - {order.order_id}: {order.status} ({order.asset})") - + # Test tracking over time print("\nโฑ๏ธ Monitoring order for 30 seconds...") start_time = datetime.now() - + while (datetime.now() - start_time).total_seconds() < 30: result = await client.check_order_result(order_result.order_id) - + if result: - status_emoji = "๐ŸŸข" if result.status == "active" else "๐Ÿ”ด" if result.status in ["win", "lose"] else "๐ŸŸก" + status_emoji = ( + "๐ŸŸข" + if result.status == "active" + else "๐Ÿ”ด" + if result.status in ["win", "lose"] + else "๐ŸŸก" + ) print(f" {status_emoji} Order {result.order_id}: {result.status}") - + # If order completed, show result if result.profit is not None: win_lose = "WIN" if result.profit > 0 else "LOSE" - print(f" ๐ŸŽฏ Final result: {win_lose} - Profit: ${result.profit:.2f}") + print(f" Final result: {win_lose} - Profit: ${result.profit:.2f}") break else: - print(" โŒ Order not found in tracking") + print(" Order not found in tracking") break - + await asyncio.sleep(5) # Check every 5 seconds - + # Final status final_result = await client.check_order_result(order_result.order_id) if final_result: print(f"\n๐Ÿ“‹ Final order status: {final_result.status}") if final_result.profit is not None: - print(f"๐Ÿ’ฐ Profit/Loss: ${final_result.profit:.2f}") + print(f"Profit/Loss: ${final_result.profit:.2f}") else: - print("๐Ÿ’ฐ Profit/Loss: Not yet determined") - + print("Profit/Loss: Not yet determined") + except Exception as e: - print(f"โŒ Error: {e}") + print(f"Error: {e}") import traceback + traceback.print_exc() - + finally: # Disconnect - print("\n๐Ÿ”Œ Disconnecting...") + print("\nDisconnecting...") await client.disconnect() - print("โœ… Test completed") + print(" Test completed") if __name__ == "__main__": @@ -135,7 +138,7 @@ async def test_order_tracking(): logger.add( lambda msg: print(msg, end=""), format="{level} | {message}", - level="INFO" + level="INFO", ) - + asyncio.run(test_order_tracking()) diff --git a/tests/test_persistent_connection.py b/tests/test_persistent_connection.py index c5f6ff8..c1ed967 100644 --- a/tests/test_persistent_connection.py +++ b/tests/test_persistent_connection.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Test script for persistent connection with keep-alive functionality Demonstrates the enhanced connection management based on old API patterns @@ -6,8 +5,6 @@ import asyncio import os -import time -from datetime import datetime, timedelta from loguru import logger from pocketoptionapi_async import AsyncPocketOptionClient @@ -15,214 +12,228 @@ async def test_persistent_connection(): """Test persistent connection with automatic keep-alive""" - - print("๐Ÿงช Testing Persistent Connection with Keep-Alive") + + print("Testing Persistent Connection with Keep-Alive") print("=" * 60) print("This test demonstrates the enhanced connection management") print("based on the old API's proven keep-alive patterns:") - print("โœ… Automatic ping every 20 seconds") - print("โœ… Automatic reconnection on disconnection") - print("โœ… Multiple region fallback") - print("โœ… Background task management") - print("โœ… Connection health monitoring") + print(" Automatic ping every 20 seconds") + print(" Automatic reconnection on disconnection") + print(" Multiple region fallback") + print(" Background task management") + print(" Connection health monitoring") print("=" * 60) print() - + # Complete SSID format - complete_ssid = os.getenv("POCKET_OPTION_SSID", - r'42["auth",{"session":"n1p5ah5u8t9438rbunpgrq0hlq","isDemo":1,"uid":0,"platform":1}]') - + complete_ssid = os.getenv( + "POCKET_OPTION_SSID", + r'42["auth",{"session":"n1p5ah5u8t9438rbunpgrq0hlq","isDemo":1,"uid":0,"platform":1}]', + ) + if "n1p5ah5u8t9438rbunpgrq0hlq" in complete_ssid: - print("โ„น๏ธ Using test SSID - connection will fail but demonstrates the keep-alive logic") - print(" For real testing, set: export POCKET_OPTION_SSID='your_complete_ssid'") + print( + " Using test SSID - connection will fail but demonstrates the keep-alive logic" + ) + print( + " For real testing, set: export POCKET_OPTION_SSID='your_complete_ssid'" + ) print() - + # Test 1: Regular connection (existing behavior) print("๐Ÿ”ง Test 1: Regular Connection (with basic keep-alive)") print("-" * 50) - + try: client_regular = AsyncPocketOptionClient( - ssid=complete_ssid, + ssid=complete_ssid, is_demo=True, persistent_connection=False, # Regular connection - auto_reconnect=True # But with auto-reconnect + auto_reconnect=True, # But with auto-reconnect ) - + print("๐Ÿ“Š Connecting with regular mode...") success = await client_regular.connect() - + if success: - print("โœ… Regular connection established") - + print(" Regular connection established") + # Monitor for 30 seconds print("๐Ÿ“Š Monitoring regular connection for 30 seconds...") for i in range(30): await asyncio.sleep(1) - + if i % 10 == 0: stats = client_regular.get_connection_stats() - print(f" ๐Ÿ“ˆ Stats: Connected={client_regular.is_connected}, " - f"Pings sent={stats.get('messages_sent', 0)}, " - f"Reconnects={stats.get('total_reconnects', 0)}") + print( + f" Stats: Connected={client_regular.is_connected}, " + f"Pings sent={stats.get('messages_sent', 0)}, " + f"Reconnects={stats.get('total_reconnects', 0)}" + ) else: - print("โ„น๏ธ Regular connection failed (expected with test SSID)") - + print(" Regular connection failed (expected with test SSID)") + await client_regular.disconnect() - print("โœ… Regular connection test completed") - + print(" Regular connection test completed") + except Exception as e: - print(f"โ„น๏ธ Regular connection error (expected): {str(e)[:100]}...") - + print(f" Regular connection error (expected): {str(e)[:100]}...") + print() - + # Test 2: Persistent connection (new enhanced behavior) - print("๐Ÿš€ Test 2: Persistent Connection (enhanced keep-alive)") + print("Test 2: Persistent Connection (enhanced keep-alive)") print("-" * 50) - + try: client_persistent = AsyncPocketOptionClient( - ssid=complete_ssid, + ssid=complete_ssid, is_demo=True, persistent_connection=True, # Enhanced persistent mode - auto_reconnect=True + auto_reconnect=True, ) - + # Add event handlers to monitor keep-alive events connection_events = [] - + def on_connected(data): connection_events.append(f"Connected: {data}") print(f"๐ŸŽ‰ Event: Connected to {data}") - + def on_reconnected(data): connection_events.append(f"Reconnected: {data}") - print(f"๐Ÿ”„ Event: Reconnected after {data}") - + print(f"Event: Reconnected after {data}") + def on_authenticated(data): connection_events.append(f"Authenticated: {data}") - print(f"โœ… Event: Authenticated") - - client_persistent.add_event_callback('connected', on_connected) - client_persistent.add_event_callback('reconnected', on_reconnected) - client_persistent.add_event_callback('authenticated', on_authenticated) - + print(" Event: Authenticated") + + client_persistent.add_event_callback("connected", on_connected) + client_persistent.add_event_callback("reconnected", on_reconnected) + client_persistent.add_event_callback("authenticated", on_authenticated) + print("๐Ÿ“Š Connecting with persistent mode...") success = await client_persistent.connect() - + if success: - print("โœ… Persistent connection established with keep-alive active") - + print(" Persistent connection established with keep-alive active") + # Monitor for 60 seconds to see keep-alive in action print("๐Ÿ“Š Monitoring persistent connection for 60 seconds...") print(" (Watch for automatic pings every 20 seconds)") - + for i in range(60): await asyncio.sleep(1) - + # Print stats every 15 seconds if i % 15 == 0 and i > 0: stats = client_persistent.get_connection_stats() - print(f" ๐Ÿ“ˆ Stats: Connected={client_persistent.is_connected}, " - f"Pings={stats.get('last_ping_time')}, " - f"Messages sent={stats.get('messages_sent', 0)}, " - f"Messages received={stats.get('messages_received', 0)}, " - f"Reconnects={stats.get('total_reconnects', 0)}, " - f"Uptime={stats.get('uptime', 'N/A')}") - + print( + f" Stats: Connected={client_persistent.is_connected}, " + f"Pings={stats.get('last_ping_time')}, " + f"Messages sent={stats.get('messages_sent', 0)}, " + f"Messages received={stats.get('messages_received', 0)}, " + f"Reconnects={stats.get('total_reconnects', 0)}, " + f"Uptime={stats.get('uptime', 'N/A')}" + ) + # Send test message every 30 seconds if i % 30 == 0 and i > 0: print(" ๐Ÿ“ค Sending test message...") await client_persistent.send_message('42["test"]') - + # Show final statistics final_stats = client_persistent.get_connection_stats() print("\n๐Ÿ“Š Final Connection Statistics:") print(f" Total connections: {final_stats.get('total_connections', 0)}") - print(f" Successful connections: {final_stats.get('successful_connections', 0)}") + print( + f" Successful connections: {final_stats.get('successful_connections', 0)}" + ) print(f" Total reconnects: {final_stats.get('total_reconnects', 0)}") print(f" Messages sent: {final_stats.get('messages_sent', 0)}") print(f" Messages received: {final_stats.get('messages_received', 0)}") print(f" Connection uptime: {final_stats.get('uptime', 'N/A')}") print(f" Last ping: {final_stats.get('last_ping_time', 'None')}") print(f" Available regions: {final_stats.get('available_regions', 0)}") - + print(f"\n๐Ÿ“‹ Connection Events ({len(connection_events)} total):") for event in connection_events[-5:]: # Show last 5 events print(f" โ€ข {event}") - + else: - print("โ„น๏ธ Persistent connection failed (expected with test SSID)") - + print(" Persistent connection failed (expected with test SSID)") + await client_persistent.disconnect() - print("โœ… Persistent connection test completed") - + print(" Persistent connection test completed") + except Exception as e: - print(f"โ„น๏ธ Persistent connection error (expected): {str(e)[:100]}...") - + print(f" Persistent connection error (expected): {str(e)[:100]}...") + print() - + # Test 3: Connection resilience simulation print("๐Ÿ”ง Test 3: Connection Resilience Simulation") print("-" * 50) print("This would test automatic reconnection when connection drops") print("(Requires real SSID for full testing)") - + real_ssid = os.getenv("POCKET_OPTION_SSID") - if real_ssid and not "n1p5ah5u8t9438rbunpgrq0hlq" in real_ssid: + if real_ssid and "n1p5ah5u8t9438rbunpgrq0hlq" not in real_ssid: print("๐Ÿ”‘ Real SSID detected, testing with actual connection...") - + try: resilience_client = AsyncPocketOptionClient( ssid=real_ssid, is_demo=True, persistent_connection=True, - auto_reconnect=True + auto_reconnect=True, ) - + print("๐Ÿ“Š Establishing resilient connection...") success = await resilience_client.connect() - + if success: - print("โœ… Resilient connection established") - + print(" Resilient connection established") + # Monitor for 2 minutes print("๐Ÿ“Š Monitoring resilient connection for 2 minutes...") for i in range(120): await asyncio.sleep(1) - + if i % 30 == 0: stats = resilience_client.get_connection_stats() - print(f" ๐Ÿ“ˆ Stats: Connected={resilience_client.is_connected}, " - f"Uptime={stats.get('uptime', 'N/A')}") - + print( + f" Stats: Connected={resilience_client.is_connected}, " + f"Uptime={stats.get('uptime', 'N/A')}" + ) + # Try to get balance to test API functionality try: balance = await resilience_client.get_balance() - print(f" ๐Ÿ’ฐ Balance: ${balance.balance:.2f}") + print(f" Balance: ${balance.balance:.2f}") except Exception as e: - print(f" โš ๏ธ Balance check failed: {e}") - + print(f" Balance check failed: {e}") + await resilience_client.disconnect() - print("โœ… Resilience test completed") + print(" Resilience test completed") else: - print("โŒ Resilient connection failed") - + print("Resilient connection failed") + except Exception as e: - print(f"โŒ Resilience test error: {e}") + print(f"Resilience test error: {e}") else: - print("โ„น๏ธ Skipping resilience test (requires real SSID)") - + print(" Skipping resilience test (requires real SSID)") + print() print("๐ŸŽ‰ All persistent connection tests completed!") print() print("๐Ÿ“‹ Summary of Enhanced Features:") - print("โœ… Persistent connections with automatic keep-alive") - print("โœ… Automatic reconnection with multiple region fallback") - print("โœ… Background ping/pong handling (20-second intervals)") - print("โœ… Connection health monitoring and statistics") - print("โœ… Event-driven connection management") - print("โœ… Graceful connection cleanup and resource management") + print(" Persistent connections with automatic keep-alive") + print(" Automatic reconnection with multiple region fallback") + print(" Background ping/pong handling (20-second intervals)") + print(" Connection health monitoring and statistics") + print(" Event-driven connection management") + print(" Graceful connection cleanup and resource management") print() print("๐Ÿ’ก Usage Tips:") print("โ€ข Use persistent_connection=True for long-running applications") @@ -233,10 +244,10 @@ def on_authenticated(data): async def test_comparison_with_old_api(): """Compare new API behavior with old API patterns""" - + print("\n๐Ÿ” Comparison with Old API Patterns") print("=" * 50) - + print("Old API Features โ†’ New Async API Implementation:") print("โ€ข daemon threads โ†’ asyncio background tasks") print("โ€ข ping every 20s โ†’ async ping loop with '42[\"ps\"]'") @@ -246,7 +257,7 @@ async def test_comparison_with_old_api(): print("โ€ข manual error handling โ†’ automatic exception recovery") print("โ€ข blocking operations โ†’ non-blocking async operations") print() - + print("Enhanced Features in New API:") print("โœจ Type safety with Pydantic models") print("โœจ Comprehensive error monitoring and health checks") @@ -260,8 +271,8 @@ async def test_comparison_with_old_api(): if __name__ == "__main__": - logger.info("๐Ÿš€ Testing Enhanced Persistent Connection Functionality") - + logger.info("Testing Enhanced Persistent Connection Functionality") + # Run tests asyncio.run(test_persistent_connection()) asyncio.run(test_comparison_with_old_api()) diff --git a/tests/test_ssid_formats.py b/tests/test_ssid_formats.py index 97d22d8..de52142 100644 --- a/tests/test_ssid_formats.py +++ b/tests/test_ssid_formats.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Test script to demonstrate the updated SSID handling in PocketOption Async API """ @@ -6,109 +5,107 @@ import asyncio import json from pocketoptionapi_async import AsyncPocketOptionClient -from loguru import logger + async def test_ssid_formats(): """Test different SSID format handling""" - - print("๐Ÿงช Testing SSID Format Handling") + + print("Testing SSID Format Handling") print("=" * 50) - + # Test 1: Complete SSID format (as provided by user) complete_ssid = '42["auth",{"session":"n1p5ah5u8t9438rbunpgrq0hlq","isDemo":1,"uid":72645361,"platform":1,"isFastHistory":true}]' - - print("1๏ธโƒฃ Testing Complete SSID Format") + + print("Testing Complete SSID Format") print(f"Input: {complete_ssid}") - + client1 = AsyncPocketOptionClient(ssid=complete_ssid) - + # Verify parsing - print(f"โœ… Parsed session: {client1.session_id}") - print(f"โœ… Parsed demo: {client1.is_demo}") - print(f"โœ… Parsed UID: {client1.uid}") - print(f"โœ… Parsed platform: {client1.platform}") - print(f"โœ… Parsed fast history: {client1.is_fast_history}") - + print(f" Parsed session: {client1.session_id}") + print(f" Parsed demo: {client1.is_demo}") + print(f" Parsed UID: {client1.uid}") + print(f" Parsed platform: {client1.platform}") + print(f" Parsed fast history: {client1.is_fast_history}") + formatted_message = client1._format_session_message() - print(f"โœ… Formatted message: {formatted_message}") + print(f" Formatted message: {formatted_message}") print() - + # Test 2: Raw session ID raw_session = "n1p5ah5u8t9438rbunpgrq0hlq" - - print("2๏ธโƒฃ Testing Raw Session ID") + + print("Testing Raw Session ID") print(f"Input: {raw_session}") - + client2 = AsyncPocketOptionClient( - ssid=raw_session, - is_demo=True, - uid=72645361, - platform=1, - is_fast_history=True + ssid=raw_session, is_demo=True, uid=72645361, platform=1, is_fast_history=True ) - - print(f"โœ… Session: {client2.session_id}") - print(f"โœ… Demo: {client2.is_demo}") - print(f"โœ… UID: {client2.uid}") - print(f"โœ… Platform: {client2.platform}") - + + print(f" Session: {client2.session_id}") + print(f" Demo: {client2.is_demo}") + print(f" UID: {client2.uid}") + print(f" Platform: {client2.platform}") + formatted_message2 = client2._format_session_message() - print(f"โœ… Formatted message: {formatted_message2}") + print(f" Formatted message: {formatted_message2}") print() - + # Test 3: Verify both produce same result - print("3๏ธโƒฃ Comparing Results") - + print("Comparing Results") + # Parse the JSON parts to compare def extract_auth_data(msg): json_part = msg[10:-1] # Remove '42["auth",' and ']' return json.loads(json_part) - + auth_data1 = extract_auth_data(formatted_message) auth_data2 = extract_auth_data(formatted_message2) - + print(f"Complete SSID auth data: {auth_data1}") print(f"Raw session auth data: {auth_data2}") - + # Compare key fields fields_match = ( - auth_data1["session"] == auth_data2["session"] and - auth_data1["isDemo"] == auth_data2["isDemo"] and - auth_data1["uid"] == auth_data2["uid"] and - auth_data1["platform"] == auth_data2["platform"] + auth_data1["session"] == auth_data2["session"] + and auth_data1["isDemo"] == auth_data2["isDemo"] + and auth_data1["uid"] == auth_data2["uid"] + and auth_data1["platform"] == auth_data2["platform"] ) - + if fields_match: - print("โœ… Both methods produce equivalent authentication data!") + print(" Both methods produce equivalent authentication data!") else: - print("โŒ Authentication data mismatch!") - + print("Authentication data mismatch!") + print() - + # Test 4: Test connection with real SSID format (mock) - print("4๏ธโƒฃ Testing Connection with Complete SSID") - + print("Testing Connection with Complete SSID") + try: # This will fail with test data, but should show proper SSID handling await client1.connect() - print("โœ… Connection successful") + print(" Connection successful") except Exception as e: - print(f"โ„น๏ธ Expected connection failure with test data: {e}") - - print("\n๐ŸŽฏ SSID Format Support Summary:") - print("โœ… Complete SSID format: 42[\"auth\",{...}] - SUPPORTED") - print("โœ… Raw session ID with parameters - SUPPORTED") - print("โœ… Automatic parsing and formatting - WORKING") - print("โœ… UID and platform preservation - WORKING") - print("โœ… Fast history support - WORKING") - + print(f"Expected connection failure with test data: {e}") + + print("\nSSID Format Support Summary:") + print(' Complete SSID format: 42["auth",{...}] - SUPPORTED') + print(" Raw session ID with parameters - SUPPORTED") + print(" Automatic parsing and formatting - WORKING") + print(" UID and platform preservation - WORKING") + print(" Fast history support - WORKING") + # Show example usage - print("\n๐Ÿ“– Usage Examples:") + print("\nUsage Examples:") print("\n# Method 1: Complete SSID (recommended)") print("client = AsyncPocketOptionClient(") - print(' ssid=\'42["auth",{"session":"your_session","isDemo":1,"uid":12345,"platform":1,"isFastHistory":true}]\'') + print( + ' ssid=\'42["auth",{"session":"your_session","isDemo":1,"uid":12345,"platform":1,"isFastHistory":true}]\'' + ) print(")") - + print("\n# Method 2: Raw session with parameters") print("client = AsyncPocketOptionClient(") print(" ssid='your_raw_session_id',") @@ -120,30 +117,30 @@ def extract_auth_data(msg): async def test_real_connection_simulation(): """Simulate what a real connection would look like""" - - print("\n\n๐Ÿ”— Real Connection Simulation") + + print("\n\nReal Connection Simulation") print("=" * 40) - + # Example with real-looking SSID format realistic_ssid = '42["auth",{"session":"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6","isDemo":1,"uid":72645361,"platform":1,"isFastHistory":true}]' - + client = AsyncPocketOptionClient(ssid=realistic_ssid) - - print(f"Initialized client with parsed data:") + + print("Initialized client with parsed data:") print(f" Session: {client.session_id}") print(f" Demo: {client.is_demo}") print(f" UID: {client.uid}") print(f" Platform: {client.platform}") - + # Show what would be sent during handshake auth_message = client._format_session_message() - print(f"\nAuthentication message to be sent:") + print("\nAuthentication message to be sent:") print(f" {auth_message}") - + # Parse and display nicely json_part = auth_message[10:-1] # Remove '42["auth",' and ']' auth_data = json.loads(json_part) - print(f"\nParsed authentication data:") + print("\nParsed authentication data:") for key, value in auth_data.items(): print(f" {key}: {value}")