Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
516 changes: 510 additions & 6 deletions README.md

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions README_ENHANCED.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# PocketOption Async API - Enhanced Edition

Check BinaryOptionToolsv2: [https://github.com/ChipaDevTeam/BinaryOptionsTools-v2](https://github.com/ChipaDevTeam/BinaryOptionsTools-v2) <br>

## Support us
join PocketOption with our affiliate link: [PocketOption Affiliate link](https://u3.shortink.io/smart/SDIaxbeamcYYqB) <br>
donate in paypal: [Paypal.me](https://paypal.me/ChipaCL?country.x=CL&locale.x=en_US) <br>
help us in patreon: [Patreon](https://patreon.com/VigoDEV?utm_medium=unknown&utm_source=join_link&utm_campaign=creatorshare_creator&utm_content=copyLink) <br>

## 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
Expand Down
1,038 changes: 542 additions & 496 deletions pocketoptionapi_async/client.py

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions pocketoptionapi_async/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,7 @@ 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]:
Expand All @@ -179,6 +178,11 @@ def get_all(cls, randomize: bool = True) -> List[str]:
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"""
Expand Down Expand Up @@ -218,7 +222,7 @@ def get_demo_regions(cls) -> List[str]:
API_LIMITS = {
'min_order_amount': 1.0,
'max_order_amount': 50000.0,
'min_duration': 60, # seconds
'min_duration': 5, # seconds
'max_duration': 43200, # 12 hours in seconds
'max_concurrent_orders': 10,
'rate_limit': 100, # requests per minute
Expand Down
4 changes: 2 additions & 2 deletions pocketoptionapi_async/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ def amount_must_be_positive(cls, v):

@validator('duration')
def duration_must_be_valid(cls, v):
if v < 60: # minimum 60 seconds
raise ValueError('Duration must be at least 60 seconds')
if v < 5: # minimum 5 seconds
raise ValueError('Duration must be at least 5 seconds')
return v

class OrderResult(BaseModel):
Expand Down
99 changes: 77 additions & 22 deletions pocketoptionapi_async/websocket_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,7 @@ async def connect(self, urls: List[str], ssid: str) -> bool:
),
timeout=10.0
)

# Update connection info
# Update connection info
region = self._extract_region_from_url(url)
self.connection_info = ConnectionInfo(
url=url,
Expand All @@ -186,14 +185,13 @@ async def connect(self, urls: List[str], ssid: str) -> bool:
)

logger.info(f"Connected to {region} region successfully")

# Start message handling
# Start message handling
self._running = True

# Send initial handshake
# Send initial handshake and wait for completion
await self._send_handshake(ssid)

# Start background tasks
# Start background tasks after handshake is complete
await self._start_background_tasks()

self._reconnect_attempts = 0
Expand Down Expand Up @@ -344,27 +342,50 @@ 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"""
# Wait for initial connection message
await asyncio.sleep(0.5)

# Send handshake sequence
await self.send_message("40")
await asyncio.sleep(0.1)
await self.send_message(ssid)

logger.debug("Handshake completed")

"""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)
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:
# 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)
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:
# 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}")
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
# Start message receiving task (only start it once here)
asyncio.create_task(self.receive_messages())

async def _ping_loop(self) -> None:
Expand All @@ -390,15 +411,49 @@ async def _ping_loop(self) -> None:
except Exception as e:
logger.error(f"Ping failed: {e}")
break

async def _process_message(self, message) -> None:
"""
Process incoming WebSocket message
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')
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))
}
if "uid" in json_data:
balance_data['uid'] = json_data['uid']

logger.info(f"Balance data received: {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)

# Handle other JSON data
else:
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')
Expand Down
39 changes: 37 additions & 2 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,45 @@
from pocketoptionapi_async import AsyncPocketOptionClient
import logging
import os
logging.basicConfig(level=logging.DEBUG,format='%(asctime)s %(message)s')
import time

dotenv.load_dotenv()

ssid = (r'42["auth",{"session":"n1p5ah5u8t9438rbunpgrq0hlq","isDemo":1,"uid":72645361,"platform":1,"isFastHistory":true}]') #os.getenv("SSID")
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...")
92 changes: 92 additions & 0 deletions test_balance_fix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/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("=" * 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)

# Test connection and balance retrieval
try:
await client.connect()

if client.is_connected:
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.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")

except Exception as 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!")
else:
logger.warning("⚠️ No balance event received")

else:
logger.warning("⚠️ Connection failed (expected with test SSID)")

except Exception as e:
logger.info(f"ℹ️ Connection test: {e}")

finally:
await client.disconnect()

except Exception as e:
logger.error(f"❌ Test failed: {e}")
return False

logger.info("=" * 50)
logger.success("✅ Balance fix test completed!")
return True

if __name__ == "__main__":
# Configure logging
logger.remove()
logger.add(
lambda msg: print(msg, end=""),
format="<green>{time:HH:mm:ss}</green> | <level>{level}</level> | {message}",
level="INFO"
)

asyncio.run(test_balance_fix())
Loading